pi-subagents 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +129 -17
- package/package.json +1 -1
- package/prompts/parallel-context-build.md +3 -1
- package/prompts/parallel-handoff-plan.md +3 -1
- package/skills/pi-subagents/SKILL.md +32 -17
- package/src/agents/agent-management.ts +57 -15
- package/src/agents/agent-serializer.ts +3 -2
- package/src/agents/agents.ts +47 -16
- package/src/agents/chain-serializer.ts +120 -0
- package/src/extension/fanout-child.ts +1 -0
- package/src/extension/index.ts +1 -0
- package/src/extension/schemas.ts +138 -5
- package/src/runs/background/async-execution.ts +84 -6
- package/src/runs/background/async-status.ts +11 -1
- package/src/runs/background/run-status.ts +10 -1
- package/src/runs/background/subagent-runner.ts +600 -31
- package/src/runs/foreground/chain-execution.ts +325 -118
- package/src/runs/foreground/execution.ts +222 -10
- package/src/runs/foreground/subagent-executor.ts +67 -0
- package/src/runs/shared/acceptance-contract.ts +291 -0
- package/src/runs/shared/acceptance-evaluation.ts +221 -0
- package/src/runs/shared/acceptance-finalization.ts +161 -0
- package/src/runs/shared/acceptance-reports.ts +127 -0
- package/src/runs/shared/acceptance.ts +22 -0
- package/src/runs/shared/chain-outputs.ts +101 -0
- package/src/runs/shared/completion-guard.ts +26 -3
- package/src/runs/shared/dynamic-fanout.ts +293 -0
- package/src/runs/shared/parallel-utils.ts +31 -1
- package/src/runs/shared/pi-args.ts +11 -0
- package/src/runs/shared/structured-output.ts +77 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +53 -3
- package/src/runs/shared/workflow-graph.ts +206 -0
- package/src/shared/formatters.ts +2 -2
- package/src/shared/settings.ts +53 -4
- package/src/shared/types.ts +250 -0
- package/src/slash/slash-commands.ts +41 -3
- package/src/tui/render.ts +162 -34
|
@@ -20,9 +20,11 @@ import {
|
|
|
20
20
|
createParallelDirs,
|
|
21
21
|
suppressProgressForReadOnlyTask,
|
|
22
22
|
aggregateParallelOutputs,
|
|
23
|
+
isDynamicParallelStep,
|
|
23
24
|
isParallelStep,
|
|
24
25
|
type StepOverrides,
|
|
25
26
|
type ChainStep,
|
|
27
|
+
type ParallelStep,
|
|
26
28
|
type SequentialStep,
|
|
27
29
|
type ParallelTaskResult,
|
|
28
30
|
type ResolvedStepBehavior,
|
|
@@ -59,6 +61,11 @@ import {
|
|
|
59
61
|
} from "../../shared/types.ts";
|
|
60
62
|
import { resolveModelCandidate } from "../shared/model-fallback.ts";
|
|
61
63
|
import { validateFileOnlyOutputMode } from "../shared/single-output.ts";
|
|
64
|
+
import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
|
|
65
|
+
import { ChainOutputValidationError, outputEntryFromResult, resolveOutputReferences, validateChainOutputBindings } from "../shared/chain-outputs.ts";
|
|
66
|
+
import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
|
|
67
|
+
import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection, type DynamicCollectedResult } from "../shared/dynamic-fanout.ts";
|
|
68
|
+
import type { ChainOutputMap } from "../../shared/types.ts";
|
|
62
69
|
|
|
63
70
|
interface ChainExecutionDetailsInput {
|
|
64
71
|
results: SingleResult[];
|
|
@@ -67,12 +74,18 @@ interface ChainExecutionDetailsInput {
|
|
|
67
74
|
allArtifactPaths: ArtifactPaths[];
|
|
68
75
|
artifactsDir: string;
|
|
69
76
|
chainAgents: string[];
|
|
77
|
+
chainSteps: ChainStep[];
|
|
70
78
|
totalSteps: number;
|
|
71
79
|
currentStepIndex?: number;
|
|
80
|
+
runId: string;
|
|
81
|
+
outputs?: ChainOutputMap;
|
|
82
|
+
currentFlatIndex?: number;
|
|
83
|
+
dynamicChildren?: Record<number, Array<{ agent: string; label?: string; flatIndex: number; itemKey: string; outputName?: string; structured?: boolean; error?: string }>>;
|
|
84
|
+
dynamicGroupStatuses?: Record<number, { status: "pending" | "running" | "completed" | "failed" | "paused" | "detached"; error?: string; acceptance?: SingleResult["acceptance"] }>;
|
|
72
85
|
}
|
|
73
86
|
|
|
74
87
|
interface ParallelChainRunInput {
|
|
75
|
-
step:
|
|
88
|
+
step: ParallelStep;
|
|
76
89
|
parallelTemplates: string[];
|
|
77
90
|
parallelBehaviors: ResolvedStepBehavior[];
|
|
78
91
|
agents: AgentConfig[];
|
|
@@ -105,12 +118,20 @@ interface ParallelChainRunInput {
|
|
|
105
118
|
lastActivityAt?: number;
|
|
106
119
|
currentTool?: string;
|
|
107
120
|
currentToolStartedAt?: number;
|
|
121
|
+
currentPath?: string;
|
|
122
|
+
turnCount?: number;
|
|
123
|
+
tokens?: number;
|
|
124
|
+
toolCount?: number;
|
|
108
125
|
interrupt?: () => boolean;
|
|
109
126
|
};
|
|
110
127
|
results: SingleResult[];
|
|
111
128
|
allProgress: AgentProgress[];
|
|
129
|
+
outputs: ChainOutputMap;
|
|
112
130
|
chainAgents: string[];
|
|
131
|
+
chainSteps: ChainStep[];
|
|
113
132
|
totalSteps: number;
|
|
133
|
+
dynamicChildren?: ChainExecutionDetailsInput["dynamicChildren"];
|
|
134
|
+
dynamicGroupStatuses?: ChainExecutionDetailsInput["dynamicGroupStatuses"];
|
|
114
135
|
worktreeSetup?: WorktreeSetup;
|
|
115
136
|
maxSubagentDepth: number;
|
|
116
137
|
nestedRoute?: NestedRouteInfo;
|
|
@@ -125,6 +146,17 @@ function buildChainExecutionDetails(input: ChainExecutionDetailsInput): Details
|
|
|
125
146
|
chainAgents: input.chainAgents,
|
|
126
147
|
totalSteps: input.totalSteps,
|
|
127
148
|
currentStepIndex: input.currentStepIndex,
|
|
149
|
+
outputs: input.outputs,
|
|
150
|
+
workflowGraph: buildWorkflowGraphSnapshot({
|
|
151
|
+
runId: input.runId,
|
|
152
|
+
mode: "chain",
|
|
153
|
+
steps: input.chainSteps,
|
|
154
|
+
results: input.results,
|
|
155
|
+
currentStepIndex: input.currentStepIndex,
|
|
156
|
+
currentFlatIndex: input.currentFlatIndex,
|
|
157
|
+
dynamicChildren: input.dynamicChildren,
|
|
158
|
+
dynamicGroupStatuses: input.dynamicGroupStatuses,
|
|
159
|
+
}),
|
|
128
160
|
});
|
|
129
161
|
}
|
|
130
162
|
|
|
@@ -191,7 +223,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
191
223
|
templateHasPrevious ? undefined : input.prev,
|
|
192
224
|
);
|
|
193
225
|
|
|
194
|
-
let taskStr = taskTemplate;
|
|
226
|
+
let taskStr = resolveOutputReferences(taskTemplate, input.outputs);
|
|
195
227
|
taskStr = taskStr.replace(/\{task\}/g, input.originalTask);
|
|
196
228
|
taskStr = taskStr.replace(/\{previous\}/g, input.prev);
|
|
197
229
|
taskStr = taskStr.replace(/\{chain_dir\}/g, input.chainDir);
|
|
@@ -226,6 +258,9 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
226
258
|
};
|
|
227
259
|
}
|
|
228
260
|
|
|
261
|
+
const structuredRuntime = task.outputSchema
|
|
262
|
+
? createStructuredOutputRuntime(task.outputSchema, path.join(input.chainDir, "structured-output"))
|
|
263
|
+
: undefined;
|
|
229
264
|
const result = await runSync(input.ctx.cwd, input.agents, task.agent, taskStr, {
|
|
230
265
|
cwd: taskCwd,
|
|
231
266
|
signal: input.signal,
|
|
@@ -251,6 +286,9 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
251
286
|
availableModels: input.availableModels,
|
|
252
287
|
preferredModelProvider: input.ctx.model?.provider,
|
|
253
288
|
skills: behavior.skills === false ? [] : behavior.skills,
|
|
289
|
+
structuredOutput: structuredRuntime,
|
|
290
|
+
acceptance: task.acceptance,
|
|
291
|
+
acceptanceContext: { mode: "chain" },
|
|
254
292
|
onUpdate: input.onUpdate
|
|
255
293
|
? (progressUpdate) => {
|
|
256
294
|
const stepResults = progressUpdate.details?.results || [];
|
|
@@ -279,6 +317,17 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
|
|
|
279
317
|
chainAgents: input.chainAgents,
|
|
280
318
|
totalSteps: input.totalSteps,
|
|
281
319
|
currentStepIndex: input.stepIndex,
|
|
320
|
+
outputs: input.outputs,
|
|
321
|
+
workflowGraph: buildWorkflowGraphSnapshot({
|
|
322
|
+
runId: input.runId,
|
|
323
|
+
mode: "chain",
|
|
324
|
+
steps: input.chainSteps,
|
|
325
|
+
results: input.results.concat(stepResults),
|
|
326
|
+
currentStepIndex: input.stepIndex,
|
|
327
|
+
currentFlatIndex: input.globalTaskIndex + taskIndex,
|
|
328
|
+
dynamicChildren: input.dynamicChildren,
|
|
329
|
+
dynamicGroupStatuses: input.dynamicGroupStatuses,
|
|
330
|
+
}),
|
|
282
331
|
},
|
|
283
332
|
});
|
|
284
333
|
}
|
|
@@ -329,10 +378,15 @@ interface ChainExecutionParams {
|
|
|
329
378
|
lastActivityAt?: number;
|
|
330
379
|
currentTool?: string;
|
|
331
380
|
currentToolStartedAt?: number;
|
|
381
|
+
currentPath?: string;
|
|
382
|
+
turnCount?: number;
|
|
383
|
+
tokens?: number;
|
|
384
|
+
toolCount?: number;
|
|
332
385
|
interrupt?: () => boolean;
|
|
333
386
|
};
|
|
334
387
|
chainSkills?: string[];
|
|
335
388
|
chainDir?: string;
|
|
389
|
+
dynamicFanoutMaxItems?: number;
|
|
336
390
|
maxSubagentDepth: number;
|
|
337
391
|
nestedRoute?: NestedRouteInfo;
|
|
338
392
|
worktreeSetupHook?: string;
|
|
@@ -380,22 +434,60 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
380
434
|
} = params;
|
|
381
435
|
const chainSkills = chainSkillsParam ?? [];
|
|
382
436
|
|
|
437
|
+
const results: SingleResult[] = [];
|
|
438
|
+
const outputs: ChainOutputMap = {};
|
|
439
|
+
const dynamicChildren: ChainExecutionDetailsInput["dynamicChildren"] = {};
|
|
440
|
+
const dynamicGroupStatuses: ChainExecutionDetailsInput["dynamicGroupStatuses"] = {};
|
|
383
441
|
const allProgress: AgentProgress[] = [];
|
|
384
442
|
const allArtifactPaths: ArtifactPaths[] = [];
|
|
385
443
|
|
|
386
444
|
const chainAgents: string[] = chainSteps.map((step) =>
|
|
387
445
|
isParallelStep(step)
|
|
388
446
|
? `[${step.parallel.map((t) => t.agent).join("+")}]`
|
|
447
|
+
: isDynamicParallelStep(step)
|
|
448
|
+
? `expand:${step.parallel.agent}`
|
|
389
449
|
: (step as SequentialStep).agent,
|
|
390
450
|
);
|
|
391
451
|
const totalSteps = chainSteps.length;
|
|
392
452
|
|
|
453
|
+
const makeDetailsInput = (overrides: Pick<Partial<ChainExecutionDetailsInput>, "currentStepIndex" | "currentFlatIndex"> = {}): ChainExecutionDetailsInput => ({
|
|
454
|
+
results,
|
|
455
|
+
...(includeProgress !== undefined ? { includeProgress } : {}),
|
|
456
|
+
allProgress,
|
|
457
|
+
allArtifactPaths,
|
|
458
|
+
artifactsDir,
|
|
459
|
+
chainAgents,
|
|
460
|
+
chainSteps,
|
|
461
|
+
totalSteps,
|
|
462
|
+
runId,
|
|
463
|
+
outputs,
|
|
464
|
+
dynamicChildren,
|
|
465
|
+
dynamicGroupStatuses,
|
|
466
|
+
...overrides,
|
|
467
|
+
});
|
|
468
|
+
|
|
393
469
|
const firstStep = chainSteps[0]!;
|
|
394
470
|
const originalTask = params.task
|
|
395
|
-
?? (isParallelStep(firstStep)
|
|
471
|
+
?? (isParallelStep(firstStep)
|
|
472
|
+
? firstStep.parallel[0]!.task!
|
|
473
|
+
: isDynamicParallelStep(firstStep)
|
|
474
|
+
? firstStep.parallel.task!
|
|
475
|
+
: (firstStep as SequentialStep).task!);
|
|
476
|
+
try {
|
|
477
|
+
validateChainOutputBindings(chainSteps, { maxItems: params.dynamicFanoutMaxItems });
|
|
478
|
+
} catch (error) {
|
|
479
|
+
if (error instanceof ChainOutputValidationError) {
|
|
480
|
+
return {
|
|
481
|
+
content: [{ type: "text", text: error.message }],
|
|
482
|
+
isError: true,
|
|
483
|
+
details: buildChainExecutionDetails(makeDetailsInput()),
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
throw error;
|
|
487
|
+
}
|
|
396
488
|
|
|
397
489
|
const chainDir = createChainDir(runId, chainDirBase);
|
|
398
|
-
const hasParallelSteps = chainSteps.some(isParallelStep);
|
|
490
|
+
const hasParallelSteps = chainSteps.some((step) => isParallelStep(step) || isDynamicParallelStep(step));
|
|
399
491
|
let templates: ResolvedTemplates = resolveChainTemplates(chainSteps);
|
|
400
492
|
const shouldClarify = clarify !== false && ctx.hasUI && !hasParallelSteps;
|
|
401
493
|
let tuiBehaviorOverrides: (BehaviorOverride | undefined)[] | undefined;
|
|
@@ -412,7 +504,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
412
504
|
return {
|
|
413
505
|
content: [{ type: "text", text: `Unknown agent: ${step.agent}` }],
|
|
414
506
|
isError: true,
|
|
415
|
-
details: {
|
|
507
|
+
details: buildChainExecutionDetails(makeDetailsInput({ currentStepIndex: seqSteps.indexOf(step) })),
|
|
416
508
|
};
|
|
417
509
|
}
|
|
418
510
|
agentConfigs.push(config);
|
|
@@ -457,7 +549,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
457
549
|
removeChainDir(chainDir);
|
|
458
550
|
return {
|
|
459
551
|
content: [{ type: "text", text: "Chain cancelled" }],
|
|
460
|
-
details:
|
|
552
|
+
details: buildChainExecutionDetails(makeDetailsInput()),
|
|
461
553
|
};
|
|
462
554
|
}
|
|
463
555
|
|
|
@@ -479,7 +571,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
479
571
|
});
|
|
480
572
|
return {
|
|
481
573
|
content: [{ type: "text", text: "Launching in background..." }],
|
|
482
|
-
details:
|
|
574
|
+
details: buildChainExecutionDetails(makeDetailsInput()),
|
|
483
575
|
requestedAsync: { chain: updatedChain, chainSkills },
|
|
484
576
|
};
|
|
485
577
|
}
|
|
@@ -488,7 +580,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
488
580
|
tuiBehaviorOverrides = result.behaviorOverrides;
|
|
489
581
|
}
|
|
490
582
|
|
|
491
|
-
const results: SingleResult[] = [];
|
|
492
583
|
let prev = "";
|
|
493
584
|
let globalTaskIndex = 0;
|
|
494
585
|
let progressCreated = false;
|
|
@@ -506,16 +597,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
506
597
|
if (worktreeTaskCwdConflict) {
|
|
507
598
|
return buildChainExecutionErrorResult(
|
|
508
599
|
`parallel chain step ${stepIndex + 1}: ${formatWorktreeTaskCwdConflict(worktreeTaskCwdConflict, parallelCwd)}`,
|
|
509
|
-
{
|
|
510
|
-
results,
|
|
511
|
-
includeProgress,
|
|
512
|
-
allProgress,
|
|
513
|
-
allArtifactPaths,
|
|
514
|
-
artifactsDir,
|
|
515
|
-
chainAgents,
|
|
516
|
-
totalSteps,
|
|
517
|
-
currentStepIndex: stepIndex,
|
|
518
|
-
},
|
|
600
|
+
makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }),
|
|
519
601
|
);
|
|
520
602
|
}
|
|
521
603
|
try {
|
|
@@ -527,16 +609,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
527
609
|
});
|
|
528
610
|
} catch (error) {
|
|
529
611
|
const message = error instanceof Error ? error.message : String(error);
|
|
530
|
-
return buildChainExecutionErrorResult(message, {
|
|
531
|
-
results,
|
|
532
|
-
includeProgress,
|
|
533
|
-
allProgress,
|
|
534
|
-
allArtifactPaths,
|
|
535
|
-
artifactsDir,
|
|
536
|
-
chainAgents,
|
|
537
|
-
totalSteps,
|
|
538
|
-
currentStepIndex: stepIndex,
|
|
539
|
-
});
|
|
612
|
+
return buildChainExecutionErrorResult(message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
|
|
540
613
|
}
|
|
541
614
|
}
|
|
542
615
|
|
|
@@ -550,16 +623,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
550
623
|
? (path.isAbsolute(behavior.output) ? behavior.output : path.join(chainDir, behavior.output))
|
|
551
624
|
: undefined;
|
|
552
625
|
const validationError = validateFileOnlyOutputMode(behavior.outputMode, outputPath, `Parallel chain step ${stepIndex + 1} task ${taskIndex + 1} (${step.parallel[taskIndex]!.agent})`);
|
|
553
|
-
if (validationError) return buildChainExecutionErrorResult(validationError, {
|
|
554
|
-
results,
|
|
555
|
-
includeProgress,
|
|
556
|
-
allProgress,
|
|
557
|
-
allArtifactPaths,
|
|
558
|
-
artifactsDir,
|
|
559
|
-
chainAgents,
|
|
560
|
-
totalSteps,
|
|
561
|
-
currentStepIndex: stepIndex,
|
|
562
|
-
});
|
|
626
|
+
if (validationError) return buildChainExecutionErrorResult(validationError, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex + taskIndex }));
|
|
563
627
|
}
|
|
564
628
|
progressCreated = ensureParallelProgressFile(chainDir, progressCreated, parallelBehaviors);
|
|
565
629
|
createParallelDirs(chainDir, stepIndex, step.parallel.length, agentNames);
|
|
@@ -588,8 +652,12 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
588
652
|
onUpdate,
|
|
589
653
|
results,
|
|
590
654
|
allProgress,
|
|
655
|
+
outputs,
|
|
591
656
|
chainAgents,
|
|
657
|
+
chainSteps,
|
|
592
658
|
totalSteps,
|
|
659
|
+
dynamicChildren,
|
|
660
|
+
dynamicGroupStatuses,
|
|
593
661
|
controlConfig,
|
|
594
662
|
onControlEvent,
|
|
595
663
|
childIntercomTarget,
|
|
@@ -606,21 +674,15 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
606
674
|
if (result.progress) allProgress.push(result.progress);
|
|
607
675
|
if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
|
|
608
676
|
}
|
|
609
|
-
|
|
610
|
-
const interrupted = parallelResults
|
|
677
|
+
const interruptedIndexInStep = parallelResults.findIndex((result) => result.interrupted);
|
|
678
|
+
const interrupted = interruptedIndexInStep >= 0 ? parallelResults[interruptedIndexInStep] : undefined;
|
|
611
679
|
if (interrupted) {
|
|
612
680
|
return {
|
|
613
681
|
content: [{ type: "text", text: `Chain paused after interrupt at step ${stepIndex + 1} (${interrupted.agent}). Waiting for explicit next action.` }],
|
|
614
|
-
details: buildChainExecutionDetails({
|
|
615
|
-
results,
|
|
616
|
-
includeProgress,
|
|
617
|
-
allProgress,
|
|
618
|
-
allArtifactPaths,
|
|
619
|
-
artifactsDir,
|
|
620
|
-
chainAgents,
|
|
621
|
-
totalSteps,
|
|
682
|
+
details: buildChainExecutionDetails(makeDetailsInput({
|
|
622
683
|
currentStepIndex: stepIndex,
|
|
623
|
-
|
|
684
|
+
currentFlatIndex: globalTaskIndex - step.parallel.length + interruptedIndexInStep,
|
|
685
|
+
})),
|
|
624
686
|
};
|
|
625
687
|
}
|
|
626
688
|
const detachedIndexInStep = parallelResults.findIndex((result) => result.detached);
|
|
@@ -628,16 +690,10 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
628
690
|
if (detached) {
|
|
629
691
|
return {
|
|
630
692
|
content: [{ type: "text", text: `Chain detached for intercom coordination at step ${stepIndex + 1} (${detached.agent}). Reply to the supervisor request first. After the child exits, start a fresh follow-up if needed.` }],
|
|
631
|
-
details: buildChainExecutionDetails({
|
|
632
|
-
results,
|
|
633
|
-
includeProgress,
|
|
634
|
-
allProgress,
|
|
635
|
-
allArtifactPaths,
|
|
636
|
-
artifactsDir,
|
|
637
|
-
chainAgents,
|
|
638
|
-
totalSteps,
|
|
693
|
+
details: buildChainExecutionDetails(makeDetailsInput({
|
|
639
694
|
currentStepIndex: stepIndex,
|
|
640
|
-
|
|
695
|
+
currentFlatIndex: globalTaskIndex - step.parallel.length + detachedIndexInStep,
|
|
696
|
+
})),
|
|
641
697
|
};
|
|
642
698
|
}
|
|
643
699
|
|
|
@@ -656,19 +712,18 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
656
712
|
return {
|
|
657
713
|
content: [{ type: "text", text: summary }],
|
|
658
714
|
isError: true,
|
|
659
|
-
details: buildChainExecutionDetails({
|
|
660
|
-
results,
|
|
661
|
-
includeProgress,
|
|
662
|
-
allProgress,
|
|
663
|
-
allArtifactPaths,
|
|
664
|
-
artifactsDir,
|
|
665
|
-
chainAgents,
|
|
666
|
-
totalSteps,
|
|
715
|
+
details: buildChainExecutionDetails(makeDetailsInput({
|
|
667
716
|
currentStepIndex: stepIndex,
|
|
668
|
-
|
|
717
|
+
currentFlatIndex: globalTaskIndex - step.parallel.length + failures[0]!.originalIndex,
|
|
718
|
+
})),
|
|
669
719
|
};
|
|
670
720
|
}
|
|
671
721
|
|
|
722
|
+
for (let taskIndex = 0; taskIndex < parallelResults.length; taskIndex++) {
|
|
723
|
+
const outputName = step.parallel[taskIndex]?.as;
|
|
724
|
+
if (outputName) outputs[outputName] = outputEntryFromResult(parallelResults[taskIndex]!, stepIndex);
|
|
725
|
+
}
|
|
726
|
+
|
|
672
727
|
const taskResults: ParallelTaskResult[] = parallelResults.map((result, i) => {
|
|
673
728
|
const outputTarget = parallelBehaviors[i]?.output;
|
|
674
729
|
const outputTargetPath = typeof outputTarget === "string"
|
|
@@ -694,6 +749,184 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
694
749
|
} finally {
|
|
695
750
|
if (worktreeSetup) cleanupWorktrees(worktreeSetup);
|
|
696
751
|
}
|
|
752
|
+
} else if (isDynamicParallelStep(step)) {
|
|
753
|
+
if (Object.hasOwn(step, "acceptance")) {
|
|
754
|
+
const message = `Dynamic fanout step ${stepIndex + 1} does not support group-level acceptance; set acceptance on the child template instead.`;
|
|
755
|
+
dynamicGroupStatuses[stepIndex] = { status: "failed", error: message };
|
|
756
|
+
return buildChainExecutionErrorResult(message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
|
|
757
|
+
}
|
|
758
|
+
let materialized: ReturnType<typeof materializeDynamicParallelStep>;
|
|
759
|
+
try {
|
|
760
|
+
materialized = materializeDynamicParallelStep(step, outputs, stepIndex, { maxItems: params.dynamicFanoutMaxItems });
|
|
761
|
+
} catch (error) {
|
|
762
|
+
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
763
|
+
dynamicGroupStatuses[stepIndex] = { status: "failed", error: message };
|
|
764
|
+
return buildChainExecutionErrorResult(message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
dynamicChildren[stepIndex] = materialized.items.map((item, itemIndex) => ({
|
|
768
|
+
agent: step.parallel.agent,
|
|
769
|
+
label: materialized.parallel[itemIndex]?.label,
|
|
770
|
+
flatIndex: globalTaskIndex + itemIndex,
|
|
771
|
+
itemKey: item.key,
|
|
772
|
+
structured: Boolean(step.parallel.outputSchema),
|
|
773
|
+
}));
|
|
774
|
+
|
|
775
|
+
if (materialized.parallel.length === 0) {
|
|
776
|
+
const collection: DynamicCollectedResult[] = [];
|
|
777
|
+
try {
|
|
778
|
+
validateDynamicCollection(step.collect.outputSchema, collection);
|
|
779
|
+
} catch (error) {
|
|
780
|
+
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
781
|
+
dynamicGroupStatuses[stepIndex] = { status: "failed", error: message };
|
|
782
|
+
return buildChainExecutionErrorResult(message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
|
|
783
|
+
}
|
|
784
|
+
outputs[step.collect.as] = {
|
|
785
|
+
text: JSON.stringify(collection),
|
|
786
|
+
structured: collection,
|
|
787
|
+
agent: step.parallel.agent,
|
|
788
|
+
stepIndex,
|
|
789
|
+
};
|
|
790
|
+
dynamicGroupStatuses[stepIndex] = { status: "completed" };
|
|
791
|
+
prev = "Dynamic fanout produced 0 results.";
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const dynamicParallelStep: ParallelStep = {
|
|
796
|
+
parallel: materialized.parallel,
|
|
797
|
+
concurrency: step.concurrency,
|
|
798
|
+
failFast: step.failFast,
|
|
799
|
+
};
|
|
800
|
+
const parallelTemplates = materialized.parallel.map((task) => task.task ?? "{previous}");
|
|
801
|
+
const parallelBehaviors = resolveParallelBehaviors(dynamicParallelStep.parallel, agents, stepIndex, chainSkills)
|
|
802
|
+
.map((behavior, taskIndex) => suppressProgressForReadOnlyTask(behavior, parallelTemplates[taskIndex] ?? dynamicParallelStep.parallel[taskIndex]?.task, originalTask));
|
|
803
|
+
|
|
804
|
+
for (let taskIndex = 0; taskIndex < dynamicParallelStep.parallel.length; taskIndex++) {
|
|
805
|
+
const behavior = parallelBehaviors[taskIndex]!;
|
|
806
|
+
const outputPath = typeof behavior.output === "string"
|
|
807
|
+
? (path.isAbsolute(behavior.output) ? behavior.output : path.join(chainDir, behavior.output))
|
|
808
|
+
: undefined;
|
|
809
|
+
const validationError = validateFileOnlyOutputMode(behavior.outputMode, outputPath, `Dynamic chain step ${stepIndex + 1} item ${taskIndex + 1} (${dynamicParallelStep.parallel[taskIndex]!.agent})`);
|
|
810
|
+
if (validationError) {
|
|
811
|
+
dynamicGroupStatuses[stepIndex] = { status: "failed", error: validationError };
|
|
812
|
+
return buildChainExecutionErrorResult(validationError, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex + taskIndex }));
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
progressCreated = ensureParallelProgressFile(chainDir, progressCreated, parallelBehaviors);
|
|
817
|
+
createParallelDirs(chainDir, stepIndex, dynamicParallelStep.parallel.length, dynamicParallelStep.parallel.map((task) => task.agent));
|
|
818
|
+
const parallelResults = await runParallelChainTasks({
|
|
819
|
+
step: dynamicParallelStep,
|
|
820
|
+
parallelTemplates,
|
|
821
|
+
parallelBehaviors,
|
|
822
|
+
agents,
|
|
823
|
+
stepIndex,
|
|
824
|
+
availableModels,
|
|
825
|
+
chainDir,
|
|
826
|
+
prev,
|
|
827
|
+
originalTask,
|
|
828
|
+
ctx,
|
|
829
|
+
intercomEvents,
|
|
830
|
+
cwd,
|
|
831
|
+
runId,
|
|
832
|
+
globalTaskIndex,
|
|
833
|
+
sessionDirForIndex,
|
|
834
|
+
sessionFileForIndex,
|
|
835
|
+
shareEnabled,
|
|
836
|
+
artifactConfig,
|
|
837
|
+
artifactsDir,
|
|
838
|
+
signal,
|
|
839
|
+
onUpdate,
|
|
840
|
+
results,
|
|
841
|
+
allProgress,
|
|
842
|
+
outputs,
|
|
843
|
+
chainAgents,
|
|
844
|
+
chainSteps,
|
|
845
|
+
totalSteps,
|
|
846
|
+
dynamicChildren,
|
|
847
|
+
dynamicGroupStatuses,
|
|
848
|
+
controlConfig,
|
|
849
|
+
onControlEvent,
|
|
850
|
+
childIntercomTarget,
|
|
851
|
+
orchestratorIntercomTarget,
|
|
852
|
+
foregroundControl,
|
|
853
|
+
nestedRoute: params.nestedRoute,
|
|
854
|
+
maxSubagentDepth: params.maxSubagentDepth,
|
|
855
|
+
});
|
|
856
|
+
globalTaskIndex += dynamicParallelStep.parallel.length;
|
|
857
|
+
|
|
858
|
+
for (const result of parallelResults) {
|
|
859
|
+
results.push(result);
|
|
860
|
+
if (result.progress) allProgress.push(result.progress);
|
|
861
|
+
if (result.artifactPaths) allArtifactPaths.push(result.artifactPaths);
|
|
862
|
+
}
|
|
863
|
+
const collected = collectDynamicResults(step, materialized.items, parallelResults);
|
|
864
|
+
const interruptedIndexInStep = parallelResults.findIndex((result) => result.interrupted);
|
|
865
|
+
const interrupted = interruptedIndexInStep >= 0 ? parallelResults[interruptedIndexInStep] : undefined;
|
|
866
|
+
if (interrupted) {
|
|
867
|
+
return {
|
|
868
|
+
content: [{ type: "text", text: `Chain paused after interrupt at step ${stepIndex + 1} (${interrupted.agent}). Waiting for explicit next action.` }],
|
|
869
|
+
details: buildChainExecutionDetails(makeDetailsInput({
|
|
870
|
+
currentStepIndex: stepIndex,
|
|
871
|
+
currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length + interruptedIndexInStep,
|
|
872
|
+
})),
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
const detachedIndexInStep = parallelResults.findIndex((result) => result.detached);
|
|
876
|
+
const detached = detachedIndexInStep >= 0 ? parallelResults[detachedIndexInStep] : undefined;
|
|
877
|
+
if (detached) {
|
|
878
|
+
return {
|
|
879
|
+
content: [{ type: "text", text: `Chain detached for intercom coordination at step ${stepIndex + 1} (${detached.agent}). Reply to the supervisor request first. After the child exits, start a fresh follow-up if needed.` }],
|
|
880
|
+
details: buildChainExecutionDetails(makeDetailsInput({
|
|
881
|
+
currentStepIndex: stepIndex,
|
|
882
|
+
currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length + detachedIndexInStep,
|
|
883
|
+
})),
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
const failures = parallelResults
|
|
887
|
+
.map((result, originalIndex) => ({ ...result, originalIndex }))
|
|
888
|
+
.filter((result) => result.exitCode !== 0 && result.exitCode !== -1);
|
|
889
|
+
if (failures.length > 0) {
|
|
890
|
+
const failureSummary = failures
|
|
891
|
+
.map((failure) => `- Item ${failure.originalIndex + 1} (${failure.agent}, key ${materialized.items[failure.originalIndex]?.key ?? failure.originalIndex}): ${failure.error || "failed"}`)
|
|
892
|
+
.join("\n");
|
|
893
|
+
const errorMsg = `Dynamic step ${stepIndex + 1} failed:\n${failureSummary}`;
|
|
894
|
+
dynamicGroupStatuses[stepIndex] = { status: "failed", error: errorMsg };
|
|
895
|
+
const summary = buildChainSummary(chainSteps, results, chainDir, "failed", {
|
|
896
|
+
index: stepIndex,
|
|
897
|
+
error: errorMsg,
|
|
898
|
+
});
|
|
899
|
+
return {
|
|
900
|
+
content: [{ type: "text", text: summary }],
|
|
901
|
+
isError: true,
|
|
902
|
+
details: buildChainExecutionDetails(makeDetailsInput({
|
|
903
|
+
currentStepIndex: stepIndex,
|
|
904
|
+
currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length + failures[0]!.originalIndex,
|
|
905
|
+
})),
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
try {
|
|
909
|
+
validateDynamicCollection(step.collect.outputSchema, collected);
|
|
910
|
+
} catch (error) {
|
|
911
|
+
const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
|
|
912
|
+
dynamicGroupStatuses[stepIndex] = { status: "failed", error: message };
|
|
913
|
+
return buildChainExecutionErrorResult(message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex - dynamicParallelStep.parallel.length }));
|
|
914
|
+
}
|
|
915
|
+
outputs[step.collect.as] = {
|
|
916
|
+
text: JSON.stringify(collected),
|
|
917
|
+
structured: collected,
|
|
918
|
+
agent: step.parallel.agent,
|
|
919
|
+
stepIndex,
|
|
920
|
+
};
|
|
921
|
+
dynamicGroupStatuses[stepIndex] = { status: "completed" };
|
|
922
|
+
const taskResults: ParallelTaskResult[] = parallelResults.map((result, i) => ({
|
|
923
|
+
agent: result.agent,
|
|
924
|
+
taskIndex: i,
|
|
925
|
+
output: getSingleResultOutput(result),
|
|
926
|
+
exitCode: result.exitCode,
|
|
927
|
+
error: result.error,
|
|
928
|
+
}));
|
|
929
|
+
prev = aggregateParallelOutputs(taskResults, (i, agent) => `=== Dynamic Item ${i + 1} (${agent}, key ${materialized.items[i]?.key ?? i}) ===`);
|
|
697
930
|
} else {
|
|
698
931
|
const seqStep = step as SequentialStep;
|
|
699
932
|
const stepTemplate = stepTemplates as string;
|
|
@@ -704,7 +937,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
704
937
|
return {
|
|
705
938
|
content: [{ type: "text", text: `Unknown agent: ${seqStep.agent}` }],
|
|
706
939
|
isError: true,
|
|
707
|
-
details: {
|
|
940
|
+
details: buildChainExecutionDetails(makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex })),
|
|
708
941
|
};
|
|
709
942
|
}
|
|
710
943
|
|
|
@@ -734,7 +967,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
734
967
|
templateHasPrevious ? undefined : prev,
|
|
735
968
|
);
|
|
736
969
|
|
|
737
|
-
let stepTask = stepTemplate;
|
|
970
|
+
let stepTask = resolveOutputReferences(stepTemplate, outputs);
|
|
738
971
|
stepTask = stepTask.replace(/\{task\}/g, originalTask);
|
|
739
972
|
stepTask = stepTask.replace(/\{previous\}/g, prev);
|
|
740
973
|
stepTask = stepTask.replace(/\{chain_dir\}/g, chainDir);
|
|
@@ -751,16 +984,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
751
984
|
: undefined;
|
|
752
985
|
const validationError = validateFileOnlyOutputMode(behavior.outputMode, outputPath, `Chain step ${stepIndex + 1} (${seqStep.agent})`);
|
|
753
986
|
if (validationError) {
|
|
754
|
-
return buildChainExecutionErrorResult(validationError, {
|
|
755
|
-
results,
|
|
756
|
-
includeProgress,
|
|
757
|
-
allProgress,
|
|
758
|
-
allArtifactPaths,
|
|
759
|
-
artifactsDir,
|
|
760
|
-
chainAgents,
|
|
761
|
-
totalSteps,
|
|
762
|
-
currentStepIndex: stepIndex,
|
|
763
|
-
});
|
|
987
|
+
return buildChainExecutionErrorResult(validationError, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
|
|
764
988
|
}
|
|
765
989
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(params.maxSubagentDepth, agentConfig.maxSubagentDepth);
|
|
766
990
|
const interruptController = new AbortController();
|
|
@@ -778,6 +1002,9 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
778
1002
|
};
|
|
779
1003
|
}
|
|
780
1004
|
|
|
1005
|
+
const structuredRuntime = seqStep.outputSchema
|
|
1006
|
+
? createStructuredOutputRuntime(seqStep.outputSchema, path.join(chainDir, "structured-output"))
|
|
1007
|
+
: undefined;
|
|
781
1008
|
const r = await runSync(ctx.cwd, agents, seqStep.agent, stepTask, {
|
|
782
1009
|
cwd: resolveChildCwd(cwd ?? ctx.cwd, seqStep.cwd),
|
|
783
1010
|
signal,
|
|
@@ -803,6 +1030,9 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
803
1030
|
availableModels,
|
|
804
1031
|
preferredModelProvider: ctx.model?.provider,
|
|
805
1032
|
skills: behavior.skills === false ? [] : behavior.skills,
|
|
1033
|
+
structuredOutput: structuredRuntime,
|
|
1034
|
+
acceptance: seqStep.acceptance,
|
|
1035
|
+
acceptanceContext: { mode: "chain" },
|
|
806
1036
|
onUpdate: onUpdate
|
|
807
1037
|
? (p) => {
|
|
808
1038
|
const stepResults = p.details?.results || [];
|
|
@@ -831,6 +1061,17 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
831
1061
|
chainAgents,
|
|
832
1062
|
totalSteps,
|
|
833
1063
|
currentStepIndex: stepIndex,
|
|
1064
|
+
outputs,
|
|
1065
|
+
workflowGraph: buildWorkflowGraphSnapshot({
|
|
1066
|
+
runId,
|
|
1067
|
+
mode: "chain",
|
|
1068
|
+
steps: chainSteps,
|
|
1069
|
+
results: results.concat(stepResults),
|
|
1070
|
+
currentStepIndex: stepIndex,
|
|
1071
|
+
currentFlatIndex: globalTaskIndex,
|
|
1072
|
+
dynamicChildren,
|
|
1073
|
+
dynamicGroupStatuses,
|
|
1074
|
+
}),
|
|
834
1075
|
},
|
|
835
1076
|
});
|
|
836
1077
|
}
|
|
@@ -850,31 +1091,13 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
850
1091
|
if (r.interrupted) {
|
|
851
1092
|
return {
|
|
852
1093
|
content: [{ type: "text", text: `Chain paused after interrupt at step ${stepIndex + 1} (${r.agent}). Waiting for explicit next action.` }],
|
|
853
|
-
details: buildChainExecutionDetails({
|
|
854
|
-
results,
|
|
855
|
-
includeProgress,
|
|
856
|
-
allProgress,
|
|
857
|
-
allArtifactPaths,
|
|
858
|
-
artifactsDir,
|
|
859
|
-
chainAgents,
|
|
860
|
-
totalSteps,
|
|
861
|
-
currentStepIndex: stepIndex,
|
|
862
|
-
}),
|
|
1094
|
+
details: buildChainExecutionDetails(makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex - 1 })),
|
|
863
1095
|
};
|
|
864
1096
|
}
|
|
865
1097
|
if (r.detached) {
|
|
866
1098
|
return {
|
|
867
1099
|
content: [{ type: "text", text: `Chain detached for intercom coordination at step ${stepIndex + 1} (${r.agent}). Reply to the supervisor request first. After the child exits, start a fresh follow-up if needed.` }],
|
|
868
|
-
details: buildChainExecutionDetails({
|
|
869
|
-
results,
|
|
870
|
-
includeProgress,
|
|
871
|
-
allProgress,
|
|
872
|
-
allArtifactPaths,
|
|
873
|
-
artifactsDir,
|
|
874
|
-
chainAgents,
|
|
875
|
-
totalSteps,
|
|
876
|
-
currentStepIndex: stepIndex,
|
|
877
|
-
}),
|
|
1100
|
+
details: buildChainExecutionDetails(makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex - 1 })),
|
|
878
1101
|
};
|
|
879
1102
|
}
|
|
880
1103
|
|
|
@@ -885,16 +1108,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
885
1108
|
});
|
|
886
1109
|
return {
|
|
887
1110
|
content: [{ type: "text", text: summary }],
|
|
888
|
-
details: buildChainExecutionDetails({
|
|
889
|
-
results,
|
|
890
|
-
includeProgress,
|
|
891
|
-
allProgress,
|
|
892
|
-
allArtifactPaths,
|
|
893
|
-
artifactsDir,
|
|
894
|
-
chainAgents,
|
|
895
|
-
totalSteps,
|
|
896
|
-
currentStepIndex: stepIndex,
|
|
897
|
-
}),
|
|
1111
|
+
details: buildChainExecutionDetails(makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex - 1 })),
|
|
898
1112
|
isError: true,
|
|
899
1113
|
};
|
|
900
1114
|
}
|
|
@@ -917,6 +1131,7 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
917
1131
|
}
|
|
918
1132
|
}
|
|
919
1133
|
|
|
1134
|
+
if (seqStep.as) outputs[seqStep.as] = outputEntryFromResult(r, stepIndex);
|
|
920
1135
|
prev = getSingleResultOutput(r);
|
|
921
1136
|
}
|
|
922
1137
|
}
|
|
@@ -925,14 +1140,6 @@ export async function executeChain(params: ChainExecutionParams): Promise<ChainE
|
|
|
925
1140
|
|
|
926
1141
|
return {
|
|
927
1142
|
content: [{ type: "text", text: summary }],
|
|
928
|
-
details: buildChainExecutionDetails(
|
|
929
|
-
results,
|
|
930
|
-
includeProgress,
|
|
931
|
-
allProgress,
|
|
932
|
-
allArtifactPaths,
|
|
933
|
-
artifactsDir,
|
|
934
|
-
chainAgents,
|
|
935
|
-
totalSteps,
|
|
936
|
-
}),
|
|
1143
|
+
details: buildChainExecutionDetails(makeDetailsInput()),
|
|
937
1144
|
};
|
|
938
1145
|
}
|