UNPKG

agents

Version:

A home for your AI agents

6,696 lines 272 kB
import { camelCaseToKebabCase, isInternalJsStubProp } from "./utils.js";
import { getAgentByName } from "./agent-routing.js";
import { AGENT_TOOL_MILESTONE_PART } from "./agent-tool-types.js";
import { n as getCurrentAgent$1, t as __DO_NOT_USE_WILL_BREAK__agentContext } from "./current-agent-DhoDkSnH.js";
import "./internal_context.js";
import { copyCallableMetadata, decoratedMethods, getCallableMetadata, isCallableMethod } from "./callable-decorator.js";
import "./types.js";
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-DZBYAB34.js";
import { signAgentHeaders } from "./email.js";
import { sendAgentEmail } from "./email-send.js";
import { n as withInvocationScope, r as writeSpanAttributes, t as tracer } from "./cloudflare-Dzvc7V2N.js";
import { parseSubAgentPath } from "./sub-routing.js";
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";
import { t as LifecycleCapability } from "./capability-B4WbF81e.js";
import { t as _classPrivateMethodInitSpec } from "./classPrivateMethodInitSpec-qMjJ6sHQ.js";
import { a as abortWithoutAlarmRetry, i as setLifecycleRouteTransport, n as setLifecycleEventSink, r as setLifecycleHostInvoker, t as Lifecycle } from "./lifecycle-Mm_jQh7r.js";
import { isErrorRetryable, tryN, validateRetryOptions } from "./retries.js";
import { SqlError } from "./sql-error.js";
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";
import { DurableObjectOAuthClientProvider } from "./mcp/client/do-oauth-client-provider.js";
import { genericObservability } from "./observability/index.js";
import { n as setSchedulerCallbackResolver, t as Scheduler } from "./scheduler-D_KHqxBa.js";
import { n as setQueueCallbackResolver, t as Queue } from "./queue-BC9F5cUo.js";
import { n as setTaskDefinitionResolver, r as setTaskRoutedMemoryLimitHandler, t as Tasks } from "./tasks-D4nLqVSI.js";
import { State } from "./state/index.js";
import { AsyncLocalStorage } from "node:async_hooks";
import { DurableObject, RpcTarget, exports } from "cloudflare:workers";
import { nanoid } from "nanoid";
import { EmailMessage } from "cloudflare:email";
//#region src/dynamic-agents/bridges.ts
function isClosedWebSocketSendError(error) {
	return error instanceof TypeError && error.message.includes("WebSocket send() after close");
}
const dynamicAgentRpcReplyContext = new AsyncLocalStorage();
function sendFacetRpcResponseIfOpen(target, response) {
	try {
		return {
			sent: true,
			completion: Promise.resolve(target.send(JSON.stringify(response))).catch((error) => {
				if (!isClosedWebSocketSendError(error)) console.error("[Agent] Facet RPC response delivery failed:", error);
			})
		};
	} catch (error) {
		if (isClosedWebSocketSendError(error)) return {
			sent: false,
			completion: Promise.resolve()
		};
		throw error;
	}
}
const facetStreamingResponseDeliveryStates = /* @__PURE__ */ new WeakMap();
/**
* Mark a StreamingResponse as facet-bridged: its chunks are delivered to
* `replyTarget` (the RPC frame that carried the request into the facet)
* instead of the connection's native WebSocket.
*/
function registerFacetStreamingDelivery(stream, replyTarget) {
	facetStreamingResponseDeliveryStates.set(stream, {
		replyTarget,
		pending: /* @__PURE__ */ new Set()
	});
}
/**
* Deliver one streamed RPC response for a facet-bridged stream, tracking
* its completion. Returns null when the stream is not facet-bridged (the
* caller should send on the native connection instead).
*/
function sendFacetStreamingResponse(stream, response) {
	const state = facetStreamingResponseDeliveryStates.get(stream);
	if (!state) return null;
	const delivery = sendFacetRpcResponseIfOpen(state.replyTarget, response);
	state.pending.add(delivery.completion);
	delivery.completion.finally(() => state.pending.delete(delivery.completion));
	return delivery.sent;
}
async function waitForFacetStreamingResponseDeliveries(stream) {
	const state = facetStreamingResponseDeliveryStates.get(stream);
	if (!state) return;
	try {
		await Promise.all(state.pending);
	} finally {
		facetStreamingResponseDeliveryStates.delete(stream);
	}
}
var _connection = /* @__PURE__ */ new WeakMap();
var _broadcast = /* @__PURE__ */ new WeakMap();
/**
* Parent-side bridge handed to a facet over RPC: wraps a live root-owned
* `Connection` so the facet can send/close/setState on it, and carries
* the root's broadcast entry point for facet-scoped broadcasts.
*/
var DynamicAgentConnectionBridge = class extends RpcTarget {
	constructor(connection, broadcast) {
		super();
		_classPrivateFieldInitSpec(this, _connection, void 0);
		_classPrivateFieldInitSpec(this, _broadcast, void 0);
		_classPrivateFieldSet2(_connection, this, connection);
		_classPrivateFieldSet2(_broadcast, this, broadcast);
	}
	send(message) {
		_classPrivateFieldGet2(_connection, this).send(message);
	}
	close(code, reason) {
		_classPrivateFieldGet2(_connection, this).close(code, reason);
	}
	setState(state) {
		return _classPrivateFieldGet2(_connection, this).setState(state);
	}
	broadcast(ownerPath, message, without) {
		return _classPrivateFieldGet2(_broadcast, this)?.call(this, ownerPath, message, without);
	}
};
var _root = /* @__PURE__ */ new WeakMap();
var _connectionId = /* @__PURE__ */ new WeakMap();
/**
* Facet-side bridge used after the originating RPC frame has completed:
* routes connection operations back to the root over a fresh RPC call.
*/
var RootDynamicAgentConnectionBridge = class {
	constructor(root, connectionId) {
		_classPrivateFieldInitSpec(this, _root, void 0);
		_classPrivateFieldInitSpec(this, _connectionId, void 0);
		_classPrivateFieldSet2(_root, this, root);
		_classPrivateFieldSet2(_connectionId, this, connectionId);
	}
	send(message) {
		return _classPrivateFieldGet2(_root, this)._cf_sendToSubAgentConnection(_classPrivateFieldGet2(_connectionId, this), message);
	}
	close(code, reason) {
		return _classPrivateFieldGet2(_root, this)._cf_closeSubAgentConnection(_classPrivateFieldGet2(_connectionId, this), code, reason);
	}
	setState(state) {
		return _classPrivateFieldGet2(_root, this)._cf_setSubAgentConnectionState(_classPrivateFieldGet2(_connectionId, this), state);
	}
	broadcast(ownerPath, message, without) {
		return _classPrivateFieldGet2(_root, this)._cf_broadcastToSubAgent(ownerPath, message, without);
	}
};
//#endregion
//#region src/dynamic-agents/identity.ts
const SUB_AGENT_IDENTITY_VERSION_LEGACY = "legacy";
const SUB_AGENT_IDENTITY_VERSION_PATH_V2 = "path-v2";
const SUB_AGENT_IDENTITY_PATH_V2_PREFIX = "cf-agents:v2:";
async function sha256Hex(value) {
	const bytes = new TextEncoder().encode(value);
	const digest = await crypto.subtle.digest("SHA-256", bytes);
	return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
}
function pathV2IdentityName(logicalName, digest) {
	return `${SUB_AGENT_IDENTITY_PATH_V2_PREFIX}${encodeURIComponent(logicalName)}:${digest}`;
}
function logicalNameFromPathV2Identity(identityName) {
	if (!identityName.startsWith("cf-agents:v2:")) return null;
	const rest = identityName.slice(13);
	const separator = rest.lastIndexOf(":");
	if (separator === -1) return null;
	try {
		return decodeURIComponent(rest.slice(0, separator));
	} catch {
		return null;
	}
}
/**
* Validate that a stored `parentPath` has the expected shape. Used
* when restoring from DO storage to guard against corrupted data.
*/
function isValidParentPath(value) {
	if (!Array.isArray(value)) return false;
	return value.every((entry) => entry != null && typeof entry === "object" && typeof entry.className === "string" && typeof entry.name === "string");
}
function agentPathKey(path) {
	if (!path) return null;
	return path.map((step) => `${encodeURIComponent(step.className)}:${encodeURIComponent(step.name)}`).join("/");
}
//#endregion
//#region src/dynamic-agents/registry.ts
var _host$1 = /* @__PURE__ */ new WeakMap();
var _ready = /* @__PURE__ */ new WeakMap();
var _DynamicAgentRegistry_brand = /* @__PURE__ */ new WeakSet();
/**
* The parent-side registry of spawned dynamic agents (facets), stored
* in the parent's own SQLite. Backs `hasSubAgent` / `listSubAgents`
* and the identity-versioning decision (legacy bare-name facets vs
* path-scoped v2 identities).
*
* Table and column names are storage-frozen — never rename them.
*/
var DynamicAgentRegistry = class {
	constructor(host) {
		_classPrivateMethodInitSpec(this, _DynamicAgentRegistry_brand);
		_classPrivateFieldInitSpec(this, _host$1, void 0);
		_classPrivateFieldInitSpec(this, _ready, false);
		_classPrivateFieldSet2(_host$1, this, host);
	}
	ensure() {
		if (_classPrivateFieldGet2(_ready, this)) return;
		_classPrivateFieldGet2(_host$1, this).sql`
      CREATE TABLE IF NOT EXISTS cf_agents_sub_agents (
        class TEXT NOT NULL,
        name TEXT NOT NULL,
        created_at INTEGER NOT NULL,
        identity_version TEXT,
        identity_name TEXT,
        PRIMARY KEY (class, name)
      )
    `;
		_assertClassBrand(_DynamicAgentRegistry_brand, this, _addColumnIfNotExists).call(this, "ALTER TABLE cf_agents_sub_agents ADD COLUMN identity_version TEXT");
		_assertClassBrand(_DynamicAgentRegistry_brand, this, _addColumnIfNotExists).call(this, "ALTER TABLE cf_agents_sub_agents ADD COLUMN identity_name TEXT");
		_classPrivateFieldSet2(_ready, this, true);
	}
	record(className, name, identity) {
		this.ensure();
		_classPrivateFieldGet2(_host$1, this).sql`
      INSERT OR IGNORE INTO cf_agents_sub_agents
        (class, name, created_at, identity_version, identity_name)
      VALUES
        (${className}, ${name}, ${Date.now()}, ${identity.version}, ${identity.name})
    `;
	}
	row(className, name) {
		this.ensure();
		return _classPrivateFieldGet2(_host$1, this).sql`
      SELECT identity_version, identity_name
      FROM cf_agents_sub_agents
      WHERE class = ${className} AND name = ${name}
      LIMIT 1
    `[0] ?? null;
	}
	async identity(className, name, childPath) {
		const row = this.row(className, name);
		if (row) {
			if (row.identity_version === "path-v2" && typeof row.identity_name === "string") return {
				version: SUB_AGENT_IDENTITY_VERSION_PATH_V2,
				name: row.identity_name,
				existing: true
			};
			return {
				version: SUB_AGENT_IDENTITY_VERSION_LEGACY,
				name,
				existing: true
			};
		}
		return {
			version: SUB_AGENT_IDENTITY_VERSION_PATH_V2,
			name: pathV2IdentityName(name, await sha256Hex(JSON.stringify(childPath))),
			existing: false
		};
	}
	forget(className, name) {
		this.ensure();
		_classPrivateFieldGet2(_host$1, this).sql`
      DELETE FROM cf_agents_sub_agents
      WHERE class = ${className} AND name = ${name}
    `;
	}
	has(className, name) {
		this.ensure();
		return (_classPrivateFieldGet2(_host$1, this).sql`
      SELECT COUNT(*) AS n FROM cf_agents_sub_agents
      WHERE class = ${className} AND name = ${name}
    `[0]?.n ?? 0) > 0;
	}
	list(className) {
		this.ensure();
		return (className ? _classPrivateFieldGet2(_host$1, this).sql`
          SELECT class, name, created_at FROM cf_agents_sub_agents
          WHERE class = ${className}
          ORDER BY created_at ASC
        ` : _classPrivateFieldGet2(_host$1, this).sql`
          SELECT class, name, created_at FROM cf_agents_sub_agents
          ORDER BY created_at ASC
        `).map((r) => ({
			className: r.class,
			name: r.name,
			createdAt: r.created_at
		}));
	}
};
function _addColumnIfNotExists(sql) {
	try {
		_classPrivateFieldGet2(_host$1, this).execRawSql(sql);
	} catch (e) {
		if (!(e instanceof Error ? e.message : String(e)).toLowerCase().includes("duplicate column")) throw e;
	}
}
//#endregion
//#region src/dynamic-agents/dynamic-agents.ts
/**
* Internal key used to remember the outer `/sub/...` URL for a
* WebSocket accepted by the parent on behalf of a child facet.
* Hibernated events then wake the parent, which forwards frames to
* the child over serializable RPC while keeping native WebSocket I/O
* parent-owned.
*
* Storage-frozen — never rename.
*/
const CF_SUB_AGENT_OUTER_URL_KEY = "_cf_subAgentOuterUrl";
const CF_SUB_AGENT_TAGS_KEY = "_cf_subAgentTags";
/** Wire-frozen internal header carrying the outer URL on WS upgrades. */
const SUB_AGENT_OUTER_URL_HEADER = "x-cf-agents-subagent-url";
var _host = /* @__PURE__ */ new WeakMap();
var _facetKeepAliveTokens = /* @__PURE__ */ new WeakMap();
var _bridgeContext = /* @__PURE__ */ new WeakMap();
var _virtualConnections = /* @__PURE__ */ new WeakMap();
var _connectionOperationTails = /* @__PURE__ */ new WeakMap();
var _broadcastOperationTail = /* @__PURE__ */ new WeakMap();
var _DynamicAgentsInternal_brand = /* @__PURE__ */ new WeakSet();
/**
* The facet-backed dynamic-agent machinery, extracted from the Agent
* class. One instance per Agent, installed as a Lifecycle capability
* (`capabilityId: "dynamic-agents"`); the host port documents exactly
* which Agent internals it touches.
*
* The capability claims no runner hooks — four integration points are
* deliberately wired directly through the Agent composition root
* instead, because the runner's dispatch contract cannot express them:
* the `/sub/` upgrade path rewrites the request and *continues* into
* `lifecycle.fetch` (onRequest can only claim), forwarded WS frames run
* inside the host's onMessage wrapper *after* the WebSockets capability
* has claimed the wake, this module *implements* the lifecycle route
* transport rather than consuming it, and facet-context restore has
* load-bearing startup ordering inside the host's startup span.
*
* Nothing here renames any wire- or storage-visible identifier: the
* `cf_agents_facet_runs` table, `_cf_*` RPC method names, and route
* key formats are frozen.
*
* @internal
*/
var DynamicAgentsInternal = class extends LifecycleCapability {
	constructor(host) {
		super("dynamic-agents");
		_classPrivateMethodInitSpec(this, _DynamicAgentsInternal_brand);
		_classPrivateFieldInitSpec(this, _host, void 0);
		_classPrivateFieldInitSpec(this, _facetKeepAliveTokens, /* @__PURE__ */ new Set());
		_classPrivateFieldInitSpec(this, _bridgeContext, new AsyncLocalStorage());
		_classPrivateFieldInitSpec(this, _virtualConnections, /* @__PURE__ */ new Map());
		_classPrivateFieldInitSpec(this, _connectionOperationTails, /* @__PURE__ */ new Map());
		_classPrivateFieldInitSpec(this, _broadcastOperationTail, void 0);
		_classPrivateFieldSet2(_host, this, host);
		this.registry = new DynamicAgentRegistry({
			sql: host.sql.bind(host),
			execRawSql: (sql) => void host.ctx.storage.sql.exec(sql)
		});
	}
	runRowsForPrefix(ownerPath) {
		return _classPrivateFieldGet2(_host, this).sql`
      SELECT owner_path, owner_path_key, run_id, created_at
      FROM cf_agents_facet_runs
    `.filter((row) => {
			try {
				const rowOwnerPath = JSON.parse(row.owner_path);
				return _classPrivateFieldGet2(_host, this)._isSameAgentPathPrefix(ownerPath, rowOwnerPath);
			} catch {
				return false;
			}
		});
	}
	deleteRunRowsForPrefix(ownerPath) {
		for (const row of this.runRowsForPrefix(ownerPath)) _classPrivateFieldGet2(_host, this).sql`
        DELETE FROM cf_agents_facet_runs
        WHERE owner_path_key = ${row.owner_path_key}
          AND run_id = ${row.run_id}
      `;
	}
	lifecycleRouteAddress() {
		if (!_classPrivateFieldGet2(_host, this)._isFacet) return void 0;
		const key = agentPathKey(_classPrivateFieldGet2(_host, this).selfPath);
		return key ? {
			key,
			data: JSON.stringify(_classPrivateFieldGet2(_host, this).selfPath)
		} : void 0;
	}
	async routeLifecycleToRoot(envelope) {
		if (!_classPrivateFieldGet2(_host, this)._isFacet) return _classPrivateFieldGet2(_host, this).lifecycle.route(envelope);
		return (await this.rootAlarmOwner())._cf_routeLifecycle(void 0, envelope);
	}
	async routeLifecycleToTarget(target, envelope) {
		let targetPath;
		try {
			targetPath = JSON.parse(target.data);
		} catch {
			throw new Error("Lifecycle route target is not a valid Agent path");
		}
		const selfPath = _classPrivateFieldGet2(_host, this).selfPath;
		if (!_classPrivateFieldGet2(_host, this)._isSameAgentPathPrefix(selfPath, targetPath)) throw new Error(`Lifecycle route does not descend from ${JSON.stringify(selfPath)}.`);
		if (selfPath.length === targetPath.length) return _classPrivateFieldGet2(_host, this).lifecycle.route(envelope);
		const next = targetPath[selfPath.length];
		if (!_classPrivateFieldGet2(_host, this).hasSubAgent(next.className, next.name)) {
			const stalePath = targetPath.slice(0, selfPath.length + 1);
			if (_classPrivateFieldGet2(_host, this)._isFacet) await (await this.rootAlarmOwner())._cf_cleanupFacetPrefix(stalePath);
			else await _classPrivateFieldGet2(_host, this)._cf_cleanupFacetPrefix(stalePath);
			return false;
		}
		return (await _classPrivateFieldGet2(_host, this)._cf_resolveSubAgent(next.className, next.name))._cf_routeLifecycle(target, envelope);
	}
	/** Body of the single native-RPC aperture for routed Lifecycle capabilities. */
	routeLifecycle(target, envelope) {
		return target ? this.routeLifecycleToTarget(target, envelope) : _classPrivateFieldGet2(_host, this).lifecycle.route(envelope);
	}
	async rootAlarmOwner() {
		const root = _classPrivateFieldGet2(_host, this)._parentPath[0];
		if (!root) throw new Error("Facet routing requires a root parent.");
		const binding = _classPrivateFieldGet2(_host, this).ctx.exports?.[root.className];
		if (!binding) throw new Error(`Unable to resolve root "${root.className}" for facet routing.`);
		return await getAgentByName(binding, root.name);
	}
	rootResolvesToSelf() {
		const root = _classPrivateFieldGet2(_host, this)._parentPath[0];
		if (!root) return false;
		const binding = _classPrivateFieldGet2(_host, this).ctx.exports?.[root.className];
		if (!binding?.idFromName) return false;
		return binding.idFromName(root.name).equals(_classPrivateFieldGet2(_host, this).ctx.id);
	}
	/**
	* Clean root-owned bookkeeping for a sub-tree of facets: bulk-cancel
	* schedules, queue items, and routed Task wake mirrors under the
	* owner-path prefix, and delete root-side facet fiber recovery leases for
	* the same sub-tree.
	*/
	async cleanupPrefix(ownerPath) {
		const prefix = agentPathKey(ownerPath);
		if (prefix) {
			await _classPrivateFieldGet2(_host, this).scheduler.__DO_NOT_USE_WILL_BREAK__cleanupRoutePrefix(prefix);
			await _classPrivateFieldGet2(_host, this).tasks.__DO_NOT_USE_WILL_BREAK__cleanupRoutePrefix(prefix);
			await _classPrivateFieldGet2(_host, this)._queue.__DO_NOT_USE_WILL_BREAK__cleanupRoutePrefix(prefix);
		}
		this.deleteRunRowsForPrefix(ownerPath);
		await _classPrivateFieldGet2(_host, this)._syncHostJobs();
	}
	/**
	* Acquire a root-owned keepAlive ref on behalf of a descendant facet.
	*/
	async acquireKeepAlive(ownerPath) {
		const token = `${agentPathKey(ownerPath) ?? "unknown"}:${nanoid(9)}`;
		_classPrivateFieldGet2(_facetKeepAliveTokens, this).add(token);
		_classPrivateFieldGet2(_host, this)._keepAliveRefs++;
		if (_classPrivateFieldGet2(_host, this)._keepAliveRefs === 1) await _classPrivateFieldGet2(_host, this)._syncHostJobs();
		return token;
	}
	/**
	* Release a root-owned keepAlive ref previously acquired for a facet.
	* Idempotent so disposer calls can safely race or run twice.
	*/
	async releaseKeepAlive(token) {
		if (!_classPrivateFieldGet2(_facetKeepAliveTokens, this).delete(token)) return;
		_classPrivateFieldGet2(_host, this)._keepAliveRefs = Math.max(0, _classPrivateFieldGet2(_host, this)._keepAliveRefs - 1);
		await _classPrivateFieldGet2(_host, this)._syncHostJobs();
	}
	/**
	* Register a facet's durable run row in the root-side index so root
	* alarm housekeeping can dispatch recovery checks into idle facets.
	*/
	async registerRun(ownerPath, runId) {
		const ownerPathJson = JSON.stringify(ownerPath);
		const ownerPathKey = agentPathKey(ownerPath);
		if (!ownerPathKey) throw new Error("_cf_registerFacetRun requires a non-empty owner path.");
		_classPrivateFieldGet2(_host, this).sql`
      INSERT OR REPLACE INTO cf_agents_facet_runs
        (owner_path, owner_path_key, run_id, created_at)
      VALUES
        (${ownerPathJson}, ${ownerPathKey}, ${runId}, ${Date.now()})
    `;
		await _classPrivateFieldGet2(_host, this)._syncHostJobs();
	}
	/**
	* Root-side scan for durable fibers owned by descendant facets.
	* `cf_agents_facet_runs` is only an index; actual snapshots and
	* recovery hooks live in each facet's own `cf_agents_runs` table.
	*/
	async checkRunFibers() {
		if (_classPrivateFieldGet2(_host, this)._parentPath.length > 0) return;
		const rows = _classPrivateFieldGet2(_host, this).sql`
      SELECT owner_path, owner_path_key, run_id, created_at
      FROM cf_agents_facet_runs
      ORDER BY created_at ASC
    `;
		const firstRowByOwner = /* @__PURE__ */ new Map();
		for (const row of rows) if (!firstRowByOwner.has(row.owner_path_key)) firstRowByOwner.set(row.owner_path_key, row);
		for (const row of firstRowByOwner.values()) {
			let ownerPath;
			try {
				ownerPath = JSON.parse(row.owner_path);
			} catch (e) {
				console.warn(`[Agent] Corrupted facet fiber owner path for ${row.owner_path_key}; pruning stale lease.`, e);
				_classPrivateFieldGet2(_host, this).sql`
          DELETE FROM cf_agents_facet_runs
          WHERE owner_path_key = ${row.owner_path_key}
        `;
				continue;
			}
			try {
				if (await _classPrivateFieldGet2(_host, this)._cf_checkRunFibersForFacet(ownerPath) === 0) _classPrivateFieldGet2(_host, this).sql`
            DELETE FROM cf_agents_facet_runs
            WHERE owner_path_key = ${row.owner_path_key}
          `;
			} catch (e) {
				console.error(`[Agent] Facet fiber recovery check failed for ${row.owner_path_key}:`, e);
			}
		}
	}
	/**
	* Dispatch a runFiber recovery check into the facet identified by
	* `ownerPath`. Returns the number of remaining local `cf_agents_runs`
	* rows on the target facet after recovery.
	*/
	async checkRunFibersAtPath(ownerPath) {
		const selfPath = _classPrivateFieldGet2(_host, this).selfPath;
		if (!_classPrivateFieldGet2(_host, this)._isSameAgentPathPrefix(selfPath, ownerPath)) throw new Error(`Facet fiber owner path does not descend from ${JSON.stringify(selfPath)}.`);
		if (selfPath.length === ownerPath.length) {
			await _classPrivateFieldGet2(_host, this)._checkRunFibers();
			return _classPrivateFieldGet2(_host, this).sql`
        SELECT COUNT(*) as count FROM cf_agents_runs
      `[0]?.count ?? 0;
		}
		const next = ownerPath[selfPath.length];
		if (!_classPrivateFieldGet2(_host, this).hasSubAgent(next.className, next.name)) return 0;
		return (await this.resolve(next.className, next.name))._cf_checkRunFibersForFacet(ownerPath);
	}
	/**
	* Invoke an RPC method on the host Agent or a descendant facet
	* identified by a root-first path. Used by AgentWorkflow to route
	* callbacks and `this.agent` calls back to the exact sub-agent that
	* started a workflow.
	*/
	async invokeAgentPath(targetPath, method, args) {
		await _classPrivateFieldGet2(_host, this).__unsafe_ensureInitialized();
		const selfPath = _classPrivateFieldGet2(_host, this).selfPath;
		if (!_classPrivateFieldGet2(_host, this)._isSameAgentPathPrefix(selfPath, targetPath)) throw new Error(`Workflow origin path does not descend from ${JSON.stringify(selfPath)}.`);
		if (selfPath.length === targetPath.length) {
			const fn = _classPrivateFieldGet2(_host, this)[method];
			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}.`);
			return await fn.apply(_classPrivateFieldGet2(_host, this), args);
		}
		const next = targetPath[selfPath.length];
		if (!_classPrivateFieldGet2(_host, this).hasSubAgent(next.className, next.name)) throw new Error(`Workflow origin sub-agent ${next.className} "${next.name}" no longer exists.`);
		return await (await this.resolve(next.className, next.name))._cf_invokeAgentPath(targetPath, method, args);
	}
	/**
	* Recursively destroy a descendant facet identified by `targetPath`.
	* Walks down from `selfPath` until reaching the target's immediate
	* parent, where it cancels the target's parent-owned schedules (and
	* any descendants), removes the target from the registry, and calls
	* `ctx.facets.delete` to wipe the target's storage.
	*/
	async destroyDescendant(targetPath) {
		const selfPath = _classPrivateFieldGet2(_host, this).selfPath;
		if (targetPath.length === 0) throw new Error("_cf_destroyDescendantFacet: target path must not be empty.");
		if (selfPath.length >= targetPath.length) throw new Error("_cf_destroyDescendantFacet: target must be a strict descendant.");
		if (!_classPrivateFieldGet2(_host, this)._isSameAgentPathPrefix(selfPath, targetPath)) throw new Error("_cf_destroyDescendantFacet: target path does not descend from this agent.");
		if (_classPrivateFieldGet2(_host, this)._parentPath.length === 0) await _classPrivateFieldGet2(_host, this)._cf_cleanupFacetPrefix(targetPath);
		if (selfPath.length === targetPath.length - 1) {
			const target = targetPath[targetPath.length - 1];
			const ctx = _classPrivateFieldGet2(_host, this).ctx;
			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.");
			try {
				ctx.facets.delete(`${target.className}\0${target.name}`);
			} catch {}
			this.registry.forget(target.className, target.name);
			return;
		}
		const next = targetPath[selfPath.length];
		if (!_classPrivateFieldGet2(_host, this).hasSubAgent(next.className, next.name)) return;
		await (await this.resolve(next.className, next.name))._cf_destroyDescendantFacet(targetPath);
	}
	/**
	* Shared facet resolution — takes a CamelCase class name string
	* (matching `ctx.exports`) rather than a class reference. Both
	* `subAgent(cls, name)` and `_cf_invokeSubAgent(className, ...)`
	* funnel through here so registry bookkeeping and the
	* `_cf_initAsFacet` handshake are consistent.
	*/
	async resolve(className, name) {
		const ctx = _classPrivateFieldGet2(_host, this).ctx;
		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.");
		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").`);
		const Cls = ctx.exports[className];
		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.`);
		if (name.includes("\0")) throw new Error(`Sub-agent name contains null character (\\0), which is reserved.`);
		const facetKey = `${className}\0${name}`;
		const childParentPath = _classPrivateFieldGet2(_host, this).selfPath;
		const childPath = [...childParentPath, {
			className,
			name
		}];
		const rootClassName = _classPrivateFieldGet2(_host, this)._parentPath[0]?.className ?? _classPrivateFieldGet2(_host, this).constructor.name;
		const rootNs = ctx.exports[rootClassName];
		if (!rootNs?.idFromName) {
			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\`).` : "";
			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.`);
		}
		const identity = await this.registry.identity(className, name, childPath);
		const facetId = rootNs.idFromName(identity.name);
		const stub = ctx.facets.get(facetKey, () => ({
			class: Cls,
			id: facetId
		}));
		this.registry.record(className, name, identity);
		try {
			await _classPrivateFieldGet2(_host, this)._runFacetInitInvocation(async () => {
				await stub._cf_initAsFacet(name, childParentPath, identity.name);
			});
		} catch (error) {
			if (!identity.existing) this.registry.forget(className, name);
			throw error;
		}
		return stub;
	}
	/**
	* Forcefully abort a running facet. Transitively aborts the child's
	* own children; storage is preserved.
	*/
	abort(className, name, reason) {
		const ctx = _classPrivateFieldGet2(_host, this).ctx;
		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.");
		const facetKey = `${className}\0${name}`;
		ctx.facets.abort(facetKey, reason);
	}
	/**
	* Delete a facet: abort it if running, then permanently wipe its
	* storage. Transitively deletes the child's own children.
	*/
	async delete(className, name) {
		const ctx = _classPrivateFieldGet2(_host, this).ctx;
		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.");
		const facetKey = `${className}\0${name}`;
		const childPath = [..._classPrivateFieldGet2(_host, this).selfPath, {
			className,
			name
		}];
		if (_classPrivateFieldGet2(_host, this)._isFacet) await (await this.rootAlarmOwner())._cf_cleanupFacetPrefix(childPath);
		else await _classPrivateFieldGet2(_host, this)._cf_cleanupFacetPrefix(childPath);
		try {
			ctx.facets.delete(facetKey);
		} catch {}
		this.registry.forget(className, name);
	}
	/** Drop all facet-side virtual connections (test/rehydration hook). */
	clearVirtualConnections() {
		_classPrivateFieldGet2(_virtualConnections, this).clear();
	}
	/** Facet-side lookup of a virtual connection by id. */
	getVirtualConnection(id) {
		const stored = _classPrivateFieldGet2(_virtualConnections, this).get(id);
		if (!stored) return void 0;
		return this.createBridgeConnection(stored.meta);
	}
	/** Facet-side iteration over virtual connections, optionally by tag. */
	*getVirtualConnections(tag) {
		for (const stored of _classPrivateFieldGet2(_virtualConnections, this).values()) if (!tag || stored.meta.tags.includes(tag)) yield this.createBridgeConnection(stored.meta);
	}
	activeBridge(connectionId) {
		const context = _classPrivateFieldGet2(_bridgeContext, this).getStore();
		if (connectionId !== void 0 && context?.connectionId !== connectionId) return;
		return context?.bridge;
	}
	/**
	* Route a virtual sub-agent connection operation through its live frame
	* bridge, or through the durable root Agent after that frame completes.
	* All operations share one per-connection queue. Facet broadcasts wait for
	* older queued operations; failures do not block later work.
	*/
	routeConnectionOperation(connectionId, operationName, operation) {
		const activeBridge = this.activeBridge(connectionId);
		const previousConnectionOperation = _classPrivateFieldGet2(_connectionOperationTails, this).get(connectionId);
		let pending;
		if (activeBridge && !previousConnectionOperation) try {
			pending = Promise.resolve(operation(activeBridge)).then(() => {});
		} catch (error) {
			pending = Promise.reject(error);
		}
		else pending = (previousConnectionOperation ?? Promise.resolve()).then(async () => {
			await operation(new RootDynamicAgentConnectionBridge(await this.rootAlarmOwner(), connectionId));
		});
		const completion = pending.catch((error) => {
			_assertClassBrand(_DynamicAgentsInternal_brand, this, _reportConnectionOperationFailure).call(this, connectionId, operationName, error);
		});
		_classPrivateFieldGet2(_connectionOperationTails, this).set(connectionId, completion);
		_classPrivateFieldGet2(_host, this).ctx.waitUntil(completion);
		completion.then(() => {
			if (_classPrivateFieldGet2(_connectionOperationTails, this).get(connectionId) === completion) _classPrivateFieldGet2(_connectionOperationTails, this).delete(connectionId);
		});
	}
	/**
	* Route a facet broadcast after every older connection operation.
	*
	* This barrier is intentionally one-way: facet startup can broadcast before
	* a child connection has finished initializing its tags and protocol flags.
	* Making those later connection operations wait would let the next frame
	* observe stale root-owned metadata.
	*/
	async routeBroadcast(ownerPath, message, without, upstreamBridge) {
		const activeBridge = upstreamBridge ?? this.activeBridge();
		const previousOperations = /* @__PURE__ */ new Set([..._classPrivateFieldGet2(_broadcastOperationTail, this) ? [_classPrivateFieldGet2(_broadcastOperationTail, this)] : [], ..._classPrivateFieldGet2(_connectionOperationTails, this).values()]);
		let pending;
		if (activeBridge && previousOperations.size === 0) try {
			pending = Promise.resolve(activeBridge.broadcast(ownerPath, message, without));
		} catch (error) {
			pending = Promise.reject(error);
		}
		else pending = Promise.all(previousOperations).then(async () => {
			await (await this.rootAlarmOwner())._cf_broadcastToSubAgent(ownerPath, message, without);
		});
		const completion = pending.catch((error) => {
			console.error("[Agent] Sub-agent broadcast operation failed:", {
				operation: "broadcast",
				error
			});
		});
		_classPrivateFieldSet2(_broadcastOperationTail, this, completion);
		_classPrivateFieldGet2(_host, this).ctx.waitUntil(completion);
		completion.then(() => {
			if (_classPrivateFieldGet2(_broadcastOperationTail, this) === completion) _classPrivateFieldSet2(_broadcastOperationTail, this, void 0);
		});
		await completion;
	}
	async broadcastToParent(message, without) {
		await this.routeBroadcast(_classPrivateFieldGet2(_host, this).selfPath, message, without);
	}
	async broadcastToPath(ownerPath, message, without) {
		if (_classPrivateFieldGet2(_host, this)._isFacet) {
			await this.routeBroadcast(ownerPath, message, without);
			return;
		}
		for (const connection of _classPrivateFieldGet2(_host, this)._webSockets.getConnections()) {
			if (without?.includes(connection.id)) continue;
			const targetPath = this.connectionTargetPath(connection);
			if (!targetPath) continue;
			if (!this.isSameAgentPath(targetPath, ownerPath)) continue;
			connection.send(message);
		}
	}
	async connectionMetas(ownerPath) {
		const metas = [];
		for (const connection of _classPrivateFieldGet2(_host, this)._webSockets.getConnections()) {
			const meta = _assertClassBrand(_DynamicAgentsInternal_brand, this, _connectionMetaForPath).call(this, connection, ownerPath);
			if (meta) metas.push(meta);
		}
		return metas;
	}
	async sendToConnection(connectionId, message) {
		const connection = _classPrivateFieldGet2(_host, this)._webSockets.getConnection(connectionId);
		if (!connection || !this.connectionHasChildTarget(connection)) return;
		connection.send(message);
	}
	async closeConnection(connectionId, code, reason) {
		const connection = _classPrivateFieldGet2(_host, this)._webSockets.getConnection(connectionId);
		if (!connection || !this.connectionHasChildTarget(connection)) return;
		connection.close(code, reason);
	}
	async setConnectionState(connectionId, state) {
		const connection = _classPrivateFieldGet2(_host, this)._webSockets.getConnection(connectionId);
		if (!connection || !this.connectionHasChildTarget(connection)) return null;
		ensureConnectionWrapped(connection);
		connection.setState(state);
		return this.getForwardedState(connection);
	}
	connectionTargetPath(connection) {
		ensureConnectionWrapped(connection);
		const outerUri = _classPrivateFieldGet2(_host, this)._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY);
		if (typeof outerUri !== "string") return null;
		return _assertClassBrand(_DynamicAgentsInternal_brand, this, _pathFromOuterUri).call(this, outerUri)?.path ?? null;
	}
	isSameAgentPath(a, b) {
		if (a.length !== b.length) return false;
		return a.every((step, index) => step.className === b[index]?.className && step.name === b[index]?.name);
	}
	connectionHasChildTarget(connection) {
		ensureConnectionWrapped(connection);
		return typeof _classPrivateFieldGet2(_host, this)._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY) === "string";
	}
	connectionTargetsChild(connection) {
		if (!connection.uri) return false;
		const ctx = _classPrivateFieldGet2(_host, this).ctx;
		return parseSubAgentPath(connection.uri, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 }) !== null;
	}
	requestTargetsChild(request) {
		const ctx = _classPrivateFieldGet2(_host, this).ctx;
		return parseSubAgentPath(request.url, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 }) !== null;
	}
	async forwardWebSocketConnect(connection, request, options) {
		const routed = await _assertClassBrand(_DynamicAgentsInternal_brand, this, _resolveConnection).call(this, connection, request, options);
		if (!routed) return false;
		await routed.child._cf_handleSubAgentWebSocketConnect(_assertClassBrand(_DynamicAgentsInternal_brand, this, _createConnectionBridge).call(this, connection), routed.meta);
		return true;
	}
	async forwardWebSocketMessage(connection, message, replyBridge) {
		const routed = await _assertClassBrand(_DynamicAgentsInternal_brand, this, _resolveConnection).call(this, connection);
		if (!routed) return false;
		const bridge = _assertClassBrand(_DynamicAgentsInternal_brand, this, _createConnectionBridge).call(this, connection);
		await routed.child._cf_handleSubAgentWebSocketMessage(message, bridge, routed.meta, replyBridge ?? bridge);
		return true;
	}
	async forwardWebSocketClose(connection, code, reason, wasClean) {
		const routed = await _assertClassBrand(_DynamicAgentsInternal_brand, this, _resolveConnection).call(this, connection);
		if (!routed) return false;
		await routed.child._cf_handleSubAgentWebSocketClose(code, reason, wasClean, _assertClassBrand(_DynamicAgentsInternal_brand, this, _createConnectionBridge).call(this, connection), routed.meta);
		return true;
	}
	async handleWebSocketConnect(bridge, meta) {
		await this.runWithBridge(bridge, meta.id, async () => {
			const connection = this.createBridgeConnection(meta);
			const request = new Request(meta.uri ?? "http://placeholder/", { headers: meta.requestHeaders });
			if (await this.forwardWebSocketConnect(connection, request, { gate: true })) return;
			if (_classPrivateFieldGet2(_host, this).shouldConnectionBeReadonly(connection, { request })) _classPrivateFieldGet2(_host, this).setConnectionReadonly(connection, true);
			if (!_classPrivateFieldGet2(_host, this).shouldSendProtocolMessages(connection, { request })) setConnectionProtocolEnabled(connection, false);
			const childTags = await _classPrivateFieldGet2(_host, this).getConnectionTags(connection, { request });
			connection.tags = [connection.id, ...childTags.filter((tag) => tag !== connection.id)];
			this.storeVirtualConnection(connection);
			await _classPrivateFieldGet2(_host, this).onConnect(connection, { request });
			this.storeVirtualConnection(connection);
			await _classPrivateFieldGet2(_connectionOperationTails, this).get(meta.id);
		});
	}
	async handleWebSocketMessage(message, bridge, meta, replyBridge = bridge) {
		const connection = this.createBridgeConnection(meta);
		this.storeVirtualConnection(connection);
		const replyContext = { bridge: replyBridge };
		try {
			await dynamicAgentRpcReplyContext.run(replyContext, () => this.runWithBridge(bridge, meta.id, () => _classPrivateFieldGet2(_host, this).onMessage(connection, message)));
		} finally {
			replyContext.bridge = void 0;
		}
	}
	async handleWebSocketClose(code, reason, wasClean, bridge, meta) {
		const connection = this.createBridgeConnection(meta);
		this.storeVirtualConnection(connection);
		await this.runWithBridge(bridge, meta.id, () => _classPrivateFieldGet2(_host, this).onClose(connection, code, reason, wasClean));
		_classPrivateFieldGet2(_virtualConnections, this).delete(meta.id);
	}
	async runWithBridge(bridge, connectionId, fn) {
		const context = {
			bridge,
			connectionId
		};
		try {
			return await _classPrivateFieldGet2(_bridgeContext, this).run(context, fn);
		} finally {
			context.bridge = void 0;
		}
	}
	createBridgeConnection(meta) {
		let stored = _classPrivateFieldGet2(_virtualConnections, this).get(meta.id);
		if (stored) {
			stored.meta = meta;
			if (stored.connection) {
				stored.connection.uri = meta.uri;
				stored.connection.tags = meta.tags;
				return stored.connection;
			}
		} else {
			stored = { meta };
			_classPrivateFieldGet2(_virtualConnections, this).set(meta.id, stored);
		}
		const owner = this;
		const getStored = () => _classPrivateFieldGet2(_virtualConnections, this).get(meta.id) ?? stored;
		const updateStoredState = (nextState) => {
			const current = _classPrivateFieldGet2(_virtualConnections, this).get(meta.id);
			if (current) current.meta = {
				...current.meta,
				state: nextState
			};
		};
		const connection = {
			id: meta.id,
			uri: meta.uri,
			tags: meta.tags,
			get state() {
				return getStored().meta.state;
			},
			setState(next) {
				const currentState = getStored().meta.state;
				const state = typeof next === "function" ? next(currentState) : next;
				updateStoredState(state);
				owner.routeConnectionOperation(meta.id, "setState", (bridge) => bridge.setState(state));
				return state;
			},
			send(message) {
				owner.routeConnectionOperation(meta.id, "send", (bridge) => bridge.send(message));
			},
			close(code, reason) {
				owner.routeConnectionOperation(meta.id, "close", (bridge) => bridge.close(code, reason));
			},
			addEventListener() {},
			removeEventListener() {}
		};
		stored.connection = connection;
		ensureConnectionWrapped(connection);
		return connection;
	}
	storeVirtualConnection(connection) {
		_classPrivateFieldGet2(_host, this)._unsafe_setConnectionFlag(connection, CF_SUB_AGENT_TAGS_KEY, [...connection.tags]);
		const stored = _classPrivateFieldGet2(_virtualConnections, this).get(connection.id);
		_classPrivateFieldGet2(_virtualConnections, this).set(connection.id, {
			meta: {
				id: connection.id,
				uri: connection.uri,
				tags: [...connection.tags],
				state: this.getRawConnectionState(connection)
			},
			connection: stored?.connection ?? connection
		});
	}
	/**
	* Restore the facet identity persisted by `init` (wake after
	* hibernation), then best-effort hydrate the virtual connections
	* from the root's WebSocket state.
	*/
	async restoreFacetContext() {
		if (await _classPrivateFieldGet2(_host, this).ctx.storage.get("cf_agents_is_facet")) _classPrivateFieldGet2(_host, this)._isFacet = true;
		const storedFacetName = await _classPrivateFieldGet2(_host, this).ctx.storage.get("cf_agents_facet_name");
		if (typeof storedFacetName === "string") _classPrivateFieldGet2(_host, this)._facetName = storedFacetName;
		const storedParentPath = await _classPrivateFieldGet2(_host, this).ctx.storage.get("cf_agents_parent_path");
		if (isValidParentPath(storedParentPath)) _classPrivateFieldGet2(_host, this)._parentPath = storedParentPath;
		try {
			await this.hydrateConnectionsFromRoot();
		} catch (error) {
			console.warn("[Agent] Unable to hydrate sub-agent WebSocket connections:", error);
		}
	}
	async hydrateConnectionsFromRoot() {
		if (!_classPrivateFieldGet2(_host, this)._isFacet || _classPrivateFieldGet2(_host, this)._parentPath.length === 0) return;
		if (this.rootResolvesToSelf()) return;
		const metas = await (await this.rootAlarmOwner())._cf_subAgentConnectionMetas(_classPrivateFieldGet2(_host, this).selfPath);
		for (const meta of metas) _classPrivateFieldGet2(_virtualConnections, this).set(meta.id, { meta });
	}
	getRawConnectionState(connection) {
		return getConnectionRawState(connection);
	}
	getForwardedState(connection) {
		const raw = this.getRawConnectionState(connection);
		if (raw == null || typeof raw !== "object") return raw;
		const { [CF_SUB_AGENT_OUTER_URL_KEY]: _, ...rest } = raw;
		return Object.keys(rest).length > 0 ? rest : null;
	}
	/**
	* Resolve the facet Fetcher for the match and forward the request to
	* it with `/sub/{class}/{name}` stripped.
	*/
	async forward(req, match) {
		let fetcher;
		try {
			fetcher = await this.resolve(match.childClass, match.childName);
		} catch (err) {
			const message = err instanceof Error ? err.message : String(err);
			console.error("[agents] sub-agent route failed:", message);
			if (/null character/i.test(message) || /reserved/i.test(message)) return new Response("Bad Request", { status: 400 });
			return new Response("Not Found", { status: 404 });
		}
		const rewritten = new URL(req.url);
		rewritten.pathname = match.remainingPath;
		const forwardedHeaders = new Headers(req.headers);
		const forwardedInit = {
			method: req.method,
			headers: forwardedHeaders
		};
		if (req.headers.get("Upgrade")?.toLowerCase() === "websocket") forwardedHeaders.set(SUB_AGENT_OUTER_URL_HEADER, req.url);
		if (req.body && req.method !== "GET" && req.method !== "HEAD") forwardedInit.body = req.body;
		const forwarded = new Request(rewritten, forwardedInit);
		return fetcher.fetch(forwarded);
	}
	/**
	* Bridge used by `getSubAgentByName`: resolve the facet and dispatch
	* one RPC method. Stateless — no cached references.
	*/
	async invoke(className, name, method, args) {
		const stub = await this.resolve(className, name);
		return await this.invokeStubMethod(stub, className, method, args);
	}
	/**
	* Bridge used by `parentAgent()` when the requested parent is itself
	* a facet (and therefore has no top-level env namespace). The root
	* receives the full root-first target path, then each hop delegates
	* to the next facet using that facet's own `ctx.facets`.
	*/
	async invokePath(path, method, args) {
		const [self, next, ...rest] = path;
		if (!self) throw new Error(`Sub-agent path invocation requires a non-empty path.`);
		const ownClassName = _classPrivateFieldGet2(_host, this).constructor.name;
		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}").`);
		if (!next) return await this.invokeStubMethod(_classPrivateFieldGet2(_host, this), ownClassName, method, args);
		const child = await this.resolve(next.className, next.name);
		if (rest.length === 0) return await this.invokeStubMethod(child, next.className, method, args);
		return await child._cf_invokeSubAgentPath([next, ...rest], method, args);
	}
	async invokeStubMethod(stub, className, method, args) {
		const handle = stub;
		if (typeof handle[method] !== "function") throw new Error(`Method "${method}" not found on ${className}.`);
		return await handle[method](...args);
	}
	/**
	* Initialize the host agent as a facet in a single RPC. Runs entirely
	* inside the child's isolate, so every storage write and `onStart()`
	* I/O is owned by the child DO.
	*/
	async init(name, parentPath = [], identityName = name) {
		const routedName = _classPrivateFieldGet2(_host, this).lifecycle.name;
		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.`);
		_classPrivateFieldGet2(_host, this)._isFacet = true;
		_classPrivateFieldGet2(_host, this)._facetName = name;
		_classPrivateFieldGet2(_host, this)._parentPath = parentPath;
		await Promise.all([
			_classPrivateFieldGet2(_host, this).ctx.storage.put("cf_agents_is_facet", true),
			_classPrivateFieldGet2(_host, this).ctx.storage.put("cf_agents_facet_name", name),
			_classPrivateFieldGet2(_host, this).ctx.storage.put("cf_agents_parent_path", parentPath)
		]);
		await _classPrivateFieldGet2(_host, this).__unsafe_ensureInitialized();
	}
	/** Remove a completed facet fiber from the root-side index. */
	async unregisterRun(ownerPath, runId) {
		const ownerPathKey = agentPathKey(ownerPath);
		_classPrivateFieldGet2(_host, this).sql`
      DELETE FROM cf_agents_facet_runs
      WHERE owner_path_key IS ${ownerPathKey}
        AND run_id = ${runId}
    `;
		await _classPrivateFieldGet2(_host, this)._syncHostJobs();
	}
};
function _reportConnectionOperationFailure(connectionId, operation, error) {
	console.error("[Agent] Sub-agent connection operation failed:", {
		connectionId,
		operation,
		error
	});
}
function _connectionMetaForPath(connection, ownerPath) {
	ensureConnectionWrapped(connection);
	const outerUri = _classPrivateFieldGet2(_host, this)._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY);
	if (typeof outerUri !== "string") return null;
	const target = _assertClassBrand(_DynamicAgentsInternal_brand, this, _pathFromOuterUri).call(this, outerUri, ownerPath);
	if (!target) return null;
	const raw = this.getRawConnectionState(connection);
	const rawTags = raw != null && typeof raw === "object" ? raw[CF_SUB_AGENT_TAGS_KEY] : void 0;
	const tags = Array.isArray(rawTags) ? rawTags.filter((tag) => typeof tag === "string") : [...connection.tags];
	return {
		id: connection.id,
		uri: target.uri,
		tags,
		state: this.getForwardedState(connection)
	};
}
function _pathFromOuterUri(outerUri, stopAt) {
	const ctx = _classPrivateFieldGet2(_host, this).ctx;
	const knownClasses = ctx.exports ? Object.keys(ctx.exports) : void 0;
	const path = [..._classPrivateFieldGet2(_host, this).selfPath];
	let currentUrl = outerUri;
	while (true) {
		const match = parseSubAgentPath(currentUrl, { knownClasses });
		if (!match) break;
		path.push({
			className: match.childClass,
			name: match.childName
		});
		const rewritten = new URL(currentUrl);
		rewritten.pathname = match.remainingPath;
		currentUrl = rewritten.toString();
		if (stopAt && this.isSameAgentPath(path, stopAt)) return {
			path,
			uri: currentUrl
		};
	}
	if (path.length === _classPrivateFieldGet2(_host, this).selfPath.length) return null;
	if (stopAt) return null;
	return {
		path,
		uri: currentUrl
	};
}
function _createConnectionBridge(connection) {
	const upstreamBroadcastBridge = _classPrivateFieldGet2(_host, this)._isFacet ? this.activeBridge(connection.id) : void 0;
	return new DynamicAgentConnectionBridge(connection, (ownerPath, message, without) => {
		if (upstreamBroadcastBridge) return this.routeBroadcast(ownerPath, message, without, upstreamBroadcastBridge);
		return _classPrivateFieldGet2(_host, this)._cf_broadcastToSubAgent(ownerPath, message, without);
	});
}
async function _resolveConnection(connection, request, options = { gate: false }) {
	ensureConnectionWrapped(connection);
	const outerUri = _classPrivateFieldGet2(_host, this)._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY);
	const uri = typeof outerUri === "string" ? outerUri : connection.uri;
	if (!uri) return null;
	const ctx = _classPrivateFieldGet2(_host, this).ctx;
	let match = parseSubAgentPath(uri, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 });
	if (!match) return null;
	if (_classPrivateFieldGet2(_host, this)._ParentClass.name === match.childClass && _classPrivateFieldGet2(_host, this).name === match.childName) {
		const tailUri = new URL(uri);
		tailUri.pathname = match.remainingPath;
		match = parseSubAgentPath(tailUri.toString(), { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 });
		if (!match) return null;
	}
	let forwardReq = request;
	if (request && options.gate) {
		const decision = await _classPrivateFieldGet2(_host, this).onBeforeSubAgent(request, {
			className: match.childClass,
			name: match.childName
		});
		if (decision instanceof Response) {
			connection.close(1008, "Sub-agent connection rejected");
			return null;
		}
		forwardReq = decision instanceof Request ? decision : request;
	}
	const child = await this.resolve(match.childClass, match.childName);
	const childUri = new URL(forwardReq?.url ?? uri);
	childUri.pathname = match.remainingPath;
	const raw = this.getRawConnectionState(connection);
	const rawTags = raw != null && typeof raw === "object" ? raw[CF_SUB_AGENT_TAGS_KEY] : void 0;
	const tags = Array.isArray(rawTags) ? rawTags.filter((tag) => typeof tag === "string") : [...connection.tags];
	return {
		child,
		meta: {
			id: connection.id,
			uri: childUri.toString(),
			tags,
			state: this.getForwardedState(connection),
			requestHeaders: forwardReq ? [...forwardReq.headers] : void 0
		}
	};
}
//#endregion
//#region src/dynamic-agents/api.ts
var _internal = /* @__PURE__ */ new WeakMap();
/**
* The public dynamic-agents capability surface, reached via
* `this.dynamicAgents` on an Agent.
*
* A dynamic agent is a facet-backed child: it runs in its own isolate
* with its own SQLite database, colocated with — and supervised by —
* its parent Agent. Use dynamic agents for code whose class or
* lifecycle the parent owns (dynamically-loaded/generated code,
* per-run tool agents, sandboxed components). For independent peers
* such as one-DO-per-chat, use `getAgentByName` instead.
*
* @experimental The API surface may change before stabilizing.
*/
var DynamicAgents = class {
	/** @internal Constructed by Agent; do not instantiate directly. */
	constructor(internal) {
		_classPrivateFieldInitSpec(this, _internal, void 0);
		_classPrivateFieldSet2(_internal, this, internal);
	}
	/**
	* Get (creating or waking if needed) the dynamic agent of the given
	* class and name, as a typed RPC stub. Idempotent — repeated calls
	* with the same class and name return the same child.
	*
	* @experimental
	*/
	async get(cls, name) {
		return await _classPrivateFieldGet2(_internal, this).resolve(cls.name, name);
	}
	/**
	* Forcefully abort a running dynamic agent. The child stops
	* executing immediately and restarts on the next {@link get} call;
	* its storage is preserved. Transitively aborts the child's own
	* children. Pending RPC calls receive the reason as an error.
	*
	* @experimental
	*/
	abort(cls, name, reason) {
		_classPrivateFieldGet2(_internal, this).abort(cls.name, name, reason);
	}
	/**
	* Delete a dynamic agent: abort it if running, then permanently wipe
	* its storage. Transitively deletes the child's own children.
	*
	* @experimental
	*/
	delete(cls, name) {
		return _classPrivateFieldGet2(_internal, this).delete(cls.name, name);
	}
	has(classOrName, name) {
		const className = typeof classOrName === "string" ? classOrName : classOrName.name;
		return _classPrivateFieldGet2(_internal, this).registry.has(className, name);
	}
	list(classOrName) {
		const className = typeof classOrName === "string" ? classOrName : classOrName?.name;
		return _classPrivateFieldGet2(_internal, this).registry.list(className);
	}
};
//#endregion
//#region src/observability/agent-span-attributes.ts
function agentSpanAttributes(input) {
	return {
		"instrumentation_scope.name": "agents",
		"instrumentation_scope.version": "1",
		"cloudflare.agents.session.id": input.sessionId,
		"cloudflare.agents.session.name": input.sessionName,
		"gen_ai.agent.name": input.agentClassName
	};
}
//#endregion
//#region src/index.ts
let _Symbol$dispose;
/**
* Enters an agent invocation: the context every handler reads, plus the span
* scope that stops invocation-bounded spans from outliving it. Scopes do not
* nest, so the outermost live entry point owns the boundary — pass
* `detached` for work that deliberately runs on past its caller.
*/
function runInInvocation(store, body, options) {
	return __DO_NOT_USE_WILL_BREAK__agentContext.run(store, () => withInvocationScope(body, options));
}
function sendRpcResponseIfOpen(connection, response) {
	try {
		connection.send(JSON.stringify(response));
		return true;
	} catch (error) {
		if (isClosedWebSocketSendError(error)) return false;
		throw error;
	}
}
/**
* Type guard for RPC request messages
*/
function isRPCRequest(msg) {
	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);
}
const _fiberALS = new AsyncLocalStorage();
const DEFAULT_KEEP_ALIVE_INTERVAL_MS = 3e4;
const DEFAULT_AGENT_TOOL_RECOVERY_TIMEOUT_MS = 2e3;
const DEFAULT_AGENT_TOOL_RECOVERY_TOTAL_TIMEOUT_MS = 5e3;
const DESTROY_PENDING_KEY = "cf_agents_destroy_pending";
const HOST_JOB_KEEP_ALIVE_ID = "cf:keep-alive";
const HOST_JOB_HOUSEKEEPING_ID = "cf:housekeeping";
const HOST_JOB_DESTROY_ID = "cf:destroy";
const DESTROY_ALARM_DELAY_MS = 1e3;
const FIBER_RECOVERY_MAX_BACKOFF_MS = 5 * 6e4;
const FIBER_RECOVERY_BACKOFF_MAX_EXP = 20;
const DEFAULT_AGENT_TOOL_REATTACH_NO_PROGRESS_TIMEOUT_MS = 12e4;
const DEFAULT_AGENT_TOOL_REATTACH_MAX_WINDOW_MS = Number.POSITIVE_INFINITY;
const DEFAULT_DETACHED_MAX_BUDGET_MS = 1440 * 60 * 1e3;
const DEFAULT_DETACHED_NO_PROGRESS_BUDGET_MS = 3600 * 1e3;
const DETACHED_DELIVERY_LEASE_MS = 6e4;
const DETACHED_BACKBONE_CADENCE_S = [
	5,
	15,
	30,
	120
];
const DETACHED_LIVE_COUNT_WARN_THRESHOLD = 50;
const DETACHED_RECONCILE_CALLBACK = "_cfDetachedReconcileTick";
const DETACHED_NOTIFY_CALLBACK = "_cfDetachedNotifyFinish";
/**
* Schema version for the Agent's internal SQLite tables.
* Bump this when adding new tables, columns, or migrations.
* The constructor stores this under a namespaced KV key (the same convention
* every capability uses for its own schema version) and checks it on wake to
* skip DDL on established DOs.
*/
const CURRENT_SCHEMA_VERSION = 11;
const SCHEMA_VERSION_KEY = "cf_agents:schema_version";
const LEGACY_SCHEMA_VERSION_ROW_ID = "cf_schema_version";
const DEFAULT_STATE = {};
registerInternalConnectionKeys("_cf_voiceInCall", CF_SUB_AGENT_OUTER_URL_KEY, CF_SUB_AGENT_TAGS_KEY);
/** Max length for error strings broadcast to clients. */
const MAX_ERROR_STRING_LENGTH = 500;
/**
* Sanitize an error string before broadcasting to clients.
* MCP error strings may contain untrusted content from external OAuth
* providers — truncate and strip control characters to limit XSS risk.
*/
const CONTROL_CHAR_RE = /* @__PURE__ */ new RegExp("[\\u0000-\\u0008\\u000B\\u000C\\u000E-\\u001F\\u007F]", "g");
function sanitizeErrorString(error) {
	if (error === null) return null;
	let sanitized = error.replace(CONTROL_CHAR_RE, "");
	if (sanitized.length > MAX_ERROR_STRING_LENGTH) sanitized = sanitized.substring(0, MAX_ERROR_STRING_LENGTH) + "...";
	return sanitized;
}
/**
* Tracks which agent constructors have already emitted the onStateUpdate
* deprecation warning, so it fires at most once per class.
*/
const _onStateUpdateWarnedClasses = /* @__PURE__ */ new WeakSet();
/**
* Tracks which agent constructors have already emitted the
* sendIdentityOnConnect deprecation warning, so it fires at most once per class.
*/
const _sendIdentityWarnedClasses = /* @__PURE__ */ new WeakSet();
/**
* Default options for Agent configuration.
* Child classes can override specific options without spreading.
*/
const DEFAULT_AGENT_STATIC_OPTIONS = {
	/** Whether to send identity (name, agent) to clients on connect */
	sendIdentityOnConnect: true,
	/**
	* Timeout in seconds before a running interval schedule is considered "hung"
	* and force-reset. Increase this if you have callbacks that legitimately
	* take longer than 30 seconds.
	*/
	hungScheduleTimeoutSeconds: 30,
	/**
	* Interval in milliseconds for keepAlive() alarm heartbeats.
	* Lower values mean faster recovery after eviction but more frequent alarms.
	*/
	keepAliveIntervalMs: DEFAULT_KEEP_ALIVE_INTERVAL_MS,
	/** Default retry options for schedule(), queue(), and this.retry() */
	retry: {
		maxAttempts: 3,
		baseDelayMs: 100,
		maxDelayMs: 3e3
	},
	/** Timeout for internal framework fiber recovery hooks. */
	fiberRecoveryHookTimeoutMs: 1e4,
	/** Soft deadline for one interrupted-fiber recovery scan. */
	fiberRecoveryScanDeadlineMs: 1e4,
	/**
	* Maximum age of an unmanaged interrupted-fiber row before recovery gives
	* up. Bounds repeated retries of a `onFiberRecovered()` hook that keeps
	* throwing so a poison row cannot re-trigger forever across boots.
	*/
	fiberRecoveryMaxAgeMs: 1440 * 60 * 1e3,
	/**
	* No-progress budget (ms) for re-attaching to a still-running agent-tool
	* child after a deploy / parent recovery (#1630). Bounds how long the parent
	* waits with NO forward progress from the child; it resets on every forwarded
	* chunk, so a child that keeps streaming is never abandoned mid-flight. Only a
	* genuinely silent/hung child seals `interrupted` after a full window. Raise
	* for children with long quiet stretches between outputs.
	*/
	agentToolReattachNoProgressTimeoutMs: DEFAULT_AGENT_TOOL_REATTACH_NO_PROGRESS_TIMEOUT_MS,
	/**
	* Optional hard wall-clock ceiling (ms) on a single agent-tool re-attach
	* (#1630). Caps the total wait even as the no-progress budget re-arms across
	* stream-closes. Defaults to `Infinity` (no implicit cap), mirroring
	* chat-recovery's `maxRecoveryWork` (#1672): a healthy, still-advancing child
	* is followed for as long as it makes progress — a hung child is bounded by
	* the no-progress budget, and a content-runaway by the child's own
	* `maxRecoveryWork` / `shouldKeepRecovering`. Set a finite value to impose a
	* wall-clock cap (which also tears the child down on `window-exceeded`).
	*/
	agentToolReattachMaxWindowMs: DEFAULT_AGENT_TOOL_REATTACH_MAX_WINDOW_MS,
	detachedMaxBudgetMs: DEFAULT_DETACHED_MAX_BUDGET_MS,
	detachedNoProgressBudgetMs: DEFAULT_DETACHED_NO_PROGRESS_BUDGET_MS,
	/**
	* Consecutive alarm invocations that may end in a Durable Object memory-limit
	* reset (the isolate exceeded its 128 MB limit) before the alarm-boundary
	* circuit breaker stops the platform's auto-retry loop and seals the looping
	* work (#1825). A small budget tolerates a genuinely transient memory spike;
	* a deterministic OOM (the work's footprint, not the platform, is the cause)
	* is bounded here regardless of whether the in-DO recovery budgets could run.
	*/
	maxAlarmMemoryLimitStrikes: 3
};
/** Compatibility alias for the lifecycle-owned current Agent accessor. */
const getCurrentAgent = getCurrentAgent$1;
/**
* Restore Agent context when a public method is entered outside a Lifecycle
* hook, notably through native Durable Object RPC or cross-Agent re-entry.
* Lifecycle already owns context for its capability and semantic user hooks.
*/
function withAgentContext(method) {
	return function(...args) {
		const { agent } = getCurrentAgent();
		if (agent === this) return method.apply(this, args);
		return runInInvocation({
			agent: this,
			connection: void 0,
			request: void 0,
			email: void 0
		}, () => {
			return method.apply(this, args);
		});
	};
}
/**
* Base class for creating Agent implementations
* @template Env Environment type containing bindings
* @template TState State type to store within the Agent
*/
var Agent = class Agent extends DurableObject {
	/** Run user initialization after lifecycle components have started. */
	onStart(_props) {}
	/** Handle an HTTP request not claimed by a lifecycle component. */
	onRequest(_request) {
		return new Response("Not implemented", { status: 404 });
	}
	/** Handle a newly accepted hibernating WebSocket connection. */
	onConnect(_connection, _context) {}
	/** Handle a message from a hibernating WebSocket connection. */
	onMessage(_connection, _message) {}
	/** Handle a hibernating WebSocket connection closing. */
	onClose(_connection, _code, _reason, _wasClean) {}
	/** Return tags persisted with a hibernating WebSocket connection. */
	getConnectionTags(_connection, _context) {
		return [];
	}
	/** @internal Ensure lifecycle startup before a native RPC implementation. */
	async __unsafe_ensureInitialized(props) {
		await this.lifecycle.start(props);
	}
	/** @internal */
	get _dynamicAgents() {
		this._dynamicAgentsInstance ??= new DynamicAgentsInternal(this);
		return this._dynamicAgentsInstance;
	}
	/**
	* The dynamic-agents capability: facet-backed child agents that run
	* in their own isolate with their own SQLite database, colocated
	* with — and supervised by — this agent.
	*
	* Use dynamic agents for code whose class or lifecycle this agent
	* owns: dynamically-loaded or AI-generated code, per-run tool
	* agents, sandboxed components. For independent peers (for example
	* one Durable Object per chat), use `getAgentByName` instead.
	*
	* ```ts
	* const child = await this.dynamicAgents.get(Researcher, id);
	* await child.doWork();
	* this.dynamicAgents.abort(Researcher, id, reason);
	* await this.dynamicAgents.delete(Researcher, id);
	* ```
	*
	* @experimental The API surface may change before stabilizing.
	*/
	get dynamicAgents() {
		this._dynamicAgentsApi ??= new DynamicAgents(this._dynamicAgents);
		return this._dynamicAgentsApi;
	}
	/**
	* Stable key for Workers AI session affinity (prefix-cache optimization).
	*
	* Uses the Durable Object ID, which is globally unique across all agent
	* classes and stable for the lifetime of the instance. Pass this value as
	* the `sessionAffinity` option when creating a Workers AI model so that
	* requests from the same agent instance are routed to the same backend
	* replica, improving KV-prefix-cache hit rates across conversation turns.
	*
	* @example
	* ```typescript
	* const workersai = createWorkersAI({ binding: this.env.AI });
	* const model = workersai("@cf/meta/llama-3.3-70b-instruct-fp8-fast", {
	*   sessionAffinity: this.sessionAffinity,
	* });
	* ```
	*/
	get sessionAffinity() {
		return this.ctx.id.toString();
	}
	/**
	* Current state of the Agent.
	*
	* Delegates to the State capability, which owns lazy load and the
	* in-memory cache; Agent seeds `initialState` on first access.
	*/
	get state() {
		const stored = this._state.get();
		if (stored !== void 0) return stored;
		if (this.initialState === DEFAULT_STATE) return void 0;
		this._state.set(this.initialState, "server");
		return this.initialState;
	}
	get _resolvedOptions() {
		if (this._cachedOptions) return this._cachedOptions;
		const ctor = this.constructor;
		const userRetry = ctor.options?.retry;
		this._cachedOptions = {
			sendIdentityOnConnect: ctor.options?.sendIdentityOnConnect ?? DEFAULT_AGENT_STATIC_OPTIONS.sendIdentityOnConnect,
			hungScheduleTimeoutSeconds: ctor.options?.hungScheduleTimeoutSeconds ?? DEFAULT_AGENT_STATIC_OPTIONS.hungScheduleTimeoutSeconds,
			keepAliveIntervalMs: ctor.options?.keepAliveIntervalMs ?? DEFAULT_AGENT_STATIC_OPTIONS.keepAliveIntervalMs,
			retry: {
				maxAttempts: userRetry?.maxAttempts ?? DEFAULT_AGENT_STATIC_OPTIONS.retry.maxAttempts,
				baseDelayMs: userRetry?.baseDelayMs ?? DEFAULT_AGENT_STATIC_OPTIONS.retry.baseDelayMs,
				maxDelayMs: userRetry?.maxDelayMs ?? DEFAULT_AGENT_STATIC_OPTIONS.retry.maxDelayMs
			},
			fiberRecoveryHookTimeoutMs: ctor.options?.fiberRecoveryHookTimeoutMs ?? DEFAULT_AGENT_STATIC_OPTIONS.fiberRecoveryHookTimeoutMs,
			fiberRecoveryScanDeadlineMs: ctor.options?.fiberRecoveryScanDeadlineMs ?? DEFAULT_AGENT_STATIC_OPTIONS.fiberRecoveryScanDeadlineMs,
			fiberRecoveryMaxAgeMs: ctor.options?.fiberRecoveryMaxAgeMs ?? DEFAULT_AGENT_STATIC_OPTIONS.fiberRecoveryMaxAgeMs,
			agentToolReattachNoProgressTimeoutMs: ctor.options?.agentToolReattachNoProgressTimeoutMs ?? DEFAULT_AGENT_STATIC_OPTIONS.agentToolReattachNoProgressTimeoutMs,
			agentToolReattachMaxWindowMs: ctor.options?.agentToolReattachMaxWindowMs ?? DEFAULT_AGENT_STATIC_OPTIONS.agentToolReattachMaxWindowMs,
			detachedMaxBudgetMs: ctor.options?.detachedMaxBudgetMs ?? DEFAULT_AGENT_STATIC_OPTIONS.detachedMaxBudgetMs,
			detachedNoProgressBudgetMs: ctor.options?.detachedNoProgressBudgetMs ?? DEFAULT_AGENT_STATIC_OPTIONS.detachedNoProgressBudgetMs,
			maxAlarmMemoryLimitStrikes: ctor.options?.maxAlarmMemoryLimitStrikes ?? DEFAULT_AGENT_STATIC_OPTIONS.maxAlarmMemoryLimitStrikes
		};
		return this._cachedOptions;
	}
	/**
	* Emit an observability event with auto-generated timestamp.
	* @internal
	*/
	_emit(type, payload = {}) {
		this.observability?.emit({
			type,
			agent: this._ParentClass.name,
			name: this.name,
			payload,
			timestamp: Date.now()
		});
	}
	_withAgentSpan(operation, storagePhase, attributes, run) {
		let agentId;
		try {
			agentId = this.name;
		} catch {
			agentId = void 0;
		}
		return tracer.withSpan(operation, {
			...agentSpanAttributes({
				agentClassName: this._ParentClass.name,
				sessionId: this.ctx.id.toString(),
				sessionName: agentId
			}),
			"cloudflare.agents.operation.name": operation,
			"cloudflare.agents.storage.grouped": true,
			"cloudflare.agents.storage.system": "durable_object",
			"cloudflare.agents.storage.phase": storagePhase,
			...attributes
		}, (span) => run((finishAttributes) => writeSpanAttributes(span, finishAttributes)), __DO_NOT_USE_WILL_BREAK__agentContext.getStore()?.connection === void 0 ? void 0 : { boundToInvocation: true });
	}
	/**
	* Execute SQL queries against the Agent's database
	* @template T Type of the returned rows
	* @param strings SQL query template strings
	* @param values Values to be inserted into the query
	* @returns Array of query results
	*/
	sql(strings, ...values) {
		let query = "";
		try {
			query = strings.reduce((acc, str, i) => acc + str + (i < values.length ? "?" : ""), "");
			return [...this.ctx.storage.sql.exec(query, ...values)];
		} catch (e) {
			throw new SqlError(query, e);
		}
	}
	/**
	* Create all internal tables and run migrations if needed.
	* Called by the constructor on every wake. Idempotent — skips DDL when
	* the stored schema version matches CURRENT_SCHEMA_VERSION.
	*
	* Protected so that test agents can re-run the real migration path
	* after manipulating DB state (since ctx.abort() is unavailable in
	* local dev and the constructor only runs once per DO instance).
	*/
	_ensureSchema() {
		const schemaVersion = this._readSchemaVersion();
		if (schemaVersion < CURRENT_SCHEMA_VERSION) {
			ensureMcpServerTable(this.ctx.storage);
			const addColumnIfNotExists = (sql) => {
				try {
					this.ctx.storage.sql.exec(sql);
				} catch (error) {
					if (!(error instanceof Error ? error.message : String(error)).toLowerCase().includes("duplicate column")) throw error;
				}
			};
			this.sql`
        CREATE TABLE IF NOT EXISTS cf_agents_workflows (
          id TEXT PRIMARY KEY NOT NULL,
          workflow_id TEXT NOT NULL UNIQUE,
          workflow_name TEXT NOT NULL,
          status TEXT NOT NULL CHECK(status IN (
            'queued', 'running', 'paused', 'errored',
            'terminated', 'complete', 'waiting',
            'waitingForPause', 'unknown'
          )),
          metadata TEXT,
          error_name TEXT,
          error_message TEXT,
          created_at INTEGER NOT NULL DEFAULT (unixepoch()),
          updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
          completed_at INTEGER
        )
      `;
			this.sql`
        CREATE INDEX IF NOT EXISTS idx_workflows_status ON cf_agents_workflows(status)
      `;
			this.sql`
        CREATE INDEX IF NOT EXISTS idx_workflows_name ON cf_agents_workflows(workflow_name)
      `;
			this.sql`
        CREATE TABLE IF NOT EXISTS cf_agents_runs (
          id TEXT PRIMARY KEY NOT NULL,
          name TEXT NOT NULL,
          snapshot TEXT,
          created_at INTEGER NOT NULL
        )
      `;
			this.sql`
        CREATE TABLE IF NOT EXISTS cf_agents_facet_runs (
          owner_path TEXT NOT NULL,
          owner_path_key TEXT NOT NULL,
          run_id TEXT NOT NULL,
          created_at INTEGER NOT NULL,
          PRIMARY KEY (owner_path_key, run_id)
        )
      `;
			this.sql`
        CREATE INDEX IF NOT EXISTS idx_facet_runs_owner_path_key
        ON cf_agents_facet_runs(owner_path_key)
      `;
			this.sql`
        CREATE TABLE IF NOT EXISTS cf_agents_fibers (
          fiber_id TEXT PRIMARY KEY,
          idempotency_key TEXT UNIQUE,
          name TEXT NOT NULL,
          status TEXT NOT NULL,
          snapshot TEXT,
          metadata_json TEXT,
          error_message TEXT,
          created_at INTEGER NOT NULL,
          started_at INTEGER,
          completed_at INTEGER
        )
      `;
			this.sql`
        CREATE INDEX IF NOT EXISTS idx_fibers_status_created
        ON cf_agents_fibers(status, created_at, fiber_id)
      `;
			this.sql`
        CREATE INDEX IF NOT EXISTS idx_fibers_name_status_created
        ON cf_agents_fibers(name, status, created_at, fiber_id)
      `;
			this.sql`
        CREATE INDEX IF NOT EXISTS idx_fibers_status_completed
        ON cf_agents_fibers(status, completed_at, created_at)
      `;
			this.sql`
        CREATE TABLE IF NOT EXISTS cf_agent_tool_runs (
          run_id TEXT PRIMARY KEY,
          parent_tool_call_id TEXT,
          agent_type TEXT NOT NULL,
          input_preview TEXT,
          input_redacted INTEGER NOT NULL DEFAULT 1,
          status TEXT NOT NULL,
          summary TEXT,
          output_json TEXT,
          error_message TEXT,
          interrupted_reason TEXT,
          child_still_running INTEGER,
          display_metadata TEXT,
          display_order INTEGER NOT NULL DEFAULT 0,
          started_at INTEGER NOT NULL,
          completed_at INTEGER
        )
      `;
			this.sql`
        CREATE INDEX IF NOT EXISTS idx_agent_tool_runs_parent_tool_call_id
        ON cf_agent_tool_runs(parent_tool_call_id, display_order)
      `;
			addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN output_json TEXT");
			addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN interrupted_reason TEXT");
			addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN child_still_running INTEGER");
			addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached INTEGER NOT NULL DEFAULT 0");
			addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_on_finish TEXT");
			addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_notify_source TEXT");
			addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_max_budget_at INTEGER");
			addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN finish_claimed_at INTEGER");
			addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN finish_delivered_at INTEGER");
			addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN give_up_claimed_at INTEGER");
			addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN give_up_delivered_at INTEGER");
			addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_no_progress_budget_ms INTEGER");
			addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN last_progress_at INTEGER");
			addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_on_milestones TEXT");
			this.ctx.storage.kv.put(SCHEMA_VERSION_KEY, CURRENT_SCHEMA_VERSION);
		}
		this._schemaInitialization = {
			previousVersion: schemaVersion,
			currentVersion: CURRENT_SCHEMA_VERSION,
			migrated: schemaVersion < CURRENT_SCHEMA_VERSION
		};
	}
	/**
	* Read the Agent's schema version from its KV key. A DO created before the
	* State capability owned `cf_agents_state` has the version as a row in that
	* table instead: read it once, move it to the key, and delete the row so the
	* table is left with a single owner. Synchronous (`storage.kv`) because the
	* constructor gates DDL on it.
	*/
	_readSchemaVersion() {
		const stored = this.ctx.storage.kv.get(SCHEMA_VERSION_KEY);
		if (stored !== void 0) return stored;
		if (!(this.ctx.storage.sql.exec("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'cf_agents_state'").toArray().length > 0)) return 0;
		const rows = this.ctx.storage.sql.exec("SELECT state FROM cf_agents_state WHERE id = ?", LEGACY_SCHEMA_VERSION_ROW_ID).toArray();
		if (rows.length === 0) return 0;
		const version = Number(rows[0].state) || 0;
		this.ctx.storage.kv.put(SCHEMA_VERSION_KEY, version);
		this.ctx.storage.sql.exec("DELETE FROM cf_agents_state WHERE id = ?", LEGACY_SCHEMA_VERSION_ROW_ID);
		return version;
	}
	constructor(ctx, env) {
		super(ctx, env);
		this.lifecycle = Lifecycle.install(this, { maxAlarmMemoryLimitStrikes: this._resolvedOptions.maxAlarmMemoryLimitStrikes });
		this._state = new State({
			validateStateChange: (nextState, source) => this.validateStateChange(nextState, source),
			onChanged: (nextState, source) => this._handleStateChanged(nextState, source)
		});
		this._webSockets = new WebSockets({
			handlers: {
				onConnect: (connection, ctx) => this.onConnect(connection, ctx),
				onMessage: (connection, message) => this.onMessage(connection, message),
				onClose: (connection, code, reason, wasClean) => this.onClose(connection, code, reason, wasClean),
				onError: (connection, error) => this.onError(connection, error)
			},
			protocol: false,
			state: this._state,
			getConnectionTags: (connection, ctx) => this.getConnectionTags(connection, ctx)
		});
		this._disposables = new DisposableStore();
		this._destroyed = false;
		this._persistenceHookMode = "none";
		this._isFacet = false;
		this._protocolBroadcastExcludeIds = /* @__PURE__ */ new Set();
		this._parentPath = [];
		this._warnedChatRecoveryInOnStart = false;
		this._keepAliveRefs = 0;
		this._runFiberActiveFibers = /* @__PURE__ */ new Set();
		this._managedFiberAbortControllers = /* @__PURE__ */ new Map();
		this._managedFiberExecutions = /* @__PURE__ */ new Map();
		this._managedFiberTerminalWaiters = /* @__PURE__ */ new Map();
		this._runFiberRecoveryInProgress = false;
		this._recoveryNoProgressScans = 0;
		this._detachedBackboneArming = Promise.resolve();
		this._detachedLiveCountWarned = false;
		this._ParentClass = Object.getPrototypeOf(this).constructor;
		this.initialState = DEFAULT_STATE;
		this.observability = genericObservability;
		this.maxConcurrentAgentTools = Infinity;
		const routeHost = this;
		setLifecycleRouteTransport(this.lifecycle, {
			get source() {
				return routeHost._lifecycleRouteAddress();
			},
			toRoot: (envelope) => this._routeLifecycleToRoot(envelope),
			to: (target, envelope) => this._routeLifecycleToTarget(target, envelope)
		});
		setLifecycleEventSink(this.lifecycle, (event) => {
			const payload = event.payload !== null && typeof event.payload === "object" && !Array.isArray(event.payload) ? event.payload : { value: event.payload };
			this._emit(event.type, payload);
		});
		setLifecycleHostInvoker(this.lifecycle, (run, scope) => runInInvocation({
			agent: this,
			connection: scope?.connection,
			request: scope?.request,
			email: void 0
		}, run));
		this.scheduler = new Scheduler({
			retry: this._resolvedOptions.retry,
			hungScheduleTimeoutSeconds: this._resolvedOptions.hungScheduleTimeoutSeconds,
			onError: (error) => runInInvocation({
				agent: this,
				connection: void 0,
				request: void 0,
				email: void 0
			}, () => this.onError(error))
		});
		setSchedulerCallbackResolver(this.scheduler, (name) => {
			const method = this[name];
			if (typeof method !== "function") return void 0;
			return (payload, schedule) => method.call(this, payload, schedule);
		});
		this._queue = new Queue({
			retry: this._resolvedOptions.retry,
			onError: (error) => runInInvocation({
				agent: this,
				connection: void 0,
				request: void 0,
				email: void 0
			}, () => this.onError(error))
		});
		setQueueCallbackResolver(this._queue, (name) => {
			const method = this[name];
			if (typeof method !== "function") return void 0;
			return (payload, item) => method.call(this, payload, item);
		});
		this.tasks = new Tasks({ onError: (error) => this.onError(error) });
		setTaskRoutedMemoryLimitHandler(this.tasks, (context) => {
			return this.onAlarmMemoryLimit?.call(this, context);
		});
		setTaskDefinitionResolver(this.tasks, (name) => this.taskDefinitions?.[name]);
		this.mcp = this._withAgentSpan("agent_initialization", "initialization", {}, (update) => {
			if (!wrappedClasses.has(this.constructor)) {
				this._autoWrapCustomMethods();
				wrappedClasses.add(this.constructor);
			}
			this._withAgentSpan("initialize_agent_storage", "initialization", {}, (updateStorage) => {
				this._ensureSchema();
				const schemaAttributes = {
					"cloudflare.agents.schema.version.previous": this._schemaInitialization?.previousVersion,
					"cloudflare.agents.schema.version.current": this._schemaInitialization?.currentVersion,
					"cloudflare.agents.schema.migrated": this._schemaInitialization?.migrated
				};
				updateStorage(schemaAttributes);
				update(schemaAttributes);
			});
			return new MCPClientManager(this._ParentClass.name, "0.0.1", {
				env: this.env,
				createAuthProvider: (callbackUrl) => this.createMcpOAuthProvider(callbackUrl)
			});
		});
		this.lifecycle.use(this.scheduler).use(this._queue).use(this.mcp).use(this._state).use(this._webSockets).use(this.tasks).use(this._dynamicAgents);
		let mcpBroadcastReady = false;
		this._disposables.add(this.mcp.onServerStateChanged(() => {
			if (mcpBroadcastReady) this.broadcastMcpServers();
		}));
		this._disposables.add(this.mcp.onObservabilityEvent((event) => {
			this.observability?.emit({
				...event,
				agent: this._ParentClass.name,
				name: this.name
			});
		}));
		{
			const proto = Object.getPrototypeOf(this);
			const hasOwnNew = Object.prototype.hasOwnProperty.call(proto, "onStateChanged");
			const hasOwnOld = Object.prototype.hasOwnProperty.call(proto, "onStateUpdate");
			if (hasOwnNew && hasOwnOld) throw new Error("[Agent] Cannot override both onStateChanged and onStateUpdate. Remove onStateUpdate — it has been renamed to onStateChanged.");
			if (hasOwnOld) {
				const ctor = this.constructor;
				if (!_onStateUpdateWarnedClasses.has(ctor)) {
					_onStateUpdateWarnedClasses.add(ctor);
					console.warn(`[Agent] onStateUpdate is deprecated. Rename to onStateChanged — the behavior is identical.`);
				}
			}
			const base = Agent.prototype;
			if (proto.onStateChanged !== base.onStateChanged) this._persistenceHookMode = "new";
			else if (proto.onStateUpdate !== base.onStateUpdate) this._persistenceHookMode = "old";
		}
		const _onAlarm = this.onAlarm.bind(this);
		this.onAlarm = async () => {
			if (this._destroyed) return;
			await _onAlarm();
			if (this._destroyed) return;
			await this._onAlarmHousekeeping();
			if (this._destroyed) return;
			await this._syncHostJobs();
		};
		const _onRequest = this.onRequest.bind(this);
		this.onRequest = (request) => {
			return runInInvocation({
				agent: this,
				connection: void 0,
				request,
				email: void 0
			}, () => this._tryCatch(() => _onRequest(request)));
		};
		const _onMessage = this.onMessage.bind(this);
		this.onMessage = async (connection, message) => {
			const replyBridge = dynamicAgentRpcReplyContext.getStore()?.bridge;
			if (await __DO_NOT_USE_WILL_BREAK__agentContext.exit(() => this._cf_forwardSubAgentWebSocketMessage(connection, message, replyBridge))) return;
			ensureConnectionWrapped(connection);
			return runInInvocation({
				agent: this,
				connection,
				request: void 0,
				email: void 0
			}, async () => {
				if (typeof message !== "string") return this._tryCatch(() => _onMessage(connection, message));
				let parsed;
				try {
					parsed = JSON.parse(message);
				} catch (_e) {
					return this._tryCatch(() => _onMessage(connection, message));
				}
				if (this._webSockets.applyStateFrame(connection, parsed)) return;
				if (isRPCRequest(parsed)) {
					try {
						const { id, method, args } = parsed;
						const methodFn = this[method];
						if (typeof methodFn !== "function") throw new Error(`Method ${method} does not exist`);
						if (!this._isCallable(method)) throw new Error(`Method ${method} is not callable`);
						const metadata = getCallableMetadata(methodFn);
						if (metadata?.streaming) {
							const stream = new StreamingResponse(connection, id);
							if (replyBridge) registerFacetStreamingDelivery(stream, replyBridge);
							this._emit("rpc", {
								method,
								streaming: true
							});
							try {
								await methodFn.apply(this, [stream, ...args]);
							} catch (err) {
								console.error(`Error in streaming method "${method}":`, err);
								this._emit("rpc:error", {
									method,
									error: err instanceof Error ? err.message : String(err)
								});
								if (!stream.isClosed) stream.error(err instanceof Error ? err.message : String(err));
							}
							await waitForFacetStreamingResponseDeliveries(stream);
							return;
						}
						const result = await methodFn.apply(this, args);
						this._emit("rpc", {
							method,
							streaming: metadata?.streaming
						});
						const response = {
							done: true,
							id,
							result,
							success: true,
							type: "rpc"
						};
						if (replyBridge) await sendFacetRpcResponseIfOpen(replyBridge, response).completion;
						else sendRpcResponseIfOpen(connection, response);
					} catch (e) {
						const response = {
							error: e instanceof Error ? e.message : "Unknown error occurred",
							id: parsed.id,
							success: false,
							type: "rpc"
						};
						if (replyBridge) await sendFacetRpcResponseIfOpen(replyBridge, response).completion;
						else sendRpcResponseIfOpen(connection, response);
						console.error("RPC error:", e);
						this._emit("rpc:error", {
							method: parsed.method,
							error: e instanceof Error ? e.message : String(e)
						});
					}
					return;
				}
				return this._tryCatch(() => _onMessage(connection, message));
			});
		};
		const _onConnect = this.onConnect.bind(this);
		this.onConnect = async (connection, ctx) => {
			ensureConnectionWrapped(connection);
			const subAgentOuterUrl = ctx.request.headers.get(SUB_AGENT_OUTER_URL_HEADER);
			if (subAgentOuterUrl) this._unsafe_setConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY, subAgentOuterUrl);
			if (await __DO_NOT_USE_WILL_BREAK__agentContext.exit(() => this._cf_forwardSubAgentWebSocketConnect(connection, ctx.request, { gate: false }))) return;
			return runInInvocation({
				agent: this,
				connection,
				request: ctx.request,
				email: void 0
			}, async () => {
				if (this.shouldConnectionBeReadonly(connection, ctx)) this.setConnectionReadonly(connection, true);
				if (this.shouldSendProtocolMessages(connection, ctx)) {
					if (this._resolvedOptions.sendIdentityOnConnect) {
						const ctor = this.constructor;
						if (ctor.options?.sendIdentityOnConnect === void 0 && !_sendIdentityWarnedClasses.has(ctor) && !this._isFacet) {
							if (!new URL(ctx.request.url).pathname.includes(this.name)) {
								_sendIdentityWarnedClasses.add(ctor);
								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.`);
							}
						}
						this._webSockets.sendIdentity(connection, {
							name: this.name,
							agent: camelCaseToKebabCase(this._ParentClass.name)
						});
					}
					const wasExcludedFromStateInitBroadcast = this._protocolBroadcastExcludeIds.has(connection.id);
					let currentState;
					this._protocolBroadcastExcludeIds.add(connection.id);
					try {
						currentState = this.state;
					} finally {
						if (!wasExcludedFromStateInitBroadcast) this._protocolBroadcastExcludeIds.delete(connection.id);
					}
					if (currentState !== void 0) this._webSockets.sendState(connection);
					connection.send(JSON.stringify({
						mcp: this.getMcpServers(),
						type: "cf_agent_mcp_servers"
					}));
				} else this._webSockets.setProtocolEnabled(connection, false);
				this._emit("connect", { connectionId: connection.id });
				await this._replayAgentToolRuns(connection);
				return this._tryCatch(() => _onConnect(connection, ctx));
			});
		};
		const _onClose = this.onClose.bind(this);
		this.onClose = async (connection, code, reason, wasClean) => {
			if (await __DO_NOT_USE_WILL_BREAK__agentContext.exit(() => this._cf_forwardSubAgentWebSocketClose(connection, code, reason, wasClean))) return;
			return runInInvocation({
				agent: this,
				connection,
				request: void 0,
				email: void 0
			}, () => {
				this._emit("disconnect", {
					connectionId: connection.id,
					code,
					reason
				});
				return _onClose(connection, code, reason, wasClean);
			});
		};
		const _onStart = this.onStart.bind(this);
		const startAgent = async (props, update) => {
			return runInInvocation({
				agent: this,
				connection: void 0,
				request: void 0,
				email: void 0
			}, async () => {
				await this._restoreAgentFacetContext();
				await this._tryCatch(async () => {
					mcpBroadcastReady = true;
					this.broadcastMcpServers();
					const startupAgentToolRunIds = await this._withAgentSpan("recover_agent_work", "startup", {}, async () => {
						this._checkOrphanedWorkflows();
						await this._checkRunFibers();
						return this._agentToolRunRecoveryRunIds();
					});
					update({
						"cloudflare.agents.start.facet": this._isFacet,
						"cloudflare.agents.recovery.agent_tools.count": startupAgentToolRunIds.length
					});
					const chatRecoveryBefore = this.chatRecovery;
					const result = await this._withAgentSpan("run_user_on_start", "startup", {}, () => _onStart(props));
					const chatRecoveryAfter = this.chatRecovery;
					const chatRecoveryAfterMatters = typeof chatRecoveryAfter === "boolean" || typeof chatRecoveryAfter === "object" && chatRecoveryAfter !== null;
					if (!this._warnedChatRecoveryInOnStart && chatRecoveryBefore !== chatRecoveryAfter && chatRecoveryAfterMatters) {
						this._warnedChatRecoveryInOnStart = true;
						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.");
					}
					this._scheduleAgentToolRunRecovery({ runIds: startupAgentToolRunIds });
					await this._syncHostJobs();
					return result;
				});
			});
		};
		this.onStart = (props) => this._withAgentSpan("agent_start", "startup", {}, (update) => startAgent(props, update));
	}
	async _restoreAgentFacetContext() {
		await this._withAgentSpan("restore_agent_state", "startup", {}, () => this._dynamicAgents.restoreFacetContext());
	}
	/**
	* Check for workflows referencing unknown bindings and warn with migration suggestion.
	*/
	_checkOrphanedWorkflows() {
		const orphaned = this.sql`
      SELECT 
        workflow_name,
        COUNT(*) as total,
        SUM(CASE WHEN status NOT IN ('complete', 'errored', 'terminated') THEN 1 ELSE 0 END) as active,
        SUM(CASE WHEN status IN ('complete', 'errored', 'terminated') THEN 1 ELSE 0 END) as completed
      FROM cf_agents_workflows 
      GROUP BY workflow_name
    `.filter((row) => !this._findWorkflowBindingByName(row.workflow_name));
		if (orphaned.length > 0) {
			const currentBindings = this._getWorkflowBindingNames();
			for (const { workflow_name: oldName, total, active, completed } of orphaned) {
				const suggestion = currentBindings.length === 1 ? `this.migrateWorkflowBinding('${oldName}', '${currentBindings[0]}')` : `this.migrateWorkflowBinding('${oldName}', '<NEW_BINDING_NAME>')`;
				const breakdown = active > 0 && completed > 0 ? ` (${active} active, ${completed} completed)` : active > 0 ? ` (${active} active)` : ` (${completed} completed)`;
				console.warn(`[Agent] Found ${total} workflow(s) referencing unknown binding '${oldName}'${breakdown}. If you renamed the binding, call: ${suggestion}`);
			}
		}
	}
	/**
	* Broadcast a protocol message only to connections that have protocol
	* messages enabled. Connections where shouldSendProtocolMessages returned
	* false are excluded automatically.
	* @param msg The JSON-encoded protocol message
	* @param excludeIds Additional connection IDs to exclude (e.g. the source)
	*/
	_broadcastProtocol(msg, excludeIds = []) {
		const exclude = [...excludeIds, ...this._protocolBroadcastExcludeIds];
		for (const conn of this.getConnections()) if (!this.isConnectionProtocolEnabled(conn)) exclude.push(conn.id);
		this.broadcast(msg, exclude);
	}
	/**
	* React to a persisted state change from the State capability.
	*
	* Reproduces the pre-migration steps 3-4: broadcast the new state to
	* protocol-enabled connections (excluding the originating connection) and
	* run the notification hook off the invocation tail.
	*/
	_handleStateChanged(nextState, source) {
		this._broadcastProtocol(JSON.stringify({
			state: nextState,
			type: "cf_agent_state"
		}), source !== "server" ? [source.id] : []);
		const { connection, request, email } = __DO_NOT_USE_WILL_BREAK__agentContext.getStore() || {};
		this.ctx.waitUntil((async () => {
			try {
				await runInInvocation({
					agent: this,
					connection,
					request,
					email
				}, async () => {
					this._emit("state:update");
					await this._callStatePersistenceHook(nextState, source);
				}, { detached: true });
			} catch (e) {
				try {
					await this.onError(e);
				} catch {}
			}
		})());
	}
	/**
	* Update the Agent's state
	* @param state New state to set
	* @throws Error if called from a readonly connection context
	*/
	setState(state) {
		const store = __DO_NOT_USE_WILL_BREAK__agentContext.getStore();
		if (store?.connection && this.isConnectionReadonly(store.connection)) throw new Error("Connection is readonly");
		this._state.set(state, "server");
	}
	/**
	* Mark a connection as readonly or readwrite
	* @param connection The connection to mark
	* @param readonly Whether the connection should be readonly (default: true)
	*/
	setConnectionReadonly(connection, readonly = true) {
		setConnectionReadonly(connection, readonly);
	}
	/**
	* Check if a connection is marked as readonly.
	*
	* Safe to call after hibernation — re-wraps the connection if the
	* in-memory accessor cache was cleared.
	* @param connection The connection to check
	* @returns True if the connection is readonly
	*/
	isConnectionReadonly(connection) {
		return isConnectionReadonly(connection);
	}
	/**
	* ⚠️ INTERNAL — DO NOT USE IN APPLICATION CODE. ⚠️
	*
	* Read an internal `_cf_`-prefixed flag from the raw connection state,
	* bypassing the user-facing state wrapper that strips internal keys.
	*
	* This exists for framework mixins (e.g. voice) that need to persist
	* flags in the connection attachment across hibernation. Application
	* code should use `connection.state` and `connection.setState()` instead.
	*
	* @internal
	*/
	_unsafe_getConnectionFlag(connection, key) {
		return getConnectionFlag(connection, key);
	}
	/**
	* ⚠️ INTERNAL — DO NOT USE IN APPLICATION CODE. ⚠️
	*
	* Write an internal `_cf_`-prefixed flag to the raw connection state,
	* bypassing the user-facing state wrapper. The key must be registered
	* with `registerInternalConnectionKeys` so it is preserved across user
	* `setState` calls and hidden from `connection.state`.
	*
	* @internal
	*/
	_unsafe_setConnectionFlag(connection, key, value) {
		setConnectionFlag(connection, key, value);
	}
	/**
	* Override this method to determine if a connection should be readonly on connect
	* @param _connection The connection that is being established
	* @param _ctx Connection context
	* @returns True if the connection should be readonly
	*/
	shouldConnectionBeReadonly(_connection, _ctx) {
		return false;
	}
	/**
	* Override this method to control whether protocol messages are sent to a
	* connection. Protocol messages include identity (CF_AGENT_IDENTITY), state
	* sync (CF_AGENT_STATE), and MCP server lists (CF_AGENT_MCP_SERVERS).
	*
	* When this returns `false` for a connection, that connection will not
	* receive any protocol text frames — neither on connect nor via broadcasts.
	* This is useful for binary-only clients (e.g. MQTT devices) that cannot
	* handle JSON text frames.
	*
	* The connection can still send and receive regular messages, use RPC, and
	* participate in all non-protocol communication.
	*
	* @param _connection The connection that is being established
	* @param _ctx Connection context (includes the upgrade request)
	* @returns True if protocol messages should be sent (default), false to suppress them
	*/
	shouldSendProtocolMessages(_connection, _ctx) {
		return true;
	}
	/**
	* Check if a connection has protocol messages enabled.
	* Protocol messages include identity, state sync, and MCP server lists.
	*
	* Safe to call after hibernation — re-wraps the connection if the
	* in-memory accessor cache was cleared.
	* @param connection The connection to check
	* @returns True if the connection receives protocol messages
	*/
	isConnectionProtocolEnabled(connection) {
		return isConnectionProtocolEnabled(connection);
	}
	/**
	* Called before the Agent's state is persisted and broadcast.
	* Override to validate or reject an update by throwing an error.
	*
	* IMPORTANT: This hook must be synchronous.
	*/
	validateStateChange(_nextState, _source) {}
	/**
	* Called after the Agent's state has been persisted and broadcast to all clients.
	* This is a notification hook — errors here are routed to onError and do not
	* affect state persistence or client broadcasts.
	*
	* @param state Updated state
	* @param source Source of the state update ("server" or a client connection)
	*/
	onStateChanged(_state, _source) {}
	/**
	* @deprecated Renamed to `onStateChanged` — the behavior is identical.
	* `onStateUpdate` will be removed in the next major version.
	*
	* Called after the Agent's state has been persisted and broadcast to all clients.
	* This is a server-side notification hook. For the client-side state callback,
	* see the `onStateUpdate` option in `useAgent` / `AgentClient`.
	*
	* @param state Updated state
	* @param source Source of the state update ("server" or a client connection)
	*/
	onStateUpdate(_state, _source) {}
	/**
	* Dispatch to the appropriate persistence hook based on the mode
	* cached in the constructor. No prototype walks at call time.
	*/
	async _callStatePersistenceHook(state, source) {
		switch (this._persistenceHookMode) {
			case "new":
				await this.onStateChanged(state, source);
				break;
			case "old":
				await this.onStateUpdate(state, source);
				break;
		}
	}
	/**
	* Called when the Agent receives an email via routeAgentEmail()
	* Override this method to handle incoming emails
	* @param payload Internal wire format — plain data + RpcTarget bridge
	*/
	async _onEmail(payload) {
		const email = {
			from: payload.from,
			to: payload.to,
			headers: payload.headers,
			rawSize: payload.rawSize,
			_secureRouted: payload._secureRouted,
			getRaw: () => payload._bridge.getRaw(),
			setReject: (reason) => payload._bridge.setReject(reason),
			forward: (rcptTo, headers) => payload._bridge.forward(rcptTo, headers),
			reply: (options) => payload._bridge.reply(options)
		};
		return runInInvocation({
			agent: this,
			connection: void 0,
			request: void 0,
			email
		}, async () => {
			this._emit("email:receive", {
				from: email.from,
				to: email.to,
				subject: email.headers.get("subject") ?? void 0
			});
			if ("onEmail" in this && typeof this.onEmail === "function") return this._tryCatch(() => this.onEmail(email));
			else {
				console.log("Received email from:", email.from, "to:", email.to);
				console.log("Subject:", email.headers.get("subject"));
				console.log("Implement onEmail(email: AgentEmail): Promise<void> in your agent to process emails");
			}
		});
	}
	/**
	* Reply to an email
	* @param email The email to reply to
	* @param options Options for the reply
	* @param options.secret Secret for signing agent headers (enables secure reply routing).
	*   Required if the email was routed via createSecureReplyEmailResolver.
	*   Pass explicit `null` to opt-out of signing (not recommended for secure routing).
	* @returns void
	*/
	async replyToEmail(email, options) {
		return this._tryCatch(async () => {
			if (email._secureRouted && options.secret === void 0) throw new Error("This email was routed via createSecureReplyEmailResolver. You must pass a secret to replyToEmail() to sign replies, or pass explicit null to opt-out (not recommended).");
			const agentName = camelCaseToKebabCase(this._ParentClass.name);
			const agentId = this.name;
			const { createMimeMessage } = await import("mimetext");
			const msg = createMimeMessage();
			msg.setSender({
				addr: email.to,
				name: options.fromName
			});
			msg.setRecipient(email.from);
			msg.setSubject(options.subject || `Re: ${email.headers.get("subject")}` || "No subject");
			msg.addMessage({
				contentType: options.contentType || "text/plain",
				data: options.body
			});
			const messageId = `<${agentId}@${email.from.split("@")[1]}>`;
			msg.setHeader("In-Reply-To", email.headers.get("Message-ID"));
			msg.setHeader("Message-ID", messageId);
			msg.setHeader("X-Agent-Name", agentName);
			msg.setHeader("X-Agent-ID", agentId);
			if (typeof options.secret === "string") {
				const signedHeaders = await signAgentHeaders(options.secret, agentName, agentId);
				msg.setHeader("X-Agent-Sig", signedHeaders["X-Agent-Sig"]);
				msg.setHeader("X-Agent-Sig-Ts", signedHeaders["X-Agent-Sig-Ts"]);
			}
			if (options.headers) for (const [key, value] of Object.entries(options.headers)) msg.setHeader(key, value);
			await email.reply({
				from: email.to,
				raw: msg.asRaw(),
				to: email.from
			});
			const rawSubject = email.headers.get("subject");
			this._emit("email:reply", {
				from: email.to,
				to: email.from,
				subject: options.subject ?? (rawSubject ? `Re: ${rawSubject}` : void 0)
			});
		});
	}
	/**
	* Send an outbound email via an Email Service binding.
	*
	* Automatically injects agent routing headers (X-Agent-Name, X-Agent-ID).
	* When `secret` is provided, signs headers with HMAC-SHA256 so that replies
	* can be routed back to this agent instance via createSecureReplyEmailResolver.
	*
	* @param options.binding The send_email binding (e.g. this.env.EMAIL)
	* @param options.to Recipient address(es)
	* @param options.from Sender address or {email, name} object
	* @param options.subject Email subject line
	* @param options.text Plain text body (at least one of text/html required)
	* @param options.html HTML body (at least one of text/html required)
	* @param options.replyTo Reply-to address
	* @param options.cc CC recipient(s)
	* @param options.bcc BCC recipient(s)
	* @param options.inReplyTo Message-ID of the email this is replying to (for threading)
	* @param options.headers Additional custom headers
	* @param options.secret Secret for signing agent routing headers
	* @returns The messageId from Email Service
	*/
	async sendEmail(options) {
		return this._tryCatch(async () => {
			const result = await sendAgentEmail(options, {
				agentName: camelCaseToKebabCase(this._ParentClass.name),
				agentId: this.name
			});
			const fromAddr = typeof options.from === "string" ? options.from : options.from.email;
			this._emit("email:send", {
				from: fromAddr,
				to: options.to,
				subject: options.subject
			});
			return result;
		});
	}
	async _tryCatch(fn) {
		try {
			return await fn();
		} catch (e) {
			throw this.onError(e);
		}
	}
	/**
	* Wrap public subclass methods that may be entered outside Lifecycle, such as
	* native Durable Object RPC. Lifecycle hooks already have Agent context.
	*/
	_autoWrapCustomMethods() {
		const basePrototypes = [Agent.prototype];
		const baseMethods = /* @__PURE__ */ new Set();
		for (const baseProto of basePrototypes) {
			let proto = baseProto;
			while (proto && proto !== Object.prototype) {
				const methodNames = Object.getOwnPropertyNames(proto);
				for (const methodName of methodNames) baseMethods.add(methodName);
				proto = Object.getPrototypeOf(proto);
			}
		}
		let proto = Object.getPrototypeOf(this);
		let depth = 0;
		while (proto && proto !== Object.prototype && depth < 10) {
			const methodNames = Object.getOwnPropertyNames(proto);
			for (const methodName of methodNames) {
				const descriptor = Object.getOwnPropertyDescriptor(proto, methodName);
				if (baseMethods.has(methodName) || methodName.startsWith("_") || !descriptor || !!descriptor.get || typeof descriptor.value !== "function") continue;
				const wrappedFunction = withAgentContext(this[methodName]);
				if (this._isCallable(methodName)) copyCallableMetadata(this[methodName], wrappedFunction);
				this.constructor.prototype[methodName] = wrappedFunction;
			}
			proto = Object.getPrototypeOf(proto);
			depth++;
		}
	}
	onError(connectionOrError, error) {
		let theError;
		if (connectionOrError && error) {
			theError = error;
			console.error("Error on websocket connection:", connectionOrError.id, theError);
			console.error("Override onError(connection, error) to handle websocket connection errors");
		} else {
			theError = connectionOrError;
			console.error("Error on server:", theError);
			console.error("Override onError(error) to handle server errors");
		}
		throw theError;
	}
	/**
	* Render content (not implemented in base class)
	*/
	render() {
		throw new Error("Not implemented");
	}
	/**
	* Retry an async operation with exponential backoff and jitter.
	* Retries on all errors by default. Use `shouldRetry` to bail early on non-retryable errors.
	*
	* @param fn The async function to retry. Receives the current attempt number (1-indexed).
	* @param options Retry configuration.
	* @param options.maxAttempts Maximum number of attempts (including the first). Falls back to static options, then 3.
	* @param options.baseDelayMs Base delay in ms for exponential backoff. Falls back to static options, then 100.
	* @param options.maxDelayMs Maximum delay cap in ms. Falls back to static options, then 3000.
	* @param options.shouldRetry Predicate called with the error and next attempt number. Return false to stop retrying immediately. Default: retry all errors.
	* @returns The result of fn on success.
	* @throws The last error if all attempts fail or shouldRetry returns false.
	*/
	async retry(fn, options) {
		const defaults = this._resolvedOptions.retry;
		if (options) validateRetryOptions(options, defaults);
		return tryN(options?.maxAttempts ?? defaults.maxAttempts, fn, {
			baseDelayMs: options?.baseDelayMs ?? defaults.baseDelayMs,
			maxDelayMs: options?.maxDelayMs ?? defaults.maxDelayMs,
			shouldRetry: options?.shouldRetry
		});
	}
	/**
	* Queue a task to run in the background.
	*
	* The item is durable: it runs from the Lifecycle alarm event loop after
	* this call returns, in push order, one at a time, with retries per
	* `options.retry`, and survives the Durable Object leaving memory.
	* @param callback Name of the method to call
	* @param payload Payload to pass to the callback
	* @param options Options for the queued task
	* @param options.retry Retry options for the callback execution
	* @param options.id Stable id; a push with an existing id replaces that item
	* @returns The ID of the queued task
	*/
	async queue(callback, payload, options) {
		if (typeof callback !== "string") throw new Error("Callback must be a string");
		if (typeof this[callback] !== "function") throw new Error(`this.${callback} is not a function`);
		return (await this._queue.push(callback, payload, options)).id;
	}
	/**
	* Dequeue a task by ID
	* @param id ID of the task to dequeue
	*/
	dequeue(id) {
		return this._queue.cancel(id);
	}
	/**
	* Dequeue all tasks
	*/
	dequeueAll() {
		return this._queue.cancelAll();
	}
	/**
	* Dequeue all tasks by callback
	* @param callback Name of the callback to dequeue
	*/
	dequeueAllByCallback(callback) {
		return this._queue.cancelAll(callback);
	}
	/**
	* Get a queued task by ID
	* @param id ID of the task to get
	* @returns The task or undefined if not found
	*/
	getQueue(id) {
		return this._queue.get(id);
	}
	/**
	* Get all queued tasks whose payload has `key` equal to `value`
	* @param key Key to filter by
	* @param value Value to filter by
	* @returns Array of matching QueueItem objects
	*/
	async getQueues(key, value) {
		return (await this._queue.list()).filter((item) => typeof item.payload === "object" && item.payload !== null && item.payload[key] === value);
	}
	_lifecycleRouteAddress() {
		return this._dynamicAgents.lifecycleRouteAddress();
	}
	_routeLifecycleToRoot(envelope) {
		return this._dynamicAgents.routeLifecycleToRoot(envelope);
	}
	_routeLifecycleToTarget(target, envelope) {
		return this._dynamicAgents.routeLifecycleToTarget(target, envelope);
	}
	/** Single native-RPC aperture for routed Lifecycle capabilities. */
	_cf_routeLifecycle(target, envelope) {
		return this._dynamicAgents.routeLifecycle(target, envelope);
	}
	_rootAlarmOwner() {
		return this._dynamicAgents.rootAlarmOwner();
	}
	/**
	* Clean root-owned bookkeeping for a sub-tree of facets. This
	* bulk-cancels schedules whose `owner_path` starts with the given
	* prefix and deletes root-side facet fiber recovery leases for the
	* same sub-tree. Used by `deleteSubAgent` and recursive facet
	* destroy. Emits `schedule:cancel` on this agent (the alarm-owning
	* root) for each schedule row removed — the facets being torn down
	* may not be alive to receive the events themselves.
	* @internal
	*/
	async _cf_cleanupFacetPrefix(ownerPath) {
		await this._dynamicAgents.cleanupPrefix(ownerPath);
	}
	/**
	* Acquire a root-owned keepAlive ref on behalf of a descendant facet.
	* Facets run in separate colocated isolates but cannot set their own
	* physical alarm, so this lets facet work use the root alarm heartbeat.
	* @internal
	*/
	_cf_acquireFacetKeepAlive(ownerPath) {
		return this._dynamicAgents.acquireKeepAlive(ownerPath);
	}
	/**
	* Release a root-owned keepAlive ref previously acquired for a facet.
	* Idempotent so disposer calls can safely race or run twice.
	* @internal
	*/
	_cf_releaseFacetKeepAlive(token) {
		return this._dynamicAgents.releaseKeepAlive(token);
	}
	/**
	* Register a facet's durable run row in the root-side index so root
	* alarm housekeeping can dispatch recovery checks into idle facets.
	* The facet remains authoritative for snapshots and recovery hooks.
	* @internal
	*/
	_cf_registerFacetRun(ownerPath, runId) {
		return this._dynamicAgents.registerRun(ownerPath, runId);
	}
	/**
	* Remove a completed facet fiber from the root-side index.
	* @internal
	*/
	_cf_unregisterFacetRun(ownerPath, runId) {
		return this._dynamicAgents.unregisterRun(ownerPath, runId);
	}
	/**
	* Schedule a task to be executed in the future
	*
	* Cron schedules are **idempotent by default** — calling `schedule("0 * * * *", "tick")`
	* multiple times with the same callback, cron expression, and payload returns
	* the existing schedule instead of creating a duplicate. Set `idempotent: false`
	* to override this.
	*
	* For delayed and scheduled (Date) types, set `idempotent: true` to opt in
	* to the same dedup behavior (matched on callback + payload). This is useful
	* when calling `schedule()` in `onStart()` to avoid accumulating duplicate
	* rows across Durable Object restarts.
	*
	* @template T Type of the payload data
	* @param when When to execute the task (Date, seconds delay, or cron expression)
	* @param callback Name of the method to call
	* @param payload Data to pass to the callback
	* @param options Options for the scheduled task
	* @param options.retry Retry options for the callback execution
	* @param options.idempotent Dedup by callback+payload. Defaults to `true` for cron, `false` otherwise.
	* @returns Schedule object representing the scheduled task
	*/
	schedule(when, callback, payload, options) {
		return this.scheduler.set(when, callback, payload, options);
	}
	/**
	* Schedule a task to run repeatedly at a fixed interval.
	*
	* This method is **idempotent** — calling it multiple times with the same
	* `callback`, `intervalSeconds`, and `payload` returns the existing schedule
	* instead of creating a duplicate. A different interval or payload is
	* treated as a distinct schedule and creates a new row.
	*
	* This makes it safe to call in `onStart()`, which runs on every Durable
	* Object wake:
	*
	* ```ts
	* async onStart() {
	*   // Only one schedule is created, no matter how many times the DO wakes
	*   await this.scheduleEvery(30, "tick");
	* }
	* ```
	*
	* @template T Type of the payload data
	* @param intervalSeconds Number of seconds between executions
	* @param callback Name of the method to call
	* @param payload Data to pass to the callback
	* @param options Options for the scheduled task
	* @param options.retry Retry options for the callback execution
	* @returns Schedule object representing the scheduled task
	*/
	scheduleEvery(intervalSeconds, callback, payload, options) {
		return this.scheduler.every(intervalSeconds, callback, payload, {
			retry: options?.retry,
			idempotent: options?._idempotent
		});
	}
	/**
	* Get a scheduled task by ID
	* @template T Type of the payload data
	* @param id ID of the scheduled task
	* @returns The Schedule object or undefined if not found
	* @deprecated Use {@link getScheduleById}. This synchronous API cannot cross
	* Durable Object boundaries and throws inside sub-agents.
	*/
	getSchedule(id) {
		return this.scheduler.__DO_NOT_USE_WILL_REMOVE__getSchedule(id);
	}
	/**
	* Get a scheduled task by ID.
	*
	* Unlike the deprecated synchronous {@link getSchedule}, this works inside
	* sub-agents by delegating to the top-level parent that owns the alarm.
	*
	* @param id ID of the scheduled task
	* @returns The Schedule object or undefined if not found
	*/
	getScheduleById(id) {
		return this.scheduler.get(id);
	}
	/**
	* Get scheduled tasks matching the given criteria
	* @template T Type of the payload data
	* @param criteria Criteria to filter schedules
	* @returns Array of matching Schedule objects
	* @deprecated Use {@link listSchedules}. This synchronous API cannot cross
	* Durable Object boundaries and throws inside sub-agents.
	*/
	getSchedules(criteria = {}) {
		return this.scheduler.__DO_NOT_USE_WILL_REMOVE__getSchedules(criteria);
	}
	/**
	* List scheduled tasks matching the given criteria.
	*
	* Unlike the deprecated synchronous {@link getSchedules}, this works inside
	* sub-agents by delegating to the top-level parent that owns the alarm.
	*
	* @param criteria Criteria to filter schedules
	* @returns Array of matching Schedule objects
	*/
	listSchedules(criteria = {}) {
		return this.scheduler.list(criteria);
	}
	/**
	* Cancel a scheduled task.
	*
	* Schedules are isolated by owner: a top-level agent's
	* `cancelSchedule(id)` only matches its own schedules, and a
	* sub-agent's `cancelSchedule(id)` only matches schedules it
	* created. To clear every schedule under a sub-agent (and its
	* descendants), call `parent.deleteSubAgent(Cls, name)` from the
	* parent — that bulk-cleans root-owned bookkeeping via
	* {@link _cf_cleanupFacetPrefix}.
	*
	* @param id ID of the task to cancel
	* @returns true if the task was cancelled, false if the task was not found
	*/
	cancelSchedule(id) {
		return this.scheduler.cancel(id);
	}
	/**
	* Keep the Durable Object alive via alarm heartbeats.
	* Returns a disposer function that stops the heartbeat when called.
	*
	* Use this when you have long-running work and need to prevent the
	* DO from going idle (eviction after ~70-140s of inactivity).
	* The heartbeat fires every `keepAliveIntervalMs` (default 30s) via the
	* alarm system, without creating schedule rows or emitting observability
	* events. Configure via `static options = { keepAliveIntervalMs: 5000 }`.
	*
	* In facets, delegates the physical heartbeat to the root parent
	* because facets do not have independent alarm slots.
	*
	* @example
	* ```ts
	* const dispose = await this.keepAlive();
	* try {
	*   // ... long-running work ...
	* } finally {
	*   dispose();
	* }
	* ```
	*/
	async keepAlive() {
		if (this._isFacet) {
			const root = await this._rootAlarmOwner();
			const token = await root._cf_acquireFacetKeepAlive(this.selfPath);
			let disposed = false;
			return () => {
				if (disposed) return;
				disposed = true;
				const release = root._cf_releaseFacetKeepAlive(token).catch((e) => {
					console.error("[Agent] Failed to release facet keepAlive:", e);
				});
				this.ctx.waitUntil(release);
			};
		}
		this._keepAliveRefs++;
		if (this._keepAliveRefs === 1) await this._syncHostJobs();
		let disposed = false;
		return () => {
			if (disposed) return;
			disposed = true;
			this._keepAliveRefs = Math.max(0, this._keepAliveRefs - 1);
			if (this._keepAliveRefs === 0) this.ctx.waitUntil(this._syncHostJobs().catch((e) => {
				console.error("[Agent] Failed to reschedule alarm after keepAlive dispose:", e);
			}));
		};
	}
	/**
	* Run an async function while keeping the Durable Object alive.
	* The heartbeat is automatically stopped when the function completes
	* (whether it succeeds or throws).
	*
	* This is the recommended way to use keepAlive — it guarantees cleanup
	* so you cannot forget to dispose the heartbeat.
	*
	* @example
	* ```ts
	* const result = await this.keepAliveWhile(async () => {
	*   const data = await longRunningComputation();
	*   return data;
	* });
	* ```
	*/
	async keepAliveWhile(fn) {
		const dispose = await this.keepAlive();
		try {
			return await fn();
		} finally {
			dispose();
		}
	}
	_isTerminalFiberStatus(status) {
		return status === "completed" || status === "aborted" || status === "interrupted" || status === "error";
	}
	_notifyManagedFiberTerminal(fiberId) {
		const row = this._readFiber(fiberId);
		if (row && !this._isTerminalFiberStatus(row.status)) return;
		const waiters = this._managedFiberTerminalWaiters.get(fiberId);
		if (!waiters) return;
		this._managedFiberTerminalWaiters.delete(fiberId);
		for (const resolve of waiters) resolve();
	}
	_waitForManagedFiberTerminal(fiberId) {
		const row = this._readFiber(fiberId);
		if (!row || this._isTerminalFiberStatus(row.status)) return Promise.resolve();
		return new Promise((resolve) => {
			let waiters = this._managedFiberTerminalWaiters.get(fiberId);
			if (!waiters) {
				waiters = /* @__PURE__ */ new Set();
				this._managedFiberTerminalWaiters.set(fiberId, waiters);
			}
			waiters.add(resolve);
		});
	}
	_normalizeFiberStatusFilter(status) {
		if (!status) return null;
		return new Set(Array.isArray(status) ? status : [status]);
	}
	_parseFiberJsonObject(value) {
		if (value === null) return null;
		try {
			const parsed = JSON.parse(value);
			if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
		} catch {}
		return null;
	}
	_parseFiberSnapshot(value) {
		if (value === null) return void 0;
		try {
			return JSON.parse(value);
		} catch {
			return;
		}
	}
	_fiberErrorMessage(error) {
		return error instanceof Error ? error.message : String(error);
	}
	_stringifyFiberSnapshot(snapshot) {
		return snapshot === void 0 ? null : JSON.stringify(snapshot);
	}
	_fiberRecoveryErrorMessage(result) {
		if (result.status === "error") return result.error === void 0 ? null : this._fiberErrorMessage(result.error);
		if (result.status === "aborted" || result.status === "interrupted") return result.reason ?? null;
		return null;
	}
	_applyManagedFiberRecoveryResult(fiberId, result) {
		const completedAt = Date.now();
		const snapshot = this._stringifyFiberSnapshot(result.snapshot);
		const errorMessage = this._fiberRecoveryErrorMessage(result);
		const metadata = result.status === "completed" && result.metadata !== void 0 ? JSON.stringify(result.metadata) : void 0;
		if (metadata !== void 0) {
			this.sql`
        UPDATE cf_agents_fibers
        SET status = ${result.status},
            snapshot = COALESCE(${snapshot}, snapshot),
            metadata_json = ${metadata},
            error_message = ${errorMessage},
            completed_at = ${completedAt}
        WHERE fiber_id = ${fiberId}
          AND status = 'interrupted'
      `;
			this._notifyManagedFiberTerminal(fiberId);
			return;
		}
		this.sql`
      UPDATE cf_agents_fibers
      SET status = ${result.status},
          snapshot = COALESCE(${snapshot}, snapshot),
          error_message = ${errorMessage},
          completed_at = ${completedAt}
      WHERE fiber_id = ${fiberId}
        AND status = 'interrupted'
    `;
		this._notifyManagedFiberTerminal(fiberId);
	}
	_settleManagedFiberExecution(fiberId, outcome, signal) {
		const completedAt = Date.now();
		if (outcome.ok) {
			this.sql`
        UPDATE cf_agents_fibers
        SET status = 'completed', completed_at = ${completedAt}
        WHERE fiber_id = ${fiberId} AND status = 'running'
      `;
			this._notifyManagedFiberTerminal(fiberId);
			return;
		}
		const message = this._fiberErrorMessage(outcome.error);
		const status = signal.aborted ? "aborted" : "error";
		this.sql`
      UPDATE cf_agents_fibers
      SET status = ${status},
          error_message = ${message},
          completed_at = ${completedAt}
      WHERE fiber_id = ${fiberId} AND status = 'running'
    `;
		this._notifyManagedFiberTerminal(fiberId);
	}
	_parseFiberRecoverySnapshot(fiberId, snapshotText) {
		if (!snapshotText) return null;
		try {
			return JSON.parse(snapshotText);
		} catch {
			console.warn(`[Agent] Corrupted snapshot for fiber ${fiberId}, treating as null`);
			return null;
		}
	}
	_fiberRecoveryPayload(ctx, managedRow, startedAt) {
		return {
			fiberId: ctx.id,
			fiberName: ctx.name,
			managed: managedRow !== null,
			recoveryReason: ctx.recoveryReason,
			elapsedMs: startedAt === void 0 ? void 0 : Date.now() - startedAt
		};
	}
	async _withFiberRecoveryTimeout(ctx, operation) {
		const timeoutMs = this._resolvedOptions.fiberRecoveryHookTimeoutMs;
		if (timeoutMs <= 0) return operation();
		let timer;
		try {
			return await Promise.race([operation(), new Promise((_, reject) => {
				timer = setTimeout(() => {
					reject(/* @__PURE__ */ new Error(`Fiber recovery hook timed out after ${timeoutMs}ms for "${ctx.name}" (${ctx.id})`));
				}, timeoutMs);
			})]);
		} finally {
			if (timer !== void 0) clearTimeout(timer);
		}
	}
	_recordFiberRecoveryFailure(ctx, managedRow, error, startedAt, reason = "handler_error") {
		const errorMessage = this._fiberErrorMessage(error);
		const completedAt = Date.now();
		if (managedRow) {
			this.sql`
        UPDATE cf_agents_fibers
        SET status = 'error',
            error_message = ${errorMessage},
            completed_at = ${completedAt}
        WHERE fiber_id = ${ctx.id}
          AND status = 'interrupted'
      `;
			this._notifyManagedFiberTerminal(ctx.id);
		}
		this._emit("fiber:recovery:failed", {
			...this._fiberRecoveryPayload(ctx, managedRow, startedAt),
			error: errorMessage,
			reason
		});
	}
	async _runFiberRecoveryHook(ctx, managedRow) {
		const startedAt = Date.now();
		this._emit("fiber:recovery:attempt", this._fiberRecoveryPayload(ctx, managedRow));
		try {
			const handled = await this._withFiberRecoveryTimeout(ctx, () => this._handleInternalFiberRecovery(ctx));
			if (!handled) {
				const recoveryResult = await this.onFiberRecovered(ctx);
				if (managedRow && recoveryResult) this._applyManagedFiberRecoveryResult(ctx.id, recoveryResult);
			}
			this._emit("fiber:recovery:handled", {
				...this._fiberRecoveryPayload(ctx, managedRow, startedAt),
				status: handled ? "internal" : managedRow ? "managed" : "user"
			});
			return true;
		} catch (e) {
			this._recordFiberRecoveryFailure(ctx, managedRow, e, startedAt);
			console.error(`[Agent] Fiber recovery failed for "${ctx.name}" (${ctx.id}):`, e);
			return false;
		}
	}
	_fiberInspectionFromRow(row) {
		const snapshot = this._parseFiberSnapshot(row.snapshot);
		const inspection = {
			fiberId: row.fiber_id,
			name: row.name,
			status: row.status,
			createdAt: row.created_at
		};
		if (row.idempotency_key !== null) inspection.idempotencyKey = row.idempotency_key;
		if (snapshot !== void 0) inspection.snapshot = snapshot;
		if (row.error_message !== null) inspection.error = row.error_message;
		const metadata = this._parseFiberJsonObject(row.metadata_json);
		if (metadata !== null) inspection.metadata = metadata;
		if (row.started_at !== null) inspection.startedAt = row.started_at;
		if (row.completed_at !== null) inspection.settledAt = row.completed_at;
		return inspection;
	}
	async _waitForManagedFiber(fiberId) {
		const row = this._readFiber(fiberId);
		if (!row || this._isTerminalFiberStatus(row.status)) return row ? this._fiberInspectionFromRow(row) : null;
		if (this._managedFiberExecutions.has(fiberId)) {
			await this._waitForManagedFiberTerminal(fiberId);
			return this.inspectFiber(fiberId);
		}
		await this._checkRunFibers();
		await this._waitForManagedFiberTerminal(fiberId);
		return this.inspectFiber(fiberId);
	}
	_readFiber(fiberId) {
		return this.sql`
      SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
             error_message, created_at, started_at, completed_at
      FROM cf_agents_fibers
      WHERE fiber_id = ${fiberId}
      LIMIT 1
    `[0] ?? null;
	}
	_readFiberByKey(idempotencyKey) {
		return this.sql`
      SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
             error_message, created_at, started_at, completed_at
      FROM cf_agents_fibers
      WHERE idempotency_key = ${idempotencyKey}
      LIMIT 1
    `[0] ?? null;
	}
	_listFiberRows(options) {
		const limit = Math.min(Math.max(options?.limit ?? 50, 1), 100);
		const statuses = this._normalizeFiberStatusFilter(options?.status);
		if (statuses) return [...statuses].flatMap((status) => this._listFiberRowsByStatus(status, limit, options?.name)).sort((a, b) => b.created_at === a.created_at ? b.fiber_id.localeCompare(a.fiber_id) : b.created_at - a.created_at).slice(0, limit);
		if (options?.name) return this.sql`
        SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
               error_message, created_at, started_at, completed_at
        FROM cf_agents_fibers
        WHERE name = ${options.name}
        ORDER BY created_at DESC, fiber_id DESC
        LIMIT ${limit}
      `;
		return this.sql`
      SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
             error_message, created_at, started_at, completed_at
      FROM cf_agents_fibers
      ORDER BY created_at DESC, fiber_id DESC
      LIMIT ${limit}
    `;
	}
	_listFiberRowsByStatus(status, limit, name) {
		if (name) return this.sql`
        SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
               error_message, created_at, started_at, completed_at
        FROM cf_agents_fibers
        WHERE status = ${status} AND name = ${name}
        ORDER BY created_at DESC, fiber_id DESC
        LIMIT ${limit}
      `;
		return this.sql`
      SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
             error_message, created_at, started_at, completed_at
      FROM cf_agents_fibers
      WHERE status = ${status}
      ORDER BY created_at DESC, fiber_id DESC
      LIMIT ${limit}
    `;
	}
	async inspectFiber(fiberId) {
		const row = this._readFiber(fiberId);
		return row ? this._fiberInspectionFromRow(row) : null;
	}
	async inspectFiberByKey(idempotencyKey) {
		const row = this._readFiberByKey(idempotencyKey);
		return row ? this._fiberInspectionFromRow(row) : null;
	}
	async listFibers(options) {
		return this._listFiberRows(options).map((row) => this._fiberInspectionFromRow(row));
	}
	async cancelFiber(fiberId, reason) {
		const row = this._readFiber(fiberId);
		if (!row || this._isTerminalFiberStatus(row.status)) return false;
		const now = Date.now();
		this.sql`
      UPDATE cf_agents_fibers
      SET status = 'aborted',
          error_message = ${reason ?? null},
          completed_at = ${now}
      WHERE fiber_id = ${fiberId}
        AND status IN ('pending', 'running')
    `;
		this._managedFiberAbortControllers.get(fiberId)?.abort(reason);
		this._notifyManagedFiberTerminal(fiberId);
		return true;
	}
	async cancelFiberByKey(idempotencyKey, reason) {
		const row = this._readFiberByKey(idempotencyKey);
		return row ? this.cancelFiber(row.fiber_id, reason) : false;
	}
	async resolveFiber(fiberId, result) {
		const row = this._readFiber(fiberId);
		if (!row || row.status !== "interrupted") return false;
		this._applyManagedFiberRecoveryResult(fiberId, result);
		return true;
	}
	async deleteFibers(options) {
		const terminalStatuses = [...this._normalizeFiberStatusFilter(options?.status) ?? /* @__PURE__ */ new Set([
			"completed",
			"aborted",
			"error"
		])].filter((status) => this._isTerminalFiberStatus(status));
		if (terminalStatuses.length === 0) return 0;
		const limit = Math.min(Math.max(options?.limit ?? 100, 1), 500);
		const settledBefore = options?.settledBefore?.getTime();
		const rows = terminalStatuses.flatMap((status) => this._listTerminalFiberRowsForDelete(status, limit, settledBefore)).sort((a, b) => a.completed_at === b.completed_at ? a.created_at - b.created_at : (a.completed_at ?? 0) - (b.completed_at ?? 0)).slice(0, limit);
		for (const row of rows) this.sql`
        DELETE FROM cf_agents_fibers
        WHERE fiber_id = ${row.fiber_id}
          AND status IN ('completed', 'aborted', 'interrupted', 'error')
      `;
		return rows.length;
	}
	_listTerminalFiberRowsForDelete(status, limit, settledBefore) {
		if (settledBefore !== void 0) return this.sql`
        SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
               error_message, created_at, started_at, completed_at
        FROM cf_agents_fibers
        WHERE status = ${status}
          AND completed_at IS NOT NULL
          AND completed_at < ${settledBefore}
        ORDER BY completed_at ASC, created_at ASC
        LIMIT ${limit}
      `;
		return this.sql`
      SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
             error_message, created_at, started_at, completed_at
      FROM cf_agents_fibers
      WHERE status = ${status}
      ORDER BY completed_at ASC, created_at ASC
      LIMIT ${limit}
    `;
	}
	/**
	* Run a function as a durable fiber. The fiber is registered in SQLite
	* before execution, checkpointable during execution via `ctx.stash()`,
	* and recoverable after eviction via `onFiberRecovered`.
	*
	* - Row created in `cf_agents_runs` at start, deleted on completion
	* - `keepAlive()` held for the duration — prevents idle eviction
	* - Inline (await result) or fire-and-forget (`void this.runFiber(...)`)
	*
	* @param name Informational name for debugging and recovery filtering
	* @param fn Async function to execute. Receives a FiberContext with stash/snapshot.
	* @returns The return value of fn
	*/
	async runFiber(name, fn) {
		return this._runFiberInternal(nanoid(), name, fn);
	}
	/**
	* Internal framework entry point for fibers that need to compose their own
	* recovery metadata with user checkpoint data while preserving the public
	* `this.stash()` behavior.
	*
	* This deliberately stays protected/internal rather than becoming a public
	* `runFiber()` option until the durable execution API needs this generality.
	* @internal
	*/
	async _runFiberWithStashWrapper(name, fn, options) {
		return this._runFiberInternal(nanoid(), name, fn, options);
	}
	async startFiber(name, fn, options) {
		const fiberId = options?.fiberId ?? nanoid();
		const idempotencyKey = options?.idempotencyKey;
		if (options?.fiberId !== void 0 && options.fiberId.trim() === "") throw new Error("fiberId must not be blank");
		if (options?.idempotencyKey !== void 0 && options.idempotencyKey.trim() === "") throw new Error("idempotencyKey must not be blank");
		const existingById = this._readFiber(fiberId);
		const existingByKey = idempotencyKey ? this._readFiberByKey(idempotencyKey) : null;
		if (existingById && existingByKey && existingById.fiber_id !== existingByKey.fiber_id) throw new Error("fiberId and idempotencyKey refer to different fibers");
		if (existingByKey && options?.fiberId && existingByKey.fiber_id !== fiberId) throw new Error("fiberId and idempotencyKey refer to different fibers");
		const existing = existingById ?? existingByKey;
		if (existing) {
			if (options?.waitForCompletion && !this._isTerminalFiberStatus(existing.status)) {
				const waited = await this._waitForManagedFiber(existing.fiber_id);
				if (waited) return {
					...waited,
					accepted: false
				};
				throw new Error(`Fiber ${existing.fiber_id} no longer exists`);
			}
			return {
				...this._fiberInspectionFromRow(existing),
				accepted: false
			};
		}
		const now = Date.now();
		this.sql`
      INSERT INTO cf_agents_fibers
        (fiber_id, idempotency_key, name, status, snapshot, metadata_json,
         error_message, created_at, started_at, completed_at)
      VALUES
        (${fiberId}, ${idempotencyKey ?? null}, ${name}, 'pending', NULL,
         ${options?.metadata ? JSON.stringify(options.metadata) : null}, NULL,
         ${now}, NULL, NULL)
    `;
		const row = this._readFiber(fiberId);
		if (!row) throw new Error(`Failed to create fiber ${fiberId}`);
		const execution = this._executeManagedFiber(fiberId, name, fn).catch((error) => {
			console.error(`[Agent] Managed fiber "${name}" (${fiberId}) failed:`, error);
		}).finally(() => {
			if (this._managedFiberExecutions.get(fiberId) === execution) this._managedFiberExecutions.delete(fiberId);
		});
		this._managedFiberExecutions.set(fiberId, execution);
		if (options?.waitForCompletion) {
			const completed = await this._waitForManagedFiber(fiberId);
			if (!completed) throw new Error(`Fiber ${fiberId} no longer exists`);
			return {
				...completed,
				accepted: true
			};
		}
		return {
			...this._fiberInspectionFromRow(row),
			accepted: true
		};
	}
	async _executeManagedFiber(fiberId, name, fn) {
		const row = this._readFiber(fiberId);
		if (!row || row.status !== "pending") return;
		const controller = new AbortController();
		this._managedFiberAbortControllers.set(fiberId, controller);
		const now = Date.now();
		this.sql`
      UPDATE cf_agents_fibers
      SET status = 'running', started_at = ${now}
      WHERE fiber_id = ${fiberId} AND status = 'pending'
    `;
		const updated = this._readFiber(fiberId);
		if (!updated || updated.status !== "running") {
			this._managedFiberAbortControllers.delete(fiberId);
			return;
		}
		let settled = false;
		try {
			await this._runFiberInternal(fiberId, name, fn, {
				signal: controller.signal,
				managed: true,
				beforeRunCleanup: (outcome) => {
					settled = true;
					this._settleManagedFiberExecution(fiberId, outcome, controller.signal);
				}
			});
		} catch (error) {
			if (!settled) this._settleManagedFiberExecution(fiberId, {
				ok: false,
				error
			}, controller.signal);
		} finally {
			this._managedFiberAbortControllers.delete(fiberId);
		}
	}
	async _runFiberInternal(id, name, fn, options) {
		const signal = options?.signal ?? new AbortController().signal;
		this._withAgentSpan("initialize_fiber", "fiber", {
			"cloudflare.agents.fiber.id": id,
			"cloudflare.agents.fiber.name": name
		}, () => {
			this.sql`
          INSERT INTO cf_agents_runs (id, name, snapshot, created_at)
          VALUES (${id}, ${name}, NULL, ${Date.now()})
        `;
		});
		const startedAt = Date.now();
		this._emit("fiber:run:started", {
			fiberId: id,
			fiberName: name,
			managed: options?.managed === true
		});
		this._runFiberActiveFibers.add(id);
		const writeSnapshot = (data) => {
			const snapshot = JSON.stringify(data);
			this._withAgentSpan("persist_fiber_snapshot", "fiber", {
				"cloudflare.agents.fiber.id": id,
				"cloudflare.agents.fiber.name": name
			}, () => {
				this.sql`
            UPDATE cf_agents_runs SET snapshot = ${snapshot}
            WHERE id = ${id}
          `;
				if (options?.managed) this.sql`
              UPDATE cf_agents_fibers SET snapshot = ${snapshot}
              WHERE fiber_id = ${id}
            `;
			});
		};
		let root;
		let registeredFacetRun = false;
		let dispose = () => {};
		try {
			if ("initialSnapshot" in (options ?? {})) writeSnapshot(options?.initialSnapshot);
			if (this._isFacet) {
				root = await this._rootAlarmOwner();
				await root._cf_registerFacetRun(this.selfPath, id);
				registeredFacetRun = true;
			}
			dispose = await this.keepAlive();
			const stash = (data) => {
				writeSnapshot(options?.wrapStash ? options.wrapStash(data) : data);
			};
			try {
				const result = await _fiberALS.run({
					id,
					signal,
					stash
				}, () => fn({
					id,
					signal,
					stash,
					snapshot: null
				}));
				options?.beforeRunCleanup?.({ ok: true });
				this._emit("fiber:run:completed", {
					fiberId: id,
					fiberName: name,
					managed: options?.managed === true,
					elapsedMs: Date.now() - startedAt
				});
				return result;
			} catch (error) {
				options?.beforeRunCleanup?.({
					ok: false,
					error
				});
				this._emit("fiber:run:failed", {
					fiberId: id,
					fiberName: name,
					managed: options?.managed === true,
					error: this._fiberErrorMessage(error),
					elapsedMs: Date.now() - startedAt
				});
				throw error;
			}
		} finally {
			this._runFiberActiveFibers.delete(id);
			try {
				this._withAgentSpan("finalize_fiber", "fiber", {
					"cloudflare.agents.fiber.id": id,
					"cloudflare.agents.fiber.name": name
				}, () => {
					this.sql`DELETE FROM cf_agents_runs WHERE id = ${id}`;
				});
			} catch (error) {
				console.error(`[Agent] Failed to finalize fiber "${name}" (${id}); leaving run row for recovery:`, error);
			}
			dispose();
			if (root && registeredFacetRun) try {
				await root._cf_unregisterFacetRun(this.selfPath, id);
			} catch (e) {
				console.error("[Agent] Failed to unregister facet fiber:", e);
			}
		}
	}
	/**
	* Checkpoint data for the currently executing fiber.
	* Uses AsyncLocalStorage to identify the correct fiber,
	* so it works correctly even with concurrent fibers.
	*
	* Throws if called outside a `runFiber` callback.
	*/
	stash(data) {
		const ctx = _fiberALS.getStore();
		if (!ctx) throw new Error("stash() called outside a fiber");
		ctx.stash(data);
	}
	/**
	* Run `fn` inside the fiber stash context so `this.stash()` keeps working
	* for turns executing on the `tasks` capability exactly as it does inside
	* legacy `runFiber()` closures.
	* @internal
	*/
	_withFiberStash(context, fn) {
		return _fiberALS.run(context, fn);
	}
	/**
	* Called when an interrupted fiber is detected after restart.
	* Override to implement recovery (re-invoke work, notify clients, etc.).
	*
	* Internal framework fibers are filtered by `_handleInternalFiberRecovery`
	* before this hook runs — users only see their own fibers.
	*
	* Default: logs a warning.
	*/
	async onFiberRecovered(_ctx) {
		console.warn(`[Agent] Fiber "${_ctx.name}" (${_ctx.id}) was interrupted. Override onFiberRecovered to handle recovery.`);
	}
	/**
	* Override point for subclasses to handle internal (framework) fibers
	* before the user's recovery hook fires. Return `true` if handled.
	* @internal
	*/
	async _handleInternalFiberRecovery(_ctx) {
		return false;
	}
	/** @internal Detect fibers left by a dead process (runFiber system). */
	async _checkRunFibers() {
		if (this._runFiberRecoveryInProgress) return;
		this._runFiberRecoveryInProgress = true;
		const scanStartedAt = Date.now();
		const scanDeadlineMs = this._resolvedOptions.fiberRecoveryScanDeadlineMs;
		const fiberRecoveryMaxAgeMs = this._resolvedOptions.fiberRecoveryMaxAgeMs;
		let madeProgress = false;
		try {
			const rows = this.sql`SELECT id, name, snapshot, created_at FROM cf_agents_runs`;
			for (const row of rows) {
				if (scanDeadlineMs > 0 && Date.now() - scanStartedAt > scanDeadlineMs) {
					this._emit("fiber:recovery:skipped", {
						fiberId: row.id,
						fiberName: row.name,
						reason: "scan_deadline_exceeded",
						elapsedMs: Date.now() - scanStartedAt
					});
					break;
				}
				if (this._runFiberActiveFibers.has(row.id)) continue;
				const snapshot = this._parseFiberRecoverySnapshot(row.id, row.snapshot);
				const ctx = {
					id: row.id,
					name: row.name,
					snapshot,
					createdAt: row.created_at,
					recoveryReason: "interrupted"
				};
				const managedRow = this._readFiber(row.id);
				this._emit("fiber:recovery:detected", {
					...this._fiberRecoveryPayload(ctx, managedRow),
					elapsedMs: Date.now() - row.created_at
				});
				this._emit("fiber:run:interrupted", {
					fiberId: row.id,
					fiberName: row.name,
					managed: managedRow !== null,
					recoveryReason: "interrupted",
					elapsedMs: Date.now() - row.created_at
				});
				if (managedRow) {
					if (this._isTerminalFiberStatus(managedRow.status)) {
						this.sql`DELETE FROM cf_agents_runs WHERE id = ${row.id}`;
						madeProgress = true;
						this._notifyManagedFiberTerminal(row.id);
						continue;
					}
					const completedAt = Date.now();
					this.sql`
            UPDATE cf_agents_fibers
            SET status = 'interrupted',
                snapshot = ${row.snapshot},
                completed_at = ${completedAt}
            WHERE fiber_id = ${row.id}
              AND status IN ('pending', 'running')
          `;
					ctx.idempotencyKey = managedRow.idempotency_key ?? void 0;
					ctx.metadata = this._parseFiberJsonObject(managedRow.metadata_json);
					ctx.status = "interrupted";
				}
				const recovered = await this._runFiberRecoveryHook(ctx, managedRow);
				const tooOld = fiberRecoveryMaxAgeMs > 0 && Date.now() - row.created_at > fiberRecoveryMaxAgeMs;
				if (recovered || managedRow || tooOld) {
					if (!recovered && !managedRow && tooOld) this._emit("fiber:recovery:skipped", {
						fiberId: row.id,
						fiberName: row.name,
						reason: "max_age_exceeded",
						elapsedMs: Date.now() - row.created_at
					});
					this.sql`DELETE FROM cf_agents_runs WHERE id = ${row.id}`;
					madeProgress = true;
				}
				if (managedRow) this._notifyManagedFiberTerminal(row.id);
			}
			const ledgerOnlyRows = this.sql`
        SELECT f.fiber_id, f.idempotency_key, f.name, f.status, f.snapshot,
               f.metadata_json, f.error_message, f.created_at, f.started_at,
               f.completed_at
        FROM cf_agents_fibers f
        LEFT JOIN cf_agents_runs r ON r.id = f.fiber_id
        WHERE f.status IN ('pending', 'running')
          AND r.id IS NULL
      `;
			for (const row of ledgerOnlyRows) {
				if (scanDeadlineMs > 0 && Date.now() - scanStartedAt > scanDeadlineMs) {
					this._emit("fiber:recovery:skipped", {
						fiberId: row.fiber_id,
						fiberName: row.name,
						reason: "scan_deadline_exceeded",
						elapsedMs: Date.now() - scanStartedAt,
						managed: true
					});
					break;
				}
				if (this._runFiberActiveFibers.has(row.fiber_id)) continue;
				const snapshot = this._parseFiberRecoverySnapshot(row.fiber_id, row.snapshot);
				const completedAt = Date.now();
				this.sql`
          UPDATE cf_agents_fibers
          SET status = 'interrupted',
              completed_at = ${completedAt}
          WHERE fiber_id = ${row.fiber_id}
            AND status IN ('pending', 'running')
        `;
				const ctx = {
					id: row.fiber_id,
					name: row.name,
					snapshot,
					createdAt: row.created_at,
					idempotencyKey: row.idempotency_key ?? void 0,
					metadata: this._parseFiberJsonObject(row.metadata_json),
					status: "interrupted",
					recoveryReason: "interrupted"
				};
				this._emit("fiber:recovery:detected", {
					...this._fiberRecoveryPayload(ctx, row),
					elapsedMs: Date.now() - row.created_at
				});
				this._emit("fiber:run:interrupted", {
					fiberId: row.fiber_id,
					fiberName: row.name,
					managed: true,
					recoveryReason: "interrupted",
					elapsedMs: Date.now() - row.created_at
				});
				await this._runFiberRecoveryHook(ctx, row);
				madeProgress = true;
				this._notifyManagedFiberTerminal(row.fiber_id);
			}
		} finally {
			this._runFiberRecoveryInProgress = false;
			if (madeProgress) this._recoveryNoProgressScans = 0;
			else this._recoveryNoProgressScans = this._hasPendingFiberRecovery() ? this._recoveryNoProgressScans + 1 : 0;
		}
	}
	/** @internal */
	async _onAlarmHousekeeping() {
		await this._checkRunFibers();
		await this._checkFacetRunFibers();
	}
	_isSameAgentPathPrefix(prefix, path) {
		if (prefix.length > path.length) return false;
		return prefix.every((step, index) => step.className === path[index].className && step.name === path[index].name);
	}
	/**
	* Root-side scan for durable fibers owned by descendant facets.
	* `cf_agents_facet_runs` is only an index; actual snapshots and
	* recovery hooks live in each facet's own `cf_agents_runs` table.
	* @internal
	*/
	_checkFacetRunFibers() {
		return this._dynamicAgents.checkRunFibers();
	}
	/**
	* Dispatch a runFiber recovery check into the facet identified by
	* `ownerPath`. Returns the number of remaining local `cf_agents_runs`
	* rows on the target facet after recovery.
	* @internal
	*/
	_cf_checkRunFibersForFacet(ownerPath) {
		return this._dynamicAgents.checkRunFibersAtPath(ownerPath);
	}
	/**
	* Invoke an RPC method on this Agent or a descendant facet identified
	* by a root-first path. Used by AgentWorkflow to route callbacks and
	* `this.agent` calls back to the exact sub-agent that started a workflow.
	* @internal
	*/
	_cf_invokeAgentPath(targetPath, method, args) {
		return this._dynamicAgents.invokeAgentPath(targetPath, method, args);
	}
	/**
	* Recursively destroy a descendant facet identified by
	* `targetPath`. Walks down from `selfPath` until reaching the
	* target's immediate parent, where it cancels the target's
	* parent-owned schedules (and any descendants), removes the
	* target from the registry, and calls `ctx.facets.delete` to
	* wipe the target's storage.
	*
	* Called by a facet's own `destroy()` (via the root) so that
	* `this.destroy()` inside a sub-agent results in the same
	* cleanup as `parent.deleteSubAgent(Cls, name)` from the parent.
	* @internal
	*/
	_cf_destroyDescendantFacet(targetPath) {
		return this._dynamicAgents.destroyDescendant(targetPath);
	}
	/**
	* Whether any runFiber recovery work is still outstanding: orphaned
	* `cf_agents_runs` rows left by a dead process (excluding fibers currently
	* executing in memory, which already hold a keepAlive ref) or managed
	* ledger fibers stuck in a non-terminal state with no live run row.
	*
	* Used by `_syncHostJobs` to arm a follow-up alarm so multi-pass
	* recovery (e.g. after a scan-deadline yield, or while retrying a throwing
	* recovery hook) resumes instead of starving.
	* @internal
	*/
	_hasPendingFiberRecovery() {
		const runRows = this.sql`
      SELECT id FROM cf_agents_runs
    `;
		for (const row of runRows) if (!this._runFiberActiveFibers.has(row.id)) return true;
		return (this.sql`
      SELECT COUNT(*) AS count
      FROM cf_agents_fibers f
      LEFT JOIN cf_agents_runs r ON r.id = f.fiber_id
      WHERE f.status IN ('pending', 'running')
        AND r.id IS NULL
    `[0]?.count ?? 0) > 0;
	}
	/**
	* Synchronize Agent-owned host jobs with current durable state.
	*
	* Replaces the old pull-based `getNextAlarm()` contribution: keep-alive
	* refs hold a `cf:keep-alive` job, and fiber-recovery / facet-run state
	* holds a `cf:housekeeping` job. Every state change that used to trigger
	* an alarm recalculation now re-pushes or cancels these jobs; queue
	* mutations re-arm the physical alarm automatically.
	* @internal
	*/
	async _syncHostJobs() {
		if (this._destroyed) return;
		await this._withAgentSpan("schedule_agent_alarm", "alarm", {}, async () => {
			const work = this.lifecycle.jobs;
			const nowMs = Date.now();
			const pendingDestroy = await this._pendingDestroyAlarm();
			if (pendingDestroy !== null) {
				await work.push({
					id: HOST_JOB_DESTROY_ID,
					fn: "destroy",
					time: pendingDestroy,
					exclusive: true
				});
				return;
			}
			if (work.get(HOST_JOB_DESTROY_ID)) await work.cancel(HOST_JOB_DESTROY_ID);
			if (this._keepAliveRefs > 0) await work.push({
				id: HOST_JOB_KEEP_ALIVE_ID,
				fn: "keepAlive",
				time: nowMs + this._resolvedOptions.keepAliveIntervalMs
			});
			else if (work.get(HOST_JOB_KEEP_ALIVE_ID)) await work.cancel(HOST_JOB_KEEP_ALIVE_ID);
			const housekeepingAt = this._nextHousekeepingWakeMs(nowMs);
			if (housekeepingAt !== null) await work.push({
				id: HOST_JOB_HOUSEKEEPING_ID,
				fn: "housekeeping",
				time: housekeepingAt
			});
			else if (work.get(HOST_JOB_HOUSEKEEPING_ID)) await work.cancel(HOST_JOB_HOUSEKEEPING_ID);
		});
	}
	/**
	* The next wake fiber-recovery or facet-run housekeeping needs, or `null`
	* when neither has pending durable state.
	*/
	_nextHousekeepingWakeMs(nowMs) {
		let nextTimeMs = null;
		if (this._hasPendingFiberRecovery()) {
			const base = this._resolvedOptions.keepAliveIntervalMs;
			const exp = Math.min(this._recoveryNoProgressScans, FIBER_RECOVERY_BACKOFF_MAX_EXP);
			nextTimeMs = nowMs + Math.min(FIBER_RECOVERY_MAX_BACKOFF_MS, base * 2 ** exp);
		}
		if ((this.sql`
      SELECT COUNT(*) as count FROM cf_agents_facet_runs
    `[0]?.count ?? 0) > 0) {
			const facetRecoveryMs = nowMs + this._resolvedOptions.keepAliveIntervalMs;
			nextTimeMs = nextTimeMs === null ? facetRecoveryMs : Math.min(nextTimeMs, facetRecoveryMs);
		}
		return nextTimeMs;
	}
	/** Lifecycle alarm callback; Agent housekeeping runs after user alarm work. */
	onAlarm() {}
	/**
	* Drive one Agent-owned host job from the Lifecycle queue.
	* @internal Dispatched by Lifecycle's alarm event loop; extensions add
	* job fns through {@link _onHostJob}.
	*/
	onJob(context) {
		return this._onHostJob(context.job.fn, context);
	}
	/**
	* @internal Dispatch one host job fn. Agent extensions (Think) override
	* this to add fns and delegate unknown ones to `super`.
	*/
	_onHostJob(fn, _context) {
		switch (fn) {
			case "keepAlive": return this._keepAliveRefs > 0 ? { rescheduleAt: Date.now() + this._resolvedOptions.keepAliveIntervalMs } : void 0;
			case "housekeeping": {
				const next = this._nextHousekeepingWakeMs(Date.now());
				return next === null ? void 0 : { rescheduleAt: next };
			}
			case "destroy": return;
			default:
				console.warn(`Unknown Agent host job fn ${JSON.stringify(fn)}`);
				return;
		}
	}
	/**
	* Apply host policy after the alarm memory-limit breaker records a strike.
	*
	* New chat hosts override this hook directly. The sealed-only fallback keeps
	* `agents` 0.23 compatible with already-published chat packages whose peer
	* ranges accept it but which implement only the former
	* `_cf_sealMemoryLimitedRecovery` template method. Queue membership remains
	* job-row policy; this invokes terminalization only and can be removed once
	* old chat releases no longer accept the current `agents` range.
	*
	* @internal
	*/
	async onAlarmMemoryLimit(context) {
		if (!context.sealed) return;
		await this._cf_sealMemoryLimitedRecovery?.call(this);
	}
	/**
	* Run Lifecycle's alarm event loop after the pending-destroy preamble.
	*
	* The alarm memory-limit circuit breaker (#1825) lives inside
	* `Lifecycle.alarm()`; capabilities and hosts opt into extra domain
	* policy via their `onMemoryLimit` / `onAlarmMemoryLimit` hooks and the
	* `recoveryLoop` schedule option.
	*
	* @remarks Use `this.schedule()` for named Agent callbacks. Reusable durable
	* work belongs in a capability that pushes jobs and implements `onJob()`.
	*/
	async alarm() {
		if (await this._hasPendingDestroy()) {
			await this.destroy();
			return;
		}
		await this.lifecycle.alarm();
	}
	/**
	* Intercept incoming HTTP/WS requests whose URL contains a
	* `/sub/{child-class}/{child-name}` marker and forward them to
	* the facet. The `onBeforeSubAgent` hook fires first (authorize,
	* mutate, or short-circuit). If the hook doesn't return a
	* Response, the framework resolves the facet and hands the
	* request off.
	*
	* The parent owns an upgraded WebSocket for its lifetime. Subsequent
	* frames wake the root parent, which forwards them to the child over
	* RPC and routes replies back to the native socket.
	*
	* @experimental The API surface may change before stabilizing.
	*/
	async fetch(request) {
		const ctx = this.ctx;
		const match = parseSubAgentPath(request.url, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 });
		if (!match) return this.lifecycle.fetch(request);
		const decision = await this.onBeforeSubAgent(request, {
			className: match.childClass,
			name: match.childName
		});
		if (decision instanceof Response) return decision;
		const forwardReq = decision instanceof Request ? decision : request;
		if (request.headers.get("Upgrade")?.toLowerCase() === "websocket") {
			const acceptHeaders = new Headers(forwardReq.headers);
			const routedUrl = new URL(forwardReq.url);
			routedUrl.pathname = new URL(request.url).pathname;
			acceptHeaders.set(SUB_AGENT_OUTER_URL_HEADER, routedUrl.toString());
			return this.lifecycle.fetch(new Request(forwardReq, { headers: acceptHeaders }));
		}
		return this._cf_forwardToFacet(forwardReq, match);
	}
	broadcast(msg, without) {
		if (this._isFacet) {
			this._dynamicAgents.broadcastToParent(msg, without);
			return;
		}
		for (const connection of this._webSockets.getConnections()) {
			if (without?.includes(connection.id)) continue;
			if (this._dynamicAgents.connectionHasChildTarget(connection)) continue;
			connection.send(msg);
		}
	}
	getConnection(id) {
		if (this._isFacet) return this._dynamicAgents.getVirtualConnection(id);
		const connection = this._webSockets.getConnection(id);
		if (!connection || this._dynamicAgents.connectionHasChildTarget(connection)) return;
		return connection;
	}
	*getConnections(tag) {
		if (this._isFacet) {
			yield* this._dynamicAgents.getVirtualConnections(tag);
			return;
		}
		for (const connection of this._webSockets.getConnections(tag)) {
			if (this._dynamicAgents.connectionHasChildTarget(connection)) continue;
			yield connection;
		}
	}
	async _cf_broadcastToSubAgent(ownerPath, message, without) {
		await this._dynamicAgents.broadcastToPath(ownerPath, message, without);
	}
	_cf_subAgentConnectionMetas(ownerPath) {
		return this._dynamicAgents.connectionMetas(ownerPath);
	}
	_cf_sendToSubAgentConnection(connectionId, message) {
		return this._dynamicAgents.sendToConnection(connectionId, message);
	}
	_cf_closeSubAgentConnection(connectionId, code, reason) {
		return this._dynamicAgents.closeConnection(connectionId, code, reason);
	}
	_cf_setSubAgentConnectionState(connectionId, state) {
		return this._dynamicAgents.setConnectionState(connectionId, state);
	}
	_cf_connectionTargetsSubAgent(connection) {
		return this._dynamicAgents.connectionTargetsChild(connection);
	}
	/**
	* Returns true when the current request is addressed to a child facet of
	* this agent rather than to this agent itself.
	*
	* Chat-style subclasses wrap `onConnect` before the base Agent forwarding
	* wrapper runs, so they need a request-level check to avoid sending their
	* own protocol frames on sockets that are about to be forwarded to a child.
	*/
	_cf_requestTargetsSubAgent(request) {
		return this._dynamicAgents.requestTargetsChild(request);
	}
	_cf_forwardSubAgentWebSocketConnect(connection, request, options) {
		return this._dynamicAgents.forwardWebSocketConnect(connection, request, options);
	}
	_cf_forwardSubAgentWebSocketMessage(connection, message, replyBridge) {
		return this._dynamicAgents.forwardWebSocketMessage(connection, message, replyBridge);
	}
	_cf_forwardSubAgentWebSocketClose(connection, code, reason, wasClean) {
		return this._dynamicAgents.forwardWebSocketClose(connection, code, reason, wasClean);
	}
	_cf_handleSubAgentWebSocketConnect(bridge, meta) {
		return this._dynamicAgents.handleWebSocketConnect(bridge, meta);
	}
	_cf_handleSubAgentWebSocketMessage(message, bridge, meta, replyBridge = bridge) {
		return this._dynamicAgents.handleWebSocketMessage(message, bridge, meta, replyBridge);
	}
	_cf_handleSubAgentWebSocketClose(code, reason, wasClean, bridge, meta) {
		return this._dynamicAgents.handleWebSocketClose(code, reason, wasClean, bridge, meta);
	}
	_cf_hydrateSubAgentConnectionsFromRoot() {
		return this._dynamicAgents.hydrateConnectionsFromRoot();
	}
	/**
	* Parent-side middleware hook. Fires before a request is
	* forwarded into a facet sub-agent. Mirrors `onBeforeConnect` /
	* `onBeforeRequest`.
	*
	*   - return `void` (default) → forward the original request
	*   - return `Request`        → forward this (modified) request
	*   - return `Response`       → return this response to the
	*                               client; do not wake the child
	*
	* Default implementation: return void (permissive).
	*
	* The hook receives the **original** request with its URL intact —
	* including the `/sub/{class}/{name}` segment. The routing
	* decision for which facet to wake is fixed at parse time, so if
	* you return a modified `Request`, its headers, body, method, and
	* query string flow through to the child, but the **pathname**
	* the child sees is always the tail after `/sub/{class}/{name}`.
	* Customize via headers/body rather than URL-rewriting.
	*
	* WebSocket upgrade requests flow through this hook the same way as
	* plain HTTP. If you return a mutated `Request`, make sure it still
	* carries the original `Upgrade: websocket` and `Sec-WebSocket-*`
	* headers — the simplest safe recipe is to clone the incoming
	* request's headers (via `new Headers(req.headers)`) and only add
	* or replace entries, rather than constructing a fresh `Headers`
	* object from scratch.
	*
	* @experimental The API surface may change before stabilizing.
	*
	* @example
	* ```ts
	* class Inbox extends Agent {
	*   override async onBeforeSubAgent(req, { className, name }) {
	*     // Strict registry gate
	*     if (!this.dynamicAgents.has(className, name)) {
	*       return new Response("Not found", { status: 404 });
	*     }
	*   }
	* }
	* ```
	*/
	async onBeforeSubAgent(_request, _child) {}
	/**
	* Resolve the facet Fetcher for the match and forward the
	* request to it with `/sub/{class}/{name}` stripped.
	*
	* @internal
	*/
	_cf_forwardToFacet(req, match) {
		return this._dynamicAgents.forward(req, match);
	}
	/**
	* Bridge method used by `getSubAgentByName`. Resolves the facet
	* on each call (idempotent via `subAgent`) and dispatches one
	* RPC method. Stateless — no cached references.
	*
	* @internal
	*/
	_cf_invokeSubAgent(className, name, method, args) {
		return this._dynamicAgents.invoke(className, name, method, args);
	}
	/**
	* Bridge method used by `parentAgent()` when the requested parent is
	* itself a facet (and therefore has no top-level env namespace).
	* The root receives the full root-first target path, then each hop
	* delegates to the next facet using that facet's own `ctx.facets`.
	*
	* @internal
	*/
	_cf_invokeSubAgentPath(path, method, args) {
		return this._dynamicAgents.invokePath(path, method, args);
	}
	/**
	* Initialize this agent as a facet in a single RPC.
	*
	* Runs entirely inside the child's isolate, so every storage write
	* and `onStart()` I/O is owned by the child DO. This replaces the
	* previous "construct a Request in the parent DO and `stub.fetch()`
	* it on the child" handshake, whose native I/O was tied to the
	* parent and triggered "Cannot perform I/O on behalf of a different
	* Durable Object" on the child.
	*
	* We set `_isFacet` eagerly (before `__unsafe_ensureInitialized`
	* runs `onStart()`) so any code that legitimately branches on it
	* — e.g. skipping parent-owned alarms in schedule guards — sees
	* the flag during the first `onStart()` run. Protocol broadcasts are
	* suppressed only during this bootstrap window; afterward, facets can
	* broadcast to their own WebSocket clients reached via sub-agent
	* routing.
	*
	* The facet's logical name is persisted separately from its routing id.
	* Legacy facets used the logical name directly as `ctx.id.name`; newer
	* facets can use path-scoped routing ids while preserving `this.name`.
	*
	* @internal Called by {@link subAgent}.
	*/
	_cf_initAsFacet(name, parentPath = [], identityName = name) {
		return this._dynamicAgents.init(name, parentPath, identityName);
	}
	get name() {
		const routedName = this.lifecycle.name;
		return this._facetName ?? logicalNameFromPathV2Identity(routedName) ?? routedName;
	}
	/**
	* Ancestor chain for this agent, root-first. Empty for top-level
	* DOs. Populated at facet init time; survives hibernation.
	*
	* @example
	* ```ts
	* class Chat extends Agent {
	*   onStart() {
	*     console.log("chat started under:", this.parentPath);
	*     // → [{ className: "Tenant", name: "acme" }, { className: "Inbox", name: "alice" }]
	*   }
	* }
	* ```
	*
	* @experimental The API surface may change before stabilizing.
	*/
	get parentPath() {
		return this._parentPath;
	}
	/**
	* Ancestor chain + self, root-first. Convenient for logging.
	*
	* @experimental The API surface may change before stabilizing.
	*/
	get selfPath() {
		return [...this._parentPath, {
			className: this.constructor.name,
			name: this.name
		}];
	}
	/**
	* Resolve a typed parent stub for this facet's **immediate** parent
	* agent.
	*
	* Symmetric with `subAgent(Cls, name)`: while `subAgent` opens a
	* stub from parent to child, `parentAgent` opens one from child
	* to parent. Pass the direct parent's class reference — the
	* framework verifies it matches the last entry of
	* `this.parentPath` at runtime. If the parent is a top-level
	* Durable Object, the framework returns the normal namespace stub.
	* If the parent is itself a facet, the framework returns a bridge
	* proxy that routes method calls through the root/supervisor and
	* then down the recorded facet path.
	*
	* `this.parentPath` is root-first, so the direct parent is the
	* **last** entry: `this.parentPath.at(-1)`. For grandparents and
	* further ancestors, iterate `this.parentPath` and use
	* `getAgentByName(env.X, this.parentPath[i].name)` directly.
	*
	* For top-level parents, the framework first checks `env[Cls.name]`,
	* then falls back to the Worker `exports` object. This supports
	* custom binding names as long as the parent class is exported under
	* its class name.
	*
	* Facet-parent stubs route normal HTTP `.fetch()` calls through the
	* same root bridge as RPC methods. WebSocket upgrade requests are
	* not supported yet because WebSocket handles cannot be serialized
	* over RPC.
	*
	* @experimental The API surface may change before stabilizing.
	*
	* @throws If this agent is not a facet (no parent).
	* @throws If `Cls.name` doesn't match the recorded direct-parent
	*         class (guards against accidentally reaching the wrong
	*         DO, especially in nested Root → Mid → Leaf chains).
	* @throws If no namespace is found for a top-level parent, or no
	*         root namespace is available for a facet parent bridge.
	*
	* @example
	* ```ts
	* class Chat extends AIChatAgent<Env> {
	*   async onChatMessage(...) {
	*     const inbox = await this.parentAgent(Inbox);
	*     const memory = await inbox.getSharedMemory("facts");
	*     // ...
	*   }
	* }
	* ```
	*/
	async parentAgent(cls) {
		const parent = this._parentPath[this._parentPath.length - 1];
		if (!parent) throw new Error(`parentAgent(): ${this.constructor.name} is not a facet — only sub-agents (spawned via \`subAgent()\`) have a parent.`);
		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.`);
		if (this._parentPath.length > 1) return await this._cf_parentAgentFacetProxy(cls.name, this._parentPath);
		const binding = this._cf_getTopLevelNamespaceByClassName(cls.name);
		if (!binding) throw new Error(`parentAgent(${cls.name}): no top-level namespace for "${cls.name}" was found in env or worker exports. Make sure the parent class is exported under that class name and registered as a Durable Object binding.`);
		return await getAgentByName(binding, parent.name);
	}
	_cf_getTopLevelNamespaceByClassName(className) {
		return this._cf_asDurableObjectNamespace(this.env[className]) ?? this._cf_asDurableObjectNamespace(exports[className]);
	}
	_cf_asDurableObjectNamespace(candidate) {
		const binding = candidate;
		return binding?.idFromName ? binding : void 0;
	}
	async _cf_parentAgentFacetProxy(className, parentPath) {
		const [root] = parentPath;
		if (!root) throw new Error(`parentAgent(${className}): parent path is empty.`);
		const rootBinding = this._cf_getTopLevelNamespaceByClassName(root.className);
		if (!rootBinding) throw new Error(`parentAgent(${className}): direct parent is a facet, but no top-level root namespace "${root.className}" was found in env or worker exports to bridge the call.`);
		const rootStubPromise = getAgentByName(rootBinding, root.name);
		const targetPath = parentPath.map((step) => ({ ...step }));
		const invokeBridge = async (method, args) => {
			return await (await rootStubPromise)._cf_invokeSubAgentPath(targetPath, method, args);
		};
		const owner = this;
		return new Proxy({}, { get(_target, prop) {
			if (isInternalJsStubProp(prop)) return void 0;
			if (typeof prop !== "string") return void 0;
			if (prop === "fetch") return async (input, init) => {
				if (owner._cf_isWebSocketUpgradeRequest(input, init)) throw new Error(`parentAgent(${className}).fetch() does not support WebSocket upgrade requests yet. Use externally routed sub-agent URLs for WebSocket connections.`);
				return await invokeBridge(prop, [input, init]);
			};
			return async (...args) => {
				return await invokeBridge(prop, args);
			};
		} });
	}
	_cf_isWebSocketUpgradeRequest(input, init) {
		const initHeaders = init?.headers ? new Headers(init.headers) : void 0;
		const requestHeaders = input instanceof Request ? new Headers(input.headers) : void 0;
		return initHeaders?.get("Upgrade")?.toLowerCase() === "websocket" || requestHeaders?.get("Upgrade")?.toLowerCase() === "websocket";
	}
	/**
	* Get or create a named sub-agent — a child Durable Object (facet)
	* with its own isolated SQLite storage running on the same machine.
	*
	* The child class must extend `Agent` and be exported from the worker
	* entry point. The first call for a given name triggers the child's
	* `onStart()`. Subsequent calls return the existing instance.
	*
	* @experimental The API surface may change before stabilizing.
	*
	* @param cls The Agent subclass (must be exported from the worker)
	* @param name Unique name for this child instance
	* @returns A typed RPC stub for calling methods on the child
	*
	* @example
	* ```typescript
	* const searcher = await this.subAgent(SearchAgent, "main-search");
	* const results = await searcher.search("cloudflare agents");
	* ```
	*
	* @deprecated Use {@link Agent.dynamicAgents | this.dynamicAgents.get()} instead.
	*/
	async subAgent(cls, name) {
		return this.dynamicAgents.get(cls, name);
	}
	async onAgentToolStart(_run) {}
	async onAgentToolFinish(_run, _result) {}
	/**
	* Parent hook fired (best-effort) whenever a child agent-tool run emits a
	* `reportProgress` signal that is forwarded through this parent's tail. Use it
	* to meter / steer / surface progress server-side. Fires for both awaited and
	* detached runs; it is NOT durable — after eviction a detached run's latest
	* snapshot is read from `inspectAgentToolRun().progress` on reconcile instead.
	*/
	async onProgress(_run, _progress) {}
	/**
	* Emit an ephemeral progress signal from a sub-agent that is currently running
	* as an agent tool. Rides the child's active turn stream as a transient
	* `data-agent-progress` part (re-broadcast to the parent's clients + surfaced
	* in `useAgentToolEvents`) and persists a latest-wins snapshot for recovery /
	* inspection. A no-op (with a dev warning) on the base `Agent`, which has no
	* streaming turn — overridden by chat hosts (`@cloudflare/think`,
	* `AIChatAgent`). See `design/rfc-detached-agent-tools.md`.
	*/
	async reportProgress(_progress, _options) {
		console.warn("[agents] reportProgress() is only supported on chat agents (@cloudflare/think, AIChatAgent) running as an agent tool; ignoring on base Agent.");
	}
	async runAgentTool(cls, options) {
		const runId = options.runId ?? nanoid(12);
		const agentType = cls.name;
		const detached = this._parseDetachedOption(options.detached);
		const existing = this._readAgentToolRun(runId);
		if (existing) {
			if (detached) {
				if (!this._isAgentToolRowHardTerminal(existing.status)) await this._armDetachedBackbone();
				return {
					runId,
					agentType,
					status: "running"
				};
			}
			if (existing.status === "completed" || existing.status === "error" || existing.status === "aborted") {
				if (existing.status === "completed" && existing.output_json == null) try {
					const child = await this.subAgent(cls, runId);
					const inspection = await this._asAgentToolChildAdapter(child).inspectAgentToolRun(runId);
					if (inspection?.status === "completed") {
						const result = this._terminalResultFromInspection(agentType, inspection);
						this._updateAgentToolTerminal(runId, result, inspection.completedAt);
						return result;
					}
				} catch {}
				return this._resultFromAgentToolRow(existing);
			}
			let reattachReason;
			let childTornDown = false;
			try {
				const child = await this.subAgent(cls, runId);
				const adapter = this._asAgentToolChildAdapter(child);
				const reattach = await this._reattachAgentToolRunToTerminal(adapter, existing, 1, this._resolvedOptions.agentToolReattachNoProgressTimeoutMs, this._resolvedOptions.agentToolReattachMaxWindowMs);
				if (reattach.result) {
					await this._finishAgentToolRun(this._agentToolRunInfoFromRow(existing), reattach.result, {
						sequence: reattach.sequence,
						completedAt: reattach.completedAt
					});
					return reattach.result;
				}
				reattachReason = reattach.reason;
				childTornDown = await this._teardownGivenUpAgentToolChild(adapter, runId, reattach.reason);
			} catch {}
			return await this._replayAndInterruptAgentToolRun(existing, this._interruptedMessageForReason(reattachReason), {
				reason: reattachReason,
				childStillRunning: !childTornDown
			});
		}
		const displayOrder = options.displayOrder ?? 0;
		const inputPreview = options.inputPreview ?? this._defaultAgentToolPreview(options.input);
		const displayJson = options.display !== void 0 ? JSON.stringify(options.display) : null;
		const inputPreviewJson = inputPreview !== void 0 ? JSON.stringify(inputPreview) : null;
		const startedAt = Date.now();
		if (this._activeAgentToolRunCount() >= this.maxConcurrentAgentTools) {
			const error = `maxConcurrentAgentTools (${this.maxConcurrentAgentTools}) exceeded`;
			this.sql`
        INSERT INTO cf_agent_tool_runs (
          run_id, parent_tool_call_id, agent_type, input_preview,
          input_redacted, status, error_message, display_metadata,
          display_order, started_at, completed_at
        ) VALUES (
          ${runId}, ${options.parentToolCallId ?? null}, ${agentType},
          ${inputPreviewJson}, 1, 'error', ${error}, ${displayJson},
          ${displayOrder}, ${startedAt}, ${Date.now()}
        )
      `;
			this._broadcastAgentToolEvent(options.parentToolCallId, 0, {
				kind: "started",
				runId,
				agentType,
				inputPreview,
				order: displayOrder,
				display: options.display
			});
			this._broadcastAgentToolEvent(options.parentToolCallId, 1, {
				kind: "error",
				runId,
				error
			});
			return {
				runId,
				agentType,
				status: "error",
				error
			};
		}
		const detachedMaxBudgetAt = detached ? startedAt + (detached.maxBudgetMs ?? this._resolvedOptions.detachedMaxBudgetMs) : null;
		const detachedNoProgressBudgetMs = detached ? detached.noProgressBudgetMs ?? this._resolvedOptions.detachedNoProgressBudgetMs : null;
		const detachedOnMilestonesJson = detached?.onMilestones ? JSON.stringify(detached.onMilestones) : null;
		this.sql`
      INSERT INTO cf_agent_tool_runs (
        run_id, parent_tool_call_id, agent_type, input_preview,
        input_redacted, status, display_metadata, display_order, started_at,
        detached, detached_on_finish, detached_notify_source,
        detached_max_budget_at, detached_no_progress_budget_ms,
        detached_on_milestones
      ) VALUES (
        ${runId}, ${options.parentToolCallId ?? null}, ${agentType},
        ${inputPreviewJson}, 1, 'starting', ${displayJson}, ${displayOrder},
        ${startedAt}, ${detached ? 1 : 0}, ${detached?.onFinishName ?? null},
        ${detached?.notifySource ?? null}, ${detachedMaxBudgetAt},
        ${detachedNoProgressBudgetMs}, ${detachedOnMilestonesJson}
      )
    `;
		const runInfo = {
			runId,
			parentToolCallId: options.parentToolCallId,
			agentType,
			inputPreview,
			status: "starting",
			display: options.display,
			...detached?.notifySource !== void 0 ? { notifySource: detached.notifySource } : {},
			displayOrder,
			startedAt
		};
		await this.onAgentToolStart(runInfo);
		this._broadcastAgentToolEvent(options.parentToolCallId, 0, {
			kind: "started",
			runId,
			agentType,
			inputPreview,
			order: displayOrder,
			display: options.display
		});
		const child = await this.subAgent(cls, runId);
		const adapter = this._asAgentToolChildAdapter(child);
		const childStart = await adapter.startAgentToolRun(options.input, { runId });
		this._markAgentToolRunning(runId);
		if (detached) {
			if (options.signal) console.warn(`[agents] runAgentTool: \`signal\` is ignored for a detached run (${runId}); a detached child must outlive the spawning turn. Use cancelAgentTool(runId) to cancel it.`);
			await this._armDetachedBackbone({ resetCadence: true });
			this._maybeWarnDetachedLiveCount();
			this.ctx.waitUntil(this._detachedFastPath(runInfo, cls, runId));
			return {
				runId,
				agentType,
				status: "running"
			};
		}
		let sequence = 1;
		let parentAbortListener;
		if (options.signal) if (options.signal.aborted) {
			await adapter.cancelAgentToolRun(runId, options.signal.reason);
			const result = {
				runId,
				agentType,
				status: "aborted",
				error: options.signal.reason instanceof Error ? options.signal.reason.message : String(options.signal.reason ?? "cancelled")
			};
			await this._finishAgentToolRun(runInfo, result, { sequence });
			return result;
		} else {
			parentAbortListener = () => {
				adapter.cancelAgentToolRun(runId, options.signal?.reason);
			};
			options.signal.addEventListener("abort", parentAbortListener, { once: true });
		}
		try {
			if (adapter.tailAgentToolRun) {
				const stream = await adapter.tailAgentToolRun(runId, { afterSequence: -1 });
				sequence = (await this._forwardAgentToolStream(stream, options.parentToolCallId, runId, sequence, options.signal)).next;
			} else {
				const chunks = await adapter.getAgentToolChunks(runId);
				sequence = this._broadcastAgentToolChunks(options.parentToolCallId, runId, chunks, sequence);
			}
			if (options.signal?.aborted) {
				await adapter.cancelAgentToolRun(runId, options.signal.reason);
				const result = {
					runId,
					agentType,
					status: "aborted",
					error: options.signal.reason instanceof Error ? options.signal.reason.message : String(options.signal.reason ?? "cancelled")
				};
				await this._finishAgentToolRun(runInfo, result, { sequence });
				return result;
			}
			const inspection = await adapter.inspectAgentToolRun(runId) ?? childStart;
			const result = this._terminalResultFromInspection(agentType, inspection);
			await this._finishAgentToolRun(runInfo, result, {
				sequence,
				completedAt: inspection.completedAt
			});
			return result;
		} catch (error) {
			if (options.signal?.aborted) {
				await adapter.cancelAgentToolRun(runId, options.signal.reason);
				const result = {
					runId,
					agentType,
					status: "aborted",
					error: options.signal.reason instanceof Error ? options.signal.reason.message : String(options.signal.reason ?? "cancelled")
				};
				await this._finishAgentToolRun(runInfo, result, { sequence });
				return result;
			}
			const result = {
				runId,
				agentType,
				status: "error",
				error: error instanceof Error ? error.message : String(error)
			};
			await this._finishAgentToolRun(runInfo, result, { sequence });
			return result;
		} finally {
			if (parentAbortListener && options.signal) options.signal.removeEventListener("abort", parentAbortListener);
		}
	}
	/**
	* Cancel an agent-tool run by id. Idempotent: cancelling an already-terminal
	* run is a no-op. Detached runs deliver through the guarded ledger so a wired
	* `onFinish` fires once with `status: "aborted"`; awaited runs leave terminal
	* observation to the awaiting/recovery path, avoiding duplicate finish hooks.
	*/
	async cancelAgentTool(runId, reason) {
		const row = this._readAgentToolRun(runId);
		if (!row) return;
		if (this._isAgentToolRowHardTerminal(row.status)) return;
		const isDetached = row.detached === 1;
		const message = reason instanceof Error ? reason.message : String(reason ?? "cancelled by parent");
		try {
			const child = await this._cf_resolveSubAgent(row.agent_type, runId);
			await this._asAgentToolChildAdapter(child).cancelAgentToolRun(runId, reason);
		} catch {}
		if (!isDetached) return;
		await this._deliverDetachedTerminal(runId, "finish", {
			runId,
			agentType: row.agent_type,
			status: "aborted",
			error: message
		});
	}
	/**
	* Parse + validate the `detached` option. Returns `null` for a non-detached
	* run, or the normalized config (with the validated `onFinish` method name)
	* for a detached one. Throws if `onFinish` does not name a method on this
	* agent — closures cannot survive Durable Object eviction, so the durable
	* hook is referenced by method name (the same contract as `schedule`).
	*/
	_parseDetachedOption(detached) {
		if (!detached) return null;
		if (detached === true) return {};
		let onFinishName = detached.onFinish;
		const notifySource = typeof detached.notify === "object" ? detached.notify.source : void 0;
		if (onFinishName !== void 0) {
			if (typeof this[onFinishName] !== "function") throw new Error(`runAgentTool: detached.onFinish "${onFinishName}" is not a method on ${this.constructor.name}. Pass the NAME of a method (e.g. "onImportDone"), not a closure — closures cannot be rehydrated after the Durable Object is evicted.`);
		} else if (detached.notify) {
			if (typeof this[DETACHED_NOTIFY_CALLBACK] === "function") onFinishName = DETACHED_NOTIFY_CALLBACK;
		}
		return {
			...onFinishName !== void 0 ? { onFinishName } : {},
			...notifySource !== void 0 ? { notifySource } : {},
			...detached.maxBudgetMs !== void 0 ? { maxBudgetMs: detached.maxBudgetMs } : {},
			...detached.noProgressBudgetMs !== void 0 ? { noProgressBudgetMs: detached.noProgressBudgetMs } : {},
			...(() => {
				const raw = detached.onMilestones;
				if (!raw) return {};
				const names = Array.isArray(raw) ? raw : raw.names;
				if (!Array.isArray(names) || names.length === 0) return {};
				return { onMilestones: {
					names,
					mode: Array.isArray(raw) ? "narrate" : raw.mode ?? "narrate"
				} };
			})()
		};
	}
	_isAgentToolRowHardTerminal(status) {
		return status === "completed" || status === "error" || status === "aborted";
	}
	_hasOutstandingDetachedRuns() {
		return (this.sql`
      SELECT COUNT(*) AS n FROM cf_agent_tool_runs
      WHERE detached = 1 AND finish_delivered_at IS NULL
    `[0]?.n ?? 0) > 0;
	}
	/** Detached runs still holding a concurrency slot (non-terminal). */
	_liveDetachedRunCount() {
		return this.sql`
      SELECT COUNT(*) AS n FROM cf_agent_tool_runs
      WHERE detached = 1 AND status IN ('starting', 'running')
    `[0]?.n ?? 0;
	}
	/**
	* Edge-triggered warning when live detached runs cross
	* `DETACHED_LIVE_COUNT_WARN_THRESHOLD`. Fires once on the up-crossing and
	* re-arms only after the count falls back below the threshold, so a parent
	* accumulating long-lived background runs surfaces a signal without spamming.
	*/
	_maybeWarnDetachedLiveCount() {
		const liveCount = this._liveDetachedRunCount();
		if (liveCount < DETACHED_LIVE_COUNT_WARN_THRESHOLD) {
			this._detachedLiveCountWarned = false;
			return;
		}
		if (this._detachedLiveCountWarned) return;
		this._detachedLiveCountWarned = true;
		this._emit("agent_tool:detached:live_count_warning", {
			liveCount,
			threshold: DETACHED_LIVE_COUNT_WARN_THRESHOLD
		});
		console.warn(`[agents] ${liveCount} detached agent-tool runs are live on this agent (threshold ${DETACHED_LIVE_COUNT_WARN_THRESHOLD}). Detached runs hold a concurrency slot until they finish — make sure they are completing or being cancelled, or lower \`maxConcurrentAgentTools\`.`);
	}
	/**
	* Warm fast path for a detached run: tail the child to terminal (so the
	* parent re-broadcasts its live stream to clients) and deliver the completion
	* with low latency while the isolate stays alive. Best-effort — the durable
	* `_cfDetachedReconcileTick` backbone is the guarantee; anything this misses
	* (eviction, a child that has not yet reached terminal) the backbone collects.
	*/
	async _detachedFastPath(runInfo, cls, runId) {
		try {
			const child = await this.subAgent(cls, runId);
			const adapter = this._asAgentToolChildAdapter(child);
			let sequence = 1;
			if (adapter.tailAgentToolRun) {
				const stream = await adapter.tailAgentToolRun(runId, { afterSequence: -1 });
				sequence = (await this._forwardAgentToolStream(stream, runInfo.parentToolCallId, runId, sequence, void 0)).next;
			}
			const inspection = await adapter.inspectAgentToolRun(runId);
			if (inspection && this._isAgentToolRowHardTerminal(inspection.status)) {
				const result = this._terminalResultFromInspection(runInfo.agentType, inspection);
				await this._deliverDetachedTerminal(runId, "finish", result, {
					sequence,
					serialize: true
				}, inspection.completedAt);
			}
		} catch {}
	}
	/**
	* Single delivery funnel for a detached terminal. Both the warm fast path and
	* the durable backbone route through here, with INDEPENDENT ledger slots for
	* `finish` (the real terminal) vs `give_up` (budget exhausted). Each slot is
	* delivered at-least-once via a claim + lease:
	*
	* - Concurrent double-fire is prevented by the guarded CAS claim (RETURNING
	*   yields the row only to the winner).
	* - A crash after the side effect but before `*_delivered_at` is written lets
	*   the lease expire so a later reconcile re-delivers — hence handlers must be
	*   idempotent.
	* - Two slots, not one, because `interrupted` is SOFT: a give-up followed by a
	*   real completion is legitimate, and a single shared "delivered" bit would
	*   dedupe the child's real late result away (the #1752 production incident).
	*/
	async _deliverDetachedTerminal(runId, kind, result, options, completedAt = Date.now()) {
		const now = Date.now();
		const leaseFloor = now - DETACHED_DELIVERY_LEASE_MS;
		const claimQuery = kind === "finish" ? `UPDATE cf_agent_tool_runs
             SET finish_claimed_at = ?
             WHERE run_id = ?
               AND finish_delivered_at IS NULL
               AND (finish_claimed_at IS NULL OR finish_claimed_at < ?)` : `UPDATE cf_agent_tool_runs
             SET give_up_claimed_at = ?
             WHERE run_id = ?
               AND give_up_delivered_at IS NULL
               AND (give_up_claimed_at IS NULL OR give_up_claimed_at < ?)`;
		if (this.ctx.storage.sql.exec(claimQuery, now, runId, leaseFloor).rowsWritten === 0) return;
		const row = this._readAgentToolRun(runId);
		if (!row) return;
		this._updateAgentToolTerminal(runId, result, completedAt);
		this._broadcastAgentToolTerminal(row.parent_tool_call_id ?? void 0, options?.sequence ?? Date.now(), result);
		const runInfo = this._agentToolRunInfoFromRow(row, result.status, completedAt);
		const lifecycle = {
			status: result.status,
			...result.summary !== void 0 ? { summary: result.summary } : {},
			...result.error !== void 0 ? { error: result.error } : {},
			...result.reason !== void 0 ? { reason: result.reason } : {},
			...result.childStillRunning !== void 0 ? { childStillRunning: result.childStillRunning } : {}
		};
		const invoke = async () => {
			try {
				await this.onAgentToolFinish(runInfo, lifecycle);
			} catch (error) {
				await this._safeRunOnError(error);
			}
			const callbackName = row.detached_on_finish;
			if (callbackName) {
				const callback = this[callbackName];
				if (typeof callback === "function") try {
					await callback.bind(this)(runInfo, lifecycle);
				} catch (error) {
					this._emit("agent_tool:detached:delivery_failed", {
						runId,
						kind,
						status: result.status,
						callback: callbackName,
						error: error instanceof Error ? error.message : String(error)
					});
					await this._safeRunOnError(error);
					throw error;
				}
			}
		};
		await this._runDetachedDelivery(invoke, { serialize: options?.serialize });
		if (kind === "finish") this.sql`
        UPDATE cf_agent_tool_runs
        SET finish_delivered_at = ${Date.now()}
        WHERE run_id = ${runId}
      `;
		else this.sql`
        UPDATE cf_agent_tool_runs
        SET give_up_delivered_at = ${Date.now()}
        WHERE run_id = ${runId}
      `;
	}
	async _safeRunOnError(error) {
		try {
			await this.onError(error);
		} catch {}
	}
	/**
	* Run a detached terminal delivery (the `onAgentToolFinish` + per-run
	* `onFinish` callbacks) in an appropriate execution context. The base `Agent`
	* has no turn queue, so it only establishes `agentContext` — a handler that
	* calls `runAgentTool` / `setState` therefore works regardless of where the
	* delivery fired from.
	*
	* Chat-layer subclasses (`@cloudflare/think`, `@cloudflare/ai-chat`) override
	* this to additionally serialize delivery against their turn queue when
	* `serialize` is set: a fast-path push or backbone tick can land mid-turn, and
	* a state-mutating `onFinish` running concurrently with an active LLM turn is a
	* data race. The fast path and backbone never run synchronously inside a turn
	* (they fire from `waitUntil` / a scheduled alarm), so enqueuing them on the
	* turn queue is deadlock-free. An explicit `cancelAgentTool` runs with
	* `serialize` unset because it may be called from inside the very turn that
	* triggers it, where enqueuing would self-deadlock.
	*/
	async _runDetachedDelivery(invoke, _options) {
		if (__DO_NOT_USE_WILL_BREAK__agentContext.getStore()?.agent) {
			await invoke();
			return;
		}
		await runInInvocation({
			agent: this,
			connection: void 0,
			request: void 0,
			email: void 0
		}, invoke, { detached: true });
	}
	/**
	* Arm the self-scheduling detached reconcile backbone. Existing schedules are
	* reused for recovery/startup calls, but a fresh detached dispatch resets the
	* pending cadence to the fast end so new work is noticed promptly.
	*/
	async _armDetachedBackbone(options) {
		const run = this._detachedBackboneArming.then(() => this._armDetachedBackboneInner(options));
		this._detachedBackboneArming = run.then(() => void 0, () => void 0);
		return run;
	}
	async _armDetachedBackboneInner(options) {
		const armed = (await this.listSchedules()).filter((schedule) => schedule.callback === DETACHED_RECONCILE_CALLBACK);
		if (armed.length > 0 && !options?.resetCadence) {
			for (const schedule of armed.slice(1)) await this.cancelSchedule(schedule.id);
			return;
		}
		for (const schedule of armed) await this.cancelSchedule(schedule.id);
		await this.schedule(DETACHED_BACKBONE_CADENCE_S[0], DETACHED_RECONCILE_CALLBACK, { cadenceIndex: 0 }, { idempotent: true });
	}
	/**
	* Durable backbone for detached runs. Runs on a self-rescheduling alarm:
	* collects any detached run that has reached terminal but was not yet
	* delivered (e.g. the parent was evicted before the fast path landed), gives
	* up on any run past its absolute budget (tearing the child down), and
	* reschedules itself while any detached run remains undelivered — cancelling
	* itself once everything has settled (zero steady-state cost).
	*/
	async _cfDetachedReconcileTick(payload) {
		const rows = this.sql`
      SELECT run_id, parent_tool_call_id, agent_type, input_preview, status,
             summary, output_json, error_message, interrupted_reason,
             child_still_running, display_metadata, display_order,
             started_at, completed_at, detached, detached_on_finish,
             detached_notify_source, detached_max_budget_at,
             detached_no_progress_budget_ms, last_progress_at,
             detached_on_milestones,
             finish_claimed_at, finish_delivered_at, give_up_claimed_at,
             give_up_delivered_at
      FROM cf_agent_tool_runs
      WHERE detached = 1 AND finish_delivered_at IS NULL
      ORDER BY started_at ASC
    `;
		for (const row of rows) {
			const runId = row.run_id;
			let inspection = null;
			try {
				const child = await this._cf_resolveSubAgent(row.agent_type, runId);
				inspection = await this._asAgentToolChildAdapter(child).inspectAgentToolRun(runId);
			} catch {}
			if (inspection?.milestones && row.detached_on_milestones) {
				const milestoneRunInfo = this._agentToolRunInfoFromRow(row);
				for (const milestone of inspection.milestones) this._maybeDeliverDetachedMilestone(row, milestoneRunInfo, milestone);
			}
			if (inspection && this._isAgentToolRowHardTerminal(inspection.status)) {
				const result = this._terminalResultFromInspection(row.agent_type, inspection);
				await this._deliverDetachedTerminal(runId, "finish", result, {
					sequence: Date.now(),
					serialize: true
				}, inspection.completedAt);
				continue;
			}
			const now = Date.now();
			const budgetAt = row.detached_max_budget_at;
			const latestMilestone = inspection?.milestones?.length ? inspection.milestones[inspection.milestones.length - 1].at : void 0;
			const signalTimes = [
				inspection?.progress?.at,
				latestMilestone,
				row.last_progress_at
			].filter((t) => typeof t === "number");
			const lastSignalAt = signalTimes.length > 0 ? Math.max(...signalTimes) : void 0;
			const noProgressBudgetMs = row.detached_no_progress_budget_ms;
			const overAbsolute = budgetAt !== null && now >= budgetAt;
			const overNoProgress = typeof noProgressBudgetMs === "number" && noProgressBudgetMs > 0 && Number.isFinite(noProgressBudgetMs) && typeof lastSignalAt === "number" && now - lastSignalAt >= noProgressBudgetMs;
			if ((overAbsolute || overNoProgress) && row.give_up_delivered_at === null) {
				let childTornDown = false;
				try {
					const child = await this._cf_resolveSubAgent(row.agent_type, runId);
					await this._asAgentToolChildAdapter(child).cancelAgentToolRun(runId, overAbsolute ? "detached budget exceeded" : "detached run went silent past its no-progress window");
					childTornDown = true;
				} catch {}
				await this._deliverDetachedTerminal(runId, "give_up", {
					runId,
					agentType: row.agent_type,
					status: "interrupted",
					error: overAbsolute ? "detached run exceeded its budget before completing" : "detached run went silent past its no-progress window",
					reason: overAbsolute ? "budget-exceeded" : "no-progress",
					childStillRunning: !childTornDown
				}, { serialize: true });
			}
		}
		if (this._hasOutstandingDetachedRuns()) {
			const currentIndex = typeof payload?.cadenceIndex === "number" ? payload.cadenceIndex : 0;
			const nextIndex = Math.min(currentIndex + 1, DETACHED_BACKBONE_CADENCE_S.length - 1);
			await this.schedule(DETACHED_BACKBONE_CADENCE_S[nextIndex], DETACHED_RECONCILE_CALLBACK, { cadenceIndex: nextIndex });
		}
	}
	hasAgentToolRun(classOrName, runId) {
		const agentType = typeof classOrName === "string" ? classOrName : classOrName.name;
		return (this.sql`
      SELECT COUNT(*) AS n FROM cf_agent_tool_runs
      WHERE run_id = ${runId} AND agent_type = ${agentType}
    `[0]?.n ?? 0) > 0;
	}
	async clearAgentToolRuns(options) {
		const rows = this.sql`
      SELECT run_id, agent_type, status FROM cf_agent_tool_runs
      ORDER BY started_at ASC
    `;
		const statusFilter = options?.status ? new Set(options.status) : null;
		const retained = rows.filter((row) => {
			if (statusFilter && !statusFilter.has(row.status)) return false;
			if (options?.olderThan !== void 0) {
				const full = this._readAgentToolRun(row.run_id);
				if (!full || full.started_at >= options.olderThan) return false;
			}
			return true;
		});
		for (const row of retained) {
			try {
				const cls = this._agentToolClassByName(row.agent_type);
				if (row.status === "starting" || row.status === "running") {
					const child = await this.subAgent(cls, row.run_id);
					await this._asAgentToolChildAdapter(child).cancelAgentToolRun(row.run_id, "clearing agent tool run");
				}
				await this.deleteSubAgent(cls, row.run_id);
			} catch {}
			this.sql`
        DELETE FROM cf_agent_tool_runs WHERE run_id = ${row.run_id}
      `;
		}
	}
	_isAgentToolTerminal(status) {
		return status === "completed" || status === "error" || status === "aborted" || status === "interrupted";
	}
	_activeAgentToolRunCount() {
		return this.sql`
      SELECT COUNT(*) AS n FROM cf_agent_tool_runs
      WHERE status IN ('starting', 'running')
    `[0]?.n ?? 0;
	}
	_defaultAgentToolPreview(input) {
		if (typeof input === "string") return input.slice(0, 500);
		if (input === null || input === void 0) return input;
		try {
			const json = JSON.stringify(input);
			return json.length > 500 ? `${json.slice(0, 497)}...` : json;
		} catch {
			return String(input).slice(0, 500);
		}
	}
	_readAgentToolRun(runId) {
		return this.sql`
      SELECT run_id, parent_tool_call_id, agent_type, input_preview, status,
             summary, output_json, error_message, interrupted_reason,
             child_still_running, display_metadata, display_order,
             started_at, completed_at, detached, detached_on_finish,
             detached_notify_source, detached_max_budget_at,
             finish_claimed_at, finish_delivered_at, give_up_claimed_at,
             give_up_delivered_at
      FROM cf_agent_tool_runs
      WHERE run_id = ${runId}
      LIMIT 1
    `[0] ?? null;
	}
	/**
	* Reconstruct the typed interrupted cause (`reason` / `childStillRunning`,
	* #1630 follow-up) from a stored row so a row→result/event rebuild — e.g. a
	* reconnect replay — carries the same fields a live client saw. Only
	* `interrupted` rows store a cause; everything else yields `{}` (the columns
	* are cleared whenever a row settles to a hard terminal).
	*/
	_agentToolInterruptedExtrasFromRow(row) {
		if (row.status !== "interrupted") return {};
		return {
			...row.interrupted_reason !== null ? { reason: row.interrupted_reason } : {},
			...row.child_still_running !== null ? { childStillRunning: row.child_still_running !== 0 } : {}
		};
	}
	_resultFromAgentToolRow(row) {
		const output = this._parseAgentToolJson(row.output_json);
		return {
			runId: row.run_id,
			agentType: row.agent_type,
			status: row.status,
			...output !== void 0 ? { output } : {},
			...row.summary !== null ? { summary: row.summary } : {},
			...row.error_message !== null ? { error: row.error_message } : {},
			...this._agentToolInterruptedExtrasFromRow(row)
		};
	}
	_agentToolRunInfoFromRow(row, status = row.status, completedAt = row.completed_at ?? void 0) {
		return {
			runId: row.run_id,
			parentToolCallId: row.parent_tool_call_id ?? void 0,
			agentType: row.agent_type,
			inputPreview: this._parseAgentToolJson(row.input_preview),
			status,
			display: this._parseAgentToolJson(row.display_metadata),
			...row.detached_notify_source != null ? { notifySource: row.detached_notify_source } : {},
			displayOrder: row.display_order,
			startedAt: row.started_at,
			completedAt
		};
	}
	_terminalResultFromInspection(agentType, inspection) {
		if (inspection.status === "completed") return {
			runId: inspection.runId,
			agentType,
			status: "completed",
			output: inspection.output,
			summary: inspection.summary
		};
		if (inspection.status === "aborted") return {
			runId: inspection.runId,
			agentType,
			status: "aborted",
			error: inspection.error
		};
		return {
			runId: inspection.runId,
			agentType,
			status: "error",
			error: inspection.error ?? "Agent tool run failed"
		};
	}
	async _finishAgentToolRun(run, result, options) {
		const completedAt = options?.completedAt ?? Date.now();
		this._updateAgentToolTerminal(run.runId, result, completedAt);
		if (options?.sequence !== void 0) this._broadcastAgentToolTerminal(run.parentToolCallId, options.sequence, result);
		const finish = () => this.onAgentToolFinish({
			...run,
			status: result.status,
			completedAt
		}, result);
		if (options?.deferFinishHook) return finish;
		await finish();
	}
	async _runDeferredAgentToolFinishHooks(hooks) {
		for (const hook of hooks) try {
			await hook();
		} catch (error) {
			try {
				await this.onError(error);
			} catch {}
		}
	}
	_updateAgentToolTerminal(runId, result, completedAt = Date.now()) {
		const childStillRunning = result.childStillRunning === void 0 ? null : result.childStillRunning ? 1 : 0;
		this.sql`
      UPDATE cf_agent_tool_runs
      SET status = ${result.status},
          summary = ${result.summary ?? null},
          output_json = ${this._stringifyAgentToolOutput(result.output)},
          error_message = ${result.error ?? null},
          interrupted_reason = ${result.reason ?? null},
          child_still_running = ${childStillRunning},
          completed_at = ${completedAt}
      WHERE run_id = ${runId}
        AND status NOT IN ('completed', 'error', 'aborted')
    `;
		if (result.status === "completed" && result.output !== void 0) this.sql`
        UPDATE cf_agent_tool_runs
        SET output_json = COALESCE(output_json, ${this._stringifyAgentToolOutput(result.output)}),
            summary = COALESCE(summary, ${result.summary ?? null})
        WHERE run_id = ${runId} AND status = 'completed'
      `;
	}
	_markAgentToolRunning(runId) {
		this.sql`
      UPDATE cf_agent_tool_runs
      SET status = 'running'
      WHERE run_id = ${runId} AND status = 'starting'
    `;
	}
	_parseAgentToolJson(value) {
		if (value === null) return void 0;
		try {
			return JSON.parse(value);
		} catch {
			return value;
		}
	}
	_stringifyAgentToolOutput(output) {
		if (output === void 0) return null;
		const json = JSON.stringify(output);
		return json === void 0 ? null : json;
	}
	_broadcastAgentToolEvent(parentToolCallId, sequence, event, replay, connection) {
		const message = {
			type: "agent-tool-event",
			parentToolCallId,
			sequence,
			event,
			...replay ? { replay } : {}
		};
		const body = JSON.stringify(message);
		if (connection) connection.send(body);
		else this.broadcast(body);
	}
	_broadcastAgentToolChunks(parentToolCallId, runId, chunks, sequence, replay, connection) {
		let next = sequence;
		for (const chunk of chunks) this._broadcastAgentToolEvent(parentToolCallId, next++, {
			kind: "chunk",
			runId,
			body: chunk.body
		}, replay, connection);
		return next;
	}
	async _broadcastAgentToolStoredChunks(row, sequence, replay, connection) {
		const child = await this._cf_resolveSubAgent(row.agent_type, row.run_id);
		const adapter = this._asAgentToolChildAdapter(child);
		return this._broadcastAgentToolStoredChunksFromAdapter(adapter, row, sequence, replay, connection);
	}
	async _broadcastAgentToolStoredChunksFromAdapter(adapter, row, sequence, replay, connection, timeoutMs) {
		const chunks = await this._getAgentToolChunksForRecovery(adapter, row.run_id, timeoutMs);
		if (!chunks) return sequence;
		return this._broadcastAgentToolChunks(row.parent_tool_call_id ?? void 0, row.run_id, chunks, sequence, replay, connection);
	}
	async _forwardAgentToolStream(stream, parentToolCallId, runId, sequence, signal, idleTimeoutMs) {
		let next = sequence;
		if (signal?.aborted) return {
			next,
			ended: "aborted"
		};
		let ended = "done";
		const reader = stream.getReader();
		const decoder = new TextDecoder();
		let bufferedBytes = "";
		let aborted = false;
		let resolveAbort;
		const abortPromise = new Promise((resolve) => {
			resolveAbort = resolve;
		});
		let abortListener;
		if (signal) {
			abortListener = () => resolveAbort?.();
			signal.addEventListener("abort", abortListener, { once: true });
		}
		const idleEnabled = typeof idleTimeoutMs === "number" && idleTimeoutMs > 0 && Number.isFinite(idleTimeoutMs);
		let resolveIdle;
		let idleTimer;
		const idlePromise = new Promise((resolve) => {
			resolveIdle = resolve;
		});
		const armIdle = () => {
			if (!idleEnabled) return;
			if (idleTimer !== void 0) clearTimeout(idleTimer);
			idleTimer = setTimeout(() => resolveIdle?.(), idleTimeoutMs);
		};
		let forwardedSinceProgress = false;
		try {
			const forwardChunk = (chunk) => {
				this._broadcastAgentToolEvent(parentToolCallId, next++, {
					kind: "chunk",
					runId,
					body: chunk.body
				});
				this._observeForwardedProgress(runId, chunk.body);
				forwardedSinceProgress = true;
				armIdle();
			};
			const forwardLine = (line) => {
				try {
					const chunk = JSON.parse(line);
					if (typeof chunk.body === "string") forwardChunk(chunk);
				} catch {}
			};
			const flushBufferedBytes = (final = false) => {
				while (true) {
					const newline = bufferedBytes.indexOf("\n");
					if (newline === -1) break;
					const line = bufferedBytes.slice(0, newline).trim();
					bufferedBytes = bufferedBytes.slice(newline + 1);
					if (line.length > 0) forwardLine(line);
				}
				if (final && bufferedBytes.trim().length > 0) {
					forwardLine(bufferedBytes);
					bufferedBytes = "";
				}
			};
			armIdle();
			while (true) {
				const readPromise = reader.read();
				readPromise.catch(() => {});
				const raced = await Promise.race([
					readPromise.then((result) => ({
						kind: "read",
						result
					})),
					abortPromise.then(() => ({ kind: "abort" })),
					idlePromise.then(() => ({ kind: "idle" }))
				]);
				if (raced.kind === "abort" || raced.kind === "idle") {
					aborted = true;
					ended = raced.kind === "idle" ? "idle" : "aborted";
					break;
				}
				const { done, value } = raced.result;
				if (done) {
					bufferedBytes += decoder.decode();
					flushBufferedBytes(true);
					break;
				}
				if (value instanceof Uint8Array) {
					bufferedBytes += decoder.decode(value, { stream: true });
					flushBufferedBytes();
				} else forwardChunk(value);
				if (forwardedSinceProgress) {
					forwardedSinceProgress = false;
					try {
						await this._onAgentToolStreamProgress();
					} catch {}
				}
			}
		} finally {
			if (idleTimer !== void 0) clearTimeout(idleTimer);
			if (abortListener && signal) signal.removeEventListener("abort", abortListener);
			if (!aborted) try {
				reader.releaseLock();
			} catch {}
		}
		return {
			next,
			ended
		};
	}
	/**
	* Hook invoked by `_forwardAgentToolStream` after a child produces output that
	* was forwarded to the parent's connections. Forwarding a sub-agent's stream
	* is genuine forward progress for the *parent* turn (the parent is
	* orchestrating the child), so chat-recovery subclasses (Think / AIChatAgent)
	* override this to advance their recovery progress marker.
	*
	* Without it, a parent whose turn merely `await`s a sub-agent banks zero
	* progress of its own, so under deploy churn the parent's no-progress recovery
	* window exhausts and abandons the turn as `interrupted` — even though the
	* child is healthily streaming and ultimately completes (observed in the
	* `deploy-churn --mode subagent` harness: `attempt 6/6, stable_timeout,
	* progress: 1`).
	*
	* Called ONLY after at least one chunk was actually forwarded — never merely
	* because a child is attached — so a silent / hung child still lets the parent
	* exhaust on its own timer. The base Agent has no recovery budget, so this is
	* a no-op; subclasses should throttle the (durable) bump since this can be
	* called repeatedly while a child streams.
	*/
	async _onAgentToolStreamProgress() {}
	/**
	* Best-effort observation of a forwarded child chunk: if it is a reserved
	* `data-agent-progress` frame, refresh the cached liveness timestamp on the
	* run row (a hint for a still-warm parent) and fire the public `onProgress`
	* hook. Never throws into the forward loop — the child's own persisted
	* snapshot (read via `inspectAgentToolRun`) remains authoritative for the
	* resetting no-progress budget after eviction.
	*/
	_observeForwardedProgress(runId, body) {
		let parsed;
		try {
			parsed = JSON.parse(body);
		} catch {
			return;
		}
		if (!parsed) return;
		const isMilestone = parsed.type === AGENT_TOOL_MILESTONE_PART;
		if (parsed.type !== "data-agent-progress" && !isMilestone) return;
		const data = parsed.data ?? {};
		const at = Date.now();
		const snapshot = {
			...typeof data.fraction === "number" ? { fraction: data.fraction } : {},
			...typeof data.message === "string" ? { message: data.message } : {},
			...typeof data.phase === "string" ? { phase: data.phase } : {},
			...isMilestone && typeof data.name === "string" ? { milestone: data.name } : {},
			...data.data !== void 0 ? { data: data.data } : {},
			at
		};
		const row = this._readAgentToolRun(runId);
		if (!row) return;
		if (row.detached) try {
			this.sql`
          UPDATE cf_agent_tool_runs SET last_progress_at = ${at}
          WHERE run_id = ${runId}
        `;
		} catch {}
		const runInfo = this._agentToolRunInfoFromRow(row);
		Promise.resolve(this.onProgress(runInfo, snapshot)).catch((error) => {
			console.error(`[agents] onProgress hook threw for run ${runId}:`, error instanceof Error ? error.message : String(error));
		});
		if (isMilestone && typeof data.name === "string") this._maybeDeliverDetachedMilestone(row, runInfo, {
			name: data.name,
			sequence: typeof data.sequence === "number" ? data.sequence : 0,
			at: typeof data.at === "number" ? data.at : at,
			...data.data !== void 0 ? { data: data.data } : {}
		});
	}
	/**
	* Deliver a milestone notification IF this run opted into it via
	* `detached: { onMilestones }` and the milestone name is in that set. Routes
	* to the overridable `_deliverDetachedMilestone` seam (a no-op on the base
	* `Agent`; chat hosts inject an idempotent synthetic chat message).
	*/
	_maybeDeliverDetachedMilestone(row, runInfo, milestone) {
		const configured = this._parseAgentToolJson(row.detached_on_milestones ?? null);
		const names = Array.isArray(configured) ? configured : configured?.names;
		const mode = Array.isArray(configured) ? "narrate" : configured?.mode ?? "narrate";
		if (!Array.isArray(names) || !names.includes(milestone.name)) return;
		Promise.resolve(this._deliverDetachedMilestone(runInfo, milestone, mode)).catch((error) => {
			console.error(`[agents] detached milestone delivery threw for run ${runInfo.runId} (${milestone.name}):`, error instanceof Error ? error.message : String(error));
		});
	}
	/**
	* Overridable seam for the `detached: { onMilestones }` convenience. The base
	* `Agent` has no chat surface, so this is a no-op; chat hosts
	* (`@cloudflare/think`, `AIChatAgent`) override it to submit an idempotent
	* synthetic message keyed on `(runId, milestone.name)`. Called from both the
	* warm tail and the backbone reconcile, so it MUST be idempotent.
	*/
	async _deliverDetachedMilestone(_run, _milestone, _mode) {}
	_broadcastAgentToolTerminal(parentToolCallId, sequence, result, replay, connection) {
		if (result.status === "completed") this._broadcastAgentToolEvent(parentToolCallId, sequence, {
			kind: "finished",
			runId: result.runId,
			summary: result.summary ?? ""
		}, replay, connection);
		else if (result.status === "aborted") this._broadcastAgentToolEvent(parentToolCallId, sequence, {
			kind: "aborted",
			runId: result.runId,
			reason: result.error
		}, replay, connection);
		else if (result.status === "interrupted") this._broadcastAgentToolEvent(parentToolCallId, sequence, {
			kind: "interrupted",
			runId: result.runId,
			error: result.error ?? "Agent tool run was interrupted",
			...result.reason !== void 0 ? { reason: result.reason } : {},
			...result.childStillRunning !== void 0 ? { childStillRunning: result.childStillRunning } : {}
		}, replay, connection);
		else this._broadcastAgentToolEvent(parentToolCallId, sequence, {
			kind: "error",
			runId: result.runId,
			error: result.error ?? "Agent tool run failed"
		}, replay, connection);
	}
	_asAgentToolChildAdapter(child) {
		const candidate = child;
		if (typeof candidate.startAgentToolRun !== "function" || typeof candidate.cancelAgentToolRun !== "function" || typeof candidate.inspectAgentToolRun !== "function" || typeof candidate.getAgentToolChunks !== "function") throw new Error("Agent tool child must implement the framework agent-tool adapter. Use a @cloudflare/think Think subclass or an AIChatAgent subclass.");
		return candidate;
	}
	_agentToolClassByName(className) {
		const cls = this.ctx.exports?.[className];
		if (!cls) throw new Error(`Agent tool class "${className}" is not exported.`);
		return cls;
	}
	async _replayAndInterruptAgentToolRun(row, message, extra) {
		let sequence = 1;
		try {
			sequence = await this._broadcastAgentToolStoredChunks(row, sequence);
		} catch {}
		const result = {
			runId: row.run_id,
			agentType: row.agent_type,
			status: "interrupted",
			error: message,
			...extra?.reason !== void 0 ? { reason: extra.reason } : {},
			...extra?.childStillRunning !== void 0 ? { childStillRunning: extra.childStillRunning } : {}
		};
		await this._finishAgentToolRun(this._agentToolRunInfoFromRow(row), result, { sequence });
		return result;
	}
	/**
	* Human-readable prose for an `interrupted` seal. Kept in sync with
	* {@link AgentToolInterruptedReason}; callers branch on the typed `reason`
	* field, not this string.
	*/
	_interruptedMessageForReason(reason) {
		switch (reason) {
			case "no-progress": return "Agent tool run was still running but made no forward progress within the re-attach no-progress budget; the parent gave up.";
			case "window-exceeded": return "Agent tool run did not reach a terminal result within the maximum re-attach window; the parent gave up.";
			case "not-tailable": return "Agent tool run was still running, but live-tail reattachment is not supported in this runtime.";
			case "inspect-timeout": return "Agent tool run inspection timed out during parent recovery.";
			case "inspect-failed": return "Agent tool run could not be inspected during parent recovery.";
			case "recovery-deadline": return "Agent tool run recovery deadline exceeded.";
			default: return "Agent tool run was still running and did not reach a terminal result.";
		}
	}
	/**
	* Tear down a child agent-tool run the parent has genuinely given up on
	* (#1630 follow-up). Teardown is scoped to `window-exceeded` ONLY — the hard
	* ceiling, where the child has had its full recovery window and is therefore
	* truly exhausted, so cancelling it reclaims its fiber / keep-alive. Every
	* other give-up is deliberately left repairable: `no-progress` seals stay
	* SOFT (`interrupted`, `childStillRunning: true`) so a re-issue can still
	* re-attach and collect the child if it self-heals — tearing those down would
	* defeat the repair-on-re-issue path and convert a retryable interrupt into a
	* non-retryable `aborted`. Reasons where the child's state is unknown
	* (`inspect-*`, `recovery-deadline`, `not-tailable`) are also left alone.
	* Returns whether the child was torn down (so the caller reports
	* `childStillRunning: false`).
	*/
	async _teardownGivenUpAgentToolChild(adapter, runId, reason) {
		if (reason !== "window-exceeded") return false;
		try {
			await adapter.cancelAgentToolRun(runId, `agent tool run given up by parent recovery: ${reason}`);
			return true;
		} catch {
			return false;
		}
	}
	/**
	* Re-attach to a still-running child agent-tool run and tail it to its real
	* terminal result, instead of abandoning it as `interrupted` (#1630). The
	* child is a separate facet with its own `chatRecovery`, so resolving it via
	* the adapter wakes it and lets it self-complete the interrupted turn; we tail
	* its live stream (forwarding chunks to the parent's connections) until it
	* reaches terminal, then inspect for the collected result.
	*
	* The wait is PROGRESS-KEYED, not a flat wall clock (which previously abandoned
	* healthy, still-advancing children whose recovery simply outran a fixed
	* budget). `noProgressTimeoutMs` bounds how long the parent waits with NO
	* forward progress; it is reset on every forwarded chunk. As long as the child
	* keeps streaming it is followed through to terminal. The loop also RE-ARMS
	* across stream-closes (a child re-evicted mid-recovery, or a tail that ends
	* before terminal) as long as the prior attempt made progress, so a child that
	* dies and recovers again during deploy churn is still collected. A genuinely
	* silent/hung child can never block recovery forever: it seals `interrupted`
	* after one `noProgressTimeoutMs` window. `maxWindowMs` is an OPTIONAL hard
	* wall-clock ceiling (default `Infinity` — uncapped, mirroring #1672's
	* `maxRecoveryWork`); set it finite to also bound a child that keeps
	* progressing, which seals `window-exceeded` and tears the child down.
	*
	* Returns the terminal `result` (and `completedAt`) when the child reaches a
	* terminal status, plus the advanced broadcast `sequence`. Returns
	* `{ result: undefined }` when there is no `tailAgentToolRun` adapter, the
	* child makes no progress within a full no-progress window, or the ceiling is
	* reached while the child is still non-terminal — the caller then seals
	* `interrupted`.
	*/
	async _reattachAgentToolRunToTerminal(adapter, row, sequence, noProgressTimeoutMs = DEFAULT_AGENT_TOOL_REATTACH_NO_PROGRESS_TIMEOUT_MS, maxWindowMs = DEFAULT_AGENT_TOOL_REATTACH_MAX_WINDOW_MS) {
		if (typeof adapter.tailAgentToolRun !== "function") return {
			sequence,
			reason: "not-tailable"
		};
		this._emit("agent_tool:recovery:reattach", {
			runId: row.run_id,
			agentType: row.agent_type,
			budgetMs: noProgressTimeoutMs
		});
		const collectTerminal = async (seq) => {
			let inspection = null;
			try {
				inspection = await adapter.inspectAgentToolRun(row.run_id);
			} catch {
				return null;
			}
			if (inspection && inspection.status !== "running" && inspection.status !== "starting") return {
				sequence: seq,
				result: this._terminalResultFromInspection(row.agent_type, inspection),
				completedAt: inspection.completedAt
			};
			return null;
		};
		let nextSequence = sequence;
		if (!(noProgressTimeoutMs > 0)) return await collectTerminal(nextSequence) ?? {
			sequence: nextSequence,
			reason: "no-progress"
		};
		const ceilingController = new AbortController();
		let ceilingTimer;
		if (maxWindowMs > 0 && Number.isFinite(maxWindowMs)) ceilingTimer = setTimeout(() => ceilingController.abort(), maxWindowMs);
		let reason = "no-progress";
		try {
			while (!ceilingController.signal.aborted) {
				let afterSequence = -1;
				try {
					const existing = await adapter.getAgentToolChunks(row.run_id);
					const last = existing[existing.length - 1];
					if (last) afterSequence = last.sequence;
				} catch {}
				const beforeSequence = nextSequence;
				let streamEnded = "idle";
				try {
					const stream = await adapter.tailAgentToolRun(row.run_id, { afterSequence });
					const forwarded = await this._forwardAgentToolStream(stream, row.parent_tool_call_id ?? void 0, row.run_id, nextSequence, ceilingController.signal, noProgressTimeoutMs);
					nextSequence = forwarded.next;
					streamEnded = forwarded.ended;
				} catch {}
				const terminal = await collectTerminal(nextSequence);
				if (terminal) return terminal;
				if (ceilingController.signal.aborted) {
					reason = "window-exceeded";
					break;
				}
				if (streamEnded !== "done") break;
				if (nextSequence <= beforeSequence) break;
			}
		} finally {
			if (ceilingTimer !== void 0) clearTimeout(ceilingTimer);
		}
		return {
			sequence: nextSequence,
			reason
		};
	}
	async _replayAgentToolRuns(connection) {
		const rows = this.sql`
      SELECT run_id, parent_tool_call_id, agent_type, input_preview, status,
             summary, output_json, error_message, interrupted_reason,
             child_still_running, display_metadata, display_order
      FROM cf_agent_tool_runs
      ORDER BY started_at ASC
    `;
		for (const row of rows) {
			const parentToolCallId = row.parent_tool_call_id ?? void 0;
			let sequence = 0;
			this._broadcastAgentToolEvent(parentToolCallId, sequence++, {
				kind: "started",
				runId: row.run_id,
				agentType: row.agent_type,
				inputPreview: this._parseAgentToolJson(row.input_preview),
				order: row.display_order,
				display: this._parseAgentToolJson(row.display_metadata)
			}, true, connection);
			try {
				sequence = await this._broadcastAgentToolStoredChunks(row, sequence, true, connection);
			} catch {}
			if (this._isAgentToolTerminal(row.status)) this._broadcastAgentToolTerminal(parentToolCallId, sequence, {
				runId: row.run_id,
				agentType: row.agent_type,
				status: row.status,
				output: this._parseAgentToolJson(row.output_json),
				summary: row.summary ?? void 0,
				error: row.error_message ?? void 0,
				...this._agentToolInterruptedExtrasFromRow(row)
			}, true, connection);
		}
	}
	async _reconcileAgentToolRuns(options) {
		const reattachTimeoutMs = options?.reattachTimeoutMs ?? this._resolvedOptions.agentToolReattachNoProgressTimeoutMs;
		const reattachMaxWindowMs = options?.reattachMaxWindowMs ?? this._resolvedOptions.agentToolReattachMaxWindowMs;
		const startedAt = Date.now();
		const totalTimeoutMs = options?.totalRecoveryTimeoutMs ?? DEFAULT_AGENT_TOOL_RECOVERY_TOTAL_TIMEOUT_MS;
		const deadlineAt = totalTimeoutMs > 0 ? startedAt + totalTimeoutMs : Number.POSITIVE_INFINITY;
		const deferredFinishes = [];
		const rows = this.sql`
      SELECT run_id, parent_tool_call_id, agent_type, input_preview, status,
             summary, output_json, error_message, interrupted_reason,
             child_still_running, display_metadata, display_order,
             started_at, completed_at
      FROM cf_agent_tool_runs
      WHERE status IN ('starting', 'running') AND detached = 0
      ORDER BY started_at ASC
    `;
		const runIds = options?.runIds !== void 0 ? new Set(options.runIds) : void 0;
		const recoveryRows = rows.filter((row) => !runIds || runIds.has(row.run_id));
		this._emit("agent_tool:recovery:begin", {
			runCount: recoveryRows.length,
			totalTimeoutMs
		});
		const finalizeRow = async (row, result, sequence, completedAt) => {
			this._emit("agent_tool:recovery:row", {
				runId: row.run_id,
				agentType: row.agent_type,
				status: result.status,
				reason: result.error,
				elapsedMs: Date.now() - startedAt
			});
			const deferredFinish = await this._finishAgentToolRun(this._agentToolRunInfoFromRow(row), result, {
				sequence,
				completedAt,
				deferFinishHook: options?.deferFinishHooks
			});
			if (deferredFinish) deferredFinishes.push(deferredFinish);
		};
		const reattachQueue = [];
		for (const row of recoveryRows) {
			const sequence = 1;
			const remainingMs = deadlineAt - Date.now();
			if (remainingMs <= 0) {
				this._emit("agent_tool:recovery:deadline", {
					runId: row.run_id,
					agentType: row.agent_type,
					elapsedMs: Date.now() - startedAt
				});
				await finalizeRow(row, {
					runId: row.run_id,
					agentType: row.agent_type,
					status: "interrupted",
					reason: "recovery-deadline",
					error: this._interruptedMessageForReason("recovery-deadline")
				}, sequence, void 0);
				continue;
			}
			const childTimeout = options?.childInspectionTimeoutMs ?? DEFAULT_AGENT_TOOL_RECOVERY_TIMEOUT_MS;
			const boundedChildTimeout = childTimeout > 0 ? Math.min(childTimeout, remainingMs) : remainingMs;
			const recovery = await this._inspectAgentToolRunForRecovery(row, sequence, boundedChildTimeout);
			if (recovery.status !== "inspected") {
				await finalizeRow(row, (() => {
					const reason = recovery.status === "timed-out" ? "inspect-timeout" : "inspect-failed";
					return {
						runId: row.run_id,
						agentType: row.agent_type,
						status: "interrupted",
						reason,
						error: this._interruptedMessageForReason(reason)
					};
				})(), sequence, void 0);
				continue;
			}
			const inspection = recovery.inspection;
			const stillRunning = !inspection || inspection.status === "running" || inspection.status === "starting";
			if (stillRunning && typeof recovery.adapter.tailAgentToolRun === "function") {
				reattachQueue.push({
					row,
					adapter: recovery.adapter
				});
				continue;
			}
			let sequenceAfterReplay = sequence;
			try {
				sequenceAfterReplay = await this._broadcastAgentToolStoredChunksFromAdapter(recovery.adapter, row, sequence, void 0, void 0, boundedChildTimeout);
			} catch {}
			if (stillRunning) await finalizeRow(row, {
				runId: row.run_id,
				agentType: row.agent_type,
				status: "interrupted",
				reason: "not-tailable",
				childStillRunning: true,
				error: this._interruptedMessageForReason("not-tailable")
			}, sequenceAfterReplay, void 0);
			else await finalizeRow(row, this._terminalResultFromInspection(row.agent_type, inspection), sequenceAfterReplay, inspection.completedAt);
		}
		await Promise.all(reattachQueue.map(async ({ row, adapter }) => {
			const reattach = await this._reattachAgentToolRunToTerminal(adapter, row, 1, reattachTimeoutMs, reattachMaxWindowMs);
			if (reattach.result) {
				await finalizeRow(row, reattach.result, reattach.sequence, reattach.completedAt);
				return;
			}
			const tornDown = await this._teardownGivenUpAgentToolChild(adapter, row.run_id, reattach.reason);
			await finalizeRow(row, {
				runId: row.run_id,
				agentType: row.agent_type,
				status: "interrupted",
				reason: reattach.reason,
				childStillRunning: !tornDown,
				error: this._interruptedMessageForReason(reattach.reason)
			}, reattach.sequence, reattach.completedAt);
		}));
		this._emit("agent_tool:recovery:complete", {
			runCount: recoveryRows.length,
			elapsedMs: Date.now() - startedAt
		});
		return deferredFinishes;
	}
	async _inspectAgentToolRunForRecovery(row, _sequence, timeoutMs = DEFAULT_AGENT_TOOL_RECOVERY_TIMEOUT_MS) {
		const inspect = (async () => {
			const child = await this._cf_resolveSubAgent(row.agent_type, row.run_id);
			const adapter = this._asAgentToolChildAdapter(child);
			return {
				status: "inspected",
				adapter,
				inspection: await adapter.inspectAgentToolRun(row.run_id)
			};
		})().catch(() => ({ status: "failed" }));
		if (timeoutMs <= 0) return inspect;
		let timeoutId;
		const timeout = new Promise((resolve) => {
			timeoutId = setTimeout(() => {
				resolve({ status: "timed-out" });
			}, timeoutMs);
		});
		const result = await Promise.race([inspect, timeout]);
		if (timeoutId !== void 0) clearTimeout(timeoutId);
		return result;
	}
	_scheduleAgentToolRunRecovery(options) {
		if (this._agentToolRunRecoveryPromise) return this._agentToolRunRecoveryPromise;
		if (options?.runIds && options.runIds.length === 0) return Promise.resolve();
		const recovery = (async () => {
			await new Promise((resolve) => setTimeout(resolve, 0));
			const recoveredAgentToolFinishes = await this._reconcileAgentToolRuns({
				deferFinishHooks: true,
				childInspectionTimeoutMs: options?.childInspectionTimeoutMs,
				totalRecoveryTimeoutMs: options?.totalRecoveryTimeoutMs,
				reattachTimeoutMs: options?.reattachTimeoutMs,
				reattachMaxWindowMs: options?.reattachMaxWindowMs,
				runIds: options?.runIds
			});
			await this._runDeferredAgentToolFinishHooks(recoveredAgentToolFinishes);
			if (this._hasOutstandingDetachedRuns()) await this._armDetachedBackbone();
		})().catch(async (error) => {
			this._emit("agent_tool:recovery:failed", { error: error instanceof Error ? error.message : String(error) });
			try {
				await this.onError(error);
			} catch {}
		}).finally(() => {
			this._agentToolRunRecoveryPromise = void 0;
		});
		this._agentToolRunRecoveryPromise = recovery;
		this.ctx.waitUntil(recovery);
		return recovery;
	}
	_agentToolRunRecoveryRunIds() {
		return this.sql`
      SELECT run_id
      FROM cf_agent_tool_runs
      WHERE status IN ('starting', 'running')
      ORDER BY started_at ASC
    `.map((row) => row.run_id);
	}
	async _getAgentToolChunksForRecovery(adapter, runId, timeoutMs) {
		const chunks = adapter.getAgentToolChunks(runId).catch(() => void 0);
		if (timeoutMs === void 0 || timeoutMs <= 0) return chunks;
		let timeoutId;
		const timeout = new Promise((resolve) => {
			timeoutId = setTimeout(() => resolve(void 0), timeoutMs);
		});
		const result = await Promise.race([chunks, timeout]);
		if (timeoutId !== void 0) clearTimeout(timeoutId);
		return result;
	}
	/**
	* Shared facet resolution — takes a CamelCase class name string
	* (matching `ctx.exports`) rather than a class reference. Both
	* `subAgent(cls, name)` and `_cf_invokeSubAgent(className, ...)`
	* funnel through here so registry bookkeeping and the
	* `_cf_initAsFacet` handshake are consistent.
	*
	* @internal
	*/
	_cf_resolveSubAgent(className, name) {
		return this._dynamicAgents.resolve(className, name);
	}
	/**
	* Run `body` in a fresh invocation scope with no native request/
	* connection context attached, so a child-facet RPC never sees
	* parent-owned I/O handles.
	* @internal
	*/
	_runFacetInitInvocation(body) {
		return runInInvocation({
			agent: this,
			connection: void 0,
			request: void 0,
			email: void 0
		}, body);
	}
	/**
	* Forcefully abort a running sub-agent. The child stops executing
	* immediately and will be restarted on next {@link subAgent} call.
	* Pending RPC calls receive the reason as an error.
	* Transitively aborts the child's own children.
	*
	* @experimental The API surface may change before stabilizing.
	*
	* @param cls The Agent subclass used when creating the child
	* @param name Name of the child to abort
	* @param reason Error thrown to pending/future RPC callers
	*
	* @deprecated Use {@link Agent.dynamicAgents | this.dynamicAgents.abort()} instead.
	*/
	abortSubAgent(cls, name, reason) {
		this.dynamicAgents.abort(cls, name, reason);
	}
	/**
	* Delete a sub-agent: abort it if running, then permanently wipe its
	* storage. Transitively deletes the child's own children.
	*
	* @experimental The API surface may change before stabilizing.
	*
	* @param cls The Agent subclass used when creating the child
	* @param name Name of the child to delete
	*
	* @deprecated Use {@link Agent.dynamicAgents | this.dynamicAgents.delete()} instead.
	*/
	deleteSubAgent(cls, name) {
		return this.dynamicAgents.delete(cls, name);
	}
	hasSubAgent(classOrName, name) {
		return typeof classOrName === "string" ? this.dynamicAgents.has(classOrName, name) : this.dynamicAgents.has(classOrName, name);
	}
	listSubAgents(classOrName) {
		if (typeof classOrName === "string" || classOrName === void 0) return this.dynamicAgents.list(classOrName);
		return this.dynamicAgents.list(classOrName);
	}
	/**
	* Destroy the Agent, removing all state and scheduled tasks.
	*
	* On a top-level agent: drops every table, clears the alarm, and
	* aborts the isolate.
	*
	* On a sub-agent (facet): delegates teardown to the immediate
	* parent so the parent-owned schedule rows for this sub-agent
	* (and any of its descendants) are cancelled, the parent's
	* `cf_agents_sub_agents` registry entry is cleared, and
	* `ctx.facets.delete` wipes the facet's own storage. The
	* `ctx.facets.delete` call aborts this isolate, so this method
	* may not return cleanly when invoked from inside the facet —
	* callers should treat it as fire-and-forget.
	*/
	async destroy() {
		if (this._isFacet) {
			this._emit("destroy");
			await (await this._rootAlarmOwner())._cf_destroyDescendantFacet(this.selfPath);
			return;
		}
		await this.ctx.storage.put(DESTROY_PENDING_KEY, true);
		await this.lifecycle.disableAlarms();
		await this.lifecycle.dispose();
		this._disposables.dispose();
		await this.ctx.storage.deleteAll();
		this._destroyed = true;
		setTimeout(() => {
			abortWithoutAlarmRetry(this.ctx, "destroyed");
		}, 0);
		this._emit("destroy");
	}
	/**
	* @internal Defer this agent's destruction to its own alarm invocation
	* instead of running it inline (#1625).
	*
	* `destroy()` is a multi-step I/O sequence (drop tables, delete alarm,
	* delete all storage, dispose connections). Running it on the `waitUntil`
	* of a request whose client has already disconnected — the MCP
	* Streamable-HTTP session-DELETE path — gives it little to no
	* post-invocation grace, so the runtime routinely cancels it mid-flight.
	* This method instead performs two fast storage writes (a durable
	* "condemned" marker and an immediate alarm) that the caller can await
	* before responding; the alarm then fires as a fresh invocation with its
	* own full execution budget and runs `destroy()` there. If even that
	* invocation is interrupted, the marker survives and the next wake
	* finishes teardown — see the `alarm()` preamble.
	*
	* Unlike `destroy()`, this method does not abort the isolate, so RPC
	* callers don't need to swallow an abort error.
	*/
	async _cf_scheduleDestroy() {
		await this.__unsafe_ensureInitialized();
		if (this._isFacet) {
			await this.destroy();
			return;
		}
		const destroyAt = Date.now() + DESTROY_ALARM_DELAY_MS;
		await this.ctx.storage.put(DESTROY_PENDING_KEY, destroyAt);
		await this.lifecycle.jobs.push({
			id: HOST_JOB_DESTROY_ID,
			fn: "destroy",
			time: destroyAt,
			exclusive: true
		});
	}
	/**
	* Whether a (deferred or interrupted) destroy is pending. Reads the
	* durable marker directly — the in-memory `_isFacet` flag may not be
	* hydrated yet at the call sites, but facets never write the marker.
	*/
	async _pendingDestroyAlarm() {
		const pending = await this.ctx.storage.get(DESTROY_PENDING_KEY);
		if (typeof pending === "number") return pending;
		return pending === true ? Date.now() : null;
	}
	async _hasPendingDestroy() {
		return await this._pendingDestroyAlarm() !== null;
	}
	/**
	* Check if a method is callable
	* @param method The method name to check
	* @returns True if the method is marked as callable
	*/
	_isCallable(method) {
		return isCallableMethod(this[method]);
	}
	/**
	* Get all methods marked as callable on this Agent
	* @returns A map of method names to their metadata
	*/
	getCallableMethods() {
		return new Map(decoratedMethods(this));
	}
	/**
	* Start a workflow and track it in this Agent's database.
	* Automatically injects agent identity into the workflow params.
	*
	* The originating Agent identity is persisted in the workflow params so
	* callbacks (`this.agent` RPC, progress/completion/error, state updates)
	* route back to the exact Agent or sub-agent facet that started the run.
	* Note the following constraints:
	*
	* - **Resolution is by name.** Callbacks re-resolve the originating Agent via
	*   `getAgentByName(...)`. Agents addressed by a raw Durable Object id
	*   (`idFromString`/`get(id)`) rather than by name will not receive
	*   callbacks on the same instance.
	* - **Sub-agent runs are facet-local.** A workflow started from a sub-agent
	*   is tracked in that facet's own storage; the parent's `getWorkflows()` /
	*   `getWorkflowById()` do not see it. Aggregate across facets yourself if
	*   you need a combined view.
	* - **Class names must survive bundling.** The originating path is keyed by
	*   `constructor.name`. Ensure your bundler preserves class names
	*   (e.g. esbuild `keepNames: true`) so callbacks can be routed.
	*
	* @template P - Type of params to pass to the workflow
	* @param workflowName - Name of the workflow binding in env (e.g., 'MY_WORKFLOW')
	* @param params - Params to pass to the workflow
	* @param options - Optional workflow options. For sub-agents, pass
	*   `agentBinding` as the **root** Agent's Durable Object binding name, not a
	*   child binding.
	* @returns The workflow instance ID
	*
	* @example
	* ```typescript
	* const workflowId = await this.runWorkflow(
	*   'MY_WORKFLOW',
	*   { taskId: '123', data: 'process this' }
	* );
	* ```
	*/
	async runWorkflow(workflowName, params, options) {
		const workflow = this._findWorkflowBindingByName(workflowName);
		if (!workflow) throw new Error(`Workflow binding '${workflowName}' not found in environment`);
		const agentOrigin = this._workflowOrigin(options);
		if (!agentOrigin) throw new Error("Could not detect Agent binding name from class name. Pass it explicitly via options.agentBinding");
		const workflowId = options?.id ?? `wf_${nanoid()}`;
		const augmentedParams = {
			...params,
			__agentName: this.name,
			__agentBinding: agentOrigin.kind === "agent" ? agentOrigin.binding : agentOrigin.rootBinding,
			__workflowName: workflowName,
			__agentOrigin: agentOrigin
		};
		const instance = await workflow.create({
			id: workflowId,
			params: augmentedParams,
			retention: options?.retention
		});
		const id = nanoid();
		const metadataJson = options?.metadata ? JSON.stringify(options.metadata) : null;
		try {
			this.sql`
        INSERT INTO cf_agents_workflows (id, workflow_id, workflow_name, status, metadata)
        VALUES (${id}, ${instance.id}, ${workflowName}, 'queued', ${metadataJson})
      `;
		} catch (e) {
			if (e instanceof Error && e.message.includes("UNIQUE constraint failed")) throw new Error(`Workflow with ID "${workflowId}" is already being tracked`);
			throw e;
		}
		this._emit("workflow:start", {
			workflowId: instance.id,
			workflowName
		});
		return instance.id;
	}
	/**
	* Send an event to a running workflow.
	* The workflow can wait for this event using step.waitForEvent().
	*
	* @param workflowName - Name of the workflow binding in env (e.g., 'MY_WORKFLOW')
	* @param workflowId - ID of the workflow instance
	* @param event - Event to send
	*
	* @example
	* ```typescript
	* await this.sendWorkflowEvent(
	*   'MY_WORKFLOW',
	*   workflowId,
	*   { type: 'approval', payload: { approved: true } }
	* );
	* ```
	*/
	async sendWorkflowEvent(workflowName, workflowId, event) {
		const workflow = this._findWorkflowBindingByName(workflowName);
		if (!workflow) throw new Error(`Workflow binding '${workflowName}' not found in environment`);
		const instance = await workflow.get(workflowId);
		await tryN(3, async () => instance.sendEvent(event), {
			shouldRetry: isErrorRetryable,
			baseDelayMs: 200,
			maxDelayMs: 3e3
		});
		this._emit("workflow:event", {
			workflowId,
			eventType: event.type
		});
	}
	/**
	* Approve a waiting workflow.
	* Sends an approval event to the workflow that can be received by waitForApproval().
	*
	* @param workflowId - ID of the workflow to approve
	* @param data - Optional approval data (reason, metadata)
	*
	* @example
	* ```typescript
	* await this.approveWorkflow(workflowId, {
	*   reason: 'Approved by admin',
	*   metadata: { approvedBy: userId }
	* });
	* ```
	*/
	async approveWorkflow(workflowId, data) {
		const workflowInfo = this.getWorkflow(workflowId);
		if (!workflowInfo) throw new Error(`Workflow ${workflowId} not found in tracking table`);
		await this.sendWorkflowEvent(workflowInfo.workflowName, workflowId, {
			type: "approval",
			payload: {
				approved: true,
				reason: data?.reason,
				metadata: data?.metadata
			}
		});
		this._emit("workflow:approved", {
			workflowId,
			reason: data?.reason
		});
	}
	/**
	* Reject a waiting workflow.
	* Sends a rejection event to the workflow that will cause waitForApproval() to throw.
	*
	* @param workflowId - ID of the workflow to reject
	* @param data - Optional rejection data (reason)
	*
	* @example
	* ```typescript
	* await this.rejectWorkflow(workflowId, {
	*   reason: 'Request denied by admin'
	* });
	* ```
	*/
	async rejectWorkflow(workflowId, data) {
		const workflowInfo = this.getWorkflow(workflowId);
		if (!workflowInfo) throw new Error(`Workflow ${workflowId} not found in tracking table`);
		await this.sendWorkflowEvent(workflowInfo.workflowName, workflowId, {
			type: "approval",
			payload: {
				approved: false,
				reason: data?.reason
			}
		});
		this._emit("workflow:rejected", {
			workflowId,
			reason: data?.reason
		});
	}
	/**
	* Terminate a running workflow.
	* This immediately stops the workflow and sets its status to "terminated".
	*
	* @param workflowId - ID of the workflow to terminate (must be tracked via runWorkflow)
	* @throws Error if workflow not found in tracking table
	* @throws Error if workflow binding not found in environment
	* @throws Error if workflow is already completed/errored/terminated (from Cloudflare)
	*
	* @example
	* ```typescript
	* await this.terminateWorkflow(workflowId);
	* ```
	*/
	async terminateWorkflow(workflowId) {
		const workflowInfo = this.getWorkflow(workflowId);
		if (!workflowInfo) throw new Error(`Workflow ${workflowId} not found in tracking table`);
		const workflow = this._findWorkflowBindingByName(workflowInfo.workflowName);
		if (!workflow) throw new Error(`Workflow binding '${workflowInfo.workflowName}' not found in environment`);
		const instance = await workflow.get(workflowId);
		await tryN(3, async () => instance.terminate(), {
			shouldRetry: isErrorRetryable,
			baseDelayMs: 200,
			maxDelayMs: 3e3
		});
		const status = await instance.status();
		this._updateWorkflowTracking(workflowId, status);
		this._emit("workflow:terminated", {
			workflowId,
			workflowName: workflowInfo.workflowName
		});
	}
	/**
	* Pause a running workflow.
	* The workflow can be resumed later with resumeWorkflow().
	*
	* @param workflowId - ID of the workflow to pause (must be tracked via runWorkflow)
	* @throws Error if workflow not found in tracking table
	* @throws Error if workflow binding not found in environment
	* @throws Error if workflow is not running (from Cloudflare)
	*
	* @example
	* ```typescript
	* await this.pauseWorkflow(workflowId);
	* ```
	*/
	async pauseWorkflow(workflowId) {
		const workflowInfo = this.getWorkflow(workflowId);
		if (!workflowInfo) throw new Error(`Workflow ${workflowId} not found in tracking table`);
		const workflow = this._findWorkflowBindingByName(workflowInfo.workflowName);
		if (!workflow) throw new Error(`Workflow binding '${workflowInfo.workflowName}' not found in environment`);
		const instance = await workflow.get(workflowId);
		await tryN(3, async () => instance.pause(), {
			shouldRetry: isErrorRetryable,
			baseDelayMs: 200,
			maxDelayMs: 3e3
		});
		const status = await instance.status();
		this._updateWorkflowTracking(workflowId, status);
		this._emit("workflow:paused", {
			workflowId,
			workflowName: workflowInfo.workflowName
		});
	}
	/**
	* Resume a paused workflow.
	*
	* @param workflowId - ID of the workflow to resume (must be tracked via runWorkflow)
	* @throws Error if workflow not found in tracking table
	* @throws Error if workflow binding not found in environment
	* @throws Error if workflow is not paused (from Cloudflare)
	*
	* @example
	* ```typescript
	* await this.resumeWorkflow(workflowId);
	* ```
	*/
	async resumeWorkflow(workflowId) {
		const workflowInfo = this.getWorkflow(workflowId);
		if (!workflowInfo) throw new Error(`Workflow ${workflowId} not found in tracking table`);
		const workflow = this._findWorkflowBindingByName(workflowInfo.workflowName);
		if (!workflow) throw new Error(`Workflow binding '${workflowInfo.workflowName}' not found in environment`);
		const instance = await workflow.get(workflowId);
		await tryN(3, async () => instance.resume(), {
			shouldRetry: isErrorRetryable,
			baseDelayMs: 200,
			maxDelayMs: 3e3
		});
		const status = await instance.status();
		this._updateWorkflowTracking(workflowId, status);
		this._emit("workflow:resumed", {
			workflowId,
			workflowName: workflowInfo.workflowName
		});
	}
	/**
	* Restart a workflow instance.
	* This re-runs the workflow from the beginning with the same ID.
	*
	* @param workflowId - ID of the workflow to restart (must be tracked via runWorkflow)
	* @param options - Optional settings
	* @param options.resetTracking - If true (default), resets created_at and clears error fields.
	*                                If false, preserves original timestamps.
	* @throws Error if workflow not found in tracking table
	* @throws Error if workflow binding not found in environment
	*
	* @example
	* ```typescript
	* // Reset tracking (default)
	* await this.restartWorkflow(workflowId);
	*
	* // Preserve original timestamps
	* await this.restartWorkflow(workflowId, { resetTracking: false });
	* ```
	*/
	async restartWorkflow(workflowId, options = {}) {
		const { resetTracking = true } = options;
		const workflowInfo = this.getWorkflow(workflowId);
		if (!workflowInfo) throw new Error(`Workflow ${workflowId} not found in tracking table`);
		const workflow = this._findWorkflowBindingByName(workflowInfo.workflowName);
		if (!workflow) throw new Error(`Workflow binding '${workflowInfo.workflowName}' not found in environment`);
		const instance = await workflow.get(workflowId);
		await tryN(3, async () => instance.restart(), {
			shouldRetry: isErrorRetryable,
			baseDelayMs: 200,
			maxDelayMs: 3e3
		});
		if (resetTracking) {
			const now = Math.floor(Date.now() / 1e3);
			this.sql`
        UPDATE cf_agents_workflows
        SET status = 'queued',
            created_at = ${now},
            updated_at = ${now},
            completed_at = NULL,
            error_name = NULL,
            error_message = NULL
        WHERE workflow_id = ${workflowId}
      `;
		} else {
			const status = await instance.status();
			this._updateWorkflowTracking(workflowId, status);
		}
		this._emit("workflow:restarted", {
			workflowId,
			workflowName: workflowInfo.workflowName
		});
	}
	/**
	* Find a workflow binding by its name.
	*/
	_findWorkflowBindingByName(workflowName) {
		const binding = this.env[workflowName];
		if (binding && typeof binding === "object" && "create" in binding && "get" in binding) return binding;
	}
	/**
	* Get all workflow binding names from the environment.
	*/
	_getWorkflowBindingNames() {
		const names = [];
		for (const [key, value] of Object.entries(this.env)) if (value && typeof value === "object" && "create" in value && "get" in value) names.push(key);
		return names;
	}
	/**
	* Get the status of a workflow and update the tracking record.
	*
	* @param workflowName - Name of the workflow binding in env (e.g., 'MY_WORKFLOW')
	* @param workflowId - ID of the workflow instance
	* @returns The workflow status
	*/
	async getWorkflowStatus(workflowName, workflowId) {
		const workflow = this._findWorkflowBindingByName(workflowName);
		if (!workflow) throw new Error(`Workflow binding '${workflowName}' not found in environment`);
		const status = await (await workflow.get(workflowId)).status();
		this._updateWorkflowTracking(workflowId, status);
		return status;
	}
	/**
	* Get a tracked workflow by ID.
	*
	* @param workflowId - Workflow instance ID
	* @returns Workflow info or undefined if not found
	*/
	getWorkflow(workflowId) {
		const rows = this.sql`
      SELECT * FROM cf_agents_workflows WHERE workflow_id = ${workflowId}
    `;
		if (!rows || rows.length === 0) return;
		return this._rowToWorkflowInfo(rows[0]);
	}
	/**
	* Query tracked workflows with cursor-based pagination.
	*
	* @param criteria - Query criteria including optional cursor for pagination
	* @returns WorkflowPage with workflows, total count, and next cursor
	*
	* @example
	* ```typescript
	* // First page
	* const page1 = this.getWorkflows({ status: 'running', limit: 20 });
	*
	* // Next page
	* if (page1.nextCursor) {
	*   const page2 = this.getWorkflows({
	*     status: 'running',
	*     limit: 20,
	*     cursor: page1.nextCursor
	*   });
	* }
	* ```
	*/
	getWorkflows(criteria = {}) {
		const limit = Math.min(criteria.limit ?? 50, 100);
		const isAsc = criteria.orderBy === "asc";
		const total = this._countWorkflows(criteria);
		let query = "SELECT * FROM cf_agents_workflows WHERE 1=1";
		const params = [];
		if (criteria.status) {
			const statuses = Array.isArray(criteria.status) ? criteria.status : [criteria.status];
			const placeholders = statuses.map(() => "?").join(", ");
			query += ` AND status IN (${placeholders})`;
			params.push(...statuses);
		}
		if (criteria.workflowName) {
			query += " AND workflow_name = ?";
			params.push(criteria.workflowName);
		}
		if (criteria.metadata) for (const [key, value] of Object.entries(criteria.metadata)) {
			query += ` AND json_extract(metadata, '$.' || ?) = ?`;
			params.push(key, value);
		}
		if (criteria.cursor) {
			const cursor = this._decodeCursor(criteria.cursor);
			if (isAsc) query += " AND (created_at > ? OR (created_at = ? AND workflow_id > ?))";
			else query += " AND (created_at < ? OR (created_at = ? AND workflow_id < ?))";
			params.push(cursor.createdAt, cursor.createdAt, cursor.workflowId);
		}
		query += ` ORDER BY created_at ${isAsc ? "ASC" : "DESC"}, workflow_id ${isAsc ? "ASC" : "DESC"}`;
		query += " LIMIT ?";
		params.push(limit + 1);
		const rows = this.ctx.storage.sql.exec(query, ...params).toArray();
		const hasMore = rows.length > limit;
		const workflows = (hasMore ? rows.slice(0, limit) : rows).map((row) => this._rowToWorkflowInfo(row));
		return {
			workflows,
			total,
			nextCursor: hasMore && workflows.length > 0 ? this._encodeCursor(workflows[workflows.length - 1]) : null
		};
	}
	/**
	* Count workflows matching criteria (for pagination total).
	*/
	_countWorkflows(criteria) {
		let query = "SELECT COUNT(*) as count FROM cf_agents_workflows WHERE 1=1";
		const params = [];
		if (criteria.status) {
			const statuses = Array.isArray(criteria.status) ? criteria.status : [criteria.status];
			const placeholders = statuses.map(() => "?").join(", ");
			query += ` AND status IN (${placeholders})`;
			params.push(...statuses);
		}
		if (criteria.workflowName) {
			query += " AND workflow_name = ?";
			params.push(criteria.workflowName);
		}
		if (criteria.metadata) for (const [key, value] of Object.entries(criteria.metadata)) {
			query += ` AND json_extract(metadata, '$.' || ?) = ?`;
			params.push(key, value);
		}
		if (criteria.createdBefore) {
			query += " AND created_at < ?";
			params.push(Math.floor(criteria.createdBefore.getTime() / 1e3));
		}
		return this.ctx.storage.sql.exec(query, ...params).toArray()[0]?.count ?? 0;
	}
	/**
	* Encode a cursor from workflow info for pagination.
	* Stores createdAt as Unix timestamp in seconds (matching DB storage).
	*/
	_encodeCursor(workflow) {
		return btoa(JSON.stringify({
			c: Math.floor(workflow.createdAt.getTime() / 1e3),
			i: workflow.workflowId
		}));
	}
	/**
	* Decode a pagination cursor.
	* Returns createdAt as Unix timestamp in seconds (matching DB storage).
	*/
	_decodeCursor(cursor) {
		try {
			const data = JSON.parse(atob(cursor));
			if (typeof data.c !== "number" || typeof data.i !== "string") throw new Error("Invalid cursor structure");
			return {
				createdAt: data.c,
				workflowId: data.i
			};
		} catch {
			throw new Error("Invalid pagination cursor. The cursor may be malformed or corrupted.");
		}
	}
	/**
	* Delete a workflow tracking record.
	*
	* @param workflowId - ID of the workflow to delete
	* @returns true if a record was deleted, false if not found
	*/
	deleteWorkflow(workflowId) {
		const existing = this.sql`
      SELECT COUNT(*) as count FROM cf_agents_workflows WHERE workflow_id = ${workflowId}
    `;
		if (!existing[0] || existing[0].count === 0) return false;
		this.sql`DELETE FROM cf_agents_workflows WHERE workflow_id = ${workflowId}`;
		return true;
	}
	/**
	* Delete workflow tracking records matching criteria.
	* Useful for cleaning up old completed/errored workflows.
	*
	* @param criteria - Criteria for which workflows to delete
	* @returns Number of records matching criteria (expected deleted count)
	*
	* @example
	* ```typescript
	* // Delete all completed workflows created more than 7 days ago
	* const deleted = this.deleteWorkflows({
	*   status: 'complete',
	*   createdBefore: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
	* });
	*
	* // Delete all errored and terminated workflows
	* const deleted = this.deleteWorkflows({
	*   status: ['errored', 'terminated']
	* });
	* ```
	*/
	deleteWorkflows(criteria = {}) {
		let query = "DELETE FROM cf_agents_workflows WHERE 1=1";
		const params = [];
		if (criteria.status) {
			const statuses = Array.isArray(criteria.status) ? criteria.status : [criteria.status];
			const placeholders = statuses.map(() => "?").join(", ");
			query += ` AND status IN (${placeholders})`;
			params.push(...statuses);
		}
		if (criteria.workflowName) {
			query += " AND workflow_name = ?";
			params.push(criteria.workflowName);
		}
		if (criteria.metadata) for (const [key, value] of Object.entries(criteria.metadata)) {
			query += ` AND json_extract(metadata, '$.' || ?) = ?`;
			params.push(key, value);
		}
		if (criteria.createdBefore) {
			query += " AND created_at < ?";
			params.push(Math.floor(criteria.createdBefore.getTime() / 1e3));
		}
		return this.ctx.storage.sql.exec(query, ...params).rowsWritten;
	}
	/**
	* Migrate workflow tracking records from an old binding name to a new one.
	* Use this after renaming a workflow binding in wrangler.toml.
	*
	* @param oldName - Previous workflow binding name
	* @param newName - New workflow binding name
	* @returns Number of records migrated
	*
	* @example
	* ```typescript
	* // After renaming OLD_WORKFLOW to NEW_WORKFLOW in wrangler.toml
	* async onStart() {
	*   const migrated = this.migrateWorkflowBinding('OLD_WORKFLOW', 'NEW_WORKFLOW');
	* }
	* ```
	*/
	migrateWorkflowBinding(oldName, newName) {
		if (!this._findWorkflowBindingByName(newName)) throw new Error(`Workflow binding '${newName}' not found in environment`);
		const count = this.sql`
      SELECT COUNT(*) as count FROM cf_agents_workflows WHERE workflow_name = ${oldName}
    `[0]?.count ?? 0;
		if (count > 0) {
			this.sql`UPDATE cf_agents_workflows SET workflow_name = ${newName} WHERE workflow_name = ${oldName}`;
			console.log(`[Agent] Migrated ${count} workflow(s) from '${oldName}' to '${newName}'`);
		}
		return count;
	}
	/**
	* Update workflow tracking record from InstanceStatus
	*/
	_updateWorkflowTracking(workflowId, status) {
		const statusName = status.status;
		const now = Math.floor(Date.now() / 1e3);
		const completedAt = [
			"complete",
			"errored",
			"terminated"
		].includes(statusName) ? now : null;
		const errorName = status.error?.name ?? null;
		const errorMessage = status.error?.message ?? null;
		this.sql`
      UPDATE cf_agents_workflows
      SET status = ${statusName},
          error_name = ${errorName},
          error_message = ${errorMessage},
          updated_at = ${now},
          completed_at = ${completedAt}
      WHERE workflow_id = ${workflowId}
    `;
	}
	/**
	* Convert a database row to WorkflowInfo
	*/
	_rowToWorkflowInfo(row) {
		return {
			id: row.id,
			workflowId: row.workflow_id,
			workflowName: row.workflow_name,
			status: row.status,
			metadata: row.metadata ? JSON.parse(row.metadata) : null,
			error: row.error_name ? {
				name: row.error_name,
				message: row.error_message ?? ""
			} : null,
			createdAt: /* @__PURE__ */ new Date(row.created_at * 1e3),
			updatedAt: /* @__PURE__ */ new Date(row.updated_at * 1e3),
			completedAt: row.completed_at ? /* @__PURE__ */ new Date(row.completed_at * 1e3) : null
		};
	}
	_workflowOrigin(options) {
		if (this._isFacet) {
			const root = this._parentPath[0];
			const rootBindingName = options?.agentBinding ?? (root ? this._findAgentBindingNameForClass(root.className) : void 0);
			if (!rootBindingName) return void 0;
			return {
				kind: "facet",
				version: 1,
				rootBinding: rootBindingName,
				path: this.selfPath.map((step) => ({ ...step }))
			};
		}
		const agentBindingName = options?.agentBinding ?? this._findAgentBindingNameForClass(this._ParentClass.name);
		if (!agentBindingName) return void 0;
		return {
			kind: "agent",
			version: 1,
			binding: agentBindingName,
			name: this.name
		};
	}
	_findAgentBindingNameForClass(className) {
		for (const [key, value] of Object.entries(this.env)) if (value && typeof value === "object" && "idFromName" in value && typeof value.idFromName === "function") {
			if (key === className || camelCaseToKebabCase(key) === camelCaseToKebabCase(className)) return key;
		}
	}
	_findBindingNameForNamespace(namespace) {
		for (const [key, value] of Object.entries(this.env)) if (value === namespace) return key;
	}
	/**
	* Handle a callback from a workflow.
	* Invoked via the internal `_workflow_handleCallback` RPC whenever an
	* {@link AgentWorkflow} reports progress, completion, an error, or a custom
	* event back to its originating Agent (or sub-agent facet).
	* Override this to handle all callback types in one place.
	*
	* @param callback - The callback payload
	*/
	async onWorkflowCallback(callback) {
		const now = Math.floor(Date.now() / 1e3);
		switch (callback.type) {
			case "progress":
				this.sql`
          UPDATE cf_agents_workflows
          SET status = 'running', updated_at = ${now}
          WHERE workflow_id = ${callback.workflowId} AND status IN ('queued', 'waiting')
        `;
				await this.onWorkflowProgress(callback.workflowName, callback.workflowId, callback.progress);
				break;
			case "complete":
				this.sql`
          UPDATE cf_agents_workflows
          SET status = 'complete', updated_at = ${now}, completed_at = ${now}
          WHERE workflow_id = ${callback.workflowId}
            AND status NOT IN ('terminated', 'paused')
        `;
				await this.onWorkflowComplete(callback.workflowName, callback.workflowId, callback.result);
				break;
			case "error":
				this.sql`
          UPDATE cf_agents_workflows
          SET status = 'errored', updated_at = ${now}, completed_at = ${now},
              error_name = 'WorkflowError', error_message = ${callback.error}
          WHERE workflow_id = ${callback.workflowId}
            AND status NOT IN ('terminated', 'paused')
        `;
				await this.onWorkflowError(callback.workflowName, callback.workflowId, callback.error);
				break;
			case "event":
				await this.onWorkflowEvent(callback.workflowName, callback.workflowId, callback.event);
				break;
		}
	}
	/**
	* Called when a workflow reports progress.
	* Override to handle progress updates.
	*
	* @param workflowName - Workflow binding name
	* @param workflowId - ID of the workflow
	* @param progress - Typed progress data (default: DefaultProgress)
	*/
	async onWorkflowProgress(workflowName, workflowId, progress) {}
	/**
	* Called when a workflow completes successfully.
	* Override to handle completion.
	*
	* @param workflowName - Workflow binding name
	* @param workflowId - ID of the workflow
	* @param result - Optional result data
	*/
	async onWorkflowComplete(workflowName, workflowId, result) {}
	/**
	* Called when a workflow encounters an error.
	* Override to handle errors.
	*
	* @param workflowName - Workflow binding name
	* @param workflowId - ID of the workflow
	* @param error - Error message
	*/
	async onWorkflowError(workflowName, workflowId, error) {
		console.error(`Workflow error [${workflowName}/${workflowId}]: ${error}\nOverride onWorkflowError() in your Agent to handle workflow errors.`);
	}
	/**
	* Called when a workflow sends a custom event.
	* Override to handle custom events.
	*
	* @param workflowName - Workflow binding name
	* @param workflowId - ID of the workflow
	* @param event - Custom event payload
	*/
	async onWorkflowEvent(workflowName, workflowId, event) {}
	/**
	* Handle a workflow callback via RPC.
	* @internal - Called by AgentWorkflow, do not call directly
	*/
	async _workflow_handleCallback(callback) {
		await this.__unsafe_ensureInitialized();
		await this.onWorkflowCallback(callback);
	}
	/**
	* Broadcast a message to all connected clients via RPC.
	* @internal - Called by AgentWorkflow, do not call directly
	*/
	async _workflow_broadcast(message) {
		await this.__unsafe_ensureInitialized();
		this.broadcast(JSON.stringify(message));
	}
	/**
	* Update agent state via RPC.
	* @internal - Called by AgentWorkflow, do not call directly
	*/
	async _workflow_updateState(action, state) {
		await this.__unsafe_ensureInitialized();
		if (action === "set") this.setState(state);
		else if (action === "merge") {
			const currentState = this.state ?? {};
			this.setState({
				...currentState,
				...state
			});
		} else if (action === "reset") this.setState(this.initialState);
	}
	async addMcpServer(serverName, urlOrBinding, callbackHostOrOptions, agentsPrefix, options) {
		const isHttpTransport = typeof urlOrBinding === "string";
		const normalizedUrl = isHttpTransport ? new URL(urlOrBinding).href : void 0;
		let requestedId;
		if (typeof callbackHostOrOptions === "object" && callbackHostOrOptions !== null && typeof callbackHostOrOptions.id === "string") {
			const rawId = callbackHostOrOptions.id;
			requestedId = normalizeServerId(rawId);
		}
		const allServers = this.mcp.listServers();
		const existingServer = allServers.find((s) => s.name === serverName && (!isHttpTransport || new URL(s.server_url).href === normalizedUrl));
		if (requestedId) {
			const idConflict = allServers.find((s) => {
				if (s.id !== requestedId) return false;
				if (s.name !== serverName) return true;
				if (isHttpTransport) return new URL(s.server_url).href !== normalizedUrl;
				return false;
			});
			if (idConflict) throw new Error(`MCP server id "${requestedId}" is already in use by server "${idConflict.name}" (${idConflict.server_url}). Stable ids must be unique per (name, url).`);
			if (existingServer && existingServer.id !== requestedId) {
				await this.mcp.migrateServerId(existingServer.id, requestedId, this.name);
				existingServer.id = requestedId;
			}
		}
		if (existingServer && this.mcp.mcpConnections[existingServer.id]) {
			const conn = this.mcp.mcpConnections[existingServer.id];
			if (conn.connectionState === MCPConnectionState.AUTHENTICATING) {
				const authProvider = conn.options.transport.authProvider;
				const authUrl = await this._redeemableAuthUrl(existingServer.id, authProvider?.authUrl, authProvider) ?? await this._redeemableAuthUrl(existingServer.id, existingServer.auth_url, authProvider);
				if (authUrl) return {
					id: existingServer.id,
					state: MCPConnectionState.AUTHENTICATING,
					authUrl
				};
				const reconnectResult = await this.mcp.connectToServer(existingServer.id);
				if (reconnectResult.state === MCPConnectionState.AUTHENTICATING) {
					if (!reconnectResult.authUrl) throw new Error("OAuth configuration incomplete: missing authUrl");
					return {
						id: existingServer.id,
						state: reconnectResult.state,
						authUrl: reconnectResult.authUrl
					};
				}
				if (reconnectResult.state === MCPConnectionState.CONNECTED) {
					const discoverResult = await this.mcp.discoverIfConnected(existingServer.id);
					if (!discoverResult?.success) throw new Error(`Failed to discover MCP server capabilities: ${discoverResult?.error ?? "connection not found"}`);
					return {
						id: existingServer.id,
						state: MCPConnectionState.READY
					};
				}
				throw new Error(`Failed to connect to MCP server at ${normalizedUrl}: ${reconnectResult.error}`);
			}
			if (conn.connectionState === MCPConnectionState.FAILED) throw new Error(`MCP server "${serverName}" is in failed state: ${conn.connectionError}`);
			return {
				id: existingServer.id,
				state: MCPConnectionState.READY
			};
		}
		if (typeof urlOrBinding !== "string") {
			const rpcOpts = callbackHostOrOptions;
			const normalizedName = serverName.toLowerCase().replace(/\s+/g, "-");
			const reconnectId = requestedId ?? existingServer?.id;
			const { id } = await this.mcp.connect(`${RPC_DO_PREFIX}${normalizedName}`, {
				reconnect: reconnectId ? { id: reconnectId } : void 0,
				transport: {
					type: "rpc",
					namespace: urlOrBinding,
					name: normalizedName,
					props: rpcOpts?.props
				}
			});
			const conn = this.mcp.mcpConnections[id];
			if (conn && conn.connectionState === MCPConnectionState.CONNECTED) {
				const discoverResult = await this.mcp.discoverIfConnected(id);
				if (discoverResult && !discoverResult.success) throw new Error(`Failed to discover MCP server capabilities: ${discoverResult.error}`);
			} else if (conn && conn.connectionState === MCPConnectionState.FAILED) throw new Error(`Failed to connect to MCP server "${serverName}" via RPC: ${conn.connectionError}`);
			const bindingName = this._findBindingNameForNamespace(urlOrBinding);
			if (bindingName) this.mcp.saveRpcServerToStorage(id, serverName, normalizedName, bindingName, rpcOpts?.props);
			return {
				id,
				state: MCPConnectionState.READY
			};
		}
		const httpOptions = callbackHostOrOptions;
		let resolvedCallbackHost;
		let resolvedAgentsPrefix;
		let resolvedOptions;
		let resolvedCallbackPath;
		if (typeof httpOptions === "object" && httpOptions !== null) {
			resolvedCallbackHost = httpOptions.callbackHost;
			resolvedCallbackPath = httpOptions.callbackPath;
			resolvedAgentsPrefix = httpOptions.agentsPrefix ?? "agents";
			resolvedOptions = {
				client: httpOptions.client,
				transport: httpOptions.transport,
				retry: httpOptions.retry
			};
		} else {
			resolvedCallbackHost = httpOptions;
			resolvedAgentsPrefix = agentsPrefix ?? "agents";
			resolvedOptions = options;
		}
		if (!this._resolvedOptions.sendIdentityOnConnect && resolvedCallbackHost && !resolvedCallbackPath) throw new Error("callbackPath is required in addMcpServer options when sendIdentityOnConnect is false — the default callback URL would expose the instance name. Provide a callbackPath and route the callback request to this agent via getAgentByName.");
		if (!resolvedCallbackHost) {
			const { request, connection } = getCurrentAgent();
			if (request) {
				const requestUrl = new URL(request.url);
				resolvedCallbackHost = `${requestUrl.protocol}//${requestUrl.host}`;
			} else if (connection?.uri) {
				const connectionUrl = new URL(connection.uri);
				resolvedCallbackHost = `${connectionUrl.protocol}//${connectionUrl.host}`;
			}
		}
		let callbackUrl;
		if (resolvedCallbackHost) {
			const normalizedHost = resolvedCallbackHost.replace(/\/$/, "");
			callbackUrl = resolvedCallbackPath ? `${normalizedHost}/${resolvedCallbackPath.replace(/^\//, "")}` : `${normalizedHost}/${resolvedAgentsPrefix}/${camelCaseToKebabCase(this._ParentClass.name)}/${this.name}/callback`;
		}
		const id = requestedId ?? existingServer?.id ?? nanoid(8);
		let authProvider;
		if (callbackUrl) {
			authProvider = this.createMcpOAuthProvider(callbackUrl);
			authProvider.serverId = id;
		}
		const transportType = resolvedOptions?.transport?.type ?? "auto";
		let headerTransportOpts = {};
		if (resolvedOptions?.transport?.headers) headerTransportOpts = {
			eventSourceInit: { fetch: (url, init) => fetch(url, {
				...init,
				headers: resolvedOptions?.transport?.headers
			}) },
			requestInit: { headers: resolvedOptions?.transport?.headers }
		};
		await this.mcp.registerServer(id, {
			url: normalizedUrl,
			name: serverName,
			callbackUrl,
			client: resolvedOptions?.client,
			transport: {
				...headerTransportOpts,
				authProvider,
				type: transportType,
				skipIssuerMetadataValidation: resolvedOptions?.transport?.skipIssuerMetadataValidation
			},
			retry: resolvedOptions?.retry
		});
		const result = await this.mcp.connectToServer(id);
		if (result.state === MCPConnectionState.FAILED) throw new Error(`Failed to connect to MCP server at ${normalizedUrl}: ${result.error}`);
		if (result.state === MCPConnectionState.AUTHENTICATING) {
			if (!callbackUrl) throw new Error("This MCP server requires OAuth authentication. Provide callbackHost in addMcpServer options to enable the OAuth flow.");
			return {
				id,
				state: result.state,
				authUrl: result.authUrl
			};
		}
		const discoverResult = await this.mcp.discoverIfConnected(id);
		if (discoverResult && !discoverResult.success) throw new Error(`Failed to discover MCP server capabilities: ${discoverResult.error}`);
		return {
			id,
			state: MCPConnectionState.READY
		};
	}
	async _redeemableAuthUrl(serverId, authUrl, authProvider) {
		if (!this._isAbsoluteHttpUrl(authUrl) || !authProvider) return;
		const state = new URL(authUrl).searchParams.get("state");
		if (!state) return authUrl;
		authProvider.serverId = serverId;
		try {
			return (await authProvider.checkState(state)).valid ? authUrl : void 0;
		} catch {
			return;
		}
	}
	_isAbsoluteHttpUrl(value) {
		if (!value) return false;
		try {
			const url = new URL(value);
			return url.protocol === "http:" || url.protocol === "https:";
		} catch {
			return false;
		}
	}
	async removeMcpServer(id) {
		await this.mcp.removeServer(id);
	}
	getMcpServers() {
		const mcpState = {
			prompts: this.mcp.listPrompts(),
			resources: this.mcp.listResources(),
			servers: {},
			tools: this.mcp.listTools()
		};
		const servers = this.mcp.listServers();
		if (servers && Array.isArray(servers) && servers.length > 0) for (const server of servers) {
			const serverConn = this.mcp.mcpConnections[server.id];
			let defaultState = "not-connected";
			if (!serverConn && server.auth_url) defaultState = "authenticating";
			mcpState.servers[server.id] = {
				auth_url: server.auth_url,
				capabilities: serverConn?.serverCapabilities ?? null,
				error: sanitizeErrorString(serverConn?.connectionError ?? null),
				instructions: serverConn?.instructions ?? null,
				name: server.name,
				server_url: server.server_url,
				state: serverConn?.connectionState ?? defaultState
			};
		}
		return mcpState;
	}
	/**
	* Create the OAuth provider used when connecting to MCP servers that require authentication.
	*
	* Override this method in a subclass to supply a custom OAuth provider implementation,
	* for example to use pre-registered client credentials, mTLS-based authentication,
	* or any other OAuth flow beyond dynamic client registration.
	*
	* @example
	* // Custom OAuth provider
	* class MyAgent extends Agent {
	*   createMcpOAuthProvider(callbackUrl: string): AgentMcpOAuthProvider {
	*     return new MyCustomOAuthProvider(
	*       this.ctx.storage,
	*       this.name,
	*       callbackUrl
	*     );
	*   }
	* }
	*
	* @param callbackUrl The OAuth callback URL for the authorization flow
	* @returns An {@link AgentMcpOAuthProvider} instance used by {@link addMcpServer}
	*/
	createMcpOAuthProvider(callbackUrl) {
		return new DurableObjectOAuthClientProvider(this.ctx.storage, this.name, callbackUrl);
	}
	broadcastMcpServers() {
		this._broadcastProtocol(JSON.stringify({
			mcp: this.getMcpServers(),
			type: "cf_agent_mcp_servers"
		}));
	}
};
Agent.options = {};
const wrappedClasses = /* @__PURE__ */ new Set();
var _email = /* @__PURE__ */ new WeakMap();
_Symbol$dispose = Symbol.dispose;
var EmailBridge = class extends RpcTarget {
	constructor(email) {
		super();
		_classPrivateFieldInitSpec(this, _email, void 0);
		_classPrivateFieldSet2(_email, this, email);
	}
	async getRaw() {
		const reader = _classPrivateFieldGet2(_email, this).raw.getReader();
		const chunks = [];
		let done = false;
		while (!done) {
			const { value, done: readerDone } = await reader.read();
			done = readerDone;
			if (value) chunks.push(value);
		}
		const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
		const combined = new Uint8Array(totalLength);
		let offset = 0;
		for (const chunk of chunks) {
			combined.set(chunk, offset);
			offset += chunk.length;
		}
		return combined;
	}
	setReject(reason) {
		_classPrivateFieldGet2(_email, this).setReject(reason);
	}
	forward(rcptTo, headers) {
		return _classPrivateFieldGet2(_email, this).forward(rcptTo, headers);
	}
	reply(options) {
		return _classPrivateFieldGet2(_email, this).reply(new EmailMessage(options.from, options.to, options.raw));
	}
	[_Symbol$dispose]() {}
};
const agentMapCache = /* @__PURE__ */ new WeakMap();
/**
* Route an email to the appropriate Agent
* @param email The email to route
* @param env The environment containing the Agent bindings
* @param options The options for routing the email
* @returns A promise that resolves when the email has been routed
*/
async function routeAgentEmail(email, env, options) {
	const routingInfo = await options.resolver(email, env);
	if (!routingInfo) {
		if (options.onNoRoute) await options.onNoRoute(email);
		else console.warn("No routing information found for email, dropping message");
		return;
	}
	if (!agentMapCache.has(env)) {
		const map = {};
		const originalNames = [];
		for (const [key, value] of Object.entries(env)) if (value && typeof value === "object" && "idFromName" in value && typeof value.idFromName === "function") {
			map[key] = value;
			map[camelCaseToKebabCase(key)] = value;
			map[key.toLowerCase()] = value;
			originalNames.push(key);
		}
		agentMapCache.set(env, {
			map,
			originalNames
		});
	}
	const cached = agentMapCache.get(env);
	const namespace = cached.map[routingInfo.agentName];
	if (!namespace) {
		const availableAgents = cached.originalNames.join(", ");
		throw new Error(`Agent namespace '${routingInfo.agentName}' not found in environment. Available agents: ${availableAgents}`);
	}
	const agent = await getAgentByName(namespace, routingInfo.agentId);
	const bridge = new EmailBridge(email);
	await agent._onEmail({
		from: email.from,
		to: email.to,
		headers: email.headers,
		rawSize: email.rawSize,
		_secureRouted: routingInfo._secureRouted,
		_bridge: bridge
	});
}
/**
* A wrapper for streaming responses in callable methods
*/
var StreamingResponse = class {
	constructor(connection, id) {
		this._closed = false;
		this._connection = connection;
		this._id = id;
	}
	_send(response) {
		const facetSent = sendFacetStreamingResponse(this, response);
		if (facetSent !== null) return facetSent;
		return sendRpcResponseIfOpen(this._connection, response);
	}
	/**
	* Whether the stream has been closed (via end() or error())
	*/
	get isClosed() {
		return this._closed;
	}
	/**
	* Send a chunk of data to the client
	* @param chunk The data to send
	* @returns false if stream is already closed (no-op), true if sent
	*/
	send(chunk) {
		if (this._closed) {
			console.warn("StreamingResponse.send() called after stream was closed - data not sent");
			return false;
		}
		const response = {
			done: false,
			id: this._id,
			result: chunk,
			success: true,
			type: "rpc"
		};
		return this._send(response);
	}
	/**
	* End the stream and send the final chunk (if any)
	* @param finalChunk Optional final chunk of data to send
	* @returns false if stream is already closed (no-op), true if sent
	*/
	end(finalChunk) {
		if (this._closed) return false;
		this._closed = true;
		const response = {
			done: true,
			id: this._id,
			result: finalChunk,
			success: true,
			type: "rpc"
		};
		return this._send(response);
	}
	/**
	* Send an error to the client and close the stream
	* @param message Error message to send
	* @returns false if stream is already closed (no-op), true if sent
	*/
	error(message) {
		if (this._closed) return false;
		this._closed = true;
		const response = {
			error: message,
			id: this._id,
			success: false,
			type: "rpc"
		};
		return this._send(response);
	}
};
//#endregion
export { routeAgentEmail as a, getCurrentAgent as i, DEFAULT_AGENT_STATIC_OPTIONS as n, StreamingResponse as r, Agent as t };

//# sourceMappingURL=src-BNU3ZiJM.js.map