agents 0.23.0 → 0.25.0

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 (187) hide show
  1. package/dist/{agent-routing-DE5zmCQ8.d.ts → agent-routing-0DR0Tk2v.d.ts} +270 -190
  2. package/dist/agent-routing.d.ts +1 -1
  3. package/dist/agent-routing.js.map +1 -1
  4. package/dist/agent-tool-types.d.ts +13 -7
  5. package/dist/agent-tool-types.js +23 -1
  6. package/dist/agent-tool-types.js.map +1 -1
  7. package/dist/{agent-tools-y7zLfw4Q.js → agent-tools-CdJC-8Dc.js} +72 -9
  8. package/dist/agent-tools-CdJC-8Dc.js.map +1 -0
  9. package/dist/{agent-tools-DtXMTDGM.d.ts → agent-tools-YrATZJu2.d.ts} +29 -15
  10. package/dist/agent-tools.d.ts +6 -4
  11. package/dist/agent-tools.js.map +1 -1
  12. package/dist/ai-fm7xx1BK.js +312 -0
  13. package/dist/ai-fm7xx1BK.js.map +1 -0
  14. package/dist/base64-redaction-C1Uu29t5.js +59 -0
  15. package/dist/base64-redaction-C1Uu29t5.js.map +1 -0
  16. package/dist/browser/ai-sdk.d.ts +90 -0
  17. package/dist/browser/ai-sdk.js +51 -0
  18. package/dist/browser/ai-sdk.js.map +1 -0
  19. package/dist/browser/ai.d.ts +4 -4
  20. package/dist/browser/ai.js +1 -347
  21. package/dist/browser/index.d.ts +81 -79
  22. package/dist/browser/index.js +361 -2
  23. package/dist/browser/index.js.map +1 -0
  24. package/dist/browser/tanstack-ai.d.ts +96 -1
  25. package/dist/browser/tanstack-ai.js +62 -2
  26. package/dist/browser/tanstack-ai.js.map +1 -1
  27. package/dist/browser-DE3d-RLk.d.ts +275 -0
  28. package/dist/browser-tool-CQ10e6Kj.js +480 -0
  29. package/dist/browser-tool-CQ10e6Kj.js.map +1 -0
  30. package/dist/browser-tool-DuNdlwZ9.d.ts +73 -0
  31. package/dist/{capability-BjSKYpzg.js → capability-B4WbF81e.js} +2 -1
  32. package/dist/capability-B4WbF81e.js.map +1 -0
  33. package/dist/{capability-runner-Be_-PLR1.d.ts → capability-runner-DBAnGr6L.d.ts} +45 -15
  34. package/dist/chat/index.d.ts +341 -232
  35. package/dist/chat/index.js +602 -72
  36. package/dist/chat/index.js.map +1 -1
  37. package/dist/chat/react.d.ts +42 -2
  38. package/dist/chat/react.js +191 -35
  39. package/dist/chat/react.js.map +1 -1
  40. package/dist/chat/transport.d.ts +1 -1
  41. package/dist/chat/transport.js +1 -1
  42. package/dist/chat-sdk/index.d.ts +17 -3
  43. package/dist/chat-sdk/index.js +28 -2
  44. package/dist/chat-sdk/index.js.map +1 -1
  45. package/dist/client-4OlMIJx-.js +591 -0
  46. package/dist/client-4OlMIJx-.js.map +1 -0
  47. package/dist/{client-jagG8a9_.js → client-fyQrLGkD.js} +2 -2
  48. package/dist/{client-jagG8a9_.js.map → client-fyQrLGkD.js.map} +1 -1
  49. package/dist/client-je3koD9Z.d.ts +446 -0
  50. package/dist/client.d.ts +29 -283
  51. package/dist/client.js +2 -274
  52. package/dist/{connector-CptFKzRh.js → connector-58XDGvc3.js} +251 -120
  53. package/dist/connector-58XDGvc3.js.map +1 -0
  54. package/dist/{connector-CkQD4MK3.d.ts → connector-Cl-VqPmA.d.ts} +244 -146
  55. package/dist/context/index.d.ts +29 -0
  56. package/dist/context/index.js +64 -17
  57. package/dist/context/index.js.map +1 -1
  58. package/dist/current-agent-DhoDkSnH.js.map +1 -1
  59. package/dist/current-agent-vAk_5JMt.d.ts +93 -0
  60. package/dist/{current-agent-Da_C9a3b.d.ts → durable-object-lifecycle-HhgOMWq9.d.ts} +26 -127
  61. package/dist/harnesses/pi/index.d.ts +348 -0
  62. package/dist/harnesses/pi/index.js +754 -0
  63. package/dist/harnesses/pi/index.js.map +1 -0
  64. package/dist/images-iiTDSyRu.js +1631 -0
  65. package/dist/images-iiTDSyRu.js.map +1 -0
  66. package/dist/index-BreL0YrQ.d.ts +78 -0
  67. package/dist/index-kbSUIer3.d.ts +343 -0
  68. package/dist/index.d.ts +43 -41
  69. package/dist/index.js +2 -2
  70. package/dist/internal_context.d.ts +1 -1
  71. package/dist/lifecycle/index.d.ts +19 -17
  72. package/dist/lifecycle/index.js +2 -2
  73. package/dist/{lifecycle-CMRGjZdw.js → lifecycle-CFu6OV03.js} +73 -29
  74. package/dist/lifecycle-CFu6OV03.js.map +1 -0
  75. package/dist/mcp/client/index.d.ts +20 -20
  76. package/dist/mcp/client/index.js +1 -1
  77. package/dist/mcp/client/x402.js +21 -19
  78. package/dist/mcp/client/x402.js.map +1 -1
  79. package/dist/mcp/index.d.ts +35 -35
  80. package/dist/mcp/index.js +2 -2
  81. package/dist/{message-builder-BymO4N_D.js → message-builder-DAl9AhHH.js} +89 -4
  82. package/dist/message-builder-DAl9AhHH.js.map +1 -0
  83. package/dist/models/ai-sdk/index.d.ts +353 -0
  84. package/dist/models/ai-sdk/index.js +2443 -0
  85. package/dist/models/ai-sdk/index.js.map +1 -0
  86. package/dist/models/pi-ai/index.d.ts +313 -0
  87. package/dist/models/pi-ai/index.js +1795 -0
  88. package/dist/models/pi-ai/index.js.map +1 -0
  89. package/dist/observability/ai/index.js +20 -4
  90. package/dist/observability/ai/index.js.map +1 -1
  91. package/dist/{protocol-B0nh6KNf.js → protocol-kHsr-f0X.js} +26 -12
  92. package/dist/{protocol-B0nh6KNf.js.map → protocol-kHsr-f0X.js.map} +1 -1
  93. package/dist/queue/index.d.ts +123 -0
  94. package/dist/queue/index.js +2 -0
  95. package/dist/queue-BB-zfIYF.js +406 -0
  96. package/dist/queue-BB-zfIYF.js.map +1 -0
  97. package/dist/react.d.ts +31 -13
  98. package/dist/react.js +190 -71
  99. package/dist/react.js.map +1 -1
  100. package/dist/{retries-D9Ds-1lz.d.ts → retries-CV6AkXmQ.d.ts} +23 -8
  101. package/dist/retries.d.ts +12 -8
  102. package/dist/retries.js +19 -1
  103. package/dist/retries.js.map +1 -1
  104. package/dist/routing/index.d.ts +2 -2
  105. package/dist/routing/index.js +1 -1
  106. package/dist/{scheduler-DD9NdYbF.js → scheduler-DZ3xQfeK.js} +4 -11
  107. package/dist/scheduler-DZ3xQfeK.js.map +1 -0
  108. package/dist/{scheduler-Dwh85ZGl.d.ts → scheduler-RdACNXDP.d.ts} +6 -6
  109. package/dist/schedules/index.d.ts +1 -1
  110. package/dist/schedules/index.js +1 -1
  111. package/dist/serializable.d.ts +1 -1
  112. package/dist/sessions/index.d.ts +70 -3
  113. package/dist/sessions/index.js +276 -67
  114. package/dist/sessions/index.js.map +1 -1
  115. package/dist/skills/index.d.ts +29 -314
  116. package/dist/socket-address.d.ts +16 -0
  117. package/dist/socket-address.js +19 -0
  118. package/dist/socket-address.js.map +1 -0
  119. package/dist/{src-DlSHshb2.js → src-BZeu-SCD.js} +541 -500
  120. package/dist/src-BZeu-SCD.js.map +1 -0
  121. package/dist/state/index.d.ts +6 -0
  122. package/dist/state/index.js +137 -0
  123. package/dist/state/index.js.map +1 -0
  124. package/dist/streams/index.d.ts +1 -1
  125. package/dist/streams/index.js +1 -1
  126. package/dist/{streams-D6tJ0NN9.d.ts → streams-Cmkj9QfW.d.ts} +4 -2
  127. package/dist/{streams-DZKgAj9b.js → streams-DW-UCOUc.js} +9 -2
  128. package/dist/streams-DW-UCOUc.js.map +1 -0
  129. package/dist/sub-routing.d.ts +12 -12
  130. package/dist/tasks/index.d.ts +1 -1
  131. package/dist/tasks/index.js +1 -1
  132. package/dist/{tasks-ylZgBjhj.js → tasks-68DESxf0.js} +3 -3
  133. package/dist/{tasks-ylZgBjhj.js.map → tasks-68DESxf0.js.map} +1 -1
  134. package/dist/{tasks-BRJ5zgya.d.ts → tasks-9YxYtByh.d.ts} +5 -5
  135. package/dist/transport-PfFnt-Ld.d.ts +525 -0
  136. package/dist/transport-protocol-BxyngQ_R.js +25 -0
  137. package/dist/transport-protocol-BxyngQ_R.js.map +1 -0
  138. package/dist/transport-protocol-ZTew5Wso.d.ts +27 -0
  139. package/dist/types-BA4anrjI.d.ts +70 -0
  140. package/dist/voice/index.d.ts +2 -2
  141. package/dist/websockets/index.d.ts +195 -69
  142. package/dist/websockets/index.js +3 -2
  143. package/dist/websockets-Ci-LZFHH.js +1030 -0
  144. package/dist/websockets-Ci-LZFHH.js.map +1 -0
  145. package/dist/wire-types-CnMt6_HR.js.map +1 -1
  146. package/dist/wire-types-CrjKvDyi.d.ts +300 -0
  147. package/dist/workflow-types.d.ts +25 -25
  148. package/dist/workflows.d.ts +21 -21
  149. package/dist/workflows.js +1 -1
  150. package/dist/{ws-chat-transport-UNRIS2xl.d.ts → ws-chat-transport-BbT1A5Lc.d.ts} +51 -2
  151. package/dist/{ws-chat-transport-rWwta645.js → ws-chat-transport-cqMKq3IY.js} +143 -16
  152. package/dist/ws-chat-transport-cqMKq3IY.js.map +1 -0
  153. package/docs/agent-class.md +2 -2
  154. package/docs/agent-tools.md +29 -4
  155. package/docs/browse-the-web.md +87 -5
  156. package/docs/chat-agents.md +124 -16
  157. package/docs/client-sdk.md +30 -0
  158. package/docs/context.md +27 -0
  159. package/docs/durable-execution.md +2 -0
  160. package/docs/harnesses/pi.md +91 -0
  161. package/docs/human-in-the-loop.md +4 -0
  162. package/docs/index.md +4 -1
  163. package/docs/lifecycle.md +114 -23
  164. package/docs/models-pi-ai.md +325 -0
  165. package/docs/models.md +504 -0
  166. package/docs/queue.md +135 -241
  167. package/docs/resumable-streaming.md +2 -0
  168. package/docs/retries.md +1 -1
  169. package/docs/routing.md +13 -13
  170. package/docs/sessions.md +36 -1
  171. package/docs/state.md +1 -0
  172. package/docs/sub-agents.md +12 -3
  173. package/package.json +51 -3
  174. package/dist/agent-tools-y7zLfw4Q.js.map +0 -1
  175. package/dist/browser/ai.js.map +0 -1
  176. package/dist/capability-BjSKYpzg.js.map +0 -1
  177. package/dist/client-tools-aIBO0Fk7.d.ts +0 -53
  178. package/dist/client.js.map +0 -1
  179. package/dist/connector-CptFKzRh.js.map +0 -1
  180. package/dist/lifecycle-CMRGjZdw.js.map +0 -1
  181. package/dist/message-builder-BymO4N_D.js.map +0 -1
  182. package/dist/scheduler-DD9NdYbF.js.map +0 -1
  183. package/dist/src-DlSHshb2.js.map +0 -1
  184. package/dist/streams-DZKgAj9b.js.map +0 -1
  185. package/dist/websockets-DUfRHPRq.js +0 -502
  186. package/dist/websockets-DUfRHPRq.js.map +0 -1
  187. package/dist/ws-chat-transport-rWwta645.js.map +0 -1
@@ -1,26 +1,28 @@
1
1
  import { camelCaseToKebabCase, isInternalJsStubProp } from "./utils.js";
2
2
  import { getAgentByName } from "./agent-routing.js";
3
- import { AGENT_TOOL_MILESTONE_PART } from "./agent-tool-types.js";
3
+ import { AGENT_TOOL_MILESTONE_PART, isAgentToolLifecycleChunk } from "./agent-tool-types.js";
4
4
  import { n as getCurrentAgent$1, t as __DO_NOT_USE_WILL_BREAK__agentContext } from "./current-agent-DhoDkSnH.js";
5
5
  import "./internal_context.js";
6
6
  import { copyCallableMetadata, decoratedMethods, getCallableMetadata, isCallableMethod } from "./callable-decorator.js";
7
7
  import "./types.js";
8
+ import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-DZBYAB34.js";
9
+ import { t as _classPrivateMethodInitSpec } from "./classPrivateMethodInitSpec-qMjJ6sHQ.js";
8
10
  import { signAgentHeaders } from "./email.js";
9
11
  import { sendAgentEmail } from "./email-send.js";
10
- import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-DZBYAB34.js";
11
12
  import { n as withInvocationScope, r as writeSpanAttributes, t as tracer } from "./cloudflare-Dzvc7V2N.js";
12
13
  import { parseSubAgentPath } from "./sub-routing.js";
13
- import { t as LifecycleCapability } from "./capability-BjSKYpzg.js";
14
- import { t as _classPrivateMethodInitSpec } from "./classPrivateMethodInitSpec-qMjJ6sHQ.js";
15
- import { a as abortWithoutAlarmRetry, i as setLifecycleRouteTransport, n as setLifecycleEventSink, r as setLifecycleHostInvoker, t as Lifecycle } from "./lifecycle-CMRGjZdw.js";
14
+ import { a as getConnectionFlag, c as isConnectionReadonly, d as setConnectionProtocolEnabled, f as setConnectionReadonly, i as ensureConnectionWrapped, l as registerInternalConnectionKeys, o as getConnectionRawState, s as isConnectionProtocolEnabled, t as WebSockets, u as setConnectionFlag } from "./websockets-Ci-LZFHH.js";
15
+ import { t as LifecycleCapability } from "./capability-B4WbF81e.js";
16
+ import { a as abortWithoutAlarmRetry, i as setLifecycleRouteTransport, n as setLifecycleEventSink, r as setLifecycleHostInvoker, t as Lifecycle } from "./lifecycle-CFu6OV03.js";
16
17
  import { isErrorRetryable, tryN, validateRetryOptions } from "./retries.js";
