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