chainlesschain 0.162.187 → 0.162.189

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-CETaO3s8.js → AIOps-DbZft8w0.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-CAe7E9TW.js → ActionButton-DoOZpwfN.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-BGr2mzwO.js → Analytics-B0J0KLTW.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-BEoY-NcT.js → AppLayout-D9eIR0qq.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-DUZivCgt.js → Artifacts-BnZXEVeo.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-C3yBs68k.js → Audit-X1ce4Bjn.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-C9KwiY3O.js → BackgroundAgents-BvWR-y5v.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-BoSWD_C8.js → Backup-BublSGEd.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-Dp09GkxL.js → BaseInput-CR_77Hso.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-5t9d-Ja0.js → Chat-DixW52tq.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-DmVku4GH.js → ChatBubbleRenderer-DDHDpBBA.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-8QJFlkqI.js → Checkbox-CCv1C87K.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-BJVFQ3lL.js → Codegen-B03Cc3dd.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-6hdLD0oB.js → Col-CA06-6pU.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-B8opo0uY.js → Community-Bj881J52.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-Dv8HudiN.js → Compact-UZLTPoaL.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-DhxuCS5N.js → Compliance-kGwzRb_0.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-Dggy6VIL.js → Cowork-KundoYM8.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-DSmDqJTT.js → Cron-BYZyoqDe.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-7bZq0j5f.js → Crosschain-BhrrppBX.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BWY4kh-H.js → DID-CdC6EguZ.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-bOzPZxlu.js → Dashboard-CcQx3J4y.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-BTFJHqQu.js → Dropdown-BpijtEzQ.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-BC3MOdtT.js → EmailListRenderer-DfZjeZTe.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CFHAdTTY.js → FamilyGuardDashboard-BFYSf69C.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-DZ_4aF_n.js → Federation-Okdi3pJm.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-BZje6UTj.js → FormItemContext-mH-HebKW.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-D7QfCvjN.js → GenericCardRenderer-xivV69x7.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-DuG7qANG.js → Git-1rh_YBwg.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-BDmYAgTh.js → Governance-eAXkNxdY.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-DSXAfc1Q.js → Inference-CS6dMVTN.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-DusleniP.js → KnowledgeGraph-Bq_TnkvD.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-CpImmA8p.js → Logs-KchHtiHt.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-BZ6LJBkk.js → MarkdownRenderer-TabqHW9B.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-DXS8CXkE.js → Marketplace-CsmGcX5X.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-D151UfUI.js → McpTools-BaPFnnyz.js} +5 -5
  38. package/src/assets/web-panel/assets/{Memory-DKDxnoA1.js → Memory-2m-sUUf7.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-BFZ95iKM.js → MobileBridge-D5nVZBRu.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-CDAR-grF.js → MobileProjects-Bo8MLZvT.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-OGJ95xJs.js → Mtc-3GDvamGa.js} +6 -6
  42. package/src/assets/web-panel/assets/{MtcAudit-BmGTo1XC.js → MtcAudit-D2zGuZCH.js} +4 -4
  43. package/src/assets/web-panel/assets/{Multisig-XumJRqOa.js → Multisig-BBVQ4t6t.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-DHhhRZed.js → NLProgramming-BIOCyOGp.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-B9rGxNNr.js → Notes-DVwBGpzc.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-DClLbMYV.js → NotificationSettings-BDnHfIfX.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-I8OohT7N.js → OrderTableRenderer-C7Ek20ag.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-M6WzaYVa.js → Organization-D3SRKaa7.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-Cb3B5KtM.js → Overflow-C2B8ahyn.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-BGk57kya.js → P2P-2iwQzG65.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-CXcKyPqK.js → PdhVaultBrowser-CoPiWqMN.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-hsfJ-Y0e.js → Permissions-yV5XNXo5.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-BSBii4js.js → PersonalDataHub-uCDJhMUF.js} +4 -4
  54. package/src/assets/web-panel/assets/{Pipeline-BNw52rey.js → Pipeline-hN7ucMOA.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-DUpfue4E.js → Privacy-CgU0ptN4.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-C68NsP3h.js → ProjectInit-CfYPiT6j.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-BJ2It9v4.js → ProjectSettings-B0A2C4nw.js} +2 -2
  58. package/src/assets/web-panel/assets/Projects-BE5C0ajn.js +1 -0
  59. package/src/assets/web-panel/assets/{Providers-EnU4VWwS.js → Providers-CstQPf8W.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-BRt5-B_I.js → QrScannerModal-Co3z_ynk.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-Jd5L9UL2.js → QuickAsk-DMTS6pQ6.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-BNDhoVIu.js → Recommend-ETw4odUa.js} +1 -1
  63. package/src/assets/web-panel/assets/RemoteSession-B0rkE09d.js +5 -0
  64. package/src/assets/web-panel/assets/{Reputation-D1wQPUNv.js → Reputation-BTVqQgKq.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-DnjRegul.js → Row-CBKKwwtO.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-C_T4I-nF.js → RssFeed-BHUQ438I.js} +2 -2
  67. package/src/assets/web-panel/assets/{Search-C4lrxlLh.js → Search-B4NqeUsF.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-ByYT-VzP.js → Security-BRjoYG5_.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-BHpyEqB5.js → Services-CxQzu3p3.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-C0I23vG-.js → Skeleton-BKzwwmWO.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-DGZXz22K.js → Skills-DrsFcgth.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-DL_urP9A.js → Sla-D21tmXCH.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-6x-hUNQ_.js → SpeechSettings-CjyjLfp8.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-Crk5TCXq.js → SyncSettings-Cu45-tnx.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-rZGq5PZs.js → Tasks-oMcILYOj.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-DT5el8Q8.js → Templates-Bh-Zcr5R.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-mlgeyMK8.js → Tenant-68ns63aI.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-C8HEA-lM.js → Terminal-BLJkFykE.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-DvlqBayB.js → TimelineRenderer-xcrJLq-O.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-C7lDcNmT.js → Tokens-C6PpFJtf.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-CM8nCnIJ.js → Trigger-BaKA7tdM.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-BQDLUQ9x.js → Trust-BzhA57Jc.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-CVqM0OFE.js → UkeySign-4_U3JkIR.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-BvAcQpzT.js → VideoEditing-162AANxX.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-zFVFaAZ2.js → Wallet-B1viuql_.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-Cc522ums.js → WebAuthn-ZVWazhCy.js} +4 -4
  87. package/src/assets/web-panel/assets/{WorkflowEditor-Bg_wxpDo.js → WorkflowEditor-tzdyUk-1.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-jg0QwC8K.js → chat-DmrHNuwq.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-ALzYz1gO.js → colors-DAJ_M-PK.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-DXHYUoqi.js → compact-item-uIzmYwLs.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-BsGsBM5m.js → createContext-D_ZmuLwb.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-DIrncrV1.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-BccMLzxA.js → hasIn-CDt_shTL.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-Cg9cOVe9.js → index-BAvSmJXW.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BTTeAiOP.js → index-BDF6EQBs.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-C3k3ZwkQ.js → index-BHvlm0In.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CQ1ORdp-.js → index-BRa4kJ_S.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BcJzphrP.js → index-BTCXf1t-.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-rIy96PeM.js → index-Bbxjfstq.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CSFESurl.js → index-Bx4d6c1k.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Cmg_Npx5.js → index-ByzGvpR9.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CahDve8M.js → index-C16fYKFD.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-2VstcImq.js → index-C5WcdJH2.js} +3 -3
  104. package/src/assets/web-panel/assets/{index-C3Rf8wIM.js → index-CG0lY4h3.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-uzykuL_R.js → index-CKyd472S.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CBVIFX-x.js → index-CaelyMgF.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-TeCof1s3.js → index-CbD_WW_6.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BfF0OGKU.js → index-CcZ9xJI9.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-oWltafBn.js → index-CeUpKjvi.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CWLWBgfe.js → index-Cvak6Oe2.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-C0AbXsDy.js → index-CzXqvMuP.js} +1 -1
  112. package/src/assets/web-panel/assets/index-D-Swwqld.js +1 -0
  113. package/src/assets/web-panel/assets/{index-BfV8RDlg.js → index-D4n1lGTB.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Dxqiu9-F.js → index-D9OE8B3d.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BdCmJ_bG.js → index-DBFmcaCQ.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DgjQ-kdT.js → index-DdUMcwRB.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BBbbr1YB.js → index-DeGBoLQA.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Dmk7dD-T.js → index-DfSSy9uy.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-JS2LXdd0.js → index-DqDSF2RW.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-D9oV2iRR.js → index-DvFhhe6r.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BvsII10i.js → index-DwfvPe-j.js} +1 -1
  122. package/src/assets/web-panel/assets/index-FMZAMKy8.js +1 -0
  123. package/src/assets/web-panel/assets/{index-CrZHUsOZ.js → index-FyplHuNt.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CLzPzN75.js → index-GcKOHD_c.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-koT8Q86Z.js → index-OzTxFIJP.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-08diKcTm.js → index-QsgfSZrV.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CJg6Wsjc.js → index-Z8P4BexE.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-srqW5_-X.js → index-clSX4PEc.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-mIQUoUx1.js → index-ifxmu8SY.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Cz_s25m3.js → index-rS3gl_68.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-UQXedWQz.js → index-rXdwUeE3.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-rcwgNMc0.js → index-saf3VNeP.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-xafkqrW3.js → initDefaultProps-pmfaD-aK.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-BrtyQXLk.js → motion-CaQ2CpYF.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-BunUyg59.js → move-CkKTqNbI.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-BPwjBeuE.js → mtc-parser-Dd4xsgRI.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-BpyqZlNO.js → omit-BFuA5YEs.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-DDrV0XjE.js → pickAttrs-DDfxOAJc.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-1A18Pcl2.js → placementArrow-Be595JCp.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-B8Fnlmso.js → responsiveObserve-7byljUM3.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-x-Fwb2Zm.js → slide-CTWfOTaY.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-aAG5yJl_.js → statusUtils-CNZOuIxM.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-BRQSTMRL.js → styleChecker-DwOcBV78.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-D5X_KozY.js → useFlexGapSupport-B6MNwAO9.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-DN_YIjP-.js → useFs-CzyQjH9r.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-BizxAvjw.js → usePersonalDataHub-Dxh3KiVA.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-B5oAxN93.js → vnode-DVfZiS5N.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-DJhvyzKv.js → zoom-BYme7fbY.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agent.js +22 -0
  151. package/src/commands/auto-mode.js +47 -0
  152. package/src/commands/checkpoint-managed.js +1059 -0
  153. package/src/commands/checkpoint.js +3 -0
  154. package/src/commands/team-distributed.js +3324 -0
  155. package/src/commands/team.js +1130 -74
  156. package/src/data/auto-mode-safety-eval-v1.json +3294 -0
  157. package/src/data/changelog.json +26 -0
  158. package/src/lib/agent-core.js +1 -0
  159. package/src/lib/agent-team/task-lease.js +271 -3
  160. package/src/lib/agent-team/team-adjudication.js +1501 -0
  161. package/src/lib/agent-team/team-budget.js +4 -1
  162. package/src/lib/agent-team/team-control-store.js +582 -0
  163. package/src/lib/agent-team/team-distributed-queue.js +5246 -0
  164. package/src/lib/agent-team/team-process-checkpoint.js +211 -0
  165. package/src/lib/agent-team/team-runner.js +319 -22
  166. package/src/lib/agent-team/team-worktree.js +1403 -95
  167. package/src/lib/auto-mode-safety-classifier.js +2034 -0
  168. package/src/lib/auto-mode-safety-eval.js +1022 -0
  169. package/src/lib/managed-checkpoint-render.js +60 -0
  170. package/src/lib/managed-tool-checkpoint.js +194 -0
  171. package/src/lib/process-execution-broker/index.js +438 -5
  172. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +28 -1
  173. package/src/lib/process-execution-broker/linux-generic-bwrap.js +28 -4
  174. package/src/lib/process-execution-broker/platform-sandbox.js +92 -70
  175. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  176. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  177. package/src/lib/process-execution-broker/windows-sandbox.cs +284 -19
  178. package/src/lib/process-execution-broker/workspace-transaction.js +3556 -0
  179. package/src/lib/secure-file-identity.js +433 -0
  180. package/src/lib/turn-binding.js +160 -2
  181. package/src/lib/with-file-lock.js +140 -23
  182. package/src/repl/agent-repl.js +19 -1
  183. package/src/runtime/agent-core.js +366 -122
  184. package/src/runtime/headless-runner.js +12 -0
  185. package/src/runtime/headless-stream.js +3 -0
  186. package/src/runtime/policies/agent-policy.js +14 -0
  187. package/src/assets/web-panel/assets/Projects-D2FHLUa-.js +0 -1
  188. package/src/assets/web-panel/assets/RemoteSession-BII41Mel.js +0 -5
  189. package/src/assets/web-panel/assets/devWarning-Dy1GJA4S.js +0 -1
  190. package/src/assets/web-panel/assets/index-8zbMbbuf.js +0 -1
  191. package/src/assets/web-panel/assets/index-C97f71oi.js +0 -1
@@ -47,6 +47,7 @@ import {
47
47
  import { consumeIssuedPluginSandboxExecutionContract } from "../plugin-runtime/bin.js";
48
48
  import runtimeProvenanceLedger from "../runtime-provenance-ledger.js";
49
49
  import { credentialAgent } from "./credential-agent.js";
50
+ import { WorkspaceTransactionManager } from "./workspace-transaction.js";
50
51
 
51
52
  const SUPPORTED_SANDBOX_BOUNDARIES = new Set(Object.values(SANDBOX_BOUNDARIES));
52
53
  const SUPPORTED_SANDBOX_PROFILES = new Set([
@@ -537,6 +538,7 @@ class ProcessExecutionBroker extends EventEmitter {
537
538
  };
538
539
  this._ptyAdapter = createNativePtyAdapter();
539
540
  this._hooksEventSink = null;
541
+ this._workspaceTransactionManagers = new Map();
540
542
 
541
543
  this._ensureLogDir();
542
544
  this._loadPermissions();
@@ -868,7 +870,8 @@ class ProcessExecutionBroker extends EventEmitter {
868
870
  requiredBoundaries.some(
869
871
  (boundary) =>
870
872
  boundary !== SANDBOX_BOUNDARIES.FILESYSTEM &&
871
- boundary !== SANDBOX_BOUNDARIES.NETWORK,
873
+ boundary !== SANDBOX_BOUNDARIES.NETWORK &&
874
+ boundary !== SANDBOX_BOUNDARIES.PROCESS_TREE,
872
875
  )
873
876
  ) {
874
877
  return null;
@@ -986,6 +989,12 @@ class ProcessExecutionBroker extends EventEmitter {
986
989
  delete options.sandboxExecutionContract;
987
990
  }
988
991
 
992
+ _stripWorkspaceTransactionOptions(options) {
993
+ delete options.workspaceTransactionId;
994
+ delete options.workspaceTransactionStateDir;
995
+ delete options.workspaceTransactionCapture;
996
+ }
997
+
989
998
  _sandboxUnavailablePlan(command, args, options, reason, sandboxPolicy = {}) {
990
999
  return {
991
1000
  contractVersion: 1,
@@ -1124,6 +1133,7 @@ class ProcessExecutionBroker extends EventEmitter {
1124
1133
  let genericWorkspaceProbe = false;
1125
1134
  let sanitizedFilesystemPolicy = null;
1126
1135
  let sanitizedNetworkPolicy = null;
1136
+ let sanitizedProcessTreePolicy = null;
1127
1137
  let sanitizedPtyPolicy = null;
1128
1138
  if (plan.runtimeProbe !== null && plan.runtimeProbe !== undefined) {
1129
1139
  if (
@@ -1515,6 +1525,12 @@ class ProcessExecutionBroker extends EventEmitter {
1515
1525
  "outsideMarkerHidden",
1516
1526
  "networkNamespace",
1517
1527
  "networkNamespaceChanged",
1528
+ "pidNamespace",
1529
+ "pidNamespaceChanged",
1530
+ "processTreeCloseProbe",
1531
+ "bubblewrapPid1Reaper",
1532
+ "dieWithParent",
1533
+ "closeImpliesProcessTreeClosed",
1518
1534
  "socketCreationDenied",
1519
1535
  "descriptorMounts",
1520
1536
  "mountTopologyAtomic",
@@ -1564,6 +1580,7 @@ class ProcessExecutionBroker extends EventEmitter {
1564
1580
  if (genericWorkspaceKind) {
1565
1581
  const filesystemPolicy = plan.filesystemPolicy;
1566
1582
  const networkPolicy = plan.networkPolicy;
1583
+ const processTreePolicy = plan.processTreePolicy;
1567
1584
  const ptyPolicy = plan.ptyPolicy ?? null;
1568
1585
  const ptyPlan =
1569
1586
  ptyPolicy !== null &&
@@ -1607,6 +1624,7 @@ class ProcessExecutionBroker extends EventEmitter {
1607
1624
  /^[a-f0-9]{64}$/.test(plan.runtimeProbe.contractDigest || "") &&
1608
1625
  guarantees.includes(SANDBOX_BOUNDARIES.FILESYSTEM) &&
1609
1626
  guarantees.includes(SANDBOX_BOUNDARIES.NETWORK) &&
1627
+ guarantees.includes(SANDBOX_BOUNDARIES.PROCESS_TREE) &&
1610
1628
  plan.runtimeProbe.attempted === true &&
1611
1629
  plan.runtimeProbe.runnable === true &&
1612
1630
  plan.runtimeProbe.reason === null &&
@@ -1633,6 +1651,12 @@ class ProcessExecutionBroker extends EventEmitter {
1633
1651
  "outsideMarkerHidden",
1634
1652
  "networkNamespace",
1635
1653
  "networkNamespaceChanged",
1654
+ "pidNamespace",
1655
+ "pidNamespaceChanged",
1656
+ "processTreeCloseProbe",
1657
+ "bubblewrapPid1Reaper",
1658
+ "dieWithParent",
1659
+ "closeImpliesProcessTreeClosed",
1636
1660
  "socketCreationDenied",
1637
1661
  "descriptorMounts",
1638
1662
  ].every((field) => plan.runtimeProbe[field] === true) &&
@@ -1670,14 +1694,24 @@ class ProcessExecutionBroker extends EventEmitter {
1670
1694
  networkPolicy?.namespace === "new" &&
1671
1695
  networkPolicy?.namespaceIdentityChanged === true &&
1672
1696
  networkPolicy?.seccomp === "deny-network-creation" &&
1697
+ processTreePolicy?.namespace === "new" &&
1698
+ processTreePolicy?.namespaceIdentityChanged === true &&
1699
+ processTreePolicy?.init === "bubblewrap-pid1-reaper" &&
1700
+ processTreePolicy?.parentDeathSignal === "SIGKILL" &&
1701
+ processTreePolicy?.asPid1 === false &&
1702
+ processTreePolicy?.closeFence === "pid-namespace-empty-or-killed" &&
1673
1703
  plan.command.startsWith("/proc/self/fd/") &&
1674
1704
  plan.options?.cwd === "/" &&
1675
1705
  plan.options?.shell === false &&
1706
+ plan.options?.detached === false &&
1676
1707
  Array.isArray(plan.options?.stdio) &&
1677
1708
  plan.args.includes("--bind-fd") &&
1678
1709
  plan.args.includes("--ro-bind-fd") &&
1679
1710
  plan.args.includes("--remount-ro") &&
1680
1711
  plan.args.includes("--unshare-net") &&
1712
+ plan.args.includes("--unshare-pid") &&
1713
+ plan.args.includes("--die-with-parent") &&
1714
+ !plan.args.includes("--as-pid-1") &&
1681
1715
  plan.args.includes("--seccomp") &&
1682
1716
  !plan.args.includes("--ro-bind") &&
1683
1717
  ptyPlanValid;
@@ -1715,6 +1749,14 @@ class ProcessExecutionBroker extends EventEmitter {
1715
1749
  namespaceIdentityChanged: networkPolicy.namespaceIdentityChanged,
1716
1750
  seccomp: networkPolicy.seccomp,
1717
1751
  });
1752
+ sanitizedProcessTreePolicy = Object.freeze({
1753
+ namespace: processTreePolicy.namespace,
1754
+ namespaceIdentityChanged: processTreePolicy.namespaceIdentityChanged,
1755
+ init: processTreePolicy.init,
1756
+ parentDeathSignal: processTreePolicy.parentDeathSignal,
1757
+ asPid1: processTreePolicy.asPid1,
1758
+ closeFence: processTreePolicy.closeFence,
1759
+ });
1718
1760
  sanitizedPtyPolicy = ptyPlan
1719
1761
  ? Object.freeze({
1720
1762
  mode: ptyPolicy.mode,
@@ -1835,6 +1877,7 @@ class ProcessExecutionBroker extends EventEmitter {
1835
1877
  ? {
1836
1878
  filesystemPolicy: sanitizedFilesystemPolicy,
1837
1879
  networkPolicy: sanitizedNetworkPolicy,
1880
+ processTreePolicy: sanitizedProcessTreePolicy,
1838
1881
  ptyPolicy: sanitizedPtyPolicy,
1839
1882
  }
1840
1883
  : {}),
@@ -2038,6 +2081,17 @@ class ProcessExecutionBroker extends EventEmitter {
2038
2081
  seccomp: plan.networkPolicy.seccomp,
2039
2082
  }
2040
2083
  : null;
2084
+ auditEntry.sandboxProcessTreePolicy = genericPolicy
2085
+ ? {
2086
+ namespace: plan.processTreePolicy.namespace,
2087
+ namespaceIdentityChanged:
2088
+ plan.processTreePolicy.namespaceIdentityChanged,
2089
+ init: plan.processTreePolicy.init,
2090
+ parentDeathSignal: plan.processTreePolicy.parentDeathSignal,
2091
+ asPid1: plan.processTreePolicy.asPid1,
2092
+ closeFence: plan.processTreePolicy.closeFence,
2093
+ }
2094
+ : null;
2041
2095
  auditEntry.sandboxPtyPolicy =
2042
2096
  genericPolicy && plan.ptyPolicy
2043
2097
  ? {
@@ -2115,6 +2169,34 @@ class ProcessExecutionBroker extends EventEmitter {
2115
2169
  return run;
2116
2170
  }
2117
2171
 
2172
+ _createWorkspaceProcessCloseFence(auditEntry, proc) {
2173
+ if (
2174
+ !Array.isArray(auditEntry.workspaceTransactionIds) ||
2175
+ auditEntry.workspaceTransactionIds.length === 0
2176
+ ) {
2177
+ return null;
2178
+ }
2179
+ return new Promise((resolve) => {
2180
+ if (typeof proc?.once === "function") {
2181
+ proc.once("close", (exitCode, signal) => {
2182
+ resolve({ observed: true, exitCode, signal });
2183
+ });
2184
+ return;
2185
+ }
2186
+ if (typeof proc?.onExit === "function") {
2187
+ proc.onExit((event = {}) => {
2188
+ resolve({
2189
+ observed: true,
2190
+ exitCode: event.exitCode ?? null,
2191
+ signal: event.signal ?? null,
2192
+ });
2193
+ });
2194
+ return;
2195
+ }
2196
+ resolve({ observed: false, exitCode: null, signal: null });
2197
+ });
2198
+ }
2199
+
2118
2200
  _runPostSpawnSandbox(proc, plan, auditEntry) {
2119
2201
  if (!plan.applied || !plan.postSpawn.required) {
2120
2202
  this._applySandboxAudit(auditEntry, plan, plan.applied);
@@ -2142,10 +2224,12 @@ class ProcessExecutionBroker extends EventEmitter {
2142
2224
  } catch {
2143
2225
  // The sandbox failure remains fatal even if the child already exited.
2144
2226
  }
2145
- throw this._sandboxError(
2227
+ const failure = this._sandboxError(
2146
2228
  "post_spawn_failed",
2147
2229
  `Post-spawn sandbox setup failed: ${error.message}`,
2148
2230
  );
2231
+ failure.processTerminationRequested = true;
2232
+ throw failure;
2149
2233
  }
2150
2234
  process.emitWarning(
2151
2235
  `Post-spawn sandbox setup failed (continuing without): ${error.message}`,
@@ -2163,10 +2247,12 @@ class ProcessExecutionBroker extends EventEmitter {
2163
2247
  } catch {
2164
2248
  // The sandbox failure remains fatal even if the child already exited.
2165
2249
  }
2166
- throw this._sandboxError(
2250
+ const failure = this._sandboxError(
2167
2251
  "async_post_spawn_contract_violation",
2168
2252
  "Strict sandbox adapter returned an asynchronous post-spawn result",
2169
2253
  );
2254
+ failure.processTerminationRequested = true;
2255
+ throw failure;
2170
2256
  }
2171
2257
  auditEntry.sandboxed = false;
2172
2258
  auditEntry.sandboxState = "pending";
@@ -2349,6 +2435,7 @@ class ProcessExecutionBroker extends EventEmitter {
2349
2435
  policyDigest: auditEntry.sandboxPolicyDigest || null,
2350
2436
  filesystemPolicy: auditEntry.sandboxFilesystemPolicy || null,
2351
2437
  networkPolicy: auditEntry.sandboxNetworkPolicy || null,
2438
+ processTreePolicy: auditEntry.sandboxProcessTreePolicy || null,
2352
2439
  },
2353
2440
  traceId:
2354
2441
  traceCtx?.traceId || auditEntry.traceId || `trace-${Date.now()}`,
@@ -2380,11 +2467,235 @@ class ProcessExecutionBroker extends EventEmitter {
2380
2467
  this._hooksEventSink = hooks;
2381
2468
  }
2382
2469
 
2470
+ _workspaceTransactionManager(stateDir, _lockDir) {
2471
+ const stateKey =
2472
+ typeof stateDir === "string" && stateDir.trim()
2473
+ ? path.resolve(stateDir)
2474
+ : "<default>";
2475
+ // Lock authority is intentionally not caller-selectable. Keep accepting
2476
+ // legacy lockDir-bearing option objects at the Broker boundary, but route
2477
+ // every one of them to the same canonical manager.
2478
+ const key = `${stateKey}\0<default>`;
2479
+ let manager = this._workspaceTransactionManagers.get(key);
2480
+ if (!manager) {
2481
+ manager = new WorkspaceTransactionManager({
2482
+ ...(stateKey === "<default>" ? {} : { stateDir: stateKey }),
2483
+ });
2484
+ this._workspaceTransactionManagers.set(key, manager);
2485
+ }
2486
+ return manager;
2487
+ }
2488
+
2489
+ _workspaceTransactionMembershipForCwd(cwd) {
2490
+ const memberships = [];
2491
+ for (const manager of this._workspaceTransactionManagers.values()) {
2492
+ const membership = manager.activeWorkspaceMembershipForCwd(cwd);
2493
+ if (!membership) continue;
2494
+ const key =
2495
+ process.platform === "win32"
2496
+ ? path.resolve(membership.workspaceRoot).toLowerCase()
2497
+ : path.resolve(membership.workspaceRoot);
2498
+ if (!memberships.some((entry) => entry.key === key)) {
2499
+ memberships.push({ key, ...membership });
2500
+ }
2501
+ }
2502
+ if (memberships.length > 1) {
2503
+ const error = new Error(
2504
+ "multiple workspace transactions claim the same process cwd",
2505
+ );
2506
+ error.code = "WORKSPACE_TRANSACTION_OVERLAPPING_WORKSPACE";
2507
+ error.workspaceRoots = memberships.map((entry) => entry.workspaceRoot);
2508
+ throw error;
2509
+ }
2510
+ return memberships[0] || null;
2511
+ }
2512
+
2513
+ _workspaceTransactionRequiredBoundaries(cwd, membership = undefined) {
2514
+ const resolved =
2515
+ membership === undefined
2516
+ ? this._workspaceTransactionMembershipForCwd(cwd)
2517
+ : membership;
2518
+ return resolved ? [SANDBOX_BOUNDARIES.PROCESS_TREE] : [];
2519
+ }
2520
+
2521
+ _workspaceTransactionRootForCwd(cwd, membership = undefined) {
2522
+ const resolved =
2523
+ membership === undefined
2524
+ ? this._workspaceTransactionMembershipForCwd(cwd)
2525
+ : membership;
2526
+ return resolved?.workspaceRoot || null;
2527
+ }
2528
+
2529
+ _withWorkspaceTransactionBoundaries(
2530
+ options,
2531
+ cwd,
2532
+ { command, args = [], sync = false, pty = false } = {},
2533
+ ) {
2534
+ const membership = this._workspaceTransactionMembershipForCwd(cwd);
2535
+ const required = this._workspaceTransactionRequiredBoundaries(
2536
+ cwd,
2537
+ membership,
2538
+ );
2539
+ if (required.length === 0) return options;
2540
+ if (
2541
+ options.requiredBoundaries !== undefined &&
2542
+ !Array.isArray(options.requiredBoundaries)
2543
+ ) {
2544
+ // Preserve the invalid value so normal policy validation rejects it.
2545
+ return options;
2546
+ }
2547
+ const nextOptions = {
2548
+ ...options,
2549
+ ...(membership?.cwd ? { cwd: membership.cwd } : {}),
2550
+ requiredBoundaries: [
2551
+ ...new Set([...(options.requiredBoundaries || []), ...required]),
2552
+ ],
2553
+ };
2554
+ const workspaceRoot = this._workspaceTransactionRootForCwd(cwd, membership);
2555
+ // Transaction preflight always rejects detached writers. Do not let a
2556
+ // platform contract fail first and hide that stable, typed denial.
2557
+ if (
2558
+ workspaceRoot &&
2559
+ nextOptions.detached !== true &&
2560
+ nextOptions.sandboxExecutionContract === undefined
2561
+ ) {
2562
+ const contract = this.issueLinuxWorkspaceSandboxExecutionContract(
2563
+ command,
2564
+ args,
2565
+ nextOptions,
2566
+ workspaceRoot,
2567
+ { sync, pty },
2568
+ );
2569
+ if (contract) nextOptions.sandboxExecutionContract = contract;
2570
+ }
2571
+ return nextOptions;
2572
+ }
2573
+
2574
+ _requiresExplicitLinuxWorkspaceShell(cwd) {
2575
+ return (
2576
+ process.platform === "linux" &&
2577
+ this._workspaceTransactionRequiredBoundaries(cwd).length > 0
2578
+ );
2579
+ }
2580
+
2581
+ _explicitLinuxShellInvocation(command, options = {}) {
2582
+ return {
2583
+ command:
2584
+ typeof options.shell === "string" && options.shell.trim()
2585
+ ? options.shell
2586
+ : "/bin/sh",
2587
+ args: ["-c", String(command)],
2588
+ };
2589
+ }
2590
+
2591
+ /**
2592
+ * Start a fail-closed, content-addressed workspace checkpoint transaction.
2593
+ *
2594
+ * Active transactions automatically bind every Broker spawn whose canonical
2595
+ * cwd is inside their workspace. The transaction itself owns accept/rollback
2596
+ * and durable evidence APIs.
2597
+ */
2598
+ beginWorkspaceTransaction(options = {}) {
2599
+ return this._workspaceTransactionManager(
2600
+ options.stateDir,
2601
+ options.lockDir,
2602
+ ).begin(options);
2603
+ }
2604
+
2605
+ recoverWorkspaceTransactions(options = {}) {
2606
+ return this._workspaceTransactionManager(
2607
+ options.stateDir,
2608
+ options.lockDir,
2609
+ ).recoverPending(options);
2610
+ }
2611
+
2612
+ inspectWorkspaceTransaction(id, options = {}) {
2613
+ return this._workspaceTransactionManager(
2614
+ options.stateDir,
2615
+ options.lockDir,
2616
+ ).inspect(id);
2617
+ }
2618
+
2619
+ listWorkspaceTransactions(options = {}) {
2620
+ return this._workspaceTransactionManager(
2621
+ options.stateDir,
2622
+ options.lockDir,
2623
+ ).list(options);
2624
+ }
2625
+
2626
+ restoreWorkspaceTransaction(id, options = {}) {
2627
+ return this._workspaceTransactionManager(
2628
+ options.stateDir,
2629
+ options.lockDir,
2630
+ ).restore(id, options);
2631
+ }
2632
+
2633
+ undoWorkspaceTransactionRestore(id, options = {}) {
2634
+ return this._workspaceTransactionManager(
2635
+ options.stateDir,
2636
+ options.lockDir,
2637
+ ).undoRestore(id, options);
2638
+ }
2639
+
2640
+ getWorkspaceTransaction(id) {
2641
+ for (const manager of this._workspaceTransactionManagers.values()) {
2642
+ const transaction = manager.get(id);
2643
+ if (transaction) return transaction;
2644
+ }
2645
+ return null;
2646
+ }
2647
+
2648
+ _prepareWorkspaceTransactionAudit(auditEntry) {
2649
+ const transactionIds = [];
2650
+ for (const manager of this._workspaceTransactionManagers.values()) {
2651
+ transactionIds.push(...manager.prepareSpawn(auditEntry));
2652
+ }
2653
+ auditEntry.workspaceTransactionIds = [...new Set(transactionIds)].sort();
2654
+ return auditEntry.workspaceTransactionIds;
2655
+ }
2656
+
2657
+ _preflightWorkspaceTransactionAudit(auditEntry) {
2658
+ const transactionIds = [];
2659
+ try {
2660
+ for (const manager of this._workspaceTransactionManagers.values()) {
2661
+ transactionIds.push(...manager.preflightSpawn(auditEntry));
2662
+ }
2663
+ } catch (error) {
2664
+ error.auditEntry = auditEntry;
2665
+ throw error;
2666
+ }
2667
+ return [...new Set(transactionIds)].sort();
2668
+ }
2669
+
2670
+ _bindWorkspaceTransactionProcess(auditEntry, proc) {
2671
+ for (const manager of this._workspaceTransactionManagers.values()) {
2672
+ manager.bindProcess(auditEntry, proc);
2673
+ }
2674
+ }
2675
+
2676
+ _updateWorkspaceTransactionProcessGuarantees(auditEntry) {
2677
+ for (const manager of this._workspaceTransactionManagers.values()) {
2678
+ manager.updateProcessGuarantees(auditEntry);
2679
+ }
2680
+ }
2681
+
2682
+ _settleWorkspaceTransactionSpawn(auditEntry, outcome = {}) {
2683
+ for (const manager of this._workspaceTransactionManagers.values()) {
2684
+ manager.settleSpawn(auditEntry, outcome);
2685
+ }
2686
+ }
2687
+
2383
2688
  spawn(command, args, options = {}) {
2384
2689
  const executionId = crypto.randomUUID();
2385
2690
  const startTime = Date.now();
2386
2691
  const origin = options.origin || "unknown";
2387
- const cwd = options.cwd || process.cwd();
2692
+ const requestedCwd = options.cwd || process.cwd();
2693
+ options = this._withWorkspaceTransactionBoundaries(options, requestedCwd, {
2694
+ command,
2695
+ args,
2696
+ sync: false,
2697
+ });
2698
+ const cwd = options.cwd || requestedCwd;
2388
2699
  const scope = options.scope || "default";
2389
2700
  const policy = options.policy || this._checkPermission(origin, command);
2390
2701
  const isDangerous = this._isDangerousCommand(
@@ -2408,6 +2719,7 @@ class ProcessExecutionBroker extends EventEmitter {
2408
2719
  policy,
2409
2720
  isDangerous,
2410
2721
  shell: !!options.shell,
2722
+ detached: options.detached === true,
2411
2723
  pid: null,
2412
2724
  exitCode: null,
2413
2725
  endTime: null,
@@ -2463,9 +2775,12 @@ class ProcessExecutionBroker extends EventEmitter {
2463
2775
  }
2464
2776
 
2465
2777
  // 传播traceparent环境变量
2778
+ this._preflightWorkspaceTransactionAudit(auditEntry);
2779
+
2466
2780
  const spawnOpts = this._sanitizeOptions(options);
2467
2781
  this._stripSandboxControlOptions(spawnOpts);
2468
2782
  this._stripPluginControlOptions(spawnOpts);
2783
+ this._stripWorkspaceTransactionOptions(spawnOpts);
2469
2784
  if (traceCtx) {
2470
2785
  spawnOpts.env = { ...(spawnOpts.env || process.env) };
2471
2786
  spawnOpts.env.TRACEPARENT = traceCtx.traceparent;
@@ -2527,17 +2842,45 @@ class ProcessExecutionBroker extends EventEmitter {
2527
2842
  sandboxPlan,
2528
2843
  sandboxPlan.applied && !sandboxPlan.postSpawn.required,
2529
2844
  );
2845
+ try {
2846
+ this._prepareWorkspaceTransactionAudit(auditEntry);
2847
+ } catch (transactionError) {
2848
+ sandboxPlan.cleanup?.();
2849
+ transactionError.auditEntry = auditEntry;
2850
+ throw transactionError;
2851
+ }
2530
2852
 
2531
2853
  // Use native spawn from _native (set by patch-child-process.js)
2532
2854
  const nativeSpawnFn = this._native?.spawn || nativeSpawn;
2533
2855
  let proc;
2534
2856
  try {
2535
2857
  proc = nativeSpawnFn(command, args, optsForSpawn);
2858
+ this._bindWorkspaceTransactionProcess(auditEntry, proc);
2536
2859
  } catch (spawnError) {
2860
+ if (proc && typeof proc.kill === "function") {
2861
+ try {
2862
+ proc.kill("SIGKILL");
2863
+ } catch {
2864
+ // The transaction remains unsettled if shutdown cannot be proven.
2865
+ }
2866
+ }
2867
+ if (!proc) {
2868
+ try {
2869
+ this._settleWorkspaceTransactionSpawn(auditEntry, {
2870
+ error: spawnError.message,
2871
+ });
2872
+ } catch {
2873
+ // An unsettled durable execution keeps the transaction fail-closed.
2874
+ }
2875
+ }
2537
2876
  sandboxPlan.cleanup?.();
2538
2877
  throw spawnError;
2539
2878
  }
2540
2879
  auditEntry.pid = proc.pid;
2880
+ const workspaceProcessClosed = this._createWorkspaceProcessCloseFence(
2881
+ auditEntry,
2882
+ proc,
2883
+ );
2541
2884
  const cleanupSandbox = this._scheduleSandboxCleanup(
2542
2885
  proc,
2543
2886
  sandboxPlan.cleanup,
@@ -2558,6 +2901,7 @@ class ProcessExecutionBroker extends EventEmitter {
2558
2901
  }
2559
2902
  try {
2560
2903
  this._runPostSpawnSandbox(proc, sandboxPlan, auditEntry);
2904
+ this._updateWorkspaceTransactionProcessGuarantees(auditEntry);
2561
2905
  auditEntry.pid = proc.pid;
2562
2906
  if (Number.isSafeInteger(proc.sandboxWrapperPid)) {
2563
2907
  auditEntry.sandboxWrapperPid = proc.sandboxWrapperPid;
@@ -2566,9 +2910,19 @@ class ProcessExecutionBroker extends EventEmitter {
2566
2910
  auditEntry.sandboxTargetPid = proc.sandboxTargetPid;
2567
2911
  }
2568
2912
  } catch (postSpawnError) {
2913
+ if (postSpawnError.processTerminationRequested !== true) {
2914
+ try {
2915
+ proc.kill?.("SIGKILL");
2916
+ } catch {
2917
+ // The close fence remains pending and recovery stays fail-closed.
2918
+ }
2919
+ }
2569
2920
  cleanupSandbox();
2570
2921
  this._recordSandboxDenial(auditEntry, postSpawnError, startTime);
2571
2922
  postSpawnError.auditEntry = auditEntry;
2923
+ postSpawnError.spawnedProcess = proc;
2924
+ postSpawnError.workspaceProcessClosed = workspaceProcessClosed;
2925
+ postSpawnError.workspaceTerminationRequested = true;
2572
2926
  throw postSpawnError;
2573
2927
  }
2574
2928
 
@@ -2624,7 +2978,13 @@ class ProcessExecutionBroker extends EventEmitter {
2624
2978
  const executionId = crypto.randomUUID();
2625
2979
  const startTime = Date.now();
2626
2980
  const origin = options.origin || "unknown";
2627
- const cwd = options.cwd || process.cwd();
2981
+ const requestedCwd = options.cwd || process.cwd();
2982
+ options = this._withWorkspaceTransactionBoundaries(options, requestedCwd, {
2983
+ command,
2984
+ args,
2985
+ sync: true,
2986
+ });
2987
+ const cwd = options.cwd || requestedCwd;
2628
2988
  const scope = options.scope || "default";
2629
2989
  const policy = options.policy || this._checkPermission(origin, command);
2630
2990
  const isDangerous = this._isDangerousCommand(
@@ -2648,6 +3008,7 @@ class ProcessExecutionBroker extends EventEmitter {
2648
3008
  policy,
2649
3009
  isDangerous,
2650
3010
  shell: !!options.shell,
3011
+ detached: options.detached === true,
2651
3012
  sync: true,
2652
3013
  pluginId: options.pluginId || null,
2653
3014
  pluginVersion: options.pluginVersion || null,
@@ -2693,9 +3054,12 @@ class ProcessExecutionBroker extends EventEmitter {
2693
3054
  throw err;
2694
3055
  }
2695
3056
 
3057
+ this._preflightWorkspaceTransactionAudit(auditEntry);
3058
+
2696
3059
  const spawnOpts = this._sanitizeOptions(options);
2697
3060
  this._stripSandboxControlOptions(spawnOpts);
2698
3061
  this._stripPluginControlOptions(spawnOpts);
3062
+ this._stripWorkspaceTransactionOptions(spawnOpts);
2699
3063
  if (traceCtx) {
2700
3064
  spawnOpts.env = { ...(spawnOpts.env || process.env) };
2701
3065
  spawnOpts.env.TRACEPARENT = traceCtx.traceparent;
@@ -2754,10 +3118,22 @@ class ProcessExecutionBroker extends EventEmitter {
2754
3118
  args = [...sandboxPlan.args];
2755
3119
  const optsForSync = { ...sandboxPlan.options };
2756
3120
  this._applySandboxAudit(auditEntry, sandboxPlan, sandboxPlan.applied);
3121
+ try {
3122
+ this._prepareWorkspaceTransactionAudit(auditEntry);
3123
+ } catch (transactionError) {
3124
+ sandboxPlan.cleanup?.();
3125
+ transactionError.auditEntry = auditEntry;
3126
+ throw transactionError;
3127
+ }
2757
3128
 
2758
3129
  const nativeSpawnSyncFn = this._native?.spawnSync || nativeSpawnSync;
2759
3130
  try {
2760
3131
  const result = nativeSpawnSyncFn(command, args, optsForSync);
3132
+ this._settleWorkspaceTransactionSpawn(auditEntry, {
3133
+ exitCode: result.status,
3134
+ signal: result.signal,
3135
+ error: result.error?.message || null,
3136
+ });
2761
3137
  if (sandboxPlan.applied) this._stats.sandboxed++;
2762
3138
  auditEntry.exitCode = result.status;
2763
3139
  auditEntry.endTime = Date.now();
@@ -2766,6 +3142,14 @@ class ProcessExecutionBroker extends EventEmitter {
2766
3142
  this._writeRplEntry(auditEntry, "completed");
2767
3143
  return result;
2768
3144
  } catch (err) {
3145
+ try {
3146
+ this._settleWorkspaceTransactionSpawn(auditEntry, {
3147
+ error: err.message,
3148
+ });
3149
+ } catch {
3150
+ // Preserve the primary spawn failure; transaction settlement remains
3151
+ // fail-closed and blocks accept/rollback.
3152
+ }
2769
3153
  auditEntry.error = err.message;
2770
3154
  auditEntry.endTime = Date.now();
2771
3155
  auditEntry.durationMs = auditEntry.endTime - startTime;
@@ -2788,6 +3172,11 @@ class ProcessExecutionBroker extends EventEmitter {
2788
3172
  if (!ptyModule || typeof ptyModule.spawn !== "function") {
2789
3173
  throw new TypeError("pty_module_spawn_unavailable");
2790
3174
  }
3175
+ options = this._withWorkspaceTransactionBoundaries(
3176
+ options,
3177
+ options.cwd || process.cwd(),
3178
+ { command, args, sync: false, pty: true },
3179
+ );
2791
3180
  const executionId = crypto.randomUUID();
2792
3181
  const startTime = Date.now();
2793
3182
  const origin = options.origin || "terminal:pty";
@@ -2806,6 +3195,7 @@ class ProcessExecutionBroker extends EventEmitter {
2806
3195
  policy,
2807
3196
  operation: "pty.spawn",
2808
3197
  pty: true,
3198
+ detached: options.detached === true,
2809
3199
  sandboxed: false,
2810
3200
  sandboxReason: "native_pty_host_boundary",
2811
3201
  sandboxRequired: [],
@@ -2850,6 +3240,7 @@ class ProcessExecutionBroker extends EventEmitter {
2850
3240
  const spawnOptions = { ...options, args: [...auditEntry.args] };
2851
3241
  this._stripSandboxControlOptions(spawnOptions);
2852
3242
  this._stripPluginControlOptions(spawnOptions);
3243
+ this._stripWorkspaceTransactionOptions(spawnOptions);
2853
3244
  try {
2854
3245
  const terminalName =
2855
3246
  typeof options.name === "string" && options.name
@@ -2880,12 +3271,23 @@ class ProcessExecutionBroker extends EventEmitter {
2880
3271
  }
2881
3272
  const filteredArgs = spawnOptions.args || [];
2882
3273
  delete spawnOptions.args;
3274
+ this._prepareWorkspaceTransactionAudit(auditEntry);
2883
3275
 
2884
3276
  if (sandboxPolicy.requiredBoundaries.length === 0) {
2885
3277
  delete spawnOptions.origin;
2886
3278
  delete spawnOptions.policy;
2887
3279
  delete spawnOptions.scope;
2888
3280
  const proc = ptyModule.spawn(command, filteredArgs, spawnOptions);
3281
+ try {
3282
+ this._bindWorkspaceTransactionProcess(auditEntry, proc);
3283
+ } catch (transactionError) {
3284
+ try {
3285
+ proc.kill?.();
3286
+ } catch {
3287
+ // The durable execution remains unsettled and blocks recovery.
3288
+ }
3289
+ throw transactionError;
3290
+ }
2889
3291
  auditEntry.pid = proc?.pid ?? null;
2890
3292
  auditEntry.endTime = Date.now();
2891
3293
  auditEntry.durationMs = auditEntry.endTime - startTime;
@@ -2966,6 +3368,7 @@ class ProcessExecutionBroker extends EventEmitter {
2966
3368
  ],
2967
3369
  });
2968
3370
  proc = wrapSandboxedPty(terminal, child, command, this._ptyAdapter);
3371
+ this._bindWorkspaceTransactionProcess(auditEntry, proc);
2969
3372
  } catch (ptyError) {
2970
3373
  if (child && typeof child.kill === "function") {
2971
3374
  try {
@@ -3025,6 +3428,7 @@ class ProcessExecutionBroker extends EventEmitter {
3025
3428
  }
3026
3429
  sandboxPlan.cleanup?.();
3027
3430
  this._applySandboxAudit(auditEntry, sandboxPlan, true);
3431
+ this._updateWorkspaceTransactionProcessGuarantees(auditEntry);
3028
3432
  this._stats.sandboxed++;
3029
3433
 
3030
3434
  child.once("exit", (code, signal) => {
@@ -3064,6 +3468,14 @@ class ProcessExecutionBroker extends EventEmitter {
3064
3468
  exec(command, options, callback) {
3065
3469
  const opts = typeof options === "function" ? {} : options;
3066
3470
  const cb = typeof options === "function" ? options : callback;
3471
+ if (this._requiresExplicitLinuxWorkspaceShell(opts.cwd || process.cwd())) {
3472
+ const invocation = this._explicitLinuxShellInvocation(command, opts);
3473
+ return this.spawn(invocation.command, invocation.args, {
3474
+ ...opts,
3475
+ shell: false,
3476
+ origin: opts.origin || "shell:exec",
3477
+ });
3478
+ }
3067
3479
  return this.spawn(command, [], {
3068
3480
  ...opts,
3069
3481
  shell: true,
@@ -3072,6 +3484,27 @@ class ProcessExecutionBroker extends EventEmitter {
3072
3484
  }
3073
3485
 
3074
3486
  execSync(command, options = {}) {
3487
+ if (
3488
+ this._requiresExplicitLinuxWorkspaceShell(options.cwd || process.cwd())
3489
+ ) {
3490
+ const invocation = this._explicitLinuxShellInvocation(command, options);
3491
+ const result = this.spawnSync(invocation.command, invocation.args, {
3492
+ ...options,
3493
+ shell: false,
3494
+ origin: options.origin || "shell:execSync",
3495
+ });
3496
+ if (result?.error) throw result.error;
3497
+ if (result?.status != null && result.status !== 0) {
3498
+ const error = new Error(
3499
+ `Command failed (exit ${result.status}): ${command}`,
3500
+ );
3501
+ error.status = result.status;
3502
+ error.stdout = result.stdout;
3503
+ error.stderr = result.stderr;
3504
+ throw error;
3505
+ }
3506
+ return result?.stdout ?? "";
3507
+ }
3075
3508
  const spawnOpts = {
3076
3509
  ...options,
3077
3510
  shell: true,