agent-accelerator 0.5.2 → 0.5.4
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 +117 -32
- package/examples/prompts/SYSTEM_PROMPT_AGENT.md +14 -20
- package/package.json +1 -1
- package/src/agent/agent.ts +355 -38
- package/src/agent/context.ts +3 -1
- package/src/agent/delegation.ts +208 -524
- package/src/agent/loop.ts +139 -18
- package/src/agent/subagents-tool.ts +87 -0
- package/src/agent/subagents.md +31 -0
- package/src/agent/turns.ts +60 -0
- package/src/agent/worker.ts +38 -30
- package/src/agent/workers.ts +405 -0
- package/src/index.ts +5 -3
- package/src/models/catalog-cache.ts +3 -10
- package/src/scheduler/scheduler.ts +2 -0
- package/src/scheduler/store.ts +34 -21
- package/src/session/journal.ts +212 -0
- package/src/session/store.ts +203 -90
- package/src/session/tool-log.ts +62 -0
- package/src/session/usage.ts +201 -0
- package/src/telemetry-file.ts +4 -1
- package/src/tools/executor.ts +72 -15
- package/src/tools/subagent-result.ts +1 -1
- package/src/tools/tool.ts +0 -6
- package/src/types/agent.ts +45 -6
- package/src/types/message.ts +4 -0
- package/src/types/response.ts +2 -0
- package/src/types/tool.ts +19 -7
- package/src/utils/durable.ts +218 -0
- package/src/utils/headers.ts +1 -1
- package/src/utils/retry.ts +15 -1
package/src/agent/delegation.ts
CHANGED
|
@@ -2,7 +2,10 @@ import { z } from "zod";
|
|
|
2
2
|
import { tool } from "../tools/tool.ts";
|
|
3
3
|
import { escapeXml } from "../utils/serialization.ts";
|
|
4
4
|
import type { ToolDefinition } from "../types/tool.ts";
|
|
5
|
-
import type { SubAgentExecutionMetadata, SubAgentStep } from "../types/response.ts";
|
|
5
|
+
import type { AgentResponse, SubAgentExecutionMetadata, SubAgentStep } from "../types/response.ts";
|
|
6
|
+
import type { TokenUsage } from "../types/core.ts";
|
|
7
|
+
import type { ToolCallRecord } from "../types/tool.ts";
|
|
8
|
+
import type { ProviderRawData } from "../types/model.ts";
|
|
6
9
|
import type { Agent } from "./agent.ts";
|
|
7
10
|
import type { Budget, BudgetState } from "../types/agent.ts";
|
|
8
11
|
import { BudgetExceededError } from "../types/errors.ts";
|
|
@@ -12,38 +15,10 @@ import { createTrackingId } from "../utils/session.ts";
|
|
|
12
15
|
import { nowMs } from "../utils/session.ts";
|
|
13
16
|
import { newRunId } from "../utils/session.ts";
|
|
14
17
|
import { newTurnId } from "../utils/session.ts";
|
|
15
|
-
import {
|
|
16
|
-
sanitizeToolName,
|
|
17
|
-
sanitizeXmlTag,
|
|
18
|
-
buildDynamicWorkerConfig,
|
|
19
|
-
buildSubagentsXml,
|
|
20
|
-
createLiveWorker,
|
|
21
|
-
grantWorkerTools,
|
|
22
|
-
normalizeWorkerTimeoutValue,
|
|
23
|
-
providerOfWorkerModel,
|
|
24
|
-
} from "./worker.ts";
|
|
18
|
+
import { sanitizeToolName, createLiveWorker } from "./worker.ts";
|
|
25
19
|
|
|
26
20
|
export { escapeXml };
|
|
27
21
|
|
|
28
|
-
/** Task descriptor accepted by the automatic `spawn_subagents` tool.
|
|
29
|
-
*
|
|
30
|
-
* The Main Agent controls prompts (`name`/`role`/`instructions`/`task`), which
|
|
31
|
-
* worker tools to grant (`tools`), and — only when the developer sets
|
|
32
|
-
* `dynamicSubagents.timeout: -1` — each worker's `timeoutMs`. Model, reasoning
|
|
33
|
-
* level, and history are never LLM-choosable: workers are stateless and run on
|
|
34
|
-
* the developer-configured model.
|
|
35
|
-
*/
|
|
36
|
-
export interface DynamicSubagentTask {
|
|
37
|
-
name: string;
|
|
38
|
-
role?: string;
|
|
39
|
-
instructions: string;
|
|
40
|
-
task: string;
|
|
41
|
-
/** Names of worker tools to grant this sub-agent. Must be a subset of the developer-configured `dynamicSubagents.tools` pool; unknown names are ignored. Omit for no tools. */
|
|
42
|
-
tools?: string[];
|
|
43
|
-
/** Per-worker timeout in ms. Honored ONLY when the developer sets `dynamicSubagents.timeout: -1`. Must be > 0, otherwise the worker runs with no limit. */
|
|
44
|
-
timeoutMs?: number;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
22
|
export function createChildSessionId(parentId: string, tag: string): string {
|
|
48
23
|
let rand: string;
|
|
49
24
|
try {
|
|
@@ -367,9 +342,184 @@ export function appendSubAgentStep(trackingId: string, step: SubAgentStep): void
|
|
|
367
342
|
}
|
|
368
343
|
|
|
369
344
|
// ---------------------------------------------------------------------------
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
|
|
345
|
+
// One worker run: live trace + execution metadata (shared by fixed workers and the worker registry)
|
|
346
|
+
// ---------------------------------------------------------------------------
|
|
347
|
+
|
|
348
|
+
/** What a worker run reports when it finished; an `AgentResponse` fits, so does a reply saved before a crash (only `text` is required). */
|
|
349
|
+
export interface WorkerRunResult {
|
|
350
|
+
text: string;
|
|
351
|
+
model?: string;
|
|
352
|
+
provider?: string;
|
|
353
|
+
turns?: number;
|
|
354
|
+
usage?: TokenUsage;
|
|
355
|
+
finishReason?: string;
|
|
356
|
+
responseId?: string;
|
|
357
|
+
thinking?: string;
|
|
358
|
+
toolCalls?: ToolCallRecord[];
|
|
359
|
+
raw?: ProviderRawData;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Live handle of one worker run: its trace plus the hooks that feed it and the closers that finalize it. */
|
|
363
|
+
export interface WorkerTraceHandle {
|
|
364
|
+
trackingId: string;
|
|
365
|
+
/** `NAME-{trackingId}`, the id shown in metadata and accepted by `agent.track`. */
|
|
366
|
+
displayId: string;
|
|
367
|
+
onWorkerTurn: ReturnType<typeof createLiveWorker>["onWorkerTurn"];
|
|
368
|
+
attachToStream: ReturnType<typeof createLiveWorker>["attachToStream"];
|
|
369
|
+
/** Marks the run done and copies its result into the trace. */
|
|
370
|
+
finish(result: WorkerRunResult): void;
|
|
371
|
+
/** Marks the run failed. */
|
|
372
|
+
fail(message: string): void;
|
|
373
|
+
/** Copy of the steps logged so far. */
|
|
374
|
+
steps(): SubAgentStep[] | undefined;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Registers one worker run's live trace (visible to `agent.track`) and wires the shared live-worker state machine; in: a fixed-worker call or a registry spawn/message, out: the hooks that feed the trace and the closers that finalize it.
|
|
378
|
+
export function openWorkerTrace(a: {
|
|
379
|
+
name: string;
|
|
380
|
+
sessionId: string;
|
|
381
|
+
parentSessionId: string;
|
|
382
|
+
task: string;
|
|
383
|
+
role?: string;
|
|
384
|
+
/** When given, the trace is also kept in this agent's own registry and saved with its session. */
|
|
385
|
+
parent?: Agent;
|
|
386
|
+
emit?: (event: { trackingId: string; delta?: string; thinkingDelta?: string; partialText?: string; partialThinking?: string }) => void;
|
|
387
|
+
}): WorkerTraceHandle {
|
|
388
|
+
const trackingId = createTrackingId();
|
|
389
|
+
const displayId = `${a.name}-${trackingId}`;
|
|
390
|
+
const trace: SubAgentTrace = {
|
|
391
|
+
trackingId,
|
|
392
|
+
name: a.name,
|
|
393
|
+
sessionId: a.sessionId,
|
|
394
|
+
parentSessionId: a.parentSessionId,
|
|
395
|
+
status: "running",
|
|
396
|
+
task: a.task,
|
|
397
|
+
role: a.role,
|
|
398
|
+
turns: 0,
|
|
399
|
+
steps: [],
|
|
400
|
+
};
|
|
401
|
+
insertTrace(trace, displayId);
|
|
402
|
+
if (a.parent) {
|
|
403
|
+
try {
|
|
404
|
+
const registry = ((a.parent as any).subagentTraces ??= {}) as Record<string, SubAgentTrace>;
|
|
405
|
+
registry[trackingId] = trace;
|
|
406
|
+
} catch {}
|
|
407
|
+
}
|
|
408
|
+
const live = createLiveWorker(trace, trackingId, displayId, {
|
|
409
|
+
trim: (t) => trimTraceSteps(t),
|
|
410
|
+
touch: (id) => touchTrace(id),
|
|
411
|
+
notify: (id) => notifyTrace(id),
|
|
412
|
+
...(a.parent ? { persist: () => { try { a.parent!.persistNow(); } catch {} } } : {}),
|
|
413
|
+
emit: (ev) => {
|
|
414
|
+
try { a.emit?.(ev); } catch {}
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
const settle = (): void => {
|
|
418
|
+
const t = subagentTraces.get(trackingId);
|
|
419
|
+
if (!t) return;
|
|
420
|
+
trimTraceSteps(t);
|
|
421
|
+
touchTrace(t.trackingId);
|
|
422
|
+
notifyTrace(trackingId);
|
|
423
|
+
};
|
|
424
|
+
return {
|
|
425
|
+
trackingId,
|
|
426
|
+
displayId,
|
|
427
|
+
onWorkerTurn: live.onWorkerTurn,
|
|
428
|
+
attachToStream: live.attachToStream,
|
|
429
|
+
finish(result) {
|
|
430
|
+
const t = subagentTraces.get(trackingId);
|
|
431
|
+
if (t) {
|
|
432
|
+
t.status = "done";
|
|
433
|
+
t.model = result.model;
|
|
434
|
+
t.provider = result.provider;
|
|
435
|
+
t.turns = result.turns ?? t.turns;
|
|
436
|
+
t.text = result.text;
|
|
437
|
+
if (result.usage) {
|
|
438
|
+
t.usage = { inputTokens: result.usage.inputTokens ?? 0, outputTokens: result.usage.outputTokens ?? 0, totalTokens: result.usage.totalTokens ?? 0 };
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
settle();
|
|
442
|
+
},
|
|
443
|
+
fail(message) {
|
|
444
|
+
const t = subagentTraces.get(trackingId);
|
|
445
|
+
if (t) {
|
|
446
|
+
t.status = "error";
|
|
447
|
+
t.error = message;
|
|
448
|
+
}
|
|
449
|
+
settle();
|
|
450
|
+
},
|
|
451
|
+
steps() {
|
|
452
|
+
return subagentTraces.get(trackingId)?.steps.map((s) => ({ ...s }));
|
|
453
|
+
},
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Builds the metadata of one worker run, success or failure, so every delegation path reports the same fields and lineage ids; in: the run's result or error, out: the branded result envelope and `AgentResponse.subagents`.
|
|
458
|
+
export function workerMetadata(a: {
|
|
459
|
+
name: string;
|
|
460
|
+
displayId: string;
|
|
461
|
+
sessionId?: string;
|
|
462
|
+
parentSessionId?: string;
|
|
463
|
+
role?: string;
|
|
464
|
+
task: string;
|
|
465
|
+
durationMs: number;
|
|
466
|
+
result?: WorkerRunResult;
|
|
467
|
+
error?: string;
|
|
468
|
+
/** Text reported for a failed run; default `Error in NAME: message`. */
|
|
469
|
+
errorText?: string;
|
|
470
|
+
steps?: SubAgentStep[];
|
|
471
|
+
runId: string;
|
|
472
|
+
turnId: string;
|
|
473
|
+
}): SubAgentExecutionMetadata {
|
|
474
|
+
const r = a.result;
|
|
475
|
+
const base = {
|
|
476
|
+
name: a.name,
|
|
477
|
+
trackingId: a.displayId,
|
|
478
|
+
sessionId: a.sessionId,
|
|
479
|
+
parentSessionId: a.parentSessionId,
|
|
480
|
+
...(a.role ? { role: a.role } : {}),
|
|
481
|
+
task: a.task,
|
|
482
|
+
durationMs: a.durationMs,
|
|
483
|
+
steps: a.steps,
|
|
484
|
+
};
|
|
485
|
+
const metadata: SubAgentExecutionMetadata = r
|
|
486
|
+
? {
|
|
487
|
+
...base,
|
|
488
|
+
model: r.model ?? "unknown",
|
|
489
|
+
provider: r.provider ?? "unknown",
|
|
490
|
+
usage: r.usage ?? { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
491
|
+
turns: r.turns ?? 0,
|
|
492
|
+
finishReason: r.finishReason,
|
|
493
|
+
responseId: r.responseId,
|
|
494
|
+
text: r.text,
|
|
495
|
+
thinking: r.thinking,
|
|
496
|
+
toolCalls: r.toolCalls,
|
|
497
|
+
raw: r.raw,
|
|
498
|
+
isError: false,
|
|
499
|
+
}
|
|
500
|
+
: {
|
|
501
|
+
...base,
|
|
502
|
+
model: "unknown",
|
|
503
|
+
provider: "unknown",
|
|
504
|
+
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
505
|
+
turns: 0,
|
|
506
|
+
text: a.errorText ?? `Error in ${a.name}: ${a.error}`,
|
|
507
|
+
isError: true,
|
|
508
|
+
error: a.error,
|
|
509
|
+
};
|
|
510
|
+
try {
|
|
511
|
+
const m = metadata as unknown as Record<string, unknown>;
|
|
512
|
+
m["runId"] = a.runId;
|
|
513
|
+
m["turnId"] = a.turnId;
|
|
514
|
+
const extra = (r as { meta?: unknown } | undefined)?.meta;
|
|
515
|
+
if (extra !== undefined) m["meta"] = extra;
|
|
516
|
+
} catch {}
|
|
517
|
+
return metadata;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// ---------------------------------------------------------------------------
|
|
521
|
+
// Budgets (Q-39): counters + guards. The loop charges usage per turn; the
|
|
522
|
+
// worker registry enforces `maxTotalSubagents` through `reserveSpawn`. Durations use the monotonic `nowMs()`
|
|
373
523
|
// clock; TTL timestamps above use wall-clock `Date.now()`.
|
|
374
524
|
// ---------------------------------------------------------------------------
|
|
375
525
|
|
|
@@ -505,401 +655,6 @@ export function isStreamingLoop(value: unknown): value is StreamingLoopHandle {
|
|
|
505
655
|
return typeof r["on"] === "function" && typeof r["result"] === "function";
|
|
506
656
|
}
|
|
507
657
|
|
|
508
|
-
/**
|
|
509
|
-
* Creates the built-in tool that spawns stateless dynamic sub-agents concurrently.
|
|
510
|
-
*
|
|
511
|
-
* Workers run on the developer-configured model with the developer-configured
|
|
512
|
-
* reasoning level. The Main Agent controls prompts, per-worker tool grants, and
|
|
513
|
-
* (when `dynamicSubagents.timeout: -1`) per-worker timeouts — nothing else.
|
|
514
|
-
*
|
|
515
|
-
* @example `new Agent({ model, dynamicSubagents: { enabled: true, maxSpawn: 4 } })`
|
|
516
|
-
*/
|
|
517
|
-
export function createSubagentSpawnTool(parentAgent: Agent): ToolDefinition {
|
|
518
|
-
const dyn = (parentAgent as any).dynamicSubagents as
|
|
519
|
-
| { maxSpawn: number; tools: Record<string, unknown>; timeout: number }
|
|
520
|
-
| undefined;
|
|
521
|
-
const maxSpawn = dyn && Number.isFinite(dyn.maxSpawn) ? Math.max(1, Math.floor(dyn.maxSpawn)) : 4;
|
|
522
|
-
const poolNames = dyn ? Object.keys(dyn.tools ?? {}) : [];
|
|
523
|
-
const parentTimeout = dyn && Number.isFinite(dyn.timeout) ? Math.floor(dyn.timeout) : 0;
|
|
524
|
-
const timeoutNote =
|
|
525
|
-
parentTimeout === -1
|
|
526
|
-
? "Set a per-task timeoutMs (ms, > 0) to time-limit a worker; omit it for no limit."
|
|
527
|
-
: parentTimeout === 0
|
|
528
|
-
? "Workers run with no time limit; any per-task timeoutMs is ignored."
|
|
529
|
-
: `Every worker is limited to ${parentTimeout}ms; any per-task timeoutMs is ignored.`;
|
|
530
|
-
return tool({
|
|
531
|
-
name: "spawn_subagents",
|
|
532
|
-
description:
|
|
533
|
-
"Dynamically creates and runs specialized stateless sub-agents concurrently to handle sub-tasks. " +
|
|
534
|
-
"Use this whenever a query or task benefits from modular delegation, parallel research, multi-perspective analysis, or division of labor. " +
|
|
535
|
-
"All sub-agent outputs are aggregated and returned inside structured XML tags. " +
|
|
536
|
-
`You may spawn at most ${maxSpawn} sub-agent(s) per call — extra tasks beyond ${maxSpawn} are ignored. ` +
|
|
537
|
-
"Workers are stateless: each receives one task, returns its result, then shuts down; no conversation history is kept. " +
|
|
538
|
-
"You cannot choose worker models or reasoning levels. " +
|
|
539
|
-
(poolNames.length > 0
|
|
540
|
-
? `Worker-available tools: ${poolNames.join(", ")}. Grant each worker ONLY the tools its task needs via the per-task tools list; omit it for no tools. `
|
|
541
|
-
: "No worker tools are available; omit the per-task tools list. ") +
|
|
542
|
-
timeoutNote + " " +
|
|
543
|
-
"Every entry in tasks MUST include all of: name (UPPER-KEBAB tag), instructions (system prompt for the worker), task (concrete assignment for the worker). " +
|
|
544
|
-
"Example: {\"tasks\": [{\"name\": \"RESEARCH-ANALYST\", \"role\": \"memory market analyst\", \"instructions\": \"You are a memory market analyst. Return sourced findings only.\", \"task\": \"Research market pricing and supply constraints.\", \"tools\": [\"recent_news\"]}]}",
|
|
545
|
-
input: z.object({
|
|
546
|
-
tasks: z
|
|
547
|
-
.array(
|
|
548
|
-
z.object({
|
|
549
|
-
name: z
|
|
550
|
-
.string()
|
|
551
|
-
.optional()
|
|
552
|
-
.describe("Unique UPPER-KEBAB role tag dynamically derived from task (e.g. RESEARCH-ANALYST, MARKET-ANALYST, CODE-REVIEWER). Auto-generated when omitted."),
|
|
553
|
-
role: z
|
|
554
|
-
.string()
|
|
555
|
-
.optional()
|
|
556
|
-
.describe("Short persona / domain expertise for this sub-agent"),
|
|
557
|
-
instructions: z
|
|
558
|
-
.string()
|
|
559
|
-
.optional()
|
|
560
|
-
.describe("Personalized system prompt crafted by Main Agent to increase instruction following. Falls back to task when omitted."),
|
|
561
|
-
task: z
|
|
562
|
-
.string()
|
|
563
|
-
.optional()
|
|
564
|
-
.describe("Specific research/task prompt for this sub-agent. REQUIRED — falls back to instructions when omitted."),
|
|
565
|
-
tools: z
|
|
566
|
-
.array(z.string())
|
|
567
|
-
.optional()
|
|
568
|
-
.describe(
|
|
569
|
-
poolNames.length > 0
|
|
570
|
-
? `Tool names to grant this worker (subset of: ${poolNames.join(", ")}). Unknown names are ignored. Omit for no tools.`
|
|
571
|
-
: "No worker tools are available; omit this field."
|
|
572
|
-
),
|
|
573
|
-
timeoutMs: z
|
|
574
|
-
.number()
|
|
575
|
-
.optional()
|
|
576
|
-
.describe(
|
|
577
|
-
parentTimeout === -1
|
|
578
|
-
? "Per-worker timeout in ms (> 0). Honored because the developer set timeout: -1. Omit for no limit."
|
|
579
|
-
: "Per-worker timeout is developer-controlled; this field is ignored."
|
|
580
|
-
),
|
|
581
|
-
})
|
|
582
|
-
)
|
|
583
|
-
.min(1)
|
|
584
|
-
.describe(`Array of sub-agents to spawn (max ${maxSpawn} per call) — each gets a personalized prompt and runs statelessly on the developer-configured model`),
|
|
585
|
-
}),
|
|
586
|
-
execute: async ({ tasks }, context) => {
|
|
587
|
-
if (!tasks || tasks.length === 0) {
|
|
588
|
-
return "Error: tasks array is empty. Provide at least one entry shaped like {\"name\": \"RESEARCH-ANALYST\", \"role\": \"memory market analyst\", \"instructions\": \"<system prompt>\", \"task\": \"<concrete assignment>\"}. Fix the arguments and call spawn_subagents again.";
|
|
589
|
-
}
|
|
590
|
-
const repaired = tasks.map((entry: Record<string, unknown>, index: number) => {
|
|
591
|
-
const rec = (entry ?? {}) as Record<string, unknown>;
|
|
592
|
-
const taskText =
|
|
593
|
-
(typeof rec["task"] === "string" && rec["task"].trim()) ||
|
|
594
|
-
(typeof rec["instructions"] === "string" && (rec["instructions"] as string).trim()) ||
|
|
595
|
-
"";
|
|
596
|
-
const instructionsText =
|
|
597
|
-
(typeof rec["instructions"] === "string" && (rec["instructions"] as string).trim()) ||
|
|
598
|
-
(typeof rec["task"] === "string" && (rec["task"] as string).trim()) ||
|
|
599
|
-
(typeof rec["role"] === "string" && (rec["role"] as string).trim()) ||
|
|
600
|
-
"";
|
|
601
|
-
const nameText =
|
|
602
|
-
(typeof rec["name"] === "string" && (rec["name"] as string).trim()) ||
|
|
603
|
-
`SUBAGENT-${index + 1}`;
|
|
604
|
-
const roleText =
|
|
605
|
-
typeof rec["role"] === "string" ? ((rec["role"] as string).trim() || undefined) : undefined;
|
|
606
|
-
const toolsList = Array.isArray(rec["tools"])
|
|
607
|
-
? (rec["tools"] as unknown[]).filter((x): x is string => typeof x === "string" && x.trim().length > 0).map((x) => x.trim())
|
|
608
|
-
: undefined;
|
|
609
|
-
const timeoutRaw = rec["timeoutMs"];
|
|
610
|
-
const timeoutMs = typeof timeoutRaw === "number" && Number.isFinite(timeoutRaw) ? Math.floor(timeoutRaw) : undefined;
|
|
611
|
-
return { ...rec, name: nameText, role: roleText, instructions: instructionsText, task: taskText, tools: toolsList, timeoutMs };
|
|
612
|
-
});
|
|
613
|
-
const invalid = repaired.findIndex((r) => !r.task);
|
|
614
|
-
if (invalid >= 0) {
|
|
615
|
-
const keys = Object.keys((tasks[invalid] ?? {}) as object).join(", ") || "(none)";
|
|
616
|
-
return (
|
|
617
|
-
`Error: tasks[${invalid}].task is missing and could not be inferred. ` +
|
|
618
|
-
`Received keys: [${keys}]. ` +
|
|
619
|
-
`Each tasks[] entry MUST include task (concrete assignment) plus instructions (system prompt) and name (UPPER-KEBAB tag). ` +
|
|
620
|
-
`Example: {\"name\": \"RESEARCH-ANALYST\", \"role\": \"memory market analyst\", \"instructions\": \"You are a memory market analyst.\", \"task\": \"Research market pricing.\"}. ` +
|
|
621
|
-
`Fix the entry and call spawn_subagents again.`
|
|
622
|
-
);
|
|
623
|
-
}
|
|
624
|
-
if (context?.signal?.aborted) {
|
|
625
|
-
throw new Error("Sub-agent spawning aborted");
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
const dynCfg = (parentAgent as any).dynamicSubagents as
|
|
629
|
-
| { model?: unknown; maxSpawn: number; thinkingLevel?: string; tools: Record<string, ToolDefinition>; timeout: number }
|
|
630
|
-
| undefined;
|
|
631
|
-
const effectiveMax = dynCfg && Number.isFinite(dynCfg.maxSpawn) ? Math.max(1, Math.floor(dynCfg.maxSpawn)) : 4;
|
|
632
|
-
const toolPool: Record<string, ToolDefinition> = (dynCfg?.tools as any) ?? {};
|
|
633
|
-
const cfgTimeout = dynCfg && Number.isFinite(dynCfg.timeout) ? Math.floor(dynCfg.timeout) : 0;
|
|
634
|
-
|
|
635
|
-
if (!parentAgent.subagentModel) {
|
|
636
|
-
throw new Error(
|
|
637
|
-
"[Agent Accelerator] Cannot spawn sub-agents: no sub-agent model is configured. " +
|
|
638
|
-
"Set dynamicSubagents.model in Agent config or set the SUB_AGENT_MODEL environment variable."
|
|
639
|
-
);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
const { Agent: AgentClass } = await import("./agent.ts");
|
|
643
|
-
|
|
644
|
-
const subagentMetadataList: SubAgentExecutionMetadata[] = [];
|
|
645
|
-
const seenNames = new Set<string>();
|
|
646
|
-
|
|
647
|
-
// maxSpawn: trim extras safely — only the first N tasks run.
|
|
648
|
-
const limitedTasks = repaired.slice(0, effectiveMax);
|
|
649
|
-
// Q-39: reserve spawns against maxTotalSubagents before running.
|
|
650
|
-
reserveSpawn(parentAgent, limitedTasks.length);
|
|
651
|
-
// Q-42: batch run id for lineage; per-worker ids attach to metadata.
|
|
652
|
-
const batchRunId = newRunId();
|
|
653
|
-
const parentRunId =
|
|
654
|
-
(parentAgent as unknown as { currentRunId?: unknown; runId?: unknown }).currentRunId ??
|
|
655
|
-
(parentAgent as unknown as { runId?: unknown }).runId;
|
|
656
|
-
const rootRunId =
|
|
657
|
-
(parentAgent as unknown as { rootRunId?: unknown }).rootRunId ?? parentRunId;
|
|
658
|
-
const executedResults = await Promise.all(
|
|
659
|
-
limitedTasks.map(async (t) => {
|
|
660
|
-
const startTime = nowMs();
|
|
661
|
-
const workerTurnId = newTurnId();
|
|
662
|
-
let sanitized = sanitizeXmlTag(t.name);
|
|
663
|
-
let deduped = sanitized;
|
|
664
|
-
let suffix = 1;
|
|
665
|
-
while (seenNames.has(deduped)) {
|
|
666
|
-
deduped = `${sanitized}-${suffix++}`;
|
|
667
|
-
}
|
|
668
|
-
seenNames.add(deduped);
|
|
669
|
-
const subagentName = deduped;
|
|
670
|
-
// Stable tracking id: `SUBAGENT-NAME-{32hex}` addresses one worker
|
|
671
|
-
// among many concurrent ones for `agent.track(id)` + session logs.
|
|
672
|
-
const trackingId = createTrackingId();
|
|
673
|
-
const displayId = `${subagentName}-${trackingId}`;
|
|
674
|
-
let workerSessionId: string | undefined;
|
|
675
|
-
|
|
676
|
-
// Fixed developer-configured worker model — never LLM-choosable.
|
|
677
|
-
const chosenModel: any = parentAgent.subagentModel;
|
|
678
|
-
const chosenModelStrForProvider = typeof chosenModel === "string" ? chosenModel : (chosenModel as any)?.model ?? (chosenModel as any)?.id ?? String(chosenModel);
|
|
679
|
-
|
|
680
|
-
const runWithModel = async () => {
|
|
681
|
-
if (context?.signal?.aborted) throw new Error("Aborted before spawn");
|
|
682
|
-
const childSessionId = createChildSessionId(parentAgent.sessionId, subagentName);
|
|
683
|
-
workerSessionId = childSessionId;
|
|
684
|
-
// Register the live trace BEFORE the run so `agent.track(id)`
|
|
685
|
-
// sees every step in realtime, even while the worker runs.
|
|
686
|
-
const trace: SubAgentTrace = {
|
|
687
|
-
trackingId,
|
|
688
|
-
name: subagentName,
|
|
689
|
-
sessionId: childSessionId,
|
|
690
|
-
parentSessionId: parentAgent.sessionId,
|
|
691
|
-
status: "running",
|
|
692
|
-
task: t.task,
|
|
693
|
-
role: t.role,
|
|
694
|
-
turns: 0,
|
|
695
|
-
steps: [],
|
|
696
|
-
};
|
|
697
|
-
insertTrace(trace, displayId);
|
|
698
|
-
try {
|
|
699
|
-
const parentTraces = ((parentAgent as any).subagentTraces ??= {}) as Record<string, SubAgentTrace>;
|
|
700
|
-
parentTraces[trackingId] = trace;
|
|
701
|
-
} catch {}
|
|
702
|
-
const persistParent = () => {
|
|
703
|
-
try { (parentAgent as any).persistNow?.(); } catch {}
|
|
704
|
-
};
|
|
705
|
-
// Unified live worker state machine (shared with fixed workers).
|
|
706
|
-
const { onWorkerTurn, attachToStream } = createLiveWorker(trace, trackingId, displayId, {
|
|
707
|
-
trim: (t) => trimTraceSteps(t),
|
|
708
|
-
touch: (id) => touchTrace(id),
|
|
709
|
-
notify: (id) => notifyTrace(id),
|
|
710
|
-
persist: persistParent,
|
|
711
|
-
emit: (ev) => {
|
|
712
|
-
try { context?.onSubagentEvent?.(ev); } catch {}
|
|
713
|
-
},
|
|
714
|
-
});
|
|
715
|
-
// Grant ONLY the Main Agent-selected subset from the developer pool.
|
|
716
|
-
const grantedTools = grantWorkerTools(toolPool, (t as { tools?: unknown }).tools);
|
|
717
|
-
// Timeout: >0 fixed for every worker; 0 = no limit; -1 = per-task timeoutMs.
|
|
718
|
-
const workerTimeout = normalizeWorkerTimeoutValue(cfgTimeout, (t as { timeoutMs?: unknown }).timeoutMs);
|
|
719
|
-
const subAgent = new AgentClass(
|
|
720
|
-
buildDynamicWorkerConfig(parentAgent, {
|
|
721
|
-
name: t.name,
|
|
722
|
-
role: t.role,
|
|
723
|
-
instructions: t.instructions,
|
|
724
|
-
sessionId: childSessionId,
|
|
725
|
-
tools: grantedTools,
|
|
726
|
-
})
|
|
727
|
-
);
|
|
728
|
-
// Lineage binding: child records its parent session so persisted
|
|
729
|
-
// snapshots and routing can tell siblings apart even when ids
|
|
730
|
-
// are truncated to 64 chars.
|
|
731
|
-
try {
|
|
732
|
-
(subAgent as any).parentSessionId = parentAgent.sessionId;
|
|
733
|
-
} catch {}
|
|
734
|
-
let abortListener: (() => void) | null = null;
|
|
735
|
-
// Per-worker controller: the parent signal alone cannot stop a
|
|
736
|
-
// worker that hits its own timeout, so the timeout path aborts
|
|
737
|
-
// the worker explicitly instead of leaving it running unseen.
|
|
738
|
-
const workerController = new AbortController();
|
|
739
|
-
const parentSignal = context?.signal;
|
|
740
|
-
const forwardParentAbort = () => {
|
|
741
|
-
try {
|
|
742
|
-
workerController.abort((parentSignal as any)?.reason);
|
|
743
|
-
} catch {
|
|
744
|
-
try { workerController.abort(); } catch {}
|
|
745
|
-
}
|
|
746
|
-
};
|
|
747
|
-
if (parentSignal?.aborted) forwardParentAbort();
|
|
748
|
-
else parentSignal?.addEventListener("abort", forwardParentAbort, { once: true });
|
|
749
|
-
const abortPromise = parentSignal
|
|
750
|
-
? new Promise<never>((_, reject) => {
|
|
751
|
-
const onAbort = () => reject(new Error("Sub-agent aborted via parent signal"));
|
|
752
|
-
abortListener = onAbort;
|
|
753
|
-
if (parentSignal.aborted) reject(new Error("Sub-agent aborted via parent signal"));
|
|
754
|
-
else parentSignal.addEventListener("abort", onAbort, { once: true });
|
|
755
|
-
})
|
|
756
|
-
: null;
|
|
757
|
-
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
758
|
-
const timeoutPromise = workerTimeout > 0
|
|
759
|
-
? new Promise<never>((_, reject) => {
|
|
760
|
-
timeoutId = setTimeout(() => {
|
|
761
|
-
try {
|
|
762
|
-
workerController.abort(new Error(`Sub-agent ${subagentName} timed out after ${workerTimeout}ms`));
|
|
763
|
-
} catch {}
|
|
764
|
-
reject(new Error(`Sub-agent ${subagentName} timed out after ${workerTimeout}ms`));
|
|
765
|
-
}, workerTimeout);
|
|
766
|
-
})
|
|
767
|
-
: null;
|
|
768
|
-
// Streamed internally (consumed to completion): same final result
|
|
769
|
-
// as run(), but text/thinking deltas flow into the trace live.
|
|
770
|
-
const workerStream = subAgent.stream(t.task, { signal: workerController.signal, onTurn: onWorkerTurn } as any);
|
|
771
|
-
const detachWorkerListeners = attachToStream(workerStream as unknown as Parameters<typeof attachToStream>[0]);
|
|
772
|
-
const taskPromise = workerStream.result();
|
|
773
|
-
try {
|
|
774
|
-
const racers: Promise<unknown>[] = [taskPromise as unknown as Promise<unknown>];
|
|
775
|
-
if (abortPromise) racers.push(abortPromise);
|
|
776
|
-
if (timeoutPromise) racers.push(timeoutPromise);
|
|
777
|
-
const res = await Promise.race(racers);
|
|
778
|
-
return res;
|
|
779
|
-
} finally {
|
|
780
|
-
if (timeoutId) clearTimeout(timeoutId);
|
|
781
|
-
try { workerStream.cancel(); } catch {}
|
|
782
|
-
try { subAgent.reset(); } catch {} // one-shot worker: free its chain/routing state
|
|
783
|
-
try { detachWorkerListeners(); } catch {}
|
|
784
|
-
if (abortListener && parentSignal) {
|
|
785
|
-
try { parentSignal.removeEventListener("abort", abortListener as any); } catch {}
|
|
786
|
-
}
|
|
787
|
-
try { parentSignal?.removeEventListener("abort", forwardParentAbort); } catch {}
|
|
788
|
-
}
|
|
789
|
-
};
|
|
790
|
-
|
|
791
|
-
try {
|
|
792
|
-
const res: any = await runWithModel();
|
|
793
|
-
|
|
794
|
-
const durationMs = Math.max(0, nowMs() - startTime);
|
|
795
|
-
const rawText = (res as any).text;
|
|
796
|
-
const subagentText = typeof rawText === "string" && rawText.trim().length > 0
|
|
797
|
-
? rawText
|
|
798
|
-
: `[Sub-agent ${subagentName} produced no output (empty response).]`;
|
|
799
|
-
|
|
800
|
-
const live = subagentTraces.get(trackingId);
|
|
801
|
-
if (live) {
|
|
802
|
-
live.status = "done";
|
|
803
|
-
live.model = (res as any).model;
|
|
804
|
-
live.provider = (res as any).provider;
|
|
805
|
-
live.turns = (res as any).turns ?? live.turns;
|
|
806
|
-
live.usage = (res as any).usage
|
|
807
|
-
? { inputTokens: (res as any).usage.inputTokens ?? 0, outputTokens: (res as any).usage.outputTokens ?? 0, totalTokens: (res as any).usage.totalTokens ?? 0 }
|
|
808
|
-
: live.usage;
|
|
809
|
-
live.text = subagentText;
|
|
810
|
-
trimTraceSteps(live);
|
|
811
|
-
touchTrace(live.trackingId);
|
|
812
|
-
notifyTrace(trackingId);
|
|
813
|
-
}
|
|
814
|
-
try { (parentAgent as any).persistNow?.(); } catch {}
|
|
815
|
-
// Worker usage is charged against the parent's budget centrally by the
|
|
816
|
-
// loop (it sees every finished worker, fixed and dynamic, exactly once).
|
|
817
|
-
const metadata: SubAgentExecutionMetadata = {
|
|
818
|
-
name: subagentName,
|
|
819
|
-
trackingId: displayId,
|
|
820
|
-
sessionId: workerSessionId,
|
|
821
|
-
parentSessionId: parentAgent.sessionId,
|
|
822
|
-
role: t.role,
|
|
823
|
-
task: t.task,
|
|
824
|
-
model: (res as any).model,
|
|
825
|
-
provider: (res as any).provider,
|
|
826
|
-
durationMs,
|
|
827
|
-
usage: (res as any).usage,
|
|
828
|
-
turns: (res as any).turns,
|
|
829
|
-
finishReason: (res as any).finishReason,
|
|
830
|
-
responseId: (res as any).responseId,
|
|
831
|
-
text: subagentText,
|
|
832
|
-
thinking: (res as any).thinking,
|
|
833
|
-
toolCalls: (res as any).toolCalls,
|
|
834
|
-
raw: (res as any).raw,
|
|
835
|
-
steps: live ? live.steps.map((s) => ({ ...s })) : undefined,
|
|
836
|
-
isError: false,
|
|
837
|
-
};
|
|
838
|
-
// Q-42 lineage (untyped: response.ts is owned elsewhere) + Q-43 provider meta passthrough.
|
|
839
|
-
try {
|
|
840
|
-
const m = metadata as unknown as Record<string, unknown>;
|
|
841
|
-
m["runId"] = newRunId();
|
|
842
|
-
m["turnId"] = workerTurnId;
|
|
843
|
-
if (parentRunId !== undefined) m["parentRunId"] = parentRunId;
|
|
844
|
-
if (rootRunId !== undefined) m["rootRunId"] = rootRunId;
|
|
845
|
-
else if (parentRunId !== undefined) m["rootRunId"] = parentRunId;
|
|
846
|
-
else m["rootRunId"] = batchRunId;
|
|
847
|
-
if ((res as any).meta !== undefined) m["meta"] = (res as any).meta;
|
|
848
|
-
} catch {}
|
|
849
|
-
return { name: subagentName, trackingId: displayId, text: subagentText, metadata };
|
|
850
|
-
} catch (err: any) {
|
|
851
|
-
if (err && (err as { code?: unknown }).code === "budget_exceeded") throw err;
|
|
852
|
-
const durationMs = Math.max(0, nowMs() - startTime);
|
|
853
|
-
const errorMessage = err?.message || String(err);
|
|
854
|
-
const prov = providerOfWorkerModel(chosenModelStrForProvider) || providerOfWorkerModel(chosenModel as unknown) || "unknown";
|
|
855
|
-
const live = subagentTraces.get(trackingId);
|
|
856
|
-
if (live) {
|
|
857
|
-
live.status = "error";
|
|
858
|
-
live.error = errorMessage;
|
|
859
|
-
notifyTrace(trackingId);
|
|
860
|
-
}
|
|
861
|
-
try { (parentAgent as any).persistNow?.(); } catch {}
|
|
862
|
-
const metadata: SubAgentExecutionMetadata = {
|
|
863
|
-
name: subagentName,
|
|
864
|
-
trackingId: displayId,
|
|
865
|
-
sessionId: workerSessionId,
|
|
866
|
-
parentSessionId: parentAgent.sessionId,
|
|
867
|
-
role: t.role,
|
|
868
|
-
task: t.task,
|
|
869
|
-
model: typeof chosenModel === "string" ? chosenModel : ((chosenModel as any)?.model ?? (chosenModel as any)?.id ?? "unknown"),
|
|
870
|
-
provider: prov,
|
|
871
|
-
durationMs,
|
|
872
|
-
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
873
|
-
turns: 0,
|
|
874
|
-
text: `Error executing sub-agent ${t.name}: ${errorMessage}`,
|
|
875
|
-
steps: live ? live.steps.map((s) => ({ ...s })) : undefined,
|
|
876
|
-
isError: true,
|
|
877
|
-
error: errorMessage,
|
|
878
|
-
};
|
|
879
|
-
try {
|
|
880
|
-
const m = metadata as unknown as Record<string, unknown>;
|
|
881
|
-
m["runId"] = newRunId();
|
|
882
|
-
m["turnId"] = workerTurnId;
|
|
883
|
-
if (parentRunId !== undefined) m["parentRunId"] = parentRunId;
|
|
884
|
-
if (rootRunId !== undefined) m["rootRunId"] = rootRunId;
|
|
885
|
-
else if (parentRunId !== undefined) m["rootRunId"] = parentRunId;
|
|
886
|
-
else m["rootRunId"] = batchRunId;
|
|
887
|
-
} catch {}
|
|
888
|
-
return { name: subagentName, trackingId: displayId, text: `Error executing sub-agent ${t.name}: ${errorMessage}`, metadata };
|
|
889
|
-
}
|
|
890
|
-
})
|
|
891
|
-
);
|
|
892
|
-
|
|
893
|
-
const xmlOutput = buildSubagentsXml(executedResults.map((r) => {
|
|
894
|
-
subagentMetadataList.push(r.metadata);
|
|
895
|
-
return r;
|
|
896
|
-
}));
|
|
897
|
-
|
|
898
|
-
return makeSubagentResult(xmlOutput, subagentMetadataList);
|
|
899
|
-
},
|
|
900
|
-
});
|
|
901
|
-
}
|
|
902
|
-
|
|
903
658
|
/** Optional name/description wrapper for converting an Agent into a tool. */
|
|
904
659
|
export interface AgentAsToolTarget {
|
|
905
660
|
name?: string;
|
|
@@ -934,118 +689,47 @@ export function agentToTool(
|
|
|
934
689
|
}),
|
|
935
690
|
execute: async ({ task }, ctx) => {
|
|
936
691
|
const startTime = nowMs();
|
|
937
|
-
const trackingId = createTrackingId();
|
|
938
|
-
const displayId = `${name}-${trackingId}`;
|
|
939
692
|
// Q-42 lineage for fixed-delegation calls.
|
|
940
693
|
const callRunId = newRunId();
|
|
941
694
|
const callTurnId = newTurnId();
|
|
695
|
+
const parentSessionId = ctx?.sessionId || agentInstance.sessionId || "session";
|
|
696
|
+
const subSessionId = createFixedChildSessionId(parentSessionId, sanitizeToolName(name));
|
|
697
|
+
const live = openWorkerTrace({
|
|
698
|
+
name,
|
|
699
|
+
sessionId: subSessionId,
|
|
700
|
+
parentSessionId,
|
|
701
|
+
task,
|
|
702
|
+
emit: (ev) => ctx?.onSubagentEvent?.(ev),
|
|
703
|
+
});
|
|
704
|
+
const lineage = { name, displayId: live.displayId, sessionId: subSessionId, parentSessionId, task, runId: callRunId, turnId: callTurnId };
|
|
942
705
|
try {
|
|
943
|
-
const parentSessionId = ctx?.sessionId || agentInstance.sessionId || "session";
|
|
944
|
-
const subSessionId = createFixedChildSessionId(parentSessionId, sanitizeToolName(name));
|
|
945
|
-
const trace: SubAgentTrace = {
|
|
946
|
-
trackingId,
|
|
947
|
-
name,
|
|
948
|
-
sessionId: subSessionId,
|
|
949
|
-
parentSessionId,
|
|
950
|
-
status: "running",
|
|
951
|
-
task,
|
|
952
|
-
turns: 0,
|
|
953
|
-
steps: [],
|
|
954
|
-
};
|
|
955
|
-
insertTrace(trace, displayId);
|
|
956
|
-
// Unified live worker state machine (shared with dynamic workers).
|
|
957
|
-
const { onWorkerTurn, attachToStream } = createLiveWorker(trace, trackingId, displayId, {
|
|
958
|
-
trim: (t) => trimTraceSteps(t),
|
|
959
|
-
touch: (id) => touchTrace(id),
|
|
960
|
-
notify: (id) => notifyTrace(id),
|
|
961
|
-
emit: (ev) => {
|
|
962
|
-
try { ctx?.onSubagentEvent?.(ev); } catch {}
|
|
963
|
-
},
|
|
964
|
-
});
|
|
965
706
|
// One isolated copy per call: the shared instance is single-flight, so
|
|
966
707
|
// calling it directly would serialize (or steer) parallel tool calls.
|
|
967
708
|
const worker: Agent =
|
|
968
709
|
typeof (agentInstance as Agent).fork === "function" ? agentInstance.fork(subSessionId, parentSessionId) : agentInstance;
|
|
969
|
-
const workerStream = worker.stream(task, { signal: ctx?.signal, sessionId: subSessionId, onTurn: onWorkerTurn } as any);
|
|
970
|
-
const detachWorkerListeners = attachToStream(workerStream as unknown as Parameters<typeof attachToStream>[0]);
|
|
971
|
-
let response:
|
|
710
|
+
const workerStream = worker.stream(task, { signal: ctx?.signal, sessionId: subSessionId, onTurn: live.onWorkerTurn } as any);
|
|
711
|
+
const detachWorkerListeners = live.attachToStream(workerStream as unknown as Parameters<typeof live.attachToStream>[0]);
|
|
712
|
+
let response: AgentResponse;
|
|
972
713
|
try {
|
|
973
714
|
response = await workerStream.result();
|
|
974
715
|
} finally {
|
|
975
716
|
try { detachWorkerListeners(); } catch {}
|
|
976
717
|
// The per-call session id is never reused: drop its provider chain/routing state.
|
|
977
|
-
if (worker !== agentInstance) {
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
if (live) {
|
|
982
|
-
live.status = "done";
|
|
983
|
-
live.model = response.model;
|
|
984
|
-
live.provider = response.provider;
|
|
985
|
-
live.turns = response.turns ?? live.turns;
|
|
986
|
-
live.text = response.text;
|
|
987
|
-
trimTraceSteps(live);
|
|
988
|
-
touchTrace(live.trackingId);
|
|
989
|
-
notifyTrace(trackingId);
|
|
718
|
+
if (worker !== agentInstance) {
|
|
719
|
+
try { agentInstance.absorbUsage(worker.usageTotals); } catch {} // the template keeps what its copies spent
|
|
720
|
+
try { worker.reset(); } catch {}
|
|
721
|
+
}
|
|
990
722
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
parentSessionId,
|
|
996
|
-
task,
|
|
997
|
-
model: response.model,
|
|
998
|
-
provider: response.provider,
|
|
999
|
-
durationMs,
|
|
1000
|
-
usage: response.usage,
|
|
1001
|
-
turns: response.turns,
|
|
1002
|
-
finishReason: response.finishReason,
|
|
1003
|
-
responseId: response.responseId,
|
|
1004
|
-
text: response.text,
|
|
1005
|
-
thinking: response.thinking,
|
|
1006
|
-
toolCalls: response.toolCalls,
|
|
1007
|
-
raw: response.raw,
|
|
1008
|
-
steps: live ? live.steps.map((s) => ({ ...s })) : undefined,
|
|
1009
|
-
isError: false,
|
|
1010
|
-
};
|
|
1011
|
-
try {
|
|
1012
|
-
const m = metadata as unknown as Record<string, unknown>;
|
|
1013
|
-
m["runId"] = callRunId;
|
|
1014
|
-
m["turnId"] = callTurnId;
|
|
1015
|
-
if (response.meta !== undefined) m["meta"] = response.meta;
|
|
1016
|
-
} catch {}
|
|
1017
|
-
return makeSubagentResult(response.text, [metadata]);
|
|
723
|
+
live.finish(response);
|
|
724
|
+
return makeSubagentResult(response.text, [
|
|
725
|
+
workerMetadata({ ...lineage, durationMs: Math.max(0, nowMs() - startTime), result: response, steps: live.steps() }),
|
|
726
|
+
]);
|
|
1018
727
|
} catch (err: any) {
|
|
1019
|
-
const durationMs = Math.max(0, nowMs() - startTime);
|
|
1020
728
|
const msg = err?.message || String(err);
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
trimTraceSteps(live);
|
|
1026
|
-
touchTrace(live.trackingId);
|
|
1027
|
-
notifyTrace(trackingId);
|
|
1028
|
-
}
|
|
1029
|
-
const metadata: SubAgentExecutionMetadata = {
|
|
1030
|
-
name,
|
|
1031
|
-
trackingId: displayId,
|
|
1032
|
-
task,
|
|
1033
|
-
model: "unknown",
|
|
1034
|
-
provider: "unknown",
|
|
1035
|
-
durationMs,
|
|
1036
|
-
usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
1037
|
-
turns: 0,
|
|
1038
|
-
text: `Error in ${name}: ${msg}`,
|
|
1039
|
-
steps: live ? live.steps.map((s) => ({ ...s })) : undefined,
|
|
1040
|
-
isError: true,
|
|
1041
|
-
error: msg,
|
|
1042
|
-
};
|
|
1043
|
-
try {
|
|
1044
|
-
const m = metadata as unknown as Record<string, unknown>;
|
|
1045
|
-
m["runId"] = callRunId;
|
|
1046
|
-
m["turnId"] = callTurnId;
|
|
1047
|
-
} catch {}
|
|
1048
|
-
return makeSubagentResult(`Error in ${name}: ${msg}`, [metadata]);
|
|
729
|
+
live.fail(msg);
|
|
730
|
+
return makeSubagentResult(`Error in ${name}: ${msg}`, [
|
|
731
|
+
workerMetadata({ ...lineage, durationMs: Math.max(0, nowMs() - startTime), error: msg, steps: live.steps() }),
|
|
732
|
+
]);
|
|
1049
733
|
}
|
|
1050
734
|
},
|
|
1051
735
|
});
|