agents 0.22.0 → 0.23.0
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/README.md +20 -1
- package/dist/{agent-routing-CnkaHb-v.d.ts → agent-routing-DE5zmCQ8.d.ts} +1038 -394
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-tool-types.d.ts +26 -26
- package/dist/{agent-tools-C0XJqWzB.d.ts → agent-tools-DtXMTDGM.d.ts} +7 -7
- package/dist/agent-tools.d.ts +21 -21
- package/dist/browser/ai.js +4 -1
- package/dist/browser/ai.js.map +1 -1
- package/dist/browser/tanstack-ai.js.map +1 -1
- package/dist/callable-decorator-DP__HhBA.d.ts +72 -0
- package/dist/callable-decorator.d.ts +18 -0
- package/dist/callable-decorator.js +71 -0
- package/dist/callable-decorator.js.map +1 -0
- package/dist/capability-BjSKYpzg.js.map +1 -1
- package/dist/capability-runner-Be_-PLR1.d.ts +459 -0
- package/dist/channel-Bnm4S7T2.d.ts +491 -0
- package/dist/channels/ai-sdk.d.ts +46 -0
- package/dist/channels/ai-sdk.js +120 -0
- package/dist/channels/ai-sdk.js.map +1 -0
- package/dist/channels/email.d.ts +95 -0
- package/dist/channels/email.js +323 -0
- package/dist/channels/email.js.map +1 -0
- package/dist/channels/index.d.ts +233 -0
- package/dist/channels/index.js +608 -0
- package/dist/channels/index.js.map +1 -0
- package/dist/channels/slack.d.ts +140 -0
- package/dist/channels/slack.js +614 -0
- package/dist/channels/slack.js.map +1 -0
- package/dist/channels/tanstack-ai.d.ts +39 -0
- package/dist/channels/tanstack-ai.js +17 -0
- package/dist/channels/tanstack-ai.js.map +1 -0
- package/dist/channels/telegram.d.ts +106 -0
- package/dist/channels/telegram.js +427 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/channels/voice.d.ts +45 -0
- package/dist/channels/voice.js +122 -0
- package/dist/channels/voice.js.map +1 -0
- package/dist/chat/index.d.ts +2325 -2019
- package/dist/chat/index.js +888 -518
- package/dist/chat/index.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +7 -7
- package/dist/chat-sdk/index.js +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/context/index.d.ts +216 -0
- package/dist/context/index.js +454 -0
- package/dist/context/index.js.map +1 -0
- package/dist/{current-agent-CuMErtly.d.ts → current-agent-Da_C9a3b.d.ts} +90 -107
- package/dist/current-agent-DhoDkSnH.js.map +1 -1
- package/dist/{diagnostics-CaBjfz4J.js → diagnostics-BzvaX2UT.js} +5 -1
- package/dist/diagnostics-BzvaX2UT.js.map +1 -0
- package/dist/diagnostics-C4jcz3VK.js +360 -0
- package/dist/diagnostics-C4jcz3VK.js.map +1 -0
- package/dist/index-BB0kqhIz.d.ts +101 -0
- package/dist/index-BVVgDSdq.d.ts +1 -0
- package/dist/index-XDkuQ7zm.d.ts +89 -0
- package/dist/{index-DcSAZKsB.d.ts → index-YSKgfgg9.d.ts} +3 -1
- package/dist/index.d.ts +91 -82
- package/dist/index.js +3 -2
- package/dist/ingress-BfetZbMO.js +83 -0
- package/dist/ingress-BfetZbMO.js.map +1 -0
- package/dist/internal-CYlgHl1l.js +59 -0
- package/dist/internal-CYlgHl1l.js.map +1 -0
- package/dist/internal_context.d.ts +1 -1
- package/dist/lifecycle/index.d.ts +35 -17
- package/dist/lifecycle/index.js +1 -1
- package/dist/lifecycle-CMRGjZdw.js +1299 -0
- package/dist/lifecycle-CMRGjZdw.js.map +1 -0
- package/dist/mcp/client/index.d.ts +20 -20
- package/dist/mcp/index.d.ts +35 -35
- package/dist/mcp/index.js +1 -1
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +1 -1
- package/dist/react.d.ts +4 -4
- package/dist/{retries-CAvxtG9d.d.ts → retries-D9Ds-1lz.d.ts} +17 -6
- package/dist/retries.d.ts +8 -6
- package/dist/retries.js +13 -1
- package/dist/retries.js.map +1 -1
- package/dist/routing/index.d.ts +137 -0
- package/dist/routing/index.js +244 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/sanitize-D9TujEK8.js +79 -0
- package/dist/sanitize-D9TujEK8.js.map +1 -0
- package/dist/scheduler-DD9NdYbF.js +665 -0
- package/dist/scheduler-DD9NdYbF.js.map +1 -0
- package/dist/{scheduler-DQoTGoAW.d.ts → scheduler-Dwh85ZGl.d.ts} +21 -22
- package/dist/schedules/index.d.ts +1 -1
- package/dist/schedules/index.js +1 -1
- package/dist/sentence-chunker-BAidJ4DA.d.ts +68 -0
- package/dist/serializable.d.ts +1 -1
- package/dist/sessions/index.d.ts +441 -0
- package/dist/sessions/index.js +2063 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/skills/index.d.ts +99 -0
- package/dist/skills/index.js +254 -5
- package/dist/skills/index.js.map +1 -1
- package/dist/{src-5W6JNKVb.js → src-DlSHshb2.js} +1460 -1110
- package/dist/src-DlSHshb2.js.map +1 -0
- package/dist/streams/index.d.ts +120 -0
- package/dist/streams/index.js +107 -0
- package/dist/streams/index.js.map +1 -0
- package/dist/streams-D6tJ0NN9.d.ts +370 -0
- package/dist/streams-DZKgAj9b.js +709 -0
- package/dist/streams-DZKgAj9b.js.map +1 -0
- package/dist/sub-routing.d.ts +12 -12
- package/dist/surface-bZZJqBka.js +17 -0
- package/dist/surface-bZZJqBka.js.map +1 -0
- package/dist/tasks/index.d.ts +64 -0
- package/dist/tasks/index.js +2 -0
- package/dist/tasks-BRJ5zgya.d.ts +517 -0
- package/dist/tasks-ylZgBjhj.js +1656 -0
- package/dist/tasks-ylZgBjhj.js.map +1 -0
- package/dist/text-segment-joiner-BtAFQSA_.js +57 -0
- package/dist/text-segment-joiner-BtAFQSA_.js.map +1 -0
- package/dist/text-stream-CpdiKrJB.js +272 -0
- package/dist/text-stream-CpdiKrJB.js.map +1 -0
- package/dist/tokens-nHAKcN6M.js +52 -0
- package/dist/tokens-nHAKcN6M.js.map +1 -0
- package/dist/tool-schema-CBjGPrsQ.js +31 -0
- package/dist/tool-schema-CBjGPrsQ.js.map +1 -0
- package/dist/types-B7LojTe4.d.ts +202 -0
- package/dist/types-_Faxb570.d.ts +439 -0
- package/dist/voice/client.d.ts +226 -0
- package/dist/voice/client.js +932 -0
- package/dist/voice/client.js.map +1 -0
- package/dist/voice/errors.d.ts +43 -0
- package/dist/voice/errors.js +41 -0
- package/dist/voice/errors.js.map +1 -0
- package/dist/voice/index.d.ts +271 -0
- package/dist/voice/index.js +1812 -0
- package/dist/voice/index.js.map +1 -0
- package/dist/voice/react.d.ts +167 -0
- package/dist/voice/react.js +234 -0
- package/dist/voice/react.js.map +1 -0
- package/dist/voice/sfu.d.ts +71 -0
- package/dist/voice/sfu.js +157 -0
- package/dist/voice/sfu.js.map +1 -0
- package/dist/voice/text.d.ts +6 -0
- package/dist/voice/text.js +2 -0
- package/dist/voice/types.d.ts +58 -0
- package/dist/voice/types.js +18 -0
- package/dist/voice/types.js.map +1 -0
- package/dist/voice/workers-ai.d.ts +136 -0
- package/dist/voice/workers-ai.js +568 -0
- package/dist/voice/workers-ai.js.map +1 -0
- package/dist/websockets/index.d.ts +192 -0
- package/dist/websockets/index.js +2 -0
- package/dist/websockets-DUfRHPRq.js +502 -0
- package/dist/websockets-DUfRHPRq.js.map +1 -0
- package/dist/workflow-types.d.ts +25 -25
- package/dist/workflows.d.ts +21 -21
- package/dist/workflows.js +1 -1
- package/docs/agent-class.md +2 -2
- package/docs/agent-tools.md +2 -1
- package/docs/channels.md +323 -0
- package/docs/chat-agents.md +6 -13
- package/docs/context.md +131 -0
- package/docs/index.md +15 -12
- package/docs/lifecycle.md +102 -55
- package/docs/long-running-agents.md +2 -2
- package/docs/mcp-servers.md +5 -1
- package/docs/resumable-streaming.md +1 -1
- package/docs/routing.md +105 -0
- package/docs/sessions.md +237 -871
- package/docs/streams.md +213 -0
- package/docs/sub-agents.md +184 -124
- package/docs/tasks.md +246 -0
- package/docs/voice.md +745 -0
- package/package.json +115 -13
- package/dist/capability-runner-CvHGZqUu.d.ts +0 -150
- package/dist/compaction-helpers-iiKMr2TQ.js +0 -340
- package/dist/compaction-helpers-iiKMr2TQ.js.map +0 -1
- package/dist/compaction-helpers-wUz6M3us.d.ts +0 -621
- package/dist/diagnostics-CaBjfz4J.js.map +0 -1
- package/dist/durable-object-lifecycle-D6nNQJJd.js +0 -862
- package/dist/durable-object-lifecycle-D6nNQJJd.js.map +0 -1
- package/dist/experimental/memory/session/index.d.ts +0 -671
- package/dist/experimental/memory/session/index.js +0 -2379
- package/dist/experimental/memory/session/index.js.map +0 -1
- package/dist/experimental/memory/utils/index.d.ts +0 -96
- package/dist/experimental/memory/utils/index.js +0 -79
- package/dist/experimental/memory/utils/index.js.map +0 -1
- package/dist/scheduler-CR9RHGos.js +0 -857
- package/dist/scheduler-CR9RHGos.js.map +0 -1
- package/dist/src-5W6JNKVb.js.map +0 -1
- package/dist/tool-output-truncation-CNnnGZQ3.js +0 -98
- package/dist/tool-output-truncation-CNnnGZQ3.js.map +0 -1
|
@@ -1,857 +0,0 @@
|
|
|
1
|
-
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-DZBYAB34.js";
|
|
2
|
-
import { t as LifecycleCapability } from "./capability-BjSKYpzg.js";
|
|
3
|
-
import { t as _classPrivateMethodInitSpec } from "./classPrivateMethodInitSpec-qMjJ6sHQ.js";
|
|
4
|
-
import { SqlError } from "./sql-error.js";
|
|
5
|
-
import { isDurableObjectCodeUpdateReset, isDurableObjectMemoryLimitReset, isPlatformTransientError, tryN, validateRetryOptions } from "./retries.js";
|
|
6
|
-
import { nanoid } from "nanoid";
|
|
7
|
-
import { parseCronExpression } from "cron-schedule";
|
|
8
|
-
//#region src/schedules/schedule-timing.ts
|
|
9
|
-
/**
|
|
10
|
-
* Pure timing rules for persistent schedules.
|
|
11
|
-
*
|
|
12
|
-
* A `ScheduleTiming` is the parsed "when and how it runs" half of a
|
|
13
|
-
* `Schedule` — everything except identity, callback, and payload. Parsing
|
|
14
|
-
* user-facing inputs here keeps Scheduler's storage code to one insert path.
|
|
15
|
-
*/
|
|
16
|
-
/** Longest allowed gap between interval executions: 30 days. */
|
|
17
|
-
const MAX_INTERVAL_SECONDS = 720 * 60 * 60;
|
|
18
|
-
/** True when this timing repeats and therefore deduplicates by default. */
|
|
19
|
-
function isRecurring(timing) {
|
|
20
|
-
return timing.type === "cron" || timing.type === "interval";
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Next wall-clock execution time for a cron expression.
|
|
24
|
-
*
|
|
25
|
-
* @param cron - A standard cron expression.
|
|
26
|
-
* @param nowMs - Current epoch time in milliseconds.
|
|
27
|
-
* @returns The next execution epoch time in milliseconds.
|
|
28
|
-
* @throws For an unparseable cron expression.
|
|
29
|
-
*/
|
|
30
|
-
function nextCronTimeMs(cron, nowMs) {
|
|
31
|
-
return parseCronExpression(cron).getNextDate(new Date(nowMs)).getTime();
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Parse a user-facing `when` into schedule timing.
|
|
35
|
-
*
|
|
36
|
-
* A `Date` runs once at that date, a number runs once after that many
|
|
37
|
-
* seconds, and a string is a recurring cron expression.
|
|
38
|
-
*
|
|
39
|
-
* @param when - The requested execution time or recurrence.
|
|
40
|
-
* @param nowMs - Current epoch time in milliseconds.
|
|
41
|
-
* @param callback - Callback name, used only in error messages.
|
|
42
|
-
* @returns The parsed timing.
|
|
43
|
-
* @throws For a `when` value that is not a `Date`, number, or string.
|
|
44
|
-
*/
|
|
45
|
-
function parseWhen(when, nowMs, callback) {
|
|
46
|
-
if (when instanceof Date) return {
|
|
47
|
-
type: "scheduled",
|
|
48
|
-
time: Math.floor(when.getTime() / 1e3)
|
|
49
|
-
};
|
|
50
|
-
if (typeof when === "number") return {
|
|
51
|
-
type: "delayed",
|
|
52
|
-
time: Math.floor((nowMs + when * 1e3) / 1e3),
|
|
53
|
-
delayInSeconds: when
|
|
54
|
-
};
|
|
55
|
-
if (typeof when === "string") return {
|
|
56
|
-
type: "cron",
|
|
57
|
-
time: Math.floor(nextCronTimeMs(when, nowMs) / 1e3),
|
|
58
|
-
cron: when
|
|
59
|
-
};
|
|
60
|
-
throw new Error(`Invalid schedule type: ${JSON.stringify(when)}(${typeof when}) trying to schedule ${callback}`);
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Reject an interval outside the supported range.
|
|
64
|
-
*
|
|
65
|
-
* @param intervalSeconds - The requested gap between executions.
|
|
66
|
-
* @throws For a non-positive interval or one longer than 30 days.
|
|
67
|
-
*/
|
|
68
|
-
function validateIntervalSeconds(intervalSeconds) {
|
|
69
|
-
if (typeof intervalSeconds !== "number" || intervalSeconds <= 0) throw new Error("intervalSeconds must be a positive number");
|
|
70
|
-
if (intervalSeconds > 2592e3) throw new Error(`intervalSeconds cannot exceed ${MAX_INTERVAL_SECONDS} seconds (30 days)`);
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Parse a fixed interval into schedule timing.
|
|
74
|
-
*
|
|
75
|
-
* @param intervalSeconds - Seconds between executions.
|
|
76
|
-
* @param nowMs - Current epoch time in milliseconds.
|
|
77
|
-
* @returns The parsed timing with the first execution one interval from now.
|
|
78
|
-
* @throws For an interval outside the supported range.
|
|
79
|
-
*/
|
|
80
|
-
function parseInterval(intervalSeconds, nowMs) {
|
|
81
|
-
validateIntervalSeconds(intervalSeconds);
|
|
82
|
-
return {
|
|
83
|
-
type: "interval",
|
|
84
|
-
time: Math.floor((nowMs + intervalSeconds * 1e3) / 1e3),
|
|
85
|
-
intervalSeconds
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
//#endregion
|
|
89
|
-
//#region src/schedules/scheduler.ts
|
|
90
|
-
/**
|
|
91
|
-
* Lifecycle scheduling primitive. Owns the `cf_agents_schedules` table,
|
|
92
|
-
* callback retry policy, and due-row processing.
|
|
93
|
-
*
|
|
94
|
-
* Scheduler consumes only the standard capability services: storage, alarm
|
|
95
|
-
* coordination, the host invocation boundary, events, and routing. Lifecycle combines
|
|
96
|
-
* Scheduler's next wake-up candidate with contributions from other
|
|
97
|
-
* capabilities and the host before arming the Durable Object.
|
|
98
|
-
*/
|
|
99
|
-
const schedulerCallbackResolvers = /* @__PURE__ */ new WeakMap();
|
|
100
|
-
/**
|
|
101
|
-
* @internal Supply a composition-root fallback for callback names outside the
|
|
102
|
-
* registered map. Agent uses this to keep its historical name-based
|
|
103
|
-
* scheduling API (`this.schedule(60, "methodName")`) working: the resolver
|
|
104
|
-
* looks the method up on the Agent, and the resolved handler still runs
|
|
105
|
-
* inside the Lifecycle host boundary.
|
|
106
|
-
*/
|
|
107
|
-
function setSchedulerCallbackResolver(scheduler, resolver) {
|
|
108
|
-
schedulerCallbackResolvers.set(scheduler, resolver);
|
|
109
|
-
}
|
|
110
|
-
const SCHEDULE_SCHEMA_VERSION_KEY = "cf_agents:schedules_schema_version";
|
|
111
|
-
const CURRENT_SCHEDULE_SCHEMA_VERSION = 1;
|
|
112
|
-
const DEFAULT_RETRY = {
|
|
113
|
-
maxAttempts: 3,
|
|
114
|
-
baseDelayMs: 100,
|
|
115
|
-
maxDelayMs: 3e3
|
|
116
|
-
};
|
|
117
|
-
function parseRetryOptions(row) {
|
|
118
|
-
return typeof row.retry_options === "string" ? JSON.parse(row.retry_options) : void 0;
|
|
119
|
-
}
|
|
120
|
-
function resolveRetryConfig(retry, defaults) {
|
|
121
|
-
return {
|
|
122
|
-
maxAttempts: retry?.maxAttempts ?? defaults.maxAttempts,
|
|
123
|
-
baseDelayMs: retry?.baseDelayMs ?? defaults.baseDelayMs,
|
|
124
|
-
maxDelayMs: retry?.maxDelayMs ?? defaults.maxDelayMs
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* @internal Create or migrate the schedule table. Idempotent. Shared with
|
|
129
|
-
* Agent's constructor-time schema initialization so a brand-new Agent
|
|
130
|
-
* exposes the table before Lifecycle startup completes, exactly as it did
|
|
131
|
-
* before Scheduler was extracted; {@link Scheduler.onStart} applies the
|
|
132
|
-
* same migration for plain Lifecycle Objects.
|
|
133
|
-
*/
|
|
134
|
-
function ensureScheduleTable(storage) {
|
|
135
|
-
const rawSql = (query, ...params) => storage.sql.exec(query, ...params);
|
|
136
|
-
rawSql(`
|
|
137
|
-
CREATE TABLE IF NOT EXISTS cf_agents_schedules (
|
|
138
|
-
id TEXT PRIMARY KEY NOT NULL DEFAULT (randomblob(9)),
|
|
139
|
-
callback TEXT,
|
|
140
|
-
payload TEXT,
|
|
141
|
-
type TEXT NOT NULL CHECK(type IN ('scheduled', 'delayed', 'cron', 'interval')),
|
|
142
|
-
time INTEGER,
|
|
143
|
-
delayInSeconds INTEGER,
|
|
144
|
-
cron TEXT,
|
|
145
|
-
intervalSeconds INTEGER,
|
|
146
|
-
running INTEGER DEFAULT 0,
|
|
147
|
-
created_at INTEGER DEFAULT (unixepoch()),
|
|
148
|
-
execution_started_at INTEGER,
|
|
149
|
-
retry_options TEXT,
|
|
150
|
-
owner_path TEXT,
|
|
151
|
-
owner_path_key TEXT
|
|
152
|
-
)
|
|
153
|
-
`);
|
|
154
|
-
const addColumnIfMissing = (statement) => {
|
|
155
|
-
try {
|
|
156
|
-
rawSql(statement);
|
|
157
|
-
} catch (error) {
|
|
158
|
-
if (!(error instanceof Error ? error.message : String(error)).toLowerCase().includes("duplicate column")) throw error;
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
addColumnIfMissing("ALTER TABLE cf_agents_schedules ADD COLUMN intervalSeconds INTEGER");
|
|
162
|
-
addColumnIfMissing("ALTER TABLE cf_agents_schedules ADD COLUMN running INTEGER DEFAULT 0");
|
|
163
|
-
addColumnIfMissing("ALTER TABLE cf_agents_schedules ADD COLUMN execution_started_at INTEGER");
|
|
164
|
-
addColumnIfMissing("ALTER TABLE cf_agents_schedules ADD COLUMN retry_options TEXT");
|
|
165
|
-
addColumnIfMissing("ALTER TABLE cf_agents_schedules ADD COLUMN owner_path TEXT");
|
|
166
|
-
addColumnIfMissing("ALTER TABLE cf_agents_schedules ADD COLUMN owner_path_key TEXT");
|
|
167
|
-
const rows = rawSql("SELECT sql FROM sqlite_master WHERE type='table' AND name='cf_agents_schedules'").toArray();
|
|
168
|
-
const ddl = typeof rows[0]?.sql === "string" ? rows[0].sql : "";
|
|
169
|
-
if (ddl && !ddl.includes("'interval'")) {
|
|
170
|
-
rawSql("DROP TABLE IF EXISTS cf_agents_schedules_new");
|
|
171
|
-
rawSql(`
|
|
172
|
-
CREATE TABLE cf_agents_schedules_new (
|
|
173
|
-
id TEXT PRIMARY KEY NOT NULL DEFAULT (randomblob(9)),
|
|
174
|
-
callback TEXT,
|
|
175
|
-
payload TEXT,
|
|
176
|
-
type TEXT NOT NULL CHECK(type IN ('scheduled', 'delayed', 'cron', 'interval')),
|
|
177
|
-
time INTEGER,
|
|
178
|
-
delayInSeconds INTEGER,
|
|
179
|
-
cron TEXT,
|
|
180
|
-
intervalSeconds INTEGER,
|
|
181
|
-
running INTEGER DEFAULT 0,
|
|
182
|
-
created_at INTEGER DEFAULT (unixepoch()),
|
|
183
|
-
execution_started_at INTEGER,
|
|
184
|
-
retry_options TEXT,
|
|
185
|
-
owner_path TEXT,
|
|
186
|
-
owner_path_key TEXT
|
|
187
|
-
)
|
|
188
|
-
`);
|
|
189
|
-
rawSql(`
|
|
190
|
-
INSERT INTO cf_agents_schedules_new
|
|
191
|
-
(id, callback, payload, type, time, delayInSeconds, cron,
|
|
192
|
-
intervalSeconds, running, created_at, execution_started_at,
|
|
193
|
-
retry_options, owner_path, owner_path_key)
|
|
194
|
-
SELECT id, callback, payload, type, time, delayInSeconds, cron,
|
|
195
|
-
intervalSeconds, running, created_at, execution_started_at,
|
|
196
|
-
retry_options, owner_path, owner_path_key
|
|
197
|
-
FROM cf_agents_schedules
|
|
198
|
-
`);
|
|
199
|
-
rawSql("DROP TABLE cf_agents_schedules");
|
|
200
|
-
rawSql("ALTER TABLE cf_agents_schedules_new RENAME TO cf_agents_schedules");
|
|
201
|
-
}
|
|
202
|
-
rawSql("DELETE FROM cf_agents_schedules WHERE callback = '_cf_keepAliveHeartbeat'");
|
|
203
|
-
}
|
|
204
|
-
var _handlers = /* @__PURE__ */ new WeakMap();
|
|
205
|
-
var _retryDefaults = /* @__PURE__ */ new WeakMap();
|
|
206
|
-
var _hungScheduleTimeoutSeconds = /* @__PURE__ */ new WeakMap();
|
|
207
|
-
var _onError = /* @__PURE__ */ new WeakMap();
|
|
208
|
-
var _warnedStartupCallbacks = /* @__PURE__ */ new WeakMap();
|
|
209
|
-
var _executingRowId = /* @__PURE__ */ new WeakMap();
|
|
210
|
-
var _Scheduler_brand = /* @__PURE__ */ new WeakSet();
|
|
211
|
-
/**
|
|
212
|
-
* Persistent task scheduling for a Lifecycle Object.
|
|
213
|
-
*
|
|
214
|
-
* Register callbacks in the constructor and install the instance with
|
|
215
|
-
* `Lifecycle.use()`. Scheduler owns its SQL schema, task CRUD, callback
|
|
216
|
-
* retries, and due-row processing. It contributes its next wake time while
|
|
217
|
-
* Lifecycle owns the physical alarm, and it runs registered callbacks
|
|
218
|
-
* through Lifecycle's host invocation boundary.
|
|
219
|
-
*
|
|
220
|
-
* @experimental The API surface may change before stabilizing.
|
|
221
|
-
*/
|
|
222
|
-
var Scheduler = class extends LifecycleCapability {
|
|
223
|
-
/**
|
|
224
|
-
* Create a persistent Scheduler.
|
|
225
|
-
*
|
|
226
|
-
* @param options - Registered callbacks plus optional retry,
|
|
227
|
-
* hung-interval, and error policy. Registering `callbacks` types
|
|
228
|
-
* {@link set} and {@link every} against the map — names and
|
|
229
|
-
* payloads are checked where the handlers are declared and where they are
|
|
230
|
-
* scheduled. Names outside the map are rejected unless a composition-root
|
|
231
|
-
* resolver supplies them — the internal aperture behind `Agent`'s
|
|
232
|
-
* name-based scheduling API.
|
|
233
|
-
*/
|
|
234
|
-
constructor(options = {}) {
|
|
235
|
-
super("scheduler");
|
|
236
|
-
_classPrivateMethodInitSpec(this, _Scheduler_brand);
|
|
237
|
-
_classPrivateFieldInitSpec(this, _handlers, void 0);
|
|
238
|
-
_classPrivateFieldInitSpec(this, _retryDefaults, void 0);
|
|
239
|
-
_classPrivateFieldInitSpec(this, _hungScheduleTimeoutSeconds, void 0);
|
|
240
|
-
_classPrivateFieldInitSpec(this, _onError, void 0);
|
|
241
|
-
_classPrivateFieldInitSpec(this, _warnedStartupCallbacks, /* @__PURE__ */ new Set());
|
|
242
|
-
_classPrivateFieldInitSpec(this, _executingRowId, void 0);
|
|
243
|
-
_classPrivateFieldSet2(_handlers, this, options.callbacks ?? {});
|
|
244
|
-
_classPrivateFieldSet2(_retryDefaults, this, resolveRetryConfig(options.retry, DEFAULT_RETRY));
|
|
245
|
-
_classPrivateFieldSet2(_hungScheduleTimeoutSeconds, this, options.hungScheduleTimeoutSeconds ?? 30);
|
|
246
|
-
_classPrivateFieldSet2(_onError, this, options.onError);
|
|
247
|
-
}
|
|
248
|
-
/** Initialize and migrate schedule storage during Lifecycle startup. */
|
|
249
|
-
async onStart() {
|
|
250
|
-
_classPrivateFieldGet2(_warnedStartupCallbacks, this).clear();
|
|
251
|
-
const storage = this.lifecycle.storage;
|
|
252
|
-
if ((await storage.get(SCHEDULE_SCHEMA_VERSION_KEY) ?? 0) >= CURRENT_SCHEDULE_SCHEMA_VERSION) return;
|
|
253
|
-
ensureScheduleTable(this.lifecycle.storage);
|
|
254
|
-
await storage.put(SCHEDULE_SCHEMA_VERSION_KEY, CURRENT_SCHEDULE_SCHEMA_VERSION);
|
|
255
|
-
}
|
|
256
|
-
/** Execute due schedule rows during the Lifecycle alarm phase. */
|
|
257
|
-
async onAlarm() {
|
|
258
|
-
await _assertClassBrand(_Scheduler_brand, this, _fireDueSchedules).call(this);
|
|
259
|
-
}
|
|
260
|
-
/** Handle Scheduler protocol messages routed by another Lifecycle. */
|
|
261
|
-
async onRoute(context) {
|
|
262
|
-
const message = context.payload;
|
|
263
|
-
const owner = context.source ?? null;
|
|
264
|
-
switch (message.type) {
|
|
265
|
-
case "schedule": return _assertClassBrand(_Scheduler_brand, this, _insert).call(this, owner, parseWhen(message.when, Date.now(), message.callback), message.callback, message.payload, message.options);
|
|
266
|
-
case "every": return _assertClassBrand(_Scheduler_brand, this, _insert).call(this, owner, parseInterval(message.intervalSeconds, Date.now()), message.callback, message.payload, message.options);
|
|
267
|
-
case "get": return _assertClassBrand(_Scheduler_brand, this, _getForOwner).call(this, owner, message.id);
|
|
268
|
-
case "list": return _assertClassBrand(_Scheduler_brand, this, _listForOwner).call(this, owner, message.criteria);
|
|
269
|
-
case "cancel": return _assertClassBrand(_Scheduler_brand, this, _cancelForOwner).call(this, owner, message.id);
|
|
270
|
-
case "dispatch":
|
|
271
|
-
await _assertClassBrand(_Scheduler_brand, this, _executeCallback).call(this, message.row);
|
|
272
|
-
return true;
|
|
273
|
-
default: throw new Error("Unknown routed Scheduler message");
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
/** Contribute the earliest pending schedule to Lifecycle alarm selection. */
|
|
277
|
-
getNextAlarm() {
|
|
278
|
-
const nowMs = Date.now();
|
|
279
|
-
const hungCutoffSeconds = Math.floor(nowMs / 1e3) - _classPrivateFieldGet2(_hungScheduleTimeoutSeconds, this);
|
|
280
|
-
const nextSchedule = _assertClassBrand(_Scheduler_brand, this, _nextScheduleTimeMs).call(this, nowMs, hungCutoffSeconds);
|
|
281
|
-
const hungIntervalRecheck = _assertClassBrand(_Scheduler_brand, this, _nextHungIntervalRecheckMs).call(this, hungCutoffSeconds);
|
|
282
|
-
if (nextSchedule === null) return hungIntervalRecheck;
|
|
283
|
-
if (hungIntervalRecheck === null) return nextSchedule;
|
|
284
|
-
return Math.min(nextSchedule, hungIntervalRecheck);
|
|
285
|
-
}
|
|
286
|
-
/** Set a delayed, dated, or cron schedule for a registered callback. */
|
|
287
|
-
async set(when, callback, payload, options) {
|
|
288
|
-
await this.lifecycle.ready();
|
|
289
|
-
_assertClassBrand(_Scheduler_brand, this, _validateSchedule).call(this, when, callback, options);
|
|
290
|
-
const result = this.lifecycle.routes.source ? await this.lifecycle.routes.toRoot({
|
|
291
|
-
type: "schedule",
|
|
292
|
-
when,
|
|
293
|
-
callback,
|
|
294
|
-
payload,
|
|
295
|
-
options
|
|
296
|
-
}) : await _assertClassBrand(_Scheduler_brand, this, _insert).call(this, null, parseWhen(when, Date.now(), callback), callback, payload, options);
|
|
297
|
-
_assertClassBrand(_Scheduler_brand, this, _emitCreated).call(this, result);
|
|
298
|
-
return result.schedule;
|
|
299
|
-
}
|
|
300
|
-
/** Set a fixed-interval schedule for a registered callback. */
|
|
301
|
-
async every(intervalSeconds, callback, payload, options) {
|
|
302
|
-
await this.lifecycle.ready();
|
|
303
|
-
_assertClassBrand(_Scheduler_brand, this, _validateInterval).call(this, intervalSeconds, callback, options?.retry);
|
|
304
|
-
const result = this.lifecycle.routes.source ? await this.lifecycle.routes.toRoot({
|
|
305
|
-
type: "every",
|
|
306
|
-
intervalSeconds,
|
|
307
|
-
callback,
|
|
308
|
-
payload,
|
|
309
|
-
options
|
|
310
|
-
}) : await _assertClassBrand(_Scheduler_brand, this, _insert).call(this, null, parseInterval(intervalSeconds, Date.now()), callback, payload, options);
|
|
311
|
-
_assertClassBrand(_Scheduler_brand, this, _emitCreated).call(this, result);
|
|
312
|
-
return result.schedule;
|
|
313
|
-
}
|
|
314
|
-
/** Get a schedule by ID. Works inside routed sub-agents. */
|
|
315
|
-
async get(id) {
|
|
316
|
-
await this.lifecycle.ready();
|
|
317
|
-
return this.lifecycle.routes.source ? await this.lifecycle.routes.toRoot({
|
|
318
|
-
type: "get",
|
|
319
|
-
id
|
|
320
|
-
}) : _assertClassBrand(_Scheduler_brand, this, _getForOwner).call(this, null, id);
|
|
321
|
-
}
|
|
322
|
-
/** List schedules matching criteria. Works inside routed sub-agents. */
|
|
323
|
-
async list(criteria = {}) {
|
|
324
|
-
await this.lifecycle.ready();
|
|
325
|
-
return this.lifecycle.routes.source ? await this.lifecycle.routes.toRoot({
|
|
326
|
-
type: "list",
|
|
327
|
-
criteria
|
|
328
|
-
}) : _assertClassBrand(_Scheduler_brand, this, _listForOwner).call(this, null, criteria);
|
|
329
|
-
}
|
|
330
|
-
/**
|
|
331
|
-
* Cancel one schedule owned by this Scheduler.
|
|
332
|
-
*
|
|
333
|
-
* @param id - ID of the schedule to cancel.
|
|
334
|
-
* @returns True when a schedule was cancelled, false when none matched.
|
|
335
|
-
*/
|
|
336
|
-
async cancel(id) {
|
|
337
|
-
await this.lifecycle.ready();
|
|
338
|
-
const result = this.lifecycle.routes.source ? await this.lifecycle.routes.toRoot({
|
|
339
|
-
type: "cancel",
|
|
340
|
-
id
|
|
341
|
-
}) : await _assertClassBrand(_Scheduler_brand, this, _cancelForOwner).call(this, null, id);
|
|
342
|
-
if (result.ok && result.callback) _assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:cancel", {
|
|
343
|
-
callback: result.callback,
|
|
344
|
-
id
|
|
345
|
-
});
|
|
346
|
-
return result.ok;
|
|
347
|
-
}
|
|
348
|
-
/**
|
|
349
|
-
* @internal Synchronous read backing Agent's deprecated `getSchedule()`.
|
|
350
|
-
* Not part of the primitive's contract — use {@link get}. Cannot cross
|
|
351
|
-
* Durable Object boundaries and throws inside routed sub-agents.
|
|
352
|
-
*/
|
|
353
|
-
__DO_NOT_USE_WILL_REMOVE__getSchedule(id) {
|
|
354
|
-
if (this.lifecycle.routes.source) throw new Error("getSchedule() is synchronous and cannot read routed schedule storage. Use await getScheduleById(id) on Agent, or await scheduler.get(id) on a standalone Scheduler.");
|
|
355
|
-
return _assertClassBrand(_Scheduler_brand, this, _getForOwner).call(this, null, id);
|
|
356
|
-
}
|
|
357
|
-
/**
|
|
358
|
-
* @internal Synchronous read backing Agent's deprecated `getSchedules()`.
|
|
359
|
-
* Not part of the primitive's contract — use {@link list}. Cannot cross
|
|
360
|
-
* Durable Object boundaries and throws inside routed sub-agents.
|
|
361
|
-
*/
|
|
362
|
-
__DO_NOT_USE_WILL_REMOVE__getSchedules(criteria = {}) {
|
|
363
|
-
if (this.lifecycle.routes.source) throw new Error("getSchedules() is synchronous and cannot read routed schedule storage. Use await listSchedules(criteria) on Agent, or await scheduler.list(criteria) on a standalone Scheduler.");
|
|
364
|
-
return _assertClassBrand(_Scheduler_brand, this, _listForOwner).call(this, null, criteria);
|
|
365
|
-
}
|
|
366
|
-
/** @internal Remove rows owned by one routed Lifecycle subtree. */
|
|
367
|
-
__DO_NOT_USE_WILL_BREAK__cleanupRoutePrefix(prefix) {
|
|
368
|
-
_assertClassBrand(_Scheduler_brand, this, _cancelOwners).call(this, (owner) => owner.key === prefix || owner.key.startsWith(`${prefix}/`));
|
|
369
|
-
}
|
|
370
|
-
/** @internal Apply Agent's outer alarm memory-limit policy to Scheduler rows. */
|
|
371
|
-
__DO_NOT_USE_WILL_BREAK__handleAlarmMemoryLimit(options) {
|
|
372
|
-
const executingRowId = _classPrivateFieldGet2(_executingRowId, this);
|
|
373
|
-
_classPrivateFieldSet2(_executingRowId, this, void 0);
|
|
374
|
-
if (options.sealed) {
|
|
375
|
-
_assertClassBrand(_Scheduler_brand, this, _deleteRows).call(this, options.callbacks, executingRowId);
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
if (options.nextTime === void 0) throw new Error("Scheduler memory-limit backoff requires nextTime");
|
|
379
|
-
_assertClassBrand(_Scheduler_brand, this, _moveRows).call(this, options.callbacks, executingRowId, options.nextTime);
|
|
380
|
-
}
|
|
381
|
-
};
|
|
382
|
-
function _validateSchedule(when, callback, options) {
|
|
383
|
-
if (typeof callback !== "string") throw new Error("Callback must be a string");
|
|
384
|
-
if (!_assertClassBrand(_Scheduler_brand, this, _hasCallback).call(this, callback)) throw new Error(`Unknown scheduled callback "${callback}": not registered on this Scheduler`);
|
|
385
|
-
if (options?.retry) validateRetryOptions(options.retry, _classPrivateFieldGet2(_retryDefaults, this));
|
|
386
|
-
if (!(when instanceof Date) && typeof when !== "number" && typeof when !== "string") throw new Error(`Invalid schedule type: ${JSON.stringify(when)}(${typeof when}) trying to schedule ${callback}`);
|
|
387
|
-
_assertClassBrand(_Scheduler_brand, this, _warnWhenScheduledDuringStartup).call(this, when, callback, options);
|
|
388
|
-
}
|
|
389
|
-
function _validateInterval(intervalSeconds, callback, retry) {
|
|
390
|
-
validateIntervalSeconds(intervalSeconds);
|
|
391
|
-
if (typeof callback !== "string") throw new Error("Callback must be a string");
|
|
392
|
-
if (!_assertClassBrand(_Scheduler_brand, this, _hasCallback).call(this, callback)) throw new Error(`Unknown scheduled callback "${callback}": not registered on this Scheduler`);
|
|
393
|
-
if (retry) validateRetryOptions(retry, _classPrivateFieldGet2(_retryDefaults, this));
|
|
394
|
-
}
|
|
395
|
-
/**
|
|
396
|
-
* A non-idempotent one-shot created during startup accumulates one row per
|
|
397
|
-
* Durable Object wake, whether it came from the host's onStart or another
|
|
398
|
-
* startup hook. Warn once per callback; an explicit `idempotent` choice
|
|
399
|
-
* (either value) opts out.
|
|
400
|
-
*/
|
|
401
|
-
function _warnWhenScheduledDuringStartup(when, callback, options) {
|
|
402
|
-
if (!this.lifecycle.starting()) return;
|
|
403
|
-
if (options?.idempotent !== void 0) return;
|
|
404
|
-
if (typeof when === "string") return;
|
|
405
|
-
if (_classPrivateFieldGet2(_warnedStartupCallbacks, this).has(callback)) return;
|
|
406
|
-
_classPrivateFieldGet2(_warnedStartupCallbacks, this).add(callback);
|
|
407
|
-
console.warn(`Scheduling "${callback}" during startup (e.g. onStart()) without { idempotent: true } creates a new row on every Durable Object restart, which can cause duplicate executions. Pass { idempotent: true } to deduplicate, or use an interval schedule for recurring tasks.`);
|
|
408
|
-
}
|
|
409
|
-
/** Resolve a name to its registered or composition-root-supplied handler. */
|
|
410
|
-
function _resolveCallback(name) {
|
|
411
|
-
const handler = _classPrivateFieldGet2(_handlers, this)[name];
|
|
412
|
-
if (handler) return handler;
|
|
413
|
-
return schedulerCallbackResolvers.get(this)?.(name);
|
|
414
|
-
}
|
|
415
|
-
/** True when a name resolves to a runnable callback. */
|
|
416
|
-
function _hasCallback(name) {
|
|
417
|
-
return _assertClassBrand(_Scheduler_brand, this, _resolveCallback).call(this, name) !== void 0;
|
|
418
|
-
}
|
|
419
|
-
/** Run one resolved callback inside the host invocation boundary. */
|
|
420
|
-
async function _invokeCallback(name, payload, schedule) {
|
|
421
|
-
const handler = _assertClassBrand(_Scheduler_brand, this, _resolveCallback).call(this, name);
|
|
422
|
-
if (!handler) throw new Error(`Unknown scheduled callback "${name}"`);
|
|
423
|
-
await this.lifecycle.runInHostContext(() => handler(payload, schedule));
|
|
424
|
-
}
|
|
425
|
-
function _sql(strings, ...values) {
|
|
426
|
-
const query = strings.reduce((result, part, index) => result + part + (index < values.length ? "?" : ""), "");
|
|
427
|
-
try {
|
|
428
|
-
return [...this.lifecycle.storage.sql.exec(query, ...values)];
|
|
429
|
-
} catch (cause) {
|
|
430
|
-
throw new SqlError(query, cause);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
/**
|
|
434
|
-
* Insert a schedule row for the given owner, or return the existing row
|
|
435
|
-
* when an idempotent request matches one. One-shot timings deduplicate only
|
|
436
|
-
* when `idempotent: true` is passed; recurring timings deduplicate unless
|
|
437
|
-
* `idempotent: false` opts out. `created: false` marks a dedup hit so
|
|
438
|
-
* callers suppress the `schedule:create` event.
|
|
439
|
-
*/
|
|
440
|
-
async function _insert(owner, timing, callback, payload, options) {
|
|
441
|
-
const payloadJson = JSON.stringify(payload);
|
|
442
|
-
if (isRecurring(timing) ? options?.idempotent !== false : Boolean(options?.idempotent)) {
|
|
443
|
-
const existing = _assertClassBrand(_Scheduler_brand, this, _findMatchingRow).call(this, owner?.key ?? null, timing, callback, payloadJson);
|
|
444
|
-
if (existing) {
|
|
445
|
-
await this.lifecycle.alarms.rearm();
|
|
446
|
-
return {
|
|
447
|
-
schedule: _assertClassBrand(_Scheduler_brand, this, _rowToSchedule).call(this, existing),
|
|
448
|
-
created: false
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
const id = nanoid(9);
|
|
453
|
-
_assertClassBrand(_Scheduler_brand, this, _sql).bind(this)`
|
|
454
|
-
INSERT OR REPLACE INTO cf_agents_schedules
|
|
455
|
-
(id, callback, payload, type, time, delayInSeconds, cron,
|
|
456
|
-
intervalSeconds, running, retry_options, owner_path, owner_path_key)
|
|
457
|
-
VALUES
|
|
458
|
-
(${id}, ${callback}, ${payloadJson}, ${timing.type}, ${timing.time},
|
|
459
|
-
${timing.type === "delayed" ? timing.delayInSeconds : null},
|
|
460
|
-
${timing.type === "cron" ? timing.cron : null},
|
|
461
|
-
${timing.type === "interval" ? timing.intervalSeconds : null},
|
|
462
|
-
0,
|
|
463
|
-
${options?.retry ? JSON.stringify(options.retry) : null},
|
|
464
|
-
${owner?.data ?? null}, ${owner?.key ?? null})
|
|
465
|
-
`;
|
|
466
|
-
await this.lifecycle.alarms.rearm();
|
|
467
|
-
return {
|
|
468
|
-
schedule: {
|
|
469
|
-
id,
|
|
470
|
-
callback,
|
|
471
|
-
payload,
|
|
472
|
-
retry: options?.retry,
|
|
473
|
-
...timing
|
|
474
|
-
},
|
|
475
|
-
created: true
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
/** Find the row an idempotent insert deduplicates onto, if any. */
|
|
479
|
-
function _findMatchingRow(ownerKey, timing, callback, payloadJson) {
|
|
480
|
-
let query = "SELECT * FROM cf_agents_schedules WHERE type = ? AND callback = ? AND payload IS ? AND owner_path_key IS ?";
|
|
481
|
-
const params = [
|
|
482
|
-
timing.type,
|
|
483
|
-
callback,
|
|
484
|
-
payloadJson,
|
|
485
|
-
ownerKey
|
|
486
|
-
];
|
|
487
|
-
if (timing.type === "cron") {
|
|
488
|
-
query += " AND cron = ?";
|
|
489
|
-
params.push(timing.cron);
|
|
490
|
-
}
|
|
491
|
-
if (timing.type === "interval") {
|
|
492
|
-
query += " AND intervalSeconds = ?";
|
|
493
|
-
params.push(timing.intervalSeconds);
|
|
494
|
-
}
|
|
495
|
-
let rows;
|
|
496
|
-
try {
|
|
497
|
-
rows = this.lifecycle.storage.sql.exec(query, ...params).toArray();
|
|
498
|
-
} catch (cause) {
|
|
499
|
-
throw new SqlError(query, cause);
|
|
500
|
-
}
|
|
501
|
-
return rows[0];
|
|
502
|
-
}
|
|
503
|
-
function _rowToSchedule(row) {
|
|
504
|
-
const base = {
|
|
505
|
-
callback: row.callback,
|
|
506
|
-
id: row.id,
|
|
507
|
-
payload: JSON.parse(row.payload),
|
|
508
|
-
retry: parseRetryOptions(row)
|
|
509
|
-
};
|
|
510
|
-
switch (row.type) {
|
|
511
|
-
case "scheduled": return {
|
|
512
|
-
...base,
|
|
513
|
-
time: row.time,
|
|
514
|
-
type: "scheduled"
|
|
515
|
-
};
|
|
516
|
-
case "delayed": return {
|
|
517
|
-
...base,
|
|
518
|
-
delayInSeconds: row.delayInSeconds ?? 0,
|
|
519
|
-
time: row.time,
|
|
520
|
-
type: "delayed"
|
|
521
|
-
};
|
|
522
|
-
case "cron": return {
|
|
523
|
-
...base,
|
|
524
|
-
cron: row.cron ?? "",
|
|
525
|
-
time: row.time,
|
|
526
|
-
type: "cron"
|
|
527
|
-
};
|
|
528
|
-
case "interval": return {
|
|
529
|
-
...base,
|
|
530
|
-
intervalSeconds: row.intervalSeconds ?? 0,
|
|
531
|
-
time: row.time,
|
|
532
|
-
type: "interval"
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
function _getForOwner(owner, id) {
|
|
537
|
-
const ownerPathKey = owner?.key ?? null;
|
|
538
|
-
const result = _assertClassBrand(_Scheduler_brand, this, _sql).bind(this)`
|
|
539
|
-
SELECT * FROM cf_agents_schedules
|
|
540
|
-
WHERE id = ${id} AND owner_path_key IS ${ownerPathKey}
|
|
541
|
-
`;
|
|
542
|
-
if (result.length === 0) return void 0;
|
|
543
|
-
return _assertClassBrand(_Scheduler_brand, this, _rowToSchedule).call(this, result[0]);
|
|
544
|
-
}
|
|
545
|
-
function _listForOwner(owner, criteria = {}) {
|
|
546
|
-
let query = "SELECT * FROM cf_agents_schedules WHERE owner_path_key IS ?";
|
|
547
|
-
const params = [owner?.key ?? null];
|
|
548
|
-
if (criteria.id) {
|
|
549
|
-
query += " AND id = ?";
|
|
550
|
-
params.push(criteria.id);
|
|
551
|
-
}
|
|
552
|
-
if (criteria.type) {
|
|
553
|
-
query += " AND type = ?";
|
|
554
|
-
params.push(criteria.type);
|
|
555
|
-
}
|
|
556
|
-
if (criteria.timeRange) {
|
|
557
|
-
query += " AND time >= ? AND time <= ?";
|
|
558
|
-
const start = criteria.timeRange.start || /* @__PURE__ */ new Date(0);
|
|
559
|
-
const end = criteria.timeRange.end || /* @__PURE__ */ new Date(999999999999999);
|
|
560
|
-
params.push(Math.floor(start.getTime() / 1e3), Math.floor(end.getTime() / 1e3));
|
|
561
|
-
}
|
|
562
|
-
return this.lifecycle.storage.sql.exec(query, ...params).toArray().map((row) => _assertClassBrand(_Scheduler_brand, this, _rowToSchedule).call(this, row));
|
|
563
|
-
}
|
|
564
|
-
async function _cancelForOwner(owner, id) {
|
|
565
|
-
const ownerPathKey = owner?.key ?? null;
|
|
566
|
-
const result = _assertClassBrand(_Scheduler_brand, this, _sql).bind(this)`
|
|
567
|
-
SELECT * FROM cf_agents_schedules
|
|
568
|
-
WHERE id = ${id} AND owner_path_key IS ${ownerPathKey}
|
|
569
|
-
`;
|
|
570
|
-
if (result.length === 0) return { ok: false };
|
|
571
|
-
const callback = result[0].callback;
|
|
572
|
-
_assertClassBrand(_Scheduler_brand, this, _sql).bind(this)`
|
|
573
|
-
DELETE FROM cf_agents_schedules
|
|
574
|
-
WHERE id = ${id} AND owner_path_key IS ${ownerPathKey}
|
|
575
|
-
`;
|
|
576
|
-
await this.lifecycle.alarms.rearm();
|
|
577
|
-
return {
|
|
578
|
-
ok: true,
|
|
579
|
-
callback
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
/** Cancel every owned row matched by a route-address predicate. */
|
|
583
|
-
function _cancelOwners(matches) {
|
|
584
|
-
const rows = _assertClassBrand(_Scheduler_brand, this, _sql).bind(this)`
|
|
585
|
-
SELECT * FROM cf_agents_schedules
|
|
586
|
-
WHERE owner_path IS NOT NULL
|
|
587
|
-
`;
|
|
588
|
-
for (const row of rows) {
|
|
589
|
-
if (!row.owner_path || !matches({
|
|
590
|
-
key: row.owner_path_key ?? row.owner_path,
|
|
591
|
-
data: row.owner_path
|
|
592
|
-
})) continue;
|
|
593
|
-
_assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:cancel", {
|
|
594
|
-
callback: row.callback,
|
|
595
|
-
id: row.id
|
|
596
|
-
});
|
|
597
|
-
_assertClassBrand(_Scheduler_brand, this, _sql).bind(this)`DELETE FROM cf_agents_schedules WHERE id = ${row.id}`;
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
/** Delete rows selected by a host-owned failure policy. Best-effort. */
|
|
601
|
-
function _deleteRows(callbacks, executingRowId) {
|
|
602
|
-
for (const callback of callbacks) try {
|
|
603
|
-
_assertClassBrand(_Scheduler_brand, this, _sql).bind(this)`
|
|
604
|
-
DELETE FROM cf_agents_schedules WHERE callback = ${callback}
|
|
605
|
-
`;
|
|
606
|
-
} catch {}
|
|
607
|
-
if (executingRowId) try {
|
|
608
|
-
_assertClassBrand(_Scheduler_brand, this, _sql).bind(this)`
|
|
609
|
-
DELETE FROM cf_agents_schedules WHERE id = ${executingRowId}
|
|
610
|
-
`;
|
|
611
|
-
} catch {}
|
|
612
|
-
}
|
|
613
|
-
/** Delay rows selected by a host-owned failure policy. Best-effort. */
|
|
614
|
-
function _moveRows(callbacks, executingRowId, nextTime) {
|
|
615
|
-
for (const callback of callbacks) try {
|
|
616
|
-
_assertClassBrand(_Scheduler_brand, this, _sql).bind(this)`
|
|
617
|
-
UPDATE cf_agents_schedules
|
|
618
|
-
SET time = ${nextTime}
|
|
619
|
-
WHERE callback = ${callback} AND time <= ${nextTime}
|
|
620
|
-
`;
|
|
621
|
-
} catch {}
|
|
622
|
-
if (executingRowId) try {
|
|
623
|
-
_assertClassBrand(_Scheduler_brand, this, _sql).bind(this)`
|
|
624
|
-
UPDATE cf_agents_schedules
|
|
625
|
-
SET time = ${nextTime}
|
|
626
|
-
WHERE id = ${executingRowId} AND time <= ${nextTime}
|
|
627
|
-
`;
|
|
628
|
-
} catch {}
|
|
629
|
-
}
|
|
630
|
-
function _emit(type, payload) {
|
|
631
|
-
this.lifecycle.events.emit(type, payload);
|
|
632
|
-
}
|
|
633
|
-
function _emitCreated(result) {
|
|
634
|
-
if (!result.created) return;
|
|
635
|
-
_assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:create", {
|
|
636
|
-
callback: result.schedule.callback,
|
|
637
|
-
id: result.schedule.id
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
/**
|
|
641
|
-
* Execute a local schedule row with retry handling.
|
|
642
|
-
*
|
|
643
|
-
* The capability remains outside ambient context. Lifecycle's host
|
|
644
|
-
* invocation boundary establishes host context only around the user
|
|
645
|
-
* callback itself.
|
|
646
|
-
*/
|
|
647
|
-
async function _executeCallback(row) {
|
|
648
|
-
if (!_assertClassBrand(_Scheduler_brand, this, _hasCallback).call(this, row.callback)) {
|
|
649
|
-
console.error(`callback ${row.callback} not found`);
|
|
650
|
-
return;
|
|
651
|
-
}
|
|
652
|
-
const { maxAttempts, baseDelayMs, maxDelayMs } = resolveRetryConfig(parseRetryOptions(row), _classPrivateFieldGet2(_retryDefaults, this));
|
|
653
|
-
let parsedPayload;
|
|
654
|
-
try {
|
|
655
|
-
parsedPayload = JSON.parse(row.payload);
|
|
656
|
-
} catch (error) {
|
|
657
|
-
console.error(`Failed to parse payload for schedule "${row.id}" (callback "${row.callback}")`, error);
|
|
658
|
-
_assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:error", {
|
|
659
|
-
callback: row.callback,
|
|
660
|
-
id: row.id,
|
|
661
|
-
error: error instanceof Error ? error.message : String(error),
|
|
662
|
-
attempts: 0
|
|
663
|
-
});
|
|
664
|
-
return;
|
|
665
|
-
}
|
|
666
|
-
const isOneShotSchedule = row.type === "delayed" || row.type === "scheduled";
|
|
667
|
-
const shouldDeferReset = (error) => isOneShotSchedule && isDurableObjectCodeUpdateReset(error);
|
|
668
|
-
const shouldDeferOnExhaustion = (error) => isOneShotSchedule && isPlatformTransientError(error);
|
|
669
|
-
const shouldDeferMemoryLimit = (error) => isOneShotSchedule && isDurableObjectMemoryLimitReset(error);
|
|
670
|
-
const schedule = _assertClassBrand(_Scheduler_brand, this, _rowToSchedule).call(this, row);
|
|
671
|
-
try {
|
|
672
|
-
_assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:execute", {
|
|
673
|
-
callback: row.callback,
|
|
674
|
-
id: row.id
|
|
675
|
-
});
|
|
676
|
-
await tryN(maxAttempts, async (attempt) => {
|
|
677
|
-
if (attempt > 1) _assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:retry", {
|
|
678
|
-
callback: row.callback,
|
|
679
|
-
id: row.id,
|
|
680
|
-
attempt,
|
|
681
|
-
maxAttempts
|
|
682
|
-
});
|
|
683
|
-
await _assertClassBrand(_Scheduler_brand, this, _invokeCallback).call(this, row.callback, parsedPayload, schedule);
|
|
684
|
-
}, {
|
|
685
|
-
baseDelayMs,
|
|
686
|
-
maxDelayMs,
|
|
687
|
-
shouldRetry: (error) => !shouldDeferReset(error)
|
|
688
|
-
});
|
|
689
|
-
} catch (error) {
|
|
690
|
-
if (shouldDeferReset(error)) {
|
|
691
|
-
console.warn(`Deferring scheduled callback "${row.callback}" to a fresh invocation after a Durable Object code-update reset; the one-shot row is preserved and the alarm will re-run on new code.`);
|
|
692
|
-
throw error;
|
|
693
|
-
}
|
|
694
|
-
if (shouldDeferOnExhaustion(error)) {
|
|
695
|
-
console.warn(`Deferring scheduled callback "${row.callback}" after exhausting in-process retries on a transient platform error; the one-shot row is preserved and the alarm will re-run once the platform recovers.`);
|
|
696
|
-
throw error;
|
|
697
|
-
}
|
|
698
|
-
if (shouldDeferMemoryLimit(error)) {
|
|
699
|
-
console.warn(`Deferring scheduled callback "${row.callback}" to the alarm memory-limit circuit breaker after a Durable Object memory-limit reset; the one-shot row is preserved so the breaker can bound the retry loop and seal it (#1825).`);
|
|
700
|
-
throw error;
|
|
701
|
-
}
|
|
702
|
-
console.error(`error executing callback "${row.callback}" after ${maxAttempts} attempts`, error);
|
|
703
|
-
_assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:error", {
|
|
704
|
-
callback: row.callback,
|
|
705
|
-
id: row.id,
|
|
706
|
-
error: error instanceof Error ? error.message : String(error),
|
|
707
|
-
attempts: maxAttempts
|
|
708
|
-
});
|
|
709
|
-
try {
|
|
710
|
-
await _classPrivateFieldGet2(_onError, this)?.call(this, error);
|
|
711
|
-
} catch {}
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
/** Execute every schedule row due in the current alarm phase. */
|
|
715
|
-
async function _fireDueSchedules() {
|
|
716
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
717
|
-
const dueRows = _assertClassBrand(_Scheduler_brand, this, _sql).bind(this)`
|
|
718
|
-
SELECT * FROM cf_agents_schedules WHERE time <= ${now}
|
|
719
|
-
`;
|
|
720
|
-
_assertClassBrand(_Scheduler_brand, this, _warnStaleOneShots).call(this, dueRows);
|
|
721
|
-
for (const row of dueRows) {
|
|
722
|
-
const outcome = await _assertClassBrand(_Scheduler_brand, this, _runDueRow).call(this, row, now);
|
|
723
|
-
if (outcome === "stop") return;
|
|
724
|
-
if (outcome === "executed") _assertClassBrand(_Scheduler_brand, this, _advanceRow).call(this, row);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
/**
|
|
728
|
-
* Warn when many stale one-shot rows share the same callback — this
|
|
729
|
-
* usually means one-shot schedules were created repeatedly (e.g. in
|
|
730
|
-
* onStart) without idempotent:true and rows accumulated across restarts.
|
|
731
|
-
*/
|
|
732
|
-
function _warnStaleOneShots(dueRows) {
|
|
733
|
-
const DUPLICATE_SCHEDULE_THRESHOLD = 10;
|
|
734
|
-
const oneShotCounts = /* @__PURE__ */ new Map();
|
|
735
|
-
for (const row of dueRows) if (row.type === "delayed" || row.type === "scheduled") oneShotCounts.set(row.callback, (oneShotCounts.get(row.callback) ?? 0) + 1);
|
|
736
|
-
for (const [callback, count] of oneShotCounts) {
|
|
737
|
-
if (count < DUPLICATE_SCHEDULE_THRESHOLD) continue;
|
|
738
|
-
try {
|
|
739
|
-
console.warn(`Processing ${count} stale "${callback}" schedules in a single alarm cycle. This usually means one-shot schedules are created repeatedly without the idempotent option. Consider an interval schedule for recurring tasks, or pass { idempotent: true } when creating one-shot schedules.`);
|
|
740
|
-
_assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:duplicate_warning", {
|
|
741
|
-
callback,
|
|
742
|
-
count,
|
|
743
|
-
type: "one-shot"
|
|
744
|
-
});
|
|
745
|
-
} catch {}
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
/**
|
|
749
|
-
* Run one due row.
|
|
750
|
-
*
|
|
751
|
-
* @returns "executed" when the row ran and should advance, "skipped" when
|
|
752
|
-
* it should be left for a later alarm cycle, and "stop" when host teardown
|
|
753
|
-
* disabled further durable work mid-phase.
|
|
754
|
-
*/
|
|
755
|
-
async function _runDueRow(row, now) {
|
|
756
|
-
if (row.type === "interval" && row.running === 1) {
|
|
757
|
-
const elapsedSeconds = now - (row.execution_started_at ?? 0);
|
|
758
|
-
if (elapsedSeconds < _classPrivateFieldGet2(_hungScheduleTimeoutSeconds, this)) {
|
|
759
|
-
console.warn(`Skipping interval schedule ${row.id}: previous execution still running`);
|
|
760
|
-
return "skipped";
|
|
761
|
-
}
|
|
762
|
-
console.warn(`Forcing reset of hung interval schedule ${row.id} (started ${elapsedSeconds}s ago)`);
|
|
763
|
-
}
|
|
764
|
-
if (row.type === "interval") _assertClassBrand(_Scheduler_brand, this, _sql).bind(this)`
|
|
765
|
-
UPDATE cf_agents_schedules
|
|
766
|
-
SET running = 1, execution_started_at = ${now}
|
|
767
|
-
WHERE id = ${row.id}
|
|
768
|
-
`;
|
|
769
|
-
let executed = false;
|
|
770
|
-
if (row.owner_path) try {
|
|
771
|
-
executed = await this.lifecycle.routes.to({
|
|
772
|
-
key: row.owner_path_key ?? row.owner_path,
|
|
773
|
-
data: row.owner_path
|
|
774
|
-
}, {
|
|
775
|
-
type: "dispatch",
|
|
776
|
-
row
|
|
777
|
-
});
|
|
778
|
-
} catch (error) {
|
|
779
|
-
console.error(`error dispatching scheduled callback "${row.callback}"`, error);
|
|
780
|
-
_assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:error", {
|
|
781
|
-
callback: row.callback,
|
|
782
|
-
id: row.id,
|
|
783
|
-
error: error instanceof Error ? error.message : String(error),
|
|
784
|
-
attempts: 0
|
|
785
|
-
});
|
|
786
|
-
try {
|
|
787
|
-
await _classPrivateFieldGet2(_onError, this)?.call(this, error);
|
|
788
|
-
} catch {}
|
|
789
|
-
if (row.type === "interval") _assertClassBrand(_Scheduler_brand, this, _sql).bind(this)`
|
|
790
|
-
UPDATE cf_agents_schedules SET running = 0 WHERE id = ${row.id}
|
|
791
|
-
`;
|
|
792
|
-
return "skipped";
|
|
793
|
-
}
|
|
794
|
-
else {
|
|
795
|
-
_classPrivateFieldSet2(_executingRowId, this, row.id);
|
|
796
|
-
await _assertClassBrand(_Scheduler_brand, this, _executeCallback).call(this, row);
|
|
797
|
-
_classPrivateFieldSet2(_executingRowId, this, void 0);
|
|
798
|
-
executed = true;
|
|
799
|
-
}
|
|
800
|
-
if (this.lifecycle.alarms.disabled()) return "stop";
|
|
801
|
-
return executed ? "executed" : "skipped";
|
|
802
|
-
}
|
|
803
|
-
/** Reschedule a recurring row or delete an executed one-shot row. */
|
|
804
|
-
function _advanceRow(row) {
|
|
805
|
-
if (row.type === "cron") {
|
|
806
|
-
const nextTimestamp = Math.floor(nextCronTimeMs(row.cron ?? "", Date.now()) / 1e3);
|
|
807
|
-
_assertClassBrand(_Scheduler_brand, this, _sql).bind(this)`
|
|
808
|
-
UPDATE cf_agents_schedules SET time = ${nextTimestamp} WHERE id = ${row.id}
|
|
809
|
-
`;
|
|
810
|
-
} else if (row.type === "interval") {
|
|
811
|
-
const nextTimestamp = Math.floor(Date.now() / 1e3) + (row.intervalSeconds ?? 0);
|
|
812
|
-
_assertClassBrand(_Scheduler_brand, this, _sql).bind(this)`
|
|
813
|
-
UPDATE cf_agents_schedules
|
|
814
|
-
SET running = 0, time = ${nextTimestamp}
|
|
815
|
-
WHERE id = ${row.id}
|
|
816
|
-
`;
|
|
817
|
-
} else _assertClassBrand(_Scheduler_brand, this, _sql).bind(this)`
|
|
818
|
-
DELETE FROM cf_agents_schedules WHERE id = ${row.id}
|
|
819
|
-
`;
|
|
820
|
-
}
|
|
821
|
-
/**
|
|
822
|
-
* Earliest wall-clock time (ms) a schedule row is ready to execute,
|
|
823
|
-
* clamped to the future, or `null` when no row qualifies.
|
|
824
|
-
*/
|
|
825
|
-
function _nextScheduleTimeMs(nowMs, hungCutoffSeconds) {
|
|
826
|
-
const readySchedules = _assertClassBrand(_Scheduler_brand, this, _sql).bind(this)`
|
|
827
|
-
SELECT time FROM cf_agents_schedules
|
|
828
|
-
WHERE type != 'interval'
|
|
829
|
-
OR running = 0
|
|
830
|
-
OR coalesce(execution_started_at, 0) <= ${hungCutoffSeconds}
|
|
831
|
-
ORDER BY time ASC
|
|
832
|
-
LIMIT 1
|
|
833
|
-
`;
|
|
834
|
-
if (readySchedules.length > 0 && "time" in readySchedules[0]) return Math.max(readySchedules[0].time * 1e3, nowMs + 1);
|
|
835
|
-
return null;
|
|
836
|
-
}
|
|
837
|
-
/**
|
|
838
|
-
* Wall-clock time (ms) at which the earliest still-running (not yet hung)
|
|
839
|
-
* interval schedule crosses the hung timeout and must be re-checked, or
|
|
840
|
-
* `null` when none is running.
|
|
841
|
-
*/
|
|
842
|
-
function _nextHungIntervalRecheckMs(hungCutoffSeconds) {
|
|
843
|
-
const startedAt = _assertClassBrand(_Scheduler_brand, this, _sql).bind(this)`
|
|
844
|
-
SELECT execution_started_at FROM cf_agents_schedules
|
|
845
|
-
WHERE type = 'interval'
|
|
846
|
-
AND running = 1
|
|
847
|
-
AND coalesce(execution_started_at, 0) > ${hungCutoffSeconds}
|
|
848
|
-
ORDER BY execution_started_at ASC
|
|
849
|
-
LIMIT 1
|
|
850
|
-
`[0]?.execution_started_at;
|
|
851
|
-
if (startedAt !== null && startedAt !== void 0) return (startedAt + _classPrivateFieldGet2(_hungScheduleTimeoutSeconds, this)) * 1e3;
|
|
852
|
-
return null;
|
|
853
|
-
}
|
|
854
|
-
//#endregion
|
|
855
|
-
export { ensureScheduleTable as n, setSchedulerCallbackResolver as r, Scheduler as t };
|
|
856
|
-
|
|
857
|
-
//# sourceMappingURL=scheduler-CR9RHGos.js.map
|