@davidorex/pi-workflows 0.2.0 → 0.3.1
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 +7 -4
- package/README.md +12 -2
- package/agents/architecture-designer.agent.yaml +58 -0
- package/agents/architecture-inferrer.agent.yaml +60 -0
- package/agents/gap-identifier.agent.yaml +66 -0
- package/agents/handoff-writer.agent.yaml +64 -0
- package/agents/plan-creator.agent.yaml +63 -0
- package/agents/project-definer.agent.yaml +53 -0
- package/agents/project-inferrer.agent.yaml +63 -0
- package/agents/requirements-gatherer.agent.yaml +58 -0
- package/dist/agent-spec.d.ts +27 -0
- package/dist/agent-spec.d.ts.map +1 -0
- package/dist/agent-spec.js +112 -0
- package/dist/agent-spec.js.map +1 -0
- package/dist/checkpoint.d.ts +50 -0
- package/dist/checkpoint.d.ts.map +1 -0
- package/dist/checkpoint.js +109 -0
- package/dist/checkpoint.js.map +1 -0
- package/dist/completion.d.ts +18 -0
- package/dist/completion.d.ts.map +1 -0
- package/dist/completion.js +58 -0
- package/dist/completion.js.map +1 -0
- package/dist/dag.d.ts +56 -0
- package/dist/dag.d.ts.map +1 -0
- package/dist/dag.js +178 -0
- package/dist/dag.js.map +1 -0
- package/dist/dispatch.d.ts +35 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/dispatch.js +311 -0
- package/dist/dispatch.js.map +1 -0
- package/dist/expression.d.ts +64 -0
- package/dist/expression.d.ts.map +1 -0
- package/dist/expression.js +294 -0
- package/dist/expression.js.map +1 -0
- package/dist/format.d.ts +19 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +42 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +724 -0
- package/dist/index.js.map +1 -0
- package/dist/output.d.ts +12 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +59 -0
- package/dist/output.js.map +1 -0
- package/dist/state.d.ts +66 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +227 -0
- package/dist/state.js.map +1 -0
- package/dist/step-agent.d.ts +35 -0
- package/dist/step-agent.d.ts.map +1 -0
- package/dist/step-agent.js +171 -0
- package/dist/step-agent.js.map +1 -0
- package/dist/step-command.d.ts +21 -0
- package/dist/step-command.d.ts.map +1 -0
- package/dist/step-command.js +150 -0
- package/dist/step-command.js.map +1 -0
- package/dist/step-foreach.d.ts +33 -0
- package/dist/step-foreach.d.ts.map +1 -0
- package/dist/step-foreach.js +112 -0
- package/dist/step-foreach.js.map +1 -0
- package/dist/step-gate.d.ts +18 -0
- package/dist/step-gate.d.ts.map +1 -0
- package/dist/step-gate.js +137 -0
- package/dist/step-gate.js.map +1 -0
- package/dist/step-loop.d.ts +32 -0
- package/dist/step-loop.d.ts.map +1 -0
- package/dist/step-loop.js +278 -0
- package/dist/step-loop.js.map +1 -0
- package/dist/step-monitor.d.ts +56 -0
- package/dist/step-monitor.d.ts.map +1 -0
- package/dist/step-monitor.js +317 -0
- package/dist/step-monitor.js.map +1 -0
- package/dist/step-parallel.d.ts +43 -0
- package/dist/step-parallel.d.ts.map +1 -0
- package/dist/step-parallel.js +122 -0
- package/dist/step-parallel.js.map +1 -0
- package/{src/step-pause.ts → dist/step-pause.d.ts} +3 -13
- package/dist/step-pause.d.ts.map +1 -0
- package/dist/step-pause.js +22 -0
- package/dist/step-pause.js.map +1 -0
- package/dist/step-shared.d.ts +63 -0
- package/dist/step-shared.d.ts.map +1 -0
- package/dist/step-shared.js +136 -0
- package/dist/step-shared.js.map +1 -0
- package/dist/step-transform.d.ts +11 -0
- package/dist/step-transform.d.ts.map +1 -0
- package/dist/step-transform.js +41 -0
- package/dist/step-transform.js.map +1 -0
- package/dist/template.d.ts +51 -0
- package/dist/template.d.ts.map +1 -0
- package/{src/template.ts → dist/template.js} +32 -38
- package/dist/template.js.map +1 -0
- package/dist/test-helpers.d.ts +17 -0
- package/dist/test-helpers.d.ts.map +1 -0
- package/dist/test-helpers.js +46 -0
- package/dist/test-helpers.js.map +1 -0
- package/dist/tui.d.ts +47 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +143 -0
- package/dist/tui.js.map +1 -0
- package/dist/types.d.ts +165 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/workflow-discovery.d.ts +23 -0
- package/dist/workflow-discovery.d.ts.map +1 -0
- package/dist/workflow-discovery.js +103 -0
- package/dist/workflow-discovery.js.map +1 -0
- package/dist/workflow-executor.d.ts +50 -0
- package/dist/workflow-executor.d.ts.map +1 -0
- package/dist/workflow-executor.js +738 -0
- package/dist/workflow-executor.js.map +1 -0
- package/dist/workflow-sdk.d.ts +44 -0
- package/dist/workflow-sdk.d.ts.map +1 -0
- package/dist/workflow-sdk.js +457 -0
- package/dist/workflow-sdk.js.map +1 -0
- package/dist/workflow-spec.d.ts +33 -0
- package/dist/workflow-spec.d.ts.map +1 -0
- package/dist/workflow-spec.js +476 -0
- package/dist/workflow-spec.js.map +1 -0
- package/dist/workflows-dir.d.ts +3 -0
- package/dist/workflows-dir.d.ts.map +1 -0
- package/{src/workflows-dir.ts → dist/workflows-dir.js} +1 -0
- package/dist/workflows-dir.js.map +1 -0
- package/package.json +53 -38
- package/schemas/decomposition-specs.schema.json +48 -48
- package/schemas/execution-results.schema.json +48 -48
- package/schemas/investigation-findings.schema.json +43 -43
- package/schemas/pattern-analysis.schema.json +41 -41
- package/schemas/phase.schema.json +48 -0
- package/schemas/plan-breakdown.schema.json +20 -20
- package/schemas/quality-analysis.schema.json +33 -33
- package/schemas/research-findings.schema.json +42 -42
- package/schemas/structure-analysis.schema.json +40 -40
- package/schemas/synthesis.schema.json +31 -31
- package/schemas/verifier-output.schema.json +94 -94
- package/skill-narrative.md +107 -0
- package/skills/pi-workflows/SKILL.md +185 -0
- package/skills/pi-workflows/references/bundled-resources.md +87 -0
- package/templates/architecture-designer/task.md +117 -0
- package/templates/architecture-inferrer/task.md +61 -0
- package/templates/gap-identifier/task.md +103 -0
- package/templates/handoff-writer/task.md +91 -0
- package/templates/plan-creator/task.md +143 -0
- package/templates/project-definer/task.md +67 -0
- package/templates/project-inferrer/task.md +56 -0
- package/templates/requirements-gatherer/task.md +90 -0
- package/workflows/analyze-existing-project.workflow.yaml +94 -0
- package/workflows/create-handoff.workflow.yaml +66 -0
- package/workflows/create-phase.workflow.yaml +1 -1
- package/workflows/do-gap.workflow.yaml +4 -4
- package/workflows/fix-audit.workflow.yaml +1 -1
- package/workflows/gap-to-phase.workflow.yaml +1 -1
- package/workflows/init-new-project.workflow.yaml +81 -0
- package/workflows/plan-from-requirements.workflow.yaml +59 -0
- package/workflows/self-implement.workflow.yaml +3 -3
- package/workflows/typed-analysis.workflow.yaml +4 -4
- package/src/agent-spec.test.ts +0 -289
- package/src/agent-spec.ts +0 -122
- package/src/block-validation.test.ts +0 -350
- package/src/checkpoint.test.ts +0 -237
- package/src/checkpoint.ts +0 -140
- package/src/completion.test.ts +0 -143
- package/src/completion.ts +0 -68
- package/src/dag.test.ts +0 -350
- package/src/dag.ts +0 -219
- package/src/dispatch.test.ts +0 -475
- package/src/dispatch.ts +0 -353
- package/src/expression.test.ts +0 -353
- package/src/expression.ts +0 -332
- package/src/format.test.ts +0 -80
- package/src/format.ts +0 -41
- package/src/graduated-failure.test.ts +0 -556
- package/src/index.test.ts +0 -114
- package/src/index.ts +0 -516
- package/src/loop.test.ts +0 -551
- package/src/output.test.ts +0 -213
- package/src/output.ts +0 -70
- package/src/parallel-integration.test.ts +0 -175
- package/src/resume.test.ts +0 -192
- package/src/state.test.ts +0 -192
- package/src/state.ts +0 -254
- package/src/step-agent.test.ts +0 -327
- package/src/step-agent.ts +0 -178
- package/src/step-command.test.ts +0 -330
- package/src/step-command.ts +0 -132
- package/src/step-foreach.test.ts +0 -647
- package/src/step-foreach.ts +0 -148
- package/src/step-gate.test.ts +0 -185
- package/src/step-gate.ts +0 -116
- package/src/step-loop.test.ts +0 -626
- package/src/step-loop.ts +0 -323
- package/src/step-parallel.test.ts +0 -475
- package/src/step-parallel.ts +0 -168
- package/src/step-pause.test.ts +0 -30
- package/src/step-shared.test.ts +0 -355
- package/src/step-shared.ts +0 -157
- package/src/step-transform.test.ts +0 -155
- package/src/step-transform.ts +0 -47
- package/src/template-integration.test.ts +0 -72
- package/src/template.test.ts +0 -162
- package/src/test-helpers.ts +0 -51
- package/src/tui.test.ts +0 -355
- package/src/tui.ts +0 -182
- package/src/types.ts +0 -195
- package/src/verifier-schema.test.ts +0 -226
- package/src/workflow-discovery.test.ts +0 -105
- package/src/workflow-discovery.ts +0 -114
- package/src/workflow-executor.test.ts +0 -1532
- package/src/workflow-executor.ts +0 -810
- package/src/workflow-sdk.test.ts +0 -238
- package/src/workflow-sdk.ts +0 -262
- package/src/workflow-spec.test.ts +0 -576
- package/src/workflow-spec.ts +0 -471
package/src/step-loop.ts
DELETED
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Loop step executor — runs sub-steps repeatedly until a gate breaks,
|
|
3
|
-
* max attempts is reached, or a step fails.
|
|
4
|
-
*/
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import type { LoopSpec, StepResult, StepUsage, ExecutionState, AgentSpec, StepSpec, WorkflowSpec, LoopAttempt } from "./types.ts";
|
|
7
|
-
import { resolveExpressions, evaluateCondition } from "./expression.ts";
|
|
8
|
-
import { executeGate } from "./step-gate.ts";
|
|
9
|
-
import { executeTransform } from "./step-transform.ts";
|
|
10
|
-
import { zeroUsage, addUsage, buildPrompt, DEFAULT_MAX_ATTEMPTS, compileAgentSpec } from "./step-shared.ts";
|
|
11
|
-
import { persistStepOutput } from "./output.ts";
|
|
12
|
-
import type nunjucks from "nunjucks";
|
|
13
|
-
|
|
14
|
-
/** Options for executeLoop, including callback-injected dispatch to avoid circular imports. */
|
|
15
|
-
export interface LoopExecuteOptions {
|
|
16
|
-
ctx: any;
|
|
17
|
-
pi: any;
|
|
18
|
-
signal?: AbortSignal;
|
|
19
|
-
loadAgent: (name: string) => AgentSpec;
|
|
20
|
-
dispatchAgent: (
|
|
21
|
-
stepSpec: StepSpec,
|
|
22
|
-
agentSpec: AgentSpec,
|
|
23
|
-
prompt: string,
|
|
24
|
-
opts: { cwd: string; sessionLogDir: string; stepName: string; signal?: AbortSignal },
|
|
25
|
-
) => Promise<StepResult>;
|
|
26
|
-
runDir: string;
|
|
27
|
-
spec: WorkflowSpec;
|
|
28
|
-
templateEnv?: nunjucks.Environment;
|
|
29
|
-
outputPath?: string; // author-declared output path (resolved ${{ }} expressions)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Execute a loop step: runs sub-steps repeatedly until a gate breaks,
|
|
34
|
-
* max attempts is reached, or a step fails.
|
|
35
|
-
*
|
|
36
|
-
* Loop sub-steps can be agent, gate, or transform steps.
|
|
37
|
-
* Gates inside loops support onPass: "break" (stop looping on success)
|
|
38
|
-
* and onFail: "continue" (retry on failure, the default inside loops).
|
|
39
|
-
*
|
|
40
|
-
* The loop scope provides ${{ loop.iteration }}, ${{ loop.maxAttempts }},
|
|
41
|
-
* and ${{ loop.priorAttempts }} for expression resolution inside sub-steps.
|
|
42
|
-
*/
|
|
43
|
-
export async function executeLoop(
|
|
44
|
-
loopSpec: LoopSpec,
|
|
45
|
-
stepName: string,
|
|
46
|
-
state: ExecutionState,
|
|
47
|
-
options: LoopExecuteOptions,
|
|
48
|
-
): Promise<StepResult> {
|
|
49
|
-
const { ctx, signal, loadAgent, dispatchAgent, runDir } = options;
|
|
50
|
-
const startTime = Date.now();
|
|
51
|
-
|
|
52
|
-
// Resolve maxAttempts (may be a ${{ }} expression)
|
|
53
|
-
const scope = { input: state.input, steps: state.steps };
|
|
54
|
-
let maxAttempts: number;
|
|
55
|
-
if (loopSpec.attempts) {
|
|
56
|
-
const resolved = resolveExpressions(loopSpec.attempts, scope);
|
|
57
|
-
maxAttempts = Number(resolved);
|
|
58
|
-
if (isNaN(maxAttempts) || maxAttempts < 1) maxAttempts = loopSpec.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
59
|
-
} else {
|
|
60
|
-
maxAttempts = loopSpec.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const allAttempts: LoopAttempt[] = [];
|
|
64
|
-
let loopStatus: "completed" | "failed" = "failed"; // assume failure until break
|
|
65
|
-
let lastIterationSteps: Record<string, StepResult> = {};
|
|
66
|
-
|
|
67
|
-
for (let iteration = 0; iteration < maxAttempts; iteration++) {
|
|
68
|
-
// Check cancellation
|
|
69
|
-
if (signal?.aborted) break;
|
|
70
|
-
|
|
71
|
-
const iterationSteps: Record<string, StepResult> = {};
|
|
72
|
-
let shouldBreak = false;
|
|
73
|
-
let iterationFailed = false;
|
|
74
|
-
|
|
75
|
-
// Execute sub-steps sequentially
|
|
76
|
-
const subStepEntries = Object.entries(loopSpec.steps);
|
|
77
|
-
for (const [subName, subSpec] of subStepEntries) {
|
|
78
|
-
if (signal?.aborted) break;
|
|
79
|
-
|
|
80
|
-
// Build scope with loop context
|
|
81
|
-
const subScope: Record<string, unknown> = {
|
|
82
|
-
input: state.input,
|
|
83
|
-
steps: { ...state.steps, ...iterationSteps }, // see prior sub-steps in this iteration
|
|
84
|
-
loop: {
|
|
85
|
-
iteration,
|
|
86
|
-
maxAttempts,
|
|
87
|
-
priorAttempts: allAttempts,
|
|
88
|
-
},
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
// Handle `when` conditional
|
|
92
|
-
if (subSpec.when) {
|
|
93
|
-
const condExpr = subSpec.when.replace(/^\$\{\{\s*/, "").replace(/\s*\}\}$/, "");
|
|
94
|
-
if (!evaluateCondition(condExpr, subScope)) {
|
|
95
|
-
iterationSteps[subName] = {
|
|
96
|
-
step: subName,
|
|
97
|
-
agent: subSpec.agent ?? "skipped",
|
|
98
|
-
status: "skipped",
|
|
99
|
-
usage: zeroUsage(),
|
|
100
|
-
durationMs: 0,
|
|
101
|
-
};
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Execute sub-step based on type
|
|
107
|
-
let result: StepResult;
|
|
108
|
-
|
|
109
|
-
if (subSpec.gate) {
|
|
110
|
-
const resolvedCheck = String(resolveExpressions(subSpec.gate.check, subScope));
|
|
111
|
-
result = await executeGate({ ...subSpec.gate, check: resolvedCheck }, subName, { cwd: ctx.cwd, signal });
|
|
112
|
-
|
|
113
|
-
// Handle gate pass/fail with break/continue
|
|
114
|
-
const passed = result.output?.passed;
|
|
115
|
-
if (passed && subSpec.gate.onPass === "break") {
|
|
116
|
-
iterationSteps[subName] = result;
|
|
117
|
-
shouldBreak = true;
|
|
118
|
-
loopStatus = "completed";
|
|
119
|
-
break;
|
|
120
|
-
}
|
|
121
|
-
if (!passed) {
|
|
122
|
-
if (subSpec.gate.onFail === "break") {
|
|
123
|
-
iterationSteps[subName] = result;
|
|
124
|
-
shouldBreak = true;
|
|
125
|
-
break;
|
|
126
|
-
}
|
|
127
|
-
if (subSpec.gate.onFail === "fail") {
|
|
128
|
-
// Explicitly set to "fail" — stop the loop
|
|
129
|
-
result.status = "failed";
|
|
130
|
-
iterationSteps[subName] = result;
|
|
131
|
-
iterationFailed = true;
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
// Default inside loops is "continue" — proceed to next sub-step
|
|
135
|
-
// (or end iteration, triggering retry)
|
|
136
|
-
}
|
|
137
|
-
} else if (subSpec.transform) {
|
|
138
|
-
result = executeTransform(subSpec.transform, subName, subScope);
|
|
139
|
-
if (result.status === "failed") {
|
|
140
|
-
iterationSteps[subName] = result;
|
|
141
|
-
iterationFailed = true;
|
|
142
|
-
break;
|
|
143
|
-
}
|
|
144
|
-
} else if (subSpec.agent) {
|
|
145
|
-
// Resolve input, load agent, dispatch
|
|
146
|
-
let resolvedInput: unknown;
|
|
147
|
-
try {
|
|
148
|
-
resolvedInput = resolveExpressions(subSpec.input ?? {}, subScope);
|
|
149
|
-
} catch (err) {
|
|
150
|
-
iterationSteps[subName] = {
|
|
151
|
-
step: subName,
|
|
152
|
-
agent: subSpec.agent,
|
|
153
|
-
status: "failed",
|
|
154
|
-
usage: zeroUsage(),
|
|
155
|
-
durationMs: 0,
|
|
156
|
-
error: err instanceof Error ? err.message : String(err),
|
|
157
|
-
};
|
|
158
|
-
iterationFailed = true;
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
let agentSpec: AgentSpec;
|
|
163
|
-
try {
|
|
164
|
-
agentSpec = loadAgent(subSpec.agent);
|
|
165
|
-
} catch (err) {
|
|
166
|
-
iterationSteps[subName] = {
|
|
167
|
-
step: subName,
|
|
168
|
-
agent: subSpec.agent,
|
|
169
|
-
status: "failed",
|
|
170
|
-
usage: zeroUsage(),
|
|
171
|
-
durationMs: 0,
|
|
172
|
-
error: err instanceof Error ? err.message : String(err),
|
|
173
|
-
};
|
|
174
|
-
iterationFailed = true;
|
|
175
|
-
break;
|
|
176
|
-
}
|
|
177
|
-
agentSpec = compileAgentSpec(agentSpec, resolvedInput, options.templateEnv);
|
|
178
|
-
|
|
179
|
-
const prompt = buildPrompt(subSpec, agentSpec, resolvedInput, runDir, `${stepName}-${iteration}-${subName}`);
|
|
180
|
-
|
|
181
|
-
result = await dispatchAgent(subSpec, agentSpec, prompt, {
|
|
182
|
-
cwd: ctx.cwd,
|
|
183
|
-
sessionLogDir: path.join(runDir, "sessions"),
|
|
184
|
-
stepName: `${stepName}-${iteration}-${subName}`,
|
|
185
|
-
signal,
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
if (result.status === "failed") {
|
|
189
|
-
iterationSteps[subName] = result;
|
|
190
|
-
iterationFailed = true;
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
193
|
-
} else {
|
|
194
|
-
continue; // unknown step type, skip
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
iterationSteps[subName] = result!;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Record this iteration
|
|
201
|
-
lastIterationSteps = iterationSteps;
|
|
202
|
-
allAttempts.push({ iteration, steps: iterationSteps });
|
|
203
|
-
|
|
204
|
-
if (shouldBreak) break;
|
|
205
|
-
if (iterationFailed) {
|
|
206
|
-
// Agent step failed within loop — stop the loop (not just the iteration)
|
|
207
|
-
loopStatus = "failed";
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// End of iteration without break — loop continues to next iteration
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// If loop exhausted without break or failure, run onExhausted
|
|
215
|
-
if (loopStatus !== "completed" && loopSpec.onExhausted) {
|
|
216
|
-
const exhaustedScope: Record<string, unknown> = {
|
|
217
|
-
input: state.input,
|
|
218
|
-
steps: state.steps,
|
|
219
|
-
loop: {
|
|
220
|
-
iteration: allAttempts.length,
|
|
221
|
-
maxAttempts,
|
|
222
|
-
priorAttempts: allAttempts,
|
|
223
|
-
allAttempts,
|
|
224
|
-
},
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
let resolvedInput: unknown;
|
|
228
|
-
let exhaustedError: string | undefined;
|
|
229
|
-
try {
|
|
230
|
-
resolvedInput = resolveExpressions(loopSpec.onExhausted.input ?? {}, exhaustedScope);
|
|
231
|
-
} catch (err) {
|
|
232
|
-
exhaustedError = err instanceof Error ? err.message : String(err);
|
|
233
|
-
// Still proceed with onExhausted — let the agent run with empty input
|
|
234
|
-
// but record the expression error so it's visible in the result
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (loopSpec.onExhausted.agent) {
|
|
238
|
-
let agentSpec: AgentSpec;
|
|
239
|
-
try {
|
|
240
|
-
agentSpec = loadAgent(loopSpec.onExhausted.agent);
|
|
241
|
-
} catch (err) {
|
|
242
|
-
lastIterationSteps["_exhausted"] = {
|
|
243
|
-
step: `${stepName}-exhausted`,
|
|
244
|
-
agent: loopSpec.onExhausted.agent,
|
|
245
|
-
status: "failed",
|
|
246
|
-
usage: zeroUsage(),
|
|
247
|
-
durationMs: 0,
|
|
248
|
-
error: err instanceof Error ? err.message : String(err),
|
|
249
|
-
};
|
|
250
|
-
agentSpec = null as any;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (agentSpec) {
|
|
254
|
-
const prompt = buildPrompt(loopSpec.onExhausted, agentSpec, resolvedInput, runDir, `${stepName}-exhausted`);
|
|
255
|
-
|
|
256
|
-
const exhaustedResult = await dispatchAgent(loopSpec.onExhausted, agentSpec, prompt, {
|
|
257
|
-
cwd: ctx.cwd,
|
|
258
|
-
sessionLogDir: path.join(runDir, "sessions"),
|
|
259
|
-
stepName: `${stepName}-exhausted`,
|
|
260
|
-
signal,
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
if (exhaustedResult && exhaustedError) {
|
|
264
|
-
exhaustedResult.error = `Expression error in onExhausted input: ${exhaustedError}. Agent ran with empty input.`;
|
|
265
|
-
}
|
|
266
|
-
// Include exhausted result in the loop's output
|
|
267
|
-
lastIterationSteps["_exhausted"] = exhaustedResult;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (!loopSpec.onExhausted.agent && exhaustedError) {
|
|
272
|
-
lastIterationSteps["_exhausted"] = {
|
|
273
|
-
step: `${stepName}-exhausted`,
|
|
274
|
-
agent: "exhausted",
|
|
275
|
-
status: "failed",
|
|
276
|
-
usage: zeroUsage(),
|
|
277
|
-
durationMs: 0,
|
|
278
|
-
error: `Expression error in onExhausted input: ${exhaustedError}`,
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Aggregate usage across all iterations
|
|
284
|
-
const totalUsage = zeroUsage();
|
|
285
|
-
for (const attempt of allAttempts) {
|
|
286
|
-
for (const result of Object.values(attempt.steps)) {
|
|
287
|
-
addUsage(totalUsage, result.usage);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const loopOutput = {
|
|
292
|
-
iterations: allAttempts.length,
|
|
293
|
-
maxAttempts,
|
|
294
|
-
attempts: allAttempts,
|
|
295
|
-
lastIteration: lastIterationSteps,
|
|
296
|
-
};
|
|
297
|
-
const loopTextOutput = `Loop '${stepName}': ${allAttempts.length}/${maxAttempts} iterations, status: ${loopStatus}`;
|
|
298
|
-
|
|
299
|
-
const result: StepResult = {
|
|
300
|
-
step: stepName,
|
|
301
|
-
agent: "loop",
|
|
302
|
-
status: loopStatus,
|
|
303
|
-
output: loopOutput,
|
|
304
|
-
textOutput: loopTextOutput,
|
|
305
|
-
usage: totalUsage,
|
|
306
|
-
durationMs: Date.now() - startTime,
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
// Propagate truncation from any sub-step
|
|
310
|
-
const anyTruncated = allAttempts.some(a =>
|
|
311
|
-
Object.values(a.steps).some(s => s.truncated)
|
|
312
|
-
);
|
|
313
|
-
if (anyTruncated) {
|
|
314
|
-
result.truncated = true;
|
|
315
|
-
result.warnings = [
|
|
316
|
-
...(result.warnings ?? []),
|
|
317
|
-
"One or more loop sub-steps had truncated stdout — usage totals may undercount.",
|
|
318
|
-
];
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
result.outputPath = persistStepOutput(runDir, stepName, loopOutput, loopTextOutput, options.outputPath);
|
|
322
|
-
return result;
|
|
323
|
-
}
|