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