chainlesschain 0.165.1 → 0.165.3

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 (206) hide show
  1. package/README.md +248 -2
  2. package/package.json +4 -1
  3. package/src/assets/web-panel/assets/{AIOps-CYxFDrcm.js → AIOps-BiHNUlbU.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-CPBtdiU2.js → ActionButton-D-UFjDRQ.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-Dayq7Dp7.js → Analytics-DazVsDgW.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-6U7bPb2s.js → AppLayout-DXh9UfNJ.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-D2Iqg_3D.js → Artifacts-CZ6tsceY.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-CdoOB7WV.js → Audit-RyxnBr_h.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-DrnAVB8w.js → BackgroundAgents-BWS51Q6X.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-CesMi5cy.js → Backup-BRtPcUul.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-DaX9ptax.js → BaseInput-CGd4vZYY.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-OrwOPxDS.js → Chat-h8ijVwCg.js} +4 -4
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CvPZFRxM.js → ChatBubbleRenderer-D7tcdDS9.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-DiHbcysC.js → Checkbox-NyF6dPl8.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-CbgCkaBJ.js → Codegen-Ci4OOxr5.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-Du5hgrh4.js → Col-CwTkc9BM.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-DsPOBW-p.js → Community-DW_tM82m.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-BwHIFkNC.js → Compact-DEvHRV4S.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-BvrDQSgd.js → Compliance-BbjBEEfD.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-gwAE6arx.js → Cowork-10IXBaBa.js} +2 -2
  21. package/src/assets/web-panel/assets/{Cron-DW-0l4uF.js → Cron-BZkbpd5I.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-j-NsLesB.js → Crosschain-CFQ8L_6v.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-CibVEBOI.js → DID-BDF6AkXe.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-W4z9vSfe.js → Dashboard-CoOdWMi4.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-DAcrkKxG.js → Dropdown-DEsGmu8Y.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-6GXah-89.js → EmailListRenderer-BS89SWlK.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-TKDCq8Ku.js → FamilyGuardDashboard-C1FpCHiI.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-BXBLlcNm.js → Federation-BzvpVmLA.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-BoJJBkG3.js → FormItemContext-BeC259PQ.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-okjsl46v.js → GenericCardRenderer-azgG3bmV.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-Rvs7i6nn.js → Git-BmSSowc5.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-CHSUxXXS.js → Governance-CTBCSyQZ.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-V3BlfxsB.js → Inference-DA5xyk4a.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-C_NlD4aR.js → KnowledgeGraph-D1gPHNig.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-Nn4LNxhA.js → Logs-BPmLf1AR.js} +2 -2
  36. package/src/assets/web-panel/assets/{MarkdownRenderer-SVAgNnkt.js → MarkdownRenderer-SVLOg49H.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-DEmjFcg-.js → Marketplace-DtUFcR0E.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-DlopXipI.js → McpTools-DS_kqBv-.js} +3 -3
  39. package/src/assets/web-panel/assets/{Memory-BG3ynX60.js → Memory-Fvh7XL7V.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-DeRzWOfT.js → MobileBridge-C7itAP45.js} +1 -1
  41. package/src/assets/web-panel/assets/MobileProjects-DGbP36-3.js +1 -0
  42. package/src/assets/web-panel/assets/{Mtc-BoPbOv1C.js → Mtc-B57MiNLK.js} +6 -6
  43. package/src/assets/web-panel/assets/{MtcAudit-un-rTHp7.js → MtcAudit-BXfCUcM7.js} +4 -4
  44. package/src/assets/web-panel/assets/{Multisig-Ds0oveEB.js → Multisig-yaJ-ZcF6.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-D09JI-yY.js → NLProgramming-CoaUCKrO.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-DTRhiKJn.js → Notes-BKLM_65R.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-BAnnNCim.js → NotificationSettings-DoDtK9uZ.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-Yr-1fSYi.js → OrderTableRenderer-BJ28TGr-.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-S9EAolpj.js → Organization-MuGsdoIb.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-CqcLKqJ8.js → Overflow-BLUN9J4R.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-Dw0F9EcU.js → P2P-DWuoBUPd.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-BDRwUevN.js → PdhVaultBrowser-CUi37YPj.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-D3_c3nKB.js → Permissions-BYQg2pcr.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-CxdMV98x.js → PersonalDataHub-CAxoV2xu.js} +6 -6
  55. package/src/assets/web-panel/assets/{Pipeline-CgjNQFLI.js → Pipeline-B33wGvIO.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-BHghIIht.js → Privacy-A8yXTORP.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-De6pqaKM.js → ProjectInit-D49I2I1O.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-Bg4ZkNYQ.js → ProjectSettings-Ci73jJDw.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-CF-yD6Cn.js +1 -0
  60. package/src/assets/web-panel/assets/Providers-D-etEhg5.js +1 -0
  61. package/src/assets/web-panel/assets/{QrScannerModal-DA2f3Wts.js → QrScannerModal-Brza0ykJ.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-C1FeHDqp.js → QuickAsk-DFydhjxL.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-B5guaUMN.js → Recommend-DLF8_NhA.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-PDADSds3.js → RemoteSession-dyuStW3x.js} +3 -3
  65. package/src/assets/web-panel/assets/{Reputation-eTlRhgvK.js → Reputation-5F3tA6yw.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-D7AqSGY0.js → Row-Ca2A57S5.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-JiuLm4Ja.js → RssFeed-B0bxBkq_.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-CwruahW3.js → Search-CFNvHalZ.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-CdhqaR0b.js → Security-DSoH3Hav.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-CU0ZIlcT.js → Services-BHpfEmBQ.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-CtduPfAb.js → Skeleton-Dsh-QNmn.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-CROXm6AY.js → Skills-BhT1NkSU.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-DiZiABFD.js → Sla-DY9snPeJ.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-B5pLcXUw.js → SpeechSettings-CWaX0_Cx.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-DBX6J1Oe.js → SyncSettings-B690y_TC.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-BLyRJd3-.js → Tasks-2N-w3zWd.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-BKa5qzmk.js → Templates-BjwQOQYC.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-DnD3a4M6.js → Tenant-CUqHErxY.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-DEitQeTb.js → Terminal-C13rXvNy.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-CjHggTDq.js → TimelineRenderer-CpbWcya_.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-CsGT7sfl.js → Tokens-BXHLGXYC.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-yr4rFDrW.js → Trigger-1e5i65o3.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-CLvu1Sjp.js → Trust-FLGqNHs5.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-B0m7yUP8.js → UkeySign-BbaR_LJB.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-C0VvP4lc.js → VideoEditing-NOp4RIov.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-kllBAG1M.js → Wallet-B7DW7bG4.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-C54UQNLx.js → WebAuthn-BptSUWix.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-B7y8qSvU.js → WorkflowEditor-CAJnDOXS.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-DRzTh8CJ.js → chat-I8LgG1-Z.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-BSMwSnCx.js → colors-CWe7SOVD.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-BTg602H5.js → compact-item-Cx3u3CA3.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-B6EGsdvp.js → createContext-CgD5ArwY.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-DLv_872g.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-DDKqJ8RD.js → hasIn-BfQWiXN8.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-D5CsBShd.js → index-3iKP9OTJ.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-Dfv2lKu3.js → index-87gMxqhN.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-C66NpWy5.js → index-B1cX4Rxu.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DBT3YeQ9.js → index-B6yChzt1.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BVTAl5N2.js → index-BREnPKGo.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CwPUZbMI.js → index-BUAR7RY-.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-th2ihDc0.js → index-Bi24S7Dw.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CmAubjDi.js → index-BiI-xPRH.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CUEU48o1.js → index-BuEQSorC.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Cf23wVMs.js → index-Bx7BNIRH.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BRqt4fO-.js → index-C-rIKccl.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-C_JzIXUq.js → index-C0nDKhWv.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DiW0sDkz.js → index-C0ztiqtB.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BICn3Qh1.js → index-C1UjHdgZ.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DpTHIRfZ.js → index-CAUnl4Jv.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Co7oM1qr.js → index-CMu16vPC.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CNxg61YP.js → index-CNYFQxAW.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-XFe-1twF.js → index-CQDKYbnv.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-p2HYn89m.js → index-CWZ3REg6.js} +3 -3
  114. package/src/assets/web-panel/assets/{index-Bx044qDj.js → index-CcX4FSPf.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BMuGd2--.js → index-Cd79NQAx.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CGNklbP6.js → index-CdRkCwm4.js} +1 -1
  117. package/src/assets/web-panel/assets/index-CdiGCibe.js +1 -0
  118. package/src/assets/web-panel/assets/{index-rjzk9z1Q.js → index-Cy_cCJlr.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-323Hhxxt.js → index-DAVy98u7.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CDTvsX8t.js → index-DCJEY2T0.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DLdrHhML.js → index-DFb-XIKy.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-pqfux8vZ.js → index-DGMYTN5W.js} +1 -1
  123. package/src/assets/web-panel/assets/index-D_0z-gyz.js +1 -0
  124. package/src/assets/web-panel/assets/{index-u2YVld9W.js → index-Dcdm_Z2c.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CmAInUkX.js → index-DeizoQVZ.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CHfLYwcW.js → index-DlIyt1qF.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CWGax2QW.js → index-DmgtY58H.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Dc77VR3H.js → index-DmrdUwzU.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-D1naNj4U.js → index-Doc0Ftpd.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BsR7Y2NG.js → index-DsQcURkA.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-CQlHFN_g.js → index-DslTuqro.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DqFXGzzC.js → index-V-kIHzAV.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-C1GFfuqN.js → index-e8BM1yY1.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-C3Djh9fa.js → initDefaultProps-DiBszVdG.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BzqFmbSe.js → motion-BGIFFozv.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-DJo_FFaW.js → move-D5W7mp7B.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-_tlTiBvk.js → mtc-parser-JFIlxJ7F.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-CB2ZyxUg.js → omit-CQ3o6LIU.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-DZwIFD4u.js → pickAttrs-R8MIlXo3.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-DxZY0dgk.js → placementArrow-sW7ylSky.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-CzzGt7GL.js → responsiveObserve-Cnl39Vbz.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-BaIK52yz.js → slide-sn8tFd14.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-3-FHu9_M.js → statusUtils-IKKY_QxZ.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-BkRT68fK.js → styleChecker-BNpV4Nqs.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-DHYwKS-s.js → useFlexGapSupport-BWyT_eoU.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-DVVUhV8p.js → useFs-D09p6_U9.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-CkrFKAzQ.js → usePersonalDataHub-CzW3TkJZ.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-BuBLFz0c.js → vnode-3s3WoAKB.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-kpm6LtXU.js → zoom-Cjy_673s.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/command-help-index.json +1 -1
  152. package/src/commands/cowork.js +280 -5
  153. package/src/commands/plugin.js +819 -64
  154. package/src/commands/session-location.js +318 -7
  155. package/src/constants.js +2 -2
  156. package/src/data/changelog.json +27 -1
  157. package/src/gateways/ws/action-protocol.js +1 -1
  158. package/src/harness/jsonl-session-store.js +638 -2
  159. package/src/harness/prompt-compressor.js +4 -0
  160. package/src/lib/background-agent-keeper-client.js +29 -0
  161. package/src/lib/background-agent-keeper-protocol.js +78 -3
  162. package/src/lib/background-agent-launch-barrier.js +74 -0
  163. package/src/lib/background-agent-supervisor.js +208 -34
  164. package/src/lib/background-turn-bootstrap-protocol.js +4 -0
  165. package/src/lib/cowork-task-runner.js +70 -0
  166. package/src/lib/cowork-workflow.js +42 -9
  167. package/src/lib/doctor-checkup.js +34 -10
  168. package/src/lib/dynamic-workflow-draft.js +535 -0
  169. package/src/lib/dynamic-workflow-facade.js +11 -11
  170. package/src/lib/dynamic-workflow-runtime.js +2974 -0
  171. package/src/lib/execution-location-contract.js +111 -7
  172. package/src/lib/execution-location-runtime.js +3 -1
  173. package/src/lib/execution-location-target.js +910 -0
  174. package/src/lib/llm-providers.js +1 -0
  175. package/src/lib/mcp-call-ledger-store.js +35 -0
  176. package/src/lib/mcp-call-ledger.js +30 -0
  177. package/src/lib/plugin-runtime/__tests__/remote-source.test.js +34 -16
  178. package/src/lib/plugin-runtime/install.js +4956 -657
  179. package/src/lib/plugin-runtime/manifest.js +3 -1
  180. package/src/lib/plugin-runtime/marketplace-artifact-readback.js +671 -31
  181. package/src/lib/plugin-runtime/marketplace-catalog.js +68 -2
  182. package/src/lib/plugin-runtime/marketplace-impact.js +208 -15
  183. package/src/lib/plugin-runtime/marketplace-network.js +317 -0
  184. package/src/lib/plugin-runtime/marketplace-pac-worker.js +15 -0
  185. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +25 -2
  186. package/src/lib/plugin-runtime/marketplace-source-cache.js +359 -0
  187. package/src/lib/plugin-runtime/policy.js +21 -6
  188. package/src/lib/plugin-runtime/publisher-trust.js +332 -0
  189. package/src/lib/plugin-runtime/remote-source.js +323 -27
  190. package/src/lib/plugin-runtime/scopes.js +317 -41
  191. package/src/lib/plugin-runtime/transaction-journal.js +806 -0
  192. package/src/lib/public-ide-capability-manifest.js +1 -1
  193. package/src/lib/remote-membership-coordinator.js +8 -6
  194. package/src/lib/sub-agent-context.js +268 -2
  195. package/src/lib/task-model-selector.js +1 -1
  196. package/src/lib/with-file-lock.js +266 -27
  197. package/src/runtime/agent-core.js +595 -41
  198. package/src/workers/background-agent-keeper.js +147 -24
  199. package/src/workers/background-agent-worker.js +383 -134
  200. package/src/workers/background-turn-bootstrap.js +13 -8
  201. package/src/assets/web-panel/assets/MobileProjects-Tyd3o2A0.js +0 -1
  202. package/src/assets/web-panel/assets/Projects-CP8vPvzs.js +0 -1
  203. package/src/assets/web-panel/assets/Providers-CUnHbNf-.js +0 -1
  204. package/src/assets/web-panel/assets/devWarning-Dol8jLEP.js +0 -1
  205. package/src/assets/web-panel/assets/index-C3QX5tOl.js +0 -1
  206. package/src/assets/web-panel/assets/index-xYybvEic.js +0 -1
@@ -5,6 +5,8 @@ import {
5
5
  BACKGROUND_AGENT_KEEPER_ARM,
6
6
  BACKGROUND_AGENT_KEEPER_ARMED,
7
7
  BACKGROUND_AGENT_KEEPER_HELLO,
8
+ BACKGROUND_AGENT_KEEPER_HEARTBEAT,
9
+ BACKGROUND_AGENT_KEEPER_HEARTBEAT_INTERVAL_MS,
8
10
  BACKGROUND_AGENT_KEEPER_PROTOCOL_VERSION,
9
11
  BACKGROUND_AGENT_KEEPER_READY,
10
12
  BACKGROUND_AGENT_KEEPER_RETIRE,
@@ -76,11 +78,17 @@ export async function connectBackgroundAgentKeeper(options = {}) {
76
78
  const retireTimeoutMs = resolveBackgroundAgentKeeperRetireTimeoutMs(
77
79
  options.retireTimeoutMs,
78
80
  );
81
+ const heartbeatIntervalMs = Math.max(
82
+ 10,
83
+ Number(options.heartbeatIntervalMs) ||
84
+ BACKGROUND_AGENT_KEEPER_HEARTBEAT_INTERVAL_MS,
85
+ );
79
86
  const socket = await connectWithDeadline(options.pipePath, connectTimeoutMs);
80
87
  const pending = new Map();
81
88
  let ready = false;
82
89
  let closing = false;
83
90
  let activeTurn = null;
91
+ let heartbeatTimer = null;
84
92
  let readyResolve;
85
93
  let readyReject;
86
94
  const readyPromise = new Promise((resolve, reject) => {
@@ -89,6 +97,8 @@ export async function connectBackgroundAgentKeeper(options = {}) {
89
97
  });
90
98
 
91
99
  const failPending = (error) => {
100
+ clearInterval(heartbeatTimer);
101
+ heartbeatTimer = null;
92
102
  readyReject(error);
93
103
  for (const entry of pending.values()) {
94
104
  clearTimeout(entry.timer);
@@ -171,6 +181,23 @@ export async function connectBackgroundAgentKeeper(options = {}) {
171
181
  );
172
182
  helloTimer.unref?.();
173
183
  await readyPromise.finally(() => clearTimeout(helloTimer));
184
+ const sendHeartbeat = () => {
185
+ try {
186
+ writeMessage(
187
+ socket,
188
+ createBackgroundAgentKeeperMessage(BACKGROUND_AGENT_KEEPER_HEARTBEAT, {
189
+ id: hello.id,
190
+ workerGeneration: hello.workerGeneration,
191
+ workerPid: hello.workerPid,
192
+ }),
193
+ );
194
+ } catch (error) {
195
+ socket.destroy(error);
196
+ }
197
+ };
198
+ sendHeartbeat();
199
+ heartbeatTimer = setInterval(sendHeartbeat, heartbeatIntervalMs);
200
+ heartbeatTimer.unref?.();
174
201
 
175
202
  const request = (
176
203
  type,
@@ -241,6 +268,8 @@ export async function connectBackgroundAgentKeeper(options = {}) {
241
268
  close() {
242
269
  if (closing) return;
243
270
  closing = true;
271
+ clearInterval(heartbeatTimer);
272
+ heartbeatTimer = null;
244
273
  socket.end();
245
274
  },
246
275
  activeTurn: () => activeTurn,
@@ -1,15 +1,33 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { lstatSync, mkdirSync, rmdirSync } from "node:fs";
3
- import { posix } from "node:path";
3
+ import { join, posix, resolve } from "node:path";
4
4
 
5
5
  export const BACKGROUND_AGENT_KEEPER_PROTOCOL_VERSION = 1;
6
6
  export const BACKGROUND_AGENT_KEEPER_HELLO = "background-agent-keeper-hello";
7
7
  export const BACKGROUND_AGENT_KEEPER_READY = "background-agent-keeper-ready";
8
+ export const BACKGROUND_AGENT_KEEPER_HEARTBEAT =
9
+ "background-agent-keeper-heartbeat";
8
10
  export const BACKGROUND_AGENT_KEEPER_ARM = "background-agent-keeper-arm";
9
11
  export const BACKGROUND_AGENT_KEEPER_ARMED = "background-agent-keeper-armed";
10
12
  export const BACKGROUND_AGENT_KEEPER_RETIRE = "background-agent-keeper-retire";
11
13
  export const BACKGROUND_AGENT_KEEPER_RETIRED =
12
14
  "background-agent-keeper-retired";
15
+ export const BACKGROUND_AGENT_KEEPER_LAUNCH_CLAIM =
16
+ "background-agent-keeper-launch-claim";
17
+ export const BACKGROUND_AGENT_KEEPER_HEARTBEAT_INTERVAL_MS = 1_000;
18
+ // Do not launch an OS process-identity probe while authenticated application
19
+ // heartbeats are fresh. On Windows the cached WMIC/CIM fallback can otherwise
20
+ // make 20 keepers synchronously spawn probe helpers together every ten seconds.
21
+ export const BACKGROUND_AGENT_KEEPER_IDENTITY_PROBE_DELAY_MS = 15_000;
22
+ // This application-level fence is a backstop for the rare case where Windows
23
+ // retains both the named-pipe handle and an exact process object after worker
24
+ // death. Normal death still closes the socket immediately; a stale channel
25
+ // gets the delayed PID/start-time probe before this final fence. A 20-worker
26
+ // hosted Windows launch has demonstrated
27
+ // 37-second scheduling tails, so 15 seconds could kill a live, healthy worker
28
+ // while it was merely descheduled. Keep the backstop bounded but above that
29
+ // measured contention window.
30
+ export const BACKGROUND_AGENT_KEEPER_HEARTBEAT_TIMEOUT_MS = 60_000;
13
31
 
14
32
  // RETIRE is the only keeper request that performs destructive OS cleanup. Its
15
33
  // deadline is deliberately independent from the short HELLO/ARM request
@@ -18,7 +36,7 @@ export const BACKGROUND_AGENT_KEEPER_RETIRED =
18
36
  //
19
37
  // 2 targets * (strict identity + taskkill + cleanup confirmation identity) +
20
38
  // 2 cleanup-critical persistence retry windows + cleanup confirmation +
21
- // scheduling margin = 120 seconds.
39
+ // scheduling margin = 128 seconds.
22
40
  //
23
41
  // Keeping these limits in the shared protocol contract prevents either side
24
42
  // from silently reintroducing the old 10-second client / longer keeper race.
@@ -32,7 +50,12 @@ export const BACKGROUND_AGENT_KEEPER_IDENTITY_PROBE_TIMEOUT_MS =
32
50
  export const BACKGROUND_AGENT_KEEPER_STATE_LOCK_TIMEOUT_MS = 5_000;
33
51
  export const BACKGROUND_AGENT_KEEPER_PERSIST_RETRY_TIMEOUT_MS =
34
52
  3 * BACKGROUND_AGENT_KEEPER_STATE_LOCK_TIMEOUT_MS;
35
- export const BACKGROUND_AGENT_KEEPER_CLEANUP_CONFIRM_TIMEOUT_MS = 2_000;
53
+ // A 20-Agent macOS formal run can leave successfully SIGKILLed process groups
54
+ // observable for several seconds while launchd reaps their orphaned leaders.
55
+ // Two seconds made that bounded kernel/reaper delay look like an escaped tree.
56
+ // Ten seconds stays inside the formal observer's 150-second budget while
57
+ // retaining a finite fail-closed confirmation window.
58
+ export const BACKGROUND_AGENT_KEEPER_CLEANUP_CONFIRM_TIMEOUT_MS = 10_000;
36
59
  export const BACKGROUND_AGENT_KEEPER_RETIRE_TIMEOUT_MARGIN_MS = 8_000;
37
60
  export const BACKGROUND_AGENT_KEEPER_RETIRE_TIMEOUT_MS =
38
61
  BACKGROUND_AGENT_KEEPER_CLEANUP_TARGET_LIMIT *
@@ -88,6 +111,58 @@ function requiredTimestamp(value, label) {
88
111
  return normalized;
89
112
  }
90
113
 
114
+ export function backgroundAgentKeeperLaunchClaimPath(
115
+ id,
116
+ keeperGeneration,
117
+ directory,
118
+ ) {
119
+ const safeId = requiredSafeString(id, "id");
120
+ const safeGeneration = requiredSafeString(
121
+ keeperGeneration,
122
+ "keeper generation",
123
+ );
124
+ const requestedDirectory =
125
+ typeof directory === "string" ? directory.trim() : "";
126
+ if (!requestedDirectory || requestedDirectory.includes("\0")) {
127
+ throw new TypeError("invalid background agent keeper state directory");
128
+ }
129
+ const stateDirectory = resolve(requestedDirectory);
130
+ return join(
131
+ stateDirectory,
132
+ `.${safeId}.keeper-${safeGeneration}.launch-claim.json`,
133
+ );
134
+ }
135
+
136
+ export function createBackgroundAgentKeeperLaunchClaim(binding = {}) {
137
+ return Object.freeze({
138
+ type: BACKGROUND_AGENT_KEEPER_LAUNCH_CLAIM,
139
+ protocolVersion: BACKGROUND_AGENT_KEEPER_PROTOCOL_VERSION,
140
+ id: requiredSafeString(binding.id, "id"),
141
+ workerGeneration: requiredSafeString(
142
+ binding.workerGeneration,
143
+ "worker generation",
144
+ ),
145
+ keeperGeneration: requiredSafeString(
146
+ binding.keeperGeneration,
147
+ "keeper generation",
148
+ ),
149
+ keeperPid: requiredPositiveInteger(binding.keeperPid, "keeper pid"),
150
+ token: requiredSafeString(binding.token, "token", SAFE_TOKEN),
151
+ });
152
+ }
153
+
154
+ export function matchesBackgroundAgentKeeperLaunchClaim(value, expected = {}) {
155
+ let actual;
156
+ let binding;
157
+ try {
158
+ actual = createBackgroundAgentKeeperLaunchClaim(value);
159
+ binding = createBackgroundAgentKeeperLaunchClaim(expected);
160
+ } catch {
161
+ return false;
162
+ }
163
+ return Object.keys(binding).every((key) => actual[key] === binding[key]);
164
+ }
165
+
91
166
  function digestKeeperPath(value, length) {
92
167
  return createHash("sha256").update(value).digest("hex").slice(0, length);
93
168
  }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Read-only startup barrier shared by the detached background worker and its
3
+ * keeper. The launcher publishes both exact child PIDs in one state
4
+ * transaction before clearing `launchFinalizationUncertain`. Until then the
5
+ * children must not contend for the same state lock.
6
+ */
7
+
8
+ export const BACKGROUND_AGENT_LAUNCH_BARRIER_TIMEOUT_MS = 30_000;
9
+
10
+ const DEFAULT_POLL_MS = 25;
11
+ const PID_FIELDS = new Set(["workerPid", "keeperPid"]);
12
+
13
+ function delay(milliseconds) {
14
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
15
+ }
16
+
17
+ export async function waitForBackgroundAgentLaunchBarrier(options = {}) {
18
+ const id = String(options.id || "").trim();
19
+ const workerGeneration = String(options.workerGeneration || "").trim();
20
+ const expectedPid = Number(options.expectedPid);
21
+ const pidField = String(options.pidField || "");
22
+ const readState = options.readState;
23
+ const readyWhen = options.readyWhen;
24
+ const now = options.now || Date.now;
25
+ const sleep = options.sleep || delay;
26
+ const timeoutMs = Math.max(
27
+ 1,
28
+ Number(options.timeoutMs) || BACKGROUND_AGENT_LAUNCH_BARRIER_TIMEOUT_MS,
29
+ );
30
+ const pollMs = Math.max(1, Number(options.pollMs) || DEFAULT_POLL_MS);
31
+
32
+ if (!id) {
33
+ throw new TypeError("background launch barrier id is required");
34
+ }
35
+ if (!PID_FIELDS.has(pidField)) {
36
+ throw new TypeError("background launch barrier pid field is invalid");
37
+ }
38
+ if (!Number.isSafeInteger(expectedPid) || expectedPid <= 0) {
39
+ throw new TypeError("background launch barrier pid is invalid");
40
+ }
41
+ if (typeof readState !== "function") {
42
+ throw new TypeError("background launch barrier state reader is required");
43
+ }
44
+ if (readyWhen !== undefined && typeof readyWhen !== "function") {
45
+ throw new TypeError("background launch barrier readiness check is invalid");
46
+ }
47
+
48
+ const deadline = now() + timeoutMs;
49
+ for (;;) {
50
+ const state = readState(id);
51
+ if (!state || state.status !== "running") {
52
+ return Object.freeze({ status: "terminal", state: state || null });
53
+ }
54
+ // Jobs written before generation fencing are allowed to observe an
55
+ // already-terminal record above, but can never enter a running record.
56
+ if (!workerGeneration || state.workerGeneration !== workerGeneration) {
57
+ return Object.freeze({ status: "terminal", state });
58
+ }
59
+ if (state.launchFinalizationUncertain !== true) {
60
+ if (Number(state[pidField]) !== expectedPid) {
61
+ return Object.freeze({ status: "identity-mismatch", state });
62
+ }
63
+ if (!readyWhen || readyWhen(state)) {
64
+ return Object.freeze({ status: "ready", state });
65
+ }
66
+ }
67
+
68
+ const remaining = deadline - now();
69
+ if (remaining <= 0) {
70
+ return Object.freeze({ status: "timeout", state });
71
+ }
72
+ await sleep(Math.min(pollMs, remaining));
73
+ }
74
+ }
@@ -79,6 +79,110 @@ export const PID_IDENTITY_TOLERANCE_MS = 60000;
79
79
  export const PROCESS_START_TIME_CACHE_TTL_MS = 10_000;
80
80
  export const PROCESS_START_TIME_CACHE_MAX_ENTRIES = 4_096;
81
81
 
82
+ // Linux exposes these fields directly through procfs. `/proc/<pid>/stat`
83
+ // reports starttime in USER_HZ units, which Linux defines as 100 ticks/second
84
+ // for this userspace ABI independently of the kernel scheduler frequency.
85
+ const LINUX_PROC_CLOCK_TICKS_PER_SECOND = 100;
86
+ let linuxBootTimeMsCache;
87
+
88
+ export function parseLinuxProcStat(text, bootTimeMs = null) {
89
+ const value = String(text || "").trim();
90
+ const open = value.indexOf("(");
91
+ const close = value.lastIndexOf(")");
92
+ if (open <= 0 || close <= open || value.charAt(close + 1) !== " ") {
93
+ return null;
94
+ }
95
+ const pid = Number(value.slice(0, open).trim());
96
+ const fields = value
97
+ .slice(close + 2)
98
+ .trim()
99
+ .split(/\s+/u);
100
+ const state = fields[0];
101
+ const processGroupId = Number(fields[2]);
102
+ const startTimeTicks = Number(fields[19]);
103
+ if (
104
+ !Number.isSafeInteger(pid) ||
105
+ pid <= 0 ||
106
+ !/^[A-Z]$/iu.test(state || "") ||
107
+ !Number.isSafeInteger(processGroupId) ||
108
+ processGroupId <= 0 ||
109
+ !Number.isFinite(startTimeTicks) ||
110
+ startTimeTicks < 0
111
+ ) {
112
+ return null;
113
+ }
114
+ const boot = Number(bootTimeMs);
115
+ return {
116
+ pid,
117
+ state: state.toUpperCase(),
118
+ processGroupId,
119
+ startTimeTicks,
120
+ startedAtMs:
121
+ Number.isFinite(boot) && boot > 0
122
+ ? boot + (startTimeTicks * 1000) / LINUX_PROC_CLOCK_TICKS_PER_SECOND
123
+ : null,
124
+ };
125
+ }
126
+
127
+ function linuxBootTimeMs() {
128
+ if (linuxBootTimeMsCache !== undefined) return linuxBootTimeMsCache;
129
+ try {
130
+ const match = /^btime\s+(\d+)\s*$/mu.exec(
131
+ readFileSync("/proc/stat", "utf8"),
132
+ );
133
+ const seconds = match ? Number(match[1]) : null;
134
+ linuxBootTimeMsCache =
135
+ Number.isFinite(seconds) && seconds > 0 ? seconds * 1000 : null;
136
+ } catch {
137
+ linuxBootTimeMsCache = null;
138
+ }
139
+ return linuxBootTimeMsCache;
140
+ }
141
+
142
+ function readLinuxProcProcess(pid) {
143
+ const target = Number(pid);
144
+ if (!Number.isSafeInteger(target) || target <= 0) {
145
+ return { status: "unknown", process: null };
146
+ }
147
+ try {
148
+ const parsed = parseLinuxProcStat(
149
+ readFileSync(`/proc/${target}/stat`, "utf8"),
150
+ linuxBootTimeMs(),
151
+ );
152
+ return parsed
153
+ ? { status: "present", process: parsed }
154
+ : { status: "unknown", process: null };
155
+ } catch (error) {
156
+ return ["ENOENT", "ESRCH"].includes(error?.code)
157
+ ? { status: "absent", process: null }
158
+ : { status: "unknown", process: null };
159
+ }
160
+ }
161
+
162
+ function readLinuxProcProcessGroupStates(pgid) {
163
+ const target = Number(pgid);
164
+ if (!Number.isSafeInteger(target) || target <= 0) return null;
165
+ let entries;
166
+ try {
167
+ entries = readdirSync("/proc");
168
+ } catch {
169
+ return null;
170
+ }
171
+ const states = [];
172
+ for (const entry of entries) {
173
+ if (!/^\d+$/u.test(entry)) continue;
174
+ const observed = readLinuxProcProcess(Number(entry));
175
+ if (observed.status === "unknown") return null;
176
+ if (
177
+ observed.status === "present" &&
178
+ observed.process.processGroupId === target
179
+ ) {
180
+ states.push(observed.process.state);
181
+ }
182
+ }
183
+ return states;
184
+ }
185
+
82
186
  /**
83
187
  * A bounded FIFO/TTL cache for process creation-time probes.
84
188
  *
@@ -263,6 +367,13 @@ function defaultReadProcessStartTimeMs(pid) {
263
367
  }
264
368
  return null;
265
369
  }
370
+ if (process.platform === "linux") {
371
+ const observed = readLinuxProcProcess(target);
372
+ if (observed.status === "absent") return { status: "absent" };
373
+ if (Number.isFinite(observed.process?.startedAtMs)) {
374
+ return observed.process.startedAtMs;
375
+ }
376
+ }
266
377
  // POSIX: `ps -o lstart=` gives an absolute start timestamp on Linux + macOS
267
378
  // (procps and BSD ps both support it; busybox ps does not → null → open).
268
379
  try {
@@ -300,6 +411,12 @@ function defaultReadProcessState(pid) {
300
411
  ) {
301
412
  return null;
302
413
  }
414
+ if (process.platform === "linux") {
415
+ const observed = readLinuxProcProcess(target);
416
+ if (observed.status !== "unknown") {
417
+ return observed.process?.state || null;
418
+ }
419
+ }
303
420
  try {
304
421
  const result = runSupervisorCommand(
305
422
  "ps",
@@ -335,6 +452,10 @@ function defaultReadProcessGroupStates(pgid) {
335
452
  ) {
336
453
  return null;
337
454
  }
455
+ if (process.platform === "linux") {
456
+ const states = readLinuxProcProcessGroupStates(target);
457
+ if (Array.isArray(states)) return states;
458
+ }
338
459
  try {
339
460
  const result = runSupervisorCommand(
340
461
  "ps",
@@ -1128,7 +1249,7 @@ function mergeBackgroundAgentState(current, requested) {
1128
1249
  return next;
1129
1250
  }
1130
1251
 
1131
- function persistBackgroundAgentState(target, next) {
1252
+ function persistBackgroundAgentState(target, next, options = {}) {
1132
1253
  // Multiple async state writers can run in one process; a pid-only temporary
1133
1254
  // name lets them overwrite each other's staging file. A unique suffix keeps
1134
1255
  // each atomic replacement independent. Windows scanners/readers can also
@@ -1136,6 +1257,21 @@ function persistBackgroundAgentState(target, next) {
1136
1257
  // transient class rather than surfacing a spurious EPERM background failure.
1137
1258
  const tmp = `${target}.${process.pid}.${randomBytes(4).toString("hex")}.tmp`;
1138
1259
  writeFileSync(tmp, JSON.stringify(next, null, 2), { mode: 0o600 });
1260
+ try {
1261
+ if (
1262
+ typeof options.publishReleaseAfterPathRemoved === "function" &&
1263
+ options.publishReleaseAfterPathRemoved(tmp) !== true
1264
+ ) {
1265
+ const error = new Error(
1266
+ `Could not publish state-lock release handoff: ${target}`,
1267
+ );
1268
+ error.code = "STATE_LOCK_RELEASE_HANDOFF_FAILED";
1269
+ throw error;
1270
+ }
1271
+ } catch (error) {
1272
+ rmSync(tmp, { force: true });
1273
+ throw error;
1274
+ }
1139
1275
  for (let attempt = 0; ; attempt++) {
1140
1276
  try {
1141
1277
  renameSync(tmp, target);
@@ -1179,7 +1315,7 @@ export function mutateBackgroundAgentState(id, updater, options = {}) {
1179
1315
  const target = statePath(safe);
1180
1316
  return withFileLock(
1181
1317
  target,
1182
- () => {
1318
+ ({ publishReleaseAfterPathRemoved }) => {
1183
1319
  const read = readBackgroundAgentStateResult(safe);
1184
1320
  if (read.error) throw read.error;
1185
1321
  if (read.missing && options.createIfMissing !== true) {
@@ -1193,7 +1329,9 @@ export function mutateBackgroundAgentState(id, updater, options = {}) {
1193
1329
  ...requested,
1194
1330
  id: safe,
1195
1331
  });
1196
- persistBackgroundAgentState(target, next);
1332
+ persistBackgroundAgentState(target, next, {
1333
+ publishReleaseAfterPathRemoved,
1334
+ });
1197
1335
  return { applied: true, state: next, previous: read.state };
1198
1336
  },
1199
1337
  {
@@ -2976,8 +3114,14 @@ function recordedBackgroundProcessesAlive(state) {
2976
3114
  );
2977
3115
  }
2978
3116
 
2979
- export function isBackgroundProcessTreeExecutionAlive(pid, startedAt) {
2980
- const identity = inspectDestructiveProcessIdentity(pid, startedAt);
3117
+ export function isBackgroundProcessTreeExecutionAlive(
3118
+ pid,
3119
+ startedAt,
3120
+ options = {},
3121
+ ) {
3122
+ const identity = inspectDestructiveProcessIdentity(pid, startedAt, {
3123
+ processGroup: options.processGroup !== false,
3124
+ });
2981
3125
  // `unverifiable` includes a vanished/zombie POSIX leader whose process group
2982
3126
  // is still executable or whose group snapshot failed. Both must block
2983
3127
  // worktree deletion just as a verified live leader does.
@@ -3051,22 +3195,10 @@ function isDestructiveIdentityRetired(identity) {
3051
3195
  return ["dead", "reused"].includes(identity?.status);
3052
3196
  }
3053
3197
 
3054
- function isRecoverableCreationTimeIdentityProbe(identity) {
3055
- // A one-shot `ps -o lstart=` failure is observational: it says nothing about
3056
- // whether the exact target is still present. Other unverifiable outcomes can
3057
- // mean a missing durable anchor, a vanished group owner, PID reuse with a
3058
- // surviving group, or an untrusted group snapshot; those remain immediately
3059
- // fail-closed rather than being promoted into the resend path.
3060
- return (
3061
- identity?.status === "unverifiable" &&
3062
- identity?.reason === "creation-time-probe-failed"
3063
- );
3064
- }
3065
-
3066
- function isRecoverableInitialDestructiveIdentityProbe(identity) {
3198
+ function isRecoverableDestructiveIdentityProbe(identity) {
3067
3199
  // These outcomes come only from read-only OS observations. Retrying them
3068
3200
  // cannot address a process, and the caller still requires two consecutive
3069
- // fresh exact matches before it may issue the first signal.
3201
+ // fresh exact matches before it may issue or reissue a signal.
3070
3202
  return (
3071
3203
  identity?.status === "unverifiable" &&
3072
3204
  ["creation-time-probe-failed", "process-group-state-probe-failed"].includes(
@@ -3137,7 +3269,7 @@ function retryRecoverableInitialPosixIdentity({
3137
3269
  }
3138
3270
  if (
3139
3271
  lastIdentity?.status !== "match" &&
3140
- !isRecoverableInitialDestructiveIdentityProbe(lastIdentity)
3272
+ !isRecoverableDestructiveIdentityProbe(lastIdentity)
3141
3273
  ) {
3142
3274
  return outcome("failed", "preflight-identity");
3143
3275
  }
@@ -3154,7 +3286,7 @@ function retryRecoverableInitialPosixIdentity({
3154
3286
  }
3155
3287
  if (
3156
3288
  lastIdentity?.status !== "match" &&
3157
- !isRecoverableInitialDestructiveIdentityProbe(lastIdentity)
3289
+ !isRecoverableDestructiveIdentityProbe(lastIdentity)
3158
3290
  ) {
3159
3291
  return outcome("failed", "preflight-identity");
3160
3292
  }
@@ -3172,8 +3304,9 @@ function retryRecoverableInitialPosixIdentity({
3172
3304
 
3173
3305
  /**
3174
3306
  * Recover from a transient POSIX EPERM without ever signalling from unknown
3175
- * identity evidence. Probe-only waits may bridge a transient creation-time
3176
- * lookup failure, but a resend requires two consecutive fresh exact matches.
3307
+ * identity evidence. Probe-only waits may bridge a transient creation-time or
3308
+ * process-group-state lookup failure, but a resend requires two consecutive
3309
+ * fresh exact matches.
3177
3310
  */
3178
3311
  function retryExactPosixSignalAfterPermissionError({
3179
3312
  inspectIdentity,
@@ -3203,7 +3336,7 @@ function retryExactPosixSignalAfterPermissionError({
3203
3336
  }
3204
3337
  if (
3205
3338
  lastIdentity?.status !== "match" &&
3206
- !isRecoverableCreationTimeIdentityProbe(lastIdentity)
3339
+ !isRecoverableDestructiveIdentityProbe(lastIdentity)
3207
3340
  ) {
3208
3341
  return outcome("failed", "identity");
3209
3342
  }
@@ -3226,7 +3359,7 @@ function retryExactPosixSignalAfterPermissionError({
3226
3359
  }
3227
3360
  if (
3228
3361
  lastIdentity?.status !== "match" &&
3229
- !isRecoverableCreationTimeIdentityProbe(lastIdentity)
3362
+ !isRecoverableDestructiveIdentityProbe(lastIdentity)
3230
3363
  ) {
3231
3364
  return outcome("failed", "identity");
3232
3365
  }
@@ -3306,7 +3439,7 @@ function signalExactBackgroundProcessTree(
3306
3439
  if (lastIdentity.status !== "match") {
3307
3440
  if (
3308
3441
  platform === "win32" ||
3309
- !isRecoverableInitialDestructiveIdentityProbe(lastIdentity)
3442
+ !isRecoverableDestructiveIdentityProbe(lastIdentity)
3310
3443
  ) {
3311
3444
  retryStoppedBy = "preflight-identity";
3312
3445
  throw failure(
@@ -3663,7 +3796,7 @@ export function stopBackgroundAgent(id) {
3663
3796
  identityProbeRetries: 0,
3664
3797
  };
3665
3798
  try {
3666
- const preflightIdentity = inspectIdentity();
3799
+ let preflightIdentity = inspectIdentity();
3667
3800
  activeTerminationContext.lastIdentityStatus =
3668
3801
  preflightIdentity.status;
3669
3802
  activeTerminationContext.lastIdentityReason =
@@ -3681,14 +3814,55 @@ export function stopBackgroundAgent(id) {
3681
3814
  continue;
3682
3815
  }
3683
3816
  if (preflightIdentity.status !== "match") {
3684
- activeTerminationContext.retryStoppedBy = "preflight-identity";
3685
- throw new Error(
3686
- `destructive process identity is ${preflightIdentity.status}: ${preflightIdentity.reason || "unknown"}`,
3687
- );
3817
+ if (
3818
+ process.platform === "win32" ||
3819
+ !isRecoverableDestructiveIdentityProbe(preflightIdentity)
3820
+ ) {
3821
+ activeTerminationContext.retryStoppedBy = "preflight-identity";
3822
+ throw new Error(
3823
+ `destructive process identity is ${preflightIdentity.status}: ${preflightIdentity.reason || "unknown"}`,
3824
+ );
3825
+ }
3826
+ permissionRetryDeadline =
3827
+ _deps.posixPermissionRetryNowMs() +
3828
+ Math.max(0, Number(_deps.processExitWaitDeadlineMs) || 0);
3829
+ const retry = retryRecoverableInitialPosixIdentity({
3830
+ inspectIdentity,
3831
+ initialIdentity: preflightIdentity,
3832
+ deadline: permissionRetryDeadline,
3833
+ });
3834
+ activeTerminationContext.identityProbeRetries +=
3835
+ retry.identityProbeRetries;
3836
+ preflightIdentity = retry.lastIdentity;
3837
+ activeTerminationContext.lastIdentityStatus =
3838
+ preflightIdentity?.status;
3839
+ activeTerminationContext.lastIdentityReason =
3840
+ preflightIdentity?.reason || null;
3841
+ activeTerminationContext.retryStoppedBy = retry.retryStoppedBy;
3842
+ if (retry.status === "retired") {
3843
+ if (
3844
+ preflightIdentity?.status === "reused" &&
3845
+ primaryWorker?.pid === target.pid &&
3846
+ !signalledPids.has(target.pid)
3847
+ ) {
3848
+ workerPidReused = true;
3849
+ }
3850
+ target.retiredBeforeWait = true;
3851
+ signalledPids.add(target.pid);
3852
+ continue;
3853
+ }
3854
+ if (retry.status !== "matched") {
3855
+ throw new Error(
3856
+ `destructive process identity is ${preflightIdentity?.status || "unknown"}: ${preflightIdentity?.reason || "unknown"}`,
3857
+ );
3858
+ }
3859
+ }
3860
+ if (!(permissionRetryDeadline > 0)) {
3861
+ permissionRetryDeadline =
3862
+ _deps.posixPermissionRetryNowMs() +
3863
+ Math.max(0, Number(_deps.processExitWaitDeadlineMs) || 0);
3688
3864
  }
3689
- permissionRetryDeadline =
3690
- _deps.posixPermissionRetryNowMs() +
3691
- Math.max(0, Number(_deps.processExitWaitDeadlineMs) || 0);
3865
+ activeTerminationContext.retryStoppedBy = "initial-signal";
3692
3866
  target.signalledAsGroup = sendInitialSignal() === "process-group";
3693
3867
  signalledPids.add(target.pid);
3694
3868
  } catch (error) {
@@ -11,6 +11,10 @@ export const BACKGROUND_TURN_BOOTSTRAP_READY =
11
11
  "background-turn-bootstrap-ready";
12
12
  export const BACKGROUND_TURN_BOOTSTRAP_RELEASE =
13
13
  "background-turn-bootstrap-release";
14
+ // The formal launch gate permits 120 seconds for a 20-worker hosted runner to
15
+ // become ready. Keep the pre-main barrier alive for that same bounded window;
16
+ // worker/IPC death still fails immediately through the disconnect listener.
17
+ export const BACKGROUND_TURN_BOOTSTRAP_RELEASE_TIMEOUT_MS = 120_000;
14
18
 
15
19
  export const BACKGROUND_TURN_BOOTSTRAP_ENV = Object.freeze({
16
20
  nonce: "CC_BACKGROUND_TURN_BOOTSTRAP_NONCE",