17
18
  import { SqlError } from "./sql-error.js";
18
- import { o as callablesFromDecorated, t as WebSockets } from "./websockets-DUfRHPRq.js";
19
- import { a as ensureMcpServerTable, d as DisposableStore, i as normalizeServerId, l as RPC_DO_PREFIX, o as MCPConnectionState, t as MCPClientManager } from "./client-jagG8a9_.js";
19
+ import { a as ensureMcpServerTable, d as DisposableStore, i as normalizeServerId, l as RPC_DO_PREFIX, o as MCPConnectionState, t as MCPClientManager } from "./client-fyQrLGkD.js";
20
20
  import { DurableObjectOAuthClientProvider } from "./mcp/client/do-oauth-client-provider.js";
21
21
  import { genericObservability } from "./observability/index.js";
22
- import { n as setSchedulerCallbackResolver, t as Scheduler } from "./scheduler-DD9NdYbF.js";
23
- import { n as setTaskDefinitionResolver, r as setTaskRoutedMemoryLimitHandler, t as Tasks } from "./tasks-ylZgBjhj.js";
22
+ import { n as setSchedulerCallbackResolver, t as Scheduler } from "./scheduler-DZ3xQfeK.js";
23
+ import { n as setQueueCallbackResolver, t as Queue } from "./queue-BB-zfIYF.js";
24
+ import { n as setTaskDefinitionResolver, r as setTaskRoutedMemoryLimitHandler, t as Tasks } from "./tasks-68DESxf0.js";
25
+ import { State } from "./state/index.js";
24
26
  import { AsyncLocalStorage } from "node:async_hooks";
25
27
  import { DurableObject, RpcTarget, exports } from "cloudflare:workers";
26
28
  import { nanoid } from "nanoid";
