agents 0.7.5 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { camelCaseToKebabCase } from "./utils.js";
3
3
  import { createHeaderBasedEmailResolver, signAgentHeaders } from "./email.js";
4
4
  import { __DO_NOT_USE_WILL_BREAK__agentContext } from "./internal_context.js";
5
5
  import { isErrorRetryable, tryN, validateRetryOptions } from "./retries.js";
6
- import { o as RPC_DO_PREFIX, r as MCPConnectionState, s as DisposableStore, t as MCPClientManager } from "./client-CgXIwdcc.js";
6
+ import { o as RPC_DO_PREFIX, r as MCPConnectionState, s as DisposableStore, t as MCPClientManager } from "./client-K8Z-u76l.js";
7
7
  import { DurableObjectOAuthClientProvider } from "./mcp/do-oauth-client-provider.js";
8
8
  import { genericObservability } from "./observability/index.js";
9
9
  import { parseCronExpression } from "cron-schedule";
@@ -65,6 +65,14 @@ function getNextCronTime(cron) {
65
65
  return parseCronExpression(cron).getNextDate();
66
66
  }
67
67
  const KEEP_ALIVE_INTERVAL_MS = 3e4;
68
+ /**
69
+ * Schema version for the Agent's internal SQLite tables.
70
+ * Bump this when adding new tables, columns, or migrations.
71
+ * The constructor stores this as a row in cf_agents_state and checks it
72
+ * on wake to skip DDL on established DOs.
73
+ */
74
+ const CURRENT_SCHEMA_VERSION = 1;
75
+ const SCHEMA_VERSION_ROW_ID = "cf_schema_version";
68
76
  const STATE_ROW_ID = "cf_state_row_id";
69
77
  const STATE_WAS_CHANGED = "cf_state_was_changed";
70
78
  const DEFAULT_STATE = {};
@@ -83,7 +91,11 @@ const CF_NO_PROTOCOL_KEY = "_cf_no_protocol";
83
91
  * The set of all internal keys stored in connection state that must be
84
92
  * hidden from user code and preserved across setState calls.
85
93
  */
86
- const CF_INTERNAL_KEYS = new Set([CF_READONLY_KEY, CF_NO_PROTOCOL_KEY]);
94
+ const CF_INTERNAL_KEYS = new Set([
95
+ CF_READONLY_KEY,
96
+ CF_NO_PROTOCOL_KEY,
97
+ "_cf_voiceInCall"
98
+ ]);
87
99
  /** Check if a raw connection state object contains any internal keys. */
