chainlesschain 0.163.1 → 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.
Files changed (191) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-Cn7quRgL.js → AIOps-DIY6KxV-.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-YqMX3ueH.js → ActionButton-BXH0muPt.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-Bxmy1b7Y.js → Analytics-D39w8vmg.js} +2 -2
  5. package/src/assets/web-panel/assets/{AppLayout-DBOw69jE.js → AppLayout-CUU1zvLs.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-mq1nyzN1.js → Artifacts-D8azHO3T.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-mMiKtZ0D.js → Audit-D0tAwXTg.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-WBUjpYVh.js → BackgroundAgents-C29mhJll.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-c25OoJgs.js → Backup-D6Aa1Gro.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-BbfWhZ4-.js → BaseInput-B1uoHbPQ.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-CVVzsTwU.js → Chat-Bs4DLOR5.js} +5 -5
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CVCjZJ04.js → ChatBubbleRenderer-DUYIBSle.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-C9o2HK-1.js → Checkbox-CNcBPEa4.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-B7X5LfKc.js → Codegen-CIY89763.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-jpPj2Pur.js → Col-CsPYFmgo.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-Dti6bHxN.js → Community-BBocEge4.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-S4ZsLt3W.js → Compact-TQ8Ux0Lf.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-Bge0sQ3O.js → Compliance-DfBsZTft.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-C4XyzVI7.js → Cowork-Cg-I6FVH.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-CPR0v2xy.js → Cron-DTQzfRTp.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DAX8lDaj.js → Crosschain-DGxEKGpR.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-fBHPa0QH.js → DID-DLZ1RDSr.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-vvaUTafU.js → Dashboard-XneICKNI.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-DyaQM63v.js → Dropdown-BRU00J1-.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-CAF0wiU2.js → EmailListRenderer-BzCbJuAy.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-ia-WzDRs.js → FamilyGuardDashboard-D7BRyNBG.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-Cz3gHoT5.js → Federation-D9DeFIhW.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-CJQDJr4Q.js → FormItemContext-6rAtA9Ko.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-B05BZlQp.js → GenericCardRenderer-8Hqy80pU.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-DT0YcQnI.js → Git-Cmvkj1rt.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-Bqbl6m5Q.js → Governance-BRiQUjjw.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-BdebLR0N.js → Inference-CKN_EzVf.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-M36b8YYV.js → KnowledgeGraph-BCDHwpwQ.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-5LN_XL43.js → Logs-DK9ovhaB.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-BCb1Dm8l.js → MarkdownRenderer-D53pHiHy.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-BkIin-QY.js → Marketplace-BILPGakp.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-Bkppx8Uj.js → McpTools-DTVCOr9-.js} +5 -5
  38. package/src/assets/web-panel/assets/{Memory-Qk9wg_Ib.js → Memory-Hzc1iow4.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-BsTDU3Aj.js → MobileBridge-GXfgnGYV.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-CrZW5I28.js → MobileProjects-B-3TJWsS.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-BMkmgtsA.js → Mtc-DwSJMWtK.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-6m4CC0Yn.js → MtcAudit-D2uxf-DA.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-BZ8yB7QV.js → Multisig-DG6kbKK0.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-CxitAMIO.js → NLProgramming-f5p49EDU.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-Bv0msnHu.js → Notes-FUgHyzSD.js} +4 -4
  46. package/src/assets/web-panel/assets/{NotificationSettings-DeVMqM3P.js → NotificationSettings-DCHGee7e.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-CXBSWa3R.js → OrderTableRenderer-Dm6LlIoa.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-b_G5Vs1Q.js → Organization-BawA7qAg.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-DW7riOmA.js → Overflow-D6a5wZU1.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-CtpQNYjL.js → P2P-K8WavwnA.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-Ba63yT4l.js → PdhVaultBrowser-4JAJYaqx.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-DnzyeB67.js → Permissions-BDWvi8PD.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-DbPZ7HWw.js → PersonalDataHub-DF_K4E9i.js} +3 -3
  54. package/src/assets/web-panel/assets/{Pipeline-gs_HOThJ.js → Pipeline-BYeZlesI.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-CZlQ1EjE.js → Privacy-BIHlgnIc.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-DQp__Ccb.js → ProjectInit-Cvf-6B5P.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-C7h2sOWZ.js → ProjectSettings-CnwfJrRz.js} +2 -2
  58. package/src/assets/web-panel/assets/Projects-b-jPXzF4.js +1 -0
  59. package/src/assets/web-panel/assets/Providers-CxC7JuYv.js +1 -0
  60. package/src/assets/web-panel/assets/{QrScannerModal-B_8M1-FC.js → QrScannerModal-VLf3gAfL.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-Bpycr8WD.js → QuickAsk-B5vGjRiF.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-Z4TrcNzs.js → Recommend--UW4iYs-.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-BD6TsJlS.js → RemoteSession-DMSPcfKR.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-IKxDXHEu.js → Reputation-kQXlEHmj.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-DbmR20tJ.js → Row-CxGu3pit.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-CLjA5PPf.js → RssFeed-Bi5lo8MZ.js} +2 -2
  67. package/src/assets/web-panel/assets/{Search-Bm3owbIU.js → Search-Cq_H5RTh.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-CY4RyOuq.js → Security-YFbmkaQS.js} +2 -2
  69. package/src/assets/web-panel/assets/{Services-CYO9qrd_.js → Services-DLOGdaLm.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-ClF51Go6.js → Skeleton-CteIxb3D.js} +1 -1
  71. package/src/assets/web-panel/assets/Skills-CccopaIZ.js +1 -0
  72. package/src/assets/web-panel/assets/{Sla-zqpDUxzP.js → Sla-AzfEQKX1.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-DPSi8Qco.js → SpeechSettings-ekGo5l77.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-CqABopsQ.js → SyncSettings-BmZmAgYH.js} +2 -2
  75. package/src/assets/web-panel/assets/Tasks-D7q3Z4uL.js +1 -0
  76. package/src/assets/web-panel/assets/{Templates-Dzl6f0M2.js → Templates-CX1Tgeka.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-XeZkw40a.js → Tenant-Zc59jF4i.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-CYw2YzPm.js → Terminal-CXOD7y83.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-Bc2pko4i.js → TimelineRenderer-B1aBHPBo.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-CfCenOHx.js → Tokens-zALlOq_U.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-DTz-_YqV.js → Trigger-CQfJMXaq.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-CB3tt3Z6.js → Trust-BU0RcM-w.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-y1ykmMvU.js → UkeySign-IFZ3HsbH.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-UlZ_lX0e.js → VideoEditing-BzslgW0v.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-DMBaHTha.js → Wallet-BZAcQNS6.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-Cksm-hBI.js → WebAuthn-CBK1DMi0.js} +4 -4
  87. package/src/assets/web-panel/assets/{WorkflowEditor-BhOa40sX.js → WorkflowEditor-BlMTPzO-.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-DNbeSTDO.js → chat-B51-8QVn.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-9zz8gBmW.js → colors-504RTByU.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-D7aoVv-5.js → compact-item-C-GP6IQM.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-DYg7B8nd.js → createContext-DOkIj7ID.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-kB0nCNwN.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-BNbhkcoI.js → hasIn-DN7V-_FW.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-DeD1wWA8.js → index--n-ZciV3.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Da0SysnX.js → index-7jIqHbzw.js} +1 -1
  96. package/src/assets/web-panel/assets/{index--TvoQ6UC.js → index-B2-x9bS8.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-m2pXS3W2.js → index-BFIFJ5Dr.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CzDDeZBo.js → index-B_vGU-Js.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-gNKj00Fx.js → index-Bkxhaeta.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-C8LC0nvw.js → index-By6u5IV1.js} +1 -1
  101. package/src/assets/web-panel/assets/index-Bya64sZE.js +1 -0
  102. package/src/assets/web-panel/assets/{index-B8irlIUK.js → index-C0k_Nqg9.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-B9zUawcE.js → index-C3TSohzc.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BPojutcZ.js → index-C4DV_5uA.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-ZPDbEh-L.js → index-C8UltbXK.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-jS6NASar.js → index-C9_48OzU.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-67GdrUx2.js → index-CKTfZyfU.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CYu-F-gp.js → index-CP2A226L.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Cq3l0L7C.js → index-CUhQpiYv.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DtQzs0hH.js → index-CX78k-qf.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Bdxe0nrD.js → index-CYopNjcJ.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-uc8Rz96X.js → index-Cao5DZy7.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DnTM8FWK.js → index-CfiEM64n.js} +3 -3
  114. package/src/assets/web-panel/assets/{index-DHskwmwI.js → index-ConjaJro.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CA72qB1n.js → index-CqcLo5WV.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-Dn-JFqEi.js → index-CySTJKeq.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DUWmZ059.js → index-D1phyca8.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-_-Vt1Wqk.js → index-D9LyiN-L.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DIljkSt9.js → index-DA2f1UV3.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-B9TzVW7i.js → index-DAOpQvl_.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CRKukX1W.js → index-DE-hmI2l.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DIMVe3HJ.js → index-DEMPUVa3.js} +1 -1
  123. package/src/assets/web-panel/assets/index-D_FWpDZM.js +1 -0
  124. package/src/assets/web-panel/assets/{index-FlBuITVG.js → index-DaA__-q1.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Cqy-hqD9.js → index-FcV8J_Qz.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CpEIwp4A.js → index-P2mfY27Z.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-nnmo3ipi.js → index-et5JIPHO.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-SmEIFaRm.js → index-f4Bo4KXm.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DMrqEsOS.js → index-kAqQXfP6.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CV06YdPO.js → index-kt_5rfcZ.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CN6-OEgS.js → index-so8eIIxF.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-BRd1zkLB.js → index-yYTQZypF.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-D0mTMUGz.js → initDefaultProps-CPYEIdpV.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-DK16p4-o.js → motion-Cjim9pxs.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-DalC20HC.js → move-DuU-cgz3.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-B5RusszF.js → mtc-parser-DJHNAkta.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-C9Mhitht.js → omit-DSQBgdBj.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-51xQLgwM.js → pickAttrs-CTmT_j5m.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-CJzIqp8u.js → placementArrow-Lfm6v-r4.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-HQvC19u4.js → responsiveObserve-CgrcPcDi.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-C6hhabOp.js → slide-CuE3110i.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-Q1r48-oc.js → statusUtils-DiK5nbpf.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-UakVWrHs.js → styleChecker-DPEJdQaZ.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-Dmv94cxx.js → useFlexGapSupport-CnnodGmp.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-DrxOnt_V.js → useFs-DWF84Yav.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-BVf2M-Hg.js → usePersonalDataHub-a5b5Dw2f.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-BzzW9qKH.js → vnode-DAluzXOq.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-C1ZvMgAZ.js → zoom-Dokg7uAZ.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/command-help-index.json +1 -1
  151. package/src/commands/agenda.js +6 -5
  152. package/src/commands/agent.js +237 -75
  153. package/src/commands/agents.js +6 -0
  154. package/src/commands/background-session.js +11 -1
  155. package/src/commands/mcp.js +245 -30
  156. package/src/data/changelog.json +28 -2
  157. package/src/harness/mcp-client.js +256 -43
  158. package/src/harness/worktree-isolator.js +26 -3
  159. package/src/lib/agent-worktree.js +41 -2
  160. package/src/lib/background-agent-supervisor.js +790 -186
  161. package/src/lib/background-command-argv.js +318 -0
  162. package/src/lib/background-launch-profile.js +1 -0
  163. package/src/lib/background-phase-reporter.js +22 -2
  164. package/src/lib/background-worktree-policy.js +109 -0
  165. package/src/lib/cowork-mcp-tools.js +52 -6
  166. package/src/lib/cowork-task-runner.js +1 -0
  167. package/src/lib/cowork-task-templates.js +1 -0
  168. package/src/lib/mcp-headers-helper.js +76 -8
  169. package/src/lib/mcp-sandbox-policy.js +155 -0
  170. package/src/lib/mcp-stdio-executable-identity.js +75 -11
  171. package/src/lib/mcp-stdio-execution-authority.js +168 -0
  172. package/src/lib/mcp-stdio-workspace-authority.js +252 -0
  173. package/src/lib/plugin-runtime/sandbox-policy.js +42 -2
  174. package/src/lib/process-execution-broker/index.js +495 -126
  175. package/src/lib/process-execution-broker/platform-sandbox.js +1424 -189
  176. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  177. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  178. package/src/lib/process-execution-broker/windows-sandbox.cs +5 -4
  179. package/src/lib/process-tree-termination.js +17 -1
  180. package/src/lib/skill-mcp.js +74 -3
  181. package/src/lib/web-fetch.js +4 -4
  182. package/src/runtime/agent-runtime.js +61 -8
  183. package/src/runtime/mcp-config.js +238 -28
  184. package/src/workers/background-agent-worker.js +492 -71
  185. package/src/assets/web-panel/assets/Projects-DdCpNRNt.js +0 -1
  186. package/src/assets/web-panel/assets/Providers-CyvtUkUf.js +0 -1
  187. package/src/assets/web-panel/assets/Skills-CCCEmSf5.js +0 -1
  188. package/src/assets/web-panel/assets/Tasks-4dbAJng4.js +0 -1
  189. package/src/assets/web-panel/assets/devWarning-uadaGF_y.js +0 -1
  190. package/src/assets/web-panel/assets/index-C7zhie9M.js +0 -1
  191. package/src/assets/web-panel/assets/index-Cmbg0Wbr.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
- const sourceConfig = approvedStdioInvocation
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 isPlugin = sourceConfig.origin === "plugin:mcp";
1869
- pluginWorkspaceRoot = isPlugin
1870
- ? resolvePluginWorkspaceAuthority(
1871
- sourceConfig.pluginWorkspaceAuthority,
1872
- {
1873
- origin: sourceConfig.origin,
1874
- pluginId: sourceConfig.pluginId,
1875
- pluginVersion: sourceConfig.pluginVersion,
1876
- pluginSource: sourceConfig.pluginSource,
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: pluginWorkspaceRoot || process.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,13 +2422,21 @@ 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
  )
2359
2429
  : null);
2430
+ const capsuleUsesLinuxProcessTreeSandbox =
2431
+ Boolean(stdioCapsuleSandboxExecutionContract) &&
2432
+ process.platform === "linux" &&
2433
+ sourceConfig.sandboxPolicy?.requiredBoundaries?.some((boundary) =>
2434
+ ["filesystem", "network", "process-tree"].includes(boundary),
2435
+ );
2360
2436
  const capsuleUsesPosixProcessGroup =
2361
2437
  Boolean(stdioCapsuleSandboxExecutionContract) &&
2362
- process.platform !== "win32";
2438
+ process.platform !== "win32" &&
2439
+ !capsuleUsesLinuxProcessTreeSandbox;
2363
2440
  const launchOptions = {
2364
2441
  ...spawnOptions,
2365
2442
  detached:
@@ -4925,6 +5002,9 @@ export class MCPServerConfig {
4925
5002
  command TEXT,
4926
5003
  args TEXT DEFAULT '[]',
4927
5004
  env TEXT DEFAULT '{}',
5005
+ runtime_kind TEXT,
5006
+ cwd TEXT,
5007
+ sandbox_policy TEXT,
4928
5008
  auto_connect INTEGER DEFAULT 0,
4929
5009
  url TEXT,
4930
5010
  transport TEXT DEFAULT 'stdio',
@@ -4941,9 +5021,9 @@ export class MCPServerConfig {
4941
5021
  }
4942
5022
 
4943
5023
  /**
4944
- * Idempotent schema migration for databases that predate url/transport
4945
- * columns. Silently skipped on mocks or anything that doesn't expose
4946
- * `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()`.
4947
5027
  */
4948
5028
  _migrateSchema() {
4949
5029
  try {
@@ -4956,6 +5036,15 @@ export class MCPServerConfig {
4956
5036
  if (!cols.has("url")) {
4957
5037
  this.db.exec("ALTER TABLE mcp_servers ADD COLUMN url TEXT");
4958
5038
  }
5039
+ if (!cols.has("runtime_kind")) {
5040
+ this.db.exec("ALTER TABLE mcp_servers ADD COLUMN runtime_kind TEXT");
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
+ }
4959
5048
  if (!cols.has("transport")) {
4960
5049
  this.db.exec(
4961
5050
  "ALTER TABLE mcp_servers ADD COLUMN transport TEXT DEFAULT 'stdio'",
@@ -4989,6 +5078,13 @@ export class MCPServerConfig {
4989
5078
  }
4990
5079
 
4991
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
+ }
4992
5088
  const url = config.url || null;
4993
5089
  const transport =
4994
5090
  config.transport || (url ? inferTransport({ url }) : "stdio");
@@ -5016,6 +5112,17 @@ export class MCPServerConfig {
5016
5112
  typeof config.headersHelper === "string" && config.headersHelper.trim()
5017
5113
  ? config.headersHelper
5018
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
+ }
5019
5126
  // The legacy table uses `name` as its primary key. Encode workspace-bound
5020
5127
  // identities in that key so `foo` can coexist at local/project/user scope
5021
5128
  // while retaining the old key for user rows and backwards compatibility.
@@ -5023,15 +5130,41 @@ export class MCPServerConfig {
5023
5130
  configScope === "user"
5024
5131
  ? name
5025
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
+ }
5026
5156
  this.db
5027
5157
  .prepare(
5028
- "INSERT OR REPLACE INTO mcp_servers (name, command, args, env, 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'))",
5029
5159
  )
5030
5160
  .run(
5031
5161
  storageName,
5032
5162
  config.command || null,
5033
5163
  JSON.stringify(config.args || []),
5034
5164
  JSON.stringify(config.env || {}),
5165
+ config.runtimeKind || null,
5166
+ workingDirectory,
5167
+ sandboxPolicy ? JSON.stringify(sandboxPolicy) : null,
5035
5168
  config.autoConnect ? 1 : 0,
5036
5169
  url,
5037
5170
  transport,
@@ -5068,6 +5201,26 @@ export class MCPServerConfig {
5068
5201
  }
5069
5202
 
5070
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
+ }
5071
5224
  const config = {
5072
5225
  name: row.display_name || row.name,
5073
5226
  command: row.command || null,
@@ -5076,6 +5229,9 @@ export class MCPServerConfig {
5076
5229
  // MCP server list (`|| "[]"` only guards NULL, not a corrupt non-empty string).
5077
5230
  args: safeJsonParse(row.args, []),
5078
5231
  env: safeJsonParse(row.env, {}),
5232
+ runtimeKind: row.runtime_kind || null,
5233
+ ...(cwd ? { cwd } : {}),
5234
+ ...(sandboxPolicy ? { sandboxPolicy } : {}),
5079
5235
  autoConnect: row.auto_connect === 1,
5080
5236
  url: row.url || null,
5081
5237
  transport:
@@ -5103,39 +5259,96 @@ export class MCPServerConfig {
5103
5259
  return this.list(options).find((config) => config.name === name) || null;
5104
5260
  }
5105
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
+
5106
5299
  list(options = {}) {
5107
5300
  const rows = this.db
5108
5301
  .prepare("SELECT * FROM mcp_servers ORDER BY name")
5109
5302
  .all();
5110
- const visible = rows
5111
- .map((row) => this._rowToConfig(row))
5112
- .filter((config) => this._isVisible(config, options));
5113
- return options.allScopes === true || options.scope
5114
- ? visible
5115
- : 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);
5116
5308
  }
5117
5309
 
5118
5310
  getAutoConnect(options = {}) {
5119
5311
  const rows = this.db
5120
- .prepare("SELECT * FROM mcp_servers WHERE auto_connect = ? ORDER BY name")
5121
- .all(1);
5122
- const visible = rows
5123
- .map((row) => this._rowToConfig(row))
5124
- .filter((config) => this._isVisible(config, options));
5125
- return options.allScopes === true || options.scope
5126
- ? visible
5127
- : this._effectiveByName(visible);
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
+ );
5128
5321
  }
5129
5322
 
5130
5323
  _effectiveByName(configs) {
5131
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
+ };
5132
5336
  const selected = new Map();
5133
5337
  for (const config of configs) {
5134
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;
5135
5343
  if (
5136
5344
  !current ||
5137
- (priority[config.configScope] || 0) >
5138
- (priority[current.configScope] || 0)
5345
+ candidatePriority > currentPriority ||
5346
+ (candidatePriority === currentPriority &&
5347
+ candidateSpecificity > currentSpecificity) ||
5348
+ (candidatePriority === currentPriority &&
5349
+ candidateSpecificity === currentSpecificity &&
5350
+ isInvalidMcpConfigRow(config) &&
5351
+ !isInvalidMcpConfigRow(current))
5139
5352
  ) {
5140
5353
  selected.set(config.name, config);
5141
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
- commonDirIdentity: normalizeFilesystemPath(
106
- canonicalExistingPrefixPath(absoluteCommonDir),
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
- const baseSha = git(["rev-parse", "HEAD"], info.path, deps).trim();
178
- return { ...info, repoRoot, baseSha };
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
  /**