chainlesschain 0.165.2 → 0.165.4

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 (210) hide show
  1. package/README.md +151 -29
  2. package/package.json +2 -2
  3. package/src/assets/web-panel/assets/{AIOps-lS1R0Cza.js → AIOps-C__nnraW.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-BmlgBPsj.js → ActionButton-CsKWHEZ-.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-CNKczv1S.js → Analytics-BhIjfjWO.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-BuZGJARQ.js → AppLayout-BjuXFzWZ.js} +4 -4
  7. package/src/assets/web-panel/assets/{Artifacts-Dm73Sl-O.js → Artifacts-k6whDkZJ.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-BGMW7A5e.js → Audit-C4FBMYRh.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-BQ6M54JR.js → BackgroundAgents-C_vRrSXk.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-DBzGP0bR.js → Backup-CngFZeVp.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-D1mLCoOV.js → BaseInput-B_H9EBRm.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-BUfcN7p8.js → Chat-DLEC5kt7.js} +4 -4
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer-xijcWkJe.js +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-Du0Yg5Pi.js → Checkbox-m3ClR4xh.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-B2RXziHW.js → Codegen-CmLSFrgp.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-DWhekOrR.js → Col-DFGQteHN.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-HQhrkon4.js → Community-EUUVORHE.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-izHNb4uz.js → Compact-2PSg0Yr7.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-BDwLy41-.js → Compliance-CcKur7gB.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-DFJhLa7K.js → Cowork-DK_Fezfa.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-DYY9RG7-.js → Cron-Dbw89xl7.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-rr7tJTtv.js → Crosschain-CH66h_Ec.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-D2sfHRLi.js → DID-D1igRb17.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-q_eGdhha.js → Dashboard-DVAsrobk.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-Cufrh2Bz.js → Dropdown-Dl7W8AQs.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-B-kdv10f.js → EmailListRenderer-D5tpaEQC.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-D21XjgfG.js → FamilyGuardDashboard-O_VQf8gq.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-CAwRTLZH.js → Federation-BTVcgS9n.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-vx3daNDF.js → FormItemContext-BRnkJAIz.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-CRWY2wU4.js → GenericCardRenderer-8OWSCkDj.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-Q6SwauyD.js → Git-WXLYTQj2.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-BJ1q4IQZ.js → Governance-BezaUhtc.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-CSWjROgD.js → Inference-CuRay3Gu.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-fzuQsWuY.js → KnowledgeGraph-DfWLDW1m.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-DVF8Mnns.js → Logs-C55y2pPY.js} +2 -2
  36. package/src/assets/web-panel/assets/MarkdownRenderer-hDpWdiBc.js +1 -0
  37. package/src/assets/web-panel/assets/{Marketplace-BQP7LOA4.js → Marketplace-Dk08Uycg.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-BNlJCmNc.js → McpTools-C11fSB5V.js} +4 -4
  39. package/src/assets/web-panel/assets/{Memory-DLLg5WQ4.js → Memory-mr0eZc97.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-CR2skNAF.js → MobileBridge-C0iCSRbb.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-BxVavObl.js → MobileProjects-CY-xMG8Z.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-B17pgqMI.js → Mtc-Bplqc_Wc.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-4UmV5cIL.js → MtcAudit-BlzwsoSM.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-DoMEXEcU.js → Multisig-BBLmTty6.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-h9DMXFX4.js → NLProgramming-DtT1uf1W.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-Cihn-xX8.js → Notes-DcqQAxTK.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-BE6ZGa3R.js → NotificationSettings-cb0kWoZe.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-CnwojoSI.js → OrderTableRenderer-kw9HZQNN.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-B2vpwmjb.js → Organization-DlTlbkUN.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-C4z28R0-.js → Overflow-pp1qs-oY.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-BVlFkwhW.js → P2P-BESLnyYz.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-BXbEdL5B.js → PdhVaultBrowser-B-J1mGuf.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-C28xs4lp.js → Permissions-B36PeT1_.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-Dgl_rBjM.js → PersonalDataHub-CTK2ad-3.js} +3 -3
  55. package/src/assets/web-panel/assets/{Pipeline-3QUIhrKI.js → Pipeline-zJNGXsac.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-8Gh33OZ5.js → Privacy-b9fz0Qf0.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-BNmg4tZi.js → ProjectInit-DjZbZgiB.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-vgMfeCf9.js → ProjectSettings-C-hHJXlD.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-BC9KSDM6.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-CXoOa8eX.js → Providers-Dz-CAYYU.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-BhfTF0oS.js → QrScannerModal-A0yoQ3-3.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-C746HEAj.js → QuickAsk-DrFgFJNC.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-BSej8Eo0.js → Recommend-KC8hF1g4.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-f3gmnlG3.js → RemoteSession-DGTPIk6u.js} +3 -3
  65. package/src/assets/web-panel/assets/{Reputation-CyvybpkN.js → Reputation-QmbwM7Yk.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-DwV2PeYn.js → Row-BxjJylia.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-11GrzjZy.js → RssFeed-B7Wda1J6.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-BjKXkrAY.js → Search-_plQ_YSA.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-eWiFb9Yk.js → Security-CD2YJZfx.js} +2 -2
  70. package/src/assets/web-panel/assets/{Services-CdCsMAtt.js → Services-qQE7NmwE.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-4Q2UTKQ5.js → Skeleton-DgQqkFf7.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-v4rtYrRj.js → Skills-C4C0oFFt.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-CRhsFvd_.js → Sla-Px2N2B5c.js} +1 -1
  74. package/src/assets/web-panel/assets/SpeechSettings-DN6C3lQd.js +1 -0
  75. package/src/assets/web-panel/assets/{SyncSettings-D16zonl7.js → SyncSettings-jm5aH_m7.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-Xxozz0i_.js → Tasks-DlkJcv67.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-CXJpPtCT.js → Templates-DyrTHUBs.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-XRV0L1ZF.js → Tenant-mC434PtL.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-CYuO1i_w.js → Terminal-o0_s3NgK.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-BXqitWsu.js → TimelineRenderer-DAGlAXOw.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-BvPrDHIy.js → Tokens-BnulS_bs.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-DhCrSTf5.js → Trigger-suQbVXYM.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-DmsuZD6K.js → Trust-DbIJAPo5.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-D_yrts5C.js → UkeySign-CQNpbIHi.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-B01ihPrm.js → VideoEditing-D8aWCERk.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-DpUjKa-N.js → Wallet-CuLpYfZc.js} +2 -2
  87. package/src/assets/web-panel/assets/{WebAuthn-CL07yON6.js → WebAuthn-CsfG8ZMc.js} +2 -2
  88. package/src/assets/web-panel/assets/{WorkflowEditor-CLMflnRr.js → WorkflowEditor-BYsEAZbd.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-DtUqe8h_.js → chat-FT4Um3-I.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-DPKrgVfy.js → colors-CKbS_Wlt.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-BTt2GtOm.js → compact-item-CFJtzuJM.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-D_VeUc1J.js → createContext-B9kSRW6K.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-DJZ0gAMu.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-1kndiylj.js → hasIn-DR-x0Xpi.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-dVc-CXY4.js → index-7jHr_A_k.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DFIZ_1Sf.js → index-B3ZX81mK.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-fk2Rjacw.js → index-B7osrXqy.js} +3 -3
  98. package/src/assets/web-panel/assets/{index-CHG-KcS4.js → index-BGbPnC5x.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-Dbbymxpj.js → index-BI5hP_qy.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BaZAbw9A.js → index-BK3TiKVq.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CzjkKUmR.js → index-BVPU43b1.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BAXfRKfG.js → index-BW-t9n-q.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-8nz_fmlj.js → index-BfFCQHL3.js} +1 -1
  104. package/src/assets/web-panel/assets/index-Bizb4hkw.js +1 -0
  105. package/src/assets/web-panel/assets/{index-DdkplL9R.js → index-Bt4fS2eL.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DG2MZQ3f.js → index-BwR7yc2h.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DfKBRdSS.js → index-Bzz0C1BZ.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-GsoUQ2M7.js → index-C2JvmYRc.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-l1BETrDb.js → index-CGaXUsIs.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-4bna2_SR.js → index-CJmIQ9XR.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DfGl8CqG.js → index-C_enk9I_.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-D95OFht7.js → index-Cr2FLUsD.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DsJCDUJG.js → index-CvrCCqJt.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-sDoMHTNA.js → index-D4LfPvWA.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-By5n8up9.js → index-DMGmg497.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-ocf06EVQ.js → index-DShSkww-.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Bhudc_Ez.js → index-DTxNy0ZU.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CX81qSM4.js → index-DW4BWjTk.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BlDNVgBt.js → index-DWdqx7zI.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CcO04eDn.js → index-Dap9c3eD.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Dwqmtk65.js → index-DcfxhKXR.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-2eyAXE3d.js → index-DdkIhfi-.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-brCInrzq.js → index-DeSgw-WV.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DJ8vXGOa.js → index-DiNUEpSQ.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BHpBMWCh.js → index-DjEP7WkH.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-m31Ka8hZ.js → index-DtkFqPVA.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CtF7EN49.js → index-KqDaCApw.js} +1 -1
  128. package/src/assets/web-panel/assets/index-OHSl-xCh.js +1 -0
  129. package/src/assets/web-panel/assets/{index-B9KvcS7R.js → index-OSzjTzGM.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CauoBQkH.js → index-QBSeNVO3.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-FzdwcSy2.js → index-WE08gMJP.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DxdTdBix.js → index-r2VI1Ikk.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-BepLZwgB.js → index-tqSEHPRT.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-CvyYJd7z.js → initDefaultProps-Dd7n7VgI.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-DYZkhUdz.js → motion-BwxouRw1.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-slnJRVT1.js → move-DObN0PG9.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-DVc0CKmy.js → mtc-parser-B3nOUQc7.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-BCXAtHCq.js → omit-o9WU2Nbl.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-DBsemz_K.js → pickAttrs-CL_vvfQ_.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-BRtkJ-nv.js → placementArrow-CYrM2tyK.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-Cebq7EZn.js → responsiveObserve-k_06cQ96.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-DT7qvId2.js → slide-CPKG0pxS.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-pQiITLkG.js → statusUtils-BDDEeLTw.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-c-19BVKQ.js → styleChecker-Cmb7RTh7.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-DsS5-0bE.js → useFlexGapSupport-e2IedC4N.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-CxGiMe5n.js → useFs-CE_gxVnm.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-D9btVID5.js → usePersonalDataHub-BkMIIKMz.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-CUYFsFEN.js → vnode-DW886XV1.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-CXmn_35j.js → zoom-Dwo_adaf.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/command-help-index.json +3 -3
  152. package/src/commands/agent.js +105 -35
  153. package/src/commands/artifacts.js +363 -33
  154. package/src/commands/cowork.js +202 -36
  155. package/src/commands/session-budget.js +155 -0
  156. package/src/commands/session-location.js +948 -0
  157. package/src/commands/session.js +72 -46
  158. package/src/data/changelog.json +26 -0
  159. package/src/gateways/ws/artifact-protocol.js +77 -13
  160. package/src/gateways/ws/session-protocol.js +391 -24
  161. package/src/gateways/ws/ws-agent-handler.js +191 -14
  162. package/src/gateways/ws/ws-server.js +14 -3
  163. package/src/gateways/ws/ws-session-gateway.js +101 -5
  164. package/src/harness/jsonl-session-store.js +1210 -0
  165. package/src/lib/agent-team/team-runner.js +43 -13
  166. package/src/lib/artifact-access-ledger.js +461 -0
  167. package/src/lib/artifact-cleanup-ledger.js +861 -0
  168. package/src/lib/artifact-deletion-ledger.js +677 -0
  169. package/src/lib/artifact-store.js +256 -51
  170. package/src/lib/background-agent-keeper-protocol.js +75 -4
  171. package/src/lib/background-agent-launch-barrier.js +74 -0
  172. package/src/lib/background-agent-supervisor.js +149 -5
  173. package/src/lib/background-turn-bootstrap-protocol.js +4 -0
  174. package/src/lib/command-lifecycle-report.js +8 -4
  175. package/src/lib/cowork-adapter.js +9 -5
  176. package/src/lib/cowork-task-runner.js +20 -0
  177. package/src/lib/cowork-workflow.js +178 -20
  178. package/src/lib/direct-model-usage.js +62 -22
  179. package/src/lib/dynamic-workflow-runtime.js +3184 -111
  180. package/src/lib/execution-location-result-apply.js +339 -0
  181. package/src/lib/execution-location-result-artifact.js +271 -0
  182. package/src/lib/execution-location-result-review.js +253 -0
  183. package/src/lib/execution-location-result-store.js +331 -0
  184. package/src/lib/execution-location-result.js +617 -0
  185. package/src/lib/execution-location-target.js +236 -1
  186. package/src/lib/managed-tool-checkpoint.js +47 -4
  187. package/src/lib/process-execution-broker/workspace-transaction.js +3 -0
  188. package/src/lib/session-budget-production-root.js +376 -0
  189. package/src/lib/session-budget-runtime.js +109 -18
  190. package/src/lib/session-budget-usage.js +63 -0
  191. package/src/lib/session-host-snapshot.js +19 -2
  192. package/src/lib/session-resource-budget.js +766 -105
  193. package/src/lib/sub-agent-context.js +69 -22
  194. package/src/lib/web-ui-server.js +12 -1
  195. package/src/lib/with-file-lock.js +175 -26
  196. package/src/repl/agent-repl.js +237 -16
  197. package/src/runtime/agent-core.js +123 -0
  198. package/src/runtime/headless-runner.js +135 -46
  199. package/src/runtime/headless-stream.js +133 -25
  200. package/src/runtime/policies/agent-policy.js +10 -0
  201. package/src/workers/background-agent-keeper.js +112 -19
  202. package/src/workers/background-agent-worker.js +383 -134
  203. package/src/workers/background-turn-bootstrap.js +13 -8
  204. package/src/assets/web-panel/assets/ChatBubbleRenderer-CY5m8ZHg.js +0 -1
  205. package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +0 -1
  206. package/src/assets/web-panel/assets/Projects-BhtAVilc.js +0 -1
  207. package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +0 -1
  208. package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +0 -1
  209. package/src/assets/web-panel/assets/index-CPcM-eqo.js +0 -1
  210. package/src/assets/web-panel/assets/index-xeSyVoRe.js +0 -1
@@ -109,6 +109,16 @@ import {
109
109
  readSessionHostResumeState,
110
110
  } from "../lib/session-host-snapshot.js";
111
111
  import { acquireSessionHostLease } from "../lib/session-host-lease.js";
112
+ import {
113
+ openProductionSessionBudgetRoot,
114
+ sessionBudgetAdmissionError,
115
+ } from "../lib/session-budget-production-root.js";
116
+ import {
117
+ beginSessionBudgetUsage,
118
+ markSessionBudgetUsageUnknown,
119
+ recordSessionBudgetUsage,
120
+ rejectSessionBudgetUsageUnknown,
121
+ } from "../lib/session-budget-usage.js";
112
122
  import { classifyStreamRetryReason } from "../lib/stream-retry.js";
113
123
  import {
114
124
  SideEffectLedger,
@@ -809,6 +819,7 @@ async function runTurn(
809
819
  waitForOutput,
810
820
  persistUsageEvent,
811
821
  persistToolEvent,
822
+ sessionBudget,
812
823
  now = Date.now,
813
824
  },
814
825
  ) {
@@ -1008,6 +1019,11 @@ async function runTurn(
1008
1019
  }
1009
1020
  case "token-usage":
1010
1021
  if (event.ledgerPersisted !== true) persistUsageEvent?.(event);
1022
+ recordSessionBudgetUsage(
1023
+ sessionBudget,
1024
+ event,
1025
+ "headless usage settlement",
1026
+ );
1011
1027
  // 用量归因: attributed child-loop usage (sub-agent / isolated skill)
1012
1028
  // is forwarded on the stream (same wire shape) and counted toward the
1013
1029
  // cost budget, but stays out of the turn's `usage` envelope, which
@@ -1044,6 +1060,15 @@ async function runTurn(
1044
1060
  case "model-usage-started":
1045
1061
  case "model-usage-unknown":
1046
1062
  if (event.ledgerPersisted !== true) persistUsageEvent?.(event);
1063
+ if (event.type === "model-usage-started") {
1064
+ beginSessionBudgetUsage(
1065
+ sessionBudget,
1066
+ event,
1067
+ "headless provider call",
1068
+ );
1069
+ } else if (markSessionBudgetUsageUnknown(sessionBudget, event)) {
1070
+ rejectSessionBudgetUsageUnknown(event, "headless provider call");
1071
+ }
1047
1072
  break;
1048
1073
  case "compaction-usage-unknown":
1049
1074
  if (event.ledgerPersisted !== true) {
@@ -1053,6 +1078,12 @@ async function runTurn(
1053
1078
  code: event.code || "provider_transport_outcome_unknown",
1054
1079
  });
1055
1080
  }
1081
+ if (markSessionBudgetUsageUnknown(sessionBudget, event)) {
1082
+ rejectSessionBudgetUsageUnknown(
1083
+ event,
1084
+ "headless semantic compaction",
1085
+ );
1086
+ }
1056
1087
  emit({
1057
1088
  type: "compaction_usage_unknown",
1058
1089
  provider: event.provider,
@@ -1249,6 +1280,7 @@ async function withStreamSessionHostLease(options, deps, streamCleanup, task) {
1249
1280
  deps.acquireSessionHostLease ||
1250
1281
  (!hasInjectedSessionStore ? acquireSessionHostLease : null);
1251
1282
  let lease = null;
1283
+ let budgetRoot = null;
1252
1284
  let onLeaseAbort = null;
1253
1285
  try {
1254
1286
  if (persist && typeof acquireHostLease === "function") {
@@ -1262,7 +1294,7 @@ async function withStreamSessionHostLease(options, deps, streamCleanup, task) {
1262
1294
  if (lease.signal.aborted) onLeaseAbort();
1263
1295
  }
1264
1296
  }
1265
- const scopedOptions = lease?.signal
1297
+ let scopedOptions = lease?.signal
1266
1298
  ? {
1267
1299
  ...options,
1268
1300
  signal: options.signal
@@ -1270,12 +1302,28 @@ async function withStreamSessionHostLease(options, deps, streamCleanup, task) {
1270
1302
  : lease.signal,
1271
1303
  }
1272
1304
  : options;
1305
+ budgetRoot = openProductionSessionBudgetRoot(
1306
+ persist ? sessionId : null,
1307
+ options.sessionBudgetRoot,
1308
+ {
1309
+ persist,
1310
+ signal: scopedOptions.signal || null,
1311
+ table: options.sessionBudgetRoot?.table,
1312
+ open: deps.openSessionBudget,
1313
+ store: deps.sessionBudgetStore,
1314
+ registry: deps.sessionBudgetRegistry,
1315
+ },
1316
+ );
1317
+ if (budgetRoot.enabled) {
1318
+ scopedOptions = { ...scopedOptions, ...budgetRoot.options };
1319
+ }
1273
1320
  return await task(scopedOptions, {
1274
1321
  ...deps,
1275
1322
  [STREAM_SESSION_ID]: sessionId,
1276
1323
  [STREAM_SESSION_HOST_LEASE]: lease,
1277
1324
  });
1278
1325
  } finally {
1326
+ budgetRoot?.close?.();
1279
1327
  if (lease?.signal && onLeaseAbort) {
1280
1328
  lease.signal.removeEventListener("abort", onLeaseAbort);
1281
1329
  }
@@ -1646,8 +1694,9 @@ async function runAgentHeadlessStreamInWorkspace(
1646
1694
  const { loadHooks, projectHookTrustNotice, attachAuthorityErrors } =
1647
1695
  await import("../lib/settings-hooks.cjs");
1648
1696
  const loaded = loadHooks({ cwd, settingsFile: options.settingsFile });
1649
- const { mergePluginHooks } =
1650
- await import("../lib/plugin-runtime/hooks.js");
1697
+ const { mergePluginHooks } = await import(
1698
+ "../lib/plugin-runtime/hooks.js"
1699
+ );
1651
1700
  const effectiveHooks = mergePluginHooks(
1652
1701
  attachAuthorityErrors(loaded.hooks, loaded.authorityErrors),
1653
1702
  { cwd },
@@ -1728,8 +1777,9 @@ async function runAgentHeadlessStreamInWorkspace(
1728
1777
  });
1729
1778
 
1730
1779
  if (managedSettings) {
1731
- const { assertManagedPermissionMode } =
1732
- await import("../lib/settings-loader.cjs");
1780
+ const { assertManagedPermissionMode } = await import(
1781
+ "../lib/settings-loader.cjs"
1782
+ );
1733
1783
  assertManagedPermissionMode(options.permissionMode, managedSettings);
1734
1784
  }
1735
1785
  const perm = resolvePermissionMode(options.permissionMode);
@@ -2217,8 +2267,9 @@ async function runAgentHeadlessStreamInWorkspace(
2217
2267
  : null;
2218
2268
  if (!instructionExcludes) {
2219
2269
  try {
2220
- const { readStringArraySetting } =
2221
- await import("../lib/settings-loader.cjs");
2270
+ const { readStringArraySetting } = await import(
2271
+ "../lib/settings-loader.cjs"
2272
+ );
2222
2273
  const fromSettings = readStringArraySetting("instructionExcludes", {
2223
2274
  cwd,
2224
2275
  settingsFile: options.settingsFile,
@@ -2283,8 +2334,9 @@ async function runAgentHeadlessStreamInWorkspace(
2283
2334
  // Best-effort; no-op when project memory is off or no hook is registered.
2284
2335
  if (settingsHooks && _loadedInstructions) {
2285
2336
  try {
2286
- const { runInstructionsLoadedHooks } =
2287
- await import("../lib/settings-hook-events.js");
2337
+ const { runInstructionsLoadedHooks } = await import(
2338
+ "../lib/settings-hook-events.js"
2339
+ );
2288
2340
  const ctx = runInstructionsLoadedHooks(settingsHooks, {
2289
2341
  files: _loadedInstructions.files,
2290
2342
  cwd,
@@ -2324,8 +2376,9 @@ async function runAgentHeadlessStreamInWorkspace(
2324
2376
  // settings.json SessionStart hooks → inject session context once (observe-only).
2325
2377
  if (settingsHooks) {
2326
2378
  try {
2327
- const { runSessionStartHooks } =
2328
- await import("../lib/settings-hook-events.js");
2379
+ const { runSessionStartHooks } = await import(
2380
+ "../lib/settings-hook-events.js"
2381
+ );
2329
2382
  const ctx = runSessionStartHooks(settingsHooks, {
2330
2383
  source: "startup",
2331
2384
  cwd,
@@ -2696,8 +2749,9 @@ async function runAgentHeadlessStreamInWorkspace(
2696
2749
  // the single-prompt runner + the REPL /add-dir path. Best-effort.
2697
2750
  if (hostMcp?.mcpClient && additionalDirectories.length > 0) {
2698
2751
  try {
2699
- const { notifyMcpRootsChanged, workspaceRootDirs } =
2700
- await import("../repl/add-dir.js");
2752
+ const { notifyMcpRootsChanged, workspaceRootDirs } = await import(
2753
+ "../repl/add-dir.js"
2754
+ );
2701
2755
  notifyMcpRootsChanged(
2702
2756
  [hostMcp.mcpClient],
2703
2757
  workspaceRootDirs(options.cwd || process.cwd(), additionalDirectories),
@@ -2798,6 +2852,7 @@ async function runAgentHeadlessStreamInWorkspace(
2798
2852
  cwd,
2799
2853
  additionalDirectories,
2800
2854
  sessionId,
2855
+ sessionBudget: options.sessionBudget || null,
2801
2856
  sessionHostSnapshot: canonicalResume?.snapshot || null,
2802
2857
  // Auto-checkpoint (Claude-Code parity): snapshot the work tree before each
2803
2858
  // mutating tool so a stream consumer (e.g. the IDE chat panel) can rewind.
@@ -3281,13 +3336,19 @@ async function runAgentHeadlessStreamInWorkspace(
3281
3336
  maxOutputTokens: options.compactionMaxOutputTokens,
3282
3337
  summaryInputMaxChars: options.compactionInputMaxChars,
3283
3338
  onProviderCallStart: () => {
3284
- persistUsageEvent?.({
3339
+ const event = {
3285
3340
  type: "model-usage-started",
3286
3341
  callId: compactionCallId,
3287
3342
  provider,
3288
3343
  model,
3289
3344
  source: "semantic-compaction",
3290
- });
3345
+ };
3346
+ persistUsageEvent?.(event);
3347
+ beginSessionBudgetUsage(
3348
+ options.sessionBudget,
3349
+ event,
3350
+ "headless semantic compaction",
3351
+ );
3291
3352
  compactionProviderStarted = true;
3292
3353
  return compactionCallId;
3293
3354
  },
@@ -3296,14 +3357,21 @@ async function runAgentHeadlessStreamInWorkspace(
3296
3357
  if (isAbortError(error) || options.signal?.aborted) throw error;
3297
3358
  if (error?.runtimeLedgerPersistence === true) throw error;
3298
3359
  if (compactionProviderStarted) {
3299
- persistUsageEvent?.({
3360
+ const event = {
3300
3361
  type: "model-usage-unknown",
3301
3362
  callId: compactionCallId,
3302
3363
  provider,
3303
3364
  model,
3304
3365
  source: "semantic-compaction",
3305
3366
  code: "provider_call_failed",
3306
- });
3367
+ };
3368
+ persistUsageEvent?.(event);
3369
+ if (markSessionBudgetUsageUnknown(options.sessionBudget, event)) {
3370
+ rejectSessionBudgetUsageUnknown(
3371
+ event,
3372
+ "headless semantic compaction",
3373
+ );
3374
+ }
3307
3375
  }
3308
3376
  emit({
3309
3377
  type: "compaction-degraded",
@@ -3316,14 +3384,21 @@ async function runAgentHeadlessStreamInWorkspace(
3316
3384
  emit({ type: "compaction-degraded", ...degradedEvent });
3317
3385
  }
3318
3386
  if (usageUnknownEvent) {
3319
- persistUsageEvent?.({
3387
+ const event = {
3320
3388
  type: "model-usage-unknown",
3321
3389
  callId: compactionCallId,
3322
3390
  provider: usageUnknownEvent.provider || provider,
3323
3391
  model: usageUnknownEvent.model || model,
3324
3392
  source: "semantic-compaction",
3325
3393
  code: "provider_transport_outcome_unknown",
3326
- });
3394
+ };
3395
+ persistUsageEvent?.(event);
3396
+ if (markSessionBudgetUsageUnknown(options.sessionBudget, event)) {
3397
+ rejectSessionBudgetUsageUnknown(
3398
+ event,
3399
+ "headless semantic compaction",
3400
+ );
3401
+ }
3327
3402
  emit({
3328
3403
  type: "compaction_usage_unknown",
3329
3404
  ...usageUnknownEvent,
@@ -3344,12 +3419,18 @@ async function runAgentHeadlessStreamInWorkspace(
3344
3419
  }
3345
3420
  let costExceeded = false;
3346
3421
  if (usageEvent) {
3347
- persistUsageEvent?.({
3422
+ const event = {
3348
3423
  type: "token-usage",
3349
3424
  callId: compactionCallId,
3350
3425
  ...usageEvent,
3351
3426
  source: "semantic-compaction",
3352
- });
3427
+ };
3428
+ persistUsageEvent?.(event);
3429
+ recordSessionBudgetUsage(
3430
+ options.sessionBudget,
3431
+ event,
3432
+ "headless semantic compaction usage settlement",
3433
+ );
3353
3434
  emit({ type: "token_usage", ...usageEvent });
3354
3435
  if (costBudget) {
3355
3436
  costBudget.add(usageEvent);
@@ -3609,8 +3690,9 @@ async function runAgentHeadlessStreamInWorkspace(
3609
3690
  // session can honour the user's standing preferences ("越用越聪明"
3610
3691
  // flywheel). Best-effort — a learning ledger must never break the chat.
3611
3692
  try {
3612
- const { appendFeedback } =
3613
- await import("../lib/pdh-feedback-ledger.js");
3693
+ const { appendFeedback } = await import(
3694
+ "../lib/pdh-feedback-ledger.js"
3695
+ );
3614
3696
  appendFeedback({ sessionId, turnId, kind, comment });
3615
3697
  } catch {
3616
3698
  /* persistence is best-effort */
@@ -3741,8 +3823,9 @@ async function runAgentHeadlessStreamInWorkspace(
3741
3823
  // settings.json UserPromptSubmit hooks. block → skip this turn; context → inject.
3742
3824
  if (settingsHooks) {
3743
3825
  try {
3744
- const { runUserPromptSubmitHooks } =
3745
- await import("../lib/settings-hook-events.js");
3826
+ const { runUserPromptSubmitHooks } = await import(
3827
+ "../lib/settings-hook-events.js"
3828
+ );
3746
3829
  const ups = runUserPromptSubmitHooks(settingsHooks, {
3747
3830
  prompt: userContent,
3748
3831
  cwd,
@@ -3958,6 +4041,7 @@ async function runAgentHeadlessStreamInWorkspace(
3958
4041
  now: deps.now || Date.now,
3959
4042
  persistUsageEvent,
3960
4043
  persistToolEvent,
4044
+ sessionBudget: options.sessionBudget || null,
3961
4045
  },
3962
4046
  );
3963
4047
  } catch (err) {
@@ -4001,6 +4085,30 @@ async function runAgentHeadlessStreamInWorkspace(
4001
4085
  break;
4002
4086
  }
4003
4087
  }
4088
+ if (
4089
+ err?.code === "CC_SESSION_BUDGET_EXHAUSTED" ||
4090
+ err?.code === "CC_SESSION_BUDGET_USAGE_UNKNOWN" ||
4091
+ options.sessionBudget?.signal?.aborted === true
4092
+ ) {
4093
+ const budgetReason =
4094
+ options.sessionBudget?.reason?.() ||
4095
+ err.budgetReason ||
4096
+ "session-aborted";
4097
+ emit({
4098
+ type: "result",
4099
+ subtype: "error_session_budget",
4100
+ is_error: true,
4101
+ code: err.code,
4102
+ error:
4103
+ err?.code === "CC_SESSION_BUDGET_USAGE_UNKNOWN"
4104
+ ? err.message
4105
+ : sessionBudgetAdmissionError(budgetReason, "run").message,
4106
+ budget_reason: budgetReason,
4107
+ turn: turns,
4108
+ });
4109
+ sawError = true;
4110
+ break;
4111
+ }
4004
4112
  const isAbort =
4005
4113
  err?.name === "AbortError" || /abort/i.test(err?.message || "");
4006
4114
  if (isAbort && !options.signal?.aborted) {
@@ -49,6 +49,16 @@ export function resolveAgentPolicy({
49
49
  : [],
50
50
  worktreeId:
51
51
  overrides.worktreeId == null ? null : String(overrides.worktreeId),
52
+ sessionBudgetRoot:
53
+ overrides.sessionBudgetRoot &&
54
+ typeof overrides.sessionBudgetRoot === "object"
55
+ ? Object.freeze({
56
+ ...overrides.sessionBudgetRoot,
57
+ limits: Object.freeze({
58
+ ...(overrides.sessionBudgetRoot.limits || {}),
59
+ }),
60
+ })
61
+ : null,
52
62
  ...(overrides.observabilityScope &&
53
63
  typeof overrides.observabilityScope === "object"
54
64
  ? { observabilityScope: { ...overrides.observabilityScope } }
@@ -9,8 +9,15 @@
9
9
  */
10
10
 
11
11
  import net from "node:net";
12
- import { readFileSync, unlinkSync, writeSync } from "node:fs";
13
- import { resolve } from "node:path";
12
+ import {
13
+ readFileSync,
14
+ renameSync,
15
+ rmSync,
16
+ unlinkSync,
17
+ writeFileSync,
18
+ writeSync,
19
+ } from "node:fs";
20
+ import { dirname, resolve } from "node:path";
14
21
  import { fileURLToPath } from "node:url";
15
22
  import { createNdjsonReader } from "../lib/background-session-transport.js";
16
23
  import {
@@ -28,6 +35,7 @@ import {
28
35
  BACKGROUND_AGENT_KEEPER_CLEANUP_CONFIRM_TIMEOUT_MS,
29
36
  BACKGROUND_AGENT_KEEPER_HELLO,
30
37
  BACKGROUND_AGENT_KEEPER_HEARTBEAT,
38
+ BACKGROUND_AGENT_KEEPER_IDENTITY_PROBE_DELAY_MS,
31
39
  BACKGROUND_AGENT_KEEPER_HEARTBEAT_TIMEOUT_MS,
32
40
  BACKGROUND_AGENT_KEEPER_PERSIST_RETRY_TIMEOUT_MS,
33
41
  BACKGROUND_AGENT_KEEPER_PROTOCOL_VERSION,
@@ -35,12 +43,15 @@ import {
35
43
  BACKGROUND_AGENT_KEEPER_RETIRE,
36
44
  BACKGROUND_AGENT_KEEPER_RETIRED,
37
45
  BACKGROUND_AGENT_KEEPER_STATE_LOCK_TIMEOUT_MS,
46
+ backgroundAgentKeeperLaunchClaimPath,
38
47
  cleanupBackgroundAgentKeeperPipeDirectory,
48
+ createBackgroundAgentKeeperLaunchClaim,
39
49
  createBackgroundAgentKeeperMessage,
40
50
  normalizeBackgroundAgentKeeperHello,
41
51
  normalizeBackgroundAgentKeeperTurn,
42
52
  sameBackgroundAgentKeeperTurn,
43
53
  } from "../lib/background-agent-keeper-protocol.js";
54
+ import { waitForBackgroundAgentLaunchBarrier } from "../lib/background-agent-launch-barrier.js";
44
55
 
45
56
  const STARTUP_TIMEOUT_MS = 30_000;
46
57
  const HEARTBEAT_INTERVAL_MS = 1_000;
@@ -132,18 +143,29 @@ export function runBackgroundAgentKeeperHeartbeat({
132
143
  reportPersistenceFailure = reportKeeperPersistenceFailure,
133
144
  }) {
134
145
  if (finishing) return false;
146
+ // Startup is deliberately serialized: the keeper publishes its listening
147
+ // claim, then remains read-only until the worker has claimed and
148
+ // authenticated. Persisting a pre-auth heartbeat would reintroduce the
149
+ // first-write lock race the launch barrier prevents.
150
+ if (!workerSocket || !authenticatedHello) return false;
135
151
  const currentTime = now();
152
+ const workerHeartbeatAge = Number.isFinite(workerHeartbeatAt)
153
+ ? Math.max(0, currentTime - workerHeartbeatAt)
154
+ : 0;
136
155
  const workerHeartbeatExpired =
137
156
  workerSocket &&
138
157
  authenticatedHello &&
139
158
  Number.isFinite(workerHeartbeatAt) &&
140
- currentTime - workerHeartbeatAt >
141
- BACKGROUND_AGENT_KEEPER_HEARTBEAT_TIMEOUT_MS;
159
+ workerHeartbeatAge > BACKGROUND_AGENT_KEEPER_HEARTBEAT_TIMEOUT_MS;
160
+ const workerIdentityProbeDue =
161
+ armedTurn &&
162
+ Number.isFinite(workerHeartbeatAt) &&
163
+ workerHeartbeatAge > BACKGROUND_AGENT_KEEPER_IDENTITY_PROBE_DELAY_MS;
142
164
  if (
143
165
  workerSocket &&
144
166
  authenticatedHello &&
145
167
  (workerHeartbeatExpired ||
146
- (armedTurn &&
168
+ (workerIdentityProbeDue &&
147
169
  !workerIdentityAlive(
148
170
  authenticatedHello.workerPid,
149
171
  authenticatedWorkerStartedAt,
@@ -153,8 +175,9 @@ export function runBackgroundAgentKeeperHeartbeat({
153
175
  // after its worker dies if a platform helper retained a duplicate.
154
176
  // Socket EOF and PID visibility are therefore not sufficient lifetime
155
177
  // signals. The authenticated application heartbeat provides an
156
- // independent bounded fence, while the PID/start anchor remains the
157
- // immediate path on platforms that report death promptly.
178
+ // independent bounded fence. Delay the PID/start probe until application
179
+ // heartbeats are already stale so healthy Windows keepers do not create a
180
+ // synchronized WMIC/PowerShell probe storm under normal operation.
158
181
  finishForWorkerDisconnect();
159
182
  workerSocket.destroy();
160
183
  return false;
@@ -239,7 +262,9 @@ export function stopBackgroundAgentKeeperTurnTrees(targets, options = {}) {
239
262
  leaderAlive ||
240
263
  (platform !== "win32" &&
241
264
  target.processGroup === true &&
242
- processTreeExecutionAlive(target.pid, target.startedAt));
265
+ processTreeExecutionAlive(target.pid, target.startedAt, {
266
+ processGroup: true,
267
+ }));
243
268
  if (residualAlive) {
244
269
  failures.push(error?.message || String(error));
245
270
  }
@@ -248,6 +273,46 @@ export function stopBackgroundAgentKeeperTurnTrees(targets, options = {}) {
248
273
  return failures;
249
274
  }
250
275
 
276
+ export async function waitForBackgroundAgentKeeperTurnTreesToStop(
277
+ targets,
278
+ options = {},
279
+ ) {
280
+ const now = options.now || Date.now;
281
+ const sleep =
282
+ options.sleep ||
283
+ ((milliseconds) =>
284
+ new Promise((resolvePromise) =>
285
+ setTimeout(resolvePromise, milliseconds),
286
+ ));
287
+ const processTreeExecutionAlive =
288
+ options.isProcessTreeExecutionAlive ||
289
+ isBackgroundProcessTreeExecutionAlive;
290
+ const timeoutMs = Math.max(
291
+ 1,
292
+ Number(options.timeoutMs) ||
293
+ BACKGROUND_AGENT_KEEPER_CLEANUP_CONFIRM_TIMEOUT_MS,
294
+ );
295
+ // Each strict macOS liveness probe invokes ps. Polling every 10 ms across
296
+ // 20 keepers created a process storm precisely while launchd was trying to
297
+ // reap the killed groups. A 100 ms cadence remains responsive and bounded
298
+ // while leaving the host enough scheduling capacity to establish absence.
299
+ const pollMs = Math.max(1, Number(options.pollMs) || 100);
300
+ const deadline = now() + timeoutMs;
301
+ const observeAlive = () =>
302
+ targets.filter((target) =>
303
+ processTreeExecutionAlive(target.pid, target.startedAt, {
304
+ processGroup: target.processGroup !== false,
305
+ }),
306
+ );
307
+
308
+ let alive = observeAlive();
309
+ while (alive.length > 0 && now() < deadline) {
310
+ await sleep(Math.max(1, Math.min(pollMs, deadline - now())));
311
+ alive = observeAlive();
312
+ }
313
+ return alive;
314
+ }
315
+
251
316
  async function cleanupTurn(job, turn, reason) {
252
317
  const requestedAt = Date.now();
253
318
  const requestedPersistence = await retryKeeperPersistence(() =>
@@ -275,17 +340,7 @@ async function cleanupTurn(job, turn, reason) {
275
340
  );
276
341
  const failures = stopBackgroundAgentKeeperTurnTrees(targets);
277
342
 
278
- const deadline =
279
- Date.now() + BACKGROUND_AGENT_KEEPER_CLEANUP_CONFIRM_TIMEOUT_MS;
280
- let alive = targets.filter((target) =>
281
- isBackgroundProcessTreeExecutionAlive(target.pid, target.startedAt),
282
- );
283
- while (alive.length > 0 && Date.now() < deadline) {
284
- await new Promise((resolvePromise) => setTimeout(resolvePromise, 10));
285
- alive = targets.filter((target) =>
286
- isBackgroundProcessTreeExecutionAlive(target.pid, target.startedAt),
287
- );
288
- }
343
+ const alive = await waitForBackgroundAgentKeeperTurnTreesToStop(targets);
289
344
  const confirmed = alive.length === 0 && failures.length === 0;
290
345
  let finalPersistenceAttempts = 0;
291
346
  const finalPersistence = await retryKeeperPersistence(() => {
@@ -322,6 +377,24 @@ async function cleanupTurn(job, turn, reason) {
322
377
 
323
378
  export async function runBackgroundAgentKeeper(jobFile, options = {}) {
324
379
  const job = JSON.parse(readFileSync(jobFile, "utf8"));
380
+ const launchClaimPath = backgroundAgentKeeperLaunchClaimPath(
381
+ job.id,
382
+ job.keeperGeneration,
383
+ options.backgroundAgentsDirectory || dirname(resolve(jobFile)),
384
+ );
385
+ const launchBarrier = await waitForBackgroundAgentLaunchBarrier({
386
+ id: job.id,
387
+ workerGeneration: job.workerGeneration,
388
+ expectedPid: process.pid,
389
+ pidField: "keeperPid",
390
+ readState: options.readBackgroundAgentState || readBackgroundAgentState,
391
+ timeoutMs: Number(options.launchBarrierTimeoutMs) || undefined,
392
+ sleep: options.launchBarrierSleep,
393
+ });
394
+ if (launchBarrier.status !== "ready") {
395
+ removeJobFile(jobFile);
396
+ return { status: `launch-${launchBarrier.status}` };
397
+ }
325
398
  removeJobFile(jobFile);
326
399
  const startedAt = Date.now();
327
400
  const claim = mutateBackgroundAgentState(job.id, (current) => {
@@ -342,6 +415,25 @@ export async function runBackgroundAgentKeeper(jobFile, options = {}) {
342
415
  };
343
416
  });
344
417
  if (!claim.applied) return { status: "unclaimed" };
418
+ const launchClaim = createBackgroundAgentKeeperLaunchClaim({
419
+ id: job.id,
420
+ workerGeneration: job.workerGeneration,
421
+ keeperGeneration: job.keeperGeneration,
422
+ keeperPid: process.pid,
423
+ token: job.token,
424
+ });
425
+ const launchClaimCandidate = `${launchClaimPath}.${process.pid}.tmp`;
426
+ try {
427
+ writeFileSync(launchClaimCandidate, JSON.stringify(launchClaim), {
428
+ encoding: "utf8",
429
+ mode: 0o600,
430
+ flag: "wx",
431
+ });
432
+ renameSync(launchClaimCandidate, launchClaimPath);
433
+ } catch (error) {
434
+ rmSync(launchClaimCandidate, { force: true });
435
+ throw error;
436
+ }
345
437
 
346
438
  if (process.platform !== "win32") {
347
439
  try {
@@ -686,6 +778,7 @@ export async function runBackgroundAgentKeeper(jobFile, options = {}) {
686
778
  // Another keeper can still own a sibling socket in the shared namespace.
687
779
  }
688
780
  }
781
+ rmSync(launchClaimPath, { force: true });
689
782
  return {
690
783
  ...result,
691
784
  authenticatedWorkerPid: authenticatedHello?.workerPid || null,