chainlesschain 0.163.5 → 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-Cv2EE1Zy.js → AIOps-DDIUofA8.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CcpxMLuW.js → ActionButton-Bm-5xYRn.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DE8mJoMI.js → Analytics-D5UH4ljI.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DKn9dk9L.js → AppLayout-D_ifjOOP.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-Cp3mG3Py.js → Artifacts-BY1mXdC7.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-zvsQfF2C.js → Audit-Bujr4Ml3.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-Bi85SsjN.js → BackgroundAgents-sioKSTcC.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Cw47Dj6s.js → Backup-DDOAK8RK.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-C5Ip2afH.js → BaseInput-CtCsVVke.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-C0iAFkJv.js → Chat-DpNj0vnd.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BV9Wv7wO.js → ChatBubbleRenderer-CZta3Ftr.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-3KZZCiAF.js → Checkbox-DoHVNSXq.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-C8OMbBAz.js → Codegen-DlVl7Qmv.js} +1 -1
- package/src/assets/web-panel/assets/{Col-B-MB84LJ.js → Col-GIqoPYty.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DpR7Dxao.js → Community-CIAPEVji.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CyDtz0OS.js → Compact-BN7JiI_h.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-5tiRa2An.js → Compliance-CqUWCM9B.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DnQaKZ5x.js → Cowork-A4iPp7A4.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-BaWfoEDf.js → Cron-B2zeXXWb.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-Bu0HoHhb.js → Crosschain-BR2xmWr8.js} +1 -1
- package/src/assets/web-panel/assets/{DID-31zk8upX.js → DID-BzFDjrIv.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BmVne-e2.js → Dashboard-DKtDo3yA.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-B4PkoOWd.js → Dropdown-RNx43_YG.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BZ6tyFtI.js → EmailListRenderer-SwMt4Rcy.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Osozn4Ds.js → FamilyGuardDashboard-CsYebZ4X.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DaH8TOAP.js → Federation-BmRKvBTy.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-tbU0uwis.js → FormItemContext-CVxGTA-0.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Df8LQBrL.js → GenericCardRenderer-x2iXpyB7.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DsMB6-14.js → Git-DfqbkiOP.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BnH0dZzd.js → Governance-RuHyyj5O.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DhnN1N_P.js → Inference-Bn7uZ7rI.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DnNshAgn.js → KnowledgeGraph-DlP7XefZ.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BmBFX3_N.js → Logs-BYNdOiCd.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-DCslfQU3.js → MarkdownRenderer-aMtgN-R6.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-CPEgWDc3.js → Marketplace-JQYmDqM8.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-C36qjgF9.js → McpTools-Cyx4yZ-9.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-tNHvrgqN.js → Memory-VDuGZTN2.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Xaf6cKjc.js → MobileBridge-_yKo97gi.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-D2E5TTeO.js → MobileProjects-CXRXW3q7.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-D4AJ2dwV.js → Mtc-acTGtfLH.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-Df-gtn2_.js → MtcAudit-ClpjXfkY.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-DTCHLWld.js → Multisig-_6Tn3o96.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DruZ_1Jb.js → NLProgramming-CK7pbZzP.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Dx3pdGDs.js → Notes-Czy6V_P8.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-Bi3L69oL.js → NotificationSettings-Cqx-UVv3.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BHY33m7C.js → OrderTableRenderer-C69WDxGy.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BG2ZVEV-.js → Organization-DFnA9vLQ.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Viwtg4Mn.js → Overflow-ReZkk2yT.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Clm_yc1Z.js → P2P-DzjSI0aI.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-NKmmDiOw.js → PdhVaultBrowser-DKTf5xIP.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-DIL8u9Nk.js → Permissions-DhQ5klhR.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-B-jM4wt-.js → PersonalDataHub-CUL1iHrW.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-627I__H5.js → Pipeline-DU88iu4N.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-2DN2qrkW.js → Privacy-Bw-sYfyK.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CN4wC0sF.js → ProjectInit-Bq3Q5y0z.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-K35vsh5H.js → ProjectSettings-C8spzpho.js} +2 -2
- package/src/assets/web-panel/assets/Projects-Dd-xuPIc.js +1 -0
- package/src/assets/web-panel/assets/{Providers-MckaQUhQ.js → Providers-Bf4eTP9W.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BS3mtA_O.js → QrScannerModal-Dme9BDU1.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BHp_nofL.js → QuickAsk-CIXfv1rE.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DlmnPuZj.js → Recommend-CIxvdeZ-.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-tsyFbvpy.js → RemoteSession-C6k7MExI.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-Ba1iWv2D.js → Reputation-CISDz2bl.js} +1 -1
- package/src/assets/web-panel/assets/{Row-NnlGC--f.js → Row-CdnDxXyf.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CjThSIRv.js → RssFeed-C-QpRHsl.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CojUyC5x.js → Search-DBjaGzSn.js} +1 -1
- package/src/assets/web-panel/assets/{Security-c-sxeJEc.js → Security-CQKukOeE.js} +3 -3
- package/src/assets/web-panel/assets/{Services-CtcKEEmF.js → Services-DZz86jWb.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CIBBXflN.js → Skeleton-jb8RMK0d.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-D1XOOK9n.js → Skills-CdY76fZG.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-hRfEp5Sn.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-BJsSFGYy.js → SyncSettings-qvimGO0O.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CfSZ8A1L.js → Tasks-Dng5LPJC.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-idDf47W-.js → Templates-Bwqq5Wll.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-C-UsX6w6.js → Tenant-DNWuwP7Z.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BgJtX5n1.js → Terminal-B-6Othpp.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DP0BLKTl.js → TimelineRenderer-DKpKZfzx.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-LwCzqETp.js → Tokens-DzfGMd2J.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-D_9tVch0.js → Trigger-BXwUi74J.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BH2UlEWh.js → Trust-BNzUq4ui.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CIpD0nYb.js → UkeySign-BYugxww4.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-9ShhYxK_.js → VideoEditing-DdauhtSJ.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-C7afGNIG.js → Wallet-CSC9bCe_.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-bUKn6i5R.js → WebAuthn-Q8AN3bTO.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-CxgQaGyp.js → WorkflowEditor-d-LBGCuA.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DMVkmJwx.js → chat-BLVDCgsZ.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CDzQh5nS.js → colors-BZlPSEHl.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-C51kEF4D.js → compact-item-IWseP6R6.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BTk9bXBn.js → createContext-DnVRV3Ao.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Jzu62CLC.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CMje33rb.js → hasIn-DLQZA_Wn.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBz9LIMS.js → index-1jeAudHd.js} +1 -1
- package/src/assets/web-panel/assets/{index-hg_emBsi.js → index-BEu2lMBy.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRG-aqOs.js → index-BOZJBTWZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDLkNkLj.js → index-BSjilZ9L.js} +1 -1
- package/src/assets/web-panel/assets/{index-CvSeVzqe.js → index-BZs5bBnk.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpcrFxMI.js → index-B_N4-Gq9.js} +1 -1
- package/src/assets/web-panel/assets/{index-D9rC-fGW.js → index-Bbj2NS_I.js} +3 -3
- package/src/assets/web-panel/assets/{index-BVePe_Rf.js → index-BhA3QEni.js} +1 -1
- package/src/assets/web-panel/assets/{index-BzCkNylB.js → index-Bxp7uDD8.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAfY7m7i.js → index-C1ifZtXo.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTlsE8Df.js → index-C2FqfMvU.js} +1 -1
- package/src/assets/web-panel/assets/{index-DqU9njjT.js → index-C5dgnilC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtlEqHLP.js → index-CINJQEj6.js} +1 -1
- package/src/assets/web-panel/assets/{index-BeEaAEIp.js → index-COev0W2g.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCLj9hcD.js → index-CT9vVN-X.js} +1 -1
- package/src/assets/web-panel/assets/{index-h31MwrME.js → index-CZDekj7T.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ld_4mFmC.js → index-CarMTIyQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CwGPwjUu.js → index-CbQNL0ue.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5IWTMVl.js → index-CpBaJ3TD.js} +1 -1
- package/src/assets/web-panel/assets/{index-BW4F5YDF.js → index-DB0MJgF_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CypsfcEq.js → index-DOoQpuQY.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfC-piWZ.js → index-DTbxp4UH.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCRvG9lq.js → index-DUq0c7gq.js} +1 -1
- package/src/assets/web-panel/assets/{index-0Auljb10.js → index-DZpYeqrh.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4rQCS8T.js → index-D_LQodeJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtM8OT_7.js → index-DalA0TKC.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1oZQmAA.js → index-DjWnONdy.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRLQyp2A.js → index-Dkwsi1Kn.js} +1 -1
- package/src/assets/web-panel/assets/{index-togc_uYn.js → index-DocEhinm.js} +1 -1
- package/src/assets/web-panel/assets/{index-x53Vf7la.js → index-DpVrK6fr.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgivOKHA.js → index-Dy0R5ZED.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0-zMtKA.js → index-M7wxfAbm.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXQBPLyE.js → index-N_GoWZX4.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1oZReaY.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-Cd0hmPxD.js → index-gt8bNefW.js} +1 -1
- package/src/assets/web-panel/assets/{index-OS-vfbTA.js → index-hwVDeoC0.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJfnCuMq.js → index-jWb70ZgZ.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-vrA8K-ac.js → initDefaultProps-Co67Kqmb.js} +1 -1
- package/src/assets/web-panel/assets/{motion-ByEzwIAl.js → motion-BMOxQSF_.js} +1 -1
- package/src/assets/web-panel/assets/{move-Ck4AWjw-.js → move-C5fWDw-N.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-6p0l9SrB.js → mtc-parser-BJevpL0e.js} +1 -1
- package/src/assets/web-panel/assets/{omit-B1CUPQ6R.js → omit-BCzNU8s6.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-B1STg1iW.js → pickAttrs-CybWvDPk.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DTTllkBV.js → placementArrow-BMWS9HnW.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-AIrrCfBb.js → responsiveObserve-BoL2UnGY.js} +1 -1
- package/src/assets/web-panel/assets/{slide-C_fPMjdR.js → slide-DWNfXraU.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-D5ow2yIP.js → statusUtils-BquTM3tX.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Dy3hEA--.js → styleChecker-BEkCleim.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-WfzcfSh4.js → useFlexGapSupport-Oecnq7EA.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CKSejfas.js → useFs-CkDffJ4F.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-od3zpPBN.js → usePersonalDataHub-qbvKXesB.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DRD_2DFA.js → vnode-C6v8n6mA.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DmNrEtgf.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 +76 -4
- package/src/commands/automation.js +462 -3
- package/src/commands/background-session.js +102 -1
- package/src/commands/cowork.js +6 -1
- package/src/commands/loop.js +167 -36
- package/src/commands/routine.js +11 -4
- package/src/commands/scheduler-daemon.js +450 -0
- package/src/data/changelog.json +15 -2
- package/src/lib/agent-schedule-store.js +323 -43
- 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 +316 -18
- package/src/lib/cowork-observe.js +5 -1
- package/src/lib/loop.js +37 -2
- package/src/lib/permission-engine.js +14 -7
- package/src/lib/public-ide-capability-manifest.js +2 -0
- package/src/lib/routine-store.js +166 -65
- package/src/lib/scheduler-kernel/agenda-adapter.js +297 -27
- 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 +58 -12
- package/src/lib/scheduler-kernel/loop-adapter.js +378 -0
- package/src/lib/scheduler-kernel/routine-adapter.js +303 -8
- package/src/lib/scheduler-kernel/service.js +328 -0
- package/src/lib/scheduler-kernel/store.js +828 -96
- package/src/runtime/agent-core.js +2 -1
- 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/Projects-B_wQadhX.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-Dexb1f_6.js +0 -1
- 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
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { nextCronTime } from "../agent-schedule-store.js";
|
|
3
|
+
import {
|
|
4
|
+
automationExecutionAuthorityDigest,
|
|
5
|
+
automationExecutionAuthoritySnapshot,
|
|
6
|
+
automationSchedulerAuthority,
|
|
7
|
+
normalizeAutomationExecutionAuthoritySnapshot,
|
|
8
|
+
reserveAutomationExecutionAuthority,
|
|
9
|
+
} from "../automation-execution-authority.js";
|
|
10
|
+
import {
|
|
11
|
+
EXECUTION_STATUS,
|
|
12
|
+
FLOW_STATUS,
|
|
13
|
+
TRIGGER_TYPE,
|
|
14
|
+
executeFlow,
|
|
15
|
+
getExecution,
|
|
16
|
+
getFlow,
|
|
17
|
+
listFlows,
|
|
18
|
+
} from "../automation-engine.js";
|
|
19
|
+
import {
|
|
20
|
+
SchedulerKernelError,
|
|
21
|
+
canonicalJson,
|
|
22
|
+
normalizeEpochMs,
|
|
23
|
+
normalizeIdentifier,
|
|
24
|
+
normalizeJson,
|
|
25
|
+
} from "./contract.js";
|
|
26
|
+
import { SchedulerRuntime } from "./runtime.js";
|
|
27
|
+
import {
|
|
28
|
+
bindSchedulerAuthorityPolicy,
|
|
29
|
+
createSchedulerAuthorityResolver,
|
|
30
|
+
} from "./authority-resolver.js";
|
|
31
|
+
|
|
32
|
+
export const AUTOMATION_SCHEDULER_KIND = "automation";
|
|
33
|
+
export const AUTOMATION_SCHEDULER_CAPABILITY = "automation.execute";
|
|
34
|
+
export const AUTOMATION_SCHEDULER_CHANNEL = "scheduled";
|
|
35
|
+
export const AUTOMATION_SCHEDULER_MAX_CATCHUP_STEPS = 527_040;
|
|
36
|
+
|
|
37
|
+
function automationError(
|
|
38
|
+
code,
|
|
39
|
+
message,
|
|
40
|
+
details = undefined,
|
|
41
|
+
cause = undefined,
|
|
42
|
+
) {
|
|
43
|
+
const error = new SchedulerKernelError(
|
|
44
|
+
code,
|
|
45
|
+
message,
|
|
46
|
+
details,
|
|
47
|
+
cause ? { cause } : undefined,
|
|
48
|
+
);
|
|
49
|
+
// Flow execution may eventually perform connector side effects. Unless a
|
|
50
|
+
// future adapter can prove an attempt did not start, never replay it merely
|
|
51
|
+
// because the process or settlement failed.
|
|
52
|
+
error.retryable = false;
|
|
53
|
+
return error;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizeIsoTime(value, field) {
|
|
57
|
+
const epoch = Date.parse(value);
|
|
58
|
+
if (!Number.isSafeInteger(epoch) || epoch < 0) {
|
|
59
|
+
throw automationError(
|
|
60
|
+
"AUTOMATION_SCHEDULER_FLOW_INVALID",
|
|
61
|
+
`${field} must be a valid timestamp`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return new Date(epoch).toISOString();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizeFlow(flow) {
|
|
68
|
+
if (!flow || typeof flow !== "object" || Array.isArray(flow)) {
|
|
69
|
+
throw automationError(
|
|
70
|
+
"AUTOMATION_SCHEDULER_FLOW_INVALID",
|
|
71
|
+
"Automation scheduler requires a flow object",
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const plain = normalizeJson(flow, "automationFlow");
|
|
75
|
+
const id = normalizeIdentifier(plain.id, "automationFlow.id");
|
|
76
|
+
if (typeof plain.name !== "string" || plain.name.length === 0) {
|
|
77
|
+
throw automationError(
|
|
78
|
+
"AUTOMATION_SCHEDULER_FLOW_INVALID",
|
|
79
|
+
`Automation flow name is missing: ${id}`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (!Array.isArray(plain.nodes) || !Array.isArray(plain.edges)) {
|
|
83
|
+
throw automationError(
|
|
84
|
+
"AUTOMATION_SCHEDULER_FLOW_INVALID",
|
|
85
|
+
`Automation flow graph is malformed: ${id}`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (!Object.values(FLOW_STATUS).includes(plain.status)) {
|
|
89
|
+
throw automationError(
|
|
90
|
+
"AUTOMATION_SCHEDULER_FLOW_INVALID",
|
|
91
|
+
`Automation flow status is invalid: ${id}`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (typeof plain.schedule !== "string" || plain.schedule.length === 0) {
|
|
95
|
+
throw automationError(
|
|
96
|
+
"AUTOMATION_SCHEDULER_FLOW_UNSCHEDULED",
|
|
97
|
+
`Automation flow has no cron schedule: ${id}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
// Parsing here binds the accepted cron dialect into the snapshot contract.
|
|
101
|
+
nextCronTime(plain.schedule, Date.now());
|
|
102
|
+
return {
|
|
103
|
+
id,
|
|
104
|
+
name: plain.name,
|
|
105
|
+
description: typeof plain.description === "string" ? plain.description : "",
|
|
106
|
+
nodes: plain.nodes,
|
|
107
|
+
edges: plain.edges,
|
|
108
|
+
status: plain.status,
|
|
109
|
+
schedule: plain.schedule,
|
|
110
|
+
createdBy: typeof plain.createdBy === "string" ? plain.createdBy : null,
|
|
111
|
+
sharedWith: Array.isArray(plain.sharedWith) ? plain.sharedWith : [],
|
|
112
|
+
createdAt: normalizeIsoTime(plain.createdAt, "automationFlow.createdAt"),
|
|
113
|
+
updatedAt: normalizeIsoTime(plain.updatedAt, "automationFlow.updatedAt"),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function automationFlowSnapshot(flow) {
|
|
118
|
+
return normalizeFlow(flow);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function automationFlowSnapshotDigest(flow) {
|
|
122
|
+
return createHash("sha256")
|
|
123
|
+
.update("chainlesschain.scheduler.automation-flow.v1\0", "utf8")
|
|
124
|
+
.update(
|
|
125
|
+
canonicalJson(automationFlowSnapshot(flow), "automationFlowSnapshot"),
|
|
126
|
+
"utf8",
|
|
127
|
+
)
|
|
128
|
+
.digest("hex");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function automationSchedulerJobId(flowId) {
|
|
132
|
+
return `automation:${normalizeIdentifier(flowId, "automationFlowId")}:scheduled`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function automationSchedulerExecutionId(occurrenceId) {
|
|
136
|
+
const id = normalizeIdentifier(occurrenceId, "occurrenceId");
|
|
137
|
+
const digest = createHash("sha256")
|
|
138
|
+
.update("chainlesschain.scheduler.automation-execution.v1\0", "utf8")
|
|
139
|
+
.update(id, "utf8")
|
|
140
|
+
.digest("hex");
|
|
141
|
+
return `exec-scheduler-${digest}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function buildAutomationSchedulerJob(flow, executionAuthority) {
|
|
145
|
+
const snapshot = automationFlowSnapshot(flow);
|
|
146
|
+
const normalizedExecutionAuthority =
|
|
147
|
+
normalizeAutomationExecutionAuthoritySnapshot(executionAuthority, snapshot);
|
|
148
|
+
return {
|
|
149
|
+
id: automationSchedulerJobId(snapshot.id),
|
|
150
|
+
kind: AUTOMATION_SCHEDULER_KIND,
|
|
151
|
+
trigger: {
|
|
152
|
+
source: "automation-engine",
|
|
153
|
+
channel: AUTOMATION_SCHEDULER_CHANNEL,
|
|
154
|
+
cron: snapshot.schedule,
|
|
155
|
+
},
|
|
156
|
+
payload: {
|
|
157
|
+
channel: AUTOMATION_SCHEDULER_CHANNEL,
|
|
158
|
+
flow: snapshot,
|
|
159
|
+
snapshotDigest: automationFlowSnapshotDigest(snapshot),
|
|
160
|
+
executionAuthority: normalizedExecutionAuthority,
|
|
161
|
+
executionAuthorityDigest: automationExecutionAuthorityDigest(
|
|
162
|
+
normalizedExecutionAuthority,
|
|
163
|
+
snapshot,
|
|
164
|
+
),
|
|
165
|
+
},
|
|
166
|
+
authority: automationSchedulerAuthority(
|
|
167
|
+
normalizedExecutionAuthority,
|
|
168
|
+
snapshot,
|
|
169
|
+
AUTOMATION_SCHEDULER_CAPABILITY,
|
|
170
|
+
),
|
|
171
|
+
enabled: snapshot.status === FLOW_STATUS.ACTIVE,
|
|
172
|
+
// A reclaimed occurrence needs a second attempt to read deterministic
|
|
173
|
+
// execution evidence. Adapter errors themselves remain non-retryable.
|
|
174
|
+
maxAttempts: 3,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function comparableJob(job) {
|
|
179
|
+
return {
|
|
180
|
+
kind: job.kind,
|
|
181
|
+
trigger: job.trigger,
|
|
182
|
+
payload: job.payload,
|
|
183
|
+
authority: job.authority,
|
|
184
|
+
enabled: job.enabled,
|
|
185
|
+
maxAttempts: job.maxAttempts,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function sameJob(current, desired) {
|
|
190
|
+
return (
|
|
191
|
+
current !== null &&
|
|
192
|
+
canonicalJson(comparableJob(current), "currentAutomationJob") ===
|
|
193
|
+
canonicalJson(comparableJob(desired), "desiredAutomationJob")
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function syncAutomationSchedulerJob(
|
|
198
|
+
schedulerStore,
|
|
199
|
+
flow,
|
|
200
|
+
executionAuthority,
|
|
201
|
+
) {
|
|
202
|
+
const desired = buildAutomationSchedulerJob(flow, executionAuthority);
|
|
203
|
+
desired.authority = bindSchedulerAuthorityPolicy(
|
|
204
|
+
schedulerStore,
|
|
205
|
+
desired.authority,
|
|
206
|
+
);
|
|
207
|
+
let current = schedulerStore.getJob(desired.id);
|
|
208
|
+
if (!current) {
|
|
209
|
+
try {
|
|
210
|
+
return schedulerStore.createJob(desired);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (error?.code !== "SCHEDULER_CONFLICT") throw error;
|
|
213
|
+
current = schedulerStore.getJob(desired.id);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (sameJob(current, desired)) return current;
|
|
217
|
+
try {
|
|
218
|
+
return schedulerStore.updateJob(current.id, current.revision, {
|
|
219
|
+
kind: desired.kind,
|
|
220
|
+
trigger: desired.trigger,
|
|
221
|
+
payload: desired.payload,
|
|
222
|
+
authority: desired.authority,
|
|
223
|
+
enabled: desired.enabled,
|
|
224
|
+
maxAttempts: desired.maxAttempts,
|
|
225
|
+
});
|
|
226
|
+
} catch (error) {
|
|
227
|
+
if (error?.code !== "SCHEDULER_REVISION_CONFLICT") throw error;
|
|
228
|
+
const latest = schedulerStore.getJob(desired.id);
|
|
229
|
+
if (sameJob(latest, desired)) return latest;
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function latestEnqueuedScheduledFor(schedulerStore, jobId) {
|
|
235
|
+
const event = schedulerStore
|
|
236
|
+
.history({ jobId, limit: 100 })
|
|
237
|
+
.find(
|
|
238
|
+
(candidate) =>
|
|
239
|
+
candidate.type === "occurrence_enqueued" &&
|
|
240
|
+
Number.isSafeInteger(candidate.data?.scheduledFor),
|
|
241
|
+
);
|
|
242
|
+
return event?.data?.scheduledFor ?? null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Return the next logical occurrence, collapsing missed cron periods to the
|
|
247
|
+
* latest due minute. This preserves the existing Agenda invariant: downtime
|
|
248
|
+
* causes one catch-up execution, never an unbounded replay storm.
|
|
249
|
+
*/
|
|
250
|
+
export function scheduledAutomationFireAt(schedulerStore, flow, now) {
|
|
251
|
+
const snapshot = automationFlowSnapshot(flow);
|
|
252
|
+
const currentTime = normalizeEpochMs(Number(now), "now");
|
|
253
|
+
const jobId = automationSchedulerJobId(snapshot.id);
|
|
254
|
+
const lastEnqueued = latestEnqueuedScheduledFor(schedulerStore, jobId);
|
|
255
|
+
const definitionUpdatedAt = Date.parse(snapshot.updatedAt);
|
|
256
|
+
const initial = normalizeEpochMs(
|
|
257
|
+
Math.max(lastEnqueued ?? 0, definitionUpdatedAt),
|
|
258
|
+
"automation schedule cursor",
|
|
259
|
+
);
|
|
260
|
+
let next = nextCronTime(snapshot.schedule, initial);
|
|
261
|
+
if (next === null) {
|
|
262
|
+
throw automationError(
|
|
263
|
+
"AUTOMATION_SCHEDULER_NO_NEXT_FIRE",
|
|
264
|
+
`Automation cron has no next occurrence: ${snapshot.id}`,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
let latestDue = null;
|
|
268
|
+
let steps = 0;
|
|
269
|
+
while (next <= currentTime) {
|
|
270
|
+
latestDue = next;
|
|
271
|
+
steps += 1;
|
|
272
|
+
if (steps > AUTOMATION_SCHEDULER_MAX_CATCHUP_STEPS) {
|
|
273
|
+
throw automationError(
|
|
274
|
+
"AUTOMATION_SCHEDULER_CATCHUP_RANGE_EXCEEDED",
|
|
275
|
+
`Automation cron catch-up exceeds the supported range: ${snapshot.id}`,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
next = nextCronTime(snapshot.schedule, next);
|
|
279
|
+
if (next === null) break;
|
|
280
|
+
}
|
|
281
|
+
return latestDue ?? next;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function enqueueScheduledAutomation(
|
|
285
|
+
schedulerStore,
|
|
286
|
+
flow,
|
|
287
|
+
now,
|
|
288
|
+
executionAuthority,
|
|
289
|
+
) {
|
|
290
|
+
const job = syncAutomationSchedulerJob(
|
|
291
|
+
schedulerStore,
|
|
292
|
+
flow,
|
|
293
|
+
executionAuthority,
|
|
294
|
+
);
|
|
295
|
+
if (!job.enabled) {
|
|
296
|
+
throw automationError(
|
|
297
|
+
"AUTOMATION_SCHEDULER_FLOW_NOT_ACTIVE",
|
|
298
|
+
`Automation flow is not active: ${flow.id}`,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
const scheduledFor = scheduledAutomationFireAt(schedulerStore, flow, now);
|
|
302
|
+
if (scheduledFor === null || scheduledFor > now) return null;
|
|
303
|
+
return schedulerStore.enqueueOccurrence({
|
|
304
|
+
jobId: job.id,
|
|
305
|
+
scheduledFor,
|
|
306
|
+
triggerKey: `cron:${scheduledFor}`,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function automationDomainAuthority(authority) {
|
|
311
|
+
return {
|
|
312
|
+
...authority,
|
|
313
|
+
authorizationRefs: {
|
|
314
|
+
decisionId: authority.authorizationRefs.decisionId,
|
|
315
|
+
policyRevision: authority.authorizationRefs.policyRevision,
|
|
316
|
+
grantIds: authority.authorizationRefs.grantIds,
|
|
317
|
+
approvalIds: authority.authorizationRefs.approvalIds,
|
|
318
|
+
delegationIds: authority.authorizationRefs.delegationIds,
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function authorizeAutomationOccurrence({ job, occurrence }) {
|
|
324
|
+
try {
|
|
325
|
+
const payload = occurrence?.payload;
|
|
326
|
+
const authority = occurrence?.authority;
|
|
327
|
+
const flowId = payload?.flow?.id;
|
|
328
|
+
const expectedDigest = automationFlowSnapshotDigest(payload?.flow);
|
|
329
|
+
const executionAuthority = normalizeAutomationExecutionAuthoritySnapshot(
|
|
330
|
+
payload?.executionAuthority,
|
|
331
|
+
payload?.flow,
|
|
332
|
+
);
|
|
333
|
+
const executionAuthorityDigest = automationExecutionAuthorityDigest(
|
|
334
|
+
executionAuthority,
|
|
335
|
+
payload?.flow,
|
|
336
|
+
);
|
|
337
|
+
const expectedAuthority = automationSchedulerAuthority(
|
|
338
|
+
executionAuthority,
|
|
339
|
+
payload?.flow,
|
|
340
|
+
AUTOMATION_SCHEDULER_CAPABILITY,
|
|
341
|
+
);
|
|
342
|
+
const allowed =
|
|
343
|
+
job?.kind === AUTOMATION_SCHEDULER_KIND &&
|
|
344
|
+
payload?.channel === AUTOMATION_SCHEDULER_CHANNEL &&
|
|
345
|
+
executionAuthority.flowId === flowId &&
|
|
346
|
+
payload?.executionAuthorityDigest === executionAuthorityDigest &&
|
|
347
|
+
canonicalJson(
|
|
348
|
+
automationDomainAuthority(authority),
|
|
349
|
+
"automationOccurrenceAuthority",
|
|
350
|
+
) === canonicalJson(expectedAuthority, "expectedAutomationAuthority") &&
|
|
351
|
+
payload?.snapshotDigest === expectedDigest;
|
|
352
|
+
return {
|
|
353
|
+
allowed,
|
|
354
|
+
reason: allowed
|
|
355
|
+
? "automation_snapshot_bound"
|
|
356
|
+
: "automation_authority_mismatch",
|
|
357
|
+
};
|
|
358
|
+
} catch {
|
|
359
|
+
return { allowed: false, reason: "automation_authority_malformed" };
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function existingExecutionResult(db, occurrence, expected) {
|
|
364
|
+
const executionId = automationSchedulerExecutionId(occurrence.id);
|
|
365
|
+
const execution = getExecution(db, executionId);
|
|
366
|
+
if (!execution) return null;
|
|
367
|
+
if (
|
|
368
|
+
execution.flowId !== expected.id ||
|
|
369
|
+
execution.triggerType !== TRIGGER_TYPE.SCHEDULE
|
|
370
|
+
) {
|
|
371
|
+
throw automationError(
|
|
372
|
+
"AUTOMATION_SCHEDULER_EXECUTION_BINDING_MISMATCH",
|
|
373
|
+
`Automation execution evidence does not match occurrence: ${occurrence.id}`,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
if (execution.status === EXECUTION_STATUS.RUNNING) {
|
|
377
|
+
throw automationError(
|
|
378
|
+
"AUTOMATION_SCHEDULER_OUTCOME_UNKNOWN",
|
|
379
|
+
`Automation outcome is unknown; refusing duplicate execution: ${execution.id}`,
|
|
380
|
+
{ executionId: execution.id },
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
if (execution.status !== EXECUTION_STATUS.SUCCESS) {
|
|
384
|
+
throw automationError(
|
|
385
|
+
"AUTOMATION_SCHEDULER_EXECUTION_FAILED",
|
|
386
|
+
`Automation execution did not succeed: ${execution.id}`,
|
|
387
|
+
{ executionId: execution.id, status: execution.status },
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
return execution;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export function createAutomationSchedulerAdapter({ db, now = Date.now } = {}) {
|
|
394
|
+
if (!db || typeof db.prepare !== "function") {
|
|
395
|
+
throw automationError(
|
|
396
|
+
"AUTOMATION_SCHEDULER_DATABASE_REQUIRED",
|
|
397
|
+
"Automation scheduler adapter requires a compatible database",
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
return {
|
|
401
|
+
kind: AUTOMATION_SCHEDULER_KIND,
|
|
402
|
+
async execute({ occurrence }) {
|
|
403
|
+
const payload = occurrence?.payload;
|
|
404
|
+
const expected = automationFlowSnapshot(payload?.flow);
|
|
405
|
+
const expectedDigest = automationFlowSnapshotDigest(expected);
|
|
406
|
+
if (
|
|
407
|
+
payload?.channel !== AUTOMATION_SCHEDULER_CHANNEL ||
|
|
408
|
+
payload?.snapshotDigest !== expectedDigest
|
|
409
|
+
) {
|
|
410
|
+
throw automationError(
|
|
411
|
+
"AUTOMATION_SCHEDULER_SNAPSHOT_INVALID",
|
|
412
|
+
`Automation occurrence snapshot is invalid: ${occurrence?.id}`,
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// A prior process may have committed the flow result and died before the
|
|
417
|
+
// scheduler settlement. Durable success is authoritative even if the flow
|
|
418
|
+
// was subsequently paused or edited.
|
|
419
|
+
const recovered = existingExecutionResult(db, occurrence, expected);
|
|
420
|
+
if (recovered) return recovered;
|
|
421
|
+
|
|
422
|
+
const current = getFlow(db, expected.id);
|
|
423
|
+
if (!current) {
|
|
424
|
+
throw automationError(
|
|
425
|
+
"AUTOMATION_SCHEDULER_FLOW_NOT_FOUND",
|
|
426
|
+
`Automation flow disappeared before execution: ${expected.id}`,
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
if (current.status !== FLOW_STATUS.ACTIVE) {
|
|
430
|
+
throw automationError(
|
|
431
|
+
"AUTOMATION_SCHEDULER_FLOW_NOT_ACTIVE",
|
|
432
|
+
`Automation flow is not active: ${expected.id}`,
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
if (automationFlowSnapshotDigest(current) !== expectedDigest) {
|
|
436
|
+
throw automationError(
|
|
437
|
+
"AUTOMATION_SCHEDULER_STALE_SNAPSHOT",
|
|
438
|
+
`Automation flow changed after occurrence enqueue: ${expected.id}`,
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
reserveAutomationExecutionAuthority(
|
|
443
|
+
db,
|
|
444
|
+
current,
|
|
445
|
+
occurrence.id,
|
|
446
|
+
payload.executionAuthority,
|
|
447
|
+
payload.executionAuthorityDigest,
|
|
448
|
+
{ now },
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
let execution;
|
|
452
|
+
try {
|
|
453
|
+
execution = executeFlow(db, expected.id, {
|
|
454
|
+
inputData: {},
|
|
455
|
+
triggerType: TRIGGER_TYPE.SCHEDULE,
|
|
456
|
+
executionId: automationSchedulerExecutionId(occurrence.id),
|
|
457
|
+
});
|
|
458
|
+
} catch (cause) {
|
|
459
|
+
if (cause?.code === "AUTOMATION_EXECUTION_OUTCOME_UNKNOWN") {
|
|
460
|
+
throw automationError(
|
|
461
|
+
"AUTOMATION_SCHEDULER_OUTCOME_UNKNOWN",
|
|
462
|
+
cause.message,
|
|
463
|
+
{ executionId: automationSchedulerExecutionId(occurrence.id) },
|
|
464
|
+
cause,
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
throw cause;
|
|
468
|
+
}
|
|
469
|
+
if (execution.status !== EXECUTION_STATUS.SUCCESS) {
|
|
470
|
+
throw automationError(
|
|
471
|
+
"AUTOMATION_SCHEDULER_EXECUTION_FAILED",
|
|
472
|
+
`Automation execution failed: ${execution.id}`,
|
|
473
|
+
{ executionId: execution.id, error: execution.error },
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
return execution;
|
|
477
|
+
},
|
|
478
|
+
classifyError() {
|
|
479
|
+
return { retryable: false };
|
|
480
|
+
},
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export class AutomationSchedulerBridge {
|
|
485
|
+
constructor({
|
|
486
|
+
db,
|
|
487
|
+
schedulerStore,
|
|
488
|
+
now = Date.now,
|
|
489
|
+
ownerId,
|
|
490
|
+
leaseMs,
|
|
491
|
+
renewIntervalMs,
|
|
492
|
+
authorityResolver,
|
|
493
|
+
} = {}) {
|
|
494
|
+
if (typeof now !== "function") {
|
|
495
|
+
throw automationError(
|
|
496
|
+
"AUTOMATION_SCHEDULER_CLOCK_REQUIRED",
|
|
497
|
+
"Automation scheduler bridge requires a clock function",
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
this.db = db;
|
|
501
|
+
this.schedulerStore = schedulerStore;
|
|
502
|
+
this.now = now;
|
|
503
|
+
this.runtime = new SchedulerRuntime({
|
|
504
|
+
store: schedulerStore,
|
|
505
|
+
adapters: [createAutomationSchedulerAdapter({ db, now })],
|
|
506
|
+
authorize:
|
|
507
|
+
authorityResolver ||
|
|
508
|
+
createSchedulerAuthorityResolver({
|
|
509
|
+
store: schedulerStore,
|
|
510
|
+
validate: authorizeAutomationOccurrence,
|
|
511
|
+
units: ({ occurrence }) =>
|
|
512
|
+
Math.max(
|
|
513
|
+
1,
|
|
514
|
+
occurrence?.payload?.flow?.nodes?.filter(
|
|
515
|
+
(node) => (node?.type || "action") === "action",
|
|
516
|
+
).length || 1,
|
|
517
|
+
),
|
|
518
|
+
}),
|
|
519
|
+
...(ownerId === undefined ? {} : { ownerId }),
|
|
520
|
+
...(leaseMs === undefined ? {} : { leaseMs }),
|
|
521
|
+
...(renewIntervalMs === undefined ? {} : { renewIntervalMs }),
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async runDue({ signal } = {}) {
|
|
526
|
+
const results = [];
|
|
527
|
+
const observedOccurrences = new Set();
|
|
528
|
+
const recovered = await this.runtime.runUntilIdle({
|
|
529
|
+
limit: 10_000,
|
|
530
|
+
signal,
|
|
531
|
+
jobKind: AUTOMATION_SCHEDULER_KIND,
|
|
532
|
+
});
|
|
533
|
+
for (const result of recovered.results) {
|
|
534
|
+
const occurrence = result.occurrence;
|
|
535
|
+
const flowId = occurrence?.payload?.flow?.id;
|
|
536
|
+
if (typeof flowId !== "string") continue;
|
|
537
|
+
observedOccurrences.add(occurrence.id);
|
|
538
|
+
results.push({
|
|
539
|
+
flow: flowId,
|
|
540
|
+
occurrence: occurrence.id,
|
|
541
|
+
recovered: true,
|
|
542
|
+
result,
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
if (signal?.aborted) return results;
|
|
546
|
+
|
|
547
|
+
const now = normalizeEpochMs(Number(this.now()), "now");
|
|
548
|
+
const flows = listFlows(this.db, {
|
|
549
|
+
status: FLOW_STATUS.ACTIVE,
|
|
550
|
+
limit: 10_000,
|
|
551
|
+
}).filter((flow) => typeof flow.schedule === "string" && flow.schedule);
|
|
552
|
+
for (const flow of flows) {
|
|
553
|
+
let occurrence;
|
|
554
|
+
try {
|
|
555
|
+
occurrence = enqueueScheduledAutomation(
|
|
556
|
+
this.schedulerStore,
|
|
557
|
+
flow,
|
|
558
|
+
now,
|
|
559
|
+
automationExecutionAuthoritySnapshot(this.db, flow),
|
|
560
|
+
);
|
|
561
|
+
} catch (error) {
|
|
562
|
+
if (!String(error?.code || "").startsWith("AUTOMATION_EXECUTION_")) {
|
|
563
|
+
throw error;
|
|
564
|
+
}
|
|
565
|
+
results.push({
|
|
566
|
+
flow: flow.id,
|
|
567
|
+
occurrence: null,
|
|
568
|
+
rejected: true,
|
|
569
|
+
result: {
|
|
570
|
+
status: "rejected",
|
|
571
|
+
error: {
|
|
572
|
+
code: error?.code || "AUTOMATION_EXECUTION_PREFLIGHT_REJECTED",
|
|
573
|
+
message: String(error?.message || error).slice(0, 1000),
|
|
574
|
+
},
|
|
575
|
+
},
|
|
576
|
+
});
|
|
577
|
+
continue;
|
|
578
|
+
}
|
|
579
|
+
if (!occurrence || observedOccurrences.has(occurrence.id)) continue;
|
|
580
|
+
const result = await this.runtime.runOccurrence(occurrence.id, {
|
|
581
|
+
signal,
|
|
582
|
+
});
|
|
583
|
+
results.push({
|
|
584
|
+
flow: flow.id,
|
|
585
|
+
occurrence: occurrence.id,
|
|
586
|
+
deduplicated: occurrence.deduplicated,
|
|
587
|
+
result,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
return results;
|
|
591
|
+
}
|
|
592
|
+
}
|