agents 0.16.2 → 0.17.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 +11 -8
- package/dist/{agent-tool-types-CTw3UJUP.d.ts → agent-tool-types-Cd1TZPfB.d.ts} +587 -137
- 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-_E8wxUIK.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 +1918 -72
- package/dist/chat/index.js +1730 -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-CcbnKkNh.d.ts} +188 -14
- package/dist/index.d.ts +91 -71
- package/dist/index.js +562 -24
- 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/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 +1687 -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
8
|
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-
|
|
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,9 @@ 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
|
|
316
331
|
};
|
|
317
332
|
/**
|
|
318
333
|
* Parse the raw `retry_options` TEXT column from a SQLite row into a
|
|
@@ -437,7 +452,9 @@ var Agent = class Agent extends Server {
|
|
|
437
452
|
fiberRecoveryScanDeadlineMs: ctor.options?.fiberRecoveryScanDeadlineMs ?? DEFAULT_AGENT_STATIC_OPTIONS.fiberRecoveryScanDeadlineMs,
|
|
438
453
|
fiberRecoveryMaxAgeMs: ctor.options?.fiberRecoveryMaxAgeMs ?? DEFAULT_AGENT_STATIC_OPTIONS.fiberRecoveryMaxAgeMs,
|
|
439
454
|
agentToolReattachNoProgressTimeoutMs: ctor.options?.agentToolReattachNoProgressTimeoutMs ?? DEFAULT_AGENT_STATIC_OPTIONS.agentToolReattachNoProgressTimeoutMs,
|
|
440
|
-
agentToolReattachMaxWindowMs: ctor.options?.agentToolReattachMaxWindowMs ?? DEFAULT_AGENT_STATIC_OPTIONS.agentToolReattachMaxWindowMs
|
|
455
|
+
agentToolReattachMaxWindowMs: ctor.options?.agentToolReattachMaxWindowMs ?? DEFAULT_AGENT_STATIC_OPTIONS.agentToolReattachMaxWindowMs,
|
|
456
|
+
detachedMaxBudgetMs: ctor.options?.detachedMaxBudgetMs ?? DEFAULT_AGENT_STATIC_OPTIONS.detachedMaxBudgetMs,
|
|
457
|
+
detachedNoProgressBudgetMs: ctor.options?.detachedNoProgressBudgetMs ?? DEFAULT_AGENT_STATIC_OPTIONS.detachedNoProgressBudgetMs
|
|
441
458
|
};
|
|
442
459
|
return this._cachedOptions;
|
|
443
460
|
}
|
|
@@ -679,6 +696,17 @@ var Agent = class Agent extends Server {
|
|
|
679
696
|
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN output_json TEXT");
|
|
680
697
|
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN interrupted_reason TEXT");
|
|
681
698
|
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN child_still_running INTEGER");
|
|
699
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached INTEGER NOT NULL DEFAULT 0");
|
|
700
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_on_finish TEXT");
|
|
701
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_notify_source TEXT");
|
|
702
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_max_budget_at INTEGER");
|
|
703
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN finish_claimed_at INTEGER");
|
|
704
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN finish_delivered_at INTEGER");
|
|
705
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN give_up_claimed_at INTEGER");
|
|
706
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN give_up_delivered_at INTEGER");
|
|
707
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_no_progress_budget_ms INTEGER");
|
|
708
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN last_progress_at INTEGER");
|
|
709
|
+
addColumnIfNotExists("ALTER TABLE cf_agent_tool_runs ADD COLUMN detached_on_milestones TEXT");
|
|
682
710
|
this.sql`
|
|
683
711
|
INSERT OR REPLACE INTO cf_agents_state (id, state)
|
|
684
712
|
VALUES (${SCHEMA_VERSION_ROW_ID}, ${String(CURRENT_SCHEMA_VERSION)})
|
|
@@ -707,6 +735,8 @@ var Agent = class Agent extends Server {
|
|
|
707
735
|
this._managedFiberTerminalWaiters = /* @__PURE__ */ new Map();
|
|
708
736
|
this._runFiberRecoveryInProgress = false;
|
|
709
737
|
this._recoveryNoProgressScans = 0;
|
|
738
|
+
this._detachedBackboneArming = Promise.resolve();
|
|
739
|
+
this._detachedLiveCountWarned = false;
|
|
710
740
|
this._ParentClass = Object.getPrototypeOf(this).constructor;
|
|
711
741
|
this.initialState = DEFAULT_STATE;
|
|
712
742
|
this.observability = genericObservability;
|
|
@@ -2641,7 +2671,7 @@ var Agent = class Agent extends Server {
|
|
|
2641
2671
|
return true;
|
|
2642
2672
|
}
|
|
2643
2673
|
async deleteFibers(options) {
|
|
2644
|
-
const terminalStatuses = [...this._normalizeFiberStatusFilter(options?.status) ?? new Set([
|
|
2674
|
+
const terminalStatuses = [...this._normalizeFiberStatusFilter(options?.status) ?? /* @__PURE__ */ new Set([
|
|
2645
2675
|
"completed",
|
|
2646
2676
|
"aborted",
|
|
2647
2677
|
"error"
|
|
@@ -3138,6 +3168,25 @@ var Agent = class Agent extends Server {
|
|
|
3138
3168
|
return (await this._cf_resolveSubAgent(next.className, next.name))._cf_dispatchScheduledCallback(ownerPath, row);
|
|
3139
3169
|
}
|
|
3140
3170
|
/**
|
|
3171
|
+
* Invoke an RPC method on this Agent or a descendant facet identified
|
|
3172
|
+
* by a root-first path. Used by AgentWorkflow to route callbacks and
|
|
3173
|
+
* `this.agent` calls back to the exact sub-agent that started a workflow.
|
|
3174
|
+
* @internal
|
|
3175
|
+
*/
|
|
3176
|
+
async _cf_invokeAgentPath(targetPath, method, args) {
|
|
3177
|
+
await this.__unsafe_ensureInitialized();
|
|
3178
|
+
const selfPath = this.selfPath;
|
|
3179
|
+
if (!this._isSameAgentPathPrefix(selfPath, targetPath)) throw new Error(`Workflow origin path does not descend from ${JSON.stringify(selfPath)}.`);
|
|
3180
|
+
if (selfPath.length === targetPath.length) {
|
|
3181
|
+
const fn = this[method];
|
|
3182
|
+
if (isInternalJsStubProp(method) || method in Object.prototype || typeof fn !== "function") throw new Error(`Workflow origin method "${method}" is not callable on ${this.constructor.name}.`);
|
|
3183
|
+
return await fn.apply(this, args);
|
|
3184
|
+
}
|
|
3185
|
+
const next = targetPath[selfPath.length];
|
|
3186
|
+
if (!this.hasSubAgent(next.className, next.name)) throw new Error(`Workflow origin sub-agent ${next.className} "${next.name}" no longer exists.`);
|
|
3187
|
+
return await (await this._cf_resolveSubAgent(next.className, next.name))._cf_invokeAgentPath(targetPath, method, args);
|
|
3188
|
+
}
|
|
3189
|
+
/**
|
|
3141
3190
|
* Recursively destroy a descendant facet identified by
|
|
3142
3191
|
* `targetPath`. Walks down from `selfPath` until reaching the
|
|
3143
3192
|
* target's immediate parent, where it cancels the target's
|
|
@@ -4075,11 +4124,40 @@ var Agent = class Agent extends Server {
|
|
|
4075
4124
|
}
|
|
4076
4125
|
async onAgentToolStart(_run) {}
|
|
4077
4126
|
async onAgentToolFinish(_run, _result) {}
|
|
4127
|
+
/**
|
|
4128
|
+
* Parent hook fired (best-effort) whenever a child agent-tool run emits a
|
|
4129
|
+
* `reportProgress` signal that is forwarded through this parent's tail. Use it
|
|
4130
|
+
* to meter / steer / surface progress server-side. Fires for both awaited and
|
|
4131
|
+
* detached runs; it is NOT durable — after eviction a detached run's latest
|
|
4132
|
+
* snapshot is read from `inspectAgentToolRun().progress` on reconcile instead.
|
|
4133
|
+
*/
|
|
4134
|
+
async onProgress(_run, _progress) {}
|
|
4135
|
+
/**
|
|
4136
|
+
* Emit an ephemeral progress signal from a sub-agent that is currently running
|
|
4137
|
+
* as an agent tool. Rides the child's active turn stream as a transient
|
|
4138
|
+
* `data-agent-progress` part (re-broadcast to the parent's clients + surfaced
|
|
4139
|
+
* in `useAgentToolEvents`) and persists a latest-wins snapshot for recovery /
|
|
4140
|
+
* inspection. A no-op (with a dev warning) on the base `Agent`, which has no
|
|
4141
|
+
* streaming turn — overridden by chat hosts (`@cloudflare/think`,
|
|
4142
|
+
* `AIChatAgent`). See `design/rfc-detached-agent-tools.md`.
|
|
4143
|
+
*/
|
|
4144
|
+
async reportProgress(_progress, _options) {
|
|
4145
|
+
console.warn("[agents] reportProgress() is only supported on chat agents (@cloudflare/think, AIChatAgent) running as an agent tool; ignoring on base Agent.");
|
|
4146
|
+
}
|
|
4078
4147
|
async runAgentTool(cls, options) {
|
|
4079
4148
|
const runId = options.runId ?? nanoid(12);
|
|
4080
4149
|
const agentType = cls.name;
|
|
4150
|
+
const detached = this._parseDetachedOption(options.detached);
|
|
4081
4151
|
const existing = this._readAgentToolRun(runId);
|
|
4082
4152
|
if (existing) {
|
|
4153
|
+
if (detached) {
|
|
4154
|
+
if (!this._isAgentToolRowHardTerminal(existing.status)) await this._armDetachedBackbone();
|
|
4155
|
+
return {
|
|
4156
|
+
runId,
|
|
4157
|
+
agentType,
|
|
4158
|
+
status: "running"
|
|
4159
|
+
};
|
|
4160
|
+
}
|
|
4083
4161
|
if (existing.status === "completed" || existing.status === "error" || existing.status === "aborted") {
|
|
4084
4162
|
if (existing.status === "completed" && existing.output_json == null) try {
|
|
4085
4163
|
const child = await this.subAgent(cls, runId);
|
|
@@ -4151,14 +4229,22 @@ var Agent = class Agent extends Server {
|
|
|
4151
4229
|
error
|
|
4152
4230
|
};
|
|
4153
4231
|
}
|
|
4232
|
+
const detachedMaxBudgetAt = detached ? startedAt + (detached.maxBudgetMs ?? this._resolvedOptions.detachedMaxBudgetMs) : null;
|
|
4233
|
+
const detachedNoProgressBudgetMs = detached ? detached.noProgressBudgetMs ?? this._resolvedOptions.detachedNoProgressBudgetMs : null;
|
|
4234
|
+
const detachedOnMilestonesJson = detached?.onMilestones ? JSON.stringify(detached.onMilestones) : null;
|
|
4154
4235
|
this.sql`
|
|
4155
4236
|
INSERT INTO cf_agent_tool_runs (
|
|
4156
4237
|
run_id, parent_tool_call_id, agent_type, input_preview,
|
|
4157
|
-
input_redacted, status, display_metadata, display_order, started_at
|
|
4238
|
+
input_redacted, status, display_metadata, display_order, started_at,
|
|
4239
|
+
detached, detached_on_finish, detached_notify_source,
|
|
4240
|
+
detached_max_budget_at, detached_no_progress_budget_ms,
|
|
4241
|
+
detached_on_milestones
|
|
4158
4242
|
) VALUES (
|
|
4159
4243
|
${runId}, ${options.parentToolCallId ?? null}, ${agentType},
|
|
4160
4244
|
${inputPreviewJson}, 1, 'starting', ${displayJson}, ${displayOrder},
|
|
4161
|
-
${startedAt}
|
|
4245
|
+
${startedAt}, ${detached ? 1 : 0}, ${detached?.onFinishName ?? null},
|
|
4246
|
+
${detached?.notifySource ?? null}, ${detachedMaxBudgetAt},
|
|
4247
|
+
${detachedNoProgressBudgetMs}, ${detachedOnMilestonesJson}
|
|
4162
4248
|
)
|
|
4163
4249
|
`;
|
|
4164
4250
|
const runInfo = {
|
|
@@ -4168,6 +4254,7 @@ var Agent = class Agent extends Server {
|
|
|
4168
4254
|
inputPreview,
|
|
4169
4255
|
status: "starting",
|
|
4170
4256
|
display: options.display,
|
|
4257
|
+
...detached?.notifySource !== void 0 ? { notifySource: detached.notifySource } : {},
|
|
4171
4258
|
displayOrder,
|
|
4172
4259
|
startedAt
|
|
4173
4260
|
};
|
|
@@ -4184,6 +4271,17 @@ var Agent = class Agent extends Server {
|
|
|
4184
4271
|
const adapter = this._asAgentToolChildAdapter(child);
|
|
4185
4272
|
const childStart = await adapter.startAgentToolRun(options.input, { runId });
|
|
4186
4273
|
this._markAgentToolRunning(runId);
|
|
4274
|
+
if (detached) {
|
|
4275
|
+
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.`);
|
|
4276
|
+
await this._armDetachedBackbone({ resetCadence: true });
|
|
4277
|
+
this._maybeWarnDetachedLiveCount();
|
|
4278
|
+
this.ctx.waitUntil(this._detachedFastPath(runInfo, cls, runId));
|
|
4279
|
+
return {
|
|
4280
|
+
runId,
|
|
4281
|
+
agentType,
|
|
4282
|
+
status: "running"
|
|
4283
|
+
};
|
|
4284
|
+
}
|
|
4187
4285
|
let sequence = 1;
|
|
4188
4286
|
let parentAbortListener;
|
|
4189
4287
|
if (options.signal) if (options.signal.aborted) {
|
|
@@ -4252,6 +4350,332 @@ var Agent = class Agent extends Server {
|
|
|
4252
4350
|
if (parentAbortListener && options.signal) options.signal.removeEventListener("abort", parentAbortListener);
|
|
4253
4351
|
}
|
|
4254
4352
|
}
|
|
4353
|
+
/**
|
|
4354
|
+
* Cancel an agent-tool run by id. Idempotent: cancelling an already-terminal
|
|
4355
|
+
* run is a no-op. Detached runs deliver through the guarded ledger so a wired
|
|
4356
|
+
* `onFinish` fires once with `status: "aborted"`; awaited runs leave terminal
|
|
4357
|
+
* observation to the awaiting/recovery path, avoiding duplicate finish hooks.
|
|
4358
|
+
*/
|
|
4359
|
+
async cancelAgentTool(runId, reason) {
|
|
4360
|
+
const row = this._readAgentToolRun(runId);
|
|
4361
|
+
if (!row) return;
|
|
4362
|
+
if (this._isAgentToolRowHardTerminal(row.status)) return;
|
|
4363
|
+
const isDetached = row.detached === 1;
|
|
4364
|
+
const message = reason instanceof Error ? reason.message : String(reason ?? "cancelled by parent");
|
|
4365
|
+
try {
|
|
4366
|
+
const child = await this._cf_resolveSubAgent(row.agent_type, runId);
|
|
4367
|
+
await this._asAgentToolChildAdapter(child).cancelAgentToolRun(runId, reason);
|
|
4368
|
+
} catch {}
|
|
4369
|
+
if (!isDetached) return;
|
|
4370
|
+
await this._deliverDetachedTerminal(runId, "finish", {
|
|
4371
|
+
runId,
|
|
4372
|
+
agentType: row.agent_type,
|
|
4373
|
+
status: "aborted",
|
|
4374
|
+
error: message
|
|
4375
|
+
});
|
|
4376
|
+
}
|
|
4377
|
+
/**
|
|
4378
|
+
* Parse + validate the `detached` option. Returns `null` for a non-detached
|
|
4379
|
+
* run, or the normalized config (with the validated `onFinish` method name)
|
|
4380
|
+
* for a detached one. Throws if `onFinish` does not name a method on this
|
|
4381
|
+
* agent — closures cannot survive Durable Object eviction, so the durable
|
|
4382
|
+
* hook is referenced by method name (the same contract as `schedule`).
|
|
4383
|
+
*/
|
|
4384
|
+
_parseDetachedOption(detached) {
|
|
4385
|
+
if (!detached) return null;
|
|
4386
|
+
if (detached === true) return {};
|
|
4387
|
+
let onFinishName = detached.onFinish;
|
|
4388
|
+
const notifySource = typeof detached.notify === "object" ? detached.notify.source : void 0;
|
|
4389
|
+
if (onFinishName !== void 0) {
|
|
4390
|
+
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.`);
|
|
4391
|
+
} else if (detached.notify) {
|
|
4392
|
+
if (typeof this[DETACHED_NOTIFY_CALLBACK] === "function") onFinishName = DETACHED_NOTIFY_CALLBACK;
|
|
4393
|
+
}
|
|
4394
|
+
return {
|
|
4395
|
+
...onFinishName !== void 0 ? { onFinishName } : {},
|
|
4396
|
+
...notifySource !== void 0 ? { notifySource } : {},
|
|
4397
|
+
...detached.maxBudgetMs !== void 0 ? { maxBudgetMs: detached.maxBudgetMs } : {},
|
|
4398
|
+
...detached.noProgressBudgetMs !== void 0 ? { noProgressBudgetMs: detached.noProgressBudgetMs } : {},
|
|
4399
|
+
...(() => {
|
|
4400
|
+
const raw = detached.onMilestones;
|
|
4401
|
+
if (!raw) return {};
|
|
4402
|
+
const names = Array.isArray(raw) ? raw : raw.names;
|
|
4403
|
+
if (!Array.isArray(names) || names.length === 0) return {};
|
|
4404
|
+
return { onMilestones: {
|
|
4405
|
+
names,
|
|
4406
|
+
mode: Array.isArray(raw) ? "narrate" : raw.mode ?? "narrate"
|
|
4407
|
+
} };
|
|
4408
|
+
})()
|
|
4409
|
+
};
|
|
4410
|
+
}
|
|
4411
|
+
_isAgentToolRowHardTerminal(status) {
|
|
4412
|
+
return status === "completed" || status === "error" || status === "aborted";
|
|
4413
|
+
}
|
|
4414
|
+
_hasOutstandingDetachedRuns() {
|
|
4415
|
+
return (this.sql`
|
|
4416
|
+
SELECT COUNT(*) AS n FROM cf_agent_tool_runs
|
|
4417
|
+
WHERE detached = 1 AND finish_delivered_at IS NULL
|
|
4418
|
+
`[0]?.n ?? 0) > 0;
|
|
4419
|
+
}
|
|
4420
|
+
/** Detached runs still holding a concurrency slot (non-terminal). */
|
|
4421
|
+
_liveDetachedRunCount() {
|
|
4422
|
+
return this.sql`
|
|
4423
|
+
SELECT COUNT(*) AS n FROM cf_agent_tool_runs
|
|
4424
|
+
WHERE detached = 1 AND status IN ('starting', 'running')
|
|
4425
|
+
`[0]?.n ?? 0;
|
|
4426
|
+
}
|
|
4427
|
+
/**
|
|
4428
|
+
* Edge-triggered warning when live detached runs cross
|
|
4429
|
+
* `DETACHED_LIVE_COUNT_WARN_THRESHOLD`. Fires once on the up-crossing and
|
|
4430
|
+
* re-arms only after the count falls back below the threshold, so a parent
|
|
4431
|
+
* accumulating long-lived background runs surfaces a signal without spamming.
|
|
4432
|
+
*/
|
|
4433
|
+
_maybeWarnDetachedLiveCount() {
|
|
4434
|
+
const liveCount = this._liveDetachedRunCount();
|
|
4435
|
+
if (liveCount < DETACHED_LIVE_COUNT_WARN_THRESHOLD) {
|
|
4436
|
+
this._detachedLiveCountWarned = false;
|
|
4437
|
+
return;
|
|
4438
|
+
}
|
|
4439
|
+
if (this._detachedLiveCountWarned) return;
|
|
4440
|
+
this._detachedLiveCountWarned = true;
|
|
4441
|
+
this._emit("agent_tool:detached:live_count_warning", {
|
|
4442
|
+
liveCount,
|
|
4443
|
+
threshold: DETACHED_LIVE_COUNT_WARN_THRESHOLD
|
|
4444
|
+
});
|
|
4445
|
+
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\`.`);
|
|
4446
|
+
}
|
|
4447
|
+
/**
|
|
4448
|
+
* Warm fast path for a detached run: tail the child to terminal (so the
|
|
4449
|
+
* parent re-broadcasts its live stream to clients) and deliver the completion
|
|
4450
|
+
* with low latency while the isolate stays alive. Best-effort — the durable
|
|
4451
|
+
* `_cfDetachedReconcileTick` backbone is the guarantee; anything this misses
|
|
4452
|
+
* (eviction, a child that has not yet reached terminal) the backbone collects.
|
|
4453
|
+
*/
|
|
4454
|
+
async _detachedFastPath(runInfo, cls, runId) {
|
|
4455
|
+
try {
|
|
4456
|
+
const child = await this.subAgent(cls, runId);
|
|
4457
|
+
const adapter = this._asAgentToolChildAdapter(child);
|
|
4458
|
+
let sequence = 1;
|
|
4459
|
+
if (adapter.tailAgentToolRun) {
|
|
4460
|
+
const stream = await adapter.tailAgentToolRun(runId, { afterSequence: -1 });
|
|
4461
|
+
sequence = (await this._forwardAgentToolStream(stream, runInfo.parentToolCallId, runId, sequence, void 0)).next;
|
|
4462
|
+
}
|
|
4463
|
+
const inspection = await adapter.inspectAgentToolRun(runId);
|
|
4464
|
+
if (inspection && this._isAgentToolRowHardTerminal(inspection.status)) {
|
|
4465
|
+
const result = this._terminalResultFromInspection(runInfo.agentType, inspection);
|
|
4466
|
+
await this._deliverDetachedTerminal(runId, "finish", result, {
|
|
4467
|
+
sequence,
|
|
4468
|
+
serialize: true
|
|
4469
|
+
}, inspection.completedAt);
|
|
4470
|
+
}
|
|
4471
|
+
} catch {}
|
|
4472
|
+
}
|
|
4473
|
+
/**
|
|
4474
|
+
* Single delivery funnel for a detached terminal. Both the warm fast path and
|
|
4475
|
+
* the durable backbone route through here, with INDEPENDENT ledger slots for
|
|
4476
|
+
* `finish` (the real terminal) vs `give_up` (budget exhausted). Each slot is
|
|
4477
|
+
* delivered at-least-once via a claim + lease:
|
|
4478
|
+
*
|
|
4479
|
+
* - Concurrent double-fire is prevented by the guarded CAS claim (RETURNING
|
|
4480
|
+
* yields the row only to the winner).
|
|
4481
|
+
* - A crash after the side effect but before `*_delivered_at` is written lets
|
|
4482
|
+
* the lease expire so a later reconcile re-delivers — hence handlers must be
|
|
4483
|
+
* idempotent.
|
|
4484
|
+
* - Two slots, not one, because `interrupted` is SOFT: a give-up followed by a
|
|
4485
|
+
* real completion is legitimate, and a single shared "delivered" bit would
|
|
4486
|
+
* dedupe the child's real late result away (the #1752 production incident).
|
|
4487
|
+
*/
|
|
4488
|
+
async _deliverDetachedTerminal(runId, kind, result, options, completedAt = Date.now()) {
|
|
4489
|
+
const now = Date.now();
|
|
4490
|
+
const leaseFloor = now - DETACHED_DELIVERY_LEASE_MS;
|
|
4491
|
+
const claimQuery = kind === "finish" ? `UPDATE cf_agent_tool_runs
|
|
4492
|
+
SET finish_claimed_at = ?
|
|
4493
|
+
WHERE run_id = ?
|
|
4494
|
+
AND finish_delivered_at IS NULL
|
|
4495
|
+
AND (finish_claimed_at IS NULL OR finish_claimed_at < ?)` : `UPDATE cf_agent_tool_runs
|
|
4496
|
+
SET give_up_claimed_at = ?
|
|
4497
|
+
WHERE run_id = ?
|
|
4498
|
+
AND give_up_delivered_at IS NULL
|
|
4499
|
+
AND (give_up_claimed_at IS NULL OR give_up_claimed_at < ?)`;
|
|
4500
|
+
if (this.ctx.storage.sql.exec(claimQuery, now, runId, leaseFloor).rowsWritten === 0) return;
|
|
4501
|
+
const row = this._readAgentToolRun(runId);
|
|
4502
|
+
if (!row) return;
|
|
4503
|
+
this._updateAgentToolTerminal(runId, result, completedAt);
|
|
4504
|
+
this._broadcastAgentToolTerminal(row.parent_tool_call_id ?? void 0, options?.sequence ?? Date.now(), result);
|
|
4505
|
+
const runInfo = this._agentToolRunInfoFromRow(row, result.status, completedAt);
|
|
4506
|
+
const lifecycle = {
|
|
4507
|
+
status: result.status,
|
|
4508
|
+
...result.summary !== void 0 ? { summary: result.summary } : {},
|
|
4509
|
+
...result.error !== void 0 ? { error: result.error } : {},
|
|
4510
|
+
...result.reason !== void 0 ? { reason: result.reason } : {},
|
|
4511
|
+
...result.childStillRunning !== void 0 ? { childStillRunning: result.childStillRunning } : {}
|
|
4512
|
+
};
|
|
4513
|
+
const invoke = async () => {
|
|
4514
|
+
try {
|
|
4515
|
+
await this.onAgentToolFinish(runInfo, lifecycle);
|
|
4516
|
+
} catch (error) {
|
|
4517
|
+
await this._safeRunOnError(error);
|
|
4518
|
+
}
|
|
4519
|
+
const callbackName = row.detached_on_finish;
|
|
4520
|
+
if (callbackName) {
|
|
4521
|
+
const callback = this[callbackName];
|
|
4522
|
+
if (typeof callback === "function") try {
|
|
4523
|
+
await callback.bind(this)(runInfo, lifecycle);
|
|
4524
|
+
} catch (error) {
|
|
4525
|
+
this._emit("agent_tool:detached:delivery_failed", {
|
|
4526
|
+
runId,
|
|
4527
|
+
kind,
|
|
4528
|
+
status: result.status,
|
|
4529
|
+
callback: callbackName,
|
|
4530
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4531
|
+
});
|
|
4532
|
+
await this._safeRunOnError(error);
|
|
4533
|
+
throw error;
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
};
|
|
4537
|
+
await this._runDetachedDelivery(invoke, { serialize: options?.serialize });
|
|
4538
|
+
if (kind === "finish") this.sql`
|
|
4539
|
+
UPDATE cf_agent_tool_runs
|
|
4540
|
+
SET finish_delivered_at = ${Date.now()}
|
|
4541
|
+
WHERE run_id = ${runId}
|
|
4542
|
+
`;
|
|
4543
|
+
else this.sql`
|
|
4544
|
+
UPDATE cf_agent_tool_runs
|
|
4545
|
+
SET give_up_delivered_at = ${Date.now()}
|
|
4546
|
+
WHERE run_id = ${runId}
|
|
4547
|
+
`;
|
|
4548
|
+
}
|
|
4549
|
+
async _safeRunOnError(error) {
|
|
4550
|
+
try {
|
|
4551
|
+
await this.onError(error);
|
|
4552
|
+
} catch {}
|
|
4553
|
+
}
|
|
4554
|
+
/**
|
|
4555
|
+
* Run a detached terminal delivery (the `onAgentToolFinish` + per-run
|
|
4556
|
+
* `onFinish` callbacks) in an appropriate execution context. The base `Agent`
|
|
4557
|
+
* has no turn queue, so it only establishes `agentContext` — a handler that
|
|
4558
|
+
* calls `runAgentTool` / `setState` therefore works regardless of where the
|
|
4559
|
+
* delivery fired from.
|
|
4560
|
+
*
|
|
4561
|
+
* Chat-layer subclasses (`@cloudflare/think`, `@cloudflare/ai-chat`) override
|
|
4562
|
+
* this to additionally serialize delivery against their turn queue when
|
|
4563
|
+
* `serialize` is set: a fast-path push or backbone tick can land mid-turn, and
|
|
4564
|
+
* a state-mutating `onFinish` running concurrently with an active LLM turn is a
|
|
4565
|
+
* data race. The fast path and backbone never run synchronously inside a turn
|
|
4566
|
+
* (they fire from `waitUntil` / a scheduled alarm), so enqueuing them on the
|
|
4567
|
+
* turn queue is deadlock-free. An explicit `cancelAgentTool` runs with
|
|
4568
|
+
* `serialize` unset because it may be called from inside the very turn that
|
|
4569
|
+
* triggers it, where enqueuing would self-deadlock.
|
|
4570
|
+
*/
|
|
4571
|
+
async _runDetachedDelivery(invoke, _options) {
|
|
4572
|
+
if (__DO_NOT_USE_WILL_BREAK__agentContext.getStore()?.agent) {
|
|
4573
|
+
await invoke();
|
|
4574
|
+
return;
|
|
4575
|
+
}
|
|
4576
|
+
await __DO_NOT_USE_WILL_BREAK__agentContext.run({
|
|
4577
|
+
agent: this,
|
|
4578
|
+
connection: void 0,
|
|
4579
|
+
request: void 0,
|
|
4580
|
+
email: void 0
|
|
4581
|
+
}, invoke);
|
|
4582
|
+
}
|
|
4583
|
+
/**
|
|
4584
|
+
* Arm the self-scheduling detached reconcile backbone. Existing schedules are
|
|
4585
|
+
* reused for recovery/startup calls, but a fresh detached dispatch resets the
|
|
4586
|
+
* pending cadence to the fast end so new work is noticed promptly.
|
|
4587
|
+
*/
|
|
4588
|
+
async _armDetachedBackbone(options) {
|
|
4589
|
+
const run = this._detachedBackboneArming.then(() => this._armDetachedBackboneInner(options));
|
|
4590
|
+
this._detachedBackboneArming = run.then(() => void 0, () => void 0);
|
|
4591
|
+
return run;
|
|
4592
|
+
}
|
|
4593
|
+
async _armDetachedBackboneInner(options) {
|
|
4594
|
+
const armed = (await this.listSchedules()).filter((schedule) => schedule.callback === DETACHED_RECONCILE_CALLBACK);
|
|
4595
|
+
if (armed.length > 0 && !options?.resetCadence) {
|
|
4596
|
+
for (const schedule of armed.slice(1)) await this.cancelSchedule(schedule.id);
|
|
4597
|
+
return;
|
|
4598
|
+
}
|
|
4599
|
+
for (const schedule of armed) await this.cancelSchedule(schedule.id);
|
|
4600
|
+
await this.schedule(DETACHED_BACKBONE_CADENCE_S[0], DETACHED_RECONCILE_CALLBACK, { cadenceIndex: 0 });
|
|
4601
|
+
}
|
|
4602
|
+
/**
|
|
4603
|
+
* Durable backbone for detached runs. Runs on a self-rescheduling alarm:
|
|
4604
|
+
* collects any detached run that has reached terminal but was not yet
|
|
4605
|
+
* delivered (e.g. the parent was evicted before the fast path landed), gives
|
|
4606
|
+
* up on any run past its absolute budget (tearing the child down), and
|
|
4607
|
+
* reschedules itself while any detached run remains undelivered — cancelling
|
|
4608
|
+
* itself once everything has settled (zero steady-state cost).
|
|
4609
|
+
*/
|
|
4610
|
+
async _cfDetachedReconcileTick(payload) {
|
|
4611
|
+
const rows = this.sql`
|
|
4612
|
+
SELECT run_id, parent_tool_call_id, agent_type, input_preview, status,
|
|
4613
|
+
summary, output_json, error_message, interrupted_reason,
|
|
4614
|
+
child_still_running, display_metadata, display_order,
|
|
4615
|
+
started_at, completed_at, detached, detached_on_finish,
|
|
4616
|
+
detached_notify_source, detached_max_budget_at,
|
|
4617
|
+
detached_no_progress_budget_ms, last_progress_at,
|
|
4618
|
+
detached_on_milestones,
|
|
4619
|
+
finish_claimed_at, finish_delivered_at, give_up_claimed_at,
|
|
4620
|
+
give_up_delivered_at
|
|
4621
|
+
FROM cf_agent_tool_runs
|
|
4622
|
+
WHERE detached = 1 AND finish_delivered_at IS NULL
|
|
4623
|
+
ORDER BY started_at ASC
|
|
4624
|
+
`;
|
|
4625
|
+
for (const row of rows) {
|
|
4626
|
+
const runId = row.run_id;
|
|
4627
|
+
let inspection = null;
|
|
4628
|
+
try {
|
|
4629
|
+
const child = await this._cf_resolveSubAgent(row.agent_type, runId);
|
|
4630
|
+
inspection = await this._asAgentToolChildAdapter(child).inspectAgentToolRun(runId);
|
|
4631
|
+
} catch {}
|
|
4632
|
+
if (inspection?.milestones && row.detached_on_milestones) {
|
|
4633
|
+
const milestoneRunInfo = this._agentToolRunInfoFromRow(row);
|
|
4634
|
+
for (const milestone of inspection.milestones) this._maybeDeliverDetachedMilestone(row, milestoneRunInfo, milestone);
|
|
4635
|
+
}
|
|
4636
|
+
if (inspection && this._isAgentToolRowHardTerminal(inspection.status)) {
|
|
4637
|
+
const result = this._terminalResultFromInspection(row.agent_type, inspection);
|
|
4638
|
+
await this._deliverDetachedTerminal(runId, "finish", result, {
|
|
4639
|
+
sequence: Date.now(),
|
|
4640
|
+
serialize: true
|
|
4641
|
+
}, inspection.completedAt);
|
|
4642
|
+
continue;
|
|
4643
|
+
}
|
|
4644
|
+
const now = Date.now();
|
|
4645
|
+
const budgetAt = row.detached_max_budget_at;
|
|
4646
|
+
const latestMilestone = inspection?.milestones?.length ? inspection.milestones[inspection.milestones.length - 1].at : void 0;
|
|
4647
|
+
const signalTimes = [
|
|
4648
|
+
inspection?.progress?.at,
|
|
4649
|
+
latestMilestone,
|
|
4650
|
+
row.last_progress_at
|
|
4651
|
+
].filter((t) => typeof t === "number");
|
|
4652
|
+
const lastSignalAt = signalTimes.length > 0 ? Math.max(...signalTimes) : void 0;
|
|
4653
|
+
const noProgressBudgetMs = row.detached_no_progress_budget_ms;
|
|
4654
|
+
const overAbsolute = budgetAt !== null && now >= budgetAt;
|
|
4655
|
+
const overNoProgress = typeof noProgressBudgetMs === "number" && noProgressBudgetMs > 0 && Number.isFinite(noProgressBudgetMs) && typeof lastSignalAt === "number" && now - lastSignalAt >= noProgressBudgetMs;
|
|
4656
|
+
if ((overAbsolute || overNoProgress) && row.give_up_delivered_at === null) {
|
|
4657
|
+
let childTornDown = false;
|
|
4658
|
+
try {
|
|
4659
|
+
const child = await this._cf_resolveSubAgent(row.agent_type, runId);
|
|
4660
|
+
await this._asAgentToolChildAdapter(child).cancelAgentToolRun(runId, overAbsolute ? "detached budget exceeded" : "detached run went silent past its no-progress window");
|
|
4661
|
+
childTornDown = true;
|
|
4662
|
+
} catch {}
|
|
4663
|
+
await this._deliverDetachedTerminal(runId, "give_up", {
|
|
4664
|
+
runId,
|
|
4665
|
+
agentType: row.agent_type,
|
|
4666
|
+
status: "interrupted",
|
|
4667
|
+
error: overAbsolute ? "detached run exceeded its budget before completing" : "detached run went silent past its no-progress window",
|
|
4668
|
+
reason: overAbsolute ? "budget-exceeded" : "no-progress",
|
|
4669
|
+
childStillRunning: !childTornDown
|
|
4670
|
+
}, { serialize: true });
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
if (this._hasOutstandingDetachedRuns()) {
|
|
4674
|
+
const currentIndex = typeof payload?.cadenceIndex === "number" ? payload.cadenceIndex : 0;
|
|
4675
|
+
const nextIndex = Math.min(currentIndex + 1, DETACHED_BACKBONE_CADENCE_S.length - 1);
|
|
4676
|
+
await this.schedule(DETACHED_BACKBONE_CADENCE_S[nextIndex], DETACHED_RECONCILE_CALLBACK, { cadenceIndex: nextIndex });
|
|
4677
|
+
}
|
|
4678
|
+
}
|
|
4255
4679
|
hasAgentToolRun(classOrName, runId) {
|
|
4256
4680
|
const agentType = typeof classOrName === "string" ? classOrName : classOrName.name;
|
|
4257
4681
|
return (this.sql`
|
|
@@ -4311,7 +4735,10 @@ var Agent = class Agent extends Server {
|
|
|
4311
4735
|
SELECT run_id, parent_tool_call_id, agent_type, input_preview, status,
|
|
4312
4736
|
summary, output_json, error_message, interrupted_reason,
|
|
4313
4737
|
child_still_running, display_metadata, display_order,
|
|
4314
|
-
started_at, completed_at
|
|
4738
|
+
started_at, completed_at, detached, detached_on_finish,
|
|
4739
|
+
detached_notify_source, detached_max_budget_at,
|
|
4740
|
+
finish_claimed_at, finish_delivered_at, give_up_claimed_at,
|
|
4741
|
+
give_up_delivered_at
|
|
4315
4742
|
FROM cf_agent_tool_runs
|
|
4316
4743
|
WHERE run_id = ${runId}
|
|
4317
4744
|
LIMIT 1
|
|
@@ -4351,6 +4778,7 @@ var Agent = class Agent extends Server {
|
|
|
4351
4778
|
inputPreview: this._parseAgentToolJson(row.input_preview),
|
|
4352
4779
|
status,
|
|
4353
4780
|
display: this._parseAgentToolJson(row.display_metadata),
|
|
4781
|
+
...row.detached_notify_source != null ? { notifySource: row.detached_notify_source } : {},
|
|
4354
4782
|
displayOrder: row.display_order,
|
|
4355
4783
|
startedAt: row.started_at,
|
|
4356
4784
|
completedAt
|
|
@@ -4509,6 +4937,7 @@ var Agent = class Agent extends Server {
|
|
|
4509
4937
|
runId,
|
|
4510
4938
|
body: chunk.body
|
|
4511
4939
|
});
|
|
4940
|
+
this._observeForwardedProgress(runId, chunk.body);
|
|
4512
4941
|
forwardedSinceProgress = true;
|
|
4513
4942
|
armIdle();
|
|
4514
4943
|
};
|
|
@@ -4598,6 +5027,76 @@ var Agent = class Agent extends Server {
|
|
|
4598
5027
|
* called repeatedly while a child streams.
|
|
4599
5028
|
*/
|
|
4600
5029
|
async _onAgentToolStreamProgress() {}
|
|
5030
|
+
/**
|
|
5031
|
+
* Best-effort observation of a forwarded child chunk: if it is a reserved
|
|
5032
|
+
* `data-agent-progress` frame, refresh the cached liveness timestamp on the
|
|
5033
|
+
* run row (a hint for a still-warm parent) and fire the public `onProgress`
|
|
5034
|
+
* hook. Never throws into the forward loop — the child's own persisted
|
|
5035
|
+
* snapshot (read via `inspectAgentToolRun`) remains authoritative for the
|
|
5036
|
+
* resetting no-progress budget after eviction.
|
|
5037
|
+
*/
|
|
5038
|
+
_observeForwardedProgress(runId, body) {
|
|
5039
|
+
let parsed;
|
|
5040
|
+
try {
|
|
5041
|
+
parsed = JSON.parse(body);
|
|
5042
|
+
} catch {
|
|
5043
|
+
return;
|
|
5044
|
+
}
|
|
5045
|
+
if (!parsed) return;
|
|
5046
|
+
const isMilestone = parsed.type === AGENT_TOOL_MILESTONE_PART;
|
|
5047
|
+
if (parsed.type !== "data-agent-progress" && !isMilestone) return;
|
|
5048
|
+
const data = parsed.data ?? {};
|
|
5049
|
+
const at = Date.now();
|
|
5050
|
+
const snapshot = {
|
|
5051
|
+
...typeof data.fraction === "number" ? { fraction: data.fraction } : {},
|
|
5052
|
+
...typeof data.message === "string" ? { message: data.message } : {},
|
|
5053
|
+
...typeof data.phase === "string" ? { phase: data.phase } : {},
|
|
5054
|
+
...isMilestone && typeof data.name === "string" ? { milestone: data.name } : {},
|
|
5055
|
+
...data.data !== void 0 ? { data: data.data } : {},
|
|
5056
|
+
at
|
|
5057
|
+
};
|
|
5058
|
+
const row = this._readAgentToolRun(runId);
|
|
5059
|
+
if (!row) return;
|
|
5060
|
+
if (row.detached) try {
|
|
5061
|
+
this.sql`
|
|
5062
|
+
UPDATE cf_agent_tool_runs SET last_progress_at = ${at}
|
|
5063
|
+
WHERE run_id = ${runId}
|
|
5064
|
+
`;
|
|
5065
|
+
} catch {}
|
|
5066
|
+
const runInfo = this._agentToolRunInfoFromRow(row);
|
|
5067
|
+
Promise.resolve(this.onProgress(runInfo, snapshot)).catch((error) => {
|
|
5068
|
+
console.error(`[agents] onProgress hook threw for run ${runId}:`, error instanceof Error ? error.message : String(error));
|
|
5069
|
+
});
|
|
5070
|
+
if (isMilestone && typeof data.name === "string") this._maybeDeliverDetachedMilestone(row, runInfo, {
|
|
5071
|
+
name: data.name,
|
|
5072
|
+
sequence: typeof data.sequence === "number" ? data.sequence : 0,
|
|
5073
|
+
at: typeof data.at === "number" ? data.at : at,
|
|
5074
|
+
...data.data !== void 0 ? { data: data.data } : {}
|
|
5075
|
+
});
|
|
5076
|
+
}
|
|
5077
|
+
/**
|
|
5078
|
+
* Deliver a milestone notification IF this run opted into it via
|
|
5079
|
+
* `detached: { onMilestones }` and the milestone name is in that set. Routes
|
|
5080
|
+
* to the overridable `_deliverDetachedMilestone` seam (a no-op on the base
|
|
5081
|
+
* `Agent`; chat hosts inject an idempotent synthetic chat message).
|
|
5082
|
+
*/
|
|
5083
|
+
_maybeDeliverDetachedMilestone(row, runInfo, milestone) {
|
|
5084
|
+
const configured = this._parseAgentToolJson(row.detached_on_milestones ?? null);
|
|
5085
|
+
const names = Array.isArray(configured) ? configured : configured?.names;
|
|
5086
|
+
const mode = Array.isArray(configured) ? "narrate" : configured?.mode ?? "narrate";
|
|
5087
|
+
if (!Array.isArray(names) || !names.includes(milestone.name)) return;
|
|
5088
|
+
Promise.resolve(this._deliverDetachedMilestone(runInfo, milestone, mode)).catch((error) => {
|
|
5089
|
+
console.error(`[agents] detached milestone delivery threw for run ${runInfo.runId} (${milestone.name}):`, error instanceof Error ? error.message : String(error));
|
|
5090
|
+
});
|
|
5091
|
+
}
|
|
5092
|
+
/**
|
|
5093
|
+
* Overridable seam for the `detached: { onMilestones }` convenience. The base
|
|
5094
|
+
* `Agent` has no chat surface, so this is a no-op; chat hosts
|
|
5095
|
+
* (`@cloudflare/think`, `AIChatAgent`) override it to submit an idempotent
|
|
5096
|
+
* synthetic message keyed on `(runId, milestone.name)`. Called from both the
|
|
5097
|
+
* warm tail and the backbone reconcile, so it MUST be idempotent.
|
|
5098
|
+
*/
|
|
5099
|
+
async _deliverDetachedMilestone(_run, _milestone, _mode) {}
|
|
4601
5100
|
_broadcastAgentToolTerminal(parentToolCallId, sequence, result, replay, connection) {
|
|
4602
5101
|
if (result.status === "completed") this._broadcastAgentToolEvent(parentToolCallId, sequence, {
|
|
4603
5102
|
kind: "finished",
|
|
@@ -4828,7 +5327,7 @@ var Agent = class Agent extends Server {
|
|
|
4828
5327
|
child_still_running, display_metadata, display_order,
|
|
4829
5328
|
started_at, completed_at
|
|
4830
5329
|
FROM cf_agent_tool_runs
|
|
4831
|
-
WHERE status IN ('starting', 'running')
|
|
5330
|
+
WHERE status IN ('starting', 'running') AND detached = 0
|
|
4832
5331
|
ORDER BY started_at ASC
|
|
4833
5332
|
`;
|
|
4834
5333
|
const runIds = options?.runIds !== void 0 ? new Set(options.runIds) : void 0;
|
|
@@ -4967,6 +5466,7 @@ var Agent = class Agent extends Server {
|
|
|
4967
5466
|
runIds: options?.runIds
|
|
4968
5467
|
});
|
|
4969
5468
|
await this._runDeferredAgentToolFinishHooks(recoveredAgentToolFinishes);
|
|
5469
|
+
if (this._hasOutstandingDetachedRuns()) await this._armDetachedBackbone();
|
|
4970
5470
|
})().catch(async (error) => {
|
|
4971
5471
|
this._emit("agent_tool:recovery:failed", { error: error instanceof Error ? error.message : String(error) });
|
|
4972
5472
|
try {
|
|
@@ -5305,10 +5805,29 @@ var Agent = class Agent extends Server {
|
|
|
5305
5805
|
* Start a workflow and track it in this Agent's database.
|
|
5306
5806
|
* Automatically injects agent identity into the workflow params.
|
|
5307
5807
|
*
|
|
5808
|
+
* The originating Agent identity is persisted in the workflow params so
|
|
5809
|
+
* callbacks (`this.agent` RPC, progress/completion/error, state updates)
|
|
5810
|
+
* route back to the exact Agent or sub-agent facet that started the run.
|
|
5811
|
+
* Note the following constraints:
|
|
5812
|
+
*
|
|
5813
|
+
* - **Resolution is by name.** Callbacks re-resolve the originating Agent via
|
|
5814
|
+
* `getAgentByName(...)`. Agents addressed by a raw Durable Object id
|
|
5815
|
+
* (`idFromString`/`get(id)`) rather than by name will not receive
|
|
5816
|
+
* callbacks on the same instance.
|
|
5817
|
+
* - **Sub-agent runs are facet-local.** A workflow started from a sub-agent
|
|
5818
|
+
* is tracked in that facet's own storage; the parent's `getWorkflows()` /
|
|
5819
|
+
* `getWorkflowById()` do not see it. Aggregate across facets yourself if
|
|
5820
|
+
* you need a combined view.
|
|
5821
|
+
* - **Class names must survive bundling.** The originating path is keyed by
|
|
5822
|
+
* `constructor.name`. Ensure your bundler preserves class names
|
|
5823
|
+
* (e.g. esbuild `keepNames: true`) so callbacks can be routed.
|
|
5824
|
+
*
|
|
5308
5825
|
* @template P - Type of params to pass to the workflow
|
|
5309
5826
|
* @param workflowName - Name of the workflow binding in env (e.g., 'MY_WORKFLOW')
|
|
5310
5827
|
* @param params - Params to pass to the workflow
|
|
5311
|
-
* @param options - Optional workflow options
|
|
5828
|
+
* @param options - Optional workflow options. For sub-agents, pass
|
|
5829
|
+
* `agentBinding` as the **root** Agent's Durable Object binding name, not a
|
|
5830
|
+
* child binding.
|
|
5312
5831
|
* @returns The workflow instance ID
|
|
5313
5832
|
*
|
|
5314
5833
|
* @example
|
|
@@ -5322,14 +5841,15 @@ var Agent = class Agent extends Server {
|
|
|
5322
5841
|
async runWorkflow(workflowName, params, options) {
|
|
5323
5842
|
const workflow = this._findWorkflowBindingByName(workflowName);
|
|
5324
5843
|
if (!workflow) throw new Error(`Workflow binding '${workflowName}' not found in environment`);
|
|
5325
|
-
const
|
|
5326
|
-
if (!
|
|
5844
|
+
const agentOrigin = this._workflowOrigin(options);
|
|
5845
|
+
if (!agentOrigin) throw new Error("Could not detect Agent binding name from class name. Pass it explicitly via options.agentBinding");
|
|
5327
5846
|
const workflowId = options?.id ?? `wf_${nanoid()}`;
|
|
5328
5847
|
const augmentedParams = {
|
|
5329
5848
|
...params,
|
|
5330
5849
|
__agentName: this.name,
|
|
5331
|
-
__agentBinding:
|
|
5332
|
-
__workflowName: workflowName
|
|
5850
|
+
__agentBinding: agentOrigin.kind === "agent" ? agentOrigin.binding : agentOrigin.rootBinding,
|
|
5851
|
+
__workflowName: workflowName,
|
|
5852
|
+
__agentOrigin: agentOrigin
|
|
5333
5853
|
};
|
|
5334
5854
|
const instance = await workflow.create({
|
|
5335
5855
|
id: workflowId,
|
|
@@ -5871,12 +6391,28 @@ var Agent = class Agent extends Server {
|
|
|
5871
6391
|
completedAt: row.completed_at ? /* @__PURE__ */ new Date(row.completed_at * 1e3) : null
|
|
5872
6392
|
};
|
|
5873
6393
|
}
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
6394
|
+
_workflowOrigin(options) {
|
|
6395
|
+
if (this._isFacet) {
|
|
6396
|
+
const root = this._parentPath[0];
|
|
6397
|
+
const rootBindingName = options?.agentBinding ?? (root ? this._findAgentBindingNameForClass(root.className) : void 0);
|
|
6398
|
+
if (!rootBindingName) return void 0;
|
|
6399
|
+
return {
|
|
6400
|
+
kind: "facet",
|
|
6401
|
+
version: 1,
|
|
6402
|
+
rootBinding: rootBindingName,
|
|
6403
|
+
path: this.selfPath.map((step) => ({ ...step }))
|
|
6404
|
+
};
|
|
6405
|
+
}
|
|
6406
|
+
const agentBindingName = options?.agentBinding ?? this._findAgentBindingNameForClass(this._ParentClass.name);
|
|
6407
|
+
if (!agentBindingName) return void 0;
|
|
6408
|
+
return {
|
|
6409
|
+
kind: "agent",
|
|
6410
|
+
version: 1,
|
|
6411
|
+
binding: agentBindingName,
|
|
6412
|
+
name: this.name
|
|
6413
|
+
};
|
|
6414
|
+
}
|
|
6415
|
+
_findAgentBindingNameForClass(className) {
|
|
5880
6416
|
for (const [key, value] of Object.entries(this.env)) if (value && typeof value === "object" && "idFromName" in value && typeof value.idFromName === "function") {
|
|
5881
6417
|
if (key === className || camelCaseToKebabCase(key) === camelCaseToKebabCase(className)) return key;
|
|
5882
6418
|
}
|
|
@@ -5914,7 +6450,9 @@ var Agent = class Agent extends Server {
|
|
|
5914
6450
|
}
|
|
5915
6451
|
/**
|
|
5916
6452
|
* Handle a callback from a workflow.
|
|
5917
|
-
*
|
|
6453
|
+
* Invoked via the internal `_workflow_handleCallback` RPC whenever an
|
|
6454
|
+
* {@link AgentWorkflow} reports progress, completion, an error, or a custom
|
|
6455
|
+
* event back to its originating Agent (or sub-agent facet).
|
|
5918
6456
|
* Override this to handle all callback types in one place.
|
|
5919
6457
|
*
|
|
5920
6458
|
* @param callback - The callback payload
|
|
@@ -6448,6 +6986,6 @@ var StreamingResponse = class {
|
|
|
6448
6986
|
}
|
|
6449
6987
|
};
|
|
6450
6988
|
//#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 };
|
|
6989
|
+
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, isPlatformTransientError, normalizeServerId, parseSubAgentPath, routeAgentEmail, routeAgentRequest, routeSubAgentRequest, unstable_callable };
|
|
6452
6990
|
|
|
6453
6991
|
//# sourceMappingURL=index.js.map
|