pi-subagents 0.24.4 → 0.27.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 +29 -0
- package/README.md +145 -27
- package/package.json +1 -1
- package/prompts/parallel-context-build.md +3 -1
- package/prompts/parallel-handoff-plan.md +3 -1
- package/prompts/review-loop.md +1 -1
- package/skills/pi-subagents/SKILL.md +71 -20
- package/src/agents/agent-management.ts +57 -15
- package/src/agents/agent-serializer.ts +3 -2
- package/src/agents/agents.ts +47 -16
- package/src/agents/chain-serializer.ts +120 -0
- package/src/extension/fanout-child.ts +171 -0
- package/src/extension/index.ts +7 -2
- package/src/extension/schemas.ts +138 -5
- package/src/intercom/result-intercom.ts +108 -0
- package/src/runs/background/async-execution.ts +185 -10
- package/src/runs/background/async-job-tracker.ts +41 -6
- package/src/runs/background/async-resume.ts +28 -15
- package/src/runs/background/async-status.ts +71 -31
- package/src/runs/background/result-watcher.ts +111 -54
- package/src/runs/background/run-id-resolver.ts +83 -0
- package/src/runs/background/run-status.ts +89 -4
- package/src/runs/background/stale-run-reconciler.ts +46 -1
- package/src/runs/background/subagent-runner.ts +648 -42
- package/src/runs/foreground/chain-execution.ts +331 -118
- package/src/runs/foreground/execution.ts +226 -10
- package/src/runs/foreground/subagent-executor.ts +377 -14
- package/src/runs/shared/acceptance-contract.ts +291 -0
- package/src/runs/shared/acceptance-evaluation.ts +221 -0
- package/src/runs/shared/acceptance-finalization.ts +161 -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/nested-events.ts +819 -0
- package/src/runs/shared/nested-path.ts +52 -0
- package/src/runs/shared/nested-render.ts +115 -0
- package/src/runs/shared/parallel-utils.ts +31 -1
- package/src/runs/shared/pi-args.ts +73 -5
- package/src/runs/shared/structured-output.ts +77 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +77 -7
- package/src/runs/shared/workflow-graph.ts +206 -0
- package/src/shared/formatters.ts +2 -2
- package/src/shared/settings.ts +53 -4
- package/src/shared/types.ts +345 -0
- package/src/slash/slash-commands.ts +41 -3
- package/src/tui/render.ts +268 -43
|
@@ -8,15 +8,21 @@ 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,
|
|
20
|
+
type NestedRouteInfo,
|
|
17
21
|
type ResolvedControlConfig,
|
|
18
22
|
type SubagentRunMode,
|
|
23
|
+
type TokenUsage,
|
|
19
24
|
type Usage,
|
|
25
|
+
type WorkflowGraphSnapshot,
|
|
20
26
|
DEFAULT_MAX_OUTPUT,
|
|
21
27
|
type MaxOutputConfig,
|
|
22
28
|
truncateOutput,
|
|
@@ -33,6 +39,7 @@ import {
|
|
|
33
39
|
import {
|
|
34
40
|
type RunnerSubagentStep as SubagentStep,
|
|
35
41
|
type RunnerStep,
|
|
42
|
+
isDynamicRunnerGroup,
|
|
36
43
|
isParallelGroup,
|
|
37
44
|
flattenSteps,
|
|
38
45
|
mapConcurrent,
|
|
@@ -40,10 +47,14 @@ import {
|
|
|
40
47
|
MAX_PARALLEL_CONCURRENCY,
|
|
41
48
|
} from "../shared/parallel-utils.ts";
|
|
42
49
|
import { buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
|
|
50
|
+
import { outputEntryFromAsyncResult, resolveOutputReferences } from "../shared/chain-outputs.ts";
|
|
51
|
+
import { createStructuredOutputRuntime, readStructuredOutput } from "../shared/structured-output.ts";
|
|
52
|
+
import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection } from "../shared/dynamic-fanout.ts";
|
|
53
|
+
import { nestedSummaryFromAsyncStatus, writeNestedEvent } from "../shared/nested-events.ts";
|
|
43
54
|
import { formatModelAttemptNote, isRetryableModelFailure } from "../shared/model-fallback.ts";
|
|
44
55
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
45
56
|
import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput } from "../../shared/utils.ts";
|
|
46
|
-
import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
|
|
57
|
+
import { evaluateCompletionMutationGuard, resolveCompletionPolicy } from "../shared/completion-guard.ts";
|
|
47
58
|
import {
|
|
48
59
|
createMutatingFailureState,
|
|
49
60
|
didMutatingToolFail,
|
|
@@ -56,7 +67,6 @@ import {
|
|
|
56
67
|
summarizeRecentMutatingFailures,
|
|
57
68
|
} from "../shared/long-running-guard.ts";
|
|
58
69
|
import { parseSessionTokens } from "../../shared/session-tokens.ts";
|
|
59
|
-
import type { TokenUsage } from "../../shared/types.ts";
|
|
60
70
|
import {
|
|
61
71
|
cleanupWorktrees,
|
|
62
72
|
createWorktrees,
|
|
@@ -68,6 +78,20 @@ import {
|
|
|
68
78
|
} from "../shared/worktree.ts";
|
|
69
79
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
70
80
|
import { writeInitialProgressFile } from "../../shared/settings.ts";
|
|
81
|
+
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
82
|
+
import {
|
|
83
|
+
acceptanceFailureMessage,
|
|
84
|
+
acceptanceSelfReviewConfig,
|
|
85
|
+
attachFinalizationToLedger,
|
|
86
|
+
buildFinalizationProcessFailureLedger,
|
|
87
|
+
createFinalizationProcessFailureTurn,
|
|
88
|
+
createFinalizationTurn,
|
|
89
|
+
evaluateAcceptance,
|
|
90
|
+
formatAcceptanceFinalizationPrompt,
|
|
91
|
+
formatAcceptancePrompt,
|
|
92
|
+
shouldRunAcceptanceFinalization,
|
|
93
|
+
stripAcceptanceReport,
|
|
94
|
+
} from "../shared/acceptance.ts";
|
|
71
95
|
|
|
72
96
|
interface SubagentRunConfig {
|
|
73
97
|
id: string;
|
|
@@ -92,6 +116,10 @@ interface SubagentRunConfig {
|
|
|
92
116
|
controlIntercomTarget?: string;
|
|
93
117
|
childIntercomTargets?: Array<string | undefined>;
|
|
94
118
|
resultMode?: SubagentRunMode;
|
|
119
|
+
dynamicFanoutMaxItems?: number;
|
|
120
|
+
workflowGraph?: WorkflowGraphSnapshot;
|
|
121
|
+
nestedRoute?: NestedRouteInfo;
|
|
122
|
+
nestedSelf?: { parentRunId: string; parentStepIndex?: number; depth: number; path?: Array<{ runId: string; stepIndex?: number; agent?: string }> };
|
|
95
123
|
}
|
|
96
124
|
|
|
97
125
|
interface StepResult {
|
|
@@ -99,6 +127,7 @@ interface StepResult {
|
|
|
99
127
|
output: string;
|
|
100
128
|
error?: string;
|
|
101
129
|
success: boolean;
|
|
130
|
+
exitCode?: number | null;
|
|
102
131
|
skipped?: boolean;
|
|
103
132
|
sessionFile?: string;
|
|
104
133
|
intercomTarget?: string;
|
|
@@ -107,6 +136,10 @@ interface StepResult {
|
|
|
107
136
|
modelAttempts?: ModelAttempt[];
|
|
108
137
|
artifactPaths?: ArtifactPaths;
|
|
109
138
|
truncated?: boolean;
|
|
139
|
+
structuredOutput?: unknown;
|
|
140
|
+
structuredOutputPath?: string;
|
|
141
|
+
structuredOutputSchemaPath?: string;
|
|
142
|
+
acceptance?: AcceptanceLedger;
|
|
110
143
|
}
|
|
111
144
|
|
|
112
145
|
const ASYNC_INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2";
|
|
@@ -539,6 +572,7 @@ function writeRunLog(
|
|
|
539
572
|
/** Context for running a single step */
|
|
540
573
|
interface SingleStepContext {
|
|
541
574
|
previousOutput: string;
|
|
575
|
+
outputs?: ChainOutputMap;
|
|
542
576
|
placeholder: string;
|
|
543
577
|
cwd: string;
|
|
544
578
|
sessionEnabled: boolean;
|
|
@@ -554,6 +588,7 @@ interface SingleStepContext {
|
|
|
554
588
|
registerInterrupt?: (interrupt: (() => void) | undefined) => void;
|
|
555
589
|
childIntercomTarget?: string;
|
|
556
590
|
orchestratorIntercomTarget?: string;
|
|
591
|
+
nestedRoute?: NestedRouteInfo;
|
|
557
592
|
onAttemptStart?: (attempt: { model?: string; thinking?: string }) => void;
|
|
558
593
|
onChildEvent?: (event: ChildEvent) => void;
|
|
559
594
|
}
|
|
@@ -575,9 +610,22 @@ async function runSingleStep(
|
|
|
575
610
|
sessionFile?: string;
|
|
576
611
|
intercomTarget?: string;
|
|
577
612
|
completionGuardTriggered?: boolean;
|
|
613
|
+
structuredOutput?: unknown;
|
|
614
|
+
structuredOutputPath?: string;
|
|
615
|
+
structuredOutputSchemaPath?: string;
|
|
616
|
+
acceptance?: AcceptanceLedger;
|
|
578
617
|
}> {
|
|
618
|
+
const effectiveStructuredOutput = step.structuredOutput ?? (step.structuredOutputSchema
|
|
619
|
+
? createStructuredOutputRuntime(step.structuredOutputSchema, path.join(path.dirname(ctx.outputFile), "structured-output"))
|
|
620
|
+
: undefined);
|
|
579
621
|
const placeholderRegex = new RegExp(ctx.placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
|
|
580
|
-
|
|
622
|
+
let task = step.task.replace(placeholderRegex, () => ctx.previousOutput);
|
|
623
|
+
task = resolveOutputReferences(task, ctx.outputs ?? {});
|
|
624
|
+
const taskForCompletionGuard = task;
|
|
625
|
+
if (step.effectiveAcceptance) {
|
|
626
|
+
const acceptancePrompt = formatAcceptancePrompt(step.effectiveAcceptance);
|
|
627
|
+
if (acceptancePrompt) task = `${task}\n${acceptancePrompt}`;
|
|
628
|
+
}
|
|
581
629
|
const sessionEnabled = Boolean(step.sessionFile) || ctx.sessionEnabled;
|
|
582
630
|
const sessionDir = step.sessionFile ? undefined : ctx.sessionDir;
|
|
583
631
|
|
|
@@ -608,6 +656,13 @@ async function runSingleStep(
|
|
|
608
656
|
const candidate = candidates[index];
|
|
609
657
|
ctx.onAttemptStart?.({ model: candidate, thinking: resolveEffectiveThinking(candidate, step.thinking) });
|
|
610
658
|
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
659
|
+
if (effectiveStructuredOutput) {
|
|
660
|
+
try {
|
|
661
|
+
if (fs.existsSync(effectiveStructuredOutput.outputPath)) fs.unlinkSync(effectiveStructuredOutput.outputPath);
|
|
662
|
+
} catch {
|
|
663
|
+
// Missing/stale structured-output files are handled after the child exits.
|
|
664
|
+
}
|
|
665
|
+
}
|
|
611
666
|
const { args, env, tempDir } = buildPiArgs({
|
|
612
667
|
baseArgs: ["--mode", "json", "-p"],
|
|
613
668
|
task,
|
|
@@ -629,6 +684,11 @@ async function runSingleStep(
|
|
|
629
684
|
runId: ctx.id,
|
|
630
685
|
childAgentName: step.agent,
|
|
631
686
|
childIndex: ctx.flatIndex,
|
|
687
|
+
parentEventSink: ctx.nestedRoute?.eventSink,
|
|
688
|
+
parentControlInbox: ctx.nestedRoute?.controlInbox,
|
|
689
|
+
parentRootRunId: ctx.nestedRoute?.rootRunId,
|
|
690
|
+
parentCapabilityToken: ctx.nestedRoute?.capabilityToken,
|
|
691
|
+
structuredOutput: effectiveStructuredOutput,
|
|
632
692
|
});
|
|
633
693
|
const run = await runPiStreaming(
|
|
634
694
|
args,
|
|
@@ -645,10 +705,29 @@ async function runSingleStep(
|
|
|
645
705
|
cleanupTempDir(tempDir);
|
|
646
706
|
|
|
647
707
|
const hiddenError = run.exitCode === 0 && !run.error ? detectSubagentError(run.messages) : null;
|
|
648
|
-
|
|
708
|
+
let structuredOutput: unknown;
|
|
709
|
+
let structuredError: string | undefined;
|
|
710
|
+
if (effectiveStructuredOutput && run.exitCode === 0 && !run.error && !hiddenError?.hasError) {
|
|
711
|
+
const structured = readStructuredOutput({
|
|
712
|
+
schema: effectiveStructuredOutput.schema,
|
|
713
|
+
schemaPath: effectiveStructuredOutput.schemaPath,
|
|
714
|
+
outputPath: effectiveStructuredOutput.outputPath,
|
|
715
|
+
});
|
|
716
|
+
if (structured.error) structuredError = structured.error;
|
|
717
|
+
else structuredOutput = structured.value;
|
|
718
|
+
}
|
|
719
|
+
const completionPolicy = resolveCompletionPolicy({
|
|
720
|
+
agent: step.agent,
|
|
721
|
+
task: taskForCompletionGuard,
|
|
722
|
+
completionGuardEnabled: step.completionGuard !== false,
|
|
723
|
+
usesAcceptanceContract: step.effectiveAcceptance?.explicit === true,
|
|
724
|
+
tools: step.tools,
|
|
725
|
+
mcpDirectTools: step.mcpDirectTools,
|
|
726
|
+
});
|
|
727
|
+
const completionGuard = run.exitCode === 0 && !run.error && !hiddenError?.hasError && completionPolicy === "mutation-guard"
|
|
649
728
|
? evaluateCompletionMutationGuard({
|
|
650
729
|
agent: step.agent,
|
|
651
|
-
task,
|
|
730
|
+
task: taskForCompletionGuard,
|
|
652
731
|
messages: run.messages,
|
|
653
732
|
tools: step.tools,
|
|
654
733
|
mcpDirectTools: step.mcpDirectTools,
|
|
@@ -658,14 +737,17 @@ async function runSingleStep(
|
|
|
658
737
|
const completionGuardError = completionGuardTriggered
|
|
659
738
|
? "Subagent completed without making edits for an implementation task.\nIt appears to have returned planning or scratchpad output instead of applying changes."
|
|
660
739
|
: undefined;
|
|
661
|
-
const effectiveExitCode =
|
|
740
|
+
const effectiveExitCode = completionGuardError
|
|
662
741
|
? 1
|
|
663
|
-
:
|
|
742
|
+
: structuredError
|
|
743
|
+
? 1
|
|
744
|
+
: hiddenError?.hasError
|
|
664
745
|
? (hiddenError.exitCode ?? 1)
|
|
665
746
|
: run.error && run.exitCode === 0
|
|
666
747
|
? 1
|
|
667
748
|
: run.exitCode;
|
|
668
749
|
const error = completionGuardError
|
|
750
|
+
?? structuredError
|
|
669
751
|
?? (hiddenError?.hasError
|
|
670
752
|
? hiddenError.details
|
|
671
753
|
? `${hiddenError.errorType} failed (exit ${effectiveExitCode}): ${hiddenError.details}`
|
|
@@ -682,22 +764,24 @@ async function runSingleStep(
|
|
|
682
764
|
if (candidate) attemptedModels.push(candidate);
|
|
683
765
|
completionGuardTriggeredFinal = completionGuardTriggered;
|
|
684
766
|
finalOutputSnapshot = outputSnapshot;
|
|
685
|
-
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error };
|
|
686
|
-
if (attempt.success ||
|
|
767
|
+
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput } as RunPiStreamingResult & { structuredOutput?: unknown };
|
|
768
|
+
if (attempt.success || completionGuardError) break;
|
|
687
769
|
if (!isRetryableModelFailure(error) || index === candidates.length - 1) break;
|
|
688
770
|
attemptNotes.push(formatModelAttemptNote(attempt, candidates[index + 1]));
|
|
689
771
|
}
|
|
690
772
|
|
|
691
773
|
const rawOutput = finalResult?.finalOutput ?? "";
|
|
774
|
+
const outputForPersistence = stripAcceptanceReport(rawOutput);
|
|
692
775
|
const resolvedOutput = step.outputPath && finalResult?.exitCode === 0
|
|
693
|
-
? resolveSingleOutput(step.outputPath,
|
|
694
|
-
: { fullOutput:
|
|
776
|
+
? resolveSingleOutput(step.outputPath, outputForPersistence, finalOutputSnapshot)
|
|
777
|
+
: { fullOutput: outputForPersistence };
|
|
695
778
|
const output = resolvedOutput.fullOutput;
|
|
696
779
|
const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, output) : undefined;
|
|
697
780
|
let outputForSummary = output;
|
|
698
781
|
if (attemptNotes.length > 0) {
|
|
699
782
|
outputForSummary = `${attemptNotes.join("\n")}\n\n${outputForSummary}`.trim();
|
|
700
783
|
}
|
|
784
|
+
const outputForAcceptance = rawOutput;
|
|
701
785
|
const finalizedOutput = finalizeSingleOutput({
|
|
702
786
|
fullOutput: outputForSummary,
|
|
703
787
|
outputPath: step.outputPath,
|
|
@@ -708,6 +792,121 @@ async function runSingleStep(
|
|
|
708
792
|
saveError: resolvedOutput.saveError,
|
|
709
793
|
});
|
|
710
794
|
outputForSummary = finalizedOutput.displayOutput;
|
|
795
|
+
const acceptanceForInitialReport = step.effectiveAcceptance && shouldRunAcceptanceFinalization(step.effectiveAcceptance)
|
|
796
|
+
? acceptanceSelfReviewConfig(step.effectiveAcceptance)
|
|
797
|
+
: step.effectiveAcceptance;
|
|
798
|
+
let acceptance = acceptanceForInitialReport
|
|
799
|
+
? await evaluateAcceptance({
|
|
800
|
+
acceptance: acceptanceForInitialReport,
|
|
801
|
+
output: outputForAcceptance,
|
|
802
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
803
|
+
})
|
|
804
|
+
: undefined;
|
|
805
|
+
if (acceptance && step.effectiveAcceptance && shouldRunAcceptanceFinalization(step.effectiveAcceptance) && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted) {
|
|
806
|
+
const sessionFile = step.sessionFile ?? (sessionDir ? findLatestSessionFile(sessionDir) ?? undefined : undefined);
|
|
807
|
+
const maxTurns = step.effectiveAcceptance.finalization.maxTurns;
|
|
808
|
+
const turns: AcceptanceFinalizationTurn[] = [];
|
|
809
|
+
if (!sessionFile) {
|
|
810
|
+
const message = "Acceptance finalization requires a session file for same-session continuation.";
|
|
811
|
+
turns.push(createFinalizationProcessFailureTurn({ turn: 1, prompt: "", message }));
|
|
812
|
+
acceptance = buildFinalizationProcessFailureLedger({ initialLedger: acceptance, turns, maxTurns, message });
|
|
813
|
+
} else {
|
|
814
|
+
const selfReviewAcceptance = acceptanceSelfReviewConfig(step.effectiveAcceptance);
|
|
815
|
+
let previousFailure = acceptanceFailureMessage(acceptance);
|
|
816
|
+
let authoritativeLedger = acceptance;
|
|
817
|
+
for (let turn = 1; turn <= maxTurns; turn++) {
|
|
818
|
+
const prompt = formatAcceptanceFinalizationPrompt({
|
|
819
|
+
acceptance: step.effectiveAcceptance,
|
|
820
|
+
initialOutput: outputForAcceptance,
|
|
821
|
+
initialLedger: acceptance,
|
|
822
|
+
turn,
|
|
823
|
+
maxTurns,
|
|
824
|
+
...(previousFailure ? { previousFailure } : {}),
|
|
825
|
+
});
|
|
826
|
+
const { args, env, tempDir } = buildPiArgs({
|
|
827
|
+
baseArgs: ["--mode", "json", "-p"],
|
|
828
|
+
task: prompt,
|
|
829
|
+
sessionEnabled: true,
|
|
830
|
+
sessionFile,
|
|
831
|
+
model: finalResult?.model ?? step.model,
|
|
832
|
+
thinking: step.thinking,
|
|
833
|
+
inheritProjectContext: step.inheritProjectContext,
|
|
834
|
+
inheritSkills: step.inheritSkills,
|
|
835
|
+
tools: step.tools,
|
|
836
|
+
extensions: step.extensions,
|
|
837
|
+
systemPrompt: step.systemPrompt,
|
|
838
|
+
systemPromptMode: step.systemPromptMode,
|
|
839
|
+
mcpDirectTools: step.mcpDirectTools,
|
|
840
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
841
|
+
promptFileStem: `${step.agent}-acceptance-finalization`,
|
|
842
|
+
intercomSessionName: ctx.childIntercomTarget,
|
|
843
|
+
orchestratorIntercomTarget: ctx.orchestratorIntercomTarget,
|
|
844
|
+
runId: ctx.id,
|
|
845
|
+
childAgentName: step.agent,
|
|
846
|
+
childIndex: ctx.flatIndex,
|
|
847
|
+
parentEventSink: ctx.nestedRoute?.eventSink,
|
|
848
|
+
parentControlInbox: ctx.nestedRoute?.controlInbox,
|
|
849
|
+
parentRootRunId: ctx.nestedRoute?.rootRunId,
|
|
850
|
+
parentCapabilityToken: ctx.nestedRoute?.capabilityToken,
|
|
851
|
+
});
|
|
852
|
+
ctx.onAttemptStart?.({ model: finalResult?.model ?? step.model, thinking: resolveEffectiveThinking(finalResult?.model ?? step.model, step.thinking) });
|
|
853
|
+
const finalizationRun = await runPiStreaming(
|
|
854
|
+
args,
|
|
855
|
+
step.cwd ?? ctx.cwd,
|
|
856
|
+
`${ctx.outputFile}.finalization-${turn}.log`,
|
|
857
|
+
env,
|
|
858
|
+
ctx.piPackageRoot,
|
|
859
|
+
ctx.piArgv1,
|
|
860
|
+
step.maxSubagentDepth,
|
|
861
|
+
{ eventsPath, runId: ctx.id, stepIndex: ctx.flatIndex, agent: step.agent },
|
|
862
|
+
ctx.registerInterrupt,
|
|
863
|
+
ctx.onChildEvent,
|
|
864
|
+
);
|
|
865
|
+
cleanupTempDir(tempDir);
|
|
866
|
+
modelAttempts.push({
|
|
867
|
+
model: finalResult?.model ?? finalizationRun.model ?? step.model ?? "default",
|
|
868
|
+
success: finalizationRun.exitCode === 0 && !finalizationRun.error,
|
|
869
|
+
exitCode: finalizationRun.exitCode,
|
|
870
|
+
error: finalizationRun.error,
|
|
871
|
+
usage: finalizationRun.usage,
|
|
872
|
+
});
|
|
873
|
+
const finalizationOutput = finalizationRun.finalOutput;
|
|
874
|
+
if (finalizationRun.exitCode !== 0 || finalizationRun.error || finalizationRun.interrupted) {
|
|
875
|
+
const message = finalizationRun.error ?? "Acceptance finalization turn did not complete successfully.";
|
|
876
|
+
turns.push(createFinalizationProcessFailureTurn({ turn, prompt, rawOutput: finalizationOutput, message }));
|
|
877
|
+
acceptance = buildFinalizationProcessFailureLedger({ initialLedger: acceptance, turns, maxTurns, message });
|
|
878
|
+
break;
|
|
879
|
+
}
|
|
880
|
+
const selfReviewLedger = await evaluateAcceptance({
|
|
881
|
+
acceptance: selfReviewAcceptance,
|
|
882
|
+
output: finalizationOutput,
|
|
883
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
884
|
+
});
|
|
885
|
+
authoritativeLedger = selfReviewLedger;
|
|
886
|
+
turns.push(createFinalizationTurn({ turn, prompt, rawOutput: finalizationOutput, ledger: selfReviewLedger }));
|
|
887
|
+
const failure = acceptanceFailureMessage(selfReviewLedger);
|
|
888
|
+
if (!failure) {
|
|
889
|
+
authoritativeLedger = step.effectiveAcceptance === selfReviewAcceptance
|
|
890
|
+
? selfReviewLedger
|
|
891
|
+
: await evaluateAcceptance({
|
|
892
|
+
acceptance: step.effectiveAcceptance,
|
|
893
|
+
output: finalizationOutput,
|
|
894
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
895
|
+
});
|
|
896
|
+
acceptance = attachFinalizationToLedger({ initialLedger: acceptance, authoritativeLedger, turns, status: "completed", maxTurns });
|
|
897
|
+
break;
|
|
898
|
+
}
|
|
899
|
+
previousFailure = failure;
|
|
900
|
+
if (turn === maxTurns) acceptance = attachFinalizationToLedger({ initialLedger: acceptance, authoritativeLedger, turns, status: "failed", maxTurns });
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
const acceptanceFailure = acceptance ? acceptanceFailureMessage(acceptance) : undefined;
|
|
905
|
+
const acceptanceCanFailRun = acceptanceFailure && acceptance?.explicit && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted;
|
|
906
|
+
const effectiveFinalExitCode = acceptanceCanFailRun ? 1 : finalResult?.exitCode ?? 1;
|
|
907
|
+
const effectiveFinalError = acceptanceCanFailRun
|
|
908
|
+
? (finalResult?.error ? `${finalResult.error}\n${acceptanceFailure}` : acceptanceFailure)
|
|
909
|
+
: finalResult?.error;
|
|
711
910
|
|
|
712
911
|
if (artifactPaths && ctx.artifactConfig?.enabled !== false) {
|
|
713
912
|
if (ctx.artifactConfig?.includeOutput !== false) {
|
|
@@ -720,7 +919,7 @@ async function runSingleStep(
|
|
|
720
919
|
runId: ctx.id,
|
|
721
920
|
agent: step.agent,
|
|
722
921
|
task,
|
|
723
|
-
exitCode:
|
|
922
|
+
exitCode: effectiveFinalExitCode,
|
|
724
923
|
model: finalResult?.model,
|
|
725
924
|
attemptedModels: attemptedModels.length > 0 ? attemptedModels : undefined,
|
|
726
925
|
modelAttempts,
|
|
@@ -735,8 +934,8 @@ async function runSingleStep(
|
|
|
735
934
|
return {
|
|
736
935
|
agent: step.agent,
|
|
737
936
|
output: outputForSummary,
|
|
738
|
-
exitCode:
|
|
739
|
-
error:
|
|
937
|
+
exitCode: effectiveFinalExitCode,
|
|
938
|
+
error: effectiveFinalError,
|
|
740
939
|
sessionFile: step.sessionFile,
|
|
741
940
|
intercomTarget: ctx.childIntercomTarget,
|
|
742
941
|
model: finalResult?.model,
|
|
@@ -745,6 +944,10 @@ async function runSingleStep(
|
|
|
745
944
|
artifactPaths,
|
|
746
945
|
interrupted: finalResult?.interrupted,
|
|
747
946
|
completionGuardTriggered: completionGuardTriggeredFinal,
|
|
947
|
+
structuredOutput: (finalResult as (RunPiStreamingResult & { structuredOutput?: unknown }) | undefined)?.structuredOutput,
|
|
948
|
+
structuredOutputPath: effectiveStructuredOutput?.outputPath,
|
|
949
|
+
structuredOutputSchemaPath: effectiveStructuredOutput?.schemaPath,
|
|
950
|
+
acceptance,
|
|
748
951
|
};
|
|
749
952
|
}
|
|
750
953
|
|
|
@@ -787,7 +990,7 @@ function markParallelGroupSetupFailure(input: {
|
|
|
787
990
|
input.statusPayload.steps[flatTaskIndex].endedAt = input.failedAt;
|
|
788
991
|
input.statusPayload.steps[flatTaskIndex].durationMs = 0;
|
|
789
992
|
input.statusPayload.steps[flatTaskIndex].exitCode = 1;
|
|
790
|
-
input.results.push({ agent: input.group.parallel[taskIndex].agent, output: input.setupError, success: false, sessionFile: input.group.parallel[taskIndex].sessionFile });
|
|
993
|
+
input.results.push({ agent: input.group.parallel[taskIndex].agent, output: input.setupError, success: false, exitCode: 1, sessionFile: input.group.parallel[taskIndex].sessionFile });
|
|
791
994
|
}
|
|
792
995
|
input.statusPayload.currentStep = input.groupStartFlatIndex;
|
|
793
996
|
input.statusPayload.lastUpdate = input.failedAt;
|
|
@@ -877,6 +1080,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
877
1080
|
const { id, steps, resultPath, cwd, placeholder, taskIndex, totalTasks, maxOutput, artifactsDir, artifactConfig } =
|
|
878
1081
|
config;
|
|
879
1082
|
let previousOutput = "";
|
|
1083
|
+
const outputs: ChainOutputMap = {};
|
|
880
1084
|
const results: StepResult[] = [];
|
|
881
1085
|
const overallStartTime = Date.now();
|
|
882
1086
|
const shareEnabled = config.share === true;
|
|
@@ -893,13 +1097,59 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
893
1097
|
let latestSessionFile: string | undefined;
|
|
894
1098
|
|
|
895
1099
|
const parallelGroups: Array<{ start: number; count: number; stepIndex: number }> = [];
|
|
1100
|
+
const initialStatusSteps: RunnerStatusStep[] = [];
|
|
896
1101
|
let flatStepCount = 0;
|
|
897
1102
|
for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
|
|
898
1103
|
const step = steps[stepIndex]!;
|
|
899
1104
|
if (isParallelGroup(step)) {
|
|
900
1105
|
parallelGroups.push({ start: flatStepCount, count: step.parallel.length, stepIndex });
|
|
1106
|
+
for (const task of step.parallel) {
|
|
1107
|
+
initialStatusSteps.push({
|
|
1108
|
+
agent: task.agent,
|
|
1109
|
+
phase: task.phase,
|
|
1110
|
+
label: task.label,
|
|
1111
|
+
outputName: task.outputName,
|
|
1112
|
+
structured: task.structured,
|
|
1113
|
+
status: "pending",
|
|
1114
|
+
...(task.sessionFile ? { sessionFile: task.sessionFile } : {}),
|
|
1115
|
+
skills: task.skills,
|
|
1116
|
+
model: task.model,
|
|
1117
|
+
thinking: task.thinking,
|
|
1118
|
+
attemptedModels: task.modelCandidates && task.modelCandidates.length > 0 ? task.modelCandidates : task.model ? [task.model] : undefined,
|
|
1119
|
+
recentTools: [],
|
|
1120
|
+
recentOutput: [],
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
901
1123
|
flatStepCount += step.parallel.length;
|
|
1124
|
+
} else if (isDynamicRunnerGroup(step)) {
|
|
1125
|
+
parallelGroups.push({ start: flatStepCount, count: 1, stepIndex });
|
|
1126
|
+
initialStatusSteps.push({
|
|
1127
|
+
agent: `expand:${step.parallel.agent}`,
|
|
1128
|
+
phase: step.phase ?? step.parallel.phase,
|
|
1129
|
+
label: step.label ?? step.parallel.label ?? `Dynamic fanout (${step.collect.as})`,
|
|
1130
|
+
outputName: step.collect.as,
|
|
1131
|
+
structured: Boolean(step.collect.outputSchema),
|
|
1132
|
+
status: "pending",
|
|
1133
|
+
recentTools: [],
|
|
1134
|
+
recentOutput: [],
|
|
1135
|
+
});
|
|
1136
|
+
flatStepCount++;
|
|
902
1137
|
} else {
|
|
1138
|
+
initialStatusSteps.push({
|
|
1139
|
+
agent: step.agent,
|
|
1140
|
+
phase: step.phase,
|
|
1141
|
+
label: step.label,
|
|
1142
|
+
outputName: step.outputName,
|
|
1143
|
+
structured: step.structured,
|
|
1144
|
+
status: "pending",
|
|
1145
|
+
...(step.sessionFile ? { sessionFile: step.sessionFile } : {}),
|
|
1146
|
+
skills: step.skills,
|
|
1147
|
+
model: step.model,
|
|
1148
|
+
thinking: step.thinking,
|
|
1149
|
+
attemptedModels: step.modelCandidates && step.modelCandidates.length > 0 ? step.modelCandidates : step.model ? [step.model] : undefined,
|
|
1150
|
+
recentTools: [],
|
|
1151
|
+
recentOutput: [],
|
|
1152
|
+
});
|
|
903
1153
|
flatStepCount++;
|
|
904
1154
|
}
|
|
905
1155
|
}
|
|
@@ -920,17 +1170,8 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
920
1170
|
currentStep: 0,
|
|
921
1171
|
chainStepCount: steps.length,
|
|
922
1172
|
parallelGroups,
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
status: "pending",
|
|
926
|
-
...(step.sessionFile ? { sessionFile: step.sessionFile } : {}),
|
|
927
|
-
skills: step.skills,
|
|
928
|
-
model: step.model,
|
|
929
|
-
thinking: step.thinking,
|
|
930
|
-
attemptedModels: step.modelCandidates && step.modelCandidates.length > 0 ? step.modelCandidates : step.model ? [step.model] : undefined,
|
|
931
|
-
recentTools: [],
|
|
932
|
-
recentOutput: [],
|
|
933
|
-
})),
|
|
1173
|
+
workflowGraph: config.workflowGraph,
|
|
1174
|
+
steps: initialStatusSteps,
|
|
934
1175
|
artifactsDir,
|
|
935
1176
|
sessionDir: config.sessionDir,
|
|
936
1177
|
outputFile: path.join(asyncDir, "output-0.log"),
|
|
@@ -938,6 +1179,70 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
938
1179
|
|
|
939
1180
|
fs.mkdirSync(asyncDir, { recursive: true });
|
|
940
1181
|
writeAtomicJson(statusPath, statusPayload);
|
|
1182
|
+
const emitNestedSelfEvent = (type: "subagent.nested.updated" | "subagent.nested.completed"): void => {
|
|
1183
|
+
if (!config.nestedRoute || !config.nestedSelf) return;
|
|
1184
|
+
try {
|
|
1185
|
+
writeNestedEvent(config.nestedRoute, {
|
|
1186
|
+
type,
|
|
1187
|
+
ts: Date.now(),
|
|
1188
|
+
parentRunId: config.nestedSelf.parentRunId,
|
|
1189
|
+
parentStepIndex: config.nestedSelf.parentStepIndex,
|
|
1190
|
+
child: nestedSummaryFromAsyncStatus(statusPayload, asyncDir, {
|
|
1191
|
+
id,
|
|
1192
|
+
parentRunId: config.nestedSelf.parentRunId,
|
|
1193
|
+
parentStepIndex: config.nestedSelf.parentStepIndex,
|
|
1194
|
+
depth: config.nestedSelf.depth,
|
|
1195
|
+
path: config.nestedSelf.path,
|
|
1196
|
+
mode: statusPayload.mode,
|
|
1197
|
+
ts: Date.now(),
|
|
1198
|
+
}),
|
|
1199
|
+
});
|
|
1200
|
+
} catch (error) {
|
|
1201
|
+
console.error("Failed to emit nested async status event:", error);
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
const refreshWorkflowGraph = (): void => {
|
|
1205
|
+
if (!config.workflowGraph) return;
|
|
1206
|
+
const graph = structuredClone(statusPayload.workflowGraph ?? config.workflowGraph);
|
|
1207
|
+
const normalize = (status: RunnerStatusStep["status"]): "pending" | "running" | "completed" | "failed" | "paused" | "detached" => {
|
|
1208
|
+
if (status === "complete" || status === "completed") return "completed";
|
|
1209
|
+
if (status === "running" || status === "failed" || status === "paused" || status === "pending") return status;
|
|
1210
|
+
return "pending";
|
|
1211
|
+
};
|
|
1212
|
+
const updateNode = (node: NonNullable<typeof graph.nodes>[number]): void => {
|
|
1213
|
+
if (node.flatIndex !== undefined) {
|
|
1214
|
+
const step = statusPayload.steps[node.flatIndex];
|
|
1215
|
+
if (step) {
|
|
1216
|
+
node.status = normalize(step.status);
|
|
1217
|
+
node.error = step.error;
|
|
1218
|
+
node.acceptanceStatus = step.acceptance?.status;
|
|
1219
|
+
}
|
|
1220
|
+
if (statusPayload.currentStep === node.flatIndex) graph.currentNodeId = node.id;
|
|
1221
|
+
}
|
|
1222
|
+
for (const child of node.children ?? []) updateNode(child);
|
|
1223
|
+
if (node.children?.length) {
|
|
1224
|
+
if (node.children.every((child) => child.status === "completed")) node.status = "completed";
|
|
1225
|
+
else if (node.children.some((child) => child.status === "running")) node.status = "running";
|
|
1226
|
+
else if (node.children.some((child) => child.status === "failed")) node.status = "failed";
|
|
1227
|
+
else if (node.children.some((child) => child.status === "paused")) node.status = "paused";
|
|
1228
|
+
}
|
|
1229
|
+
if (node.error) node.status = "failed";
|
|
1230
|
+
};
|
|
1231
|
+
for (const node of graph.nodes) updateNode(node);
|
|
1232
|
+
statusPayload.workflowGraph = graph;
|
|
1233
|
+
};
|
|
1234
|
+
const writeStatusPayload = (): void => {
|
|
1235
|
+
refreshWorkflowGraph();
|
|
1236
|
+
writeAtomicJson(statusPath, statusPayload);
|
|
1237
|
+
emitNestedSelfEvent(statusPayload.state === "running" || statusPayload.state === "queued" ? "subagent.nested.updated" : "subagent.nested.completed");
|
|
1238
|
+
};
|
|
1239
|
+
const markDynamicGraphGroup = (stepIndex: number, status: "completed" | "failed" | "running", error?: string, acceptance?: AcceptanceLedger): void => {
|
|
1240
|
+
const groupNode = statusPayload.workflowGraph?.nodes.find((node) => node.id === `step-${stepIndex}`);
|
|
1241
|
+
if (!groupNode) return;
|
|
1242
|
+
groupNode.status = status;
|
|
1243
|
+
groupNode.error = error;
|
|
1244
|
+
groupNode.acceptanceStatus = acceptance?.status ?? groupNode.acceptanceStatus;
|
|
1245
|
+
};
|
|
941
1246
|
|
|
942
1247
|
const stepOutputActivityAt = (index: number): number => {
|
|
943
1248
|
const step = statusPayload.steps[index];
|
|
@@ -954,8 +1259,8 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
954
1259
|
};
|
|
955
1260
|
const emittedControlEventKeys = new Set<string>();
|
|
956
1261
|
const activeLongRunningSteps = new Set<number>();
|
|
957
|
-
const mutatingFailureStates =
|
|
958
|
-
const pendingToolResults: Array<{ tool: string; path?: string; mutates: boolean; startedAt?: number } | undefined> =
|
|
1262
|
+
const mutatingFailureStates = initialStatusSteps.map(() => createMutatingFailureState());
|
|
1263
|
+
const pendingToolResults: Array<{ tool: string; path?: string; mutates: boolean; startedAt?: number } | undefined> = initialStatusSteps.map(() => undefined);
|
|
959
1264
|
const mutatingFailureWindowMs = 5 * 60_000;
|
|
960
1265
|
const appendControlEvent = (event: ReturnType<typeof buildControlEvent>) => {
|
|
961
1266
|
if (!controlConfig.enabled) return;
|
|
@@ -1028,7 +1333,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1028
1333
|
step.model = model;
|
|
1029
1334
|
step.thinking = thinking;
|
|
1030
1335
|
statusPayload.lastUpdate = now;
|
|
1031
|
-
|
|
1336
|
+
writeStatusPayload();
|
|
1032
1337
|
};
|
|
1033
1338
|
const updateStepFromChildEvent = (flatIndex: number, event: ChildEvent): void => {
|
|
1034
1339
|
const step = statusPayload.steps[flatIndex];
|
|
@@ -1096,7 +1401,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1096
1401
|
resetMutatingFailureState(mutatingFailureStates[flatIndex]!);
|
|
1097
1402
|
}
|
|
1098
1403
|
} else if (event.type === "message_end" && event.message?.role === "assistant") {
|
|
1099
|
-
appendRecentStepOutput(step, extractTextFromContent(event.message.content).split("\n").slice(-10));
|
|
1404
|
+
appendRecentStepOutput(step, stripAcceptanceReport(extractTextFromContent(event.message.content)).split("\n").slice(-10));
|
|
1100
1405
|
step.turnCount = (step.turnCount ?? 0) + 1;
|
|
1101
1406
|
const usage = event.message.usage;
|
|
1102
1407
|
if (usage) {
|
|
@@ -1116,7 +1421,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1116
1421
|
statusPayload.lastActivityAt = now;
|
|
1117
1422
|
statusPayload.lastUpdate = now;
|
|
1118
1423
|
maybeEmitActiveLongRunning(flatIndex, now);
|
|
1119
|
-
|
|
1424
|
+
writeStatusPayload();
|
|
1120
1425
|
};
|
|
1121
1426
|
const updateRunnerActivityState = (now: number): boolean => {
|
|
1122
1427
|
if (!controlConfig.enabled) return false;
|
|
@@ -1171,7 +1476,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1171
1476
|
changed = true;
|
|
1172
1477
|
}
|
|
1173
1478
|
statusPayload.lastUpdate = now;
|
|
1174
|
-
if (changed)
|
|
1479
|
+
if (changed) writeStatusPayload();
|
|
1175
1480
|
return changed;
|
|
1176
1481
|
};
|
|
1177
1482
|
if (controlConfig.enabled) {
|
|
@@ -1200,7 +1505,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1200
1505
|
step.lastActivityAt = now;
|
|
1201
1506
|
}
|
|
1202
1507
|
}
|
|
1203
|
-
|
|
1508
|
+
writeStatusPayload();
|
|
1204
1509
|
appendJsonl(eventsPath, JSON.stringify({
|
|
1205
1510
|
type: "subagent.run.paused",
|
|
1206
1511
|
ts: now,
|
|
@@ -1227,6 +1532,262 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1227
1532
|
if (interrupted) break;
|
|
1228
1533
|
const step = steps[stepIndex];
|
|
1229
1534
|
|
|
1535
|
+
if (isDynamicRunnerGroup(step)) {
|
|
1536
|
+
const groupStartFlatIndex = flatIndex;
|
|
1537
|
+
let materialized: ReturnType<typeof materializeDynamicParallelStep>;
|
|
1538
|
+
try {
|
|
1539
|
+
materialized = materializeDynamicParallelStep(step as Parameters<typeof materializeDynamicParallelStep>[0], outputs, stepIndex, { maxItems: config.dynamicFanoutMaxItems, allowRunnerFields: true });
|
|
1540
|
+
if (materialized.collectedOnEmpty) validateDynamicCollection(step.collect.outputSchema, materialized.collectedOnEmpty);
|
|
1541
|
+
} catch (error) {
|
|
1542
|
+
const now = Date.now();
|
|
1543
|
+
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
1544
|
+
statusPayload.state = "failed";
|
|
1545
|
+
statusPayload.error = message;
|
|
1546
|
+
statusPayload.currentStep = flatIndex;
|
|
1547
|
+
const placeholder = statusPayload.steps[groupStartFlatIndex];
|
|
1548
|
+
if (placeholder) {
|
|
1549
|
+
placeholder.status = "failed";
|
|
1550
|
+
placeholder.error = message;
|
|
1551
|
+
placeholder.startedAt = now;
|
|
1552
|
+
placeholder.endedAt = now;
|
|
1553
|
+
placeholder.durationMs = 0;
|
|
1554
|
+
placeholder.exitCode = 1;
|
|
1555
|
+
}
|
|
1556
|
+
statusPayload.lastUpdate = now;
|
|
1557
|
+
markDynamicGraphGroup(stepIndex, "failed", message);
|
|
1558
|
+
writeStatusPayload();
|
|
1559
|
+
results.push({ agent: step.parallel.agent, output: message, error: message, success: false, exitCode: 1 });
|
|
1560
|
+
break;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
if (materialized.parallel.length === 0) {
|
|
1564
|
+
const now = Date.now();
|
|
1565
|
+
const collection = materialized.collectedOnEmpty ?? [];
|
|
1566
|
+
outputs[step.collect.as] = {
|
|
1567
|
+
text: JSON.stringify(collection),
|
|
1568
|
+
structured: collection,
|
|
1569
|
+
agent: step.parallel.agent,
|
|
1570
|
+
stepIndex,
|
|
1571
|
+
};
|
|
1572
|
+
statusPayload.outputs = outputs;
|
|
1573
|
+
const placeholder = statusPayload.steps[groupStartFlatIndex];
|
|
1574
|
+
if (placeholder) {
|
|
1575
|
+
placeholder.status = "complete";
|
|
1576
|
+
placeholder.startedAt = now;
|
|
1577
|
+
placeholder.endedAt = now;
|
|
1578
|
+
placeholder.durationMs = 0;
|
|
1579
|
+
}
|
|
1580
|
+
previousOutput = "Dynamic fanout produced 0 results.";
|
|
1581
|
+
flatIndex++;
|
|
1582
|
+
statusPayload.lastUpdate = now;
|
|
1583
|
+
markDynamicGraphGroup(stepIndex, "completed");
|
|
1584
|
+
writeStatusPayload();
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
const dynamicSteps = materialized.parallel.map((task, itemIndex) => ({
|
|
1589
|
+
...step.parallel,
|
|
1590
|
+
task: task.task ?? step.parallel.task,
|
|
1591
|
+
label: task.label ?? step.parallel.label,
|
|
1592
|
+
structuredOutput: undefined,
|
|
1593
|
+
structuredOutputSchema: step.parallel.structuredOutputSchema ?? step.parallel.structuredOutput?.schema,
|
|
1594
|
+
}));
|
|
1595
|
+
const dynamicStatusSteps: RunnerStatusStep[] = dynamicSteps.map((task) => ({
|
|
1596
|
+
agent: task.agent,
|
|
1597
|
+
phase: task.phase ?? step.phase,
|
|
1598
|
+
label: task.label,
|
|
1599
|
+
outputName: undefined,
|
|
1600
|
+
structured: Boolean(task.structuredOutputSchema),
|
|
1601
|
+
status: "pending",
|
|
1602
|
+
...(task.sessionFile ? { sessionFile: task.sessionFile } : {}),
|
|
1603
|
+
skills: task.skills,
|
|
1604
|
+
model: task.model,
|
|
1605
|
+
thinking: task.thinking,
|
|
1606
|
+
attemptedModels: task.modelCandidates && task.modelCandidates.length > 0 ? task.modelCandidates : task.model ? [task.model] : undefined,
|
|
1607
|
+
recentTools: [],
|
|
1608
|
+
recentOutput: [],
|
|
1609
|
+
}));
|
|
1610
|
+
statusPayload.steps.splice(groupStartFlatIndex, 1, ...dynamicStatusSteps);
|
|
1611
|
+
if (config.childIntercomTargets) {
|
|
1612
|
+
config.childIntercomTargets = statusPayload.steps.map((statusStep, index) => resolveSubagentIntercomTarget(id, statusStep.agent, index));
|
|
1613
|
+
}
|
|
1614
|
+
mutatingFailureStates.splice(groupStartFlatIndex, 1, ...dynamicStatusSteps.map(() => createMutatingFailureState()));
|
|
1615
|
+
pendingToolResults.splice(groupStartFlatIndex, 1, ...dynamicStatusSteps.map(() => undefined));
|
|
1616
|
+
const materializedDelta = dynamicStatusSteps.length - 1;
|
|
1617
|
+
for (const group of statusPayload.parallelGroups) {
|
|
1618
|
+
if (group.stepIndex === stepIndex) {
|
|
1619
|
+
group.start = groupStartFlatIndex;
|
|
1620
|
+
group.count = dynamicStatusSteps.length;
|
|
1621
|
+
} else if (group.start > groupStartFlatIndex) {
|
|
1622
|
+
group.start += materializedDelta;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
if (statusPayload.workflowGraph) {
|
|
1626
|
+
const shiftFlatIndexes = (nodes: NonNullable<typeof statusPayload.workflowGraph>["nodes"]): void => {
|
|
1627
|
+
for (const node of nodes) {
|
|
1628
|
+
if (node.stepIndex !== undefined && node.stepIndex > stepIndex && node.flatIndex !== undefined && node.flatIndex >= groupStartFlatIndex) {
|
|
1629
|
+
node.flatIndex += dynamicStatusSteps.length;
|
|
1630
|
+
}
|
|
1631
|
+
if (node.children) shiftFlatIndexes(node.children);
|
|
1632
|
+
}
|
|
1633
|
+
};
|
|
1634
|
+
shiftFlatIndexes(statusPayload.workflowGraph.nodes);
|
|
1635
|
+
const groupNode = statusPayload.workflowGraph.nodes.find((node) => node.id === `step-${stepIndex}`);
|
|
1636
|
+
if (groupNode) {
|
|
1637
|
+
groupNode.children = materialized.items.map((item, itemIndex) => ({
|
|
1638
|
+
id: `step-${stepIndex}-item-${item.idKey}`,
|
|
1639
|
+
kind: "agent",
|
|
1640
|
+
agent: step.parallel.agent,
|
|
1641
|
+
phase: dynamicSteps[itemIndex]?.phase ?? step.phase,
|
|
1642
|
+
label: dynamicSteps[itemIndex]?.label?.trim() || `${step.parallel.agent} ${item.key}`,
|
|
1643
|
+
status: "pending",
|
|
1644
|
+
flatIndex: groupStartFlatIndex + itemIndex,
|
|
1645
|
+
stepIndex,
|
|
1646
|
+
itemKey: item.key,
|
|
1647
|
+
structured: Boolean(dynamicSteps[itemIndex]?.structuredOutputSchema),
|
|
1648
|
+
}));
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
writeStatusPayload();
|
|
1652
|
+
|
|
1653
|
+
const concurrency = step.concurrency ?? MAX_PARALLEL_CONCURRENCY;
|
|
1654
|
+
const failFast = step.failFast ?? false;
|
|
1655
|
+
let aborted = false;
|
|
1656
|
+
const parallelResults = await mapConcurrent(dynamicSteps, concurrency, async (task, taskIdx) => {
|
|
1657
|
+
const fi = groupStartFlatIndex + taskIdx;
|
|
1658
|
+
if (aborted && failFast) {
|
|
1659
|
+
const skippedAt = Date.now();
|
|
1660
|
+
statusPayload.steps[fi].status = "failed";
|
|
1661
|
+
statusPayload.steps[fi].error = "Skipped due to fail-fast";
|
|
1662
|
+
statusPayload.steps[fi].startedAt = skippedAt;
|
|
1663
|
+
statusPayload.steps[fi].endedAt = skippedAt;
|
|
1664
|
+
statusPayload.steps[fi].durationMs = 0;
|
|
1665
|
+
statusPayload.steps[fi].exitCode = -1;
|
|
1666
|
+
statusPayload.lastUpdate = skippedAt;
|
|
1667
|
+
writeStatusPayload();
|
|
1668
|
+
return { agent: task.agent, output: "(skipped — fail-fast)", exitCode: -1 as number | null, skipped: true };
|
|
1669
|
+
}
|
|
1670
|
+
const taskStartTime = Date.now();
|
|
1671
|
+
statusPayload.currentStep = fi;
|
|
1672
|
+
statusPayload.steps[fi].status = "running";
|
|
1673
|
+
statusPayload.steps[fi].error = undefined;
|
|
1674
|
+
statusPayload.steps[fi].activityState = undefined;
|
|
1675
|
+
resetStepLiveDetail(statusPayload.steps[fi]);
|
|
1676
|
+
statusPayload.steps[fi].startedAt = taskStartTime;
|
|
1677
|
+
statusPayload.steps[fi].lastActivityAt = taskStartTime;
|
|
1678
|
+
statusPayload.outputFile = path.join(asyncDir, `output-${fi}.log`);
|
|
1679
|
+
statusPayload.lastActivityAt = taskStartTime;
|
|
1680
|
+
statusPayload.lastUpdate = taskStartTime;
|
|
1681
|
+
writeStatusPayload();
|
|
1682
|
+
appendJsonl(eventsPath, JSON.stringify({ type: "subagent.step.started", ts: taskStartTime, runId: id, stepIndex: fi, agent: task.agent }));
|
|
1683
|
+
const singleResult = await runSingleStep(task, {
|
|
1684
|
+
previousOutput, placeholder, cwd, sessionEnabled,
|
|
1685
|
+
outputs,
|
|
1686
|
+
sessionDir: config.sessionDir ? path.join(config.sessionDir, `dynamic-${stepIndex}-${taskIdx}`) : undefined,
|
|
1687
|
+
artifactsDir, artifactConfig, id,
|
|
1688
|
+
flatIndex: fi, flatStepCount: Math.max(statusPayload.steps.length, 1),
|
|
1689
|
+
outputFile: path.join(asyncDir, `output-${fi}.log`),
|
|
1690
|
+
piPackageRoot: config.piPackageRoot,
|
|
1691
|
+
piArgv1: config.piArgv1,
|
|
1692
|
+
childIntercomTarget: config.childIntercomTargets?.[fi],
|
|
1693
|
+
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
1694
|
+
nestedRoute: config.nestedRoute,
|
|
1695
|
+
registerInterrupt: (interrupt) => {
|
|
1696
|
+
activeChildInterrupt = interrupt;
|
|
1697
|
+
},
|
|
1698
|
+
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking),
|
|
1699
|
+
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
1700
|
+
});
|
|
1701
|
+
const taskEndTime = Date.now();
|
|
1702
|
+
statusPayload.steps[fi].status = singleResult.exitCode === 0 ? "complete" : "failed";
|
|
1703
|
+
statusPayload.steps[fi].endedAt = taskEndTime;
|
|
1704
|
+
statusPayload.steps[fi].durationMs = taskEndTime - taskStartTime;
|
|
1705
|
+
statusPayload.steps[fi].exitCode = singleResult.exitCode;
|
|
1706
|
+
statusPayload.steps[fi].model = singleResult.model;
|
|
1707
|
+
statusPayload.steps[fi].thinking = resolveEffectiveThinking(singleResult.model, statusPayload.steps[fi].thinking);
|
|
1708
|
+
statusPayload.steps[fi].attemptedModels = singleResult.attemptedModels;
|
|
1709
|
+
statusPayload.steps[fi].modelAttempts = singleResult.modelAttempts;
|
|
1710
|
+
statusPayload.steps[fi].error = singleResult.error;
|
|
1711
|
+
statusPayload.steps[fi].structuredOutput = singleResult.structuredOutput;
|
|
1712
|
+
statusPayload.steps[fi].structuredOutputPath = singleResult.structuredOutputPath;
|
|
1713
|
+
statusPayload.steps[fi].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
1714
|
+
statusPayload.steps[fi].acceptance = singleResult.acceptance;
|
|
1715
|
+
statusPayload.lastUpdate = taskEndTime;
|
|
1716
|
+
writeStatusPayload();
|
|
1717
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
1718
|
+
type: singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
1719
|
+
ts: taskEndTime, runId: id, stepIndex: fi, agent: task.agent,
|
|
1720
|
+
exitCode: singleResult.exitCode, durationMs: taskEndTime - taskStartTime,
|
|
1721
|
+
}));
|
|
1722
|
+
if (singleResult.exitCode !== 0 && failFast) aborted = true;
|
|
1723
|
+
return { ...singleResult, skipped: false };
|
|
1724
|
+
});
|
|
1725
|
+
|
|
1726
|
+
flatIndex += dynamicSteps.length;
|
|
1727
|
+
for (const pr of parallelResults) {
|
|
1728
|
+
results.push({
|
|
1729
|
+
agent: pr.agent,
|
|
1730
|
+
output: pr.output,
|
|
1731
|
+
error: pr.error,
|
|
1732
|
+
success: pr.exitCode === 0,
|
|
1733
|
+
exitCode: pr.exitCode,
|
|
1734
|
+
skipped: pr.skipped,
|
|
1735
|
+
sessionFile: pr.sessionFile,
|
|
1736
|
+
intercomTarget: pr.intercomTarget,
|
|
1737
|
+
model: pr.model,
|
|
1738
|
+
attemptedModels: pr.attemptedModels,
|
|
1739
|
+
modelAttempts: pr.modelAttempts,
|
|
1740
|
+
artifactPaths: pr.artifactPaths,
|
|
1741
|
+
structuredOutput: pr.structuredOutput,
|
|
1742
|
+
structuredOutputPath: pr.structuredOutputPath,
|
|
1743
|
+
structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
|
|
1744
|
+
acceptance: pr.acceptance,
|
|
1745
|
+
});
|
|
1746
|
+
}
|
|
1747
|
+
const collection = collectDynamicResults(step as Parameters<typeof collectDynamicResults>[0], materialized.items, parallelResults);
|
|
1748
|
+
const failures = parallelResults.filter((result) => result.exitCode !== 0 && result.exitCode !== -1);
|
|
1749
|
+
if (failures.length === 0) {
|
|
1750
|
+
try {
|
|
1751
|
+
validateDynamicCollection(step.collect.outputSchema, collection);
|
|
1752
|
+
outputs[step.collect.as] = {
|
|
1753
|
+
text: JSON.stringify(collection),
|
|
1754
|
+
structured: collection,
|
|
1755
|
+
agent: step.parallel.agent,
|
|
1756
|
+
stepIndex,
|
|
1757
|
+
};
|
|
1758
|
+
statusPayload.outputs = outputs;
|
|
1759
|
+
markDynamicGraphGroup(stepIndex, "completed");
|
|
1760
|
+
} catch (error) {
|
|
1761
|
+
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
1762
|
+
results.push({ agent: step.parallel.agent, output: message, error: message, success: false, exitCode: 1, structuredOutput: collection });
|
|
1763
|
+
statusPayload.error = message;
|
|
1764
|
+
markDynamicGraphGroup(stepIndex, "failed", message);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
previousOutput = aggregateParallelOutputs(
|
|
1768
|
+
parallelResults.map((r, i) => ({
|
|
1769
|
+
agent: r.agent,
|
|
1770
|
+
taskIndex: i,
|
|
1771
|
+
output: r.output,
|
|
1772
|
+
exitCode: r.exitCode,
|
|
1773
|
+
error: r.error,
|
|
1774
|
+
})),
|
|
1775
|
+
(i, agent) => `=== Dynamic Item ${i + 1} (${agent}, key ${materialized.items[i]?.key ?? i}) ===`,
|
|
1776
|
+
);
|
|
1777
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
1778
|
+
type: "subagent.dynamic.completed",
|
|
1779
|
+
ts: Date.now(),
|
|
1780
|
+
runId: id,
|
|
1781
|
+
stepIndex,
|
|
1782
|
+
success: failures.length === 0,
|
|
1783
|
+
}));
|
|
1784
|
+
if (failures.length > 0) markDynamicGraphGroup(stepIndex, "failed", failures[0]?.error ?? "Dynamic fanout child failed.");
|
|
1785
|
+
statusPayload.lastUpdate = Date.now();
|
|
1786
|
+
writeStatusPayload();
|
|
1787
|
+
if (failures.length > 0 || statusPayload.error) break;
|
|
1788
|
+
continue;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1230
1791
|
if (isParallelGroup(step)) {
|
|
1231
1792
|
const group = step;
|
|
1232
1793
|
const concurrency = group.concurrency ?? MAX_PARALLEL_CONCURRENCY;
|
|
@@ -1311,7 +1872,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1311
1872
|
statusPayload.steps[fi].exitCode = -1;
|
|
1312
1873
|
statusPayload.steps[fi].activityState = undefined;
|
|
1313
1874
|
statusPayload.lastUpdate = skippedAt;
|
|
1314
|
-
|
|
1875
|
+
writeStatusPayload();
|
|
1315
1876
|
appendJsonl(eventsPath, JSON.stringify({
|
|
1316
1877
|
type: "subagent.step.failed", ts: skippedAt, runId: id, stepIndex: fi, agent: task.agent, exitCode: -1, durationMs: 0,
|
|
1317
1878
|
}));
|
|
@@ -1331,7 +1892,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1331
1892
|
statusPayload.outputFile = path.join(asyncDir, `output-${fi}.log`);
|
|
1332
1893
|
statusPayload.lastActivityAt = taskStartTime;
|
|
1333
1894
|
statusPayload.lastUpdate = taskStartTime;
|
|
1334
|
-
|
|
1895
|
+
writeStatusPayload();
|
|
1335
1896
|
|
|
1336
1897
|
appendJsonl(eventsPath, JSON.stringify({
|
|
1337
1898
|
type: "subagent.step.started", ts: taskStartTime, runId: id, stepIndex: fi, agent: task.agent,
|
|
@@ -1344,6 +1905,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1344
1905
|
|
|
1345
1906
|
const singleResult = await runSingleStep(taskForRun, {
|
|
1346
1907
|
previousOutput, placeholder, cwd: taskCwd, sessionEnabled,
|
|
1908
|
+
outputs,
|
|
1347
1909
|
sessionDir: taskSessionDir,
|
|
1348
1910
|
artifactsDir, artifactConfig, id,
|
|
1349
1911
|
flatIndex: fi, flatStepCount: flatSteps.length,
|
|
@@ -1352,6 +1914,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1352
1914
|
piArgv1: config.piArgv1,
|
|
1353
1915
|
childIntercomTarget: config.childIntercomTargets?.[fi],
|
|
1354
1916
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
1917
|
+
nestedRoute: config.nestedRoute,
|
|
1355
1918
|
registerInterrupt: (interrupt) => {
|
|
1356
1919
|
activeChildInterrupt = interrupt;
|
|
1357
1920
|
},
|
|
@@ -1374,8 +1937,12 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1374
1937
|
statusPayload.steps[fi].attemptedModels = singleResult.attemptedModels;
|
|
1375
1938
|
statusPayload.steps[fi].modelAttempts = singleResult.modelAttempts;
|
|
1376
1939
|
statusPayload.steps[fi].error = singleResult.error;
|
|
1940
|
+
statusPayload.steps[fi].structuredOutput = singleResult.structuredOutput;
|
|
1941
|
+
statusPayload.steps[fi].structuredOutputPath = singleResult.structuredOutputPath;
|
|
1942
|
+
statusPayload.steps[fi].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
1943
|
+
statusPayload.steps[fi].acceptance = singleResult.acceptance;
|
|
1377
1944
|
statusPayload.lastUpdate = taskEndTime;
|
|
1378
|
-
|
|
1945
|
+
writeStatusPayload();
|
|
1379
1946
|
|
|
1380
1947
|
appendJsonl(eventsPath, JSON.stringify({
|
|
1381
1948
|
type: singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
@@ -1419,7 +1986,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1419
1986
|
}
|
|
1420
1987
|
statusPayload.totalTokens = { ...previousCumulativeTokens };
|
|
1421
1988
|
statusPayload.lastUpdate = Date.now();
|
|
1422
|
-
|
|
1989
|
+
writeStatusPayload();
|
|
1423
1990
|
|
|
1424
1991
|
for (const pr of parallelResults) {
|
|
1425
1992
|
results.push({
|
|
@@ -1427,6 +1994,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1427
1994
|
output: pr.output,
|
|
1428
1995
|
error: pr.error,
|
|
1429
1996
|
success: pr.exitCode === 0,
|
|
1997
|
+
exitCode: pr.exitCode,
|
|
1430
1998
|
skipped: pr.skipped,
|
|
1431
1999
|
sessionFile: pr.sessionFile,
|
|
1432
2000
|
intercomTarget: pr.intercomTarget,
|
|
@@ -1434,8 +2002,21 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1434
2002
|
attemptedModels: pr.attemptedModels,
|
|
1435
2003
|
modelAttempts: pr.modelAttempts,
|
|
1436
2004
|
artifactPaths: pr.artifactPaths,
|
|
1437
|
-
|
|
2005
|
+
structuredOutput: pr.structuredOutput,
|
|
2006
|
+
structuredOutputPath: pr.structuredOutputPath,
|
|
2007
|
+
structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
|
|
2008
|
+
acceptance: pr.acceptance,
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
for (let t = 0; t < group.parallel.length; t++) {
|
|
2012
|
+
const outputName = group.parallel[t]?.outputName;
|
|
2013
|
+
if (outputName) outputs[outputName] = outputEntryFromAsyncResult({
|
|
2014
|
+
agent: parallelResults[t]!.agent,
|
|
2015
|
+
output: parallelResults[t]!.output,
|
|
2016
|
+
structuredOutput: parallelResults[t]!.structuredOutput,
|
|
2017
|
+
}, stepIndex);
|
|
1438
2018
|
}
|
|
2019
|
+
statusPayload.outputs = outputs;
|
|
1439
2020
|
|
|
1440
2021
|
previousOutput = aggregateParallelOutputs(
|
|
1441
2022
|
parallelResults.map((r) => ({
|
|
@@ -1477,7 +2058,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1477
2058
|
statusPayload.lastActivityAt = stepStartTime;
|
|
1478
2059
|
statusPayload.lastUpdate = stepStartTime;
|
|
1479
2060
|
statusPayload.outputFile = path.join(asyncDir, `output-${flatIndex}.log`);
|
|
1480
|
-
|
|
2061
|
+
writeStatusPayload();
|
|
1481
2062
|
|
|
1482
2063
|
appendJsonl(eventsPath, JSON.stringify({
|
|
1483
2064
|
type: "subagent.step.started",
|
|
@@ -1489,6 +2070,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1489
2070
|
|
|
1490
2071
|
const singleResult = await runSingleStep(seqStep, {
|
|
1491
2072
|
previousOutput, placeholder, cwd, sessionEnabled,
|
|
2073
|
+
outputs,
|
|
1492
2074
|
sessionDir: config.sessionDir,
|
|
1493
2075
|
artifactsDir, artifactConfig, id,
|
|
1494
2076
|
flatIndex, flatStepCount: flatSteps.length,
|
|
@@ -1497,6 +2079,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1497
2079
|
piArgv1: config.piArgv1,
|
|
1498
2080
|
childIntercomTarget: config.childIntercomTargets?.[flatIndex],
|
|
1499
2081
|
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
2082
|
+
nestedRoute: config.nestedRoute,
|
|
1500
2083
|
registerInterrupt: (interrupt) => {
|
|
1501
2084
|
activeChildInterrupt = interrupt;
|
|
1502
2085
|
},
|
|
@@ -1513,13 +2096,26 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1513
2096
|
output: singleResult.output,
|
|
1514
2097
|
error: singleResult.error,
|
|
1515
2098
|
success: singleResult.exitCode === 0,
|
|
2099
|
+
exitCode: singleResult.exitCode,
|
|
1516
2100
|
sessionFile: singleResult.sessionFile,
|
|
1517
2101
|
intercomTarget: singleResult.intercomTarget,
|
|
1518
2102
|
model: singleResult.model,
|
|
1519
2103
|
attemptedModels: singleResult.attemptedModels,
|
|
1520
2104
|
modelAttempts: singleResult.modelAttempts,
|
|
1521
2105
|
artifactPaths: singleResult.artifactPaths,
|
|
2106
|
+
structuredOutput: singleResult.structuredOutput,
|
|
2107
|
+
structuredOutputPath: singleResult.structuredOutputPath,
|
|
2108
|
+
structuredOutputSchemaPath: singleResult.structuredOutputSchemaPath,
|
|
2109
|
+
acceptance: singleResult.acceptance,
|
|
1522
2110
|
});
|
|
2111
|
+
if (seqStep.outputName) {
|
|
2112
|
+
outputs[seqStep.outputName] = outputEntryFromAsyncResult({
|
|
2113
|
+
agent: singleResult.agent,
|
|
2114
|
+
output: singleResult.output,
|
|
2115
|
+
structuredOutput: singleResult.structuredOutput,
|
|
2116
|
+
}, stepIndex);
|
|
2117
|
+
}
|
|
2118
|
+
statusPayload.outputs = outputs;
|
|
1523
2119
|
|
|
1524
2120
|
const cumulativeTokens = config.sessionDir ? parseSessionTokens(config.sessionDir) : null;
|
|
1525
2121
|
let stepTokens: TokenUsage | null = cumulativeTokens
|
|
@@ -1552,12 +2148,16 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1552
2148
|
statusPayload.steps[flatIndex].attemptedModels = singleResult.attemptedModels;
|
|
1553
2149
|
statusPayload.steps[flatIndex].modelAttempts = singleResult.modelAttempts;
|
|
1554
2150
|
statusPayload.steps[flatIndex].error = singleResult.error;
|
|
2151
|
+
statusPayload.steps[flatIndex].structuredOutput = singleResult.structuredOutput;
|
|
2152
|
+
statusPayload.steps[flatIndex].structuredOutputPath = singleResult.structuredOutputPath;
|
|
2153
|
+
statusPayload.steps[flatIndex].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
2154
|
+
statusPayload.steps[flatIndex].acceptance = singleResult.acceptance;
|
|
1555
2155
|
if (stepTokens) {
|
|
1556
2156
|
statusPayload.steps[flatIndex].tokens = stepTokens;
|
|
1557
2157
|
statusPayload.totalTokens = { ...previousCumulativeTokens };
|
|
1558
2158
|
}
|
|
1559
2159
|
statusPayload.lastUpdate = stepEndTime;
|
|
1560
|
-
|
|
2160
|
+
writeStatusPayload();
|
|
1561
2161
|
|
|
1562
2162
|
appendJsonl(eventsPath, JSON.stringify({
|
|
1563
2163
|
type: singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
@@ -1653,13 +2253,13 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1653
2253
|
statusPayload.shareUrl = shareUrl;
|
|
1654
2254
|
statusPayload.gistUrl = gistUrl;
|
|
1655
2255
|
statusPayload.shareError = shareError;
|
|
1656
|
-
if (statusPayload.state === "failed") {
|
|
2256
|
+
if (statusPayload.state === "failed" && !statusPayload.error) {
|
|
1657
2257
|
const failedStep = statusPayload.steps.find((s) => s.status === "failed");
|
|
1658
2258
|
if (failedStep?.agent) {
|
|
1659
2259
|
statusPayload.error = `Step failed: ${failedStep.agent}`;
|
|
1660
2260
|
}
|
|
1661
2261
|
}
|
|
1662
|
-
|
|
2262
|
+
writeStatusPayload();
|
|
1663
2263
|
appendJsonl(
|
|
1664
2264
|
eventsPath,
|
|
1665
2265
|
JSON.stringify({
|
|
@@ -1710,7 +2310,13 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1710
2310
|
modelAttempts: r.modelAttempts,
|
|
1711
2311
|
artifactPaths: r.artifactPaths,
|
|
1712
2312
|
truncated: r.truncated,
|
|
2313
|
+
structuredOutput: r.structuredOutput,
|
|
2314
|
+
structuredOutputPath: r.structuredOutputPath,
|
|
2315
|
+
structuredOutputSchemaPath: r.structuredOutputSchemaPath,
|
|
2316
|
+
acceptance: r.acceptance,
|
|
1713
2317
|
})),
|
|
2318
|
+
outputs,
|
|
2319
|
+
workflowGraph: statusPayload.workflowGraph,
|
|
1714
2320
|
exitCode: interrupted || results.every((r) => r.success) ? 0 : 1,
|
|
1715
2321
|
timestamp: runEndedAt,
|
|
1716
2322
|
durationMs: runEndedAt - overallStartTime,
|