agents 0.11.4 → 0.11.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +44 -1
  2. package/dist/browser/ai.js +1 -1
  3. package/dist/browser/index.js +1 -1
  4. package/dist/browser/tanstack-ai.js +1 -1
  5. package/dist/browser/tanstack-ai.js.map +1 -1
  6. package/dist/chat/index.d.ts +169 -23
  7. package/dist/chat/index.js +232 -1
  8. package/dist/chat/index.js.map +1 -1
  9. package/dist/{classPrivateFieldGet2-DAZNVUKb.js → classPrivateFieldGet2-Bqby-AHD.js} +5 -5
  10. package/dist/{client-B_xdiZbn.js → client-D1kFXo80.js} +9 -1
  11. package/dist/{client-B_xdiZbn.js.map → client-D1kFXo80.js.map} +1 -1
  12. package/dist/client.d.ts +1 -1
  13. package/dist/client.js +2 -2
  14. package/dist/client.js.map +1 -1
  15. package/dist/compaction-helpers-C_cN3z55.js.map +1 -1
  16. package/dist/email.js.map +1 -1
  17. package/dist/experimental/memory/session/index.js.map +1 -1
  18. package/dist/{index-D9qo_Inc.d.ts → index-BM7Nk0QD.d.ts} +378 -21
  19. package/dist/index.d.ts +12 -2
  20. package/dist/index.js +329 -30
  21. package/dist/index.js.map +1 -1
  22. package/dist/mcp/client.d.ts +1 -1
  23. package/dist/mcp/client.js +1 -1
  24. package/dist/mcp/do-oauth-client-provider.js.map +1 -1
  25. package/dist/mcp/index.d.ts +1 -1
  26. package/dist/mcp/index.js +1 -1
  27. package/dist/mcp/index.js.map +1 -1
  28. package/dist/mcp/x402.js.map +1 -1
  29. package/dist/react.d.ts +53 -5
  30. package/dist/react.js +47 -7
  31. package/dist/react.js.map +1 -1
  32. package/dist/{retries-JlwH9mnV.d.ts → retries-fLD8cGNf.d.ts} +1 -1
  33. package/dist/retries.d.ts +1 -1
  34. package/dist/{shared-BovR6hRc.js → shared-mfBbxjS1.js} +3 -3
  35. package/dist/{shared-BovR6hRc.js.map → shared-mfBbxjS1.js.map} +1 -1
  36. package/dist/sub-routing.d.ts +14 -0
  37. package/dist/sub-routing.js +171 -0
  38. package/dist/sub-routing.js.map +1 -0
  39. package/dist/utils.d.ts +21 -1
  40. package/dist/utils.js +36 -1
  41. package/dist/utils.js.map +1 -1
  42. package/dist/workflows.d.ts +1 -1
  43. package/dist/workflows.js.map +1 -1
  44. package/package.json +6 -19
package/dist/index.js CHANGED
@@ -2,9 +2,10 @@ import { MessageType } from "./types.js";
2
2
  import { camelCaseToKebabCase } from "./utils.js";
3
3
  import { createHeaderBasedEmailResolver, signAgentHeaders } from "./email.js";
4
4
  import { __DO_NOT_USE_WILL_BREAK__agentContext } from "./internal_context.js";
5
- import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-DAZNVUKb.js";
5
+ import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-Bqby-AHD.js";
6
+ import { SUB_PREFIX, getSubAgentByName, parseSubAgentPath, routeSubAgentRequest } from "./sub-routing.js";
6
7
  import { isErrorRetryable, tryN, validateRetryOptions } from "./retries.js";
7
- import { o as RPC_DO_PREFIX, r as MCPConnectionState, s as DisposableStore, t as MCPClientManager } from "./client-B_xdiZbn.js";
8
+ import { o as RPC_DO_PREFIX, r as MCPConnectionState, s as DisposableStore, t as MCPClientManager } from "./client-D1kFXo80.js";
8
9
  import { DurableObjectOAuthClientProvider } from "./mcp/do-oauth-client-provider.js";
