agents 0.13.2 → 0.14.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 +6 -4
- package/dist/{agent-tool-types-Dn9n-3SI.d.ts → agent-tool-types-LInzZfLo.d.ts} +511 -124
- package/dist/agent-tool-types.d.ts +13 -11
- package/dist/{agent-tools-B1ttU-pq.d.ts → agent-tools-BE9xosUG.d.ts} +2 -2
- package/dist/agent-tools.d.ts +14 -20
- package/dist/agent-tools.js +10 -6
- package/dist/agent-tools.js.map +1 -1
- package/dist/browser/ai.d.ts +1 -1
- package/dist/browser/ai.js +1 -1
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/index.js +1 -1
- package/dist/browser/tanstack-ai.d.ts +1 -1
- package/dist/browser/tanstack-ai.js +1 -1
- package/dist/chat/index.d.ts +194 -22
- package/dist/chat/index.js +144 -11
- package/dist/chat/index.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +4 -4
- package/dist/classPrivateMethodInitSpec-bG0tD96O.js +7 -0
- package/dist/{client-D1kFXo80.js → client-NradHZZz.js} +206 -75
- package/dist/client-NradHZZz.js.map +1 -0
- package/dist/client.d.ts +1 -1
- package/dist/{compaction-helpers-DvcZnvQ1.js → compaction-helpers-BjT2NKRZ.js} +37 -9
- package/dist/compaction-helpers-BjT2NKRZ.js.map +1 -0
- package/dist/{compaction-helpers-DAe-xiVY.d.ts → compaction-helpers-DpP_XP9J.d.ts} +86 -29
- package/dist/{do-oauth-client-provider-4OKQU9rT.d.ts → do-oauth-client-provider-CPm9rK5I.d.ts} +1 -1
- package/dist/{email-J0GGS3sa.d.ts → email-1fTSJwPm.d.ts} +1 -1
- package/dist/email.d.ts +2 -2
- package/dist/experimental/memory/session/index.d.ts +58 -23
- package/dist/experimental/memory/session/index.js +98 -9
- package/dist/experimental/memory/session/index.js.map +1 -1
- package/dist/experimental/memory/utils/index.d.ts +13 -11
- package/dist/experimental/memory/utils/index.js +2 -2
- package/dist/{index-DKey3P4s.d.ts → index-Brdu5nMI.d.ts} +270 -1
- package/dist/index.d.ts +74 -67
- package/dist/index.js +607 -97
- package/dist/index.js.map +1 -1
- package/dist/{internal_context-BZrMS0B5.d.ts → internal_context-CcZy2Em7.d.ts} +1 -1
- package/dist/internal_context.d.ts +1 -1
- package/dist/mcp/client.d.ts +17 -13
- package/dist/mcp/client.js +2 -2
- package/dist/mcp/do-oauth-client-provider.d.ts +1 -1
- package/dist/mcp/index.d.ts +35 -27
- package/dist/mcp/index.js +402 -69
- package/dist/mcp/index.js.map +1 -1
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +15 -1
- package/dist/observability/index.js.map +1 -1
- package/dist/react.d.ts +3 -3
- package/dist/{retries-BVdRl5ZE.d.ts → retries-ClWwxADl.d.ts} +1 -1
- package/dist/retries.d.ts +1 -1
- package/dist/serializable.d.ts +1 -1
- package/dist/{shared-Cvj92byG.d.ts → shared-CpY1FLvm.d.ts} +1 -1
- package/dist/{shared-CiKaIK4h.js → shared-DdOn6sp4.js} +3 -7
- package/dist/{shared-CiKaIK4h.js.map → shared-DdOn6sp4.js.map} +1 -1
- package/dist/skills/index.d.ts +236 -0
- package/dist/skills/index.js +1326 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/sub-routing.d.ts +6 -6
- package/dist/{tool-output-truncation-CH-khbZ3.js → tool-output-truncation-BF4AZQlw.js} +1 -1
- package/dist/{tool-output-truncation-CH-khbZ3.js.map → tool-output-truncation-BF4AZQlw.js.map} +1 -1
- package/dist/{types-_JjKmv-l.d.ts → types-B0GymtN_.d.ts} +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +248 -2
- package/dist/vite.js.map +1 -1
- package/dist/{workflow-types-Dkzg4hAx.d.ts → workflow-types-DPkuBi--.d.ts} +1 -1
- package/dist/workflow-types.d.ts +1 -1
- package/dist/workflows.d.ts +13 -3
- package/dist/workflows.js +10 -1
- package/dist/workflows.js.map +1 -1
- package/package.json +21 -3
- package/skills-module.d.ts +22 -0
- package/dist/client-D1kFXo80.js.map +0 -1
- package/dist/compaction-helpers-DvcZnvQ1.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createHeaderBasedEmailResolver, signAgentHeaders } from "./email.js";
|
|
|
5
5
|
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-Evpt0SEr.js";
|
|
6
6
|
import { SUB_PREFIX, getSubAgentByName, parseSubAgentPath, routeSubAgentRequest } from "./sub-routing.js";
|
|
7
7
|
import { isErrorRetryable, tryN, validateRetryOptions } from "./retries.js";
|
|
8
|
-
import {
|
|
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-NradHZZz.js";
|
|
9
9
|
import { DurableObjectOAuthClientProvider } from "./mcp/do-oauth-client-provider.js";
|
|
10
10
|
import { genericObservability } from "./observability/index.js";
|
|
11
11
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -114,6 +114,12 @@ function getNextCronTime(cron) {
|
|
|
114
114
|
return parseCronExpression(cron).getNextDate();
|
|
115
115
|
}
|
|
116
116
|
const DEFAULT_KEEP_ALIVE_INTERVAL_MS = 3e4;
|
|
117
|
+
const DEFAULT_AGENT_TOOL_RECOVERY_TIMEOUT_MS = 2e3;
|
|
118
|
+
const DEFAULT_AGENT_TOOL_RECOVERY_TOTAL_TIMEOUT_MS = 5e3;
|
|
119
|
+
const DEFAULT_AGENT_TOOL_REATTACH_TIMEOUT_MS = 12e4;
|
|
120
|
+
const SUB_AGENT_IDENTITY_VERSION_LEGACY = "legacy";
|
|
121
|
+
const SUB_AGENT_IDENTITY_VERSION_PATH_V2 = "path-v2";
|
|
122
|
+
const SUB_AGENT_IDENTITY_PATH_V2_PREFIX = "cf-agents:v2:";
|
|
117
123
|
/**
|
|
118
124
|
* Schema version for the Agent's internal SQLite tables.
|
|
119
125
|
* Bump this when adding new tables, columns, or migrations.
|
|
@@ -125,6 +131,25 @@ const SCHEMA_VERSION_ROW_ID = "cf_schema_version";
|
|
|
125
131
|
const STATE_ROW_ID = "cf_state_row_id";
|
|
126
132
|
const STATE_WAS_CHANGED = "cf_state_was_changed";
|
|
127
133
|
const DEFAULT_STATE = {};
|
|
134
|
+
async function sha256Hex(value) {
|
|
135
|
+
const bytes = new TextEncoder().encode(value);
|
|
136
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
137
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
138
|
+
}
|
|
139
|
+
function pathV2IdentityName(logicalName, digest) {
|
|
140
|
+
return `${SUB_AGENT_IDENTITY_PATH_V2_PREFIX}${encodeURIComponent(logicalName)}:${digest}`;
|
|
141
|
+
}
|
|
142
|
+
function logicalNameFromPathV2Identity(identityName) {
|
|
143
|
+
if (!identityName.startsWith(SUB_AGENT_IDENTITY_PATH_V2_PREFIX)) return null;
|
|
144
|
+
const rest = identityName.slice(13);
|
|
145
|
+
const separator = rest.lastIndexOf(":");
|
|
146
|
+
if (separator === -1) return null;
|
|
147
|
+
try {
|
|
148
|
+
return decodeURIComponent(rest.slice(0, separator));
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
128
153
|
/**
|
|
129
154
|
* Validate that a stored `parentPath` has the expected shape. Used
|
|
130
155
|
* when restoring from DO storage to guard against corrupted data.
|
|
@@ -240,7 +265,17 @@ const DEFAULT_AGENT_STATIC_OPTIONS = {
|
|
|
240
265
|
maxAttempts: 3,
|
|
241
266
|
baseDelayMs: 100,
|
|
242
267
|
maxDelayMs: 3e3
|
|
243
|
-
}
|
|
268
|
+
},
|
|
269
|
+
/** Timeout for internal framework fiber recovery hooks. */
|
|
270
|
+
fiberRecoveryHookTimeoutMs: 1e4,
|
|
271
|
+
/** Soft deadline for one interrupted-fiber recovery scan. */
|
|
272
|
+
fiberRecoveryScanDeadlineMs: 1e4,
|
|
273
|
+
/**
|
|
274
|
+
* Maximum age of an unmanaged interrupted-fiber row before recovery gives
|
|
275
|
+
* up. Bounds repeated retries of a `onFiberRecovered()` hook that keeps
|
|
276
|
+
* throwing so a poison row cannot re-trigger forever across boots.
|
|
277
|
+
*/
|
|
278
|
+
fiberRecoveryMaxAgeMs: 1440 * 60 * 1e3
|
|
244
279
|
};
|
|
245
280
|
/**
|
|
246
281
|
* Parse the raw `retry_options` TEXT column from a SQLite row into a
|
|
@@ -263,6 +298,19 @@ function resolveRetryConfig(taskRetry, defaults) {
|
|
|
263
298
|
maxDelayMs: taskRetry?.maxDelayMs ?? defaults.maxDelayMs
|
|
264
299
|
};
|
|
265
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* Whether an error is a Durable Object reset caused by a code update (deploy).
|
|
303
|
+
*
|
|
304
|
+
* This is a transient, environmental failure: the invocation started on a
|
|
305
|
+
* superseded isolate, so every `ctx.storage` op throws this for the entire
|
|
306
|
+
* life of the invocation (code never reloads mid-invocation) — but the next
|
|
307
|
+
* fresh invocation runs the new code and succeeds. workerd surfaces it as a
|
|
308
|
+
* plain `Error` with this message, so a message match is the only signal.
|
|
309
|
+
*/
|
|
310
|
+
function isDurableObjectCodeUpdateReset(error) {
|
|
311
|
+
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
|
312
|
+
return /reset because its code was updated/i.test(message);
|
|
313
|
+
}
|
|
266
314
|
function getCurrentAgent() {
|
|
267
315
|
const store = __DO_NOT_USE_WILL_BREAK__agentContext.getStore();
|
|
268
316
|
if (!store) return {
|
|
@@ -360,7 +408,10 @@ var Agent = class Agent extends Server {
|
|
|
360
408
|
maxAttempts: userRetry?.maxAttempts ?? DEFAULT_AGENT_STATIC_OPTIONS.retry.maxAttempts,
|
|
361
409
|
baseDelayMs: userRetry?.baseDelayMs ?? DEFAULT_AGENT_STATIC_OPTIONS.retry.baseDelayMs,
|
|
362
410
|
maxDelayMs: userRetry?.maxDelayMs ?? DEFAULT_AGENT_STATIC_OPTIONS.retry.maxDelayMs
|
|
363
|
-
}
|
|
411
|
+
},
|
|
412
|
+
fiberRecoveryHookTimeoutMs: ctor.options?.fiberRecoveryHookTimeoutMs ?? DEFAULT_AGENT_STATIC_OPTIONS.fiberRecoveryHookTimeoutMs,
|
|
413
|
+
fiberRecoveryScanDeadlineMs: ctor.options?.fiberRecoveryScanDeadlineMs ?? DEFAULT_AGENT_STATIC_OPTIONS.fiberRecoveryScanDeadlineMs,
|
|
414
|
+
fiberRecoveryMaxAgeMs: ctor.options?.fiberRecoveryMaxAgeMs ?? DEFAULT_AGENT_STATIC_OPTIONS.fiberRecoveryMaxAgeMs
|
|
364
415
|
};
|
|
365
416
|
return this._cachedOptions;
|
|
366
417
|
}
|
|
@@ -848,6 +899,8 @@ var Agent = class Agent extends Server {
|
|
|
848
899
|
email: void 0
|
|
849
900
|
}, async () => {
|
|
850
901
|
if (await this.ctx.storage.get("cf_agents_is_facet")) this._isFacet = true;
|
|
902
|
+
const storedFacetName = await this.ctx.storage.get("cf_agents_facet_name");
|
|
903
|
+
if (typeof storedFacetName === "string") this._facetName = storedFacetName;
|
|
851
904
|
const storedParentPath = await this.ctx.storage.get("cf_agents_parent_path");
|
|
852
905
|
if (isValidParentPath(storedParentPath)) this._parentPath = storedParentPath;
|
|
853
906
|
try {
|
|
@@ -861,7 +914,7 @@ var Agent = class Agent extends Server {
|
|
|
861
914
|
this.broadcastMcpServers();
|
|
862
915
|
this._checkOrphanedWorkflows();
|
|
863
916
|
await this._checkRunFibers();
|
|
864
|
-
const
|
|
917
|
+
const startupAgentToolRunIds = this._agentToolRunRecoveryRunIds();
|
|
865
918
|
this._insideOnStart = true;
|
|
866
919
|
this._warnedScheduleInOnStart.clear();
|
|
867
920
|
let result;
|
|
@@ -870,7 +923,7 @@ var Agent = class Agent extends Server {
|
|
|
870
923
|
} finally {
|
|
871
924
|
this._insideOnStart = false;
|
|
872
925
|
}
|
|
873
|
-
|
|
926
|
+
this._scheduleAgentToolRunRecovery({ runIds: startupAgentToolRunIds });
|
|
874
927
|
return result;
|
|
875
928
|
});
|
|
876
929
|
});
|
|
@@ -1569,6 +1622,13 @@ var Agent = class Agent extends Server {
|
|
|
1569
1622
|
if (!binding) throw new Error(`Unable to resolve root scheduler "${root.className}" for sub-agent schedule delegation.`);
|
|
1570
1623
|
return await getServerByName(binding, root.name);
|
|
1571
1624
|
}
|
|
1625
|
+
_cf_rootResolvesToSelf() {
|
|
1626
|
+
const root = this._parentPath[0];
|
|
1627
|
+
if (!root) return false;
|
|
1628
|
+
const binding = this.ctx.exports?.[root.className];
|
|
1629
|
+
if (!binding?.idFromName) return false;
|
|
1630
|
+
return binding.idFromName(root.name).equals(this.ctx.id);
|
|
1631
|
+
}
|
|
1572
1632
|
_validateScheduleCallback(when, callback, options) {
|
|
1573
1633
|
if (typeof callback !== "string") throw new Error("Callback must be a string");
|
|
1574
1634
|
if (typeof this[callback] !== "function") throw new Error(`this.${callback} is not a function`);
|
|
@@ -2355,20 +2415,67 @@ var Agent = class Agent extends Server {
|
|
|
2355
2415
|
return null;
|
|
2356
2416
|
}
|
|
2357
2417
|
}
|
|
2418
|
+
_fiberRecoveryPayload(ctx, managedRow, startedAt) {
|
|
2419
|
+
return {
|
|
2420
|
+
fiberId: ctx.id,
|
|
2421
|
+
fiberName: ctx.name,
|
|
2422
|
+
managed: managedRow !== null,
|
|
2423
|
+
recoveryReason: ctx.recoveryReason,
|
|
2424
|
+
elapsedMs: startedAt === void 0 ? void 0 : Date.now() - startedAt
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
async _withFiberRecoveryTimeout(ctx, operation) {
|
|
2428
|
+
const timeoutMs = this._resolvedOptions.fiberRecoveryHookTimeoutMs;
|
|
2429
|
+
if (timeoutMs <= 0) return operation();
|
|
2430
|
+
let timer;
|
|
2431
|
+
try {
|
|
2432
|
+
return await Promise.race([operation(), new Promise((_, reject) => {
|
|
2433
|
+
timer = setTimeout(() => {
|
|
2434
|
+
reject(/* @__PURE__ */ new Error(`Fiber recovery hook timed out after ${timeoutMs}ms for "${ctx.name}" (${ctx.id})`));
|
|
2435
|
+
}, timeoutMs);
|
|
2436
|
+
})]);
|
|
2437
|
+
} finally {
|
|
2438
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
_recordFiberRecoveryFailure(ctx, managedRow, error, startedAt, reason = "handler_error") {
|
|
2442
|
+
const errorMessage = this._fiberErrorMessage(error);
|
|
2443
|
+
const completedAt = Date.now();
|
|
2444
|
+
if (managedRow) {
|
|
2445
|
+
this.sql`
|
|
2446
|
+
UPDATE cf_agents_fibers
|
|
2447
|
+
SET status = 'error',
|
|
2448
|
+
error_message = ${errorMessage},
|
|
2449
|
+
completed_at = ${completedAt}
|
|
2450
|
+
WHERE fiber_id = ${ctx.id}
|
|
2451
|
+
AND status = 'interrupted'
|
|
2452
|
+
`;
|
|
2453
|
+
this._notifyManagedFiberTerminal(ctx.id);
|
|
2454
|
+
}
|
|
2455
|
+
this._emit("fiber:recovery:failed", {
|
|
2456
|
+
...this._fiberRecoveryPayload(ctx, managedRow, startedAt),
|
|
2457
|
+
error: errorMessage,
|
|
2458
|
+
reason
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2358
2461
|
async _runFiberRecoveryHook(ctx, managedRow) {
|
|
2462
|
+
const startedAt = Date.now();
|
|
2463
|
+
this._emit("fiber:recovery:attempt", this._fiberRecoveryPayload(ctx, managedRow));
|
|
2359
2464
|
try {
|
|
2360
|
-
|
|
2465
|
+
const handled = await this._withFiberRecoveryTimeout(ctx, () => this._handleInternalFiberRecovery(ctx));
|
|
2466
|
+
if (!handled) {
|
|
2361
2467
|
const recoveryResult = await this.onFiberRecovered(ctx);
|
|
2362
2468
|
if (managedRow && recoveryResult) this._applyManagedFiberRecoveryResult(ctx.id, recoveryResult);
|
|
2363
2469
|
}
|
|
2470
|
+
this._emit("fiber:recovery:handled", {
|
|
2471
|
+
...this._fiberRecoveryPayload(ctx, managedRow, startedAt),
|
|
2472
|
+
status: handled ? "internal" : managedRow ? "managed" : "user"
|
|
2473
|
+
});
|
|
2474
|
+
return true;
|
|
2364
2475
|
} catch (e) {
|
|
2365
|
-
|
|
2366
|
-
UPDATE cf_agents_fibers
|
|
2367
|
-
SET error_message = ${this._fiberErrorMessage(e)}
|
|
2368
|
-
WHERE fiber_id = ${ctx.id}
|
|
2369
|
-
AND status = 'interrupted'
|
|
2370
|
-
`;
|
|
2476
|
+
this._recordFiberRecoveryFailure(ctx, managedRow, e, startedAt);
|
|
2371
2477
|
console.error(`[Agent] Fiber recovery failed for "${ctx.name}" (${ctx.id}):`, e);
|
|
2478
|
+
return false;
|
|
2372
2479
|
}
|
|
2373
2480
|
}
|
|
2374
2481
|
_fiberInspectionFromRow(row) {
|
|
@@ -2545,6 +2652,18 @@ var Agent = class Agent extends Server {
|
|
|
2545
2652
|
async runFiber(name, fn) {
|
|
2546
2653
|
return this._runFiberInternal(nanoid(), name, fn);
|
|
2547
2654
|
}
|
|
2655
|
+
/**
|
|
2656
|
+
* Internal framework entry point for fibers that need to compose their own
|
|
2657
|
+
* recovery metadata with user checkpoint data while preserving the public
|
|
2658
|
+
* `this.stash()` behavior.
|
|
2659
|
+
*
|
|
2660
|
+
* This deliberately stays protected/internal rather than becoming a public
|
|
2661
|
+
* `runFiber()` option until the durable execution API needs this generality.
|
|
2662
|
+
* @internal
|
|
2663
|
+
*/
|
|
2664
|
+
async _runFiberWithStashWrapper(name, fn, options) {
|
|
2665
|
+
return this._runFiberInternal(nanoid(), name, fn, options);
|
|
2666
|
+
}
|
|
2548
2667
|
async startFiber(name, fn, options) {
|
|
2549
2668
|
const fiberId = options?.fiberId ?? nanoid();
|
|
2550
2669
|
const idempotencyKey = options?.idempotencyKey;
|
|
@@ -2641,11 +2760,29 @@ var Agent = class Agent extends Server {
|
|
|
2641
2760
|
INSERT INTO cf_agents_runs (id, name, snapshot, created_at)
|
|
2642
2761
|
VALUES (${id}, ${name}, NULL, ${Date.now()})
|
|
2643
2762
|
`;
|
|
2763
|
+
const startedAt = Date.now();
|
|
2764
|
+
this._emit("fiber:run:started", {
|
|
2765
|
+
fiberId: id,
|
|
2766
|
+
fiberName: name,
|
|
2767
|
+
managed: options?.managed === true
|
|
2768
|
+
});
|
|
2644
2769
|
this._runFiberActiveFibers.add(id);
|
|
2770
|
+
const writeSnapshot = (data) => {
|
|
2771
|
+
const snapshot = JSON.stringify(data);
|
|
2772
|
+
this.sql`
|
|
2773
|
+
UPDATE cf_agents_runs SET snapshot = ${snapshot}
|
|
2774
|
+
WHERE id = ${id}
|
|
2775
|
+
`;
|
|
2776
|
+
if (options?.managed) this.sql`
|
|
2777
|
+
UPDATE cf_agents_fibers SET snapshot = ${snapshot}
|
|
2778
|
+
WHERE fiber_id = ${id}
|
|
2779
|
+
`;
|
|
2780
|
+
};
|
|
2645
2781
|
let root;
|
|
2646
2782
|
let registeredFacetRun = false;
|
|
2647
2783
|
let dispose = () => {};
|
|
2648
2784
|
try {
|
|
2785
|
+
if ("initialSnapshot" in (options ?? {})) writeSnapshot(options?.initialSnapshot);
|
|
2649
2786
|
if (this._isFacet) {
|
|
2650
2787
|
root = await this._rootAlarmOwner();
|
|
2651
2788
|
await root._cf_registerFacetRun(this.selfPath, id);
|
|
@@ -2653,15 +2790,7 @@ var Agent = class Agent extends Server {
|
|
|
2653
2790
|
}
|
|
2654
2791
|
dispose = await this.keepAlive();
|
|
2655
2792
|
const stash = (data) => {
|
|
2656
|
-
|
|
2657
|
-
this.sql`
|
|
2658
|
-
UPDATE cf_agents_runs SET snapshot = ${snapshot}
|
|
2659
|
-
WHERE id = ${id}
|
|
2660
|
-
`;
|
|
2661
|
-
if (options?.managed) this.sql`
|
|
2662
|
-
UPDATE cf_agents_fibers SET snapshot = ${snapshot}
|
|
2663
|
-
WHERE fiber_id = ${id}
|
|
2664
|
-
`;
|
|
2793
|
+
writeSnapshot(options?.wrapStash ? options.wrapStash(data) : data);
|
|
2665
2794
|
};
|
|
2666
2795
|
try {
|
|
2667
2796
|
const result = await _fiberALS.run({
|
|
@@ -2675,12 +2804,25 @@ var Agent = class Agent extends Server {
|
|
|
2675
2804
|
snapshot: null
|
|
2676
2805
|
}));
|
|
2677
2806
|
options?.beforeRunCleanup?.({ ok: true });
|
|
2807
|
+
this._emit("fiber:run:completed", {
|
|
2808
|
+
fiberId: id,
|
|
2809
|
+
fiberName: name,
|
|
2810
|
+
managed: options?.managed === true,
|
|
2811
|
+
elapsedMs: Date.now() - startedAt
|
|
2812
|
+
});
|
|
2678
2813
|
return result;
|
|
2679
2814
|
} catch (error) {
|
|
2680
2815
|
options?.beforeRunCleanup?.({
|
|
2681
2816
|
ok: false,
|
|
2682
2817
|
error
|
|
2683
2818
|
});
|
|
2819
|
+
this._emit("fiber:run:failed", {
|
|
2820
|
+
fiberId: id,
|
|
2821
|
+
fiberName: name,
|
|
2822
|
+
managed: options?.managed === true,
|
|
2823
|
+
error: this._fiberErrorMessage(error),
|
|
2824
|
+
elapsedMs: Date.now() - startedAt
|
|
2825
|
+
});
|
|
2684
2826
|
throw error;
|
|
2685
2827
|
}
|
|
2686
2828
|
} finally {
|
|
@@ -2730,18 +2872,42 @@ var Agent = class Agent extends Server {
|
|
|
2730
2872
|
async _checkRunFibers() {
|
|
2731
2873
|
if (this._runFiberRecoveryInProgress) return;
|
|
2732
2874
|
this._runFiberRecoveryInProgress = true;
|
|
2875
|
+
const scanStartedAt = Date.now();
|
|
2876
|
+
const scanDeadlineMs = this._resolvedOptions.fiberRecoveryScanDeadlineMs;
|
|
2877
|
+
const fiberRecoveryMaxAgeMs = this._resolvedOptions.fiberRecoveryMaxAgeMs;
|
|
2733
2878
|
try {
|
|
2734
2879
|
const rows = this.sql`SELECT id, name, snapshot, created_at FROM cf_agents_runs`;
|
|
2735
2880
|
for (const row of rows) {
|
|
2881
|
+
if (scanDeadlineMs > 0 && Date.now() - scanStartedAt > scanDeadlineMs) {
|
|
2882
|
+
this._emit("fiber:recovery:skipped", {
|
|
2883
|
+
fiberId: row.id,
|
|
2884
|
+
fiberName: row.name,
|
|
2885
|
+
reason: "scan_deadline_exceeded",
|
|
2886
|
+
elapsedMs: Date.now() - scanStartedAt
|
|
2887
|
+
});
|
|
2888
|
+
break;
|
|
2889
|
+
}
|
|
2736
2890
|
if (this._runFiberActiveFibers.has(row.id)) continue;
|
|
2737
2891
|
const snapshot = this._parseFiberRecoverySnapshot(row.id, row.snapshot);
|
|
2738
2892
|
const ctx = {
|
|
2739
2893
|
id: row.id,
|
|
2740
2894
|
name: row.name,
|
|
2741
2895
|
snapshot,
|
|
2742
|
-
createdAt: row.created_at
|
|
2896
|
+
createdAt: row.created_at,
|
|
2897
|
+
recoveryReason: "interrupted"
|
|
2743
2898
|
};
|
|
2744
2899
|
const managedRow = this._readFiber(row.id);
|
|
2900
|
+
this._emit("fiber:recovery:detected", {
|
|
2901
|
+
...this._fiberRecoveryPayload(ctx, managedRow),
|
|
2902
|
+
elapsedMs: Date.now() - row.created_at
|
|
2903
|
+
});
|
|
2904
|
+
this._emit("fiber:run:interrupted", {
|
|
2905
|
+
fiberId: row.id,
|
|
2906
|
+
fiberName: row.name,
|
|
2907
|
+
managed: managedRow !== null,
|
|
2908
|
+
recoveryReason: "interrupted",
|
|
2909
|
+
elapsedMs: Date.now() - row.created_at
|
|
2910
|
+
});
|
|
2745
2911
|
if (managedRow) {
|
|
2746
2912
|
if (this._isTerminalFiberStatus(managedRow.status)) {
|
|
2747
2913
|
this.sql`DELETE FROM cf_agents_runs WHERE id = ${row.id}`;
|
|
@@ -2761,8 +2927,17 @@ var Agent = class Agent extends Server {
|
|
|
2761
2927
|
ctx.metadata = this._parseFiberJsonObject(managedRow.metadata_json);
|
|
2762
2928
|
ctx.status = "interrupted";
|
|
2763
2929
|
}
|
|
2764
|
-
await this._runFiberRecoveryHook(ctx, managedRow);
|
|
2765
|
-
|
|
2930
|
+
const recovered = await this._runFiberRecoveryHook(ctx, managedRow);
|
|
2931
|
+
const tooOld = fiberRecoveryMaxAgeMs > 0 && Date.now() - row.created_at > fiberRecoveryMaxAgeMs;
|
|
2932
|
+
if (recovered || managedRow || tooOld) {
|
|
2933
|
+
if (!recovered && !managedRow && tooOld) this._emit("fiber:recovery:skipped", {
|
|
2934
|
+
fiberId: row.id,
|
|
2935
|
+
fiberName: row.name,
|
|
2936
|
+
reason: "max_age_exceeded",
|
|
2937
|
+
elapsedMs: Date.now() - row.created_at
|
|
2938
|
+
});
|
|
2939
|
+
this.sql`DELETE FROM cf_agents_runs WHERE id = ${row.id}`;
|
|
2940
|
+
}
|
|
2766
2941
|
if (managedRow) this._notifyManagedFiberTerminal(row.id);
|
|
2767
2942
|
}
|
|
2768
2943
|
const ledgerOnlyRows = this.sql`
|
|
@@ -2775,6 +2950,16 @@ var Agent = class Agent extends Server {
|
|
|
2775
2950
|
AND r.id IS NULL
|
|
2776
2951
|
`;
|
|
2777
2952
|
for (const row of ledgerOnlyRows) {
|
|
2953
|
+
if (scanDeadlineMs > 0 && Date.now() - scanStartedAt > scanDeadlineMs) {
|
|
2954
|
+
this._emit("fiber:recovery:skipped", {
|
|
2955
|
+
fiberId: row.fiber_id,
|
|
2956
|
+
fiberName: row.name,
|
|
2957
|
+
reason: "scan_deadline_exceeded",
|
|
2958
|
+
elapsedMs: Date.now() - scanStartedAt,
|
|
2959
|
+
managed: true
|
|
2960
|
+
});
|
|
2961
|
+
break;
|
|
2962
|
+
}
|
|
2778
2963
|
if (this._runFiberActiveFibers.has(row.fiber_id)) continue;
|
|
2779
2964
|
const snapshot = this._parseFiberRecoverySnapshot(row.fiber_id, row.snapshot);
|
|
2780
2965
|
const completedAt = Date.now();
|
|
@@ -2785,15 +2970,28 @@ var Agent = class Agent extends Server {
|
|
|
2785
2970
|
WHERE fiber_id = ${row.fiber_id}
|
|
2786
2971
|
AND status IN ('pending', 'running')
|
|
2787
2972
|
`;
|
|
2788
|
-
|
|
2973
|
+
const ctx = {
|
|
2789
2974
|
id: row.fiber_id,
|
|
2790
2975
|
name: row.name,
|
|
2791
2976
|
snapshot,
|
|
2792
2977
|
createdAt: row.created_at,
|
|
2793
2978
|
idempotencyKey: row.idempotency_key ?? void 0,
|
|
2794
2979
|
metadata: this._parseFiberJsonObject(row.metadata_json),
|
|
2795
|
-
status: "interrupted"
|
|
2796
|
-
|
|
2980
|
+
status: "interrupted",
|
|
2981
|
+
recoveryReason: "interrupted"
|
|
2982
|
+
};
|
|
2983
|
+
this._emit("fiber:recovery:detected", {
|
|
2984
|
+
...this._fiberRecoveryPayload(ctx, row),
|
|
2985
|
+
elapsedMs: Date.now() - row.created_at
|
|
2986
|
+
});
|
|
2987
|
+
this._emit("fiber:run:interrupted", {
|
|
2988
|
+
fiberId: row.fiber_id,
|
|
2989
|
+
fiberName: row.name,
|
|
2990
|
+
managed: true,
|
|
2991
|
+
recoveryReason: "interrupted",
|
|
2992
|
+
elapsedMs: Date.now() - row.created_at
|
|
2993
|
+
});
|
|
2994
|
+
await this._runFiberRecoveryHook(ctx, row);
|
|
2797
2995
|
this._notifyManagedFiberTerminal(row.fiber_id);
|
|
2798
2996
|
}
|
|
2799
2997
|
} finally {
|
|
@@ -2951,6 +3149,8 @@ var Agent = class Agent extends Server {
|
|
|
2951
3149
|
});
|
|
2952
3150
|
return;
|
|
2953
3151
|
}
|
|
3152
|
+
const isOneShotSchedule = row.type === "delayed" || row.type === "scheduled";
|
|
3153
|
+
const shouldDeferReset = (error) => isOneShotSchedule && isDurableObjectCodeUpdateReset(error);
|
|
2954
3154
|
try {
|
|
2955
3155
|
this._emit("schedule:execute", {
|
|
2956
3156
|
callback: row.callback,
|
|
@@ -2966,9 +3166,14 @@ var Agent = class Agent extends Server {
|
|
|
2966
3166
|
await callback.bind(this)(parsedPayload, row);
|
|
2967
3167
|
}, {
|
|
2968
3168
|
baseDelayMs,
|
|
2969
|
-
maxDelayMs
|
|
3169
|
+
maxDelayMs,
|
|
3170
|
+
shouldRetry: (error) => !shouldDeferReset(error)
|
|
2970
3171
|
});
|
|
2971
3172
|
} catch (e) {
|
|
3173
|
+
if (shouldDeferReset(e)) {
|
|
3174
|
+
console.warn(`Deferring scheduled callback "${row.callback}" to a fresh invocation after a Durable Object code-update reset; the one-shot row is preserved and the alarm will re-run on new code.`);
|
|
3175
|
+
throw e;
|
|
3176
|
+
}
|
|
2972
3177
|
console.error(`error executing callback "${row.callback}" after ${maxAttempts} attempts`, e);
|
|
2973
3178
|
this._emit("schedule:error", {
|
|
2974
3179
|
callback: row.callback,
|
|
@@ -3467,6 +3672,7 @@ var Agent = class Agent extends Server {
|
|
|
3467
3672
|
}
|
|
3468
3673
|
async _cf_hydrateSubAgentConnectionsFromRoot() {
|
|
3469
3674
|
if (!this._isFacet || this._parentPath.length === 0) return;
|
|
3675
|
+
if (this._cf_rootResolvesToSelf()) return;
|
|
3470
3676
|
const root = await this._rootAlarmOwner();
|
|
3471
3677
|
const metas = await root._cf_subAgentConnectionMetas(this.selfPath);
|
|
3472
3678
|
for (const meta of metas) this._cf_virtualSubAgentConnections.set(meta.id, {
|
|
@@ -3607,21 +3813,23 @@ var Agent = class Agent extends Server {
|
|
|
3607
3813
|
* broadcast to their own WebSocket clients reached via sub-agent
|
|
3608
3814
|
* routing.
|
|
3609
3815
|
*
|
|
3610
|
-
* The facet's name
|
|
3611
|
-
*
|
|
3612
|
-
*
|
|
3613
|
-
* `ctx.facets.get()` — see {@link _cf_resolveSubAgent}. No
|
|
3614
|
-
* `setName()` call or `__ps_name` storage write is needed; the
|
|
3615
|
-
* facet's name survives cold wake automatically because the factory
|
|
3616
|
-
* re-runs and `idFromName` is deterministic.
|
|
3816
|
+
* The facet's logical name is persisted separately from its routing id.
|
|
3817
|
+
* Legacy facets used the logical name directly as `ctx.id.name`; newer
|
|
3818
|
+
* facets can use path-scoped routing ids while preserving `this.name`.
|
|
3617
3819
|
*
|
|
3618
3820
|
* @internal Called by {@link subAgent}.
|
|
3619
3821
|
*/
|
|
3620
|
-
async _cf_initAsFacet(name, parentPath = []) {
|
|
3621
|
-
|
|
3822
|
+
async _cf_initAsFacet(name, parentPath = [], identityName = name) {
|
|
3823
|
+
const routedName = super.name;
|
|
3824
|
+
if (routedName !== identityName) throw new Error(`Facet bootstrap mismatch: expected routed identity "${identityName}" but got "${routedName}". This usually means the parent passed the wrong id to ctx.facets.get(). See _cf_resolveSubAgent.`);
|
|
3622
3825
|
this._isFacet = true;
|
|
3826
|
+
this._facetName = name;
|
|
3623
3827
|
this._parentPath = parentPath;
|
|
3624
|
-
await Promise.all([
|
|
3828
|
+
await Promise.all([
|
|
3829
|
+
this.ctx.storage.put("cf_agents_is_facet", true),
|
|
3830
|
+
this.ctx.storage.put("cf_agents_facet_name", name),
|
|
3831
|
+
this.ctx.storage.put("cf_agents_parent_path", parentPath)
|
|
3832
|
+
]);
|
|
3625
3833
|
this._suppressProtocolBroadcasts = true;
|
|
3626
3834
|
try {
|
|
3627
3835
|
await this.__unsafe_ensureInitialized();
|
|
@@ -3629,6 +3837,9 @@ var Agent = class Agent extends Server {
|
|
|
3629
3837
|
this._suppressProtocolBroadcasts = false;
|
|
3630
3838
|
}
|
|
3631
3839
|
}
|
|
3840
|
+
get name() {
|
|
3841
|
+
return this._facetName ?? logicalNameFromPathV2Identity(super.name) ?? super.name;
|
|
3842
|
+
}
|
|
3632
3843
|
/**
|
|
3633
3844
|
* Ancestor chain for this agent, root-first. Empty for top-level
|
|
3634
3845
|
* DOs. Populated at facet init time; survives hibernation.
|
|
@@ -3782,7 +3993,7 @@ var Agent = class Agent extends Server {
|
|
|
3782
3993
|
const agentType = cls.name;
|
|
3783
3994
|
const existing = this._readAgentToolRun(runId);
|
|
3784
3995
|
if (existing) {
|
|
3785
|
-
if (
|
|
3996
|
+
if (existing.status === "completed" || existing.status === "error" || existing.status === "aborted") {
|
|
3786
3997
|
if (existing.status === "completed" && existing.output_json == null) try {
|
|
3787
3998
|
const child = await this.subAgent(cls, runId);
|
|
3788
3999
|
const inspection = await this._asAgentToolChildAdapter(child).inspectAgentToolRun(runId);
|
|
@@ -3794,7 +4005,19 @@ var Agent = class Agent extends Server {
|
|
|
3794
4005
|
} catch {}
|
|
3795
4006
|
return this._resultFromAgentToolRow(existing);
|
|
3796
4007
|
}
|
|
3797
|
-
|
|
4008
|
+
try {
|
|
4009
|
+
const child = await this.subAgent(cls, runId);
|
|
4010
|
+
const adapter = this._asAgentToolChildAdapter(child);
|
|
4011
|
+
const reattach = await this._reattachAgentToolRunToTerminal(adapter, existing, 1);
|
|
4012
|
+
if (reattach.result) {
|
|
4013
|
+
await this._finishAgentToolRun(this._agentToolRunInfoFromRow(existing), reattach.result, {
|
|
4014
|
+
sequence: reattach.sequence,
|
|
4015
|
+
completedAt: reattach.completedAt
|
|
4016
|
+
});
|
|
4017
|
+
return reattach.result;
|
|
4018
|
+
}
|
|
4019
|
+
} catch {}
|
|
4020
|
+
return await this._replayAndInterruptAgentToolRun(existing, "Agent tool run was still running and could not be re-attached to a terminal result within the recovery budget.");
|
|
3798
4021
|
}
|
|
3799
4022
|
const displayOrder = options.displayOrder ?? 0;
|
|
3800
4023
|
const inputPreview = options.inputPreview ?? this._defaultAgentToolPreview(options.input);
|
|
@@ -4074,7 +4297,7 @@ var Agent = class Agent extends Server {
|
|
|
4074
4297
|
error_message = ${result.error ?? null},
|
|
4075
4298
|
completed_at = ${completedAt}
|
|
4076
4299
|
WHERE run_id = ${runId}
|
|
4077
|
-
AND status NOT IN ('completed', 'error', 'aborted'
|
|
4300
|
+
AND status NOT IN ('completed', 'error', 'aborted')
|
|
4078
4301
|
`;
|
|
4079
4302
|
if (result.status === "completed" && result.output !== void 0) this.sql`
|
|
4080
4303
|
UPDATE cf_agent_tool_runs
|
|
@@ -4126,7 +4349,12 @@ var Agent = class Agent extends Server {
|
|
|
4126
4349
|
}
|
|
4127
4350
|
async _broadcastAgentToolStoredChunks(row, sequence, replay, connection) {
|
|
4128
4351
|
const child = await this._cf_resolveSubAgent(row.agent_type, row.run_id);
|
|
4129
|
-
const
|
|
4352
|
+
const adapter = this._asAgentToolChildAdapter(child);
|
|
4353
|
+
return this._broadcastAgentToolStoredChunksFromAdapter(adapter, row, sequence, replay, connection);
|
|
4354
|
+
}
|
|
4355
|
+
async _broadcastAgentToolStoredChunksFromAdapter(adapter, row, sequence, replay, connection, timeoutMs) {
|
|
4356
|
+
const chunks = await this._getAgentToolChunksForRecovery(adapter, row.run_id, timeoutMs);
|
|
4357
|
+
if (!chunks) return sequence;
|
|
4130
4358
|
return this._broadcastAgentToolChunks(row.parent_tool_call_id ?? void 0, row.run_id, chunks, sequence, replay, connection);
|
|
4131
4359
|
}
|
|
4132
4360
|
async _forwardAgentToolStream(stream, parentToolCallId, runId, sequence, signal) {
|
|
@@ -4135,11 +4363,17 @@ var Agent = class Agent extends Server {
|
|
|
4135
4363
|
const reader = stream.getReader();
|
|
4136
4364
|
const decoder = new TextDecoder();
|
|
4137
4365
|
let bufferedBytes = "";
|
|
4366
|
+
let aborted = false;
|
|
4367
|
+
let resolveAbort;
|
|
4368
|
+
const abortPromise = new Promise((resolve) => {
|
|
4369
|
+
resolveAbort = resolve;
|
|
4370
|
+
});
|
|
4138
4371
|
let abortListener;
|
|
4139
4372
|
if (signal) {
|
|
4140
|
-
abortListener = () =>
|
|
4373
|
+
abortListener = () => resolveAbort?.();
|
|
4141
4374
|
signal.addEventListener("abort", abortListener, { once: true });
|
|
4142
4375
|
}
|
|
4376
|
+
let forwardedSinceProgress = false;
|
|
4143
4377
|
try {
|
|
4144
4378
|
const forwardChunk = (chunk) => {
|
|
4145
4379
|
this._broadcastAgentToolEvent(parentToolCallId, next++, {
|
|
@@ -4147,6 +4381,7 @@ var Agent = class Agent extends Server {
|
|
|
4147
4381
|
runId,
|
|
4148
4382
|
body: chunk.body
|
|
4149
4383
|
});
|
|
4384
|
+
forwardedSinceProgress = true;
|
|
4150
4385
|
};
|
|
4151
4386
|
const forwardLine = (line) => {
|
|
4152
4387
|
try {
|
|
@@ -4168,14 +4403,17 @@ var Agent = class Agent extends Server {
|
|
|
4168
4403
|
}
|
|
4169
4404
|
};
|
|
4170
4405
|
while (true) {
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4406
|
+
const readPromise = reader.read();
|
|
4407
|
+
readPromise.catch(() => {});
|
|
4408
|
+
const raced = await Promise.race([readPromise.then((result) => ({
|
|
4409
|
+
kind: "read",
|
|
4410
|
+
result
|
|
4411
|
+
})), abortPromise.then(() => ({ kind: "abort" }))]);
|
|
4412
|
+
if (raced.kind === "abort") {
|
|
4413
|
+
aborted = true;
|
|
4414
|
+
break;
|
|
4177
4415
|
}
|
|
4178
|
-
const { done, value } =
|
|
4416
|
+
const { done, value } = raced.result;
|
|
4179
4417
|
if (done) {
|
|
4180
4418
|
bufferedBytes += decoder.decode();
|
|
4181
4419
|
flushBufferedBytes(true);
|
|
@@ -4185,13 +4423,42 @@ var Agent = class Agent extends Server {
|
|
|
4185
4423
|
bufferedBytes += decoder.decode(value, { stream: true });
|
|
4186
4424
|
flushBufferedBytes();
|
|
4187
4425
|
} else forwardChunk(value);
|
|
4426
|
+
if (forwardedSinceProgress) {
|
|
4427
|
+
forwardedSinceProgress = false;
|
|
4428
|
+
try {
|
|
4429
|
+
await this._onAgentToolStreamProgress();
|
|
4430
|
+
} catch {}
|
|
4431
|
+
}
|
|
4188
4432
|
}
|
|
4189
4433
|
} finally {
|
|
4190
4434
|
if (abortListener && signal) signal.removeEventListener("abort", abortListener);
|
|
4191
|
-
|
|
4435
|
+
if (!aborted) try {
|
|
4436
|
+
reader.releaseLock();
|
|
4437
|
+
} catch {}
|
|
4192
4438
|
}
|
|
4193
4439
|
return next;
|
|
4194
4440
|
}
|
|
4441
|
+
/**
|
|
4442
|
+
* Hook invoked by `_forwardAgentToolStream` after a child produces output that
|
|
4443
|
+
* was forwarded to the parent's connections. Forwarding a sub-agent's stream
|
|
4444
|
+
* is genuine forward progress for the *parent* turn (the parent is
|
|
4445
|
+
* orchestrating the child), so chat-recovery subclasses (Think / AIChatAgent)
|
|
4446
|
+
* override this to advance their recovery progress marker.
|
|
4447
|
+
*
|
|
4448
|
+
* Without it, a parent whose turn merely `await`s a sub-agent banks zero
|
|
4449
|
+
* progress of its own, so under deploy churn the parent's no-progress recovery
|
|
4450
|
+
* window exhausts and abandons the turn as `interrupted` — even though the
|
|
4451
|
+
* child is healthily streaming and ultimately completes (observed in the
|
|
4452
|
+
* `deploy-churn --mode subagent` harness: `attempt 6/6, stable_timeout,
|
|
4453
|
+
* progress: 1`).
|
|
4454
|
+
*
|
|
4455
|
+
* Called ONLY after at least one chunk was actually forwarded — never merely
|
|
4456
|
+
* because a child is attached — so a silent / hung child still lets the parent
|
|
4457
|
+
* exhaust on its own timer. The base Agent has no recovery budget, so this is
|
|
4458
|
+
* a no-op; subclasses should throttle the (durable) bump since this can be
|
|
4459
|
+
* called repeatedly while a child streams.
|
|
4460
|
+
*/
|
|
4461
|
+
async _onAgentToolStreamProgress() {}
|
|
4195
4462
|
_broadcastAgentToolTerminal(parentToolCallId, sequence, result, replay, connection) {
|
|
4196
4463
|
if (result.status === "completed") this._broadcastAgentToolEvent(parentToolCallId, sequence, {
|
|
4197
4464
|
kind: "finished",
|
|
@@ -4238,6 +4505,58 @@ var Agent = class Agent extends Server {
|
|
|
4238
4505
|
await this._finishAgentToolRun(this._agentToolRunInfoFromRow(row), result, { sequence });
|
|
4239
4506
|
return result;
|
|
4240
4507
|
}
|
|
4508
|
+
/**
|
|
4509
|
+
* Re-attach to a still-running child agent-tool run and tail it to its real
|
|
4510
|
+
* terminal result, instead of abandoning it as `interrupted` (#1630). The
|
|
4511
|
+
* child is a separate facet with its own `chatRecovery`, so resolving it via
|
|
4512
|
+
* the adapter wakes it and lets it self-complete the interrupted turn; we tail
|
|
4513
|
+
* its live stream (forwarding chunks to the parent's connections) until it
|
|
4514
|
+
* reaches terminal, then inspect for the collected result.
|
|
4515
|
+
*
|
|
4516
|
+
* Bounded by {@link DEFAULT_AGENT_TOOL_REATTACH_TIMEOUT_MS}: a child that keeps
|
|
4517
|
+
* advancing toward terminal within the window is collected; a genuinely hung
|
|
4518
|
+
* child returns `{ result: undefined }` once the budget elapses so the caller
|
|
4519
|
+
* falls back to `interrupted` and recovery can never block forever.
|
|
4520
|
+
*
|
|
4521
|
+
* Returns the terminal `result` (and `completedAt`) when the child reaches a
|
|
4522
|
+
* terminal status within the budget, plus the advanced broadcast `sequence`.
|
|
4523
|
+
* Returns `{ result: undefined }` when there is no `tailAgentToolRun` adapter,
|
|
4524
|
+
* the budget is exhausted, or the child is still non-terminal.
|
|
4525
|
+
*/
|
|
4526
|
+
async _reattachAgentToolRunToTerminal(adapter, row, sequence, budgetMs = DEFAULT_AGENT_TOOL_REATTACH_TIMEOUT_MS) {
|
|
4527
|
+
if (typeof adapter.tailAgentToolRun !== "function") return { sequence };
|
|
4528
|
+
const controller = new AbortController();
|
|
4529
|
+
let timeoutId;
|
|
4530
|
+
if (budgetMs > 0 && Number.isFinite(budgetMs)) timeoutId = setTimeout(() => controller.abort(), budgetMs);
|
|
4531
|
+
else if (budgetMs <= 0) controller.abort();
|
|
4532
|
+
this._emit("agent_tool:recovery:reattach", {
|
|
4533
|
+
runId: row.run_id,
|
|
4534
|
+
agentType: row.agent_type,
|
|
4535
|
+
budgetMs
|
|
4536
|
+
});
|
|
4537
|
+
let nextSequence = sequence;
|
|
4538
|
+
if (!controller.signal.aborted) try {
|
|
4539
|
+
let afterSequence = -1;
|
|
4540
|
+
try {
|
|
4541
|
+
const existing = await adapter.getAgentToolChunks(row.run_id);
|
|
4542
|
+
const last = existing[existing.length - 1];
|
|
4543
|
+
if (last) afterSequence = last.sequence;
|
|
4544
|
+
} catch {}
|
|
4545
|
+
const stream = await adapter.tailAgentToolRun(row.run_id, { afterSequence });
|
|
4546
|
+
nextSequence = await this._forwardAgentToolStream(stream, row.parent_tool_call_id ?? void 0, row.run_id, nextSequence, controller.signal);
|
|
4547
|
+
} catch {}
|
|
4548
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
4549
|
+
let inspection = null;
|
|
4550
|
+
try {
|
|
4551
|
+
inspection = await adapter.inspectAgentToolRun(row.run_id);
|
|
4552
|
+
} catch {}
|
|
4553
|
+
if (inspection && inspection.status !== "running" && inspection.status !== "starting") return {
|
|
4554
|
+
sequence: nextSequence,
|
|
4555
|
+
result: this._terminalResultFromInspection(row.agent_type, inspection),
|
|
4556
|
+
completedAt: inspection.completedAt
|
|
4557
|
+
};
|
|
4558
|
+
return { sequence: nextSequence };
|
|
4559
|
+
}
|
|
4241
4560
|
async _replayAgentToolRuns(connection) {
|
|
4242
4561
|
const rows = this.sql`
|
|
4243
4562
|
SELECT run_id, parent_tool_call_id, agent_type, input_preview, status,
|
|
@@ -4270,6 +4589,10 @@ var Agent = class Agent extends Server {
|
|
|
4270
4589
|
}
|
|
4271
4590
|
}
|
|
4272
4591
|
async _reconcileAgentToolRuns(options) {
|
|
4592
|
+
const reattachTimeoutMs = options?.reattachTimeoutMs ?? DEFAULT_AGENT_TOOL_REATTACH_TIMEOUT_MS;
|
|
4593
|
+
const startedAt = Date.now();
|
|
4594
|
+
const totalTimeoutMs = options?.totalRecoveryTimeoutMs ?? DEFAULT_AGENT_TOOL_RECOVERY_TOTAL_TIMEOUT_MS;
|
|
4595
|
+
const deadlineAt = totalTimeoutMs > 0 ? startedAt + totalTimeoutMs : Number.POSITIVE_INFINITY;
|
|
4273
4596
|
const deferredFinishes = [];
|
|
4274
4597
|
const rows = this.sql`
|
|
4275
4598
|
SELECT run_id, parent_tool_call_id, agent_type, input_preview, status,
|
|
@@ -4279,43 +4602,158 @@ var Agent = class Agent extends Server {
|
|
|
4279
4602
|
WHERE status IN ('starting', 'running')
|
|
4280
4603
|
ORDER BY started_at ASC
|
|
4281
4604
|
`;
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4605
|
+
const runIds = options?.runIds !== void 0 ? new Set(options.runIds) : void 0;
|
|
4606
|
+
const recoveryRows = rows.filter((row) => !runIds || runIds.has(row.run_id));
|
|
4607
|
+
this._emit("agent_tool:recovery:begin", {
|
|
4608
|
+
runCount: recoveryRows.length,
|
|
4609
|
+
totalTimeoutMs
|
|
4610
|
+
});
|
|
4611
|
+
const finalizeRow = async (row, result, sequence, completedAt) => {
|
|
4612
|
+
this._emit("agent_tool:recovery:row", {
|
|
4613
|
+
runId: row.run_id,
|
|
4614
|
+
agentType: row.agent_type,
|
|
4615
|
+
status: result.status,
|
|
4616
|
+
reason: result.error,
|
|
4617
|
+
elapsedMs: Date.now() - startedAt
|
|
4618
|
+
});
|
|
4619
|
+
const deferredFinish = await this._finishAgentToolRun(this._agentToolRunInfoFromRow(row), result, {
|
|
4620
|
+
sequence,
|
|
4621
|
+
completedAt,
|
|
4622
|
+
deferFinishHook: options?.deferFinishHooks
|
|
4623
|
+
});
|
|
4624
|
+
if (deferredFinish) deferredFinishes.push(deferredFinish);
|
|
4625
|
+
};
|
|
4626
|
+
const reattachQueue = [];
|
|
4627
|
+
for (const row of recoveryRows) {
|
|
4628
|
+
const sequence = 1;
|
|
4629
|
+
const remainingMs = deadlineAt - Date.now();
|
|
4630
|
+
if (remainingMs <= 0) {
|
|
4631
|
+
this._emit("agent_tool:recovery:deadline", {
|
|
4632
|
+
runId: row.run_id,
|
|
4633
|
+
agentType: row.agent_type,
|
|
4634
|
+
elapsedMs: Date.now() - startedAt
|
|
4635
|
+
});
|
|
4636
|
+
await finalizeRow(row, {
|
|
4293
4637
|
runId: row.run_id,
|
|
4294
4638
|
agentType: row.agent_type,
|
|
4295
4639
|
status: "interrupted",
|
|
4296
|
-
error: "Agent tool run
|
|
4297
|
-
};
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4640
|
+
error: "Agent tool run recovery deadline exceeded."
|
|
4641
|
+
}, sequence, void 0);
|
|
4642
|
+
continue;
|
|
4643
|
+
}
|
|
4644
|
+
const childTimeout = options?.childInspectionTimeoutMs ?? DEFAULT_AGENT_TOOL_RECOVERY_TIMEOUT_MS;
|
|
4645
|
+
const boundedChildTimeout = childTimeout > 0 ? Math.min(childTimeout, remainingMs) : remainingMs;
|
|
4646
|
+
const recovery = await this._inspectAgentToolRunForRecovery(row, sequence, boundedChildTimeout);
|
|
4647
|
+
if (recovery.status !== "inspected") {
|
|
4648
|
+
await finalizeRow(row, {
|
|
4304
4649
|
runId: row.run_id,
|
|
4305
4650
|
agentType: row.agent_type,
|
|
4306
4651
|
status: "interrupted",
|
|
4307
|
-
error: "Agent tool run could not be inspected during parent recovery."
|
|
4308
|
-
};
|
|
4652
|
+
error: recovery.status === "timed-out" ? "Agent tool run inspection timed out during parent recovery." : "Agent tool run could not be inspected during parent recovery."
|
|
4653
|
+
}, sequence, void 0);
|
|
4654
|
+
continue;
|
|
4309
4655
|
}
|
|
4310
|
-
const
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4656
|
+
const inspection = recovery.inspection;
|
|
4657
|
+
const stillRunning = !inspection || inspection.status === "running" || inspection.status === "starting";
|
|
4658
|
+
if (stillRunning && typeof recovery.adapter.tailAgentToolRun === "function") {
|
|
4659
|
+
reattachQueue.push({
|
|
4660
|
+
row,
|
|
4661
|
+
adapter: recovery.adapter
|
|
4662
|
+
});
|
|
4663
|
+
continue;
|
|
4664
|
+
}
|
|
4665
|
+
let sequenceAfterReplay = sequence;
|
|
4666
|
+
try {
|
|
4667
|
+
sequenceAfterReplay = await this._broadcastAgentToolStoredChunksFromAdapter(recovery.adapter, row, sequence, void 0, void 0, boundedChildTimeout);
|
|
4668
|
+
} catch {}
|
|
4669
|
+
if (stillRunning) await finalizeRow(row, {
|
|
4670
|
+
runId: row.run_id,
|
|
4671
|
+
agentType: row.agent_type,
|
|
4672
|
+
status: "interrupted",
|
|
4673
|
+
error: "Agent tool run was still running, but live-tail reattachment is not supported in this runtime."
|
|
4674
|
+
}, sequenceAfterReplay, void 0);
|
|
4675
|
+
else await finalizeRow(row, this._terminalResultFromInspection(row.agent_type, inspection), sequenceAfterReplay, inspection.completedAt);
|
|
4316
4676
|
}
|
|
4677
|
+
await Promise.all(reattachQueue.map(async ({ row, adapter }) => {
|
|
4678
|
+
const reattach = await this._reattachAgentToolRunToTerminal(adapter, row, 1, reattachTimeoutMs);
|
|
4679
|
+
await finalizeRow(row, reattach.result ?? {
|
|
4680
|
+
runId: row.run_id,
|
|
4681
|
+
agentType: row.agent_type,
|
|
4682
|
+
status: "interrupted",
|
|
4683
|
+
error: "Agent tool run was still running and did not reach a terminal result within the re-attach budget."
|
|
4684
|
+
}, reattach.sequence, reattach.completedAt);
|
|
4685
|
+
}));
|
|
4686
|
+
this._emit("agent_tool:recovery:complete", {
|
|
4687
|
+
runCount: recoveryRows.length,
|
|
4688
|
+
elapsedMs: Date.now() - startedAt
|
|
4689
|
+
});
|
|
4317
4690
|
return deferredFinishes;
|
|
4318
4691
|
}
|
|
4692
|
+
async _inspectAgentToolRunForRecovery(row, _sequence, timeoutMs = DEFAULT_AGENT_TOOL_RECOVERY_TIMEOUT_MS) {
|
|
4693
|
+
const inspect = (async () => {
|
|
4694
|
+
const child = await this._cf_resolveSubAgent(row.agent_type, row.run_id);
|
|
4695
|
+
const adapter = this._asAgentToolChildAdapter(child);
|
|
4696
|
+
return {
|
|
4697
|
+
status: "inspected",
|
|
4698
|
+
adapter,
|
|
4699
|
+
inspection: await adapter.inspectAgentToolRun(row.run_id)
|
|
4700
|
+
};
|
|
4701
|
+
})().catch(() => ({ status: "failed" }));
|
|
4702
|
+
if (timeoutMs <= 0) return inspect;
|
|
4703
|
+
let timeoutId;
|
|
4704
|
+
const timeout = new Promise((resolve) => {
|
|
4705
|
+
timeoutId = setTimeout(() => {
|
|
4706
|
+
resolve({ status: "timed-out" });
|
|
4707
|
+
}, timeoutMs);
|
|
4708
|
+
});
|
|
4709
|
+
const result = await Promise.race([inspect, timeout]);
|
|
4710
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
4711
|
+
return result;
|
|
4712
|
+
}
|
|
4713
|
+
_scheduleAgentToolRunRecovery(options) {
|
|
4714
|
+
if (this._agentToolRunRecoveryPromise) return this._agentToolRunRecoveryPromise;
|
|
4715
|
+
if (options?.runIds && options.runIds.length === 0) return Promise.resolve();
|
|
4716
|
+
const recovery = (async () => {
|
|
4717
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
4718
|
+
const recoveredAgentToolFinishes = await this._reconcileAgentToolRuns({
|
|
4719
|
+
deferFinishHooks: true,
|
|
4720
|
+
childInspectionTimeoutMs: options?.childInspectionTimeoutMs,
|
|
4721
|
+
totalRecoveryTimeoutMs: options?.totalRecoveryTimeoutMs,
|
|
4722
|
+
reattachTimeoutMs: options?.reattachTimeoutMs,
|
|
4723
|
+
runIds: options?.runIds
|
|
4724
|
+
});
|
|
4725
|
+
await this._runDeferredAgentToolFinishHooks(recoveredAgentToolFinishes);
|
|
4726
|
+
})().catch(async (error) => {
|
|
4727
|
+
this._emit("agent_tool:recovery:failed", { error: error instanceof Error ? error.message : String(error) });
|
|
4728
|
+
try {
|
|
4729
|
+
await this.onError(error);
|
|
4730
|
+
} catch {}
|
|
4731
|
+
}).finally(() => {
|
|
4732
|
+
this._agentToolRunRecoveryPromise = void 0;
|
|
4733
|
+
});
|
|
4734
|
+
this._agentToolRunRecoveryPromise = recovery;
|
|
4735
|
+
this.ctx.waitUntil(recovery);
|
|
4736
|
+
return recovery;
|
|
4737
|
+
}
|
|
4738
|
+
_agentToolRunRecoveryRunIds() {
|
|
4739
|
+
return this.sql`
|
|
4740
|
+
SELECT run_id
|
|
4741
|
+
FROM cf_agent_tool_runs
|
|
4742
|
+
WHERE status IN ('starting', 'running')
|
|
4743
|
+
ORDER BY started_at ASC
|
|
4744
|
+
`.map((row) => row.run_id);
|
|
4745
|
+
}
|
|
4746
|
+
async _getAgentToolChunksForRecovery(adapter, runId, timeoutMs) {
|
|
4747
|
+
const chunks = adapter.getAgentToolChunks(runId).catch(() => void 0);
|
|
4748
|
+
if (timeoutMs === void 0 || timeoutMs <= 0) return chunks;
|
|
4749
|
+
let timeoutId;
|
|
4750
|
+
const timeout = new Promise((resolve) => {
|
|
4751
|
+
timeoutId = setTimeout(() => resolve(void 0), timeoutMs);
|
|
4752
|
+
});
|
|
4753
|
+
const result = await Promise.race([chunks, timeout]);
|
|
4754
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
4755
|
+
return result;
|
|
4756
|
+
}
|
|
4319
4757
|
/**
|
|
4320
4758
|
* Shared facet resolution — takes a CamelCase class name string
|
|
4321
4759
|
* (matching `ctx.exports`) rather than a class reference. Both
|
|
@@ -4333,27 +4771,37 @@ var Agent = class Agent extends Server {
|
|
|
4333
4771
|
if (!Cls) throw new Error(`Sub-agent class "${className}" not found in worker exports. Make sure the class is exported from your worker entry point and that the export name matches the class name.`);
|
|
4334
4772
|
if (name.includes("\0")) throw new Error(`Sub-agent name contains null character (\\0), which is reserved.`);
|
|
4335
4773
|
const facetKey = `${className}\0${name}`;
|
|
4774
|
+
const childParentPath = this.selfPath;
|
|
4775
|
+
const childPath = [...childParentPath, {
|
|
4776
|
+
className,
|
|
4777
|
+
name
|
|
4778
|
+
}];
|
|
4336
4779
|
const rootClassName = this._parentPath[0]?.className ?? this.constructor.name;
|
|
4337
4780
|
const rootNs = ctx.exports[rootClassName];
|
|
4338
4781
|
if (!rootNs?.idFromName) {
|
|
4339
4782
|
const minificationHint = /^_*[a-z][a-z0-9]{0,2}$/.test(rootClassName) ? ` The class name "${rootClassName}" looks minified — make sure your bundler preserves class names (e.g. esbuild's \`keepNames: true\`).` : "";
|
|
4340
4783
|
throw new Error(`Sub-agent bootstrap requires the root agent class "${rootClassName}" to be available as a Durable Object namespace, but ctx.exports["${rootClassName}"] is missing or doesn't expose idFromName.${minificationHint} Make sure the root agent class is exported under that class name and registered in your wrangler.jsonc durable_objects.bindings.`);
|
|
4341
4784
|
}
|
|
4342
|
-
const
|
|
4785
|
+
const identity = await this._cf_subAgentIdentity(className, name, childPath);
|
|
4786
|
+
const facetId = rootNs.idFromName(identity.name);
|
|
4343
4787
|
const stub = ctx.facets.get(facetKey, () => ({
|
|
4344
4788
|
class: Cls,
|
|
4345
4789
|
id: facetId
|
|
4346
4790
|
}));
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4791
|
+
this._recordSubAgent(className, name, identity);
|
|
4792
|
+
try {
|
|
4793
|
+
await __DO_NOT_USE_WILL_BREAK__agentContext.run({
|
|
4794
|
+
agent: this,
|
|
4795
|
+
connection: void 0,
|
|
4796
|
+
request: void 0,
|
|
4797
|
+
email: void 0
|
|
4798
|
+
}, async () => {
|
|
4799
|
+
await stub._cf_initAsFacet(name, childParentPath, identity.name);
|
|
4800
|
+
});
|
|
4801
|
+
} catch (error) {
|
|
4802
|
+
if (!identity.existing) this._forgetSubAgent(className, name);
|
|
4803
|
+
throw error;
|
|
4804
|
+
}
|
|
4357
4805
|
return stub;
|
|
4358
4806
|
}
|
|
4359
4807
|
/**
|
|
@@ -4398,6 +4846,13 @@ var Agent = class Agent extends Server {
|
|
|
4398
4846
|
} catch {}
|
|
4399
4847
|
this._forgetSubAgent(cls.name, name);
|
|
4400
4848
|
}
|
|
4849
|
+
_addColumnIfNotExists(sql) {
|
|
4850
|
+
try {
|
|
4851
|
+
this.ctx.storage.sql.exec(sql);
|
|
4852
|
+
} catch (e) {
|
|
4853
|
+
if (!(e instanceof Error ? e.message : String(e)).toLowerCase().includes("duplicate column")) throw e;
|
|
4854
|
+
}
|
|
4855
|
+
}
|
|
4401
4856
|
/** @internal */
|
|
4402
4857
|
_ensureSubAgentRegistry() {
|
|
4403
4858
|
if (this._subAgentRegistryReady) return;
|
|
@@ -4406,20 +4861,56 @@ var Agent = class Agent extends Server {
|
|
|
4406
4861
|
class TEXT NOT NULL,
|
|
4407
4862
|
name TEXT NOT NULL,
|
|
4408
4863
|
created_at INTEGER NOT NULL,
|
|
4864
|
+
identity_version TEXT,
|
|
4865
|
+
identity_name TEXT,
|
|
4409
4866
|
PRIMARY KEY (class, name)
|
|
4410
4867
|
)
|
|
4411
4868
|
`;
|
|
4869
|
+
this._addColumnIfNotExists("ALTER TABLE cf_agents_sub_agents ADD COLUMN identity_version TEXT");
|
|
4870
|
+
this._addColumnIfNotExists("ALTER TABLE cf_agents_sub_agents ADD COLUMN identity_name TEXT");
|
|
4412
4871
|
this._subAgentRegistryReady = true;
|
|
4413
4872
|
}
|
|
4414
4873
|
/** @internal */
|
|
4415
|
-
_recordSubAgent(className, name) {
|
|
4874
|
+
_recordSubAgent(className, name, identity) {
|
|
4416
4875
|
this._ensureSubAgentRegistry();
|
|
4417
4876
|
this.sql`
|
|
4418
|
-
INSERT OR IGNORE INTO cf_agents_sub_agents
|
|
4419
|
-
|
|
4877
|
+
INSERT OR IGNORE INTO cf_agents_sub_agents
|
|
4878
|
+
(class, name, created_at, identity_version, identity_name)
|
|
4879
|
+
VALUES
|
|
4880
|
+
(${className}, ${name}, ${Date.now()}, ${identity.version}, ${identity.name})
|
|
4420
4881
|
`;
|
|
4421
4882
|
}
|
|
4422
4883
|
/** @internal */
|
|
4884
|
+
_subAgentRegistryRow(className, name) {
|
|
4885
|
+
this._ensureSubAgentRegistry();
|
|
4886
|
+
return this.sql`
|
|
4887
|
+
SELECT identity_version, identity_name
|
|
4888
|
+
FROM cf_agents_sub_agents
|
|
4889
|
+
WHERE class = ${className} AND name = ${name}
|
|
4890
|
+
LIMIT 1
|
|
4891
|
+
`[0] ?? null;
|
|
4892
|
+
}
|
|
4893
|
+
async _cf_subAgentIdentity(className, name, childPath) {
|
|
4894
|
+
const row = this._subAgentRegistryRow(className, name);
|
|
4895
|
+
if (row) {
|
|
4896
|
+
if (row.identity_version === SUB_AGENT_IDENTITY_VERSION_PATH_V2 && typeof row.identity_name === "string") return {
|
|
4897
|
+
version: SUB_AGENT_IDENTITY_VERSION_PATH_V2,
|
|
4898
|
+
name: row.identity_name,
|
|
4899
|
+
existing: true
|
|
4900
|
+
};
|
|
4901
|
+
return {
|
|
4902
|
+
version: SUB_AGENT_IDENTITY_VERSION_LEGACY,
|
|
4903
|
+
name,
|
|
4904
|
+
existing: true
|
|
4905
|
+
};
|
|
4906
|
+
}
|
|
4907
|
+
return {
|
|
4908
|
+
version: SUB_AGENT_IDENTITY_VERSION_PATH_V2,
|
|
4909
|
+
name: pathV2IdentityName(name, await sha256Hex(JSON.stringify(childPath))),
|
|
4910
|
+
existing: false
|
|
4911
|
+
};
|
|
4912
|
+
}
|
|
4913
|
+
/** @internal */
|
|
4423
4914
|
_forgetSubAgent(className, name) {
|
|
4424
4915
|
this._ensureSubAgentRegistry();
|
|
4425
4916
|
this.sql`
|
|
@@ -4552,7 +5043,7 @@ var Agent = class Agent extends Server {
|
|
|
4552
5043
|
if (!workflow) throw new Error(`Workflow binding '${workflowName}' not found in environment`);
|
|
4553
5044
|
const agentBindingName = options?.agentBinding ?? this._findAgentBindingName();
|
|
4554
5045
|
if (!agentBindingName) throw new Error("Could not detect Agent binding name from class name. Pass it explicitly via options.agentBinding");
|
|
4555
|
-
const workflowId = options?.id ?? nanoid()
|
|
5046
|
+
const workflowId = options?.id ?? `wf_${nanoid()}`;
|
|
4556
5047
|
const augmentedParams = {
|
|
4557
5048
|
...params,
|
|
4558
5049
|
__agentName: this.name,
|
|
@@ -5254,7 +5745,26 @@ var Agent = class Agent extends Server {
|
|
|
5254
5745
|
async addMcpServer(serverName, urlOrBinding, callbackHostOrOptions, agentsPrefix, options) {
|
|
5255
5746
|
const isHttpTransport = typeof urlOrBinding === "string";
|
|
5256
5747
|
const normalizedUrl = isHttpTransport ? new URL(urlOrBinding).href : void 0;
|
|
5257
|
-
|
|
5748
|
+
let requestedId;
|
|
5749
|
+
if (typeof callbackHostOrOptions === "object" && callbackHostOrOptions !== null && typeof callbackHostOrOptions.id === "string") {
|
|
5750
|
+
const rawId = callbackHostOrOptions.id;
|
|
5751
|
+
requestedId = normalizeServerId(rawId);
|
|
5752
|
+
}
|
|
5753
|
+
const allServers = this.mcp.listServers();
|
|
5754
|
+
const existingServer = allServers.find((s) => s.name === serverName && (!isHttpTransport || new URL(s.server_url).href === normalizedUrl));
|
|
5755
|
+
if (requestedId) {
|
|
5756
|
+
const idConflict = allServers.find((s) => {
|
|
5757
|
+
if (s.id !== requestedId) return false;
|
|
5758
|
+
if (s.name !== serverName) return true;
|
|
5759
|
+
if (isHttpTransport) return new URL(s.server_url).href !== normalizedUrl;
|
|
5760
|
+
return false;
|
|
5761
|
+
});
|
|
5762
|
+
if (idConflict) throw new Error(`MCP server id "${requestedId}" is already in use by server "${idConflict.name}" (${idConflict.server_url}). Stable ids must be unique per (name, url).`);
|
|
5763
|
+
if (existingServer && existingServer.id !== requestedId) {
|
|
5764
|
+
await this.mcp.migrateServerId(existingServer.id, requestedId, this.name);
|
|
5765
|
+
existingServer.id = requestedId;
|
|
5766
|
+
}
|
|
5767
|
+
}
|
|
5258
5768
|
if (existingServer && this.mcp.mcpConnections[existingServer.id]) {
|
|
5259
5769
|
const conn = this.mcp.mcpConnections[existingServer.id];
|
|
5260
5770
|
if (conn.connectionState === MCPConnectionState.AUTHENTICATING && conn.options.transport.authProvider?.authUrl) return {
|
|
@@ -5271,7 +5781,7 @@ var Agent = class Agent extends Server {
|
|
|
5271
5781
|
if (typeof urlOrBinding !== "string") {
|
|
5272
5782
|
const rpcOpts = callbackHostOrOptions;
|
|
5273
5783
|
const normalizedName = serverName.toLowerCase().replace(/\s+/g, "-");
|
|
5274
|
-
const reconnectId = existingServer?.id;
|
|
5784
|
+
const reconnectId = requestedId ?? existingServer?.id;
|
|
5275
5785
|
const { id } = await this.mcp.connect(`${RPC_DO_PREFIX}${normalizedName}`, {
|
|
5276
5786
|
reconnect: reconnectId ? { id: reconnectId } : void 0,
|
|
5277
5787
|
transport: {
|
|
@@ -5328,7 +5838,7 @@ var Agent = class Agent extends Server {
|
|
|
5328
5838
|
const normalizedHost = resolvedCallbackHost.replace(/\/$/, "");
|
|
5329
5839
|
callbackUrl = resolvedCallbackPath ? `${normalizedHost}/${resolvedCallbackPath.replace(/^\//, "")}` : `${normalizedHost}/${resolvedAgentsPrefix}/${camelCaseToKebabCase(this._ParentClass.name)}/${this.name}/callback`;
|
|
5330
5840
|
}
|
|
5331
|
-
const id = nanoid(8);
|
|
5841
|
+
const id = requestedId ?? nanoid(8);
|
|
5332
5842
|
let authProvider;
|
|
5333
5843
|
if (callbackUrl) {
|
|
5334
5844
|
authProvider = this.createMcpOAuthProvider(callbackUrl);
|
|
@@ -5660,6 +6170,6 @@ var StreamingResponse = class {
|
|
|
5660
6170
|
}
|
|
5661
6171
|
};
|
|
5662
6172
|
//#endregion
|
|
5663
|
-
export { Agent, DEFAULT_AGENT_STATIC_OPTIONS, DurableObjectOAuthClientProvider, MessageType, SUB_PREFIX, SqlError, StreamingResponse, __DO_NOT_USE_WILL_BREAK__agentContext, callable, createHeaderBasedEmailResolver, getAgentByName, getCurrentAgent, getSubAgentByName, parseSubAgentPath, routeAgentEmail, routeAgentRequest, routeSubAgentRequest, unstable_callable };
|
|
6173
|
+
export { Agent, DEFAULT_AGENT_STATIC_OPTIONS, DurableObjectOAuthClientProvider, MCP_SERVER_ID_MAX_LENGTH, MessageType, SUB_PREFIX, SqlError, StreamingResponse, __DO_NOT_USE_WILL_BREAK__agentContext, callable, createHeaderBasedEmailResolver, getAgentByName, getCurrentAgent, getSubAgentByName, normalizeServerId, parseSubAgentPath, routeAgentEmail, routeAgentRequest, routeSubAgentRequest, unstable_callable };
|
|
5664
6174
|
|
|
5665
6175
|
//# sourceMappingURL=index.js.map
|