chainlesschain 0.162.161 → 0.162.163
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-DMn528q-.js → AIOps-BQ0kgfA1.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-D9ZcDCJL.js → ActionButton-Dr1MDANe.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-A_K-8PX0.js → Analytics-CZ5vsaXW.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-D4pFW3X6.js → AppLayout-BIBga881.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-Dz46COAd.js → Artifacts-vpXHRU3u.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-cbMjbyg0.js → Audit-B8exuoub.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-Dtf88GAj.js → BackgroundAgents-923evRRN.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-C5KRxn52.js → Backup-sr_sswlQ.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Dd2UK3BN.js → BaseInput-CnW-4XKT.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-lptKTFdG.js → Chat-FZCsTZIL.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DDPd9qT0.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-_yspG6rG.js → Checkbox-DFrxB59H.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BGq_GAha.js → Codegen-_b6WEel8.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CePor71g.js → Col-CkbnnZ-K.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CLOWhIT7.js → Community-BxM_82zW.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CivJt2vT.js → Compact-DYinP45R.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Btmo1GGE.js → Compliance-DyXp3sSP.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Ct35OlG4.js → Cowork-BEr2DhW7.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-CTc2GmhB.js → Cron-B-Vwo1hN.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-ZtiBETk1.js → Crosschain-DbLxKddd.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BNqMQQXw.js → DID-BsQ_LFw9.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BmzZd-9V.js → Dashboard-lVlXQHu5.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CMVgvP2M.js → Dropdown-Beo_foO7.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CcqQFAfT.js → EmailListRenderer-DszHIXED.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CTqB1_4V.js → FamilyGuardDashboard-DcAwoh81.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CiKOOzlb.js → Federation-CuJLa2eb.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Wk-OT0gr.js → FormItemContext-Cxd1lxhc.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DbEQT6ue.js → GenericCardRenderer-BBvq5Jsw.js} +1 -1
- package/src/assets/web-panel/assets/{Git-0ZvCPSD9.js → Git-WqPKetx6.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CmVuKkPb.js → Governance-CWYtY3ke.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CTWE3Jwg.js → Inference-BnX1iiNV.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CBEIF00L.js → KnowledgeGraph-BJuM8GN5.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DLhxDwVG.js → Logs-DqLMNntm.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BqYwEYou.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BgsCBrnc.js → Marketplace-_vGfmhNl.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-ClBUGDS2.js → McpTools-BeJDOkDj.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CnG_ZnYw.js → Memory-Da0EEZDO.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BUOAbR1Z.js → MobileBridge-DSuzcCCy.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Dnt_9-WB.js → MobileProjects-D0RAk4e8.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CLjt4kCD.js → Mtc-BE9YgdA0.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-f24U9Aes.js → MtcAudit-fC2BpO5S.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-GrmGQbSq.js → Multisig-D24Xxqw3.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DADaWn-n.js → NLProgramming-Bs3oGvS8.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-xtf5RX6C.js → Notes-BFjmAA-3.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Cd27Rgtx.js → NotificationSettings-BX_9Bhv0.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-fXRxlDjl.js → OrderTableRenderer-wjkZwJXI.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-joCFlNB0.js → Organization-DBcdsVEP.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-B1HoURq0.js → Overflow-DDyaF9vC.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DiwsKbT7.js → P2P-BB78L4he.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Xe-BsW6S.js → PdhVaultBrowser-DqTiZFum.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-C23qi8kl.js → Permissions-INbnWv4j.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-BS1syX2f.js → PersonalDataHub-BST-VmTq.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-DRYwwDyh.js → Pipeline-CE7_tS2k.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BQKaDHPH.js → Privacy-hK5L8dOB.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BC6F9xiG.js → ProjectInit-BvkkUoDn.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-ClFPc1Nd.js → ProjectSettings-u7_EUtkm.js} +2 -2
- package/src/assets/web-panel/assets/Projects-5Fmt_8Nw.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CLWuOUNt.js → Providers-B9eanarR.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DiqULTxb.js → QrScannerModal-C0dX5N2f.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-9A03P-E_.js → QuickAsk-CKTpG-PZ.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CJZAnbq_.js → Recommend-Px_w_XQM.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C9A1R77h.js → RemoteSession-Di0fnYkM.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BIZc6Rky.js → Reputation-vesnWjXt.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DfIPNtuG.js → Row-exbY6LkT.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C5znFcQ1.js → RssFeed-B3O3x70t.js} +2 -2
- package/src/assets/web-panel/assets/{Search-DSs2z3OX.js → Search-uRA0DhNz.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CV6LcrLY.js → Security-CViqdH6K.js} +4 -4
- package/src/assets/web-panel/assets/{Services-cmhoX0Xj.js → Services-CkXS97WN.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-tCsg2po7.js → Skeleton-DxW5WYnE.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BK1KUDiy.js → Skills-CLxmGFse.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-yRgyeA82.js → Sla-Cr5fziXq.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-c1mo1mlp.js → SpeechSettings-H0JSb3cp.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-koIAfuT3.js → SyncSettings-DMYWMae7.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DkkreAaB.js → Tasks-CbYyDdF4.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-fy5Ctf9M.js → Templates-Cpc8JjiK.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-Bw70Jf6S.js → Tenant-CRh77Jda.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BKh6Rx-4.js → Terminal-4D8tNBO5.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Dg54WP0c.js → TimelineRenderer-D41jzzbG.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BvvD5SxJ.js → Tokens-C_jWEy6b.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-B7Ug9x4e.js → Trigger-Dk1ZUhsy.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BjvuMORY.js → Trust-CLVy4l7m.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CZqhkx8p.js → UkeySign-C-FeqFDj.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-ClR50lhX.js → VideoEditing-CqVZzyMm.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DlfppYjx.js → Wallet-DX9BW9pu.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-BirgVr8B.js → WebAuthn-7hQDpWw_.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-B8DP_bax.js → WorkflowEditor-6XqUAO0j.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DA8AUZzL.js → chat-5R_vkaJG.js} +1 -1
- package/src/assets/web-panel/assets/{colors-C38pLGp1.js → colors-huUPZdC-.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-81rtr98o.js → compact-item-CfJlgisE.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-D34ixq8t.js → createContext-jO8heofr.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DeIrCfH-.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-D4bzqTrZ.js → hasIn-DY8Vxqx7.js} +1 -1
- package/src/assets/web-panel/assets/{index-E1OJRXs4.js → index-A48PtXw9.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3zCCZRS.js → index-B300RPnT.js} +3 -3
- package/src/assets/web-panel/assets/{index-Cd3KuNK5.js → index-B74OXFxC.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dmo3dFHb.js → index-BAON7TzU.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ddk0e_Pi.js → index-BCqxyvqp.js} +1 -1
- package/src/assets/web-panel/assets/{index-9qoVpPt6.js → index-BHE8OIls.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBFSo1NF.js → index-BQS-uPOK.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUQLXLQM.js → index-BRXOP09X.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6GfvEQG.js → index-BYLBPpt8.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFyWKOBD.js → index-BfCx4pFT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BP-zkXK8.js → index-BliaT02i.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMgnRwrK.js → index-BmqGerpC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIP62a6r.js → index-BsBtRWbA.js} +1 -1
- package/src/assets/web-panel/assets/{index-DkHqMnn3.js → index-BtEXSoPL.js} +1 -1
- package/src/assets/web-panel/assets/index-BxUKMRpP.js +1 -0
- package/src/assets/web-panel/assets/{index-B8mkLM4x.js → index-Bxax9vQ6.js} +1 -1
- package/src/assets/web-panel/assets/{index-ZRJRItm0.js → index-C2iVSFpy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CP5alL4t.js → index-CA6sO5VD.js} +1 -1
- package/src/assets/web-panel/assets/{index-BS3M5H9V.js → index-CA90TZxG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvfCEJG-.js → index-CLhBh-qE.js} +1 -1
- package/src/assets/web-panel/assets/{index-tKiC8T-4.js → index-CRElRhyS.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0QArPQt.js → index-Cbe9G0V4.js} +1 -1
- package/src/assets/web-panel/assets/{index-COe_cOLJ.js → index-ChvHubX3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvL_3J_n.js → index-Cis_Ip7H.js} +1 -1
- package/src/assets/web-panel/assets/{index-F76w1NiL.js → index-Cjxbu68P.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlxEphl7.js → index-CzKmteNS.js} +1 -1
- package/src/assets/web-panel/assets/{index-CuS-UPoj.js → index-D0pet5Uy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbhE3j5j.js → index-D1VLuww0.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_2VYPr1.js → index-DXBcvUnf.js} +1 -1
- package/src/assets/web-panel/assets/{index-DakfFZ-k.js → index-Db1cLWir.js} +1 -1
- package/src/assets/web-panel/assets/{index-fI5sXMLg.js → index-DoI2sZm7.js} +1 -1
- package/src/assets/web-panel/assets/index-DscU9iPI.js +1 -0
- package/src/assets/web-panel/assets/{index-CoVgZw_b.js → index-KRHroryI.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvD7b2IK.js → index-d1xkbtNz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtGxuQrO.js → index-dKx57io7.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqIhLAbA.js → index-j3O2EEJe.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXd2_LmH.js → index-p85YaaKw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFHa1HIg.js → index-v3pTaEQm.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0Ih7Hwn.js → index-vXs3B8GJ.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-ByyvflIR.js → initDefaultProps-MCB7-raZ.js} +1 -1
- package/src/assets/web-panel/assets/{motion-B3FQm2By.js → motion-UWDF9swV.js} +1 -1
- package/src/assets/web-panel/assets/{move-BSfns6Ef.js → move-CsTmgXZU.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-CTG1Ieri.js → mtc-parser-fQy6KGLg.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DObM_2j6.js → omit-woPnEjd1.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DOfelQ1t.js → pickAttrs-Bs2dZ1gf.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CHZILds_.js → placementArrow-C7EVgPkg.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-REbucKSx.js → responsiveObserve-BH2kYCTN.js} +1 -1
- package/src/assets/web-panel/assets/{slide-C6EZzD7o.js → slide-BFBP2V_e.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BxkWS-g3.js → statusUtils-xdDxDYse.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Chklyoq6.js → styleChecker-aAWgPPQf.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CRVwKrrI.js → useFlexGapSupport-DkGSAWoO.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DV24ik_J.js → useFs-BrOih40N.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-C7vIo4Pg.js → usePersonalDataHub-eIYrgMqO.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-By-v5GlF.js → vnode-B8lutNSE.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-B1w9Am3r.js → zoom-CffNre7V.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +254 -23
- package/src/commands/agent.js +105 -11
- package/src/commands/context.js +125 -14
- package/src/commands/hook.js +181 -7
- package/src/commands/plugin.js +239 -1
- package/src/commands/review.js +352 -2
- package/src/data/changelog.json +14 -4
- package/src/gateways/ws/remote-session-protocol.js +39 -0
- package/src/gateways/ws/session-protocol.js +5 -2
- package/src/lib/agent-schedule-store.js +289 -10
- package/src/lib/agents.js +19 -0
- package/src/lib/background-agent-supervisor.js +34 -7
- package/src/lib/channels/channel-manager.js +54 -2
- package/src/lib/cli-context-engineering.js +9 -2
- package/src/lib/context-breakdown.js +116 -0
- package/src/lib/docs-drift.js +166 -0
- package/src/lib/doctor-checkup.js +128 -0
- package/src/lib/hook-event-bus.cjs +0 -0
- package/src/lib/hook-event-log.cjs +193 -0
- package/src/lib/interaction-adapter.js +34 -4
- package/src/lib/json-schema-output.js +75 -12
- package/src/lib/json-schema-validate.js +831 -0
- package/src/lib/lsp/__tests__/diagnostics-scheduler.test.js +93 -0
- package/src/lib/lsp/diagnostics-scheduler.js +127 -0
- package/src/lib/operation-fingerprint.js +95 -0
- package/src/lib/plugin-runtime/capabilities.js +371 -0
- package/src/lib/plugin-runtime/capability-consent.js +175 -0
- package/src/lib/plugin-runtime/manifest.js +30 -0
- package/src/lib/plugin-runtime/policy.js +62 -0
- package/src/lib/plugin-runtime/scopes.js +20 -4
- package/src/lib/project-instructions.js +128 -0
- package/src/lib/remote-approval-bridge.js +16 -0
- package/src/lib/repl-goal.js +132 -0
- package/src/lib/repl-rewind.js +52 -0
- package/src/lib/review-pipeline.js +227 -0
- package/src/lib/runtime-checkup.js +318 -0
- package/src/lib/schedule-planner.js +17 -1
- package/src/lib/session-lifecycle.js +305 -0
- package/src/lib/settings-hook-events.cjs +52 -13
- package/src/lib/settings-loader.cjs +42 -0
- package/src/lib/side-effect-ledger-store.js +88 -0
- package/src/lib/side-effect-ledger.js +296 -0
- package/src/lib/sub-agent-context.js +74 -5
- package/src/lib/subagent-contract.js +383 -0
- package/src/lib/subagent-inheritance.js +112 -0
- package/src/lib/telemetry-ids.js +190 -0
- package/src/lib/turn-binding-store.js +119 -0
- package/src/lib/turn-binding.js +397 -0
- package/src/repl/agent-repl.js +136 -0
- package/src/repl/permission-tier.js +28 -0
- package/src/runtime/agent-core.js +387 -14
- package/src/runtime/coding-agent-contract-shared.cjs +60 -1
- package/src/runtime/headless-runner.js +267 -0
- package/src/runtime/headless-stream.js +60 -1
- package/src/assets/web-panel/assets/ChatBubbleRenderer-C2KLtwHz.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-DxywvhTU.js +0 -1
- package/src/assets/web-panel/assets/Projects-Dn-f5aYe.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CHDIEOeN.js +0 -1
- package/src/assets/web-panel/assets/index-CY1NURWZ.js +0 -1
- package/src/assets/web-panel/assets/index-DokUYqpq.js +0 -1
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified session lifecycle state machine (P0#4 "会话生命周期可靠性 —
|
|
3
|
+
* 统一状态机" from CLAUDE_CODE_IDE_GAP_ANALYSIS.md).
|
|
4
|
+
*
|
|
5
|
+
* A running agent session is described by THREE divergent representations that
|
|
6
|
+
* grew independently and disagree on vocabulary:
|
|
7
|
+
*
|
|
8
|
+
* 1. supervisor `status` — running | completed | failed | stopped | lost
|
|
9
|
+
* (liveness reconciliation, owns the terminal verdict). See
|
|
10
|
+
* background-agent-supervisor.js.
|
|
11
|
+
* 2. worker `phase` — starting | working | idle | needs_input |
|
|
12
|
+
* waiting_permission (the live sub-state WHILE running). See
|
|
13
|
+
* [[background-agent-phase.js]].
|
|
14
|
+
* 3. IDE connection state — starting | ready | running | disconnected |
|
|
15
|
+
* crashed (the transport/process view the VS Code / JetBrains panel keeps).
|
|
16
|
+
*
|
|
17
|
+
* None of the three alone can answer the question that matters on reconnect:
|
|
18
|
+
* "is a dangerous tool call about to be silently retried?" The gap doc's P0#4
|
|
19
|
+
* asks for ONE state machine — starting / ready / running / waitingApproval /
|
|
20
|
+
* cancelling / disconnected / recovering / completed / failed / stopped — that
|
|
21
|
+
* every producer folds into, so the supervisor, the IDE bridge and the resume
|
|
22
|
+
* path reason about the same lifecycle.
|
|
23
|
+
*
|
|
24
|
+
* This module is that single source of truth. It is pure (no fs / process /
|
|
25
|
+
* clock): `deriveSessionState` collapses the three axes into one canonical
|
|
26
|
+
* state with a documented precedence, `canTransition` encodes the legal graph,
|
|
27
|
+
* and `requiresIdempotencyGuard` marks the `recovering` window during which a
|
|
28
|
+
* resumed session MUST re-check a dangerous tool's ledger before re-executing
|
|
29
|
+
* (the "危险工具恢复时不得静默重试" invariant) rather than blindly replaying it.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The canonical unified lifecycle. camelCase mirrors the gap doc's wording.
|
|
34
|
+
* `ready` is the parked/idle steady state (transport open, no active turn);
|
|
35
|
+
* `running` is a live turn. The three terminal states are mutually exclusive
|
|
36
|
+
* and absorbing.
|
|
37
|
+
*/
|
|
38
|
+
export const SESSION_STATES = Object.freeze({
|
|
39
|
+
STARTING: "starting",
|
|
40
|
+
READY: "ready",
|
|
41
|
+
RUNNING: "running",
|
|
42
|
+
WAITING_APPROVAL: "waitingApproval",
|
|
43
|
+
CANCELLING: "cancelling",
|
|
44
|
+
DISCONNECTED: "disconnected",
|
|
45
|
+
RECOVERING: "recovering",
|
|
46
|
+
COMPLETED: "completed",
|
|
47
|
+
FAILED: "failed",
|
|
48
|
+
STOPPED: "stopped",
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const ALL_STATES = new Set(Object.values(SESSION_STATES));
|
|
52
|
+
|
|
53
|
+
/** Terminal (absorbing) states — a session can never leave one. */
|
|
54
|
+
const TERMINAL_STATES = new Set([
|
|
55
|
+
SESSION_STATES.COMPLETED,
|
|
56
|
+
SESSION_STATES.FAILED,
|
|
57
|
+
SESSION_STATES.STOPPED,
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Every vocabulary the three producers emit → canonical unified state. Callers
|
|
62
|
+
* that hold a native representation (a supervisor status, a worker phase, an
|
|
63
|
+
* IDE connection state) map it here before reasoning about the lifecycle.
|
|
64
|
+
*/
|
|
65
|
+
const STATE_ALIASES = new Map([
|
|
66
|
+
// canonical
|
|
67
|
+
["starting", SESSION_STATES.STARTING],
|
|
68
|
+
["ready", SESSION_STATES.READY],
|
|
69
|
+
["running", SESSION_STATES.RUNNING],
|
|
70
|
+
["waitingapproval", SESSION_STATES.WAITING_APPROVAL],
|
|
71
|
+
["cancelling", SESSION_STATES.CANCELLING],
|
|
72
|
+
["disconnected", SESSION_STATES.DISCONNECTED],
|
|
73
|
+
["recovering", SESSION_STATES.RECOVERING],
|
|
74
|
+
["completed", SESSION_STATES.COMPLETED],
|
|
75
|
+
["failed", SESSION_STATES.FAILED],
|
|
76
|
+
["stopped", SESSION_STATES.STOPPED],
|
|
77
|
+
// worker phase (background-agent-phase.js)
|
|
78
|
+
["working", SESSION_STATES.RUNNING],
|
|
79
|
+
["turn", SESSION_STATES.RUNNING],
|
|
80
|
+
["idle", SESSION_STATES.READY],
|
|
81
|
+
["needs_input", SESSION_STATES.WAITING_APPROVAL],
|
|
82
|
+
["needs-input", SESSION_STATES.WAITING_APPROVAL],
|
|
83
|
+
["awaiting_input", SESSION_STATES.WAITING_APPROVAL],
|
|
84
|
+
["question", SESSION_STATES.WAITING_APPROVAL],
|
|
85
|
+
["waiting_permission", SESSION_STATES.WAITING_APPROVAL],
|
|
86
|
+
["waiting-permission", SESSION_STATES.WAITING_APPROVAL],
|
|
87
|
+
["waiting_approval", SESSION_STATES.WAITING_APPROVAL],
|
|
88
|
+
["awaiting_approval", SESSION_STATES.WAITING_APPROVAL],
|
|
89
|
+
// supervisor liveness — a running process that vanished is a failure.
|
|
90
|
+
["lost", SESSION_STATES.FAILED],
|
|
91
|
+
// IDE connection / transport
|
|
92
|
+
["connected", SESSION_STATES.READY],
|
|
93
|
+
["reconnecting", SESSION_STATES.RECOVERING],
|
|
94
|
+
["resuming", SESSION_STATES.RECOVERING],
|
|
95
|
+
["reattaching", SESSION_STATES.RECOVERING],
|
|
96
|
+
["cancelled", SESSION_STATES.STOPPED],
|
|
97
|
+
["canceled", SESSION_STATES.STOPPED],
|
|
98
|
+
["cancelling", SESSION_STATES.CANCELLING],
|
|
99
|
+
["stopping", SESSION_STATES.CANCELLING],
|
|
100
|
+
["crashed", SESSION_STATES.FAILED],
|
|
101
|
+
["error", SESSION_STATES.FAILED],
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Normalize any producer's state/status/phase token to a canonical unified
|
|
106
|
+
* state, or null when absent / unrecognized. Case- and whitespace-tolerant;
|
|
107
|
+
* never throws.
|
|
108
|
+
*/
|
|
109
|
+
export function normalizeSessionState(value) {
|
|
110
|
+
if (typeof value !== "string") return null;
|
|
111
|
+
return STATE_ALIASES.get(value.trim().toLowerCase()) || null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Legal transition graph. Terminal states have no outgoing edges. */
|
|
115
|
+
const TRANSITIONS = Object.freeze({
|
|
116
|
+
starting: [
|
|
117
|
+
"ready",
|
|
118
|
+
"running",
|
|
119
|
+
"waitingApproval",
|
|
120
|
+
"disconnected",
|
|
121
|
+
"failed",
|
|
122
|
+
"stopped",
|
|
123
|
+
],
|
|
124
|
+
ready: [
|
|
125
|
+
"running",
|
|
126
|
+
"waitingApproval",
|
|
127
|
+
"cancelling",
|
|
128
|
+
"disconnected",
|
|
129
|
+
"completed",
|
|
130
|
+
"failed",
|
|
131
|
+
"stopped",
|
|
132
|
+
],
|
|
133
|
+
running: [
|
|
134
|
+
"ready",
|
|
135
|
+
"waitingApproval",
|
|
136
|
+
"cancelling",
|
|
137
|
+
"disconnected",
|
|
138
|
+
"completed",
|
|
139
|
+
"failed",
|
|
140
|
+
"stopped",
|
|
141
|
+
],
|
|
142
|
+
waitingApproval: [
|
|
143
|
+
"running",
|
|
144
|
+
"ready",
|
|
145
|
+
"cancelling",
|
|
146
|
+
"disconnected",
|
|
147
|
+
"completed",
|
|
148
|
+
"failed",
|
|
149
|
+
"stopped",
|
|
150
|
+
],
|
|
151
|
+
// a turn may finish (or die) before a cancel lands, so completed/failed are legal.
|
|
152
|
+
cancelling: ["stopped", "failed", "completed", "disconnected"],
|
|
153
|
+
disconnected: ["recovering", "failed", "stopped", "completed"],
|
|
154
|
+
recovering: [
|
|
155
|
+
"ready",
|
|
156
|
+
"running",
|
|
157
|
+
"waitingApproval",
|
|
158
|
+
"disconnected",
|
|
159
|
+
"completed",
|
|
160
|
+
"failed",
|
|
161
|
+
"stopped",
|
|
162
|
+
],
|
|
163
|
+
completed: [],
|
|
164
|
+
failed: [],
|
|
165
|
+
stopped: [],
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
/** Is `state` a terminal (absorbing) state? */
|
|
169
|
+
export function isTerminalSessionState(state) {
|
|
170
|
+
return TERMINAL_STATES.has(state);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Is `state` a live (non-terminal, known) state? */
|
|
174
|
+
export function isActiveSessionState(state) {
|
|
175
|
+
return ALL_STATES.has(state) && !TERMINAL_STATES.has(state);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Is `from → to` a legal transition? A self-transition (from === to) is always
|
|
180
|
+
* allowed (idempotent re-assert of the current state). Unknown states → false.
|
|
181
|
+
*/
|
|
182
|
+
export function canTransitionSessionState(from, to) {
|
|
183
|
+
if (!ALL_STATES.has(from) || !ALL_STATES.has(to)) return false;
|
|
184
|
+
if (from === to) return true;
|
|
185
|
+
return TRANSITIONS[from].includes(to);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* States during which a resumed/reconnected session must NOT silently replay a
|
|
190
|
+
* dangerous tool call — it has to re-check the command ledger first (P0#4
|
|
191
|
+
* "危险工具恢复时不得静默重试"). `recovering` is the reattach/replay window;
|
|
192
|
+
* `disconnected` is guarded too because the very next legal step is recovery
|
|
193
|
+
* and a re-dispatch across the gap is exactly the at-most-once hazard.
|
|
194
|
+
*/
|
|
195
|
+
export function requiresIdempotencyGuard(state) {
|
|
196
|
+
return (
|
|
197
|
+
state === SESSION_STATES.RECOVERING || state === SESSION_STATES.DISCONNECTED
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Collapse the three divergent axes into ONE canonical unified state.
|
|
203
|
+
*
|
|
204
|
+
* Precedence (strongest first), each documented by WHY it outranks the next:
|
|
205
|
+
* 1. terminal `status` (completed/failed/stopped/lost) — a settled verdict
|
|
206
|
+
* from the supervisor is authoritative and absorbing; nothing un-settles it.
|
|
207
|
+
* 2. `recovering` (connection reconnecting/resuming, or explicit flag) — an
|
|
208
|
+
* active reattach is the state that carries the idempotency guard, so it
|
|
209
|
+
* must surface even mid-cancel.
|
|
210
|
+
* 3. `disconnected` (connection lost) — the transport is gone; we cannot
|
|
211
|
+
* observe cancel/approval progress, so report the disconnect, not a stale
|
|
212
|
+
* inner phase.
|
|
213
|
+
* 4. `cancelling` (cancel requested, not yet settled) — outranks approval /
|
|
214
|
+
* running because a pending cancel is what the operator is waiting on.
|
|
215
|
+
* 5. `waitingApproval` (pendingApprovals > 0 OR a blocking worker phase) — a
|
|
216
|
+
* human decision blocks progress; mirrors phaseGroupKey's approval-wins rule.
|
|
217
|
+
* 6. worker phase: working/turn → running, idle → ready, starting → starting.
|
|
218
|
+
* 7. fallback: a running session with an unknown/absent phase is `running`
|
|
219
|
+
* (never idle) — the safe default, matching phaseGroupKey.
|
|
220
|
+
*
|
|
221
|
+
* @param {object} input
|
|
222
|
+
* @param {string} [input.status] supervisor liveness status
|
|
223
|
+
* @param {string} [input.phase] worker sub-phase
|
|
224
|
+
* @param {string} [input.connection] IDE transport state
|
|
225
|
+
* @param {boolean} [input.cancelRequested] a cancel is in flight
|
|
226
|
+
* @param {boolean} [input.recovering] an explicit reattach/replay flag
|
|
227
|
+
* @param {number} [input.pendingApprovals] count of blocking approvals
|
|
228
|
+
* @returns {string} a canonical SESSION_STATES value
|
|
229
|
+
*/
|
|
230
|
+
export function deriveSessionState(input) {
|
|
231
|
+
const s = input && typeof input === "object" ? input : {};
|
|
232
|
+
const status = normalizeSessionState(s.status);
|
|
233
|
+
const connection = normalizeSessionState(s.connection);
|
|
234
|
+
const phase = normalizeSessionState(s.phase);
|
|
235
|
+
|
|
236
|
+
// 1. terminal verdict absorbs everything.
|
|
237
|
+
if (status && TERMINAL_STATES.has(status)) return status;
|
|
238
|
+
|
|
239
|
+
// 2. active recovery (explicit flag or a reconnecting/resuming transport).
|
|
240
|
+
if (s.recovering === true || connection === SESSION_STATES.RECOVERING) {
|
|
241
|
+
return SESSION_STATES.RECOVERING;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 3. transport gone.
|
|
245
|
+
if (connection === SESSION_STATES.DISCONNECTED)
|
|
246
|
+
return SESSION_STATES.DISCONNECTED;
|
|
247
|
+
|
|
248
|
+
// 4. cancel in flight (flag, or a producer that reported a cancelling state).
|
|
249
|
+
if (
|
|
250
|
+
s.cancelRequested === true ||
|
|
251
|
+
phase === SESSION_STATES.CANCELLING ||
|
|
252
|
+
connection === SESSION_STATES.CANCELLING
|
|
253
|
+
) {
|
|
254
|
+
return SESSION_STATES.CANCELLING;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 5. blocked on a human decision.
|
|
258
|
+
if (
|
|
259
|
+
Number(s.pendingApprovals) > 0 ||
|
|
260
|
+
phase === SESSION_STATES.WAITING_APPROVAL
|
|
261
|
+
) {
|
|
262
|
+
return SESSION_STATES.WAITING_APPROVAL;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// 6. steady sub-states from the worker phase.
|
|
266
|
+
if (phase === SESSION_STATES.RUNNING) return SESSION_STATES.RUNNING;
|
|
267
|
+
if (phase === SESSION_STATES.READY) return SESSION_STATES.READY;
|
|
268
|
+
if (phase === SESSION_STATES.STARTING) return SESSION_STATES.STARTING;
|
|
269
|
+
if (connection === SESSION_STATES.STARTING) return SESSION_STATES.STARTING;
|
|
270
|
+
if (connection === SESSION_STATES.READY) return SESSION_STATES.READY;
|
|
271
|
+
|
|
272
|
+
// 7. a running session with no legible phase is running, never idle.
|
|
273
|
+
if (status === SESSION_STATES.RUNNING) return SESSION_STATES.RUNNING;
|
|
274
|
+
|
|
275
|
+
// nothing legible at all → the session is starting up.
|
|
276
|
+
return SESSION_STATES.STARTING;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Human-readable one-liner for a canonical state (dashboards / logs). */
|
|
280
|
+
export function describeSessionState(state) {
|
|
281
|
+
switch (state) {
|
|
282
|
+
case SESSION_STATES.STARTING:
|
|
283
|
+
return "starting: process spawned, first turn not begun";
|
|
284
|
+
case SESSION_STATES.READY:
|
|
285
|
+
return "ready: transport open, parked between turns";
|
|
286
|
+
case SESSION_STATES.RUNNING:
|
|
287
|
+
return "running: a turn is executing";
|
|
288
|
+
case SESSION_STATES.WAITING_APPROVAL:
|
|
289
|
+
return "waiting for approval: a human decision blocks progress";
|
|
290
|
+
case SESSION_STATES.CANCELLING:
|
|
291
|
+
return "cancelling: a cancel is in flight, not yet settled";
|
|
292
|
+
case SESSION_STATES.DISCONNECTED:
|
|
293
|
+
return "disconnected: transport lost, session may still be alive";
|
|
294
|
+
case SESSION_STATES.RECOVERING:
|
|
295
|
+
return "recovering: reattaching; dangerous tools must re-check the ledger";
|
|
296
|
+
case SESSION_STATES.COMPLETED:
|
|
297
|
+
return "completed: finished successfully (terminal)";
|
|
298
|
+
case SESSION_STATES.FAILED:
|
|
299
|
+
return "failed: ended in error or was lost (terminal)";
|
|
300
|
+
case SESSION_STATES.STOPPED:
|
|
301
|
+
return "stopped: cancelled by the user (terminal)";
|
|
302
|
+
default:
|
|
303
|
+
return "unknown session state";
|
|
304
|
+
}
|
|
305
|
+
}
|
|
@@ -18,6 +18,37 @@
|
|
|
18
18
|
|
|
19
19
|
const { collectHooks } = require("./settings-hooks.cjs");
|
|
20
20
|
const { runHooks } = require("./hook-runner.cjs");
|
|
21
|
+
const { buildHookEnvelope } = require("./hook-event-bus.cjs");
|
|
22
|
+
const hookEventLog = require("./hook-event-log.cjs");
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Stamp a unified-bus delivery id (event_id) onto a hook payload (P2 event bus).
|
|
26
|
+
* Additive — a hook that ignores it is unaffected; a hook that reads it gets a
|
|
27
|
+
* stable per-delivery id for correlation / `cc hook replay`. traceId/parentId
|
|
28
|
+
* are threaded when the caller provides them (else null).
|
|
29
|
+
*
|
|
30
|
+
* When `CC_HOOK_EVENT_LOG` is enabled, the FULL envelope is best-effort recorded
|
|
31
|
+
* to the hash-chained hook event log so `cc hook replay <event-id>` has a real
|
|
32
|
+
* source. Recording is opt-in and never throws, so the default firing path is
|
|
33
|
+
* byte-for-byte unchanged.
|
|
34
|
+
*/
|
|
35
|
+
function withDeliveryId(event, payload, { sessionId, traceId, parentId } = {}) {
|
|
36
|
+
const env = buildHookEnvelope({
|
|
37
|
+
eventType: event,
|
|
38
|
+
data: payload,
|
|
39
|
+
sessionId: sessionId || payload.session_id || null,
|
|
40
|
+
traceId: traceId || null,
|
|
41
|
+
parentId: parentId || null,
|
|
42
|
+
});
|
|
43
|
+
if (hookEventLog.isHookEventLogEnabled()) {
|
|
44
|
+
try {
|
|
45
|
+
hookEventLog.appendHookEvent(env);
|
|
46
|
+
} catch {
|
|
47
|
+
/* best-effort — recording must not break the turn */
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { ...payload, event_id: env.event_id };
|
|
51
|
+
}
|
|
21
52
|
|
|
22
53
|
/**
|
|
23
54
|
* Split collected hooks into the blocking (sync) set and the fire-and-forget
|
|
@@ -70,12 +101,16 @@ function runUserPromptSubmitHooks(
|
|
|
70
101
|
if (matched.length === 0) return { blocked: false, additionalContext: null };
|
|
71
102
|
const { sync } = partitionAsyncHooks(matched);
|
|
72
103
|
if (sync.length === 0) return { blocked: false, additionalContext: null };
|
|
73
|
-
const payload =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
104
|
+
const payload = withDeliveryId(
|
|
105
|
+
"UserPromptSubmit",
|
|
106
|
+
{
|
|
107
|
+
hook_event_name: "UserPromptSubmit",
|
|
108
|
+
prompt: String(prompt || ""),
|
|
109
|
+
cwd,
|
|
110
|
+
session_id: sessionId || null,
|
|
111
|
+
},
|
|
112
|
+
{ sessionId },
|
|
113
|
+
);
|
|
79
114
|
const outcome = runHooks(sync, payload, {
|
|
80
115
|
cwd,
|
|
81
116
|
event: "UserPromptSubmit",
|
|
@@ -129,12 +164,16 @@ function runSessionStartHooks(settingsHooks, { source, cwd, sessionId } = {}) {
|
|
|
129
164
|
if (!settingsHooks) return { additionalContext: null };
|
|
130
165
|
const matched = collectHooks(settingsHooks, "SessionStart", source || "");
|
|
131
166
|
if (matched.length === 0) return { additionalContext: null };
|
|
132
|
-
const payload =
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
167
|
+
const payload = withDeliveryId(
|
|
168
|
+
"SessionStart",
|
|
169
|
+
{
|
|
170
|
+
hook_event_name: "SessionStart",
|
|
171
|
+
source: source || "startup",
|
|
172
|
+
cwd,
|
|
173
|
+
session_id: sessionId || null,
|
|
174
|
+
},
|
|
175
|
+
{ sessionId },
|
|
176
|
+
);
|
|
138
177
|
const outcome = runHooks(matched, payload, { cwd, event: "SessionStart" });
|
|
139
178
|
return { additionalContext: aggregateContext(outcome.results) };
|
|
140
179
|
}
|
|
@@ -159,7 +198,7 @@ function runObserveHooks(
|
|
|
159
198
|
if (sync.length === 0) return { decision: "continue", results: [] };
|
|
160
199
|
return runHooks(
|
|
161
200
|
sync,
|
|
162
|
-
{ hook_event_name: event, cwd, ...payload },
|
|
201
|
+
withDeliveryId(event, { hook_event_name: event, cwd, ...payload }),
|
|
163
202
|
{ cwd, event },
|
|
164
203
|
);
|
|
165
204
|
}
|
|
@@ -415,11 +415,53 @@ function readBooleanSetting(key, opts = {}) {
|
|
|
415
415
|
return value;
|
|
416
416
|
}
|
|
417
417
|
|
|
418
|
+
/**
|
|
419
|
+
* Read a string-array setting across the layered `.claude/settings.json` files
|
|
420
|
+
* plus managed settings. Unlike {@link readBooleanSetting} (last-layer wins),
|
|
421
|
+
* arrays UNION across layers (mirroring how `env`/`sandbox` accumulate in
|
|
422
|
+
* {@link loadSettingsConfig}) — a user-scope and a project-scope layer can each
|
|
423
|
+
* contribute entries. Non-array values and non-string members are ignored.
|
|
424
|
+
* Returns a de-duplicated array, or `undefined` when no layer sets it.
|
|
425
|
+
*
|
|
426
|
+
* @param {string} key settings key or dotted path (e.g. "instructionExcludes")
|
|
427
|
+
* @param {object} [opts]
|
|
428
|
+
* @param {string} [opts.cwd=process.cwd()]
|
|
429
|
+
* @param {string} [opts.settingsFile]
|
|
430
|
+
* @param {(msg:string)=>void} [opts.onWarn]
|
|
431
|
+
* @returns {string[]|undefined}
|
|
432
|
+
*/
|
|
433
|
+
function readStringArraySetting(key, opts = {}) {
|
|
434
|
+
const cwd = opts.cwd || process.cwd();
|
|
435
|
+
const parts = String(key).split(".");
|
|
436
|
+
const collected = [];
|
|
437
|
+
let sawAny = false;
|
|
438
|
+
const absorb = (data) => {
|
|
439
|
+
let node = data;
|
|
440
|
+
for (const p of parts) {
|
|
441
|
+
node = node && typeof node === "object" ? node[p] : undefined;
|
|
442
|
+
}
|
|
443
|
+
if (Array.isArray(node)) {
|
|
444
|
+
sawAny = true;
|
|
445
|
+
for (const item of node) {
|
|
446
|
+
if (typeof item === "string" && item.trim()) collected.push(item);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
for (const file of settingsPaths(cwd, opts.settingsFile)) {
|
|
451
|
+
absorb(readSettingsFile(file, { onWarn: opts.onWarn }));
|
|
452
|
+
}
|
|
453
|
+
const managed = loadManagedSettings(opts);
|
|
454
|
+
if (managed.settings) absorb(managed.settings);
|
|
455
|
+
if (!sawAny) return undefined;
|
|
456
|
+
return [...new Set(collected)];
|
|
457
|
+
}
|
|
458
|
+
|
|
418
459
|
module.exports = {
|
|
419
460
|
loadSettings,
|
|
420
461
|
loadSettingsConfig,
|
|
421
462
|
readSettingsFile,
|
|
422
463
|
readBooleanSetting,
|
|
464
|
+
readStringArraySetting,
|
|
423
465
|
settingsPaths,
|
|
424
466
|
managedSettingsPath,
|
|
425
467
|
loadManagedSettings,
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persist the side-effect ledger ([[side-effect-ledger.js]]) as a tamper-evident
|
|
3
|
+
* chained session event so a worker that crashed mid `git push` / `file-write` /
|
|
4
|
+
* `package-install` can rebuild its ledger on recovery and reconcile WITHOUT
|
|
5
|
+
* repeating a side-effect it already issued.
|
|
6
|
+
*
|
|
7
|
+
* Same shape as [[turn-binding-store.js]]: each persist writes the FULL ledger
|
|
8
|
+
* snapshot (newest wins), all I/O is delegated to the canonical session store
|
|
9
|
+
* via `_deps` so the module stays pure-testable.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
appendEvent as storeAppendEvent,
|
|
14
|
+
readEvents as storeReadEvents,
|
|
15
|
+
} from "../harness/jsonl-session-store.js";
|
|
16
|
+
import {
|
|
17
|
+
SideEffectLedger,
|
|
18
|
+
reconcileSideEffects,
|
|
19
|
+
} from "./side-effect-ledger.js";
|
|
20
|
+
|
|
21
|
+
/** Event type carrying a full SideEffectLedger snapshot. */
|
|
22
|
+
export const SIDE_EFFECT_LEDGER_EVENT = "side_effect_ledger";
|
|
23
|
+
|
|
24
|
+
export const _deps = {
|
|
25
|
+
appendEvent: storeAppendEvent,
|
|
26
|
+
readEvents: storeReadEvents,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Append the current ledger as a chained event. Best-effort — a persistence
|
|
31
|
+
* failure must not crash the operation it is guarding.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} sessionId
|
|
34
|
+
* @param {SideEffectLedger|object} ledger
|
|
35
|
+
* @returns {boolean}
|
|
36
|
+
*/
|
|
37
|
+
export function persistSideEffectLedger(sessionId, ledger) {
|
|
38
|
+
if (!sessionId || !ledger) return false;
|
|
39
|
+
const snapshot =
|
|
40
|
+
ledger instanceof SideEffectLedger ? ledger.toJSON() : ledger;
|
|
41
|
+
if (!snapshot || !Array.isArray(snapshot.ops)) return false;
|
|
42
|
+
try {
|
|
43
|
+
_deps.appendEvent(sessionId, SIDE_EFFECT_LEDGER_EVENT, snapshot);
|
|
44
|
+
return true;
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Rebuild the ledger from the LATEST persisted snapshot, or an empty ledger when
|
|
52
|
+
* the session never persisted one (or on read error).
|
|
53
|
+
*
|
|
54
|
+
* @param {string} sessionId
|
|
55
|
+
* @param {{clock?:Function}} [opts]
|
|
56
|
+
* @returns {SideEffectLedger}
|
|
57
|
+
*/
|
|
58
|
+
export function loadSideEffectLedger(sessionId, opts = {}) {
|
|
59
|
+
let events = [];
|
|
60
|
+
try {
|
|
61
|
+
events = _deps.readEvents(sessionId) || [];
|
|
62
|
+
} catch {
|
|
63
|
+
return new SideEffectLedger(opts);
|
|
64
|
+
}
|
|
65
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
66
|
+
const e = events[i];
|
|
67
|
+
if (
|
|
68
|
+
e &&
|
|
69
|
+
e.type === SIDE_EFFECT_LEDGER_EVENT &&
|
|
70
|
+
e.data &&
|
|
71
|
+
Array.isArray(e.data.ops)
|
|
72
|
+
) {
|
|
73
|
+
return SideEffectLedger.fromJSON(e.data, opts);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return new SideEffectLedger(opts);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Load + reconcile in one step: the recovery entry point. Returns the plan a
|
|
81
|
+
* restarting worker uses to decide, per op, redo / inspect / skip.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} sessionId
|
|
84
|
+
* @returns {{plans:Array, redo:string[], inspect:string[], skip:string[]}}
|
|
85
|
+
*/
|
|
86
|
+
export function reconcileSessionSideEffects(sessionId) {
|
|
87
|
+
return reconcileSideEffects(loadSideEffectLedger(sessionId));
|
|
88
|
+
}
|