agents 0.13.3 → 0.14.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 (83) hide show
  1. package/README.md +6 -4
  2. package/dist/{agent-tool-types-l98LCbBl.d.ts → agent-tool-types-BAJWu8s4.d.ts} +474 -117
  3. package/dist/agent-tool-types.d.ts +13 -11
  4. package/dist/{agent-tools-Bg5ilERh.d.ts → agent-tools-0R6KEert.d.ts} +2 -2
  5. package/dist/{agent-tools-BAdX1vdI.js → agent-tools-DYrkT-Kx.js} +46 -6
  6. package/dist/agent-tools-DYrkT-Kx.js.map +1 -0
  7. package/dist/agent-tools.d.ts +14 -20
  8. package/dist/agent-tools.js +10 -6
  9. package/dist/agent-tools.js.map +1 -1
  10. package/dist/browser/ai.d.ts +1 -1
  11. package/dist/browser/ai.js +1 -1
  12. package/dist/browser/index.d.ts +1 -1
  13. package/dist/browser/index.js +1 -1
  14. package/dist/browser/tanstack-ai.d.ts +1 -1
  15. package/dist/browser/tanstack-ai.js +1 -1
  16. package/dist/chat/index.d.ts +162 -19
  17. package/dist/chat/index.js +97 -13
  18. package/dist/chat/index.js.map +1 -1
  19. package/dist/chat-sdk/index.d.ts +5 -5
  20. package/dist/chat-sdk/index.js +2 -2
  21. package/dist/chat-sdk/index.js.map +1 -1
  22. package/dist/{classPrivateFieldGet2-Evpt0SEr.js → classPrivateFieldGet2-D_obpP6O.js} +5 -5
  23. package/dist/classPrivateMethodInitSpec-10iTYB7F.js +7 -0
  24. package/dist/{client-D1kFXo80.js → client-FUizKzj2.js} +299 -95
  25. package/dist/client-FUizKzj2.js.map +1 -0
  26. package/dist/client.d.ts +1 -1
  27. package/dist/{compaction-helpers-B-pG5J22.d.ts → compaction-helpers-BEUILPss.d.ts} +59 -33
  28. package/dist/{compaction-helpers-fJyf8j4m.js → compaction-helpers-iiKMr2TQ.js} +22 -3
  29. package/dist/compaction-helpers-iiKMr2TQ.js.map +1 -0
  30. package/dist/{do-oauth-client-provider-4OKQU9rT.d.ts → do-oauth-client-provider-D4ZwyBDu.d.ts} +21 -1
  31. package/dist/{email-J0GGS3sa.d.ts → email-CL27preh.d.ts} +1 -1
  32. package/dist/email.d.ts +2 -2
  33. package/dist/experimental/memory/session/index.d.ts +30 -25
  34. package/dist/experimental/memory/session/index.js +7 -2
  35. package/dist/experimental/memory/session/index.js.map +1 -1
  36. package/dist/experimental/memory/utils/index.d.ts +12 -10
  37. package/dist/experimental/memory/utils/index.js +2 -2
  38. package/dist/{index-DKey3P4s.d.ts → index-RJ4OxMOe.d.ts} +270 -1
  39. package/dist/index.d.ts +74 -67
  40. package/dist/index.js +485 -64
  41. package/dist/index.js.map +1 -1
  42. package/dist/{internal_context-BZrMS0B5.d.ts → internal_context-Dg4Cgjcu.d.ts} +1 -1
  43. package/dist/internal_context.d.ts +1 -1
  44. package/dist/mcp/client.d.ts +17 -13
  45. package/dist/mcp/client.js +2 -2
  46. package/dist/mcp/do-oauth-client-provider.d.ts +1 -1
  47. package/dist/mcp/do-oauth-client-provider.js +143 -17
  48. package/dist/mcp/do-oauth-client-provider.js.map +1 -1
  49. package/dist/mcp/index.d.ts +35 -27
  50. package/dist/mcp/index.js +402 -69
  51. package/dist/mcp/index.js.map +1 -1
  52. package/dist/observability/index.d.ts +1 -1
  53. package/dist/observability/index.js +15 -1
  54. package/dist/observability/index.js.map +1 -1
  55. package/dist/react.d.ts +3 -3
  56. package/dist/react.js +1 -1
  57. package/dist/{retries-BVdRl5ZE.d.ts → retries-CF_HKSlJ.d.ts} +1 -1
  58. package/dist/retries.d.ts +1 -1
  59. package/dist/serializable.d.ts +1 -1
  60. package/dist/{shared-Cvj92byG.d.ts → shared-4CAYLCTO.d.ts} +1 -1
  61. package/dist/{shared-CiKaIK4h.js → shared-BIpUk4G5.js} +3 -7
  62. package/dist/{shared-CiKaIK4h.js.map → shared-BIpUk4G5.js.map} +1 -1
  63. package/dist/skills/index.d.ts +236 -0
  64. package/dist/skills/index.js +1326 -0
  65. package/dist/skills/index.js.map +1 -0
  66. package/dist/sub-routing.d.ts +6 -6
  67. package/dist/{tool-output-truncation-CH-khbZ3.js → tool-output-truncation-CNnnGZQ3.js} +1 -1
  68. package/dist/{tool-output-truncation-CH-khbZ3.js.map → tool-output-truncation-CNnnGZQ3.js.map} +1 -1
  69. package/dist/{types-_JjKmv-l.d.ts → types-6Zo2zfoO.d.ts} +1 -1
  70. package/dist/types.d.ts +1 -1
  71. package/dist/vite.d.ts +1 -1
  72. package/dist/vite.js +248 -2
  73. package/dist/vite.js.map +1 -1
  74. package/dist/{workflow-types-Dkzg4hAx.d.ts → workflow-types-SrZK_o9p.d.ts} +1 -1
  75. package/dist/workflow-types.d.ts +1 -1
  76. package/dist/workflows.d.ts +13 -3
  77. package/dist/workflows.js +10 -1
  78. package/dist/workflows.js.map +1 -1
  79. package/package.json +31 -13
  80. package/skills-module.d.ts +22 -0
  81. package/dist/agent-tools-BAdX1vdI.js.map +0 -1
  82. package/dist/client-D1kFXo80.js.map +0 -1
  83. package/dist/compaction-helpers-fJyf8j4m.js.map +0 -1
package/dist/index.js CHANGED
@@ -2,10 +2,10 @@ import { __DO_NOT_USE_WILL_BREAK__agentContext } from "./internal_context.js";
2
2
  import { MessageType } from "./types.js";
3
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-Evpt0SEr.js";
5
+ import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-D_obpP6O.js";
6
6
  import { SUB_PREFIX, getSubAgentByName, parseSubAgentPath, routeSubAgentRequest } from "./sub-routing.js";
