chainlesschain 0.162.180 → 0.162.182

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 (263) hide show
  1. package/package.json +7 -3
  2. package/scripts/build-windows-sandbox-helper.ps1 +163 -0
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-D_TIL54O.js → AIOps-DzZ5_xoj.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-eQwy9r-f.js → ActionButton-DeLXddIg.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-D2eNuDgT.js → Analytics-rvDCmet1.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-CFhwOFsf.js → AppLayout-Yt6QVDnp.js} +5 -5
  8. package/src/assets/web-panel/assets/{Artifacts-CJUMw6PB.js → Artifacts-BS112K5L.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-CDB9V58o.js → Audit-0wJuQrNu.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-TBppYFjN.js → BackgroundAgents-B90PMgbn.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-CYEOUpzJ.js → Backup-TkgMglQQ.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-C88fFT2u.js → BaseInput-CQVS9p1l.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-mkr1vC3N.js → Chat-YTjQHoC7.js} +6 -6
  14. package/src/assets/web-panel/assets/ChatBubbleRenderer-PR074hUN.js +1 -0
  15. package/src/assets/web-panel/assets/{Checkbox-CVU4xqti.js → Checkbox-5bwaOabq.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-rrNcByz3.js → Codegen-BFmNQ_Xe.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-Cqu-zCvh.js → Col-BT_u4Dzp.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-RGRlSgXV.js → Community-CJP52u5b.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-DBm6epOK.js → Compact-BpzhJE9i.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-BgKw5iaW.js → Compliance-Cg7rfslp.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-BqQcNO3Z.js → Cowork-JisxL6Yy.js} +3 -3
  22. package/src/assets/web-panel/assets/{Cron-DO5gTFNb.js → Cron-Bu7Ug_MC.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-B8hbJNuN.js → Crosschain-CvuqoLdj.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-B08bFJ2C.js → DID-C2u9G8Pu.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-xNdy_ifo.js → Dashboard-BFt5Yv3d.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-D-KIZgWL.js → Dropdown-DIaT9Wxq.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-DKCvsaGo.js → EmailListRenderer-D_A601Y4.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-D_cfqama.js → FamilyGuardDashboard--IyQ8I8w.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-B-UqQD68.js → Federation-BihWHxuY.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-BWWvWuJW.js → FormItemContext-BB8dOXc_.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-CPchx6N7.js → GenericCardRenderer-DQYhj1HH.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-Vx4co9Ff.js → Git-B-2QZxGf.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-D4x0pp_m.js → Governance-CusSb-nG.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-T3KvwPYR.js → Inference-BAjmvPPL.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-Ddv3KUv3.js → KnowledgeGraph-DoNYWKYq.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-DfH-Dkae.js → Logs-B8fKWD-n.js} +2 -2
  37. package/src/assets/web-panel/assets/{MarkdownRenderer-DHW3PbCq.js → MarkdownRenderer-BlJm1J7E.js} +1 -1
  38. package/src/assets/web-panel/assets/{Marketplace-tjgb0R3D.js → Marketplace-CyOscbSL.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-DHAk5nYX.js → McpTools-Ex6Om5da.js} +5 -5
  40. package/src/assets/web-panel/assets/{Memory-A2Yx7thM.js → Memory-CDcD8l8r.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-BEmTVfN5.js → MobileBridge-xfBqoFDg.js} +1 -1
  42. package/src/assets/web-panel/assets/{MobileProjects-DuFHUgE4.js → MobileProjects-BwObMMA2.js} +1 -1
  43. package/src/assets/web-panel/assets/{Mtc-BDSll0OZ.js → Mtc-CIWd36OV.js} +4 -4
  44. package/src/assets/web-panel/assets/{MtcAudit-CuBvluaS.js → MtcAudit-CPX0ouuA.js} +4 -4
  45. package/src/assets/web-panel/assets/{Multisig-BmXAA-Sb.js → Multisig-BDJsIUuW.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-ci2stZAe.js → NLProgramming-DEkbuD3N.js} +1 -1
  47. package/src/assets/web-panel/assets/{Notes-D-1RpqnS.js → Notes-DJpHRhH1.js} +3 -3
  48. package/src/assets/web-panel/assets/{NotificationSettings-C0GyitIT.js → NotificationSettings-C1UZ-Qd5.js} +1 -1
  49. package/src/assets/web-panel/assets/{OrderTableRenderer-U2bN-hj5.js → OrderTableRenderer-GlJtqLjT.js} +1 -1
  50. package/src/assets/web-panel/assets/{Organization-BNZgpPci.js → Organization-Bbhj0fac.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-CDidKmUN.js → Overflow-1H04x3kx.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-BnreBaXq.js → P2P-CDpPe3et.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-DwJUxO9j.js → PdhVaultBrowser-MUX0Bpvw.js} +3 -3
  54. package/src/assets/web-panel/assets/{Permissions-BQGgJPH0.js → Permissions-A07-0Ci1.js} +4 -4
  55. package/src/assets/web-panel/assets/{PersonalDataHub-k_fSkCwL.js → PersonalDataHub-DhhDWxAZ.js} +5 -5
  56. package/src/assets/web-panel/assets/{Pipeline-WGqKMqau.js → Pipeline-CyfbJfe-.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-DtHj9Ymq.js → Privacy-CNFps3ED.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-B3wH2SFC.js → ProjectInit--T-u3Dr5.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-Cd0cgcA1.js → ProjectSettings-DJmrK2fC.js} +2 -2
  60. package/src/assets/web-panel/assets/Projects-Qx_MdX9k.js +1 -0
  61. package/src/assets/web-panel/assets/{Providers-DTpxEXur.js → Providers-DLzzbOZd.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-B2v6gR-C.js → QrScannerModal-CexOomKS.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-D3Nr9xXR.js → QuickAsk-QklGi19_.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-C9inoMky.js → Recommend-Bb9kwSw8.js} +1 -1
  65. package/src/assets/web-panel/assets/RemoteSession-DZuVNNN5.js +5 -0
  66. package/src/assets/web-panel/assets/{Reputation-41KFG646.js → Reputation-Dr5cLP0N.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-DqAaqqCv.js → Row-oCQHYv5t.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-CMDpCDKc.js → RssFeed-IFDEykIR.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-B6mtegNL.js → Search-CspVIxDc.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-BaePFVZ8.js → Security-Bm66o_DH.js} +3 -3
  71. package/src/assets/web-panel/assets/{Services-DeG3Qscl.js → Services-DvanNqXl.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-D_QP7F90.js → Skeleton-CHjEe9_O.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-DLGyUgIA.js → Skills-Cb8jWjMX.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-BN2pxW1Q.js → Sla-DxZm1491.js} +1 -1
  75. package/src/assets/web-panel/assets/{SpeechSettings-BWsZPd8B.js → SpeechSettings-DWxEEclC.js} +1 -1
  76. package/src/assets/web-panel/assets/{SyncSettings-VOZRPODW.js → SyncSettings-FIvOFw5p.js} +2 -2
  77. package/src/assets/web-panel/assets/Tasks-CyuLnDFR.js +1 -0
  78. package/src/assets/web-panel/assets/{Templates-eJSob-q0.js → Templates-CaNjzV_t.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-Bdwk8Rwy.js → Tenant-DjlP6TmH.js} +1 -1
  80. package/src/assets/web-panel/assets/Terminal-DwzGMniO.js +3 -0
  81. package/src/assets/web-panel/assets/{TimelineRenderer-D5qkbPKT.js → TimelineRenderer-DoZ8y7y8.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-DKgwn8hI.js → Tokens-390cg2TC.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-HlEHniGM.js → Trigger-CLrtCj5_.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-CdPcsBpN.js → Trust-DpESfSPn.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-vAdXd-vG.js → UkeySign-CN6279O0.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-9hHBYFnH.js → VideoEditing-DPtAUlSe.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-Dxh5d1yR.js → Wallet-nKOMV_1n.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-BsFmLHkp.js → WebAuthn-HuXlSOc4.js} +5 -5
  89. package/src/assets/web-panel/assets/{WorkflowEditor-Cn0OIwLn.js → WorkflowEditor-CUfFjARi.js} +1 -1
  90. package/src/assets/web-panel/assets/chat-6Eq0PcZJ.js +1 -0
  91. package/src/assets/web-panel/assets/{colors-Du9jzLN0.js → colors-BP_Sf6Iy.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-DhEscfzw.js → compact-item-BufMKIh5.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-CwzEfF3i.js → createContext-BBMXxWSW.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-DgVKduH2.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-DT_GSE6J.js → hasIn-fUK_tRO2.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-tND8-TDU.js → index-2A5ph8b2.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-B8s5FxJY.js → index-3FuwBewR.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-53z1YUxX.js → index-6GmBa4Kg.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BFD0cEna.js → index-B2vS66eI.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CQWxDOvM.js → index-B7Etk0Te.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-VeHs6qH0.js → index-B9WYSn4m.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CGvgnfl9.js → index-BHy6yNiF.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-D3llLkYM.js → index-BMLHv389.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Dny_ZnAO.js → index-BWKoBU8R.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DzjzrOli.js → index-Bkg4ZePE.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DN60VBn5.js → index-C44atv4k.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-v5HLYCVa.js → index-CE2DdTP2.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BF-ZCVGE.js → index-CGjmILDm.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-gFjYdstW.js → index-CW_G-MXw.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-NA2oBLSB.js → index-CZ6Yt17L.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Mobw3PHs.js → index-CeASi4fr.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DWL3SYN3.js → index-CgiECHu2.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-1LoutPJd.js → index-Cp8vhyZy.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BnHhFnwt.js → index-CwSf5pnf.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-B6QDWRLk.js → index-D4_MuGkd.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-D-QkW1fp.js → index-D9vU25ij.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-C4SN3by5.js → index-DASluSqY.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CyEhUJcu.js → index-DLpIs_P_.js} +1 -1
  119. package/src/assets/web-panel/assets/index-DOZTSoS7.js +1 -0
  120. package/src/assets/web-panel/assets/{index-BrkJ7JPm.js → index-DPdUNk1i.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Cnok8mhZ.js → index-DVsqvI4M.js} +1 -1
  122. package/src/assets/web-panel/assets/index-DhpvjpqI.js +1 -0
  123. package/src/assets/web-panel/assets/{index-CAVW0cWM.js → index-DmkMXwnR.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-Cc9aPkv-.js → index-DocdRcbd.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DnVVYKCE.js → index-Dyngbd29.js} +20 -20
  126. package/src/assets/web-panel/assets/{index-D8gt0j7r.js → index-GwdRUZ-x.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BIYTjbv_.js → index-LvCBfTLo.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DhXay17G.js → index-cpEli44X.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BQWENaZf.js → index-fzaMtSJ8.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DfRDyY1q.js → index-pum7HYBA.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BgSR2WMG.js → index-t8N1tSeX.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-D5IytTND.js → index-uCbEN7cY.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-DtRwLhee.js → index-wKTLbKyc.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-D4kZoXNr.js → index-wV6iGdpY.js} +1 -1
  135. package/src/assets/web-panel/assets/{initDefaultProps-Cvt59c10.js → initDefaultProps-V5Fo95FM.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-D15pPEzl.js → motion-BYWU0hUT.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-N55KeXJ7.js → move-Cr1M-J2h.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-CyFO1bYP.js → mtc-parser-BysQKi2N.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-B5IXfxU2.js → omit-D1zHU8GC.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-CpVqsulV.js → pickAttrs-CUArxtfi.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-ME36sMF3.js → placementArrow-ChoYEys0.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-BXAIR1r-.js → responsiveObserve-BQRs3Mo7.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-BkOX3-Dp.js → slide-B8zbsCkg.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-Cy4Aklpt.js → statusUtils-C2U8Gs6h.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-gUd2T206.js → styleChecker-Dd8MgL_V.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-CaVD4o99.js → useFlexGapSupport-hknqY5NH.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs--pFGl0KD.js → useFs-CLTAmcfw.js} +1 -1
  148. package/src/assets/web-panel/assets/{usePersonalDataHub-D45iP86i.js → usePersonalDataHub-ByegHbiB.js} +1 -1
  149. package/src/assets/web-panel/assets/{vnode-BayoChH_.js → vnode-DqlAy5DP.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-CpP38poi.js → zoom-DiXiztQn.js} +1 -1
  151. package/src/assets/web-panel/index.html +1 -1
  152. package/src/commands/agenda.js +115 -3
  153. package/src/commands/agent.js +10 -1
  154. package/src/commands/background-session.js +2 -1
  155. package/src/commands/context.js +69 -2
  156. package/src/data/changelog.json +26 -0
  157. package/src/gateways/terminal/PtyManager.js +45 -4
  158. package/src/gateways/ws/background-agent-protocol.js +38 -0
  159. package/src/gateways/ws/message-dispatcher.js +1 -0
  160. package/src/gateways/ws/remote-session-protocol.js +65 -6
  161. package/src/gateways/ws/ws-server.js +18 -2
  162. package/src/harness/background-task-manager.js +70 -2
  163. package/src/harness/feature-flags.js +15 -8
  164. package/src/harness/mcp-client.js +618 -43
  165. package/src/harness/worktree-isolator.js +14 -3
  166. package/src/lazy-dispatch.js +30 -1
  167. package/src/lib/agent-authority.js +22 -0
  168. package/src/lib/agent-core.js +3 -0
  169. package/src/lib/agent-sandbox.js +161 -0
  170. package/src/lib/agent-schedule-store.js +45 -22
  171. package/src/lib/agent-team/team-runner.js +22 -0
  172. package/src/lib/artifact-store.js +35 -0
  173. package/src/lib/async-hook-supervisor.cjs +13 -2
  174. package/src/lib/audit-mtc.js +3 -2
  175. package/src/lib/background-agent-supervisor.js +104 -3
  176. package/src/lib/background-interaction-journal.js +407 -0
  177. package/src/lib/background-interaction-resolver.js +23 -34
  178. package/src/lib/background-session-transport.js +12 -2
  179. package/src/lib/capability-manifest.js +41 -3
  180. package/src/lib/capability-negotiation.js +1 -1
  181. package/src/lib/channels/channel-manager.js +33 -0
  182. package/src/lib/channels/telegram-channel.js +23 -5
  183. package/src/lib/channels/webhook-channel.js +38 -15
  184. package/src/lib/chrome-connector.js +169 -23
  185. package/src/lib/config-manager.js +23 -9
  186. package/src/lib/context-breakdown.js +31 -0
  187. package/src/lib/cowork-cron.js +274 -32
  188. package/src/lib/cross-chain-mtc.js +3 -2
  189. package/src/lib/durable-security-store.js +124 -0
  190. package/src/lib/event-runtime-host.js +67 -6
  191. package/src/lib/event-runtime-store.js +163 -0
  192. package/src/lib/goal-store.js +32 -12
  193. package/src/lib/headless-manifest.js +2 -1
  194. package/src/lib/hook-environment.cjs +58 -0
  195. package/src/lib/hook-manager.js +12 -5
  196. package/src/lib/hook-runner.cjs +122 -28
  197. package/src/lib/hook-runner.js +7 -2
  198. package/src/lib/hooks-v2-producers.js +36 -0
  199. package/src/lib/hooks-v2-runtime.js +329 -52
  200. package/src/lib/interaction-adapter.js +60 -19
  201. package/src/lib/interaction-binding.js +64 -0
  202. package/src/lib/json-schema-output.js +439 -14
  203. package/src/lib/json-schema-validate.js +514 -883
  204. package/src/lib/lan-pairing-tokens.js +115 -57
  205. package/src/lib/lsp/__tests__/lsp-client.test.js +32 -0
  206. package/src/lib/lsp/__tests__/lsp-manager.test.js +25 -0
  207. package/src/lib/lsp/lsp-client.js +4 -0
  208. package/src/lib/lsp/lsp-manager.js +3 -0
  209. package/src/lib/lsp/lsp-server-registry.js +18 -0
  210. package/src/lib/mcp-client.js +1 -0
  211. package/src/lib/mcp-oauth.js +35 -21
  212. package/src/lib/packer/pkg-config-generator.js +3 -0
  213. package/src/lib/permission-decision.js +99 -0
  214. package/src/lib/plugin-monitor-supervisor.js +3 -0
  215. package/src/lib/plugin-runtime/bin.js +1148 -70
  216. package/src/lib/plugin-runtime/capabilities.js +50 -0
  217. package/src/lib/plugin-runtime/capability-consent.js +42 -35
  218. package/src/lib/plugin-runtime/hooks.js +36 -9
  219. package/src/lib/plugin-runtime/install.js +23 -7
  220. package/src/lib/plugin-runtime/lsp.js +4 -0
  221. package/src/lib/plugin-runtime/manifest.js +112 -8
  222. package/src/lib/plugin-runtime/mcp.js +30 -5
  223. package/src/lib/plugin-runtime/monitors.js +11 -0
  224. package/src/lib/plugin-runtime/plugin-options.js +94 -38
  225. package/src/lib/plugin-runtime/policy.js +29 -2
  226. package/src/lib/plugin-runtime/sandbox-policy.js +84 -0
  227. package/src/lib/plugin-runtime/scopes.js +27 -13
  228. package/src/lib/plugin-runtime/trust.js +36 -31
  229. package/src/lib/process-execution-broker/credential-agent.js +114 -16
  230. package/src/lib/process-execution-broker/credential-transport-worker.js +312 -0
  231. package/src/lib/process-execution-broker/credential-transport.js +378 -0
  232. package/src/lib/process-execution-broker/index.js +765 -49
  233. package/src/lib/process-execution-broker/platform-sandbox.js +4233 -138
  234. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  235. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  236. package/src/lib/process-execution-broker/windows-sandbox.cs +3856 -0
  237. package/src/lib/project-mcp-trust.js +21 -26
  238. package/src/lib/protocol-replay.js +19 -12
  239. package/src/lib/repl-bang-memorize.js +67 -10
  240. package/src/lib/settings-hooks.cjs +13 -0
  241. package/src/lib/skill-loader.js +83 -10
  242. package/src/lib/slash-commands.js +27 -4
  243. package/src/lib/sync-credentials.js +5 -4
  244. package/src/lib/turn-binding.js +8 -6
  245. package/src/repl/agent-repl.js +117 -31
  246. package/src/repl/repl-turn-binding.js +5 -2
  247. package/src/runtime/agent-core.js +699 -70
  248. package/src/runtime/agent-runtime.js +13 -1
  249. package/src/runtime/coding-agent-contract-shared.cjs +3 -3
  250. package/src/runtime/headless-runner.js +105 -7
  251. package/src/runtime/headless-stream.js +209 -23
  252. package/src/runtime/mcp-config.js +29 -17
  253. package/src/runtime/policies/agent-policy.js +2 -0
  254. package/src/workers/background-agent-worker.js +128 -13
  255. package/src/assets/web-panel/assets/ChatBubbleRenderer-C7bofJuA.js +0 -1
  256. package/src/assets/web-panel/assets/Projects-BbgmyQnS.js +0 -1
  257. package/src/assets/web-panel/assets/RemoteSession-Bqk3EYSu.js +0 -4
  258. package/src/assets/web-panel/assets/Tasks-3bDAUz8K.js +0 -1
  259. package/src/assets/web-panel/assets/Terminal-CsMLKIoV.js +0 -3
  260. package/src/assets/web-panel/assets/chat-CJ19O6Dh.js +0 -1
  261. package/src/assets/web-panel/assets/devWarning-BBMFnsja.js +0 -1
  262. package/src/assets/web-panel/assets/index-Do8JMV4N.js +0 -1
  263. package/src/assets/web-panel/assets/index-UYAJ6iNM.js +0 -1
