feat(host): unified turn boundary + child-session state derivation (#364, replaces #366) - #380
Conversation
📦 Built Extension ArtifactBranch: Option A — Install from npm PR tag (recommended)pi install npm:billion-context-pi@pr-380Each push to this PR publishes a new version under the Option B — Download artifact
tar xzf billion-context-pi-pr380.tgz
pi install ./packageThis comment is automatically updated on each push. |
[bot] 🏷 Reviewing PR #380 now. Confirmed branch state: head Note: master has advanced past the PR base ( |
…ion (#380) The degenerationGuard doc block was re-indented by one extra space when the hostSession field was added, so the region sat at 3-space field / 4-space comment-continuation while every sibling uses 2-space / 3-space. Restore the convention so the diff carries only the genuine hostSession addition instead of unrelated whitespace churn on degenerationGuard.
[bot] 🏷 Reviewed PR #380 (head Local verification
Design — sound.
Diff-cleanliness fix (pushed). FYI (non-blocking): I measure 738 tests vs 677 in the addendum — master has grown past your stated 648 baseline since the rebase; the suite itself is healthy. Looks good to merge once CI is green (the merge itself is yours per repo rules). 中文摘要:修复了 |
|
为何这个diff这么多 这个目标是为了单进程多会话吗 |
[bot] 🏷 两个问题分开答。 1) 为什么 diff 这么大(+1270 / −130,35 文件) 按类别拆一下行数就清楚了——新增行里约 71% 是测试和文档,不是功能逻辑:
而 src 那 362 行其实捆了三件事,其中一件是跨文件重构,所以文件数看着多、单文件改动却很小:
一句话:测试+文档占了大头,源码部分是"一个新特性 + 一次跨文件的宿主兼容重构"叠在一起,所以总行数和文件数都偏高,但没有灌水。 2) 这个目标是单进程多会话吗? 是的,核心两条都是为同进程内联多会话宿主(如 Prime RLM 在主会话里跑子 RLM)准备的:
第三条(fork 可加载/识别)算前置使能:让 Pi-fork 宿主能加载并识别 ACP(OMP 默认仍拒绝,fork 用 结论:默认对存量用户零影响,功能只在显式开启的内联多会话 / fork 场景生效。 |
|
解决冲突 |
…ion (#380) The degenerationGuard doc block was re-indented by one extra space when the hostSession field was added, so the region sat at 3-space field / 4-space comment-continuation while every sibling uses 2-space / 3-space. Restore the convention so the diff carries only the genuine hostSession addition instead of unrelated whitespace churn on degenerationGuard.
740e102 to
db017be
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)
…old branch shares this SHA)
…ion (#380) The degenerationGuard doc block was re-indented by one extra space when the hostSession field was added, so the region sat at 3-space field / 4-space comment-continuation while every sibling uses 2-space / 3-space. Restore the convention so the diff carries only the genuine hostSession addition instead of unrelated whitespace churn on degenerationGuard.
…ntry (#364) Rebase-integration follow-up onto master (#272 added ACP_EXPORT_CUSTOM_TYPE + CONTEXT_EXCLUDED_CUSTOM_TYPES). isCustomMessageEntry now excludes via that shared set, but TurnBoundaryEntry.customType is optional (string|undefined), which Set<string>.has() rejects under tsc. Guard with an explicit undefined check so narrowing makes the argument a definite string.
db017be to
ed8594e
Compare
✅ 冲突已全部解决并推送。 分支 为什么有冲突
验证(rebase 后全量重跑)
关于那 7 个失败(重点:非本 PR 引入) 一句话中文摘要:解决了本 PR 与最新 master(#376/#377、#272)在 CHANGELOG 及 custom_message 常量上的冲突,rebase 后 typecheck/build 通过、并确认仅剩的 7 个 export 测试失败属 master 既有问题(已另开 issue #389),本 PR 可以合并。 |
|
CI 失败原因已查明:与本 PR 无关,是 master 红(#272×#377 交叉回归) 四个 test 矩阵失败于 修复已提交:#392(Fixes #391,一行断言更新,master 本地全套 722 pass 已验证)。#392 合并后本分支 rebase/update 即恢复全绿 — 本 PR 自身改动没有问题(昨晚 9/9 检查绿时挂的就是这个根因之后的 master 变化)。 |
收到,根因判断一致 ✅ —— 我们独立得出同一结论:master 红是 #272 × #377 交叉回归( 去重:我先前已就同一问题开了 #389;既然你这边已有 #391 + 修复 PR #392,我已把 #389 关闭并打 当前状态
计划:#392 合入、master 前进后,我再把 #380 rebase 到新 master 并重跑全量门禁(typecheck/build/test)确认全绿。在此之前不 churn 分支。#392 合入后在此 ping 我,或等下一次 sync 触发即可。 一句话中文摘要:确认与你根因一致(同一 #272×#377 交叉回归),已把我早开的重复 issue #389 关闭并归并到 #391/#392;#392 未合入前 master 无新内容可 rebase,待其合入后再 rebase #380 复绿。 |
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).Replaces #366 — same three commits (edf6a9d/95db150/1b062bd) rebased onto master v0.1.67 + conflict resolution against #327's per-sid ledger (noteCompressOutcomes(sid, turnKey, ...) signature + lastTurnBoundaryIndex in index.ts; runtime return line keeps master's named per-sid closures and re-adds deriveChildState: deriveChild). Branch renamed wt-364-ework-daemon → 2026-09-10_host-turn-boundary to satisfy the pr-validation branch convention; deleting the old branch closed #366, hence this replacement PR.