agents 0.13.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{agent-tool-types-BVgYyKO9.d.ts → agent-tool-types-Dn9n-3SI.d.ts} +183 -68
- package/dist/agent-tool-types.d.ts +1 -1
- package/dist/{agent-tools-C-Ch8Thl.d.ts → agent-tools-B1ttU-pq.d.ts} +2 -2
- package/dist/agent-tools.d.ts +1 -1
- package/dist/chat/index.d.ts +2 -2
- package/dist/client.d.ts +1 -1
- package/dist/index.d.ts +47 -33
- package/dist/index.js +493 -21
- package/dist/index.js.map +1 -1
- package/dist/mcp/client.d.ts +12 -12
- package/dist/mcp/index.d.ts +26 -26
- package/dist/react.d.ts +3 -3
- package/dist/serializable.d.ts +1 -1
- package/dist/sub-routing.d.ts +6 -6
- package/dist/workflows.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -120,7 +120,7 @@ const DEFAULT_KEEP_ALIVE_INTERVAL_MS = 3e4;
|
|
|
120
120
|
* The constructor stores this as a row in cf_agents_state and checks it
|
|
121
121
|
* on wake to skip DDL on established DOs.
|
|
122
122
|
*/
|
|
123
|
-
const CURRENT_SCHEMA_VERSION =
|
|
123
|
+
const CURRENT_SCHEMA_VERSION = 8;
|
|
124
124
|
const SCHEMA_VERSION_ROW_ID = "cf_schema_version";
|
|
125
125
|
const STATE_ROW_ID = "cf_state_row_id";
|
|
126
126
|
const STATE_WAS_CHANGED = "cf_state_was_changed";
|
|
@@ -549,6 +549,32 @@ var Agent = class Agent extends Server {
|
|
|
549
549
|
this.sql`
|
|
550
550
|
CREATE INDEX IF NOT EXISTS idx_facet_runs_owner_path_key
|
|
551
551
|
ON cf_agents_facet_runs(owner_path_key)
|
|
552
|
+
`;
|
|
553
|
+
this.sql`
|
|
554
|
+
CREATE TABLE IF NOT EXISTS cf_agents_fibers (
|
|
555
|
+
fiber_id TEXT PRIMARY KEY,
|
|
556
|
+
idempotency_key TEXT UNIQUE,
|
|
557
|
+
name TEXT NOT NULL,
|
|
558
|
+
status TEXT NOT NULL,
|
|
559
|
+
snapshot TEXT,
|
|
560
|
+
metadata_json TEXT,
|
|
561
|
+
error_message TEXT,
|
|
562
|
+
created_at INTEGER NOT NULL,
|
|
563
|
+
started_at INTEGER,
|
|
564
|
+
completed_at INTEGER
|
|
565
|
+
)
|
|
566
|
+
`;
|
|
567
|
+
this.sql`
|
|
568
|
+
CREATE INDEX IF NOT EXISTS idx_fibers_status_created
|
|
569
|
+
ON cf_agents_fibers(status, created_at, fiber_id)
|
|
570
|
+
`;
|
|
571
|
+
this.sql`
|
|
572
|
+
CREATE INDEX IF NOT EXISTS idx_fibers_name_status_created
|
|
573
|
+
ON cf_agents_fibers(name, status, created_at, fiber_id)
|
|
574
|
+
`;
|
|
575
|
+
this.sql`
|
|
576
|
+
CREATE INDEX IF NOT EXISTS idx_fibers_status_completed
|
|
577
|
+
ON cf_agents_fibers(status, completed_at, created_at)
|
|
552
578
|
`;
|
|
553
579
|
this.sql`
|
|
554
580
|
CREATE TABLE IF NOT EXISTS cf_agent_tool_runs (
|
|
@@ -595,6 +621,9 @@ var Agent = class Agent extends Server {
|
|
|
595
621
|
this._keepAliveRefs = 0;
|
|
596
622
|
this._facetKeepAliveTokens = /* @__PURE__ */ new Set();
|
|
597
623
|
this._runFiberActiveFibers = /* @__PURE__ */ new Set();
|
|
624
|
+
this._managedFiberAbortControllers = /* @__PURE__ */ new Map();
|
|
625
|
+
this._managedFiberExecutions = /* @__PURE__ */ new Map();
|
|
626
|
+
this._managedFiberTerminalWaiters = /* @__PURE__ */ new Map();
|
|
598
627
|
this._runFiberRecoveryInProgress = false;
|
|
599
628
|
this._ParentClass = Object.getPrototypeOf(this).constructor;
|
|
600
629
|
this.initialState = DEFAULT_STATE;
|
|
@@ -2211,6 +2240,295 @@ var Agent = class Agent extends Server {
|
|
|
2211
2240
|
dispose();
|
|
2212
2241
|
}
|
|
2213
2242
|
}
|
|
2243
|
+
_isTerminalFiberStatus(status) {
|
|
2244
|
+
return status === "completed" || status === "aborted" || status === "interrupted" || status === "error";
|
|
2245
|
+
}
|
|
2246
|
+
_notifyManagedFiberTerminal(fiberId) {
|
|
2247
|
+
const row = this._readFiber(fiberId);
|
|
2248
|
+
if (row && !this._isTerminalFiberStatus(row.status)) return;
|
|
2249
|
+
const waiters = this._managedFiberTerminalWaiters.get(fiberId);
|
|
2250
|
+
if (!waiters) return;
|
|
2251
|
+
this._managedFiberTerminalWaiters.delete(fiberId);
|
|
2252
|
+
for (const resolve of waiters) resolve();
|
|
2253
|
+
}
|
|
2254
|
+
_waitForManagedFiberTerminal(fiberId) {
|
|
2255
|
+
const row = this._readFiber(fiberId);
|
|
2256
|
+
if (!row || this._isTerminalFiberStatus(row.status)) return Promise.resolve();
|
|
2257
|
+
return new Promise((resolve) => {
|
|
2258
|
+
let waiters = this._managedFiberTerminalWaiters.get(fiberId);
|
|
2259
|
+
if (!waiters) {
|
|
2260
|
+
waiters = /* @__PURE__ */ new Set();
|
|
2261
|
+
this._managedFiberTerminalWaiters.set(fiberId, waiters);
|
|
2262
|
+
}
|
|
2263
|
+
waiters.add(resolve);
|
|
2264
|
+
});
|
|
2265
|
+
}
|
|
2266
|
+
_normalizeFiberStatusFilter(status) {
|
|
2267
|
+
if (!status) return null;
|
|
2268
|
+
return new Set(Array.isArray(status) ? status : [status]);
|
|
2269
|
+
}
|
|
2270
|
+
_parseFiberJsonObject(value) {
|
|
2271
|
+
if (value === null) return null;
|
|
2272
|
+
try {
|
|
2273
|
+
const parsed = JSON.parse(value);
|
|
2274
|
+
if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
2275
|
+
} catch {}
|
|
2276
|
+
return null;
|
|
2277
|
+
}
|
|
2278
|
+
_parseFiberSnapshot(value) {
|
|
2279
|
+
if (value === null) return void 0;
|
|
2280
|
+
try {
|
|
2281
|
+
return JSON.parse(value);
|
|
2282
|
+
} catch {
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
_fiberErrorMessage(error) {
|
|
2287
|
+
return error instanceof Error ? error.message : String(error);
|
|
2288
|
+
}
|
|
2289
|
+
_stringifyFiberSnapshot(snapshot) {
|
|
2290
|
+
return snapshot === void 0 ? null : JSON.stringify(snapshot);
|
|
2291
|
+
}
|
|
2292
|
+
_fiberRecoveryErrorMessage(result) {
|
|
2293
|
+
if (result.status === "error") return result.error === void 0 ? null : this._fiberErrorMessage(result.error);
|
|
2294
|
+
if (result.status === "aborted" || result.status === "interrupted") return result.reason ?? null;
|
|
2295
|
+
return null;
|
|
2296
|
+
}
|
|
2297
|
+
_applyManagedFiberRecoveryResult(fiberId, result) {
|
|
2298
|
+
const completedAt = Date.now();
|
|
2299
|
+
const snapshot = this._stringifyFiberSnapshot(result.snapshot);
|
|
2300
|
+
const errorMessage = this._fiberRecoveryErrorMessage(result);
|
|
2301
|
+
const metadata = result.status === "completed" && result.metadata !== void 0 ? JSON.stringify(result.metadata) : void 0;
|
|
2302
|
+
if (metadata !== void 0) {
|
|
2303
|
+
this.sql`
|
|
2304
|
+
UPDATE cf_agents_fibers
|
|
2305
|
+
SET status = ${result.status},
|
|
2306
|
+
snapshot = COALESCE(${snapshot}, snapshot),
|
|
2307
|
+
metadata_json = ${metadata},
|
|
2308
|
+
error_message = ${errorMessage},
|
|
2309
|
+
completed_at = ${completedAt}
|
|
2310
|
+
WHERE fiber_id = ${fiberId}
|
|
2311
|
+
AND status = 'interrupted'
|
|
2312
|
+
`;
|
|
2313
|
+
this._notifyManagedFiberTerminal(fiberId);
|
|
2314
|
+
return;
|
|
2315
|
+
}
|
|
2316
|
+
this.sql`
|
|
2317
|
+
UPDATE cf_agents_fibers
|
|
2318
|
+
SET status = ${result.status},
|
|
2319
|
+
snapshot = COALESCE(${snapshot}, snapshot),
|
|
2320
|
+
error_message = ${errorMessage},
|
|
2321
|
+
completed_at = ${completedAt}
|
|
2322
|
+
WHERE fiber_id = ${fiberId}
|
|
2323
|
+
AND status = 'interrupted'
|
|
2324
|
+
`;
|
|
2325
|
+
this._notifyManagedFiberTerminal(fiberId);
|
|
2326
|
+
}
|
|
2327
|
+
_settleManagedFiberExecution(fiberId, outcome, signal) {
|
|
2328
|
+
const completedAt = Date.now();
|
|
2329
|
+
if (outcome.ok) {
|
|
2330
|
+
this.sql`
|
|
2331
|
+
UPDATE cf_agents_fibers
|
|
2332
|
+
SET status = 'completed', completed_at = ${completedAt}
|
|
2333
|
+
WHERE fiber_id = ${fiberId} AND status = 'running'
|
|
2334
|
+
`;
|
|
2335
|
+
this._notifyManagedFiberTerminal(fiberId);
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
const message = this._fiberErrorMessage(outcome.error);
|
|
2339
|
+
const status = signal.aborted ? "aborted" : "error";
|
|
2340
|
+
this.sql`
|
|
2341
|
+
UPDATE cf_agents_fibers
|
|
2342
|
+
SET status = ${status},
|
|
2343
|
+
error_message = ${message},
|
|
2344
|
+
completed_at = ${completedAt}
|
|
2345
|
+
WHERE fiber_id = ${fiberId} AND status = 'running'
|
|
2346
|
+
`;
|
|
2347
|
+
this._notifyManagedFiberTerminal(fiberId);
|
|
2348
|
+
}
|
|
2349
|
+
_parseFiberRecoverySnapshot(fiberId, snapshotText) {
|
|
2350
|
+
if (!snapshotText) return null;
|
|
2351
|
+
try {
|
|
2352
|
+
return JSON.parse(snapshotText);
|
|
2353
|
+
} catch {
|
|
2354
|
+
console.warn(`[Agent] Corrupted snapshot for fiber ${fiberId}, treating as null`);
|
|
2355
|
+
return null;
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
async _runFiberRecoveryHook(ctx, managedRow) {
|
|
2359
|
+
try {
|
|
2360
|
+
if (!await this._handleInternalFiberRecovery(ctx)) {
|
|
2361
|
+
const recoveryResult = await this.onFiberRecovered(ctx);
|
|
2362
|
+
if (managedRow && recoveryResult) this._applyManagedFiberRecoveryResult(ctx.id, recoveryResult);
|
|
2363
|
+
}
|
|
2364
|
+
} catch (e) {
|
|
2365
|
+
if (managedRow) this.sql`
|
|
2366
|
+
UPDATE cf_agents_fibers
|
|
2367
|
+
SET error_message = ${this._fiberErrorMessage(e)}
|
|
2368
|
+
WHERE fiber_id = ${ctx.id}
|
|
2369
|
+
AND status = 'interrupted'
|
|
2370
|
+
`;
|
|
2371
|
+
console.error(`[Agent] Fiber recovery failed for "${ctx.name}" (${ctx.id}):`, e);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
_fiberInspectionFromRow(row) {
|
|
2375
|
+
const snapshot = this._parseFiberSnapshot(row.snapshot);
|
|
2376
|
+
const inspection = {
|
|
2377
|
+
fiberId: row.fiber_id,
|
|
2378
|
+
name: row.name,
|
|
2379
|
+
status: row.status,
|
|
2380
|
+
createdAt: row.created_at
|
|
2381
|
+
};
|
|
2382
|
+
if (row.idempotency_key !== null) inspection.idempotencyKey = row.idempotency_key;
|
|
2383
|
+
if (snapshot !== void 0) inspection.snapshot = snapshot;
|
|
2384
|
+
if (row.error_message !== null) inspection.error = row.error_message;
|
|
2385
|
+
const metadata = this._parseFiberJsonObject(row.metadata_json);
|
|
2386
|
+
if (metadata !== null) inspection.metadata = metadata;
|
|
2387
|
+
if (row.started_at !== null) inspection.startedAt = row.started_at;
|
|
2388
|
+
if (row.completed_at !== null) inspection.settledAt = row.completed_at;
|
|
2389
|
+
return inspection;
|
|
2390
|
+
}
|
|
2391
|
+
async _waitForManagedFiber(fiberId) {
|
|
2392
|
+
const row = this._readFiber(fiberId);
|
|
2393
|
+
if (!row || this._isTerminalFiberStatus(row.status)) return row ? this._fiberInspectionFromRow(row) : null;
|
|
2394
|
+
if (this._managedFiberExecutions.has(fiberId)) {
|
|
2395
|
+
await this._waitForManagedFiberTerminal(fiberId);
|
|
2396
|
+
return this.inspectFiber(fiberId);
|
|
2397
|
+
}
|
|
2398
|
+
await this._checkRunFibers();
|
|
2399
|
+
await this._waitForManagedFiberTerminal(fiberId);
|
|
2400
|
+
return this.inspectFiber(fiberId);
|
|
2401
|
+
}
|
|
2402
|
+
_readFiber(fiberId) {
|
|
2403
|
+
return this.sql`
|
|
2404
|
+
SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
|
|
2405
|
+
error_message, created_at, started_at, completed_at
|
|
2406
|
+
FROM cf_agents_fibers
|
|
2407
|
+
WHERE fiber_id = ${fiberId}
|
|
2408
|
+
LIMIT 1
|
|
2409
|
+
`[0] ?? null;
|
|
2410
|
+
}
|
|
2411
|
+
_readFiberByKey(idempotencyKey) {
|
|
2412
|
+
return this.sql`
|
|
2413
|
+
SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
|
|
2414
|
+
error_message, created_at, started_at, completed_at
|
|
2415
|
+
FROM cf_agents_fibers
|
|
2416
|
+
WHERE idempotency_key = ${idempotencyKey}
|
|
2417
|
+
LIMIT 1
|
|
2418
|
+
`[0] ?? null;
|
|
2419
|
+
}
|
|
2420
|
+
_listFiberRows(options) {
|
|
2421
|
+
const limit = Math.min(Math.max(options?.limit ?? 50, 1), 100);
|
|
2422
|
+
const statuses = this._normalizeFiberStatusFilter(options?.status);
|
|
2423
|
+
if (statuses) return [...statuses].flatMap((status) => this._listFiberRowsByStatus(status, limit, options?.name)).sort((a, b) => b.created_at === a.created_at ? b.fiber_id.localeCompare(a.fiber_id) : b.created_at - a.created_at).slice(0, limit);
|
|
2424
|
+
if (options?.name) return this.sql`
|
|
2425
|
+
SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
|
|
2426
|
+
error_message, created_at, started_at, completed_at
|
|
2427
|
+
FROM cf_agents_fibers
|
|
2428
|
+
WHERE name = ${options.name}
|
|
2429
|
+
ORDER BY created_at DESC, fiber_id DESC
|
|
2430
|
+
LIMIT ${limit}
|
|
2431
|
+
`;
|
|
2432
|
+
return this.sql`
|
|
2433
|
+
SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
|
|
2434
|
+
error_message, created_at, started_at, completed_at
|
|
2435
|
+
FROM cf_agents_fibers
|
|
2436
|
+
ORDER BY created_at DESC, fiber_id DESC
|
|
2437
|
+
LIMIT ${limit}
|
|
2438
|
+
`;
|
|
2439
|
+
}
|
|
2440
|
+
_listFiberRowsByStatus(status, limit, name) {
|
|
2441
|
+
if (name) return this.sql`
|
|
2442
|
+
SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
|
|
2443
|
+
error_message, created_at, started_at, completed_at
|
|
2444
|
+
FROM cf_agents_fibers
|
|
2445
|
+
WHERE status = ${status} AND name = ${name}
|
|
2446
|
+
ORDER BY created_at DESC, fiber_id DESC
|
|
2447
|
+
LIMIT ${limit}
|
|
2448
|
+
`;
|
|
2449
|
+
return this.sql`
|
|
2450
|
+
SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
|
|
2451
|
+
error_message, created_at, started_at, completed_at
|
|
2452
|
+
FROM cf_agents_fibers
|
|
2453
|
+
WHERE status = ${status}
|
|
2454
|
+
ORDER BY created_at DESC, fiber_id DESC
|
|
2455
|
+
LIMIT ${limit}
|
|
2456
|
+
`;
|
|
2457
|
+
}
|
|
2458
|
+
async inspectFiber(fiberId) {
|
|
2459
|
+
const row = this._readFiber(fiberId);
|
|
2460
|
+
return row ? this._fiberInspectionFromRow(row) : null;
|
|
2461
|
+
}
|
|
2462
|
+
async inspectFiberByKey(idempotencyKey) {
|
|
2463
|
+
const row = this._readFiberByKey(idempotencyKey);
|
|
2464
|
+
return row ? this._fiberInspectionFromRow(row) : null;
|
|
2465
|
+
}
|
|
2466
|
+
async listFibers(options) {
|
|
2467
|
+
return this._listFiberRows(options).map((row) => this._fiberInspectionFromRow(row));
|
|
2468
|
+
}
|
|
2469
|
+
async cancelFiber(fiberId, reason) {
|
|
2470
|
+
const row = this._readFiber(fiberId);
|
|
2471
|
+
if (!row || this._isTerminalFiberStatus(row.status)) return false;
|
|
2472
|
+
const now = Date.now();
|
|
2473
|
+
this.sql`
|
|
2474
|
+
UPDATE cf_agents_fibers
|
|
2475
|
+
SET status = 'aborted',
|
|
2476
|
+
error_message = ${reason ?? null},
|
|
2477
|
+
completed_at = ${now}
|
|
2478
|
+
WHERE fiber_id = ${fiberId}
|
|
2479
|
+
AND status IN ('pending', 'running')
|
|
2480
|
+
`;
|
|
2481
|
+
this._managedFiberAbortControllers.get(fiberId)?.abort(reason);
|
|
2482
|
+
this._notifyManagedFiberTerminal(fiberId);
|
|
2483
|
+
return true;
|
|
2484
|
+
}
|
|
2485
|
+
async cancelFiberByKey(idempotencyKey, reason) {
|
|
2486
|
+
const row = this._readFiberByKey(idempotencyKey);
|
|
2487
|
+
return row ? this.cancelFiber(row.fiber_id, reason) : false;
|
|
2488
|
+
}
|
|
2489
|
+
async resolveFiber(fiberId, result) {
|
|
2490
|
+
const row = this._readFiber(fiberId);
|
|
2491
|
+
if (!row || row.status !== "interrupted") return false;
|
|
2492
|
+
this._applyManagedFiberRecoveryResult(fiberId, result);
|
|
2493
|
+
return true;
|
|
2494
|
+
}
|
|
2495
|
+
async deleteFibers(options) {
|
|
2496
|
+
const terminalStatuses = [...this._normalizeFiberStatusFilter(options?.status) ?? new Set([
|
|
2497
|
+
"completed",
|
|
2498
|
+
"aborted",
|
|
2499
|
+
"error"
|
|
2500
|
+
])].filter((status) => this._isTerminalFiberStatus(status));
|
|
2501
|
+
if (terminalStatuses.length === 0) return 0;
|
|
2502
|
+
const limit = Math.min(Math.max(options?.limit ?? 100, 1), 500);
|
|
2503
|
+
const settledBefore = options?.settledBefore?.getTime();
|
|
2504
|
+
const rows = terminalStatuses.flatMap((status) => this._listTerminalFiberRowsForDelete(status, limit, settledBefore)).sort((a, b) => a.completed_at === b.completed_at ? a.created_at - b.created_at : (a.completed_at ?? 0) - (b.completed_at ?? 0)).slice(0, limit);
|
|
2505
|
+
for (const row of rows) this.sql`
|
|
2506
|
+
DELETE FROM cf_agents_fibers
|
|
2507
|
+
WHERE fiber_id = ${row.fiber_id}
|
|
2508
|
+
AND status IN ('completed', 'aborted', 'interrupted', 'error')
|
|
2509
|
+
`;
|
|
2510
|
+
return rows.length;
|
|
2511
|
+
}
|
|
2512
|
+
_listTerminalFiberRowsForDelete(status, limit, settledBefore) {
|
|
2513
|
+
if (settledBefore !== void 0) return this.sql`
|
|
2514
|
+
SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
|
|
2515
|
+
error_message, created_at, started_at, completed_at
|
|
2516
|
+
FROM cf_agents_fibers
|
|
2517
|
+
WHERE status = ${status}
|
|
2518
|
+
AND completed_at IS NOT NULL
|
|
2519
|
+
AND completed_at < ${settledBefore}
|
|
2520
|
+
ORDER BY completed_at ASC, created_at ASC
|
|
2521
|
+
LIMIT ${limit}
|
|
2522
|
+
`;
|
|
2523
|
+
return this.sql`
|
|
2524
|
+
SELECT fiber_id, idempotency_key, name, status, snapshot, metadata_json,
|
|
2525
|
+
error_message, created_at, started_at, completed_at
|
|
2526
|
+
FROM cf_agents_fibers
|
|
2527
|
+
WHERE status = ${status}
|
|
2528
|
+
ORDER BY completed_at ASC, created_at ASC
|
|
2529
|
+
LIMIT ${limit}
|
|
2530
|
+
`;
|
|
2531
|
+
}
|
|
2214
2532
|
/**
|
|
2215
2533
|
* Run a function as a durable fiber. The fiber is registered in SQLite
|
|
2216
2534
|
* before execution, checkpointable during execution via `ctx.stash()`,
|
|
@@ -2225,7 +2543,100 @@ var Agent = class Agent extends Server {
|
|
|
2225
2543
|
* @returns The return value of fn
|
|
2226
2544
|
*/
|
|
2227
2545
|
async runFiber(name, fn) {
|
|
2228
|
-
|
|
2546
|
+
return this._runFiberInternal(nanoid(), name, fn);
|
|
2547
|
+
}
|
|
2548
|
+
async startFiber(name, fn, options) {
|
|
2549
|
+
const fiberId = options?.fiberId ?? nanoid();
|
|
2550
|
+
const idempotencyKey = options?.idempotencyKey;
|
|
2551
|
+
if (options?.fiberId !== void 0 && options.fiberId.trim() === "") throw new Error("fiberId must not be blank");
|
|
2552
|
+
if (options?.idempotencyKey !== void 0 && options.idempotencyKey.trim() === "") throw new Error("idempotencyKey must not be blank");
|
|
2553
|
+
const existingById = this._readFiber(fiberId);
|
|
2554
|
+
const existingByKey = idempotencyKey ? this._readFiberByKey(idempotencyKey) : null;
|
|
2555
|
+
if (existingById && existingByKey && existingById.fiber_id !== existingByKey.fiber_id) throw new Error("fiberId and idempotencyKey refer to different fibers");
|
|
2556
|
+
if (existingByKey && options?.fiberId && existingByKey.fiber_id !== fiberId) throw new Error("fiberId and idempotencyKey refer to different fibers");
|
|
2557
|
+
const existing = existingById ?? existingByKey;
|
|
2558
|
+
if (existing) {
|
|
2559
|
+
if (options?.waitForCompletion && !this._isTerminalFiberStatus(existing.status)) {
|
|
2560
|
+
const waited = await this._waitForManagedFiber(existing.fiber_id);
|
|
2561
|
+
if (waited) return {
|
|
2562
|
+
...waited,
|
|
2563
|
+
accepted: false
|
|
2564
|
+
};
|
|
2565
|
+
throw new Error(`Fiber ${existing.fiber_id} no longer exists`);
|
|
2566
|
+
}
|
|
2567
|
+
return {
|
|
2568
|
+
...this._fiberInspectionFromRow(existing),
|
|
2569
|
+
accepted: false
|
|
2570
|
+
};
|
|
2571
|
+
}
|
|
2572
|
+
const now = Date.now();
|
|
2573
|
+
this.sql`
|
|
2574
|
+
INSERT INTO cf_agents_fibers
|
|
2575
|
+
(fiber_id, idempotency_key, name, status, snapshot, metadata_json,
|
|
2576
|
+
error_message, created_at, started_at, completed_at)
|
|
2577
|
+
VALUES
|
|
2578
|
+
(${fiberId}, ${idempotencyKey ?? null}, ${name}, 'pending', NULL,
|
|
2579
|
+
${options?.metadata ? JSON.stringify(options.metadata) : null}, NULL,
|
|
2580
|
+
${now}, NULL, NULL)
|
|
2581
|
+
`;
|
|
2582
|
+
const row = this._readFiber(fiberId);
|
|
2583
|
+
if (!row) throw new Error(`Failed to create fiber ${fiberId}`);
|
|
2584
|
+
const execution = this._executeManagedFiber(fiberId, name, fn).catch((error) => {
|
|
2585
|
+
console.error(`[Agent] Managed fiber "${name}" (${fiberId}) failed:`, error);
|
|
2586
|
+
}).finally(() => {
|
|
2587
|
+
if (this._managedFiberExecutions.get(fiberId) === execution) this._managedFiberExecutions.delete(fiberId);
|
|
2588
|
+
});
|
|
2589
|
+
this._managedFiberExecutions.set(fiberId, execution);
|
|
2590
|
+
if (options?.waitForCompletion) {
|
|
2591
|
+
const completed = await this._waitForManagedFiber(fiberId);
|
|
2592
|
+
if (!completed) throw new Error(`Fiber ${fiberId} no longer exists`);
|
|
2593
|
+
return {
|
|
2594
|
+
...completed,
|
|
2595
|
+
accepted: true
|
|
2596
|
+
};
|
|
2597
|
+
}
|
|
2598
|
+
return {
|
|
2599
|
+
...this._fiberInspectionFromRow(row),
|
|
2600
|
+
accepted: true
|
|
2601
|
+
};
|
|
2602
|
+
}
|
|
2603
|
+
async _executeManagedFiber(fiberId, name, fn) {
|
|
2604
|
+
const row = this._readFiber(fiberId);
|
|
2605
|
+
if (!row || row.status !== "pending") return;
|
|
2606
|
+
const controller = new AbortController();
|
|
2607
|
+
this._managedFiberAbortControllers.set(fiberId, controller);
|
|
2608
|
+
const now = Date.now();
|
|
2609
|
+
this.sql`
|
|
2610
|
+
UPDATE cf_agents_fibers
|
|
2611
|
+
SET status = 'running', started_at = ${now}
|
|
2612
|
+
WHERE fiber_id = ${fiberId} AND status = 'pending'
|
|
2613
|
+
`;
|
|
2614
|
+
const updated = this._readFiber(fiberId);
|
|
2615
|
+
if (!updated || updated.status !== "running") {
|
|
2616
|
+
this._managedFiberAbortControllers.delete(fiberId);
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
let settled = false;
|
|
2620
|
+
try {
|
|
2621
|
+
await this._runFiberInternal(fiberId, name, fn, {
|
|
2622
|
+
signal: controller.signal,
|
|
2623
|
+
managed: true,
|
|
2624
|
+
beforeRunCleanup: (outcome) => {
|
|
2625
|
+
settled = true;
|
|
2626
|
+
this._settleManagedFiberExecution(fiberId, outcome, controller.signal);
|
|
2627
|
+
}
|
|
2628
|
+
});
|
|
2629
|
+
} catch (error) {
|
|
2630
|
+
if (!settled) this._settleManagedFiberExecution(fiberId, {
|
|
2631
|
+
ok: false,
|
|
2632
|
+
error
|
|
2633
|
+
}, controller.signal);
|
|
2634
|
+
} finally {
|
|
2635
|
+
this._managedFiberAbortControllers.delete(fiberId);
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
async _runFiberInternal(id, name, fn, options) {
|
|
2639
|
+
const signal = options?.signal ?? new AbortController().signal;
|
|
2229
2640
|
this.sql`
|
|
2230
2641
|
INSERT INTO cf_agents_runs (id, name, snapshot, created_at)
|
|
2231
2642
|
VALUES (${id}, ${name}, NULL, ${Date.now()})
|
|
@@ -2242,19 +2653,36 @@ var Agent = class Agent extends Server {
|
|
|
2242
2653
|
}
|
|
2243
2654
|
dispose = await this.keepAlive();
|
|
2244
2655
|
const stash = (data) => {
|
|
2656
|
+
const snapshot = JSON.stringify(data);
|
|
2245
2657
|
this.sql`
|
|
2246
|
-
UPDATE cf_agents_runs SET snapshot = ${
|
|
2658
|
+
UPDATE cf_agents_runs SET snapshot = ${snapshot}
|
|
2247
2659
|
WHERE id = ${id}
|
|
2248
2660
|
`;
|
|
2661
|
+
if (options?.managed) this.sql`
|
|
2662
|
+
UPDATE cf_agents_fibers SET snapshot = ${snapshot}
|
|
2663
|
+
WHERE fiber_id = ${id}
|
|
2664
|
+
`;
|
|
2249
2665
|
};
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2666
|
+
try {
|
|
2667
|
+
const result = await _fiberALS.run({
|
|
2668
|
+
id,
|
|
2669
|
+
signal,
|
|
2670
|
+
stash
|
|
2671
|
+
}, () => fn({
|
|
2672
|
+
id,
|
|
2673
|
+
signal,
|
|
2674
|
+
stash,
|
|
2675
|
+
snapshot: null
|
|
2676
|
+
}));
|
|
2677
|
+
options?.beforeRunCleanup?.({ ok: true });
|
|
2678
|
+
return result;
|
|
2679
|
+
} catch (error) {
|
|
2680
|
+
options?.beforeRunCleanup?.({
|
|
2681
|
+
ok: false,
|
|
2682
|
+
error
|
|
2683
|
+
});
|
|
2684
|
+
throw error;
|
|
2685
|
+
}
|
|
2258
2686
|
} finally {
|
|
2259
2687
|
this._runFiberActiveFibers.delete(id);
|
|
2260
2688
|
this.sql`DELETE FROM cf_agents_runs WHERE id = ${id}`;
|
|
@@ -2306,24 +2734,67 @@ var Agent = class Agent extends Server {
|
|
|
2306
2734
|
const rows = this.sql`SELECT id, name, snapshot, created_at FROM cf_agents_runs`;
|
|
2307
2735
|
for (const row of rows) {
|
|
2308
2736
|
if (this._runFiberActiveFibers.has(row.id)) continue;
|
|
2309
|
-
|
|
2310
|
-
if (row.snapshot) try {
|
|
2311
|
-
snapshot = JSON.parse(row.snapshot);
|
|
2312
|
-
} catch {
|
|
2313
|
-
console.warn(`[Agent] Corrupted snapshot for fiber ${row.id}, treating as null`);
|
|
2314
|
-
}
|
|
2737
|
+
const snapshot = this._parseFiberRecoverySnapshot(row.id, row.snapshot);
|
|
2315
2738
|
const ctx = {
|
|
2316
2739
|
id: row.id,
|
|
2317
2740
|
name: row.name,
|
|
2318
2741
|
snapshot,
|
|
2319
2742
|
createdAt: row.created_at
|
|
2320
2743
|
};
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2744
|
+
const managedRow = this._readFiber(row.id);
|
|
2745
|
+
if (managedRow) {
|
|
2746
|
+
if (this._isTerminalFiberStatus(managedRow.status)) {
|
|
2747
|
+
this.sql`DELETE FROM cf_agents_runs WHERE id = ${row.id}`;
|
|
2748
|
+
this._notifyManagedFiberTerminal(row.id);
|
|
2749
|
+
continue;
|
|
2750
|
+
}
|
|
2751
|
+
const completedAt = Date.now();
|
|
2752
|
+
this.sql`
|
|
2753
|
+
UPDATE cf_agents_fibers
|
|
2754
|
+
SET status = 'interrupted',
|
|
2755
|
+
snapshot = ${row.snapshot},
|
|
2756
|
+
completed_at = ${completedAt}
|
|
2757
|
+
WHERE fiber_id = ${row.id}
|
|
2758
|
+
AND status IN ('pending', 'running')
|
|
2759
|
+
`;
|
|
2760
|
+
ctx.idempotencyKey = managedRow.idempotency_key ?? void 0;
|
|
2761
|
+
ctx.metadata = this._parseFiberJsonObject(managedRow.metadata_json);
|
|
2762
|
+
ctx.status = "interrupted";
|
|
2325
2763
|
}
|
|
2764
|
+
await this._runFiberRecoveryHook(ctx, managedRow);
|
|
2326
2765
|
this.sql`DELETE FROM cf_agents_runs WHERE id = ${row.id}`;
|
|
2766
|
+
if (managedRow) this._notifyManagedFiberTerminal(row.id);
|
|
2767
|
+
}
|
|
2768
|
+
const ledgerOnlyRows = this.sql`
|
|
2769
|
+
SELECT f.fiber_id, f.idempotency_key, f.name, f.status, f.snapshot,
|
|
2770
|
+
f.metadata_json, f.error_message, f.created_at, f.started_at,
|
|
2771
|
+
f.completed_at
|
|
2772
|
+
FROM cf_agents_fibers f
|
|
2773
|
+
LEFT JOIN cf_agents_runs r ON r.id = f.fiber_id
|
|
2774
|
+
WHERE f.status IN ('pending', 'running')
|
|
2775
|
+
AND r.id IS NULL
|
|
2776
|
+
`;
|
|
2777
|
+
for (const row of ledgerOnlyRows) {
|
|
2778
|
+
if (this._runFiberActiveFibers.has(row.fiber_id)) continue;
|
|
2779
|
+
const snapshot = this._parseFiberRecoverySnapshot(row.fiber_id, row.snapshot);
|
|
2780
|
+
const completedAt = Date.now();
|
|
2781
|
+
this.sql`
|
|
2782
|
+
UPDATE cf_agents_fibers
|
|
2783
|
+
SET status = 'interrupted',
|
|
2784
|
+
completed_at = ${completedAt}
|
|
2785
|
+
WHERE fiber_id = ${row.fiber_id}
|
|
2786
|
+
AND status IN ('pending', 'running')
|
|
2787
|
+
`;
|
|
2788
|
+
await this._runFiberRecoveryHook({
|
|
2789
|
+
id: row.fiber_id,
|
|
2790
|
+
name: row.name,
|
|
2791
|
+
snapshot,
|
|
2792
|
+
createdAt: row.created_at,
|
|
2793
|
+
idempotencyKey: row.idempotency_key ?? void 0,
|
|
2794
|
+
metadata: this._parseFiberJsonObject(row.metadata_json),
|
|
2795
|
+
status: "interrupted"
|
|
2796
|
+
}, row);
|
|
2797
|
+
this._notifyManagedFiberTerminal(row.fiber_id);
|
|
2327
2798
|
}
|
|
2328
2799
|
} finally {
|
|
2329
2800
|
this._runFiberRecoveryInProgress = false;
|
|
@@ -4021,6 +4492,7 @@ var Agent = class Agent extends Server {
|
|
|
4021
4492
|
this.sql`DROP TABLE IF EXISTS cf_agents_workflows`;
|
|
4022
4493
|
this.sql`DROP TABLE IF EXISTS cf_agents_sub_agents`;
|
|
4023
4494
|
this.sql`DROP TABLE IF EXISTS cf_agents_runs`;
|
|
4495
|
+
this.sql`DROP TABLE IF EXISTS cf_agents_fibers`;
|
|
4024
4496
|
this.sql`DROP TABLE IF EXISTS cf_agents_facet_runs`;
|
|
4025
4497
|
this.sql`DROP TABLE IF EXISTS cf_agent_tool_runs`;
|
|
4026
4498
|
}
|