chainlesschain 0.163.2 → 0.163.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DnGsmsqJ.js → AIOps-DIY6KxV-.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CrO6fcQ7.js → ActionButton-BXH0muPt.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DsBdOaP2.js → Analytics-D39w8vmg.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DmQslKUB.js → AppLayout-CUU1zvLs.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BvyrT_QQ.js → Artifacts-D8azHO3T.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-prndoLiC.js → Audit-D0tAwXTg.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DOTFyA55.js → BackgroundAgents-C29mhJll.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-9zOnPp_8.js → Backup-D6Aa1Gro.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-C4PC6sAG.js → BaseInput-B1uoHbPQ.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BBAF-W9v.js → Chat-Bs4DLOR5.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CWhvvloO.js → ChatBubbleRenderer-DUYIBSle.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BI9iculd.js → Checkbox-CNcBPEa4.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-3DP9BCKb.js → Codegen-CIY89763.js} +1 -1
- package/src/assets/web-panel/assets/{Col-df-2TnJX.js → Col-CsPYFmgo.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CHs8gG_A.js → Community-BBocEge4.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CDfF6q7V.js → Compact-TQ8Ux0Lf.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CthrDnO7.js → Compliance-DfBsZTft.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-HjIqsWJw.js → Cowork-Cg-I6FVH.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-BKc196fb.js → Cron-DTQzfRTp.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CJnjdc8W.js → Crosschain-DGxEKGpR.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BHUy-8Q2.js → DID-DLZ1RDSr.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-QDB7sKeY.js → Dashboard-XneICKNI.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DStcVK11.js → Dropdown-BRU00J1-.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BwSUskMJ.js → EmailListRenderer-BzCbJuAy.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DgF_kurT.js → FamilyGuardDashboard-D7BRyNBG.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-k8wvv-wN.js → Federation-D9DeFIhW.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-C11qoq2r.js → FormItemContext-6rAtA9Ko.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BIIpr9kg.js → GenericCardRenderer-8Hqy80pU.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BlA1exTe.js → Git-Cmvkj1rt.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CYa1ZYM4.js → Governance-BRiQUjjw.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-6_kKVIt6.js → Inference-CKN_EzVf.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CB4AwxYy.js → KnowledgeGraph-BCDHwpwQ.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-xf7nHFAe.js → Logs-DK9ovhaB.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-D53pHiHy.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-aPg8e4Jt.js → Marketplace-BILPGakp.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-C-TNlVUf.js → McpTools-DTVCOr9-.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-DMTPIpGa.js → Memory-Hzc1iow4.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BxyvveRq.js → MobileBridge-GXfgnGYV.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DyPCrAyQ.js → MobileProjects-B-3TJWsS.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BdhqNXp6.js → Mtc-DwSJMWtK.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-Cz-HFxzW.js → MtcAudit-D2uxf-DA.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-CIUaFNsx.js → Multisig-DG6kbKK0.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CWe6-J17.js → NLProgramming-f5p49EDU.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-vYAKeviU.js → Notes-FUgHyzSD.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DQL9-2d7.js → NotificationSettings-DCHGee7e.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DnXj1jBn.js → OrderTableRenderer-Dm6LlIoa.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-B3spU8AN.js → Organization-BawA7qAg.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DgEwo7vm.js → Overflow-D6a5wZU1.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-hujMRH8X.js → P2P-K8WavwnA.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-C_qhdg9h.js → PdhVaultBrowser-4JAJYaqx.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-vp7LMcSf.js → Permissions-BDWvi8PD.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Dxr5Hgnq.js → PersonalDataHub-DF_K4E9i.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-DEPZqqH4.js → Pipeline-BYeZlesI.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DTpDiKPe.js → Privacy-BIHlgnIc.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-jDJsRWmF.js → ProjectInit-Cvf-6B5P.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-Dhor5RWH.js → ProjectSettings-CnwfJrRz.js} +2 -2
- package/src/assets/web-panel/assets/Projects-b-jPXzF4.js +1 -0
- package/src/assets/web-panel/assets/{Providers-BGs47s1a.js → Providers-CxC7JuYv.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CjIw-wIb.js → QrScannerModal-VLf3gAfL.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DX6jcqoi.js → QuickAsk-B5vGjRiF.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DopXkhar.js → Recommend--UW4iYs-.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-BLvqJE9c.js → RemoteSession-DMSPcfKR.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-DhRj2Me7.js → Reputation-kQXlEHmj.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DQ53vuPq.js → Row-CxGu3pit.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BslvwiBD.js → RssFeed-Bi5lo8MZ.js} +2 -2
- package/src/assets/web-panel/assets/{Search-DJKBDNKZ.js → Search-Cq_H5RTh.js} +1 -1
- package/src/assets/web-panel/assets/{Security-COhpxHER.js → Security-YFbmkaQS.js} +3 -3
- package/src/assets/web-panel/assets/{Services-dbulD6cI.js → Services-DLOGdaLm.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-ciIVNfhC.js → Skeleton-CteIxb3D.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-AiRTkTCk.js → Skills-CccopaIZ.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DDMcWYBc.js → Sla-AzfEQKX1.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BPxWbpXO.js → SpeechSettings-ekGo5l77.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BoEOXCyg.js → SyncSettings-BmZmAgYH.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-D7q3Z4uL.js +1 -0
- package/src/assets/web-panel/assets/{Templates-UowY8Ixe.js → Templates-CX1Tgeka.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DauSLQXN.js → Tenant-Zc59jF4i.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DBj7Jneq.js → Terminal-CXOD7y83.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-UMrPwoZC.js → TimelineRenderer-B1aBHPBo.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-4ea96-bn.js → Tokens-zALlOq_U.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger--ISCqt4b.js → Trigger-CQfJMXaq.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-D4GVimRJ.js → Trust-BU0RcM-w.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-Ci-nXRQc.js → UkeySign-IFZ3HsbH.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Cjk7mKPw.js → VideoEditing-BzslgW0v.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BgOLOO3Z.js → Wallet-BZAcQNS6.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-CCX0K29i.js → WebAuthn-CBK1DMi0.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-Ch3zgIsE.js → WorkflowEditor-BlMTPzO-.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DKOU_2Vb.js → chat-B51-8QVn.js} +1 -1
- package/src/assets/web-panel/assets/{colors-6OVo4u3C.js → colors-504RTByU.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BH78Hlsq.js → compact-item-C-GP6IQM.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CFRpSbbC.js → createContext-DOkIj7ID.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-kB0nCNwN.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DNQhgcVi.js → hasIn-DN7V-_FW.js} +1 -1
- package/src/assets/web-panel/assets/{index-dDkoL4Pe.js → index--n-ZciV3.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaYqY35Z.js → index-7jIqHbzw.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYrnm5Ob.js → index-B2-x9bS8.js} +1 -1
- package/src/assets/web-panel/assets/{index-sm7W8AXy.js → index-BFIFJ5Dr.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKN2noIF.js → index-B_vGU-Js.js} +1 -1
- package/src/assets/web-panel/assets/{index--rq90rL9.js → index-Bkxhaeta.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFPJ8yzF.js → index-By6u5IV1.js} +1 -1
- package/src/assets/web-panel/assets/index-Bya64sZE.js +1 -0
- package/src/assets/web-panel/assets/{index-caye7AOE.js → index-C0k_Nqg9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxRy5LPR.js → index-C3TSohzc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BepBTpa8.js → index-C4DV_5uA.js} +1 -1
- package/src/assets/web-panel/assets/{index-D84id2Y4.js → index-C8UltbXK.js} +1 -1
- package/src/assets/web-panel/assets/{index-Y9MJv28e.js → index-C9_48OzU.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bb6kDrUx.js → index-CKTfZyfU.js} +1 -1
- package/src/assets/web-panel/assets/{index-CrcLHN2F.js → index-CP2A226L.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkNEMANR.js → index-CUhQpiYv.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cu3L4r5l.js → index-CX78k-qf.js} +1 -1
- package/src/assets/web-panel/assets/{index-DaDgdJYj.js → index-CYopNjcJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Du0rnksC.js → index-Cao5DZy7.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaVE6E8y.js → index-CfiEM64n.js} +3 -3
- package/src/assets/web-panel/assets/{index-BujsOZfc.js → index-ConjaJro.js} +1 -1
- package/src/assets/web-panel/assets/{index-hPY0fFrd.js → index-CqcLo5WV.js} +1 -1
- package/src/assets/web-panel/assets/{index-BDSR7ecX.js → index-CySTJKeq.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHa4WyTZ.js → index-D1phyca8.js} +1 -1
- package/src/assets/web-panel/assets/{index-nbfHl1oV.js → index-D9LyiN-L.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkqHgG-b.js → index-DA2f1UV3.js} +1 -1
- package/src/assets/web-panel/assets/{index-Eag8e_mA.js → index-DAOpQvl_.js} +1 -1
- package/src/assets/web-panel/assets/{index-cdf7fd7d.js → index-DE-hmI2l.js} +1 -1
- package/src/assets/web-panel/assets/{index-bKm3TUDp.js → index-DEMPUVa3.js} +1 -1
- package/src/assets/web-panel/assets/index-D_FWpDZM.js +1 -0
- package/src/assets/web-panel/assets/{index-7TC3S8SN.js → index-DaA__-q1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvHGtNnF.js → index-FcV8J_Qz.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_Z-YoxX.js → index-P2mfY27Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-AuBhLqOm.js → index-et5JIPHO.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3RtTT4K.js → index-f4Bo4KXm.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNuCHHQT.js → index-kAqQXfP6.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkN3iTTp.js → index-kt_5rfcZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnnXQot_.js → index-so8eIIxF.js} +1 -1
- package/src/assets/web-panel/assets/{index-HNGGVF6o.js → index-yYTQZypF.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BZ9w6jPL.js → initDefaultProps-CPYEIdpV.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BnwUiOho.js → motion-Cjim9pxs.js} +1 -1
- package/src/assets/web-panel/assets/{move-ByUKJnNm.js → move-DuU-cgz3.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-V7yK1KEl.js → mtc-parser-DJHNAkta.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BEXBBt4j.js → omit-DSQBgdBj.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-qDm_46Gi.js → pickAttrs-CTmT_j5m.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BIbhEo_0.js → placementArrow-Lfm6v-r4.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-NbPbDgA_.js → responsiveObserve-CgrcPcDi.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BnUWglZ0.js → slide-CuE3110i.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BX8_wZI5.js → statusUtils-DiK5nbpf.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BV4OycQX.js → styleChecker-DPEJdQaZ.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Od_H5llY.js → useFlexGapSupport-CnnodGmp.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BWI-PyiX.js → useFs-DWF84Yav.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BqMuhG3g.js → usePersonalDataHub-a5b5Dw2f.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DNd23z6f.js → vnode-DAluzXOq.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-Cjaxnm7l.js → zoom-Dokg7uAZ.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/agenda.js +6 -5
- package/src/commands/agent.js +237 -75
- package/src/commands/agents.js +6 -0
- package/src/commands/background-session.js +11 -1
- package/src/commands/mcp.js +217 -30
- package/src/data/changelog.json +14 -1
- package/src/harness/mcp-client.js +242 -42
- package/src/harness/worktree-isolator.js +26 -3
- package/src/lib/agent-worktree.js +41 -2
- package/src/lib/background-agent-supervisor.js +790 -186
- package/src/lib/background-command-argv.js +318 -0
- package/src/lib/background-launch-profile.js +1 -0
- package/src/lib/background-phase-reporter.js +22 -2
- package/src/lib/background-worktree-policy.js +109 -0
- package/src/lib/cowork-mcp-tools.js +51 -6
- package/src/lib/cowork-task-runner.js +1 -0
- package/src/lib/mcp-headers-helper.js +76 -8
- package/src/lib/mcp-sandbox-policy.js +155 -0
- package/src/lib/mcp-stdio-execution-authority.js +163 -0
- package/src/lib/mcp-stdio-workspace-authority.js +252 -0
- package/src/lib/plugin-runtime/sandbox-policy.js +42 -2
- package/src/lib/process-execution-broker/index.js +179 -104
- package/src/lib/process-execution-broker/platform-sandbox.js +942 -168
- package/src/lib/process-tree-termination.js +17 -1
- package/src/lib/skill-mcp.js +65 -4
- package/src/lib/web-fetch.js +4 -4
- package/src/runtime/agent-runtime.js +61 -8
- package/src/runtime/mcp-config.js +235 -28
- package/src/workers/background-agent-worker.js +492 -71
- package/src/assets/web-panel/assets/MarkdownRenderer-Bf3DEqP6.js +0 -1
- package/src/assets/web-panel/assets/Projects-08xUDZGt.js +0 -1
- package/src/assets/web-panel/assets/Tasks-CEYFJwmr.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DEMblAIm.js +0 -1
- package/src/assets/web-panel/assets/index-BTvq-36D.js +0 -1
- package/src/assets/web-panel/assets/index-hUpvk6Jl.js +0 -1
|
@@ -15,9 +15,13 @@ import { pathToFileURL } from "url";
|
|
|
15
15
|
import { isProxy } from "node:util/types";
|
|
16
16
|
import WebSocket from "ws";
|
|
17
17
|
import { safeJsonParse } from "../lib/safe-json.js";
|
|
18
|
+
import {
|
|
19
|
+
MCP_SANDBOX_POLICY_INVALID_CODE,
|
|
20
|
+
normalizeMcpSandboxPolicy,
|
|
21
|
+
readMcpStdioCwd,
|
|
22
|
+
} from "../lib/mcp-sandbox-policy.js";
|
|
18
23
|
import { EventRuntimeProducer } from "../lib/event-runtime-producer.js";
|
|
19
24
|
import { currentHostHooksV2WorkspaceBinding } from "../lib/hooks-v2-workspace-context.js";
|
|
20
|
-
import { resolvePluginWorkspaceAuthority } from "../lib/plugin-runtime/sandbox-policy.js";
|
|
21
25
|
import {
|
|
22
26
|
admitMcpToolList,
|
|
23
27
|
isMcpToolMetadataError,
|
|
@@ -33,11 +37,13 @@ import {
|
|
|
33
37
|
consumeMcpStdioExecutionAuthority,
|
|
34
38
|
materializeApprovedMcpStdioInvocation,
|
|
35
39
|
renewMcpStdioExecutionAuthority,
|
|
40
|
+
verifyMcpStdioApprovedWorkingDirectory,
|
|
36
41
|
} from "../lib/mcp-stdio-execution-authority.js";
|
|
37
42
|
import {
|
|
38
43
|
MCP_STDIO_CAPSULE_CODE_SNAPSHOT_BOUNDARY,
|
|
39
44
|
prepareMcpStdioExecutableIdentity,
|
|
40
45
|
} from "../lib/mcp-stdio-executable-identity.js";
|
|
46
|
+
import { resolveMcpStdioSandboxContext } from "../lib/mcp-stdio-workspace-authority.js";
|
|
41
47
|
|
|
42
48
|
/**
|
|
43
49
|
* Injectable dependencies — overridable from tests.
|
|
@@ -53,7 +59,9 @@ export const _deps = {
|
|
|
53
59
|
consumeMcpStdioExecutionAuthority,
|
|
54
60
|
materializeApprovedMcpStdioInvocation,
|
|
55
61
|
renewMcpStdioExecutionAuthority,
|
|
62
|
+
verifyMcpStdioApprovedWorkingDirectory,
|
|
56
63
|
prepareMcpStdioExecutableIdentity,
|
|
64
|
+
resolveMcpStdioSandboxContext,
|
|
57
65
|
// Backoff sleep seam (tests override with a no-op so retries don't wait).
|
|
58
66
|
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
59
67
|
};
|
|
@@ -116,6 +124,60 @@ export function normalizeMcpConfigScope(scope = "user") {
|
|
|
116
124
|
return normalized;
|
|
117
125
|
}
|
|
118
126
|
|
|
127
|
+
const INVALID_MCP_CONFIG_ROW = Symbol("invalidMcpConfigRow");
|
|
128
|
+
const INVALID_MCP_CONFIG_ERROR = Symbol("invalidMcpConfigError");
|
|
129
|
+
const INVALID_MCP_CONFIG_RAW_ROW = Symbol("invalidMcpConfigRawRow");
|
|
130
|
+
|
|
131
|
+
function rawMcpRowValue(row, key) {
|
|
132
|
+
if (!row || typeof row !== "object" || isProxy(row)) return undefined;
|
|
133
|
+
const descriptor = Object.getOwnPropertyDescriptor(row, key);
|
|
134
|
+
return descriptor && "value" in descriptor ? descriptor.value : undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function mcpRowVisibility(row, { invalid = false, error = null } = {}) {
|
|
138
|
+
const displayName = rawMcpRowValue(row, "display_name");
|
|
139
|
+
const storageName = rawMcpRowValue(row, "name");
|
|
140
|
+
const name =
|
|
141
|
+
typeof displayName === "string" && displayName.length > 0
|
|
142
|
+
? displayName
|
|
143
|
+
: typeof storageName === "string" && storageName.length > 0
|
|
144
|
+
? storageName
|
|
145
|
+
: null;
|
|
146
|
+
if (!name) return null;
|
|
147
|
+
|
|
148
|
+
let configScope;
|
|
149
|
+
try {
|
|
150
|
+
configScope = normalizeMcpConfigScope(
|
|
151
|
+
rawMcpRowValue(row, "config_scope") || "user",
|
|
152
|
+
);
|
|
153
|
+
} catch {
|
|
154
|
+
// An unparseable scope cannot safely be treated as the lowest tier.
|
|
155
|
+
configScope = "managed";
|
|
156
|
+
}
|
|
157
|
+
const rawProjectPath = rawMcpRowValue(row, "project_path");
|
|
158
|
+
const visibility = {
|
|
159
|
+
name,
|
|
160
|
+
configScope,
|
|
161
|
+
projectPath:
|
|
162
|
+
typeof rawProjectPath === "string" && rawProjectPath.length > 0
|
|
163
|
+
? rawProjectPath
|
|
164
|
+
: null,
|
|
165
|
+
autoConnect: rawMcpRowValue(row, "auto_connect") === 1,
|
|
166
|
+
[INVALID_MCP_CONFIG_ROW]: invalid,
|
|
167
|
+
};
|
|
168
|
+
if (invalid) {
|
|
169
|
+
Object.defineProperties(visibility, {
|
|
170
|
+
[INVALID_MCP_CONFIG_ERROR]: { value: error },
|
|
171
|
+
[INVALID_MCP_CONFIG_RAW_ROW]: { value: row },
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return visibility;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isInvalidMcpConfigRow(config) {
|
|
178
|
+
return config?.[INVALID_MCP_CONFIG_ROW] === true;
|
|
179
|
+
}
|
|
180
|
+
|
|
119
181
|
/**
|
|
120
182
|
* Default per-call timeout for HTTP MCP requests, mirroring the 30s stdio
|
|
121
183
|
* timeout so a hung/dead HTTP server can't block a request forever. Servers
|
|
@@ -1775,6 +1837,11 @@ export class MCPClient extends EventEmitter {
|
|
|
1775
1837
|
else this._reconnectors.delete(name);
|
|
1776
1838
|
}
|
|
1777
1839
|
|
|
1840
|
+
/** Return the opaque host authority used to resolve policy-bearing MCP cwd. */
|
|
1841
|
+
executionWorkspaceBinding() {
|
|
1842
|
+
return this._workspaceBinding;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1778
1845
|
async _connectionHeaders(name, config, transportKind) {
|
|
1779
1846
|
const staticHeaders = { ...(config?.headers || {}) };
|
|
1780
1847
|
if (!config?.headersHelper) return staticHeaders;
|
|
@@ -1853,7 +1920,8 @@ export class MCPClient extends EventEmitter {
|
|
|
1853
1920
|
mcpStdioExecutionAuthority: _stdioExecutionAuthority,
|
|
1854
1921
|
...untrustedSourceConfig
|
|
1855
1922
|
} = config;
|
|
1856
|
-
|
|
1923
|
+
void _stdioExecutionAuthority;
|
|
1924
|
+
let sourceConfig = approvedStdioInvocation
|
|
1857
1925
|
? { ...untrustedSourceConfig, ...approvedStdioInvocation }
|
|
1858
1926
|
: untrustedSourceConfig;
|
|
1859
1927
|
let pluginWorkspaceRoot = null;
|
|
@@ -1863,34 +1931,34 @@ export class MCPClient extends EventEmitter {
|
|
|
1863
1931
|
let stdioExecutableWorkingDirectory = null;
|
|
1864
1932
|
let stdioExecutableIdentityDigest = null;
|
|
1865
1933
|
let stdioCapsuleSandboxExecutionContract = null;
|
|
1934
|
+
let stdioTrustedWorkingDirectory = null;
|
|
1866
1935
|
let runtimeConfig = sourceConfig;
|
|
1867
1936
|
if (transportKind === "stdio") {
|
|
1868
|
-
const
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
: null;
|
|
1880
|
-
if (
|
|
1881
|
-
isPlugin &&
|
|
1882
|
-
sourceConfig.sandboxPolicy?.requiredBoundaries?.length > 0 &&
|
|
1883
|
-
!pluginWorkspaceRoot
|
|
1884
|
-
) {
|
|
1885
|
-
throw new Error(
|
|
1886
|
-
`plugin MCP server "${name}" is missing its trusted workspace authority`,
|
|
1937
|
+
const sandboxContext = _deps.resolveMcpStdioSandboxContext({
|
|
1938
|
+
serverName: name,
|
|
1939
|
+
config: sourceConfig,
|
|
1940
|
+
workspaceBinding: this._workspaceBinding,
|
|
1941
|
+
});
|
|
1942
|
+
pluginWorkspaceRoot = sandboxContext.pluginWorkspaceRoot;
|
|
1943
|
+
stdioTrustedWorkingDirectory = sandboxContext.workingDirectory;
|
|
1944
|
+
if (sandboxContext.sandboxPolicy && sourceConfig.cwd) {
|
|
1945
|
+
_deps.verifyMcpStdioApprovedWorkingDirectory(
|
|
1946
|
+
stdioExecutionApproval,
|
|
1947
|
+
stdioTrustedWorkingDirectory,
|
|
1887
1948
|
);
|
|
1888
1949
|
}
|
|
1950
|
+
if (sandboxContext.sandboxPolicy) {
|
|
1951
|
+
sourceConfig = {
|
|
1952
|
+
...sourceConfig,
|
|
1953
|
+
sandboxPolicy: sandboxContext.sandboxPolicy,
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1889
1956
|
const prepared = _deps.prepareMcpStdioExecutableIdentity({
|
|
1890
1957
|
serverName: name,
|
|
1891
1958
|
config: sourceConfig,
|
|
1892
1959
|
approval: stdioExecutionApproval,
|
|
1893
|
-
cwd:
|
|
1960
|
+
cwd:
|
|
1961
|
+
stdioTrustedWorkingDirectory || pluginWorkspaceRoot || process.cwd(),
|
|
1894
1962
|
env: { ...process.env, ...this._agentIdentityEnv() },
|
|
1895
1963
|
});
|
|
1896
1964
|
runtimeConfig = {
|
|
@@ -2306,6 +2374,7 @@ export class MCPClient extends EventEmitter {
|
|
|
2306
2374
|
const spawnOptions = {
|
|
2307
2375
|
cwd:
|
|
2308
2376
|
stdioExecutableWorkingDirectory ||
|
|
2377
|
+
stdioTrustedWorkingDirectory ||
|
|
2309
2378
|
pluginWorkspaceRoot ||
|
|
2310
2379
|
process.cwd(),
|
|
2311
2380
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -2353,6 +2422,7 @@ export class MCPClient extends EventEmitter {
|
|
|
2353
2422
|
config.args || [],
|
|
2354
2423
|
{ ...spawnOptions, detached: false },
|
|
2355
2424
|
stdioExecutableWorkingDirectory ||
|
|
2425
|
+
stdioTrustedWorkingDirectory ||
|
|
2356
2426
|
pluginWorkspaceRoot ||
|
|
2357
2427
|
process.cwd(),
|
|
2358
2428
|
)
|
|
@@ -4933,6 +5003,8 @@ export class MCPServerConfig {
|
|
|
4933
5003
|
args TEXT DEFAULT '[]',
|
|
4934
5004
|
env TEXT DEFAULT '{}',
|
|
4935
5005
|
runtime_kind TEXT,
|
|
5006
|
+
cwd TEXT,
|
|
5007
|
+
sandbox_policy TEXT,
|
|
4936
5008
|
auto_connect INTEGER DEFAULT 0,
|
|
4937
5009
|
url TEXT,
|
|
4938
5010
|
transport TEXT DEFAULT 'stdio',
|
|
@@ -4949,9 +5021,9 @@ export class MCPServerConfig {
|
|
|
4949
5021
|
}
|
|
4950
5022
|
|
|
4951
5023
|
/**
|
|
4952
|
-
* Idempotent schema migration for databases that predate
|
|
4953
|
-
* columns. Silently skipped on mocks or anything
|
|
4954
|
-
* `pragma()`.
|
|
5024
|
+
* Idempotent schema migration for databases that predate runtime, transport,
|
|
5025
|
+
* sandbox-policy, and scope columns. Silently skipped on mocks or anything
|
|
5026
|
+
* that doesn't expose `pragma()`.
|
|
4955
5027
|
*/
|
|
4956
5028
|
_migrateSchema() {
|
|
4957
5029
|
try {
|
|
@@ -4967,6 +5039,12 @@ export class MCPServerConfig {
|
|
|
4967
5039
|
if (!cols.has("runtime_kind")) {
|
|
4968
5040
|
this.db.exec("ALTER TABLE mcp_servers ADD COLUMN runtime_kind TEXT");
|
|
4969
5041
|
}
|
|
5042
|
+
if (!cols.has("cwd")) {
|
|
5043
|
+
this.db.exec("ALTER TABLE mcp_servers ADD COLUMN cwd TEXT");
|
|
5044
|
+
}
|
|
5045
|
+
if (!cols.has("sandbox_policy")) {
|
|
5046
|
+
this.db.exec("ALTER TABLE mcp_servers ADD COLUMN sandbox_policy TEXT");
|
|
5047
|
+
}
|
|
4970
5048
|
if (!cols.has("transport")) {
|
|
4971
5049
|
this.db.exec(
|
|
4972
5050
|
"ALTER TABLE mcp_servers ADD COLUMN transport TEXT DEFAULT 'stdio'",
|
|
@@ -5000,6 +5078,13 @@ export class MCPServerConfig {
|
|
|
5000
5078
|
}
|
|
5001
5079
|
|
|
5002
5080
|
add(name, config) {
|
|
5081
|
+
if (isProxy(config)) {
|
|
5082
|
+
const error = new TypeError(
|
|
5083
|
+
`MCP server "${name}" config must not be a Proxy`,
|
|
5084
|
+
);
|
|
5085
|
+
error.code = MCP_SANDBOX_POLICY_INVALID_CODE;
|
|
5086
|
+
throw error;
|
|
5087
|
+
}
|
|
5003
5088
|
const url = config.url || null;
|
|
5004
5089
|
const transport =
|
|
5005
5090
|
config.transport || (url ? inferTransport({ url }) : "stdio");
|
|
@@ -5027,6 +5112,17 @@ export class MCPServerConfig {
|
|
|
5027
5112
|
typeof config.headersHelper === "string" && config.headersHelper.trim()
|
|
5028
5113
|
? config.headersHelper
|
|
5029
5114
|
: null;
|
|
5115
|
+
const sandboxPolicyDescriptor = Object.getOwnPropertyDescriptor(
|
|
5116
|
+
config,
|
|
5117
|
+
"sandboxPolicy",
|
|
5118
|
+
);
|
|
5119
|
+
if (sandboxPolicyDescriptor && !("value" in sandboxPolicyDescriptor)) {
|
|
5120
|
+
const error = new TypeError(
|
|
5121
|
+
`MCP server "${name}" sandboxPolicy must be an own data property`,
|
|
5122
|
+
);
|
|
5123
|
+
error.code = MCP_SANDBOX_POLICY_INVALID_CODE;
|
|
5124
|
+
throw error;
|
|
5125
|
+
}
|
|
5030
5126
|
// The legacy table uses `name` as its primary key. Encode workspace-bound
|
|
5031
5127
|
// identities in that key so `foo` can coexist at local/project/user scope
|
|
5032
5128
|
// while retaining the old key for user rows and backwards compatibility.
|
|
@@ -5034,9 +5130,32 @@ export class MCPServerConfig {
|
|
|
5034
5130
|
configScope === "user"
|
|
5035
5131
|
? name
|
|
5036
5132
|
: `${configScope}:${Buffer.from(projectPath || "managed", "utf8").toString("base64url")}:${name}`;
|
|
5133
|
+
const existingRow = this.db
|
|
5134
|
+
.prepare("SELECT * FROM mcp_servers WHERE name = ?")
|
|
5135
|
+
.get(storageName);
|
|
5136
|
+
const cwdUpdate = readMcpStdioCwd(config, {
|
|
5137
|
+
label: `MCP server "${name}"`,
|
|
5138
|
+
});
|
|
5139
|
+
const storedCwd = existingRow
|
|
5140
|
+
? readMcpStdioCwd(existingRow, {
|
|
5141
|
+
label: `Stored MCP server "${name}"`,
|
|
5142
|
+
}).cwd
|
|
5143
|
+
: null;
|
|
5144
|
+
let workingDirectory = storedCwd;
|
|
5145
|
+
if (cwdUpdate.present) workingDirectory = cwdUpdate.cwd;
|
|
5146
|
+
let sandboxPolicy;
|
|
5147
|
+
if (sandboxPolicyDescriptor) {
|
|
5148
|
+
sandboxPolicy = normalizeMcpSandboxPolicy(sandboxPolicyDescriptor.value, {
|
|
5149
|
+
label: `MCP server "${name}" sandboxPolicy`,
|
|
5150
|
+
});
|
|
5151
|
+
} else {
|
|
5152
|
+
sandboxPolicy = existingRow
|
|
5153
|
+
? this._rowToConfig(existingRow).sandboxPolicy || null
|
|
5154
|
+
: null;
|
|
5155
|
+
}
|
|
5037
5156
|
this.db
|
|
5038
5157
|
.prepare(
|
|
5039
|
-
"INSERT OR REPLACE INTO mcp_servers (name, command, args, env, runtime_kind, auto_connect, url, transport, headers, headers_helper, config_scope, config_source, project_path, display_name, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))",
|
|
5158
|
+
"INSERT OR REPLACE INTO mcp_servers (name, command, args, env, runtime_kind, cwd, sandbox_policy, auto_connect, url, transport, headers, headers_helper, config_scope, config_source, project_path, display_name, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))",
|
|
5040
5159
|
)
|
|
5041
5160
|
.run(
|
|
5042
5161
|
storageName,
|
|
@@ -5044,6 +5163,8 @@ export class MCPServerConfig {
|
|
|
5044
5163
|
JSON.stringify(config.args || []),
|
|
5045
5164
|
JSON.stringify(config.env || {}),
|
|
5046
5165
|
config.runtimeKind || null,
|
|
5166
|
+
workingDirectory,
|
|
5167
|
+
sandboxPolicy ? JSON.stringify(sandboxPolicy) : null,
|
|
5047
5168
|
config.autoConnect ? 1 : 0,
|
|
5048
5169
|
url,
|
|
5049
5170
|
transport,
|
|
@@ -5080,6 +5201,26 @@ export class MCPServerConfig {
|
|
|
5080
5201
|
}
|
|
5081
5202
|
|
|
5082
5203
|
_rowToConfig(row) {
|
|
5204
|
+
const { cwd } = readMcpStdioCwd(row, {
|
|
5205
|
+
label: `Stored MCP server "${row?.display_name || row?.name || "unknown"}"`,
|
|
5206
|
+
});
|
|
5207
|
+
let sandboxPolicy = null;
|
|
5208
|
+
if (row.sandbox_policy != null && row.sandbox_policy !== "") {
|
|
5209
|
+
let parsedPolicy;
|
|
5210
|
+
try {
|
|
5211
|
+
parsedPolicy = JSON.parse(row.sandbox_policy);
|
|
5212
|
+
} catch (cause) {
|
|
5213
|
+
const error = new TypeError(
|
|
5214
|
+
`Stored MCP sandbox policy for "${row.display_name || row.name}" is not valid JSON`,
|
|
5215
|
+
{ cause },
|
|
5216
|
+
);
|
|
5217
|
+
error.code = MCP_SANDBOX_POLICY_INVALID_CODE;
|
|
5218
|
+
throw error;
|
|
5219
|
+
}
|
|
5220
|
+
sandboxPolicy = normalizeMcpSandboxPolicy(parsedPolicy, {
|
|
5221
|
+
label: `Stored MCP sandbox policy for "${row.display_name || row.name}"`,
|
|
5222
|
+
});
|
|
5223
|
+
}
|
|
5083
5224
|
const config = {
|
|
5084
5225
|
name: row.display_name || row.name,
|
|
5085
5226
|
command: row.command || null,
|
|
@@ -5089,6 +5230,8 @@ export class MCPServerConfig {
|
|
|
5089
5230
|
args: safeJsonParse(row.args, []),
|
|
5090
5231
|
env: safeJsonParse(row.env, {}),
|
|
5091
5232
|
runtimeKind: row.runtime_kind || null,
|
|
5233
|
+
...(cwd ? { cwd } : {}),
|
|
5234
|
+
...(sandboxPolicy ? { sandboxPolicy } : {}),
|
|
5092
5235
|
autoConnect: row.auto_connect === 1,
|
|
5093
5236
|
url: row.url || null,
|
|
5094
5237
|
transport:
|
|
@@ -5116,39 +5259,96 @@ export class MCPServerConfig {
|
|
|
5116
5259
|
return this.list(options).find((config) => config.name === name) || null;
|
|
5117
5260
|
}
|
|
5118
5261
|
|
|
5262
|
+
_rowsToConfigs(rows, options = {}) {
|
|
5263
|
+
const configs = [];
|
|
5264
|
+
for (const row of rows) {
|
|
5265
|
+
const visibility = mcpRowVisibility(row);
|
|
5266
|
+
if (!visibility || !this._isVisible(visibility, options)) continue;
|
|
5267
|
+
let config;
|
|
5268
|
+
try {
|
|
5269
|
+
config = this._rowToConfig(row);
|
|
5270
|
+
} catch (error) {
|
|
5271
|
+
const invalid = mcpRowVisibility(row, { invalid: true, error });
|
|
5272
|
+
if (invalid) configs.push(invalid);
|
|
5273
|
+
continue;
|
|
5274
|
+
}
|
|
5275
|
+
configs.push(config);
|
|
5276
|
+
}
|
|
5277
|
+
return configs;
|
|
5278
|
+
}
|
|
5279
|
+
|
|
5280
|
+
_publicConfigs(configs) {
|
|
5281
|
+
return configs.filter((config) => !isInvalidMcpConfigRow(config));
|
|
5282
|
+
}
|
|
5283
|
+
|
|
5284
|
+
_reportInvalidConfigs(configs, options = {}) {
|
|
5285
|
+
if (typeof options.onInvalidRow !== "function") return;
|
|
5286
|
+
for (const config of configs) {
|
|
5287
|
+
if (!isInvalidMcpConfigRow(config)) continue;
|
|
5288
|
+
try {
|
|
5289
|
+
options.onInvalidRow(
|
|
5290
|
+
config[INVALID_MCP_CONFIG_ERROR],
|
|
5291
|
+
config[INVALID_MCP_CONFIG_RAW_ROW],
|
|
5292
|
+
);
|
|
5293
|
+
} catch {
|
|
5294
|
+
// Diagnostics must not turn one corrupt row into a global outage.
|
|
5295
|
+
}
|
|
5296
|
+
}
|
|
5297
|
+
}
|
|
5298
|
+
|
|
5119
5299
|
list(options = {}) {
|
|
5120
5300
|
const rows = this.db
|
|
5121
5301
|
.prepare("SELECT * FROM mcp_servers ORDER BY name")
|
|
5122
5302
|
.all();
|
|
5123
|
-
const visible = rows
|
|
5124
|
-
|
|
5125
|
-
.
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
: this._effectiveByName(visible);
|
|
5303
|
+
const visible = this._rowsToConfigs(rows, options);
|
|
5304
|
+
const selected =
|
|
5305
|
+
options.allScopes === true ? visible : this._effectiveByName(visible);
|
|
5306
|
+
this._reportInvalidConfigs(selected, options);
|
|
5307
|
+
return this._publicConfigs(selected);
|
|
5129
5308
|
}
|
|
5130
5309
|
|
|
5131
5310
|
getAutoConnect(options = {}) {
|
|
5132
5311
|
const rows = this.db
|
|
5133
|
-
.prepare("SELECT * FROM mcp_servers
|
|
5134
|
-
.all(
|
|
5135
|
-
const visible = rows
|
|
5136
|
-
|
|
5137
|
-
.
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5312
|
+
.prepare("SELECT * FROM mcp_servers ORDER BY name")
|
|
5313
|
+
.all();
|
|
5314
|
+
const visible = this._rowsToConfigs(rows, options);
|
|
5315
|
+
const selected =
|
|
5316
|
+
options.allScopes === true ? visible : this._effectiveByName(visible);
|
|
5317
|
+
this._reportInvalidConfigs(selected, options);
|
|
5318
|
+
return this._publicConfigs(selected).filter(
|
|
5319
|
+
(config) => config.autoConnect === true,
|
|
5320
|
+
);
|
|
5141
5321
|
}
|
|
5142
5322
|
|
|
5143
5323
|
_effectiveByName(configs) {
|
|
5144
5324
|
const priority = { managed: 4, local: 3, project: 2, user: 1 };
|
|
5325
|
+
const specificity = (config) => {
|
|
5326
|
+
if (config.configScope !== "local" && config.configScope !== "project") {
|
|
5327
|
+
return 0;
|
|
5328
|
+
}
|
|
5329
|
+
if (typeof config.projectPath !== "string") return 0;
|
|
5330
|
+
try {
|
|
5331
|
+
return pathToFileURL(config.projectPath).href.length;
|
|
5332
|
+
} catch {
|
|
5333
|
+
return 0;
|
|
5334
|
+
}
|
|
5335
|
+
};
|
|
5145
5336
|
const selected = new Map();
|
|
5146
5337
|
for (const config of configs) {
|
|
5147
5338
|
const current = selected.get(config.name);
|
|
5339
|
+
const candidatePriority = priority[config.configScope] || 0;
|
|
5340
|
+
const currentPriority = current ? priority[current.configScope] || 0 : -1;
|
|
5341
|
+
const candidateSpecificity = specificity(config);
|
|
5342
|
+
const currentSpecificity = current ? specificity(current) : -1;
|
|
5148
5343
|
if (
|
|
5149
5344
|
!current ||
|
|
5150
|
-
|
|
5151
|
-
|
|
5345
|
+
candidatePriority > currentPriority ||
|
|
5346
|
+
(candidatePriority === currentPriority &&
|
|
5347
|
+
candidateSpecificity > currentSpecificity) ||
|
|
5348
|
+
(candidatePriority === currentPriority &&
|
|
5349
|
+
candidateSpecificity === currentSpecificity &&
|
|
5350
|
+
isInvalidMcpConfigRow(config) &&
|
|
5351
|
+
!isInvalidMcpConfigRow(current))
|
|
5152
5352
|
) {
|
|
5153
5353
|
selected.set(config.name, config);
|
|
5154
5354
|
}
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
existsSync,
|
|
12
|
+
appendFileSync,
|
|
12
13
|
lstatSync,
|
|
14
|
+
readFileSync,
|
|
13
15
|
realpathSync,
|
|
14
16
|
rmdirSync,
|
|
15
17
|
symlinkSync,
|
|
@@ -97,17 +99,36 @@ function gitRepositoryIdentity(repoDir) {
|
|
|
97
99
|
const absoluteCommonDir = isAbsolute(commonDir)
|
|
98
100
|
? commonDir
|
|
99
101
|
: resolve(repoDir, commonDir);
|
|
102
|
+
const commonDirPath = canonicalExistingPrefixPath(absoluteCommonDir);
|
|
100
103
|
return {
|
|
101
104
|
topLevel,
|
|
102
105
|
topLevelIdentity: normalizeFilesystemPath(
|
|
103
106
|
canonicalExistingPrefixPath(topLevel),
|
|
104
107
|
),
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
),
|
|
108
|
+
commonDirPath,
|
|
109
|
+
commonDirIdentity: normalizeFilesystemPath(commonDirPath),
|
|
108
110
|
};
|
|
109
111
|
}
|
|
110
112
|
|
|
113
|
+
function ensureManagedWorktreeExcluded(context) {
|
|
114
|
+
const infoDir = resolve(context.repository.commonDirPath, "info");
|
|
115
|
+
const excludeFile = resolve(infoDir, "exclude");
|
|
116
|
+
mkdirSync(infoDir, { recursive: true });
|
|
117
|
+
try {
|
|
118
|
+
assertNotSymlink(infoDir, "git info directory");
|
|
119
|
+
assertNotSymlink(excludeFile, "git exclude file");
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (error?.code !== "ENOENT") throw error;
|
|
122
|
+
}
|
|
123
|
+
const pattern = `/${WORKTREE_DIR}/`;
|
|
124
|
+
const current = existsSync(excludeFile)
|
|
125
|
+
? readFileSync(excludeFile, "utf8")
|
|
126
|
+
: "";
|
|
127
|
+
if (current.split(/\r?\n/).includes(pattern)) return;
|
|
128
|
+
const separator = current.length > 0 && !/\r?\n$/.test(current) ? "\n" : "";
|
|
129
|
+
appendFileSync(excludeFile, `${separator}${pattern}\n`, "utf8");
|
|
130
|
+
}
|
|
131
|
+
|
|
111
132
|
function assertSameGitRepository(repoDir, expectedCommonDir, expectedRoot) {
|
|
112
133
|
const identity = gitRepositoryIdentity(repoDir);
|
|
113
134
|
if (
|
|
@@ -381,6 +402,8 @@ export function createWorktree(repoDir, branchName, baseBranch, options = {}) {
|
|
|
381
402
|
throw new Error("Not a git repository");
|
|
382
403
|
}
|
|
383
404
|
|
|
405
|
+
const context = repositoryManagementContext(repoDir);
|
|
406
|
+
ensureManagedWorktreeExcluded(context);
|
|
384
407
|
const worktreePath = assertManagedWorktreePath(
|
|
385
408
|
repoDir,
|
|
386
409
|
managedWorktreePath(repoDir, branchName),
|
|
@@ -164,18 +164,57 @@ function removeVerifiedAgentWorktree(info, deps, expectedBranchOid) {
|
|
|
164
164
|
export function setupAgentWorktree({
|
|
165
165
|
cwd = process.cwd(),
|
|
166
166
|
now = new Date(),
|
|
167
|
+
requireCleanSource = false,
|
|
167
168
|
deps = _deps,
|
|
168
169
|
} = {}) {
|
|
169
170
|
const repoRoot = findProjectRoot(cwd);
|
|
170
171
|
if (!repoRoot) {
|
|
171
172
|
throw new Error("--worktree requires running inside a git repository");
|
|
172
173
|
}
|
|
174
|
+
if (requireCleanSource) {
|
|
175
|
+
const status = git(
|
|
176
|
+
[
|
|
177
|
+
"status",
|
|
178
|
+
"--porcelain=v1",
|
|
179
|
+
"--untracked-files=all",
|
|
180
|
+
"--ignore-submodules=none",
|
|
181
|
+
"--",
|
|
182
|
+
".",
|
|
183
|
+
":(exclude).worktrees",
|
|
184
|
+
],
|
|
185
|
+
repoRoot,
|
|
186
|
+
deps,
|
|
187
|
+
).trim();
|
|
188
|
+
if (status) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
"Background worktree isolation requires a clean source checkout; commit/stash the current changes or pass --no-worktree explicitly.",
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
173
194
|
const stamp = now.toISOString().slice(0, 19).replace(/[-:T]/g, "");
|
|
174
195
|
const suffix = Math.random().toString(36).slice(2, 6);
|
|
175
196
|
const branch = `cc-agent-${stamp}-${suffix}`;
|
|
176
197
|
const info = (deps.createWorktree || createWorktree)(repoRoot, branch);
|
|
177
|
-
|
|
178
|
-
|
|
198
|
+
try {
|
|
199
|
+
const baseSha = git(["rev-parse", "HEAD"], info.path, deps).trim();
|
|
200
|
+
return { ...info, repoRoot, baseSha };
|
|
201
|
+
} catch (error) {
|
|
202
|
+
// createWorktree is transactional internally, but ownership has already
|
|
203
|
+
// transferred to this wrapper when the follow-up base-SHA query runs. Roll
|
|
204
|
+
// that partial setup back here so callers never receive `_worktree = null`
|
|
205
|
+
// while a branch and linked checkout have already been created.
|
|
206
|
+
try {
|
|
207
|
+
(deps.removeWorktree || removeWorktree)(repoRoot, info.path, {
|
|
208
|
+
deleteBranch: true,
|
|
209
|
+
branchName: branch,
|
|
210
|
+
force: false,
|
|
211
|
+
});
|
|
212
|
+
} catch (cleanupError) {
|
|
213
|
+
error.retainedWorktree = { ...info, repoRoot, branch };
|
|
214
|
+
error.worktreeCleanupError = cleanupError;
|
|
215
|
+
}
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
179
218
|
}
|
|
180
219
|
|
|
181
220
|
/**
|