@@ -93,6 +93,8 @@ import {
93
93
  formatProviderHttpError,
94
94
  formatProviderResponseError,
95
95
  } from "../lib/provider-http-error.js";
96
+ import { buildPermissionDecision } from "../lib/permission-decision.js";
97
+ import { resolveSandboxPolicyPath } from "../lib/agent-sandbox.js";
96
98
 
97
99
  export { formatProviderHttpError };
98
100
 
@@ -149,6 +151,56 @@ function runCodeProcess(file, args, options = {}, origin = "run-code") {
149
151
  });
150
152
  }
151
153
 
154
+ function collectWorkspacePluginBinSandboxPolicy(
155
+ pluginBin,
156
+ workspaceCwd,
157
+ executionCwd = workspaceCwd,
158
+ ) {
159
+ const resolvedWorkspaceCwd = path.resolve(workspaceCwd);
160
+ const pluginPolicyBoundaries = new Set();
161
+ for (const policyCwd of new Set([
162
+ resolvedWorkspaceCwd,
163
+ path.resolve(executionCwd),
164
+ ])) {
165
+ const observedPolicy = pluginBin.collectPluginBinSandboxPolicy({
166
+ cwd: policyCwd,
167
+ });
168
+ for (const boundary of observedPolicy?.requiredBoundaries || []) {
169
+ pluginPolicyBoundaries.add(boundary);
170
+ }
171
+ }
172
+ return pluginBin.pinPluginBinSandboxPolicy(
173
+ { requiredBoundaries: [...pluginPolicyBoundaries] },
174
+ { cwd: resolvedWorkspaceCwd },
175
+ );
176
+ }
177
+
178
+ function rethrowRunCodeSandboxFailure(error) {
179
+ if (
180
+ error?.sandboxFailClosed === true ||
181
+ (typeof error?.code === "string" &&
182
+ error.code.startsWith("ERR_PROCESS_SANDBOX"))
183
+ ) {
184
+ throw error;
185
+ }
186
+ }
187
+
188
+ function createPythonInterpreterProbeSandboxFailure(sandboxPolicy) {
189
+ const requiredBoundaries = [...(sandboxPolicy?.requiredBoundaries || [])];
190
+ const error = new Error(
191
+ `Python interpreter discovery cannot run without its required sandbox boundaries: ${requiredBoundaries.join(", ")}`,
192
+ );
193
+ error.code = "ERR_PROCESS_SANDBOX_BOUNDARY_UNSATISFIED";
194
+ error.sandboxReason = "python_interpreter_probe_requires_sandbox";
195
+ error.sandboxFailClosed = true;
196
+ error.requiredBoundaries = requiredBoundaries;
197
+ error.actualGuarantees = [];
198
+ error.missingBoundaries = [...requiredBoundaries];
199
+ error.sandboxBackend = null;
200
+ error.sandboxCandidateBackend = null;
201
+ return error;
202
+ }
203
+
152
204
  // ─── Background shell tasks ────────────────────────────────────────────────
