@bastani/atomic 0.8.24-alpha.2 → 0.8.24-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +2 -1
- package/dist/builtin/intercom/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/CHANGELOG.md +6 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +10 -0
- package/dist/builtin/subagents/README.md +132 -21
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/prompts/parallel-context-build.md +4 -2
- package/dist/builtin/subagents/prompts/parallel-handoff-plan.md +3 -1
- package/dist/builtin/subagents/skills/subagent/SKILL.md +49 -11
- package/dist/builtin/subagents/src/agents/agent-management.ts +79 -16
- package/dist/builtin/subagents/src/agents/agents.ts +47 -16
- package/dist/builtin/subagents/src/agents/chain-serializer.ts +114 -0
- package/dist/builtin/subagents/src/extension/schemas.ts +139 -3
- package/dist/builtin/subagents/src/runs/background/async-execution.ts +92 -6
- package/dist/builtin/subagents/src/runs/background/async-status.ts +11 -1
- package/dist/builtin/subagents/src/runs/background/run-status.ts +4 -1
- package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +529 -32
- package/dist/builtin/subagents/src/runs/foreground/chain-execution.ts +361 -118
- package/dist/builtin/subagents/src/runs/foreground/execution.ts +75 -7
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +33 -0
- package/dist/builtin/subagents/src/runs/shared/acceptance.ts +611 -0
- package/dist/builtin/subagents/src/runs/shared/chain-outputs.ts +101 -0
- package/dist/builtin/subagents/src/runs/shared/dynamic-fanout.ts +293 -0
- package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +29 -1
- package/dist/builtin/subagents/src/runs/shared/pi-args.ts +11 -0
- package/dist/builtin/subagents/src/runs/shared/structured-output.ts +79 -0
- package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +52 -2
- package/dist/builtin/subagents/src/runs/shared/workflow-graph.ts +206 -0
- package/dist/builtin/subagents/src/shared/formatters.ts +2 -2
- package/dist/builtin/subagents/src/shared/settings.ts +53 -4
- package/dist/builtin/subagents/src/shared/types.ts +226 -0
- package/dist/builtin/subagents/src/shared/utils.ts +2 -1
- package/dist/builtin/subagents/src/slash/slash-commands.ts +41 -3
- package/dist/builtin/subagents/src/tui/render.ts +152 -34
- package/dist/builtin/web-access/CHANGELOG.md +6 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +6 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/create-spec/SKILL.md +1 -1
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +0 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +4 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +1 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/usage.md +1 -0
- package/docs/workflows.md +173 -0
- package/package.json +1 -1
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { isDynamicParallelStep, isParallelStep, type ChainStep, type SequentialStep } from "../../shared/settings.ts";
|
|
2
|
+
import type { SingleResult, SubagentRunMode, WorkflowGraphNode, WorkflowGraphSnapshot, WorkflowNodeStatus } from "../../shared/types.ts";
|
|
3
|
+
|
|
4
|
+
export interface WorkflowGraphBuildInput {
|
|
5
|
+
runId: string;
|
|
6
|
+
mode?: SubagentRunMode;
|
|
7
|
+
steps: ChainStep[];
|
|
8
|
+
results?: Array<Pick<SingleResult, "exitCode" | "detached" | "interrupted" | "error" | "acceptance">>;
|
|
9
|
+
currentFlatIndex?: number;
|
|
10
|
+
currentStepIndex?: number;
|
|
11
|
+
stepStatuses?: Array<{ status?: string; error?: string }>;
|
|
12
|
+
dynamicChildren?: Record<number, Array<{ agent: string; label?: string; flatIndex: number; itemKey: string; outputName?: string; structured?: boolean; error?: string }>>;
|
|
13
|
+
dynamicGroupStatuses?: Record<number, { status: WorkflowNodeStatus; error?: string; acceptance?: SingleResult["acceptance"] }>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function normalizeStatus(status: string | undefined): WorkflowNodeStatus | undefined {
|
|
17
|
+
switch (status) {
|
|
18
|
+
case "complete":
|
|
19
|
+
case "completed":
|
|
20
|
+
return "completed";
|
|
21
|
+
case "running":
|
|
22
|
+
return "running";
|
|
23
|
+
case "failed":
|
|
24
|
+
return "failed";
|
|
25
|
+
case "paused":
|
|
26
|
+
return "paused";
|
|
27
|
+
case "detached":
|
|
28
|
+
return "detached";
|
|
29
|
+
case "pending":
|
|
30
|
+
return "pending";
|
|
31
|
+
default:
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function resultStatus(result: Pick<SingleResult, "exitCode" | "detached" | "interrupted"> | undefined): WorkflowNodeStatus | undefined {
|
|
37
|
+
if (!result) return undefined;
|
|
38
|
+
if (result.detached) return "detached";
|
|
39
|
+
if (result.interrupted) return "paused";
|
|
40
|
+
return result.exitCode === 0 ? "completed" : "failed";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function nodeStatus(input: WorkflowGraphBuildInput, flatIndex: number): WorkflowNodeStatus {
|
|
44
|
+
return normalizeStatus(input.stepStatuses?.[flatIndex]?.status)
|
|
45
|
+
?? resultStatus(input.results?.[flatIndex])
|
|
46
|
+
?? (input.currentFlatIndex === flatIndex ? "running" : "pending");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function pushPhase(phases: WorkflowGraphSnapshot["phases"], phase: string | undefined, nodeId: string): void {
|
|
50
|
+
if (!phase) return;
|
|
51
|
+
let group = phases.find((candidate) => candidate.title === phase);
|
|
52
|
+
if (!group) {
|
|
53
|
+
group = { title: phase, nodeIds: [] };
|
|
54
|
+
phases.push(group);
|
|
55
|
+
}
|
|
56
|
+
group.nodeIds.push(nodeId);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function seqLabel(step: SequentialStep, stepIndex: number): string {
|
|
60
|
+
return step.label?.trim() || step.agent || `Step ${stepIndex + 1}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function summarizeParallelStatuses(statuses: WorkflowNodeStatus[]): WorkflowNodeStatus {
|
|
64
|
+
if (statuses.some((status) => status === "running")) return "running";
|
|
65
|
+
if (statuses.some((status) => status === "failed")) return "failed";
|
|
66
|
+
if (statuses.some((status) => status === "paused")) return "paused";
|
|
67
|
+
if (statuses.some((status) => status === "detached")) return "detached";
|
|
68
|
+
if (statuses.length > 0 && statuses.every((status) => status === "completed")) return "completed";
|
|
69
|
+
if (statuses.some((status) => status === "completed")) return "running";
|
|
70
|
+
return "pending";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function buildWorkflowGraphSnapshot(input: WorkflowGraphBuildInput): WorkflowGraphSnapshot {
|
|
74
|
+
const nodes: WorkflowGraphNode[] = [];
|
|
75
|
+
const phases: WorkflowGraphSnapshot["phases"] = [];
|
|
76
|
+
let flatIndex = 0;
|
|
77
|
+
let currentNodeId: string | undefined;
|
|
78
|
+
|
|
79
|
+
for (let stepIndex = 0; stepIndex < input.steps.length; stepIndex++) {
|
|
80
|
+
const step = input.steps[stepIndex]!;
|
|
81
|
+
if (isParallelStep(step)) {
|
|
82
|
+
const groupId = `step-${stepIndex}`;
|
|
83
|
+
const children: WorkflowGraphNode[] = [];
|
|
84
|
+
const childStatuses: WorkflowNodeStatus[] = [];
|
|
85
|
+
for (let taskIndex = 0; taskIndex < step.parallel.length; taskIndex++) {
|
|
86
|
+
const task = step.parallel[taskIndex]!;
|
|
87
|
+
const status = nodeStatus(input, flatIndex);
|
|
88
|
+
childStatuses.push(status);
|
|
89
|
+
const childId = `step-${stepIndex}-agent-${taskIndex}`;
|
|
90
|
+
const child: WorkflowGraphNode = {
|
|
91
|
+
id: childId,
|
|
92
|
+
kind: "agent",
|
|
93
|
+
agent: task.agent,
|
|
94
|
+
phase: task.phase,
|
|
95
|
+
label: task.label?.trim() || task.agent || `Agent ${taskIndex + 1}`,
|
|
96
|
+
status,
|
|
97
|
+
flatIndex,
|
|
98
|
+
stepIndex,
|
|
99
|
+
outputName: task.as,
|
|
100
|
+
structured: Boolean(task.outputSchema),
|
|
101
|
+
acceptanceStatus: input.results?.[flatIndex]?.acceptance?.status,
|
|
102
|
+
error: input.stepStatuses?.[flatIndex]?.error ?? input.results?.[flatIndex]?.error,
|
|
103
|
+
};
|
|
104
|
+
children.push(child);
|
|
105
|
+
pushPhase(phases, task.phase, childId);
|
|
106
|
+
if (status === "running" || input.currentFlatIndex === flatIndex) currentNodeId = childId;
|
|
107
|
+
flatIndex++;
|
|
108
|
+
}
|
|
109
|
+
const groupStatus = summarizeParallelStatuses(childStatuses);
|
|
110
|
+
if (input.currentStepIndex === stepIndex && !currentNodeId) currentNodeId = groupId;
|
|
111
|
+
nodes.push({
|
|
112
|
+
id: groupId,
|
|
113
|
+
kind: "parallel-group",
|
|
114
|
+
label: step.parallel.length === 1 ? "Parallel task" : `Parallel group (${step.parallel.length})`,
|
|
115
|
+
status: groupStatus,
|
|
116
|
+
stepIndex,
|
|
117
|
+
children,
|
|
118
|
+
});
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (isDynamicParallelStep(step)) {
|
|
123
|
+
const groupId = `step-${stepIndex}`;
|
|
124
|
+
const materialized = input.dynamicChildren?.[stepIndex] ?? [];
|
|
125
|
+
const groupOverride = input.dynamicGroupStatuses?.[stepIndex];
|
|
126
|
+
const children: WorkflowGraphNode[] = [];
|
|
127
|
+
const childStatuses: WorkflowNodeStatus[] = [];
|
|
128
|
+
for (let taskIndex = 0; taskIndex < materialized.length; taskIndex++) {
|
|
129
|
+
const task = materialized[taskIndex]!;
|
|
130
|
+
const status = nodeStatus(input, task.flatIndex);
|
|
131
|
+
childStatuses.push(status);
|
|
132
|
+
const childId = `step-${stepIndex}-item-${task.itemKey.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
|
|
133
|
+
const child: WorkflowGraphNode = {
|
|
134
|
+
id: childId,
|
|
135
|
+
kind: "agent",
|
|
136
|
+
agent: task.agent,
|
|
137
|
+
phase: step.parallel.phase ?? step.phase,
|
|
138
|
+
label: task.label?.trim() || step.parallel.label?.trim() || `${task.agent} ${task.itemKey}`,
|
|
139
|
+
status,
|
|
140
|
+
flatIndex: task.flatIndex,
|
|
141
|
+
stepIndex,
|
|
142
|
+
itemKey: task.itemKey,
|
|
143
|
+
outputName: task.outputName,
|
|
144
|
+
structured: task.structured,
|
|
145
|
+
acceptanceStatus: input.results?.[task.flatIndex]?.acceptance?.status,
|
|
146
|
+
error: input.stepStatuses?.[task.flatIndex]?.error ?? input.results?.[task.flatIndex]?.error ?? task.error,
|
|
147
|
+
};
|
|
148
|
+
children.push(child);
|
|
149
|
+
pushPhase(phases, child.phase, childId);
|
|
150
|
+
if (status === "running" || input.currentFlatIndex === task.flatIndex) currentNodeId = childId;
|
|
151
|
+
}
|
|
152
|
+
const groupStatus = groupOverride?.status ?? (children.length > 0 ? summarizeParallelStatuses(childStatuses) : (input.currentStepIndex === stepIndex ? "running" : "pending"));
|
|
153
|
+
if (input.currentStepIndex === stepIndex && !currentNodeId) currentNodeId = groupId;
|
|
154
|
+
nodes.push({
|
|
155
|
+
id: groupId,
|
|
156
|
+
kind: "dynamic-parallel-group",
|
|
157
|
+
label: step.label?.trim() || step.parallel.label?.trim() || `Dynamic fanout (${step.collect.as})`,
|
|
158
|
+
status: groupStatus,
|
|
159
|
+
stepIndex,
|
|
160
|
+
outputName: step.collect.as,
|
|
161
|
+
structured: Boolean(step.collect.outputSchema),
|
|
162
|
+
acceptanceStatus: groupOverride?.acceptance?.status,
|
|
163
|
+
error: groupOverride?.error,
|
|
164
|
+
dynamic: {
|
|
165
|
+
sourceOutput: step.expand.from.output,
|
|
166
|
+
sourcePath: step.expand.from.path,
|
|
167
|
+
itemName: step.expand.item ?? "item",
|
|
168
|
+
maxItems: step.expand.maxItems,
|
|
169
|
+
collectAs: step.collect.as,
|
|
170
|
+
},
|
|
171
|
+
children,
|
|
172
|
+
});
|
|
173
|
+
if (materialized.length > 0) flatIndex = Math.max(flatIndex, ...materialized.map((child) => child.flatIndex + 1));
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const seq = step as SequentialStep;
|
|
178
|
+
const status = nodeStatus(input, flatIndex);
|
|
179
|
+
const id = `step-${stepIndex}`;
|
|
180
|
+
nodes.push({
|
|
181
|
+
id,
|
|
182
|
+
kind: "step",
|
|
183
|
+
agent: seq.agent,
|
|
184
|
+
phase: seq.phase,
|
|
185
|
+
label: seqLabel(seq, stepIndex),
|
|
186
|
+
status,
|
|
187
|
+
flatIndex,
|
|
188
|
+
stepIndex,
|
|
189
|
+
outputName: seq.as,
|
|
190
|
+
structured: Boolean(seq.outputSchema),
|
|
191
|
+
acceptanceStatus: input.results?.[flatIndex]?.acceptance?.status,
|
|
192
|
+
error: input.stepStatuses?.[flatIndex]?.error ?? input.results?.[flatIndex]?.error,
|
|
193
|
+
});
|
|
194
|
+
pushPhase(phases, seq.phase, id);
|
|
195
|
+
if (status === "running" || input.currentFlatIndex === flatIndex || input.currentStepIndex === stepIndex) currentNodeId = id;
|
|
196
|
+
flatIndex++;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
runId: input.runId,
|
|
201
|
+
mode: input.mode ?? "chain",
|
|
202
|
+
phases,
|
|
203
|
+
nodes,
|
|
204
|
+
currentNodeId,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
@@ -6,7 +6,7 @@ import * as fs from "node:fs";
|
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import type { Usage, SingleResult } from "./types.ts";
|
|
8
8
|
import type { ChainStep } from "./settings.ts";
|
|
9
|
-
import { isParallelStep } from "./settings.ts";
|
|
9
|
+
import { isDynamicParallelStep, isParallelStep } from "./settings.ts";
|
|
10
10
|
import { splitKnownThinkingSuffix, THINKING_LEVELS } from "./model-info.ts";
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -70,7 +70,7 @@ export function buildChainSummary(
|
|
|
70
70
|
failedStep?: { index: number; error: string },
|
|
71
71
|
): string {
|
|
72
72
|
const stepNames = steps
|
|
73
|
-
.map((step) => (isParallelStep(step) ? `parallel[${step.parallel.length}]` : step.agent))
|
|
73
|
+
.map((step) => (isParallelStep(step) ? `parallel[${step.parallel.length}]` : isDynamicParallelStep(step) ? `expand:${step.parallel.agent}` : step.agent))
|
|
74
74
|
.join(" → ");
|
|
75
75
|
|
|
76
76
|
const totalDuration = results.reduce((sum, r) => sum + (r.progress?.durationMs || 0), 0);
|
|
@@ -6,7 +6,7 @@ import * as fs from "node:fs";
|
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import type { AgentConfig } from "../agents/agents.ts";
|
|
8
8
|
import { normalizeSkillInput } from "../agents/skills.ts";
|
|
9
|
-
import { CHAIN_RUNS_DIR, type OutputMode } from "./types.ts";
|
|
9
|
+
import { CHAIN_RUNS_DIR, type AcceptanceInput, type JsonSchemaObject, type OutputMode } from "./types.ts";
|
|
10
10
|
const CHAIN_DIR_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
11
11
|
const INITIAL_PROGRESS_CONTENT = "# Progress\n\n## Status\nIn Progress\n\n## Tasks\n\n## Files Changed\n\n## Notes\n";
|
|
12
12
|
|
|
@@ -44,6 +44,10 @@ function normalizeOutputOverride(output: string | false | undefined): string | f
|
|
|
44
44
|
export interface SequentialStep {
|
|
45
45
|
agent: string;
|
|
46
46
|
task?: string;
|
|
47
|
+
phase?: string;
|
|
48
|
+
label?: string;
|
|
49
|
+
as?: string;
|
|
50
|
+
outputSchema?: JsonSchemaObject;
|
|
47
51
|
cwd?: string;
|
|
48
52
|
output?: string | false;
|
|
49
53
|
outputMode?: OutputMode;
|
|
@@ -51,12 +55,17 @@ export interface SequentialStep {
|
|
|
51
55
|
progress?: boolean;
|
|
52
56
|
skill?: string | string[] | false;
|
|
53
57
|
model?: string;
|
|
58
|
+
acceptance?: AcceptanceInput;
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
/** Parallel task item within a parallel step */
|
|
57
|
-
interface ParallelTaskItem {
|
|
62
|
+
export interface ParallelTaskItem {
|
|
58
63
|
agent: string;
|
|
59
64
|
task?: string;
|
|
65
|
+
phase?: string;
|
|
66
|
+
label?: string;
|
|
67
|
+
as?: string;
|
|
68
|
+
outputSchema?: JsonSchemaObject;
|
|
60
69
|
cwd?: string;
|
|
61
70
|
count?: number;
|
|
62
71
|
output?: string | false;
|
|
@@ -65,10 +74,40 @@ interface ParallelTaskItem {
|
|
|
65
74
|
progress?: boolean;
|
|
66
75
|
skill?: string | string[] | false;
|
|
67
76
|
model?: string;
|
|
77
|
+
acceptance?: AcceptanceInput;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface DynamicExpandSpec {
|
|
81
|
+
from: {
|
|
82
|
+
output: string;
|
|
83
|
+
path: string;
|
|
84
|
+
};
|
|
85
|
+
item?: string;
|
|
86
|
+
key?: string;
|
|
87
|
+
maxItems?: number;
|
|
88
|
+
onEmpty?: "skip" | "fail";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export type DynamicParallelTemplate = Omit<ParallelTaskItem, "as" | "count">;
|
|
92
|
+
|
|
93
|
+
export interface DynamicCollectSpec {
|
|
94
|
+
as: string;
|
|
95
|
+
outputSchema?: JsonSchemaObject;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface DynamicParallelStep {
|
|
99
|
+
expand: DynamicExpandSpec;
|
|
100
|
+
parallel: DynamicParallelTemplate;
|
|
101
|
+
collect: DynamicCollectSpec;
|
|
102
|
+
concurrency?: number;
|
|
103
|
+
failFast?: boolean;
|
|
104
|
+
phase?: string;
|
|
105
|
+
label?: string;
|
|
106
|
+
acceptance?: AcceptanceInput;
|
|
68
107
|
}
|
|
69
108
|
|
|
70
109
|
/** Parallel step: multiple agents running concurrently */
|
|
71
|
-
interface ParallelStep {
|
|
110
|
+
export interface ParallelStep {
|
|
72
111
|
parallel: ParallelTaskItem[];
|
|
73
112
|
cwd?: string;
|
|
74
113
|
concurrency?: number;
|
|
@@ -77,7 +116,7 @@ interface ParallelStep {
|
|
|
77
116
|
}
|
|
78
117
|
|
|
79
118
|
/** Union type for chain steps */
|
|
80
|
-
export type ChainStep = SequentialStep | ParallelStep;
|
|
119
|
+
export type ChainStep = SequentialStep | ParallelStep | DynamicParallelStep;
|
|
81
120
|
|
|
82
121
|
// =============================================================================
|
|
83
122
|
// Type Guards
|
|
@@ -87,11 +126,18 @@ export function isParallelStep(step: ChainStep): step is ParallelStep {
|
|
|
87
126
|
return "parallel" in step && Array.isArray((step as ParallelStep).parallel);
|
|
88
127
|
}
|
|
89
128
|
|
|
129
|
+
export function isDynamicParallelStep(step: ChainStep): step is DynamicParallelStep {
|
|
130
|
+
return "expand" in step && "collect" in step && "parallel" in step && !Array.isArray((step as { parallel?: unknown }).parallel);
|
|
131
|
+
}
|
|
132
|
+
|
|
90
133
|
/** Get all agent names in a step (single for sequential, multiple for parallel) */
|
|
91
134
|
export function getStepAgents(step: ChainStep): string[] {
|
|
92
135
|
if (isParallelStep(step)) {
|
|
93
136
|
return step.parallel.map((t) => t.agent);
|
|
94
137
|
}
|
|
138
|
+
if (isDynamicParallelStep(step)) {
|
|
139
|
+
return [step.parallel.agent];
|
|
140
|
+
}
|
|
95
141
|
return [step.agent];
|
|
96
142
|
}
|
|
97
143
|
|
|
@@ -161,6 +207,9 @@ export function resolveChainTemplates(
|
|
|
161
207
|
return "{previous}";
|
|
162
208
|
});
|
|
163
209
|
}
|
|
210
|
+
if (isDynamicParallelStep(step)) {
|
|
211
|
+
return step.parallel.task ?? "{previous}";
|
|
212
|
+
}
|
|
164
213
|
// Sequential step: existing logic
|
|
165
214
|
const seq = step as SequentialStep;
|
|
166
215
|
if (seq.task) return seq.task;
|
|
@@ -26,6 +26,51 @@ export interface MaxOutputConfig {
|
|
|
26
26
|
|
|
27
27
|
export type OutputMode = "inline" | "file-only";
|
|
28
28
|
|
|
29
|
+
export type JsonSchemaObject = Record<string, unknown>;
|
|
30
|
+
|
|
31
|
+
export interface ChainOutputMapEntry {
|
|
32
|
+
text: string;
|
|
33
|
+
structured?: unknown;
|
|
34
|
+
agent: string;
|
|
35
|
+
stepIndex: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type ChainOutputMap = Record<string, ChainOutputMapEntry>;
|
|
39
|
+
|
|
40
|
+
export type WorkflowNodeStatus = "pending" | "running" | "completed" | "failed" | "paused" | "detached";
|
|
41
|
+
|
|
42
|
+
export interface WorkflowGraphNode {
|
|
43
|
+
id: string;
|
|
44
|
+
kind: "step" | "parallel-group" | "dynamic-parallel-group" | "agent";
|
|
45
|
+
agent?: string;
|
|
46
|
+
phase?: string;
|
|
47
|
+
label: string;
|
|
48
|
+
status: WorkflowNodeStatus;
|
|
49
|
+
flatIndex?: number;
|
|
50
|
+
stepIndex?: number;
|
|
51
|
+
children?: WorkflowGraphNode[];
|
|
52
|
+
dynamic?: {
|
|
53
|
+
sourceOutput: string;
|
|
54
|
+
sourcePath: string;
|
|
55
|
+
itemName: string;
|
|
56
|
+
maxItems?: number;
|
|
57
|
+
collectAs?: string;
|
|
58
|
+
};
|
|
59
|
+
itemKey?: string;
|
|
60
|
+
outputName?: string;
|
|
61
|
+
structured?: boolean;
|
|
62
|
+
acceptanceStatus?: AcceptanceLedgerStatus;
|
|
63
|
+
error?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface WorkflowGraphSnapshot {
|
|
67
|
+
runId: string;
|
|
68
|
+
mode: "chain" | "parallel" | "single";
|
|
69
|
+
phases: Array<{ title: string; nodeIds: string[] }>;
|
|
70
|
+
nodes: WorkflowGraphNode[];
|
|
71
|
+
currentNodeId?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
29
74
|
export interface SavedOutputReference {
|
|
30
75
|
path: string;
|
|
31
76
|
bytes: number;
|
|
@@ -202,6 +247,151 @@ export interface ModelAttempt {
|
|
|
202
247
|
usage?: Usage;
|
|
203
248
|
}
|
|
204
249
|
|
|
250
|
+
export type AcceptanceLevel = "auto" | "none" | "attested" | "checked" | "verified" | "reviewed";
|
|
251
|
+
|
|
252
|
+
export type AcceptanceEvidenceKind =
|
|
253
|
+
| "changed-files"
|
|
254
|
+
| "tests-added"
|
|
255
|
+
| "commands-run"
|
|
256
|
+
| "validation-output"
|
|
257
|
+
| "residual-risks"
|
|
258
|
+
| "no-staged-files"
|
|
259
|
+
| "diff-summary"
|
|
260
|
+
| "review-findings"
|
|
261
|
+
| "manual-notes";
|
|
262
|
+
|
|
263
|
+
export interface AcceptanceGate {
|
|
264
|
+
id: string;
|
|
265
|
+
must: string;
|
|
266
|
+
evidence?: AcceptanceEvidenceKind[];
|
|
267
|
+
severity?: "required" | "recommended";
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export interface AcceptanceVerifyCommand {
|
|
271
|
+
id: string;
|
|
272
|
+
command: string;
|
|
273
|
+
timeoutMs?: number;
|
|
274
|
+
cwd?: string;
|
|
275
|
+
env?: Record<string, string>;
|
|
276
|
+
allowFailure?: boolean;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export interface AcceptanceReviewGate {
|
|
280
|
+
agent?: string;
|
|
281
|
+
focus?: string;
|
|
282
|
+
required?: boolean;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export interface AcceptanceConfig {
|
|
286
|
+
level?: AcceptanceLevel;
|
|
287
|
+
criteria?: Array<string | AcceptanceGate>;
|
|
288
|
+
evidence?: AcceptanceEvidenceKind[];
|
|
289
|
+
verify?: AcceptanceVerifyCommand[];
|
|
290
|
+
review?: AcceptanceReviewGate | false;
|
|
291
|
+
stopRules?: string[];
|
|
292
|
+
reason?: string;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export type AcceptanceInput = AcceptanceLevel | false | AcceptanceConfig;
|
|
296
|
+
|
|
297
|
+
export interface ResolvedAcceptanceGate extends AcceptanceGate {
|
|
298
|
+
id: string;
|
|
299
|
+
must: string;
|
|
300
|
+
evidence: AcceptanceEvidenceKind[];
|
|
301
|
+
severity: "required" | "recommended";
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export interface ResolvedAcceptanceConfig {
|
|
305
|
+
level: Exclude<AcceptanceLevel, "auto">;
|
|
306
|
+
explicit: boolean;
|
|
307
|
+
inferredReason: string[];
|
|
308
|
+
criteria: ResolvedAcceptanceGate[];
|
|
309
|
+
evidence: AcceptanceEvidenceKind[];
|
|
310
|
+
verify: AcceptanceVerifyCommand[];
|
|
311
|
+
review?: AcceptanceReviewGate | false;
|
|
312
|
+
stopRules: string[];
|
|
313
|
+
reason?: string;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export interface AcceptanceReport {
|
|
317
|
+
criteriaSatisfied?: Array<{
|
|
318
|
+
id?: string;
|
|
319
|
+
status: "satisfied" | "not-satisfied" | "not-applicable";
|
|
320
|
+
evidence: string;
|
|
321
|
+
}>;
|
|
322
|
+
changedFiles?: string[];
|
|
323
|
+
testsAddedOrUpdated?: string[];
|
|
324
|
+
commandsRun?: Array<{
|
|
325
|
+
command: string;
|
|
326
|
+
result: "passed" | "failed" | "not-run";
|
|
327
|
+
summary: string;
|
|
328
|
+
}>;
|
|
329
|
+
validationOutput?: string[];
|
|
330
|
+
residualRisks?: string[];
|
|
331
|
+
noStagedFiles?: boolean;
|
|
332
|
+
diffSummary?: string;
|
|
333
|
+
reviewFindings?: string[];
|
|
334
|
+
manualNotes?: string;
|
|
335
|
+
notes?: string;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export type AcceptanceRuntimeCheckStatus = "passed" | "failed" | "not-applicable";
|
|
339
|
+
|
|
340
|
+
export interface AcceptanceRuntimeCheck {
|
|
341
|
+
id: string;
|
|
342
|
+
status: AcceptanceRuntimeCheckStatus;
|
|
343
|
+
message: string;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export interface AcceptanceVerifyResult {
|
|
347
|
+
id: string;
|
|
348
|
+
command: string;
|
|
349
|
+
cwd?: string;
|
|
350
|
+
exitCode: number | null;
|
|
351
|
+
status: "passed" | "failed" | "timed-out" | "allowed-failure";
|
|
352
|
+
stdout?: string;
|
|
353
|
+
stderr?: string;
|
|
354
|
+
durationMs: number;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export interface AcceptanceReviewResult {
|
|
358
|
+
status: "no-blockers" | "blockers" | "needs-parent-decision";
|
|
359
|
+
findings: Array<{
|
|
360
|
+
severity: "blocker" | "non-blocking";
|
|
361
|
+
file?: string;
|
|
362
|
+
issue: string;
|
|
363
|
+
rationale: string;
|
|
364
|
+
}>;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export type AcceptanceLedgerStatus =
|
|
368
|
+
| "not-required"
|
|
369
|
+
| "claimed"
|
|
370
|
+
| "attested"
|
|
371
|
+
| "checked"
|
|
372
|
+
| "verified"
|
|
373
|
+
| "reviewed"
|
|
374
|
+
| "accepted"
|
|
375
|
+
| "rejected";
|
|
376
|
+
|
|
377
|
+
export interface AcceptanceLedger {
|
|
378
|
+
status: AcceptanceLedgerStatus;
|
|
379
|
+
explicit: boolean;
|
|
380
|
+
effectiveAcceptance: ResolvedAcceptanceConfig;
|
|
381
|
+
inferredReason: string[];
|
|
382
|
+
criteria: ResolvedAcceptanceGate[];
|
|
383
|
+
childReport?: AcceptanceReport;
|
|
384
|
+
childReportParseError?: string;
|
|
385
|
+
runtimeChecks: AcceptanceRuntimeCheck[];
|
|
386
|
+
verifyRuns: AcceptanceVerifyResult[];
|
|
387
|
+
reviewResult?: AcceptanceReviewResult;
|
|
388
|
+
parentDecision?: {
|
|
389
|
+
status: "accepted" | "rejected";
|
|
390
|
+
at: string;
|
|
391
|
+
reason?: string;
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
205
395
|
export interface SingleResult {
|
|
206
396
|
agent: string;
|
|
207
397
|
task: string;
|
|
@@ -230,6 +420,10 @@ export interface SingleResult {
|
|
|
230
420
|
savedOutputPath?: string;
|
|
231
421
|
outputReference?: SavedOutputReference;
|
|
232
422
|
outputSaveError?: string;
|
|
423
|
+
structuredOutput?: unknown;
|
|
424
|
+
structuredOutputPath?: string;
|
|
425
|
+
structuredOutputSchemaPath?: string;
|
|
426
|
+
acceptance?: AcceptanceLedger;
|
|
233
427
|
}
|
|
234
428
|
|
|
235
429
|
export interface Details {
|
|
@@ -256,6 +450,8 @@ export interface Details {
|
|
|
256
450
|
chainAgents?: string[]; // Agent names in order, e.g., ["scout", "planner"]
|
|
257
451
|
totalSteps?: number; // Total steps in chain
|
|
258
452
|
currentStepIndex?: number; // 0-indexed current step (for running chains)
|
|
453
|
+
workflowGraph?: WorkflowGraphSnapshot;
|
|
454
|
+
outputs?: ChainOutputMap;
|
|
259
455
|
}
|
|
260
456
|
|
|
261
457
|
// Upstream AgentToolResult omits the runtime isError flag that subagent tool results still emit/read.
|
|
@@ -372,6 +568,7 @@ export interface AsyncStartedEvent {
|
|
|
372
568
|
chain?: string[];
|
|
373
569
|
chainStepCount?: number;
|
|
374
570
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
571
|
+
workflowGraph?: WorkflowGraphSnapshot;
|
|
375
572
|
nestedRoute?: NestedRouteInfo;
|
|
376
573
|
}
|
|
377
574
|
|
|
@@ -395,8 +592,13 @@ export interface AsyncStatus {
|
|
|
395
592
|
currentStep?: number;
|
|
396
593
|
chainStepCount?: number;
|
|
397
594
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
595
|
+
workflowGraph?: WorkflowGraphSnapshot;
|
|
398
596
|
steps?: Array<{
|
|
399
597
|
agent: string;
|
|
598
|
+
phase?: string;
|
|
599
|
+
label?: string;
|
|
600
|
+
outputName?: string;
|
|
601
|
+
structured?: boolean;
|
|
400
602
|
status: "pending" | "running" | "complete" | "completed" | "failed" | "paused";
|
|
401
603
|
children?: NestedRunSummary[];
|
|
402
604
|
sessionFile?: string;
|
|
@@ -422,11 +624,16 @@ export interface AsyncStatus {
|
|
|
422
624
|
attemptedModels?: string[];
|
|
423
625
|
modelAttempts?: ModelAttempt[];
|
|
424
626
|
error?: string;
|
|
627
|
+
structuredOutput?: unknown;
|
|
628
|
+
structuredOutputPath?: string;
|
|
629
|
+
structuredOutputSchemaPath?: string;
|
|
630
|
+
acceptance?: AcceptanceLedger;
|
|
425
631
|
}>;
|
|
426
632
|
sessionDir?: string;
|
|
427
633
|
outputFile?: string;
|
|
428
634
|
totalTokens?: TokenUsage;
|
|
429
635
|
sessionFile?: string;
|
|
636
|
+
outputs?: ChainOutputMap;
|
|
430
637
|
}
|
|
431
638
|
|
|
432
639
|
export type AsyncJobStep = NonNullable<AsyncStatus["steps"]>[number] & {
|
|
@@ -592,6 +799,18 @@ export interface RunSyncOptions {
|
|
|
592
799
|
currentModel?: string;
|
|
593
800
|
/** Skills to inject (overrides agent default if provided) */
|
|
594
801
|
skills?: string[];
|
|
802
|
+
structuredOutput?: {
|
|
803
|
+
schema: JsonSchemaObject;
|
|
804
|
+
schemaPath: string;
|
|
805
|
+
outputPath: string;
|
|
806
|
+
};
|
|
807
|
+
acceptance?: AcceptanceInput;
|
|
808
|
+
acceptanceContext?: {
|
|
809
|
+
mode?: SubagentRunMode;
|
|
810
|
+
async?: boolean;
|
|
811
|
+
dynamic?: boolean;
|
|
812
|
+
dynamicGroup?: boolean;
|
|
813
|
+
};
|
|
595
814
|
}
|
|
596
815
|
|
|
597
816
|
export type IntercomBridgeMode = "off" | "fork-only" | "always";
|
|
@@ -606,6 +825,12 @@ interface TopLevelParallelConfig {
|
|
|
606
825
|
concurrency?: number;
|
|
607
826
|
}
|
|
608
827
|
|
|
828
|
+
interface ExtensionChainConfig {
|
|
829
|
+
dynamicFanout?: {
|
|
830
|
+
maxItems?: number;
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
|
|
609
834
|
export interface ExtensionConfig {
|
|
610
835
|
asyncByDefault?: boolean;
|
|
611
836
|
forceTopLevelAsync?: boolean;
|
|
@@ -613,6 +838,7 @@ export interface ExtensionConfig {
|
|
|
613
838
|
maxSubagentDepth?: number;
|
|
614
839
|
control?: ControlConfig;
|
|
615
840
|
parallel?: TopLevelParallelConfig;
|
|
841
|
+
chain?: ExtensionChainConfig;
|
|
616
842
|
worktreeSetupHook?: string;
|
|
617
843
|
worktreeSetupHookTimeoutMs?: number;
|
|
618
844
|
intercomBridge?: IntercomBridgeConfig;
|
|
@@ -132,7 +132,8 @@ export function getFinalOutput(messages: Message[]): string {
|
|
|
132
132
|
const hasAssistantError = ("errorMessage" in msg && typeof msg.errorMessage === "string" && msg.errorMessage.length > 0)
|
|
133
133
|
|| ("stopReason" in msg && msg.stopReason === "error");
|
|
134
134
|
if (hasAssistantError) continue;
|
|
135
|
-
for (
|
|
135
|
+
for (let j = msg.content.length - 1; j >= 0; j--) {
|
|
136
|
+
const part = msg.content[j];
|
|
136
137
|
if (part.type === "text" && part.text.trim().length > 0) return part.text;
|
|
137
138
|
}
|
|
138
139
|
}
|