Repository navigation
feat: apply the platform model route before confirming agent/start - #4
Conversation
SDK 0.11.0 lets agent/start return the AgentModelApplied confirmation a platform-model start requires. deno.json and deno.lock pin the published 0.11.0.
A start that carries a frozen model context accepts only the declared anthropic-messages connection with no Settings. The bridge sends the adapter's own providers/set with the token as x-api-key, consumes the answer before anything reaches Ora, and pins every model alias to the frozen model; agent/start confirms the configuration only once the adapter accepted the route, and an Effect restart reapplies it.
04aa3f4 to
97c9300
Compare
The repository had no CI, so a change reached review without its type check or tests having run. None of the tasks needs the Claude CLI.
wanglongan587
left a comment
There was a problem hiding this comment.
审查版本:fa94f9714c0a422658d09eb548cfa7a891468e6d(base 8ae7e2f)。结论:Request changes。
路由形状、token 位置和 pinned adapter 的行为都核对过,是对的;但这次替代路线丢了几项 ADR 明文要求的保护,需要补回。
我核对过的
- 本地
deno task check/lint/test全部通过(31 passed)。 - pinned
@agentclientprotocol/claude-agent-acp@0.94.0(upstream.lock.json)的发布包源码:providers/set存在;programmatic settings 里apiKeyHelper: ""、settingSources: ["user","project","local"];createEnvForProvider会重置ANTHROPIC_BASE_URL/ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN/OAuth/Bedrock/Vertex 路由变量;providers/set的日志只含 apiType/baseUrl,不含 headers。因此“spawn 环境里的上游 Key 会被 provider 路由压住”这一条成立,token 只经providers/set的 header 下发也是对的。 AgentModelApplied的校验在 SDK 0.11.0 里确实会拒绝缺失/不匹配的确认。
阻塞
1. 绑定执行的模型发现仍然是“影子运行”(ADR D5)。
src/main.ts:120-130 的 onListModels 无条件调用 listClaudeModels;src/handlers/models.ts 用 spawnClaude 起一个一次性 adapter(不携带 route env),在 cwd 下建 session 再读模型选择器,缓存只按 cwd(catalogs)。D5 明确:“模型发现和别名不能扩大冻结输入……额外发现不能制造未经准入的影子运行……缓存身份至少区分执行/用户隔离范围、Plugin ID/发行、声明/配置 revision 和模型绑定,不能只有 cwd。”
在一个带 modelContext 的执行里,这个探测进程既没有平台路由,也不属于本次授权;它读到的是宿主/用户配置下的模型集合。应改为:route 存在时直接返回冻结模型(configuration.model,别名已由 env 固定),不 spawn;若确实需要探测,则必须带同一 route env,并按执行/配置 revision 而不仅是 cwd 缓存。
2. 原始 CLI 错误和 stderr 在平台会话里原样进入日志(ADR D2)。
src/services/claude-client.ts:287-290 记录非 JSON stdout 原文,:310 原样转发错误帧,:330-334 把 adapter stderr 逐行以 info 重发。D2:“临时令牌、私钥内容及带认证头的 provider 初始化材料不能进入……错误链或用户日志……原始供应商/CLI 错误不直接公开。” 虽然 pinned adapter 的 [providers/set] 日志不含 header,但 adapter/CLI 的错误体、网关 listing 失败体、claude auth 的 stderr 都可能包含私有上游信息;一旦将来包含 token,就会被持久化。恢复 #3 的 sensitive 模式:route 存在时不重发 stderr 原文,错误帧只保留数值 code + 通用 message。
3. 平台启动仍可运行另一个 adapter 版本(ADR D2)。
src/services/command.ts:65-76 仍接受 ORA_CLAUDE_ACP_BIN,:89-133 在包内没有 bundled adapter 时回退到 PATH。D2:“身份/声明/契约不匹配必须在首次模型调用前拒绝,不能启动另一个版本、直连上游或用部署 Key 重试。” providers/set 的语义依赖 0.94.0 的程序化设置行为;换一个版本要么改变语义,要么每次启动都 AGENT_UNUSABLE。有 modelContext 时只能启动包内 bundled adapter(#3 的 spawnClaudeModel),否则以 AGENT_UNUSABLE 失败;不要走 override/PATH 回退。
4. 绑定执行仍转发模型/认证变更帧(ADR D4/D5)。
src/services/claude-client.ts:204-230 把所有 host→agent 帧直接写入 adapter,没有过滤 session/set_model、session/set_config_option(model)、providers/*、authenticate、logout。D5:“会话配置选项不能扩大允许集合”;D4:“在途会话只能使用创建时的冻结输入。” 网关会逐次拒绝非冻结模型,但 adapter 这一层应当直接以 -32602 拒绝,而不是把变更送到 CLI 再失败。至少拒绝把 model 改成冻结值以外的 set_model/set_config_option;providers/*、authenticate、logout 一并拒绝。
should-fix
- Skill barrier 在重启失败时不会释放(
src/handlers/effects.ts:217-224):await this.#client.start(cwd)抛错时#release()不执行,队列里的session/prompt会一直被挡住。route 拒绝/超时是这次新增的失败模式,建议try/finally释放(或先释放再抛)。 CLAUDE_CONFIG_DIR没有显式固定:model-route.ts:73-84只设别名和两个 flags;Node 会把HOME设为私有目录,且createEnvForProvider会重置ANTHROPIC_*,但被继承的CLAUDE_CONFIG_DIR仍可把 adapter 指回宿主配置目录,settingSources: ["user","project","local"]和claude auth status就会读到宿主登录。建议显式设成一个执行私有目录(这需要 Node 侧的 env 清理一起,见 desktop#708)。- 冻结的
maxTokens没下发:model-route.ts未设置CLAUDE_CODE_MAX_OUTPUT_TOKENS(#3 设过),会话可请求超过冻结模型声明的输出量。 - token 未拒绝控制字符:
model-route.ts:51-56只检查非空;adapter 用${key}: ${value}拼ANTHROPIC_CUSTOM_HEADERS,\r/\n会注入或破坏 header。#3 检查过[\r\n]。 #running在 route 应用前就为 true(claude-client.ts:135vs:140):effects.verifyReady可能在重启应用路由期间看到“已就绪”。建议单独跟踪 route 已应用状态。- 失败分类:route 拒绝/超时/网关 listing 失败全部变成
AGENT_UNUSABLE(claude-client.ts:184-190)。D2 要区分配置不完整、不兼容、授权拒绝和准备失败;网关 listing 的 5xx/超时更接近可重试的准备失败。providers/set会先GET <baseUrl>/v1/models(pinned adapter 源码),这条真实路径在测试里没覆盖,建议在真实验收里确认网关对/v1/models的响应(4xx 可接受,5xx/超时会让每次平台启动失败)。 - 网关认证优先级:adapter 同时发
x-api-key和Authorization: Bearer acp-proxy。PR 声称网关优先x-api-key(cloud#102),请在真实验收里确认;如果网关优先 Authorization,Messages 和 listing 都会失败。 configuration.target/pluginId自检:#3 检查过target === Deno.build.target与插件 ID;Node 会校验,但 adapter 侧是廉价防御。- 测试:
tests/model_route_test.ts用的是 ScriptedAdapter;没有覆盖真实 0.94.0 的/v1/models、超时、请求 id 不匹配、CRLF、productiononStart/effects.reactivate接线。另 AGENTS.md“token 从不进入日志”的表述与第 2 条冲突,需改。
未验证
没有安装真实的 Claude Code CLI,也没有接真实 model-gateway 跑会话;providers/set 的真实上游行为、/v1/models、认证头优先级仍需在真实验收运行里证明(本运行已列)。此结论只针对 fa94f97。
wanglongan587
left a comment
There was a problem hiding this comment.
审查版本:3ea3029248fa9fc8d8cb780670bcb567beab7b0f(base 8ae7e2f,7 commits,13 files)。结论:Request changes。
上一轮四个阻塞中,1、3、4 已在新 head 上修复;阻塞 2(D2 stderr/错误不得原文进入日志或用户可见)没有按上一轮要求修复,只做了精确串脱敏。另新增一项需要 owner 决定:本 PR 的 providers/set 机制会把运行令牌带进 Claude CLI 的 --settings argv(bug #452),不符合 ADR D4「临时令牌仅在内存或本执行的环境中」。
阻塞 2 未修复:route 存在时 stderr / 非协议输出 / 上游错误原文仍会外泄
已做的:JSON-RPC error 帧保留数值 code、替换 message、丢弃 data(src/services/model-guard.ts:160-176),有测试。
未做的:
- route 存在时 adapter 的每一行 stderr 仍以 info 重发到
claude-agent-acptarget,仅替换 token/baseUrl/host/hostname(src/services/claude-client.ts:374-386)。 - 非 JSON stdout 仍记录 scrub 后前 512 字符(
src/services/claude-client.ts:320-365)。 - CLI/上游错误文本可能作为
session/update的内容转发给 Ora;scrub只做四个精确串替换,上游错误 body、上游 host/path、宿主路径原样保留(model-guard.ts:98-107与claude-client.ts:429-437)。
我用 pinned @agentclientprotocol/claude-agent-acp@0.94.0 + 假 gateway 复现:gateway 返回 503 且 body 回显 token 与其他上游信息时,token 被替换,但 body 其余内容和 upstream host=... path=... 仍进入 adapter stdout/stderr,插件会原样转发/记录。ADR D2 明文「原始供应商/CLI 错误不直接公开」,上一轮要求的是恢复 #3 的 sensitive 模式(route 存在时不重发 stderr 原文)。本次 AGENTS.md 新增的 "Diagnostics stay in the log rather than being dropped" 是策略变更,但 issue 里没有 owner 授权,且没有测试断言上游错误体/宿主路径被抑制。
要求:恢复敏感模式——route 存在时 adapter stderr 与非协议 stdout 不记录原文,错误只保留数值 code + 通用 message;或先取得 owner 对 "secret-scrub 而非丢弃原文" 的明确接受,并补「上游错误体/上游 host/path/宿主路径不得进入日志或用户可见内容」的测试与文档。补完后这条可转 Approve。
需要 owner 决定:token 进入 Claude CLI 的 argv(D4)
providers/set 的 x-api-key 经 pinned adapter 的 createEnvForProvider 拼成 ANTHROPIC_CUSTOM_HEADERS,并入 CLI 进程 env 和 programmatic settings 的 env(dist/acp-agent.js:8427-8451、:9347-9402);SDK 0.3.303 再把对象 settings 序列化成 --settings <json> 放进 CLI argv(#452 已按固定版本源码确认)。token 在本插件传给 adapter 的 spawn env/日志里确实没有,问题在 adapter 内部对 CLI 的传递;但 ADR D4 要求「临时令牌仅在内存或本执行的环境中」,argv 既不是内存也不是本执行环境。
#452 已开成 P1 bug,但它不改变本 PR 的机制与描述:PR 描述和 AGENTS.md:369-371 仍写「token only as the x-api-key header / never placed in the adapter's environment or logged」,与 pinned adapter 的实际行为不符。请:
- 先改正 PR 描述和 AGENTS.md(如实说明 adapter 会把 header 并入 CLI env/settings,argv 可见性由 #452 承接);
- 由 owner 明确 #452 是「合并前门禁」还是「合并后修复」;若合并后修复,需在 PR 描述/运行 issue 写明并由哪个 issue 承接。
在 owner 明确前,我不会给本 PR Approve。
should-fix(本轮至少处理或明确承接)
- barrier 在重启失败时不释放:
src/handlers/effects.ts:217-224直接await this.#client.start(cwd),抛错时#release()不执行。本次新增的 route 拒绝/超时(AGENT_UNUSABLE、不重试)会让#held里的session/prompt永久滞留。建议try/finally释放并补测试。 - 失败分类:
claude-client.ts:184-208把 route 的所有失败(拒绝、10s 超时、gateway listing 5xx/超时)都变成AGENT_UNUSABLE。ADR D2 要求区分配置不兼容、授权拒绝和准备失败;gateway/v1/models的 503 是可重试的准备失败,不是不可用。至少把 listing 5xx/超时与「adapter 拒绝该配置」分开,或记录取舍。 - 冻结
maxTokens未下发:src/services/model-route.ts未设置CLAUDE_CODE_MAX_OUTPUT_TOKENS,settings 仍可抬高输出上限,与冻结模型元数据不一致。 - token 未拒绝控制字符:
model-route.ts:57只查非空;实测 CRLF token 会让 adapter 失败关闭,但仍建议补[\r\n]检查。 #running早于 route 应用:claude-client.ts:148先置 running,:153才#applyRoute;verifyReady可能在路由未应用时看到「已就绪」。建议单独跟踪 route 已应用状态。- target/pluginId 自检:
modelRoute未检查configuration.target === Deno.build.target与 pluginId;Node 侧检查不能替代这一廉价防御。 - 测试:模型列表和 adapter-entry 两处是读源码文本断言,不是行为测试;没有走 production
onListModels/reactivate的用例;没有 stderr/上游 body 的负向断言;_providers/*未加对称防御(当前 pinned 版本不可利用)。 - PR 描述数字:Evidence 写「new, 6 tests / full suite 31」,实际
deno task test为 41 passed(其中tests/model_route_test.ts16 个);CI 实际是 1 个 job、3 个 step。请更正。
已核对
- 阻塞 1:route 存在时
onListModels直接返回冻结模型、不 spawn(src/main.ts:120-133),选择器经model-guard收窄;SDK 0.11.0verifyModelApplied精确匹配。残留:start 前调用仍可能探测、缓存仍只按 cwd,记为 should-fix。 - 阻塞 3:route 路径只走
spawnBundledClaude(claude-client.ts:405-408、command.ts:90-108),不读ORA_CLAUDE_ACP_BIN/PATH;adapter-entry.mjs在ORA_CLAUDE_BUNDLED_CLI_ONLY=1时先于ORA_CLAUDE_BIN/本地安装返回 bundled CLI。 - 阻塞 4:
authenticate/logout/auth/*/_auth/*/providers/*拒绝,非冻结set_model/set_config_option(model)以-32602拒绝,session 选项剥离,session/new|load|resume|fork均覆盖,barrier 回放也走writeAcp。 - 真实 pinned adapter:
providers/set可在initialize前发送并返回{};listing 为GET /runtime/anthropic/v1/models?limit=1000并带x-api-key;404 成功、503 失败;CRLF token 失败关闭。 - cloud main 的 gateway:anthropic 路由优先
X-Api-Key,/v1/models只放行 query 空或limit=1000并返回冻结模型(cloud#102 已合并)。 - 本地
deno task check/lint/test通过(41 passed);LF 归一后deno fmt --check通过(CI 未跑 fmt)。git diff --check通过。
未验证
真实 bundled adapter + 原生 Claude CLI + 真实 Node/model-gateway 的端到端;effect reactivate 的 production 路径;Ora ACP client 对 authenticate/providers/list 被拒后的启动行为;#452 的真机 ps 复现。
交付物 / ADR
13 个文件全是交付物,7 个 commit 拆分合理;Refs ora-space/circus#354 与合并顺序正确。ADR 保持 approved 的判断正确:本 PR 是 adapter 半边(D3–D5),真实双用户/真实 CLI 验收与 implemented specs PR 仍由本运行后续承接。
修完请按新 head 重新请审。
On a platform route the adapter's stderr is no longer republished, only counted, and a stdout line that is not a frame is logged by length. Claude Code echoes a failed request with the upstream's body, address and host paths, which scrubbing known secrets left in place (ADR D2). A route the adapter refuses (invalid_params, method_not_found) stays AGENT_UNUSABLE; a gateway model listing that failed, any other error and a timeout now fail with -32603, which Ora retries.
A process waiting for providers/set was already running, so verify_ready could pass before the route existed. ClaudeClient.ready tracks the applied route per generation, and readiness reads it.
A restart that failed left the barrier up and every held prompt waiting for the life of the process. The barrier is now released in a finally; held turns that no adapter can receive are dropped, as a frame arriving while the adapter is down is.
Refuse a context frozen for another plugin or target, a token with a control character (the adapter would only fail later, retryably) and a non-positive maxTokens, and pass the frozen maxTokens as CLAUDE_CODE_MAX_OUTPUT_TOKENS.
The pinned adapter serves no _providers/* method, but the guard now covers the extension spelling the same way it covers _auth/*.
… source agent/list_models moves into a handler a test can call with a started client, and the compiled adapter's CLI ladder moves into scripts/resolve-claude-cli.mjs, which packaging stages beside the entry point and tests run against real files.
Correct the claim that the token stays in the providers/set header: the pinned adapter merges it into Claude Code's environment and programmatic settings, which the SDK passes as a --settings argument.
wanglongan587
left a comment
There was a problem hiding this comment.
审查版本:014d0d57094ad0d00ce122f329454bd43f8b44ee(base 8ae7e2f,14 commits,18 files)。结论:Approve(无阻塞)。
上一轮 Request changes 的阻塞 2 与全部 should-fix 已在新 head 上落地,本轮 7 个新 commit(8b3d905、74b58e7、ea3fde8、7eb2d79、1a9577f、c7c9419、014d0d5)未发现回归。ADR 依据:specs 3c41a34699d71b1111eb01788bec874dd3516c17 的 plugin-owned-model-adapters,状态仍为 approved。
阻塞 2(D2 敏感输出)——已修复
- route 下 adapter 的 stderr 不再逐行重发:
src/services/claude-client.ts:430-455只在流结束时记录lines计数;本地(verbatim)仍逐行记录(:442)。 - 非 JSON stdout 行只记长度:
claude-client.ts:374-386(sensitive 分支记{pid, length})。 - 错误帧只保留 code + 通用 message:
src/services/model-guard.ts:155-184(data丢弃)。 - 上游错误 body、上游 host/path、宿主路径、网关、令牌都进不了日志:负向测试
tests/model_route_test.ts:635-665注入这些串并断言所有日志记录里都不出现;本地 adapter 诊断仍原样保留(:667-686)。 - ACP 帧仍转发,令牌/网关 baseUrl/host/hostname 替换为
[redacted](model-guard.ts:91-106、claude-client.ts:494-502);AGENTS.md 已写明「agent 消息引用的上游错误无法与模型回答区分,因此脱敏不删除」。 - 我做了变异验证(临时副本,未改 worktree):去掉 stderr 抑制、恢复记录非 JSON 内容后,对应测试各失败一条(45 passed / 1 failed);改回即恢复全绿。
should-fix 1–9——全部落地
- barrier:重启放进
try/finally,失败也释放;不可投递的 held 帧按停机规则丢弃(src/handlers/effects.ts:215-233、:294-303;测试tests/main_test.ts:502-530,变异验证有效)。 - 失败分类:
-32601/-32602→AGENT_UNUSABLE并停 adapter,其余错误(含 gateway listing 503)与 10 秒超时 →-32603(claude-client.ts:20-32、:224-244)。已对照 pinnedclaude-agent-acp@0.94.0源码(invalid_params,listing 失败internalError)与宿主重试分类(-32603 → Retryable)。 maxTokens经CLAUDE_CODE_MAX_OUTPUT_TOKENS下发,非正数拒绝(src/services/model-route.ts:66-77、:102)。- 令牌含控制字符拒绝(
model-route.ts:19-20、:73)。 ClaudeClient.ready:路由被接受后才 ready,readiness 与 Effect 回放改读它(claude-client.ts:115-122;测试model_route_test.ts:707-724)。- 冻结 target 必须等于
Deno.build.target、pluginId 必须是本插件(model-route.ts:68-69)。 - 行为测试:
agent/list_models走 handler 且断言不 spawn 探测进程(src/handlers/models.ts:58-73、测试:552-572);CLI 解析梯子抽到scripts/resolve-claude-cli.mjs、打包复制(scripts/package.ts:227-232)、测试在真实文件上跑(:438-467);两处源码文本断言已替换为行为测试。 _providers/*与providers/*同等拒绝(model-guard.ts:36-40)。- 测试 46(route 20 + main 26)、CI 单 job(本 head
checkpass)。
#452 / D4
owner @EricWvi 已定 #452 为发布门禁(2026-10-21T12:46Z,见 circus#452),不是合并门禁。PR 描述与 AGENTS.md 已如实写明:令牌会被 pinned adapter 并入 CLI 进程环境与 programmatic settings 的 env,再由 SDK 以 --settings 传给 CLI,因此对能列进程的用户可见。与 0.94.0 源码一致,该说明需保留到 #452 修复。
非阻塞(建议顺手改,不影响合并)
- PR 描述里「whether this blocks merging is an owner decision」已过时,改为 owner 的发布门禁结论。
- 描述里的 fmt 证据字面不成立:裸
deno fmt --check在干净 LF 检出上仍会因logo.svg、assets/config.json(formattask 范围外)失败;仓库门禁应写deno task format --check。 - pluginId 只比较
/最后一段,other/ora-space.claude也会被接受(model-route.ts:68)。 - 10 秒超时与
-32601分支没有自动化测试(claude-client.ts:231-244)。 - D5 的「start 前本地发现 + 缓存按 cwd」仍在(作者已在描述声明范围外,建议挂 follow-up)。
- AGENTS.md 说「One test reads
src/main.tsas text」,实际还有tests/main_test.ts:255-270读scripts/adapter-entry.mjs。 #release重放中抛错会留下#held(effects.ts:294-318)。- 测试
waitFor上限约 1 秒(tests/model_route_test.ts:875-881),慢 CI 可能闪断。 - 路由下 bundled CLI 缺失时的错误未归类为
AGENT_UNUSABLE。
验证与未覆盖
- 本地(worktree detached 在审查 head):
deno task check、deno task lint、deno task test通过(46 passed / 0 failed);变异测试见上。 - CI:
checkpass(run 39598977974,head 014d0d5),PRMERGEABLE/CLEAN。 - 未跑真实 bundled adapter/CLI 与真实 gateway;真实接受验收仍留给运行后续步骤(PR 描述也这样声明)。
- 7 个新 commit 各自单一目的,隔离树 check/lint/test 各自通过。
The adapter from #4 changes how the plugin starts Claude, so the release that carries the Agent declaration needs a version of its own. The next package must differ from the 0.3.23 already published without a declaration.
Summary
A Node start can now carry a frozen
modelContext(ora-space/desktop#703). This plugin turns it into Claude Code's routing and confirms it before ACP begins:providers/set: the pinnedclaude-agent-acp0.94.0 puts a provider route into Claude Code's programmatic settings tier withapiKeyHelpercleared, so a repository's.claude/settings.jsoncannot restore anotherANTHROPIC_BASE_URLand send the token there. Setting the base URL in the spawn environment would not have that property.x-api-keyheader of that one request (the gateway reads it first on its Messages route; feat(model-gateway): support pinned Claude Messages requests cloud#102), never in the adapter's spawn environment or a log line, and the bridge consumes the answer by its own id so Ora never sees either frame. The pinned adapter, however, merges the header into Claude Code's process environment (ANTHROPIC_CUSTOM_HEADERS) and into the programmatic settings'env, and the Claude Agent SDK passes those settings to the CLI as a--settingsargument. The token is therefore visible in the CLI's command line to anyone who can list that process. It is valid for one execution only. The owner decided this gates the release, not this merge: the--agent-configurationrelease switch and Cloud'splugins.personal_agent_targetstay off until it is fixed, and this description andAGENTS.mdkeep saying so until then.opus/sonnet/haikualiases are pinned to the frozen model; an override fails closed at the gateway, which serves only the frozen model id.reactivaterestart reapplies the same route;ClaudeClient.ready(notrunning) is what readiness and the Effect replay read, so an adapter still waiting forproviders/setis not reported ready. A restart that fails still lowers the Skill barrier.Protections carried over from the Draft #3 (same scope, different route):
ModelGuardon every frame to the adapter refusesauthenticate,logout,auth/*,_auth/*,providers/*and_providers/*(0.94'slogoutandproviders/disablewould clear the route), refuses switching to any model but the frozen one, and stripsmodel,fallbackModel,env,settings,extraArgsand executable options from session setup_meta.claudeCode.options.ORA_CLAUDE_BUNDLED_CLI_ONLY=1, noORA_CLAUDE_ACP_BIN/ORA_CLAUDE_BIN/PATH).agent/list_modelsand every model selector report only the frozen model, without a native-credential probe.codebut losemessageanddata. ACP frames are still forwarded with the token, gateway URL, host and hostname replaced by[redacted]; an agent message that quotes an upstream error is scrubbed but not removed, since nothing distinguishes it from the model's answer.Depends on: plugin SDK 0.11.0 (ora-space/desktop#703), merged and published on JSR;
deno.jsonanddeno.lockpin it. The repository had no CI, so this PR also adds.github/workflows/ci.yml(one job:check,lint,test). This PR does not enable--agent-configurationinrelease.yml.Merge order: ora-space/cloud#106 → ora-space/desktop#708 → this PR (no code dependency on them; nothing sends a model context until the Node half is deployed).
ADR: plugin-owned-model-adapters remains approved; this is the Claude adapter half of D3–D5. Real acceptance with the bundled adapter/CLI pair is still open.
Not addressed here: a picker opened before
agent/starton a fresh plugin process still runs local discovery (the route is only known at start, and the Node decides which pickers a configured run shows); discovery's cache stays keyed by workspace.Refs ora-space/circus#354
Evidence
deno task test: 46 passed (tests/model_route_test.ts20,tests/main_test.ts26). Route-specific coverage includes: refusal of another protocol, plugin, target, a Settings key, a token with CR/LF or NUL, a non-positivemaxTokensand malformed proxies, none echoing the token; confirmation only afterproviders/setwas accepted; an adapter waiting for its route being running but not ready; a refused route failing withAGENT_UNUSABLEand a failed listing with-32603, both stopping the adapter; upstream bodies, upstream host/path, host paths, the gateway and the token absent from every log record on a route, while a local adapter's diagnostics stay verbatim;agent/list_modelsanswering the frozen model through the production handler with no probe spawned; the CLI resolver run against real files, choosing only the bundled CLI on a route; a failed reactivation restart lowering the barrier.deno task check,deno task lintanddeno task format --checkpass on each commit (a baredeno fmt --checkalso flags the untouchedlogo.svgandassets/config.json, whose bytes the declaration digest pins).providers/setbehaviour, its error codes and the listing failure path were read from the pinned@agentclientprotocol/claude-agent-acp@0.94.0package. It has not been exercised against the real bundled binaries here.Merge Danger
Door: two-way
Blast Radius: agent-start
Only starts that carry a model context change behaviour; none do until the declaration ships.