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.
Files changed (185) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-DnGsmsqJ.js → AIOps-DIY6KxV-.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-CrO6fcQ7.js → ActionButton-BXH0muPt.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DsBdOaP2.js → Analytics-D39w8vmg.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-DmQslKUB.js → AppLayout-CUU1zvLs.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-BvyrT_QQ.js → Artifacts-D8azHO3T.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-prndoLiC.js → Audit-D0tAwXTg.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-DOTFyA55.js → BackgroundAgents-C29mhJll.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-9zOnPp_8.js → Backup-D6Aa1Gro.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-C4PC6sAG.js → BaseInput-B1uoHbPQ.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-BBAF-W9v.js → Chat-Bs4DLOR5.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CWhvvloO.js → ChatBubbleRenderer-DUYIBSle.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-BI9iculd.js → Checkbox-CNcBPEa4.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-3DP9BCKb.js → Codegen-CIY89763.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-df-2TnJX.js → Col-CsPYFmgo.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-CHs8gG_A.js → Community-BBocEge4.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-CDfF6q7V.js → Compact-TQ8Ux0Lf.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-CthrDnO7.js → Compliance-DfBsZTft.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-HjIqsWJw.js → Cowork-Cg-I6FVH.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-BKc196fb.js → Cron-DTQzfRTp.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-CJnjdc8W.js → Crosschain-DGxEKGpR.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BHUy-8Q2.js → DID-DLZ1RDSr.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-QDB7sKeY.js → Dashboard-XneICKNI.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-DStcVK11.js → Dropdown-BRU00J1-.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-BwSUskMJ.js → EmailListRenderer-BzCbJuAy.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DgF_kurT.js → FamilyGuardDashboard-D7BRyNBG.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-k8wvv-wN.js → Federation-D9DeFIhW.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-C11qoq2r.js → FormItemContext-6rAtA9Ko.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BIIpr9kg.js → GenericCardRenderer-8Hqy80pU.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-BlA1exTe.js → Git-Cmvkj1rt.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-CYa1ZYM4.js → Governance-BRiQUjjw.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-6_kKVIt6.js → Inference-CKN_EzVf.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-CB4AwxYy.js → KnowledgeGraph-BCDHwpwQ.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-xf7nHFAe.js → Logs-DK9ovhaB.js} +2 -2
  35. package/src/assets/web-panel/assets/MarkdownRenderer-D53pHiHy.js +1 -0
  36. package/src/assets/web-panel/assets/{Marketplace-aPg8e4Jt.js → Marketplace-BILPGakp.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-C-TNlVUf.js → McpTools-DTVCOr9-.js} +5 -5
  38. package/src/assets/web-panel/assets/{Memory-DMTPIpGa.js → Memory-Hzc1iow4.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-BxyvveRq.js → MobileBridge-GXfgnGYV.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-DyPCrAyQ.js → MobileProjects-B-3TJWsS.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-BdhqNXp6.js → Mtc-DwSJMWtK.js} +5 -5
  42. package/src/assets/web-panel/assets/{MtcAudit-Cz-HFxzW.js → MtcAudit-D2uxf-DA.js} +5 -5
  43. package/src/assets/web-panel/assets/{Multisig-CIUaFNsx.js → Multisig-DG6kbKK0.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-CWe6-J17.js → NLProgramming-f5p49EDU.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-vYAKeviU.js → Notes-FUgHyzSD.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-DQL9-2d7.js → NotificationSettings-DCHGee7e.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-DnXj1jBn.js → OrderTableRenderer-Dm6LlIoa.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-B3spU8AN.js → Organization-BawA7qAg.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-DgEwo7vm.js → Overflow-D6a5wZU1.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-hujMRH8X.js → P2P-K8WavwnA.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-C_qhdg9h.js → PdhVaultBrowser-4JAJYaqx.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-vp7LMcSf.js → Permissions-BDWvi8PD.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-Dxr5Hgnq.js → PersonalDataHub-DF_K4E9i.js} +3 -3
  54. package/src/assets/web-panel/assets/{Pipeline-DEPZqqH4.js → Pipeline-BYeZlesI.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-DTpDiKPe.js → Privacy-BIHlgnIc.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-jDJsRWmF.js → ProjectInit-Cvf-6B5P.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-Dhor5RWH.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-BGs47s1a.js → Providers-CxC7JuYv.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-CjIw-wIb.js → QrScannerModal-VLf3gAfL.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-DX6jcqoi.js → QuickAsk-B5vGjRiF.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-DopXkhar.js → Recommend--UW4iYs-.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-BLvqJE9c.js → RemoteSession-DMSPcfKR.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-DhRj2Me7.js → Reputation-kQXlEHmj.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-DQ53vuPq.js → Row-CxGu3pit.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-BslvwiBD.js → RssFeed-Bi5lo8MZ.js} +2 -2
  67. package/src/assets/web-panel/assets/{Search-DJKBDNKZ.js → Search-Cq_H5RTh.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-COhpxHER.js → Security-YFbmkaQS.js} +3 -3
  69. package/src/assets/web-panel/assets/{Services-dbulD6cI.js → Services-DLOGdaLm.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-ciIVNfhC.js → Skeleton-CteIxb3D.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-AiRTkTCk.js → Skills-CccopaIZ.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-DDMcWYBc.js → Sla-AzfEQKX1.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-BPxWbpXO.js → SpeechSettings-ekGo5l77.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-BoEOXCyg.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-UowY8Ixe.js → Templates-CX1Tgeka.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-DauSLQXN.js → Tenant-Zc59jF4i.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-DBj7Jneq.js → Terminal-CXOD7y83.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-UMrPwoZC.js → TimelineRenderer-B1aBHPBo.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-4ea96-bn.js → Tokens-zALlOq_U.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger--ISCqt4b.js → Trigger-CQfJMXaq.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-D4GVimRJ.js → Trust-BU0RcM-w.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-Ci-nXRQc.js → UkeySign-IFZ3HsbH.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-Cjk7mKPw.js → VideoEditing-BzslgW0v.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-BgOLOO3Z.js → Wallet-BZAcQNS6.js} +2 -2
  86. package/src/assets/web-panel/assets/{WebAuthn-CCX0K29i.js → WebAuthn-CBK1DMi0.js} +4 -4
  87. package/src/assets/web-panel/assets/{WorkflowEditor-Ch3zgIsE.js → WorkflowEditor-BlMTPzO-.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-DKOU_2Vb.js → chat-B51-8QVn.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-6OVo4u3C.js → colors-504RTByU.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-BH78Hlsq.js → compact-item-C-GP6IQM.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-CFRpSbbC.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-DNQhgcVi.js → hasIn-DN7V-_FW.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-dDkoL4Pe.js → index--n-ZciV3.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CaYqY35Z.js → index-7jIqHbzw.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BYrnm5Ob.js → index-B2-x9bS8.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-sm7W8AXy.js → index-BFIFJ5Dr.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DKN2noIF.js → index-B_vGU-Js.js} +1 -1
  99. package/src/assets/web-panel/assets/{index--rq90rL9.js → index-Bkxhaeta.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CFPJ8yzF.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-caye7AOE.js → index-C0k_Nqg9.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BxRy5LPR.js → index-C3TSohzc.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BepBTpa8.js → index-C4DV_5uA.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-D84id2Y4.js → index-C8UltbXK.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Y9MJv28e.js → index-C9_48OzU.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Bb6kDrUx.js → index-CKTfZyfU.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CrcLHN2F.js → index-CP2A226L.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CkNEMANR.js → index-CUhQpiYv.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Cu3L4r5l.js → index-CX78k-qf.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DaDgdJYj.js → index-CYopNjcJ.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Du0rnksC.js → index-Cao5DZy7.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BaVE6E8y.js → index-CfiEM64n.js} +3 -3
  114. package/src/assets/web-panel/assets/{index-BujsOZfc.js → index-ConjaJro.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-hPY0fFrd.js → index-CqcLo5WV.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-BDSR7ecX.js → index-CySTJKeq.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BHa4WyTZ.js → index-D1phyca8.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-nbfHl1oV.js → index-D9LyiN-L.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CkqHgG-b.js → index-DA2f1UV3.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Eag8e_mA.js → index-DAOpQvl_.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-cdf7fd7d.js → index-DE-hmI2l.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-bKm3TUDp.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-7TC3S8SN.js → index-DaA__-q1.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DvHGtNnF.js → index-FcV8J_Qz.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-B_Z-YoxX.js → index-P2mfY27Z.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-AuBhLqOm.js → index-et5JIPHO.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-D3RtTT4K.js → index-f4Bo4KXm.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CNuCHHQT.js → index-kAqQXfP6.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BkN3iTTp.js → index-kt_5rfcZ.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BnnXQot_.js → index-so8eIIxF.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-HNGGVF6o.js → index-yYTQZypF.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-BZ9w6jPL.js → initDefaultProps-CPYEIdpV.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-BnwUiOho.js → motion-Cjim9pxs.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-ByUKJnNm.js → move-DuU-cgz3.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-V7yK1KEl.js → mtc-parser-DJHNAkta.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-BEXBBt4j.js → omit-DSQBgdBj.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-qDm_46Gi.js → pickAttrs-CTmT_j5m.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BIbhEo_0.js → placementArrow-Lfm6v-r4.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-NbPbDgA_.js → responsiveObserve-CgrcPcDi.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-BnUWglZ0.js → slide-CuE3110i.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BX8_wZI5.js → statusUtils-DiK5nbpf.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-BV4OycQX.js → styleChecker-DPEJdQaZ.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-Od_H5llY.js → useFlexGapSupport-CnnodGmp.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-BWI-PyiX.js → useFs-DWF84Yav.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-BqMuhG3g.js → usePersonalDataHub-a5b5Dw2f.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-DNd23z6f.js → vnode-DAluzXOq.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-Cjaxnm7l.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 +217 -30
  156. package/src/data/changelog.json +14 -1
  157. package/src/harness/mcp-client.js +242 -42
  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 +51 -6
  166. package/src/lib/cowork-task-runner.js +1 -0
  167. package/src/lib/mcp-headers-helper.js +76 -8
  168. package/src/lib/mcp-sandbox-policy.js +155 -0
  169. package/src/lib/mcp-stdio-execution-authority.js +163 -0
  170. package/src/lib/mcp-stdio-workspace-authority.js +252 -0
  171. package/src/lib/plugin-runtime/sandbox-policy.js +42 -2
  172. package/src/lib/process-execution-broker/index.js +179 -104
  173. package/src/lib/process-execution-broker/platform-sandbox.js +942 -168
  174. package/src/lib/process-tree-termination.js +17 -1
  175. package/src/lib/skill-mcp.js +65 -4
  176. package/src/lib/web-fetch.js +4 -4
  177. package/src/runtime/agent-runtime.js +61 -8
  178. package/src/runtime/mcp-config.js +235 -28
  179. package/src/workers/background-agent-worker.js +492 -71
  180. package/src/assets/web-panel/assets/MarkdownRenderer-Bf3DEqP6.js +0 -1
  181. package/src/assets/web-panel/assets/Projects-08xUDZGt.js +0 -1
  182. package/src/assets/web-panel/assets/Tasks-CEYFJwmr.js +0 -1
  183. package/src/assets/web-panel/assets/devWarning-DEMblAIm.js +0 -1
  184. package/src/assets/web-panel/assets/index-BTvq-36D.js +0 -1
  185. package/src/assets/web-panel/assets/index-hUpvk6Jl.js +0 -1
@@ -0,0 +1,252 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { isProxy } from "node:util/types";
4
+ import { resolveHostHooksV2WorkspaceBinding } from "./hooks-v2-workspace-context.js";
5
+ import { normalizeMcpSandboxPolicy } from "./mcp-sandbox-policy.js";
6
+ import { resolvePluginWorkspaceAuthority } from "./plugin-runtime/sandbox-policy.js";
7
+ import { resolveProjectMcpWorkspaceAuthority } from "./project-mcp-trust.js";
8
+
9
+ export const MCP_STDIO_SANDBOX_WORKSPACE_REQUIRED_CODE =
10
+ "CC_MCP_STDIO_SANDBOX_WORKSPACE_REQUIRED";
11
+ export const MCP_STDIO_SANDBOX_CWD_INVALID_CODE =
12
+ "CC_MCP_STDIO_SANDBOX_CWD_INVALID";
13
+
14
+ function workspaceError(code, message, cause) {
15
+ const error = new Error(message, cause ? { cause } : undefined);
16
+ error.code = code;
17
+ return error;
18
+ }
19
+
20
+ function dataProperty(owner, key, fallback) {
21
+ if (!owner || typeof owner !== "object" || isProxy(owner)) {
22
+ throw workspaceError(
23
+ MCP_STDIO_SANDBOX_WORKSPACE_REQUIRED_CODE,
24
+ "MCP stdio sandbox config must be a non-Proxy object",
25
+ );
26
+ }
27
+ const descriptor = Object.getOwnPropertyDescriptor(owner, key);
28
+ if (!descriptor) return fallback;
29
+ if (!("value" in descriptor)) {
30
+ throw workspaceError(
31
+ MCP_STDIO_SANDBOX_WORKSPACE_REQUIRED_CODE,
32
+ `MCP stdio sandbox config.${key} must be an own data property`,
33
+ );
34
+ }
35
+ return descriptor.value;
36
+ }
37
+
38
+ function canonicalDirectory(value, label) {
39
+ if (typeof value !== "string" || value.length === 0 || value.includes("\0")) {
40
+ throw workspaceError(
41
+ MCP_STDIO_SANDBOX_CWD_INVALID_CODE,
42
+ `${label} must be a non-empty directory path`,
43
+ );
44
+ }
45
+ try {
46
+ const canonical = fs.realpathSync.native(path.resolve(value));
47
+ if (!fs.statSync(canonical).isDirectory()) {
48
+ throw new Error("not a directory");
49
+ }
50
+ return canonical;
51
+ } catch (cause) {
52
+ throw workspaceError(
53
+ MCP_STDIO_SANDBOX_CWD_INVALID_CODE,
54
+ `${label} must resolve to an existing directory`,
55
+ cause,
56
+ );
57
+ }
58
+ }
59
+
60
+ function isPathInside(root, candidate) {
61
+ const relative = path.relative(root, candidate);
62
+ return (
63
+ relative === "" ||
64
+ (relative !== ".." &&
65
+ !relative.startsWith(`..${path.sep}`) &&
66
+ !path.isAbsolute(relative))
67
+ );
68
+ }
69
+
70
+ function sandboxPolicyFromConfig(config, serverName) {
71
+ const raw = dataProperty(config, "sandboxPolicy", null);
72
+ return normalizeMcpSandboxPolicy(raw, {
73
+ label: `MCP stdio server "${serverName}" sandboxPolicy`,
74
+ });
75
+ }
76
+
77
+ function pluginRootFromConfig(config, deps) {
78
+ if (dataProperty(config, "origin", null) !== "plugin:mcp") return null;
79
+ const resolvePlugin =
80
+ deps.resolvePluginWorkspaceAuthority || resolvePluginWorkspaceAuthority;
81
+ return resolvePlugin(dataProperty(config, "pluginWorkspaceAuthority", null), {
82
+ origin: "plugin:mcp",
83
+ pluginId: dataProperty(config, "pluginId", null),
84
+ pluginVersion: dataProperty(config, "pluginVersion", null),
85
+ pluginSource: dataProperty(config, "pluginSource", null),
86
+ });
87
+ }
88
+
89
+ function isProjectFileSource(configScope, configSource) {
90
+ return (
91
+ configScope === "project" &&
92
+ typeof configSource === "string" &&
93
+ path.basename(configSource).toLowerCase() === ".mcp.json"
94
+ );
95
+ }
96
+
97
+ function requirePublicWorkspaceRoot(
98
+ serverName,
99
+ config,
100
+ workspaceBinding,
101
+ deps,
102
+ ) {
103
+ const resolveHost =
104
+ deps.resolveHostWorkspaceBinding || resolveHostHooksV2WorkspaceBinding;
105
+ const binding = resolveHost(workspaceBinding);
106
+ if (!binding?.workspaceRoot) {
107
+ throw workspaceError(
108
+ MCP_STDIO_SANDBOX_WORKSPACE_REQUIRED_CODE,
109
+ `Policy-bearing MCP stdio server "${serverName}" requires a trusted host workspace`,
110
+ );
111
+ }
112
+ const hostRoot = canonicalDirectory(
113
+ binding.workspaceRoot,
114
+ `MCP stdio server "${serverName}" host workspace`,
115
+ );
116
+ const configScope = dataProperty(config, "configScope", null);
117
+ const projectPath = dataProperty(config, "projectPath", null);
118
+ const configSource = dataProperty(config, "configSource", null);
119
+ const projectAuthority = dataProperty(
120
+ config,
121
+ "projectMcpWorkspaceAuthority",
122
+ null,
123
+ );
124
+ const projectFileSource = isProjectFileSource(configScope, configSource);
125
+
126
+ if (
127
+ configScope === "project" &&
128
+ (projectFileSource || projectAuthority) &&
129
+ !projectPath
130
+ ) {
131
+ throw workspaceError(
132
+ MCP_STDIO_SANDBOX_WORKSPACE_REQUIRED_CODE,
133
+ `Project MCP stdio server "${serverName}" is missing its project workspace`,
134
+ );
135
+ }
136
+
137
+ if ((configScope === "local" || configScope === "project") && projectPath) {
138
+ const projectRoot = canonicalDirectory(
139
+ projectPath,
140
+ `MCP stdio server "${serverName}" project workspace`,
141
+ );
142
+ if (!isPathInside(projectRoot, hostRoot)) {
143
+ throw workspaceError(
144
+ MCP_STDIO_SANDBOX_WORKSPACE_REQUIRED_CODE,
145
+ `MCP stdio server "${serverName}" does not belong to the trusted host workspace`,
146
+ );
147
+ }
148
+
149
+ if (projectAuthority || projectFileSource) {
150
+ const resolveProject =
151
+ deps.resolveProjectMcpWorkspaceAuthority ||
152
+ resolveProjectMcpWorkspaceAuthority;
153
+ const authorityRoot = resolveProject(projectAuthority, {
154
+ configSource,
155
+ serverName,
156
+ url: dataProperty(config, "url", null),
157
+ transport: dataProperty(config, "transport", null),
158
+ headersHelper: dataProperty(config, "headersHelper", null),
159
+ });
160
+ let canonicalAuthorityRoot = null;
161
+ try {
162
+ canonicalAuthorityRoot = authorityRoot
163
+ ? canonicalDirectory(
164
+ authorityRoot,
165
+ `MCP stdio server "${serverName}" project authority`,
166
+ )
167
+ : null;
168
+ } catch {
169
+ canonicalAuthorityRoot = null;
170
+ }
171
+ if (canonicalAuthorityRoot !== projectRoot) {
172
+ throw workspaceError(
173
+ MCP_STDIO_SANDBOX_WORKSPACE_REQUIRED_CODE,
174
+ `Project MCP stdio server "${serverName}" is missing its trusted file authority`,
175
+ );
176
+ }
177
+ }
178
+ }
179
+
180
+ return hostRoot;
181
+ }
182
+
183
+ function requestedWorkingDirectory(config, trustedRoot, serverName) {
184
+ const requested = dataProperty(config, "cwd", null);
185
+ if (requested == null || requested === "") return trustedRoot;
186
+ if (typeof requested !== "string" || requested.includes("\0")) {
187
+ throw workspaceError(
188
+ MCP_STDIO_SANDBOX_CWD_INVALID_CODE,
189
+ `MCP stdio server "${serverName}" cwd must be a path`,
190
+ );
191
+ }
192
+ const candidate = canonicalDirectory(
193
+ path.isAbsolute(requested)
194
+ ? requested
195
+ : path.resolve(trustedRoot, requested),
196
+ `MCP stdio server "${serverName}" cwd`,
197
+ );
198
+ if (!isPathInside(trustedRoot, candidate)) {
199
+ throw workspaceError(
200
+ MCP_STDIO_SANDBOX_CWD_INVALID_CODE,
201
+ `MCP stdio server "${serverName}" cwd escapes its trusted workspace`,
202
+ );
203
+ }
204
+ return candidate;
205
+ }
206
+
207
+ /**
208
+ * Resolve source-declared MCP isolation against an opaque host/plugin
209
+ * workspace capability. A policy-bearing launch never derives authority from
210
+ * process.cwd(); the selected cwd is canonical, existing, and contained by the
211
+ * trusted root before executable identity or Broker contract issuance.
212
+ */
213
+ export function resolveMcpStdioSandboxContext(
214
+ { serverName, config, workspaceBinding },
215
+ deps = {},
216
+ ) {
217
+ const name = String(serverName || "unknown");
218
+ const sandboxPolicy = sandboxPolicyFromConfig(config, name);
219
+ const pluginWorkspaceRoot = pluginRootFromConfig(config, deps);
220
+
221
+ if (!sandboxPolicy) {
222
+ return Object.freeze({
223
+ sandboxPolicy: null,
224
+ pluginWorkspaceRoot,
225
+ workingDirectory: pluginWorkspaceRoot,
226
+ });
227
+ }
228
+
229
+ if (
230
+ dataProperty(config, "origin", null) === "plugin:mcp" &&
231
+ !pluginWorkspaceRoot
232
+ ) {
233
+ throw workspaceError(
234
+ MCP_STDIO_SANDBOX_WORKSPACE_REQUIRED_CODE,
235
+ `Plugin MCP stdio server "${name}" is missing its trusted workspace authority`,
236
+ );
237
+ }
238
+
239
+ const trustedRoot = pluginWorkspaceRoot
240
+ ? canonicalDirectory(
241
+ pluginWorkspaceRoot,
242
+ `Plugin MCP stdio server "${name}" workspace`,
243
+ )
244
+ : requirePublicWorkspaceRoot(name, config, workspaceBinding, deps);
245
+ const workingDirectory = requestedWorkingDirectory(config, trustedRoot, name);
246
+
247
+ return Object.freeze({
248
+ sandboxPolicy,
249
+ pluginWorkspaceRoot,
250
+ workingDirectory,
251
+ });
252
+ }
@@ -20,6 +20,28 @@ const SUPPORTED_BOUNDARIES = new Set(PLUGIN_SANDBOX_BOUNDARIES);
20
20
  const PLUGIN_WORKSPACE_AUTHORITY_KIND = "trusted-plugin-workspace";
21
21
  const pluginWorkspaceAuthorities = new WeakMap();
22
22
 
23
+ function directoryGeneration(stat) {
24
+ return stat.birthtimeNs > 0n
25
+ ? `birth:${stat.birthtimeNs.toString()}`
26
+ : `ctime:${stat.ctimeNs.toString()}`;
27
+ }
28
+
29
+ function directoryIdentity(stat) {
30
+ return Object.freeze({
31
+ device: stat.dev,
32
+ inode: stat.ino,
33
+ generation: directoryGeneration(stat),
34
+ });
35
+ }
36
+
37
+ function sameDirectoryIdentity(left, right) {
38
+ return (
39
+ left.device === right.dev &&
40
+ left.inode === right.ino &&
41
+ left.generation === directoryGeneration(right)
42
+ );
43
+ }
44
+
23
45
  function invalidPolicy(message) {
24
46
  const error = new Error(message);
25
47
  error.code = "ERR_PLUGIN_SANDBOX_POLICY_INVALID";
@@ -69,7 +91,7 @@ export function issuePluginWorkspaceAuthority(value = {}) {
69
91
  try {
70
92
  const implementation = fs.realpathSync.native || fs.realpathSync;
71
93
  canonicalRoot = implementation.call(fs.realpathSync, requestedRoot);
72
- stat = fs.statSync(canonicalRoot);
94
+ stat = fs.statSync(canonicalRoot, { bigint: true });
73
95
  } catch {
74
96
  throw invalidPolicy("trusted plugin workspace root is unavailable");
75
97
  }
@@ -92,7 +114,11 @@ export function issuePluginWorkspaceAuthority(value = {}) {
92
114
  });
93
115
  pluginWorkspaceAuthorities.set(
94
116
  authority,
95
- Object.freeze({ root: canonicalRoot, provenance }),
117
+ Object.freeze({
118
+ root: canonicalRoot,
119
+ identity: directoryIdentity(stat),
120
+ provenance,
121
+ }),
96
122
  );
97
123
  return authority;
98
124
  }
@@ -117,6 +143,20 @@ export function resolvePluginWorkspaceAuthority(authority, provenance = {}) {
117
143
  ) {
118
144
  return null;
119
145
  }
146
+ try {
147
+ const implementation = fs.realpathSync.native || fs.realpathSync;
148
+ const currentRoot = implementation.call(fs.realpathSync, binding.root);
149
+ const currentStat = fs.statSync(currentRoot, { bigint: true });
150
+ if (
151
+ currentRoot !== binding.root ||
152
+ !currentStat.isDirectory() ||
153
+ !sameDirectoryIdentity(binding.identity, currentStat)
154
+ ) {
155
+ return null;
156
+ }
157
+ } catch {
158
+ return null;
159
+ }
120
160
  return binding.root;
121
161
  }