153
205
  //
154
206
  // run_shell is synchronous (execSync) and capped at a foreground timeout, which
@@ -837,6 +889,10 @@ export function getCachedPython() {
837
889
  return _cachedPython;
838
890
  }
839
891
 
892
+ export function _resetCachedPythonForTests() {
893
+ _cachedPython = null;
894
+ }
895
+
840
896
  /**
841
897
  * Gather environment info (cached once per process).
842
898
  * @returns {{ os: string, arch: string, python: string|null, pip: boolean, node: string|null, git: boolean }}
@@ -1172,6 +1228,89 @@ const GUARDED_FILE_MUTATION_TOOLS = new Set([
1172
1228
  "notebook_edit",
1173
1229
  ]);
1174
1230
 
1231
+ function agentFileToolPathRequests(name, args = {}, workspaceRoots = []) {
1232
+ const one = (argument, access) =>
1233
+ typeof args[argument] === "string" && args[argument].length > 0
1234
+ ? [{ argument, path: args[argument], access }]
1235
+ : [];
1236
+ switch (name) {
1237
+ case "read_file":
1238
+ return one("path", "read");
1239
+ case "write_file":
1240
+ case "edit_file":
1241
+ case "edit_file_hashed":
1242
+ case "delete_file":
1243
+ case "notebook_edit":
1244
+ return one("path", "write");
1245
+ case "move_file":
1246
+ return [...one("path", "write"), ...one("target_path", "write")];
1247
+ case "list_dir":
1248
+ return [
1249
+ {
1250
+ argument: "path",
1251
+ path:
1252
+ typeof args.path === "string" && args.path.length > 0
1253
+ ? args.path
1254
+ : ".",
1255
+ access: "read",
1256
+ },
1257
+ ];
1258
+ case "search_files":
1259
+ return typeof args.directory === "string" && args.directory.length > 0
1260
+ ? [
1261
+ {
1262
+ argument: "directory",
1263
+ path: args.directory,
1264
+ access: "read",
1265
+ },
1266
+ ]
1267
+ : workspaceRoots.map((root) => ({
1268
+ argument: "directory",
1269
+ path: root,
1270
+ access: "read",
1271
+ }));
1272
+ case "code_intelligence":
1273
+ return args.action === "workspace_symbols"
1274
+ ? workspaceRoots.map((root) => ({
1275
+ argument: "file",
1276
+ path: root,
1277
+ access: "read",
1278
+ }))
1279
+ : one("file", "read");
1280
+ case "publish_artifact":
1281
+ return one("path", "read");
1282
+ default:
1283
+ return [];
1284
+ }
1285
+ }
1286
+
1287
+ function guardAgentFileToolPaths(name, args, context, cwd) {
1288
+ const workspaceRoots = workspaceRootsFor(cwd, context.additionalDirectories);
1289
+ const requests = agentFileToolPathRequests(name, args || {}, workspaceRoots);
1290
+ for (const request of requests) {
1291
+ const verdict = resolveSandboxPolicyPath(request.path, {
1292
+ access: request.access,
1293
+ cwd,
1294
+ workspaceRoots,
1295
+ sandbox: context.sandbox || null,
1296
+ });
1297
+ if (!verdict.ok) {
1298
+ return {
1299
+ error:
1300
+ `[Workspace Path Guard] ${name}.${request.argument} ` +
1301
+ `"${request.path}" was blocked: ${verdict.error}.`,
1302
+ policy: {
1303
+ decision: "deny",
1304
+ via: "workspace-path-guard",
1305
+ reason: verdict.reason,
1306
+ access: request.access,
1307
+ },
1308
+ };
1309
+ }
1310
+ }
1311
+ return null;
1312
+ }
1313
+
1175
1314
  function fileMutationPaths(name, args = {}) {
1176
1315
  return [
1177
1316
  args.path,
@@ -1553,6 +1692,16 @@ export async function executeTool(name, args, context = {}) {
1553
1692
  }
1554
1693
  context = { ...context, toolAttribution: attribution };
1555
1694
  }
1695
+
1696
+ // Built-in file tools are confined to the declared workspace roots even
1697
+ // when no process sandbox is enabled. Resolve existing targets (and the
1698
+ // nearest existing parent for creates) through symlinks/junctions before any
1699
+ // permission hook, IDE preview, credential check, or filesystem operation
1700
+ // can touch them. Explicit --add-dir roots and sandbox allowRead/allowWrite
1701
+ // paths are the only supported expansions beyond cwd; deny paths still win.
1702
+ const workspacePathDenial = guardAgentFileToolPaths(name, args, context, cwd);
1703
+ if (workspacePathDenial) return workspacePathDenial;
1704
+
1556
1705
  const toolContext = createToolContext({
1557
1706
  toolName: runtimeDescriptor?.name || name,
1558
1707
  cwd,
@@ -2758,6 +2907,39 @@ async function filterSkillsByCwd(skills, cwd) {
2758
2907
  }
2759
2908
  }
2760
2909
 
2910
+ async function promoteBrowserScreenshot(
2911
+ filePath,
2912
+ { sessionId = null, title = "Browser screenshot" } = {},
2913
+ ) {
2914
+ if (!filePath) return { artifact: null, error: null };
2915
+ try {
2916
+ const { ArtifactStore, publicArtifactMetadata } =
2917
+ await import("../lib/artifact-store.js");
2918
+ const entry = new ArtifactStore().publish({
2919
+ filePath,
2920
+ title,
2921
+ kind: "screenshot",
2922
+ sessionId: sessionId ? String(sessionId) : null,
2923
+ });
2924
+ return { artifact: publicArtifactMetadata(entry), error: null };
2925
+ } catch (err) {
2926
+ const code =
2927
+ typeof err?.code === "string"
2928
+ ? err.code.replace(/[^A-Z0-9_-]/gi, "").slice(0, 40)
2929
+ : "";
2930
+ return {
2931
+ artifact: null,
2932
+ error: `browser screenshot artifact publication failed${code ? ` (${code})` : ""}`,
2933
+ };
2934
+ } finally {
2935
+ try {
2936
+ fs.rmSync(filePath, { force: true });
2937
+ } catch {
2938
+ /* best-effort cleanup */
2939
+ }
2940
+ }
2941
+ }
2942
+
2761
2943
  async function executeToolInner(
2762
2944
  name,
2763
2945
  args,
@@ -3337,25 +3519,117 @@ async function executeToolInner(
3337
3519
  : {}
3338
3520
  : { shell: shellInv.kind };
3339
3521
 
3340
- // PATH-based plugin binaries need explicit provenance at the Broker
3341
- // boundary; otherwise a trusted plugin executable is indistinguishable
3342
- // from an ambient system command in the process audit log.
3343
- let pluginBinProvenance = null;
3522
+ // Policy-bearing plugin bins never enter PATH. Their declared alias is
3523
+ // accepted only as one direct command: resolve it to literal argv, attest
3524
+ // the exact target, then give Broker an absolute command with shell:false.
3525
+ // Resolver failures for a matching strict alias are terminal — falling
3526
+ // back to a shell would bypass target identity and manifest requirements.
3527
+ let pluginBinInvocation = null;
3528
+ let reattestPluginBinInvocation = null;
3529
+ let pluginBinSandboxPolicy = null;
3530
+ let pluginBinSandboxExecutionContract = null;
3344
3531
  try {
3345
- const { resolvePluginBinCommand } =
3346
- await import("../lib/plugin-runtime/bin.js");
3347
- pluginBinProvenance = resolvePluginBinCommand(args.command, { cwd });
3348
- } catch {
3349
- pluginBinProvenance = null;
3532
+ const pluginBin = await import("../lib/plugin-runtime/bin.js");
3533
+ reattestPluginBinInvocation = pluginBin.reattestPluginBinInvocation;
3534
+ pluginBinSandboxPolicy = collectWorkspacePluginBinSandboxPolicy(
3535
+ pluginBin,
3536
+ cwd,
3537
+ args.cwd || cwd,
3538
+ );
3539
+ pluginBinInvocation = pluginBin.resolvePluginBinInvocation(
3540
+ args.command,
3541
+ {
3542
+ cwd,
3543
+ commandCwd: args.cwd || cwd,
3544
+ },
3545
+ );
3546
+ if (pluginBinInvocation?.sandboxPolicy) {
3547
+ pluginBinSandboxPolicy = pluginBin.pinPluginBinSandboxPolicy(
3548
+ {
3549
+ requiredBoundaries: [
3550
+ ...(pluginBinSandboxPolicy?.requiredBoundaries || []),
3551
+ ...(pluginBinInvocation.sandboxPolicy.requiredBoundaries || []),
3552
+ ],
3553
+ },
3554
+ { cwd },
3555
+ );
3556
+ if (
3557
+ process.platform === "linux" ||
3558
+ (process.platform === "win32" &&
3559
+ pluginBinInvocation.runtime === "node")
3560
+ ) {
3561
+ try {
3562
+ pluginBinSandboxExecutionContract =
3563
+ pluginBin.createPluginSandboxExecutionContract(
3564
+ pluginBinInvocation,
3565
+ );
3566
+ } catch (error) {
3567
+ if (error?.pluginBinFailClosed) throw error;
3568
+ const contractError = new Error(
3569
+ `plugin sandbox execution contract could not be created: ${error.message}`,
3570
+ );
3571
+ contractError.code = "ERR_PLUGIN_SANDBOX_CONTRACT_UNATTESTED";
3572
+ contractError.pluginBinFailClosed = true;
3573
+ throw contractError;
3574
+ }
3575
+ }
3576
+ }
3577
+ } catch (err) {
3578
+ if (err?.pluginBinFailClosed) {
3579
+ const policyVia =
3580
+ err.code === "ERR_PLUGIN_BIN_DISCOVERY_FAILED"
3581
+ ? "plugin-bin-pinned-sandbox-policy"
3582
+ : "plugin-bin-direct-invocation";
3583
+ return attachDescriptor(
3584
+ {
3585
+ error: `[Plugin bin] ${err.message}`,
3586
+ policy: {
3587
+ decision: "deny",
3588
+ via: policyVia,
3589
+ reason: err.code || "plugin_bin_resolution_failed",
3590
+ },
3591
+ shellCommandPolicy: shellPolicy,
3592
+ approval: approvalOutcome,
3593
+ },
3594
+ override || runtimeDescriptor,
3595
+ );
3596
+ }
3597
+ pluginBinInvocation = null;
3350
3598
  }
3351
- const processOrigin = pluginBinProvenance
3599
+ const processOrigin = pluginBinInvocation
3352
3600
  ? "plugin:bin"
3353
3601
  : "tool:run_shell";
3354
- const processProvenance = pluginBinProvenance
3602
+ const processProvenance = {
3603
+ ...(pluginBinInvocation
3604
+ ? {
3605
+ pluginId: pluginBinInvocation.pluginId,
3606
+ pluginVersion: pluginBinInvocation.pluginVersion,
3607
+ pluginSource: pluginBinInvocation.pluginSource,
3608
+ pluginExecutableIdentity: pluginBinInvocation.executableIdentity,
3609
+ }
3610
+ : {}),
3611
+ ...(pluginBinSandboxPolicy
3612
+ ? { sandboxPolicy: pluginBinSandboxPolicy }
3613
+ : {}),
3614
+ ...(pluginBinSandboxExecutionContract
3615
+ ? {
3616
+ sandboxExecutionContract: pluginBinSandboxExecutionContract,
3617
+ }
3618
+ : {}),
3619
+ };
3620
+ const pluginBinResult = pluginBinInvocation
3355
3621
  ? {
3356
- pluginId: pluginBinProvenance.pluginId,
3357
- pluginVersion: pluginBinProvenance.pluginVersion,
3358
- pluginSource: pluginBinProvenance.pluginSource,
3622
+ plugin_bin: {
3623
+ plugin: pluginBinInvocation.pluginId,
3624
+ version: pluginBinInvocation.pluginVersion,
3625
+ name: pluginBinInvocation.binName,
3626
+ target: pluginBinInvocation.binPath,
3627
+ runtime: pluginBinInvocation.runtime,
3628
+ sha256: pluginBinInvocation.executableIdentity.sha256,
3629
+ identity_attested: true,
3630
+ launch_identity_reattested: false,
3631
+ direct_argv: true,
3632
+ },
3359
3633
  }
3360
3634
  : {};
3361
3635
 
@@ -3370,6 +3644,23 @@ async function executeToolInner(
3370
3644
  policy: { decision: "deny", via: "sandbox" },
3371
3645
  });
3372
3646
  }
