chainlesschain 0.163.6 → 0.163.8
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 +2 -1
- package/src/assets/web-panel/assets/{AIOps-DDIUofA8.js → AIOps-CavM7W3k.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Bm-5xYRn.js → ActionButton-CUvZGabQ.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-D5UH4ljI.js → Analytics-DeBnWnbe.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-D_ifjOOP.js → AppLayout-DbI5kxvd.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-BY1mXdC7.js → Artifacts-D93bigKm.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Bujr4Ml3.js → Audit-CBNvfUZa.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-sioKSTcC.js → BackgroundAgents-CJLyh_YU.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DDOAK8RK.js → Backup-D_0yoehD.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CtCsVVke.js → BaseInput-TJxmbObW.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DpNj0vnd.js → Chat-CE0dto1q.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZta3Ftr.js → ChatBubbleRenderer-CZ5-yYiD.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DoHVNSXq.js → Checkbox-Ck1RVIVo.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DlVl7Qmv.js → Codegen-qpoyf6DK.js} +1 -1
- package/src/assets/web-panel/assets/{Col-GIqoPYty.js → Col-DUyPWjIE.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CIAPEVji.js → Community-D_S1BemS.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BN7JiI_h.js → Compact-BjxeK-Gc.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CqUWCM9B.js → Compliance-Dd3CEJsu.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-A4iPp7A4.js → Cowork-B4QG7-ad.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-B2zeXXWb.js → Cron-BtZnids5.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BR2xmWr8.js → Crosschain-CHPfCBFD.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BzFDjrIv.js → DID-DT8DCEOK.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DKtDo3yA.js → Dashboard-COQab5_U.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-RNx43_YG.js → Dropdown-BY1PsQuU.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-SwMt4Rcy.js → EmailListRenderer-CsuAuaq3.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CsYebZ4X.js → FamilyGuardDashboard-BqpinD-j.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BmRKvBTy.js → Federation-D0gkfQFv.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CVxGTA-0.js → FormItemContext-BqgE-axK.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-x2iXpyB7.js → GenericCardRenderer-D0hJ2KvC.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DfqbkiOP.js → Git-CmMB0uPY.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-RuHyyj5O.js → Governance-BnVEI0Dk.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Bn7uZ7rI.js → Inference-YTAMihRP.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DlP7XefZ.js → KnowledgeGraph-0ax7VHop.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BYNdOiCd.js → Logs-B9TRcokL.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-yySN5YNM.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-JQYmDqM8.js → Marketplace-B6CSuD_H.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Cyx4yZ-9.js → McpTools-CjMXvOVU.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-VDuGZTN2.js → Memory-CXRF71U3.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-_yKo97gi.js → MobileBridge-DQgNp_oS.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CXRXW3q7.js → MobileProjects-DlPwcCbq.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-acTGtfLH.js → Mtc-6hDTRPe1.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-ClpjXfkY.js → MtcAudit-BlVHU1DS.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-_6Tn3o96.js → Multisig-DP9pzEbV.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CK7pbZzP.js → NLProgramming-Djorh8EV.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Czy6V_P8.js → Notes-oLZ-JkUg.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-Cqx-UVv3.js → NotificationSettings-Cg288VH3.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-C69WDxGy.js → OrderTableRenderer-Bb1O3Ofr.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DFnA9vLQ.js → Organization-CBdo-bHs.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-ReZkk2yT.js → Overflow-qZdnVumx.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DzjSI0aI.js → P2P-D4kNfGNQ.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-DnlMemM7.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-DhQ5klhR.js → Permissions-DHucHFBJ.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-CUL1iHrW.js → PersonalDataHub-BovlOIPR.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-DU88iu4N.js → Pipeline-Deru3CQY.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Bw-sYfyK.js → Privacy-Bq5oTTbU.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Bq3Q5y0z.js → ProjectInit-BfzxVWAz.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C8spzpho.js → ProjectSettings-CKC6XBtH.js} +2 -2
- package/src/assets/web-panel/assets/Projects-CMolGhae.js +1 -0
- package/src/assets/web-panel/assets/{Providers-Bf4eTP9W.js → Providers-C_jx4TWX.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Dme9BDU1.js → QrScannerModal-BdIHeVGC.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CIXfv1rE.js → QuickAsk-BwZwAcPD.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CIxvdeZ-.js → Recommend-BNTt0L-m.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C6k7MExI.js → RemoteSession-Biyn2-Ro.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CISDz2bl.js → Reputation-D6VpG32B.js} +1 -1
- package/src/assets/web-panel/assets/{Row-CdnDxXyf.js → Row-D_74OvQQ.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C-QpRHsl.js → RssFeed-BTr0a-BQ.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DBjaGzSn.js → Search-C3MCFoNU.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CQKukOeE.js → Security-CqW9aJ6s.js} +3 -3
- package/src/assets/web-panel/assets/{Services-DZz86jWb.js → Services-Cw5GNaHF.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-jb8RMK0d.js → Skeleton-DZps5qeY.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CdY76fZG.js → Skills-C6NQ1znT.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-C_CSiZX9.js → Sla-DFP8G2Sh.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-ESt6MB6v.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-qvimGO0O.js → SyncSettings-BHnN98aM.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Dng5LPJC.js → Tasks-NiSCaOf-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Bwqq5Wll.js → Templates-DsKFhW2C.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DNWuwP7Z.js → Tenant-CQ0E0ze5.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B-6Othpp.js → Terminal-D_rBu_by.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DKpKZfzx.js → TimelineRenderer-D3Vm4Po4.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DzfGMd2J.js → Tokens-DNTxRH6q.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BXwUi74J.js → Trigger-Dgprz1ms.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BNzUq4ui.js → Trust-B2rUnVBD.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BYugxww4.js → UkeySign-DEGQNc6_.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DdauhtSJ.js → VideoEditing-jlsAx3Eh.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CSC9bCe_.js → Wallet-8jQih1ij.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Q8AN3bTO.js → WebAuthn-XLeO6xIB.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-d-LBGCuA.js → WorkflowEditor-De_dgDAG.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BLVDCgsZ.js → chat-CI19R_Sk.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BZlPSEHl.js → colors-DsYTOYN3.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-IWseP6R6.js → compact-item-CM3-t_Qr.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DnVRV3Ao.js → createContext-BjxlBRZv.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-S1ld2LwU.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DLQZA_Wn.js → hasIn-RDaWX_4i.js} +1 -1
- package/src/assets/web-panel/assets/{index-BSjilZ9L.js → index-9R6BxAr0.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_N4-Gq9.js → index-B-Z8eFUJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dy0R5ZED.js → index-B0PMlbMs.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1ifZtXo.js → index-B5REnKt8.js} +1 -1
- package/src/assets/web-panel/assets/{index-CINJQEj6.js → index-B7WBt_4T.js} +1 -1
- package/src/assets/web-panel/assets/{index-DocEhinm.js → index-B9O9YyWE.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjWnONdy.js → index-BCnZyIMA.js} +1 -1
- package/src/assets/web-panel/assets/{index-hwVDeoC0.js → index-BGqqJfL0.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bbj2NS_I.js → index-BHNYPO0b.js} +3 -3
- package/src/assets/web-panel/assets/{index-C5dgnilC.js → index-BKJr-TZp.js} +1 -1
- package/src/assets/web-panel/assets/{index-CT9vVN-X.js → index-BN6UPD2E.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpVrK6fr.js → index-Bi46Xu4u.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpBaJ3TD.js → index-BlT2VAEK.js} +1 -1
- package/src/assets/web-panel/assets/{index-1jeAudHd.js → index-C0yx-jtQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DOoQpuQY.js → index-C4gkzxkw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZpYeqrh.js → index-CVQMeiwH.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhA3QEni.js → index-Cd3euP_4.js} +1 -1
- package/src/assets/web-panel/assets/{index-N_GoWZX4.js → index-Cd9OJhHG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOZJBTWZ.js → index-CiECJi-Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-CarMTIyQ.js → index-CjTrRt13.js} +1 -1
- package/src/assets/web-panel/assets/{index-CZDekj7T.js → index-CnHH7bnD.js} +1 -1
- package/src/assets/web-panel/assets/{index-M7wxfAbm.js → index-Cpqm1qfU.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bxp7uDD8.js → index-CyOGN6a5.js} +1 -1
- package/src/assets/web-panel/assets/{index-DalA0TKC.js → index-D0zcy54x.js} +1 -1
- package/src/assets/web-panel/assets/{index-COev0W2g.js → index-D6sm5-Xq.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUq0c7gq.js → index-DIUnNOjG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZs5bBnk.js → index-DX7tuWVJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbQNL0ue.js → index-DXXtGMUJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTbxp4UH.js → index-DcPFB8qJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-YSw_gxlZ.js → index-DoQA0-52.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_LQodeJ.js → index-Drlnwr8F.js} +1 -1
- package/src/assets/web-panel/assets/index-Drz6yq0m.js +1 -0
- package/src/assets/web-panel/assets/{index-jWb70ZgZ.js → index-Dsij_KsN.js} +1 -1
- package/src/assets/web-panel/assets/{index-DB0MJgF_.js → index-Dxge-xWl.js} +1 -1
- package/src/assets/web-panel/assets/{index-BEu2lMBy.js → index-DyIWssPC.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2FqfMvU.js → index-IeiL9RKc.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dkwsi1Kn.js → index-nAH87mQP.js} +1 -1
- package/src/assets/web-panel/assets/{index-gt8bNefW.js → index-pPL66J85.js} +1 -1
- package/src/assets/web-panel/assets/index-uWiUs7S_.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-Co67Kqmb.js → initDefaultProps-BTL88of4.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BMOxQSF_.js → motion-BUB2pXwB.js} +1 -1
- package/src/assets/web-panel/assets/{move-C5fWDw-N.js → move-egCkkOMm.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BJevpL0e.js → mtc-parser-DAWhZC8c.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BCzNU8s6.js → omit-CvOxe_6d.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CybWvDPk.js → pickAttrs-DZUSLZ5Z.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BMWS9HnW.js → placementArrow-BERxGNMY.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BoL2UnGY.js → responsiveObserve-DZuDfZ9n.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DWNfXraU.js → slide-DO756sgI.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BquTM3tX.js → statusUtils-BRQlIz28.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BEkCleim.js → styleChecker-DCSfdybl.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Oecnq7EA.js → useFlexGapSupport-LD8dWVve.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CkDffJ4F.js → useFs-BX9uW2LK.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-qbvKXesB.js → usePersonalDataHub-BPSlPuHU.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-C6v8n6mA.js → vnode-BmOILC9V.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-4CTeFILV.js → zoom-1iKowKGg.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +5 -5
- package/src/commands/agent.js +90 -1
- package/src/commands/automation.js +103 -1
- package/src/commands/chat.js +11 -2
- package/src/commands/compact.js +106 -23
- package/src/commands/loop.js +28 -10
- package/src/commands/routine.js +1 -1
- package/src/commands/scheduler-daemon.js +359 -2
- package/src/commands/session-observability.js +305 -0
- package/src/commands/session.js +38 -0
- package/src/commands/team-merge-review.js +937 -0
- package/src/commands/team.js +2 -0
- package/src/data/changelog.json +27 -1
- package/src/gateways/ws/ws-agent-handler.js +439 -5
- package/src/harness/jsonl-session-store.js +463 -125
- package/src/harness/prompt-compressor.js +86 -13
- package/src/harness/provider-backed-compaction.js +100 -27
- package/src/lib/advisor-runtime.js +3 -1
- package/src/lib/agent-schedule-store.js +549 -28
- package/src/lib/agent-team/team-adjudication.js +40 -1
- package/src/lib/agent-team/team-merge-review-store.js +765 -0
- package/src/lib/agent-team/team-merge-review-transaction.js +1223 -0
- package/src/lib/agent-team/team-merge-review.js +1847 -0
- package/src/lib/automation-center-incidents.js +314 -0
- package/src/lib/automation-center-runtime.js +448 -0
- package/src/lib/automation-center.js +182 -38
- package/src/lib/automation-engine.js +535 -0
- package/src/lib/automation-execution-authority.js +241 -12
- package/src/lib/automation-execution-incident.js +737 -0
- package/src/lib/background-agent-supervisor.js +793 -215
- package/src/lib/background-command-argv.js +24 -0
- package/src/lib/background-interaction-journal.js +361 -52
- package/src/lib/background-interaction-resolver.js +32 -0
- package/src/lib/background-launch-profile.js +2 -0
- package/src/lib/causal-observability.js +2661 -0
- package/src/lib/chat-session-admission.js +76 -0
- package/src/lib/cost-budget.js +5 -7
- package/src/lib/cowork/ab-comparator-cli.js +11 -0
- package/src/lib/cowork/debate-review-cli.js +12 -0
- package/src/lib/cowork-adapter.js +159 -38
- package/src/lib/cowork-cron.js +442 -15
- package/src/lib/cowork-task-runner.js +112 -17
- package/src/lib/delivery-coordinator.js +142 -0
- package/src/lib/direct-model-usage.js +121 -0
- package/src/lib/file-lines.js +183 -47
- package/src/lib/llm-pricing.js +12 -17
- package/src/lib/loop.js +26 -2
- package/src/lib/routine-store.js +380 -21
- package/src/lib/runtime-usage-ledger.js +182 -0
- package/src/lib/scheduler-kernel/agenda-adapter.js +325 -10
- package/src/lib/scheduler-kernel/automation-adapter.js +514 -18
- package/src/lib/scheduler-kernel/automation-event-adapter.js +123 -11
- package/src/lib/scheduler-kernel/contract.js +91 -0
- package/src/lib/scheduler-kernel/cowork-cron-adapter.js +297 -4
- package/src/lib/scheduler-kernel/loop-adapter.js +547 -6
- package/src/lib/scheduler-kernel/migration-admin.js +346 -0
- package/src/lib/scheduler-kernel/migration-rollback-admin.js +932 -0
- package/src/lib/scheduler-kernel/routine-adapter.js +400 -20
- package/src/lib/scheduler-kernel/runtime-control-capabilities.js +31 -0
- package/src/lib/scheduler-kernel/runtime.js +237 -2
- package/src/lib/scheduler-kernel/source-locator-path.js +142 -0
- package/src/lib/scheduler-kernel/store.js +3957 -248
- package/src/lib/session-host-lease.js +171 -2
- package/src/lib/session-host-snapshot.js +14 -3
- package/src/lib/session-persistence-failure.js +1 -0
- package/src/lib/session-tail.js +179 -34
- package/src/lib/session-transcript-structure.js +96 -0
- package/src/lib/sub-agent-context.js +328 -22
- package/src/lib/ws-agent-handler.js +4 -1
- package/src/repl/agent-repl.js +732 -91
- package/src/repl/chat-repl.js +5 -1
- package/src/runtime/agent-core.js +1282 -207
- package/src/runtime/fallback-model.js +5 -0
- package/src/runtime/headless-runner.js +324 -82
- package/src/runtime/headless-stream.js +232 -26
- package/src/runtime/mcp-config.js +81 -10
- package/src/runtime/policies/agent-policy.js +4 -0
- package/src/workers/background-agent-worker.js +403 -96
- package/src/assets/web-panel/assets/MarkdownRenderer-aMtgN-R6.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-DKTf5xIP.js +0 -7
- package/src/assets/web-panel/assets/Projects-Dd-xuPIc.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-C7tT9iad.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Jzu62CLC.js +0 -1
- package/src/assets/web-panel/assets/index-Zb2MeDSy.js +0 -1
- package/src/assets/web-panel/assets/index-fikqml4k.js +0 -1
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
import fs from "fs";
|
|
23
23
|
import os from "os";
|
|
24
24
|
import path from "path";
|
|
25
|
-
import { randomUUID } from "crypto";
|
|
25
|
+
import { createHash, randomUUID } from "crypto";
|
|
26
26
|
import { isEntryExpired, effectiveFireAt } from "./schedule-planner.js";
|
|
27
27
|
import { SUBAGENT_PERMISSION_MODES } from "./subagent-contract.js";
|
|
28
28
|
import { parseGoalCondition } from "./goal-condition-engine.js";
|
|
@@ -30,6 +30,7 @@ import { normalizeActionClass } from "./unattended-action-policy.js";
|
|
|
30
30
|
import { withFileLock } from "./with-file-lock.js";
|
|
31
31
|
|
|
32
32
|
export const SCHEDULE_KINDS = Object.freeze(["wakeup", "cron", "monitor"]);
|
|
33
|
+
export const AGENDA_SCHEDULER_MIGRATION_SCHEMA_VERSION = 1;
|
|
33
34
|
|
|
34
35
|
/** A valid absolute-epoch-ms expiry, or null (never expires). */
|
|
35
36
|
function normalizeExpiresAt(v) {
|
|
@@ -168,6 +169,87 @@ function hasLiveExecutionLease(entry, now) {
|
|
|
168
169
|
return Number(entry.executionLease?.expiresAt) > Number(now);
|
|
169
170
|
}
|
|
170
171
|
|
|
172
|
+
function stableJsonValue(value) {
|
|
173
|
+
if (Array.isArray(value)) return value.map(stableJsonValue);
|
|
174
|
+
if (value && typeof value === "object") {
|
|
175
|
+
return Object.fromEntries(
|
|
176
|
+
Object.keys(value)
|
|
177
|
+
.sort()
|
|
178
|
+
.map((key) => [key, stableJsonValue(value[key])]),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function agendaMigrationSource(entry) {
|
|
185
|
+
const source = { ...entry };
|
|
186
|
+
delete source.executionLease;
|
|
187
|
+
delete source.schedulerExecution;
|
|
188
|
+
delete source.schedulerMigration;
|
|
189
|
+
delete source.firedAt;
|
|
190
|
+
delete source.lastRunAt;
|
|
191
|
+
delete source.runs;
|
|
192
|
+
delete source.nextAt;
|
|
193
|
+
delete source.lastCheckAt;
|
|
194
|
+
delete source.checks;
|
|
195
|
+
delete source.lastMtimeMs;
|
|
196
|
+
delete source.lastEventId;
|
|
197
|
+
delete source.lastAuthority;
|
|
198
|
+
delete source.status;
|
|
199
|
+
return source;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function agendaMigrationSourceDigest(entry) {
|
|
203
|
+
return createHash("sha256")
|
|
204
|
+
.update("chainlesschain.agenda.scheduler-migration-source.v1\0", "utf8")
|
|
205
|
+
.update(
|
|
206
|
+
JSON.stringify(stableJsonValue(agendaMigrationSource(entry))),
|
|
207
|
+
"utf8",
|
|
208
|
+
)
|
|
209
|
+
.digest("hex");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function agendaMigrationSourceSnapshot(entry) {
|
|
213
|
+
return stableJsonValue(agendaMigrationSource(entry));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function schedulerMigrationFence(entry, now) {
|
|
217
|
+
const migration = entry.schedulerMigration;
|
|
218
|
+
if (
|
|
219
|
+
migration?.schemaVersion !== AGENDA_SCHEDULER_MIGRATION_SCHEMA_VERSION ||
|
|
220
|
+
typeof migration.retirementToken !== "string" ||
|
|
221
|
+
migration.retirementToken.length === 0
|
|
222
|
+
) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
return (
|
|
226
|
+
hasLiveExecutionLease(entry, now) &&
|
|
227
|
+
entry.executionLease?.owner ===
|
|
228
|
+
`scheduler-migration:${migration.retirementToken}`
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function applySchedulerMigrationFence(entry, now) {
|
|
233
|
+
const token = entry.schedulerMigration?.retirementToken;
|
|
234
|
+
if (typeof token !== "string" || token.length === 0) return false;
|
|
235
|
+
entry.executionLease = {
|
|
236
|
+
owner: `scheduler-migration:${token}`,
|
|
237
|
+
claimedAt: now,
|
|
238
|
+
expiresAt: Number.MAX_SAFE_INTEGER,
|
|
239
|
+
};
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function normalizeMigrationField(value, field) {
|
|
244
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
245
|
+
throw agendaStoreError(
|
|
246
|
+
"AGENDA_SCHEDULER_MIGRATION_INVALID",
|
|
247
|
+
`Agenda scheduler migration ${field} is required`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return value.trim();
|
|
251
|
+
}
|
|
252
|
+
|
|
171
253
|
function agendaStoreError(code, message, { retryable = false, retryAt } = {}) {
|
|
172
254
|
const error = new Error(message);
|
|
173
255
|
error.code = code;
|
|
@@ -176,6 +258,21 @@ function agendaStoreError(code, message, { retryable = false, retryAt } = {}) {
|
|
|
176
258
|
return error;
|
|
177
259
|
}
|
|
178
260
|
|
|
261
|
+
function agendaPersistenceError(
|
|
262
|
+
code,
|
|
263
|
+
message,
|
|
264
|
+
cause,
|
|
265
|
+
{ filePath, commitState } = {},
|
|
266
|
+
) {
|
|
267
|
+
const error = new Error(message, { cause });
|
|
268
|
+
error.name = "AgendaScheduleStoreError";
|
|
269
|
+
error.code = code;
|
|
270
|
+
error.filePath = filePath;
|
|
271
|
+
if (typeof cause?.code === "string") error.fsCode = cause.code;
|
|
272
|
+
if (commitState) error.commitState = commitState;
|
|
273
|
+
return error;
|
|
274
|
+
}
|
|
275
|
+
|
|
179
276
|
function applyScheduledAgentSuccess(entry, atMs) {
|
|
180
277
|
if (entry.kind === "wakeup") {
|
|
181
278
|
entry.status = "fired";
|
|
@@ -584,9 +681,16 @@ export function previousCronTime(expr, fromMs, options = {}) {
|
|
|
584
681
|
// ─── store ───────────────────────────────────────────────────────────────
|
|
585
682
|
|
|
586
683
|
export class AgentScheduleStore {
|
|
587
|
-
constructor({
|
|
684
|
+
constructor({
|
|
685
|
+
dir = null,
|
|
686
|
+
now = () => Date.now(),
|
|
687
|
+
fileSystem = fs,
|
|
688
|
+
platform = process.platform,
|
|
689
|
+
} = {}) {
|
|
588
690
|
this.dir = dir || agentScheduleDir();
|
|
589
691
|
this._now = typeof now === "function" ? now : () => now;
|
|
692
|
+
this._fs = fileSystem;
|
|
693
|
+
this._platform = platform;
|
|
590
694
|
}
|
|
591
695
|
|
|
592
696
|
_file(kind) {
|
|
@@ -594,37 +698,124 @@ export class AgentScheduleStore {
|
|
|
594
698
|
}
|
|
595
699
|
|
|
596
700
|
_ensureDir() {
|
|
597
|
-
|
|
701
|
+
this._fs.mkdirSync(this.dir, { recursive: true, mode: 0o700 });
|
|
598
702
|
}
|
|
599
703
|
|
|
600
|
-
_readAll(kind) {
|
|
704
|
+
_readAll(kind, { failOnMalformed = false } = {}) {
|
|
705
|
+
const filePath = this._file(kind);
|
|
601
706
|
let raw;
|
|
602
707
|
try {
|
|
603
|
-
raw =
|
|
604
|
-
} catch {
|
|
605
|
-
return [];
|
|
708
|
+
raw = this._fs.readFileSync(filePath, "utf-8");
|
|
709
|
+
} catch (cause) {
|
|
710
|
+
if (cause?.code === "ENOENT") return [];
|
|
711
|
+
throw agendaPersistenceError(
|
|
712
|
+
"AGENDA_SCHEDULE_STORE_READ_FAILED",
|
|
713
|
+
`Agenda schedule store read failed (${filePath})`,
|
|
714
|
+
cause,
|
|
715
|
+
{ filePath },
|
|
716
|
+
);
|
|
606
717
|
}
|
|
607
718
|
const entries = [];
|
|
719
|
+
let lineNumber = 0;
|
|
608
720
|
for (const line of raw.split("\n")) {
|
|
721
|
+
lineNumber += 1;
|
|
609
722
|
const trimmed = line.trim();
|
|
610
723
|
if (!trimmed) continue;
|
|
611
724
|
try {
|
|
612
|
-
|
|
613
|
-
|
|
725
|
+
const entry = JSON.parse(trimmed);
|
|
726
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
727
|
+
throw new TypeError("schedule entry must be an object");
|
|
728
|
+
}
|
|
729
|
+
entries.push(entry);
|
|
730
|
+
} catch (cause) {
|
|
731
|
+
if (failOnMalformed) {
|
|
732
|
+
throw agendaPersistenceError(
|
|
733
|
+
"AGENDA_SCHEDULE_STORE_MALFORMED",
|
|
734
|
+
`Agenda schedule store contains malformed JSONL (${filePath}:${lineNumber})`,
|
|
735
|
+
cause,
|
|
736
|
+
{ filePath },
|
|
737
|
+
);
|
|
738
|
+
}
|
|
614
739
|
// Skip a corrupt line rather than throwing — one bad append must not
|
|
615
|
-
// poison
|
|
740
|
+
// poison advisory reads. Authoritative mutations opt into strict mode
|
|
741
|
+
// so they never replace a store after silently dropping durable rows.
|
|
616
742
|
}
|
|
617
743
|
}
|
|
618
744
|
return entries;
|
|
619
745
|
}
|
|
620
746
|
|
|
621
747
|
_writeAll(kind, entries) {
|
|
622
|
-
this.
|
|
748
|
+
const filePath = this._file(kind);
|
|
623
749
|
const body = entries.map((entry) => JSON.stringify(entry)).join("\n");
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
750
|
+
const contents = Buffer.from(body ? `${body}\n` : "", "utf8");
|
|
751
|
+
const temporaryPath = path.join(
|
|
752
|
+
this.dir,
|
|
753
|
+
`.${path.basename(filePath)}.${process.pid}.${randomUUID()}.tmp`,
|
|
754
|
+
);
|
|
755
|
+
let descriptor = null;
|
|
756
|
+
let renamed = false;
|
|
757
|
+
|
|
758
|
+
try {
|
|
759
|
+
this._ensureDir();
|
|
760
|
+
descriptor = this._fs.openSync(temporaryPath, "wx", 0o600);
|
|
761
|
+
let offset = 0;
|
|
762
|
+
while (offset < contents.length) {
|
|
763
|
+
const written = this._fs.writeSync(
|
|
764
|
+
descriptor,
|
|
765
|
+
contents,
|
|
766
|
+
offset,
|
|
767
|
+
contents.length - offset,
|
|
768
|
+
null,
|
|
769
|
+
);
|
|
770
|
+
if (!Number.isSafeInteger(written) || written <= 0) {
|
|
771
|
+
const cause = new Error("write returned no progress");
|
|
772
|
+
cause.code = "EIO";
|
|
773
|
+
throw cause;
|
|
774
|
+
}
|
|
775
|
+
offset += written;
|
|
776
|
+
}
|
|
777
|
+
this._fs.fsyncSync(descriptor);
|
|
778
|
+
this._fs.closeSync(descriptor);
|
|
779
|
+
descriptor = null;
|
|
780
|
+
this._fs.renameSync(temporaryPath, filePath);
|
|
781
|
+
renamed = true;
|
|
782
|
+
|
|
783
|
+
if (this._platform !== "win32") {
|
|
784
|
+
const directoryDescriptor = this._fs.openSync(this.dir, "r");
|
|
785
|
+
try {
|
|
786
|
+
this._fs.fsyncSync(directoryDescriptor);
|
|
787
|
+
} finally {
|
|
788
|
+
this._fs.closeSync(directoryDescriptor);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
} catch (cause) {
|
|
792
|
+
if (descriptor !== null) {
|
|
793
|
+
try {
|
|
794
|
+
this._fs.closeSync(descriptor);
|
|
795
|
+
} catch {
|
|
796
|
+
// Preserve the original persistence failure.
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
if (!renamed) {
|
|
800
|
+
try {
|
|
801
|
+
this._fs.unlinkSync(temporaryPath);
|
|
802
|
+
} catch {
|
|
803
|
+
// Best-effort orphan cleanup; the authoritative file is untouched.
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
throw agendaPersistenceError(
|
|
807
|
+
"AGENDA_SCHEDULE_STORE_WRITE_FAILED",
|
|
808
|
+
`Agenda schedule store write failed (${filePath})`,
|
|
809
|
+
cause,
|
|
810
|
+
{
|
|
811
|
+
filePath,
|
|
812
|
+
// A failure before rename leaves the old generation authoritative.
|
|
813
|
+
// A directory-fsync failure after rename makes crash durability
|
|
814
|
+
// unknowable even though an immediate read sees the new generation.
|
|
815
|
+
commitState: renamed ? "unknown" : "not-committed",
|
|
816
|
+
},
|
|
817
|
+
);
|
|
818
|
+
}
|
|
628
819
|
}
|
|
629
820
|
|
|
630
821
|
// ── create ──────────────────────────────────────────────────────────────
|
|
@@ -659,7 +850,7 @@ export class AgentScheduleStore {
|
|
|
659
850
|
createdAt: now,
|
|
660
851
|
status: "pending",
|
|
661
852
|
};
|
|
662
|
-
const entries = this._readAll("wakeup");
|
|
853
|
+
const entries = this._readAll("wakeup", { failOnMalformed: true });
|
|
663
854
|
entries.push(entry);
|
|
664
855
|
this._writeAll("wakeup", entries);
|
|
665
856
|
return entry;
|
|
@@ -704,7 +895,7 @@ export class AgentScheduleStore {
|
|
|
704
895
|
runs: 0,
|
|
705
896
|
status: "active",
|
|
706
897
|
};
|
|
707
|
-
const entries = this._readAll("cron");
|
|
898
|
+
const entries = this._readAll("cron", { failOnMalformed: true });
|
|
708
899
|
entries.push(entry);
|
|
709
900
|
this._writeAll("cron", entries);
|
|
710
901
|
return entry;
|
|
@@ -800,7 +991,7 @@ export class AgentScheduleStore {
|
|
|
800
991
|
checks: 0,
|
|
801
992
|
status: "active",
|
|
802
993
|
};
|
|
803
|
-
const entries = this._readAll("monitor");
|
|
994
|
+
const entries = this._readAll("monitor", { failOnMalformed: true });
|
|
804
995
|
entries.push(entry);
|
|
805
996
|
this._writeAll("monitor", entries);
|
|
806
997
|
return entry;
|
|
@@ -820,15 +1011,246 @@ export class AgentScheduleStore {
|
|
|
820
1011
|
return this.list().find((entry) => entry.id === id) || null;
|
|
821
1012
|
}
|
|
822
1013
|
|
|
1014
|
+
prepareSchedulerMigration(
|
|
1015
|
+
id,
|
|
1016
|
+
{ migrationId, sourceDigest, targetJobId } = {},
|
|
1017
|
+
) {
|
|
1018
|
+
const migration = normalizeMigrationField(migrationId, "migrationId");
|
|
1019
|
+
const expectedDigest = normalizeMigrationField(
|
|
1020
|
+
sourceDigest,
|
|
1021
|
+
"sourceDigest",
|
|
1022
|
+
);
|
|
1023
|
+
const target = normalizeMigrationField(targetJobId, "targetJobId");
|
|
1024
|
+
this._ensureDir();
|
|
1025
|
+
return withFileLock(
|
|
1026
|
+
path.join(this.dir, ".agenda-claims"),
|
|
1027
|
+
() => {
|
|
1028
|
+
for (const kind of SCHEDULE_KINDS) {
|
|
1029
|
+
const entries = this._readAll(kind, { failOnMalformed: true });
|
|
1030
|
+
const entry = entries.find((candidate) => candidate.id === id);
|
|
1031
|
+
if (!entry) continue;
|
|
1032
|
+
if (!isSchedulableStatus(entry)) {
|
|
1033
|
+
throw agendaStoreError(
|
|
1034
|
+
"AGENDA_SCHEDULER_ENTRY_TERMINAL",
|
|
1035
|
+
`Agenda entry is not schedulable: ${id}`,
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
const actualDigest = agendaMigrationSourceDigest(entry);
|
|
1039
|
+
if (actualDigest !== expectedDigest) {
|
|
1040
|
+
throw agendaStoreError(
|
|
1041
|
+
"AGENDA_SCHEDULER_MIGRATION_SOURCE_CHANGED",
|
|
1042
|
+
`Agenda entry changed before migration: ${id}`,
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
const prior = entry.schedulerMigration;
|
|
1046
|
+
if (prior) {
|
|
1047
|
+
if (
|
|
1048
|
+
prior.schemaVersion !==
|
|
1049
|
+
AGENDA_SCHEDULER_MIGRATION_SCHEMA_VERSION ||
|
|
1050
|
+
prior.sourceDigest !== expectedDigest ||
|
|
1051
|
+
prior.targetJobId !== target ||
|
|
1052
|
+
prior.migrationId !== migration ||
|
|
1053
|
+
prior.state !== "prepared"
|
|
1054
|
+
) {
|
|
1055
|
+
throw agendaStoreError(
|
|
1056
|
+
"AGENDA_SCHEDULER_MIGRATION_CONFLICT",
|
|
1057
|
+
`Agenda entry already belongs to another scheduler migration: ${id}`,
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
return entry;
|
|
1061
|
+
}
|
|
1062
|
+
entry.schedulerMigration = {
|
|
1063
|
+
schemaVersion: AGENDA_SCHEDULER_MIGRATION_SCHEMA_VERSION,
|
|
1064
|
+
state: "prepared",
|
|
1065
|
+
migrationId: migration,
|
|
1066
|
+
sourceDigest: expectedDigest,
|
|
1067
|
+
targetJobId: target,
|
|
1068
|
+
preparedAt: this._now(),
|
|
1069
|
+
};
|
|
1070
|
+
this._writeAll(kind, entries);
|
|
1071
|
+
return entry;
|
|
1072
|
+
}
|
|
1073
|
+
throw agendaStoreError(
|
|
1074
|
+
"AGENDA_SCHEDULER_ENTRY_NOT_FOUND",
|
|
1075
|
+
`Agenda entry does not exist: ${id}`,
|
|
1076
|
+
);
|
|
1077
|
+
},
|
|
1078
|
+
{ failIfUnavailable: true },
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
retireForSchedulerMigration(
|
|
1083
|
+
id,
|
|
1084
|
+
{
|
|
1085
|
+
migrationId,
|
|
1086
|
+
sourceDigest,
|
|
1087
|
+
targetJobId,
|
|
1088
|
+
retirementToken,
|
|
1089
|
+
atMs = null,
|
|
1090
|
+
} = {},
|
|
1091
|
+
) {
|
|
1092
|
+
const migration = normalizeMigrationField(migrationId, "migrationId");
|
|
1093
|
+
const expectedDigest = normalizeMigrationField(
|
|
1094
|
+
sourceDigest,
|
|
1095
|
+
"sourceDigest",
|
|
1096
|
+
);
|
|
1097
|
+
const target = normalizeMigrationField(targetJobId, "targetJobId");
|
|
1098
|
+
const token = normalizeMigrationField(retirementToken, "retirementToken");
|
|
1099
|
+
const now = atMs != null ? Number(atMs) : this._now();
|
|
1100
|
+
this._ensureDir();
|
|
1101
|
+
return withFileLock(
|
|
1102
|
+
path.join(this.dir, ".agenda-claims"),
|
|
1103
|
+
() => {
|
|
1104
|
+
for (const kind of SCHEDULE_KINDS) {
|
|
1105
|
+
const entries = this._readAll(kind, { failOnMalformed: true });
|
|
1106
|
+
const entry = entries.find((candidate) => candidate.id === id);
|
|
1107
|
+
if (!entry) continue;
|
|
1108
|
+
const prior = entry.schedulerMigration;
|
|
1109
|
+
if (
|
|
1110
|
+
prior?.schemaVersion !==
|
|
1111
|
+
AGENDA_SCHEDULER_MIGRATION_SCHEMA_VERSION ||
|
|
1112
|
+
prior.sourceDigest !== expectedDigest ||
|
|
1113
|
+
prior.targetJobId !== target ||
|
|
1114
|
+
prior.migrationId !== migration ||
|
|
1115
|
+
!["prepared", "retired"].includes(prior.state)
|
|
1116
|
+
) {
|
|
1117
|
+
throw agendaStoreError(
|
|
1118
|
+
"AGENDA_SCHEDULER_MIGRATION_CONFLICT",
|
|
1119
|
+
`Agenda migration preparation changed before retirement: ${id}`,
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
if (prior.state === "retired") {
|
|
1123
|
+
if (
|
|
1124
|
+
prior.retirementToken !== token ||
|
|
1125
|
+
!schedulerMigrationFence(entry, now)
|
|
1126
|
+
) {
|
|
1127
|
+
throw agendaStoreError(
|
|
1128
|
+
"AGENDA_SCHEDULER_MIGRATION_CONFLICT",
|
|
1129
|
+
`Agenda migration retirement fence changed: ${id}`,
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
return entry;
|
|
1133
|
+
}
|
|
1134
|
+
if (agendaMigrationSourceDigest(entry) !== expectedDigest) {
|
|
1135
|
+
throw agendaStoreError(
|
|
1136
|
+
"AGENDA_SCHEDULER_MIGRATION_SOURCE_CHANGED",
|
|
1137
|
+
`Agenda entry changed before retirement: ${id}`,
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
if (hasLiveExecutionLease(entry, now)) {
|
|
1141
|
+
throw agendaStoreError(
|
|
1142
|
+
"AGENDA_SCHEDULER_LEGACY_CLAIM_ACTIVE",
|
|
1143
|
+
`Agenda entry is already claimed by a legacy runner: ${id}`,
|
|
1144
|
+
{
|
|
1145
|
+
retryable: true,
|
|
1146
|
+
retryAt: Number(entry.executionLease.expiresAt),
|
|
1147
|
+
},
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
entry.schedulerMigration = {
|
|
1151
|
+
...prior,
|
|
1152
|
+
state: "retired",
|
|
1153
|
+
retirementToken: token,
|
|
1154
|
+
retiredAt: now,
|
|
1155
|
+
};
|
|
1156
|
+
applySchedulerMigrationFence(entry, now);
|
|
1157
|
+
this._writeAll(kind, entries);
|
|
1158
|
+
return entry;
|
|
1159
|
+
}
|
|
1160
|
+
throw agendaStoreError(
|
|
1161
|
+
"AGENDA_SCHEDULER_ENTRY_NOT_FOUND",
|
|
1162
|
+
`Agenda entry does not exist: ${id}`,
|
|
1163
|
+
);
|
|
1164
|
+
},
|
|
1165
|
+
{ failIfUnavailable: true },
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
restoreFromSchedulerMigration(
|
|
1170
|
+
id,
|
|
1171
|
+
{ migrationId, sourceDigest, targetJobId, retirementToken } = {},
|
|
1172
|
+
) {
|
|
1173
|
+
const migrationIdValue = normalizeMigrationField(
|
|
1174
|
+
migrationId,
|
|
1175
|
+
"migrationId",
|
|
1176
|
+
);
|
|
1177
|
+
const expectedDigest = normalizeMigrationField(
|
|
1178
|
+
sourceDigest,
|
|
1179
|
+
"sourceDigest",
|
|
1180
|
+
);
|
|
1181
|
+
const target = normalizeMigrationField(targetJobId, "targetJobId");
|
|
1182
|
+
const token =
|
|
1183
|
+
retirementToken === null || retirementToken === undefined
|
|
1184
|
+
? null
|
|
1185
|
+
: normalizeMigrationField(retirementToken, "retirementToken");
|
|
1186
|
+
this._ensureDir();
|
|
1187
|
+
return withFileLock(
|
|
1188
|
+
path.join(this.dir, ".agenda-claims"),
|
|
1189
|
+
() => {
|
|
1190
|
+
for (const kind of SCHEDULE_KINDS) {
|
|
1191
|
+
const entries = this._readAll(kind, { failOnMalformed: true });
|
|
1192
|
+
const entry = entries.find((candidate) => candidate.id === id);
|
|
1193
|
+
if (!entry) continue;
|
|
1194
|
+
const migration = entry.schedulerMigration;
|
|
1195
|
+
if (!migration) {
|
|
1196
|
+
if (
|
|
1197
|
+
agendaMigrationSourceDigest(entry) !== expectedDigest ||
|
|
1198
|
+
String(entry.executionLease?.owner || "").startsWith(
|
|
1199
|
+
"scheduler-migration:",
|
|
1200
|
+
)
|
|
1201
|
+
) {
|
|
1202
|
+
throw agendaStoreError(
|
|
1203
|
+
"AGENDA_SCHEDULER_MIGRATION_CONFLICT",
|
|
1204
|
+
`Agenda source changed after rollback restoration: ${id}`,
|
|
1205
|
+
);
|
|
1206
|
+
}
|
|
1207
|
+
return entry;
|
|
1208
|
+
}
|
|
1209
|
+
if (
|
|
1210
|
+
migration?.schemaVersion !==
|
|
1211
|
+
AGENDA_SCHEDULER_MIGRATION_SCHEMA_VERSION ||
|
|
1212
|
+
migration.sourceDigest !== expectedDigest ||
|
|
1213
|
+
agendaMigrationSourceDigest(entry) !== expectedDigest ||
|
|
1214
|
+
migration.targetJobId !== target ||
|
|
1215
|
+
migration.migrationId !== migrationIdValue ||
|
|
1216
|
+
!["prepared", "retired"].includes(migration.state) ||
|
|
1217
|
+
(migration.state === "retired" &&
|
|
1218
|
+
(migration.retirementToken !== token ||
|
|
1219
|
+
entry.executionLease?.owner !== `scheduler-migration:${token}`))
|
|
1220
|
+
) {
|
|
1221
|
+
throw agendaStoreError(
|
|
1222
|
+
"AGENDA_SCHEDULER_MIGRATION_CONFLICT",
|
|
1223
|
+
`Agenda migration fence changed before rollback: ${id}`,
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
delete entry.schedulerMigration;
|
|
1227
|
+
if (migration.state === "retired") delete entry.executionLease;
|
|
1228
|
+
this._writeAll(kind, entries);
|
|
1229
|
+
return entry;
|
|
1230
|
+
}
|
|
1231
|
+
throw agendaStoreError(
|
|
1232
|
+
"AGENDA_SCHEDULER_ENTRY_NOT_FOUND",
|
|
1233
|
+
`Agenda entry does not exist: ${id}`,
|
|
1234
|
+
);
|
|
1235
|
+
},
|
|
1236
|
+
{ failIfUnavailable: true },
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
|
|
823
1240
|
/** Entries whose time has come (pending wakeup / active cron|monitor). */
|
|
824
|
-
due(kind = null, atMs = null) {
|
|
1241
|
+
due(kind = null, atMs = null, { schedulerMigration = false } = {}) {
|
|
825
1242
|
const now = atMs != null ? atMs : this._now();
|
|
826
1243
|
return this.list(kind).filter((entry) => {
|
|
827
1244
|
// An entry past its expiry never fires — even if it is not yet retired
|
|
828
1245
|
// (defense-in-depth: retireExpired need not have run first).
|
|
829
1246
|
if (isEntryExpired(entry, now)) return false;
|
|
830
1247
|
if (!isSchedulableStatus(entry)) return false;
|
|
831
|
-
if (
|
|
1248
|
+
if (
|
|
1249
|
+
hasLiveExecutionLease(entry, now) &&
|
|
1250
|
+
!(schedulerMigration && schedulerMigrationFence(entry, now))
|
|
1251
|
+
) {
|
|
1252
|
+
return false;
|
|
1253
|
+
}
|
|
832
1254
|
// The effective fire time applies the entry's OWN deterministic jitter
|
|
833
1255
|
// (`entry.jitterMs`, default 0 → base fire time, byte-identical to the
|
|
834
1256
|
// prior `dueAt`/`nextAt` comparison). A jittered entry fires slightly
|
|
@@ -858,7 +1280,9 @@ export class AgentScheduleStore {
|
|
|
858
1280
|
() => {
|
|
859
1281
|
const claimed = [];
|
|
860
1282
|
for (const scheduleKind of kinds) {
|
|
861
|
-
const entries = this._readAll(scheduleKind
|
|
1283
|
+
const entries = this._readAll(scheduleKind, {
|
|
1284
|
+
failOnMalformed: true,
|
|
1285
|
+
});
|
|
862
1286
|
let changed = false;
|
|
863
1287
|
for (const entry of entries) {
|
|
864
1288
|
if (!isSchedulableStatus(entry) || isEntryExpired(entry, now))
|
|
@@ -929,7 +1353,7 @@ export class AgentScheduleStore {
|
|
|
929
1353
|
path.join(this.dir, ".agenda-claims"),
|
|
930
1354
|
() => {
|
|
931
1355
|
for (const kind of SCHEDULE_KINDS) {
|
|
932
|
-
const entries = this._readAll(kind);
|
|
1356
|
+
const entries = this._readAll(kind, { failOnMalformed: true });
|
|
933
1357
|
const entry = entries.find((candidate) => candidate.id === id);
|
|
934
1358
|
if (!entry) continue;
|
|
935
1359
|
const prior = entry.schedulerExecution;
|
|
@@ -946,7 +1370,8 @@ export class AgentScheduleStore {
|
|
|
946
1370
|
) {
|
|
947
1371
|
return entry;
|
|
948
1372
|
}
|
|
949
|
-
|
|
1373
|
+
const migrationFence = schedulerMigrationFence(entry, now);
|
|
1374
|
+
if (hasLiveExecutionLease(entry, now) && !migrationFence) {
|
|
950
1375
|
throw agendaStoreError(
|
|
951
1376
|
"AGENDA_SCHEDULER_LEGACY_CLAIM_ACTIVE",
|
|
952
1377
|
`Agenda entry is already claimed by a legacy runner: ${id}`,
|
|
@@ -1016,7 +1441,7 @@ export class AgentScheduleStore {
|
|
|
1016
1441
|
path.join(this.dir, ".agenda-claims"),
|
|
1017
1442
|
() => {
|
|
1018
1443
|
for (const kind of SCHEDULE_KINDS) {
|
|
1019
|
-
const entries = this._readAll(kind);
|
|
1444
|
+
const entries = this._readAll(kind, { failOnMalformed: true });
|
|
1020
1445
|
const entry = entries.find((candidate) => candidate.id === id);
|
|
1021
1446
|
if (!entry) continue;
|
|
1022
1447
|
const evidence = entry.schedulerExecution;
|
|
@@ -1051,6 +1476,102 @@ export class AgentScheduleStore {
|
|
|
1051
1476
|
...(outcome === "succeeded" ? { result: storedResult } : { error }),
|
|
1052
1477
|
};
|
|
1053
1478
|
delete entry.executionLease;
|
|
1479
|
+
if (isSchedulableStatus(entry))
|
|
1480
|
+
applySchedulerMigrationFence(entry, now);
|
|
1481
|
+
this._writeAll(kind, entries);
|
|
1482
|
+
return entry;
|
|
1483
|
+
}
|
|
1484
|
+
throw agendaStoreError(
|
|
1485
|
+
"AGENDA_SCHEDULER_ENTRY_NOT_FOUND",
|
|
1486
|
+
`Agenda entry does not exist: ${id}`,
|
|
1487
|
+
);
|
|
1488
|
+
},
|
|
1489
|
+
{ failIfUnavailable: true },
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
/**
|
|
1494
|
+
* Resolve start-only scheduler evidence after an operator has verified the
|
|
1495
|
+
* external outcome. This is intentionally separate from normal completion:
|
|
1496
|
+
* it preserves the adjudication identity and can release the non-expiring
|
|
1497
|
+
* legacy fence without pretending that a machine observed a terminal result.
|
|
1498
|
+
*/
|
|
1499
|
+
adjudicateSchedulerExecution(
|
|
1500
|
+
id,
|
|
1501
|
+
{
|
|
1502
|
+
occurrenceId,
|
|
1503
|
+
snapshotDigest,
|
|
1504
|
+
attempt,
|
|
1505
|
+
decision,
|
|
1506
|
+
requestId,
|
|
1507
|
+
atMs = null,
|
|
1508
|
+
} = {},
|
|
1509
|
+
) {
|
|
1510
|
+
if (!["confirmed_applied", "confirmed_not_applied"].includes(decision)) {
|
|
1511
|
+
throw agendaStoreError(
|
|
1512
|
+
"AGENDA_SCHEDULER_ADJUDICATION_INVALID",
|
|
1513
|
+
"Agenda scheduler adjudication decision is invalid",
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
const now = atMs != null ? Number(atMs) : this._now();
|
|
1517
|
+
this._ensureDir();
|
|
1518
|
+
return withFileLock(
|
|
1519
|
+
path.join(this.dir, ".agenda-claims"),
|
|
1520
|
+
() => {
|
|
1521
|
+
for (const kind of SCHEDULE_KINDS) {
|
|
1522
|
+
const entries = this._readAll(kind, { failOnMalformed: true });
|
|
1523
|
+
const entry = entries.find((candidate) => candidate.id === id);
|
|
1524
|
+
if (!entry) continue;
|
|
1525
|
+
const evidence = entry.schedulerExecution;
|
|
1526
|
+
if (
|
|
1527
|
+
evidence?.occurrenceId === occurrenceId &&
|
|
1528
|
+
evidence.snapshotDigest === snapshotDigest &&
|
|
1529
|
+
evidence.adjudication?.requestId === requestId &&
|
|
1530
|
+
evidence.adjudication.decision === decision &&
|
|
1531
|
+
evidence.status ===
|
|
1532
|
+
(decision === "confirmed_applied"
|
|
1533
|
+
? "adjudicated_applied"
|
|
1534
|
+
: "adjudicated_not_applied")
|
|
1535
|
+
) {
|
|
1536
|
+
return entry;
|
|
1537
|
+
}
|
|
1538
|
+
if (
|
|
1539
|
+
!evidence ||
|
|
1540
|
+
evidence.occurrenceId !== occurrenceId ||
|
|
1541
|
+
evidence.snapshotDigest !== snapshotDigest ||
|
|
1542
|
+
!Number.isSafeInteger(evidence.attempt) ||
|
|
1543
|
+
evidence.attempt < 1 ||
|
|
1544
|
+
evidence.attempt > attempt ||
|
|
1545
|
+
evidence.status !== "running"
|
|
1546
|
+
) {
|
|
1547
|
+
throw agendaStoreError(
|
|
1548
|
+
"AGENDA_SCHEDULER_ADJUDICATION_EVIDENCE_MISMATCH",
|
|
1549
|
+
`Agenda scheduler adjudication evidence does not match: ${id}`,
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
if (decision === "confirmed_applied") {
|
|
1553
|
+
if (entry.kind === "monitor") {
|
|
1554
|
+
// A generic operator decision proves the prior check ran, but
|
|
1555
|
+
// not whether its observation matched. Pause the monitor rather
|
|
1556
|
+
// than inventing a domain result or rearming a duplicate check.
|
|
1557
|
+
entry.status = "adjudicated";
|
|
1558
|
+
entry.lastCheckAt = now;
|
|
1559
|
+
} else {
|
|
1560
|
+
applyScheduledAgentSuccess(entry, now);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
entry.schedulerExecution = {
|
|
1564
|
+
...evidence,
|
|
1565
|
+
status:
|
|
1566
|
+
decision === "confirmed_applied"
|
|
1567
|
+
? "adjudicated_applied"
|
|
1568
|
+
: "adjudicated_not_applied",
|
|
1569
|
+
endedAt: now,
|
|
1570
|
+
adjudication: { requestId, decision },
|
|
1571
|
+
};
|
|
1572
|
+
delete entry.executionLease;
|
|
1573
|
+
if (isSchedulableStatus(entry))
|
|
1574
|
+
applySchedulerMigrationFence(entry, now);
|
|
1054
1575
|
this._writeAll(kind, entries);
|
|
1055
1576
|
return entry;
|
|
1056
1577
|
}
|
|
@@ -1074,7 +1595,7 @@ export class AgentScheduleStore {
|
|
|
1074
1595
|
const now = atMs != null ? atMs : this._now();
|
|
1075
1596
|
const retired = [];
|
|
1076
1597
|
for (const kind of SCHEDULE_KINDS) {
|
|
1077
|
-
const entries = this._readAll(kind);
|
|
1598
|
+
const entries = this._readAll(kind, { failOnMalformed: true });
|
|
1078
1599
|
let changed = false;
|
|
1079
1600
|
for (const entry of entries) {
|
|
1080
1601
|
if (isSchedulableStatus(entry) && isEntryExpired(entry, now)) {
|
|
@@ -1091,7 +1612,7 @@ export class AgentScheduleStore {
|
|
|
1091
1612
|
|
|
1092
1613
|
cancel(id) {
|
|
1093
1614
|
for (const kind of SCHEDULE_KINDS) {
|
|
1094
|
-
const entries = this._readAll(kind);
|
|
1615
|
+
const entries = this._readAll(kind, { failOnMalformed: true });
|
|
1095
1616
|
const idx = entries.findIndex((entry) => entry.id === id);
|
|
1096
1617
|
if (idx >= 0) {
|
|
1097
1618
|
const [removed] = entries.splice(idx, 1);
|
|
@@ -1112,7 +1633,7 @@ export class AgentScheduleStore {
|
|
|
1112
1633
|
pruneTerminal({ before = Infinity } = {}) {
|
|
1113
1634
|
const removed = [];
|
|
1114
1635
|
for (const kind of SCHEDULE_KINDS) {
|
|
1115
|
-
const entries = this._readAll(kind);
|
|
1636
|
+
const entries = this._readAll(kind, { failOnMalformed: true });
|
|
1116
1637
|
const keep = entries.filter((entry) => {
|
|
1117
1638
|
const prunable =
|
|
1118
1639
|
TERMINAL_SET.has(entry.status) && terminalAt(entry) <= before;
|
|
@@ -1176,7 +1697,7 @@ export class AgentScheduleStore {
|
|
|
1176
1697
|
}
|
|
1177
1698
|
|
|
1178
1699
|
_mutate(kind, id, fn) {
|
|
1179
|
-
const entries = this._readAll(kind);
|
|
1700
|
+
const entries = this._readAll(kind, { failOnMalformed: true });
|
|
1180
1701
|
const entry = entries.find((e) => e.id === id);
|
|
1181
1702
|
if (!entry) return null;
|
|
1182
1703
|
fn(entry);
|