fix: reconcile compaction restart and custom-storage fork recovery - #409
ranxianglei wants to merge 4 commits into
Conversation
📦 Built Plugin ArtifactBranch: Option A — Install from npm PR tag (recommended)opencode plugin opencode-acp@pr-409 --globalEach push to this PR publishes a new version under the Option B — Install from GitHubopencode plugin "github:ranxianglei/opencode-acp#2026-09-16_compaction-fork-recovery" --globalOption C — Download artifact
tar xzf opencode-acp-pr409.tgz
cp -r package/dist ~/.cache/opencode/packages/opencode-acp@latest/node_modules/opencode-acp/dist
This comment is automatically updated on each push. |
[bot] 🏷 Received — this is a completed agent PR (ework-agent-pr marker), so I'm verifying rather than redoing work. Status so far:
I'm now checking the code changes against issue #407 (root-cause validation) and will report findings shortly. |
[bot] 🏷 Review complete — PR verified, ready for human merge. Root-cause validation (against #407, per triage rules):
Fix semantics checked:
Verification performed on branch
No issues found that need fixing in this PR. Merge is a human-only operation — please merge when 中文摘要:已独立验证 #407 两个根因均真实存在(compaction 后重启时 lastCompaction 先于持久化状态加载导致重置永不触发;fork 恢复未传 storageDir 且旧版 4 位 ref 无法匹配 5 位 ref),修复逻辑正确且向后兼容,本地 1268 测试全过、无修复时新测试确实失败(回归有效性成立),CI 除 e2e 外全绿——可以合并。 |
Dual-agent review (AGENTS.md §5.3 + §5.6) — completed, findings fixed on-branchReviewer 1 (tests) — verdict: APPROVE.
Reviewer 2 (lib code) — verdict: APPROVE, one minor finding fixed on-branch:
Verification: 6/6 new regression tests pass (all bug repros verified to fail pre-fix); full suite 1269/1269; typecheck + build + Prettier clean. 中文摘要:修复了重启落在原生压缩之后导致过期瞬态状态被无条件恢复、以及自定义 storagePath / 旧版 4 位引用下 fork 恢复丢失继承压缩块的问题;双 agent 审查发现的 storagePath 过渡场景父状态回退缺失已直接在分支上补上并加了回归测试,可以合并。 |
6d94b7a to
026f7bc
Compare
[bot] 🏷 Re-verified the updated branch (head Verification of
|
- state.ts: after loading persisted state, compare the current completed-compaction boundary against the persisted boundary. When history is newer (process restarted between native compaction and the next transform), reset stale transient refs/nudges/tool cache via resetOnCompaction while preserving compression blocks and stats, then persist the corrected state. updatePerTurnState cannot catch this case because lastCompaction was already set from current history before load. - state.ts: load parent fork state with the child's resolved storageDir so custom storagePath configurations find the parent file. - rebuild.ts: normalize legacy 4-digit parent message refs to the current 5-digit format before parent-to-fork translation (mapForkIds). - tests: restart-after-compaction (with negative control), custom-storage fork, and legacy-ref fork regression tests (unit + full-init paths). Fixes #407
Code review minor: parent-state lookup falls back to the default storage location when the custom-dir load misses, preserving master behavior in the just-configured-storagePath transition (transfer instead of replay degradation). New regression test for the transition path, verified to fail without the fallback. Test review minors: buildConfig() now type-conformant to PluginConfig (logLevel, top-level allowSubAgents, qualityGate, messageFilters); comment pinning the transient tool-cache assertion; persisted-side-effect reload assertion in the legacy-ref e2e test.
026f7bc to
1576ed4
Compare
Summary
Fixes #407 — two pre-existing state-recovery paths could restore stale or incomplete ACP state.
1. Restart immediately after native compaction (lib/state/state.ts)
ensureSessionInitializedsetstate.lastCompactionfrom the current history before loading persisted state, then restored persisted nudge anchors/baselines, message refs, and tool-cache state unconditionally. If native compaction completed after the last persist (restart in between), those fields were stale — andupdatePerTurnStatecould never reset them because its trigger compares against an already-currentlastCompaction.Fix: after the load, compare the current completed-compaction boundary with the persisted
_persistedLastCompaction. When history is newer, call the existingresetOnCompaction(state)— which resets message refs, all nudge anchors/baselines, and the tool cache while preserving compression blocks and stats (parity with the live-compaction path) — and let the existing end-of-init save persist the corrected state.2. Fork recovery (lib/state/state.ts + lib/state/rebuild.ts)
storageDir, so with a customstoragePaththe parent file was never found → inherited blocks lost whenever the copied history lacked replayable compress inputs. Now passesstate.storageDir(undefined falls back to the default location — backward compatible).mapForkIdsconsumed parentbyRefkeys verbatim; legacy pre-1.1.0 four-digit refs (m0001) never match fork five-digit refs (m00001). NewnormalizeParentMessageIds()mirrors the own-session load-time migration (byRef rebuilt from byRawId).Tests
New
tests/restart-compaction-fork-recovery.test.ts(5 tests):storagePath, block transferred, fork persisted thererestoreForkCompressionState)ensureSessionInitialized)Regression validity verified per AGENTS.md §5.7.3: with the lib fixes stashed, tests 1/3/4/5 FAIL and the control passes; with fixes, 5/5 pass.
Full suite: 1268 tests, 0 failures. Typecheck + build clean.
Devlog:
devlog/2026-09-16_compaction-fork-recovery/