pi-subagents 0.27.0 → 0.28.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/CHANGELOG.md +13 -0
- package/README.md +46 -2
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +29 -1
- package/src/agents/agent-management.ts +14 -0
- package/src/agents/agent-serializer.ts +10 -0
- package/src/agents/agents.ts +41 -1
- package/src/extension/fanout-child.ts +1 -0
- package/src/extension/index.ts +4 -2
- package/src/extension/schemas.ts +4 -11
- package/src/intercom/result-intercom.ts +5 -0
- package/src/runs/background/async-execution.ts +4 -0
- package/src/runs/background/subagent-runner.ts +65 -8
- package/src/runs/foreground/chain-execution.ts +45 -1
- package/src/runs/foreground/execution.ts +171 -10
- package/src/runs/foreground/subagent-executor.ts +59 -3
- package/src/runs/shared/acceptance-contract.ts +27 -0
- package/src/runs/shared/acceptance-finalization.ts +12 -0
- package/src/runs/shared/parallel-utils.ts +2 -0
- package/src/runs/shared/workflow-graph.ts +6 -2
- package/src/shared/types.ts +16 -2
- package/src/shared/utils.ts +7 -0
- package/src/tui/render.ts +18 -13
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
type ModelAttempt,
|
|
20
20
|
type NestedRouteInfo,
|
|
21
21
|
type ResolvedControlConfig,
|
|
22
|
+
type ResourceLimitExceeded,
|
|
22
23
|
type SubagentRunMode,
|
|
23
24
|
type TokenUsage,
|
|
24
25
|
type Usage,
|
|
@@ -53,7 +54,7 @@ import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelSt
|
|
|
53
54
|
import { nestedSummaryFromAsyncStatus, writeNestedEvent } from "../shared/nested-events.ts";
|
|
54
55
|
import { formatModelAttemptNote, isRetryableModelFailure } from "../shared/model-fallback.ts";
|
|
55
56
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
56
|
-
import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput } from "../../shared/utils.ts";
|
|
57
|
+
import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, formatResourceLimitExceeded, getFinalOutput } from "../../shared/utils.ts";
|
|
57
58
|
import { evaluateCompletionMutationGuard, resolveCompletionPolicy } from "../shared/completion-guard.ts";
|
|
58
59
|
import {
|
|
59
60
|
createMutatingFailureState,
|
|
@@ -140,6 +141,7 @@ interface StepResult {
|
|
|
140
141
|
structuredOutputPath?: string;
|
|
141
142
|
structuredOutputSchemaPath?: string;
|
|
142
143
|
acceptance?: AcceptanceLedger;
|
|
144
|
+
resourceLimitExceeded?: ResourceLimitExceeded;
|
|
143
145
|
}
|
|
144
146
|
|
|
145
147
|
const ASYNC_INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2";
|
|
@@ -234,6 +236,7 @@ interface RunPiStreamingResult {
|
|
|
234
236
|
finalOutput: string;
|
|
235
237
|
interrupted?: boolean;
|
|
236
238
|
observedMutationAttempt?: boolean;
|
|
239
|
+
resourceLimitExceeded?: ResourceLimitExceeded;
|
|
237
240
|
}
|
|
238
241
|
|
|
239
242
|
function runPiStreaming(
|
|
@@ -247,6 +250,8 @@ function runPiStreaming(
|
|
|
247
250
|
childEventContext?: ChildEventContext,
|
|
248
251
|
registerInterrupt?: (interrupt: (() => void) | undefined) => void,
|
|
249
252
|
onChildEvent?: (event: ChildEvent) => void,
|
|
253
|
+
maxExecutionTimeMs?: number,
|
|
254
|
+
maxTokens?: number,
|
|
250
255
|
): Promise<RunPiStreamingResult> {
|
|
251
256
|
return new Promise((resolve) => {
|
|
252
257
|
const outputStream = fs.createWriteStream(outputFile, { flags: "w" });
|
|
@@ -270,7 +275,10 @@ function runPiStreaming(
|
|
|
270
275
|
let error: string | undefined;
|
|
271
276
|
let assistantError: string | undefined;
|
|
272
277
|
let interrupted = false;
|
|
278
|
+
let resourceLimitExceeded: ResourceLimitExceeded | undefined;
|
|
273
279
|
let observedMutationAttempt = false;
|
|
280
|
+
let resourceLimitTimer: NodeJS.Timeout | undefined;
|
|
281
|
+
let resourceLimitEscalationTimer: NodeJS.Timeout | undefined;
|
|
274
282
|
const rawStdoutLines: string[] = [];
|
|
275
283
|
|
|
276
284
|
const writeOutputLine = (line: string) => {
|
|
@@ -284,6 +292,19 @@ function runPiStreaming(
|
|
|
284
292
|
}
|
|
285
293
|
};
|
|
286
294
|
|
|
295
|
+
const triggerResourceLimit = (kind: ResourceLimitExceeded["kind"], limit: number, observed?: number) => {
|
|
296
|
+
if (settled || resourceLimitExceeded) return;
|
|
297
|
+
const message = formatResourceLimitExceeded({ agent: childEventContext?.agent ?? "subagent", kind, limit, observed });
|
|
298
|
+
resourceLimitExceeded = { kind, limit, ...(observed !== undefined ? { observed } : {}), message };
|
|
299
|
+
error = message;
|
|
300
|
+
writeOutputLine(message);
|
|
301
|
+
trySignalChild(child, "SIGINT");
|
|
302
|
+
resourceLimitEscalationTimer = setTimeout(() => {
|
|
303
|
+
if (!settled) trySignalChild(child, "SIGTERM");
|
|
304
|
+
}, 1000);
|
|
305
|
+
resourceLimitEscalationTimer.unref?.();
|
|
306
|
+
};
|
|
307
|
+
|
|
287
308
|
const appendChildEvent = (event: Record<string, unknown>) => {
|
|
288
309
|
if (!childEventContext) return;
|
|
289
310
|
appendJsonl(childEventContext.eventsPath, JSON.stringify({
|
|
@@ -338,6 +359,10 @@ function runPiStreaming(
|
|
|
338
359
|
usage.cacheRead += eventUsage.cacheRead ?? 0;
|
|
339
360
|
usage.cacheWrite += eventUsage.cacheWrite ?? 0;
|
|
340
361
|
usage.cost += eventUsage.cost?.total ?? 0;
|
|
362
|
+
const observedTokens = usage.input + usage.output;
|
|
363
|
+
if (maxTokens !== undefined && observedTokens >= maxTokens) {
|
|
364
|
+
triggerResourceLimit("maxTokens", maxTokens, observedTokens);
|
|
365
|
+
}
|
|
341
366
|
}
|
|
342
367
|
const stopReason = (event.message as { stopReason?: string }).stopReason;
|
|
343
368
|
const hasToolCall = Array.isArray(event.message.content)
|
|
@@ -373,6 +398,12 @@ function runPiStreaming(
|
|
|
373
398
|
let finalDrainTimer: NodeJS.Timeout | undefined;
|
|
374
399
|
let finalHardKillTimer: NodeJS.Timeout | undefined;
|
|
375
400
|
let settled = false;
|
|
401
|
+
if (maxExecutionTimeMs !== undefined) {
|
|
402
|
+
resourceLimitTimer = setTimeout(() => {
|
|
403
|
+
triggerResourceLimit("maxExecutionTimeMs", maxExecutionTimeMs);
|
|
404
|
+
}, maxExecutionTimeMs);
|
|
405
|
+
resourceLimitTimer.unref?.();
|
|
406
|
+
}
|
|
376
407
|
const clearStdioGuard = attachPostExitStdioGuard(child, { idleMs: 2000, hardMs: 8000 });
|
|
377
408
|
child.stdout.on("data", (chunk: Buffer) => {
|
|
378
409
|
const text = chunk.toString();
|
|
@@ -386,7 +417,7 @@ function runPiStreaming(
|
|
|
386
417
|
processStderrText(chunk.toString());
|
|
387
418
|
});
|
|
388
419
|
registerInterrupt?.(() => {
|
|
389
|
-
if (settled) return;
|
|
420
|
+
if (settled || resourceLimitExceeded) return;
|
|
390
421
|
interrupted = true;
|
|
391
422
|
if (!error) error = "Interrupted. Waiting for explicit next action.";
|
|
392
423
|
trySignalChild(child, "SIGINT");
|
|
@@ -403,6 +434,14 @@ function runPiStreaming(
|
|
|
403
434
|
clearTimeout(finalHardKillTimer);
|
|
404
435
|
finalHardKillTimer = undefined;
|
|
405
436
|
}
|
|
437
|
+
if (resourceLimitTimer) {
|
|
438
|
+
clearTimeout(resourceLimitTimer);
|
|
439
|
+
resourceLimitTimer = undefined;
|
|
440
|
+
}
|
|
441
|
+
if (resourceLimitEscalationTimer) {
|
|
442
|
+
clearTimeout(resourceLimitEscalationTimer);
|
|
443
|
+
resourceLimitEscalationTimer = undefined;
|
|
444
|
+
}
|
|
406
445
|
};
|
|
407
446
|
function startFinalDrain(): void {
|
|
408
447
|
if (childExited || finalDrainTimer || settled) return;
|
|
@@ -434,12 +473,12 @@ function runPiStreaming(
|
|
|
434
473
|
if (stdoutBuf.trim()) processStdoutLine(stdoutBuf);
|
|
435
474
|
if (stderrBuf.trim()) appendChildLine("subagent.child.stderr", stderrBuf);
|
|
436
475
|
outputStream.end();
|
|
437
|
-
const finalOutput = getFinalOutput(messages) || rawStdoutLines.join("\n").trim();
|
|
438
|
-
const finalError = error ?? assistantError;
|
|
476
|
+
const finalOutput = resourceLimitExceeded?.message ?? (getFinalOutput(messages) || rawStdoutLines.join("\n").trim());
|
|
477
|
+
const finalError = resourceLimitExceeded?.message ?? error ?? assistantError;
|
|
439
478
|
const forcedDrainAfterFinalSuccess = forcedTerminationSignal && cleanTerminalAssistantStopReceived && !finalError;
|
|
440
479
|
resolve({
|
|
441
480
|
stderr,
|
|
442
|
-
exitCode: interrupted || forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (exitCode ?? 1) : exitCode,
|
|
481
|
+
exitCode: resourceLimitExceeded ? 1 : interrupted || forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (exitCode ?? 1) : exitCode,
|
|
443
482
|
messages,
|
|
444
483
|
usage,
|
|
445
484
|
model,
|
|
@@ -447,6 +486,7 @@ function runPiStreaming(
|
|
|
447
486
|
finalOutput,
|
|
448
487
|
interrupted,
|
|
449
488
|
observedMutationAttempt,
|
|
489
|
+
resourceLimitExceeded,
|
|
450
490
|
});
|
|
451
491
|
});
|
|
452
492
|
|
|
@@ -456,9 +496,9 @@ function runPiStreaming(
|
|
|
456
496
|
clearDrainTimers();
|
|
457
497
|
clearStdioGuard();
|
|
458
498
|
outputStream.end();
|
|
459
|
-
const finalOutput = getFinalOutput(messages) || rawStdoutLines.join("\n").trim();
|
|
499
|
+
const finalOutput = resourceLimitExceeded?.message ?? (getFinalOutput(messages) || rawStdoutLines.join("\n").trim());
|
|
460
500
|
const spawnErrorMessage = spawnError instanceof Error ? spawnError.message : String(spawnError);
|
|
461
|
-
resolve({ stderr, exitCode: 1, messages, usage, model, error: error ?? assistantError ?? spawnErrorMessage, finalOutput, observedMutationAttempt });
|
|
501
|
+
resolve({ stderr, exitCode: 1, messages, usage, model, error: resourceLimitExceeded?.message ?? error ?? assistantError ?? spawnErrorMessage, finalOutput, observedMutationAttempt, resourceLimitExceeded });
|
|
462
502
|
});
|
|
463
503
|
});
|
|
464
504
|
}
|
|
@@ -614,6 +654,7 @@ async function runSingleStep(
|
|
|
614
654
|
structuredOutputPath?: string;
|
|
615
655
|
structuredOutputSchemaPath?: string;
|
|
616
656
|
acceptance?: AcceptanceLedger;
|
|
657
|
+
resourceLimitExceeded?: ResourceLimitExceeded;
|
|
617
658
|
}> {
|
|
618
659
|
const effectiveStructuredOutput = step.structuredOutput ?? (step.structuredOutputSchema
|
|
619
660
|
? createStructuredOutputRuntime(step.structuredOutputSchema, path.join(path.dirname(ctx.outputFile), "structured-output"))
|
|
@@ -701,6 +742,8 @@ async function runSingleStep(
|
|
|
701
742
|
{ eventsPath, runId: ctx.id, stepIndex: ctx.flatIndex, agent: step.agent },
|
|
702
743
|
ctx.registerInterrupt,
|
|
703
744
|
ctx.onChildEvent,
|
|
745
|
+
step.maxExecutionTimeMs,
|
|
746
|
+
step.maxTokens,
|
|
704
747
|
);
|
|
705
748
|
cleanupTempDir(tempDir);
|
|
706
749
|
|
|
@@ -766,7 +809,7 @@ async function runSingleStep(
|
|
|
766
809
|
finalOutputSnapshot = outputSnapshot;
|
|
767
810
|
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput } as RunPiStreamingResult & { structuredOutput?: unknown };
|
|
768
811
|
if (attempt.success || completionGuardError) break;
|
|
769
|
-
if (!isRetryableModelFailure(error) || index === candidates.length - 1) break;
|
|
812
|
+
if (run.resourceLimitExceeded || !isRetryableModelFailure(error) || index === candidates.length - 1) break;
|
|
770
813
|
attemptNotes.push(formatModelAttemptNote(attempt, candidates[index + 1]));
|
|
771
814
|
}
|
|
772
815
|
|
|
@@ -861,6 +904,8 @@ async function runSingleStep(
|
|
|
861
904
|
{ eventsPath, runId: ctx.id, stepIndex: ctx.flatIndex, agent: step.agent },
|
|
862
905
|
ctx.registerInterrupt,
|
|
863
906
|
ctx.onChildEvent,
|
|
907
|
+
step.maxExecutionTimeMs,
|
|
908
|
+
step.maxTokens,
|
|
864
909
|
);
|
|
865
910
|
cleanupTempDir(tempDir);
|
|
866
911
|
modelAttempts.push({
|
|
@@ -923,6 +968,7 @@ async function runSingleStep(
|
|
|
923
968
|
model: finalResult?.model,
|
|
924
969
|
attemptedModels: attemptedModels.length > 0 ? attemptedModels : undefined,
|
|
925
970
|
modelAttempts,
|
|
971
|
+
resourceLimitExceeded: finalResult?.resourceLimitExceeded,
|
|
926
972
|
skills: step.skills,
|
|
927
973
|
timestamp: Date.now(),
|
|
928
974
|
}, null, 2),
|
|
@@ -948,6 +994,7 @@ async function runSingleStep(
|
|
|
948
994
|
structuredOutputPath: effectiveStructuredOutput?.outputPath,
|
|
949
995
|
structuredOutputSchemaPath: effectiveStructuredOutput?.schemaPath,
|
|
950
996
|
acceptance,
|
|
997
|
+
resourceLimitExceeded: finalResult?.resourceLimitExceeded,
|
|
951
998
|
};
|
|
952
999
|
}
|
|
953
1000
|
|
|
@@ -1712,12 +1759,14 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1712
1759
|
statusPayload.steps[fi].structuredOutputPath = singleResult.structuredOutputPath;
|
|
1713
1760
|
statusPayload.steps[fi].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
1714
1761
|
statusPayload.steps[fi].acceptance = singleResult.acceptance;
|
|
1762
|
+
statusPayload.steps[fi].resourceLimitExceeded = singleResult.resourceLimitExceeded;
|
|
1715
1763
|
statusPayload.lastUpdate = taskEndTime;
|
|
1716
1764
|
writeStatusPayload();
|
|
1717
1765
|
appendJsonl(eventsPath, JSON.stringify({
|
|
1718
1766
|
type: singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
1719
1767
|
ts: taskEndTime, runId: id, stepIndex: fi, agent: task.agent,
|
|
1720
1768
|
exitCode: singleResult.exitCode, durationMs: taskEndTime - taskStartTime,
|
|
1769
|
+
resourceLimitExceeded: singleResult.resourceLimitExceeded,
|
|
1721
1770
|
}));
|
|
1722
1771
|
if (singleResult.exitCode !== 0 && failFast) aborted = true;
|
|
1723
1772
|
return { ...singleResult, skipped: false };
|
|
@@ -1742,6 +1791,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1742
1791
|
structuredOutputPath: pr.structuredOutputPath,
|
|
1743
1792
|
structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
|
|
1744
1793
|
acceptance: pr.acceptance,
|
|
1794
|
+
resourceLimitExceeded: pr.resourceLimitExceeded,
|
|
1745
1795
|
});
|
|
1746
1796
|
}
|
|
1747
1797
|
const collection = collectDynamicResults(step as Parameters<typeof collectDynamicResults>[0], materialized.items, parallelResults);
|
|
@@ -1941,6 +1991,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1941
1991
|
statusPayload.steps[fi].structuredOutputPath = singleResult.structuredOutputPath;
|
|
1942
1992
|
statusPayload.steps[fi].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
1943
1993
|
statusPayload.steps[fi].acceptance = singleResult.acceptance;
|
|
1994
|
+
statusPayload.steps[fi].resourceLimitExceeded = singleResult.resourceLimitExceeded;
|
|
1944
1995
|
statusPayload.lastUpdate = taskEndTime;
|
|
1945
1996
|
writeStatusPayload();
|
|
1946
1997
|
|
|
@@ -1948,6 +1999,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1948
1999
|
type: singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
1949
2000
|
ts: taskEndTime, runId: id, stepIndex: fi, agent: task.agent,
|
|
1950
2001
|
exitCode: singleResult.exitCode, durationMs: taskDuration,
|
|
2002
|
+
resourceLimitExceeded: singleResult.resourceLimitExceeded,
|
|
1951
2003
|
}));
|
|
1952
2004
|
if (singleResult.completionGuardTriggered) {
|
|
1953
2005
|
const event = buildControlEvent({
|
|
@@ -2006,6 +2058,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2006
2058
|
structuredOutputPath: pr.structuredOutputPath,
|
|
2007
2059
|
structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
|
|
2008
2060
|
acceptance: pr.acceptance,
|
|
2061
|
+
resourceLimitExceeded: pr.resourceLimitExceeded,
|
|
2009
2062
|
});
|
|
2010
2063
|
}
|
|
2011
2064
|
for (let t = 0; t < group.parallel.length; t++) {
|
|
@@ -2107,6 +2160,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2107
2160
|
structuredOutputPath: singleResult.structuredOutputPath,
|
|
2108
2161
|
structuredOutputSchemaPath: singleResult.structuredOutputSchemaPath,
|
|
2109
2162
|
acceptance: singleResult.acceptance,
|
|
2163
|
+
resourceLimitExceeded: singleResult.resourceLimitExceeded,
|
|
2110
2164
|
});
|
|
2111
2165
|
if (seqStep.outputName) {
|
|
2112
2166
|
outputs[seqStep.outputName] = outputEntryFromAsyncResult({
|
|
@@ -2152,6 +2206,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2152
2206
|
statusPayload.steps[flatIndex].structuredOutputPath = singleResult.structuredOutputPath;
|
|
2153
2207
|
statusPayload.steps[flatIndex].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
2154
2208
|
statusPayload.steps[flatIndex].acceptance = singleResult.acceptance;
|
|
2209
|
+
statusPayload.steps[flatIndex].resourceLimitExceeded = singleResult.resourceLimitExceeded;
|
|
2155
2210
|
if (stepTokens) {
|
|
2156
2211
|
statusPayload.steps[flatIndex].tokens = stepTokens;
|
|
2157
2212
|
statusPayload.totalTokens = { ...previousCumulativeTokens };
|
|
@@ -2168,6 +2223,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2168
2223
|
exitCode: singleResult.exitCode,
|
|
2169
2224
|
durationMs: stepEndTime - stepStartTime,
|
|
2170
2225
|
tokens: stepTokens,
|
|
2226
|
+
resourceLimitExceeded: singleResult.resourceLimitExceeded,
|
|
2171
2227
|
}));
|
|
2172
2228
|
if (singleResult.completionGuardTriggered) {
|
|
2173
2229
|
const event = buildControlEvent({
|
|
@@ -2314,6 +2370,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
2314
2370
|
structuredOutputPath: r.structuredOutputPath,
|
|
2315
2371
|
structuredOutputSchemaPath: r.structuredOutputSchemaPath,
|
|
2316
2372
|
acceptance: r.acceptance,
|
|
2373
|
+
resourceLimitExceeded: r.resourceLimitExceeded,
|
|
2317
2374
|
})),
|
|
2318
2375
|
outputs,
|
|
2319
2376
|
workflowGraph: statusPayload.workflowGraph,
|
|
@@ -81,7 +81,7 @@ interface ChainExecutionDetailsInput {
|
|
|
81
81
|
outputs?: ChainOutputMap;
|
|
82
82
|
currentFlatIndex?: number;
|
|
83
83
|
dynamicChildren?: Record<number, Array<{ agent: string; label?: string; flatIndex: number; itemKey: string; outputName?: string; structured?: boolean; error?: string }>>;
|
|
84
|
-
dynamicGroupStatuses?: Record<number, { status: "pending" | "running" | "completed" | "failed" | "paused" | "detached"; error?: string; acceptance?: SingleResult["acceptance"] }>;
|
|
84
|
+
dynamicGroupStatuses?: Record<number, { status: "pending" | "running" | "completed" | "failed" | "paused" | "detached" | "timed-out"; error?: string; acceptance?: SingleResult["acceptance"] }>;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
interface ParallelChainRunInput {
|
|
@@ -103,6 +103,8 @@ interface ParallelChainRunInput {
|
|
|
103
103
|
sessionFileForIndex?: (idx?: number) => string | undefined;
|
|
104
104
|
shareEnabled: boolean;
|
|
105
105
|
artifactConfig: ArtifactConfig;
|
|
106
|
+
timeoutMs?: number;
|
|
107
|
+
timeoutAt?: number;
|
|
106
108
|
artifactsDir: string;
|
|
107
109
|
signal?: AbortSignal;
|
|
108
110
|
onUpdate?: (r: AgentToolResult<Details>) => void;
|
|
@@ -265,6 +267,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
265
267
|
cwd: taskCwd,
|
|
266
268
|
signal: input.signal,
|
|
267
269
|
interruptSignal: interruptController.signal,
|
|
270
|
+
...(input.timeoutMs !== undefined && input.timeoutAt !== undefined ? { timeoutMs: input.timeoutMs, timeoutAt: input.timeoutAt } : {}),
|
|
268
271
|
allowIntercomDetach: taskAgentConfig?.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
269
272
|
intercomEvents: input.intercomEvents,
|
|
270
273
|
runId: input.runId,
|
|
@@ -277,6 +280,8 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
277
280
|
outputPath,
|
|
278
281
|
outputMode: behavior.outputMode,
|
|
279
282
|
maxSubagentDepth,
|
|
283
|
+
maxExecutionTimeMs: taskAgentConfig?.maxExecutionTimeMs,
|
|
284
|
+
maxTokens: taskAgentConfig?.maxTokens,
|
|
280
285
|
controlConfig: input.controlConfig,
|
|
281
286
|
onControlEvent: input.onControlEvent,
|
|
282
287
|
intercomSessionName: input.childIntercomTarget?.(task.agent, input.globalTaskIndex + taskIndex),
|
|
@@ -391,6 +396,7 @@ interface ChainExecutionParams {
|
|
|
391
396
|
nestedRoute?: NestedRouteInfo;
|
|
392
397
|
worktreeSetupHook?: string;
|
|
393
398
|
worktreeSetupHookTimeoutMs?: number;
|
|
399
|
+
timeoutMs?: number;
|
|
394
400
|
}
|
|
395
401
|
|
|
396
402
|
interface ChainExecutionResult {
|
|
@@ -580,6 +586,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
580
586
|
tuiBehaviorOverrides = result.behaviorOverrides;
|
|
581
587
|
}
|
|
582
588
|
|
|
589
|
+
const timeoutAt = params.timeoutMs !== undefined ? Date.now() + params.timeoutMs : undefined;
|
|
583
590
|
let prev = "";
|
|
584
591
|
let globalTaskIndex = 0;
|
|
585
592
|
let progressCreated = false;
|
|
@@ -648,6 +655,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
648
655
|
shareEnabled,
|
|
649
656
|
artifactConfig,
|
|
650
657
|
artifactsDir,
|
|
658
|
+
...(params.timeoutMs !== undefined && timeoutAt !== undefined ? { timeoutMs: params.timeoutMs, timeoutAt } : {}),
|
|
651
659
|
signal,
|
|
652
660
|
onUpdate,
|
|
653
661
|
results,
|
|
@@ -674,6 +682,18 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
674
682
|
if (result.progress) allProgress.push(result.progress);
|
|
675
683
|
if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
|
|
676
684
|
}
|
|
685
|
+
const timedOutIndexInStep = parallelResults.findIndex((result) => result.timedOut);
|
|
686
|
+
const timedOut = timedOutIndexInStep >= 0 ? parallelResults[timedOutIndexInStep] : undefined;
|
|
687
|
+
if (timedOut) {
|
|
688
|
+
return {
|
|
689
|
+
content: [{ type: "text", text: `Chain timed out at step ${stepIndex + 1} (${timedOut.agent}): ${timedOut.error ?? "timeout expired"}` }],
|
|
690
|
+
isError: true,
|
|
691
|
+
details: buildChainExecutionDetails(makeDetailsInput({
|
|
692
|
+
currentStepIndex: stepIndex,
|
|
693
|
+
currentFlatIndex: globalTaskIndex - step.parallel.length + timedOutIndexInStep,
|
|
694
|
+
})),
|
|
695
|
+
};
|
|
696
|
+
}
|
|
677
697
|
const interruptedIndexInStep = parallelResults.findIndex((result) => result.interrupted);
|
|
678
698
|
const interrupted = interruptedIndexInStep >= 0 ? parallelResults[interruptedIndexInStep] : undefined;
|
|
679
699
|
if (interrupted) {
|
|
@@ -835,6 +855,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
835
855
|
shareEnabled,
|
|
836
856
|
artifactConfig,
|
|
837
857
|
artifactsDir,
|
|
858
|
+
...(params.timeoutMs !== undefined && timeoutAt !== undefined ? { timeoutMs: params.timeoutMs, timeoutAt } : {}),
|
|
838
859
|
signal,
|
|
839
860
|
onUpdate,
|
|
840
861
|
results,
|
|
@@ -861,6 +882,19 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
861
882
|
if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
|
|
862
883
|
}
|
|
863
884
|
const collected = collectDynamicResults(step, materialized.items, parallelResults);
|
|
885
|
+
const timedOutIndexInStep = parallelResults.findIndex((result) => result.timedOut);
|
|
886
|
+
const timedOut = timedOutIndexInStep >= 0 ? parallelResults[timedOutIndexInStep] : undefined;
|
|
887
|
+
if (timedOut) {
|
|
888
|
+
dynamicGroupStatuses[stepIndex] = { status: "timed-out", error: timedOut.error };
|
|
889
|
+
return {
|
|
890
|
+
content: [{ type: "text", text: `Chain timed out at step ${stepIndex + 1} (${timedOut.agent}): ${timedOut.error ?? "timeout expired"}` }],
|
|
891
|
+
isError: true,
|
|
892
|
+
details: buildChainExecutionDetails(makeDetailsInput({
|
|
893
|
+
currentStepIndex: stepIndex,
|
|
894
|
+
currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length + timedOutIndexInStep,
|
|
895
|
+
})),
|
|
896
|
+
};
|
|
897
|
+
}
|
|
864
898
|
const interruptedIndexInStep = parallelResults.findIndex((result) => result.interrupted);
|
|
865
899
|
const interrupted = interruptedIndexInStep >= 0 ? parallelResults[interruptedIndexInStep] : undefined;
|
|
866
900
|
if (interrupted) {
|
|
@@ -1009,6 +1043,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1009
1043
|
cwd: resolveChildCwd(cwd ?? ctx.cwd, seqStep.cwd),
|
|
1010
1044
|
signal,
|
|
1011
1045
|
interruptSignal: interruptController.signal,
|
|
1046
|
+
...(params.timeoutMs !== undefined && timeoutAt !== undefined ? { timeoutMs: params.timeoutMs, timeoutAt } : {}),
|
|
1012
1047
|
allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
|
|
1013
1048
|
intercomEvents,
|
|
1014
1049
|
runId,
|
|
@@ -1021,6 +1056,8 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1021
1056
|
outputPath,
|
|
1022
1057
|
outputMode: behavior.outputMode,
|
|
1023
1058
|
maxSubagentDepth,
|
|
1059
|
+
maxExecutionTimeMs: agentConfig.maxExecutionTimeMs,
|
|
1060
|
+
maxTokens: agentConfig.maxTokens,
|
|
1024
1061
|
controlConfig,
|
|
1025
1062
|
onControlEvent,
|
|
1026
1063
|
intercomSessionName: childIntercomTarget?.(seqStep.agent, globalTaskIndex),
|
|
@@ -1088,6 +1125,13 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
1088
1125
|
if (r.progress) allProgress.push(r.progress);
|
|
1089
1126
|
if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
|
|
1090
1127
|
|
|
1128
|
+
if (r.timedOut) {
|
|
1129
|
+
return {
|
|
1130
|
+
content: [{ type: "text", text: `Chain timed out at step ${stepIndex + 1} (${r.agent}): ${r.error ?? "timeout expired"}` }],
|
|
1131
|
+
isError: true,
|
|
1132
|
+
details: buildChainExecutionDetails(makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex - 1 })),
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1091
1135
|
if (r.interrupted) {
|
|
1092
1136
|
return {
|
|
1093
1137
|
content: [{ type: "text", text: `Chain paused after interrupt at step ${stepIndex + 1} (${r.agent}). Waiting for explicit next action.` }],
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
detectSubagentError,
|
|
45
45
|
extractToolArgsPreview,
|
|
46
46
|
extractTextFromContent,
|
|
47
|
+
formatResourceLimitExceeded,
|
|
47
48
|
} from "../../shared/utils.ts";
|
|
48
49
|
import { buildSkillInjection, resolveSkillsWithFallback } from "../../agents/skills.ts";
|
|
49
50
|
import { evaluateCompletionMutationGuard, resolveCompletionPolicy, type CompletionPolicy } from "../shared/completion-guard.ts";
|
|
@@ -108,6 +109,43 @@ function appendRecentOutput(progress: AgentProgress, lines: string[]): void {
|
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
const FOREGROUND_TIMEOUT_EXIT_CODE = 124;
|
|
113
|
+
|
|
114
|
+
function formatForegroundTimeoutMessage(timeoutMs: number | undefined): string {
|
|
115
|
+
return timeoutMs ? `Timed out after ${timeoutMs}ms.` : "Timed out.";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function createTimedOutResult(agent: string, task: string, options: RunSyncOptions): SingleResult {
|
|
119
|
+
const message = formatForegroundTimeoutMessage(options.timeoutMs);
|
|
120
|
+
return {
|
|
121
|
+
agent,
|
|
122
|
+
task,
|
|
123
|
+
exitCode: FOREGROUND_TIMEOUT_EXIT_CODE,
|
|
124
|
+
messages: [],
|
|
125
|
+
usage: emptyUsage(),
|
|
126
|
+
error: message,
|
|
127
|
+
finalOutput: message,
|
|
128
|
+
timedOut: true,
|
|
129
|
+
progress: {
|
|
130
|
+
index: options.index ?? 0,
|
|
131
|
+
agent,
|
|
132
|
+
status: "failed",
|
|
133
|
+
task,
|
|
134
|
+
recentTools: [],
|
|
135
|
+
recentOutput: [message],
|
|
136
|
+
toolCount: 0,
|
|
137
|
+
tokens: 0,
|
|
138
|
+
durationMs: 0,
|
|
139
|
+
lastActivityAt: Date.now(),
|
|
140
|
+
},
|
|
141
|
+
progressSummary: {
|
|
142
|
+
toolCount: 0,
|
|
143
|
+
tokens: 0,
|
|
144
|
+
durationMs: 0,
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
111
149
|
function stripAcceptanceReportsFromMessages(messages: Message[] | undefined): void {
|
|
112
150
|
for (const message of messages ?? []) {
|
|
113
151
|
if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
|
|
@@ -263,6 +301,12 @@ async function runSingleAttempt(
|
|
|
263
301
|
let detached = false;
|
|
264
302
|
let intercomStarted = false;
|
|
265
303
|
let assistantError: string | undefined;
|
|
304
|
+
let timedOut = false;
|
|
305
|
+
let resourceLimited = false;
|
|
306
|
+
let timeoutTimer: NodeJS.Timeout | undefined;
|
|
307
|
+
let timeoutEscalationTimer: NodeJS.Timeout | undefined;
|
|
308
|
+
let resourceLimitTimer: NodeJS.Timeout | undefined;
|
|
309
|
+
let resourceLimitEscalationTimer: NodeJS.Timeout | undefined;
|
|
266
310
|
let removeAbortListener: (() => void) | undefined;
|
|
267
311
|
let removeInterruptListener: (() => void) | undefined;
|
|
268
312
|
let activityTimer: NodeJS.Timeout | undefined;
|
|
@@ -334,6 +378,22 @@ async function runSingleAttempt(
|
|
|
334
378
|
settled = true;
|
|
335
379
|
clearFinalDrainTimers();
|
|
336
380
|
clearStdioGuard();
|
|
381
|
+
if (timeoutTimer) {
|
|
382
|
+
clearTimeout(timeoutTimer);
|
|
383
|
+
timeoutTimer = undefined;
|
|
384
|
+
}
|
|
385
|
+
if (timeoutEscalationTimer) {
|
|
386
|
+
clearTimeout(timeoutEscalationTimer);
|
|
387
|
+
timeoutEscalationTimer = undefined;
|
|
388
|
+
}
|
|
389
|
+
if (resourceLimitTimer) {
|
|
390
|
+
clearTimeout(resourceLimitTimer);
|
|
391
|
+
resourceLimitTimer = undefined;
|
|
392
|
+
}
|
|
393
|
+
if (resourceLimitEscalationTimer) {
|
|
394
|
+
clearTimeout(resourceLimitEscalationTimer);
|
|
395
|
+
resourceLimitEscalationTimer = undefined;
|
|
396
|
+
}
|
|
337
397
|
if (activityTimer) {
|
|
338
398
|
clearInterval(activityTimer);
|
|
339
399
|
activityTimer = undefined;
|
|
@@ -428,6 +488,26 @@ async function runSingleAttempt(
|
|
|
428
488
|
};
|
|
429
489
|
|
|
430
490
|
|
|
491
|
+
const triggerResourceLimit = (kind: "maxExecutionTimeMs" | "maxTokens", limit: number, observed?: number) => {
|
|
492
|
+
if (processClosed || detached || settled || timedOut || resourceLimited) return;
|
|
493
|
+
resourceLimited = true;
|
|
494
|
+
const message = formatResourceLimitExceeded({ agent: agent.name, kind, limit, observed });
|
|
495
|
+
result.resourceLimitExceeded = { kind, limit, ...(observed !== undefined ? { observed } : {}), message };
|
|
496
|
+
result.error = message;
|
|
497
|
+
result.finalOutput = message;
|
|
498
|
+
progress.status = "failed";
|
|
499
|
+
progress.durationMs = Date.now() - startTime;
|
|
500
|
+
appendRecentOutput(progress, [message]);
|
|
501
|
+
progress.activityState = undefined;
|
|
502
|
+
fireUpdate();
|
|
503
|
+
trySignalChild(proc, "SIGINT");
|
|
504
|
+
resourceLimitEscalationTimer = setTimeout(() => {
|
|
505
|
+
if (settled || processClosed || detached) return;
|
|
506
|
+
trySignalChild(proc, "SIGTERM");
|
|
507
|
+
}, 1000);
|
|
508
|
+
resourceLimitEscalationTimer.unref?.();
|
|
509
|
+
};
|
|
510
|
+
|
|
431
511
|
const emitUpdateSnapshot = (text: string) => {
|
|
432
512
|
if (!options.onUpdate || processClosed) return;
|
|
433
513
|
const progressSnapshot = snapshotProgress(progress);
|
|
@@ -512,6 +592,9 @@ async function runSingleAttempt(
|
|
|
512
592
|
result.usage.cacheWrite += u.cacheWrite || 0;
|
|
513
593
|
result.usage.cost += u.cost?.total || 0;
|
|
514
594
|
progress.tokens = result.usage.input + result.usage.output;
|
|
595
|
+
if (options.maxTokens !== undefined && progress.tokens >= options.maxTokens) {
|
|
596
|
+
triggerResourceLimit("maxTokens", options.maxTokens, progress.tokens);
|
|
597
|
+
}
|
|
515
598
|
}
|
|
516
599
|
if (!result.model && evt.message.model) result.model = evt.message.model;
|
|
517
600
|
if (evt.message.errorMessage) assistantError = evt.message.errorMessage;
|
|
@@ -640,9 +723,45 @@ async function runSingleAttempt(
|
|
|
640
723
|
}
|
|
641
724
|
}
|
|
642
725
|
|
|
726
|
+
if (options.timeoutAt !== undefined) {
|
|
727
|
+
const triggerTimeout = () => {
|
|
728
|
+
if (processClosed || detached || settled || timedOut || resourceLimited) return;
|
|
729
|
+
timedOut = true;
|
|
730
|
+
const message = formatForegroundTimeoutMessage(options.timeoutMs);
|
|
731
|
+
result.timedOut = true;
|
|
732
|
+
result.error = message;
|
|
733
|
+
result.finalOutput = message;
|
|
734
|
+
progress.status = "failed";
|
|
735
|
+
progress.durationMs = Date.now() - startTime;
|
|
736
|
+
appendRecentOutput(progress, [message]);
|
|
737
|
+
progress.activityState = undefined;
|
|
738
|
+
fireUpdate();
|
|
739
|
+
trySignalChild(proc, "SIGINT");
|
|
740
|
+
timeoutEscalationTimer = setTimeout(() => {
|
|
741
|
+
if (settled || processClosed || detached) return;
|
|
742
|
+
trySignalChild(proc, "SIGTERM");
|
|
743
|
+
}, 1000);
|
|
744
|
+
timeoutEscalationTimer.unref?.();
|
|
745
|
+
};
|
|
746
|
+
const delay = options.timeoutAt - Date.now();
|
|
747
|
+
if (delay <= 0) triggerTimeout();
|
|
748
|
+
else {
|
|
749
|
+
timeoutTimer = setTimeout(triggerTimeout, delay);
|
|
750
|
+
timeoutTimer.unref?.();
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (options.maxExecutionTimeMs !== undefined) {
|
|
755
|
+
const maxExecutionTimeMs = options.maxExecutionTimeMs;
|
|
756
|
+
resourceLimitTimer = setTimeout(() => {
|
|
757
|
+
triggerResourceLimit("maxExecutionTimeMs", maxExecutionTimeMs);
|
|
758
|
+
}, maxExecutionTimeMs);
|
|
759
|
+
resourceLimitTimer.unref?.();
|
|
760
|
+
}
|
|
761
|
+
|
|
643
762
|
if (options.interruptSignal) {
|
|
644
763
|
const interrupt = () => {
|
|
645
|
-
if (processClosed || detached || settled) return;
|
|
764
|
+
if (processClosed || detached || settled || timedOut || resourceLimited) return;
|
|
646
765
|
interruptedByControl = true;
|
|
647
766
|
progress.status = "running";
|
|
648
767
|
progress.durationMs = Date.now() - startTime;
|
|
@@ -664,6 +783,40 @@ async function runSingleAttempt(
|
|
|
664
783
|
}
|
|
665
784
|
});
|
|
666
785
|
result.exitCode = exitCode;
|
|
786
|
+
if (result.resourceLimitExceeded) {
|
|
787
|
+
result.exitCode = 1;
|
|
788
|
+
result.error = result.error ?? result.resourceLimitExceeded.message;
|
|
789
|
+
result.finalOutput = result.finalOutput || result.error;
|
|
790
|
+
if (result.progress) {
|
|
791
|
+
result.progress.status = "failed";
|
|
792
|
+
result.progress.activityState = undefined;
|
|
793
|
+
result.progress.durationMs = Date.now() - startTime;
|
|
794
|
+
}
|
|
795
|
+
result.progressSummary = {
|
|
796
|
+
toolCount: progress.toolCount,
|
|
797
|
+
tokens: progress.tokens,
|
|
798
|
+
durationMs: result.progress?.durationMs ?? Date.now() - startTime,
|
|
799
|
+
};
|
|
800
|
+
result.controlEvents = allControlEvents.length ? allControlEvents : undefined;
|
|
801
|
+
return result;
|
|
802
|
+
}
|
|
803
|
+
if (result.timedOut) {
|
|
804
|
+
result.exitCode = FOREGROUND_TIMEOUT_EXIT_CODE;
|
|
805
|
+
result.error = result.error ?? formatForegroundTimeoutMessage(options.timeoutMs);
|
|
806
|
+
result.finalOutput = result.finalOutput || result.error;
|
|
807
|
+
if (result.progress) {
|
|
808
|
+
result.progress.status = "failed";
|
|
809
|
+
result.progress.activityState = undefined;
|
|
810
|
+
result.progress.durationMs = Date.now() - startTime;
|
|
811
|
+
}
|
|
812
|
+
result.progressSummary = {
|
|
813
|
+
toolCount: progress.toolCount,
|
|
814
|
+
tokens: progress.tokens,
|
|
815
|
+
durationMs: result.progress?.durationMs ?? Date.now() - startTime,
|
|
816
|
+
};
|
|
817
|
+
result.controlEvents = allControlEvents.length ? allControlEvents : undefined;
|
|
818
|
+
return result;
|
|
819
|
+
}
|
|
667
820
|
if (interruptedByControl) {
|
|
668
821
|
result.exitCode = 0;
|
|
669
822
|
result.interrupted = true;
|
|
@@ -915,8 +1068,16 @@ export async function runSync(
|
|
|
915
1068
|
error: outputModeValidationError,
|
|
916
1069
|
};
|
|
917
1070
|
}
|
|
1071
|
+
if (options.timeoutAt !== undefined && Date.now() >= options.timeoutAt) {
|
|
1072
|
+
return createTimedOutResult(agentName, task, options);
|
|
1073
|
+
}
|
|
1074
|
+
const effectiveOptions: RunSyncOptions = {
|
|
1075
|
+
...options,
|
|
1076
|
+
maxExecutionTimeMs: options.maxExecutionTimeMs ?? agent.maxExecutionTimeMs,
|
|
1077
|
+
maxTokens: options.maxTokens ?? agent.maxTokens,
|
|
1078
|
+
};
|
|
918
1079
|
|
|
919
|
-
const shareEnabled =
|
|
1080
|
+
const shareEnabled = effectiveOptions.share === true;
|
|
920
1081
|
const effectiveAcceptance = resolveEffectiveAcceptance({
|
|
921
1082
|
explicit: options.acceptance,
|
|
922
1083
|
agentName,
|
|
@@ -967,13 +1128,13 @@ export async function runSync(
|
|
|
967
1128
|
|
|
968
1129
|
let artifactPathsResult: ArtifactPaths | undefined;
|
|
969
1130
|
let jsonlPath: string | undefined;
|
|
970
|
-
if (
|
|
971
|
-
artifactPathsResult = getArtifactPaths(
|
|
972
|
-
ensureArtifactsDir(
|
|
973
|
-
if (
|
|
1131
|
+
if (effectiveOptions.artifactsDir && effectiveOptions.artifactConfig?.enabled !== false) {
|
|
1132
|
+
artifactPathsResult = getArtifactPaths(effectiveOptions.artifactsDir, effectiveOptions.runId, agentName, effectiveOptions.index);
|
|
1133
|
+
ensureArtifactsDir(effectiveOptions.artifactsDir);
|
|
1134
|
+
if (effectiveOptions.artifactConfig?.includeInput !== false) {
|
|
974
1135
|
writeArtifact(artifactPathsResult.inputPath, `# Task for ${agentName}\n\n${taskWithAcceptance}`);
|
|
975
1136
|
}
|
|
976
|
-
if (
|
|
1137
|
+
if (effectiveOptions.artifactConfig?.includeJsonl !== false) {
|
|
977
1138
|
jsonlPath = artifactPathsResult.jsonlPath;
|
|
978
1139
|
}
|
|
979
1140
|
}
|
|
@@ -983,8 +1144,8 @@ export async function runSync(
|
|
|
983
1144
|
for (let i = 0; i < modelsToTry.length; i++) {
|
|
984
1145
|
const candidate = modelsToTry[i];
|
|
985
1146
|
if (candidate) attemptedModels.push(candidate);
|
|
986
|
-
const outputSnapshot = captureSingleOutputSnapshot(
|
|
987
|
-
const result = await runSingleAttempt(runtimeCwd, agent, taskWithAcceptance, candidate,
|
|
1147
|
+
const outputSnapshot = captureSingleOutputSnapshot(effectiveOptions.outputPath);
|
|
1148
|
+
const result = await runSingleAttempt(runtimeCwd, agent, taskWithAcceptance, candidate, effectiveOptions, {
|
|
988
1149
|
sessionEnabled,
|
|
989
1150
|
systemPrompt,
|
|
990
1151
|
resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
|
|
@@ -1019,7 +1180,7 @@ export async function runSync(
|
|
|
1019
1180
|
if (attemptSucceeded) {
|
|
1020
1181
|
break;
|
|
1021
1182
|
}
|
|
1022
|
-
if (!isRetryableModelFailure(result.error) || i === modelsToTry.length - 1) {
|
|
1183
|
+
if (result.timedOut || result.resourceLimitExceeded || !isRetryableModelFailure(result.error) || i === modelsToTry.length - 1) {
|
|
1023
1184
|
break;
|
|
1024
1185
|
}
|
|
1025
1186
|
attemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[i + 1]));
|