pi-cohort 7.3.0 → 7.4.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 +14 -0
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/runs/background/async-execution.ts +12 -6
- package/src/runs/background/subagent-runner.ts +53 -31
- package/src/runs/foreground/chain-execution.ts +515 -498
- package/src/runs/foreground/execution.ts +1 -1
- package/src/runs/shared/acceptance.ts +4 -2
- package/src/runs/shared/chain-outputs.ts +73 -12
- package/src/runs/shared/parallel-utils.ts +1 -0
- package/src/runs/shared/worktree.ts +34 -3
- package/src/shared/types.ts +4 -0
|
@@ -38,6 +38,7 @@ import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, resolve
|
|
|
38
38
|
import { recordRun } from "../shared/run-history.ts";
|
|
39
39
|
import {
|
|
40
40
|
cleanupWorktrees,
|
|
41
|
+
removeTrees,
|
|
41
42
|
createWorktrees,
|
|
42
43
|
diffWorktrees,
|
|
43
44
|
findWorktreeTaskCwdConflict,
|
|
@@ -64,7 +65,7 @@ import { resolveModelCandidate } from "../shared/model-fallback.ts";
|
|
|
64
65
|
import { applyThinkingSuffix } from "../shared/pi-args.ts";
|
|
65
66
|
import { validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
66
67
|
import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
|
|
67
|
-
import { ChainOutputValidationError, outputEntryFromResult, resolveOutputReferences, validateChainOutputBindings } from "../shared/chain-outputs.ts";
|
|
68
|
+
import { ChainOutputValidationError, TreeRegistry, outputEntryFromResult, resolveOutputReferences, validateChainOutputBindings } from "../shared/chain-outputs.ts";
|
|
68
69
|
import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
69
70
|
import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection, type DynamicCollectedResult } from "../shared/dynamic-fanout.ts";
|
|
70
71
|
import { acceptanceFailureMessage, aggregateAcceptanceReport, evaluateAcceptance, resolveEffectiveAcceptance } from "../shared/acceptance.ts";
|
|
@@ -130,6 +131,8 @@ interface ParallelChainRunInput {
|
|
|
130
131
|
results: SingleResult[];
|
|
131
132
|
allProgress: AgentProgress[];
|
|
132
133
|
outputs: ChainOutputMap;
|
|
134
|
+
trees: TreeRegistry;
|
|
135
|
+
stepCwd?: string;
|
|
133
136
|
chainAgents: string[];
|
|
134
137
|
chainSteps: ChainStep[];
|
|
135
138
|
totalSteps: number;
|
|
@@ -236,8 +239,13 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
236
239
|
);
|
|
237
240
|
|
|
238
241
|
let taskStr: string;
|
|
242
|
+
let taskCwd: string;
|
|
239
243
|
try {
|
|
240
|
-
|
|
244
|
+
const cwdTemplate = task.cwd ?? input.stepCwd;
|
|
245
|
+
taskCwd = input.worktreeSetup
|
|
246
|
+
? input.worktreeSetup.worktrees[taskIndex]!.agentCwd
|
|
247
|
+
: resolveChildCwd(input.cwd ?? input.ctx.cwd, cwdTemplate === undefined ? undefined : resolveOutputReferences(cwdTemplate, input.outputs, input.trees));
|
|
248
|
+
taskStr = resolveOutputReferences(taskTemplate, input.outputs, input.trees);
|
|
241
249
|
} catch (error) {
|
|
242
250
|
if (!(error instanceof ChainOutputValidationError)) throw error;
|
|
243
251
|
if (failFast) aborted = true;
|
|
@@ -263,10 +271,6 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
263
271
|
?? resolveModelCandidate(taskAgentConfig?.model, input.availableModels, input.ctx.model?.provider);
|
|
264
272
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(input.maxSubagentDepth, taskAgentConfig?.maxSubagentDepth);
|
|
265
273
|
|
|
266
|
-
const taskCwd = input.worktreeSetup
|
|
267
|
-
? input.worktreeSetup.worktrees[taskIndex]!.agentCwd
|
|
268
|
-
: resolveChildCwd(input.cwd ?? input.ctx.cwd, task.cwd);
|
|
269
|
-
|
|
270
274
|
const outputPath = typeof behavior.output === "string"
|
|
271
275
|
? (path.isAbsolute(behavior.output) ? behavior.output : path.join(input.chainDir, behavior.output))
|
|
272
276
|
: undefined;
|
|
@@ -526,6 +530,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
526
530
|
}
|
|
527
531
|
|
|
528
532
|
const chainDir = createChainDir(runId, chainDirBase);
|
|
533
|
+
const trees = new TreeRegistry(chainDir);
|
|
529
534
|
const hasParallelSteps = chainSteps.some((step) => isParallelStep(step) || isDynamicParallelStep(step));
|
|
530
535
|
let templates: ResolvedTemplates = resolveChainTemplates(chainSteps);
|
|
531
536
|
const shouldClarify = clarify === true && ctx.hasUI && !hasParallelSteps;
|
|
@@ -631,54 +636,264 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
631
636
|
let globalTaskIndex = 0;
|
|
632
637
|
let progressCreated = false;
|
|
633
638
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
639
|
+
try {
|
|
640
|
+
for (let stepIndex = 0; stepIndex < chainSteps.length; stepIndex++) {
|
|
641
|
+
const step = chainSteps[stepIndex]!;
|
|
642
|
+
const stepTemplates = templates[stepIndex]!;
|
|
643
|
+
|
|
644
|
+
if (isParallelStep(step)) {
|
|
645
|
+
const parallelTemplates = stepTemplates as string[];
|
|
646
|
+
const parallelCwd = resolveChildCwd(cwd ?? ctx.cwd, step.cwd);
|
|
647
|
+
let worktreeSetup: WorktreeSetup | undefined;
|
|
648
|
+
if (step.worktree) {
|
|
649
|
+
const worktreeTaskCwdConflict = findWorktreeTaskCwdConflict(step.parallel, parallelCwd);
|
|
650
|
+
if (worktreeTaskCwdConflict) {
|
|
651
|
+
return buildChainExecutionErrorResult(
|
|
652
|
+
`parallel chain step ${stepIndex + 1}: ${formatWorktreeTaskCwdConflict(worktreeTaskCwdConflict, parallelCwd)}`,
|
|
653
|
+
makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }),
|
|
654
|
+
failureSummaryOptions(),
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
try {
|
|
658
|
+
worktreeSetup = createWorktrees(parallelCwd, `${runId}-s${stepIndex}`, step.parallel.length, {
|
|
659
|
+
agents: step.parallel.map((task) => task.agent),
|
|
660
|
+
setupHook: params.worktreeSetupHook
|
|
661
|
+
? { hookPath: params.worktreeSetupHook, timeoutMs: params.worktreeSetupHookTimeoutMs }
|
|
662
|
+
: undefined,
|
|
663
|
+
});
|
|
664
|
+
} catch (error) {
|
|
665
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
666
|
+
return buildChainExecutionErrorResult(message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }), failureSummaryOptions());
|
|
667
|
+
}
|
|
650
668
|
}
|
|
669
|
+
|
|
651
670
|
try {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
671
|
+
const agentNames = step.parallel.map((task) => task.agent);
|
|
672
|
+
const parallelBehaviors = resolveParallelBehaviors(step.parallel, agents, stepIndex, chainSkills)
|
|
673
|
+
.map((behavior, taskIndex) => suppressProgressForReadOnlyTask(behavior, parallelTemplates[taskIndex] ?? step.parallel[taskIndex]?.task, originalTask));
|
|
674
|
+
for (let taskIndex = 0; taskIndex < step.parallel.length; taskIndex++) {
|
|
675
|
+
const behavior = parallelBehaviors[taskIndex]!;
|
|
676
|
+
const outputPath = typeof behavior.output === "string"
|
|
677
|
+
? (path.isAbsolute(behavior.output) ? behavior.output : path.join(chainDir, behavior.output))
|
|
678
|
+
: undefined;
|
|
679
|
+
const validationError = validateFileOnlyOutputMode(behavior.outputMode, outputPath, `Parallel chain step ${stepIndex + 1} task ${taskIndex + 1} (${step.parallel[taskIndex]!.agent})`);
|
|
680
|
+
if (validationError) return buildChainExecutionErrorResult(validationError, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex + taskIndex }), failureSummaryOptions());
|
|
681
|
+
}
|
|
682
|
+
progressCreated = ensureParallelProgressFile(chainDir, progressCreated, parallelBehaviors);
|
|
683
|
+
createParallelDirs(chainDir, stepIndex, step.parallel.length, agentNames);
|
|
684
|
+
|
|
685
|
+
const parallelResults = await runParallelChainTasks({
|
|
686
|
+
runtime: params.runtime,
|
|
687
|
+
step,
|
|
688
|
+
parallelTemplates,
|
|
689
|
+
parallelBehaviors,
|
|
690
|
+
agents,
|
|
691
|
+
stepIndex,
|
|
692
|
+
availableModels,
|
|
693
|
+
chainDir,
|
|
694
|
+
prev,
|
|
695
|
+
originalTask,
|
|
696
|
+
ctx,
|
|
697
|
+
cwd,
|
|
698
|
+
runId,
|
|
699
|
+
globalTaskIndex,
|
|
700
|
+
forwardedFlags: params.forwardedFlags,
|
|
701
|
+
sessionDirForIndex,
|
|
702
|
+
sessionFileForIndex,
|
|
703
|
+
shareEnabled,
|
|
704
|
+
artifactConfig,
|
|
705
|
+
artifactsDir,
|
|
706
|
+
signal,
|
|
707
|
+
onUpdate,
|
|
708
|
+
results,
|
|
709
|
+
allProgress,
|
|
710
|
+
outputs,
|
|
711
|
+
trees,
|
|
712
|
+
stepCwd: step.cwd,
|
|
713
|
+
chainAgents,
|
|
714
|
+
chainSteps,
|
|
715
|
+
totalSteps,
|
|
716
|
+
dynamicChildren,
|
|
717
|
+
dynamicGroupStatuses,
|
|
718
|
+
controlConfig,
|
|
719
|
+
onControlEvent,
|
|
720
|
+
foregroundControl,
|
|
721
|
+
nestedRoute: params.nestedRoute,
|
|
722
|
+
worktreeSetup,
|
|
723
|
+
maxSubagentDepth: params.maxSubagentDepth,
|
|
724
|
+
});
|
|
725
|
+
globalTaskIndex += step.parallel.length;
|
|
726
|
+
|
|
727
|
+
for (const result of parallelResults) {
|
|
728
|
+
results.push(result);
|
|
729
|
+
if (result.progress) allProgress.push(result.progress);
|
|
730
|
+
if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
|
|
731
|
+
}
|
|
732
|
+
const interruptedIndexInStep = parallelResults.findIndex((result) => result.interrupted);
|
|
733
|
+
const interrupted = interruptedIndexInStep >= 0 ? parallelResults[interruptedIndexInStep] : undefined;
|
|
734
|
+
if (interrupted) {
|
|
735
|
+
return {
|
|
736
|
+
content: [{ type: "text", text: `Chain paused after interrupt at step ${stepIndex + 1} (${interrupted.agent}). Waiting for explicit next action.` }],
|
|
737
|
+
details: buildChainExecutionDetails(makeDetailsInput({
|
|
738
|
+
currentStepIndex: stepIndex,
|
|
739
|
+
currentFlatIndex: globalTaskIndex - step.parallel.length + interruptedIndexInStep,
|
|
740
|
+
})),
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const diffs = captureParallelWorktreeDiffs(worktreeSetup, path.join(chainDir, "worktree-diffs", `step-${stepIndex}`), agentNames);
|
|
745
|
+
for (let taskIndex = 0; taskIndex < parallelResults.length; taskIndex++) {
|
|
746
|
+
const result = parallelResults[taskIndex]!;
|
|
747
|
+
const task = step.parallel[taskIndex];
|
|
748
|
+
logTaskOutput(stepIndex, taskIndex, result, task?.as, task?.label);
|
|
749
|
+
if (task?.as && result.exitCode === 0) outputs[task.as] = outputEntryFromResult(result, stepIndex, {
|
|
750
|
+
...patchInfoForDiff(diffs[taskIndex]),
|
|
751
|
+
...(worktreeSetup ? { taskIndex, baseCommit: worktreeSetup.baseCommit, repoCwd: worktreeSetup.cwd } : {}),
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
if (step.stopOn) {
|
|
755
|
+
const matches = evaluateStopCondition(step.stopOn, stepIndex, parallelResults.map((result, i) => ({
|
|
756
|
+
agent: result.agent, as: step.parallel[i]?.as, label: step.parallel[i]?.label, exitCode: result.exitCode, structuredOutput: result.structuredOutput,
|
|
757
|
+
})));
|
|
758
|
+
if (matches.length > 0) stopRecord = buildStopRecord(step.stopOn, stepIndex, matches);
|
|
759
|
+
}
|
|
760
|
+
const failures = parallelResults
|
|
761
|
+
.map((result, originalIndex) => ({ ...result, originalIndex }))
|
|
762
|
+
.filter((result) => result.exitCode !== 0 && result.exitCode !== -1);
|
|
763
|
+
if (failures.length > 0) {
|
|
764
|
+
const failureSummary = failures
|
|
765
|
+
.map((failure) => `- Task ${failure.originalIndex + 1} (${failure.agent}): ${failure.error || "failed"}`)
|
|
766
|
+
.join("\n");
|
|
767
|
+
const errorMsg = `Parallel step ${stepIndex + 1} failed:\n${failureSummary}`;
|
|
768
|
+
const summary = buildChainSummary(chainSteps, results, chainDir, "failed", {
|
|
769
|
+
index: stepIndex,
|
|
770
|
+
error: errorMsg,
|
|
771
|
+
}, failureSummaryOptions());
|
|
772
|
+
return {
|
|
773
|
+
content: [{ type: "text", text: summary }],
|
|
774
|
+
isError: true,
|
|
775
|
+
details: buildChainExecutionDetails(makeDetailsInput({
|
|
776
|
+
currentStepIndex: stepIndex,
|
|
777
|
+
currentFlatIndex: globalTaskIndex - step.parallel.length + failures[0]!.originalIndex,
|
|
778
|
+
})),
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const taskResults: ParallelTaskResult[] = parallelResults.map((result, i) => {
|
|
783
|
+
const outputTarget = parallelBehaviors[i]?.output;
|
|
784
|
+
const outputTargetPath = typeof outputTarget === "string"
|
|
785
|
+
? (path.isAbsolute(outputTarget) ? outputTarget : path.join(chainDir, outputTarget))
|
|
786
|
+
: undefined;
|
|
787
|
+
return {
|
|
788
|
+
agent: result.agent,
|
|
789
|
+
taskIndex: i,
|
|
790
|
+
output: getSingleResultOutput(result),
|
|
791
|
+
exitCode: result.exitCode,
|
|
792
|
+
error: result.error,
|
|
793
|
+
outputTargetPath,
|
|
794
|
+
outputTargetExists: outputTargetPath ? fs.existsSync(outputTargetPath) : undefined,
|
|
795
|
+
};
|
|
657
796
|
});
|
|
797
|
+
prev = aggregateParallelOutputs(taskResults);
|
|
798
|
+
prev = appendParallelWorktreeSummary(prev, diffs);
|
|
799
|
+
if (stopRecord) {
|
|
800
|
+
return {
|
|
801
|
+
content: [{ type: "text", text: buildChainSummary(chainSteps, results, chainDir, "stopped", undefined, summaryOptions()) }],
|
|
802
|
+
details: buildChainExecutionDetails(makeDetailsInput({ currentStepIndex: stepIndex })),
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
} finally {
|
|
807
|
+
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
808
|
+
}
|
|
809
|
+
} else if (isDynamicParallelStep(step)) {
|
|
810
|
+
let materialized: ReturnType<typeof materializeDynamicParallelStep>;
|
|
811
|
+
try {
|
|
812
|
+
materialized = materializeDynamicParallelStep(step, outputs, stepIndex, { maxItems: params.dynamicFanoutMaxItems });
|
|
658
813
|
} catch (error) {
|
|
659
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
814
|
+
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
815
|
+
dynamicGroupStatuses[stepIndex] = { status: "failed", error: message };
|
|
660
816
|
return buildChainExecutionErrorResult(message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }), failureSummaryOptions());
|
|
661
817
|
}
|
|
662
|
-
}
|
|
663
818
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
819
|
+
dynamicChildren[stepIndex] = materialized.items.map((item, itemIndex) => ({
|
|
820
|
+
agent: step.parallel.agent,
|
|
821
|
+
label: materialized.parallel[itemIndex]?.label,
|
|
822
|
+
flatIndex: globalTaskIndex + itemIndex,
|
|
823
|
+
itemKey: item.key,
|
|
824
|
+
structured: Boolean(step.parallel.outputSchema),
|
|
825
|
+
}));
|
|
826
|
+
|
|
827
|
+
if (materialized.parallel.length === 0) {
|
|
828
|
+
const collection: DynamicCollectedResult[] = [];
|
|
829
|
+
outputs[step.collect.as] = {
|
|
830
|
+
text: JSON.stringify(collection),
|
|
831
|
+
structured: collection,
|
|
832
|
+
agent: step.parallel.agent,
|
|
833
|
+
stepIndex,
|
|
834
|
+
};
|
|
835
|
+
try {
|
|
836
|
+
validateDynamicCollection(step.collect.outputSchema, collection);
|
|
837
|
+
} catch (error) {
|
|
838
|
+
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
839
|
+
dynamicGroupStatuses[stepIndex] = { status: "failed", error: message };
|
|
840
|
+
return buildChainExecutionErrorResult(message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }), failureSummaryOptions());
|
|
841
|
+
}
|
|
842
|
+
dynamicGroupStatuses[stepIndex] = { status: "completed" };
|
|
843
|
+
if (step.acceptance !== undefined) {
|
|
844
|
+
const effectiveGroupAcceptance = resolveEffectiveAcceptance({
|
|
845
|
+
explicit: step.acceptance,
|
|
846
|
+
agentName: step.parallel.agent,
|
|
847
|
+
task: step.parallel.task ?? originalTask,
|
|
848
|
+
mode: "chain",
|
|
849
|
+
dynamicGroup: true,
|
|
850
|
+
});
|
|
851
|
+
const groupAcceptance = await evaluateAcceptance({
|
|
852
|
+
acceptance: effectiveGroupAcceptance,
|
|
853
|
+
output: "",
|
|
854
|
+
report: aggregateAcceptanceReport({
|
|
855
|
+
results: [],
|
|
856
|
+
notes: "Dynamic fanout produced 0 results.",
|
|
857
|
+
}),
|
|
858
|
+
cwd: cwd ?? ctx.cwd,
|
|
859
|
+
});
|
|
860
|
+
dynamicGroupStatuses[stepIndex].acceptance = groupAcceptance;
|
|
861
|
+
const groupAcceptanceFailure = acceptanceFailureMessage(groupAcceptance);
|
|
862
|
+
if (groupAcceptanceFailure) {
|
|
863
|
+
dynamicGroupStatuses[stepIndex] = { status: "failed", error: groupAcceptanceFailure, acceptance: groupAcceptance };
|
|
864
|
+
return buildChainExecutionErrorResult(groupAcceptanceFailure, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }), failureSummaryOptions());
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
prev = "Dynamic fanout produced 0 results.";
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
const dynamicParallelStep: ParallelStep = {
|
|
872
|
+
parallel: materialized.parallel,
|
|
873
|
+
concurrency: step.concurrency,
|
|
874
|
+
failFast: step.failFast,
|
|
875
|
+
};
|
|
876
|
+
const parallelTemplates = materialized.parallel.map((task) => task.task ?? "{previous}");
|
|
877
|
+
const parallelBehaviors = resolveParallelBehaviors(dynamicParallelStep.parallel, agents, stepIndex, chainSkills)
|
|
878
|
+
.map((behavior, taskIndex) => suppressProgressForReadOnlyTask(behavior, parallelTemplates[taskIndex] ?? dynamicParallelStep.parallel[taskIndex]?.task, originalTask));
|
|
879
|
+
|
|
880
|
+
for (let taskIndex = 0; taskIndex < dynamicParallelStep.parallel.length; taskIndex++) {
|
|
669
881
|
const behavior = parallelBehaviors[taskIndex]!;
|
|
670
882
|
const outputPath = typeof behavior.output === "string"
|
|
671
883
|
? (path.isAbsolute(behavior.output) ? behavior.output : path.join(chainDir, behavior.output))
|
|
672
884
|
: undefined;
|
|
673
|
-
const validationError = validateFileOnlyOutputMode(behavior.outputMode, outputPath, `
|
|
674
|
-
if (validationError)
|
|
885
|
+
const validationError = validateFileOnlyOutputMode(behavior.outputMode, outputPath, `Dynamic chain step ${stepIndex + 1} item ${taskIndex + 1} (${dynamicParallelStep.parallel[taskIndex]!.agent})`);
|
|
886
|
+
if (validationError) {
|
|
887
|
+
dynamicGroupStatuses[stepIndex] = { status: "failed", error: validationError };
|
|
888
|
+
return buildChainExecutionErrorResult(validationError, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex + taskIndex }), failureSummaryOptions());
|
|
889
|
+
}
|
|
675
890
|
}
|
|
676
|
-
progressCreated = ensureParallelProgressFile(chainDir, progressCreated, parallelBehaviors);
|
|
677
|
-
createParallelDirs(chainDir, stepIndex, step.parallel.length, agentNames);
|
|
678
891
|
|
|
892
|
+
progressCreated = ensureParallelProgressFile(chainDir, progressCreated, parallelBehaviors);
|
|
893
|
+
createParallelDirs(chainDir, stepIndex, dynamicParallelStep.parallel.length, dynamicParallelStep.parallel.map((task) => task.agent));
|
|
679
894
|
const parallelResults = await runParallelChainTasks({
|
|
680
895
|
runtime: params.runtime,
|
|
681
|
-
step,
|
|
896
|
+
step: dynamicParallelStep,
|
|
682
897
|
parallelTemplates,
|
|
683
898
|
parallelBehaviors,
|
|
684
899
|
agents,
|
|
@@ -702,6 +917,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
702
917
|
results,
|
|
703
918
|
allProgress,
|
|
704
919
|
outputs,
|
|
920
|
+
trees,
|
|
705
921
|
chainAgents,
|
|
706
922
|
chainSteps,
|
|
707
923
|
totalSteps,
|
|
@@ -711,16 +927,16 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
711
927
|
onControlEvent,
|
|
712
928
|
foregroundControl,
|
|
713
929
|
nestedRoute: params.nestedRoute,
|
|
714
|
-
worktreeSetup,
|
|
715
930
|
maxSubagentDepth: params.maxSubagentDepth,
|
|
716
931
|
});
|
|
717
|
-
globalTaskIndex +=
|
|
932
|
+
globalTaskIndex += dynamicParallelStep.parallel.length;
|
|
718
933
|
|
|
719
934
|
for (const result of parallelResults) {
|
|
720
935
|
results.push(result);
|
|
721
936
|
if (result.progress) allProgress.push(result.progress);
|
|
722
937
|
if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
|
|
723
938
|
}
|
|
939
|
+
const collected = collectDynamicResults(step, materialized.items, parallelResults);
|
|
724
940
|
const interruptedIndexInStep = parallelResults.findIndex((result) => result.interrupted);
|
|
725
941
|
const interrupted = interruptedIndexInStep >= 0 ? parallelResults[interruptedIndexInStep] : undefined;
|
|
726
942
|
if (interrupted) {
|
|
@@ -728,32 +944,36 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
728
944
|
content: [{ type: "text", text: `Chain paused after interrupt at step ${stepIndex + 1} (${interrupted.agent}). Waiting for explicit next action.` }],
|
|
729
945
|
details: buildChainExecutionDetails(makeDetailsInput({
|
|
730
946
|
currentStepIndex: stepIndex,
|
|
731
|
-
currentFlatIndex: globalTaskIndex -
|
|
947
|
+
currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length + interruptedIndexInStep,
|
|
732
948
|
})),
|
|
733
949
|
};
|
|
734
950
|
}
|
|
735
|
-
|
|
736
|
-
const diffs = captureParallelWorktreeDiffs(worktreeSetup, path.join(chainDir, "worktree-diffs", `step-${stepIndex}`), agentNames);
|
|
737
951
|
for (let taskIndex = 0; taskIndex < parallelResults.length; taskIndex++) {
|
|
738
952
|
const result = parallelResults[taskIndex]!;
|
|
739
|
-
const task =
|
|
740
|
-
logTaskOutput(stepIndex, taskIndex, result,
|
|
741
|
-
if (task?.as && result.exitCode === 0) outputs[task.as] = outputEntryFromResult(result, stepIndex, patchInfoForDiff(diffs[taskIndex]));
|
|
953
|
+
const task = materialized.parallel[taskIndex];
|
|
954
|
+
logTaskOutput(stepIndex, taskIndex, result, undefined, task?.label);
|
|
742
955
|
}
|
|
743
956
|
if (step.stopOn) {
|
|
744
957
|
const matches = evaluateStopCondition(step.stopOn, stepIndex, parallelResults.map((result, i) => ({
|
|
745
|
-
agent: result.agent,
|
|
958
|
+
agent: result.agent, label: materialized.parallel[i]?.label, exitCode: result.exitCode, structuredOutput: result.structuredOutput,
|
|
746
959
|
})));
|
|
747
960
|
if (matches.length > 0) stopRecord = buildStopRecord(step.stopOn, stepIndex, matches);
|
|
748
961
|
}
|
|
962
|
+
outputs[step.collect.as] = {
|
|
963
|
+
text: JSON.stringify(collected),
|
|
964
|
+
structured: collected,
|
|
965
|
+
agent: step.parallel.agent,
|
|
966
|
+
stepIndex,
|
|
967
|
+
};
|
|
749
968
|
const failures = parallelResults
|
|
750
969
|
.map((result, originalIndex) => ({ ...result, originalIndex }))
|
|
751
970
|
.filter((result) => result.exitCode !== 0 && result.exitCode !== -1);
|
|
752
971
|
if (failures.length > 0) {
|
|
753
972
|
const failureSummary = failures
|
|
754
|
-
.map((failure) => `-
|
|
973
|
+
.map((failure) => `- Item ${failure.originalIndex + 1} (${failure.agent}, key ${materialized.items[failure.originalIndex]?.key ?? failure.originalIndex}): ${failure.error || "failed"}`)
|
|
755
974
|
.join("\n");
|
|
756
|
-
const errorMsg = `
|
|
975
|
+
const errorMsg = `Dynamic step ${stepIndex + 1} failed:\n${failureSummary}`;
|
|
976
|
+
dynamicGroupStatuses[stepIndex] = { status: "failed", error: errorMsg };
|
|
757
977
|
const summary = buildChainSummary(chainSteps, results, chainDir, "failed", {
|
|
758
978
|
index: stepIndex,
|
|
759
979
|
error: errorMsg,
|
|
@@ -763,492 +983,289 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
763
983
|
isError: true,
|
|
764
984
|
details: buildChainExecutionDetails(makeDetailsInput({
|
|
765
985
|
currentStepIndex: stepIndex,
|
|
766
|
-
currentFlatIndex: globalTaskIndex -
|
|
986
|
+
currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length + failures[0]!.originalIndex,
|
|
767
987
|
})),
|
|
768
988
|
};
|
|
769
989
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
const
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
990
|
+
try {
|
|
991
|
+
validateDynamicCollection(step.collect.outputSchema, collected);
|
|
992
|
+
} catch (error) {
|
|
993
|
+
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
994
|
+
dynamicGroupStatuses[stepIndex] = { status: "failed", error: message };
|
|
995
|
+
return buildChainExecutionErrorResult(
|
|
996
|
+
stopRecord ? buildChainSummary(chainSteps, results, chainDir, "failed", { index: stepIndex, error: message }, failureSummaryOptions()) : message,
|
|
997
|
+
makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length }),
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
dynamicGroupStatuses[stepIndex] = { status: "completed" };
|
|
1001
|
+
const effectiveGroupAcceptance = resolveEffectiveAcceptance({
|
|
1002
|
+
explicit: step.acceptance,
|
|
1003
|
+
agentName: step.parallel.agent,
|
|
1004
|
+
task: step.parallel.task ?? originalTask,
|
|
1005
|
+
mode: "chain",
|
|
1006
|
+
dynamicGroup: true,
|
|
1007
|
+
});
|
|
1008
|
+
const groupAcceptance = await evaluateAcceptance({
|
|
1009
|
+
acceptance: effectiveGroupAcceptance,
|
|
1010
|
+
output: "",
|
|
1011
|
+
report: aggregateAcceptanceReport({
|
|
1012
|
+
results: parallelResults,
|
|
1013
|
+
notes: `Dynamic fanout collected ${collected.length} result(s) into ${step.collect.as}.`,
|
|
1014
|
+
}),
|
|
1015
|
+
cwd: cwd ?? ctx.cwd,
|
|
785
1016
|
});
|
|
786
|
-
|
|
787
|
-
|
|
1017
|
+
dynamicGroupStatuses[stepIndex].acceptance = groupAcceptance;
|
|
1018
|
+
const groupAcceptanceFailure = acceptanceFailureMessage(groupAcceptance);
|
|
1019
|
+
if (groupAcceptanceFailure) {
|
|
1020
|
+
dynamicGroupStatuses[stepIndex] = { status: "failed", error: groupAcceptanceFailure, acceptance: groupAcceptance };
|
|
1021
|
+
return buildChainExecutionErrorResult(
|
|
1022
|
+
stopRecord ? buildChainSummary(chainSteps, results, chainDir, "failed", { index: stepIndex, error: groupAcceptanceFailure }, failureSummaryOptions()) : groupAcceptanceFailure,
|
|
1023
|
+
makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length }),
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
const taskResults: ParallelTaskResult[] = parallelResults.map((result, i) => ({
|
|
1027
|
+
agent: result.agent,
|
|
1028
|
+
taskIndex: i,
|
|
1029
|
+
output: getSingleResultOutput(result),
|
|
1030
|
+
exitCode: result.exitCode,
|
|
1031
|
+
error: result.error,
|
|
1032
|
+
}));
|
|
1033
|
+
prev = aggregateParallelOutputs(taskResults, (i, agent) => `=== Dynamic Item ${i + 1} (${agent}, key ${materialized.items[i]?.key ?? i}) ===`);
|
|
788
1034
|
if (stopRecord) {
|
|
789
1035
|
return {
|
|
790
1036
|
content: [{ type: "text", text: buildChainSummary(chainSteps, results, chainDir, "stopped", undefined, summaryOptions()) }],
|
|
791
1037
|
details: buildChainExecutionDetails(makeDetailsInput({ currentStepIndex: stepIndex })),
|
|
792
1038
|
};
|
|
793
1039
|
}
|
|
1040
|
+
} else {
|
|
1041
|
+
const seqStep = step as SequentialStep;
|
|
1042
|
+
const stepTemplate = stepTemplates as string;
|
|
794
1043
|
|
|
795
|
-
|
|
796
|
-
if (
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
dynamicGroupStatuses[stepIndex] = { status: "failed", error: message };
|
|
805
|
-
return buildChainExecutionErrorResult(message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }), failureSummaryOptions());
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
dynamicChildren[stepIndex] = materialized.items.map((item, itemIndex) => ({
|
|
809
|
-
agent: step.parallel.agent,
|
|
810
|
-
label: materialized.parallel[itemIndex]?.label,
|
|
811
|
-
flatIndex: globalTaskIndex + itemIndex,
|
|
812
|
-
itemKey: item.key,
|
|
813
|
-
structured: Boolean(step.parallel.outputSchema),
|
|
814
|
-
}));
|
|
1044
|
+
const agentConfig = agents.find((a) => a.name === seqStep.agent);
|
|
1045
|
+
if (!agentConfig) {
|
|
1046
|
+
removeChainDir(chainDir);
|
|
1047
|
+
return {
|
|
1048
|
+
content: [{ type: "text", text: `Unknown agent: ${seqStep.agent}` }],
|
|
1049
|
+
isError: true,
|
|
1050
|
+
details: buildChainExecutionDetails(makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex })),
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
815
1053
|
|
|
816
|
-
|
|
817
|
-
const
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1054
|
+
const tuiOverride = tuiBehaviorOverrides?.[stepIndex];
|
|
1055
|
+
const stepOverride: StepOverrides = {
|
|
1056
|
+
output: tuiOverride?.output !== undefined ? tuiOverride.output : seqStep.output,
|
|
1057
|
+
outputMode: seqStep.outputMode,
|
|
1058
|
+
reads: tuiOverride?.reads !== undefined ? tuiOverride.reads : seqStep.reads,
|
|
1059
|
+
progress: tuiOverride?.progress !== undefined ? tuiOverride.progress : seqStep.progress,
|
|
1060
|
+
skills:
|
|
1061
|
+
tuiOverride?.skills !== undefined
|
|
1062
|
+
? tuiOverride.skills
|
|
1063
|
+
: normalizeSkillInput(seqStep.skill),
|
|
823
1064
|
};
|
|
1065
|
+
const behavior = suppressProgressForReadOnlyTask(resolveStepBehavior(agentConfig, stepOverride, chainSkills), stepTemplate, originalTask);
|
|
1066
|
+
|
|
1067
|
+
const isFirstProgress = behavior.progress && !progressCreated;
|
|
1068
|
+
if (isFirstProgress) {
|
|
1069
|
+
progressCreated = true;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
const templateHasPrevious = stepTemplate.includes("{previous}");
|
|
1073
|
+
const { prefix, suffix } = buildChainInstructions(
|
|
1074
|
+
behavior,
|
|
1075
|
+
chainDir,
|
|
1076
|
+
isFirstProgress,
|
|
1077
|
+
templateHasPrevious ? undefined : prev,
|
|
1078
|
+
);
|
|
1079
|
+
|
|
1080
|
+
let stepTask: string;
|
|
1081
|
+
let stepCwd: string;
|
|
824
1082
|
try {
|
|
825
|
-
|
|
1083
|
+
stepCwd = resolveChildCwd(cwd ?? ctx.cwd, seqStep.cwd === undefined ? undefined : resolveOutputReferences(seqStep.cwd, outputs, trees));
|
|
1084
|
+
stepTask = resolveOutputReferences(stepTemplate, outputs, trees);
|
|
826
1085
|
} catch (error) {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
return buildChainExecutionErrorResult(message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }), failureSummaryOptions());
|
|
830
|
-
}
|
|
831
|
-
dynamicGroupStatuses[stepIndex] = { status: "completed" };
|
|
832
|
-
if (step.acceptance !== undefined) {
|
|
833
|
-
const effectiveGroupAcceptance = resolveEffectiveAcceptance({
|
|
834
|
-
explicit: step.acceptance,
|
|
835
|
-
agentName: step.parallel.agent,
|
|
836
|
-
task: step.parallel.task ?? originalTask,
|
|
837
|
-
mode: "chain",
|
|
838
|
-
dynamicGroup: true,
|
|
839
|
-
});
|
|
840
|
-
const groupAcceptance = await evaluateAcceptance({
|
|
841
|
-
acceptance: effectiveGroupAcceptance,
|
|
842
|
-
output: "",
|
|
843
|
-
report: aggregateAcceptanceReport({
|
|
844
|
-
results: [],
|
|
845
|
-
notes: "Dynamic fanout produced 0 results.",
|
|
846
|
-
}),
|
|
847
|
-
cwd: cwd ?? ctx.cwd,
|
|
848
|
-
});
|
|
849
|
-
dynamicGroupStatuses[stepIndex].acceptance = groupAcceptance;
|
|
850
|
-
const groupAcceptanceFailure = acceptanceFailureMessage(groupAcceptance);
|
|
851
|
-
if (groupAcceptanceFailure) {
|
|
852
|
-
dynamicGroupStatuses[stepIndex] = { status: "failed", error: groupAcceptanceFailure, acceptance: groupAcceptance };
|
|
853
|
-
return buildChainExecutionErrorResult(groupAcceptanceFailure, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }), failureSummaryOptions());
|
|
854
|
-
}
|
|
1086
|
+
if (!(error instanceof ChainOutputValidationError)) throw error;
|
|
1087
|
+
return buildChainExecutionErrorResult(error.message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }), failureSummaryOptions());
|
|
855
1088
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1089
|
+
stepTask = stepTask.replace(/\{task\}/g, originalTask);
|
|
1090
|
+
stepTask = stepTask.replace(/\{previous\}/g, prev);
|
|
1091
|
+
stepTask = stepTask.replace(/\{chain_dir\}/g, chainDir);
|
|
1092
|
+
const cleanTask = stepTask;
|
|
1093
|
+
stepTask = prefix + stepTask + suffix;
|
|
859
1094
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
};
|
|
865
|
-
const parallelTemplates = materialized.parallel.map((task) => task.task ?? "{previous}");
|
|
866
|
-
const parallelBehaviors = resolveParallelBehaviors(dynamicParallelStep.parallel, agents, stepIndex, chainSkills)
|
|
867
|
-
.map((behavior, taskIndex) => suppressProgressForReadOnlyTask(behavior, parallelTemplates[taskIndex] ?? dynamicParallelStep.parallel[taskIndex]?.task, originalTask));
|
|
1095
|
+
const effectiveModel =
|
|
1096
|
+
tuiOverride?.model
|
|
1097
|
+
?? (seqStep.model ? resolveModelCandidate(seqStep.model, availableModels, ctx.model?.provider) : null)
|
|
1098
|
+
?? resolveModelCandidate(agentConfig.model, availableModels, ctx.model?.provider);
|
|
868
1099
|
|
|
869
|
-
for (let taskIndex = 0; taskIndex < dynamicParallelStep.parallel.length; taskIndex++) {
|
|
870
|
-
const behavior = parallelBehaviors[taskIndex]!;
|
|
871
1100
|
const outputPath = typeof behavior.output === "string"
|
|
872
1101
|
? (path.isAbsolute(behavior.output) ? behavior.output : path.join(chainDir, behavior.output))
|
|
873
1102
|
: undefined;
|
|
874
|
-
const validationError = validateFileOnlyOutputMode(behavior.outputMode, outputPath, `
|
|
1103
|
+
const validationError = validateFileOnlyOutputMode(behavior.outputMode, outputPath, `Chain step ${stepIndex + 1} (${seqStep.agent})`);
|
|
875
1104
|
if (validationError) {
|
|
876
|
-
|
|
877
|
-
return buildChainExecutionErrorResult(validationError, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex + taskIndex }), failureSummaryOptions());
|
|
1105
|
+
return buildChainExecutionErrorResult(validationError, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }), failureSummaryOptions());
|
|
878
1106
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
runtime: params.runtime,
|
|
885
|
-
step: dynamicParallelStep,
|
|
886
|
-
parallelTemplates,
|
|
887
|
-
parallelBehaviors,
|
|
888
|
-
agents,
|
|
889
|
-
stepIndex,
|
|
890
|
-
availableModels,
|
|
891
|
-
chainDir,
|
|
892
|
-
prev,
|
|
893
|
-
originalTask,
|
|
894
|
-
ctx,
|
|
895
|
-
cwd,
|
|
896
|
-
runId,
|
|
897
|
-
globalTaskIndex,
|
|
898
|
-
forwardedFlags: params.forwardedFlags,
|
|
899
|
-
sessionDirForIndex,
|
|
900
|
-
sessionFileForIndex,
|
|
901
|
-
shareEnabled,
|
|
902
|
-
artifactConfig,
|
|
903
|
-
artifactsDir,
|
|
904
|
-
signal,
|
|
905
|
-
onUpdate,
|
|
906
|
-
results,
|
|
907
|
-
allProgress,
|
|
908
|
-
outputs,
|
|
909
|
-
chainAgents,
|
|
910
|
-
chainSteps,
|
|
911
|
-
totalSteps,
|
|
912
|
-
dynamicChildren,
|
|
913
|
-
dynamicGroupStatuses,
|
|
914
|
-
controlConfig,
|
|
915
|
-
onControlEvent,
|
|
916
|
-
foregroundControl,
|
|
917
|
-
nestedRoute: params.nestedRoute,
|
|
918
|
-
maxSubagentDepth: params.maxSubagentDepth,
|
|
919
|
-
});
|
|
920
|
-
globalTaskIndex += dynamicParallelStep.parallel.length;
|
|
921
|
-
|
|
922
|
-
for (const result of parallelResults) {
|
|
923
|
-
results.push(result);
|
|
924
|
-
if (result.progress) allProgress.push(result.progress);
|
|
925
|
-
if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
|
|
926
|
-
}
|
|
927
|
-
const collected = collectDynamicResults(step, materialized.items, parallelResults);
|
|
928
|
-
const interruptedIndexInStep = parallelResults.findIndex((result) => result.interrupted);
|
|
929
|
-
const interrupted = interruptedIndexInStep >= 0 ? parallelResults[interruptedIndexInStep] : undefined;
|
|
930
|
-
if (interrupted) {
|
|
931
|
-
return {
|
|
932
|
-
content: [{ type: "text", text: `Chain paused after interrupt at step ${stepIndex + 1} (${interrupted.agent}). Waiting for explicit next action.` }],
|
|
933
|
-
details: buildChainExecutionDetails(makeDetailsInput({
|
|
934
|
-
currentStepIndex: stepIndex,
|
|
935
|
-
currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length + interruptedIndexInStep,
|
|
936
|
-
})),
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
for (let taskIndex = 0; taskIndex < parallelResults.length; taskIndex++) {
|
|
940
|
-
const result = parallelResults[taskIndex]!;
|
|
941
|
-
const task = materialized.parallel[taskIndex];
|
|
942
|
-
logTaskOutput(stepIndex, taskIndex, result, undefined, task?.label);
|
|
943
|
-
}
|
|
944
|
-
if (step.stopOn) {
|
|
945
|
-
const matches = evaluateStopCondition(step.stopOn, stepIndex, parallelResults.map((result, i) => ({
|
|
946
|
-
agent: result.agent, label: materialized.parallel[i]?.label, exitCode: result.exitCode, structuredOutput: result.structuredOutput,
|
|
947
|
-
})));
|
|
948
|
-
if (matches.length > 0) stopRecord = buildStopRecord(step.stopOn, stepIndex, matches);
|
|
949
|
-
}
|
|
950
|
-
outputs[step.collect.as] = {
|
|
951
|
-
text: JSON.stringify(collected),
|
|
952
|
-
structured: collected,
|
|
953
|
-
agent: step.parallel.agent,
|
|
954
|
-
stepIndex,
|
|
955
|
-
};
|
|
956
|
-
const failures = parallelResults
|
|
957
|
-
.map((result, originalIndex) => ({ ...result, originalIndex }))
|
|
958
|
-
.filter((result) => result.exitCode !== 0 && result.exitCode !== -1);
|
|
959
|
-
if (failures.length > 0) {
|
|
960
|
-
const failureSummary = failures
|
|
961
|
-
.map((failure) => `- Item ${failure.originalIndex + 1} (${failure.agent}, key ${materialized.items[failure.originalIndex]?.key ?? failure.originalIndex}): ${failure.error || "failed"}`)
|
|
962
|
-
.join("\n");
|
|
963
|
-
const errorMsg = `Dynamic step ${stepIndex + 1} failed:\n${failureSummary}`;
|
|
964
|
-
dynamicGroupStatuses[stepIndex] = { status: "failed", error: errorMsg };
|
|
965
|
-
const summary = buildChainSummary(chainSteps, results, chainDir, "failed", {
|
|
966
|
-
index: stepIndex,
|
|
967
|
-
error: errorMsg,
|
|
968
|
-
}, failureSummaryOptions());
|
|
969
|
-
return {
|
|
970
|
-
content: [{ type: "text", text: summary }],
|
|
971
|
-
isError: true,
|
|
972
|
-
details: buildChainExecutionDetails(makeDetailsInput({
|
|
973
|
-
currentStepIndex: stepIndex,
|
|
974
|
-
currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length + failures[0]!.originalIndex,
|
|
975
|
-
})),
|
|
976
|
-
};
|
|
977
|
-
}
|
|
978
|
-
try {
|
|
979
|
-
validateDynamicCollection(step.collect.outputSchema, collected);
|
|
980
|
-
} catch (error) {
|
|
981
|
-
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
982
|
-
dynamicGroupStatuses[stepIndex] = { status: "failed", error: message };
|
|
983
|
-
return buildChainExecutionErrorResult(
|
|
984
|
-
stopRecord ? buildChainSummary(chainSteps, results, chainDir, "failed", { index: stepIndex, error: message }, failureSummaryOptions()) : message,
|
|
985
|
-
makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length }),
|
|
986
|
-
);
|
|
987
|
-
}
|
|
988
|
-
dynamicGroupStatuses[stepIndex] = { status: "completed" };
|
|
989
|
-
const effectiveGroupAcceptance = resolveEffectiveAcceptance({
|
|
990
|
-
explicit: step.acceptance,
|
|
991
|
-
agentName: step.parallel.agent,
|
|
992
|
-
task: step.parallel.task ?? originalTask,
|
|
993
|
-
mode: "chain",
|
|
994
|
-
dynamicGroup: true,
|
|
995
|
-
});
|
|
996
|
-
const groupAcceptance = await evaluateAcceptance({
|
|
997
|
-
acceptance: effectiveGroupAcceptance,
|
|
998
|
-
output: "",
|
|
999
|
-
report: aggregateAcceptanceReport({
|
|
1000
|
-
results: parallelResults,
|
|
1001
|
-
notes: `Dynamic fanout collected ${collected.length} result(s) into ${step.collect.as}.`,
|
|
1002
|
-
}),
|
|
1003
|
-
cwd: cwd ?? ctx.cwd,
|
|
1004
|
-
});
|
|
1005
|
-
dynamicGroupStatuses[stepIndex].acceptance = groupAcceptance;
|
|
1006
|
-
const groupAcceptanceFailure = acceptanceFailureMessage(groupAcceptance);
|
|
1007
|
-
if (groupAcceptanceFailure) {
|
|
1008
|
-
dynamicGroupStatuses[stepIndex] = { status: "failed", error: groupAcceptanceFailure, acceptance: groupAcceptance };
|
|
1009
|
-
return buildChainExecutionErrorResult(
|
|
1010
|
-
stopRecord ? buildChainSummary(chainSteps, results, chainDir, "failed", { index: stepIndex, error: groupAcceptanceFailure }, failureSummaryOptions()) : groupAcceptanceFailure,
|
|
1011
|
-
makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length }),
|
|
1012
|
-
);
|
|
1013
|
-
}
|
|
1014
|
-
const taskResults: ParallelTaskResult[] = parallelResults.map((result, i) => ({
|
|
1015
|
-
agent: result.agent,
|
|
1016
|
-
taskIndex: i,
|
|
1017
|
-
output: getSingleResultOutput(result),
|
|
1018
|
-
exitCode: result.exitCode,
|
|
1019
|
-
error: result.error,
|
|
1020
|
-
}));
|
|
1021
|
-
prev = aggregateParallelOutputs(taskResults, (i, agent) => `=== Dynamic Item ${i + 1} (${agent}, key ${materialized.items[i]?.key ?? i}) ===`);
|
|
1022
|
-
if (stopRecord) {
|
|
1023
|
-
return {
|
|
1024
|
-
content: [{ type: "text", text: buildChainSummary(chainSteps, results, chainDir, "stopped", undefined, summaryOptions()) }],
|
|
1025
|
-
details: buildChainExecutionDetails(makeDetailsInput({ currentStepIndex: stepIndex })),
|
|
1026
|
-
};
|
|
1027
|
-
}
|
|
1028
|
-
} else {
|
|
1029
|
-
const seqStep = step as SequentialStep;
|
|
1030
|
-
const stepTemplate = stepTemplates as string;
|
|
1031
|
-
|
|
1032
|
-
const agentConfig = agents.find((a) => a.name === seqStep.agent);
|
|
1033
|
-
if (!agentConfig) {
|
|
1034
|
-
removeChainDir(chainDir);
|
|
1035
|
-
return {
|
|
1036
|
-
content: [{ type: "text", text: `Unknown agent: ${seqStep.agent}` }],
|
|
1037
|
-
isError: true,
|
|
1038
|
-
details: buildChainExecutionDetails(makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex })),
|
|
1039
|
-
};
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
const tuiOverride = tuiBehaviorOverrides?.[stepIndex];
|
|
1043
|
-
const stepOverride: StepOverrides = {
|
|
1044
|
-
output: tuiOverride?.output !== undefined ? tuiOverride.output : seqStep.output,
|
|
1045
|
-
outputMode: seqStep.outputMode,
|
|
1046
|
-
reads: tuiOverride?.reads !== undefined ? tuiOverride.reads : seqStep.reads,
|
|
1047
|
-
progress: tuiOverride?.progress !== undefined ? tuiOverride.progress : seqStep.progress,
|
|
1048
|
-
skills:
|
|
1049
|
-
tuiOverride?.skills !== undefined
|
|
1050
|
-
? tuiOverride.skills
|
|
1051
|
-
: normalizeSkillInput(seqStep.skill),
|
|
1052
|
-
};
|
|
1053
|
-
const behavior = suppressProgressForReadOnlyTask(resolveStepBehavior(agentConfig, stepOverride, chainSkills), stepTemplate, originalTask);
|
|
1054
|
-
|
|
1055
|
-
const isFirstProgress = behavior.progress && !progressCreated;
|
|
1056
|
-
if (isFirstProgress) {
|
|
1057
|
-
progressCreated = true;
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
const templateHasPrevious = stepTemplate.includes("{previous}");
|
|
1061
|
-
const { prefix, suffix } = buildChainInstructions(
|
|
1062
|
-
behavior,
|
|
1063
|
-
chainDir,
|
|
1064
|
-
isFirstProgress,
|
|
1065
|
-
templateHasPrevious ? undefined : prev,
|
|
1066
|
-
);
|
|
1067
|
-
|
|
1068
|
-
let stepTask: string;
|
|
1069
|
-
try {
|
|
1070
|
-
stepTask = resolveOutputReferences(stepTemplate, outputs);
|
|
1071
|
-
} catch (error) {
|
|
1072
|
-
if (!(error instanceof ChainOutputValidationError)) throw error;
|
|
1073
|
-
return buildChainExecutionErrorResult(error.message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }), failureSummaryOptions());
|
|
1074
|
-
}
|
|
1075
|
-
stepTask = stepTask.replace(/\{task\}/g, originalTask);
|
|
1076
|
-
stepTask = stepTask.replace(/\{previous\}/g, prev);
|
|
1077
|
-
stepTask = stepTask.replace(/\{chain_dir\}/g, chainDir);
|
|
1078
|
-
const cleanTask = stepTask;
|
|
1079
|
-
stepTask = prefix + stepTask + suffix;
|
|
1080
|
-
|
|
1081
|
-
const effectiveModel =
|
|
1082
|
-
tuiOverride?.model
|
|
1083
|
-
?? (seqStep.model ? resolveModelCandidate(seqStep.model, availableModels, ctx.model?.provider) : null)
|
|
1084
|
-
?? resolveModelCandidate(agentConfig.model, availableModels, ctx.model?.provider);
|
|
1085
|
-
|
|
1086
|
-
const outputPath = typeof behavior.output === "string"
|
|
1087
|
-
? (path.isAbsolute(behavior.output) ? behavior.output : path.join(chainDir, behavior.output))
|
|
1088
|
-
: undefined;
|
|
1089
|
-
const validationError = validateFileOnlyOutputMode(behavior.outputMode, outputPath, `Chain step ${stepIndex + 1} (${seqStep.agent})`);
|
|
1090
|
-
if (validationError) {
|
|
1091
|
-
return buildChainExecutionErrorResult(validationError, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }), failureSummaryOptions());
|
|
1092
|
-
}
|
|
1093
|
-
const maxSubagentDepth = resolveChildMaxSubagentDepth(params.maxSubagentDepth, agentConfig.maxSubagentDepth);
|
|
1094
|
-
const interruptController = new AbortController();
|
|
1095
|
-
if (foregroundControl) {
|
|
1096
|
-
foregroundControl.currentAgent = seqStep.agent;
|
|
1097
|
-
foregroundControl.currentIndex = globalTaskIndex;
|
|
1098
|
-
foregroundControl.currentActivityState = undefined;
|
|
1099
|
-
foregroundControl.updatedAt = Date.now();
|
|
1100
|
-
foregroundControl.interrupt = () => {
|
|
1101
|
-
if (interruptController.signal.aborted) return false;
|
|
1102
|
-
interruptController.abort();
|
|
1107
|
+
const maxSubagentDepth = resolveChildMaxSubagentDepth(params.maxSubagentDepth, agentConfig.maxSubagentDepth);
|
|
1108
|
+
const interruptController = new AbortController();
|
|
1109
|
+
if (foregroundControl) {
|
|
1110
|
+
foregroundControl.currentAgent = seqStep.agent;
|
|
1111
|
+
foregroundControl.currentIndex = globalTaskIndex;
|
|
1103
1112
|
foregroundControl.currentActivityState = undefined;
|
|
1104
1113
|
foregroundControl.updatedAt = Date.now();
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1114
|
+
foregroundControl.interrupt = () => {
|
|
1115
|
+
if (interruptController.signal.aborted) return false;
|
|
1116
|
+
interruptController.abort();
|
|
1117
|
+
foregroundControl.currentActivityState = undefined;
|
|
1118
|
+
foregroundControl.updatedAt = Date.now();
|
|
1119
|
+
return true;
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1108
1122
|
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
mode: "chain",
|
|
1162
|
-
results: results.concat(stepResults),
|
|
1163
|
-
progress: allProgress.concat(stepProgress),
|
|
1164
|
-
controlEvents: p.details?.controlEvents,
|
|
1165
|
-
chainAgents,
|
|
1166
|
-
totalSteps,
|
|
1167
|
-
currentStepIndex: stepIndex,
|
|
1168
|
-
outputs,
|
|
1169
|
-
workflowGraph: buildWorkflowGraphSnapshot({
|
|
1170
|
-
runId,
|
|
1123
|
+
const structuredRuntime = seqStep.outputSchema
|
|
1124
|
+
? createStructuredOutputRuntime(seqStep.outputSchema, path.join(chainDir, "structured-output"))
|
|
1125
|
+
: undefined;
|
|
1126
|
+
const r = await runSync(ctx.cwd, agents, seqStep.agent, stepTask, {
|
|
1127
|
+
runtime: params.runtime,
|
|
1128
|
+
cwd: stepCwd,
|
|
1129
|
+
signal,
|
|
1130
|
+
interruptSignal: interruptController.signal,
|
|
1131
|
+
runId,
|
|
1132
|
+
index: globalTaskIndex,
|
|
1133
|
+
sessionDir: sessionDirForIndex(globalTaskIndex),
|
|
1134
|
+
sessionFile: sessionFileForIndex?.(globalTaskIndex),
|
|
1135
|
+
share: shareEnabled,
|
|
1136
|
+
artifactsDir: artifactConfig.enabled ? artifactsDir : undefined,
|
|
1137
|
+
artifactConfig,
|
|
1138
|
+
outputPath,
|
|
1139
|
+
outputMode: behavior.outputMode,
|
|
1140
|
+
forwardedFlags: params.forwardedFlags,
|
|
1141
|
+
maxSubagentDepth,
|
|
1142
|
+
controlConfig,
|
|
1143
|
+
onControlEvent,
|
|
1144
|
+
nestedRoute: params.nestedRoute,
|
|
1145
|
+
modelOverride: effectiveModel,
|
|
1146
|
+
availableModels,
|
|
1147
|
+
preferredModelProvider: ctx.model?.provider,
|
|
1148
|
+
parentModel: parentModelFullId(ctx.model),
|
|
1149
|
+
parentThinking: ctx.thinkingLevel,
|
|
1150
|
+
skills: behavior.skills === false ? [] : behavior.skills,
|
|
1151
|
+
structuredOutput: structuredRuntime,
|
|
1152
|
+
acceptance: seqStep.acceptance,
|
|
1153
|
+
acceptanceContext: { mode: "chain" },
|
|
1154
|
+
onUpdate: onUpdate
|
|
1155
|
+
? (p) => {
|
|
1156
|
+
const stepResults = p.details?.results || [];
|
|
1157
|
+
const stepProgress = p.details?.progress || [];
|
|
1158
|
+
if (foregroundControl && stepProgress.length > 0) {
|
|
1159
|
+
const current = stepProgress[0];
|
|
1160
|
+
foregroundControl.currentAgent = seqStep.agent;
|
|
1161
|
+
foregroundControl.currentIndex = globalTaskIndex;
|
|
1162
|
+
foregroundControl.currentActivityState = current?.activityState;
|
|
1163
|
+
foregroundControl.lastActivityAt = current?.lastActivityAt;
|
|
1164
|
+
foregroundControl.currentTool = current?.currentTool;
|
|
1165
|
+
foregroundControl.currentToolStartedAt = current?.currentToolStartedAt;
|
|
1166
|
+
foregroundControl.currentPath = current?.currentPath;
|
|
1167
|
+
foregroundControl.turnCount = current?.turnCount;
|
|
1168
|
+
foregroundControl.tokens = current?.tokens;
|
|
1169
|
+
foregroundControl.toolCount = current?.toolCount;
|
|
1170
|
+
foregroundControl.updatedAt = Date.now();
|
|
1171
|
+
}
|
|
1172
|
+
onUpdate({
|
|
1173
|
+
...p,
|
|
1174
|
+
details: {
|
|
1171
1175
|
mode: "chain",
|
|
1172
|
-
steps: chainSteps,
|
|
1173
1176
|
results: results.concat(stepResults),
|
|
1177
|
+
progress: allProgress.concat(stepProgress),
|
|
1178
|
+
controlEvents: p.details?.controlEvents,
|
|
1179
|
+
chainAgents,
|
|
1180
|
+
totalSteps,
|
|
1174
1181
|
currentStepIndex: stepIndex,
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1182
|
+
outputs,
|
|
1183
|
+
workflowGraph: buildWorkflowGraphSnapshot({
|
|
1184
|
+
runId,
|
|
1185
|
+
mode: "chain",
|
|
1186
|
+
steps: chainSteps,
|
|
1187
|
+
results: results.concat(stepResults),
|
|
1188
|
+
currentStepIndex: stepIndex,
|
|
1189
|
+
currentFlatIndex: globalTaskIndex,
|
|
1190
|
+
dynamicChildren,
|
|
1191
|
+
dynamicGroupStatuses,
|
|
1192
|
+
}),
|
|
1193
|
+
},
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
: undefined,
|
|
1197
|
+
});
|
|
1198
|
+
if (foregroundControl?.currentIndex === globalTaskIndex) {
|
|
1199
|
+
foregroundControl.interrupt = undefined;
|
|
1200
|
+
foregroundControl.updatedAt = Date.now();
|
|
1201
|
+
}
|
|
1202
|
+
recordRun(seqStep.agent, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
|
|
1189
1203
|
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1204
|
+
globalTaskIndex++;
|
|
1205
|
+
results.push(r);
|
|
1206
|
+
logTaskOutput(stepIndex, 0, r, seqStep.as, seqStep.label);
|
|
1207
|
+
if (r.progress) allProgress.push(r.progress);
|
|
1208
|
+
if (r.artifactPaths) allArtifactPaths.push(r.artifactPaths);
|
|
1195
1209
|
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1210
|
+
if (r.interrupted) {
|
|
1211
|
+
return {
|
|
1212
|
+
content: [{ type: "text", text: `Chain paused after interrupt at step ${stepIndex + 1} (${r.agent}). Waiting for explicit next action.` }],
|
|
1213
|
+
details: buildChainExecutionDetails(makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex - 1 })),
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1202
1216
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1217
|
+
if (r.exitCode !== 0) {
|
|
1218
|
+
const summary = buildChainSummary(chainSteps, results, chainDir, "failed", {
|
|
1219
|
+
index: stepIndex,
|
|
1220
|
+
error: r.error || "Chain failed",
|
|
1221
|
+
}, failureSummaryOptions());
|
|
1222
|
+
return {
|
|
1223
|
+
content: [{ type: "text", text: summary }],
|
|
1224
|
+
details: buildChainExecutionDetails(makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex - 1 })),
|
|
1225
|
+
isError: true,
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1214
1228
|
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1229
|
+
if (behavior.output) {
|
|
1230
|
+
try {
|
|
1231
|
+
const expectedPath = path.isAbsolute(behavior.output)
|
|
1232
|
+
? behavior.output
|
|
1233
|
+
: path.join(chainDir, behavior.output);
|
|
1234
|
+
if (!fs.existsSync(expectedPath)) {
|
|
1235
|
+
const dirFiles = fs.readdirSync(chainDir);
|
|
1236
|
+
const mdFiles = dirFiles.filter((file) => file.endsWith(".md") && file !== "progress.md");
|
|
1237
|
+
const warning = mdFiles.length > 0
|
|
1238
|
+
? `Agent wrote to different file(s): ${mdFiles.join(", ")} instead of ${behavior.output}`
|
|
1239
|
+
: `Agent did not create expected output file: ${behavior.output}`;
|
|
1240
|
+
r.error = r.error ? `${r.error}\n${warning}` : warning;
|
|
1241
|
+
}
|
|
1242
|
+
} catch {
|
|
1243
|
+
// Ignore validation errors; this diagnostic should not mask successful chain output.
|
|
1227
1244
|
}
|
|
1228
|
-
} catch {
|
|
1229
|
-
// Ignore validation errors; this diagnostic should not mask successful chain output.
|
|
1230
1245
|
}
|
|
1231
|
-
}
|
|
1232
1246
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1247
|
+
if (seqStep.as) outputs[seqStep.as] = outputEntryFromResult(r, stepIndex);
|
|
1248
|
+
prev = getSingleResultOutput(r);
|
|
1249
|
+
if (seqStep.stopOn) {
|
|
1250
|
+
const matches = evaluateStopCondition(seqStep.stopOn, stepIndex, [{ agent: r.agent, as: seqStep.as, label: seqStep.label, exitCode: r.exitCode, structuredOutput: r.structuredOutput }], { sequential: true });
|
|
1251
|
+
if (matches.length > 0) stopRecord = buildStopRecord(seqStep.stopOn, stepIndex, matches);
|
|
1252
|
+
}
|
|
1253
|
+
if (stopRecord) {
|
|
1254
|
+
return {
|
|
1255
|
+
content: [{ type: "text", text: buildChainSummary(chainSteps, results, chainDir, "stopped", undefined, summaryOptions()) }],
|
|
1256
|
+
details: buildChainExecutionDetails(makeDetailsInput({ currentStepIndex: stepIndex })),
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1244
1259
|
}
|
|
1245
1260
|
}
|
|
1246
|
-
}
|
|
1247
1261
|
|
|
1248
|
-
|
|
1262
|
+
const summary = buildChainSummary(chainSteps, results, chainDir, "completed", undefined, summaryOptions());
|
|
1249
1263
|
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1264
|
+
return {
|
|
1265
|
+
content: [{ type: "text", text: summary }],
|
|
1266
|
+
details: buildChainExecutionDetails(makeDetailsInput()),
|
|
1267
|
+
};
|
|
1268
|
+
} finally {
|
|
1269
|
+
removeTrees(trees.created);
|
|
1270
|
+
}
|
|
1254
1271
|
}
|