chainlesschain 0.162.183 → 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 (205) 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-BapS7lz4.js → AIOps-Co4ipAKx.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-CXYdXEUI.js → ActionButton-BpsoDrDq.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-CKR5Lexn.js → Analytics-DMUqeBik.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-DATVUOXB.js → AppLayout-C6fD_dXr.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-vb_0ZlV1.js → Artifacts-DW0YlQlV.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-BI1l9GWt.js → Audit-DXtHiiFK.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-BFXbaUEf.js → BackgroundAgents-BgmF62U-.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-DEYBJJ6H.js → Backup-D8vhVLQC.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-COdzeO0M.js → BaseInput-D7nCtY2X.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-oSN6sfM5.js → Chat-CBZLEV2G.js} +6 -6
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CI_jT0ru.js → ChatBubbleRenderer-CtDb-2Qu.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-BezEa4tm.js → Checkbox-DJLkeD4M.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-CNEL0Ife.js → Codegen-Cs6xO8Fd.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-Bf2Y2Bn1.js → Col-Dtx9wxfT.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-BfUlu4Hc.js → Community-DiigJg4n.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-BN4xWP9e.js → Compact-B67oMDDL.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-Bt6BKmEP.js → Compliance-BBDl-Jos.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-DKLp8YxO.js → Cowork-Ba98hSsa.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-o7Hhw6ky.js → Cron-DBWQZIz7.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-DaNXWJIw.js → Crosschain-OcrKzxAt.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-B-Qa2mha.js → DID-mW2eSbjM.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-CMX0Qnxw.js → Dashboard-BmsDPmKJ.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-BLuLyRu_.js → Dropdown-CVzmCyRx.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-CFBAeMij.js → EmailListRenderer-vVgqKzhh.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DWzXQ67V.js → FamilyGuardDashboard-CWY5t_ZJ.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-DB1QSLgr.js → Federation-ZGomhjxr.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-C-OKCToP.js → FormItemContext-DyaknbLr.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-DMJFtFZf.js → GenericCardRenderer-CmIvN1qr.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-DsGSujOV.js → Git-DQOJIYkG.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-B0iS9NEW.js → Governance-CMiY-EEp.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-CH004H7c.js → Inference-D6IXuucf.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-Q9MTQ83c.js → KnowledgeGraph-6jkTL0hV.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-B_fyjZK9.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-BIStF9d1.js → Marketplace-WhYuhBxm.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-pGfal768.js → McpTools-DTeO28xy.js} +5 -5
  39. package/src/assets/web-panel/assets/{Memory-xbNqpIqB.js → Memory-MSHzPSE-.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-B5EQGrIK.js → MobileBridge-Cyqs3J3W.js} +1 -1
  41. package/src/assets/web-panel/assets/MobileProjects-7IN2hWHg.js +1 -0
  42. package/src/assets/web-panel/assets/{Mtc-a_rmfxJH.js → Mtc-MWBnezNM.js} +5 -5
  43. package/src/assets/web-panel/assets/{MtcAudit-BmKuofUr.js → MtcAudit-DQu35Nu-.js} +4 -4
  44. package/src/assets/web-panel/assets/{Multisig-BQbJ-CAN.js → Multisig-CQS_M-DP.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-JtPJKX25.js → NLProgramming-WD16vQvY.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-ZFKznTSG.js → Notes-DqsiSwOq.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-B6aHRBBR.js → NotificationSettings-D3LoxO-P.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-DdcsOqEz.js → OrderTableRenderer-C5jPoTd6.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-BFDTAUgS.js → Organization-48aMBJso.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-0FoTfbWH.js → Overflow-j-a2XkhR.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-CWd90etw.js → P2P-BYpBNYnh.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-DtCGC-W-.js → PdhVaultBrowser-CYCsrrLg.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-CvQ7nr7T.js → Permissions-ZRXGXDFb.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-rdNPcR-Z.js → PersonalDataHub-6OWUPHyL.js} +5 -5
  55. package/src/assets/web-panel/assets/{Pipeline-CmrfiNOs.js → Pipeline-FcjlDK9E.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-D7ep1h_t.js → Privacy-vD_6Tldp.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-C0xKINHS.js → ProjectInit-B_CHOLXp.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-AOmZFp-F.js → ProjectSettings-rEiqQ2da.js} +2 -2
  59. package/src/assets/web-panel/assets/{Projects-BWHSkXZP.js → Projects-Bg8obRAM.js} +1 -1
  60. package/src/assets/web-panel/assets/{Providers-Cb7JCTPL.js → Providers-Dgtt97B4.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-BDMehcXC.js → QrScannerModal-DaGVylZO.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-BF6_1nvx.js → QuickAsk-DP2_2x3u.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-BHfT-A28.js → Recommend-D29yj4ki.js} +1 -1
  64. package/src/assets/web-panel/assets/RemoteSession-Ue2wnBLp.js +5 -0
  65. package/src/assets/web-panel/assets/{Reputation-CsWHLEcj.js → Reputation-CrxIHqeC.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-D6srnWhm.js → Row-BgSzJPCn.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-Xhr-KyVd.js → RssFeed-AwhYOrP9.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-vIbIJvwL.js → Search-D2shpEcm.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-Bzi5Aiko.js → Security-BtKzhTbT.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-BHtVX52P.js → Services-BY0DsUSm.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-C0R8B2jf.js → Skeleton-B3Sb0UI9.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-DtT6rWOX.js → Skills-Ne5w0bDs.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-0B37mxVU.js → Sla-C8VkaP4T.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-0V2jE1Bs.js → SpeechSettings-BQdIEYP9.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-CYedRzaj.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-BKoHztGT.js → Templates-DYn9qCmx.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant--17nwVNZ.js → Tenant-DkKReHvn.js} +1 -1
  79. package/src/assets/web-panel/assets/Terminal-r6bd-owt.js +3 -0
  80. package/src/assets/web-panel/assets/{TimelineRenderer-C5jx4AE3.js → TimelineRenderer-Mq2juUC3.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-DYJKYa9t.js → Tokens-BnMw7MRi.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-DChpWiQ9.js → Trigger-CTzKp4o9.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-DfWG52EA.js → Trust-BBrSZm9U.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-CMugYcyA.js → UkeySign-D9fDvEMq.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-BOSmrHAd.js → VideoEditing-DUi5ve-0.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-D5g3k3Sa.js → Wallet-C8Fi1P8_.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-D-7u4Mkc.js → WebAuthn-s1LppYi2.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-CFzZMSUJ.js → WorkflowEditor-DJL2C4lj.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-VbR6l6mc.js → chat-D3F2ndOB.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-BSZn8EDS.js → colors-B7739-e3.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-y74obMEq.js → compact-item-CcJbFTF4.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-C326bjex.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-D_INJQT_.js → hasIn-6YEXfIik.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-DuIkfnTF.js → index-4h3lpWJ3.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DBkzrpiL.js → index-BIID4XcP.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-B7SrC-On.js → index-BOHUo3aM.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BnSfnSMP.js → index-BYYSLEjn.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-KF9Zmqln.js → index-Bh4hcNWW.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-C327pLbw.js → index-BhIKnQAW.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-E_v1ZCTP.js → index-Bru_kpZl.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Bfw2hf2s.js → index-BsdSnHQE.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-jYaogFE8.js → index-BxSqVMGh.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Dlxh9D8D.js → index-C3ZA_h1j.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DdyjrCAu.js → index-C3pctdCb.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-abmn_8Yz.js → index-CRuoRapN.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-qMA4GYdQ.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-Cr3uuz0C.js → index-CxradkS6.js} +19 -19
  111. package/src/assets/web-panel/assets/{index-DwHSZLXg.js → index-D0K_rFC1.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CMHqs3Jb.js → index-D0zdPyTD.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-Bnua8IX3.js → index-D3haorUt.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BY8-nCgs.js → index-D6Ij_2uw.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-FQBsuExX.js → index-DDRlBaDt.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-C-ZCaUJc.js → index-DGsE7MQz.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BjcWKg1S.js → index-DHvxXXNA.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-GA80ir_u.js → index-DV11jpeO.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CsovTpx9.js → index-DXdnfDVK.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-nSn2-C4N.js → index-D_75jYWk.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BJi3UqBt.js → index-DjI9MkId.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-gINyI1rh.js → index-DlSvpNMf.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CfLJ42kv.js → index-Dox44j29.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-C2NyllnW.js → index-DpEKmjoa.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Iw0FfqUz.js → index-Dsy7_x4s.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-4eyH-eC2.js → index-Gxw09e9R.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-4PIeTM--.js → index-LfRlSmqe.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-IZAWt0H8.js → index-Nz-MUHD1.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-DUU2606o.js → index-b9cuvFEv.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CzE53HiB.js → index-eR6H6NR7.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-9PIVuXqo.js → index-fWgUQZ9d.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DkH1JvEt.js → index-mfWQRSLn.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-DNMxIsDg.js → index-yOZYSNmG.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-DI0Zaf8-.js → initDefaultProps-CJ1Bq2L3.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BZDKSy3Z.js → motion-rYJN_vzf.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-DsIW01Vp.js → move-DnDK2gTP.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-CohkDhsf.js → mtc-parser-CKnXy7ES.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-DpiN5rzg.js → omit-3nRc3hWT.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-qbuqni3y.js → pickAttrs-Ddjj3sLu.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-mBky6714.js → placementArrow-CbsbphQY.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-Bas3eQ2K.js → responsiveObserve-D9aZ4VDJ.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-CBx_SKoK.js → slide-D4pvdFj0.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-BlYguJSV.js → statusUtils-C2W-vY-S.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-BCYQmRGv.js → styleChecker-Cbq5150d.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-Dp5dybLM.js → useFlexGapSupport-BA2B7R82.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-DHAMBgA2.js → useFs-CLrwg3xg.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-W6jsSw76.js → usePersonalDataHub-DQeAI5qs.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-BkI6aayc.js → vnode-8BDGy-YU.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-C_IMGueq.js → zoom-B2SzFprl.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/commands/agenda.js +74 -1
  152. package/src/commands/background-session.js +11 -1
  153. package/src/commands/batch.js +160 -5
  154. package/src/commands/plugin.js +90 -27
  155. package/src/commands/session.js +31 -1
  156. package/src/commands/team.js +212 -6
  157. package/src/data/changelog.json +26 -0
  158. package/src/gateways/terminal/PtyManager.js +45 -15
  159. package/src/gateways/ws/ws-agent-handler.js +15 -1
  160. package/src/gateways/ws/ws-session-gateway.js +88 -0
  161. package/src/harness/background-task-command-runner.js +234 -0
  162. package/src/harness/background-task-manager.js +150 -20
  163. package/src/harness/background-task-worker.js +17 -11
  164. package/src/harness/jsonl-session-store.js +57 -6
  165. package/src/harness/mcp-client.js +65 -43
  166. package/src/lib/agent-batch.js +4 -1
  167. package/src/lib/agent-team/team-worktree.js +3 -0
  168. package/src/lib/async-hook-supervisor.cjs +82 -10
  169. package/src/lib/async-hook-supervisor.js +50 -4
  170. package/src/lib/background-agent-supervisor.js +46 -0
  171. package/src/lib/collaboration-run-store.js +362 -0
  172. package/src/lib/event-runtime-host.js +12 -10
  173. package/src/lib/hook-runner.cjs +105 -18
  174. package/src/lib/hook-runner.js +79 -6
  175. package/src/lib/hook-shell-command.cjs +148 -0
  176. package/src/lib/hooks-v2-runtime.js +280 -81
  177. package/src/lib/hooks-v2-workspace-context.js +258 -0
  178. package/src/lib/jsonl-session-store.js +2 -0
  179. package/src/lib/lsp/__tests__/lsp-client.test.js +15 -0
  180. package/src/lib/lsp/lsp-client.js +12 -1
  181. package/src/lib/plugin-monitor-supervisor.js +53 -16
  182. package/src/lib/plugin-runtime/bin.js +21 -5
  183. package/src/lib/plugin-runtime/install.js +249 -57
  184. package/src/lib/plugin-runtime/mcp.js +21 -2
  185. package/src/lib/plugin-runtime/monitors.js +19 -3
  186. package/src/lib/plugin-runtime/sandbox-policy.js +100 -4
  187. package/src/lib/process-execution-broker/index.js +1151 -44
  188. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +1557 -0
  189. package/src/lib/process-execution-broker/linux-generic-bwrap.js +1378 -0
  190. package/src/lib/process-execution-broker/platform-sandbox.js +586 -58
  191. package/src/lib/session-usage.js +202 -2
  192. package/src/lib/stream-retry.js +29 -0
  193. package/src/repl/agent-repl.js +40 -3
  194. package/src/runtime/agent-core.js +304 -42
  195. package/src/runtime/headless-runner.js +48 -1
  196. package/src/runtime/headless-stream.js +42 -2
  197. package/src/workers/background-agent-worker.js +5 -11
  198. package/src/assets/web-panel/assets/MarkdownRenderer-C0o4FtpD.js +0 -1
  199. package/src/assets/web-panel/assets/MobileProjects-VTc3gT1y.js +0 -1
  200. package/src/assets/web-panel/assets/RemoteSession-B6EEf8hh.js +0 -5
  201. package/src/assets/web-panel/assets/Tasks-D-R1x8vm.js +0 -1
  202. package/src/assets/web-panel/assets/Terminal-Dnkos7vW.js +0 -3
  203. package/src/assets/web-panel/assets/devWarning-D3Bho_6u.js +0 -1
  204. package/src/assets/web-panel/assets/index-5iAFwYx1.js +0 -1
  205. package/src/assets/web-panel/assets/index-BfnB7LPx.js +0 -1
@@ -36,6 +36,13 @@ const {
36
36
  tryParseDecision,
37
37
  resolveHookSandboxPolicy,
38
38
  } = require("./hook-runner.cjs");
39
+ const {
40
+ buildExplicitHookShellInvocation,
41
+ issueTrustedHookSandboxContract,
42
+ requireTrustedHookRoot,
43
+ requiresExplicitHookShell,
44
+ sandboxBoundaryError,
45
+ } = require("./hook-shell-command.cjs");
39
46
 
40
47
  const DEFAULT_TIMEOUT_MS = 60000;
41
48
  const MAX_TIMEOUT_MS = 600000;
@@ -117,8 +124,25 @@ class AsyncHookSupervisor {
117
124
  * the process 'exit' reaper, where async work would be cut off. A child with no
118
125
  * real pid (the unit-test fakes) falls back to its own `.kill()`.
119
126
  */
120
- _killChildTree(child, signal = "SIGTERM") {
127
+ _killChildTree(
128
+ child,
129
+ signal = "SIGTERM",
130
+ { sandboxManagedTree = false } = {},
131
+ ) {
121
132
  if (!child) return;
133
+ if (sandboxManagedTree) {
134
+ // The Linux generic backend launches bubblewrap with --die-with-parent,
135
+ // --new-session, and a private PID namespace. Killing the Broker child
136
+ // therefore tears down the complete sandbox tree; a negative host PID
137
+ // would be both unnecessary and invalid because strong launches must not
138
+ // use detached:true.
139
+ try {
140
+ child.kill(signal);
141
+ } catch {
142
+ /* already gone */
143
+ }
144
+ return;
145
+ }
122
146
  const pid = child.pid;
123
147
  if (typeof pid !== "number" || pid <= 0) {
124
148
  try {
@@ -361,25 +385,39 @@ class AsyncHookSupervisor {
361
385
  : DEFAULT_TIMEOUT_MS;
362
386
 
363
387
  let child;
388
+ let sandboxManagedTree = false;
364
389
  try {
365
390
  const sandboxPolicy = resolveHookSandboxPolicy(hook, opts);
391
+ const explicitSandboxShell = requiresExplicitHookShell(sandboxPolicy);
392
+ const trustedRoot = explicitSandboxShell
393
+ ? requireTrustedHookRoot(opts.cwd)
394
+ : null;
395
+ const workingDirectory = trustedRoot || opts.cwd || process.cwd();
396
+ sandboxManagedTree =
397
+ explicitSandboxShell && this._deps.platform === "linux";
366
398
  const spawnOptions = {
367
- cwd: opts.cwd || process.cwd(),
368
- shell: true,
399
+ cwd: workingDirectory,
400
+ shell: explicitSandboxShell ? false : true,
369
401
  // POSIX: own process group so a timeout / stopAll can signal the whole
370
402
  // tree (shell + the real hook command), not just the shell wrapper.
371
403
  // No-op on Windows, where the tree is reaped via `taskkill /T` instead.
372
- detached: this._deps.platform !== "win32",
404
+ detached: this._deps.platform !== "win32" && !sandboxManagedTree,
373
405
  env: {
374
406
  ...process.env,
375
407
  CLAUDE_HOOK_EVENT: hook.event || payload.hook_event_name || "",
376
408
  },
377
409
  ...(sandboxPolicy ? { sandboxPolicy } : {}),
378
410
  };
379
- if (typeof this._deps.run !== "function") {
411
+ const brokerRun =
412
+ explicitSandboxShell &&
413
+ opts.broker &&
414
+ typeof opts.broker.spawn === "function"
415
+ ? opts.broker.spawn.bind(opts.broker)
416
+ : null;
417
+ if (typeof (brokerRun || this._deps.run) !== "function") {
380
418
  throw new Error("async hook process runner unavailable");
381
419
  }
382
- child = this._deps.run(hook.command, {
420
+ const processOptions = {
383
421
  ...spawnOptions,
384
422
  origin: hook.origin || "async-hook:command",
385
423
  policy: "allow",
@@ -387,7 +425,39 @@ class AsyncHookSupervisor {
387
425
  pluginId: hook.pluginId || null,
388
426
  pluginVersion: hook.pluginVersion || null,
389
427
  pluginSource: hook.pluginSource || null,
390
- });
428
+ };
429
+ if (explicitSandboxShell) {
430
+ const invocation = buildExplicitHookShellInvocation(hook.command, {
431
+ platform: this._deps.platform,
432
+ });
433
+ const run = brokerRun || this._deps.run;
434
+ let brokerOptions = processOptions;
435
+ if (brokerRun) {
436
+ const issuer =
437
+ opts.broker.issueLinuxWorkspaceSandboxExecutionContract;
438
+ const sandboxExecutionContract = issueTrustedHookSandboxContract({
439
+ issuer,
440
+ receiver: opts.broker,
441
+ file: invocation.file,
442
+ args: invocation.argv,
443
+ options: processOptions,
444
+ trustedRoot,
445
+ label: "trusted async-hook sandbox contract issuance failed",
446
+ });
447
+ if (this._deps.platform === "linux" && !sandboxExecutionContract) {
448
+ throw sandboxBoundaryError(
449
+ "trusted Linux async-hook sandbox contract issuer is unavailable",
450
+ );
451
+ }
452
+ brokerOptions = {
453
+ ...processOptions,
454
+ ...(sandboxExecutionContract ? { sandboxExecutionContract } : {}),
455
+ };
456
+ }
457
+ child = run(invocation.file, invocation.argv, brokerOptions);
458
+ } else {
459
+ child = this._deps.run(hook.command, processOptions);
460
+ }
391
461
  } catch (err) {
392
462
  const sandboxDenied =
393
463
  err?.code === "ERR_PROCESS_SANDBOX_BOUNDARY_UNSATISFIED";
@@ -406,7 +476,7 @@ class AsyncHookSupervisor {
406
476
  let out = "";
407
477
  let errOut = "";
408
478
  let killedByTimeout = false;
409
- const rec = { child, timer: null };
479
+ const rec = { child, timer: null, sandboxManagedTree };
410
480
  this._running.set(key, rec);
411
481
 
412
482
  try {
@@ -432,7 +502,7 @@ class AsyncHookSupervisor {
432
502
  killedByTimeout = true;
433
503
  // Tree-kill: a hook that spawned a subprocess (e.g. `npm test`) must not
434
504
  // leave that subprocess orphaned when it exceeds its timeout.
435
- this._killChildTree(child, "SIGTERM");
505
+ this._killChildTree(child, "SIGTERM", { sandboxManagedTree });
436
506
  }, timeout);
437
507
  if (typeof timer.unref === "function") timer.unref();
438
508
  rec.timer = timer;
@@ -594,7 +664,9 @@ class AsyncHookSupervisor {
594
664
  }
595
665
  // Tree-kill (not a bare child.kill) so a session end / process exit never
596
666
  // leaves an orphaned hook subprocess — the guarantee this class documents.
597
- this._killChildTree(rec.child, "SIGTERM");
667
+ this._killChildTree(rec.child, "SIGTERM", {
668
+ sandboxManagedTree: rec.sandboxManagedTree === true,
669
+ });
598
670
  this._running.delete(key);
599
671
  }
600
672
  try {
@@ -1,5 +1,13 @@
1
1
  import supervisor from "./async-hook-supervisor.cjs";
2
2
  import executionBroker from "./process-execution-broker/index.js";
3
+ import hookShellCommand from "./hook-shell-command.cjs";
4
+
5
+ const {
6
+ issueTrustedHookSandboxContract,
7
+ requireTrustedHookRoot,
8
+ requiresExplicitHookShell,
9
+ sandboxBoundaryError,
10
+ } = hookShellCommand;
3
11
 
4
12
  const brokerRunner = executionBroker.spawn.bind(executionBroker);
5
13
  const brokerSyncRunner = executionBroker.spawnSync.bind(executionBroker);
@@ -9,12 +17,50 @@ export const _processDeps = {
9
17
  runSync: brokerSyncRunner,
10
18
  };
11
19
 
12
- function runAsyncHook(command, options = {}) {
13
- return _processDeps.run(command, [], {
14
- ...options,
15
- origin: options.origin || "async-hook:command",
20
+ function normalizeInvocation(argsOrOptions, maybeOptions) {
21
+ return Array.isArray(argsOrOptions)
22
+ ? { args: argsOrOptions, options: maybeOptions || {} }
23
+ : { args: [], options: argsOrOptions || {} };
24
+ }
25
+
26
+ function runAsyncHook(command, argsOrOptions = {}, maybeOptions) {
27
+ const { args, options } = normalizeInvocation(argsOrOptions, maybeOptions);
28
+ const callerOptions = { ...options };
29
+ delete callerOptions.sandboxExecutionContract;
30
+ const processOptions = {
31
+ ...callerOptions,
32
+ origin: callerOptions.origin || "async-hook:command",
16
33
  policy: "allow",
17
34
  scope: "async-hook",
35
+ };
36
+ const requiresContract = requiresExplicitHookShell(
37
+ processOptions.sandboxPolicy,
38
+ );
39
+ const trustedRoot = requiresContract
40
+ ? requireTrustedHookRoot(processOptions.cwd)
41
+ : null;
42
+ const sandboxExecutionContract = requiresContract
43
+ ? issueTrustedHookSandboxContract({
44
+ issuer: executionBroker.issueLinuxWorkspaceSandboxExecutionContract,
45
+ receiver: executionBroker,
46
+ file: command,
47
+ args,
48
+ options: processOptions,
49
+ trustedRoot,
50
+ })
51
+ : null;
52
+ if (
53
+ process.platform === "linux" &&
54
+ requiresContract &&
55
+ !sandboxExecutionContract
56
+ ) {
57
+ throw sandboxBoundaryError(
58
+ "trusted Linux async-hook sandbox contract could not be issued",
59
+ );
60
+ }
61
+ return _processDeps.run(command, args, {
62
+ ...processOptions,
63
+ ...(sandboxExecutionContract ? { sandboxExecutionContract } : {}),
18
64
  });
19
65
  }
20
66
 
@@ -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) {
@@ -0,0 +1,362 @@
1
+ /**
2
+ * Durable, secret-free governance records for short-lived `cc team` / `cc
3
+ * batch` worktree runs.
4
+ *
5
+ * These are deliberately separate from background-agent state: a team/batch
6
+ * task is not independently attachable or stoppable, and pretending otherwise
7
+ * could make `cc daemon stop` kill the shared coordinator process. The daemon
8
+ * JSON view may project these records read-only for IDE task rows.
9
+ *
10
+ * Prompts, argv, tool arguments, model output, credentials, and side-effect
11
+ * metadata never enter this store.
12
+ */
13
+
14
+ import {
15
+ existsSync,
16
+ mkdirSync,
17
+ readFileSync,
18
+ readdirSync,
19
+ renameSync,
20
+ writeFileSync,
21
+ } from "node:fs";
22
+ import { join } from "node:path";
23
+ import { createHash, randomBytes } from "node:crypto";
24
+ import { getHomeDir } from "./paths.js";
25
+
26
+ export const COLLABORATION_RUN_VERSION = 1;
27
+ const RUN_KINDS = new Set(["team", "batch"]);
28
+ const PERMISSION_MODES = new Set([
29
+ "manual",
30
+ "auto",
31
+ "dontAsk",
32
+ "default",
33
+ "plan",
34
+ "acceptEdits",
35
+ "bypassPermissions",
36
+ ]);
37
+ const TERMINAL_RUN_STATUSES = new Set(["completed", "failed", "cancelled"]);
38
+ const UNIT_STATUSES = new Set([
39
+ "pending",
40
+ "running",
41
+ "completed",
42
+ "failed",
43
+ "test-failed",
44
+ "no-changes",
45
+ "cancelled",
46
+ ]);
47
+
48
+ export const _deps = {
49
+ now: () => Date.now(),
50
+ randomHex: () => randomBytes(3).toString("hex"),
51
+ };
52
+
53
+ function boundedString(value, max) {
54
+ if (value === null || value === undefined) return null;
55
+ const clean = String(value)
56
+ .replace(/\p{Cc}/gu, "")
57
+ .trim();
58
+ return clean ? clean.slice(0, max) : null;
59
+ }
60
+
61
+ function positiveNumber(value) {
62
+ const number = Number(value);
63
+ return Number.isFinite(number) && number > 0 ? number : null;
64
+ }
65
+
66
+ function nonNegativeInteger(value) {
67
+ const number = Number(value);
68
+ return Number.isFinite(number) && number >= 0 ? Math.floor(number) : 0;
69
+ }
70
+
71
+ function safeKind(kind) {
72
+ const value = String(kind || "");
73
+ if (!RUN_KINDS.has(value)) {
74
+ throw new Error(
75
+ `Unsupported collaboration run kind: ${value || "(empty)"}`,
76
+ );
77
+ }
78
+ return value;
79
+ }
80
+
81
+ function safeRunId(id) {
82
+ const value = String(id || "");
83
+ if (value.length > 160 || !/^(team|batch)-[a-zA-Z0-9._-]+$/.test(value)) {
84
+ throw new Error(`Invalid collaboration run id: ${value || "(empty)"}`);
85
+ }
86
+ return value;
87
+ }
88
+
89
+ export function collaborationRunsDir() {
90
+ const dir =
91
+ process.env.CC_COLLABORATION_RUNS_DIR ||
92
+ join(getHomeDir(), "collaboration-runs");
93
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
94
+ return dir;
95
+ }
96
+
97
+ export function createCollaborationRunId(kind) {
98
+ const normalizedKind = safeKind(kind);
99
+ return `${normalizedKind}-${Math.floor(_deps.now())}-${_deps.randomHex()}`;
100
+ }
101
+
102
+ export function createCollaborationSessionId(runId, key) {
103
+ const safeId = safeRunId(runId);
104
+ const digest = createHash("sha256")
105
+ .update(String(key || "unit"))
106
+ .digest("hex")
107
+ .slice(0, 12);
108
+ return `session-${safeId}-${digest}`.slice(0, 256);
109
+ }
110
+
111
+ export function collaborationRunPath(id) {
112
+ return join(collaborationRunsDir(), `${safeRunId(id)}.json`);
113
+ }
114
+
115
+ function normalizeBudget(resourceBudget) {
116
+ const source =
117
+ resourceBudget && typeof resourceBudget === "object" ? resourceBudget : {};
118
+ return {
119
+ maxTurns: positiveNumber(source.maxTurns),
120
+ maxCostUsd: positiveNumber(source.maxCostUsd),
121
+ maxTasks: positiveNumber(source.maxTasks),
122
+ maxTokens: positiveNumber(source.maxTokens),
123
+ maxWallMs: positiveNumber(source.maxWallMs),
124
+ };
125
+ }
126
+
127
+ function summarizeSideEffects(source) {
128
+ const ops =
129
+ source && typeof source.list === "function"
130
+ ? source.list()
131
+ : Array.isArray(source?.ops)
132
+ ? source.ops
133
+ : null;
134
+ if (ops) {
135
+ const count = (state) => ops.filter((op) => op?.state === state).length;
136
+ return {
137
+ total: ops.length,
138
+ unsettled: ops.filter(
139
+ (op) => op?.state === "prepared" || op?.state === "started",
140
+ ).length,
141
+ unknown: count("unknown"),
142
+ committed: count("committed"),
143
+ failed: count("failed"),
144
+ };
145
+ }
146
+ const value = source && typeof source === "object" ? source : {};
147
+ return {
148
+ total: nonNegativeInteger(value.total),
149
+ unsettled: nonNegativeInteger(value.unsettled),
150
+ unknown: nonNegativeInteger(value.unknown),
151
+ committed: nonNegativeInteger(value.committed),
152
+ failed: nonNegativeInteger(value.failed),
153
+ };
154
+ }
155
+
156
+ function normalizeUnit(unit, run) {
157
+ const key = boundedString(unit?.key, 256);
158
+ if (!key) throw new Error("Collaboration unit key is required");
159
+ const status = UNIT_STATUSES.has(unit?.status) ? unit.status : "pending";
160
+ const sessionId = boundedString(unit?.sessionId, 256);
161
+ return {
162
+ key,
163
+ owner: `${run.kind}:${run.id}:${key}`.slice(0, 512),
164
+ sessionId,
165
+ branch: boundedString(unit?.branch, 512),
166
+ worktreePath: boundedString(unit?.worktreePath, 4096),
167
+ status,
168
+ startedAt: positiveNumber(unit?.startedAt),
169
+ endedAt: positiveNumber(unit?.endedAt),
170
+ sideEffects: summarizeSideEffects(unit?.sideEffects),
171
+ };
172
+ }
173
+
174
+ function normalizeRun(input) {
175
+ const kind = safeKind(input?.kind);
176
+ const id = safeRunId(input?.id);
177
+ if (!id.startsWith(`${kind}-`)) {
178
+ throw new Error(
179
+ `Collaboration run id "${id}" does not match kind "${kind}"`,
180
+ );
181
+ }
182
+ const permissionMode = PERMISSION_MODES.has(input?.permissionMode)
183
+ ? input.permissionMode
184
+ : "default";
185
+ const status = new Set(["running", ...TERMINAL_RUN_STATUSES]).has(
186
+ input?.status,
187
+ )
188
+ ? input.status
189
+ : "running";
190
+ const startedAt = positiveNumber(input?.startedAt) || _deps.now();
191
+ const run = {
192
+ version: COLLABORATION_RUN_VERSION,
193
+ id,
194
+ kind,
195
+ owner: `${kind}:${id}`,
196
+ status,
197
+ repoRoot: boundedString(input?.repoRoot, 4096),
198
+ permissionMode,
199
+ resourceBudget: normalizeBudget(input?.resourceBudget),
200
+ startedAt,
201
+ updatedAt: positiveNumber(input?.updatedAt) || startedAt,
202
+ endedAt: TERMINAL_RUN_STATUSES.has(status)
203
+ ? positiveNumber(input?.endedAt) || startedAt
204
+ : null,
205
+ units: [],
206
+ };
207
+ const seen = new Set();
208
+ for (const unit of Array.isArray(input?.units)
209
+ ? input.units.slice(0, 1000)
210
+ : []) {
211
+ const normalized = normalizeUnit(unit, run);
212
+ if (seen.has(normalized.key)) {
213
+ throw new Error(`Duplicate collaboration unit key: ${normalized.key}`);
214
+ }
215
+ seen.add(normalized.key);
216
+ run.units.push(normalized);
217
+ }
218
+ return run;
219
+ }
220
+
221
+ export function writeCollaborationRun(input) {
222
+ const run = normalizeRun(input);
223
+ const target = collaborationRunPath(run.id);
224
+ const temp = `${target}.${process.pid}.tmp`;
225
+ writeFileSync(temp, JSON.stringify(run, null, 2), { mode: 0o600 });
226
+ renameSync(temp, target);
227
+ return run;
228
+ }
229
+
230
+ export function createCollaborationRun(input) {
231
+ const kind = safeKind(input?.kind);
232
+ const id = input?.id ? safeRunId(input.id) : createCollaborationRunId(kind);
233
+ if (existsSync(collaborationRunPath(id))) {
234
+ throw new Error(`Collaboration run already exists: ${id}`);
235
+ }
236
+ return writeCollaborationRun({
237
+ ...input,
238
+ id,
239
+ kind,
240
+ status: "running",
241
+ startedAt: input?.startedAt || _deps.now(),
242
+ });
243
+ }
244
+
245
+ export function readCollaborationRun(id) {
246
+ const target = collaborationRunPath(id);
247
+ if (!existsSync(target)) return null;
248
+ try {
249
+ return normalizeRun(JSON.parse(readFileSync(target, "utf8")));
250
+ } catch {
251
+ return null;
252
+ }
253
+ }
254
+
255
+ export function updateCollaborationRun(id, updater) {
256
+ const current = readCollaborationRun(id);
257
+ if (!current) throw new Error(`Collaboration run not found: ${id}`);
258
+ const draft = JSON.parse(JSON.stringify(current));
259
+ const changed =
260
+ typeof updater === "function"
261
+ ? updater(draft) || draft
262
+ : { ...draft, ...updater };
263
+ return writeCollaborationRun({
264
+ ...changed,
265
+ id: current.id,
266
+ kind: current.kind,
267
+ });
268
+ }
269
+
270
+ export function updateCollaborationUnit(runId, key, patch = {}) {
271
+ const targetKey = boundedString(key, 256);
272
+ return updateCollaborationRun(runId, (run) => {
273
+ const index = run.units.findIndex((unit) => unit.key === targetKey);
274
+ if (index < 0) {
275
+ throw new Error(`Collaboration unit not found: ${targetKey}`);
276
+ }
277
+ const current = run.units[index];
278
+ const status = UNIT_STATUSES.has(patch.status)
279
+ ? patch.status
280
+ : current.status;
281
+ run.units[index] = {
282
+ ...current,
283
+ ...(patch.branch !== undefined
284
+ ? { branch: boundedString(patch.branch, 512) }
285
+ : {}),
286
+ ...(patch.worktreePath !== undefined
287
+ ? { worktreePath: boundedString(patch.worktreePath, 4096) }
288
+ : {}),
289
+ status,
290
+ startedAt:
291
+ patch.startedAt !== undefined
292
+ ? positiveNumber(patch.startedAt)
293
+ : current.startedAt,
294
+ endedAt:
295
+ patch.endedAt !== undefined
296
+ ? positiveNumber(patch.endedAt)
297
+ : current.endedAt,
298
+ sideEffects:
299
+ patch.sideEffects !== undefined
300
+ ? summarizeSideEffects(patch.sideEffects)
301
+ : current.sideEffects,
302
+ };
303
+ run.updatedAt = _deps.now();
304
+ return run;
305
+ });
306
+ }
307
+
308
+ export function finalizeCollaborationRun(id, status = "completed") {
309
+ if (!TERMINAL_RUN_STATUSES.has(status)) {
310
+ throw new Error(`Invalid terminal collaboration status: ${status}`);
311
+ }
312
+ return updateCollaborationRun(id, (run) => {
313
+ run.status = status;
314
+ run.updatedAt = _deps.now();
315
+ run.endedAt = run.updatedAt;
316
+ return run;
317
+ });
318
+ }
319
+
320
+ export function listCollaborationRuns({ limit = 200 } = {}) {
321
+ const capped = Math.max(1, Math.min(1000, Number(limit) || 200));
322
+ return readdirSync(collaborationRunsDir())
323
+ .filter((name) => /^(team|batch)-[a-zA-Z0-9._-]+\.json$/.test(name))
324
+ .map((name) => readCollaborationRun(name.slice(0, -5)))
325
+ .filter(Boolean)
326
+ .sort((a, b) => b.startedAt - a.startedAt)
327
+ .slice(0, capped);
328
+ }
329
+
330
+ /**
331
+ * Project run units into the bounded shape consumed by IDE worktree task rows.
332
+ * The caller can publish this as `managedTasks`; no control operation consumes
333
+ * these records.
334
+ */
335
+ export function projectCollaborationTasks(runs) {
336
+ const rows = [];
337
+ for (const run of Array.isArray(runs) ? runs.slice(0, 200) : []) {
338
+ for (const unit of Array.isArray(run?.units)
339
+ ? run.units.slice(0, 1000)
340
+ : []) {
341
+ if (!unit.branch && !unit.worktreePath) continue;
342
+ rows.push({
343
+ managedTaskId: `${run.id}:${unit.key}`.slice(0, 512),
344
+ runId: run.id,
345
+ runKind: run.kind,
346
+ branch: unit.branch,
347
+ worktreePath: unit.worktreePath,
348
+ status: unit.status,
349
+ governance: {
350
+ version: 1,
351
+ owner: unit.owner,
352
+ sessionId: unit.sessionId,
353
+ permissionMode: run.permissionMode,
354
+ resourceBudget: run.resourceBudget,
355
+ },
356
+ sideEffects: summarizeSideEffects(unit.sideEffects),
357
+ });
358
+ if (rows.length >= 1000) return rows;
359
+ }
360
+ }
361
+ return rows;
362
+ }
@@ -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
  }