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.
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-lS1R0Cza.js → AIOps-BiHNUlbU.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BmlgBPsj.js → ActionButton-D-UFjDRQ.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CNKczv1S.js → Analytics-DazVsDgW.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BuZGJARQ.js → AppLayout-DXh9UfNJ.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-Dm73Sl-O.js → Artifacts-CZ6tsceY.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-BGMW7A5e.js → Audit-RyxnBr_h.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BQ6M54JR.js → BackgroundAgents-BWS51Q6X.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DBzGP0bR.js → Backup-BRtPcUul.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-D1mLCoOV.js → BaseInput-CGd4vZYY.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BUfcN7p8.js → Chat-h8ijVwCg.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CY5m8ZHg.js → ChatBubbleRenderer-D7tcdDS9.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-Du0Yg5Pi.js → Checkbox-NyF6dPl8.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-B2RXziHW.js → Codegen-Ci4OOxr5.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DWhekOrR.js → Col-CwTkc9BM.js} +1 -1
- package/src/assets/web-panel/assets/{Community-HQhrkon4.js → Community-DW_tM82m.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-izHNb4uz.js → Compact-DEvHRV4S.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BDwLy41-.js → Compliance-BbjBEEfD.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DFJhLa7K.js → Cowork-10IXBaBa.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DYY9RG7-.js → Cron-BZkbpd5I.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-rr7tJTtv.js → Crosschain-CFQ8L_6v.js} +1 -1
- package/src/assets/web-panel/assets/{DID-D2sfHRLi.js → DID-BDF6AkXe.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-q_eGdhha.js → Dashboard-CoOdWMi4.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-Cufrh2Bz.js → Dropdown-DEsGmu8Y.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-B-kdv10f.js → EmailListRenderer-BS89SWlK.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-D21XjgfG.js → FamilyGuardDashboard-C1FpCHiI.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CAwRTLZH.js → Federation-BzvpVmLA.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-vx3daNDF.js → FormItemContext-BeC259PQ.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CRWY2wU4.js → GenericCardRenderer-azgG3bmV.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Q6SwauyD.js → Git-BmSSowc5.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BJ1q4IQZ.js → Governance-CTBCSyQZ.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CSWjROgD.js → Inference-DA5xyk4a.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-fzuQsWuY.js → KnowledgeGraph-D1gPHNig.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DVF8Mnns.js → Logs-BPmLf1AR.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-SVLOg49H.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BQP7LOA4.js → Marketplace-DtUFcR0E.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BNlJCmNc.js → McpTools-DS_kqBv-.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-DLLg5WQ4.js → Memory-Fvh7XL7V.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CR2skNAF.js → MobileBridge-C7itAP45.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-DGbP36-3.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-B17pgqMI.js → Mtc-B57MiNLK.js} +6 -6
- package/src/assets/web-panel/assets/{MtcAudit-4UmV5cIL.js → MtcAudit-BXfCUcM7.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-DoMEXEcU.js → Multisig-yaJ-ZcF6.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-h9DMXFX4.js → NLProgramming-CoaUCKrO.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Cihn-xX8.js → Notes-BKLM_65R.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BE6ZGa3R.js → NotificationSettings-DoDtK9uZ.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BJ28TGr-.js +1 -0
- package/src/assets/web-panel/assets/{Organization-B2vpwmjb.js → Organization-MuGsdoIb.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-C4z28R0-.js → Overflow-BLUN9J4R.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BVlFkwhW.js → P2P-DWuoBUPd.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BXbEdL5B.js → PdhVaultBrowser-CUi37YPj.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-C28xs4lp.js → Permissions-BYQg2pcr.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Dgl_rBjM.js → PersonalDataHub-CAxoV2xu.js} +6 -6
- package/src/assets/web-panel/assets/{Pipeline-3QUIhrKI.js → Pipeline-B33wGvIO.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-8Gh33OZ5.js → Privacy-A8yXTORP.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BNmg4tZi.js → ProjectInit-D49I2I1O.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-vgMfeCf9.js → ProjectSettings-Ci73jJDw.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BhtAVilc.js → Projects-CF-yD6Cn.js} +1 -1
- package/src/assets/web-panel/assets/Providers-D-etEhg5.js +1 -0
- package/src/assets/web-panel/assets/{QrScannerModal-BhfTF0oS.js → QrScannerModal-Brza0ykJ.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-C746HEAj.js → QuickAsk-DFydhjxL.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BSej8Eo0.js → Recommend-DLF8_NhA.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-f3gmnlG3.js → RemoteSession-dyuStW3x.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-CyvybpkN.js → Reputation-5F3tA6yw.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DwV2PeYn.js → Row-Ca2A57S5.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-11GrzjZy.js → RssFeed-B0bxBkq_.js} +3 -3
- package/src/assets/web-panel/assets/{Search-BjKXkrAY.js → Search-CFNvHalZ.js} +1 -1
- package/src/assets/web-panel/assets/{Security-eWiFb9Yk.js → Security-DSoH3Hav.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CdCsMAtt.js → Services-BHpfEmBQ.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-4Q2UTKQ5.js → Skeleton-Dsh-QNmn.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-v4rtYrRj.js → Skills-BhT1NkSU.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CRhsFvd_.js → Sla-DY9snPeJ.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-CWaX0_Cx.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-D16zonl7.js → SyncSettings-B690y_TC.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Xxozz0i_.js → Tasks-2N-w3zWd.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CXJpPtCT.js → Templates-BjwQOQYC.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-XRV0L1ZF.js → Tenant-CUqHErxY.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CYuO1i_w.js → Terminal-C13rXvNy.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BXqitWsu.js → TimelineRenderer-CpbWcya_.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BvPrDHIy.js → Tokens-BXHLGXYC.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DhCrSTf5.js → Trigger-1e5i65o3.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DmsuZD6K.js → Trust-FLGqNHs5.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-D_yrts5C.js → UkeySign-BbaR_LJB.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B01ihPrm.js → VideoEditing-NOp4RIov.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DpUjKa-N.js → Wallet-B7DW7bG4.js} +3 -3
- package/src/assets/web-panel/assets/{WebAuthn-CL07yON6.js → WebAuthn-BptSUWix.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-CLMflnRr.js → WorkflowEditor-CAJnDOXS.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DtUqe8h_.js → chat-I8LgG1-Z.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DPKrgVfy.js → colors-CWe7SOVD.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BTt2GtOm.js → compact-item-Cx3u3CA3.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-D_VeUc1J.js → createContext-CgD5ArwY.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DLv_872g.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-1kndiylj.js → hasIn-BfQWiXN8.js} +1 -1
- package/src/assets/web-panel/assets/{index-brCInrzq.js → index-3iKP9OTJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-l1BETrDb.js → index-87gMxqhN.js} +1 -1
- package/src/assets/web-panel/assets/{index-DxdTdBix.js → index-B1cX4Rxu.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdkplL9R.js → index-B6yChzt1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHpBMWCh.js → index-BREnPKGo.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzjkKUmR.js → index-BUAR7RY-.js} +1 -1
- package/src/assets/web-panel/assets/{index-D95OFht7.js → index-Bi24S7Dw.js} +1 -1
- package/src/assets/web-panel/assets/{index-8nz_fmlj.js → index-BiI-xPRH.js} +1 -1
- package/src/assets/web-panel/assets/{index-2eyAXE3d.js → index-BuEQSorC.js} +1 -1
- package/src/assets/web-panel/assets/{index-GsoUQ2M7.js → index-Bx7BNIRH.js} +1 -1
- package/src/assets/web-panel/assets/{index-CX81qSM4.js → index-C-rIKccl.js} +1 -1
- package/src/assets/web-panel/assets/{index-CcO04eDn.js → index-C0nDKhWv.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bhudc_Ez.js → index-C0ztiqtB.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfGl8CqG.js → index-C1UjHdgZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaZAbw9A.js → index-CAUnl4Jv.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9KvcS7R.js → index-CMu16vPC.js} +1 -1
- package/src/assets/web-panel/assets/{index-m31Ka8hZ.js → index-CNYFQxAW.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dwqmtk65.js → index-CQDKYbnv.js} +1 -1
- package/src/assets/web-panel/assets/{index-fk2Rjacw.js → index-CWZ3REg6.js} +3 -3
- package/src/assets/web-panel/assets/{index-DsJCDUJG.js → index-CcX4FSPf.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAXfRKfG.js → index-Cd79NQAx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlDNVgBt.js → index-CdRkCwm4.js} +1 -1
- package/src/assets/web-panel/assets/index-CdiGCibe.js +1 -0
- package/src/assets/web-panel/assets/{index-4bna2_SR.js → index-Cy_cCJlr.js} +1 -1
- package/src/assets/web-panel/assets/{index-dVc-CXY4.js → index-DAVy98u7.js} +1 -1
- package/src/assets/web-panel/assets/{index-ocf06EVQ.js → index-DCJEY2T0.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dbbymxpj.js → index-DFb-XIKy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CHG-KcS4.js → index-DGMYTN5W.js} +1 -1
- package/src/assets/web-panel/assets/index-D_0z-gyz.js +1 -0
- package/src/assets/web-panel/assets/{index-By5n8up9.js → index-Dcdm_Z2c.js} +1 -1
- package/src/assets/web-panel/assets/{index-FzdwcSy2.js → index-DeizoQVZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFIZ_1Sf.js → index-DlIyt1qF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BepLZwgB.js → index-DmgtY58H.js} +1 -1
- package/src/assets/web-panel/assets/{index-sDoMHTNA.js → index-DmrdUwzU.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJ8vXGOa.js → index-Doc0Ftpd.js} +1 -1
- package/src/assets/web-panel/assets/{index-CauoBQkH.js → index-DsQcURkA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtF7EN49.js → index-DslTuqro.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfKBRdSS.js → index-V-kIHzAV.js} +1 -1
- package/src/assets/web-panel/assets/{index-DG2MZQ3f.js → index-e8BM1yY1.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CvyYJd7z.js → initDefaultProps-DiBszVdG.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DYZkhUdz.js → motion-BGIFFozv.js} +1 -1
- package/src/assets/web-panel/assets/{move-slnJRVT1.js → move-D5W7mp7B.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DVc0CKmy.js → mtc-parser-JFIlxJ7F.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BCXAtHCq.js → omit-CQ3o6LIU.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DBsemz_K.js → pickAttrs-R8MIlXo3.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BRtkJ-nv.js → placementArrow-sW7ylSky.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Cebq7EZn.js → responsiveObserve-Cnl39Vbz.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DT7qvId2.js → slide-sn8tFd14.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-pQiITLkG.js → statusUtils-IKKY_QxZ.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-c-19BVKQ.js → styleChecker-BNpV4Nqs.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DsS5-0bE.js → useFlexGapSupport-BWyT_eoU.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CxGiMe5n.js → useFs-D09p6_U9.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-D9btVID5.js → usePersonalDataHub-CzW3TkJZ.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CUYFsFEN.js → vnode-3s3WoAKB.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CXmn_35j.js → zoom-Cjy_673s.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/data/changelog.json +13 -0
- package/src/lib/background-agent-keeper-protocol.js +75 -4
- package/src/lib/background-agent-launch-barrier.js +74 -0
- package/src/lib/background-agent-supervisor.js +149 -5
- package/src/lib/background-turn-bootstrap-protocol.js +4 -0
- package/src/lib/with-file-lock.js +175 -26
- package/src/workers/background-agent-keeper.js +112 -19
- package/src/workers/background-agent-worker.js +383 -134
- package/src/workers/background-turn-bootstrap.js +13 -8
- package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-BxVavObl.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CnwojoSI.js +0 -1
- package/src/assets/web-panel/assets/Providers-CXoOa8eX.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +0 -1
- package/src/assets/web-panel/assets/index-CPcM-eqo.js +0 -1
- 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
|
|
212
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
842
|
-
|
|
843
|
-
|
|
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
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
}
|