chainlesschain 0.163.8 → 0.165.1
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 +7 -4
- package/scripts/macos-mcp-launcher-build.mjs +317 -0
- package/scripts/macos-mcp-launcher-postinstall.sh +54 -0
- package/scripts/macos-mcp-launcher-preinstall.sh +61 -0
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-CavM7W3k.js → AIOps-CYxFDrcm.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CUvZGabQ.js → ActionButton-CPBtdiU2.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DeBnWnbe.js → Analytics-Dayq7Dp7.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DbI5kxvd.js → AppLayout-6U7bPb2s.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-D93bigKm.js → Artifacts-D2Iqg_3D.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CBNvfUZa.js → Audit-CdoOB7WV.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-CJLyh_YU.js → BackgroundAgents-DrnAVB8w.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-D_0yoehD.js → Backup-CesMi5cy.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-TJxmbObW.js → BaseInput-DaX9ptax.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CE0dto1q.js → Chat-OrwOPxDS.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZ5-yYiD.js → ChatBubbleRenderer-CvPZFRxM.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-Ck1RVIVo.js → Checkbox-DiHbcysC.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-qpoyf6DK.js → Codegen-CbgCkaBJ.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DUyPWjIE.js → Col-Du5hgrh4.js} +1 -1
- package/src/assets/web-panel/assets/{Community-D_S1BemS.js → Community-DsPOBW-p.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BjxeK-Gc.js → Compact-BwHIFkNC.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Dd3CEJsu.js → Compliance-BvrDQSgd.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-B4QG7-ad.js → Cowork-gwAE6arx.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-BtZnids5.js → Cron-DW-0l4uF.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CHPfCBFD.js → Crosschain-j-NsLesB.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DT8DCEOK.js → DID-CibVEBOI.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-COQab5_U.js → Dashboard-W4z9vSfe.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BY1PsQuU.js → Dropdown-DAcrkKxG.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CsuAuaq3.js → EmailListRenderer-6GXah-89.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BqpinD-j.js → FamilyGuardDashboard-TKDCq8Ku.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-D0gkfQFv.js → Federation-BXBLlcNm.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BqgE-axK.js → FormItemContext-BoJJBkG3.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-D0hJ2KvC.js → GenericCardRenderer-okjsl46v.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CmMB0uPY.js → Git-Rvs7i6nn.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BnVEI0Dk.js → Governance-CHSUxXXS.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-YTAMihRP.js → Inference-V3BlfxsB.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-0ax7VHop.js → KnowledgeGraph-C_NlD4aR.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-B9TRcokL.js → Logs-Nn4LNxhA.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-SVAgNnkt.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-B6CSuD_H.js → Marketplace-DEmjFcg-.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CjMXvOVU.js → McpTools-DlopXipI.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CXRF71U3.js → Memory-BG3ynX60.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DQgNp_oS.js → MobileBridge-DeRzWOfT.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DlPwcCbq.js → MobileProjects-Tyd3o2A0.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-6hDTRPe1.js → Mtc-BoPbOv1C.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-BlVHU1DS.js → MtcAudit-un-rTHp7.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DP9pzEbV.js → Multisig-Ds0oveEB.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Djorh8EV.js → NLProgramming-D09JI-yY.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-oLZ-JkUg.js → Notes-DTRhiKJn.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-Cg288VH3.js → NotificationSettings-BAnnNCim.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Yr-1fSYi.js +1 -0
- package/src/assets/web-panel/assets/{Organization-CBdo-bHs.js → Organization-S9EAolpj.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-qZdnVumx.js → Overflow-CqcLKqJ8.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-D4kNfGNQ.js → P2P-Dw0F9EcU.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DnlMemM7.js → PdhVaultBrowser-BDRwUevN.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-DHucHFBJ.js → Permissions-D3_c3nKB.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BovlOIPR.js → PersonalDataHub-CxdMV98x.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-Deru3CQY.js → Pipeline-CgjNQFLI.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Bq5oTTbU.js → Privacy-BHghIIht.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BfzxVWAz.js → ProjectInit-De6pqaKM.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CKC6XBtH.js → ProjectSettings-Bg4ZkNYQ.js} +2 -2
- package/src/assets/web-panel/assets/Projects-CP8vPvzs.js +1 -0
- package/src/assets/web-panel/assets/{Providers-C_jx4TWX.js → Providers-CUnHbNf-.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BdIHeVGC.js → QrScannerModal-DA2f3Wts.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BwZwAcPD.js → QuickAsk-C1FeHDqp.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BNTt0L-m.js → Recommend-B5guaUMN.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-PDADSds3.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-D6VpG32B.js → Reputation-eTlRhgvK.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D_74OvQQ.js → Row-D7AqSGY0.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BTr0a-BQ.js → RssFeed-JiuLm4Ja.js} +3 -3
- package/src/assets/web-panel/assets/{Search-C3MCFoNU.js → Search-CwruahW3.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CqW9aJ6s.js → Security-CdhqaR0b.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Cw5GNaHF.js → Services-CU0ZIlcT.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DZps5qeY.js → Skeleton-CtduPfAb.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-C6NQ1znT.js → Skills-CROXm6AY.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DFP8G2Sh.js → Sla-DiZiABFD.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-ESt6MB6v.js → SpeechSettings-B5pLcXUw.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BHnN98aM.js → SyncSettings-DBX6J1Oe.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-NiSCaOf-.js → Tasks-BLyRJd3-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DsKFhW2C.js → Templates-BKa5qzmk.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CQ0E0ze5.js → Tenant-DnD3a4M6.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-D_rBu_by.js → Terminal-DEitQeTb.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-D3Vm4Po4.js → TimelineRenderer-CjHggTDq.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DNTxRH6q.js → Tokens-CsGT7sfl.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-Dgprz1ms.js → Trigger-yr4rFDrW.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-B2rUnVBD.js → Trust-CLvu1Sjp.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DEGQNc6_.js → UkeySign-B0m7yUP8.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-jlsAx3Eh.js → VideoEditing-C0VvP4lc.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-8jQih1ij.js → Wallet-kllBAG1M.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-XLeO6xIB.js → WebAuthn-C54UQNLx.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-De_dgDAG.js → WorkflowEditor-B7y8qSvU.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CI19R_Sk.js → chat-DRzTh8CJ.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DsYTOYN3.js → colors-BSMwSnCx.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CM3-t_Qr.js → compact-item-BTg602H5.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BjxlBRZv.js → createContext-B6EGsdvp.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Dol8jLEP.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-RDaWX_4i.js → hasIn-DDKqJ8RD.js} +1 -1
- package/src/assets/web-panel/assets/{index-nAH87mQP.js → index-323Hhxxt.js} +1 -1
- package/src/assets/web-panel/assets/{index-DIUnNOjG.js → index-BICn3Qh1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DyIWssPC.js → index-BMuGd2--.js} +1 -1
- package/src/assets/web-panel/assets/{index-DX7tuWVJ.js → index-BRqt4fO-.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cd3euP_4.js → index-BVTAl5N2.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cpqm1qfU.js → index-BsR7Y2NG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DcPFB8qJ.js → index-Bx044qDj.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cd9OJhHG.js → index-C1GFfuqN.js} +1 -1
- package/src/assets/web-panel/assets/index-C3QX5tOl.js +1 -0
- package/src/assets/web-panel/assets/{index-B-Z8eFUJ.js → index-C66NpWy5.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlT2VAEK.js → index-CDTvsX8t.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dsij_KsN.js → index-CGNklbP6.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4gkzxkw.js → index-CHfLYwcW.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6sm5-Xq.js → index-CNxg61YP.js} +1 -1
- package/src/assets/web-panel/assets/{index-C0yx-jtQ.js → index-CQlHFN_g.js} +1 -1
- package/src/assets/web-panel/assets/{index-CjTrRt13.js → index-CUEU48o1.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7WBt_4T.js → index-CWGax2QW.js} +1 -1
- package/src/assets/web-panel/assets/{index-IeiL9RKc.js → index-C_JzIXUq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CnHH7bnD.js → index-Cf23wVMs.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKJr-TZp.js → index-CmAInUkX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiECJi-Q.js → index-CmAubjDi.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCnZyIMA.js → index-Co7oM1qr.js} +1 -1
- package/src/assets/web-panel/assets/{index-Drlnwr8F.js → index-CwPUZbMI.js} +1 -1
- package/src/assets/web-panel/assets/{index-9R6BxAr0.js → index-D1naNj4U.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGqqJfL0.js → index-D5CsBShd.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bi46Xu4u.js → index-DBT3YeQ9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DXXtGMUJ.js → index-DLdrHhML.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0PMlbMs.js → index-Dc77VR3H.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0zcy54x.js → index-Dfv2lKu3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BN6UPD2E.js → index-DiW0sDkz.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dxge-xWl.js → index-DpTHIRfZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CyOGN6a5.js → index-DqFXGzzC.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9O9YyWE.js → index-XFe-1twF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHNYPO0b.js → index-p2HYn89m.js} +3 -3
- package/src/assets/web-panel/assets/{index-CVQMeiwH.js → index-pqfux8vZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-pPL66J85.js → index-rjzk9z1Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-DoQA0-52.js → index-th2ihDc0.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5REnKt8.js → index-u2YVld9W.js} +1 -1
- package/src/assets/web-panel/assets/index-xYybvEic.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-BTL88of4.js → initDefaultProps-C3Djh9fa.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BUB2pXwB.js → motion-BzqFmbSe.js} +1 -1
- package/src/assets/web-panel/assets/{move-egCkkOMm.js → move-DJo_FFaW.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DAWhZC8c.js → mtc-parser-_tlTiBvk.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CvOxe_6d.js → omit-CB2ZyxUg.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DZUSLZ5Z.js → pickAttrs-DZwIFD4u.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BERxGNMY.js → placementArrow-DxZY0dgk.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DZuDfZ9n.js → responsiveObserve-CzzGt7GL.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DO756sgI.js → slide-BaIK52yz.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BRQlIz28.js → statusUtils-3-FHu9_M.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DCSfdybl.js → styleChecker-BkRT68fK.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-LD8dWVve.js → useFlexGapSupport-DHYwKS-s.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BX9uW2LK.js → useFs-DVVUhV8p.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BPSlPuHU.js → usePersonalDataHub-CkrFKAzQ.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BmOILC9V.js → vnode-BuBLFz0c.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-1iKowKGg.js → zoom-kpm6LtXU.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +3 -3
- package/src/commands/cowork.js +418 -41
- package/src/commands/permissions.js +240 -16
- package/src/commands/plugin.js +1293 -56
- package/src/commands/session-location.js +276 -0
- package/src/commands/session.js +2 -0
- package/src/data/changelog.json +28 -2
- package/src/gateways/ws/action-protocol.js +316 -17
- package/src/gateways/ws/message-dispatcher.js +30 -0
- package/src/gateways/ws/remote-session-protocol.js +565 -35
- package/src/gateways/ws/ws-agent-handler.js +12 -0
- package/src/gateways/ws/ws-approval-gate.js +5 -0
- package/src/gateways/ws/ws-server.js +510 -50
- package/src/harness/jsonl-session-store.js +341 -24
- package/src/harness/mcp-client.js +8 -1
- package/src/harness/remote-session-audit.js +2 -0
- package/src/harness/remote-session-crypto.js +929 -21
- package/src/harness/remote-session-registry.js +945 -24
- package/src/harness/session-list-index.js +233 -14
- package/src/lib/agent-authority.js +6 -0
- package/src/lib/auto-mode-config.js +98 -3
- package/src/lib/background-agent-keeper-client.js +248 -0
- package/src/lib/background-agent-keeper-protocol.js +291 -0
- package/src/lib/background-agent-supervisor.js +1357 -85
- package/src/lib/background-interaction-resolver.js +12 -0
- package/src/lib/background-session-transport.js +23 -6
- package/src/lib/background-turn-bootstrap-protocol.js +190 -0
- package/src/lib/cowork-workflow.js +1188 -76
- package/src/lib/delivery-production-adapter.js +38 -2
- package/src/lib/dynamic-workflow-facade.js +1736 -0
- package/src/lib/execution-location-contract.js +551 -0
- package/src/lib/execution-location-runtime.js +130 -0
- package/src/lib/ide-context.js +94 -6
- package/src/lib/mcp-stdio-package-materialization.js +166 -14
- package/src/lib/packer/pkg-config-generator.js +2 -0
- package/src/lib/permission-authority.js +77 -0
- package/src/lib/permission-side-effect-center.js +495 -0
- package/src/lib/plugin-runtime/install.js +514 -13
- package/src/lib/plugin-runtime/marketplace-artifact-readback.js +900 -0
- package/src/lib/plugin-runtime/marketplace-catalog.js +1181 -0
- package/src/lib/plugin-runtime/marketplace-impact.js +408 -0
- package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +1567 -0
- package/src/lib/plugin-security.js +60 -3
- package/src/lib/process-execution-broker/index.js +1095 -57
- package/src/lib/process-execution-broker/linux-bwrap-descriptor-launch.js +375 -0
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +90 -16
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +159 -18
- package/src/lib/process-execution-broker/macos-mcp-launcher-contract.js +278 -0
- package/src/lib/process-execution-broker/macos-mcp-launcher-protocol.json +23 -0
- package/src/lib/process-execution-broker/macos-mcp-launcher.c +1497 -0
- package/src/lib/process-execution-broker/mcp-fd-entry-bootstrap.js +37 -0
- package/src/lib/process-execution-broker/platform-sandbox.js +2330 -279
- package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
- package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
- package/src/lib/process-execution-broker/windows-sandbox.cs +50 -4
- package/src/lib/public-ide-capability-manifest.js +3 -0
- package/src/lib/remote-approval-bridge.js +777 -34
- package/src/lib/remote-control.js +2 -0
- package/src/lib/remote-membership-authority.js +632 -0
- package/src/lib/remote-membership-coordinator.js +2973 -0
- package/src/lib/remote-membership-host-store.js +1799 -0
- package/src/lib/runtime-provenance-ledger.js +171 -46
- package/src/lib/scoped-permission-store.js +369 -0
- package/src/lib/session-anti-rollback-anchor.js +262 -5
- package/src/lib/shell-approval.js +169 -6
- package/src/lib/sub-agent-context.js +4 -0
- package/src/lib/with-file-lock.js +73 -38
- package/src/lib/workflow-definition-contract.js +149 -0
- package/src/repl/agent-repl.js +111 -36
- package/src/repl/chat-repl.js +2 -0
- package/src/repl/clipboard-image.js +735 -16
- package/src/repl/ide-status.js +1 -1
- package/src/repl/prompt-interactions.js +59 -6
- package/src/repl/remote-approval.js +29 -7
- package/src/runtime/agent-core.js +1182 -155
- package/src/runtime/headless-runner.js +52 -30
- package/src/runtime/headless-stream.js +51 -28
- package/src/workers/background-agent-keeper.js +689 -0
- package/src/workers/background-agent-worker.js +365 -13
- package/src/workers/background-turn-bootstrap.js +170 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-yySN5YNM.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Bb1O3Ofr.js +0 -1
- package/src/assets/web-panel/assets/Projects-CMolGhae.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-Biyn2-Ro.js +0 -5
- package/src/assets/web-panel/assets/devWarning-S1ld2LwU.js +0 -1
- package/src/assets/web-panel/assets/index-Drz6yq0m.js +0 -1
- package/src/assets/web-panel/assets/index-uWiUs7S_.js +0 -1
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Independent lifetime keeper for one detached background-agent worker.
|
|
3
|
+
*
|
|
4
|
+
* The launcher starts this process as a sibling of the worker. The worker must
|
|
5
|
+
* arm an exact turn identity over the private local channel and receive the
|
|
6
|
+
* keeper's durable acknowledgement before releasing Agent main. If the worker
|
|
7
|
+
* disappears, this process remains outside the worker's process group / Job
|
|
8
|
+
* and retires the armed process trees before exiting.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import net from "node:net";
|
|
12
|
+
import { readFileSync, unlinkSync, writeSync } from "node:fs";
|
|
13
|
+
import { resolve } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import { createNdjsonReader } from "../lib/background-session-transport.js";
|
|
16
|
+
import {
|
|
17
|
+
isBackgroundProcessTreeExecutionAlive,
|
|
18
|
+
isProcessAlive,
|
|
19
|
+
isSameProcess,
|
|
20
|
+
mutateBackgroundAgentState,
|
|
21
|
+
readBackgroundAgentState,
|
|
22
|
+
removeJobFile,
|
|
23
|
+
stopBackgroundAgentChildTree,
|
|
24
|
+
} from "../lib/background-agent-supervisor.js";
|
|
25
|
+
import {
|
|
26
|
+
BACKGROUND_AGENT_KEEPER_ARM,
|
|
27
|
+
BACKGROUND_AGENT_KEEPER_ARMED,
|
|
28
|
+
BACKGROUND_AGENT_KEEPER_CLEANUP_CONFIRM_TIMEOUT_MS,
|
|
29
|
+
BACKGROUND_AGENT_KEEPER_HELLO,
|
|
30
|
+
BACKGROUND_AGENT_KEEPER_PERSIST_RETRY_TIMEOUT_MS,
|
|
31
|
+
BACKGROUND_AGENT_KEEPER_PROTOCOL_VERSION,
|
|
32
|
+
BACKGROUND_AGENT_KEEPER_READY,
|
|
33
|
+
BACKGROUND_AGENT_KEEPER_RETIRE,
|
|
34
|
+
BACKGROUND_AGENT_KEEPER_RETIRED,
|
|
35
|
+
BACKGROUND_AGENT_KEEPER_STATE_LOCK_TIMEOUT_MS,
|
|
36
|
+
cleanupBackgroundAgentKeeperPipeDirectory,
|
|
37
|
+
createBackgroundAgentKeeperMessage,
|
|
38
|
+
normalizeBackgroundAgentKeeperHello,
|
|
39
|
+
normalizeBackgroundAgentKeeperTurn,
|
|
40
|
+
sameBackgroundAgentKeeperTurn,
|
|
41
|
+
} from "../lib/background-agent-keeper-protocol.js";
|
|
42
|
+
|
|
43
|
+
const STARTUP_TIMEOUT_MS = 30_000;
|
|
44
|
+
const HEARTBEAT_INTERVAL_MS = 1_000;
|
|
45
|
+
const KEEPER_PERSIST_RETRY_DELAY_MS = 50;
|
|
46
|
+
|
|
47
|
+
function delay(ms) {
|
|
48
|
+
return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Retry a cleanup-critical state mutation after a worker is hard-killed.
|
|
53
|
+
*
|
|
54
|
+
* A worker can disappear while it owns the cross-process state lock. Windows
|
|
55
|
+
* may keep that terminated PID observable briefly, so the strict lock cannot
|
|
56
|
+
* immediately prove the owner dead and correctly fails closed. The keeper is
|
|
57
|
+
* the only remaining owner of the armed turn; it must wait for that transient
|
|
58
|
+
* fence instead of turning a lock exception into an unhandled rejection and
|
|
59
|
+
* exiting with the durable projection still `armed`.
|
|
60
|
+
*/
|
|
61
|
+
export async function retryKeeperPersistence(operation, options = {}) {
|
|
62
|
+
const now = options.now || Date.now;
|
|
63
|
+
const sleep = options.sleep || delay;
|
|
64
|
+
const timeoutMs = Math.max(
|
|
65
|
+
0,
|
|
66
|
+
Number(
|
|
67
|
+
options.timeoutMs ?? BACKGROUND_AGENT_KEEPER_PERSIST_RETRY_TIMEOUT_MS,
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
const retryDelayMs = Math.max(
|
|
71
|
+
1,
|
|
72
|
+
Number(options.retryDelayMs ?? KEEPER_PERSIST_RETRY_DELAY_MS),
|
|
73
|
+
);
|
|
74
|
+
const deadline = now() + timeoutMs;
|
|
75
|
+
let attempts = 0;
|
|
76
|
+
let error = null;
|
|
77
|
+
for (;;) {
|
|
78
|
+
attempts += 1;
|
|
79
|
+
try {
|
|
80
|
+
return { result: operation(), error: null, attempts };
|
|
81
|
+
} catch (cause) {
|
|
82
|
+
error = cause;
|
|
83
|
+
}
|
|
84
|
+
const remaining = deadline - now();
|
|
85
|
+
if (remaining <= 0) return { result: null, error, attempts };
|
|
86
|
+
await sleep(Math.min(retryDelayMs, remaining));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function writeMessage(socket, message) {
|
|
91
|
+
if (!socket || socket.destroyed) return false;
|
|
92
|
+
try {
|
|
93
|
+
socket.write(`${JSON.stringify(message)}\n`);
|
|
94
|
+
return true;
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function keeperWorkerIdentityAlive(
|
|
101
|
+
workerPid,
|
|
102
|
+
workerStartedAt,
|
|
103
|
+
probe = isSameProcess,
|
|
104
|
+
) {
|
|
105
|
+
return probe(Number(workerPid), Number(workerStartedAt));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function reportKeeperPersistenceFailure(stage, error) {
|
|
109
|
+
try {
|
|
110
|
+
writeSync(
|
|
111
|
+
2,
|
|
112
|
+
`[background-agent-keeper] ${stage} persistence failed: ${error?.stack || error?.message || String(error)}\n`,
|
|
113
|
+
);
|
|
114
|
+
} catch {
|
|
115
|
+
// The inherited diagnostic handle may already be closed.
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function runBackgroundAgentKeeperHeartbeat({
|
|
120
|
+
finishing,
|
|
121
|
+
workerSocket,
|
|
122
|
+
authenticatedHello,
|
|
123
|
+
authenticatedWorkerStartedAt,
|
|
124
|
+
armedTurn,
|
|
125
|
+
persistKeeper,
|
|
126
|
+
finishForWorkerDisconnect,
|
|
127
|
+
now = Date.now,
|
|
128
|
+
workerIdentityAlive = keeperWorkerIdentityAlive,
|
|
129
|
+
reportPersistenceFailure = reportKeeperPersistenceFailure,
|
|
130
|
+
}) {
|
|
131
|
+
if (finishing) return false;
|
|
132
|
+
if (
|
|
133
|
+
workerSocket &&
|
|
134
|
+
authenticatedHello &&
|
|
135
|
+
armedTurn &&
|
|
136
|
+
!workerIdentityAlive(
|
|
137
|
+
authenticatedHello.workerPid,
|
|
138
|
+
authenticatedWorkerStartedAt,
|
|
139
|
+
)
|
|
140
|
+
) {
|
|
141
|
+
// A Windows named-pipe handle can remain open after its worker dies if a
|
|
142
|
+
// platform helper retained a duplicate. Socket EOF alone is therefore
|
|
143
|
+
// not a sufficient lifetime signal. The generation-bound worker PID and
|
|
144
|
+
// its durable launch anchor provide an independent identity fence.
|
|
145
|
+
finishForWorkerDisconnect();
|
|
146
|
+
workerSocket.destroy();
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
persistKeeper({ keeperHeartbeatAt: now() });
|
|
151
|
+
return true;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
try {
|
|
154
|
+
reportPersistenceFailure("heartbeat", error);
|
|
155
|
+
} catch {
|
|
156
|
+
// Diagnostics are best effort and must not escape the timer callback.
|
|
157
|
+
}
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function stateOwnsTurn(state, turn) {
|
|
163
|
+
return Boolean(
|
|
164
|
+
state &&
|
|
165
|
+
state.id === turn.id &&
|
|
166
|
+
state.workerGeneration === turn.workerGeneration &&
|
|
167
|
+
state.turnLaunchResolution?.token === turn.turnLaunchToken &&
|
|
168
|
+
Number(state.turnLaunchResolution?.attempt) === turn.attempt &&
|
|
169
|
+
state.turnLaunchResolution?.outcome === "spawned" &&
|
|
170
|
+
Number(state.agentPid) === turn.agentPid &&
|
|
171
|
+
Number(state.agentStartedAt) === turn.agentStartedAt &&
|
|
172
|
+
Number(state.agentRuntimePid) === turn.agentRuntimePid &&
|
|
173
|
+
Number(state.agentRuntimeStartedAt) === turn.agentRuntimeStartedAt,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function persistKeeperTurn(job, turn, patch) {
|
|
178
|
+
return mutateBackgroundAgentState(
|
|
179
|
+
job.id,
|
|
180
|
+
(current) => {
|
|
181
|
+
if (
|
|
182
|
+
!stateOwnsTurn(current, turn) ||
|
|
183
|
+
Number(current.keeperPid) !== process.pid
|
|
184
|
+
) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
return { ...current, ...patch };
|
|
188
|
+
},
|
|
189
|
+
{ timeoutMs: BACKGROUND_AGENT_KEEPER_STATE_LOCK_TIMEOUT_MS },
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function stopBackgroundAgentKeeperTurnTrees(targets, options = {}) {
|
|
194
|
+
const platform = options.platform || process.platform;
|
|
195
|
+
const processAlive = options.isProcessAlive || isProcessAlive;
|
|
196
|
+
const processTreeExecutionAlive =
|
|
197
|
+
options.isProcessTreeExecutionAlive ||
|
|
198
|
+
isBackgroundProcessTreeExecutionAlive;
|
|
199
|
+
const stopTree = options.stopProcessTree || stopBackgroundAgentChildTree;
|
|
200
|
+
const failures = [];
|
|
201
|
+
let precedingTreeStopSucceeded = false;
|
|
202
|
+
for (const target of targets) {
|
|
203
|
+
// On Windows the wrapper's taskkill /T also retires its runtime child.
|
|
204
|
+
// Avoid starting a second bounded taskkill for a target the first tree
|
|
205
|
+
// operation already proved absent; under 20-way churn that redundant
|
|
206
|
+
// command can consume the keeper's entire cleanup SLO. Never skip the
|
|
207
|
+
// first root merely because its leader exited: POSIX descendants can keep
|
|
208
|
+
// executing in the leader's process group and still require a group kill.
|
|
209
|
+
if (precedingTreeStopSucceeded && !processAlive(target.pid)) continue;
|
|
210
|
+
try {
|
|
211
|
+
stopTree(target.pid, {
|
|
212
|
+
signal: "SIGKILL",
|
|
213
|
+
expectedStartedAt: target.startedAt,
|
|
214
|
+
processGroup: target.processGroup,
|
|
215
|
+
strictIdentity: true,
|
|
216
|
+
allowDirectFallback: false,
|
|
217
|
+
});
|
|
218
|
+
precedingTreeStopSucceeded = true;
|
|
219
|
+
} catch (error) {
|
|
220
|
+
// A detached POSIX group can keep executing after its leader exits. Do
|
|
221
|
+
// not discard the strict stop failure from a leader-only liveness probe;
|
|
222
|
+
// the group-aware residual check preserves the actionable identity
|
|
223
|
+
// reason while descendants remain executable.
|
|
224
|
+
const leaderAlive = processAlive(target.pid);
|
|
225
|
+
const residualAlive =
|
|
226
|
+
leaderAlive ||
|
|
227
|
+
(platform !== "win32" &&
|
|
228
|
+
target.processGroup === true &&
|
|
229
|
+
processTreeExecutionAlive(target.pid, target.startedAt));
|
|
230
|
+
if (residualAlive) {
|
|
231
|
+
failures.push(error?.message || String(error));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return failures;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function cleanupTurn(job, turn, reason) {
|
|
239
|
+
const requestedAt = Date.now();
|
|
240
|
+
const requestedPersistence = await retryKeeperPersistence(() =>
|
|
241
|
+
persistKeeperTurn(job, turn, {
|
|
242
|
+
turnKeeperStatus: "cleanup-requested",
|
|
243
|
+
turnKeeperCleanupReason: reason,
|
|
244
|
+
turnKeeperCleanupRequestedAt: requestedAt,
|
|
245
|
+
}),
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
const targets = [
|
|
249
|
+
{
|
|
250
|
+
pid: turn.agentPid,
|
|
251
|
+
startedAt: turn.agentStartedAt,
|
|
252
|
+
processGroup: true,
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
pid: turn.agentRuntimePid,
|
|
256
|
+
startedAt: turn.agentRuntimeStartedAt,
|
|
257
|
+
processGroup: false,
|
|
258
|
+
},
|
|
259
|
+
].filter(
|
|
260
|
+
(target, index, values) =>
|
|
261
|
+
values.findIndex((candidate) => candidate.pid === target.pid) === index,
|
|
262
|
+
);
|
|
263
|
+
const failures = stopBackgroundAgentKeeperTurnTrees(targets);
|
|
264
|
+
|
|
265
|
+
const deadline =
|
|
266
|
+
Date.now() + BACKGROUND_AGENT_KEEPER_CLEANUP_CONFIRM_TIMEOUT_MS;
|
|
267
|
+
let alive = targets.filter((target) =>
|
|
268
|
+
isBackgroundProcessTreeExecutionAlive(target.pid, target.startedAt),
|
|
269
|
+
);
|
|
270
|
+
while (alive.length > 0 && Date.now() < deadline) {
|
|
271
|
+
await new Promise((resolvePromise) => setTimeout(resolvePromise, 10));
|
|
272
|
+
alive = targets.filter((target) =>
|
|
273
|
+
isBackgroundProcessTreeExecutionAlive(target.pid, target.startedAt),
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
const confirmed = alive.length === 0 && failures.length === 0;
|
|
277
|
+
let finalPersistenceAttempts = 0;
|
|
278
|
+
const finalPersistence = await retryKeeperPersistence(() => {
|
|
279
|
+
finalPersistenceAttempts += 1;
|
|
280
|
+
return persistKeeperTurn(job, turn, {
|
|
281
|
+
turnKeeperStatus: confirmed ? "retired" : "cleanup-unconfirmed",
|
|
282
|
+
turnKeeperCleanupReason: reason,
|
|
283
|
+
turnKeeperCleanupRequestedAt: requestedAt,
|
|
284
|
+
turnKeeperCleanupConfirmedAt: confirmed ? Date.now() : null,
|
|
285
|
+
turnKeeperCleanupError:
|
|
286
|
+
failures.length > 0
|
|
287
|
+
? failures.join("; ").slice(0, 1_000)
|
|
288
|
+
: alive.length > 0
|
|
289
|
+
? `process tree still executable: ${alive.map(({ pid }) => pid).join(",")}`
|
|
290
|
+
: null,
|
|
291
|
+
turnKeeperPersistenceRetries:
|
|
292
|
+
Math.max(0, requestedPersistence.attempts - 1) +
|
|
293
|
+
Math.max(0, finalPersistenceAttempts - 1),
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
const persistenceError = finalPersistence.error
|
|
297
|
+
? finalPersistence.error?.message || String(finalPersistence.error)
|
|
298
|
+
: finalPersistence.result?.applied === false
|
|
299
|
+
? "keeper cleanup state no longer owned"
|
|
300
|
+
: null;
|
|
301
|
+
if (persistenceError) failures.push(persistenceError);
|
|
302
|
+
return {
|
|
303
|
+
confirmed: confirmed && !persistenceError,
|
|
304
|
+
failures,
|
|
305
|
+
alive,
|
|
306
|
+
persistenceError,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export async function runBackgroundAgentKeeper(jobFile, options = {}) {
|
|
311
|
+
const job = JSON.parse(readFileSync(jobFile, "utf8"));
|
|
312
|
+
removeJobFile(jobFile);
|
|
313
|
+
const startedAt = Date.now();
|
|
314
|
+
const claim = mutateBackgroundAgentState(job.id, (current) => {
|
|
315
|
+
if (
|
|
316
|
+
!current ||
|
|
317
|
+
current.status !== "running" ||
|
|
318
|
+
current.workerGeneration !== job.workerGeneration ||
|
|
319
|
+
current.keeperGeneration !== job.keeperGeneration
|
|
320
|
+
) {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
...current,
|
|
325
|
+
keeperPid: process.pid,
|
|
326
|
+
keeperStartedAt: startedAt,
|
|
327
|
+
keeperStatus: "listening",
|
|
328
|
+
keeperHeartbeatAt: startedAt,
|
|
329
|
+
};
|
|
330
|
+
});
|
|
331
|
+
if (!claim.applied) return { status: "unclaimed" };
|
|
332
|
+
|
|
333
|
+
if (process.platform !== "win32") {
|
|
334
|
+
try {
|
|
335
|
+
unlinkSync(job.pipePath);
|
|
336
|
+
} catch {
|
|
337
|
+
// Usually ENOENT; the generation-bound endpoint is not reusable.
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
let workerSocket = null;
|
|
342
|
+
let candidateSocket = null;
|
|
343
|
+
let authenticatedHello = null;
|
|
344
|
+
let authenticatedWorkerStartedAt = null;
|
|
345
|
+
let armedTurn = null;
|
|
346
|
+
let finishing = false;
|
|
347
|
+
let finishResolve;
|
|
348
|
+
const finished = new Promise((resolvePromise) => {
|
|
349
|
+
finishResolve = resolvePromise;
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const persistKeeper = (patch) =>
|
|
353
|
+
mutateBackgroundAgentState(job.id, (current) => {
|
|
354
|
+
if (
|
|
355
|
+
!current ||
|
|
356
|
+
current.workerGeneration !== job.workerGeneration ||
|
|
357
|
+
current.keeperGeneration !== job.keeperGeneration ||
|
|
358
|
+
Number(current.keeperPid) !== process.pid
|
|
359
|
+
) {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
return { ...current, ...patch };
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const finishForWorkerDisconnect = () => {
|
|
366
|
+
if (finishing) return false;
|
|
367
|
+
finishing = true;
|
|
368
|
+
const active = armedTurn;
|
|
369
|
+
void (async () => {
|
|
370
|
+
let cleanup;
|
|
371
|
+
try {
|
|
372
|
+
cleanup = active
|
|
373
|
+
? await cleanupTurn(job, active, "worker-disconnected")
|
|
374
|
+
: { confirmed: true, failures: [], alive: [] };
|
|
375
|
+
} catch (error) {
|
|
376
|
+
cleanup = {
|
|
377
|
+
confirmed: false,
|
|
378
|
+
failures: [error?.message || String(error)],
|
|
379
|
+
alive: [],
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
// Closing the keeper must never be skipped because one final metadata
|
|
383
|
+
// update throws. Retry the critical status write, retain the failure in
|
|
384
|
+
// the log, and always settle the server promise without an unhandled
|
|
385
|
+
// rejection.
|
|
386
|
+
const persisted = await retryKeeperPersistence(() =>
|
|
387
|
+
persistKeeper({
|
|
388
|
+
keeperStatus: active
|
|
389
|
+
? cleanup.confirmed
|
|
390
|
+
? "worker-disconnected"
|
|
391
|
+
: "cleanup-unconfirmed"
|
|
392
|
+
: "closed",
|
|
393
|
+
keeperEndedAt: Date.now(),
|
|
394
|
+
...(cleanup.failures.length > 0
|
|
395
|
+
? {
|
|
396
|
+
keeperError: cleanup.failures.join("; ").slice(0, 1_000),
|
|
397
|
+
}
|
|
398
|
+
: {}),
|
|
399
|
+
}),
|
|
400
|
+
);
|
|
401
|
+
if (persisted.error) {
|
|
402
|
+
try {
|
|
403
|
+
writeSync(
|
|
404
|
+
2,
|
|
405
|
+
`[background-agent-keeper] final state persistence failed: ${persisted.error?.stack || persisted.error?.message || String(persisted.error)}\n`,
|
|
406
|
+
);
|
|
407
|
+
} catch {
|
|
408
|
+
// The inherited diagnostic handle may already be closed.
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
server.close(() => finishResolve({ status: "closed" }));
|
|
412
|
+
})();
|
|
413
|
+
return true;
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const server = net.createServer((socket) => {
|
|
417
|
+
let authenticated = false;
|
|
418
|
+
if (workerSocket || candidateSocket) {
|
|
419
|
+
socket.destroy();
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
candidateSocket = socket;
|
|
423
|
+
const helloTimer = setTimeout(() => socket.destroy(), 5_000);
|
|
424
|
+
helloTimer.unref?.();
|
|
425
|
+
socket.on(
|
|
426
|
+
"data",
|
|
427
|
+
createNdjsonReader(
|
|
428
|
+
(message) => {
|
|
429
|
+
if (
|
|
430
|
+
message?.protocolVersion !==
|
|
431
|
+
BACKGROUND_AGENT_KEEPER_PROTOCOL_VERSION
|
|
432
|
+
) {
|
|
433
|
+
socket.destroy();
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (!authenticated) {
|
|
437
|
+
if (message.type !== BACKGROUND_AGENT_KEEPER_HELLO) {
|
|
438
|
+
socket.destroy();
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
let hello;
|
|
442
|
+
try {
|
|
443
|
+
hello = normalizeBackgroundAgentKeeperHello(message);
|
|
444
|
+
} catch {
|
|
445
|
+
socket.destroy();
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const current = readBackgroundAgentState(job.id);
|
|
449
|
+
if (
|
|
450
|
+
hello.id !== job.id ||
|
|
451
|
+
hello.workerGeneration !== job.workerGeneration ||
|
|
452
|
+
hello.token !== job.token ||
|
|
453
|
+
Number(current?.workerClaimedPid) !== hello.workerPid ||
|
|
454
|
+
current?.keeperGeneration !== job.keeperGeneration ||
|
|
455
|
+
Number(current?.keeperPid) !== process.pid
|
|
456
|
+
) {
|
|
457
|
+
socket.destroy();
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
authenticated = true;
|
|
461
|
+
authenticatedHello = hello;
|
|
462
|
+
authenticatedWorkerStartedAt = Number(current.startedAt);
|
|
463
|
+
workerSocket = socket;
|
|
464
|
+
clearTimeout(helloTimer);
|
|
465
|
+
persistKeeper({ keeperStatus: "ready", keeperReadyAt: Date.now() });
|
|
466
|
+
writeMessage(
|
|
467
|
+
socket,
|
|
468
|
+
createBackgroundAgentKeeperMessage(
|
|
469
|
+
BACKGROUND_AGENT_KEEPER_READY,
|
|
470
|
+
{
|
|
471
|
+
id: job.id,
|
|
472
|
+
workerGeneration: job.workerGeneration,
|
|
473
|
+
keeperPid: process.pid,
|
|
474
|
+
},
|
|
475
|
+
),
|
|
476
|
+
);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (
|
|
481
|
+
message.type !== BACKGROUND_AGENT_KEEPER_ARM &&
|
|
482
|
+
message.type !== BACKGROUND_AGENT_KEEPER_RETIRE
|
|
483
|
+
) {
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
let turn;
|
|
487
|
+
try {
|
|
488
|
+
turn = normalizeBackgroundAgentKeeperTurn(message);
|
|
489
|
+
} catch (error) {
|
|
490
|
+
writeMessage(
|
|
491
|
+
socket,
|
|
492
|
+
createBackgroundAgentKeeperMessage(
|
|
493
|
+
"background-agent-keeper-error",
|
|
494
|
+
{
|
|
495
|
+
requestId: message.requestId,
|
|
496
|
+
code: "BACKGROUND_AGENT_KEEPER_INVALID_TURN",
|
|
497
|
+
message: error?.message || String(error),
|
|
498
|
+
},
|
|
499
|
+
),
|
|
500
|
+
);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (message.type === BACKGROUND_AGENT_KEEPER_ARM) {
|
|
505
|
+
const durableTurnState = readBackgroundAgentState(job.id);
|
|
506
|
+
if (
|
|
507
|
+
armedTurn ||
|
|
508
|
+
durableTurnState?.status !== "running" ||
|
|
509
|
+
!stateOwnsTurn(durableTurnState, turn)
|
|
510
|
+
) {
|
|
511
|
+
writeMessage(
|
|
512
|
+
socket,
|
|
513
|
+
createBackgroundAgentKeeperMessage(
|
|
514
|
+
"background-agent-keeper-error",
|
|
515
|
+
{
|
|
516
|
+
requestId: message.requestId,
|
|
517
|
+
code: "BACKGROUND_AGENT_KEEPER_ARM_REJECTED",
|
|
518
|
+
message: "keeper arm did not match the durable turn",
|
|
519
|
+
},
|
|
520
|
+
),
|
|
521
|
+
);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
armedTurn = turn;
|
|
525
|
+
const armedAt = Date.now();
|
|
526
|
+
const armed = persistKeeperTurn(job, turn, {
|
|
527
|
+
turnKeeperStatus: "armed",
|
|
528
|
+
turnKeeperPid: process.pid,
|
|
529
|
+
turnKeeperArmedAt: armedAt,
|
|
530
|
+
});
|
|
531
|
+
if (!armed.applied) {
|
|
532
|
+
armedTurn = null;
|
|
533
|
+
writeMessage(
|
|
534
|
+
socket,
|
|
535
|
+
createBackgroundAgentKeeperMessage(
|
|
536
|
+
"background-agent-keeper-error",
|
|
537
|
+
{
|
|
538
|
+
requestId: message.requestId,
|
|
539
|
+
code: "BACKGROUND_AGENT_KEEPER_ARM_REJECTED",
|
|
540
|
+
message: "keeper arm persistence was rejected",
|
|
541
|
+
},
|
|
542
|
+
),
|
|
543
|
+
);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
writeMessage(
|
|
547
|
+
socket,
|
|
548
|
+
createBackgroundAgentKeeperMessage(
|
|
549
|
+
BACKGROUND_AGENT_KEEPER_ARMED,
|
|
550
|
+
{ requestId: message.requestId, ...turn },
|
|
551
|
+
),
|
|
552
|
+
);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (!sameBackgroundAgentKeeperTurn(turn, armedTurn)) {
|
|
557
|
+
writeMessage(
|
|
558
|
+
socket,
|
|
559
|
+
createBackgroundAgentKeeperMessage(
|
|
560
|
+
"background-agent-keeper-error",
|
|
561
|
+
{
|
|
562
|
+
requestId: message.requestId,
|
|
563
|
+
code: "BACKGROUND_AGENT_KEEPER_RETIRE_REJECTED",
|
|
564
|
+
message: "keeper retire binding mismatch",
|
|
565
|
+
},
|
|
566
|
+
),
|
|
567
|
+
);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
void cleanupTurn(job, turn, "turn-exited").then((result) => {
|
|
571
|
+
if (!result.confirmed) {
|
|
572
|
+
writeMessage(
|
|
573
|
+
socket,
|
|
574
|
+
createBackgroundAgentKeeperMessage(
|
|
575
|
+
"background-agent-keeper-error",
|
|
576
|
+
{
|
|
577
|
+
requestId: message.requestId,
|
|
578
|
+
code: "BACKGROUND_AGENT_KEEPER_CLEANUP_UNCONFIRMED",
|
|
579
|
+
message: "keeper could not confirm process-tree cleanup",
|
|
580
|
+
},
|
|
581
|
+
),
|
|
582
|
+
);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
armedTurn = null;
|
|
586
|
+
writeMessage(
|
|
587
|
+
socket,
|
|
588
|
+
createBackgroundAgentKeeperMessage(
|
|
589
|
+
BACKGROUND_AGENT_KEEPER_RETIRED,
|
|
590
|
+
{ requestId: message.requestId, ...turn },
|
|
591
|
+
),
|
|
592
|
+
);
|
|
593
|
+
});
|
|
594
|
+
},
|
|
595
|
+
() => socket.destroy(),
|
|
596
|
+
),
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
const disconnected = () => {
|
|
600
|
+
clearTimeout(helloTimer);
|
|
601
|
+
if (candidateSocket === socket) candidateSocket = null;
|
|
602
|
+
if (workerSocket !== socket || finishing) return;
|
|
603
|
+
finishForWorkerDisconnect();
|
|
604
|
+
};
|
|
605
|
+
socket.once("close", disconnected);
|
|
606
|
+
socket.once("error", disconnected);
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
const heartbeat = setInterval(
|
|
610
|
+
() =>
|
|
611
|
+
runBackgroundAgentKeeperHeartbeat({
|
|
612
|
+
finishing,
|
|
613
|
+
workerSocket,
|
|
614
|
+
authenticatedHello,
|
|
615
|
+
authenticatedWorkerStartedAt,
|
|
616
|
+
armedTurn,
|
|
617
|
+
persistKeeper,
|
|
618
|
+
finishForWorkerDisconnect,
|
|
619
|
+
}),
|
|
620
|
+
HEARTBEAT_INTERVAL_MS,
|
|
621
|
+
);
|
|
622
|
+
heartbeat.unref?.();
|
|
623
|
+
const startupTimer = setTimeout(
|
|
624
|
+
() => {
|
|
625
|
+
if (workerSocket || finishing) return;
|
|
626
|
+
finishing = true;
|
|
627
|
+
persistKeeper({
|
|
628
|
+
keeperStatus: "startup-timeout",
|
|
629
|
+
keeperEndedAt: Date.now(),
|
|
630
|
+
});
|
|
631
|
+
server.close(() => finishResolve({ status: "startup-timeout" }));
|
|
632
|
+
},
|
|
633
|
+
Number(options.startupTimeoutMs) || STARTUP_TIMEOUT_MS,
|
|
634
|
+
);
|
|
635
|
+
startupTimer.unref?.();
|
|
636
|
+
|
|
637
|
+
await new Promise((resolvePromise, reject) => {
|
|
638
|
+
server.once("error", reject);
|
|
639
|
+
server.listen(job.pipePath, () => {
|
|
640
|
+
server.removeListener("error", reject);
|
|
641
|
+
resolvePromise();
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
const result = await finished;
|
|
645
|
+
clearInterval(heartbeat);
|
|
646
|
+
clearTimeout(startupTimer);
|
|
647
|
+
if (process.platform !== "win32") {
|
|
648
|
+
try {
|
|
649
|
+
unlinkSync(job.pipePath);
|
|
650
|
+
} catch {
|
|
651
|
+
// Already removed or never materialized.
|
|
652
|
+
}
|
|
653
|
+
try {
|
|
654
|
+
cleanupBackgroundAgentKeeperPipeDirectory(job.pipePath);
|
|
655
|
+
} catch {
|
|
656
|
+
// Another keeper can still own a sibling socket in the shared namespace.
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return {
|
|
660
|
+
...result,
|
|
661
|
+
authenticatedWorkerPid: authenticatedHello?.workerPid || null,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function reportFatal(error) {
|
|
666
|
+
try {
|
|
667
|
+
writeSync(
|
|
668
|
+
2,
|
|
669
|
+
`[background-agent-keeper] fatal: ${error?.stack || error?.message || String(error)}\n`,
|
|
670
|
+
);
|
|
671
|
+
} catch {
|
|
672
|
+
// The inherited diagnostic handle may already be closed.
|
|
673
|
+
}
|
|
674
|
+
process.exit(1);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const invokedPath = process.argv[1] ? resolve(process.argv[1]) : null;
|
|
678
|
+
const modulePath = resolve(fileURLToPath(import.meta.url));
|
|
679
|
+
if (
|
|
680
|
+
invokedPath !== null &&
|
|
681
|
+
(process.platform === "win32"
|
|
682
|
+
? invokedPath.toLowerCase() === modulePath.toLowerCase()
|
|
683
|
+
: invokedPath === modulePath)
|
|
684
|
+
) {
|
|
685
|
+
runBackgroundAgentKeeper(process.argv[2]).then(
|
|
686
|
+
() => process.exit(0),
|
|
687
|
+
reportFatal,
|
|
688
|
+
);
|
|
689
|
+
}
|