chainlesschain 0.163.4 → 0.163.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DB5X7qqV.js → AIOps-Cv2EE1Zy.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DxHRtKLk.js → ActionButton-CcpxMLuW.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DIH5QrxT.js → Analytics-DE8mJoMI.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-uu0oxVz7.js → AppLayout-DKn9dk9L.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BNSMiW9Y.js → Artifacts-Cp3mG3Py.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DcAzB4_x.js → Audit-zvsQfF2C.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-ClSsvjIc.js → BackgroundAgents-Bi85SsjN.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BMciY0js.js → Backup-Cw47Dj6s.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Hn3b57Sh.js → BaseInput-C5Ip2afH.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CoSlC53V.js → Chat-C0iAFkJv.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CFevJ3So.js → ChatBubbleRenderer-BV9Wv7wO.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-B4L4Eu4w.js → Checkbox-3KZZCiAF.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-C77FgRxF.js → Codegen-C8OMbBAz.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BrLtJLH3.js → Col-B-MB84LJ.js} +1 -1
- package/src/assets/web-panel/assets/{Community-Be7pXXhA.js → Community-DpR7Dxao.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DuGPecjo.js → Compact-CyDtz0OS.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CzS66mjP.js → Compliance-5tiRa2An.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CEhjCoyK.js → Cowork-DnQaKZ5x.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-D7ir8Vja.js → Cron-BaWfoEDf.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BcXmuLxf.js → Crosschain-Bu0HoHhb.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Cto1dtBy.js → DID-31zk8upX.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-B7KjgwOX.js → Dashboard-BmVne-e2.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-COMqxijg.js → Dropdown-B4PkoOWd.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BZrya-nf.js → EmailListRenderer-BZ6tyFtI.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-AmVeMq0J.js → FamilyGuardDashboard-Osozn4Ds.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-kZU8c-27.js → Federation-DaH8TOAP.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-sOV22v2-.js → FormItemContext-tbU0uwis.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BUuLtGCz.js → GenericCardRenderer-Df8LQBrL.js} +1 -1
- package/src/assets/web-panel/assets/{Git-K-ZxsLsH.js → Git-DsMB6-14.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-D3OEnawI.js → Governance-BnH0dZzd.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Bll0gRmP.js → Inference-DhnN1N_P.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DBXwjr53.js → KnowledgeGraph-DnNshAgn.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CEzPwEfP.js → Logs-BmBFX3_N.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-DCslfQU3.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BYyRyOPz.js → Marketplace-CPEgWDc3.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DVu8w_rM.js → McpTools-C36qjgF9.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-tenuZ_hO.js → Memory-tNHvrgqN.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Bxgy4ozS.js → MobileBridge-Xaf6cKjc.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-Dgx1D6sc.js → MobileProjects-D2E5TTeO.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CRfJ8r8j.js → Mtc-D4AJ2dwV.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-BBJq4Aiz.js → MtcAudit-Df-gtn2_.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-B3ii9lai.js → Multisig-DTCHLWld.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-FIi-Cubv.js → NLProgramming-DruZ_1Jb.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DIq8MPf1.js → Notes-Dx3pdGDs.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-BFOD9eGd.js → NotificationSettings-Bi3L69oL.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer--41u7vre.js → OrderTableRenderer-BHY33m7C.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-B_0AWztW.js → Organization-BG2ZVEV-.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-B6I7TuXv.js → Overflow-Viwtg4Mn.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Cdf6qens.js → P2P-Clm_yc1Z.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-B_0FcQJY.js → PdhVaultBrowser-NKmmDiOw.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-6nFBjsQp.js → Permissions-DIL8u9Nk.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-nQYV7xpn.js → PersonalDataHub-B-jM4wt-.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-CBVod5jm.js → Pipeline-627I__H5.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-D52jW1Nt.js → Privacy-2DN2qrkW.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Bdx3BYhf.js → ProjectInit-CN4wC0sF.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-b7xr4DUa.js → ProjectSettings-K35vsh5H.js} +2 -2
- package/src/assets/web-panel/assets/Projects-B_wQadhX.js +1 -0
- package/src/assets/web-panel/assets/Providers-MckaQUhQ.js +1 -0
- package/src/assets/web-panel/assets/{QrScannerModal-FQKSmc3e.js → QrScannerModal-BS3mtA_O.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CSTrhzcF.js → QuickAsk-BHp_nofL.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CxMABOUh.js → Recommend-DlmnPuZj.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-Cv2T8yRa.js → RemoteSession-tsyFbvpy.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CE1k4Vvu.js → Reputation-Ba1iWv2D.js} +1 -1
- package/src/assets/web-panel/assets/{Row-BNgNcAV_.js → Row-NnlGC--f.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-5ZiBZ3ph.js → RssFeed-CjThSIRv.js} +3 -3
- package/src/assets/web-panel/assets/{Search-mDOIF1UV.js → Search-CojUyC5x.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CtuCTC-e.js → Security-c-sxeJEc.js} +3 -3
- package/src/assets/web-panel/assets/{Services-DxFO2Zg7.js → Services-CtcKEEmF.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CKmKkH9y.js → Skeleton-CIBBXflN.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-Bmzl9qkk.js → Skills-D1XOOK9n.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-9RqaWHOC.js → Sla-hRfEp5Sn.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CGdzFIYw.js → SpeechSettings-Dexb1f_6.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CvNuCeBT.js → SyncSettings-BJsSFGYy.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-B_sLdcnw.js → Tasks-CfSZ8A1L.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-D9_YijdK.js → Templates-idDf47W-.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-Cuc7BUSP.js → Tenant-C-UsX6w6.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-6VYRnJgd.js → Terminal-BgJtX5n1.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-GtK8xd8Y.js → TimelineRenderer-DP0BLKTl.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BZ94Lt5W.js → Tokens-LwCzqETp.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DEcZgDaX.js → Trigger-D_9tVch0.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CC3z9THe.js → Trust-BH2UlEWh.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-Cvtw8cV3.js → UkeySign-CIpD0nYb.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CL4e6Bn3.js → VideoEditing-9ShhYxK_.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-ycEemP27.js → Wallet-C7afGNIG.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DgZ4X_aw.js → WebAuthn-bUKn6i5R.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-D0qMhSv7.js → WorkflowEditor-CxgQaGyp.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DsAg01Zn.js → chat-DMVkmJwx.js} +1 -1
- package/src/assets/web-panel/assets/{colors-B0oqAhrQ.js → colors-CDzQh5nS.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CmX0Rabz.js → compact-item-C51kEF4D.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-Be4cn1Ub.js → createContext-BTk9bXBn.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CdwPwYj8.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-D5v12kVq.js → hasIn-CMje33rb.js} +1 -1
- package/src/assets/web-panel/assets/{index-CyyGTMKd.js → index-0Auljb10.js} +1 -1
- package/src/assets/web-panel/assets/{index-frSojpHj.js → index-B0-zMtKA.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3mUHuVc.js → index-BAfY7m7i.js} +1 -1
- package/src/assets/web-panel/assets/{index-BA4oulaP.js → index-BBz9LIMS.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1YGjOWC.js → index-BCRvG9lq.js} +1 -1
- package/src/assets/web-panel/assets/index-BEeOdfEe.js +1 -0
- package/src/assets/web-panel/assets/index-BTe0TOu6.js +1 -0
- package/src/assets/web-panel/assets/{index-BBvyHguy.js → index-BTlsE8Df.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGiIyTZw.js → index-BVePe_Rf.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBREnn1V.js → index-BW4F5YDF.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0BSkxYQ.js → index-BXQBPLyE.js} +1 -1
- package/src/assets/web-panel/assets/{index-0eWLkQKT.js → index-BeEaAEIp.js} +1 -1
- package/src/assets/web-panel/assets/{index-D5YVopyr.js → index-BtlEqHLP.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKmsS2jj.js → index-BzCkNylB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaE0Ui07.js → index-C1oZQmAA.js} +1 -1
- package/src/assets/web-panel/assets/{index-8WvYGnON.js → index-C1oZReaY.js} +1 -1
- package/src/assets/web-panel/assets/{index-_JjRLSD0.js → index-C4rQCS8T.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKUBj36_.js → index-C5IWTMVl.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoAIAZ5C.js → index-CCLj9hcD.js} +1 -1
- package/src/assets/web-panel/assets/{index-DikJ0Qqe.js → index-CDLkNkLj.js} +1 -1
- package/src/assets/web-panel/assets/{index-Hfe_dEGx.js → index-Cd0hmPxD.js} +1 -1
- package/src/assets/web-panel/assets/{index-jK3ax6Z6.js → index-CfC-piWZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DyUaFWEk.js → index-CtM8OT_7.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqltWojQ.js → index-CvSeVzqe.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dn2dSdXx.js → index-CwGPwjUu.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRIqLy1m.js → index-CypsfcEq.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXGSbelg.js → index-D9rC-fGW.js} +3 -3
- package/src/assets/web-panel/assets/{index-CZpfzcuy.js → index-DJfnCuMq.js} +1 -1
- package/src/assets/web-panel/assets/{index-5LGY0JbG.js → index-DRG-aqOs.js} +1 -1
- package/src/assets/web-panel/assets/{index-CyWBir7N.js → index-DRLQyp2A.js} +1 -1
- package/src/assets/web-panel/assets/{index-DQRsQen0.js → index-DgivOKHA.js} +1 -1
- package/src/assets/web-panel/assets/{index-7VKDn-t1.js → index-DpcrFxMI.js} +1 -1
- package/src/assets/web-panel/assets/{index-q_fQ94SE.js → index-DqU9njjT.js} +1 -1
- package/src/assets/web-panel/assets/{index-301ogenv.js → index-Ld_4mFmC.js} +1 -1
- package/src/assets/web-panel/assets/{index-CZ2wUEgr.js → index-OS-vfbTA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqoCZ5vR.js → index-h31MwrME.js} +1 -1
- package/src/assets/web-panel/assets/{index-DM724NgU.js → index-hg_emBsi.js} +1 -1
- package/src/assets/web-panel/assets/{index-qMuZ-NRR.js → index-togc_uYn.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ckrk3pIV.js → index-x53Vf7la.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-D4VdhJ9_.js → initDefaultProps-vrA8K-ac.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CZJA0MQW.js → motion-ByEzwIAl.js} +1 -1
- package/src/assets/web-panel/assets/{move-BqEs7Sb1.js → move-Ck4AWjw-.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BpHEtsu1.js → mtc-parser-6p0l9SrB.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BW94tY9W.js → omit-B1CUPQ6R.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BnBjPWP9.js → pickAttrs-B1STg1iW.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-uUkjQ_hu.js → placementArrow-DTTllkBV.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-B-klUqXK.js → responsiveObserve-AIrrCfBb.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CvY1cNAs.js → slide-C_fPMjdR.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Ix5sEDT2.js → statusUtils-D5ow2yIP.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-C8qcXOne.js → styleChecker-Dy3hEA--.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-KNF6Kj8f.js → useFlexGapSupport-WfzcfSh4.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DGJr345n.js → useFs-CKSejfas.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BboN99rI.js → usePersonalDataHub-od3zpPBN.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-Bgt8Vgte.js → vnode-DRD_2DFA.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BhJtErgt.js → zoom-DmNrEtgf.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +52 -6
- package/src/commands/cowork.js +24 -12
- package/src/commands/routine.js +40 -10
- package/src/data/changelog.json +13 -0
- package/src/lib/agent-schedule-store.js +197 -4
- package/src/lib/cowork-cron.js +243 -25
- package/src/lib/micro-compact.js +11 -5
- package/src/lib/process-execution-broker/index.js +14 -3
- package/src/lib/routine-store.js +76 -3
- package/src/lib/scheduler-kernel/agenda-adapter.js +542 -0
- package/src/lib/scheduler-kernel/cowork-cron-adapter.js +774 -0
- package/src/lib/scheduler-kernel/routine-adapter.js +517 -0
- package/src/lib/scheduler-kernel/runtime.js +527 -0
- package/src/lib/scheduler-kernel/store.js +112 -2
- package/src/lib/session-message-provenance.js +4 -1
- package/src/repl/agent-repl.js +24 -5
- package/src/runtime/agent-core.js +36 -16
- package/src/assets/web-panel/assets/MarkdownRenderer-CLmCbHx6.js +0 -1
- package/src/assets/web-panel/assets/Projects-DhfifCKx.js +0 -1
- package/src/assets/web-panel/assets/Providers-Dy_PgDMr.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,542 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { effectiveFireAt } from "../schedule-planner.js";
|
|
3
|
+
import {
|
|
4
|
+
SchedulerKernelError,
|
|
5
|
+
canonicalJson,
|
|
6
|
+
normalizeEpochMs,
|
|
7
|
+
normalizeIdentifier,
|
|
8
|
+
normalizeJson,
|
|
9
|
+
} from "./contract.js";
|
|
10
|
+
import { SchedulerRuntime } from "./runtime.js";
|
|
11
|
+
|
|
12
|
+
export const AGENDA_SCHEDULER_KIND = "agenda";
|
|
13
|
+
export const AGENDA_SCHEDULER_CAPABILITY = "agent.execute";
|
|
14
|
+
export const AGENDA_SCHEDULER_ENTRY_KINDS = Object.freeze(["wakeup", "cron"]);
|
|
15
|
+
export const AGENDA_SCHEDULER_RETRY_DELAY_MS = 60_000;
|
|
16
|
+
|
|
17
|
+
function agendaError(
|
|
18
|
+
code,
|
|
19
|
+
message,
|
|
20
|
+
details = undefined,
|
|
21
|
+
cause = undefined,
|
|
22
|
+
{ retryable = false, retryAt } = {},
|
|
23
|
+
) {
|
|
24
|
+
const error = new SchedulerKernelError(
|
|
25
|
+
code,
|
|
26
|
+
message,
|
|
27
|
+
details,
|
|
28
|
+
cause ? { cause } : undefined,
|
|
29
|
+
);
|
|
30
|
+
error.retryable = retryable;
|
|
31
|
+
if (retryAt !== undefined) error.retryAt = retryAt;
|
|
32
|
+
return error;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function safeFailure(error) {
|
|
36
|
+
return {
|
|
37
|
+
code:
|
|
38
|
+
typeof error?.code === "string" && error.code
|
|
39
|
+
? error.code.slice(0, 128)
|
|
40
|
+
: "AGENDA_AGENT_EXECUTION_FAILED",
|
|
41
|
+
message:
|
|
42
|
+
typeof error?.message === "string" && error.message
|
|
43
|
+
? error.message.slice(0, 2_000)
|
|
44
|
+
: "Agenda agent execution failed",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function validateEntrySnapshot(entry) {
|
|
49
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
50
|
+
throw agendaError(
|
|
51
|
+
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
52
|
+
"Agenda scheduler adapter requires an entry object",
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const plain = { ...entry };
|
|
56
|
+
delete plain.executionLease;
|
|
57
|
+
delete plain.schedulerExecution;
|
|
58
|
+
const snapshot = normalizeJson(plain, "agendaEntrySnapshot");
|
|
59
|
+
snapshot.id = normalizeIdentifier(snapshot.id, "agenda.entry.id");
|
|
60
|
+
if (!AGENDA_SCHEDULER_ENTRY_KINDS.includes(snapshot.kind)) {
|
|
61
|
+
throw agendaError(
|
|
62
|
+
"AGENDA_SCHEDULER_KIND_UNSUPPORTED",
|
|
63
|
+
`Agenda scheduler does not support entry kind: ${snapshot.kind}`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
if (typeof snapshot.prompt !== "string" || snapshot.prompt.length === 0) {
|
|
67
|
+
throw agendaError(
|
|
68
|
+
"AGENDA_SCHEDULER_ENTRY_INVALID",
|
|
69
|
+
`Agenda prompt is missing: ${snapshot.id}`,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const schedulable =
|
|
73
|
+
(snapshot.kind === "wakeup" && snapshot.status === "pending") ||
|
|
74
|
+
(snapshot.kind === "cron" && snapshot.status === "active");
|
|
75
|
+
if (!schedulable) {
|
|
76
|
+
throw agendaError(
|
|
77
|
+
"AGENDA_SCHEDULER_ENTRY_TERMINAL",
|
|
78
|
+
`Agenda entry is not schedulable: ${snapshot.id}`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
normalizeEpochMs(Number(snapshot.createdAt), "agenda.entry.createdAt");
|
|
82
|
+
const fireAt = effectiveFireAt(snapshot);
|
|
83
|
+
if (fireAt == null) {
|
|
84
|
+
throw agendaError(
|
|
85
|
+
"AGENDA_SCHEDULER_FIRE_TIME_INVALID",
|
|
86
|
+
`Agenda entry has no fire time: ${snapshot.id}`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
normalizeEpochMs(Number(fireAt), "agenda.entry.fireAt");
|
|
90
|
+
return snapshot;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function agendaEntrySnapshot(entry) {
|
|
94
|
+
return validateEntrySnapshot(entry);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function agendaEntrySnapshotDigest(entry) {
|
|
98
|
+
return createHash("sha256")
|
|
99
|
+
.update("chainlesschain.scheduler.agenda-entry.v1\0", "utf8")
|
|
100
|
+
.update(
|
|
101
|
+
canonicalJson(agendaEntrySnapshot(entry), "agendaEntrySnapshot"),
|
|
102
|
+
"utf8",
|
|
103
|
+
)
|
|
104
|
+
.digest("hex");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function agendaSchedulerJobId(entryId) {
|
|
108
|
+
return `agenda:${normalizeIdentifier(entryId, "agendaEntryId")}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function buildAgendaSchedulerJob(entry) {
|
|
112
|
+
const snapshot = agendaEntrySnapshot(entry);
|
|
113
|
+
return {
|
|
114
|
+
id: agendaSchedulerJobId(snapshot.id),
|
|
115
|
+
kind: AGENDA_SCHEDULER_KIND,
|
|
116
|
+
trigger: {
|
|
117
|
+
source: "agent-schedule-store",
|
|
118
|
+
scheduleKind: snapshot.kind,
|
|
119
|
+
expression:
|
|
120
|
+
snapshot.kind === "cron" ? snapshot.cron : String(snapshot.dueAt),
|
|
121
|
+
},
|
|
122
|
+
payload: {
|
|
123
|
+
entry: snapshot,
|
|
124
|
+
snapshotDigest: agendaEntrySnapshotDigest(snapshot),
|
|
125
|
+
},
|
|
126
|
+
authority: {
|
|
127
|
+
schemaVersion: 1,
|
|
128
|
+
principal: { type: "agenda", id: snapshot.id },
|
|
129
|
+
tenantId: null,
|
|
130
|
+
workspaceId: null,
|
|
131
|
+
requestedCapabilities: [AGENDA_SCHEDULER_CAPABILITY],
|
|
132
|
+
authorizationRefs: {
|
|
133
|
+
decisionId: null,
|
|
134
|
+
policyRevision: null,
|
|
135
|
+
grantIds: [],
|
|
136
|
+
approvalIds: [],
|
|
137
|
+
delegationIds: [],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
enabled: true,
|
|
141
|
+
maxAttempts: 3,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function comparableJob(job) {
|
|
146
|
+
return {
|
|
147
|
+
kind: job.kind,
|
|
148
|
+
trigger: job.trigger,
|
|
149
|
+
payload: job.payload,
|
|
150
|
+
authority: job.authority,
|
|
151
|
+
enabled: job.enabled,
|
|
152
|
+
maxAttempts: job.maxAttempts,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function sameJob(current, desired) {
|
|
157
|
+
return (
|
|
158
|
+
current !== null &&
|
|
159
|
+
canonicalJson(comparableJob(current), "currentAgendaJob") ===
|
|
160
|
+
canonicalJson(comparableJob(desired), "desiredAgendaJob")
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function syncAgendaSchedulerJob(schedulerStore, entry) {
|
|
165
|
+
const desired = buildAgendaSchedulerJob(entry);
|
|
166
|
+
let current = schedulerStore.getJob(desired.id);
|
|
167
|
+
if (!current) {
|
|
168
|
+
try {
|
|
169
|
+
return schedulerStore.createJob(desired);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
if (error?.code !== "SCHEDULER_CONFLICT") throw error;
|
|
172
|
+
current = schedulerStore.getJob(desired.id);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (sameJob(current, desired)) return current;
|
|
176
|
+
try {
|
|
177
|
+
return schedulerStore.updateJob(current.id, current.revision, {
|
|
178
|
+
kind: desired.kind,
|
|
179
|
+
trigger: desired.trigger,
|
|
180
|
+
payload: desired.payload,
|
|
181
|
+
authority: desired.authority,
|
|
182
|
+
enabled: desired.enabled,
|
|
183
|
+
maxAttempts: desired.maxAttempts,
|
|
184
|
+
});
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (error?.code !== "SCHEDULER_REVISION_CONFLICT") throw error;
|
|
187
|
+
const latest = schedulerStore.getJob(desired.id);
|
|
188
|
+
if (sameJob(latest, desired)) return latest;
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function enqueueAgendaEntry(schedulerStore, entry) {
|
|
194
|
+
const snapshot = agendaEntrySnapshot(entry);
|
|
195
|
+
const job = syncAgendaSchedulerJob(schedulerStore, snapshot);
|
|
196
|
+
const scheduledFor = normalizeEpochMs(
|
|
197
|
+
Number(effectiveFireAt(snapshot)),
|
|
198
|
+
"agenda.entry.fireAt",
|
|
199
|
+
);
|
|
200
|
+
return schedulerStore.enqueueOccurrence({
|
|
201
|
+
jobId: job.id,
|
|
202
|
+
scheduledFor,
|
|
203
|
+
triggerKey: `${snapshot.kind}:${scheduledFor}`,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function authorizeAgendaOccurrence({ job, occurrence }) {
|
|
208
|
+
try {
|
|
209
|
+
const payload = occurrence?.payload;
|
|
210
|
+
const entry = payload?.entry;
|
|
211
|
+
const authority = occurrence?.authority;
|
|
212
|
+
const expectedDigest = agendaEntrySnapshotDigest(entry);
|
|
213
|
+
const allowed =
|
|
214
|
+
job?.kind === AGENDA_SCHEDULER_KIND &&
|
|
215
|
+
authority?.principal?.type === "agenda" &&
|
|
216
|
+
authority?.principal?.id === entry.id &&
|
|
217
|
+
Array.isArray(authority?.requestedCapabilities) &&
|
|
218
|
+
authority.requestedCapabilities.length === 1 &&
|
|
219
|
+
authority.requestedCapabilities[0] === AGENDA_SCHEDULER_CAPABILITY &&
|
|
220
|
+
payload.snapshotDigest === expectedDigest;
|
|
221
|
+
return {
|
|
222
|
+
allowed,
|
|
223
|
+
reason: allowed ? "agenda_snapshot_bound" : "agenda_authority_mismatch",
|
|
224
|
+
};
|
|
225
|
+
} catch {
|
|
226
|
+
return { allowed: false, reason: "agenda_authority_malformed" };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function recoveredResult(entry, evidence) {
|
|
231
|
+
const result =
|
|
232
|
+
evidence.result && typeof evidence.result === "object"
|
|
233
|
+
? evidence.result
|
|
234
|
+
: { id: entry.id, kind: entry.kind, action: "fired" };
|
|
235
|
+
return { ...result, recovered: true };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function matchingExecutionEvidence(
|
|
239
|
+
entry,
|
|
240
|
+
{ occurrenceId, snapshotDigest, attempt, status },
|
|
241
|
+
) {
|
|
242
|
+
const evidence = entry?.schedulerExecution;
|
|
243
|
+
return (
|
|
244
|
+
evidence?.occurrenceId === occurrenceId &&
|
|
245
|
+
evidence.snapshotDigest === snapshotDigest &&
|
|
246
|
+
evidence.attempt === attempt &&
|
|
247
|
+
evidence.status === status
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function createAgendaSchedulerAdapter({
|
|
252
|
+
agendaStore,
|
|
253
|
+
runAgent,
|
|
254
|
+
now = Date.now,
|
|
255
|
+
} = {}) {
|
|
256
|
+
if (
|
|
257
|
+
!agendaStore ||
|
|
258
|
+
typeof agendaStore.get !== "function" ||
|
|
259
|
+
typeof agendaStore.bindSchedulerExecution !== "function" ||
|
|
260
|
+
typeof agendaStore.completeSchedulerExecution !== "function"
|
|
261
|
+
) {
|
|
262
|
+
throw agendaError(
|
|
263
|
+
"AGENDA_SCHEDULER_STORE_REQUIRED",
|
|
264
|
+
"Agenda scheduler adapter requires a compatible AgentScheduleStore",
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
if (typeof runAgent !== "function") {
|
|
268
|
+
throw agendaError(
|
|
269
|
+
"AGENDA_SCHEDULER_RUNNER_REQUIRED",
|
|
270
|
+
"Agenda scheduler adapter requires a runAgent function",
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
if (typeof now !== "function") {
|
|
274
|
+
throw agendaError(
|
|
275
|
+
"AGENDA_SCHEDULER_CLOCK_REQUIRED",
|
|
276
|
+
"Agenda scheduler adapter requires a clock function",
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
kind: AGENDA_SCHEDULER_KIND,
|
|
281
|
+
async execute({ occurrence }) {
|
|
282
|
+
const payload = occurrence.payload;
|
|
283
|
+
const expected = agendaEntrySnapshot(payload?.entry);
|
|
284
|
+
const expectedDigest = agendaEntrySnapshotDigest(expected);
|
|
285
|
+
if (payload.snapshotDigest !== expectedDigest) {
|
|
286
|
+
throw agendaError(
|
|
287
|
+
"AGENDA_SCHEDULER_SNAPSHOT_INVALID",
|
|
288
|
+
`Agenda occurrence snapshot is invalid: ${occurrence.id}`,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
let current = agendaStore.get(expected.id);
|
|
292
|
+
if (!current) {
|
|
293
|
+
throw agendaError(
|
|
294
|
+
"AGENDA_SCHEDULER_ENTRY_NOT_FOUND",
|
|
295
|
+
`Agenda entry disappeared before execution: ${expected.id}`,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
const prior = current.schedulerExecution;
|
|
299
|
+
if (prior?.occurrenceId === occurrence.id) {
|
|
300
|
+
if (prior.snapshotDigest !== expectedDigest) {
|
|
301
|
+
throw agendaError(
|
|
302
|
+
"AGENDA_SCHEDULER_BINDING_MISMATCH",
|
|
303
|
+
`Agenda execution evidence has a different snapshot: ${expected.id}`,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
if (prior.status === "succeeded") {
|
|
307
|
+
return recoveredResult(expected, prior);
|
|
308
|
+
}
|
|
309
|
+
if (prior.status === "running") {
|
|
310
|
+
throw agendaError(
|
|
311
|
+
"AGENDA_SCHEDULER_OUTCOME_UNKNOWN",
|
|
312
|
+
`Agenda agent outcome is unknown; refusing duplicate execution: ${expected.id}`,
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
} else if (prior?.status === "running") {
|
|
316
|
+
throw agendaError(
|
|
317
|
+
"AGENDA_SCHEDULER_EXECUTION_CONFLICT",
|
|
318
|
+
`Agenda entry has another unresolved scheduler execution: ${expected.id}`,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
if (agendaEntrySnapshotDigest(current) !== expectedDigest) {
|
|
322
|
+
throw agendaError(
|
|
323
|
+
"AGENDA_SCHEDULER_STALE_SNAPSHOT",
|
|
324
|
+
`Agenda entry changed after occurrence enqueue: ${expected.id}`,
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
current = agendaStore.bindSchedulerExecution(expected.id, {
|
|
329
|
+
occurrenceId: occurrence.id,
|
|
330
|
+
snapshotDigest: expectedDigest,
|
|
331
|
+
attempt: occurrence.attempt,
|
|
332
|
+
atMs: now(),
|
|
333
|
+
});
|
|
334
|
+
if (agendaEntrySnapshotDigest(current) !== expectedDigest) {
|
|
335
|
+
const stale = agendaError(
|
|
336
|
+
"AGENDA_SCHEDULER_STALE_SNAPSHOT",
|
|
337
|
+
`Agenda entry changed while binding occurrence: ${expected.id}`,
|
|
338
|
+
);
|
|
339
|
+
agendaStore.completeSchedulerExecution(expected.id, {
|
|
340
|
+
occurrenceId: occurrence.id,
|
|
341
|
+
snapshotDigest: expectedDigest,
|
|
342
|
+
attempt: occurrence.attempt,
|
|
343
|
+
outcome: "failed",
|
|
344
|
+
error: safeFailure(stale),
|
|
345
|
+
atMs: now(),
|
|
346
|
+
});
|
|
347
|
+
throw stale;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
await runAgent({
|
|
352
|
+
entry: expected,
|
|
353
|
+
prompt: expected.prompt,
|
|
354
|
+
runPolicy: expected.runPolicy,
|
|
355
|
+
occurrenceId: occurrence.id,
|
|
356
|
+
attempt: occurrence.attempt,
|
|
357
|
+
});
|
|
358
|
+
} catch (error) {
|
|
359
|
+
let failurePersisted = false;
|
|
360
|
+
try {
|
|
361
|
+
agendaStore.completeSchedulerExecution(expected.id, {
|
|
362
|
+
occurrenceId: occurrence.id,
|
|
363
|
+
snapshotDigest: expectedDigest,
|
|
364
|
+
attempt: occurrence.attempt,
|
|
365
|
+
outcome: "failed",
|
|
366
|
+
error: safeFailure(error),
|
|
367
|
+
atMs: now(),
|
|
368
|
+
});
|
|
369
|
+
failurePersisted = true;
|
|
370
|
+
} catch (completionError) {
|
|
371
|
+
failurePersisted = matchingExecutionEvidence(
|
|
372
|
+
agendaStore.get(expected.id),
|
|
373
|
+
{
|
|
374
|
+
occurrenceId: occurrence.id,
|
|
375
|
+
snapshotDigest: expectedDigest,
|
|
376
|
+
attempt: occurrence.attempt,
|
|
377
|
+
status: "failed",
|
|
378
|
+
},
|
|
379
|
+
);
|
|
380
|
+
if (!failurePersisted) {
|
|
381
|
+
throw agendaError(
|
|
382
|
+
"AGENDA_SCHEDULER_FAILURE_PERSIST_FAILED",
|
|
383
|
+
`Agenda execution failed and its evidence could not be persisted: ${expected.id}`,
|
|
384
|
+
undefined,
|
|
385
|
+
completionError,
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
throw agendaError(
|
|
390
|
+
typeof error?.code === "string"
|
|
391
|
+
? error.code
|
|
392
|
+
: "AGENDA_AGENT_EXECUTION_FAILED",
|
|
393
|
+
typeof error?.message === "string"
|
|
394
|
+
? error.message
|
|
395
|
+
: `Agenda agent execution failed: ${expected.id}`,
|
|
396
|
+
undefined,
|
|
397
|
+
error,
|
|
398
|
+
{
|
|
399
|
+
retryable: true,
|
|
400
|
+
retryAt: normalizeEpochMs(
|
|
401
|
+
Number(now()) + AGENDA_SCHEDULER_RETRY_DELAY_MS,
|
|
402
|
+
"agenda.retryAt",
|
|
403
|
+
),
|
|
404
|
+
},
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const action = {
|
|
409
|
+
id: expected.id,
|
|
410
|
+
kind: expected.kind,
|
|
411
|
+
action: "fired",
|
|
412
|
+
};
|
|
413
|
+
try {
|
|
414
|
+
agendaStore.completeSchedulerExecution(expected.id, {
|
|
415
|
+
occurrenceId: occurrence.id,
|
|
416
|
+
snapshotDigest: expectedDigest,
|
|
417
|
+
attempt: occurrence.attempt,
|
|
418
|
+
outcome: "succeeded",
|
|
419
|
+
result: action,
|
|
420
|
+
atMs: now(),
|
|
421
|
+
});
|
|
422
|
+
return action;
|
|
423
|
+
} catch (completionError) {
|
|
424
|
+
const persisted = agendaStore.get(expected.id);
|
|
425
|
+
if (
|
|
426
|
+
matchingExecutionEvidence(persisted, {
|
|
427
|
+
occurrenceId: occurrence.id,
|
|
428
|
+
snapshotDigest: expectedDigest,
|
|
429
|
+
attempt: occurrence.attempt,
|
|
430
|
+
status: "succeeded",
|
|
431
|
+
})
|
|
432
|
+
) {
|
|
433
|
+
return recoveredResult(expected, persisted.schedulerExecution);
|
|
434
|
+
}
|
|
435
|
+
throw agendaError(
|
|
436
|
+
"AGENDA_SCHEDULER_OUTCOME_UNKNOWN",
|
|
437
|
+
`Agenda agent finished but its durable outcome is unknown; refusing a retry: ${expected.id}`,
|
|
438
|
+
undefined,
|
|
439
|
+
completionError,
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
},
|
|
443
|
+
classifyError(error) {
|
|
444
|
+
return {
|
|
445
|
+
retryable: error?.retryable === true,
|
|
446
|
+
...(error?.retryAt === undefined ? {} : { retryAt: error.retryAt }),
|
|
447
|
+
};
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export class AgendaSchedulerBridge {
|
|
453
|
+
constructor({
|
|
454
|
+
agendaStore,
|
|
455
|
+
schedulerStore,
|
|
456
|
+
runAgent,
|
|
457
|
+
now = Date.now,
|
|
458
|
+
ownerId,
|
|
459
|
+
leaseMs,
|
|
460
|
+
renewIntervalMs,
|
|
461
|
+
} = {}) {
|
|
462
|
+
if (typeof now !== "function") {
|
|
463
|
+
throw agendaError(
|
|
464
|
+
"AGENDA_SCHEDULER_CLOCK_REQUIRED",
|
|
465
|
+
"Agenda scheduler bridge requires a clock function",
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
this.agendaStore = agendaStore;
|
|
469
|
+
this.schedulerStore = schedulerStore;
|
|
470
|
+
this.now = now;
|
|
471
|
+
this.runtime = new SchedulerRuntime({
|
|
472
|
+
store: schedulerStore,
|
|
473
|
+
adapters: [createAgendaSchedulerAdapter({ agendaStore, runAgent, now })],
|
|
474
|
+
authorize: authorizeAgendaOccurrence,
|
|
475
|
+
...(ownerId === undefined ? {} : { ownerId }),
|
|
476
|
+
...(leaseMs === undefined ? {} : { leaseMs }),
|
|
477
|
+
...(renewIntervalMs === undefined ? {} : { renewIntervalMs }),
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async runDue({ signal } = {}) {
|
|
482
|
+
const results = [];
|
|
483
|
+
const observedOccurrences = new Set();
|
|
484
|
+
const recovered = await this.runtime.runUntilIdle({
|
|
485
|
+
limit: 10_000,
|
|
486
|
+
signal,
|
|
487
|
+
jobKind: AGENDA_SCHEDULER_KIND,
|
|
488
|
+
});
|
|
489
|
+
for (const result of recovered.results) {
|
|
490
|
+
const occurrence = result.occurrence;
|
|
491
|
+
const entry = occurrence?.payload?.entry;
|
|
492
|
+
if (!entry?.id) continue;
|
|
493
|
+
observedOccurrences.add(occurrence.id);
|
|
494
|
+
results.push({
|
|
495
|
+
entry: entry.id,
|
|
496
|
+
kind: entry.kind,
|
|
497
|
+
occurrence: occurrence.id,
|
|
498
|
+
recovered: true,
|
|
499
|
+
result,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
if (signal?.aborted) return results;
|
|
503
|
+
|
|
504
|
+
const now = normalizeEpochMs(Number(this.now()), "now");
|
|
505
|
+
const due = [
|
|
506
|
+
...this.agendaStore.due("wakeup", now),
|
|
507
|
+
...this.agendaStore.due("cron", now),
|
|
508
|
+
];
|
|
509
|
+
for (const entry of due) {
|
|
510
|
+
const occurrence = enqueueAgendaEntry(this.schedulerStore, entry);
|
|
511
|
+
if (observedOccurrences.has(occurrence.id)) continue;
|
|
512
|
+
const result = await this.runtime.runOccurrence(occurrence.id, {
|
|
513
|
+
signal,
|
|
514
|
+
});
|
|
515
|
+
results.push({
|
|
516
|
+
entry: entry.id,
|
|
517
|
+
kind: entry.kind,
|
|
518
|
+
occurrence: occurrence.id,
|
|
519
|
+
deduplicated: occurrence.deduplicated,
|
|
520
|
+
result,
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
return results;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
export function agendaBridgeAction(entry) {
|
|
528
|
+
const result = entry?.result;
|
|
529
|
+
if (result?.status === "succeeded") {
|
|
530
|
+
return result.result;
|
|
531
|
+
}
|
|
532
|
+
if (result?.status === "busy" || result?.status === "idle") return null;
|
|
533
|
+
return {
|
|
534
|
+
id: entry.entry,
|
|
535
|
+
kind: entry.kind,
|
|
536
|
+
action: "error",
|
|
537
|
+
error: result?.error?.message || `agenda occurrence ${result?.status}`,
|
|
538
|
+
...(typeof result?.error?.code === "string"
|
|
539
|
+
? { errorCode: result.error.code }
|
|
540
|
+
: {}),
|
|
541
|
+
};
|
|
542
|
+
}
|