chainlesschain 0.162.184 → 0.162.185

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 (183) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-DzAl5fGx.js → AIOps-Co4ipAKx.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-DAHtMGeC.js → ActionButton-BpsoDrDq.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-BHQaexv5.js → Analytics-DMUqeBik.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-Phz_pD0n.js → AppLayout-C6fD_dXr.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-DHt47kCA.js → Artifacts-DW0YlQlV.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit--Y_8FULl.js → Audit-DXtHiiFK.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-N1zchH7D.js → BackgroundAgents-BgmF62U-.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-B_BoyN30.js → Backup-D8vhVLQC.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-Gl4uQHcT.js → BaseInput-D7nCtY2X.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-Cg-eIyAZ.js → Chat-CBZLEV2G.js} +5 -5
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer-CtDb-2Qu.js +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-D0MGR2wA.js → Checkbox-DJLkeD4M.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-BdObJEFn.js → Codegen-Cs6xO8Fd.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col--ghxXT3z.js → Col-Dtx9wxfT.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-BUFeXOqz.js → Community-DiigJg4n.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-DhQO2_Mr.js → Compact-B67oMDDL.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-3HoTqKYd.js → Compliance-BBDl-Jos.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-B3zmO4VW.js → Cowork-Ba98hSsa.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-CJDrZOcc.js → Cron-DBWQZIz7.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-SMl15obd.js → Crosschain-OcrKzxAt.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-CgTrTWrl.js → DID-mW2eSbjM.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-dXFKaKol.js → Dashboard-BmsDPmKJ.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-qQHEhCvI.js → Dropdown-CVzmCyRx.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-C_ej0MWH.js → EmailListRenderer-vVgqKzhh.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-D6ajoE7I.js → FamilyGuardDashboard-CWY5t_ZJ.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-Cio2y49h.js → Federation-ZGomhjxr.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-ClJz1cYp.js → FormItemContext-DyaknbLr.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-Dr_8Maca.js → GenericCardRenderer-CmIvN1qr.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-CevLwp5u.js → Git-DQOJIYkG.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-CW9YyQUY.js → Governance-CMiY-EEp.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-CQqeAyER.js → Inference-D6IXuucf.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-cuGVxHrw.js → KnowledgeGraph-6jkTL0hV.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-DIuHri5d.js → Logs-N0jD-349.js} +2 -2
  36. package/src/assets/web-panel/assets/MarkdownRenderer-BL1IVXm-.js +1 -0
  37. package/src/assets/web-panel/assets/{Marketplace-CY1VhFGx.js → Marketplace-WhYuhBxm.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-BBAbi1vt.js → McpTools-DTeO28xy.js} +5 -5
  39. package/src/assets/web-panel/assets/{Memory-BWYGHxJA.js → Memory-MSHzPSE-.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-CPIFkW7A.js → MobileBridge-Cyqs3J3W.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-DQyWxCho.js → MobileProjects-7IN2hWHg.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-Bo3GnQbx.js → Mtc-MWBnezNM.js} +5 -5
  43. package/src/assets/web-panel/assets/{MtcAudit-C8-c4rSW.js → MtcAudit-DQu35Nu-.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-ChqMTtoq.js → Multisig-CQS_M-DP.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-C48TyhvI.js → NLProgramming-WD16vQvY.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-zogUFWxp.js → Notes-DqsiSwOq.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-v-K9XpSB.js → NotificationSettings-D3LoxO-P.js} +1 -1
  48. package/src/assets/web-panel/assets/OrderTableRenderer-C5jPoTd6.js +1 -0
  49. package/src/assets/web-panel/assets/{Organization-Boi1dQfR.js → Organization-48aMBJso.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-DM5YWwgA.js → Overflow-j-a2XkhR.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-C57LKpKK.js → P2P-BYpBNYnh.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-BFkPqu6Q.js → PdhVaultBrowser-CYCsrrLg.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-D6fB9YSp.js → Permissions-ZRXGXDFb.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-Bp7EefEo.js → PersonalDataHub-6OWUPHyL.js} +5 -5
  55. package/src/assets/web-panel/assets/{Pipeline-BVln3hZf.js → Pipeline-FcjlDK9E.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-Bf8P3nF6.js → Privacy-vD_6Tldp.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-DM6wA2E2.js → ProjectInit-B_CHOLXp.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-CGbc5Iho.js → ProjectSettings-rEiqQ2da.js} +2 -2
  59. package/src/assets/web-panel/assets/{Projects-D0p9EOxm.js → Projects-Bg8obRAM.js} +1 -1
  60. package/src/assets/web-panel/assets/{Providers-B2kJcxS6.js → Providers-Dgtt97B4.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-Cxu7dXYy.js → QrScannerModal-DaGVylZO.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-DTlrTPms.js → QuickAsk-DP2_2x3u.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-Cm3X-cCj.js → Recommend-D29yj4ki.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-DNb2kjvt.js → RemoteSession-Ue2wnBLp.js} +2 -2
  65. package/src/assets/web-panel/assets/{Reputation-CZUFFjc1.js → Reputation-CrxIHqeC.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-DAPgloxA.js → Row-BgSzJPCn.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-dsx9S793.js → RssFeed-AwhYOrP9.js} +2 -2
  68. package/src/assets/web-panel/assets/{Search-DnnC8kz-.js → Search-D2shpEcm.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-VuZknRMK.js → Security-BtKzhTbT.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-B3XNX8Vx.js → Services-BY0DsUSm.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-Dl1nVtCj.js → Skeleton-B3Sb0UI9.js} +1 -1
  72. package/src/assets/web-panel/assets/Skills-Ne5w0bDs.js +1 -0
  73. package/src/assets/web-panel/assets/{Sla-HzU56n1z.js → Sla-C8VkaP4T.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-CRle7Jl2.js → SpeechSettings-BQdIEYP9.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-DWadz7Sl.js → SyncSettings-CwFafrUg.js} +2 -2
  76. package/src/assets/web-panel/assets/Tasks-yJuKoDlR.js +1 -0
  77. package/src/assets/web-panel/assets/{Templates-BBAgvrlM.js → Templates-DYn9qCmx.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-BsbbvGN8.js → Tenant-DkKReHvn.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-uscpZI4q.js → Terminal-r6bd-owt.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-mTyjNE6r.js → TimelineRenderer-Mq2juUC3.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-Bz97oBgs.js → Tokens-BnMw7MRi.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-BTRKC140.js → Trigger-CTzKp4o9.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-B4A_KuyX.js → Trust-BBrSZm9U.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-DES9TmLU.js → UkeySign-D9fDvEMq.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-ClXs7Qxp.js → VideoEditing-DUi5ve-0.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-IzZqFz-7.js → Wallet-C8Fi1P8_.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-BtOP04g_.js → WebAuthn-s1LppYi2.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-_0cqnopY.js → WorkflowEditor-DJL2C4lj.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-ax7193uU.js → chat-D3F2ndOB.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-Bu4WGJah.js → colors-B7739-e3.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-BX1dlTW0.js → compact-item-CcJbFTF4.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-CktA_Dj7.js → createContext-CuP_DEFS.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-Cj_36GHq.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-D5wd817l.js → hasIn-6YEXfIik.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-C3HmzCLt.js → index-4h3lpWJ3.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CjEJjl5C.js → index-BIID4XcP.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BQtoRyTO.js → index-BOHUo3aM.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CP8ELJBc.js → index-BYYSLEjn.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CfzxR61U.js → index-Bh4hcNWW.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DmPJ6T6F.js → index-BhIKnQAW.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-C8DkWyNr.js → index-Bru_kpZl.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-iHcPtQ2e.js → index-BsdSnHQE.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-D9op8poE.js → index-BxSqVMGh.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BcGdL3sG.js → index-C3ZA_h1j.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DmVUeWUk.js → index-C3pctdCb.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Cv7bVWJ5.js → index-CRuoRapN.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-D-o4Rwjk.js → index-CY0zd_jc.js} +1 -1
  108. package/src/assets/web-panel/assets/index-CezFrAmD.js +1 -0
  109. package/src/assets/web-panel/assets/index-Cxgguez6.js +1 -0
  110. package/src/assets/web-panel/assets/{index-Ci3y1ZAv.js → index-CxradkS6.js} +19 -19
  111. package/src/assets/web-panel/assets/{index-CUForzsG.js → index-D0K_rFC1.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BLLsUNDc.js → index-D0zdPyTD.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CeVZaJSC.js → index-D3haorUt.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-o14NNnxw.js → index-D6Ij_2uw.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-Db3CpWTr.js → index-DDRlBaDt.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-h3KJZr95.js → index-DGsE7MQz.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BAtBNfkr.js → index-DHvxXXNA.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DF1TysIF.js → index-DV11jpeO.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-5PYYkr8S.js → index-DXdnfDVK.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-tYPSfGbd.js → index-D_75jYWk.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-jzlzm4VK.js → index-DjI9MkId.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-D_cycOET.js → index-DlSvpNMf.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-Dzdr0zDf.js → index-Dox44j29.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CYRE0NJA.js → index-DpEKmjoa.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Bad2CYeH.js → index-Dsy7_x4s.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-C8tLR2TG.js → index-Gxw09e9R.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-ytp2H5Zx.js → index-LfRlSmqe.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DkD1K-fF.js → index-Nz-MUHD1.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BRfXoRuh.js → index-b9cuvFEv.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DYo9S5OT.js → index-eR6H6NR7.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-A4uBPQCz.js → index-fWgUQZ9d.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-PBLzwYnu.js → index-mfWQRSLn.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-bFCaM7Eq.js → index-yOZYSNmG.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-D2Iut83B.js → initDefaultProps-CJ1Bq2L3.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BuaJVdK9.js → motion-rYJN_vzf.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-CaEuM92y.js → move-DnDK2gTP.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-wuKNBwys.js → mtc-parser-CKnXy7ES.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-7a3eklaq.js → omit-3nRc3hWT.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-Dn4z7KvJ.js → pickAttrs-Ddjj3sLu.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-DVn_qT4-.js → placementArrow-CbsbphQY.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-Dodz1OMQ.js → responsiveObserve-D9aZ4VDJ.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-CxoGskbp.js → slide-D4pvdFj0.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-DBh_epQH.js → statusUtils-C2W-vY-S.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-RXQ4gG5P.js → styleChecker-Cbq5150d.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-1Wxm0HEG.js → useFlexGapSupport-BA2B7R82.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-CI_ySNxJ.js → useFs-CLrwg3xg.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-C6973rZP.js → usePersonalDataHub-DQeAI5qs.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-CalynwKr.js → vnode-8BDGy-YU.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-CM1cd-Q0.js → zoom-B2SzFprl.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/data/changelog.json +13 -0
  152. package/src/gateways/terminal/PtyManager.js +45 -15
  153. package/src/gateways/ws/ws-agent-handler.js +15 -1
  154. package/src/gateways/ws/ws-session-gateway.js +88 -0
  155. package/src/harness/background-task-command-runner.js +234 -0
  156. package/src/harness/background-task-manager.js +150 -20
  157. package/src/harness/background-task-worker.js +17 -11
  158. package/src/lib/background-agent-supervisor.js +46 -0
  159. package/src/lib/event-runtime-host.js +12 -10
  160. package/src/lib/hook-runner.cjs +7 -1
  161. package/src/lib/hook-runner.js +2 -0
  162. package/src/lib/hooks-v2-runtime.js +94 -6
  163. package/src/lib/hooks-v2-workspace-context.js +258 -0
  164. package/src/lib/lsp/__tests__/lsp-client.test.js +1 -4
  165. package/src/lib/plugin-runtime/bin.js +20 -4
  166. package/src/lib/plugin-runtime/sandbox-policy.js +1 -4
  167. package/src/lib/process-execution-broker/index.js +706 -40
  168. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +212 -2
  169. package/src/lib/process-execution-broker/linux-generic-bwrap.js +124 -6
  170. package/src/lib/process-execution-broker/platform-sandbox.js +6 -2
  171. package/src/repl/agent-repl.js +8 -0
  172. package/src/runtime/agent-core.js +272 -38
  173. package/src/runtime/headless-runner.js +11 -1
  174. package/src/runtime/headless-stream.js +8 -0
  175. package/src/workers/background-agent-worker.js +5 -11
  176. package/src/assets/web-panel/assets/ChatBubbleRenderer-BzeUi3JH.js +0 -1
  177. package/src/assets/web-panel/assets/MarkdownRenderer-R91Qw5JA.js +0 -1
  178. package/src/assets/web-panel/assets/OrderTableRenderer-P94JGGNz.js +0 -1
  179. package/src/assets/web-panel/assets/Skills-CY09CZ_W.js +0 -1
  180. package/src/assets/web-panel/assets/Tasks-CNVTDGg8.js +0 -1
  181. package/src/assets/web-panel/assets/devWarning-x877iKKS.js +0 -1
  182. package/src/assets/web-panel/assets/index-D7pfPouB.js +0 -1
  183. package/src/assets/web-panel/assets/index-e4i-VLBd.js +0 -1
@@ -0,0 +1,234 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import executionBroker from "../lib/process-execution-broker/index.js";
4
+
5
+ const SUPPORTED_BOUNDARIES = new Set(["filesystem", "network"]);
6
+
7
+ export const _backgroundTaskCommandDeps = {
8
+ execSync: (...args) => executionBroker.execSync(...args),
9
+ execFile: (...args) => executionBroker.execFile(...args),
10
+ issueLinuxWorkspaceSandboxExecutionContract: (...args) =>
11
+ executionBroker.issueLinuxWorkspaceSandboxExecutionContract(...args),
12
+ platform: process.platform,
13
+ };
14
+
15
+ function sandboxError(code, reason, message, requiredBoundaries = []) {
16
+ const error = new Error(message);
17
+ error.code = code;
18
+ error.sandboxReason = reason;
19
+ error.sandboxFailClosed = true;
20
+ error.requiredBoundaries = [...requiredBoundaries];
21
+ error.actualGuarantees = [];
22
+ error.missingBoundaries = [...requiredBoundaries];
23
+ error.sandboxBackend = null;
24
+ error.sandboxCandidateBackend =
25
+ _backgroundTaskCommandDeps.platform === "linux"
26
+ ? "linux-bwrap-workspace"
27
+ : null;
28
+ return error;
29
+ }
30
+
31
+ function normalizeRequiredBoundaries(raw) {
32
+ if (raw === undefined || raw === null || raw === "") return [];
33
+ let parsed;
34
+ try {
35
+ parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
36
+ } catch (cause) {
37
+ const error = sandboxError(
38
+ "ERR_BACKGROUND_TASK_SANDBOX_ENVELOPE_INVALID",
39
+ "background_sandbox_envelope_invalid",
40
+ "Background task sandbox boundary envelope is invalid.",
41
+ );
42
+ error.cause = cause;
43
+ throw error;
44
+ }
45
+ if (!Array.isArray(parsed)) {
46
+ throw sandboxError(
47
+ "ERR_BACKGROUND_TASK_SANDBOX_ENVELOPE_INVALID",
48
+ "background_sandbox_envelope_invalid",
49
+ "Background task sandbox boundaries must use filesystem/network.",
50
+ );
51
+ }
52
+ if (parsed.length === 0) return [];
53
+ if (
54
+ parsed.some(
55
+ (boundary) =>
56
+ typeof boundary !== "string" || !SUPPORTED_BOUNDARIES.has(boundary),
57
+ )
58
+ ) {
59
+ throw sandboxError(
60
+ "ERR_BACKGROUND_TASK_SANDBOX_ENVELOPE_INVALID",
61
+ "background_sandbox_envelope_invalid",
62
+ "Background task sandbox boundaries must use filesystem/network.",
63
+ );
64
+ }
65
+ return [...new Set(parsed)].sort();
66
+ }
67
+
68
+ function canonicalBinding(workspaceCwd, executionCwd, boundaries) {
69
+ try {
70
+ if (
71
+ typeof workspaceCwd !== "string" ||
72
+ !path.isAbsolute(workspaceCwd) ||
73
+ typeof executionCwd !== "string" ||
74
+ !path.isAbsolute(executionCwd)
75
+ ) {
76
+ throw new Error("sandbox paths must be absolute");
77
+ }
78
+ const canonicalRoot = fs.realpathSync.native(workspaceCwd);
79
+ const canonicalCwd = fs.realpathSync.native(executionCwd);
80
+ if (
81
+ canonicalRoot !== workspaceCwd ||
82
+ canonicalCwd !== executionCwd ||
83
+ !fs.statSync(canonicalRoot).isDirectory() ||
84
+ !fs.statSync(canonicalCwd).isDirectory()
85
+ ) {
86
+ throw new Error("sandbox paths must be canonical directories");
87
+ }
88
+ const relative = path.relative(canonicalRoot, canonicalCwd);
89
+ if (
90
+ relative === ".." ||
91
+ relative.startsWith(`..${path.sep}`) ||
92
+ path.isAbsolute(relative)
93
+ ) {
94
+ throw new Error("execution cwd escapes workspace");
95
+ }
96
+ return { workspaceRoot: canonicalRoot, workingDirectory: canonicalCwd };
97
+ } catch (cause) {
98
+ const error = sandboxError(
99
+ "ERR_BACKGROUND_TASK_SANDBOX_BINDING_INVALID",
100
+ "background_sandbox_binding_invalid",
101
+ "Background task sandbox workspace/cwd binding is invalid.",
102
+ boundaries,
103
+ );
104
+ error.cause = cause;
105
+ throw error;
106
+ }
107
+ }
108
+
109
+ function explicitShellInvocation(command) {
110
+ if (_backgroundTaskCommandDeps.platform === "win32") {
111
+ return {
112
+ file: process.env.ComSpec || "cmd.exe",
113
+ args: ["/d", "/s", "/c", command],
114
+ };
115
+ }
116
+ return { file: "/bin/sh", args: ["-c", command] };
117
+ }
118
+
119
+ export function executeBackgroundTaskCommand({
120
+ command,
121
+ cwd,
122
+ type,
123
+ workspaceCwd = "",
124
+ requiredBoundaries = [],
125
+ }) {
126
+ if (
127
+ typeof command !== "string" ||
128
+ command.length === 0 ||
129
+ command.includes("\0")
130
+ ) {
131
+ throw new TypeError("background task command must be non-empty");
132
+ }
133
+ const boundaries = normalizeRequiredBoundaries(requiredBoundaries);
134
+ const hasSandboxEnvelope =
135
+ typeof workspaceCwd === "string" && workspaceCwd.length > 0;
136
+ if (hasSandboxEnvelope !== boundaries.length > 0) {
137
+ throw sandboxError(
138
+ "ERR_BACKGROUND_TASK_SANDBOX_ENVELOPE_INVALID",
139
+ "background_sandbox_envelope_invalid",
140
+ "Background task sandbox root and boundaries must be supplied together.",
141
+ boundaries,
142
+ );
143
+ }
144
+ if (
145
+ boundaries.length > 0 &&
146
+ _backgroundTaskCommandDeps.platform !== "linux"
147
+ ) {
148
+ throw sandboxError(
149
+ "ERR_PROCESS_SANDBOX_BOUNDARY_UNSATISFIED",
150
+ "background_platform_backend_unavailable",
151
+ "Background task strong sandbox execution is only available on Linux.",
152
+ boundaries,
153
+ );
154
+ }
155
+
156
+ const commonOptions = {
157
+ cwd: cwd || process.cwd(),
158
+ encoding: "utf-8",
159
+ timeout: 300000,
160
+ maxBuffer: 10 * 1024 * 1024,
161
+ origin: `background-task:command:${type || "unknown"}`,
162
+ policy: "allow",
163
+ scope: "background-task",
164
+ };
165
+ if (boundaries.length === 0) {
166
+ return _backgroundTaskCommandDeps.execSync(command, {
167
+ ...commonOptions,
168
+ shell: true,
169
+ });
170
+ }
171
+
172
+ const binding = canonicalBinding(workspaceCwd, commonOptions.cwd, boundaries);
173
+ const sandboxPolicy = Object.freeze({
174
+ requiredBoundaries: Object.freeze(boundaries),
175
+ });
176
+ const invocation = explicitShellInvocation(command);
177
+ const spawnOptions = {
178
+ ...commonOptions,
179
+ cwd: binding.workingDirectory,
180
+ stdio: ["ignore", "pipe", "pipe"],
181
+ shell: false,
182
+ sandboxPolicy,
183
+ };
184
+ let sandboxExecutionContract;
185
+ try {
186
+ sandboxExecutionContract =
187
+ _backgroundTaskCommandDeps.issueLinuxWorkspaceSandboxExecutionContract(
188
+ invocation.file,
189
+ invocation.args,
190
+ spawnOptions,
191
+ binding.workspaceRoot,
192
+ { sync: false },
193
+ );
194
+ } catch (cause) {
195
+ const error = sandboxError(
196
+ "ERR_PROCESS_SANDBOX_BOUNDARY_UNSATISFIED",
197
+ "background_linux_execution_contract_unavailable",
198
+ "Background task could not bind its Linux sandbox contract.",
199
+ boundaries,
200
+ );
201
+ error.cause = cause;
202
+ throw error;
203
+ }
204
+ if (
205
+ _backgroundTaskCommandDeps.platform === "linux" &&
206
+ !sandboxExecutionContract
207
+ ) {
208
+ throw sandboxError(
209
+ "ERR_PROCESS_SANDBOX_BOUNDARY_UNSATISFIED",
210
+ "background_linux_execution_contract_unavailable",
211
+ "Background task could not bind its Linux sandbox contract.",
212
+ boundaries,
213
+ );
214
+ }
215
+ return new Promise((resolve, reject) => {
216
+ const options = {
217
+ ...spawnOptions,
218
+ ...(sandboxExecutionContract ? { sandboxExecutionContract } : {}),
219
+ };
220
+ try {
221
+ _backgroundTaskCommandDeps.execFile(
222
+ invocation.file,
223
+ invocation.args,
224
+ options,
225
+ (error, stdout) => {
226
+ if (error) reject(error);
227
+ else resolve(stdout);
228
+ },
229
+ );
230
+ } catch (error) {
231
+ reject(error);
232
+ }
233
+ });
234
+ }
@@ -8,8 +8,15 @@
8
8
  * Feature-flag gated: BACKGROUND_TASKS
9
9
  */
10
10
 
11
- import { existsSync, mkdirSync, appendFileSync, readFileSync } from "node:fs";
12
- import { join, resolve } from "node:path";
11
+ import {
12
+ existsSync,
13
+ mkdirSync,
14
+ appendFileSync,
15
+ readFileSync,
16
+ realpathSync,
17
+ statSync,
18
+ } from "node:fs";
19
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
13
20
  import { createHash } from "node:crypto";
14
21
  import { EventEmitter } from "node:events";
15
22
  import { getHomeDir } from "../lib/paths.js";
@@ -17,6 +24,7 @@ import executionBroker from "../lib/process-execution-broker/index.js";
17
24
 
18
25
  export const _deps = {
19
26
  spawn: executionBroker.spawn.bind(executionBroker),
27
+ platform: process.platform,
20
28
  };
21
29
 
22
30
  function getTasksDir() {
@@ -41,6 +49,7 @@ const RECOVERABLE_TASK_STATUSES = new Set([
41
49
  TaskStatus.PENDING,
42
50
  TaskStatus.RUNNING,
43
51
  ]);
52
+ const SUPPORTED_STRONG_BOUNDARIES = new Set(["filesystem", "network"]);
44
53
 
45
54
  function validatePinnedSandboxPolicy(policy) {
46
55
  if (policy == null) return null;
@@ -51,7 +60,9 @@ function validatePinnedSandboxPolicy(policy) {
51
60
  !Array.isArray(policy.requiredBoundaries) ||
52
61
  policy.requiredBoundaries.length === 0 ||
53
62
  policy.requiredBoundaries.some(
54
- (boundary) => typeof boundary !== "string" || boundary.length === 0,
63
+ (boundary) =>
64
+ typeof boundary !== "string" ||
65
+ !SUPPORTED_STRONG_BOUNDARIES.has(boundary),
55
66
  ) ||
56
67
  !Object.isFrozen(policy) ||
57
68
  !Object.isFrozen(policy.requiredBoundaries)
@@ -65,22 +76,88 @@ function validatePinnedSandboxPolicy(policy) {
65
76
  return policy;
66
77
  }
67
78
 
68
- function createUnsupportedSandboxError(sandboxPolicy) {
69
- const requiredBoundaries = [...sandboxPolicy.requiredBoundaries];
70
- const error = new Error(
71
- `Background tasks cannot run without their required sandbox boundaries: ${requiredBoundaries.join(", ")}`,
72
- );
73
- error.code = "ERR_BACKGROUND_TASK_SANDBOX_UNSUPPORTED";
74
- error.sandboxReason = "background_execution_unsupported";
79
+ function createSandboxError(code, reason, message, requiredBoundaries = []) {
80
+ const error = new Error(message);
81
+ error.code = code;
82
+ error.sandboxReason = reason;
75
83
  error.sandboxFailClosed = true;
76
- error.requiredBoundaries = requiredBoundaries;
84
+ error.requiredBoundaries = [...requiredBoundaries];
77
85
  error.actualGuarantees = [];
78
86
  error.missingBoundaries = [...requiredBoundaries];
79
87
  error.sandboxBackend = null;
80
- error.sandboxCandidateBackend = null;
88
+ error.sandboxCandidateBackend =
89
+ _deps.platform === "linux" ? "linux-bwrap-workspace" : null;
81
90
  return error;
82
91
  }
83
92
 
93
+ function canonicalSandboxBinding(workspaceCwd, executionCwd) {
94
+ try {
95
+ if (
96
+ typeof workspaceCwd !== "string" ||
97
+ !isAbsolute(workspaceCwd) ||
98
+ typeof executionCwd !== "string"
99
+ ) {
100
+ throw new Error("sandbox paths must be absolute");
101
+ }
102
+ const workspaceRoot = realpathSync.native(resolve(workspaceCwd));
103
+ const workingDirectory = realpathSync.native(resolve(executionCwd));
104
+ if (
105
+ !statSync(workspaceRoot).isDirectory() ||
106
+ !statSync(workingDirectory).isDirectory()
107
+ ) {
108
+ throw new Error("sandbox paths must be directories");
109
+ }
110
+ const rel = relative(workspaceRoot, workingDirectory);
111
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
112
+ throw new Error("execution cwd escapes workspace");
113
+ }
114
+ return { workspaceRoot, workingDirectory };
115
+ } catch (cause) {
116
+ const error = createSandboxError(
117
+ "ERR_BACKGROUND_TASK_SANDBOX_BINDING_INVALID",
118
+ "background_sandbox_binding_invalid",
119
+ "Background task sandbox workspace/cwd binding is invalid.",
120
+ );
121
+ error.cause = cause;
122
+ throw error;
123
+ }
124
+ }
125
+
126
+ function sameBoundaries(left, right) {
127
+ const normalizedLeft = Array.isArray(left) ? [...left].sort() : null;
128
+ const normalizedRight = Array.isArray(right) ? [...right].sort() : null;
129
+ return (
130
+ normalizedLeft &&
131
+ normalizedRight &&
132
+ normalizedLeft.length === normalizedRight.length &&
133
+ normalizedLeft.every((value, index) => value === normalizedRight[index])
134
+ );
135
+ }
136
+
137
+ function strongWorkerEnvironment(taskId) {
138
+ const blocked = new Set([
139
+ "BASH_ENV",
140
+ "CC_TASK_ID",
141
+ "ELECTRON_RUN_AS_NODE",
142
+ "ENV",
143
+ "GCONV_PATH",
144
+ "OPENSSL_CONF",
145
+ "RUBYOPT",
146
+ ]);
147
+ const blockedPrefixes = ["DYLD_", "LD_", "NODE_", "PYTHON"];
148
+ return Object.fromEntries([
149
+ ...Object.entries(process.env).filter(([key, value]) => {
150
+ const normalizedKey = key.toUpperCase();
151
+ return (
152
+ value != null &&
153
+ !blocked.has(normalizedKey) &&
154
+ !blockedPrefixes.some((prefix) => normalizedKey.startsWith(prefix))
155
+ );
156
+ }),
157
+ ["CC_TASK_ID", taskId],
158
+ ]);
159
+ }
160
+
84
161
  export class BackgroundTaskManager extends EventEmitter {
85
162
  constructor(options = {}) {
86
163
  super();
@@ -123,14 +200,17 @@ export class BackgroundTaskManager extends EventEmitter {
123
200
  }
124
201
 
125
202
  const cwd = spec.cwd || process.cwd();
126
- this._assertSandboxSupported(cwd);
203
+ const sandboxPolicy = this._resolvePinnedSandboxPolicy(cwd);
204
+ const sandboxBinding = sandboxPolicy
205
+ ? canonicalSandboxBinding(this._policyCwd, cwd)
206
+ : null;
127
207
 
128
208
  const id = `task-${Date.now()}-${createHash("sha256").update(Math.random().toString()).digest("hex").slice(0, 6)}`;
129
209
  const task = {
130
210
  id,
131
211
  type: spec.type || "shell",
132
212
  command: spec.command,
133
- cwd,
213
+ cwd: sandboxBinding?.workingDirectory || cwd,
134
214
  description: spec.description || spec.command,
135
215
  status: TaskStatus.PENDING,
136
216
  createdAt: Date.now(),
@@ -145,6 +225,10 @@ export class BackgroundTaskManager extends EventEmitter {
145
225
  recoverySourceStatus: null,
146
226
  ownerNodeId: spec.ownerNodeId || this.nodeId,
147
227
  recoveryDecision: null,
228
+ sandboxWorkspaceCwd: sandboxBinding?.workspaceRoot || null,
229
+ sandboxRequiredBoundaries: sandboxPolicy
230
+ ? [...sandboxPolicy.requiredBoundaries].sort()
231
+ : null,
148
232
  _seq: ++this._createSeq,
149
233
  };
150
234
 
@@ -164,7 +248,34 @@ export class BackgroundTaskManager extends EventEmitter {
164
248
  throw new Error(`Task ${taskId} is not pending (status: ${task.status})`);
165
249
  }
166
250
 
167
- this._assertSandboxSupported(task.cwd);
251
+ const sandboxPolicy = this._resolvePinnedSandboxPolicy(task.cwd);
252
+ const pinnedBoundaries = task.sandboxRequiredBoundaries;
253
+ if (
254
+ Boolean(sandboxPolicy) !== Array.isArray(pinnedBoundaries) ||
255
+ (sandboxPolicy &&
256
+ !sameBoundaries(sandboxPolicy.requiredBoundaries, pinnedBoundaries))
257
+ ) {
258
+ throw createSandboxError(
259
+ "ERR_BACKGROUND_TASK_SANDBOX_POLICY_CHANGED",
260
+ "background_sandbox_policy_changed",
261
+ "Background task sandbox policy changed after creation; recreate the task.",
262
+ sandboxPolicy?.requiredBoundaries || pinnedBoundaries || [],
263
+ );
264
+ }
265
+ if (sandboxPolicy) {
266
+ const currentBinding = canonicalSandboxBinding(this._policyCwd, task.cwd);
267
+ if (
268
+ currentBinding.workspaceRoot !== task.sandboxWorkspaceCwd ||
269
+ currentBinding.workingDirectory !== task.cwd
270
+ ) {
271
+ throw createSandboxError(
272
+ "ERR_BACKGROUND_TASK_SANDBOX_BINDING_CHANGED",
273
+ "background_sandbox_binding_changed",
274
+ "Background task sandbox workspace/cwd identity changed after creation; recreate the task.",
275
+ pinnedBoundaries,
276
+ );
277
+ }
278
+ }
168
279
 
169
280
  task.status = TaskStatus.RUNNING;
170
281
  task.startedAt = Date.now();
@@ -179,11 +290,21 @@ export class BackgroundTaskManager extends EventEmitter {
179
290
  );
180
291
  const child = _deps.spawn(
181
292
  process.execPath,
182
- [...process.execArgv, workerPath, task.command, task.cwd, task.type],
293
+ [
294
+ ...(sandboxPolicy ? [] : process.execArgv),
295
+ workerPath,
296
+ task.command,
297
+ task.cwd,
298
+ task.type,
299
+ task.sandboxWorkspaceCwd || "",
300
+ JSON.stringify(task.sandboxRequiredBoundaries || []),
301
+ ],
183
302
  {
184
303
  cwd: task.cwd,
185
304
  stdio: ["pipe", "pipe", "pipe", "ipc"],
186
- env: { ...process.env, CC_TASK_ID: taskId },
305
+ env: sandboxPolicy
306
+ ? strongWorkerEnvironment(taskId)
307
+ : { ...process.env, CC_TASK_ID: taskId },
187
308
  origin: "background-task:worker",
188
309
  policy: "allow",
189
310
  scope: "background-task",
@@ -199,6 +320,9 @@ export class BackgroundTaskManager extends EventEmitter {
199
320
  } else if (msg.type === "result") {
200
321
  this._complete(taskId, TaskStatus.COMPLETED, msg.data, null);
201
322
  } else if (msg.type === "error") {
323
+ task.errorCode = msg.code || null;
324
+ task.sandboxReason = msg.sandboxReason || null;
325
+ task.sandboxFailClosed = msg.sandboxFailClosed === true;
202
326
  this._complete(taskId, TaskStatus.FAILED, null, msg.error);
203
327
  }
204
328
  });
@@ -240,7 +364,7 @@ export class BackgroundTaskManager extends EventEmitter {
240
364
  return task;
241
365
  }
242
366
 
243
- _assertSandboxSupported(executionCwd) {
367
+ _resolvePinnedSandboxPolicy(executionCwd) {
244
368
  if (!this._resolveSandboxPolicy) return;
245
369
  const sandboxPolicy = validatePinnedSandboxPolicy(
246
370
  this._resolveSandboxPolicy({
@@ -248,9 +372,15 @@ export class BackgroundTaskManager extends EventEmitter {
248
372
  executionCwd,
249
373
  }),
250
374
  );
251
- if (sandboxPolicy) {
252
- throw createUnsupportedSandboxError(sandboxPolicy);
375
+ if (sandboxPolicy && _deps.platform !== "linux") {
376
+ throw createSandboxError(
377
+ "ERR_BACKGROUND_TASK_SANDBOX_UNSUPPORTED",
378
+ "background_platform_backend_unavailable",
379
+ "Background task strong sandbox execution is only available on Linux.",
380
+ sandboxPolicy.requiredBoundaries,
381
+ );
253
382
  }
383
+ return sandboxPolicy;
254
384
  }
255
385
 
256
386
  get(taskId) {
@@ -5,24 +5,27 @@
5
5
  * Sends messages to parent: { type: "heartbeat"|"result"|"error", ... }
6
6
  */
7
7
 
8
- import executionBroker from "../lib/process-execution-broker/index.js";
8
+ import { executeBackgroundTaskCommand } from "./background-task-command-runner.js";
9
9
 
10
- const [command, cwd, type] = process.argv.slice(2);
10
+ const [
11
+ command,
12
+ cwd,
13
+ type,
14
+ sandboxWorkspaceCwd = "",
15
+ sandboxRequiredBoundaries = "",
16
+ ] = process.argv.slice(2);
11
17
 
12
18
  const heartbeat = setInterval(() => {
13
19
  if (process.send) process.send({ type: "heartbeat" });
14
20
  }, 5000);
15
21
 
16
22
  try {
17
- const result = executionBroker.execSync(command, {
18
- cwd: cwd || process.cwd(),
19
- encoding: "utf-8",
20
- timeout: 300000,
21
- maxBuffer: 10 * 1024 * 1024,
22
- origin: `background-task:command:${type || "unknown"}`,
23
- policy: "allow",
24
- scope: "background-task",
25
- shell: true,
23
+ const result = await executeBackgroundTaskCommand({
24
+ command,
25
+ cwd,
26
+ type,
27
+ workspaceCwd: sandboxWorkspaceCwd,
28
+ requiredBoundaries: sandboxRequiredBoundaries,
26
29
  });
27
30
 
28
31
  if (process.send) {
@@ -33,6 +36,9 @@ try {
33
36
  process.send({
34
37
  type: "error",
35
38
  error: err.stderr || err.message || String(err),
39
+ code: err.code || null,
40
+ sandboxReason: err.sandboxReason || null,
41
+ sandboxFailClosed: err.sandboxFailClosed === true,
36
42
  });
37
43
  }
38
44
  process.exitCode = 1;
@@ -201,6 +201,52 @@ export const _deps = {
201
201
  killProcessTree: defaultKillProcessTree,
202
202
  };
203
203
 
204
+ /**
205
+ * Stop the active turn launched by an attached background session.
206
+ * Windows must terminate the complete descendant tree; a bare ChildProcess
207
+ * kill only reaches the top-level CLI process and can orphan tool children.
208
+ */
209
+ export function stopBackgroundAgentChildTree(
210
+ pid,
211
+ { platform = process.platform, signal = "SIGTERM" } = {},
212
+ ) {
213
+ const target = Number(pid);
214
+ if (!Number.isInteger(target) || target <= 0 || target === process.pid) {
215
+ const error = new Error("background_agent_child_pid_invalid");
216
+ error.code = "ERR_BACKGROUND_AGENT_CHILD_PID_INVALID";
217
+ throw error;
218
+ }
219
+ if (platform === "win32") {
220
+ const result = runSupervisorCommand(
221
+ "taskkill",
222
+ ["/PID", String(target), "/T", "/F"],
223
+ {
224
+ windowsHide: true,
225
+ encoding: "utf8",
226
+ },
227
+ "background-agent:session-stop-tree",
228
+ );
229
+ if (result?.error || result?.status !== 0) {
230
+ const error = new Error(
231
+ `background_agent_child_tree_stop_failed: ${
232
+ result?.error?.message ||
233
+ result?.stderr ||
234
+ `taskkill exited ${result?.status}`
235
+ }`,
236
+ );
237
+ error.code = "ERR_BACKGROUND_AGENT_CHILD_TREE_STOP_FAILED";
238
+ throw error;
239
+ }
240
+ return true;
241
+ }
242
+ try {
243
+ _deps.kill(-target, signal);
244
+ } catch {
245
+ _deps.kill(target, signal);
246
+ }
247
+ return true;
248
+ }
249
+
204
250
  /** Cached probe (default impl only — injected probes run uncached for tests). */
205
251
  function processStartTimeMs(pid) {
206
252
  if (_deps.readProcessStartTimeMs !== defaultReadProcessStartTimeMs) {
@@ -9,6 +9,7 @@
9
9
  import { EventEmitter } from "node:events";
10
10
  import { EventRuntimeStore } from "./event-runtime-store.js";
11
11
  import { EventRuntimeWorker } from "./event-runtime-worker.js";
12
+ import { registerHostHooksV2Workspace } from "./hooks-v2-workspace-context.js";
12
13
 
13
14
  function routeType(event) {
14
15
  return String(
@@ -63,10 +64,8 @@ export class EventRuntimeHost extends EventEmitter {
63
64
  worker ||
64
65
  new EventRuntimeWorker({
65
66
  store,
66
- onInbox: (event, record) =>
67
- this._dispatch("inbox", event, record),
68
- onOutbox: (event, record) =>
69
- this._dispatch("outbox", event, record),
67
+ onInbox: (event, record) => this._dispatch("inbox", event, record),
68
+ onOutbox: (event, record) => this._dispatch("outbox", event, record),
70
69
  });
71
70
  }
72
71
 
@@ -89,10 +88,7 @@ export class EventRuntimeHost extends EventEmitter {
89
88
  }
90
89
  }
91
90
 
92
- registerHandler(
93
- handler,
94
- { queue = "inbox", type = "*", origin = "*" } = {},
95
- ) {
91
+ registerHandler(handler, { queue = "inbox", type = "*", origin = "*" } = {}) {
96
92
  if (typeof handler !== "function") {
97
93
  throw new TypeError("Event Runtime handler must be a function");
98
94
  }
@@ -223,8 +219,7 @@ export class EventRuntimeHost extends EventEmitter {
223
219
  break;
224
220
  }
225
221
  const claimed =
226
- Number(stats?.inboxClaimed || 0) +
227
- Number(stats?.outboxClaimed || 0);
222
+ Number(stats?.inboxClaimed || 0) + Number(stats?.outboxClaimed || 0);
228
223
  if (claimed === 0) break;
229
224
  }
230
225
  }
@@ -272,7 +267,14 @@ export function startDefaultEventRuntimeHost(options = {}) {
272
267
  if (process.env.CC_EVENT_RUNTIME_DURABLE !== "1" && options.force !== true) {
273
268
  return null;
274
269
  }
270
+ // The CLI process startup directory is host state. Register it before the
271
+ // first durable claim so a recovered Hooks v2 record can resolve its opaque
272
+ // workspace ID without accepting any path from the record itself.
273
+ const hooksV2WorkspaceBinding = registerHostHooksV2Workspace(
274
+ options.workspaceRoot ?? process.cwd(),
275
+ );
275
276
  const host = getDefaultEventRuntimeHost(options);
277
+ host.hooksV2WorkspaceBindingId = hooksV2WorkspaceBinding.bindingId;
276
278
  host.start({ immediate: options.immediate !== false });
277
279
  return host;
278
280
  }
@@ -438,7 +438,13 @@ function _runCommandHookInner(command, input = {}, opts = {}) {
438
438
  function interpretHookOutcome(res) {
439
439
  // A surfaced spawn error (timeout / ENOENT, status null) is non-blocking — a
440
440
  // broken hook must never wedge the agent; only an explicit block blocks.
441
- if (res.error) {
441
+ // spawnSync can also report EPIPE after a fast-exiting hook closes stdin,
442
+ // even though the child has a real exit status and complete output. The
443
+ // async runner already treats that early stdin close as harmless; preserve
444
+ // the exit-code/stdout protocol on the sync path as well.
445
+ const inputClosedAfterExit =
446
+ res.error?.code === "EPIPE" && Number.isInteger(res.status);
447
+ if (res.error && !inputClosedAfterExit) {
442
448
  return {
443
449
  decision: HOOK_DECISIONS.CONTINUE,
444
450
  reason: `hook error: ${res.error.message}`,
@@ -107,6 +107,8 @@ function runHookProcessSync(file, argsOrOptions, maybeOptions) {
107
107
  }
108
108
 
109
109
  export function _restoreProcessRunners() {
110
+ _processDeps.run = brokerRunner;
111
+ _processDeps.runSync = brokerSyncRunner;
110
112
  runner._deps.run = runHookProcess;
111
113
  runner._deps.runSync = runHookProcessSync;
112
114
  }