chainlesschain 0.163.5 → 0.163.7
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/completions/_cc +1 -1
- package/completions/cc.bash +1 -1
- package/completions/cc.fish +1 -1
- package/completions/cc.ps1 +1 -1
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-Cv2EE1Zy.js → AIOps-axqQfwv8.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CcpxMLuW.js → ActionButton-DV-Ro0By.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DE8mJoMI.js → Analytics-Bt72WEVu.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DKn9dk9L.js → AppLayout-BtepG55E.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-Cp3mG3Py.js → Artifacts-B7KaJ7S_.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-zvsQfF2C.js → Audit-FNFaUg7A.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-Bi85SsjN.js → BackgroundAgents-DFb6J2wQ.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Cw47Dj6s.js → Backup-CC7YBhyj.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-C5Ip2afH.js → BaseInput-RkgTNVtj.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-C0iAFkJv.js → Chat-hHEmGZy-.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BV9Wv7wO.js → ChatBubbleRenderer-CAP9YhHv.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-3KZZCiAF.js → Checkbox-z5wXOQP1.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-C8OMbBAz.js → Codegen-DVaDJnxy.js} +1 -1
- package/src/assets/web-panel/assets/{Col-B-MB84LJ.js → Col-CJmaRDR5.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DpR7Dxao.js → Community-DF29_2bI.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CyDtz0OS.js → Compact-DtkQkBzn.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-5tiRa2An.js → Compliance-xoHDxp7I.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DnQaKZ5x.js → Cowork-BUm-R7ht.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-BaWfoEDf.js → Cron-Cgl14ZkB.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-Bu0HoHhb.js → Crosschain-BNcbmoqh.js} +1 -1
- package/src/assets/web-panel/assets/{DID-31zk8upX.js → DID-Bwja0Zo9.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BmVne-e2.js → Dashboard-Bk5XjEdJ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-B4PkoOWd.js → Dropdown-Ci36Oitq.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BZ6tyFtI.js → EmailListRenderer--U04ZN4q.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Osozn4Ds.js → FamilyGuardDashboard-B2gNPR2s.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DaH8TOAP.js → Federation-C4KghYn7.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-tbU0uwis.js → FormItemContext-QL7x_Q_0.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Df8LQBrL.js → GenericCardRenderer-F3Ryltwm.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DsMB6-14.js → Git-2HassK5k.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BnH0dZzd.js → Governance-DoBIp7oe.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DhnN1N_P.js → Inference--W01QhZ-.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DnNshAgn.js → KnowledgeGraph-DmvwqGpT.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BmBFX3_N.js → Logs-BBJ4yAel.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-DCslfQU3.js → MarkdownRenderer-BLCZMzhJ.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-CPEgWDc3.js → Marketplace-D5lNngSI.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-C36qjgF9.js → McpTools-CkEVOMKH.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-tNHvrgqN.js → Memory-B1Ow1xVE.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Xaf6cKjc.js → MobileBridge-SgYbUfb6.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-D2E5TTeO.js → MobileProjects-BWdOvQAj.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-D4AJ2dwV.js → Mtc-DcTXSSma.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-Df-gtn2_.js → MtcAudit-Dt3WG4oj.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-DTCHLWld.js → Multisig-B9B38UZx.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DruZ_1Jb.js → NLProgramming-B0S4GGvG.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Dx3pdGDs.js → Notes-DZ5U58Ng.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-Bi3L69oL.js → NotificationSettings-PjL5ZW34.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BHY33m7C.js → OrderTableRenderer-DrAX2ESt.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BG2ZVEV-.js → Organization-Cv1GYs-c.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Viwtg4Mn.js → Overflow-Bbkp_PL3.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Clm_yc1Z.js → P2P-BD8QUgoZ.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-NKmmDiOw.js → PdhVaultBrowser-BO-2PeaB.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-DIL8u9Nk.js → Permissions-D_OPVa02.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-B-jM4wt-.js → PersonalDataHub-mh-pEmaB.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-627I__H5.js → Pipeline-6JC2EDdb.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-2DN2qrkW.js → Privacy-CUshzUAG.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CN4wC0sF.js → ProjectInit-Cs2Aalri.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-K35vsh5H.js → ProjectSettings-1ak49tcj.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-B_wQadhX.js → Projects-BV4oApsm.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-MckaQUhQ.js → Providers-CXXxvafH.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BS3mtA_O.js → QrScannerModal-PTcDsF58.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BHp_nofL.js → QuickAsk-CDwucrJw.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DlmnPuZj.js → Recommend-CJlLVRXR.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-tsyFbvpy.js → RemoteSession-DlN-ETsO.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-Ba1iWv2D.js → Reputation-DI23LR6m.js} +1 -1
- package/src/assets/web-panel/assets/{Row-NnlGC--f.js → Row-Cl7UQMrM.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CjThSIRv.js → RssFeed-CexHNfh8.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CojUyC5x.js → Search-BneBWvRN.js} +1 -1
- package/src/assets/web-panel/assets/{Security-c-sxeJEc.js → Security-DX7Phgrp.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CtcKEEmF.js → Services-BWXO8Mte.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CIBBXflN.js → Skeleton-DCyOjKPJ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-D1XOOK9n.js → Skills-CXqERsTB.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-hRfEp5Sn.js → Sla-bhbrbz8E.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Dexb1f_6.js → SpeechSettings-3IPU0HBZ.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BJsSFGYy.js → SyncSettings-DBNTQLbM.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CfSZ8A1L.js → Tasks-quP0bkN-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-idDf47W-.js → Templates-BouWmgho.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-C-UsX6w6.js → Tenant-qv0D9s7z.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BgJtX5n1.js → Terminal-BxvbE8BB.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DP0BLKTl.js → TimelineRenderer-D-IeLnJ2.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-LwCzqETp.js → Tokens-CFMKKyVG.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-D_9tVch0.js → Trigger-L8as3UuJ.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BH2UlEWh.js → Trust-MxmA4VZG.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CIpD0nYb.js → UkeySign-C0VvJYRi.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-9ShhYxK_.js → VideoEditing-Bf5QZj8N.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-C7afGNIG.js → Wallet-BIJ9riRz.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-bUKn6i5R.js → WebAuthn-B8p9Mhkw.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-CxgQaGyp.js → WorkflowEditor-BVsuaPen.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DMVkmJwx.js → chat-BfkjoKpV.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CDzQh5nS.js → colors-DeAlsWK8.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-C51kEF4D.js → compact-item-Cs97bz5j.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BTk9bXBn.js → createContext-BqbG60LH.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DPty0mGN.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CMje33rb.js → hasIn-i_RY0b1X.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpcrFxMI.js → index-0-agYlWL.js} +1 -1
- package/src/assets/web-panel/assets/{index-BW4F5YDF.js → index-79V_zv1s.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRG-aqOs.js → index-B-3tEpTc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCRvG9lq.js → index-B-JVTrpr.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgivOKHA.js → index-B1CbOH1i.js} +1 -1
- package/src/assets/web-panel/assets/{index-BzCkNylB.js → index-B5UkyA3c.js} +1 -1
- package/src/assets/web-panel/assets/{index-x53Vf7la.js → index-BESLFt4q.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCLj9hcD.js → index-BOVkf1K3.js} +1 -1
- package/src/assets/web-panel/assets/{index-CypsfcEq.js → index-BYUVuwKt.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtlEqHLP.js → index-BcUXAB2s.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ld_4mFmC.js → index-BkBi5TmC.js} +1 -1
- package/src/assets/web-panel/assets/{index-DqU9njjT.js → index-Bm7lLR4c.js} +1 -1
- package/src/assets/web-panel/assets/index-Bt7g0Vq_.js +1 -0
- package/src/assets/web-panel/assets/{index-CwGPwjUu.js → index-BuLQobzW.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5IWTMVl.js → index-CE2VtTlp.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJfnCuMq.js → index-CED2PL_y.js} +1 -1
- package/src/assets/web-panel/assets/{index-hg_emBsi.js → index-CG3PRyAL.js} +1 -1
- package/src/assets/web-panel/assets/{index-togc_uYn.js → index-CIG8fQ2_.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4rQCS8T.js → index-COdGA-8n.js} +1 -1
- package/src/assets/web-panel/assets/{index-0Auljb10.js → index-CeWoNHau.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cd0hmPxD.js → index-Cf3WCLmy.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXQBPLyE.js → index-CgsqdnRV.js} +1 -1
- package/src/assets/web-panel/assets/index-ChVT0p9u.js +1 -0
- package/src/assets/web-panel/assets/{index-h31MwrME.js → index-D2B4VPyP.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1oZReaY.js → index-D9evtvg2.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfC-piWZ.js → index-DUUrv141.js} +1 -1
- package/src/assets/web-panel/assets/{index-D9rC-fGW.js → index-DiSkx4UB.js} +3 -3
- package/src/assets/web-panel/assets/{index-CvSeVzqe.js → index-Dk2qouAz.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtM8OT_7.js → index-Dk7-v4hP.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBz9LIMS.js → index-DmbfhJUP.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAfY7m7i.js → index-DqBgBpgp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVePe_Rf.js → index-PT_5sBMA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDLkNkLj.js → index-U4f5izHz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTlsE8Df.js → index-YXgLng0K.js} +1 -1
- package/src/assets/web-panel/assets/{index-BeEaAEIp.js → index-ZxO_-E8a.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0-zMtKA.js → index-dIHr4B_u.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1oZQmAA.js → index-gyFpvL7l.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRLQyp2A.js → index-s1opAL-M.js} +1 -1
- package/src/assets/web-panel/assets/{index-OS-vfbTA.js → index-yObj1O4n.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-vrA8K-ac.js → initDefaultProps-C9h9yKL3.js} +1 -1
- package/src/assets/web-panel/assets/{motion-ByEzwIAl.js → motion-BvmLsPdu.js} +1 -1
- package/src/assets/web-panel/assets/{move-Ck4AWjw-.js → move-CoL4ekU1.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-6p0l9SrB.js → mtc-parser-C4pMy6xV.js} +1 -1
- package/src/assets/web-panel/assets/{omit-B1CUPQ6R.js → omit-DPuJfNxx.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-B1STg1iW.js → pickAttrs-D0KEeaK2.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DTTllkBV.js → placementArrow-BpkyINxt.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-AIrrCfBb.js → responsiveObserve-DQI_wOq8.js} +1 -1
- package/src/assets/web-panel/assets/{slide-C_fPMjdR.js → slide-DYfdDiRg.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-D5ow2yIP.js → statusUtils-B5dtvb0D.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Dy3hEA--.js → styleChecker-CF310mSj.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-WfzcfSh4.js → useFlexGapSupport-D1Qp4BLp.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CKSejfas.js → useFs-C7_iyvJK.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-od3zpPBN.js → usePersonalDataHub-DZUt_fIm.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DRD_2DFA.js → vnode-D3dsi0lZ.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DmNrEtgf.js → zoom-WdbOlvdQ.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +5 -5
- package/src/command-manifest.json +1 -1
- package/src/commands/agenda.js +76 -4
- package/src/commands/agent.js +90 -1
- package/src/commands/automation.js +462 -3
- package/src/commands/background-session.js +102 -1
- package/src/commands/chat.js +11 -2
- package/src/commands/compact.js +106 -23
- package/src/commands/cowork.js +6 -1
- package/src/commands/loop.js +187 -38
- package/src/commands/routine.js +12 -5
- package/src/commands/scheduler-daemon.js +807 -0
- package/src/commands/session-observability.js +305 -0
- package/src/commands/session.js +38 -0
- package/src/data/changelog.json +28 -2
- 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 +737 -47
- package/src/lib/agent-team/team-adjudication.js +40 -1
- package/src/lib/automation-center-routines.js +363 -0
- package/src/lib/automation-center.js +435 -0
- package/src/lib/automation-engine.js +568 -19
- package/src/lib/automation-execution-authority.js +736 -0
- package/src/lib/background-agent-supervisor.js +871 -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/background-needs-input-incident.js +221 -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 +673 -19
- package/src/lib/cowork-observe.js +5 -1
- 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 +62 -3
- package/src/lib/permission-engine.js +14 -7
- package/src/lib/public-ide-capability-manifest.js +2 -0
- package/src/lib/routine-store.js +546 -86
- package/src/lib/runtime-usage-ledger.js +182 -0
- package/src/lib/scheduler-kernel/agenda-adapter.js +604 -29
- package/src/lib/scheduler-kernel/authority-resolver.js +163 -0
- package/src/lib/scheduler-kernel/automation-adapter.js +994 -0
- package/src/lib/scheduler-kernel/automation-event-adapter.js +757 -0
- package/src/lib/scheduler-kernel/contract.js +11 -3
- package/src/lib/scheduler-kernel/cowork-cron-adapter.js +343 -15
- package/src/lib/scheduler-kernel/loop-adapter.js +913 -0
- 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 +691 -22
- package/src/lib/scheduler-kernel/runtime.js +63 -1
- package/src/lib/scheduler-kernel/service.js +328 -0
- package/src/lib/scheduler-kernel/source-locator-path.js +142 -0
- package/src/lib/scheduler-kernel/store.js +3439 -206
- 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 +1274 -208
- package/src/runtime/coding-agent-contract-shared.cjs +5 -0
- 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/policies/agent-policy.js +4 -0
- package/src/workers/background-agent-worker.js +468 -98
- package/src/assets/web-panel/assets/devWarning-CdwPwYj8.js +0 -1
- package/src/assets/web-panel/assets/index-BEeOdfEe.js +0 -1
- package/src/assets/web-panel/assets/index-BTe0TOu6.js +0 -1
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { effectiveFireAt } from "../schedule-planner.js";
|
|
3
|
+
import {
|
|
4
|
+
agendaMigrationSourceDigest,
|
|
5
|
+
agendaMigrationSourceSnapshot,
|
|
6
|
+
nextCronTime,
|
|
7
|
+
normalizeTimeZone,
|
|
8
|
+
parseCron,
|
|
9
|
+
} from "../agent-schedule-store.js";
|
|
3
10
|
import {
|
|
4
11
|
SchedulerKernelError,
|
|
5
12
|
canonicalJson,
|
|
@@ -8,10 +15,21 @@ import {
|
|
|
8
15
|
normalizeJson,
|
|
9
16
|
} from "./contract.js";
|
|
10
17
|
import { SchedulerRuntime } from "./runtime.js";
|
|
18
|
+
import { schedulerMigrationSourceDigest } from "./store.js";
|
|
19
|
+
import {
|
|
20
|
+
bindSchedulerAuthorityPolicy,
|
|
21
|
+
createSchedulerAuthorityResolver,
|
|
22
|
+
} from "./authority-resolver.js";
|
|
23
|
+
import { canonicalSchedulerSourcePath } from "./source-locator-path.js";
|
|
11
24
|
|
|
12
25
|
export const AGENDA_SCHEDULER_KIND = "agenda";
|
|
13
26
|
export const AGENDA_SCHEDULER_CAPABILITY = "agent.execute";
|
|
14
|
-
export const
|
|
27
|
+
export const AGENDA_SCHEDULER_MONITOR_CAPABILITY = "monitor.observe";
|
|
28
|
+
export const AGENDA_SCHEDULER_ENTRY_KINDS = Object.freeze([
|
|
29
|
+
"wakeup",
|
|
30
|
+
"cron",
|
|
31
|
+
"monitor",
|
|
32
|
+
]);
|
|
15
33
|
export const AGENDA_SCHEDULER_RETRY_DELAY_MS = 60_000;
|
|
16
34
|
|
|
17
35
|
function agendaError(
|
|
@@ -45,6 +63,125 @@ function safeFailure(error) {
|
|
|
45
63
|
};
|
|
46
64
|
}
|
|
47
65
|
|
|
66
|
+
function capabilityForEntry(entry) {
|
|
67
|
+
return entry?.kind === "monitor"
|
|
68
|
+
? AGENDA_SCHEDULER_MONITOR_CAPABILITY
|
|
69
|
+
: AGENDA_SCHEDULER_CAPABILITY;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function normalizeMonitorObservation(entry, observation) {
|
|
73
|
+
if (
|
|
74
|
+
!observation ||
|
|
75
|
+
typeof observation !== "object" ||
|
|
76
|
+
Array.isArray(observation) ||
|
|
77
|
+
typeof observation.matched !== "boolean"
|
|
78
|
+
) {
|
|
79
|
+
throw agendaError(
|
|
80
|
+
"AGENDA_SCHEDULER_MONITOR_RESULT_INVALID",
|
|
81
|
+
`Agenda monitor returned an invalid observation: ${entry.id}`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
const mtimeMs =
|
|
85
|
+
observation.mtimeMs === null || observation.mtimeMs === undefined
|
|
86
|
+
? null
|
|
87
|
+
: Number(observation.mtimeMs);
|
|
88
|
+
if (mtimeMs !== null && (!Number.isFinite(mtimeMs) || mtimeMs < 0)) {
|
|
89
|
+
throw agendaError(
|
|
90
|
+
"AGENDA_SCHEDULER_MONITOR_RESULT_INVALID",
|
|
91
|
+
`Agenda monitor returned an invalid mtime: ${entry.id}`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
const eventId = observation.matched
|
|
95
|
+
? normalizeIdentifier(observation.eventId, "agenda.monitor.eventId")
|
|
96
|
+
: null;
|
|
97
|
+
const authority = observation.matched
|
|
98
|
+
? normalizeIdentifier(observation.authority, "agenda.monitor.authority")
|
|
99
|
+
: null;
|
|
100
|
+
const duplicate = observation.matched && entry.lastEventId === eventId;
|
|
101
|
+
const notification =
|
|
102
|
+
observation.notification &&
|
|
103
|
+
typeof observation.notification === "object" &&
|
|
104
|
+
!Array.isArray(observation.notification)
|
|
105
|
+
? {
|
|
106
|
+
title: String(observation.notification.title || ""),
|
|
107
|
+
body: String(observation.notification.body || ""),
|
|
108
|
+
level: String(observation.notification.level || "success"),
|
|
109
|
+
}
|
|
110
|
+
: null;
|
|
111
|
+
return {
|
|
112
|
+
matched: observation.matched,
|
|
113
|
+
mtimeMs,
|
|
114
|
+
eventId,
|
|
115
|
+
authority,
|
|
116
|
+
duplicate,
|
|
117
|
+
truncated: observation.truncated === true,
|
|
118
|
+
notification,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function validateMonitorSnapshot(snapshot) {
|
|
123
|
+
if (
|
|
124
|
+
!Number.isSafeInteger(snapshot.intervalMs) ||
|
|
125
|
+
snapshot.intervalMs < 1_000
|
|
126
|
+
) {
|
|
127
|
+
throw agendaError(
|
|
128
|
+
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
129
|
+
`Agenda monitor interval is invalid: ${snapshot.id}`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
if (!Number.isSafeInteger(snapshot.checks) || snapshot.checks < 0) {
|
|
133
|
+
throw agendaError(
|
|
134
|
+
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
135
|
+
`Agenda monitor check count is invalid: ${snapshot.id}`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
if (!["command", "file", "http"].includes(snapshot.source)) {
|
|
139
|
+
throw agendaError(
|
|
140
|
+
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
141
|
+
`Agenda monitor source is invalid: ${snapshot.id}`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
const sourceValue =
|
|
145
|
+
snapshot.source === "command"
|
|
146
|
+
? snapshot.command
|
|
147
|
+
: snapshot.source === "file"
|
|
148
|
+
? snapshot.watchFile
|
|
149
|
+
: snapshot.watchUrl;
|
|
150
|
+
if (typeof sourceValue !== "string" || sourceValue.length === 0) {
|
|
151
|
+
throw agendaError(
|
|
152
|
+
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
153
|
+
`Agenda monitor source target is missing: ${snapshot.id}`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
if (
|
|
157
|
+
snapshot.watchChange === true &&
|
|
158
|
+
(snapshot.source !== "file" || snapshot.stopWhen != null)
|
|
159
|
+
) {
|
|
160
|
+
throw agendaError(
|
|
161
|
+
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
162
|
+
`Agenda monitor change condition is invalid: ${snapshot.id}`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
if (snapshot.stopWhen != null) {
|
|
166
|
+
if (typeof snapshot.stopWhen !== "string") {
|
|
167
|
+
throw agendaError(
|
|
168
|
+
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
169
|
+
`Agenda monitor stop condition is invalid: ${snapshot.id}`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
new RegExp(snapshot.stopWhen);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
throw agendaError(
|
|
176
|
+
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
177
|
+
`Agenda monitor stop condition is invalid: ${snapshot.id}`,
|
|
178
|
+
undefined,
|
|
179
|
+
error,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
48
185
|
function validateEntrySnapshot(entry) {
|
|
49
186
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
50
187
|
throw agendaError(
|
|
@@ -55,6 +192,7 @@ function validateEntrySnapshot(entry) {
|
|
|
55
192
|
const plain = { ...entry };
|
|
56
193
|
delete plain.executionLease;
|
|
57
194
|
delete plain.schedulerExecution;
|
|
195
|
+
delete plain.schedulerMigration;
|
|
58
196
|
const snapshot = normalizeJson(plain, "agendaEntrySnapshot");
|
|
59
197
|
snapshot.id = normalizeIdentifier(snapshot.id, "agenda.entry.id");
|
|
60
198
|
if (!AGENDA_SCHEDULER_ENTRY_KINDS.includes(snapshot.kind)) {
|
|
@@ -63,21 +201,48 @@ function validateEntrySnapshot(entry) {
|
|
|
63
201
|
`Agenda scheduler does not support entry kind: ${snapshot.kind}`,
|
|
64
202
|
);
|
|
65
203
|
}
|
|
66
|
-
if (
|
|
204
|
+
if (
|
|
205
|
+
snapshot.kind !== "monitor" &&
|
|
206
|
+
(typeof snapshot.prompt !== "string" || snapshot.prompt.length === 0)
|
|
207
|
+
) {
|
|
67
208
|
throw agendaError(
|
|
68
209
|
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
69
210
|
`Agenda prompt is missing: ${snapshot.id}`,
|
|
70
211
|
);
|
|
71
212
|
}
|
|
213
|
+
if (snapshot.kind === "cron") {
|
|
214
|
+
try {
|
|
215
|
+
parseCron(snapshot.cron);
|
|
216
|
+
if (snapshot.timeZone != null) {
|
|
217
|
+
const timeZone = normalizeTimeZone(snapshot.timeZone);
|
|
218
|
+
if (!timeZone) throw new Error("cron timeZone must not be empty");
|
|
219
|
+
const nextAt = Number(snapshot.nextAt);
|
|
220
|
+
if (
|
|
221
|
+
nextCronTime(snapshot.cron, nextAt - 60_000, { timeZone }) !== nextAt
|
|
222
|
+
) {
|
|
223
|
+
throw new Error("cron nextAt does not match its time zone");
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
throw agendaError(
|
|
228
|
+
"AGENDA_SCHEDULER_CRON_INVALID",
|
|
229
|
+
`Agenda cron definition is invalid: ${snapshot.id}`,
|
|
230
|
+
undefined,
|
|
231
|
+
error,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
72
235
|
const schedulable =
|
|
73
236
|
(snapshot.kind === "wakeup" && snapshot.status === "pending") ||
|
|
74
|
-
(
|
|
237
|
+
(["cron", "monitor"].includes(snapshot.kind) &&
|
|
238
|
+
snapshot.status === "active");
|
|
75
239
|
if (!schedulable) {
|
|
76
240
|
throw agendaError(
|
|
77
241
|
"AGENDA_SCHEDULER_ENTRY_TERMINAL",
|
|
78
242
|
`Agenda entry is not schedulable: ${snapshot.id}`,
|
|
79
243
|
);
|
|
80
244
|
}
|
|
245
|
+
if (snapshot.kind === "monitor") validateMonitorSnapshot(snapshot);
|
|
81
246
|
normalizeEpochMs(Number(snapshot.createdAt), "agenda.entry.createdAt");
|
|
82
247
|
const fireAt = effectiveFireAt(snapshot);
|
|
83
248
|
if (fireAt == null) {
|
|
@@ -117,7 +282,14 @@ export function buildAgendaSchedulerJob(entry) {
|
|
|
117
282
|
source: "agent-schedule-store",
|
|
118
283
|
scheduleKind: snapshot.kind,
|
|
119
284
|
expression:
|
|
120
|
-
snapshot.kind === "cron"
|
|
285
|
+
snapshot.kind === "cron"
|
|
286
|
+
? snapshot.cron
|
|
287
|
+
: snapshot.kind === "monitor"
|
|
288
|
+
? `every:${snapshot.intervalMs}`
|
|
289
|
+
: String(snapshot.dueAt),
|
|
290
|
+
...(snapshot.kind === "cron" && snapshot.timeZone
|
|
291
|
+
? { timeZone: snapshot.timeZone }
|
|
292
|
+
: {}),
|
|
121
293
|
},
|
|
122
294
|
payload: {
|
|
123
295
|
entry: snapshot,
|
|
@@ -128,7 +300,7 @@ export function buildAgendaSchedulerJob(entry) {
|
|
|
128
300
|
principal: { type: "agenda", id: snapshot.id },
|
|
129
301
|
tenantId: null,
|
|
130
302
|
workspaceId: null,
|
|
131
|
-
requestedCapabilities: [
|
|
303
|
+
requestedCapabilities: [capabilityForEntry(snapshot)],
|
|
132
304
|
authorizationRefs: {
|
|
133
305
|
decisionId: null,
|
|
134
306
|
policyRevision: null,
|
|
@@ -163,6 +335,10 @@ function sameJob(current, desired) {
|
|
|
163
335
|
|
|
164
336
|
export function syncAgendaSchedulerJob(schedulerStore, entry) {
|
|
165
337
|
const desired = buildAgendaSchedulerJob(entry);
|
|
338
|
+
desired.authority = bindSchedulerAuthorityPolicy(
|
|
339
|
+
schedulerStore,
|
|
340
|
+
desired.authority,
|
|
341
|
+
);
|
|
166
342
|
let current = schedulerStore.getJob(desired.id);
|
|
167
343
|
if (!current) {
|
|
168
344
|
try {
|
|
@@ -190,6 +366,223 @@ export function syncAgendaSchedulerJob(schedulerStore, entry) {
|
|
|
190
366
|
}
|
|
191
367
|
}
|
|
192
368
|
|
|
369
|
+
export function migrateAgendaSchedulerEntry({
|
|
370
|
+
agendaStore,
|
|
371
|
+
schedulerStore,
|
|
372
|
+
entry,
|
|
373
|
+
}) {
|
|
374
|
+
const snapshot = agendaEntrySnapshot(entry);
|
|
375
|
+
const directory = canonicalSchedulerSourcePath(agendaStore.dir);
|
|
376
|
+
const sourceScope = { store: "agent-schedule", directory };
|
|
377
|
+
const sourceLocator = {
|
|
378
|
+
schemaVersion: 1,
|
|
379
|
+
type: "agenda-store",
|
|
380
|
+
directory,
|
|
381
|
+
};
|
|
382
|
+
let prepared = schedulerStore.getActiveDomainMigrationBySource({
|
|
383
|
+
domain: "agenda",
|
|
384
|
+
sourceScope,
|
|
385
|
+
sourceId: snapshot.id,
|
|
386
|
+
});
|
|
387
|
+
if (!prepared && agendaStore.dir !== directory) {
|
|
388
|
+
const ambiguousLegacy = schedulerStore.getActiveDomainMigrationBySource({
|
|
389
|
+
domain: "agenda",
|
|
390
|
+
sourceScope: {
|
|
391
|
+
store: "agent-schedule",
|
|
392
|
+
directory: agendaStore.dir,
|
|
393
|
+
},
|
|
394
|
+
sourceId: snapshot.id,
|
|
395
|
+
});
|
|
396
|
+
if (ambiguousLegacy) {
|
|
397
|
+
throw agendaError(
|
|
398
|
+
"AGENDA_SCHEDULER_MIGRATION_SOURCE_UNBOUND",
|
|
399
|
+
`Legacy Agenda migration uses a non-canonical path scope and cannot be bound safely: ${snapshot.id}`,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const desired = buildAgendaSchedulerJob(snapshot);
|
|
404
|
+
desired.authority = bindSchedulerAuthorityPolicy(
|
|
405
|
+
schedulerStore,
|
|
406
|
+
desired.authority,
|
|
407
|
+
);
|
|
408
|
+
const sourceDigest = agendaMigrationSourceDigest(entry);
|
|
409
|
+
const sourceSnapshot = agendaMigrationSourceSnapshot(entry);
|
|
410
|
+
if (prepared) {
|
|
411
|
+
const migrationEntries = prepared.entries.filter(
|
|
412
|
+
(candidate) =>
|
|
413
|
+
candidate.domain === "agenda" && candidate.sourceId === snapshot.id,
|
|
414
|
+
);
|
|
415
|
+
if (prepared.entries.length !== 1 || migrationEntries.length !== 1) {
|
|
416
|
+
throw agendaError(
|
|
417
|
+
"AGENDA_SCHEDULER_MIGRATION_DOMAIN_MISMATCH",
|
|
418
|
+
"Agenda entry belongs to an invalid mixed-domain migration",
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
const [migrationEntry] = migrationEntries;
|
|
422
|
+
schedulerStore.bindDomainMigrationSourceLocator({
|
|
423
|
+
migrationId: prepared.id,
|
|
424
|
+
entryId: migrationEntry.entryId,
|
|
425
|
+
sourceLocator,
|
|
426
|
+
expectedSourceDigest: migrationEntry.sourceDigest,
|
|
427
|
+
expectedTargetJobId: migrationEntry.targetJobId,
|
|
428
|
+
});
|
|
429
|
+
prepared = schedulerStore.getDomainMigration(prepared.id);
|
|
430
|
+
} else {
|
|
431
|
+
prepared = schedulerStore.prepareDomainMigration({
|
|
432
|
+
entries: [
|
|
433
|
+
{
|
|
434
|
+
domain: "agenda",
|
|
435
|
+
sourceId: snapshot.id,
|
|
436
|
+
sourceScope,
|
|
437
|
+
sourceLocator,
|
|
438
|
+
source: sourceSnapshot,
|
|
439
|
+
targetJob: desired,
|
|
440
|
+
rollbackStrategy: "disable",
|
|
441
|
+
},
|
|
442
|
+
],
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
if (
|
|
446
|
+
prepared.entries.length !== 1 ||
|
|
447
|
+
prepared.entries[0].domain !== "agenda"
|
|
448
|
+
) {
|
|
449
|
+
throw agendaError(
|
|
450
|
+
"AGENDA_SCHEDULER_MIGRATION_DOMAIN_MISMATCH",
|
|
451
|
+
"Agenda entry belongs to an invalid mixed-domain migration",
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
const expectedJournalDigest = schedulerMigrationSourceDigest(sourceSnapshot);
|
|
455
|
+
if (
|
|
456
|
+
prepared.entries[0].sourceDigest !== expectedJournalDigest ||
|
|
457
|
+
prepared.entries[0].targetJobId !== desired.id
|
|
458
|
+
) {
|
|
459
|
+
throw agendaError(
|
|
460
|
+
"AGENDA_SCHEDULER_MIGRATION_SOURCE_CHANGED",
|
|
461
|
+
`Agenda entry changed after its scheduler migration started: ${snapshot.id}`,
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
let migration = prepared;
|
|
465
|
+
if (migration.state === "rolled_back" || migration.state === "rolling_back") {
|
|
466
|
+
throw agendaError(
|
|
467
|
+
"AGENDA_SCHEDULER_MIGRATION_STATE_CONFLICT",
|
|
468
|
+
`Agenda migration cannot resume from state ${migration.state}`,
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
if (migration.state === "retired") {
|
|
472
|
+
const migrationEntry = migration.entries[0];
|
|
473
|
+
agendaStore.retireForSchedulerMigration(snapshot.id, {
|
|
474
|
+
migrationId: migration.id,
|
|
475
|
+
sourceDigest,
|
|
476
|
+
targetJobId: desired.id,
|
|
477
|
+
retirementToken: migrationEntry.retirementToken,
|
|
478
|
+
});
|
|
479
|
+
return migration;
|
|
480
|
+
}
|
|
481
|
+
if (migration.state === "prepared") {
|
|
482
|
+
agendaStore.prepareSchedulerMigration(snapshot.id, {
|
|
483
|
+
migrationId: migration.id,
|
|
484
|
+
sourceDigest,
|
|
485
|
+
targetJobId: desired.id,
|
|
486
|
+
});
|
|
487
|
+
migration = schedulerStore.applyDomainMigration(migration.id);
|
|
488
|
+
}
|
|
489
|
+
if (migration.state === "applied") {
|
|
490
|
+
migration = schedulerStore.verifyDomainMigration(migration.id, {
|
|
491
|
+
sources: [
|
|
492
|
+
{
|
|
493
|
+
entryId: migration.entries[0].entryId,
|
|
494
|
+
source: agendaMigrationSourceSnapshot(entry),
|
|
495
|
+
},
|
|
496
|
+
],
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
const retiring =
|
|
500
|
+
migration.state === "retiring"
|
|
501
|
+
? migration
|
|
502
|
+
: schedulerStore.beginDomainMigrationRetirement(migration.id);
|
|
503
|
+
const retiringEntry = retiring.entries[0];
|
|
504
|
+
const retired = agendaStore.retireForSchedulerMigration(snapshot.id, {
|
|
505
|
+
migrationId: prepared.id,
|
|
506
|
+
sourceDigest,
|
|
507
|
+
targetJobId: desired.id,
|
|
508
|
+
retirementToken: retiringEntry.retirementToken,
|
|
509
|
+
});
|
|
510
|
+
schedulerStore.confirmDomainMigrationEntryRetired({
|
|
511
|
+
migrationId: prepared.id,
|
|
512
|
+
entryId: retiringEntry.entryId,
|
|
513
|
+
retirementToken: retiringEntry.retirementToken,
|
|
514
|
+
source: retired,
|
|
515
|
+
});
|
|
516
|
+
return schedulerStore.getDomainMigration(prepared.id);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
export function rollbackAgendaSchedulerMigration({
|
|
520
|
+
agendaStore,
|
|
521
|
+
schedulerStore,
|
|
522
|
+
migrationId,
|
|
523
|
+
}) {
|
|
524
|
+
const directory = canonicalSchedulerSourcePath(agendaStore.dir);
|
|
525
|
+
const assertRollbackBinding = (migration) => {
|
|
526
|
+
if (
|
|
527
|
+
migration?.id !== migrationId ||
|
|
528
|
+
!Array.isArray(migration.entries) ||
|
|
529
|
+
migration.entries.length === 0 ||
|
|
530
|
+
migration.entries.some((entry) => entry.domain !== "agenda")
|
|
531
|
+
) {
|
|
532
|
+
throw agendaError(
|
|
533
|
+
"AGENDA_SCHEDULER_MIGRATION_DOMAIN_MISMATCH",
|
|
534
|
+
"Agenda rollback cannot operate on a mixed-domain migration",
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
for (const entry of migration.entries) {
|
|
538
|
+
const locator = entry.sourceLocator;
|
|
539
|
+
if (
|
|
540
|
+
locator?.schemaVersion !== 1 ||
|
|
541
|
+
locator.type !== "agenda-store" ||
|
|
542
|
+
locator.directory !== directory
|
|
543
|
+
) {
|
|
544
|
+
throw agendaError(
|
|
545
|
+
"AGENDA_SCHEDULER_MIGRATION_SOURCE_UNBOUND",
|
|
546
|
+
`Agenda rollback source locator does not match its store: ${entry.sourceId}`,
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return migration;
|
|
551
|
+
};
|
|
552
|
+
const current = assertRollbackBinding(
|
|
553
|
+
schedulerStore.getDomainMigration(migrationId),
|
|
554
|
+
);
|
|
555
|
+
if (current.state === "rolled_back") {
|
|
556
|
+
return { ...current, deduplicated: true };
|
|
557
|
+
}
|
|
558
|
+
const migration = assertRollbackBinding(
|
|
559
|
+
schedulerStore.beginDomainMigrationRollback(migrationId),
|
|
560
|
+
);
|
|
561
|
+
if (migration.state === "rolled_back") return migration;
|
|
562
|
+
const entries = migration.entries;
|
|
563
|
+
for (const entry of entries) {
|
|
564
|
+
if (entry.state === "rolled_back") continue;
|
|
565
|
+
schedulerStore.restoreDomainMigrationEntrySource({
|
|
566
|
+
migrationId,
|
|
567
|
+
entryId: entry.entryId,
|
|
568
|
+
retirementToken: entry.retirementToken,
|
|
569
|
+
restoreSource: () => {
|
|
570
|
+
const current = agendaStore.get(entry.sourceId);
|
|
571
|
+
const restored = current.schedulerMigration
|
|
572
|
+
? agendaStore.restoreFromSchedulerMigration(entry.sourceId, {
|
|
573
|
+
migrationId,
|
|
574
|
+
sourceDigest: agendaMigrationSourceDigest(current),
|
|
575
|
+
targetJobId: entry.targetJobId,
|
|
576
|
+
retirementToken: entry.retirementToken,
|
|
577
|
+
})
|
|
578
|
+
: current;
|
|
579
|
+
return agendaMigrationSourceSnapshot(restored);
|
|
580
|
+
},
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
return schedulerStore.getDomainMigration(migrationId);
|
|
584
|
+
}
|
|
585
|
+
|
|
193
586
|
export function enqueueAgendaEntry(schedulerStore, entry) {
|
|
194
587
|
const snapshot = agendaEntrySnapshot(entry);
|
|
195
588
|
const job = syncAgendaSchedulerJob(schedulerStore, snapshot);
|
|
@@ -216,7 +609,7 @@ export function authorizeAgendaOccurrence({ job, occurrence }) {
|
|
|
216
609
|
authority?.principal?.id === entry.id &&
|
|
217
610
|
Array.isArray(authority?.requestedCapabilities) &&
|
|
218
611
|
authority.requestedCapabilities.length === 1 &&
|
|
219
|
-
authority.requestedCapabilities[0] ===
|
|
612
|
+
authority.requestedCapabilities[0] === capabilityForEntry(entry) &&
|
|
220
613
|
payload.snapshotDigest === expectedDigest;
|
|
221
614
|
return {
|
|
222
615
|
allowed,
|
|
@@ -231,7 +624,11 @@ function recoveredResult(entry, evidence) {
|
|
|
231
624
|
const result =
|
|
232
625
|
evidence.result && typeof evidence.result === "object"
|
|
233
626
|
? evidence.result
|
|
234
|
-
: {
|
|
627
|
+
: {
|
|
628
|
+
id: entry.id,
|
|
629
|
+
kind: entry.kind,
|
|
630
|
+
action: entry.kind === "monitor" ? "checked" : "fired",
|
|
631
|
+
};
|
|
235
632
|
return { ...result, recovered: true };
|
|
236
633
|
}
|
|
237
634
|
|
|
@@ -251,6 +648,8 @@ function matchingExecutionEvidence(
|
|
|
251
648
|
export function createAgendaSchedulerAdapter({
|
|
252
649
|
agendaStore,
|
|
253
650
|
runAgent,
|
|
651
|
+
runMonitor,
|
|
652
|
+
notifyMonitor,
|
|
254
653
|
now = Date.now,
|
|
255
654
|
} = {}) {
|
|
256
655
|
if (
|
|
@@ -278,6 +677,40 @@ export function createAgendaSchedulerAdapter({
|
|
|
278
677
|
}
|
|
279
678
|
return {
|
|
280
679
|
kind: AGENDA_SCHEDULER_KIND,
|
|
680
|
+
async adjudicate({ occurrence, adjudication }) {
|
|
681
|
+
const payload = occurrence.payload;
|
|
682
|
+
const expected = agendaEntrySnapshot(payload?.entry);
|
|
683
|
+
const expectedDigest = agendaEntrySnapshotDigest(expected);
|
|
684
|
+
if (
|
|
685
|
+
payload.snapshotDigest !== expectedDigest ||
|
|
686
|
+
typeof agendaStore.adjudicateSchedulerExecution !== "function"
|
|
687
|
+
) {
|
|
688
|
+
throw agendaError(
|
|
689
|
+
"AGENDA_SCHEDULER_ADJUDICATION_UNSUPPORTED",
|
|
690
|
+
`Agenda adjudication cannot be applied: ${occurrence.id}`,
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
agendaStore.adjudicateSchedulerExecution(expected.id, {
|
|
694
|
+
occurrenceId: occurrence.id,
|
|
695
|
+
snapshotDigest: expectedDigest,
|
|
696
|
+
attempt: adjudication.expectedAttempt,
|
|
697
|
+
decision: adjudication.decision,
|
|
698
|
+
requestId: adjudication.requestId,
|
|
699
|
+
atMs: now(),
|
|
700
|
+
});
|
|
701
|
+
if (adjudication.decision === "confirmed_applied") {
|
|
702
|
+
return {
|
|
703
|
+
settled: true,
|
|
704
|
+
result: {
|
|
705
|
+
id: expected.id,
|
|
706
|
+
kind: expected.kind,
|
|
707
|
+
action: "adjudicated-applied",
|
|
708
|
+
adjudicationRequestId: adjudication.requestId,
|
|
709
|
+
},
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
return { continue: true };
|
|
713
|
+
},
|
|
281
714
|
async execute({ occurrence }) {
|
|
282
715
|
const payload = occurrence.payload;
|
|
283
716
|
const expected = agendaEntrySnapshot(payload?.entry);
|
|
@@ -309,7 +742,7 @@ export function createAgendaSchedulerAdapter({
|
|
|
309
742
|
if (prior.status === "running") {
|
|
310
743
|
throw agendaError(
|
|
311
744
|
"AGENDA_SCHEDULER_OUTCOME_UNKNOWN",
|
|
312
|
-
`Agenda
|
|
745
|
+
`Agenda execution outcome is unknown; refusing duplicate execution: ${expected.id}`,
|
|
313
746
|
);
|
|
314
747
|
}
|
|
315
748
|
} else if (prior?.status === "running") {
|
|
@@ -347,14 +780,54 @@ export function createAgendaSchedulerAdapter({
|
|
|
347
780
|
throw stale;
|
|
348
781
|
}
|
|
349
782
|
|
|
783
|
+
let observation = null;
|
|
784
|
+
let action = null;
|
|
350
785
|
try {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
786
|
+
if (expected.kind === "monitor") {
|
|
787
|
+
if (typeof runMonitor !== "function") {
|
|
788
|
+
throw agendaError(
|
|
789
|
+
"AGENDA_SCHEDULER_MONITOR_RUNNER_REQUIRED",
|
|
790
|
+
"Agenda scheduler monitor execution requires a monitor runner",
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
observation = normalizeMonitorObservation(
|
|
794
|
+
expected,
|
|
795
|
+
await runMonitor({
|
|
796
|
+
entry: expected,
|
|
797
|
+
occurrenceId: occurrence.id,
|
|
798
|
+
attempt: occurrence.attempt,
|
|
799
|
+
}),
|
|
800
|
+
);
|
|
801
|
+
action = {
|
|
802
|
+
id: expected.id,
|
|
803
|
+
kind: expected.kind,
|
|
804
|
+
action: observation.matched
|
|
805
|
+
? observation.duplicate
|
|
806
|
+
? "duplicate"
|
|
807
|
+
: "matched"
|
|
808
|
+
: "checked",
|
|
809
|
+
...(observation.matched
|
|
810
|
+
? {
|
|
811
|
+
event_id: observation.eventId,
|
|
812
|
+
authority: observation.authority,
|
|
813
|
+
}
|
|
814
|
+
: {}),
|
|
815
|
+
...(observation.truncated ? { truncated: true } : {}),
|
|
816
|
+
};
|
|
817
|
+
} else {
|
|
818
|
+
await runAgent({
|
|
819
|
+
entry: expected,
|
|
820
|
+
prompt: expected.prompt,
|
|
821
|
+
runPolicy: expected.runPolicy,
|
|
822
|
+
occurrenceId: occurrence.id,
|
|
823
|
+
attempt: occurrence.attempt,
|
|
824
|
+
});
|
|
825
|
+
action = {
|
|
826
|
+
id: expected.id,
|
|
827
|
+
kind: expected.kind,
|
|
828
|
+
action: "fired",
|
|
829
|
+
};
|
|
830
|
+
}
|
|
358
831
|
} catch (error) {
|
|
359
832
|
let failurePersisted = false;
|
|
360
833
|
try {
|
|
@@ -389,14 +862,16 @@ export function createAgendaSchedulerAdapter({
|
|
|
389
862
|
throw agendaError(
|
|
390
863
|
typeof error?.code === "string"
|
|
391
864
|
? error.code
|
|
392
|
-
: "
|
|
865
|
+
: expected.kind === "monitor"
|
|
866
|
+
? "AGENDA_MONITOR_EXECUTION_FAILED"
|
|
867
|
+
: "AGENDA_AGENT_EXECUTION_FAILED",
|
|
393
868
|
typeof error?.message === "string"
|
|
394
869
|
? error.message
|
|
395
|
-
: `Agenda agent execution failed: ${expected.id}`,
|
|
870
|
+
: `Agenda ${expected.kind === "monitor" ? "monitor" : "agent"} execution failed: ${expected.id}`,
|
|
396
871
|
undefined,
|
|
397
872
|
error,
|
|
398
873
|
{
|
|
399
|
-
retryable:
|
|
874
|
+
retryable: error?.retryable !== false,
|
|
400
875
|
retryAt: normalizeEpochMs(
|
|
401
876
|
Number(now()) + AGENDA_SCHEDULER_RETRY_DELAY_MS,
|
|
402
877
|
"agenda.retryAt",
|
|
@@ -405,21 +880,50 @@ export function createAgendaSchedulerAdapter({
|
|
|
405
880
|
);
|
|
406
881
|
}
|
|
407
882
|
|
|
408
|
-
const action = {
|
|
409
|
-
id: expected.id,
|
|
410
|
-
kind: expected.kind,
|
|
411
|
-
action: "fired",
|
|
412
|
-
};
|
|
413
883
|
try {
|
|
414
|
-
agendaStore.completeSchedulerExecution(expected.id, {
|
|
884
|
+
const completed = agendaStore.completeSchedulerExecution(expected.id, {
|
|
415
885
|
occurrenceId: occurrence.id,
|
|
416
886
|
snapshotDigest: expectedDigest,
|
|
417
887
|
attempt: occurrence.attempt,
|
|
418
888
|
outcome: "succeeded",
|
|
419
889
|
result: action,
|
|
890
|
+
...(observation
|
|
891
|
+
? {
|
|
892
|
+
monitorCheck: {
|
|
893
|
+
matched: observation.matched,
|
|
894
|
+
mtimeMs: observation.mtimeMs,
|
|
895
|
+
eventId: observation.eventId,
|
|
896
|
+
authority: observation.authority,
|
|
897
|
+
},
|
|
898
|
+
}
|
|
899
|
+
: {}),
|
|
420
900
|
atMs: now(),
|
|
421
901
|
});
|
|
422
|
-
|
|
902
|
+
const persistedAction = completed.schedulerExecution?.result || action;
|
|
903
|
+
if (
|
|
904
|
+
observation?.matched &&
|
|
905
|
+
!observation.duplicate &&
|
|
906
|
+
observation.notification
|
|
907
|
+
) {
|
|
908
|
+
if (typeof notifyMonitor !== "function") {
|
|
909
|
+
return {
|
|
910
|
+
...persistedAction,
|
|
911
|
+
notifyError: "Agenda monitor notification handler is unavailable",
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
try {
|
|
915
|
+
await notifyMonitor(observation.notification);
|
|
916
|
+
} catch (notifyError) {
|
|
917
|
+
return {
|
|
918
|
+
...persistedAction,
|
|
919
|
+
notifyError:
|
|
920
|
+
typeof notifyError?.message === "string"
|
|
921
|
+
? notifyError.message
|
|
922
|
+
: String(notifyError),
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return persistedAction;
|
|
423
927
|
} catch (completionError) {
|
|
424
928
|
const persisted = agendaStore.get(expected.id);
|
|
425
929
|
if (
|
|
@@ -454,10 +958,14 @@ export class AgendaSchedulerBridge {
|
|
|
454
958
|
agendaStore,
|
|
455
959
|
schedulerStore,
|
|
456
960
|
runAgent,
|
|
961
|
+
runMonitor,
|
|
962
|
+
notifyMonitor,
|
|
457
963
|
now = Date.now,
|
|
458
964
|
ownerId,
|
|
459
965
|
leaseMs,
|
|
460
966
|
renewIntervalMs,
|
|
967
|
+
authorityResolver,
|
|
968
|
+
migrateLegacy = true,
|
|
461
969
|
} = {}) {
|
|
462
970
|
if (typeof now !== "function") {
|
|
463
971
|
throw agendaError(
|
|
@@ -468,10 +976,29 @@ export class AgendaSchedulerBridge {
|
|
|
468
976
|
this.agendaStore = agendaStore;
|
|
469
977
|
this.schedulerStore = schedulerStore;
|
|
470
978
|
this.now = now;
|
|
979
|
+
this.migrateLegacy = migrateLegacy !== false;
|
|
471
980
|
this.runtime = new SchedulerRuntime({
|
|
472
981
|
store: schedulerStore,
|
|
473
|
-
adapters: [
|
|
474
|
-
|
|
982
|
+
adapters: [
|
|
983
|
+
createAgendaSchedulerAdapter({
|
|
984
|
+
agendaStore,
|
|
985
|
+
runAgent,
|
|
986
|
+
runMonitor,
|
|
987
|
+
notifyMonitor,
|
|
988
|
+
now,
|
|
989
|
+
}),
|
|
990
|
+
],
|
|
991
|
+
authorize:
|
|
992
|
+
authorityResolver ||
|
|
993
|
+
createSchedulerAuthorityResolver({
|
|
994
|
+
store: schedulerStore,
|
|
995
|
+
validate: authorizeAgendaOccurrence,
|
|
996
|
+
units: ({ occurrence }) =>
|
|
997
|
+
Math.max(
|
|
998
|
+
1,
|
|
999
|
+
Number(occurrence?.payload?.entry?.runPolicy?.maxTurns) || 1,
|
|
1000
|
+
),
|
|
1001
|
+
}),
|
|
475
1002
|
...(ownerId === undefined ? {} : { ownerId }),
|
|
476
1003
|
...(leaseMs === undefined ? {} : { leaseMs }),
|
|
477
1004
|
...(renewIntervalMs === undefined ? {} : { renewIntervalMs }),
|
|
@@ -481,6 +1008,31 @@ export class AgendaSchedulerBridge {
|
|
|
481
1008
|
async runDue({ signal } = {}) {
|
|
482
1009
|
const results = [];
|
|
483
1010
|
const observedOccurrences = new Set();
|
|
1011
|
+
if (!signal?.aborted && this.migrateLegacy) {
|
|
1012
|
+
const sourceScope = {
|
|
1013
|
+
store: "agent-schedule",
|
|
1014
|
+
directory: canonicalSchedulerSourcePath(this.agendaStore.dir),
|
|
1015
|
+
};
|
|
1016
|
+
const candidates = this.agendaStore.list().filter((entry) => {
|
|
1017
|
+
const activeMigration =
|
|
1018
|
+
this.schedulerStore.getActiveDomainMigrationBySource({
|
|
1019
|
+
domain: "agenda",
|
|
1020
|
+
sourceScope,
|
|
1021
|
+
sourceId: entry.id,
|
|
1022
|
+
});
|
|
1023
|
+
if (entry.schedulerMigration) {
|
|
1024
|
+
return activeMigration?.state !== "retired";
|
|
1025
|
+
}
|
|
1026
|
+
return activeMigration?.state !== undefined;
|
|
1027
|
+
});
|
|
1028
|
+
for (const entry of candidates) {
|
|
1029
|
+
migrateAgendaSchedulerEntry({
|
|
1030
|
+
agendaStore: this.agendaStore,
|
|
1031
|
+
schedulerStore: this.schedulerStore,
|
|
1032
|
+
entry,
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
484
1036
|
const recovered = await this.runtime.runUntilIdle({
|
|
485
1037
|
limit: 10_000,
|
|
486
1038
|
signal,
|
|
@@ -503,8 +1055,9 @@ export class AgendaSchedulerBridge {
|
|
|
503
1055
|
|
|
504
1056
|
const now = normalizeEpochMs(Number(this.now()), "now");
|
|
505
1057
|
const due = [
|
|
506
|
-
...this.agendaStore.due("wakeup", now),
|
|
507
|
-
...this.agendaStore.due("cron", now),
|
|
1058
|
+
...this.agendaStore.due("wakeup", now, { schedulerMigration: true }),
|
|
1059
|
+
...this.agendaStore.due("cron", now, { schedulerMigration: true }),
|
|
1060
|
+
...this.agendaStore.due("monitor", now, { schedulerMigration: true }),
|
|
508
1061
|
];
|
|
509
1062
|
for (const entry of due) {
|
|
510
1063
|
const occurrence = enqueueAgendaEntry(this.schedulerStore, entry);
|
|
@@ -520,6 +1073,28 @@ export class AgendaSchedulerBridge {
|
|
|
520
1073
|
result,
|
|
521
1074
|
});
|
|
522
1075
|
}
|
|
1076
|
+
if (this.migrateLegacy) {
|
|
1077
|
+
const candidates = this.agendaStore
|
|
1078
|
+
.list()
|
|
1079
|
+
.filter(
|
|
1080
|
+
(entry) =>
|
|
1081
|
+
!entry.schedulerMigration &&
|
|
1082
|
+
[undefined, "succeeded"].includes(
|
|
1083
|
+
entry.schedulerExecution?.status,
|
|
1084
|
+
) &&
|
|
1085
|
+
!entry.executionLease &&
|
|
1086
|
+
((entry.kind === "wakeup" && entry.status === "pending") ||
|
|
1087
|
+
(["cron", "monitor"].includes(entry.kind) &&
|
|
1088
|
+
entry.status === "active")),
|
|
1089
|
+
);
|
|
1090
|
+
for (const entry of candidates) {
|
|
1091
|
+
migrateAgendaSchedulerEntry({
|
|
1092
|
+
agendaStore: this.agendaStore,
|
|
1093
|
+
schedulerStore: this.schedulerStore,
|
|
1094
|
+
entry,
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
523
1098
|
return results;
|
|
524
1099
|
}
|
|
525
1100
|
}
|