agents 0.22.0 → 0.24.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 (215) hide show
  1. package/README.md +20 -1
  2. package/dist/{agent-routing-CnkaHb-v.d.ts → agent-routing-B2XLNMxq.d.ts} +1094 -443
  3. package/dist/agent-routing.d.ts +1 -1
  4. package/dist/agent-tool-types.d.ts +26 -26
  5. package/dist/{agent-tools-C0XJqWzB.d.ts → agent-tools-Dj5Mzlw7.d.ts} +7 -7
  6. package/dist/agent-tools.d.ts +21 -21
  7. package/dist/browser/ai.js +4 -1
  8. package/dist/browser/ai.js.map +1 -1
  9. package/dist/browser/tanstack-ai.js.map +1 -1
  10. package/dist/callable-decorator-DP__HhBA.d.ts +72 -0
  11. package/dist/callable-decorator.d.ts +18 -0
  12. package/dist/callable-decorator.js +71 -0
  13. package/dist/callable-decorator.js.map +1 -0
  14. package/dist/{capability-BjSKYpzg.js → capability-B4WbF81e.js} +2 -1
  15. package/dist/capability-B4WbF81e.js.map +1 -0
  16. package/dist/capability-runner-BUBa6Ake.d.ts +484 -0
  17. package/dist/channel-Bnm4S7T2.d.ts +491 -0
  18. package/dist/channels/ai-sdk.d.ts +46 -0
  19. package/dist/channels/ai-sdk.js +120 -0
  20. package/dist/channels/ai-sdk.js.map +1 -0
  21. package/dist/channels/email.d.ts +95 -0
  22. package/dist/channels/email.js +323 -0
  23. package/dist/channels/email.js.map +1 -0
  24. package/dist/channels/index.d.ts +233 -0
  25. package/dist/channels/index.js +608 -0
  26. package/dist/channels/index.js.map +1 -0
  27. package/dist/channels/slack.d.ts +140 -0
  28. package/dist/channels/slack.js +614 -0
  29. package/dist/channels/slack.js.map +1 -0
  30. package/dist/channels/tanstack-ai.d.ts +39 -0
  31. package/dist/channels/tanstack-ai.js +17 -0
  32. package/dist/channels/tanstack-ai.js.map +1 -0
  33. package/dist/channels/telegram.d.ts +106 -0
  34. package/dist/channels/telegram.js +427 -0
  35. package/dist/channels/telegram.js.map +1 -0
  36. package/dist/channels/voice.d.ts +45 -0
  37. package/dist/channels/voice.js +122 -0
  38. package/dist/channels/voice.js.map +1 -0
  39. package/dist/chat/index.d.ts +2319 -2010
  40. package/dist/chat/index.js +892 -518
  41. package/dist/chat/index.js.map +1 -1
  42. package/dist/chat-sdk/index.d.ts +7 -7
  43. package/dist/chat-sdk/index.js +1 -1
  44. package/dist/client-BxdkCuVy.d.ts +411 -0
  45. package/dist/client-C9DAjZIY.js +518 -0
  46. package/dist/client-C9DAjZIY.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.d.ts +27 -283
  50. package/dist/client.js +2 -274
  51. package/dist/context/index.d.ts +216 -0
  52. package/dist/context/index.js +454 -0
  53. package/dist/context/index.js.map +1 -0
  54. package/dist/current-agent-C4w86kcN.d.ts +93 -0
  55. package/dist/current-agent-DhoDkSnH.js.map +1 -1
  56. package/dist/{diagnostics-CaBjfz4J.js → diagnostics-BzvaX2UT.js} +5 -1
  57. package/dist/diagnostics-BzvaX2UT.js.map +1 -0
  58. package/dist/diagnostics-C4jcz3VK.js +360 -0
  59. package/dist/diagnostics-C4jcz3VK.js.map +1 -0
  60. package/dist/durable-object-lifecycle-ei7M7lqB.d.ts +157 -0
  61. package/dist/index-BB0kqhIz.d.ts +101 -0
  62. package/dist/index-BVVgDSdq.d.ts +1 -0
  63. package/dist/index-Dlgtd3Mj.d.ts +78 -0
  64. package/dist/index-XDkuQ7zm.d.ts +89 -0
  65. package/dist/{index-DcSAZKsB.d.ts → index-YSKgfgg9.d.ts} +3 -1
  66. package/dist/index.d.ts +92 -83
  67. package/dist/index.js +4 -3
  68. package/dist/ingress-BfetZbMO.js +83 -0
  69. package/dist/ingress-BfetZbMO.js.map +1 -0
  70. package/dist/internal-CYlgHl1l.js +59 -0
  71. package/dist/internal-CYlgHl1l.js.map +1 -0
  72. package/dist/internal_context.d.ts +1 -1
  73. package/dist/lifecycle/index.d.ts +39 -19
  74. package/dist/lifecycle/index.js +2 -2
  75. package/dist/lifecycle-Mm_jQh7r.js +1314 -0
  76. package/dist/lifecycle-Mm_jQh7r.js.map +1 -0
  77. package/dist/mcp/client/index.d.ts +20 -20
  78. package/dist/mcp/client/index.js +1 -1
  79. package/dist/mcp/index.d.ts +35 -35
  80. package/dist/mcp/index.js +2 -2
  81. package/dist/observability/index.d.ts +1 -1
  82. package/dist/observability/index.js +1 -1
  83. package/dist/queue/index.d.ts +123 -0
  84. package/dist/queue/index.js +2 -0
  85. package/dist/queue-BC9F5cUo.js +406 -0
  86. package/dist/queue-BC9F5cUo.js.map +1 -0
  87. package/dist/react.d.ts +34 -16
  88. package/dist/react.js +31 -8
  89. package/dist/react.js.map +1 -1
  90. package/dist/{retries-CAvxtG9d.d.ts → retries-D9B2UCq3.d.ts} +23 -6
  91. package/dist/retries.d.ts +10 -6
  92. package/dist/retries.js +21 -1
  93. package/dist/retries.js.map +1 -1
  94. package/dist/routing/index.d.ts +137 -0
  95. package/dist/routing/index.js +244 -0
  96. package/dist/routing/index.js.map +1 -0
  97. package/dist/sanitize-D9TujEK8.js +79 -0
  98. package/dist/sanitize-D9TujEK8.js.map +1 -0
  99. package/dist/{scheduler-DQoTGoAW.d.ts → scheduler-BGq6M5Kd.d.ts} +20 -21
  100. package/dist/scheduler-D_KHqxBa.js +658 -0
  101. package/dist/scheduler-D_KHqxBa.js.map +1 -0
  102. package/dist/schedules/index.d.ts +1 -1
  103. package/dist/schedules/index.js +1 -1
  104. package/dist/sentence-chunker-BAidJ4DA.d.ts +68 -0
  105. package/dist/serializable.d.ts +1 -1
  106. package/dist/sessions/index.d.ts +441 -0
  107. package/dist/sessions/index.js +2063 -0
  108. package/dist/sessions/index.js.map +1 -0
  109. package/dist/skills/index.d.ts +99 -0
  110. package/dist/skills/index.js +254 -5
  111. package/dist/skills/index.js.map +1 -1
  112. package/dist/{src-5W6JNKVb.js → src-BNU3ZiJM.js} +1524 -1441
  113. package/dist/src-BNU3ZiJM.js.map +1 -0
  114. package/dist/state/index.d.ts +6 -0
  115. package/dist/state/index.js +137 -0
  116. package/dist/state/index.js.map +1 -0
  117. package/dist/streams/index.d.ts +120 -0
  118. package/dist/streams/index.js +107 -0
  119. package/dist/streams/index.js.map +1 -0
  120. package/dist/streams-BcE8JP2X.d.ts +370 -0
  121. package/dist/streams-CCPRV6dt.js +709 -0
  122. package/dist/streams-CCPRV6dt.js.map +1 -0
  123. package/dist/sub-routing.d.ts +12 -12
  124. package/dist/surface-bZZJqBka.js +17 -0
  125. package/dist/surface-bZZJqBka.js.map +1 -0
  126. package/dist/tasks/index.d.ts +64 -0
  127. package/dist/tasks/index.js +2 -0
  128. package/dist/tasks-D4nLqVSI.js +1656 -0
  129. package/dist/tasks-D4nLqVSI.js.map +1 -0
  130. package/dist/tasks-V2lCT8ZV.d.ts +517 -0
  131. package/dist/text-segment-joiner-BtAFQSA_.js +57 -0
  132. package/dist/text-segment-joiner-BtAFQSA_.js.map +1 -0
  133. package/dist/text-stream-CpdiKrJB.js +272 -0
  134. package/dist/text-stream-CpdiKrJB.js.map +1 -0
  135. package/dist/tokens-nHAKcN6M.js +52 -0
  136. package/dist/tokens-nHAKcN6M.js.map +1 -0
  137. package/dist/tool-schema-CBjGPrsQ.js +31 -0
  138. package/dist/tool-schema-CBjGPrsQ.js.map +1 -0
  139. package/dist/transport-protocol-BxyngQ_R.js +25 -0
  140. package/dist/transport-protocol-BxyngQ_R.js.map +1 -0
  141. package/dist/transport-protocol-ZTew5Wso.d.ts +27 -0
  142. package/dist/types-B7LojTe4.d.ts +202 -0
  143. package/dist/types-DCD6BNZH.d.ts +70 -0
  144. package/dist/types-_Faxb570.d.ts +439 -0
  145. package/dist/voice/client.d.ts +226 -0
  146. package/dist/voice/client.js +932 -0
  147. package/dist/voice/client.js.map +1 -0
  148. package/dist/voice/errors.d.ts +43 -0
  149. package/dist/voice/errors.js +41 -0
  150. package/dist/voice/errors.js.map +1 -0
  151. package/dist/voice/index.d.ts +271 -0
  152. package/dist/voice/index.js +1812 -0
  153. package/dist/voice/index.js.map +1 -0
  154. package/dist/voice/react.d.ts +167 -0
  155. package/dist/voice/react.js +234 -0
  156. package/dist/voice/react.js.map +1 -0
  157. package/dist/voice/sfu.d.ts +71 -0
  158. package/dist/voice/sfu.js +157 -0
  159. package/dist/voice/sfu.js.map +1 -0
  160. package/dist/voice/text.d.ts +6 -0
  161. package/dist/voice/text.js +2 -0
  162. package/dist/voice/types.d.ts +58 -0
  163. package/dist/voice/types.js +18 -0
  164. package/dist/voice/types.js.map +1 -0
  165. package/dist/voice/workers-ai.d.ts +136 -0
  166. package/dist/voice/workers-ai.js +568 -0
  167. package/dist/voice/workers-ai.js.map +1 -0
  168. package/dist/websockets/index.d.ts +301 -0
  169. package/dist/websockets/index.js +3 -0
  170. package/dist/websockets-D7IzWbZb.js +1005 -0
  171. package/dist/websockets-D7IzWbZb.js.map +1 -0
  172. package/dist/workflow-types.d.ts +25 -25
  173. package/dist/workflows.d.ts +21 -21
  174. package/dist/workflows.js +1 -1
  175. package/docs/agent-class.md +4 -4
  176. package/docs/agent-tools.md +2 -1
  177. package/docs/channels.md +323 -0
  178. package/docs/chat-agents.md +6 -13
  179. package/docs/client-sdk.md +30 -0
  180. package/docs/context.md +131 -0
  181. package/docs/index.md +16 -13
  182. package/docs/lifecycle.md +179 -57
  183. package/docs/long-running-agents.md +2 -2
  184. package/docs/mcp-servers.md +5 -1
  185. package/docs/queue.md +135 -241
  186. package/docs/resumable-streaming.md +1 -1
  187. package/docs/retries.md +1 -1
  188. package/docs/routing.md +105 -0
  189. package/docs/sessions.md +237 -871
  190. package/docs/streams.md +213 -0
  191. package/docs/sub-agents.md +184 -124
  192. package/docs/tasks.md +246 -0
  193. package/docs/voice.md +745 -0
  194. package/package.json +126 -14
  195. package/dist/capability-BjSKYpzg.js.map +0 -1
  196. package/dist/capability-runner-CvHGZqUu.d.ts +0 -150
  197. package/dist/client.js.map +0 -1
  198. package/dist/compaction-helpers-iiKMr2TQ.js +0 -340
  199. package/dist/compaction-helpers-iiKMr2TQ.js.map +0 -1
  200. package/dist/compaction-helpers-wUz6M3us.d.ts +0 -621
  201. package/dist/current-agent-CuMErtly.d.ts +0 -283
  202. package/dist/diagnostics-CaBjfz4J.js.map +0 -1
  203. package/dist/durable-object-lifecycle-D6nNQJJd.js +0 -862
  204. package/dist/durable-object-lifecycle-D6nNQJJd.js.map +0 -1
  205. package/dist/experimental/memory/session/index.d.ts +0 -671
  206. package/dist/experimental/memory/session/index.js +0 -2379
  207. package/dist/experimental/memory/session/index.js.map +0 -1
  208. package/dist/experimental/memory/utils/index.d.ts +0 -96
  209. package/dist/experimental/memory/utils/index.js +0 -79
  210. package/dist/experimental/memory/utils/index.js.map +0 -1
  211. package/dist/scheduler-CR9RHGos.js +0 -857
  212. package/dist/scheduler-CR9RHGos.js.map +0 -1
  213. package/dist/src-5W6JNKVb.js.map +0 -1
  214. package/dist/tool-output-truncation-CNnnGZQ3.js +0 -98
  215. package/dist/tool-output-truncation-CNnnGZQ3.js.map +0 -1
@@ -3,58 +3,35 @@ import { getAgentByName } from "./agent-routing.js";
3
3
  import { AGENT_TOOL_MILESTONE_PART } 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
+ import { copyCallableMetadata, decoratedMethods, getCallableMetadata, isCallableMethod } from "./callable-decorator.js";
6
7
  import "./types.js";
8
+ import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-DZBYAB34.js";
7
9
  import { signAgentHeaders } from "./email.js";
8
10
  import { sendAgentEmail } from "./email-send.js";
9
- import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-DZBYAB34.js";
10
11
  import { n as withInvocationScope, r as writeSpanAttributes, t as tracer } from "./cloudflare-Dzvc7V2N.js";
11
12
  import { parseSubAgentPath } from "./sub-routing.js";
12
- import { i as setLifecycleRouteTransport, n as setLifecycleEventSink, r as setLifecycleHostInvoker, t as Lifecycle } from "./durable-object-lifecycle-D6nNQJJd.js";
13
+ 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-D7IzWbZb.js";
14
+ import { t as LifecycleCapability } from "./capability-B4WbF81e.js";
15
+ import { t as _classPrivateMethodInitSpec } from "./classPrivateMethodInitSpec-qMjJ6sHQ.js";
16
+ import { a as abortWithoutAlarmRetry, i as setLifecycleRouteTransport, n as setLifecycleEventSink, r as setLifecycleHostInvoker, t as Lifecycle } from "./lifecycle-Mm_jQh7r.js";
17
+ import { isErrorRetryable, tryN, validateRetryOptions } from "./retries.js";
13
18
  import { SqlError } from "./sql-error.js";
14
- import { isDurableObjectMemoryLimitReset, isErrorRetryable, tryN, validateRetryOptions } from "./retries.js";
15
- 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";
16
20
  import { DurableObjectOAuthClientProvider } from "./mcp/client/do-oauth-client-provider.js";
17
21
  import { genericObservability } from "./observability/index.js";
18
- import { n as ensureScheduleTable, r as setSchedulerCallbackResolver, t as Scheduler } from "./scheduler-CR9RHGos.js";
22
+ import { n as setSchedulerCallbackResolver, t as Scheduler } from "./scheduler-D_KHqxBa.js";
23
+ import { n as setQueueCallbackResolver, t as Queue } from "./queue-BC9F5cUo.js";
24
+ import { n as setTaskDefinitionResolver, r as setTaskRoutedMemoryLimitHandler, t as Tasks } from "./tasks-D4nLqVSI.js";
25
+ import { State } from "./state/index.js";
19
26
  import { AsyncLocalStorage } from "node:async_hooks";
27
+ import { DurableObject, RpcTarget, exports } from "cloudflare:workers";
20
28
  import { nanoid } from "nanoid";
21
29
  import { EmailMessage } from "cloudflare:email";
22
- import { DurableObject, RpcTarget, exports } from "cloudflare:workers";
23
- //#region src/observability/agent-span-attributes.ts
24
- function agentSpanAttributes(input) {
25
- return {
26
- "instrumentation_scope.name": "agents",
27
- "instrumentation_scope.version": "1",
28
- "cloudflare.agents.session.id": input.sessionId,
29
- "cloudflare.agents.session.name": input.sessionName,
30
- "gen_ai.agent.name": input.agentClassName
31
- };
32
- }
33
- //#endregion
34
- //#region src/index.ts
35
- let _Symbol$dispose;
36
- /**
37
- * Enters an agent invocation: the context every handler reads, plus the span
38
- * scope that stops invocation-bounded spans from outliving it. Scopes do not
39
- * nest, so the outermost live entry point owns the boundary — pass
40
- * `detached` for work that deliberately runs on past its caller.
41
- */
42
- function runInInvocation(store, body, options) {
43
- return __DO_NOT_USE_WILL_BREAK__agentContext.run(store, () => withInvocationScope(body, options));
44
- }
30
+ //#region src/dynamic-agents/bridges.ts
45
31
  function isClosedWebSocketSendError(error) {
46
32
  return error instanceof TypeError && error.message.includes("WebSocket send() after close");
47
33
  }