122
162
 
@@ -1320,6 +1320,20 @@ class ProcessExecutionBroker extends EventEmitter {
1320
1320
  "pluginTreeSnapshotContractBound",
1321
1321
  "pluginTreeSnapshotAtomic",
1322
1322
  ];
1323
+ const nodePluginTreeSnapshotEvidence =
1324
+ plan.runtimeProbe.kind === "linux-bwrap-plugin-node-policy-v1" &&
1325
+ plan.runtimeProbe.targetRuntime === "node" &&
1326
+ plan.runtimeProbe.contentSnapshotScope === "plugin-entry-source";
1327
+ const nativePluginRuntimeEvidence =
1328
+ (plan.runtimeProbe.kind ===
1329
+ "linux-bwrap-plugin-native-static-elf-policy-v1" &&
1330
+ plan.runtimeProbe.targetRuntime === "native-static-elf") ||
1331
+ (plan.runtimeProbe.kind ===
1332
+ "linux-bwrap-plugin-native-dynamic-elf-policy-v1" &&
1333
+ plan.runtimeProbe.targetRuntime === "native-dynamic-elf");
1334
+ const nativePluginTreeSnapshotEvidence =
1335
+ nativePluginRuntimeEvidence &&
1336
+ plan.runtimeProbe.contentSnapshotScope === "plugin-entry-executable";
1323
1337
  if (
1324
1338
  pluginTreeSnapshot &&
1325
1339
  (plan.applied !== true ||
@@ -1334,11 +1348,10 @@ class ProcessExecutionBroker extends EventEmitter {
1334
1348
  plan.runtimeProbe.attempted !== true ||
1335
1349
  plan.runtimeProbe.runnable !== true ||
1336
1350
  plan.runtimeProbe.reason !== null ||
1337
- plan.runtimeProbe.kind !== "linux-bwrap-plugin-node-policy-v1" ||
1351
+ (!nodePluginTreeSnapshotEvidence &&
1352
+ !nativePluginTreeSnapshotEvidence) ||
1338
1353
  plan.runtimeProbe.probeRuntime !== "node" ||
1339
- plan.runtimeProbe.targetRuntime !== "node" ||
1340
1354
  plan.runtimeProbe.contentSnapshot !== true ||
1341
- plan.runtimeProbe.contentSnapshotScope !== "plugin-entry-source" ||
1342
1355
  plan.runtimeProbe.contentSnapshotMechanism !==
1343
1356
  "verified-o_tmpfile-copy-bwrap-ro-bind-data-v1" ||
1344
1357
  plan.runtimeProbe.handleAtomic !== false ||
@@ -1382,6 +1395,18 @@ class ProcessExecutionBroker extends EventEmitter {
1382
1395
  "Sandbox runtime probe plugin tree snapshot evidence requires pluginTreeContentSnapshot",
1383
1396
  );
1384
1397
  }
1398
+ const successfulNativeProbe =
1399
+ plan.applied === true &&
1400
+ plan.runtimeProbe.attempted === true &&
1401
+ plan.runtimeProbe.runnable === true &&
1402
+ plan.runtimeProbe.reason === null &&
1403
+ nativePluginRuntimeEvidence;
1404
+ if (successfulNativeProbe && !pluginTreeSnapshot) {
1405
+ throw this._sandboxError(
1406
+ "invalid_sandbox_plan",
1407
+ "Successful Linux native plugin evidence requires a complete plugin tree snapshot",
1408
+ );
1409
+ }
1385
1410
  const supervisorStringFields = [
1386
1411
  "supervisorBindingScope",
1387
1412
  "supervisorDescriptorBindingMechanism",
@@ -2585,12 +2610,6 @@ class ProcessExecutionBroker extends EventEmitter {
2585
2610
  }
2586
2611
 
2587
2612
  _createWorkspaceProcessCloseFence(auditEntry, proc) {
2588
- if (
2589
- !Array.isArray(auditEntry.workspaceTransactionIds) ||
2590
- auditEntry.workspaceTransactionIds.length === 0
2591
- ) {
2592
- return null;
2593
- }
2594
2613
  return new Promise((resolve) => {
2595
2614
  if (typeof proc?.once === "function") {
2596
2615
  proc.once("close", (exitCode, signal) => {
@@ -2612,6 +2631,54 @@ class ProcessExecutionBroker extends EventEmitter {
2612
2631
  });
2613
2632
  }
2614
2633
 
2634
+ _postSpawnOwnershipError(
2635
+ error,
2636
+ { proc, auditEntry, workspaceProcessClosed, cleanup },
2637
+ ) {
2638
+ let failure =
2639
+ error instanceof Error
2640
+ ? error
2641
+ : new Error(String(error || "spawn failed"));
2642
+ if (
2643
+ failure.processTerminationRequested !== true &&
2644
+ failure.workspaceTerminationRequested !== true
2645
+ ) {
2646
+ try {
2647
+ if (typeof proc?.once === "function") proc.once("error", () => {});
2648
+ } catch {
2649
+ // Listener installation failure is itself post-spawn evidence.
2650
+ }
2651
+ try {
2652
+ proc?.kill?.("SIGKILL");
2653
+ } catch {
2654
+ // The close fence remains the only settlement authority.
2655
+ }
2656
+ }
2657
+ try {
2658
+ cleanup?.();
2659
+ } catch {
2660
+ // Ownership evidence must survive cleanup reporting failures.
2661
+ }
2662
+ const attach = (target) => {
2663
+ target.auditEntry = auditEntry;
2664
+ target.spawnedProcess = proc;
2665
+ target.workspaceProcessClosed = workspaceProcessClosed;
2666
+ target.workspaceTerminationRequested = true;
2667
+ return target;
2668
+ };
2669
+ try {
2670
+ return attach(failure);
2671
+ } catch {
2672
+ const wrapped = new Error(failure.message || "post-spawn failure", {
2673
+ cause: failure,
2674
+ });
2675
+ wrapped.name = failure.name || wrapped.name;
2676
+ if (failure.code !== undefined) wrapped.code = failure.code;
2677
+ failure = wrapped;
2678
+ return attach(failure);
2679
+ }
2680
+ }
2681
+
2615
2682
  _runPostSpawnSandbox(proc, plan, auditEntry) {
2616
2683
  if (!plan.applied || !plan.postSpawn.required) {
2617
2684
  this._applySandboxAudit(auditEntry, plan, plan.applied);
@@ -3328,6 +3395,7 @@ class ProcessExecutionBroker extends EventEmitter {
3328
3395
  // Use native spawn from _native (set by patch-child-process.js)
3329
3396
  const nativeSpawnFn = this._native?.spawn || nativeSpawn;
3330
3397
  let proc;
3398
+ let workspaceProcessClosed = null;
3331
3399
  try {
3332
3400
  if (mcpStdioExecutableIdentityAuthority !== undefined) {
3333
3401
  const admitted = consumeMcpStdioExecutableIdentityAuthority(
@@ -3341,16 +3409,20 @@ class ProcessExecutionBroker extends EventEmitter {
3341
3409
  auditEntry.mcpStdioExecutableIdentityDigest = admitted.identityDigest;
3342
3410
  }
3343
3411
  proc = nativeSpawnFn(command, args, optsForSpawn);
3412
+ workspaceProcessClosed = this._createWorkspaceProcessCloseFence(
3413
+ auditEntry,
3414
+ proc,
3415
+ );
3344
3416
  this._bindWorkspaceTransactionProcess(auditEntry, proc);
3345
3417
  } catch (spawnError) {
3346
- if (proc && typeof proc.kill === "function") {
3347
- try {
3348
- proc.kill("SIGKILL");
3349
- } catch {
3350
- // The transaction remains unsettled if shutdown cannot be proven.
3351
- }
3352
- }
3353
- if (!proc) {
3418
+ if (proc) {
3419
+ throw this._postSpawnOwnershipError(spawnError, {
3420
+ proc,
3421
+ auditEntry,
3422
+ workspaceProcessClosed,
3423
+ cleanup: sandboxPlan.cleanup,
3424
+ });
3425
+ } else {
3354
3426
  try {
3355
3427
  this._settleWorkspaceTransactionSpawn(auditEntry, {
3356
3428
  error: spawnError.message,
@@ -3363,102 +3435,105 @@ class ProcessExecutionBroker extends EventEmitter {
3363
3435
  throw spawnError;
3364
3436
  }
3365
3437
  auditEntry.pid = proc.pid;
3366
- const workspaceProcessClosed = this._createWorkspaceProcessCloseFence(
3367
- auditEntry,
3368
- proc,
3369
- );
3370
- const cleanupSandbox = this._scheduleSandboxCleanup(
3371
- proc,
3372
- sandboxPlan.cleanup,
3373
- );
3374
- if (
3375
- sandboxPlan.applied === true &&
3376
- (sandboxPlan.backend === "linux-bwrap-workspace" ||
3377
- sandboxPlan.backend === "linux-bwrap" ||
3378
- sandboxPlan.backend === "linux-fd-code-snapshot") &&
3379
- sandboxPlan.postSpawn.required === false
3380
- ) {
3381
- // child_process.spawn() has synchronously duplicated every stdio entry
3382
- // into the launched process before returning. The generic/direct bwrap
3383
- // backends and the MCP anonymous-code snapshot backend are complete
3384
- // pre-spawn plans with no parent-side enforcement handle. Retaining
3385
- // their pinned descriptors until a long-lived process exits would turn
3386
- // each child into an avoidable FD leak.
3387
- cleanupSandbox();
3388
- }
3438
+ let cleanupSandbox = sandboxPlan.cleanup;
3389
3439
  try {
3390
- this._runPostSpawnSandbox(proc, sandboxPlan, auditEntry);
3391
- this._updateWorkspaceTransactionProcessGuarantees(auditEntry);
3392
- auditEntry.pid = proc.pid;
3393
- if (Number.isSafeInteger(proc.sandboxWrapperPid)) {
3394
- auditEntry.sandboxWrapperPid = proc.sandboxWrapperPid;
3395
- }
3396
- if (Number.isSafeInteger(proc.sandboxTargetPid)) {
3397
- auditEntry.sandboxTargetPid = proc.sandboxTargetPid;
3440
+ cleanupSandbox = this._scheduleSandboxCleanup(proc, sandboxPlan.cleanup);
3441
+ if (
3442
+ sandboxPlan.applied === true &&
3443
+ (sandboxPlan.backend === "linux-bwrap-workspace" ||
3444
+ sandboxPlan.backend === "linux-bwrap" ||
3445
+ sandboxPlan.backend === "linux-fd-code-snapshot") &&
3446
+ sandboxPlan.postSpawn.required === false
3447
+ ) {
3448
+ // child_process.spawn() has synchronously duplicated every stdio entry
3449
+ // into the launched process before returning. The generic/direct bwrap
3450
+ // backends and the MCP anonymous-code snapshot backend are complete
3451
+ // pre-spawn plans with no parent-side enforcement handle. Retaining
3452
+ // their pinned descriptors until a long-lived process exits would turn
3453
+ // each child into an avoidable FD leak.
3454
+ cleanupSandbox();
3398
3455
  }
3399
- } catch (postSpawnError) {
3400
- if (postSpawnError.processTerminationRequested !== true) {
3401
- try {
3402
- proc.kill?.("SIGKILL");
3403
- } catch {
3404
- // The close fence remains pending and recovery stays fail-closed.
3456
+ try {
3457
+ this._runPostSpawnSandbox(proc, sandboxPlan, auditEntry);
3458
+ this._updateWorkspaceTransactionProcessGuarantees(auditEntry);
3459
+ auditEntry.pid = proc.pid;
3460
+ if (Number.isSafeInteger(proc.sandboxWrapperPid)) {
3461
+ auditEntry.sandboxWrapperPid = proc.sandboxWrapperPid;
3462
+ }
3463
+ if (Number.isSafeInteger(proc.sandboxTargetPid)) {
3464
+ auditEntry.sandboxTargetPid = proc.sandboxTargetPid;
3465
+ }
3466
+ } catch (postSpawnError) {
3467
+ if (postSpawnError.processTerminationRequested !== true) {
3468
+ try {
3469
+ proc.kill?.("SIGKILL");
3470
+ } catch {
3471
+ // The close fence remains pending and recovery stays fail-closed.
3472
+ }
3405
3473
  }
3474
+ cleanupSandbox();
3475
+ this._recordSandboxDenial(auditEntry, postSpawnError, startTime);
3476
+ postSpawnError.auditEntry = auditEntry;
3477
+ postSpawnError.spawnedProcess = proc;
3478
+ postSpawnError.workspaceProcessClosed = workspaceProcessClosed;
3479
+ postSpawnError.workspaceTerminationRequested = true;
3480
+ throw postSpawnError;
3406
3481
  }
3407
- cleanupSandbox();
3408
- this._recordSandboxDenial(auditEntry, postSpawnError, startTime);
3409
- postSpawnError.auditEntry = auditEntry;
3410
- postSpawnError.spawnedProcess = proc;
3411
- postSpawnError.workspaceProcessClosed = workspaceProcessClosed;
3412
- postSpawnError.workspaceTerminationRequested = true;
3413
- throw postSpawnError;
3414
- }
3415
-
3416
- this._recordAudit(auditEntry);
3417
- this._writeRplEntry(auditEntry, "started");
3418
- this._emitHooksEvent("tool:start", {
3419
- executionId,
3420
- toolName: origin,
3421
- command,
3422
- args: [...auditEntry.args],
3423
- cwd,
3424
- pid: proc.pid,
3425
- component: origin,
3426
- });
3427
3482
 
3428
- proc.on("exit", (code, signal) => {
3429
- const endTime = Date.now();
3430
- auditEntry.exitCode = code;
3431
- auditEntry.signal = signal;
3432
- auditEntry.endTime = endTime;
3433
- auditEntry.durationMs = endTime - startTime;
3434
- this._writeRplEntry(auditEntry, "completed");
3435
- this._emitHooksEvent("tool:end", {
3483
+ this._recordAudit(auditEntry);
3484
+ this._writeRplEntry(auditEntry, "started");
3485
+ this._emitHooksEvent("tool:start", {
3436
3486
  executionId,
3437
- success: code === 0,
3438
- exitCode: code,
3439
- signal,
3440
- durationMs: auditEntry.durationMs,
3487
+ toolName: origin,
3488
+ command,
3489
+ args: [...auditEntry.args],
3490
+ cwd,
3491
+ pid: proc.pid,
3441
3492
  component: origin,
3442
3493
  });
3443
- this.emit("exit", auditEntry);
3444
- });
3445
- proc.on("error", (err) => {
3446
- auditEntry.error = err.message;
3447
- auditEntry.endTime = Date.now();
3448
- auditEntry.durationMs = auditEntry.endTime - startTime;
3449
- this._writeRplEntry(auditEntry, "error", err);
3450
- this._emitHooksEvent("tool:end", {
3451
- executionId,
3452
- success: false,
3453
- error: err.message,
3454
- component: origin,
3494
+
3495
+ proc.on("exit", (code, signal) => {
3496
+ const endTime = Date.now();
3497
+ auditEntry.exitCode = code;
3498
+ auditEntry.signal = signal;
3499
+ auditEntry.endTime = endTime;
3500
+ auditEntry.durationMs = endTime - startTime;
3501
+ this._writeRplEntry(auditEntry, "completed");
3502
+ this._emitHooksEvent("tool:end", {
3503
+ executionId,
3504
+ success: code === 0,
3505
+ exitCode: code,
3506
+ signal,
3507
+ durationMs: auditEntry.durationMs,
3508
+ component: origin,
3509
+ });
3510
+ this.emit("exit", auditEntry);
3455
3511
  });
3456
- // EventEmitter treats an unhandled `error` event as an exception. A
3457
- // process ENOENT must reach the child-process listener/callback instead
3458
- // of crashing callers that did not subscribe to broker diagnostics.
3459
- if (this.listenerCount("error") > 0) this.emit("error", auditEntry);
3460
- });
3461
- return proc;
3512
+ proc.on("error", (err) => {
3513
+ auditEntry.error = err.message;
3514
+ auditEntry.endTime = Date.now();
3515
+ auditEntry.durationMs = auditEntry.endTime - startTime;
3516
+ this._writeRplEntry(auditEntry, "error", err);
3517
+ this._emitHooksEvent("tool:end", {
3518
+ executionId,
3519
+ success: false,
3520
+ error: err.message,
3521
+ component: origin,
3522
+ });
3523
+ // EventEmitter treats an unhandled `error` event as an exception. A
3524
+ // process ENOENT must reach the child-process listener/callback instead
3525
+ // of crashing callers that did not subscribe to broker diagnostics.
3526
+ if (this.listenerCount("error") > 0) this.emit("error", auditEntry);
3527
+ });
3528
+ return proc;
3529
+ } catch (postNativeError) {
3530
+ throw this._postSpawnOwnershipError(postNativeError, {
3531
+ proc,
3532
+ auditEntry,
3533
+ workspaceProcessClosed,
3534
+ cleanup: cleanupSandbox,
3535
+ });
3536
+ }
3462
3537
  }
3463
3538
 
3464
3539
  spawnSync(command, args, options = {}) {