pi-subagents 0.25.0 → 0.28.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 +34 -0
- package/README.md +175 -19
- 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 +60 -17
- package/src/agents/agent-management.ts +71 -15
- package/src/agents/agent-serializer.ts +13 -2
- package/src/agents/agents.ts +88 -17
- package/src/agents/chain-serializer.ts +120 -0
- package/src/extension/fanout-child.ts +2 -0
- package/src/extension/index.ts +5 -2
- package/src/extension/schemas.ts +132 -6
- package/src/intercom/result-intercom.ts +5 -0
- package/src/runs/background/async-execution.ts +88 -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 +665 -39
- package/src/runs/foreground/chain-execution.ts +369 -118
- package/src/runs/foreground/execution.ts +392 -19
- package/src/runs/foreground/subagent-executor.ts +126 -3
- package/src/runs/shared/acceptance-contract.ts +318 -0
- package/src/runs/shared/acceptance-evaluation.ts +221 -0
- package/src/runs/shared/acceptance-finalization.ts +173 -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 +33 -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 +210 -0
- package/src/shared/formatters.ts +2 -2
- package/src/shared/settings.ts +53 -4
- package/src/shared/types.ts +265 -1
- package/src/shared/utils.ts +7 -0
- package/src/slash/slash-commands.ts +41 -3
- package/src/tui/render.ts +178 -45
|
@@ -0,0 +1,210 @@
|
|
|
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" | "timedOut" | "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 "timed-out":
|
|
30
|
+
return "timed-out";
|
|
31
|
+
case "pending":
|
|
32
|
+
return "pending";
|
|
33
|
+
default:
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function resultStatus(result: Pick<SingleResult, "exitCode" | "detached" | "interrupted" | "timedOut"> | undefined): WorkflowNodeStatus | undefined {
|
|
39
|
+
if (!result) return undefined;
|
|
40
|
+
if (result.detached) return "detached";
|
|
41
|
+
if (result.timedOut) return "timed-out";
|
|
42
|
+
if (result.interrupted) return "paused";
|
|
43
|
+
return result.exitCode === 0 ? "completed" : "failed";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function nodeStatus(input: WorkflowGraphBuildInput, flatIndex: number): WorkflowNodeStatus {
|
|
47
|
+
return normalizeStatus(input.stepStatuses?.[flatIndex]?.status)
|
|
48
|
+
?? resultStatus(input.results?.[flatIndex])
|
|
49
|
+
?? (input.currentFlatIndex === flatIndex ? "running" : "pending");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function pushPhase(phases: WorkflowGraphSnapshot["phases"], phase: string | undefined, nodeId: string): void {
|
|
53
|
+
if (!phase) return;
|
|
54
|
+
let group = phases.find((candidate) => candidate.title === phase);
|
|
55
|
+
if (!group) {
|
|
56
|
+
group = { title: phase, nodeIds: [] };
|
|
57
|
+
phases.push(group);
|
|
58
|
+
}
|
|
59
|
+
group.nodeIds.push(nodeId);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function seqLabel(step: SequentialStep, stepIndex: number): string {
|
|
63
|
+
return step.label?.trim() || step.agent || `Step ${stepIndex + 1}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function summarizeParallelStatuses(statuses: WorkflowNodeStatus[]): WorkflowNodeStatus {
|
|
67
|
+
if (statuses.some((status) => status === "running")) return "running";
|
|
68
|
+
if (statuses.some((status) => status === "failed")) return "failed";
|
|
69
|
+
if (statuses.some((status) => status === "timed-out")) return "timed-out";
|
|
70
|
+
if (statuses.some((status) => status === "paused")) return "paused";
|
|
71
|
+
if (statuses.some((status) => status === "detached")) return "detached";
|
|
72
|
+
if (statuses.length > 0 && statuses.every((status) => status === "completed")) return "completed";
|
|
73
|
+
if (statuses.some((status) => status === "completed")) return "running";
|
|
74
|
+
return "pending";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function buildWorkflowGraphSnapshot(input: WorkflowGraphBuildInput): WorkflowGraphSnapshot {
|
|
78
|
+
const nodes: WorkflowGraphNode[] = [];
|
|
79
|
+
const phases: WorkflowGraphSnapshot["phases"] = [];
|
|
80
|
+
let flatIndex = 0;
|
|
81
|
+
let currentNodeId: string | undefined;
|
|
82
|
+
|
|
83
|
+
for (let stepIndex = 0; stepIndex < input.steps.length; stepIndex++) {
|
|
84
|
+
const step = input.steps[stepIndex]!;
|
|
85
|
+
if (isParallelStep(step)) {
|
|
86
|
+
const groupId = `step-${stepIndex}`;
|
|
87
|
+
const children: WorkflowGraphNode[] = [];
|
|
88
|
+
const childStatuses: WorkflowNodeStatus[] = [];
|
|
89
|
+
for (let taskIndex = 0; taskIndex < step.parallel.length; taskIndex++) {
|
|
90
|
+
const task = step.parallel[taskIndex]!;
|
|
91
|
+
const status = nodeStatus(input, flatIndex);
|
|
92
|
+
childStatuses.push(status);
|
|
93
|
+
const childId = `step-${stepIndex}-agent-${taskIndex}`;
|
|
94
|
+
const child: WorkflowGraphNode = {
|
|
95
|
+
id: childId,
|
|
96
|
+
kind: "agent",
|
|
97
|
+
agent: task.agent,
|
|
98
|
+
phase: task.phase,
|
|
99
|
+
label: task.label?.trim() || task.agent || `Agent ${taskIndex + 1}`,
|
|
100
|
+
status,
|
|
101
|
+
flatIndex,
|
|
102
|
+
stepIndex,
|
|
103
|
+
outputName: task.as,
|
|
104
|
+
structured: Boolean(task.outputSchema),
|
|
105
|
+
acceptanceStatus: input.results?.[flatIndex]?.acceptance?.status,
|
|
106
|
+
error: input.stepStatuses?.[flatIndex]?.error ?? input.results?.[flatIndex]?.error,
|
|
107
|
+
};
|
|
108
|
+
children.push(child);
|
|
109
|
+
pushPhase(phases, task.phase, childId);
|
|
110
|
+
if (status === "running" || input.currentFlatIndex === flatIndex) currentNodeId = childId;
|
|
111
|
+
flatIndex++;
|
|
112
|
+
}
|
|
113
|
+
const groupStatus = summarizeParallelStatuses(childStatuses);
|
|
114
|
+
if (input.currentStepIndex === stepIndex && !currentNodeId) currentNodeId = groupId;
|
|
115
|
+
nodes.push({
|
|
116
|
+
id: groupId,
|
|
117
|
+
kind: "parallel-group",
|
|
118
|
+
label: step.parallel.length === 1 ? "Parallel task" : `Parallel group (${step.parallel.length})`,
|
|
119
|
+
status: groupStatus,
|
|
120
|
+
stepIndex,
|
|
121
|
+
children,
|
|
122
|
+
});
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (isDynamicParallelStep(step)) {
|
|
127
|
+
const groupId = `step-${stepIndex}`;
|
|
128
|
+
const materialized = input.dynamicChildren?.[stepIndex] ?? [];
|
|
129
|
+
const groupOverride = input.dynamicGroupStatuses?.[stepIndex];
|
|
130
|
+
const children: WorkflowGraphNode[] = [];
|
|
131
|
+
const childStatuses: WorkflowNodeStatus[] = [];
|
|
132
|
+
for (let taskIndex = 0; taskIndex < materialized.length; taskIndex++) {
|
|
133
|
+
const task = materialized[taskIndex]!;
|
|
134
|
+
const status = nodeStatus(input, task.flatIndex);
|
|
135
|
+
childStatuses.push(status);
|
|
136
|
+
const childId = `step-${stepIndex}-item-${task.itemKey.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
|
|
137
|
+
const child: WorkflowGraphNode = {
|
|
138
|
+
id: childId,
|
|
139
|
+
kind: "agent",
|
|
140
|
+
agent: task.agent,
|
|
141
|
+
phase: step.parallel.phase ?? step.phase,
|
|
142
|
+
label: task.label?.trim() || step.parallel.label?.trim() || `${task.agent} ${task.itemKey}`,
|
|
143
|
+
status,
|
|
144
|
+
flatIndex: task.flatIndex,
|
|
145
|
+
stepIndex,
|
|
146
|
+
itemKey: task.itemKey,
|
|
147
|
+
outputName: task.outputName,
|
|
148
|
+
structured: task.structured,
|
|
149
|
+
acceptanceStatus: input.results?.[task.flatIndex]?.acceptance?.status,
|
|
150
|
+
error: input.stepStatuses?.[task.flatIndex]?.error ?? input.results?.[task.flatIndex]?.error ?? task.error,
|
|
151
|
+
};
|
|
152
|
+
children.push(child);
|
|
153
|
+
pushPhase(phases, child.phase, childId);
|
|
154
|
+
if (status === "running" || input.currentFlatIndex === task.flatIndex) currentNodeId = childId;
|
|
155
|
+
}
|
|
156
|
+
const groupStatus = groupOverride?.status ?? (children.length > 0 ? summarizeParallelStatuses(childStatuses) : (input.currentStepIndex === stepIndex ? "running" : "pending"));
|
|
157
|
+
if (input.currentStepIndex === stepIndex && !currentNodeId) currentNodeId = groupId;
|
|
158
|
+
nodes.push({
|
|
159
|
+
id: groupId,
|
|
160
|
+
kind: "dynamic-parallel-group",
|
|
161
|
+
label: step.label?.trim() || step.parallel.label?.trim() || `Dynamic fanout (${step.collect.as})`,
|
|
162
|
+
status: groupStatus,
|
|
163
|
+
stepIndex,
|
|
164
|
+
outputName: step.collect.as,
|
|
165
|
+
structured: Boolean(step.collect.outputSchema),
|
|
166
|
+
acceptanceStatus: groupOverride?.acceptance?.status,
|
|
167
|
+
error: groupOverride?.error,
|
|
168
|
+
dynamic: {
|
|
169
|
+
sourceOutput: step.expand.from.output,
|
|
170
|
+
sourcePath: step.expand.from.path,
|
|
171
|
+
itemName: step.expand.item ?? "item",
|
|
172
|
+
maxItems: step.expand.maxItems,
|
|
173
|
+
collectAs: step.collect.as,
|
|
174
|
+
},
|
|
175
|
+
children,
|
|
176
|
+
});
|
|
177
|
+
if (materialized.length > 0) flatIndex = Math.max(flatIndex, ...materialized.map((child) => child.flatIndex + 1));
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const seq = step as SequentialStep;
|
|
182
|
+
const status = nodeStatus(input, flatIndex);
|
|
183
|
+
const id = `step-${stepIndex}`;
|
|
184
|
+
nodes.push({
|
|
185
|
+
id,
|
|
186
|
+
kind: "step",
|
|
187
|
+
agent: seq.agent,
|
|
188
|
+
phase: seq.phase,
|
|
189
|
+
label: seqLabel(seq, stepIndex),
|
|
190
|
+
status,
|
|
191
|
+
flatIndex,
|
|
192
|
+
stepIndex,
|
|
193
|
+
outputName: seq.as,
|
|
194
|
+
structured: Boolean(seq.outputSchema),
|
|
195
|
+
acceptanceStatus: input.results?.[flatIndex]?.acceptance?.status,
|
|
196
|
+
error: input.stepStatuses?.[flatIndex]?.error ?? input.results?.[flatIndex]?.error,
|
|
197
|
+
});
|
|
198
|
+
pushPhase(phases, seq.phase, id);
|
|
199
|
+
if (status === "running" || input.currentFlatIndex === flatIndex || input.currentStepIndex === stepIndex) currentNodeId = id;
|
|
200
|
+
flatIndex++;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
runId: input.runId,
|
|
205
|
+
mode: input.mode ?? "chain",
|
|
206
|
+
phases,
|
|
207
|
+
nodes,
|
|
208
|
+
currentNodeId,
|
|
209
|
+
};
|
|
210
|
+
}
|
package/src/shared/formatters.ts
CHANGED
|
@@ -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
|
/**
|
|
@@ -63,7 +63,7 @@ export function buildChainSummary(
|
|
|
63
63
|
failedStep?: { index: number; error: string },
|
|
64
64
|
): string {
|
|
65
65
|
const stepNames = steps
|
|
66
|
-
.map((step) => (isParallelStep(step) ? `parallel[${step.parallel.length}]` : step.agent))
|
|
66
|
+
.map((step) => (isParallelStep(step) ? `parallel[${step.parallel.length}]` : isDynamicParallelStep(step) ? `expand:${step.parallel.agent}` : step.agent))
|
|
67
67
|
.join(" → ");
|
|
68
68
|
|
|
69
69
|
const totalDuration = results.reduce((sum, r) => sum + (r.progress?.durationMs || 0), 0);
|
package/src/shared/settings.ts
CHANGED
|
@@ -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,18 +74,48 @@ 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;
|
|
68
106
|
}
|
|
69
107
|
|
|
70
108
|
/** Parallel step: multiple agents running concurrently */
|
|
71
|
-
interface ParallelStep {
|
|
109
|
+
export interface ParallelStep {
|
|
72
110
|
parallel: ParallelTaskItem[];
|
|
73
111
|
concurrency?: number;
|
|
74
112
|
failFast?: boolean;
|
|
75
113
|
worktree?: boolean;
|
|
114
|
+
cwd?: string;
|
|
76
115
|
}
|
|
77
116
|
|
|
78
117
|
/** Union type for chain steps */
|
|
79
|
-
export type ChainStep = SequentialStep | ParallelStep;
|
|
118
|
+
export type ChainStep = SequentialStep | ParallelStep | DynamicParallelStep;
|
|
80
119
|
|
|
81
120
|
// =============================================================================
|
|
82
121
|
// Type Guards
|
|
@@ -86,11 +125,18 @@ export function isParallelStep(step: ChainStep): step is ParallelStep {
|
|
|
86
125
|
return "parallel" in step && Array.isArray((step as ParallelStep).parallel);
|
|
87
126
|
}
|
|
88
127
|
|
|
128
|
+
export function isDynamicParallelStep(step: ChainStep): step is DynamicParallelStep {
|
|
129
|
+
return "expand" in step && "collect" in step && "parallel" in step && !Array.isArray((step as { parallel?: unknown }).parallel);
|
|
130
|
+
}
|
|
131
|
+
|
|
89
132
|
/** Get all agent names in a step (single for sequential, multiple for parallel) */
|
|
90
133
|
export function getStepAgents(step: ChainStep): string[] {
|
|
91
134
|
if (isParallelStep(step)) {
|
|
92
135
|
return step.parallel.map((t) => t.agent);
|
|
93
136
|
}
|
|
137
|
+
if (isDynamicParallelStep(step)) {
|
|
138
|
+
return [step.parallel.agent];
|
|
139
|
+
}
|
|
94
140
|
return [step.agent];
|
|
95
141
|
}
|
|
96
142
|
|
|
@@ -160,6 +206,9 @@ export function resolveChainTemplates(
|
|
|
160
206
|
return "{previous}";
|
|
161
207
|
});
|
|
162
208
|
}
|
|
209
|
+
if (isDynamicParallelStep(step)) {
|
|
210
|
+
return step.parallel.task ?? "{previous}";
|
|
211
|
+
}
|
|
163
212
|
// Sequential step: existing logic
|
|
164
213
|
const seq = step as SequentialStep;
|
|
165
214
|
if (seq.task) return seq.task;
|
package/src/shared/types.ts
CHANGED
|
@@ -19,6 +19,51 @@ export interface MaxOutputConfig {
|
|
|
19
19
|
|
|
20
20
|
export type OutputMode = "inline" | "file-only";
|
|
21
21
|
|
|
22
|
+
export type JsonSchemaObject = Record<string, unknown>;
|
|
23
|
+
|
|
24
|
+
export interface ChainOutputMapEntry {
|
|
25
|
+
text: string;
|
|
26
|
+
structured?: unknown;
|
|
27
|
+
agent: string;
|
|
28
|
+
stepIndex: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type ChainOutputMap = Record<string, ChainOutputMapEntry>;
|
|
32
|
+
|
|
33
|
+
export type WorkflowNodeStatus = "pending" | "running" | "completed" | "failed" | "paused" | "detached" | "timed-out";
|
|
34
|
+
|
|
35
|
+
export interface WorkflowGraphNode {
|
|
36
|
+
id: string;
|
|
37
|
+
kind: "step" | "parallel-group" | "dynamic-parallel-group" | "agent";
|
|
38
|
+
agent?: string;
|
|
39
|
+
phase?: string;
|
|
40
|
+
label: string;
|
|
41
|
+
status: WorkflowNodeStatus;
|
|
42
|
+
flatIndex?: number;
|
|
43
|
+
stepIndex?: number;
|
|
44
|
+
children?: WorkflowGraphNode[];
|
|
45
|
+
dynamic?: {
|
|
46
|
+
sourceOutput: string;
|
|
47
|
+
sourcePath: string;
|
|
48
|
+
itemName: string;
|
|
49
|
+
maxItems?: number;
|
|
50
|
+
collectAs?: string;
|
|
51
|
+
};
|
|
52
|
+
itemKey?: string;
|
|
53
|
+
outputName?: string;
|
|
54
|
+
structured?: boolean;
|
|
55
|
+
acceptanceStatus?: AcceptanceLedgerStatus;
|
|
56
|
+
error?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface WorkflowGraphSnapshot {
|
|
60
|
+
runId: string;
|
|
61
|
+
mode: "chain" | "parallel" | "single";
|
|
62
|
+
phases: Array<{ title: string; nodeIds: string[] }>;
|
|
63
|
+
nodes: WorkflowGraphNode[];
|
|
64
|
+
currentNodeId?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
22
67
|
export interface SavedOutputReference {
|
|
23
68
|
path: string;
|
|
24
69
|
bytes: number;
|
|
@@ -97,7 +142,7 @@ export interface ControlEvent {
|
|
|
97
142
|
recentFailureSummary?: string;
|
|
98
143
|
}
|
|
99
144
|
|
|
100
|
-
export type SubagentResultStatus = "completed" | "failed" | "paused" | "detached";
|
|
145
|
+
export type SubagentResultStatus = "completed" | "failed" | "paused" | "detached" | "timed-out";
|
|
101
146
|
export type SubagentRunMode = "single" | "parallel" | "chain";
|
|
102
147
|
|
|
103
148
|
export type PublicNestedStepSummary = Pick<
|
|
@@ -194,6 +239,182 @@ export interface ModelAttempt {
|
|
|
194
239
|
usage?: Usage;
|
|
195
240
|
}
|
|
196
241
|
|
|
242
|
+
export type AcceptanceProvenanceLevel = "none" | "attested" | "checked" | "verified" | "reviewed";
|
|
243
|
+
|
|
244
|
+
export type AcceptanceEvidenceKind =
|
|
245
|
+
| "changed-files"
|
|
246
|
+
| "tests-added"
|
|
247
|
+
| "commands-run"
|
|
248
|
+
| "validation-output"
|
|
249
|
+
| "residual-risks"
|
|
250
|
+
| "no-staged-files"
|
|
251
|
+
| "diff-summary"
|
|
252
|
+
| "review-findings"
|
|
253
|
+
| "manual-notes";
|
|
254
|
+
|
|
255
|
+
export interface AcceptanceGate {
|
|
256
|
+
id: string;
|
|
257
|
+
must: string;
|
|
258
|
+
evidence?: AcceptanceEvidenceKind[];
|
|
259
|
+
severity?: "required" | "recommended";
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export interface AcceptanceVerifyCommand {
|
|
263
|
+
id: string;
|
|
264
|
+
command: string;
|
|
265
|
+
timeoutMs?: number;
|
|
266
|
+
cwd?: string;
|
|
267
|
+
env?: Record<string, string>;
|
|
268
|
+
allowFailure?: boolean;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export interface AcceptanceReviewGate {
|
|
272
|
+
agent?: string;
|
|
273
|
+
focus?: string;
|
|
274
|
+
required?: boolean;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export interface AcceptanceConfig {
|
|
278
|
+
criteria?: Array<string | AcceptanceGate>;
|
|
279
|
+
evidence?: AcceptanceEvidenceKind[];
|
|
280
|
+
verify?: AcceptanceVerifyCommand[];
|
|
281
|
+
review?: AcceptanceReviewGate;
|
|
282
|
+
stopRules?: string[];
|
|
283
|
+
maxFinalizationTurns?: number;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export type AcceptanceInput = AcceptanceConfig;
|
|
287
|
+
|
|
288
|
+
export interface ResolvedAcceptanceGate extends AcceptanceGate {
|
|
289
|
+
id: string;
|
|
290
|
+
must: string;
|
|
291
|
+
evidence: AcceptanceEvidenceKind[];
|
|
292
|
+
severity: "required" | "recommended";
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export interface ResolvedAcceptanceConfig {
|
|
296
|
+
level: AcceptanceProvenanceLevel;
|
|
297
|
+
explicit: boolean;
|
|
298
|
+
inferredReason: string[];
|
|
299
|
+
criteria: ResolvedAcceptanceGate[];
|
|
300
|
+
evidence: AcceptanceEvidenceKind[];
|
|
301
|
+
verify: AcceptanceVerifyCommand[];
|
|
302
|
+
review?: AcceptanceReviewGate;
|
|
303
|
+
stopRules: string[];
|
|
304
|
+
finalization: {
|
|
305
|
+
mode: "none" | "self-review-loop";
|
|
306
|
+
maxTurns: number;
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export interface AcceptanceReport {
|
|
311
|
+
criteriaSatisfied?: Array<{
|
|
312
|
+
id?: string;
|
|
313
|
+
status: "satisfied" | "not-satisfied" | "not-applicable";
|
|
314
|
+
evidence: string;
|
|
315
|
+
}>;
|
|
316
|
+
changedFiles?: string[];
|
|
317
|
+
testsAddedOrUpdated?: string[];
|
|
318
|
+
commandsRun?: Array<{
|
|
319
|
+
command: string;
|
|
320
|
+
result: "passed" | "failed" | "not-run";
|
|
321
|
+
summary: string;
|
|
322
|
+
}>;
|
|
323
|
+
validationOutput?: string[];
|
|
324
|
+
residualRisks?: string[];
|
|
325
|
+
noStagedFiles?: boolean;
|
|
326
|
+
diffSummary?: string;
|
|
327
|
+
reviewFindings?: string[];
|
|
328
|
+
manualNotes?: string;
|
|
329
|
+
notes?: string;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export type AcceptanceRuntimeCheckStatus = "passed" | "failed" | "not-applicable";
|
|
333
|
+
|
|
334
|
+
export interface AcceptanceRuntimeCheck {
|
|
335
|
+
id: string;
|
|
336
|
+
status: AcceptanceRuntimeCheckStatus;
|
|
337
|
+
message: string;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export interface AcceptanceVerifyResult {
|
|
341
|
+
id: string;
|
|
342
|
+
command: string;
|
|
343
|
+
cwd?: string;
|
|
344
|
+
exitCode: number | null;
|
|
345
|
+
status: "passed" | "failed" | "timed-out" | "allowed-failure";
|
|
346
|
+
stdout?: string;
|
|
347
|
+
stderr?: string;
|
|
348
|
+
durationMs: number;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export interface AcceptanceReviewResult {
|
|
352
|
+
status: "no-blockers" | "blockers" | "needs-parent-decision";
|
|
353
|
+
findings: Array<{
|
|
354
|
+
severity: "blocker" | "non-blocking";
|
|
355
|
+
file?: string;
|
|
356
|
+
issue: string;
|
|
357
|
+
rationale: string;
|
|
358
|
+
}>;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export type AcceptanceLedgerStatus =
|
|
362
|
+
| "not-required"
|
|
363
|
+
| "claimed"
|
|
364
|
+
| "attested"
|
|
365
|
+
| "checked"
|
|
366
|
+
| "verified"
|
|
367
|
+
| "reviewed"
|
|
368
|
+
| "accepted"
|
|
369
|
+
| "rejected";
|
|
370
|
+
|
|
371
|
+
export interface AcceptanceFinalizationTurn {
|
|
372
|
+
turn: number;
|
|
373
|
+
prompt: string;
|
|
374
|
+
status: AcceptanceLedgerStatus;
|
|
375
|
+
rawOutput?: string;
|
|
376
|
+
report?: AcceptanceReport;
|
|
377
|
+
parseError?: string;
|
|
378
|
+
runtimeChecks: AcceptanceRuntimeCheck[];
|
|
379
|
+
verifyRuns: AcceptanceVerifyResult[];
|
|
380
|
+
failureMessage?: string;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export interface AcceptanceFinalizationLedger {
|
|
384
|
+
mode: "self-review-loop";
|
|
385
|
+
status: "not-run" | "completed" | "failed";
|
|
386
|
+
maxTurns: number;
|
|
387
|
+
turns: AcceptanceFinalizationTurn[];
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export interface AcceptanceLedger {
|
|
391
|
+
status: AcceptanceLedgerStatus;
|
|
392
|
+
explicit: boolean;
|
|
393
|
+
effectiveAcceptance: ResolvedAcceptanceConfig;
|
|
394
|
+
inferredReason: string[];
|
|
395
|
+
criteria: ResolvedAcceptanceGate[];
|
|
396
|
+
childReport?: AcceptanceReport;
|
|
397
|
+
childReportParseError?: string;
|
|
398
|
+
initialChildReport?: AcceptanceReport;
|
|
399
|
+
initialChildReportParseError?: string;
|
|
400
|
+
runtimeChecks: AcceptanceRuntimeCheck[];
|
|
401
|
+
verifyRuns: AcceptanceVerifyResult[];
|
|
402
|
+
reviewResult?: AcceptanceReviewResult;
|
|
403
|
+
finalization?: AcceptanceFinalizationLedger;
|
|
404
|
+
parentDecision?: {
|
|
405
|
+
status: "accepted" | "rejected";
|
|
406
|
+
at: string;
|
|
407
|
+
reason?: string;
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export interface ResourceLimitExceeded {
|
|
412
|
+
kind: "maxExecutionTimeMs" | "maxTokens";
|
|
413
|
+
limit: number;
|
|
414
|
+
observed?: number;
|
|
415
|
+
message: string;
|
|
416
|
+
}
|
|
417
|
+
|
|
197
418
|
export interface SingleResult {
|
|
198
419
|
agent: string;
|
|
199
420
|
task: string;
|
|
@@ -201,6 +422,8 @@ export interface SingleResult {
|
|
|
201
422
|
detached?: boolean;
|
|
202
423
|
detachedReason?: string;
|
|
203
424
|
interrupted?: boolean;
|
|
425
|
+
timedOut?: boolean;
|
|
426
|
+
resourceLimitExceeded?: ResourceLimitExceeded;
|
|
204
427
|
messages?: Message[];
|
|
205
428
|
usage: Usage;
|
|
206
429
|
model?: string;
|
|
@@ -221,6 +444,10 @@ export interface SingleResult {
|
|
|
221
444
|
savedOutputPath?: string;
|
|
222
445
|
outputReference?: SavedOutputReference;
|
|
223
446
|
outputSaveError?: string;
|
|
447
|
+
structuredOutput?: unknown;
|
|
448
|
+
structuredOutputPath?: string;
|
|
449
|
+
structuredOutputSchemaPath?: string;
|
|
450
|
+
acceptance?: AcceptanceLedger;
|
|
224
451
|
}
|
|
225
452
|
|
|
226
453
|
export interface Details {
|
|
@@ -247,6 +474,8 @@ export interface Details {
|
|
|
247
474
|
chainAgents?: string[]; // Agent names in order, e.g., ["scout", "planner"]
|
|
248
475
|
totalSteps?: number; // Total steps in chain
|
|
249
476
|
currentStepIndex?: number; // 0-indexed current step (for running chains)
|
|
477
|
+
workflowGraph?: WorkflowGraphSnapshot;
|
|
478
|
+
outputs?: ChainOutputMap;
|
|
250
479
|
}
|
|
251
480
|
|
|
252
481
|
// ============================================================================
|
|
@@ -360,6 +589,7 @@ export interface AsyncStartedEvent {
|
|
|
360
589
|
chain?: string[];
|
|
361
590
|
chainStepCount?: number;
|
|
362
591
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
592
|
+
workflowGraph?: WorkflowGraphSnapshot;
|
|
363
593
|
nestedRoute?: NestedRouteInfo;
|
|
364
594
|
}
|
|
365
595
|
|
|
@@ -383,8 +613,13 @@ export interface AsyncStatus {
|
|
|
383
613
|
currentStep?: number;
|
|
384
614
|
chainStepCount?: number;
|
|
385
615
|
parallelGroups?: AsyncParallelGroupStatus[];
|
|
616
|
+
workflowGraph?: WorkflowGraphSnapshot;
|
|
386
617
|
steps?: Array<{
|
|
387
618
|
agent: string;
|
|
619
|
+
phase?: string;
|
|
620
|
+
label?: string;
|
|
621
|
+
outputName?: string;
|
|
622
|
+
structured?: boolean;
|
|
388
623
|
status: "pending" | "running" | "complete" | "completed" | "failed" | "paused";
|
|
389
624
|
children?: NestedRunSummary[];
|
|
390
625
|
sessionFile?: string;
|
|
@@ -409,11 +644,17 @@ export interface AsyncStatus {
|
|
|
409
644
|
attemptedModels?: string[];
|
|
410
645
|
modelAttempts?: ModelAttempt[];
|
|
411
646
|
error?: string;
|
|
647
|
+
structuredOutput?: unknown;
|
|
648
|
+
structuredOutputPath?: string;
|
|
649
|
+
structuredOutputSchemaPath?: string;
|
|
650
|
+
acceptance?: AcceptanceLedger;
|
|
651
|
+
resourceLimitExceeded?: ResourceLimitExceeded;
|
|
412
652
|
}>;
|
|
413
653
|
sessionDir?: string;
|
|
414
654
|
outputFile?: string;
|
|
415
655
|
totalTokens?: TokenUsage;
|
|
416
656
|
sessionFile?: string;
|
|
657
|
+
outputs?: ChainOutputMap;
|
|
417
658
|
}
|
|
418
659
|
|
|
419
660
|
export type AsyncJobStep = NonNullable<AsyncStatus["steps"]>[number] & {
|
|
@@ -549,6 +790,8 @@ export interface RunSyncOptions {
|
|
|
549
790
|
cwd?: string;
|
|
550
791
|
signal?: AbortSignal;
|
|
551
792
|
interruptSignal?: AbortSignal;
|
|
793
|
+
timeoutMs?: number;
|
|
794
|
+
timeoutAt?: number;
|
|
552
795
|
allowIntercomDetach?: boolean;
|
|
553
796
|
intercomEvents?: IntercomEventBus;
|
|
554
797
|
onUpdate?: (r: import("@earendil-works/pi-agent-core").AgentToolResult<Details>) => void;
|
|
@@ -567,6 +810,8 @@ export interface RunSyncOptions {
|
|
|
567
810
|
outputPath?: string;
|
|
568
811
|
outputMode?: OutputMode;
|
|
569
812
|
maxSubagentDepth?: number;
|
|
813
|
+
maxExecutionTimeMs?: number;
|
|
814
|
+
maxTokens?: number;
|
|
570
815
|
nestedRoute?: NestedRouteInfo;
|
|
571
816
|
/** Override the agent's default model (format: "provider/id" or just "id") */
|
|
572
817
|
modelOverride?: string;
|
|
@@ -576,6 +821,18 @@ export interface RunSyncOptions {
|
|
|
576
821
|
preferredModelProvider?: string;
|
|
577
822
|
/** Skills to inject (overrides agent default if provided) */
|
|
578
823
|
skills?: string[];
|
|
824
|
+
structuredOutput?: {
|
|
825
|
+
schema: JsonSchemaObject;
|
|
826
|
+
schemaPath: string;
|
|
827
|
+
outputPath: string;
|
|
828
|
+
};
|
|
829
|
+
acceptance?: AcceptanceInput;
|
|
830
|
+
acceptanceContext?: {
|
|
831
|
+
mode?: SubagentRunMode;
|
|
832
|
+
async?: boolean;
|
|
833
|
+
dynamic?: boolean;
|
|
834
|
+
dynamicGroup?: boolean;
|
|
835
|
+
};
|
|
579
836
|
}
|
|
580
837
|
|
|
581
838
|
export type IntercomBridgeMode = "off" | "fork-only" | "always";
|
|
@@ -590,6 +847,12 @@ interface TopLevelParallelConfig {
|
|
|
590
847
|
concurrency?: number;
|
|
591
848
|
}
|
|
592
849
|
|
|
850
|
+
interface ExtensionChainConfig {
|
|
851
|
+
dynamicFanout?: {
|
|
852
|
+
maxItems?: number;
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
|
|
593
856
|
export interface ExtensionConfig {
|
|
594
857
|
asyncByDefault?: boolean;
|
|
595
858
|
forceTopLevelAsync?: boolean;
|
|
@@ -597,6 +860,7 @@ export interface ExtensionConfig {
|
|
|
597
860
|
maxSubagentDepth?: number;
|
|
598
861
|
control?: ControlConfig;
|
|
599
862
|
parallel?: TopLevelParallelConfig;
|
|
863
|
+
chain?: ExtensionChainConfig;
|
|
600
864
|
worktreeSetupHook?: string;
|
|
601
865
|
worktreeSetupHookTimeoutMs?: number;
|
|
602
866
|
intercomBridge?: IntercomBridgeConfig;
|