48
- function sendRpcResponseIfOpen(connection, response) {
49
- try {
50
- connection.send(JSON.stringify(response));
51
- return true;
52
- } catch (error) {
53
- if (isClosedWebSocketSendError(error)) return false;
54
- throw error;
55
- }
56
- }
57
- const subAgentRpcReplyContext = new AsyncLocalStorage();
34
+ const dynamicAgentRpcReplyContext = new AsyncLocalStorage();
58
35
  function sendFacetRpcResponseIfOpen(target, response) {
59
36
  try {
60
37
  return {
@@ -72,19 +49,29 @@ function sendFacetRpcResponseIfOpen(target, response) {
72
49
  }
73
50
  }
74
51
  const facetStreamingResponseDeliveryStates = /* @__PURE__ */ new WeakMap();
75
- function createStreamingResponse(connection, id, facetReplyTarget) {
76
- const stream = new StreamingResponse(connection, id);
77
- if (facetReplyTarget) facetStreamingResponseDeliveryStates.set(stream, {
78
- replyTarget: facetReplyTarget,
52
+ /**
53
+ * Mark a StreamingResponse as facet-bridged: its chunks are delivered to
54
+ * `replyTarget` (the RPC frame that carried the request into the facet)
55
+ * instead of the connection's native WebSocket.
56
+ */
57
+ function registerFacetStreamingDelivery(stream, replyTarget) {
58
+ facetStreamingResponseDeliveryStates.set(stream, {
59
+ replyTarget,
79
60
  pending: /* @__PURE__ */ new Set()
80
61
  });
81
- return stream;
82
62
  }
83
- function trackFacetStreamingResponseDelivery(stream, completion) {
63
+ /**
64
+ * Deliver one streamed RPC response for a facet-bridged stream, tracking
65
+ * its completion. Returns null when the stream is not facet-bridged (the
66
+ * caller should send on the native connection instead).
67
+ */
68
+ function sendFacetStreamingResponse(stream, response) {
84
69
  const state = facetStreamingResponseDeliveryStates.get(stream);
85
- if (!state) return;
86
- state.pending.add(completion);
87
- completion.finally(() => state.pending.delete(completion));
70
+ if (!state) return null;
71
+ const delivery = sendFacetRpcResponseIfOpen(state.replyTarget, response);
72
+ state.pending.add(delivery.completion);
73
+ delivery.completion.finally(() => state.pending.delete(delivery.completion));
74
+ return delivery.sent;
88
75
  }
89
76
  async function waitForFacetStreamingResponseDeliveries(stream) {
90
77
  const state = facetStreamingResponseDeliveryStates.get(stream);
@@ -95,37 +82,29 @@ async function waitForFacetStreamingResponseDeliveries(stream) {
95
82
  facetStreamingResponseDeliveryStates.delete(stream);
96
83
  }
97
84
  }
85
+ var _connection = /* @__PURE__ */ new WeakMap();
86
+ var _broadcast = /* @__PURE__ */ new WeakMap();
98
87
  /**
99
- * Type guard for RPC request messages
100
- */
101
- function isRPCRequest(msg) {
102
- 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);
103
- }
104
- /**
105
- * Type guard for state update messages
88
+ * Parent-side bridge handed to a facet over RPC: wraps a live root-owned
89
+ * `Connection` so the facet can send/close/setState on it, and carries
90
+ * the root's broadcast entry point for facet-scoped broadcasts.
106
91
  */
107
- function isStateUpdateMessage(msg) {
108
- return typeof msg === "object" && msg !== null && "type" in msg && msg.type === "cf_agent_state" && "state" in msg;
109
- }
110
- const callableMetadata = /* @__PURE__ */ new WeakMap();
111
- var _connection2 = /* @__PURE__ */ new WeakMap();
112
- var _broadcast = /* @__PURE__ */ new WeakMap();
113
- var SubAgentConnectionBridge = class extends RpcTarget {
92
+ var DynamicAgentConnectionBridge = class extends RpcTarget {
114
93
  constructor(connection, broadcast) {
115
94
  super();
116
- _classPrivateFieldInitSpec(this, _connection2, void 0);
95
+ _classPrivateFieldInitSpec(this, _connection, void 0);
117
96
  _classPrivateFieldInitSpec(this, _broadcast, void 0);
118
- _classPrivateFieldSet2(_connection2, this, connection);
97
+ _classPrivateFieldSet2(_connection, this, connection);
119
98
  _classPrivateFieldSet2(_broadcast, this, broadcast);
120
99
  }
121
100
  send(message) {
122
- _classPrivateFieldGet2(_connection2, this).send(message);
101
+ _classPrivateFieldGet2(_connection, this).send(message);
123
102
  }
124
103
  close(code, reason) {
125
- _classPrivateFieldGet2(_connection2, this).close(code, reason);
104
+ _classPrivateFieldGet2(_connection, this).close(code, reason);
126
105
  }
127
106
  setState(state) {
128
- return _classPrivateFieldGet2(_connection2, this).setState(state);
107
+ return _classPrivateFieldGet2(_connection, this).setState(state);
129
108
  }
130
109
  broadcast(ownerPath, message, without) {
131
110
  return _classPrivateFieldGet2(_broadcast, this)?.call(this, ownerPath, message, without);
@@ -133,58 +112,1118 @@ var SubAgentConnectionBridge = class extends RpcTarget {
133
112
  };
134
113
  var _root = /* @__PURE__ */ new WeakMap();
135
114
  var _connectionId = /* @__PURE__ */ new WeakMap();
136
- var RootSubAgentConnectionBridge = class {
115
+ /**
116
+ * Facet-side bridge used after the originating RPC frame has completed:
117
+ * routes connection operations back to the root over a fresh RPC call.
118
+ */
119
+ var RootDynamicAgentConnectionBridge = class {
137
120
  constructor(root, connectionId) {
138
121
  _classPrivateFieldInitSpec(this, _root, void 0);
139
122
  _classPrivateFieldInitSpec(this, _connectionId, void 0);
140
123
  _classPrivateFieldSet2(_root, this, root);
141
124
  _classPrivateFieldSet2(_connectionId, this, connectionId);
142
125
  }
143
- send(message) {
144
- return _classPrivateFieldGet2(_root, this)._cf_sendToSubAgentConnection(_classPrivateFieldGet2(_connectionId, this), message);
126
+ send(message) {
127
+ return _classPrivateFieldGet2(_root, this)._cf_sendToSubAgentConnection(_classPrivateFieldGet2(_connectionId, this), message);
128
+ }
129
+ close(code, reason) {
130
+ return _classPrivateFieldGet2(_root, this)._cf_closeSubAgentConnection(_classPrivateFieldGet2(_connectionId, this), code, reason);
131
+ }
132
+ setState(state) {
133
+ return _classPrivateFieldGet2(_root, this)._cf_setSubAgentConnectionState(_classPrivateFieldGet2(_connectionId, this), state);
134
+ }
135
+ broadcast(ownerPath, message, without) {
136
+ return _classPrivateFieldGet2(_root, this)._cf_broadcastToSubAgent(ownerPath, message, without);
137
+ }
138
+ };
139
+ //#endregion
140
+ //#region src/dynamic-agents/identity.ts
141
+ const SUB_AGENT_IDENTITY_VERSION_LEGACY = "legacy";
142
+ const SUB_AGENT_IDENTITY_VERSION_PATH_V2 = "path-v2";
143
+ const SUB_AGENT_IDENTITY_PATH_V2_PREFIX = "cf-agents:v2:";
144
+ async function sha256Hex(value) {
145
+ const bytes = new TextEncoder().encode(value);
146
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
147
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
148
+ }
149
+ function pathV2IdentityName(logicalName, digest) {
150
+ return `${SUB_AGENT_IDENTITY_PATH_V2_PREFIX}${encodeURIComponent(logicalName)}:${digest}`;
151
+ }
152
+ function logicalNameFromPathV2Identity(identityName) {
153
+ if (!identityName.startsWith("cf-agents:v2:")) return null;
154
+ const rest = identityName.slice(13);
155
+ const separator = rest.lastIndexOf(":");
156
+ if (separator === -1) return null;
157
+ try {
158
+ return decodeURIComponent(rest.slice(0, separator));
159
+ } catch {
160
+ return null;
161
+ }
162
+ }
163
+ /**
164
+ * Validate that a stored `parentPath` has the expected shape. Used
165
+ * when restoring from DO storage to guard against corrupted data.
166
+ */
167
+ function isValidParentPath(value) {
168
+ if (!Array.isArray(value)) return false;
169
+ return value.every((entry) => entry != null && typeof entry === "object" && typeof entry.className === "string" && typeof entry.name === "string");
170
+ }
171
+ function agentPathKey(path) {
172
+ if (!path) return null;
173
+ return path.map((step) => `${encodeURIComponent(step.className)}:${encodeURIComponent(step.name)}`).join("/");
174
+ }
175
+ //#endregion
176
+ //#region src/dynamic-agents/registry.ts
177
+ var _host$1 = /* @__PURE__ */ new WeakMap();
178
+ var _ready = /* @__PURE__ */ new WeakMap();
179
+ var _DynamicAgentRegistry_brand = /* @__PURE__ */ new WeakSet();
180
+ /**
181
+ * The parent-side registry of spawned dynamic agents (facets), stored
182
+ * in the parent's own SQLite. Backs `hasSubAgent` / `listSubAgents`
183
+ * and the identity-versioning decision (legacy bare-name facets vs
184
+ * path-scoped v2 identities).
185
+ *
186
+ * Table and column names are storage-frozen — never rename them.
187
+ */
188
+ var DynamicAgentRegistry = class {
189
+ constructor(host) {
190
+ _classPrivateMethodInitSpec(this, _DynamicAgentRegistry_brand);
191
+ _classPrivateFieldInitSpec(this, _host$1, void 0);
192
+ _classPrivateFieldInitSpec(this, _ready, false);
193
+ _classPrivateFieldSet2(_host$1, this, host);
194
+ }
195
+ ensure() {
196
+ if (_classPrivateFieldGet2(_ready, this)) return;
197
+ _classPrivateFieldGet2(_host$1, this).sql`
198
+ CREATE TABLE IF NOT EXISTS cf_agents_sub_agents (
199
+ class TEXT NOT NULL,
200
+ name TEXT NOT NULL,
201
+ created_at INTEGER NOT NULL,
202
+ identity_version TEXT,
203
+ identity_name TEXT,
204
+ PRIMARY KEY (class, name)
205
+ )
206
+ `;
207
+ _assertClassBrand(_DynamicAgentRegistry_brand, this, _addColumnIfNotExists).call(this, "ALTER TABLE cf_agents_sub_agents ADD COLUMN identity_version TEXT");
208
+ _assertClassBrand(_DynamicAgentRegistry_brand, this, _addColumnIfNotExists).call(this, "ALTER TABLE cf_agents_sub_agents ADD COLUMN identity_name TEXT");
209
+ _classPrivateFieldSet2(_ready, this, true);
210
+ }
211
+ record(className, name, identity) {
212
+ this.ensure();
213
+ _classPrivateFieldGet2(_host$1, this).sql`
214
+ INSERT OR IGNORE INTO cf_agents_sub_agents
215
+ (class, name, created_at, identity_version, identity_name)
216
+ VALUES
217
+ (${className}, ${name}, ${Date.now()}, ${identity.version}, ${identity.name})
218
+ `;
219
+ }
220
+ row(className, name) {
221
+ this.ensure();
222
+ return _classPrivateFieldGet2(_host$1, this).sql`
223
+ SELECT identity_version, identity_name
224
+ FROM cf_agents_sub_agents
225
+ WHERE class = ${className} AND name = ${name}
226
+ LIMIT 1
227
+ `[0] ?? null;
228
+ }
229
+ async identity(className, name, childPath) {
230
+ const row = this.row(className, name);
231
+ if (row) {
232
+ if (row.identity_version === "path-v2" && typeof row.identity_name === "string") return {
233
+ version: SUB_AGENT_IDENTITY_VERSION_PATH_V2,
234
+ name: row.identity_name,
235
+ existing: true
236
+ };
237
+ return {
238
+ version: SUB_AGENT_IDENTITY_VERSION_LEGACY,
239
+ name,
240
+ existing: true
241
+ };
242
+ }
243
+ return {
244
+ version: SUB_AGENT_IDENTITY_VERSION_PATH_V2,
245
+ name: pathV2IdentityName(name, await sha256Hex(JSON.stringify(childPath))),
246
+ existing: false
247
+ };
248
+ }
249
+ forget(className, name) {
250
+ this.ensure();
251
+ _classPrivateFieldGet2(_host$1, this).sql`
252
+ DELETE FROM cf_agents_sub_agents
253
+ WHERE class = ${className} AND name = ${name}
254
+ `;
255
+ }
256
+ has(className, name) {
257
+ this.ensure();
258
+ return (_classPrivateFieldGet2(_host$1, this).sql`
259
+ SELECT COUNT(*) AS n FROM cf_agents_sub_agents
260
+ WHERE class = ${className} AND name = ${name}
261
+ `[0]?.n ?? 0) > 0;
262
+ }
263
+ list(className) {
264
+ this.ensure();
265
+ return (className ? _classPrivateFieldGet2(_host$1, this).sql`
266
+ SELECT class, name, created_at FROM cf_agents_sub_agents
267
+ WHERE class = ${className}
268
+ ORDER BY created_at ASC
269
+ ` : _classPrivateFieldGet2(_host$1, this).sql`
270
+ SELECT class, name, created_at FROM cf_agents_sub_agents
271
+ ORDER BY created_at ASC
272
+ `).map((r) => ({
273
+ className: r.class,
274
+ name: r.name,
275
+ createdAt: r.created_at
276
+ }));
277
+ }
278
+ };
279
+ function _addColumnIfNotExists(sql) {
280
+ try {
281
+ _classPrivateFieldGet2(_host$1, this).execRawSql(sql);
282
+ } catch (e) {
283
+ if (!(e instanceof Error ? e.message : String(e)).toLowerCase().includes("duplicate column")) throw e;
284
+ }
285
+ }
286
+ //#endregion
287
+ //#region src/dynamic-agents/dynamic-agents.ts
288
+ /**
289
+ * Internal key used to remember the outer `/sub/...` URL for a
290
+ * WebSocket accepted by the parent on behalf of a child facet.
291
+ * Hibernated events then wake the parent, which forwards frames to
292
+ * the child over serializable RPC while keeping native WebSocket I/O
293
+ * parent-owned.
294
+ *
295
+ * Storage-frozen — never rename.
296
+ */
297
+ const CF_SUB_AGENT_OUTER_URL_KEY = "_cf_subAgentOuterUrl";
298
+ const CF_SUB_AGENT_TAGS_KEY = "_cf_subAgentTags";
299
+ /** Wire-frozen internal header carrying the outer URL on WS upgrades. */
300
+ const SUB_AGENT_OUTER_URL_HEADER = "x-cf-agents-subagent-url";
301
+ var _host = /* @__PURE__ */ new WeakMap();
302
+ var _facetKeepAliveTokens = /* @__PURE__ */ new WeakMap();
303
+ var _bridgeContext = /* @__PURE__ */ new WeakMap();
304
+ var _virtualConnections = /* @__PURE__ */ new WeakMap();
305
+ var _connectionOperationTails = /* @__PURE__ */ new WeakMap();
306
+ var _broadcastOperationTail = /* @__PURE__ */ new WeakMap();
307
+ var _DynamicAgentsInternal_brand = /* @__PURE__ */ new WeakSet();
308
+ /**
309
+ * The facet-backed dynamic-agent machinery, extracted from the Agent
310
+ * class. One instance per Agent, installed as a Lifecycle capability
311
+ * (`capabilityId: "dynamic-agents"`); the host port documents exactly
312
+ * which Agent internals it touches.
313
+ *
314
+ * The capability claims no runner hooks — four integration points are
315
+ * deliberately wired directly through the Agent composition root
316
+ * instead, because the runner's dispatch contract cannot express them:
317
+ * the `/sub/` upgrade path rewrites the request and *continues* into
318
+ * `lifecycle.fetch` (onRequest can only claim), forwarded WS frames run
319
+ * inside the host's onMessage wrapper *after* the WebSockets capability
320
+ * has claimed the wake, this module *implements* the lifecycle route
321
+ * transport rather than consuming it, and facet-context restore has
322
+ * load-bearing startup ordering inside the host's startup span.
323
+ *
324
+ * Nothing here renames any wire- or storage-visible identifier: the
325
+ * `cf_agents_facet_runs` table, `_cf_*` RPC method names, and route
326
+ * key formats are frozen.
327
+ *
328
+ * @internal
329
+ */
330
+ var DynamicAgentsInternal = class extends LifecycleCapability {
331
+ constructor(host) {
332
+ super("dynamic-agents");
333
+ _classPrivateMethodInitSpec(this, _DynamicAgentsInternal_brand);
334
+ _classPrivateFieldInitSpec(this, _host, void 0);
335
+ _classPrivateFieldInitSpec(this, _facetKeepAliveTokens, /* @__PURE__ */ new Set());
336
+ _classPrivateFieldInitSpec(this, _bridgeContext, new AsyncLocalStorage());
337
+ _classPrivateFieldInitSpec(this, _virtualConnections, /* @__PURE__ */ new Map());
338
+ _classPrivateFieldInitSpec(this, _connectionOperationTails, /* @__PURE__ */ new Map());
339
+ _classPrivateFieldInitSpec(this, _broadcastOperationTail, void 0);
340
+ _classPrivateFieldSet2(_host, this, host);
341
+ this.registry = new DynamicAgentRegistry({
342
+ sql: host.sql.bind(host),
343
+ execRawSql: (sql) => void host.ctx.storage.sql.exec(sql)
344
+ });
345
+ }
346
+ runRowsForPrefix(ownerPath) {
347
+ return _classPrivateFieldGet2(_host, this).sql`
348
+ SELECT owner_path, owner_path_key, run_id, created_at
349
+ FROM cf_agents_facet_runs
350
+ `.filter((row) => {
351
+ try {
352
+ const rowOwnerPath = JSON.parse(row.owner_path);
353
+ return _classPrivateFieldGet2(_host, this)._isSameAgentPathPrefix(ownerPath, rowOwnerPath);
354
+ } catch {
355
+ return false;
356
+ }
357
+ });
358
+ }
359
+ deleteRunRowsForPrefix(ownerPath) {
360
+ for (const row of this.runRowsForPrefix(ownerPath)) _classPrivateFieldGet2(_host, this).sql`
361
+ DELETE FROM cf_agents_facet_runs
362
+ WHERE owner_path_key = ${row.owner_path_key}
363
+ AND run_id = ${row.run_id}
364
+ `;
365
+ }
366
+ lifecycleRouteAddress() {
367
+ if (!_classPrivateFieldGet2(_host, this)._isFacet) return void 0;
368
+ const key = agentPathKey(_classPrivateFieldGet2(_host, this).selfPath);
369
+ return key ? {
370
+ key,
371
+ data: JSON.stringify(_classPrivateFieldGet2(_host, this).selfPath)
372
+ } : void 0;
373
+ }
374
+ async routeLifecycleToRoot(envelope) {
375
+ if (!_classPrivateFieldGet2(_host, this)._isFacet) return _classPrivateFieldGet2(_host, this).lifecycle.route(envelope);
376
+ return (await this.rootAlarmOwner())._cf_routeLifecycle(void 0, envelope);
377
+ }
378
+ async routeLifecycleToTarget(target, envelope) {
379
+ let targetPath;
380
+ try {
381
+ targetPath = JSON.parse(target.data);
382
+ } catch {
383
+ throw new Error("Lifecycle route target is not a valid Agent path");
384
+ }
385
+ const selfPath = _classPrivateFieldGet2(_host, this).selfPath;
386
+ if (!_classPrivateFieldGet2(_host, this)._isSameAgentPathPrefix(selfPath, targetPath)) throw new Error(`Lifecycle route does not descend from ${JSON.stringify(selfPath)}.`);
387
+ if (selfPath.length === targetPath.length) return _classPrivateFieldGet2(_host, this).lifecycle.route(envelope);
388
+ const next = targetPath[selfPath.length];
389
+ if (!_classPrivateFieldGet2(_host, this).hasSubAgent(next.className, next.name)) {
390
+ const stalePath = targetPath.slice(0, selfPath.length + 1);
391
+ if (_classPrivateFieldGet2(_host, this)._isFacet) await (await this.rootAlarmOwner())._cf_cleanupFacetPrefix(stalePath);
392
+ else await _classPrivateFieldGet2(_host, this)._cf_cleanupFacetPrefix(stalePath);
393
+ return false;
394
+ }
395
+ return (await _classPrivateFieldGet2(_host, this)._cf_resolveSubAgent(next.className, next.name))._cf_routeLifecycle(target, envelope);
396
+ }
397
+ /** Body of the single native-RPC aperture for routed Lifecycle capabilities. */
398
+ routeLifecycle(target, envelope) {
399
+ return target ? this.routeLifecycleToTarget(target, envelope) : _classPrivateFieldGet2(_host, this).lifecycle.route(envelope);
400
+ }
401
+ async rootAlarmOwner() {
402
+ const root = _classPrivateFieldGet2(_host, this)._parentPath[0];
403
+ if (!root) throw new Error("Facet routing requires a root parent.");
404
+ const binding = _classPrivateFieldGet2(_host, this).ctx.exports?.[root.className];
405
+ if (!binding) throw new Error(`Unable to resolve root "${root.className}" for facet routing.`);
406
+ return await getAgentByName(binding, root.name);
407
+ }
408
+ rootResolvesToSelf() {
409
+ const root = _classPrivateFieldGet2(_host, this)._parentPath[0];
410
+ if (!root) return false;
411
+ const binding = _classPrivateFieldGet2(_host, this).ctx.exports?.[root.className];
412
+ if (!binding?.idFromName) return false;
413
+ return binding.idFromName(root.name).equals(_classPrivateFieldGet2(_host, this).ctx.id);
414
+ }
415
+ /**
416
+ * Clean root-owned bookkeeping for a sub-tree of facets: bulk-cancel
417
+ * schedules, queue items, and routed Task wake mirrors under the
418
+ * owner-path prefix, and delete root-side facet fiber recovery leases for
419
+ * the same sub-tree.
420
+ */
421
+ async cleanupPrefix(ownerPath) {
422
+ const prefix = agentPathKey(ownerPath);
423
+ if (prefix) {
424
+ await _classPrivateFieldGet2(_host, this).scheduler.__DO_NOT_USE_WILL_BREAK__cleanupRoutePrefix(prefix);
425
+ await _classPrivateFieldGet2(_host, this).tasks.__DO_NOT_USE_WILL_BREAK__cleanupRoutePrefix(prefix);
426
+ await _classPrivateFieldGet2(_host, this)._queue.__DO_NOT_USE_WILL_BREAK__cleanupRoutePrefix(prefix);
427
+ }
428
+ this.deleteRunRowsForPrefix(ownerPath);
429
+ await _classPrivateFieldGet2(_host, this)._syncHostJobs();
430
+ }
431
+ /**
432
+ * Acquire a root-owned keepAlive ref on behalf of a descendant facet.
433
+ */
434
+ async acquireKeepAlive(ownerPath) {
435
+ const token = `${agentPathKey(ownerPath) ?? "unknown"}:${nanoid(9)}`;
436
+ _classPrivateFieldGet2(_facetKeepAliveTokens, this).add(token);
437
+ _classPrivateFieldGet2(_host, this)._keepAliveRefs++;
438
+ if (_classPrivateFieldGet2(_host, this)._keepAliveRefs === 1) await _classPrivateFieldGet2(_host, this)._syncHostJobs();
439
+ return token;
440
+ }
441
+ /**
442
+ * Release a root-owned keepAlive ref previously acquired for a facet.
443
+ * Idempotent so disposer calls can safely race or run twice.
444
+ */
445
+ async releaseKeepAlive(token) {
446
+ if (!_classPrivateFieldGet2(_facetKeepAliveTokens, this).delete(token)) return;
447
+ _classPrivateFieldGet2(_host, this)._keepAliveRefs = Math.max(0, _classPrivateFieldGet2(_host, this)._keepAliveRefs - 1);
448
+ await _classPrivateFieldGet2(_host, this)._syncHostJobs();
449
+ }
450
+ /**
451
+ * Register a facet's durable run row in the root-side index so root
452
+ * alarm housekeeping can dispatch recovery checks into idle facets.
453
+ */
454
+ async registerRun(ownerPath, runId) {
455
+ const ownerPathJson = JSON.stringify(ownerPath);
456
+ const ownerPathKey = agentPathKey(ownerPath);
457
+ if (!ownerPathKey) throw new Error("_cf_registerFacetRun requires a non-empty owner path.");
458
+ _classPrivateFieldGet2(_host, this).sql`
459
+ INSERT OR REPLACE INTO cf_agents_facet_runs
460
+ (owner_path, owner_path_key, run_id, created_at)
461
+ VALUES
462
+ (${ownerPathJson}, ${ownerPathKey}, ${runId}, ${Date.now()})
463
+ `;
464
+ await _classPrivateFieldGet2(_host, this)._syncHostJobs();
465
+ }
466
+ /**
467
+ * Root-side scan for durable fibers owned by descendant facets.
468
+ * `cf_agents_facet_runs` is only an index; actual snapshots and
469
+ * recovery hooks live in each facet's own `cf_agents_runs` table.
470
+ */
471
+ async checkRunFibers() {
472
+ if (_classPrivateFieldGet2(_host, this)._parentPath.length > 0) return;
473
+ const rows = _classPrivateFieldGet2(_host, this).sql`
474
+ SELECT owner_path, owner_path_key, run_id, created_at
475
+ FROM cf_agents_facet_runs
476
+ ORDER BY created_at ASC
477
+ `;
478
+ const firstRowByOwner = /* @__PURE__ */ new Map();
479
+ for (const row of rows) if (!firstRowByOwner.has(row.owner_path_key)) firstRowByOwner.set(row.owner_path_key, row);
480
+ for (const row of firstRowByOwner.values()) {
481
+ let ownerPath;
482
+ try {
483
+ ownerPath = JSON.parse(row.owner_path);
484
+ } catch (e) {
485
+ console.warn(`[Agent] Corrupted facet fiber owner path for ${row.owner_path_key}; pruning stale lease.`, e);
486
+ _classPrivateFieldGet2(_host, this).sql`
487
+ DELETE FROM cf_agents_facet_runs
488
+ WHERE owner_path_key = ${row.owner_path_key}
489
+ `;
490
+ continue;
491
+ }
492
+ try {
493
+ if (await _classPrivateFieldGet2(_host, this)._cf_checkRunFibersForFacet(ownerPath) === 0) _classPrivateFieldGet2(_host, this).sql`
494
+ DELETE FROM cf_agents_facet_runs
495
+ WHERE owner_path_key = ${row.owner_path_key}
496
+ `;
497
+ } catch (e) {
498
+ console.error(`[Agent] Facet fiber recovery check failed for ${row.owner_path_key}:`, e);
499
+ }
500
+ }
501
+ }
502
+ /**
503
+ * Dispatch a runFiber recovery check into the facet identified by
504
+ * `ownerPath`. Returns the number of remaining local `cf_agents_runs`
505
+ * rows on the target facet after recovery.
506
+ */
507
+ async checkRunFibersAtPath(ownerPath) {
508
+ const selfPath = _classPrivateFieldGet2(_host, this).selfPath;
509
+ if (!_classPrivateFieldGet2(_host, this)._isSameAgentPathPrefix(selfPath, ownerPath)) throw new Error(`Facet fiber owner path does not descend from ${JSON.stringify(selfPath)}.`);
510
+ if (selfPath.length === ownerPath.length) {
511
+ await _classPrivateFieldGet2(_host, this)._checkRunFibers();
512
+ return _classPrivateFieldGet2(_host, this).sql`
513
+ SELECT COUNT(*) as count FROM cf_agents_runs
514
+ `[0]?.count ?? 0;
515
+ }
516
+ const next = ownerPath[selfPath.length];
517
+ if (!_classPrivateFieldGet2(_host, this).hasSubAgent(next.className, next.name)) return 0;
518
+ return (await this.resolve(next.className, next.name))._cf_checkRunFibersForFacet(ownerPath);
519
+ }
520
+ /**
521
+ * Invoke an RPC method on the host Agent or a descendant facet
522
+ * identified by a root-first path. Used by AgentWorkflow to route
523
+ * callbacks and `this.agent` calls back to the exact sub-agent that
524
+ * started a workflow.
525
+ */
526
+ async invokeAgentPath(targetPath, method, args) {
527
+ await _classPrivateFieldGet2(_host, this).__unsafe_ensureInitialized();
528
+ const selfPath = _classPrivateFieldGet2(_host, this).selfPath;
529
+ if (!_classPrivateFieldGet2(_host, this)._isSameAgentPathPrefix(selfPath, targetPath)) throw new Error(`Workflow origin path does not descend from ${JSON.stringify(selfPath)}.`);
530
+ if (selfPath.length === targetPath.length) {
531
+ const fn = _classPrivateFieldGet2(_host, this)[method];
532
+ if (isInternalJsStubProp(method) || method in Object.prototype || typeof fn !== "function") throw new Error(`Workflow origin method "${method}" is not callable on ${_classPrivateFieldGet2(_host, this).constructor.name}.`);
533
+ return await fn.apply(_classPrivateFieldGet2(_host, this), args);
534
+ }
535
+ const next = targetPath[selfPath.length];
536
+ if (!_classPrivateFieldGet2(_host, this).hasSubAgent(next.className, next.name)) throw new Error(`Workflow origin sub-agent ${next.className} "${next.name}" no longer exists.`);
537
+ return await (await this.resolve(next.className, next.name))._cf_invokeAgentPath(targetPath, method, args);
538
+ }
539
+ /**
540
+ * Recursively destroy a descendant facet identified by `targetPath`.
541
+ * Walks down from `selfPath` until reaching the target's immediate
542
+ * parent, where it cancels the target's parent-owned schedules (and
543
+ * any descendants), removes the target from the registry, and calls
544
+ * `ctx.facets.delete` to wipe the target's storage.
545
+ */
546
+ async destroyDescendant(targetPath) {
547
+ const selfPath = _classPrivateFieldGet2(_host, this).selfPath;
548
+ if (targetPath.length === 0) throw new Error("_cf_destroyDescendantFacet: target path must not be empty.");
549
+ if (selfPath.length >= targetPath.length) throw new Error("_cf_destroyDescendantFacet: target must be a strict descendant.");
550
+ if (!_classPrivateFieldGet2(_host, this)._isSameAgentPathPrefix(selfPath, targetPath)) throw new Error("_cf_destroyDescendantFacet: target path does not descend from this agent.");
551
+ if (_classPrivateFieldGet2(_host, this)._parentPath.length === 0) await _classPrivateFieldGet2(_host, this)._cf_cleanupFacetPrefix(targetPath);
552
+ if (selfPath.length === targetPath.length - 1) {
553
+ const target = targetPath[targetPath.length - 1];
554
+ const ctx = _classPrivateFieldGet2(_host, this).ctx;
555
+ 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.");
556
+ try {
557
+ ctx.facets.delete(`${target.className}\0${target.name}`);
558
+ } catch {}
559
+ this.registry.forget(target.className, target.name);
560
+ return;
561
+ }
562
+ const next = targetPath[selfPath.length];
563
+ if (!_classPrivateFieldGet2(_host, this).hasSubAgent(next.className, next.name)) return;
564
+ await (await this.resolve(next.className, next.name))._cf_destroyDescendantFacet(targetPath);
565
+ }
566
+ /**
567
+ * Shared facet resolution — takes a CamelCase class name string
568
+ * (matching `ctx.exports`) rather than a class reference. Both
569
+ * `subAgent(cls, name)` and `_cf_invokeSubAgent(className, ...)`
570
+ * funnel through here so registry bookkeeping and the
571
+ * `_cf_initAsFacet` handshake are consistent.
572
+ */
573
+ async resolve(className, name) {
574
+ const ctx = _classPrivateFieldGet2(_host, this).ctx;
575
+ if (!ctx.facets || !ctx.exports) throw new Error("subAgent() is not supported in this runtime — `ctx.facets` / `ctx.exports` are unavailable. Update to the latest `compatibility_date` in your wrangler.jsonc.");
576
+ if (camelCaseToKebabCase(className) === "sub") throw new Error(`Sub-agent class name "${className}" kebab-cases to "sub", which collides with the reserved URL separator — rename the class (e.g. "SubThing" or "Subtask").`);
577
+ const Cls = ctx.exports[className];
578
+ if (!Cls) throw new Error(`Sub-agent class "${className}" not found in worker exports. Make sure the class is exported from your worker entry point and that the export name matches the class name.`);
579
+ if (name.includes("\0")) throw new Error(`Sub-agent name contains null character (\\0), which is reserved.`);
580
+ const facetKey = `${className}\0${name}`;
581
+ const childParentPath = _classPrivateFieldGet2(_host, this).selfPath;
582
+ const childPath = [...childParentPath, {
583
+ className,
584
+ name
585
+ }];
586
+ const rootClassName = _classPrivateFieldGet2(_host, this)._parentPath[0]?.className ?? _classPrivateFieldGet2(_host, this).constructor.name;
587
+ const rootNs = ctx.exports[rootClassName];
588
+ if (!rootNs?.idFromName) {
589
+ const minificationHint = /^_*[a-z][a-z0-9]{0,2}$/.test(rootClassName) ? ` The class name "${rootClassName}" looks minified — make sure your bundler preserves class names (e.g. esbuild's \`keepNames: true\`).` : "";
590
+ throw new Error(`Sub-agent bootstrap requires the root agent class "${rootClassName}" to be available as a Durable Object namespace, but ctx.exports["${rootClassName}"] is missing or doesn't expose idFromName.${minificationHint} Make sure the root agent class is exported under that class name and registered in your wrangler.jsonc durable_objects.bindings.`);
591
+ }
592
+ const identity = await this.registry.identity(className, name, childPath);
593
+ const facetId = rootNs.idFromName(identity.name);
594
+ const stub = ctx.facets.get(facetKey, () => ({
595
+ class: Cls,
596
+ id: facetId
597
+ }));
598
+ this.registry.record(className, name, identity);
599
+ try {
600
+ await _classPrivateFieldGet2(_host, this)._runFacetInitInvocation(async () => {
601
+ await stub._cf_initAsFacet(name, childParentPath, identity.name);
602
+ });
603
+ } catch (error) {
604
+ if (!identity.existing) this.registry.forget(className, name);
605
+ throw error;
606
+ }
607
+ return stub;
608
+ }
609
+ /**
610
+ * Forcefully abort a running facet. Transitively aborts the child's
611
+ * own children; storage is preserved.
612
+ */
613
+ abort(className, name, reason) {
614
+ const ctx = _classPrivateFieldGet2(_host, this).ctx;
615
+ if (!ctx.facets) throw new Error("abort() is not supported in this runtime — `ctx.facets` is unavailable. Update to the latest `compatibility_date` in your wrangler.jsonc.");
616
+ const facetKey = `${className}\0${name}`;
617
+ ctx.facets.abort(facetKey, reason);
618
+ }
619
+ /**
620
+ * Delete a facet: abort it if running, then permanently wipe its
621
+ * storage. Transitively deletes the child's own children.
622
+ */
623
+ async delete(className, name) {
624
+ const ctx = _classPrivateFieldGet2(_host, this).ctx;
625
+ if (!ctx.facets) throw new Error("delete() is not supported in this runtime — `ctx.facets` is unavailable. Update to the latest `compatibility_date` in your wrangler.jsonc.");
626
+ const facetKey = `${className}\0${name}`;
627
+ const childPath = [..._classPrivateFieldGet2(_host, this).selfPath, {
628
+ className,
629
+ name
630
+ }];
631
+ if (_classPrivateFieldGet2(_host, this)._isFacet) await (await this.rootAlarmOwner())._cf_cleanupFacetPrefix(childPath);
632
+ else await _classPrivateFieldGet2(_host, this)._cf_cleanupFacetPrefix(childPath);
633
+ try {
634
+ ctx.facets.delete(facetKey);
635
+ } catch {}
636
+ this.registry.forget(className, name);
637
+ }
638
+ /** Drop all facet-side virtual connections (test/rehydration hook). */
639
+ clearVirtualConnections() {
640
+ _classPrivateFieldGet2(_virtualConnections, this).clear();
641
+ }
642
+ /** Facet-side lookup of a virtual connection by id. */
643
+ getVirtualConnection(id) {
644
+ const stored = _classPrivateFieldGet2(_virtualConnections, this).get(id);
645
+ if (!stored) return void 0;
646
+ return this.createBridgeConnection(stored.meta);
647
+ }
648
+ /** Facet-side iteration over virtual connections, optionally by tag. */
649
+ *getVirtualConnections(tag) {
650
+ for (const stored of _classPrivateFieldGet2(_virtualConnections, this).values()) if (!tag || stored.meta.tags.includes(tag)) yield this.createBridgeConnection(stored.meta);
651
+ }
652
+ activeBridge(connectionId) {
653
+ const context = _classPrivateFieldGet2(_bridgeContext, this).getStore();
654
+ if (connectionId !== void 0 && context?.connectionId !== connectionId) return;
655
+ return context?.bridge;
656
+ }
657
+ /**
658
+ * Route a virtual sub-agent connection operation through its live frame
659
+ * bridge, or through the durable root Agent after that frame completes.
660
+ * All operations share one per-connection queue. Facet broadcasts wait for
661
+ * older queued operations; failures do not block later work.
662
+ */
663
+ routeConnectionOperation(connectionId, operationName, operation) {
664
+ const activeBridge = this.activeBridge(connectionId);
665
+ const previousConnectionOperation = _classPrivateFieldGet2(_connectionOperationTails, this).get(connectionId);
666
+ let pending;
667
+ if (activeBridge && !previousConnectionOperation) try {
668
+ pending = Promise.resolve(operation(activeBridge)).then(() => {});
669
+ } catch (error) {
670
+ pending = Promise.reject(error);
671
+ }
672
+ else pending = (previousConnectionOperation ?? Promise.resolve()).then(async () => {
673
+ await operation(new RootDynamicAgentConnectionBridge(await this.rootAlarmOwner(), connectionId));
674
+ });
675
+ const completion = pending.catch((error) => {
676
+ _assertClassBrand(_DynamicAgentsInternal_brand, this, _reportConnectionOperationFailure).call(this, connectionId, operationName, error);
677
+ });
678
+ _classPrivateFieldGet2(_connectionOperationTails, this).set(connectionId, completion);
679
+ _classPrivateFieldGet2(_host, this).ctx.waitUntil(completion);
680
+ completion.then(() => {
681
+ if (_classPrivateFieldGet2(_connectionOperationTails, this).get(connectionId) === completion) _classPrivateFieldGet2(_connectionOperationTails, this).delete(connectionId);
682
+ });
683
+ }
684
+ /**
685
+ * Route a facet broadcast after every older connection operation.
686
+ *
687
+ * This barrier is intentionally one-way: facet startup can broadcast before
688
+ * a child connection has finished initializing its tags and protocol flags.
689
+ * Making those later connection operations wait would let the next frame
690
+ * observe stale root-owned metadata.
691
+ */
692
+ async routeBroadcast(ownerPath, message, without, upstreamBridge) {
693
+ const activeBridge = upstreamBridge ?? this.activeBridge();
694
+ const previousOperations = /* @__PURE__ */ new Set([..._classPrivateFieldGet2(_broadcastOperationTail, this) ? [_classPrivateFieldGet2(_broadcastOperationTail, this)] : [], ..._classPrivateFieldGet2(_connectionOperationTails, this).values()]);
695
+ let pending;
696
+ if (activeBridge && previousOperations.size === 0) try {
697
+ pending = Promise.resolve(activeBridge.broadcast(ownerPath, message, without));
698
+ } catch (error) {
699
+ pending = Promise.reject(error);
700
+ }
701
+ else pending = Promise.all(previousOperations).then(async () => {
702
+ await (await this.rootAlarmOwner())._cf_broadcastToSubAgent(ownerPath, message, without);
703
+ });
704
+ const completion = pending.catch((error) => {
705
+ console.error("[Agent] Sub-agent broadcast operation failed:", {
706
+ operation: "broadcast",
707
+ error
708
+ });
709
+ });
710
+ _classPrivateFieldSet2(_broadcastOperationTail, this, completion);
711
+ _classPrivateFieldGet2(_host, this).ctx.waitUntil(completion);
712
+ completion.then(() => {
713
+ if (_classPrivateFieldGet2(_broadcastOperationTail, this) === completion) _classPrivateFieldSet2(_broadcastOperationTail, this, void 0);
714
+ });
715
+ await completion;
716
+ }
717
+ async broadcastToParent(message, without) {
718
+ await this.routeBroadcast(_classPrivateFieldGet2(_host, this).selfPath, message, without);
719
+ }
720
+ async broadcastToPath(ownerPath, message, without) {
721
+ if (_classPrivateFieldGet2(_host, this)._isFacet) {
722
+ await this.routeBroadcast(ownerPath, message, without);
723
+ return;
724
+ }
725
+ for (const connection of _classPrivateFieldGet2(_host, this)._webSockets.getConnections()) {
726
+ if (without?.includes(connection.id)) continue;
727
+ const targetPath = this.connectionTargetPath(connection);
728
+ if (!targetPath) continue;
729
+ if (!this.isSameAgentPath(targetPath, ownerPath)) continue;
730
+ connection.send(message);
731
+ }
732
+ }
733
+ async connectionMetas(ownerPath) {
734
+ const metas = [];
735
+ for (const connection of _classPrivateFieldGet2(_host, this)._webSockets.getConnections()) {
736
+ const meta = _assertClassBrand(_DynamicAgentsInternal_brand, this, _connectionMetaForPath).call(this, connection, ownerPath);
737
+ if (meta) metas.push(meta);
738
+ }
739
+ return metas;
740
+ }
741
+ async sendToConnection(connectionId, message) {
742
+ const connection = _classPrivateFieldGet2(_host, this)._webSockets.getConnection(connectionId);
743
+ if (!connection || !this.connectionHasChildTarget(connection)) return;
744
+ connection.send(message);
745
+ }
746
+ async closeConnection(connectionId, code, reason) {
747
+ const connection = _classPrivateFieldGet2(_host, this)._webSockets.getConnection(connectionId);
748
+ if (!connection || !this.connectionHasChildTarget(connection)) return;
749
+ connection.close(code, reason);
750
+ }
751
+ async setConnectionState(connectionId, state) {
752
+ const connection = _classPrivateFieldGet2(_host, this)._webSockets.getConnection(connectionId);
753
+ if (!connection || !this.connectionHasChildTarget(connection)) return null;
754
+ ensureConnectionWrapped(connection);
755
+ connection.setState(state);
756
+ return this.getForwardedState(connection);
757
+ }
758
+ connectionTargetPath(connection) {
759
+ ensureConnectionWrapped(connection);
760
+ const outerUri = _classPrivateFieldGet2(_host, this)._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY);
761
+ if (typeof outerUri !== "string") return null;
762
+ return _assertClassBrand(_DynamicAgentsInternal_brand, this, _pathFromOuterUri).call(this, outerUri)?.path ?? null;
763
+ }
764
+ isSameAgentPath(a, b) {
765
+ if (a.length !== b.length) return false;
766
+ return a.every((step, index) => step.className === b[index]?.className && step.name === b[index]?.name);
767
+ }
768
+ connectionHasChildTarget(connection) {
769
+ ensureConnectionWrapped(connection);
770
+ return typeof _classPrivateFieldGet2(_host, this)._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY) === "string";
771
+ }
772
+ connectionTargetsChild(connection) {
773
+ if (!connection.uri) return false;
774
+ const ctx = _classPrivateFieldGet2(_host, this).ctx;
775
+ return parseSubAgentPath(connection.uri, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 }) !== null;
776
+ }
777
+ requestTargetsChild(request) {
778
+ const ctx = _classPrivateFieldGet2(_host, this).ctx;
779
+ return parseSubAgentPath(request.url, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 }) !== null;
780
+ }
781
+ async forwardWebSocketConnect(connection, request, options) {
782
+ const routed = await _assertClassBrand(_DynamicAgentsInternal_brand, this, _resolveConnection).call(this, connection, request, options);
783
+ if (!routed) return false;
784
+ await routed.child._cf_handleSubAgentWebSocketConnect(_assertClassBrand(_DynamicAgentsInternal_brand, this, _createConnectionBridge).call(this, connection), routed.meta);
785
+ return true;
786
+ }
787
+ async forwardWebSocketMessage(connection, message, replyBridge) {
788
+ const routed = await _assertClassBrand(_DynamicAgentsInternal_brand, this, _resolveConnection).call(this, connection);
789
+ if (!routed) return false;
790
+ const bridge = _assertClassBrand(_DynamicAgentsInternal_brand, this, _createConnectionBridge).call(this, connection);
791
+ await routed.child._cf_handleSubAgentWebSocketMessage(message, bridge, routed.meta, replyBridge ?? bridge);
792
+ return true;
793
+ }
794
+ async forwardWebSocketClose(connection, code, reason, wasClean) {
795
+ const routed = await _assertClassBrand(_DynamicAgentsInternal_brand, this, _resolveConnection).call(this, connection);
796
+ if (!routed) return false;
797
+ await routed.child._cf_handleSubAgentWebSocketClose(code, reason, wasClean, _assertClassBrand(_DynamicAgentsInternal_brand, this, _createConnectionBridge).call(this, connection), routed.meta);
798
+ return true;
799
+ }
800
+ async handleWebSocketConnect(bridge, meta) {
801
+ await this.runWithBridge(bridge, meta.id, async () => {
802
+ const connection = this.createBridgeConnection(meta);
803
+ const request = new Request(meta.uri ?? "http://placeholder/", { headers: meta.requestHeaders });
804
+ if (await this.forwardWebSocketConnect(connection, request, { gate: true })) return;
805
+ if (_classPrivateFieldGet2(_host, this).shouldConnectionBeReadonly(connection, { request })) _classPrivateFieldGet2(_host, this).setConnectionReadonly(connection, true);
806
+ if (!_classPrivateFieldGet2(_host, this).shouldSendProtocolMessages(connection, { request })) setConnectionProtocolEnabled(connection, false);
807
+ const childTags = await _classPrivateFieldGet2(_host, this).getConnectionTags(connection, { request });
808
+ connection.tags = [connection.id, ...childTags.filter((tag) => tag !== connection.id)];
809
+ this.storeVirtualConnection(connection);
810
+ await _classPrivateFieldGet2(_host, this).onConnect(connection, { request });
811
+ this.storeVirtualConnection(connection);
812
+ await _classPrivateFieldGet2(_connectionOperationTails, this).get(meta.id);
813
+ });
814
+ }
815
+ async handleWebSocketMessage(message, bridge, meta, replyBridge = bridge) {
816
+ const connection = this.createBridgeConnection(meta);
817
+ this.storeVirtualConnection(connection);
818
+ const replyContext = { bridge: replyBridge };
819
+ try {
820
+ await dynamicAgentRpcReplyContext.run(replyContext, () => this.runWithBridge(bridge, meta.id, () => _classPrivateFieldGet2(_host, this).onMessage(connection, message)));
821
+ } finally {
822
+ replyContext.bridge = void 0;
823
+ }
824
+ }
825
+ async handleWebSocketClose(code, reason, wasClean, bridge, meta) {
826
+ const connection = this.createBridgeConnection(meta);
827
+ this.storeVirtualConnection(connection);
828
+ await this.runWithBridge(bridge, meta.id, () => _classPrivateFieldGet2(_host, this).onClose(connection, code, reason, wasClean));
829
+ _classPrivateFieldGet2(_virtualConnections, this).delete(meta.id);
830
+ }
831
+ async runWithBridge(bridge, connectionId, fn) {
832
+ const context = {
833
+ bridge,
834
+ connectionId
835
+ };
836
+ try {
837
+ return await _classPrivateFieldGet2(_bridgeContext, this).run(context, fn);
838
+ } finally {
839
+ context.bridge = void 0;
840
+ }
841
+ }
842
+ createBridgeConnection(meta) {
843
+ let stored = _classPrivateFieldGet2(_virtualConnections, this).get(meta.id);
844
+ if (stored) {
845
+ stored.meta = meta;
846
+ if (stored.connection) {
847
+ stored.connection.uri = meta.uri;
848
+ stored.connection.tags = meta.tags;
849
+ return stored.connection;
850
+ }
851
+ } else {
852
+ stored = { meta };
853
+ _classPrivateFieldGet2(_virtualConnections, this).set(meta.id, stored);
854
+ }
855
+ const owner = this;
856
+ const getStored = () => _classPrivateFieldGet2(_virtualConnections, this).get(meta.id) ?? stored;
857
+ const updateStoredState = (nextState) => {
858
+ const current = _classPrivateFieldGet2(_virtualConnections, this).get(meta.id);
859
+ if (current) current.meta = {
860
+ ...current.meta,
861
+ state: nextState
862
+ };
863
+ };
864
+ const connection = {
865
+ id: meta.id,
866
+ uri: meta.uri,
867
+ tags: meta.tags,
868
+ get state() {
869
+ return getStored().meta.state;
870
+ },
871
+ setState(next) {
872
+ const currentState = getStored().meta.state;
873
+ const state = typeof next === "function" ? next(currentState) : next;
874
+ updateStoredState(state);
875
+ owner.routeConnectionOperation(meta.id, "setState", (bridge) => bridge.setState(state));
876
+ return state;
877
+ },
878
+ send(message) {
879
+ owner.routeConnectionOperation(meta.id, "send", (bridge) => bridge.send(message));
880
+ },
881
+ close(code, reason) {
882
+ owner.routeConnectionOperation(meta.id, "close", (bridge) => bridge.close(code, reason));
883
+ },
884
+ addEventListener() {},
885
+ removeEventListener() {}
886
+ };
887
+ stored.connection = connection;
888
+ ensureConnectionWrapped(connection);
889
+ return connection;
890
+ }
891
+ storeVirtualConnection(connection) {
892
+ _classPrivateFieldGet2(_host, this)._unsafe_setConnectionFlag(connection, CF_SUB_AGENT_TAGS_KEY, [...connection.tags]);
893
+ const stored = _classPrivateFieldGet2(_virtualConnections, this).get(connection.id);
894
+ _classPrivateFieldGet2(_virtualConnections, this).set(connection.id, {
895
+ meta: {
896
+ id: connection.id,
897
+ uri: connection.uri,
898
+ tags: [...connection.tags],
899
+ state: this.getRawConnectionState(connection)
900
+ },
901
+ connection: stored?.connection ?? connection
902
+ });
903
+ }
904
+ /**
905
+ * Restore the facet identity persisted by `init` (wake after
906
+ * hibernation), then best-effort hydrate the virtual connections
907
+ * from the root's WebSocket state.
908
+ */
909
+ async restoreFacetContext() {
910
+ if (await _classPrivateFieldGet2(_host, this).ctx.storage.get("cf_agents_is_facet")) _classPrivateFieldGet2(_host, this)._isFacet = true;
911
+ const storedFacetName = await _classPrivateFieldGet2(_host, this).ctx.storage.get("cf_agents_facet_name");
912
+ if (typeof storedFacetName === "string") _classPrivateFieldGet2(_host, this)._facetName = storedFacetName;
913
+ const storedParentPath = await _classPrivateFieldGet2(_host, this).ctx.storage.get("cf_agents_parent_path");
914
+ if (isValidParentPath(storedParentPath)) _classPrivateFieldGet2(_host, this)._parentPath = storedParentPath;
915
+ try {
916
+ await this.hydrateConnectionsFromRoot();
917
+ } catch (error) {
918
+ console.warn("[Agent] Unable to hydrate sub-agent WebSocket connections:", error);
919
+ }
920
+ }
921
+ async hydrateConnectionsFromRoot() {
922
+ if (!_classPrivateFieldGet2(_host, this)._isFacet || _classPrivateFieldGet2(_host, this)._parentPath.length === 0) return;
923
+ if (this.rootResolvesToSelf()) return;
924
+ const metas = await (await this.rootAlarmOwner())._cf_subAgentConnectionMetas(_classPrivateFieldGet2(_host, this).selfPath);
925
+ for (const meta of metas) _classPrivateFieldGet2(_virtualConnections, this).set(meta.id, { meta });
926
+ }
927
+ getRawConnectionState(connection) {
928
+ return getConnectionRawState(connection);
929
+ }
930
+ getForwardedState(connection) {
931
+ const raw = this.getRawConnectionState(connection);
932
+ if (raw == null || typeof raw !== "object") return raw;
933
+ const { [CF_SUB_AGENT_OUTER_URL_KEY]: _, ...rest } = raw;
934
+ return Object.keys(rest).length > 0 ? rest : null;
935
+ }
936
+ /**
937
+ * Resolve the facet Fetcher for the match and forward the request to
938
+ * it with `/sub/{class}/{name}` stripped.
939
+ */
940
+ async forward(req, match) {
941
+ let fetcher;
942
+ try {
943
+ fetcher = await this.resolve(match.childClass, match.childName);
944
+ } catch (err) {
945
+ const message = err instanceof Error ? err.message : String(err);
946
+ console.error("[agents] sub-agent route failed:", message);
947
+ if (/null character/i.test(message) || /reserved/i.test(message)) return new Response("Bad Request", { status: 400 });
948
+ return new Response("Not Found", { status: 404 });
949
+ }
950
+ const rewritten = new URL(req.url);
951
+ rewritten.pathname = match.remainingPath;
952
+ const forwardedHeaders = new Headers(req.headers);
953
+ const forwardedInit = {
954
+ method: req.method,
955
+ headers: forwardedHeaders
956
+ };
957
+ if (req.headers.get("Upgrade")?.toLowerCase() === "websocket") forwardedHeaders.set(SUB_AGENT_OUTER_URL_HEADER, req.url);
958
+ if (req.body && req.method !== "GET" && req.method !== "HEAD") forwardedInit.body = req.body;
959
+ const forwarded = new Request(rewritten, forwardedInit);
960
+ return fetcher.fetch(forwarded);
961
+ }
962
+ /**
963
+ * Bridge used by `getSubAgentByName`: resolve the facet and dispatch
964
+ * one RPC method. Stateless — no cached references.
965
+ */
966
+ async invoke(className, name, method, args) {
967
+ const stub = await this.resolve(className, name);
968
+ return await this.invokeStubMethod(stub, className, method, args);
969
+ }
970
+ /**
971
+ * Bridge used by `parentAgent()` when the requested parent is itself
972
+ * a facet (and therefore has no top-level env namespace). The root
973
+ * receives the full root-first target path, then each hop delegates
974
+ * to the next facet using that facet's own `ctx.facets`.
975
+ */
976
+ async invokePath(path, method, args) {
977
+ const [self, next, ...rest] = path;
978
+ if (!self) throw new Error(`Sub-agent path invocation requires a non-empty path.`);
979
+ const ownClassName = _classPrivateFieldGet2(_host, this).constructor.name;
980
+ if (self.className !== ownClassName || self.name !== _classPrivateFieldGet2(_host, this).name) throw new Error(`Sub-agent path invocation reached ${ownClassName}("${_classPrivateFieldGet2(_host, this).name}") but expected ${self.className}("${self.name}").`);
981
+ if (!next) return await this.invokeStubMethod(_classPrivateFieldGet2(_host, this), ownClassName, method, args);
982
+ const child = await this.resolve(next.className, next.name);
983
+ if (rest.length === 0) return await this.invokeStubMethod(child, next.className, method, args);
984
+ return await child._cf_invokeSubAgentPath([next, ...rest], method, args);
985
+ }
986
+ async invokeStubMethod(stub, className, method, args) {
987
+ const handle = stub;
988
+ if (typeof handle[method] !== "function") throw new Error(`Method "${method}" not found on ${className}.`);
989
+ return await handle[method](...args);
990
+ }
991
+ /**
992
+ * Initialize the host agent as a facet in a single RPC. Runs entirely
993
+ * inside the child's isolate, so every storage write and `onStart()`
994
+ * I/O is owned by the child DO.
995
+ */
996
+ async init(name, parentPath = [], identityName = name) {
997
+ const routedName = _classPrivateFieldGet2(_host, this).lifecycle.name;
998
+ if (routedName !== identityName) throw new Error(`Facet bootstrap mismatch: expected routed identity "${identityName}" but got "${routedName}". This usually means the parent passed the wrong id to ctx.facets.get(). See _cf_resolveSubAgent.`);
999
+ _classPrivateFieldGet2(_host, this)._isFacet = true;
1000
+ _classPrivateFieldGet2(_host, this)._facetName = name;
1001
+ _classPrivateFieldGet2(_host, this)._parentPath = parentPath;
1002
+ await Promise.all([
1003
+ _classPrivateFieldGet2(_host, this).ctx.storage.put("cf_agents_is_facet", true),
1004
+ _classPrivateFieldGet2(_host, this).ctx.storage.put("cf_agents_facet_name", name),
1005
+ _classPrivateFieldGet2(_host, this).ctx.storage.put("cf_agents_parent_path", parentPath)
1006
+ ]);
1007
+ await _classPrivateFieldGet2(_host, this).__unsafe_ensureInitialized();
1008
+ }
1009
+ /** Remove a completed facet fiber from the root-side index. */
1010
+ async unregisterRun(ownerPath, runId) {
1011
+ const ownerPathKey = agentPathKey(ownerPath);
1012
+ _classPrivateFieldGet2(_host, this).sql`
1013
+ DELETE FROM cf_agents_facet_runs
1014
+ WHERE owner_path_key IS ${ownerPathKey}
1015
+ AND run_id = ${runId}
1016
+ `;
1017
+ await _classPrivateFieldGet2(_host, this)._syncHostJobs();
1018
+ }
1019
+ };
1020
+ function _reportConnectionOperationFailure(connectionId, operation, error) {
1021
+ console.error("[Agent] Sub-agent connection operation failed:", {
1022
+ connectionId,
1023
+ operation,
1024
+ error
1025
+ });
1026
+ }
1027
+ function _connectionMetaForPath(connection, ownerPath) {
1028
+ ensureConnectionWrapped(connection);
1029
+ const outerUri = _classPrivateFieldGet2(_host, this)._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY);
1030
+ if (typeof outerUri !== "string") return null;
1031
+ const target = _assertClassBrand(_DynamicAgentsInternal_brand, this, _pathFromOuterUri).call(this, outerUri, ownerPath);
1032
+ if (!target) return null;
1033
+ const raw = this.getRawConnectionState(connection);
1034
+ const rawTags = raw != null && typeof raw === "object" ? raw[CF_SUB_AGENT_TAGS_KEY] : void 0;
1035
+ const tags = Array.isArray(rawTags) ? rawTags.filter((tag) => typeof tag === "string") : [...connection.tags];
1036
+ return {
1037
+ id: connection.id,
1038
+ uri: target.uri,
1039
+ tags,
1040
+ state: this.getForwardedState(connection)
1041
+ };
1042
+ }
1043
+ function _pathFromOuterUri(outerUri, stopAt) {
1044
+ const ctx = _classPrivateFieldGet2(_host, this).ctx;
1045
+ const knownClasses = ctx.exports ? Object.keys(ctx.exports) : void 0;
1046
+ const path = [..._classPrivateFieldGet2(_host, this).selfPath];
1047
+ let currentUrl = outerUri;
1048
+ while (true) {
1049
+ const match = parseSubAgentPath(currentUrl, { knownClasses });
1050
+ if (!match) break;
1051
+ path.push({
1052
+ className: match.childClass,
1053
+ name: match.childName
1054
+ });
1055
+ const rewritten = new URL(currentUrl);
1056
+ rewritten.pathname = match.remainingPath;
1057
+ currentUrl = rewritten.toString();
1058
+ if (stopAt && this.isSameAgentPath(path, stopAt)) return {
1059
+ path,
1060
+ uri: currentUrl
1061
+ };
1062
+ }
1063
+ if (path.length === _classPrivateFieldGet2(_host, this).selfPath.length) return null;
1064
+ if (stopAt) return null;
1065
+ return {
1066
+ path,
1067
+ uri: currentUrl
1068
+ };
1069
+ }
1070
+ function _createConnectionBridge(connection) {
1071
+ const upstreamBroadcastBridge = _classPrivateFieldGet2(_host, this)._isFacet ? this.activeBridge(connection.id) : void 0;
1072
+ return new DynamicAgentConnectionBridge(connection, (ownerPath, message, without) => {
1073
+ if (upstreamBroadcastBridge) return this.routeBroadcast(ownerPath, message, without, upstreamBroadcastBridge);
1074
+ return _classPrivateFieldGet2(_host, this)._cf_broadcastToSubAgent(ownerPath, message, without);
1075
+ });
1076
+ }
1077
+ async function _resolveConnection(connection, request, options = { gate: false }) {
1078
+ ensureConnectionWrapped(connection);
1079
+ const outerUri = _classPrivateFieldGet2(_host, this)._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY);
1080
+ const uri = typeof outerUri === "string" ? outerUri : connection.uri;
1081
+ if (!uri) return null;
1082
+ const ctx = _classPrivateFieldGet2(_host, this).ctx;
1083
+ let match = parseSubAgentPath(uri, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 });
1084
+ if (!match) return null;
1085
+ if (_classPrivateFieldGet2(_host, this)._ParentClass.name === match.childClass && _classPrivateFieldGet2(_host, this).name === match.childName) {
1086
+ const tailUri = new URL(uri);
1087
+ tailUri.pathname = match.remainingPath;
1088
+ match = parseSubAgentPath(tailUri.toString(), { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 });
1089
+ if (!match) return null;
1090
+ }
1091
+ let forwardReq = request;
1092
+ if (request && options.gate) {
1093
+ const decision = await _classPrivateFieldGet2(_host, this).onBeforeSubAgent(request, {
1094
+ className: match.childClass,
1095
+ name: match.childName
1096
+ });
1097
+ if (decision instanceof Response) {
1098
+ connection.close(1008, "Sub-agent connection rejected");
1099
+ return null;
1100
+ }
1101
+ forwardReq = decision instanceof Request ? decision : request;
1102
+ }
1103
+ const child = await this.resolve(match.childClass, match.childName);
1104
+ const childUri = new URL(forwardReq?.url ?? uri);
1105
+ childUri.pathname = match.remainingPath;
1106
+ const raw = this.getRawConnectionState(connection);
1107
+ const rawTags = raw != null && typeof raw === "object" ? raw[CF_SUB_AGENT_TAGS_KEY] : void 0;
1108
+ const tags = Array.isArray(rawTags) ? rawTags.filter((tag) => typeof tag === "string") : [...connection.tags];
1109
+ return {
1110
+ child,
1111
+ meta: {
1112
+ id: connection.id,
1113
+ uri: childUri.toString(),
1114
+ tags,
1115
+ state: this.getForwardedState(connection),
1116
+ requestHeaders: forwardReq ? [...forwardReq.headers] : void 0
1117
+ }
1118
+ };
1119
+ }
1120
+ //#endregion
1121
+ //#region src/dynamic-agents/api.ts
1122
+ var _internal = /* @__PURE__ */ new WeakMap();
1123
+ /**
1124
+ * The public dynamic-agents capability surface, reached via
1125
+ * `this.dynamicAgents` on an Agent.
1126
+ *
1127
+ * A dynamic agent is a facet-backed child: it runs in its own isolate
1128
+ * with its own SQLite database, colocated with — and supervised by —
1129
+ * its parent Agent. Use dynamic agents for code whose class or
1130
+ * lifecycle the parent owns (dynamically-loaded/generated code,
1131
+ * per-run tool agents, sandboxed components). For independent peers
1132
+ * such as one-DO-per-chat, use `getAgentByName` instead.
1133
+ *
1134
+ * @experimental The API surface may change before stabilizing.
1135
+ */
1136
+ var DynamicAgents = class {
1137
+ /** @internal Constructed by Agent; do not instantiate directly. */
1138
+ constructor(internal) {
1139
+ _classPrivateFieldInitSpec(this, _internal, void 0);
1140
+ _classPrivateFieldSet2(_internal, this, internal);
1141
+ }
1142
+ /**
1143
+ * Get (creating or waking if needed) the dynamic agent of the given
1144
+ * class and name, as a typed RPC stub. Idempotent — repeated calls
1145
+ * with the same class and name return the same child.
1146
+ *
1147
+ * @experimental
1148
+ */
1149
+ async get(cls, name) {
1150
+ return await _classPrivateFieldGet2(_internal, this).resolve(cls.name, name);
1151
+ }
1152
+ /**
1153
+ * Forcefully abort a running dynamic agent. The child stops
1154
+ * executing immediately and restarts on the next {@link get} call;
1155
+ * its storage is preserved. Transitively aborts the child's own
1156
+ * children. Pending RPC calls receive the reason as an error.
1157
+ *
1158
+ * @experimental
1159
+ */
1160
+ abort(cls, name, reason) {
1161
+ _classPrivateFieldGet2(_internal, this).abort(cls.name, name, reason);
145
1162
  }
146
- close(code, reason) {
147
- return _classPrivateFieldGet2(_root, this)._cf_closeSubAgentConnection(_classPrivateFieldGet2(_connectionId, this), code, reason);
1163
+ /**
1164
+ * Delete a dynamic agent: abort it if running, then permanently wipe
1165
+ * its storage. Transitively deletes the child's own children.
1166
+ *
1167
+ * @experimental
1168
+ */
1169
+ delete(cls, name) {
1170
+ return _classPrivateFieldGet2(_internal, this).delete(cls.name, name);
148
1171
  }
149
- setState(state) {
150
- return _classPrivateFieldGet2(_root, this)._cf_setSubAgentConnectionState(_classPrivateFieldGet2(_connectionId, this), state);
1172
+ has(classOrName, name) {
1173
+ const className = typeof classOrName === "string" ? classOrName : classOrName.name;
1174
+ return _classPrivateFieldGet2(_internal, this).registry.has(className, name);
151
1175
  }
152
- broadcast(ownerPath, message, without) {
153
- return _classPrivateFieldGet2(_root, this)._cf_broadcastToSubAgent(ownerPath, message, without);
1176
+ list(classOrName) {
1177
+ const className = typeof classOrName === "string" ? classOrName : classOrName?.name;
1178
+ return _classPrivateFieldGet2(_internal, this).registry.list(className);
154
1179
  }
155
1180
  };
156
- /**
157
- * Decorator that marks a method as callable by clients
158
- * @param metadata Optional metadata about the callable method
159
- */
160
- function callable(metadata = {}) {
161
- return function callableDecorator(target, _context) {
162
- if (!callableMetadata.has(target)) callableMetadata.set(target, metadata);
163
- return target;
1181
+ //#endregion
1182
+ //#region src/observability/agent-span-attributes.ts
1183
+ function agentSpanAttributes(input) {
1184
+ return {
1185
+ "instrumentation_scope.name": "agents",
1186
+ "instrumentation_scope.version": "1",
1187
+ "cloudflare.agents.session.id": input.sessionId,
1188
+ "cloudflare.agents.session.name": input.sessionName,
1189
+ "gen_ai.agent.name": input.agentClassName
164
1190
  };
165
1191
  }
166
- let didWarnAboutUnstableCallable = false;
1192
+ //#endregion
1193
+ //#region src/index.ts
1194
+ let _Symbol$dispose;
167
1195
  /**
168
- * Decorator that marks a method as callable by clients
169
- * @deprecated this has been renamed to callable, and unstable_callable will be removed in the next major version
170
- * @param metadata Optional metadata about the callable method
1196
+ * Enters an agent invocation: the context every handler reads, plus the span
1197
+ * scope that stops invocation-bounded spans from outliving it. Scopes do not
1198
+ * nest, so the outermost live entry point owns the boundary — pass
1199
+ * `detached` for work that deliberately runs on past its caller.
171
1200
  */
172
- const unstable_callable = (metadata = {}) => {
173
- if (!didWarnAboutUnstableCallable) {
174
- didWarnAboutUnstableCallable = true;
175
- console.warn("unstable_callable is deprecated, use callable instead. unstable_callable will be removed in the next major version.");
1201
+ function runInInvocation(store, body, options) {
1202
+ return __DO_NOT_USE_WILL_BREAK__agentContext.run(store, () => withInvocationScope(body, options));
1203
+ }
1204
+ function sendRpcResponseIfOpen(connection, response) {
1205
+ try {
1206
+ connection.send(JSON.stringify(response));
1207
+ return true;
1208
+ } catch (error) {
1209
+ if (isClosedWebSocketSendError(error)) return false;
1210
+ throw error;
176
1211
  }
177
- return callable(metadata);
178
- };
179
- function agentPathKey(path) {
180
- if (!path) return null;
181
- return path.map((step) => `${encodeURIComponent(step.className)}:${encodeURIComponent(step.name)}`).join("/");
1212
+ }
1213
+ /**
1214
+ * Type guard for RPC request messages
1215
+ */
1216
+ function isRPCRequest(msg) {
1217
+ 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);
182
1218
  }
183
1219
  const _fiberALS = new AsyncLocalStorage();
184
1220
  const DEFAULT_KEEP_ALIVE_INTERVAL_MS = 3e4;
185
1221
  const DEFAULT_AGENT_TOOL_RECOVERY_TIMEOUT_MS = 2e3;
186
1222
  const DEFAULT_AGENT_TOOL_RECOVERY_TOTAL_TIMEOUT_MS = 5e3;
187
1223
  const DESTROY_PENDING_KEY = "cf_agents_destroy_pending";
1224
+ const HOST_JOB_KEEP_ALIVE_ID = "cf:keep-alive";
1225
+ const HOST_JOB_HOUSEKEEPING_ID = "cf:housekeeping";
1226
+ const HOST_JOB_DESTROY_ID = "cf:destroy";
188
1227
  const DESTROY_ALARM_DELAY_MS = 1e3;
189
1228
  const FIBER_RECOVERY_MAX_BACKOFF_MS = 5 * 6e4;
190
1229
  const FIBER_RECOVERY_BACKOFF_MAX_EXP = 20;
@@ -202,105 +1241,18 @@ const DETACHED_BACKBONE_CADENCE_S = [
202
1241
  const DETACHED_LIVE_COUNT_WARN_THRESHOLD = 50;
203
1242
  const DETACHED_RECONCILE_CALLBACK = "_cfDetachedReconcileTick";
204
1243
  const DETACHED_NOTIFY_CALLBACK = "_cfDetachedNotifyFinish";
205
- const SUB_AGENT_IDENTITY_VERSION_LEGACY = "legacy";
206
- const SUB_AGENT_IDENTITY_VERSION_PATH_V2 = "path-v2";
207
- const SUB_AGENT_IDENTITY_PATH_V2_PREFIX = "cf-agents:v2:";
208
1244
  /**
209
1245
  * Schema version for the Agent's internal SQLite tables.
210
1246
  * Bump this when adding new tables, columns, or migrations.
211
- * The constructor stores this as a row in cf_agents_state and checks it
212
- * on wake to skip DDL on established DOs.
1247
+ * The constructor stores this under a namespaced KV key (the same convention
1248
+ * every capability uses for its own schema version) and checks it on wake to
1249
+ * skip DDL on established DOs.
213
1250
  */
214
1251
  const CURRENT_SCHEMA_VERSION = 11;
215
- const SCHEMA_VERSION_ROW_ID = "cf_schema_version";
216
- const STATE_ROW_ID = "cf_state_row_id";
217
- const STATE_WAS_CHANGED = "cf_state_was_changed";
1252
+ const SCHEMA_VERSION_KEY = "cf_agents:schema_version";
1253
+ const LEGACY_SCHEMA_VERSION_ROW_ID = "cf_schema_version";
218
1254
  const DEFAULT_STATE = {};
219
- async function sha256Hex(value) {
220
- const bytes = new TextEncoder().encode(value);
221
- const digest = await crypto.subtle.digest("SHA-256", bytes);
222
- return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
223
- }
224
- function pathV2IdentityName(logicalName, digest) {
225
- return `${SUB_AGENT_IDENTITY_PATH_V2_PREFIX}${encodeURIComponent(logicalName)}:${digest}`;
226
- }
227
- function logicalNameFromPathV2Identity(identityName) {
228
- if (!identityName.startsWith(SUB_AGENT_IDENTITY_PATH_V2_PREFIX)) return null;
229
- const rest = identityName.slice(13);
230
- const separator = rest.lastIndexOf(":");
231
- if (separator === -1) return null;
232
- try {
233
- return decodeURIComponent(rest.slice(0, separator));
234
- } catch {
235
- return null;
236
- }
237
- }
238
- /**
239
- * Validate that a stored `parentPath` has the expected shape. Used
240
- * when restoring from DO storage to guard against corrupted data.
241
- */
242
- function isValidParentPath(value) {
243
- if (!Array.isArray(value)) return false;
244
- return value.every((entry) => entry != null && typeof entry === "object" && typeof entry.className === "string" && typeof entry.name === "string");
245
- }
246
- /**
247
- * Internal key used to store the readonly flag in connection state.
248
- * Prefixed with _cf_ to avoid collision with user state keys.
249
- */
250
- const CF_READONLY_KEY = "_cf_readonly";
251
- /**
252
- * Internal key used to store the no-protocol flag in connection state.
253
- * When set, protocol messages (identity, state sync, MCP servers) are not
254
- * sent to this connection — neither on connect nor via broadcasts.
255
- */
256
- const CF_NO_PROTOCOL_KEY = "_cf_no_protocol";
257
- /**
258
- * Internal key used to store voice call state in connection state.
259
- * Used by the voice mixin to track whether a connection is in an active call.
260
- */
261
- const CF_VOICE_IN_CALL_KEY = "_cf_voiceInCall";
262
- /**
263
- * Internal key used to remember the outer `/sub/...` URL for a
264
- * WebSocket accepted by the parent on behalf of a child facet.
265
- * Hibernated events then wake the parent, which forwards frames to
266
- * the child over serializable RPC while keeping native WebSocket I/O
267
- * parent-owned.
268
- */
269
- const CF_SUB_AGENT_OUTER_URL_KEY = "_cf_subAgentOuterUrl";
270
- const CF_SUB_AGENT_TAGS_KEY = "_cf_subAgentTags";
271
- const SUB_AGENT_OUTER_URL_HEADER = "x-cf-agents-subagent-url";
272
- /**
273
- * The set of all internal keys stored in connection state that must be
274
- * hidden from user code and preserved across setState calls.
275
- */
276
- const CF_INTERNAL_KEYS = /* @__PURE__ */ new Set([
277
- CF_READONLY_KEY,
278
- CF_NO_PROTOCOL_KEY,
279
- CF_VOICE_IN_CALL_KEY,
280
- CF_SUB_AGENT_OUTER_URL_KEY,
281
- CF_SUB_AGENT_TAGS_KEY
282
- ]);
283
- /** Check if a raw connection state object contains any internal keys. */
284
- function rawHasInternalKeys(raw) {
285
- for (const key of Object.keys(raw)) if (CF_INTERNAL_KEYS.has(key)) return true;
286
- return false;
287
- }
288
- /** Return a copy of `raw` with all internal keys removed, or null if no user keys remain. */
289
- function stripInternalKeys(raw) {
290
- const result = {};
291
- let hasUserKeys = false;
292
- for (const key of Object.keys(raw)) if (!CF_INTERNAL_KEYS.has(key)) {
293
- result[key] = raw[key];
294
- hasUserKeys = true;
295
- }
296
- return hasUserKeys ? result : null;
297
- }
298
- /** Return a copy containing only the internal keys present in `raw`. */
299
- function extractInternalFlags(raw) {
300
- const result = {};
301
- for (const key of Object.keys(raw)) if (CF_INTERNAL_KEYS.has(key)) result[key] = raw[key];
302
- return result;
303
- }
1255
+ registerInternalConnectionKeys("_cf_voiceInCall", CF_SUB_AGENT_OUTER_URL_KEY, CF_SUB_AGENT_TAGS_KEY);
304
1256
  /** Max length for error strings broadcast to clients. */
305
1257
  const MAX_ERROR_STRING_LENGTH = 500;
306
1258
  /**
@@ -391,27 +1343,6 @@ const DEFAULT_AGENT_STATIC_OPTIONS = {
391
1343
  */
392
1344
  maxAlarmMemoryLimitStrikes: 3
393
1345
  };
394
- /**
395
- * Parse the raw `retry_options` TEXT column from a SQLite row into a
396
- * typed `RetryOptions` object, or `undefined` if not set.
397
- */
398
- function parseRetryOptions(row) {
399
- const raw = row.retry_options;
400
- if (typeof raw !== "string") return void 0;
401
- return JSON.parse(raw);
402
- }
403
- /**
404
- * Resolve per-task retry options against class-level defaults and call
405
- * `tryN`. This is the retry-execution path for queue flush; Scheduler owns
406
- * its own copy for schedule callbacks.
407
- */
408
- function resolveRetryConfig(taskRetry, defaults) {
409
- return {
410
- maxAttempts: taskRetry?.maxAttempts ?? defaults.maxAttempts,
411
- baseDelayMs: taskRetry?.baseDelayMs ?? defaults.baseDelayMs,
412
- maxDelayMs: taskRetry?.maxDelayMs ?? defaults.maxDelayMs
413
- };
414
- }
415
1346
  /** Compatibility alias for the lifecycle-owned current Agent accessor. */
416
1347
  const getCurrentAgent = getCurrentAgent$1;
417
1348
  /**
@@ -436,7 +1367,7 @@ function withAgentContext(method) {
436
1367
  /**
437
1368
  * Base class for creating Agent implementations
438
1369
  * @template Env Environment type containing bindings
439
- * @template State State type to store within the Agent
1370
+ * @template TState State type to store within the Agent
440
1371
  */
441
1372
  var Agent = class Agent extends DurableObject {
442
1373
  /** Run user initialization after lifecycle components have started. */
@@ -459,6 +1390,34 @@ var Agent = class Agent extends DurableObject {
459
1390
  async __unsafe_ensureInitialized(props) {
460
1391
  await this.lifecycle.start(props);
461
1392
  }
1393
+ /** @internal */
1394
+ get _dynamicAgents() {
1395
+ this._dynamicAgentsInstance ??= new DynamicAgentsInternal(this);
1396
+ return this._dynamicAgentsInstance;
1397
+ }
1398
+ /**
1399
+ * The dynamic-agents capability: facet-backed child agents that run
1400
+ * in their own isolate with their own SQLite database, colocated
1401
+ * with — and supervised by — this agent.
1402
+ *
1403
+ * Use dynamic agents for code whose class or lifecycle this agent
1404
+ * owns: dynamically-loaded or AI-generated code, per-run tool
1405
+ * agents, sandboxed components. For independent peers (for example
1406
+ * one Durable Object per chat), use `getAgentByName` instead.
1407
+ *
1408
+ * ```ts
1409
+ * const child = await this.dynamicAgents.get(Researcher, id);
1410
+ * await child.doWork();
1411
+ * this.dynamicAgents.abort(Researcher, id, reason);
1412
+ * await this.dynamicAgents.delete(Researcher, id);
1413
+ * ```
1414
+ *
1415
+ * @experimental The API surface may change before stabilizing.
1416
+ */
1417
+ get dynamicAgents() {
1418
+ this._dynamicAgentsApi ??= new DynamicAgents(this._dynamicAgents);
1419
+ return this._dynamicAgentsApi;
1420
+ }
462
1421
  /**
463
1422
  * Stable key for Workers AI session affinity (prefix-cache optimization).
464
1423
  *
@@ -480,31 +1439,16 @@ var Agent = class Agent extends DurableObject {
480
1439
  return this.ctx.id.toString();
481
1440
  }
482
1441
  /**
483
- * Current state of the Agent
1442
+ * Current state of the Agent.
1443
+ *
1444
+ * Delegates to the State capability, which owns lazy load and the
1445
+ * in-memory cache; Agent seeds `initialState` on first access.
484
1446
  */
485
1447
  get state() {
486
- if (this._state !== DEFAULT_STATE) return this._state;
487
- const result = this.sql`
488
- SELECT state FROM cf_agents_state WHERE id = ${STATE_ROW_ID}
489
- `;
490
- if (result.length > 0) {
491
- const state = result[0].state;
492
- try {
493
- this._state = JSON.parse(state);
494
- } catch (e) {
495
- console.error("Failed to parse stored state, falling back to initialState:", e);
496
- if (this.initialState !== DEFAULT_STATE) {
497
- this._state = this.initialState;
498
- this._setStateInternal(this.initialState);
499
- } else {
500
- this.sql`DELETE FROM cf_agents_state WHERE id = ${STATE_ROW_ID}`;
501
- return;
502
- }
503
- }
504
- return this._state;
505
- }
506
- if (this.initialState === DEFAULT_STATE) return;
507
- this._setStateInternal(this.initialState);
1448
+ const stored = this._state.get();
1449
+ if (stored !== void 0) return stored;
1450
+ if (this.initialState === DEFAULT_STATE) return void 0;
1451
+ this._state.set(this.initialState, "server");
508
1452
  return this.initialState;
509
1453
  }
510
1454
  get _resolvedOptions() {
@@ -590,26 +1534,9 @@ var Agent = class Agent extends DurableObject {
590
1534
  * local dev and the constructor only runs once per DO instance).
591
1535
  */
592
1536
  _ensureSchema() {
593
- this.sql`
594
- CREATE TABLE IF NOT EXISTS cf_agents_state (
595
- id TEXT PRIMARY KEY NOT NULL,
596
- state TEXT
597
- )
598
- `;
599
- const versionRow = this.sql`
600
- SELECT state FROM cf_agents_state WHERE id = ${SCHEMA_VERSION_ROW_ID}
601
- `;
602
- const schemaVersion = versionRow.length > 0 ? Number(versionRow[0].state) : 0;
1537
+ const schemaVersion = this._readSchemaVersion();
603
1538
  if (schemaVersion < CURRENT_SCHEMA_VERSION) {
604
1539
  ensureMcpServerTable(this.ctx.storage);
605
- this.sql`
606
- CREATE TABLE IF NOT EXISTS cf_agents_queues (
607
- id TEXT PRIMARY KEY NOT NULL,
608
- payload TEXT,
609
- callback TEXT,
610
- created_at INTEGER DEFAULT (unixepoch())
611
- )
612
- `;
613
1540
  const addColumnIfNotExists = (sql) => {
614
1541
  try {
615
1542
  this.ctx.storage.sql.exec(sql);
@@ -617,7 +1544,6 @@ var Agent = class Agent extends DurableObject {
617
1544
  if (!(error instanceof Error ? error.message : String(error)).toLowerCase().includes("duplicate column")) throw error;
618
1545
  }
619
1546
  };
620
- addColumnIfNotExists("ALTER TABLE cf_agents_queues ADD COLUMN retry_options TEXT");
621
1547
  this.sql`
622
1548
  CREATE TABLE IF NOT EXISTS cf_agents_workflows (
623
1549
  id TEXT PRIMARY KEY NOT NULL,
@@ -642,8 +1568,6 @@ var Agent = class Agent extends DurableObject {
642
1568
  this.sql`
643
1569
  CREATE INDEX IF NOT EXISTS idx_workflows_name ON cf_agents_workflows(workflow_name)
644
1570
  `;
645
- this.ctx.storage.sql.exec("DELETE FROM cf_agents_state WHERE id = ?", STATE_WAS_CHANGED);
646
- ensureScheduleTable(this.ctx.storage);
647
1571
  this.sql`
648
1572
  CREATE TABLE IF NOT EXISTS cf_agents_runs (
649
1573
  id TEXT PRIMARY KEY NOT NULL,
@@ -728,10 +1652,7 @@ var Agent = class Agent extends DurableObject {
728
1652
  addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_no_progress_budget_ms INTEGER");
729
1653
  addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN last_progress_at INTEGER");
730
1654
  addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_on_milestones TEXT");
731
- this.sql`
732
- INSERT OR REPLACE INTO cf_agents_state (id, state)
733
- VALUES (${SCHEMA_VERSION_ROW_ID}, ${String(CURRENT_SCHEMA_VERSION)})
734
- `;
1655
+ this.ctx.storage.kv.put(SCHEMA_VERSION_KEY, CURRENT_SCHEMA_VERSION);
735
1656
  }
736
1657
  this._schemaInitialization = {
737
1658
  previousVersion: schemaVersion,
@@ -739,23 +1660,50 @@ var Agent = class Agent extends DurableObject {
739
1660
  migrated: schemaVersion < CURRENT_SCHEMA_VERSION
740
1661
  };
741
1662
  }
1663
+ /**
1664
+ * Read the Agent's schema version from its KV key. A DO created before the
1665
+ * State capability owned `cf_agents_state` has the version as a row in that
1666
+ * table instead: read it once, move it to the key, and delete the row so the
1667
+ * table is left with a single owner. Synchronous (`storage.kv`) because the
1668
+ * constructor gates DDL on it.
1669
+ */
1670
+ _readSchemaVersion() {
1671
+ const stored = this.ctx.storage.kv.get(SCHEMA_VERSION_KEY);
1672
+ if (stored !== void 0) return stored;
1673
+ if (!(this.ctx.storage.sql.exec("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'cf_agents_state'").toArray().length > 0)) return 0;
1674
+ const rows = this.ctx.storage.sql.exec("SELECT state FROM cf_agents_state WHERE id = ?", LEGACY_SCHEMA_VERSION_ROW_ID).toArray();
1675
+ if (rows.length === 0) return 0;
1676
+ const version = Number(rows[0].state) || 0;
1677
+ this.ctx.storage.kv.put(SCHEMA_VERSION_KEY, version);
1678
+ this.ctx.storage.sql.exec("DELETE FROM cf_agents_state WHERE id = ?", LEGACY_SCHEMA_VERSION_ROW_ID);
1679
+ return version;
1680
+ }
742
1681
  constructor(ctx, env) {
743
1682
  super(ctx, env);
744
- this.lifecycle = Lifecycle.install(this);
745
- this._state = DEFAULT_STATE;
1683
+ this.lifecycle = Lifecycle.install(this, { maxAlarmMemoryLimitStrikes: this._resolvedOptions.maxAlarmMemoryLimitStrikes });
1684
+ this._state = new State({
1685
+ validateStateChange: (nextState, source) => this.validateStateChange(nextState, source),
1686
+ onChanged: (nextState, source) => this._handleStateChanged(nextState, source)
1687
+ });
1688
+ this._webSockets = new WebSockets({
1689
+ handlers: {
1690
+ onConnect: (connection, ctx) => this.onConnect(connection, ctx),
1691
+ onMessage: (connection, message) => this.onMessage(connection, message),
1692
+ onClose: (connection, code, reason, wasClean) => this.onClose(connection, code, reason, wasClean),
1693
+ onError: (connection, error) => this.onError(connection, error)
1694
+ },
1695
+ protocol: false,
1696
+ state: this._state,
1697
+ getConnectionTags: (connection, ctx) => this.getConnectionTags(connection, ctx)
1698
+ });
746
1699
  this._disposables = new DisposableStore();
747
1700
  this._destroyed = false;
748
- this._rawStateAccessors = /* @__PURE__ */ new WeakMap();
749
1701
  this._persistenceHookMode = "none";
750
1702
  this._isFacet = false;
751
1703
  this._protocolBroadcastExcludeIds = /* @__PURE__ */ new Set();
752
- this._cf_subAgentBridgeContext = new AsyncLocalStorage();
753
- this._cf_virtualSubAgentConnections = /* @__PURE__ */ new Map();
754
- this._cf_subAgentConnectionOperationTails = /* @__PURE__ */ new Map();
755
1704
  this._parentPath = [];
756
1705
  this._warnedChatRecoveryInOnStart = false;
757
1706
  this._keepAliveRefs = 0;
758
- this._facetKeepAliveTokens = /* @__PURE__ */ new Set();
759
1707
  this._runFiberActiveFibers = /* @__PURE__ */ new Set();
760
1708
  this._managedFiberAbortControllers = /* @__PURE__ */ new Map();
761
1709
  this._managedFiberExecutions = /* @__PURE__ */ new Map();
@@ -767,9 +1715,7 @@ var Agent = class Agent extends DurableObject {
767
1715
  this._ParentClass = Object.getPrototypeOf(this).constructor;
768
1716
  this.initialState = DEFAULT_STATE;
769
1717
  this.observability = genericObservability;
770
- this._flushingQueue = false;
771
1718
  this.maxConcurrentAgentTools = Infinity;
772
- this._subAgentRegistryReady = false;
773
1719
  const routeHost = this;
774
1720
  setLifecycleRouteTransport(this.lifecycle, {
775
1721
  get source() {
@@ -782,10 +1728,10 @@ var Agent = class Agent extends DurableObject {
782
1728
  const payload = event.payload !== null && typeof event.payload === "object" && !Array.isArray(event.payload) ? event.payload : { value: event.payload };
783
1729
  this._emit(event.type, payload);
784
1730
  });
785
- setLifecycleHostInvoker(this.lifecycle, (run) => runInInvocation({
1731
+ setLifecycleHostInvoker(this.lifecycle, (run, scope) => runInInvocation({
786
1732
  agent: this,
787
- connection: void 0,
788
- request: void 0,
1733
+ connection: scope?.connection,
1734
+ request: scope?.request,
789
1735
  email: void 0
790
1736
  }, run));
791
1737
  this.scheduler = new Scheduler({
@@ -803,6 +1749,25 @@ var Agent = class Agent extends DurableObject {
803
1749
  if (typeof method !== "function") return void 0;
804
1750
  return (payload, schedule) => method.call(this, payload, schedule);
805
1751
  });
1752
+ this._queue = new Queue({
1753
+ retry: this._resolvedOptions.retry,
1754
+ onError: (error) => runInInvocation({
1755
+ agent: this,
1756
+ connection: void 0,
1757
+ request: void 0,
1758
+ email: void 0
1759
+ }, () => this.onError(error))
1760
+ });
1761
+ setQueueCallbackResolver(this._queue, (name) => {
1762
+ const method = this[name];
1763
+ if (typeof method !== "function") return void 0;
1764
+ return (payload, item) => method.call(this, payload, item);
1765
+ });
1766
+ this.tasks = new Tasks({ onError: (error) => this.onError(error) });
1767
+ setTaskRoutedMemoryLimitHandler(this.tasks, (context) => {
1768
+ return this.onAlarmMemoryLimit?.call(this, context);
1769
+ });
1770
+ setTaskDefinitionResolver(this.tasks, (name) => this.taskDefinitions?.[name]);
806
1771
  this.mcp = this._withAgentSpan("agent_initialization", "initialization", {}, (update) => {
807
1772
  if (!wrappedClasses.has(this.constructor)) {
808
1773
  this._autoWrapCustomMethods();
@@ -823,7 +1788,7 @@ var Agent = class Agent extends DurableObject {
823
1788
  createAuthProvider: (callbackUrl) => this.createMcpOAuthProvider(callbackUrl)
824
1789
  });
825
1790
  });
826
- this.lifecycle.use(this.scheduler).use(this.mcp);
1791
+ this.lifecycle.use(this.scheduler).use(this._queue).use(this.mcp).use(this._state).use(this._webSockets).use(this.tasks).use(this._dynamicAgents);
827
1792
  let mcpBroadcastReady = false;
828
1793
  this._disposables.add(this.mcp.onServerStateChanged(() => {
829
1794
  if (mcpBroadcastReady) this.broadcastMcpServers();
@@ -857,6 +1822,8 @@ var Agent = class Agent extends DurableObject {
857
1822
  await _onAlarm();
858
1823
  if (this._destroyed) return;
859
1824
  await this._onAlarmHousekeeping();
1825
+ if (this._destroyed) return;
1826
+ await this._syncHostJobs();
860
1827
  };
861
1828
  const _onRequest = this.onRequest.bind(this);
862
1829
  this.onRequest = (request) => {
@@ -869,9 +1836,9 @@ var Agent = class Agent extends DurableObject {
869
1836
  };
870
1837
  const _onMessage = this.onMessage.bind(this);
871
1838
  this.onMessage = async (connection, message) => {
872
- const replyBridge = subAgentRpcReplyContext.getStore()?.bridge;
1839
+ const replyBridge = dynamicAgentRpcReplyContext.getStore()?.bridge;
873
1840
  if (await __DO_NOT_USE_WILL_BREAK__agentContext.exit(() => this._cf_forwardSubAgentWebSocketMessage(connection, message, replyBridge))) return;
874
- this._ensureConnectionWrapped(connection);
1841
+ ensureConnectionWrapped(connection);
875
1842
  return runInInvocation({
876
1843
  agent: this,
877
1844
  connection,
@@ -885,34 +1852,17 @@ var Agent = class Agent extends DurableObject {
885
1852
  } catch (_e) {
886
1853
  return this._tryCatch(() => _onMessage(connection, message));
887
1854
  }
888
- if (isStateUpdateMessage(parsed)) {
889
- if (this.isConnectionReadonly(connection)) {
890
- connection.send(JSON.stringify({
891
- type: "cf_agent_state_error",
892
- error: "Connection is readonly"
893
- }));
894
- return;
895
- }
896
- try {
897
- this._setStateInternal(parsed.state, connection);
898
- } catch (e) {
899
- console.error("[Agent] State update rejected:", e);
900
- connection.send(JSON.stringify({
901
- type: "cf_agent_state_error",
902
- error: "State update rejected"
903
- }));
904
- }
905
- return;
906
- }
1855
+ if (this._webSockets.applyStateFrame(connection, parsed)) return;
907
1856
  if (isRPCRequest(parsed)) {
908
1857
  try {
909
1858
  const { id, method, args } = parsed;
910
1859
  const methodFn = this[method];
911
1860
  if (typeof methodFn !== "function") throw new Error(`Method ${method} does not exist`);
912
1861
  if (!this._isCallable(method)) throw new Error(`Method ${method} is not callable`);
913
- const metadata = callableMetadata.get(methodFn);
1862
+ const metadata = getCallableMetadata(methodFn);
914
1863
  if (metadata?.streaming) {
915
- const stream = createStreamingResponse(connection, id, replyBridge);
1864
+ const stream = new StreamingResponse(connection, id);
1865
+ if (replyBridge) registerFacetStreamingDelivery(stream, replyBridge);
916
1866
  this._emit("rpc", {
917
1867
  method,
918
1868
  streaming: true
@@ -966,7 +1916,7 @@ var Agent = class Agent extends DurableObject {
966
1916
  };
967
1917
  const _onConnect = this.onConnect.bind(this);
968
1918
  this.onConnect = async (connection, ctx) => {
969
- this._ensureConnectionWrapped(connection);
1919
+ ensureConnectionWrapped(connection);
970
1920
  const subAgentOuterUrl = ctx.request.headers.get(SUB_AGENT_OUTER_URL_HEADER);
971
1921
  if (subAgentOuterUrl) this._unsafe_setConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY, subAgentOuterUrl);
972
1922
  if (await __DO_NOT_USE_WILL_BREAK__agentContext.exit(() => this._cf_forwardSubAgentWebSocketConnect(connection, ctx.request, { gate: false }))) return;
@@ -986,11 +1936,10 @@ var Agent = class Agent extends DurableObject {
986
1936
  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.`);
987
1937
  }
988
1938
  }
989
- connection.send(JSON.stringify({
1939
+ this._webSockets.sendIdentity(connection, {
990
1940
  name: this.name,
991
- agent: camelCaseToKebabCase(this._ParentClass.name),
992
- type: "cf_agent_identity"
993
- }));
1941
+ agent: camelCaseToKebabCase(this._ParentClass.name)
1942
+ });
994
1943
  }
995
1944
  const wasExcludedFromStateInitBroadcast = this._protocolBroadcastExcludeIds.has(connection.id);
996
1945
  let currentState;
@@ -1000,15 +1949,12 @@ var Agent = class Agent extends DurableObject {
1000
1949
  } finally {
1001
1950
  if (!wasExcludedFromStateInitBroadcast) this._protocolBroadcastExcludeIds.delete(connection.id);
1002
1951
  }
1003
- if (currentState !== void 0) connection.send(JSON.stringify({
1004
- state: currentState,
1005
- type: "cf_agent_state"
1006
- }));
1952
+ if (currentState !== void 0) this._webSockets.sendState(connection);
1007
1953
  connection.send(JSON.stringify({
1008
1954
  mcp: this.getMcpServers(),
1009
1955
  type: "cf_agent_mcp_servers"
1010
1956
  }));
1011
- } else this._setConnectionNoProtocol(connection);
1957
+ } else this._webSockets.setProtocolEnabled(connection, false);
1012
1958
  this._emit("connect", { connectionId: connection.id });
1013
1959
  await this._replayAgentToolRuns(connection);
1014
1960
  return this._tryCatch(() => _onConnect(connection, ctx));
@@ -1061,6 +2007,7 @@ var Agent = class Agent extends DurableObject {
1061
2007
  console.warn("[Agent] `chatRecovery` was assigned during onStart(). Chat recovery evaluates its budgets (and may seal an interrupted turn, firing onExhausted) on wake BEFORE onStart() runs, so a config set here is applied too late and the built-in defaults are used for the recovery that matters. Assign `chatRecovery` as a class field or in the constructor instead.");
1062
2008
  }
1063
2009
  this._scheduleAgentToolRunRecovery({ runIds: startupAgentToolRunIds });
2010
+ await this._syncHostJobs();
1064
2011
  return result;
1065
2012
  });
1066
2013
  });
@@ -1068,18 +2015,7 @@ var Agent = class Agent extends DurableObject {
1068
2015
  this.onStart = (props) => this._withAgentSpan("agent_start", "startup", {}, (update) => startAgent(props, update));
1069
2016
  }
1070
2017
  async _restoreAgentFacetContext() {
1071
- await this._withAgentSpan("restore_agent_state", "startup", {}, async () => {
1072
- if (await this.ctx.storage.get("cf_agents_is_facet")) this._isFacet = true;
1073
- const storedFacetName = await this.ctx.storage.get("cf_agents_facet_name");
1074
- if (typeof storedFacetName === "string") this._facetName = storedFacetName;
1075
- const storedParentPath = await this.ctx.storage.get("cf_agents_parent_path");
1076
- if (isValidParentPath(storedParentPath)) this._parentPath = storedParentPath;
1077
- try {
1078
- await this._cf_hydrateSubAgentConnectionsFromRoot();
1079
- } catch (error) {
1080
- console.warn("[Agent] Unable to hydrate sub-agent WebSocket connections:", error);
1081
- }
1082
- });
2018
+ await this._withAgentSpan("restore_agent_state", "startup", {}, () => this._dynamicAgents.restoreFacetContext());
1083
2019
  }
1084
2020
  /**
1085
2021
  * Check for workflows referencing unknown bindings and warn with migration suggestion.
@@ -1115,13 +2051,14 @@ var Agent = class Agent extends DurableObject {
1115
2051
  for (const conn of this.getConnections()) if (!this.isConnectionProtocolEnabled(conn)) exclude.push(conn.id);
1116
2052
  this.broadcast(msg, exclude);
1117
2053
  }
1118
- _setStateInternal(nextState, source = "server") {
1119
- this.validateStateChange(nextState, source);
1120
- this._state = nextState;
1121
- this.sql`
1122
- INSERT OR REPLACE INTO cf_agents_state (id, state)
1123
- VALUES (${STATE_ROW_ID}, ${JSON.stringify(nextState)})
1124
- `;
2054
+ /**
2055
+ * React to a persisted state change from the State capability.
2056
+ *
2057
+ * Reproduces the pre-migration steps 3-4: broadcast the new state to
2058
+ * protocol-enabled connections (excluding the originating connection) and
2059
+ * run the notification hook off the invocation tail.
2060
+ */
2061
+ _handleStateChanged(nextState, source) {
1125
2062
  this._broadcastProtocol(JSON.stringify({
1126
2063
  state: nextState,
1127
2064
  type: "cf_agent_state"
@@ -1153,69 +2090,7 @@ var Agent = class Agent extends DurableObject {
1153
2090
  setState(state) {
1154
2091
  const store = __DO_NOT_USE_WILL_BREAK__agentContext.getStore();
1155
2092
  if (store?.connection && this.isConnectionReadonly(store.connection)) throw new Error("Connection is readonly");
1156
- this._setStateInternal(state, "server");
1157
- }
1158
- /**
1159
- * Wraps connection.state and connection.setState so that internal
1160
- * _cf_-prefixed flags (readonly, no-protocol) are hidden from user code
1161
- * and cannot be accidentally overwritten.
1162
- *
1163
- * Idempotent — safe to call multiple times on the same connection.
1164
- * After hibernation, the _rawStateAccessors WeakMap is empty but the
1165
- * connection's state getter still reads from the persisted WebSocket
1166
- * attachment. Calling this method re-captures the raw getter so that
1167
- * predicate methods (isConnectionReadonly, isConnectionProtocolEnabled)
1168
- * work correctly post-hibernation.
1169
- */
1170
- _ensureConnectionWrapped(connection) {
1171
- if (this._rawStateAccessors.has(connection)) return;
1172
- const descriptor = Object.getOwnPropertyDescriptor(connection, "state");
1173
- let getRaw;
1174
- let setRaw;
1175
- if (descriptor?.get) {
1176
- getRaw = descriptor.get.bind(connection);
1177
- setRaw = connection.setState.bind(connection);
1178
- } else {
1179
- let rawState = connection.state ?? null;
1180
- getRaw = () => rawState;
1181
- setRaw = (state) => {
1182
- rawState = state;
1183
- return rawState;
1184
- };
1185
- }
1186
- this._rawStateAccessors.set(connection, {
1187
- getRaw,
1188
- setRaw
1189
- });
1190
- Object.defineProperty(connection, "state", {
1191
- configurable: true,
1192
- enumerable: true,
1193
- get() {
1194
- const raw = getRaw();
1195
- if (raw != null && typeof raw === "object" && rawHasInternalKeys(raw)) return stripInternalKeys(raw);
1196
- return raw;
1197
- }
1198
- });
1199
- Object.defineProperty(connection, "setState", {
1200
- configurable: true,
1201
- writable: true,
1202
- value(stateOrFn) {
1203
- const raw = getRaw();
1204
- const flags = raw != null && typeof raw === "object" ? extractInternalFlags(raw) : {};
1205
- const hasFlags = Object.keys(flags).length > 0;
1206
- let newUserState;
1207
- if (typeof stateOrFn === "function") newUserState = stateOrFn(hasFlags ? stripInternalKeys(raw) : raw);
1208
- else newUserState = stateOrFn;
1209
- if (hasFlags) {
1210
- if (newUserState != null && typeof newUserState === "object") return setRaw({
1211
- ...newUserState,
1212
- ...flags
1213
- });
1214
- return setRaw(flags);
1215
- }
1216
- return setRaw(newUserState);
1217
- }
1218
- });
2093
+ this._state.set(state, "server");
1219
2094
  }
1220
2095
  /**
1221
2096
  * Mark a connection as readonly or readwrite
@@ -1223,17 +2098,7 @@ var Agent = class Agent extends DurableObject {
1223
2098
  * @param readonly Whether the connection should be readonly (default: true)
1224
2099
  */
1225
2100
  setConnectionReadonly(connection, readonly = true) {
1226
- this._ensureConnectionWrapped(connection);
1227
- const accessors = this._rawStateAccessors.get(connection);
1228
- const raw = accessors.getRaw() ?? {};
1229
- if (readonly) accessors.setRaw({
1230
- ...raw,
1231
- [CF_READONLY_KEY]: true
1232
- });
1233
- else {
1234
- const { [CF_READONLY_KEY]: _, ...rest } = raw;
1235
- accessors.setRaw(Object.keys(rest).length > 0 ? rest : null);
1236
- }
2101
+ setConnectionReadonly(connection, readonly);
1237
2102
  }
1238
2103
  /**
1239
2104
  * Check if a connection is marked as readonly.
@@ -1244,8 +2109,7 @@ var Agent = class Agent extends DurableObject {
1244
2109
  * @returns True if the connection is readonly
1245
2110
  */
1246
2111
  isConnectionReadonly(connection) {
1247
- this._ensureConnectionWrapped(connection);
1248
- return !!this._rawStateAccessors.get(connection).getRaw()?.[CF_READONLY_KEY];
2112
+ return isConnectionReadonly(connection);
1249
2113
  }
1250
2114
  /**
1251
2115
  * ⚠️ INTERNAL — DO NOT USE IN APPLICATION CODE. ⚠️
@@ -1260,30 +2124,20 @@ var Agent = class Agent extends DurableObject {
1260
2124
  * @internal
1261
2125
  */
1262
2126
  _unsafe_getConnectionFlag(connection, key) {
1263
- this._ensureConnectionWrapped(connection);
1264
- return this._rawStateAccessors.get(connection).getRaw()?.[key];
2127
+ return getConnectionFlag(connection, key);
1265
2128
  }
1266
2129
  /**
1267
2130
  * ⚠️ INTERNAL — DO NOT USE IN APPLICATION CODE. ⚠️
1268
2131
  *
1269
2132
  * Write an internal `_cf_`-prefixed flag to the raw connection state,
1270
2133
  * bypassing the user-facing state wrapper. The key must be registered
1271
- * in `CF_INTERNAL_KEYS` so it is preserved across user `setState` calls
1272
- * and hidden from `connection.state`.
2134
+ * with `registerInternalConnectionKeys` so it is preserved across user
2135
+ * `setState` calls and hidden from `connection.state`.
1273
2136
  *
1274
2137
  * @internal
1275
2138
  */
1276
2139
  _unsafe_setConnectionFlag(connection, key, value) {
1277
- this._ensureConnectionWrapped(connection);
1278
- const accessors = this._rawStateAccessors.get(connection);
1279
- const raw = accessors.getRaw() ?? {};
1280
- if (value === void 0) {
1281
- const { [key]: _, ...rest } = raw;
1282
- accessors.setRaw(Object.keys(rest).length > 0 ? rest : null);
1283
- } else accessors.setRaw({
1284
- ...raw,
1285
- [key]: value
1286
- });
2140
+ setConnectionFlag(connection, key, value);
1287
2141
  }
1288
2142
  /**
1289
2143
  * Override this method to determine if a connection should be readonly on connect
@@ -1324,21 +2178,7 @@ var Agent = class Agent extends DurableObject {
1324
2178
  * @returns True if the connection receives protocol messages
1325
2179
  */
1326
2180
  isConnectionProtocolEnabled(connection) {
1327
- this._ensureConnectionWrapped(connection);
1328
- return !this._rawStateAccessors.get(connection).getRaw()?.[CF_NO_PROTOCOL_KEY];
1329
- }
1330
- /**
1331
- * Mark a connection as having protocol messages disabled.
1332
- * Called internally when shouldSendProtocolMessages returns false.
1333
- */
1334
- _setConnectionNoProtocol(connection) {
1335
- this._ensureConnectionWrapped(connection);
1336
- const accessors = this._rawStateAccessors.get(connection);
1337
- const raw = accessors.getRaw() ?? {};
1338
- accessors.setRaw({
1339
- ...raw,
1340
- [CF_NO_PROTOCOL_KEY]: true
1341
- });
2181
+ return isConnectionProtocolEnabled(connection);
1342
2182
  }
1343
2183
  /**
1344
2184
  * Called before the Agent's state is persisted and broadcast.
@@ -1534,7 +2374,7 @@ var Agent = class Agent extends DurableObject {
1534
2374
  const descriptor = Object.getOwnPropertyDescriptor(proto, methodName);
1535
2375
  if (baseMethods.has(methodName) || methodName.startsWith("_") || !descriptor || !!descriptor.get || typeof descriptor.value !== "function") continue;
1536
2376
  const wrappedFunction = withAgentContext(this[methodName]);
1537
- if (this._isCallable(methodName)) callableMetadata.set(wrappedFunction, callableMetadata.get(this[methodName]));
2377
+ if (this._isCallable(methodName)) copyCallableMetadata(this[methodName], wrappedFunction);
1538
2378
  this.constructor.prototype[methodName] = wrappedFunction;
1539
2379
  }
1540
2380
  proto = Object.getPrototypeOf(proto);
@@ -1583,111 +2423,42 @@ var Agent = class Agent extends DurableObject {
1583
2423
  });
1584
2424
  }
1585
2425
  /**
1586
- * Queue a task to be executed in the future
2426
+ * Queue a task to run in the background.
2427
+ *
2428
+ * The item is durable: it runs from the Lifecycle alarm event loop after
2429
+ * this call returns, in push order, one at a time, with retries per
2430
+ * `options.retry`, and survives the Durable Object leaving memory.
1587
2431
  * @param callback Name of the method to call
1588
2432
  * @param payload Payload to pass to the callback
1589
2433
  * @param options Options for the queued task
1590
2434
  * @param options.retry Retry options for the callback execution
2435
+ * @param options.id Stable id; a push with an existing id replaces that item
1591
2436
  * @returns The ID of the queued task
1592
2437
  */
1593
2438
  async queue(callback, payload, options) {
1594
- const id = nanoid(9);
1595
2439
  if (typeof callback !== "string") throw new Error("Callback must be a string");
1596
2440
  if (typeof this[callback] !== "function") throw new Error(`this.${callback} is not a function`);
1597
- if (options?.retry) validateRetryOptions(options.retry, this._resolvedOptions.retry);
1598
- const retryJson = options?.retry ? JSON.stringify(options.retry) : null;
1599
- this.sql`
1600
- INSERT OR REPLACE INTO cf_agents_queues (id, payload, callback, retry_options)
1601
- VALUES (${id}, ${JSON.stringify(payload)}, ${callback}, ${retryJson})
1602
- `;
1603
- this._emit("queue:create", {
1604
- callback,
1605
- id
1606
- });
1607
- this._flushQueue().catch((e) => {
1608
- console.error("Error flushing queue:", e);
1609
- });
1610
- return id;
1611
- }
1612
- async _flushQueue() {
1613
- if (this._flushingQueue) return;
1614
- this._flushingQueue = true;
1615
- try {
1616
- while (true) {
1617
- const result = this.sql`
1618
- SELECT * FROM cf_agents_queues
1619
- ORDER BY created_at ASC
1620
- `;
1621
- if (!result || result.length === 0) break;
1622
- for (const row of result || []) {
1623
- const callback = this[row.callback];
1624
- if (!callback) {
1625
- console.error(`callback ${row.callback} not found`);
1626
- await this.dequeue(row.id);
1627
- continue;
1628
- }
1629
- const { connection, request, email } = __DO_NOT_USE_WILL_BREAK__agentContext.getStore() || {};
1630
- await runInInvocation({
1631
- agent: this,
1632
- connection,
1633
- request,
1634
- email
1635
- }, async () => {
1636
- const { maxAttempts, baseDelayMs, maxDelayMs } = resolveRetryConfig(parseRetryOptions(row), this._resolvedOptions.retry);
1637
- const parsedPayload = JSON.parse(row.payload);
1638
- try {
1639
- await tryN(maxAttempts, async (attempt) => {
1640
- if (attempt > 1) this._emit("queue:retry", {
1641
- callback: row.callback,
1642
- id: row.id,
1643
- attempt,
1644
- maxAttempts
1645
- });
1646
- await callback.bind(this)(parsedPayload, row);
1647
- }, {
1648
- baseDelayMs,
1649
- maxDelayMs
1650
- });
1651
- } catch (e) {
1652
- console.error(`queue callback "${row.callback}" failed after ${maxAttempts} attempts`, e);
1653
- this._emit("queue:error", {
1654
- callback: row.callback,
1655
- id: row.id,
1656
- error: e instanceof Error ? e.message : String(e),
1657
- attempts: maxAttempts
1658
- });
1659
- try {
1660
- await this.onError(e);
1661
- } catch {}
1662
- } finally {
1663
- this.dequeue(row.id);
1664
- }
1665
- }, { detached: true });
1666
- }
1667
- }
1668
- } finally {
1669
- this._flushingQueue = false;
1670
- }
2441
+ return (await this._queue.push(callback, payload, options)).id;
1671
2442
  }
1672
2443
  /**
1673
2444
  * Dequeue a task by ID
1674
2445
  * @param id ID of the task to dequeue
1675
2446
  */
1676
2447
  dequeue(id) {
1677
- this.sql`DELETE FROM cf_agents_queues WHERE id = ${id}`;
2448
+ return this._queue.cancel(id);
1678
2449
  }
1679
2450
  /**
1680
2451
  * Dequeue all tasks
1681
2452
  */
1682
2453
  dequeueAll() {
1683
- this.sql`DELETE FROM cf_agents_queues`;
2454
+ return this._queue.cancelAll();
1684
2455
  }
1685
2456
  /**
1686
2457
  * Dequeue all tasks by callback
1687
2458
  * @param callback Name of the callback to dequeue
1688
2459
  */
1689
2460
  dequeueAllByCallback(callback) {
1690
- this.sql`DELETE FROM cf_agents_queues WHERE callback = ${callback}`;
2461
+ return this._queue.cancelAll(callback);
1691
2462
  }
1692
2463
  /**
1693
2464
  * Get a queued task by ID
@@ -1695,100 +2466,32 @@ var Agent = class Agent extends DurableObject {
1695
2466
  * @returns The task or undefined if not found
1696
2467
  */
1697
2468
  getQueue(id) {
1698
- const result = this.sql`
1699
- SELECT * FROM cf_agents_queues WHERE id = ${id}
1700
- `;
1701
- if (!result || result.length === 0) return void 0;
1702
- const row = result[0];
1703
- return {
1704
- ...row,
1705
- payload: JSON.parse(row.payload),
1706
- retry: parseRetryOptions(row)
1707
- };
2469
+ return this._queue.get(id);
1708
2470
  }
1709
2471
  /**
1710
- * Get all queues by key and value
2472
+ * Get all queued tasks whose payload has `key` equal to `value`
1711
2473
  * @param key Key to filter by
1712
2474
  * @param value Value to filter by
1713
2475
  * @returns Array of matching QueueItem objects
1714
2476
  */
1715
- getQueues(key, value) {
1716
- return this.sql`
1717
- SELECT * FROM cf_agents_queues
1718
- `.filter((row) => JSON.parse(row.payload)[key] === value).map((row) => ({
1719
- ...row,
1720
- payload: JSON.parse(row.payload),
1721
- retry: parseRetryOptions(row)
1722
- }));
1723
- }
1724
- _facetRunRowsForPrefix(ownerPath) {
1725
- return this.sql`
1726
- SELECT owner_path, owner_path_key, run_id, created_at
1727
- FROM cf_agents_facet_runs
1728
- `.filter((row) => {
1729
- try {
1730
- const rowOwnerPath = JSON.parse(row.owner_path);
1731
- return this._isSameAgentPathPrefix(ownerPath, rowOwnerPath);
1732
- } catch {
1733
- return false;
1734
- }
1735
- });
1736
- }
1737
- _deleteFacetRunRowsForPrefix(ownerPath) {
1738
- for (const row of this._facetRunRowsForPrefix(ownerPath)) this.sql`
1739
- DELETE FROM cf_agents_facet_runs
1740
- WHERE owner_path_key = ${row.owner_path_key}
1741
- AND run_id = ${row.run_id}
1742
- `;
2477
+ async getQueues(key, value) {
2478
+ return (await this._queue.list()).filter((item) => typeof item.payload === "object" && item.payload !== null && item.payload[key] === value);
1743
2479
  }
1744
2480
  _lifecycleRouteAddress() {
1745
- if (!this._isFacet) return void 0;
1746
- const key = agentPathKey(this.selfPath);
1747
- return key ? {
1748
- key,
1749
- data: JSON.stringify(this.selfPath)
1750
- } : void 0;
2481
+ return this._dynamicAgents.lifecycleRouteAddress();
1751
2482
  }
1752
- async _routeLifecycleToRoot(envelope) {
1753
- if (!this._isFacet) return this.lifecycle.route(envelope);
1754
- return (await this._rootAlarmOwner())._cf_routeLifecycle(void 0, envelope);
2483
+ _routeLifecycleToRoot(envelope) {
2484
+ return this._dynamicAgents.routeLifecycleToRoot(envelope);
1755
2485
  }
1756
- async _routeLifecycleToTarget(target, envelope) {
1757
- let targetPath;
1758
- try {
1759
- targetPath = JSON.parse(target.data);
1760
- } catch {
1761
- throw new Error("Lifecycle route target is not a valid Agent path");
1762
- }
1763
- const selfPath = this.selfPath;
1764
- if (!this._isSameAgentPathPrefix(selfPath, targetPath)) throw new Error(`Lifecycle route does not descend from ${JSON.stringify(selfPath)}.`);
1765
- if (selfPath.length === targetPath.length) return this.lifecycle.route(envelope);
1766
- const next = targetPath[selfPath.length];
1767
- if (!this.hasSubAgent(next.className, next.name)) {
1768
- const stalePath = targetPath.slice(0, selfPath.length + 1);
1769
- if (this._isFacet) await (await this._rootAlarmOwner())._cf_cleanupFacetPrefix(stalePath);
1770
- else await this._cf_cleanupFacetPrefix(stalePath);
1771
- return false;
1772
- }
1773
- return (await this._cf_resolveSubAgent(next.className, next.name))._cf_routeLifecycle(target, envelope);
2486
+ _routeLifecycleToTarget(target, envelope) {
2487
+ return this._dynamicAgents.routeLifecycleToTarget(target, envelope);
1774
2488
  }
1775
2489
  /** Single native-RPC aperture for routed Lifecycle capabilities. */
1776
- _cf_routeLifecycle(target, envelope) {
1777
- return target ? this._routeLifecycleToTarget(target, envelope) : this.lifecycle.route(envelope);
1778
- }
1779
- async _rootAlarmOwner() {
1780
- const root = this._parentPath[0];
1781
- if (!root) throw new Error("Facet routing requires a root parent.");
1782
- const binding = this.ctx.exports?.[root.className];
1783
- if (!binding) throw new Error(`Unable to resolve root "${root.className}" for facet routing.`);
1784
- return await getAgentByName(binding, root.name);
2490
+ _cf_routeLifecycle(target, envelope) {
2491
+ return this._dynamicAgents.routeLifecycle(target, envelope);
1785
2492
  }
1786
- _cf_rootResolvesToSelf() {
1787
- const root = this._parentPath[0];
1788
- if (!root) return false;
1789
- const binding = this.ctx.exports?.[root.className];
1790
- if (!binding?.idFromName) return false;
1791
- return binding.idFromName(root.name).equals(this.ctx.id);
2493
+ _rootAlarmOwner() {
2494
+ return this._dynamicAgents.rootAlarmOwner();
1792
2495
  }
1793
2496
  /**
1794
2497
  * Clean root-owned bookkeeping for a sub-tree of facets. This
@@ -1801,33 +2504,24 @@ var Agent = class Agent extends DurableObject {
1801
2504
  * @internal
1802
2505
  */
1803
2506
  async _cf_cleanupFacetPrefix(ownerPath) {
1804
- const prefix = agentPathKey(ownerPath);
1805
- if (prefix) this.scheduler.__DO_NOT_USE_WILL_BREAK__cleanupRoutePrefix(prefix);
1806
- this._deleteFacetRunRowsForPrefix(ownerPath);
1807
- await this._rearmAlarm();
2507
+ await this._dynamicAgents.cleanupPrefix(ownerPath);
1808
2508
  }
1809
2509
  /**
1810
2510
  * Acquire a root-owned keepAlive ref on behalf of a descendant facet.
1811
- * Facets share the root isolate but cannot set their own physical
1812
- * alarm, so this lets facet work use the root alarm heartbeat.
2511
+ * Facets run in separate colocated isolates but cannot set their own
2512
+ * physical alarm, so this lets facet work use the root alarm heartbeat.
1813
2513
  * @internal
1814
2514
  */
1815
- async _cf_acquireFacetKeepAlive(ownerPath) {
1816
- const token = `${agentPathKey(ownerPath) ?? "unknown"}:${nanoid(9)}`;
1817
- this._facetKeepAliveTokens.add(token);
1818
- this._keepAliveRefs++;
1819
- if (this._keepAliveRefs === 1) await this._rearmAlarm();
1820
- return token;
2515
+ _cf_acquireFacetKeepAlive(ownerPath) {
2516
+ return this._dynamicAgents.acquireKeepAlive(ownerPath);
1821
2517
  }
1822
2518
  /**
1823
2519
  * Release a root-owned keepAlive ref previously acquired for a facet.
1824
2520
  * Idempotent so disposer calls can safely race or run twice.
1825
2521
  * @internal
1826
2522
  */
1827
- async _cf_releaseFacetKeepAlive(token) {
1828
- if (!this._facetKeepAliveTokens.delete(token)) return;
1829
- this._keepAliveRefs = Math.max(0, this._keepAliveRefs - 1);
1830
- await this._rearmAlarm();
2523
+ _cf_releaseFacetKeepAlive(token) {
2524
+ return this._dynamicAgents.releaseKeepAlive(token);
1831
2525
  }
1832
2526
  /**
1833
2527
  * Register a facet's durable run row in the root-side index so root
@@ -1835,30 +2529,15 @@ var Agent = class Agent extends DurableObject {
1835
2529
  * The facet remains authoritative for snapshots and recovery hooks.
1836
2530
  * @internal
1837
2531
  */
1838
- async _cf_registerFacetRun(ownerPath, runId) {
1839
- const ownerPathJson = JSON.stringify(ownerPath);
1840
- const ownerPathKey = agentPathKey(ownerPath);
1841
- if (!ownerPathKey) throw new Error("_cf_registerFacetRun requires a non-empty owner path.");
1842
- this.sql`
1843
- INSERT OR REPLACE INTO cf_agents_facet_runs
1844
- (owner_path, owner_path_key, run_id, created_at)
1845
- VALUES
1846
- (${ownerPathJson}, ${ownerPathKey}, ${runId}, ${Date.now()})
1847
- `;
1848
- await this._rearmAlarm();
2532
+ _cf_registerFacetRun(ownerPath, runId) {
2533
+ return this._dynamicAgents.registerRun(ownerPath, runId);
1849
2534
  }
1850
2535
  /**
1851
2536
  * Remove a completed facet fiber from the root-side index.
1852
2537
  * @internal
1853
2538
  */
1854
- async _cf_unregisterFacetRun(ownerPath, runId) {
1855
- const ownerPathKey = agentPathKey(ownerPath);
1856
- this.sql`
1857
- DELETE FROM cf_agents_facet_runs
1858
- WHERE owner_path_key IS ${ownerPathKey}
1859
- AND run_id = ${runId}
1860
- `;
1861
- await this._rearmAlarm();
2539
+ _cf_unregisterFacetRun(ownerPath, runId) {
2540
+ return this._dynamicAgents.unregisterRun(ownerPath, runId);
1862
2541
  }
1863
2542
  /**
1864
2543
  * Schedule a task to be executed in the future
@@ -2018,13 +2697,13 @@ var Agent = class Agent extends DurableObject {
2018
2697
  };
2019
2698
  }
2020
2699
  this._keepAliveRefs++;
2021
- if (this._keepAliveRefs === 1) await this._rearmAlarm();
2700
+ if (this._keepAliveRefs === 1) await this._syncHostJobs();
2022
2701
  let disposed = false;
2023
2702
  return () => {
2024
2703
  if (disposed) return;
2025
2704
  disposed = true;
2026
2705
  this._keepAliveRefs = Math.max(0, this._keepAliveRefs - 1);
2027
- if (this._keepAliveRefs === 0) this.ctx.waitUntil(this._rearmAlarm().catch((e) => {
2706
+ if (this._keepAliveRefs === 0) this.ctx.waitUntil(this._syncHostJobs().catch((e) => {
2028
2707
  console.error("[Agent] Failed to reschedule alarm after keepAlive dispose:", e);
2029
2708
  }));
2030
2709
  };
@@ -2590,12 +3269,16 @@ var Agent = class Agent extends DurableObject {
2590
3269
  }
2591
3270
  } finally {
2592
3271
  this._runFiberActiveFibers.delete(id);
2593
- this._withAgentSpan("finalize_fiber", "fiber", {
2594
- "cloudflare.agents.fiber.id": id,
2595
- "cloudflare.agents.fiber.name": name
2596
- }, () => {
2597
- this.sql`DELETE FROM cf_agents_runs WHERE id = ${id}`;
2598
- });
3272
+ try {
3273
+ this._withAgentSpan("finalize_fiber", "fiber", {
3274
+ "cloudflare.agents.fiber.id": id,
3275
+ "cloudflare.agents.fiber.name": name
3276
+ }, () => {
3277
+ this.sql`DELETE FROM cf_agents_runs WHERE id = ${id}`;
3278
+ });
3279
+ } catch (error) {
3280
+ console.error(`[Agent] Failed to finalize fiber "${name}" (${id}); leaving run row for recovery:`, error);
3281
+ }
2599
3282
  dispose();
2600
3283
  if (root && registeredFacetRun) try {
2601
3284
  await root._cf_unregisterFacetRun(this.selfPath, id);
@@ -2617,6 +3300,15 @@ var Agent = class Agent extends DurableObject {
2617
3300
  ctx.stash(data);
2618
3301
  }
2619
3302
  /**
3303
+ * Run `fn` inside the fiber stash context so `this.stash()` keeps working
3304
+ * for turns executing on the `tasks` capability exactly as it does inside
3305
+ * legacy `runFiber()` closures.
3306
+ * @internal
3307
+ */
3308
+ _withFiberStash(context, fn) {
3309
+ return _fiberALS.run(context, fn);
3310
+ }
3311
+ /**
2620
3312
  * Called when an interrupted fiber is detected after restart.
2621
3313
  * Override to implement recovery (re-invoke work, notify clients, etc.).
2622
3314
  *
@@ -2787,36 +3479,8 @@ var Agent = class Agent extends DurableObject {
2787
3479
  * recovery hooks live in each facet's own `cf_agents_runs` table.
2788
3480
  * @internal
2789
3481
  */
2790
- async _checkFacetRunFibers() {
2791
- if (this._parentPath.length > 0) return;
2792
- const rows = this.sql`
2793
- SELECT owner_path, owner_path_key, run_id, created_at
2794
- FROM cf_agents_facet_runs
2795
- ORDER BY created_at ASC
2796
- `;
2797
- const firstRowByOwner = /* @__PURE__ */ new Map();
2798
- for (const row of rows) if (!firstRowByOwner.has(row.owner_path_key)) firstRowByOwner.set(row.owner_path_key, row);
2799
- for (const row of firstRowByOwner.values()) {
2800
- let ownerPath;
2801
- try {
2802
- ownerPath = JSON.parse(row.owner_path);
2803
- } catch (e) {
2804
- console.warn(`[Agent] Corrupted facet fiber owner path for ${row.owner_path_key}; pruning stale lease.`, e);
2805
- this.sql`
2806
- DELETE FROM cf_agents_facet_runs
2807
- WHERE owner_path_key = ${row.owner_path_key}
2808
- `;
2809
- continue;
2810
- }
2811
- try {
2812
- if (await this._cf_checkRunFibersForFacet(ownerPath) === 0) this.sql`
2813
- DELETE FROM cf_agents_facet_runs
2814
- WHERE owner_path_key = ${row.owner_path_key}
2815
- `;
2816
- } catch (e) {
2817
- console.error(`[Agent] Facet fiber recovery check failed for ${row.owner_path_key}:`, e);
2818
- }
2819
- }
3482
+ _checkFacetRunFibers() {
3483
+ return this._dynamicAgents.checkRunFibers();
2820
3484
  }
2821
3485
  /**
2822
3486
  * Dispatch a runFiber recovery check into the facet identified by
@@ -2824,18 +3488,8 @@ var Agent = class Agent extends DurableObject {
2824
3488
  * rows on the target facet after recovery.
2825
3489
  * @internal
2826
3490
  */
2827
- async _cf_checkRunFibersForFacet(ownerPath) {
2828
- const selfPath = this.selfPath;
2829
- if (!this._isSameAgentPathPrefix(selfPath, ownerPath)) throw new Error(`Facet fiber owner path does not descend from ${JSON.stringify(selfPath)}.`);
2830
- if (selfPath.length === ownerPath.length) {
2831
- await this._checkRunFibers();
2832
- return this.sql`
2833
- SELECT COUNT(*) as count FROM cf_agents_runs
2834
- `[0]?.count ?? 0;
2835
- }
2836
- const next = ownerPath[selfPath.length];
2837
- if (!this.hasSubAgent(next.className, next.name)) return 0;
2838
- return (await this._cf_resolveSubAgent(next.className, next.name))._cf_checkRunFibersForFacet(ownerPath);
3491
+ _cf_checkRunFibersForFacet(ownerPath) {
3492
+ return this._dynamicAgents.checkRunFibersAtPath(ownerPath);
2839
3493
  }
2840
3494
  /**
2841
3495
  * Invoke an RPC method on this Agent or a descendant facet identified
@@ -2843,18 +3497,8 @@ var Agent = class Agent extends DurableObject {
2843
3497
  * `this.agent` calls back to the exact sub-agent that started a workflow.
2844
3498
  * @internal
2845
3499
  */
2846
- async _cf_invokeAgentPath(targetPath, method, args) {
2847
- await this.__unsafe_ensureInitialized();
2848
- const selfPath = this.selfPath;
2849
- if (!this._isSameAgentPathPrefix(selfPath, targetPath)) throw new Error(`Workflow origin path does not descend from ${JSON.stringify(selfPath)}.`);
2850
- if (selfPath.length === targetPath.length) {
2851
- const fn = this[method];
2852
- if (isInternalJsStubProp(method) || method in Object.prototype || typeof fn !== "function") throw new Error(`Workflow origin method "${method}" is not callable on ${this.constructor.name}.`);
2853
- return await fn.apply(this, args);
2854
- }
2855
- const next = targetPath[selfPath.length];
2856
- if (!this.hasSubAgent(next.className, next.name)) throw new Error(`Workflow origin sub-agent ${next.className} "${next.name}" no longer exists.`);
2857
- return await (await this._cf_resolveSubAgent(next.className, next.name))._cf_invokeAgentPath(targetPath, method, args);
3500
+ _cf_invokeAgentPath(targetPath, method, args) {
3501
+ return this._dynamicAgents.invokeAgentPath(targetPath, method, args);
2858
3502
  }
2859
3503
  /**
2860
3504
  * Recursively destroy a descendant facet identified by
@@ -2869,25 +3513,8 @@ var Agent = class Agent extends DurableObject {
2869
3513
  * cleanup as `parent.deleteSubAgent(Cls, name)` from the parent.
2870
3514
  * @internal
2871
3515
  */
2872
- async _cf_destroyDescendantFacet(targetPath) {
2873
- const selfPath = this.selfPath;
2874
- if (targetPath.length === 0) throw new Error("_cf_destroyDescendantFacet: target path must not be empty.");
2875
- if (selfPath.length >= targetPath.length) throw new Error("_cf_destroyDescendantFacet: target must be a strict descendant.");
2876
- if (!this._isSameAgentPathPrefix(selfPath, targetPath)) throw new Error("_cf_destroyDescendantFacet: target path does not descend from this agent.");
2877
- if (this._parentPath.length === 0) await this._cf_cleanupFacetPrefix(targetPath);
2878
- if (selfPath.length === targetPath.length - 1) {
2879
- const target = targetPath[targetPath.length - 1];
2880
- const ctx = this.ctx;
2881
- 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.");
2882
- try {
2883
- ctx.facets.delete(`${target.className}\0${target.name}`);
2884
- } catch {}
2885
- this._forgetSubAgent(target.className, target.name);
2886
- return;
2887
- }
2888
- const next = targetPath[selfPath.length];
2889
- if (!this.hasSubAgent(next.className, next.name)) return;
2890
- await (await this._cf_resolveSubAgent(next.className, next.name))._cf_destroyDescendantFacet(targetPath);
3516
+ _cf_destroyDescendantFacet(targetPath) {
3517
+ return this._dynamicAgents.destroyDescendant(targetPath);
2891
3518
  }
2892
3519
  /**
2893
3520
  * Whether any runFiber recovery work is still outstanding: orphaned
@@ -2895,7 +3522,7 @@ var Agent = class Agent extends DurableObject {
2895
3522
  * executing in memory, which already hold a keepAlive ref) or managed
2896
3523
  * ledger fibers stuck in a non-terminal state with no live run row.
2897
3524
  *
2898
- * Used by `_rearmAlarm` to arm a follow-up alarm so multi-pass
3525
+ * Used by `_syncHostJobs` to arm a follow-up alarm so multi-pass
2899
3526
  * recovery (e.g. after a scan-deadline yield, or while retrying a throwing
2900
3527
  * recovery hook) resumes instead of starving.
2901
3528
  * @internal
@@ -2913,28 +3540,57 @@ var Agent = class Agent extends DurableObject {
2913
3540
  AND r.id IS NULL
2914
3541
  `[0]?.count ?? 0) > 0;
2915
3542
  }
2916
- async _rearmAlarm() {
2917
- await this._withAgentSpan("schedule_agent_alarm", "alarm", {}, () => this.lifecycle.rearmAlarm());
2918
- }
2919
3543
  /**
2920
- * Contribute Agent-owned alarm work that has not yet been extracted into its
2921
- * own capability. Scheduler contributes independently.
3544
+ * Synchronize Agent-owned host jobs with current durable state.
3545
+ *
3546
+ * Replaces the old pull-based `getNextAlarm()` contribution: keep-alive
3547
+ * refs hold a `cf:keep-alive` job, and fiber-recovery / facet-run state
3548
+ * holds a `cf:housekeeping` job. Every state change that used to trigger
3549
+ * an alarm recalculation now re-pushes or cancels these jobs; queue
3550
+ * mutations re-arm the physical alarm automatically.
2922
3551
  * @internal
2923
3552
  */
2924
- async getNextAlarm() {
2925
- const pendingDestroy = await this._pendingDestroyAlarm();
2926
- if (pendingDestroy !== null) return {
2927
- time: pendingDestroy,
2928
- exclusive: true
2929
- };
2930
- const nowMs = Date.now();
3553
+ async _syncHostJobs() {
3554
+ if (this._destroyed) return;
3555
+ await this._withAgentSpan("schedule_agent_alarm", "alarm", {}, async () => {
3556
+ const work = this.lifecycle.jobs;
3557
+ const nowMs = Date.now();
3558
+ const pendingDestroy = await this._pendingDestroyAlarm();
3559
+ if (pendingDestroy !== null) {
3560
+ await work.push({
3561
+ id: HOST_JOB_DESTROY_ID,
3562
+ fn: "destroy",
3563
+ time: pendingDestroy,
3564
+ exclusive: true
3565
+ });
3566
+ return;
3567
+ }
3568
+ if (work.get(HOST_JOB_DESTROY_ID)) await work.cancel(HOST_JOB_DESTROY_ID);
3569
+ if (this._keepAliveRefs > 0) await work.push({
3570
+ id: HOST_JOB_KEEP_ALIVE_ID,
3571
+ fn: "keepAlive",
3572
+ time: nowMs + this._resolvedOptions.keepAliveIntervalMs
3573
+ });
3574
+ else if (work.get(HOST_JOB_KEEP_ALIVE_ID)) await work.cancel(HOST_JOB_KEEP_ALIVE_ID);
3575
+ const housekeepingAt = this._nextHousekeepingWakeMs(nowMs);
3576
+ if (housekeepingAt !== null) await work.push({
3577
+ id: HOST_JOB_HOUSEKEEPING_ID,
3578
+ fn: "housekeeping",
3579
+ time: housekeepingAt
3580
+ });
3581
+ else if (work.get(HOST_JOB_HOUSEKEEPING_ID)) await work.cancel(HOST_JOB_HOUSEKEEPING_ID);
3582
+ });
3583
+ }
3584
+ /**
3585
+ * The next wake fiber-recovery or facet-run housekeeping needs, or `null`
3586
+ * when neither has pending durable state.
3587
+ */
3588
+ _nextHousekeepingWakeMs(nowMs) {
2931
3589
  let nextTimeMs = null;
2932
- if (this._keepAliveRefs > 0) nextTimeMs = nowMs + this._resolvedOptions.keepAliveIntervalMs;
2933
3590
  if (this._hasPendingFiberRecovery()) {
2934
3591
  const base = this._resolvedOptions.keepAliveIntervalMs;
2935
3592
  const exp = Math.min(this._recoveryNoProgressScans, FIBER_RECOVERY_BACKOFF_MAX_EXP);
2936
- const recoveryMs = nowMs + Math.min(FIBER_RECOVERY_MAX_BACKOFF_MS, base * 2 ** exp);
2937
- nextTimeMs = nextTimeMs === null ? recoveryMs : Math.min(nextTimeMs, recoveryMs);
3593
+ nextTimeMs = nowMs + Math.min(FIBER_RECOVERY_MAX_BACKOFF_MS, base * 2 ** exp);
2938
3594
  }
2939
3595
  if ((this.sql`
2940
3596
  SELECT COUNT(*) as count FROM cf_agents_facet_runs
@@ -2942,132 +3598,68 @@ var Agent = class Agent extends DurableObject {
2942
3598
  const facetRecoveryMs = nowMs + this._resolvedOptions.keepAliveIntervalMs;
2943
3599
  nextTimeMs = nextTimeMs === null ? facetRecoveryMs : Math.min(nextTimeMs, facetRecoveryMs);
2944
3600
  }
2945
- const extensionAlarm = await this._getExtensionAlarm();
2946
- if (extensionAlarm !== null) nextTimeMs = nextTimeMs === null ? extensionAlarm : Math.min(nextTimeMs, extensionAlarm);
2947
3601
  return nextTimeMs;
2948
3602
  }
2949
- /** @internal Transitional alarm contribution for Agent subclasses. */
2950
- _getExtensionAlarm() {
2951
- return null;
2952
- }
2953
3603
  /** Lifecycle alarm callback; Agent housekeeping runs after user alarm work. */
2954
3604
  onAlarm() {}
2955
3605
  /**
2956
- * Run Lifecycle's alarm phase inside Agent's memory-limit circuit breaker.
2957
- *
2958
- * @remarks Use `this.schedule()` for named Agent callbacks. Reusable durable
2959
- * work belongs in a capability with `getNextAlarm()` and `onAlarm()`.
3606
+ * Drive one Agent-owned host job from the Lifecycle queue.
3607
+ * @internal Dispatched by Lifecycle's alarm event loop; extensions add
3608
+ * job fns through {@link _onHostJob}.
2960
3609
  */
2961
- async alarm() {
2962
- if (await this._hasPendingDestroy()) {
2963
- await this.destroy();
2964
- return;
2965
- }
2966
- try {
2967
- await this._cf_runAlarmBody();
2968
- await this._cf_clearAlarmMemoryLimitStrikes();
2969
- } catch (error) {
2970
- if (!isDurableObjectMemoryLimitReset(error)) throw error;
2971
- await this._cf_handleAlarmMemoryLimitReset(error);
2972
- }
2973
- }
2974
- /** Run the Lifecycle alarm phase inside Agent's OOM circuit breaker. */
2975
- async _cf_runAlarmBody() {
2976
- await this.lifecycle.alarm();
3610
+ onJob(context) {
3611
+ return this._onHostJob(context.job.fn, context);
2977
3612
  }
2978
3613
  /**
2979
- * The schedule-callback names whose alarm rows drive a recovery loop that can
2980
- * deterministically OOM. The base agent has none; chat hosts (`Think`,
2981
- * `AIChatAgent`) override this to return their recovery continuation callbacks
2982
- * so the circuit breaker can surgically back them off / purge them WITHOUT
2983
- * disturbing unrelated scheduled tasks. See {@link _cf_handleAlarmMemoryLimitReset}.
3614
+ * @internal Dispatch one host job fn. Agent extensions (Think) override
3615
+ * this to add fns and delegate unknown ones to `super`.
2984
3616
  */
2985
- _cf_recoveryAlarmCallbacks() {
2986
- return [];
3617
+ _onHostJob(fn, _context) {
3618
+ switch (fn) {
3619
+ case "keepAlive": return this._keepAliveRefs > 0 ? { rescheduleAt: Date.now() + this._resolvedOptions.keepAliveIntervalMs } : void 0;
3620
+ case "housekeeping": {
3621
+ const next = this._nextHousekeepingWakeMs(Date.now());
3622
+ return next === null ? void 0 : { rescheduleAt: next };
3623
+ }
3624
+ case "destroy": return;
3625
+ default:
3626
+ console.warn(`Unknown Agent host job fn ${JSON.stringify(fn)}`);
3627
+ return;
3628
+ }
2987
3629
  }
2988
3630
  /**
2989
- * Hook for a host to terminalize ("seal") any in-flight recovery work as an
2990
- * out-of-memory exhaustion when the alarm circuit breaker trips at its strike
2991
- * budget (#1825). Runs at the outermost alarm frame (post-unwind, so writes
2992
- * can land). Default: no-op. Chat hosts override to fire `onExhausted` + the
2993
- * terminal banner and persist the sealed incident.
2994
- */
2995
- async _cf_sealMemoryLimitedRecovery() {}
2996
- /**
2997
- * Clear the durable memory-limit strike counter after a clean alarm so the
2998
- * circuit breaker counts CONSECUTIVE resets rather than lifetime ones
2999
- * (#1825). Reads first (cheap, usually cached) and only writes when a strike
3000
- * is actually recorded, so the common no-strike path costs no write.
3001
- * Best-effort: a stale strike only costs one extra tolerated spike later.
3631
+ * Apply host policy after the alarm memory-limit breaker records a strike.
3632
+ *
3633
+ * New chat hosts override this hook directly. The sealed-only fallback keeps
3634
+ * `agents` 0.23 compatible with already-published chat packages whose peer
3635
+ * ranges accept it but which implement only the former
3636
+ * `_cf_sealMemoryLimitedRecovery` template method. Queue membership remains
3637
+ * job-row policy; this invokes terminalization only and can be removed once
3638
+ * old chat releases no longer accept the current `agents` range.
3639
+ *
3640
+ * @internal
3002
3641
  */
3003
- async _cf_clearAlarmMemoryLimitStrikes() {
3004
- try {
3005
- const prior = await this.ctx.storage.get(Agent._CF_OOM_ALARM_STRIKES_KEY);
3006
- if (typeof prior === "number" && prior > 0) await this.ctx.storage.delete(Agent._CF_OOM_ALARM_STRIKES_KEY);
3007
- } catch {}
3642
+ async onAlarmMemoryLimit(context) {
3643
+ if (!context.sealed) return;
3644
+ await this._cf_sealMemoryLimitedRecovery?.call(this);
3008
3645
  }
3009
3646
  /**
3010
- * Alarm-boundary circuit breaker for Durable Object memory-limit resets
3011
- * (#1825). The in-DO recovery budgets (`chatRecovery.maxOomRetries` /
3012
- * `maxRecoveryWork`) only engage if their code runs AND its writes land; a
3013
- * severe OOM can defeat both — thrown before the budget runs (boot hydration),
3014
- * or its own small writes also OOM under memory pressure. In that case the
3015
- * error reaches {@link alarm} and, unhandled, the platform auto-retries the
3016
- * alarm indefinitely (re-running the doomed, billable turn each cycle).
3017
- *
3018
- * This runs at the OUTERMOST frame: the heavy turn has unwound and GC has
3019
- * reclaimed its footprint, so the small writes here can land where mid-turn
3020
- * ones (e.g. give-up's incident read) OOMed. A durable strike counter tolerates
3021
- * a few resets (a transient spike may clear), backing off the recovery rows so
3022
- * the retry is not a hot loop. At the `maxAlarmMemoryLimitStrikes` budget it
3023
- * seals the recovery work and purges the looping rows so the loop — and the
3024
- * bill — stops. Each step is best-effort: even these tiny writes can OOM, but
3025
- * swallowing (not re-throwing) still halts the platform's auto-retry, and a
3026
- * later wake re-arms legitimate schedules.
3027
- */
3028
- async _cf_handleAlarmMemoryLimitReset(error) {
3029
- const key = Agent._CF_OOM_ALARM_STRIKES_KEY;
3030
- let strikes = 1;
3031
- try {
3032
- const prior = await this.ctx.storage.get(key);
3033
- strikes = (typeof prior === "number" ? prior : 0) + 1;
3034
- await this.ctx.storage.put(key, strikes);
3035
- } catch {}
3036
- const limit = this._resolvedOptions.maxAlarmMemoryLimitStrikes;
3037
- const sealed = strikes >= limit;
3038
- const recoveryCallbacks = this._cf_recoveryAlarmCallbacks();
3039
- console.error(`Alarm hit a Durable Object memory-limit reset (strike ${strikes}/${limit}${sealed ? ", sealing recovery" : ", will retry with backoff"}). Breaking the platform alarm-retry loop (#1825).`, error instanceof Error ? error.message : String(error));
3040
- if (sealed) {
3041
- this.scheduler.__DO_NOT_USE_WILL_BREAK__handleAlarmMemoryLimit({
3042
- callbacks: recoveryCallbacks,
3043
- sealed: true
3044
- });
3045
- try {
3046
- await this._cf_sealMemoryLimitedRecovery();
3047
- } catch {}
3048
- try {
3049
- await this.ctx.storage.delete(key);
3050
- } catch {}
3051
- } else {
3052
- const backoffSeconds = Math.min(300, 30 * strikes);
3053
- const nextTime = Math.floor(Date.now() / 1e3) + backoffSeconds;
3054
- this.scheduler.__DO_NOT_USE_WILL_BREAK__handleAlarmMemoryLimit({
3055
- callbacks: recoveryCallbacks,
3056
- sealed: false,
3057
- nextTime
3058
- });
3647
+ * Run Lifecycle's alarm event loop after the pending-destroy preamble.
3648
+ *
3649
+ * The alarm memory-limit circuit breaker (#1825) lives inside
3650
+ * `Lifecycle.alarm()`; capabilities and hosts opt into extra domain
3651
+ * policy via their `onMemoryLimit` / `onAlarmMemoryLimit` hooks and the
3652
+ * `recoveryLoop` schedule option.
3653
+ *
3654
+ * @remarks Use `this.schedule()` for named Agent callbacks. Reusable durable
3655
+ * work belongs in a capability that pushes jobs and implements `onJob()`.
3656
+ */
3657
+ async alarm() {
3658
+ if (await this._hasPendingDestroy()) {
3659
+ await this.destroy();
3660
+ return;
3059
3661
  }
3060
- try {
3061
- this._emit("alarm:memory_limit_reset", {
3062
- strikes,
3063
- limit,
3064
- sealed,
3065
- error: error instanceof Error ? error.message : String(error)
3066
- });
3067
- } catch {}
3068
- try {
3069
- await this._rearmAlarm();
3070
- } catch {}
3662
+ await this.lifecycle.alarm();
3071
3663
  }
3072
3664
  /**
3073
3665
  * Intercept incoming HTTP/WS requests whose URL contains a
@@ -3077,9 +3669,9 @@ var Agent = class Agent extends DurableObject {
3077
3669
  * Response, the framework resolves the facet and hands the
3078
3670
  * request off.
3079
3671
  *
3080
- * After a WebSocket upgrade completes, subsequent frames route
3081
- * directly to the child — the parent is only on the path for the
3082
- * initial request.
3672
+ * The parent owns an upgraded WebSocket for its lifetime. Subsequent
3673
+ * frames wake the root parent, which forwards them to the child over
3674
+ * RPC and routes replies back to the native socket.
3083
3675
  *
3084
3676
  * @experimental The API surface may change before stabilizing.
3085
3677
  */
@@ -3104,409 +3696,80 @@ var Agent = class Agent extends DurableObject {
3104
3696
  }
3105
3697
  broadcast(msg, without) {
3106
3698
  if (this._isFacet) {
3107
- this._cf_broadcastToParentSubAgent(msg, without);
3699
+ this._dynamicAgents.broadcastToParent(msg, without);
3108
3700
  return;
3109
3701
  }
3110
- for (const connection of this.lifecycle.getConnections()) {
3702
+ for (const connection of this._webSockets.getConnections()) {
3111
3703
  if (without?.includes(connection.id)) continue;
3112
- if (this._cf_connectionHasSubAgentTarget(connection)) continue;
3704
+ if (this._dynamicAgents.connectionHasChildTarget(connection)) continue;
3113
3705
  connection.send(msg);
3114
3706
  }
3115
3707
  }
3116
3708
  getConnection(id) {
3117
- if (this._isFacet) {
3118
- const stored = this._cf_virtualSubAgentConnections.get(id);
3119
- if (stored) return this._cf_createSubAgentBridgeConnection(stored.meta);
3120
- return;
3121
- }
3122
- const connection = this.lifecycle.getConnection(id);
3123
- if (!connection || this._cf_connectionHasSubAgentTarget(connection)) return;
3709
+ if (this._isFacet) return this._dynamicAgents.getVirtualConnection(id);
3710
+ const connection = this._webSockets.getConnection(id);
3711
+ if (!connection || this._dynamicAgents.connectionHasChildTarget(connection)) return;
3124
3712
  return connection;
3125
3713
  }
3126
3714
  *getConnections(tag) {
3127
3715
  if (this._isFacet) {
3128
- for (const stored of this._cf_virtualSubAgentConnections.values()) if (!tag || stored.meta.tags.includes(tag)) yield this._cf_createSubAgentBridgeConnection(stored.meta);
3716
+ yield* this._dynamicAgents.getVirtualConnections(tag);
3129
3717
  return;
3130
3718
  }
3131
- for (const connection of this.lifecycle.getConnections(tag)) {
3132
- if (this._cf_connectionHasSubAgentTarget(connection)) continue;
3719
+ for (const connection of this._webSockets.getConnections(tag)) {
3720
+ if (this._dynamicAgents.connectionHasChildTarget(connection)) continue;
3133
3721
  yield connection;
3134
3722
  }
3135
3723
  }
3136
- _cf_activeSubAgentBridge(connectionId) {
3137
- const context = this._cf_subAgentBridgeContext.getStore();
3138
- if (connectionId !== void 0 && context?.connectionId !== connectionId) return;
3139
- return context?.bridge;
3140
- }
3141
- /**
3142
- * Route a virtual sub-agent connection operation through its live frame
3143
- * bridge, or through the durable root Agent after that frame completes.
3144
- * All operations share one per-connection queue. Facet broadcasts wait for
3145
- * older queued operations; failures do not block later work.
3146
- */
3147
- _cf_routeSubAgentConnectionOperation(connectionId, operationName, operation) {
3148
- const activeBridge = this._cf_activeSubAgentBridge(connectionId);
3149
- const previousConnectionOperation = this._cf_subAgentConnectionOperationTails.get(connectionId);
3150
- let pending;
3151
- if (activeBridge && !previousConnectionOperation) try {
3152
- pending = Promise.resolve(operation(activeBridge)).then(() => {});
3153
- } catch (error) {
3154
- pending = Promise.reject(error);
3155
- }
3156
- else pending = (previousConnectionOperation ?? Promise.resolve()).then(async () => {
3157
- await operation(new RootSubAgentConnectionBridge(await this._rootAlarmOwner(), connectionId));
3158
- });
3159
- const completion = pending.catch((error) => {
3160
- this._cf_reportSubAgentConnectionOperationFailure(connectionId, operationName, error);
3161
- });
3162
- this._cf_subAgentConnectionOperationTails.set(connectionId, completion);
3163
- this.ctx.waitUntil(completion);
3164
- completion.then(() => {
3165
- if (this._cf_subAgentConnectionOperationTails.get(connectionId) === completion) this._cf_subAgentConnectionOperationTails.delete(connectionId);
3166
- });
3167
- }
3168
- /**
3169
- * Route a facet broadcast after every older connection operation.
3170
- *
3171
- * This barrier is intentionally one-way: facet startup can broadcast before
3172
- * a child connection has finished initializing its tags and protocol flags.
3173
- * Making those later connection operations wait would let the next frame
3174
- * observe stale root-owned metadata.
3175
- */
3176
- async _cf_routeSubAgentBroadcast(ownerPath, message, without, upstreamBridge) {
3177
- const activeBridge = upstreamBridge ?? this._cf_activeSubAgentBridge();
3178
- const previousOperations = /* @__PURE__ */ new Set([...this._cf_subAgentBroadcastOperationTail ? [this._cf_subAgentBroadcastOperationTail] : [], ...this._cf_subAgentConnectionOperationTails.values()]);
3179
- let pending;
3180
- if (activeBridge && previousOperations.size === 0) try {
3181
- pending = Promise.resolve(activeBridge.broadcast(ownerPath, message, without));
3182
- } catch (error) {
3183
- pending = Promise.reject(error);
3184
- }
3185
- else pending = Promise.all(previousOperations).then(async () => {
3186
- await (await this._rootAlarmOwner())._cf_broadcastToSubAgent(ownerPath, message, without);
3187
- });
3188
- const completion = pending.catch((error) => {
3189
- console.error("[Agent] Sub-agent broadcast operation failed:", {
3190
- operation: "broadcast",
3191
- error
3192
- });
3193
- });
3194
- this._cf_subAgentBroadcastOperationTail = completion;
3195
- this.ctx.waitUntil(completion);
3196
- completion.then(() => {
3197
- if (this._cf_subAgentBroadcastOperationTail === completion) this._cf_subAgentBroadcastOperationTail = void 0;
3198
- });
3199
- await completion;
3200
- }
3201
- _cf_reportSubAgentConnectionOperationFailure(connectionId, operation, error) {
3202
- console.error("[Agent] Sub-agent connection operation failed:", {
3203
- connectionId,
3204
- operation,
3205
- error
3206
- });
3207
- }
3208
- async _cf_broadcastToParentSubAgent(message, without) {
3209
- await this._cf_routeSubAgentBroadcast(this.selfPath, message, without);
3210
- }
3211
3724
  async _cf_broadcastToSubAgent(ownerPath, message, without) {
3212
- if (this._isFacet) {
3213
- await this._cf_routeSubAgentBroadcast(ownerPath, message, without);
3214
- return;
3215
- }
3216
- for (const connection of this.lifecycle.getConnections()) {
3217
- if (without?.includes(connection.id)) continue;
3218
- const targetPath = this._cf_subAgentTargetPath(connection);
3219
- if (!targetPath) continue;
3220
- if (!this._isSameAgentPath(targetPath, ownerPath)) continue;
3221
- connection.send(message);
3222
- }
3223
- }
3224
- async _cf_subAgentConnectionMetas(ownerPath) {
3225
- const metas = [];
3226
- for (const connection of this.lifecycle.getConnections()) {
3227
- const meta = this._cf_subAgentConnectionMetaForPath(connection, ownerPath);
3228
- if (meta) metas.push(meta);
3229
- }
3230
- return metas;
3231
- }
3232
- async _cf_sendToSubAgentConnection(connectionId, message) {
3233
- const connection = this.lifecycle.getConnection(connectionId);
3234
- if (!connection || !this._cf_connectionHasSubAgentTarget(connection)) return;
3235
- connection.send(message);
3236
- }
3237
- async _cf_closeSubAgentConnection(connectionId, code, reason) {
3238
- const connection = this.lifecycle.getConnection(connectionId);
3239
- if (!connection || !this._cf_connectionHasSubAgentTarget(connection)) return;
3240
- connection.close(code, reason);
3241
- }
3242
- async _cf_setSubAgentConnectionState(connectionId, state) {
3243
- const connection = this.lifecycle.getConnection(connectionId);
3244
- if (!connection || !this._cf_connectionHasSubAgentTarget(connection)) return null;
3245
- this._ensureConnectionWrapped(connection);
3246
- connection.setState(state);
3247
- return this._cf_getForwardedSubAgentState(connection);
3248
- }
3249
- _cf_subAgentConnectionMetaForPath(connection, ownerPath) {
3250
- this._ensureConnectionWrapped(connection);
3251
- const outerUri = this._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY);
3252
- if (typeof outerUri !== "string") return null;
3253
- const target = this._cf_subAgentPathFromOuterUri(outerUri, ownerPath);
3254
- if (!target) return null;
3255
- const raw = this._cf_getRawConnectionState(connection);
3256
- const rawTags = raw != null && typeof raw === "object" ? raw[CF_SUB_AGENT_TAGS_KEY] : void 0;
3257
- const tags = Array.isArray(rawTags) ? rawTags.filter((tag) => typeof tag === "string") : [...connection.tags];
3258
- return {
3259
- id: connection.id,
3260
- uri: target.uri,
3261
- tags,
3262
- state: this._cf_getForwardedSubAgentState(connection)
3263
- };
3264
- }
3265
- _cf_subAgentTargetPath(connection) {
3266
- this._ensureConnectionWrapped(connection);
3267
- const outerUri = this._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY);
3268
- if (typeof outerUri !== "string") return null;
3269
- return this._cf_subAgentPathFromOuterUri(outerUri)?.path ?? null;
3270
- }
3271
- _cf_subAgentPathFromOuterUri(outerUri, stopAt) {
3272
- const ctx = this.ctx;
3273
- const knownClasses = ctx.exports ? Object.keys(ctx.exports) : void 0;
3274
- const path = [...this.selfPath];
3275
- let currentUrl = outerUri;
3276
- while (true) {
3277
- const match = parseSubAgentPath(currentUrl, { knownClasses });
3278
- if (!match) break;
3279
- path.push({
3280
- className: match.childClass,
3281
- name: match.childName
3282
- });
3283
- const rewritten = new URL(currentUrl);
3284
- rewritten.pathname = match.remainingPath;
3285
- currentUrl = rewritten.toString();
3286
- if (stopAt && this._isSameAgentPath(path, stopAt)) return {
3287
- path,
3288
- uri: currentUrl
3289
- };
3290
- }
3291
- if (path.length === this.selfPath.length) return null;
3292
- if (stopAt) return null;
3293
- return {
3294
- path,
3295
- uri: currentUrl
3296
- };
3297
- }
3298
- _isSameAgentPath(a, b) {
3299
- if (a.length !== b.length) return false;
3300
- return a.every((step, index) => step.className === b[index]?.className && step.name === b[index]?.name);
3301
- }
3302
- _cf_connectionHasSubAgentTarget(connection) {
3303
- this._ensureConnectionWrapped(connection);
3304
- return typeof this._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY) === "string";
3305
- }
3306
- _cf_connectionTargetsSubAgent(connection) {
3307
- if (!connection.uri) return false;
3308
- const ctx = this.ctx;
3309
- return parseSubAgentPath(connection.uri, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 }) !== null;
3310
- }
3311
- /**
3312
- * Returns true when the current request is addressed to a child facet of
3313
- * this agent rather than to this agent itself.
3314
- *
3315
- * Chat-style subclasses wrap `onConnect` before the base Agent forwarding
3316
- * wrapper runs, so they need a request-level check to avoid sending their
3317
- * own protocol frames on sockets that are about to be forwarded to a child.
3318
- */
3319
- _cf_requestTargetsSubAgent(request) {
3320
- const ctx = this.ctx;
3321
- return parseSubAgentPath(request.url, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 }) !== null;
3322
- }
3323
- async _cf_forwardSubAgentWebSocketConnect(connection, request, options) {
3324
- const routed = await this._cf_resolveSubAgentConnection(connection, request, options);
3325
- if (!routed) return false;
3326
- await routed.child._cf_handleSubAgentWebSocketConnect(this._cf_createSubAgentConnectionBridge(connection), routed.meta);
3327
- return true;
3725
+ await this._dynamicAgents.broadcastToPath(ownerPath, message, without);
3328
3726
  }
3329
- _cf_createSubAgentConnectionBridge(connection) {
3330
- const upstreamBroadcastBridge = this._isFacet ? this._cf_activeSubAgentBridge(connection.id) : void 0;
3331
- return new SubAgentConnectionBridge(connection, (ownerPath, message, without) => {
3332
- if (upstreamBroadcastBridge) return this._cf_routeSubAgentBroadcast(ownerPath, message, without, upstreamBroadcastBridge);
3333
- return this._cf_broadcastToSubAgent(ownerPath, message, without);
3334
- });
3727
+ _cf_subAgentConnectionMetas(ownerPath) {
3728
+ return this._dynamicAgents.connectionMetas(ownerPath);
3335
3729
  }
3336
- async _cf_forwardSubAgentWebSocketMessage(connection, message, replyBridge) {
3337
- const routed = await this._cf_resolveSubAgentConnection(connection);
3338
- if (!routed) return false;
3339
- const bridge = this._cf_createSubAgentConnectionBridge(connection);
3340
- await routed.child._cf_handleSubAgentWebSocketMessage(message, bridge, routed.meta, replyBridge ?? bridge);
3341
- return true;
3730
+ _cf_sendToSubAgentConnection(connectionId, message) {
3731
+ return this._dynamicAgents.sendToConnection(connectionId, message);
3342
3732
  }
3343
- async _cf_forwardSubAgentWebSocketClose(connection, code, reason, wasClean) {
3344
- const routed = await this._cf_resolveSubAgentConnection(connection);
3345
- if (!routed) return false;
3346
- await routed.child._cf_handleSubAgentWebSocketClose(code, reason, wasClean, this._cf_createSubAgentConnectionBridge(connection), routed.meta);
3347
- return true;
3733
+ _cf_closeSubAgentConnection(connectionId, code, reason) {
3734
+ return this._dynamicAgents.closeConnection(connectionId, code, reason);
3348
3735
  }
3349
- async _cf_resolveSubAgentConnection(connection, request, options = { gate: false }) {
3350
- this._ensureConnectionWrapped(connection);
3351
- const outerUri = this._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY);
3352
- const uri = typeof outerUri === "string" ? outerUri : connection.uri;
3353
- if (!uri) return null;
3354
- const ctx = this.ctx;
3355
- let match = parseSubAgentPath(uri, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 });
3356
- if (!match) return null;
3357
- if (this._ParentClass.name === match.childClass && this.name === match.childName) {
3358
- const tailUri = new URL(uri);
3359
- tailUri.pathname = match.remainingPath;
3360
- match = parseSubAgentPath(tailUri.toString(), { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 });
3361
- if (!match) return null;
3362
- }
3363
- let forwardReq = request;
3364
- if (request && options.gate) {
3365
- const decision = await this.onBeforeSubAgent(request, {
3366
- className: match.childClass,
3367
- name: match.childName
3368
- });
3369
- if (decision instanceof Response) {
3370
- connection.close(1008, "Sub-agent connection rejected");
3371
- return null;
3372
- }
3373
- forwardReq = decision instanceof Request ? decision : request;
3374
- }
3375
- const child = await this._cf_resolveSubAgent(match.childClass, match.childName);
3376
- const childUri = new URL(forwardReq?.url ?? uri);
3377
- childUri.pathname = match.remainingPath;
3378
- const raw = this._cf_getRawConnectionState(connection);
3379
- const rawTags = raw != null && typeof raw === "object" ? raw[CF_SUB_AGENT_TAGS_KEY] : void 0;
3380
- const tags = Array.isArray(rawTags) ? rawTags.filter((tag) => typeof tag === "string") : [...connection.tags];
3381
- return {
3382
- child,
3383
- meta: {
3384
- id: connection.id,
3385
- uri: childUri.toString(),
3386
- tags,
3387
- state: this._cf_getForwardedSubAgentState(connection),
3388
- requestHeaders: forwardReq ? [...forwardReq.headers] : void 0
3389
- }
3390
- };
3736
+ _cf_setSubAgentConnectionState(connectionId, state) {
3737
+ return this._dynamicAgents.setConnectionState(connectionId, state);
3391
3738
  }
3392
- async _cf_handleSubAgentWebSocketConnect(bridge, meta) {
3393
- await this._cf_runWithSubAgentBridge(bridge, meta.id, async () => {
3394
- const connection = this._cf_createSubAgentBridgeConnection(meta);
3395
- const request = new Request(meta.uri ?? "http://placeholder/", { headers: meta.requestHeaders });
3396
- if (await this._cf_forwardSubAgentWebSocketConnect(connection, request, { gate: true })) return;
3397
- if (this.shouldConnectionBeReadonly(connection, { request })) this.setConnectionReadonly(connection, true);
3398
- if (!this.shouldSendProtocolMessages(connection, { request })) this._setConnectionNoProtocol(connection);
3399
- const childTags = await this.getConnectionTags(connection, { request });
3400
- connection.tags = [connection.id, ...childTags.filter((tag) => tag !== connection.id)];
3401
- this._cf_storeVirtualSubAgentConnection(connection);
3402
- await this.onConnect(connection, { request });
3403
- this._cf_storeVirtualSubAgentConnection(connection);
3404
- });
3739
+ _cf_connectionTargetsSubAgent(connection) {
3740
+ return this._dynamicAgents.connectionTargetsChild(connection);
3405
3741
  }
3406
- async _cf_handleSubAgentWebSocketMessage(message, bridge, meta, replyBridge = bridge) {
3407
- const connection = this._cf_createSubAgentBridgeConnection(meta);
3408
- this._cf_storeVirtualSubAgentConnection(connection);
3409
- const replyContext = { bridge: replyBridge };
3410
- try {
3411
- await subAgentRpcReplyContext.run(replyContext, () => this._cf_runWithSubAgentBridge(bridge, meta.id, () => this.onMessage(connection, message)));
3412
- } finally {
3413
- replyContext.bridge = void 0;
3414
- }
3742
+ /**
3743
+ * Returns true when the current request is addressed to a child facet of
3744
+ * this agent rather than to this agent itself.
3745
+ *
3746
+ * Chat-style subclasses wrap `onConnect` before the base Agent forwarding
3747
+ * wrapper runs, so they need a request-level check to avoid sending their
3748
+ * own protocol frames on sockets that are about to be forwarded to a child.
3749
+ */
3750
+ _cf_requestTargetsSubAgent(request) {
3751
+ return this._dynamicAgents.requestTargetsChild(request);
3415
3752
  }
3416
- async _cf_handleSubAgentWebSocketClose(code, reason, wasClean, bridge, meta) {
3417
- const connection = this._cf_createSubAgentBridgeConnection(meta);
3418
- this._cf_storeVirtualSubAgentConnection(connection);
3419
- await this._cf_runWithSubAgentBridge(bridge, meta.id, () => this.onClose(connection, code, reason, wasClean));
3420
- this._cf_virtualSubAgentConnections.delete(meta.id);
3753
+ _cf_forwardSubAgentWebSocketConnect(connection, request, options) {
3754
+ return this._dynamicAgents.forwardWebSocketConnect(connection, request, options);
3421
3755
  }
3422
- async _cf_runWithSubAgentBridge(bridge, connectionId, fn) {
3423
- const context = {
3424
- bridge,
3425
- connectionId
3426
- };
3427
- try {
3428
- return await this._cf_subAgentBridgeContext.run(context, fn);
3429
- } finally {
3430
- context.bridge = void 0;
3431
- }
3756
+ _cf_forwardSubAgentWebSocketMessage(connection, message, replyBridge) {
3757
+ return this._dynamicAgents.forwardWebSocketMessage(connection, message, replyBridge);
3432
3758
  }
3433
- _cf_createSubAgentBridgeConnection(meta) {
3434
- let stored = this._cf_virtualSubAgentConnections.get(meta.id);
3435
- if (stored) {
3436
- stored.meta = meta;
3437
- if (stored.connection) {
3438
- stored.connection.uri = meta.uri;
3439
- stored.connection.tags = meta.tags;
3440
- return stored.connection;
3441
- }
3442
- } else {
3443
- stored = { meta };
3444
- this._cf_virtualSubAgentConnections.set(meta.id, stored);
3445
- }
3446
- const owner = this;
3447
- const getStored = () => this._cf_virtualSubAgentConnections.get(meta.id) ?? stored;
3448
- const updateStoredState = (nextState) => {
3449
- const current = this._cf_virtualSubAgentConnections.get(meta.id);
3450
- if (current) current.meta = {
3451
- ...current.meta,
3452
- state: nextState
3453
- };
3454
- };
3455
- const connection = {
3456
- id: meta.id,
3457
- uri: meta.uri,
3458
- tags: meta.tags,
3459
- get state() {
3460
- return getStored().meta.state;
3461
- },
3462
- setState(next) {
3463
- const currentState = getStored().meta.state;
3464
- const state = typeof next === "function" ? next(currentState) : next;
3465
- updateStoredState(state);
3466
- owner._cf_routeSubAgentConnectionOperation(meta.id, "setState", (bridge) => bridge.setState(state));
3467
- return state;
3468
- },
3469
- send(message) {
3470
- owner._cf_routeSubAgentConnectionOperation(meta.id, "send", (bridge) => bridge.send(message));
3471
- },
3472
- close(code, reason) {
3473
- owner._cf_routeSubAgentConnectionOperation(meta.id, "close", (bridge) => bridge.close(code, reason));
3474
- },
3475
- addEventListener() {},
3476
- removeEventListener() {}
3477
- };
3478
- stored.connection = connection;
3479
- this._ensureConnectionWrapped(connection);
3480
- return connection;
3759
+ _cf_forwardSubAgentWebSocketClose(connection, code, reason, wasClean) {
3760
+ return this._dynamicAgents.forwardWebSocketClose(connection, code, reason, wasClean);
3481
3761
  }
3482
- _cf_storeVirtualSubAgentConnection(connection) {
3483
- this._unsafe_setConnectionFlag(connection, CF_SUB_AGENT_TAGS_KEY, [...connection.tags]);
3484
- const stored = this._cf_virtualSubAgentConnections.get(connection.id);
3485
- this._cf_virtualSubAgentConnections.set(connection.id, {
3486
- meta: {
3487
- id: connection.id,
3488
- uri: connection.uri,
3489
- tags: [...connection.tags],
3490
- state: this._cf_getRawConnectionState(connection)
3491
- },
3492
- connection: stored?.connection ?? connection
3493
- });
3762
+ _cf_handleSubAgentWebSocketConnect(bridge, meta) {
3763
+ return this._dynamicAgents.handleWebSocketConnect(bridge, meta);
3494
3764
  }
3495
- async _cf_hydrateSubAgentConnectionsFromRoot() {
3496
- if (!this._isFacet || this._parentPath.length === 0) return;
3497
- if (this._cf_rootResolvesToSelf()) return;
3498
- const metas = await (await this._rootAlarmOwner())._cf_subAgentConnectionMetas(this.selfPath);
3499
- for (const meta of metas) this._cf_virtualSubAgentConnections.set(meta.id, { meta });
3765
+ _cf_handleSubAgentWebSocketMessage(message, bridge, meta, replyBridge = bridge) {
3766
+ return this._dynamicAgents.handleWebSocketMessage(message, bridge, meta, replyBridge);
3500
3767
  }
3501
- _cf_getRawConnectionState(connection) {
3502
- this._ensureConnectionWrapped(connection);
3503
- return this._rawStateAccessors.get(connection)?.getRaw() ?? null;
3768
+ _cf_handleSubAgentWebSocketClose(code, reason, wasClean, bridge, meta) {
3769
+ return this._dynamicAgents.handleWebSocketClose(code, reason, wasClean, bridge, meta);
3504
3770
  }
3505
- _cf_getForwardedSubAgentState(connection) {
3506
- const raw = this._cf_getRawConnectionState(connection);
3507
- if (raw == null || typeof raw !== "object") return raw;
3508
- const { [CF_SUB_AGENT_OUTER_URL_KEY]: _, ...rest } = raw;
3509
- return Object.keys(rest).length > 0 ? rest : null;
3771
+ _cf_hydrateSubAgentConnectionsFromRoot() {
3772
+ return this._dynamicAgents.hydrateConnectionsFromRoot();
3510
3773
  }
3511
3774
  /**
3512
3775
  * Parent-side middleware hook. Fires before a request is
@@ -3543,7 +3806,7 @@ var Agent = class Agent extends DurableObject {
3543
3806
  * class Inbox extends Agent {
3544
3807
  * override async onBeforeSubAgent(req, { className, name }) {
3545
3808
  * // Strict registry gate
3546
- * if (!this.hasSubAgent(className, name)) {
3809
+ * if (!this.dynamicAgents.has(className, name)) {
3547
3810
  * return new Response("Not found", { status: 404 });
3548
3811
  * }
3549
3812
  * }
@@ -3557,27 +3820,8 @@ var Agent = class Agent extends DurableObject {
3557
3820
  *
3558
3821
  * @internal
3559
3822
  */
3560
- async _cf_forwardToFacet(req, match) {
3561
- let fetcher;
3562
- try {
3563
- fetcher = await this._cf_resolveSubAgent(match.childClass, match.childName);
3564
- } catch (err) {
3565
- const message = err instanceof Error ? err.message : String(err);
3566
- console.error("[agents] sub-agent route failed:", message);
3567
- if (/null character/i.test(message) || /reserved/i.test(message)) return new Response("Bad Request", { status: 400 });
3568
- return new Response("Not Found", { status: 404 });
3569
- }
3570
- const rewritten = new URL(req.url);
3571
- rewritten.pathname = match.remainingPath;
3572
- const forwardedHeaders = new Headers(req.headers);
3573
- const forwardedInit = {
3574
- method: req.method,
3575
- headers: forwardedHeaders
3576
- };
3577
- if (req.headers.get("Upgrade")?.toLowerCase() === "websocket") forwardedHeaders.set(SUB_AGENT_OUTER_URL_HEADER, req.url);
3578
- if (req.body && req.method !== "GET" && req.method !== "HEAD") forwardedInit.body = req.body;
3579
- const forwarded = new Request(rewritten, forwardedInit);
3580
- return fetcher.fetch(forwarded);
3823
+ _cf_forwardToFacet(req, match) {
3824
+ return this._dynamicAgents.forward(req, match);
3581
3825
  }
3582
3826
  /**
3583
3827
  * Bridge method used by `getSubAgentByName`. Resolves the facet
@@ -3586,9 +3830,8 @@ var Agent = class Agent extends DurableObject {
3586
3830
  *
3587
3831
  * @internal
3588
3832
  */
3589
- async _cf_invokeSubAgent(className, name, method, args) {
3590
- const stub = await this._cf_resolveSubAgent(className, name);
3591
- return await this._cf_invokeStubMethod(stub, className, method, args);
3833
+ _cf_invokeSubAgent(className, name, method, args) {
3834
+ return this._dynamicAgents.invoke(className, name, method, args);
3592
3835
  }
3593
3836
  /**
3594
3837
  * Bridge method used by `parentAgent()` when the requested parent is
@@ -3598,20 +3841,8 @@ var Agent = class Agent extends DurableObject {
3598
3841
  *
3599
3842
  * @internal
3600
3843
  */
3601
- async _cf_invokeSubAgentPath(path, method, args) {
3602
- const [self, next, ...rest] = path;
3603
- if (!self) throw new Error(`Sub-agent path invocation requires a non-empty path.`);
3604
- const ownClassName = this.constructor.name;
3605
- if (self.className !== ownClassName || self.name !== this.name) throw new Error(`Sub-agent path invocation reached ${ownClassName}("${this.name}") but expected ${self.className}("${self.name}").`);
3606
- if (!next) return await this._cf_invokeStubMethod(this, this.constructor.name, method, args);
3607
- const child = await this._cf_resolveSubAgent(next.className, next.name);
3608
- if (rest.length === 0) return await this._cf_invokeStubMethod(child, next.className, method, args);
3609
- return await child._cf_invokeSubAgentPath([next, ...rest], method, args);
3610
- }
3611
- async _cf_invokeStubMethod(stub, className, method, args) {
3612
- const handle = stub;
3613
- if (typeof handle[method] !== "function") throw new Error(`Method "${method}" not found on ${className}.`);
3614
- return await handle[method](...args);
3844
+ _cf_invokeSubAgentPath(path, method, args) {
3845
+ return this._dynamicAgents.invokePath(path, method, args);
3615
3846
  }
3616
3847
  /**
3617
3848
  * Initialize this agent as a facet in a single RPC.
@@ -3637,18 +3868,8 @@ var Agent = class Agent extends DurableObject {
3637
3868
  *
3638
3869
  * @internal Called by {@link subAgent}.
3639
3870
  */
3640
- async _cf_initAsFacet(name, parentPath = [], identityName = name) {
3641
- const routedName = this.lifecycle.name;
3642
- if (routedName !== identityName) throw new Error(`Facet bootstrap mismatch: expected routed identity "${identityName}" but got "${routedName}". This usually means the parent passed the wrong id to ctx.facets.get(). See _cf_resolveSubAgent.`);
3643
- this._isFacet = true;
3644
- this._facetName = name;
3645
- this._parentPath = parentPath;
3646
- await Promise.all([
3647
- this.ctx.storage.put("cf_agents_is_facet", true),
3648
- this.ctx.storage.put("cf_agents_facet_name", name),
3649
- this.ctx.storage.put("cf_agents_parent_path", parentPath)
3650
- ]);
3651
- await this.__unsafe_ensureInitialized();
3871
+ _cf_initAsFacet(name, parentPath = [], identityName = name) {
3872
+ return this._dynamicAgents.init(name, parentPath, identityName);
3652
3873
  }
3653
3874
  get name() {
3654
3875
  const routedName = this.lifecycle.name;
@@ -3796,9 +4017,11 @@ var Agent = class Agent extends DurableObject {
3796
4017
  * const searcher = await this.subAgent(SearchAgent, "main-search");
3797
4018
  * const results = await searcher.search("cloudflare agents");
3798
4019
  * ```
4020
+ *
4021
+ * @deprecated Use {@link Agent.dynamicAgents | this.dynamicAgents.get()} instead.
3799
4022
  */
3800
4023
  async subAgent(cls, name) {
3801
- return await this._cf_resolveSubAgent(cls.name, name);
4024
+ return this.dynamicAgents.get(cls, name);
3802
4025
  }
3803
4026
  async onAgentToolStart(_run) {}
3804
4027
  async onAgentToolFinish(_run, _result) {}
@@ -5185,46 +5408,22 @@ var Agent = class Agent extends DurableObject {
5185
5408
  *
5186
5409
  * @internal
5187
5410
  */
5188
- async _cf_resolveSubAgent(className, name) {
5189
- const ctx = this.ctx;
5190
- if (!ctx.facets || !ctx.exports) throw new Error("subAgent() is not supported in this runtime — `ctx.facets` / `ctx.exports` are unavailable. Update to the latest `compatibility_date` in your wrangler.jsonc.");
5191
- if (camelCaseToKebabCase(className) === "sub") throw new Error(`Sub-agent class name "${className}" kebab-cases to "sub", which collides with the reserved URL separator — rename the class (e.g. "SubThing" or "Subtask").`);
5192
- const Cls = ctx.exports[className];
5193
- if (!Cls) throw new Error(`Sub-agent class "${className}" not found in worker exports. Make sure the class is exported from your worker entry point and that the export name matches the class name.`);
5194
- if (name.includes("\0")) throw new Error(`Sub-agent name contains null character (\\0), which is reserved.`);
5195
- const facetKey = `${className}\0${name}`;
5196
- const childParentPath = this.selfPath;
5197
- const childPath = [...childParentPath, {
5198
- className,
5199
- name
5200
- }];
5201
- const rootClassName = this._parentPath[0]?.className ?? this.constructor.name;
5202
- const rootNs = ctx.exports[rootClassName];
5203
- if (!rootNs?.idFromName) {
5204
- const minificationHint = /^_*[a-z][a-z0-9]{0,2}$/.test(rootClassName) ? ` The class name "${rootClassName}" looks minified — make sure your bundler preserves class names (e.g. esbuild's \`keepNames: true\`).` : "";
5205
- throw new Error(`Sub-agent bootstrap requires the root agent class "${rootClassName}" to be available as a Durable Object namespace, but ctx.exports["${rootClassName}"] is missing or doesn't expose idFromName.${minificationHint} Make sure the root agent class is exported under that class name and registered in your wrangler.jsonc durable_objects.bindings.`);
5206
- }
5207
- const identity = await this._cf_subAgentIdentity(className, name, childPath);
5208
- const facetId = rootNs.idFromName(identity.name);
5209
- const stub = ctx.facets.get(facetKey, () => ({
5210
- class: Cls,
5211
- id: facetId
5212
- }));
5213
- this._recordSubAgent(className, name, identity);
5214
- try {
5215
- await runInInvocation({
5216
- agent: this,
5217
- connection: void 0,
5218
- request: void 0,
5219
- email: void 0
5220
- }, async () => {
5221
- await stub._cf_initAsFacet(name, childParentPath, identity.name);
5222
- });
5223
- } catch (error) {
5224
- if (!identity.existing) this._forgetSubAgent(className, name);
5225
- throw error;
5226
- }
5227
- return stub;
5411
+ _cf_resolveSubAgent(className, name) {
5412
+ return this._dynamicAgents.resolve(className, name);
5413
+ }
5414
+ /**
5415
+ * Run `body` in a fresh invocation scope with no native request/
5416
+ * connection context attached, so a child-facet RPC never sees
5417
+ * parent-owned I/O handles.
5418
+ * @internal
5419
+ */
5420
+ _runFacetInitInvocation(body) {
5421
+ return runInInvocation({
5422
+ agent: this,
5423
+ connection: void 0,
5424
+ request: void 0,
5425
+ email: void 0
5426
+ }, body);
5228
5427
  }
5229
5428
  /**
5230
5429
  * Forcefully abort a running sub-agent. The child stops executing
@@ -5237,12 +5436,11 @@ var Agent = class Agent extends DurableObject {
5237
5436
  * @param cls The Agent subclass used when creating the child
5238
5437
  * @param name Name of the child to abort
5239
5438
  * @param reason Error thrown to pending/future RPC callers
5439
+ *
5440
+ * @deprecated Use {@link Agent.dynamicAgents | this.dynamicAgents.abort()} instead.
5240
5441
  */
5241
5442
  abortSubAgent(cls, name, reason) {
5242
- const ctx = this.ctx;
5243
- if (!ctx.facets) throw new Error("abortSubAgent() is not supported in this runtime — `ctx.facets` is unavailable. Update to the latest `compatibility_date` in your wrangler.jsonc.");
5244
- const facetKey = `${cls.name}\0${name}`;
5245
- ctx.facets.abort(facetKey, reason);
5443
+ this.dynamicAgents.abort(cls, name, reason);
5246
5444
  }
5247
5445
  /**
5248
5446
  * Delete a sub-agent: abort it if running, then permanently wipe its
@@ -5252,117 +5450,18 @@ var Agent = class Agent extends DurableObject {
5252
5450
  *
5253
5451
  * @param cls The Agent subclass used when creating the child
5254
5452
  * @param name Name of the child to delete
5453
+ *
5454
+ * @deprecated Use {@link Agent.dynamicAgents | this.dynamicAgents.delete()} instead.
5255
5455
  */
5256
- async deleteSubAgent(cls, name) {
5257
- const ctx = this.ctx;
5258
- if (!ctx.facets) throw new Error("deleteSubAgent() is not supported in this runtime — `ctx.facets` is unavailable. Update to the latest `compatibility_date` in your wrangler.jsonc.");
5259
- const facetKey = `${cls.name}\0${name}`;
5260
- const childPath = [...this.selfPath, {
5261
- className: cls.name,
5262
- name
5263
- }];
5264
- if (this._isFacet) await (await this._rootAlarmOwner())._cf_cleanupFacetPrefix(childPath);
5265
- else await this._cf_cleanupFacetPrefix(childPath);
5266
- try {
5267
- ctx.facets.delete(facetKey);
5268
- } catch {}
5269
- this._forgetSubAgent(cls.name, name);
5270
- }
5271
- _addColumnIfNotExists(sql) {
5272
- try {
5273
- this.ctx.storage.sql.exec(sql);
5274
- } catch (e) {
5275
- if (!(e instanceof Error ? e.message : String(e)).toLowerCase().includes("duplicate column")) throw e;
5276
- }
5277
- }
5278
- /** @internal */
5279
- _ensureSubAgentRegistry() {
5280
- if (this._subAgentRegistryReady) return;
5281
- this.sql`
5282
- CREATE TABLE IF NOT EXISTS cf_agents_sub_agents (
5283
- class TEXT NOT NULL,
5284
- name TEXT NOT NULL,
5285
- created_at INTEGER NOT NULL,
5286
- identity_version TEXT,
5287
- identity_name TEXT,
5288
- PRIMARY KEY (class, name)
5289
- )
5290
- `;
5291
- this._addColumnIfNotExists("ALTER TABLE cf_agents_sub_agents ADD COLUMN identity_version TEXT");
5292
- this._addColumnIfNotExists("ALTER TABLE cf_agents_sub_agents ADD COLUMN identity_name TEXT");
5293
- this._subAgentRegistryReady = true;
5294
- }
5295
- /** @internal */
5296
- _recordSubAgent(className, name, identity) {
5297
- this._ensureSubAgentRegistry();
5298
- this.sql`
5299
- INSERT OR IGNORE INTO cf_agents_sub_agents
5300
- (class, name, created_at, identity_version, identity_name)
5301
- VALUES
5302
- (${className}, ${name}, ${Date.now()}, ${identity.version}, ${identity.name})
5303
- `;
5304
- }
5305
- /** @internal */
5306
- _subAgentRegistryRow(className, name) {
5307
- this._ensureSubAgentRegistry();
5308
- return this.sql`
5309
- SELECT identity_version, identity_name
5310
- FROM cf_agents_sub_agents
5311
- WHERE class = ${className} AND name = ${name}
5312
- LIMIT 1
5313
- `[0] ?? null;
5314
- }
5315
- async _cf_subAgentIdentity(className, name, childPath) {
5316
- const row = this._subAgentRegistryRow(className, name);
5317
- if (row) {
5318
- if (row.identity_version === SUB_AGENT_IDENTITY_VERSION_PATH_V2 && typeof row.identity_name === "string") return {
5319
- version: SUB_AGENT_IDENTITY_VERSION_PATH_V2,
5320
- name: row.identity_name,
5321
- existing: true
5322
- };
5323
- return {
5324
- version: SUB_AGENT_IDENTITY_VERSION_LEGACY,
5325
- name,
5326
- existing: true
5327
- };
5328
- }
5329
- return {
5330
- version: SUB_AGENT_IDENTITY_VERSION_PATH_V2,
5331
- name: pathV2IdentityName(name, await sha256Hex(JSON.stringify(childPath))),
5332
- existing: false
5333
- };
5334
- }
5335
- /** @internal */
5336
- _forgetSubAgent(className, name) {
5337
- this._ensureSubAgentRegistry();
5338
- this.sql`
5339
- DELETE FROM cf_agents_sub_agents
5340
- WHERE class = ${className} AND name = ${name}
5341
- `;
5456
+ deleteSubAgent(cls, name) {
5457
+ return this.dynamicAgents.delete(cls, name);
5342
5458
  }
5343
5459
  hasSubAgent(classOrName, name) {
5344
- const className = typeof classOrName === "string" ? classOrName : classOrName.name;
5345
- this._ensureSubAgentRegistry();
5346
- return (this.sql`
5347
- SELECT COUNT(*) AS n FROM cf_agents_sub_agents
5348
- WHERE class = ${className} AND name = ${name}
5349
- `[0]?.n ?? 0) > 0;
5460
+ return typeof classOrName === "string" ? this.dynamicAgents.has(classOrName, name) : this.dynamicAgents.has(classOrName, name);
5350
5461
  }
5351
5462
  listSubAgents(classOrName) {
5352
- const className = typeof classOrName === "string" ? classOrName : classOrName?.name;
5353
- this._ensureSubAgentRegistry();
5354
- return (className ? this.sql`
5355
- SELECT class, name, created_at FROM cf_agents_sub_agents
5356
- WHERE class = ${className}
5357
- ORDER BY created_at ASC
5358
- ` : this.sql`
5359
- SELECT class, name, created_at FROM cf_agents_sub_agents
5360
- ORDER BY created_at ASC
5361
- `).map((r) => ({
5362
- className: r.class,
5363
- name: r.name,
5364
- createdAt: r.created_at
5365
- }));
5463
+ if (typeof classOrName === "string" || classOrName === void 0) return this.dynamicAgents.list(classOrName);
5464
+ return this.dynamicAgents.list(classOrName);
5366
5465
  }
5367
5466
  /**
5368
5467
  * Destroy the Agent, removing all state and scheduled tasks.
@@ -5392,7 +5491,7 @@ var Agent = class Agent extends DurableObject {
5392
5491
  await this.ctx.storage.deleteAll();
5393
5492
  this._destroyed = true;
5394
5493
  setTimeout(() => {
5395
- this.ctx.abort("destroyed");
5494
+ abortWithoutAlarmRetry(this.ctx, "destroyed");
5396
5495
  }, 0);
5397
5496
  this._emit("destroy");
5398
5497
  }
@@ -5423,7 +5522,12 @@ var Agent = class Agent extends DurableObject {
5423
5522
  }
5424
5523
  const destroyAt = Date.now() + DESTROY_ALARM_DELAY_MS;
5425
5524
  await this.ctx.storage.put(DESTROY_PENDING_KEY, destroyAt);
5426
- await this.lifecycle.rearmAlarm();
5525
+ await this.lifecycle.jobs.push({
5526
+ id: HOST_JOB_DESTROY_ID,
5527
+ fn: "destroy",
5528
+ time: destroyAt,
5529
+ exclusive: true
5530
+ });
5427
5531
  }
5428
5532
  /**
5429
5533
  * Whether a (deferred or interrupted) destroy is pending. Reads the
@@ -5444,32 +5548,14 @@ var Agent = class Agent extends DurableObject {
5444
5548
  * @returns True if the method is marked as callable
5445
5549
  */
5446
5550
  _isCallable(method) {
5447
- return callableMetadata.has(this[method]);
5551
+ return isCallableMethod(this[method]);
5448
5552
  }
5449
5553
  /**
5450
5554
  * Get all methods marked as callable on this Agent
5451
5555
  * @returns A map of method names to their metadata
5452
5556
  */
5453
5557
  getCallableMethods() {
5454
- const result = /* @__PURE__ */ new Map();
5455
- let prototype = Object.getPrototypeOf(this);
5456
- while (prototype && prototype !== Object.prototype) {
5457
- for (const name of Object.getOwnPropertyNames(prototype)) {
5458
- if (name === "constructor") continue;
5459
- if (result.has(name)) continue;
5460
- try {
5461
- const fn = prototype[name];
5462
- if (typeof fn === "function") {
5463
- const meta = callableMetadata.get(fn);
5464
- if (meta) result.set(name, meta);
5465
- }
5466
- } catch (e) {
5467
- if (!(e instanceof TypeError)) throw e;
5468
- }
5469
- }
5470
- prototype = Object.getPrototypeOf(prototype);
5471
- }
5472
- return result;
5558
+ return new Map(decoratedMethods(this));
5473
5559
  }
5474
5560
  /**
5475
5561
  * Start a workflow and track it in this Agent's database.
@@ -6447,7 +6533,6 @@ var Agent = class Agent extends DurableObject {
6447
6533
  }
6448
6534
  };
6449
6535
  Agent.options = {};
6450
- Agent._CF_OOM_ALARM_STRIKES_KEY = "cf_agents:oom_alarm_strikes";
6451
6536
  const wrappedClasses = /* @__PURE__ */ new Set();
6452
6537
  var _email = /* @__PURE__ */ new WeakMap();
6453
6538
  _Symbol$dispose = Symbol.dispose;
@@ -6542,11 +6627,9 @@ var StreamingResponse = class {
6542
6627
  this._id = id;
6543
6628
  }
6544
6629
  _send(response) {
6545
- const state = facetStreamingResponseDeliveryStates.get(this);
6546
- if (!state) return sendRpcResponseIfOpen(this._connection, response);
6547
- const delivery = sendFacetRpcResponseIfOpen(state.replyTarget, response);
6548
- trackFacetStreamingResponseDelivery(this, delivery.completion);
6549
- return delivery.sent;
6630
+ const facetSent = sendFacetStreamingResponse(this, response);
6631
+ if (facetSent !== null) return facetSent;
6632
+ return sendRpcResponseIfOpen(this._connection, response);
6550
6633
  }
6551
6634
  /**
6552
6635
  * Whether the stream has been closed (via end() or error())
@@ -6608,6 +6691,6 @@ var StreamingResponse = class {
6608
6691
  }
6609
6692
  };
6610
6693
  //#endregion
6611
- export { getCurrentAgent as a, callable as i, DEFAULT_AGENT_STATIC_OPTIONS as n, routeAgentEmail as o, StreamingResponse as r, unstable_callable as s, Agent as t };
6694
+ export { routeAgentEmail as a, getCurrentAgent as i, DEFAULT_AGENT_STATIC_OPTIONS as n, StreamingResponse as r, Agent as t };
6612
6695
 
6613
- //# sourceMappingURL=src-5W6JNKVb.js.map
6696
+ //# sourceMappingURL=src-BNU3ZiJM.js.map