chainlesschain 0.163.2 → 0.163.4
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 +3 -2
- package/scripts/command-lifecycle-report.mjs +330 -38
- package/src/assets/web-panel/assets/{AIOps-DnGsmsqJ.js → AIOps-DB5X7qqV.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CrO6fcQ7.js → ActionButton-DxHRtKLk.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DsBdOaP2.js → Analytics-DIH5QrxT.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DmQslKUB.js → AppLayout-uu0oxVz7.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BvyrT_QQ.js → Artifacts-BNSMiW9Y.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-prndoLiC.js → Audit-DcAzB4_x.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DOTFyA55.js → BackgroundAgents-ClSsvjIc.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-9zOnPp_8.js → Backup-BMciY0js.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-C4PC6sAG.js → BaseInput-Hn3b57Sh.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BBAF-W9v.js → Chat-CoSlC53V.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CWhvvloO.js → ChatBubbleRenderer-CFevJ3So.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-BI9iculd.js → Checkbox-B4L4Eu4w.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-3DP9BCKb.js → Codegen-C77FgRxF.js} +1 -1
- package/src/assets/web-panel/assets/{Col-df-2TnJX.js → Col-BrLtJLH3.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CHs8gG_A.js → Community-Be7pXXhA.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CDfF6q7V.js → Compact-DuGPecjo.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CthrDnO7.js → Compliance-CzS66mjP.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-HjIqsWJw.js → Cowork-CEhjCoyK.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-BKc196fb.js → Cron-D7ir8Vja.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CJnjdc8W.js → Crosschain-BcXmuLxf.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BHUy-8Q2.js → DID-Cto1dtBy.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-QDB7sKeY.js → Dashboard-B7KjgwOX.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DStcVK11.js → Dropdown-COMqxijg.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BwSUskMJ.js → EmailListRenderer-BZrya-nf.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DgF_kurT.js → FamilyGuardDashboard-AmVeMq0J.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-k8wvv-wN.js → Federation-kZU8c-27.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-C11qoq2r.js → FormItemContext-sOV22v2-.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BIIpr9kg.js → GenericCardRenderer-BUuLtGCz.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BlA1exTe.js → Git-K-ZxsLsH.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CYa1ZYM4.js → Governance-D3OEnawI.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-6_kKVIt6.js → Inference-Bll0gRmP.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CB4AwxYy.js → KnowledgeGraph-DBXwjr53.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-xf7nHFAe.js → Logs-CEzPwEfP.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-Bf3DEqP6.js → MarkdownRenderer-CLmCbHx6.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-aPg8e4Jt.js → Marketplace-BYyRyOPz.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-C-TNlVUf.js → McpTools-DVu8w_rM.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-DMTPIpGa.js → Memory-tenuZ_hO.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BxyvveRq.js → MobileBridge-Bxgy4ozS.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DyPCrAyQ.js → MobileProjects-Dgx1D6sc.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BdhqNXp6.js → Mtc-CRfJ8r8j.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-Cz-HFxzW.js → MtcAudit-BBJq4Aiz.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-CIUaFNsx.js → Multisig-B3ii9lai.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CWe6-J17.js → NLProgramming-FIi-Cubv.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-vYAKeviU.js → Notes-DIq8MPf1.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DQL9-2d7.js → NotificationSettings-BFOD9eGd.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DnXj1jBn.js → OrderTableRenderer--41u7vre.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-B3spU8AN.js → Organization-B_0AWztW.js} +2 -2
- package/src/assets/web-panel/assets/{Overflow-DgEwo7vm.js → Overflow-B6I7TuXv.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-hujMRH8X.js → P2P-Cdf6qens.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-C_qhdg9h.js → PdhVaultBrowser-B_0FcQJY.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-vp7LMcSf.js → Permissions-6nFBjsQp.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Dxr5Hgnq.js → PersonalDataHub-nQYV7xpn.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-DEPZqqH4.js → Pipeline-CBVod5jm.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DTpDiKPe.js → Privacy-D52jW1Nt.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-jDJsRWmF.js → ProjectInit-Bdx3BYhf.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-Dhor5RWH.js → ProjectSettings-b7xr4DUa.js} +2 -2
- package/src/assets/web-panel/assets/Projects-DhfifCKx.js +1 -0
- package/src/assets/web-panel/assets/Providers-Dy_PgDMr.js +1 -0
- package/src/assets/web-panel/assets/{QrScannerModal-CjIw-wIb.js → QrScannerModal-FQKSmc3e.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DX6jcqoi.js → QuickAsk-CSTrhzcF.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DopXkhar.js → Recommend-CxMABOUh.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-BLvqJE9c.js → RemoteSession-Cv2T8yRa.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-DhRj2Me7.js → Reputation-CE1k4Vvu.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DQ53vuPq.js → Row-BNgNcAV_.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BslvwiBD.js → RssFeed-5ZiBZ3ph.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DJKBDNKZ.js → Search-mDOIF1UV.js} +1 -1
- package/src/assets/web-panel/assets/{Security-COhpxHER.js → Security-CtuCTC-e.js} +3 -3
- package/src/assets/web-panel/assets/{Services-dbulD6cI.js → Services-DxFO2Zg7.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-ciIVNfhC.js → Skeleton-CKmKkH9y.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-AiRTkTCk.js → Skills-Bmzl9qkk.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DDMcWYBc.js → Sla-9RqaWHOC.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BPxWbpXO.js → SpeechSettings-CGdzFIYw.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BoEOXCyg.js → SyncSettings-CvNuCeBT.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-B_sLdcnw.js +1 -0
- package/src/assets/web-panel/assets/{Templates-UowY8Ixe.js → Templates-D9_YijdK.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DauSLQXN.js → Tenant-Cuc7BUSP.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DBj7Jneq.js → Terminal-6VYRnJgd.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-UMrPwoZC.js → TimelineRenderer-GtK8xd8Y.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-4ea96-bn.js → Tokens-BZ94Lt5W.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger--ISCqt4b.js → Trigger-DEcZgDaX.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-D4GVimRJ.js → Trust-CC3z9THe.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-Ci-nXRQc.js → UkeySign-Cvtw8cV3.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Cjk7mKPw.js → VideoEditing-CL4e6Bn3.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BgOLOO3Z.js → Wallet-ycEemP27.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-CCX0K29i.js → WebAuthn-DgZ4X_aw.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-Ch3zgIsE.js → WorkflowEditor-D0qMhSv7.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DKOU_2Vb.js → chat-DsAg01Zn.js} +1 -1
- package/src/assets/web-panel/assets/{colors-6OVo4u3C.js → colors-B0oqAhrQ.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BH78Hlsq.js → compact-item-CmX0Rabz.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CFRpSbbC.js → createContext-Be4cn1Ub.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CP2qzG6D.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DNQhgcVi.js → hasIn-D5v12kVq.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkN3iTTp.js → index-0eWLkQKT.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkNEMANR.js → index-301ogenv.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3RtTT4K.js → index-5LGY0JbG.js} +1 -1
- package/src/assets/web-panel/assets/{index-dDkoL4Pe.js → index-7VKDn-t1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHa4WyTZ.js → index-8WvYGnON.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaYqY35Z.js → index-B1YGjOWC.js} +1 -1
- package/src/assets/web-panel/assets/{index-bKm3TUDp.js → index-B3mUHuVc.js} +1 -1
- package/src/assets/web-panel/assets/{index--rq90rL9.js → index-BA4oulaP.js} +1 -1
- package/src/assets/web-panel/assets/{index-DaDgdJYj.js → index-BBREnn1V.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_Z-YoxX.js → index-BBvyHguy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkqHgG-b.js → index-BKmsS2jj.js} +1 -1
- package/src/assets/web-panel/assets/{index-CrcLHN2F.js → index-BRIqLy1m.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaVE6E8y.js → index-BXGSbelg.js} +3 -3
- package/src/assets/web-panel/assets/{index-BYrnm5Ob.js → index-BaE0Ui07.js} +1 -1
- package/src/assets/web-panel/assets/{index-BDSR7ecX.js → index-BoAIAZ5C.js} +1 -1
- package/src/assets/web-panel/assets/{index-CNuCHHQT.js → index-BqltWojQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Eag8e_mA.js → index-CGiIyTZw.js} +1 -1
- package/src/assets/web-panel/assets/index-CKB0vbJU.js +1 -0
- package/src/assets/web-panel/assets/{index-BepBTpa8.js → index-CZ2wUEgr.js} +1 -1
- package/src/assets/web-panel/assets/{index-HNGGVF6o.js → index-CZpfzcuy.js} +1 -1
- package/src/assets/web-panel/assets/{index-AuBhLqOm.js → index-Ckrk3pIV.js} +1 -1
- package/src/assets/web-panel/assets/{index-7TC3S8SN.js → index-CqoCZ5vR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFPJ8yzF.js → index-CyWBir7N.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bb6kDrUx.js → index-CyyGTMKd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKN2noIF.js → index-D0BSkxYQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Du0rnksC.js → index-D5YVopyr.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxRy5LPR.js → index-DKUBj36_.js} +1 -1
- package/src/assets/web-panel/assets/{index-sm7W8AXy.js → index-DM724NgU.js} +1 -1
- package/src/assets/web-panel/assets/{index-BujsOZfc.js → index-DQRsQen0.js} +1 -1
- package/src/assets/web-panel/assets/index-DgoXmwqB.js +1 -0
- package/src/assets/web-panel/assets/{index-Y9MJv28e.js → index-DikJ0Qqe.js} +1 -1
- package/src/assets/web-panel/assets/{index-caye7AOE.js → index-Dn2dSdXx.js} +1 -1
- package/src/assets/web-panel/assets/{index-cdf7fd7d.js → index-DyUaFWEk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnnXQot_.js → index-Hfe_dEGx.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvHGtNnF.js → index-_JjRLSD0.js} +1 -1
- package/src/assets/web-panel/assets/{index-nbfHl1oV.js → index-frSojpHj.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cu3L4r5l.js → index-jK3ax6Z6.js} +1 -1
- package/src/assets/web-panel/assets/{index-D84id2Y4.js → index-qMuZ-NRR.js} +1 -1
- package/src/assets/web-panel/assets/{index-hPY0fFrd.js → index-q_fQ94SE.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BZ9w6jPL.js → initDefaultProps-D4VdhJ9_.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BnwUiOho.js → motion-CZJA0MQW.js} +1 -1
- package/src/assets/web-panel/assets/{move-ByUKJnNm.js → move-BqEs7Sb1.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-V7yK1KEl.js → mtc-parser-BpHEtsu1.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BEXBBt4j.js → omit-BW94tY9W.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-qDm_46Gi.js → pickAttrs-BnBjPWP9.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BIbhEo_0.js → placementArrow-uUkjQ_hu.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-NbPbDgA_.js → responsiveObserve-B-klUqXK.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BnUWglZ0.js → slide-CvY1cNAs.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BX8_wZI5.js → statusUtils-Ix5sEDT2.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BV4OycQX.js → styleChecker-C8qcXOne.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Od_H5llY.js → useFlexGapSupport-KNF6Kj8f.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BWI-PyiX.js → useFs-DGJr345n.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BqMuhG3g.js → usePersonalDataHub-BboN99rI.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DNd23z6f.js → vnode-Bgt8Vgte.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-Cjaxnm7l.js → zoom-BhJtErgt.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +2 -2
- package/src/commands/agenda.js +6 -5
- package/src/commands/agent.js +237 -75
- package/src/commands/agents.js +6 -0
- package/src/commands/artifacts.js +98 -0
- package/src/commands/background-session.js +11 -1
- package/src/commands/mcp.js +218 -31
- package/src/commands/review.js +221 -22
- package/src/commands/session-mcp-recovery.js +5 -5
- package/src/data/changelog.json +27 -1
- package/src/gateways/ws/ws-agent-handler.js +492 -14
- package/src/harness/jsonl-session-store.js +39 -1
- package/src/harness/mcp-client.js +414 -71
- package/src/harness/prompt-compressor.js +24 -0
- package/src/harness/provider-backed-compaction.js +205 -0
- package/src/harness/worktree-isolator.js +26 -3
- package/src/lib/agent-worktree.js +41 -2
- package/src/lib/app-preview.js +3 -0
- package/src/lib/background-agent-supervisor.js +790 -186
- package/src/lib/background-command-argv.js +318 -0
- package/src/lib/background-launch-profile.js +1 -0
- package/src/lib/background-phase-reporter.js +22 -2
- package/src/lib/background-worktree-policy.js +109 -0
- package/src/lib/command-lifecycle-report.js +1340 -145
- package/src/lib/cowork-mcp-tools.js +51 -6
- package/src/lib/cowork-task-runner.js +1 -0
- package/src/lib/delivery-coordinator.js +30 -5
- package/src/lib/delivery-production-adapter.js +1777 -0
- package/src/lib/delivery-production-runner.js +240 -0
- package/src/lib/durable-security-store.js +29 -17
- package/src/lib/execution-trace/trace-context.cjs +179 -0
- package/src/lib/execution-trace/trace-context.js +8 -175
- package/src/lib/mcp-call-ledger-store.js +58 -6
- package/src/lib/mcp-headers-helper.js +76 -8
- package/src/lib/mcp-host-recovery-runtime.js +2 -0
- package/src/lib/mcp-ledger-recovery-admission.js +39 -3
- package/src/lib/mcp-recovery-adjudication.js +256 -19
- package/src/lib/mcp-sandbox-policy.js +184 -0
- package/src/lib/mcp-stdio-capsule-builder-worker.cjs +495 -0
- package/src/lib/mcp-stdio-executable-identity.js +33 -2
- package/src/lib/mcp-stdio-execution-authority.js +163 -0
- package/src/lib/mcp-stdio-immutable-vfs-resolver.cjs +830 -0
- package/src/lib/mcp-stdio-package-materialization.js +721 -333
- package/src/lib/mcp-stdio-workspace-authority.js +252 -0
- package/src/lib/observability/index.js +2 -0
- package/src/lib/observability/otlp-exporter.js +2 -2
- package/src/lib/packer/pack-update-applier.js +8 -1
- package/src/lib/packer/pkg-config-generator.js +34 -0
- package/src/lib/plugin-runtime/sandbox-policy.js +42 -2
- package/src/lib/process-execution-broker/index.js +213 -109
- package/src/lib/process-execution-broker/platform-sandbox.js +1024 -169
- package/src/lib/process-tree-termination.js +17 -1
- package/src/lib/scheduler-kernel/contract.js +315 -0
- package/src/lib/scheduler-kernel/store.js +1345 -0
- package/src/lib/session-host-lease.js +403 -30
- package/src/lib/session-persistence-failure.js +4 -1
- package/src/lib/skill-mcp.js +65 -4
- package/src/lib/sub-agent-context.js +8 -0
- package/src/lib/web-fetch.js +4 -4
- package/src/repl/agent-repl.js +285 -38
- package/src/runtime/agent-core.js +1044 -90
- package/src/runtime/agent-runtime.js +61 -8
- package/src/runtime/headless-runner.js +260 -166
- package/src/runtime/headless-stream.js +254 -25
- package/src/runtime/mcp-config.js +235 -28
- package/src/workers/background-agent-worker.js +492 -71
- package/src/assets/web-panel/assets/Projects-08xUDZGt.js +0 -1
- package/src/assets/web-panel/assets/Providers-BGs47s1a.js +0 -1
- package/src/assets/web-panel/assets/Tasks-CEYFJwmr.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DEMblAIm.js +0 -1
- package/src/assets/web-panel/assets/index-BTvq-36D.js +0 -1
- package/src/assets/web-panel/assets/index-hUpvk6Jl.js +0 -1
|
@@ -16,12 +16,15 @@ import { readFileSync, writeSync } from "node:fs";
|
|
|
16
16
|
import { randomBytes } from "node:crypto";
|
|
17
17
|
import {
|
|
18
18
|
DEFAULT_HEARTBEAT_INTERVAL_MS,
|
|
19
|
+
DEFAULT_HEARTBEAT_STALE_MS,
|
|
19
20
|
backgroundAgentsDir,
|
|
21
|
+
claimBackgroundAgentHeartbeat,
|
|
22
|
+
insertArgumentsBeforeOptionTerminator,
|
|
23
|
+
mutateBackgroundAgentState,
|
|
20
24
|
openBackgroundLogFile,
|
|
21
25
|
readBackgroundAgentState,
|
|
22
26
|
removeJobFile,
|
|
23
27
|
stopBackgroundAgentChildTree,
|
|
24
|
-
writeBackgroundAgentState,
|
|
25
28
|
} from "../lib/background-agent-supervisor.js";
|
|
26
29
|
import { startBackgroundSessionServer } from "../lib/background-session-transport.js";
|
|
27
30
|
import { idlePhaseFor } from "../lib/background-agent-phase.js";
|
|
@@ -32,6 +35,11 @@ import {
|
|
|
32
35
|
updateBackgroundInteractionJournal,
|
|
33
36
|
} from "../lib/background-interaction-journal.js";
|
|
34
37
|
import executionBroker from "../lib/process-execution-broker/index.js";
|
|
38
|
+
import { agentPrintArgument } from "../lib/background-command-argv.js";
|
|
39
|
+
import {
|
|
40
|
+
shouldRetryOwnedProcessTreeTermination,
|
|
41
|
+
terminateOwnedProcessTree,
|
|
42
|
+
} from "../lib/process-tree-termination.js";
|
|
35
43
|
|
|
36
44
|
const jobFile = process.argv[2];
|
|
37
45
|
let job;
|
|
@@ -46,6 +54,8 @@ let lastExit = { code: 0, signal: null };
|
|
|
46
54
|
let transportState = null;
|
|
47
55
|
let detachInteractionHandler = null;
|
|
48
56
|
let interactionJournal = null;
|
|
57
|
+
let turnLaunchUncertainty = null;
|
|
58
|
+
let turnLaunchSettlement = null;
|
|
49
59
|
const promptQueue = [];
|
|
50
60
|
// P0-2: 保存 pending 的交互请求,等 attach 客户端连接后转发
|
|
51
61
|
const pendingInteractions = new Map();
|
|
@@ -75,51 +85,106 @@ function persistInteractionSettlement(requestId, binding, outcome) {
|
|
|
75
85
|
}
|
|
76
86
|
|
|
77
87
|
function mergeState(patch) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
88
|
+
return mutateBackgroundAgentState(job.id, (current) => {
|
|
89
|
+
if (!current || current.status !== "running" || current.stopRequestedAt) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return { ...current, id: job.id, ...patch };
|
|
93
|
+
}).state;
|
|
82
94
|
}
|
|
83
95
|
|
|
84
96
|
function writeHeartbeat() {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
writeBackgroundAgentState({
|
|
88
|
-
...current,
|
|
89
|
-
id: job.id,
|
|
97
|
+
const mutation = claimBackgroundAgentHeartbeat(job.id, {
|
|
98
|
+
workerGeneration: job.workerGeneration,
|
|
90
99
|
pid: process.pid,
|
|
91
100
|
workerPid: process.pid,
|
|
92
|
-
agentPid: child
|
|
93
|
-
|
|
101
|
+
...(child?.pid ? { agentPid: child.pid } : {}),
|
|
102
|
+
...(turnLaunchUncertainty || {}),
|
|
94
103
|
heartbeatAt: Date.now(),
|
|
95
104
|
// Re-assert the transport endpoint so a launcher write racing the
|
|
96
105
|
// worker's initial merge self-heals within one heartbeat.
|
|
97
106
|
...(transportState ? { transport: transportState } : {}),
|
|
98
107
|
});
|
|
99
|
-
|
|
108
|
+
const applied =
|
|
109
|
+
mutation.applied &&
|
|
110
|
+
mutation.state?.status === "running" &&
|
|
111
|
+
Number(mutation.state.workerPid) === process.pid &&
|
|
112
|
+
(!mutation.state.workerGeneration ||
|
|
113
|
+
mutation.state.workerGeneration === job.workerGeneration);
|
|
114
|
+
if (applied && turnLaunchSettlement?.termination?.confirmed === true) {
|
|
115
|
+
scheduleTurnLaunchSettlementPersistence(0);
|
|
116
|
+
}
|
|
117
|
+
return applied;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function waitForLaunchFinalization() {
|
|
121
|
+
const deadline = Date.now() + DEFAULT_HEARTBEAT_STALE_MS;
|
|
122
|
+
for (;;) {
|
|
123
|
+
const current = readBackgroundAgentState(job.id);
|
|
124
|
+
if (!current) return "terminal";
|
|
125
|
+
if (current.status && current.status !== "running") return "terminal";
|
|
126
|
+
if (current.launchFinalizationUncertain !== true) return "ready";
|
|
127
|
+
if (Date.now() >= deadline) {
|
|
128
|
+
mergeState({
|
|
129
|
+
status: "failed",
|
|
130
|
+
endedAt: Date.now(),
|
|
131
|
+
exitCode: 1,
|
|
132
|
+
error: "background launcher did not finalize process ownership",
|
|
133
|
+
phase: null,
|
|
134
|
+
transport: null,
|
|
135
|
+
launchFinalizationUncertain: false,
|
|
136
|
+
});
|
|
137
|
+
return "timeout";
|
|
138
|
+
}
|
|
139
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
140
|
+
}
|
|
100
141
|
}
|
|
101
142
|
|
|
102
143
|
function finalize(code, signal, errorMessage) {
|
|
144
|
+
// A launch settlement owns the worker lifetime until its tree/no-spawn
|
|
145
|
+
// result is durably recorded. Transport detach, a queued prompt, or a fatal
|
|
146
|
+
// callback must not exit through finalize and strand that retry protocol.
|
|
147
|
+
if (turnLaunchSettlement) return;
|
|
103
148
|
if (finalized) return;
|
|
104
149
|
finalized = true;
|
|
105
150
|
clearInterval(heartbeat);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
151
|
+
try {
|
|
152
|
+
mutateBackgroundAgentState(job.id, (current) => {
|
|
153
|
+
if (!current) return null;
|
|
154
|
+
const ownsRecord =
|
|
155
|
+
(!current.workerGeneration ||
|
|
156
|
+
current.workerGeneration === job.workerGeneration) &&
|
|
157
|
+
(Number(current.workerClaimedPid) === process.pid ||
|
|
158
|
+
(current.workerClaimedPid == null &&
|
|
159
|
+
Number(current.workerPid ?? current.pid) === process.pid));
|
|
160
|
+
if (!ownsRecord) return null;
|
|
161
|
+
// An external stop/remove owns an already-terminal or deleted record.
|
|
162
|
+
if (current.status !== "running") {
|
|
163
|
+
return { ...current, phase: null, transport: null };
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
...current,
|
|
167
|
+
id: job.id,
|
|
168
|
+
status: errorMessage ? "failed" : code === 0 ? "completed" : "failed",
|
|
169
|
+
endedAt: Date.now(),
|
|
170
|
+
exitCode: errorMessage ? (code ?? 1) : code,
|
|
171
|
+
signal: signal || null,
|
|
172
|
+
...(errorMessage ? { error: errorMessage } : {}),
|
|
173
|
+
phase: null,
|
|
174
|
+
transport: null,
|
|
175
|
+
};
|
|
120
176
|
});
|
|
121
|
-
}
|
|
122
|
-
|
|
177
|
+
} catch (error) {
|
|
178
|
+
try {
|
|
179
|
+
writeSync(
|
|
180
|
+
2,
|
|
181
|
+
`[background-agent-worker] final state persistence failed: ${
|
|
182
|
+
error?.message || String(error)
|
|
183
|
+
}\n`,
|
|
184
|
+
);
|
|
185
|
+
} catch {
|
|
186
|
+
/* inherited stderr may already be closed */
|
|
187
|
+
}
|
|
123
188
|
}
|
|
124
189
|
const done = () => {
|
|
125
190
|
try {
|
|
@@ -137,43 +202,351 @@ function finalize(code, signal, errorMessage) {
|
|
|
137
202
|
}
|
|
138
203
|
}
|
|
139
204
|
|
|
205
|
+
function scheduleTurnLaunchSettlementRetry(context, delayMs = 250) {
|
|
206
|
+
if (
|
|
207
|
+
!context ||
|
|
208
|
+
turnLaunchSettlement !== context ||
|
|
209
|
+
context.retryTimer ||
|
|
210
|
+
(context.termination?.confirmed !== true &&
|
|
211
|
+
context.terminationRetryBlocked === true)
|
|
212
|
+
) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
context.retryTimer = setTimeout(
|
|
216
|
+
() => {
|
|
217
|
+
context.retryTimer = null;
|
|
218
|
+
if (turnLaunchSettlement !== context) return;
|
|
219
|
+
if (context.termination?.confirmed === true) {
|
|
220
|
+
persistConfirmedTurnLaunchSettlement(context);
|
|
221
|
+
} else {
|
|
222
|
+
void settleUncertainTurnLaunch(context);
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
Math.max(0, delayMs),
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function scheduleTurnLaunchSettlementPersistence(delayMs = 100) {
|
|
230
|
+
const context = turnLaunchSettlement;
|
|
231
|
+
if (!context || context.termination?.confirmed !== true) return;
|
|
232
|
+
scheduleTurnLaunchSettlementRetry(context, delayMs);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function persistConfirmedTurnLaunchSettlement(context) {
|
|
236
|
+
try {
|
|
237
|
+
mutateBackgroundAgentState(job.id, (current) => {
|
|
238
|
+
const sameWorker =
|
|
239
|
+
current?.workerGeneration === job.workerGeneration &&
|
|
240
|
+
Number(current?.workerClaimedPid) === process.pid;
|
|
241
|
+
const tokenMatches =
|
|
242
|
+
current?.turnLaunchToken === context.turnLaunchToken ||
|
|
243
|
+
current?.turnLaunchIntent?.token === context.turnLaunchToken;
|
|
244
|
+
const ownedPid = Number(context.owned?.pid);
|
|
245
|
+
if (
|
|
246
|
+
!current ||
|
|
247
|
+
(!tokenMatches && !sameWorker) ||
|
|
248
|
+
(Number.isInteger(ownedPid) &&
|
|
249
|
+
ownedPid > 0 &&
|
|
250
|
+
current.agentPid != null &&
|
|
251
|
+
Number(current.agentPid) !== ownedPid)
|
|
252
|
+
) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
return {
|
|
256
|
+
...current,
|
|
257
|
+
status: current.stopRequestedAt ? "stopped" : "failed",
|
|
258
|
+
endedAt: current.endedAt || Date.now(),
|
|
259
|
+
exitCode: current.exitCode ?? 1,
|
|
260
|
+
error: `background turn launch failed after spawn: ${
|
|
261
|
+
context.error?.message || "unknown post-spawn failure"
|
|
262
|
+
}`,
|
|
263
|
+
phase: null,
|
|
264
|
+
transport: null,
|
|
265
|
+
turnLaunchIntent: null,
|
|
266
|
+
turnLaunchResolution: {
|
|
267
|
+
token: context.turnLaunchToken,
|
|
268
|
+
attempt: context.turnLaunchAttempt,
|
|
269
|
+
outcome: context.owned ? "terminated" : "not-spawned",
|
|
270
|
+
...(context.owned ? { agentPid: ownedPid } : {}),
|
|
271
|
+
resolvedAt: Date.now(),
|
|
272
|
+
},
|
|
273
|
+
turnLaunchFinalizationUncertain: false,
|
|
274
|
+
turnLaunchToken: null,
|
|
275
|
+
turnLaunchError: null,
|
|
276
|
+
turnLaunchTermination: {
|
|
277
|
+
confirmed: true,
|
|
278
|
+
treeMode: context.termination.treeMode || null,
|
|
279
|
+
closed: context.termination.closed === true,
|
|
280
|
+
treeTerminated: context.termination.treeTerminated === true,
|
|
281
|
+
settledAt: Date.now(),
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
});
|
|
285
|
+
// A missing or incompatible record must not be recreated or retargeted.
|
|
286
|
+
// The owned tree is already confirmed gone, so it is safe for this worker
|
|
287
|
+
// to close even though the retained record (if any) stays fail-closed.
|
|
288
|
+
clearInterval(context.keeper);
|
|
289
|
+
turnLaunchUncertainty = null;
|
|
290
|
+
turnLaunchSettlement = null;
|
|
291
|
+
child = null;
|
|
292
|
+
finalize(
|
|
293
|
+
1,
|
|
294
|
+
null,
|
|
295
|
+
context.error?.message || "background turn launch failed",
|
|
296
|
+
);
|
|
297
|
+
} catch {
|
|
298
|
+
scheduleTurnLaunchSettlementPersistence();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function settleUncertainTurnLaunch(context) {
|
|
303
|
+
if (!context || turnLaunchSettlement !== context || context.settling) return;
|
|
304
|
+
if (context.termination?.confirmed === true) {
|
|
305
|
+
persistConfirmedTurnLaunchSettlement(context);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
if (
|
|
309
|
+
!shouldRetryOwnedProcessTreeTermination(context.owned, context.termination)
|
|
310
|
+
) {
|
|
311
|
+
context.terminationRetryBlocked = true;
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
context.settling = true;
|
|
315
|
+
try {
|
|
316
|
+
context.termination = await terminateOwnedProcessTree(context.owned, {
|
|
317
|
+
treeMode: process.platform === "win32" ? "windows-tree" : "posix-group",
|
|
318
|
+
});
|
|
319
|
+
} catch (error) {
|
|
320
|
+
context.terminationError = error?.message || String(error);
|
|
321
|
+
} finally {
|
|
322
|
+
context.settling = false;
|
|
323
|
+
}
|
|
324
|
+
if (context.termination?.confirmed === true) {
|
|
325
|
+
persistConfirmedTurnLaunchSettlement(context);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (
|
|
329
|
+
!shouldRetryOwnedProcessTreeTermination(context.owned, context.termination)
|
|
330
|
+
) {
|
|
331
|
+
// The root is closed but descendants were not proven gone. Its numeric
|
|
332
|
+
// pid/pgid can now be reused, so another taskkill/kill would risk an
|
|
333
|
+
// unrelated process tree. Retain the durable uncertainty and the keeper;
|
|
334
|
+
// only explicit recovery with stronger identity evidence may proceed.
|
|
335
|
+
context.terminationRetryBlocked = true;
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
context.terminationAttempts = Number(context.terminationAttempts || 0) + 1;
|
|
339
|
+
const retryMs = Math.min(
|
|
340
|
+
5_000,
|
|
341
|
+
250 * 2 ** Math.min(context.terminationAttempts, 4),
|
|
342
|
+
);
|
|
343
|
+
scheduleTurnLaunchSettlementRetry(context, retryMs);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function beginTurnLaunchSettlement({
|
|
347
|
+
owned,
|
|
348
|
+
error,
|
|
349
|
+
turnLaunchToken,
|
|
350
|
+
turnLaunchAttempt,
|
|
351
|
+
agentStartedAt,
|
|
352
|
+
}) {
|
|
353
|
+
child = owned || null;
|
|
354
|
+
phase = "turn_launch_uncertain";
|
|
355
|
+
const ownedPid = Number(owned?.pid);
|
|
356
|
+
turnLaunchUncertainty = {
|
|
357
|
+
...(owned
|
|
358
|
+
? { agentPid: ownedPid, agentStartedAt: agentStartedAt || Date.now() }
|
|
359
|
+
: {}),
|
|
360
|
+
phase,
|
|
361
|
+
turnLaunchIntent: null,
|
|
362
|
+
turnLaunchResolution: {
|
|
363
|
+
token: turnLaunchToken,
|
|
364
|
+
attempt: turnLaunchAttempt,
|
|
365
|
+
outcome: owned ? "spawned" : "not-spawned",
|
|
366
|
+
...(owned ? { agentPid: ownedPid } : {}),
|
|
367
|
+
resolvedAt: Date.now(),
|
|
368
|
+
},
|
|
369
|
+
turnLaunchFinalizationUncertain: true,
|
|
370
|
+
turnLaunchError: error?.message || String(error),
|
|
371
|
+
turnLaunchToken,
|
|
372
|
+
};
|
|
373
|
+
const settlement = {
|
|
374
|
+
owned,
|
|
375
|
+
error,
|
|
376
|
+
turnLaunchToken,
|
|
377
|
+
turnLaunchAttempt,
|
|
378
|
+
termination: owned
|
|
379
|
+
? null
|
|
380
|
+
: {
|
|
381
|
+
confirmed: true,
|
|
382
|
+
treeMode: "none",
|
|
383
|
+
closed: true,
|
|
384
|
+
treeTerminated: true,
|
|
385
|
+
},
|
|
386
|
+
terminationAttempts: 0,
|
|
387
|
+
terminationRetryBlocked: false,
|
|
388
|
+
settling: false,
|
|
389
|
+
retryTimer: null,
|
|
390
|
+
keeper: null,
|
|
391
|
+
};
|
|
392
|
+
settlement.keeper = setInterval(() => {
|
|
393
|
+
if (turnLaunchSettlement !== settlement) return;
|
|
394
|
+
if (settlement.termination?.confirmed === true) {
|
|
395
|
+
scheduleTurnLaunchSettlementPersistence(0);
|
|
396
|
+
} else if (settlement.terminationRetryBlocked !== true) {
|
|
397
|
+
scheduleTurnLaunchSettlementRetry(settlement, 0);
|
|
398
|
+
}
|
|
399
|
+
}, 1_000);
|
|
400
|
+
turnLaunchSettlement = settlement;
|
|
401
|
+
try {
|
|
402
|
+
mutateBackgroundAgentState(job.id, (current) =>
|
|
403
|
+
current ? { ...current, ...turnLaunchUncertainty } : null,
|
|
404
|
+
);
|
|
405
|
+
} catch {
|
|
406
|
+
// The durable prepare record remains the stop/cleanup fence. Settlement
|
|
407
|
+
// retries both tree termination and the final confirmation write.
|
|
408
|
+
}
|
|
409
|
+
if (settlement.termination?.confirmed === true) {
|
|
410
|
+
persistConfirmedTurnLaunchSettlement(settlement);
|
|
411
|
+
} else {
|
|
412
|
+
void settleUncertainTurnLaunch(settlement);
|
|
413
|
+
}
|
|
414
|
+
return "uncertain";
|
|
415
|
+
}
|
|
416
|
+
|
|
140
417
|
function startTurn(argv, promptText) {
|
|
418
|
+
if (!writeHeartbeat()) return false;
|
|
419
|
+
const nextTurn = turnCount + 1;
|
|
420
|
+
const turnLaunchToken = randomBytes(16).toString("hex");
|
|
421
|
+
let turnLaunchAttempt = 0;
|
|
422
|
+
let prepared;
|
|
423
|
+
try {
|
|
424
|
+
prepared = mutateBackgroundAgentState(job.id, (current) => {
|
|
425
|
+
if (
|
|
426
|
+
!current ||
|
|
427
|
+
current.status !== "running" ||
|
|
428
|
+
current.stopRequestedAt ||
|
|
429
|
+
current.turnLaunchIntent ||
|
|
430
|
+
current.turnLaunchFinalizationUncertain === true ||
|
|
431
|
+
Number(current.workerPid) !== process.pid ||
|
|
432
|
+
Number(current.workerClaimedPid) !== process.pid ||
|
|
433
|
+
(current.workerGeneration &&
|
|
434
|
+
current.workerGeneration !== job.workerGeneration)
|
|
435
|
+
) {
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
turnLaunchAttempt =
|
|
439
|
+
Math.max(0, Number(current.turnLaunchAttempt) || 0) + 1;
|
|
440
|
+
return {
|
|
441
|
+
...current,
|
|
442
|
+
phase: "turn_launching",
|
|
443
|
+
turnLaunchAttempt,
|
|
444
|
+
turnLaunchIntent: {
|
|
445
|
+
token: turnLaunchToken,
|
|
446
|
+
attempt: turnLaunchAttempt,
|
|
447
|
+
workerPid: process.pid,
|
|
448
|
+
workerGeneration: job.workerGeneration,
|
|
449
|
+
preparedAt: Date.now(),
|
|
450
|
+
},
|
|
451
|
+
heartbeatAt: Date.now(),
|
|
452
|
+
};
|
|
453
|
+
});
|
|
454
|
+
} catch (error) {
|
|
455
|
+
if (turnLaunchAttempt > 0) {
|
|
456
|
+
// The atomic rename may have committed even when strict lock release
|
|
457
|
+
// reports ownership loss. Resolve that window as an explicit no-spawn
|
|
458
|
+
// attempt instead of exiting with a durable intent and no settlement.
|
|
459
|
+
return beginTurnLaunchSettlement({
|
|
460
|
+
owned: null,
|
|
461
|
+
error,
|
|
462
|
+
turnLaunchToken,
|
|
463
|
+
turnLaunchAttempt,
|
|
464
|
+
agentStartedAt: null,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
throw error;
|
|
468
|
+
}
|
|
469
|
+
if (!prepared.applied) return false;
|
|
470
|
+
|
|
471
|
+
let spawned = null;
|
|
472
|
+
let agentStartedAt = null;
|
|
473
|
+
try {
|
|
474
|
+
const mutation = mutateBackgroundAgentState(job.id, (current) => {
|
|
475
|
+
if (
|
|
476
|
+
!current ||
|
|
477
|
+
current.status !== "running" ||
|
|
478
|
+
current.stopRequestedAt ||
|
|
479
|
+
current.turnLaunchIntent?.token !== turnLaunchToken ||
|
|
480
|
+
Number(current.turnLaunchIntent?.attempt) !== turnLaunchAttempt ||
|
|
481
|
+
Number(current.workerPid) !== process.pid ||
|
|
482
|
+
Number(current.workerClaimedPid) !== process.pid ||
|
|
483
|
+
(current.workerGeneration &&
|
|
484
|
+
current.workerGeneration !== job.workerGeneration)
|
|
485
|
+
) {
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
// The prepare intent is already durable. Keep the second state lock
|
|
489
|
+
// through native spawn and pid commit so a stopper sees either the
|
|
490
|
+
// unresolved intent or the exact child identity it must reap.
|
|
491
|
+
agentStartedAt = Date.now();
|
|
492
|
+
spawned = executionBroker.spawn(
|
|
493
|
+
process.execPath,
|
|
494
|
+
[job.cliEntry, ...argv],
|
|
495
|
+
{
|
|
496
|
+
cwd: job.cwd,
|
|
497
|
+
env: { ...process.env, CC_BACKGROUND_AGENT_ID: job.id },
|
|
498
|
+
stdio: ["ignore", log.fd, log.fd, "ipc"],
|
|
499
|
+
windowsHide: true,
|
|
500
|
+
origin: "background-agent:turn",
|
|
501
|
+
policy: "allow",
|
|
502
|
+
scope: "background-agent",
|
|
503
|
+
shell: false,
|
|
504
|
+
detached: process.platform !== "win32",
|
|
505
|
+
},
|
|
506
|
+
);
|
|
507
|
+
return {
|
|
508
|
+
...current,
|
|
509
|
+
status: "running",
|
|
510
|
+
phase: "turn",
|
|
511
|
+
turnCount: nextTurn,
|
|
512
|
+
pendingApprovals: 0,
|
|
513
|
+
pendingQuestion: null,
|
|
514
|
+
uncertainSideEffects: 0,
|
|
515
|
+
agentPid: spawned.pid,
|
|
516
|
+
agentStartedAt,
|
|
517
|
+
turnLaunchIntent: null,
|
|
518
|
+
turnLaunchResolution: {
|
|
519
|
+
token: turnLaunchToken,
|
|
520
|
+
attempt: turnLaunchAttempt,
|
|
521
|
+
outcome: "spawned",
|
|
522
|
+
agentPid: spawned.pid,
|
|
523
|
+
resolvedAt: Date.now(),
|
|
524
|
+
},
|
|
525
|
+
heartbeatAt: Date.now(),
|
|
526
|
+
};
|
|
527
|
+
});
|
|
528
|
+
if (!mutation.applied) {
|
|
529
|
+
return beginTurnLaunchSettlement({
|
|
530
|
+
owned: null,
|
|
531
|
+
error: new Error("background turn was stopped before native spawn"),
|
|
532
|
+
turnLaunchToken,
|
|
533
|
+
turnLaunchAttempt,
|
|
534
|
+
agentStartedAt: null,
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
} catch (error) {
|
|
538
|
+
const owned = spawned || error?.spawnedProcess;
|
|
539
|
+
return beginTurnLaunchSettlement({
|
|
540
|
+
owned,
|
|
541
|
+
error,
|
|
542
|
+
turnLaunchToken,
|
|
543
|
+
turnLaunchAttempt,
|
|
544
|
+
agentStartedAt,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
child = spawned;
|
|
141
548
|
phase = "turn";
|
|
142
|
-
turnCount
|
|
143
|
-
child = executionBroker.spawn(process.execPath, [job.cliEntry, ...argv], {
|
|
144
|
-
cwd: job.cwd,
|
|
145
|
-
env: { ...process.env, CC_BACKGROUND_AGENT_ID: job.id },
|
|
146
|
-
stdio: ["ignore", log.fd, log.fd, "ipc"], // P0-2: 开启 IPC 通道用于人机交互
|
|
147
|
-
windowsHide: true,
|
|
148
|
-
origin: "background-agent:turn",
|
|
149
|
-
policy: "allow",
|
|
150
|
-
scope: "background-agent",
|
|
151
|
-
shell: false,
|
|
152
|
-
// POSIX: give the agent child its own process group so an orphan reclaim
|
|
153
|
-
// (worker crashed → supervisor kills the recorded agentPid) or /stop can
|
|
154
|
-
// signal the WHOLE agent tree via -pid. Windows reaps via taskkill /T.
|
|
155
|
-
detached: process.platform !== "win32",
|
|
156
|
-
});
|
|
157
|
-
mergeState({
|
|
158
|
-
status: "running",
|
|
159
|
-
phase,
|
|
160
|
-
turnCount,
|
|
161
|
-
// A fresh turn has nothing pending; also clears a stale count left behind
|
|
162
|
-
// if the previous turn's child was hard-killed mid-approval (the child's
|
|
163
|
-
// phase reporter clears on settle, but SIGKILL never settles).
|
|
164
|
-
pendingApprovals: 0,
|
|
165
|
-
// The incoming prompt IS the user's reply to any parked question, and the
|
|
166
|
-
// uncertain-side-effect annotation only covers the verify turn it was
|
|
167
|
-
// raised in — both reset at the turn boundary.
|
|
168
|
-
pendingQuestion: null,
|
|
169
|
-
uncertainSideEffects: 0,
|
|
170
|
-
agentPid: child.pid,
|
|
171
|
-
// Identity anchor for the supervisor's orphan reclaim (Gap 2): pairs
|
|
172
|
-
// with agentPid the way startedAt pairs with the worker pid, so a
|
|
173
|
-
// pid-reusing stranger is never killed in the worker's name.
|
|
174
|
-
agentStartedAt: Date.now(),
|
|
175
|
-
heartbeatAt: Date.now(),
|
|
176
|
-
});
|
|
549
|
+
turnCount = nextTurn;
|
|
177
550
|
server?.broadcast({
|
|
178
551
|
type: "turn-started",
|
|
179
552
|
turn: turnCount,
|
|
@@ -305,6 +678,7 @@ function startTurn(argv, promptText) {
|
|
|
305
678
|
child = null;
|
|
306
679
|
finalize(1, null, error.message);
|
|
307
680
|
});
|
|
681
|
+
return true;
|
|
308
682
|
}
|
|
309
683
|
|
|
310
684
|
function maybeContinue() {
|
|
@@ -312,13 +686,22 @@ function maybeContinue() {
|
|
|
312
686
|
// External stop marked the session terminal while a turn was in flight —
|
|
313
687
|
// don't start another turn against a stopped session.
|
|
314
688
|
const current = readBackgroundAgentState(job.id);
|
|
315
|
-
if (current
|
|
689
|
+
if (!current || current.status !== "running") {
|
|
316
690
|
finalize(lastExit.code, lastExit.signal);
|
|
317
691
|
return;
|
|
318
692
|
}
|
|
319
693
|
if (promptQueue.length && Array.isArray(job.followUpArgv)) {
|
|
320
694
|
const text = promptQueue.shift();
|
|
321
|
-
|
|
695
|
+
if (
|
|
696
|
+
!startTurn(
|
|
697
|
+
insertArgumentsBeforeOptionTerminator(job.followUpArgv, [
|
|
698
|
+
agentPrintArgument(text),
|
|
699
|
+
]),
|
|
700
|
+
text,
|
|
701
|
+
)
|
|
702
|
+
) {
|
|
703
|
+
finalize(lastExit.code, lastExit.signal);
|
|
704
|
+
}
|
|
322
705
|
return;
|
|
323
706
|
}
|
|
324
707
|
if (server && server.clientCount() > 0) {
|
|
@@ -359,6 +742,13 @@ function getStatus() {
|
|
|
359
742
|
|
|
360
743
|
async function main() {
|
|
361
744
|
job = JSON.parse(readFileSync(jobFile, "utf8"));
|
|
745
|
+
// Claim before opening logs, recovering interactions or creating the pipe.
|
|
746
|
+
// A stopped/removed/differently-generated record must produce no worker
|
|
747
|
+
// side effects and must never be recreated by bootstrap merges.
|
|
748
|
+
if (!writeHeartbeat()) {
|
|
749
|
+
removeJobFile(jobFile);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
362
752
|
removeJobFile(jobFile);
|
|
363
753
|
log = openBackgroundLogFile(job.id);
|
|
364
754
|
interactionJournal = loadBackgroundInteractionJournal(job.sessionId, job.id);
|
|
@@ -477,14 +867,34 @@ async function main() {
|
|
|
477
867
|
server = null;
|
|
478
868
|
}
|
|
479
869
|
|
|
480
|
-
writeHeartbeat()
|
|
870
|
+
if (!writeHeartbeat()) {
|
|
871
|
+
// A list/stop/remove writer may have terminalized the record before this
|
|
872
|
+
// worker claimed it. Never execute the task after losing ownership of the
|
|
873
|
+
// session id; close any best-effort transport and exit without rewriting
|
|
874
|
+
// the terminal status.
|
|
875
|
+
finalize(0, null);
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
481
878
|
heartbeat = setInterval(() => {
|
|
482
879
|
try {
|
|
483
880
|
if (!writeHeartbeat()) {
|
|
881
|
+
if (turnLaunchSettlement) {
|
|
882
|
+
// The owned tree is already in explicit settlement. Keep the worker
|
|
883
|
+
// alive until confirmed termination evidence is durably recorded;
|
|
884
|
+
// an external stopper may still terminate this worker directly.
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
484
887
|
clearInterval(heartbeat);
|
|
485
|
-
// External stop
|
|
486
|
-
//
|
|
487
|
-
if (
|
|
888
|
+
// External stop owns the terminal fence. Reap our current detached
|
|
889
|
+
// child as a second line of defence, then stop holding the pipe.
|
|
890
|
+
if (child?.pid) {
|
|
891
|
+
try {
|
|
892
|
+
stopBackgroundAgentChildTree(child.pid);
|
|
893
|
+
} catch {
|
|
894
|
+
/* the stopper may already have reaped it */
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
finalize(lastExit.code, lastExit.signal);
|
|
488
898
|
}
|
|
489
899
|
} catch {
|
|
490
900
|
/* do not let heartbeat persistence kill the worker */
|
|
@@ -492,7 +902,18 @@ async function main() {
|
|
|
492
902
|
}, DEFAULT_HEARTBEAT_INTERVAL_MS);
|
|
493
903
|
heartbeat.unref?.();
|
|
494
904
|
|
|
495
|
-
|
|
905
|
+
const launchReadiness = await waitForLaunchFinalization();
|
|
906
|
+
if (launchReadiness !== "ready") {
|
|
907
|
+
finalize(launchReadiness === "timeout" ? 1 : 0, null);
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
// Launch finalization can take long enough for an external stop/remove to
|
|
911
|
+
// win after bootstrap. Re-claim immediately before the first native turn.
|
|
912
|
+
if (!writeHeartbeat()) {
|
|
913
|
+
finalize(0, null);
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
if (!startTurn(job.argv, null)) finalize(0, null);
|
|
496
917
|
}
|
|
497
918
|
|
|
498
919
|
main().catch((error) => {
|