7
7
  import { isErrorRetryable, tryN, validateRetryOptions } from "./retries.js";
8
- import { o as RPC_DO_PREFIX, r as MCPConnectionState, s as DisposableStore, t as MCPClientManager } from "./client-D1kFXo80.js";
8
+ import { a as MCPConnectionState, c as RPC_DO_PREFIX, i as normalizeServerId, l as DisposableStore, n as MCP_SERVER_ID_MAX_LENGTH, t as MCPClientManager } from "./client-FUizKzj2.js";
9
9
  import { DurableObjectOAuthClientProvider } from "./mcp/do-oauth-client-provider.js";
10
10
  import { genericObservability } from "./observability/index.js";
11
11
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -114,6 +114,9 @@ function getNextCronTime(cron) {
114
114
  return parseCronExpression(cron).getNextDate();
115
115
  }
116
116
  const DEFAULT_KEEP_ALIVE_INTERVAL_MS = 3e4;
117
+ const DEFAULT_AGENT_TOOL_RECOVERY_TIMEOUT_MS = 2e3;
118
+ const DEFAULT_AGENT_TOOL_RECOVERY_TOTAL_TIMEOUT_MS = 5e3;
119
+ const DEFAULT_AGENT_TOOL_REATTACH_TIMEOUT_MS = 12e4;
117
120
  const SUB_AGENT_IDENTITY_VERSION_LEGACY = "legacy";
118
121
  const SUB_AGENT_IDENTITY_VERSION_PATH_V2 = "path-v2";
119
122
  const SUB_AGENT_IDENTITY_PATH_V2_PREFIX = "cf-agents:v2:";
@@ -262,7 +265,17 @@ const DEFAULT_AGENT_STATIC_OPTIONS = {
262
265
  maxAttempts: 3,
263
266
  baseDelayMs: 100,
264
267
  maxDelayMs: 3e3
265
- }
268
+ },
269
+ /** Timeout for internal framework fiber recovery hooks. */
270
+ fiberRecoveryHookTimeoutMs: 1e4,
271
+ /** Soft deadline for one interrupted-fiber recovery scan. */
272
+ fiberRecoveryScanDeadlineMs: 1e4,
273
+ /**
274
+ * Maximum age of an unmanaged interrupted-fiber row before recovery gives
275
+ * up. Bounds repeated retries of a `onFiberRecovered()` hook that keeps
276
+ * throwing so a poison row cannot re-trigger forever across boots.
277
+ */
278
+ fiberRecoveryMaxAgeMs: 1440 * 60 * 1e3
266
279
  };
267
280
  /**
268
281
  * Parse the raw `retry_options` TEXT column from a SQLite row into a
@@ -285,6 +298,36 @@ function resolveRetryConfig(taskRetry, defaults) {
285
298
  maxDelayMs: taskRetry?.maxDelayMs ?? defaults.maxDelayMs
286
299
  };
287
300
  }
301
+ /**
302
+ * Whether an error is a transient "superseded isolate" failure — the invocation
303
+ * is running on an isolate the platform has replaced with a new version (a
304
+ * deploy / code update). For the rest of that invocation every operation throws
305
+ * the same error (code never reloads mid-invocation), so in-process retries are
306
+ * futile; but the next fresh invocation runs the new code and succeeds.
307
+ *
308
+ * workerd surfaces this as a plain `Error` with one of a few messages, all the
309
+ * same failure class — a message match is the only signal:
310
+ * - "Durable Object reset because its code was updated." (DO storage op on a
311
+ * superseded isolate / deploy bounce)
312
+ * - "This script has been upgraded. Please send a new request to connect to
313
+ * the new version." (a stub/connection to a superseded script; the message
314
+ * literally instructs the caller to retry on the new version)
315
+ *
316
+ * The match stays close to the verbatim platform strings (rather than a loose
317
+ * "upgraded"/"reset" substring) so an ordinary application error that happens
318
+ * to mention those words is NOT misclassified as a supersede — a false positive
319
+ * would defer + re-run a genuinely-failing callback on the platform's alarm
320
+ * retries instead of abandoning it.
321
+ *
322
+ * NOTE: "Network connection lost." is deliberately NOT included — it is a
323
+ * connection error, not an isolate replacement, and may succeed on in-process
324
+ * retry (it is gated by the CF `retryable` property via `isErrorRetryable`),
325
+ * so it stays on the normal retry path rather than the immediate-defer path.
326
+ */
327
+ function isDurableObjectCodeUpdateReset(error) {
328
+ const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
329
+ return /reset because its code was updated|this script has been upgraded/i.test(message);
330
+ }
288
331
  function getCurrentAgent() {
289
332
  const store = __DO_NOT_USE_WILL_BREAK__agentContext.getStore();
290
333
  if (!store) return {
@@ -382,7 +425,10 @@ var Agent = class Agent extends Server {
382
425
  maxAttempts: userRetry?.maxAttempts ?? DEFAULT_AGENT_STATIC_OPTIONS.retry.maxAttempts,
383
426
  baseDelayMs: userRetry?.baseDelayMs ?? DEFAULT_AGENT_STATIC_OPTIONS.retry.baseDelayMs,
384
427
  maxDelayMs: userRetry?.maxDelayMs ?? DEFAULT_AGENT_STATIC_OPTIONS.retry.maxDelayMs
385
- }
428
+ },
429
+ fiberRecoveryHookTimeoutMs: ctor.options?.fiberRecoveryHookTimeoutMs ?? DEFAULT_AGENT_STATIC_OPTIONS.fiberRecoveryHookTimeoutMs,
430
+ fiberRecoveryScanDeadlineMs: ctor.options?.fiberRecoveryScanDeadlineMs ?? DEFAULT_AGENT_STATIC_OPTIONS.fiberRecoveryScanDeadlineMs,
431
+ fiberRecoveryMaxAgeMs: ctor.options?.fiberRecoveryMaxAgeMs ?? DEFAULT_AGENT_STATIC_OPTIONS.fiberRecoveryMaxAgeMs
386
432
  };
387
433
  return this._cachedOptions;
388
434
  }
@@ -885,7 +931,7 @@ var Agent = class Agent extends Server {
885
931
  this.broadcastMcpServers();
886
932
  this._checkOrphanedWorkflows();
887
933
  await this._checkRunFibers();
888
- const recoveredAgentToolFinishes = await this._reconcileAgentToolRuns({ deferFinishHooks: true });
934
+ const startupAgentToolRunIds = this._agentToolRunRecoveryRunIds();
889
935
  this._insideOnStart = true;
890
936
  this._warnedScheduleInOnStart.clear();
891
937
  let result;
@@ -894,7 +940,7 @@ var Agent = class Agent extends Server {
894
940
  } finally {
895
941
  this._insideOnStart = false;
896
942
  }
897
- await this._runDeferredAgentToolFinishHooks(recoveredAgentToolFinishes);
943
+ this._scheduleAgentToolRunRecovery({ runIds: startupAgentToolRunIds });
898
944
  return result;
899
945
  });