9
10
  import { genericObservability } from "./observability/index.js";
10
11
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -79,6 +80,14 @@ const STATE_ROW_ID = "cf_state_row_id";
79
80
  const STATE_WAS_CHANGED = "cf_state_was_changed";
80
81
  const DEFAULT_STATE = {};
81
82
  /**
83
+ * Validate that a stored `parentPath` has the expected shape. Used
84
+ * when restoring from DO storage to guard against corrupted data.
85
+ */
86
+ function isValidParentPath(value) {
87
+ if (!Array.isArray(value)) return false;
88
+ return value.every((entry) => entry != null && typeof entry === "object" && typeof entry.className === "string" && typeof entry.name === "string");
89
+ }
90
+ /**
82
91
  * Internal key used to store the readonly flag in connection state.
83
92
  * Prefixed with _cf_ to avoid collision with user state keys.
84
93
  */
@@ -148,10 +157,22 @@ const _sendIdentityWarnedClasses = /* @__PURE__ */ new WeakSet();
148
157
  * Child classes can override specific options without spreading.
149
158
  */
150
159
  const DEFAULT_AGENT_STATIC_OPTIONS = {
160
+ /** Whether the Agent should hibernate when inactive */
151
161
  hibernate: true,
162
+ /** Whether to send identity (name, agent) to clients on connect */
152
163
  sendIdentityOnConnect: true,
164
+ /**
165
+ * Timeout in seconds before a running interval schedule is considered "hung"
166
+ * and force-reset. Increase this if you have callbacks that legitimately
167
+ * take longer than 30 seconds.
168
+ */
153
169
  hungScheduleTimeoutSeconds: 30,
170
+ /**
171
+ * Interval in milliseconds for keepAlive() alarm heartbeats.
172
+ * Lower values mean faster recovery after eviction but more frequent alarms.
173
+ */
154
174
  keepAliveIntervalMs: DEFAULT_KEEP_ALIVE_INTERVAL_MS,
175
+ /** Default retry options for schedule(), queue(), and this.retry() */
155
176
  retry: {
156
177
  maxAttempts: 3,
157
178
  baseDelayMs: 100,
@@ -459,6 +480,7 @@ var Agent = class Agent extends Server {
459
480
  this._rawStateAccessors = /* @__PURE__ */ new WeakMap();
460
481
  this._persistenceHookMode = "none";
461
482
  this._isFacet = false;
483
+ this._parentPath = [];
462
484
  this._insideOnStart = false;
463
485
  this._warnedScheduleInOnStart = /* @__PURE__ */ new Set();
464
486
  this._keepAliveRefs = 0;
@@ -468,6 +490,7 @@ var Agent = class Agent extends Server {
468
490
  this.initialState = DEFAULT_STATE;
469
491
  this.observability = genericObservability;
470
492
  this._flushingQueue = false;
493
+ this._subAgentRegistryReady = false;
471
494
  if (!wrappedClasses.has(this.constructor)) {
472
495
  this._autoWrapCustomMethods();
473
496
  wrappedClasses.add(this.constructor);
@@ -621,7 +644,7 @@ var Agent = class Agent extends Server {
621
644
  if (this.shouldSendProtocolMessages(connection, ctx)) {
622
645
  if (this._resolvedOptions.sendIdentityOnConnect) {
623
646
  const ctor = this.constructor;
624
- if (ctor.options?.sendIdentityOnConnect === void 0 && !_sendIdentityWarnedClasses.has(ctor)) {
647
+ if (ctor.options?.sendIdentityOnConnect === void 0 && !_sendIdentityWarnedClasses.has(ctor) && !this._isFacet) {
625
648
  if (!new URL(ctx.request.url).pathname.includes(this.name)) {
626
649
  _sendIdentityWarnedClasses.add(ctor);
627
650
  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.`);
@@ -671,6 +694,8 @@ var Agent = class Agent extends Server {
671
694
  email: void 0
672
695
  }, async () => {
673
696
  if (await this.ctx.storage.get("cf_agents_is_facet")) this._isFacet = true;
697
+ const storedParentPath = await this.ctx.storage.get("cf_agents_parent_path");
698
+ if (isValidParentPath(storedParentPath)) this._parentPath = storedParentPath;
674
699
  await this._tryCatch(async () => {
675
700
  await this.mcp.restoreConnectionsFromStorage(this.name);
676
701
  await this._restoreRpcMcpServers();
@@ -718,23 +743,10 @@ var Agent = class Agent extends Server {
718
743
  * @param excludeIds Additional connection IDs to exclude (e.g. the source)
719
744
  */
720
745
  _broadcastProtocol(msg, excludeIds = []) {
721
- if (this._isFacet) return;
722
746
  const exclude = [...excludeIds];
723
747
  for (const conn of this.getConnections()) if (!this.isConnectionProtocolEnabled(conn)) exclude.push(conn.id);
724
748
  this.broadcast(msg, exclude);
725
749
  }
726
- /**
727
- * When running as a facet, the parent DO owns the WebSocket registry
728
- * (`ctx.getWebSockets()`). Iterating from the child isolate throws
729
- * "Cannot perform I/O on behalf of a different Durable Object".
730
- * Downstream callers (e.g. chat-streaming paths) invoke
731
- * `this.broadcast()` directly, bypassing `_broadcastProtocol`'s
732
- * guard, so override at the base to catch every path.
733
- */
734
- broadcast(msg, without) {
735
- if (this._isFacet) return;
736
- super.broadcast(msg, without);
737
- }
738
750
  _setStateInternal(nextState, source = "server") {
739
751
  this.validateStateChange(nextState, source);
740
752
  this._state = nextState;
@@ -1690,6 +1702,12 @@ var Agent = class Agent extends Server {
1690
1702
  * alarm system, without creating schedule rows or emitting observability
1691
1703
  * events. Configure via `static options = { keepAliveIntervalMs: 5000 }`.
1692
1704
  *
1705
+ * No-op on facets. Facets share the parent's isolate and don't
1706
+ * need a separate alarm heartbeat — the parent's own activity,
1707
+ * any open WebSocket to the facet, and any in-flight Promise
1708
+ * already keep the shared machine alive for the duration of
1709
+ * real work.
1710
+ *
1693
1711
  * @example
1694
1712
  * ```ts
1695
1713
  * const dispose = await this.keepAlive();
@@ -1701,7 +1719,7 @@ var Agent = class Agent extends Server {
1701
1719
  * ```
1702
1720
  */
1703
1721
  async keepAlive() {
1704
- if (this._isFacet) throw new Error("keepAlive() is not supported in sub-agents. Use keepAlive() from the parent agent instead.");
1722
+ if (this._isFacet) return () => {};
1705
1723
  this._keepAliveRefs++;
1706
1724
  if (this._keepAliveRefs === 1) await this._scheduleNextAlarm();
1707
1725
  let disposed = false;
@@ -1888,7 +1906,10 @@ var Agent = class Agent extends Server {
1888
1906
  * Executes any scheduled tasks that are due.
1889
1907
  *
1890
1908
  * Calls super.alarm() first to ensure PartyServer's #ensureInitialized()
1891
- * runs, which hydrates this.name from storage and calls onStart() if needed.
1909
+ * runs, which resolves this.name from ctx.id.name (including for
1910
+ * facets, which are spawned with an explicit id so they have their
1911
+ * own ctx.id.name; pre-2026-03-15 alarms fall back to the legacy
1912
+ * __ps_name storage record) and calls onStart() if needed.
1892
1913
  *
1893
1914
  * @remarks
1894
1915
  * To schedule a task, please use the `this.schedule` method instead.
@@ -2001,6 +2022,108 @@ var Agent = class Agent extends Server {
2001
2022
  await this._scheduleNextAlarm();
2002
2023
  }
2003
2024
  /**
2025
+ * Intercept incoming HTTP/WS requests whose URL contains a
2026
+ * `/sub/{child-class}/{child-name}` marker and forward them to
2027
+ * the facet. The `onBeforeSubAgent` hook fires first (authorize,
2028
+ * mutate, or short-circuit). If the hook doesn't return a
2029
+ * Response, the framework resolves the facet and hands the
2030
+ * request off.
2031
+ *
2032
+ * After a WebSocket upgrade completes, subsequent frames route
2033
+ * directly to the child — the parent is only on the path for the
2034
+ * initial request.
2035
+ *
2036
+ * @experimental The API surface may change before stabilizing.
2037
+ */
2038
+ async fetch(request) {
2039
+ const ctx = this.ctx;
2040
+ const match = parseSubAgentPath(request.url, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 });
2041
+ if (!match) return super.fetch(request);
2042
+ const decision = await this.onBeforeSubAgent(request, {
2043
+ className: match.childClass,
2044
+ name: match.childName
2045
+ });
2046
+ if (decision instanceof Response) return decision;
2047
+ const forwardReq = decision instanceof Request ? decision : request;
2048
+ return this._cf_forwardToFacet(forwardReq, match);
2049
+ }
2050
+ /**
2051
+ * Parent-side middleware hook. Fires before a request is
2052
+ * forwarded into a facet sub-agent. Mirrors `onBeforeConnect` /
2053
+ * `onBeforeRequest`.
2054
+ *
2055
+ * - return `void` (default) → forward the original request
2056
+ * - return `Request` → forward this (modified) request
2057
+ * - return `Response` → return this response to the
2058
+ * client; do not wake the child
2059
+ *
2060
+ * Default implementation: return void (permissive).
2061
+ *
2062
+ * The hook receives the **original** request with its URL intact —
2063
+ * including the `/sub/{class}/{name}` segment. The routing
2064
+ * decision for which facet to wake is fixed at parse time, so if
2065
+ * you return a modified `Request`, its headers, body, method, and
2066
+ * query string flow through to the child, but the **pathname**
2067
+ * the child sees is always the tail after `/sub/{class}/{name}`.
2068
+ * Customize via headers/body rather than URL-rewriting.
2069
+ *
2070
+ * WebSocket upgrade requests flow through this hook the same way as
2071
+ * plain HTTP. If you return a mutated `Request`, make sure it still
2072
+ * carries the original `Upgrade: websocket` and `Sec-WebSocket-*`
2073
+ * headers — the simplest safe recipe is to clone the incoming
2074
+ * request's headers (via `new Headers(req.headers)`) and only add
2075
+ * or replace entries, rather than constructing a fresh `Headers`
2076
+ * object from scratch.
2077
+ *
2078
+ * @experimental The API surface may change before stabilizing.
2079
+ *
2080
+ * @example
2081
+ * ```ts
2082
+ * class Inbox extends Agent {
2083
+ * override async onBeforeSubAgent(req, { className, name }) {
2084
+ * // Strict registry gate
2085
+ * if (!this.hasSubAgent(className, name)) {
2086
+ * return new Response("Not found", { status: 404 });
2087
+ * }
2088
+ * }
2089
+ * }
2090
+ * ```
2091
+ */
2092
+ async onBeforeSubAgent(_request, _child) {}
2093
+ /**
2094
+ * Resolve the facet Fetcher for the match and forward the
2095
+ * request to it with `/sub/{class}/{name}` stripped.
2096
+ *
2097
+ * @internal
2098
+ */
2099
+ async _cf_forwardToFacet(req, match) {
2100
+ let fetcher;
2101
+ try {
2102
+ fetcher = await this._cf_resolveSubAgent(match.childClass, match.childName);
2103
+ } catch (err) {
2104
+ const message = err instanceof Error ? err.message : String(err);
2105
+ console.error("[agents] sub-agent route failed:", message);
2106
+ if (/null character/i.test(message) || /reserved/i.test(message)) return new Response("Bad Request", { status: 400 });
2107
+ return new Response("Not Found", { status: 404 });
2108
+ }
2109
+ const rewritten = new URL(req.url);
2110
+ rewritten.pathname = match.remainingPath;
2111
+ const forwarded = new Request(rewritten, req);
2112
+ return fetcher.fetch(forwarded);
2113
+ }
2114
+ /**
2115
+ * Bridge method used by `getSubAgentByName`. Resolves the facet
2116
+ * on each call (idempotent via `subAgent`) and dispatches one
2117
+ * RPC method. Stateless — no cached references.
2118
+ *
2119
+ * @internal
2120
+ */
2121
+ async _cf_invokeSubAgent(className, name, method, args) {
2122
+ const handle = await this._cf_resolveSubAgent(className, name);
2123
+ if (typeof handle[method] !== "function") throw new Error(`Method "${method}" not found on ${className}.`);
2124
+ return await handle[method](...args);
2125
+ }
2126
+ /**
2004
2127
  * Initialize this agent as a facet in a single RPC.
2005
2128
  *
2006
2129
  * Runs entirely inside the child's isolate, so every storage write
@@ -2010,19 +2133,111 @@ var Agent = class Agent extends Server {
2010
2133
  * parent and triggered "Cannot perform I/O on behalf of a different
2011
2134
  * Durable Object" on the child.
2012
2135
  *
2013
- * Order matters: set `_isFacet` BEFORE triggering initialization, so
2014
- * the first `onStart()` run (which calls `broadcastMcpServers`) sees
2015
- * the flag and skips broadcasts that would touch the parent DO's
2016
- * WebSocket registry.
2136
+ * We set `_isFacet` eagerly (before `__unsafe_ensureInitialized`
2137
+ * runs `onStart()`) so any code that legitimately branches on it
2138
+ * e.g. skipping parent-owned alarms in schedule guards sees
2139
+ * the flag during the first `onStart()` run. Broadcast paths no
2140
+ * longer special-case facets, since facets can be directly
2141
+ * addressed via sub-agent routing and have their own WebSocket
2142
+ * connections.
2143
+ *
2144
+ * The facet's name (and `this.name` getter) is handled entirely by
2145
+ * partyserver via `ctx.id.name`, which is populated because the
2146
+ * parent passed an explicit `id: parentNs.idFromName(name)` to
2147
+ * `ctx.facets.get()` — see {@link _cf_resolveSubAgent}. No
2148
+ * `setName()` call or `__ps_name` storage write is needed; the
2149
+ * facet's name survives cold wake automatically because the
2150
+ * factory re-runs and `idFromName` is deterministic.
2017
2151
  *
2018
2152
  * @internal Called by {@link subAgent}.
2019
2153
  */
2020
- async _cf_initAsFacet(name) {
2154
+ async _cf_initAsFacet(name, parentPath = []) {
2155
+ if (this.name !== name) throw new Error(`Facet bootstrap mismatch: expected this.name === "${name}" but got "${this.name}". This usually means the parent passed the wrong (or no) id to ctx.facets.get(). See _cf_resolveSubAgent.`);
2021
2156
  this._isFacet = true;
2022
- await Promise.all([this.ctx.storage.put("cf_agents_is_facet", true), this.ctx.storage.put("__ps_name", name)]);
2157
+ this._parentPath = parentPath;
2158
+ await Promise.all([this.ctx.storage.put("cf_agents_is_facet", true), this.ctx.storage.put("cf_agents_parent_path", parentPath)]);
2023
2159
  await this.__unsafe_ensureInitialized();
2024
2160
  }
2025
2161
  /**
2162
+ * Ancestor chain for this agent, root-first. Empty for top-level
2163
+ * DOs. Populated at facet init time; survives hibernation.
2164
+ *
2165
+ * @example
2166
+ * ```ts
2167
+ * class Chat extends Agent {
2168
+ * onStart() {
2169
+ * console.log("chat started under:", this.parentPath);
2170
+ * // → [{ className: "Tenant", name: "acme" }, { className: "Inbox", name: "alice" }]
2171
+ * }
2172
+ * }
2173
+ * ```
2174
+ *
2175
+ * @experimental The API surface may change before stabilizing.
2176
+ */
2177
+ get parentPath() {
2178
+ return this._parentPath;
2179
+ }
2180
+ /**
2181
+ * Ancestor chain + self, root-first. Convenient for logging.
2182
+ *
2183
+ * @experimental The API surface may change before stabilizing.
2184
+ */
2185
+ get selfPath() {
2186
+ return [...this._parentPath, {
2187
+ className: this.constructor.name,
2188
+ name: this.name
2189
+ }];
2190
+ }
2191
+ /**
2192
+ * Resolve a typed RPC stub for this facet's **immediate** parent
2193
+ * agent.
2194
+ *
2195
+ * Symmetric with `subAgent(Cls, name)`: while `subAgent` opens a
2196
+ * stub from parent to child, `parentAgent` opens one from child
2197
+ * to parent. Pass the direct parent's class reference — the
2198
+ * framework verifies it matches the last entry of
2199
+ * `this.parentPath` at runtime, then looks up `env[Cls.name]` to
2200
+ * find the namespace binding.
2201
+ *
2202
+ * `this.parentPath` is root-first, so the direct parent is the
2203
+ * **last** entry: `this.parentPath.at(-1)`. For grandparents and
2204
+ * further ancestors, iterate `this.parentPath` and use
2205
+ * `getAgentByName(env.X, this.parentPath[i].name)` directly.
2206
+ *
2207
+ * Assumes the standard "binding name matches class name" convention.
2208
+ * If your `wrangler.jsonc` binds the parent under a different name
2209
+ * (e.g. `{ class_name: "Inbox", name: "MY_INBOX" }`), call
2210
+ * `getAgentByName(env.MY_INBOX, this.parentPath.at(-1)!.name)`
2211
+ * directly instead.
2212
+ *
2213
+ * @experimental The API surface may change before stabilizing.
2214
+ *
2215
+ * @throws If this agent is not a facet (no parent).
2216
+ * @throws If `Cls.name` doesn't match the recorded direct-parent
2217
+ * class (guards against accidentally reaching the wrong
2218
+ * DO, especially in nested Root → Mid → Leaf chains).
2219
+ * @throws If no env binding named `Cls.name` is found.
2220
+ *
2221
+ * @example
2222
+ * ```ts
2223
+ * class Chat extends AIChatAgent<Env> {
2224
+ * async onChatMessage(...) {
2225
+ * const inbox = await this.parentAgent(Inbox);
2226
+ * const memory = await inbox.getSharedMemory("facts");
2227
+ * // ...
2228
+ * }
2229
+ * }
2230
+ * ```
2231
+ */
2232
+ async parentAgent(cls) {
2233
+ const parent = this._parentPath[this._parentPath.length - 1];
2234
+ if (!parent) throw new Error(`parentAgent(): ${this.constructor.name} is not a facet — only sub-agents (spawned via \`subAgent()\`) have a parent.`);
2235
+ if (cls.name !== parent.className) throw new Error(`parentAgent(${cls.name}): this facet's recorded parent class is "${parent.className}", not "${cls.name}". Pass the class whose constructor actually spawned this facet.`);
2236
+ const binding = this.env[cls.name];
2237
+ if (!binding) throw new Error(`parentAgent(${cls.name}): no top-level binding "${cls.name}" found in env. If the parent is bound under a different name (e.g. "MY_${cls.name.toUpperCase()}"), use \`getAgentByName(env.MY_${cls.name.toUpperCase()}, this.parentPath.at(-1)!.name)\` directly.`);
2238
+ return await getServerByName(binding, parent.name);
2239
+ }
2240
+ /**
2026
2241
  * Get or create a named sub-agent — a child Durable Object (facet)
2027
2242
  * with its own isolated SQLite storage running on the same machine.
2028
2243
  *
@@ -2043,12 +2258,39 @@ var Agent = class Agent extends Server {
2043
2258
  * ```
2044
2259
  */
2045
2260
  async subAgent(cls, name) {
2261
+ return await this._cf_resolveSubAgent(cls.name, name);
2262
+ }
2263
+ /**
2264
+ * Shared facet resolution — takes a CamelCase class name string
2265
+ * (matching `ctx.exports`) rather than a class reference. Both
2266
+ * `subAgent(cls, name)` and `_cf_invokeSubAgent(className, ...)`
2267
+ * funnel through here so registry bookkeeping and the
2268
+ * `_cf_initAsFacet` handshake are consistent.
2269
+ *
2270
+ * @internal
2271
+ */
2272
+ async _cf_resolveSubAgent(className, name) {
2046
2273
  const ctx = this.ctx;
2047
2274
  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.");
2048
- if (!ctx.exports[cls.name]) throw new Error(`Sub-agent class "${cls.name}" not found in worker exports. Make sure the class is exported from your worker entry point and that the export name matches the class name.`);
2049
- const facetKey = `${cls.name}\0${name}`;
2050
- const stub = ctx.facets.get(facetKey, () => ({ class: ctx.exports[cls.name] }));
2051
- await stub._cf_initAsFacet(name);
2275
+ 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").`);
2276
+ const Cls = ctx.exports[className];
2277
+ 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.`);
2278
+ if (name.includes("\0")) throw new Error(`Sub-agent name contains null character (\\0), which is reserved.`);
2279
+ const facetKey = `${className}\0${name}`;
2280
+ const parentClassName = this.constructor.name;
2281
+ const parentNs = ctx.exports[parentClassName];
2282
+ if (!parentNs?.idFromName) {
2283
+ const minificationHint = /^_*[a-z][a-z0-9]{0,2}$/.test(parentClassName) ? ` The class name "${parentClassName}" looks minified — make sure your bundler preserves class names (e.g. esbuild's \`keepNames: true\`).` : "";
2284
+ throw new Error(`Sub-agent bootstrap requires the parent class "${parentClassName}" to be bound as a Durable Object namespace, but ctx.exports["${parentClassName}"] is missing or doesn't expose idFromName.${minificationHint} Make sure the parent agent class is registered in your wrangler.jsonc durable_objects.bindings under its class name.`);
2285
+ }
2286
+ const facetId = parentNs.idFromName(name);
2287
+ const stub = ctx.facets.get(facetKey, () => ({
2288
+ class: Cls,
2289
+ id: facetId
2290
+ }));
2291
+ const childParentPath = this.selfPath;
2292
+ await stub._cf_initAsFacet(name, childParentPath);
2293
+ this._recordSubAgent(className, name);
2052
2294
  return stub;
2053
2295
  }
2054
2296
  /**
@@ -2082,7 +2324,63 @@ var Agent = class Agent extends Server {
2082
2324
  const ctx = this.ctx;
2083
2325
  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.");
2084
2326
  const facetKey = `${cls.name}\0${name}`;
2085
- ctx.facets.delete(facetKey);
2327
+ try {
2328
+ ctx.facets.delete(facetKey);
2329
+ } catch {}
2330
+ this._forgetSubAgent(cls.name, name);
2331
+ }
2332
+ /** @internal */
2333
+ _ensureSubAgentRegistry() {
2334
+ if (this._subAgentRegistryReady) return;
2335
+ this.sql`
2336
+ CREATE TABLE IF NOT EXISTS cf_agents_sub_agents (
2337
+ class TEXT NOT NULL,
2338
+ name TEXT NOT NULL,
2339
+ created_at INTEGER NOT NULL,
2340
+ PRIMARY KEY (class, name)
2341
+ )
2342
+ `;
2343
+ this._subAgentRegistryReady = true;
2344
+ }
2345
+ /** @internal */
2346
+ _recordSubAgent(className, name) {
2347
+ this._ensureSubAgentRegistry();
2348
+ this.sql`
2349
+ INSERT OR IGNORE INTO cf_agents_sub_agents (class, name, created_at)
2350
+ VALUES (${className}, ${name}, ${Date.now()})
2351
+ `;
2352
+ }
2353
+ /** @internal */
2354
+ _forgetSubAgent(className, name) {
2355
+ this._ensureSubAgentRegistry();
2356
+ this.sql`
2357
+ DELETE FROM cf_agents_sub_agents
2358
+ WHERE class = ${className} AND name = ${name}
2359
+ `;
2360
+ }
2361
+ hasSubAgent(classOrName, name) {
2362
+ const className = typeof classOrName === "string" ? classOrName : classOrName.name;
2363
+ this._ensureSubAgentRegistry();
2364
+ return (this.sql`
2365
+ SELECT COUNT(*) AS n FROM cf_agents_sub_agents
2366
+ WHERE class = ${className} AND name = ${name}
2367
+ `[0]?.n ?? 0) > 0;
2368
+ }
2369
+ listSubAgents(classOrName) {
2370
+ const className = typeof classOrName === "string" ? classOrName : classOrName?.name;
2371
+ this._ensureSubAgentRegistry();
2372
+ return (className ? this.sql`
2373
+ SELECT class, name, created_at FROM cf_agents_sub_agents
2374
+ WHERE class = ${className}
2375
+ ORDER BY created_at ASC
2376
+ ` : this.sql`
2377
+ SELECT class, name, created_at FROM cf_agents_sub_agents
2378
+ ORDER BY created_at ASC
2379
+ `).map((r) => ({
2380
+ className: r.class,
2381
+ name: r.name,
2382
+ createdAt: r.created_at
2383
+ }));
2086
2384
  }
2087
2385
  /**
2088
2386
  * Destroy the Agent, removing all state and scheduled tasks
@@ -2093,6 +2391,7 @@ var Agent = class Agent extends Server {
2093
2391
  this.sql`DROP TABLE IF EXISTS cf_agents_schedules`;
2094
2392
  this.sql`DROP TABLE IF EXISTS cf_agents_queues`;
2095
2393
  this.sql`DROP TABLE IF EXISTS cf_agents_workflows`;
2394
+ this.sql`DROP TABLE IF EXISTS cf_agents_sub_agents`;
2096
2395
  if (!this._isFacet) await this.ctx.storage.deleteAlarm();
2097
2396
  await this.ctx.storage.deleteAll();
2098
2397
  this._disposables.dispose();
@@ -3267,6 +3566,6 @@ var StreamingResponse = class {
3267
3566
  }
3268
3567
  };
3269
3568
  //#endregion
3270
- export { Agent, DEFAULT_AGENT_STATIC_OPTIONS, DurableObjectOAuthClientProvider, MessageType, SqlError, StreamingResponse, __DO_NOT_USE_WILL_BREAK__agentContext, callable, createHeaderBasedEmailResolver, getAgentByName, getCurrentAgent, routeAgentEmail, routeAgentRequest, unstable_callable };
3569
+ export { Agent, DEFAULT_AGENT_STATIC_OPTIONS, DurableObjectOAuthClientProvider, MessageType, SUB_PREFIX, SqlError, StreamingResponse, __DO_NOT_USE_WILL_BREAK__agentContext, callable, createHeaderBasedEmailResolver, getAgentByName, getCurrentAgent, getSubAgentByName, parseSubAgentPath, routeAgentEmail, routeAgentRequest, routeSubAgentRequest, unstable_callable };
3271
3570
 
3272
3571
  //# sourceMappingURL=index.js.map