@agentproto/workflow-runtime 0.9.0 → 0.10.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/dist/index.d.ts +246 -4
- package/dist/index.mjs +1045 -590
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -4
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { runTool } from '@agentproto/driver';
|
|
2
2
|
import { createHash } from 'crypto';
|
|
3
|
+
import { execFile } from 'child_process';
|
|
4
|
+
import { stat, readFile, mkdir, writeFile, appendFile, readdir } from 'fs/promises';
|
|
5
|
+
import { join, dirname, isAbsolute, basename, resolve } from 'path';
|
|
6
|
+
import { assertKnownStepRefs } from '@agentproto/workflow';
|
|
7
|
+
import { isRefinedKind, filterEntriesByAllOf, resolveKnowledge } from '@agentproto/corpus';
|
|
3
8
|
import { parseWorkflowManifest, workflowFromManifest } from '@agentproto/workflow/manifest';
|
|
4
9
|
|
|
5
10
|
/**
|
|
@@ -7,702 +12,1152 @@ import { parseWorkflowManifest, workflowFromManifest } from '@agentproto/workflo
|
|
|
7
12
|
* AIP-15 step-walker over @agentproto/driver runTool.
|
|
8
13
|
*/
|
|
9
14
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
|
|
16
|
+
// src/build-agent-step.ts
|
|
17
|
+
function buildAgentStep(id, fields) {
|
|
18
|
+
const prompt = fields.prompt;
|
|
19
|
+
return {
|
|
20
|
+
kind: "agent",
|
|
21
|
+
id,
|
|
22
|
+
...fields.adapter !== void 0 ? { adapter: fields.adapter } : {},
|
|
23
|
+
...fields.sessionRef !== void 0 ? { sessionRef: fields.sessionRef } : {},
|
|
24
|
+
...fields.sandbox !== void 0 ? { sandbox: fields.sandbox } : {},
|
|
25
|
+
...fields.cacheable ? { cacheable: true } : {},
|
|
26
|
+
...fields.options !== void 0 ? { options: fields.options } : {},
|
|
27
|
+
prompt: typeof prompt === "function" ? prompt : ((_b) => prompt),
|
|
28
|
+
policy: fields.policy ?? { awaiting: "fail" },
|
|
29
|
+
...fields.outputSchema !== void 0 ? { outputSchema: fields.outputSchema } : {},
|
|
30
|
+
...fields.maxRetries !== void 0 ? { maxRetries: fields.maxRetries } : {},
|
|
31
|
+
...fields.harness !== void 0 ? { harness: fields.harness } : {}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/compile-workflow.ts
|
|
36
|
+
var WorkflowCompileError = class extends Error {
|
|
37
|
+
constructor(message) {
|
|
38
|
+
super(`compileWorkflow: ${message}`);
|
|
39
|
+
this.name = "WorkflowCompileError";
|
|
18
40
|
}
|
|
19
|
-
stepId;
|
|
20
|
-
on;
|
|
21
41
|
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
42
|
+
var get = (m, key) => m instanceof Map ? m.get(key) : m[key];
|
|
43
|
+
function dig(value, path) {
|
|
44
|
+
let v = value;
|
|
45
|
+
for (const seg of path) {
|
|
46
|
+
if (v == null) return void 0;
|
|
47
|
+
v = v[seg];
|
|
48
|
+
}
|
|
49
|
+
return v;
|
|
27
50
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
51
|
+
var REF_RE = /^\$(input|item|steps)((?:\.[^.]+)*)$/;
|
|
52
|
+
var REF_PREFIX_RE = /^\$(?:input|item|steps|index)((?:\.[^.$/]+)*)/;
|
|
53
|
+
function resolveRef(ref, b) {
|
|
54
|
+
if (ref === "$index") return b.index;
|
|
55
|
+
const m = ref.match(REF_RE);
|
|
56
|
+
if (!m) throw new WorkflowCompileError(`bad reference '${ref}'`);
|
|
57
|
+
const segs = m[2] ? m[2].slice(1).split(".") : [];
|
|
58
|
+
switch (m[1]) {
|
|
59
|
+
case "input":
|
|
60
|
+
return dig(b.input, segs);
|
|
61
|
+
case "item":
|
|
62
|
+
return dig(b.item, segs);
|
|
63
|
+
case "steps": {
|
|
64
|
+
const [stepId, ...rest] = segs;
|
|
65
|
+
if (!stepId) throw new WorkflowCompileError(`'$steps' needs a step id`);
|
|
66
|
+
return dig(b.steps[stepId], rest);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
36
69
|
}
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return
|
|
70
|
+
function resolveRefPrefixed(value, b) {
|
|
71
|
+
const m = value.match(REF_PREFIX_RE);
|
|
72
|
+
if (!m) return void 0;
|
|
73
|
+
const token = value.slice(0, m[0].length);
|
|
74
|
+
return { resolved: resolveRef(token, b), rest: value.slice(m[0].length) };
|
|
42
75
|
}
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
76
|
+
function resolveValue(node, b) {
|
|
77
|
+
if (typeof node === "string") {
|
|
78
|
+
if (node.startsWith("$$")) return node.slice(1);
|
|
79
|
+
if (node.startsWith("$")) return resolveRef(node, b);
|
|
80
|
+
return node;
|
|
81
|
+
}
|
|
82
|
+
if (Array.isArray(node)) return node.map((n) => resolveValue(n, b));
|
|
83
|
+
if (node && typeof node === "object") {
|
|
84
|
+
const out = {};
|
|
85
|
+
for (const [k, v] of Object.entries(node))
|
|
86
|
+
out[k] = resolveValue(v, b);
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
return node;
|
|
47
90
|
}
|
|
48
|
-
function
|
|
49
|
-
|
|
91
|
+
function refPathExists(ref, b) {
|
|
92
|
+
const m = ref.match(REF_RE);
|
|
93
|
+
if (!m) return false;
|
|
94
|
+
const segs = m[2] ? m[2].slice(1).split(".") : [];
|
|
95
|
+
let v;
|
|
96
|
+
if (m[1] === "steps") {
|
|
97
|
+
const [stepId, ...rest] = segs;
|
|
98
|
+
if (!stepId || !(stepId in b.steps)) return false;
|
|
99
|
+
v = b.steps[stepId];
|
|
100
|
+
segs.length = 0;
|
|
101
|
+
segs.push(...rest);
|
|
102
|
+
} else {
|
|
103
|
+
v = m[1] === "input" ? b.input : b.item;
|
|
104
|
+
}
|
|
105
|
+
for (const seg of segs) {
|
|
106
|
+
if (v === null || typeof v !== "object") return false;
|
|
107
|
+
const o = v;
|
|
108
|
+
if (!(seg in o)) return false;
|
|
109
|
+
v = o[seg];
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
50
112
|
}
|
|
51
|
-
function
|
|
52
|
-
|
|
113
|
+
function resolveValueStrict(node, b, label) {
|
|
114
|
+
if (typeof node === "string") {
|
|
115
|
+
if (node.startsWith("$$")) return node.slice(1);
|
|
116
|
+
if (node.startsWith("$")) {
|
|
117
|
+
const v = resolveRef(node, b);
|
|
118
|
+
if (v === void 0 && !refPathExists(node, b)) {
|
|
119
|
+
throw new WorkflowCompileError(
|
|
120
|
+
`${label}: reference '${node}' resolves to nothing \u2014 the referenced field does not exist`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return v;
|
|
124
|
+
}
|
|
125
|
+
return node;
|
|
126
|
+
}
|
|
127
|
+
if (Array.isArray(node)) return node.map((n) => resolveValueStrict(n, b, label));
|
|
128
|
+
if (node && typeof node === "object") {
|
|
129
|
+
const out = {};
|
|
130
|
+
for (const [k, v] of Object.entries(node))
|
|
131
|
+
out[k] = resolveValueStrict(v, b, label);
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
return node;
|
|
53
135
|
}
|
|
54
|
-
|
|
55
|
-
|
|
136
|
+
var COMPARATORS = {
|
|
137
|
+
"<": (a, c) => a < c,
|
|
138
|
+
"<=": (a, c) => a <= c,
|
|
139
|
+
">": (a, c) => a > c,
|
|
140
|
+
">=": (a, c) => a >= c
|
|
141
|
+
};
|
|
142
|
+
function literal(token, b) {
|
|
143
|
+
if (token.startsWith("$")) return resolveRef(token, b);
|
|
144
|
+
if (token === "true") return true;
|
|
145
|
+
if (token === "false") return false;
|
|
146
|
+
if (token === "null") return null;
|
|
147
|
+
if (/^-?\d+(\.\d+)?$/.test(token)) return Number(token);
|
|
148
|
+
return token.replace(/^['"]|['"]$/g, "");
|
|
56
149
|
}
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
150
|
+
function evalPredicate(expr, b) {
|
|
151
|
+
const m = expr.match(/^(.+?)\s*(==|!=|<=|>=|<|>)\s*(.+)$/);
|
|
152
|
+
if (!m) return Boolean(literal(expr.trim(), b));
|
|
153
|
+
const left = literal(m[1].trim(), b);
|
|
154
|
+
const right = literal(m[3].trim(), b);
|
|
155
|
+
switch (m[2]) {
|
|
156
|
+
case "==":
|
|
157
|
+
return left === right;
|
|
158
|
+
case "!=":
|
|
159
|
+
return left !== right;
|
|
160
|
+
default: {
|
|
161
|
+
const cmp = COMPARATORS[m[2]];
|
|
162
|
+
return cmp(Number(left), Number(right));
|
|
163
|
+
}
|
|
63
164
|
}
|
|
64
|
-
return { hit: false, key, hash };
|
|
65
165
|
}
|
|
66
|
-
function
|
|
67
|
-
|
|
166
|
+
function normalizeToolId(ref) {
|
|
167
|
+
let s = ref.trim();
|
|
168
|
+
if (s.endsWith("/TOOL.md")) s = s.slice(0, -"/TOOL.md".length);
|
|
169
|
+
const slash = s.lastIndexOf("/");
|
|
170
|
+
return slash === -1 ? s : s.slice(slash + 1);
|
|
68
171
|
}
|
|
69
|
-
|
|
70
|
-
|
|
172
|
+
var f = (step, key) => step[key];
|
|
173
|
+
function assertLinear(steps) {
|
|
174
|
+
steps.forEach((s, i) => {
|
|
175
|
+
const next = s.next;
|
|
176
|
+
const followingId = steps[i + 1]?.id;
|
|
177
|
+
if (next && next !== followingId) {
|
|
178
|
+
throw new WorkflowCompileError(
|
|
179
|
+
`step '${s.id}' has a non-linear next='${next}' \u2014 the structured-subset compiler only runs steps in document order. Hand-author a BranchStep or nest structurally.`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
71
183
|
}
|
|
72
|
-
|
|
73
|
-
let last;
|
|
184
|
+
function collectStepIds(steps, ids = /* @__PURE__ */ new Set()) {
|
|
74
185
|
for (const s of steps) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
186
|
+
if (typeof s?.id === "string") ids.add(s.id);
|
|
187
|
+
if (Array.isArray(s?.steps)) collectStepIds(s.steps, ids);
|
|
188
|
+
if (Array.isArray(s?.branches)) {
|
|
189
|
+
for (const br of s.branches) {
|
|
190
|
+
if (Array.isArray(br?.steps)) collectStepIds(br.steps, ids);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
79
193
|
}
|
|
80
|
-
return
|
|
194
|
+
return ids;
|
|
81
195
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
throw new
|
|
86
|
-
`
|
|
196
|
+
function compileWorkflow(handle, opts) {
|
|
197
|
+
const steps = handle.steps;
|
|
198
|
+
if (handle.start && handle.start !== steps[0]?.id) {
|
|
199
|
+
throw new WorkflowCompileError(
|
|
200
|
+
`start='${handle.start}' is not the first step \u2014 structured-subset runs in order`
|
|
87
201
|
);
|
|
88
202
|
}
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
...
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
203
|
+
const ctx = { opts, knownStepIds: collectStepIds(steps) };
|
|
204
|
+
const result = handle.result;
|
|
205
|
+
const output = result !== void 0 ? (b) => resolveValue(result, b) : void 0;
|
|
206
|
+
return {
|
|
207
|
+
id: handle.id,
|
|
208
|
+
description: handle.description,
|
|
209
|
+
steps: compileSiblingsToSteps(steps, ctx),
|
|
210
|
+
...output ? { output } : {}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function compileSiblingsToSteps(steps, ctx) {
|
|
214
|
+
assertLinear(steps);
|
|
215
|
+
const out = [];
|
|
216
|
+
for (let i = 0; i < steps.length; i++) {
|
|
217
|
+
const s = steps[i];
|
|
218
|
+
if (s.kind === "branch") {
|
|
219
|
+
out.push(compileBranchChain(steps, i, ctx));
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
out.push(compileStep(s, ctx));
|
|
105
223
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
224
|
+
return out;
|
|
225
|
+
}
|
|
226
|
+
function compileStepList(steps, id, ctx) {
|
|
227
|
+
const compiled = compileSiblingsToSteps(steps, ctx);
|
|
228
|
+
if (compiled.length === 1) return compiled[0];
|
|
229
|
+
return { kind: "group", id, steps: compiled };
|
|
230
|
+
}
|
|
231
|
+
function compileBranchChain(steps, i, ctx) {
|
|
232
|
+
const branchStep = steps[i];
|
|
233
|
+
const branches = f(branchStep, "branches");
|
|
234
|
+
const defaultTarget = f(branchStep, "default");
|
|
235
|
+
const resolveTarget = (targetId, label) => {
|
|
236
|
+
const idx = steps.findIndex((s) => s.id === targetId);
|
|
237
|
+
if (idx === -1 || idx <= i) {
|
|
238
|
+
throw new WorkflowCompileError(
|
|
239
|
+
`branch '${branchStep.id}' ${label} targets '${targetId}', which is not a sibling later in this step list \u2014 the forward-only branch compiler only supports jumping to a later sibling in the SAME list (not backward, and not into a nested map/loop/parallel body). Hand-author a 'loop' step for retry-style control flow instead.`
|
|
240
|
+
);
|
|
113
241
|
}
|
|
114
|
-
return
|
|
242
|
+
return idx;
|
|
243
|
+
};
|
|
244
|
+
const defaultIdx = defaultTarget !== void 0 ? resolveTarget(defaultTarget, "default") : i + 1;
|
|
245
|
+
let otherwise = compileSiblingsToSteps(steps.slice(defaultIdx), ctx);
|
|
246
|
+
for (let k = branches.length - 1; k >= 0; k--) {
|
|
247
|
+
const br = branches[k];
|
|
248
|
+
const targetIdx = resolveTarget(br.next, `branches[${k}].next`);
|
|
249
|
+
const thenSteps = compileSiblingsToSteps(steps.slice(targetIdx), ctx);
|
|
250
|
+
const condExpr = br.when;
|
|
251
|
+
const nodeId = k === 0 ? branchStep.id : `${branchStep.id}__branch${k}`;
|
|
252
|
+
otherwise = [
|
|
253
|
+
{
|
|
254
|
+
kind: "branch",
|
|
255
|
+
id: nodeId,
|
|
256
|
+
cond: (b) => evalPredicate(condExpr, b),
|
|
257
|
+
then: thenSteps,
|
|
258
|
+
...otherwise.length > 0 ? { otherwise } : {}
|
|
259
|
+
}
|
|
260
|
+
];
|
|
115
261
|
}
|
|
116
|
-
|
|
117
|
-
|
|
262
|
+
return otherwise[0];
|
|
263
|
+
}
|
|
264
|
+
function compileAgentStep(step, id, ctx) {
|
|
265
|
+
const opts = ctx.opts;
|
|
266
|
+
const prompt = f(step, "prompt");
|
|
267
|
+
if (typeof prompt !== "string" || prompt.length === 0) {
|
|
268
|
+
throw new WorkflowCompileError(`agent step '${id}' needs a non-empty 'prompt'`);
|
|
118
269
|
}
|
|
119
|
-
|
|
120
|
-
let
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
try {
|
|
126
|
-
value = JSON.parse(candidate);
|
|
127
|
-
} catch {
|
|
128
|
-
lastErr = "not valid JSON";
|
|
129
|
-
if (attempt < maxRetries) {
|
|
130
|
-
await ctx.agents.sendPromptAndWait(
|
|
131
|
-
sessionId,
|
|
132
|
-
`Your previous reply did not match the required schema: ${lastErr}. Reply again with ONLY a JSON object that matches. No prose, no code fence needed.`
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
continue;
|
|
270
|
+
let adapter = typeof step.adapter === "string" ? step.adapter : void 0;
|
|
271
|
+
let options = step.options !== void 0 ? step.options : void 0;
|
|
272
|
+
const agentRef = step.agent?.ref;
|
|
273
|
+
if (agentRef !== void 0) {
|
|
274
|
+
if (typeof agentRef !== "string" || agentRef.trim().length === 0) {
|
|
275
|
+
throw new WorkflowCompileError(`agent step '${id}' has an empty 'agent.ref'`);
|
|
136
276
|
}
|
|
137
|
-
const
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
sessionId,
|
|
143
|
-
`Your previous reply did not match the required schema: ${lastErr}. Reply again with ONLY a JSON object that matches. No prose, no code fence needed.`
|
|
277
|
+
const resolved = opts.agentRefs ? get(opts.agentRefs, agentRef) : void 0;
|
|
278
|
+
if (!resolved) {
|
|
279
|
+
const available = opts.agentRefs ? opts.agentRefs instanceof Map ? [...opts.agentRefs.keys()] : Object.keys(opts.agentRefs) : [];
|
|
280
|
+
throw new WorkflowCompileError(
|
|
281
|
+
`agent step '${id}' references unknown agent ref '${agentRef}'` + (available.length > 0 ? ` \u2014 available refs: ${available.join(", ")}` : " \u2014 no agent refs are configured for this compile (not running in an app context?)")
|
|
144
282
|
);
|
|
145
283
|
}
|
|
284
|
+
adapter = resolved.adapter;
|
|
285
|
+
options = resolved.options;
|
|
146
286
|
}
|
|
147
|
-
|
|
287
|
+
return buildAgentStep(id, {
|
|
288
|
+
prompt: (b) => String(resolveValue(prompt, b)),
|
|
289
|
+
...adapter !== void 0 ? { adapter } : {},
|
|
290
|
+
...step.sessionRef !== void 0 ? { sessionRef: step.sessionRef } : {},
|
|
291
|
+
...step.sandbox !== void 0 ? { sandbox: step.sandbox } : {},
|
|
292
|
+
...step.cacheable ? { cacheable: true } : {},
|
|
293
|
+
...options !== void 0 ? { options } : {},
|
|
294
|
+
policy: step.policy,
|
|
295
|
+
...step.outputSchema !== void 0 ? { outputSchema: step.outputSchema } : {},
|
|
296
|
+
...step.maxRetries !== void 0 ? { maxRetries: step.maxRetries } : {},
|
|
297
|
+
...step.harness !== void 0 ? { harness: step.harness } : {}
|
|
298
|
+
});
|
|
148
299
|
}
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
ctx.onStepStart?.(step.id);
|
|
300
|
+
function compileGateStep(step, id) {
|
|
301
|
+
const command = f(step, "command");
|
|
302
|
+
if (typeof command !== "string" || command.trim().length === 0) {
|
|
303
|
+
throw new WorkflowCompileError(`gate step '${id}' needs a non-empty 'command'`);
|
|
154
304
|
}
|
|
305
|
+
const args = f(step, "args");
|
|
306
|
+
const cwd = f(step, "cwd");
|
|
307
|
+
const reportPath = f(step, "report");
|
|
308
|
+
const timeoutMs = f(step, "timeout_ms");
|
|
309
|
+
const retry = f(step, "retry");
|
|
310
|
+
const onFail = f(
|
|
311
|
+
step,
|
|
312
|
+
"on_fail"
|
|
313
|
+
);
|
|
314
|
+
return {
|
|
315
|
+
kind: "gate",
|
|
316
|
+
id,
|
|
317
|
+
command,
|
|
318
|
+
...args !== void 0 ? { args } : {},
|
|
319
|
+
...cwd !== void 0 ? { cwd } : {},
|
|
320
|
+
...reportPath !== void 0 ? { reportPath } : {},
|
|
321
|
+
...timeoutMs !== void 0 ? { timeoutMs } : {},
|
|
322
|
+
...retry !== void 0 ? {
|
|
323
|
+
retry: {
|
|
324
|
+
maxAttempts: retry.max_attempts,
|
|
325
|
+
backoff: retry.backoff,
|
|
326
|
+
...retry.initial_ms !== void 0 ? { initialMs: retry.initial_ms } : {}
|
|
327
|
+
}
|
|
328
|
+
} : {},
|
|
329
|
+
...onFail !== void 0 ? { onFail: { reprompt: onFail.reprompt, ...onFail.with !== void 0 ? { with: onFail.with } : {} } } : {}
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function compileStep(step, ctx) {
|
|
333
|
+
const opts = ctx.opts;
|
|
334
|
+
const id = step.id;
|
|
155
335
|
switch (step.kind) {
|
|
156
336
|
case "tool": {
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
signal
|
|
337
|
+
const ref = typeof step.tool === "string" ? step.tool : step.tool.entry;
|
|
338
|
+
const toolId = normalizeToolId(ref);
|
|
339
|
+
const tool = get(opts.tools, toolId);
|
|
340
|
+
if (!tool)
|
|
341
|
+
throw new WorkflowCompileError(`no tool registered for '${toolId}'`);
|
|
342
|
+
const inputs = f(step, "inputs") ?? {};
|
|
343
|
+
assertKnownStepRefs(inputs, ctx.knownStepIds, `tool step '${id}' inputs`, {
|
|
344
|
+
makeError: (message) => new WorkflowCompileError(message)
|
|
166
345
|
});
|
|
167
|
-
if (!isCacheEnabled(ctx, step)) return runIt();
|
|
168
|
-
const c = await readStepCache(ctx, step, input);
|
|
169
|
-
if (c.hit) return c.output;
|
|
170
|
-
const out = await runIt();
|
|
171
|
-
await ctx.cache.set(c.key, { output: out, resolvedInputHash: c.hash });
|
|
172
|
-
return out;
|
|
173
|
-
}
|
|
174
|
-
case "transform":
|
|
175
|
-
return step.compute(b);
|
|
176
|
-
case "map": {
|
|
177
|
-
const arr = [...step.over(b)];
|
|
178
|
-
const parallelism = Math.max(1, step.parallelism ?? 1);
|
|
179
|
-
const tolerant = step.onError === "collect";
|
|
180
|
-
const results = new Array(arr.length);
|
|
181
|
-
for (let i = 0; i < arr.length; i += parallelism) {
|
|
182
|
-
const chunk = arr.slice(i, i + parallelism);
|
|
183
|
-
if (!tolerant) {
|
|
184
|
-
const outs = await Promise.all(
|
|
185
|
-
chunk.map((el, j) => {
|
|
186
|
-
const idx = i + j;
|
|
187
|
-
const inner = step.body(el, idx, view(state, el, idx));
|
|
188
|
-
return execStep(inner, ctx, el, idx);
|
|
189
|
-
})
|
|
190
|
-
);
|
|
191
|
-
for (let j = 0; j < outs.length; j++) results[i + j] = outs[j];
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
const settled = await Promise.allSettled(
|
|
195
|
-
chunk.map((el, j) => {
|
|
196
|
-
const idx = i + j;
|
|
197
|
-
const inner = step.body(el, idx, view(state, el, idx));
|
|
198
|
-
return execStep(inner, ctx, el, idx);
|
|
199
|
-
})
|
|
200
|
-
);
|
|
201
|
-
settled.forEach((s, j) => {
|
|
202
|
-
const idx = i + j;
|
|
203
|
-
results[idx] = s.status === "fulfilled" ? { status: "fulfilled", index: idx, value: s.value } : { status: "rejected", index: idx, item: chunk[j], error: errorMessage(s.reason) };
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
if (!tolerant) return results;
|
|
207
|
-
const outcomes = results;
|
|
208
346
|
return {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
347
|
+
kind: "tool",
|
|
348
|
+
id,
|
|
349
|
+
tool,
|
|
350
|
+
candidates: opts.candidates,
|
|
351
|
+
input: (b) => resolveValue(inputs, b),
|
|
352
|
+
context: opts.contextFor ? (b) => opts.contextFor(toolId, b) : void 0
|
|
212
353
|
};
|
|
213
354
|
}
|
|
214
|
-
case "
|
|
215
|
-
const
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
const cap = Math.max(1, step.concurrency ?? items.length);
|
|
219
|
-
const results = new Array(items.length);
|
|
220
|
-
let next = 0;
|
|
221
|
-
const runItem = async (idx) => {
|
|
222
|
-
let prev = void 0;
|
|
223
|
-
try {
|
|
224
|
-
for (const stage of step.stages) {
|
|
225
|
-
prev = await execStep(stage(items[idx], idx, prev, view(state, items[idx], idx)), ctx, items[idx], idx);
|
|
226
|
-
}
|
|
227
|
-
results[idx] = tolerant ? { status: "fulfilled", index: idx, value: prev } : prev;
|
|
228
|
-
} catch (err) {
|
|
229
|
-
if (!tolerant) throw err;
|
|
230
|
-
results[idx] = { status: "rejected", index: idx, item: items[idx], error: errorMessage(err) };
|
|
231
|
-
}
|
|
232
|
-
};
|
|
233
|
-
const worker = async () => {
|
|
234
|
-
while (next < items.length) {
|
|
235
|
-
const idx = next++;
|
|
236
|
-
await runItem(idx);
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
await Promise.all(Array.from({ length: Math.min(cap, items.length) }, () => worker()));
|
|
240
|
-
if (!tolerant) return results;
|
|
241
|
-
const outcomes = results;
|
|
355
|
+
case "map": {
|
|
356
|
+
const over = f(step, "over");
|
|
357
|
+
const inner = compileStepList(f(step, "steps"), `${id}__body`, ctx);
|
|
358
|
+
const onError = f(step, "onError");
|
|
242
359
|
return {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
360
|
+
kind: "map",
|
|
361
|
+
id,
|
|
362
|
+
parallelism: f(step, "parallelism"),
|
|
363
|
+
over: (b) => resolveRef(over, b),
|
|
364
|
+
body: () => inner,
|
|
365
|
+
...onError !== void 0 ? { onError } : {}
|
|
246
366
|
};
|
|
247
367
|
}
|
|
248
|
-
case "branch": {
|
|
249
|
-
const chosen = step.cond(b) ? step.then : step.otherwise ?? [];
|
|
250
|
-
return runSequence(chosen, ctx, item, index);
|
|
251
|
-
}
|
|
252
368
|
case "loop": {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return record;
|
|
369
|
+
const whileExpr = f(step, "while");
|
|
370
|
+
return {
|
|
371
|
+
kind: "loop",
|
|
372
|
+
id,
|
|
373
|
+
maxIterations: f(step, "max_iterations"),
|
|
374
|
+
while: (b) => evalPredicate(whileExpr, b),
|
|
375
|
+
body: [compileStepList(f(step, "steps"), `${id}__body`, ctx)]
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
case "parallel": {
|
|
379
|
+
const branches = f(step, "branches");
|
|
380
|
+
return {
|
|
381
|
+
kind: "parallel",
|
|
382
|
+
id,
|
|
383
|
+
branches: branches.map((br) => ({
|
|
384
|
+
id: br.id,
|
|
385
|
+
steps: [compileStepList(br.steps, `${id}__${br.id}`, ctx)]
|
|
386
|
+
}))
|
|
387
|
+
};
|
|
273
388
|
}
|
|
274
389
|
case "approval": {
|
|
275
|
-
const prompt = step
|
|
276
|
-
const approvers = step
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
390
|
+
const prompt = f(step, "prompt");
|
|
391
|
+
const approvers = (f(step, "approvers") ?? []).map(
|
|
392
|
+
(a) => a.role ?? a.userId ?? String(a)
|
|
393
|
+
);
|
|
394
|
+
const artifacts = f(step, "artifacts");
|
|
395
|
+
const timeoutMs = f(step, "timeout_ms");
|
|
396
|
+
return {
|
|
397
|
+
kind: "approval",
|
|
398
|
+
id,
|
|
399
|
+
approvers,
|
|
400
|
+
...artifacts !== void 0 ? { artifacts } : {},
|
|
401
|
+
...timeoutMs !== void 0 ? { timeoutMs } : {},
|
|
402
|
+
prompt: (b) => String(resolveValue(prompt, b))
|
|
403
|
+
};
|
|
281
404
|
}
|
|
282
405
|
case "suspend": {
|
|
283
|
-
|
|
284
|
-
return
|
|
406
|
+
const resume = f(step, "resume");
|
|
407
|
+
return { kind: "suspend", id, on: resume.on, timeoutMs: resume.timeout_ms };
|
|
285
408
|
}
|
|
286
|
-
case "group":
|
|
287
|
-
return runSequence(step.steps, ctx, item, index);
|
|
288
409
|
case "subworkflow": {
|
|
289
|
-
const
|
|
290
|
-
const child =
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
410
|
+
const wfId = f(step, "workflow");
|
|
411
|
+
const child = opts.workflows ? get(opts.workflows, wfId) : void 0;
|
|
412
|
+
if (!child)
|
|
413
|
+
throw new WorkflowCompileError(
|
|
414
|
+
`subworkflow '${wfId}' not found \u2014 pass it in opts.workflows`
|
|
415
|
+
);
|
|
416
|
+
const compiledChild = compileWorkflow(child, opts);
|
|
417
|
+
const inputs = f(step, "inputs");
|
|
418
|
+
if (inputs === void 0) {
|
|
419
|
+
return { kind: "subworkflow", id, workflow: compiledChild };
|
|
420
|
+
}
|
|
421
|
+
assertKnownStepRefs(inputs, ctx.knownStepIds, `subworkflow step '${id}' inputs`, {
|
|
422
|
+
makeError: (message) => new WorkflowCompileError(message)
|
|
299
423
|
});
|
|
300
|
-
|
|
424
|
+
const entries = Object.entries(inputs);
|
|
425
|
+
return {
|
|
426
|
+
kind: "subworkflow",
|
|
427
|
+
id,
|
|
428
|
+
workflow: compiledChild,
|
|
429
|
+
// Each top-level key resolves strictly: a missing referenced field
|
|
430
|
+
// throws at run time naming this step + key (no silent `undefined`).
|
|
431
|
+
input: (b) => {
|
|
432
|
+
const out = {};
|
|
433
|
+
for (const [k, v] of entries)
|
|
434
|
+
out[k] = resolveValueStrict(v, b, `subworkflow step '${id}' input key '${k}'`);
|
|
435
|
+
return out;
|
|
436
|
+
}
|
|
437
|
+
};
|
|
301
438
|
}
|
|
302
439
|
case "agent": {
|
|
303
|
-
if (
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
return out;
|
|
440
|
+
if (typeof step.prompt === "function") {
|
|
441
|
+
const passthrough = step;
|
|
442
|
+
return passthrough;
|
|
443
|
+
}
|
|
444
|
+
return compileAgentStep(step, id, ctx);
|
|
445
|
+
}
|
|
446
|
+
case "gate":
|
|
447
|
+
return compileGateStep(step, id);
|
|
448
|
+
case "transform": {
|
|
449
|
+
const passthrough = step;
|
|
450
|
+
return passthrough;
|
|
315
451
|
}
|
|
452
|
+
case "branch":
|
|
453
|
+
throw new WorkflowCompileError(
|
|
454
|
+
`internal: branch step '${id}' reached compileStep directly \u2014 branch steps must be compiled via the sibling-list walker`
|
|
455
|
+
);
|
|
456
|
+
default:
|
|
457
|
+
throw new WorkflowCompileError(
|
|
458
|
+
`unsupported step kind '${step.kind}' (step '${id}')`
|
|
459
|
+
);
|
|
316
460
|
}
|
|
317
461
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
462
|
+
|
|
463
|
+
// src/ref-string.ts
|
|
464
|
+
function resolveRefString(stepId, field, value, b, onUnmatched = "passthrough") {
|
|
465
|
+
if (value.startsWith("$$")) return value.slice(1);
|
|
466
|
+
let hit;
|
|
467
|
+
try {
|
|
468
|
+
hit = resolveRefPrefixed(value, b);
|
|
469
|
+
} catch (err) {
|
|
470
|
+
throw new Error(
|
|
471
|
+
`step '${stepId}': ${field} '${value}' is not a valid reference \u2014 ${err instanceof Error ? err.message : String(err)}`
|
|
472
|
+
);
|
|
327
473
|
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
474
|
+
if (!hit) {
|
|
475
|
+
if (onUnmatched === "error" && value.startsWith("$")) {
|
|
476
|
+
throw new Error(
|
|
477
|
+
`step '${stepId}': ${field} '${value}' is not a valid reference \u2014 a leading '$' must open a $input/$item/$steps.<id>/$index reference ('$$' escapes a literal '$')`
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
return value;
|
|
481
|
+
}
|
|
482
|
+
const token = value.slice(0, value.length - hit.rest.length);
|
|
483
|
+
if (hit.resolved === void 0) {
|
|
484
|
+
throw new Error(
|
|
485
|
+
`step '${stepId}': ${field} '${token}' resolves to nothing \u2014 the referenced field does not exist`
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
return String(hit.resolved) + hit.rest;
|
|
331
489
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
490
|
+
var NodeFsPort = class {
|
|
491
|
+
root;
|
|
492
|
+
constructor(root) {
|
|
493
|
+
this.root = root;
|
|
494
|
+
}
|
|
495
|
+
resolve(p) {
|
|
496
|
+
const normalized = p.replace(/^\/+/, "");
|
|
497
|
+
if (normalized.startsWith("..")) {
|
|
498
|
+
throw new Error(`NodeFsPort: path "${p}" escapes the workspace root "${this.root}"`);
|
|
499
|
+
}
|
|
500
|
+
return join(this.root, normalized);
|
|
501
|
+
}
|
|
502
|
+
async exists(p) {
|
|
503
|
+
try {
|
|
504
|
+
await stat(this.resolve(p));
|
|
505
|
+
return true;
|
|
506
|
+
} catch {
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
async readFile(p) {
|
|
511
|
+
return readFile(this.resolve(p), "utf8");
|
|
512
|
+
}
|
|
513
|
+
async writeFile(p, content) {
|
|
514
|
+
const target = this.resolve(p);
|
|
515
|
+
await mkdir(dirname(target), { recursive: true });
|
|
516
|
+
await writeFile(target, content, "utf8");
|
|
517
|
+
}
|
|
518
|
+
async appendFile(p, content) {
|
|
519
|
+
const target = this.resolve(p);
|
|
520
|
+
await mkdir(dirname(target), { recursive: true });
|
|
521
|
+
await appendFile(target, content, "utf8");
|
|
522
|
+
}
|
|
523
|
+
async readdir(p) {
|
|
524
|
+
return readdir(this.resolve(p));
|
|
525
|
+
}
|
|
526
|
+
async walk(p) {
|
|
527
|
+
const out = [];
|
|
528
|
+
const visit = async (dir) => {
|
|
529
|
+
let entries = [];
|
|
530
|
+
try {
|
|
531
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
532
|
+
} catch {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
for (const ent of entries) {
|
|
536
|
+
if (ent.name.startsWith(".")) continue;
|
|
537
|
+
const full = join(dir, ent.name);
|
|
538
|
+
if (ent.isDirectory()) {
|
|
539
|
+
await visit(full);
|
|
540
|
+
} else if (ent.isFile()) {
|
|
541
|
+
out.push(full);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
await visit(this.resolve(p));
|
|
546
|
+
const prefix = this.resolve(p);
|
|
547
|
+
return out.map((f2) => f2.slice(prefix.length).replace(/^\//, ""));
|
|
548
|
+
}
|
|
549
|
+
async stat(p) {
|
|
550
|
+
try {
|
|
551
|
+
const s = await stat(this.resolve(p));
|
|
552
|
+
return {
|
|
553
|
+
kind: s.isDirectory() ? "directory" : "file",
|
|
554
|
+
...s.isFile() ? { bytes: s.size } : {},
|
|
555
|
+
modifiedAt: s.mtime
|
|
556
|
+
};
|
|
557
|
+
} catch {
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
async lock() {
|
|
562
|
+
return { release: async () => {
|
|
563
|
+
} };
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
// src/knowledge.ts
|
|
568
|
+
var DEFAULT_MAX_ENTRIES = 50;
|
|
569
|
+
function resolveStringArray(stepId, index, field, values, b) {
|
|
570
|
+
if (values === void 0) return void 0;
|
|
571
|
+
return values.map(
|
|
572
|
+
(v) => resolveRefString(stepId, `harness.knowledge[${index}].${field}`, v, b)
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
function resolveKnowledgeSelectors(stepId, selectors, b) {
|
|
576
|
+
return selectors.map((sel, index) => {
|
|
577
|
+
const deferred = sel.deferred === true;
|
|
578
|
+
const workspace = deferred ? resolveRefString(stepId, `harness.knowledge[${index}].workspace`, sel.workspace, b) : sel.workspace;
|
|
579
|
+
return {
|
|
580
|
+
...sel,
|
|
581
|
+
deferred: void 0,
|
|
582
|
+
workspace,
|
|
583
|
+
...sel.anyOf !== void 0 ? { anyOf: deferred ? resolveStringArray(stepId, index, "anyOf", sel.anyOf, b) : [...sel.anyOf] } : {},
|
|
584
|
+
...sel.allOf !== void 0 ? { allOf: deferred ? resolveStringArray(stepId, index, "allOf", sel.allOf, b) : [...sel.allOf] } : {},
|
|
585
|
+
...sel.kinds !== void 0 ? { kinds: deferred ? resolveStringArray(stepId, index, "kinds", sel.kinds, b) : [...sel.kinds] } : {}
|
|
586
|
+
};
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
function workspaceDirName(workspace) {
|
|
590
|
+
const trimmed = workspace.replace(/\/+$/, "");
|
|
591
|
+
return basename(trimmed) || trimmed;
|
|
345
592
|
}
|
|
593
|
+
function safeSlug(slug) {
|
|
594
|
+
return slug.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
595
|
+
}
|
|
596
|
+
async function materializeKnowledge(stepId, selectors, stepCwd) {
|
|
597
|
+
const knowledgeDir = join(stepCwd, ".knowledge");
|
|
598
|
+
const records = [];
|
|
599
|
+
const warnings = [];
|
|
600
|
+
const indexSections = [];
|
|
601
|
+
let written = 0;
|
|
602
|
+
for (const [i, sel] of selectors.entries()) {
|
|
603
|
+
if (sel.mode !== void 0 && sel.mode !== "files") {
|
|
604
|
+
throw new Error(
|
|
605
|
+
`step '${stepId}': harness.knowledge[${i}].mode must be "files" (v1 supports no other mode)`
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
const workspace = sel.workspace;
|
|
609
|
+
if (typeof workspace !== "string" || workspace.length === 0) {
|
|
610
|
+
throw new Error(
|
|
611
|
+
`step '${stepId}': harness.knowledge[${i}] needs a non-empty 'workspace'`
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
if (!isAbsolute(workspace)) {
|
|
615
|
+
throw new Error(
|
|
616
|
+
`step '${stepId}': harness.knowledge[${i}].workspace '${workspace}' must be absolute by run time \u2014 the loader resolves relative paths; did you bypass loadWorkflowHandle?`
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
const fs = new NodeFsPort(workspace);
|
|
620
|
+
const rootStat = await fs.stat(".");
|
|
621
|
+
if (rootStat === null || rootStat.kind !== "directory") {
|
|
622
|
+
const warning = `knowledge-workspace-missing: harness.knowledge[${i}] (workspace '${workspace}') does not name an existing directory`;
|
|
623
|
+
warnings.push(warning);
|
|
624
|
+
records.push({ workspace, matched: 0, written: 0 });
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
const kinds = (sel.kinds ?? []).filter((k) => isRefinedKind(k));
|
|
628
|
+
const resolved = filterEntriesByAllOf(
|
|
629
|
+
await resolveKnowledge({
|
|
630
|
+
fs,
|
|
631
|
+
query: {
|
|
632
|
+
...sel.anyOf && sel.anyOf.length > 0 ? { tags: [...sel.anyOf] } : {},
|
|
633
|
+
...kinds.length > 0 ? { kinds } : {}
|
|
634
|
+
}
|
|
635
|
+
}),
|
|
636
|
+
sel.allOf
|
|
637
|
+
);
|
|
638
|
+
const sorted = [...resolved].sort((a, b) => a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0);
|
|
639
|
+
const cap = sel.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
640
|
+
const picked = sorted.slice(0, cap);
|
|
641
|
+
const dirName = workspaceDirName(workspace);
|
|
642
|
+
const lines = [];
|
|
643
|
+
for (const entry of picked) {
|
|
644
|
+
const rel = join(dirName, `${safeSlug(entry.slug)}.md`);
|
|
645
|
+
const raw = await fs.readFile(entry.path);
|
|
646
|
+
await mkdir(join(knowledgeDir, dirName), { recursive: true });
|
|
647
|
+
await writeFile(join(knowledgeDir, rel), raw, "utf8");
|
|
648
|
+
lines.push(
|
|
649
|
+
`- [${entry.title || entry.slug}](${rel.split("\\").join("/")}) \u2014 ${entry.kind}, ${entry.tags.join(", ")}`
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
written += picked.length;
|
|
653
|
+
if (lines.length > 0) {
|
|
654
|
+
indexSections.push(`## ${dirName}
|
|
346
655
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
...fields.outputSchema !== void 0 ? { outputSchema: fields.outputSchema } : {},
|
|
361
|
-
...fields.maxRetries !== void 0 ? { maxRetries: fields.maxRetries } : {}
|
|
362
|
-
};
|
|
656
|
+
${lines.join("\n")}`);
|
|
657
|
+
}
|
|
658
|
+
records.push({ workspace, matched: resolved.length, written: picked.length });
|
|
659
|
+
if (resolved.length === 0) {
|
|
660
|
+
warnings.push(
|
|
661
|
+
`knowledge-empty: harness.knowledge[${i}] (workspace '${workspace}') matched 0 entries`
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
const index = "# Knowledge index\n\n" + (indexSections.length > 0 ? indexSections.join("\n\n") : "_No entries matched._") + "\n";
|
|
666
|
+
await mkdir(knowledgeDir, { recursive: true });
|
|
667
|
+
await writeFile(join(knowledgeDir, "INDEX.md"), index, "utf8");
|
|
668
|
+
return { records, written, warnings };
|
|
363
669
|
}
|
|
364
670
|
|
|
365
|
-
// src/
|
|
366
|
-
var
|
|
367
|
-
constructor(
|
|
368
|
-
super(
|
|
369
|
-
|
|
671
|
+
// src/run-workflow.ts
|
|
672
|
+
var WorkflowSuspendedError = class extends Error {
|
|
673
|
+
constructor(stepId, on) {
|
|
674
|
+
super(
|
|
675
|
+
`workflow suspended at step '${stepId}' awaiting [${on.join(", ")}] \u2014 no resume hook supplied`
|
|
676
|
+
);
|
|
677
|
+
this.stepId = stepId;
|
|
678
|
+
this.on = on;
|
|
679
|
+
this.name = "WorkflowSuspendedError";
|
|
370
680
|
}
|
|
681
|
+
stepId;
|
|
682
|
+
on;
|
|
371
683
|
};
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
let v = value;
|
|
375
|
-
for (const seg of path) {
|
|
376
|
-
if (v == null) return void 0;
|
|
377
|
-
v = v[seg];
|
|
378
|
-
}
|
|
379
|
-
return v;
|
|
684
|
+
function view(state, item, index) {
|
|
685
|
+
return { input: state.input, steps: state.steps, item, index };
|
|
380
686
|
}
|
|
381
|
-
function
|
|
382
|
-
|
|
383
|
-
const m = ref.match(/^\$(input|item|steps)((?:\.[^.]+)*)$/);
|
|
384
|
-
if (!m) throw new WorkflowCompileError(`bad reference '${ref}'`);
|
|
385
|
-
const segs = m[2] ? m[2].slice(1).split(".") : [];
|
|
386
|
-
switch (m[1]) {
|
|
387
|
-
case "input":
|
|
388
|
-
return dig(b.input, segs);
|
|
389
|
-
case "item":
|
|
390
|
-
return dig(b.item, segs);
|
|
391
|
-
case "steps": {
|
|
392
|
-
const [stepId, ...rest] = segs;
|
|
393
|
-
if (!stepId) throw new WorkflowCompileError(`'$steps' needs a step id`);
|
|
394
|
-
return dig(b.steps[stepId], rest);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
687
|
+
function resolveSel(sel, b) {
|
|
688
|
+
return typeof sel === "function" ? sel(b) : sel;
|
|
397
689
|
}
|
|
398
|
-
function
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
if (
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
return
|
|
690
|
+
function extractJsonCandidate(raw) {
|
|
691
|
+
const jsonFence = /```json\s*([\s\S]*?)```/g;
|
|
692
|
+
const lastJson = lastMatch(jsonFence, raw);
|
|
693
|
+
if (lastJson !== void 0) return lastJson;
|
|
694
|
+
const anyFence = /```\s*([\s\S]*?)```/g;
|
|
695
|
+
const lastAny = lastMatch(anyFence, raw);
|
|
696
|
+
if (lastAny !== void 0) return lastAny;
|
|
697
|
+
return raw.trim();
|
|
698
|
+
}
|
|
699
|
+
function lastMatch(re, s) {
|
|
700
|
+
let m;
|
|
701
|
+
let last = null;
|
|
702
|
+
while ((m = re.exec(s)) !== null) last = m;
|
|
703
|
+
return last ? last[1]?.trim() : void 0;
|
|
704
|
+
}
|
|
705
|
+
function spentUsd(state) {
|
|
706
|
+
let total = 0;
|
|
707
|
+
for (const c of state.costBySession.values()) total += c;
|
|
708
|
+
return total;
|
|
412
709
|
}
|
|
413
|
-
|
|
414
|
-
"
|
|
415
|
-
"<=": (a, c) => a <= c,
|
|
416
|
-
">": (a, c) => a > c,
|
|
417
|
-
">=": (a, c) => a >= c
|
|
418
|
-
};
|
|
419
|
-
function literal(token, b) {
|
|
420
|
-
if (token.startsWith("$")) return resolveRef(token, b);
|
|
421
|
-
if (token === "true") return true;
|
|
422
|
-
if (token === "false") return false;
|
|
423
|
-
if (token === "null") return null;
|
|
424
|
-
if (/^-?\d+(\.\d+)?$/.test(token)) return Number(token);
|
|
425
|
-
return token.replace(/^['"]|['"]$/g, "");
|
|
710
|
+
function hashResolvedInputs(kind, resolved) {
|
|
711
|
+
return createHash("sha256").update(`${kind}\0${JSON.stringify(resolved) ?? "undefined"}`).digest("hex");
|
|
426
712
|
}
|
|
427
|
-
function
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
return cmp(Number(left), Number(right));
|
|
440
|
-
}
|
|
713
|
+
function stepJournalKey(cacheKey, step) {
|
|
714
|
+
return `${cacheKey}\0${step.id}\0${step.kind}`;
|
|
715
|
+
}
|
|
716
|
+
function isCacheEnabled(ctx, step) {
|
|
717
|
+
return step.cacheable === true && ctx.cache !== void 0 && ctx.cacheKey !== void 0;
|
|
718
|
+
}
|
|
719
|
+
async function readStepCache(ctx, step, resolvedInputs) {
|
|
720
|
+
const key = stepJournalKey(ctx.cacheKey, step);
|
|
721
|
+
const hash = hashResolvedInputs(step.kind, resolvedInputs);
|
|
722
|
+
const entry = await ctx.cache.get(key);
|
|
723
|
+
if (entry !== void 0 && entry.resolvedInputHash === hash) {
|
|
724
|
+
return { hit: true, output: entry.output };
|
|
441
725
|
}
|
|
726
|
+
return { hit: false, key, hash };
|
|
442
727
|
}
|
|
443
|
-
function
|
|
444
|
-
|
|
445
|
-
if (s.endsWith("/TOOL.md")) s = s.slice(0, -"/TOOL.md".length);
|
|
446
|
-
const slash = s.lastIndexOf("/");
|
|
447
|
-
return slash === -1 ? s : s.slice(slash + 1);
|
|
728
|
+
function errorMessage(err) {
|
|
729
|
+
return err instanceof Error ? err.message : String(err);
|
|
448
730
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
steps.forEach((s, i) => {
|
|
452
|
-
const next = s.next;
|
|
453
|
-
const followingId = steps[i + 1]?.id;
|
|
454
|
-
if (next && next !== followingId) {
|
|
455
|
-
throw new WorkflowCompileError(
|
|
456
|
-
`step '${s.id}' has a non-linear next='${next}' \u2014 the structured-subset compiler only runs steps in document order. Hand-author a BranchStep or nest structurally.`
|
|
457
|
-
);
|
|
458
|
-
}
|
|
459
|
-
});
|
|
731
|
+
function formatZodError(err) {
|
|
732
|
+
return err.issues.map((i) => `${i.path.length > 0 ? i.path.join(".") + ": " : ""}${i.message}`).join(", ");
|
|
460
733
|
}
|
|
461
|
-
function
|
|
734
|
+
async function runSequence(steps, ctx, item, index) {
|
|
735
|
+
let last;
|
|
462
736
|
for (const s of steps) {
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
if (Array.isArray(br?.steps)) collectStepIds(br.steps, ids);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
737
|
+
const out = await execStep(s, ctx, item, index);
|
|
738
|
+
ctx.state.steps[s.id] = out;
|
|
739
|
+
ctx.onStepComplete?.(s.id, out);
|
|
740
|
+
last = out;
|
|
470
741
|
}
|
|
471
|
-
return
|
|
742
|
+
return last;
|
|
472
743
|
}
|
|
473
|
-
function
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
744
|
+
async function execAgentStep(step, ctx, b) {
|
|
745
|
+
ctx.onStepStart?.(step.id);
|
|
746
|
+
if (step.adapter && ctx.state.maxTotalCostUsd !== void 0 && spentUsd(ctx.state) >= ctx.state.maxTotalCostUsd) {
|
|
747
|
+
throw new Error(
|
|
748
|
+
`step '${step.id}': budget_exceeded \u2014 run spend $${spentUsd(ctx.state).toFixed(4)} >= cap $${ctx.state.maxTotalCostUsd}`
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
const cwd = step.harness?.cwd ?? (step.cwd ? resolveSel(step.cwd, b) : ctx.cwd);
|
|
752
|
+
let knowledgeOut;
|
|
753
|
+
let knowledgeWarnings = [];
|
|
754
|
+
if (step.harness?.knowledge && step.harness.knowledge.length > 0) {
|
|
755
|
+
if (cwd === void 0) {
|
|
756
|
+
throw new Error(
|
|
757
|
+
`step '${step.id}': harness.knowledge requires a resolvable working directory (set step cwd, harness.cwd, or the run cwd)`
|
|
480
758
|
);
|
|
481
759
|
}
|
|
482
|
-
|
|
760
|
+
const knowledgeSelectors = resolveKnowledgeSelectors(step.id, step.harness.knowledge, b).map(
|
|
761
|
+
(sel) => ({
|
|
762
|
+
...sel,
|
|
763
|
+
workspace: isAbsolute(sel.workspace) ? sel.workspace : join(cwd, sel.workspace)
|
|
764
|
+
})
|
|
765
|
+
);
|
|
766
|
+
const materialized = await materializeKnowledge(step.id, knowledgeSelectors, cwd);
|
|
767
|
+
knowledgeOut = materialized.records;
|
|
768
|
+
knowledgeWarnings = materialized.warnings;
|
|
769
|
+
if (materialized.written > 0) {
|
|
770
|
+
const note = `Knowledge for this step is materialized under .knowledge/ (see .knowledge/INDEX.md, ${materialized.written} entries).`;
|
|
771
|
+
const inner = step.prompt;
|
|
772
|
+
step = { ...step, prompt: (b2) => `${note}
|
|
773
|
+
|
|
774
|
+
${inner(b2)}` };
|
|
775
|
+
}
|
|
483
776
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
777
|
+
const sandbox = typeof step.sandbox === "function" ? step.sandbox(b) : step.sandbox;
|
|
778
|
+
const sessionId = step.adapter ? await ctx.agents.spawn(resolveSel(step.adapter, b), {
|
|
779
|
+
cwd,
|
|
780
|
+
workspaceSlug: ctx.workspaceSlug,
|
|
781
|
+
stepId: step.id,
|
|
782
|
+
...sandbox !== void 0 ? { sandbox } : {},
|
|
783
|
+
...step.options !== void 0 ? { options: step.options } : {},
|
|
784
|
+
...step.harness !== void 0 ? { harness: step.harness } : {}
|
|
785
|
+
}) : ctx.agents.resolveByLabel(step.sessionRef);
|
|
786
|
+
if (!sessionId) throw new Error(`step '${step.id}': no session (adapter and sessionRef both unresolved)`);
|
|
787
|
+
if (knowledgeWarnings.length > 0 && ctx.agents.emitHarnessWarning) {
|
|
788
|
+
ctx.agents.emitHarnessWarning({
|
|
789
|
+
sessionId,
|
|
790
|
+
warnings: knowledgeWarnings,
|
|
791
|
+
label: step.id
|
|
792
|
+
});
|
|
487
793
|
}
|
|
488
|
-
|
|
489
|
-
|
|
794
|
+
const harnessOut = step.harness !== void 0 ? {
|
|
795
|
+
...step.harness,
|
|
796
|
+
...step.harness.tools && step.harness.tools.length > 0 ? { toolsApplied: false } : {}
|
|
797
|
+
} : void 0;
|
|
798
|
+
await ctx.agents.sendPromptAndWait(sessionId, step.prompt(b));
|
|
799
|
+
if (step.policy && ctx.agents.onAwaitingInput) {
|
|
800
|
+
await ctx.agents.onAwaitingInput(sessionId, step.policy);
|
|
490
801
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
const steps = handle.steps;
|
|
494
|
-
if (handle.start && handle.start !== steps[0]?.id) {
|
|
495
|
-
throw new WorkflowCompileError(
|
|
496
|
-
`start='${handle.start}' is not the first step \u2014 structured-subset runs in order`
|
|
497
|
-
);
|
|
802
|
+
if (ctx.agents.readCostUsd) {
|
|
803
|
+
ctx.state.costBySession.set(sessionId, await ctx.agents.readCostUsd(sessionId));
|
|
498
804
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
...output ? { output } : {}
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
function compileSiblingsToSteps(steps, ctx) {
|
|
510
|
-
assertLinear(steps);
|
|
511
|
-
const out = [];
|
|
512
|
-
for (let i = 0; i < steps.length; i++) {
|
|
513
|
-
const s = steps[i];
|
|
514
|
-
if (s.kind === "branch") {
|
|
515
|
-
out.push(compileBranchChain(steps, i, ctx));
|
|
516
|
-
return out;
|
|
805
|
+
if (!step.outputSchema) {
|
|
806
|
+
let text;
|
|
807
|
+
if (ctx.agents.readFinalMessage) {
|
|
808
|
+
try {
|
|
809
|
+
text = await ctx.agents.readFinalMessage(sessionId);
|
|
810
|
+
} catch {
|
|
811
|
+
}
|
|
517
812
|
}
|
|
518
|
-
|
|
813
|
+
return { sessionId, ...text !== void 0 ? { text } : {}, ...harnessOut ? { harness: harnessOut } : {}, ...knowledgeOut ? { knowledgeApplied: knowledgeOut } : {} };
|
|
519
814
|
}
|
|
520
|
-
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
815
|
+
if (!ctx.agents.readFinalMessage) {
|
|
816
|
+
throw new Error(`step '${step.id}': outputSchema requires a host with readFinalMessage`);
|
|
817
|
+
}
|
|
818
|
+
const maxRetries = step.maxRetries ?? 2;
|
|
819
|
+
let lastErr = "";
|
|
820
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
821
|
+
const raw = await ctx.agents.readFinalMessage(sessionId);
|
|
822
|
+
const candidate = extractJsonCandidate(raw);
|
|
823
|
+
let value;
|
|
824
|
+
try {
|
|
825
|
+
value = JSON.parse(candidate);
|
|
826
|
+
} catch {
|
|
827
|
+
lastErr = "not valid JSON";
|
|
828
|
+
if (attempt < maxRetries) {
|
|
829
|
+
await ctx.agents.sendPromptAndWait(
|
|
830
|
+
sessionId,
|
|
831
|
+
`Your previous reply did not match the required schema: ${lastErr}. Reply again with ONLY a JSON object that matches. No prose, no code fence needed.`
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
const res = step.outputSchema.safeParse(value);
|
|
837
|
+
if (res.success) return { sessionId, output: res.data, ...harnessOut ? { harness: harnessOut } : {}, ...knowledgeOut ? { knowledgeApplied: knowledgeOut } : {} };
|
|
838
|
+
lastErr = formatZodError(res.error);
|
|
839
|
+
if (attempt < maxRetries) {
|
|
840
|
+
await ctx.agents.sendPromptAndWait(
|
|
841
|
+
sessionId,
|
|
842
|
+
`Your previous reply did not match the required schema: ${lastErr}. Reply again with ONLY a JSON object that matches. No prose, no code fence needed.`
|
|
536
843
|
);
|
|
537
844
|
}
|
|
538
|
-
return idx;
|
|
539
|
-
};
|
|
540
|
-
const defaultIdx = defaultTarget !== void 0 ? resolveTarget(defaultTarget, "default") : i + 1;
|
|
541
|
-
let otherwise = compileSiblingsToSteps(steps.slice(defaultIdx), ctx);
|
|
542
|
-
for (let k = branches.length - 1; k >= 0; k--) {
|
|
543
|
-
const br = branches[k];
|
|
544
|
-
const targetIdx = resolveTarget(br.next, `branches[${k}].next`);
|
|
545
|
-
const thenSteps = compileSiblingsToSteps(steps.slice(targetIdx), ctx);
|
|
546
|
-
const condExpr = br.when;
|
|
547
|
-
const nodeId = k === 0 ? branchStep.id : `${branchStep.id}__branch${k}`;
|
|
548
|
-
otherwise = [
|
|
549
|
-
{
|
|
550
|
-
kind: "branch",
|
|
551
|
-
id: nodeId,
|
|
552
|
-
cond: (b) => evalPredicate(condExpr, b),
|
|
553
|
-
then: thenSteps,
|
|
554
|
-
...otherwise.length > 0 ? { otherwise } : {}
|
|
555
|
-
}
|
|
556
|
-
];
|
|
557
845
|
}
|
|
558
|
-
|
|
846
|
+
throw new Error(`step '${step.id}': output_invalid \u2014 final message never matched outputSchema (${lastErr})`);
|
|
559
847
|
}
|
|
560
|
-
function
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
848
|
+
function tryParseJson(text) {
|
|
849
|
+
const trimmed = text.trim();
|
|
850
|
+
if (!trimmed) return void 0;
|
|
851
|
+
try {
|
|
852
|
+
return JSON.parse(trimmed);
|
|
853
|
+
} catch {
|
|
854
|
+
return void 0;
|
|
565
855
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
856
|
+
}
|
|
857
|
+
function defaultRunGateCommand(spec) {
|
|
858
|
+
return new Promise((resolve2) => {
|
|
859
|
+
execFile(
|
|
860
|
+
spec.command,
|
|
861
|
+
[...spec.args],
|
|
862
|
+
{ cwd: spec.cwd, timeout: spec.timeoutMs, maxBuffer: 10 * 1024 * 1024 },
|
|
863
|
+
(err, stdout, stderr) => {
|
|
864
|
+
if (!err) {
|
|
865
|
+
resolve2({ exitCode: 0, stdout, stderr });
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
const nodeErr = err;
|
|
869
|
+
const timedOut = nodeErr.killed === true && nodeErr.signal !== void 0 && spec.timeoutMs !== void 0;
|
|
870
|
+
const exitCode = typeof nodeErr.code === "number" ? nodeErr.code : 1;
|
|
871
|
+
resolve2({ exitCode, stdout, stderr, ...timedOut ? { timedOut: true } : {} });
|
|
872
|
+
}
|
|
873
|
+
);
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
async function resolveGateReport(cmd, cwd, reportPath) {
|
|
877
|
+
const fromStdout = tryParseJson(cmd.stdout);
|
|
878
|
+
if (fromStdout !== void 0) return fromStdout;
|
|
879
|
+
if (!reportPath) return void 0;
|
|
880
|
+
try {
|
|
881
|
+
const abs = isAbsolute(reportPath) ? reportPath : join(cwd, reportPath);
|
|
882
|
+
const raw = await readFile(abs, "utf8");
|
|
883
|
+
return tryParseJson(raw);
|
|
884
|
+
} catch {
|
|
885
|
+
return void 0;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
function resolveGateCwd(step, ctx, b) {
|
|
889
|
+
const fallback = ctx.cwd ?? process.cwd();
|
|
890
|
+
if (!step.cwd) return fallback;
|
|
891
|
+
const resolved = resolveRefString(step.id, "cwd", resolveSel(step.cwd, b), b, "error");
|
|
892
|
+
return isAbsolute(resolved) ? resolved : resolve(fallback, resolved);
|
|
893
|
+
}
|
|
894
|
+
async function execGateStep(step, ctx, b) {
|
|
895
|
+
const cwd = resolveGateCwd(step, ctx, b);
|
|
896
|
+
const args = (step.args ?? []).map(
|
|
897
|
+
(arg, index) => resolveRefString(step.id, `args[${index}]`, resolveSel(arg, b), b, "error")
|
|
898
|
+
);
|
|
899
|
+
const maxAttempts = Math.max(1, step.retry?.maxAttempts ?? 1);
|
|
900
|
+
let last;
|
|
901
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
902
|
+
if (attempt > 1 && step.onFail?.reprompt) {
|
|
903
|
+
if (!ctx.agents) {
|
|
904
|
+
throw new Error(`step '${step.id}': on_fail.reprompt requires a host agents implementation`);
|
|
905
|
+
}
|
|
906
|
+
const targetSessionId = ctx.agents.resolveByLabel(step.onFail.reprompt);
|
|
907
|
+
if (!targetSessionId) {
|
|
908
|
+
throw new Error(
|
|
909
|
+
`step '${step.id}': on_fail.reprompt targets unknown step '${step.onFail.reprompt}' \u2014 no session found`
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
const reportText = JSON.stringify(last?.report ?? null, null, 2);
|
|
913
|
+
const extra = step.onFail.with ? `
|
|
914
|
+
|
|
915
|
+
Additional context:
|
|
916
|
+
${JSON.stringify(step.onFail.with, null, 2)}` : "";
|
|
917
|
+
await ctx.agents.sendPromptAndWait(
|
|
918
|
+
targetSessionId,
|
|
919
|
+
`Gate '${step.id}' failed (exit code ${last?.exitCode}). Report:
|
|
920
|
+
${reportText}${extra}
|
|
921
|
+
|
|
922
|
+
Please address the findings above, then reply when done.`
|
|
578
923
|
);
|
|
579
924
|
}
|
|
580
|
-
|
|
581
|
-
|
|
925
|
+
if (attempt > 1 && step.retry?.initialMs) {
|
|
926
|
+
const delayMs = step.retry.backoff === "exponential" ? step.retry.initialMs * 2 ** (attempt - 2) : step.retry.initialMs;
|
|
927
|
+
await new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
928
|
+
}
|
|
929
|
+
const cmdResult = await (ctx.runGateCommand ?? defaultRunGateCommand)({
|
|
930
|
+
command: step.command,
|
|
931
|
+
args,
|
|
932
|
+
cwd,
|
|
933
|
+
timeoutMs: step.timeoutMs
|
|
934
|
+
});
|
|
935
|
+
const report = await resolveGateReport(cmdResult, cwd, step.reportPath);
|
|
936
|
+
const ok = cmdResult.exitCode === 0;
|
|
937
|
+
last = { ok, exitCode: cmdResult.exitCode, report };
|
|
938
|
+
ctx.onGateReport?.({ stepId: step.id, ok, exitCode: cmdResult.exitCode, report, attempt });
|
|
939
|
+
if (ok) break;
|
|
940
|
+
}
|
|
941
|
+
if (!last.ok) {
|
|
942
|
+
const err = new Error(
|
|
943
|
+
`step '${step.id}': gate failed after ${maxAttempts} attempt(s) \u2014 exit code ${last.exitCode}`
|
|
944
|
+
);
|
|
945
|
+
err.exitCode = last.exitCode;
|
|
946
|
+
err.report = last.report;
|
|
947
|
+
throw err;
|
|
948
|
+
}
|
|
949
|
+
return last;
|
|
950
|
+
}
|
|
951
|
+
async function execStep(step, ctx, item, index) {
|
|
952
|
+
const { state, signal } = ctx;
|
|
953
|
+
const b = view(state, item, index);
|
|
954
|
+
if (step.kind !== "agent") {
|
|
955
|
+
ctx.onStepStart?.(step.id);
|
|
582
956
|
}
|
|
583
|
-
return buildAgentStep(id, {
|
|
584
|
-
prompt: (b) => String(resolveValue(prompt, b)),
|
|
585
|
-
...adapter !== void 0 ? { adapter } : {},
|
|
586
|
-
...step.sessionRef !== void 0 ? { sessionRef: step.sessionRef } : {},
|
|
587
|
-
...step.sandbox !== void 0 ? { sandbox: step.sandbox } : {},
|
|
588
|
-
...step.cacheable ? { cacheable: true } : {},
|
|
589
|
-
...options !== void 0 ? { options } : {},
|
|
590
|
-
policy: step.policy,
|
|
591
|
-
...step.outputSchema !== void 0 ? { outputSchema: step.outputSchema } : {},
|
|
592
|
-
...step.maxRetries !== void 0 ? { maxRetries: step.maxRetries } : {}
|
|
593
|
-
});
|
|
594
|
-
}
|
|
595
|
-
function compileStep(step, ctx) {
|
|
596
|
-
const opts = ctx.opts;
|
|
597
|
-
const id = step.id;
|
|
598
957
|
switch (step.kind) {
|
|
599
958
|
case "tool": {
|
|
600
|
-
const
|
|
601
|
-
const
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
};
|
|
959
|
+
const input = step.input(b);
|
|
960
|
+
const runIt = () => runTool({
|
|
961
|
+
tool: step.tool,
|
|
962
|
+
candidates: step.candidates,
|
|
963
|
+
input,
|
|
964
|
+
context: step.context ? step.context(b) : void 0,
|
|
965
|
+
resolverContext: step.resolverContext,
|
|
966
|
+
secrets: step.secrets,
|
|
967
|
+
signal
|
|
968
|
+
});
|
|
969
|
+
if (!isCacheEnabled(ctx, step)) return runIt();
|
|
970
|
+
const c = await readStepCache(ctx, step, input);
|
|
971
|
+
if (c.hit) return c.output;
|
|
972
|
+
const out = await runIt();
|
|
973
|
+
await ctx.cache.set(c.key, { output: out, resolvedInputHash: c.hash });
|
|
974
|
+
return out;
|
|
615
975
|
}
|
|
976
|
+
case "transform":
|
|
977
|
+
return step.compute(b);
|
|
616
978
|
case "map": {
|
|
617
|
-
const
|
|
618
|
-
const
|
|
619
|
-
const
|
|
979
|
+
const arr = [...step.over(b)];
|
|
980
|
+
const parallelism = Math.max(1, step.parallelism ?? 1);
|
|
981
|
+
const tolerant = step.onError === "collect";
|
|
982
|
+
const results = new Array(arr.length);
|
|
983
|
+
for (let i = 0; i < arr.length; i += parallelism) {
|
|
984
|
+
const chunk = arr.slice(i, i + parallelism);
|
|
985
|
+
if (!tolerant) {
|
|
986
|
+
const outs = await Promise.all(
|
|
987
|
+
chunk.map((el, j) => {
|
|
988
|
+
const idx = i + j;
|
|
989
|
+
const inner = step.body(el, idx, view(state, el, idx));
|
|
990
|
+
return execStep(inner, ctx, el, idx);
|
|
991
|
+
})
|
|
992
|
+
);
|
|
993
|
+
for (let j = 0; j < outs.length; j++) results[i + j] = outs[j];
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
const settled = await Promise.allSettled(
|
|
997
|
+
chunk.map((el, j) => {
|
|
998
|
+
const idx = i + j;
|
|
999
|
+
const inner = step.body(el, idx, view(state, el, idx));
|
|
1000
|
+
return execStep(inner, ctx, el, idx);
|
|
1001
|
+
})
|
|
1002
|
+
);
|
|
1003
|
+
settled.forEach((s, j) => {
|
|
1004
|
+
const idx = i + j;
|
|
1005
|
+
results[idx] = s.status === "fulfilled" ? { status: "fulfilled", index: idx, value: s.value } : { status: "rejected", index: idx, item: chunk[j], error: errorMessage(s.reason) };
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
if (!tolerant) return results;
|
|
1009
|
+
const outcomes = results;
|
|
620
1010
|
return {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
over: (b) => resolveRef(over, b),
|
|
625
|
-
body: () => inner,
|
|
626
|
-
...onError !== void 0 ? { onError } : {}
|
|
1011
|
+
results: outcomes,
|
|
1012
|
+
succeeded: outcomes.filter((r) => r.status === "fulfilled").length,
|
|
1013
|
+
failed: outcomes.filter((r) => r.status === "rejected").length
|
|
627
1014
|
};
|
|
628
1015
|
}
|
|
629
|
-
case "
|
|
630
|
-
const
|
|
1016
|
+
case "pipeline": {
|
|
1017
|
+
const items = [...step.over(b)];
|
|
1018
|
+
const tolerant = step.onError === "collect";
|
|
1019
|
+
if (items.length === 0) return tolerant ? { results: [], succeeded: 0, failed: 0 } : [];
|
|
1020
|
+
const cap = Math.max(1, step.concurrency ?? items.length);
|
|
1021
|
+
const results = new Array(items.length);
|
|
1022
|
+
let next = 0;
|
|
1023
|
+
const runItem = async (idx) => {
|
|
1024
|
+
let prev = void 0;
|
|
1025
|
+
try {
|
|
1026
|
+
for (const stage of step.stages) {
|
|
1027
|
+
prev = await execStep(stage(items[idx], idx, prev, view(state, items[idx], idx)), ctx, items[idx], idx);
|
|
1028
|
+
}
|
|
1029
|
+
results[idx] = tolerant ? { status: "fulfilled", index: idx, value: prev } : prev;
|
|
1030
|
+
} catch (err) {
|
|
1031
|
+
if (!tolerant) throw err;
|
|
1032
|
+
results[idx] = { status: "rejected", index: idx, item: items[idx], error: errorMessage(err) };
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
const worker = async () => {
|
|
1036
|
+
while (next < items.length) {
|
|
1037
|
+
const idx = next++;
|
|
1038
|
+
await runItem(idx);
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
await Promise.all(Array.from({ length: Math.min(cap, items.length) }, () => worker()));
|
|
1042
|
+
if (!tolerant) return results;
|
|
1043
|
+
const outcomes = results;
|
|
631
1044
|
return {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
while: (b) => evalPredicate(whileExpr, b),
|
|
636
|
-
body: [compileStepList(f(step, "steps"), `${id}__body`, ctx)]
|
|
1045
|
+
results: outcomes,
|
|
1046
|
+
succeeded: outcomes.filter((r) => r.status === "fulfilled").length,
|
|
1047
|
+
failed: outcomes.filter((r) => r.status === "rejected").length
|
|
637
1048
|
};
|
|
638
1049
|
}
|
|
1050
|
+
case "branch": {
|
|
1051
|
+
const chosen = step.cond(b) ? step.then : step.otherwise ?? [];
|
|
1052
|
+
return runSequence(chosen, ctx, item, index);
|
|
1053
|
+
}
|
|
1054
|
+
case "loop": {
|
|
1055
|
+
let iterations = 0;
|
|
1056
|
+
let last;
|
|
1057
|
+
while (iterations < step.maxIterations && step.while(view(state, item, index))) {
|
|
1058
|
+
last = await runSequence(step.body, ctx, item, index);
|
|
1059
|
+
iterations++;
|
|
1060
|
+
}
|
|
1061
|
+
return last;
|
|
1062
|
+
}
|
|
639
1063
|
case "parallel": {
|
|
640
|
-
const
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
};
|
|
1064
|
+
const outs = await Promise.all(
|
|
1065
|
+
step.branches.map(
|
|
1066
|
+
(br) => runSequence(br.steps, ctx, item, index).then((last) => ({
|
|
1067
|
+
id: br.id,
|
|
1068
|
+
last
|
|
1069
|
+
}))
|
|
1070
|
+
)
|
|
1071
|
+
);
|
|
1072
|
+
const record = {};
|
|
1073
|
+
for (const { id, last } of outs) record[id] = last;
|
|
1074
|
+
return record;
|
|
649
1075
|
}
|
|
650
1076
|
case "approval": {
|
|
651
|
-
const prompt =
|
|
652
|
-
const approvers =
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
kind: "approval",
|
|
657
|
-
id,
|
|
1077
|
+
const prompt = step.prompt(b);
|
|
1078
|
+
const approvers = step.approvers ?? [];
|
|
1079
|
+
const raw = ctx.approve ? await ctx.approve({
|
|
1080
|
+
stepId: step.id,
|
|
1081
|
+
prompt,
|
|
658
1082
|
approvers,
|
|
659
|
-
|
|
660
|
-
|
|
1083
|
+
...step.artifacts !== void 0 ? { artifacts: step.artifacts } : {},
|
|
1084
|
+
...step.timeoutMs !== void 0 ? { timeoutMs: step.timeoutMs } : {}
|
|
1085
|
+
}) : true;
|
|
1086
|
+
const decision = typeof raw === "boolean" ? { approved: raw, who: "host" } : raw;
|
|
1087
|
+
const approved = decision.approved;
|
|
1088
|
+
const followups = approved ? step.onApprove ?? [] : step.onReject ?? [];
|
|
1089
|
+
await runSequence(followups, ctx, item, index);
|
|
1090
|
+
return { approved, who: decision.who, ...decision.note !== void 0 ? { note: decision.note } : {} };
|
|
661
1091
|
}
|
|
662
1092
|
case "suspend": {
|
|
663
|
-
|
|
664
|
-
return {
|
|
1093
|
+
if (!ctx.resume) throw new WorkflowSuspendedError(step.id, step.on);
|
|
1094
|
+
return ctx.resume({ stepId: step.id, on: step.on });
|
|
665
1095
|
}
|
|
1096
|
+
case "group":
|
|
1097
|
+
return runSequence(step.steps, ctx, item, index);
|
|
666
1098
|
case "subworkflow": {
|
|
667
|
-
const
|
|
668
|
-
const child =
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
id,
|
|
682
|
-
workflow: compiledChild,
|
|
683
|
-
input: (b) => resolveValue(inputs, b)
|
|
684
|
-
};
|
|
1099
|
+
const childInput = step.input ? step.input(b) : state.input;
|
|
1100
|
+
const child = await runWorkflowInner(step.workflow, childInput, {
|
|
1101
|
+
approve: ctx.approve,
|
|
1102
|
+
resume: ctx.resume,
|
|
1103
|
+
signal,
|
|
1104
|
+
agents: ctx.agents,
|
|
1105
|
+
cwd: ctx.cwd,
|
|
1106
|
+
workspaceSlug: ctx.workspaceSlug,
|
|
1107
|
+
cache: ctx.cache,
|
|
1108
|
+
cacheKey: ctx.cacheKey,
|
|
1109
|
+
runGateCommand: ctx.runGateCommand,
|
|
1110
|
+
onGateReport: ctx.onGateReport
|
|
1111
|
+
});
|
|
1112
|
+
return child.output;
|
|
685
1113
|
}
|
|
686
1114
|
case "agent": {
|
|
687
|
-
if (
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
const
|
|
695
|
-
return
|
|
1115
|
+
if (!ctx.agents) throw new Error(`step '${step.id}': AgentStep requires a host agents implementation`);
|
|
1116
|
+
if (!isCacheEnabled(ctx, step)) return execAgentStep(step, ctx, b);
|
|
1117
|
+
const resolved = {
|
|
1118
|
+
prompt: step.prompt(b),
|
|
1119
|
+
adapter: step.adapter ? resolveSel(step.adapter, b) : void 0,
|
|
1120
|
+
sessionRef: step.sessionRef
|
|
1121
|
+
};
|
|
1122
|
+
const c = await readStepCache(ctx, step, resolved);
|
|
1123
|
+
if (c.hit) return c.output;
|
|
1124
|
+
const out = await execAgentStep(step, ctx, b);
|
|
1125
|
+
await ctx.cache.set(c.key, { output: out, resolvedInputHash: c.hash });
|
|
1126
|
+
return out;
|
|
696
1127
|
}
|
|
697
|
-
case "
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1128
|
+
case "gate":
|
|
1129
|
+
return execGateStep(step, ctx, b);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
async function runWorkflowInner(workflow, input, hooks, maxTotalCostUsd) {
|
|
1133
|
+
const state = { input, steps: {}, costBySession: /* @__PURE__ */ new Map(), maxTotalCostUsd };
|
|
1134
|
+
const ctx = { state, ...hooks };
|
|
1135
|
+
let lastId;
|
|
1136
|
+
for (const step of workflow.steps) {
|
|
1137
|
+
const out = await execStep(step, ctx, void 0, void 0);
|
|
1138
|
+
state.steps[step.id] = out;
|
|
1139
|
+
ctx.onStepComplete?.(step.id, out);
|
|
1140
|
+
lastId = step.id;
|
|
705
1141
|
}
|
|
1142
|
+
const bindings = view(state);
|
|
1143
|
+
const output = workflow.output ? workflow.output(bindings) : lastId !== void 0 ? state.steps[lastId] : void 0;
|
|
1144
|
+
return { output, bindings };
|
|
1145
|
+
}
|
|
1146
|
+
async function runWorkflow(args) {
|
|
1147
|
+
return runWorkflowInner(args.workflow, args.input, {
|
|
1148
|
+
approve: args.approve,
|
|
1149
|
+
resume: args.resume,
|
|
1150
|
+
signal: args.signal,
|
|
1151
|
+
agents: args.agents,
|
|
1152
|
+
cwd: args.cwd,
|
|
1153
|
+
workspaceSlug: args.workspaceSlug,
|
|
1154
|
+
cache: args.cache,
|
|
1155
|
+
cacheKey: args.cacheKey,
|
|
1156
|
+
onStepStart: args.onStepStart,
|
|
1157
|
+
onStepComplete: args.onStepComplete,
|
|
1158
|
+
runGateCommand: args.runGateCommand,
|
|
1159
|
+
onGateReport: args.onGateReport
|
|
1160
|
+
}, args.maxTotalCostUsd);
|
|
706
1161
|
}
|
|
707
1162
|
function compileWorkflowManifest(source, opts) {
|
|
708
1163
|
const manifest = parseWorkflowManifest(source);
|
|
@@ -710,6 +1165,6 @@ function compileWorkflowManifest(source, opts) {
|
|
|
710
1165
|
return compileWorkflow(handle, opts);
|
|
711
1166
|
}
|
|
712
1167
|
|
|
713
|
-
export { WorkflowCompileError, WorkflowSuspendedError, buildAgentStep, compileWorkflow, compileWorkflowManifest, evalPredicate, resolveRef, resolveValue, runWorkflow };
|
|
1168
|
+
export { NodeFsPort, WorkflowCompileError, WorkflowSuspendedError, buildAgentStep, compileWorkflow, compileWorkflowManifest, evalPredicate, materializeKnowledge, resolveKnowledgeSelectors, resolveRef, resolveRefPrefixed, resolveValue, runWorkflow };
|
|
714
1169
|
//# sourceMappingURL=index.mjs.map
|
|
715
1170
|
//# sourceMappingURL=index.mjs.map
|