88
100
  function rawHasInternalKeys(raw) {
89
101
  for (const key of Object.keys(raw)) if (CF_INTERNAL_KEYS.has(key)) return true;
@@ -205,14 +217,11 @@ var Agent = class Agent extends Server {
205
217
  */
206
218
  get state() {
207
219
  if (this._state !== DEFAULT_STATE) return this._state;
208
- const wasChanged = this.sql`
209
- SELECT state FROM cf_agents_state WHERE id = ${STATE_WAS_CHANGED}
210
- `;
211
220
  const result = this.sql`
212
221
  SELECT state FROM cf_agents_state WHERE id = ${STATE_ROW_ID}
213
222
  `;
214
- if (wasChanged[0]?.state === "true" || result[0]?.state) {
215
- const state = result[0]?.state;
223
+ if (result.length > 0) {
224
+ const state = result[0].state;
216
225
  try {
217
226
  this._state = JSON.parse(state);
218
227
  } catch (e) {
@@ -222,7 +231,6 @@ var Agent = class Agent extends Server {
222
231
  this._setStateInternal(this.initialState);
223
232
  } else {
224
233
  this.sql`DELETE FROM cf_agents_state WHERE id = ${STATE_ROW_ID}`;
225
- this.sql`DELETE FROM cf_agents_state WHERE id = ${STATE_WAS_CHANGED}`;
226
234
  return;
227
235
  }
228
236
  }
@@ -232,9 +240,6 @@ var Agent = class Agent extends Server {
232
240
  this._setStateInternal(this.initialState);
233
241
  return this.initialState;
234
242
  }
235
- static {
236
- this.options = { hibernate: true };
237
- }
238
243
  get _resolvedOptions() {
239
244
  if (this._cachedOptions) return this._cachedOptions;
240
245
  const ctor = this.constructor;
@@ -280,6 +285,138 @@ var Agent = class Agent extends Server {
280
285
  throw new SqlError(query, e);
281
286
  }
282
287
  }
288
+ /**
289
+ * Create all internal tables and run migrations if needed.
290
+ * Called by the constructor on every wake. Idempotent — skips DDL when
291
+ * the stored schema version matches CURRENT_SCHEMA_VERSION.
292
+ *
293
+ * Protected so that test agents can re-run the real migration path
294
+ * after manipulating DB state (since ctx.abort() is unavailable in
295
+ * local dev and the constructor only runs once per DO instance).
296
+ */
297
+ _ensureSchema() {
298
+ this.sql`
299
+ CREATE TABLE IF NOT EXISTS cf_agents_state (
300
+ id TEXT PRIMARY KEY NOT NULL,
301
+ state TEXT
302
+ )
303
+ `;
304
+ const versionRow = this.sql`
305
+ SELECT state FROM cf_agents_state WHERE id = ${SCHEMA_VERSION_ROW_ID}
306
+ `;
307
+ if ((versionRow.length > 0 ? Number(versionRow[0].state) : 0) < CURRENT_SCHEMA_VERSION) {
308
+ this.sql`
309
+ CREATE TABLE IF NOT EXISTS cf_agents_mcp_servers (
310
+ id TEXT PRIMARY KEY NOT NULL,
311
+ name TEXT NOT NULL,
312
+ server_url TEXT NOT NULL,
313
+ callback_url TEXT NOT NULL,
314
+ client_id TEXT,
315
+ auth_url TEXT,
316
+ server_options TEXT
317
+ )
318
+ `;
319
+ this.sql`
320
+ CREATE TABLE IF NOT EXISTS cf_agents_queues (
321
+ id TEXT PRIMARY KEY NOT NULL,
322
+ payload TEXT,
323
+ callback TEXT,
324
+ created_at INTEGER DEFAULT (unixepoch())
325
+ )
326
+ `;
327
+ this.sql`
328
+ CREATE TABLE IF NOT EXISTS cf_agents_schedules (
329
+ id TEXT PRIMARY KEY NOT NULL DEFAULT (randomblob(9)),
330
+ callback TEXT,
331
+ payload TEXT,
332
+ type TEXT NOT NULL CHECK(type IN ('scheduled', 'delayed', 'cron', 'interval')),
333
+ time INTEGER,
334
+ delayInSeconds INTEGER,
335
+ cron TEXT,
336
+ intervalSeconds INTEGER,
337
+ running INTEGER DEFAULT 0,
338
+ created_at INTEGER DEFAULT (unixepoch()),
339
+ execution_started_at INTEGER,
340
+ retry_options TEXT
341
+ )
342
+ `;
343
+ const addColumnIfNotExists = (sql) => {
344
+ try {
345
+ this.ctx.storage.sql.exec(sql);
346
+ } catch (e) {
347
+ if (!(e instanceof Error ? e.message : String(e)).toLowerCase().includes("duplicate column")) throw e;
348
+ }
349
+ };
350
+ addColumnIfNotExists("ALTER TABLE cf_agents_schedules ADD COLUMN intervalSeconds INTEGER");
351
+ addColumnIfNotExists("ALTER TABLE cf_agents_schedules ADD COLUMN running INTEGER DEFAULT 0");
352
+ addColumnIfNotExists("ALTER TABLE cf_agents_schedules ADD COLUMN execution_started_at INTEGER");
353
+ addColumnIfNotExists("ALTER TABLE cf_agents_schedules ADD COLUMN retry_options TEXT");
354
+ addColumnIfNotExists("ALTER TABLE cf_agents_queues ADD COLUMN retry_options TEXT");
355
+ {
356
+ const rows = this.ctx.storage.sql.exec("SELECT sql FROM sqlite_master WHERE type='table' AND name='cf_agents_schedules'").toArray();
357
+ if (rows.length > 0) {
358
+ if (!String(rows[0].sql).includes("'interval'")) {
359
+ this.ctx.storage.sql.exec("DROP TABLE IF EXISTS cf_agents_schedules_new");
360
+ this.ctx.storage.sql.exec(`
361
+ CREATE TABLE cf_agents_schedules_new (
362
+ id TEXT PRIMARY KEY NOT NULL DEFAULT (randomblob(9)),
363
+ callback TEXT,
364
+ payload TEXT,
365
+ type TEXT NOT NULL CHECK(type IN ('scheduled', 'delayed', 'cron', 'interval')),
366
+ time INTEGER,
367
+ delayInSeconds INTEGER,
368
+ cron TEXT,
369
+ intervalSeconds INTEGER,
370
+ running INTEGER DEFAULT 0,
371
+ created_at INTEGER DEFAULT (unixepoch()),
372
+ execution_started_at INTEGER,
373
+ retry_options TEXT
374
+ )
375
+ `);
376
+ this.ctx.storage.sql.exec(`
377
+ INSERT INTO cf_agents_schedules_new
378
+ (id, callback, payload, type, time, delayInSeconds, cron,
379
+ intervalSeconds, running, created_at, execution_started_at, retry_options)
380
+ SELECT id, callback, payload, type, time, delayInSeconds, cron,
381
+ intervalSeconds, running, created_at, execution_started_at, retry_options
382
+ FROM cf_agents_schedules
383
+ `);
384
+ this.ctx.storage.sql.exec("DROP TABLE cf_agents_schedules");
385
+ this.ctx.storage.sql.exec("ALTER TABLE cf_agents_schedules_new RENAME TO cf_agents_schedules");
386
+ }
387
+ }
388
+ }
389
+ this.sql`
390
+ CREATE TABLE IF NOT EXISTS cf_agents_workflows (
391
+ id TEXT PRIMARY KEY NOT NULL,
392
+ workflow_id TEXT NOT NULL UNIQUE,
393
+ workflow_name TEXT NOT NULL,
394
+ status TEXT NOT NULL CHECK(status IN (
395
+ 'queued', 'running', 'paused', 'errored',
396
+ 'terminated', 'complete', 'waiting',
397
+ 'waitingForPause', 'unknown'
398
+ )),
399
+ metadata TEXT,
400
+ error_name TEXT,
401
+ error_message TEXT,
402
+ created_at INTEGER NOT NULL DEFAULT (unixepoch()),
403
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
404
+ completed_at INTEGER
405
+ )
406
+ `;
407
+ this.sql`
408
+ CREATE INDEX IF NOT EXISTS idx_workflows_status ON cf_agents_workflows(status)
409
+ `;
410
+ this.sql`
411
+ CREATE INDEX IF NOT EXISTS idx_workflows_name ON cf_agents_workflows(workflow_name)
412
+ `;
413
+ this.ctx.storage.sql.exec("DELETE FROM cf_agents_state WHERE id = ?", STATE_WAS_CHANGED);
414
+ this.sql`
415
+ INSERT OR REPLACE INTO cf_agents_state (id, state)
416
+ VALUES (${SCHEMA_VERSION_ROW_ID}, ${String(CURRENT_SCHEMA_VERSION)})
417
+ `;
418
+ }
419
+ }
283
420
  constructor(ctx, env) {
284
421
  super(ctx, env);
285
422
  this._state = DEFAULT_STATE;
@@ -287,6 +424,7 @@ var Agent = class Agent extends Server {
287
424
  this._destroyed = false;
288
425
  this._rawStateAccessors = /* @__PURE__ */ new WeakMap();
289
426
  this._persistenceHookMode = "none";
427
+ this._isFacet = false;
290
428
  this._ParentClass = Object.getPrototypeOf(this).constructor;
291
429
  this.initialState = DEFAULT_STATE;
292
430
  this.observability = genericObservability;
@@ -295,117 +433,7 @@ var Agent = class Agent extends Server {
295
433
  this._autoWrapCustomMethods();
296
434
  wrappedClasses.add(this.constructor);
297
435
  }
298
- this.sql`
299
- CREATE TABLE IF NOT EXISTS cf_agents_mcp_servers (
300
- id TEXT PRIMARY KEY NOT NULL,
301
- name TEXT NOT NULL,
302
- server_url TEXT NOT NULL,
303
- callback_url TEXT NOT NULL,
304
- client_id TEXT,
305
- auth_url TEXT,
306
- server_options TEXT
307
- )
308
- `;
309
- this.sql`
310
- CREATE TABLE IF NOT EXISTS cf_agents_state (
311
- id TEXT PRIMARY KEY NOT NULL,
312
- state TEXT
313
- )
314
- `;
315
- this.sql`
316
- CREATE TABLE IF NOT EXISTS cf_agents_queues (
317
- id TEXT PRIMARY KEY NOT NULL,
318
- payload TEXT,
319
- callback TEXT,
320
- created_at INTEGER DEFAULT (unixepoch())
321
- )
322
- `;
323
- this.sql`
324
- CREATE TABLE IF NOT EXISTS cf_agents_schedules (
325
- id TEXT PRIMARY KEY NOT NULL DEFAULT (randomblob(9)),
326
- callback TEXT,
327
- payload TEXT,
328
- type TEXT NOT NULL CHECK(type IN ('scheduled', 'delayed', 'cron', 'interval')),
329
- time INTEGER,
330
- delayInSeconds INTEGER,
331
- cron TEXT,
332
- intervalSeconds INTEGER,
333
- running INTEGER DEFAULT 0,
334
- created_at INTEGER DEFAULT (unixepoch()),
335
- execution_started_at INTEGER,
336
- retry_options TEXT
337
- )
338
- `;
339
- const addColumnIfNotExists = (sql) => {
340
- try {
341
- this.ctx.storage.sql.exec(sql);
342
- } catch (e) {
343
- if (!(e instanceof Error ? e.message : String(e)).toLowerCase().includes("duplicate column")) throw e;
344
- }
345
- };
346
- addColumnIfNotExists("ALTER TABLE cf_agents_schedules ADD COLUMN intervalSeconds INTEGER");
347
- addColumnIfNotExists("ALTER TABLE cf_agents_schedules ADD COLUMN running INTEGER DEFAULT 0");
348
- addColumnIfNotExists("ALTER TABLE cf_agents_schedules ADD COLUMN execution_started_at INTEGER");
349
- addColumnIfNotExists("ALTER TABLE cf_agents_schedules ADD COLUMN retry_options TEXT");
350
- addColumnIfNotExists("ALTER TABLE cf_agents_queues ADD COLUMN retry_options TEXT");
351
- {
352
- const rows = this.ctx.storage.sql.exec("SELECT sql FROM sqlite_master WHERE type='table' AND name='cf_agents_schedules'").toArray();
353
- if (rows.length > 0) {
354
- if (!String(rows[0].sql).includes("'interval'")) {
355
- this.ctx.storage.sql.exec("DROP TABLE IF EXISTS cf_agents_schedules_new");
356
- this.ctx.storage.sql.exec(`
357
- CREATE TABLE cf_agents_schedules_new (
358
- id TEXT PRIMARY KEY NOT NULL DEFAULT (randomblob(9)),
359
- callback TEXT,
360
- payload TEXT,
361
- type TEXT NOT NULL CHECK(type IN ('scheduled', 'delayed', 'cron', 'interval')),
362
- time INTEGER,
363
- delayInSeconds INTEGER,
364
- cron TEXT,
365
- intervalSeconds INTEGER,
366
- running INTEGER DEFAULT 0,
367
- created_at INTEGER DEFAULT (unixepoch()),
368
- execution_started_at INTEGER,
369
- retry_options TEXT
370
- )
371
- `);
372
- this.ctx.storage.sql.exec(`
373
- INSERT INTO cf_agents_schedules_new
374
- (id, callback, payload, type, time, delayInSeconds, cron,
375
- intervalSeconds, running, created_at, execution_started_at, retry_options)
376
- SELECT id, callback, payload, type, time, delayInSeconds, cron,
377
- intervalSeconds, running, created_at, execution_started_at, retry_options
378
- FROM cf_agents_schedules
379
- `);
380
- this.ctx.storage.sql.exec("DROP TABLE cf_agents_schedules");
381
- this.ctx.storage.sql.exec("ALTER TABLE cf_agents_schedules_new RENAME TO cf_agents_schedules");
382
- }
383
- }
384
- }
385
- this.sql`
386
- CREATE TABLE IF NOT EXISTS cf_agents_workflows (
387
- id TEXT PRIMARY KEY NOT NULL,
388
- workflow_id TEXT NOT NULL UNIQUE,
389
- workflow_name TEXT NOT NULL,
390
- status TEXT NOT NULL CHECK(status IN (
391
- 'queued', 'running', 'paused', 'errored',
392
- 'terminated', 'complete', 'waiting',
393
- 'waitingForPause', 'unknown'
394
- )),
395
- metadata TEXT,
396
- error_name TEXT,
397
- error_message TEXT,
398
- created_at INTEGER NOT NULL DEFAULT (unixepoch()),
399
- updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
400
- completed_at INTEGER
401
- )
402
- `;
403
- this.sql`
404
- CREATE INDEX IF NOT EXISTS idx_workflows_status ON cf_agents_workflows(status)
405
- `;
406
- this.sql`
407
- CREATE INDEX IF NOT EXISTS idx_workflows_name ON cf_agents_workflows(workflow_name)
408
- `;
436
+ this._ensureSchema();
409
437
  this.mcp = new MCPClientManager(this._ParentClass.name, "0.0.1", {
410
438
  storage: this.ctx.storage,
411
439
  createAuthProvider: (callbackUrl) => this.createMcpOAuthProvider(callbackUrl)
@@ -605,6 +633,7 @@ var Agent = class Agent extends Server {
605
633
  request: void 0,
606
634
  email: void 0
607
635
  }, async () => {
636
+ if (await this.ctx.storage.get("cf_agents_is_facet")) this._isFacet = true;
608
637
  await this._tryCatch(async () => {
609
638
  await this.mcp.restoreConnectionsFromStorage(this.name);
610
639
  await this._restoreRpcMcpServers();
@@ -655,10 +684,6 @@ var Agent = class Agent extends Server {
655
684
  this.sql`
656
685
  INSERT OR REPLACE INTO cf_agents_state (id, state)
657
686
  VALUES (${STATE_ROW_ID}, ${JSON.stringify(nextState)})
658
- `;
659
- this.sql`
660
- INSERT OR REPLACE INTO cf_agents_state (id, state)
661
- VALUES (${STATE_WAS_CHANGED}, ${JSON.stringify(true)})
662
687
  `;
663
688
  this._broadcastProtocol(JSON.stringify({
664
689
  state: nextState,
@@ -786,6 +811,44 @@ var Agent = class Agent extends Server {
786
811
  return !!this._rawStateAccessors.get(connection).getRaw()?.[CF_READONLY_KEY];
787
812
  }
788
813
  /**
814
+ * ⚠️ INTERNAL — DO NOT USE IN APPLICATION CODE. ⚠️
815
+ *
816
+ * Read an internal `_cf_`-prefixed flag from the raw connection state,
817
+ * bypassing the user-facing state wrapper that strips internal keys.
818
+ *
819
+ * This exists for framework mixins (e.g. voice) that need to persist
820
+ * flags in the connection attachment across hibernation. Application
821
+ * code should use `connection.state` and `connection.setState()` instead.
822
+ *
823
+ * @internal
824
+ */
825
+ _unsafe_getConnectionFlag(connection, key) {
826
+ this._ensureConnectionWrapped(connection);
827
+ return this._rawStateAccessors.get(connection).getRaw()?.[key];
828
+ }
829
+ /**
830
+ * ⚠️ INTERNAL — DO NOT USE IN APPLICATION CODE. ⚠️
831
+ *
832
+ * Write an internal `_cf_`-prefixed flag to the raw connection state,
833
+ * bypassing the user-facing state wrapper. The key must be registered
834
+ * in `CF_INTERNAL_KEYS` so it is preserved across user `setState` calls
835
+ * and hidden from `connection.state`.
836
+ *
837
+ * @internal
838
+ */
839
+ _unsafe_setConnectionFlag(connection, key, value) {
840
+ this._ensureConnectionWrapped(connection);
841
+ const accessors = this._rawStateAccessors.get(connection);
842
+ const raw = accessors.getRaw() ?? {};
843
+ if (value === void 0) {
844
+ const { [key]: _, ...rest } = raw;
845
+ accessors.setRaw(Object.keys(rest).length > 0 ? rest : null);
846
+ } else accessors.setRaw({
847
+ ...raw,
848
+ [key]: value
849
+ });
850
+ }
851
+ /**
789
852
  * Override this method to determine if a connection should be readonly on connect
790
853
  * @param _connection The connection that is being established
791
854
  * @param _ctx Connection context
@@ -1185,6 +1248,7 @@ var Agent = class Agent extends Server {
1185
1248
  * @returns Schedule object representing the scheduled task
1186
1249
  */
1187
1250
  async schedule(when, callback, payload, options) {
1251
+ if (this._isFacet) throw new Error("Scheduling is not supported in sub-agents. Schedule from the parent agent instead.");
1188
1252
  const id = nanoid(9);
1189
1253
  if (options?.retry) validateRetryOptions(options.retry, this._resolvedOptions.retry);
1190
1254
  const retryJson = options?.retry ? JSON.stringify(options.retry) : null;
@@ -1281,6 +1345,7 @@ var Agent = class Agent extends Server {
1281
1345
  * @returns Schedule object representing the scheduled task
1282
1346
  */
1283
1347
  async scheduleEvery(intervalSeconds, callback, payload, options) {
1348
+ if (this._isFacet) throw new Error("Scheduling is not supported in sub-agents. Schedule from the parent agent instead.");
1284
1349
  const MAX_INTERVAL_SECONDS = 720 * 60 * 60;
1285
1350
  if (typeof intervalSeconds !== "number" || intervalSeconds <= 0) throw new Error("intervalSeconds must be a positive number");
1286
1351
  if (intervalSeconds > MAX_INTERVAL_SECONDS) throw new Error(`intervalSeconds cannot exceed ${MAX_INTERVAL_SECONDS} seconds (30 days)`);
@@ -1388,6 +1453,7 @@ var Agent = class Agent extends Server {
1388
1453
  * @returns true if the task was cancelled, false if the task was not found
1389
1454
  */
1390
1455
  async cancelSchedule(id) {
1456
+ if (this._isFacet) throw new Error("Scheduling is not supported in sub-agents. Schedule from the parent agent instead.");
1391
1457
  const schedule = this.getSchedule(id);
1392
1458
  if (!schedule) return false;
1393
1459
  this._emit("schedule:cancel", {
@@ -1419,6 +1485,7 @@ var Agent = class Agent extends Server {
1419
1485
  * ```
1420
1486
  */
1421
1487
  async keepAlive() {
1488
+ if (this._isFacet) throw new Error("keepAlive() is not supported in sub-agents. Use keepAlive() from the parent agent instead.");
1422
1489
  const heartbeatSeconds = Math.ceil(KEEP_ALIVE_INTERVAL_MS / 1e3);
1423
1490
  const schedule = await this.scheduleEvery(heartbeatSeconds, "_cf_keepAliveHeartbeat", void 0, { _idempotent: false });
1424
1491
  let disposed = false;
@@ -1469,10 +1536,10 @@ var Agent = class Agent extends Server {
1469
1536
  LIMIT 1
1470
1537
  `;
1471
1538
  if (!result) return;
1472
- if (result.length > 0 && "time" in result[0]) {
1473
- const nextTime = result[0].time * 1e3;
1474
- await this.ctx.storage.setAlarm(nextTime);
1475
- } else await this.ctx.storage.deleteAlarm();
1539
+ let nextTimeMs = null;
1540
+ if (result.length > 0 && "time" in result[0]) nextTimeMs = result[0].time * 1e3;
1541
+ if (nextTimeMs !== null) await this.ctx.storage.setAlarm(nextTimeMs);
1542
+ else await this.ctx.storage.deleteAlarm();
1476
1543
  }
1477
1544
  /**
1478
1545
  * Override PartyServer's onAlarm hook as a no-op.
@@ -1573,6 +1640,81 @@ var Agent = class Agent extends Server {
1573
1640
  await this._scheduleNextAlarm();
1574
1641
  }
1575
1642
  /**
1643
+ * Marks this agent as running inside a facet (sub-agent). Once set,
1644
+ * scheduling methods throw a clear error instead of crashing on
1645
+ * `setAlarm()` (which is not supported in facets).
1646
+ * @internal
1647
+ */
1648
+ async _cf_markAsFacet() {
1649
+ this._isFacet = true;
1650
+ await this.ctx.storage.put("cf_agents_is_facet", true);
1651
+ }
1652
+ /**
1653
+ * Get or create a named sub-agent — a child Durable Object (facet)
1654
+ * with its own isolated SQLite storage running on the same machine.
1655
+ *
1656
+ * The child class must extend `Agent` and be exported from the worker
1657
+ * entry point. The first call for a given name triggers the child's
1658
+ * `onStart()`. Subsequent calls return the existing instance.
1659
+ *
1660
+ * @experimental Requires the `"experimental"` compatibility flag.
1661
+ *
1662
+ * @param cls The Agent subclass (must be exported from the worker)
1663
+ * @param name Unique name for this child instance
1664
+ * @returns A typed RPC stub for calling methods on the child
1665
+ *
1666
+ * @example
1667
+ * ```typescript
1668
+ * const searcher = await this.subAgent(SearchAgent, "main-search");
1669
+ * const results = await searcher.search("cloudflare agents");
1670
+ * ```
1671
+ */
1672
+ async subAgent(cls, name) {
1673
+ const ctx = this.ctx;
1674
+ if (!ctx.facets || !ctx.exports) throw new Error("subAgent() requires the \"experimental\" compatibility flag. Add it to your wrangler.jsonc compatibility_flags.");
1675
+ if (!ctx.exports[cls.name]) throw new Error(`Sub-agent class "${cls.name}" not found in worker exports. Make sure the class is exported from your worker entry point and that the export name matches the class name.`);
1676
+ const facetKey = `${cls.name}\0${name}`;
1677
+ const stub = ctx.facets.get(facetKey, () => ({ class: ctx.exports[cls.name] }));
1678
+ const req = new Request("http://dummy-example.cloudflare.com/cdn-cgi/partyserver/set-name/");
1679
+ req.headers.set("x-partykit-room", name);
1680
+ await stub.fetch(req).then((res) => res.text());
1681
+ await stub._cf_markAsFacet();
1682
+ return stub;
1683
+ }
1684
+ /**
1685
+ * Forcefully abort a running sub-agent. The child stops executing
1686
+ * immediately and will be restarted on next {@link subAgent} call.
1687
+ * Pending RPC calls receive the reason as an error.
1688
+ * Transitively aborts the child's own children.
1689
+ *
1690
+ * @experimental Requires the `"experimental"` compatibility flag.
1691
+ *
1692
+ * @param cls The Agent subclass used when creating the child
1693
+ * @param name Name of the child to abort
1694
+ * @param reason Error thrown to pending/future RPC callers
1695
+ */
1696
+ abortSubAgent(cls, name, reason) {
1697
+ const ctx = this.ctx;
1698
+ if (!ctx.facets) throw new Error("abortSubAgent() requires the \"experimental\" compatibility flag.");
1699
+ const facetKey = `${cls.name}\0${name}`;
1700
+ ctx.facets.abort(facetKey, reason);
1701
+ }
1702
+ /**
1703
+ * Delete a sub-agent: abort it if running, then permanently wipe its
1704
+ * storage. Transitively deletes the child's own children.
1705
+ *
1706
+ * @experimental Requires the `"experimental"` compatibility flag.
1707
+ *
1708
+ * @param cls The Agent subclass used when creating the child
1709
+ * @param name Name of the child to delete
1710
+ */
1711
+ deleteSubAgent(cls, name) {
1712
+ const ctx = this.ctx;
1713
+ if (!ctx.facets) throw new Error("deleteSubAgent() requires the \"experimental\" compatibility flag.");
1714
+ const facetKey = `${cls.name}\0${name}`;
1715
+ ctx.facets.delete(facetKey);
1716
+ }
1717
+ /**
1576
1718
  * Destroy the Agent, removing all state and scheduled tasks
1577
1719
  */
1578
1720
  async destroy() {
@@ -1581,7 +1723,7 @@ var Agent = class Agent extends Server {
1581
1723
  this.sql`DROP TABLE IF EXISTS cf_agents_schedules`;
1582
1724
  this.sql`DROP TABLE IF EXISTS cf_agents_queues`;
1583
1725
  this.sql`DROP TABLE IF EXISTS cf_agents_workflows`;
1584
- await this.ctx.storage.deleteAlarm();
1726
+ if (!this._isFacet) await this.ctx.storage.deleteAlarm();
1585
1727
  await this.ctx.storage.deleteAll();
1586
1728
  this._disposables.dispose();
1587
1729
  await this.mcp.dispose();
@@ -2317,7 +2459,7 @@ var Agent = class Agent extends Server {
2317
2459
  * @param workflowId - ID of the workflow
2318
2460
  * @param progress - Typed progress data (default: DefaultProgress)
2319
2461
  */
2320
- async onWorkflowProgress(_workflowName, _workflowId, _progress) {}
2462
+ async onWorkflowProgress(workflowName, workflowId, progress) {}
2321
2463
  /**
2322
2464
  * Called when a workflow completes successfully.
2323
2465
  * Override to handle completion.
@@ -2326,7 +2468,7 @@ var Agent = class Agent extends Server {
2326
2468
  * @param workflowId - ID of the workflow
2327
2469
  * @param result - Optional result data
2328
2470
  */
2329
- async onWorkflowComplete(_workflowName, _workflowId, _result) {}
2471
+ async onWorkflowComplete(workflowName, workflowId, result) {}
2330
2472
  /**
2331
2473
  * Called when a workflow encounters an error.
2332
2474
  * Override to handle errors.
@@ -2335,7 +2477,9 @@ var Agent = class Agent extends Server {
2335
2477
  * @param workflowId - ID of the workflow
2336
2478
  * @param error - Error message
2337
2479
  */
2338
- async onWorkflowError(_workflowName, _workflowId, _error) {}
2480
+ async onWorkflowError(workflowName, workflowId, error) {
2481
+ console.error(`Workflow error [${workflowName}/${workflowId}]: ${error}\nOverride onWorkflowError() in your Agent to handle workflow errors.`);
2482
+ }
2339
2483
  /**
2340
2484
  * Called when a workflow sends a custom event.
2341
2485
  * Override to handle custom events.
@@ -2344,7 +2488,7 @@ var Agent = class Agent extends Server {
2344
2488
  * @param workflowId - ID of the workflow
2345
2489
  * @param event - Custom event payload
2346
2490
  */
2347
- async onWorkflowEvent(_workflowName, _workflowId, _event) {}
2491
+ async onWorkflowEvent(workflowName, workflowId, event) {}
2348
2492
  /**
2349
2493
  * Handle a workflow callback via RPC.
2350
2494
  * @internal - Called by AgentWorkflow, do not call directly
@@ -2601,6 +2745,7 @@ var Agent = class Agent extends Server {
2601
2745
  return Response.redirect(baseOrigin);
2602
2746
  }
2603
2747
  };
2748
+ Agent.options = { hibernate: true };
2604
2749
  const wrappedClasses = /* @__PURE__ */ new Set();
2605
2750
  /**
2606
2751
  * Route a request to the appropriate Agent