chainlesschain 0.165.5 → 0.165.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/assets/web-panel/assets/{AIOps-CEeYTeDB.js → AIOps-B3gWmXjo.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BeRUQ3V2.js → ActionButton-G9pdT-0e.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DrV718XF.js → Analytics-Ca30J7m2.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-Cd2Frt7Q.js → AppLayout-BtVTm8PU.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CUJMlORU.js → Artifacts-BhViNa6u.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CXJcfT6p.js → Audit-DhKq-FnY.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DOv6Z7H9.js → BackgroundAgents-BL0SVbnE.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BwdDyBI0.js → Backup-BWkblnFi.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BmLn4kYH.js → BaseInput-ClaRNrF8.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-COg8pAW2.js → Chat-sZE2Z77o.js} +4 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BAfKOnRe.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-BCP4uoU8.js → Checkbox-Cvhay5WM.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-pRt6Y9JI.js → Codegen-BpejL-4N.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DLaprKhS.js → Col-g2Ki6g_1.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DyATsz5f.js → Community-D2xKbNo9.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BXAaupZK.js → Compact-DbL1Pds3.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DpcJBELr.js → Compliance-DGtZCV4c.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DG4EFWyd.js → Cowork-4kQpoNsI.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-F6Oy5n2P.js → Cron-BxTYXXAN.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BOdgtalX.js → Crosschain-k0J0LT2N.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DT1dSkjH.js → DID-BaqhoPgN.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-NBUIqWzC.js → Dashboard-T4RVPIM1.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CSpHeMYB.js → Dropdown-DszOToWq.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CEG0xISr.js → EmailListRenderer-C9KJU7HF.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BTZ0BZna.js → FamilyGuardDashboard-Cmr0JnsA.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BuuCTn2V.js → Federation-BI-_2xZX.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-KAVceevk.js → FormItemContext-DsnDEPsl.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DnHpcQvW.js → GenericCardRenderer-BLncAKKK.js} +1 -1
- package/src/assets/web-panel/assets/{Git-C_B_0HwB.js → Git-BPQhGXDn.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DPSZuzuu.js → Governance-CyElbUQW.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BV-06V7q.js → Inference-CcSntBQD.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-wpDuupn6.js → KnowledgeGraph-p6e7HZ2r.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Bseca1_I.js → Logs-CNEtr-wz.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-Bj9xI0YH.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-d3kM4DDL.js → Marketplace-Dd2notAJ.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DQ_xZ23i.js → McpTools-BBsN2CSW.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-CRIeYO4L.js → Memory-Dg2uTvwx.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BsllIa9l.js → MobileBridge-r1YMRiJV.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BUuQy30X.js → MobileProjects-C-89kSm4.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BOFDIzrC.js → Mtc-CrPZcVgf.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BQg-vyvG.js → MtcAudit-sxi7hqJb.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-Bo1jrPdN.js → Multisig-CAgOMfF1.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Bmod8alw.js → NLProgramming-DafaPts3.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CGGFL8w-.js → Notes-CJM-xUW_.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BsZvCJxC.js → NotificationSettings-L0ZjfAh_.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-C1-Quo_h.js → OrderTableRenderer-BB1CvCK0.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BjOeDYOi.js → Organization-DeLtgAD8.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DwVV3l5A.js → Overflow-CKC2b10z.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Cu3DQT9W.js → P2P-eyf2rzY1.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Cg0gR7dH.js → PdhVaultBrowser-5upNIOW8.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CsAXxRN5.js → Permissions-B8p3W6JK.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-C-nNmnYp.js → PersonalDataHub-BnRMxTq3.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-BOnW7nNc.js → Pipeline-DjCNI1li.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-ClLSyGKt.js → Privacy-u6gXLbWT.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DuLvjjq7.js → ProjectInit-BZAw4tkM.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DQYYbDD9.js → ProjectSettings-B1Tr72q7.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Drw8MBGK.js → Projects-DO7HFuBQ.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-ckqHQe6K.js → Providers-BjCztyhj.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DW5BXk-G.js → QrScannerModal-ZWyCUbR1.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DDLnhNTp.js → QuickAsk-DhDjeW4G.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-PJQ2C91U.js → Recommend-R_CN2MuU.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-s6wmda34.js → RemoteSession-uxFcujeu.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-DC2sb-I3.js → Reputation-DX8D7rks.js} +1 -1
- package/src/assets/web-panel/assets/{Row-hPGk1eGj.js → Row-BYahQILD.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-RIvfZql0.js → RssFeed-CUSQ2tm-.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DFg1G1eR.js → Search-CnN_S8Cs.js} +1 -1
- package/src/assets/web-panel/assets/{Security-D6H5p8Dp.js → Security-DcZlh-O4.js} +4 -4
- package/src/assets/web-panel/assets/{Services-nxYiFV_w.js → Services-ZhBOjzIX.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BmgFNAdB.js → Skeleton-D5Qnq_lI.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CsaaD2av.js → Skills-BC9cZ3Be.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-yM5y9yvK.js → Sla-DauJ3E8_.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-DbNeZ_fh.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-CFVhEyyr.js → SyncSettings-DUvJbSct.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DdO02Inn.js → Tasks-BXa-5noQ.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Eo2d8Llu.js → Templates-JxaiUt_3.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-ChESOXgy.js → Tenant-BW4ERQN8.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BJ7oDxTI.js → Terminal-DltUbdtv.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-hWX0fUvz.js → TimelineRenderer-CMoZz0Tc.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DVBZQnd-.js → Tokens-D_VEV60K.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-D89cQfVq.js → Trigger-CCXTCs0R.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BIVSb4-V.js → Trust-lkGRIPcG.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B58gWopl.js → UkeySign-BfGeQ9HX.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DhHI7bmG.js → VideoEditing-BEC7pYvc.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-C4FheWS_.js → Wallet-BZdG24o0.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DnPRq-Bx.js → WebAuthn-yoEddedp.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-BerRPjpJ.js → WorkflowEditor-BXZEprOW.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BzZ52iKr.js → chat-gg0bzZal.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BI_E4BAn.js → colors-DjRVRuin.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-d50mr3yk.js → compact-item-su-dieAc.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-C6FsQ1IH.js → createContext-Ci3S8oKF.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CxbFX9uH.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DLZEMgLP.js → hasIn-D8F10heH.js} +1 -1
- package/src/assets/web-panel/assets/{index-D-aBumtY.js → index-11wtYQiF.js} +1 -1
- package/src/assets/web-panel/assets/{index-CjuKZBqJ.js → index-1IgxgALJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-9H_M2jju.js → index-1agckWSq.js} +1 -1
- package/src/assets/web-panel/assets/{index-DE0V6cp-.js → index-5JqhujnR.js} +1 -1
- package/src/assets/web-panel/assets/{index-Y2-q7QLm.js → index-6Tniqk1s.js} +1 -1
- package/src/assets/web-panel/assets/{index-DoFtCVZ7.js → index-8Z-8BIlN.js} +1 -1
- package/src/assets/web-panel/assets/{index-KNGgO4Wm.js → index-BCI5v0yB.js} +1 -1
- package/src/assets/web-panel/assets/{index-DY4j5zkF.js → index-BDJntYmJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZjjdQ62.js → index-BGzixOGZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTpIOTfR.js → index-BMMOVoLD.js} +1 -1
- package/src/assets/web-panel/assets/{index-BB9SqVSt.js → index-BTFpdBtt.js} +1 -1
- package/src/assets/web-panel/assets/{index-AHr7ryWf.js → index-Ba_HyPnE.js} +1 -1
- package/src/assets/web-panel/assets/{index-Pvf30m8Q.js → index-BbF1IHof.js} +1 -1
- package/src/assets/web-panel/assets/{index-CRN5TRTQ.js → index-BgT9su_e.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUaEHfQ0.js → index-BzPB6kGZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CdKt9fOd.js → index-C6ygzplr.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dec0Kf_Z.js → index-CC3ZDjzY.js} +1 -1
- package/src/assets/web-panel/assets/{index-IPkwSAW_.js → index-CEmXRhAE.js} +1 -1
- package/src/assets/web-panel/assets/index-CJGOlduY.js +1 -0
- package/src/assets/web-panel/assets/{index-C3k8kevG.js → index-CMPqd5Cv.js} +1 -1
- package/src/assets/web-panel/assets/{index-A5CDWP7y.js → index-COWd8uGx.js} +1 -1
- package/src/assets/web-panel/assets/{index-CO8DAF7U.js → index-CPIYSp4H.js} +1 -1
- package/src/assets/web-panel/assets/{index-CVzuawCX.js → index-CR0h6seT.js} +1 -1
- package/src/assets/web-panel/assets/{index-Co7s9LsB.js → index-CUmDhC4j.js} +1 -1
- package/src/assets/web-panel/assets/index-CkBCN5yH.js +1 -0
- package/src/assets/web-panel/assets/{index-CLoTQr0W.js → index-CusgZPtc.js} +1 -1
- package/src/assets/web-panel/assets/{index-B81mo1Et.js → index-DBAETXft.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcSUl9fA.js → index-DIJ2qFOC.js} +1 -1
- package/src/assets/web-panel/assets/{index-Q5Tn735s.js → index-Dbq4NMET.js} +1 -1
- package/src/assets/web-panel/assets/{index-GXE0D8Nx.js → index-DkMEF5VT.js} +1 -1
- package/src/assets/web-panel/assets/{index-TWG-_D1M.js → index-DkfQR3hM.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAE3hwev.js → index-DnLucOn9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgkoH3_g.js → index-DugB_aKa.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRGx3w6T.js → index-DyLUarhZ.js} +3 -3
- package/src/assets/web-panel/assets/{index-BeM4hfht.js → index-T6_6ygJZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DsscwxPE.js → index-Tu8eWqh-.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bmr_vxXD.js → index-W7XUgdVL.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoNZlzhQ.js → index-dHZinfko.js} +1 -1
- package/src/assets/web-panel/assets/{index-DqkbHHg8.js → index-jyNgp5FI.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-D-4Hzgej.js → initDefaultProps-Cv4p8e4w.js} +1 -1
- package/src/assets/web-panel/assets/{motion-D4Wl4xU7.js → motion-B8EwmMOw.js} +1 -1
- package/src/assets/web-panel/assets/{move-Cd5eQvL0.js → move-B8R9F8TK.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-B4ilslCW.js → mtc-parser-DoFYz5Bp.js} +1 -1
- package/src/assets/web-panel/assets/{omit-Ckarx-tf.js → omit-CHWYLBjI.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-QJ0bpCmc.js → pickAttrs-CXuPDO77.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CKHm-tq8.js → placementArrow-BaAtUUon.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CV6a_w_Y.js → responsiveObserve-DCc4zcDD.js} +1 -1
- package/src/assets/web-panel/assets/{slide-mHpeQFgk.js → slide-B_lx89oZ.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DIaQNQWA.js → statusUtils-DE8DPmR0.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-PJpzavV8.js → styleChecker-1ktXOsL_.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BH7zTw8S.js → useFlexGapSupport-xH89-wjh.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BDNG3JDd.js → useFs-DscvfW5p.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CY2VWdUg.js → usePersonalDataHub-RdMKQta7.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-D0kw9jhz.js → vnode-Z3wTni2T.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-B5g7TN7r.js → zoom-B0iChCsm.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +3 -3
- package/src/commands/agent.js +105 -62
- package/src/commands/background-session.js +100 -10
- package/src/commands/git.js +39 -0
- package/src/commands/mcp.js +5 -0
- package/src/commands/plugin.js +160 -29
- package/src/commands/remote-control.js +295 -110
- package/src/commands/session-group.js +131 -0
- package/src/commands/session-location.js +48 -0
- package/src/commands/session-message.js +171 -0
- package/src/commands/session.js +22 -0
- package/src/commands/sync-providers.js +4 -1
- package/src/commands/update.js +1 -0
- package/src/data/changelog.json +41 -2
- package/src/gateways/ws/ws-agent-handler.js +20 -12
- package/src/harness/jsonl-session-store.js +279 -37
- package/src/harness/mcp-client.js +832 -91
- package/src/harness/session-index.js +15 -4
- package/src/lib/agent-worktree.js +8 -1
- package/src/lib/auto-mode-safety-classifier.js +3 -17
- package/src/lib/background-agent-keeper-protocol.js +26 -6
- package/src/lib/background-agent-supervisor.js +17 -1
- package/src/lib/background-session-transport.js +153 -27
- package/src/lib/browser-evidence.js +1405 -0
- package/src/lib/chrome-connector.js +465 -79
- package/src/lib/claude-project-auto-memory.js +481 -0
- package/src/lib/claude-project-storage-layout.js +104 -0
- package/src/lib/cloud/cloud-client.js +40 -0
- package/src/lib/execution-location-local-supervisor.mjs +226 -0
- package/src/lib/execution-location-runner-lifecycle.js +991 -0
- package/src/lib/execution-location-target-preflight.js +333 -0
- package/src/lib/execution-location-target.js +783 -13
- package/src/lib/file-lines.js +6 -1
- package/src/lib/gateway-client-resilience.js +571 -0
- package/src/lib/gitlab-mr-worktree.js +176 -0
- package/src/lib/hooks-v2-producers.js +119 -15
- package/src/lib/hooks-v2-workspace-context.js +44 -1
- package/src/lib/host-resource-budget.js +295 -0
- package/src/lib/ide-context.js +15 -2
- package/src/lib/llm-config-defaults.js +43 -0
- package/src/lib/mcp-client.js +2 -0
- package/src/lib/mcp-headers-helper.js +47 -5
- package/src/lib/mcp-lifecycle-authority.js +887 -0
- package/src/lib/mcp-oauth.js +156 -26
- package/src/lib/mcp-tls.js +330 -0
- package/src/lib/output-styles.js +12 -0
- package/src/lib/paths.js +260 -32
- package/src/lib/permission-request.js +48 -0
- package/src/lib/plugin-runtime/bin.js +3 -1
- package/src/lib/plugin-runtime/hooks.js +3 -1
- package/src/lib/plugin-runtime/install.js +730 -71
- package/src/lib/plugin-runtime/lsp.js +4 -4
- package/src/lib/plugin-runtime/marketplace-archive-source.js +366 -0
- package/src/lib/plugin-runtime/marketplace-catalog.js +63 -6
- package/src/lib/plugin-runtime/marketplace-command-descriptor.js +343 -0
- package/src/lib/plugin-runtime/marketplace-command-source.js +580 -0
- package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +1 -0
- package/src/lib/plugin-runtime/marketplace-source-adapter.js +188 -0
- package/src/lib/plugin-runtime/marketplace-source-cache.js +11 -1
- package/src/lib/plugin-runtime/mcp.js +3 -1
- package/src/lib/plugin-runtime/monitors.js +3 -1
- package/src/lib/plugin-runtime/remote-source.js +583 -58
- package/src/lib/plugin-runtime/settings.js +3 -1
- package/src/lib/plugin-runtime/trust.js +274 -13
- package/src/lib/plugin-security.js +393 -41
- package/src/lib/plugin-source-identity.js +850 -0
- package/src/lib/process-execution-broker/credential-agent.js +210 -41
- package/src/lib/process-execution-broker/index.js +15 -1
- package/src/lib/process-execution-broker/linux-cgroup-v2.js +391 -0
- package/src/lib/process-execution-broker/platform-sandbox.js +50 -28
- package/src/lib/project-mcp-trust.js +165 -10
- package/src/lib/remote-approval-bridge.js +136 -46
- package/src/lib/remote-control.js +217 -31
- package/src/lib/resume-session.js +9 -6
- package/src/lib/session-core-singletons.js +26 -11
- package/src/lib/session-host-lease.js +52 -16
- package/src/lib/session-lifecycle-scope.js +153 -0
- package/src/lib/session-message-fabric.js +1252 -0
- package/src/lib/session-projection.js +286 -3
- package/src/lib/session-runtime-retention.js +155 -0
- package/src/lib/session-store-guard.js +3 -1
- package/src/lib/session-workbench-store.js +466 -0
- package/src/lib/settings-loader.cjs +29 -0
- package/src/lib/spellcheck.js +211 -0
- package/src/lib/sub-agent-context.js +9 -9
- package/src/lib/sync-cli-db.js +485 -129
- package/src/lib/sync-credentials.js +31 -20
- package/src/lib/web-fetch.js +112 -40
- package/src/lib/web-search.js +127 -61
- package/src/lib/workspace-trust.js +793 -0
- package/src/repl/agent-repl.js +175 -19
- package/src/repl/permission-prompt.js +29 -8
- package/src/repl/repl-keybindings.js +55 -0
- package/src/runtime/agent-core.js +362 -20
- package/src/runtime/bootstrap.js +37 -13
- package/src/runtime/coding-agent-contract-shared.cjs +11 -4
- package/src/runtime/headless-runner.js +281 -102
- package/src/runtime/headless-stream.js +199 -30
- package/src/runtime/mcp-config.js +298 -6
- package/src/runtime/policies/agent-policy.js +10 -0
- package/src/runtime/status-diagnostics-lite.js +9 -1
- package/src/assets/web-panel/assets/ChatBubbleRenderer-4gGkdGgA.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-B4RLp2zs.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-DPy1XMYh.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CagEcIex.js +0 -1
- package/src/assets/web-panel/assets/index-pzT9PpOU.js +0 -1
- package/src/assets/web-panel/assets/index-z1kFadaX.js +0 -1
package/src/lib/mcp-oauth.js
CHANGED
|
@@ -52,6 +52,56 @@ export const _deps = {
|
|
|
52
52
|
withStoreLock,
|
|
53
53
|
};
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Resolve the exact IPv4-loopback redirect used by both the listener and the
|
|
57
|
+
* OAuth wire exchange. Pre-registered clients need byte-for-byte redirect URI
|
|
58
|
+
* stability, so explicit values are accepted only in canonical form.
|
|
59
|
+
*/
|
|
60
|
+
export function resolveOAuthLoopbackRedirect({
|
|
61
|
+
redirectUri,
|
|
62
|
+
host = "127.0.0.1",
|
|
63
|
+
port = 53682,
|
|
64
|
+
redirectPath = "/callback",
|
|
65
|
+
} = {}) {
|
|
66
|
+
if (redirectUri != null && typeof redirectUri !== "string") {
|
|
67
|
+
throw new Error("OAuth redirectUri must be a string");
|
|
68
|
+
}
|
|
69
|
+
const candidate =
|
|
70
|
+
redirectUri == null ? `http://${host}:${port}${redirectPath}` : redirectUri;
|
|
71
|
+
let parsed;
|
|
72
|
+
try {
|
|
73
|
+
parsed = new URL(candidate);
|
|
74
|
+
} catch {
|
|
75
|
+
throw new Error("OAuth redirectUri must be a valid loopback URL");
|
|
76
|
+
}
|
|
77
|
+
const parsedPort = Number(parsed.port);
|
|
78
|
+
const canonical = `http://127.0.0.1:${parsed.port}${parsed.pathname}`;
|
|
79
|
+
if (
|
|
80
|
+
parsed.protocol !== "http:" ||
|
|
81
|
+
parsed.hostname !== "127.0.0.1" ||
|
|
82
|
+
!parsed.port ||
|
|
83
|
+
!Number.isInteger(parsedPort) ||
|
|
84
|
+
parsedPort < 1 ||
|
|
85
|
+
parsedPort > 65535 ||
|
|
86
|
+
parsed.username ||
|
|
87
|
+
parsed.password ||
|
|
88
|
+
parsed.search ||
|
|
89
|
+
parsed.hash ||
|
|
90
|
+
!parsed.pathname.startsWith("/") ||
|
|
91
|
+
candidate !== canonical
|
|
92
|
+
) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
"OAuth redirectUri must be an exact canonical http://127.0.0.1:<port>/<path> URL",
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
redirectUri: candidate,
|
|
99
|
+
host: "127.0.0.1",
|
|
100
|
+
port: parsedPort,
|
|
101
|
+
redirectPath: parsed.pathname,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
55
105
|
/** Discovery/token requests retry ONCE after a transient error (CC 2.1.191). */
|
|
56
106
|
const MCP_OAUTH_RETRY_BACKOFF_MS = 300;
|
|
57
107
|
|
|
@@ -434,7 +484,23 @@ export function loadTokenStore() {
|
|
|
434
484
|
}
|
|
435
485
|
|
|
436
486
|
export function getStoredToken(serverUrl) {
|
|
437
|
-
|
|
487
|
+
const record = loadTokenStore()[serverKey(serverUrl)] || null;
|
|
488
|
+
return record?.revoked === true ? null : record;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function tokenRecordRevision(record) {
|
|
492
|
+
return Number.isSafeInteger(record?.credential_revision) &&
|
|
493
|
+
record.credential_revision >= 0
|
|
494
|
+
? record.credential_revision
|
|
495
|
+
: 0;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function tokenRevisionConflict(serverUrl) {
|
|
499
|
+
const error = new Error(
|
|
500
|
+
`MCP OAuth credential changed while it was being refreshed: ${serverKey(serverUrl)}`,
|
|
501
|
+
);
|
|
502
|
+
error.code = "MCP_TOKEN_STORE_REVISION_CONFLICT";
|
|
503
|
+
return error;
|
|
438
504
|
}
|
|
439
505
|
|
|
440
506
|
// The store holds OAuth access + refresh tokens, so it must not be world-
|
|
@@ -592,7 +658,7 @@ export function withStoreLock(file, fn) {
|
|
|
592
658
|
}
|
|
593
659
|
}
|
|
594
660
|
|
|
595
|
-
export function saveStoredToken(serverUrl, record) {
|
|
661
|
+
export function saveStoredToken(serverUrl, record, options = {}) {
|
|
596
662
|
const file = tokenStorePath();
|
|
597
663
|
return _deps.withStoreLock(file, () => {
|
|
598
664
|
const r = _readStore(file); // re-read inside the lock (latest state)
|
|
@@ -604,24 +670,56 @@ export function saveStoredToken(serverUrl, record) {
|
|
|
604
670
|
_warnCorruptStoreOnce(file, r.err, archived);
|
|
605
671
|
}
|
|
606
672
|
const store = r.store;
|
|
607
|
-
|
|
673
|
+
const key = serverKey(serverUrl);
|
|
674
|
+
const current = store[key] || null;
|
|
675
|
+
if (
|
|
676
|
+
options.expectedRevision != null &&
|
|
677
|
+
(tokenRecordRevision(current) !== options.expectedRevision ||
|
|
678
|
+
current?.revoked === true)
|
|
679
|
+
) {
|
|
680
|
+
throw tokenRevisionConflict(serverUrl);
|
|
681
|
+
}
|
|
682
|
+
store[key] = {
|
|
683
|
+
...record,
|
|
684
|
+
server: key,
|
|
685
|
+
credential_revision: tokenRecordRevision(current) + 1,
|
|
686
|
+
revoked: false,
|
|
687
|
+
};
|
|
608
688
|
_writeStoreSecure(file, store);
|
|
609
|
-
return store[
|
|
689
|
+
return store[key];
|
|
610
690
|
});
|
|
611
691
|
}
|
|
612
692
|
|
|
613
|
-
|
|
693
|
+
function revokeStoredToken(serverUrl, { expectedRevision = null } = {}) {
|
|
614
694
|
const file = tokenStorePath();
|
|
615
695
|
return _deps.withStoreLock(file, () => {
|
|
616
696
|
const store = loadTokenStore(); // re-read inside the lock (latest state)
|
|
617
697
|
const key = serverKey(serverUrl);
|
|
618
|
-
|
|
619
|
-
|
|
698
|
+
const current = store[key];
|
|
699
|
+
if (!current || current.revoked === true) return false;
|
|
700
|
+
if (
|
|
701
|
+
expectedRevision != null &&
|
|
702
|
+
tokenRecordRevision(current) !== expectedRevision
|
|
703
|
+
) {
|
|
704
|
+
return false;
|
|
705
|
+
}
|
|
706
|
+
// Keep a revision-only tombstone. An in-flight refresh that read the old
|
|
707
|
+
// credential must not resurrect it after explicit logout/revocation.
|
|
708
|
+
store[key] = {
|
|
709
|
+
server: key,
|
|
710
|
+
credential_revision: tokenRecordRevision(current) + 1,
|
|
711
|
+
revoked: true,
|
|
712
|
+
revoked_at: _deps.now(),
|
|
713
|
+
};
|
|
620
714
|
_writeStoreSecure(file, store);
|
|
621
715
|
return true;
|
|
622
716
|
});
|
|
623
717
|
}
|
|
624
718
|
|
|
719
|
+
export function deleteStoredToken(serverUrl) {
|
|
720
|
+
return revokeStoredToken(serverUrl);
|
|
721
|
+
}
|
|
722
|
+
|
|
625
723
|
/** True when a record's access token is missing or within `skewMs` of expiry. */
|
|
626
724
|
export function isTokenExpired(record, { skewMs = 60_000 } = {}) {
|
|
627
725
|
if (!record || !record.access_token) return true;
|
|
@@ -642,6 +740,8 @@ export function isTokenExpired(record, { skewMs = 60_000 } = {}) {
|
|
|
642
740
|
* @param {string} serverUrl
|
|
643
741
|
* @param {{ forceRefresh?: boolean }} [opts]
|
|
644
742
|
*/
|
|
743
|
+
const tokenRefreshFlights = new Map();
|
|
744
|
+
|
|
645
745
|
export async function ensureValidToken(
|
|
646
746
|
serverUrl,
|
|
647
747
|
{ forceRefresh = false } = {},
|
|
@@ -650,21 +750,52 @@ export async function ensureValidToken(
|
|
|
650
750
|
if (!record) return null;
|
|
651
751
|
if (!forceRefresh && !isTokenExpired(record)) return record.access_token;
|
|
652
752
|
if (!record.refresh_token || !record.endpoints?.token_endpoint) {
|
|
653
|
-
//
|
|
654
|
-
//
|
|
655
|
-
//
|
|
656
|
-
return
|
|
753
|
+
// Reaching this branch means the cached access token is expired (including
|
|
754
|
+
// the safety skew) or the server already rejected it. Reusing it would
|
|
755
|
+
// create a deterministic 401/403 loop, so every caller fails closed.
|
|
756
|
+
return null;
|
|
657
757
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
758
|
+
const key = serverKey(serverUrl);
|
|
759
|
+
let refresh = tokenRefreshFlights.get(key);
|
|
760
|
+
if (!refresh) {
|
|
761
|
+
refresh = (async () => {
|
|
762
|
+
try {
|
|
763
|
+
const tok = await refreshAccessToken(
|
|
764
|
+
{ token_endpoint: record.endpoints.token_endpoint },
|
|
765
|
+
{ refreshToken: record.refresh_token, clientId: record.client_id },
|
|
766
|
+
);
|
|
767
|
+
const updated = saveStoredToken(
|
|
768
|
+
serverUrl,
|
|
769
|
+
{ ...record, ...tok },
|
|
770
|
+
{ expectedRevision: tokenRecordRevision(record) },
|
|
771
|
+
);
|
|
772
|
+
return { status: "refreshed", accessToken: updated.access_token };
|
|
773
|
+
} catch (error) {
|
|
774
|
+
if ([400, 401, 403].includes(error?.status)) {
|
|
775
|
+
const revoked = revokeStoredToken(serverUrl, {
|
|
776
|
+
expectedRevision: tokenRecordRevision(record),
|
|
777
|
+
});
|
|
778
|
+
return { status: revoked ? "revoked" : "conflict" };
|
|
779
|
+
}
|
|
780
|
+
return {
|
|
781
|
+
status:
|
|
782
|
+
error?.code === "MCP_TOKEN_STORE_REVISION_CONFLICT"
|
|
783
|
+
? "conflict"
|
|
784
|
+
: "failed",
|
|
785
|
+
};
|
|
786
|
+
} finally {
|
|
787
|
+
tokenRefreshFlights.delete(key);
|
|
788
|
+
}
|
|
789
|
+
})();
|
|
790
|
+
tokenRefreshFlights.set(key, refresh);
|
|
667
791
|
}
|
|
792
|
+
const outcome = await refresh;
|
|
793
|
+
if (outcome.status === "refreshed") return outcome.accessToken;
|
|
794
|
+
if (outcome.status === "revoked") return null;
|
|
795
|
+
if (outcome.status === "conflict") {
|
|
796
|
+
return getStoredToken(serverUrl)?.access_token || null;
|
|
797
|
+
}
|
|
798
|
+
return null;
|
|
668
799
|
}
|
|
669
800
|
|
|
670
801
|
// ── interactive orchestrator ──────────────────────────────────────────────
|
|
@@ -794,23 +925,22 @@ export function waitForCallback({
|
|
|
794
925
|
* Run the full interactive Authorization Code + PKCE flow and persist the token.
|
|
795
926
|
*
|
|
796
927
|
* @param {string} serverUrl
|
|
797
|
-
* @param {object} [opts] { scope, clientId, port=53682, host,
|
|
798
|
-
* timeout, writeOut }
|
|
928
|
+
* @param {object} [opts] { scope, clientId, redirectUri, port=53682, host,
|
|
929
|
+
* redirectPath, timeout, writeOut }
|
|
799
930
|
* @returns {Promise<{server, access_token, ...}>} the stored record
|
|
800
931
|
*/
|
|
801
932
|
export async function authorizeInteractive(serverUrl, opts = {}) {
|
|
802
933
|
const {
|
|
803
934
|
scope,
|
|
804
935
|
clientId: cfgClientId,
|
|
805
|
-
port = 53682,
|
|
806
|
-
host = "127.0.0.1",
|
|
807
|
-
redirectPath = "/callback",
|
|
808
936
|
timeout = 300_000,
|
|
809
937
|
writeOut = (s) => process.stdout.write(s),
|
|
810
938
|
} = opts;
|
|
811
939
|
|
|
940
|
+
const { redirectUri, host, port, redirectPath } =
|
|
941
|
+
resolveOAuthLoopbackRedirect(opts);
|
|
942
|
+
|
|
812
943
|
const metadata = await discoverAuthMetadata(serverUrl);
|
|
813
|
-
const redirectUri = `http://${host}:${port}${redirectPath}`;
|
|
814
944
|
|
|
815
945
|
let clientId = cfgClientId;
|
|
816
946
|
let clientSecret = null;
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/** Bounded mTLS material loading for HTTP/WebSocket MCP transports. */
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { createHash, X509Certificate } from "node:crypto";
|
|
6
|
+
import { createSecureContext } from "node:tls";
|
|
7
|
+
import { isProxy } from "node:util/types";
|
|
8
|
+
import { Agent } from "undici";
|
|
9
|
+
|
|
10
|
+
export const MCP_TLS_MAX_FILE_BYTES = 1024 * 1024;
|
|
11
|
+
export const MCP_TLS_MANAGED_SOURCE_REQUIRED_CODE =
|
|
12
|
+
"CC_MCP_TLS_MANAGED_SOURCE_REQUIRED";
|
|
13
|
+
export const MCP_TLS_MANAGED_SOURCE_REQUIRED_MESSAGE =
|
|
14
|
+
"MCP TLS configuration is restricted to managed provisioned sources";
|
|
15
|
+
|
|
16
|
+
// TLS file paths are a privileged configuration capability: merely marking a
|
|
17
|
+
// JSON object as `configScope: "managed"` must never let a project, user, or
|
|
18
|
+
// headless config read certificate material. Keep the provisioned snapshot in
|
|
19
|
+
// a module-private WeakMap so it cannot be recreated by JSON, object spread,
|
|
20
|
+
// or a lookalike field supplied by an MCP config source.
|
|
21
|
+
const MANAGED_MCP_TLS_PROVENANCE = new WeakMap();
|
|
22
|
+
const MANAGED_MCP_TLS_SCOPE = "managed";
|
|
23
|
+
|
|
24
|
+
const TLS_KEYS = new Set([
|
|
25
|
+
"certFile",
|
|
26
|
+
"keyFile",
|
|
27
|
+
"caFile",
|
|
28
|
+
"serverName",
|
|
29
|
+
"rejectUnauthorized",
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
function tlsError(code, message, cause = undefined) {
|
|
33
|
+
const error = new Error(message, cause ? { cause } : undefined);
|
|
34
|
+
error.code = code;
|
|
35
|
+
return error;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function managedTlsSourceRequiredError() {
|
|
39
|
+
// Do not include the claimed scope, source file, certificate path, or any
|
|
40
|
+
// TLS configuration field here. This error is surfaced by headless and MCP
|
|
41
|
+
// connection diagnostics, where config values are not safe to disclose.
|
|
42
|
+
return tlsError(
|
|
43
|
+
MCP_TLS_MANAGED_SOURCE_REQUIRED_CODE,
|
|
44
|
+
MCP_TLS_MANAGED_SOURCE_REQUIRED_MESSAGE,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function plainTlsObject(value) {
|
|
49
|
+
if (
|
|
50
|
+
!value ||
|
|
51
|
+
typeof value !== "object" ||
|
|
52
|
+
Array.isArray(value) ||
|
|
53
|
+
isProxy(value)
|
|
54
|
+
) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
let prototype;
|
|
58
|
+
let descriptors;
|
|
59
|
+
try {
|
|
60
|
+
prototype = Object.getPrototypeOf(value);
|
|
61
|
+
descriptors = Object.getOwnPropertyDescriptors(value);
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
if (prototype !== Object.prototype && prototype !== null) return null;
|
|
66
|
+
const keys = Reflect.ownKeys(descriptors);
|
|
67
|
+
if (
|
|
68
|
+
keys.some(
|
|
69
|
+
(key) =>
|
|
70
|
+
typeof key !== "string" ||
|
|
71
|
+
!TLS_KEYS.has(key) ||
|
|
72
|
+
!Object.prototype.hasOwnProperty.call(descriptors[key], "value") ||
|
|
73
|
+
descriptors[key].enumerable !== true,
|
|
74
|
+
)
|
|
75
|
+
) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return Object.fromEntries(keys.map((key) => [key, descriptors[key].value]));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function absoluteFile(value, label) {
|
|
82
|
+
if (value == null || value === "") return null;
|
|
83
|
+
if (typeof value !== "string" || !path.isAbsolute(value)) {
|
|
84
|
+
throw tlsError(
|
|
85
|
+
"CC_MCP_TLS_CONFIG_INVALID",
|
|
86
|
+
`MCP TLS ${label} must be an absolute file path`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return path.resolve(value);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function validServerName(value) {
|
|
93
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 253) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
return value
|
|
97
|
+
.split(".")
|
|
98
|
+
.every(
|
|
99
|
+
(label) =>
|
|
100
|
+
label.length > 0 &&
|
|
101
|
+
label.length <= 63 &&
|
|
102
|
+
/^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/u.test(label),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function normalizeMcpTlsConfig(value) {
|
|
107
|
+
if (value == null) return null;
|
|
108
|
+
const snapshot = plainTlsObject(value);
|
|
109
|
+
if (!snapshot) {
|
|
110
|
+
throw tlsError(
|
|
111
|
+
"CC_MCP_TLS_CONFIG_INVALID",
|
|
112
|
+
"MCP TLS configuration must be a plain allowlisted data object",
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
const certFile = absoluteFile(snapshot.certFile, "certFile");
|
|
116
|
+
const keyFile = absoluteFile(snapshot.keyFile, "keyFile");
|
|
117
|
+
if (Boolean(certFile) !== Boolean(keyFile)) {
|
|
118
|
+
throw tlsError(
|
|
119
|
+
"CC_MCP_TLS_CONFIG_INVALID",
|
|
120
|
+
"MCP TLS certFile and keyFile must be configured together",
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
const caFile = absoluteFile(snapshot.caFile, "caFile");
|
|
124
|
+
if (!certFile && !caFile) {
|
|
125
|
+
throw tlsError(
|
|
126
|
+
"CC_MCP_TLS_CONFIG_INVALID",
|
|
127
|
+
"MCP TLS configuration requires a client certificate or CA file",
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
const serverName = snapshot.serverName == null ? null : snapshot.serverName;
|
|
131
|
+
if (serverName != null && !validServerName(serverName)) {
|
|
132
|
+
throw tlsError(
|
|
133
|
+
"CC_MCP_TLS_CONFIG_INVALID",
|
|
134
|
+
"MCP TLS serverName must be a valid DNS name",
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (
|
|
138
|
+
snapshot.rejectUnauthorized != null &&
|
|
139
|
+
typeof snapshot.rejectUnauthorized !== "boolean"
|
|
140
|
+
) {
|
|
141
|
+
throw tlsError(
|
|
142
|
+
"CC_MCP_TLS_CONFIG_INVALID",
|
|
143
|
+
"MCP TLS rejectUnauthorized must be boolean",
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
certFile,
|
|
148
|
+
keyFile,
|
|
149
|
+
caFile,
|
|
150
|
+
serverName,
|
|
151
|
+
rejectUnauthorized: snapshot.rejectUnauthorized !== false,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Convert TLS settings from the organization-managed MCP loader into an
|
|
157
|
+
* immutable, non-forgeable configuration capability.
|
|
158
|
+
*
|
|
159
|
+
* This is deliberately separate from `normalizeMcpTlsConfig`: normalization
|
|
160
|
+
* validates data, while provisioning records the trusted source provenance
|
|
161
|
+
* that is required before a certificate/key/CA file can be opened.
|
|
162
|
+
*/
|
|
163
|
+
export function provisionManagedMcpTlsConfig(value) {
|
|
164
|
+
const config = normalizeMcpTlsConfig(value);
|
|
165
|
+
if (!config) return null;
|
|
166
|
+
const provisioned = Object.freeze({ ...config });
|
|
167
|
+
MANAGED_MCP_TLS_PROVENANCE.set(
|
|
168
|
+
provisioned,
|
|
169
|
+
Object.freeze({ configScope: MANAGED_MCP_TLS_SCOPE }),
|
|
170
|
+
);
|
|
171
|
+
return provisioned;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Enforce the managed source boundary before any TLS path is normalized or
|
|
176
|
+
* opened. The private provenance association survives normal config-object
|
|
177
|
+
* spreads because the provisioned `tls` value itself is preserved, but a
|
|
178
|
+
* caller cannot manufacture it from JSON or by setting `configScope`.
|
|
179
|
+
*/
|
|
180
|
+
export function assertManagedMcpTlsConfig(value, { configScope } = {}) {
|
|
181
|
+
if (value == null) return null;
|
|
182
|
+
const provenance =
|
|
183
|
+
value && typeof value === "object"
|
|
184
|
+
? MANAGED_MCP_TLS_PROVENANCE.get(value)
|
|
185
|
+
: null;
|
|
186
|
+
if (
|
|
187
|
+
!provenance ||
|
|
188
|
+
configScope !== MANAGED_MCP_TLS_SCOPE ||
|
|
189
|
+
provenance.configScope !== MANAGED_MCP_TLS_SCOPE
|
|
190
|
+
) {
|
|
191
|
+
throw managedTlsSourceRequiredError();
|
|
192
|
+
}
|
|
193
|
+
return value;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function readTlsFile(filePath, label, fsImpl) {
|
|
197
|
+
if (!filePath) return null;
|
|
198
|
+
let descriptor = null;
|
|
199
|
+
let stat;
|
|
200
|
+
try {
|
|
201
|
+
const link = fsImpl.lstatSync(filePath);
|
|
202
|
+
if (link.isSymbolicLink()) {
|
|
203
|
+
throw tlsError(
|
|
204
|
+
"CC_MCP_TLS_FILE_UNSAFE",
|
|
205
|
+
`MCP TLS ${label} must not be a symbolic link`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
const constants = fsImpl.constants || fs.constants;
|
|
209
|
+
descriptor = fsImpl.openSync(
|
|
210
|
+
filePath,
|
|
211
|
+
constants.O_RDONLY | (constants.O_NOFOLLOW || 0),
|
|
212
|
+
);
|
|
213
|
+
stat = fsImpl.fstatSync(descriptor);
|
|
214
|
+
} catch (cause) {
|
|
215
|
+
if (descriptor != null) fsImpl.closeSync?.(descriptor);
|
|
216
|
+
if (cause?.code?.startsWith?.("CC_MCP_TLS_")) throw cause;
|
|
217
|
+
throw tlsError(
|
|
218
|
+
"CC_MCP_TLS_FILE_UNAVAILABLE",
|
|
219
|
+
`MCP TLS ${label} could not be inspected`,
|
|
220
|
+
cause,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
if (!stat.isFile() || stat.size <= 0 || stat.size > MCP_TLS_MAX_FILE_BYTES) {
|
|
224
|
+
fsImpl.closeSync?.(descriptor);
|
|
225
|
+
throw tlsError(
|
|
226
|
+
"CC_MCP_TLS_FILE_UNSAFE",
|
|
227
|
+
`MCP TLS ${label} must be a non-empty regular file no larger than ${MCP_TLS_MAX_FILE_BYTES} bytes`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
if (
|
|
231
|
+
label === "keyFile" &&
|
|
232
|
+
process.platform !== "win32" &&
|
|
233
|
+
(stat.mode & 0o077) !== 0
|
|
234
|
+
) {
|
|
235
|
+
fsImpl.closeSync?.(descriptor);
|
|
236
|
+
throw tlsError(
|
|
237
|
+
"CC_MCP_TLS_FILE_UNSAFE",
|
|
238
|
+
"MCP TLS keyFile must not be readable or writable by group/other users",
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
return fsImpl.readFileSync(descriptor);
|
|
243
|
+
} catch (cause) {
|
|
244
|
+
throw tlsError(
|
|
245
|
+
"CC_MCP_TLS_FILE_UNAVAILABLE",
|
|
246
|
+
`MCP TLS ${label} could not be read`,
|
|
247
|
+
cause,
|
|
248
|
+
);
|
|
249
|
+
} finally {
|
|
250
|
+
fsImpl.closeSync?.(descriptor);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function updateDigestPart(hash, value) {
|
|
255
|
+
const bytes = value || Buffer.alloc(0);
|
|
256
|
+
const length = Buffer.alloc(4);
|
|
257
|
+
length.writeUInt32BE(bytes.length);
|
|
258
|
+
hash.update(length).update(bytes);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function validateCertificateBytes(bytes) {
|
|
262
|
+
if (!bytes) return;
|
|
263
|
+
const text = bytes.toString("utf8");
|
|
264
|
+
const pemCertificates = text.match(
|
|
265
|
+
/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/gu,
|
|
266
|
+
);
|
|
267
|
+
if (pemCertificates?.length) {
|
|
268
|
+
for (const certificate of pemCertificates) new X509Certificate(certificate);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
new X509Certificate(bytes);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export function loadMcpTlsMaterial(
|
|
275
|
+
value,
|
|
276
|
+
{
|
|
277
|
+
configScope,
|
|
278
|
+
fsImpl = fs,
|
|
279
|
+
createSecureContextImpl = createSecureContext,
|
|
280
|
+
validateCertificateImpl = validateCertificateBytes,
|
|
281
|
+
} = {},
|
|
282
|
+
) {
|
|
283
|
+
const config = assertManagedMcpTlsConfig(value, { configScope });
|
|
284
|
+
if (!config) return null;
|
|
285
|
+
const cert = readTlsFile(config.certFile, "certFile", fsImpl);
|
|
286
|
+
const key = readTlsFile(config.keyFile, "keyFile", fsImpl);
|
|
287
|
+
const ca = readTlsFile(config.caFile, "caFile", fsImpl);
|
|
288
|
+
const connectOptions = {
|
|
289
|
+
...(cert ? { cert } : {}),
|
|
290
|
+
...(key ? { key } : {}),
|
|
291
|
+
...(ca ? { ca } : {}),
|
|
292
|
+
...(config.serverName ? { servername: config.serverName } : {}),
|
|
293
|
+
rejectUnauthorized: config.rejectUnauthorized,
|
|
294
|
+
};
|
|
295
|
+
try {
|
|
296
|
+
// Parse certificates and verify the client cert/key pair before creating a
|
|
297
|
+
// dispatcher or issuing any outbound request. Node/Undici would otherwise
|
|
298
|
+
// discover malformed or mismatched material only during a network dial.
|
|
299
|
+
validateCertificateImpl(cert);
|
|
300
|
+
validateCertificateImpl(ca);
|
|
301
|
+
createSecureContextImpl(connectOptions);
|
|
302
|
+
} catch {
|
|
303
|
+
// TLS parser diagnostics may quote attacker-controlled PEM text or paths.
|
|
304
|
+
// Keep the product error fixed and do not retain the parser error as cause.
|
|
305
|
+
throw tlsError(
|
|
306
|
+
"CC_MCP_TLS_MATERIAL_INVALID",
|
|
307
|
+
"MCP TLS certificate material is invalid or the client key does not match",
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
const identityHash = createHash("sha256");
|
|
311
|
+
updateDigestPart(identityHash, cert);
|
|
312
|
+
updateDigestPart(identityHash, key);
|
|
313
|
+
updateDigestPart(identityHash, ca);
|
|
314
|
+
const identityDigest = `sha256:${identityHash.digest("hex")}`;
|
|
315
|
+
return { connectOptions, identityDigest };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function createMcpTlsDispatcher(material) {
|
|
319
|
+
if (!material?.connectOptions) return null;
|
|
320
|
+
return new Agent({ connect: material.connectOptions });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export async function closeMcpTlsDispatcher(dispatcher) {
|
|
324
|
+
if (!dispatcher) return;
|
|
325
|
+
if (typeof dispatcher.close === "function") {
|
|
326
|
+
await dispatcher.close();
|
|
327
|
+
} else if (typeof dispatcher.destroy === "function") {
|
|
328
|
+
dispatcher.destroy();
|
|
329
|
+
}
|
|
330
|
+
}
|
package/src/lib/output-styles.js
CHANGED
|
@@ -33,6 +33,18 @@ export const BUILTIN_OUTPUT_STYLES = Object.freeze({
|
|
|
33
33
|
body: "",
|
|
34
34
|
builtin: true,
|
|
35
35
|
},
|
|
36
|
+
concise: {
|
|
37
|
+
name: "concise",
|
|
38
|
+
description: "Uses short, direct responses with minimal explanation.",
|
|
39
|
+
body: [
|
|
40
|
+
"## Output style: Concise",
|
|
41
|
+
"Respond directly and briefly. Prefer the smallest complete answer that",
|
|
42
|
+
"preserves essential context, decisions, verification results, and safety",
|
|
43
|
+
"warnings. Avoid restating the request, long preambles, and optional",
|
|
44
|
+
"tangents unless the user asks for more detail.",
|
|
45
|
+
].join("\n"),
|
|
46
|
+
builtin: true,
|
|
47
|
+
},
|
|
36
48
|
explanatory: {
|
|
37
49
|
name: "explanatory",
|
|
38
50
|
description: "Explains the reasoning and trade-offs behind changes.",
|