3647
+ if (
3648
+ (process.platform === "linux" || process.platform === "win32") &&
3649
+ pluginBinSandboxPolicy?.requiredBoundaries?.length > 0
3650
+ ) {
3651
+ return attachDescriptor({
3652
+ error:
3653
+ "[Plugin bin] The current strong plugin boundary supports foreground execution only; background execution is fail-closed until Broker-owned process-tree teardown is available.",
3654
+ policy: {
3655
+ decision: "deny",
3656
+ via: "plugin-bin-pinned-sandbox-policy",
3657
+ reason: "background_execution_unsupported",
3658
+ },
3659
+ ...pluginBinResult,
3660
+ shellCommandPolicy: shellPolicy,
3661
+ approval: approvalOutcome,
3662
+ });
3663
+ }
3373
3664
  // Free memory from idle background tasks before adding another, so a
3374
3665
  // long agent run can't accumulate forgotten dev servers (no-op unless
3375
3666
  // the machine is actually under memory pressure).
@@ -3425,9 +3716,29 @@ async function executeToolInner(
3425
3716
  scope: "agent",
3426
3717
  ...processProvenance,
3427
3718
  };
3428
- const child = shellInv.useDefaultShell
3429
- ? broker.spawn(args.command, [], brokerOpts)
3430
- : broker.spawn(shellInv.file, shellInv.argv, brokerOpts);
3719
+ let child;
3720
+ if (pluginBinInvocation) {
3721
+ reattestPluginBinInvocation(pluginBinInvocation);
3722
+ pluginBinResult.plugin_bin.launch_identity_reattested = true;
3723
+ child = broker.spawn(
3724
+ pluginBinSandboxExecutionContract?.kind ===
3725
+ "strict-plugin-node-bin"
3726
+ ? pluginBinSandboxExecutionContract.runtimePath
3727
+ : pluginBinInvocation.command,
3728
+ pluginBinInvocation.args,
3729
+ {
3730
+ ...brokerOpts,
3731
+ cwd:
3732
+ pluginBinSandboxExecutionContract?.workingDirectory ||
3733
+ brokerOpts.cwd,
3734
+ shell: false,
3735
+ },
3736
+ );
3737
+ } else {
3738
+ child = shellInv.useDefaultShell
3739
+ ? broker.spawn(args.command, [], brokerOpts)
3740
+ : broker.spawn(shellInv.file, shellInv.argv, brokerOpts);
3741
+ }
3431
3742
  task.child = child;
