agents 0.12.4 → 0.13.1

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.
Files changed (62) hide show
  1. package/README.md +8 -8
  2. package/dist/{agent-tool-types-CM_50fcV.d.ts → agent-tool-types-Dn9n-3SI.d.ts} +234 -85
  3. package/dist/agent-tool-types.d.ts +1 -1
  4. package/dist/{agent-tools-BylX6WXG.d.ts → agent-tools-B1ttU-pq.d.ts} +2 -2
  5. package/dist/agent-tools-BAdX1vdI.js.map +1 -1
  6. package/dist/agent-tools.d.ts +1 -1
  7. package/dist/agent-tools.js.map +1 -1
  8. package/dist/ai-chat-agent.js.map +1 -1
  9. package/dist/ai-chat-v5-migration.js.map +1 -1
  10. package/dist/ai-react.js.map +1 -1
  11. package/dist/ai-types.js.map +1 -1
  12. package/dist/browser/ai.js +1 -1
  13. package/dist/browser/ai.js.map +1 -1
  14. package/dist/browser/index.js +1 -1
  15. package/dist/browser/tanstack-ai.js +1 -1
  16. package/dist/browser/tanstack-ai.js.map +1 -1
  17. package/dist/chat/index.d.ts +2 -2
  18. package/dist/chat/index.js.map +1 -1
  19. package/dist/{classPrivateFieldGet2-CS51BNGR.js → classPrivateFieldGet2-Evpt0SEr.js} +5 -5
  20. package/dist/cli/index.js.map +1 -1
  21. package/dist/client-D1kFXo80.js.map +1 -1
  22. package/dist/client.d.ts +1 -1
  23. package/dist/client.js.map +1 -1
  24. package/dist/codemode/ai.js.map +1 -1
  25. package/dist/{compaction-helpers-bYvP1o2S.d.ts → compaction-helpers-DAe-xiVY.d.ts} +33 -15
  26. package/dist/compaction-helpers-DvcZnvQ1.js.map +1 -1
  27. package/dist/email.d.ts +1 -1
  28. package/dist/email.js.map +1 -1
  29. package/dist/experimental/memory/session/index.d.ts +247 -34
  30. package/dist/experimental/memory/session/index.js +540 -135
  31. package/dist/experimental/memory/session/index.js.map +1 -1
  32. package/dist/experimental/memory/utils/index.d.ts +1 -1
  33. package/dist/experimental/memory/utils/index.js.map +1 -1
  34. package/dist/experimental/webmcp.js.map +1 -1
  35. package/dist/index.d.ts +71 -57
  36. package/dist/index.js +583 -45
  37. package/dist/index.js.map +1 -1
  38. package/dist/internal_context.js.map +1 -1
  39. package/dist/mcp/client.d.ts +12 -12
  40. package/dist/mcp/do-oauth-client-provider.js.map +1 -1
  41. package/dist/mcp/index.d.ts +40 -40
  42. package/dist/mcp/index.js +21 -45
  43. package/dist/mcp/index.js.map +1 -1
  44. package/dist/mcp/x402.js.map +1 -1
  45. package/dist/observability/index.js.map +1 -1
  46. package/dist/react.d.ts +3 -3
  47. package/dist/react.js.map +1 -1
  48. package/dist/retries.js.map +1 -1
  49. package/dist/schedule.js.map +1 -1
  50. package/dist/serializable.d.ts +1 -1
  51. package/dist/{shared-DzJYHisH.js → shared-CiKaIK4h.js} +4 -5
  52. package/dist/{shared-DzJYHisH.js.map → shared-CiKaIK4h.js.map} +1 -1
  53. package/dist/sub-routing.d.ts +6 -6
  54. package/dist/sub-routing.js.map +1 -1
  55. package/dist/tool-output-truncation-CH-khbZ3.js.map +1 -1
  56. package/dist/types.js.map +1 -1
  57. package/dist/utils.js.map +1 -1
  58. package/dist/vite.js.map +1 -1
  59. package/dist/workflow-types.js.map +1 -1
  60. package/dist/workflows.d.ts +1 -1
  61. package/dist/workflows.js.map +1 -1
  62. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { __DO_NOT_USE_WILL_BREAK__agentContext } from "./internal_context.js";
2
2
  import { MessageType } from "./types.js";
3
- import { camelCaseToKebabCase } from "./utils.js";
3
+ import { camelCaseToKebabCase, isInternalJsStubProp } from "./utils.js";
4
4
  import { createHeaderBasedEmailResolver, signAgentHeaders } from "./email.js";
5
- import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-CS51BNGR.js";
5
+ import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-Evpt0SEr.js";
6
6
  import { SUB_PREFIX, getSubAgentByName, parseSubAgentPath, routeSubAgentRequest } from "./sub-routing.js";
7
7
  import { isErrorRetryable, tryN, validateRetryOptions } from "./retries.js";
8
8
  import { o as RPC_DO_PREFIX, r as MCPConnectionState, s as DisposableStore, t as MCPClientManager } from "./client-D1kFXo80.js";
@@ -12,7 +12,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
12
12
  import { parseCronExpression } from "cron-schedule";
13
13
  import { nanoid } from "nanoid";
14
14
  import { EmailMessage } from "cloudflare:email";
15
- import { RpcTarget } from "cloudflare:workers";
15
+ import { RpcTarget, exports } from "cloudflare:workers";
16
16
  import { Server, getServerByName, routePartykitRequest } from "partyserver";
17
17
  //#region src/index.ts
18
18
  let _Symbol$dispose;
@@ -120,7 +120,7 @@ const DEFAULT_KEEP_ALIVE_INTERVAL_MS = 3e4;
120
120
  * The constructor stores this as a row in cf_agents_state and checks it
121
121
  * on wake to skip DDL on established DOs.
122
122
  */