@@ -294,12 +296,47 @@ function _addColumnIfNotExists(sql) {
294
296
  */
295
297
  const CF_SUB_AGENT_OUTER_URL_KEY = "_cf_subAgentOuterUrl";
296
298
  const CF_SUB_AGENT_TAGS_KEY = "_cf_subAgentTags";
299
+ /**
300
+ * Set on a socket closed because its sub-agent was deleted. Its late
301
+ * message/close events are dropped, so they can't reach a same-name
302
+ * replacement created after the delete. Storage-frozen — never rename.
303
+ */
304
+ const CF_SUB_AGENT_DELETED_KEY = "_cf_subAgentDeleted";
297
305
  /** Wire-frozen internal header carrying the outer URL on WS upgrades. */
298
306
  const SUB_AGENT_OUTER_URL_HEADER = "x-cf-agents-subagent-url";
307
+ /**
308
+ * The close frame a rejected sub-agent WebSocket receives in place of the
309
+ * `onBeforeSubAgent` response, which a browser cannot read from a failed
310
+ * handshake. A 4xx closes with `4000 + status`, which clients treat as
311
+ * final; anything else closes with 1011 so the client retries.
312
+ */
313
+ function subAgentRejectionClose(response) {
314
+ const { status } = response;
315
+ return {
316
+ code: status >= 400 && status < 500 ? 4e3 + status : 1011,
317
+ reason: `Sub-agent connection rejected (${status})`
318
+ };
319
+ }
320
+ /**
321
+ * Upgrade a rejected sub-agent WebSocket only to close it, so the client
322
+ * sees a close frame instead of a failed handshake that reconnects forever.
323
+ */
324
+ function rejectSubAgentWebSocket(response) {
325
+ const { code, reason } = subAgentRejectionClose(response);
326
+ const [client, server] = Object.values(new WebSocketPair());
327
+ server.accept();
328
+ server.close(code, reason);
329
+ return new Response(null, {
330
+ status: 101,
331
+ webSocket: client
332
+ });
333
+ }
299
334
  var _host = /* @__PURE__ */ new WeakMap();
300
335
  var _facetKeepAliveTokens = /* @__PURE__ */ new WeakMap();
301
336
  var _bridgeContext = /* @__PURE__ */ new WeakMap();
302
337
  var _virtualConnections = /* @__PURE__ */ new WeakMap();
338
+ var _virtualConnectionsHydrated = /* @__PURE__ */ new WeakMap();
339
+ var _virtualConnectionsHydration = /* @__PURE__ */ new WeakMap();
303
340
  var _connectionOperationTails = /* @__PURE__ */ new WeakMap();
304
341
  var _broadcastOperationTail = /* @__PURE__ */ new WeakMap();
305
342
  var _DynamicAgentsInternal_brand = /* @__PURE__ */ new WeakSet();
@@ -333,6 +370,8 @@ var DynamicAgentsInternal = class extends LifecycleCapability {
333
370
  _classPrivateFieldInitSpec(this, _facetKeepAliveTokens, /* @__PURE__ */ new Set());
334
371
  _classPrivateFieldInitSpec(this, _bridgeContext, new AsyncLocalStorage());
335
372
  _classPrivateFieldInitSpec(this, _virtualConnections, /* @__PURE__ */ new Map());
373
+ _classPrivateFieldInitSpec(this, _virtualConnectionsHydrated, false);
374
+ _classPrivateFieldInitSpec(this, _virtualConnectionsHydration, void 0);
336
375
  _classPrivateFieldInitSpec(this, _connectionOperationTails, /* @__PURE__ */ new Map());
337
376
  _classPrivateFieldInitSpec(this, _broadcastOperationTail, void 0);
338
377
  _classPrivateFieldSet2(_host, this, host);
@@ -387,7 +426,7 @@ var DynamicAgentsInternal = class extends LifecycleCapability {
387
426
  if (!_classPrivateFieldGet2(_host, this).hasSubAgent(next.className, next.name)) {
388
427
  const stalePath = targetPath.slice(0, selfPath.length + 1);
389
428
  if (_classPrivateFieldGet2(_host, this)._isFacet) await (await this.rootAlarmOwner())._cf_cleanupFacetPrefix(stalePath);
390
- else await _classPrivateFieldGet2(_host, this)._cf_cleanupFacetPrefix(stalePath);
429
+ else await this.cleanupPrefix(stalePath);
391
430
  return false;
392
431
  }
393
432
  return (await _classPrivateFieldGet2(_host, this)._cf_resolveSubAgent(next.className, next.name))._cf_routeLifecycle(target, envelope);
@@ -401,7 +440,7 @@ var DynamicAgentsInternal = class extends LifecycleCapability {
401
440
  if (!root) throw new Error("Facet routing requires a root parent.");
402
441
  const binding = _classPrivateFieldGet2(_host, this).ctx.exports?.[root.className];
403
442
  if (!binding) throw new Error(`Unable to resolve root "${root.className}" for facet routing.`);
404
- return await getAgentByName(binding, root.name);
443
+ return binding.get(binding.idFromName(root.name));
405
444
  }
406
445
  rootResolvesToSelf() {
407
446
  const root = _classPrivateFieldGet2(_host, this)._parentPath[0];
@@ -412,14 +451,16 @@ var DynamicAgentsInternal = class extends LifecycleCapability {
412
451
  }
413
452
  /**
414
453
  * Clean root-owned bookkeeping for a sub-tree of facets: bulk-cancel
415
- * schedules and routed Task wake mirrors under the owner-path prefix,
416
- * and delete root-side facet fiber recovery leases for the same sub-tree.
454
+ * schedules, queue items, and routed Task wake mirrors under the
455
+ * owner-path prefix, and delete root-side facet fiber recovery leases for
456
+ * the same sub-tree.
417
457
  */
418
458
  async cleanupPrefix(ownerPath) {
419
459
  const prefix = agentPathKey(ownerPath);
420
460
  if (prefix) {
421
461
  await _classPrivateFieldGet2(_host, this).scheduler.__DO_NOT_USE_WILL_BREAK__cleanupRoutePrefix(prefix);
422
462
  await _classPrivateFieldGet2(_host, this).tasks.__DO_NOT_USE_WILL_BREAK__cleanupRoutePrefix(prefix);
463
+ await _classPrivateFieldGet2(_host, this)._queue.__DO_NOT_USE_WILL_BREAK__cleanupRoutePrefix(prefix);
423
464
  }
424
465
  this.deleteRunRowsForPrefix(ownerPath);
425
466
  await _classPrivateFieldGet2(_host, this)._syncHostJobs();
@@ -501,6 +542,7 @@ var DynamicAgentsInternal = class extends LifecycleCapability {
501
542
  * rows on the target facet after recovery.
502
543
  */
503
544
  async checkRunFibersAtPath(ownerPath) {
545
+ await _classPrivateFieldGet2(_host, this).__unsafe_ensureInitialized();
504
546
  const selfPath = _classPrivateFieldGet2(_host, this).selfPath;
505
547
  if (!_classPrivateFieldGet2(_host, this)._isSameAgentPathPrefix(selfPath, ownerPath)) throw new Error(`Facet fiber owner path does not descend from ${JSON.stringify(selfPath)}.`);
506
548
  if (selfPath.length === ownerPath.length) {
@@ -544,11 +586,12 @@ var DynamicAgentsInternal = class extends LifecycleCapability {
544
586
  if (targetPath.length === 0) throw new Error("_cf_destroyDescendantFacet: target path must not be empty.");
545
587
  if (selfPath.length >= targetPath.length) throw new Error("_cf_destroyDescendantFacet: target must be a strict descendant.");
546
588
  if (!_classPrivateFieldGet2(_host, this)._isSameAgentPathPrefix(selfPath, targetPath)) throw new Error("_cf_destroyDescendantFacet: target path does not descend from this agent.");
547
- if (_classPrivateFieldGet2(_host, this)._parentPath.length === 0) await _classPrivateFieldGet2(_host, this)._cf_cleanupFacetPrefix(targetPath);
589
+ if (_classPrivateFieldGet2(_host, this)._parentPath.length === 0) await this.cleanupPrefix(targetPath);
548
590
  if (selfPath.length === targetPath.length - 1) {
549
591
  const target = targetPath[targetPath.length - 1];
550
592
  const ctx = _classPrivateFieldGet2(_host, this).ctx;
551
593
  if (!ctx.facets) throw new Error("destroy() (delegated from facet) is not supported in this runtime — `ctx.facets` is unavailable. Update to the latest `compatibility_date` in your wrangler.jsonc.");
594
+ await _assertClassBrand(_DynamicAgentsInternal_brand, this, _closeConnectionsForDeletedPath).call(this, targetPath);
552
595
  try {
553
596
  ctx.facets.delete(`${target.className}\0${target.name}`);
554
597
  } catch {}
@@ -624,16 +667,31 @@ var DynamicAgentsInternal = class extends LifecycleCapability {
624
667
  className,
625
668
  name
626
669
  }];
627
- if (_classPrivateFieldGet2(_host, this)._isFacet) await (await this.rootAlarmOwner())._cf_cleanupFacetPrefix(childPath);
628
- else await _classPrivateFieldGet2(_host, this)._cf_cleanupFacetPrefix(childPath);
670
+ const root = _classPrivateFieldGet2(_host, this)._isFacet ? await this.rootAlarmOwner() : void 0;
671
+ await _assertClassBrand(_DynamicAgentsInternal_brand, this, _closeConnectionsForDeletedPath).call(this, childPath, root);
629
672
  try {
630
673
  ctx.facets.delete(facetKey);
631
674
  } catch {}
632
675
  this.registry.forget(className, name);
676
+ if (root) await root._cf_cleanupFacetPrefix(childPath);
677
+ else await this.cleanupPrefix(childPath);
678
+ }
679
+ /** Root-side: close sockets whose `/sub/...` target is under `prefix`. */
680
+ closeConnectionsForPrefix(prefix, code, reason) {
681
+ for (const connection of _classPrivateFieldGet2(_host, this)._webSockets.getConnections()) {
682
+ const targetPath = this.connectionTargetPath(connection);
683
+ if (!targetPath) continue;
684
+ if (!_classPrivateFieldGet2(_host, this)._isSameAgentPathPrefix(prefix, targetPath)) continue;
685
+ _classPrivateFieldGet2(_host, this)._unsafe_setConnectionFlag(connection, CF_SUB_AGENT_DELETED_KEY, true);
686
+ try {
687
+ connection.close(code, reason);
688
+ } catch {}
689
+ }
633
690
  }
634
691
  /** Drop all facet-side virtual connections (test/rehydration hook). */
635
692
  clearVirtualConnections() {
636
693
  _classPrivateFieldGet2(_virtualConnections, this).clear();
694
+ _classPrivateFieldSet2(_virtualConnectionsHydrated, this, false);
637
695
  }
638
696
  /** Facet-side lookup of a virtual connection by id. */
639
697
  getVirtualConnection(id) {
@@ -711,6 +769,7 @@ var DynamicAgentsInternal = class extends LifecycleCapability {
711
769
  await completion;
712
770
  }
713
771
  async broadcastToParent(message, without) {
772
+ if (_assertClassBrand(_DynamicAgentsInternal_brand, this, _broadcastHasNoRecipients).call(this, without)) return;
714
773
  await this.routeBroadcast(_classPrivateFieldGet2(_host, this).selfPath, message, without);
715
774
  }
716
775
  async broadcastToPath(ownerPath, message, without) {
@@ -747,12 +806,12 @@ var DynamicAgentsInternal = class extends LifecycleCapability {
747
806
  async setConnectionState(connectionId, state) {
748
807
  const connection = _classPrivateFieldGet2(_host, this)._webSockets.getConnection(connectionId);
749
808
  if (!connection || !this.connectionHasChildTarget(connection)) return null;
750
- _classPrivateFieldGet2(_host, this)._ensureConnectionWrapped(connection);
809
+ ensureConnectionWrapped(connection);
751
810
  connection.setState(state);
752
811
  return this.getForwardedState(connection);
753
812
  }
754
813
  connectionTargetPath(connection) {
755
- _classPrivateFieldGet2(_host, this)._ensureConnectionWrapped(connection);
814
+ ensureConnectionWrapped(connection);
756
815
  const outerUri = _classPrivateFieldGet2(_host, this)._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY);
757
816
  if (typeof outerUri !== "string") return null;
758
817
  return _assertClassBrand(_DynamicAgentsInternal_brand, this, _pathFromOuterUri).call(this, outerUri)?.path ?? null;
@@ -762,7 +821,7 @@ var DynamicAgentsInternal = class extends LifecycleCapability {
762
821
  return a.every((step, index) => step.className === b[index]?.className && step.name === b[index]?.name);
763
822
  }
764
823
  connectionHasChildTarget(connection) {
765
- _classPrivateFieldGet2(_host, this)._ensureConnectionWrapped(connection);
824
+ ensureConnectionWrapped(connection);
766
825
  return typeof _classPrivateFieldGet2(_host, this)._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY) === "string";
767
826
  }
768
827
  connectionTargetsChild(connection) {
@@ -775,21 +834,27 @@ var DynamicAgentsInternal = class extends LifecycleCapability {
775
834
  return parseSubAgentPath(request.url, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 }) !== null;
776
835
  }
777
836
  async forwardWebSocketConnect(connection, request, options) {
778
- const routed = await _assertClassBrand(_DynamicAgentsInternal_brand, this, _resolveConnection).call(this, connection, request, options);
779
- if (!routed) return false;
837
+ const routed = await _assertClassBrand(_DynamicAgentsInternal_brand, this, _resolveConnection).call(this, connection, {
838
+ create: true,
839
+ request,
840
+ gate: options.gate
841
+ });
842
+ if (routed.status !== "ok") return false;
780
843
  await routed.child._cf_handleSubAgentWebSocketConnect(_assertClassBrand(_DynamicAgentsInternal_brand, this, _createConnectionBridge).call(this, connection), routed.meta);
781
844
  return true;
782
845
  }
783
846
  async forwardWebSocketMessage(connection, message, replyBridge) {
784
- const routed = await _assertClassBrand(_DynamicAgentsInternal_brand, this, _resolveConnection).call(this, connection);
785
- if (!routed) return false;
847
+ const routed = await _assertClassBrand(_DynamicAgentsInternal_brand, this, _resolveConnection).call(this, connection, { create: false });
848
+ if (routed.status === "no-match") return false;
849
+ if (routed.status === "dropped") return true;
786
850
  const bridge = _assertClassBrand(_DynamicAgentsInternal_brand, this, _createConnectionBridge).call(this, connection);
787
851
  await routed.child._cf_handleSubAgentWebSocketMessage(message, bridge, routed.meta, replyBridge ?? bridge);
788
852
  return true;
789
853
  }
790
854
  async forwardWebSocketClose(connection, code, reason, wasClean) {
791
- const routed = await _assertClassBrand(_DynamicAgentsInternal_brand, this, _resolveConnection).call(this, connection);
792
- if (!routed) return false;
855
+ const routed = await _assertClassBrand(_DynamicAgentsInternal_brand, this, _resolveConnection).call(this, connection, { create: false });
856
+ if (routed.status === "no-match") return false;
857
+ if (routed.status === "dropped") return true;
793
858
  await routed.child._cf_handleSubAgentWebSocketClose(code, reason, wasClean, _assertClassBrand(_DynamicAgentsInternal_brand, this, _createConnectionBridge).call(this, connection), routed.meta);
794
859
  return true;
795
860
  }
@@ -799,7 +864,7 @@ var DynamicAgentsInternal = class extends LifecycleCapability {
799
864
  const request = new Request(meta.uri ?? "http://placeholder/", { headers: meta.requestHeaders });
800
865
  if (await this.forwardWebSocketConnect(connection, request, { gate: true })) return;
801
866
  if (_classPrivateFieldGet2(_host, this).shouldConnectionBeReadonly(connection, { request })) _classPrivateFieldGet2(_host, this).setConnectionReadonly(connection, true);
802
- if (!_classPrivateFieldGet2(_host, this).shouldSendProtocolMessages(connection, { request })) _classPrivateFieldGet2(_host, this)._setConnectionNoProtocol(connection);
867
+ if (!_classPrivateFieldGet2(_host, this).shouldSendProtocolMessages(connection, { request })) setConnectionProtocolEnabled(connection, false);
803
868
  const childTags = await _classPrivateFieldGet2(_host, this).getConnectionTags(connection, { request });
804
869
  connection.tags = [connection.id, ...childTags.filter((tag) => tag !== connection.id)];
805
870
  this.storeVirtualConnection(connection);
@@ -881,7 +946,7 @@ var DynamicAgentsInternal = class extends LifecycleCapability {
881
946
  removeEventListener() {}
882
947
  };
883
948
  stored.connection = connection;
884
- _classPrivateFieldGet2(_host, this)._ensureConnectionWrapped(connection);
949
+ ensureConnectionWrapped(connection);
885
950
  return connection;
886
951
  }
887
952
  storeVirtualConnection(connection) {
@@ -918,11 +983,11 @@ var DynamicAgentsInternal = class extends LifecycleCapability {
918
983
  if (!_classPrivateFieldGet2(_host, this)._isFacet || _classPrivateFieldGet2(_host, this)._parentPath.length === 0) return;
919
984
  if (this.rootResolvesToSelf()) return;
920
985
  const metas = await (await this.rootAlarmOwner())._cf_subAgentConnectionMetas(_classPrivateFieldGet2(_host, this).selfPath);
921
- for (const meta of metas) _classPrivateFieldGet2(_virtualConnections, this).set(meta.id, { meta });
986
+ for (const meta of metas) if (!_classPrivateFieldGet2(_virtualConnections, this).has(meta.id)) _classPrivateFieldGet2(_virtualConnections, this).set(meta.id, { meta });
987
+ _classPrivateFieldSet2(_virtualConnectionsHydrated, this, true);
922
988
  }
923
989
  getRawConnectionState(connection) {
924
- _classPrivateFieldGet2(_host, this)._ensureConnectionWrapped(connection);
925
- return _classPrivateFieldGet2(_host, this)._rawStateAccessors.get(connection)?.getRaw() ?? null;
990
+ return getConnectionRawState(connection);
926
991
  }
927
992
  getForwardedState(connection) {
928
993
  const raw = this.getRawConnectionState(connection);
@@ -961,6 +1026,7 @@ var DynamicAgentsInternal = class extends LifecycleCapability {
961
1026
  * one RPC method. Stateless — no cached references.
962
1027
  */
963
1028
  async invoke(className, name, method, args) {
1029
+ await _classPrivateFieldGet2(_host, this).__unsafe_ensureInitialized();
964
1030
  const stub = await this.resolve(className, name);
965
1031
  return await this.invokeStubMethod(stub, className, method, args);
966
1032
  }
@@ -971,6 +1037,7 @@ var DynamicAgentsInternal = class extends LifecycleCapability {
971
1037
  * to the next facet using that facet's own `ctx.facets`.
972
1038
  */
973
1039
  async invokePath(path, method, args) {
1040
+ await _classPrivateFieldGet2(_host, this).__unsafe_ensureInitialized();
974
1041
  const [self, next, ...rest] = path;
975
1042
  if (!self) throw new Error(`Sub-agent path invocation requires a non-empty path.`);
976
1043
  const ownClassName = _classPrivateFieldGet2(_host, this).constructor.name;
@@ -1014,6 +1081,46 @@ var DynamicAgentsInternal = class extends LifecycleCapability {
1014
1081
  await _classPrivateFieldGet2(_host, this)._syncHostJobs();
1015
1082
  }
1016
1083
  };
1084
+ /**
1085
+ * Non-creating counterpart to {@link resolve} for WebSocket message/close
1086
+ * forwarding. Returns `null` when the child has no registry row, and never
1087
+ * writes one. The connection's initial connect already went through
1088
+ * {@link resolve} on this hop, so the runtime/export checks there hold.
1089
+ */
1090
+ async function _resolveExisting(className, name) {
1091
+ const row = this.registry.row(className, name);
1092
+ if (!row) return null;
1093
+ const ctx = _classPrivateFieldGet2(_host, this).ctx;
1094
+ const identityName = row.identity_version === "path-v2" && typeof row.identity_name === "string" ? row.identity_name : name;
1095
+ const rootClassName = _classPrivateFieldGet2(_host, this)._parentPath[0]?.className ?? _classPrivateFieldGet2(_host, this).constructor.name;
1096
+ const rootNs = ctx.exports[rootClassName];
1097
+ const stub = ctx.facets.get(`${className}\0${name}`, () => ({
1098
+ class: ctx.exports[className],
1099
+ id: rootNs.idFromName(identityName)
1100
+ }));
1101
+ const childParentPath = _classPrivateFieldGet2(_host, this).selfPath;
1102
+ try {
1103
+ await _classPrivateFieldGet2(_host, this)._runFacetInitInvocation(async () => {
1104
+ await stub._cf_initAsFacet(name, childParentPath, identityName);
1105
+ });
1106
+ } catch (error) {
1107
+ if (!this.registry.row(className, name)) return null;
1108
+ throw error;
1109
+ }
1110
+ return stub;
1111
+ }
1112
+ /**
1113
+ * Close every client socket targeting `path` or a descendant, so a client
1114
+ * attached to a deleted facet gets an explicit close instead of a socket
1115
+ * that goes nowhere (#2003). Only the root owns real sockets.
1116
+ */
1117
+ async function _closeConnectionsForDeletedPath(path, root) {
1118
+ if (!_classPrivateFieldGet2(_host, this)._isFacet) {
1119
+ this.closeConnectionsForPrefix(path, 1001, "Sub-agent deleted");
1120
+ return;
1121
+ }
1122
+ await (root ?? await this.rootAlarmOwner())._cf_closeSubAgentConnectionsForPrefix(path, 1001, "Sub-agent deleted");
1123
+ }
1017
1124
  function _reportConnectionOperationFailure(connectionId, operation, error) {
1018
1125
  console.error("[Agent] Sub-agent connection operation failed:", {
1019
1126
  connectionId,
@@ -1021,8 +1128,31 @@ function _reportConnectionOperationFailure(connectionId, operation, error) {
1021
1128
  error
1022
1129
  });
1023
1130
  }
1131
+ /**
1132
+ * Whether a hydrated mirror proves no root socket targets this facet
1133
+ * outside `without`. Stale mirror entries only cost a root call; a socket
1134
+ * missing from a hydrated mirror has not had its connect forwarded here
1135
+ * yet, so it has not been through `onConnect`. Synchronous so a skipped
1136
+ * broadcast never reorders later operations.
1137
+ */
1138
+ function _broadcastHasNoRecipients(without) {
1139
+ if (!_classPrivateFieldGet2(_virtualConnectionsHydrated, this)) {
1140
+ _assertClassBrand(_DynamicAgentsInternal_brand, this, _hydrateVirtualConnectionsInBackground).call(this);
1141
+ return false;
1142
+ }
1143
+ for (const id of _classPrivateFieldGet2(_virtualConnections, this).keys()) if (!without?.includes(id)) return false;
1144
+ return true;
1145
+ }
1146
+ function _hydrateVirtualConnectionsInBackground() {
1147
+ if (_classPrivateFieldGet2(_virtualConnectionsHydration, this)) return;
1148
+ const hydration = this.hydrateConnectionsFromRoot().catch(() => {}).finally(() => {
1149
+ _classPrivateFieldSet2(_virtualConnectionsHydration, this, void 0);
1150
+ });
1151
+ _classPrivateFieldSet2(_virtualConnectionsHydration, this, hydration);
1152
+ _classPrivateFieldGet2(_host, this).ctx.waitUntil(hydration);
1153
+ }
1024
1154
  function _connectionMetaForPath(connection, ownerPath) {
1025
- _classPrivateFieldGet2(_host, this)._ensureConnectionWrapped(connection);
1155
+ ensureConnectionWrapped(connection);
1026
1156
  const outerUri = _classPrivateFieldGet2(_host, this)._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY);
1027
1157
  if (typeof outerUri !== "string") return null;
1028
1158
  const target = _assertClassBrand(_DynamicAgentsInternal_brand, this, _pathFromOuterUri).call(this, outerUri, ownerPath);
@@ -1071,20 +1201,31 @@ function _createConnectionBridge(connection) {
1071
1201
  return _classPrivateFieldGet2(_host, this)._cf_broadcastToSubAgent(ownerPath, message, without);
1072
1202
  });
1073
1203
  }
1074
- async function _resolveConnection(connection, request, options = { gate: false }) {
1075
- _classPrivateFieldGet2(_host, this)._ensureConnectionWrapped(connection);
1204
+ /**
1205
+ * Resolve a connection's `/sub/{class}/{name}` target.
1206
+ *
1207
+ * `create: true` (the initial connect) may wake or create the child, like
1208
+ * `subAgent()`. `create: false` (message/close on an already-forwarded
1209
+ * connection) only attaches to a child that still has a registry row; a
1210
+ * deleted child resolves to `"dropped"`, which the caller must treat as
1211
+ * consumed so a stale frame can't recreate it (#2003).
1212
+ */
1213
+ async function _resolveConnection(connection, options) {
1214
+ ensureConnectionWrapped(connection);
1076
1215
  const outerUri = _classPrivateFieldGet2(_host, this)._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY);
1077
1216
  const uri = typeof outerUri === "string" ? outerUri : connection.uri;
1078
- if (!uri) return null;
1217
+ if (!uri) return { status: "no-match" };
1218
+ if (_classPrivateFieldGet2(_host, this)._unsafe_getConnectionFlag(connection, "_cf_subAgentDeleted")) return { status: "dropped" };
1079
1219
  const ctx = _classPrivateFieldGet2(_host, this).ctx;
1080
1220
  let match = parseSubAgentPath(uri, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 });
1081
- if (!match) return null;
1221
+ if (!match) return { status: "no-match" };
1082
1222
  if (_classPrivateFieldGet2(_host, this)._ParentClass.name === match.childClass && _classPrivateFieldGet2(_host, this).name === match.childName) {
1083
1223
  const tailUri = new URL(uri);
1084
1224
  tailUri.pathname = match.remainingPath;
1085
1225
  match = parseSubAgentPath(tailUri.toString(), { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 });
1086
- if (!match) return null;
1226
+ if (!match) return { status: "no-match" };
1087
1227
  }
1228
+ const { request } = options;
1088
1229
  let forwardReq = request;
1089
1230
  if (request && options.gate) {
1090
1231
  const decision = await _classPrivateFieldGet2(_host, this).onBeforeSubAgent(request, {
@@ -1092,25 +1233,28 @@ async function _resolveConnection(connection, request, options = { gate: false }
1092
1233
  name: match.childName
1093
1234
  });
1094
1235
  if (decision instanceof Response) {
1095
- connection.close(1008, "Sub-agent connection rejected");
1096
- return null;
1236
+ const { code, reason } = subAgentRejectionClose(decision);
1237
+ connection.close(code, reason);
1238
+ return { status: "dropped" };
1097
1239
  }
1098
1240
  forwardReq = decision instanceof Request ? decision : request;
1099
1241
  }
1100
- const child = await this.resolve(match.childClass, match.childName);
1242
+ const child = options.create ? await this.resolve(match.childClass, match.childName) : await _assertClassBrand(_DynamicAgentsInternal_brand, this, _resolveExisting).call(this, match.childClass, match.childName);
1243
+ if (!child) return { status: "dropped" };
1101
1244
  const childUri = new URL(forwardReq?.url ?? uri);
1102
1245
  childUri.pathname = match.remainingPath;
1103
1246
  const raw = this.getRawConnectionState(connection);
1104
1247
  const rawTags = raw != null && typeof raw === "object" ? raw[CF_SUB_AGENT_TAGS_KEY] : void 0;
1105
1248
  const tags = Array.isArray(rawTags) ? rawTags.filter((tag) => typeof tag === "string") : [...connection.tags];
1106
1249
  return {
1250
+ status: "ok",
1107
1251
  child,
1108
1252
  meta: {
1109
1253
  id: connection.id,
1110
1254
  uri: childUri.toString(),
1111
1255
  tags,
1112
1256
  state: this.getForwardedState(connection),
1113
- requestHeaders: forwardReq ? [...forwardReq.headers] : void 0
1257
+ requestHeaders: forwardReq ? [...forwardReq.headers].filter(([name]) => name !== SUB_AGENT_OUTER_URL_HEADER) : void 0
1114
1258
  }
1115
1259
  };
1116
1260
  }
@@ -1213,12 +1357,6 @@ function sendRpcResponseIfOpen(connection, response) {
1213
1357
  function isRPCRequest(msg) {
1214
1358
  return typeof msg === "object" && msg !== null && "type" in msg && msg.type === "rpc" && "id" in msg && typeof msg.id === "string" && "method" in msg && typeof msg.method === "string" && "args" in msg && Array.isArray(msg.args);
1215
1359
  }
1216
- /**
1217
- * Type guard for state update messages
1218
- */
1219
- function isStateUpdateMessage(msg) {
1220
- return typeof msg === "object" && msg !== null && "type" in msg && msg.type === "cf_agent_state" && "state" in msg;
1221
- }
1222
1360
  const _fiberALS = new AsyncLocalStorage();
1223
1361
  const DEFAULT_KEEP_ALIVE_INTERVAL_MS = 3e4;
1224
1362
  const DEFAULT_AGENT_TOOL_RECOVERY_TIMEOUT_MS = 2e3;
@@ -1247,57 +1385,15 @@ const DETACHED_NOTIFY_CALLBACK = "_cfDetachedNotifyFinish";
1247
1385
  /**
1248
1386
  * Schema version for the Agent's internal SQLite tables.
1249
1387
  * Bump this when adding new tables, columns, or migrations.
1250
- * The constructor stores this as a row in cf_agents_state and checks it
1251
- * on wake to skip DDL on established DOs.
1388
+ * The constructor stores this under a namespaced KV key (the same convention
1389
+ * every capability uses for its own schema version) and checks it on wake to
1390
+ * skip DDL on established DOs.
1252
1391
  */
1253
- const CURRENT_SCHEMA_VERSION = 11;
1254
- const SCHEMA_VERSION_ROW_ID = "cf_schema_version";
1255
- const STATE_ROW_ID = "cf_state_row_id";
1256
- const STATE_WAS_CHANGED = "cf_state_was_changed";
1392
+ const CURRENT_SCHEMA_VERSION = 14;
1393
+ const SCHEMA_VERSION_KEY = "cf_agents:schema_version";
1394
+ const LEGACY_SCHEMA_VERSION_ROW_ID = "cf_schema_version";
1257
1395
  const DEFAULT_STATE = {};
1258
- /**
1259
- * Internal key used to store the readonly flag in connection state.
1260
- * Prefixed with _cf_ to avoid collision with user state keys.
1261
- */
1262
- const CF_READONLY_KEY = "_cf_readonly";
1263
- /**
1264
- * Internal key used to store the no-protocol flag in connection state.
1265
- * When set, protocol messages (identity, state sync, MCP servers) are not
1266
- * sent to this connection — neither on connect nor via broadcasts.
1267
- */
1268
- const CF_NO_PROTOCOL_KEY = "_cf_no_protocol";
1269
- /**
1270
- * The set of all internal keys stored in connection state that must be
1271
- * hidden from user code and preserved across setState calls.
1272
- */
1273
- const CF_INTERNAL_KEYS = /* @__PURE__ */ new Set([
1274
- CF_READONLY_KEY,
1275
- CF_NO_PROTOCOL_KEY,
1276
- "_cf_voiceInCall",
1277
- CF_SUB_AGENT_OUTER_URL_KEY,
1278
- CF_SUB_AGENT_TAGS_KEY
1279
- ]);
1280
- /** Check if a raw connection state object contains any internal keys. */
1281
- function rawHasInternalKeys(raw) {
1282
- for (const key of Object.keys(raw)) if (CF_INTERNAL_KEYS.has(key)) return true;
1283
- return false;
1284
- }
1285
- /** Return a copy of `raw` with all internal keys removed, or null if no user keys remain. */
1286
- function stripInternalKeys(raw) {
1287
- const result = {};
1288
- let hasUserKeys = false;
1289
- for (const key of Object.keys(raw)) if (!CF_INTERNAL_KEYS.has(key)) {
1290
- result[key] = raw[key];
1291
- hasUserKeys = true;
1292
- }
1293
- return hasUserKeys ? result : null;
1294
- }
1295
- /** Return a copy containing only the internal keys present in `raw`. */
1296
- function extractInternalFlags(raw) {
1297
- const result = {};
1298
- for (const key of Object.keys(raw)) if (CF_INTERNAL_KEYS.has(key)) result[key] = raw[key];
1299
- return result;
1300
- }
1396
+ registerInternalConnectionKeys("_cf_voiceInCall", CF_SUB_AGENT_OUTER_URL_KEY, CF_SUB_AGENT_TAGS_KEY);
1301
1397
  /** Max length for error strings broadcast to clients. */
1302
1398
  const MAX_ERROR_STRING_LENGTH = 500;
1303
1399
  /**
@@ -1368,9 +1464,9 @@ const DEFAULT_AGENT_STATIC_OPTIONS = {
1368
1464
  /**
1369
1465
  * Optional hard wall-clock ceiling (ms) on a single agent-tool re-attach
1370
1466
  * (#1630). Caps the total wait even as the no-progress budget re-arms across
1371
- * stream-closes. Defaults to `Infinity` (no implicit cap), mirroring
1372
- * chat-recovery's `maxRecoveryWork` (#1672): a healthy, still-advancing child
1373
- * is followed for as long as it makes progress — a hung child is bounded by
1467
+ * stream-closes. Defaults to `Infinity` (no implicit cap, #1672): a healthy,
1468
+ * still-advancing child is followed for as long as it makes progress — a
1469
+ * hung child is bounded by
1374
1470
  * the no-progress budget, and a content-runaway by the child's own
1375
1471
  * `maxRecoveryWork` / `shouldKeepRecovering`. Set a finite value to impose a
1376
1472
  * wall-clock cap (which also tears the child down on `window-exceeded`).
@@ -1388,36 +1484,23 @@ const DEFAULT_AGENT_STATIC_OPTIONS = {
1388
1484
  */
1389
1485
  maxAlarmMemoryLimitStrikes: 3
1390
1486
  };
1391
- /**
1392
- * Parse the raw `retry_options` TEXT column from a SQLite row into a
1393
- * typed `RetryOptions` object, or `undefined` if not set.
1394
- */
1395
- function parseRetryOptions(row) {
1396
- const raw = row.retry_options;
1397
- if (typeof raw !== "string") return void 0;
1398
- return JSON.parse(raw);
1399
- }
1400
- /**
1401
- * Resolve per-task retry options against class-level defaults and call
1402
- * `tryN`. This is the retry-execution path for queue flush; Scheduler owns
1403
- * its own copy for schedule callbacks.
1404
- */
1405
- function resolveRetryConfig(taskRetry, defaults) {
1406
- return {
1407
- maxAttempts: taskRetry?.maxAttempts ?? defaults.maxAttempts,
1408
- baseDelayMs: taskRetry?.baseDelayMs ?? defaults.baseDelayMs,
1409
- maxDelayMs: taskRetry?.maxDelayMs ?? defaults.maxDelayMs
1410
- };
1411
- }
1412
1487
  /** Compatibility alias for the lifecycle-owned current Agent accessor. */
1413
1488
  const getCurrentAgent = getCurrentAgent$1;
1489
+ const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor;
1490
+ /** Functions produced by {@link withAgentContext}, so they are wrapped once. */
1491
+ const agentContextWrappers = /* @__PURE__ */ new WeakSet();
1414
1492
  /**
1415
1493
  * Restore Agent context when a public method is entered outside a Lifecycle
1416
1494
  * hook, notably through native Durable Object RPC or cross-Agent re-entry.
1417
1495
  * Lifecycle already owns context for its capability and semantic user hooks.
1496
+ *
1497
+ * Native RPC bypasses the lifecycle's runtime handlers, so an `async` method
1498
+ * entered from outside this Agent's context first starts the lifecycle when
1499
+ * the call is the one that wakes a cold instance. Synchronous methods are
1500
+ * never deferred, so they keep their synchronous return type.
1418
1501
  */
1419
1502
  function withAgentContext(method) {
1420
- return function(...args) {
1503
+ const enter = function(...args) {
1421
1504
  const { agent } = getCurrentAgent();
1422
1505
  if (agent === this) return method.apply(this, args);
1423
1506
  return runInInvocation({
@@ -1429,11 +1512,21 @@ function withAgentContext(method) {
1429
1512
  return method.apply(this, args);
1430
1513
  });
1431
1514
  };
1515
+ if (!(method instanceof AsyncFunction)) {
1516
+ agentContextWrappers.add(enter);
1517
+ return enter;
1518
+ }
1519
+ const enterStarted = async function(...args) {
1520
+ if (getCurrentAgent().agent !== this && !this.lifecycle.isStarted()) await this.lifecycle.start();
1521
+ return enter.apply(this, args);
1522
+ };
1523
+ agentContextWrappers.add(enterStarted);
1524
+ return enterStarted;
1432
1525
  }
1433
1526
  /**
1434
1527
  * Base class for creating Agent implementations
1435
1528
  * @template Env Environment type containing bindings
1436
- * @template State State type to store within the Agent
1529
+ * @template TState State type to store within the Agent
1437
1530
  */
1438
1531
  var Agent = class Agent extends DurableObject {
1439
1532
  /** Run user initialization after lifecycle components have started. */
@@ -1505,31 +1598,16 @@ var Agent = class Agent extends DurableObject {
1505
1598
  return this.ctx.id.toString();
1506
1599
  }
1507
1600
  /**
1508
- * Current state of the Agent
1601
+ * Current state of the Agent.
1602
+ *
1603
+ * Delegates to the State capability, which owns lazy load and the
1604
+ * in-memory cache; Agent seeds `initialState` on first access.
1509
1605
  */
1510
1606
  get state() {
1511
- if (this._state !== DEFAULT_STATE) return this._state;
1512
- const result = this.sql`
1513
- SELECT state FROM cf_agents_state WHERE id = ${STATE_ROW_ID}
1514
- `;
1515
- if (result.length > 0) {
1516
- const state = result[0].state;
1517
- try {
1518
- this._state = JSON.parse(state);
1519
- } catch (e) {
1520
- console.error("Failed to parse stored state, falling back to initialState:", e);
1521
- if (this.initialState !== DEFAULT_STATE) {
1522
- this._state = this.initialState;
1523
- this._setStateInternal(this.initialState);
1524
- } else {
1525
- this.sql`DELETE FROM cf_agents_state WHERE id = ${STATE_ROW_ID}`;
1526
- return;
1527
- }
1528
- }
1529
- return this._state;
1530
- }
1531
- if (this.initialState === DEFAULT_STATE) return;
1532
- this._setStateInternal(this.initialState);
1607
+ const stored = this._state.get();
1608
+ if (stored !== void 0) return stored;
1609
+ if (this.initialState === DEFAULT_STATE) return void 0;
1610
+ this._state.set(this.initialState, "server");
1533
1611
  return this.initialState;
1534
1612
  }
1535
1613
  get _resolvedOptions() {
@@ -1615,26 +1693,9 @@ var Agent = class Agent extends DurableObject {
1615
1693
  * local dev and the constructor only runs once per DO instance).
1616
1694
  */
1617
1695
  _ensureSchema() {
1618
- this.sql`
1619
- CREATE TABLE IF NOT EXISTS cf_agents_state (
1620
- id TEXT PRIMARY KEY NOT NULL,
1621
- state TEXT
1622
- )
1623
- `;
1624
- const versionRow = this.sql`
1625
- SELECT state FROM cf_agents_state WHERE id = ${SCHEMA_VERSION_ROW_ID}
1626
- `;
1627
- const schemaVersion = versionRow.length > 0 ? Number(versionRow[0].state) : 0;
1696
+ const schemaVersion = this._readSchemaVersion();
1628
1697
  if (schemaVersion < CURRENT_SCHEMA_VERSION) {
1629
1698
  ensureMcpServerTable(this.ctx.storage);
1630
- this.sql`
1631
- CREATE TABLE IF NOT EXISTS cf_agents_queues (
1632
- id TEXT PRIMARY KEY NOT NULL,
1633
- payload TEXT,
1634
- callback TEXT,
1635
- created_at INTEGER DEFAULT (unixepoch())
1636
- )
1637
- `;
1638
1699
  const addColumnIfNotExists = (sql) => {
1639
1700
  try {
1640
1701
  this.ctx.storage.sql.exec(sql);
@@ -1642,7 +1703,6 @@ var Agent = class Agent extends DurableObject {
1642
1703
  if (!(error instanceof Error ? error.message : String(error)).toLowerCase().includes("duplicate column")) throw error;
1643
1704
  }
1644
1705
  };
1645
- addColumnIfNotExists("ALTER TABLE cf_agents_queues ADD COLUMN retry_options TEXT");
1646
1706
  this.sql`
1647
1707
  CREATE TABLE IF NOT EXISTS cf_agents_workflows (
1648
1708
  id TEXT PRIMARY KEY NOT NULL,
@@ -1667,13 +1727,15 @@ var Agent = class Agent extends DurableObject {
1667
1727
  this.sql`
1668
1728
  CREATE INDEX IF NOT EXISTS idx_workflows_name ON cf_agents_workflows(workflow_name)
1669
1729
  `;
1670
- this.ctx.storage.sql.exec("DELETE FROM cf_agents_state WHERE id = ?", STATE_WAS_CHANGED);
1671
1730
  this.sql`
1672
1731
  CREATE TABLE IF NOT EXISTS cf_agents_runs (
1673
1732
  id TEXT PRIMARY KEY NOT NULL,
1674
1733
  name TEXT NOT NULL,
1675
1734
  snapshot TEXT,
1676
- created_at INTEGER NOT NULL
1735
+ created_at INTEGER NOT NULL,
1736
+ completed_at INTEGER,
1737
+ outcome TEXT,
1738
+ error_message TEXT
1677
1739
  )
1678
1740
  `;
1679
1741
  this.sql`
@@ -1752,10 +1814,11 @@ var Agent = class Agent extends DurableObject {
1752
1814
  addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_no_progress_budget_ms INTEGER");
1753
1815
  addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN last_progress_at INTEGER");
1754
1816
  addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_on_milestones TEXT");
1755
- this.sql`
1756
- INSERT OR REPLACE INTO cf_agents_state (id, state)
1757
- VALUES (${SCHEMA_VERSION_ROW_ID}, ${String(CURRENT_SCHEMA_VERSION)})
1758
- `;
1817
+ addColumnIfNotExists("ALTER TABLE cf_agents_runs ADD COLUMN completed_at INTEGER");
1818
+ addColumnIfNotExists("ALTER TABLE cf_agents_runs ADD COLUMN outcome TEXT");
1819
+ addColumnIfNotExists("ALTER TABLE cf_agents_runs ADD COLUMN error_message TEXT");
1820
+ addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN event_delivery TEXT");
1821
+ this.ctx.storage.kv.put(SCHEMA_VERSION_KEY, CURRENT_SCHEMA_VERSION);
1759
1822
  }
1760
1823
  this._schemaInitialization = {
1761
1824
  previousVersion: schemaVersion,
@@ -1763,9 +1826,31 @@ var Agent = class Agent extends DurableObject {
1763
1826
  migrated: schemaVersion < CURRENT_SCHEMA_VERSION
1764
1827
  };
1765
1828
  }
1829
+ /**
1830
+ * Read the Agent's schema version from its KV key. A DO created before the
1831
+ * State capability owned `cf_agents_state` has the version as a row in that
1832
+ * table instead: read it once, move it to the key, and delete the row so the
1833
+ * table is left with a single owner. Synchronous (`storage.kv`) because the
1834
+ * constructor gates DDL on it.
1835
+ */
1836
+ _readSchemaVersion() {
1837
+ const stored = this.ctx.storage.kv.get(SCHEMA_VERSION_KEY);
1838
+ if (stored !== void 0) return stored;
1839
+ if (!(this.ctx.storage.sql.exec("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'cf_agents_state'").toArray().length > 0)) return 0;
1840
+ const rows = this.ctx.storage.sql.exec("SELECT state FROM cf_agents_state WHERE id = ?", LEGACY_SCHEMA_VERSION_ROW_ID).toArray();
1841
+ if (rows.length === 0) return 0;
1842
+ const version = Number(rows[0].state) || 0;
1843
+ this.ctx.storage.kv.put(SCHEMA_VERSION_KEY, version);
1844
+ this.ctx.storage.sql.exec("DELETE FROM cf_agents_state WHERE id = ?", LEGACY_SCHEMA_VERSION_ROW_ID);
1845
+ return version;
1846
+ }
1766
1847
  constructor(ctx, env) {
1767
1848
  super(ctx, env);
1768
1849
  this.lifecycle = Lifecycle.install(this, { maxAlarmMemoryLimitStrikes: this._resolvedOptions.maxAlarmMemoryLimitStrikes });
1850
+ this._state = new State({
1851
+ validateStateChange: (nextState, source) => this.validateStateChange(nextState, source),
1852
+ onChanged: (nextState, source) => this._handleStateChanged(nextState, source)
1853
+ });
1769
1854
  this._webSockets = new WebSockets({
1770
1855
  handlers: {
1771
1856
  onConnect: (connection, ctx) => this.onConnect(connection, ctx),
@@ -1773,13 +1858,12 @@ var Agent = class Agent extends DurableObject {
1773
1858
  onClose: (connection, code, reason, wasClean) => this.onClose(connection, code, reason, wasClean),
1774
1859
  onError: (connection, error) => this.onError(connection, error)
1775
1860
  },
1776
- callables: callablesFromDecorated(this),
1861
+ protocol: false,
1862
+ state: this._state,
1777
1863
  getConnectionTags: (connection, ctx) => this.getConnectionTags(connection, ctx)
1778
1864
  });
1779
- this._state = DEFAULT_STATE;
1780
1865
  this._disposables = new DisposableStore();
1781
1866
  this._destroyed = false;
1782
- this._rawStateAccessors = /* @__PURE__ */ new WeakMap();
1783
1867
  this._persistenceHookMode = "none";
1784
1868
  this._isFacet = false;
1785
1869
  this._protocolBroadcastExcludeIds = /* @__PURE__ */ new Set();
@@ -1797,7 +1881,6 @@ var Agent = class Agent extends DurableObject {
1797
1881
  this._ParentClass = Object.getPrototypeOf(this).constructor;
1798
1882
  this.initialState = DEFAULT_STATE;
1799
1883
  this.observability = genericObservability;
1800
- this._flushingQueue = false;
1801
1884
  this.maxConcurrentAgentTools = Infinity;
1802
1885
  const routeHost = this;
1803
1886
  setLifecycleRouteTransport(this.lifecycle, {
@@ -1832,6 +1915,20 @@ var Agent = class Agent extends DurableObject {
1832
1915
  if (typeof method !== "function") return void 0;
1833
1916
  return (payload, schedule) => method.call(this, payload, schedule);
1834
1917
  });
1918
+ this._queue = new Queue({
1919
+ retry: this._resolvedOptions.retry,
1920
+ onError: (error) => runInInvocation({
1921
+ agent: this,
1922
+ connection: void 0,
1923
+ request: void 0,
1924
+ email: void 0
1925
+ }, () => this.onError(error))
1926
+ });
1927
+ setQueueCallbackResolver(this._queue, (name) => {
1928
+ const method = this[name];
1929
+ if (typeof method !== "function") return void 0;
1930
+ return (payload, item) => method.call(this, payload, item);
1931
+ });
1835
1932
  this.tasks = new Tasks({ onError: (error) => this.onError(error) });
1836
1933
  setTaskRoutedMemoryLimitHandler(this.tasks, (context) => {
1837
1934
  return this.onAlarmMemoryLimit?.call(this, context);
@@ -1857,7 +1954,7 @@ var Agent = class Agent extends DurableObject {
1857
1954
  createAuthProvider: (callbackUrl) => this.createMcpOAuthProvider(callbackUrl)
1858
1955
  });
1859
1956
  });
1860
- this.lifecycle.use(this.scheduler).use(this.mcp).use(this._webSockets, { fallback: true }).use(this.tasks).use(this._dynamicAgents);
1957
+ this.lifecycle.use(this.scheduler).use(this._queue).use(this.mcp).use(this._state).use(this._webSockets).use(this.tasks).use(this._dynamicAgents);
1861
1958
  let mcpBroadcastReady = false;
1862
1959
  this._disposables.add(this.mcp.onServerStateChanged(() => {
1863
1960
  if (mcpBroadcastReady) this.broadcastMcpServers();
@@ -1907,7 +2004,7 @@ var Agent = class Agent extends DurableObject {
1907
2004
  this.onMessage = async (connection, message) => {
1908
2005
  const replyBridge = dynamicAgentRpcReplyContext.getStore()?.bridge;
1909
2006
  if (await __DO_NOT_USE_WILL_BREAK__agentContext.exit(() => this._cf_forwardSubAgentWebSocketMessage(connection, message, replyBridge))) return;
1910
- this._ensureConnectionWrapped(connection);
2007
+ ensureConnectionWrapped(connection);
1911
2008
  return runInInvocation({
1912
2009
  agent: this,
1913
2010
  connection,
@@ -1921,25 +2018,7 @@ var Agent = class Agent extends DurableObject {
1921
2018
  } catch (_e) {
1922
2019
  return this._tryCatch(() => _onMessage(connection, message));
1923
2020
  }
1924
- if (isStateUpdateMessage(parsed)) {
1925
- if (this.isConnectionReadonly(connection)) {
1926
- connection.send(JSON.stringify({
1927
- type: "cf_agent_state_error",
1928
- error: "Connection is readonly"
1929
- }));
1930
- return;
1931
- }
1932
- try {
1933
- this._setStateInternal(parsed.state, connection);
1934
- } catch (e) {
1935
- console.error("[Agent] State update rejected:", e);
1936
- connection.send(JSON.stringify({
1937
- type: "cf_agent_state_error",
1938
- error: "State update rejected"
1939
- }));
1940
- }
1941
- return;
1942
- }
2021
+ if (this._webSockets.applyStateFrame(connection, parsed)) return;
1943
2022
  if (isRPCRequest(parsed)) {
1944
2023
  try {
1945
2024
  const { id, method, args } = parsed;
@@ -2003,7 +2082,7 @@ var Agent = class Agent extends DurableObject {
2003
2082
  };
2004
2083
  const _onConnect = this.onConnect.bind(this);
2005
2084
  this.onConnect = async (connection, ctx) => {
2006
- this._ensureConnectionWrapped(connection);
2085
+ ensureConnectionWrapped(connection);
2007
2086
  const subAgentOuterUrl = ctx.request.headers.get(SUB_AGENT_OUTER_URL_HEADER);
2008
2087
  if (subAgentOuterUrl) this._unsafe_setConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY, subAgentOuterUrl);
2009
2088
  if (await __DO_NOT_USE_WILL_BREAK__agentContext.exit(() => this._cf_forwardSubAgentWebSocketConnect(connection, ctx.request, { gate: false }))) return;
@@ -2015,6 +2094,14 @@ var Agent = class Agent extends DurableObject {
2015
2094
  }, async () => {
2016
2095
  if (this.shouldConnectionBeReadonly(connection, ctx)) this.setConnectionReadonly(connection, true);
2017
2096
  if (this.shouldSendProtocolMessages(connection, ctx)) {
2097
+ const wasExcludedFromStateInitBroadcast = this._protocolBroadcastExcludeIds.has(connection.id);
2098
+ let currentState;
2099
+ this._protocolBroadcastExcludeIds.add(connection.id);
2100
+ try {
2101
+ currentState = this.state;
2102
+ } finally {
2103
+ if (!wasExcludedFromStateInitBroadcast) this._protocolBroadcastExcludeIds.delete(connection.id);
2104
+ }
2018
2105
  if (this._resolvedOptions.sendIdentityOnConnect) {
2019
2106
  const ctor = this.constructor;
2020
2107
  if (ctor.options?.sendIdentityOnConnect === void 0 && !_sendIdentityWarnedClasses.has(ctor) && !this._isFacet) {
@@ -2023,29 +2110,16 @@ var Agent = class Agent extends DurableObject {
2023
2110
  console.warn(`[Agent] ${ctor.name}: sending instance name "${this.name}" to clients via sendIdentityOnConnect (the name is not visible in the URL with custom routing). If this name is sensitive, add \`static options = { sendIdentityOnConnect: false }\` to opt out. Set it to true to silence this message.`);
2024
2111
  }
2025
2112
  }
2026
- connection.send(JSON.stringify({
2113
+ this._webSockets.sendConnectFrames(connection, {
2027
2114
  name: this.name,
2028
- agent: camelCaseToKebabCase(this._ParentClass.name),
2029
- type: "cf_agent_identity"
2030
- }));
2031
- }
2032
- const wasExcludedFromStateInitBroadcast = this._protocolBroadcastExcludeIds.has(connection.id);
2033
- let currentState;
2034
- this._protocolBroadcastExcludeIds.add(connection.id);
2035
- try {
2036
- currentState = this.state;
2037
- } finally {
2038
- if (!wasExcludedFromStateInitBroadcast) this._protocolBroadcastExcludeIds.delete(connection.id);
2039
- }
2040
- if (currentState !== void 0) connection.send(JSON.stringify({
2041
- state: currentState,
2042
- type: "cf_agent_state"
2043
- }));
2115
+ agent: camelCaseToKebabCase(this._ParentClass.name)
2116
+ });
2117
+ } else if (currentState !== void 0) this._webSockets.sendState(connection);
2044
2118
  connection.send(JSON.stringify({
2045
2119
  mcp: this.getMcpServers(),
2046
2120
  type: "cf_agent_mcp_servers"
2047
2121
  }));
2048
- } else this._setConnectionNoProtocol(connection);
2122
+ } else this._webSockets.setProtocolEnabled(connection, false);
2049
2123
  this._emit("connect", { connectionId: connection.id });
2050
2124
  await this._replayAgentToolRuns(connection);
2051
2125
  return this._tryCatch(() => _onConnect(connection, ctx));
@@ -2142,13 +2216,14 @@ var Agent = class Agent extends DurableObject {
2142
2216
  for (const conn of this.getConnections()) if (!this.isConnectionProtocolEnabled(conn)) exclude.push(conn.id);
2143
2217
  this.broadcast(msg, exclude);
2144
2218
  }
2145
- _setStateInternal(nextState, source = "server") {
2146
- this.validateStateChange(nextState, source);
2147
- this._state = nextState;
2148
- this.sql`
2149
- INSERT OR REPLACE INTO cf_agents_state (id, state)
2150
- VALUES (${STATE_ROW_ID}, ${JSON.stringify(nextState)})
2151
- `;
2219
+ /**
2220
+ * React to a persisted state change from the State capability.
2221
+ *
2222
+ * Reproduces the pre-migration steps 3-4: broadcast the new state to
2223
+ * protocol-enabled connections (excluding the originating connection) and
2224
+ * run the notification hook off the invocation tail.
2225
+ */
2226
+ _handleStateChanged(nextState, source) {
2152
2227
  this._broadcastProtocol(JSON.stringify({
2153
2228
  state: nextState,
2154
2229
  type: "cf_agent_state"
@@ -2180,69 +2255,7 @@ var Agent = class Agent extends DurableObject {
2180
2255
  setState(state) {
2181
2256
  const store = __DO_NOT_USE_WILL_BREAK__agentContext.getStore();
2182
2257
  if (store?.connection && this.isConnectionReadonly(store.connection)) throw new Error("Connection is readonly");
2183
- this._setStateInternal(state, "server");
2184
- }
2185
- /**
2186
- * Wraps connection.state and connection.setState so that internal
2187
- * _cf_-prefixed flags (readonly, no-protocol) are hidden from user code
2188
- * and cannot be accidentally overwritten.
2189
- *
2190
- * Idempotent — safe to call multiple times on the same connection.
2191
- * After hibernation, the _rawStateAccessors WeakMap is empty but the
2192
- * connection's state getter still reads from the persisted WebSocket
2193
- * attachment. Calling this method re-captures the raw getter so that
2194
- * predicate methods (isConnectionReadonly, isConnectionProtocolEnabled)
2195
- * work correctly post-hibernation.
2196
- */
2197
- _ensureConnectionWrapped(connection) {
2198
- if (this._rawStateAccessors.has(connection)) return;
2199
- const descriptor = Object.getOwnPropertyDescriptor(connection, "state");
2200
- let getRaw;
2201
- let setRaw;
2202
- if (descriptor?.get) {
2203
- getRaw = descriptor.get.bind(connection);
2204
- setRaw = connection.setState.bind(connection);
2205
- } else {
2206
- let rawState = connection.state ?? null;
2207
- getRaw = () => rawState;
2208
- setRaw = (state) => {
2209
- rawState = state;
2210
- return rawState;
2211
- };
2212
- }
2213
- this._rawStateAccessors.set(connection, {
2214
- getRaw,
2215
- setRaw
2216
- });
2217
- Object.defineProperty(connection, "state", {
2218
- configurable: true,
2219
- enumerable: true,
2220
- get() {
2221
- const raw = getRaw();
2222
- if (raw != null && typeof raw === "object" && rawHasInternalKeys(raw)) return stripInternalKeys(raw);
2223
- return raw;
2224
- }
2225
- });
2226
- Object.defineProperty(connection, "setState", {
2227
- configurable: true,
2228
- writable: true,
2229
- value(stateOrFn) {
2230
- const raw = getRaw();
2231
- const flags = raw != null && typeof raw === "object" ? extractInternalFlags(raw) : {};
2232
- const hasFlags = Object.keys(flags).length > 0;
2233
- let newUserState;
2234
- if (typeof stateOrFn === "function") newUserState = stateOrFn(hasFlags ? stripInternalKeys(raw) : raw);
2235
- else newUserState = stateOrFn;
2236
- if (hasFlags) {
2237
- if (newUserState != null && typeof newUserState === "object") return setRaw({
2238
- ...newUserState,
2239
- ...flags
2240
- });
2241
- return setRaw(flags);
2242
- }
2243
- return setRaw(newUserState);
2244
- }
2245
- });
2258
+ this._state.set(state, "server");
2246
2259
  }
2247
2260
  /**
2248
2261
  * Mark a connection as readonly or readwrite
@@ -2250,17 +2263,7 @@ var Agent = class Agent extends DurableObject {
2250
2263
  * @param readonly Whether the connection should be readonly (default: true)
2251
2264
  */
2252
2265
  setConnectionReadonly(connection, readonly = true) {
2253
- this._ensureConnectionWrapped(connection);
2254
- const accessors = this._rawStateAccessors.get(connection);
2255
- const raw = accessors.getRaw() ?? {};
2256
- if (readonly) accessors.setRaw({
2257
- ...raw,
2258
- [CF_READONLY_KEY]: true
2259
- });
2260
- else {
2261
- const { [CF_READONLY_KEY]: _, ...rest } = raw;
2262
- accessors.setRaw(Object.keys(rest).length > 0 ? rest : null);
2263
- }
2266
+ setConnectionReadonly(connection, readonly);
2264
2267
  }
2265
2268
  /**
2266
2269
  * Check if a connection is marked as readonly.
@@ -2271,8 +2274,7 @@ var Agent = class Agent extends DurableObject {
2271
2274
  * @returns True if the connection is readonly
2272
2275
  */
2273
2276
  isConnectionReadonly(connection) {
2274
- this._ensureConnectionWrapped(connection);
2275
- return !!this._rawStateAccessors.get(connection).getRaw()?.[CF_READONLY_KEY];
2277
+ return isConnectionReadonly(connection);
2276
2278
  }
2277
2279
  /**
2278
2280
  * ⚠️ INTERNAL — DO NOT USE IN APPLICATION CODE. ⚠️
@@ -2287,30 +2289,20 @@ var Agent = class Agent extends DurableObject {
2287
2289
  * @internal
2288
2290
  */
2289
2291
  _unsafe_getConnectionFlag(connection, key) {
2290
- this._ensureConnectionWrapped(connection);
2291
- return this._rawStateAccessors.get(connection).getRaw()?.[key];
2292
+ return getConnectionFlag(connection, key);
2292
2293
  }
2293
2294
  /**
2294
2295
  * ⚠️ INTERNAL — DO NOT USE IN APPLICATION CODE. ⚠️
2295
2296
  *
2296
2297
  * Write an internal `_cf_`-prefixed flag to the raw connection state,
2297
2298
  * bypassing the user-facing state wrapper. The key must be registered
2298
- * in `CF_INTERNAL_KEYS` so it is preserved across user `setState` calls
2299
- * and hidden from `connection.state`.
2299
+ * with `registerInternalConnectionKeys` so it is preserved across user
2300
+ * `setState` calls and hidden from `connection.state`.
2300
2301
  *
2301
2302
  * @internal
2302
2303
  */
2303
2304
  _unsafe_setConnectionFlag(connection, key, value) {
2304
- this._ensureConnectionWrapped(connection);
2305
- const accessors = this._rawStateAccessors.get(connection);
2306
- const raw = accessors.getRaw() ?? {};
2307
- if (value === void 0) {
2308
- const { [key]: _, ...rest } = raw;
2309
- accessors.setRaw(Object.keys(rest).length > 0 ? rest : null);
2310
- } else accessors.setRaw({
2311
- ...raw,
2312
- [key]: value
2313
- });
2305
+ setConnectionFlag(connection, key, value);
2314
2306
  }
2315
2307
  /**
2316
2308
  * Override this method to determine if a connection should be readonly on connect
@@ -2351,21 +2343,7 @@ var Agent = class Agent extends DurableObject {
2351
2343
  * @returns True if the connection receives protocol messages
2352
2344
  */
2353
2345
  isConnectionProtocolEnabled(connection) {
2354
- this._ensureConnectionWrapped(connection);
2355
- return !this._rawStateAccessors.get(connection).getRaw()?.[CF_NO_PROTOCOL_KEY];
2356
- }
2357
- /**
2358
- * Mark a connection as having protocol messages disabled.
2359
- * Called internally when shouldSendProtocolMessages returns false.
2360
- */
2361
- _setConnectionNoProtocol(connection) {
2362
- this._ensureConnectionWrapped(connection);
2363
- const accessors = this._rawStateAccessors.get(connection);
2364
- const raw = accessors.getRaw() ?? {};
2365
- accessors.setRaw({
2366
- ...raw,
2367
- [CF_NO_PROTOCOL_KEY]: true
2368
- });
2346
+ return isConnectionProtocolEnabled(connection);
2369
2347
  }
2370
2348
  /**
2371
2349
  * Called before the Agent's state is persisted and broadcast.
@@ -2553,19 +2531,21 @@ var Agent = class Agent extends DurableObject {
2553
2531
  proto = Object.getPrototypeOf(proto);
2554
2532
  }
2555
2533
  }
2534
+ const seen = /* @__PURE__ */ new Set();
2556
2535
  let proto = Object.getPrototypeOf(this);
2557
- let depth = 0;
2558
- while (proto && proto !== Object.prototype && depth < 10) {
2536
+ while (proto && proto !== Agent.prototype && proto !== Object.prototype) {
2559
2537
  const methodNames = Object.getOwnPropertyNames(proto);
2560
2538
  for (const methodName of methodNames) {
2539
+ if (seen.has(methodName)) continue;
2540
+ seen.add(methodName);
2561
2541
  const descriptor = Object.getOwnPropertyDescriptor(proto, methodName);
2562
- if (baseMethods.has(methodName) || methodName.startsWith("_") || !descriptor || !!descriptor.get || typeof descriptor.value !== "function") continue;
2563
- const wrappedFunction = withAgentContext(this[methodName]);
2564
- if (this._isCallable(methodName)) copyCallableMetadata(this[methodName], wrappedFunction);
2542
+ if (baseMethods.has(methodName) || methodName.startsWith("_") || !descriptor || !!descriptor.get || typeof descriptor.value !== "function" || agentContextWrappers.has(descriptor.value)) continue;
2543
+ const method = descriptor.value;
2544
+ const wrappedFunction = withAgentContext(method);
2545
+ if (this._isCallable(methodName)) copyCallableMetadata(method, wrappedFunction);
2565
2546
  this.constructor.prototype[methodName] = wrappedFunction;
2566
2547
  }
2567
2548
  proto = Object.getPrototypeOf(proto);
2568
- depth++;
2569
2549
  }
2570
2550
  }
2571
2551
  onError(connectionOrError, error) {
@@ -2610,111 +2590,42 @@ var Agent = class Agent extends DurableObject {
2610
2590
  });
2611
2591
  }
2612
2592
  /**
2613
- * Queue a task to be executed in the future
2593
+ * Queue a task to run in the background.
2594
+ *
2595
+ * The item is durable: it runs from the Lifecycle alarm event loop after
2596
+ * this call returns, in push order, one at a time, with retries per
2597
+ * `options.retry`, and survives the Durable Object leaving memory.
2614
2598
  * @param callback Name of the method to call
2615
2599
  * @param payload Payload to pass to the callback
2616
2600
  * @param options Options for the queued task
2617
2601
  * @param options.retry Retry options for the callback execution
2602
+ * @param options.id Stable id; a push with an existing id replaces that item
2618
2603
  * @returns The ID of the queued task
2619
2604
  */
2620
2605
  async queue(callback, payload, options) {
2621
- const id = nanoid(9);
2622
2606
  if (typeof callback !== "string") throw new Error("Callback must be a string");
2623
2607
  if (typeof this[callback] !== "function") throw new Error(`this.${callback} is not a function`);
2624
- if (options?.retry) validateRetryOptions(options.retry, this._resolvedOptions.retry);
2625
- const retryJson = options?.retry ? JSON.stringify(options.retry) : null;
2626
- this.sql`
2627
- INSERT OR REPLACE INTO cf_agents_queues (id, payload, callback, retry_options)
2628
- VALUES (${id}, ${JSON.stringify(payload)}, ${callback}, ${retryJson})
2629
- `;
2630
- this._emit("queue:create", {
2631
- callback,
2632
- id
2633
- });
2634
- this._flushQueue().catch((e) => {
2635
- console.error("Error flushing queue:", e);
2636
- });
2637
- return id;
2638
- }
2639
- async _flushQueue() {
2640
- if (this._flushingQueue) return;
2641
- this._flushingQueue = true;
2642
- try {
2643
- while (true) {
2644
- const result = this.sql`
2645
- SELECT * FROM cf_agents_queues
2646
- ORDER BY created_at ASC
2647
- `;
2648
- if (!result || result.length === 0) break;
2649
- for (const row of result || []) {
2650
- const callback = this[row.callback];
2651
- if (!callback) {
2652
- console.error(`callback ${row.callback} not found`);
2653
- await this.dequeue(row.id);
2654
- continue;
2655
- }
2656
- const { connection, request, email } = __DO_NOT_USE_WILL_BREAK__agentContext.getStore() || {};
2657
- await runInInvocation({
2658
- agent: this,
2659
- connection,
2660
- request,
2661
- email
2662
- }, async () => {
2663
- const { maxAttempts, baseDelayMs, maxDelayMs } = resolveRetryConfig(parseRetryOptions(row), this._resolvedOptions.retry);
2664
- const parsedPayload = JSON.parse(row.payload);
2665
- try {
2666
- await tryN(maxAttempts, async (attempt) => {
2667
- if (attempt > 1) this._emit("queue:retry", {
2668
- callback: row.callback,
2669
- id: row.id,
2670
- attempt,
2671
- maxAttempts
2672
- });
2673
- await callback.bind(this)(parsedPayload, row);
2674
- }, {
2675
- baseDelayMs,
2676
- maxDelayMs
2677
- });
2678
- } catch (e) {
2679
- console.error(`queue callback "${row.callback}" failed after ${maxAttempts} attempts`, e);
2680
- this._emit("queue:error", {
2681
- callback: row.callback,
2682
- id: row.id,
2683
- error: e instanceof Error ? e.message : String(e),
2684
- attempts: maxAttempts
2685
- });
2686
- try {
2687
- await this.onError(e);
2688
- } catch {}
2689
- } finally {
2690
- this.dequeue(row.id);
2691
- }
2692
- }, { detached: true });
2693
- }
2694
- }
2695
- } finally {
2696
- this._flushingQueue = false;
2697
- }
2608
+ return (await this._queue.push(callback, payload, options)).id;
2698
2609
  }
2699
2610
  /**
2700
2611
  * Dequeue a task by ID
2701
2612
  * @param id ID of the task to dequeue
2702
2613
  */
2703
2614
  dequeue(id) {
2704
- this.sql`DELETE FROM cf_agents_queues WHERE id = ${id}`;
2615
+ return this._queue.cancel(id);
2705
2616
  }
2706
2617
  /**
2707
2618
  * Dequeue all tasks
2708
2619
  */
2709
2620
  dequeueAll() {
2710
- this.sql`DELETE FROM cf_agents_queues`;
2621
+ return this._queue.cancelAll();
2711
2622
  }
2712
2623
  /**
2713
2624
  * Dequeue all tasks by callback
2714
2625
  * @param callback Name of the callback to dequeue
2715
2626
  */
2716
2627
  dequeueAllByCallback(callback) {
2717
- this.sql`DELETE FROM cf_agents_queues WHERE callback = ${callback}`;
2628
+ return this._queue.cancelAll(callback);
2718
2629
  }
2719
2630
  /**
2720
2631
  * Get a queued task by ID
@@ -2722,31 +2633,16 @@ var Agent = class Agent extends DurableObject {
2722
2633
  * @returns The task or undefined if not found
2723
2634
  */
2724
2635
  getQueue(id) {
2725
- const result = this.sql`
2726
- SELECT * FROM cf_agents_queues WHERE id = ${id}
2727
- `;
2728
- if (!result || result.length === 0) return void 0;
2729
- const row = result[0];
2730
- return {
2731
- ...row,
2732
- payload: JSON.parse(row.payload),
2733
- retry: parseRetryOptions(row)
2734
- };
2636
+ return this._queue.get(id);
2735
2637
  }
2736
2638
  /**
2737
- * Get all queues by key and value
2639
+ * Get all queued tasks whose payload has `key` equal to `value`
2738
2640
  * @param key Key to filter by
2739
2641
  * @param value Value to filter by
2740
2642
  * @returns Array of matching QueueItem objects
2741
2643
  */
2742
- getQueues(key, value) {
2743
- return this.sql`
2744
- SELECT * FROM cf_agents_queues
2745
- `.filter((row) => JSON.parse(row.payload)[key] === value).map((row) => ({
2746
- ...row,
2747
- payload: JSON.parse(row.payload),
2748
- retry: parseRetryOptions(row)
2749
- }));
2644
+ async getQueues(key, value) {
2645
+ return (await this._queue.list()).filter((item) => typeof item.payload === "object" && item.payload !== null && item.payload[key] === value);
2750
2646
  }
2751
2647
  _lifecycleRouteAddress() {
2752
2648
  return this._dynamicAgents.lifecycleRouteAddress();
@@ -2758,7 +2654,8 @@ var Agent = class Agent extends DurableObject {
2758
2654
  return this._dynamicAgents.routeLifecycleToTarget(target, envelope);
2759
2655
  }
2760
2656
  /** Single native-RPC aperture for routed Lifecycle capabilities. */
2761
- _cf_routeLifecycle(target, envelope) {
2657
+ async _cf_routeLifecycle(target, envelope) {
2658
+ await this.__unsafe_ensureInitialized();
2762
2659
  return this._dynamicAgents.routeLifecycle(target, envelope);
2763
2660
  }
2764
2661
  _rootAlarmOwner() {
@@ -2775,6 +2672,7 @@ var Agent = class Agent extends DurableObject {
2775
2672
  * @internal
2776
2673
  */
2777
2674
  async _cf_cleanupFacetPrefix(ownerPath) {
2675
+ await this.__unsafe_ensureInitialized();
2778
2676
  await this._dynamicAgents.cleanupPrefix(ownerPath);
2779
2677
  }
2780
2678
  /**
@@ -2783,7 +2681,8 @@ var Agent = class Agent extends DurableObject {
2783
2681
  * physical alarm, so this lets facet work use the root alarm heartbeat.
2784
2682
  * @internal
2785
2683
  */
2786
- _cf_acquireFacetKeepAlive(ownerPath) {
2684
+ async _cf_acquireFacetKeepAlive(ownerPath) {
2685
+ await this.__unsafe_ensureInitialized();
2787
2686
  return this._dynamicAgents.acquireKeepAlive(ownerPath);
2788
2687
  }
2789
2688
  /**
@@ -2791,8 +2690,9 @@ var Agent = class Agent extends DurableObject {
2791
2690
  * Idempotent so disposer calls can safely race or run twice.
2792
2691
  * @internal
2793
2692
  */
2794
- _cf_releaseFacetKeepAlive(token) {
2795
- return this._dynamicAgents.releaseKeepAlive(token);
2693
+ async _cf_releaseFacetKeepAlive(token) {
2694
+ await this.__unsafe_ensureInitialized();
2695
+ await this._dynamicAgents.releaseKeepAlive(token);
2796
2696
  }
2797
2697
  /**
2798
2698
  * Register a facet's durable run row in the root-side index so root
@@ -2800,15 +2700,17 @@ var Agent = class Agent extends DurableObject {
2800
2700
  * The facet remains authoritative for snapshots and recovery hooks.
2801
2701
  * @internal
2802
2702
  */
2803
- _cf_registerFacetRun(ownerPath, runId) {
2804
- return this._dynamicAgents.registerRun(ownerPath, runId);
2703
+ async _cf_registerFacetRun(ownerPath, runId) {
2704
+ await this.__unsafe_ensureInitialized();
2705
+ await this._dynamicAgents.registerRun(ownerPath, runId);
2805
2706
  }
2806
2707
  /**
2807
2708
  * Remove a completed facet fiber from the root-side index.
2808
2709
  * @internal
2809
2710
  */
2810
- _cf_unregisterFacetRun(ownerPath, runId) {
2811
- return this._dynamicAgents.unregisterRun(ownerPath, runId);
2711
+ async _cf_unregisterFacetRun(ownerPath, runId) {
2712
+ await this.__unsafe_ensureInitialized();
2713
+ await this._dynamicAgents.unregisterRun(ownerPath, runId);
2812
2714
  }
2813
2715
  /**
2814
2716
  * Schedule a task to be executed in the future
@@ -3493,6 +3395,7 @@ var Agent = class Agent extends DurableObject {
3493
3395
  };
3494
3396
  let root;
3495
3397
  let registeredFacetRun = false;
3398
+ let bodyOutcome;
3496
3399
  let dispose = () => {};
3497
3400
  try {
3498
3401
  if ("initialSnapshot" in (options ?? {})) writeSnapshot(options?.initialSnapshot);
@@ -3506,16 +3409,29 @@ var Agent = class Agent extends DurableObject {
3506
3409
  writeSnapshot(options?.wrapStash ? options.wrapStash(data) : data);
3507
3410
  };
3508
3411
  try {
3509
- const result = await _fiberALS.run({
3510
- id,
3511
- signal,
3512
- stash
3513
- }, () => fn({
3514
- id,
3515
- signal,
3516
- stash,
3517
- snapshot: null
3518
- }));
3412
+ let result;
3413
+ try {
3414
+ result = await _fiberALS.run({
3415
+ id,
3416
+ signal,
3417
+ stash
3418
+ }, () => fn({
3419
+ id,
3420
+ signal,
3421
+ stash,
3422
+ snapshot: null
3423
+ }));
3424
+ } catch (error) {
3425
+ bodyOutcome = {
3426
+ status: signal.aborted ? "aborted" : "error",
3427
+ error: this._fiberErrorMessage(error)
3428
+ };
3429
+ throw error;
3430
+ }
3431
+ bodyOutcome = {
3432
+ status: "completed",
3433
+ error: null
3434
+ };
3519
3435
  options?.beforeRunCleanup?.({ ok: true });
3520
3436
  this._emit("fiber:run:completed", {
3521
3437
  fiberId: id,
@@ -3540,14 +3456,29 @@ var Agent = class Agent extends DurableObject {
3540
3456
  }
3541
3457
  } finally {
3542
3458
  this._runFiberActiveFibers.delete(id);
3543
- this._withAgentSpan("finalize_fiber", "fiber", {
3544
- "cloudflare.agents.fiber.id": id,
3545
- "cloudflare.agents.fiber.name": name
3546
- }, () => {
3547
- this.sql`DELETE FROM cf_agents_runs WHERE id = ${id}`;
3548
- });
3459
+ let rowDeleted = false;
3460
+ try {
3461
+ this._withAgentSpan("finalize_fiber", "fiber", {
3462
+ "cloudflare.agents.fiber.id": id,
3463
+ "cloudflare.agents.fiber.name": name
3464
+ }, () => {
3465
+ if (bodyOutcome) try {
3466
+ this.sql`
3467
+ UPDATE cf_agents_runs
3468
+ SET completed_at = ${Date.now()},
3469
+ outcome = ${bodyOutcome.status},
3470
+ error_message = ${bodyOutcome.error}
3471
+ WHERE id = ${id}
3472
+ `;
3473
+ } catch {}
3474
+ this.sql`DELETE FROM cf_agents_runs WHERE id = ${id}`;
3475
+ });
3476
+ rowDeleted = true;
3477
+ } catch (error) {
3478
+ console.error(`[Agent] Failed to finalize fiber "${name}" (${id}); leaving run row for recovery:`, error);
3479
+ }
3549
3480
  dispose();
3550
- if (root && registeredFacetRun) try {
3481
+ if (root && registeredFacetRun && rowDeleted) try {
3551
3482
  await root._cf_unregisterFacetRun(this.selfPath, id);
3552
3483
  } catch (e) {
3553
3484
  console.error("[Agent] Failed to unregister facet fiber:", e);
@@ -3604,7 +3535,11 @@ var Agent = class Agent extends DurableObject {
3604
3535
  const fiberRecoveryMaxAgeMs = this._resolvedOptions.fiberRecoveryMaxAgeMs;
3605
3536
  let madeProgress = false;
3606
3537
  try {
3607
- const rows = this.sql`SELECT id, name, snapshot, created_at FROM cf_agents_runs`;
3538
+ const rows = this.sql`
3539
+ SELECT id, name, snapshot, created_at, completed_at, outcome,
3540
+ error_message
3541
+ FROM cf_agents_runs
3542
+ `;
3608
3543
  for (const row of rows) {
3609
3544
  if (scanDeadlineMs > 0 && Date.now() - scanStartedAt > scanDeadlineMs) {
3610
3545
  this._emit("fiber:recovery:skipped", {
@@ -3616,6 +3551,27 @@ var Agent = class Agent extends DurableObject {
3616
3551
  break;
3617
3552
  }
3618
3553
  if (this._runFiberActiveFibers.has(row.id)) continue;
3554
+ const managedRow = this._readFiber(row.id);
3555
+ if (row.completed_at !== null && (!managedRow || row.outcome !== null)) {
3556
+ if (managedRow) this.sql`
3557
+ UPDATE cf_agents_fibers
3558
+ SET status = ${row.outcome},
3559
+ error_message = ${row.error_message},
3560
+ completed_at = ${row.completed_at}
3561
+ WHERE fiber_id = ${row.id}
3562
+ AND status IN ('pending', 'running')
3563
+ `;
3564
+ this.sql`DELETE FROM cf_agents_runs WHERE id = ${row.id}`;
3565
+ madeProgress = true;
3566
+ if (managedRow) this._notifyManagedFiberTerminal(row.id);
3567
+ continue;
3568
+ }
3569
+ if (managedRow && this._isTerminalFiberStatus(managedRow.status)) {
3570
+ this.sql`DELETE FROM cf_agents_runs WHERE id = ${row.id}`;
3571
+ madeProgress = true;
3572
+ this._notifyManagedFiberTerminal(row.id);
3573
+ continue;
3574
+ }
3619
3575
  const snapshot = this._parseFiberRecoverySnapshot(row.id, row.snapshot);
3620
3576
  const ctx = {
3621
3577
  id: row.id,
@@ -3624,7 +3580,6 @@ var Agent = class Agent extends DurableObject {
3624
3580
  createdAt: row.created_at,
3625
3581
  recoveryReason: "interrupted"
3626
3582
  };
3627
- const managedRow = this._readFiber(row.id);
3628
3583
  this._emit("fiber:recovery:detected", {
3629
3584
  ...this._fiberRecoveryPayload(ctx, managedRow),
3630
3585
  elapsedMs: Date.now() - row.created_at
@@ -3637,12 +3592,6 @@ var Agent = class Agent extends DurableObject {
3637
3592
  elapsedMs: Date.now() - row.created_at
3638
3593
  });
3639
3594
  if (managedRow) {
3640
- if (this._isTerminalFiberStatus(managedRow.status)) {
3641
- this.sql`DELETE FROM cf_agents_runs WHERE id = ${row.id}`;
3642
- madeProgress = true;
3643
- this._notifyManagedFiberTerminal(row.id);
3644
- continue;
3645
- }
3646
3595
  const completedAt = Date.now();
3647
3596
  this.sql`
3648
3597
  UPDATE cf_agents_fibers
@@ -3780,8 +3729,9 @@ var Agent = class Agent extends DurableObject {
3780
3729
  * cleanup as `parent.deleteSubAgent(Cls, name)` from the parent.
3781
3730
  * @internal
3782
3731
  */
3783
- _cf_destroyDescendantFacet(targetPath) {
3784
- return this._dynamicAgents.destroyDescendant(targetPath);
3732
+ async _cf_destroyDescendantFacet(targetPath) {
3733
+ await this.__unsafe_ensureInitialized();
3734
+ await this._dynamicAgents.destroyDescendant(targetPath);
3785
3735
  }
3786
3736
  /**
3787
3737
  * Whether any runFiber recovery work is still outstanding: orphaned
@@ -3819,6 +3769,15 @@ var Agent = class Agent extends DurableObject {
3819
3769
  */
3820
3770
  async _syncHostJobs() {
3821
3771
  if (this._destroyed) return;
3772
+ if (this._isFacet) {
3773
+ const work = this.lifecycle.jobs;
3774
+ for (const id of [
3775
+ HOST_JOB_DESTROY_ID,
3776
+ HOST_JOB_KEEP_ALIVE_ID,
3777
+ HOST_JOB_HOUSEKEEPING_ID
3778
+ ]) if (work.get(id)) await work.cancel(id);
3779
+ return;
3780
+ }
3822
3781
  await this._withAgentSpan("schedule_agent_alarm", "alarm", {}, async () => {
3823
3782
  const work = this.lifecycle.jobs;
3824
3783
  const nowMs = Date.now();
@@ -3950,9 +3909,10 @@ var Agent = class Agent extends DurableObject {
3950
3909
  className: match.childClass,
3951
3910
  name: match.childName
3952
3911
  });
3953
- if (decision instanceof Response) return decision;
3912
+ const isWebSocketUpgrade = request.headers.get("Upgrade")?.toLowerCase() === "websocket";
3913
+ if (decision instanceof Response) return isWebSocketUpgrade && !decision.webSocket ? rejectSubAgentWebSocket(decision) : decision;
3954
3914
  const forwardReq = decision instanceof Request ? decision : request;
3955
- if (request.headers.get("Upgrade")?.toLowerCase() === "websocket") {
3915
+ if (isWebSocketUpgrade) {
3956
3916
  const acceptHeaders = new Headers(forwardReq.headers);
3957
3917
  const routedUrl = new URL(forwardReq.url);
3958
3918
  routedUrl.pathname = new URL(request.url).pathname;
@@ -3989,18 +3949,27 @@ var Agent = class Agent extends DurableObject {
3989
3949
  }
3990
3950
  }
3991
3951
  async _cf_broadcastToSubAgent(ownerPath, message, without) {
3952
+ await this.__unsafe_ensureInitialized();
3992
3953
  await this._dynamicAgents.broadcastToPath(ownerPath, message, without);
3993
3954
  }
3994
- _cf_subAgentConnectionMetas(ownerPath) {
3955
+ async _cf_subAgentConnectionMetas(ownerPath) {
3956
+ await this.__unsafe_ensureInitialized();
3995
3957
  return this._dynamicAgents.connectionMetas(ownerPath);
3996
3958
  }
3997
- _cf_sendToSubAgentConnection(connectionId, message) {
3998
- return this._dynamicAgents.sendToConnection(connectionId, message);
3959
+ async _cf_sendToSubAgentConnection(connectionId, message) {
3960
+ await this.__unsafe_ensureInitialized();
3961
+ await this._dynamicAgents.sendToConnection(connectionId, message);
3962
+ }
3963
+ async _cf_closeSubAgentConnection(connectionId, code, reason) {
3964
+ await this.__unsafe_ensureInitialized();
3965
+ await this._dynamicAgents.closeConnection(connectionId, code, reason);
3999
3966
  }
4000
- _cf_closeSubAgentConnection(connectionId, code, reason) {
4001
- return this._dynamicAgents.closeConnection(connectionId, code, reason);
3967
+ async _cf_closeSubAgentConnectionsForPrefix(prefix, code, reason) {
3968
+ await this.__unsafe_ensureInitialized();
3969
+ this._dynamicAgents.closeConnectionsForPrefix(prefix, code, reason);
4002
3970
  }
4003
- _cf_setSubAgentConnectionState(connectionId, state) {
3971
+ async _cf_setSubAgentConnectionState(connectionId, state) {
3972
+ await this.__unsafe_ensureInitialized();
4004
3973
  return this._dynamicAgents.setConnectionState(connectionId, state);
4005
3974
  }
4006
3975
  _cf_connectionTargetsSubAgent(connection) {
@@ -4316,6 +4285,9 @@ var Agent = class Agent extends DurableObject {
4316
4285
  const runId = options.runId ?? nanoid(12);
4317
4286
  const agentType = cls.name;
4318
4287
  const detached = this._parseDetachedOption(options.detached);
4288
+ const eventDelivery = options.eventDelivery ?? "full";
4289
+ if (eventDelivery !== "full" && eventDelivery !== "terminal") throw new Error(`runAgentTool: eventDelivery must be "full" or "terminal", got ${JSON.stringify(eventDelivery)}.`);
4290
+ if (detached && eventDelivery === "terminal") throw new Error("runAgentTool: eventDelivery \"terminal\" is not supported for detached runs.");
4319
4291
  const existing = this._readAgentToolRun(runId);
4320
4292
  if (existing) {
4321
4293
  if (detached) {
@@ -4406,13 +4378,14 @@ var Agent = class Agent extends DurableObject {
4406
4378
  input_redacted, status, display_metadata, display_order, started_at,
4407
4379
  detached, detached_on_finish, detached_notify_source,
4408
4380
  detached_max_budget_at, detached_no_progress_budget_ms,
4409
- detached_on_milestones
4381
+ detached_on_milestones, event_delivery
4410
4382
  ) VALUES (
4411
4383
  ${runId}, ${options.parentToolCallId ?? null}, ${agentType},
4412
4384
  ${inputPreviewJson}, 1, 'starting', ${displayJson}, ${displayOrder},
4413
4385
  ${startedAt}, ${detached ? 1 : 0}, ${detached?.onFinishName ?? null},
4414
4386
  ${detached?.notifySource ?? null}, ${detachedMaxBudgetAt},
4415
- ${detachedNoProgressBudgetMs}, ${detachedOnMilestonesJson}
4387
+ ${detachedNoProgressBudgetMs}, ${detachedOnMilestonesJson},
4388
+ ${eventDelivery === "terminal" ? "terminal" : null}
4416
4389
  )
4417
4390
  `;
4418
4391
  const runInfo = {
@@ -4437,7 +4410,10 @@ var Agent = class Agent extends DurableObject {
4437
4410
  });
4438
4411
  const child = await this.subAgent(cls, runId);
4439
4412
  const adapter = this._asAgentToolChildAdapter(child);
4440
- const childStart = await adapter.startAgentToolRun(options.input, { runId });
4413
+ const childStart = await adapter.startAgentToolRun(options.input, {
4414
+ runId,
4415
+ ...eventDelivery === "terminal" ? { eventDelivery } : {}
4416
+ });
4441
4417
  this._markAgentToolRunning(runId);
4442
4418
  if (detached) {
4443
4419
  if (options.signal) console.warn(`[agents] runAgentTool: \`signal\` is ignored for a detached run (${runId}); a detached child must outlive the spawning turn. Use cancelAgentTool(runId) to cancel it.`);
@@ -5048,29 +5024,79 @@ var Agent = class Agent extends DurableObject {
5048
5024
  else this.broadcast(body);
5049
5025
  }
5050
5026
  _broadcastAgentToolChunks(parentToolCallId, runId, chunks, sequence, replay, connection) {
5027
+ const terminalOnly = this._isTerminalOnlyAgentToolRun(runId);
5051
5028
  let next = sequence;
5052
- for (const chunk of chunks) this._broadcastAgentToolEvent(parentToolCallId, next++, {
5053
- kind: "chunk",
5054
- runId,
5055
- body: chunk.body
5056
- }, replay, connection);
5029
+ for (const chunk of chunks) {
5030
+ const chunkSequence = next++;
5031
+ if (terminalOnly && !isAgentToolLifecycleChunk(chunk.body)) continue;
5032
+ this._broadcastAgentToolEvent(parentToolCallId, chunkSequence, {
5033
+ kind: "chunk",
5034
+ runId,
5035
+ body: chunk.body
5036
+ }, replay, connection);
5037
+ }
5057
5038
  return next;
5058
5039
  }
5059
- async _broadcastAgentToolStoredChunks(row, sequence, replay, connection) {
5060
- const child = await this._cf_resolveSubAgent(row.agent_type, row.run_id);
5040
+ _isTerminalOnlyAgentToolRun(runId) {
5041
+ return this.sql`
5042
+ SELECT event_delivery FROM cf_agent_tool_runs WHERE run_id = ${runId}
5043
+ `[0]?.event_delivery === "terminal";
5044
+ }
5045
+ async _broadcastAgentToolStoredChunks(row, sequence, replay, connection, timeoutMs) {
5046
+ const deadline = this._agentToolRecoveryDeadline(timeoutMs);
5047
+ const resolving = this._cf_resolveSubAgent(row.agent_type, row.run_id);
5048
+ const child = deadline === void 0 ? await resolving : await this._settleWithinRecoveryTimeout(resolving, deadline());
5049
+ if (child === void 0) return sequence;
5061
5050
  const adapter = this._asAgentToolChildAdapter(child);
5062
- return this._broadcastAgentToolStoredChunksFromAdapter(adapter, row, sequence, replay, connection);
5051
+ return this._broadcastAgentToolStoredChunksFromAdapter(adapter, row, sequence, replay, connection, deadline?.());
5052
+ }
5053
+ /**
5054
+ * One budget shared by every step of a bounded recovery read, so a run's
5055
+ * total time stays within `timeoutMs`. Returns the remaining milliseconds,
5056
+ * never below 1 because a non-positive timeout means "unbounded".
5057
+ */
5058
+ _agentToolRecoveryDeadline(timeoutMs) {
5059
+ if (timeoutMs === void 0 || timeoutMs <= 0) return void 0;
5060
+ const deadline = Date.now() + timeoutMs;
5061
+ return () => Math.max(1, deadline - Date.now());
5063
5062
  }
5064
5063
  async _broadcastAgentToolStoredChunksFromAdapter(adapter, row, sequence, replay, connection, timeoutMs) {
5065
- const chunks = await this._getAgentToolChunksForRecovery(adapter, row.run_id, timeoutMs);
5066
- if (!chunks) return sequence;
5067
- return this._broadcastAgentToolChunks(row.parent_tool_call_id ?? void 0, row.run_id, chunks, sequence, replay, connection);
5064
+ const deadline = this._agentToolRecoveryDeadline(timeoutMs);
5065
+ const chunks = await this._getAgentToolChunksForRecovery(adapter, row.run_id, deadline?.());
5066
+ const next = chunks ? this._broadcastAgentToolChunks(row.parent_tool_call_id ?? void 0, row.run_id, chunks, sequence, replay, connection) : sequence;
5067
+ return this._broadcastAgentToolMilestones(adapter, row, next, replay, connection, deadline?.());
5068
+ }
5069
+ /**
5070
+ * Milestones are persisted on the child run rather than in its chunk log, so
5071
+ * replay re-emits them from the child's inspection. The client dedupes them
5072
+ * on the milestone's own sequence, so a milestone already seen live is a no-op.
5073
+ * They carry the next stored-chunk sequence without consuming it, matching
5074
+ * the live path.
5075
+ */
5076
+ async _broadcastAgentToolMilestones(adapter, row, sequence, replay, connection, timeoutMs) {
5077
+ const milestones = (await this._settleWithinRecoveryTimeout(adapter.inspectAgentToolRun(row.run_id, { reconcile: false }), timeoutMs))?.milestones ?? [];
5078
+ for (const milestone of milestones) this._broadcastAgentToolEvent(row.parent_tool_call_id ?? void 0, sequence, {
5079
+ kind: "chunk",
5080
+ runId: row.run_id,
5081
+ body: JSON.stringify({
5082
+ type: AGENT_TOOL_MILESTONE_PART,
5083
+ data: {
5084
+ name: milestone.name,
5085
+ sequence: milestone.sequence,
5086
+ at: milestone.at,
5087
+ ...milestone.data !== void 0 ? { data: milestone.data } : {}
5088
+ }
5089
+ })
5090
+ }, replay, connection);
5091
+ return sequence;
5068
5092
  }
5069
5093
  async _forwardAgentToolStream(stream, parentToolCallId, runId, sequence, signal, idleTimeoutMs) {
5070
5094
  let next = sequence;
5095
+ let forwarded = false;
5071
5096
  if (signal?.aborted) return {
5072
5097
  next,
5073
- ended: "aborted"
5098
+ ended: "aborted",
5099
+ forwarded
5074
5100
  };
5075
5101
  let ended = "done";
5076
5102
  const reader = stream.getReader();
@@ -5098,12 +5124,18 @@ var Agent = class Agent extends DurableObject {
5098
5124
  idleTimer = setTimeout(() => resolveIdle?.(), idleTimeoutMs);
5099
5125
  };
5100
5126
  let forwardedSinceProgress = false;
5127
+ const terminalOnly = this._isTerminalOnlyAgentToolRun(runId);
5101
5128
  try {
5102
5129
  const forwardChunk = (chunk) => {
5103
- this._broadcastAgentToolEvent(parentToolCallId, next++, {
5130
+ const lifecycle = isAgentToolLifecycleChunk(chunk.body);
5131
+ const unstoredId = typeof chunk.unstoredId === "string" ? chunk.unstoredId : void 0;
5132
+ const chunkSequence = lifecycle || unstoredId !== void 0 ? next : next++;
5133
+ forwarded = true;
5134
+ if (!terminalOnly || lifecycle) this._broadcastAgentToolEvent(parentToolCallId, chunkSequence, {
5104
5135
  kind: "chunk",
5105
5136
  runId,
5106
- body: chunk.body
5137
+ body: chunk.body,
5138
+ ...unstoredId !== void 0 ? { unstoredId } : {}
5107
5139
  });
5108
5140
  this._observeForwardedProgress(runId, chunk.body);
5109
5141
  forwardedSinceProgress = true;
@@ -5171,7 +5203,8 @@ var Agent = class Agent extends DurableObject {
5171
5203
  }
5172
5204
  return {
5173
5205
  next,
5174
- ended
5206
+ ended,
5207
+ forwarded
5175
5208
  };
5176
5209
  }
5177
5210
  /**
@@ -5372,9 +5405,9 @@ var Agent = class Agent extends DurableObject {
5372
5405
  * dies and recovers again during deploy churn is still collected. A genuinely
5373
5406
  * silent/hung child can never block recovery forever: it seals `interrupted`
5374
5407
  * after one `noProgressTimeoutMs` window. `maxWindowMs` is an OPTIONAL hard
5375
- * wall-clock ceiling (default `Infinity` — uncapped, mirroring #1672's
5376
- * `maxRecoveryWork`); set it finite to also bound a child that keeps
5377
- * progressing, which seals `window-exceeded` and tears the child down.
5408
+ * wall-clock ceiling (default `Infinity` — uncapped, #1672); set it finite
5409
+ * to also bound a child that keeps progressing, which seals
5410
+ * `window-exceeded` and tears the child down.
5378
5411
  *
5379
5412
  * Returns the terminal `result` (and `completedAt`) when the child reaches a
5380
5413
  * terminal status, plus the advanced broadcast `sequence`. Returns
@@ -5419,18 +5452,22 @@ var Agent = class Agent extends DurableObject {
5419
5452
  try {
5420
5453
  while (!ceilingController.signal.aborted) {
5421
5454
  let afterSequence = -1;
5455
+ let storedCount = 0;
5422
5456
  try {
5423
5457
  const existing = await adapter.getAgentToolChunks(row.run_id);
5424
5458
  const last = existing[existing.length - 1];
5425
5459
  if (last) afterSequence = last.sequence;
5460
+ storedCount = existing.length;
5426
5461
  } catch {}
5427
- const beforeSequence = nextSequence;
5462
+ nextSequence = storedCount + 1;
5428
5463
  let streamEnded = "idle";
5464
+ let forwardedAny = false;
5429
5465
  try {
5430
5466
  const stream = await adapter.tailAgentToolRun(row.run_id, { afterSequence });
5431
5467
  const forwarded = await this._forwardAgentToolStream(stream, row.parent_tool_call_id ?? void 0, row.run_id, nextSequence, ceilingController.signal, noProgressTimeoutMs);
5432
5468
  nextSequence = forwarded.next;
5433
5469
  streamEnded = forwarded.ended;
5470
+ forwardedAny = forwarded.forwarded;
5434
5471
  } catch {}
5435
5472
  const terminal = await collectTerminal(nextSequence);
5436
5473
  if (terminal) return terminal;
@@ -5439,7 +5476,7 @@ var Agent = class Agent extends DurableObject {
5439
5476
  break;
5440
5477
  }
5441
5478
  if (streamEnded !== "done") break;
5442
- if (nextSequence <= beforeSequence) break;
5479
+ if (!forwardedAny) break;
5443
5480
  }
5444
5481
  } finally {
5445
5482
  if (ceilingTimer !== void 0) clearTimeout(ceilingTimer);
@@ -5469,7 +5506,7 @@ var Agent = class Agent extends DurableObject {
5469
5506
  display: this._parseAgentToolJson(row.display_metadata)
5470
5507
  }, true, connection);
5471
5508
  try {
5472
- sequence = await this._broadcastAgentToolStoredChunks(row, sequence, true, connection);
5509
+ sequence = await this._broadcastAgentToolStoredChunks(row, sequence, true, connection, DEFAULT_AGENT_TOOL_RECOVERY_TIMEOUT_MS);
5473
5510
  } catch {}
5474
5511
  if (this._isAgentToolTerminal(row.status)) this._broadcastAgentToolTerminal(parentToolCallId, sequence, {
5475
5512
  runId: row.run_id,
@@ -5656,13 +5693,17 @@ var Agent = class Agent extends DurableObject {
5656
5693
  `.map((row) => row.run_id);
5657
5694
  }
5658
5695
  async _getAgentToolChunksForRecovery(adapter, runId, timeoutMs) {
5659
- const chunks = adapter.getAgentToolChunks(runId).catch(() => void 0);
5660
- if (timeoutMs === void 0 || timeoutMs <= 0) return chunks;
5696
+ return this._settleWithinRecoveryTimeout(adapter.getAgentToolChunks(runId), timeoutMs);
5697
+ }
5698
+ /** Resolve to `undefined` when `promise` rejects or outlasts `timeoutMs`. */
5699
+ async _settleWithinRecoveryTimeout(promise, timeoutMs) {
5700
+ const settled = promise.catch(() => void 0);
5701
+ if (timeoutMs === void 0 || timeoutMs <= 0) return settled;
5661
5702
  let timeoutId;
5662
5703
  const timeout = new Promise((resolve) => {
5663
5704
  timeoutId = setTimeout(() => resolve(void 0), timeoutMs);
5664
5705
  });
5665
- const result = await Promise.race([chunks, timeout]);
5706
+ const result = await Promise.race([settled, timeout]);
5666
5707
  if (timeoutId !== void 0) clearTimeout(timeoutId);
5667
5708
  return result;
5668
5709
  }
@@ -6960,4 +7001,4 @@ var StreamingResponse = class {
6960
7001
  //#endregion
6961
7002
  export { routeAgentEmail as a, getCurrentAgent as i, DEFAULT_AGENT_STATIC_OPTIONS as n, StreamingResponse as r, Agent as t };
6962
7003
 
6963
- //# sourceMappingURL=src-DlSHshb2.js.map
7004
+ //# sourceMappingURL=src-BZeu-SCD.js.map