900
946
  });
@@ -2386,20 +2432,67 @@ var Agent = class Agent extends Server {
2386
2432
  return null;
2387
2433
  }
2388
2434
  }
2435
+ _fiberRecoveryPayload(ctx, managedRow, startedAt) {
2436
+ return {
2437
+ fiberId: ctx.id,
2438
+ fiberName: ctx.name,
2439
+ managed: managedRow !== null,
2440
+ recoveryReason: ctx.recoveryReason,
2441
+ elapsedMs: startedAt === void 0 ? void 0 : Date.now() - startedAt
2442
+ };
2443
+ }
2444
+ async _withFiberRecoveryTimeout(ctx, operation) {
2445
+ const timeoutMs = this._resolvedOptions.fiberRecoveryHookTimeoutMs;
2446
+ if (timeoutMs <= 0) return operation();
2447
+ let timer;
2448
+ try {
2449
+ return await Promise.race([operation(), new Promise((_, reject) => {
2450
+ timer = setTimeout(() => {
2451
+ reject(/* @__PURE__ */ new Error(`Fiber recovery hook timed out after ${timeoutMs}ms for "${ctx.name}" (${ctx.id})`));
2452
+ }, timeoutMs);
2453
+ })]);
2454
+ } finally {
2455
+ if (timer !== void 0) clearTimeout(timer);
2456
+ }
2457
+ }
2458
+ _recordFiberRecoveryFailure(ctx, managedRow, error, startedAt, reason = "handler_error") {
2459
+ const errorMessage = this._fiberErrorMessage(error);
2460
+ const completedAt = Date.now();
2461
+ if (managedRow) {
2462
+ this.sql`
2463
+ UPDATE cf_agents_fibers
2464
+ SET status = 'error',
2465
+ error_message = ${errorMessage},
2466
+ completed_at = ${completedAt}
2467
+ WHERE fiber_id = ${ctx.id}
2468
+ AND status = 'interrupted'
2469
+ `;
2470
+ this._notifyManagedFiberTerminal(ctx.id);
2471
+ }
2472
+ this._emit("fiber:recovery:failed", {
2473
+ ...this._fiberRecoveryPayload(ctx, managedRow, startedAt),
2474
+ error: errorMessage,
2475
+ reason
2476
+ });
2477
+ }
2389
2478
  async _runFiberRecoveryHook(ctx, managedRow) {
2479
+ const startedAt = Date.now();
2480
+ this._emit("fiber:recovery:attempt", this._fiberRecoveryPayload(ctx, managedRow));
2390
2481
  try {
2391
- if (!await this._handleInternalFiberRecovery(ctx)) {
2482
+ const handled = await this._withFiberRecoveryTimeout(ctx, () => this._handleInternalFiberRecovery(ctx));
2483
+ if (!handled) {
2392
2484
  const recoveryResult = await this.onFiberRecovered(ctx);
2393
2485
  if (managedRow && recoveryResult) this._applyManagedFiberRecoveryResult(ctx.id, recoveryResult);
2394
2486
  }
2487
+ this._emit("fiber:recovery:handled", {
2488
+ ...this._fiberRecoveryPayload(ctx, managedRow, startedAt),
2489
+ status: handled ? "internal" : managedRow ? "managed" : "user"
2490
+ });
2491
+ return true;
2395
2492
  } catch (e) {
2396
- if (managedRow) this.sql`
2397
- UPDATE cf_agents_fibers
2398
- SET error_message = ${this._fiberErrorMessage(e)}
2399
- WHERE fiber_id = ${ctx.id}
2400
- AND status = 'interrupted'
2401
- `;
2493
+ this._recordFiberRecoveryFailure(ctx, managedRow, e, startedAt);
2402
2494
  console.error(`[Agent] Fiber recovery failed for "${ctx.name}" (${ctx.id}):`, e);
2495
+ return false;
2403
2496
  }
2404
2497
  }
2405
2498
  _fiberInspectionFromRow(row) {
@@ -2684,6 +2777,12 @@ var Agent = class Agent extends Server {
2684
2777
  INSERT INTO cf_agents_runs (id, name, snapshot, created_at)
2685
2778
  VALUES (${id}, ${name}, NULL, ${Date.now()})
2686
2779
  `;
2780
+ const startedAt = Date.now();
2781
+ this._emit("fiber:run:started", {
2782
+ fiberId: id,
2783
+ fiberName: name,
2784
+ managed: options?.managed === true
2785
+ });
2687
2786
  this._runFiberActiveFibers.add(id);
2688
2787
  const writeSnapshot = (data) => {
2689
2788
  const snapshot = JSON.stringify(data);
@@ -2722,12 +2821,25 @@ var Agent = class Agent extends Server {
2722
2821
  snapshot: null
2723
2822
  }));
2724
2823
  options?.beforeRunCleanup?.({ ok: true });
2824
+ this._emit("fiber:run:completed", {
2825
+ fiberId: id,
2826
+ fiberName: name,
2827
+ managed: options?.managed === true,
2828
+ elapsedMs: Date.now() - startedAt
2829
+ });
2725
2830
  return result;
2726
2831
  } catch (error) {
2727
2832
  options?.beforeRunCleanup?.({
2728
2833
  ok: false,
2729
2834
  error
2730
2835
  });
2836
+ this._emit("fiber:run:failed", {
2837
+ fiberId: id,
2838
+ fiberName: name,
2839
+ managed: options?.managed === true,
2840
+ error: this._fiberErrorMessage(error),
2841
+ elapsedMs: Date.now() - startedAt
2842
+ });
2731
2843
  throw error;
2732
2844
  }
2733
2845
  } finally {
@@ -2777,18 +2889,42 @@ var Agent = class Agent extends Server {
2777
2889
  async _checkRunFibers() {
2778
2890
  if (this._runFiberRecoveryInProgress) return;
2779
2891
  this._runFiberRecoveryInProgress = true;
2892
+ const scanStartedAt = Date.now();
2893
+ const scanDeadlineMs = this._resolvedOptions.fiberRecoveryScanDeadlineMs;
2894
+ const fiberRecoveryMaxAgeMs = this._resolvedOptions.fiberRecoveryMaxAgeMs;
2780
2895
  try {
2781
2896
  const rows = this.sql`SELECT id, name, snapshot, created_at FROM cf_agents_runs`;
2782
2897
  for (const row of rows) {
2898
+ if (scanDeadlineMs > 0 && Date.now() - scanStartedAt > scanDeadlineMs) {
2899
+ this._emit("fiber:recovery:skipped", {
2900
+ fiberId: row.id,
2901
+ fiberName: row.name,
2902
+ reason: "scan_deadline_exceeded",
2903
+ elapsedMs: Date.now() - scanStartedAt
2904
+ });
2905
+ break;
2906
+ }
2783
2907
  if (this._runFiberActiveFibers.has(row.id)) continue;
2784
2908
  const snapshot = this._parseFiberRecoverySnapshot(row.id, row.snapshot);
2785
2909
  const ctx = {
2786
2910
  id: row.id,
2787
2911
  name: row.name,
2788
2912
  snapshot,
2789
- createdAt: row.created_at
2913
+ createdAt: row.created_at,
2914
+ recoveryReason: "interrupted"
2790
2915
  };
2791
2916
  const managedRow = this._readFiber(row.id);
2917
+ this._emit("fiber:recovery:detected", {
2918
+ ...this._fiberRecoveryPayload(ctx, managedRow),
2919
+ elapsedMs: Date.now() - row.created_at
2920
+ });
2921
+ this._emit("fiber:run:interrupted", {
2922
+ fiberId: row.id,
2923
+ fiberName: row.name,
2924
+ managed: managedRow !== null,
2925
+ recoveryReason: "interrupted",
2926
+ elapsedMs: Date.now() - row.created_at
2927
+ });
2792
2928
  if (managedRow) {
2793
2929
  if (this._isTerminalFiberStatus(managedRow.status)) {
2794
2930
  this.sql`DELETE FROM cf_agents_runs WHERE id = ${row.id}`;
@@ -2808,8 +2944,17 @@ var Agent = class Agent extends Server {
2808
2944
  ctx.metadata = this._parseFiberJsonObject(managedRow.metadata_json);
2809
2945
  ctx.status = "interrupted";
2810
2946
  }
2811
- await this._runFiberRecoveryHook(ctx, managedRow);
2812
- this.sql`DELETE FROM cf_agents_runs WHERE id = ${row.id}`;
2947
+ const recovered = await this._runFiberRecoveryHook(ctx, managedRow);
2948
+ const tooOld = fiberRecoveryMaxAgeMs > 0 && Date.now() - row.created_at > fiberRecoveryMaxAgeMs;
2949
+ if (recovered || managedRow || tooOld) {
2950
+ if (!recovered && !managedRow && tooOld) this._emit("fiber:recovery:skipped", {
2951
+ fiberId: row.id,
2952
+ fiberName: row.name,
2953
+ reason: "max_age_exceeded",
2954
+ elapsedMs: Date.now() - row.created_at
2955
+ });
2956
+ this.sql`DELETE FROM cf_agents_runs WHERE id = ${row.id}`;
2957
+ }
2813
2958
  if (managedRow) this._notifyManagedFiberTerminal(row.id);
2814
2959
  }
2815
2960
  const ledgerOnlyRows = this.sql`
@@ -2822,6 +2967,16 @@ var Agent = class Agent extends Server {
2822
2967
  AND r.id IS NULL
2823
2968
  `;
2824
2969
  for (const row of ledgerOnlyRows) {
2970
+ if (scanDeadlineMs > 0 && Date.now() - scanStartedAt > scanDeadlineMs) {
2971
+ this._emit("fiber:recovery:skipped", {
2972
+ fiberId: row.fiber_id,
2973
+ fiberName: row.name,
2974
+ reason: "scan_deadline_exceeded",
2975
+ elapsedMs: Date.now() - scanStartedAt,
2976
+ managed: true
2977
+ });
2978
+ break;
2979
+ }
2825
2980
  if (this._runFiberActiveFibers.has(row.fiber_id)) continue;
2826
2981
  const snapshot = this._parseFiberRecoverySnapshot(row.fiber_id, row.snapshot);
2827
2982
  const completedAt = Date.now();
@@ -2832,15 +2987,28 @@ var Agent = class Agent extends Server {
2832
2987
  WHERE fiber_id = ${row.fiber_id}
2833
2988
  AND status IN ('pending', 'running')
2834
2989
  `;
2835
- await this._runFiberRecoveryHook({
2990
+ const ctx = {
2836
2991
  id: row.fiber_id,
2837
2992
  name: row.name,
2838
2993
  snapshot,
2839
2994
  createdAt: row.created_at,
2840
2995
  idempotencyKey: row.idempotency_key ?? void 0,
2841
2996
  metadata: this._parseFiberJsonObject(row.metadata_json),
2842
- status: "interrupted"
2843
- }, row);
2997
+ status: "interrupted",
2998
+ recoveryReason: "interrupted"
2999
+ };
3000
+ this._emit("fiber:recovery:detected", {
3001
+ ...this._fiberRecoveryPayload(ctx, row),
3002
+ elapsedMs: Date.now() - row.created_at
3003
+ });
3004
+ this._emit("fiber:run:interrupted", {
3005
+ fiberId: row.fiber_id,
3006
+ fiberName: row.name,
3007
+ managed: true,
3008
+ recoveryReason: "interrupted",
3009
+ elapsedMs: Date.now() - row.created_at
3010
+ });
3011
+ await this._runFiberRecoveryHook(ctx, row);
2844
3012
  this._notifyManagedFiberTerminal(row.fiber_id);
2845
3013
  }
2846
3014
  } finally {
@@ -2998,6 +3166,8 @@ var Agent = class Agent extends Server {
2998
3166
  });
2999
3167
  return;
3000
3168
  }
3169
+ const isOneShotSchedule = row.type === "delayed" || row.type === "scheduled";
3170
+ const shouldDeferReset = (error) => isOneShotSchedule && isDurableObjectCodeUpdateReset(error);
3001
3171
  try {
3002
3172
  this._emit("schedule:execute", {
3003
3173
  callback: row.callback,
@@ -3013,9 +3183,14 @@ var Agent = class Agent extends Server {
3013
3183
  await callback.bind(this)(parsedPayload, row);
3014
3184
  }, {
3015
3185
  baseDelayMs,
3016
- maxDelayMs
3186
+ maxDelayMs,
3187
+ shouldRetry: (error) => !shouldDeferReset(error)
3017
3188
  });
3018
3189
  } catch (e) {
3190
+ if (shouldDeferReset(e)) {
3191
+ console.warn(`Deferring scheduled callback "${row.callback}" to a fresh invocation after a Durable Object code-update reset; the one-shot row is preserved and the alarm will re-run on new code.`);
3192
+ throw e;
3193
+ }
3019
3194
  console.error(`error executing callback "${row.callback}" after ${maxAttempts} attempts`, e);
3020
3195
  this._emit("schedule:error", {
3021
3196
  callback: row.callback,
@@ -3835,7 +4010,7 @@ var Agent = class Agent extends Server {
3835
4010
  const agentType = cls.name;
3836
4011
  const existing = this._readAgentToolRun(runId);
3837
4012
  if (existing) {
3838
- if (this._isAgentToolTerminal(existing.status)) {
4013
+ if (existing.status === "completed" || existing.status === "error" || existing.status === "aborted") {
3839
4014
  if (existing.status === "completed" && existing.output_json == null) try {
3840
4015
  const child = await this.subAgent(cls, runId);
3841
4016
  const inspection = await this._asAgentToolChildAdapter(child).inspectAgentToolRun(runId);
@@ -3847,7 +4022,19 @@ var Agent = class Agent extends Server {
3847
4022
  } catch {}
3848
4023
  return this._resultFromAgentToolRow(existing);
3849
4024
  }
3850
- return await this._replayAndInterruptAgentToolRun(existing, "Agent tool run was still running, but live-tail reattachment is not supported in this runtime.");
4025
+ try {
4026
+ const child = await this.subAgent(cls, runId);
4027
+ const adapter = this._asAgentToolChildAdapter(child);
4028
+ const reattach = await this._reattachAgentToolRunToTerminal(adapter, existing, 1);
4029
+ if (reattach.result) {
4030
+ await this._finishAgentToolRun(this._agentToolRunInfoFromRow(existing), reattach.result, {
4031
+ sequence: reattach.sequence,
4032
+ completedAt: reattach.completedAt
4033
+ });
4034
+ return reattach.result;
4035
+ }
4036
+ } catch {}
4037
+ return await this._replayAndInterruptAgentToolRun(existing, "Agent tool run was still running and could not be re-attached to a terminal result within the recovery budget.");
3851
4038
  }
3852
4039
  const displayOrder = options.displayOrder ?? 0;
3853
4040
  const inputPreview = options.inputPreview ?? this._defaultAgentToolPreview(options.input);
@@ -4127,7 +4314,7 @@ var Agent = class Agent extends Server {
4127
4314
  error_message = ${result.error ?? null},
4128
4315
  completed_at = ${completedAt}
4129
4316
  WHERE run_id = ${runId}
4130
- AND status NOT IN ('completed', 'error', 'aborted', 'interrupted')
4317
+ AND status NOT IN ('completed', 'error', 'aborted')
4131
4318
  `;
4132
4319
  if (result.status === "completed" && result.output !== void 0) this.sql`
4133
4320
  UPDATE cf_agent_tool_runs
@@ -4179,7 +4366,12 @@ var Agent = class Agent extends Server {
4179
4366
  }
4180
4367
  async _broadcastAgentToolStoredChunks(row, sequence, replay, connection) {
4181
4368
  const child = await this._cf_resolveSubAgent(row.agent_type, row.run_id);
4182
- const chunks = await this._asAgentToolChildAdapter(child).getAgentToolChunks(row.run_id);
4369
+ const adapter = this._asAgentToolChildAdapter(child);
4370
+ return this._broadcastAgentToolStoredChunksFromAdapter(adapter, row, sequence, replay, connection);
4371
+ }
4372
+ async _broadcastAgentToolStoredChunksFromAdapter(adapter, row, sequence, replay, connection, timeoutMs) {
4373
+ const chunks = await this._getAgentToolChunksForRecovery(adapter, row.run_id, timeoutMs);
4374
+ if (!chunks) return sequence;
4183
4375
  return this._broadcastAgentToolChunks(row.parent_tool_call_id ?? void 0, row.run_id, chunks, sequence, replay, connection);
4184
4376
  }
4185
4377
  async _forwardAgentToolStream(stream, parentToolCallId, runId, sequence, signal) {
@@ -4188,11 +4380,17 @@ var Agent = class Agent extends Server {
4188
4380
  const reader = stream.getReader();
4189
4381
  const decoder = new TextDecoder();
4190
4382
  let bufferedBytes = "";
4383
+ let aborted = false;
4384
+ let resolveAbort;
4385
+ const abortPromise = new Promise((resolve) => {
4386
+ resolveAbort = resolve;
4387
+ });
4191
4388
  let abortListener;
4192
4389
  if (signal) {
4193
- abortListener = () => {};
4390
+ abortListener = () => resolveAbort?.();
4194
4391
  signal.addEventListener("abort", abortListener, { once: true });
4195
4392
  }
4393
+ let forwardedSinceProgress = false;
4196
4394
  try {
4197
4395
  const forwardChunk = (chunk) => {
4198
4396
  this._broadcastAgentToolEvent(parentToolCallId, next++, {
@@ -4200,6 +4398,7 @@ var Agent = class Agent extends Server {
4200
4398
  runId,
4201
4399
  body: chunk.body
4202
4400
  });
4401
+ forwardedSinceProgress = true;
4203
4402
  };
4204
4403
  const forwardLine = (line) => {
4205
4404
  try {
@@ -4221,14 +4420,17 @@ var Agent = class Agent extends Server {
4221
4420
  }
4222
4421
  };
4223
4422
  while (true) {
4224
- let readResult;
4225
- try {
4226
- readResult = await reader.read();
4227
- } catch (error) {
4228
- if (signal?.aborted) break;
4229
- throw error;
4423
+ const readPromise = reader.read();
4424
+ readPromise.catch(() => {});
4425
+ const raced = await Promise.race([readPromise.then((result) => ({
4426
+ kind: "read",
4427
+ result
4428
+ })), abortPromise.then(() => ({ kind: "abort" }))]);
4429
+ if (raced.kind === "abort") {
4430
+ aborted = true;
4431
+ break;
4230
4432
  }
4231
- const { done, value } = readResult;
4433
+ const { done, value } = raced.result;
4232
4434
  if (done) {
4233
4435
  bufferedBytes += decoder.decode();
4234
4436
  flushBufferedBytes(true);
@@ -4238,13 +4440,42 @@ var Agent = class Agent extends Server {
4238
4440
  bufferedBytes += decoder.decode(value, { stream: true });
4239
4441
  flushBufferedBytes();
4240
4442
  } else forwardChunk(value);
4443
+ if (forwardedSinceProgress) {
4444
+ forwardedSinceProgress = false;
4445
+ try {
4446
+ await this._onAgentToolStreamProgress();
4447
+ } catch {}
4448
+ }
4241
4449
  }
4242
4450
  } finally {
4243
4451
  if (abortListener && signal) signal.removeEventListener("abort", abortListener);
4244
- reader.releaseLock();
4452
+ if (!aborted) try {
4453
+ reader.releaseLock();
4454
+ } catch {}
4245
4455
  }
4246
4456
  return next;
4247
4457
  }
4458
+ /**
4459
+ * Hook invoked by `_forwardAgentToolStream` after a child produces output that
4460
+ * was forwarded to the parent's connections. Forwarding a sub-agent's stream
4461
+ * is genuine forward progress for the *parent* turn (the parent is
4462
+ * orchestrating the child), so chat-recovery subclasses (Think / AIChatAgent)
4463
+ * override this to advance their recovery progress marker.
4464
+ *
4465
+ * Without it, a parent whose turn merely `await`s a sub-agent banks zero
4466
+ * progress of its own, so under deploy churn the parent's no-progress recovery
4467
+ * window exhausts and abandons the turn as `interrupted` — even though the
4468
+ * child is healthily streaming and ultimately completes (observed in the
4469
+ * `deploy-churn --mode subagent` harness: `attempt 6/6, stable_timeout,
4470
+ * progress: 1`).
4471
+ *
4472
+ * Called ONLY after at least one chunk was actually forwarded — never merely
4473
+ * because a child is attached — so a silent / hung child still lets the parent
4474
+ * exhaust on its own timer. The base Agent has no recovery budget, so this is
4475
+ * a no-op; subclasses should throttle the (durable) bump since this can be
4476
+ * called repeatedly while a child streams.
4477
+ */
4478
+ async _onAgentToolStreamProgress() {}
4248
4479
  _broadcastAgentToolTerminal(parentToolCallId, sequence, result, replay, connection) {
4249
4480
  if (result.status === "completed") this._broadcastAgentToolEvent(parentToolCallId, sequence, {
4250
4481
  kind: "finished",
@@ -4291,6 +4522,58 @@ var Agent = class Agent extends Server {
4291
4522
  await this._finishAgentToolRun(this._agentToolRunInfoFromRow(row), result, { sequence });
4292
4523
  return result;
4293
4524
  }
4525
+ /**
4526
+ * Re-attach to a still-running child agent-tool run and tail it to its real
4527
+ * terminal result, instead of abandoning it as `interrupted` (#1630). The
4528
+ * child is a separate facet with its own `chatRecovery`, so resolving it via
4529
+ * the adapter wakes it and lets it self-complete the interrupted turn; we tail
4530
+ * its live stream (forwarding chunks to the parent's connections) until it
4531
+ * reaches terminal, then inspect for the collected result.
4532
+ *
4533
+ * Bounded by {@link DEFAULT_AGENT_TOOL_REATTACH_TIMEOUT_MS}: a child that keeps
4534
+ * advancing toward terminal within the window is collected; a genuinely hung
4535
+ * child returns `{ result: undefined }` once the budget elapses so the caller
4536
+ * falls back to `interrupted` and recovery can never block forever.
4537
+ *
4538
+ * Returns the terminal `result` (and `completedAt`) when the child reaches a
4539
+ * terminal status within the budget, plus the advanced broadcast `sequence`.
4540
+ * Returns `{ result: undefined }` when there is no `tailAgentToolRun` adapter,
4541
+ * the budget is exhausted, or the child is still non-terminal.
4542
+ */
4543
+ async _reattachAgentToolRunToTerminal(adapter, row, sequence, budgetMs = DEFAULT_AGENT_TOOL_REATTACH_TIMEOUT_MS) {
4544
+ if (typeof adapter.tailAgentToolRun !== "function") return { sequence };
4545
+ const controller = new AbortController();
4546
+ let timeoutId;
4547
+ if (budgetMs > 0 && Number.isFinite(budgetMs)) timeoutId = setTimeout(() => controller.abort(), budgetMs);
4548
+ else if (budgetMs <= 0) controller.abort();
4549
+ this._emit("agent_tool:recovery:reattach", {
4550
+ runId: row.run_id,
4551
+ agentType: row.agent_type,
4552
+ budgetMs
4553
+ });
4554
+ let nextSequence = sequence;
4555
+ if (!controller.signal.aborted) try {
4556
+ let afterSequence = -1;
4557
+ try {
4558
+ const existing = await adapter.getAgentToolChunks(row.run_id);
4559
+ const last = existing[existing.length - 1];
4560
+ if (last) afterSequence = last.sequence;
4561
+ } catch {}
4562
+ const stream = await adapter.tailAgentToolRun(row.run_id, { afterSequence });
4563
+ nextSequence = await this._forwardAgentToolStream(stream, row.parent_tool_call_id ?? void 0, row.run_id, nextSequence, controller.signal);
4564
+ } catch {}
4565
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
4566
+ let inspection = null;
4567
+ try {
4568
+ inspection = await adapter.inspectAgentToolRun(row.run_id);
4569
+ } catch {}
4570
+ if (inspection && inspection.status !== "running" && inspection.status !== "starting") return {
4571
+ sequence: nextSequence,
4572
+ result: this._terminalResultFromInspection(row.agent_type, inspection),
4573
+ completedAt: inspection.completedAt
4574
+ };
4575
+ return { sequence: nextSequence };
4576
+ }
4294
4577
  async _replayAgentToolRuns(connection) {
4295
4578
  const rows = this.sql`
4296
4579
  SELECT run_id, parent_tool_call_id, agent_type, input_preview, status,
@@ -4323,6 +4606,10 @@ var Agent = class Agent extends Server {
4323
4606
  }
4324
4607
  }
4325
4608
  async _reconcileAgentToolRuns(options) {
4609
+ const reattachTimeoutMs = options?.reattachTimeoutMs ?? DEFAULT_AGENT_TOOL_REATTACH_TIMEOUT_MS;
4610
+ const startedAt = Date.now();
4611
+ const totalTimeoutMs = options?.totalRecoveryTimeoutMs ?? DEFAULT_AGENT_TOOL_RECOVERY_TOTAL_TIMEOUT_MS;
4612
+ const deadlineAt = totalTimeoutMs > 0 ? startedAt + totalTimeoutMs : Number.POSITIVE_INFINITY;
4326
4613
  const deferredFinishes = [];
4327
4614
  const rows = this.sql`
4328
4615
  SELECT run_id, parent_tool_call_id, agent_type, input_preview, status,
@@ -4332,43 +4619,158 @@ var Agent = class Agent extends Server {
4332
4619
  WHERE status IN ('starting', 'running')
4333
4620
  ORDER BY started_at ASC
4334
4621
  `;
4335
- for (const row of rows) {
4336
- let sequence = 1;
4337
- let completedAt;
4338
- let result;
4339
- try {
4340
- const child = await this._cf_resolveSubAgent(row.agent_type, row.run_id);
4341
- const inspection = await this._asAgentToolChildAdapter(child).inspectAgentToolRun(row.run_id);
4342
- try {
4343
- sequence = await this._broadcastAgentToolStoredChunks(row, sequence);
4344
- } catch {}
4345
- if (!inspection || inspection.status === "running" || inspection.status === "starting") result = {
4622
+ const runIds = options?.runIds !== void 0 ? new Set(options.runIds) : void 0;
4623
+ const recoveryRows = rows.filter((row) => !runIds || runIds.has(row.run_id));
4624
+ this._emit("agent_tool:recovery:begin", {
4625
+ runCount: recoveryRows.length,
4626
+ totalTimeoutMs
4627
+ });
4628
+ const finalizeRow = async (row, result, sequence, completedAt) => {
4629
+ this._emit("agent_tool:recovery:row", {
4630
+ runId: row.run_id,
4631
+ agentType: row.agent_type,
4632
+ status: result.status,
4633
+ reason: result.error,
4634
+ elapsedMs: Date.now() - startedAt
4635
+ });
4636
+ const deferredFinish = await this._finishAgentToolRun(this._agentToolRunInfoFromRow(row), result, {
4637
+ sequence,
4638
+ completedAt,
4639
+ deferFinishHook: options?.deferFinishHooks
4640
+ });
4641
+ if (deferredFinish) deferredFinishes.push(deferredFinish);
4642
+ };
4643
+ const reattachQueue = [];
4644
+ for (const row of recoveryRows) {
4645
+ const sequence = 1;
4646
+ const remainingMs = deadlineAt - Date.now();
4647
+ if (remainingMs <= 0) {
4648
+ this._emit("agent_tool:recovery:deadline", {
4649
+ runId: row.run_id,
4650
+ agentType: row.agent_type,
4651
+ elapsedMs: Date.now() - startedAt
4652
+ });
4653
+ await finalizeRow(row, {
4346
4654
  runId: row.run_id,
4347
4655
  agentType: row.agent_type,
4348
4656
  status: "interrupted",
4349
- error: "Agent tool run was still running, but live-tail reattachment is not supported in this runtime."
4350
- };
4351
- else {
4352
- result = this._terminalResultFromInspection(row.agent_type, inspection);
4353
- completedAt = inspection.completedAt;
4354
- }
4355
- } catch {
4356
- result = {
4657
+ error: "Agent tool run recovery deadline exceeded."
4658
+ }, sequence, void 0);
4659
+ continue;
4660
+ }
4661
+ const childTimeout = options?.childInspectionTimeoutMs ?? DEFAULT_AGENT_TOOL_RECOVERY_TIMEOUT_MS;
4662
+ const boundedChildTimeout = childTimeout > 0 ? Math.min(childTimeout, remainingMs) : remainingMs;
4663
+ const recovery = await this._inspectAgentToolRunForRecovery(row, sequence, boundedChildTimeout);
4664
+ if (recovery.status !== "inspected") {
4665
+ await finalizeRow(row, {
4357
4666
  runId: row.run_id,
4358
4667
  agentType: row.agent_type,
4359
4668
  status: "interrupted",
4360
- error: "Agent tool run could not be inspected during parent recovery."
4361
- };
4669
+ error: recovery.status === "timed-out" ? "Agent tool run inspection timed out during parent recovery." : "Agent tool run could not be inspected during parent recovery."
4670
+ }, sequence, void 0);
4671
+ continue;
4362
4672
  }
4363
- const deferredFinish = await this._finishAgentToolRun(this._agentToolRunInfoFromRow(row), result, {
4364
- sequence,
4365
- completedAt,
4366
- deferFinishHook: options?.deferFinishHooks
4367
- });
4368
- if (deferredFinish) deferredFinishes.push(deferredFinish);
4673
+ const inspection = recovery.inspection;
4674
+ const stillRunning = !inspection || inspection.status === "running" || inspection.status === "starting";
4675
+ if (stillRunning && typeof recovery.adapter.tailAgentToolRun === "function") {
4676
+ reattachQueue.push({
4677
+ row,
4678
+ adapter: recovery.adapter
4679
+ });
4680
+ continue;
4681
+ }
4682
+ let sequenceAfterReplay = sequence;
4683
+ try {
4684
+ sequenceAfterReplay = await this._broadcastAgentToolStoredChunksFromAdapter(recovery.adapter, row, sequence, void 0, void 0, boundedChildTimeout);
4685
+ } catch {}
4686
+ if (stillRunning) await finalizeRow(row, {
4687
+ runId: row.run_id,
4688
+ agentType: row.agent_type,
4689
+ status: "interrupted",
4690
+ error: "Agent tool run was still running, but live-tail reattachment is not supported in this runtime."
4691
+ }, sequenceAfterReplay, void 0);
4692
+ else await finalizeRow(row, this._terminalResultFromInspection(row.agent_type, inspection), sequenceAfterReplay, inspection.completedAt);
4369
4693
  }
4694
+ await Promise.all(reattachQueue.map(async ({ row, adapter }) => {
4695
+ const reattach = await this._reattachAgentToolRunToTerminal(adapter, row, 1, reattachTimeoutMs);
4696
+ await finalizeRow(row, reattach.result ?? {
4697
+ runId: row.run_id,
4698
+ agentType: row.agent_type,
4699
+ status: "interrupted",
4700
+ error: "Agent tool run was still running and did not reach a terminal result within the re-attach budget."
4701
+ }, reattach.sequence, reattach.completedAt);
4702
+ }));
4703
+ this._emit("agent_tool:recovery:complete", {
4704
+ runCount: recoveryRows.length,
4705
+ elapsedMs: Date.now() - startedAt
4706
+ });
4370
4707
  return deferredFinishes;
4371
4708
  }
4709
+ async _inspectAgentToolRunForRecovery(row, _sequence, timeoutMs = DEFAULT_AGENT_TOOL_RECOVERY_TIMEOUT_MS) {
4710
+ const inspect = (async () => {
4711
+ const child = await this._cf_resolveSubAgent(row.agent_type, row.run_id);
4712
+ const adapter = this._asAgentToolChildAdapter(child);
4713
+ return {
4714
+ status: "inspected",
4715
+ adapter,
4716
+ inspection: await adapter.inspectAgentToolRun(row.run_id)
4717
+ };
4718
+ })().catch(() => ({ status: "failed" }));
4719
+ if (timeoutMs <= 0) return inspect;
4720
+ let timeoutId;
4721
+ const timeout = new Promise((resolve) => {
4722
+ timeoutId = setTimeout(() => {
4723
+ resolve({ status: "timed-out" });
4724
+ }, timeoutMs);
4725
+ });
4726
+ const result = await Promise.race([inspect, timeout]);
4727
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
4728
+ return result;
4729
+ }
4730
+ _scheduleAgentToolRunRecovery(options) {
4731
+ if (this._agentToolRunRecoveryPromise) return this._agentToolRunRecoveryPromise;
4732
+ if (options?.runIds && options.runIds.length === 0) return Promise.resolve();
4733
+ const recovery = (async () => {
4734
+ await new Promise((resolve) => setTimeout(resolve, 0));
4735
+ const recoveredAgentToolFinishes = await this._reconcileAgentToolRuns({
4736
+ deferFinishHooks: true,
4737
+ childInspectionTimeoutMs: options?.childInspectionTimeoutMs,
4738
+ totalRecoveryTimeoutMs: options?.totalRecoveryTimeoutMs,
4739
+ reattachTimeoutMs: options?.reattachTimeoutMs,
4740
+ runIds: options?.runIds
4741
+ });
4742
+ await this._runDeferredAgentToolFinishHooks(recoveredAgentToolFinishes);
4743
+ })().catch(async (error) => {
4744
+ this._emit("agent_tool:recovery:failed", { error: error instanceof Error ? error.message : String(error) });
4745
+ try {
4746
+ await this.onError(error);
4747
+ } catch {}
4748
+ }).finally(() => {
4749
+ this._agentToolRunRecoveryPromise = void 0;
4750
+ });
4751
+ this._agentToolRunRecoveryPromise = recovery;
4752
+ this.ctx.waitUntil(recovery);
4753
+ return recovery;
4754
+ }
4755
+ _agentToolRunRecoveryRunIds() {
4756
+ return this.sql`
4757
+ SELECT run_id
4758
+ FROM cf_agent_tool_runs
4759
+ WHERE status IN ('starting', 'running')
4760
+ ORDER BY started_at ASC
4761
+ `.map((row) => row.run_id);
4762
+ }
4763
+ async _getAgentToolChunksForRecovery(adapter, runId, timeoutMs) {
4764
+ const chunks = adapter.getAgentToolChunks(runId).catch(() => void 0);
4765
+ if (timeoutMs === void 0 || timeoutMs <= 0) return chunks;
4766
+ let timeoutId;
4767
+ const timeout = new Promise((resolve) => {
4768
+ timeoutId = setTimeout(() => resolve(void 0), timeoutMs);
4769
+ });
4770
+ const result = await Promise.race([chunks, timeout]);
4771
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
4772
+ return result;
4773
+ }
4372
4774
  /**
4373
4775
  * Shared facet resolution — takes a CamelCase class name string
4374
4776
  * (matching `ctx.exports`) rather than a class reference. Both
@@ -5360,7 +5762,26 @@ var Agent = class Agent extends Server {
5360
5762
  async addMcpServer(serverName, urlOrBinding, callbackHostOrOptions, agentsPrefix, options) {
5361
5763
  const isHttpTransport = typeof urlOrBinding === "string";
5362
5764
  const normalizedUrl = isHttpTransport ? new URL(urlOrBinding).href : void 0;
5363
- const existingServer = this.mcp.listServers().find((s) => s.name === serverName && (!isHttpTransport || new URL(s.server_url).href === normalizedUrl));
5765
+ let requestedId;
5766
+ if (typeof callbackHostOrOptions === "object" && callbackHostOrOptions !== null && typeof callbackHostOrOptions.id === "string") {
5767
+ const rawId = callbackHostOrOptions.id;
5768
+ requestedId = normalizeServerId(rawId);
5769
+ }
5770
+ const allServers = this.mcp.listServers();
5771
+ const existingServer = allServers.find((s) => s.name === serverName && (!isHttpTransport || new URL(s.server_url).href === normalizedUrl));
5772
+ if (requestedId) {
5773
+ const idConflict = allServers.find((s) => {
5774
+ if (s.id !== requestedId) return false;
5775
+ if (s.name !== serverName) return true;
5776
+ if (isHttpTransport) return new URL(s.server_url).href !== normalizedUrl;
5777
+ return false;
5778
+ });
5779
+ if (idConflict) throw new Error(`MCP server id "${requestedId}" is already in use by server "${idConflict.name}" (${idConflict.server_url}). Stable ids must be unique per (name, url).`);
5780
+ if (existingServer && existingServer.id !== requestedId) {
5781
+ await this.mcp.migrateServerId(existingServer.id, requestedId, this.name);
5782
+ existingServer.id = requestedId;
5783
+ }
5784
+ }
5364
5785
  if (existingServer && this.mcp.mcpConnections[existingServer.id]) {
5365
5786
  const conn = this.mcp.mcpConnections[existingServer.id];
5366
5787
  if (conn.connectionState === MCPConnectionState.AUTHENTICATING && conn.options.transport.authProvider?.authUrl) return {
@@ -5377,7 +5798,7 @@ var Agent = class Agent extends Server {
5377
5798
  if (typeof urlOrBinding !== "string") {
5378
5799
  const rpcOpts = callbackHostOrOptions;
5379
5800
  const normalizedName = serverName.toLowerCase().replace(/\s+/g, "-");
5380
- const reconnectId = existingServer?.id;
5801
+ const reconnectId = requestedId ?? existingServer?.id;
5381
5802
  const { id } = await this.mcp.connect(`${RPC_DO_PREFIX}${normalizedName}`, {
5382
5803
  reconnect: reconnectId ? { id: reconnectId } : void 0,
5383
5804
  transport: {
@@ -5434,7 +5855,7 @@ var Agent = class Agent extends Server {
5434
5855
  const normalizedHost = resolvedCallbackHost.replace(/\/$/, "");
5435
5856
  callbackUrl = resolvedCallbackPath ? `${normalizedHost}/${resolvedCallbackPath.replace(/^\//, "")}` : `${normalizedHost}/${resolvedAgentsPrefix}/${camelCaseToKebabCase(this._ParentClass.name)}/${this.name}/callback`;
5436
5857
  }
5437
- const id = nanoid(8);
5858
+ const id = requestedId ?? nanoid(8);
5438
5859
  let authProvider;
5439
5860
  if (callbackUrl) {
5440
5861
  authProvider = this.createMcpOAuthProvider(callbackUrl);
@@ -5766,6 +6187,6 @@ var StreamingResponse = class {
5766
6187
  }
5767
6188
  };
5768
6189
  //#endregion
5769
- export { Agent, DEFAULT_AGENT_STATIC_OPTIONS, DurableObjectOAuthClientProvider, MessageType, SUB_PREFIX, SqlError, StreamingResponse, __DO_NOT_USE_WILL_BREAK__agentContext, callable, createHeaderBasedEmailResolver, getAgentByName, getCurrentAgent, getSubAgentByName, parseSubAgentPath, routeAgentEmail, routeAgentRequest, routeSubAgentRequest, unstable_callable };
6190
+ export { Agent, DEFAULT_AGENT_STATIC_OPTIONS, DurableObjectOAuthClientProvider, MCP_SERVER_ID_MAX_LENGTH, MessageType, SUB_PREFIX, SqlError, StreamingResponse, __DO_NOT_USE_WILL_BREAK__agentContext, callable, createHeaderBasedEmailResolver, getAgentByName, getCurrentAgent, getSubAgentByName, normalizeServerId, parseSubAgentPath, routeAgentEmail, routeAgentRequest, routeSubAgentRequest, unstable_callable };
5770
6191
 
5771
6192
  //# sourceMappingURL=index.js.map