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
@@ -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;
@@ -164,22 +164,73 @@ export function appendToolCall(sessionId, toolName, args) {
164
164
 
165
165
  /**
166
166
  * Compact tool-call record for usage attribution (用量归因): tool name +
167
- * error flag (+ optional skill and plugin/version hints) deliberately NOT
168
- * the args, which can carry whole file bodies (write_file content) and would
169
- * bloat the transcript. Written at tool-result time by the agent drivers so
170
- * `cc session usage --by tool|mcp|plugin` and `cc insights` can aggregate tool
171
- * use for any persisted session.
167
+ * error flag (+ optional skill, plugin/version, and bounded observed duration)
168
+ * — deliberately NOT the args, which can carry whole file bodies (write_file
169
+ * content) and would bloat the transcript. Written at tool-result time by the
170
+ * agent drivers so `cc session usage --by tool|mcp|plugin` and `cc insights`
171
+ * can aggregate tool use for any persisted session.
172
172
  */
173
173
  export function appendToolCallCompact(
174
174
  sessionId,
175
- { tool, isError, skill, plugin, pluginVersion } = {},
175
+ { tool, isError, skill, plugin, pluginVersion, durationMs } = {},
176
176
  ) {
177
+ const duration = normalizeCompactDuration(durationMs);
177
178
  appendEvent(sessionId, "tool_call", {
178
179
  tool: tool || "?",
179
180
  is_error: Boolean(isError),
180
181
  ...(skill ? { skill: String(skill) } : {}),
181
182
  ...(plugin ? { plugin: String(plugin) } : {}),
182
183
  ...(pluginVersion ? { plugin_version: String(pluginVersion) } : {}),
184
+ ...(duration !== null ? { duration_ms: duration } : {}),
185
+ });
186
+ }
187
+
188
+ /** Hard cap for one observed failed LLM/tool attempt (seven days). */
189
+ export const MAX_COMPACT_DURATION_MS = 7 * 24 * 60 * 60 * 1000;
190
+
191
+ function normalizeCompactDuration(value) {
192
+ if (value === null || value === undefined || value === "") return null;
193
+ const number = Number(value);
194
+ if (!Number.isFinite(number) || number < 0) return null;
195
+ return Math.min(MAX_COMPACT_DURATION_MS, Math.round(number));
196
+ }
197
+
198
+ /**
199
+ * Persist one automatic LLM stream retry without raw errors, prompts, URLs, or
200
+ * credentials. Provider/model are bounded identity labels; reason is a closed
201
+ * vocabulary produced by classifyStreamRetryReason().
202
+ */
203
+ export function appendLlmRetryCompact(
204
+ sessionId,
205
+ { attempt, durationMs, provider, model, reason } = {},
206
+ ) {
207
+ const attemptNumber = Math.max(
208
+ 1,
209
+ Math.min(15, Math.trunc(Number(attempt) || 1)),
210
+ );
211
+ const duration = normalizeCompactDuration(durationMs);
212
+ const cleanLabel = (value, max) => {
213
+ if (typeof value !== "string") return null;
214
+ const clean = value.replace(/\p{Cc}/gu, "").trim();
215
+ return clean ? clean.slice(0, max) : null;
216
+ };
217
+ const allowedReasons = new Set([
218
+ "timeout",
219
+ "dns",
220
+ "connection_refused",
221
+ "network_unreachable",
222
+ "connection_reset",
223
+ "unknown",
224
+ ]);
225
+ const cleanReason = allowedReasons.has(reason) ? reason : "unknown";
226
+ const cleanProvider = cleanLabel(provider, 64);
227
+ const cleanModel = cleanLabel(model, 128);
228
+ appendEvent(sessionId, "llm_retry", {
229
+ attempt: attemptNumber,
230
+ duration_ms: duration ?? 0,
231
+ reason: cleanReason,
232
+ ...(cleanProvider ? { provider: cleanProvider } : {}),
233
+ ...(cleanModel ? { model: cleanModel } : {}),
183
234
  });
184
235
  }
185
236
 
@@ -14,6 +14,7 @@ import { EventEmitter } from "events";
14
14
  import { pathToFileURL } from "url";
15
15
  import { safeJsonParse } from "../lib/safe-json.js";
16
16
  import { EventRuntimeProducer } from "../lib/event-runtime-producer.js";
17
+ import { resolvePluginWorkspaceAuthority } from "../lib/plugin-runtime/sandbox-policy.js";
17
18
 
18
19
  /**
19
20
  * Injectable dependencies — overridable from tests.
@@ -62,8 +63,7 @@ const URL_ELICITATION_REQUIRED = -32042;
62
63
  function supportsUrlElicitationVersion(version) {
63
64
  const normalized = String(version || "");
64
65
  return (
65
- /^\d{4}-\d{2}-\d{2}$/.test(normalized) &&
66
- normalized >= MCP_PROTOCOL_VERSION
66
+ /^\d{4}-\d{2}-\d{2}$/.test(normalized) && normalized >= MCP_PROTOCOL_VERSION
67
67
  );
68
68
  }
69
69
 
@@ -130,9 +130,7 @@ export function normalizeMcpElicitationRequest(
130
130
 
131
131
  const elicitationId = String(params.elicitationId || "").trim();
132
132
  if (!elicitationId) {
133
- const error = new Error(
134
- "URL mode MCP elicitation requires elicitationId",
135
- );
133
+ const error = new Error("URL mode MCP elicitation requires elicitationId");
136
134
  error.code = "CC_MCP_ELICITATION_INVALID";
137
135
  throw error;
138
136
  }
@@ -267,10 +265,14 @@ export class MCPClient extends EventEmitter {
267
265
  Number(options.maxConcurrentElicitations) > 0
268
266
  ? Math.min(128, Number(options.maxConcurrentElicitations))
269
267
  : 32;
270
- this._protocolVersion =
271
- String(options.protocolVersion || MCP_PROTOCOL_VERSION);
268
+ this._protocolVersion = String(
269
+ options.protocolVersion || MCP_PROTOCOL_VERSION,
270
+ );
272
271
  this._runtimeProducer = options.eventRuntimeStore
273
- ? new EventRuntimeProducer({ store: options.eventRuntimeStore, emitter: this })
272
+ ? new EventRuntimeProducer({
273
+ store: options.eventRuntimeStore,
274
+ emitter: this,
275
+ })
274
276
  : null;
275
277
  }
276
278
 
@@ -279,11 +281,11 @@ export class MCPClient extends EventEmitter {
279
281
  const sessionId = options.sessionId;
280
282
  if (sessionId != null && sessionId !== "") {
281
283
  const key = String(sessionId);
282
- if (typeof handler === "function") this._elicitationHandlers.set(key, handler);
284
+ if (typeof handler === "function")
285
+ this._elicitationHandlers.set(key, handler);
283
286
  else this._elicitationHandlers.delete(key);
284
287
  } else {
285
- this._elicitationHandler =
286
- typeof handler === "function" ? handler : null;
288
+ this._elicitationHandler = typeof handler === "function" ? handler : null;
287
289
  }
288
290
  if (Number(options.timeoutMs) > 0) {
289
291
  this._elicitationTimeoutMs = Number(options.timeoutMs);
@@ -389,8 +391,9 @@ export class MCPClient extends EventEmitter {
389
391
  requestId,
390
392
  params,
391
393
  );
392
- const negotiatedVersion =
393
- this.servers.get(String(serverName))?.protocolVersion;
394
+ const negotiatedVersion = this.servers.get(
395
+ String(serverName),
396
+ )?.protocolVersion;
394
397
  if (
395
398
  request.mode === "url" &&
396
399
  negotiatedVersion &&
@@ -431,10 +434,7 @@ export class MCPClient extends EventEmitter {
431
434
  direct !== undefined &&
432
435
  String(direct?.action || "").toLowerCase() !== "defer"
433
436
  ) {
434
- const response = this._normalizeElicitationResponse(
435
- direct,
436
- request,
437
- );
437
+ const response = this._normalizeElicitationResponse(direct, request);
438
438
  this._settleUrlElicitation(request, response);
439
439
  try {
440
440
  this._runtimeProducer?.store.acknowledgeInbox(
@@ -504,7 +504,8 @@ export class MCPClient extends EventEmitter {
504
504
  });
505
505
  this.emit("elicitation-request", {
506
506
  ...request,
507
- respond: (response) => this.respondElicitation(serverName, requestId, response),
507
+ respond: (response) =>
508
+ this.respondElicitation(serverName, requestId, response),
508
509
  cancel: () => this.cancelElicitation(serverName, requestId),
509
510
  });
510
511
  });
@@ -515,9 +516,7 @@ export class MCPClient extends EventEmitter {
515
516
  if (!id) return;
516
517
  const existing = this._urlElicitations.get(id);
517
518
  if (existing && existing.server !== request.server) {
518
- throw new Error(
519
- `MCP URL elicitation id collision across servers: ${id}`,
520
- );
519
+ throw new Error(`MCP URL elicitation id collision across servers: ${id}`);
521
520
  }
522
521
  if (existing && existing.request?.url !== request.url) {
523
522
  throw new Error(
@@ -544,7 +543,9 @@ export class MCPClient extends EventEmitter {
544
543
  const entry = this._urlElicitations.get(request.elicitationId);
545
544
  if (!entry || entry.status === "completed") return;
546
545
  entry.status =
547
- response?.action === "accept" ? "accepted" : response?.action || "decline";
546
+ response?.action === "accept"
547
+ ? "accepted"
548
+ : response?.action || "decline";
548
549
  if (entry.status === "accepted" && entry.completionNotified) {
549
550
  this._completeUrlElicitation(entry);
550
551
  }
@@ -757,7 +758,26 @@ export class MCPClient extends EventEmitter {
757
758
  `stdio transport requires a command (server "${name}")`,
758
759
  );
759
760
  }
760
- const proc = _deps.spawn(config.command, config.args || [], {
761
+ const isPlugin = config.origin === "plugin:mcp";
762
+ const pluginWorkspaceRoot = isPlugin
763
+ ? resolvePluginWorkspaceAuthority(config.pluginWorkspaceAuthority, {
764
+ origin: config.origin,
765
+ pluginId: config.pluginId,
766
+ pluginVersion: config.pluginVersion,
767
+ pluginSource: config.pluginSource,
768
+ })
769
+ : null;
770
+ if (
771
+ isPlugin &&
772
+ config.sandboxPolicy?.requiredBoundaries?.length > 0 &&
773
+ !pluginWorkspaceRoot
774
+ ) {
775
+ throw new Error(
776
+ `plugin MCP server "${name}" is missing its trusted workspace authority`,
777
+ );
778
+ }
779
+ const spawnOptions = {
780
+ cwd: pluginWorkspaceRoot || process.cwd(),
761
781
  stdio: ["pipe", "pipe", "pipe"],
762
782
  // process.env < agent identity (CLAUDECODE / session id) < the
763
783
  // server's own config.env, so an explicit per-server override wins.
@@ -776,6 +796,19 @@ export class MCPClient extends EventEmitter {
776
796
  ...(config.sandboxPolicy
777
797
  ? { sandboxPolicy: config.sandboxPolicy }
778
798
  : {}),
799
+ };
800
+ const sandboxExecutionContract =
801
+ !isPlugin || pluginWorkspaceRoot
802
+ ? executionBroker.issueLinuxWorkspaceSandboxExecutionContract(
803
+ config.command,
804
+ config.args || [],
805
+ spawnOptions,
806
+ pluginWorkspaceRoot || process.cwd(),
807
+ )
808
+ : null;
809
+ const proc = _deps.spawn(config.command, config.args || [], {
810
+ ...spawnOptions,
811
+ ...(sandboxExecutionContract ? { sandboxExecutionContract } : {}),
779
812
  });
780
813
 
781
814
  entry.process = proc;
@@ -1113,7 +1146,8 @@ export class MCPClient extends EventEmitter {
1113
1146
  } catch {
1114
1147
  return false;
1115
1148
  }
1116
- if (request.mode !== "url" || ids.has(request.elicitationId)) return false;
1149
+ if (request.mode !== "url" || ids.has(request.elicitationId))
1150
+ return false;
1117
1151
  ids.add(request.elicitationId);
1118
1152
  normalized.push(request);
1119
1153
  }
@@ -1392,10 +1426,7 @@ export class MCPClient extends EventEmitter {
1392
1426
  entry._httpMessageStream = stream;
1393
1427
  stream.promise = (async () => {
1394
1428
  try {
1395
- while (
1396
- !stream.stopped &&
1397
- this.servers.get(serverName) === entry
1398
- ) {
1429
+ while (!stream.stopped && this.servers.get(serverName) === entry) {
1399
1430
  stream.controller =
1400
1431
  typeof AbortController === "function"
1401
1432
  ? new AbortController()
@@ -1427,9 +1458,7 @@ export class MCPClient extends EventEmitter {
1427
1458
  );
1428
1459
  }
1429
1460
  const contentType = response.headers?.get
1430
- ? String(
1431
- response.headers.get("content-type") || "",
1432
- ).toLowerCase()
1461
+ ? String(response.headers.get("content-type") || "").toLowerCase()
1433
1462
  : "";
1434
1463
  if (!contentType.includes("text/event-stream")) return;
1435
1464
  const cap = Number.isFinite(entry.config?.maxBufferChars)
@@ -1438,8 +1467,7 @@ export class MCPClient extends EventEmitter {
1438
1467
  await _consumeSseMessageStream(response, {
1439
1468
  cap,
1440
1469
  stream,
1441
- onMessage: (message) =>
1442
- this._handleMessage(serverName, message),
1470
+ onMessage: (message) => this._handleMessage(serverName, message),
1443
1471
  });
1444
1472
  } catch (error) {
1445
1473
  if (stream.stopped || stream.controller?.signal.aborted) return;
@@ -1564,11 +1592,8 @@ export class MCPClient extends EventEmitter {
1564
1592
 
1565
1593
  let envelope;
1566
1594
  if (contentType.includes("text/event-stream")) {
1567
- envelope = await _extractSseResponse(
1568
- response,
1569
- id,
1570
- cap,
1571
- (message) => this._handleMessage(serverName, message),
1595
+ envelope = await _extractSseResponse(response, id, cap, (message) =>
1596
+ this._handleMessage(serverName, message),
1572
1597
  );
1573
1598
  } else {
1574
1599
  const text = await _readBodyCapped(response, cap);
@@ -1813,8 +1838,7 @@ export class MCPClient extends EventEmitter {
1813
1838
  ...(entry.httpSessionId
1814
1839
  ? { "Mcp-Session-Id": entry.httpSessionId }
1815
1840
  : {}),
1816
- "MCP-Protocol-Version":
1817
- entry.protocolVersion || this._protocolVersion,
1841
+ "MCP-Protocol-Version": entry.protocolVersion || this._protocolVersion,
1818
1842
  };
1819
1843
  try {
1820
1844
  const pending = _deps.fetch(entry.httpUrl, {
@@ -1986,9 +2010,7 @@ async function _consumeSseMessageStream(
1986
2010
  if (done) break;
1987
2011
  buffer += decoder.decode(value, { stream: true });
1988
2012
  if (cap > 0 && buffer.length > cap) {
1989
- throw new Error(
1990
- `MCP HTTP SSE event exceeded the ${cap}-char cap`,
1991
- );
2013
+ throw new Error(`MCP HTTP SSE event exceeded the ${cap}-char cap`);
1992
2014
  }
1993
2015
  let boundary;
1994
2016
  while ((boundary = buffer.search(/\r?\n\r?\n/)) >= 0) {
@@ -94,7 +94,10 @@ export async function runBatch(opts, deps) {
94
94
  try {
95
95
  worktreePath = createWorktree(unit.key, branch);
96
96
  onEvent({ type: "unit:start", key: unit.key, branch });
97
- await runAgent(unit.prompt, worktreePath);
97
+ await runAgent(unit.prompt, worktreePath, {
98
+ key: unit.key,
99
+ branch,
100
+ });
98
101
 
99
102
  const stat = diffStat(worktreePath);
100
103
  record.filesChanged = stat.filesChanged;
@@ -126,6 +126,8 @@ export class TeamWorktreeCoordinator {
126
126
  sparsePaths: options.sparsePaths ?? null,
127
127
  symlinkDirectories: options.symlinkDirectories ?? null,
128
128
  };
129
+ this._onWorktree =
130
+ typeof options.onWorktree === "function" ? options.onWorktree : null;
129
131
  }
130
132
 
131
133
  isGitRepo() {
@@ -173,6 +175,7 @@ export class TeamWorktreeCoordinator {
173
175
  wtOptions,
174
176
  );
175
177
  this._created.set(key, { branch, path: worktreePath, committed: false });
178
+ this._onWorktree?.({ key, branch, path: worktreePath, holder });
176
179
  if (typeof runInWorktree === "function") {
177
180
  await runInWorktree({ key, task, holder, cwd: worktreePath });
178
181
  } else {