agents 0.22.0 → 0.23.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-DE5zmCQ8.d.ts} +1038 -394
- 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-DtXMTDGM.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.map +1 -1
- package/dist/capability-runner-Be_-PLR1.d.ts +459 -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 +2325 -2019
- package/dist/chat/index.js +888 -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.d.ts +1 -1
- 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-CuMErtly.d.ts → current-agent-Da_C9a3b.d.ts} +90 -107
- 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/index-BB0kqhIz.d.ts +101 -0
- package/dist/index-BVVgDSdq.d.ts +1 -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 +91 -82
- package/dist/index.js +3 -2
- 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 +35 -17
- package/dist/lifecycle/index.js +1 -1
- package/dist/lifecycle-CMRGjZdw.js +1299 -0
- package/dist/lifecycle-CMRGjZdw.js.map +1 -0
- package/dist/mcp/client/index.d.ts +20 -20
- package/dist/mcp/index.d.ts +35 -35
- package/dist/mcp/index.js +1 -1
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +1 -1
- package/dist/react.d.ts +4 -4
- package/dist/{retries-CAvxtG9d.d.ts → retries-D9Ds-1lz.d.ts} +17 -6
- package/dist/retries.d.ts +8 -6
- package/dist/retries.js +13 -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-DD9NdYbF.js +665 -0
- package/dist/scheduler-DD9NdYbF.js.map +1 -0
- package/dist/{scheduler-DQoTGoAW.d.ts → scheduler-Dwh85ZGl.d.ts} +21 -22
- 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-DlSHshb2.js} +1460 -1110
- package/dist/src-DlSHshb2.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-D6tJ0NN9.d.ts +370 -0
- package/dist/streams-DZKgAj9b.js +709 -0
- package/dist/streams-DZKgAj9b.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-BRJ5zgya.d.ts +517 -0
- package/dist/tasks-ylZgBjhj.js +1656 -0
- package/dist/tasks-ylZgBjhj.js.map +1 -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/types-B7LojTe4.d.ts +202 -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 +192 -0
- package/dist/websockets/index.js +2 -0
- package/dist/websockets-DUfRHPRq.js +502 -0
- package/dist/websockets-DUfRHPRq.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 +2 -2
- package/docs/agent-tools.md +2 -1
- package/docs/channels.md +323 -0
- package/docs/chat-agents.md +6 -13
- package/docs/context.md +131 -0
- package/docs/index.md +15 -12
- package/docs/lifecycle.md +102 -55
- package/docs/long-running-agents.md +2 -2
- package/docs/mcp-servers.md +5 -1
- package/docs/resumable-streaming.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 +115 -13
- package/dist/capability-runner-CvHGZqUu.d.ts +0 -150
- 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/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,23 +3,1179 @@ 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";
|
|
7
8
|
import { signAgentHeaders } from "./email.js";
|
|
8
9
|
import { sendAgentEmail } from "./email-send.js";
|
|
9
|
-
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-DZBYAB34.js";
|
|
10
|
+
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, 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 {
|
|
13
|
+
import { t as LifecycleCapability } from "./capability-BjSKYpzg.js";
|
|
14
|
+
import { t as _classPrivateMethodInitSpec } from "./classPrivateMethodInitSpec-qMjJ6sHQ.js";
|
|
15
|
+
import { a as abortWithoutAlarmRetry, i as setLifecycleRouteTransport, n as setLifecycleEventSink, r as setLifecycleHostInvoker, t as Lifecycle } from "./lifecycle-CMRGjZdw.js";
|
|
16
|
+
import { isErrorRetryable, tryN, validateRetryOptions } from "./retries.js";
|
|
13
17
|
import { SqlError } from "./sql-error.js";
|
|
14
|
-
import {
|
|
18
|
+
import { o as callablesFromDecorated, t as WebSockets } from "./websockets-DUfRHPRq.js";
|
|
15
19
|
import { a as ensureMcpServerTable, d as DisposableStore, i as normalizeServerId, l as RPC_DO_PREFIX, o as MCPConnectionState, t as MCPClientManager } from "./client-jagG8a9_.js";
|
|
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-DD9NdYbF.js";
|
|
23
|
+
import { n as setTaskDefinitionResolver, r as setTaskRoutedMemoryLimitHandler, t as Tasks } from "./tasks-ylZgBjhj.js";
|
|
19
24
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
25
|
+
import { DurableObject, RpcTarget, exports } from "cloudflare:workers";
|
|
20
26
|
import { nanoid } from "nanoid";
|
|
21
27
|
import { EmailMessage } from "cloudflare:email";
|
|
22
|
-
|
|
28
|
+
//#region src/dynamic-agents/bridges.ts
|
|
29
|
+
function isClosedWebSocketSendError(error) {
|
|
30
|
+
return error instanceof TypeError && error.message.includes("WebSocket send() after close");
|
|
31
|
+
}
|
|
32
|
+
const dynamicAgentRpcReplyContext = new AsyncLocalStorage();
|
|
33
|
+
function sendFacetRpcResponseIfOpen(target, response) {
|
|
34
|
+
try {
|
|
35
|
+
return {
|
|
36
|
+
sent: true,
|
|
37
|
+
completion: Promise.resolve(target.send(JSON.stringify(response))).catch((error) => {
|
|
38
|
+
if (!isClosedWebSocketSendError(error)) console.error("[Agent] Facet RPC response delivery failed:", error);
|
|
39
|
+
})
|
|
40
|
+
};
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (isClosedWebSocketSendError(error)) return {
|
|
43
|
+
sent: false,
|
|
44
|
+
completion: Promise.resolve()
|
|
45
|
+
};
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const facetStreamingResponseDeliveryStates = /* @__PURE__ */ new WeakMap();
|
|
50
|
+
/**
|
|
51
|
+
* Mark a StreamingResponse as facet-bridged: its chunks are delivered to
|
|
52
|
+
* `replyTarget` (the RPC frame that carried the request into the facet)
|
|
53
|
+
* instead of the connection's native WebSocket.
|
|
54
|
+
*/
|
|
55
|
+
function registerFacetStreamingDelivery(stream, replyTarget) {
|
|
56
|
+
facetStreamingResponseDeliveryStates.set(stream, {
|
|
57
|
+
replyTarget,
|
|
58
|
+
pending: /* @__PURE__ */ new Set()
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Deliver one streamed RPC response for a facet-bridged stream, tracking
|
|
63
|
+
* its completion. Returns null when the stream is not facet-bridged (the
|
|
64
|
+
* caller should send on the native connection instead).
|
|
65
|
+
*/
|
|
66
|
+
function sendFacetStreamingResponse(stream, response) {
|
|
67
|
+
const state = facetStreamingResponseDeliveryStates.get(stream);
|
|
68
|
+
if (!state) return null;
|
|
69
|
+
const delivery = sendFacetRpcResponseIfOpen(state.replyTarget, response);
|
|
70
|
+
state.pending.add(delivery.completion);
|
|
71
|
+
delivery.completion.finally(() => state.pending.delete(delivery.completion));
|
|
72
|
+
return delivery.sent;
|
|
73
|
+
}
|
|
74
|
+
async function waitForFacetStreamingResponseDeliveries(stream) {
|
|
75
|
+
const state = facetStreamingResponseDeliveryStates.get(stream);
|
|
76
|
+
if (!state) return;
|
|
77
|
+
try {
|
|
78
|
+
await Promise.all(state.pending);
|
|
79
|
+
} finally {
|
|
80
|
+
facetStreamingResponseDeliveryStates.delete(stream);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
var _connection = /* @__PURE__ */ new WeakMap();
|
|
84
|
+
var _broadcast = /* @__PURE__ */ new WeakMap();
|
|
85
|
+
/**
|
|
86
|
+
* Parent-side bridge handed to a facet over RPC: wraps a live root-owned
|
|
87
|
+
* `Connection` so the facet can send/close/setState on it, and carries
|
|
88
|
+
* the root's broadcast entry point for facet-scoped broadcasts.
|
|
89
|
+
*/
|
|
90
|
+
var DynamicAgentConnectionBridge = class extends RpcTarget {
|
|
91
|
+
constructor(connection, broadcast) {
|
|
92
|
+
super();
|
|
93
|
+
_classPrivateFieldInitSpec(this, _connection, void 0);
|
|
94
|
+
_classPrivateFieldInitSpec(this, _broadcast, void 0);
|
|
95
|
+
_classPrivateFieldSet2(_connection, this, connection);
|
|
96
|
+
_classPrivateFieldSet2(_broadcast, this, broadcast);
|
|
97
|
+
}
|
|
98
|
+
send(message) {
|
|
99
|
+
_classPrivateFieldGet2(_connection, this).send(message);
|
|
100
|
+
}
|
|
101
|
+
close(code, reason) {
|
|
102
|
+
_classPrivateFieldGet2(_connection, this).close(code, reason);
|
|
103
|
+
}
|
|
104
|
+
setState(state) {
|
|
105
|
+
return _classPrivateFieldGet2(_connection, this).setState(state);
|
|
106
|
+
}
|
|
107
|
+
broadcast(ownerPath, message, without) {
|
|
108
|
+
return _classPrivateFieldGet2(_broadcast, this)?.call(this, ownerPath, message, without);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
var _root = /* @__PURE__ */ new WeakMap();
|
|
112
|
+
var _connectionId = /* @__PURE__ */ new WeakMap();
|
|
113
|
+
/**
|
|
114
|
+
* Facet-side bridge used after the originating RPC frame has completed:
|
|
115
|
+
* routes connection operations back to the root over a fresh RPC call.
|
|
116
|
+
*/
|
|
117
|
+
var RootDynamicAgentConnectionBridge = class {
|
|
118
|
+
constructor(root, connectionId) {
|
|
119
|
+
_classPrivateFieldInitSpec(this, _root, void 0);
|
|
120
|
+
_classPrivateFieldInitSpec(this, _connectionId, void 0);
|
|
121
|
+
_classPrivateFieldSet2(_root, this, root);
|
|
122
|
+
_classPrivateFieldSet2(_connectionId, this, connectionId);
|
|
123
|
+
}
|
|
124
|
+
send(message) {
|
|
125
|
+
return _classPrivateFieldGet2(_root, this)._cf_sendToSubAgentConnection(_classPrivateFieldGet2(_connectionId, this), message);
|
|
126
|
+
}
|
|
127
|
+
close(code, reason) {
|
|
128
|
+
return _classPrivateFieldGet2(_root, this)._cf_closeSubAgentConnection(_classPrivateFieldGet2(_connectionId, this), code, reason);
|
|
129
|
+
}
|
|
130
|
+
setState(state) {
|
|
131
|
+
return _classPrivateFieldGet2(_root, this)._cf_setSubAgentConnectionState(_classPrivateFieldGet2(_connectionId, this), state);
|
|
132
|
+
}
|
|
133
|
+
broadcast(ownerPath, message, without) {
|
|
134
|
+
return _classPrivateFieldGet2(_root, this)._cf_broadcastToSubAgent(ownerPath, message, without);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/dynamic-agents/identity.ts
|
|
139
|
+
const SUB_AGENT_IDENTITY_VERSION_LEGACY = "legacy";
|
|
140
|
+
const SUB_AGENT_IDENTITY_VERSION_PATH_V2 = "path-v2";
|
|
141
|
+
const SUB_AGENT_IDENTITY_PATH_V2_PREFIX = "cf-agents:v2:";
|
|
142
|
+
async function sha256Hex(value) {
|
|
143
|
+
const bytes = new TextEncoder().encode(value);
|
|
144
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
145
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
146
|
+
}
|
|
147
|
+
function pathV2IdentityName(logicalName, digest) {
|
|
148
|
+
return `${SUB_AGENT_IDENTITY_PATH_V2_PREFIX}${encodeURIComponent(logicalName)}:${digest}`;
|
|
149
|
+
}
|
|
150
|
+
function logicalNameFromPathV2Identity(identityName) {
|
|
151
|
+
if (!identityName.startsWith("cf-agents:v2:")) return null;
|
|
152
|
+
const rest = identityName.slice(13);
|
|
153
|
+
const separator = rest.lastIndexOf(":");
|
|
154
|
+
if (separator === -1) return null;
|
|
155
|
+
try {
|
|
156
|
+
return decodeURIComponent(rest.slice(0, separator));
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Validate that a stored `parentPath` has the expected shape. Used
|
|
163
|
+
* when restoring from DO storage to guard against corrupted data.
|
|
164
|
+
*/
|
|
165
|
+
function isValidParentPath(value) {
|
|
166
|
+
if (!Array.isArray(value)) return false;
|
|
167
|
+
return value.every((entry) => entry != null && typeof entry === "object" && typeof entry.className === "string" && typeof entry.name === "string");
|
|
168
|
+
}
|
|
169
|
+
function agentPathKey(path) {
|
|
170
|
+
if (!path) return null;
|
|
171
|
+
return path.map((step) => `${encodeURIComponent(step.className)}:${encodeURIComponent(step.name)}`).join("/");
|
|
172
|
+
}
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/dynamic-agents/registry.ts
|
|
175
|
+
var _host$1 = /* @__PURE__ */ new WeakMap();
|
|
176
|
+
var _ready = /* @__PURE__ */ new WeakMap();
|
|
177
|
+
var _DynamicAgentRegistry_brand = /* @__PURE__ */ new WeakSet();
|
|
178
|
+
/**
|
|
179
|
+
* The parent-side registry of spawned dynamic agents (facets), stored
|
|
180
|
+
* in the parent's own SQLite. Backs `hasSubAgent` / `listSubAgents`
|
|
181
|
+
* and the identity-versioning decision (legacy bare-name facets vs
|
|
182
|
+
* path-scoped v2 identities).
|
|
183
|
+
*
|
|
184
|
+
* Table and column names are storage-frozen — never rename them.
|
|
185
|
+
*/
|
|
186
|
+
var DynamicAgentRegistry = class {
|
|
187
|
+
constructor(host) {
|
|
188
|
+
_classPrivateMethodInitSpec(this, _DynamicAgentRegistry_brand);
|
|
189
|
+
_classPrivateFieldInitSpec(this, _host$1, void 0);
|
|
190
|
+
_classPrivateFieldInitSpec(this, _ready, false);
|
|
191
|
+
_classPrivateFieldSet2(_host$1, this, host);
|
|
192
|
+
}
|
|
193
|
+
ensure() {
|
|
194
|
+
if (_classPrivateFieldGet2(_ready, this)) return;
|
|
195
|
+
_classPrivateFieldGet2(_host$1, this).sql`
|
|
196
|
+
CREATE TABLE IF NOT EXISTS cf_agents_sub_agents (
|
|
197
|
+
class TEXT NOT NULL,
|
|
198
|
+
name TEXT NOT NULL,
|
|
199
|
+
created_at INTEGER NOT NULL,
|
|
200
|
+
identity_version TEXT,
|
|
201
|
+
identity_name TEXT,
|
|
202
|
+
PRIMARY KEY (class, name)
|
|
203
|
+
)
|
|
204
|
+
`;
|
|
205
|
+
_assertClassBrand(_DynamicAgentRegistry_brand, this, _addColumnIfNotExists).call(this, "ALTER TABLE cf_agents_sub_agents ADD COLUMN identity_version TEXT");
|
|
206
|
+
_assertClassBrand(_DynamicAgentRegistry_brand, this, _addColumnIfNotExists).call(this, "ALTER TABLE cf_agents_sub_agents ADD COLUMN identity_name TEXT");
|
|
207
|
+
_classPrivateFieldSet2(_ready, this, true);
|
|
208
|
+
}
|
|
209
|
+
record(className, name, identity) {
|
|
210
|
+
this.ensure();
|
|
211
|
+
_classPrivateFieldGet2(_host$1, this).sql`
|
|
212
|
+
INSERT OR IGNORE INTO cf_agents_sub_agents
|
|
213
|
+
(class, name, created_at, identity_version, identity_name)
|
|
214
|
+
VALUES
|
|
215
|
+
(${className}, ${name}, ${Date.now()}, ${identity.version}, ${identity.name})
|
|
216
|
+
`;
|
|
217
|
+
}
|
|
218
|
+
row(className, name) {
|
|
219
|
+
this.ensure();
|
|
220
|
+
return _classPrivateFieldGet2(_host$1, this).sql`
|
|
221
|
+
SELECT identity_version, identity_name
|
|
222
|
+
FROM cf_agents_sub_agents
|
|
223
|
+
WHERE class = ${className} AND name = ${name}
|
|
224
|
+
LIMIT 1
|
|
225
|
+
`[0] ?? null;
|
|
226
|
+
}
|
|
227
|
+
async identity(className, name, childPath) {
|
|
228
|
+
const row = this.row(className, name);
|
|
229
|
+
if (row) {
|
|
230
|
+
if (row.identity_version === "path-v2" && typeof row.identity_name === "string") return {
|
|
231
|
+
version: SUB_AGENT_IDENTITY_VERSION_PATH_V2,
|
|
232
|
+
name: row.identity_name,
|
|
233
|
+
existing: true
|
|
234
|
+
};
|
|
235
|
+
return {
|
|
236
|
+
version: SUB_AGENT_IDENTITY_VERSION_LEGACY,
|
|
237
|
+
name,
|
|
238
|
+
existing: true
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
version: SUB_AGENT_IDENTITY_VERSION_PATH_V2,
|
|
243
|
+
name: pathV2IdentityName(name, await sha256Hex(JSON.stringify(childPath))),
|
|
244
|
+
existing: false
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
forget(className, name) {
|
|
248
|
+
this.ensure();
|
|
249
|
+
_classPrivateFieldGet2(_host$1, this).sql`
|
|
250
|
+
DELETE FROM cf_agents_sub_agents
|
|
251
|
+
WHERE class = ${className} AND name = ${name}
|
|
252
|
+
`;
|
|
253
|
+
}
|
|
254
|
+
has(className, name) {
|
|
255
|
+
this.ensure();
|
|
256
|
+
return (_classPrivateFieldGet2(_host$1, this).sql`
|
|
257
|
+
SELECT COUNT(*) AS n FROM cf_agents_sub_agents
|
|
258
|
+
WHERE class = ${className} AND name = ${name}
|
|
259
|
+
`[0]?.n ?? 0) > 0;
|
|
260
|
+
}
|
|
261
|
+
list(className) {
|
|
262
|
+
this.ensure();
|
|
263
|
+
return (className ? _classPrivateFieldGet2(_host$1, this).sql`
|
|
264
|
+
SELECT class, name, created_at FROM cf_agents_sub_agents
|
|
265
|
+
WHERE class = ${className}
|
|
266
|
+
ORDER BY created_at ASC
|
|
267
|
+
` : _classPrivateFieldGet2(_host$1, this).sql`
|
|
268
|
+
SELECT class, name, created_at FROM cf_agents_sub_agents
|
|
269
|
+
ORDER BY created_at ASC
|
|
270
|
+
`).map((r) => ({
|
|
271
|
+
className: r.class,
|
|
272
|
+
name: r.name,
|
|
273
|
+
createdAt: r.created_at
|
|
274
|
+
}));
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
function _addColumnIfNotExists(sql) {
|
|
278
|
+
try {
|
|
279
|
+
_classPrivateFieldGet2(_host$1, this).execRawSql(sql);
|
|
280
|
+
} catch (e) {
|
|
281
|
+
if (!(e instanceof Error ? e.message : String(e)).toLowerCase().includes("duplicate column")) throw e;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region src/dynamic-agents/dynamic-agents.ts
|
|
286
|
+
/**
|
|
287
|
+
* Internal key used to remember the outer `/sub/...` URL for a
|
|
288
|
+
* WebSocket accepted by the parent on behalf of a child facet.
|
|
289
|
+
* Hibernated events then wake the parent, which forwards frames to
|
|
290
|
+
* the child over serializable RPC while keeping native WebSocket I/O
|
|
291
|
+
* parent-owned.
|
|
292
|
+
*
|
|
293
|
+
* Storage-frozen — never rename.
|
|
294
|
+
*/
|
|
295
|
+
const CF_SUB_AGENT_OUTER_URL_KEY = "_cf_subAgentOuterUrl";
|
|
296
|
+
const CF_SUB_AGENT_TAGS_KEY = "_cf_subAgentTags";
|
|
297
|
+
/** Wire-frozen internal header carrying the outer URL on WS upgrades. */
|
|
298
|
+
const SUB_AGENT_OUTER_URL_HEADER = "x-cf-agents-subagent-url";
|
|
299
|
+
var _host = /* @__PURE__ */ new WeakMap();
|
|
300
|
+
var _facetKeepAliveTokens = /* @__PURE__ */ new WeakMap();
|
|
301
|
+
var _bridgeContext = /* @__PURE__ */ new WeakMap();
|
|
302
|
+
var _virtualConnections = /* @__PURE__ */ new WeakMap();
|
|
303
|
+
var _connectionOperationTails = /* @__PURE__ */ new WeakMap();
|
|
304
|
+
var _broadcastOperationTail = /* @__PURE__ */ new WeakMap();
|
|
305
|
+
var _DynamicAgentsInternal_brand = /* @__PURE__ */ new WeakSet();
|
|
306
|
+
/**
|
|
307
|
+
* The facet-backed dynamic-agent machinery, extracted from the Agent
|
|
308
|
+
* class. One instance per Agent, installed as a Lifecycle capability
|
|
309
|
+
* (`capabilityId: "dynamic-agents"`); the host port documents exactly
|
|
310
|
+
* which Agent internals it touches.
|
|
311
|
+
*
|
|
312
|
+
* The capability claims no runner hooks — four integration points are
|
|
313
|
+
* deliberately wired directly through the Agent composition root
|
|
314
|
+
* instead, because the runner's dispatch contract cannot express them:
|
|
315
|
+
* the `/sub/` upgrade path rewrites the request and *continues* into
|
|
316
|
+
* `lifecycle.fetch` (onRequest can only claim), forwarded WS frames run
|
|
317
|
+
* inside the host's onMessage wrapper *after* the WebSockets capability
|
|
318
|
+
* has claimed the wake, this module *implements* the lifecycle route
|
|
319
|
+
* transport rather than consuming it, and facet-context restore has
|
|
320
|
+
* load-bearing startup ordering inside the host's startup span.
|
|
321
|
+
*
|
|
322
|
+
* Nothing here renames any wire- or storage-visible identifier: the
|
|
323
|
+
* `cf_agents_facet_runs` table, `_cf_*` RPC method names, and route
|
|
324
|
+
* key formats are frozen.
|
|
325
|
+
*
|
|
326
|
+
* @internal
|
|
327
|
+
*/
|
|
328
|
+
var DynamicAgentsInternal = class extends LifecycleCapability {
|
|
329
|
+
constructor(host) {
|
|
330
|
+
super("dynamic-agents");
|
|
331
|
+
_classPrivateMethodInitSpec(this, _DynamicAgentsInternal_brand);
|
|
332
|
+
_classPrivateFieldInitSpec(this, _host, void 0);
|
|
333
|
+
_classPrivateFieldInitSpec(this, _facetKeepAliveTokens, /* @__PURE__ */ new Set());
|
|
334
|
+
_classPrivateFieldInitSpec(this, _bridgeContext, new AsyncLocalStorage());
|
|
335
|
+
_classPrivateFieldInitSpec(this, _virtualConnections, /* @__PURE__ */ new Map());
|
|
336
|
+
_classPrivateFieldInitSpec(this, _connectionOperationTails, /* @__PURE__ */ new Map());
|
|
337
|
+
_classPrivateFieldInitSpec(this, _broadcastOperationTail, void 0);
|
|
338
|
+
_classPrivateFieldSet2(_host, this, host);
|
|
339
|
+
this.registry = new DynamicAgentRegistry({
|
|
340
|
+
sql: host.sql.bind(host),
|
|
341
|
+
execRawSql: (sql) => void host.ctx.storage.sql.exec(sql)
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
runRowsForPrefix(ownerPath) {
|
|
345
|
+
return _classPrivateFieldGet2(_host, this).sql`
|
|
346
|
+
SELECT owner_path, owner_path_key, run_id, created_at
|
|
347
|
+
FROM cf_agents_facet_runs
|
|
348
|
+
`.filter((row) => {
|
|
349
|
+
try {
|
|
350
|
+
const rowOwnerPath = JSON.parse(row.owner_path);
|
|
351
|
+
return _classPrivateFieldGet2(_host, this)._isSameAgentPathPrefix(ownerPath, rowOwnerPath);
|
|
352
|
+
} catch {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
deleteRunRowsForPrefix(ownerPath) {
|
|
358
|
+
for (const row of this.runRowsForPrefix(ownerPath)) _classPrivateFieldGet2(_host, this).sql`
|
|
359
|
+
DELETE FROM cf_agents_facet_runs
|
|
360
|
+
WHERE owner_path_key = ${row.owner_path_key}
|
|
361
|
+
AND run_id = ${row.run_id}
|
|
362
|
+
`;
|
|
363
|
+
}
|
|
364
|
+
lifecycleRouteAddress() {
|
|
365
|
+
if (!_classPrivateFieldGet2(_host, this)._isFacet) return void 0;
|
|
366
|
+
const key = agentPathKey(_classPrivateFieldGet2(_host, this).selfPath);
|
|
367
|
+
return key ? {
|
|
368
|
+
key,
|
|
369
|
+
data: JSON.stringify(_classPrivateFieldGet2(_host, this).selfPath)
|
|
370
|
+
} : void 0;
|
|
371
|
+
}
|
|
372
|
+
async routeLifecycleToRoot(envelope) {
|
|
373
|
+
if (!_classPrivateFieldGet2(_host, this)._isFacet) return _classPrivateFieldGet2(_host, this).lifecycle.route(envelope);
|
|
374
|
+
return (await this.rootAlarmOwner())._cf_routeLifecycle(void 0, envelope);
|
|
375
|
+
}
|
|
376
|
+
async routeLifecycleToTarget(target, envelope) {
|
|
377
|
+
let targetPath;
|
|
378
|
+
try {
|
|
379
|
+
targetPath = JSON.parse(target.data);
|
|
380
|
+
} catch {
|
|
381
|
+
throw new Error("Lifecycle route target is not a valid Agent path");
|
|
382
|
+
}
|
|
383
|
+
const selfPath = _classPrivateFieldGet2(_host, this).selfPath;
|
|
384
|
+
if (!_classPrivateFieldGet2(_host, this)._isSameAgentPathPrefix(selfPath, targetPath)) throw new Error(`Lifecycle route does not descend from ${JSON.stringify(selfPath)}.`);
|
|
385
|
+
if (selfPath.length === targetPath.length) return _classPrivateFieldGet2(_host, this).lifecycle.route(envelope);
|
|
386
|
+
const next = targetPath[selfPath.length];
|
|
387
|
+
if (!_classPrivateFieldGet2(_host, this).hasSubAgent(next.className, next.name)) {
|
|
388
|
+
const stalePath = targetPath.slice(0, selfPath.length + 1);
|
|
389
|
+
if (_classPrivateFieldGet2(_host, this)._isFacet) await (await this.rootAlarmOwner())._cf_cleanupFacetPrefix(stalePath);
|
|
390
|
+
else await _classPrivateFieldGet2(_host, this)._cf_cleanupFacetPrefix(stalePath);
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
return (await _classPrivateFieldGet2(_host, this)._cf_resolveSubAgent(next.className, next.name))._cf_routeLifecycle(target, envelope);
|
|
394
|
+
}
|
|
395
|
+
/** Body of the single native-RPC aperture for routed Lifecycle capabilities. */
|
|
396
|
+
routeLifecycle(target, envelope) {
|
|
397
|
+
return target ? this.routeLifecycleToTarget(target, envelope) : _classPrivateFieldGet2(_host, this).lifecycle.route(envelope);
|
|
398
|
+
}
|
|
399
|
+
async rootAlarmOwner() {
|
|
400
|
+
const root = _classPrivateFieldGet2(_host, this)._parentPath[0];
|
|
401
|
+
if (!root) throw new Error("Facet routing requires a root parent.");
|
|
402
|
+
const binding = _classPrivateFieldGet2(_host, this).ctx.exports?.[root.className];
|
|
403
|
+
if (!binding) throw new Error(`Unable to resolve root "${root.className}" for facet routing.`);
|
|
404
|
+
return await getAgentByName(binding, root.name);
|
|
405
|
+
}
|
|
406
|
+
rootResolvesToSelf() {
|
|
407
|
+
const root = _classPrivateFieldGet2(_host, this)._parentPath[0];
|
|
408
|
+
if (!root) return false;
|
|
409
|
+
const binding = _classPrivateFieldGet2(_host, this).ctx.exports?.[root.className];
|
|
410
|
+
if (!binding?.idFromName) return false;
|
|
411
|
+
return binding.idFromName(root.name).equals(_classPrivateFieldGet2(_host, this).ctx.id);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Clean root-owned bookkeeping for a sub-tree of facets: bulk-cancel
|
|
415
|
+
* schedules and routed Task wake mirrors under the owner-path prefix,
|
|
416
|
+
* and delete root-side facet fiber recovery leases for the same sub-tree.
|
|
417
|
+
*/
|
|
418
|
+
async cleanupPrefix(ownerPath) {
|
|
419
|
+
const prefix = agentPathKey(ownerPath);
|
|
420
|
+
if (prefix) {
|
|
421
|
+
await _classPrivateFieldGet2(_host, this).scheduler.__DO_NOT_USE_WILL_BREAK__cleanupRoutePrefix(prefix);
|
|
422
|
+
await _classPrivateFieldGet2(_host, this).tasks.__DO_NOT_USE_WILL_BREAK__cleanupRoutePrefix(prefix);
|
|
423
|
+
}
|
|
424
|
+
this.deleteRunRowsForPrefix(ownerPath);
|
|
425
|
+
await _classPrivateFieldGet2(_host, this)._syncHostJobs();
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Acquire a root-owned keepAlive ref on behalf of a descendant facet.
|
|
429
|
+
*/
|
|
430
|
+
async acquireKeepAlive(ownerPath) {
|
|
431
|
+
const token = `${agentPathKey(ownerPath) ?? "unknown"}:${nanoid(9)}`;
|
|
432
|
+
_classPrivateFieldGet2(_facetKeepAliveTokens, this).add(token);
|
|
433
|
+
_classPrivateFieldGet2(_host, this)._keepAliveRefs++;
|
|
434
|
+
if (_classPrivateFieldGet2(_host, this)._keepAliveRefs === 1) await _classPrivateFieldGet2(_host, this)._syncHostJobs();
|
|
435
|
+
return token;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Release a root-owned keepAlive ref previously acquired for a facet.
|
|
439
|
+
* Idempotent so disposer calls can safely race or run twice.
|
|
440
|
+
*/
|
|
441
|
+
async releaseKeepAlive(token) {
|
|
442
|
+
if (!_classPrivateFieldGet2(_facetKeepAliveTokens, this).delete(token)) return;
|
|
443
|
+
_classPrivateFieldGet2(_host, this)._keepAliveRefs = Math.max(0, _classPrivateFieldGet2(_host, this)._keepAliveRefs - 1);
|
|
444
|
+
await _classPrivateFieldGet2(_host, this)._syncHostJobs();
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Register a facet's durable run row in the root-side index so root
|
|
448
|
+
* alarm housekeeping can dispatch recovery checks into idle facets.
|
|
449
|
+
*/
|
|
450
|
+
async registerRun(ownerPath, runId) {
|
|
451
|
+
const ownerPathJson = JSON.stringify(ownerPath);
|
|
452
|
+
const ownerPathKey = agentPathKey(ownerPath);
|
|
453
|
+
if (!ownerPathKey) throw new Error("_cf_registerFacetRun requires a non-empty owner path.");
|
|
454
|
+
_classPrivateFieldGet2(_host, this).sql`
|
|
455
|
+
INSERT OR REPLACE INTO cf_agents_facet_runs
|
|
456
|
+
(owner_path, owner_path_key, run_id, created_at)
|
|
457
|
+
VALUES
|
|
458
|
+
(${ownerPathJson}, ${ownerPathKey}, ${runId}, ${Date.now()})
|
|
459
|
+
`;
|
|
460
|
+
await _classPrivateFieldGet2(_host, this)._syncHostJobs();
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Root-side scan for durable fibers owned by descendant facets.
|
|
464
|
+
* `cf_agents_facet_runs` is only an index; actual snapshots and
|
|
465
|
+
* recovery hooks live in each facet's own `cf_agents_runs` table.
|
|
466
|
+
*/
|
|
467
|
+
async checkRunFibers() {
|
|
468
|
+
if (_classPrivateFieldGet2(_host, this)._parentPath.length > 0) return;
|
|
469
|
+
const rows = _classPrivateFieldGet2(_host, this).sql`
|
|
470
|
+
SELECT owner_path, owner_path_key, run_id, created_at
|
|
471
|
+
FROM cf_agents_facet_runs
|
|
472
|
+
ORDER BY created_at ASC
|
|
473
|
+
`;
|
|
474
|
+
const firstRowByOwner = /* @__PURE__ */ new Map();
|
|
475
|
+
for (const row of rows) if (!firstRowByOwner.has(row.owner_path_key)) firstRowByOwner.set(row.owner_path_key, row);
|
|
476
|
+
for (const row of firstRowByOwner.values()) {
|
|
477
|
+
let ownerPath;
|
|
478
|
+
try {
|
|
479
|
+
ownerPath = JSON.parse(row.owner_path);
|
|
480
|
+
} catch (e) {
|
|
481
|
+
console.warn(`[Agent] Corrupted facet fiber owner path for ${row.owner_path_key}; pruning stale lease.`, e);
|
|
482
|
+
_classPrivateFieldGet2(_host, this).sql`
|
|
483
|
+
DELETE FROM cf_agents_facet_runs
|
|
484
|
+
WHERE owner_path_key = ${row.owner_path_key}
|
|
485
|
+
`;
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
try {
|
|
489
|
+
if (await _classPrivateFieldGet2(_host, this)._cf_checkRunFibersForFacet(ownerPath) === 0) _classPrivateFieldGet2(_host, this).sql`
|
|
490
|
+
DELETE FROM cf_agents_facet_runs
|
|
491
|
+
WHERE owner_path_key = ${row.owner_path_key}
|
|
492
|
+
`;
|
|
493
|
+
} catch (e) {
|
|
494
|
+
console.error(`[Agent] Facet fiber recovery check failed for ${row.owner_path_key}:`, e);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Dispatch a runFiber recovery check into the facet identified by
|
|
500
|
+
* `ownerPath`. Returns the number of remaining local `cf_agents_runs`
|
|
501
|
+
* rows on the target facet after recovery.
|
|
502
|
+
*/
|
|
503
|
+
async checkRunFibersAtPath(ownerPath) {
|
|
504
|
+
const selfPath = _classPrivateFieldGet2(_host, this).selfPath;
|
|
505
|
+
if (!_classPrivateFieldGet2(_host, this)._isSameAgentPathPrefix(selfPath, ownerPath)) throw new Error(`Facet fiber owner path does not descend from ${JSON.stringify(selfPath)}.`);
|
|
506
|
+
if (selfPath.length === ownerPath.length) {
|
|
507
|
+
await _classPrivateFieldGet2(_host, this)._checkRunFibers();
|
|
508
|
+
return _classPrivateFieldGet2(_host, this).sql`
|
|
509
|
+
SELECT COUNT(*) as count FROM cf_agents_runs
|
|
510
|
+
`[0]?.count ?? 0;
|
|
511
|
+
}
|
|
512
|
+
const next = ownerPath[selfPath.length];
|
|
513
|
+
if (!_classPrivateFieldGet2(_host, this).hasSubAgent(next.className, next.name)) return 0;
|
|
514
|
+
return (await this.resolve(next.className, next.name))._cf_checkRunFibersForFacet(ownerPath);
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Invoke an RPC method on the host Agent or a descendant facet
|
|
518
|
+
* identified by a root-first path. Used by AgentWorkflow to route
|
|
519
|
+
* callbacks and `this.agent` calls back to the exact sub-agent that
|
|
520
|
+
* started a workflow.
|
|
521
|
+
*/
|
|
522
|
+
async invokeAgentPath(targetPath, method, args) {
|
|
523
|
+
await _classPrivateFieldGet2(_host, this).__unsafe_ensureInitialized();
|
|
524
|
+
const selfPath = _classPrivateFieldGet2(_host, this).selfPath;
|
|
525
|
+
if (!_classPrivateFieldGet2(_host, this)._isSameAgentPathPrefix(selfPath, targetPath)) throw new Error(`Workflow origin path does not descend from ${JSON.stringify(selfPath)}.`);
|
|
526
|
+
if (selfPath.length === targetPath.length) {
|
|
527
|
+
const fn = _classPrivateFieldGet2(_host, this)[method];
|
|
528
|
+
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}.`);
|
|
529
|
+
return await fn.apply(_classPrivateFieldGet2(_host, this), args);
|
|
530
|
+
}
|
|
531
|
+
const next = targetPath[selfPath.length];
|
|
532
|
+
if (!_classPrivateFieldGet2(_host, this).hasSubAgent(next.className, next.name)) throw new Error(`Workflow origin sub-agent ${next.className} "${next.name}" no longer exists.`);
|
|
533
|
+
return await (await this.resolve(next.className, next.name))._cf_invokeAgentPath(targetPath, method, args);
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Recursively destroy a descendant facet identified by `targetPath`.
|
|
537
|
+
* Walks down from `selfPath` until reaching the target's immediate
|
|
538
|
+
* parent, where it cancels the target's parent-owned schedules (and
|
|
539
|
+
* any descendants), removes the target from the registry, and calls
|
|
540
|
+
* `ctx.facets.delete` to wipe the target's storage.
|
|
541
|
+
*/
|
|
542
|
+
async destroyDescendant(targetPath) {
|
|
543
|
+
const selfPath = _classPrivateFieldGet2(_host, this).selfPath;
|
|
544
|
+
if (targetPath.length === 0) throw new Error("_cf_destroyDescendantFacet: target path must not be empty.");
|
|
545
|
+
if (selfPath.length >= targetPath.length) throw new Error("_cf_destroyDescendantFacet: target must be a strict descendant.");
|
|
546
|
+
if (!_classPrivateFieldGet2(_host, this)._isSameAgentPathPrefix(selfPath, targetPath)) throw new Error("_cf_destroyDescendantFacet: target path does not descend from this agent.");
|
|
547
|
+
if (_classPrivateFieldGet2(_host, this)._parentPath.length === 0) await _classPrivateFieldGet2(_host, this)._cf_cleanupFacetPrefix(targetPath);
|
|
548
|
+
if (selfPath.length === targetPath.length - 1) {
|
|
549
|
+
const target = targetPath[targetPath.length - 1];
|
|
550
|
+
const ctx = _classPrivateFieldGet2(_host, this).ctx;
|
|
551
|
+
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.");
|
|
552
|
+
try {
|
|
553
|
+
ctx.facets.delete(`${target.className}\0${target.name}`);
|
|
554
|
+
} catch {}
|
|
555
|
+
this.registry.forget(target.className, target.name);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const next = targetPath[selfPath.length];
|
|
559
|
+
if (!_classPrivateFieldGet2(_host, this).hasSubAgent(next.className, next.name)) return;
|
|
560
|
+
await (await this.resolve(next.className, next.name))._cf_destroyDescendantFacet(targetPath);
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Shared facet resolution — takes a CamelCase class name string
|
|
564
|
+
* (matching `ctx.exports`) rather than a class reference. Both
|
|
565
|
+
* `subAgent(cls, name)` and `_cf_invokeSubAgent(className, ...)`
|
|
566
|
+
* funnel through here so registry bookkeeping and the
|
|
567
|
+
* `_cf_initAsFacet` handshake are consistent.
|
|
568
|
+
*/
|
|
569
|
+
async resolve(className, name) {
|
|
570
|
+
const ctx = _classPrivateFieldGet2(_host, this).ctx;
|
|
571
|
+
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.");
|
|
572
|
+
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").`);
|
|
573
|
+
const Cls = ctx.exports[className];
|
|
574
|
+
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.`);
|
|
575
|
+
if (name.includes("\0")) throw new Error(`Sub-agent name contains null character (\\0), which is reserved.`);
|
|
576
|
+
const facetKey = `${className}\0${name}`;
|
|
577
|
+
const childParentPath = _classPrivateFieldGet2(_host, this).selfPath;
|
|
578
|
+
const childPath = [...childParentPath, {
|
|
579
|
+
className,
|
|
580
|
+
name
|
|
581
|
+
}];
|
|
582
|
+
const rootClassName = _classPrivateFieldGet2(_host, this)._parentPath[0]?.className ?? _classPrivateFieldGet2(_host, this).constructor.name;
|
|
583
|
+
const rootNs = ctx.exports[rootClassName];
|
|
584
|
+
if (!rootNs?.idFromName) {
|
|
585
|
+
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\`).` : "";
|
|
586
|
+
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.`);
|
|
587
|
+
}
|
|
588
|
+
const identity = await this.registry.identity(className, name, childPath);
|
|
589
|
+
const facetId = rootNs.idFromName(identity.name);
|
|
590
|
+
const stub = ctx.facets.get(facetKey, () => ({
|
|
591
|
+
class: Cls,
|
|
592
|
+
id: facetId
|
|
593
|
+
}));
|
|
594
|
+
this.registry.record(className, name, identity);
|
|
595
|
+
try {
|
|
596
|
+
await _classPrivateFieldGet2(_host, this)._runFacetInitInvocation(async () => {
|
|
597
|
+
await stub._cf_initAsFacet(name, childParentPath, identity.name);
|
|
598
|
+
});
|
|
599
|
+
} catch (error) {
|
|
600
|
+
if (!identity.existing) this.registry.forget(className, name);
|
|
601
|
+
throw error;
|
|
602
|
+
}
|
|
603
|
+
return stub;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Forcefully abort a running facet. Transitively aborts the child's
|
|
607
|
+
* own children; storage is preserved.
|
|
608
|
+
*/
|
|
609
|
+
abort(className, name, reason) {
|
|
610
|
+
const ctx = _classPrivateFieldGet2(_host, this).ctx;
|
|
611
|
+
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.");
|
|
612
|
+
const facetKey = `${className}\0${name}`;
|
|
613
|
+
ctx.facets.abort(facetKey, reason);
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Delete a facet: abort it if running, then permanently wipe its
|
|
617
|
+
* storage. Transitively deletes the child's own children.
|
|
618
|
+
*/
|
|
619
|
+
async delete(className, name) {
|
|
620
|
+
const ctx = _classPrivateFieldGet2(_host, this).ctx;
|
|
621
|
+
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.");
|
|
622
|
+
const facetKey = `${className}\0${name}`;
|
|
623
|
+
const childPath = [..._classPrivateFieldGet2(_host, this).selfPath, {
|
|
624
|
+
className,
|
|
625
|
+
name
|
|
626
|
+
}];
|
|
627
|
+
if (_classPrivateFieldGet2(_host, this)._isFacet) await (await this.rootAlarmOwner())._cf_cleanupFacetPrefix(childPath);
|
|
628
|
+
else await _classPrivateFieldGet2(_host, this)._cf_cleanupFacetPrefix(childPath);
|
|
629
|
+
try {
|
|
630
|
+
ctx.facets.delete(facetKey);
|
|
631
|
+
} catch {}
|
|
632
|
+
this.registry.forget(className, name);
|
|
633
|
+
}
|
|
634
|
+
/** Drop all facet-side virtual connections (test/rehydration hook). */
|
|
635
|
+
clearVirtualConnections() {
|
|
636
|
+
_classPrivateFieldGet2(_virtualConnections, this).clear();
|
|
637
|
+
}
|
|
638
|
+
/** Facet-side lookup of a virtual connection by id. */
|
|
639
|
+
getVirtualConnection(id) {
|
|
640
|
+
const stored = _classPrivateFieldGet2(_virtualConnections, this).get(id);
|
|
641
|
+
if (!stored) return void 0;
|
|
642
|
+
return this.createBridgeConnection(stored.meta);
|
|
643
|
+
}
|
|
644
|
+
/** Facet-side iteration over virtual connections, optionally by tag. */
|
|
645
|
+
*getVirtualConnections(tag) {
|
|
646
|
+
for (const stored of _classPrivateFieldGet2(_virtualConnections, this).values()) if (!tag || stored.meta.tags.includes(tag)) yield this.createBridgeConnection(stored.meta);
|
|
647
|
+
}
|
|
648
|
+
activeBridge(connectionId) {
|
|
649
|
+
const context = _classPrivateFieldGet2(_bridgeContext, this).getStore();
|
|
650
|
+
if (connectionId !== void 0 && context?.connectionId !== connectionId) return;
|
|
651
|
+
return context?.bridge;
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Route a virtual sub-agent connection operation through its live frame
|
|
655
|
+
* bridge, or through the durable root Agent after that frame completes.
|
|
656
|
+
* All operations share one per-connection queue. Facet broadcasts wait for
|
|
657
|
+
* older queued operations; failures do not block later work.
|
|
658
|
+
*/
|
|
659
|
+
routeConnectionOperation(connectionId, operationName, operation) {
|
|
660
|
+
const activeBridge = this.activeBridge(connectionId);
|
|
661
|
+
const previousConnectionOperation = _classPrivateFieldGet2(_connectionOperationTails, this).get(connectionId);
|
|
662
|
+
let pending;
|
|
663
|
+
if (activeBridge && !previousConnectionOperation) try {
|
|
664
|
+
pending = Promise.resolve(operation(activeBridge)).then(() => {});
|
|
665
|
+
} catch (error) {
|
|
666
|
+
pending = Promise.reject(error);
|
|
667
|
+
}
|
|
668
|
+
else pending = (previousConnectionOperation ?? Promise.resolve()).then(async () => {
|
|
669
|
+
await operation(new RootDynamicAgentConnectionBridge(await this.rootAlarmOwner(), connectionId));
|
|
670
|
+
});
|
|
671
|
+
const completion = pending.catch((error) => {
|
|
672
|
+
_assertClassBrand(_DynamicAgentsInternal_brand, this, _reportConnectionOperationFailure).call(this, connectionId, operationName, error);
|
|
673
|
+
});
|
|
674
|
+
_classPrivateFieldGet2(_connectionOperationTails, this).set(connectionId, completion);
|
|
675
|
+
_classPrivateFieldGet2(_host, this).ctx.waitUntil(completion);
|
|
676
|
+
completion.then(() => {
|
|
677
|
+
if (_classPrivateFieldGet2(_connectionOperationTails, this).get(connectionId) === completion) _classPrivateFieldGet2(_connectionOperationTails, this).delete(connectionId);
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Route a facet broadcast after every older connection operation.
|
|
682
|
+
*
|
|
683
|
+
* This barrier is intentionally one-way: facet startup can broadcast before
|
|
684
|
+
* a child connection has finished initializing its tags and protocol flags.
|
|
685
|
+
* Making those later connection operations wait would let the next frame
|
|
686
|
+
* observe stale root-owned metadata.
|
|
687
|
+
*/
|
|
688
|
+
async routeBroadcast(ownerPath, message, without, upstreamBridge) {
|
|
689
|
+
const activeBridge = upstreamBridge ?? this.activeBridge();
|
|
690
|
+
const previousOperations = /* @__PURE__ */ new Set([..._classPrivateFieldGet2(_broadcastOperationTail, this) ? [_classPrivateFieldGet2(_broadcastOperationTail, this)] : [], ..._classPrivateFieldGet2(_connectionOperationTails, this).values()]);
|
|
691
|
+
let pending;
|
|
692
|
+
if (activeBridge && previousOperations.size === 0) try {
|
|
693
|
+
pending = Promise.resolve(activeBridge.broadcast(ownerPath, message, without));
|
|
694
|
+
} catch (error) {
|
|
695
|
+
pending = Promise.reject(error);
|
|
696
|
+
}
|
|
697
|
+
else pending = Promise.all(previousOperations).then(async () => {
|
|
698
|
+
await (await this.rootAlarmOwner())._cf_broadcastToSubAgent(ownerPath, message, without);
|
|
699
|
+
});
|
|
700
|
+
const completion = pending.catch((error) => {
|
|
701
|
+
console.error("[Agent] Sub-agent broadcast operation failed:", {
|
|
702
|
+
operation: "broadcast",
|
|
703
|
+
error
|
|
704
|
+
});
|
|
705
|
+
});
|
|
706
|
+
_classPrivateFieldSet2(_broadcastOperationTail, this, completion);
|
|
707
|
+
_classPrivateFieldGet2(_host, this).ctx.waitUntil(completion);
|
|
708
|
+
completion.then(() => {
|
|
709
|
+
if (_classPrivateFieldGet2(_broadcastOperationTail, this) === completion) _classPrivateFieldSet2(_broadcastOperationTail, this, void 0);
|
|
710
|
+
});
|
|
711
|
+
await completion;
|
|
712
|
+
}
|
|
713
|
+
async broadcastToParent(message, without) {
|
|
714
|
+
await this.routeBroadcast(_classPrivateFieldGet2(_host, this).selfPath, message, without);
|
|
715
|
+
}
|
|
716
|
+
async broadcastToPath(ownerPath, message, without) {
|
|
717
|
+
if (_classPrivateFieldGet2(_host, this)._isFacet) {
|
|
718
|
+
await this.routeBroadcast(ownerPath, message, without);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
for (const connection of _classPrivateFieldGet2(_host, this)._webSockets.getConnections()) {
|
|
722
|
+
if (without?.includes(connection.id)) continue;
|
|
723
|
+
const targetPath = this.connectionTargetPath(connection);
|
|
724
|
+
if (!targetPath) continue;
|
|
725
|
+
if (!this.isSameAgentPath(targetPath, ownerPath)) continue;
|
|
726
|
+
connection.send(message);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
async connectionMetas(ownerPath) {
|
|
730
|
+
const metas = [];
|
|
731
|
+
for (const connection of _classPrivateFieldGet2(_host, this)._webSockets.getConnections()) {
|
|
732
|
+
const meta = _assertClassBrand(_DynamicAgentsInternal_brand, this, _connectionMetaForPath).call(this, connection, ownerPath);
|
|
733
|
+
if (meta) metas.push(meta);
|
|
734
|
+
}
|
|
735
|
+
return metas;
|
|
736
|
+
}
|
|
737
|
+
async sendToConnection(connectionId, message) {
|
|
738
|
+
const connection = _classPrivateFieldGet2(_host, this)._webSockets.getConnection(connectionId);
|
|
739
|
+
if (!connection || !this.connectionHasChildTarget(connection)) return;
|
|
740
|
+
connection.send(message);
|
|
741
|
+
}
|
|
742
|
+
async closeConnection(connectionId, code, reason) {
|
|
743
|
+
const connection = _classPrivateFieldGet2(_host, this)._webSockets.getConnection(connectionId);
|
|
744
|
+
if (!connection || !this.connectionHasChildTarget(connection)) return;
|
|
745
|
+
connection.close(code, reason);
|
|
746
|
+
}
|
|
747
|
+
async setConnectionState(connectionId, state) {
|
|
748
|
+
const connection = _classPrivateFieldGet2(_host, this)._webSockets.getConnection(connectionId);
|
|
749
|
+
if (!connection || !this.connectionHasChildTarget(connection)) return null;
|
|
750
|
+
_classPrivateFieldGet2(_host, this)._ensureConnectionWrapped(connection);
|
|
751
|
+
connection.setState(state);
|
|
752
|
+
return this.getForwardedState(connection);
|
|
753
|
+
}
|
|
754
|
+
connectionTargetPath(connection) {
|
|
755
|
+
_classPrivateFieldGet2(_host, this)._ensureConnectionWrapped(connection);
|
|
756
|
+
const outerUri = _classPrivateFieldGet2(_host, this)._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY);
|
|
757
|
+
if (typeof outerUri !== "string") return null;
|
|
758
|
+
return _assertClassBrand(_DynamicAgentsInternal_brand, this, _pathFromOuterUri).call(this, outerUri)?.path ?? null;
|
|
759
|
+
}
|
|
760
|
+
isSameAgentPath(a, b) {
|
|
761
|
+
if (a.length !== b.length) return false;
|
|
762
|
+
return a.every((step, index) => step.className === b[index]?.className && step.name === b[index]?.name);
|
|
763
|
+
}
|
|
764
|
+
connectionHasChildTarget(connection) {
|
|
765
|
+
_classPrivateFieldGet2(_host, this)._ensureConnectionWrapped(connection);
|
|
766
|
+
return typeof _classPrivateFieldGet2(_host, this)._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY) === "string";
|
|
767
|
+
}
|
|
768
|
+
connectionTargetsChild(connection) {
|
|
769
|
+
if (!connection.uri) return false;
|
|
770
|
+
const ctx = _classPrivateFieldGet2(_host, this).ctx;
|
|
771
|
+
return parseSubAgentPath(connection.uri, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 }) !== null;
|
|
772
|
+
}
|
|
773
|
+
requestTargetsChild(request) {
|
|
774
|
+
const ctx = _classPrivateFieldGet2(_host, this).ctx;
|
|
775
|
+
return parseSubAgentPath(request.url, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 }) !== null;
|
|
776
|
+
}
|
|
777
|
+
async forwardWebSocketConnect(connection, request, options) {
|
|
778
|
+
const routed = await _assertClassBrand(_DynamicAgentsInternal_brand, this, _resolveConnection).call(this, connection, request, options);
|
|
779
|
+
if (!routed) return false;
|
|
780
|
+
await routed.child._cf_handleSubAgentWebSocketConnect(_assertClassBrand(_DynamicAgentsInternal_brand, this, _createConnectionBridge).call(this, connection), routed.meta);
|
|
781
|
+
return true;
|
|
782
|
+
}
|
|
783
|
+
async forwardWebSocketMessage(connection, message, replyBridge) {
|
|
784
|
+
const routed = await _assertClassBrand(_DynamicAgentsInternal_brand, this, _resolveConnection).call(this, connection);
|
|
785
|
+
if (!routed) return false;
|
|
786
|
+
const bridge = _assertClassBrand(_DynamicAgentsInternal_brand, this, _createConnectionBridge).call(this, connection);
|
|
787
|
+
await routed.child._cf_handleSubAgentWebSocketMessage(message, bridge, routed.meta, replyBridge ?? bridge);
|
|
788
|
+
return true;
|
|
789
|
+
}
|
|
790
|
+
async forwardWebSocketClose(connection, code, reason, wasClean) {
|
|
791
|
+
const routed = await _assertClassBrand(_DynamicAgentsInternal_brand, this, _resolveConnection).call(this, connection);
|
|
792
|
+
if (!routed) return false;
|
|
793
|
+
await routed.child._cf_handleSubAgentWebSocketClose(code, reason, wasClean, _assertClassBrand(_DynamicAgentsInternal_brand, this, _createConnectionBridge).call(this, connection), routed.meta);
|
|
794
|
+
return true;
|
|
795
|
+
}
|
|
796
|
+
async handleWebSocketConnect(bridge, meta) {
|
|
797
|
+
await this.runWithBridge(bridge, meta.id, async () => {
|
|
798
|
+
const connection = this.createBridgeConnection(meta);
|
|
799
|
+
const request = new Request(meta.uri ?? "http://placeholder/", { headers: meta.requestHeaders });
|
|
800
|
+
if (await this.forwardWebSocketConnect(connection, request, { gate: true })) return;
|
|
801
|
+
if (_classPrivateFieldGet2(_host, this).shouldConnectionBeReadonly(connection, { request })) _classPrivateFieldGet2(_host, this).setConnectionReadonly(connection, true);
|
|
802
|
+
if (!_classPrivateFieldGet2(_host, this).shouldSendProtocolMessages(connection, { request })) _classPrivateFieldGet2(_host, this)._setConnectionNoProtocol(connection);
|
|
803
|
+
const childTags = await _classPrivateFieldGet2(_host, this).getConnectionTags(connection, { request });
|
|
804
|
+
connection.tags = [connection.id, ...childTags.filter((tag) => tag !== connection.id)];
|
|
805
|
+
this.storeVirtualConnection(connection);
|
|
806
|
+
await _classPrivateFieldGet2(_host, this).onConnect(connection, { request });
|
|
807
|
+
this.storeVirtualConnection(connection);
|
|
808
|
+
await _classPrivateFieldGet2(_connectionOperationTails, this).get(meta.id);
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
async handleWebSocketMessage(message, bridge, meta, replyBridge = bridge) {
|
|
812
|
+
const connection = this.createBridgeConnection(meta);
|
|
813
|
+
this.storeVirtualConnection(connection);
|
|
814
|
+
const replyContext = { bridge: replyBridge };
|
|
815
|
+
try {
|
|
816
|
+
await dynamicAgentRpcReplyContext.run(replyContext, () => this.runWithBridge(bridge, meta.id, () => _classPrivateFieldGet2(_host, this).onMessage(connection, message)));
|
|
817
|
+
} finally {
|
|
818
|
+
replyContext.bridge = void 0;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
async handleWebSocketClose(code, reason, wasClean, bridge, meta) {
|
|
822
|
+
const connection = this.createBridgeConnection(meta);
|
|
823
|
+
this.storeVirtualConnection(connection);
|
|
824
|
+
await this.runWithBridge(bridge, meta.id, () => _classPrivateFieldGet2(_host, this).onClose(connection, code, reason, wasClean));
|
|
825
|
+
_classPrivateFieldGet2(_virtualConnections, this).delete(meta.id);
|
|
826
|
+
}
|
|
827
|
+
async runWithBridge(bridge, connectionId, fn) {
|
|
828
|
+
const context = {
|
|
829
|
+
bridge,
|
|
830
|
+
connectionId
|
|
831
|
+
};
|
|
832
|
+
try {
|
|
833
|
+
return await _classPrivateFieldGet2(_bridgeContext, this).run(context, fn);
|
|
834
|
+
} finally {
|
|
835
|
+
context.bridge = void 0;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
createBridgeConnection(meta) {
|
|
839
|
+
let stored = _classPrivateFieldGet2(_virtualConnections, this).get(meta.id);
|
|
840
|
+
if (stored) {
|
|
841
|
+
stored.meta = meta;
|
|
842
|
+
if (stored.connection) {
|
|
843
|
+
stored.connection.uri = meta.uri;
|
|
844
|
+
stored.connection.tags = meta.tags;
|
|
845
|
+
return stored.connection;
|
|
846
|
+
}
|
|
847
|
+
} else {
|
|
848
|
+
stored = { meta };
|
|
849
|
+
_classPrivateFieldGet2(_virtualConnections, this).set(meta.id, stored);
|
|
850
|
+
}
|
|
851
|
+
const owner = this;
|
|
852
|
+
const getStored = () => _classPrivateFieldGet2(_virtualConnections, this).get(meta.id) ?? stored;
|
|
853
|
+
const updateStoredState = (nextState) => {
|
|
854
|
+
const current = _classPrivateFieldGet2(_virtualConnections, this).get(meta.id);
|
|
855
|
+
if (current) current.meta = {
|
|
856
|
+
...current.meta,
|
|
857
|
+
state: nextState
|
|
858
|
+
};
|
|
859
|
+
};
|
|
860
|
+
const connection = {
|
|
861
|
+
id: meta.id,
|
|
862
|
+
uri: meta.uri,
|
|
863
|
+
tags: meta.tags,
|
|
864
|
+
get state() {
|
|
865
|
+
return getStored().meta.state;
|
|
866
|
+
},
|
|
867
|
+
setState(next) {
|
|
868
|
+
const currentState = getStored().meta.state;
|
|
869
|
+
const state = typeof next === "function" ? next(currentState) : next;
|
|
870
|
+
updateStoredState(state);
|
|
871
|
+
owner.routeConnectionOperation(meta.id, "setState", (bridge) => bridge.setState(state));
|
|
872
|
+
return state;
|
|
873
|
+
},
|
|
874
|
+
send(message) {
|
|
875
|
+
owner.routeConnectionOperation(meta.id, "send", (bridge) => bridge.send(message));
|
|
876
|
+
},
|
|
877
|
+
close(code, reason) {
|
|
878
|
+
owner.routeConnectionOperation(meta.id, "close", (bridge) => bridge.close(code, reason));
|
|
879
|
+
},
|
|
880
|
+
addEventListener() {},
|
|
881
|
+
removeEventListener() {}
|
|
882
|
+
};
|
|
883
|
+
stored.connection = connection;
|
|
884
|
+
_classPrivateFieldGet2(_host, this)._ensureConnectionWrapped(connection);
|
|
885
|
+
return connection;
|
|
886
|
+
}
|
|
887
|
+
storeVirtualConnection(connection) {
|
|
888
|
+
_classPrivateFieldGet2(_host, this)._unsafe_setConnectionFlag(connection, CF_SUB_AGENT_TAGS_KEY, [...connection.tags]);
|
|
889
|
+
const stored = _classPrivateFieldGet2(_virtualConnections, this).get(connection.id);
|
|
890
|
+
_classPrivateFieldGet2(_virtualConnections, this).set(connection.id, {
|
|
891
|
+
meta: {
|
|
892
|
+
id: connection.id,
|
|
893
|
+
uri: connection.uri,
|
|
894
|
+
tags: [...connection.tags],
|
|
895
|
+
state: this.getRawConnectionState(connection)
|
|
896
|
+
},
|
|
897
|
+
connection: stored?.connection ?? connection
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Restore the facet identity persisted by `init` (wake after
|
|
902
|
+
* hibernation), then best-effort hydrate the virtual connections
|
|
903
|
+
* from the root's WebSocket state.
|
|
904
|
+
*/
|
|
905
|
+
async restoreFacetContext() {
|
|
906
|
+
if (await _classPrivateFieldGet2(_host, this).ctx.storage.get("cf_agents_is_facet")) _classPrivateFieldGet2(_host, this)._isFacet = true;
|
|
907
|
+
const storedFacetName = await _classPrivateFieldGet2(_host, this).ctx.storage.get("cf_agents_facet_name");
|
|
908
|
+
if (typeof storedFacetName === "string") _classPrivateFieldGet2(_host, this)._facetName = storedFacetName;
|
|
909
|
+
const storedParentPath = await _classPrivateFieldGet2(_host, this).ctx.storage.get("cf_agents_parent_path");
|
|
910
|
+
if (isValidParentPath(storedParentPath)) _classPrivateFieldGet2(_host, this)._parentPath = storedParentPath;
|
|
911
|
+
try {
|
|
912
|
+
await this.hydrateConnectionsFromRoot();
|
|
913
|
+
} catch (error) {
|
|
914
|
+
console.warn("[Agent] Unable to hydrate sub-agent WebSocket connections:", error);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
async hydrateConnectionsFromRoot() {
|
|
918
|
+
if (!_classPrivateFieldGet2(_host, this)._isFacet || _classPrivateFieldGet2(_host, this)._parentPath.length === 0) return;
|
|
919
|
+
if (this.rootResolvesToSelf()) return;
|
|
920
|
+
const metas = await (await this.rootAlarmOwner())._cf_subAgentConnectionMetas(_classPrivateFieldGet2(_host, this).selfPath);
|
|
921
|
+
for (const meta of metas) _classPrivateFieldGet2(_virtualConnections, this).set(meta.id, { meta });
|
|
922
|
+
}
|
|
923
|
+
getRawConnectionState(connection) {
|
|
924
|
+
_classPrivateFieldGet2(_host, this)._ensureConnectionWrapped(connection);
|
|
925
|
+
return _classPrivateFieldGet2(_host, this)._rawStateAccessors.get(connection)?.getRaw() ?? null;
|
|
926
|
+
}
|
|
927
|
+
getForwardedState(connection) {
|
|
928
|
+
const raw = this.getRawConnectionState(connection);
|
|
929
|
+
if (raw == null || typeof raw !== "object") return raw;
|
|
930
|
+
const { [CF_SUB_AGENT_OUTER_URL_KEY]: _, ...rest } = raw;
|
|
931
|
+
return Object.keys(rest).length > 0 ? rest : null;
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Resolve the facet Fetcher for the match and forward the request to
|
|
935
|
+
* it with `/sub/{class}/{name}` stripped.
|
|
936
|
+
*/
|
|
937
|
+
async forward(req, match) {
|
|
938
|
+
let fetcher;
|
|
939
|
+
try {
|
|
940
|
+
fetcher = await this.resolve(match.childClass, match.childName);
|
|
941
|
+
} catch (err) {
|
|
942
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
943
|
+
console.error("[agents] sub-agent route failed:", message);
|
|
944
|
+
if (/null character/i.test(message) || /reserved/i.test(message)) return new Response("Bad Request", { status: 400 });
|
|
945
|
+
return new Response("Not Found", { status: 404 });
|
|
946
|
+
}
|
|
947
|
+
const rewritten = new URL(req.url);
|
|
948
|
+
rewritten.pathname = match.remainingPath;
|
|
949
|
+
const forwardedHeaders = new Headers(req.headers);
|
|
950
|
+
const forwardedInit = {
|
|
951
|
+
method: req.method,
|
|
952
|
+
headers: forwardedHeaders
|
|
953
|
+
};
|
|
954
|
+
if (req.headers.get("Upgrade")?.toLowerCase() === "websocket") forwardedHeaders.set(SUB_AGENT_OUTER_URL_HEADER, req.url);
|
|
955
|
+
if (req.body && req.method !== "GET" && req.method !== "HEAD") forwardedInit.body = req.body;
|
|
956
|
+
const forwarded = new Request(rewritten, forwardedInit);
|
|
957
|
+
return fetcher.fetch(forwarded);
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Bridge used by `getSubAgentByName`: resolve the facet and dispatch
|
|
961
|
+
* one RPC method. Stateless — no cached references.
|
|
962
|
+
*/
|
|
963
|
+
async invoke(className, name, method, args) {
|
|
964
|
+
const stub = await this.resolve(className, name);
|
|
965
|
+
return await this.invokeStubMethod(stub, className, method, args);
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Bridge used by `parentAgent()` when the requested parent is itself
|
|
969
|
+
* a facet (and therefore has no top-level env namespace). The root
|
|
970
|
+
* receives the full root-first target path, then each hop delegates
|
|
971
|
+
* to the next facet using that facet's own `ctx.facets`.
|
|
972
|
+
*/
|
|
973
|
+
async invokePath(path, method, args) {
|
|
974
|
+
const [self, next, ...rest] = path;
|
|
975
|
+
if (!self) throw new Error(`Sub-agent path invocation requires a non-empty path.`);
|
|
976
|
+
const ownClassName = _classPrivateFieldGet2(_host, this).constructor.name;
|
|
977
|
+
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}").`);
|
|
978
|
+
if (!next) return await this.invokeStubMethod(_classPrivateFieldGet2(_host, this), ownClassName, method, args);
|
|
979
|
+
const child = await this.resolve(next.className, next.name);
|
|
980
|
+
if (rest.length === 0) return await this.invokeStubMethod(child, next.className, method, args);
|
|
981
|
+
return await child._cf_invokeSubAgentPath([next, ...rest], method, args);
|
|
982
|
+
}
|
|
983
|
+
async invokeStubMethod(stub, className, method, args) {
|
|
984
|
+
const handle = stub;
|
|
985
|
+
if (typeof handle[method] !== "function") throw new Error(`Method "${method}" not found on ${className}.`);
|
|
986
|
+
return await handle[method](...args);
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Initialize the host agent as a facet in a single RPC. Runs entirely
|
|
990
|
+
* inside the child's isolate, so every storage write and `onStart()`
|
|
991
|
+
* I/O is owned by the child DO.
|
|
992
|
+
*/
|
|
993
|
+
async init(name, parentPath = [], identityName = name) {
|
|
994
|
+
const routedName = _classPrivateFieldGet2(_host, this).lifecycle.name;
|
|
995
|
+
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.`);
|
|
996
|
+
_classPrivateFieldGet2(_host, this)._isFacet = true;
|
|
997
|
+
_classPrivateFieldGet2(_host, this)._facetName = name;
|
|
998
|
+
_classPrivateFieldGet2(_host, this)._parentPath = parentPath;
|
|
999
|
+
await Promise.all([
|
|
1000
|
+
_classPrivateFieldGet2(_host, this).ctx.storage.put("cf_agents_is_facet", true),
|
|
1001
|
+
_classPrivateFieldGet2(_host, this).ctx.storage.put("cf_agents_facet_name", name),
|
|
1002
|
+
_classPrivateFieldGet2(_host, this).ctx.storage.put("cf_agents_parent_path", parentPath)
|
|
1003
|
+
]);
|
|
1004
|
+
await _classPrivateFieldGet2(_host, this).__unsafe_ensureInitialized();
|
|
1005
|
+
}
|
|
1006
|
+
/** Remove a completed facet fiber from the root-side index. */
|
|
1007
|
+
async unregisterRun(ownerPath, runId) {
|
|
1008
|
+
const ownerPathKey = agentPathKey(ownerPath);
|
|
1009
|
+
_classPrivateFieldGet2(_host, this).sql`
|
|
1010
|
+
DELETE FROM cf_agents_facet_runs
|
|
1011
|
+
WHERE owner_path_key IS ${ownerPathKey}
|
|
1012
|
+
AND run_id = ${runId}
|
|
1013
|
+
`;
|
|
1014
|
+
await _classPrivateFieldGet2(_host, this)._syncHostJobs();
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
function _reportConnectionOperationFailure(connectionId, operation, error) {
|
|
1018
|
+
console.error("[Agent] Sub-agent connection operation failed:", {
|
|
1019
|
+
connectionId,
|
|
1020
|
+
operation,
|
|
1021
|
+
error
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
function _connectionMetaForPath(connection, ownerPath) {
|
|
1025
|
+
_classPrivateFieldGet2(_host, this)._ensureConnectionWrapped(connection);
|
|
1026
|
+
const outerUri = _classPrivateFieldGet2(_host, this)._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY);
|
|
1027
|
+
if (typeof outerUri !== "string") return null;
|
|
1028
|
+
const target = _assertClassBrand(_DynamicAgentsInternal_brand, this, _pathFromOuterUri).call(this, outerUri, ownerPath);
|
|
1029
|
+
if (!target) return null;
|
|
1030
|
+
const raw = this.getRawConnectionState(connection);
|
|
1031
|
+
const rawTags = raw != null && typeof raw === "object" ? raw[CF_SUB_AGENT_TAGS_KEY] : void 0;
|
|
1032
|
+
const tags = Array.isArray(rawTags) ? rawTags.filter((tag) => typeof tag === "string") : [...connection.tags];
|
|
1033
|
+
return {
|
|
1034
|
+
id: connection.id,
|
|
1035
|
+
uri: target.uri,
|
|
1036
|
+
tags,
|
|
1037
|
+
state: this.getForwardedState(connection)
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
function _pathFromOuterUri(outerUri, stopAt) {
|
|
1041
|
+
const ctx = _classPrivateFieldGet2(_host, this).ctx;
|
|
1042
|
+
const knownClasses = ctx.exports ? Object.keys(ctx.exports) : void 0;
|
|
1043
|
+
const path = [..._classPrivateFieldGet2(_host, this).selfPath];
|
|
1044
|
+
let currentUrl = outerUri;
|
|
1045
|
+
while (true) {
|
|
1046
|
+
const match = parseSubAgentPath(currentUrl, { knownClasses });
|
|
1047
|
+
if (!match) break;
|
|
1048
|
+
path.push({
|
|
1049
|
+
className: match.childClass,
|
|
1050
|
+
name: match.childName
|
|
1051
|
+
});
|
|
1052
|
+
const rewritten = new URL(currentUrl);
|
|
1053
|
+
rewritten.pathname = match.remainingPath;
|
|
1054
|
+
currentUrl = rewritten.toString();
|
|
1055
|
+
if (stopAt && this.isSameAgentPath(path, stopAt)) return {
|
|
1056
|
+
path,
|
|
1057
|
+
uri: currentUrl
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
if (path.length === _classPrivateFieldGet2(_host, this).selfPath.length) return null;
|
|
1061
|
+
if (stopAt) return null;
|
|
1062
|
+
return {
|
|
1063
|
+
path,
|
|
1064
|
+
uri: currentUrl
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
function _createConnectionBridge(connection) {
|
|
1068
|
+
const upstreamBroadcastBridge = _classPrivateFieldGet2(_host, this)._isFacet ? this.activeBridge(connection.id) : void 0;
|
|
1069
|
+
return new DynamicAgentConnectionBridge(connection, (ownerPath, message, without) => {
|
|
1070
|
+
if (upstreamBroadcastBridge) return this.routeBroadcast(ownerPath, message, without, upstreamBroadcastBridge);
|
|
1071
|
+
return _classPrivateFieldGet2(_host, this)._cf_broadcastToSubAgent(ownerPath, message, without);
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
async function _resolveConnection(connection, request, options = { gate: false }) {
|
|
1075
|
+
_classPrivateFieldGet2(_host, this)._ensureConnectionWrapped(connection);
|
|
1076
|
+
const outerUri = _classPrivateFieldGet2(_host, this)._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY);
|
|
1077
|
+
const uri = typeof outerUri === "string" ? outerUri : connection.uri;
|
|
1078
|
+
if (!uri) return null;
|
|
1079
|
+
const ctx = _classPrivateFieldGet2(_host, this).ctx;
|
|
1080
|
+
let match = parseSubAgentPath(uri, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 });
|
|
1081
|
+
if (!match) return null;
|
|
1082
|
+
if (_classPrivateFieldGet2(_host, this)._ParentClass.name === match.childClass && _classPrivateFieldGet2(_host, this).name === match.childName) {
|
|
1083
|
+
const tailUri = new URL(uri);
|
|
1084
|
+
tailUri.pathname = match.remainingPath;
|
|
1085
|
+
match = parseSubAgentPath(tailUri.toString(), { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 });
|
|
1086
|
+
if (!match) return null;
|
|
1087
|
+
}
|
|
1088
|
+
let forwardReq = request;
|
|
1089
|
+
if (request && options.gate) {
|
|
1090
|
+
const decision = await _classPrivateFieldGet2(_host, this).onBeforeSubAgent(request, {
|
|
1091
|
+
className: match.childClass,
|
|
1092
|
+
name: match.childName
|
|
1093
|
+
});
|
|
1094
|
+
if (decision instanceof Response) {
|
|
1095
|
+
connection.close(1008, "Sub-agent connection rejected");
|
|
1096
|
+
return null;
|
|
1097
|
+
}
|
|
1098
|
+
forwardReq = decision instanceof Request ? decision : request;
|
|
1099
|
+
}
|
|
1100
|
+
const child = await this.resolve(match.childClass, match.childName);
|
|
1101
|
+
const childUri = new URL(forwardReq?.url ?? uri);
|
|
1102
|
+
childUri.pathname = match.remainingPath;
|
|
1103
|
+
const raw = this.getRawConnectionState(connection);
|
|
1104
|
+
const rawTags = raw != null && typeof raw === "object" ? raw[CF_SUB_AGENT_TAGS_KEY] : void 0;
|
|
1105
|
+
const tags = Array.isArray(rawTags) ? rawTags.filter((tag) => typeof tag === "string") : [...connection.tags];
|
|
1106
|
+
return {
|
|
1107
|
+
child,
|
|
1108
|
+
meta: {
|
|
1109
|
+
id: connection.id,
|
|
1110
|
+
uri: childUri.toString(),
|
|
1111
|
+
tags,
|
|
1112
|
+
state: this.getForwardedState(connection),
|
|
1113
|
+
requestHeaders: forwardReq ? [...forwardReq.headers] : void 0
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
//#endregion
|
|
1118
|
+
//#region src/dynamic-agents/api.ts
|
|
1119
|
+
var _internal = /* @__PURE__ */ new WeakMap();
|
|
1120
|
+
/**
|
|
1121
|
+
* The public dynamic-agents capability surface, reached via
|
|
1122
|
+
* `this.dynamicAgents` on an Agent.
|
|
1123
|
+
*
|
|
1124
|
+
* A dynamic agent is a facet-backed child: it runs in its own isolate
|
|
1125
|
+
* with its own SQLite database, colocated with — and supervised by —
|
|
1126
|
+
* its parent Agent. Use dynamic agents for code whose class or
|
|
1127
|
+
* lifecycle the parent owns (dynamically-loaded/generated code,
|
|
1128
|
+
* per-run tool agents, sandboxed components). For independent peers
|
|
1129
|
+
* such as one-DO-per-chat, use `getAgentByName` instead.
|
|
1130
|
+
*
|
|
1131
|
+
* @experimental The API surface may change before stabilizing.
|
|
1132
|
+
*/
|
|
1133
|
+
var DynamicAgents = class {
|
|
1134
|
+
/** @internal Constructed by Agent; do not instantiate directly. */
|
|
1135
|
+
constructor(internal) {
|
|
1136
|
+
_classPrivateFieldInitSpec(this, _internal, void 0);
|
|
1137
|
+
_classPrivateFieldSet2(_internal, this, internal);
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Get (creating or waking if needed) the dynamic agent of the given
|
|
1141
|
+
* class and name, as a typed RPC stub. Idempotent — repeated calls
|
|
1142
|
+
* with the same class and name return the same child.
|
|
1143
|
+
*
|
|
1144
|
+
* @experimental
|
|
1145
|
+
*/
|
|
1146
|
+
async get(cls, name) {
|
|
1147
|
+
return await _classPrivateFieldGet2(_internal, this).resolve(cls.name, name);
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Forcefully abort a running dynamic agent. The child stops
|
|
1151
|
+
* executing immediately and restarts on the next {@link get} call;
|
|
1152
|
+
* its storage is preserved. Transitively aborts the child's own
|
|
1153
|
+
* children. Pending RPC calls receive the reason as an error.
|
|
1154
|
+
*
|
|
1155
|
+
* @experimental
|
|
1156
|
+
*/
|
|
1157
|
+
abort(cls, name, reason) {
|
|
1158
|
+
_classPrivateFieldGet2(_internal, this).abort(cls.name, name, reason);
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Delete a dynamic agent: abort it if running, then permanently wipe
|
|
1162
|
+
* its storage. Transitively deletes the child's own children.
|
|
1163
|
+
*
|
|
1164
|
+
* @experimental
|
|
1165
|
+
*/
|
|
1166
|
+
delete(cls, name) {
|
|
1167
|
+
return _classPrivateFieldGet2(_internal, this).delete(cls.name, name);
|
|
1168
|
+
}
|
|
1169
|
+
has(classOrName, name) {
|
|
1170
|
+
const className = typeof classOrName === "string" ? classOrName : classOrName.name;
|
|
1171
|
+
return _classPrivateFieldGet2(_internal, this).registry.has(className, name);
|
|
1172
|
+
}
|
|
1173
|
+
list(classOrName) {
|
|
1174
|
+
const className = typeof classOrName === "string" ? classOrName : classOrName?.name;
|
|
1175
|
+
return _classPrivateFieldGet2(_internal, this).registry.list(className);
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
//#endregion
|
|
23
1179
|
//#region src/observability/agent-span-attributes.ts
|
|
24
1180
|
function agentSpanAttributes(input) {
|
|
25
1181
|
return {
|
|
@@ -42,9 +1198,6 @@ let _Symbol$dispose;
|
|
|
42
1198
|
function runInInvocation(store, body, options) {
|
|
43
1199
|
return __DO_NOT_USE_WILL_BREAK__agentContext.run(store, () => withInvocationScope(body, options));
|
|
44
1200
|
}
|
|
45
|
-
function isClosedWebSocketSendError(error) {
|
|
46
|
-
return error instanceof TypeError && error.message.includes("WebSocket send() after close");
|
|
47
|
-
}
|
|
48
1201
|
function sendRpcResponseIfOpen(connection, response) {
|
|
49
1202
|
try {
|
|
50
1203
|
connection.send(JSON.stringify(response));
|
|
@@ -54,47 +1207,6 @@ function sendRpcResponseIfOpen(connection, response) {
|
|
|
54
1207
|
throw error;
|
|
55
1208
|
}
|
|
56
1209
|
}
|
|
57
|
-
const subAgentRpcReplyContext = new AsyncLocalStorage();
|
|
58
|
-
function sendFacetRpcResponseIfOpen(target, response) {
|
|
59
|
-
try {
|
|
60
|
-
return {
|
|
61
|
-
sent: true,
|
|
62
|
-
completion: Promise.resolve(target.send(JSON.stringify(response))).catch((error) => {
|
|
63
|
-
if (!isClosedWebSocketSendError(error)) console.error("[Agent] Facet RPC response delivery failed:", error);
|
|
64
|
-
})
|
|
65
|
-
};
|
|
66
|
-
} catch (error) {
|
|
67
|
-
if (isClosedWebSocketSendError(error)) return {
|
|
68
|
-
sent: false,
|
|
69
|
-
completion: Promise.resolve()
|
|
70
|
-
};
|
|
71
|
-
throw error;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
const facetStreamingResponseDeliveryStates = /* @__PURE__ */ new WeakMap();
|
|
75
|
-
function createStreamingResponse(connection, id, facetReplyTarget) {
|
|
76
|
-
const stream = new StreamingResponse(connection, id);
|
|
77
|
-
if (facetReplyTarget) facetStreamingResponseDeliveryStates.set(stream, {
|
|
78
|
-
replyTarget: facetReplyTarget,
|
|
79
|
-
pending: /* @__PURE__ */ new Set()
|
|
80
|
-
});
|
|
81
|
-
return stream;
|
|
82
|
-
}
|
|
83
|
-
function trackFacetStreamingResponseDelivery(stream, completion) {
|
|
84
|
-
const state = facetStreamingResponseDeliveryStates.get(stream);
|
|
85
|
-
if (!state) return;
|
|
86
|
-
state.pending.add(completion);
|
|
87
|
-
completion.finally(() => state.pending.delete(completion));
|
|
88
|
-
}
|
|
89
|
-
async function waitForFacetStreamingResponseDeliveries(stream) {
|
|
90
|
-
const state = facetStreamingResponseDeliveryStates.get(stream);
|
|
91
|
-
if (!state) return;
|
|
92
|
-
try {
|
|
93
|
-
await Promise.all(state.pending);
|
|
94
|
-
} finally {
|
|
95
|
-
facetStreamingResponseDeliveryStates.delete(stream);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
1210
|
/**
|
|
99
1211
|
* Type guard for RPC request messages
|
|
100
1212
|
*/
|
|
@@ -107,84 +1219,14 @@ function isRPCRequest(msg) {
|
|
|
107
1219
|
function isStateUpdateMessage(msg) {
|
|
108
1220
|
return typeof msg === "object" && msg !== null && "type" in msg && msg.type === "cf_agent_state" && "state" in msg;
|
|
109
1221
|
}
|
|
110
|
-
const callableMetadata = /* @__PURE__ */ new WeakMap();
|
|
111
|
-
var _connection2 = /* @__PURE__ */ new WeakMap();
|
|
112
|
-
var _broadcast = /* @__PURE__ */ new WeakMap();
|
|
113
|
-
var SubAgentConnectionBridge = class extends RpcTarget {
|
|
114
|
-
constructor(connection, broadcast) {
|
|
115
|
-
super();
|
|
116
|
-
_classPrivateFieldInitSpec(this, _connection2, void 0);
|
|
117
|
-
_classPrivateFieldInitSpec(this, _broadcast, void 0);
|
|
118
|
-
_classPrivateFieldSet2(_connection2, this, connection);
|
|
119
|
-
_classPrivateFieldSet2(_broadcast, this, broadcast);
|
|
120
|
-
}
|
|
121
|
-
send(message) {
|
|
122
|
-
_classPrivateFieldGet2(_connection2, this).send(message);
|
|
123
|
-
}
|
|
124
|
-
close(code, reason) {
|
|
125
|
-
_classPrivateFieldGet2(_connection2, this).close(code, reason);
|
|
126
|
-
}
|
|
127
|
-
setState(state) {
|
|
128
|
-
return _classPrivateFieldGet2(_connection2, this).setState(state);
|
|
129
|
-
}
|
|
130
|
-
broadcast(ownerPath, message, without) {
|
|
131
|
-
return _classPrivateFieldGet2(_broadcast, this)?.call(this, ownerPath, message, without);
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
var _root = /* @__PURE__ */ new WeakMap();
|
|
135
|
-
var _connectionId = /* @__PURE__ */ new WeakMap();
|
|
136
|
-
var RootSubAgentConnectionBridge = class {
|
|
137
|
-
constructor(root, connectionId) {
|
|
138
|
-
_classPrivateFieldInitSpec(this, _root, void 0);
|
|
139
|
-
_classPrivateFieldInitSpec(this, _connectionId, void 0);
|
|
140
|
-
_classPrivateFieldSet2(_root, this, root);
|
|
141
|
-
_classPrivateFieldSet2(_connectionId, this, connectionId);
|
|
142
|
-
}
|
|
143
|
-
send(message) {
|
|
144
|
-
return _classPrivateFieldGet2(_root, this)._cf_sendToSubAgentConnection(_classPrivateFieldGet2(_connectionId, this), message);
|
|
145
|
-
}
|
|
146
|
-
close(code, reason) {
|
|
147
|
-
return _classPrivateFieldGet2(_root, this)._cf_closeSubAgentConnection(_classPrivateFieldGet2(_connectionId, this), code, reason);
|
|
148
|
-
}
|
|
149
|
-
setState(state) {
|
|
150
|
-
return _classPrivateFieldGet2(_root, this)._cf_setSubAgentConnectionState(_classPrivateFieldGet2(_connectionId, this), state);
|
|
151
|
-
}
|
|
152
|
-
broadcast(ownerPath, message, without) {
|
|
153
|
-
return _classPrivateFieldGet2(_root, this)._cf_broadcastToSubAgent(ownerPath, message, without);
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
/**
|
|
157
|
-
* Decorator that marks a method as callable by clients
|
|
158
|
-
* @param metadata Optional metadata about the callable method
|
|
159
|
-
*/
|
|
160
|
-
function callable(metadata = {}) {
|
|
161
|
-
return function callableDecorator(target, _context) {
|
|
162
|
-
if (!callableMetadata.has(target)) callableMetadata.set(target, metadata);
|
|
163
|
-
return target;
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
let didWarnAboutUnstableCallable = false;
|
|
167
|
-
/**
|
|
168
|
-
* Decorator that marks a method as callable by clients
|
|
169
|
-
* @deprecated this has been renamed to callable, and unstable_callable will be removed in the next major version
|
|
170
|
-
* @param metadata Optional metadata about the callable method
|
|
171
|
-
*/
|
|
172
|
-
const unstable_callable = (metadata = {}) => {
|
|
173
|
-
if (!didWarnAboutUnstableCallable) {
|
|
174
|
-
didWarnAboutUnstableCallable = true;
|
|
175
|
-
console.warn("unstable_callable is deprecated, use callable instead. unstable_callable will be removed in the next major version.");
|
|
176
|
-
}
|
|
177
|
-
return callable(metadata);
|
|
178
|
-
};
|
|
179
|
-
function agentPathKey(path) {
|
|
180
|
-
if (!path) return null;
|
|
181
|
-
return path.map((step) => `${encodeURIComponent(step.className)}:${encodeURIComponent(step.name)}`).join("/");
|
|
182
|
-
}
|
|
183
1222
|
const _fiberALS = new AsyncLocalStorage();
|
|
184
1223
|
const DEFAULT_KEEP_ALIVE_INTERVAL_MS = 3e4;
|
|
185
1224
|
const DEFAULT_AGENT_TOOL_RECOVERY_TIMEOUT_MS = 2e3;
|
|
186
1225
|
const DEFAULT_AGENT_TOOL_RECOVERY_TOTAL_TIMEOUT_MS = 5e3;
|
|
187
1226
|
const DESTROY_PENDING_KEY = "cf_agents_destroy_pending";
|
|
1227
|
+
const HOST_JOB_KEEP_ALIVE_ID = "cf:keep-alive";
|
|
1228
|
+
const HOST_JOB_HOUSEKEEPING_ID = "cf:housekeeping";
|
|
1229
|
+
const HOST_JOB_DESTROY_ID = "cf:destroy";
|
|
188
1230
|
const DESTROY_ALARM_DELAY_MS = 1e3;
|
|
189
1231
|
const FIBER_RECOVERY_MAX_BACKOFF_MS = 5 * 6e4;
|
|
190
1232
|
const FIBER_RECOVERY_BACKOFF_MAX_EXP = 20;
|
|
@@ -202,9 +1244,6 @@ const DETACHED_BACKBONE_CADENCE_S = [
|
|
|
202
1244
|
const DETACHED_LIVE_COUNT_WARN_THRESHOLD = 50;
|
|
203
1245
|
const DETACHED_RECONCILE_CALLBACK = "_cfDetachedReconcileTick";
|
|
204
1246
|
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
1247
|
/**
|
|
209
1248
|
* Schema version for the Agent's internal SQLite tables.
|
|
210
1249
|
* Bump this when adding new tables, columns, or migrations.
|
|
@@ -216,33 +1255,6 @@ const SCHEMA_VERSION_ROW_ID = "cf_schema_version";
|
|
|
216
1255
|
const STATE_ROW_ID = "cf_state_row_id";
|
|
217
1256
|
const STATE_WAS_CHANGED = "cf_state_was_changed";
|
|
218
1257
|
const DEFAULT_STATE = {};
|
|
219
|
-
async function sha256Hex(value) {
|
|
220
|
-
const bytes = new TextEncoder().encode(value);
|
|
221
|
-
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
222
|
-
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
223
|
-
}
|
|
224
|
-
function pathV2IdentityName(logicalName, digest) {
|
|
225
|
-
return `${SUB_AGENT_IDENTITY_PATH_V2_PREFIX}${encodeURIComponent(logicalName)}:${digest}`;
|
|
226
|
-
}
|
|
227
|
-
function logicalNameFromPathV2Identity(identityName) {
|
|
228
|
-
if (!identityName.startsWith(SUB_AGENT_IDENTITY_PATH_V2_PREFIX)) return null;
|
|
229
|
-
const rest = identityName.slice(13);
|
|
230
|
-
const separator = rest.lastIndexOf(":");
|
|
231
|
-
if (separator === -1) return null;
|
|
232
|
-
try {
|
|
233
|
-
return decodeURIComponent(rest.slice(0, separator));
|
|
234
|
-
} catch {
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Validate that a stored `parentPath` has the expected shape. Used
|
|
240
|
-
* when restoring from DO storage to guard against corrupted data.
|
|
241
|
-
*/
|
|
242
|
-
function isValidParentPath(value) {
|
|
243
|
-
if (!Array.isArray(value)) return false;
|
|
244
|
-
return value.every((entry) => entry != null && typeof entry === "object" && typeof entry.className === "string" && typeof entry.name === "string");
|
|
245
|
-
}
|
|
246
1258
|
/**
|
|
247
1259
|
* Internal key used to store the readonly flag in connection state.
|
|
248
1260
|
* Prefixed with _cf_ to avoid collision with user state keys.
|
|
@@ -255,28 +1267,13 @@ const CF_READONLY_KEY = "_cf_readonly";
|
|
|
255
1267
|
*/
|
|
256
1268
|
const CF_NO_PROTOCOL_KEY = "_cf_no_protocol";
|
|
257
1269
|
/**
|
|
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
1270
|
* The set of all internal keys stored in connection state that must be
|
|
274
1271
|
* hidden from user code and preserved across setState calls.
|
|
275
1272
|
*/
|
|
276
1273
|
const CF_INTERNAL_KEYS = /* @__PURE__ */ new Set([
|
|
277
1274
|
CF_READONLY_KEY,
|
|
278
1275
|
CF_NO_PROTOCOL_KEY,
|
|
279
|
-
|
|
1276
|
+
"_cf_voiceInCall",
|
|
280
1277
|
CF_SUB_AGENT_OUTER_URL_KEY,
|
|
281
1278
|
CF_SUB_AGENT_TAGS_KEY
|
|
282
1279
|
]);
|
|
@@ -459,6 +1456,34 @@ var Agent = class Agent extends DurableObject {
|
|
|
459
1456
|
async __unsafe_ensureInitialized(props) {
|
|
460
1457
|
await this.lifecycle.start(props);
|
|
461
1458
|
}
|
|
1459
|
+
/** @internal */
|
|
1460
|
+
get _dynamicAgents() {
|
|
1461
|
+
this._dynamicAgentsInstance ??= new DynamicAgentsInternal(this);
|
|
1462
|
+
return this._dynamicAgentsInstance;
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* The dynamic-agents capability: facet-backed child agents that run
|
|
1466
|
+
* in their own isolate with their own SQLite database, colocated
|
|
1467
|
+
* with — and supervised by — this agent.
|
|
1468
|
+
*
|
|
1469
|
+
* Use dynamic agents for code whose class or lifecycle this agent
|
|
1470
|
+
* owns: dynamically-loaded or AI-generated code, per-run tool
|
|
1471
|
+
* agents, sandboxed components. For independent peers (for example
|
|
1472
|
+
* one Durable Object per chat), use `getAgentByName` instead.
|
|
1473
|
+
*
|
|
1474
|
+
* ```ts
|
|
1475
|
+
* const child = await this.dynamicAgents.get(Researcher, id);
|
|
1476
|
+
* await child.doWork();
|
|
1477
|
+
* this.dynamicAgents.abort(Researcher, id, reason);
|
|
1478
|
+
* await this.dynamicAgents.delete(Researcher, id);
|
|
1479
|
+
* ```
|
|
1480
|
+
*
|
|
1481
|
+
* @experimental The API surface may change before stabilizing.
|
|
1482
|
+
*/
|
|
1483
|
+
get dynamicAgents() {
|
|
1484
|
+
this._dynamicAgentsApi ??= new DynamicAgents(this._dynamicAgents);
|
|
1485
|
+
return this._dynamicAgentsApi;
|
|
1486
|
+
}
|
|
462
1487
|
/**
|
|
463
1488
|
* Stable key for Workers AI session affinity (prefix-cache optimization).
|
|
464
1489
|
*
|
|
@@ -643,7 +1668,6 @@ var Agent = class Agent extends DurableObject {
|
|
|
643
1668
|
CREATE INDEX IF NOT EXISTS idx_workflows_name ON cf_agents_workflows(workflow_name)
|
|
644
1669
|
`;
|
|
645
1670
|
this.ctx.storage.sql.exec("DELETE FROM cf_agents_state WHERE id = ?", STATE_WAS_CHANGED);
|
|
646
|
-
ensureScheduleTable(this.ctx.storage);
|
|
647
1671
|
this.sql`
|
|
648
1672
|
CREATE TABLE IF NOT EXISTS cf_agents_runs (
|
|
649
1673
|
id TEXT PRIMARY KEY NOT NULL,
|
|
@@ -741,7 +1765,17 @@ var Agent = class Agent extends DurableObject {
|
|
|
741
1765
|
}
|
|
742
1766
|
constructor(ctx, env) {
|
|
743
1767
|
super(ctx, env);
|
|
744
|
-
this.lifecycle = Lifecycle.install(this);
|
|
1768
|
+
this.lifecycle = Lifecycle.install(this, { maxAlarmMemoryLimitStrikes: this._resolvedOptions.maxAlarmMemoryLimitStrikes });
|
|
1769
|
+
this._webSockets = new WebSockets({
|
|
1770
|
+
handlers: {
|
|
1771
|
+
onConnect: (connection, ctx) => this.onConnect(connection, ctx),
|
|
1772
|
+
onMessage: (connection, message) => this.onMessage(connection, message),
|
|
1773
|
+
onClose: (connection, code, reason, wasClean) => this.onClose(connection, code, reason, wasClean),
|
|
1774
|
+
onError: (connection, error) => this.onError(connection, error)
|
|
1775
|
+
},
|
|
1776
|
+
callables: callablesFromDecorated(this),
|
|
1777
|
+
getConnectionTags: (connection, ctx) => this.getConnectionTags(connection, ctx)
|
|
1778
|
+
});
|
|
745
1779
|
this._state = DEFAULT_STATE;
|
|
746
1780
|
this._disposables = new DisposableStore();
|
|
747
1781
|
this._destroyed = false;
|
|
@@ -749,13 +1783,9 @@ var Agent = class Agent extends DurableObject {
|
|
|
749
1783
|
this._persistenceHookMode = "none";
|
|
750
1784
|
this._isFacet = false;
|
|
751
1785
|
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
1786
|
this._parentPath = [];
|
|
756
1787
|
this._warnedChatRecoveryInOnStart = false;
|
|
757
1788
|
this._keepAliveRefs = 0;
|
|
758
|
-
this._facetKeepAliveTokens = /* @__PURE__ */ new Set();
|
|
759
1789
|
this._runFiberActiveFibers = /* @__PURE__ */ new Set();
|
|
760
1790
|
this._managedFiberAbortControllers = /* @__PURE__ */ new Map();
|
|
761
1791
|
this._managedFiberExecutions = /* @__PURE__ */ new Map();
|
|
@@ -769,7 +1799,6 @@ var Agent = class Agent extends DurableObject {
|
|
|
769
1799
|
this.observability = genericObservability;
|
|
770
1800
|
this._flushingQueue = false;
|
|
771
1801
|
this.maxConcurrentAgentTools = Infinity;
|
|
772
|
-
this._subAgentRegistryReady = false;
|
|
773
1802
|
const routeHost = this;
|
|
774
1803
|
setLifecycleRouteTransport(this.lifecycle, {
|
|
775
1804
|
get source() {
|
|
@@ -782,10 +1811,10 @@ var Agent = class Agent extends DurableObject {
|
|
|
782
1811
|
const payload = event.payload !== null && typeof event.payload === "object" && !Array.isArray(event.payload) ? event.payload : { value: event.payload };
|
|
783
1812
|
this._emit(event.type, payload);
|
|
784
1813
|
});
|
|
785
|
-
setLifecycleHostInvoker(this.lifecycle, (run) => runInInvocation({
|
|
1814
|
+
setLifecycleHostInvoker(this.lifecycle, (run, scope) => runInInvocation({
|
|
786
1815
|
agent: this,
|
|
787
|
-
connection:
|
|
788
|
-
request:
|
|
1816
|
+
connection: scope?.connection,
|
|
1817
|
+
request: scope?.request,
|
|
789
1818
|
email: void 0
|
|
790
1819
|
}, run));
|
|
791
1820
|
this.scheduler = new Scheduler({
|
|
@@ -803,6 +1832,11 @@ var Agent = class Agent extends DurableObject {
|
|
|
803
1832
|
if (typeof method !== "function") return void 0;
|
|
804
1833
|
return (payload, schedule) => method.call(this, payload, schedule);
|
|
805
1834
|
});
|
|
1835
|
+
this.tasks = new Tasks({ onError: (error) => this.onError(error) });
|
|
1836
|
+
setTaskRoutedMemoryLimitHandler(this.tasks, (context) => {
|
|
1837
|
+
return this.onAlarmMemoryLimit?.call(this, context);
|
|
1838
|
+
});
|
|
1839
|
+
setTaskDefinitionResolver(this.tasks, (name) => this.taskDefinitions?.[name]);
|
|
806
1840
|
this.mcp = this._withAgentSpan("agent_initialization", "initialization", {}, (update) => {
|
|
807
1841
|
if (!wrappedClasses.has(this.constructor)) {
|
|
808
1842
|
this._autoWrapCustomMethods();
|
|
@@ -823,7 +1857,7 @@ var Agent = class Agent extends DurableObject {
|
|
|
823
1857
|
createAuthProvider: (callbackUrl) => this.createMcpOAuthProvider(callbackUrl)
|
|
824
1858
|
});
|
|
825
1859
|
});
|
|
826
|
-
this.lifecycle.use(this.scheduler).use(this.mcp);
|
|
1860
|
+
this.lifecycle.use(this.scheduler).use(this.mcp).use(this._webSockets, { fallback: true }).use(this.tasks).use(this._dynamicAgents);
|
|
827
1861
|
let mcpBroadcastReady = false;
|
|
828
1862
|
this._disposables.add(this.mcp.onServerStateChanged(() => {
|
|
829
1863
|
if (mcpBroadcastReady) this.broadcastMcpServers();
|
|
@@ -857,6 +1891,8 @@ var Agent = class Agent extends DurableObject {
|
|
|
857
1891
|
await _onAlarm();
|
|
858
1892
|
if (this._destroyed) return;
|
|
859
1893
|
await this._onAlarmHousekeeping();
|
|
1894
|
+
if (this._destroyed) return;
|
|
1895
|
+
await this._syncHostJobs();
|
|
860
1896
|
};
|
|
861
1897
|
const _onRequest = this.onRequest.bind(this);
|
|
862
1898
|
this.onRequest = (request) => {
|
|
@@ -869,7 +1905,7 @@ var Agent = class Agent extends DurableObject {
|
|
|
869
1905
|
};
|
|
870
1906
|
const _onMessage = this.onMessage.bind(this);
|
|
871
1907
|
this.onMessage = async (connection, message) => {
|
|
872
|
-
const replyBridge =
|
|
1908
|
+
const replyBridge = dynamicAgentRpcReplyContext.getStore()?.bridge;
|
|
873
1909
|
if (await __DO_NOT_USE_WILL_BREAK__agentContext.exit(() => this._cf_forwardSubAgentWebSocketMessage(connection, message, replyBridge))) return;
|
|
874
1910
|
this._ensureConnectionWrapped(connection);
|
|
875
1911
|
return runInInvocation({
|
|
@@ -910,9 +1946,10 @@ var Agent = class Agent extends DurableObject {
|
|
|
910
1946
|
const methodFn = this[method];
|
|
911
1947
|
if (typeof methodFn !== "function") throw new Error(`Method ${method} does not exist`);
|
|
912
1948
|
if (!this._isCallable(method)) throw new Error(`Method ${method} is not callable`);
|
|
913
|
-
const metadata =
|
|
1949
|
+
const metadata = getCallableMetadata(methodFn);
|
|
914
1950
|
if (metadata?.streaming) {
|
|
915
|
-
const stream =
|
|
1951
|
+
const stream = new StreamingResponse(connection, id);
|
|
1952
|
+
if (replyBridge) registerFacetStreamingDelivery(stream, replyBridge);
|
|
916
1953
|
this._emit("rpc", {
|
|
917
1954
|
method,
|
|
918
1955
|
streaming: true
|
|
@@ -1061,6 +2098,7 @@ var Agent = class Agent extends DurableObject {
|
|
|
1061
2098
|
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
2099
|
}
|
|
1063
2100
|
this._scheduleAgentToolRunRecovery({ runIds: startupAgentToolRunIds });
|
|
2101
|
+
await this._syncHostJobs();
|
|
1064
2102
|
return result;
|
|
1065
2103
|
});
|
|
1066
2104
|
});
|
|
@@ -1068,18 +2106,7 @@ var Agent = class Agent extends DurableObject {
|
|
|
1068
2106
|
this.onStart = (props) => this._withAgentSpan("agent_start", "startup", {}, (update) => startAgent(props, update));
|
|
1069
2107
|
}
|
|
1070
2108
|
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
|
-
});
|
|
2109
|
+
await this._withAgentSpan("restore_agent_state", "startup", {}, () => this._dynamicAgents.restoreFacetContext());
|
|
1083
2110
|
}
|
|
1084
2111
|
/**
|
|
1085
2112
|
* Check for workflows referencing unknown bindings and warn with migration suggestion.
|
|
@@ -1534,7 +2561,7 @@ var Agent = class Agent extends DurableObject {
|
|
|
1534
2561
|
const descriptor = Object.getOwnPropertyDescriptor(proto, methodName);
|
|
1535
2562
|
if (baseMethods.has(methodName) || methodName.startsWith("_") || !descriptor || !!descriptor.get || typeof descriptor.value !== "function") continue;
|
|
1536
2563
|
const wrappedFunction = withAgentContext(this[methodName]);
|
|
1537
|
-
if (this._isCallable(methodName))
|
|
2564
|
+
if (this._isCallable(methodName)) copyCallableMetadata(this[methodName], wrappedFunction);
|
|
1538
2565
|
this.constructor.prototype[methodName] = wrappedFunction;
|
|
1539
2566
|
}
|
|
1540
2567
|
proto = Object.getPrototypeOf(proto);
|
|
@@ -1721,74 +2748,21 @@ var Agent = class Agent extends DurableObject {
|
|
|
1721
2748
|
retry: parseRetryOptions(row)
|
|
1722
2749
|
}));
|
|
1723
2750
|
}
|
|
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
|
-
`;
|
|
1743
|
-
}
|
|
1744
2751
|
_lifecycleRouteAddress() {
|
|
1745
|
-
|
|
1746
|
-
const key = agentPathKey(this.selfPath);
|
|
1747
|
-
return key ? {
|
|
1748
|
-
key,
|
|
1749
|
-
data: JSON.stringify(this.selfPath)
|
|
1750
|
-
} : void 0;
|
|
2752
|
+
return this._dynamicAgents.lifecycleRouteAddress();
|
|
1751
2753
|
}
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
return (await this._rootAlarmOwner())._cf_routeLifecycle(void 0, envelope);
|
|
2754
|
+
_routeLifecycleToRoot(envelope) {
|
|
2755
|
+
return this._dynamicAgents.routeLifecycleToRoot(envelope);
|
|
1755
2756
|
}
|
|
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);
|
|
2757
|
+
_routeLifecycleToTarget(target, envelope) {
|
|
2758
|
+
return this._dynamicAgents.routeLifecycleToTarget(target, envelope);
|
|
1774
2759
|
}
|
|
1775
2760
|
/** Single native-RPC aperture for routed Lifecycle capabilities. */
|
|
1776
2761
|
_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);
|
|
2762
|
+
return this._dynamicAgents.routeLifecycle(target, envelope);
|
|
1785
2763
|
}
|
|
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);
|
|
2764
|
+
_rootAlarmOwner() {
|
|
2765
|
+
return this._dynamicAgents.rootAlarmOwner();
|
|
1792
2766
|
}
|
|
1793
2767
|
/**
|
|
1794
2768
|
* Clean root-owned bookkeeping for a sub-tree of facets. This
|
|
@@ -1801,64 +2775,40 @@ var Agent = class Agent extends DurableObject {
|
|
|
1801
2775
|
* @internal
|
|
1802
2776
|
*/
|
|
1803
2777
|
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();
|
|
2778
|
+
await this._dynamicAgents.cleanupPrefix(ownerPath);
|
|
1808
2779
|
}
|
|
1809
2780
|
/**
|
|
1810
2781
|
* 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.
|
|
2782
|
+
* Facets run in separate colocated isolates but cannot set their own
|
|
2783
|
+
* physical alarm, so this lets facet work use the root alarm heartbeat.
|
|
1813
2784
|
* @internal
|
|
1814
2785
|
*/
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
this._facetKeepAliveTokens.add(token);
|
|
1818
|
-
this._keepAliveRefs++;
|
|
1819
|
-
if (this._keepAliveRefs === 1) await this._rearmAlarm();
|
|
1820
|
-
return token;
|
|
2786
|
+
_cf_acquireFacetKeepAlive(ownerPath) {
|
|
2787
|
+
return this._dynamicAgents.acquireKeepAlive(ownerPath);
|
|
1821
2788
|
}
|
|
1822
2789
|
/**
|
|
1823
2790
|
* Release a root-owned keepAlive ref previously acquired for a facet.
|
|
1824
2791
|
* Idempotent so disposer calls can safely race or run twice.
|
|
1825
2792
|
* @internal
|
|
1826
2793
|
*/
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
this._keepAliveRefs = Math.max(0, this._keepAliveRefs - 1);
|
|
1830
|
-
await this._rearmAlarm();
|
|
2794
|
+
_cf_releaseFacetKeepAlive(token) {
|
|
2795
|
+
return this._dynamicAgents.releaseKeepAlive(token);
|
|
1831
2796
|
}
|
|
1832
2797
|
/**
|
|
1833
2798
|
* Register a facet's durable run row in the root-side index so root
|
|
1834
2799
|
* alarm housekeeping can dispatch recovery checks into idle facets.
|
|
1835
2800
|
* The facet remains authoritative for snapshots and recovery hooks.
|
|
1836
2801
|
* @internal
|
|
1837
|
-
*/
|
|
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();
|
|
2802
|
+
*/
|
|
2803
|
+
_cf_registerFacetRun(ownerPath, runId) {
|
|
2804
|
+
return this._dynamicAgents.registerRun(ownerPath, runId);
|
|
1849
2805
|
}
|
|
1850
2806
|
/**
|
|
1851
2807
|
* Remove a completed facet fiber from the root-side index.
|
|
1852
2808
|
* @internal
|
|
1853
2809
|
*/
|
|
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();
|
|
2810
|
+
_cf_unregisterFacetRun(ownerPath, runId) {
|
|
2811
|
+
return this._dynamicAgents.unregisterRun(ownerPath, runId);
|
|
1862
2812
|
}
|
|
1863
2813
|
/**
|
|
1864
2814
|
* Schedule a task to be executed in the future
|
|
@@ -2018,13 +2968,13 @@ var Agent = class Agent extends DurableObject {
|
|
|
2018
2968
|
};
|
|
2019
2969
|
}
|
|
2020
2970
|
this._keepAliveRefs++;
|
|
2021
|
-
if (this._keepAliveRefs === 1) await this.
|
|
2971
|
+
if (this._keepAliveRefs === 1) await this._syncHostJobs();
|
|
2022
2972
|
let disposed = false;
|
|
2023
2973
|
return () => {
|
|
2024
2974
|
if (disposed) return;
|
|
2025
2975
|
disposed = true;
|
|
2026
2976
|
this._keepAliveRefs = Math.max(0, this._keepAliveRefs - 1);
|
|
2027
|
-
if (this._keepAliveRefs === 0) this.ctx.waitUntil(this.
|
|
2977
|
+
if (this._keepAliveRefs === 0) this.ctx.waitUntil(this._syncHostJobs().catch((e) => {
|
|
2028
2978
|
console.error("[Agent] Failed to reschedule alarm after keepAlive dispose:", e);
|
|
2029
2979
|
}));
|
|
2030
2980
|
};
|
|
@@ -2617,6 +3567,15 @@ var Agent = class Agent extends DurableObject {
|
|
|
2617
3567
|
ctx.stash(data);
|
|
2618
3568
|
}
|
|
2619
3569
|
/**
|
|
3570
|
+
* Run `fn` inside the fiber stash context so `this.stash()` keeps working
|
|
3571
|
+
* for turns executing on the `tasks` capability exactly as it does inside
|
|
3572
|
+
* legacy `runFiber()` closures.
|
|
3573
|
+
* @internal
|
|
3574
|
+
*/
|
|
3575
|
+
_withFiberStash(context, fn) {
|
|
3576
|
+
return _fiberALS.run(context, fn);
|
|
3577
|
+
}
|
|
3578
|
+
/**
|
|
2620
3579
|
* Called when an interrupted fiber is detected after restart.
|
|
2621
3580
|
* Override to implement recovery (re-invoke work, notify clients, etc.).
|
|
2622
3581
|
*
|
|
@@ -2787,36 +3746,8 @@ var Agent = class Agent extends DurableObject {
|
|
|
2787
3746
|
* recovery hooks live in each facet's own `cf_agents_runs` table.
|
|
2788
3747
|
* @internal
|
|
2789
3748
|
*/
|
|
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
|
-
}
|
|
3749
|
+
_checkFacetRunFibers() {
|
|
3750
|
+
return this._dynamicAgents.checkRunFibers();
|
|
2820
3751
|
}
|
|
2821
3752
|
/**
|
|
2822
3753
|
* Dispatch a runFiber recovery check into the facet identified by
|
|
@@ -2824,18 +3755,8 @@ var Agent = class Agent extends DurableObject {
|
|
|
2824
3755
|
* rows on the target facet after recovery.
|
|
2825
3756
|
* @internal
|
|
2826
3757
|
*/
|
|
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);
|
|
3758
|
+
_cf_checkRunFibersForFacet(ownerPath) {
|
|
3759
|
+
return this._dynamicAgents.checkRunFibersAtPath(ownerPath);
|
|
2839
3760
|
}
|
|
2840
3761
|
/**
|
|
2841
3762
|
* Invoke an RPC method on this Agent or a descendant facet identified
|
|
@@ -2843,18 +3764,8 @@ var Agent = class Agent extends DurableObject {
|
|
|
2843
3764
|
* `this.agent` calls back to the exact sub-agent that started a workflow.
|
|
2844
3765
|
* @internal
|
|
2845
3766
|
*/
|
|
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);
|
|
3767
|
+
_cf_invokeAgentPath(targetPath, method, args) {
|
|
3768
|
+
return this._dynamicAgents.invokeAgentPath(targetPath, method, args);
|
|
2858
3769
|
}
|
|
2859
3770
|
/**
|
|
2860
3771
|
* Recursively destroy a descendant facet identified by
|
|
@@ -2869,25 +3780,8 @@ var Agent = class Agent extends DurableObject {
|
|
|
2869
3780
|
* cleanup as `parent.deleteSubAgent(Cls, name)` from the parent.
|
|
2870
3781
|
* @internal
|
|
2871
3782
|
*/
|
|
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);
|
|
3783
|
+
_cf_destroyDescendantFacet(targetPath) {
|
|
3784
|
+
return this._dynamicAgents.destroyDescendant(targetPath);
|
|
2891
3785
|
}
|
|
2892
3786
|
/**
|
|
2893
3787
|
* Whether any runFiber recovery work is still outstanding: orphaned
|
|
@@ -2895,7 +3789,7 @@ var Agent = class Agent extends DurableObject {
|
|
|
2895
3789
|
* executing in memory, which already hold a keepAlive ref) or managed
|
|
2896
3790
|
* ledger fibers stuck in a non-terminal state with no live run row.
|
|
2897
3791
|
*
|
|
2898
|
-
* Used by `
|
|
3792
|
+
* Used by `_syncHostJobs` to arm a follow-up alarm so multi-pass
|
|
2899
3793
|
* recovery (e.g. after a scan-deadline yield, or while retrying a throwing
|
|
2900
3794
|
* recovery hook) resumes instead of starving.
|
|
2901
3795
|
* @internal
|
|
@@ -2913,28 +3807,57 @@ var Agent = class Agent extends DurableObject {
|
|
|
2913
3807
|
AND r.id IS NULL
|
|
2914
3808
|
`[0]?.count ?? 0) > 0;
|
|
2915
3809
|
}
|
|
2916
|
-
async _rearmAlarm() {
|
|
2917
|
-
await this._withAgentSpan("schedule_agent_alarm", "alarm", {}, () => this.lifecycle.rearmAlarm());
|
|
2918
|
-
}
|
|
2919
3810
|
/**
|
|
2920
|
-
*
|
|
2921
|
-
*
|
|
3811
|
+
* Synchronize Agent-owned host jobs with current durable state.
|
|
3812
|
+
*
|
|
3813
|
+
* Replaces the old pull-based `getNextAlarm()` contribution: keep-alive
|
|
3814
|
+
* refs hold a `cf:keep-alive` job, and fiber-recovery / facet-run state
|
|
3815
|
+
* holds a `cf:housekeeping` job. Every state change that used to trigger
|
|
3816
|
+
* an alarm recalculation now re-pushes or cancels these jobs; queue
|
|
3817
|
+
* mutations re-arm the physical alarm automatically.
|
|
2922
3818
|
* @internal
|
|
2923
3819
|
*/
|
|
2924
|
-
async
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
3820
|
+
async _syncHostJobs() {
|
|
3821
|
+
if (this._destroyed) return;
|
|
3822
|
+
await this._withAgentSpan("schedule_agent_alarm", "alarm", {}, async () => {
|
|
3823
|
+
const work = this.lifecycle.jobs;
|
|
3824
|
+
const nowMs = Date.now();
|
|
3825
|
+
const pendingDestroy = await this._pendingDestroyAlarm();
|
|
3826
|
+
if (pendingDestroy !== null) {
|
|
3827
|
+
await work.push({
|
|
3828
|
+
id: HOST_JOB_DESTROY_ID,
|
|
3829
|
+
fn: "destroy",
|
|
3830
|
+
time: pendingDestroy,
|
|
3831
|
+
exclusive: true
|
|
3832
|
+
});
|
|
3833
|
+
return;
|
|
3834
|
+
}
|
|
3835
|
+
if (work.get(HOST_JOB_DESTROY_ID)) await work.cancel(HOST_JOB_DESTROY_ID);
|
|
3836
|
+
if (this._keepAliveRefs > 0) await work.push({
|
|
3837
|
+
id: HOST_JOB_KEEP_ALIVE_ID,
|
|
3838
|
+
fn: "keepAlive",
|
|
3839
|
+
time: nowMs + this._resolvedOptions.keepAliveIntervalMs
|
|
3840
|
+
});
|
|
3841
|
+
else if (work.get(HOST_JOB_KEEP_ALIVE_ID)) await work.cancel(HOST_JOB_KEEP_ALIVE_ID);
|
|
3842
|
+
const housekeepingAt = this._nextHousekeepingWakeMs(nowMs);
|
|
3843
|
+
if (housekeepingAt !== null) await work.push({
|
|
3844
|
+
id: HOST_JOB_HOUSEKEEPING_ID,
|
|
3845
|
+
fn: "housekeeping",
|
|
3846
|
+
time: housekeepingAt
|
|
3847
|
+
});
|
|
3848
|
+
else if (work.get(HOST_JOB_HOUSEKEEPING_ID)) await work.cancel(HOST_JOB_HOUSEKEEPING_ID);
|
|
3849
|
+
});
|
|
3850
|
+
}
|
|
3851
|
+
/**
|
|
3852
|
+
* The next wake fiber-recovery or facet-run housekeeping needs, or `null`
|
|
3853
|
+
* when neither has pending durable state.
|
|
3854
|
+
*/
|
|
3855
|
+
_nextHousekeepingWakeMs(nowMs) {
|
|
2931
3856
|
let nextTimeMs = null;
|
|
2932
|
-
if (this._keepAliveRefs > 0) nextTimeMs = nowMs + this._resolvedOptions.keepAliveIntervalMs;
|
|
2933
3857
|
if (this._hasPendingFiberRecovery()) {
|
|
2934
3858
|
const base = this._resolvedOptions.keepAliveIntervalMs;
|
|
2935
3859
|
const exp = Math.min(this._recoveryNoProgressScans, FIBER_RECOVERY_BACKOFF_MAX_EXP);
|
|
2936
|
-
|
|
2937
|
-
nextTimeMs = nextTimeMs === null ? recoveryMs : Math.min(nextTimeMs, recoveryMs);
|
|
3860
|
+
nextTimeMs = nowMs + Math.min(FIBER_RECOVERY_MAX_BACKOFF_MS, base * 2 ** exp);
|
|
2938
3861
|
}
|
|
2939
3862
|
if ((this.sql`
|
|
2940
3863
|
SELECT COUNT(*) as count FROM cf_agents_facet_runs
|
|
@@ -2942,132 +3865,68 @@ var Agent = class Agent extends DurableObject {
|
|
|
2942
3865
|
const facetRecoveryMs = nowMs + this._resolvedOptions.keepAliveIntervalMs;
|
|
2943
3866
|
nextTimeMs = nextTimeMs === null ? facetRecoveryMs : Math.min(nextTimeMs, facetRecoveryMs);
|
|
2944
3867
|
}
|
|
2945
|
-
const extensionAlarm = await this._getExtensionAlarm();
|
|
2946
|
-
if (extensionAlarm !== null) nextTimeMs = nextTimeMs === null ? extensionAlarm : Math.min(nextTimeMs, extensionAlarm);
|
|
2947
3868
|
return nextTimeMs;
|
|
2948
3869
|
}
|
|
2949
|
-
/** @internal Transitional alarm contribution for Agent subclasses. */
|
|
2950
|
-
_getExtensionAlarm() {
|
|
2951
|
-
return null;
|
|
2952
|
-
}
|
|
2953
3870
|
/** Lifecycle alarm callback; Agent housekeeping runs after user alarm work. */
|
|
2954
3871
|
onAlarm() {}
|
|
2955
3872
|
/**
|
|
2956
|
-
*
|
|
2957
|
-
*
|
|
2958
|
-
*
|
|
2959
|
-
* work belongs in a capability with `getNextAlarm()` and `onAlarm()`.
|
|
3873
|
+
* Drive one Agent-owned host job from the Lifecycle queue.
|
|
3874
|
+
* @internal Dispatched by Lifecycle's alarm event loop; extensions add
|
|
3875
|
+
* job fns through {@link _onHostJob}.
|
|
2960
3876
|
*/
|
|
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();
|
|
3877
|
+
onJob(context) {
|
|
3878
|
+
return this._onHostJob(context.job.fn, context);
|
|
2977
3879
|
}
|
|
2978
3880
|
/**
|
|
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}.
|
|
3881
|
+
* @internal Dispatch one host job fn. Agent extensions (Think) override
|
|
3882
|
+
* this to add fns and delegate unknown ones to `super`.
|
|
2984
3883
|
*/
|
|
2985
|
-
|
|
2986
|
-
|
|
3884
|
+
_onHostJob(fn, _context) {
|
|
3885
|
+
switch (fn) {
|
|
3886
|
+
case "keepAlive": return this._keepAliveRefs > 0 ? { rescheduleAt: Date.now() + this._resolvedOptions.keepAliveIntervalMs } : void 0;
|
|
3887
|
+
case "housekeeping": {
|
|
3888
|
+
const next = this._nextHousekeepingWakeMs(Date.now());
|
|
3889
|
+
return next === null ? void 0 : { rescheduleAt: next };
|
|
3890
|
+
}
|
|
3891
|
+
case "destroy": return;
|
|
3892
|
+
default:
|
|
3893
|
+
console.warn(`Unknown Agent host job fn ${JSON.stringify(fn)}`);
|
|
3894
|
+
return;
|
|
3895
|
+
}
|
|
2987
3896
|
}
|
|
2988
3897
|
/**
|
|
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.
|
|
3898
|
+
* Apply host policy after the alarm memory-limit breaker records a strike.
|
|
3899
|
+
*
|
|
3900
|
+
* New chat hosts override this hook directly. The sealed-only fallback keeps
|
|
3901
|
+
* `agents` 0.23 compatible with already-published chat packages whose peer
|
|
3902
|
+
* ranges accept it but which implement only the former
|
|
3903
|
+
* `_cf_sealMemoryLimitedRecovery` template method. Queue membership remains
|
|
3904
|
+
* job-row policy; this invokes terminalization only and can be removed once
|
|
3905
|
+
* old chat releases no longer accept the current `agents` range.
|
|
3906
|
+
*
|
|
3907
|
+
* @internal
|
|
3002
3908
|
*/
|
|
3003
|
-
async
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
if (typeof prior === "number" && prior > 0) await this.ctx.storage.delete(Agent._CF_OOM_ALARM_STRIKES_KEY);
|
|
3007
|
-
} catch {}
|
|
3909
|
+
async onAlarmMemoryLimit(context) {
|
|
3910
|
+
if (!context.sealed) return;
|
|
3911
|
+
await this._cf_sealMemoryLimitedRecovery?.call(this);
|
|
3008
3912
|
}
|
|
3009
3913
|
/**
|
|
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
|
-
});
|
|
3914
|
+
* Run Lifecycle's alarm event loop after the pending-destroy preamble.
|
|
3915
|
+
*
|
|
3916
|
+
* The alarm memory-limit circuit breaker (#1825) lives inside
|
|
3917
|
+
* `Lifecycle.alarm()`; capabilities and hosts opt into extra domain
|
|
3918
|
+
* policy via their `onMemoryLimit` / `onAlarmMemoryLimit` hooks and the
|
|
3919
|
+
* `recoveryLoop` schedule option.
|
|
3920
|
+
*
|
|
3921
|
+
* @remarks Use `this.schedule()` for named Agent callbacks. Reusable durable
|
|
3922
|
+
* work belongs in a capability that pushes jobs and implements `onJob()`.
|
|
3923
|
+
*/
|
|
3924
|
+
async alarm() {
|
|
3925
|
+
if (await this._hasPendingDestroy()) {
|
|
3926
|
+
await this.destroy();
|
|
3927
|
+
return;
|
|
3059
3928
|
}
|
|
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 {}
|
|
3929
|
+
await this.lifecycle.alarm();
|
|
3071
3930
|
}
|
|
3072
3931
|
/**
|
|
3073
3932
|
* Intercept incoming HTTP/WS requests whose URL contains a
|
|
@@ -3077,9 +3936,9 @@ var Agent = class Agent extends DurableObject {
|
|
|
3077
3936
|
* Response, the framework resolves the facet and hands the
|
|
3078
3937
|
* request off.
|
|
3079
3938
|
*
|
|
3080
|
-
*
|
|
3081
|
-
*
|
|
3082
|
-
*
|
|
3939
|
+
* The parent owns an upgraded WebSocket for its lifetime. Subsequent
|
|
3940
|
+
* frames wake the root parent, which forwards them to the child over
|
|
3941
|
+
* RPC and routes replies back to the native socket.
|
|
3083
3942
|
*
|
|
3084
3943
|
* @experimental The API surface may change before stabilizing.
|
|
3085
3944
|
*/
|
|
@@ -3104,409 +3963,80 @@ var Agent = class Agent extends DurableObject {
|
|
|
3104
3963
|
}
|
|
3105
3964
|
broadcast(msg, without) {
|
|
3106
3965
|
if (this._isFacet) {
|
|
3107
|
-
this.
|
|
3966
|
+
this._dynamicAgents.broadcastToParent(msg, without);
|
|
3108
3967
|
return;
|
|
3109
3968
|
}
|
|
3110
|
-
for (const connection of this.
|
|
3969
|
+
for (const connection of this._webSockets.getConnections()) {
|
|
3111
3970
|
if (without?.includes(connection.id)) continue;
|
|
3112
|
-
if (this.
|
|
3971
|
+
if (this._dynamicAgents.connectionHasChildTarget(connection)) continue;
|
|
3113
3972
|
connection.send(msg);
|
|
3114
3973
|
}
|
|
3115
3974
|
}
|
|
3116
3975
|
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;
|
|
3976
|
+
if (this._isFacet) return this._dynamicAgents.getVirtualConnection(id);
|
|
3977
|
+
const connection = this._webSockets.getConnection(id);
|
|
3978
|
+
if (!connection || this._dynamicAgents.connectionHasChildTarget(connection)) return;
|
|
3124
3979
|
return connection;
|
|
3125
3980
|
}
|
|
3126
3981
|
*getConnections(tag) {
|
|
3127
3982
|
if (this._isFacet) {
|
|
3128
|
-
|
|
3129
|
-
return;
|
|
3130
|
-
}
|
|
3131
|
-
for (const connection of this.lifecycle.getConnections(tag)) {
|
|
3132
|
-
if (this._cf_connectionHasSubAgentTarget(connection)) continue;
|
|
3133
|
-
yield connection;
|
|
3134
|
-
}
|
|
3135
|
-
}
|
|
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
|
-
async _cf_broadcastToSubAgent(ownerPath, message, without) {
|
|
3212
|
-
if (this._isFacet) {
|
|
3213
|
-
await this._cf_routeSubAgentBroadcast(ownerPath, message, without);
|
|
3214
|
-
return;
|
|
3215
|
-
}
|
|
3216
|
-
for (const connection of this.lifecycle.getConnections()) {
|
|
3217
|
-
if (without?.includes(connection.id)) continue;
|
|
3218
|
-
const targetPath = this._cf_subAgentTargetPath(connection);
|
|
3219
|
-
if (!targetPath) continue;
|
|
3220
|
-
if (!this._isSameAgentPath(targetPath, ownerPath)) continue;
|
|
3221
|
-
connection.send(message);
|
|
3222
|
-
}
|
|
3223
|
-
}
|
|
3224
|
-
async _cf_subAgentConnectionMetas(ownerPath) {
|
|
3225
|
-
const metas = [];
|
|
3226
|
-
for (const connection of this.lifecycle.getConnections()) {
|
|
3227
|
-
const meta = this._cf_subAgentConnectionMetaForPath(connection, ownerPath);
|
|
3228
|
-
if (meta) metas.push(meta);
|
|
3229
|
-
}
|
|
3230
|
-
return metas;
|
|
3231
|
-
}
|
|
3232
|
-
async _cf_sendToSubAgentConnection(connectionId, message) {
|
|
3233
|
-
const connection = this.lifecycle.getConnection(connectionId);
|
|
3234
|
-
if (!connection || !this._cf_connectionHasSubAgentTarget(connection)) return;
|
|
3235
|
-
connection.send(message);
|
|
3236
|
-
}
|
|
3237
|
-
async _cf_closeSubAgentConnection(connectionId, code, reason) {
|
|
3238
|
-
const connection = this.lifecycle.getConnection(connectionId);
|
|
3239
|
-
if (!connection || !this._cf_connectionHasSubAgentTarget(connection)) return;
|
|
3240
|
-
connection.close(code, reason);
|
|
3241
|
-
}
|
|
3242
|
-
async _cf_setSubAgentConnectionState(connectionId, state) {
|
|
3243
|
-
const connection = this.lifecycle.getConnection(connectionId);
|
|
3244
|
-
if (!connection || !this._cf_connectionHasSubAgentTarget(connection)) return null;
|
|
3245
|
-
this._ensureConnectionWrapped(connection);
|
|
3246
|
-
connection.setState(state);
|
|
3247
|
-
return this._cf_getForwardedSubAgentState(connection);
|
|
3248
|
-
}
|
|
3249
|
-
_cf_subAgentConnectionMetaForPath(connection, ownerPath) {
|
|
3250
|
-
this._ensureConnectionWrapped(connection);
|
|
3251
|
-
const outerUri = this._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY);
|
|
3252
|
-
if (typeof outerUri !== "string") return null;
|
|
3253
|
-
const target = this._cf_subAgentPathFromOuterUri(outerUri, ownerPath);
|
|
3254
|
-
if (!target) return null;
|
|
3255
|
-
const raw = this._cf_getRawConnectionState(connection);
|
|
3256
|
-
const rawTags = raw != null && typeof raw === "object" ? raw[CF_SUB_AGENT_TAGS_KEY] : void 0;
|
|
3257
|
-
const tags = Array.isArray(rawTags) ? rawTags.filter((tag) => typeof tag === "string") : [...connection.tags];
|
|
3258
|
-
return {
|
|
3259
|
-
id: connection.id,
|
|
3260
|
-
uri: target.uri,
|
|
3261
|
-
tags,
|
|
3262
|
-
state: this._cf_getForwardedSubAgentState(connection)
|
|
3263
|
-
};
|
|
3264
|
-
}
|
|
3265
|
-
_cf_subAgentTargetPath(connection) {
|
|
3266
|
-
this._ensureConnectionWrapped(connection);
|
|
3267
|
-
const outerUri = this._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY);
|
|
3268
|
-
if (typeof outerUri !== "string") return null;
|
|
3269
|
-
return this._cf_subAgentPathFromOuterUri(outerUri)?.path ?? null;
|
|
3270
|
-
}
|
|
3271
|
-
_cf_subAgentPathFromOuterUri(outerUri, stopAt) {
|
|
3272
|
-
const ctx = this.ctx;
|
|
3273
|
-
const knownClasses = ctx.exports ? Object.keys(ctx.exports) : void 0;
|
|
3274
|
-
const path = [...this.selfPath];
|
|
3275
|
-
let currentUrl = outerUri;
|
|
3276
|
-
while (true) {
|
|
3277
|
-
const match = parseSubAgentPath(currentUrl, { knownClasses });
|
|
3278
|
-
if (!match) break;
|
|
3279
|
-
path.push({
|
|
3280
|
-
className: match.childClass,
|
|
3281
|
-
name: match.childName
|
|
3282
|
-
});
|
|
3283
|
-
const rewritten = new URL(currentUrl);
|
|
3284
|
-
rewritten.pathname = match.remainingPath;
|
|
3285
|
-
currentUrl = rewritten.toString();
|
|
3286
|
-
if (stopAt && this._isSameAgentPath(path, stopAt)) return {
|
|
3287
|
-
path,
|
|
3288
|
-
uri: currentUrl
|
|
3289
|
-
};
|
|
3290
|
-
}
|
|
3291
|
-
if (path.length === this.selfPath.length) return null;
|
|
3292
|
-
if (stopAt) return null;
|
|
3293
|
-
return {
|
|
3294
|
-
path,
|
|
3295
|
-
uri: currentUrl
|
|
3296
|
-
};
|
|
3297
|
-
}
|
|
3298
|
-
_isSameAgentPath(a, b) {
|
|
3299
|
-
if (a.length !== b.length) return false;
|
|
3300
|
-
return a.every((step, index) => step.className === b[index]?.className && step.name === b[index]?.name);
|
|
3301
|
-
}
|
|
3302
|
-
_cf_connectionHasSubAgentTarget(connection) {
|
|
3303
|
-
this._ensureConnectionWrapped(connection);
|
|
3304
|
-
return typeof this._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY) === "string";
|
|
3305
|
-
}
|
|
3306
|
-
_cf_connectionTargetsSubAgent(connection) {
|
|
3307
|
-
if (!connection.uri) return false;
|
|
3308
|
-
const ctx = this.ctx;
|
|
3309
|
-
return parseSubAgentPath(connection.uri, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 }) !== null;
|
|
3310
|
-
}
|
|
3311
|
-
/**
|
|
3312
|
-
* Returns true when the current request is addressed to a child facet of
|
|
3313
|
-
* this agent rather than to this agent itself.
|
|
3314
|
-
*
|
|
3315
|
-
* Chat-style subclasses wrap `onConnect` before the base Agent forwarding
|
|
3316
|
-
* wrapper runs, so they need a request-level check to avoid sending their
|
|
3317
|
-
* own protocol frames on sockets that are about to be forwarded to a child.
|
|
3318
|
-
*/
|
|
3319
|
-
_cf_requestTargetsSubAgent(request) {
|
|
3320
|
-
const ctx = this.ctx;
|
|
3321
|
-
return parseSubAgentPath(request.url, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 }) !== null;
|
|
3322
|
-
}
|
|
3323
|
-
async _cf_forwardSubAgentWebSocketConnect(connection, request, options) {
|
|
3324
|
-
const routed = await this._cf_resolveSubAgentConnection(connection, request, options);
|
|
3325
|
-
if (!routed) return false;
|
|
3326
|
-
await routed.child._cf_handleSubAgentWebSocketConnect(this._cf_createSubAgentConnectionBridge(connection), routed.meta);
|
|
3327
|
-
return true;
|
|
3328
|
-
}
|
|
3329
|
-
_cf_createSubAgentConnectionBridge(connection) {
|
|
3330
|
-
const upstreamBroadcastBridge = this._isFacet ? this._cf_activeSubAgentBridge(connection.id) : void 0;
|
|
3331
|
-
return new SubAgentConnectionBridge(connection, (ownerPath, message, without) => {
|
|
3332
|
-
if (upstreamBroadcastBridge) return this._cf_routeSubAgentBroadcast(ownerPath, message, without, upstreamBroadcastBridge);
|
|
3333
|
-
return this._cf_broadcastToSubAgent(ownerPath, message, without);
|
|
3334
|
-
});
|
|
3335
|
-
}
|
|
3336
|
-
async _cf_forwardSubAgentWebSocketMessage(connection, message, replyBridge) {
|
|
3337
|
-
const routed = await this._cf_resolveSubAgentConnection(connection);
|
|
3338
|
-
if (!routed) return false;
|
|
3339
|
-
const bridge = this._cf_createSubAgentConnectionBridge(connection);
|
|
3340
|
-
await routed.child._cf_handleSubAgentWebSocketMessage(message, bridge, routed.meta, replyBridge ?? bridge);
|
|
3341
|
-
return true;
|
|
3342
|
-
}
|
|
3343
|
-
async _cf_forwardSubAgentWebSocketClose(connection, code, reason, wasClean) {
|
|
3344
|
-
const routed = await this._cf_resolveSubAgentConnection(connection);
|
|
3345
|
-
if (!routed) return false;
|
|
3346
|
-
await routed.child._cf_handleSubAgentWebSocketClose(code, reason, wasClean, this._cf_createSubAgentConnectionBridge(connection), routed.meta);
|
|
3347
|
-
return true;
|
|
3348
|
-
}
|
|
3349
|
-
async _cf_resolveSubAgentConnection(connection, request, options = { gate: false }) {
|
|
3350
|
-
this._ensureConnectionWrapped(connection);
|
|
3351
|
-
const outerUri = this._unsafe_getConnectionFlag(connection, CF_SUB_AGENT_OUTER_URL_KEY);
|
|
3352
|
-
const uri = typeof outerUri === "string" ? outerUri : connection.uri;
|
|
3353
|
-
if (!uri) return null;
|
|
3354
|
-
const ctx = this.ctx;
|
|
3355
|
-
let match = parseSubAgentPath(uri, { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 });
|
|
3356
|
-
if (!match) return null;
|
|
3357
|
-
if (this._ParentClass.name === match.childClass && this.name === match.childName) {
|
|
3358
|
-
const tailUri = new URL(uri);
|
|
3359
|
-
tailUri.pathname = match.remainingPath;
|
|
3360
|
-
match = parseSubAgentPath(tailUri.toString(), { knownClasses: ctx.exports ? Object.keys(ctx.exports) : void 0 });
|
|
3361
|
-
if (!match) return null;
|
|
3983
|
+
yield* this._dynamicAgents.getVirtualConnections(tag);
|
|
3984
|
+
return;
|
|
3362
3985
|
}
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
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;
|
|
3986
|
+
for (const connection of this._webSockets.getConnections(tag)) {
|
|
3987
|
+
if (this._dynamicAgents.connectionHasChildTarget(connection)) continue;
|
|
3988
|
+
yield connection;
|
|
3374
3989
|
}
|
|
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
|
-
};
|
|
3391
3990
|
}
|
|
3392
|
-
async
|
|
3393
|
-
await this.
|
|
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
|
-
});
|
|
3991
|
+
async _cf_broadcastToSubAgent(ownerPath, message, without) {
|
|
3992
|
+
await this._dynamicAgents.broadcastToPath(ownerPath, message, without);
|
|
3405
3993
|
}
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
this._cf_storeVirtualSubAgentConnection(connection);
|
|
3409
|
-
const replyContext = { bridge: replyBridge };
|
|
3410
|
-
try {
|
|
3411
|
-
await subAgentRpcReplyContext.run(replyContext, () => this._cf_runWithSubAgentBridge(bridge, meta.id, () => this.onMessage(connection, message)));
|
|
3412
|
-
} finally {
|
|
3413
|
-
replyContext.bridge = void 0;
|
|
3414
|
-
}
|
|
3994
|
+
_cf_subAgentConnectionMetas(ownerPath) {
|
|
3995
|
+
return this._dynamicAgents.connectionMetas(ownerPath);
|
|
3415
3996
|
}
|
|
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);
|
|
3997
|
+
_cf_sendToSubAgentConnection(connectionId, message) {
|
|
3998
|
+
return this._dynamicAgents.sendToConnection(connectionId, message);
|
|
3421
3999
|
}
|
|
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
|
-
}
|
|
4000
|
+
_cf_closeSubAgentConnection(connectionId, code, reason) {
|
|
4001
|
+
return this._dynamicAgents.closeConnection(connectionId, code, reason);
|
|
3432
4002
|
}
|
|
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;
|
|
4003
|
+
_cf_setSubAgentConnectionState(connectionId, state) {
|
|
4004
|
+
return this._dynamicAgents.setConnectionState(connectionId, state);
|
|
3481
4005
|
}
|
|
3482
|
-
|
|
3483
|
-
this.
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
4006
|
+
_cf_connectionTargetsSubAgent(connection) {
|
|
4007
|
+
return this._dynamicAgents.connectionTargetsChild(connection);
|
|
4008
|
+
}
|
|
4009
|
+
/**
|
|
4010
|
+
* Returns true when the current request is addressed to a child facet of
|
|
4011
|
+
* this agent rather than to this agent itself.
|
|
4012
|
+
*
|
|
4013
|
+
* Chat-style subclasses wrap `onConnect` before the base Agent forwarding
|
|
4014
|
+
* wrapper runs, so they need a request-level check to avoid sending their
|
|
4015
|
+
* own protocol frames on sockets that are about to be forwarded to a child.
|
|
4016
|
+
*/
|
|
4017
|
+
_cf_requestTargetsSubAgent(request) {
|
|
4018
|
+
return this._dynamicAgents.requestTargetsChild(request);
|
|
3494
4019
|
}
|
|
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 });
|
|
4020
|
+
_cf_forwardSubAgentWebSocketConnect(connection, request, options) {
|
|
4021
|
+
return this._dynamicAgents.forwardWebSocketConnect(connection, request, options);
|
|
3500
4022
|
}
|
|
3501
|
-
|
|
3502
|
-
this.
|
|
3503
|
-
return this._rawStateAccessors.get(connection)?.getRaw() ?? null;
|
|
4023
|
+
_cf_forwardSubAgentWebSocketMessage(connection, message, replyBridge) {
|
|
4024
|
+
return this._dynamicAgents.forwardWebSocketMessage(connection, message, replyBridge);
|
|
3504
4025
|
}
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
return
|
|
4026
|
+
_cf_forwardSubAgentWebSocketClose(connection, code, reason, wasClean) {
|
|
4027
|
+
return this._dynamicAgents.forwardWebSocketClose(connection, code, reason, wasClean);
|
|
4028
|
+
}
|
|
4029
|
+
_cf_handleSubAgentWebSocketConnect(bridge, meta) {
|
|
4030
|
+
return this._dynamicAgents.handleWebSocketConnect(bridge, meta);
|
|
4031
|
+
}
|
|
4032
|
+
_cf_handleSubAgentWebSocketMessage(message, bridge, meta, replyBridge = bridge) {
|
|
4033
|
+
return this._dynamicAgents.handleWebSocketMessage(message, bridge, meta, replyBridge);
|
|
4034
|
+
}
|
|
4035
|
+
_cf_handleSubAgentWebSocketClose(code, reason, wasClean, bridge, meta) {
|
|
4036
|
+
return this._dynamicAgents.handleWebSocketClose(code, reason, wasClean, bridge, meta);
|
|
4037
|
+
}
|
|
4038
|
+
_cf_hydrateSubAgentConnectionsFromRoot() {
|
|
4039
|
+
return this._dynamicAgents.hydrateConnectionsFromRoot();
|
|
3510
4040
|
}
|
|
3511
4041
|
/**
|
|
3512
4042
|
* Parent-side middleware hook. Fires before a request is
|
|
@@ -3543,7 +4073,7 @@ var Agent = class Agent extends DurableObject {
|
|
|
3543
4073
|
* class Inbox extends Agent {
|
|
3544
4074
|
* override async onBeforeSubAgent(req, { className, name }) {
|
|
3545
4075
|
* // Strict registry gate
|
|
3546
|
-
* if (!this.
|
|
4076
|
+
* if (!this.dynamicAgents.has(className, name)) {
|
|
3547
4077
|
* return new Response("Not found", { status: 404 });
|
|
3548
4078
|
* }
|
|
3549
4079
|
* }
|
|
@@ -3557,27 +4087,8 @@ var Agent = class Agent extends DurableObject {
|
|
|
3557
4087
|
*
|
|
3558
4088
|
* @internal
|
|
3559
4089
|
*/
|
|
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);
|
|
4090
|
+
_cf_forwardToFacet(req, match) {
|
|
4091
|
+
return this._dynamicAgents.forward(req, match);
|
|
3581
4092
|
}
|
|
3582
4093
|
/**
|
|
3583
4094
|
* Bridge method used by `getSubAgentByName`. Resolves the facet
|
|
@@ -3586,9 +4097,8 @@ var Agent = class Agent extends DurableObject {
|
|
|
3586
4097
|
*
|
|
3587
4098
|
* @internal
|
|
3588
4099
|
*/
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
return await this._cf_invokeStubMethod(stub, className, method, args);
|
|
4100
|
+
_cf_invokeSubAgent(className, name, method, args) {
|
|
4101
|
+
return this._dynamicAgents.invoke(className, name, method, args);
|
|
3592
4102
|
}
|
|
3593
4103
|
/**
|
|
3594
4104
|
* Bridge method used by `parentAgent()` when the requested parent is
|
|
@@ -3598,20 +4108,8 @@ var Agent = class Agent extends DurableObject {
|
|
|
3598
4108
|
*
|
|
3599
4109
|
* @internal
|
|
3600
4110
|
*/
|
|
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);
|
|
4111
|
+
_cf_invokeSubAgentPath(path, method, args) {
|
|
4112
|
+
return this._dynamicAgents.invokePath(path, method, args);
|
|
3615
4113
|
}
|
|
3616
4114
|
/**
|
|
3617
4115
|
* Initialize this agent as a facet in a single RPC.
|
|
@@ -3637,18 +4135,8 @@ var Agent = class Agent extends DurableObject {
|
|
|
3637
4135
|
*
|
|
3638
4136
|
* @internal Called by {@link subAgent}.
|
|
3639
4137
|
*/
|
|
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();
|
|
4138
|
+
_cf_initAsFacet(name, parentPath = [], identityName = name) {
|
|
4139
|
+
return this._dynamicAgents.init(name, parentPath, identityName);
|
|
3652
4140
|
}
|
|
3653
4141
|
get name() {
|
|
3654
4142
|
const routedName = this.lifecycle.name;
|
|
@@ -3796,9 +4284,11 @@ var Agent = class Agent extends DurableObject {
|
|
|
3796
4284
|
* const searcher = await this.subAgent(SearchAgent, "main-search");
|
|
3797
4285
|
* const results = await searcher.search("cloudflare agents");
|
|
3798
4286
|
* ```
|
|
4287
|
+
*
|
|
4288
|
+
* @deprecated Use {@link Agent.dynamicAgents | this.dynamicAgents.get()} instead.
|
|
3799
4289
|
*/
|
|
3800
4290
|
async subAgent(cls, name) {
|
|
3801
|
-
return
|
|
4291
|
+
return this.dynamicAgents.get(cls, name);
|
|
3802
4292
|
}
|
|
3803
4293
|
async onAgentToolStart(_run) {}
|
|
3804
4294
|
async onAgentToolFinish(_run, _result) {}
|
|
@@ -5185,46 +5675,22 @@ var Agent = class Agent extends DurableObject {
|
|
|
5185
5675
|
*
|
|
5186
5676
|
* @internal
|
|
5187
5677
|
*/
|
|
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;
|
|
5678
|
+
_cf_resolveSubAgent(className, name) {
|
|
5679
|
+
return this._dynamicAgents.resolve(className, name);
|
|
5680
|
+
}
|
|
5681
|
+
/**
|
|
5682
|
+
* Run `body` in a fresh invocation scope with no native request/
|
|
5683
|
+
* connection context attached, so a child-facet RPC never sees
|
|
5684
|
+
* parent-owned I/O handles.
|
|
5685
|
+
* @internal
|
|
5686
|
+
*/
|
|
5687
|
+
_runFacetInitInvocation(body) {
|
|
5688
|
+
return runInInvocation({
|
|
5689
|
+
agent: this,
|
|
5690
|
+
connection: void 0,
|
|
5691
|
+
request: void 0,
|
|
5692
|
+
email: void 0
|
|
5693
|
+
}, body);
|
|
5228
5694
|
}
|
|
5229
5695
|
/**
|
|
5230
5696
|
* Forcefully abort a running sub-agent. The child stops executing
|
|
@@ -5237,12 +5703,11 @@ var Agent = class Agent extends DurableObject {
|
|
|
5237
5703
|
* @param cls The Agent subclass used when creating the child
|
|
5238
5704
|
* @param name Name of the child to abort
|
|
5239
5705
|
* @param reason Error thrown to pending/future RPC callers
|
|
5706
|
+
*
|
|
5707
|
+
* @deprecated Use {@link Agent.dynamicAgents | this.dynamicAgents.abort()} instead.
|
|
5240
5708
|
*/
|
|
5241
5709
|
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);
|
|
5710
|
+
this.dynamicAgents.abort(cls, name, reason);
|
|
5246
5711
|
}
|
|
5247
5712
|
/**
|
|
5248
5713
|
* Delete a sub-agent: abort it if running, then permanently wipe its
|
|
@@ -5252,117 +5717,18 @@ var Agent = class Agent extends DurableObject {
|
|
|
5252
5717
|
*
|
|
5253
5718
|
* @param cls The Agent subclass used when creating the child
|
|
5254
5719
|
* @param name Name of the child to delete
|
|
5720
|
+
*
|
|
5721
|
+
* @deprecated Use {@link Agent.dynamicAgents | this.dynamicAgents.delete()} instead.
|
|
5255
5722
|
*/
|
|
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
|
-
`;
|
|
5723
|
+
deleteSubAgent(cls, name) {
|
|
5724
|
+
return this.dynamicAgents.delete(cls, name);
|
|
5342
5725
|
}
|
|
5343
5726
|
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;
|
|
5727
|
+
return typeof classOrName === "string" ? this.dynamicAgents.has(classOrName, name) : this.dynamicAgents.has(classOrName, name);
|
|
5350
5728
|
}
|
|
5351
5729
|
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
|
-
}));
|
|
5730
|
+
if (typeof classOrName === "string" || classOrName === void 0) return this.dynamicAgents.list(classOrName);
|
|
5731
|
+
return this.dynamicAgents.list(classOrName);
|
|
5366
5732
|
}
|
|
5367
5733
|
/**
|
|
5368
5734
|
* Destroy the Agent, removing all state and scheduled tasks.
|
|
@@ -5392,7 +5758,7 @@ var Agent = class Agent extends DurableObject {
|
|
|
5392
5758
|
await this.ctx.storage.deleteAll();
|
|
5393
5759
|
this._destroyed = true;
|
|
5394
5760
|
setTimeout(() => {
|
|
5395
|
-
this.ctx
|
|
5761
|
+
abortWithoutAlarmRetry(this.ctx, "destroyed");
|
|
5396
5762
|
}, 0);
|
|
5397
5763
|
this._emit("destroy");
|
|
5398
5764
|
}
|
|
@@ -5423,7 +5789,12 @@ var Agent = class Agent extends DurableObject {
|
|
|
5423
5789
|
}
|
|
5424
5790
|
const destroyAt = Date.now() + DESTROY_ALARM_DELAY_MS;
|
|
5425
5791
|
await this.ctx.storage.put(DESTROY_PENDING_KEY, destroyAt);
|
|
5426
|
-
await this.lifecycle.
|
|
5792
|
+
await this.lifecycle.jobs.push({
|
|
5793
|
+
id: HOST_JOB_DESTROY_ID,
|
|
5794
|
+
fn: "destroy",
|
|
5795
|
+
time: destroyAt,
|
|
5796
|
+
exclusive: true
|
|
5797
|
+
});
|
|
5427
5798
|
}
|
|
5428
5799
|
/**
|
|
5429
5800
|
* Whether a (deferred or interrupted) destroy is pending. Reads the
|
|
@@ -5444,32 +5815,14 @@ var Agent = class Agent extends DurableObject {
|
|
|
5444
5815
|
* @returns True if the method is marked as callable
|
|
5445
5816
|
*/
|
|
5446
5817
|
_isCallable(method) {
|
|
5447
|
-
return
|
|
5818
|
+
return isCallableMethod(this[method]);
|
|
5448
5819
|
}
|
|
5449
5820
|
/**
|
|
5450
5821
|
* Get all methods marked as callable on this Agent
|
|
5451
5822
|
* @returns A map of method names to their metadata
|
|
5452
5823
|
*/
|
|
5453
5824
|
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;
|
|
5825
|
+
return new Map(decoratedMethods(this));
|
|
5473
5826
|
}
|
|
5474
5827
|
/**
|
|
5475
5828
|
* Start a workflow and track it in this Agent's database.
|
|
@@ -6447,7 +6800,6 @@ var Agent = class Agent extends DurableObject {
|
|
|
6447
6800
|
}
|
|
6448
6801
|
};
|
|
6449
6802
|
Agent.options = {};
|
|
6450
|
-
Agent._CF_OOM_ALARM_STRIKES_KEY = "cf_agents:oom_alarm_strikes";
|
|
6451
6803
|
const wrappedClasses = /* @__PURE__ */ new Set();
|
|
6452
6804
|
var _email = /* @__PURE__ */ new WeakMap();
|
|
6453
6805
|
_Symbol$dispose = Symbol.dispose;
|
|
@@ -6542,11 +6894,9 @@ var StreamingResponse = class {
|
|
|
6542
6894
|
this._id = id;
|
|
6543
6895
|
}
|
|
6544
6896
|
_send(response) {
|
|
6545
|
-
const
|
|
6546
|
-
if (
|
|
6547
|
-
|
|
6548
|
-
trackFacetStreamingResponseDelivery(this, delivery.completion);
|
|
6549
|
-
return delivery.sent;
|
|
6897
|
+
const facetSent = sendFacetStreamingResponse(this, response);
|
|
6898
|
+
if (facetSent !== null) return facetSent;
|
|
6899
|
+
return sendRpcResponseIfOpen(this._connection, response);
|
|
6550
6900
|
}
|
|
6551
6901
|
/**
|
|
6552
6902
|
* Whether the stream has been closed (via end() or error())
|
|
@@ -6608,6 +6958,6 @@ var StreamingResponse = class {
|
|
|
6608
6958
|
}
|
|
6609
6959
|
};
|
|
6610
6960
|
//#endregion
|
|
6611
|
-
export {
|
|
6961
|
+
export { routeAgentEmail as a, getCurrentAgent as i, DEFAULT_AGENT_STATIC_OPTIONS as n, StreamingResponse as r, Agent as t };
|
|
6612
6962
|
|
|
6613
|
-
//# sourceMappingURL=src-
|
|
6963
|
+
//# sourceMappingURL=src-DlSHshb2.js.map
|