chainlesschain 0.163.4 → 0.163.6
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-DB5X7qqV.js → AIOps-DDIUofA8.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DxHRtKLk.js → ActionButton-Bm-5xYRn.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DIH5QrxT.js → Analytics-D5UH4ljI.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-uu0oxVz7.js → AppLayout-D_ifjOOP.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BNSMiW9Y.js → Artifacts-BY1mXdC7.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DcAzB4_x.js → Audit-Bujr4Ml3.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-ClSsvjIc.js → BackgroundAgents-sioKSTcC.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BMciY0js.js → Backup-DDOAK8RK.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Hn3b57Sh.js → BaseInput-CtCsVVke.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CoSlC53V.js → Chat-DpNj0vnd.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CFevJ3So.js → ChatBubbleRenderer-CZta3Ftr.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-B4L4Eu4w.js → Checkbox-DoHVNSXq.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-C77FgRxF.js → Codegen-DlVl7Qmv.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BrLtJLH3.js → Col-GIqoPYty.js} +1 -1
- package/src/assets/web-panel/assets/{Community-Be7pXXhA.js → Community-CIAPEVji.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DuGPecjo.js → Compact-BN7JiI_h.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CzS66mjP.js → Compliance-CqUWCM9B.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CEhjCoyK.js → Cowork-A4iPp7A4.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-D7ir8Vja.js → Cron-B2zeXXWb.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BcXmuLxf.js → Crosschain-BR2xmWr8.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Cto1dtBy.js → DID-BzFDjrIv.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-B7KjgwOX.js → Dashboard-DKtDo3yA.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-COMqxijg.js → Dropdown-RNx43_YG.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BZrya-nf.js → EmailListRenderer-SwMt4Rcy.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-AmVeMq0J.js → FamilyGuardDashboard-CsYebZ4X.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-kZU8c-27.js → Federation-BmRKvBTy.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-sOV22v2-.js → FormItemContext-CVxGTA-0.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BUuLtGCz.js → GenericCardRenderer-x2iXpyB7.js} +1 -1
- package/src/assets/web-panel/assets/{Git-K-ZxsLsH.js → Git-DfqbkiOP.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-D3OEnawI.js → Governance-RuHyyj5O.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Bll0gRmP.js → Inference-Bn7uZ7rI.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DBXwjr53.js → KnowledgeGraph-DlP7XefZ.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CEzPwEfP.js → Logs-BYNdOiCd.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-aMtgN-R6.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BYyRyOPz.js → Marketplace-JQYmDqM8.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DVu8w_rM.js → McpTools-Cyx4yZ-9.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-tenuZ_hO.js → Memory-VDuGZTN2.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Bxgy4ozS.js → MobileBridge-_yKo97gi.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Dgx1D6sc.js → MobileProjects-CXRXW3q7.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CRfJ8r8j.js → Mtc-acTGtfLH.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BBJq4Aiz.js → MtcAudit-ClpjXfkY.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-B3ii9lai.js → Multisig-_6Tn3o96.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-FIi-Cubv.js → NLProgramming-CK7pbZzP.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DIq8MPf1.js → Notes-Czy6V_P8.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BFOD9eGd.js → NotificationSettings-Cqx-UVv3.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer--41u7vre.js → OrderTableRenderer-C69WDxGy.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-B_0AWztW.js → Organization-DFnA9vLQ.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-B6I7TuXv.js → Overflow-ReZkk2yT.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Cdf6qens.js → P2P-DzjSI0aI.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-B_0FcQJY.js → PdhVaultBrowser-DKTf5xIP.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-6nFBjsQp.js → Permissions-DhQ5klhR.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-nQYV7xpn.js → PersonalDataHub-CUL1iHrW.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-CBVod5jm.js → Pipeline-DU88iu4N.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-D52jW1Nt.js → Privacy-Bw-sYfyK.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Bdx3BYhf.js → ProjectInit-Bq3Q5y0z.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-b7xr4DUa.js → ProjectSettings-C8spzpho.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DhfifCKx.js → Projects-Dd-xuPIc.js} +1 -1
- package/src/assets/web-panel/assets/Providers-Bf4eTP9W.js +1 -0
- package/src/assets/web-panel/assets/{QrScannerModal-FQKSmc3e.js → QrScannerModal-Dme9BDU1.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CSTrhzcF.js → QuickAsk-CIXfv1rE.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CxMABOUh.js → Recommend-CIxvdeZ-.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-Cv2T8yRa.js → RemoteSession-C6k7MExI.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CE1k4Vvu.js → Reputation-CISDz2bl.js} +1 -1
- package/src/assets/web-panel/assets/{Row-BNgNcAV_.js → Row-CdnDxXyf.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-5ZiBZ3ph.js → RssFeed-C-QpRHsl.js} +3 -3
- package/src/assets/web-panel/assets/{Search-mDOIF1UV.js → Search-DBjaGzSn.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CtuCTC-e.js → Security-CQKukOeE.js} +3 -3
- package/src/assets/web-panel/assets/{Services-DxFO2Zg7.js → Services-DZz86jWb.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CKmKkH9y.js → Skeleton-jb8RMK0d.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-Bmzl9qkk.js → Skills-CdY76fZG.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-9RqaWHOC.js → Sla-C_CSiZX9.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-C7tT9iad.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-CvNuCeBT.js → SyncSettings-qvimGO0O.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-B_sLdcnw.js → Tasks-Dng5LPJC.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-D9_YijdK.js → Templates-Bwqq5Wll.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-Cuc7BUSP.js → Tenant-DNWuwP7Z.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-6VYRnJgd.js → Terminal-B-6Othpp.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-GtK8xd8Y.js → TimelineRenderer-DKpKZfzx.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BZ94Lt5W.js → Tokens-DzfGMd2J.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DEcZgDaX.js → Trigger-BXwUi74J.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CC3z9THe.js → Trust-BNzUq4ui.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-Cvtw8cV3.js → UkeySign-BYugxww4.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CL4e6Bn3.js → VideoEditing-DdauhtSJ.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-ycEemP27.js → Wallet-CSC9bCe_.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DgZ4X_aw.js → WebAuthn-Q8AN3bTO.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-D0qMhSv7.js → WorkflowEditor-d-LBGCuA.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DsAg01Zn.js → chat-BLVDCgsZ.js} +1 -1
- package/src/assets/web-panel/assets/{colors-B0oqAhrQ.js → colors-BZlPSEHl.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CmX0Rabz.js → compact-item-IWseP6R6.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-Be4cn1Ub.js → createContext-DnVRV3Ao.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Jzu62CLC.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-D5v12kVq.js → hasIn-DLQZA_Wn.js} +1 -1
- package/src/assets/web-panel/assets/{index-BA4oulaP.js → index-1jeAudHd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DM724NgU.js → index-BEu2lMBy.js} +1 -1
- package/src/assets/web-panel/assets/{index-5LGY0JbG.js → index-BOZJBTWZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DikJ0Qqe.js → index-BSjilZ9L.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqltWojQ.js → index-BZs5bBnk.js} +1 -1
- package/src/assets/web-panel/assets/{index-7VKDn-t1.js → index-B_N4-Gq9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXGSbelg.js → index-Bbj2NS_I.js} +3 -3
- package/src/assets/web-panel/assets/{index-CGiIyTZw.js → index-BhA3QEni.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKmsS2jj.js → index-Bxp7uDD8.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3mUHuVc.js → index-C1ifZtXo.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBvyHguy.js → index-C2FqfMvU.js} +1 -1
- package/src/assets/web-panel/assets/{index-q_fQ94SE.js → index-C5dgnilC.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5YVopyr.js → index-CINJQEj6.js} +1 -1
- package/src/assets/web-panel/assets/{index-0eWLkQKT.js → index-COev0W2g.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoAIAZ5C.js → index-CT9vVN-X.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqoCZ5vR.js → index-CZDekj7T.js} +1 -1
- package/src/assets/web-panel/assets/{index-301ogenv.js → index-CarMTIyQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dn2dSdXx.js → index-CbQNL0ue.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKUBj36_.js → index-CpBaJ3TD.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBREnn1V.js → index-DB0MJgF_.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRIqLy1m.js → index-DOoQpuQY.js} +1 -1
- package/src/assets/web-panel/assets/{index-jK3ax6Z6.js → index-DTbxp4UH.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1YGjOWC.js → index-DUq0c7gq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CyyGTMKd.js → index-DZpYeqrh.js} +1 -1
- package/src/assets/web-panel/assets/{index-_JjRLSD0.js → index-D_LQodeJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DyUaFWEk.js → index-DalA0TKC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaE0Ui07.js → index-DjWnONdy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CyWBir7N.js → index-Dkwsi1Kn.js} +1 -1
- package/src/assets/web-panel/assets/{index-qMuZ-NRR.js → index-DocEhinm.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ckrk3pIV.js → index-DpVrK6fr.js} +1 -1
- package/src/assets/web-panel/assets/{index-DQRsQen0.js → index-Dy0R5ZED.js} +1 -1
- package/src/assets/web-panel/assets/{index-frSojpHj.js → index-M7wxfAbm.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0BSkxYQ.js → index-N_GoWZX4.js} +1 -1
- package/src/assets/web-panel/assets/{index-8WvYGnON.js → index-YSw_gxlZ.js} +1 -1
- package/src/assets/web-panel/assets/index-Zb2MeDSy.js +1 -0
- package/src/assets/web-panel/assets/index-fikqml4k.js +1 -0
- package/src/assets/web-panel/assets/{index-Hfe_dEGx.js → index-gt8bNefW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CZ2wUEgr.js → index-hwVDeoC0.js} +1 -1
- package/src/assets/web-panel/assets/{index-CZpfzcuy.js → index-jWb70ZgZ.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-D4VdhJ9_.js → initDefaultProps-Co67Kqmb.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CZJA0MQW.js → motion-BMOxQSF_.js} +1 -1
- package/src/assets/web-panel/assets/{move-BqEs7Sb1.js → move-C5fWDw-N.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BpHEtsu1.js → mtc-parser-BJevpL0e.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BW94tY9W.js → omit-BCzNU8s6.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BnBjPWP9.js → pickAttrs-CybWvDPk.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-uUkjQ_hu.js → placementArrow-BMWS9HnW.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-B-klUqXK.js → responsiveObserve-BoL2UnGY.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CvY1cNAs.js → slide-DWNfXraU.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Ix5sEDT2.js → statusUtils-BquTM3tX.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-C8qcXOne.js → styleChecker-BEkCleim.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-KNF6Kj8f.js → useFlexGapSupport-Oecnq7EA.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DGJr345n.js → useFs-CkDffJ4F.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BboN99rI.js → usePersonalDataHub-qbvKXesB.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-Bgt8Vgte.js → vnode-C6v8n6mA.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BhJtErgt.js → zoom-4CTeFILV.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +3 -3
- package/src/command-manifest.json +1 -1
- package/src/commands/agenda.js +125 -7
- package/src/commands/automation.js +462 -3
- package/src/commands/background-session.js +102 -1
- package/src/commands/cowork.js +30 -13
- package/src/commands/loop.js +167 -36
- package/src/commands/routine.js +50 -13
- package/src/commands/scheduler-daemon.js +450 -0
- package/src/data/changelog.json +27 -1
- package/src/lib/agent-schedule-store.js +509 -36
- package/src/lib/automation-center-routines.js +363 -0
- package/src/lib/automation-center.js +435 -0
- package/src/lib/automation-engine.js +74 -19
- package/src/lib/automation-execution-authority.js +736 -0
- package/src/lib/background-agent-supervisor.js +78 -0
- package/src/lib/background-needs-input-incident.js +221 -0
- package/src/lib/cowork-cron.js +555 -39
- package/src/lib/cowork-observe.js +5 -1
- package/src/lib/loop.js +37 -2
- package/src/lib/micro-compact.js +11 -5
- package/src/lib/permission-engine.js +14 -7
- package/src/lib/process-execution-broker/index.js +14 -3
- package/src/lib/public-ide-capability-manifest.js +2 -0
- package/src/lib/routine-store.js +242 -68
- package/src/lib/scheduler-kernel/agenda-adapter.js +812 -0
- package/src/lib/scheduler-kernel/authority-resolver.js +163 -0
- package/src/lib/scheduler-kernel/automation-adapter.js +592 -0
- package/src/lib/scheduler-kernel/automation-event-adapter.js +721 -0
- package/src/lib/scheduler-kernel/contract.js +11 -3
- package/src/lib/scheduler-kernel/cowork-cron-adapter.js +820 -0
- package/src/lib/scheduler-kernel/loop-adapter.js +378 -0
- package/src/lib/scheduler-kernel/routine-adapter.js +812 -0
- package/src/lib/scheduler-kernel/runtime.js +527 -0
- package/src/lib/scheduler-kernel/service.js +328 -0
- package/src/lib/scheduler-kernel/store.js +940 -98
- package/src/lib/session-message-provenance.js +4 -1
- package/src/repl/agent-repl.js +24 -5
- package/src/runtime/agent-core.js +38 -17
- package/src/runtime/coding-agent-contract-shared.cjs +5 -0
- package/src/workers/background-agent-worker.js +91 -28
- package/src/assets/web-panel/assets/MarkdownRenderer-CLmCbHx6.js +0 -1
- package/src/assets/web-panel/assets/Providers-Dy_PgDMr.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-CGdzFIYw.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CP2qzG6D.js +0 -1
- package/src/assets/web-panel/assets/index-CKB0vbJU.js +0 -1
- package/src/assets/web-panel/assets/index-DgoXmwqB.js +0 -1
|
@@ -0,0 +1,812 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { effectiveFireAt } from "../schedule-planner.js";
|
|
3
|
+
import {
|
|
4
|
+
nextCronTime,
|
|
5
|
+
normalizeTimeZone,
|
|
6
|
+
parseCron,
|
|
7
|
+
} from "../agent-schedule-store.js";
|
|
8
|
+
import {
|
|
9
|
+
SchedulerKernelError,
|
|
10
|
+
canonicalJson,
|
|
11
|
+
normalizeEpochMs,
|
|
12
|
+
normalizeIdentifier,
|
|
13
|
+
normalizeJson,
|
|
14
|
+
} from "./contract.js";
|
|
15
|
+
import { SchedulerRuntime } from "./runtime.js";
|
|
16
|
+
import {
|
|
17
|
+
bindSchedulerAuthorityPolicy,
|
|
18
|
+
createSchedulerAuthorityResolver,
|
|
19
|
+
} from "./authority-resolver.js";
|
|
20
|
+
|
|
21
|
+
export const AGENDA_SCHEDULER_KIND = "agenda";
|
|
22
|
+
export const AGENDA_SCHEDULER_CAPABILITY = "agent.execute";
|
|
23
|
+
export const AGENDA_SCHEDULER_MONITOR_CAPABILITY = "monitor.observe";
|
|
24
|
+
export const AGENDA_SCHEDULER_ENTRY_KINDS = Object.freeze([
|
|
25
|
+
"wakeup",
|
|
26
|
+
"cron",
|
|
27
|
+
"monitor",
|
|
28
|
+
]);
|
|
29
|
+
export const AGENDA_SCHEDULER_RETRY_DELAY_MS = 60_000;
|
|
30
|
+
|
|
31
|
+
function agendaError(
|
|
32
|
+
code,
|
|
33
|
+
message,
|
|
34
|
+
details = undefined,
|
|
35
|
+
cause = undefined,
|
|
36
|
+
{ retryable = false, retryAt } = {},
|
|
37
|
+
) {
|
|
38
|
+
const error = new SchedulerKernelError(
|
|
39
|
+
code,
|
|
40
|
+
message,
|
|
41
|
+
details,
|
|
42
|
+
cause ? { cause } : undefined,
|
|
43
|
+
);
|
|
44
|
+
error.retryable = retryable;
|
|
45
|
+
if (retryAt !== undefined) error.retryAt = retryAt;
|
|
46
|
+
return error;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function safeFailure(error) {
|
|
50
|
+
return {
|
|
51
|
+
code:
|
|
52
|
+
typeof error?.code === "string" && error.code
|
|
53
|
+
? error.code.slice(0, 128)
|
|
54
|
+
: "AGENDA_AGENT_EXECUTION_FAILED",
|
|
55
|
+
message:
|
|
56
|
+
typeof error?.message === "string" && error.message
|
|
57
|
+
? error.message.slice(0, 2_000)
|
|
58
|
+
: "Agenda agent execution failed",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function capabilityForEntry(entry) {
|
|
63
|
+
return entry?.kind === "monitor"
|
|
64
|
+
? AGENDA_SCHEDULER_MONITOR_CAPABILITY
|
|
65
|
+
: AGENDA_SCHEDULER_CAPABILITY;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function normalizeMonitorObservation(entry, observation) {
|
|
69
|
+
if (
|
|
70
|
+
!observation ||
|
|
71
|
+
typeof observation !== "object" ||
|
|
72
|
+
Array.isArray(observation) ||
|
|
73
|
+
typeof observation.matched !== "boolean"
|
|
74
|
+
) {
|
|
75
|
+
throw agendaError(
|
|
76
|
+
"AGENDA_SCHEDULER_MONITOR_RESULT_INVALID",
|
|
77
|
+
`Agenda monitor returned an invalid observation: ${entry.id}`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
const mtimeMs =
|
|
81
|
+
observation.mtimeMs === null || observation.mtimeMs === undefined
|
|
82
|
+
? null
|
|
83
|
+
: Number(observation.mtimeMs);
|
|
84
|
+
if (mtimeMs !== null && (!Number.isFinite(mtimeMs) || mtimeMs < 0)) {
|
|
85
|
+
throw agendaError(
|
|
86
|
+
"AGENDA_SCHEDULER_MONITOR_RESULT_INVALID",
|
|
87
|
+
`Agenda monitor returned an invalid mtime: ${entry.id}`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const eventId = observation.matched
|
|
91
|
+
? normalizeIdentifier(observation.eventId, "agenda.monitor.eventId")
|
|
92
|
+
: null;
|
|
93
|
+
const authority = observation.matched
|
|
94
|
+
? normalizeIdentifier(observation.authority, "agenda.monitor.authority")
|
|
95
|
+
: null;
|
|
96
|
+
const duplicate = observation.matched && entry.lastEventId === eventId;
|
|
97
|
+
const notification =
|
|
98
|
+
observation.notification &&
|
|
99
|
+
typeof observation.notification === "object" &&
|
|
100
|
+
!Array.isArray(observation.notification)
|
|
101
|
+
? {
|
|
102
|
+
title: String(observation.notification.title || ""),
|
|
103
|
+
body: String(observation.notification.body || ""),
|
|
104
|
+
level: String(observation.notification.level || "success"),
|
|
105
|
+
}
|
|
106
|
+
: null;
|
|
107
|
+
return {
|
|
108
|
+
matched: observation.matched,
|
|
109
|
+
mtimeMs,
|
|
110
|
+
eventId,
|
|
111
|
+
authority,
|
|
112
|
+
duplicate,
|
|
113
|
+
truncated: observation.truncated === true,
|
|
114
|
+
notification,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function validateMonitorSnapshot(snapshot) {
|
|
119
|
+
if (
|
|
120
|
+
!Number.isSafeInteger(snapshot.intervalMs) ||
|
|
121
|
+
snapshot.intervalMs < 1_000
|
|
122
|
+
) {
|
|
123
|
+
throw agendaError(
|
|
124
|
+
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
125
|
+
`Agenda monitor interval is invalid: ${snapshot.id}`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
if (!Number.isSafeInteger(snapshot.checks) || snapshot.checks < 0) {
|
|
129
|
+
throw agendaError(
|
|
130
|
+
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
131
|
+
`Agenda monitor check count is invalid: ${snapshot.id}`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
if (!["command", "file", "http"].includes(snapshot.source)) {
|
|
135
|
+
throw agendaError(
|
|
136
|
+
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
137
|
+
`Agenda monitor source is invalid: ${snapshot.id}`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
const sourceValue =
|
|
141
|
+
snapshot.source === "command"
|
|
142
|
+
? snapshot.command
|
|
143
|
+
: snapshot.source === "file"
|
|
144
|
+
? snapshot.watchFile
|
|
145
|
+
: snapshot.watchUrl;
|
|
146
|
+
if (typeof sourceValue !== "string" || sourceValue.length === 0) {
|
|
147
|
+
throw agendaError(
|
|
148
|
+
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
149
|
+
`Agenda monitor source target is missing: ${snapshot.id}`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (
|
|
153
|
+
snapshot.watchChange === true &&
|
|
154
|
+
(snapshot.source !== "file" || snapshot.stopWhen != null)
|
|
155
|
+
) {
|
|
156
|
+
throw agendaError(
|
|
157
|
+
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
158
|
+
`Agenda monitor change condition is invalid: ${snapshot.id}`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
if (snapshot.stopWhen != null) {
|
|
162
|
+
if (typeof snapshot.stopWhen !== "string") {
|
|
163
|
+
throw agendaError(
|
|
164
|
+
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
165
|
+
`Agenda monitor stop condition is invalid: ${snapshot.id}`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
new RegExp(snapshot.stopWhen);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
throw agendaError(
|
|
172
|
+
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
173
|
+
`Agenda monitor stop condition is invalid: ${snapshot.id}`,
|
|
174
|
+
undefined,
|
|
175
|
+
error,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function validateEntrySnapshot(entry) {
|
|
182
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
183
|
+
throw agendaError(
|
|
184
|
+
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
185
|
+
"Agenda scheduler adapter requires an entry object",
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
const plain = { ...entry };
|
|
189
|
+
delete plain.executionLease;
|
|
190
|
+
delete plain.schedulerExecution;
|
|
191
|
+
const snapshot = normalizeJson(plain, "agendaEntrySnapshot");
|
|
192
|
+
snapshot.id = normalizeIdentifier(snapshot.id, "agenda.entry.id");
|
|
193
|
+
if (!AGENDA_SCHEDULER_ENTRY_KINDS.includes(snapshot.kind)) {
|
|
194
|
+
throw agendaError(
|
|
195
|
+
"AGENDA_SCHEDULER_KIND_UNSUPPORTED",
|
|
196
|
+
`Agenda scheduler does not support entry kind: ${snapshot.kind}`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (
|
|
200
|
+
snapshot.kind !== "monitor" &&
|
|
201
|
+
(typeof snapshot.prompt !== "string" || snapshot.prompt.length === 0)
|
|
202
|
+
) {
|
|
203
|
+
throw agendaError(
|
|
204
|
+
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
205
|
+
`Agenda prompt is missing: ${snapshot.id}`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
if (snapshot.kind === "cron") {
|
|
209
|
+
try {
|
|
210
|
+
parseCron(snapshot.cron);
|
|
211
|
+
if (snapshot.timeZone != null) {
|
|
212
|
+
const timeZone = normalizeTimeZone(snapshot.timeZone);
|
|
213
|
+
if (!timeZone) throw new Error("cron timeZone must not be empty");
|
|
214
|
+
const nextAt = Number(snapshot.nextAt);
|
|
215
|
+
if (
|
|
216
|
+
nextCronTime(snapshot.cron, nextAt - 60_000, { timeZone }) !== nextAt
|
|
217
|
+
) {
|
|
218
|
+
throw new Error("cron nextAt does not match its time zone");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
} catch (error) {
|
|
222
|
+
throw agendaError(
|
|
223
|
+
"AGENDA_SCHEDULER_CRON_INVALID",
|
|
224
|
+
`Agenda cron definition is invalid: ${snapshot.id}`,
|
|
225
|
+
undefined,
|
|
226
|
+
error,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const schedulable =
|
|
231
|
+
(snapshot.kind === "wakeup" && snapshot.status === "pending") ||
|
|
232
|
+
(["cron", "monitor"].includes(snapshot.kind) &&
|
|
233
|
+
snapshot.status === "active");
|
|
234
|
+
if (!schedulable) {
|
|
235
|
+
throw agendaError(
|
|
236
|
+
"AGENDA_SCHEDULER_ENTRY_TERMINAL",
|
|
237
|
+
`Agenda entry is not schedulable: ${snapshot.id}`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (snapshot.kind === "monitor") validateMonitorSnapshot(snapshot);
|
|
241
|
+
normalizeEpochMs(Number(snapshot.createdAt), "agenda.entry.createdAt");
|
|
242
|
+
const fireAt = effectiveFireAt(snapshot);
|
|
243
|
+
if (fireAt == null) {
|
|
244
|
+
throw agendaError(
|
|
245
|
+
"AGENDA_SCHEDULER_FIRE_TIME_INVALID",
|
|
246
|
+
`Agenda entry has no fire time: ${snapshot.id}`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
normalizeEpochMs(Number(fireAt), "agenda.entry.fireAt");
|
|
250
|
+
return snapshot;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function agendaEntrySnapshot(entry) {
|
|
254
|
+
return validateEntrySnapshot(entry);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function agendaEntrySnapshotDigest(entry) {
|
|
258
|
+
return createHash("sha256")
|
|
259
|
+
.update("chainlesschain.scheduler.agenda-entry.v1\0", "utf8")
|
|
260
|
+
.update(
|
|
261
|
+
canonicalJson(agendaEntrySnapshot(entry), "agendaEntrySnapshot"),
|
|
262
|
+
"utf8",
|
|
263
|
+
)
|
|
264
|
+
.digest("hex");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function agendaSchedulerJobId(entryId) {
|
|
268
|
+
return `agenda:${normalizeIdentifier(entryId, "agendaEntryId")}`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function buildAgendaSchedulerJob(entry) {
|
|
272
|
+
const snapshot = agendaEntrySnapshot(entry);
|
|
273
|
+
return {
|
|
274
|
+
id: agendaSchedulerJobId(snapshot.id),
|
|
275
|
+
kind: AGENDA_SCHEDULER_KIND,
|
|
276
|
+
trigger: {
|
|
277
|
+
source: "agent-schedule-store",
|
|
278
|
+
scheduleKind: snapshot.kind,
|
|
279
|
+
expression:
|
|
280
|
+
snapshot.kind === "cron"
|
|
281
|
+
? snapshot.cron
|
|
282
|
+
: snapshot.kind === "monitor"
|
|
283
|
+
? `every:${snapshot.intervalMs}`
|
|
284
|
+
: String(snapshot.dueAt),
|
|
285
|
+
...(snapshot.kind === "cron" && snapshot.timeZone
|
|
286
|
+
? { timeZone: snapshot.timeZone }
|
|
287
|
+
: {}),
|
|
288
|
+
},
|
|
289
|
+
payload: {
|
|
290
|
+
entry: snapshot,
|
|
291
|
+
snapshotDigest: agendaEntrySnapshotDigest(snapshot),
|
|
292
|
+
},
|
|
293
|
+
authority: {
|
|
294
|
+
schemaVersion: 1,
|
|
295
|
+
principal: { type: "agenda", id: snapshot.id },
|
|
296
|
+
tenantId: null,
|
|
297
|
+
workspaceId: null,
|
|
298
|
+
requestedCapabilities: [capabilityForEntry(snapshot)],
|
|
299
|
+
authorizationRefs: {
|
|
300
|
+
decisionId: null,
|
|
301
|
+
policyRevision: null,
|
|
302
|
+
grantIds: [],
|
|
303
|
+
approvalIds: [],
|
|
304
|
+
delegationIds: [],
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
enabled: true,
|
|
308
|
+
maxAttempts: 3,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function comparableJob(job) {
|
|
313
|
+
return {
|
|
314
|
+
kind: job.kind,
|
|
315
|
+
trigger: job.trigger,
|
|
316
|
+
payload: job.payload,
|
|
317
|
+
authority: job.authority,
|
|
318
|
+
enabled: job.enabled,
|
|
319
|
+
maxAttempts: job.maxAttempts,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function sameJob(current, desired) {
|
|
324
|
+
return (
|
|
325
|
+
current !== null &&
|
|
326
|
+
canonicalJson(comparableJob(current), "currentAgendaJob") ===
|
|
327
|
+
canonicalJson(comparableJob(desired), "desiredAgendaJob")
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function syncAgendaSchedulerJob(schedulerStore, entry) {
|
|
332
|
+
const desired = buildAgendaSchedulerJob(entry);
|
|
333
|
+
desired.authority = bindSchedulerAuthorityPolicy(
|
|
334
|
+
schedulerStore,
|
|
335
|
+
desired.authority,
|
|
336
|
+
);
|
|
337
|
+
let current = schedulerStore.getJob(desired.id);
|
|
338
|
+
if (!current) {
|
|
339
|
+
try {
|
|
340
|
+
return schedulerStore.createJob(desired);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
if (error?.code !== "SCHEDULER_CONFLICT") throw error;
|
|
343
|
+
current = schedulerStore.getJob(desired.id);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (sameJob(current, desired)) return current;
|
|
347
|
+
try {
|
|
348
|
+
return schedulerStore.updateJob(current.id, current.revision, {
|
|
349
|
+
kind: desired.kind,
|
|
350
|
+
trigger: desired.trigger,
|
|
351
|
+
payload: desired.payload,
|
|
352
|
+
authority: desired.authority,
|
|
353
|
+
enabled: desired.enabled,
|
|
354
|
+
maxAttempts: desired.maxAttempts,
|
|
355
|
+
});
|
|
356
|
+
} catch (error) {
|
|
357
|
+
if (error?.code !== "SCHEDULER_REVISION_CONFLICT") throw error;
|
|
358
|
+
const latest = schedulerStore.getJob(desired.id);
|
|
359
|
+
if (sameJob(latest, desired)) return latest;
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export function enqueueAgendaEntry(schedulerStore, entry) {
|
|
365
|
+
const snapshot = agendaEntrySnapshot(entry);
|
|
366
|
+
const job = syncAgendaSchedulerJob(schedulerStore, snapshot);
|
|
367
|
+
const scheduledFor = normalizeEpochMs(
|
|
368
|
+
Number(effectiveFireAt(snapshot)),
|
|
369
|
+
"agenda.entry.fireAt",
|
|
370
|
+
);
|
|
371
|
+
return schedulerStore.enqueueOccurrence({
|
|
372
|
+
jobId: job.id,
|
|
373
|
+
scheduledFor,
|
|
374
|
+
triggerKey: `${snapshot.kind}:${scheduledFor}`,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export function authorizeAgendaOccurrence({ job, occurrence }) {
|
|
379
|
+
try {
|
|
380
|
+
const payload = occurrence?.payload;
|
|
381
|
+
const entry = payload?.entry;
|
|
382
|
+
const authority = occurrence?.authority;
|
|
383
|
+
const expectedDigest = agendaEntrySnapshotDigest(entry);
|
|
384
|
+
const allowed =
|
|
385
|
+
job?.kind === AGENDA_SCHEDULER_KIND &&
|
|
386
|
+
authority?.principal?.type === "agenda" &&
|
|
387
|
+
authority?.principal?.id === entry.id &&
|
|
388
|
+
Array.isArray(authority?.requestedCapabilities) &&
|
|
389
|
+
authority.requestedCapabilities.length === 1 &&
|
|
390
|
+
authority.requestedCapabilities[0] === capabilityForEntry(entry) &&
|
|
391
|
+
payload.snapshotDigest === expectedDigest;
|
|
392
|
+
return {
|
|
393
|
+
allowed,
|
|
394
|
+
reason: allowed ? "agenda_snapshot_bound" : "agenda_authority_mismatch",
|
|
395
|
+
};
|
|
396
|
+
} catch {
|
|
397
|
+
return { allowed: false, reason: "agenda_authority_malformed" };
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function recoveredResult(entry, evidence) {
|
|
402
|
+
const result =
|
|
403
|
+
evidence.result && typeof evidence.result === "object"
|
|
404
|
+
? evidence.result
|
|
405
|
+
: {
|
|
406
|
+
id: entry.id,
|
|
407
|
+
kind: entry.kind,
|
|
408
|
+
action: entry.kind === "monitor" ? "checked" : "fired",
|
|
409
|
+
};
|
|
410
|
+
return { ...result, recovered: true };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function matchingExecutionEvidence(
|
|
414
|
+
entry,
|
|
415
|
+
{ occurrenceId, snapshotDigest, attempt, status },
|
|
416
|
+
) {
|
|
417
|
+
const evidence = entry?.schedulerExecution;
|
|
418
|
+
return (
|
|
419
|
+
evidence?.occurrenceId === occurrenceId &&
|
|
420
|
+
evidence.snapshotDigest === snapshotDigest &&
|
|
421
|
+
evidence.attempt === attempt &&
|
|
422
|
+
evidence.status === status
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
export function createAgendaSchedulerAdapter({
|
|
427
|
+
agendaStore,
|
|
428
|
+
runAgent,
|
|
429
|
+
runMonitor,
|
|
430
|
+
notifyMonitor,
|
|
431
|
+
now = Date.now,
|
|
432
|
+
} = {}) {
|
|
433
|
+
if (
|
|
434
|
+
!agendaStore ||
|
|
435
|
+
typeof agendaStore.get !== "function" ||
|
|
436
|
+
typeof agendaStore.bindSchedulerExecution !== "function" ||
|
|
437
|
+
typeof agendaStore.completeSchedulerExecution !== "function"
|
|
438
|
+
) {
|
|
439
|
+
throw agendaError(
|
|
440
|
+
"AGENDA_SCHEDULER_STORE_REQUIRED",
|
|
441
|
+
"Agenda scheduler adapter requires a compatible AgentScheduleStore",
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
if (typeof runAgent !== "function") {
|
|
445
|
+
throw agendaError(
|
|
446
|
+
"AGENDA_SCHEDULER_RUNNER_REQUIRED",
|
|
447
|
+
"Agenda scheduler adapter requires a runAgent function",
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
if (typeof now !== "function") {
|
|
451
|
+
throw agendaError(
|
|
452
|
+
"AGENDA_SCHEDULER_CLOCK_REQUIRED",
|
|
453
|
+
"Agenda scheduler adapter requires a clock function",
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
kind: AGENDA_SCHEDULER_KIND,
|
|
458
|
+
async execute({ occurrence }) {
|
|
459
|
+
const payload = occurrence.payload;
|
|
460
|
+
const expected = agendaEntrySnapshot(payload?.entry);
|
|
461
|
+
const expectedDigest = agendaEntrySnapshotDigest(expected);
|
|
462
|
+
if (payload.snapshotDigest !== expectedDigest) {
|
|
463
|
+
throw agendaError(
|
|
464
|
+
"AGENDA_SCHEDULER_SNAPSHOT_INVALID",
|
|
465
|
+
`Agenda occurrence snapshot is invalid: ${occurrence.id}`,
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
let current = agendaStore.get(expected.id);
|
|
469
|
+
if (!current) {
|
|
470
|
+
throw agendaError(
|
|
471
|
+
"AGENDA_SCHEDULER_ENTRY_NOT_FOUND",
|
|
472
|
+
`Agenda entry disappeared before execution: ${expected.id}`,
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
const prior = current.schedulerExecution;
|
|
476
|
+
if (prior?.occurrenceId === occurrence.id) {
|
|
477
|
+
if (prior.snapshotDigest !== expectedDigest) {
|
|
478
|
+
throw agendaError(
|
|
479
|
+
"AGENDA_SCHEDULER_BINDING_MISMATCH",
|
|
480
|
+
`Agenda execution evidence has a different snapshot: ${expected.id}`,
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
if (prior.status === "succeeded") {
|
|
484
|
+
return recoveredResult(expected, prior);
|
|
485
|
+
}
|
|
486
|
+
if (prior.status === "running") {
|
|
487
|
+
throw agendaError(
|
|
488
|
+
"AGENDA_SCHEDULER_OUTCOME_UNKNOWN",
|
|
489
|
+
`Agenda execution outcome is unknown; refusing duplicate execution: ${expected.id}`,
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
} else if (prior?.status === "running") {
|
|
493
|
+
throw agendaError(
|
|
494
|
+
"AGENDA_SCHEDULER_EXECUTION_CONFLICT",
|
|
495
|
+
`Agenda entry has another unresolved scheduler execution: ${expected.id}`,
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
if (agendaEntrySnapshotDigest(current) !== expectedDigest) {
|
|
499
|
+
throw agendaError(
|
|
500
|
+
"AGENDA_SCHEDULER_STALE_SNAPSHOT",
|
|
501
|
+
`Agenda entry changed after occurrence enqueue: ${expected.id}`,
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
current = agendaStore.bindSchedulerExecution(expected.id, {
|
|
506
|
+
occurrenceId: occurrence.id,
|
|
507
|
+
snapshotDigest: expectedDigest,
|
|
508
|
+
attempt: occurrence.attempt,
|
|
509
|
+
atMs: now(),
|
|
510
|
+
});
|
|
511
|
+
if (agendaEntrySnapshotDigest(current) !== expectedDigest) {
|
|
512
|
+
const stale = agendaError(
|
|
513
|
+
"AGENDA_SCHEDULER_STALE_SNAPSHOT",
|
|
514
|
+
`Agenda entry changed while binding occurrence: ${expected.id}`,
|
|
515
|
+
);
|
|
516
|
+
agendaStore.completeSchedulerExecution(expected.id, {
|
|
517
|
+
occurrenceId: occurrence.id,
|
|
518
|
+
snapshotDigest: expectedDigest,
|
|
519
|
+
attempt: occurrence.attempt,
|
|
520
|
+
outcome: "failed",
|
|
521
|
+
error: safeFailure(stale),
|
|
522
|
+
atMs: now(),
|
|
523
|
+
});
|
|
524
|
+
throw stale;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
let observation = null;
|
|
528
|
+
let action = null;
|
|
529
|
+
try {
|
|
530
|
+
if (expected.kind === "monitor") {
|
|
531
|
+
if (typeof runMonitor !== "function") {
|
|
532
|
+
throw agendaError(
|
|
533
|
+
"AGENDA_SCHEDULER_MONITOR_RUNNER_REQUIRED",
|
|
534
|
+
"Agenda scheduler monitor execution requires a monitor runner",
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
observation = normalizeMonitorObservation(
|
|
538
|
+
expected,
|
|
539
|
+
await runMonitor({
|
|
540
|
+
entry: expected,
|
|
541
|
+
occurrenceId: occurrence.id,
|
|
542
|
+
attempt: occurrence.attempt,
|
|
543
|
+
}),
|
|
544
|
+
);
|
|
545
|
+
action = {
|
|
546
|
+
id: expected.id,
|
|
547
|
+
kind: expected.kind,
|
|
548
|
+
action: observation.matched
|
|
549
|
+
? observation.duplicate
|
|
550
|
+
? "duplicate"
|
|
551
|
+
: "matched"
|
|
552
|
+
: "checked",
|
|
553
|
+
...(observation.matched
|
|
554
|
+
? {
|
|
555
|
+
event_id: observation.eventId,
|
|
556
|
+
authority: observation.authority,
|
|
557
|
+
}
|
|
558
|
+
: {}),
|
|
559
|
+
...(observation.truncated ? { truncated: true } : {}),
|
|
560
|
+
};
|
|
561
|
+
} else {
|
|
562
|
+
await runAgent({
|
|
563
|
+
entry: expected,
|
|
564
|
+
prompt: expected.prompt,
|
|
565
|
+
runPolicy: expected.runPolicy,
|
|
566
|
+
occurrenceId: occurrence.id,
|
|
567
|
+
attempt: occurrence.attempt,
|
|
568
|
+
});
|
|
569
|
+
action = {
|
|
570
|
+
id: expected.id,
|
|
571
|
+
kind: expected.kind,
|
|
572
|
+
action: "fired",
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
} catch (error) {
|
|
576
|
+
let failurePersisted = false;
|
|
577
|
+
try {
|
|
578
|
+
agendaStore.completeSchedulerExecution(expected.id, {
|
|
579
|
+
occurrenceId: occurrence.id,
|
|
580
|
+
snapshotDigest: expectedDigest,
|
|
581
|
+
attempt: occurrence.attempt,
|
|
582
|
+
outcome: "failed",
|
|
583
|
+
error: safeFailure(error),
|
|
584
|
+
atMs: now(),
|
|
585
|
+
});
|
|
586
|
+
failurePersisted = true;
|
|
587
|
+
} catch (completionError) {
|
|
588
|
+
failurePersisted = matchingExecutionEvidence(
|
|
589
|
+
agendaStore.get(expected.id),
|
|
590
|
+
{
|
|
591
|
+
occurrenceId: occurrence.id,
|
|
592
|
+
snapshotDigest: expectedDigest,
|
|
593
|
+
attempt: occurrence.attempt,
|
|
594
|
+
status: "failed",
|
|
595
|
+
},
|
|
596
|
+
);
|
|
597
|
+
if (!failurePersisted) {
|
|
598
|
+
throw agendaError(
|
|
599
|
+
"AGENDA_SCHEDULER_FAILURE_PERSIST_FAILED",
|
|
600
|
+
`Agenda execution failed and its evidence could not be persisted: ${expected.id}`,
|
|
601
|
+
undefined,
|
|
602
|
+
completionError,
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
throw agendaError(
|
|
607
|
+
typeof error?.code === "string"
|
|
608
|
+
? error.code
|
|
609
|
+
: expected.kind === "monitor"
|
|
610
|
+
? "AGENDA_MONITOR_EXECUTION_FAILED"
|
|
611
|
+
: "AGENDA_AGENT_EXECUTION_FAILED",
|
|
612
|
+
typeof error?.message === "string"
|
|
613
|
+
? error.message
|
|
614
|
+
: `Agenda ${expected.kind === "monitor" ? "monitor" : "agent"} execution failed: ${expected.id}`,
|
|
615
|
+
undefined,
|
|
616
|
+
error,
|
|
617
|
+
{
|
|
618
|
+
retryable: error?.retryable !== false,
|
|
619
|
+
retryAt: normalizeEpochMs(
|
|
620
|
+
Number(now()) + AGENDA_SCHEDULER_RETRY_DELAY_MS,
|
|
621
|
+
"agenda.retryAt",
|
|
622
|
+
),
|
|
623
|
+
},
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
try {
|
|
628
|
+
const completed = agendaStore.completeSchedulerExecution(expected.id, {
|
|
629
|
+
occurrenceId: occurrence.id,
|
|
630
|
+
snapshotDigest: expectedDigest,
|
|
631
|
+
attempt: occurrence.attempt,
|
|
632
|
+
outcome: "succeeded",
|
|
633
|
+
result: action,
|
|
634
|
+
...(observation
|
|
635
|
+
? {
|
|
636
|
+
monitorCheck: {
|
|
637
|
+
matched: observation.matched,
|
|
638
|
+
mtimeMs: observation.mtimeMs,
|
|
639
|
+
eventId: observation.eventId,
|
|
640
|
+
authority: observation.authority,
|
|
641
|
+
},
|
|
642
|
+
}
|
|
643
|
+
: {}),
|
|
644
|
+
atMs: now(),
|
|
645
|
+
});
|
|
646
|
+
const persistedAction = completed.schedulerExecution?.result || action;
|
|
647
|
+
if (
|
|
648
|
+
observation?.matched &&
|
|
649
|
+
!observation.duplicate &&
|
|
650
|
+
observation.notification
|
|
651
|
+
) {
|
|
652
|
+
if (typeof notifyMonitor !== "function") {
|
|
653
|
+
return {
|
|
654
|
+
...persistedAction,
|
|
655
|
+
notifyError: "Agenda monitor notification handler is unavailable",
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
try {
|
|
659
|
+
await notifyMonitor(observation.notification);
|
|
660
|
+
} catch (notifyError) {
|
|
661
|
+
return {
|
|
662
|
+
...persistedAction,
|
|
663
|
+
notifyError:
|
|
664
|
+
typeof notifyError?.message === "string"
|
|
665
|
+
? notifyError.message
|
|
666
|
+
: String(notifyError),
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
return persistedAction;
|
|
671
|
+
} catch (completionError) {
|
|
672
|
+
const persisted = agendaStore.get(expected.id);
|
|
673
|
+
if (
|
|
674
|
+
matchingExecutionEvidence(persisted, {
|
|
675
|
+
occurrenceId: occurrence.id,
|
|
676
|
+
snapshotDigest: expectedDigest,
|
|
677
|
+
attempt: occurrence.attempt,
|
|
678
|
+
status: "succeeded",
|
|
679
|
+
})
|
|
680
|
+
) {
|
|
681
|
+
return recoveredResult(expected, persisted.schedulerExecution);
|
|
682
|
+
}
|
|
683
|
+
throw agendaError(
|
|
684
|
+
"AGENDA_SCHEDULER_OUTCOME_UNKNOWN",
|
|
685
|
+
`Agenda agent finished but its durable outcome is unknown; refusing a retry: ${expected.id}`,
|
|
686
|
+
undefined,
|
|
687
|
+
completionError,
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
},
|
|
691
|
+
classifyError(error) {
|
|
692
|
+
return {
|
|
693
|
+
retryable: error?.retryable === true,
|
|
694
|
+
...(error?.retryAt === undefined ? {} : { retryAt: error.retryAt }),
|
|
695
|
+
};
|
|
696
|
+
},
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
export class AgendaSchedulerBridge {
|
|
701
|
+
constructor({
|
|
702
|
+
agendaStore,
|
|
703
|
+
schedulerStore,
|
|
704
|
+
runAgent,
|
|
705
|
+
runMonitor,
|
|
706
|
+
notifyMonitor,
|
|
707
|
+
now = Date.now,
|
|
708
|
+
ownerId,
|
|
709
|
+
leaseMs,
|
|
710
|
+
renewIntervalMs,
|
|
711
|
+
authorityResolver,
|
|
712
|
+
} = {}) {
|
|
713
|
+
if (typeof now !== "function") {
|
|
714
|
+
throw agendaError(
|
|
715
|
+
"AGENDA_SCHEDULER_CLOCK_REQUIRED",
|
|
716
|
+
"Agenda scheduler bridge requires a clock function",
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
this.agendaStore = agendaStore;
|
|
720
|
+
this.schedulerStore = schedulerStore;
|
|
721
|
+
this.now = now;
|
|
722
|
+
this.runtime = new SchedulerRuntime({
|
|
723
|
+
store: schedulerStore,
|
|
724
|
+
adapters: [
|
|
725
|
+
createAgendaSchedulerAdapter({
|
|
726
|
+
agendaStore,
|
|
727
|
+
runAgent,
|
|
728
|
+
runMonitor,
|
|
729
|
+
notifyMonitor,
|
|
730
|
+
now,
|
|
731
|
+
}),
|
|
732
|
+
],
|
|
733
|
+
authorize:
|
|
734
|
+
authorityResolver ||
|
|
735
|
+
createSchedulerAuthorityResolver({
|
|
736
|
+
store: schedulerStore,
|
|
737
|
+
validate: authorizeAgendaOccurrence,
|
|
738
|
+
units: ({ occurrence }) =>
|
|
739
|
+
Math.max(
|
|
740
|
+
1,
|
|
741
|
+
Number(occurrence?.payload?.entry?.runPolicy?.maxTurns) || 1,
|
|
742
|
+
),
|
|
743
|
+
}),
|
|
744
|
+
...(ownerId === undefined ? {} : { ownerId }),
|
|
745
|
+
...(leaseMs === undefined ? {} : { leaseMs }),
|
|
746
|
+
...(renewIntervalMs === undefined ? {} : { renewIntervalMs }),
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
async runDue({ signal } = {}) {
|
|
751
|
+
const results = [];
|
|
752
|
+
const observedOccurrences = new Set();
|
|
753
|
+
const recovered = await this.runtime.runUntilIdle({
|
|
754
|
+
limit: 10_000,
|
|
755
|
+
signal,
|
|
756
|
+
jobKind: AGENDA_SCHEDULER_KIND,
|
|
757
|
+
});
|
|
758
|
+
for (const result of recovered.results) {
|
|
759
|
+
const occurrence = result.occurrence;
|
|
760
|
+
const entry = occurrence?.payload?.entry;
|
|
761
|
+
if (!entry?.id) continue;
|
|
762
|
+
observedOccurrences.add(occurrence.id);
|
|
763
|
+
results.push({
|
|
764
|
+
entry: entry.id,
|
|
765
|
+
kind: entry.kind,
|
|
766
|
+
occurrence: occurrence.id,
|
|
767
|
+
recovered: true,
|
|
768
|
+
result,
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
if (signal?.aborted) return results;
|
|
772
|
+
|
|
773
|
+
const now = normalizeEpochMs(Number(this.now()), "now");
|
|
774
|
+
const due = [
|
|
775
|
+
...this.agendaStore.due("wakeup", now),
|
|
776
|
+
...this.agendaStore.due("cron", now),
|
|
777
|
+
...this.agendaStore.due("monitor", now),
|
|
778
|
+
];
|
|
779
|
+
for (const entry of due) {
|
|
780
|
+
const occurrence = enqueueAgendaEntry(this.schedulerStore, entry);
|
|
781
|
+
if (observedOccurrences.has(occurrence.id)) continue;
|
|
782
|
+
const result = await this.runtime.runOccurrence(occurrence.id, {
|
|
783
|
+
signal,
|
|
784
|
+
});
|
|
785
|
+
results.push({
|
|
786
|
+
entry: entry.id,
|
|
787
|
+
kind: entry.kind,
|
|
788
|
+
occurrence: occurrence.id,
|
|
789
|
+
deduplicated: occurrence.deduplicated,
|
|
790
|
+
result,
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
return results;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
export function agendaBridgeAction(entry) {
|
|
798
|
+
const result = entry?.result;
|
|
799
|
+
if (result?.status === "succeeded") {
|
|
800
|
+
return result.result;
|
|
801
|
+
}
|
|
802
|
+
if (result?.status === "busy" || result?.status === "idle") return null;
|
|
803
|
+
return {
|
|
804
|
+
id: entry.entry,
|
|
805
|
+
kind: entry.kind,
|
|
806
|
+
action: "error",
|
|
807
|
+
error: result?.error?.message || `agenda occurrence ${result?.status}`,
|
|
808
|
+
...(typeof result?.error?.code === "string"
|
|
809
|
+
? { errorCode: result.error.code }
|
|
810
|
+
: {}),
|
|
811
|
+
};
|
|
812
|
+
}
|