agents 0.7.1 → 0.7.3
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/experimental/forever.d.ts +2 -1
- package/dist/experimental/sub-agent.d.ts +205 -0
- package/dist/experimental/sub-agent.js +191 -0
- package/dist/experimental/sub-agent.js.map +1 -0
- package/dist/index.d.ts +31 -4
- package/dist/index.js +146 -84
- package/dist/index.js.map +1 -1
- package/package.json +11 -6
package/dist/index.js
CHANGED
|
@@ -293,85 +293,6 @@ var Agent = class Agent extends Server {
|
|
|
293
293
|
this.initialState = DEFAULT_STATE;
|
|
294
294
|
this.observability = genericObservability;
|
|
295
295
|
this._flushingQueue = false;
|
|
296
|
-
this.alarm = async () => {
|
|
297
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
298
|
-
const result = this.sql`
|
|
299
|
-
SELECT * FROM cf_agents_schedules WHERE time <= ${now}
|
|
300
|
-
`;
|
|
301
|
-
if (result && Array.isArray(result)) for (const row of result) {
|
|
302
|
-
const callback = this[row.callback];
|
|
303
|
-
if (!callback) {
|
|
304
|
-
console.error(`callback ${row.callback} not found`);
|
|
305
|
-
continue;
|
|
306
|
-
}
|
|
307
|
-
if (row.type === "interval" && row.running === 1) {
|
|
308
|
-
const executionStartedAt = row.execution_started_at ?? 0;
|
|
309
|
-
const hungTimeoutSeconds = this._resolvedOptions.hungScheduleTimeoutSeconds;
|
|
310
|
-
const elapsedSeconds = now - executionStartedAt;
|
|
311
|
-
if (elapsedSeconds < hungTimeoutSeconds) {
|
|
312
|
-
console.warn(`Skipping interval schedule ${row.id}: previous execution still running`);
|
|
313
|
-
continue;
|
|
314
|
-
}
|
|
315
|
-
console.warn(`Forcing reset of hung interval schedule ${row.id} (started ${elapsedSeconds}s ago)`);
|
|
316
|
-
}
|
|
317
|
-
if (row.type === "interval") this.sql`UPDATE cf_agents_schedules SET running = 1, execution_started_at = ${now} WHERE id = ${row.id}`;
|
|
318
|
-
await __DO_NOT_USE_WILL_BREAK__agentContext.run({
|
|
319
|
-
agent: this,
|
|
320
|
-
connection: void 0,
|
|
321
|
-
request: void 0,
|
|
322
|
-
email: void 0
|
|
323
|
-
}, async () => {
|
|
324
|
-
const { maxAttempts, baseDelayMs, maxDelayMs } = resolveRetryConfig(parseRetryOptions(row), this._resolvedOptions.retry);
|
|
325
|
-
const parsedPayload = JSON.parse(row.payload);
|
|
326
|
-
try {
|
|
327
|
-
this._emit("schedule:execute", {
|
|
328
|
-
callback: row.callback,
|
|
329
|
-
id: row.id
|
|
330
|
-
});
|
|
331
|
-
await tryN(maxAttempts, async (attempt) => {
|
|
332
|
-
if (attempt > 1) this._emit("schedule:retry", {
|
|
333
|
-
callback: row.callback,
|
|
334
|
-
id: row.id,
|
|
335
|
-
attempt,
|
|
336
|
-
maxAttempts
|
|
337
|
-
});
|
|
338
|
-
await callback.bind(this)(parsedPayload, row);
|
|
339
|
-
}, {
|
|
340
|
-
baseDelayMs,
|
|
341
|
-
maxDelayMs
|
|
342
|
-
});
|
|
343
|
-
} catch (e) {
|
|
344
|
-
console.error(`error executing callback "${row.callback}" after ${maxAttempts} attempts`, e);
|
|
345
|
-
this._emit("schedule:error", {
|
|
346
|
-
callback: row.callback,
|
|
347
|
-
id: row.id,
|
|
348
|
-
error: e instanceof Error ? e.message : String(e),
|
|
349
|
-
attempts: maxAttempts
|
|
350
|
-
});
|
|
351
|
-
try {
|
|
352
|
-
await this.onError(e);
|
|
353
|
-
} catch {}
|
|
354
|
-
}
|
|
355
|
-
});
|
|
356
|
-
if (this._destroyed) return;
|
|
357
|
-
if (row.type === "cron") {
|
|
358
|
-
const nextExecutionTime = getNextCronTime(row.cron);
|
|
359
|
-
const nextTimestamp = Math.floor(nextExecutionTime.getTime() / 1e3);
|
|
360
|
-
this.sql`
|
|
361
|
-
UPDATE cf_agents_schedules SET time = ${nextTimestamp} WHERE id = ${row.id}
|
|
362
|
-
`;
|
|
363
|
-
} else if (row.type === "interval") {
|
|
364
|
-
const nextTimestamp = Math.floor(Date.now() / 1e3) + (row.intervalSeconds ?? 0);
|
|
365
|
-
this.sql`
|
|
366
|
-
UPDATE cf_agents_schedules SET running = 0, time = ${nextTimestamp} WHERE id = ${row.id}
|
|
367
|
-
`;
|
|
368
|
-
} else this.sql`
|
|
369
|
-
DELETE FROM cf_agents_schedules WHERE id = ${row.id}
|
|
370
|
-
`;
|
|
371
|
-
}
|
|
372
|
-
if (this._destroyed) return;
|
|
373
|
-
await this._scheduleNextAlarm();
|
|
374
|
-
};
|
|
375
296
|
if (!wrappedClasses.has(this.constructor)) {
|
|
376
297
|
this._autoWrapCustomMethods();
|
|
377
298
|
wrappedClasses.add(this.constructor);
|
|
@@ -1300,7 +1221,23 @@ var Agent = class Agent extends Server {
|
|
|
1300
1221
|
throw new Error(`Invalid schedule type: ${JSON.stringify(when)}(${typeof when}) trying to schedule ${callback}`);
|
|
1301
1222
|
}
|
|
1302
1223
|
/**
|
|
1303
|
-
* 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
|
+
*
|
|
1304
1241
|
* @template T Type of the payload data
|
|
1305
1242
|
* @param intervalSeconds Number of seconds between executions
|
|
1306
1243
|
* @param callback Name of the method to call
|
|
@@ -1316,13 +1253,37 @@ var Agent = class Agent extends Server {
|
|
|
1316
1253
|
if (typeof callback !== "string") throw new Error("Callback must be a string");
|
|
1317
1254
|
if (typeof this[callback] !== "function") throw new Error(`this.${callback} is not a function`);
|
|
1318
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
|
+
}
|
|
1319
1280
|
const id = nanoid(9);
|
|
1320
1281
|
const time = new Date(Date.now() + intervalSeconds * 1e3);
|
|
1321
1282
|
const timestamp = Math.floor(time.getTime() / 1e3);
|
|
1322
1283
|
const retryJson = options?.retry ? JSON.stringify(options.retry) : null;
|
|
1323
1284
|
this.sql`
|
|
1324
1285
|
INSERT OR REPLACE INTO cf_agents_schedules (id, callback, payload, type, intervalSeconds, time, running, retry_options)
|
|
1325
|
-
VALUES (${id}, ${callback}, ${
|
|
1286
|
+
VALUES (${id}, ${callback}, ${payloadJson}, 'interval', ${intervalSeconds}, ${timestamp}, 0, ${retryJson})
|
|
1326
1287
|
`;
|
|
1327
1288
|
await this._scheduleNextAlarm();
|
|
1328
1289
|
const schedule = {
|
|
@@ -1425,7 +1386,7 @@ var Agent = class Agent extends Server {
|
|
|
1425
1386
|
*/
|
|
1426
1387
|
async keepAlive() {
|
|
1427
1388
|
const heartbeatSeconds = Math.ceil(KEEP_ALIVE_INTERVAL_MS / 1e3);
|
|
1428
|
-
const schedule = await this.scheduleEvery(heartbeatSeconds, "_cf_keepAliveHeartbeat");
|
|
1389
|
+
const schedule = await this.scheduleEvery(heartbeatSeconds, "_cf_keepAliveHeartbeat", void 0, { _idempotent: false });
|
|
1429
1390
|
let disposed = false;
|
|
1430
1391
|
return () => {
|
|
1431
1392
|
if (disposed) return;
|
|
@@ -1480,6 +1441,104 @@ var Agent = class Agent extends Server {
|
|
|
1480
1441
|
}
|
|
1481
1442
|
}
|
|
1482
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
|
+
/**
|
|
1483
1542
|
* Destroy the Agent, removing all state and scheduled tasks
|
|
1484
1543
|
*/
|
|
1485
1544
|
async destroy() {
|
|
@@ -2257,20 +2316,23 @@ var Agent = class Agent extends Server {
|
|
|
2257
2316
|
* @internal - Called by AgentWorkflow, do not call directly
|
|
2258
2317
|
*/
|
|
2259
2318
|
async _workflow_handleCallback(callback) {
|
|
2319
|
+
await this.__unsafe_ensureInitialized();
|
|
2260
2320
|
await this.onWorkflowCallback(callback);
|
|
2261
2321
|
}
|
|
2262
2322
|
/**
|
|
2263
2323
|
* Broadcast a message to all connected clients via RPC.
|
|
2264
2324
|
* @internal - Called by AgentWorkflow, do not call directly
|
|
2265
2325
|
*/
|
|
2266
|
-
_workflow_broadcast(message) {
|
|
2326
|
+
async _workflow_broadcast(message) {
|
|
2327
|
+
await this.__unsafe_ensureInitialized();
|
|
2267
2328
|
this.broadcast(JSON.stringify(message));
|
|
2268
2329
|
}
|
|
2269
2330
|
/**
|
|
2270
2331
|
* Update agent state via RPC.
|
|
2271
2332
|
* @internal - Called by AgentWorkflow, do not call directly
|
|
2272
2333
|
*/
|
|
2273
|
-
_workflow_updateState(action, state) {
|
|
2334
|
+
async _workflow_updateState(action, state) {
|
|
2335
|
+
await this.__unsafe_ensureInitialized();
|
|
2274
2336
|
if (action === "set") this.setState(state);
|
|
2275
2337
|
else if (action === "merge") {
|
|
2276
2338
|
const currentState = this.state ?? {};
|