3432
3743
  if (child.stdout) {
3433
3744
  child.stdout.setEncoding("utf8");
@@ -3478,6 +3789,7 @@ async function executeToolInner(
3478
3789
  status: task.status,
3479
3790
  command: task.command,
3480
3791
  ...shellMeta,
3792
+ ...pluginBinResult,
3481
3793
  hint: "Poll output and completion with the check_shell tool using this task_id. Kill long-lived servers with check_shell { task_id, kill: true } when done.",
3482
3794
  shellCommandPolicy: shellPolicy,
3483
3795
  approval: approvalOutcome,
@@ -3487,6 +3799,23 @@ async function executeToolInner(
3487
3799
  }
3488
3800
 
3489
3801
  if (sandbox) {
3802
+ if (pluginBinSandboxPolicy) {
3803
+ return attachDescriptor(
3804
+ {
3805
+ error:
3806
+ "[Plugin bin] A strict plugin bin policy cannot be combined with the legacy ephemeral shell sandbox. Run without that sandbox so the ProcessExecutionBroker can enforce the pinned plugin boundary union.",
3807
+ policy: {
3808
+ decision: "deny",
3809
+ via: "plugin-bin-pinned-sandbox-policy",
3810
+ reason: "conflicting_sandbox_routes",
3811
+ },
3812
+ ...pluginBinResult,
3813
+ shellCommandPolicy: shellPolicy,
3814
+ approval: approvalOutcome,
3815
+ },
3816
+ override || runtimeDescriptor,
3817
+ );
3818
+ }
3490
3819
  const { executeSandboxedShell, sandboxSummary } =
3491
3820
  await import("../lib/agent-sandbox.js");
3492
3821
  // Domain-restricted networking is ENFORCED by routing the sandboxed
@@ -3607,7 +3936,35 @@ async function executeToolInner(
3607
3936
  scope: "agent",
3608
3937
  ...processProvenance,
3609
3938
  };
3610
- if (shellInv.useDefaultShell) {
3939
+ if (pluginBinInvocation) {
3940
+ reattestPluginBinInvocation(pluginBinInvocation);
3941
+ pluginBinResult.plugin_bin.launch_identity_reattested = true;
3942
+ const res = broker.spawnSync(
3943
+ pluginBinSandboxExecutionContract?.kind === "strict-plugin-node-bin"
3944
+ ? pluginBinSandboxExecutionContract.runtimePath
3945
+ : pluginBinInvocation.command,
3946
+ pluginBinInvocation.args,
3947
+ {
3948
+ ...brokerExecOpts,
3949
+ cwd:
3950
+ pluginBinSandboxExecutionContract?.workingDirectory ||
3951
+ brokerExecOpts.cwd,
3952
+ shell: false,
3953
+ windowsHide: true,
3954
+ },
3955
+ );
3956
+ if (res.error) throw res.error;
3957
+ if (res.status !== 0) {
3958
+ const e = new Error(
3959
+ `Plugin bin failed (exit ${res.status}): ${pluginBinInvocation.binName}`,
3960
+ );
3961
+ e.status = res.status;
3962
+ e.stdout = res.stdout;
3963
+ e.stderr = res.stderr;
3964
+ throw e;
3965
+ }
3966
+ output = res.stdout ?? "";
3967
+ } else if (shellInv.useDefaultShell) {
3611
3968
  output = broker.execSync(args.command, brokerExecOpts);
3612
3969
  } else {
3613
3970
  // PowerShell route: explicit argv, no intermediate default shell.
@@ -3649,6 +4006,7 @@ async function executeToolInner(
3649
4006
  {
3650
4007
  stdout: output.substring(0, 30000),
3651
4008
  ...shellMeta,
4009
+ ...pluginBinResult,
3652
4010
  shellCommandPolicy: shellPolicy,
3653
4011
  approval: approvalOutcome,
3654
4012
  },
@@ -3669,6 +4027,7 @@ async function executeToolInner(
3669
4027
  stderr: (err.stderr || "").substring(0, 2000),
3670
4028
  exitCode: err.status,
3671
4029
  ...shellMeta,
4030
+ ...pluginBinResult,
3672
4031
  shellCommandPolicy: shellPolicy,
3673
4032
  approval: approvalOutcome,
3674
4033
  },
@@ -4181,6 +4540,9 @@ async function executeToolInner(
4181
4540
  : 60000;
4182
4541
  const entry = store.createMonitor({
4183
4542
  command: args.command,
4543
+ // Bind the persisted command to the host/session workspace. This
4544
+ // value comes from executeTool's trusted context, never model args.
4545
+ workspaceCwd: path.resolve(cwd),
4184
4546
  intervalMs,
4185
4547
  stopWhen: args.stop_when || null,
4186
4548
  notify: args.notify_title ? { title: args.notify_title } : null,
@@ -4219,7 +4581,8 @@ async function executeToolInner(
4219
4581
  // Only METADATA returns to the conversation (the transcript never
4220
4582
  // carries the file body); `cc artifacts` lists/inspects/cleans.
4221
4583
  try {
4222
- const { ArtifactStore } = await import("../lib/artifact-store.js");
4584
+ const { ArtifactStore, publicArtifactMetadata } =
4585
+ await import("../lib/artifact-store.js");
4223
4586
  const store = new ArtifactStore();
4224
4587
  const entry = store.publish({
4225
4588
  filePath: path.resolve(cwd, String(args.path || "")),
@@ -4229,7 +4592,7 @@ async function executeToolInner(
4229
4592
  sessionId: sessionId ? String(sessionId) : null,
4230
4593
  });
4231
4594
  return attachDescriptor({
4232
- published: entry,
4595
+ published: publicArtifactMetadata(entry),
4233
4596
  hint: "The user can browse this with `cc artifacts list` / `cc artifacts show <id>`.",
4234
4597
  });
4235
4598
  } catch (err) {
@@ -4265,7 +4628,25 @@ async function executeToolInner(
4265
4628
  error: `browser_state failed: ${state.error}`,
4266
4629
  });
4267
4630
  }
4268
- return attachDescriptor(state);
4631
+ const safeState = { ...state };
4632
+ if (state.screenshotPath) {
4633
+ const promoted = await promoteBrowserScreenshot(
4634
+ state.screenshotPath,
4635
+ {
4636
+ sessionId,
4637
+ title: `Browser state — ${state.title || "page"}`,
4638
+ },
4639
+ );
4640
+ delete safeState.screenshotPath;
4641
+ delete safeState.screenshotRef;
4642
+ if (promoted.artifact) {
4643
+ safeState.screenshotArtifact = promoted.artifact;
4644
+ safeState.screenshotRef = promoted.artifact.id;
4645
+ } else {
4646
+ safeState.screenshotArtifactError = promoted.error;
4647
+ }
4648
+ }
4649
+ return attachDescriptor(safeState);
4269
4650
  } catch (err) {
4270
4651
  return attachDescriptor({
4271
4652
  error: `browser_state failed: ${err.message}`,
@@ -4336,7 +4717,32 @@ async function executeToolInner(
4336
4717
  error: `browser_act failed: ${result.error}`,
4337
4718
  });
4338
4719
  }
4339
- return attachDescriptor(result);
4720
+ const safeResult = { ...result, steps: [] };
4721
+ for (let i = 0; i < (result.steps || []).length; i += 1) {
4722
+ const originalStep = result.steps[i];
4723
+ const step = { ...originalStep };
4724
+ if (originalStep.screenshotPath) {
4725
+ const promoted = await promoteBrowserScreenshot(
4726
+ originalStep.screenshotPath,
4727
+ {
4728
+ sessionId,
4729
+ title: `Browser action screenshot ${i + 1}`,
4730
+ },
4731
+ );
4732
+ delete step.screenshotPath;
4733
+ delete step.screenshotRef;
4734
+ if (promoted.artifact) {
4735
+ step.screenshotArtifact = promoted.artifact;
4736
+ step.screenshotRef = promoted.artifact.id;
4737
+ step.detail = `screenshot artifact: ${promoted.artifact.id}`;
4738
+ } else {
4739
+ step.screenshotArtifactError = promoted.error;
4740
+ step.detail = promoted.error;
4741
+ }
4742
+ }
4743
+ safeResult.steps.push(step);
4744
+ }
4745
+ return attachDescriptor(safeResult);
4340
4746
  } catch (err) {
4341
4747
  return attachDescriptor({
4342
4748
  error: `browser_act failed: ${err.message}`,
@@ -4982,6 +5388,54 @@ async function _executeRunCode(args, cwd) {
4982
5388
  };
4983
5389
  }
4984
5390
 
5391
+ // Every agent-generated interpreter process inherits the tighten-only union
5392
+ // of strict Plugin bin boundaries visible from this workspace. Discover and
5393
+ // pin it before creating a persistent directory, writing a temporary script,
5394
+ // or probing an interpreter so discovery failure cannot fall through to an
5395
+ // unbounded native spawn.
5396
+ let pluginBinSandboxPolicy = null;
5397
+ try {
5398
+ const pluginBin = await import("../lib/plugin-runtime/bin.js");
5399
+ pluginBinSandboxPolicy = collectWorkspacePluginBinSandboxPolicy(
5400
+ pluginBin,
5401
+ cwd,
5402
+ );
5403
+ } catch (error) {
5404
+ return {
5405
+ error: `[Plugin bin] ${error.message}`,
5406
+ policy: {
5407
+ decision: "deny",
5408
+ via: "plugin-bin-pinned-sandbox-policy",
5409
+ reason: error.code || "ERR_PLUGIN_BIN_SANDBOX_POLICY_DISCOVERY_FAILED",
5410
+ },
5411
+ };
5412
+ }
5413
+ const pluginBinSandboxOptions = pluginBinSandboxPolicy
5414
+ ? { sandboxPolicy: pluginBinSandboxPolicy }
5415
+ : {};
5416
+
5417
+ // cli-anything's Python discovery probes candidate executables through its
5418
+ // own Broker call. That probe cannot inherit this run_code invocation's
5419
+ // pinned Plugin sandbox policy, so never launch it when strict boundaries
5420
+ // are required. A previously cached interpreter is safe to execute here
5421
+ // because the real script (and any pip/retry process) still goes through
5422
+ // runCodeProcess with the exact pinned policy below.
5423
+ let cachedPythonInterpreter = null;
5424
+ if (
5425
+ lang === "python" &&
5426
+ pluginBinSandboxPolicy?.requiredBoundaries?.length > 0
5427
+ ) {
5428
+ if (
5429
+ _cachedPython?.found === true &&
5430
+ typeof _cachedPython.command === "string" &&
5431
+ _cachedPython.command.trim()
5432
+ ) {
5433
+ cachedPythonInterpreter = _cachedPython.command;
5434
+ } else {
5435
+ throw createPythonInterpreterProbeSandboxFailure(pluginBinSandboxPolicy);
5436
+ }
5437
+ }
5438
+
4985
5439
  // Determine script path
4986
5440
  let scriptPath;
4987
5441
  if (persist) {
@@ -5004,8 +5458,12 @@ async function _executeRunCode(args, cwd) {
5004
5458
  // Determine interpreter
5005
5459
  let interpreter;
5006
5460
  if (lang === "python") {
5007
- const py = getCachedPython();
5008
- interpreter = py.found ? py.command : "python";
5461
+ if (cachedPythonInterpreter) {
5462
+ interpreter = cachedPythonInterpreter;
5463
+ } else {
5464
+ const py = getCachedPython();
5465
+ interpreter = py.found ? py.command : "python";
5466
+ }
5009
5467
  } else if (lang === "node") {
5010
5468
  interpreter = "node";
5011
5469
  } else {
@@ -5020,8 +5478,13 @@ async function _executeRunCode(args, cwd) {
5020
5478
  encoding: "utf8",
5021
5479
  timeout: timeoutSec * 1000,
5022
5480
  maxBuffer: 5 * 1024 * 1024,
5481
+ ...pluginBinSandboxOptions,
5023
5482
  });
5024
5483
  } catch (err) {
5484
+ // Broker boundary errors carry the required/actual/missing guarantees and
5485
+ // backend attestation. Preserve that exact structured error for callers
5486
+ // instead of flattening it into run_code's generic runtime classifier.
5487
+ rethrowRunCodeSandboxFailure(err);
5025
5488
  const stderr = (err.stderr || "").toString();
5026
5489
  const message = err.message || "";
5027
5490
  const classified = classifyError(stderr, message, err.status, lang);
@@ -5113,6 +5576,36 @@ async function _executeRunCode(args, cwd) {
5113
5576
  /* unified audit must never affect the install itself */
5114
5577
  }
5115
5578
 
5579
+ const autoInstallFailure = (installError) => {
5580
+ recordInstallAudit({
5581
+ package: packageName,
5582
+ interpreter,
5583
+ cwd,
5584
+ outcome: "failed",
5585
+ detail: (
5586
+ installError.stderr ||
5587
+ installError.message ||
5588
+ ""
5589
+ ).substring(0, 200),
5590
+ });
5591
+ auditUnifiedInstall(
5592
+ "failed",
5593
+ (installError.stderr || installError.message || "").substring(
5594
+ 0,
5595
+ 200,
5596
+ ),
5597
+ );
5598
+ return {
5599
+ error: (stderr || message).substring(0, 5000),
5600
+ stderr: stderr.substring(0, 5000),
5601
+ exitCode: err.status,
5602
+ language: lang,
5603
+ ...classified,
5604
+ hint: `Failed to auto-install "${packageName}". ${(installError.stderr || installError.message || "").substring(0, 500)}`,
5605
+ scriptPath: persist ? scriptPath : undefined,
5606
+ };
5607
+ };
5608
+
5116
5609
  // Attempt pip install
5117
5610
  try {
5118
5611
  runCodeProcess(
@@ -5124,18 +5617,27 @@ async function _executeRunCode(args, cwd) {
5124
5617
  timeout: 120000,
5125
5618
  maxBuffer: 2 * 1024 * 1024,
5126
5619
  stdio: ["pipe", "pipe", "pipe"],
5620
+ ...pluginBinSandboxOptions,
5127
5621
  },
5128
5622
  "run-code-install",
5129
5623
  );
5130
- recordInstallAudit({
5131
- package: packageName,
5132
- interpreter,
5133
- cwd,
5134
- outcome: "installed",
5135
- });
5136
- auditUnifiedInstall("installed");
5624
+ } catch (pipErr) {
5625
+ const failure = autoInstallFailure(pipErr);
5626
+ rethrowRunCodeSandboxFailure(pipErr);
5627
+ return failure;
5628
+ }
5629
+ recordInstallAudit({
5630
+ package: packageName,
5631
+ interpreter,
5632
+ cwd,
5633
+ outcome: "installed",
5634
+ });
5635
+ auditUnifiedInstall("installed");
5137
5636
 
5138
- // Retry execution
5637
+ // Keep retry separate from the install catch: either Broker call can
5638
+ // carry an independently attested fail-closed boundary error, and
5639
+ // neither may be flattened into the legacy auto-install result.
5640
+ try {
5139
5641
  const retryStart = Date.now();
5140
5642
  const retryOutput = runCodeProcess(
5141
5643
  interpreter,
@@ -5145,6 +5647,7 @@ async function _executeRunCode(args, cwd) {
5145
5647
  encoding: "utf8",
5146
5648
  timeout: timeoutSec * 1000,
5147
5649
  maxBuffer: 5 * 1024 * 1024,
5650
+ ...pluginBinSandboxOptions,
5148
5651
  },
5149
5652
  "run-code-retry",
5150
5653
  );
@@ -5158,25 +5661,24 @@ async function _executeRunCode(args, cwd) {
5158
5661
  autoInstalled: [packageName],
5159
5662
  scriptPath: persist ? scriptPath : undefined,
5160
5663
  };
5161
- } catch (pipErr) {
5162
- recordInstallAudit({
5163
- package: packageName,
5164
- interpreter,
5165
- cwd,
5166
- outcome: "failed",
5167
- detail: (pipErr.stderr || pipErr.message || "").substring(0, 200),
5168
- });
5169
- auditUnifiedInstall(
5170
- "failed",
5171
- (pipErr.stderr || pipErr.message || "").substring(0, 200),
5664
+ } catch (retryErr) {
5665
+ rethrowRunCodeSandboxFailure(retryErr);
5666
+ const retryStderr = (retryErr.stderr || "").toString();
5667
+ const retryMessage = retryErr.message || "";
5668
+ const retryClassified = classifyError(
5669
+ retryStderr,
5670
+ retryMessage,
5671
+ retryErr.status,
5672
+ lang,
5172
5673
  );
5173
5674
  return {
5174
- error: (stderr || message).substring(0, 5000),
5175
- stderr: stderr.substring(0, 5000),
5176
- exitCode: err.status,
5675
+ error: (retryStderr || retryMessage).substring(0, 5000),
5676
+ stderr: retryStderr.substring(0, 5000),
5677
+ exitCode: retryErr.status,
5177
5678
  language: lang,
5178
- ...classified,
5179
- hint: `Failed to auto-install "${packageName}". ${(pipErr.stderr || pipErr.message || "").substring(0, 500)}`,
5679
+ ...retryClassified,
5680
+ hint: `Package "${packageName}" was installed, but the script retry failed.`,
5681
+ autoInstalled: [packageName],
5180
5682
  scriptPath: persist ? scriptPath : undefined,
5181
5683
  };
5182
5684
  }
@@ -5297,7 +5799,7 @@ export function safeStringifyToolResult(value) {
5297
5799
  }
5298
5800
  }
5299
5801
 
5300
- function emitToolHookLifecycle({
5802
+ export function emitToolHookLifecycle({
5301
5803
  tool,
5302
5804
  args,
5303
5805
  result,
@@ -5306,6 +5808,7 @@ function emitToolHookLifecycle({
5306
5808
  turnId = null,
5307
5809
  toolUseId = null,
5308
5810
  cwd = process.cwd(),
5811
+ emit = emitHooksV2Event,
5309
5812
  }) {
5310
5813
  const failed = Boolean(error || result?.error);
5311
5814
  const base = {
@@ -5317,7 +5820,7 @@ function emitToolHookLifecycle({
5317
5820
  duration_ms: Number(result?.toolTelemetryRecord?.durationMs || 0),
5318
5821
  cwd,
5319
5822
  };
5320
- emitHooksV2Event(failed ? "PostToolUseFailure" : "PostToolUse", {
5823
+ emit(failed ? "PostToolUseFailure" : "PostToolUse", {
5321
5824
  ...base,
5322
5825
  ...(failed
5323
5826
  ? {
@@ -5337,7 +5840,7 @@ function emitToolHookLifecycle({
5337
5840
  args?.targetPath ||
5338
5841
  args?.target_path ||
5339
5842
  null;
5340
- emitHooksV2Event("FileChanged", {
5843
+ emit("FileChanged", {
5341
5844
  ...base,
5342
5845
  path: changedPath == null ? null : String(changedPath),
5343
5846
  operation: tool,
@@ -5346,6 +5849,32 @@ function emitToolHookLifecycle({
5346
5849
  return failed;
5347
5850
  }
5348
5851
 
5852
+ export function emitToolBatchHookLifecycle({
5853
+ records = [],
5854
+ sessionId = null,
5855
+ turnId = null,
5856
+ cwd = process.cwd(),
5857
+ parallel = false,
5858
+ emit = emitHooksV2Event,
5859
+ }) {
5860
+ if (!Array.isArray(records) || records.length === 0) return null;
5861
+ const failed = records.filter((record) => record?.failed === true).length;
5862
+ const payload = {
5863
+ schema_version: 1,
5864
+ session_id: sessionId,
5865
+ turn_id: turnId,
5866
+ cwd,
5867
+ parallel: parallel === true,
5868
+ total: records.length,
5869
+ succeeded: records.length - failed,
5870
+ failed,
5871
+ tool_names: records.map((record) => record?.tool || "(unknown)"),
5872
+ tool_use_ids: records.map((record) => record?.toolUseId || null),
5873
+ };
5874
+ emit("PostToolBatch", payload);
5875
+ return payload;
5876
+ }
5877
+
5349
5878
  /**
5350
5879
  * Execute a spawn_sub_agent tool call.
5351
5880
  * Creates an isolated SubAgentContext, runs it, and returns only the summary.
@@ -7507,11 +8036,12 @@ function extractInitialPromptText(messages) {
7507
8036
  * recompute it. Not exported (no shim-parity impact) — only the telemetry seam
7508
8037
  * uses it.
7509
8038
  */
7510
- function permissionDecisionId(callId, policy) {
7511
- if (!policy || typeof policy !== "object" || !policy.decision) return null;
7512
- const gate = policy.via || policy.decision;
7513
- const base = callId || "call";
7514
- return `${base}:perm:${gate}`;
8039
+ function permissionDecision(callId, tool, result) {
8040
+ return buildPermissionDecision({
8041
+ toolUseId: callId,
8042
+ tool,
8043
+ result,
8044
+ });
7515
8045
  }
7516
8046
 
7517
8047
  export async function* agentLoop(messages, options) {
@@ -7931,6 +8461,14 @@ export async function* agentLoop(messages, options) {
7931
8461
  // self-persist is best-effort
7932
8462
  }
7933
8463
  }
8464
+ emitHooksV2Event("PostCompact", {
8465
+ schema_version: 1,
8466
+ trigger: "auto",
8467
+ session_id: options.sessionId || null,
8468
+ messages_after: messages.length,
8469
+ stats,
8470
+ cwd: options.cwd || process.cwd(),
8471
+ });
7934
8472
  yield { type: "compaction", stats, runId };
7935
8473
  }
7936
8474
  }
@@ -8182,7 +8720,33 @@ export async function* agentLoop(messages, options) {
8182
8720
  },
8183
8721
  { cwd: options.cwd || process.cwd() },
8184
8722
  );
8185
- } catch (_err) {
8723
+ const failures = (stopOutcome?.results || []).filter(
8724
+ (result) =>
8725
+ result?.nonBlockingError === true ||
8726
+ result?.malformedDecision === true ||
8727
+ result?.breakerOpen === true,
8728
+ );
8729
+ if (failures.length > 0) {
8730
+ emitHooksV2Event("StopFailure", {
8731
+ schema_version: 1,
8732
+ session_id: options.sessionId || null,
8733
+ run_id: runId,
8734
+ phase: "stop-hook",
8735
+ failures: failures.map((failure) => ({
8736
+ command: failure.command || null,
8737
+ exit_code: failure.exitCode ?? null,
8738
+ reason: failure.reason || failure.error || null,
8739
+ })),
8740
+ });
8741
+ }
8742
+ } catch (error) {
8743
+ emitHooksV2Event("StopFailure", {
8744
+ schema_version: 1,
8745
+ session_id: options.sessionId || null,
8746
+ run_id: runId,
8747
+ phase: "stop-hook",
8748
+ reason: error?.message || String(error),
8749
+ });
8186
8750
  stopOutcome = null; // never affect the run outcome
8187
8751
  }
8188
8752
  if (stopOutcome && stopOutcome.decision === "block") {
@@ -8243,6 +8807,8 @@ export async function* agentLoop(messages, options) {
8243
8807
  );
8244
8808
  if (parallelReads) {
8245
8809
  throwIfAborted(signal);
8810
+ const turnId = `${runId}:t${budget.consumed}`;
8811
+ const toolBatchRecords = [];
8246
8812
  // Kick every read off now; settle into {result,error} so an unawaited
8247
8813
  // promise can never surface as an unhandled rejection if the loop aborts
8248
8814
  // before it is consumed.
@@ -8277,6 +8843,21 @@ export async function* agentLoop(messages, options) {
8277
8843
  };
8278
8844
  const { result: toolResult, error: toolError } = await promise;
8279
8845
  throwIfAborted(signal);
8846
+ const failed = emitToolHookLifecycle({
8847
+ tool: call.function.name,
8848
+ args: toolArgs,
8849
+ result: toolResult,
8850
+ error: toolError,
8851
+ sessionId: options.sessionId || null,
8852
+ turnId,
8853
+ toolUseId: call.id,
8854
+ cwd: options.cwd || process.cwd(),
8855
+ });
8856
+ toolBatchRecords.push({
8857
+ tool: call.function.name,
8858
+ toolUseId: call.id,
8859
+ failed,
8860
+ });
8280
8861
  const warningMsg = budget.toWarningMessage();
8281
8862
  const resultStr = capToolResultString(
8282
8863
  safeStringifyToolResult(toolResult),
@@ -8284,6 +8865,11 @@ export async function* agentLoop(messages, options) {
8284
8865
  const toolContent = warningMsg
8285
8866
  ? `${resultStr}\n\n${warningMsg}`
8286
8867
  : resultStr;
8868
+ const decision = permissionDecision(
8869
+ call.id,
8870
+ call.function.name,
8871
+ toolResult,
8872
+ );
8287
8873
  yield {
8288
8874
  type: "tool-result",
8289
8875
  tool: call.function.name,
@@ -8291,10 +8877,8 @@ export async function* agentLoop(messages, options) {
8291
8877
  error: toolError,
8292
8878
  tool_use_id: call.id,
8293
8879
  turn_id: `${runId}:t${budget.consumed}`,
8294
- permission_decision_id: permissionDecisionId(
8295
- call.id,
8296
- toolResult?.policy,
8297
- ),
8880
+ permission_decision_id: decision?.id || null,
8881
+ permission_decision: decision,
8298
8882
  };
8299
8883
  messages.push({
8300
8884
  role: "tool",
@@ -8302,9 +8886,18 @@ export async function* agentLoop(messages, options) {
8302
8886
  tool_call_id: call.id,
8303
8887
  });
8304
8888
  }
8889
+ emitToolBatchHookLifecycle({
8890
+ records: toolBatchRecords,
8891
+ sessionId: options.sessionId || null,
8892
+ turnId,
8893
+ cwd: options.cwd || process.cwd(),
8894
+ parallel: true,
8895
+ });
8305
8896
  continue; // all read results in place — back to the LLM call
8306
8897
  }
8307
8898
 
8899
+ const turnId = `${runId}:t${budget.consumed}`;
8900
+ const toolBatchRecords = [];
8308
8901
  for (const call of toolCalls) {
8309
8902
  throwIfAborted(signal);
8310
8903
  const fn = call?.function;
@@ -8318,6 +8911,21 @@ export async function* agentLoop(messages, options) {
8318
8911
  // malformed call.
8319
8912
  if (typeof toolName !== "string" || !toolName) {
8320
8913
  const reason = "malformed tool call: missing function name";
8914
+ emitToolHookLifecycle({
8915
+ tool: "(unknown)",
8916
+ args: null,
8917
+ result: { error: reason },
8918
+ error: reason,
8919
+ sessionId: options.sessionId || null,
8920
+ turnId,
8921
+ toolUseId: call?.id || null,
8922
+ cwd: options.cwd || process.cwd(),
8923
+ });
8924
+ toolBatchRecords.push({
8925
+ tool: "(unknown)",
8926
+ toolUseId: call?.id || null,
8927
+ failed: true,
8928
+ });
8321
8929
  yield {
8322
8930
  type: "tool-result",
8323
8931
  tool: "(unknown)",
@@ -8413,14 +9021,14 @@ export async function* agentLoop(messages, options) {
8413
9021
  // (derived from the call + gate, so it's recomputable from the
8414
9022
  // tool-result's policy + tool_call_id) plus the low-cardinality
8415
9023
  // decision, letting a blocked tool span be tied to its decision.
8416
- const decId =
9024
+ const decision =
8417
9025
  r && typeof r === "object"
8418
- ? permissionDecisionId(call.id, r.policy)
9026
+ ? permissionDecision(call.id, toolName, r)
8419
9027
  : null;
8420
- if (decId) {
9028
+ if (decision?.id) {
8421
9029
  const permAttrs = buildTelemetryAttributes({
8422
- decisionId: decId,
8423
- "permission.decision": r.policy.decision,
9030
+ decisionId: decision.id,
9031
+ "permission.decision": decision.decision,
8424
9032
  });
8425
9033
  if (permAttrs["permission.decision_id"]) {
8426
9034
  span.setAttribute(
@@ -8458,6 +9066,7 @@ export async function* agentLoop(messages, options) {
8458
9066
  ? `${resultStr}\n\n${warningMsg}`
8459
9067
  : resultStr;
8460
9068
 
9069
+ const decision = permissionDecision(call.id, toolName, toolResult);
8461
9070
  yield {
8462
9071
  type: "tool-result",
8463
9072
  tool: toolName,
@@ -8465,10 +9074,8 @@ export async function* agentLoop(messages, options) {
8465
9074
  error: toolError,
8466
9075
  tool_use_id: call.id,
8467
9076
  turn_id: `${runId}:t${budget.consumed}`,
8468
- permission_decision_id: permissionDecisionId(
8469
- call.id,
8470
- toolResult?.policy,
8471
- ),
9077
+ permission_decision_id: decision?.id || null,
9078
+ permission_decision: decision,
8472
9079
  };
8473
9080
 
8474
9081
  messages.push({
@@ -8476,7 +9083,29 @@ export async function* agentLoop(messages, options) {
8476
9083
  content: toolContent,
8477
9084
  tool_call_id: call.id,
8478
9085
  });
9086
+ const failed = emitToolHookLifecycle({
9087
+ tool: toolName,
9088
+ args: toolArgs,
9089
+ result: toolResult,
9090
+ error: toolError,
9091
+ sessionId: options.sessionId || null,
9092
+ turnId,
9093
+ toolUseId: call.id,
9094
+ cwd: options.cwd || process.cwd(),
9095
+ });
9096
+ toolBatchRecords.push({
9097
+ tool: toolName,
9098
+ toolUseId: call.id,
9099
+ failed,
9100
+ });
8479
9101
  }
9102
+ emitToolBatchHookLifecycle({
9103
+ records: toolBatchRecords,
9104
+ sessionId: options.sessionId || null,
9105
+ turnId,
9106
+ cwd: options.cwd || process.cwd(),
9107
+ parallel: false,
9108
+ });
8480
9109
  }
8481
9110
 
8482
9111
  // Budget exhausted — flush any child usage the final iteration produced,