agents 0.16.2 → 0.17.1
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 +11 -8
- package/dist/{agent-tool-types-CTw3UJUP.d.ts → agent-tool-types-CNyE1iz_.d.ts} +671 -138
- package/dist/agent-tool-types.d.ts +34 -18
- package/dist/agent-tool-types.js +20 -1
- package/dist/agent-tool-types.js.map +1 -0
- package/dist/agent-tools-BXlsuX0d.js +304 -0
- package/dist/agent-tools-BXlsuX0d.js.map +1 -0
- package/dist/agent-tools-CSnyGvJ2.d.ts +119 -0
- package/dist/agent-tools.d.ts +24 -18
- package/dist/agent-tools.js.map +1 -1
- package/dist/ai-chat-agent.d.ts +1 -1
- package/dist/ai-chat-agent.js +1 -2
- package/dist/ai-chat-agent.js.map +1 -1
- package/dist/ai-chat-v5-migration.d.ts +1 -1
- package/dist/ai-chat-v5-migration.js +1 -2
- package/dist/ai-chat-v5-migration.js.map +1 -1
- package/dist/ai-react.d.ts +1 -1
- package/dist/ai-react.js +1 -2
- package/dist/ai-react.js.map +1 -1
- package/dist/ai-types.d.ts +1 -7
- package/dist/ai-types.js +2 -8
- package/dist/ai-types.js.map +1 -1
- package/dist/browser/ai.js +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/chat/index.d.ts +2033 -77
- package/dist/chat/index.js +1828 -245
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/react.d.ts +602 -0
- package/dist/chat/react.js +1506 -0
- package/dist/chat/react.js.map +1 -0
- package/dist/chat-sdk/index.d.ts +4 -4
- package/dist/{classPrivateFieldGet2-CZ7QjTXN.js → classPrivateFieldGet2-DZBYAB34.js} +5 -5
- package/dist/{classPrivateMethodInitSpec-D-0__zd9.js → classPrivateMethodInitSpec-qMjJ6sHQ.js} +2 -2
- package/dist/{client-BXJ9n2f7.js → client-BZ-B3NhC.js} +2 -2
- package/dist/client-BZ-B3NhC.js.map +1 -0
- package/dist/client-tools-aIBO0Fk7.d.ts +53 -0
- package/dist/client.d.ts +76 -57
- package/dist/client.js +33 -5
- package/dist/client.js.map +1 -1
- package/dist/{connector-CrKhowfD.js → connector-CdldGF3h.js} +5 -5
- package/dist/{connector-CrKhowfD.js.map → connector-CdldGF3h.js.map} +1 -1
- package/dist/email.js +1 -1
- package/dist/email.js.map +1 -1
- package/dist/{index-B7IbEeze.d.ts → index-BRnybD6X.d.ts} +197 -14
- package/dist/index.d.ts +95 -73
- package/dist/index.js +694 -25
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.d.ts +18 -14
- package/dist/mcp/client.js +1 -1
- package/dist/mcp/index.d.ts +30 -30
- package/dist/mcp/index.js +7 -7
- package/dist/mcp/index.js.map +1 -1
- package/dist/{agent-tools-3zLG7MgA.js → message-builder-BymO4N_D.js} +45 -126
- package/dist/message-builder-BymO4N_D.js.map +1 -0
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +4 -2
- package/dist/observability/index.js.map +1 -1
- package/dist/react.d.ts +123 -110
- package/dist/react.js +44 -11
- package/dist/react.js.map +1 -1
- package/dist/{retries-CwlpAGet.d.ts → retries-CvHJwSuh.d.ts} +15 -6
- package/dist/retries.d.ts +8 -6
- package/dist/retries.js +44 -1
- package/dist/retries.js.map +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/skills/compile.js +1 -1
- package/dist/skills/compile.js.map +1 -1
- package/dist/skills/index.js +5 -5
- package/dist/skills/index.js.map +1 -1
- package/dist/sub-routing.d.ts +6 -6
- package/dist/utils.js +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/vite.js +5 -5
- package/dist/vite.js.map +1 -1
- package/dist/wire-types-nflOzNuU.js +240 -0
- package/dist/wire-types-nflOzNuU.js.map +1 -0
- package/dist/{workflow-types-SrZK_o9p.d.ts → workflow-types-Baz_PO5v.d.ts} +42 -22
- package/dist/workflow-types.d.ts +25 -21
- package/dist/workflow-types.js.map +1 -1
- package/dist/workflows.d.ts +22 -21
- package/dist/workflows.js +31 -7
- package/dist/workflows.js.map +1 -1
- package/docs/adding-to-existing-project.md +450 -0
- package/docs/agent-class.md +503 -0
- package/docs/agent-tools.md +552 -0
- package/docs/browse-the-web.md +430 -0
- package/docs/callable-methods.md +627 -0
- package/docs/chat-agents.md +1696 -0
- package/docs/chat-sdk.md +181 -0
- package/docs/client-sdk.md +520 -0
- package/docs/client-tools-continuation.md +177 -0
- package/docs/codemode.md +440 -0
- package/docs/configuration.md +775 -0
- package/docs/cross-domain-authentication.md +171 -0
- package/docs/durable-execution.md +537 -0
- package/docs/email.md +663 -0
- package/docs/get-current-agent.md +204 -0
- package/docs/getting-started.md +305 -0
- package/docs/http-websockets.md +668 -0
- package/docs/human-in-the-loop.md +661 -0
- package/docs/index.md +151 -0
- package/docs/long-running-agents.md +730 -0
- package/docs/mcp-client.md +620 -0
- package/docs/mcp-servers.md +526 -0
- package/docs/mcp-transports.md +308 -0
- package/docs/migration-to-ai-sdk-v5.md +96 -0
- package/docs/migration-to-ai-sdk-v6.md +163 -0
- package/docs/observability.md +261 -0
- package/docs/push-notifications.md +367 -0
- package/docs/queue.md +329 -0
- package/docs/readonly-connections.md +278 -0
- package/docs/resumable-streaming.md +127 -0
- package/docs/retries.md +444 -0
- package/docs/routing.md +749 -0
- package/docs/scheduling.md +898 -0
- package/docs/securing-mcp-servers.md +359 -0
- package/docs/server-driven-messages.md +477 -0
- package/docs/sessions.md +1024 -0
- package/docs/state.md +512 -0
- package/docs/sub-agents.md +389 -0
- package/docs/webhooks.md +604 -0
- package/docs/workflows.md +877 -0
- package/package.json +41 -14
- package/dist/agent-tools-3zLG7MgA.js.map +0 -1
- package/dist/agent-tools-DZhI5F6Q.d.ts +0 -14
- package/dist/client-BXJ9n2f7.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { AGENT_TOOL_MILESTONE_PART, AGENT_TOOL_PROGRESS_PART } from "./agent-tool-types.js";
|
|
1
2
|
import { __DO_NOT_USE_WILL_BREAK__agentContext } from "./internal_context.js";
|
|
2
3
|
import { MessageType } from "./types.js";
|
|
3
4
|
import { camelCaseToKebabCase, isInternalJsStubProp } from "./utils.js";
|
|
4
5
|
import { createHeaderBasedEmailResolver, signAgentHeaders } from "./email.js";
|
|
5
|
-
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-
|
|
6
|
+
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-DZBYAB34.js";
|
|
6
7
|
import { SUB_PREFIX, getSubAgentByName, parseSubAgentPath, routeSubAgentRequest } from "./sub-routing.js";
|
|
7
|
-
import { isDurableObjectCodeUpdateReset, isErrorRetryable, isPlatformTransientError, tryN, validateRetryOptions } from "./retries.js";
|
|
8
|
-
import { a as MCPConnectionState, c as RPC_DO_PREFIX, i as normalizeServerId, l as DisposableStore, n as MCP_SERVER_ID_MAX_LENGTH, t as MCPClientManager } from "./client-
|
|
8
|
+
import { isDurableObjectCodeUpdateReset, isDurableObjectMemoryLimitReset, isErrorRetryable, isPlatformTransientError, tryN, validateRetryOptions } from "./retries.js";
|
|
9
|
+
import { a as MCPConnectionState, c as RPC_DO_PREFIX, i as normalizeServerId, l as DisposableStore, n as MCP_SERVER_ID_MAX_LENGTH, t as MCPClientManager } from "./client-BZ-B3NhC.js";
|
|
9
10
|
import { DurableObjectOAuthClientProvider } from "./mcp/do-oauth-client-provider.js";
|
|
10
11
|
import { genericObservability } from "./observability/index.js";
|
|
11
12
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -134,6 +135,18 @@ const FIBER_RECOVERY_MAX_BACKOFF_MS = 5 * 6e4;
|
|
|
134
135
|
const FIBER_RECOVERY_BACKOFF_MAX_EXP = 20;
|
|
135
136
|
const DEFAULT_AGENT_TOOL_REATTACH_NO_PROGRESS_TIMEOUT_MS = 12e4;
|
|
136
137
|
const DEFAULT_AGENT_TOOL_REATTACH_MAX_WINDOW_MS = Number.POSITIVE_INFINITY;
|
|
138
|
+
const DEFAULT_DETACHED_MAX_BUDGET_MS = 1440 * 60 * 1e3;
|
|
139
|
+
const DEFAULT_DETACHED_NO_PROGRESS_BUDGET_MS = 3600 * 1e3;
|
|
140
|
+
const DETACHED_DELIVERY_LEASE_MS = 6e4;
|
|
141
|
+
const DETACHED_BACKBONE_CADENCE_S = [
|
|
142
|
+
5,
|
|
143
|
+
15,
|
|
144
|
+
30,
|
|
145
|
+
120
|
|
146
|
+
];
|
|
147
|
+
const DETACHED_LIVE_COUNT_WARN_THRESHOLD = 50;
|
|
148
|
+
const DETACHED_RECONCILE_CALLBACK = "_cfDetachedReconcileTick";
|
|
149
|
+
const DETACHED_NOTIFY_CALLBACK = "_cfDetachedNotifyFinish";
|
|
137
150
|
const SUB_AGENT_IDENTITY_VERSION_LEGACY = "legacy";
|
|
138
151
|
const SUB_AGENT_IDENTITY_VERSION_PATH_V2 = "path-v2";
|
|
139
152
|
const SUB_AGENT_IDENTITY_PATH_V2_PREFIX = "cf-agents:v2:";
|
|
@@ -143,7 +156,7 @@ const SUB_AGENT_IDENTITY_PATH_V2_PREFIX = "cf-agents:v2:";
|
|
|
143
156
|
* The constructor stores this as a row in cf_agents_state and checks it
|
|
144
157
|
* on wake to skip DDL on established DOs.
|
|
145
158
|
*/
|
|
146
|
-
const CURRENT_SCHEMA_VERSION =
|
|
159
|
+
const CURRENT_SCHEMA_VERSION = 11;
|
|
147
160
|
const SCHEMA_VERSION_ROW_ID = "cf_schema_version";
|
|
148
161
|
const STATE_ROW_ID = "cf_state_row_id";
|
|
149
162
|
const STATE_WAS_CHANGED = "cf_state_was_changed";
|
|
@@ -205,7 +218,7 @@ const SUB_AGENT_OUTER_URL_HEADER = "x-cf-agents-subagent-url";
|
|
|
205
218
|
* The set of all internal keys stored in connection state that must be
|
|
206
219
|
* hidden from user code and preserved across setState calls.
|
|
207
220
|
*/
|
|
208
|
-
const CF_INTERNAL_KEYS = new Set([
|
|
221
|
+
const CF_INTERNAL_KEYS = /* @__PURE__ */ new Set([
|
|
209
222
|
CF_READONLY_KEY,
|
|
210
223
|
CF_NO_PROTOCOL_KEY,
|
|
211
224
|
CF_VOICE_IN_CALL_KEY,
|
|
@@ -312,7 +325,18 @@ const DEFAULT_AGENT_STATIC_OPTIONS = {
|
|
|
312
325
|
* `maxRecoveryWork` / `shouldKeepRecovering`. Set a finite value to impose a
|
|
313
326
|
* wall-clock cap (which also tears the child down on `window-exceeded`).
|
|
314
327
|
*/
|
|
315
|
-
agentToolReattachMaxWindowMs: DEFAULT_AGENT_TOOL_REATTACH_MAX_WINDOW_MS
|
|
328
|
+
agentToolReattachMaxWindowMs: DEFAULT_AGENT_TOOL_REATTACH_MAX_WINDOW_MS,
|
|
329
|
+
detachedMaxBudgetMs: DEFAULT_DETACHED_MAX_BUDGET_MS,
|
|
330
|
+
detachedNoProgressBudgetMs: DEFAULT_DETACHED_NO_PROGRESS_BUDGET_MS,
|
|
331
|
+
/**
|
|
332
|
+
* Consecutive alarm invocations that may end in a Durable Object memory-limit
|
|
333
|
+
* reset (the isolate exceeded its 128 MB limit) before the alarm-boundary
|
|
334
|
+
* circuit breaker stops the platform's auto-retry loop and seals the looping
|
|
335
|
+
* work (#1825). A small budget tolerates a genuinely transient memory spike;
|
|
336
|
+
* a deterministic OOM (the work's footprint, not the platform, is the cause)
|
|
337
|
+
* is bounded here regardless of whether the in-DO recovery budgets could run.
|
|
338
|
+
*/
|
|
339
|
+
maxAlarmMemoryLimitStrikes: 3
|
|
316
340
|
};
|
|
317
341
|
/**
|
|
318
342
|
* Parse the raw `retry_options` TEXT column from a SQLite row into a
|
|
@@ -437,7 +461,10 @@ var Agent = class Agent extends Server {
|
|
|
437
461
|
fiberRecoveryScanDeadlineMs: ctor.options?.fiberRecoveryScanDeadlineMs ?? DEFAULT_AGENT_STATIC_OPTIONS.fiberRecoveryScanDeadlineMs,
|
|
438
462
|
fiberRecoveryMaxAgeMs: ctor.options?.fiberRecoveryMaxAgeMs ?? DEFAULT_AGENT_STATIC_OPTIONS.fiberRecoveryMaxAgeMs,
|
|
439
463
|
agentToolReattachNoProgressTimeoutMs: ctor.options?.agentToolReattachNoProgressTimeoutMs ?? DEFAULT_AGENT_STATIC_OPTIONS.agentToolReattachNoProgressTimeoutMs,
|
|
440
|
-
agentToolReattachMaxWindowMs: ctor.options?.agentToolReattachMaxWindowMs ?? DEFAULT_AGENT_STATIC_OPTIONS.agentToolReattachMaxWindowMs
|
|
464
|
+
agentToolReattachMaxWindowMs: ctor.options?.agentToolReattachMaxWindowMs ?? DEFAULT_AGENT_STATIC_OPTIONS.agentToolReattachMaxWindowMs,
|
|
465
|
+
detachedMaxBudgetMs: ctor.options?.detachedMaxBudgetMs ?? DEFAULT_AGENT_STATIC_OPTIONS.detachedMaxBudgetMs,
|
|
466
|
+
detachedNoProgressBudgetMs: ctor.options?.detachedNoProgressBudgetMs ?? DEFAULT_AGENT_STATIC_OPTIONS.detachedNoProgressBudgetMs,
|
|
467
|
+
maxAlarmMemoryLimitStrikes: ctor.options?.maxAlarmMemoryLimitStrikes ?? DEFAULT_AGENT_STATIC_OPTIONS.maxAlarmMemoryLimitStrikes
|
|
441
468
|
};
|
|
442
469
|
return this._cachedOptions;
|
|
443
470
|
}
|
|
@@ -679,6 +706,17 @@ var Agent = class Agent extends Server {
|
|
|
679
706
|
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN output_json TEXT");
|
|
680
707
|
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN interrupted_reason TEXT");
|
|
681
708
|
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN child_still_running INTEGER");
|
|
709
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached INTEGER NOT NULL DEFAULT 0");
|
|
710
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_on_finish TEXT");
|
|
711
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_notify_source TEXT");
|
|
712
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_max_budget_at INTEGER");
|
|
713
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN finish_claimed_at INTEGER");
|
|
714
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN finish_delivered_at INTEGER");
|
|
715
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN give_up_claimed_at INTEGER");
|
|
716
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN give_up_delivered_at INTEGER");
|
|
717
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_no_progress_budget_ms INTEGER");
|
|
718
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN last_progress_at INTEGER");
|
|
719
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_on_milestones TEXT");
|
|
682
720
|
this.sql`
|
|
683
721
|
INSERT OR REPLACE INTO cf_agents_state (id, state)
|
|
684
722
|
VALUES (${SCHEMA_VERSION_ROW_ID}, ${String(CURRENT_SCHEMA_VERSION)})
|
|
@@ -707,6 +745,8 @@ var Agent = class Agent extends Server {
|
|
|
707
745
|
this._managedFiberTerminalWaiters = /* @__PURE__ */ new Map();
|
|
708
746
|
this._runFiberRecoveryInProgress = false;
|
|
709
747
|
this._recoveryNoProgressScans = 0;
|
|
748
|
+
this._detachedBackboneArming = Promise.resolve();
|
|
749
|
+
this._detachedLiveCountWarned = false;
|
|
710
750
|
this._ParentClass = Object.getPrototypeOf(this).constructor;
|
|
711
751
|
this.initialState = DEFAULT_STATE;
|
|
712
752
|
this.observability = genericObservability;
|
|
@@ -2641,7 +2681,7 @@ var Agent = class Agent extends Server {
|
|
|
2641
2681
|
return true;
|
|
2642
2682
|
}
|
|
2643
2683
|
async deleteFibers(options) {
|
|
2644
|
-
const terminalStatuses = [...this._normalizeFiberStatusFilter(options?.status) ?? new Set([
|
|
2684
|
+
const terminalStatuses = [...this._normalizeFiberStatusFilter(options?.status) ?? /* @__PURE__ */ new Set([
|
|
2645
2685
|
"completed",
|
|
2646
2686
|
"aborted",
|
|
2647
2687
|
"error"
|
|
@@ -3138,6 +3178,25 @@ var Agent = class Agent extends Server {
|
|
|
3138
3178
|
return (await this._cf_resolveSubAgent(next.className, next.name))._cf_dispatchScheduledCallback(ownerPath, row);
|
|
3139
3179
|
}
|
|
3140
3180
|
/**
|
|
3181
|
+
* Invoke an RPC method on this Agent or a descendant facet identified
|
|
3182
|
+
* by a root-first path. Used by AgentWorkflow to route callbacks and
|
|
3183
|
+
* `this.agent` calls back to the exact sub-agent that started a workflow.
|
|
3184
|
+
* @internal
|
|
3185
|
+
*/
|
|
3186
|
+
async _cf_invokeAgentPath(targetPath, method, args) {
|
|
3187
|
+
await this.__unsafe_ensureInitialized();
|
|
3188
|
+
const selfPath = this.selfPath;
|
|
3189
|
+
if (!this._isSameAgentPathPrefix(selfPath, targetPath)) throw new Error(`Workflow origin path does not descend from ${JSON.stringify(selfPath)}.`);
|
|
3190
|
+
if (selfPath.length === targetPath.length) {
|
|
3191
|
+
const fn = this[method];
|
|
3192
|
+
if (isInternalJsStubProp(method) || method in Object.prototype || typeof fn !== "function") throw new Error(`Workflow origin method "${method}" is not callable on ${this.constructor.name}.`);
|
|
3193
|
+
return await fn.apply(this, args);
|
|
3194
|
+
}
|
|
3195
|
+
const next = targetPath[selfPath.length];
|
|
3196
|
+
if (!this.hasSubAgent(next.className, next.name)) throw new Error(`Workflow origin sub-agent ${next.className} "${next.name}" no longer exists.`);
|
|
3197
|
+
return await (await this._cf_resolveSubAgent(next.className, next.name))._cf_invokeAgentPath(targetPath, method, args);
|
|
3198
|
+
}
|
|
3199
|
+
/**
|
|
3141
3200
|
* Recursively destroy a descendant facet identified by
|
|
3142
3201
|
* `targetPath`. Walks down from `selfPath` until reaching the
|
|
3143
3202
|
* target's immediate parent, where it cancels the target's
|
|
@@ -3199,6 +3258,7 @@ var Agent = class Agent extends Server {
|
|
|
3199
3258
|
const isOneShotSchedule = row.type === "delayed" || row.type === "scheduled";
|
|
3200
3259
|
const shouldDeferReset = (error) => isOneShotSchedule && isDurableObjectCodeUpdateReset(error);
|
|
3201
3260
|
const shouldDeferOnExhaustion = (error) => isOneShotSchedule && isPlatformTransientError(error);
|
|
3261
|
+
const shouldDeferMemoryLimit = (error) => isOneShotSchedule && isDurableObjectMemoryLimitReset(error);
|
|
3202
3262
|
try {
|
|
3203
3263
|
this._emit("schedule:execute", {
|
|
3204
3264
|
callback: row.callback,
|
|
@@ -3226,6 +3286,10 @@ var Agent = class Agent extends Server {
|
|
|
3226
3286
|
console.warn(`Deferring scheduled callback "${row.callback}" after exhausting in-process retries on a transient platform error; the one-shot row is preserved and the alarm will re-run once the platform recovers.`);
|
|
3227
3287
|
throw e;
|
|
3228
3288
|
}
|
|
3289
|
+
if (shouldDeferMemoryLimit(e)) {
|
|
3290
|
+
console.warn(`Deferring scheduled callback "${row.callback}" to the alarm memory-limit circuit breaker after a Durable Object memory-limit reset; the one-shot row is preserved so the breaker can bound the retry loop and seal it (#1825).`);
|
|
3291
|
+
throw e;
|
|
3292
|
+
}
|
|
3229
3293
|
console.error(`error executing callback "${row.callback}" after ${maxAttempts} attempts`, e);
|
|
3230
3294
|
this._emit("schedule:error", {
|
|
3231
3295
|
callback: row.callback,
|
|
@@ -3337,6 +3401,20 @@ var Agent = class Agent extends Server {
|
|
|
3337
3401
|
await this.destroy();
|
|
3338
3402
|
return;
|
|
3339
3403
|
}
|
|
3404
|
+
try {
|
|
3405
|
+
await this._cf_runAlarmBody();
|
|
3406
|
+
await this._cf_clearAlarmMemoryLimitStrikes();
|
|
3407
|
+
} catch (error) {
|
|
3408
|
+
if (!isDurableObjectMemoryLimitReset(error)) throw error;
|
|
3409
|
+
await this._cf_handleAlarmMemoryLimitReset(error);
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
/**
|
|
3413
|
+
* The alarm body: PartyServer init + due-schedule processing + housekeeping +
|
|
3414
|
+
* next-alarm arm. Extracted from {@link alarm} so the memory-limit circuit
|
|
3415
|
+
* breaker can wrap it at the outermost frame (see {@link alarm}).
|
|
3416
|
+
*/
|
|
3417
|
+
async _cf_runAlarmBody() {
|
|
3340
3418
|
await super.alarm();
|
|
3341
3419
|
const now = Math.floor(Date.now() / 1e3);
|
|
3342
3420
|
const result = this.sql`
|
|
@@ -3387,7 +3465,9 @@ var Agent = class Agent extends Server {
|
|
|
3387
3465
|
continue;
|
|
3388
3466
|
}
|
|
3389
3467
|
else {
|
|
3468
|
+
this._cf_executingScheduleRowId = row.id;
|
|
3390
3469
|
await this._executeScheduleCallback(row);
|
|
3470
|
+
this._cf_executingScheduleRowId = void 0;
|
|
3391
3471
|
executed = true;
|
|
3392
3472
|
}
|
|
3393
3473
|
if (this._destroyed) return;
|
|
@@ -3413,6 +3493,105 @@ var Agent = class Agent extends Server {
|
|
|
3413
3493
|
await this._scheduleNextAlarm();
|
|
3414
3494
|
}
|
|
3415
3495
|
/**
|
|
3496
|
+
* The schedule-callback names whose alarm rows drive a recovery loop that can
|
|
3497
|
+
* deterministically OOM. The base agent has none; chat hosts (`Think`,
|
|
3498
|
+
* `AIChatAgent`) override this to return their recovery continuation callbacks
|
|
3499
|
+
* so the circuit breaker can surgically back them off / purge them WITHOUT
|
|
3500
|
+
* disturbing unrelated scheduled tasks. See {@link _cf_handleAlarmMemoryLimitReset}.
|
|
3501
|
+
*/
|
|
3502
|
+
_cf_recoveryAlarmCallbacks() {
|
|
3503
|
+
return [];
|
|
3504
|
+
}
|
|
3505
|
+
/**
|
|
3506
|
+
* Hook for a host to terminalize ("seal") any in-flight recovery work as an
|
|
3507
|
+
* out-of-memory exhaustion when the alarm circuit breaker trips at its strike
|
|
3508
|
+
* budget (#1825). Runs at the outermost alarm frame (post-unwind, so writes
|
|
3509
|
+
* can land). Default: no-op. Chat hosts override to fire `onExhausted` + the
|
|
3510
|
+
* terminal banner and persist the sealed incident.
|
|
3511
|
+
*/
|
|
3512
|
+
async _cf_sealMemoryLimitedRecovery() {}
|
|
3513
|
+
/**
|
|
3514
|
+
* Clear the durable memory-limit strike counter after a clean alarm so the
|
|
3515
|
+
* circuit breaker counts CONSECUTIVE resets rather than lifetime ones
|
|
3516
|
+
* (#1825). Reads first (cheap, usually cached) and only writes when a strike
|
|
3517
|
+
* is actually recorded, so the common no-strike path costs no write.
|
|
3518
|
+
* Best-effort: a stale strike only costs one extra tolerated spike later.
|
|
3519
|
+
*/
|
|
3520
|
+
async _cf_clearAlarmMemoryLimitStrikes() {
|
|
3521
|
+
try {
|
|
3522
|
+
const prior = await this.ctx.storage.get(Agent._CF_OOM_ALARM_STRIKES_KEY);
|
|
3523
|
+
if (typeof prior === "number" && prior > 0) await this.ctx.storage.delete(Agent._CF_OOM_ALARM_STRIKES_KEY);
|
|
3524
|
+
} catch {}
|
|
3525
|
+
}
|
|
3526
|
+
/**
|
|
3527
|
+
* Alarm-boundary circuit breaker for Durable Object memory-limit resets
|
|
3528
|
+
* (#1825). The in-DO recovery budgets (`chatRecovery.maxOomRetries` /
|
|
3529
|
+
* `maxRecoveryWork`) only engage if their code runs AND its writes land; a
|
|
3530
|
+
* severe OOM can defeat both — thrown before the budget runs (boot hydration),
|
|
3531
|
+
* or its own small writes also OOM under memory pressure. In that case the
|
|
3532
|
+
* error reaches {@link alarm} and, unhandled, the platform auto-retries the
|
|
3533
|
+
* alarm indefinitely (re-running the doomed, billable turn each cycle).
|
|
3534
|
+
*
|
|
3535
|
+
* This runs at the OUTERMOST frame: the heavy turn has unwound and GC has
|
|
3536
|
+
* reclaimed its footprint, so the small writes here can land where mid-turn
|
|
3537
|
+
* ones (e.g. give-up's incident read) OOMed. A durable strike counter tolerates
|
|
3538
|
+
* a few resets (a transient spike may clear), backing off the recovery rows so
|
|
3539
|
+
* the retry is not a hot loop. At the `maxAlarmMemoryLimitStrikes` budget it
|
|
3540
|
+
* seals the recovery work and purges the looping rows so the loop — and the
|
|
3541
|
+
* bill — stops. Each step is best-effort: even these tiny writes can OOM, but
|
|
3542
|
+
* swallowing (not re-throwing) still halts the platform's auto-retry, and a
|
|
3543
|
+
* later wake re-arms legitimate schedules.
|
|
3544
|
+
*/
|
|
3545
|
+
async _cf_handleAlarmMemoryLimitReset(error) {
|
|
3546
|
+
const key = Agent._CF_OOM_ALARM_STRIKES_KEY;
|
|
3547
|
+
let strikes = 1;
|
|
3548
|
+
try {
|
|
3549
|
+
const prior = await this.ctx.storage.get(key);
|
|
3550
|
+
strikes = (typeof prior === "number" ? prior : 0) + 1;
|
|
3551
|
+
await this.ctx.storage.put(key, strikes);
|
|
3552
|
+
} catch {}
|
|
3553
|
+
const limit = this._resolvedOptions.maxAlarmMemoryLimitStrikes;
|
|
3554
|
+
const sealed = strikes >= limit;
|
|
3555
|
+
const recoveryCallbacks = this._cf_recoveryAlarmCallbacks();
|
|
3556
|
+
const executingRowId = this._cf_executingScheduleRowId;
|
|
3557
|
+
this._cf_executingScheduleRowId = void 0;
|
|
3558
|
+
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));
|
|
3559
|
+
if (sealed) {
|
|
3560
|
+
for (const cb of recoveryCallbacks) try {
|
|
3561
|
+
this.sql`DELETE FROM cf_agents_schedules WHERE callback = ${cb}`;
|
|
3562
|
+
} catch {}
|
|
3563
|
+
if (executingRowId) try {
|
|
3564
|
+
this.sql`DELETE FROM cf_agents_schedules WHERE id = ${executingRowId}`;
|
|
3565
|
+
} catch {}
|
|
3566
|
+
try {
|
|
3567
|
+
await this._cf_sealMemoryLimitedRecovery();
|
|
3568
|
+
} catch {}
|
|
3569
|
+
try {
|
|
3570
|
+
await this.ctx.storage.delete(key);
|
|
3571
|
+
} catch {}
|
|
3572
|
+
} else {
|
|
3573
|
+
const backoffSeconds = Math.min(300, 30 * strikes);
|
|
3574
|
+
const nextTime = Math.floor(Date.now() / 1e3) + backoffSeconds;
|
|
3575
|
+
for (const cb of recoveryCallbacks) try {
|
|
3576
|
+
this.sql`UPDATE cf_agents_schedules SET time = ${nextTime} WHERE callback = ${cb} AND time <= ${nextTime}`;
|
|
3577
|
+
} catch {}
|
|
3578
|
+
if (executingRowId) try {
|
|
3579
|
+
this.sql`UPDATE cf_agents_schedules SET time = ${nextTime} WHERE id = ${executingRowId} AND time <= ${nextTime}`;
|
|
3580
|
+
} catch {}
|
|
3581
|
+
}
|
|
3582
|
+
try {
|
|
3583
|
+
this._emit("alarm:memory_limit_reset", {
|
|
3584
|
+
strikes,
|
|
3585
|
+
limit,
|
|
3586
|
+
sealed,
|
|
3587
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3588
|
+
});
|
|
3589
|
+
} catch {}
|
|
3590
|
+
try {
|
|
3591
|
+
await this._scheduleNextAlarm();
|
|
3592
|
+
} catch {}
|
|
3593
|
+
}
|
|
3594
|
+
/**
|
|
3416
3595
|
* Intercept incoming HTTP/WS requests whose URL contains a
|
|
3417
3596
|
* `/sub/{child-class}/{child-name}` marker and forward them to
|
|
3418
3597
|
* the facet. The `onBeforeSubAgent` hook fires first (authorize,
|
|
@@ -4075,11 +4254,40 @@ var Agent = class Agent extends Server {
|
|
|
4075
4254
|
}
|
|
4076
4255
|
async onAgentToolStart(_run) {}
|
|
4077
4256
|
async onAgentToolFinish(_run, _result) {}
|
|
4257
|
+
/**
|
|
4258
|
+
* Parent hook fired (best-effort) whenever a child agent-tool run emits a
|
|
4259
|
+
* `reportProgress` signal that is forwarded through this parent's tail. Use it
|
|
4260
|
+
* to meter / steer / surface progress server-side. Fires for both awaited and
|
|
4261
|
+
* detached runs; it is NOT durable — after eviction a detached run's latest
|
|
4262
|
+
* snapshot is read from `inspectAgentToolRun().progress` on reconcile instead.
|
|
4263
|
+
*/
|
|
4264
|
+
async onProgress(_run, _progress) {}
|
|
4265
|
+
/**
|
|
4266
|
+
* Emit an ephemeral progress signal from a sub-agent that is currently running
|
|
4267
|
+
* as an agent tool. Rides the child's active turn stream as a transient
|
|
4268
|
+
* `data-agent-progress` part (re-broadcast to the parent's clients + surfaced
|
|
4269
|
+
* in `useAgentToolEvents`) and persists a latest-wins snapshot for recovery /
|
|
4270
|
+
* inspection. A no-op (with a dev warning) on the base `Agent`, which has no
|
|
4271
|
+
* streaming turn — overridden by chat hosts (`@cloudflare/think`,
|
|
4272
|
+
* `AIChatAgent`). See `design/rfc-detached-agent-tools.md`.
|
|
4273
|
+
*/
|
|
4274
|
+
async reportProgress(_progress, _options) {
|
|
4275
|
+
console.warn("[agents] reportProgress() is only supported on chat agents (@cloudflare/think, AIChatAgent) running as an agent tool; ignoring on base Agent.");
|
|
4276
|
+
}
|
|
4078
4277
|
async runAgentTool(cls, options) {
|
|
4079
4278
|
const runId = options.runId ?? nanoid(12);
|
|
4080
4279
|
const agentType = cls.name;
|
|
4280
|
+
const detached = this._parseDetachedOption(options.detached);
|
|
4081
4281
|
const existing = this._readAgentToolRun(runId);
|
|
4082
4282
|
if (existing) {
|
|
4283
|
+
if (detached) {
|
|
4284
|
+
if (!this._isAgentToolRowHardTerminal(existing.status)) await this._armDetachedBackbone();
|
|
4285
|
+
return {
|
|
4286
|
+
runId,
|
|
4287
|
+
agentType,
|
|
4288
|
+
status: "running"
|
|
4289
|
+
};
|
|
4290
|
+
}
|
|
4083
4291
|
if (existing.status === "completed" || existing.status === "error" || existing.status === "aborted") {
|
|
4084
4292
|
if (existing.status === "completed" && existing.output_json == null) try {
|
|
4085
4293
|
const child = await this.subAgent(cls, runId);
|
|
@@ -4151,14 +4359,22 @@ var Agent = class Agent extends Server {
|
|
|
4151
4359
|
error
|
|
4152
4360
|
};
|
|
4153
4361
|
}
|
|
4362
|
+
const detachedMaxBudgetAt = detached ? startedAt + (detached.maxBudgetMs ?? this._resolvedOptions.detachedMaxBudgetMs) : null;
|
|
4363
|
+
const detachedNoProgressBudgetMs = detached ? detached.noProgressBudgetMs ?? this._resolvedOptions.detachedNoProgressBudgetMs : null;
|
|
4364
|
+
const detachedOnMilestonesJson = detached?.onMilestones ? JSON.stringify(detached.onMilestones) : null;
|
|
4154
4365
|
this.sql`
|
|
4155
4366
|
INSERT INTO cf_agent_tool_runs (
|
|
4156
4367
|
run_id, parent_tool_call_id, agent_type, input_preview,
|
|
4157
|
-
input_redacted, status, display_metadata, display_order, started_at
|
|
4368
|
+
input_redacted, status, display_metadata, display_order, started_at,
|
|
4369
|
+
detached, detached_on_finish, detached_notify_source,
|
|
4370
|
+
detached_max_budget_at, detached_no_progress_budget_ms,
|
|
4371
|
+
detached_on_milestones
|
|
4158
4372
|
) VALUES (
|
|
4159
4373
|
${runId}, ${options.parentToolCallId ?? null}, ${agentType},
|
|
4160
4374
|
${inputPreviewJson}, 1, 'starting', ${displayJson}, ${displayOrder},
|
|
4161
|
-
${startedAt}
|
|
4375
|
+
${startedAt}, ${detached ? 1 : 0}, ${detached?.onFinishName ?? null},
|
|
4376
|
+
${detached?.notifySource ?? null}, ${detachedMaxBudgetAt},
|
|
4377
|
+
${detachedNoProgressBudgetMs}, ${detachedOnMilestonesJson}
|
|
4162
4378
|
)
|
|
4163
4379
|
`;
|
|
4164
4380
|
const runInfo = {
|
|
@@ -4168,6 +4384,7 @@ var Agent = class Agent extends Server {
|
|
|
4168
4384
|
inputPreview,
|
|
4169
4385
|
status: "starting",
|
|
4170
4386
|
display: options.display,
|
|
4387
|
+
...detached?.notifySource !== void 0 ? { notifySource: detached.notifySource } : {},
|
|
4171
4388
|
displayOrder,
|
|
4172
4389
|
startedAt
|
|
4173
4390
|
};
|
|
@@ -4184,6 +4401,17 @@ var Agent = class Agent extends Server {
|
|
|
4184
4401
|
const adapter = this._asAgentToolChildAdapter(child);
|
|
4185
4402
|
const childStart = await adapter.startAgentToolRun(options.input, { runId });
|
|
4186
4403
|
this._markAgentToolRunning(runId);
|
|
4404
|
+
if (detached) {
|
|
4405
|
+
if (options.signal) console.warn(`[agents] runAgentTool: \`signal\` is ignored for a detached run (${runId}); a detached child must outlive the spawning turn. Use cancelAgentTool(runId) to cancel it.`);
|
|
4406
|
+
await this._armDetachedBackbone({ resetCadence: true });
|
|
4407
|
+
this._maybeWarnDetachedLiveCount();
|
|
4408
|
+
this.ctx.waitUntil(this._detachedFastPath(runInfo, cls, runId));
|
|
4409
|
+
return {
|
|
4410
|
+
runId,
|
|
4411
|
+
agentType,
|
|
4412
|
+
status: "running"
|
|
4413
|
+
};
|
|
4414
|
+
}
|
|
4187
4415
|
let sequence = 1;
|
|
4188
4416
|
let parentAbortListener;
|
|
4189
4417
|
if (options.signal) if (options.signal.aborted) {
|
|
@@ -4252,6 +4480,332 @@ var Agent = class Agent extends Server {
|
|
|
4252
4480
|
if (parentAbortListener && options.signal) options.signal.removeEventListener("abort", parentAbortListener);
|
|
4253
4481
|
}
|
|
4254
4482
|
}
|
|
4483
|
+
/**
|
|
4484
|
+
* Cancel an agent-tool run by id. Idempotent: cancelling an already-terminal
|
|
4485
|
+
* run is a no-op. Detached runs deliver through the guarded ledger so a wired
|
|
4486
|
+
* `onFinish` fires once with `status: "aborted"`; awaited runs leave terminal
|
|
4487
|
+
* observation to the awaiting/recovery path, avoiding duplicate finish hooks.
|
|
4488
|
+
*/
|
|
4489
|
+
async cancelAgentTool(runId, reason) {
|
|
4490
|
+
const row = this._readAgentToolRun(runId);
|
|
4491
|
+
if (!row) return;
|
|
4492
|
+
if (this._isAgentToolRowHardTerminal(row.status)) return;
|
|
4493
|
+
const isDetached = row.detached === 1;
|
|
4494
|
+
const message = reason instanceof Error ? reason.message : String(reason ?? "cancelled by parent");
|
|
4495
|
+
try {
|
|
4496
|
+
const child = await this._cf_resolveSubAgent(row.agent_type, runId);
|
|
4497
|
+
await this._asAgentToolChildAdapter(child).cancelAgentToolRun(runId, reason);
|
|
4498
|
+
} catch {}
|
|
4499
|
+
if (!isDetached) return;
|
|
4500
|
+
await this._deliverDetachedTerminal(runId, "finish", {
|
|
4501
|
+
runId,
|
|
4502
|
+
agentType: row.agent_type,
|
|
4503
|
+
status: "aborted",
|
|
4504
|
+
error: message
|
|
4505
|
+
});
|
|
4506
|
+
}
|
|
4507
|
+
/**
|
|
4508
|
+
* Parse + validate the `detached` option. Returns `null` for a non-detached
|
|
4509
|
+
* run, or the normalized config (with the validated `onFinish` method name)
|
|
4510
|
+
* for a detached one. Throws if `onFinish` does not name a method on this
|
|
4511
|
+
* agent — closures cannot survive Durable Object eviction, so the durable
|
|
4512
|
+
* hook is referenced by method name (the same contract as `schedule`).
|
|
4513
|
+
*/
|
|
4514
|
+
_parseDetachedOption(detached) {
|
|
4515
|
+
if (!detached) return null;
|
|
4516
|
+
if (detached === true) return {};
|
|
4517
|
+
let onFinishName = detached.onFinish;
|
|
4518
|
+
const notifySource = typeof detached.notify === "object" ? detached.notify.source : void 0;
|
|
4519
|
+
if (onFinishName !== void 0) {
|
|
4520
|
+
if (typeof this[onFinishName] !== "function") throw new Error(`runAgentTool: detached.onFinish "${onFinishName}" is not a method on ${this.constructor.name}. Pass the NAME of a method (e.g. "onImportDone"), not a closure — closures cannot be rehydrated after the Durable Object is evicted.`);
|
|
4521
|
+
} else if (detached.notify) {
|
|
4522
|
+
if (typeof this[DETACHED_NOTIFY_CALLBACK] === "function") onFinishName = DETACHED_NOTIFY_CALLBACK;
|
|
4523
|
+
}
|
|
4524
|
+
return {
|
|
4525
|
+
...onFinishName !== void 0 ? { onFinishName } : {},
|
|
4526
|
+
...notifySource !== void 0 ? { notifySource } : {},
|
|
4527
|
+
...detached.maxBudgetMs !== void 0 ? { maxBudgetMs: detached.maxBudgetMs } : {},
|
|
4528
|
+
...detached.noProgressBudgetMs !== void 0 ? { noProgressBudgetMs: detached.noProgressBudgetMs } : {},
|
|
4529
|
+
...(() => {
|
|
4530
|
+
const raw = detached.onMilestones;
|
|
4531
|
+
if (!raw) return {};
|
|
4532
|
+
const names = Array.isArray(raw) ? raw : raw.names;
|
|
4533
|
+
if (!Array.isArray(names) || names.length === 0) return {};
|
|
4534
|
+
return { onMilestones: {
|
|
4535
|
+
names,
|
|
4536
|
+
mode: Array.isArray(raw) ? "narrate" : raw.mode ?? "narrate"
|
|
4537
|
+
} };
|
|
4538
|
+
})()
|
|
4539
|
+
};
|
|
4540
|
+
}
|
|
4541
|
+
_isAgentToolRowHardTerminal(status) {
|
|
4542
|
+
return status === "completed" || status === "error" || status === "aborted";
|
|
4543
|
+
}
|
|
4544
|
+
_hasOutstandingDetachedRuns() {
|
|
4545
|
+
return (this.sql`
|
|
4546
|
+
SELECT COUNT(*) AS n FROM cf_agent_tool_runs
|
|
4547
|
+
WHERE detached = 1 AND finish_delivered_at IS NULL
|
|
4548
|
+
`[0]?.n ?? 0) > 0;
|
|
4549
|
+
}
|
|
4550
|
+
/** Detached runs still holding a concurrency slot (non-terminal). */
|
|
4551
|
+
_liveDetachedRunCount() {
|
|
4552
|
+
return this.sql`
|
|
4553
|
+
SELECT COUNT(*) AS n FROM cf_agent_tool_runs
|
|
4554
|
+
WHERE detached = 1 AND status IN ('starting', 'running')
|
|
4555
|
+
`[0]?.n ?? 0;
|
|
4556
|
+
}
|
|
4557
|
+
/**
|
|
4558
|
+
* Edge-triggered warning when live detached runs cross
|
|
4559
|
+
* `DETACHED_LIVE_COUNT_WARN_THRESHOLD`. Fires once on the up-crossing and
|
|
4560
|
+
* re-arms only after the count falls back below the threshold, so a parent
|
|
4561
|
+
* accumulating long-lived background runs surfaces a signal without spamming.
|
|
4562
|
+
*/
|
|
4563
|
+
_maybeWarnDetachedLiveCount() {
|
|
4564
|
+
const liveCount = this._liveDetachedRunCount();
|
|
4565
|
+
if (liveCount < DETACHED_LIVE_COUNT_WARN_THRESHOLD) {
|
|
4566
|
+
this._detachedLiveCountWarned = false;
|
|
4567
|
+
return;
|
|
4568
|
+
}
|
|
4569
|
+
if (this._detachedLiveCountWarned) return;
|
|
4570
|
+
this._detachedLiveCountWarned = true;
|
|
4571
|
+
this._emit("agent_tool:detached:live_count_warning", {
|
|
4572
|
+
liveCount,
|
|
4573
|
+
threshold: DETACHED_LIVE_COUNT_WARN_THRESHOLD
|
|
4574
|
+
});
|
|
4575
|
+
console.warn(`[agents] ${liveCount} detached agent-tool runs are live on this agent (threshold ${DETACHED_LIVE_COUNT_WARN_THRESHOLD}). Detached runs hold a concurrency slot until they finish — make sure they are completing or being cancelled, or lower \`maxConcurrentAgentTools\`.`);
|
|
4576
|
+
}
|
|
4577
|
+
/**
|
|
4578
|
+
* Warm fast path for a detached run: tail the child to terminal (so the
|
|
4579
|
+
* parent re-broadcasts its live stream to clients) and deliver the completion
|
|
4580
|
+
* with low latency while the isolate stays alive. Best-effort — the durable
|
|
4581
|
+
* `_cfDetachedReconcileTick` backbone is the guarantee; anything this misses
|
|
4582
|
+
* (eviction, a child that has not yet reached terminal) the backbone collects.
|
|
4583
|
+
*/
|
|
4584
|
+
async _detachedFastPath(runInfo, cls, runId) {
|
|
4585
|
+
try {
|
|
4586
|
+
const child = await this.subAgent(cls, runId);
|
|
4587
|
+
const adapter = this._asAgentToolChildAdapter(child);
|
|
4588
|
+
let sequence = 1;
|
|
4589
|
+
if (adapter.tailAgentToolRun) {
|
|
4590
|
+
const stream = await adapter.tailAgentToolRun(runId, { afterSequence: -1 });
|
|
4591
|
+
sequence = (await this._forwardAgentToolStream(stream, runInfo.parentToolCallId, runId, sequence, void 0)).next;
|
|
4592
|
+
}
|
|
4593
|
+
const inspection = await adapter.inspectAgentToolRun(runId);
|
|
4594
|
+
if (inspection && this._isAgentToolRowHardTerminal(inspection.status)) {
|
|
4595
|
+
const result = this._terminalResultFromInspection(runInfo.agentType, inspection);
|
|
4596
|
+
await this._deliverDetachedTerminal(runId, "finish", result, {
|
|
4597
|
+
sequence,
|
|
4598
|
+
serialize: true
|
|
4599
|
+
}, inspection.completedAt);
|
|
4600
|
+
}
|
|
4601
|
+
} catch {}
|
|
4602
|
+
}
|
|
4603
|
+
/**
|
|
4604
|
+
* Single delivery funnel for a detached terminal. Both the warm fast path and
|
|
4605
|
+
* the durable backbone route through here, with INDEPENDENT ledger slots for
|
|
4606
|
+
* `finish` (the real terminal) vs `give_up` (budget exhausted). Each slot is
|
|
4607
|
+
* delivered at-least-once via a claim + lease:
|
|
4608
|
+
*
|
|
4609
|
+
* - Concurrent double-fire is prevented by the guarded CAS claim (RETURNING
|
|
4610
|
+
* yields the row only to the winner).
|
|
4611
|
+
* - A crash after the side effect but before `*_delivered_at` is written lets
|
|
4612
|
+
* the lease expire so a later reconcile re-delivers — hence handlers must be
|
|
4613
|
+
* idempotent.
|
|
4614
|
+
* - Two slots, not one, because `interrupted` is SOFT: a give-up followed by a
|
|
4615
|
+
* real completion is legitimate, and a single shared "delivered" bit would
|
|
4616
|
+
* dedupe the child's real late result away (the #1752 production incident).
|
|
4617
|
+
*/
|
|
4618
|
+
async _deliverDetachedTerminal(runId, kind, result, options, completedAt = Date.now()) {
|
|
4619
|
+
const now = Date.now();
|
|
4620
|
+
const leaseFloor = now - DETACHED_DELIVERY_LEASE_MS;
|
|
4621
|
+
const claimQuery = kind === "finish" ? `UPDATE cf_agent_tool_runs
|
|
4622
|
+
SET finish_claimed_at = ?
|
|
4623
|
+
WHERE run_id = ?
|
|
4624
|
+
AND finish_delivered_at IS NULL
|
|
4625
|
+
AND (finish_claimed_at IS NULL OR finish_claimed_at < ?)` : `UPDATE cf_agent_tool_runs
|
|
4626
|
+
SET give_up_claimed_at = ?
|
|
4627
|
+
WHERE run_id = ?
|
|
4628
|
+
AND give_up_delivered_at IS NULL
|
|
4629
|
+
AND (give_up_claimed_at IS NULL OR give_up_claimed_at < ?)`;
|
|
4630
|
+
if (this.ctx.storage.sql.exec(claimQuery, now, runId, leaseFloor).rowsWritten === 0) return;
|
|
4631
|
+
const row = this._readAgentToolRun(runId);
|
|
4632
|
+
if (!row) return;
|
|
4633
|
+
this._updateAgentToolTerminal(runId, result, completedAt);
|
|
4634
|
+
this._broadcastAgentToolTerminal(row.parent_tool_call_id ?? void 0, options?.sequence ?? Date.now(), result);
|
|
4635
|
+
const runInfo = this._agentToolRunInfoFromRow(row, result.status, completedAt);
|
|
4636
|
+
const lifecycle = {
|
|
4637
|
+
status: result.status,
|
|
4638
|
+
...result.summary !== void 0 ? { summary: result.summary } : {},
|
|
4639
|
+
...result.error !== void 0 ? { error: result.error } : {},
|
|
4640
|
+
...result.reason !== void 0 ? { reason: result.reason } : {},
|
|
4641
|
+
...result.childStillRunning !== void 0 ? { childStillRunning: result.childStillRunning } : {}
|
|
4642
|
+
};
|
|
4643
|
+
const invoke = async () => {
|
|
4644
|
+
try {
|
|
4645
|
+
await this.onAgentToolFinish(runInfo, lifecycle);
|
|
4646
|
+
} catch (error) {
|
|
4647
|
+
await this._safeRunOnError(error);
|
|
4648
|
+
}
|
|
4649
|
+
const callbackName = row.detached_on_finish;
|
|
4650
|
+
if (callbackName) {
|
|
4651
|
+
const callback = this[callbackName];
|
|
4652
|
+
if (typeof callback === "function") try {
|
|
4653
|
+
await callback.bind(this)(runInfo, lifecycle);
|
|
4654
|
+
} catch (error) {
|
|
4655
|
+
this._emit("agent_tool:detached:delivery_failed", {
|
|
4656
|
+
runId,
|
|
4657
|
+
kind,
|
|
4658
|
+
status: result.status,
|
|
4659
|
+
callback: callbackName,
|
|
4660
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4661
|
+
});
|
|
4662
|
+
await this._safeRunOnError(error);
|
|
4663
|
+
throw error;
|
|
4664
|
+
}
|
|
4665
|
+
}
|
|
4666
|
+
};
|
|
4667
|
+
await this._runDetachedDelivery(invoke, { serialize: options?.serialize });
|
|
4668
|
+
if (kind === "finish") this.sql`
|
|
4669
|
+
UPDATE cf_agent_tool_runs
|
|
4670
|
+
SET finish_delivered_at = ${Date.now()}
|
|
4671
|
+
WHERE run_id = ${runId}
|
|
4672
|
+
`;
|
|
4673
|
+
else this.sql`
|
|
4674
|
+
UPDATE cf_agent_tool_runs
|
|
4675
|
+
SET give_up_delivered_at = ${Date.now()}
|
|
4676
|
+
WHERE run_id = ${runId}
|
|
4677
|
+
`;
|
|
4678
|
+
}
|
|
4679
|
+
async _safeRunOnError(error) {
|
|
4680
|
+
try {
|
|
4681
|
+
await this.onError(error);
|
|
4682
|
+
} catch {}
|
|
4683
|
+
}
|
|
4684
|
+
/**
|
|
4685
|
+
* Run a detached terminal delivery (the `onAgentToolFinish` + per-run
|
|
4686
|
+
* `onFinish` callbacks) in an appropriate execution context. The base `Agent`
|
|
4687
|
+
* has no turn queue, so it only establishes `agentContext` — a handler that
|
|
4688
|
+
* calls `runAgentTool` / `setState` therefore works regardless of where the
|
|
4689
|
+
* delivery fired from.
|
|
4690
|
+
*
|
|
4691
|
+
* Chat-layer subclasses (`@cloudflare/think`, `@cloudflare/ai-chat`) override
|
|
4692
|
+
* this to additionally serialize delivery against their turn queue when
|
|
4693
|
+
* `serialize` is set: a fast-path push or backbone tick can land mid-turn, and
|
|
4694
|
+
* a state-mutating `onFinish` running concurrently with an active LLM turn is a
|
|
4695
|
+
* data race. The fast path and backbone never run synchronously inside a turn
|
|
4696
|
+
* (they fire from `waitUntil` / a scheduled alarm), so enqueuing them on the
|
|
4697
|
+
* turn queue is deadlock-free. An explicit `cancelAgentTool` runs with
|
|
4698
|
+
* `serialize` unset because it may be called from inside the very turn that
|
|
4699
|
+
* triggers it, where enqueuing would self-deadlock.
|
|
4700
|
+
*/
|
|
4701
|
+
async _runDetachedDelivery(invoke, _options) {
|
|
4702
|
+
if (__DO_NOT_USE_WILL_BREAK__agentContext.getStore()?.agent) {
|
|
4703
|
+
await invoke();
|
|
4704
|
+
return;
|
|
4705
|
+
}
|
|
4706
|
+
await __DO_NOT_USE_WILL_BREAK__agentContext.run({
|
|
4707
|
+
agent: this,
|
|
4708
|
+
connection: void 0,
|
|
4709
|
+
request: void 0,
|
|
4710
|
+
email: void 0
|
|
4711
|
+
}, invoke);
|
|
4712
|
+
}
|
|
4713
|
+
/**
|
|
4714
|
+
* Arm the self-scheduling detached reconcile backbone. Existing schedules are
|
|
4715
|
+
* reused for recovery/startup calls, but a fresh detached dispatch resets the
|
|
4716
|
+
* pending cadence to the fast end so new work is noticed promptly.
|
|
4717
|
+
*/
|
|
4718
|
+
async _armDetachedBackbone(options) {
|
|
4719
|
+
const run = this._detachedBackboneArming.then(() => this._armDetachedBackboneInner(options));
|
|
4720
|
+
this._detachedBackboneArming = run.then(() => void 0, () => void 0);
|
|
4721
|
+
return run;
|
|
4722
|
+
}
|
|
4723
|
+
async _armDetachedBackboneInner(options) {
|
|
4724
|
+
const armed = (await this.listSchedules()).filter((schedule) => schedule.callback === DETACHED_RECONCILE_CALLBACK);
|
|
4725
|
+
if (armed.length > 0 && !options?.resetCadence) {
|
|
4726
|
+
for (const schedule of armed.slice(1)) await this.cancelSchedule(schedule.id);
|
|
4727
|
+
return;
|
|
4728
|
+
}
|
|
4729
|
+
for (const schedule of armed) await this.cancelSchedule(schedule.id);
|
|
4730
|
+
await this.schedule(DETACHED_BACKBONE_CADENCE_S[0], DETACHED_RECONCILE_CALLBACK, { cadenceIndex: 0 });
|
|
4731
|
+
}
|
|
4732
|
+
/**
|
|
4733
|
+
* Durable backbone for detached runs. Runs on a self-rescheduling alarm:
|
|
4734
|
+
* collects any detached run that has reached terminal but was not yet
|
|
4735
|
+
* delivered (e.g. the parent was evicted before the fast path landed), gives
|
|
4736
|
+
* up on any run past its absolute budget (tearing the child down), and
|
|
4737
|
+
* reschedules itself while any detached run remains undelivered — cancelling
|
|
4738
|
+
* itself once everything has settled (zero steady-state cost).
|
|
4739
|
+
*/
|
|
4740
|
+
async _cfDetachedReconcileTick(payload) {
|
|
4741
|
+
const rows = this.sql`
|
|
4742
|
+
SELECT run_id, parent_tool_call_id, agent_type, input_preview, status,
|
|
4743
|
+
summary, output_json, error_message, interrupted_reason,
|
|
4744
|
+
child_still_running, display_metadata, display_order,
|
|
4745
|
+
started_at, completed_at, detached, detached_on_finish,
|
|
4746
|
+
detached_notify_source, detached_max_budget_at,
|
|
4747
|
+
detached_no_progress_budget_ms, last_progress_at,
|
|
4748
|
+
detached_on_milestones,
|
|
4749
|
+
finish_claimed_at, finish_delivered_at, give_up_claimed_at,
|
|
4750
|
+
give_up_delivered_at
|
|
4751
|
+
FROM cf_agent_tool_runs
|
|
4752
|
+
WHERE detached = 1 AND finish_delivered_at IS NULL
|
|
4753
|
+
ORDER BY started_at ASC
|
|
4754
|
+
`;
|
|
4755
|
+
for (const row of rows) {
|
|
4756
|
+
const runId = row.run_id;
|
|
4757
|
+
let inspection = null;
|
|
4758
|
+
try {
|
|
4759
|
+
const child = await this._cf_resolveSubAgent(row.agent_type, runId);
|
|
4760
|
+
inspection = await this._asAgentToolChildAdapter(child).inspectAgentToolRun(runId);
|
|
4761
|
+
} catch {}
|
|
4762
|
+
if (inspection?.milestones && row.detached_on_milestones) {
|
|
4763
|
+
const milestoneRunInfo = this._agentToolRunInfoFromRow(row);
|
|
4764
|
+
for (const milestone of inspection.milestones) this._maybeDeliverDetachedMilestone(row, milestoneRunInfo, milestone);
|
|
4765
|
+
}
|
|
4766
|
+
if (inspection && this._isAgentToolRowHardTerminal(inspection.status)) {
|
|
4767
|
+
const result = this._terminalResultFromInspection(row.agent_type, inspection);
|
|
4768
|
+
await this._deliverDetachedTerminal(runId, "finish", result, {
|
|
4769
|
+
sequence: Date.now(),
|
|
4770
|
+
serialize: true
|
|
4771
|
+
}, inspection.completedAt);
|
|
4772
|
+
continue;
|
|
4773
|
+
}
|
|
4774
|
+
const now = Date.now();
|
|
4775
|
+
const budgetAt = row.detached_max_budget_at;
|
|
4776
|
+
const latestMilestone = inspection?.milestones?.length ? inspection.milestones[inspection.milestones.length - 1].at : void 0;
|
|
4777
|
+
const signalTimes = [
|
|
4778
|
+
inspection?.progress?.at,
|
|
4779
|
+
latestMilestone,
|
|
4780
|
+
row.last_progress_at
|
|
4781
|
+
].filter((t) => typeof t === "number");
|
|
4782
|
+
const lastSignalAt = signalTimes.length > 0 ? Math.max(...signalTimes) : void 0;
|
|
4783
|
+
const noProgressBudgetMs = row.detached_no_progress_budget_ms;
|
|
4784
|
+
const overAbsolute = budgetAt !== null && now >= budgetAt;
|
|
4785
|
+
const overNoProgress = typeof noProgressBudgetMs === "number" && noProgressBudgetMs > 0 && Number.isFinite(noProgressBudgetMs) && typeof lastSignalAt === "number" && now - lastSignalAt >= noProgressBudgetMs;
|
|
4786
|
+
if ((overAbsolute || overNoProgress) && row.give_up_delivered_at === null) {
|
|
4787
|
+
let childTornDown = false;
|
|
4788
|
+
try {
|
|
4789
|
+
const child = await this._cf_resolveSubAgent(row.agent_type, runId);
|
|
4790
|
+
await this._asAgentToolChildAdapter(child).cancelAgentToolRun(runId, overAbsolute ? "detached budget exceeded" : "detached run went silent past its no-progress window");
|
|
4791
|
+
childTornDown = true;
|
|
4792
|
+
} catch {}
|
|
4793
|
+
await this._deliverDetachedTerminal(runId, "give_up", {
|
|
4794
|
+
runId,
|
|
4795
|
+
agentType: row.agent_type,
|
|
4796
|
+
status: "interrupted",
|
|
4797
|
+
error: overAbsolute ? "detached run exceeded its budget before completing" : "detached run went silent past its no-progress window",
|
|
4798
|
+
reason: overAbsolute ? "budget-exceeded" : "no-progress",
|
|
4799
|
+
childStillRunning: !childTornDown
|
|
4800
|
+
}, { serialize: true });
|
|
4801
|
+
}
|
|
4802
|
+
}
|
|
4803
|
+
if (this._hasOutstandingDetachedRuns()) {
|
|
4804
|
+
const currentIndex = typeof payload?.cadenceIndex === "number" ? payload.cadenceIndex : 0;
|
|
4805
|
+
const nextIndex = Math.min(currentIndex + 1, DETACHED_BACKBONE_CADENCE_S.length - 1);
|
|
4806
|
+
await this.schedule(DETACHED_BACKBONE_CADENCE_S[nextIndex], DETACHED_RECONCILE_CALLBACK, { cadenceIndex: nextIndex });
|
|
4807
|
+
}
|
|
4808
|
+
}
|
|
4255
4809
|
hasAgentToolRun(classOrName, runId) {
|
|
4256
4810
|
const agentType = typeof classOrName === "string" ? classOrName : classOrName.name;
|
|
4257
4811
|
return (this.sql`
|
|
@@ -4311,7 +4865,10 @@ var Agent = class Agent extends Server {
|
|
|
4311
4865
|
SELECT run_id, parent_tool_call_id, agent_type, input_preview, status,
|
|
4312
4866
|
summary, output_json, error_message, interrupted_reason,
|
|
4313
4867
|
child_still_running, display_metadata, display_order,
|
|
4314
|
-
started_at, completed_at
|
|
4868
|
+
started_at, completed_at, detached, detached_on_finish,
|
|
4869
|
+
detached_notify_source, detached_max_budget_at,
|
|
4870
|
+
finish_claimed_at, finish_delivered_at, give_up_claimed_at,
|
|
4871
|
+
give_up_delivered_at
|
|
4315
4872
|
FROM cf_agent_tool_runs
|
|
4316
4873
|
WHERE run_id = ${runId}
|
|
4317
4874
|
LIMIT 1
|
|
@@ -4351,6 +4908,7 @@ var Agent = class Agent extends Server {
|
|
|
4351
4908
|
inputPreview: this._parseAgentToolJson(row.input_preview),
|
|
4352
4909
|
status,
|
|
4353
4910
|
display: this._parseAgentToolJson(row.display_metadata),
|
|
4911
|
+
...row.detached_notify_source != null ? { notifySource: row.detached_notify_source } : {},
|
|
4354
4912
|
displayOrder: row.display_order,
|
|
4355
4913
|
startedAt: row.started_at,
|
|
4356
4914
|
completedAt
|
|
@@ -4509,6 +5067,7 @@ var Agent = class Agent extends Server {
|
|
|
4509
5067
|
runId,
|
|
4510
5068
|
body: chunk.body
|
|
4511
5069
|
});
|
|
5070
|
+
this._observeForwardedProgress(runId, chunk.body);
|
|
4512
5071
|
forwardedSinceProgress = true;
|
|
4513
5072
|
armIdle();
|
|
4514
5073
|
};
|
|
@@ -4598,6 +5157,76 @@ var Agent = class Agent extends Server {
|
|
|
4598
5157
|
* called repeatedly while a child streams.
|
|
4599
5158
|
*/
|
|
4600
5159
|
async _onAgentToolStreamProgress() {}
|
|
5160
|
+
/**
|
|
5161
|
+
* Best-effort observation of a forwarded child chunk: if it is a reserved
|
|
5162
|
+
* `data-agent-progress` frame, refresh the cached liveness timestamp on the
|
|
5163
|
+
* run row (a hint for a still-warm parent) and fire the public `onProgress`
|
|
5164
|
+
* hook. Never throws into the forward loop — the child's own persisted
|
|
5165
|
+
* snapshot (read via `inspectAgentToolRun`) remains authoritative for the
|
|
5166
|
+
* resetting no-progress budget after eviction.
|
|
5167
|
+
*/
|
|
5168
|
+
_observeForwardedProgress(runId, body) {
|
|
5169
|
+
let parsed;
|
|
5170
|
+
try {
|
|
5171
|
+
parsed = JSON.parse(body);
|
|
5172
|
+
} catch {
|
|
5173
|
+
return;
|
|
5174
|
+
}
|
|
5175
|
+
if (!parsed) return;
|
|
5176
|
+
const isMilestone = parsed.type === AGENT_TOOL_MILESTONE_PART;
|
|
5177
|
+
if (parsed.type !== "data-agent-progress" && !isMilestone) return;
|
|
5178
|
+
const data = parsed.data ?? {};
|
|
5179
|
+
const at = Date.now();
|
|
5180
|
+
const snapshot = {
|
|
5181
|
+
...typeof data.fraction === "number" ? { fraction: data.fraction } : {},
|
|
5182
|
+
...typeof data.message === "string" ? { message: data.message } : {},
|
|
5183
|
+
...typeof data.phase === "string" ? { phase: data.phase } : {},
|
|
5184
|
+
...isMilestone && typeof data.name === "string" ? { milestone: data.name } : {},
|
|
5185
|
+
...data.data !== void 0 ? { data: data.data } : {},
|
|
5186
|
+
at
|
|
5187
|
+
};
|
|
5188
|
+
const row = this._readAgentToolRun(runId);
|
|
5189
|
+
if (!row) return;
|
|
5190
|
+
if (row.detached) try {
|
|
5191
|
+
this.sql`
|
|
5192
|
+
UPDATE cf_agent_tool_runs SET last_progress_at = ${at}
|
|
5193
|
+
WHERE run_id = ${runId}
|
|
5194
|
+
`;
|
|
5195
|
+
} catch {}
|
|
5196
|
+
const runInfo = this._agentToolRunInfoFromRow(row);
|
|
5197
|
+
Promise.resolve(this.onProgress(runInfo, snapshot)).catch((error) => {
|
|
5198
|
+
console.error(`[agents] onProgress hook threw for run ${runId}:`, error instanceof Error ? error.message : String(error));
|
|
5199
|
+
});
|
|
5200
|
+
if (isMilestone && typeof data.name === "string") this._maybeDeliverDetachedMilestone(row, runInfo, {
|
|
5201
|
+
name: data.name,
|
|
5202
|
+
sequence: typeof data.sequence === "number" ? data.sequence : 0,
|
|
5203
|
+
at: typeof data.at === "number" ? data.at : at,
|
|
5204
|
+
...data.data !== void 0 ? { data: data.data } : {}
|
|
5205
|
+
});
|
|
5206
|
+
}
|
|
5207
|
+
/**
|
|
5208
|
+
* Deliver a milestone notification IF this run opted into it via
|
|
5209
|
+
* `detached: { onMilestones }` and the milestone name is in that set. Routes
|
|
5210
|
+
* to the overridable `_deliverDetachedMilestone` seam (a no-op on the base
|
|
5211
|
+
* `Agent`; chat hosts inject an idempotent synthetic chat message).
|
|
5212
|
+
*/
|
|
5213
|
+
_maybeDeliverDetachedMilestone(row, runInfo, milestone) {
|
|
5214
|
+
const configured = this._parseAgentToolJson(row.detached_on_milestones ?? null);
|
|
5215
|
+
const names = Array.isArray(configured) ? configured : configured?.names;
|
|
5216
|
+
const mode = Array.isArray(configured) ? "narrate" : configured?.mode ?? "narrate";
|
|
5217
|
+
if (!Array.isArray(names) || !names.includes(milestone.name)) return;
|
|
5218
|
+
Promise.resolve(this._deliverDetachedMilestone(runInfo, milestone, mode)).catch((error) => {
|
|
5219
|
+
console.error(`[agents] detached milestone delivery threw for run ${runInfo.runId} (${milestone.name}):`, error instanceof Error ? error.message : String(error));
|
|
5220
|
+
});
|
|
5221
|
+
}
|
|
5222
|
+
/**
|
|
5223
|
+
* Overridable seam for the `detached: { onMilestones }` convenience. The base
|
|
5224
|
+
* `Agent` has no chat surface, so this is a no-op; chat hosts
|
|
5225
|
+
* (`@cloudflare/think`, `AIChatAgent`) override it to submit an idempotent
|
|
5226
|
+
* synthetic message keyed on `(runId, milestone.name)`. Called from both the
|
|
5227
|
+
* warm tail and the backbone reconcile, so it MUST be idempotent.
|
|
5228
|
+
*/
|
|
5229
|
+
async _deliverDetachedMilestone(_run, _milestone, _mode) {}
|
|
4601
5230
|
_broadcastAgentToolTerminal(parentToolCallId, sequence, result, replay, connection) {
|
|
4602
5231
|
if (result.status === "completed") this._broadcastAgentToolEvent(parentToolCallId, sequence, {
|
|
4603
5232
|
kind: "finished",
|
|
@@ -4828,7 +5457,7 @@ var Agent = class Agent extends Server {
|
|
|
4828
5457
|
child_still_running, display_metadata, display_order,
|
|
4829
5458
|
started_at, completed_at
|
|
4830
5459
|
FROM cf_agent_tool_runs
|
|
4831
|
-
WHERE status IN ('starting', 'running')
|
|
5460
|
+
WHERE status IN ('starting', 'running') AND detached = 0
|
|
4832
5461
|
ORDER BY started_at ASC
|
|
4833
5462
|
`;
|
|
4834
5463
|
const runIds = options?.runIds !== void 0 ? new Set(options.runIds) : void 0;
|
|
@@ -4967,6 +5596,7 @@ var Agent = class Agent extends Server {
|
|
|
4967
5596
|
runIds: options?.runIds
|
|
4968
5597
|
});
|
|
4969
5598
|
await this._runDeferredAgentToolFinishHooks(recoveredAgentToolFinishes);
|
|
5599
|
+
if (this._hasOutstandingDetachedRuns()) await this._armDetachedBackbone();
|
|
4970
5600
|
})().catch(async (error) => {
|
|
4971
5601
|
this._emit("agent_tool:recovery:failed", { error: error instanceof Error ? error.message : String(error) });
|
|
4972
5602
|
try {
|
|
@@ -5305,10 +5935,29 @@ var Agent = class Agent extends Server {
|
|
|
5305
5935
|
* Start a workflow and track it in this Agent's database.
|
|
5306
5936
|
* Automatically injects agent identity into the workflow params.
|
|
5307
5937
|
*
|
|
5938
|
+
* The originating Agent identity is persisted in the workflow params so
|
|
5939
|
+
* callbacks (`this.agent` RPC, progress/completion/error, state updates)
|
|
5940
|
+
* route back to the exact Agent or sub-agent facet that started the run.
|
|
5941
|
+
* Note the following constraints:
|
|
5942
|
+
*
|
|
5943
|
+
* - **Resolution is by name.** Callbacks re-resolve the originating Agent via
|
|
5944
|
+
* `getAgentByName(...)`. Agents addressed by a raw Durable Object id
|
|
5945
|
+
* (`idFromString`/`get(id)`) rather than by name will not receive
|
|
5946
|
+
* callbacks on the same instance.
|
|
5947
|
+
* - **Sub-agent runs are facet-local.** A workflow started from a sub-agent
|
|
5948
|
+
* is tracked in that facet's own storage; the parent's `getWorkflows()` /
|
|
5949
|
+
* `getWorkflowById()` do not see it. Aggregate across facets yourself if
|
|
5950
|
+
* you need a combined view.
|
|
5951
|
+
* - **Class names must survive bundling.** The originating path is keyed by
|
|
5952
|
+
* `constructor.name`. Ensure your bundler preserves class names
|
|
5953
|
+
* (e.g. esbuild `keepNames: true`) so callbacks can be routed.
|
|
5954
|
+
*
|
|
5308
5955
|
* @template P - Type of params to pass to the workflow
|
|
5309
5956
|
* @param workflowName - Name of the workflow binding in env (e.g., 'MY_WORKFLOW')
|
|
5310
5957
|
* @param params - Params to pass to the workflow
|
|
5311
|
-
* @param options - Optional workflow options
|
|
5958
|
+
* @param options - Optional workflow options. For sub-agents, pass
|
|
5959
|
+
* `agentBinding` as the **root** Agent's Durable Object binding name, not a
|
|
5960
|
+
* child binding.
|
|
5312
5961
|
* @returns The workflow instance ID
|
|
5313
5962
|
*
|
|
5314
5963
|
* @example
|
|
@@ -5322,14 +5971,15 @@ var Agent = class Agent extends Server {
|
|
|
5322
5971
|
async runWorkflow(workflowName, params, options) {
|
|
5323
5972
|
const workflow = this._findWorkflowBindingByName(workflowName);
|
|
5324
5973
|
if (!workflow) throw new Error(`Workflow binding '${workflowName}' not found in environment`);
|
|
5325
|
-
const
|
|
5326
|
-
if (!
|
|
5974
|
+
const agentOrigin = this._workflowOrigin(options);
|
|
5975
|
+
if (!agentOrigin) throw new Error("Could not detect Agent binding name from class name. Pass it explicitly via options.agentBinding");
|
|
5327
5976
|
const workflowId = options?.id ?? `wf_${nanoid()}`;
|
|
5328
5977
|
const augmentedParams = {
|
|
5329
5978
|
...params,
|
|
5330
5979
|
__agentName: this.name,
|
|
5331
|
-
__agentBinding:
|
|
5332
|
-
__workflowName: workflowName
|
|
5980
|
+
__agentBinding: agentOrigin.kind === "agent" ? agentOrigin.binding : agentOrigin.rootBinding,
|
|
5981
|
+
__workflowName: workflowName,
|
|
5982
|
+
__agentOrigin: agentOrigin
|
|
5333
5983
|
};
|
|
5334
5984
|
const instance = await workflow.create({
|
|
5335
5985
|
id: workflowId,
|
|
@@ -5871,12 +6521,28 @@ var Agent = class Agent extends Server {
|
|
|
5871
6521
|
completedAt: row.completed_at ? /* @__PURE__ */ new Date(row.completed_at * 1e3) : null
|
|
5872
6522
|
};
|
|
5873
6523
|
}
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
6524
|
+
_workflowOrigin(options) {
|
|
6525
|
+
if (this._isFacet) {
|
|
6526
|
+
const root = this._parentPath[0];
|
|
6527
|
+
const rootBindingName = options?.agentBinding ?? (root ? this._findAgentBindingNameForClass(root.className) : void 0);
|
|
6528
|
+
if (!rootBindingName) return void 0;
|
|
6529
|
+
return {
|
|
6530
|
+
kind: "facet",
|
|
6531
|
+
version: 1,
|
|
6532
|
+
rootBinding: rootBindingName,
|
|
6533
|
+
path: this.selfPath.map((step) => ({ ...step }))
|
|
6534
|
+
};
|
|
6535
|
+
}
|
|
6536
|
+
const agentBindingName = options?.agentBinding ?? this._findAgentBindingNameForClass(this._ParentClass.name);
|
|
6537
|
+
if (!agentBindingName) return void 0;
|
|
6538
|
+
return {
|
|
6539
|
+
kind: "agent",
|
|
6540
|
+
version: 1,
|
|
6541
|
+
binding: agentBindingName,
|
|
6542
|
+
name: this.name
|
|
6543
|
+
};
|
|
6544
|
+
}
|
|
6545
|
+
_findAgentBindingNameForClass(className) {
|
|
5880
6546
|
for (const [key, value] of Object.entries(this.env)) if (value && typeof value === "object" && "idFromName" in value && typeof value.idFromName === "function") {
|
|
5881
6547
|
if (key === className || camelCaseToKebabCase(key) === camelCaseToKebabCase(className)) return key;
|
|
5882
6548
|
}
|
|
@@ -5914,7 +6580,9 @@ var Agent = class Agent extends Server {
|
|
|
5914
6580
|
}
|
|
5915
6581
|
/**
|
|
5916
6582
|
* Handle a callback from a workflow.
|
|
5917
|
-
*
|
|
6583
|
+
* Invoked via the internal `_workflow_handleCallback` RPC whenever an
|
|
6584
|
+
* {@link AgentWorkflow} reports progress, completion, an error, or a custom
|
|
6585
|
+
* event back to its originating Agent (or sub-agent facet).
|
|
5918
6586
|
* Override this to handle all callback types in one place.
|
|
5919
6587
|
*
|
|
5920
6588
|
* @param callback - The callback payload
|
|
@@ -6270,6 +6938,7 @@ var Agent = class Agent extends Server {
|
|
|
6270
6938
|
}
|
|
6271
6939
|
};
|
|
6272
6940
|
Agent.options = { hibernate: true };
|
|
6941
|
+
Agent._CF_OOM_ALARM_STRIKES_KEY = "cf_agents:oom_alarm_strikes";
|
|
6273
6942
|
const wrappedClasses = /* @__PURE__ */ new Set();
|
|
6274
6943
|
/**
|
|
6275
6944
|
* Route a request to the appropriate Agent
|
|
@@ -6448,6 +7117,6 @@ var StreamingResponse = class {
|
|
|
6448
7117
|
}
|
|
6449
7118
|
};
|
|
6450
7119
|
//#endregion
|
|
6451
|
-
export { Agent, DEFAULT_AGENT_STATIC_OPTIONS, DurableObjectOAuthClientProvider, MCP_SERVER_ID_MAX_LENGTH, MessageType, SUB_PREFIX, SqlError, StreamingResponse, __DO_NOT_USE_WILL_BREAK__agentContext, callable, camelCaseToKebabCase, createHeaderBasedEmailResolver, getAgentByName, getCurrentAgent, getSubAgentByName, isDurableObjectCodeUpdateReset, isPlatformTransientError, normalizeServerId, parseSubAgentPath, routeAgentEmail, routeAgentRequest, routeSubAgentRequest, unstable_callable };
|
|
7120
|
+
export { AGENT_TOOL_MILESTONE_PART, AGENT_TOOL_PROGRESS_PART, Agent, DEFAULT_AGENT_STATIC_OPTIONS, DurableObjectOAuthClientProvider, MCP_SERVER_ID_MAX_LENGTH, MessageType, SUB_PREFIX, SqlError, StreamingResponse, __DO_NOT_USE_WILL_BREAK__agentContext, callable, camelCaseToKebabCase, createHeaderBasedEmailResolver, getAgentByName, getCurrentAgent, getSubAgentByName, isDurableObjectCodeUpdateReset, isDurableObjectMemoryLimitReset, isPlatformTransientError, normalizeServerId, parseSubAgentPath, routeAgentEmail, routeAgentRequest, routeSubAgentRequest, unstable_callable };
|
|
6452
7121
|
|
|
6453
7122
|
//# sourceMappingURL=index.js.map
|