feat(host): unified turn boundary + child-session state derivation (#364) - #366
feat(host): unified turn boundary + child-session state derivation (#364)#366ranxianglei wants to merge 3 commits into
Conversation
[bot] 🏷 Review started on PR #366 (head |
[bot] 🏷 Review complete — approved, ready to merge. I checked out the PR head ( Diff cleanliness ✅20 files, +847/−71. Every file is on-topic; no lockfile churn, no generated artifacts, no drive-by reformatting. Verification (reproduced locally)
Problem 1 — turn boundary convergenceRead One deliberate non-convergence I checked and accept: Problem 2 — deriveChildStateRead the pure fn in Two things I analyzed and cleared:
Scope & duplicationComplements #317 (part 1) and #327 (ledger isolation) without overlapping them — the issue body's framing of what each part fixed checks out against the code. Closes #364 as described. Docs land where they should: new No blocking or minor defects found, so no direct fix commits were needed. Merge when convenient — merging is yours to do. 中文摘要:修复了 #317 遗留的两个多会话结构性缺口——回合边界判定收敛为单一谓词(默认关闭时与 legacy 扫描逐字节等价,有回归测试证明),并新增内联子会话状态派生契约 deriveChildState(继承块/引用/原始消息索引、重置节奏账本、一次性迁移标记、全部护栏拒绝路径有测试覆盖,pi 原生 delegate 路径零变化);diff 干净,typecheck/test(671/0/3)/build 本地复跑全绿且与声明完全一致,可以合并。 |
4a4e88a to
830d3b1
Compare
) - src/turn-boundary.ts: single isTurnBoundary(entry, policy) predicate + lastTurnBoundaryId/Index; tokens.ts lastUserMessageId, index.ts turnStartIndex, and messages.ts projection all converge on it - hostSession config (boolean | {countCustomMessages}), default off = pi-native behavior, existing single-session cadence byte-for-byte unchanged - deriveChildState(parentState): inherit blocks/messageRefs/tokenSnapshot/ counters, reset nudge/stats/absorbed; one-time derivedFrom marker persisted to the independent child sidecar; runtime.deriveChildState(childRef, parentRef) with guards (own blocks / empty parent / no file / already derived) - pi-native delegate (separate-process) path untouched - docs/host-adapter.md + CONFIGURATION.md(+zh-CN) + CHANGELOG entries - tests: tests/turn-boundary.test.ts, tests/derive-child-state.test.ts, config/user-config additions; 671 pass / 0 fail / 3 skip
- src/config-dir.ts: namespace import of the pi package + feature-detect CONFIG_DIR_NAME with ".pi" fallback — safe under both link-time and runtime missing-export failure modes; sole value import from pi pkg (tool-guardrails vendors its one guard locally, same pattern as the existing isBashToolResult vendoring) - src/host.ts: entrySourceOf / isDeclaredForkHost (PI_ACP_FORK_HOST=1|true) / isUnsupportedHost — OMP stand-down protection stays default; declared Pi-compatible forks are accepted and get the existing live-message merge - omp.ts: UNSUPPORTED_HOST_MESSAGE guidance (fork opt-in + billion-context proxy); session_start gate uses isUnsupportedHost - turn boundary: empty custom_message no longer starts a turn (same extractText gate as projection; #364 acceptance c) — isCustomMessageEntry moves back to messages.ts so projection and predicate share one definition - docs: host-adapter.md §3 detection/entry-source contract + §4 config-dir responsibility boundary; omp.md(+zh); CONFIGURATION(+zh) env table
…ckage entrypoint (#367)
830d3b1 to
1b062bd
Compare
📦 Built Extension ArtifactBranch: Option A — Install from npm PR tag (recommended)pi install npm:billion-context-pi@pr-366Each push to this PR publishes a new version under the Option B — Download artifact
tar xzf billion-context-pi-pr366.tgz
pi install ./packageThis comment is automatically updated on each push. |
Fixes #364
Host multi-session support (part 2) — the two structural gaps left over from #317 (ledger isolation was already fixed by #327).
Problem 1 — turn boundary determination was decided independently in three inconsistent places
lastUserMessageId(src/tokens.ts),turnKey/turnStartIndex(src/index.ts), and the context-entry projection (src/messages.ts) each had their own "what starts a turn" logic. Host-injectedcustom_messageagent turns enter the LLM context but never started a turn, so multiple real turns collapsed into one turnKey — nudge ledger cells misaligned, per-turn compress retry cap and throttle/overflow cycle stats distorted.Now: single predicate
isTurnBoundary(entry, policy)+lastTurnBoundaryId/lastTurnBoundaryIndexin newsrc/turn-boundary.ts; all three sites converge on it. Host policy entryhostSession(boolean | { countCustomMessages }, default off = pi-native behavior): when off, the predicate is exactly equivalent to the legacy user-role-only scans — existing single-session users see byte-for-byte unchanged cadence (proven by unit tests that diff default-policy results against the legacy scan as oracle). When on, injected non-UIcustom_messages start turns for all per-turn ledgers without touching LLM-context projection.Problem 2 — inline child sessions had no state inheritance contract
runtime.stateFor(childSid)always returned fresh state: correct for pi-native delegates (separate process), wrong for inline same-process children (Prime RLM), which need the parent's blocks to makedecompress/search_contextwork.Now:
deriveChildState(parentState)(pure fn in src/state.ts) +runtime.deriveChildState(childRef, parentRef)orchestration:derivedFrom: {parentSessionId, derivedAt}persisted into the child's own sidecar<child>.jsonl.acp.json(independent of the parent file); re-derivation refusedparentSessionheader inheritance (upgrades it exactly once)Verification
npm run typecheckcleannpm test: 671 pass / 0 fail / 3 skip (baseline 648/0/3; net +23 new tests)npm run build: clean bundle (dist/index.js 741.7 KB)New tests:
tests/turn-boundary.test.ts(predicate matrix, id/index dual-view consistency across a mixed-entry battery, default-policy ≡ legacy-scan regression),tests/derive-child-state.test.ts(inherit/reset matrix, deep-copy isolation, kernel activeBlocks/blockById viability on derived state, marker round-trip, every guard-refusal case, inline sub-agent header scenario). Config/user-config tests coverhostSessionparsing and acp.json key gating.Docs: new
docs/host-adapter.md(turn-boundary contract + child-session derivation contract, incl. host usage snippet and who-should-enable guidance);hostSessionsections added to CONFIGURATION.md (+ zh-CN); CHANGELOG entry.中文摘要:修复了 #317 遗留的两个多会话结构性缺口——回合边界判定收敛为单一谓词(默认关闭时存量行为逐字节不变,有回归测试证明),并新增内联子会话状态派生契约 deriveChildState(继承压缩块/消息引用/原始消息索引,重置节奏账本,一次性迁移标记,pi 原生 delegate 路径零变化),补齐 docs/host-adapter.md;typecheck/test/build 全绿,可以合并。
Addendum — also fixes #367 (
4a4e88a):docs/host-adapter.md's host contract requiresruntime.deriveChildState(childRef, parentRef), but "the extension runtime" was private to thecreateAcpExtensionfactory closure andexportsexposes only.→ dist/index.js, so no external host importingbillion-context-picould ever reach the documented derivation call (forced onto the implicitparentSession-header inheritance instead). Fix: re-exportcreateRuntime+ typesAcpRuntime/SessionRef+ purederiveChildStatefrom the package entrypoint;docs/host-adapter.md"API surfaces" rewritten to import from the package entrypoint; CI test job reordered sobuildprecedestest(new test resolves the package name via Node self-reference todist/index.js); newtests/host-api.test.tsimports only from"billion-context-pi"and exercises the documented call end-to-end (entrypoint typeof checks, deep-copy/rhythm-reset, temp-dir sidecar derivation with one-time marker + repeat-refusal; skips whendist/is unbuilt). Local verification: typecheck + build + full suite green (677 tests, 0 failures, 3 pre-existing skips).