Repository navigation
fix: preserve opencode sub-agent session ids across folds via structured-field sidecar (#1702) - #1722
Conversation
📦 Built Package ArtifactBranch: Option A — Install this exact build (recommended)npm install -g billion-context@0.0.1680-pr.1722.46b20f3This exact version is immutable and reproducible. The rolling Option B — Download artifact
tar xzf billion-context-pr1722.tgz
npm install -g packageInstall instructions are refreshed on each push. |
Reviewed #1722 end-to-end (code, kernel/wire plumbing, local gates, CI). Verdict: mergeable — human merge per §7.4 (wire/message-shape change; auto-merge gate not applicable). ① Original issue (direction gate)
② Regression audit (pre-existing behaviors on touched paths)
No unintended regressions found; high-risk patterns (fail-fast → swallow, threshold/default drift, semantic side-change while fixing) checked — absent. ③ Code review — key proofs
④ Test validityReal teeth: byte-identical stored-summary pin, exact receipt substrings, exact map equality, array-reference identity on no-op paths, a genuine no-regex proof (prose mentions and nested-JSON echoes extract nothing), and full ⑤ Diff cleanlinessSingle-purpose commit, 4 files (+321/−2), branch is current master (merge-base == master HEAD), no lockfile churn, no version bump, no whitespace noise — every line serves the stated purpose. Duplicate screening: no parallel or overlapping items beyond the #1702/#1704 lineage. Gates (re-run locally @410ca72e + CI)
Behavior-change disclosure (old → new)
Stored block summaries, non-opencode lanes, and every other client's wire bytes are unchanged. Residual risk (one — worth a post-merge check)Field-name coverage: capture reads top-level 中文摘要:审查了 PR #1722(opencode 子代理 ses_ id 跨折叠机械保真:结构化字段捕获 → metadata sidecar → receipt/渲染时 attach,仅 opencode)——核对了 kernel/wire 管线与全部触碰路径的既有行为(#1294 指纹行、receipt、四条 wire 管线、持久化、非 opencode 通道均保持或已披露的有意变更),本地 typecheck/build/定向测试 55/55 通过、CI 13/13 全绿,测试有牙、diff 干净、无意外回归,可以合并(按 §7.4 属 wire 形态变更,由人工合并)。 |
|
Follow-up commit 85f8238 — real-client e2e proof (opencode v2 lane) + a capture fix it uncovered. Capture fixopencode 2.x renames the dispatch tool E2E (
|
…red-field sidecar (#1702)
… a real fold (#1702) opencode 2.x renames the dispatch tool task->subagent and its result envelope to <subagent sessionID=...> (packages/core/src/tool/plugin/ subagent.ts:16): capture now accepts both machine envelope shapes on both dispatch tool names, head-anchored as before. New e2e (tests/e2e/e2e-subagent-sessions.test.ts) drives the REAL opencode 2.0.4 source checkout through the built native plugin: filler load -> subagent dispatch (child conversation + envelope verified at the fake upstream) -> push-backs -> explicit-range compress; asserts the receipt carries [acp-subagent-sessions bN: ses_...], the persisted sidecar maps block->child id, and the block summary stays byte-clean. Harness notes: oc run 2.x shell-quotes argv prompts containing spaces (run.ts formatMessage) so scripted JSON directives must ride stdin; plugin dirs need server/tui/rpc stubs beside index.js (Host.resolve rethrows uncoded ResolveMessage -> whole reload aborts -> Agent not found); managed-service pins land in service-local.json (channel=local) and teardown kills by registration pid.
Master #1699 routes opencode v2 title-gen requests verbatim (no ref tags); they re-send the filler text and race the main turn in the reqLog oracle, so the filler row lookup must filter on the title marker — mirrors the sibling suite e2e-native-opencode.test.ts.
85f8238 to
b6fec1a
Compare
Reviewed follow-up commit 85f8238, rebased the branch onto current master (it had gone dirty — no CI had started), and verified everything end-to-end. Verdict: mergeable (human merge per §7.4 — wire/message-shape change). Review of 85f8238Capture fix — correct and necessary. The 2.x rename ( New e2e — independently reproduced. Hermetic (isolated XDG/HOME/cwd,
The harness quirks you encoded (stdin-routed directives, Rebase report (cc35757 → 5fc5989, ~10 master commits)Master moved while the PR sat open — log-privacy scrub (#1718), decompress-empty-range (#1713), transport retry (#1693/#1688), titlegen intent routing (#1703/#1699), fetch-chain-depth (#1672), quiet-unrouted-get (#1661). Three files overlapped the PR:
Pushed Gate evidence (all on rebased head b6fec1a)
Regression audit (existing behaviors touched)
Behavior change disclosure (old → new)
Optional follow-up (your call, not filed)
中文摘要:复审了 2.x envelope 捕获修复与新真实客户端 e2e(本地独立复现 v1 4/4、v2 1/1);分支已 rebase 到最新 master,解决了与 #1718 日志脱敏在 stream.ts 的冲突(sidecar 注记只含块号与 ses_ 标识、无摘要文本,双份保留),并补了一个针对 #1699 title 侧信道竞态的 4 行 e2e 小修;全部门禁含 CI 13/13 绿,无回归,可以合并。 |
Fixes #1702 — replaces #1704 per the review direction there (structured fields, not regex; sidecar + render-time attach, never polluting stored summaries; opencode only).
Root cause
The kernel's fold protection is keyed by tool name (
ALWAYS_PROTECTED_TOOLS) and content-blind. Once a sub-agent dispatch pair slides out of the protected zone, the childses_…session id survives only through the model's free-text summary — weak models drop it (#1563 A/B: ~20% loss), breaking laterdecompress-style retrieval of that sub-agent's context.Design (per #1704 review)
src/subagent-sessions.ts): the dispatch tool-call's arguments JSON is parsed and known id fields (sessionId/sessionID/session_id) are read under a strict^ses_[A-Za-z0-9]+$shape predicate. Prose is never regex-scanned.session.metadata["subagentSessions"]—Record<blockId, string[]>.persist.tsalready serializes the full metadata bag, so no persistence-format change.applyRanges): the same changed-block set that drives the [feature] compress 回执加摘要指纹 + inline decompress 回执给再折叠指引 #1294 fingerprint lines now also captures ids (covered view =effectiveMessageIds) and inherits child-block entries on T2 refolds (directBlockIds). Refolds whose re-capture comes up empty keep the prior entry (conservative retention); entries for blocks gone from state are pruned.[acp-subagent-sessions bN: ses_…](plugin-mode carrier — the result rides the agent's re-sent history);applyAbsorbView(proxy-mode carrier);b.summarystays byte-identical to the model payload — tier distillation never re-copies ids, and the attach lines are render-only.session.metadata.pluginAgent === "opencode". No other client lane ships a structured sub-agent field today; the module is reusable if one appears.This intentionally deviates from the issue's literal acceptance ("id stored in the block") toward its spirit: the id is mechanically guaranteed model-visible after every fold, while summaries stay clean — direction agreed in the #1704 review thread.
Gate evidence
npm run typecheck✓npm run build✓ (dist-import-annotations: OK)tests/subagent-sessions.test.ts6/6 ✓ (capture variants + no-regex proof, sync gate/prune/inherit semantics, attach immutability, fullapplyRangesintegration both opencode and non-opencode)Diff:
src/subagent-sessions.ts(new),src/stream.ts(+14/−2, fingerprint-loop reuse),src/server.ts(+5, import + 4 attach sites),tests/subagent-sessions.test.ts(new).