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,1065 @@
|
|
|
1
|
+
import { isProxy } from "node:util/types";
|
|
2
|
+
import {
|
|
3
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS,
|
|
4
|
+
McpEffect,
|
|
5
|
+
computeMcpExactReplayDigest,
|
|
6
|
+
createMcpCallLedger,
|
|
7
|
+
normalizeMcpLedgerProtocolText,
|
|
8
|
+
normalizeMcpEffectContract,
|
|
9
|
+
snapshotMcpJsonRpcInput,
|
|
10
|
+
summarizeMcpPayload,
|
|
11
|
+
} from "./mcp-call-ledger.js";
|
|
12
|
+
|
|
13
|
+
export const McpRecoveryBlockMode = Object.freeze({
|
|
14
|
+
ALL: "all",
|
|
15
|
+
UNSAFE: "unsafe",
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const MCP_RECOVERY_INVALID_CODE = "CC_MCP_LEDGER_RECOVERY_INVALID";
|
|
19
|
+
export const MCP_OUTCOME_UNKNOWN_CODE = "CC_MCP_LEDGER_OUTCOME_UNKNOWN";
|
|
20
|
+
export const MCP_EXACT_REPLAY_DENIED_CODE = "CC_MCP_LEDGER_EXACT_REPLAY_DENIED";
|
|
21
|
+
export const MCP_RECOVERY_DENY_REGRESSION_CODE =
|
|
22
|
+
"CC_MCP_LEDGER_REPLAY_DENY_REGRESSION";
|
|
23
|
+
|
|
24
|
+
const SHA256_DIGEST = /^sha256:[0-9a-f]{64}$/;
|
|
25
|
+
const REPLAY_DENY_FIELDS = new Set([
|
|
26
|
+
"ledgerId",
|
|
27
|
+
"serverName",
|
|
28
|
+
"toolName",
|
|
29
|
+
"inputBytes",
|
|
30
|
+
"replayDigest",
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
const RECOVERY_CONTROLLERS = new WeakSet();
|
|
34
|
+
const GUARDED_LEDGER_CONTROLLERS = new WeakMap();
|
|
35
|
+
const GUARDED_MCP_CLIENTS = new WeakMap();
|
|
36
|
+
|
|
37
|
+
function ownDataValue(record, key) {
|
|
38
|
+
const descriptor = Object.getOwnPropertyDescriptor(record, key);
|
|
39
|
+
if (!descriptor || !("value" in descriptor)) {
|
|
40
|
+
return { valid: false, value: undefined };
|
|
41
|
+
}
|
|
42
|
+
return { valid: true, value: descriptor.value };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isPlainDataObject(value) {
|
|
46
|
+
if (
|
|
47
|
+
!value ||
|
|
48
|
+
typeof value !== "object" ||
|
|
49
|
+
Array.isArray(value) ||
|
|
50
|
+
isProxy(value)
|
|
51
|
+
) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const prototype = Object.getPrototypeOf(value);
|
|
55
|
+
return prototype === Object.prototype || prototype === null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function canonicalReplayDenyEntry(value) {
|
|
59
|
+
if (!isPlainDataObject(value)) return null;
|
|
60
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
61
|
+
const fields = Object.keys(descriptors);
|
|
62
|
+
if (
|
|
63
|
+
fields.length !== REPLAY_DENY_FIELDS.size ||
|
|
64
|
+
fields.some((field) => !REPLAY_DENY_FIELDS.has(field)) ||
|
|
65
|
+
[...REPLAY_DENY_FIELDS].some(
|
|
66
|
+
(field) => !descriptors[field] || !("value" in descriptors[field]),
|
|
67
|
+
)
|
|
68
|
+
) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const ledgerId = descriptors.ledgerId.value;
|
|
72
|
+
const serverName = descriptors.serverName.value;
|
|
73
|
+
const toolName = descriptors.toolName.value;
|
|
74
|
+
const inputBytes = descriptors.inputBytes.value;
|
|
75
|
+
const replayDigest = descriptors.replayDigest.value;
|
|
76
|
+
if (
|
|
77
|
+
typeof ledgerId !== "string" ||
|
|
78
|
+
ledgerId !==
|
|
79
|
+
normalizeMcpLedgerProtocolText(
|
|
80
|
+
ledgerId,
|
|
81
|
+
null,
|
|
82
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS.ledgerId,
|
|
83
|
+
) ||
|
|
84
|
+
typeof serverName !== "string" ||
|
|
85
|
+
serverName !==
|
|
86
|
+
normalizeMcpLedgerProtocolText(
|
|
87
|
+
serverName,
|
|
88
|
+
null,
|
|
89
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS.identifier,
|
|
90
|
+
) ||
|
|
91
|
+
typeof toolName !== "string" ||
|
|
92
|
+
toolName !==
|
|
93
|
+
normalizeMcpLedgerProtocolText(
|
|
94
|
+
toolName,
|
|
95
|
+
null,
|
|
96
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS.identifier,
|
|
97
|
+
) ||
|
|
98
|
+
!Number.isInteger(inputBytes) ||
|
|
99
|
+
inputBytes < 0 ||
|
|
100
|
+
typeof replayDigest !== "string" ||
|
|
101
|
+
!SHA256_DIGEST.test(replayDigest)
|
|
102
|
+
) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return Object.freeze({
|
|
106
|
+
ledgerId,
|
|
107
|
+
serverName,
|
|
108
|
+
toolName,
|
|
109
|
+
inputBytes,
|
|
110
|
+
replayDigest,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function replayDenyKey(value) {
|
|
115
|
+
return JSON.stringify([
|
|
116
|
+
value?.serverName || null,
|
|
117
|
+
value?.toolName || null,
|
|
118
|
+
Number.isInteger(value?.inputBytes) ? value.inputBytes : null,
|
|
119
|
+
value?.replayDigest || null,
|
|
120
|
+
]);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function replayDenyAuthorityKey(value) {
|
|
124
|
+
return JSON.stringify([value?.ledgerId || null, replayDenyKey(value)]);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function invalidMcpCallInput(reason) {
|
|
128
|
+
const error = new TypeError(
|
|
129
|
+
`MCP call input is not strict immutable JSON data (${reason})`,
|
|
130
|
+
);
|
|
131
|
+
error.code = "CC_MCP_WIRE_INPUT_INVALID";
|
|
132
|
+
return error;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function snapshotMcpAdmissionCall(call = {}) {
|
|
136
|
+
if (!isPlainDataObject(call)) throw invalidMcpCallInput("call-envelope");
|
|
137
|
+
if (Object.getOwnPropertySymbols(call).length > 0) {
|
|
138
|
+
throw invalidMcpCallInput("symbol-key");
|
|
139
|
+
}
|
|
140
|
+
const descriptors = Object.getOwnPropertyDescriptors(call);
|
|
141
|
+
const snapshot = {};
|
|
142
|
+
for (const key of Object.keys(descriptors)) {
|
|
143
|
+
const descriptor = descriptors[key];
|
|
144
|
+
if (!("value" in descriptor)) throw invalidMcpCallInput("accessor");
|
|
145
|
+
if (!descriptor.enumerable) throw invalidMcpCallInput("hidden-property");
|
|
146
|
+
if (key === "input") continue;
|
|
147
|
+
Object.defineProperty(snapshot, key, {
|
|
148
|
+
configurable: false,
|
|
149
|
+
enumerable: true,
|
|
150
|
+
writable: false,
|
|
151
|
+
value: descriptor.value,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
const inputDescriptor = descriptors.input;
|
|
155
|
+
Object.defineProperty(snapshot, "input", {
|
|
156
|
+
configurable: false,
|
|
157
|
+
enumerable: true,
|
|
158
|
+
writable: false,
|
|
159
|
+
value: snapshotMcpJsonRpcInput(
|
|
160
|
+
inputDescriptor ? inputDescriptor.value : {},
|
|
161
|
+
),
|
|
162
|
+
});
|
|
163
|
+
return Object.freeze(snapshot);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function replayIdentityForCall(call = {}) {
|
|
167
|
+
const serverName = normalizeMcpLedgerProtocolText(
|
|
168
|
+
call.serverName || call.server,
|
|
169
|
+
null,
|
|
170
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS.identifier,
|
|
171
|
+
);
|
|
172
|
+
const toolNameValue = normalizeMcpLedgerProtocolText(
|
|
173
|
+
call.toolName || call.tool,
|
|
174
|
+
null,
|
|
175
|
+
MCP_CALL_LEDGER_PROTOCOL_LIMITS.identifier,
|
|
176
|
+
);
|
|
177
|
+
if (!serverName || !toolNameValue) return null;
|
|
178
|
+
const toolName = toolNameValue;
|
|
179
|
+
const input = snapshotMcpJsonRpcInput(
|
|
180
|
+
Object.prototype.hasOwnProperty.call(call, "input") ? call.input : {},
|
|
181
|
+
);
|
|
182
|
+
const summary = summarizeMcpPayload(input);
|
|
183
|
+
return Object.freeze({
|
|
184
|
+
serverName,
|
|
185
|
+
toolName,
|
|
186
|
+
inputBytes: summary.bytes,
|
|
187
|
+
replayDigest: computeMcpExactReplayDigest({
|
|
188
|
+
serverName,
|
|
189
|
+
toolName,
|
|
190
|
+
inputDigest: summary.sha256,
|
|
191
|
+
inputBytes: summary.bytes,
|
|
192
|
+
}),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function findReplayDeny(replayDenied, call) {
|
|
197
|
+
if (!Array.isArray(replayDenied) || replayDenied.length === 0) return null;
|
|
198
|
+
const identity = replayIdentityForCall(call);
|
|
199
|
+
if (!identity) return null;
|
|
200
|
+
const key = replayDenyKey(identity);
|
|
201
|
+
return replayDenied.find((entry) => replayDenyKey(entry) === key) || null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function invalidRecoveryProjection() {
|
|
205
|
+
return {
|
|
206
|
+
valid: false,
|
|
207
|
+
incidents: 0,
|
|
208
|
+
unsettled: 0,
|
|
209
|
+
explicitMode: null,
|
|
210
|
+
replayDenied: Object.freeze([]),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function inspectRecoveryProjection(recovery) {
|
|
215
|
+
if (recovery == null) {
|
|
216
|
+
return {
|
|
217
|
+
valid: true,
|
|
218
|
+
incidents: 0,
|
|
219
|
+
unsettled: 0,
|
|
220
|
+
explicitMode: null,
|
|
221
|
+
replayDenied: Object.freeze([]),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
if (
|
|
227
|
+
typeof recovery !== "object" ||
|
|
228
|
+
isProxy(recovery) ||
|
|
229
|
+
Array.isArray(recovery)
|
|
230
|
+
) {
|
|
231
|
+
return invalidRecoveryProjection();
|
|
232
|
+
}
|
|
233
|
+
const prototype = Object.getPrototypeOf(recovery);
|
|
234
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
235
|
+
return invalidRecoveryProjection();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const thenDescriptor = Object.getOwnPropertyDescriptor(recovery, "then");
|
|
239
|
+
if (
|
|
240
|
+
thenDescriptor &&
|
|
241
|
+
(!("value" in thenDescriptor) ||
|
|
242
|
+
typeof thenDescriptor.value === "function")
|
|
243
|
+
) {
|
|
244
|
+
return invalidRecoveryProjection();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const incidentsProperty = ownDataValue(recovery, "incidents");
|
|
248
|
+
const unsettledProperty = ownDataValue(recovery, "unsettled");
|
|
249
|
+
const incidentsValue = incidentsProperty.value;
|
|
250
|
+
const unsettledValue = unsettledProperty.value;
|
|
251
|
+
if (
|
|
252
|
+
!incidentsProperty.valid ||
|
|
253
|
+
!unsettledProperty.valid ||
|
|
254
|
+
isProxy(incidentsValue) ||
|
|
255
|
+
isProxy(unsettledValue) ||
|
|
256
|
+
!Array.isArray(incidentsValue) ||
|
|
257
|
+
!Array.isArray(unsettledValue)
|
|
258
|
+
) {
|
|
259
|
+
return invalidRecoveryProjection();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const blockModeDescriptor = Object.getOwnPropertyDescriptor(
|
|
263
|
+
recovery,
|
|
264
|
+
"blockMode",
|
|
265
|
+
);
|
|
266
|
+
if (blockModeDescriptor && !("value" in blockModeDescriptor)) {
|
|
267
|
+
return invalidRecoveryProjection();
|
|
268
|
+
}
|
|
269
|
+
const explicitMode = blockModeDescriptor?.value;
|
|
270
|
+
if (
|
|
271
|
+
explicitMode != null &&
|
|
272
|
+
explicitMode !== McpRecoveryBlockMode.ALL &&
|
|
273
|
+
explicitMode !== McpRecoveryBlockMode.UNSAFE
|
|
274
|
+
) {
|
|
275
|
+
return invalidRecoveryProjection();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const replayDeniedDescriptor = Object.getOwnPropertyDescriptor(
|
|
279
|
+
recovery,
|
|
280
|
+
"replayDenied",
|
|
281
|
+
);
|
|
282
|
+
if (!replayDeniedDescriptor || !("value" in replayDeniedDescriptor)) {
|
|
283
|
+
return invalidRecoveryProjection();
|
|
284
|
+
}
|
|
285
|
+
const replayDeniedValue = replayDeniedDescriptor.value;
|
|
286
|
+
if (isProxy(replayDeniedValue) || !Array.isArray(replayDeniedValue)) {
|
|
287
|
+
return invalidRecoveryProjection();
|
|
288
|
+
}
|
|
289
|
+
const replayDenied = replayDeniedValue.map(canonicalReplayDenyEntry);
|
|
290
|
+
if (replayDenied.some((entry) => entry === null)) {
|
|
291
|
+
return invalidRecoveryProjection();
|
|
292
|
+
}
|
|
293
|
+
const replayKeys = new Set(replayDenied.map(replayDenyKey));
|
|
294
|
+
if (replayKeys.size !== replayDenied.length) {
|
|
295
|
+
return invalidRecoveryProjection();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
valid: true,
|
|
300
|
+
incidents: incidentsValue.length,
|
|
301
|
+
unsettled: unsettledValue.length,
|
|
302
|
+
explicitMode: explicitMode ?? null,
|
|
303
|
+
replayDenied: Object.freeze(replayDenied),
|
|
304
|
+
};
|
|
305
|
+
} catch {
|
|
306
|
+
// Accessors/proxies are not trusted recovery evidence. Keep admission
|
|
307
|
+
// synchronous and fail closed instead of allowing a malformed projection.
|
|
308
|
+
return invalidRecoveryProjection();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Reduce a content-free recovery projection to one admission decision. A
|
|
314
|
+
* present projection must contain synchronous incidents/unsettled arrays;
|
|
315
|
+
* malformed or asynchronous values are not evidence of a clean recovery.
|
|
316
|
+
* Incidents and verified-read failures block every MCP call. A surviving
|
|
317
|
+
* started record blocks non-read effects until a caller explicitly
|
|
318
|
+
* adjudicates the prior outcome.
|
|
319
|
+
*/
|
|
320
|
+
export function classifyMcpRecoveryAdmission(
|
|
321
|
+
recovery,
|
|
322
|
+
{ recoveryError = null, blockMode: requestedMode = null } = {},
|
|
323
|
+
) {
|
|
324
|
+
const projection = inspectRecoveryProjection(recovery);
|
|
325
|
+
const { incidents, unsettled, explicitMode } = projection;
|
|
326
|
+
const replayDenied = projection.replayDenied.length;
|
|
327
|
+
const requestedModeValid =
|
|
328
|
+
requestedMode == null ||
|
|
329
|
+
requestedMode === McpRecoveryBlockMode.ALL ||
|
|
330
|
+
requestedMode === McpRecoveryBlockMode.UNSAFE;
|
|
331
|
+
const reasonCode =
|
|
332
|
+
projection.valid && requestedModeValid ? null : MCP_RECOVERY_INVALID_CODE;
|
|
333
|
+
let blockMode = null;
|
|
334
|
+
if (
|
|
335
|
+
!projection.valid ||
|
|
336
|
+
!requestedModeValid ||
|
|
337
|
+
recoveryError != null ||
|
|
338
|
+
incidents > 0
|
|
339
|
+
) {
|
|
340
|
+
blockMode = McpRecoveryBlockMode.ALL;
|
|
341
|
+
} else if (requestedMode || explicitMode) {
|
|
342
|
+
blockMode = requestedMode || explicitMode;
|
|
343
|
+
} else if (unsettled > 0) {
|
|
344
|
+
blockMode = McpRecoveryBlockMode.UNSAFE;
|
|
345
|
+
}
|
|
346
|
+
return Object.freeze({
|
|
347
|
+
blockMode,
|
|
348
|
+
incidents,
|
|
349
|
+
unsettled,
|
|
350
|
+
replayDenied,
|
|
351
|
+
reasonCode,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function admissionRank(blockMode) {
|
|
356
|
+
if (blockMode === McpRecoveryBlockMode.ALL) return 2;
|
|
357
|
+
if (blockMode === McpRecoveryBlockMode.UNSAFE) return 1;
|
|
358
|
+
return 0;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Mutable-by-method, immutable-by-reference recovery admission authority.
|
|
363
|
+
* Only a new verified projection may replace (and therefore lower) the current
|
|
364
|
+
* admission. Runtime latches are monotonic until that explicit replacement.
|
|
365
|
+
*/
|
|
366
|
+
export function createMcpRecoveryAdmissionController(
|
|
367
|
+
recovery = null,
|
|
368
|
+
options = {},
|
|
369
|
+
) {
|
|
370
|
+
let currentProjection = inspectRecoveryProjection(recovery);
|
|
371
|
+
let currentAdmission = classifyMcpRecoveryAdmission(recovery, options);
|
|
372
|
+
|
|
373
|
+
const latch = (blockMode, reasonCode = null) => {
|
|
374
|
+
const requestedRank = admissionRank(blockMode);
|
|
375
|
+
const currentRank = admissionRank(currentAdmission.blockMode);
|
|
376
|
+
if (requestedRank < currentRank) return currentAdmission;
|
|
377
|
+
if (requestedRank === currentRank) {
|
|
378
|
+
if (currentAdmission.reasonCode || !reasonCode) return currentAdmission;
|
|
379
|
+
currentAdmission = Object.freeze({
|
|
380
|
+
...currentAdmission,
|
|
381
|
+
reasonCode,
|
|
382
|
+
});
|
|
383
|
+
return currentAdmission;
|
|
384
|
+
}
|
|
385
|
+
currentAdmission = Object.freeze({
|
|
386
|
+
...currentAdmission,
|
|
387
|
+
blockMode,
|
|
388
|
+
// A stricter admission must explain the stricter condition. Preserve the
|
|
389
|
+
// old reason only when the escalation itself has no diagnosable cause.
|
|
390
|
+
reasonCode: reasonCode || currentAdmission.reasonCode || null,
|
|
391
|
+
});
|
|
392
|
+
return currentAdmission;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const controller = {
|
|
396
|
+
get admission() {
|
|
397
|
+
return currentAdmission;
|
|
398
|
+
},
|
|
399
|
+
getAdmission() {
|
|
400
|
+
return currentAdmission;
|
|
401
|
+
},
|
|
402
|
+
replaceVerifiedRecovery(nextRecovery) {
|
|
403
|
+
const replacementProjection = inspectRecoveryProjection(nextRecovery);
|
|
404
|
+
const replacement = classifyMcpRecoveryAdmission(nextRecovery);
|
|
405
|
+
if (replacement.reasonCode === MCP_RECOVERY_INVALID_CODE) {
|
|
406
|
+
return latch(McpRecoveryBlockMode.ALL, MCP_RECOVERY_INVALID_CODE);
|
|
407
|
+
}
|
|
408
|
+
const replacementAuthority = new Set(
|
|
409
|
+
replacementProjection.replayDenied.map(replayDenyAuthorityKey),
|
|
410
|
+
);
|
|
411
|
+
const losesDeny = currentProjection.replayDenied.some(
|
|
412
|
+
(entry) => !replacementAuthority.has(replayDenyAuthorityKey(entry)),
|
|
413
|
+
);
|
|
414
|
+
if (losesDeny) {
|
|
415
|
+
return latch(
|
|
416
|
+
McpRecoveryBlockMode.ALL,
|
|
417
|
+
MCP_RECOVERY_DENY_REGRESSION_CODE,
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
currentProjection = replacementProjection;
|
|
421
|
+
currentAdmission = replacement;
|
|
422
|
+
return currentAdmission;
|
|
423
|
+
},
|
|
424
|
+
findReplayDeny(call) {
|
|
425
|
+
return findReplayDeny(currentProjection.replayDenied, call);
|
|
426
|
+
},
|
|
427
|
+
latchUnsafe(reasonCode = null) {
|
|
428
|
+
return latch(McpRecoveryBlockMode.UNSAFE, reasonCode);
|
|
429
|
+
},
|
|
430
|
+
latchAll(reasonCode = null) {
|
|
431
|
+
return latch(McpRecoveryBlockMode.ALL, reasonCode);
|
|
432
|
+
},
|
|
433
|
+
};
|
|
434
|
+
Object.freeze(controller);
|
|
435
|
+
RECOVERY_CONTROLLERS.add(controller);
|
|
436
|
+
return controller;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function isRecoveryController(value) {
|
|
440
|
+
return (
|
|
441
|
+
(typeof value === "object" || typeof value === "function") &&
|
|
442
|
+
value !== null &&
|
|
443
|
+
RECOVERY_CONTROLLERS.has(value)
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export class McpLedgerRecoveryBlockedError extends Error {
|
|
448
|
+
constructor(effect, admission, options = {}) {
|
|
449
|
+
super(
|
|
450
|
+
options.message ||
|
|
451
|
+
`MCP ${effect} call blocked until durable recovery incidents are explicitly adjudicated`,
|
|
452
|
+
);
|
|
453
|
+
this.name = "McpLedgerRecoveryBlockedError";
|
|
454
|
+
this.code =
|
|
455
|
+
options.code || admission.reasonCode || "CC_MCP_LEDGER_RECOVERY_BLOCKED";
|
|
456
|
+
this.effect = effect;
|
|
457
|
+
this.blockMode = admission.blockMode;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export class McpExactReplayDeniedError extends Error {
|
|
462
|
+
constructor(effect, deny) {
|
|
463
|
+
super(
|
|
464
|
+
"MCP call exactly matches a prior confirmed-applied call and must not be replayed",
|
|
465
|
+
);
|
|
466
|
+
this.name = "McpExactReplayDeniedError";
|
|
467
|
+
this.code = MCP_EXACT_REPLAY_DENIED_CODE;
|
|
468
|
+
this.effect = effect;
|
|
469
|
+
this.ledgerId = deny?.ledgerId || null;
|
|
470
|
+
this.serverName = deny?.serverName || null;
|
|
471
|
+
this.toolName = deny?.toolName || null;
|
|
472
|
+
this.inputBytes = deny?.inputBytes ?? null;
|
|
473
|
+
this.replayDigest = deny?.replayDigest || null;
|
|
474
|
+
this.replayDenied = true;
|
|
475
|
+
this.retryable = false;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export class McpCallOutcomeUnknownError extends Error {
|
|
480
|
+
constructor(
|
|
481
|
+
ticket,
|
|
482
|
+
settlementError,
|
|
483
|
+
effect = McpEffect.UNKNOWN,
|
|
484
|
+
options = {},
|
|
485
|
+
) {
|
|
486
|
+
super(
|
|
487
|
+
options.message ||
|
|
488
|
+
"MCP call outcome is unknown; do not retry automatically until durable recovery is adjudicated",
|
|
489
|
+
settlementError ? { cause: settlementError } : undefined,
|
|
490
|
+
);
|
|
491
|
+
this.name = "McpCallOutcomeUnknownError";
|
|
492
|
+
this.code = MCP_OUTCOME_UNKNOWN_CODE;
|
|
493
|
+
this.ledgerId =
|
|
494
|
+
safeProperty(ticket, "ledgerId") ||
|
|
495
|
+
safeProperty(settlementError, "ledgerId") ||
|
|
496
|
+
null;
|
|
497
|
+
this.effect = effect;
|
|
498
|
+
this.phase = options.phase || "settled";
|
|
499
|
+
this.outcomeUnknown = true;
|
|
500
|
+
this.retryable = false;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function safeProperty(value, property) {
|
|
505
|
+
try {
|
|
506
|
+
return value?.[property];
|
|
507
|
+
} catch {
|
|
508
|
+
return undefined;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function failureCode(error, fallback) {
|
|
513
|
+
const code = safeProperty(error, "code");
|
|
514
|
+
return typeof code === "string" && code ? code : fallback;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function outcomeUnknown(
|
|
518
|
+
controller,
|
|
519
|
+
ticket,
|
|
520
|
+
cause,
|
|
521
|
+
effect,
|
|
522
|
+
{ phase = "settled", fallbackCode = "CC_MCP_LEDGER_SETTLE_FAILED" } = {},
|
|
523
|
+
) {
|
|
524
|
+
controller?.latchUnsafe(failureCode(cause, fallbackCode));
|
|
525
|
+
return new McpCallOutcomeUnknownError(ticket, cause, effect, { phase });
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
async function settleWithRecoveryLatch(controller, operation) {
|
|
529
|
+
try {
|
|
530
|
+
return await operation();
|
|
531
|
+
} catch (error) {
|
|
532
|
+
controller?.latchUnsafe(failureCode(error, "CC_MCP_LEDGER_SETTLE_FAILED"));
|
|
533
|
+
throw error;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function wrapLedgerTicketForRecovery(ticket, controller) {
|
|
538
|
+
if (
|
|
539
|
+
!controller ||
|
|
540
|
+
(typeof ticket !== "object" && typeof ticket !== "function") ||
|
|
541
|
+
ticket === null
|
|
542
|
+
) {
|
|
543
|
+
return ticket;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
try {
|
|
547
|
+
if (isProxy(ticket)) {
|
|
548
|
+
const invalid = new TypeError("MCP ledger tickets cannot be Proxies");
|
|
549
|
+
invalid.code = "CC_MCP_LEDGER_TICKET_INVALID";
|
|
550
|
+
throw invalid;
|
|
551
|
+
}
|
|
552
|
+
const descriptors = Object.getOwnPropertyDescriptors(ticket);
|
|
553
|
+
for (const property of ["settle", "settleCall"]) {
|
|
554
|
+
const originalDescriptor = descriptors[property];
|
|
555
|
+
const method = Reflect.get(ticket, property, ticket);
|
|
556
|
+
if (typeof method === "function") {
|
|
557
|
+
descriptors[property] = {
|
|
558
|
+
configurable: false,
|
|
559
|
+
enumerable: originalDescriptor?.enumerable ?? true,
|
|
560
|
+
writable: false,
|
|
561
|
+
value: (...args) =>
|
|
562
|
+
settleWithRecoveryLatch(controller, () =>
|
|
563
|
+
Reflect.apply(method, ticket, args),
|
|
564
|
+
),
|
|
565
|
+
};
|
|
566
|
+
} else if (property in ticket) {
|
|
567
|
+
const unavailable = new TypeError(
|
|
568
|
+
`MCP ledger ticket ${property} is not callable`,
|
|
569
|
+
);
|
|
570
|
+
unavailable.code = "CC_MCP_LEDGER_SETTLE_UNAVAILABLE";
|
|
571
|
+
throw unavailable;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// Ledger tickets are commonly frozen. Clone their descriptors so
|
|
575
|
+
// settlement can be wrapped without violating read-only invariants.
|
|
576
|
+
return Object.freeze(
|
|
577
|
+
Object.create(Object.getPrototypeOf(ticket), descriptors),
|
|
578
|
+
);
|
|
579
|
+
} catch (error) {
|
|
580
|
+
controller.latchUnsafe(failureCode(error, "CC_MCP_LEDGER_TICKET_INVALID"));
|
|
581
|
+
throw error;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function constrainRecoveryController(controller, options = {}) {
|
|
586
|
+
const constraint = classifyMcpRecoveryAdmission(null, options);
|
|
587
|
+
if (constraint.blockMode === McpRecoveryBlockMode.ALL) {
|
|
588
|
+
controller.latchAll(
|
|
589
|
+
constraint.reasonCode ||
|
|
590
|
+
(options.recoveryError != null
|
|
591
|
+
? failureCode(
|
|
592
|
+
options.recoveryError,
|
|
593
|
+
"CC_MCP_LEDGER_EVENT_READ_FAILED",
|
|
594
|
+
)
|
|
595
|
+
: null),
|
|
596
|
+
);
|
|
597
|
+
} else if (constraint.blockMode === McpRecoveryBlockMode.UNSAFE) {
|
|
598
|
+
controller.latchUnsafe(constraint.reasonCode);
|
|
599
|
+
}
|
|
600
|
+
return controller;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/** Wrap an MCP ledger with a fail-closed recovery admission gate. */
|
|
604
|
+
export function guardMcpLedgerForRecovery(ledger, recovery, options = {}) {
|
|
605
|
+
if (!ledger) return ledger;
|
|
606
|
+
const ledgerBegin = ledger.begin;
|
|
607
|
+
if (typeof ledgerBegin !== "function") return ledger;
|
|
608
|
+
const controller = isRecoveryController(recovery) ? recovery : null;
|
|
609
|
+
if (controller) constrainRecoveryController(controller, options);
|
|
610
|
+
if (controller && GUARDED_LEDGER_CONTROLLERS.get(ledger) === controller) {
|
|
611
|
+
return ledger;
|
|
612
|
+
}
|
|
613
|
+
const staticProjection = controller
|
|
614
|
+
? null
|
|
615
|
+
: inspectRecoveryProjection(recovery);
|
|
616
|
+
const staticAdmission = controller
|
|
617
|
+
? null
|
|
618
|
+
: classifyMcpRecoveryAdmission(recovery, options);
|
|
619
|
+
if (
|
|
620
|
+
!controller &&
|
|
621
|
+
!staticAdmission.blockMode &&
|
|
622
|
+
staticProjection.replayDenied.length === 0
|
|
623
|
+
) {
|
|
624
|
+
return ledger;
|
|
625
|
+
}
|
|
626
|
+
const getAdmission = () =>
|
|
627
|
+
controller ? controller.getAdmission() : staticAdmission;
|
|
628
|
+
const getReplayDeny = (call) =>
|
|
629
|
+
controller
|
|
630
|
+
? controller.findReplayDeny(call)
|
|
631
|
+
: findReplayDeny(staticProjection.replayDenied, call);
|
|
632
|
+
|
|
633
|
+
const begin = async (call = {}) => {
|
|
634
|
+
const callSnapshot = snapshotMcpAdmissionCall(call);
|
|
635
|
+
const admission = getAdmission();
|
|
636
|
+
const normalizedEffect = normalizeMcpEffectContract(
|
|
637
|
+
snapshotMcpJsonRpcInput(
|
|
638
|
+
callSnapshot.effectContract || callSnapshot.effect || {},
|
|
639
|
+
),
|
|
640
|
+
);
|
|
641
|
+
const ledgerCall = Object.freeze({
|
|
642
|
+
...callSnapshot,
|
|
643
|
+
effectContract: normalizedEffect,
|
|
644
|
+
});
|
|
645
|
+
const effect =
|
|
646
|
+
normalizedEffect.effect === McpEffect.READ &&
|
|
647
|
+
normalizedEffect.trusted !== true
|
|
648
|
+
? McpEffect.UNKNOWN
|
|
649
|
+
: normalizedEffect.effect;
|
|
650
|
+
const deny = getReplayDeny(ledgerCall);
|
|
651
|
+
if (deny) {
|
|
652
|
+
throw new McpExactReplayDeniedError(effect, deny);
|
|
653
|
+
}
|
|
654
|
+
const blocked =
|
|
655
|
+
admission.blockMode === McpRecoveryBlockMode.ALL ||
|
|
656
|
+
(admission.blockMode === McpRecoveryBlockMode.UNSAFE &&
|
|
657
|
+
effect !== McpEffect.READ);
|
|
658
|
+
if (blocked) {
|
|
659
|
+
throw new McpLedgerRecoveryBlockedError(effect, admission, options);
|
|
660
|
+
}
|
|
661
|
+
const ticket = await Reflect.apply(ledgerBegin, ledger, [ledgerCall]);
|
|
662
|
+
return wrapLedgerTicketForRecovery(ticket, controller);
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
const guarded = {
|
|
666
|
+
begin,
|
|
667
|
+
beginCall: begin,
|
|
668
|
+
};
|
|
669
|
+
Object.defineProperty(guarded, "recoveryAdmission", {
|
|
670
|
+
enumerable: true,
|
|
671
|
+
get: getAdmission,
|
|
672
|
+
});
|
|
673
|
+
for (const method of ["settle", "settleCall"]) {
|
|
674
|
+
const ledgerMethod = ledger[method];
|
|
675
|
+
if (typeof ledgerMethod === "function") {
|
|
676
|
+
guarded[method] = (...args) =>
|
|
677
|
+
controller
|
|
678
|
+
? settleWithRecoveryLatch(controller, () =>
|
|
679
|
+
Reflect.apply(ledgerMethod, ledger, args),
|
|
680
|
+
)
|
|
681
|
+
: Reflect.apply(ledgerMethod, ledger, args);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
for (const method of ["get", "list"]) {
|
|
685
|
+
const ledgerMethod = ledger[method];
|
|
686
|
+
if (typeof ledgerMethod === "function") {
|
|
687
|
+
guarded[method] = ledgerMethod.bind(ledger);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (ledger.prewriteFailurePolicy) {
|
|
691
|
+
guarded.prewriteFailurePolicy = ledger.prewriteFailurePolicy;
|
|
692
|
+
}
|
|
693
|
+
Object.freeze(guarded);
|
|
694
|
+
if (controller) GUARDED_LEDGER_CONTROLLERS.set(guarded, controller);
|
|
695
|
+
return guarded;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Monotonically tighten a branded dynamic ledger after an external call's
|
|
700
|
+
* outcome becomes unknowable. The WeakMap is the capability boundary: callers
|
|
701
|
+
* can latch a guarded ledger but cannot read, replace, or downgrade its
|
|
702
|
+
* controller.
|
|
703
|
+
*/
|
|
704
|
+
export function markMcpLedgerOutcomeUnknown(
|
|
705
|
+
ledger,
|
|
706
|
+
reasonCode = MCP_OUTCOME_UNKNOWN_CODE,
|
|
707
|
+
) {
|
|
708
|
+
const controller =
|
|
709
|
+
ledger && (typeof ledger === "object" || typeof ledger === "function")
|
|
710
|
+
? GUARDED_LEDGER_CONTROLLERS.get(ledger)
|
|
711
|
+
: null;
|
|
712
|
+
if (!controller) return false;
|
|
713
|
+
controller.latchUnsafe(
|
|
714
|
+
typeof reasonCode === "string" && reasonCode
|
|
715
|
+
? reasonCode
|
|
716
|
+
: MCP_OUTCOME_UNKNOWN_CODE,
|
|
717
|
+
);
|
|
718
|
+
return true;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/** Build the normal call ledger, then apply the shared recovery admission. */
|
|
722
|
+
export function createRecoveryGuardedMcpCallLedger(options = {}) {
|
|
723
|
+
const {
|
|
724
|
+
sink = null,
|
|
725
|
+
recovery = null,
|
|
726
|
+
recoveryError = null,
|
|
727
|
+
blockMode = null,
|
|
728
|
+
controller = null,
|
|
729
|
+
code,
|
|
730
|
+
message,
|
|
731
|
+
...ledgerOptions
|
|
732
|
+
} = options;
|
|
733
|
+
return guardMcpLedgerForRecovery(
|
|
734
|
+
createMcpCallLedger({ ...ledgerOptions, sink }),
|
|
735
|
+
controller || recovery,
|
|
736
|
+
{ recoveryError, blockMode, code, message },
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
async function resolveHostEffectContract(
|
|
741
|
+
resolveEffect,
|
|
742
|
+
client,
|
|
743
|
+
serverName,
|
|
744
|
+
toolName,
|
|
745
|
+
input,
|
|
746
|
+
) {
|
|
747
|
+
let resolved = null;
|
|
748
|
+
if (resolveEffect) {
|
|
749
|
+
try {
|
|
750
|
+
resolved = await Reflect.apply(resolveEffect, client, [
|
|
751
|
+
serverName,
|
|
752
|
+
toolName,
|
|
753
|
+
input,
|
|
754
|
+
]);
|
|
755
|
+
} catch {
|
|
756
|
+
// A failed host classification is not evidence that a tool is read-only.
|
|
757
|
+
resolved = null;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
const contract =
|
|
761
|
+
resolved && typeof resolved === "object" && resolved.effectContract
|
|
762
|
+
? resolved.effectContract
|
|
763
|
+
: resolved || {};
|
|
764
|
+
const normalizedContract = normalizeMcpEffectContract(contract);
|
|
765
|
+
const effectContract =
|
|
766
|
+
normalizedContract.effect === McpEffect.READ &&
|
|
767
|
+
normalizedContract.trusted !== true
|
|
768
|
+
? normalizeMcpEffectContract({
|
|
769
|
+
...normalizedContract,
|
|
770
|
+
effect: McpEffect.UNKNOWN,
|
|
771
|
+
})
|
|
772
|
+
: normalizedContract;
|
|
773
|
+
return {
|
|
774
|
+
effectContract,
|
|
775
|
+
resourceScopes:
|
|
776
|
+
resolved && typeof resolved === "object"
|
|
777
|
+
? resolved.resourceScopes
|
|
778
|
+
: undefined,
|
|
779
|
+
networkScopes:
|
|
780
|
+
resolved && typeof resolved === "object"
|
|
781
|
+
? resolved.networkScopes
|
|
782
|
+
: undefined,
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
async function settleHostCall(ledger, ticket, outcome) {
|
|
787
|
+
if (typeof ticket?.settle === "function") {
|
|
788
|
+
return Reflect.apply(ticket.settle, ticket, [outcome]);
|
|
789
|
+
}
|
|
790
|
+
if (typeof ticket?.settleCall === "function") {
|
|
791
|
+
return Reflect.apply(ticket.settleCall, ticket, [outcome]);
|
|
792
|
+
}
|
|
793
|
+
if (typeof ledger.settleCall === "function") {
|
|
794
|
+
return Reflect.apply(ledger.settleCall, ledger, [ticket, outcome]);
|
|
795
|
+
}
|
|
796
|
+
if (typeof ledger.settle === "function") {
|
|
797
|
+
return Reflect.apply(ledger.settle, ledger, [ticket, outcome]);
|
|
798
|
+
}
|
|
799
|
+
const error = new Error("MCP ledger ticket has no settlement method");
|
|
800
|
+
error.code = "CC_MCP_LEDGER_SETTLE_UNAVAILABLE";
|
|
801
|
+
throw error;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Wrap host-owned auxiliary MCP calls with the same dynamic recovery admission
|
|
806
|
+
* and durable begin/settle protocol used by agent-core. Agent-core dispatch
|
|
807
|
+
* must continue to receive the raw client plus the shared guarded ledger so a
|
|
808
|
+
* model-originated call is recorded exactly once.
|
|
809
|
+
*/
|
|
810
|
+
export function createRecoveryGuardedMcpClient({
|
|
811
|
+
client,
|
|
812
|
+
ledger,
|
|
813
|
+
controller,
|
|
814
|
+
resolveEffect = null,
|
|
815
|
+
sessionId = null,
|
|
816
|
+
} = {}) {
|
|
817
|
+
if (!client || typeof client.callTool !== "function") {
|
|
818
|
+
throw new TypeError("MCP client wrapper requires client.callTool()");
|
|
819
|
+
}
|
|
820
|
+
if (!ledger || typeof ledger.begin !== "function") {
|
|
821
|
+
throw new TypeError("MCP client wrapper requires a call ledger");
|
|
822
|
+
}
|
|
823
|
+
if (!isRecoveryController(controller)) {
|
|
824
|
+
throw new TypeError("MCP client wrapper requires a recovery controller");
|
|
825
|
+
}
|
|
826
|
+
if (resolveEffect != null && typeof resolveEffect !== "function") {
|
|
827
|
+
throw new TypeError("MCP client effect resolver must be a function");
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const existingGuard = GUARDED_MCP_CLIENTS.get(client);
|
|
831
|
+
if (existingGuard) {
|
|
832
|
+
if (
|
|
833
|
+
existingGuard.ledger === ledger &&
|
|
834
|
+
existingGuard.controller === controller &&
|
|
835
|
+
existingGuard.resolveEffect === resolveEffect &&
|
|
836
|
+
existingGuard.sessionId === sessionId
|
|
837
|
+
) {
|
|
838
|
+
return client;
|
|
839
|
+
}
|
|
840
|
+
const error = new TypeError(
|
|
841
|
+
"MCP client is already guarded by a different recovery authority",
|
|
842
|
+
);
|
|
843
|
+
error.code = "CC_MCP_CLIENT_ALREADY_GUARDED";
|
|
844
|
+
throw error;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const admittedLedger = guardMcpLedgerForRecovery(ledger, controller);
|
|
848
|
+
const rawCallTool = client.callTool;
|
|
849
|
+
const callTool = async (serverName, toolName, input = {}, ...rest) => {
|
|
850
|
+
// Snapshot before the first await. Admission, durable identity and the raw
|
|
851
|
+
// transport all receive this exact deeply-frozen JSON value.
|
|
852
|
+
const inputSnapshot = snapshotMcpJsonRpcInput(input);
|
|
853
|
+
const effect = await resolveHostEffectContract(
|
|
854
|
+
resolveEffect,
|
|
855
|
+
client,
|
|
856
|
+
serverName,
|
|
857
|
+
toolName,
|
|
858
|
+
inputSnapshot,
|
|
859
|
+
);
|
|
860
|
+
const ticket = await admittedLedger.begin({
|
|
861
|
+
sessionId,
|
|
862
|
+
serverName,
|
|
863
|
+
toolName,
|
|
864
|
+
input: inputSnapshot,
|
|
865
|
+
...effect,
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
let result;
|
|
869
|
+
try {
|
|
870
|
+
result = await Reflect.apply(rawCallTool, client, [
|
|
871
|
+
serverName,
|
|
872
|
+
toolName,
|
|
873
|
+
inputSnapshot,
|
|
874
|
+
...rest,
|
|
875
|
+
]);
|
|
876
|
+
} catch (callError) {
|
|
877
|
+
if (effect.effectContract.effect !== McpEffect.READ) {
|
|
878
|
+
throw outcomeUnknown(
|
|
879
|
+
controller,
|
|
880
|
+
ticket,
|
|
881
|
+
callError,
|
|
882
|
+
effect.effectContract.effect,
|
|
883
|
+
{
|
|
884
|
+
phase: "call",
|
|
885
|
+
fallbackCode: "CC_MCP_TRANSPORT_OUTCOME_UNKNOWN",
|
|
886
|
+
},
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
try {
|
|
890
|
+
await settleHostCall(admittedLedger, ticket, {
|
|
891
|
+
status: "failed",
|
|
892
|
+
error: callError,
|
|
893
|
+
});
|
|
894
|
+
} catch (settlementError) {
|
|
895
|
+
throw outcomeUnknown(
|
|
896
|
+
controller,
|
|
897
|
+
ticket,
|
|
898
|
+
settlementError,
|
|
899
|
+
effect.effectContract.effect,
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
throw callError;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
let protocolError;
|
|
906
|
+
try {
|
|
907
|
+
if (
|
|
908
|
+
result !== null &&
|
|
909
|
+
(typeof result === "object" || typeof result === "function") &&
|
|
910
|
+
isProxy(result)
|
|
911
|
+
) {
|
|
912
|
+
const invalid = new TypeError("MCP result cannot be a Proxy");
|
|
913
|
+
invalid.code = "CC_MCP_PROTOCOL_RESULT_INVALID";
|
|
914
|
+
throw invalid;
|
|
915
|
+
}
|
|
916
|
+
protocolError = result?.isError === true;
|
|
917
|
+
} catch (inspectionError) {
|
|
918
|
+
throw outcomeUnknown(
|
|
919
|
+
controller,
|
|
920
|
+
ticket,
|
|
921
|
+
inspectionError,
|
|
922
|
+
effect.effectContract.effect,
|
|
923
|
+
{
|
|
924
|
+
phase: "result",
|
|
925
|
+
fallbackCode: "CC_MCP_PROTOCOL_RESULT_INVALID",
|
|
926
|
+
},
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
try {
|
|
930
|
+
const error = protocolError
|
|
931
|
+
? Object.assign(new Error("MCP server returned isError=true"), {
|
|
932
|
+
code: "CC_MCP_PROTOCOL_TOOL_ERROR",
|
|
933
|
+
})
|
|
934
|
+
: null;
|
|
935
|
+
await settleHostCall(
|
|
936
|
+
admittedLedger,
|
|
937
|
+
ticket,
|
|
938
|
+
protocolError
|
|
939
|
+
? { status: "failed", output: result, error }
|
|
940
|
+
: { status: "completed", output: result },
|
|
941
|
+
);
|
|
942
|
+
} catch (settlementError) {
|
|
943
|
+
throw outcomeUnknown(
|
|
944
|
+
controller,
|
|
945
|
+
ticket,
|
|
946
|
+
settlementError,
|
|
947
|
+
effect.effectContract.effect,
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
return result;
|
|
951
|
+
};
|
|
952
|
+
|
|
953
|
+
const methodCache = new Map();
|
|
954
|
+
let wrappedClient;
|
|
955
|
+
// Never proxy the raw client directly: a frozen, non-configurable callTool
|
|
956
|
+
// property would require the get trap to return the raw function byte-for-
|
|
957
|
+
// byte, defeating the guard (and otherwise throws a Proxy invariant error).
|
|
958
|
+
// An extensible facade lets reads/writes delegate to the raw instance while
|
|
959
|
+
// methods still execute with the raw `this` binding.
|
|
960
|
+
const facade = Object.create(Object.getPrototypeOf(client));
|
|
961
|
+
wrappedClient = new Proxy(facade, {
|
|
962
|
+
get(_facade, property) {
|
|
963
|
+
if (property === "callTool") return callTool;
|
|
964
|
+
const value = Reflect.get(client, property, client);
|
|
965
|
+
if (value === client) return wrappedClient;
|
|
966
|
+
if (typeof value !== "function") return value;
|
|
967
|
+
const cached = methodCache.get(property);
|
|
968
|
+
if (cached?.source === value) return cached.wrapper;
|
|
969
|
+
const wrapper = (...args) => {
|
|
970
|
+
const result = Reflect.apply(value, client, args);
|
|
971
|
+
if (result === client) return wrappedClient;
|
|
972
|
+
if (
|
|
973
|
+
result !== null &&
|
|
974
|
+
(typeof result === "object" || typeof result === "function")
|
|
975
|
+
) {
|
|
976
|
+
let then;
|
|
977
|
+
try {
|
|
978
|
+
then = Reflect.get(result, "then", result);
|
|
979
|
+
} catch (error) {
|
|
980
|
+
return Promise.reject(error);
|
|
981
|
+
}
|
|
982
|
+
if (typeof then === "function") {
|
|
983
|
+
return Promise.resolve(result).then((resolved) =>
|
|
984
|
+
resolved === client ? wrappedClient : resolved,
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return result;
|
|
989
|
+
};
|
|
990
|
+
methodCache.set(property, { source: value, wrapper });
|
|
991
|
+
return wrapper;
|
|
992
|
+
},
|
|
993
|
+
set(_facade, property, value) {
|
|
994
|
+
if (property === "callTool") return false;
|
|
995
|
+
return Reflect.set(
|
|
996
|
+
client,
|
|
997
|
+
property,
|
|
998
|
+
value === wrappedClient ? client : value,
|
|
999
|
+
client,
|
|
1000
|
+
);
|
|
1001
|
+
},
|
|
1002
|
+
has(_facade, property) {
|
|
1003
|
+
return Reflect.has(client, property);
|
|
1004
|
+
},
|
|
1005
|
+
ownKeys() {
|
|
1006
|
+
return Reflect.ownKeys(client);
|
|
1007
|
+
},
|
|
1008
|
+
getOwnPropertyDescriptor(_facade, property) {
|
|
1009
|
+
const descriptor = Reflect.getOwnPropertyDescriptor(client, property);
|
|
1010
|
+
if (!descriptor) return undefined;
|
|
1011
|
+
if (property === "callTool") {
|
|
1012
|
+
return {
|
|
1013
|
+
configurable: true,
|
|
1014
|
+
enumerable: descriptor.enumerable,
|
|
1015
|
+
writable: false,
|
|
1016
|
+
value: callTool,
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
if ("value" in descriptor) {
|
|
1020
|
+
return {
|
|
1021
|
+
configurable: true,
|
|
1022
|
+
enumerable: descriptor.enumerable,
|
|
1023
|
+
writable: descriptor.writable,
|
|
1024
|
+
value: Reflect.get(wrappedClient, property, wrappedClient),
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
return {
|
|
1028
|
+
configurable: true,
|
|
1029
|
+
enumerable: descriptor.enumerable,
|
|
1030
|
+
get: descriptor.get
|
|
1031
|
+
? () => Reflect.get(wrappedClient, property, wrappedClient)
|
|
1032
|
+
: undefined,
|
|
1033
|
+
set: descriptor.set
|
|
1034
|
+
? (value) =>
|
|
1035
|
+
Reflect.set(wrappedClient, property, value, wrappedClient)
|
|
1036
|
+
: undefined,
|
|
1037
|
+
};
|
|
1038
|
+
},
|
|
1039
|
+
defineProperty(_facade, property, descriptor) {
|
|
1040
|
+
// A non-configurable property on the raw object cannot be mirrored onto
|
|
1041
|
+
// the facade without reintroducing the get-trap invariant above.
|
|
1042
|
+
if (property === "callTool" || descriptor.configurable === false) {
|
|
1043
|
+
return false;
|
|
1044
|
+
}
|
|
1045
|
+
return Reflect.defineProperty(client, property, descriptor);
|
|
1046
|
+
},
|
|
1047
|
+
deleteProperty(_facade, property) {
|
|
1048
|
+
if (property === "callTool") return false;
|
|
1049
|
+
return Reflect.deleteProperty(client, property);
|
|
1050
|
+
},
|
|
1051
|
+
getPrototypeOf() {
|
|
1052
|
+
return Reflect.getPrototypeOf(client);
|
|
1053
|
+
},
|
|
1054
|
+
preventExtensions() {
|
|
1055
|
+
return false;
|
|
1056
|
+
},
|
|
1057
|
+
});
|
|
1058
|
+
GUARDED_MCP_CLIENTS.set(wrappedClient, {
|
|
1059
|
+
ledger,
|
|
1060
|
+
controller,
|
|
1061
|
+
resolveEffect,
|
|
1062
|
+
sessionId,
|
|
1063
|
+
});
|
|
1064
|
+
return wrappedClient;
|
|
1065
|
+
}
|