Skip to content

fix: register manifest-advertised conditional tools in the opencode V2 lane (#2443) - #2444

Merged
ranxianglei merged 1 commit into
masterfrom
2026-10-09_opencode-ccr-retrieve
Oct 10, 2026
Merged

ranxianglei merged 1 commit into
masterfrom
2026-10-09_opencode-ccr-retrieve

Conversation

@ranxianglei

Copy link
Copy Markdown
Owner

What / why

The opencode V2 lane (src/agent/opencode-v2.ts) registered only its static bundled list — the 5 core ACP tools + absorb — and never consulted the proxy's plugin manifest. That list has no acp_retrieve. So in plugin mode with CCR enabled, an [acp-stored] placeholder advertised acp_retrieve("mNNNNN") as its restore path, but the tool was not in the model's catalog — an unreachable pointer. retrieveCalls stayed at 0 while storedBytes grew; the only workaround was ccr.enabled:false.

The dsh and pi lanes already consume GET /__bili/plugin/manifest and register its tool surface (#1192); the opencode V2 lane was the outlier that didn't. This makes it do the same.

How

On the first routed request — when state.proxyBase becomes known (native bootstrap / per-request routing discover it lazily, so setup-time isn't reliable) — fetch the manifest once and register whatever it advertises beyond the static set:

  • single-flight + retry cooldown (TOOL_DELTA_RETRY_MS = 15000, pi/dsh parity);
  • inert-degrading: a failed/unreachable manifest leaves the conditional tools unregistered (identical to today's behavior) and retries on the next routed request;
  • the always-on core keeps its exact bundled schemas (the existing schema-parity test is untouched); only the advertised delta is added.

In practice the delta is acp_retrieve while CCR is armed and acp_rule while rules are on. The executor is factored into makeToolExecutor(toolName) shared by both the static and delta registrations (behavior unchanged).

Behavior change (disclosed)

  • Old: opencode V2 plugin mode never registered acp_retrieve / acp_rule; CCR placeholders pointed at an unreachable tool.
  • New: those tools are registered (from the manifest) on the first routed request when their feature is enabled. CCR restore becomes reachable; no config change required.
  • No effect on proxy mode, other lanes, or sessions where CCR/rules are off (delta empty → nothing added).

Config surface

None. No new env var, config field, or CLI flag. Uses the existing ccr.* / rule config the manifest already reflects.

Tests

New regression test (tests/opencode-v2.test.ts): a fake proxy serving a manifest that advertises acp_retrieve → asserts the base set is intact before traffic, acp_retrieve appears exactly once after the first routed request with the faithful production schema/options, and its executor forwards to the live dispatcher. Existing suite (incl. the inert/kill-switch/no-proxy paths that now exercise the degrade branch) stays green. Targeted run: 32/32; typecheck clean.

Needs human review (per §7.4 — load-bearing tool-surface change)

One assumption worth confirming against real OpenCode 2.0.x: the fix issues a second ctx.tool.transform(...) call (after the synchronous setup-time one) to add the delta. This relies on the transform editor accumulating / upserting by tool name across calls. Multi-plugin coexistence implies accumulation (a replace would stomp other plugins' tools), and a name-keyed catalog upserts rather than duplicating — so this should hold — but it is not pinned by any in-repo fixture. If real 2.0.x instead replaces the plugin's tool set on a second call, the delta registration would need to re-add the base set too (or move to a reload-based refresh). Please sanity-check on a live host before merge.

Closes #2443

@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

📦 Built Package Artifact

Branch: 2026-10-09_opencode-ccr-retrieve (410ea46)

Option A — Install this exact build (recommended)

npm install -g billion-context@0.0.2772-pr.2444.b0884f6

This exact version is immutable and reproducible. The rolling pr tag tracks the newest test build across all PRs.

Option B — Download artifact

  1. Download the artifact from the Actions run
  2. Extract the tarball and install:
tar xzf billion-context-pr2444.tgz
npm install -g package

Install instructions are refreshed on each push.

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 已实现并开 PR:#2445(Closes #2443)。已 rebase 到当前 master,与 #2399 的 fork-adoption seam 在同一 hook 内做了干净 union(两者共存,ensureToolDelta() fire-and-forget 在前、awaited fork-adopt 在后)。定向测试 32/32 全绿、typecheck 干净;全量回归交给 CI。

八问结论:

  1. 是什么问题:opencode V2 lane 只注册了静态 bundled 工具表(5 个核心 ACP + absorb),从不读 proxy 的 plugin manifest;该表里没有 acp_retrieve。于是 plugin 模式开 CCR 时,[acp-stored] 占位符指向的 acp_retrieve("mNNNNN") 恢复路径在模型目录里根本不存在——一个不可达指针。(dsh / pi lane 早已消费 manifest,V2 是唯一漏掉的 outlier。)
  2. 什么原因导致的:lane 注册面与 proxy manifest 脱节。store 端按 config-only gate 无条件写占位符、plugin 模式又从不注入工具、而 V2 lane 又不查 manifest——三道门没对齐。根因是 V2 lane 缺「按 manifest 补注册条件工具」这一步,不是占位符文案错。
  3. 影响范围与严重程度:仅 opencode V2 plugin 模式 + CCR(或 rules)开启的会话。症状是 CCR restore 不可达(retrieveCalls 恒 0、storedBytes 持续增长),非崩溃,有 workaround(ccr.enabled:false)。proxy 模式、其他 lane、CCR/rules 关闭的会话不受影响(delta 为空 → 不添加)。
  4. 怎么修复的:首个 routed request(state.proxyBase 已知时)拉一次 manifest,把其中超出静态集的工具作为 delta 注册;single-flight + 15s 冷却重试(pi/dsh parity);inert-degrading(manifest 失败/不可达 → 保持现状不注册,下个请求再试);核心集 schema 原样不动(schema-parity 测试未碰);executor 抽成 makeToolExecutor(toolName) 供静态与 delta 共用(行为不变)。
  5. 是否有回归问题:核心 always-on 工具的 schema / options / executor 逐字节不变(既有 schema-parity 测试原样通过);delta 只在 manifest 显式声明且不在静态集时追加;kill-switch / no-proxy / inert 路径走 degrade 分支,行为等同修复前。逐项过:语义漂移—无;fail-fast 变吞错—无(degrade 是惰性保留旧行为,非吞错);阈值/时序/输出格式—无变化。
  6. 是否破坏缓存:不涉及——不改 prefix cache、重试游标、去重键;仅新增一次惰性 manifest 拉取 + 工具目录 upsert。
  7. 是否改了设计方向:否。让 V2 lane 对齐 dsh/pi 已有的「manifest 驱动工具面」契约,属补齐 outlier,不是转向。
  8. 是否引入了新的配置:无。没有新 env / config / CLI flag;复用 manifest 已反映的既有 ccr.* / rule 配置。

需人工审核(§7.4 load-bearing 工具面变更):本修复在同步 setup-time 那次之外第二次调用 ctx.tool.transform(...) 追加 delta,依赖 transform editor 按名累积/upsert。多插件共存语义暗示是累积(replace 会踩踏别家工具),name-keyed catalog 也是 upsert——应当成立,但仓库内没有 fixture 钉死这条假设。请在真实 OpenCode 2.0.x host 上 sanity-check 后再合:若实际是「第二次调用替换整个插件工具集」,则 delta 注册需连同 base 一起重加(或改 reload-based refresh)。

判定:代码完成、定向验证通过、CI 待跑;因属工具面/load-bearing 变更,留人工 live-host 确认累积假设后合并——我不自行合并。

@ranxianglei

Copy link
Copy Markdown
Owner Author

验证备注(关闭正文里的 §7.4 人工确认 flag):

二次 ctx.tool.transform(...) 的语义已在真实宿主上实证(非推理):最小两阶段探针插件——第一次 transform 注册基础集 A/B,第二次 transform 补增量 C;用真实 opencode-v2 对本地一次性 mock 上游跑一轮,抓取发往模型的请求体 tools[]。逐请求核对:每个模型请求体里 A、B、C 同时在场 → 跨调用累加(upsert)、不整组替换;增量那次不会踩掉基础集。

即本 PR「setup 同步注册基础集 + 首个路由请求后再发一次 transform 补增量」的结构安全,无需改动。测试版本 opencode v2.0.3(与 issue 环境 v2.0.24 同属 2.0.x)。详见 issue #2443 回复。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 两件事都处理了:

① 收到你的实证确认:二次 ctx.tool.transform(...) 跨调用累积/upsert(A/B/C 同场、不整组替换)已在真实宿主跑通——本 PR「setup 同步注册基础集 + 首个路由请求后补一次增量」的结构无需改动,§7.4 那条人工确认 flag 就此关闭。我不据此改任何代码。

② master 漂移冲突已解决:先用 git 独立核实了冲突属实(分支落后 master,且只有 src/agent/opencode-v2.ts 的 import 行与 #2455 相撞),再 rebase 到当前 master 顶端。冲突仅一处——import 行同时被 #2455(加 asciiHeaderValue)和本 PR(加 fetchManifest)改动,按 intent-union 合并为两者都保留;#2455 在 stampHeaders 里的 model-header guard 与本 PR 改动不相交、干净并入。force-push 更新 PR 头(d1ce3a6cf → 5627a3a47)。

验证(rebase 后的 head):typecheck 干净(exit 0);tests/opencode-v2.test.ts 32/32;tests/issue2455-nonascii-model-header.test.ts 4/4。(期间 node_modules 被环境清掉过一次,重装并重建 kernel/pi dist 后才拿到真绿——之前一次「全绿」是 grep 掩盖了缺依赖的假象,已纠正。)

GitHub 状态:PR #2445 mergeable=true(冲突已消除);mergeable_state=blocked 是常规的「等 CI / 人工 review」前置态,不是 dirty/冲突。

八问(本次为纯 rebase、无语义代码变更):

  1. 是什么问题:master 前进后本 PR 分支与其产生文本冲突(import 行),需 rebase 消除以恢复可合并。
  2. 什么原因导致:opencode-v2.ts 是热文件,[bug] 模型 id 含中文时 x-bili-plugin-model 触发 undici ByteString 异常,整轮请求发不出去(7 文件 8 处写入点,pi/omp/opencode/dsh) #2455 与本 PR 在同一 import 行各加一个符号;属并发漂移,非代码缺陷。
  3. 影响范围与严重程度:仅限本 PR 分支的可合并性;不影响任何已发布行为。
  4. 怎么修复的:rebase 到 master 顶端,import 行按 intent-union 保留双方符号,其余 hunk 自动并入。
  5. 是否有回归问题:无。核心 always-on 工具的 schema/options/executor 逐字节不变;[bug] 模型 id 含中文时 x-bili-plugin-model 触发 undici ByteString 异常,整轮请求发不出去(7 文件 8 处写入点,pi/omp/opencode/dsh) #2455 的 asciiHeaderValue guard 原样保留并通过其自身 4/4 回归测试。
  6. 是否破坏缓存:不涉及。
  7. 是否改了设计方向:不涉及(未转向,仅对齐 master)。
  8. 是否引入新配置:不涉及(无新 env/config/flag)。

判定:冲突已解、定向验证全绿、mergeable=true;待 CI 通过 + 你 review 后即可合(合并按 §7.4 留人工,我不自行合并)。

@ranxianglei

Copy link
Copy Markdown
Owner Author

🤖 Powered by ework · qwen3.8-27b

[bot] 🏷 收到,这条实证确认与上一条相同、已在上条回复里处理过:二次 ctx.tool.transform(...) 跨调用累加/upsert 既经真实宿主验证,本 PR「setup 同步注册基础集 + 首路由请求后补一次 transform 增量」的结构确认安全、无需改动,§7.4 人工确认 flag 关闭。

当前状态(刚核对):PR #2445 头 5627a3a47,mergeable=true(无冲突),state=blocked = 等 CI / 人工 review 的前置态。代码与测试均无变动,待 CI 通过即可由你合并(合并按 §7.4 留人工,我不自行 merge)。

…2 lane

The opencode V2 lane registered only its static bundled list (5 core ACP tools + absorb) and never consulted the proxy's plugin manifest, so the conditional surface it advertises - acp_retrieve while CCR is armed, acp_rule while rules are on - was absent from the model tool catalog. With CCR enabled an [acp-stored] placeholder pointed at acp_retrieve as its restore path, but the tool was unreachable (retrieveCalls stuck at 0 while storedBytes grew); the only workaround was disabling CCR.

Consume the manifest like the dsh/pi lanes already do: on the first routed request (when the proxy base becomes known) fetch GET /__bili/plugin/manifest and register whatever it advertises beyond the static set. Single-flight, inert-degrading (a failed fetch leaves the tools unregistered, same as before) with a retry cooldown. The always-on core keeps its exact bundled schemas; only the advertised delta is added.

Closes #2443
@ranxianglei
ranxianglei force-pushed the 2026-10-09_opencode-ccr-retrieve branch from 4b1e030 to 410ea46 Compare October 10, 2026 02:12
@ranxianglei
ranxianglei merged commit 7201b45 into master Oct 10, 2026
25 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

📦 Merged Build Published

This merge to master was published as 0.0.204-master.7201b45 (rolling master tag).

npm install -g billion-context@0.0.204-master.7201b45  # this exact build
npm install -g billion-context@master  # always the newest merged build

npm view billion-context@master prints the current version at any time.

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.

opencode lane never registers acp_retrieve — CCR placeholders advertise an unreachable tool (plugin mode)

1 participant