chainlesschain 0.162.170 → 0.162.172
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/chainlesschain.js +8 -0
- package/package.json +6 -1
- package/src/assets/web-panel/assets/{AIOps-BTXN24I3.js → AIOps-oKCWU-LK.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BBB1VF4j.js → ActionButton-4CBfc5E4.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BVCXq0k6.js → Analytics-BEmR-k_b.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-C3EOo2s5.js → AppLayout-D6a4ZnrC.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CsJTZSMv.js → Artifacts-BRWIHCPl.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CP0AllJ_.js → Audit-CzGKlkTc.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BCAjs08m.js → BackgroundAgents-CmMYN10o.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BxQI5XH4.js → Backup-CJ6e0ukD.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Bu-PNzC5.js → BaseInput-BpEvDrNt.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DjPQynup.js → Chat-wWuwwjKu.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CygkRopu.js → ChatBubbleRenderer-CJLyGo_u.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BxL6xm7Z.js → Checkbox-C6pW60Br.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CcyB7h_6.js → Codegen-DF-nP22Y.js} +1 -1
- package/src/assets/web-panel/assets/{Col-8NQiKJJx.js → Col-BuueEwZW.js} +1 -1
- package/src/assets/web-panel/assets/{Community-B31qy5OS.js → Community-DuE0Yzwr.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-C-5fAsZ4.js → Compact-BrHim8EB.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BTSccvF0.js → Compliance-DgAet8f_.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BXctU9IK.js → Cowork-B9wY05lx.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-CxVFX9NG.js → Cron-CvNBxfJX.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DEF3dq8M.js → Crosschain-BnZRENa0.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BmKcE9he.js → DID-DiqYLHTI.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CECBq_wG.js → Dashboard-t3SsRzZL.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-U-iuPB0n.js → Dropdown-BCjMLam9.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-D8BIlSPL.js → EmailListRenderer-D2v9-IHJ.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BzQGfUH-.js → FamilyGuardDashboard-_iQu35qa.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-4_WlOqLZ.js → Federation-Bkty8VqB.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-gtrW-DdF.js → FormItemContext-DdPW81BY.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BLcvJJB6.js → GenericCardRenderer-DrNYM4Zj.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Bk0janGD.js → Git-BjF-B2h0.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CcfyMlV8.js → Governance-DPhGdJ1v.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CBLrAf4M.js → Inference--PPksOVj.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-ClguhteZ.js → KnowledgeGraph-y5WdV_JV.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Dp_oeKfJ.js → Logs-B6_x7zba.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-Bg8zSyF8.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BHk50smM.js → Marketplace-CHpfMu1A.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BAY3CV6J.js → McpTools-qqjtX3Cf.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-w0pG2AKx.js → Memory-hASSgxbw.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-C0VkDUE3.js → MobileBridge-Dwh7Bhcf.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BCvQ80I9.js → MobileProjects-D09voh7W.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-DSIl41Tt.js → Mtc-9G1dXeId.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-D1mzUlIr.js → MtcAudit-CWtYXj6f.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-CeJz4VSS.js → Multisig-bDI0EnDk.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-_O3aweu8.js → NLProgramming-CTanRaBP.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Dt7_pO86.js → Notes-BdQENJFv.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Dhg3Nktv.js → NotificationSettings-JvnQdStO.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-QsAPcTb2.js → OrderTableRenderer-BMGL9RPJ.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BuiaUPkv.js → Organization-sBZnIb2D.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-C3jb6knl.js → Overflow-CzOhNGE6.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BOMyrLkb.js → P2P-D_D2yqda.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-UvLStCgS.js → PdhVaultBrowser-D5lKd_Kx.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-B9cN0dJH.js → Permissions-L5s-wENg.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DNjW5qro.js → PersonalDataHub-iI49x6cH.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-CFLyZ6Sh.js → Pipeline-DPHqyk09.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-D3w6vOxf.js → Privacy-B-ZL9360.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-cwcVsn8K.js → ProjectInit-f05YzsN2.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-aUoAroc-.js → ProjectSettings-DZLKS6ex.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-CrzQv7Zv.js → Projects-ButF3DyM.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-jZaPizKR.js → Providers-CFDSXnkR.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DNzmwcfg.js → QrScannerModal-1N6Sdxdm.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-m-5Gwt70.js → QuickAsk-tEqRwRSR.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CrrAjNxP.js → Recommend-D4ZVHIwz.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-BDCMjRFe.js → RemoteSession-Cqm32lhm.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CPCt1Jc8.js → Reputation-BnjVsU_a.js} +1 -1
- package/src/assets/web-panel/assets/{Row-C-RorxHs.js → Row-DzpHyTuz.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CnQs4TuY.js → RssFeed-Du5-ouwx.js} +3 -3
- package/src/assets/web-panel/assets/{Search-B1jkeFZY.js → Search-CYztyf2o.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CGKI3vvd.js → Security-C1hVxy8Z.js} +3 -3
- package/src/assets/web-panel/assets/{Services-DQjesYF8.js → Services-BscEbAZ2.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-C9Ipj0A6.js → Skeleton-B2sMLqKQ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-D2j54i9O.js → Skills-CoHKax06.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-xhrVjTeb.js → Sla-Bpie5KO6.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C6KMOUCa.js → SpeechSettings-BX63ijn5.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CmIPU5wR.js → SyncSettings-Bf72so5A.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-B-T1J42r.js → Tasks-CVB2PZk4.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Aq669sjw.js → Templates-q0qyclJx.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BKWUWNXB.js → Tenant-CW-W-i0k.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DEeAvA8L.js → Terminal-D7QoPiXQ.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-C465ySdn.js → TimelineRenderer-BaRDAbyA.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BgVqF-bX.js → Tokens-D6U4PRW8.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BMNHmxwH.js → Trigger-ClBDWdLo.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-B_adk89L.js → Trust-C_M6rWPq.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-Biw38mog.js → UkeySign-bXHx8s4C.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-AC-eaw5k.js → VideoEditing-BcIt_ieI.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-8lQ5_t0X.js → Wallet-Dd-4jcLs.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-JDmH_-DF.js → WebAuthn-B4F6yxQK.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-DHQAdITG.js → WorkflowEditor-BEHkpfJq.js} +1 -1
- package/src/assets/web-panel/assets/{chat-C3qauUVv.js → chat-D7hhRpIO.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DLP70JGe.js → colors-AMRhrRzS.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DjsoqA5t.js → compact-item-BYpsOET5.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CfEdl1in.js → createContext-CoUkR4LQ.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BRylvxzY.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DWwHHlBG.js → hasIn-CJKJMKki.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dfka-vzJ.js → index-8pmZHnQU.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgQ92xdO.js → index-BBJFiZ9Y.js} +1 -1
- package/src/assets/web-panel/assets/{index-VtCz3A64.js → index-BP69Ul3S.js} +1 -1
- package/src/assets/web-panel/assets/{index-C8wrUFzY.js → index-BTFHYauX.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5VL0NYy.js → index-BUJt2ar4.js} +1 -1
- package/src/assets/web-panel/assets/{index-B-rD2rhi.js → index-Bj30Aq6h.js} +1 -1
- package/src/assets/web-panel/assets/{index-gu8zBhtp.js → index-BlpuqGt5.js} +1 -1
- package/src/assets/web-panel/assets/index-Bzvxvzlk.js +1 -0
- package/src/assets/web-panel/assets/{index-CgruUvB3.js → index-C19xZKIP.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTEcPJGf.js → index-C6bg22vc.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4k6JGm_.js → index-CH6XEWdj.js} +1 -1
- package/src/assets/web-panel/assets/{index-C0xMzvk8.js → index-CK1Kknpl.js} +1 -1
- package/src/assets/web-panel/assets/{index-UEbYl2pu.js → index-CNyLbBGj.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dmt1HDWZ.js → index-COg__P6M.js} +19 -19
- package/src/assets/web-panel/assets/{index-ArZCi1rK.js → index-CRlJOWsf.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqPuyN4p.js → index-CScG8TAY.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4gCFHtB.js → index-CdL9cQIE.js} +1 -1
- package/src/assets/web-panel/assets/{index-C47EiQ7d.js → index-Ck35u_0v.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dn2rNMmv.js → index-CkLZQtsh.js} +1 -1
- package/src/assets/web-panel/assets/{index-T5emAyFT.js → index-DFoujkWD.js} +1 -1
- package/src/assets/web-panel/assets/{index-DbFnfyKh.js → index-DFwh0R9h.js} +1 -1
- package/src/assets/web-panel/assets/{index-CsEk9s04.js → index-DStN9E7o.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTDpF8MS.js → index-DcozYrXV.js} +1 -1
- package/src/assets/web-panel/assets/{index-_g5q2hF3.js → index-De5mYp86.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bp_WNm2_.js → index-DfEVOzNb.js} +1 -1
- package/src/assets/web-panel/assets/{index-OfMwkFIo.js → index-Dfna8sKa.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcDeMCUK.js → index-DjbMaBLf.js} +1 -1
- package/src/assets/web-panel/assets/index-Dl6H3dxS.js +1 -0
- package/src/assets/web-panel/assets/{index-D-te-S9f.js → index-Dpl_SJuk.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKsWujXP.js → index-DyF9uYzB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BydBh7oT.js → index-HF3GvATm.js} +1 -1
- package/src/assets/web-panel/assets/{index-DAcBi6F-.js → index-OJmXGn8n.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNeLedtU.js → index-ZPdNPhUq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ds1BfT_C.js → index-djikyGz3.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbF27GqZ.js → index-gQWWnP3_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CnhJbWsl.js → index-owVbLJnH.js} +1 -1
- package/src/assets/web-panel/assets/{index-Lh0e2afo.js → index-tWEDZHzj.js} +1 -1
- package/src/assets/web-panel/assets/{index-DbjUePgq.js → index-ttTl376q.js} +1 -1
- package/src/assets/web-panel/assets/{index-nuZaMsMy.js → index-z5rYvstM.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C_pMmi75.js → initDefaultProps-B_Uk-wzg.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BvKgS96o.js → motion-ez7H8y8b.js} +1 -1
- package/src/assets/web-panel/assets/{move-C-1U4R1I.js → move-CKWUhhhX.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-D6BC968-.js → mtc-parser-DkNl7ocj.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DxOZvcGQ.js → omit-C0uA2ytw.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-LpzmHdkw.js → pickAttrs-CySaWMPl.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-Bm--qom1.js → placementArrow-BWL3-3xo.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CVZZlhJf.js → responsiveObserve-BRy_wJog.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DD1xINGx.js → slide-DmmvSaBD.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-LrjHAy63.js → statusUtils-C_7SYY0K.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BqknYxTe.js → styleChecker-DYgBJGDh.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Dljy3diT.js → useFlexGapSupport-nPkoZkT6.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-OedUtPLc.js → useFs-CzpsfP4_.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-Cz-kxbP_.js → usePersonalDataHub-Bo2vvMWl.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-C8ZuTk8W.js → vnode-D-hXAnHb.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CjbgJYXQ.js → zoom-DW7seTGG.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +39 -14
- package/src/commands/background-session.js +12 -1
- package/src/commands/permissions.js +24 -1
- package/src/gateways/ws/ws-agent-handler.js +26 -1
- package/src/gateways/ws/ws-approval-gate.js +131 -0
- package/src/index.js +63 -845
- package/src/lib/agent-ipc-bus.js +331 -0
- package/src/lib/agent-worktree.js +173 -5
- package/src/lib/background-agent-supervisor.js +193 -4
- package/src/lib/context-source-ledger.js +164 -0
- package/src/lib/execution-trace/index.js +10 -0
- package/src/lib/execution-trace/metrics-collector.js +218 -0
- package/src/lib/execution-trace/trace-context.js +182 -0
- package/src/lib/hook-manager.js +17 -0
- package/src/lib/hooks-v2-runtime.js +278 -0
- package/src/lib/interaction-adapter.js +26 -5
- package/src/lib/metrics-collector.js +127 -0
- package/src/lib/observability/index.js +33 -0
- package/src/lib/observability/otlp-exporter.js +260 -0
- package/src/lib/otlp-exporter.js +45 -0
- package/src/lib/process-execution-broker/SPAWN_AUDIT_REPORT.md +117 -0
- package/src/lib/process-execution-broker/index.js +450 -0
- package/src/lib/process-execution-broker/patch-child-process.js +50 -0
- package/src/lib/process-execution-broker/spawn-audit.json +3814 -0
- package/src/lib/repl-rewind.js +62 -14
- package/src/lib/runtime-provenance-ledger.js +190 -0
- package/src/lib/session-hooks.js +41 -0
- package/src/lib/turn-binding.js +134 -0
- package/src/program-base.js +4 -1
- package/src/repl/agent-repl.js +77 -3
- package/src/repl/repl-turn-binding.js +91 -0
- package/src/runtime/headless-runner.js +59 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-RQ1HbTY5.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Cwuuqc_f.js +0 -1
- package/src/assets/web-panel/assets/index-BkTk-zJK.js +0 -1
- package/src/assets/web-panel/assets/index-C-BSTEhe.js +0 -1
package/src/lib/repl-rewind.js
CHANGED
|
@@ -232,18 +232,48 @@ export function restoreClearedConversation(messages, marks, cleared) {
|
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
/**
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
235
|
+
* Match a live-conversation turn to its record in a PERSISTED explicit
|
|
236
|
+
* turn-binding table (`loadTurnBindingLog(sessionId)` — produced by headless
|
|
237
|
+
* runs of the same session, which anchor each turn at
|
|
238
|
+
* `conversationOffset = messages.length` measured just AFTER that turn's user
|
|
239
|
+
* message was appended). So the record for the user message at live index
|
|
240
|
+
* `turnIndex` is the one with `conversationOffset === turnIndex + 1`. Exact
|
|
241
|
+
* match only — anything else falls back to the marks-derived table rather than
|
|
242
|
+
* guessing (the offsets align because both runs rebuild the same persisted
|
|
243
|
+
* message sequence under a single leading system prompt).
|
|
244
|
+
*
|
|
245
|
+
* @param {import("./turn-binding.js").TurnBindingLog|null} persistedLog
|
|
246
|
+
* @param {number} turnIndex message index of the target user turn
|
|
247
|
+
* @returns {object|null} a turn view (with computed coverage), or null
|
|
248
|
+
*/
|
|
249
|
+
export function pickPersistedTurn(persistedLog, turnIndex) {
|
|
250
|
+
if (!persistedLog || typeof persistedLog.list !== "function") return null;
|
|
251
|
+
const want = Number(turnIndex) + 1;
|
|
252
|
+
if (!Number.isFinite(want)) return null;
|
|
253
|
+
for (const t of persistedLog.list()) {
|
|
254
|
+
if (Number(t.conversationOffset) === want) return t;
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Coverage-aware restore plan for a rewound turn. Prefers the PERSISTED
|
|
261
|
+
* explicit turn-binding table when the session has one for the target turn
|
|
262
|
+
* (cross-process honesty: a `--resume`d session's pre-resume turns have NO
|
|
263
|
+
* process-local marks, but their headless run persisted checkpoint ids, shell/
|
|
264
|
+
* side-effect flags and child agents — the explicit table answers where marks
|
|
265
|
+
* cannot). Falls back to the REPL's implicit state (`listUserTurns()` output +
|
|
266
|
+
* `{atMessageCount,id,tool}` marks) bridged into the same table shape. Either
|
|
267
|
+
* way `resolveRestorePlan` produces the honest plan, so `/rewind` can warn
|
|
268
|
+
* when a restore can't be fully promised (side-effects, a missing checkpoint,
|
|
269
|
+
* or a conversation/files drift) instead of silently doing a lossy rewind.
|
|
242
270
|
*
|
|
243
271
|
* @param {Array} messages live conversation (for listUserTurns)
|
|
244
272
|
* @param {Array<{atMessageCount:number,id:string,tool?:string}>} marks
|
|
245
273
|
* @param {number} turnIndex message index returned by rewindToTurn()
|
|
246
274
|
* @param {string} [scope] one of RESTORE_SCOPE (default BOTH)
|
|
275
|
+
* @param {{persistedLog?:object|null}} [opts] explicit table from
|
|
276
|
+
* `loadTurnBindingLog(sessionId)` (advisory; null → marks only)
|
|
247
277
|
* @returns {{scope, coverage, conversation, files, warnings:string[]}}
|
|
248
278
|
*/
|
|
249
279
|
export function buildRewindPlan(
|
|
@@ -251,7 +281,10 @@ export function buildRewindPlan(
|
|
|
251
281
|
marks,
|
|
252
282
|
turnIndex,
|
|
253
283
|
scope = RESTORE_SCOPE.BOTH,
|
|
284
|
+
{ persistedLog = null } = {},
|
|
254
285
|
) {
|
|
286
|
+
const persisted = pickPersistedTurn(persistedLog, turnIndex);
|
|
287
|
+
if (persisted) return resolveRestorePlan(persisted, scope);
|
|
255
288
|
const turns = listUserTurns(messages, { limit: 1000 });
|
|
256
289
|
const log = buildTurnBindingFromMarks(turns, marks);
|
|
257
290
|
const turn = log.get(`turn-${Number(turnIndex)}`);
|
|
@@ -288,18 +321,29 @@ export function renderRewindWarnings(plan) {
|
|
|
288
321
|
* @param {Array} messages live conversation (for listUserTurns)
|
|
289
322
|
* @param {Array<{atMessageCount:number,id:string,tool?:string}>} marks
|
|
290
323
|
* @param {number} turnIndex message index of the branch-point user turn
|
|
291
|
-
* @param {{parentSessionId?:string, seq?:number, writeIntent?:boolean
|
|
324
|
+
* @param {{parentSessionId?:string, seq?:number, writeIntent?:boolean,
|
|
325
|
+
* persistedLog?:object|null}} [opts] persistedLog: explicit table
|
|
326
|
+
* from `loadTurnBindingLog(sessionId)`, preferred over marks (same
|
|
327
|
+
* cross-process rationale as buildRewindPlan)
|
|
292
328
|
* @returns {object} the plan from planSessionBranch()
|
|
293
329
|
*/
|
|
294
330
|
export function buildBranchPlan(
|
|
295
331
|
messages,
|
|
296
332
|
marks,
|
|
297
333
|
turnIndex,
|
|
298
|
-
{
|
|
334
|
+
{
|
|
335
|
+
parentSessionId = null,
|
|
336
|
+
seq = 0,
|
|
337
|
+
writeIntent = false,
|
|
338
|
+
persistedLog = null,
|
|
339
|
+
} = {},
|
|
299
340
|
) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
341
|
+
let turn = pickPersistedTurn(persistedLog, turnIndex);
|
|
342
|
+
if (!turn) {
|
|
343
|
+
const turns = listUserTurns(messages, { limit: 1000 });
|
|
344
|
+
const log = buildTurnBindingFromMarks(turns, marks);
|
|
345
|
+
turn = log.get(`turn-${Number(turnIndex)}`);
|
|
346
|
+
}
|
|
303
347
|
return planSessionBranch({ parentSessionId, turn, seq, writeIntent });
|
|
304
348
|
}
|
|
305
349
|
|
|
@@ -336,6 +380,7 @@ export function renderBranchPlan(plan) {
|
|
|
336
380
|
* /rewind --files 3 → { command: "turn", n: 3, scope: FILES }
|
|
337
381
|
* /rewind 3 --branch → { command: "branch", n: 3, writeIntent: false }
|
|
338
382
|
* /rewind 3 --branch --write → { command: "branch", n: 3, writeIntent: true }
|
|
383
|
+
* /rewind 3 --both --yes → non-interactive file confirmation (IDE)
|
|
339
384
|
*
|
|
340
385
|
* An unknown flag is ignored (kept forgiving); a non-numeric, flag-less arg with
|
|
341
386
|
* no turn number falls back to `list` so a typo can't silently rewind — and a
|
|
@@ -354,6 +399,7 @@ export function parseRewindArg(arg) {
|
|
|
354
399
|
let scope = RESTORE_SCOPE.BOTH;
|
|
355
400
|
let branch = false;
|
|
356
401
|
let writeIntent = false;
|
|
402
|
+
let confirmed = false;
|
|
357
403
|
for (const t of tokens) {
|
|
358
404
|
if (t === "--files" || t === "--files-only") scope = RESTORE_SCOPE.FILES;
|
|
359
405
|
else if (
|
|
@@ -365,11 +411,13 @@ export function parseRewindArg(arg) {
|
|
|
365
411
|
else if (t === "--both") scope = RESTORE_SCOPE.BOTH;
|
|
366
412
|
else if (t === "--branch" || t === "--fork") branch = true;
|
|
367
413
|
else if (t === "--write" || t === "--write-intent") writeIntent = true;
|
|
414
|
+
else if (t === "--yes" || t === "-y") confirmed = true;
|
|
368
415
|
else if (n === null && /^\d+$/.test(t)) n = Number(t);
|
|
369
416
|
}
|
|
370
|
-
if (branch && n !== null)
|
|
417
|
+
if (branch && n !== null)
|
|
418
|
+
return { command: "branch", n, scope, writeIntent, confirmed };
|
|
371
419
|
if (n === null) return { command: "list", scope };
|
|
372
|
-
return { command: "turn", n, scope };
|
|
420
|
+
return { command: "turn", n, scope, confirmed };
|
|
373
421
|
}
|
|
374
422
|
|
|
375
423
|
/** Render the picker list (shared by /rewind and double-Esc). */
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RuntimeProvenanceLedger - Immutable audit trail for runtime provenance
|
|
3
|
+
*
|
|
4
|
+
* Records every context source, tool call, and response that flows through
|
|
5
|
+
* the CLI runtime. Provides hash-chained immutability guarantees.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
9
|
+
|
|
10
|
+
class RuntimeProvenanceLedger {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this._entries = [];
|
|
13
|
+
this._lastHash = options.genesisHash || "0000000000000000000000000000000000000000000000000000000000000000";
|
|
14
|
+
this._spanIndex = new Map();
|
|
15
|
+
this._traceIndex = new Map();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Record a context source read (RAG, file, MCP, etc.)
|
|
20
|
+
*/
|
|
21
|
+
recordRead(entry) {
|
|
22
|
+
return this._append({
|
|
23
|
+
type: "context_read",
|
|
24
|
+
timestamp: Date.now(),
|
|
25
|
+
source: entry.source,
|
|
26
|
+
span: entry.span,
|
|
27
|
+
traceId: entry.traceId,
|
|
28
|
+
content: entry.content,
|
|
29
|
+
tokens: entry.tokens || 0,
|
|
30
|
+
metadata: entry.metadata || {},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Record a tool call invocation
|
|
36
|
+
*/
|
|
37
|
+
recordToolCall(entry) {
|
|
38
|
+
return this._append({
|
|
39
|
+
type: "tool_call",
|
|
40
|
+
timestamp: Date.now(),
|
|
41
|
+
tool: entry.tool,
|
|
42
|
+
span: entry.span,
|
|
43
|
+
traceId: entry.traceId,
|
|
44
|
+
args: entry.args,
|
|
45
|
+
resultSummary: entry.resultSummary,
|
|
46
|
+
durationMs: entry.durationMs,
|
|
47
|
+
metadata: entry.metadata || {},
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Record an LLM prompt submission
|
|
53
|
+
*/
|
|
54
|
+
recordLLMCall(entry) {
|
|
55
|
+
return this._append({
|
|
56
|
+
type: "llm_call",
|
|
57
|
+
timestamp: Date.now(),
|
|
58
|
+
model: entry.model,
|
|
59
|
+
span: entry.span,
|
|
60
|
+
traceId: entry.traceId,
|
|
61
|
+
promptTokens: entry.promptTokens,
|
|
62
|
+
completionTokens: entry.completionTokens,
|
|
63
|
+
durationMs: entry.durationMs,
|
|
64
|
+
metadata: entry.metadata || {},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Record a hook execution
|
|
70
|
+
*/
|
|
71
|
+
recordHookExecution(entry) {
|
|
72
|
+
return this._append({
|
|
73
|
+
type: "hook_execution",
|
|
74
|
+
timestamp: Date.now(),
|
|
75
|
+
hookId: entry.hookId,
|
|
76
|
+
event: entry.event,
|
|
77
|
+
span: entry.span,
|
|
78
|
+
traceId: entry.traceId,
|
|
79
|
+
blocked: entry.blocked,
|
|
80
|
+
durationMs: entry.durationMs,
|
|
81
|
+
metadata: entry.metadata || {},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Generic record method for custom events
|
|
87
|
+
*/
|
|
88
|
+
record(type, entry = {}, source = "runtime") {
|
|
89
|
+
return this._append({
|
|
90
|
+
type,
|
|
91
|
+
timestamp: Date.now(),
|
|
92
|
+
source,
|
|
93
|
+
...entry,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Append entry with hash chaining for immutability
|
|
99
|
+
*/
|
|
100
|
+
_append(entry) {
|
|
101
|
+
const entryWithHash = {
|
|
102
|
+
...entry,
|
|
103
|
+
index: this._entries.length,
|
|
104
|
+
prevHash: this._lastHash,
|
|
105
|
+
};
|
|
106
|
+
// Compute hash
|
|
107
|
+
const hash = createHash("sha256")
|
|
108
|
+
.update(JSON.stringify(entryWithHash))
|
|
109
|
+
.digest("hex");
|
|
110
|
+
entryWithHash.hash = hash;
|
|
111
|
+
this._entries.push(entryWithHash);
|
|
112
|
+
this._lastHash = hash;
|
|
113
|
+
// Index
|
|
114
|
+
if (entry.traceId) {
|
|
115
|
+
if (!this._traceIndex.has(entry.traceId)) this._traceIndex.set(entry.traceId, []);
|
|
116
|
+
this._traceIndex.get(entry.traceId).push(entryWithHash.index);
|
|
117
|
+
}
|
|
118
|
+
if (entry.span) {
|
|
119
|
+
if (!this._spanIndex.has(entry.span)) this._spanIndex.set(entry.span, []);
|
|
120
|
+
this._spanIndex.get(entry.span).push(entryWithHash.index);
|
|
121
|
+
}
|
|
122
|
+
return entryWithHash;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Verify entire chain integrity
|
|
127
|
+
*/
|
|
128
|
+
verifyIntegrity() {
|
|
129
|
+
let currentHash = this._entries[0]?.prevHash || "0".repeat(64);
|
|
130
|
+
for (const entry of this._entries) {
|
|
131
|
+
const { hash, prevHash, ...rest } = entry;
|
|
132
|
+
if (prevHash !== currentHash) return false;
|
|
133
|
+
const computed = createHash("sha256").update(JSON.stringify({ ...rest, prevHash: currentHash })).digest("hex");
|
|
134
|
+
if (computed !== hash) return false;
|
|
135
|
+
currentHash = hash;
|
|
136
|
+
}
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get all entries for a trace
|
|
142
|
+
*/
|
|
143
|
+
getTraceEntries(traceId) {
|
|
144
|
+
const indices = this._traceIndex.get(traceId) || [];
|
|
145
|
+
return indices.map(i => this._entries[i]);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Get all entries for a span
|
|
150
|
+
*/
|
|
151
|
+
getSpanEntries(spanId) {
|
|
152
|
+
const indices = this._spanIndex.get(spanId) || [];
|
|
153
|
+
return indices.map(i => this._entries[i]);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get provenance chain for a response
|
|
158
|
+
*/
|
|
159
|
+
getProvenance(options = {}) {
|
|
160
|
+
if (options.traceId) return this.getTraceEntries(options.traceId);
|
|
161
|
+
if (options.span) return this.getSpanEntries(options.span);
|
|
162
|
+
return [...this._entries];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Export ledger to JSON (for persistence/debugging)
|
|
167
|
+
*/
|
|
168
|
+
export() {
|
|
169
|
+
return {
|
|
170
|
+
genesisHash: this._entries[0]?.prevHash,
|
|
171
|
+
lastHash: this._lastHash,
|
|
172
|
+
entries: this._entries,
|
|
173
|
+
verified: this.verifyIntegrity(),
|
|
174
|
+
exportedAt: new Date().toISOString(),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Flush pending entries to disk (called on exit)
|
|
180
|
+
*/
|
|
181
|
+
flush() {
|
|
182
|
+
// In production this would persist to disk; for now we're in-memory
|
|
183
|
+
return Promise.resolve(true);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Export global singleton instance
|
|
188
|
+
const runtimeProvenanceLedger = new RuntimeProvenanceLedger();
|
|
189
|
+
export default runtimeProvenanceLedger;
|
|
190
|
+
export { RuntimeProvenanceLedger, runtimeProvenanceLedger };
|
package/src/lib/session-hooks.js
CHANGED
|
@@ -569,3 +569,44 @@ export function getSessionHooksGovStatsV2() {
|
|
|
569
569
|
invocationsByStatus,
|
|
570
570
|
};
|
|
571
571
|
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Fire settings.json Stop hooks (observe-only) — fires at the END of EVERY
|
|
575
|
+
* agent turn/loop iteration, BEFORE waiting for the next user input.
|
|
576
|
+
* Aligns with Claude Code hook spec.
|
|
577
|
+
*
|
|
578
|
+
* Fires regardless of outcome: normal completion, error, max iterations,
|
|
579
|
+
* user interrupt, or tool stop. Does NOT fire on final process exit
|
|
580
|
+
* (use SessionEnd/Shutdown hooks for that case).
|
|
581
|
+
*
|
|
582
|
+
* @param {Object} opts
|
|
583
|
+
* @param {string} opts.sessionId - Current session ID
|
|
584
|
+
* @param {string} opts.transcriptPath - Path to transcript file
|
|
585
|
+
* @param {string} [opts.stopReason="end_turn"] - Reason the turn stopped:
|
|
586
|
+
* "end_turn", "error", "max_iterations", "user_interrupt", "tool_stop"
|
|
587
|
+
* @param {string} [opts.finalResponse=""] - Final assistant response text
|
|
588
|
+
* @param {number} [opts.toolCount=0] - Number of tool calls made this turn
|
|
589
|
+
* @param {number} [opts.durationMs=0] - Turn duration in milliseconds
|
|
590
|
+
* @param {Error|string} [opts.error] - Error object if stopReason is "error"
|
|
591
|
+
* @returns {Promise<Array>} Hook execution results
|
|
592
|
+
*/
|
|
593
|
+
export async function fireSessionStop({
|
|
594
|
+
sessionId,
|
|
595
|
+
transcriptPath,
|
|
596
|
+
stopReason = "end_turn",
|
|
597
|
+
finalResponse = "",
|
|
598
|
+
toolCount = 0,
|
|
599
|
+
durationMs = 0,
|
|
600
|
+
error = null,
|
|
601
|
+
}) {
|
|
602
|
+
return runSettingsHooks("Stop", {
|
|
603
|
+
session_id: sessionId,
|
|
604
|
+
transcript_path: transcriptPath,
|
|
605
|
+
stop_reason: stopReason,
|
|
606
|
+
final_response: finalResponse,
|
|
607
|
+
tool_count: toolCount,
|
|
608
|
+
duration_ms: durationMs,
|
|
609
|
+
error: error ? String(error.message ?? error) : "",
|
|
610
|
+
timestamp: new Date().toISOString(),
|
|
611
|
+
});
|
|
612
|
+
}
|
package/src/lib/turn-binding.js
CHANGED
|
@@ -183,6 +183,35 @@ export class TurnBindingLog {
|
|
|
183
183
|
return this;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
/**
|
|
187
|
+
* Drop every turn anchored AT or AFTER `offset` — the timeline-supersede rule
|
|
188
|
+
* for a live producer. After a `/rewind`, `/clear`, or compaction shrinks the
|
|
189
|
+
* live conversation, a new turn re-anchors at an offset an OLD record may
|
|
190
|
+
* still hold; under exact-offset matching (see `pickPersistedTurn`) that stale
|
|
191
|
+
* record would shadow the new one and could offer the wrong checkpoint. The
|
|
192
|
+
* discarded-timeline records are pruned instead — correctness over advisory
|
|
193
|
+
* completeness.
|
|
194
|
+
*
|
|
195
|
+
* @param {number} offset conversation offset the new timeline starts at
|
|
196
|
+
* @returns {number} how many records were removed
|
|
197
|
+
*/
|
|
198
|
+
pruneFromOffset(offset) {
|
|
199
|
+
const at = Number(offset);
|
|
200
|
+
if (!Number.isFinite(at)) return 0;
|
|
201
|
+
let removed = 0;
|
|
202
|
+
this._order = this._order.filter((id) => {
|
|
203
|
+
const rec = this._turns.get(id);
|
|
204
|
+
const off = rec ? Number(rec.conversationOffset) : NaN;
|
|
205
|
+
if (Number.isFinite(off) && off >= at) {
|
|
206
|
+
this._turns.delete(id);
|
|
207
|
+
removed += 1;
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
return true;
|
|
211
|
+
});
|
|
212
|
+
return removed;
|
|
213
|
+
}
|
|
214
|
+
|
|
186
215
|
/** One turn record with its computed coverage (or null). */
|
|
187
216
|
get(turnId) {
|
|
188
217
|
const rec = this._turns.get(String(turnId));
|
|
@@ -395,3 +424,108 @@ export function buildTurnBindingFromMarks(turns = [], marks = []) {
|
|
|
395
424
|
}
|
|
396
425
|
return log;
|
|
397
426
|
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Stateful feeder turning raw agent-loop events into TurnBindingLog records —
|
|
430
|
+
* the SHARED producer core for every runner (headless and the interactive
|
|
431
|
+
* REPL), so id synthesis and the event→table mapping can never drift between
|
|
432
|
+
* them. Provider tool_use ids are not visible on the yielded event stream, so
|
|
433
|
+
* tool-call ids are synthesized `<nonce>:c<seq>` and turn ids `<nonce>:t<seq>`
|
|
434
|
+
* (mirrors the side-effect ledger's op-id scheme — stable within a run, unique
|
|
435
|
+
* across resumes as long as `nonce` is per-run unique).
|
|
436
|
+
*
|
|
437
|
+
* Dirty-gating: `beginTurn` alone never marks the table dirty, so a tool-free
|
|
438
|
+
* turn costs the caller zero persistence writes; only a recorded signal
|
|
439
|
+
* (checkpoint / tool call / permission decision / child agent / a supersede
|
|
440
|
+
* prune) does.
|
|
441
|
+
*
|
|
442
|
+
* @param {{log?: TurnBindingLog, nonce?: string}} [opts]
|
|
443
|
+
*/
|
|
444
|
+
export function createTurnBindingFeed({
|
|
445
|
+
log = new TurnBindingLog(),
|
|
446
|
+
nonce = "run",
|
|
447
|
+
} = {}) {
|
|
448
|
+
let turnSeq = 0;
|
|
449
|
+
let callSeq = 0;
|
|
450
|
+
let currentTurnId = null;
|
|
451
|
+
let currentCallId = null;
|
|
452
|
+
let dirty = false;
|
|
453
|
+
return {
|
|
454
|
+
log,
|
|
455
|
+
/**
|
|
456
|
+
* Anchor a new turn at `conversationOffset` (= messages.length measured
|
|
457
|
+
* just AFTER the turn's user message was appended — the exact-match
|
|
458
|
+
* contract `pickPersistedTurn` relies on). `supersede: true` first prunes
|
|
459
|
+
* records at/after that offset (live-REPL rewind/clear/compaction rule);
|
|
460
|
+
* append-only runners leave it off. `worktreeId` stamps the run's isolation
|
|
461
|
+
* worktree onto the record (does NOT mark dirty by itself — a tool-free
|
|
462
|
+
* turn in a worktree still writes nothing).
|
|
463
|
+
*/
|
|
464
|
+
beginTurn(
|
|
465
|
+
conversationOffset,
|
|
466
|
+
{ supersede = false, worktreeId = null } = {},
|
|
467
|
+
) {
|
|
468
|
+
if (supersede && log.pruneFromOffset(conversationOffset) > 0) {
|
|
469
|
+
dirty = true;
|
|
470
|
+
}
|
|
471
|
+
currentTurnId = `${nonce}:t${turnSeq++}`;
|
|
472
|
+
log.startTurn(currentTurnId, { conversationOffset });
|
|
473
|
+
if (worktreeId != null) log.setWorktree(currentTurnId, worktreeId);
|
|
474
|
+
return currentTurnId;
|
|
475
|
+
},
|
|
476
|
+
currentTurnId: () => currentTurnId,
|
|
477
|
+
/** Fold one agent-loop event into the current turn's record. */
|
|
478
|
+
handleEvent(event) {
|
|
479
|
+
if (!event || !currentTurnId) return dirty;
|
|
480
|
+
const turnId = currentTurnId;
|
|
481
|
+
switch (event.type) {
|
|
482
|
+
case "checkpoint":
|
|
483
|
+
if (event.id != null) {
|
|
484
|
+
log.bindCheckpoint(turnId, event.id);
|
|
485
|
+
dirty = true;
|
|
486
|
+
}
|
|
487
|
+
break;
|
|
488
|
+
case "tool-executing": {
|
|
489
|
+
const callId = `${nonce}:c${callSeq++}`;
|
|
490
|
+
currentCallId = callId;
|
|
491
|
+
log.recordToolCall(turnId, callId, { name: event.tool });
|
|
492
|
+
dirty = true;
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
case "tool-result": {
|
|
496
|
+
// A gated tool surfaces its permission decision on `result.policy`;
|
|
497
|
+
// re-derive a stable decision id feeder-side (core's own id hangs
|
|
498
|
+
// off the provider call id, which the event stream does not expose).
|
|
499
|
+
const policy = event.result?.policy;
|
|
500
|
+
if (policy && (policy.via || policy.decision)) {
|
|
501
|
+
log.recordPermissionDecision(
|
|
502
|
+
turnId,
|
|
503
|
+
`${currentCallId || "call"}:perm:${policy.via || policy.decision}`,
|
|
504
|
+
);
|
|
505
|
+
dirty = true;
|
|
506
|
+
}
|
|
507
|
+
// Foreground spawn_sub_agent reports its child id on the result.
|
|
508
|
+
if (event.result?.subAgentId) {
|
|
509
|
+
log.recordChildAgent(turnId, event.result.subAgentId);
|
|
510
|
+
dirty = true;
|
|
511
|
+
}
|
|
512
|
+
currentCallId = null;
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
case "background-sub-agent-result":
|
|
516
|
+
if (event.subAgentId) {
|
|
517
|
+
log.recordChildAgent(turnId, event.subAgentId);
|
|
518
|
+
dirty = true;
|
|
519
|
+
}
|
|
520
|
+
break;
|
|
521
|
+
default:
|
|
522
|
+
break;
|
|
523
|
+
}
|
|
524
|
+
return dirty;
|
|
525
|
+
},
|
|
526
|
+
isDirty: () => dirty,
|
|
527
|
+
clearDirty() {
|
|
528
|
+
dirty = false;
|
|
529
|
+
},
|
|
530
|
+
};
|
|
531
|
+
}
|
package/src/program-base.js
CHANGED
|
@@ -19,6 +19,9 @@ export function createBaseProgram() {
|
|
|
19
19
|
)
|
|
20
20
|
.version(VERSION, "-v, --version")
|
|
21
21
|
.option("--verbose", "Enable verbose output")
|
|
22
|
-
.option("--quiet", "Suppress non-essential output")
|
|
22
|
+
.option("--quiet", "Suppress non-essential output")
|
|
23
|
+
// M5/M6 Runtime Convergence options
|
|
24
|
+
.option("--jsii-runtime <runtime>", "JSII runtime to use: native|quickjs", "native")
|
|
25
|
+
.option("--otlp-endpoint <endpoint>", "OTLP/HTTP endpoint for trace export (e.g. http://localhost:4318)");
|
|
23
26
|
return program;
|
|
24
27
|
}
|
package/src/repl/agent-repl.js
CHANGED
|
@@ -76,6 +76,8 @@ import {
|
|
|
76
76
|
fireSessionHook,
|
|
77
77
|
fireUserPromptSubmit,
|
|
78
78
|
fireAssistantResponse,
|
|
79
|
+
fireSessionNotification,
|
|
80
|
+
fireSessionStop,
|
|
79
81
|
} from "../lib/session-hooks.js";
|
|
80
82
|
import { HookEvents } from "../lib/hook-manager.js";
|
|
81
83
|
import { IterationBudget } from "../lib/iteration-budget.js";
|
|
@@ -295,6 +297,17 @@ export async function agentLoop(messages, options) {
|
|
|
295
297
|
autoCompact: false,
|
|
296
298
|
onProviderFallback,
|
|
297
299
|
})) {
|
|
300
|
+
// P1 explicit turn→checkpoint binding — the REPL as PRODUCER: fold every
|
|
301
|
+
// loop event into the live table (checkpoint / tool / policy / child
|
|
302
|
+
// agent), mirroring the headless runner's feed. Advisory: a feeder failure
|
|
303
|
+
// must never disturb the turn. Absent by default → byte-identical.
|
|
304
|
+
if (options.turnBindingFeed) {
|
|
305
|
+
try {
|
|
306
|
+
options.turnBindingFeed.handleEvent(event);
|
|
307
|
+
} catch {
|
|
308
|
+
/* advisory — never break the turn */
|
|
309
|
+
}
|
|
310
|
+
}
|
|
298
311
|
if (event.type === "checkpoint") {
|
|
299
312
|
// Remember which file snapshot lines up with the live conversation so
|
|
300
313
|
// `/rewind <n>` can restore code + conversation together (Claude-Code
|
|
@@ -1078,6 +1091,11 @@ export async function startAgentRepl(options = {}) {
|
|
|
1078
1091
|
// it back (Claude-Code 2.1.191). Nulled on a context swap so a stash from one
|
|
1079
1092
|
// session can't be restored into another. { messages, marks } | null.
|
|
1080
1093
|
let _clearedConversation = null;
|
|
1094
|
+
// P1 explicit turn→checkpoint binding — the REPL as PRODUCER (mirrors the
|
|
1095
|
+
// headless runner's feed via the shared feeder core): one record per
|
|
1096
|
+
// interactive turn, persisted dirty-gated at each settle. Lazily created on
|
|
1097
|
+
// the first prompt turn of a persistable (JSONL) session; advisory only.
|
|
1098
|
+
let _turnBindingProducer = null;
|
|
1081
1099
|
// Apply --output-style or the settings.json `outputStyle` default at startup.
|
|
1082
1100
|
try {
|
|
1083
1101
|
const { resolveOutputStyle } = await import("../lib/output-styles.js");
|
|
@@ -3120,6 +3138,7 @@ export async function startAgentRepl(options = {}) {
|
|
|
3120
3138
|
buildBranchPlan,
|
|
3121
3139
|
renderBranchPlan,
|
|
3122
3140
|
parseRewindArg,
|
|
3141
|
+
pickPersistedTurn,
|
|
3123
3142
|
RESTORE_SCOPE,
|
|
3124
3143
|
} = await import("../lib/repl-rewind.js");
|
|
3125
3144
|
const arg = trimmed.slice("/rewind".length).trim();
|
|
@@ -3127,6 +3146,24 @@ export async function startAgentRepl(options = {}) {
|
|
|
3127
3146
|
// --files / --both). Claude-Code parity: a rewind can restore the
|
|
3128
3147
|
// conversation only, the files only, or both.
|
|
3129
3148
|
const parsed = parseRewindArg(arg);
|
|
3149
|
+
// P1 turn/checkpoint binding: load the session's PERSISTED explicit
|
|
3150
|
+
// turn-binding table (headless runs of this session feed it). A
|
|
3151
|
+
// --resume'd session's pre-resume turns have no process-local marks —
|
|
3152
|
+
// the explicit table is the only source of their checkpoint ids and
|
|
3153
|
+
// side-effect coverage. Best-effort advisory: never blocks /rewind.
|
|
3154
|
+
let _persistedBinding = null;
|
|
3155
|
+
if (
|
|
3156
|
+
sessionId &&
|
|
3157
|
+
(parsed.command === "turn" || parsed.command === "branch")
|
|
3158
|
+
) {
|
|
3159
|
+
try {
|
|
3160
|
+
const { loadTurnBindingLog } =
|
|
3161
|
+
await import("../lib/turn-binding-store.js");
|
|
3162
|
+
_persistedBinding = loadTurnBindingLog(sessionId);
|
|
3163
|
+
} catch {
|
|
3164
|
+
/* marks-only fallback */
|
|
3165
|
+
}
|
|
3166
|
+
}
|
|
3130
3167
|
if (parsed.command === "clear") {
|
|
3131
3168
|
// Restore the conversation stashed by /clear (Claude-Code 2.1.191).
|
|
3132
3169
|
const r = restoreClearedConversation(
|
|
@@ -3173,6 +3210,7 @@ export async function startAgentRepl(options = {}) {
|
|
|
3173
3210
|
{
|
|
3174
3211
|
parentSessionId: sessionId,
|
|
3175
3212
|
writeIntent: parsed.writeIntent,
|
|
3213
|
+
persistedLog: _persistedBinding,
|
|
3176
3214
|
},
|
|
3177
3215
|
);
|
|
3178
3216
|
if (!plan || !plan.branchSessionId) {
|
|
@@ -3277,6 +3315,7 @@ export async function startAgentRepl(options = {}) {
|
|
|
3277
3315
|
_checkpointMarks,
|
|
3278
3316
|
turnIndex,
|
|
3279
3317
|
scope,
|
|
3318
|
+
{ persistedLog: _persistedBinding },
|
|
3280
3319
|
);
|
|
3281
3320
|
for (const line of renderRewindWarnings(plan))
|
|
3282
3321
|
logger.log(chalk.yellow(line));
|
|
@@ -3286,11 +3325,21 @@ export async function startAgentRepl(options = {}) {
|
|
|
3286
3325
|
// 3) File restore (skipped for conversation-only). Match the turn to
|
|
3287
3326
|
// the snapshot taken just before it first mutated the tree, then
|
|
3288
3327
|
// offer to roll the working tree back to it (undoable — the restore
|
|
3289
|
-
// takes its own safety checkpoint first).
|
|
3290
|
-
//
|
|
3291
|
-
|
|
3328
|
+
// takes its own safety checkpoint first). Process-local marks win;
|
|
3329
|
+
// a --resume'd turn with no marks falls back to the checkpoint id
|
|
3330
|
+
// the PERSISTED binding table recorded for it (same checkpoint
|
|
3331
|
+
// store, so cross-process file rewind actually works — restore
|
|
3332
|
+
// still fails soft if the snapshot is gone). Prune dropped-turn
|
|
3333
|
+
// marks only when the conversation was actually truncated.
|
|
3334
|
+
let cp = restoreFiles
|
|
3292
3335
|
? pickCheckpointForTurn(_checkpointMarks, turnIndex)
|
|
3293
3336
|
: null;
|
|
3337
|
+
if (!cp && restoreFiles) {
|
|
3338
|
+
const persisted = pickPersistedTurn(_persistedBinding, turnIndex);
|
|
3339
|
+
if (persisted?.fileCheckpointId) {
|
|
3340
|
+
cp = { id: persisted.fileCheckpointId, fromPersisted: true };
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3294
3343
|
if (rewindConversation)
|
|
3295
3344
|
pruneMarksAfter(_checkpointMarks, turnIndex);
|
|
3296
3345
|
if (cp) {
|
|
@@ -5159,6 +5208,25 @@ export async function startAgentRepl(options = {}) {
|
|
|
5159
5208
|
const _userMsg = { role: "user", content: _userMessageContent };
|
|
5160
5209
|
messages.push(_userMsg);
|
|
5161
5210
|
|
|
5211
|
+
// P1 turn→checkpoint binding: anchor this turn in the explicit table at
|
|
5212
|
+
// `conversationOffset = messages.length` measured just AFTER the user
|
|
5213
|
+
// message was appended (the exact-match contract pickPersistedTurn relies
|
|
5214
|
+
// on — same anchor as the headless runner). Only JSONL sessions can
|
|
5215
|
+
// persist the table; the producer supersedes stale records from a
|
|
5216
|
+
// discarded timeline (post-/rewind, /clear, compaction) itself.
|
|
5217
|
+
if (sessionId && useJsonl) {
|
|
5218
|
+
try {
|
|
5219
|
+
if (!_turnBindingProducer) {
|
|
5220
|
+
const { createReplTurnBindingProducer } =
|
|
5221
|
+
await import("./repl-turn-binding.js");
|
|
5222
|
+
_turnBindingProducer = createReplTurnBindingProducer({ sessionId });
|
|
5223
|
+
}
|
|
5224
|
+
_turnBindingProducer?.beginTurn(messages.length);
|
|
5225
|
+
} catch {
|
|
5226
|
+
/* advisory — never disturb the turn */
|
|
5227
|
+
}
|
|
5228
|
+
}
|
|
5229
|
+
|
|
5162
5230
|
// Slot-filling: detect intent and fill missing parameters interactively
|
|
5163
5231
|
try {
|
|
5164
5232
|
const { CLISlotFiller } = await import("../lib/slot-filler.js");
|
|
@@ -5368,6 +5436,9 @@ export async function startAgentRepl(options = {}) {
|
|
|
5368
5436
|
autoCheckpoint,
|
|
5369
5437
|
checkpointSession: sessionId,
|
|
5370
5438
|
checkpointMarks: _checkpointMarks,
|
|
5439
|
+
// Explicit turn-binding producer (null unless a JSONL session): the
|
|
5440
|
+
// wrapper folds every loop event into the live table.
|
|
5441
|
+
turnBindingFeed: _turnBindingProducer,
|
|
5371
5442
|
denialLog: _recentDenials,
|
|
5372
5443
|
persistRecentDenials: true,
|
|
5373
5444
|
permissionMode: _sessionTier,
|
|
@@ -5589,6 +5660,9 @@ export async function startAgentRepl(options = {}) {
|
|
|
5589
5660
|
// Non-critical
|
|
5590
5661
|
}
|
|
5591
5662
|
}
|
|
5663
|
+
// Persist the explicit turn-binding table for this settled turn
|
|
5664
|
+
// (dirty-gated: a tool-free Q&A turn writes nothing; never throws).
|
|
5665
|
+
_turnBindingProducer?.persistIfDirty();
|
|
5592
5666
|
// Auto-compact when context grows too large
|
|
5593
5667
|
if (
|
|
5594
5668
|
feature("PROMPT_COMPRESSOR") &&
|