agents 0.7.0 → 0.7.2
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/dist/{agent-DnmmRjyv.d.ts → agent-eZnMHidZ.d.ts} +43 -1
- package/dist/{client-storage-tusTuoSF.d.ts → client-storage-BPjfP_is.d.ts} +2 -2
- package/dist/experimental/forever.d.ts +3 -2
- package/dist/index.d.ts +35 -5
- package/dist/index.js +179 -83
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.d.ts +2 -2
- package/dist/mcp/index.d.ts +1 -1
- package/dist/observability/index.d.ts +6 -2
- package/dist/observability/index.js +3 -1
- package/dist/observability/index.js.map +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/workflows.d.ts +1 -1
- package/package.json +2 -2
|
@@ -7,6 +7,17 @@ type BaseEvent<
|
|
|
7
7
|
Payload extends Record<string, unknown> = Record<string, never>
|
|
8
8
|
> = {
|
|
9
9
|
type: T;
|
|
10
|
+
/**
|
|
11
|
+
* The class name of the agent that emitted this event
|
|
12
|
+
* (e.g. "MyChatAgent").
|
|
13
|
+
* Always present on events emitted by an Agent instance.
|
|
14
|
+
*/
|
|
15
|
+
agent?: string;
|
|
16
|
+
/**
|
|
17
|
+
* The instance name (Durable Object ID name) of the agent.
|
|
18
|
+
* Always present on events emitted by an Agent instance.
|
|
19
|
+
*/
|
|
20
|
+
name?: string;
|
|
10
21
|
/**
|
|
11
22
|
* The payload of the event
|
|
12
23
|
*/
|
|
@@ -145,6 +156,13 @@ type AgentObservabilityEvent =
|
|
|
145
156
|
attempts: number;
|
|
146
157
|
}
|
|
147
158
|
>
|
|
159
|
+
| BaseEvent<
|
|
160
|
+
"queue:create",
|
|
161
|
+
{
|
|
162
|
+
callback: string;
|
|
163
|
+
id: string;
|
|
164
|
+
}
|
|
165
|
+
>
|
|
148
166
|
| BaseEvent<
|
|
149
167
|
"queue:retry",
|
|
150
168
|
{
|
|
@@ -170,6 +188,30 @@ type AgentObservabilityEvent =
|
|
|
170
188
|
connectionId: string;
|
|
171
189
|
}
|
|
172
190
|
>
|
|
191
|
+
| BaseEvent<
|
|
192
|
+
"disconnect",
|
|
193
|
+
{
|
|
194
|
+
connectionId: string;
|
|
195
|
+
code: number;
|
|
196
|
+
reason: string;
|
|
197
|
+
}
|
|
198
|
+
>
|
|
199
|
+
| BaseEvent<
|
|
200
|
+
"email:receive",
|
|
201
|
+
{
|
|
202
|
+
from: string;
|
|
203
|
+
to: string;
|
|
204
|
+
subject?: string;
|
|
205
|
+
}
|
|
206
|
+
>
|
|
207
|
+
| BaseEvent<
|
|
208
|
+
"email:reply",
|
|
209
|
+
{
|
|
210
|
+
from: string;
|
|
211
|
+
to: string;
|
|
212
|
+
subject?: string;
|
|
213
|
+
}
|
|
214
|
+
>
|
|
173
215
|
| BaseEvent<
|
|
174
216
|
"workflow:start",
|
|
175
217
|
{
|
|
@@ -228,4 +270,4 @@ type AgentObservabilityEvent =
|
|
|
228
270
|
>;
|
|
229
271
|
//#endregion
|
|
230
272
|
export { MCPObservabilityEvent as n, AgentObservabilityEvent as t };
|
|
231
|
-
//# sourceMappingURL=agent-
|
|
273
|
+
//# sourceMappingURL=agent-eZnMHidZ.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as MCPObservabilityEvent } from "./agent-
|
|
1
|
+
import { n as MCPObservabilityEvent } from "./agent-eZnMHidZ.js";
|
|
2
2
|
import { AgentMcpOAuthProvider } from "./mcp/do-oauth-client-provider.js";
|
|
3
3
|
import { McpAgent } from "./mcp/index.js";
|
|
4
4
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
@@ -601,4 +601,4 @@ export {
|
|
|
601
601
|
MaybePromise as x,
|
|
602
602
|
StreamableHTTPEdgeClientTransport as y
|
|
603
603
|
};
|
|
604
|
-
//# sourceMappingURL=client-storage-
|
|
604
|
+
//# sourceMappingURL=client-storage-BPjfP_is.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RetryOptions } from "../retries.js";
|
|
2
|
-
import "../client-storage-
|
|
2
|
+
import "../client-storage-BPjfP_is.js";
|
|
3
3
|
import { Agent, Schedule } from "../index.js";
|
|
4
4
|
|
|
5
5
|
//#region src/experimental/forever.d.ts
|
|
@@ -92,12 +92,13 @@ declare function withFibers<TBase extends AgentLike>(Base: TBase, options?: {
|
|
|
92
92
|
_checkInterruptedFibers(): Promise<void>; /** @internal */
|
|
93
93
|
_cleanupOrphanedHeartbeats(): void; /** @internal */
|
|
94
94
|
_maybeCleanupFibers(): void;
|
|
95
|
-
readonly alarm: () => Promise<void>;
|
|
96
95
|
sql: <T = Record<string, string | number | boolean | null>>(strings: TemplateStringsArray, ...values: (string | number | boolean | null)[]) => T[];
|
|
97
96
|
scheduleEvery: <T = string>(intervalSeconds: number, callback: keyof Agent<Cloudflare.Env, unknown, Record<string, unknown>>, payload?: T | undefined, options?: {
|
|
98
97
|
retry?: RetryOptions;
|
|
98
|
+
_idempotent?: boolean;
|
|
99
99
|
}) => Promise<Schedule<T>>;
|
|
100
100
|
cancelSchedule: (id: string) => Promise<boolean>;
|
|
101
|
+
alarm: () => Promise<void>;
|
|
101
102
|
keepAlive: () => Promise<() => void>;
|
|
102
103
|
keepAliveWhile: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
103
104
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { RetryOptions } from "./retries.js";
|
|
|
7
7
|
import {
|
|
8
8
|
r as MCPConnectionState,
|
|
9
9
|
w as TransportType
|
|
10
|
-
} from "./client-storage-
|
|
10
|
+
} from "./client-storage-BPjfP_is.js";
|
|
11
11
|
import {
|
|
12
12
|
AgentMcpOAuthProvider,
|
|
13
13
|
AgentsOAuthProvider,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
WorkflowPage,
|
|
24
24
|
WorkflowQueryCriteria
|
|
25
25
|
} from "./workflow-types.js";
|
|
26
|
-
import { Observability } from "./observability/index.js";
|
|
26
|
+
import { Observability, ObservabilityEvent } from "./observability/index.js";
|
|
27
27
|
import { MessageType } from "./types.js";
|
|
28
28
|
import {
|
|
29
29
|
Connection,
|
|
@@ -325,7 +325,10 @@ declare class Agent<
|
|
|
325
325
|
* Emit an observability event with auto-generated timestamp.
|
|
326
326
|
* @internal
|
|
327
327
|
*/
|
|
328
|
-
|
|
328
|
+
protected _emit(
|
|
329
|
+
type: ObservabilityEvent["type"],
|
|
330
|
+
payload?: Record<string, unknown>
|
|
331
|
+
): void;
|
|
329
332
|
/**
|
|
330
333
|
* Execute SQL queries against the Agent's database
|
|
331
334
|
* @template T Type of the returned rows
|
|
@@ -593,7 +596,23 @@ declare class Agent<
|
|
|
593
596
|
}
|
|
594
597
|
): Promise<Schedule<T>>;
|
|
595
598
|
/**
|
|
596
|
-
* Schedule a task to run repeatedly at a fixed interval
|
|
599
|
+
* Schedule a task to run repeatedly at a fixed interval.
|
|
600
|
+
*
|
|
601
|
+
* This method is **idempotent** — calling it multiple times with the same
|
|
602
|
+
* `callback`, `intervalSeconds`, and `payload` returns the existing schedule
|
|
603
|
+
* instead of creating a duplicate. A different interval or payload is
|
|
604
|
+
* treated as a distinct schedule and creates a new row.
|
|
605
|
+
*
|
|
606
|
+
* This makes it safe to call in `onStart()`, which runs on every Durable
|
|
607
|
+
* Object wake:
|
|
608
|
+
*
|
|
609
|
+
* ```ts
|
|
610
|
+
* async onStart() {
|
|
611
|
+
* // Only one schedule is created, no matter how many times the DO wakes
|
|
612
|
+
* await this.scheduleEvery(30, "tick");
|
|
613
|
+
* }
|
|
614
|
+
* ```
|
|
615
|
+
*
|
|
597
616
|
* @template T Type of the payload data
|
|
598
617
|
* @param intervalSeconds Number of seconds between executions
|
|
599
618
|
* @param callback Name of the method to call
|
|
@@ -608,6 +627,7 @@ declare class Agent<
|
|
|
608
627
|
payload?: T,
|
|
609
628
|
options?: {
|
|
610
629
|
retry?: RetryOptions;
|
|
630
|
+
_idempotent?: boolean;
|
|
611
631
|
}
|
|
612
632
|
): Promise<Schedule<T>>;
|
|
613
633
|
/**
|
|
@@ -685,15 +705,25 @@ declare class Agent<
|
|
|
685
705
|
*/
|
|
686
706
|
_cf_keepAliveHeartbeat(): Promise<void>;
|
|
687
707
|
private _scheduleNextAlarm;
|
|
708
|
+
/**
|
|
709
|
+
* Override PartyServer's onAlarm hook as a no-op.
|
|
710
|
+
* Agent handles alarm logic directly in the alarm() method override,
|
|
711
|
+
* but super.alarm() calls onAlarm() after #ensureInitialized(),
|
|
712
|
+
* so we suppress the default "Implement onAlarm" warning.
|
|
713
|
+
*/
|
|
714
|
+
onAlarm(): void;
|
|
688
715
|
/**
|
|
689
716
|
* Method called when an alarm fires.
|
|
690
717
|
* Executes any scheduled tasks that are due.
|
|
691
718
|
*
|
|
719
|
+
* Calls super.alarm() first to ensure PartyServer's #ensureInitialized()
|
|
720
|
+
* runs, which hydrates this.name from storage and calls onStart() if needed.
|
|
721
|
+
*
|
|
692
722
|
* @remarks
|
|
693
723
|
* To schedule a task, please use the `this.schedule` method instead.
|
|
694
724
|
* See {@link https://developers.cloudflare.com/agents/api-reference/schedule-tasks/}
|
|
695
725
|
*/
|
|
696
|
-
|
|
726
|
+
alarm(): Promise<void>;
|
|
697
727
|
/**
|
|
698
728
|
* Destroy the Agent, removing all state and scheduled tasks
|
|
699
729
|
*/
|
package/dist/index.js
CHANGED
|
@@ -260,6 +260,8 @@ var Agent = class Agent extends Server {
|
|
|
260
260
|
_emit(type, payload = {}) {
|
|
261
261
|
this.observability?.emit({
|
|
262
262
|
type,
|
|
263
|
+
agent: this._ParentClass.name,
|
|
264
|
+
name: this.name,
|
|
263
265
|
payload,
|
|
264
266
|
timestamp: Date.now()
|
|
265
267
|
});
|
|
@@ -291,85 +293,6 @@ var Agent = class Agent extends Server {
|
|
|
291
293
|
this.initialState = DEFAULT_STATE;
|
|
292
294
|
this.observability = genericObservability;
|
|
293
295
|
this._flushingQueue = false;
|
|
294
|
-
this.alarm = async () => {
|
|
295
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
296
|
-
const result = this.sql`
|
|
297
|
-
SELECT * FROM cf_agents_schedules WHERE time <= ${now}
|
|
298
|
-
`;
|
|
299
|
-
if (result && Array.isArray(result)) for (const row of result) {
|
|
300
|
-
const callback = this[row.callback];
|
|
301
|
-
if (!callback) {
|
|
302
|
-
console.error(`callback ${row.callback} not found`);
|
|
303
|
-
continue;
|
|
304
|
-
}
|
|
305
|
-
if (row.type === "interval" && row.running === 1) {
|
|
306
|
-
const executionStartedAt = row.execution_started_at ?? 0;
|
|
307
|
-
const hungTimeoutSeconds = this._resolvedOptions.hungScheduleTimeoutSeconds;
|
|
308
|
-
const elapsedSeconds = now - executionStartedAt;
|
|
309
|
-
if (elapsedSeconds < hungTimeoutSeconds) {
|
|
310
|
-
console.warn(`Skipping interval schedule ${row.id}: previous execution still running`);
|
|
311
|
-
continue;
|
|
312
|
-
}
|
|
313
|
-
console.warn(`Forcing reset of hung interval schedule ${row.id} (started ${elapsedSeconds}s ago)`);
|
|
314
|
-
}
|
|
315
|
-
if (row.type === "interval") this.sql`UPDATE cf_agents_schedules SET running = 1, execution_started_at = ${now} WHERE id = ${row.id}`;
|
|
316
|
-
await __DO_NOT_USE_WILL_BREAK__agentContext.run({
|
|
317
|
-
agent: this,
|
|
318
|
-
connection: void 0,
|
|
319
|
-
request: void 0,
|
|
320
|
-
email: void 0
|
|
321
|
-
}, async () => {
|
|
322
|
-
const { maxAttempts, baseDelayMs, maxDelayMs } = resolveRetryConfig(parseRetryOptions(row), this._resolvedOptions.retry);
|
|
323
|
-
const parsedPayload = JSON.parse(row.payload);
|
|
324
|
-
try {
|
|
325
|
-
this._emit("schedule:execute", {
|
|
326
|
-
callback: row.callback,
|
|
327
|
-
id: row.id
|
|
328
|
-
});
|
|
329
|
-
await tryN(maxAttempts, async (attempt) => {
|
|
330
|
-
if (attempt > 1) this._emit("schedule:retry", {
|
|
331
|
-
callback: row.callback,
|
|
332
|
-
id: row.id,
|
|
333
|
-
attempt,
|
|
334
|
-
maxAttempts
|
|
335
|
-
});
|
|
336
|
-
await callback.bind(this)(parsedPayload, row);
|
|
337
|
-
}, {
|
|
338
|
-
baseDelayMs,
|
|
339
|
-
maxDelayMs
|
|
340
|
-
});
|
|
341
|
-
} catch (e) {
|
|
342
|
-
console.error(`error executing callback "${row.callback}" after ${maxAttempts} attempts`, e);
|
|
343
|
-
this._emit("schedule:error", {
|
|
344
|
-
callback: row.callback,
|
|
345
|
-
id: row.id,
|
|
346
|
-
error: e instanceof Error ? e.message : String(e),
|
|
347
|
-
attempts: maxAttempts
|
|
348
|
-
});
|
|
349
|
-
try {
|
|
350
|
-
await this.onError(e);
|
|
351
|
-
} catch {}
|
|
352
|
-
}
|
|
353
|
-
});
|
|
354
|
-
if (this._destroyed) return;
|
|
355
|
-
if (row.type === "cron") {
|
|
356
|
-
const nextExecutionTime = getNextCronTime(row.cron);
|
|
357
|
-
const nextTimestamp = Math.floor(nextExecutionTime.getTime() / 1e3);
|
|
358
|
-
this.sql`
|
|
359
|
-
UPDATE cf_agents_schedules SET time = ${nextTimestamp} WHERE id = ${row.id}
|
|
360
|
-
`;
|
|
361
|
-
} else if (row.type === "interval") {
|
|
362
|
-
const nextTimestamp = Math.floor(Date.now() / 1e3) + (row.intervalSeconds ?? 0);
|
|
363
|
-
this.sql`
|
|
364
|
-
UPDATE cf_agents_schedules SET running = 0, time = ${nextTimestamp} WHERE id = ${row.id}
|
|
365
|
-
`;
|
|
366
|
-
} else this.sql`
|
|
367
|
-
DELETE FROM cf_agents_schedules WHERE id = ${row.id}
|
|
368
|
-
`;
|
|
369
|
-
}
|
|
370
|
-
if (this._destroyed) return;
|
|
371
|
-
await this._scheduleNextAlarm();
|
|
372
|
-
};
|
|
373
296
|
if (!wrappedClasses.has(this.constructor)) {
|
|
374
297
|
this._autoWrapCustomMethods();
|
|
375
298
|
wrappedClasses.add(this.constructor);
|
|
@@ -457,7 +380,11 @@ var Agent = class Agent extends Server {
|
|
|
457
380
|
this.broadcastMcpServers();
|
|
458
381
|
}));
|
|
459
382
|
this._disposables.add(this.mcp.onObservabilityEvent((event) => {
|
|
460
|
-
this.observability?.emit(
|
|
383
|
+
this.observability?.emit({
|
|
384
|
+
...event,
|
|
385
|
+
agent: this._ParentClass.name,
|
|
386
|
+
name: this.name
|
|
387
|
+
});
|
|
461
388
|
}));
|
|
462
389
|
{
|
|
463
390
|
const proto = Object.getPrototypeOf(this);
|
|
@@ -620,6 +547,22 @@ var Agent = class Agent extends Server {
|
|
|
620
547
|
return this._tryCatch(() => _onConnect(connection, ctx));
|
|
621
548
|
});
|
|
622
549
|
};
|
|
550
|
+
const _onClose = this.onClose.bind(this);
|
|
551
|
+
this.onClose = (connection, code, reason, wasClean) => {
|
|
552
|
+
return __DO_NOT_USE_WILL_BREAK__agentContext.run({
|
|
553
|
+
agent: this,
|
|
554
|
+
connection,
|
|
555
|
+
request: void 0,
|
|
556
|
+
email: void 0
|
|
557
|
+
}, () => {
|
|
558
|
+
this._emit("disconnect", {
|
|
559
|
+
connectionId: connection.id,
|
|
560
|
+
code,
|
|
561
|
+
reason
|
|
562
|
+
});
|
|
563
|
+
return _onClose(connection, code, reason, wasClean);
|
|
564
|
+
});
|
|
565
|
+
};
|
|
623
566
|
const _onStart = this.onStart.bind(this);
|
|
624
567
|
this.onStart = async (props) => {
|
|
625
568
|
return __DO_NOT_USE_WILL_BREAK__agentContext.run({
|
|
@@ -917,6 +860,11 @@ var Agent = class Agent extends Server {
|
|
|
917
860
|
request: void 0,
|
|
918
861
|
email
|
|
919
862
|
}, async () => {
|
|
863
|
+
this._emit("email:receive", {
|
|
864
|
+
from: email.from,
|
|
865
|
+
to: email.to,
|
|
866
|
+
subject: email.headers.get("subject") ?? void 0
|
|
867
|
+
});
|
|
920
868
|
if ("onEmail" in this && typeof this.onEmail === "function") return this._tryCatch(() => this.onEmail(email));
|
|
921
869
|
else {
|
|
922
870
|
console.log("Received email from:", email.from, "to:", email.to);
|
|
@@ -967,6 +915,12 @@ var Agent = class Agent extends Server {
|
|
|
967
915
|
raw: msg.asRaw(),
|
|
968
916
|
to: email.from
|
|
969
917
|
});
|
|
918
|
+
const rawSubject = email.headers.get("subject");
|
|
919
|
+
this._emit("email:reply", {
|
|
920
|
+
from: email.to,
|
|
921
|
+
to: email.from,
|
|
922
|
+
subject: options.subject ?? (rawSubject ? `Re: ${rawSubject}` : void 0)
|
|
923
|
+
});
|
|
970
924
|
});
|
|
971
925
|
}
|
|
972
926
|
async _tryCatch(fn) {
|
|
@@ -1065,6 +1019,10 @@ var Agent = class Agent extends Server {
|
|
|
1065
1019
|
INSERT OR REPLACE INTO cf_agents_queues (id, payload, callback, retry_options)
|
|
1066
1020
|
VALUES (${id}, ${JSON.stringify(payload)}, ${callback}, ${retryJson})
|
|
1067
1021
|
`;
|
|
1022
|
+
this._emit("queue:create", {
|
|
1023
|
+
callback,
|
|
1024
|
+
id
|
|
1025
|
+
});
|
|
1068
1026
|
this._flushQueue().catch((e) => {
|
|
1069
1027
|
console.error("Error flushing queue:", e);
|
|
1070
1028
|
});
|
|
@@ -1263,7 +1221,23 @@ var Agent = class Agent extends Server {
|
|
|
1263
1221
|
throw new Error(`Invalid schedule type: ${JSON.stringify(when)}(${typeof when}) trying to schedule ${callback}`);
|
|
1264
1222
|
}
|
|
1265
1223
|
/**
|
|
1266
|
-
* Schedule a task to run repeatedly at a fixed interval
|
|
1224
|
+
* Schedule a task to run repeatedly at a fixed interval.
|
|
1225
|
+
*
|
|
1226
|
+
* This method is **idempotent** — calling it multiple times with the same
|
|
1227
|
+
* `callback`, `intervalSeconds`, and `payload` returns the existing schedule
|
|
1228
|
+
* instead of creating a duplicate. A different interval or payload is
|
|
1229
|
+
* treated as a distinct schedule and creates a new row.
|
|
1230
|
+
*
|
|
1231
|
+
* This makes it safe to call in `onStart()`, which runs on every Durable
|
|
1232
|
+
* Object wake:
|
|
1233
|
+
*
|
|
1234
|
+
* ```ts
|
|
1235
|
+
* async onStart() {
|
|
1236
|
+
* // Only one schedule is created, no matter how many times the DO wakes
|
|
1237
|
+
* await this.scheduleEvery(30, "tick");
|
|
1238
|
+
* }
|
|
1239
|
+
* ```
|
|
1240
|
+
*
|
|
1267
1241
|
* @template T Type of the payload data
|
|
1268
1242
|
* @param intervalSeconds Number of seconds between executions
|
|
1269
1243
|
* @param callback Name of the method to call
|
|
@@ -1279,13 +1253,37 @@ var Agent = class Agent extends Server {
|
|
|
1279
1253
|
if (typeof callback !== "string") throw new Error("Callback must be a string");
|
|
1280
1254
|
if (typeof this[callback] !== "function") throw new Error(`this.${callback} is not a function`);
|
|
1281
1255
|
if (options?.retry) validateRetryOptions(options.retry, this._resolvedOptions.retry);
|
|
1256
|
+
const idempotent = options?._idempotent !== false;
|
|
1257
|
+
const payloadJson = JSON.stringify(payload);
|
|
1258
|
+
if (idempotent) {
|
|
1259
|
+
const existing = this.sql`
|
|
1260
|
+
SELECT * FROM cf_agents_schedules
|
|
1261
|
+
WHERE type = 'interval'
|
|
1262
|
+
AND callback = ${callback}
|
|
1263
|
+
AND intervalSeconds = ${intervalSeconds}
|
|
1264
|
+
AND payload IS ${payloadJson}
|
|
1265
|
+
LIMIT 1
|
|
1266
|
+
`;
|
|
1267
|
+
if (existing.length > 0) {
|
|
1268
|
+
const row = existing[0];
|
|
1269
|
+
return {
|
|
1270
|
+
callback: row.callback,
|
|
1271
|
+
id: row.id,
|
|
1272
|
+
intervalSeconds: row.intervalSeconds,
|
|
1273
|
+
payload: JSON.parse(row.payload),
|
|
1274
|
+
retry: parseRetryOptions(row),
|
|
1275
|
+
time: row.time,
|
|
1276
|
+
type: "interval"
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1282
1280
|
const id = nanoid(9);
|
|
1283
1281
|
const time = new Date(Date.now() + intervalSeconds * 1e3);
|
|
1284
1282
|
const timestamp = Math.floor(time.getTime() / 1e3);
|
|
1285
1283
|
const retryJson = options?.retry ? JSON.stringify(options.retry) : null;
|
|
1286
1284
|
this.sql`
|
|
1287
1285
|
INSERT OR REPLACE INTO cf_agents_schedules (id, callback, payload, type, intervalSeconds, time, running, retry_options)
|
|
1288
|
-
VALUES (${id}, ${callback}, ${
|
|
1286
|
+
VALUES (${id}, ${callback}, ${payloadJson}, 'interval', ${intervalSeconds}, ${timestamp}, 0, ${retryJson})
|
|
1289
1287
|
`;
|
|
1290
1288
|
await this._scheduleNextAlarm();
|
|
1291
1289
|
const schedule = {
|
|
@@ -1388,7 +1386,7 @@ var Agent = class Agent extends Server {
|
|
|
1388
1386
|
*/
|
|
1389
1387
|
async keepAlive() {
|
|
1390
1388
|
const heartbeatSeconds = Math.ceil(KEEP_ALIVE_INTERVAL_MS / 1e3);
|
|
1391
|
-
const schedule = await this.scheduleEvery(heartbeatSeconds, "_cf_keepAliveHeartbeat");
|
|
1389
|
+
const schedule = await this.scheduleEvery(heartbeatSeconds, "_cf_keepAliveHeartbeat", void 0, { _idempotent: false });
|
|
1392
1390
|
let disposed = false;
|
|
1393
1391
|
return () => {
|
|
1394
1392
|
if (disposed) return;
|
|
@@ -1443,6 +1441,104 @@ var Agent = class Agent extends Server {
|
|
|
1443
1441
|
}
|
|
1444
1442
|
}
|
|
1445
1443
|
/**
|
|
1444
|
+
* Override PartyServer's onAlarm hook as a no-op.
|
|
1445
|
+
* Agent handles alarm logic directly in the alarm() method override,
|
|
1446
|
+
* but super.alarm() calls onAlarm() after #ensureInitialized(),
|
|
1447
|
+
* so we suppress the default "Implement onAlarm" warning.
|
|
1448
|
+
*/
|
|
1449
|
+
onAlarm() {}
|
|
1450
|
+
/**
|
|
1451
|
+
* Method called when an alarm fires.
|
|
1452
|
+
* Executes any scheduled tasks that are due.
|
|
1453
|
+
*
|
|
1454
|
+
* Calls super.alarm() first to ensure PartyServer's #ensureInitialized()
|
|
1455
|
+
* runs, which hydrates this.name from storage and calls onStart() if needed.
|
|
1456
|
+
*
|
|
1457
|
+
* @remarks
|
|
1458
|
+
* To schedule a task, please use the `this.schedule` method instead.
|
|
1459
|
+
* See {@link https://developers.cloudflare.com/agents/api-reference/schedule-tasks/}
|
|
1460
|
+
*/
|
|
1461
|
+
async alarm() {
|
|
1462
|
+
await super.alarm();
|
|
1463
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1464
|
+
const result = this.sql`
|
|
1465
|
+
SELECT * FROM cf_agents_schedules WHERE time <= ${now}
|
|
1466
|
+
`;
|
|
1467
|
+
if (result && Array.isArray(result)) for (const row of result) {
|
|
1468
|
+
const callback = this[row.callback];
|
|
1469
|
+
if (!callback) {
|
|
1470
|
+
console.error(`callback ${row.callback} not found`);
|
|
1471
|
+
continue;
|
|
1472
|
+
}
|
|
1473
|
+
if (row.type === "interval" && row.running === 1) {
|
|
1474
|
+
const executionStartedAt = row.execution_started_at ?? 0;
|
|
1475
|
+
const hungTimeoutSeconds = this._resolvedOptions.hungScheduleTimeoutSeconds;
|
|
1476
|
+
const elapsedSeconds = now - executionStartedAt;
|
|
1477
|
+
if (elapsedSeconds < hungTimeoutSeconds) {
|
|
1478
|
+
console.warn(`Skipping interval schedule ${row.id}: previous execution still running`);
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
console.warn(`Forcing reset of hung interval schedule ${row.id} (started ${elapsedSeconds}s ago)`);
|
|
1482
|
+
}
|
|
1483
|
+
if (row.type === "interval") this.sql`UPDATE cf_agents_schedules SET running = 1, execution_started_at = ${now} WHERE id = ${row.id}`;
|
|
1484
|
+
await __DO_NOT_USE_WILL_BREAK__agentContext.run({
|
|
1485
|
+
agent: this,
|
|
1486
|
+
connection: void 0,
|
|
1487
|
+
request: void 0,
|
|
1488
|
+
email: void 0
|
|
1489
|
+
}, async () => {
|
|
1490
|
+
const { maxAttempts, baseDelayMs, maxDelayMs } = resolveRetryConfig(parseRetryOptions(row), this._resolvedOptions.retry);
|
|
1491
|
+
const parsedPayload = JSON.parse(row.payload);
|
|
1492
|
+
try {
|
|
1493
|
+
this._emit("schedule:execute", {
|
|
1494
|
+
callback: row.callback,
|
|
1495
|
+
id: row.id
|
|
1496
|
+
});
|
|
1497
|
+
await tryN(maxAttempts, async (attempt) => {
|
|
1498
|
+
if (attempt > 1) this._emit("schedule:retry", {
|
|
1499
|
+
callback: row.callback,
|
|
1500
|
+
id: row.id,
|
|
1501
|
+
attempt,
|
|
1502
|
+
maxAttempts
|
|
1503
|
+
});
|
|
1504
|
+
await callback.bind(this)(parsedPayload, row);
|
|
1505
|
+
}, {
|
|
1506
|
+
baseDelayMs,
|
|
1507
|
+
maxDelayMs
|
|
1508
|
+
});
|
|
1509
|
+
} catch (e) {
|
|
1510
|
+
console.error(`error executing callback "${row.callback}" after ${maxAttempts} attempts`, e);
|
|
1511
|
+
this._emit("schedule:error", {
|
|
1512
|
+
callback: row.callback,
|
|
1513
|
+
id: row.id,
|
|
1514
|
+
error: e instanceof Error ? e.message : String(e),
|
|
1515
|
+
attempts: maxAttempts
|
|
1516
|
+
});
|
|
1517
|
+
try {
|
|
1518
|
+
await this.onError(e);
|
|
1519
|
+
} catch {}
|
|
1520
|
+
}
|
|
1521
|
+
});
|
|
1522
|
+
if (this._destroyed) return;
|
|
1523
|
+
if (row.type === "cron") {
|
|
1524
|
+
const nextExecutionTime = getNextCronTime(row.cron);
|
|
1525
|
+
const nextTimestamp = Math.floor(nextExecutionTime.getTime() / 1e3);
|
|
1526
|
+
this.sql`
|
|
1527
|
+
UPDATE cf_agents_schedules SET time = ${nextTimestamp} WHERE id = ${row.id}
|
|
1528
|
+
`;
|
|
1529
|
+
} else if (row.type === "interval") {
|
|
1530
|
+
const nextTimestamp = Math.floor(Date.now() / 1e3) + (row.intervalSeconds ?? 0);
|
|
1531
|
+
this.sql`
|
|
1532
|
+
UPDATE cf_agents_schedules SET running = 0, time = ${nextTimestamp} WHERE id = ${row.id}
|
|
1533
|
+
`;
|
|
1534
|
+
} else this.sql`
|
|
1535
|
+
DELETE FROM cf_agents_schedules WHERE id = ${row.id}
|
|
1536
|
+
`;
|
|
1537
|
+
}
|
|
1538
|
+
if (this._destroyed) return;
|
|
1539
|
+
await this._scheduleNextAlarm();
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1446
1542
|
* Destroy the Agent, removing all state and scheduled tasks
|
|
1447
1543
|
*/
|
|
1448
1544
|
async destroy() {
|