pi-subagents 0.25.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 +34 -0
- package/README.md +175 -19
- package/package.json +1 -1
- package/prompts/parallel-context-build.md +3 -1
- package/prompts/parallel-handoff-plan.md +3 -1
- package/skills/pi-subagents/SKILL.md +60 -17
- package/src/agents/agent-management.ts +71 -15
- package/src/agents/agent-serializer.ts +13 -2
- package/src/agents/agents.ts +88 -17
- package/src/agents/chain-serializer.ts +120 -0
- package/src/extension/fanout-child.ts +2 -0
- package/src/extension/index.ts +5 -2
- package/src/extension/schemas.ts +132 -6
- package/src/intercom/result-intercom.ts +5 -0
- package/src/runs/background/async-execution.ts +88 -6
- package/src/runs/background/async-status.ts +11 -1
- package/src/runs/background/run-status.ts +10 -1
- package/src/runs/background/subagent-runner.ts +665 -39
- package/src/runs/foreground/chain-execution.ts +369 -118
- package/src/runs/foreground/execution.ts +392 -19
- package/src/runs/foreground/subagent-executor.ts +126 -3
- package/src/runs/shared/acceptance-contract.ts +318 -0
- package/src/runs/shared/acceptance-evaluation.ts +221 -0
- package/src/runs/shared/acceptance-finalization.ts +173 -0
- package/src/runs/shared/acceptance-reports.ts +127 -0
- package/src/runs/shared/acceptance.ts +22 -0
- package/src/runs/shared/chain-outputs.ts +101 -0
- package/src/runs/shared/completion-guard.ts +26 -3
- package/src/runs/shared/dynamic-fanout.ts +293 -0
- package/src/runs/shared/parallel-utils.ts +33 -1
- package/src/runs/shared/pi-args.ts +11 -0
- package/src/runs/shared/structured-output.ts +77 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +53 -3
- package/src/runs/shared/workflow-graph.ts +210 -0
- package/src/shared/formatters.ts +2 -2
- package/src/shared/settings.ts +53 -4
- package/src/shared/types.ts +265 -1
- package/src/shared/utils.ts +7 -0
- package/src/slash/slash-commands.ts +41 -3
- package/src/tui/render.ts +178 -45
|
@@ -8,16 +8,22 @@ import { appendJsonl, getArtifactPaths } from "../../shared/artifacts.ts";
|
|
|
8
8
|
import { PI_CODING_AGENT_PACKAGE, getPiSpawnCommand, resolveInstalledPiPackageRoot } from "../shared/pi-spawn.ts";
|
|
9
9
|
import { captureSingleOutputSnapshot, finalizeSingleOutput, formatSavedOutputReference, resolveSingleOutput, type SingleOutputSnapshot } from "../shared/single-output.ts";
|
|
10
10
|
import {
|
|
11
|
+
type AcceptanceFinalizationTurn,
|
|
12
|
+
type AcceptanceLedger,
|
|
11
13
|
type ActivityState,
|
|
12
14
|
type ArtifactConfig,
|
|
13
15
|
type ArtifactPaths,
|
|
14
16
|
type AsyncParallelGroupStatus,
|
|
15
17
|
type AsyncStatus,
|
|
18
|
+
type ChainOutputMap,
|
|
16
19
|
type ModelAttempt,
|
|
17
20
|
type NestedRouteInfo,
|
|
18
21
|
type ResolvedControlConfig,
|
|
22
|
+
type ResourceLimitExceeded,
|
|
19
23
|
type SubagentRunMode,
|
|
24
|
+
type TokenUsage,
|
|
20
25
|
type Usage,
|
|
26
|
+
type WorkflowGraphSnapshot,
|
|
21
27
|
DEFAULT_MAX_OUTPUT,
|
|
22
28
|
type MaxOutputConfig,
|
|
23
29
|
truncateOutput,
|
|
@@ -34,6 +40,7 @@ import {
|
|
|
34
40
|
import {
|
|
35
41
|
type RunnerSubagentStep as SubagentStep,
|
|
36
42
|
type RunnerStep,
|
|
43
|
+
isDynamicRunnerGroup,
|
|
37
44
|
isParallelGroup,
|
|
38
45
|
flattenSteps,
|
|
39
46
|
mapConcurrent,
|
|
@@ -41,11 +48,14 @@ import {
|
|
|
41
48
|
MAX_PARALLEL_CONCURRENCY,
|
|
42
49
|
} from "../shared/parallel-utils.ts";
|
|
43
50
|
import { buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
|
|
51
|
+
import { outputEntryFromAsyncResult, resolveOutputReferences } from "../shared/chain-outputs.ts";
|
|
52
|
+
import { createStructuredOutputRuntime, readStructuredOutput } from "../shared/structured-output.ts";
|
|
53
|
+
import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection } from "../shared/dynamic-fanout.ts";
|
|
44
54
|
import { nestedSummaryFromAsyncStatus, writeNestedEvent } from "../shared/nested-events.ts";
|
|
45
55
|
import { formatModelAttemptNote, isRetryableModelFailure } from "../shared/model-fallback.ts";
|
|
46
56
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
47
|
-
import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput } from "../../shared/utils.ts";
|
|
48
|
-
import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
|
|
57
|
+
import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, formatResourceLimitExceeded, getFinalOutput } from "../../shared/utils.ts";
|
|
58
|
+
import { evaluateCompletionMutationGuard, resolveCompletionPolicy } from "../shared/completion-guard.ts";
|
|
49
59
|
import {
|
|
50
60
|
createMutatingFailureState,
|
|
51
61
|
didMutatingToolFail,
|
|
@@ -58,7 +68,6 @@ import {
|
|
|
58
68
|
summarizeRecentMutatingFailures,
|
|
59
69
|
} from "../shared/long-running-guard.ts";
|
|
60
70
|
import { parseSessionTokens } from "../../shared/session-tokens.ts";
|
|
61
|
-
import type { TokenUsage } from "../../shared/types.ts";
|
|
62
71
|
import {
|
|
63
72
|
cleanupWorktrees,
|
|
64
73
|
createWorktrees,
|
|
@@ -70,6 +79,20 @@ import {
|
|
|
70
79
|
} from "../shared/worktree.ts";
|
|
71
80
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
72
81
|
import { writeInitialProgressFile } from "../../shared/settings.ts";
|
|
82
|
+
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
83
|
+
import {
|
|
84
|
+
acceptanceFailureMessage,
|
|
85
|
+
acceptanceSelfReviewConfig,
|
|
86
|
+
attachFinalizationToLedger,
|
|
87
|
+
buildFinalizationProcessFailureLedger,
|
|
88
|
+
createFinalizationProcessFailureTurn,
|
|
89
|
+
createFinalizationTurn,
|
|
90
|
+
evaluateAcceptance,
|
|
91
|
+
formatAcceptanceFinalizationPrompt,
|
|
92
|
+
formatAcceptancePrompt,
|
|
93
|
+
shouldRunAcceptanceFinalization,
|
|
94
|
+
stripAcceptanceReport,
|
|
95
|
+
} from "../shared/acceptance.ts";
|
|
73
96
|
|
|
74
97
|
interface SubagentRunConfig {
|
|
75
98
|
id: string;
|
|
@@ -94,6 +117,8 @@ interface SubagentRunConfig {
|
|
|
94
117
|
controlIntercomTarget?: string;
|
|
95
118
|
childIntercomTargets?: Array<string | undefined>;
|
|
96
119
|
resultMode?: SubagentRunMode;
|
|
120
|
+
dynamicFanoutMaxItems?: number;
|
|
121
|
+
workflowGraph?: WorkflowGraphSnapshot;
|
|
97
122
|
nestedRoute?: NestedRouteInfo;
|
|
98
123
|
nestedSelf?: { parentRunId: string; parentStepIndex?: number; depth: number; path?: Array<{ runId: string; stepIndex?: number; agent?: string }> };
|
|
99
124
|
}
|
|
@@ -103,6 +128,7 @@ interface StepResult {
|
|
|
103
128
|
output: string;
|
|
104
129
|
error?: string;
|
|
105
130
|
success: boolean;
|
|
131
|
+
exitCode?: number | null;
|
|
106
132
|
skipped?: boolean;
|
|
107
133
|
sessionFile?: string;
|
|
108
134
|
intercomTarget?: string;
|
|
@@ -111,6 +137,11 @@ interface StepResult {
|
|
|
111
137
|
modelAttempts?: ModelAttempt[];
|
|
112
138
|
artifactPaths?: ArtifactPaths;
|
|
113
139
|
truncated?: boolean;
|
|
140
|
+
structuredOutput?: unknown;
|
|
141
|
+
structuredOutputPath?: string;
|
|
142
|
+
structuredOutputSchemaPath?: string;
|
|
143
|
+
acceptance?: AcceptanceLedger;
|
|
144
|
+
resourceLimitExceeded?: ResourceLimitExceeded;
|
|
114
145
|
}
|
|
115
146
|
|
|
116
147
|
const ASYNC_INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2";
|
|
@@ -205,6 +236,7 @@ interface RunPiStreamingResult {
|
|
|
205
236
|
finalOutput: string;
|
|
206
237
|
interrupted?: boolean;
|
|
207
238
|
observedMutationAttempt?: boolean;
|
|
239
|
+
resourceLimitExceeded?: ResourceLimitExceeded;
|
|
208
240
|
}
|
|
209
241
|
|
|
210
242
|
function runPiStreaming(
|
|
@@ -218,6 +250,8 @@ function runPiStreaming(
|
|
|
218
250
|
childEventContext?: ChildEventContext,
|
|
219
251
|
registerInterrupt?: (interrupt: (() => void) | undefined) => void,
|
|
220
252
|
onChildEvent?: (event: ChildEvent) => void,
|
|
253
|
+
maxExecutionTimeMs?: number,
|
|
254
|
+
maxTokens?: number,
|
|
221
255
|
): Promise<RunPiStreamingResult> {
|
|
222
256
|
return new Promise((resolve) => {
|
|
223
257
|
const outputStream = fs.createWriteStream(outputFile, { flags: "w" });
|
|
@@ -241,7 +275,10 @@ function runPiStreaming(
|
|
|
241
275
|
let error: string | undefined;
|
|
242
276
|
let assistantError: string | undefined;
|
|
243
277
|
let interrupted = false;
|
|
278
|
+
let resourceLimitExceeded: ResourceLimitExceeded | undefined;
|
|
244
279
|
let observedMutationAttempt = false;
|
|
280
|
+
let resourceLimitTimer: NodeJS.Timeout | undefined;
|
|
281
|
+
let resourceLimitEscalationTimer: NodeJS.Timeout | undefined;
|
|
245
282
|
const rawStdoutLines: string[] = [];
|
|
246
283
|
|
|
247
284
|
const writeOutputLine = (line: string) => {
|
|
@@ -255,6 +292,19 @@ function runPiStreaming(
|
|
|
255
292
|
}
|
|
256
293
|
};
|
|
257
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
|
+
|
|
258
308
|
const appendChildEvent = (event: Record<string, unknown>) => {
|
|
259
309
|
if (!childEventContext) return;
|
|
260
310
|
appendJsonl(childEventContext.eventsPath, JSON.stringify({
|
|
@@ -309,6 +359,10 @@ function runPiStreaming(
|
|
|
309
359
|
usage.cacheRead += eventUsage.cacheRead ?? 0;
|
|
310
360
|
usage.cacheWrite += eventUsage.cacheWrite ?? 0;
|
|
311
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
|
+
}
|
|
312
366
|
}
|
|
313
367
|
const stopReason = (event.message as { stopReason?: string }).stopReason;
|
|
314
368
|
const hasToolCall = Array.isArray(event.message.content)
|
|
@@ -344,6 +398,12 @@ function runPiStreaming(
|
|
|
344
398
|
let finalDrainTimer: NodeJS.Timeout | undefined;
|
|
345
399
|
let finalHardKillTimer: NodeJS.Timeout | undefined;
|
|
346
400
|
let settled = false;
|
|
401
|
+
if (maxExecutionTimeMs !== undefined) {
|
|
402
|
+
resourceLimitTimer = setTimeout(() => {
|
|
403
|
+
triggerResourceLimit("maxExecutionTimeMs", maxExecutionTimeMs);
|
|
404
|
+
}, maxExecutionTimeMs);
|
|
405
|
+
resourceLimitTimer.unref?.();
|
|
406
|
+
}
|
|
347
407
|
const clearStdioGuard = attachPostExitStdioGuard(child, { idleMs: 2000, hardMs: 8000 });
|
|
348
408
|
child.stdout.on("data", (chunk: Buffer) => {
|
|
349
409
|
const text = chunk.toString();
|
|
@@ -357,7 +417,7 @@ function runPiStreaming(
|
|
|
357
417
|
processStderrText(chunk.toString());
|
|
358
418
|
});
|
|
359
419
|
registerInterrupt?.(() => {
|
|
360
|
-
if (settled) return;
|
|
420
|
+
if (settled || resourceLimitExceeded) return;
|
|
361
421
|
interrupted = true;
|
|
362
422
|
if (!error) error = "Interrupted. Waiting for explicit next action.";
|
|
363
423
|
trySignalChild(child, "SIGINT");
|
|
@@ -374,6 +434,14 @@ function runPiStreaming(
|
|
|
374
434
|
clearTimeout(finalHardKillTimer);
|
|
375
435
|
finalHardKillTimer = undefined;
|
|
376
436
|
}
|
|
437
|
+
if (resourceLimitTimer) {
|
|
438
|
+
clearTimeout(resourceLimitTimer);
|
|
439
|
+
resourceLimitTimer = undefined;
|
|
440
|
+
}
|
|
441
|
+
if (resourceLimitEscalationTimer) {
|
|
442
|
+
clearTimeout(resourceLimitEscalationTimer);
|
|
443
|
+
resourceLimitEscalationTimer = undefined;
|
|
444
|
+
}
|
|
377
445
|
};
|
|
378
446
|
function startFinalDrain(): void {
|
|
379
447
|
if (childExited || finalDrainTimer || settled) return;
|
|
@@ -405,12 +473,12 @@ function runPiStreaming(
|
|
|
405
473
|
if (stdoutBuf.trim()) processStdoutLine(stdoutBuf);
|
|
406
474
|
if (stderrBuf.trim()) appendChildLine("subagent.child.stderr", stderrBuf);
|
|
407
475
|
outputStream.end();
|
|
408
|
-
const finalOutput = getFinalOutput(messages) || rawStdoutLines.join("\n").trim();
|
|
409
|
-
const finalError = error ?? assistantError;
|
|
476
|
+
const finalOutput = resourceLimitExceeded?.message ?? (getFinalOutput(messages) || rawStdoutLines.join("\n").trim());
|
|
477
|
+
const finalError = resourceLimitExceeded?.message ?? error ?? assistantError;
|
|
410
478
|
const forcedDrainAfterFinalSuccess = forcedTerminationSignal && cleanTerminalAssistantStopReceived && !finalError;
|
|
411
479
|
resolve({
|
|
412
480
|
stderr,
|
|
413
|
-
exitCode: interrupted || forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (exitCode ?? 1) : exitCode,
|
|
481
|
+
exitCode: resourceLimitExceeded ? 1 : interrupted || forcedDrainAfterFinalSuccess ? 0 : forcedTerminationSignal || signal ? (exitCode ?? 1) : exitCode,
|
|
414
482
|
messages,
|
|
415
483
|
usage,
|
|
416
484
|
model,
|
|
@@ -418,6 +486,7 @@ function runPiStreaming(
|
|
|
418
486
|
finalOutput,
|
|
419
487
|
interrupted,
|
|
420
488
|
observedMutationAttempt,
|
|
489
|
+
resourceLimitExceeded,
|
|
421
490
|
});
|
|
422
491
|
});
|
|
423
492
|
|
|
@@ -427,9 +496,9 @@ function runPiStreaming(
|
|
|
427
496
|
clearDrainTimers();
|
|
428
497
|
clearStdioGuard();
|
|
429
498
|
outputStream.end();
|
|
430
|
-
const finalOutput = getFinalOutput(messages) || rawStdoutLines.join("\n").trim();
|
|
499
|
+
const finalOutput = resourceLimitExceeded?.message ?? (getFinalOutput(messages) || rawStdoutLines.join("\n").trim());
|
|
431
500
|
const spawnErrorMessage = spawnError instanceof Error ? spawnError.message : String(spawnError);
|
|
432
|
-
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 });
|
|
433
502
|
});
|
|
434
503
|
});
|
|
435
504
|
}
|
|
@@ -543,6 +612,7 @@ function writeRunLog(
|
|
|
543
612
|
/** Context for running a single step */
|
|
544
613
|
interface SingleStepContext {
|
|
545
614
|
previousOutput: string;
|
|
615
|
+
outputs?: ChainOutputMap;
|
|
546
616
|
placeholder: string;
|
|
547
617
|
cwd: string;
|
|
548
618
|
sessionEnabled: boolean;
|
|
@@ -580,9 +650,23 @@ async function runSingleStep(
|
|
|
580
650
|
sessionFile?: string;
|
|
581
651
|
intercomTarget?: string;
|
|
582
652
|
completionGuardTriggered?: boolean;
|
|
653
|
+
structuredOutput?: unknown;
|
|
654
|
+
structuredOutputPath?: string;
|
|
655
|
+
structuredOutputSchemaPath?: string;
|
|
656
|
+
acceptance?: AcceptanceLedger;
|
|
657
|
+
resourceLimitExceeded?: ResourceLimitExceeded;
|
|
583
658
|
}> {
|
|
659
|
+
const effectiveStructuredOutput = step.structuredOutput ?? (step.structuredOutputSchema
|
|
660
|
+
? createStructuredOutputRuntime(step.structuredOutputSchema, path.join(path.dirname(ctx.outputFile), "structured-output"))
|
|
661
|
+
: undefined);
|
|
584
662
|
const placeholderRegex = new RegExp(ctx.placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
|
|
585
|
-
|
|
663
|
+
let task = step.task.replace(placeholderRegex, () => ctx.previousOutput);
|
|
664
|
+
task = resolveOutputReferences(task, ctx.outputs ?? {});
|
|
665
|
+
const taskForCompletionGuard = task;
|
|
666
|
+
if (step.effectiveAcceptance) {
|
|
667
|
+
const acceptancePrompt = formatAcceptancePrompt(step.effectiveAcceptance);
|
|
668
|
+
if (acceptancePrompt) task = `${task}\n${acceptancePrompt}`;
|
|
669
|
+
}
|
|
586
670
|
const sessionEnabled = Boolean(step.sessionFile) || ctx.sessionEnabled;
|
|
587
671
|
const sessionDir = step.sessionFile ? undefined : ctx.sessionDir;
|
|
588
672
|
|
|
@@ -613,6 +697,13 @@ async function runSingleStep(
|
|
|
613
697
|
const candidate = candidates[index];
|
|
614
698
|
ctx.onAttemptStart?.({ model: candidate, thinking: resolveEffectiveThinking(candidate, step.thinking) });
|
|
615
699
|
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
700
|
+
if (effectiveStructuredOutput) {
|
|
701
|
+
try {
|
|
702
|
+
if (fs.existsSync(effectiveStructuredOutput.outputPath)) fs.unlinkSync(effectiveStructuredOutput.outputPath);
|
|
703
|
+
} catch {
|
|
704
|
+
// Missing/stale structured-output files are handled after the child exits.
|
|
705
|
+
}
|
|
706
|
+
}
|
|
616
707
|
const { args, env, tempDir } = buildPiArgs({
|
|
617
708
|
baseArgs: ["--mode", "json", "-p"],
|
|
618
709
|
task,
|
|
@@ -638,6 +729,7 @@ async function runSingleStep(
|
|
|
638
729
|
parentControlInbox: ctx.nestedRoute?.controlInbox,
|
|
639
730
|
parentRootRunId: ctx.nestedRoute?.rootRunId,
|
|
640
731
|
parentCapabilityToken: ctx.nestedRoute?.capabilityToken,
|
|
732
|
+
structuredOutput: effectiveStructuredOutput,
|
|
641
733
|
});
|
|
642
734
|
const run = await runPiStreaming(
|
|
643
735
|
args,
|
|
@@ -650,14 +742,35 @@ async function runSingleStep(
|
|
|
650
742
|
{ eventsPath, runId: ctx.id, stepIndex: ctx.flatIndex, agent: step.agent },
|
|
651
743
|
ctx.registerInterrupt,
|
|
652
744
|
ctx.onChildEvent,
|
|
745
|
+
step.maxExecutionTimeMs,
|
|
746
|
+
step.maxTokens,
|
|
653
747
|
);
|
|
654
748
|
cleanupTempDir(tempDir);
|
|
655
749
|
|
|
656
750
|
const hiddenError = run.exitCode === 0 && !run.error ? detectSubagentError(run.messages) : null;
|
|
657
|
-
|
|
751
|
+
let structuredOutput: unknown;
|
|
752
|
+
let structuredError: string | undefined;
|
|
753
|
+
if (effectiveStructuredOutput && run.exitCode === 0 && !run.error && !hiddenError?.hasError) {
|
|
754
|
+
const structured = readStructuredOutput({
|
|
755
|
+
schema: effectiveStructuredOutput.schema,
|
|
756
|
+
schemaPath: effectiveStructuredOutput.schemaPath,
|
|
757
|
+
outputPath: effectiveStructuredOutput.outputPath,
|
|
758
|
+
});
|
|
759
|
+
if (structured.error) structuredError = structured.error;
|
|
760
|
+
else structuredOutput = structured.value;
|
|
761
|
+
}
|
|
762
|
+
const completionPolicy = resolveCompletionPolicy({
|
|
763
|
+
agent: step.agent,
|
|
764
|
+
task: taskForCompletionGuard,
|
|
765
|
+
completionGuardEnabled: step.completionGuard !== false,
|
|
766
|
+
usesAcceptanceContract: step.effectiveAcceptance?.explicit === true,
|
|
767
|
+
tools: step.tools,
|
|
768
|
+
mcpDirectTools: step.mcpDirectTools,
|
|
769
|
+
});
|
|
770
|
+
const completionGuard = run.exitCode === 0 && !run.error && !hiddenError?.hasError && completionPolicy === "mutation-guard"
|
|
658
771
|
? evaluateCompletionMutationGuard({
|
|
659
772
|
agent: step.agent,
|
|
660
|
-
task,
|
|
773
|
+
task: taskForCompletionGuard,
|
|
661
774
|
messages: run.messages,
|
|
662
775
|
tools: step.tools,
|
|
663
776
|
mcpDirectTools: step.mcpDirectTools,
|
|
@@ -667,14 +780,17 @@ async function runSingleStep(
|
|
|
667
780
|
const completionGuardError = completionGuardTriggered
|
|
668
781
|
? "Subagent completed without making edits for an implementation task.\nIt appears to have returned planning or scratchpad output instead of applying changes."
|
|
669
782
|
: undefined;
|
|
670
|
-
const effectiveExitCode =
|
|
783
|
+
const effectiveExitCode = completionGuardError
|
|
671
784
|
? 1
|
|
672
|
-
:
|
|
785
|
+
: structuredError
|
|
786
|
+
? 1
|
|
787
|
+
: hiddenError?.hasError
|
|
673
788
|
? (hiddenError.exitCode ?? 1)
|
|
674
789
|
: run.error && run.exitCode === 0
|
|
675
790
|
? 1
|
|
676
791
|
: run.exitCode;
|
|
677
792
|
const error = completionGuardError
|
|
793
|
+
?? structuredError
|
|
678
794
|
?? (hiddenError?.hasError
|
|
679
795
|
? hiddenError.details
|
|
680
796
|
? `${hiddenError.errorType} failed (exit ${effectiveExitCode}): ${hiddenError.details}`
|
|
@@ -691,22 +807,24 @@ async function runSingleStep(
|
|
|
691
807
|
if (candidate) attemptedModels.push(candidate);
|
|
692
808
|
completionGuardTriggeredFinal = completionGuardTriggered;
|
|
693
809
|
finalOutputSnapshot = outputSnapshot;
|
|
694
|
-
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error };
|
|
695
|
-
if (attempt.success ||
|
|
696
|
-
if (!isRetryableModelFailure(error) || index === candidates.length - 1) break;
|
|
810
|
+
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput } as RunPiStreamingResult & { structuredOutput?: unknown };
|
|
811
|
+
if (attempt.success || completionGuardError) break;
|
|
812
|
+
if (run.resourceLimitExceeded || !isRetryableModelFailure(error) || index === candidates.length - 1) break;
|
|
697
813
|
attemptNotes.push(formatModelAttemptNote(attempt, candidates[index + 1]));
|
|
698
814
|
}
|
|
699
815
|
|
|
700
816
|
const rawOutput = finalResult?.finalOutput ?? "";
|
|
817
|
+
const outputForPersistence = stripAcceptanceReport(rawOutput);
|
|
701
818
|
const resolvedOutput = step.outputPath && finalResult?.exitCode === 0
|
|
702
|
-
? resolveSingleOutput(step.outputPath,
|
|
703
|
-
: { fullOutput:
|
|
819
|
+
? resolveSingleOutput(step.outputPath, outputForPersistence, finalOutputSnapshot)
|
|
820
|
+
: { fullOutput: outputForPersistence };
|
|
704
821
|
const output = resolvedOutput.fullOutput;
|
|
705
822
|
const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, output) : undefined;
|
|
706
823
|
let outputForSummary = output;
|
|
707
824
|
if (attemptNotes.length > 0) {
|
|
708
825
|
outputForSummary = `${attemptNotes.join("\n")}\n\n${outputForSummary}`.trim();
|
|
709
826
|
}
|
|
827
|
+
const outputForAcceptance = rawOutput;
|
|
710
828
|
const finalizedOutput = finalizeSingleOutput({
|
|
711
829
|
fullOutput: outputForSummary,
|
|
712
830
|
outputPath: step.outputPath,
|
|
@@ -717,6 +835,123 @@ async function runSingleStep(
|
|
|
717
835
|
saveError: resolvedOutput.saveError,
|
|
718
836
|
});
|
|
719
837
|
outputForSummary = finalizedOutput.displayOutput;
|
|
838
|
+
const acceptanceForInitialReport = step.effectiveAcceptance && shouldRunAcceptanceFinalization(step.effectiveAcceptance)
|
|
839
|
+
? acceptanceSelfReviewConfig(step.effectiveAcceptance)
|
|
840
|
+
: step.effectiveAcceptance;
|
|
841
|
+
let acceptance = acceptanceForInitialReport
|
|
842
|
+
? await evaluateAcceptance({
|
|
843
|
+
acceptance: acceptanceForInitialReport,
|
|
844
|
+
output: outputForAcceptance,
|
|
845
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
846
|
+
})
|
|
847
|
+
: undefined;
|
|
848
|
+
if (acceptance && step.effectiveAcceptance && shouldRunAcceptanceFinalization(step.effectiveAcceptance) && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted) {
|
|
849
|
+
const sessionFile = step.sessionFile ?? (sessionDir ? findLatestSessionFile(sessionDir) ?? undefined : undefined);
|
|
850
|
+
const maxTurns = step.effectiveAcceptance.finalization.maxTurns;
|
|
851
|
+
const turns: AcceptanceFinalizationTurn[] = [];
|
|
852
|
+
if (!sessionFile) {
|
|
853
|
+
const message = "Acceptance finalization requires a session file for same-session continuation.";
|
|
854
|
+
turns.push(createFinalizationProcessFailureTurn({ turn: 1, prompt: "", message }));
|
|
855
|
+
acceptance = buildFinalizationProcessFailureLedger({ initialLedger: acceptance, turns, maxTurns, message });
|
|
856
|
+
} else {
|
|
857
|
+
const selfReviewAcceptance = acceptanceSelfReviewConfig(step.effectiveAcceptance);
|
|
858
|
+
let previousFailure = acceptanceFailureMessage(acceptance);
|
|
859
|
+
let authoritativeLedger = acceptance;
|
|
860
|
+
for (let turn = 1; turn <= maxTurns; turn++) {
|
|
861
|
+
const prompt = formatAcceptanceFinalizationPrompt({
|
|
862
|
+
acceptance: step.effectiveAcceptance,
|
|
863
|
+
initialOutput: outputForAcceptance,
|
|
864
|
+
initialLedger: acceptance,
|
|
865
|
+
turn,
|
|
866
|
+
maxTurns,
|
|
867
|
+
...(previousFailure ? { previousFailure } : {}),
|
|
868
|
+
});
|
|
869
|
+
const { args, env, tempDir } = buildPiArgs({
|
|
870
|
+
baseArgs: ["--mode", "json", "-p"],
|
|
871
|
+
task: prompt,
|
|
872
|
+
sessionEnabled: true,
|
|
873
|
+
sessionFile,
|
|
874
|
+
model: finalResult?.model ?? step.model,
|
|
875
|
+
thinking: step.thinking,
|
|
876
|
+
inheritProjectContext: step.inheritProjectContext,
|
|
877
|
+
inheritSkills: step.inheritSkills,
|
|
878
|
+
tools: step.tools,
|
|
879
|
+
extensions: step.extensions,
|
|
880
|
+
systemPrompt: step.systemPrompt,
|
|
881
|
+
systemPromptMode: step.systemPromptMode,
|
|
882
|
+
mcpDirectTools: step.mcpDirectTools,
|
|
883
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
884
|
+
promptFileStem: `${step.agent}-acceptance-finalization`,
|
|
885
|
+
intercomSessionName: ctx.childIntercomTarget,
|
|
886
|
+
orchestratorIntercomTarget: ctx.orchestratorIntercomTarget,
|
|
887
|
+
runId: ctx.id,
|
|
888
|
+
childAgentName: step.agent,
|
|
889
|
+
childIndex: ctx.flatIndex,
|
|
890
|
+
parentEventSink: ctx.nestedRoute?.eventSink,
|
|
891
|
+
parentControlInbox: ctx.nestedRoute?.controlInbox,
|
|
892
|
+
parentRootRunId: ctx.nestedRoute?.rootRunId,
|
|
893
|
+
parentCapabilityToken: ctx.nestedRoute?.capabilityToken,
|
|
894
|
+
});
|
|
895
|
+
ctx.onAttemptStart?.({ model: finalResult?.model ?? step.model, thinking: resolveEffectiveThinking(finalResult?.model ?? step.model, step.thinking) });
|
|
896
|
+
const finalizationRun = await runPiStreaming(
|
|
897
|
+
args,
|
|
898
|
+
step.cwd ?? ctx.cwd,
|
|
899
|
+
`${ctx.outputFile}.finalization-${turn}.log`,
|
|
900
|
+
env,
|
|
901
|
+
ctx.piPackageRoot,
|
|
902
|
+
ctx.piArgv1,
|
|
903
|
+
step.maxSubagentDepth,
|
|
904
|
+
{ eventsPath, runId: ctx.id, stepIndex: ctx.flatIndex, agent: step.agent },
|
|
905
|
+
ctx.registerInterrupt,
|
|
906
|
+
ctx.onChildEvent,
|
|
907
|
+
step.maxExecutionTimeMs,
|
|
908
|
+
step.maxTokens,
|
|
909
|
+
);
|
|
910
|
+
cleanupTempDir(tempDir);
|
|
911
|
+
modelAttempts.push({
|
|
912
|
+
model: finalResult?.model ?? finalizationRun.model ?? step.model ?? "default",
|
|
913
|
+
success: finalizationRun.exitCode === 0 && !finalizationRun.error,
|
|
914
|
+
exitCode: finalizationRun.exitCode,
|
|
915
|
+
error: finalizationRun.error,
|
|
916
|
+
usage: finalizationRun.usage,
|
|
917
|
+
});
|
|
918
|
+
const finalizationOutput = finalizationRun.finalOutput;
|
|
919
|
+
if (finalizationRun.exitCode !== 0 || finalizationRun.error || finalizationRun.interrupted) {
|
|
920
|
+
const message = finalizationRun.error ?? "Acceptance finalization turn did not complete successfully.";
|
|
921
|
+
turns.push(createFinalizationProcessFailureTurn({ turn, prompt, rawOutput: finalizationOutput, message }));
|
|
922
|
+
acceptance = buildFinalizationProcessFailureLedger({ initialLedger: acceptance, turns, maxTurns, message });
|
|
923
|
+
break;
|
|
924
|
+
}
|
|
925
|
+
const selfReviewLedger = await evaluateAcceptance({
|
|
926
|
+
acceptance: selfReviewAcceptance,
|
|
927
|
+
output: finalizationOutput,
|
|
928
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
929
|
+
});
|
|
930
|
+
authoritativeLedger = selfReviewLedger;
|
|
931
|
+
turns.push(createFinalizationTurn({ turn, prompt, rawOutput: finalizationOutput, ledger: selfReviewLedger }));
|
|
932
|
+
const failure = acceptanceFailureMessage(selfReviewLedger);
|
|
933
|
+
if (!failure) {
|
|
934
|
+
authoritativeLedger = step.effectiveAcceptance === selfReviewAcceptance
|
|
935
|
+
? selfReviewLedger
|
|
936
|
+
: await evaluateAcceptance({
|
|
937
|
+
acceptance: step.effectiveAcceptance,
|
|
938
|
+
output: finalizationOutput,
|
|
939
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
940
|
+
});
|
|
941
|
+
acceptance = attachFinalizationToLedger({ initialLedger: acceptance, authoritativeLedger, turns, status: "completed", maxTurns });
|
|
942
|
+
break;
|
|
943
|
+
}
|
|
944
|
+
previousFailure = failure;
|
|
945
|
+
if (turn === maxTurns) acceptance = attachFinalizationToLedger({ initialLedger: acceptance, authoritativeLedger, turns, status: "failed", maxTurns });
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
const acceptanceFailure = acceptance ? acceptanceFailureMessage(acceptance) : undefined;
|
|
950
|
+
const acceptanceCanFailRun = acceptanceFailure && acceptance?.explicit && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted;
|
|
951
|
+
const effectiveFinalExitCode = acceptanceCanFailRun ? 1 : finalResult?.exitCode ?? 1;
|
|
952
|
+
const effectiveFinalError = acceptanceCanFailRun
|
|
953
|
+
? (finalResult?.error ? `${finalResult.error}\n${acceptanceFailure}` : acceptanceFailure)
|
|
954
|
+
: finalResult?.error;
|
|
720
955
|
|
|
721
956
|
if (artifactPaths && ctx.artifactConfig?.enabled !== false) {
|
|
722
957
|
if (ctx.artifactConfig?.includeOutput !== false) {
|
|
@@ -729,10 +964,11 @@ async function runSingleStep(
|
|
|
729
964
|
runId: ctx.id,
|
|
730
965
|
agent: step.agent,
|
|
731
966
|
task,
|
|
732
|
-
exitCode:
|
|
967
|
+
exitCode: effectiveFinalExitCode,
|
|
733
968
|
model: finalResult?.model,
|
|
734
969
|
attemptedModels: attemptedModels.length > 0 ? attemptedModels : undefined,
|
|
735
970
|
modelAttempts,
|
|
971
|
+
resourceLimitExceeded: finalResult?.resourceLimitExceeded,
|
|
736
972
|
skills: step.skills,
|
|
737
973
|
timestamp: Date.now(),
|
|
738
974
|
}, null, 2),
|
|
@@ -744,8 +980,8 @@ async function runSingleStep(
|
|
|
744
980
|
return {
|
|
745
981
|
agent: step.agent,
|
|
746
982
|
output: outputForSummary,
|
|
747
|
-
exitCode:
|
|
748
|
-
error:
|
|
983
|
+
exitCode: effectiveFinalExitCode,
|
|
984
|
+
error: effectiveFinalError,
|
|
749
985
|
sessionFile: step.sessionFile,
|
|
750
986
|
intercomTarget: ctx.childIntercomTarget,
|
|
751
987
|
model: finalResult?.model,
|
|
@@ -754,6 +990,11 @@ async function runSingleStep(
|
|
|
754
990
|
artifactPaths,
|
|
755
991
|
interrupted: finalResult?.interrupted,
|
|
756
992
|
completionGuardTriggered: completionGuardTriggeredFinal,
|
|
993
|
+
structuredOutput: (finalResult as (RunPiStreamingResult & { structuredOutput?: unknown }) | undefined)?.structuredOutput,
|
|
994
|
+
structuredOutputPath: effectiveStructuredOutput?.outputPath,
|
|
995
|
+
structuredOutputSchemaPath: effectiveStructuredOutput?.schemaPath,
|
|
996
|
+
acceptance,
|
|
997
|
+
resourceLimitExceeded: finalResult?.resourceLimitExceeded,
|
|
757
998
|
};
|
|
758
999
|
}
|
|
759
1000
|
|
|
@@ -796,7 +1037,7 @@ function markParallelGroupSetupFailure(input: {
|
|
|
796
1037
|
input.statusPayload.steps[flatTaskIndex].endedAt = input.failedAt;
|
|
797
1038
|
input.statusPayload.steps[flatTaskIndex].durationMs = 0;
|
|
798
1039
|
input.statusPayload.steps[flatTaskIndex].exitCode = 1;
|
|
799
|
-
input.results.push({ agent: input.group.parallel[taskIndex].agent, output: input.setupError, success: false, sessionFile: input.group.parallel[taskIndex].sessionFile });
|
|
1040
|
+
input.results.push({ agent: input.group.parallel[taskIndex].agent, output: input.setupError, success: false, exitCode: 1, sessionFile: input.group.parallel[taskIndex].sessionFile });
|
|
800
1041
|
}
|
|
801
1042
|
input.statusPayload.currentStep = input.groupStartFlatIndex;
|
|
802
1043
|
input.statusPayload.lastUpdate = input.failedAt;
|
|
@@ -886,6 +1127,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
886
1127
|
const { id, steps, resultPath, cwd, placeholder, taskIndex, totalTasks, maxOutput, artifactsDir, artifactConfig } =
|
|
887
1128
|
config;
|
|
888
1129
|
let previousOutput = "";
|
|
1130
|
+
const outputs: ChainOutputMap = {};
|
|
889
1131
|
const results: StepResult[] = [];
|
|
890
1132
|
const overallStartTime = Date.now();
|
|
891
1133
|
const shareEnabled = config.share === true;
|
|
@@ -902,13 +1144,59 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
902
1144
|
let latestSessionFile: string | undefined;
|
|
903
1145
|
|
|
904
1146
|
const parallelGroups: Array<{ start: number; count: number; stepIndex: number }> = [];
|
|
1147
|
+
const initialStatusSteps: RunnerStatusStep[] = [];
|
|
905
1148
|
let flatStepCount = 0;
|
|
906
1149
|
for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
|
|
907
1150
|
const step = steps[stepIndex]!;
|
|
908
1151
|
if (isParallelGroup(step)) {
|
|
909
1152
|
parallelGroups.push({ start: flatStepCount, count: step.parallel.length, stepIndex });
|
|
1153
|
+
for (const task of step.parallel) {
|
|
1154
|
+
initialStatusSteps.push({
|
|
1155
|
+
agent: task.agent,
|
|
1156
|
+
phase: task.phase,
|
|
1157
|
+
label: task.label,
|
|
1158
|
+
outputName: task.outputName,
|
|
1159
|
+
structured: task.structured,
|
|
1160
|
+
status: "pending",
|
|
1161
|
+
...(task.sessionFile ? { sessionFile: task.sessionFile } : {}),
|
|
1162
|
+
skills: task.skills,
|
|
1163
|
+
model: task.model,
|
|
1164
|
+
thinking: task.thinking,
|
|
1165
|
+
attemptedModels: task.modelCandidates && task.modelCandidates.length > 0 ? task.modelCandidates : task.model ? [task.model] : undefined,
|
|
1166
|
+
recentTools: [],
|
|
1167
|
+
recentOutput: [],
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
910
1170
|
flatStepCount += step.parallel.length;
|
|
1171
|
+
} else if (isDynamicRunnerGroup(step)) {
|
|
1172
|
+
parallelGroups.push({ start: flatStepCount, count: 1, stepIndex });
|
|
1173
|
+
initialStatusSteps.push({
|
|
1174
|
+
agent: `expand:${step.parallel.agent}`,
|
|
1175
|
+
phase: step.phase ?? step.parallel.phase,
|
|
1176
|
+
label: step.label ?? step.parallel.label ?? `Dynamic fanout (${step.collect.as})`,
|
|
1177
|
+
outputName: step.collect.as,
|
|
1178
|
+
structured: Boolean(step.collect.outputSchema),
|
|
1179
|
+
status: "pending",
|
|
1180
|
+
recentTools: [],
|
|
1181
|
+
recentOutput: [],
|
|
1182
|
+
});
|
|
1183
|
+
flatStepCount++;
|
|
911
1184
|
} else {
|
|
1185
|
+
initialStatusSteps.push({
|
|
1186
|
+
agent: step.agent,
|
|
1187
|
+
phase: step.phase,
|
|
1188
|
+
label: step.label,
|
|
1189
|
+
outputName: step.outputName,
|
|
1190
|
+
structured: step.structured,
|
|
1191
|
+
status: "pending",
|
|
1192
|
+
...(step.sessionFile ? { sessionFile: step.sessionFile } : {}),
|
|
1193
|
+
skills: step.skills,
|
|
1194
|
+
model: step.model,
|
|
1195
|
+
thinking: step.thinking,
|
|
1196
|
+
attemptedModels: step.modelCandidates && step.modelCandidates.length > 0 ? step.modelCandidates : step.model ? [step.model] : undefined,
|
|
1197
|
+
recentTools: [],
|
|
1198
|
+
recentOutput: [],
|
|
1199
|
+
});
|
|
912
1200
|
flatStepCount++;
|
|
913
1201
|
}
|
|
914
1202
|
}
|
|
@@ -929,17 +1217,8 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
929
1217
|
currentStep: 0,
|
|
930
1218
|
chainStepCount: steps.length,
|
|
931
1219
|
parallelGroups,
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
status: "pending",
|
|
935
|
-
...(step.sessionFile ? { sessionFile: step.sessionFile } : {}),
|
|
936
|
-
skills: step.skills,
|
|
937
|
-
model: step.model,
|
|
938
|
-
thinking: step.thinking,
|
|
939
|
-
attemptedModels: step.modelCandidates && step.modelCandidates.length > 0 ? step.modelCandidates : step.model ? [step.model] : undefined,
|
|
940
|
-
recentTools: [],
|
|
941
|
-
recentOutput: [],
|
|
942
|
-
})),
|
|
1220
|
+
workflowGraph: config.workflowGraph,
|
|
1221
|
+
steps: initialStatusSteps,
|
|
943
1222
|
artifactsDir,
|
|
944
1223
|
sessionDir: config.sessionDir,
|
|
945
1224
|
outputFile: path.join(asyncDir, "output-0.log"),
|
|
@@ -969,10 +1248,48 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
969
1248
|
console.error("Failed to emit nested async status event:", error);
|
|
970
1249
|
}
|
|
971
1250
|
};
|
|
1251
|
+
const refreshWorkflowGraph = (): void => {
|
|
1252
|
+
if (!config.workflowGraph) return;
|
|
1253
|
+
const graph = structuredClone(statusPayload.workflowGraph ?? config.workflowGraph);
|
|
1254
|
+
const normalize = (status: RunnerStatusStep["status"]): "pending" | "running" | "completed" | "failed" | "paused" | "detached" => {
|
|
1255
|
+
if (status === "complete" || status === "completed") return "completed";
|
|
1256
|
+
if (status === "running" || status === "failed" || status === "paused" || status === "pending") return status;
|
|
1257
|
+
return "pending";
|
|
1258
|
+
};
|
|
1259
|
+
const updateNode = (node: NonNullable<typeof graph.nodes>[number]): void => {
|
|
1260
|
+
if (node.flatIndex !== undefined) {
|
|
1261
|
+
const step = statusPayload.steps[node.flatIndex];
|
|
1262
|
+
if (step) {
|
|
1263
|
+
node.status = normalize(step.status);
|
|
1264
|
+
node.error = step.error;
|
|
1265
|
+
node.acceptanceStatus = step.acceptance?.status;
|
|
1266
|
+
}
|
|
1267
|
+
if (statusPayload.currentStep === node.flatIndex) graph.currentNodeId = node.id;
|
|
1268
|
+
}
|
|
1269
|
+
for (const child of node.children ?? []) updateNode(child);
|
|
1270
|
+
if (node.children?.length) {
|
|
1271
|
+
if (node.children.every((child) => child.status === "completed")) node.status = "completed";
|
|
1272
|
+
else if (node.children.some((child) => child.status === "running")) node.status = "running";
|
|
1273
|
+
else if (node.children.some((child) => child.status === "failed")) node.status = "failed";
|
|
1274
|
+
else if (node.children.some((child) => child.status === "paused")) node.status = "paused";
|
|
1275
|
+
}
|
|
1276
|
+
if (node.error) node.status = "failed";
|
|
1277
|
+
};
|
|
1278
|
+
for (const node of graph.nodes) updateNode(node);
|
|
1279
|
+
statusPayload.workflowGraph = graph;
|
|
1280
|
+
};
|
|
972
1281
|
const writeStatusPayload = (): void => {
|
|
1282
|
+
refreshWorkflowGraph();
|
|
973
1283
|
writeAtomicJson(statusPath, statusPayload);
|
|
974
1284
|
emitNestedSelfEvent(statusPayload.state === "running" || statusPayload.state === "queued" ? "subagent.nested.updated" : "subagent.nested.completed");
|
|
975
1285
|
};
|
|
1286
|
+
const markDynamicGraphGroup = (stepIndex: number, status: "completed" | "failed" | "running", error?: string, acceptance?: AcceptanceLedger): void => {
|
|
1287
|
+
const groupNode = statusPayload.workflowGraph?.nodes.find((node) => node.id === `step-${stepIndex}`);
|
|
1288
|
+
if (!groupNode) return;
|
|
1289
|
+
groupNode.status = status;
|
|
1290
|
+
groupNode.error = error;
|
|
1291
|
+
groupNode.acceptanceStatus = acceptance?.status ?? groupNode.acceptanceStatus;
|
|
1292
|
+
};
|
|
976
1293
|
|
|
977
1294
|
const stepOutputActivityAt = (index: number): number => {
|
|
978
1295
|
const step = statusPayload.steps[index];
|
|
@@ -989,8 +1306,8 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
989
1306
|
};
|
|
990
1307
|
const emittedControlEventKeys = new Set<string>();
|
|
991
1308
|
const activeLongRunningSteps = new Set<number>();
|
|
992
|
-
const mutatingFailureStates =
|
|
993
|
-
const pendingToolResults: Array<{ tool: string; path?: string; mutates: boolean; startedAt?: number } | undefined> =
|
|
1309
|
+
const mutatingFailureStates = initialStatusSteps.map(() => createMutatingFailureState());
|
|
1310
|
+
const pendingToolResults: Array<{ tool: string; path?: string; mutates: boolean; startedAt?: number } | undefined> = initialStatusSteps.map(() => undefined);
|
|
994
1311
|
const mutatingFailureWindowMs = 5 * 60_000;
|
|
995
1312
|
const appendControlEvent = (event: ReturnType<typeof buildControlEvent>) => {
|
|
996
1313
|
if (!controlConfig.enabled) return;
|
|
@@ -1131,7 +1448,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1131
1448
|
resetMutatingFailureState(mutatingFailureStates[flatIndex]!);
|
|
1132
1449
|
}
|
|
1133
1450
|
} else if (event.type === "message_end" && event.message?.role === "assistant") {
|
|
1134
|
-
appendRecentStepOutput(step, extractTextFromContent(event.message.content).split("\n").slice(-10));
|
|
1451
|
+
appendRecentStepOutput(step, stripAcceptanceReport(extractTextFromContent(event.message.content)).split("\n").slice(-10));
|
|
1135
1452
|
step.turnCount = (step.turnCount ?? 0) + 1;
|
|
1136
1453
|
const usage = event.message.usage;
|
|
1137
1454
|
if (usage) {
|
|
@@ -1262,6 +1579,265 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1262
1579
|
if (interrupted) break;
|
|
1263
1580
|
const step = steps[stepIndex];
|
|
1264
1581
|
|
|
1582
|
+
if (isDynamicRunnerGroup(step)) {
|
|
1583
|
+
const groupStartFlatIndex = flatIndex;
|
|
1584
|
+
let materialized: ReturnType<typeof materializeDynamicParallelStep>;
|
|
1585
|
+
try {
|
|
1586
|
+
materialized = materializeDynamicParallelStep(step as Parameters<typeof materializeDynamicParallelStep>[0], outputs, stepIndex, { maxItems: config.dynamicFanoutMaxItems, allowRunnerFields: true });
|
|
1587
|
+
if (materialized.collectedOnEmpty) validateDynamicCollection(step.collect.outputSchema, materialized.collectedOnEmpty);
|
|
1588
|
+
} catch (error) {
|
|
1589
|
+
const now = Date.now();
|
|
1590
|
+
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
1591
|
+
statusPayload.state = "failed";
|
|
1592
|
+
statusPayload.error = message;
|
|
1593
|
+
statusPayload.currentStep = flatIndex;
|
|
1594
|
+
const placeholder = statusPayload.steps[groupStartFlatIndex];
|
|
1595
|
+
if (placeholder) {
|
|
1596
|
+
placeholder.status = "failed";
|
|
1597
|
+
placeholder.error = message;
|
|
1598
|
+
placeholder.startedAt = now;
|
|
1599
|
+
placeholder.endedAt = now;
|
|
1600
|
+
placeholder.durationMs = 0;
|
|
1601
|
+
placeholder.exitCode = 1;
|
|
1602
|
+
}
|
|
1603
|
+
statusPayload.lastUpdate = now;
|
|
1604
|
+
markDynamicGraphGroup(stepIndex, "failed", message);
|
|
1605
|
+
writeStatusPayload();
|
|
1606
|
+
results.push({ agent: step.parallel.agent, output: message, error: message, success: false, exitCode: 1 });
|
|
1607
|
+
break;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
if (materialized.parallel.length === 0) {
|
|
1611
|
+
const now = Date.now();
|
|
1612
|
+
const collection = materialized.collectedOnEmpty ?? [];
|
|
1613
|
+
outputs[step.collect.as] = {
|
|
1614
|
+
text: JSON.stringify(collection),
|
|
1615
|
+
structured: collection,
|
|
1616
|
+
agent: step.parallel.agent,
|
|
1617
|
+
stepIndex,
|
|
1618
|
+
};
|
|
1619
|
+
statusPayload.outputs = outputs;
|
|
1620
|
+
const placeholder = statusPayload.steps[groupStartFlatIndex];
|
|
1621
|
+
if (placeholder) {
|
|
1622
|
+
placeholder.status = "complete";
|
|
1623
|
+
placeholder.startedAt = now;
|
|
1624
|
+
placeholder.endedAt = now;
|
|
1625
|
+
placeholder.durationMs = 0;
|
|
1626
|
+
}
|
|
1627
|
+
previousOutput = "Dynamic fanout produced 0 results.";
|
|
1628
|
+
flatIndex++;
|
|
1629
|
+
statusPayload.lastUpdate = now;
|
|
1630
|
+
markDynamicGraphGroup(stepIndex, "completed");
|
|
1631
|
+
writeStatusPayload();
|
|
1632
|
+
continue;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
const dynamicSteps = materialized.parallel.map((task, itemIndex) => ({
|
|
1636
|
+
...step.parallel,
|
|
1637
|
+
task: task.task ?? step.parallel.task,
|
|
1638
|
+
label: task.label ?? step.parallel.label,
|
|
1639
|
+
structuredOutput: undefined,
|
|
1640
|
+
structuredOutputSchema: step.parallel.structuredOutputSchema ?? step.parallel.structuredOutput?.schema,
|
|
1641
|
+
}));
|
|
1642
|
+
const dynamicStatusSteps: RunnerStatusStep[] = dynamicSteps.map((task) => ({
|
|
1643
|
+
agent: task.agent,
|
|
1644
|
+
phase: task.phase ?? step.phase,
|
|
1645
|
+
label: task.label,
|
|
1646
|
+
outputName: undefined,
|
|
1647
|
+
structured: Boolean(task.structuredOutputSchema),
|
|
1648
|
+
status: "pending",
|
|
1649
|
+
...(task.sessionFile ? { sessionFile: task.sessionFile } : {}),
|
|
1650
|
+
skills: task.skills,
|
|
1651
|
+
model: task.model,
|
|
1652
|
+
thinking: task.thinking,
|
|
1653
|
+
attemptedModels: task.modelCandidates && task.modelCandidates.length > 0 ? task.modelCandidates : task.model ? [task.model] : undefined,
|
|
1654
|
+
recentTools: [],
|
|
1655
|
+
recentOutput: [],
|
|
1656
|
+
}));
|
|
1657
|
+
statusPayload.steps.splice(groupStartFlatIndex, 1, ...dynamicStatusSteps);
|
|
1658
|
+
if (config.childIntercomTargets) {
|
|
1659
|
+
config.childIntercomTargets = statusPayload.steps.map((statusStep, index) => resolveSubagentIntercomTarget(id, statusStep.agent, index));
|
|
1660
|
+
}
|
|
1661
|
+
mutatingFailureStates.splice(groupStartFlatIndex, 1, ...dynamicStatusSteps.map(() => createMutatingFailureState()));
|
|
1662
|
+
pendingToolResults.splice(groupStartFlatIndex, 1, ...dynamicStatusSteps.map(() => undefined));
|
|
1663
|
+
const materializedDelta = dynamicStatusSteps.length - 1;
|
|
1664
|
+
for (const group of statusPayload.parallelGroups) {
|
|
1665
|
+
if (group.stepIndex === stepIndex) {
|
|
1666
|
+
group.start = groupStartFlatIndex;
|
|
1667
|
+
group.count = dynamicStatusSteps.length;
|
|
1668
|
+
} else if (group.start > groupStartFlatIndex) {
|
|
1669
|
+
group.start += materializedDelta;
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
if (statusPayload.workflowGraph) {
|
|
1673
|
+
const shiftFlatIndexes = (nodes: NonNullable<typeof statusPayload.workflowGraph>["nodes"]): void => {
|
|
1674
|
+
for (const node of nodes) {
|
|
1675
|
+
if (node.stepIndex !== undefined && node.stepIndex > stepIndex && node.flatIndex !== undefined && node.flatIndex >= groupStartFlatIndex) {
|
|
1676
|
+
node.flatIndex += dynamicStatusSteps.length;
|
|
1677
|
+
}
|
|
1678
|
+
if (node.children) shiftFlatIndexes(node.children);
|
|
1679
|
+
}
|
|
1680
|
+
};
|
|
1681
|
+
shiftFlatIndexes(statusPayload.workflowGraph.nodes);
|
|
1682
|
+
const groupNode = statusPayload.workflowGraph.nodes.find((node) => node.id === `step-${stepIndex}`);
|
|
1683
|
+
if (groupNode) {
|
|
1684
|
+
groupNode.children = materialized.items.map((item, itemIndex) => ({
|
|
1685
|
+
id: `step-${stepIndex}-item-${item.idKey}`,
|
|
1686
|
+
kind: "agent",
|
|
1687
|
+
agent: step.parallel.agent,
|
|
1688
|
+
phase: dynamicSteps[itemIndex]?.phase ?? step.phase,
|
|
1689
|
+
label: dynamicSteps[itemIndex]?.label?.trim() || `${step.parallel.agent} ${item.key}`,
|
|
1690
|
+
status: "pending",
|
|
1691
|
+
flatIndex: groupStartFlatIndex + itemIndex,
|
|
1692
|
+
stepIndex,
|
|
1693
|
+
itemKey: item.key,
|
|
1694
|
+
structured: Boolean(dynamicSteps[itemIndex]?.structuredOutputSchema),
|
|
1695
|
+
}));
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
writeStatusPayload();
|
|
1699
|
+
|
|
1700
|
+
const concurrency = step.concurrency ?? MAX_PARALLEL_CONCURRENCY;
|
|
1701
|
+
const failFast = step.failFast ?? false;
|
|
1702
|
+
let aborted = false;
|
|
1703
|
+
const parallelResults = await mapConcurrent(dynamicSteps, concurrency, async (task, taskIdx) => {
|
|
1704
|
+
const fi = groupStartFlatIndex + taskIdx;
|
|
1705
|
+
if (aborted && failFast) {
|
|
1706
|
+
const skippedAt = Date.now();
|
|
1707
|
+
statusPayload.steps[fi].status = "failed";
|
|
1708
|
+
statusPayload.steps[fi].error = "Skipped due to fail-fast";
|
|
1709
|
+
statusPayload.steps[fi].startedAt = skippedAt;
|
|
1710
|
+
statusPayload.steps[fi].endedAt = skippedAt;
|
|
1711
|
+
statusPayload.steps[fi].durationMs = 0;
|
|
1712
|
+
statusPayload.steps[fi].exitCode = -1;
|
|
1713
|
+
statusPayload.lastUpdate = skippedAt;
|
|
1714
|
+
writeStatusPayload();
|
|
1715
|
+
return { agent: task.agent, output: "(skipped — fail-fast)", exitCode: -1 as number | null, skipped: true };
|
|
1716
|
+
}
|
|
1717
|
+
const taskStartTime = Date.now();
|
|
1718
|
+
statusPayload.currentStep = fi;
|
|
1719
|
+
statusPayload.steps[fi].status = "running";
|
|
1720
|
+
statusPayload.steps[fi].error = undefined;
|
|
1721
|
+
statusPayload.steps[fi].activityState = undefined;
|
|
1722
|
+
resetStepLiveDetail(statusPayload.steps[fi]);
|
|
1723
|
+
statusPayload.steps[fi].startedAt = taskStartTime;
|
|
1724
|
+
statusPayload.steps[fi].lastActivityAt = taskStartTime;
|
|
1725
|
+
statusPayload.outputFile = path.join(asyncDir, `output-${fi}.log`);
|
|
1726
|
+
statusPayload.lastActivityAt = taskStartTime;
|
|
1727
|
+
statusPayload.lastUpdate = taskStartTime;
|
|
1728
|
+
writeStatusPayload();
|
|
1729
|
+
appendJsonl(eventsPath, JSON.stringify({ type: "subagent.step.started", ts: taskStartTime, runId: id, stepIndex: fi, agent: task.agent }));
|
|
1730
|
+
const singleResult = await runSingleStep(task, {
|
|
1731
|
+
previousOutput, placeholder, cwd, sessionEnabled,
|
|
1732
|
+
outputs,
|
|
1733
|
+
sessionDir: config.sessionDir ? path.join(config.sessionDir, `dynamic-${stepIndex}-${taskIdx}`) : undefined,
|
|
1734
|
+
artifactsDir, artifactConfig, id,
|
|
1735
|
+
flatIndex: fi, flatStepCount: Math.max(statusPayload.steps.length, 1),
|
|
1736
|
+
outputFile: path.join(asyncDir, `output-${fi}.log`),
|
|
1737
|
+
piPackageRoot: config.piPackageRoot,
|
|
1738
|
+
piArgv1: config.piArgv1,
|
|
1739
|
+
childIntercomTarget: config.childIntercomTargets?.[fi],
|
|
1740
|
+
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
1741
|
+
nestedRoute: config.nestedRoute,
|
|
1742
|
+
registerInterrupt: (interrupt) => {
|
|
1743
|
+
activeChildInterrupt = interrupt;
|
|
1744
|
+
},
|
|
1745
|
+
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking),
|
|
1746
|
+
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
1747
|
+
});
|
|
1748
|
+
const taskEndTime = Date.now();
|
|
1749
|
+
statusPayload.steps[fi].status = singleResult.exitCode === 0 ? "complete" : "failed";
|
|
1750
|
+
statusPayload.steps[fi].endedAt = taskEndTime;
|
|
1751
|
+
statusPayload.steps[fi].durationMs = taskEndTime - taskStartTime;
|
|
1752
|
+
statusPayload.steps[fi].exitCode = singleResult.exitCode;
|
|
1753
|
+
statusPayload.steps[fi].model = singleResult.model;
|
|
1754
|
+
statusPayload.steps[fi].thinking = resolveEffectiveThinking(singleResult.model, statusPayload.steps[fi].thinking);
|
|
1755
|
+
statusPayload.steps[fi].attemptedModels = singleResult.attemptedModels;
|
|
1756
|
+
statusPayload.steps[fi].modelAttempts = singleResult.modelAttempts;
|
|
1757
|
+
statusPayload.steps[fi].error = singleResult.error;
|
|
1758
|
+
statusPayload.steps[fi].structuredOutput = singleResult.structuredOutput;
|
|
1759
|
+
statusPayload.steps[fi].structuredOutputPath = singleResult.structuredOutputPath;
|
|
1760
|
+
statusPayload.steps[fi].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
1761
|
+
statusPayload.steps[fi].acceptance = singleResult.acceptance;
|
|
1762
|
+
statusPayload.steps[fi].resourceLimitExceeded = singleResult.resourceLimitExceeded;
|
|
1763
|
+
statusPayload.lastUpdate = taskEndTime;
|
|
1764
|
+
writeStatusPayload();
|
|
1765
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
1766
|
+
type: singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
1767
|
+
ts: taskEndTime, runId: id, stepIndex: fi, agent: task.agent,
|
|
1768
|
+
exitCode: singleResult.exitCode, durationMs: taskEndTime - taskStartTime,
|
|
1769
|
+
resourceLimitExceeded: singleResult.resourceLimitExceeded,
|
|
1770
|
+
}));
|
|
1771
|
+
if (singleResult.exitCode !== 0 && failFast) aborted = true;
|
|
1772
|
+
return { ...singleResult, skipped: false };
|
|
1773
|
+
});
|
|
1774
|
+
|
|
1775
|
+
flatIndex += dynamicSteps.length;
|
|
1776
|
+
for (const pr of parallelResults) {
|
|
1777
|
+
results.push({
|
|
1778
|
+
agent: pr.agent,
|
|
1779
|
+
output: pr.output,
|
|
1780
|
+
error: pr.error,
|
|
1781
|
+
success: pr.exitCode === 0,
|
|
1782
|
+
exitCode: pr.exitCode,
|
|
1783
|
+
skipped: pr.skipped,
|
|
1784
|
+
sessionFile: pr.sessionFile,
|
|
1785
|
+
intercomTarget: pr.intercomTarget,
|
|
1786
|
+
model: pr.model,
|
|
1787
|
+
attemptedModels: pr.attemptedModels,
|
|
1788
|
+
modelAttempts: pr.modelAttempts,
|
|
1789
|
+
artifactPaths: pr.artifactPaths,
|
|
1790
|
+
structuredOutput: pr.structuredOutput,
|
|
1791
|
+
structuredOutputPath: pr.structuredOutputPath,
|
|
1792
|
+
structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
|
|
1793
|
+
acceptance: pr.acceptance,
|
|
1794
|
+
resourceLimitExceeded: pr.resourceLimitExceeded,
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
const collection = collectDynamicResults(step as Parameters<typeof collectDynamicResults>[0], materialized.items, parallelResults);
|
|
1798
|
+
const failures = parallelResults.filter((result) => result.exitCode !== 0 && result.exitCode !== -1);
|
|
1799
|
+
if (failures.length === 0) {
|
|
1800
|
+
try {
|
|
1801
|
+
validateDynamicCollection(step.collect.outputSchema, collection);
|
|
1802
|
+
outputs[step.collect.as] = {
|
|
1803
|
+
text: JSON.stringify(collection),
|
|
1804
|
+
structured: collection,
|
|
1805
|
+
agent: step.parallel.agent,
|
|
1806
|
+
stepIndex,
|
|
1807
|
+
};
|
|
1808
|
+
statusPayload.outputs = outputs;
|
|
1809
|
+
markDynamicGraphGroup(stepIndex, "completed");
|
|
1810
|
+
} catch (error) {
|
|
1811
|
+
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
1812
|
+
results.push({ agent: step.parallel.agent, output: message, error: message, success: false, exitCode: 1, structuredOutput: collection });
|
|
1813
|
+
statusPayload.error = message;
|
|
1814
|
+
markDynamicGraphGroup(stepIndex, "failed", message);
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
previousOutput = aggregateParallelOutputs(
|
|
1818
|
+
parallelResults.map((r, i) => ({
|
|
1819
|
+
agent: r.agent,
|
|
1820
|
+
taskIndex: i,
|
|
1821
|
+
output: r.output,
|
|
1822
|
+
exitCode: r.exitCode,
|
|
1823
|
+
error: r.error,
|
|
1824
|
+
})),
|
|
1825
|
+
(i, agent) => `=== Dynamic Item ${i + 1} (${agent}, key ${materialized.items[i]?.key ?? i}) ===`,
|
|
1826
|
+
);
|
|
1827
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
1828
|
+
type: "subagent.dynamic.completed",
|
|
1829
|
+
ts: Date.now(),
|
|
1830
|
+
runId: id,
|
|
1831
|
+
stepIndex,
|
|
1832
|
+
success: failures.length === 0,
|
|
1833
|
+
}));
|
|
1834
|
+
if (failures.length > 0) markDynamicGraphGroup(stepIndex, "failed", failures[0]?.error ?? "Dynamic fanout child failed.");
|
|
1835
|
+
statusPayload.lastUpdate = Date.now();
|
|
1836
|
+
writeStatusPayload();
|
|
1837
|
+
if (failures.length > 0 || statusPayload.error) break;
|
|
1838
|
+
continue;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1265
1841
|
if (isParallelGroup(step)) {
|
|
1266
1842
|
const group = step;
|
|
1267
1843
|
const concurrency = group.concurrency ?? MAX_PARALLEL_CONCURRENCY;
|
|
@@ -1379,6 +1955,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1379
1955
|
|
|
1380
1956
|
const singleResult = await runSingleStep(taskForRun, {
|
|
1381
1957
|
previousOutput, placeholder, cwd: taskCwd, sessionEnabled,
|
|
1958
|
+
outputs,
|
|
1382
1959
|
sessionDir: taskSessionDir,
|
|
1383
1960
|
artifactsDir, artifactConfig, id,
|
|
1384
1961
|
flatIndex: fi, flatStepCount: flatSteps.length,
|
|
@@ -1410,6 +1987,11 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1410
1987
|
statusPayload.steps[fi].attemptedModels = singleResult.attemptedModels;
|
|
1411
1988
|
statusPayload.steps[fi].modelAttempts = singleResult.modelAttempts;
|
|
1412
1989
|
statusPayload.steps[fi].error = singleResult.error;
|
|
1990
|
+
statusPayload.steps[fi].structuredOutput = singleResult.structuredOutput;
|
|
1991
|
+
statusPayload.steps[fi].structuredOutputPath = singleResult.structuredOutputPath;
|
|
1992
|
+
statusPayload.steps[fi].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
1993
|
+
statusPayload.steps[fi].acceptance = singleResult.acceptance;
|
|
1994
|
+
statusPayload.steps[fi].resourceLimitExceeded = singleResult.resourceLimitExceeded;
|
|
1413
1995
|
statusPayload.lastUpdate = taskEndTime;
|
|
1414
1996
|
writeStatusPayload();
|
|
1415
1997
|
|
|
@@ -1417,6 +1999,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1417
1999
|
type: singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
1418
2000
|
ts: taskEndTime, runId: id, stepIndex: fi, agent: task.agent,
|
|
1419
2001
|
exitCode: singleResult.exitCode, durationMs: taskDuration,
|
|
2002
|
+
resourceLimitExceeded: singleResult.resourceLimitExceeded,
|
|
1420
2003
|
}));
|
|
1421
2004
|
if (singleResult.completionGuardTriggered) {
|
|
1422
2005
|
const event = buildControlEvent({
|
|
@@ -1463,6 +2046,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1463
2046
|
output: pr.output,
|
|
1464
2047
|
error: pr.error,
|
|
1465
2048
|
success: pr.exitCode === 0,
|
|
2049
|
+
exitCode: pr.exitCode,
|
|
1466
2050
|
skipped: pr.skipped,
|
|
1467
2051
|
sessionFile: pr.sessionFile,
|
|
1468
2052
|
intercomTarget: pr.intercomTarget,
|
|
@@ -1470,8 +2054,22 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1470
2054
|
attemptedModels: pr.attemptedModels,
|
|
1471
2055
|
modelAttempts: pr.modelAttempts,
|
|
1472
2056
|
artifactPaths: pr.artifactPaths,
|
|
1473
|
-
|
|
2057
|
+
structuredOutput: pr.structuredOutput,
|
|
2058
|
+
structuredOutputPath: pr.structuredOutputPath,
|
|
2059
|
+
structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
|
|
2060
|
+
acceptance: pr.acceptance,
|
|
2061
|
+
resourceLimitExceeded: pr.resourceLimitExceeded,
|
|
2062
|
+
});
|
|
2063
|
+
}
|
|
2064
|
+
for (let t = 0; t < group.parallel.length; t++) {
|
|
2065
|
+
const outputName = group.parallel[t]?.outputName;
|
|
2066
|
+
if (outputName) outputs[outputName] = outputEntryFromAsyncResult({
|
|
2067
|
+
agent: parallelResults[t]!.agent,
|
|
2068
|
+
output: parallelResults[t]!.output,
|
|
2069
|
+
structuredOutput: parallelResults[t]!.structuredOutput,
|
|
2070
|
+
}, stepIndex);
|
|
1474
2071
|
}
|
|
2072
|
+
statusPayload.outputs = outputs;
|
|
1475
2073
|
|
|
1476
2074
|
previousOutput = aggregateParallelOutputs(
|
|
1477
2075
|
parallelResults.map((r) => ({
|
|
@@ -1525,6 +2123,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1525
2123
|
|
|
1526
2124
|
const singleResult = await runSingleStep(seqStep, {
|
|
1527
2125
|
previousOutput, placeholder, cwd, sessionEnabled,
|
|
2126
|
+
outputs,
|
|
1528
2127
|
sessionDir: config.sessionDir,
|
|
1529
2128
|
artifactsDir, artifactConfig, id,
|
|
1530
2129
|
flatIndex, flatStepCount: flatSteps.length,
|
|
@@ -1550,13 +2149,27 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1550
2149
|
output: singleResult.output,
|
|
1551
2150
|
error: singleResult.error,
|
|
1552
2151
|
success: singleResult.exitCode === 0,
|
|
2152
|
+
exitCode: singleResult.exitCode,
|
|
1553
2153
|
sessionFile: singleResult.sessionFile,
|
|
1554
2154
|
intercomTarget: singleResult.intercomTarget,
|
|
1555
2155
|
model: singleResult.model,
|
|
1556
2156
|
attemptedModels: singleResult.attemptedModels,
|
|
1557
2157
|
modelAttempts: singleResult.modelAttempts,
|
|
1558
2158
|
artifactPaths: singleResult.artifactPaths,
|
|
2159
|
+
structuredOutput: singleResult.structuredOutput,
|
|
2160
|
+
structuredOutputPath: singleResult.structuredOutputPath,
|
|
2161
|
+
structuredOutputSchemaPath: singleResult.structuredOutputSchemaPath,
|
|
2162
|
+
acceptance: singleResult.acceptance,
|
|
2163
|
+
resourceLimitExceeded: singleResult.resourceLimitExceeded,
|
|
1559
2164
|
});
|
|
2165
|
+
if (seqStep.outputName) {
|
|
2166
|
+
outputs[seqStep.outputName] = outputEntryFromAsyncResult({
|
|
2167
|
+
agent: singleResult.agent,
|
|
2168
|
+
output: singleResult.output,
|
|
2169
|
+
structuredOutput: singleResult.structuredOutput,
|
|
2170
|
+
}, stepIndex);
|
|
2171
|
+
}
|
|
2172
|
+
statusPayload.outputs = outputs;
|
|
1560
2173
|
|
|
1561
2174
|
const cumulativeTokens = config.sessionDir ? parseSessionTokens(config.sessionDir) : null;
|
|
1562
2175
|
let stepTokens: TokenUsage | null = cumulativeTokens
|
|
@@ -1589,6 +2202,11 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1589
2202
|
statusPayload.steps[flatIndex].attemptedModels = singleResult.attemptedModels;
|
|
1590
2203
|
statusPayload.steps[flatIndex].modelAttempts = singleResult.modelAttempts;
|
|
1591
2204
|
statusPayload.steps[flatIndex].error = singleResult.error;
|
|
2205
|
+
statusPayload.steps[flatIndex].structuredOutput = singleResult.structuredOutput;
|
|
2206
|
+
statusPayload.steps[flatIndex].structuredOutputPath = singleResult.structuredOutputPath;
|
|
2207
|
+
statusPayload.steps[flatIndex].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
2208
|
+
statusPayload.steps[flatIndex].acceptance = singleResult.acceptance;
|
|
2209
|
+
statusPayload.steps[flatIndex].resourceLimitExceeded = singleResult.resourceLimitExceeded;
|
|
1592
2210
|
if (stepTokens) {
|
|
1593
2211
|
statusPayload.steps[flatIndex].tokens = stepTokens;
|
|
1594
2212
|
statusPayload.totalTokens = { ...previousCumulativeTokens };
|
|
@@ -1605,6 +2223,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1605
2223
|
exitCode: singleResult.exitCode,
|
|
1606
2224
|
durationMs: stepEndTime - stepStartTime,
|
|
1607
2225
|
tokens: stepTokens,
|
|
2226
|
+
resourceLimitExceeded: singleResult.resourceLimitExceeded,
|
|
1608
2227
|
}));
|
|
1609
2228
|
if (singleResult.completionGuardTriggered) {
|
|
1610
2229
|
const event = buildControlEvent({
|
|
@@ -1690,7 +2309,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1690
2309
|
statusPayload.shareUrl = shareUrl;
|
|
1691
2310
|
statusPayload.gistUrl = gistUrl;
|
|
1692
2311
|
statusPayload.shareError = shareError;
|
|
1693
|
-
if (statusPayload.state === "failed") {
|
|
2312
|
+
if (statusPayload.state === "failed" && !statusPayload.error) {
|
|
1694
2313
|
const failedStep = statusPayload.steps.find((s) => s.status === "failed");
|
|
1695
2314
|
if (failedStep?.agent) {
|
|
1696
2315
|
statusPayload.error = `Step failed: ${failedStep.agent}`;
|
|
@@ -1747,7 +2366,14 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1747
2366
|
modelAttempts: r.modelAttempts,
|
|
1748
2367
|
artifactPaths: r.artifactPaths,
|
|
1749
2368
|
truncated: r.truncated,
|
|
2369
|
+
structuredOutput: r.structuredOutput,
|
|
2370
|
+
structuredOutputPath: r.structuredOutputPath,
|
|
2371
|
+
structuredOutputSchemaPath: r.structuredOutputSchemaPath,
|
|
2372
|
+
acceptance: r.acceptance,
|
|
2373
|
+
resourceLimitExceeded: r.resourceLimitExceeded,
|
|
1750
2374
|
})),
|
|
2375
|
+
outputs,
|
|
2376
|
+
workflowGraph: statusPayload.workflowGraph,
|
|
1751
2377
|
exitCode: interrupted || results.every((r) => r.success) ? 0 : 1,
|
|
1752
2378
|
timestamp: runEndedAt,
|
|
1753
2379
|
durationMs: runEndedAt - overallStartTime,
|