chainlesschain 0.165.2 → 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 (166) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-lS1R0Cza.js → AIOps-BiHNUlbU.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BmlgBPsj.js → ActionButton-D-UFjDRQ.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-CNKczv1S.js → Analytics-DazVsDgW.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-BuZGJARQ.js → AppLayout-DXh9UfNJ.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-Dm73Sl-O.js → Artifacts-CZ6tsceY.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-BGMW7A5e.js → Audit-RyxnBr_h.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-BQ6M54JR.js → BackgroundAgents-BWS51Q6X.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-DBzGP0bR.js → Backup-BRtPcUul.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-D1mLCoOV.js → BaseInput-CGd4vZYY.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-BUfcN7p8.js → Chat-h8ijVwCg.js} +4 -4
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CY5m8ZHg.js → ChatBubbleRenderer-D7tcdDS9.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-Du0Yg5Pi.js → Checkbox-NyF6dPl8.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-B2RXziHW.js → Codegen-Ci4OOxr5.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-DWhekOrR.js → Col-CwTkc9BM.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-HQhrkon4.js → Community-DW_tM82m.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-izHNb4uz.js → Compact-DEvHRV4S.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-BDwLy41-.js → Compliance-BbjBEEfD.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-DFJhLa7K.js → Cowork-10IXBaBa.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-DYY9RG7-.js → Cron-BZkbpd5I.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-rr7tJTtv.js → Crosschain-CFQ8L_6v.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-D2sfHRLi.js → DID-BDF6AkXe.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-q_eGdhha.js → Dashboard-CoOdWMi4.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-Cufrh2Bz.js → Dropdown-DEsGmu8Y.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-B-kdv10f.js → EmailListRenderer-BS89SWlK.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-D21XjgfG.js → FamilyGuardDashboard-C1FpCHiI.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-CAwRTLZH.js → Federation-BzvpVmLA.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-vx3daNDF.js → FormItemContext-BeC259PQ.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-CRWY2wU4.js → GenericCardRenderer-azgG3bmV.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-Q6SwauyD.js → Git-BmSSowc5.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-BJ1q4IQZ.js → Governance-CTBCSyQZ.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CSWjROgD.js → Inference-DA5xyk4a.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-fzuQsWuY.js → KnowledgeGraph-D1gPHNig.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-DVF8Mnns.js → Logs-BPmLf1AR.js} +2 -2
  35. package/src/assets/web-panel/assets/MarkdownRenderer-SVLOg49H.js +1 -0
  36. package/src/assets/web-panel/assets/{Marketplace-BQP7LOA4.js → Marketplace-DtUFcR0E.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-BNlJCmNc.js → McpTools-DS_kqBv-.js} +4 -4
  38. package/src/assets/web-panel/assets/{Memory-DLLg5WQ4.js → Memory-Fvh7XL7V.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-CR2skNAF.js → MobileBridge-C7itAP45.js} +1 -1
  40. package/src/assets/web-panel/assets/MobileProjects-DGbP36-3.js +1 -0
  41. package/src/assets/web-panel/assets/{Mtc-B17pgqMI.js → Mtc-B57MiNLK.js} +6 -6
  42. package/src/assets/web-panel/assets/{MtcAudit-4UmV5cIL.js → MtcAudit-BXfCUcM7.js} +4 -4
  43. package/src/assets/web-panel/assets/{Multisig-DoMEXEcU.js → Multisig-yaJ-ZcF6.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-h9DMXFX4.js → NLProgramming-CoaUCKrO.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-Cihn-xX8.js → Notes-BKLM_65R.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-BE6ZGa3R.js → NotificationSettings-DoDtK9uZ.js} +1 -1
  47. package/src/assets/web-panel/assets/OrderTableRenderer-BJ28TGr-.js +1 -0
  48. package/src/assets/web-panel/assets/{Organization-B2vpwmjb.js → Organization-MuGsdoIb.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-C4z28R0-.js → Overflow-BLUN9J4R.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-BVlFkwhW.js → P2P-DWuoBUPd.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-BXbEdL5B.js → PdhVaultBrowser-CUi37YPj.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-C28xs4lp.js → Permissions-BYQg2pcr.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-Dgl_rBjM.js → PersonalDataHub-CAxoV2xu.js} +6 -6
  54. package/src/assets/web-panel/assets/{Pipeline-3QUIhrKI.js → Pipeline-B33wGvIO.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-8Gh33OZ5.js → Privacy-A8yXTORP.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-BNmg4tZi.js → ProjectInit-D49I2I1O.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-vgMfeCf9.js → ProjectSettings-Ci73jJDw.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-BhtAVilc.js → Projects-CF-yD6Cn.js} +1 -1
  59. package/src/assets/web-panel/assets/Providers-D-etEhg5.js +1 -0
  60. package/src/assets/web-panel/assets/{QrScannerModal-BhfTF0oS.js → QrScannerModal-Brza0ykJ.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-C746HEAj.js → QuickAsk-DFydhjxL.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-BSej8Eo0.js → Recommend-DLF8_NhA.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-f3gmnlG3.js → RemoteSession-dyuStW3x.js} +3 -3
  64. package/src/assets/web-panel/assets/{Reputation-CyvybpkN.js → Reputation-5F3tA6yw.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-DwV2PeYn.js → Row-Ca2A57S5.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-11GrzjZy.js → RssFeed-B0bxBkq_.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-BjKXkrAY.js → Search-CFNvHalZ.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-eWiFb9Yk.js → Security-DSoH3Hav.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-CdCsMAtt.js → Services-BHpfEmBQ.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-4Q2UTKQ5.js → Skeleton-Dsh-QNmn.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-v4rtYrRj.js → Skills-BhT1NkSU.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-CRhsFvd_.js → Sla-DY9snPeJ.js} +1 -1
  73. package/src/assets/web-panel/assets/SpeechSettings-CWaX0_Cx.js +1 -0
  74. package/src/assets/web-panel/assets/{SyncSettings-D16zonl7.js → SyncSettings-B690y_TC.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-Xxozz0i_.js → Tasks-2N-w3zWd.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-CXJpPtCT.js → Templates-BjwQOQYC.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-XRV0L1ZF.js → Tenant-CUqHErxY.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-CYuO1i_w.js → Terminal-C13rXvNy.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-BXqitWsu.js → TimelineRenderer-CpbWcya_.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-BvPrDHIy.js → Tokens-BXHLGXYC.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-DhCrSTf5.js → Trigger-1e5i65o3.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-DmsuZD6K.js → Trust-FLGqNHs5.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-D_yrts5C.js → UkeySign-BbaR_LJB.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-B01ihPrm.js → VideoEditing-NOp4RIov.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-DpUjKa-N.js → Wallet-B7DW7bG4.js} +3 -3
  86. package/src/assets/web-panel/assets/{WebAuthn-CL07yON6.js → WebAuthn-BptSUWix.js} +5 -5
  87. package/src/assets/web-panel/assets/{WorkflowEditor-CLMflnRr.js → WorkflowEditor-CAJnDOXS.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-DtUqe8h_.js → chat-I8LgG1-Z.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-DPKrgVfy.js → colors-CWe7SOVD.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-BTt2GtOm.js → compact-item-Cx3u3CA3.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-D_VeUc1J.js → createContext-CgD5ArwY.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-DLv_872g.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-1kndiylj.js → hasIn-BfQWiXN8.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-brCInrzq.js → index-3iKP9OTJ.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-l1BETrDb.js → index-87gMxqhN.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DxdTdBix.js → index-B1cX4Rxu.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DdkplL9R.js → index-B6yChzt1.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BHpBMWCh.js → index-BREnPKGo.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CzjkKUmR.js → index-BUAR7RY-.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-D95OFht7.js → index-Bi24S7Dw.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-8nz_fmlj.js → index-BiI-xPRH.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-2eyAXE3d.js → index-BuEQSorC.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-GsoUQ2M7.js → index-Bx7BNIRH.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CX81qSM4.js → index-C-rIKccl.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CcO04eDn.js → index-C0nDKhWv.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Bhudc_Ez.js → index-C0ztiqtB.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DfGl8CqG.js → index-C1UjHdgZ.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BaZAbw9A.js → index-CAUnl4Jv.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-B9KvcS7R.js → index-CMu16vPC.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-m31Ka8hZ.js → index-CNYFQxAW.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Dwqmtk65.js → index-CQDKYbnv.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-fk2Rjacw.js → index-CWZ3REg6.js} +3 -3
  113. package/src/assets/web-panel/assets/{index-DsJCDUJG.js → index-CcX4FSPf.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BAXfRKfG.js → index-Cd79NQAx.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BlDNVgBt.js → index-CdRkCwm4.js} +1 -1
  116. package/src/assets/web-panel/assets/index-CdiGCibe.js +1 -0
  117. package/src/assets/web-panel/assets/{index-4bna2_SR.js → index-Cy_cCJlr.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-dVc-CXY4.js → index-DAVy98u7.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-ocf06EVQ.js → index-DCJEY2T0.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Dbbymxpj.js → index-DFb-XIKy.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CHG-KcS4.js → index-DGMYTN5W.js} +1 -1
  122. package/src/assets/web-panel/assets/index-D_0z-gyz.js +1 -0
  123. package/src/assets/web-panel/assets/{index-By5n8up9.js → index-Dcdm_Z2c.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-FzdwcSy2.js → index-DeizoQVZ.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DFIZ_1Sf.js → index-DlIyt1qF.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BepLZwgB.js → index-DmgtY58H.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-sDoMHTNA.js → index-DmrdUwzU.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DJ8vXGOa.js → index-Doc0Ftpd.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CauoBQkH.js → index-DsQcURkA.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CtF7EN49.js → index-DslTuqro.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-DfKBRdSS.js → index-V-kIHzAV.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DG2MZQ3f.js → index-e8BM1yY1.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-CvyYJd7z.js → initDefaultProps-DiBszVdG.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-DYZkhUdz.js → motion-BGIFFozv.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-slnJRVT1.js → move-D5W7mp7B.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-DVc0CKmy.js → mtc-parser-JFIlxJ7F.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-BCXAtHCq.js → omit-CQ3o6LIU.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-DBsemz_K.js → pickAttrs-R8MIlXo3.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BRtkJ-nv.js → placementArrow-sW7ylSky.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-Cebq7EZn.js → responsiveObserve-Cnl39Vbz.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-DT7qvId2.js → slide-sn8tFd14.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-pQiITLkG.js → statusUtils-IKKY_QxZ.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-c-19BVKQ.js → styleChecker-BNpV4Nqs.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-DsS5-0bE.js → useFlexGapSupport-BWyT_eoU.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-CxGiMe5n.js → useFs-D09p6_U9.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-D9btVID5.js → usePersonalDataHub-CzW3TkJZ.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-CUYFsFEN.js → vnode-3s3WoAKB.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-CXmn_35j.js → zoom-Cjy_673s.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/data/changelog.json +13 -0
  151. package/src/lib/background-agent-keeper-protocol.js +75 -4
  152. package/src/lib/background-agent-launch-barrier.js +74 -0
  153. package/src/lib/background-agent-supervisor.js +149 -5
  154. package/src/lib/background-turn-bootstrap-protocol.js +4 -0
  155. package/src/lib/with-file-lock.js +175 -26
  156. package/src/workers/background-agent-keeper.js +112 -19
  157. package/src/workers/background-agent-worker.js +383 -134
  158. package/src/workers/background-turn-bootstrap.js +13 -8
  159. package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +0 -1
  160. package/src/assets/web-panel/assets/MobileProjects-BxVavObl.js +0 -1
  161. package/src/assets/web-panel/assets/OrderTableRenderer-CnwojoSI.js +0 -1
  162. package/src/assets/web-panel/assets/Providers-CXoOa8eX.js +0 -1
  163. package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +0 -1
  164. package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +0 -1
  165. package/src/assets/web-panel/assets/index-CPcM-eqo.js +0 -1
  166. package/src/assets/web-panel/assets/index-xeSyVoRe.js +0 -1
@@ -12,7 +12,7 @@
12
12
  * no client is attached.
13
13
  */
14
14
 
15
- import { readFileSync, writeSync } from "node:fs";
15
+ import { readFileSync, unlinkSync, writeSync } from "node:fs";
16
16
  import { randomBytes } from "node:crypto";
17
17
  import { resolve } from "node:path";
18
18
  import { fileURLToPath } from "node:url";
@@ -62,6 +62,13 @@ import {
62
62
  normalizeBackgroundTurnBootstrapBinding,
63
63
  } from "../lib/background-turn-bootstrap-protocol.js";
64
64
  import { connectBackgroundAgentKeeper } from "../lib/background-agent-keeper-client.js";
65
+ import { waitForBackgroundAgentLaunchBarrier } from "../lib/background-agent-launch-barrier.js";
66
+ import {
67
+ backgroundAgentKeeperLaunchClaimPath,
68
+ createBackgroundAgentKeeperLaunchClaim,
69
+ matchesBackgroundAgentKeeperLaunchClaim,
70
+ sameBackgroundAgentKeeperTurn,
71
+ } from "../lib/background-agent-keeper-protocol.js";
65
72
 
66
73
  const TURN_BOOTSTRAP_MODULE_URL = new URL(
67
74
  "./background-turn-bootstrap.js",
@@ -82,6 +89,7 @@ let lastExit = { code: 0, signal: null };
82
89
  let transportState = null;
83
90
  let detachInteractionHandler = null;
84
91
  let detachTurnBootstrapHandler = null;
92
+ let detachTurnTerminationHandler = null;
85
93
  let keeperClient = null;
86
94
  let activeKeeperTurn = null;
87
95
  let interactionJournal = null;
@@ -155,6 +163,43 @@ export function attachTurnChildTerminationSettlement(
155
163
  };
156
164
  }
157
165
 
166
+ /**
167
+ * Accept a keeper cleanup record only when it proves retirement of the exact
168
+ * turn the worker was supervising. A socket close or lost RETIRED response is
169
+ * not evidence by itself: every durable spawn/runtime identity must still
170
+ * match, and the keeper must have persisted successful cleanup after it was
171
+ * requested. Exported for the fail-closed binding contract tests.
172
+ */
173
+ export function hasDurableKeeperTurnRetirement(state, keeperTurn) {
174
+ const cleanupRequestedAt = Number(state?.turnKeeperCleanupRequestedAt);
175
+ const cleanupConfirmedAt = Number(state?.turnKeeperCleanupConfirmedAt);
176
+ return Boolean(
177
+ state &&
178
+ keeperTurn &&
179
+ state.turnKeeperStatus === "retired" &&
180
+ Number.isFinite(cleanupRequestedAt) &&
181
+ cleanupRequestedAt > 0 &&
182
+ Number.isFinite(cleanupConfirmedAt) &&
183
+ cleanupConfirmedAt >= cleanupRequestedAt &&
184
+ !state.turnKeeperCleanupError &&
185
+ state.turnLaunchFinalizationUncertain !== true &&
186
+ sameBackgroundAgentKeeperTurn(
187
+ {
188
+ id: state.id,
189
+ workerGeneration: state.workerGeneration,
190
+ turnLaunchToken: state.turnLaunchResolution?.token,
191
+ attempt: state.turnLaunchResolution?.attempt,
192
+ agentPid: state.agentPid,
193
+ agentStartedAt: state.agentStartedAt,
194
+ agentRuntimePid: state.agentRuntimePid,
195
+ agentRuntimeStartedAt: state.agentRuntimeStartedAt,
196
+ },
197
+ keeperTurn,
198
+ ) &&
199
+ state.turnLaunchResolution?.outcome === "spawned",
200
+ );
201
+ }
202
+
158
203
  /**
159
204
  * Release a pre-main turn child only after its actual runtime PID is durably
160
205
  * committed and every worker listener needed to supervise it is installed.
@@ -186,6 +231,22 @@ export function attachTurnChildBootstrapRelease(
186
231
  let failed = false;
187
232
  let detached = false;
188
233
 
234
+ const sendRelease = (binding) => {
235
+ try {
236
+ childProcess.send(
237
+ createBackgroundTurnBootstrapMessage(
238
+ BACKGROUND_TURN_BOOTSTRAP_RELEASE,
239
+ binding,
240
+ ),
241
+ (error) => {
242
+ if (error) failOnce(error);
243
+ },
244
+ );
245
+ } catch (error) {
246
+ failOnce(error);
247
+ }
248
+ };
249
+
189
250
  const failOnce = (error) => {
190
251
  if (failed || detached) return;
191
252
  failed = true;
@@ -207,9 +268,11 @@ export function attachTurnChildBootstrapRelease(
207
268
  if (releasedBinding) {
208
269
  if (readyBinding.pid !== releasedBinding.pid) {
209
270
  failOnce(new Error("background turn bootstrap runtime pid changed"));
271
+ return;
210
272
  }
211
- // READY is retransmitted until the child observes RELEASE. Matching
212
- // duplicates are idempotent; never answer with a second RELEASE.
273
+ // A matching READY after durable commit proves the child has not yet
274
+ // observed RELEASE. Retransmit the exact-bound idempotent response.
275
+ sendRelease(readyBinding);
213
276
  return;
214
277
  }
215
278
  if (pendingBinding) {
@@ -230,19 +293,7 @@ export function attachTurnChildBootstrapRelease(
230
293
  return;
231
294
  }
232
295
  releasedBinding = readyBinding;
233
- try {
234
- childProcess.send(
235
- createBackgroundTurnBootstrapMessage(
236
- BACKGROUND_TURN_BOOTSTRAP_RELEASE,
237
- readyBinding,
238
- ),
239
- (error) => {
240
- if (error) failOnce(error);
241
- },
242
- );
243
- } catch (error) {
244
- failOnce(error);
245
- }
296
+ sendRelease(readyBinding);
246
297
  })
247
298
  .catch(failOnce);
248
299
  };
@@ -299,6 +350,66 @@ export function deliverAfterDurableInteractionCleanup({
299
350
  return { cleanupError };
300
351
  }
301
352
 
353
+ /**
354
+ * Spawn a pre-main-blocked turn only after its launch intent is durable, then
355
+ * commit the exact child identity in a short state transaction.
356
+ *
357
+ * Native process creation can block under sustained process churn. It must not
358
+ * run inside the cross-process state lock: a concurrent stop still observes
359
+ * the durable launch intent, while the bootstrap child cannot enter Agent main
360
+ * until the later PID commit releases it. If stop wins the commit race, retain
361
+ * the exact spawned child so the launch-settlement path can retire it.
362
+ */
363
+ export function spawnTurnAfterDurableIntent({
364
+ spawnTurn,
365
+ commitSpawn,
366
+ now = Date.now,
367
+ }) {
368
+ if (typeof spawnTurn !== "function" || typeof commitSpawn !== "function") {
369
+ throw new TypeError(
370
+ "background turn spawn and commit callbacks are required",
371
+ );
372
+ }
373
+ const agentStartedAt = now();
374
+ let spawned = null;
375
+ try {
376
+ spawned = spawnTurn();
377
+ } catch (error) {
378
+ return Object.freeze({
379
+ committed: false,
380
+ spawned: error?.spawnedProcess || null,
381
+ agentStartedAt,
382
+ error,
383
+ });
384
+ }
385
+ try {
386
+ const mutation = commitSpawn(spawned, agentStartedAt);
387
+ if (!mutation?.applied) {
388
+ return Object.freeze({
389
+ committed: false,
390
+ spawned,
391
+ agentStartedAt,
392
+ mutation: mutation || null,
393
+ error: new Error("background turn was stopped before PID commit"),
394
+ });
395
+ }
396
+ return Object.freeze({
397
+ committed: true,
398
+ spawned,
399
+ agentStartedAt,
400
+ mutation,
401
+ error: null,
402
+ });
403
+ } catch (error) {
404
+ return Object.freeze({
405
+ committed: false,
406
+ spawned: spawned || error?.spawnedProcess || null,
407
+ agentStartedAt,
408
+ error,
409
+ });
410
+ }
411
+ }
412
+
302
413
  function updateInteractionJournal(mutate, options = {}) {
303
414
  const {
304
415
  sessionHostWriteDelegation = null,
@@ -447,29 +558,6 @@ function writeHeartbeat() {
447
558
  return applied;
448
559
  }
449
560
 
450
- async function waitForLaunchFinalization() {
451
- const deadline = Date.now() + DEFAULT_HEARTBEAT_STALE_MS;
452
- for (;;) {
453
- const current = readBackgroundAgentState(job.id);
454
- if (!current) return "terminal";
455
- if (current.status && current.status !== "running") return "terminal";
456
- if (current.launchFinalizationUncertain !== true) return "ready";
457
- if (Date.now() >= deadline) {
458
- mergeState({
459
- status: "failed",
460
- endedAt: Date.now(),
461
- exitCode: 1,
462
- error: "background launcher did not finalize process ownership",
463
- phase: null,
464
- transport: null,
465
- launchFinalizationUncertain: false,
466
- });
467
- return "timeout";
468
- }
469
- await new Promise((resolve) => setTimeout(resolve, 25));
470
- }
471
- }
472
-
473
561
  function finalize(code, signal, errorMessage) {
474
562
  // A launch settlement owns the worker lifetime until its tree/no-spawn
475
563
  // result is durably recorded. Transport detach, a queued prompt, or a fatal
@@ -697,13 +785,93 @@ async function settleUncertainTurnLaunch(context) {
697
785
  scheduleTurnLaunchSettlementRetry(context, retryMs);
698
786
  }
699
787
 
788
+ function detachConfirmedTurnRuntime() {
789
+ detachTurnTerminationHandler?.();
790
+ detachTurnTerminationHandler = null;
791
+ detachTurnBootstrapHandler?.();
792
+ detachTurnBootstrapHandler = null;
793
+ detachInteractionHandler?.();
794
+ detachInteractionHandler = null;
795
+ for (const pending of pendingInteractions.values()) {
796
+ const error = new Error("Agent exited");
797
+ error.code = "INTERACTION_CHILD_EXITED";
798
+ pending.deliverRejected?.(error);
799
+ }
800
+ pendingInteractions.clear();
801
+ }
802
+
803
+ function finishAfterDurableKeeperTurnRetirement({
804
+ owned,
805
+ error,
806
+ keeperTurn,
807
+ turnLaunchToken,
808
+ turnLaunchAttempt,
809
+ agentStartedAt,
810
+ }) {
811
+ if (
812
+ !keeperTurn ||
813
+ Number(owned?.pid) !== keeperTurn.agentPid ||
814
+ turnLaunchToken !== keeperTurn.turnLaunchToken ||
815
+ Number(turnLaunchAttempt) !== keeperTurn.attempt ||
816
+ Number(agentStartedAt) !== keeperTurn.agentStartedAt
817
+ ) {
818
+ return false;
819
+ }
820
+ let current;
821
+ try {
822
+ current = readBackgroundAgentState(job.id);
823
+ } catch {
824
+ return false;
825
+ }
826
+ if (!hasDurableKeeperTurnRetirement(current, keeperTurn)) return false;
827
+
828
+ // The exact wrapper/runtime tree is durably proven non-executable. Do not
829
+ // start a second PID-based termination after the keeper channel has closed:
830
+ // under PID reuse that would be less safe than accepting this identity-bound
831
+ // proof. The keeper is unavailable for another turn, so terminalize the
832
+ // worker after the normal interaction journal sweep.
833
+ child = null;
834
+ if (sameBackgroundAgentKeeperTurn(activeKeeperTurn, keeperTurn)) {
835
+ activeKeeperTurn = null;
836
+ }
837
+ detachConfirmedTurnRuntime();
838
+ turnCount = Math.max(turnCount, keeperTurn.attempt);
839
+ lastExit = {
840
+ code: 1,
841
+ signal: null,
842
+ errorMessage:
843
+ error?.message ||
844
+ "background keeper disconnected after confirmed turn cleanup",
845
+ };
846
+ const finish = () => finalize(1, null, lastExit.errorMessage);
847
+ if (job.sessionId && interactionJournal) {
848
+ beginFinalInteractionSweep(finish);
849
+ } else {
850
+ finish();
851
+ }
852
+ return true;
853
+ }
854
+
700
855
  function beginTurnLaunchSettlement({
701
856
  owned,
702
857
  error,
703
858
  turnLaunchToken,
704
859
  turnLaunchAttempt,
705
860
  agentStartedAt,
861
+ keeperTurn = null,
706
862
  }) {
863
+ if (
864
+ finishAfterDurableKeeperTurnRetirement({
865
+ owned,
866
+ error,
867
+ keeperTurn,
868
+ turnLaunchToken,
869
+ turnLaunchAttempt,
870
+ agentStartedAt,
871
+ })
872
+ ) {
873
+ return "retired";
874
+ }
707
875
  child = owned || null;
708
876
  phase = "turn_launch_uncertain";
709
877
  const ownedPid = Number(owned?.pid);
@@ -838,28 +1006,9 @@ function startTurn(argv, promptText) {
838
1006
  }
839
1007
  if (!prepared.applied) return false;
840
1008
 
841
- let spawned = null;
842
- let agentStartedAt = null;
843
- try {
844
- const mutation = mutateBackgroundAgentState(job.id, (current) => {
845
- if (
846
- !current ||
847
- current.status !== "running" ||
848
- current.stopRequestedAt ||
849
- current.turnLaunchIntent?.token !== turnLaunchToken ||
850
- Number(current.turnLaunchIntent?.attempt) !== turnLaunchAttempt ||
851
- Number(current.workerPid) !== process.pid ||
852
- Number(current.workerClaimedPid) !== process.pid ||
853
- (current.workerGeneration &&
854
- current.workerGeneration !== job.workerGeneration)
855
- ) {
856
- return null;
857
- }
858
- // The prepare intent is already durable. Keep the second state lock
859
- // through native spawn and pid commit so a stopper sees either the
860
- // unresolved intent or the exact child identity it must reap.
861
- agentStartedAt = Date.now();
862
- spawned = executionBroker.spawn(
1009
+ const launch = spawnTurnAfterDurableIntent({
1010
+ spawnTurn: () =>
1011
+ executionBroker.spawn(
863
1012
  process.execPath,
864
1013
  [TURN_BOOTSTRAP_IMPORT_ARGUMENT, job.cliEntry, ...argv],
865
1014
  {
@@ -881,64 +1030,72 @@ function startTurn(argv, promptText) {
881
1030
  shell: false,
882
1031
  detached: process.platform !== "win32",
883
1032
  },
884
- );
885
- return {
886
- ...current,
887
- status: "running",
888
- phase: "turn",
889
- turnCount: nextTurn,
890
- pendingApprovals: 0,
891
- pendingQuestion: null,
892
- uncertainSideEffects: 0,
893
- interactionRecovery: {
894
- status: "pending",
895
- turn: nextTurn,
896
- workerGeneration: job.workerGeneration,
897
- startedAt: agentStartedAt,
898
- },
899
- agentPid: spawned.pid,
900
- agentStartedAt,
901
- agentRuntimePid: null,
902
- agentRuntimeStartedAt: null,
903
- turnBootstrapStatus: "awaiting-ready",
904
- turnBootstrapCommittedAt: null,
905
- turnKeeperStatus: "waiting-for-runtime",
906
- turnKeeperPid: null,
907
- turnKeeperArmedAt: null,
908
- turnKeeperCleanupReason: null,
909
- turnKeeperCleanupRequestedAt: null,
910
- turnKeeperCleanupConfirmedAt: null,
911
- turnKeeperCleanupError: null,
912
- turnLaunchIntent: null,
913
- turnLaunchResolution: {
914
- token: turnLaunchToken,
915
- attempt: turnLaunchAttempt,
916
- outcome: "spawned",
917
- agentPid: spawned.pid,
918
- resolvedAt: Date.now(),
919
- },
920
- heartbeatAt: Date.now(),
921
- };
922
- });
923
- if (!mutation.applied) {
924
- return beginTurnLaunchSettlement({
925
- owned: null,
926
- error: new Error("background turn was stopped before native spawn"),
927
- turnLaunchToken,
928
- turnLaunchAttempt,
929
- agentStartedAt: null,
930
- });
931
- }
932
- } catch (error) {
933
- const owned = spawned || error?.spawnedProcess;
1033
+ ),
1034
+ commitSpawn: (spawnedTurn, startedAt) =>
1035
+ mutateBackgroundAgentState(job.id, (current) => {
1036
+ if (
1037
+ !current ||
1038
+ current.status !== "running" ||
1039
+ current.stopRequestedAt ||
1040
+ current.turnLaunchIntent?.token !== turnLaunchToken ||
1041
+ Number(current.turnLaunchIntent?.attempt) !== turnLaunchAttempt ||
1042
+ Number(current.workerPid) !== process.pid ||
1043
+ Number(current.workerClaimedPid) !== process.pid ||
1044
+ (current.workerGeneration &&
1045
+ current.workerGeneration !== job.workerGeneration)
1046
+ ) {
1047
+ return null;
1048
+ }
1049
+ return {
1050
+ ...current,
1051
+ status: "running",
1052
+ phase: "turn",
1053
+ turnCount: nextTurn,
1054
+ pendingApprovals: 0,
1055
+ pendingQuestion: null,
1056
+ uncertainSideEffects: 0,
1057
+ interactionRecovery: {
1058
+ status: "pending",
1059
+ turn: nextTurn,
1060
+ workerGeneration: job.workerGeneration,
1061
+ startedAt,
1062
+ },
1063
+ agentPid: spawnedTurn.pid,
1064
+ agentStartedAt: startedAt,
1065
+ agentRuntimePid: null,
1066
+ agentRuntimeStartedAt: null,
1067
+ turnBootstrapStatus: "awaiting-ready",
1068
+ turnBootstrapCommittedAt: null,
1069
+ turnKeeperStatus: "waiting-for-runtime",
1070
+ turnKeeperPid: null,
1071
+ turnKeeperArmedAt: null,
1072
+ turnKeeperCleanupReason: null,
1073
+ turnKeeperCleanupRequestedAt: null,
1074
+ turnKeeperCleanupConfirmedAt: null,
1075
+ turnKeeperCleanupError: null,
1076
+ turnLaunchIntent: null,
1077
+ turnLaunchResolution: {
1078
+ token: turnLaunchToken,
1079
+ attempt: turnLaunchAttempt,
1080
+ outcome: "spawned",
1081
+ agentPid: spawnedTurn.pid,
1082
+ resolvedAt: Date.now(),
1083
+ },
1084
+ heartbeatAt: Date.now(),
1085
+ };
1086
+ }),
1087
+ });
1088
+ if (!launch.committed) {
934
1089
  return beginTurnLaunchSettlement({
935
- owned,
936
- error,
1090
+ owned: launch.spawned,
1091
+ error: launch.error,
937
1092
  turnLaunchToken,
938
1093
  turnLaunchAttempt,
939
- agentStartedAt,
1094
+ agentStartedAt: launch.agentStartedAt,
940
1095
  });
941
1096
  }
1097
+ const spawned = launch.spawned;
1098
+ const agentStartedAt = launch.agentStartedAt;
942
1099
  child = spawned;
943
1100
  phase = "turn";
944
1101
  turnCount = nextTurn;
@@ -1249,14 +1406,17 @@ function startTurn(argv, promptText) {
1249
1406
  turnLaunchToken,
1250
1407
  turnLaunchAttempt,
1251
1408
  agentStartedAt,
1409
+ keeperTurn: activeKeeperTurn,
1252
1410
  });
1253
1411
  }
1254
1412
  },
1255
1413
  );
1256
1414
 
1257
- attachTurnChildTerminationSettlement(
1415
+ detachTurnTerminationHandler = attachTurnChildTerminationSettlement(
1258
1416
  child,
1259
1417
  ({ code, signal, errorMessage }) => {
1418
+ detachTurnTerminationHandler?.();
1419
+ detachTurnTerminationHandler = null;
1260
1420
  turnTerminated = true;
1261
1421
  lastExit = {
1262
1422
  code,
@@ -1264,19 +1424,7 @@ function startTurn(argv, promptText) {
1264
1424
  ...(errorMessage ? { errorMessage } : {}),
1265
1425
  };
1266
1426
  child = null;
1267
- detachTurnBootstrapHandler?.();
1268
- detachTurnBootstrapHandler = null;
1269
- // 清理交互处理器
1270
- detachInteractionHandler?.();
1271
- detachInteractionHandler = null;
1272
- // Reject any request that survived an abnormal child exit. Normal
1273
- // responses remove themselves from this map before the turn continues.
1274
- for (const pending of pendingInteractions.values()) {
1275
- const error = new Error("Agent exited");
1276
- error.code = "INTERACTION_CHILD_EXITED";
1277
- pending.deliverRejected?.(error);
1278
- }
1279
- pendingInteractions.clear();
1427
+ detachConfirmedTurnRuntime();
1280
1428
  const finishTurn = () =>
1281
1429
  beginFinalInteractionSweep(() => {
1282
1430
  server?.broadcast({
@@ -1315,6 +1463,7 @@ function startTurn(argv, promptText) {
1315
1463
  turnLaunchToken,
1316
1464
  turnLaunchAttempt,
1317
1465
  agentStartedAt,
1466
+ keeperTurn,
1318
1467
  });
1319
1468
  }
1320
1469
  }
@@ -1396,6 +1545,86 @@ function getStatus() {
1396
1545
 
1397
1546
  async function main() {
1398
1547
  job = JSON.parse(readFileSync(jobFile, "utf8"));
1548
+ const keeperLaunchClaimPath = job.keeper
1549
+ ? backgroundAgentKeeperLaunchClaimPath(
1550
+ job.id,
1551
+ job.keeper.generation,
1552
+ backgroundAgentsDir(),
1553
+ )
1554
+ : null;
1555
+ // The launcher must publish the exact worker/keeper identities before
1556
+ // either detached child performs its first state mutation. Under sustained
1557
+ // process churn an eagerly scheduled worker could otherwise acquire the
1558
+ // state lock and then be descheduled long enough to fence the launcher's PID
1559
+ // commit. This barrier is intentionally read-only until ownership is final.
1560
+ const launchBarrier = await waitForBackgroundAgentLaunchBarrier({
1561
+ id: job.id,
1562
+ workerGeneration: job.workerGeneration,
1563
+ expectedPid: process.pid,
1564
+ pidField: "workerPid",
1565
+ readState: readBackgroundAgentState,
1566
+ timeoutMs: DEFAULT_HEARTBEAT_STALE_MS,
1567
+ // The keeper writes this private exact-generation marker only after its
1568
+ // first state transaction has returned, which proves that transaction's
1569
+ // lock has been released. Reading `keeperStatus=listening` alone is not
1570
+ // sufficient because the state replacement happens inside the lock.
1571
+ readyWhen: (state) => {
1572
+ if (
1573
+ !keeperLaunchClaimPath ||
1574
+ !Number.isSafeInteger(Number(state.keeperPid)) ||
1575
+ Number(state.keeperPid) <= 0
1576
+ ) {
1577
+ return false;
1578
+ }
1579
+ try {
1580
+ const expectedKeeperLaunchClaim =
1581
+ createBackgroundAgentKeeperLaunchClaim({
1582
+ id: job.id,
1583
+ workerGeneration: job.workerGeneration,
1584
+ keeperGeneration: job.keeper.generation,
1585
+ keeperPid: Number(state.keeperPid),
1586
+ token: job.keeper.token,
1587
+ });
1588
+ return matchesBackgroundAgentKeeperLaunchClaim(
1589
+ JSON.parse(readFileSync(keeperLaunchClaimPath, "utf8")),
1590
+ expectedKeeperLaunchClaim,
1591
+ );
1592
+ } catch {
1593
+ return false;
1594
+ }
1595
+ },
1596
+ });
1597
+ if (launchBarrier.status !== "ready") {
1598
+ if (launchBarrier.status === "timeout") {
1599
+ try {
1600
+ mutateBackgroundAgentState(job.id, (current) => {
1601
+ if (
1602
+ !current ||
1603
+ current.status !== "running" ||
1604
+ current.workerGeneration !== job.workerGeneration ||
1605
+ current.launchFinalizationUncertain !== true
1606
+ ) {
1607
+ return null;
1608
+ }
1609
+ return {
1610
+ ...current,
1611
+ status: "failed",
1612
+ endedAt: Date.now(),
1613
+ exitCode: 1,
1614
+ error: "background launcher did not finalize process ownership",
1615
+ phase: null,
1616
+ transport: null,
1617
+ launchFinalizationUncertain: false,
1618
+ };
1619
+ });
1620
+ } catch {
1621
+ // Retain the durable uncertainty fence if timeout persistence itself
1622
+ // cannot acquire the state lock; no worker side effect has started.
1623
+ }
1624
+ }
1625
+ removeJobFile(jobFile);
1626
+ return;
1627
+ }
1399
1628
  // Claim before opening logs, recovering interactions or creating the pipe.
1400
1629
  // A stopped/removed/differently-generated record must produce no worker
1401
1630
  // side effects and must never be recreated by bootstrap merges.
@@ -1403,6 +1632,13 @@ async function main() {
1403
1632
  removeJobFile(jobFile);
1404
1633
  return;
1405
1634
  }
1635
+ if (keeperLaunchClaimPath) {
1636
+ try {
1637
+ unlinkSync(keeperLaunchClaimPath);
1638
+ } catch {
1639
+ // The keeper also removes its generation-bound marker on exit.
1640
+ }
1641
+ }
1406
1642
  removeJobFile(jobFile);
1407
1643
  log = openBackgroundLogFile(job.id);
1408
1644
  const bootstrapState = readBackgroundAgentState(job.id);
@@ -1587,11 +1823,6 @@ async function main() {
1587
1823
  }, DEFAULT_HEARTBEAT_INTERVAL_MS);
1588
1824
  heartbeat.unref?.();
1589
1825
 
1590
- const launchReadiness = await waitForLaunchFinalization();
1591
- if (launchReadiness !== "ready") {
1592
- finalize(launchReadiness === "timeout" ? 1 : 0, null);
1593
- return;
1594
- }
1595
1826
  if (!job.keeper) {
1596
1827
  throw new Error("background agent keeper launch authority is missing");
1597
1828
  }
@@ -1603,13 +1834,31 @@ async function main() {
1603
1834
  token: job.keeper.token,
1604
1835
  onDisconnect(error, keeperTurn) {
1605
1836
  if (finalized || turnLaunchSettlement) return;
1606
- if (child && keeperTurn) {
1837
+ if (keeperTurn) {
1838
+ const ownedTurn = child || { pid: keeperTurn.agentPid };
1839
+ if (
1840
+ finishAfterDurableKeeperTurnRetirement({
1841
+ owned: ownedTurn,
1842
+ error,
1843
+ keeperTurn,
1844
+ turnLaunchToken: keeperTurn.turnLaunchToken,
1845
+ turnLaunchAttempt: keeperTurn.attempt,
1846
+ agentStartedAt: keeperTurn.agentStartedAt,
1847
+ })
1848
+ ) {
1849
+ return;
1850
+ }
1851
+ // A null child means its terminal callback already owns the pending
1852
+ // RETIRE rejection and will reconcile or settle with the original
1853
+ // ChildProcess identity. Do not finalize ahead of that journal sweep.
1854
+ if (!child) return;
1607
1855
  beginTurnLaunchSettlement({
1608
1856
  owned: child,
1609
1857
  error,
1610
1858
  turnLaunchToken: keeperTurn.turnLaunchToken,
1611
1859
  turnLaunchAttempt: keeperTurn.attempt,
1612
1860
  agentStartedAt: keeperTurn.agentStartedAt,
1861
+ keeperTurn,
1613
1862
  });
1614
1863
  return;
1615
1864
  }