chainlesschain 0.163.6 → 0.163.7
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/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DDIUofA8.js → AIOps-axqQfwv8.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Bm-5xYRn.js → ActionButton-DV-Ro0By.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-D5UH4ljI.js → Analytics-Bt72WEVu.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-D_ifjOOP.js → AppLayout-BtepG55E.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BY1mXdC7.js → Artifacts-B7KaJ7S_.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Bujr4Ml3.js → Audit-FNFaUg7A.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-sioKSTcC.js → BackgroundAgents-DFb6J2wQ.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DDOAK8RK.js → Backup-CC7YBhyj.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CtCsVVke.js → BaseInput-RkgTNVtj.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DpNj0vnd.js → Chat-hHEmGZy-.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZta3Ftr.js → ChatBubbleRenderer-CAP9YhHv.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DoHVNSXq.js → Checkbox-z5wXOQP1.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DlVl7Qmv.js → Codegen-DVaDJnxy.js} +1 -1
- package/src/assets/web-panel/assets/{Col-GIqoPYty.js → Col-CJmaRDR5.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CIAPEVji.js → Community-DF29_2bI.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BN7JiI_h.js → Compact-DtkQkBzn.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CqUWCM9B.js → Compliance-xoHDxp7I.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-A4iPp7A4.js → Cowork-BUm-R7ht.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-B2zeXXWb.js → Cron-Cgl14ZkB.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BR2xmWr8.js → Crosschain-BNcbmoqh.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BzFDjrIv.js → DID-Bwja0Zo9.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DKtDo3yA.js → Dashboard-Bk5XjEdJ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-RNx43_YG.js → Dropdown-Ci36Oitq.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-SwMt4Rcy.js → EmailListRenderer--U04ZN4q.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CsYebZ4X.js → FamilyGuardDashboard-B2gNPR2s.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BmRKvBTy.js → Federation-C4KghYn7.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CVxGTA-0.js → FormItemContext-QL7x_Q_0.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-x2iXpyB7.js → GenericCardRenderer-F3Ryltwm.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DfqbkiOP.js → Git-2HassK5k.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-RuHyyj5O.js → Governance-DoBIp7oe.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Bn7uZ7rI.js → Inference--W01QhZ-.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DlP7XefZ.js → KnowledgeGraph-DmvwqGpT.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BYNdOiCd.js → Logs-BBJ4yAel.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-aMtgN-R6.js → MarkdownRenderer-BLCZMzhJ.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-JQYmDqM8.js → Marketplace-D5lNngSI.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Cyx4yZ-9.js → McpTools-CkEVOMKH.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-VDuGZTN2.js → Memory-B1Ow1xVE.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-_yKo97gi.js → MobileBridge-SgYbUfb6.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CXRXW3q7.js → MobileProjects-BWdOvQAj.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-acTGtfLH.js → Mtc-DcTXSSma.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-ClpjXfkY.js → MtcAudit-Dt3WG4oj.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-_6Tn3o96.js → Multisig-B9B38UZx.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CK7pbZzP.js → NLProgramming-B0S4GGvG.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Czy6V_P8.js → Notes-DZ5U58Ng.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Cqx-UVv3.js → NotificationSettings-PjL5ZW34.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-C69WDxGy.js → OrderTableRenderer-DrAX2ESt.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DFnA9vLQ.js → Organization-Cv1GYs-c.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-ReZkk2yT.js → Overflow-Bbkp_PL3.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DzjSI0aI.js → P2P-BD8QUgoZ.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DKTf5xIP.js → PdhVaultBrowser-BO-2PeaB.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DhQ5klhR.js → Permissions-D_OPVa02.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CUL1iHrW.js → PersonalDataHub-mh-pEmaB.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-DU88iu4N.js → Pipeline-6JC2EDdb.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Bw-sYfyK.js → Privacy-CUshzUAG.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Bq3Q5y0z.js → ProjectInit-Cs2Aalri.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C8spzpho.js → ProjectSettings-1ak49tcj.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BV4oApsm.js +1 -0
- package/src/assets/web-panel/assets/{Providers-Bf4eTP9W.js → Providers-CXXxvafH.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Dme9BDU1.js → QrScannerModal-PTcDsF58.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CIXfv1rE.js → QuickAsk-CDwucrJw.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CIxvdeZ-.js → Recommend-CJlLVRXR.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C6k7MExI.js → RemoteSession-DlN-ETsO.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CISDz2bl.js → Reputation-DI23LR6m.js} +1 -1
- package/src/assets/web-panel/assets/{Row-CdnDxXyf.js → Row-Cl7UQMrM.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C-QpRHsl.js → RssFeed-CexHNfh8.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DBjaGzSn.js → Search-BneBWvRN.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CQKukOeE.js → Security-DX7Phgrp.js} +4 -4
- package/src/assets/web-panel/assets/{Services-DZz86jWb.js → Services-BWXO8Mte.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-jb8RMK0d.js → Skeleton-DCyOjKPJ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CdY76fZG.js → Skills-CXqERsTB.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-C_CSiZX9.js → Sla-bhbrbz8E.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-3IPU0HBZ.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-qvimGO0O.js → SyncSettings-DBNTQLbM.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Dng5LPJC.js → Tasks-quP0bkN-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Bwqq5Wll.js → Templates-BouWmgho.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DNWuwP7Z.js → Tenant-qv0D9s7z.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B-6Othpp.js → Terminal-BxvbE8BB.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DKpKZfzx.js → TimelineRenderer-D-IeLnJ2.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DzfGMd2J.js → Tokens-CFMKKyVG.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BXwUi74J.js → Trigger-L8as3UuJ.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BNzUq4ui.js → Trust-MxmA4VZG.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BYugxww4.js → UkeySign-C0VvJYRi.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DdauhtSJ.js → VideoEditing-Bf5QZj8N.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CSC9bCe_.js → Wallet-BIJ9riRz.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Q8AN3bTO.js → WebAuthn-B8p9Mhkw.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-d-LBGCuA.js → WorkflowEditor-BVsuaPen.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BLVDCgsZ.js → chat-BfkjoKpV.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BZlPSEHl.js → colors-DeAlsWK8.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-IWseP6R6.js → compact-item-Cs97bz5j.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DnVRV3Ao.js → createContext-BqbG60LH.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DPty0mGN.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DLQZA_Wn.js → hasIn-i_RY0b1X.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_N4-Gq9.js → index-0-agYlWL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DB0MJgF_.js → index-79V_zv1s.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOZJBTWZ.js → index-B-3tEpTc.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUq0c7gq.js → index-B-JVTrpr.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dy0R5ZED.js → index-B1CbOH1i.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bxp7uDD8.js → index-B5UkyA3c.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpVrK6fr.js → index-BESLFt4q.js} +1 -1
- package/src/assets/web-panel/assets/{index-CT9vVN-X.js → index-BOVkf1K3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DOoQpuQY.js → index-BYUVuwKt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CINJQEj6.js → index-BcUXAB2s.js} +1 -1
- package/src/assets/web-panel/assets/{index-CarMTIyQ.js → index-BkBi5TmC.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5dgnilC.js → index-Bm7lLR4c.js} +1 -1
- package/src/assets/web-panel/assets/index-Bt7g0Vq_.js +1 -0
- package/src/assets/web-panel/assets/{index-CbQNL0ue.js → index-BuLQobzW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpBaJ3TD.js → index-CE2VtTlp.js} +1 -1
- package/src/assets/web-panel/assets/{index-jWb70ZgZ.js → index-CED2PL_y.js} +1 -1
- package/src/assets/web-panel/assets/{index-BEu2lMBy.js → index-CG3PRyAL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DocEhinm.js → index-CIG8fQ2_.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_LQodeJ.js → index-COdGA-8n.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZpYeqrh.js → index-CeWoNHau.js} +1 -1
- package/src/assets/web-panel/assets/{index-gt8bNefW.js → index-Cf3WCLmy.js} +1 -1
- package/src/assets/web-panel/assets/{index-N_GoWZX4.js → index-CgsqdnRV.js} +1 -1
- package/src/assets/web-panel/assets/index-ChVT0p9u.js +1 -0
- package/src/assets/web-panel/assets/{index-CZDekj7T.js → index-D2B4VPyP.js} +1 -1
- package/src/assets/web-panel/assets/{index-YSw_gxlZ.js → index-D9evtvg2.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTbxp4UH.js → index-DUUrv141.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bbj2NS_I.js → index-DiSkx4UB.js} +3 -3
- package/src/assets/web-panel/assets/{index-BZs5bBnk.js → index-Dk2qouAz.js} +1 -1
- package/src/assets/web-panel/assets/{index-DalA0TKC.js → index-Dk7-v4hP.js} +1 -1
- package/src/assets/web-panel/assets/{index-1jeAudHd.js → index-DmbfhJUP.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1ifZtXo.js → index-DqBgBpgp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhA3QEni.js → index-PT_5sBMA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BSjilZ9L.js → index-U4f5izHz.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2FqfMvU.js → index-YXgLng0K.js} +1 -1
- package/src/assets/web-panel/assets/{index-COev0W2g.js → index-ZxO_-E8a.js} +1 -1
- package/src/assets/web-panel/assets/{index-M7wxfAbm.js → index-dIHr4B_u.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjWnONdy.js → index-gyFpvL7l.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dkwsi1Kn.js → index-s1opAL-M.js} +1 -1
- package/src/assets/web-panel/assets/{index-hwVDeoC0.js → index-yObj1O4n.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-Co67Kqmb.js → initDefaultProps-C9h9yKL3.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BMOxQSF_.js → motion-BvmLsPdu.js} +1 -1
- package/src/assets/web-panel/assets/{move-C5fWDw-N.js → move-CoL4ekU1.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BJevpL0e.js → mtc-parser-C4pMy6xV.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BCzNU8s6.js → omit-DPuJfNxx.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CybWvDPk.js → pickAttrs-D0KEeaK2.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BMWS9HnW.js → placementArrow-BpkyINxt.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BoL2UnGY.js → responsiveObserve-DQI_wOq8.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DWNfXraU.js → slide-DYfdDiRg.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BquTM3tX.js → statusUtils-B5dtvb0D.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BEkCleim.js → styleChecker-CF310mSj.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Oecnq7EA.js → useFlexGapSupport-D1Qp4BLp.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CkDffJ4F.js → useFs-C7_iyvJK.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-qbvKXesB.js → usePersonalDataHub-DZUt_fIm.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-C6v8n6mA.js → vnode-D3dsi0lZ.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-4CTeFILV.js → zoom-WdbOlvdQ.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +2 -2
- package/src/commands/agent.js +90 -1
- package/src/commands/chat.js +11 -2
- package/src/commands/compact.js +106 -23
- package/src/commands/loop.js +28 -10
- package/src/commands/routine.js +1 -1
- package/src/commands/scheduler-daemon.js +359 -2
- package/src/commands/session-observability.js +305 -0
- package/src/commands/session.js +38 -0
- package/src/data/changelog.json +14 -1
- package/src/gateways/ws/ws-agent-handler.js +439 -5
- package/src/harness/jsonl-session-store.js +463 -125
- package/src/harness/prompt-compressor.js +86 -13
- package/src/harness/provider-backed-compaction.js +100 -27
- package/src/lib/advisor-runtime.js +3 -1
- package/src/lib/agent-schedule-store.js +414 -4
- package/src/lib/agent-team/team-adjudication.js +40 -1
- package/src/lib/automation-engine.js +494 -0
- package/src/lib/background-agent-supervisor.js +793 -215
- package/src/lib/background-command-argv.js +24 -0
- package/src/lib/background-interaction-journal.js +361 -52
- package/src/lib/background-interaction-resolver.js +32 -0
- package/src/lib/background-launch-profile.js +2 -0
- package/src/lib/causal-observability.js +2661 -0
- package/src/lib/chat-session-admission.js +76 -0
- package/src/lib/cost-budget.js +5 -7
- package/src/lib/cowork/ab-comparator-cli.js +11 -0
- package/src/lib/cowork/debate-review-cli.js +12 -0
- package/src/lib/cowork-adapter.js +159 -38
- package/src/lib/cowork-cron.js +357 -1
- package/src/lib/cowork-task-runner.js +112 -17
- package/src/lib/delivery-coordinator.js +142 -0
- package/src/lib/direct-model-usage.js +121 -0
- package/src/lib/file-lines.js +183 -47
- package/src/lib/llm-pricing.js +12 -17
- package/src/lib/loop.js +26 -2
- package/src/lib/routine-store.js +380 -21
- package/src/lib/runtime-usage-ledger.js +182 -0
- package/src/lib/scheduler-kernel/agenda-adapter.js +308 -3
- package/src/lib/scheduler-kernel/automation-adapter.js +410 -8
- package/src/lib/scheduler-kernel/automation-event-adapter.js +36 -0
- package/src/lib/scheduler-kernel/cowork-cron-adapter.js +285 -3
- package/src/lib/scheduler-kernel/loop-adapter.js +541 -6
- package/src/lib/scheduler-kernel/migration-admin.js +346 -0
- package/src/lib/scheduler-kernel/migration-rollback-admin.js +932 -0
- package/src/lib/scheduler-kernel/routine-adapter.js +394 -20
- package/src/lib/scheduler-kernel/runtime.js +63 -1
- package/src/lib/scheduler-kernel/source-locator-path.js +142 -0
- package/src/lib/scheduler-kernel/store.js +2586 -85
- package/src/lib/session-host-lease.js +171 -2
- package/src/lib/session-host-snapshot.js +14 -3
- package/src/lib/session-persistence-failure.js +1 -0
- package/src/lib/session-tail.js +179 -34
- package/src/lib/session-transcript-structure.js +96 -0
- package/src/lib/sub-agent-context.js +328 -22
- package/src/lib/ws-agent-handler.js +4 -1
- package/src/repl/agent-repl.js +732 -91
- package/src/repl/chat-repl.js +5 -1
- package/src/runtime/agent-core.js +1272 -207
- package/src/runtime/fallback-model.js +5 -0
- package/src/runtime/headless-runner.js +324 -82
- package/src/runtime/headless-stream.js +232 -26
- package/src/runtime/policies/agent-policy.js +4 -0
- package/src/workers/background-agent-worker.js +403 -96
- package/src/assets/web-panel/assets/Projects-Dd-xuPIc.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-C7tT9iad.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Jzu62CLC.js +0 -1
- package/src/assets/web-panel/assets/index-Zb2MeDSy.js +0 -1
- package/src/assets/web-panel/assets/index-fikqml4k.js +0 -1
|
@@ -316,3 +316,27 @@ export function stripFirstTurnPromptArgv(
|
|
|
316
316
|
}
|
|
317
317
|
return out;
|
|
318
318
|
}
|
|
319
|
+
|
|
320
|
+
export const BACKGROUND_EPHEMERAL_UNSUPPORTED_CODE =
|
|
321
|
+
"BACKGROUND_EPHEMERAL_UNSUPPORTED";
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Background sessions are resumable, attachable durable authorities. Allowing
|
|
325
|
+
* an ephemeral child would leave the worker's interaction journal without a
|
|
326
|
+
* canonical session genesis and a later resume would silently change the
|
|
327
|
+
* caller's no-persistence contract. Keep this guard independent of Commander
|
|
328
|
+
* so direct supervisor/profile callers cannot bypass the CLI validation.
|
|
329
|
+
*/
|
|
330
|
+
export function assertBackgroundArgvDurable(argv, label = "background agent") {
|
|
331
|
+
for (const token of Array.isArray(argv) ? argv : []) {
|
|
332
|
+
if (token === "--") break;
|
|
333
|
+
if (token === "--ephemeral" || token.startsWith("--ephemeral=")) {
|
|
334
|
+
const error = new Error(
|
|
335
|
+
`${label} cannot use --ephemeral because background sessions require durable session and interaction authority`,
|
|
336
|
+
);
|
|
337
|
+
error.code = BACKGROUND_EPHEMERAL_UNSUPPORTED_CODE;
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
@@ -9,10 +9,12 @@
|
|
|
9
9
|
|
|
10
10
|
import { createHash } from "node:crypto";
|
|
11
11
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
appendAuthorityEventWithVerifiedProjection as storeAppendAuthorityEventWithVerifiedProjection,
|
|
13
|
+
getSessionPresence as storeGetSessionPresence,
|
|
14
|
+
readVerifiedEvents as storeReadVerifiedEvents,
|
|
15
|
+
SESSION_PRESENCE,
|
|
15
16
|
} from "../harness/jsonl-session-store.js";
|
|
17
|
+
import { createSessionTranscriptStructureProjection } from "./session-transcript-structure.js";
|
|
16
18
|
import {
|
|
17
19
|
normalizeInteractionBinding,
|
|
18
20
|
sameInteractionBinding,
|
|
@@ -26,41 +28,35 @@ export const MAX_BACKGROUND_INTERACTION_RECORDS = 256;
|
|
|
26
28
|
const TERMINAL_STATUSES = new Set(["resolved", "rejected", "cancelled"]);
|
|
27
29
|
|
|
28
30
|
export const _deps = {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
appendEventWithVerifiedProjection:
|
|
32
|
+
storeAppendAuthorityEventWithVerifiedProjection,
|
|
33
|
+
getSessionPresence: storeGetSessionPresence,
|
|
34
|
+
readVerifiedEvents: storeReadVerifiedEvents,
|
|
32
35
|
};
|
|
33
36
|
|
|
37
|
+
function absentJournal(backgroundAgentId, options = {}) {
|
|
38
|
+
return new BackgroundInteractionJournal({
|
|
39
|
+
backgroundAgentId,
|
|
40
|
+
now: options.now,
|
|
41
|
+
sessionId: options.sessionId,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
function findLatestJournalEvent(sessionId, backgroundAgentId) {
|
|
35
46
|
const valid = (event) => event?.data?.backgroundAgentId === backgroundAgentId;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
if (_deps.readEvents !== storeReadEvents) {
|
|
47
|
-
const events = _deps.readEvents(sessionId) || [];
|
|
48
|
-
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
49
|
-
const event = events[index];
|
|
50
|
-
if (
|
|
51
|
-
event?.type === BACKGROUND_INTERACTION_JOURNAL_EVENT &&
|
|
52
|
-
valid(event)
|
|
53
|
-
) {
|
|
54
|
-
return event;
|
|
55
|
-
}
|
|
47
|
+
const events = _deps.readVerifiedEvents(sessionId) || [];
|
|
48
|
+
const structure = createSessionTranscriptStructureProjection(sessionId, {
|
|
49
|
+
failFast: true,
|
|
50
|
+
});
|
|
51
|
+
for (const event of events) structure.accept(event);
|
|
52
|
+
structure.finish({ assertValid: true });
|
|
53
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
54
|
+
const event = events[index];
|
|
55
|
+
if (event?.type === BACKGROUND_INTERACTION_JOURNAL_EVENT && valid(event)) {
|
|
56
|
+
return event;
|
|
56
57
|
}
|
|
57
|
-
return null;
|
|
58
58
|
}
|
|
59
|
-
return
|
|
60
|
-
sessionId,
|
|
61
|
-
BACKGROUND_INTERACTION_JOURNAL_EVENT,
|
|
62
|
-
valid,
|
|
63
|
-
);
|
|
59
|
+
return null;
|
|
64
60
|
}
|
|
65
61
|
|
|
66
62
|
function journalError(message, code, details = {}) {
|
|
@@ -70,11 +66,95 @@ function journalError(message, code, details = {}) {
|
|
|
70
66
|
return error;
|
|
71
67
|
}
|
|
72
68
|
|
|
69
|
+
function assertVerifiedRecoverySession(sessionId) {
|
|
70
|
+
const events = _deps.readVerifiedEvents(sessionId);
|
|
71
|
+
if (!Array.isArray(events) || events[0]?.type !== "session_start") {
|
|
72
|
+
throw journalError(
|
|
73
|
+
`Background interaction recovery requires an existing verified session: ${sessionId}`,
|
|
74
|
+
"INTERACTION_RECOVERY_SESSION_UNAVAILABLE",
|
|
75
|
+
{ sessionId },
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return events;
|
|
79
|
+
}
|
|
80
|
+
|
|
73
81
|
function cloneJson(value) {
|
|
74
82
|
if (value === undefined) return null;
|
|
75
83
|
return JSON.parse(JSON.stringify(value));
|
|
76
84
|
}
|
|
77
85
|
|
|
86
|
+
function journalSnapshotFingerprint(journal) {
|
|
87
|
+
return fingerprint(journal.toJSON());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function createJournalProjection(sessionId, backgroundAgentId) {
|
|
91
|
+
const structure = createSessionTranscriptStructureProjection(sessionId, {
|
|
92
|
+
failFast: true,
|
|
93
|
+
});
|
|
94
|
+
let latest = null;
|
|
95
|
+
return Object.freeze({
|
|
96
|
+
accept(event) {
|
|
97
|
+
structure.accept(event);
|
|
98
|
+
if (
|
|
99
|
+
event?.type === BACKGROUND_INTERACTION_JOURNAL_EVENT &&
|
|
100
|
+
event?.data?.backgroundAgentId === backgroundAgentId
|
|
101
|
+
) {
|
|
102
|
+
latest = event.data;
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
finish() {
|
|
106
|
+
structure.finish({ assertValid: true });
|
|
107
|
+
const journal = latest
|
|
108
|
+
? BackgroundInteractionJournal.fromJSON(latest, {
|
|
109
|
+
expectedSessionId: sessionId,
|
|
110
|
+
})
|
|
111
|
+
: new BackgroundInteractionJournal({ backgroundAgentId });
|
|
112
|
+
return Object.freeze({
|
|
113
|
+
journal,
|
|
114
|
+
fingerprint: journalSnapshotFingerprint(journal),
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function assertCompleteRecoveryFallback(
|
|
121
|
+
fallback,
|
|
122
|
+
sessionId,
|
|
123
|
+
backgroundAgentId,
|
|
124
|
+
) {
|
|
125
|
+
if (fallback == null) return false;
|
|
126
|
+
if (!fallback?.requestId) {
|
|
127
|
+
throw journalError(
|
|
128
|
+
"Legacy pending interaction recovery evidence has no request id",
|
|
129
|
+
"INTERACTION_RECOVERY_FALLBACK_INVALID",
|
|
130
|
+
{ sessionId, backgroundAgentId },
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const normalized = normalizeInteractionBinding(fallback.binding);
|
|
134
|
+
const complete =
|
|
135
|
+
normalized.backgroundAgentId !== null &&
|
|
136
|
+
normalized.sessionId !== null &&
|
|
137
|
+
normalized.turnId !== null &&
|
|
138
|
+
normalized.toolUseId !== null &&
|
|
139
|
+
normalized.sequence !== null;
|
|
140
|
+
if (
|
|
141
|
+
!complete ||
|
|
142
|
+
normalized.backgroundAgentId !== backgroundAgentId ||
|
|
143
|
+
normalized.sessionId !== sessionId
|
|
144
|
+
) {
|
|
145
|
+
throw journalError(
|
|
146
|
+
"Legacy pending interaction recovery evidence is incomplete or belongs to another session",
|
|
147
|
+
"INTERACTION_RECOVERY_FALLBACK_INVALID",
|
|
148
|
+
{
|
|
149
|
+
sessionId,
|
|
150
|
+
backgroundAgentId,
|
|
151
|
+
requestId: String(fallback.requestId),
|
|
152
|
+
},
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
|
|
78
158
|
function canonicalize(value) {
|
|
79
159
|
if (value === undefined) return "__undefined__";
|
|
80
160
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
@@ -134,6 +214,8 @@ export class BackgroundInteractionJournal {
|
|
|
134
214
|
"INTERACTION_JOURNAL_INVALID",
|
|
135
215
|
);
|
|
136
216
|
}
|
|
217
|
+
this.sessionId =
|
|
218
|
+
options.sessionId == null ? null : String(options.sessionId);
|
|
137
219
|
this._now = options.now || Date.now;
|
|
138
220
|
this.records = [];
|
|
139
221
|
}
|
|
@@ -153,20 +235,88 @@ export class BackgroundInteractionJournal {
|
|
|
153
235
|
const journal = new BackgroundInteractionJournal({
|
|
154
236
|
backgroundAgentId: snapshot.backgroundAgentId,
|
|
155
237
|
now: options.now,
|
|
238
|
+
sessionId: options.expectedSessionId,
|
|
239
|
+
});
|
|
240
|
+
const seen = new Set();
|
|
241
|
+
journal.records = snapshot.records.map((record) => {
|
|
242
|
+
const requestId = String(record?.requestId || "");
|
|
243
|
+
const binding = normalizeInteractionBinding(record?.binding);
|
|
244
|
+
const payload = cloneJson(record?.payload || {});
|
|
245
|
+
const payloadFingerprint = String(record?.payloadFingerprint || "");
|
|
246
|
+
const status = String(record?.status || "");
|
|
247
|
+
const completeBinding =
|
|
248
|
+
binding.backgroundAgentId === snapshot.backgroundAgentId &&
|
|
249
|
+
(options.expectedSessionId == null ||
|
|
250
|
+
binding.sessionId === options.expectedSessionId) &&
|
|
251
|
+
binding.sessionId !== null &&
|
|
252
|
+
binding.turnId !== null &&
|
|
253
|
+
binding.toolUseId !== null &&
|
|
254
|
+
binding.sequence !== null;
|
|
255
|
+
if (
|
|
256
|
+
!requestId ||
|
|
257
|
+
seen.has(requestId) ||
|
|
258
|
+
!completeBinding ||
|
|
259
|
+
payloadFingerprint !== fingerprint(payload) ||
|
|
260
|
+
(status !== "pending" && !TERMINAL_STATUSES.has(status))
|
|
261
|
+
) {
|
|
262
|
+
throw journalError(
|
|
263
|
+
"Invalid background interaction journal record",
|
|
264
|
+
"INTERACTION_JOURNAL_CORRUPT",
|
|
265
|
+
{ requestId: requestId || null },
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
seen.add(requestId);
|
|
269
|
+
const createdAt = Number(record.createdAt);
|
|
270
|
+
const settledAt =
|
|
271
|
+
record.settledAt == null ? null : Number(record.settledAt);
|
|
272
|
+
let settlement = null;
|
|
273
|
+
let settlementFingerprint = null;
|
|
274
|
+
if (status === "pending") {
|
|
275
|
+
if (
|
|
276
|
+
settledAt !== null ||
|
|
277
|
+
record.settlement != null ||
|
|
278
|
+
record.settlementFingerprint != null
|
|
279
|
+
) {
|
|
280
|
+
throw journalError(
|
|
281
|
+
"Pending background interaction has terminal settlement data",
|
|
282
|
+
"INTERACTION_JOURNAL_CORRUPT",
|
|
283
|
+
{ requestId },
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
settlement = normalizeSettlement(record.settlement);
|
|
288
|
+
settlementFingerprint = String(record.settlementFingerprint || "");
|
|
289
|
+
if (
|
|
290
|
+
settlement.status !== status ||
|
|
291
|
+
!Number.isFinite(settledAt) ||
|
|
292
|
+
settlementFingerprint !== fingerprint(settlement)
|
|
293
|
+
) {
|
|
294
|
+
throw journalError(
|
|
295
|
+
"Terminal background interaction settlement is inconsistent",
|
|
296
|
+
"INTERACTION_JOURNAL_CORRUPT",
|
|
297
|
+
{ requestId },
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (!Number.isFinite(createdAt) || createdAt <= 0) {
|
|
302
|
+
throw journalError(
|
|
303
|
+
"Background interaction timestamp is invalid",
|
|
304
|
+
"INTERACTION_JOURNAL_CORRUPT",
|
|
305
|
+
{ requestId },
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
requestId,
|
|
310
|
+
binding,
|
|
311
|
+
payload,
|
|
312
|
+
payloadFingerprint,
|
|
313
|
+
status,
|
|
314
|
+
createdAt,
|
|
315
|
+
settledAt,
|
|
316
|
+
settlement,
|
|
317
|
+
settlementFingerprint,
|
|
318
|
+
};
|
|
156
319
|
});
|
|
157
|
-
journal.records = snapshot.records.map((record) => ({
|
|
158
|
-
requestId: String(record.requestId || ""),
|
|
159
|
-
binding: normalizeInteractionBinding(record.binding),
|
|
160
|
-
payload: cloneJson(record.payload || {}),
|
|
161
|
-
payloadFingerprint: String(record.payloadFingerprint || ""),
|
|
162
|
-
status: String(record.status || "pending"),
|
|
163
|
-
createdAt: Number(record.createdAt) || 0,
|
|
164
|
-
settledAt: record.settledAt == null ? null : Number(record.settledAt),
|
|
165
|
-
settlement: record.settlement ? cloneJson(record.settlement) : null,
|
|
166
|
-
settlementFingerprint: record.settlementFingerprint
|
|
167
|
-
? String(record.settlementFingerprint)
|
|
168
|
-
: null,
|
|
169
|
-
}));
|
|
170
320
|
return journal;
|
|
171
321
|
}
|
|
172
322
|
|
|
@@ -192,7 +342,16 @@ export class BackgroundInteractionJournal {
|
|
|
192
342
|
recordPending({ requestId, binding, payload = {}, createdAt } = {}) {
|
|
193
343
|
const id = String(requestId || "");
|
|
194
344
|
const normalizedBinding = normalizeInteractionBinding(binding);
|
|
195
|
-
if (
|
|
345
|
+
if (
|
|
346
|
+
!id ||
|
|
347
|
+
normalizedBinding.backgroundAgentId !== this.backgroundAgentId ||
|
|
348
|
+
(this.sessionId !== null &&
|
|
349
|
+
normalizedBinding.sessionId !== this.sessionId) ||
|
|
350
|
+
normalizedBinding.sessionId === null ||
|
|
351
|
+
normalizedBinding.turnId === null ||
|
|
352
|
+
normalizedBinding.toolUseId === null ||
|
|
353
|
+
normalizedBinding.sequence === null
|
|
354
|
+
) {
|
|
196
355
|
throw journalError(
|
|
197
356
|
"A requestId and complete interaction binding are required",
|
|
198
357
|
"INTERACTION_REQUEST_INVALID",
|
|
@@ -311,6 +470,8 @@ export class BackgroundInteractionPersistenceError extends Error {
|
|
|
311
470
|
: "INTERACTION_JOURNAL_PERSIST_FAILED";
|
|
312
471
|
this.operation = operation;
|
|
313
472
|
this.sessionId = sessionId;
|
|
473
|
+
if (cause?.commitState) this.commitState = cause.commitState;
|
|
474
|
+
if (cause?.code) this.causeCode = cause.code;
|
|
314
475
|
}
|
|
315
476
|
}
|
|
316
477
|
|
|
@@ -324,10 +485,44 @@ export function persistBackgroundInteractionJournal(
|
|
|
324
485
|
if (!sessionId || !(journal instanceof BackgroundInteractionJournal)) {
|
|
325
486
|
throw new TypeError("sessionId and interaction journal are required");
|
|
326
487
|
}
|
|
327
|
-
|
|
488
|
+
// Re-parse the complete candidate at the final write boundary. A mutate
|
|
489
|
+
// callback can add records after the source snapshot was validated; those
|
|
490
|
+
// records must still be bound to this exact session and agent.
|
|
491
|
+
BackgroundInteractionJournal.fromJSON(journal.toJSON(), {
|
|
492
|
+
expectedSessionId: sessionId,
|
|
493
|
+
});
|
|
494
|
+
const expectedFingerprint =
|
|
495
|
+
options.expectedFingerprint ??
|
|
496
|
+
journalSnapshotFingerprint(
|
|
497
|
+
new BackgroundInteractionJournal({
|
|
498
|
+
backgroundAgentId: journal.backgroundAgentId,
|
|
499
|
+
}),
|
|
500
|
+
);
|
|
501
|
+
_deps.appendEventWithVerifiedProjection(
|
|
328
502
|
sessionId,
|
|
329
503
|
BACKGROUND_INTERACTION_JOURNAL_EVENT,
|
|
330
504
|
journal.toJSON(),
|
|
505
|
+
{
|
|
506
|
+
createProjection: () =>
|
|
507
|
+
createJournalProjection(sessionId, journal.backgroundAgentId),
|
|
508
|
+
validateProjection(projected) {
|
|
509
|
+
if (
|
|
510
|
+
expectedFingerprint !== undefined &&
|
|
511
|
+
projected.fingerprint !== expectedFingerprint
|
|
512
|
+
) {
|
|
513
|
+
throw journalError(
|
|
514
|
+
"Background interaction journal changed before the mutation could commit",
|
|
515
|
+
"INTERACTION_JOURNAL_STALE",
|
|
516
|
+
{
|
|
517
|
+
sessionId,
|
|
518
|
+
backgroundAgentId: journal.backgroundAgentId,
|
|
519
|
+
expectedFingerprint,
|
|
520
|
+
actualFingerprint: projected.fingerprint,
|
|
521
|
+
},
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
},
|
|
331
526
|
);
|
|
332
527
|
return true;
|
|
333
528
|
} catch (error) {
|
|
@@ -350,19 +545,28 @@ export function loadBackgroundInteractionJournal(
|
|
|
350
545
|
if (!sessionId || !backgroundAgentId) {
|
|
351
546
|
throw new TypeError("sessionId and backgroundAgentId are required");
|
|
352
547
|
}
|
|
548
|
+
const presence = _deps.getSessionPresence(sessionId);
|
|
549
|
+
if (presence === SESSION_PRESENCE.ABSENT && options.allowAbsent === true) {
|
|
550
|
+
return absentJournal(backgroundAgentId, { ...options, sessionId });
|
|
551
|
+
}
|
|
353
552
|
const event = findLatestJournalEvent(sessionId, backgroundAgentId);
|
|
354
553
|
if (event) {
|
|
355
|
-
return BackgroundInteractionJournal.fromJSON(event.data,
|
|
554
|
+
return BackgroundInteractionJournal.fromJSON(event.data, {
|
|
555
|
+
...options,
|
|
556
|
+
expectedSessionId: sessionId,
|
|
557
|
+
});
|
|
356
558
|
}
|
|
357
559
|
return new BackgroundInteractionJournal({
|
|
358
560
|
backgroundAgentId,
|
|
359
561
|
now: options.now,
|
|
562
|
+
sessionId,
|
|
360
563
|
});
|
|
361
564
|
} catch (error) {
|
|
362
565
|
if (!failIfUnavailable) {
|
|
363
566
|
return new BackgroundInteractionJournal({
|
|
364
567
|
backgroundAgentId,
|
|
365
568
|
now: options.now,
|
|
569
|
+
sessionId,
|
|
366
570
|
});
|
|
367
571
|
}
|
|
368
572
|
throw new BackgroundInteractionPersistenceError(
|
|
@@ -383,16 +587,84 @@ export function updateBackgroundInteractionJournal(
|
|
|
383
587
|
mutate,
|
|
384
588
|
options = {},
|
|
385
589
|
) {
|
|
590
|
+
const expectedFingerprint = journalSnapshotFingerprint(journal);
|
|
386
591
|
const draft = BackgroundInteractionJournal.fromJSON(journal.toJSON(), {
|
|
387
592
|
now: options.now || journal._now,
|
|
593
|
+
expectedSessionId: sessionId,
|
|
388
594
|
});
|
|
389
595
|
const result = mutate(draft);
|
|
390
596
|
if (!options.persistIf || options.persistIf(result) !== false) {
|
|
391
|
-
|
|
597
|
+
try {
|
|
598
|
+
persistBackgroundInteractionJournal(sessionId, draft, {
|
|
599
|
+
...options,
|
|
600
|
+
expectedFingerprint,
|
|
601
|
+
});
|
|
602
|
+
} catch (error) {
|
|
603
|
+
// Authority append can durably reach the transcript before its metadata
|
|
604
|
+
// settlement reports an ambiguous result. Only an exact verified
|
|
605
|
+
// readback of the intended full snapshot adjudicates that ambiguity as
|
|
606
|
+
// committed; every other state remains fail closed.
|
|
607
|
+
if (error?.commitState === "unknown") {
|
|
608
|
+
try {
|
|
609
|
+
const readback = options.readbackJournal;
|
|
610
|
+
const verified =
|
|
611
|
+
typeof readback === "function"
|
|
612
|
+
? readback(sessionId, journal.backgroundAgentId, options)
|
|
613
|
+
: loadBackgroundInteractionJournal(
|
|
614
|
+
sessionId,
|
|
615
|
+
journal.backgroundAgentId,
|
|
616
|
+
options,
|
|
617
|
+
);
|
|
618
|
+
if (
|
|
619
|
+
journalSnapshotFingerprint(verified) ===
|
|
620
|
+
journalSnapshotFingerprint(draft)
|
|
621
|
+
) {
|
|
622
|
+
return { journal: verified, result, adjudicated: true };
|
|
623
|
+
}
|
|
624
|
+
} catch {
|
|
625
|
+
// Keep the original ambiguous persistence error and its commitState.
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
throw error;
|
|
629
|
+
}
|
|
392
630
|
}
|
|
393
631
|
return { journal: draft, result };
|
|
394
632
|
}
|
|
395
633
|
|
|
634
|
+
/**
|
|
635
|
+
* Classify a transport response that arrived after its live child request was
|
|
636
|
+
* removed. A still-pending row belongs to recovery authority and must never be
|
|
637
|
+
* settled by a late client. Only an exact replay of an already-terminal result
|
|
638
|
+
* is acknowledged, without appending another journal snapshot.
|
|
639
|
+
*/
|
|
640
|
+
export function classifyLateBackgroundInteractionSettlement(
|
|
641
|
+
journal,
|
|
642
|
+
requestId,
|
|
643
|
+
binding,
|
|
644
|
+
outcome,
|
|
645
|
+
) {
|
|
646
|
+
if (!(journal instanceof BackgroundInteractionJournal)) {
|
|
647
|
+
throw new TypeError("background interaction journal is required");
|
|
648
|
+
}
|
|
649
|
+
const record = journal.get(requestId);
|
|
650
|
+
if (!record || record.status === "pending") {
|
|
651
|
+
return Object.freeze({
|
|
652
|
+
accepted: false,
|
|
653
|
+
duplicate: false,
|
|
654
|
+
delivered: false,
|
|
655
|
+
reason: record
|
|
656
|
+
? "interaction_no_live_child"
|
|
657
|
+
: "interaction_request_unknown",
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
const duplicate = journal.settle(requestId, binding, outcome);
|
|
661
|
+
return Object.freeze({
|
|
662
|
+
accepted: true,
|
|
663
|
+
duplicate: duplicate.applied === false,
|
|
664
|
+
delivered: false,
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
|
|
396
668
|
/**
|
|
397
669
|
* Recovery entry point used when a worker is observed lost or starts again.
|
|
398
670
|
* An optional state-file fallback lets pre-journal pendingQuestion records be
|
|
@@ -403,19 +675,56 @@ export function rejectPendingBackgroundInteractions(
|
|
|
403
675
|
backgroundAgentId,
|
|
404
676
|
options = {},
|
|
405
677
|
) {
|
|
678
|
+
const presence = _deps.getSessionPresence(sessionId);
|
|
679
|
+
if (presence === SESSION_PRESENCE.ABSENT && options.fallbackRequest == null) {
|
|
680
|
+
return {
|
|
681
|
+
journal: absentJournal(backgroundAgentId, { ...options, sessionId }),
|
|
682
|
+
rejected: [],
|
|
683
|
+
changed: false,
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
// Normalize absent/missing/tombstoned recovery to a stable domain error
|
|
687
|
+
// before loading a canonical journal. Structurally corrupt live transcripts
|
|
688
|
+
// still surface as read failures and therefore block cleanup fail closed.
|
|
689
|
+
if (options.fallbackRequest != null) assertVerifiedRecoverySession(sessionId);
|
|
406
690
|
let journal = loadBackgroundInteractionJournal(
|
|
407
691
|
sessionId,
|
|
408
692
|
backgroundAgentId,
|
|
409
693
|
options,
|
|
410
694
|
);
|
|
411
695
|
const fallback = options.fallbackRequest;
|
|
696
|
+
const hasValidFallback = assertCompleteRecoveryFallback(
|
|
697
|
+
fallback,
|
|
698
|
+
sessionId,
|
|
699
|
+
backgroundAgentId,
|
|
700
|
+
);
|
|
412
701
|
const hasFallback =
|
|
413
|
-
fallback
|
|
414
|
-
|
|
415
|
-
|
|
702
|
+
hasValidFallback && !journal.get(String(fallback.requestId));
|
|
703
|
+
if (hasValidFallback && !hasFallback) {
|
|
704
|
+
const existing = journal.get(String(fallback.requestId));
|
|
705
|
+
const normalizedBinding = normalizeInteractionBinding(fallback.binding);
|
|
706
|
+
// The supervisor state intentionally stores a compact display projection
|
|
707
|
+
// of the question, not the complete canonical IPC payload (for example it
|
|
708
|
+
// may omit defaultValue/policyDigest). Once the exact request id and full
|
|
709
|
+
// binding match, the verified journal remains payload authority.
|
|
710
|
+
if (!sameInteractionBinding(existing.binding, normalizedBinding)) {
|
|
711
|
+
throw journalError(
|
|
712
|
+
"Legacy pending interaction recovery evidence conflicts with the canonical journal",
|
|
713
|
+
"INTERACTION_RECOVERY_FALLBACK_INVALID",
|
|
714
|
+
{
|
|
715
|
+
sessionId,
|
|
716
|
+
backgroundAgentId,
|
|
717
|
+
requestId: String(fallback.requestId),
|
|
718
|
+
},
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
416
722
|
if (!journal.pending().length && !hasFallback) {
|
|
417
723
|
return { journal, rejected: [], changed: false };
|
|
418
724
|
}
|
|
725
|
+
// Give missing/tombstoned recovery a stable domain error before the atomic
|
|
726
|
+
// verified projection handles the normal append path.
|
|
727
|
+
assertVerifiedRecoverySession(sessionId);
|
|
419
728
|
const mutation = updateBackgroundInteractionJournal(
|
|
420
729
|
sessionId,
|
|
421
730
|
journal,
|
|
@@ -80,6 +80,8 @@ function sendIpc(target, message, onError = () => {}) {
|
|
|
80
80
|
* @param {string} [options.backgroundAgentId]
|
|
81
81
|
* @param {string|null} [options.sessionId]
|
|
82
82
|
* @param {string|null} [options.turnId]
|
|
83
|
+
* @param {object|null} [options.sessionHostWriteDelegation]
|
|
84
|
+
* @param {string|null} [options.turnIpcToken]
|
|
83
85
|
* @param {number} [options.defaultTimeoutMs]
|
|
84
86
|
* @param {()=>string} [options.idFactory]
|
|
85
87
|
*/
|
|
@@ -89,6 +91,11 @@ export function createBackgroundInteractionClient(options = {}) {
|
|
|
89
91
|
options.backgroundAgentId ?? processLike?.env?.CC_BACKGROUND_AGENT_ID;
|
|
90
92
|
const sessionId = options.sessionId ?? null;
|
|
91
93
|
const turnId = options.turnId ?? null;
|
|
94
|
+
const sessionHostWriteDelegation = options.sessionHostWriteDelegation ?? null;
|
|
95
|
+
const turnIpcToken =
|
|
96
|
+
options.turnIpcToken ??
|
|
97
|
+
asNullableString(processLike?.env?.CC_BACKGROUND_TURN_IPC_NONCE);
|
|
98
|
+
const senderPid = Number(processLike?.pid ?? process.pid);
|
|
92
99
|
const defaultTimeoutMs =
|
|
93
100
|
Number.isFinite(options.defaultTimeoutMs) && options.defaultTimeoutMs > 0
|
|
94
101
|
? options.defaultTimeoutMs
|
|
@@ -205,6 +212,12 @@ export function createBackgroundInteractionClient(options = {}) {
|
|
|
205
212
|
protocolVersion: BACKGROUND_INTERACTION_PROTOCOL_VERSION,
|
|
206
213
|
requestId,
|
|
207
214
|
binding,
|
|
215
|
+
sessionHostWriteDelegation,
|
|
216
|
+
senderPid:
|
|
217
|
+
Number.isSafeInteger(senderPid) && senderPid > 0
|
|
218
|
+
? senderPid
|
|
219
|
+
: null,
|
|
220
|
+
turnIpcToken,
|
|
208
221
|
payload: {
|
|
209
222
|
kind: payload.kind || "question",
|
|
210
223
|
question: String(payload.question || payload.prompt || ""),
|
|
@@ -261,6 +274,9 @@ export function createBackgroundInteractionClient(options = {}) {
|
|
|
261
274
|
* @param {object} [options]
|
|
262
275
|
* @param {string|null} [options.backgroundAgentId]
|
|
263
276
|
* @param {string|null} [options.sessionId]
|
|
277
|
+
* @param {string|null} [options.turnIpcToken]
|
|
278
|
+
* @param {boolean} [options.requireSenderPid]
|
|
279
|
+
* @param {boolean} [options.requireDelegationOwnerPid]
|
|
264
280
|
* @returns {()=>void} detach function
|
|
265
281
|
*/
|
|
266
282
|
export function attachInteractionRequestHandler(
|
|
@@ -324,6 +340,8 @@ export function attachInteractionRequestHandler(
|
|
|
324
340
|
const binding = normalizeInteractionBinding(message.binding);
|
|
325
341
|
const expectedAgentId = asNullableString(options.backgroundAgentId);
|
|
326
342
|
const expectedSessionId = asNullableString(options.sessionId);
|
|
343
|
+
const expectedTurnIpcToken = asNullableString(options.turnIpcToken);
|
|
344
|
+
const senderPid = Number(message.senderPid);
|
|
327
345
|
if (
|
|
328
346
|
(expectedAgentId && binding.backgroundAgentId !== expectedAgentId) ||
|
|
329
347
|
(expectedSessionId && binding.sessionId !== expectedSessionId)
|
|
@@ -335,6 +353,20 @@ export function attachInteractionRequestHandler(
|
|
|
335
353
|
);
|
|
336
354
|
return;
|
|
337
355
|
}
|
|
356
|
+
if (
|
|
357
|
+
(expectedTurnIpcToken && message.turnIpcToken !== expectedTurnIpcToken) ||
|
|
358
|
+
(options.requireSenderPid === true &&
|
|
359
|
+
(!Number.isSafeInteger(senderPid) || senderPid < 1)) ||
|
|
360
|
+
(options.requireDelegationOwnerPid === true &&
|
|
361
|
+
Number(message.sessionHostWriteDelegation?.ownerPid) !== senderPid)
|
|
362
|
+
) {
|
|
363
|
+
rejectRequest(
|
|
364
|
+
message,
|
|
365
|
+
"INTERACTION_CHILD_IDENTITY_MISMATCH",
|
|
366
|
+
"Background interaction did not originate from this turn child",
|
|
367
|
+
);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
338
370
|
|
|
339
371
|
const prior = completed.get(message.requestId);
|
|
340
372
|
if (prior) {
|
|
@@ -10,6 +10,7 @@ import { createHash } from "node:crypto";
|
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
import { dirname, join, parse, resolve } from "node:path";
|
|
12
12
|
import { getConfigPath } from "./paths.js";
|
|
13
|
+
import { assertBackgroundArgvDurable } from "./background-command-argv.js";
|
|
13
14
|
|
|
14
15
|
export const BACKGROUND_LAUNCH_PROFILE_VERSION = 1;
|
|
15
16
|
|
|
@@ -314,6 +315,7 @@ export function captureBackgroundLaunchProfile({
|
|
|
314
315
|
worktree = null,
|
|
315
316
|
governance = null,
|
|
316
317
|
} = {}) {
|
|
318
|
+
assertBackgroundArgvDurable(argv, "background launch profile");
|
|
317
319
|
const profile = emptyParsedProfile(cwd, worktree, governance);
|
|
318
320
|
const omitted = new Set();
|
|
319
321
|
const addDirs = [];
|