@bastani/atomic 0.8.24-alpha.1 → 0.8.24-alpha.3
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 +17 -0
- package/README.md +2 -1
- package/dist/builtin/intercom/CHANGELOG.md +12 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/CHANGELOG.md +12 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +16 -0
- package/dist/builtin/subagents/README.md +132 -21
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/prompts/parallel-context-build.md +4 -2
- package/dist/builtin/subagents/prompts/parallel-handoff-plan.md +3 -1
- package/dist/builtin/subagents/skills/subagent/SKILL.md +49 -11
- package/dist/builtin/subagents/src/agents/agent-management.ts +79 -16
- package/dist/builtin/subagents/src/agents/agents.ts +47 -16
- package/dist/builtin/subagents/src/agents/chain-serializer.ts +114 -0
- package/dist/builtin/subagents/src/extension/schemas.ts +139 -3
- package/dist/builtin/subagents/src/runs/background/async-execution.ts +92 -6
- package/dist/builtin/subagents/src/runs/background/async-status.ts +11 -1
- package/dist/builtin/subagents/src/runs/background/run-status.ts +4 -1
- package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +529 -32
- package/dist/builtin/subagents/src/runs/foreground/chain-execution.ts +361 -118
- package/dist/builtin/subagents/src/runs/foreground/execution.ts +75 -7
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +33 -0
- package/dist/builtin/subagents/src/runs/shared/acceptance.ts +611 -0
- package/dist/builtin/subagents/src/runs/shared/chain-outputs.ts +101 -0
- package/dist/builtin/subagents/src/runs/shared/dynamic-fanout.ts +293 -0
- package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +29 -1
- package/dist/builtin/subagents/src/runs/shared/pi-args.ts +11 -0
- package/dist/builtin/subagents/src/runs/shared/structured-output.ts +79 -0
- package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +52 -2
- package/dist/builtin/subagents/src/runs/shared/workflow-graph.ts +206 -0
- package/dist/builtin/subagents/src/shared/formatters.ts +2 -2
- package/dist/builtin/subagents/src/shared/settings.ts +53 -4
- package/dist/builtin/subagents/src/shared/types.ts +226 -0
- package/dist/builtin/subagents/src/shared/utils.ts +2 -1
- package/dist/builtin/subagents/src/slash/slash-commands.ts +41 -3
- package/dist/builtin/subagents/src/tui/render.ts +152 -34
- package/dist/builtin/web-access/CHANGELOG.md +12 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +16 -0
- package/dist/builtin/workflows/ambient.d.ts +36 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/create-spec/SKILL.md +1 -1
- package/dist/builtin/workflows/src/authoring.d.ts +197 -0
- package/dist/builtin/workflows/src/runs/shared/model-fallback.ts +11 -1
- package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +523 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +0 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +4 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/packages.md +2 -2
- package/docs/usage.md +1 -0
- package/docs/workflows.md +198 -2
- package/package.json +20 -1
- package/dist/builtin/workflows/src/authoring.ts +0 -410
- package/dist/builtin/workflows/src/shared/authoring-contract.ts +0 -660
|
@@ -13,11 +13,13 @@ import {
|
|
|
13
13
|
type ArtifactPaths,
|
|
14
14
|
type AsyncParallelGroupStatus,
|
|
15
15
|
type AsyncStatus,
|
|
16
|
+
type ChainOutputMap,
|
|
16
17
|
type ModelAttempt,
|
|
17
18
|
type NestedRouteInfo,
|
|
18
19
|
type ResolvedControlConfig,
|
|
19
20
|
type SubagentRunMode,
|
|
20
21
|
type Usage,
|
|
22
|
+
type WorkflowGraphSnapshot,
|
|
21
23
|
DEFAULT_MAX_OUTPUT,
|
|
22
24
|
type MaxOutputConfig,
|
|
23
25
|
truncateOutput,
|
|
@@ -34,6 +36,7 @@ import {
|
|
|
34
36
|
import {
|
|
35
37
|
type RunnerSubagentStep as SubagentStep,
|
|
36
38
|
type RunnerStep,
|
|
39
|
+
isDynamicRunnerGroup,
|
|
37
40
|
isParallelGroup,
|
|
38
41
|
flattenSteps,
|
|
39
42
|
mapConcurrent,
|
|
@@ -41,6 +44,9 @@ import {
|
|
|
41
44
|
MAX_PARALLEL_CONCURRENCY,
|
|
42
45
|
} from "../shared/parallel-utils.ts";
|
|
43
46
|
import { buildPiArgs, cleanupTempDir } from "../shared/pi-args.ts";
|
|
47
|
+
import { outputEntryFromAsyncResult, resolveOutputReferences } from "../shared/chain-outputs.ts";
|
|
48
|
+
import { createStructuredOutputRuntime, readStructuredOutput } from "../shared/structured-output.ts";
|
|
49
|
+
import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection } from "../shared/dynamic-fanout.ts";
|
|
44
50
|
import { nestedSummaryFromAsyncStatus, writeNestedEvent } from "../shared/nested-events.ts";
|
|
45
51
|
import { formatModelAttemptNote, isRetryableModelFailure } from "../shared/model-fallback.ts";
|
|
46
52
|
import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
|
|
@@ -70,6 +76,8 @@ import {
|
|
|
70
76
|
} from "../shared/worktree.ts";
|
|
71
77
|
import { resolveEffectiveThinking } from "../../shared/model-info.ts";
|
|
72
78
|
import { writeInitialProgressFile } from "../../shared/settings.ts";
|
|
79
|
+
import { resolveSubagentIntercomTarget } from "../../intercom/intercom-bridge.ts";
|
|
80
|
+
import { acceptanceFailureMessage, aggregateAcceptanceReport, evaluateAcceptance, formatAcceptancePrompt, stripAcceptanceReport } from "../shared/acceptance.ts";
|
|
73
81
|
|
|
74
82
|
interface SubagentRunConfig {
|
|
75
83
|
id: string;
|
|
@@ -94,6 +102,8 @@ interface SubagentRunConfig {
|
|
|
94
102
|
controlIntercomTarget?: string;
|
|
95
103
|
childIntercomTargets?: Array<string | undefined>;
|
|
96
104
|
resultMode?: SubagentRunMode;
|
|
105
|
+
dynamicFanoutMaxItems?: number;
|
|
106
|
+
workflowGraph?: WorkflowGraphSnapshot;
|
|
97
107
|
nestedRoute?: NestedRouteInfo;
|
|
98
108
|
workflowStageSubagentGuard?: boolean;
|
|
99
109
|
nestedSelf?: { parentRunId: string; parentStepIndex?: number; depth: number; path?: Array<{ runId: string; stepIndex?: number; agent?: string }> };
|
|
@@ -104,6 +114,7 @@ interface StepResult {
|
|
|
104
114
|
output: string;
|
|
105
115
|
error?: string;
|
|
106
116
|
success: boolean;
|
|
117
|
+
exitCode?: number | null;
|
|
107
118
|
skipped?: boolean;
|
|
108
119
|
sessionFile?: string;
|
|
109
120
|
intercomTarget?: string;
|
|
@@ -113,6 +124,10 @@ interface StepResult {
|
|
|
113
124
|
modelAttempts?: ModelAttempt[];
|
|
114
125
|
artifactPaths?: ArtifactPaths;
|
|
115
126
|
truncated?: boolean;
|
|
127
|
+
structuredOutput?: unknown;
|
|
128
|
+
structuredOutputPath?: string;
|
|
129
|
+
structuredOutputSchemaPath?: string;
|
|
130
|
+
acceptance?: import("../../shared/types.ts").AcceptanceLedger;
|
|
116
131
|
}
|
|
117
132
|
|
|
118
133
|
const ASYNC_INTERRUPT_SIGNAL: NodeJS.Signals = process.platform === "win32" ? "SIGBREAK" : "SIGUSR2";
|
|
@@ -558,6 +573,7 @@ function writeRunLog(
|
|
|
558
573
|
/** Context for running a single step */
|
|
559
574
|
interface SingleStepContext {
|
|
560
575
|
previousOutput: string;
|
|
576
|
+
outputs?: ChainOutputMap;
|
|
561
577
|
placeholder: string;
|
|
562
578
|
cwd: string;
|
|
563
579
|
sessionEnabled: boolean;
|
|
@@ -597,9 +613,22 @@ async function runSingleStep(
|
|
|
597
613
|
sessionFile?: string;
|
|
598
614
|
intercomTarget?: string;
|
|
599
615
|
completionGuardTriggered?: boolean;
|
|
616
|
+
structuredOutput?: unknown;
|
|
617
|
+
structuredOutputPath?: string;
|
|
618
|
+
structuredOutputSchemaPath?: string;
|
|
619
|
+
acceptance?: import("../../shared/types.ts").AcceptanceLedger;
|
|
600
620
|
}> {
|
|
621
|
+
const effectiveStructuredOutput = step.structuredOutput ?? (step.structuredOutputSchema
|
|
622
|
+
? createStructuredOutputRuntime(step.structuredOutputSchema, path.join(path.dirname(ctx.outputFile), "structured-output"))
|
|
623
|
+
: undefined);
|
|
601
624
|
const placeholderRegex = new RegExp(ctx.placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
|
|
602
|
-
|
|
625
|
+
let task = step.task.replace(placeholderRegex, () => ctx.previousOutput);
|
|
626
|
+
task = resolveOutputReferences(task, ctx.outputs ?? {});
|
|
627
|
+
const taskForCompletionGuard = task;
|
|
628
|
+
if (step.effectiveAcceptance) {
|
|
629
|
+
const acceptancePrompt = formatAcceptancePrompt(step.effectiveAcceptance);
|
|
630
|
+
if (acceptancePrompt) task = `${task}\n${acceptancePrompt}`;
|
|
631
|
+
}
|
|
603
632
|
const sessionEnabled = Boolean(step.sessionFile) || ctx.sessionEnabled;
|
|
604
633
|
const sessionDir = step.sessionFile ? undefined : ctx.sessionDir;
|
|
605
634
|
|
|
@@ -632,6 +661,13 @@ async function runSingleStep(
|
|
|
632
661
|
const attemptFastMode = fastModeForStepAttempt(step, candidate);
|
|
633
662
|
ctx.onAttemptStart?.({ model: candidate, thinking: resolveEffectiveThinking(candidate, step.thinking), fastMode: attemptFastMode ? true : undefined });
|
|
634
663
|
const outputSnapshot = captureSingleOutputSnapshot(step.outputPath);
|
|
664
|
+
if (effectiveStructuredOutput) {
|
|
665
|
+
try {
|
|
666
|
+
if (fs.existsSync(effectiveStructuredOutput.outputPath)) fs.unlinkSync(effectiveStructuredOutput.outputPath);
|
|
667
|
+
} catch {
|
|
668
|
+
// Missing/stale structured-output files are handled after the child exits.
|
|
669
|
+
}
|
|
670
|
+
}
|
|
635
671
|
const { args, env, tempDir } = buildPiArgs({
|
|
636
672
|
baseArgs: ["--mode", "json", "-p"],
|
|
637
673
|
task,
|
|
@@ -659,6 +695,7 @@ async function runSingleStep(
|
|
|
659
695
|
parentCapabilityToken: ctx.nestedRoute?.capabilityToken,
|
|
660
696
|
codexFastModeSettings: step.codexFastModeSettings,
|
|
661
697
|
codexFastModeScope: step.codexFastModeScope,
|
|
698
|
+
structuredOutput: effectiveStructuredOutput,
|
|
662
699
|
});
|
|
663
700
|
const run = await runPiStreaming(
|
|
664
701
|
args,
|
|
@@ -676,10 +713,21 @@ async function runSingleStep(
|
|
|
676
713
|
cleanupTempDir(tempDir);
|
|
677
714
|
|
|
678
715
|
const hiddenError = run.exitCode === 0 && !run.error ? detectSubagentError(run.messages) : null;
|
|
716
|
+
let structuredOutput: unknown;
|
|
717
|
+
let structuredError: string | undefined;
|
|
718
|
+
if (effectiveStructuredOutput && run.exitCode === 0 && !run.error && !hiddenError?.hasError) {
|
|
719
|
+
const structured = readStructuredOutput({
|
|
720
|
+
schema: effectiveStructuredOutput.schema,
|
|
721
|
+
schemaPath: effectiveStructuredOutput.schemaPath,
|
|
722
|
+
outputPath: effectiveStructuredOutput.outputPath,
|
|
723
|
+
});
|
|
724
|
+
if (structured.error) structuredError = structured.error;
|
|
725
|
+
else structuredOutput = structured.value;
|
|
726
|
+
}
|
|
679
727
|
const completionGuard = run.exitCode === 0 && !run.error && !hiddenError?.hasError && step.completionGuard !== false
|
|
680
728
|
? evaluateCompletionMutationGuard({
|
|
681
729
|
agent: step.agent,
|
|
682
|
-
task,
|
|
730
|
+
task: taskForCompletionGuard,
|
|
683
731
|
messages: run.messages,
|
|
684
732
|
tools: step.tools,
|
|
685
733
|
mcpDirectTools: step.mcpDirectTools,
|
|
@@ -691,12 +739,15 @@ async function runSingleStep(
|
|
|
691
739
|
: undefined;
|
|
692
740
|
const effectiveExitCode = completionGuardTriggered
|
|
693
741
|
? 1
|
|
694
|
-
:
|
|
742
|
+
: structuredError
|
|
743
|
+
? 1
|
|
744
|
+
: hiddenError?.hasError
|
|
695
745
|
? (hiddenError.exitCode ?? 1)
|
|
696
746
|
: run.error && run.exitCode === 0
|
|
697
747
|
? 1
|
|
698
748
|
: run.exitCode;
|
|
699
749
|
const error = completionGuardError
|
|
750
|
+
?? structuredError
|
|
700
751
|
?? (hiddenError?.hasError
|
|
701
752
|
? hiddenError.details
|
|
702
753
|
? `${hiddenError.errorType} failed (exit ${effectiveExitCode}): ${hiddenError.details}`
|
|
@@ -716,24 +767,26 @@ async function runSingleStep(
|
|
|
716
767
|
completionGuardTriggeredFinal = completionGuardTriggered;
|
|
717
768
|
finalFastMode = attemptFastMode;
|
|
718
769
|
finalOutputSnapshot = outputSnapshot;
|
|
719
|
-
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error };
|
|
770
|
+
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput } as RunPiStreamingResult & { structuredOutput?: unknown };
|
|
720
771
|
if (attempt.success || completionGuardTriggered) break;
|
|
721
772
|
if (!isRetryableModelFailure(error) || index === candidates.length - 1) break;
|
|
722
773
|
attemptNotes.push(formatModelAttemptNote(attempt, candidates[index + 1]));
|
|
723
774
|
}
|
|
724
775
|
|
|
725
776
|
const rawOutput = finalResult?.finalOutput ?? "";
|
|
777
|
+
const outputForPersistence = stripAcceptanceReport(rawOutput);
|
|
726
778
|
const resolvedOutput = step.outputPath && finalResult?.exitCode === 0
|
|
727
|
-
? resolveSingleOutput(step.outputPath,
|
|
728
|
-
: { fullOutput:
|
|
779
|
+
? resolveSingleOutput(step.outputPath, outputForPersistence, finalOutputSnapshot)
|
|
780
|
+
: { fullOutput: outputForPersistence };
|
|
729
781
|
const output = resolvedOutput.fullOutput;
|
|
730
782
|
const outputReference = resolvedOutput.savedPath ? formatSavedOutputReference(resolvedOutput.savedPath, output) : undefined;
|
|
731
783
|
let outputForSummary = output;
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
const
|
|
736
|
-
|
|
784
|
+
if (attemptNotes.length > 0) {
|
|
785
|
+
outputForSummary = `${attemptNotes.join("\n")}\n\n${outputForSummary}`.trim();
|
|
786
|
+
}
|
|
787
|
+
const outputForAcceptance = rawOutput;
|
|
788
|
+
const finalizedOutput = finalizeSingleOutput({
|
|
789
|
+
fullOutput: outputForSummary,
|
|
737
790
|
outputPath: step.outputPath,
|
|
738
791
|
outputMode: step.outputMode,
|
|
739
792
|
exitCode: finalResult?.exitCode ?? 1,
|
|
@@ -742,6 +795,19 @@ async function runSingleStep(
|
|
|
742
795
|
saveError: resolvedOutput.saveError,
|
|
743
796
|
});
|
|
744
797
|
outputForSummary = finalizedOutput.displayOutput;
|
|
798
|
+
const acceptance = step.effectiveAcceptance
|
|
799
|
+
? await evaluateAcceptance({
|
|
800
|
+
acceptance: step.effectiveAcceptance,
|
|
801
|
+
output: outputForAcceptance,
|
|
802
|
+
cwd: step.cwd ?? ctx.cwd,
|
|
803
|
+
})
|
|
804
|
+
: undefined;
|
|
805
|
+
const acceptanceFailure = acceptance ? acceptanceFailureMessage(acceptance) : undefined;
|
|
806
|
+
const acceptanceCanFailRun = acceptanceFailure && acceptance?.explicit && (finalResult?.exitCode ?? 1) === 0 && !finalResult?.interrupted;
|
|
807
|
+
const effectiveFinalExitCode = acceptanceCanFailRun ? 1 : finalResult?.exitCode ?? 1;
|
|
808
|
+
const effectiveFinalError = acceptanceCanFailRun
|
|
809
|
+
? (finalResult?.error ? `${finalResult.error}\n${acceptanceFailure}` : acceptanceFailure)
|
|
810
|
+
: finalResult?.error;
|
|
745
811
|
|
|
746
812
|
if (artifactPaths && ctx.artifactConfig?.enabled !== false) {
|
|
747
813
|
if (ctx.artifactConfig?.includeOutput !== false) {
|
|
@@ -754,7 +820,7 @@ async function runSingleStep(
|
|
|
754
820
|
runId: ctx.id,
|
|
755
821
|
agent: step.agent,
|
|
756
822
|
task,
|
|
757
|
-
exitCode:
|
|
823
|
+
exitCode: effectiveFinalExitCode,
|
|
758
824
|
model: finalResult?.model,
|
|
759
825
|
...(finalFastMode ? { fastMode: true } : {}),
|
|
760
826
|
attemptedModels: attemptedModels.length > 0 ? attemptedModels : undefined,
|
|
@@ -770,8 +836,8 @@ async function runSingleStep(
|
|
|
770
836
|
return {
|
|
771
837
|
agent: step.agent,
|
|
772
838
|
output: outputForSummary,
|
|
773
|
-
exitCode:
|
|
774
|
-
error:
|
|
839
|
+
exitCode: effectiveFinalExitCode,
|
|
840
|
+
error: effectiveFinalError,
|
|
775
841
|
sessionFile: step.sessionFile,
|
|
776
842
|
intercomTarget: ctx.childIntercomTarget,
|
|
777
843
|
model: finalResult?.model,
|
|
@@ -781,6 +847,10 @@ async function runSingleStep(
|
|
|
781
847
|
artifactPaths,
|
|
782
848
|
interrupted: finalResult?.interrupted,
|
|
783
849
|
completionGuardTriggered: completionGuardTriggeredFinal,
|
|
850
|
+
structuredOutput: (finalResult as (RunPiStreamingResult & { structuredOutput?: unknown }) | undefined)?.structuredOutput,
|
|
851
|
+
structuredOutputPath: effectiveStructuredOutput?.outputPath,
|
|
852
|
+
structuredOutputSchemaPath: effectiveStructuredOutput?.schemaPath,
|
|
853
|
+
acceptance,
|
|
784
854
|
};
|
|
785
855
|
}
|
|
786
856
|
|
|
@@ -823,7 +893,7 @@ function markParallelGroupSetupFailure(input: {
|
|
|
823
893
|
input.statusPayload.steps[flatTaskIndex].endedAt = input.failedAt;
|
|
824
894
|
input.statusPayload.steps[flatTaskIndex].durationMs = 0;
|
|
825
895
|
input.statusPayload.steps[flatTaskIndex].exitCode = 1;
|
|
826
|
-
input.results.push({ agent: input.group.parallel[taskIndex].agent, output: input.setupError, success: false, sessionFile: input.group.parallel[taskIndex].sessionFile });
|
|
896
|
+
input.results.push({ agent: input.group.parallel[taskIndex].agent, output: input.setupError, success: false, exitCode: 1, sessionFile: input.group.parallel[taskIndex].sessionFile });
|
|
827
897
|
}
|
|
828
898
|
input.statusPayload.currentStep = input.groupStartFlatIndex;
|
|
829
899
|
input.statusPayload.lastUpdate = input.failedAt;
|
|
@@ -913,6 +983,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
913
983
|
const { id, steps, resultPath, cwd, placeholder, taskIndex, totalTasks, maxOutput, artifactsDir, artifactConfig } =
|
|
914
984
|
config;
|
|
915
985
|
let previousOutput = "";
|
|
986
|
+
const outputs: ChainOutputMap = {};
|
|
916
987
|
const results: StepResult[] = [];
|
|
917
988
|
const overallStartTime = Date.now();
|
|
918
989
|
const shareEnabled = config.share === true;
|
|
@@ -929,13 +1000,61 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
929
1000
|
let latestSessionFile: string | undefined;
|
|
930
1001
|
|
|
931
1002
|
const parallelGroups: Array<{ start: number; count: number; stepIndex: number }> = [];
|
|
1003
|
+
const initialStatusSteps: RunnerStatusStep[] = [];
|
|
932
1004
|
let flatStepCount = 0;
|
|
933
1005
|
for (let stepIndex = 0; stepIndex < steps.length; stepIndex++) {
|
|
934
1006
|
const step = steps[stepIndex]!;
|
|
935
1007
|
if (isParallelGroup(step)) {
|
|
936
1008
|
parallelGroups.push({ start: flatStepCount, count: step.parallel.length, stepIndex });
|
|
1009
|
+
for (const task of step.parallel) {
|
|
1010
|
+
initialStatusSteps.push({
|
|
1011
|
+
agent: task.agent,
|
|
1012
|
+
phase: task.phase,
|
|
1013
|
+
label: task.label,
|
|
1014
|
+
outputName: task.outputName,
|
|
1015
|
+
structured: task.structured,
|
|
1016
|
+
status: "pending",
|
|
1017
|
+
...(task.sessionFile ? { sessionFile: task.sessionFile } : {}),
|
|
1018
|
+
skills: task.skills,
|
|
1019
|
+
model: task.model,
|
|
1020
|
+
thinking: task.thinking,
|
|
1021
|
+
...(task.fastMode ? { fastMode: true } : {}),
|
|
1022
|
+
attemptedModels: task.modelCandidates && task.modelCandidates.length > 0 ? task.modelCandidates : task.model ? [task.model] : undefined,
|
|
1023
|
+
recentTools: [],
|
|
1024
|
+
recentOutput: [],
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
937
1027
|
flatStepCount += step.parallel.length;
|
|
1028
|
+
} else if (isDynamicRunnerGroup(step)) {
|
|
1029
|
+
parallelGroups.push({ start: flatStepCount, count: 1, stepIndex });
|
|
1030
|
+
initialStatusSteps.push({
|
|
1031
|
+
agent: `expand:${step.parallel.agent}`,
|
|
1032
|
+
phase: step.phase ?? step.parallel.phase,
|
|
1033
|
+
label: step.label ?? step.parallel.label ?? `Dynamic fanout (${step.collect.as})`,
|
|
1034
|
+
outputName: step.collect.as,
|
|
1035
|
+
structured: Boolean(step.collect.outputSchema),
|
|
1036
|
+
status: "pending",
|
|
1037
|
+
recentTools: [],
|
|
1038
|
+
recentOutput: [],
|
|
1039
|
+
});
|
|
1040
|
+
flatStepCount++;
|
|
938
1041
|
} else {
|
|
1042
|
+
initialStatusSteps.push({
|
|
1043
|
+
agent: step.agent,
|
|
1044
|
+
phase: step.phase,
|
|
1045
|
+
label: step.label,
|
|
1046
|
+
outputName: step.outputName,
|
|
1047
|
+
structured: step.structured,
|
|
1048
|
+
status: "pending",
|
|
1049
|
+
...(step.sessionFile ? { sessionFile: step.sessionFile } : {}),
|
|
1050
|
+
skills: step.skills,
|
|
1051
|
+
model: step.model,
|
|
1052
|
+
thinking: step.thinking,
|
|
1053
|
+
...(step.fastMode ? { fastMode: true } : {}),
|
|
1054
|
+
attemptedModels: step.modelCandidates && step.modelCandidates.length > 0 ? step.modelCandidates : step.model ? [step.model] : undefined,
|
|
1055
|
+
recentTools: [],
|
|
1056
|
+
recentOutput: [],
|
|
1057
|
+
});
|
|
939
1058
|
flatStepCount++;
|
|
940
1059
|
}
|
|
941
1060
|
}
|
|
@@ -956,18 +1075,8 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
956
1075
|
currentStep: 0,
|
|
957
1076
|
chainStepCount: steps.length,
|
|
958
1077
|
parallelGroups,
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
status: "pending",
|
|
962
|
-
...(step.sessionFile ? { sessionFile: step.sessionFile } : {}),
|
|
963
|
-
skills: step.skills,
|
|
964
|
-
model: step.model,
|
|
965
|
-
thinking: step.thinking,
|
|
966
|
-
...(step.fastMode ? { fastMode: true } : {}),
|
|
967
|
-
attemptedModels: step.modelCandidates && step.modelCandidates.length > 0 ? step.modelCandidates : step.model ? [step.model] : undefined,
|
|
968
|
-
recentTools: [],
|
|
969
|
-
recentOutput: [],
|
|
970
|
-
})),
|
|
1078
|
+
workflowGraph: config.workflowGraph,
|
|
1079
|
+
steps: initialStatusSteps,
|
|
971
1080
|
artifactsDir,
|
|
972
1081
|
sessionDir: config.sessionDir,
|
|
973
1082
|
outputFile: path.join(asyncDir, "output-0.log"),
|
|
@@ -997,10 +1106,48 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
997
1106
|
console.error("Failed to emit nested async status event:", error);
|
|
998
1107
|
}
|
|
999
1108
|
};
|
|
1109
|
+
const refreshWorkflowGraph = (): void => {
|
|
1110
|
+
if (!config.workflowGraph) return;
|
|
1111
|
+
const graph = structuredClone(statusPayload.workflowGraph ?? config.workflowGraph);
|
|
1112
|
+
const normalize = (status: RunnerStatusStep["status"]): "pending" | "running" | "completed" | "failed" | "paused" | "detached" => {
|
|
1113
|
+
if (status === "complete" || status === "completed") return "completed";
|
|
1114
|
+
if (status === "running" || status === "failed" || status === "paused" || status === "pending") return status;
|
|
1115
|
+
return "pending";
|
|
1116
|
+
};
|
|
1117
|
+
const updateNode = (node: NonNullable<typeof graph.nodes>[number]): void => {
|
|
1118
|
+
if (node.flatIndex !== undefined) {
|
|
1119
|
+
const step = statusPayload.steps[node.flatIndex];
|
|
1120
|
+
if (step) {
|
|
1121
|
+
node.status = normalize(step.status);
|
|
1122
|
+
node.error = step.error;
|
|
1123
|
+
node.acceptanceStatus = step.acceptance?.status;
|
|
1124
|
+
}
|
|
1125
|
+
if (statusPayload.currentStep === node.flatIndex) graph.currentNodeId = node.id;
|
|
1126
|
+
}
|
|
1127
|
+
for (const child of node.children ?? []) updateNode(child);
|
|
1128
|
+
if (node.children?.length) {
|
|
1129
|
+
if (node.children.every((child) => child.status === "completed")) node.status = "completed";
|
|
1130
|
+
else if (node.children.some((child) => child.status === "running")) node.status = "running";
|
|
1131
|
+
else if (node.children.some((child) => child.status === "failed")) node.status = "failed";
|
|
1132
|
+
else if (node.children.some((child) => child.status === "paused")) node.status = "paused";
|
|
1133
|
+
}
|
|
1134
|
+
if (node.error) node.status = "failed";
|
|
1135
|
+
};
|
|
1136
|
+
for (const node of graph.nodes) updateNode(node);
|
|
1137
|
+
statusPayload.workflowGraph = graph;
|
|
1138
|
+
};
|
|
1000
1139
|
const writeStatusPayload = (): void => {
|
|
1140
|
+
refreshWorkflowGraph();
|
|
1001
1141
|
writeAtomicJson(statusPath, statusPayload);
|
|
1002
1142
|
emitNestedSelfEvent(statusPayload.state === "running" || statusPayload.state === "queued" ? "subagent.nested.updated" : "subagent.nested.completed");
|
|
1003
1143
|
};
|
|
1144
|
+
const markDynamicGraphGroup = (stepIndex: number, status: "completed" | "failed" | "running", error?: string, acceptance?: import("../../shared/types.ts").AcceptanceLedger): void => {
|
|
1145
|
+
const groupNode = statusPayload.workflowGraph?.nodes.find((node) => node.id === `step-${stepIndex}`);
|
|
1146
|
+
if (!groupNode) return;
|
|
1147
|
+
groupNode.status = status;
|
|
1148
|
+
groupNode.error = error;
|
|
1149
|
+
groupNode.acceptanceStatus = acceptance?.status ?? groupNode.acceptanceStatus;
|
|
1150
|
+
};
|
|
1004
1151
|
|
|
1005
1152
|
const stepOutputActivityAt = (index: number): number => {
|
|
1006
1153
|
const step = statusPayload.steps[index];
|
|
@@ -1017,8 +1164,8 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1017
1164
|
};
|
|
1018
1165
|
const emittedControlEventKeys = new Set<string>();
|
|
1019
1166
|
const activeLongRunningSteps = new Set<number>();
|
|
1020
|
-
const mutatingFailureStates =
|
|
1021
|
-
const pendingToolResults: Array<{ tool: string; path?: string; mutates: boolean; startedAt?: number } | undefined> =
|
|
1167
|
+
const mutatingFailureStates = initialStatusSteps.map(() => createMutatingFailureState());
|
|
1168
|
+
const pendingToolResults: Array<{ tool: string; path?: string; mutates: boolean; startedAt?: number } | undefined> = initialStatusSteps.map(() => undefined);
|
|
1022
1169
|
const mutatingFailureWindowMs = 5 * 60_000;
|
|
1023
1170
|
const appendControlEvent = (event: ReturnType<typeof buildControlEvent>) => {
|
|
1024
1171
|
if (!controlConfig.enabled) return;
|
|
@@ -1160,7 +1307,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1160
1307
|
resetMutatingFailureState(mutatingFailureStates[flatIndex]!);
|
|
1161
1308
|
}
|
|
1162
1309
|
} else if (event.type === "message_end" && event.message?.role === "assistant") {
|
|
1163
|
-
appendRecentStepOutput(step, extractTextFromContent(event.message.content).split("\n").slice(-10));
|
|
1310
|
+
appendRecentStepOutput(step, stripAcceptanceReport(extractTextFromContent(event.message.content)).split("\n").slice(-10));
|
|
1164
1311
|
step.turnCount = (step.turnCount ?? 0) + 1;
|
|
1165
1312
|
const usage = event.message.usage;
|
|
1166
1313
|
if (usage) {
|
|
@@ -1291,6 +1438,313 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1291
1438
|
if (interrupted) break;
|
|
1292
1439
|
const step = steps[stepIndex];
|
|
1293
1440
|
|
|
1441
|
+
if (isDynamicRunnerGroup(step)) {
|
|
1442
|
+
const groupStartFlatIndex = flatIndex;
|
|
1443
|
+
let materialized: ReturnType<typeof materializeDynamicParallelStep>;
|
|
1444
|
+
try {
|
|
1445
|
+
materialized = materializeDynamicParallelStep(step as Parameters<typeof materializeDynamicParallelStep>[0], outputs, stepIndex, { maxItems: config.dynamicFanoutMaxItems, allowRunnerFields: true });
|
|
1446
|
+
if (materialized.collectedOnEmpty) validateDynamicCollection(step.collect.outputSchema, materialized.collectedOnEmpty);
|
|
1447
|
+
} catch (error) {
|
|
1448
|
+
const now = Date.now();
|
|
1449
|
+
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
1450
|
+
statusPayload.state = "failed";
|
|
1451
|
+
statusPayload.error = message;
|
|
1452
|
+
statusPayload.currentStep = flatIndex;
|
|
1453
|
+
const placeholder = statusPayload.steps[groupStartFlatIndex];
|
|
1454
|
+
if (placeholder) {
|
|
1455
|
+
placeholder.status = "failed";
|
|
1456
|
+
placeholder.error = message;
|
|
1457
|
+
placeholder.startedAt = now;
|
|
1458
|
+
placeholder.endedAt = now;
|
|
1459
|
+
placeholder.durationMs = 0;
|
|
1460
|
+
placeholder.exitCode = 1;
|
|
1461
|
+
}
|
|
1462
|
+
statusPayload.lastUpdate = now;
|
|
1463
|
+
markDynamicGraphGroup(stepIndex, "failed", message);
|
|
1464
|
+
writeStatusPayload();
|
|
1465
|
+
results.push({ agent: step.parallel.agent, output: message, error: message, success: false, exitCode: 1 });
|
|
1466
|
+
break;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
if (materialized.parallel.length === 0) {
|
|
1470
|
+
const now = Date.now();
|
|
1471
|
+
const collection = materialized.collectedOnEmpty ?? [];
|
|
1472
|
+
outputs[step.collect.as] = {
|
|
1473
|
+
text: JSON.stringify(collection),
|
|
1474
|
+
structured: collection,
|
|
1475
|
+
agent: step.parallel.agent,
|
|
1476
|
+
stepIndex,
|
|
1477
|
+
};
|
|
1478
|
+
statusPayload.outputs = outputs;
|
|
1479
|
+
const placeholder = statusPayload.steps[groupStartFlatIndex];
|
|
1480
|
+
if (placeholder) {
|
|
1481
|
+
placeholder.status = "complete";
|
|
1482
|
+
placeholder.startedAt = now;
|
|
1483
|
+
placeholder.endedAt = now;
|
|
1484
|
+
placeholder.durationMs = 0;
|
|
1485
|
+
}
|
|
1486
|
+
previousOutput = "Dynamic fanout produced 0 results.";
|
|
1487
|
+
const groupAcceptance = step.effectiveAcceptance?.explicit
|
|
1488
|
+
? await evaluateAcceptance({
|
|
1489
|
+
acceptance: step.effectiveAcceptance,
|
|
1490
|
+
output: "",
|
|
1491
|
+
report: aggregateAcceptanceReport({
|
|
1492
|
+
results: [],
|
|
1493
|
+
notes: "Dynamic fanout produced 0 results.",
|
|
1494
|
+
}),
|
|
1495
|
+
cwd,
|
|
1496
|
+
})
|
|
1497
|
+
: undefined;
|
|
1498
|
+
if (placeholder && groupAcceptance) placeholder.acceptance = groupAcceptance;
|
|
1499
|
+
const groupAcceptanceFailure = groupAcceptance ? acceptanceFailureMessage(groupAcceptance) : undefined;
|
|
1500
|
+
if (groupAcceptanceFailure) {
|
|
1501
|
+
statusPayload.state = "failed";
|
|
1502
|
+
statusPayload.error = groupAcceptanceFailure;
|
|
1503
|
+
if (placeholder) {
|
|
1504
|
+
placeholder.status = "failed";
|
|
1505
|
+
placeholder.error = groupAcceptanceFailure;
|
|
1506
|
+
placeholder.exitCode = 1;
|
|
1507
|
+
}
|
|
1508
|
+
markDynamicGraphGroup(stepIndex, "failed", groupAcceptanceFailure, groupAcceptance);
|
|
1509
|
+
statusPayload.lastUpdate = now;
|
|
1510
|
+
writeStatusPayload();
|
|
1511
|
+
results.push({ agent: step.parallel.agent, output: groupAcceptanceFailure, error: groupAcceptanceFailure, success: false, exitCode: 1, acceptance: groupAcceptance });
|
|
1512
|
+
break;
|
|
1513
|
+
}
|
|
1514
|
+
flatIndex++;
|
|
1515
|
+
statusPayload.lastUpdate = now;
|
|
1516
|
+
markDynamicGraphGroup(stepIndex, "completed", undefined, groupAcceptance);
|
|
1517
|
+
writeStatusPayload();
|
|
1518
|
+
continue;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
const dynamicSteps = materialized.parallel.map((task, itemIndex) => ({
|
|
1522
|
+
...step.parallel,
|
|
1523
|
+
task: task.task ?? step.parallel.task,
|
|
1524
|
+
label: task.label ?? step.parallel.label,
|
|
1525
|
+
structuredOutput: undefined,
|
|
1526
|
+
structuredOutputSchema: step.parallel.structuredOutputSchema ?? step.parallel.structuredOutput?.schema,
|
|
1527
|
+
}));
|
|
1528
|
+
const dynamicStatusSteps: RunnerStatusStep[] = dynamicSteps.map((task) => ({
|
|
1529
|
+
agent: task.agent,
|
|
1530
|
+
phase: task.phase ?? step.phase,
|
|
1531
|
+
label: task.label,
|
|
1532
|
+
outputName: undefined,
|
|
1533
|
+
structured: Boolean(task.structuredOutputSchema),
|
|
1534
|
+
status: "pending",
|
|
1535
|
+
...(task.sessionFile ? { sessionFile: task.sessionFile } : {}),
|
|
1536
|
+
skills: task.skills,
|
|
1537
|
+
model: task.model,
|
|
1538
|
+
thinking: task.thinking,
|
|
1539
|
+
attemptedModels: task.modelCandidates && task.modelCandidates.length > 0 ? task.modelCandidates : task.model ? [task.model] : undefined,
|
|
1540
|
+
recentTools: [],
|
|
1541
|
+
recentOutput: [],
|
|
1542
|
+
}));
|
|
1543
|
+
statusPayload.steps.splice(groupStartFlatIndex, 1, ...dynamicStatusSteps);
|
|
1544
|
+
if (config.childIntercomTargets) {
|
|
1545
|
+
config.childIntercomTargets = statusPayload.steps.map((statusStep, index) => resolveSubagentIntercomTarget(id, statusStep.agent, index));
|
|
1546
|
+
}
|
|
1547
|
+
mutatingFailureStates.splice(groupStartFlatIndex, 1, ...dynamicStatusSteps.map(() => createMutatingFailureState()));
|
|
1548
|
+
pendingToolResults.splice(groupStartFlatIndex, 1, ...dynamicStatusSteps.map(() => undefined));
|
|
1549
|
+
const materializedDelta = dynamicStatusSteps.length - 1;
|
|
1550
|
+
for (const group of statusPayload.parallelGroups) {
|
|
1551
|
+
if (group.stepIndex === stepIndex) {
|
|
1552
|
+
group.start = groupStartFlatIndex;
|
|
1553
|
+
group.count = dynamicStatusSteps.length;
|
|
1554
|
+
} else if (group.start > groupStartFlatIndex) {
|
|
1555
|
+
group.start += materializedDelta;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
if (statusPayload.workflowGraph) {
|
|
1559
|
+
const shiftFlatIndexes = (nodes: NonNullable<typeof statusPayload.workflowGraph>["nodes"]): void => {
|
|
1560
|
+
for (const node of nodes) {
|
|
1561
|
+
if (node.stepIndex !== undefined && node.stepIndex > stepIndex && node.flatIndex !== undefined && node.flatIndex >= groupStartFlatIndex) {
|
|
1562
|
+
node.flatIndex += dynamicStatusSteps.length;
|
|
1563
|
+
}
|
|
1564
|
+
if (node.children) shiftFlatIndexes(node.children);
|
|
1565
|
+
}
|
|
1566
|
+
};
|
|
1567
|
+
shiftFlatIndexes(statusPayload.workflowGraph.nodes);
|
|
1568
|
+
const groupNode = statusPayload.workflowGraph.nodes.find((node) => node.id === `step-${stepIndex}`);
|
|
1569
|
+
if (groupNode) {
|
|
1570
|
+
groupNode.children = materialized.items.map((item, itemIndex) => ({
|
|
1571
|
+
id: `step-${stepIndex}-item-${item.idKey}`,
|
|
1572
|
+
kind: "agent",
|
|
1573
|
+
agent: step.parallel.agent,
|
|
1574
|
+
phase: dynamicSteps[itemIndex]?.phase ?? step.phase,
|
|
1575
|
+
label: dynamicSteps[itemIndex]?.label?.trim() || `${step.parallel.agent} ${item.key}`,
|
|
1576
|
+
status: "pending",
|
|
1577
|
+
flatIndex: groupStartFlatIndex + itemIndex,
|
|
1578
|
+
stepIndex,
|
|
1579
|
+
itemKey: item.key,
|
|
1580
|
+
structured: Boolean(dynamicSteps[itemIndex]?.structuredOutputSchema),
|
|
1581
|
+
}));
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
writeStatusPayload();
|
|
1585
|
+
|
|
1586
|
+
const concurrency = step.concurrency ?? MAX_PARALLEL_CONCURRENCY;
|
|
1587
|
+
const failFast = step.failFast ?? false;
|
|
1588
|
+
let aborted = false;
|
|
1589
|
+
const parallelResults = await mapConcurrent(dynamicSteps, concurrency, async (task, taskIdx) => {
|
|
1590
|
+
const fi = groupStartFlatIndex + taskIdx;
|
|
1591
|
+
if (aborted && failFast) {
|
|
1592
|
+
const skippedAt = Date.now();
|
|
1593
|
+
statusPayload.steps[fi].status = "failed";
|
|
1594
|
+
statusPayload.steps[fi].error = "Skipped due to fail-fast";
|
|
1595
|
+
statusPayload.steps[fi].startedAt = skippedAt;
|
|
1596
|
+
statusPayload.steps[fi].endedAt = skippedAt;
|
|
1597
|
+
statusPayload.steps[fi].durationMs = 0;
|
|
1598
|
+
statusPayload.steps[fi].exitCode = -1;
|
|
1599
|
+
statusPayload.lastUpdate = skippedAt;
|
|
1600
|
+
writeStatusPayload();
|
|
1601
|
+
return { agent: task.agent, output: "(skipped — fail-fast)", exitCode: -1 as number | null, skipped: true };
|
|
1602
|
+
}
|
|
1603
|
+
const taskStartTime = Date.now();
|
|
1604
|
+
statusPayload.currentStep = fi;
|
|
1605
|
+
statusPayload.steps[fi].status = "running";
|
|
1606
|
+
statusPayload.steps[fi].error = undefined;
|
|
1607
|
+
statusPayload.steps[fi].activityState = undefined;
|
|
1608
|
+
resetStepLiveDetail(statusPayload.steps[fi]);
|
|
1609
|
+
statusPayload.steps[fi].startedAt = taskStartTime;
|
|
1610
|
+
statusPayload.steps[fi].lastActivityAt = taskStartTime;
|
|
1611
|
+
statusPayload.outputFile = path.join(asyncDir, `output-${fi}.log`);
|
|
1612
|
+
statusPayload.lastActivityAt = taskStartTime;
|
|
1613
|
+
statusPayload.lastUpdate = taskStartTime;
|
|
1614
|
+
writeStatusPayload();
|
|
1615
|
+
appendJsonl(eventsPath, JSON.stringify({ type: "subagent.step.started", ts: taskStartTime, runId: id, stepIndex: fi, agent: task.agent }));
|
|
1616
|
+
const singleResult = await runSingleStep(task, {
|
|
1617
|
+
previousOutput, placeholder, cwd, sessionEnabled,
|
|
1618
|
+
outputs,
|
|
1619
|
+
sessionDir: config.sessionDir ? path.join(config.sessionDir, `dynamic-${stepIndex}-${taskIdx}`) : undefined,
|
|
1620
|
+
artifactsDir, artifactConfig, id,
|
|
1621
|
+
flatIndex: fi, flatStepCount: Math.max(statusPayload.steps.length, 1),
|
|
1622
|
+
outputFile: path.join(asyncDir, `output-${fi}.log`),
|
|
1623
|
+
piPackageRoot: config.piPackageRoot,
|
|
1624
|
+
piArgv1: config.piArgv1,
|
|
1625
|
+
childIntercomTarget: config.childIntercomTargets?.[fi],
|
|
1626
|
+
orchestratorIntercomTarget: config.controlIntercomTarget,
|
|
1627
|
+
nestedRoute: config.nestedRoute,
|
|
1628
|
+
registerInterrupt: (interrupt) => {
|
|
1629
|
+
activeChildInterrupt = interrupt;
|
|
1630
|
+
},
|
|
1631
|
+
onAttemptStart: (attempt) => updateStepModel(fi, attempt.model, attempt.thinking),
|
|
1632
|
+
onChildEvent: (event) => updateStepFromChildEvent(fi, event),
|
|
1633
|
+
});
|
|
1634
|
+
const taskEndTime = Date.now();
|
|
1635
|
+
statusPayload.steps[fi].status = singleResult.exitCode === 0 ? "complete" : "failed";
|
|
1636
|
+
statusPayload.steps[fi].endedAt = taskEndTime;
|
|
1637
|
+
statusPayload.steps[fi].durationMs = taskEndTime - taskStartTime;
|
|
1638
|
+
statusPayload.steps[fi].exitCode = singleResult.exitCode;
|
|
1639
|
+
statusPayload.steps[fi].model = singleResult.model;
|
|
1640
|
+
statusPayload.steps[fi].thinking = resolveEffectiveThinking(singleResult.model, statusPayload.steps[fi].thinking);
|
|
1641
|
+
statusPayload.steps[fi].attemptedModels = singleResult.attemptedModels;
|
|
1642
|
+
statusPayload.steps[fi].modelAttempts = singleResult.modelAttempts;
|
|
1643
|
+
statusPayload.steps[fi].error = singleResult.error;
|
|
1644
|
+
statusPayload.steps[fi].structuredOutput = singleResult.structuredOutput;
|
|
1645
|
+
statusPayload.steps[fi].structuredOutputPath = singleResult.structuredOutputPath;
|
|
1646
|
+
statusPayload.steps[fi].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
1647
|
+
statusPayload.steps[fi].acceptance = singleResult.acceptance;
|
|
1648
|
+
statusPayload.lastUpdate = taskEndTime;
|
|
1649
|
+
writeStatusPayload();
|
|
1650
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
1651
|
+
type: singleResult.exitCode === 0 ? "subagent.step.completed" : "subagent.step.failed",
|
|
1652
|
+
ts: taskEndTime, runId: id, stepIndex: fi, agent: task.agent,
|
|
1653
|
+
exitCode: singleResult.exitCode, durationMs: taskEndTime - taskStartTime,
|
|
1654
|
+
}));
|
|
1655
|
+
if (singleResult.exitCode !== 0 && failFast) aborted = true;
|
|
1656
|
+
return { ...singleResult, skipped: false };
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1659
|
+
flatIndex += dynamicSteps.length;
|
|
1660
|
+
for (const pr of parallelResults) {
|
|
1661
|
+
results.push({
|
|
1662
|
+
agent: pr.agent,
|
|
1663
|
+
output: pr.output,
|
|
1664
|
+
error: pr.error,
|
|
1665
|
+
success: pr.exitCode === 0,
|
|
1666
|
+
exitCode: pr.exitCode,
|
|
1667
|
+
skipped: pr.skipped,
|
|
1668
|
+
sessionFile: pr.sessionFile,
|
|
1669
|
+
intercomTarget: pr.intercomTarget,
|
|
1670
|
+
model: pr.model,
|
|
1671
|
+
attemptedModels: pr.attemptedModels,
|
|
1672
|
+
modelAttempts: pr.modelAttempts,
|
|
1673
|
+
artifactPaths: pr.artifactPaths,
|
|
1674
|
+
structuredOutput: pr.structuredOutput,
|
|
1675
|
+
structuredOutputPath: pr.structuredOutputPath,
|
|
1676
|
+
structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
|
|
1677
|
+
acceptance: pr.acceptance,
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
const collection = collectDynamicResults(step as Parameters<typeof collectDynamicResults>[0], materialized.items, parallelResults);
|
|
1681
|
+
const failures = parallelResults.filter((result) => result.exitCode !== 0 && result.exitCode !== -1);
|
|
1682
|
+
if (failures.length === 0) {
|
|
1683
|
+
try {
|
|
1684
|
+
validateDynamicCollection(step.collect.outputSchema, collection);
|
|
1685
|
+
outputs[step.collect.as] = {
|
|
1686
|
+
text: JSON.stringify(collection),
|
|
1687
|
+
structured: collection,
|
|
1688
|
+
agent: step.parallel.agent,
|
|
1689
|
+
stepIndex,
|
|
1690
|
+
};
|
|
1691
|
+
statusPayload.outputs = outputs;
|
|
1692
|
+
const groupAcceptance = step.effectiveAcceptance
|
|
1693
|
+
? await evaluateAcceptance({
|
|
1694
|
+
acceptance: step.effectiveAcceptance,
|
|
1695
|
+
output: "",
|
|
1696
|
+
report: aggregateAcceptanceReport({
|
|
1697
|
+
results: parallelResults,
|
|
1698
|
+
notes: `Dynamic fanout collected ${collection.length} result(s) into ${step.collect.as}.`,
|
|
1699
|
+
}),
|
|
1700
|
+
cwd,
|
|
1701
|
+
})
|
|
1702
|
+
: undefined;
|
|
1703
|
+
const groupAcceptanceFailure = groupAcceptance ? acceptanceFailureMessage(groupAcceptance) : undefined;
|
|
1704
|
+
markDynamicGraphGroup(stepIndex, groupAcceptanceFailure ? "failed" : "completed", groupAcceptanceFailure, groupAcceptance);
|
|
1705
|
+
if (groupAcceptanceFailure) {
|
|
1706
|
+
results.push({
|
|
1707
|
+
agent: step.parallel.agent,
|
|
1708
|
+
output: groupAcceptanceFailure,
|
|
1709
|
+
error: groupAcceptanceFailure,
|
|
1710
|
+
success: false,
|
|
1711
|
+
exitCode: 1,
|
|
1712
|
+
structuredOutput: collection,
|
|
1713
|
+
acceptance: groupAcceptance,
|
|
1714
|
+
});
|
|
1715
|
+
statusPayload.error = groupAcceptanceFailure;
|
|
1716
|
+
}
|
|
1717
|
+
} catch (error) {
|
|
1718
|
+
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
1719
|
+
results.push({ agent: step.parallel.agent, output: message, error: message, success: false, exitCode: 1, structuredOutput: collection });
|
|
1720
|
+
statusPayload.error = message;
|
|
1721
|
+
markDynamicGraphGroup(stepIndex, "failed", message);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
previousOutput = aggregateParallelOutputs(
|
|
1725
|
+
parallelResults.map((r, i) => ({
|
|
1726
|
+
agent: r.agent,
|
|
1727
|
+
taskIndex: i,
|
|
1728
|
+
output: r.output,
|
|
1729
|
+
exitCode: r.exitCode,
|
|
1730
|
+
error: r.error,
|
|
1731
|
+
})),
|
|
1732
|
+
(i, agent) => `=== Dynamic Item ${i + 1} (${agent}, key ${materialized.items[i]?.key ?? i}) ===`,
|
|
1733
|
+
);
|
|
1734
|
+
appendJsonl(eventsPath, JSON.stringify({
|
|
1735
|
+
type: "subagent.dynamic.completed",
|
|
1736
|
+
ts: Date.now(),
|
|
1737
|
+
runId: id,
|
|
1738
|
+
stepIndex,
|
|
1739
|
+
success: failures.length === 0,
|
|
1740
|
+
}));
|
|
1741
|
+
if (failures.length > 0) markDynamicGraphGroup(stepIndex, "failed", failures[0]?.error ?? "Dynamic fanout child failed.");
|
|
1742
|
+
statusPayload.lastUpdate = Date.now();
|
|
1743
|
+
writeStatusPayload();
|
|
1744
|
+
if (failures.length > 0 || statusPayload.error) break;
|
|
1745
|
+
continue;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1294
1748
|
if (isParallelGroup(step)) {
|
|
1295
1749
|
const group = step;
|
|
1296
1750
|
const concurrency = group.concurrency ?? MAX_PARALLEL_CONCURRENCY;
|
|
@@ -1408,6 +1862,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1408
1862
|
|
|
1409
1863
|
const singleResult = await runSingleStep(taskForRun, {
|
|
1410
1864
|
previousOutput, placeholder, cwd: taskCwd, sessionEnabled,
|
|
1865
|
+
outputs,
|
|
1411
1866
|
sessionDir: taskSessionDir,
|
|
1412
1867
|
artifactsDir, artifactConfig, id,
|
|
1413
1868
|
flatIndex: fi, flatStepCount: flatSteps.length,
|
|
@@ -1441,6 +1896,10 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1441
1896
|
statusPayload.steps[fi].attemptedModels = singleResult.attemptedModels;
|
|
1442
1897
|
statusPayload.steps[fi].modelAttempts = singleResult.modelAttempts;
|
|
1443
1898
|
statusPayload.steps[fi].error = singleResult.error;
|
|
1899
|
+
statusPayload.steps[fi].structuredOutput = singleResult.structuredOutput;
|
|
1900
|
+
statusPayload.steps[fi].structuredOutputPath = singleResult.structuredOutputPath;
|
|
1901
|
+
statusPayload.steps[fi].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
1902
|
+
statusPayload.steps[fi].acceptance = singleResult.acceptance;
|
|
1444
1903
|
statusPayload.lastUpdate = taskEndTime;
|
|
1445
1904
|
writeStatusPayload();
|
|
1446
1905
|
|
|
@@ -1494,6 +1953,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1494
1953
|
output: pr.output,
|
|
1495
1954
|
error: pr.error,
|
|
1496
1955
|
success: pr.exitCode === 0,
|
|
1956
|
+
exitCode: pr.exitCode,
|
|
1497
1957
|
skipped: pr.skipped,
|
|
1498
1958
|
sessionFile: pr.sessionFile,
|
|
1499
1959
|
intercomTarget: pr.intercomTarget,
|
|
@@ -1502,8 +1962,21 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1502
1962
|
attemptedModels: pr.attemptedModels,
|
|
1503
1963
|
modelAttempts: pr.modelAttempts,
|
|
1504
1964
|
artifactPaths: pr.artifactPaths,
|
|
1505
|
-
|
|
1965
|
+
structuredOutput: pr.structuredOutput,
|
|
1966
|
+
structuredOutputPath: pr.structuredOutputPath,
|
|
1967
|
+
structuredOutputSchemaPath: pr.structuredOutputSchemaPath,
|
|
1968
|
+
acceptance: pr.acceptance,
|
|
1969
|
+
});
|
|
1970
|
+
}
|
|
1971
|
+
for (let t = 0; t < group.parallel.length; t++) {
|
|
1972
|
+
const outputName = group.parallel[t]?.outputName;
|
|
1973
|
+
if (outputName) outputs[outputName] = outputEntryFromAsyncResult({
|
|
1974
|
+
agent: parallelResults[t]!.agent,
|
|
1975
|
+
output: parallelResults[t]!.output,
|
|
1976
|
+
structuredOutput: parallelResults[t]!.structuredOutput,
|
|
1977
|
+
}, stepIndex);
|
|
1506
1978
|
}
|
|
1979
|
+
statusPayload.outputs = outputs;
|
|
1507
1980
|
|
|
1508
1981
|
previousOutput = aggregateParallelOutputs(
|
|
1509
1982
|
parallelResults.map((r) => ({
|
|
@@ -1558,6 +2031,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1558
2031
|
|
|
1559
2032
|
const singleResult = await runSingleStep(seqStep, {
|
|
1560
2033
|
previousOutput, placeholder, cwd, sessionEnabled,
|
|
2034
|
+
outputs,
|
|
1561
2035
|
sessionDir: config.sessionDir,
|
|
1562
2036
|
artifactsDir, artifactConfig, id,
|
|
1563
2037
|
flatIndex, flatStepCount: flatSteps.length,
|
|
@@ -1584,6 +2058,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1584
2058
|
output: singleResult.output,
|
|
1585
2059
|
error: singleResult.error,
|
|
1586
2060
|
success: singleResult.exitCode === 0,
|
|
2061
|
+
exitCode: singleResult.exitCode,
|
|
1587
2062
|
sessionFile: singleResult.sessionFile,
|
|
1588
2063
|
intercomTarget: singleResult.intercomTarget,
|
|
1589
2064
|
model: singleResult.model,
|
|
@@ -1591,7 +2066,19 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1591
2066
|
attemptedModels: singleResult.attemptedModels,
|
|
1592
2067
|
modelAttempts: singleResult.modelAttempts,
|
|
1593
2068
|
artifactPaths: singleResult.artifactPaths,
|
|
2069
|
+
structuredOutput: singleResult.structuredOutput,
|
|
2070
|
+
structuredOutputPath: singleResult.structuredOutputPath,
|
|
2071
|
+
structuredOutputSchemaPath: singleResult.structuredOutputSchemaPath,
|
|
2072
|
+
acceptance: singleResult.acceptance,
|
|
1594
2073
|
});
|
|
2074
|
+
if (seqStep.outputName) {
|
|
2075
|
+
outputs[seqStep.outputName] = outputEntryFromAsyncResult({
|
|
2076
|
+
agent: singleResult.agent,
|
|
2077
|
+
output: singleResult.output,
|
|
2078
|
+
structuredOutput: singleResult.structuredOutput,
|
|
2079
|
+
}, stepIndex);
|
|
2080
|
+
}
|
|
2081
|
+
statusPayload.outputs = outputs;
|
|
1595
2082
|
|
|
1596
2083
|
const cumulativeTokens = config.sessionDir ? parseSessionTokens(config.sessionDir) : null;
|
|
1597
2084
|
let stepTokens: TokenUsage | null = cumulativeTokens
|
|
@@ -1625,6 +2112,10 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1625
2112
|
statusPayload.steps[flatIndex].attemptedModels = singleResult.attemptedModels;
|
|
1626
2113
|
statusPayload.steps[flatIndex].modelAttempts = singleResult.modelAttempts;
|
|
1627
2114
|
statusPayload.steps[flatIndex].error = singleResult.error;
|
|
2115
|
+
statusPayload.steps[flatIndex].structuredOutput = singleResult.structuredOutput;
|
|
2116
|
+
statusPayload.steps[flatIndex].structuredOutputPath = singleResult.structuredOutputPath;
|
|
2117
|
+
statusPayload.steps[flatIndex].structuredOutputSchemaPath = singleResult.structuredOutputSchemaPath;
|
|
2118
|
+
statusPayload.steps[flatIndex].acceptance = singleResult.acceptance;
|
|
1628
2119
|
if (stepTokens) {
|
|
1629
2120
|
statusPayload.steps[flatIndex].tokens = stepTokens;
|
|
1630
2121
|
statusPayload.totalTokens = { ...previousCumulativeTokens };
|
|
@@ -1726,7 +2217,7 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1726
2217
|
statusPayload.shareUrl = shareUrl;
|
|
1727
2218
|
statusPayload.gistUrl = gistUrl;
|
|
1728
2219
|
statusPayload.shareError = shareError;
|
|
1729
|
-
if (statusPayload.state === "failed") {
|
|
2220
|
+
if (statusPayload.state === "failed" && !statusPayload.error) {
|
|
1730
2221
|
const failedStep = statusPayload.steps.find((s) => s.status === "failed");
|
|
1731
2222
|
if (failedStep?.agent) {
|
|
1732
2223
|
statusPayload.error = `Step failed: ${failedStep.agent}`;
|
|
@@ -1784,7 +2275,13 @@ async function runSubagent(config: SubagentRunConfig): Promise<void> {
|
|
|
1784
2275
|
modelAttempts: r.modelAttempts,
|
|
1785
2276
|
artifactPaths: r.artifactPaths,
|
|
1786
2277
|
truncated: r.truncated,
|
|
2278
|
+
structuredOutput: r.structuredOutput,
|
|
2279
|
+
structuredOutputPath: r.structuredOutputPath,
|
|
2280
|
+
structuredOutputSchemaPath: r.structuredOutputSchemaPath,
|
|
2281
|
+
acceptance: r.acceptance,
|
|
1787
2282
|
})),
|
|
2283
|
+
outputs,
|
|
2284
|
+
workflowGraph: statusPayload.workflowGraph,
|
|
1788
2285
|
exitCode: interrupted || results.every((r) => r.success) ? 0 : 1,
|
|
1789
2286
|
timestamp: runEndedAt,
|
|
1790
2287
|
durationMs: runEndedAt - overallStartTime,
|