fix: recover fork compression blocks from parent state when historical compress inputs are stripped - #376
fix: recover fork compression blocks from parent state when historical compress inputs are stripped#376ranxianglei wants to merge 4 commits into
Conversation
…al compress inputs are stripped Forks copy the parent's message history but often strip the historical compress tool inputs, so the replay-only reconstruction rebuilt zero blocks and the copied raw parent history overflows the fork's context (issue #375). Add a parent-state transfer path for fork init: when a session has a parentID and no fork-local state, load the parent's ACP state, map parent message IDs to the fork's IDs across the copied shared prefix (matched by position + time.created + role, robust to the one-ref shift forks get from being classified as sub-agents), translate the parent's blocks onto the fork's IDs, and save independent fork-local state. Falls back to the existing historical replay when the transfer is not possible. - lib/state/fork-transfer.ts (new): recoverFromParentState + helpers - lib/state/state.ts: fork init tries parent transfer, then replay fallback - lib/state/utils.ts: getForkParentId; isSubAgentSession reuses it - tests/rebuild-parent.test.ts (new): 7 tests incl. ref-shift + fallbacks - devlog/2026-09-09_fork-parent-state-transfer/ Does not mutate the parent; does not inherit nudge cadence / current-turn state.
📦 Built Plugin ArtifactBranch: Option A — Install from npm PR tag (recommended)opencode plugin opencode-acp@pr-376 --globalEach push to this PR publishes a new version under the Option B — Install from GitHubopencode plugin "github:ranxianglei/opencode-acp#2026-09-09_fork-parent-state-transfer" --globalOption C — Download artifact
tar xzf opencode-acp-pr376.tgz
cp -r package/dist ~/.cache/opencode/packages/opencode-acp@latest/node_modules/opencode-acp/dist
This comment is automatically updated on each push. |
|
解决冲突,并简述本issue解决了什么问题 |
…t-state transfer) Both changes coexist in ensureSessionInitialized(): master's storagePath default-location warning runs first, then the PR's fork recovery path (parent-state transfer → historical replay fallback).
✅ 冲突已解决,已推送到 冲突详情:将 master(含 #380 storagePath)合入 PR 分支,仅一处冲突 —— 合并后验证: 本 PR 解决了什么问题(Fixes #375): OpenCode fork 会话时会获得新 session ID 和复制的消息历史,但没有 fork 本地的 ACP 状态文件。ACP 原本只能靠重放历史 修复:当会话有
若翻译不可行(无父状态 / 无共享前缀 / 无 block / 拉取失败 / 无 active block 可翻译),回退到原有的历史 replay。父状态永不被修改,也不继承父会话的 nudge 节奏;锚点在共享前缀之外的 block 会被跳过(部分 fork / fork 后父会话继续推进的场景)。 |
Review: REQUEST_CHANGES — 3 majors (one empirically confirmed), plus test-coverage gapsFirst: the premise of this PR is currently unverified — see the audit on #375 (comment). No inspectable opencode version (sst Design verified good 👍
Major findingsM1 — M2 — M3 — Minor
Test coverage gaps (per §5.6/§5.7)
Suggested path forward
Happy to pair on the M1 re-activation semantics if useful. |
[bot] 🏷 冲突已解决,PR 已与最新 master 同步。 冲突处理
本 PR(#376)解决的问题 具体地:当某个 session 带有
无法迁移时(无父状态 / 无共享前缀 / 无块 / 拉取失败 / 无可迁移的活跃块)回退到既有的历史 replay,因此对其它场景是无副作用的(inert)。保证:父状态绝不被修改、不继承父会话的 nudge 节奏、锚点在复制前缀之外的块会被跳过。 |
Related to #375 — defensive layer, not the confirmed root-cause fix.
What it does
In the fork/subagent init path, when a session has a
parentIDand no fork-local state file, ACP now:time.created+ role (not by ref — a session classified as a sub-agent has itsmNNNNNrefs shifted by one relative to the parent, so a ref-based translation would point blocks at the wrong messages).directMessageIds/effectiveMessageIds, tool call ids preserved verbatim,anchorMessageId/compressMessageId, and best-effortstartId/endIdrefs).It falls back to the existing historical replay whenever the transfer is not possible (no parent state, no shared prefix, no blocks, fetch failure, or no active block translatable).
Guarantees: the parent state is never mutated; parent nudge cadence / current-turn state is not inherited; blocks anchored outside the copied prefix are skipped (partial forks / parent continued after fork).
Changes
lib/state/fork-transfer.ts(new) —recoverFromParentState()+ helpers (buildSharedPrefixMapping,translateBlock,remapBoundaryRef,translateByMessageId).lib/state/state.ts— init path tries parent transfer first, then falls back torebuildCompressionState.lib/state/utils.ts— newgetForkParentId();isSubAgentSessionreuses it (same boolean result).tests/rebuild-parent.test.ts(new) — 7 tests: stripped-input recovery, ref-shift robustness, three fallbacks, independence (no nudge inheritance / no parent mutation), and the out-of-prefix skip path.devlog/2026-09-09_fork-parent-state-transfer/— REQ / WORKLOG / DESIGN.Verification
npm run typecheck— cleannpm run build— successnpm run test— 1084 pass / 0 fail (was 1077; +7 new)Dual-agent review (code + tests) completed; findings addressed (tool-call-id fidelity, active-only state assignment, best-effort boundary-ref documentation, added test coverage).