Skip to content

feat: apply the platform model route before confirming agent/start - #4

Merged
EricWvi merged 14 commits into
ora-space:mainfrom
ObsisMc:feat/354-claude-model-adapter
Oct 21, 2026
Merged

EricWvi merged 14 commits into
ora-space:mainfrom
ObsisMc:feat/354-claude-model-adapter

Conversation

@ObsisMc

@ObsisMc ObsisMc commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

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:

agent/start + modelContext
  → accept only this plugin and target, anthropic-messages, no Settings, an https …/v1 proxy,
    a token without control characters, a positive maxTokens
  → spawn the bundled claude-agent-acp with every model alias pinned to the frozen model
    and CLAUDE_CODE_MAX_OUTPUT_TOKENS = maxTokens
  → providers/set {apiType: anthropic, baseUrl: …/runtime/anthropic, headers: {x-api-key}}
  → accepted → return AgentModelApplied
    refused (invalid_params / method_not_found) → AGENT_UNUSABLE, adapter stopped
    anything else or no answer in 10 s (e.g. the gateway's model listing returned 503) → -32603, retried by Ora
  • Why providers/set: the pinned claude-agent-acp 0.94.0 puts a provider route into Claude Code's programmatic settings tier with apiKeyHelper cleared, so a repository's .claude/settings.json cannot restore another ANTHROPIC_BASE_URL and send the token there. Setting the base URL in the spawn environment would not have that property.
  • Where the token goes: this plugin passes it only as the x-api-key header 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 --settings argument. 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-configuration release switch and Cloud's plugins.personal_agent_target stay off until it is fixed, and this description and AGENTS.md keep saying so until then.
  • Main, small/fast, subagent and opus/sonnet/haiku aliases are pinned to the frozen model; an override fails closed at the gateway, which serves only the frozen model id.
  • An Effect reactivate restart reapplies the same route; ClaudeClient.ready (not running) is what readiness and the Effect replay read, so an adapter still waiting for providers/set is not reported ready. A restart that fails still lowers the Skill barrier.
  • A start without a model context is unchanged.

Protections carried over from the Draft #3 (same scope, different route):

  • A ModelGuard on every frame to the adapter refuses authenticate, logout, auth/*, _auth/*, providers/* and _providers/* (0.94's logout and providers/disable would clear the route), refuses switching to any model but the frozen one, and strips model, fallbackModel, env, settings, extraArgs and executable options from session setup _meta.claudeCode.options.
  • A routed execution spawns only the bundled adapter and CLI (ORA_CLAUDE_BUNDLED_CLI_ONLY=1, no ORA_CLAUDE_ACP_BIN/ORA_CLAUDE_BIN/PATH).
  • agent/list_models and every model selector report only the frozen model, without a native-credential probe.
  • Sensitive output, as in feat(claude): apply frozen platform model bindings #3: on a route the adapter's stderr is not logged (only a line count), a non-frame stdout line is logged by length only, and adapter error responses keep their code but lose message and data. 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.json and deno.lock pin 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-configuration in release.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/start on 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.ts 20, tests/main_test.ts 26). Route-specific coverage includes: refusal of another protocol, plugin, target, a Settings key, a token with CR/LF or NUL, a non-positive maxTokens and malformed proxies, none echoing the token; confirmation only after providers/set was accepted; an adapter waiting for its route being running but not ready; a refused route failing with AGENT_UNUSABLE and 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_models answering 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 lint and deno task format --check pass on each commit (a bare deno fmt --check also flags the untouched logo.svg and assets/config.json, whose bytes the declaration digest pins).
  • providers/set behaviour, its error codes and the listing failure path were read from the pinned @agentclientprotocol/claude-agent-acp@0.94.0 package. 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.

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.
@ObsisMc
ObsisMc force-pushed the feat/354-claude-model-adapter branch from 04aa3f4 to 97c9300 Compare October 21, 2026 07:19
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 wanglongan587 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查版本: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:135 vs :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、production onStart/effects.reactivate 接线。另 AGENTS.md“token 从不进入日志”的表述与第 2 条冲突,需改。

未验证

没有安装真实的 Claude Code CLI,也没有接真实 model-gateway 跑会话;providers/set 的真实上游行为、/v1/models、认证头优先级仍需在真实验收运行里证明(本运行已列)。此结论只针对 fa94f97。

@wanglongan587 wanglongan587 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查版本: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-acp target,仅替换 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 的实际行为不符。请:

  1. 先改正 PR 描述和 AGENTS.md(如实说明 adapter 会把 header 并入 CLI env/settings,argv 可见性由 #452 承接);
  2. 由 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.ts 16 个);CI 实际是 1 个 job、3 个 step。请更正。

已核对

  • 阻塞 1:route 存在时 onListModels 直接返回冻结模型、不 spawn(src/main.ts:120-133),选择器经 model-guard 收窄;SDK 0.11.0 verifyModelApplied 精确匹配。残留: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.
@ObsisMc
ObsisMc requested a review from wanglongan587 October 21, 2026 12:37

@wanglongan587 wanglongan587 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

审查版本: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——全部落地

  1. barrier:重启放进 try/finally,失败也释放;不可投递的 held 帧按停机规则丢弃(src/handlers/effects.ts:215-233、:294-303;测试 tests/main_test.ts:502-530,变异验证有效)。
  2. 失败分类:-32601/-32602 → AGENT_UNUSABLE 并停 adapter,其余错误(含 gateway listing 503)与 10 秒超时 → -32603(claude-client.ts:20-32、:224-244)。已对照 pinned claude-agent-acp@0.94.0 源码(invalid_params,listing 失败 internalError)与宿主重试分类(-32603 → Retryable)。
  3. maxTokens 经 CLAUDE_CODE_MAX_OUTPUT_TOKENS 下发,非正数拒绝(src/services/model-route.ts:66-77、:102)。
  4. 令牌含控制字符拒绝(model-route.ts:19-20、:73)。
  5. ClaudeClient.ready:路由被接受后才 ready,readiness 与 Effect 回放改读它(claude-client.ts:115-122;测试 model_route_test.ts:707-724)。
  6. 冻结 target 必须等于 Deno.build.target、pluginId 必须是本插件(model-route.ts:68-69)。
  7. 行为测试: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);两处源码文本断言已替换为行为测试。
  8. _providers/* 与 providers/* 同等拒绝(model-guard.ts:36-40)。
  9. 测试 46(route 20 + main 26)、CI 单 job(本 head check pass)。

#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 修复。

非阻塞(建议顺手改,不影响合并)

  1. PR 描述里「whether this blocks merging is an owner decision」已过时,改为 owner 的发布门禁结论。
  2. 描述里的 fmt 证据字面不成立:裸 deno fmt --check 在干净 LF 检出上仍会因 logo.svg、assets/config.json(format task 范围外)失败;仓库门禁应写 deno task format --check。
  3. pluginId 只比较 / 最后一段,other/ora-space.claude 也会被接受(model-route.ts:68)。
  4. 10 秒超时与 -32601 分支没有自动化测试(claude-client.ts:231-244)。
  5. D5 的「start 前本地发现 + 缓存按 cwd」仍在(作者已在描述声明范围外,建议挂 follow-up)。
  6. AGENTS.md 说「One test reads src/main.ts as text」,实际还有 tests/main_test.ts:255-270 读 scripts/adapter-entry.mjs。
  7. #release 重放中抛错会留下 #held(effects.ts:294-318)。
  8. 测试 waitFor 上限约 1 秒(tests/model_route_test.ts:875-881),慢 CI 可能闪断。
  9. 路由下 bundled CLI 缺失时的错误未归类为 AGENT_UNUSABLE。

验证与未覆盖

  • 本地(worktree detached 在审查 head):deno task check、deno task lint、deno task test 通过(46 passed / 0 failed);变异测试见上。
  • CI:check pass(run 39598977974,head 014d0d5),PR MERGEABLE/CLEAN。
  • 未跑真实 bundled adapter/CLI 与真实 gateway;真实接受验收仍留给运行后续步骤(PR 描述也这样声明)。
  • 7 个新 commit 各自单一目的,隔离树 check/lint/test 各自通过。

@EricWvi
EricWvi merged commit 2146dfb into ora-space:main Oct 21, 2026
1 check passed
EricWvi pushed a commit that referenced this pull request Oct 22, 2026
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants