chainlesschain 0.162.188 → 0.162.193
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/README.md +61 -11
- package/bin/chainlesschain.js +7 -52
- package/completions/_cc +209 -0
- package/completions/cc.bash +18 -0
- package/completions/cc.fish +13 -0
- package/completions/cc.ps1 +226 -0
- package/package.json +10 -2
- package/src/assets/web-panel/assets/{AIOps-Brr1LVcE.js → AIOps-DBLkaTje.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DQU74DKG.js → ActionButton-BAy-Qebo.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CYp9178y.js → Analytics-Dfb1Mlau.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DW4SCYsW.js → AppLayout-BbFkN8m0.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CYlD_ed1.js → Artifacts-kM4-cEEH.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CWl1rZOi.js → Audit-BSQfYWf3.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BOR-LPsa.js → BackgroundAgents-CWDbI_fG.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DLpG7j1X.js → Backup-BxLPbq_Q.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-B35K9wdr.js → BaseInput-DuIbdrp6.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CwUJb9gB.js → Chat-NEmjGahU.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-1v6aMS1m.js → ChatBubbleRenderer-DfJEo-5_.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-pi3-tPNW.js → Checkbox-CaoeG3Ns.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-FyNF35eL.js → Codegen-BFf_g3xY.js} +1 -1
- package/src/assets/web-panel/assets/{Col-tGt_JDEr.js → Col-CNOd8mdQ.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CC7LonQX.js → Community-BhpDqz-i.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BGXrHHjE.js → Compact-CQ42T7Z6.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DSW0TBZ8.js → Compliance-DI5PcLMN.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Bhk7rbvE.js → Cowork-wYYZe0wR.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DxyiQ2NZ.js → Cron-Dlq03DD_.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-B2ZkxHok.js → Crosschain-BzqS8PP3.js} +1 -1
- package/src/assets/web-panel/assets/{DID-yOJV99eJ.js → DID-It55Yem7.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CKsttz7D.js → Dashboard-9HkH6oxn.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CVFldXSL.js → Dropdown-00e2YWRB.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Bufv638J.js → EmailListRenderer-By-ugRDL.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-4ncjoIjL.js → FamilyGuardDashboard-C396yu5m.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DsknEDoX.js → Federation-BME5sbZY.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Cqe5jWXA.js → FormItemContext-B4TEZCr-.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-NAJaFKxc.js → GenericCardRenderer-_9CX9ieY.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CszKQpnl.js → Git-BrS6WVSr.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BXGodXi6.js → Governance-BMdCWnKF.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CkoYuG2c.js → Inference-Bcp3vzcA.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DFlEr_T0.js → KnowledgeGraph-DlLuqGY5.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CVaENddA.js → Logs-D-W_6x09.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-DElGPxCZ.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-CDlLT0fw.js → Marketplace-Dzn2guvI.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DdwOyszN.js → McpTools-uPict-KL.js} +6 -6
- package/src/assets/web-panel/assets/{Memory-DKQnkKuG.js → Memory-BCw43kOb.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DGseqlzJ.js → MobileBridge-DmV8Wv7F.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-COBbEOlj.js → MobileProjects-C-HaCmSM.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-O61Vwyli.js → Mtc-C6jxJ34Y.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-Dw2SyasS.js → MtcAudit-Za8ExwhQ.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-hSu_l8kf.js → Multisig-KKDbuEgb.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CX1Epurr.js → NLProgramming-Dj97zbCk.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-R9xX8o8_.js → Notes-DBXvMraU.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-jtSNLCuK.js → NotificationSettings-DOxITLYl.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-7DVjZwMM.js +1 -0
- package/src/assets/web-panel/assets/{Organization-Pu2WO-U3.js → Organization-D1i73mjb.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BjnqUyFp.js → Overflow-kW6Q3rvg.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-jjkvz44c.js → P2P-C-v_8p2v.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-tUbbY64W.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-D4yDpivd.js → Permissions-DkHxEZ58.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-0qsL6iH1.js → PersonalDataHub-DWuMPGrK.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-Cx7Ybrk2.js → Pipeline-30IYXWWm.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-PbfR_M_V.js → Privacy-DZNQ4RrH.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CyV6Gsub.js → ProjectInit-De2MCElf.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C7KZ2Wdk.js → ProjectSettings-5uHk9wsC.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DQDjO-yK.js → Projects-XjDfrFLb.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DyFVBBMX.js → Providers-Bgwj6gcE.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CzB1BC9_.js → QrScannerModal-gKql9umv.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Bk636mC1.js → QuickAsk-BWvmwkh7.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DoWxHIT3.js → Recommend-Uok4hZPd.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-oOVIv9wq.js → RemoteSession-DLJzTk9a.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-BucoReGG.js → Reputation-ZVwIPMSO.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DzmOdJU7.js → Row-CN04HxWR.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C1tK7KGU.js → RssFeed-BdSrEG4g.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CSnInOqk.js → Search-5PwVd0IR.js} +1 -1
- package/src/assets/web-panel/assets/{Security-dlejr15l.js → Security-DdBo4zjN.js} +3 -3
- package/src/assets/web-panel/assets/{Services-BioPZuNh.js → Services-D3letU7M.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DWMibwpC.js → Skeleton--Jb83f1v.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BAAnx1B2.js → Skills-Bg-CulHX.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-jWNp4hNO.js → Sla-7QOa7S-X.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-y5kqKha-.js → SpeechSettings-DQwppjRw.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-B9GNsc3e.js → SyncSettings-dNPQGL-s.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BDBYKRen.js → Tasks-Ggs-Z48q.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DRiE5eaN.js → Templates-78TYHgRO.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BGlAvExF.js → Tenant-xHwwv9vO.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B8uzrbNY.js → Terminal-BJXSJfm4.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Df48iveY.js → TimelineRenderer-fLbjRhJw.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CcmHndN6.js → Tokens-oMSBSBmB.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DPkWxmQ2.js → Trigger-CfnY_wX_.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DTb5a5AV.js → Trust-AdyTgYKO.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CBmg5vch.js → UkeySign-C5K_81dS.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B0mZrz4O.js → VideoEditing-vcGVDqki.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Bxs7_mpd.js → Wallet-hhOc3hlY.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-C8x3cMxU.js → WebAuthn-CVloC11s.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-DPrNznrP.js → WorkflowEditor-CYeE32R5.js} +1 -1
- package/src/assets/web-panel/assets/{chat-D3bYWu7S.js → chat-CNROR5bN.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DY-PwMu4.js → colors-Cjx-cnwU.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-LDk10Kw6.js → compact-item-DOIfelvD.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-C-2MZnFq.js → createContext-DfA7UZ8u.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-ChcCBGs7.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BQIBjcnB.js → hasIn-BMSCzcfM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQVb2dWp.js → index-5-HXgOX5.js} +1 -1
- package/src/assets/web-panel/assets/{index-BdXnYMCZ.js → index-AWg-0M1S.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cx6D_dx2.js → index-B4iYzpkL.js} +1 -1
- package/src/assets/web-panel/assets/{index-_HRO_cIu.js → index-B5Udeb_6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DlRYqt3S.js → index-B7gf77CJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BrnRO9yE.js → index-BEMwBBHk.js} +1 -1
- package/src/assets/web-panel/assets/{index-VCh5zNJo.js → index-BM1y_9hM.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRI3ngNg.js → index-BUonmKSE.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3GNvExG.js → index-BbQ2eQ8H.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSvo_bp0.js → index-BemJ2u_E.js} +1 -1
- package/src/assets/web-panel/assets/{index-L6qixyLc.js → index-BolaOQxx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BI4KgcYC.js → index-BxOk9vkG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CY4KnaO9.js → index-By61ojUx.js} +1 -1
- package/src/assets/web-panel/assets/{index-L3smcoQX.js → index-C2K9SNVX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BX2b9Orc.js → index-C3L5gdiD.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cz4b6Z_7.js → index-C7xn0Nub.js} +1 -1
- package/src/assets/web-panel/assets/{index-COG61sRv.js → index-CAnvhwg6.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7FC8cP2.js → index-CEKMcOyU.js} +1 -1
- package/src/assets/web-panel/assets/{index-BN-xdSWZ.js → index-CNC5onm4.js} +1 -1
- package/src/assets/web-panel/assets/{index-DY2FIrmz.js → index-CVL27kXC.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiTvBAr-.js → index-CVm1_i3t.js} +1 -1
- package/src/assets/web-panel/assets/{index-DhRt9cYJ.js → index-CdYBye8B.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5pgtZnY.js → index-ClNMrqEY.js} +1 -1
- package/src/assets/web-panel/assets/{index-7xKu7Fns.js → index-Cs6l7uRu.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4K1weKc.js → index-CshQbilz.js} +1 -1
- package/src/assets/web-panel/assets/{index-k7C_oDCO.js → index-Cyweh0Hy.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3Oq-DLL.js → index-DBpWXFed.js} +3 -3
- package/src/assets/web-panel/assets/{index-DYyLJPlF.js → index-DHtpHZAQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dh-tkpKQ.js → index-DLmn0BoK.js} +1 -1
- package/src/assets/web-panel/assets/{index-8JC9BJa1.js → index-DPAWBrMf.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dd2Km97U.js → index-DjEsxO5h.js} +1 -1
- package/src/assets/web-panel/assets/{index-KFZO_lcx.js → index-FBc_CJBd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTIZpPtG.js → index-Icdoqmzt.js} +1 -1
- package/src/assets/web-panel/assets/index-Q2IajL54.js +1 -0
- package/src/assets/web-panel/assets/index-bRkHgOMc.js +1 -0
- package/src/assets/web-panel/assets/{index-lmm9p-jm.js → index-dCd8cSUq.js} +1 -1
- package/src/assets/web-panel/assets/{index-HasPt4-h.js → index-m2vG9gG_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaslYch6.js → index-mTpHoEzr.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbR6ROOa.js → index-ti_lnTWl.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-zQPSHf2C.js → initDefaultProps-Bu8k7H1Y.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CKCStGVk.js → motion-s3iVK41k.js} +1 -1
- package/src/assets/web-panel/assets/{move-BufVDGYF.js → move-B6B-E5Jz.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DcaFxy5D.js → mtc-parser-BiYgTidA.js} +1 -1
- package/src/assets/web-panel/assets/{omit-EScpkGdo.js → omit-pU1RdUDA.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-72hG_qfT.js → pickAttrs-Cdoq_SeM.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CMFWiH20.js → placementArrow-DIuvAn2K.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DKQHS41p.js → responsiveObserve-8w16YR2D.js} +1 -1
- package/src/assets/web-panel/assets/{slide-QVAFHTIQ.js → slide-VZ01cZe3.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DIz38e2G.js → statusUtils-lh8YMN8Z.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-zK5cyLby.js → styleChecker-CmDyreec.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DhZngk62.js → useFlexGapSupport-hiJWMU5y.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BvV2cvkg.js → useFs-Thnu-Iaj.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CRmxb9l3.js → usePersonalDataHub-LBdOxjwY.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CgQIbobP.js → vnode-Cg6lFY_X.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-D4XrrO0R.js → zoom-DVfsR1YO.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/cli-help.js +181 -0
- package/src/command-help-index.json +181 -0
- package/src/command-manifest.json +1107 -175
- package/src/command-surface-policy.js +197 -0
- package/src/commands/agent.js +98 -33
- package/src/commands/artifacts.js +400 -2
- package/src/commands/background-session.js +41 -3
- package/src/commands/checkpoint-managed.js +1059 -0
- package/src/commands/checkpoint-restore-recovery.js +1476 -0
- package/src/commands/checkpoint.js +950 -22
- package/src/commands/compact.js +101 -25
- package/src/commands/config.js +506 -20
- package/src/commands/doctor.js +4 -1
- package/src/commands/hub.js +1 -1
- package/src/commands/mcp.js +688 -24
- package/src/commands/pack.js +16 -2
- package/src/commands/plugin.js +20 -12
- package/src/commands/session-mcp-recovery.js +218 -0
- package/src/commands/session.js +470 -110
- package/src/commands/setup.js +36 -10
- package/src/commands/skill.js +181 -110
- package/src/commands/status.js +21 -5
- package/src/commands/team-distributed.js +3324 -0
- package/src/commands/team.js +1130 -74
- package/src/commands/update.js +68 -6
- package/src/constants.js +11 -0
- package/src/data/changelog.json +26 -0
- package/src/gateways/ws/action-protocol.js +10 -0
- package/src/gateways/ws/session-protocol.js +555 -27
- package/src/gateways/ws/ws-agent-handler.js +541 -14
- package/src/gateways/ws/ws-session-gateway.js +129 -22
- package/src/gateways/ws/ws-session-state.js +431 -0
- package/src/harness/background-task-manager.js +233 -36
- package/src/harness/jsonl-session-store.js +2551 -167
- package/src/harness/mcp-client.js +802 -61
- package/src/harness/prompt-compressor.js +227 -27
- package/src/harness/session-index.js +89 -38
- package/src/harness/session-list-index.js +254 -0
- package/src/harness/structured-handoff.js +410 -0
- package/src/lazy-dispatch.js +747 -84
- package/src/lib/__tests__/logger.test.js +50 -2
- package/src/lib/advisor-runtime.js +1047 -0
- package/src/lib/agent-core.js +2 -0
- package/src/lib/agent-sandbox.js +120 -26
- package/src/lib/agent-session-export.js +20 -0
- package/src/lib/agent-team/task-lease.js +271 -3
- package/src/lib/agent-team/team-adjudication.js +1501 -0
- package/src/lib/agent-team/team-budget.js +4 -1
- package/src/lib/agent-team/team-control-store.js +582 -0
- package/src/lib/agent-team/team-distributed-queue.js +5246 -0
- package/src/lib/agent-team/team-process-checkpoint.js +211 -0
- package/src/lib/agent-team/team-runner.js +543 -24
- package/src/lib/agent-team/team-worktree.js +1403 -95
- package/src/lib/artifact-store.js +131 -8
- package/src/lib/background-agent-supervisor.js +185 -13
- package/src/lib/background-interaction-journal.js +37 -9
- package/src/lib/background-launch-profile.js +1058 -0
- package/src/lib/checkpoint-restore-already-completed-controller.js +902 -0
- package/src/lib/checkpoint-restore-orchestrator.js +987 -0
- package/src/lib/checkpoint-restore-partial-rollback-controller.js +1953 -0
- package/src/lib/checkpoint-restore-recovery-controller.js +689 -0
- package/src/lib/checkpoint-restore-recovery.js +884 -0
- package/src/lib/checkpoint-restore-retention.js +126 -0
- package/src/lib/checkpoint-restore-saga.js +5079 -0
- package/src/lib/checkpoint-restore-session-recovery.js +1178 -0
- package/src/lib/checkpoint-restore-workspace-target-verifier.js +270 -0
- package/src/lib/checkpoint-store.js +1688 -97
- package/src/lib/checkpoint-timeline-authority.js +522 -0
- package/src/lib/checkpoint-timeline.js +642 -0
- package/src/lib/config-keys.js +18 -119
- package/src/lib/config-manager.js +446 -27
- package/src/lib/config-redaction.js +57 -0
- package/src/lib/config-schema.js +853 -0
- package/src/lib/cowork-mcp-tools.js +5 -0
- package/src/lib/cowork-task-runner.js +458 -63
- package/src/lib/delivery-coordinator.js +1234 -0
- package/src/lib/delivery-evidence.js +450 -0
- package/src/lib/doctor-checkup.js +199 -14
- package/src/lib/executable-path.js +34 -0
- package/src/lib/fatal-handler.js +16 -0
- package/src/lib/file-checkpoint.js +4909 -186
- package/src/lib/file-lines.js +125 -0
- package/src/lib/headless-config-command.js +16 -5
- package/src/lib/hook-runner.cjs +139 -38
- package/src/lib/hooks-v2-workspace-context.js +10 -0
- package/src/lib/impacted-gate-selector.js +424 -0
- package/src/lib/interaction-adapter.js +62 -0
- package/src/lib/jsonl-session-store.js +1 -35
- package/src/lib/logger.js +66 -14
- package/src/lib/managed-checkpoint-render.js +60 -0
- package/src/lib/managed-tool-checkpoint.js +194 -0
- package/src/lib/mcp-call-ledger-store.js +1069 -0
- package/src/lib/mcp-call-ledger.js +747 -0
- package/src/lib/mcp-client.js +5 -0
- package/src/lib/mcp-conflict-scheduler.js +468 -0
- package/src/lib/mcp-effect-contract.js +102 -0
- package/src/lib/mcp-headers-helper-trust.js +145 -0
- package/src/lib/mcp-headers-helper.js +616 -0
- package/src/lib/mcp-host-recovery-runtime.js +131 -0
- package/src/lib/mcp-ledger-recovery-admission.js +1065 -0
- package/src/lib/mcp-recovery-adjudication.js +443 -0
- package/src/lib/output-context.js +86 -0
- package/src/lib/packer/file-identity.js +170 -0
- package/src/lib/packer/index.js +3 -0
- package/src/lib/packer/native-update-state.js +277 -0
- package/src/lib/packer/pack-update-applier.js +1983 -136
- package/src/lib/packer/pack-update-checker.js +34 -0
- package/src/lib/packer/pack-update-downloader.js +875 -86
- package/src/lib/packer/pack-update-signature.js +86 -0
- package/src/lib/packer/pkg-config-generator.js +6 -0
- package/src/lib/paths.js +65 -10
- package/src/lib/plan-mode.js +831 -14
- package/src/lib/plan-persistence.js +540 -0
- package/src/lib/plugin-runtime/hooks.js +227 -69
- package/src/lib/plugin-runtime/plugin-options.js +189 -44
- package/src/lib/pr-automation-policy.js +146 -8
- package/src/lib/process-execution-broker/credential-agent.js +7 -0
- package/src/lib/process-execution-broker/index.js +491 -13
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +28 -1
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +28 -4
- package/src/lib/process-execution-broker/platform-sandbox.js +92 -70
- package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
- package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
- package/src/lib/process-execution-broker/windows-sandbox.cs +284 -19
- package/src/lib/process-execution-broker/workspace-transaction.js +4459 -0
- package/src/lib/project-instructions.js +165 -29
- package/src/lib/project-mcp-trust.js +67 -0
- package/src/lib/public-ide-capability-manifest.js +272 -0
- package/src/lib/public-ide-capability-validation.js +351 -0
- package/src/lib/secret-store.js +267 -43
- package/src/lib/secure-file-identity.js +433 -0
- package/src/lib/secure-fs.js +1089 -0
- package/src/lib/security-audit.js +58 -0
- package/src/lib/service-manager.js +34 -13
- package/src/lib/session-budget-runtime.js +1452 -0
- package/src/lib/session-host-snapshot.js +388 -0
- package/src/lib/session-message-provenance.js +429 -0
- package/src/lib/session-projection.js +804 -0
- package/src/lib/session-resource-budget.js +1495 -0
- package/src/lib/settings-hooks.cjs +138 -22
- package/src/lib/settings-loader.cjs +25 -2
- package/src/lib/side-effect-ledger-store.js +45 -23
- package/src/lib/skill-execution-identity.js +217 -0
- package/src/lib/skill-loader.js +299 -18
- package/src/lib/sub-agent-context.js +236 -28
- package/src/lib/sub-agent-registry.js +5 -0
- package/src/lib/subagent-contract.js +94 -0
- package/src/lib/todo-manager.js +185 -18
- package/src/lib/todo-persistence.js +417 -0
- package/src/lib/turn-binding-store.js +41 -23
- package/src/lib/turn-binding.js +160 -2
- package/src/lib/with-file-lock.js +165 -24
- package/src/lib/ws-chat-handler.js +21 -0
- package/src/program-base.js +18 -0
- package/src/repl/advisor-command.js +103 -0
- package/src/repl/agent-repl.js +2175 -146
- package/src/repl/btw-command.js +93 -10
- package/src/repl/clipboard-image.js +119 -0
- package/src/repl/config-summary.js +64 -108
- package/src/repl/prompt-editor.js +233 -0
- package/src/repl/prompt-interactions.js +344 -0
- package/src/repl/prompt-stash.js +354 -0
- package/src/repl/prompt-suggestions.js +312 -0
- package/src/repl/repl-keybindings.js +227 -0
- package/src/repl/session-recap.js +217 -0
- package/src/repl/slash-command-registry.js +22 -0
- package/src/repl/terminal-layout.js +88 -0
- package/src/runtime/agent-core.js +1630 -447
- package/src/runtime/coding-agent-contract-shared.cjs +13 -1
- package/src/runtime/coding-agent-managed-tool-policy.cjs +7 -2
- package/src/runtime/diagnostics.js +32 -13
- package/src/runtime/headless-runner.js +310 -118
- package/src/runtime/headless-stream.js +356 -95
- package/src/runtime/mcp-config.js +148 -14
- package/src/runtime/policies/agent-policy.js +14 -0
- package/src/runtime/status-diagnostics-lite.js +137 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-7iWGwZ-r.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BU2Yqq5H.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-69ca_WeR.js +0 -7
- package/src/assets/web-panel/assets/devWarning-B4dXIJqD.js +0 -1
- package/src/assets/web-panel/assets/index-C-C7DaG6.js +0 -1
- package/src/assets/web-panel/assets/index-DkfWzWtd.js +0 -1
|
@@ -0,0 +1,1069 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable adapter for MCP call-ledger records.
|
|
3
|
+
*
|
|
4
|
+
* Each started/settled record is appended to the canonical tamper-evident
|
|
5
|
+
* session transcript. The payload already contains digests rather than raw
|
|
6
|
+
* tool input/output. Recovery folds the newest record per ledger id and treats
|
|
7
|
+
* every surviving `started` call as outcome-unknown; callers must inspect it
|
|
8
|
+
* instead of automatically replaying the MCP operation.
|
|
9
|
+
*/
|
|
10
|
+
import { isProxy } from "node:util/types";
|
|
11
|
+
import {
|
|
12
|
+
appendAuthorityEvent as storeAppendAuthorityEvent,
|
|
13
|
+
readVerifiedEvents as storeReadVerifiedEvents,
|
|
14
|
+
readVerifiedProjection as storeReadVerifiedProjection,
|
|
15
|
+
} from "../harness/jsonl-session-store.js";
|
|
16
|
+
import {
|
|
17
|
+
canonicalizeMcpNetworkScopes,
|
|
18
|
+
canonicalizeMcpResourceScopes,
|
|
19
|
+
LedgerFailureAction,
|
|
20
|
+
MCP_CALL_LEDGER_OUTPUT_KINDS,
|
|
21
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS,
|
|
22
|
+
MCP_CALL_LEDGER_SCHEMA_VERSION,
|
|
23
|
+
McpCallStatus,
|
|
24
|
+
McpEffect,
|
|
25
|
+
computeMcpExactReplayDigest,
|
|
26
|
+
normalizeMcpLedgerProtocolText,
|
|
27
|
+
normalizeMcpEffectContract,
|
|
28
|
+
sha256PayloadDigest,
|
|
29
|
+
snapshotMcpJsonRpcInput,
|
|
30
|
+
} from "./mcp-call-ledger.js";
|
|
31
|
+
|
|
32
|
+
export const MCP_CALL_LEDGER_EVENT = "mcp_call_ledger";
|
|
33
|
+
export const MCP_CALL_LEDGER_EVENT_SCHEMA_VERSION = 1;
|
|
34
|
+
export const MCP_CALL_RECOVERY_ADJUDICATION_EVENT =
|
|
35
|
+
"mcp_call_recovery_adjudication";
|
|
36
|
+
export const MCP_CALL_RECOVERY_ADJUDICATION_SCHEMA_VERSION = 1;
|
|
37
|
+
export const McpCallRecoveryDecision = Object.freeze({
|
|
38
|
+
CONFIRMED_APPLIED: "confirmed_applied",
|
|
39
|
+
CONFIRMED_NOT_APPLIED: "confirmed_not_applied",
|
|
40
|
+
});
|
|
41
|
+
export const MCP_CALL_RECOVERY_AUTHORITY = "local-cli-tty";
|
|
42
|
+
export const MCP_CALL_RECOVERY_CONFIRMATION = "typed-digest-host-stopped";
|
|
43
|
+
|
|
44
|
+
const TERMINAL = new Set([
|
|
45
|
+
McpCallStatus.COMPLETED,
|
|
46
|
+
McpCallStatus.FAILED,
|
|
47
|
+
McpCallStatus.CANCELLED,
|
|
48
|
+
]);
|
|
49
|
+
const PAYLOAD_DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
50
|
+
const RAW_AUTHORITY_HASH = /^[0-9a-f]{64}$/;
|
|
51
|
+
const OUTPUT_KINDS = new Set(MCP_CALL_LEDGER_OUTPUT_KINDS);
|
|
52
|
+
const RECORD_FIELDS = new Set([
|
|
53
|
+
"schemaVersion",
|
|
54
|
+
"ledgerId",
|
|
55
|
+
"sessionId",
|
|
56
|
+
"turnId",
|
|
57
|
+
"toolName",
|
|
58
|
+
"serverName",
|
|
59
|
+
"inputDigest",
|
|
60
|
+
"inputBytes",
|
|
61
|
+
"effectContract",
|
|
62
|
+
"resourceScopes",
|
|
63
|
+
"networkScopes",
|
|
64
|
+
"prewritePolicy",
|
|
65
|
+
"prewritePersistence",
|
|
66
|
+
"status",
|
|
67
|
+
"startedAt",
|
|
68
|
+
"settledAt",
|
|
69
|
+
"outputSummary",
|
|
70
|
+
"outputDigest",
|
|
71
|
+
"errorSummary",
|
|
72
|
+
"settlementPersistence",
|
|
73
|
+
]);
|
|
74
|
+
const EFFECT_CONTRACT_FIELDS = new Set([
|
|
75
|
+
"schemaVersion",
|
|
76
|
+
"effect",
|
|
77
|
+
"classification",
|
|
78
|
+
"readOnly",
|
|
79
|
+
"sideEffecting",
|
|
80
|
+
"destructive",
|
|
81
|
+
"idempotent",
|
|
82
|
+
"openWorld",
|
|
83
|
+
"trusted",
|
|
84
|
+
"source",
|
|
85
|
+
]);
|
|
86
|
+
const OUTPUT_SUMMARY_FIELDS = new Set(["sha256", "bytes", "kind"]);
|
|
87
|
+
const ERROR_SUMMARY_FIELDS = new Set(["name", "code", "messageDigest"]);
|
|
88
|
+
const ADJUDICATION_FIELDS = new Set([
|
|
89
|
+
"schemaVersion",
|
|
90
|
+
"requestId",
|
|
91
|
+
"sessionId",
|
|
92
|
+
"ledgerId",
|
|
93
|
+
"decision",
|
|
94
|
+
"expectedHeadHash",
|
|
95
|
+
"expectedRecoveryDigest",
|
|
96
|
+
"authority",
|
|
97
|
+
"confirmation",
|
|
98
|
+
"reasonDigest",
|
|
99
|
+
]);
|
|
100
|
+
const RECOVERY_PROJECTION_FIELDS = new Set([
|
|
101
|
+
"sessionId",
|
|
102
|
+
"records",
|
|
103
|
+
"unsettled",
|
|
104
|
+
"incidents",
|
|
105
|
+
"adjudications",
|
|
106
|
+
"replayDenied",
|
|
107
|
+
"verified",
|
|
108
|
+
"headHash",
|
|
109
|
+
"recoveryDigest",
|
|
110
|
+
"remediation",
|
|
111
|
+
]);
|
|
112
|
+
const RECOVERY_REMEDIATIONS = new Set([
|
|
113
|
+
"inspect_transcript",
|
|
114
|
+
"adjudicate_started_calls",
|
|
115
|
+
"exact_replay_denied",
|
|
116
|
+
]);
|
|
117
|
+
const RECOVERY_DIGEST_SCHEMA_VERSION = 1;
|
|
118
|
+
|
|
119
|
+
export const MCP_LEDGER_RECOVERY_PROJECTION_INVALID_CODE =
|
|
120
|
+
"CC_MCP_LEDGER_RECOVERY_PROJECTION_INVALID";
|
|
121
|
+
|
|
122
|
+
const VERIFIED_PROJECTION_AUTHORITY_FIELDS = new Set([
|
|
123
|
+
"headHash",
|
|
124
|
+
"eventCount",
|
|
125
|
+
"readMessages",
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
export class McpCallLedgerStoreError extends Error {
|
|
129
|
+
constructor(code, message, options = {}) {
|
|
130
|
+
super(message, options.cause ? { cause: options.cause } : undefined);
|
|
131
|
+
this.name = "McpCallLedgerStoreError";
|
|
132
|
+
this.code = code;
|
|
133
|
+
this.sessionId = normalizeMcpLedgerProtocolText(options.sessionId, null);
|
|
134
|
+
this.ledgerId = normalizeMcpLedgerProtocolText(
|
|
135
|
+
options.ledgerId,
|
|
136
|
+
null,
|
|
137
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS.ledgerId,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function validTimestamp(value) {
|
|
143
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function isPlainObject(value) {
|
|
147
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
148
|
+
const prototype = Object.getPrototypeOf(value);
|
|
149
|
+
return prototype === Object.prototype || prototype === null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function hasOnlyFields(value, allowedFields) {
|
|
153
|
+
return (
|
|
154
|
+
isPlainObject(value) &&
|
|
155
|
+
Object.keys(value).every((field) => allowedFields.has(field))
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function hasExactFields(value, requiredFields) {
|
|
160
|
+
if (!hasOnlyFields(value, requiredFields)) return false;
|
|
161
|
+
const fields = Object.keys(value);
|
|
162
|
+
return (
|
|
163
|
+
fields.length === requiredFields.size &&
|
|
164
|
+
[...requiredFields].every((field) =>
|
|
165
|
+
Object.prototype.hasOwnProperty.call(value, field),
|
|
166
|
+
)
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function canonicalScopes(value, canonicalize) {
|
|
171
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
return Object.freeze(canonicalize(value));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isCanonicalProtocolText(value, maxLength, nullable = false) {
|
|
178
|
+
if (value === null) return nullable;
|
|
179
|
+
if (typeof value !== "string") return false;
|
|
180
|
+
return value === normalizeMcpLedgerProtocolText(value, null, maxLength);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function canonicalOutputSummary(value) {
|
|
184
|
+
if (value === null) return null;
|
|
185
|
+
if (
|
|
186
|
+
!hasOnlyFields(value, OUTPUT_SUMMARY_FIELDS) ||
|
|
187
|
+
!PAYLOAD_DIGEST.test(String(value.sha256 || "")) ||
|
|
188
|
+
!Number.isInteger(value.bytes) ||
|
|
189
|
+
value.bytes < 0 ||
|
|
190
|
+
!OUTPUT_KINDS.has(value.kind)
|
|
191
|
+
) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
return Object.freeze({
|
|
195
|
+
sha256: value.sha256,
|
|
196
|
+
bytes: value.bytes,
|
|
197
|
+
kind: value.kind,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function canonicalErrorSummary(value) {
|
|
202
|
+
if (value === null) return null;
|
|
203
|
+
if (
|
|
204
|
+
!hasOnlyFields(value, ERROR_SUMMARY_FIELDS) ||
|
|
205
|
+
!isCanonicalProtocolText(
|
|
206
|
+
value.name,
|
|
207
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS.errorName,
|
|
208
|
+
) ||
|
|
209
|
+
(value.code !== null &&
|
|
210
|
+
!isCanonicalProtocolText(
|
|
211
|
+
value.code,
|
|
212
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS.errorCode,
|
|
213
|
+
)) ||
|
|
214
|
+
!PAYLOAD_DIGEST.test(String(value.messageDigest || ""))
|
|
215
|
+
) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
return Object.freeze({
|
|
219
|
+
name: value.name,
|
|
220
|
+
code: value.code,
|
|
221
|
+
messageDigest: value.messageDigest,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function canonicalRecord(record) {
|
|
226
|
+
if (!hasOnlyFields(record, RECORD_FIELDS)) return null;
|
|
227
|
+
if (!hasOnlyFields(record.effectContract, EFFECT_CONTRACT_FIELDS))
|
|
228
|
+
return null;
|
|
229
|
+
if (
|
|
230
|
+
record.effectContract.schemaVersion !== undefined &&
|
|
231
|
+
record.effectContract.schemaVersion !== MCP_CALL_LEDGER_SCHEMA_VERSION
|
|
232
|
+
) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const effectContract = normalizeMcpEffectContract(record.effectContract);
|
|
237
|
+
const resourceScopes = canonicalScopes(
|
|
238
|
+
record.resourceScopes,
|
|
239
|
+
canonicalizeMcpResourceScopes,
|
|
240
|
+
);
|
|
241
|
+
const networkScopes = canonicalScopes(
|
|
242
|
+
record.networkScopes,
|
|
243
|
+
canonicalizeMcpNetworkScopes,
|
|
244
|
+
);
|
|
245
|
+
const outputSummary = canonicalOutputSummary(record.outputSummary);
|
|
246
|
+
const errorSummary = canonicalErrorSummary(record.errorSummary);
|
|
247
|
+
const effectSourceValid =
|
|
248
|
+
record.effectContract.source == null ||
|
|
249
|
+
isCanonicalProtocolText(
|
|
250
|
+
record.effectContract.source,
|
|
251
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS.identifier,
|
|
252
|
+
);
|
|
253
|
+
const baseValid =
|
|
254
|
+
record.schemaVersion === MCP_CALL_LEDGER_SCHEMA_VERSION &&
|
|
255
|
+
isCanonicalProtocolText(
|
|
256
|
+
record.ledgerId,
|
|
257
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS.ledgerId,
|
|
258
|
+
) &&
|
|
259
|
+
isCanonicalProtocolText(
|
|
260
|
+
record.sessionId,
|
|
261
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS.identifier,
|
|
262
|
+
true,
|
|
263
|
+
) &&
|
|
264
|
+
isCanonicalProtocolText(
|
|
265
|
+
record.turnId,
|
|
266
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS.identifier,
|
|
267
|
+
true,
|
|
268
|
+
) &&
|
|
269
|
+
isCanonicalProtocolText(
|
|
270
|
+
record.toolName,
|
|
271
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS.identifier,
|
|
272
|
+
) &&
|
|
273
|
+
isCanonicalProtocolText(
|
|
274
|
+
record.serverName,
|
|
275
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS.identifier,
|
|
276
|
+
) &&
|
|
277
|
+
effectSourceValid &&
|
|
278
|
+
PAYLOAD_DIGEST.test(String(record.inputDigest || "")) &&
|
|
279
|
+
Number.isInteger(record.inputBytes) &&
|
|
280
|
+
record.inputBytes >= 0 &&
|
|
281
|
+
Object.values(McpEffect).includes(effectContract.effect) &&
|
|
282
|
+
resourceScopes !== null &&
|
|
283
|
+
networkScopes !== null &&
|
|
284
|
+
outputSummary !== false &&
|
|
285
|
+
errorSummary !== false &&
|
|
286
|
+
(record.outputDigest === null ||
|
|
287
|
+
PAYLOAD_DIGEST.test(String(record.outputDigest || ""))) &&
|
|
288
|
+
(outputSummary === null
|
|
289
|
+
? record.outputDigest === null
|
|
290
|
+
: record.outputDigest === outputSummary.sha256) &&
|
|
291
|
+
Object.values(LedgerFailureAction).includes(record.prewritePolicy) &&
|
|
292
|
+
(![McpEffect.WRITE, McpEffect.DESTRUCTIVE].includes(
|
|
293
|
+
effectContract.effect,
|
|
294
|
+
) ||
|
|
295
|
+
record.prewritePolicy === LedgerFailureAction.FAIL_CLOSED) &&
|
|
296
|
+
["pending", "persisted", "failed-open", "failed-closed"].includes(
|
|
297
|
+
record.prewritePersistence,
|
|
298
|
+
) &&
|
|
299
|
+
validTimestamp(record.startedAt) &&
|
|
300
|
+
[McpCallStatus.STARTED, ...TERMINAL].includes(record.status);
|
|
301
|
+
if (!baseValid) return null;
|
|
302
|
+
|
|
303
|
+
if (record.status === McpCallStatus.STARTED) {
|
|
304
|
+
if (
|
|
305
|
+
record.prewritePersistence === "pending" &&
|
|
306
|
+
record.settledAt === null &&
|
|
307
|
+
outputSummary === null &&
|
|
308
|
+
record.outputDigest === null &&
|
|
309
|
+
errorSummary === null &&
|
|
310
|
+
record.settlementPersistence == null
|
|
311
|
+
) {
|
|
312
|
+
return Object.freeze({
|
|
313
|
+
schemaVersion: record.schemaVersion,
|
|
314
|
+
ledgerId: record.ledgerId,
|
|
315
|
+
sessionId: record.sessionId,
|
|
316
|
+
turnId: record.turnId,
|
|
317
|
+
toolName: record.toolName,
|
|
318
|
+
serverName: record.serverName,
|
|
319
|
+
inputDigest: record.inputDigest,
|
|
320
|
+
inputBytes: record.inputBytes,
|
|
321
|
+
effectContract,
|
|
322
|
+
resourceScopes,
|
|
323
|
+
networkScopes,
|
|
324
|
+
prewritePolicy: record.prewritePolicy,
|
|
325
|
+
prewritePersistence: record.prewritePersistence,
|
|
326
|
+
status: record.status,
|
|
327
|
+
startedAt: record.startedAt,
|
|
328
|
+
settledAt: null,
|
|
329
|
+
outputSummary: null,
|
|
330
|
+
outputDigest: null,
|
|
331
|
+
errorSummary: null,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
if (
|
|
337
|
+
record.prewritePersistence !== "persisted" ||
|
|
338
|
+
!validTimestamp(record.settledAt) ||
|
|
339
|
+
record.settlementPersistence !== "pending"
|
|
340
|
+
) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
return Object.freeze({
|
|
344
|
+
schemaVersion: record.schemaVersion,
|
|
345
|
+
ledgerId: record.ledgerId,
|
|
346
|
+
sessionId: record.sessionId,
|
|
347
|
+
turnId: record.turnId,
|
|
348
|
+
toolName: record.toolName,
|
|
349
|
+
serverName: record.serverName,
|
|
350
|
+
inputDigest: record.inputDigest,
|
|
351
|
+
inputBytes: record.inputBytes,
|
|
352
|
+
effectContract,
|
|
353
|
+
resourceScopes,
|
|
354
|
+
networkScopes,
|
|
355
|
+
prewritePolicy: record.prewritePolicy,
|
|
356
|
+
prewritePersistence: record.prewritePersistence,
|
|
357
|
+
status: record.status,
|
|
358
|
+
startedAt: record.startedAt,
|
|
359
|
+
settledAt: record.settledAt,
|
|
360
|
+
outputSummary,
|
|
361
|
+
outputDigest: record.outputDigest,
|
|
362
|
+
errorSummary,
|
|
363
|
+
settlementPersistence: record.settlementPersistence,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function phaseMatchesRecord(phase, record) {
|
|
368
|
+
return (
|
|
369
|
+
(phase === "started" && record.status === McpCallStatus.STARTED) ||
|
|
370
|
+
(phase === "settled" && TERMINAL.has(record.status))
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const IMMUTABLE_RECORD_FIELDS = Object.freeze([
|
|
375
|
+
"schemaVersion",
|
|
376
|
+
"ledgerId",
|
|
377
|
+
"sessionId",
|
|
378
|
+
"turnId",
|
|
379
|
+
"toolName",
|
|
380
|
+
"serverName",
|
|
381
|
+
"inputDigest",
|
|
382
|
+
"inputBytes",
|
|
383
|
+
"effectContract",
|
|
384
|
+
"resourceScopes",
|
|
385
|
+
"networkScopes",
|
|
386
|
+
"prewritePolicy",
|
|
387
|
+
"startedAt",
|
|
388
|
+
]);
|
|
389
|
+
|
|
390
|
+
function stableValue(value) {
|
|
391
|
+
if (Array.isArray(value)) return value.map(stableValue);
|
|
392
|
+
if (value && typeof value === "object") {
|
|
393
|
+
return Object.fromEntries(
|
|
394
|
+
Object.keys(value)
|
|
395
|
+
.sort()
|
|
396
|
+
.map((key) => [key, stableValue(value[key])]),
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
return value ?? null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function stableSorted(values) {
|
|
403
|
+
return values.map(stableValue).sort((left, right) => {
|
|
404
|
+
const leftDigest = sha256PayloadDigest(left);
|
|
405
|
+
const rightDigest = sha256PayloadDigest(right);
|
|
406
|
+
const digestOrder =
|
|
407
|
+
leftDigest < rightDigest ? -1 : leftDigest > rightDigest ? 1 : 0;
|
|
408
|
+
if (digestOrder) return digestOrder;
|
|
409
|
+
const leftJson = JSON.stringify(left);
|
|
410
|
+
const rightJson = JSON.stringify(right);
|
|
411
|
+
return leftJson < rightJson ? -1 : leftJson > rightJson ? 1 : 0;
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function canonicalAdjudication(value) {
|
|
416
|
+
if (
|
|
417
|
+
!hasExactFields(value, ADJUDICATION_FIELDS) ||
|
|
418
|
+
value.schemaVersion !== MCP_CALL_RECOVERY_ADJUDICATION_SCHEMA_VERSION ||
|
|
419
|
+
!isCanonicalProtocolText(
|
|
420
|
+
value.requestId,
|
|
421
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS.identifier,
|
|
422
|
+
) ||
|
|
423
|
+
!isCanonicalProtocolText(
|
|
424
|
+
value.sessionId,
|
|
425
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS.identifier,
|
|
426
|
+
) ||
|
|
427
|
+
!isCanonicalProtocolText(
|
|
428
|
+
value.ledgerId,
|
|
429
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS.ledgerId,
|
|
430
|
+
) ||
|
|
431
|
+
!Object.values(McpCallRecoveryDecision).includes(value.decision) ||
|
|
432
|
+
!RAW_AUTHORITY_HASH.test(String(value.expectedHeadHash || "")) ||
|
|
433
|
+
!PAYLOAD_DIGEST.test(String(value.expectedRecoveryDigest || "")) ||
|
|
434
|
+
value.authority !== MCP_CALL_RECOVERY_AUTHORITY ||
|
|
435
|
+
value.confirmation !== MCP_CALL_RECOVERY_CONFIRMATION ||
|
|
436
|
+
!PAYLOAD_DIGEST.test(String(value.reasonDigest || ""))
|
|
437
|
+
) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
return Object.freeze({
|
|
441
|
+
schemaVersion: value.schemaVersion,
|
|
442
|
+
requestId: value.requestId,
|
|
443
|
+
sessionId: value.sessionId,
|
|
444
|
+
ledgerId: value.ledgerId,
|
|
445
|
+
decision: value.decision,
|
|
446
|
+
expectedHeadHash: value.expectedHeadHash,
|
|
447
|
+
expectedRecoveryDigest: value.expectedRecoveryDigest,
|
|
448
|
+
authority: value.authority,
|
|
449
|
+
confirmation: value.confirmation,
|
|
450
|
+
reasonDigest: value.reasonDigest,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function replayToolNamesFromRecord(record) {
|
|
455
|
+
const toolName = record?.toolName;
|
|
456
|
+
const serverName = record?.serverName;
|
|
457
|
+
if (typeof toolName !== "string" || typeof serverName !== "string") {
|
|
458
|
+
return [];
|
|
459
|
+
}
|
|
460
|
+
const aliasPrefix = `mcp__${serverName}__`;
|
|
461
|
+
if (!toolName.startsWith(aliasPrefix)) return [toolName];
|
|
462
|
+
const stripped = toolName.slice(aliasPrefix.length);
|
|
463
|
+
// Historical records did not identify whether toolName was the raw host
|
|
464
|
+
// name or the model-facing alias. Preserve both exact candidates so either
|
|
465
|
+
// interpretation remains denied; new writes use the raw name in agent-core.
|
|
466
|
+
return stripped && stripped !== toolName ? [toolName, stripped] : [toolName];
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export function deriveMcpExactReplayDenies(record) {
|
|
470
|
+
return Object.freeze(
|
|
471
|
+
replayToolNamesFromRecord(record).map((toolName) =>
|
|
472
|
+
Object.freeze({
|
|
473
|
+
ledgerId: record.ledgerId,
|
|
474
|
+
serverName: record.serverName,
|
|
475
|
+
toolName,
|
|
476
|
+
inputBytes: record.inputBytes,
|
|
477
|
+
replayDigest: computeMcpExactReplayDigest({ ...record, toolName }),
|
|
478
|
+
}),
|
|
479
|
+
),
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function exactReplayKey(value) {
|
|
484
|
+
return JSON.stringify([
|
|
485
|
+
value?.serverName || null,
|
|
486
|
+
value?.toolName || null,
|
|
487
|
+
Number.isInteger(value?.inputBytes) ? value.inputBytes : null,
|
|
488
|
+
value?.replayDigest || computeMcpExactReplayDigest(value),
|
|
489
|
+
]);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function exactReplayKeysFromRecord(record) {
|
|
493
|
+
return deriveMcpExactReplayDenies(record).map(exactReplayKey);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/** Deterministic digest of the complete active MCP recovery authority. */
|
|
497
|
+
export function computeMcpRecoveryDigest(recovery = {}) {
|
|
498
|
+
const unsettled = Array.isArray(recovery.unsettled) ? recovery.unsettled : [];
|
|
499
|
+
const incidents = Array.isArray(recovery.incidents) ? recovery.incidents : [];
|
|
500
|
+
const adjudications = Array.isArray(recovery.adjudications)
|
|
501
|
+
? recovery.adjudications
|
|
502
|
+
: [];
|
|
503
|
+
const replayDenied = Array.isArray(recovery.replayDenied)
|
|
504
|
+
? recovery.replayDenied
|
|
505
|
+
: [];
|
|
506
|
+
return sha256PayloadDigest({
|
|
507
|
+
schemaVersion: RECOVERY_DIGEST_SCHEMA_VERSION,
|
|
508
|
+
sessionId: recovery.sessionId || null,
|
|
509
|
+
headHash: recovery.headHash || null,
|
|
510
|
+
unsettled: stableSorted(unsettled),
|
|
511
|
+
incidents: stableSorted(incidents),
|
|
512
|
+
adjudications: stableSorted(adjudications),
|
|
513
|
+
replayDenied: stableSorted(replayDenied),
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function immutableRecordMatches(started, settled) {
|
|
518
|
+
return IMMUTABLE_RECORD_FIELDS.every(
|
|
519
|
+
(field) =>
|
|
520
|
+
JSON.stringify(stableValue(started?.[field])) ===
|
|
521
|
+
JSON.stringify(stableValue(settled?.[field])),
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function invalidRecoveryProjection(reason, cause) {
|
|
526
|
+
const error = new TypeError(
|
|
527
|
+
`Verified MCP recovery projection is invalid (${reason})`,
|
|
528
|
+
cause ? { cause } : undefined,
|
|
529
|
+
);
|
|
530
|
+
error.code = MCP_LEDGER_RECOVERY_PROJECTION_INVALID_CODE;
|
|
531
|
+
return error;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/** Validate one canonical projection completion without invoking accessors. */
|
|
535
|
+
export function assertMcpVerifiedProjectionAuthority(
|
|
536
|
+
authority,
|
|
537
|
+
{ acceptedCount, headHash },
|
|
538
|
+
) {
|
|
539
|
+
if (
|
|
540
|
+
!authority ||
|
|
541
|
+
typeof authority !== "object" ||
|
|
542
|
+
Array.isArray(authority) ||
|
|
543
|
+
isProxy(authority) ||
|
|
544
|
+
(Object.getPrototypeOf(authority) !== Object.prototype &&
|
|
545
|
+
Object.getPrototypeOf(authority) !== null) ||
|
|
546
|
+
Object.getOwnPropertySymbols(authority).length > 0
|
|
547
|
+
) {
|
|
548
|
+
throw invalidRecoveryProjection("completion-authority-not-plain");
|
|
549
|
+
}
|
|
550
|
+
const descriptors = Object.getOwnPropertyDescriptors(authority);
|
|
551
|
+
const fields = Object.keys(descriptors);
|
|
552
|
+
if (
|
|
553
|
+
fields.length !== VERIFIED_PROJECTION_AUTHORITY_FIELDS.size ||
|
|
554
|
+
fields.some(
|
|
555
|
+
(field) =>
|
|
556
|
+
!VERIFIED_PROJECTION_AUTHORITY_FIELDS.has(field) ||
|
|
557
|
+
!("value" in descriptors[field]) ||
|
|
558
|
+
!descriptors[field].enumerable,
|
|
559
|
+
)
|
|
560
|
+
) {
|
|
561
|
+
throw invalidRecoveryProjection("completion-authority-malformed");
|
|
562
|
+
}
|
|
563
|
+
const projectedHeadHash = descriptors.headHash.value;
|
|
564
|
+
const projectedEventCount = descriptors.eventCount.value;
|
|
565
|
+
if (
|
|
566
|
+
(projectedHeadHash !== null &&
|
|
567
|
+
(typeof projectedHeadHash !== "string" ||
|
|
568
|
+
!RAW_AUTHORITY_HASH.test(projectedHeadHash))) ||
|
|
569
|
+
!Number.isSafeInteger(projectedEventCount) ||
|
|
570
|
+
projectedEventCount < 0 ||
|
|
571
|
+
typeof descriptors.readMessages.value !== "function" ||
|
|
572
|
+
projectedEventCount !== acceptedCount ||
|
|
573
|
+
projectedHeadHash !== headHash
|
|
574
|
+
) {
|
|
575
|
+
throw invalidRecoveryProjection("completion-authority-mismatch");
|
|
576
|
+
}
|
|
577
|
+
return Object.freeze({
|
|
578
|
+
headHash: projectedHeadHash,
|
|
579
|
+
eventCount: projectedEventCount,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Copy a recovery authority through the strict MCP JSON boundary before any
|
|
585
|
+
* caller consumes it. This rejects Promises/thenables, Proxies, accessors,
|
|
586
|
+
* toJSON hooks and malformed projections without invoking user getters.
|
|
587
|
+
*/
|
|
588
|
+
export function snapshotMcpLedgerRecoveryProjection(sessionId, recovery) {
|
|
589
|
+
let snapshot;
|
|
590
|
+
try {
|
|
591
|
+
snapshot = snapshotMcpJsonRpcInput(recovery);
|
|
592
|
+
} catch (cause) {
|
|
593
|
+
throw invalidRecoveryProjection("not-strict-synchronous-data", cause);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (
|
|
597
|
+
!hasExactFields(snapshot, RECOVERY_PROJECTION_FIELDS) ||
|
|
598
|
+
snapshot.sessionId !== sessionId ||
|
|
599
|
+
snapshot.verified !== true ||
|
|
600
|
+
!Array.isArray(snapshot.records) ||
|
|
601
|
+
!Array.isArray(snapshot.unsettled) ||
|
|
602
|
+
!Array.isArray(snapshot.incidents) ||
|
|
603
|
+
!Array.isArray(snapshot.adjudications) ||
|
|
604
|
+
!Array.isArray(snapshot.replayDenied) ||
|
|
605
|
+
(snapshot.headHash !== null &&
|
|
606
|
+
(typeof snapshot.headHash !== "string" ||
|
|
607
|
+
!RAW_AUTHORITY_HASH.test(snapshot.headHash))) ||
|
|
608
|
+
typeof snapshot.recoveryDigest !== "string" ||
|
|
609
|
+
!PAYLOAD_DIGEST.test(snapshot.recoveryDigest) ||
|
|
610
|
+
(snapshot.remediation !== null &&
|
|
611
|
+
!RECOVERY_REMEDIATIONS.has(snapshot.remediation))
|
|
612
|
+
) {
|
|
613
|
+
throw invalidRecoveryProjection("malformed-authority");
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (snapshot.recoveryDigest !== computeMcpRecoveryDigest(snapshot)) {
|
|
617
|
+
throw invalidRecoveryProjection("digest-mismatch");
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const expectedRemediation =
|
|
621
|
+
snapshot.incidents.length > 0
|
|
622
|
+
? "inspect_transcript"
|
|
623
|
+
: snapshot.unsettled.length > 0
|
|
624
|
+
? "adjudicate_started_calls"
|
|
625
|
+
: snapshot.replayDenied.length > 0
|
|
626
|
+
? "exact_replay_denied"
|
|
627
|
+
: null;
|
|
628
|
+
if (snapshot.remediation !== expectedRemediation) {
|
|
629
|
+
throw invalidRecoveryProjection("remediation-mismatch");
|
|
630
|
+
}
|
|
631
|
+
return snapshot;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/** Create the async sink accepted by `McpCallLedger`. */
|
|
635
|
+
export function createSessionMcpLedgerSink(sessionId, options = {}) {
|
|
636
|
+
const appendEvent = options.appendEvent || storeAppendAuthorityEvent;
|
|
637
|
+
if (
|
|
638
|
+
!isCanonicalProtocolText(
|
|
639
|
+
sessionId,
|
|
640
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS.identifier,
|
|
641
|
+
)
|
|
642
|
+
) {
|
|
643
|
+
throw new TypeError("MCP ledger session sink requires sessionId");
|
|
644
|
+
}
|
|
645
|
+
if (typeof appendEvent !== "function") {
|
|
646
|
+
throw new TypeError("MCP ledger session sink requires appendEvent");
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
return async (record, metadata = {}) => {
|
|
650
|
+
const canonical = canonicalRecord(record);
|
|
651
|
+
if (!canonical) {
|
|
652
|
+
throw new McpCallLedgerStoreError(
|
|
653
|
+
"CC_MCP_LEDGER_RECORD_INVALID",
|
|
654
|
+
"MCP ledger refused to persist an invalid record",
|
|
655
|
+
{ sessionId, ledgerId: record?.ledgerId || null },
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
if (canonical.sessionId !== sessionId) {
|
|
659
|
+
throw new McpCallLedgerStoreError(
|
|
660
|
+
"CC_MCP_LEDGER_SESSION_MISMATCH",
|
|
661
|
+
"MCP ledger record session does not match its durable transcript",
|
|
662
|
+
{ sessionId, ledgerId: canonical.ledgerId },
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
const phase = String(metadata.phase || "");
|
|
666
|
+
if (phase !== "started" && phase !== "settled") {
|
|
667
|
+
throw new McpCallLedgerStoreError(
|
|
668
|
+
"CC_MCP_LEDGER_PHASE_INVALID",
|
|
669
|
+
`MCP ledger event phase is invalid: ${phase || "(missing)"}`,
|
|
670
|
+
{ sessionId, ledgerId: canonical.ledgerId },
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
if (!phaseMatchesRecord(phase, canonical)) {
|
|
674
|
+
throw new McpCallLedgerStoreError(
|
|
675
|
+
"CC_MCP_LEDGER_PHASE_STATUS_MISMATCH",
|
|
676
|
+
`MCP ledger event phase ${phase} does not match status ${canonical.status}`,
|
|
677
|
+
{ sessionId, ledgerId: canonical.ledgerId },
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
try {
|
|
681
|
+
const written = await appendEvent(sessionId, MCP_CALL_LEDGER_EVENT, {
|
|
682
|
+
schemaVersion: MCP_CALL_LEDGER_EVENT_SCHEMA_VERSION,
|
|
683
|
+
phase,
|
|
684
|
+
record: canonical,
|
|
685
|
+
});
|
|
686
|
+
if (written === false) {
|
|
687
|
+
throw new Error("canonical session store rejected the event");
|
|
688
|
+
}
|
|
689
|
+
return true;
|
|
690
|
+
} catch (cause) {
|
|
691
|
+
throw new McpCallLedgerStoreError(
|
|
692
|
+
"CC_MCP_LEDGER_EVENT_PERSIST_FAILED",
|
|
693
|
+
`MCP ledger event persistence failed for ${sessionId}: ${cause.message}`,
|
|
694
|
+
{ sessionId, ledgerId: canonical.ledgerId, cause },
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Create an incremental MCP authority reducer. Every transcript event must be
|
|
702
|
+
* passed to `accept()` in canonical order so verified prefix linkage remains
|
|
703
|
+
* part of the projection. The retained heap is the active MCP authority state,
|
|
704
|
+
* not the complete transcript.
|
|
705
|
+
*/
|
|
706
|
+
export function createMcpLedgerEventReducer(options = {}) {
|
|
707
|
+
const records = new Map();
|
|
708
|
+
const incidents = [];
|
|
709
|
+
const adjudications = new Map();
|
|
710
|
+
const adjudicationRequestIds = new Set();
|
|
711
|
+
const replayDenied = new Map();
|
|
712
|
+
const targetSessionId =
|
|
713
|
+
typeof options.sessionId === "string" ? options.sessionId : null;
|
|
714
|
+
const verified = options.verified === true;
|
|
715
|
+
const addIncident = (code, ledgerId = null) => {
|
|
716
|
+
incidents.push(Object.freeze({ code, ledgerId }));
|
|
717
|
+
};
|
|
718
|
+
let prefixHeadHash = null;
|
|
719
|
+
|
|
720
|
+
const accept = (event) => {
|
|
721
|
+
const priorHeadHash = prefixHeadHash;
|
|
722
|
+
const eventHeadHash = RAW_AUTHORITY_HASH.test(String(event?.hash || ""))
|
|
723
|
+
? event.hash
|
|
724
|
+
: null;
|
|
725
|
+
const prefixLinked =
|
|
726
|
+
eventHeadHash !== null && (event?.prevHash ?? null) === priorHeadHash;
|
|
727
|
+
prefixHeadHash = eventHeadHash;
|
|
728
|
+
if (verified && !prefixLinked) {
|
|
729
|
+
addIncident("CC_MCP_RECOVERY_AUTHORITY_PREFIX_INVALID");
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (event?.type === MCP_CALL_RECOVERY_ADJUDICATION_EVENT) {
|
|
734
|
+
const adjudication = canonicalAdjudication(event.data);
|
|
735
|
+
if (!adjudication) {
|
|
736
|
+
addIncident("CC_MCP_RECOVERY_ADJUDICATION_CORRUPT");
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (!verified) {
|
|
740
|
+
addIncident(
|
|
741
|
+
"CC_MCP_RECOVERY_ADJUDICATION_UNVERIFIED",
|
|
742
|
+
adjudication.ledgerId,
|
|
743
|
+
);
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
if (
|
|
747
|
+
targetSessionId === null ||
|
|
748
|
+
adjudication.sessionId !== targetSessionId ||
|
|
749
|
+
priorHeadHash !== adjudication.expectedHeadHash ||
|
|
750
|
+
event.prevHash !== adjudication.expectedHeadHash
|
|
751
|
+
) {
|
|
752
|
+
addIncident(
|
|
753
|
+
"CC_MCP_RECOVERY_ADJUDICATION_HEAD_MISMATCH",
|
|
754
|
+
adjudication.ledgerId,
|
|
755
|
+
);
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
if (incidents.length > 0) {
|
|
759
|
+
addIncident(
|
|
760
|
+
"CC_MCP_RECOVERY_ADJUDICATION_INCIDENTS_PRESENT",
|
|
761
|
+
adjudication.ledgerId,
|
|
762
|
+
);
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const currentSnapshots = [...records.values()];
|
|
767
|
+
const currentRecovery = {
|
|
768
|
+
sessionId: targetSessionId,
|
|
769
|
+
headHash: priorHeadHash,
|
|
770
|
+
unsettled: currentSnapshots.filter(
|
|
771
|
+
(record) =>
|
|
772
|
+
record.status === McpCallStatus.STARTED &&
|
|
773
|
+
!adjudications.has(record.ledgerId),
|
|
774
|
+
),
|
|
775
|
+
incidents,
|
|
776
|
+
adjudications: [...adjudications.values()],
|
|
777
|
+
replayDenied: [...replayDenied.values()],
|
|
778
|
+
};
|
|
779
|
+
if (
|
|
780
|
+
adjudication.expectedRecoveryDigest !==
|
|
781
|
+
computeMcpRecoveryDigest(currentRecovery)
|
|
782
|
+
) {
|
|
783
|
+
addIncident(
|
|
784
|
+
"CC_MCP_RECOVERY_ADJUDICATION_DIGEST_MISMATCH",
|
|
785
|
+
adjudication.ledgerId,
|
|
786
|
+
);
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const target = records.get(adjudication.ledgerId);
|
|
791
|
+
if (
|
|
792
|
+
!target ||
|
|
793
|
+
target.status !== McpCallStatus.STARTED ||
|
|
794
|
+
adjudications.has(adjudication.ledgerId) ||
|
|
795
|
+
adjudicationRequestIds.has(adjudication.requestId)
|
|
796
|
+
) {
|
|
797
|
+
addIncident(
|
|
798
|
+
"CC_MCP_RECOVERY_ADJUDICATION_TARGET_INVALID",
|
|
799
|
+
adjudication.ledgerId,
|
|
800
|
+
);
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
adjudications.set(adjudication.ledgerId, adjudication);
|
|
805
|
+
adjudicationRequestIds.add(adjudication.requestId);
|
|
806
|
+
if (adjudication.decision === McpCallRecoveryDecision.CONFIRMED_APPLIED) {
|
|
807
|
+
for (const deny of deriveMcpExactReplayDenies(target)) {
|
|
808
|
+
const key = exactReplayKey(deny);
|
|
809
|
+
if (!replayDenied.has(key)) replayDenied.set(key, deny);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
if (event?.type !== MCP_CALL_LEDGER_EVENT) return;
|
|
816
|
+
const envelope = event.data;
|
|
817
|
+
const record = canonicalRecord(envelope?.record);
|
|
818
|
+
if (
|
|
819
|
+
envelope?.schemaVersion !== MCP_CALL_LEDGER_EVENT_SCHEMA_VERSION ||
|
|
820
|
+
!record ||
|
|
821
|
+
!phaseMatchesRecord(envelope?.phase, record)
|
|
822
|
+
) {
|
|
823
|
+
addIncident("CC_MCP_LEDGER_EVENT_CORRUPT", record?.ledgerId || null);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
if (targetSessionId !== null && record.sessionId !== targetSessionId) {
|
|
828
|
+
addIncident("CC_MCP_LEDGER_SESSION_MISMATCH", record.ledgerId);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
if (adjudications.has(record.ledgerId)) {
|
|
833
|
+
addIncident("CC_MCP_LEDGER_ADJUDICATED_REWRITTEN", record.ledgerId);
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (
|
|
838
|
+
exactReplayKeysFromRecord(record).some((key) => replayDenied.has(key))
|
|
839
|
+
) {
|
|
840
|
+
addIncident("CC_MCP_LEDGER_EXACT_REPLAY_RECORDED", record.ledgerId);
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
const previous = records.get(record.ledgerId);
|
|
845
|
+
if (previous && TERMINAL.has(previous.status)) {
|
|
846
|
+
addIncident("CC_MCP_LEDGER_TERMINAL_REWRITTEN", record.ledgerId);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
if (previous && record.status === McpCallStatus.STARTED) {
|
|
850
|
+
addIncident("CC_MCP_LEDGER_DUPLICATE_START", record.ledgerId);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
if (!previous && record.status !== McpCallStatus.STARTED) {
|
|
854
|
+
addIncident("CC_MCP_LEDGER_START_MISSING", record.ledgerId);
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
if (
|
|
858
|
+
previous &&
|
|
859
|
+
record.status !== McpCallStatus.STARTED &&
|
|
860
|
+
!immutableRecordMatches(previous, record)
|
|
861
|
+
) {
|
|
862
|
+
addIncident("CC_MCP_LEDGER_RECORD_MISMATCH", record.ledgerId);
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
records.set(record.ledgerId, Object.freeze({ ...record }));
|
|
866
|
+
};
|
|
867
|
+
|
|
868
|
+
const finish = () => {
|
|
869
|
+
const snapshots = [...records.values()];
|
|
870
|
+
const unsettled = snapshots.filter(
|
|
871
|
+
(record) =>
|
|
872
|
+
record.status === McpCallStatus.STARTED &&
|
|
873
|
+
!adjudications.has(record.ledgerId),
|
|
874
|
+
);
|
|
875
|
+
const denied = [...replayDenied.values()];
|
|
876
|
+
const recovery = {
|
|
877
|
+
sessionId: targetSessionId,
|
|
878
|
+
records: Object.freeze(snapshots),
|
|
879
|
+
unsettled: Object.freeze(unsettled),
|
|
880
|
+
incidents: Object.freeze(incidents),
|
|
881
|
+
adjudications: Object.freeze([...adjudications.values()]),
|
|
882
|
+
replayDenied: Object.freeze(denied),
|
|
883
|
+
verified,
|
|
884
|
+
headHash: prefixHeadHash,
|
|
885
|
+
};
|
|
886
|
+
const remediation =
|
|
887
|
+
incidents.length > 0
|
|
888
|
+
? "inspect_transcript"
|
|
889
|
+
: unsettled.length > 0
|
|
890
|
+
? "adjudicate_started_calls"
|
|
891
|
+
: denied.length > 0
|
|
892
|
+
? "exact_replay_denied"
|
|
893
|
+
: null;
|
|
894
|
+
return Object.freeze({
|
|
895
|
+
...recovery,
|
|
896
|
+
recoveryDigest: computeMcpRecoveryDigest(recovery),
|
|
897
|
+
remediation,
|
|
898
|
+
});
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
return Object.freeze({ accept, finish });
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Batch compatibility wrapper around the incremental authority reducer.
|
|
906
|
+
*/
|
|
907
|
+
export function reduceMcpLedgerEvents(events, options = {}) {
|
|
908
|
+
const reducer = createMcpLedgerEventReducer(options);
|
|
909
|
+
for (const event of Array.isArray(events) ? events : []) {
|
|
910
|
+
reducer.accept(event);
|
|
911
|
+
}
|
|
912
|
+
return reducer.finish();
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
export function loadMcpLedgerRecovery(sessionId, options = {}) {
|
|
916
|
+
const hasInjectedProjection = Object.prototype.hasOwnProperty.call(
|
|
917
|
+
options,
|
|
918
|
+
"readVerifiedProjection",
|
|
919
|
+
);
|
|
920
|
+
const hasInjectedLegacyReader = Object.prototype.hasOwnProperty.call(
|
|
921
|
+
options,
|
|
922
|
+
"readVerifiedEvents",
|
|
923
|
+
);
|
|
924
|
+
if (
|
|
925
|
+
(hasInjectedProjection &&
|
|
926
|
+
typeof options.readVerifiedProjection !== "function") ||
|
|
927
|
+
(hasInjectedLegacyReader &&
|
|
928
|
+
typeof options.readVerifiedEvents !== "function")
|
|
929
|
+
) {
|
|
930
|
+
throw new McpCallLedgerStoreError(
|
|
931
|
+
"CC_MCP_LEDGER_EVENT_READ_FAILED",
|
|
932
|
+
`MCP ledger recovery failed for ${sessionId}: verified reader is invalid`,
|
|
933
|
+
{
|
|
934
|
+
sessionId,
|
|
935
|
+
cause: invalidRecoveryProjection("reader-not-callable"),
|
|
936
|
+
},
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
const hasCustomLegacyReader =
|
|
940
|
+
hasInjectedLegacyReader &&
|
|
941
|
+
options.readVerifiedEvents !== storeReadVerifiedEvents;
|
|
942
|
+
const readVerifiedProjection = hasInjectedProjection
|
|
943
|
+
? options.readVerifiedProjection
|
|
944
|
+
: hasCustomLegacyReader
|
|
945
|
+
? null
|
|
946
|
+
: storeReadVerifiedProjection;
|
|
947
|
+
const readVerifiedEvents =
|
|
948
|
+
options.readVerifiedEvents || storeReadVerifiedEvents;
|
|
949
|
+
try {
|
|
950
|
+
if (typeof readVerifiedProjection === "function") {
|
|
951
|
+
let projectionCreated = false;
|
|
952
|
+
let finishedProjection;
|
|
953
|
+
const returnedProjection = readVerifiedProjection(sessionId, () => {
|
|
954
|
+
if (projectionCreated) {
|
|
955
|
+
throw invalidRecoveryProjection("factory-reused");
|
|
956
|
+
}
|
|
957
|
+
projectionCreated = true;
|
|
958
|
+
let acceptedCount = 0;
|
|
959
|
+
let finished = false;
|
|
960
|
+
const reducer = createMcpLedgerEventReducer({
|
|
961
|
+
sessionId,
|
|
962
|
+
verified: true,
|
|
963
|
+
});
|
|
964
|
+
return Object.freeze({
|
|
965
|
+
accept(event) {
|
|
966
|
+
if (finished) {
|
|
967
|
+
throw invalidRecoveryProjection("accept-after-finish");
|
|
968
|
+
}
|
|
969
|
+
acceptedCount += 1;
|
|
970
|
+
reducer.accept(event);
|
|
971
|
+
},
|
|
972
|
+
finish(authority) {
|
|
973
|
+
if (finished) {
|
|
974
|
+
throw invalidRecoveryProjection("finish-reused");
|
|
975
|
+
}
|
|
976
|
+
finished = true;
|
|
977
|
+
const recovery = reducer.finish();
|
|
978
|
+
assertMcpVerifiedProjectionAuthority(authority, {
|
|
979
|
+
acceptedCount,
|
|
980
|
+
headHash: recovery.headHash,
|
|
981
|
+
});
|
|
982
|
+
finishedProjection = recovery;
|
|
983
|
+
return recovery;
|
|
984
|
+
},
|
|
985
|
+
});
|
|
986
|
+
});
|
|
987
|
+
if (
|
|
988
|
+
!projectionCreated ||
|
|
989
|
+
finishedProjection === undefined ||
|
|
990
|
+
returnedProjection !== finishedProjection
|
|
991
|
+
) {
|
|
992
|
+
throw invalidRecoveryProjection("reader-bypassed-factory");
|
|
993
|
+
}
|
|
994
|
+
return snapshotMcpLedgerRecoveryProjection(sessionId, returnedProjection);
|
|
995
|
+
}
|
|
996
|
+
const verifiedEvents = snapshotMcpJsonRpcInput(
|
|
997
|
+
readVerifiedEvents(sessionId),
|
|
998
|
+
);
|
|
999
|
+
if (!Array.isArray(verifiedEvents)) {
|
|
1000
|
+
const invalid = new TypeError(
|
|
1001
|
+
"Verified MCP ledger events must be returned synchronously as an array",
|
|
1002
|
+
);
|
|
1003
|
+
invalid.code = "CC_MCP_LEDGER_VERIFIED_EVENTS_INVALID";
|
|
1004
|
+
throw invalid;
|
|
1005
|
+
}
|
|
1006
|
+
return snapshotMcpLedgerRecoveryProjection(
|
|
1007
|
+
sessionId,
|
|
1008
|
+
reduceMcpLedgerEvents(verifiedEvents, {
|
|
1009
|
+
sessionId,
|
|
1010
|
+
verified: true,
|
|
1011
|
+
}),
|
|
1012
|
+
);
|
|
1013
|
+
} catch (cause) {
|
|
1014
|
+
throw new McpCallLedgerStoreError(
|
|
1015
|
+
"CC_MCP_LEDGER_EVENT_READ_FAILED",
|
|
1016
|
+
`MCP ledger recovery failed for ${sessionId}: ${cause.message}`,
|
|
1017
|
+
{ sessionId, cause },
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/** Bounded, content-free recovery notice for model/IDE presentation. */
|
|
1023
|
+
export function formatMcpLedgerRecoveryNotice(recovery, options = {}) {
|
|
1024
|
+
const maxItems = Math.max(1, Math.min(20, Number(options.maxItems) || 10));
|
|
1025
|
+
const unsettled = Array.isArray(recovery?.unsettled)
|
|
1026
|
+
? recovery.unsettled.slice(0, maxItems)
|
|
1027
|
+
: [];
|
|
1028
|
+
const incidents = Array.isArray(recovery?.incidents)
|
|
1029
|
+
? recovery.incidents.slice(0, maxItems)
|
|
1030
|
+
: [];
|
|
1031
|
+
const replayDenied = Array.isArray(recovery?.replayDenied)
|
|
1032
|
+
? recovery.replayDenied.slice(0, maxItems)
|
|
1033
|
+
: [];
|
|
1034
|
+
if (
|
|
1035
|
+
unsettled.length === 0 &&
|
|
1036
|
+
incidents.length === 0 &&
|
|
1037
|
+
replayDenied.length === 0
|
|
1038
|
+
) {
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const lines = unsettled.map((record) => {
|
|
1043
|
+
const effect = record.effectContract?.effect || "unknown";
|
|
1044
|
+
return ` • ${record.ledgerId}: ${record.serverName}/${record.toolName} [${effect}] — outcome unknown; inspect before retry`;
|
|
1045
|
+
});
|
|
1046
|
+
for (const incident of incidents) {
|
|
1047
|
+
lines.push(
|
|
1048
|
+
` • ledger incident ${incident.code}${incident.ledgerId ? ` (${incident.ledgerId})` : ""} — fail closed and inspect transcript`,
|
|
1049
|
+
);
|
|
1050
|
+
}
|
|
1051
|
+
for (const deny of replayDenied) {
|
|
1052
|
+
lines.push(
|
|
1053
|
+
` • ${deny.ledgerId}: ${deny.serverName}/${deny.toolName} — exact replay denied (${deny.replayDigest})`,
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
const omitted =
|
|
1057
|
+
Math.max(0, (recovery?.unsettled?.length || 0) - unsettled.length) +
|
|
1058
|
+
Math.max(0, (recovery?.incidents?.length || 0) - incidents.length) +
|
|
1059
|
+
Math.max(0, (recovery?.replayDenied?.length || 0) - replayDenied.length);
|
|
1060
|
+
if (omitted > 0)
|
|
1061
|
+
lines.push(` • ${omitted} additional ledger item(s) omitted`);
|
|
1062
|
+
|
|
1063
|
+
return (
|
|
1064
|
+
"MCP recovery notice — a previous run left MCP recovery authority that " +
|
|
1065
|
+
"must be enforced. Do NOT automatically retry denied or outcome-unknown " +
|
|
1066
|
+
"calls. Verify the external resource first and ask the user when uncertain:\n" +
|
|
1067
|
+
lines.join("\n")
|
|
1068
|
+
);
|
|
1069
|
+
}
|