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.
- package/README.md +248 -2
- package/package.json +4 -1
- package/src/assets/web-panel/assets/{AIOps-CYxFDrcm.js → AIOps-BiHNUlbU.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CPBtdiU2.js → ActionButton-D-UFjDRQ.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Dayq7Dp7.js → Analytics-DazVsDgW.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-6U7bPb2s.js → AppLayout-DXh9UfNJ.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-D2Iqg_3D.js → Artifacts-CZ6tsceY.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CdoOB7WV.js → Audit-RyxnBr_h.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DrnAVB8w.js → BackgroundAgents-BWS51Q6X.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CesMi5cy.js → Backup-BRtPcUul.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DaX9ptax.js → BaseInput-CGd4vZYY.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-OrwOPxDS.js → Chat-h8ijVwCg.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CvPZFRxM.js → ChatBubbleRenderer-D7tcdDS9.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DiHbcysC.js → Checkbox-NyF6dPl8.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CbgCkaBJ.js → Codegen-Ci4OOxr5.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Du5hgrh4.js → Col-CwTkc9BM.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DsPOBW-p.js → Community-DW_tM82m.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BwHIFkNC.js → Compact-DEvHRV4S.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BvrDQSgd.js → Compliance-BbjBEEfD.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-gwAE6arx.js → Cowork-10IXBaBa.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-DW-0l4uF.js → Cron-BZkbpd5I.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-j-NsLesB.js → Crosschain-CFQ8L_6v.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CibVEBOI.js → DID-BDF6AkXe.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-W4z9vSfe.js → Dashboard-CoOdWMi4.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DAcrkKxG.js → Dropdown-DEsGmu8Y.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-6GXah-89.js → EmailListRenderer-BS89SWlK.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-TKDCq8Ku.js → FamilyGuardDashboard-C1FpCHiI.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BXBLlcNm.js → Federation-BzvpVmLA.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BoJJBkG3.js → FormItemContext-BeC259PQ.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-okjsl46v.js → GenericCardRenderer-azgG3bmV.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Rvs7i6nn.js → Git-BmSSowc5.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CHSUxXXS.js → Governance-CTBCSyQZ.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-V3BlfxsB.js → Inference-DA5xyk4a.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-C_NlD4aR.js → KnowledgeGraph-D1gPHNig.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-Nn4LNxhA.js → Logs-BPmLf1AR.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-SVAgNnkt.js → MarkdownRenderer-SVLOg49H.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-DEmjFcg-.js → Marketplace-DtUFcR0E.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DlopXipI.js → McpTools-DS_kqBv-.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-BG3ynX60.js → Memory-Fvh7XL7V.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DeRzWOfT.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-BoPbOv1C.js → Mtc-B57MiNLK.js} +6 -6
- package/src/assets/web-panel/assets/{MtcAudit-un-rTHp7.js → MtcAudit-BXfCUcM7.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-Ds0oveEB.js → Multisig-yaJ-ZcF6.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-D09JI-yY.js → NLProgramming-CoaUCKrO.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DTRhiKJn.js → Notes-BKLM_65R.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BAnnNCim.js → NotificationSettings-DoDtK9uZ.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-Yr-1fSYi.js → OrderTableRenderer-BJ28TGr-.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-S9EAolpj.js → Organization-MuGsdoIb.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CqcLKqJ8.js → Overflow-BLUN9J4R.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Dw0F9EcU.js → P2P-DWuoBUPd.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BDRwUevN.js → PdhVaultBrowser-CUi37YPj.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-D3_c3nKB.js → Permissions-BYQg2pcr.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CxdMV98x.js → PersonalDataHub-CAxoV2xu.js} +6 -6
- package/src/assets/web-panel/assets/{Pipeline-CgjNQFLI.js → Pipeline-B33wGvIO.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BHghIIht.js → Privacy-A8yXTORP.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-De6pqaKM.js → ProjectInit-D49I2I1O.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-Bg4ZkNYQ.js → ProjectSettings-Ci73jJDw.js} +2 -2
- package/src/assets/web-panel/assets/Projects-CF-yD6Cn.js +1 -0
- package/src/assets/web-panel/assets/Providers-D-etEhg5.js +1 -0
- package/src/assets/web-panel/assets/{QrScannerModal-DA2f3Wts.js → QrScannerModal-Brza0ykJ.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-C1FeHDqp.js → QuickAsk-DFydhjxL.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-B5guaUMN.js → Recommend-DLF8_NhA.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-PDADSds3.js → RemoteSession-dyuStW3x.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-eTlRhgvK.js → Reputation-5F3tA6yw.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D7AqSGY0.js → Row-Ca2A57S5.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-JiuLm4Ja.js → RssFeed-B0bxBkq_.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CwruahW3.js → Search-CFNvHalZ.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CdhqaR0b.js → Security-DSoH3Hav.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CU0ZIlcT.js → Services-BHpfEmBQ.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CtduPfAb.js → Skeleton-Dsh-QNmn.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CROXm6AY.js → Skills-BhT1NkSU.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DiZiABFD.js → Sla-DY9snPeJ.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-B5pLcXUw.js → SpeechSettings-CWaX0_Cx.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DBX6J1Oe.js → SyncSettings-B690y_TC.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BLyRJd3-.js → Tasks-2N-w3zWd.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BKa5qzmk.js → Templates-BjwQOQYC.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DnD3a4M6.js → Tenant-CUqHErxY.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DEitQeTb.js → Terminal-C13rXvNy.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CjHggTDq.js → TimelineRenderer-CpbWcya_.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CsGT7sfl.js → Tokens-BXHLGXYC.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-yr4rFDrW.js → Trigger-1e5i65o3.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CLvu1Sjp.js → Trust-FLGqNHs5.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B0m7yUP8.js → UkeySign-BbaR_LJB.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-C0VvP4lc.js → VideoEditing-NOp4RIov.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-kllBAG1M.js → Wallet-B7DW7bG4.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-C54UQNLx.js → WebAuthn-BptSUWix.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-B7y8qSvU.js → WorkflowEditor-CAJnDOXS.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DRzTh8CJ.js → chat-I8LgG1-Z.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BSMwSnCx.js → colors-CWe7SOVD.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BTg602H5.js → compact-item-Cx3u3CA3.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-B6EGsdvp.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-DDKqJ8RD.js → hasIn-BfQWiXN8.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5CsBShd.js → index-3iKP9OTJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dfv2lKu3.js → index-87gMxqhN.js} +1 -1
- package/src/assets/web-panel/assets/{index-C66NpWy5.js → index-B1cX4Rxu.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBT3YeQ9.js → index-B6yChzt1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVTAl5N2.js → index-BREnPKGo.js} +1 -1
- package/src/assets/web-panel/assets/{index-CwPUZbMI.js → index-BUAR7RY-.js} +1 -1
- package/src/assets/web-panel/assets/{index-th2ihDc0.js → index-Bi24S7Dw.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmAubjDi.js → index-BiI-xPRH.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUEU48o1.js → index-BuEQSorC.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cf23wVMs.js → index-Bx7BNIRH.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRqt4fO-.js → index-C-rIKccl.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_JzIXUq.js → index-C0nDKhWv.js} +1 -1
- package/src/assets/web-panel/assets/{index-DiW0sDkz.js → index-C0ztiqtB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BICn3Qh1.js → index-C1UjHdgZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpTHIRfZ.js → index-CAUnl4Jv.js} +1 -1
- package/src/assets/web-panel/assets/{index-Co7oM1qr.js → index-CMu16vPC.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNxg61YP.js → index-CNYFQxAW.js} +1 -1
- package/src/assets/web-panel/assets/{index-XFe-1twF.js → index-CQDKYbnv.js} +1 -1
- package/src/assets/web-panel/assets/{index-p2HYn89m.js → index-CWZ3REg6.js} +3 -3
- package/src/assets/web-panel/assets/{index-Bx044qDj.js → index-CcX4FSPf.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMuGd2--.js → index-Cd79NQAx.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGNklbP6.js → index-CdRkCwm4.js} +1 -1
- package/src/assets/web-panel/assets/index-CdiGCibe.js +1 -0
- package/src/assets/web-panel/assets/{index-rjzk9z1Q.js → index-Cy_cCJlr.js} +1 -1
- package/src/assets/web-panel/assets/{index-323Hhxxt.js → index-DAVy98u7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDTvsX8t.js → index-DCJEY2T0.js} +1 -1
- package/src/assets/web-panel/assets/{index-DLdrHhML.js → index-DFb-XIKy.js} +1 -1
- package/src/assets/web-panel/assets/{index-pqfux8vZ.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-u2YVld9W.js → index-Dcdm_Z2c.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmAInUkX.js → index-DeizoQVZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CHfLYwcW.js → index-DlIyt1qF.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWGax2QW.js → index-DmgtY58H.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dc77VR3H.js → index-DmrdUwzU.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1naNj4U.js → index-Doc0Ftpd.js} +1 -1
- package/src/assets/web-panel/assets/{index-BsR7Y2NG.js → index-DsQcURkA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQlHFN_g.js → index-DslTuqro.js} +1 -1
- package/src/assets/web-panel/assets/{index-DqFXGzzC.js → index-V-kIHzAV.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1GFfuqN.js → index-e8BM1yY1.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C3Djh9fa.js → initDefaultProps-DiBszVdG.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BzqFmbSe.js → motion-BGIFFozv.js} +1 -1
- package/src/assets/web-panel/assets/{move-DJo_FFaW.js → move-D5W7mp7B.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-_tlTiBvk.js → mtc-parser-JFIlxJ7F.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CB2ZyxUg.js → omit-CQ3o6LIU.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DZwIFD4u.js → pickAttrs-R8MIlXo3.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DxZY0dgk.js → placementArrow-sW7ylSky.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CzzGt7GL.js → responsiveObserve-Cnl39Vbz.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BaIK52yz.js → slide-sn8tFd14.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-3-FHu9_M.js → statusUtils-IKKY_QxZ.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BkRT68fK.js → styleChecker-BNpV4Nqs.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DHYwKS-s.js → useFlexGapSupport-BWyT_eoU.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DVVUhV8p.js → useFs-D09p6_U9.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CkrFKAzQ.js → usePersonalDataHub-CzW3TkJZ.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BuBLFz0c.js → vnode-3s3WoAKB.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-kpm6LtXU.js → zoom-Cjy_673s.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/cowork.js +280 -5
- package/src/commands/plugin.js +819 -64
- package/src/commands/session-location.js +318 -7
- package/src/constants.js +2 -2
- package/src/data/changelog.json +27 -1
- package/src/gateways/ws/action-protocol.js +1 -1
- package/src/harness/jsonl-session-store.js +638 -2
- package/src/harness/prompt-compressor.js +4 -0
- package/src/lib/background-agent-keeper-client.js +29 -0
- package/src/lib/background-agent-keeper-protocol.js +78 -3
- package/src/lib/background-agent-launch-barrier.js +74 -0
- package/src/lib/background-agent-supervisor.js +208 -34
- package/src/lib/background-turn-bootstrap-protocol.js +4 -0
- package/src/lib/cowork-task-runner.js +70 -0
- package/src/lib/cowork-workflow.js +42 -9
- package/src/lib/doctor-checkup.js +34 -10
- package/src/lib/dynamic-workflow-draft.js +535 -0
- package/src/lib/dynamic-workflow-facade.js +11 -11
- package/src/lib/dynamic-workflow-runtime.js +2974 -0
- package/src/lib/execution-location-contract.js +111 -7
- package/src/lib/execution-location-runtime.js +3 -1
- package/src/lib/execution-location-target.js +910 -0
- package/src/lib/llm-providers.js +1 -0
- package/src/lib/mcp-call-ledger-store.js +35 -0
- package/src/lib/mcp-call-ledger.js +30 -0
- package/src/lib/plugin-runtime/__tests__/remote-source.test.js +34 -16
- package/src/lib/plugin-runtime/install.js +4956 -657
- package/src/lib/plugin-runtime/manifest.js +3 -1
- package/src/lib/plugin-runtime/marketplace-artifact-readback.js +671 -31
- package/src/lib/plugin-runtime/marketplace-catalog.js +68 -2
- package/src/lib/plugin-runtime/marketplace-impact.js +208 -15
- package/src/lib/plugin-runtime/marketplace-network.js +317 -0
- package/src/lib/plugin-runtime/marketplace-pac-worker.js +15 -0
- package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +25 -2
- package/src/lib/plugin-runtime/marketplace-source-cache.js +359 -0
- package/src/lib/plugin-runtime/policy.js +21 -6
- package/src/lib/plugin-runtime/publisher-trust.js +332 -0
- package/src/lib/plugin-runtime/remote-source.js +323 -27
- package/src/lib/plugin-runtime/scopes.js +317 -41
- package/src/lib/plugin-runtime/transaction-journal.js +806 -0
- package/src/lib/public-ide-capability-manifest.js +1 -1
- package/src/lib/remote-membership-coordinator.js +8 -6
- package/src/lib/sub-agent-context.js +268 -2
- package/src/lib/task-model-selector.js +1 -1
- package/src/lib/with-file-lock.js +266 -27
- package/src/runtime/agent-core.js +595 -41
- package/src/workers/background-agent-keeper.js +147 -24
- package/src/workers/background-agent-worker.js +383 -134
- package/src/workers/background-turn-bootstrap.js +13 -8
- package/src/assets/web-panel/assets/MobileProjects-Tyd3o2A0.js +0 -1
- package/src/assets/web-panel/assets/Projects-CP8vPvzs.js +0 -1
- package/src/assets/web-panel/assets/Providers-CUnHbNf-.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Dol8jLEP.js +0 -1
- package/src/assets/web-panel/assets/index-C3QX5tOl.js +0 -1
- 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 =
|
|
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
|
-
|
|
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(
|
|
2980
|
-
|
|
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
|
|
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
|
|
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
|
-
!
|
|
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
|
-
!
|
|
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
|
|
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
|
-
!
|
|
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
|
-
!
|
|
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
|
-
!
|
|
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
|
-
|
|
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
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
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
|
-
|
|
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",
|