@davidorex/pi-workflows 0.1.0 → 0.3.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 +7 -4
- package/README.md +33 -7
- 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 +172 -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 +675 -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 +156 -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 +57 -0
- package/dist/step-shared.d.ts.map +1 -0
- package/dist/step-shared.js +125 -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 +164 -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 +726 -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 +438 -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 +464 -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/dist/workflows-dir.js +3 -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 +84 -0
- package/skills/pi-workflows/SKILL.md +247 -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 +74 -0
- package/workflows/create-handoff.workflow.yaml +58 -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 +55 -0
- package/workflows/plan-from-requirements.workflow.yaml +51 -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 -139
- 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 -473
- 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 -488
- package/src/loop.test.ts +0 -549
- 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 -253
- 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 -113
- package/src/workflow-executor.test.ts +0 -1530
- 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
|
@@ -0,0 +1,278 @@
|
|
|
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 { evaluateCondition, resolveExpressions } from "./expression.js";
|
|
7
|
+
import { persistStepOutput } from "./output.js";
|
|
8
|
+
import { executeGate } from "./step-gate.js";
|
|
9
|
+
import { addUsage, buildPrompt, compileAgentSpec, DEFAULT_MAX_ATTEMPTS, zeroUsage } from "./step-shared.js";
|
|
10
|
+
import { executeTransform } from "./step-transform.js";
|
|
11
|
+
/**
|
|
12
|
+
* Execute a loop step: runs sub-steps repeatedly until a gate breaks,
|
|
13
|
+
* max attempts is reached, or a step fails.
|
|
14
|
+
*
|
|
15
|
+
* Loop sub-steps can be agent, gate, or transform steps.
|
|
16
|
+
* Gates inside loops support onPass: "break" (stop looping on success)
|
|
17
|
+
* and onFail: "continue" (retry on failure, the default inside loops).
|
|
18
|
+
*
|
|
19
|
+
* The loop scope provides ${{ loop.iteration }}, ${{ loop.maxAttempts }},
|
|
20
|
+
* and ${{ loop.priorAttempts }} for expression resolution inside sub-steps.
|
|
21
|
+
*/
|
|
22
|
+
export async function executeLoop(loopSpec, stepName, state, options) {
|
|
23
|
+
const { ctx, signal, loadAgent, dispatchAgent, runDir } = options;
|
|
24
|
+
const startTime = Date.now();
|
|
25
|
+
// Resolve maxAttempts (may be a ${{ }} expression)
|
|
26
|
+
const scope = { input: state.input, steps: state.steps };
|
|
27
|
+
let maxAttempts;
|
|
28
|
+
if (loopSpec.attempts) {
|
|
29
|
+
const resolved = resolveExpressions(loopSpec.attempts, scope);
|
|
30
|
+
maxAttempts = Number(resolved);
|
|
31
|
+
if (isNaN(maxAttempts) || maxAttempts < 1)
|
|
32
|
+
maxAttempts = loopSpec.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
maxAttempts = loopSpec.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
36
|
+
}
|
|
37
|
+
const allAttempts = [];
|
|
38
|
+
let loopStatus = "failed"; // assume failure until break
|
|
39
|
+
let lastIterationSteps = {};
|
|
40
|
+
for (let iteration = 0; iteration < maxAttempts; iteration++) {
|
|
41
|
+
// Check cancellation
|
|
42
|
+
if (signal?.aborted)
|
|
43
|
+
break;
|
|
44
|
+
const iterationSteps = {};
|
|
45
|
+
let shouldBreak = false;
|
|
46
|
+
let iterationFailed = false;
|
|
47
|
+
// Execute sub-steps sequentially
|
|
48
|
+
const subStepEntries = Object.entries(loopSpec.steps);
|
|
49
|
+
for (const [subName, subSpec] of subStepEntries) {
|
|
50
|
+
if (signal?.aborted)
|
|
51
|
+
break;
|
|
52
|
+
// Build scope with loop context
|
|
53
|
+
const subScope = {
|
|
54
|
+
input: state.input,
|
|
55
|
+
steps: { ...state.steps, ...iterationSteps }, // see prior sub-steps in this iteration
|
|
56
|
+
loop: {
|
|
57
|
+
iteration,
|
|
58
|
+
maxAttempts,
|
|
59
|
+
priorAttempts: allAttempts,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
// Handle `when` conditional
|
|
63
|
+
if (subSpec.when) {
|
|
64
|
+
const condExpr = subSpec.when.replace(/^\$\{\{\s*/, "").replace(/\s*\}\}$/, "");
|
|
65
|
+
if (!evaluateCondition(condExpr, subScope)) {
|
|
66
|
+
iterationSteps[subName] = {
|
|
67
|
+
step: subName,
|
|
68
|
+
agent: subSpec.agent ?? "skipped",
|
|
69
|
+
status: "skipped",
|
|
70
|
+
usage: zeroUsage(),
|
|
71
|
+
durationMs: 0,
|
|
72
|
+
};
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Execute sub-step based on type
|
|
77
|
+
let result;
|
|
78
|
+
if (subSpec.gate) {
|
|
79
|
+
const resolvedCheck = String(resolveExpressions(subSpec.gate.check, subScope));
|
|
80
|
+
result = await executeGate({ ...subSpec.gate, check: resolvedCheck }, subName, { cwd: ctx.cwd, signal });
|
|
81
|
+
// Handle gate pass/fail with break/continue
|
|
82
|
+
const passed = result.output?.passed;
|
|
83
|
+
if (passed && subSpec.gate.onPass === "break") {
|
|
84
|
+
iterationSteps[subName] = result;
|
|
85
|
+
shouldBreak = true;
|
|
86
|
+
loopStatus = "completed";
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
if (!passed) {
|
|
90
|
+
if (subSpec.gate.onFail === "break") {
|
|
91
|
+
iterationSteps[subName] = result;
|
|
92
|
+
shouldBreak = true;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
if (subSpec.gate.onFail === "fail") {
|
|
96
|
+
// Explicitly set to "fail" — stop the loop
|
|
97
|
+
result.status = "failed";
|
|
98
|
+
iterationSteps[subName] = result;
|
|
99
|
+
iterationFailed = true;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
// Default inside loops is "continue" — proceed to next sub-step
|
|
103
|
+
// (or end iteration, triggering retry)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else if (subSpec.transform) {
|
|
107
|
+
result = executeTransform(subSpec.transform, subName, subScope);
|
|
108
|
+
if (result.status === "failed") {
|
|
109
|
+
iterationSteps[subName] = result;
|
|
110
|
+
iterationFailed = true;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else if (subSpec.agent) {
|
|
115
|
+
// Resolve input, load agent, dispatch
|
|
116
|
+
let resolvedInput;
|
|
117
|
+
try {
|
|
118
|
+
resolvedInput = resolveExpressions(subSpec.input ?? {}, subScope);
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
iterationSteps[subName] = {
|
|
122
|
+
step: subName,
|
|
123
|
+
agent: subSpec.agent,
|
|
124
|
+
status: "failed",
|
|
125
|
+
usage: zeroUsage(),
|
|
126
|
+
durationMs: 0,
|
|
127
|
+
error: err instanceof Error ? err.message : String(err),
|
|
128
|
+
};
|
|
129
|
+
iterationFailed = true;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
let agentSpec;
|
|
133
|
+
try {
|
|
134
|
+
agentSpec = loadAgent(subSpec.agent);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
iterationSteps[subName] = {
|
|
138
|
+
step: subName,
|
|
139
|
+
agent: subSpec.agent,
|
|
140
|
+
status: "failed",
|
|
141
|
+
usage: zeroUsage(),
|
|
142
|
+
durationMs: 0,
|
|
143
|
+
error: err instanceof Error ? err.message : String(err),
|
|
144
|
+
};
|
|
145
|
+
iterationFailed = true;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
agentSpec = compileAgentSpec(agentSpec, resolvedInput, options.templateEnv);
|
|
149
|
+
const prompt = buildPrompt(subSpec, agentSpec, resolvedInput, runDir, `${stepName}-${iteration}-${subName}`);
|
|
150
|
+
result = await dispatchAgent(subSpec, agentSpec, prompt, {
|
|
151
|
+
cwd: ctx.cwd,
|
|
152
|
+
sessionLogDir: path.join(runDir, "sessions"),
|
|
153
|
+
stepName: `${stepName}-${iteration}-${subName}`,
|
|
154
|
+
signal,
|
|
155
|
+
});
|
|
156
|
+
if (result.status === "failed") {
|
|
157
|
+
iterationSteps[subName] = result;
|
|
158
|
+
iterationFailed = true;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
continue; // unknown step type, skip
|
|
164
|
+
}
|
|
165
|
+
iterationSteps[subName] = result;
|
|
166
|
+
}
|
|
167
|
+
// Record this iteration
|
|
168
|
+
lastIterationSteps = iterationSteps;
|
|
169
|
+
allAttempts.push({ iteration, steps: iterationSteps });
|
|
170
|
+
if (shouldBreak)
|
|
171
|
+
break;
|
|
172
|
+
if (iterationFailed) {
|
|
173
|
+
// Agent step failed within loop — stop the loop (not just the iteration)
|
|
174
|
+
loopStatus = "failed";
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
// End of iteration without break — loop continues to next iteration
|
|
178
|
+
}
|
|
179
|
+
// If loop exhausted without break or failure, run onExhausted
|
|
180
|
+
if (loopStatus !== "completed" && loopSpec.onExhausted) {
|
|
181
|
+
const exhaustedScope = {
|
|
182
|
+
input: state.input,
|
|
183
|
+
steps: state.steps,
|
|
184
|
+
loop: {
|
|
185
|
+
iteration: allAttempts.length,
|
|
186
|
+
maxAttempts,
|
|
187
|
+
priorAttempts: allAttempts,
|
|
188
|
+
allAttempts,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
let resolvedInput;
|
|
192
|
+
let exhaustedError;
|
|
193
|
+
try {
|
|
194
|
+
resolvedInput = resolveExpressions(loopSpec.onExhausted.input ?? {}, exhaustedScope);
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
exhaustedError = err instanceof Error ? err.message : String(err);
|
|
198
|
+
// Still proceed with onExhausted — let the agent run with empty input
|
|
199
|
+
// but record the expression error so it's visible in the result
|
|
200
|
+
}
|
|
201
|
+
if (loopSpec.onExhausted.agent) {
|
|
202
|
+
let agentSpec;
|
|
203
|
+
try {
|
|
204
|
+
agentSpec = loadAgent(loopSpec.onExhausted.agent);
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
lastIterationSteps["_exhausted"] = {
|
|
208
|
+
step: `${stepName}-exhausted`,
|
|
209
|
+
agent: loopSpec.onExhausted.agent,
|
|
210
|
+
status: "failed",
|
|
211
|
+
usage: zeroUsage(),
|
|
212
|
+
durationMs: 0,
|
|
213
|
+
error: err instanceof Error ? err.message : String(err),
|
|
214
|
+
};
|
|
215
|
+
agentSpec = null;
|
|
216
|
+
}
|
|
217
|
+
if (agentSpec) {
|
|
218
|
+
const prompt = buildPrompt(loopSpec.onExhausted, agentSpec, resolvedInput, runDir, `${stepName}-exhausted`);
|
|
219
|
+
const exhaustedResult = await dispatchAgent(loopSpec.onExhausted, agentSpec, prompt, {
|
|
220
|
+
cwd: ctx.cwd,
|
|
221
|
+
sessionLogDir: path.join(runDir, "sessions"),
|
|
222
|
+
stepName: `${stepName}-exhausted`,
|
|
223
|
+
signal,
|
|
224
|
+
});
|
|
225
|
+
if (exhaustedResult && exhaustedError) {
|
|
226
|
+
exhaustedResult.error = `Expression error in onExhausted input: ${exhaustedError}. Agent ran with empty input.`;
|
|
227
|
+
}
|
|
228
|
+
// Include exhausted result in the loop's output
|
|
229
|
+
lastIterationSteps["_exhausted"] = exhaustedResult;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (!loopSpec.onExhausted.agent && exhaustedError) {
|
|
233
|
+
lastIterationSteps["_exhausted"] = {
|
|
234
|
+
step: `${stepName}-exhausted`,
|
|
235
|
+
agent: "exhausted",
|
|
236
|
+
status: "failed",
|
|
237
|
+
usage: zeroUsage(),
|
|
238
|
+
durationMs: 0,
|
|
239
|
+
error: `Expression error in onExhausted input: ${exhaustedError}`,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Aggregate usage across all iterations
|
|
244
|
+
const totalUsage = zeroUsage();
|
|
245
|
+
for (const attempt of allAttempts) {
|
|
246
|
+
for (const result of Object.values(attempt.steps)) {
|
|
247
|
+
addUsage(totalUsage, result.usage);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
const loopOutput = {
|
|
251
|
+
iterations: allAttempts.length,
|
|
252
|
+
maxAttempts,
|
|
253
|
+
attempts: allAttempts,
|
|
254
|
+
lastIteration: lastIterationSteps,
|
|
255
|
+
};
|
|
256
|
+
const loopTextOutput = `Loop '${stepName}': ${allAttempts.length}/${maxAttempts} iterations, status: ${loopStatus}`;
|
|
257
|
+
const result = {
|
|
258
|
+
step: stepName,
|
|
259
|
+
agent: "loop",
|
|
260
|
+
status: loopStatus,
|
|
261
|
+
output: loopOutput,
|
|
262
|
+
textOutput: loopTextOutput,
|
|
263
|
+
usage: totalUsage,
|
|
264
|
+
durationMs: Date.now() - startTime,
|
|
265
|
+
};
|
|
266
|
+
// Propagate truncation from any sub-step
|
|
267
|
+
const anyTruncated = allAttempts.some((a) => Object.values(a.steps).some((s) => s.truncated));
|
|
268
|
+
if (anyTruncated) {
|
|
269
|
+
result.truncated = true;
|
|
270
|
+
result.warnings = [
|
|
271
|
+
...(result.warnings ?? []),
|
|
272
|
+
"One or more loop sub-steps had truncated stdout — usage totals may undercount.",
|
|
273
|
+
];
|
|
274
|
+
}
|
|
275
|
+
result.outputPath = persistStepOutput(runDir, stepName, loopOutput, loopTextOutput, options.outputPath);
|
|
276
|
+
return result;
|
|
277
|
+
}
|
|
278
|
+
//# sourceMappingURL=step-loop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-loop.js","sourceRoot":"","sources":["../src/step-loop.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monitor step executor — runs a monitor's classification as a workflow
|
|
3
|
+
* verification gate. Lightweight: reads monitor spec, loads patterns,
|
|
4
|
+
* renders prompt (Nunjucks or inline), calls LLM, parses verdict.
|
|
5
|
+
*
|
|
6
|
+
* Does NOT depend on pi-behavior-monitors — implements just enough to
|
|
7
|
+
* classify. No event handling, steering, ceiling, or escalation.
|
|
8
|
+
*/
|
|
9
|
+
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
10
|
+
import type { StepResult } from "./types.js";
|
|
11
|
+
interface MonitorSpec {
|
|
12
|
+
name: string;
|
|
13
|
+
classify: {
|
|
14
|
+
model: string;
|
|
15
|
+
context: string[];
|
|
16
|
+
prompt: string;
|
|
17
|
+
promptTemplate?: string;
|
|
18
|
+
};
|
|
19
|
+
patterns: {
|
|
20
|
+
path: string;
|
|
21
|
+
};
|
|
22
|
+
instructions?: {
|
|
23
|
+
path: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Find a monitor spec by name. Searches:
|
|
28
|
+
* 1. .pi/monitors/ (project)
|
|
29
|
+
* 2. ~/.pi/agent/monitors/ (user)
|
|
30
|
+
* 3. pi-behavior-monitors examples (if installed as peer)
|
|
31
|
+
*/
|
|
32
|
+
export declare function findMonitorSpec(monitorName: string, cwd: string): {
|
|
33
|
+
spec: MonitorSpec;
|
|
34
|
+
dir: string;
|
|
35
|
+
} | null;
|
|
36
|
+
export interface MonitorStepOptions {
|
|
37
|
+
cwd: string;
|
|
38
|
+
ctx: ExtensionContext;
|
|
39
|
+
signal?: AbortSignal;
|
|
40
|
+
runDir?: string;
|
|
41
|
+
outputPath?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Execute a monitor step: discover monitor, render prompt, classify, return verdict.
|
|
45
|
+
*
|
|
46
|
+
* - CLEAN → status: "completed", output: { verdict: "clean" }
|
|
47
|
+
* - FLAG → status: "failed", output: { verdict: "flag", description }
|
|
48
|
+
* - NEW → status: "failed", output: { verdict: "new", pattern, description }
|
|
49
|
+
*/
|
|
50
|
+
export declare function executeMonitor(monitorName: string, stepName: string, input: Record<string, unknown>, options: MonitorStepOptions): Promise<StepResult>;
|
|
51
|
+
/**
|
|
52
|
+
* List all discoverable monitor names for validation.
|
|
53
|
+
*/
|
|
54
|
+
export declare function availableMonitors(cwd: string): string[];
|
|
55
|
+
export {};
|
|
56
|
+
//# sourceMappingURL=step-monitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-monitor.d.ts","sourceRoot":"","sources":["../src/step-monitor.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAItE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C,UAAU,WAAW;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE;QACT,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,EAAE,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,cAAc,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,QAAQ,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;KACb,CAAC;IACF,YAAY,CAAC,EAAE;QACd,IAAI,EAAE,MAAM,CAAC;KACb,CAAC;CACF;AAoBD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAwC3G;AAoHD,MAAM,WAAW,kBAAkB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,gBAAgB,CAAC;IACtB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CACnC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,EAAE,kBAAkB,GACzB,OAAO,CAAC,UAAU,CAAC,CAuHrB;AAYD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CA0BvD"}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Monitor step executor — runs a monitor's classification as a workflow
|
|
3
|
+
* verification gate. Lightweight: reads monitor spec, loads patterns,
|
|
4
|
+
* renders prompt (Nunjucks or inline), calls LLM, parses verdict.
|
|
5
|
+
*
|
|
6
|
+
* Does NOT depend on pi-behavior-monitors — implements just enough to
|
|
7
|
+
* classify. No event handling, steering, ceiling, or escalation.
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import * as os from "node:os";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import { complete } from "@mariozechner/pi-ai";
|
|
13
|
+
import nunjucks from "nunjucks";
|
|
14
|
+
import { persistStepOutput } from "./output.js";
|
|
15
|
+
import { zeroUsage } from "./step-shared.js";
|
|
16
|
+
// ── Discovery ────────────────────────────────────────────────────────────────
|
|
17
|
+
/**
|
|
18
|
+
* Find a monitor spec by name. Searches:
|
|
19
|
+
* 1. .pi/monitors/ (project)
|
|
20
|
+
* 2. ~/.pi/agent/monitors/ (user)
|
|
21
|
+
* 3. pi-behavior-monitors examples (if installed as peer)
|
|
22
|
+
*/
|
|
23
|
+
export function findMonitorSpec(monitorName, cwd) {
|
|
24
|
+
const searchDirs = [
|
|
25
|
+
path.join(cwd, ".pi", "monitors"),
|
|
26
|
+
path.join(os.homedir(), ".pi", "agent", "monitors"),
|
|
27
|
+
];
|
|
28
|
+
// Also check pi-behavior-monitors examples via node_modules
|
|
29
|
+
try {
|
|
30
|
+
const bmPath = require.resolve("@davidorex/pi-behavior-monitors/package.json", { paths: [cwd] });
|
|
31
|
+
searchDirs.push(path.join(path.dirname(bmPath), "examples"));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// pi-behavior-monitors not installed — skip
|
|
35
|
+
}
|
|
36
|
+
for (const dir of searchDirs) {
|
|
37
|
+
const filePath = path.join(dir, `${monitorName}.monitor.json`);
|
|
38
|
+
try {
|
|
39
|
+
const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
40
|
+
if (raw.name && raw.classify && raw.patterns?.path) {
|
|
41
|
+
return {
|
|
42
|
+
spec: {
|
|
43
|
+
name: raw.name,
|
|
44
|
+
classify: {
|
|
45
|
+
model: raw.classify.model ?? "claude-sonnet-4-20250514",
|
|
46
|
+
context: Array.isArray(raw.classify.context) ? raw.classify.context : [],
|
|
47
|
+
prompt: raw.classify.prompt ?? "",
|
|
48
|
+
promptTemplate: typeof raw.classify.promptTemplate === "string" ? raw.classify.promptTemplate : undefined,
|
|
49
|
+
},
|
|
50
|
+
patterns: { path: raw.patterns.path },
|
|
51
|
+
instructions: raw.instructions?.path ? { path: raw.instructions.path } : undefined,
|
|
52
|
+
},
|
|
53
|
+
dir,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// file doesn't exist or is invalid — try next
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
// ── Pattern & instruction loading ────────────────────────────────────────────
|
|
64
|
+
function loadPatterns(dir, patternsPath) {
|
|
65
|
+
try {
|
|
66
|
+
const resolved = path.resolve(dir, patternsPath);
|
|
67
|
+
return JSON.parse(fs.readFileSync(resolved, "utf-8"));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function loadInstructions(dir, instructionsPath) {
|
|
74
|
+
if (!instructionsPath)
|
|
75
|
+
return [];
|
|
76
|
+
try {
|
|
77
|
+
const resolved = path.resolve(dir, instructionsPath);
|
|
78
|
+
return JSON.parse(fs.readFileSync(resolved, "utf-8"));
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function formatPatterns(patterns) {
|
|
85
|
+
return patterns.map((p, i) => `${i + 1}. [${p.severity ?? "warning"}] ${p.description}`).join("\n");
|
|
86
|
+
}
|
|
87
|
+
function formatInstructions(instructions) {
|
|
88
|
+
if (instructions.length === 0)
|
|
89
|
+
return "";
|
|
90
|
+
const lines = instructions.map((i) => `- ${i.text}`).join("\n");
|
|
91
|
+
return `\nOperating instructions from the user (follow these strictly):\n${lines}\n`;
|
|
92
|
+
}
|
|
93
|
+
// ── Template rendering ───────────────────────────────────────────────────────
|
|
94
|
+
function createTemplateEnv(cwd) {
|
|
95
|
+
const searchPaths = [];
|
|
96
|
+
const projectDir = path.join(cwd, ".pi", "monitors");
|
|
97
|
+
const userDir = path.join(os.homedir(), ".pi", "agent", "monitors");
|
|
98
|
+
if (fs.existsSync(projectDir))
|
|
99
|
+
searchPaths.push(projectDir);
|
|
100
|
+
if (fs.existsSync(userDir))
|
|
101
|
+
searchPaths.push(userDir);
|
|
102
|
+
// pi-behavior-monitors examples
|
|
103
|
+
try {
|
|
104
|
+
const bmPath = require.resolve("@davidorex/pi-behavior-monitors/package.json", { paths: [cwd] });
|
|
105
|
+
const exDir = path.join(path.dirname(bmPath), "examples");
|
|
106
|
+
if (fs.existsSync(exDir))
|
|
107
|
+
searchPaths.push(exDir);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
/* not installed */
|
|
111
|
+
}
|
|
112
|
+
const loader = searchPaths.length > 0 ? new nunjucks.FileSystemLoader(searchPaths) : undefined;
|
|
113
|
+
return new nunjucks.Environment(loader, { autoescape: false, throwOnUndefined: false });
|
|
114
|
+
}
|
|
115
|
+
function renderPrompt(spec, dir, input, templateEnv) {
|
|
116
|
+
const patterns = loadPatterns(dir, spec.patterns.path);
|
|
117
|
+
if (patterns.length === 0)
|
|
118
|
+
return null;
|
|
119
|
+
const instructions = loadInstructions(dir, spec.instructions?.path);
|
|
120
|
+
// Build context: patterns, instructions, plus any input fields
|
|
121
|
+
const context = {
|
|
122
|
+
patterns: formatPatterns(patterns),
|
|
123
|
+
instructions: formatInstructions(instructions),
|
|
124
|
+
iteration: 0,
|
|
125
|
+
...input,
|
|
126
|
+
};
|
|
127
|
+
if (spec.classify.promptTemplate) {
|
|
128
|
+
try {
|
|
129
|
+
return templateEnv.render(spec.classify.promptTemplate, context);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// Fall through to inline prompt
|
|
133
|
+
if (!spec.classify.prompt)
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (!spec.classify.prompt)
|
|
138
|
+
return null;
|
|
139
|
+
return spec.classify.prompt.replace(/\{(\w+)\}/g, (match, key) => {
|
|
140
|
+
return String(context[key] ?? match);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
// ── Verdict parsing ──────────────────────────────────────────────────────────
|
|
144
|
+
function parseVerdict(raw) {
|
|
145
|
+
const text = raw.trim();
|
|
146
|
+
if (text.startsWith("CLEAN"))
|
|
147
|
+
return { verdict: "clean" };
|
|
148
|
+
if (text.startsWith("NEW:")) {
|
|
149
|
+
const rest = text.slice(4);
|
|
150
|
+
const pipe = rest.indexOf("|");
|
|
151
|
+
if (pipe !== -1) {
|
|
152
|
+
return { verdict: "new", pattern: rest.slice(0, pipe).trim(), description: rest.slice(pipe + 1).trim() };
|
|
153
|
+
}
|
|
154
|
+
return { verdict: "new", pattern: rest.trim(), description: rest.trim() };
|
|
155
|
+
}
|
|
156
|
+
if (text.startsWith("FLAG:"))
|
|
157
|
+
return { verdict: "flag", description: text.slice(5).trim() };
|
|
158
|
+
return { verdict: "clean" };
|
|
159
|
+
}
|
|
160
|
+
function extractText(parts) {
|
|
161
|
+
return parts
|
|
162
|
+
.filter((b) => b.type === "text")
|
|
163
|
+
.map((b) => b.text)
|
|
164
|
+
.join("");
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Execute a monitor step: discover monitor, render prompt, classify, return verdict.
|
|
168
|
+
*
|
|
169
|
+
* - CLEAN → status: "completed", output: { verdict: "clean" }
|
|
170
|
+
* - FLAG → status: "failed", output: { verdict: "flag", description }
|
|
171
|
+
* - NEW → status: "failed", output: { verdict: "new", pattern, description }
|
|
172
|
+
*/
|
|
173
|
+
export async function executeMonitor(monitorName, stepName, input, options) {
|
|
174
|
+
const startTime = Date.now();
|
|
175
|
+
// 1. Discover monitor
|
|
176
|
+
const found = findMonitorSpec(monitorName, options.cwd);
|
|
177
|
+
if (!found) {
|
|
178
|
+
return {
|
|
179
|
+
step: stepName,
|
|
180
|
+
agent: `monitor:${monitorName}`,
|
|
181
|
+
status: "failed",
|
|
182
|
+
usage: zeroUsage(),
|
|
183
|
+
durationMs: Date.now() - startTime,
|
|
184
|
+
error: `Monitor '${monitorName}' not found`,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const { spec, dir } = found;
|
|
188
|
+
// 2. Render prompt
|
|
189
|
+
const templateEnv = createTemplateEnv(options.cwd);
|
|
190
|
+
const prompt = renderPrompt(spec, dir, input, templateEnv);
|
|
191
|
+
if (!prompt) {
|
|
192
|
+
return {
|
|
193
|
+
step: stepName,
|
|
194
|
+
agent: `monitor:${monitorName}`,
|
|
195
|
+
status: "completed",
|
|
196
|
+
output: { verdict: "clean", note: "No patterns loaded — nothing to classify" },
|
|
197
|
+
textOutput: "CLEAN (no patterns)",
|
|
198
|
+
usage: zeroUsage(),
|
|
199
|
+
durationMs: Date.now() - startTime,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
// 3. Resolve model
|
|
203
|
+
const { provider, modelId } = parseModelSpec(spec.classify.model);
|
|
204
|
+
const model = options.ctx.modelRegistry.find(provider, modelId);
|
|
205
|
+
if (!model) {
|
|
206
|
+
return {
|
|
207
|
+
step: stepName,
|
|
208
|
+
agent: `monitor:${monitorName}`,
|
|
209
|
+
status: "failed",
|
|
210
|
+
usage: zeroUsage(),
|
|
211
|
+
durationMs: Date.now() - startTime,
|
|
212
|
+
error: `Model '${spec.classify.model}' not found in registry`,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const apiKey = await options.ctx.modelRegistry.getApiKey(model);
|
|
216
|
+
if (!apiKey) {
|
|
217
|
+
return {
|
|
218
|
+
step: stepName,
|
|
219
|
+
agent: `monitor:${monitorName}`,
|
|
220
|
+
status: "failed",
|
|
221
|
+
usage: zeroUsage(),
|
|
222
|
+
durationMs: Date.now() - startTime,
|
|
223
|
+
error: `No API key for model '${spec.classify.model}'`,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
// 4. Classify
|
|
227
|
+
let response;
|
|
228
|
+
try {
|
|
229
|
+
response = await complete(model, { messages: [{ role: "user", content: [{ type: "text", text: prompt }], timestamp: Date.now() }] }, { apiKey, maxTokens: 150, signal: options.signal });
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
if (options.signal?.aborted) {
|
|
233
|
+
return {
|
|
234
|
+
step: stepName,
|
|
235
|
+
agent: `monitor:${monitorName}`,
|
|
236
|
+
status: "failed",
|
|
237
|
+
usage: zeroUsage(),
|
|
238
|
+
durationMs: Date.now() - startTime,
|
|
239
|
+
error: "Monitor classification cancelled",
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
243
|
+
return {
|
|
244
|
+
step: stepName,
|
|
245
|
+
agent: `monitor:${monitorName}`,
|
|
246
|
+
status: "failed",
|
|
247
|
+
usage: zeroUsage(),
|
|
248
|
+
durationMs: Date.now() - startTime,
|
|
249
|
+
error: `Classification failed: ${msg}`,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
// 5. Parse verdict
|
|
253
|
+
const rawText = extractText(response.content);
|
|
254
|
+
const verdict = parseVerdict(rawText);
|
|
255
|
+
const durationMs = Date.now() - startTime;
|
|
256
|
+
// Estimate usage from response
|
|
257
|
+
const usage = zeroUsage();
|
|
258
|
+
// Token counts not directly available from complete() — leave as zero
|
|
259
|
+
const output = {
|
|
260
|
+
verdict: verdict.verdict,
|
|
261
|
+
...(verdict.description ? { description: verdict.description } : {}),
|
|
262
|
+
...(verdict.pattern ? { pattern: verdict.pattern } : {}),
|
|
263
|
+
};
|
|
264
|
+
const result = {
|
|
265
|
+
step: stepName,
|
|
266
|
+
agent: `monitor:${monitorName}`,
|
|
267
|
+
status: verdict.verdict === "clean" ? "completed" : "failed",
|
|
268
|
+
output,
|
|
269
|
+
textOutput: rawText.trim(),
|
|
270
|
+
usage,
|
|
271
|
+
durationMs,
|
|
272
|
+
...(verdict.verdict !== "clean" ? { error: verdict.description ?? "Monitor flagged an issue" } : {}),
|
|
273
|
+
};
|
|
274
|
+
if (options.runDir) {
|
|
275
|
+
result.outputPath = persistStepOutput(options.runDir, stepName, output, rawText.trim(), options.outputPath);
|
|
276
|
+
}
|
|
277
|
+
return result;
|
|
278
|
+
}
|
|
279
|
+
// ── Utility ──────────────────────────────────────────────────────────────────
|
|
280
|
+
function parseModelSpec(spec) {
|
|
281
|
+
const slashIndex = spec.indexOf("/");
|
|
282
|
+
if (slashIndex !== -1) {
|
|
283
|
+
return { provider: spec.slice(0, slashIndex), modelId: spec.slice(slashIndex + 1) };
|
|
284
|
+
}
|
|
285
|
+
return { provider: "anthropic", modelId: spec };
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* List all discoverable monitor names for validation.
|
|
289
|
+
*/
|
|
290
|
+
export function availableMonitors(cwd) {
|
|
291
|
+
const searchDirs = [
|
|
292
|
+
path.join(cwd, ".pi", "monitors"),
|
|
293
|
+
path.join(os.homedir(), ".pi", "agent", "monitors"),
|
|
294
|
+
];
|
|
295
|
+
try {
|
|
296
|
+
const bmPath = require.resolve("@davidorex/pi-behavior-monitors/package.json", { paths: [cwd] });
|
|
297
|
+
searchDirs.push(path.join(path.dirname(bmPath), "examples"));
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
/* not installed */
|
|
301
|
+
}
|
|
302
|
+
const seen = new Set();
|
|
303
|
+
for (const dir of searchDirs) {
|
|
304
|
+
try {
|
|
305
|
+
for (const file of fs.readdirSync(dir)) {
|
|
306
|
+
if (file.endsWith(".monitor.json")) {
|
|
307
|
+
seen.add(file.replace(".monitor.json", ""));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
/* dir doesn't exist */
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return Array.from(seen).sort();
|
|
316
|
+
}
|
|
317
|
+
//# sourceMappingURL=step-monitor.js.map
|