123
- const CURRENT_SCHEMA_VERSION = 7;
123
+ const CURRENT_SCHEMA_VERSION = 8;
124
124
  const SCHEMA_VERSION_ROW_ID = "cf_schema_version";
125
125
  const STATE_ROW_ID = "cf_state_row_id";
126
126
  const STATE_WAS_CHANGED = "cf_state_was_changed";
@@ -549,6 +549,32 @@ var Agent = class Agent extends Server {
549
549
  this.sql`
550
550
  CREATE INDEX IF NOT EXISTS idx_facet_runs_owner_path_key
551
551
  ON cf_agents_facet_runs(owner_path_key)
552
+ `;
553
+ this.sql`
554
+ CREATE TABLE IF NOT EXISTS cf_agents_fibers (
555
+ fiber_id TEXT PRIMARY KEY,
556
+ idempotency_key TEXT UNIQUE,
557
+ name TEXT NOT NULL,
558
+ status TEXT NOT NULL,
559
+ snapshot TEXT,
560
+ metadata_json TEXT,
561
+ error_message TEXT,
562
+ created_at INTEGER NOT NULL,
563
+ started_at INTEGER,
564
+ completed_at INTEGER
565
+ )
566
+ `;
567
+ this.sql`
568
+ CREATE INDEX IF NOT EXISTS idx_fibers_status_created
569
+ ON cf_agents_fibers(status, created_at, fiber_id)
570
+ `;
571
+ this.sql`
572
+ CREATE INDEX IF NOT EXISTS idx_fibers_name_status_created
573
+ ON cf_agents_fibers(name, status, created_at, fiber_id)
574
+ `;
575
+ this.sql`
576
+ CREATE INDEX IF NOT EXISTS idx_fibers_status_completed
577
+ ON cf_agents_fibers(status, completed_at, created_at)
552
578
  `;
553
579
  this.sql`
554
580
  CREATE TABLE IF NOT EXISTS cf_agent_tool_runs (
@@ -595,6 +621,9 @@ var Agent = class Agent extends Server {
595
621
  this._keepAliveRefs = 0;
596
622
  this._facetKeepAliveTokens = /* @__PURE__ */ new Set();
597
623
  this._runFiberActiveFibers = /* @__PURE__ */ new Set();
624
+ this._managedFiberAbortControllers = /* @__PURE__ */ new Map();
625
+ this._managedFiberExecutions = /* @__PURE__ */ new Map();
626
+ this._managedFiberTerminalWaiters = /* @__PURE__ */ new Map();
598
627
  this._runFiberRecoveryInProgress = false;
599
628
  this._ParentClass = Object.getPrototypeOf(this).constructor;
600
629
  this.initialState = DEFAULT_STATE;
@@ -2211,6 +2240,295 @@ var Agent = class Agent extends Server {
2211
2240
  dispose();
2212
2241
  }
2213
2242
  }
2243
+ _isTerminalFiberStatus(status) {
2244
+ return status === "completed" || status === "aborted" || status === "interrupted" || status === "error";
2245
+ }
2246
+ _notifyManagedFiberTerminal(fiberId) {
2247
+ const row = this._readFiber(fiberId);
2248
+ if (row && !this._isTerminalFiberStatus(row.status)) return;
2249
+ const waiters = this._managedFiberTerminalWaiters.get(fiberId);
2250
+ if (!waiters) return;
2251
+ this._managedFiberTerminalWaiters.delete(fiberId);
2252
+ for (const resolve of waiters) resolve();
2253
+ }
2254
+ _waitForManagedFiberTerminal(fiberId) {
2255
+ const row = this._readFiber(fiberId);
2256
+ if (!row || this._isTerminalFiberStatus(row.status)) return Promise.resolve();
2257
+ return new Promise((resolve) => {
2258
+ let waiters = this._managedFiberTerminalWaiters.get(fiberId);
2259
+ if (!waiters) {
2260
+ waiters = /* @__PURE__ */ new Set();
2261
+ this._managedFiberTerminalWaiters.set(fiberId, waiters);
2262
+ }
2263
+ waiters.add(resolve);
2264
+ });
2265
+ }
2266
+ _normalizeFiberStatusFilter(status) {
2267
+ if (!status) return null;
2268
+ return new Set(Array.isArray(status) ? status : [status]);
2269
+ }
2270
+ _parseFiberJsonObject(value) {
2271
+ if (value === null) return null;
2272
+ try {
2273
+ const parsed = JSON.parse(value);
2274
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
2275
+ } catch {}
2276
+ return null;
2277
+ }
2278
+ _parseFiberSnapshot(value) {
2279
+ if (value === null) return void 0;
2280
+ try {
2281
+ return JSON.parse(value);
2282
+ } catch {
2283
+ return;
2284
+ }
2285
+ }
2286
+ _fiberErrorMessage(error) {
2287
+ return error instanceof Error ? error.message : String(error);
2288
+ }
2289
+ _stringifyFiberSnapshot(snapshot) {
2290
+ return snapshot === void 0 ? null : JSON.stringify(snapshot);
2291
+ }
2292
+ _fiberRecoveryErrorMessage(result) {
2293
+ if (result.status === "error") return result.error === void 0 ? null : this._fiberErrorMessage(result.error);
2294
+ if (result.status === "aborted" || result.status === "interrupted") return result.reason ?? null;
2295
+ return null;
2296
+ }
2297
+ _applyManagedFiberRecoveryResult(fiberId, result) {
2298
+ const completedAt = Date.now();
2299
+ const snapshot = this._stringifyFiberSnapshot(result.snapshot);
2300
+ const errorMessage = this._fiberRecoveryErrorMessage(result);
2301
+ const metadata = result.status === "completed" && result.metadata !== void 0 ? JSON.stringify(result.metadata) : void 0;
2302
+ if (metadata !== void 0) {
2303
+ this.sql`
2304
+ UPDATE cf_agents_fibers
2305
+ SET status = ${result.status},
2306
+ snapshot = COALESCE(${snapshot}, snapshot),
2307
+ metadata_json = ${metadata},
2308
+ error_message = ${errorMessage},
2309
+ completed_at = ${completedAt}
2310
+ WHERE fiber_id = ${fiberId}
2311
+ AND status = 'interrupted'
2312
+ `;
2313
+ this._notifyManagedFiberTerminal(fiberId);
2314
+ return;
2315
+ }
2316
+ this.sql`
2317
+ UPDATE cf_agents_fibers
2318
+ SET status = ${result.status},
2319
+ snapshot = COALESCE(${snapshot}, snapshot),
2320
+ error_message = ${errorMessage},
2321
+ completed_at = ${completedAt}
2322
+ WHERE fiber_id = ${fiberId}
2323
+ AND status = 'interrupted'
2324
+ `;
2325
+ this._notifyManagedFiberTerminal(fiberId);
2326
+ }
2327
+ _settleManagedFiberExecution(fiberId, outcome, signal) {
2328
+ const completedAt = Date.now();
2329
+ if (outcome.ok) {
2330
+ this.sql`
2331
+ UPDATE cf_agents_fibers
2332
+ SET status = 'completed', completed_at = ${completedAt}
2333
+ WHERE fiber_id = ${fiberId} AND status = 'running'
2334
+ `;
2335
+ this._notifyManagedFiberTerminal(fiberId);
2336
+ return;
2337
+ }
2338
+ const message = this._fiberErrorMessage(outcome.error);
2339
+ const status = signal.aborted ? "aborted" : "error";
2340
+ this.sql`
2341
+ UPDATE cf_agents_fibers
2342
+ SET status = ${status},
2343
+ error_message = ${message},
2344
+ completed_at = ${completedAt}
2345
+ WHERE fiber_id = ${fiberId} AND status = 'running'
2346
+ `;
2347
+ this._notifyManagedFiberTerminal(fiberId);
2348
+ }
2349
+ _parseFiberRecoverySnapshot(fiberId, snapshotText) {
2350
+ if (!snapshotText) return null;
2351
+ try {
2352
+ return JSON.parse(snapshotText);
2353
+ } catch {
2354
+ console.warn(`[Agent] Corrupted snapshot for fiber ${fiberId}, treating as null`);
2355
+ return null;
2356
+ }
2357
+ }
2358
+ async _runFiberRecoveryHook(ctx, managedRow) {
2359
+ try {
2360
+ if (!await this._handleInternalFiberRecovery(ctx)) {
2361
+ const recoveryResult = await this.onFiberRecovered(ctx);
2362
+ if (managedRow && recoveryResult) this._applyManagedFiberRecoveryResult(ctx.id, recoveryResult);
2363
+ }
2364
+ } catch (e) {
2365
+ if (managedRow) this.sql`
2366
+ UPDATE cf_agents_fibers
2367
+ SET error_message = ${this._fiberErrorMessage(e)}
2368
+ WHERE fiber_id = ${ctx.id}
2369
+ AND status = 'interrupted'
2370
+ `;
2371
+ console.error(`[Agent] Fiber recovery failed for "${ctx.name}" (${ctx.id}):`, e);
2372
+ }
2373
+ }
2374
+ _fiberInspectionFromRow(row) {
2375
+ const snapshot = this._parseFiberSnapshot(row.snapshot);
2376
+ const inspection = {
2377
+ fiberId: row.fiber_id,
2378
+ name: row.name,
2379
+ status: row.status,
2380
+ createdAt: row.created_at
2381
+ };
2382
+ if (row.idempotency_key !== null) inspection.idempotencyKey = row.idempotency_key;
2383
+ if (snapshot !== void 0) inspection.snapshot = snapshot;
2384
+ if (row.error_message !== null) inspection.error = row.error_message;
2385
+ const metadata = this._parseFiberJsonObject(row.metadata_json);
2386
+ if (metadata !== null) inspection.metadata = metadata;
2387
+ if (row.started_at !== null) inspection.startedAt = row.started_at;
2388
+ if (row.completed_at !== null) inspection.settledAt = row.completed_at;
2389
+ return inspection;
2390
+ }
2391
+ async _waitForManagedFiber(fiberId) {
2392
+ const row = this._readFiber(fiberId);
2393
+ if (!row || this._isTerminalFiberStatus(row.status)) return row ? this._fiberInspectionFromRow(row) : null;
2394
+ if (this._managedFiberExecutions.has(fiberId)) {
2395
+ await this._waitForManagedFiberTerminal(fiberId);
2396
+ return this.inspectFiber(fiberId);
2397
+ }
2398
+ await this._checkRunFibers();
2399
+ await this._waitForManagedFiberTerminal(fiberId);
2400
+ return this.inspectFiber(fiberId);
2401
+ }
2402
+ _readFiber(fiberId) {
2403
+ return this.sql`
2404
+ SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
2405
+ error_message, created_at, started_at, completed_at
2406
+ FROM cf_agents_fibers
2407
+ WHERE fiber_id = ${fiberId}
2408
+ LIMIT 1
2409
+ `[0] ?? null;
2410
+ }
2411
+ _readFiberByKey(idempotencyKey) {
2412
+ return this.sql`
2413
+ SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
2414
+ error_message, created_at, started_at, completed_at
2415
+ FROM cf_agents_fibers
2416
+ WHERE idempotency_key = ${idempotencyKey}
2417
+ LIMIT 1
2418
+ `[0] ?? null;
2419
+ }
2420
+ _listFiberRows(options) {
2421
+ const limit = Math.min(Math.max(options?.limit ?? 50, 1), 100);
2422
+ const statuses = this._normalizeFiberStatusFilter(options?.status);
2423
+ if (statuses) return [...statuses].flatMap((status) => this._listFiberRowsByStatus(status, limit, options?.name)).sort((a, b) => b.created_at === a.created_at ? b.fiber_id.localeCompare(a.fiber_id) : b.created_at - a.created_at).slice(0, limit);
2424
+ if (options?.name) return this.sql`
2425
+ SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
2426
+ error_message, created_at, started_at, completed_at
2427
+ FROM cf_agents_fibers
2428
+ WHERE name = ${options.name}
2429
+ ORDER BY created_at DESC, fiber_id DESC
2430
+ LIMIT ${limit}
2431
+ `;
2432
+ return this.sql`
2433
+ SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
2434
+ error_message, created_at, started_at, completed_at
2435
+ FROM cf_agents_fibers
2436
+ ORDER BY created_at DESC, fiber_id DESC
2437
+ LIMIT ${limit}
2438
+ `;
2439
+ }
2440
+ _listFiberRowsByStatus(status, limit, name) {
2441
+ if (name) return this.sql`
2442
+ SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
2443
+ error_message, created_at, started_at, completed_at
2444
+ FROM cf_agents_fibers
2445
+ WHERE status = ${status} AND name = ${name}
2446
+ ORDER BY created_at DESC, fiber_id DESC
2447
+ LIMIT ${limit}
2448
+ `;
2449
+ return this.sql`
2450
+ SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
2451
+ error_message, created_at, started_at, completed_at
2452
+ FROM cf_agents_fibers
2453
+ WHERE status = ${status}
2454
+ ORDER BY created_at DESC, fiber_id DESC
2455
+ LIMIT ${limit}
2456
+ `;
2457
+ }
2458
+ async inspectFiber(fiberId) {
2459
+ const row = this._readFiber(fiberId);
2460
+ return row ? this._fiberInspectionFromRow(row) : null;
2461
+ }
2462
+ async inspectFiberByKey(idempotencyKey) {
2463
+ const row = this._readFiberByKey(idempotencyKey);
2464
+ return row ? this._fiberInspectionFromRow(row) : null;
2465
+ }
2466
+ async listFibers(options) {
2467
+ return this._listFiberRows(options).map((row) => this._fiberInspectionFromRow(row));
2468
+ }
2469
+ async cancelFiber(fiberId, reason) {
2470
+ const row = this._readFiber(fiberId);
2471
+ if (!row || this._isTerminalFiberStatus(row.status)) return false;
2472
+ const now = Date.now();
2473
+ this.sql`
2474
+ UPDATE cf_agents_fibers
2475
+ SET status = 'aborted',
2476
+ error_message = ${reason ?? null},
2477
+ completed_at = ${now}
2478
+ WHERE fiber_id = ${fiberId}
2479
+ AND status IN ('pending', 'running')
2480
+ `;
2481
+ this._managedFiberAbortControllers.get(fiberId)?.abort(reason);
2482
+ this._notifyManagedFiberTerminal(fiberId);
2483
+ return true;
2484
+ }
2485
+ async cancelFiberByKey(idempotencyKey, reason) {
2486
+ const row = this._readFiberByKey(idempotencyKey);
2487
+ return row ? this.cancelFiber(row.fiber_id, reason) : false;
2488
+ }
2489
+ async resolveFiber(fiberId, result) {
2490
+ const row = this._readFiber(fiberId);
2491
+ if (!row || row.status !== "interrupted") return false;
2492
+ this._applyManagedFiberRecoveryResult(fiberId, result);
2493
+ return true;
2494
+ }
2495
+ async deleteFibers(options) {
2496
+ const terminalStatuses = [...this._normalizeFiberStatusFilter(options?.status) ?? new Set([
2497
+ "completed",
2498
+ "aborted",
2499
+ "error"
2500
+ ])].filter((status) => this._isTerminalFiberStatus(status));
2501
+ if (terminalStatuses.length === 0) return 0;
2502
+ const limit = Math.min(Math.max(options?.limit ?? 100, 1), 500);
2503
+ const settledBefore = options?.settledBefore?.getTime();
2504
+ const rows = terminalStatuses.flatMap((status) => this._listTerminalFiberRowsForDelete(status, limit, settledBefore)).sort((a, b) => a.completed_at === b.completed_at ? a.created_at - b.created_at : (a.completed_at ?? 0) - (b.completed_at ?? 0)).slice(0, limit);
2505
+ for (const row of rows) this.sql`
2506
+ DELETE FROM cf_agents_fibers
2507
+ WHERE fiber_id = ${row.fiber_id}
2508
+ AND status IN ('completed', 'aborted', 'interrupted', 'error')
2509
+ `;
2510
+ return rows.length;
2511
+ }
2512
+ _listTerminalFiberRowsForDelete(status, limit, settledBefore) {
2513
+ if (settledBefore !== void 0) return this.sql`
2514
+ SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
2515
+ error_message, created_at, started_at, completed_at
2516
+ FROM cf_agents_fibers
2517
+ WHERE status = ${status}
2518
+ AND completed_at IS NOT NULL
2519
+ AND completed_at < ${settledBefore}
2520
+ ORDER BY completed_at ASC, created_at ASC
2521
+ LIMIT ${limit}
2522
+ `;
2523
+ return this.sql`
2524
+ SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
2525
+ error_message, created_at, started_at, completed_at
2526
+ FROM cf_agents_fibers
2527
+ WHERE status = ${status}
2528
+ ORDER BY completed_at ASC, created_at ASC
2529
+ LIMIT ${limit}
2530
+ `;
2531
+ }
2214
2532
  /**
2215
2533
  * Run a function as a durable fiber. The fiber is registered in SQLite
2216
2534
  * before execution, checkpointable during execution via `ctx.stash()`,
@@ -2225,7 +2543,100 @@ var Agent = class Agent extends Server {
2225
2543
  * @returns The return value of fn
2226
2544
  */
2227
2545
  async runFiber(name, fn) {
2228
- const id = nanoid();
2546
+ return this._runFiberInternal(nanoid(), name, fn);
2547
+ }
2548
+ async startFiber(name, fn, options) {
2549
+ const fiberId = options?.fiberId ?? nanoid();
2550
+ const idempotencyKey = options?.idempotencyKey;
2551
+ if (options?.fiberId !== void 0 && options.fiberId.trim() === "") throw new Error("fiberId must not be blank");
2552
+ if (options?.idempotencyKey !== void 0 && options.idempotencyKey.trim() === "") throw new Error("idempotencyKey must not be blank");
2553
+ const existingById = this._readFiber(fiberId);
2554
+ const existingByKey = idempotencyKey ? this._readFiberByKey(idempotencyKey) : null;
2555
+ if (existingById && existingByKey && existingById.fiber_id !== existingByKey.fiber_id) throw new Error("fiberId and idempotencyKey refer to different fibers");
2556
+ if (existingByKey && options?.fiberId && existingByKey.fiber_id !== fiberId) throw new Error("fiberId and idempotencyKey refer to different fibers");
2557
+ const existing = existingById ?? existingByKey;
2558
+ if (existing) {
2559
+ if (options?.waitForCompletion && !this._isTerminalFiberStatus(existing.status)) {
2560
+ const waited = await this._waitForManagedFiber(existing.fiber_id);
2561
+ if (waited) return {
2562
+ ...waited,
2563
+ accepted: false
2564
+ };
2565
+ throw new Error(`Fiber ${existing.fiber_id} no longer exists`);
2566
+ }
2567
+ return {
2568
+ ...this._fiberInspectionFromRow(existing),
2569
+ accepted: false
2570
+ };
2571
+ }
2572
+ const now = Date.now();
2573
+ this.sql`
2574
+ INSERT INTO cf_agents_fibers
2575
+ (fiber_id, idempotency_key, name, status, snapshot, metadata_json,
2576
+ error_message, created_at, started_at, completed_at)
2577
+ VALUES
2578
+ (${fiberId}, ${idempotencyKey ?? null}, ${name}, 'pending', NULL,
2579
+ ${options?.metadata ? JSON.stringify(options.metadata) : null}, NULL,
2580
+ ${now}, NULL, NULL)
2581
+ `;
2582
+ const row = this._readFiber(fiberId);
2583
+ if (!row) throw new Error(`Failed to create fiber ${fiberId}`);
2584
+ const execution = this._executeManagedFiber(fiberId, name, fn).catch((error) => {
2585
+ console.error(`[Agent] Managed fiber "${name}" (${fiberId}) failed:`, error);
2586
+ }).finally(() => {
2587
+ if (this._managedFiberExecutions.get(fiberId) === execution) this._managedFiberExecutions.delete(fiberId);
2588
+ });
2589
+ this._managedFiberExecutions.set(fiberId, execution);
2590
+ if (options?.waitForCompletion) {
2591
+ const completed = await this._waitForManagedFiber(fiberId);
2592
+ if (!completed) throw new Error(`Fiber ${fiberId} no longer exists`);
2593
+ return {
2594
+ ...completed,
2595
+ accepted: true
2596
+ };
2597
+ }
2598
+ return {
2599
+ ...this._fiberInspectionFromRow(row),
2600
+ accepted: true
2601
+ };
2602
+ }
2603
+ async _executeManagedFiber(fiberId, name, fn) {
2604
+ const row = this._readFiber(fiberId);
2605
+ if (!row || row.status !== "pending") return;
2606
+ const controller = new AbortController();
2607
+ this._managedFiberAbortControllers.set(fiberId, controller);
2608
+ const now = Date.now();
2609
+ this.sql`
2610
+ UPDATE cf_agents_fibers
2611
+ SET status = 'running', started_at = ${now}
2612
+ WHERE fiber_id = ${fiberId} AND status = 'pending'
2613
+ `;
2614
+ const updated = this._readFiber(fiberId);
2615
+ if (!updated || updated.status !== "running") {
2616
+ this._managedFiberAbortControllers.delete(fiberId);
2617
+ return;
2618
+ }
2619
+ let settled = false;
2620
+ try {
2621
+ await this._runFiberInternal(fiberId, name, fn, {
2622
+ signal: controller.signal,
2623
+ managed: true,
2624
+ beforeRunCleanup: (outcome) => {
2625
+ settled = true;
2626
+ this._settleManagedFiberExecution(fiberId, outcome, controller.signal);
2627
+ }
2628
+ });
2629
+ } catch (error) {
2630
+ if (!settled) this._settleManagedFiberExecution(fiberId, {
2631
+ ok: false,
2632
+ error
2633
+ }, controller.signal);
2634
+ } finally {
2635
+ this._managedFiberAbortControllers.delete(fiberId);
2636
+ }
2637
+ }
2638
+ async _runFiberInternal(id, name, fn, options) {
2639
+ const signal = options?.signal ?? new AbortController().signal;
2229
2640
  this.sql`
2230
2641
  INSERT INTO cf_agents_runs (id, name, snapshot, created_at)
2231
2642
  VALUES (${id}, ${name}, NULL, ${Date.now()})
@@ -2242,19 +2653,36 @@ var Agent = class Agent extends Server {
2242
2653
  }
2243
2654
  dispose = await this.keepAlive();
2244
2655
  const stash = (data) => {
2656
+ const snapshot = JSON.stringify(data);
2245
2657
  this.sql`
2246
- UPDATE cf_agents_runs SET snapshot = ${JSON.stringify(data)}
2658
+ UPDATE cf_agents_runs SET snapshot = ${snapshot}
2247
2659
  WHERE id = ${id}
2248
2660
  `;
2661
+ if (options?.managed) this.sql`
2662
+ UPDATE cf_agents_fibers SET snapshot = ${snapshot}
2663
+ WHERE fiber_id = ${id}
2664
+ `;
2249
2665
  };
2250
- return await _fiberALS.run({
2251
- id,
2252
- stash
2253
- }, () => fn({
2254
- id,
2255
- stash,
2256
- snapshot: null
2257
- }));
2666
+ try {
2667
+ const result = await _fiberALS.run({
2668
+ id,
2669
+ signal,
2670
+ stash
2671
+ }, () => fn({
2672
+ id,
2673
+ signal,
2674
+ stash,
2675
+ snapshot: null
2676
+ }));
2677
+ options?.beforeRunCleanup?.({ ok: true });
2678
+ return result;
2679
+ } catch (error) {
2680
+ options?.beforeRunCleanup?.({
2681
+ ok: false,
2682
+ error
2683
+ });
2684
+ throw error;
2685
+ }
2258
2686
  } finally {
2259
2687
  this._runFiberActiveFibers.delete(id);
2260
2688
  this.sql`DELETE FROM cf_agents_runs WHERE id = ${id}`;
@@ -2306,24 +2734,67 @@ var Agent = class Agent extends Server {
2306
2734
  const rows = this.sql`SELECT id, name, snapshot, created_at FROM cf_agents_runs`;
2307
2735
  for (const row of rows) {
2308
2736
  if (this._runFiberActiveFibers.has(row.id)) continue;
2309
- let snapshot = null;
2310
- if (row.snapshot) try {
2311
- snapshot = JSON.parse(row.snapshot);
2312
- } catch {
2313
- console.warn(`[Agent] Corrupted snapshot for fiber ${row.id}, treating as null`);
2314
- }
2737
+ const snapshot = this._parseFiberRecoverySnapshot(row.id, row.snapshot);
2315
2738
  const ctx = {
2316
2739
  id: row.id,
2317
2740
  name: row.name,
2318
2741
  snapshot,
2319
2742
  createdAt: row.created_at
2320
2743
  };
2321
- try {
2322
- if (!await this._handleInternalFiberRecovery(ctx)) await this.onFiberRecovered(ctx);
2323
- } catch (e) {
2324
- console.error(`[Agent] Fiber recovery failed for "${ctx.name}" (${ctx.id}):`, e);
2744
+ const managedRow = this._readFiber(row.id);
2745
+ if (managedRow) {
2746
+ if (this._isTerminalFiberStatus(managedRow.status)) {
2747
+ this.sql`DELETE FROM cf_agents_runs WHERE id = ${row.id}`;
2748
+ this._notifyManagedFiberTerminal(row.id);
2749
+ continue;
2750
+ }
2751
+ const completedAt = Date.now();
2752
+ this.sql`
2753
+ UPDATE cf_agents_fibers
2754
+ SET status = 'interrupted',
2755
+ snapshot = ${row.snapshot},
2756
+ completed_at = ${completedAt}
2757
+ WHERE fiber_id = ${row.id}
2758
+ AND status IN ('pending', 'running')
2759
+ `;
2760
+ ctx.idempotencyKey = managedRow.idempotency_key ?? void 0;
2761
+ ctx.metadata = this._parseFiberJsonObject(managedRow.metadata_json);
2762
+ ctx.status = "interrupted";
2325
2763
  }
2764
+ await this._runFiberRecoveryHook(ctx, managedRow);
2326
2765
  this.sql`DELETE FROM cf_agents_runs WHERE id = ${row.id}`;
2766
+ if (managedRow) this._notifyManagedFiberTerminal(row.id);
2767
+ }
2768
+ const ledgerOnlyRows = this.sql`
2769
+ SELECT f.fiber_id, f.idempotency_key, f.name, f.status, f.snapshot,
2770
+ f.metadata_json, f.error_message, f.created_at, f.started_at,
2771
+ f.completed_at
2772
+ FROM cf_agents_fibers f
2773
+ LEFT JOIN cf_agents_runs r ON r.id = f.fiber_id
2774
+ WHERE f.status IN ('pending', 'running')
2775
+ AND r.id IS NULL
2776
+ `;
2777
+ for (const row of ledgerOnlyRows) {
2778
+ if (this._runFiberActiveFibers.has(row.fiber_id)) continue;
2779
+ const snapshot = this._parseFiberRecoverySnapshot(row.fiber_id, row.snapshot);
2780
+ const completedAt = Date.now();
2781
+ this.sql`
2782
+ UPDATE cf_agents_fibers
2783
+ SET status = 'interrupted',
2784
+ completed_at = ${completedAt}
2785
+ WHERE fiber_id = ${row.fiber_id}
2786
+ AND status IN ('pending', 'running')
2787
+ `;
2788
+ await this._runFiberRecoveryHook({
2789
+ id: row.fiber_id,
2790
+ name: row.name,
2791
+ snapshot,
2792
+ createdAt: row.created_at,
2793
+ idempotencyKey: row.idempotency_key ?? void 0,
2794
+ metadata: this._parseFiberJsonObject(row.metadata_json),
2795
+ status: "interrupted"
2796
+ }, row);
2797
+ this._notifyManagedFiberTerminal(row.fiber_id);
2327
2798
  }
2328
2799
  } finally {
2329
2800
  this._runFiberRecoveryInProgress = false;
@@ -3092,7 +3563,29 @@ var Agent = class Agent extends Server {
3092
3563
  * @internal
3093
3564
  */
3094
3565
  async _cf_invokeSubAgent(className, name, method, args) {
3095
- const handle = await this._cf_resolveSubAgent(className, name);
3566
+ const stub = await this._cf_resolveSubAgent(className, name);
3567
+ return await this._cf_invokeStubMethod(stub, className, method, args);
3568
+ }
3569
+ /**
3570
+ * Bridge method used by `parentAgent()` when the requested parent is
3571
+ * itself a facet (and therefore has no top-level env namespace).
3572
+ * The root receives the full root-first target path, then each hop
3573
+ * delegates to the next facet using that facet's own `ctx.facets`.
3574
+ *
3575
+ * @internal
3576
+ */
3577
+ async _cf_invokeSubAgentPath(path, method, args) {
3578
+ const [self, next, ...rest] = path;
3579
+ if (!self) throw new Error(`Sub-agent path invocation requires a non-empty path.`);
3580
+ const ownClassName = this.constructor.name;
3581
+ if (self.className !== ownClassName || self.name !== this.name) throw new Error(`Sub-agent path invocation reached ${ownClassName}("${this.name}") but expected ${self.className}("${self.name}").`);
3582
+ if (!next) return await this._cf_invokeStubMethod(this, this.constructor.name, method, args);
3583
+ const child = await this._cf_resolveSubAgent(next.className, next.name);
3584
+ if (rest.length === 0) return await this._cf_invokeStubMethod(child, next.className, method, args);
3585
+ return await child._cf_invokeSubAgentPath([next, ...rest], method, args);
3586
+ }
3587
+ async _cf_invokeStubMethod(stub, className, method, args) {
3588
+ const handle = stub;
3096
3589
  if (typeof handle[method] !== "function") throw new Error(`Method "${method}" not found on ${className}.`);
3097
3590
  return await handle[method](...args);
3098
3591
  }
@@ -3116,11 +3609,11 @@ var Agent = class Agent extends Server {
3116
3609
  *
3117
3610
  * The facet's name (and `this.name` getter) is handled entirely by
3118
3611
  * partyserver via `ctx.id.name`, which is populated because the
3119
- * parent passed an explicit `id: parentNs.idFromName(name)` to
3612
+ * parent passed an explicit named Durable Object id to
3120
3613
  * `ctx.facets.get()` — see {@link _cf_resolveSubAgent}. No
3121
3614
  * `setName()` call or `__ps_name` storage write is needed; the
3122
- * facet's name survives cold wake automatically because the
3123
- * factory re-runs and `idFromName` is deterministic.
3615
+ * facet's name survives cold wake automatically because the factory
3616
+ * re-runs and `idFromName` is deterministic.
3124
3617
  *
3125
3618
  * @internal Called by {@link subAgent}.
3126
3619
  */
@@ -3167,26 +3660,33 @@ var Agent = class Agent extends Server {
3167
3660
  }];
3168
3661
  }
3169
3662
  /**
3170
- * Resolve a typed RPC stub for this facet's **immediate** parent
3663
+ * Resolve a typed parent stub for this facet's **immediate** parent
3171
3664
  * agent.
3172
3665
  *
3173
3666
  * Symmetric with `subAgent(Cls, name)`: while `subAgent` opens a
3174
3667
  * stub from parent to child, `parentAgent` opens one from child
3175
3668
  * to parent. Pass the direct parent's class reference — the
3176
3669
  * framework verifies it matches the last entry of
3177
- * `this.parentPath` at runtime, then looks up `env[Cls.name]` to
3178
- * find the namespace binding.
3670
+ * `this.parentPath` at runtime. If the parent is a top-level
3671
+ * Durable Object, the framework returns the normal namespace stub.
3672
+ * If the parent is itself a facet, the framework returns a bridge
3673
+ * proxy that routes method calls through the root/supervisor and
3674
+ * then down the recorded facet path.
3179
3675
  *
3180
3676
  * `this.parentPath` is root-first, so the direct parent is the
3181
3677
  * **last** entry: `this.parentPath.at(-1)`. For grandparents and
3182
3678
  * further ancestors, iterate `this.parentPath` and use
3183
3679
  * `getAgentByName(env.X, this.parentPath[i].name)` directly.
3184
3680
  *
3185
- * Assumes the standard "binding name matches class name" convention.
3186
- * If your `wrangler.jsonc` binds the parent under a different name
3187
- * (e.g. `{ class_name: "Inbox", name: "MY_INBOX" }`), call
3188
- * `getAgentByName(env.MY_INBOX, this.parentPath.at(-1)!.name)`
3189
- * directly instead.
3681
+ * For top-level parents, the framework first checks `env[Cls.name]`,
3682
+ * then falls back to the Worker `exports` object. This supports
3683
+ * custom binding names as long as the parent class is exported under
3684
+ * its class name.
3685
+ *
3686
+ * Facet-parent stubs route normal HTTP `.fetch()` calls through the
3687
+ * same root bridge as RPC methods. WebSocket upgrade requests are
3688
+ * not supported yet because WebSocket handles cannot be serialized
3689
+ * over RPC.
3190
3690
  *
3191
3691
  * @experimental The API surface may change before stabilizing.
3192
3692
  *
@@ -3194,7 +3694,8 @@ var Agent = class Agent extends Server {
3194
3694
  * @throws If `Cls.name` doesn't match the recorded direct-parent
3195
3695
  * class (guards against accidentally reaching the wrong
3196
3696
  * DO, especially in nested Root → Mid → Leaf chains).
3197
- * @throws If no env binding named `Cls.name` is found.
3697
+ * @throws If no namespace is found for a top-level parent, or no
3698
+ * root namespace is available for a facet parent bridge.
3198
3699
  *
3199
3700
  * @example
3200
3701
  * ```ts
@@ -3211,10 +3712,46 @@ var Agent = class Agent extends Server {
3211
3712
  const parent = this._parentPath[this._parentPath.length - 1];
3212
3713
  if (!parent) throw new Error(`parentAgent(): ${this.constructor.name} is not a facet — only sub-agents (spawned via \`subAgent()\`) have a parent.`);
3213
3714
  if (cls.name !== parent.className) throw new Error(`parentAgent(${cls.name}): this facet's recorded parent class is "${parent.className}", not "${cls.name}". Pass the class whose constructor actually spawned this facet.`);
3214
- const binding = this.env[cls.name];
3215
- if (!binding) throw new Error(`parentAgent(${cls.name}): no top-level binding "${cls.name}" found in env. If the parent is bound under a different name (e.g. "MY_${cls.name.toUpperCase()}"), use \`getAgentByName(env.MY_${cls.name.toUpperCase()}, this.parentPath.at(-1)!.name)\` directly.`);
3715
+ if (this._parentPath.length > 1) return await this._cf_parentAgentFacetProxy(cls.name, this._parentPath);
3716
+ const binding = this._cf_getTopLevelNamespaceByClassName(cls.name);
3717
+ if (!binding) throw new Error(`parentAgent(${cls.name}): no top-level namespace for "${cls.name}" was found in env or worker exports. Make sure the parent class is exported under that class name and registered as a Durable Object binding.`);
3216
3718
  return await getServerByName(binding, parent.name);
3217
3719
  }
3720
+ _cf_getTopLevelNamespaceByClassName(className) {
3721
+ return this._cf_asDurableObjectNamespace(this.env[className]) ?? this._cf_asDurableObjectNamespace(exports[className]);
3722
+ }
3723
+ _cf_asDurableObjectNamespace(candidate) {
3724
+ const binding = candidate;
3725
+ return binding?.idFromName ? binding : void 0;
3726
+ }
3727
+ async _cf_parentAgentFacetProxy(className, parentPath) {
3728
+ const [root] = parentPath;
3729
+ if (!root) throw new Error(`parentAgent(${className}): parent path is empty.`);
3730
+ const rootBinding = this._cf_getTopLevelNamespaceByClassName(root.className);
3731
+ if (!rootBinding) throw new Error(`parentAgent(${className}): direct parent is a facet, but no top-level root namespace "${root.className}" was found in env or worker exports to bridge the call.`);
3732
+ const rootStubPromise = getServerByName(rootBinding, root.name);
3733
+ const targetPath = parentPath.map((step) => ({ ...step }));
3734
+ const invokeBridge = async (method, args) => {
3735
+ return await (await rootStubPromise)._cf_invokeSubAgentPath(targetPath, method, args);
3736
+ };
3737
+ const owner = this;
3738
+ return new Proxy({}, { get(_target, prop) {
3739
+ if (isInternalJsStubProp(prop)) return void 0;
3740
+ if (typeof prop !== "string") return void 0;
3741
+ if (prop === "fetch") return async (input, init) => {
3742
+ if (owner._cf_isWebSocketUpgradeRequest(input, init)) throw new Error(`parentAgent(${className}).fetch() does not support WebSocket upgrade requests yet. Use externally routed sub-agent URLs for WebSocket connections.`);
3743
+ return await invokeBridge(prop, [input, init]);
3744
+ };
3745
+ return async (...args) => {
3746
+ return await invokeBridge(prop, args);
3747
+ };
3748
+ } });
3749
+ }
3750
+ _cf_isWebSocketUpgradeRequest(input, init) {
3751
+ const initHeaders = init?.headers ? new Headers(init.headers) : void 0;
3752
+ const requestHeaders = input instanceof Request ? new Headers(input.headers) : void 0;
3753
+ return initHeaders?.get("Upgrade")?.toLowerCase() === "websocket" || requestHeaders?.get("Upgrade")?.toLowerCase() === "websocket";
3754
+ }
3218
3755
  /**
3219
3756
  * Get or create a named sub-agent — a child Durable Object (facet)
3220
3757
  * with its own isolated SQLite storage running on the same machine.
@@ -3796,13 +4333,13 @@ var Agent = class Agent extends Server {
3796
4333
  if (!Cls) throw new Error(`Sub-agent class "${className}" 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.`);
3797
4334
  if (name.includes("\0")) throw new Error(`Sub-agent name contains null character (\\0), which is reserved.`);
3798
4335
  const facetKey = `${className}\0${name}`;
3799
- const parentClassName = this.constructor.name;
3800
- const parentNs = ctx.exports[parentClassName];
3801
- if (!parentNs?.idFromName) {
3802
- const minificationHint = /^_*[a-z][a-z0-9]{0,2}$/.test(parentClassName) ? ` The class name "${parentClassName}" looks minified — make sure your bundler preserves class names (e.g. esbuild's \`keepNames: true\`).` : "";
3803
- throw new Error(`Sub-agent bootstrap requires the parent class "${parentClassName}" to be bound as a Durable Object namespace, but ctx.exports["${parentClassName}"] is missing or doesn't expose idFromName.${minificationHint} Make sure the parent agent class is registered in your wrangler.jsonc durable_objects.bindings under its class name.`);
4336
+ const rootClassName = this._parentPath[0]?.className ?? this.constructor.name;
4337
+ const rootNs = ctx.exports[rootClassName];
4338
+ if (!rootNs?.idFromName) {
4339
+ const minificationHint = /^_*[a-z][a-z0-9]{0,2}$/.test(rootClassName) ? ` The class name "${rootClassName}" looks minified — make sure your bundler preserves class names (e.g. esbuild's \`keepNames: true\`).` : "";
4340
+ throw new Error(`Sub-agent bootstrap requires the root agent class "${rootClassName}" to be available as a Durable Object namespace, but ctx.exports["${rootClassName}"] is missing or doesn't expose idFromName.${minificationHint} Make sure the root agent class is exported under that class name and registered in your wrangler.jsonc durable_objects.bindings.`);
3804
4341
  }
3805
- const facetId = parentNs.idFromName(name);
4342
+ const facetId = rootNs.idFromName(name);
3806
4343
  const stub = ctx.facets.get(facetKey, () => ({
3807
4344
  class: Cls,
3808
4345
  id: facetId
@@ -3955,6 +4492,7 @@ var Agent = class Agent extends Server {
3955
4492
  this.sql`DROP TABLE IF EXISTS cf_agents_workflows`;
3956
4493
  this.sql`DROP TABLE IF EXISTS cf_agents_sub_agents`;
3957
4494
  this.sql`DROP TABLE IF EXISTS cf_agents_runs`;
4495
+ this.sql`DROP TABLE IF EXISTS cf_agents_fibers`;
3958
4496
  this.sql`DROP TABLE IF EXISTS cf_agents_facet_runs`;
3959
4497
  this.sql`DROP TABLE IF EXISTS cf_agent_tool_runs`;
3960
4498
  }