@agentproto/workflow-runtime 0.11.1 → 0.13.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/README.md +200 -5
- package/dist/index.d.ts +447 -13
- package/dist/index.mjs +798 -106
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -6
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { runTool } from '@agentproto/driver';
|
|
2
2
|
import { createHash } from 'crypto';
|
|
3
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';
|
|
4
|
+
import { stat, readFile, mkdir, writeFile, appendFile, readdir, copyFile } from 'fs/promises';
|
|
5
|
+
import { join, dirname, isAbsolute, basename, resolve, relative } from 'path';
|
|
6
|
+
import { z } from 'zod';
|
|
6
7
|
import { assertKnownStepRefs } from '@agentproto/workflow';
|
|
8
|
+
import Ajv from 'ajv';
|
|
9
|
+
import addFormats from 'ajv-formats';
|
|
7
10
|
import { isRefinedKind, filterEntriesByAllOf, resolveKnowledge } from '@agentproto/corpus';
|
|
8
11
|
import { parseWorkflowManifest, workflowFromManifest } from '@agentproto/workflow/manifest';
|
|
9
12
|
|
|
@@ -20,6 +23,7 @@ function buildAgentStep(id, fields) {
|
|
|
20
23
|
kind: "agent",
|
|
21
24
|
id,
|
|
22
25
|
...fields.adapter !== void 0 ? { adapter: fields.adapter } : {},
|
|
26
|
+
...fields.cwd !== void 0 ? { cwd: fields.cwd } : {},
|
|
23
27
|
...fields.model !== void 0 ? { model: fields.model } : {},
|
|
24
28
|
...fields.sessionRef !== void 0 ? { sessionRef: fields.sessionRef } : {},
|
|
25
29
|
...fields.sandbox !== void 0 ? { sandbox: fields.sandbox } : {},
|
|
@@ -29,7 +33,85 @@ function buildAgentStep(id, fields) {
|
|
|
29
33
|
policy: fields.policy ?? { awaiting: "fail" },
|
|
30
34
|
...fields.outputSchema !== void 0 ? { outputSchema: fields.outputSchema } : {},
|
|
31
35
|
...fields.maxRetries !== void 0 ? { maxRetries: fields.maxRetries } : {},
|
|
32
|
-
...fields.harness !== void 0 ? { harness: fields.harness } : {}
|
|
36
|
+
...fields.harness !== void 0 ? { harness: fields.harness } : {},
|
|
37
|
+
...fields.agentTools !== void 0 ? { agentTools: fields.agentTools } : {}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
41
|
+
function isJsonSchemaObject(inputs) {
|
|
42
|
+
return inputs["type"] === "object" && isPlainObject(inputs["properties"]);
|
|
43
|
+
}
|
|
44
|
+
function normalizeWorkflowInputsSchema(inputs) {
|
|
45
|
+
if (!isPlainObject(inputs)) return { type: "object", properties: {} };
|
|
46
|
+
if (isJsonSchemaObject(inputs)) return inputs;
|
|
47
|
+
const properties = {};
|
|
48
|
+
const required = [];
|
|
49
|
+
for (const [key, rawSpec] of Object.entries(inputs)) {
|
|
50
|
+
const spec = isPlainObject(rawSpec) ? rawSpec : {};
|
|
51
|
+
const { required: isRequired, ...schemaFields } = spec;
|
|
52
|
+
properties[key] = schemaFields;
|
|
53
|
+
if (isRequired === true && spec["default"] === void 0) required.push(key);
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
type: "object",
|
|
57
|
+
properties,
|
|
58
|
+
...required.length > 0 ? { required } : {}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
var _ajv = addFormats(new Ajv({ allErrors: true }));
|
|
62
|
+
var _validatorCache = /* @__PURE__ */ new WeakMap();
|
|
63
|
+
function compile(schema) {
|
|
64
|
+
let validate = _validatorCache.get(schema);
|
|
65
|
+
if (!validate) {
|
|
66
|
+
validate = _ajv.compile(schema);
|
|
67
|
+
_validatorCache.set(schema, validate);
|
|
68
|
+
}
|
|
69
|
+
return validate;
|
|
70
|
+
}
|
|
71
|
+
function fieldFor(err) {
|
|
72
|
+
if (err.keyword === "required") {
|
|
73
|
+
return String(err.params.missingProperty ?? "");
|
|
74
|
+
}
|
|
75
|
+
return err.instancePath.replace(/^\//, "") || "(root)";
|
|
76
|
+
}
|
|
77
|
+
function isCompilableJsonSchema(schema) {
|
|
78
|
+
if (!isPlainObject(schema)) return false;
|
|
79
|
+
try {
|
|
80
|
+
compile(schema);
|
|
81
|
+
return true;
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function toIssue(err) {
|
|
87
|
+
const path = err.instancePath.replace(/^\//, "").split("/").filter((seg) => seg.length > 0);
|
|
88
|
+
if (err.keyword === "required") {
|
|
89
|
+
const missing = err.params.missingProperty;
|
|
90
|
+
if (missing) path.push(missing);
|
|
91
|
+
}
|
|
92
|
+
return { path, message: err.message ?? "invalid" };
|
|
93
|
+
}
|
|
94
|
+
function validateAgainstJsonSchema(schema, value) {
|
|
95
|
+
const validate = compile(schema);
|
|
96
|
+
if (validate(value)) return { valid: true };
|
|
97
|
+
const errors = validate.errors ?? [];
|
|
98
|
+
const issues = errors.map(toIssue);
|
|
99
|
+
const detail = errors.map((e) => `${e.instancePath || "/"} ${e.message}`).join("; ");
|
|
100
|
+
return { valid: false, message: `does not match schema: ${detail}`, issues };
|
|
101
|
+
}
|
|
102
|
+
function validateWorkflowInput(inputsField, input) {
|
|
103
|
+
const schema = normalizeWorkflowInputsSchema(inputsField);
|
|
104
|
+
const validate = compile(schema);
|
|
105
|
+
if (validate(input ?? {})) return { valid: true, schema };
|
|
106
|
+
const errors = validate.errors ?? [];
|
|
107
|
+
const fields = [...new Set(errors.map(fieldFor).filter((f2) => f2.length > 0))];
|
|
108
|
+
const detail = errors.map((e) => `${e.instancePath || "/"} ${e.message}`).join("; ");
|
|
109
|
+
return {
|
|
110
|
+
valid: false,
|
|
111
|
+
schema,
|
|
112
|
+
code: "invalid-input",
|
|
113
|
+
fields,
|
|
114
|
+
message: `invalid input${fields.length > 0 ? ` (${fields.join(", ")})` : ""}: ${detail}`
|
|
33
115
|
};
|
|
34
116
|
}
|
|
35
117
|
|
|
@@ -49,8 +131,8 @@ function dig(value, path) {
|
|
|
49
131
|
}
|
|
50
132
|
return v;
|
|
51
133
|
}
|
|
52
|
-
var REF_RE = /^\$(input|item|steps)((?:\.[^.]+)*)$/;
|
|
53
|
-
var REF_PREFIX_RE = /^\$(?:input|item|steps|index)((?:\.[^.$/]+)*)/;
|
|
134
|
+
var REF_RE = /^\$(input|item|steps|run)((?:\.[^.]+)*)$/;
|
|
135
|
+
var REF_PREFIX_RE = /^\$(?:input|item|steps|run|index)((?:\.[^.$/]+)*)/;
|
|
54
136
|
function resolveRef(ref, b) {
|
|
55
137
|
if (ref === "$index") return b.index;
|
|
56
138
|
const m = ref.match(REF_RE);
|
|
@@ -66,6 +148,10 @@ function resolveRef(ref, b) {
|
|
|
66
148
|
if (!stepId) throw new WorkflowCompileError(`'$steps' needs a step id`);
|
|
67
149
|
return dig(b.steps[stepId], rest);
|
|
68
150
|
}
|
|
151
|
+
// AIP-58 §4 — `$run.workspace`, a convenience alias for the run
|
|
152
|
+
// workspace path (see `Bindings.run`).
|
|
153
|
+
case "run":
|
|
154
|
+
return dig(b.run, segs);
|
|
69
155
|
}
|
|
70
156
|
}
|
|
71
157
|
function resolveRefPrefixed(value, b) {
|
|
@@ -74,6 +160,72 @@ function resolveRefPrefixed(value, b) {
|
|
|
74
160
|
const token = value.slice(0, m[0].length);
|
|
75
161
|
return { resolved: resolveRef(token, b), rest: value.slice(m[0].length) };
|
|
76
162
|
}
|
|
163
|
+
var TAG_NAME = "(?:[\\w$-]+)(?:\\.(?:[\\w$-]+))*";
|
|
164
|
+
var TAG_RE = new RegExp(`\\{\\{(${TAG_NAME})\\}\\}`, "g");
|
|
165
|
+
var SECTION_RE = new RegExp(
|
|
166
|
+
`\\{\\{([#^])(${TAG_NAME})\\}\\}([\\s\\S]*?)\\{\\{/\\2\\}\\}`
|
|
167
|
+
);
|
|
168
|
+
function resolveTagName(name, b) {
|
|
169
|
+
const segs = name.split(".");
|
|
170
|
+
const head = segs[0];
|
|
171
|
+
if (head === "input") return segs.length === 1 ? b.input : dig(b.input, segs.slice(1));
|
|
172
|
+
if (head === "item") return segs.length === 1 ? b.item : dig(b.item, segs.slice(1));
|
|
173
|
+
if (head === "steps" && segs.length > 1)
|
|
174
|
+
return dig(b.steps[segs[1]], segs.slice(2));
|
|
175
|
+
if (head === "index" && segs.length === 1) return b.index;
|
|
176
|
+
if (head === "run") return segs.length === 1 ? b.run : dig(b.run, segs.slice(1));
|
|
177
|
+
return dig(b.input, segs);
|
|
178
|
+
}
|
|
179
|
+
function stringifyValue(v) {
|
|
180
|
+
if (v === null) return "";
|
|
181
|
+
if (typeof v === "string") return v;
|
|
182
|
+
if (typeof v === "object") return JSON.stringify(v);
|
|
183
|
+
return String(v);
|
|
184
|
+
}
|
|
185
|
+
function interpolateTemplate(template, b) {
|
|
186
|
+
let out = template;
|
|
187
|
+
for (; ; ) {
|
|
188
|
+
const m = out.match(SECTION_RE);
|
|
189
|
+
if (!m) break;
|
|
190
|
+
const kind = m[1];
|
|
191
|
+
const name = m[2];
|
|
192
|
+
const inner = m[3];
|
|
193
|
+
const value = resolveTagName(name, b);
|
|
194
|
+
const truthy = kind === "#" ? Boolean(value) : !value;
|
|
195
|
+
const start = m.index;
|
|
196
|
+
const end = start + m[0].length;
|
|
197
|
+
const before = out.slice(0, start);
|
|
198
|
+
const after = out.slice(end);
|
|
199
|
+
const lineStart = before.lastIndexOf("\n") + 1;
|
|
200
|
+
const nl = after.indexOf("\n");
|
|
201
|
+
const standalone = before.slice(lineStart).trim() === "" && (nl === -1 ? after : after.slice(0, nl)).trim() === "";
|
|
202
|
+
let replacement;
|
|
203
|
+
if (!truthy) {
|
|
204
|
+
replacement = "";
|
|
205
|
+
} else {
|
|
206
|
+
replacement = interpolateTemplate(inner, b);
|
|
207
|
+
if (standalone) {
|
|
208
|
+
replacement = replacement.replace(/^\n/, "").replace(/\n[ \t]*$/, "");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
out = (standalone ? before.slice(0, lineStart) : before) + replacement + (standalone ? truthy ? nl === -1 ? after : after.slice(nl) : after.slice(nl + 1) : after);
|
|
212
|
+
}
|
|
213
|
+
return out.replace(TAG_RE, (whole, name) => {
|
|
214
|
+
const v = resolveTagName(name, b);
|
|
215
|
+
return v === void 0 ? whole : stringifyValue(v);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
function renderPrompt(raw, b) {
|
|
219
|
+
if (raw.startsWith("$$")) return raw.slice(1);
|
|
220
|
+
if (raw.startsWith("$")) {
|
|
221
|
+
const pref = resolveRefPrefixed(raw, b);
|
|
222
|
+
if (pref) {
|
|
223
|
+
const head = pref.rest === "" ? String(pref.resolved) : stringifyValue(pref.resolved);
|
|
224
|
+
return head + interpolateTemplate(pref.rest, b);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return interpolateTemplate(raw, b);
|
|
228
|
+
}
|
|
77
229
|
function resolveValue(node, b) {
|
|
78
230
|
if (typeof node === "string") {
|
|
79
231
|
if (node.startsWith("$$")) return node.slice(1);
|
|
@@ -101,7 +253,7 @@ function refPathExists(ref, b) {
|
|
|
101
253
|
segs.length = 0;
|
|
102
254
|
segs.push(...rest);
|
|
103
255
|
} else {
|
|
104
|
-
v = m[1] === "input" ? b.input : b.item;
|
|
256
|
+
v = m[1] === "input" ? b.input : m[1] === "run" ? b.run : b.item;
|
|
105
257
|
}
|
|
106
258
|
for (const seg of segs) {
|
|
107
259
|
if (v === null || typeof v !== "object") return false;
|
|
@@ -204,11 +356,16 @@ function compileWorkflow(handle, opts) {
|
|
|
204
356
|
const ctx = { opts, knownStepIds: collectStepIds(steps) };
|
|
205
357
|
const result = handle.result;
|
|
206
358
|
const output = result !== void 0 ? (b) => resolveValue(result, b) : void 0;
|
|
359
|
+
const finallySteps = handle.finally;
|
|
360
|
+
const finallyCtx = finallySteps?.length ? { opts, knownStepIds: /* @__PURE__ */ new Set([...ctx.knownStepIds, ...collectStepIds(finallySteps)]) } : ctx;
|
|
361
|
+
const outputsFiles = handle.outputsFiles;
|
|
207
362
|
return {
|
|
208
363
|
id: handle.id,
|
|
209
364
|
description: handle.description,
|
|
210
365
|
steps: compileSiblingsToSteps(steps, ctx),
|
|
211
|
-
...
|
|
366
|
+
...finallySteps?.length ? { finally: compileSiblingsToSteps(finallySteps, finallyCtx) } : {},
|
|
367
|
+
...output ? { output } : {},
|
|
368
|
+
...outputsFiles ? { outputsFiles } : {}
|
|
212
369
|
};
|
|
213
370
|
}
|
|
214
371
|
function compileSiblingsToSteps(steps, ctx) {
|
|
@@ -217,8 +374,14 @@ function compileSiblingsToSteps(steps, ctx) {
|
|
|
217
374
|
for (let i = 0; i < steps.length; i++) {
|
|
218
375
|
const s = steps[i];
|
|
219
376
|
if (s.kind === "branch") {
|
|
220
|
-
|
|
221
|
-
|
|
377
|
+
if (s.fallthrough === true) {
|
|
378
|
+
out.push(compileBranchChain(steps, i, ctx));
|
|
379
|
+
return out;
|
|
380
|
+
}
|
|
381
|
+
const { node, joinIdx } = compileExclusiveBranch(steps, i, ctx);
|
|
382
|
+
out.push(node);
|
|
383
|
+
i = joinIdx - 1;
|
|
384
|
+
continue;
|
|
222
385
|
}
|
|
223
386
|
out.push(compileStep(s, ctx));
|
|
224
387
|
}
|
|
@@ -229,19 +392,74 @@ function compileStepList(steps, id, ctx) {
|
|
|
229
392
|
if (compiled.length === 1) return compiled[0];
|
|
230
393
|
return { kind: "group", id, steps: compiled };
|
|
231
394
|
}
|
|
232
|
-
function
|
|
395
|
+
function resolveBranchTarget(steps, i, targetId, label) {
|
|
396
|
+
const idx = steps.findIndex((s) => s.id === targetId);
|
|
397
|
+
if (idx === -1 || idx <= i) {
|
|
398
|
+
throw new WorkflowCompileError(
|
|
399
|
+
`branch '${steps[i].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.`
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
return idx;
|
|
403
|
+
}
|
|
404
|
+
function compileExclusiveBranch(steps, i, ctx) {
|
|
233
405
|
const branchStep = steps[i];
|
|
234
406
|
const branches = f(branchStep, "branches");
|
|
235
407
|
const defaultTarget = f(branchStep, "default");
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
408
|
+
const joinTarget = f(branchStep, "join");
|
|
409
|
+
const armIdx = branches.map((br, k) => resolveBranchTarget(steps, i, br.next, `branches[${k}].next`));
|
|
410
|
+
const defaultIdx = defaultTarget !== void 0 ? resolveBranchTarget(steps, i, defaultTarget, "default") : void 0;
|
|
411
|
+
const starts = [.../* @__PURE__ */ new Set([...armIdx, ...defaultIdx !== void 0 ? [defaultIdx] : []])].sort(
|
|
412
|
+
(a, b) => a - b
|
|
413
|
+
);
|
|
414
|
+
const lastStart = starts[starts.length - 1];
|
|
415
|
+
const joinIdx = joinTarget !== void 0 ? resolveBranchTarget(steps, i, joinTarget, "join") : lastStart + 1;
|
|
416
|
+
if (joinIdx <= lastStart) {
|
|
417
|
+
throw new WorkflowCompileError(
|
|
418
|
+
`branch '${branchStep.id}' join '${joinTarget}' must come after every arm target \u2014 '${steps[lastStart].id}' is an arm target at or after it`
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
if (defaultIdx !== void 0 && starts[0] > i + 1) {
|
|
422
|
+
throw new WorkflowCompileError(
|
|
423
|
+
`branch '${branchStep.id}': step '${steps[i + 1].id}' sits between the branch and its first arm target and belongs to no arm \u2014 with an explicit 'default' it can never run. Move it after the join, or make it an arm target.`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
const bodies = /* @__PURE__ */ new Map();
|
|
427
|
+
const bodyAt = (start) => {
|
|
428
|
+
let body = bodies.get(start);
|
|
429
|
+
if (!body) {
|
|
430
|
+
const pos = starts.indexOf(start);
|
|
431
|
+
const end = pos + 1 < starts.length ? starts[pos + 1] : joinIdx;
|
|
432
|
+
body = compileSiblingsToSteps(steps.slice(start, end), ctx);
|
|
433
|
+
bodies.set(start, body);
|
|
242
434
|
}
|
|
243
|
-
return
|
|
435
|
+
return body;
|
|
244
436
|
};
|
|
437
|
+
let otherwise = defaultIdx !== void 0 ? bodyAt(defaultIdx) : compileSiblingsToSteps(steps.slice(i + 1, starts[0]), ctx);
|
|
438
|
+
for (let k = branches.length - 1; k >= 0; k--) {
|
|
439
|
+
const condExpr = branches[k].when;
|
|
440
|
+
otherwise = [
|
|
441
|
+
{
|
|
442
|
+
kind: "branch",
|
|
443
|
+
id: k === 0 ? branchStep.id : `${branchStep.id}__branch${k}`,
|
|
444
|
+
...k === 0 ? {} : { sourceId: branchStep.id },
|
|
445
|
+
cond: (b) => evalPredicate(condExpr, b),
|
|
446
|
+
then: bodyAt(armIdx[k]),
|
|
447
|
+
...otherwise.length > 0 ? { otherwise } : {}
|
|
448
|
+
}
|
|
449
|
+
];
|
|
450
|
+
}
|
|
451
|
+
return { node: otherwise[0], joinIdx };
|
|
452
|
+
}
|
|
453
|
+
function compileBranchChain(steps, i, ctx) {
|
|
454
|
+
const branchStep = steps[i];
|
|
455
|
+
const branches = f(branchStep, "branches");
|
|
456
|
+
const defaultTarget = f(branchStep, "default");
|
|
457
|
+
if (f(branchStep, "join") !== void 0) {
|
|
458
|
+
throw new WorkflowCompileError(
|
|
459
|
+
`branch '${branchStep.id}' declares both 'fallthrough: true' and 'join' \u2014 a fall-through branch runs every later sibling, so it has no join point`
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
const resolveTarget = (targetId, label) => resolveBranchTarget(steps, i, targetId, label);
|
|
245
463
|
const defaultIdx = defaultTarget !== void 0 ? resolveTarget(defaultTarget, "default") : i + 1;
|
|
246
464
|
let otherwise = compileSiblingsToSteps(steps.slice(defaultIdx), ctx);
|
|
247
465
|
for (let k = branches.length - 1; k >= 0; k--) {
|
|
@@ -254,6 +472,7 @@ function compileBranchChain(steps, i, ctx) {
|
|
|
254
472
|
{
|
|
255
473
|
kind: "branch",
|
|
256
474
|
id: nodeId,
|
|
475
|
+
...k === 0 ? {} : { sourceId: branchStep.id },
|
|
257
476
|
cond: (b) => evalPredicate(condExpr, b),
|
|
258
477
|
then: thenSteps,
|
|
259
478
|
...otherwise.length > 0 ? { otherwise } : {}
|
|
@@ -262,6 +481,25 @@ function compileBranchChain(steps, i, ctx) {
|
|
|
262
481
|
}
|
|
263
482
|
return otherwise[0];
|
|
264
483
|
}
|
|
484
|
+
function hasSafeParse(value) {
|
|
485
|
+
return typeof value === "object" && value !== null && typeof value.safeParse === "function";
|
|
486
|
+
}
|
|
487
|
+
function compileOutputSchema(schema, stepId) {
|
|
488
|
+
if (!isCompilableJsonSchema(schema)) {
|
|
489
|
+
throw new WorkflowCompileError(
|
|
490
|
+
`agent step '${stepId}' has an invalid 'outputSchema' \u2014 not a compilable JSON Schema`
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
safeParse: (value) => {
|
|
495
|
+
const result = validateAgainstJsonSchema(schema, value);
|
|
496
|
+
return result.valid ? { success: true, data: value } : { success: false, error: { issues: result.issues } };
|
|
497
|
+
},
|
|
498
|
+
// F27: lets `describeOutputSchemaForPrompt` (run-workflow.ts) render the
|
|
499
|
+
// EXACT WORKFLOW.md-authored schema into the agent's prompt.
|
|
500
|
+
jsonSchema: schema
|
|
501
|
+
};
|
|
502
|
+
}
|
|
265
503
|
function compileAgentStep(step, id, ctx) {
|
|
266
504
|
const opts = ctx.opts;
|
|
267
505
|
const prompt = f(step, "prompt");
|
|
@@ -270,6 +508,8 @@ function compileAgentStep(step, id, ctx) {
|
|
|
270
508
|
}
|
|
271
509
|
let adapter = typeof step.adapter === "string" ? step.adapter : void 0;
|
|
272
510
|
let options = step.options !== void 0 ? step.options : void 0;
|
|
511
|
+
let model = step.model;
|
|
512
|
+
let agentTools;
|
|
273
513
|
const agentRef = step.agent?.ref;
|
|
274
514
|
if (agentRef !== void 0) {
|
|
275
515
|
if (typeof agentRef !== "string" || agentRef.trim().length === 0) {
|
|
@@ -282,21 +522,37 @@ function compileAgentStep(step, id, ctx) {
|
|
|
282
522
|
`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?)")
|
|
283
523
|
);
|
|
284
524
|
}
|
|
285
|
-
adapter = resolved.adapter;
|
|
286
|
-
|
|
525
|
+
if (adapter === void 0) adapter = resolved.adapter;
|
|
526
|
+
if (model === void 0) model = resolved.model;
|
|
527
|
+
if (options === void 0 && adapter === resolved.adapter) options = resolved.options;
|
|
528
|
+
agentTools = resolved.tools;
|
|
529
|
+
}
|
|
530
|
+
let outputSchema;
|
|
531
|
+
if (step.outputSchema === void 0) {
|
|
532
|
+
console.warn(
|
|
533
|
+
`[workflow-runtime] agent step '${id}' declares no output contract; AIP-58 cannot detect a missing output`
|
|
534
|
+
);
|
|
535
|
+
} else if (hasSafeParse(step.outputSchema)) {
|
|
536
|
+
outputSchema = step.outputSchema;
|
|
537
|
+
} else {
|
|
538
|
+
outputSchema = compileOutputSchema(step.outputSchema, id);
|
|
287
539
|
}
|
|
540
|
+
const rawCwd = step.cwd;
|
|
541
|
+
const cwd = typeof rawCwd === "function" ? rawCwd : typeof rawCwd === "string" ? (b) => resolveRefString(id, "cwd", rawCwd, b, "error") : void 0;
|
|
288
542
|
return buildAgentStep(id, {
|
|
289
|
-
prompt: (b) =>
|
|
543
|
+
prompt: (b) => renderPrompt(prompt, b),
|
|
290
544
|
...adapter !== void 0 ? { adapter } : {},
|
|
545
|
+
...cwd !== void 0 ? { cwd } : {},
|
|
291
546
|
...step.sessionRef !== void 0 ? { sessionRef: step.sessionRef } : {},
|
|
292
|
-
...
|
|
547
|
+
...model !== void 0 ? { model } : {},
|
|
293
548
|
...step.sandbox !== void 0 ? { sandbox: step.sandbox } : {},
|
|
294
549
|
...step.cacheable ? { cacheable: true } : {},
|
|
295
550
|
...options !== void 0 ? { options } : {},
|
|
296
551
|
policy: step.policy,
|
|
297
|
-
...
|
|
552
|
+
...outputSchema !== void 0 ? { outputSchema } : {},
|
|
298
553
|
...step.maxRetries !== void 0 ? { maxRetries: step.maxRetries } : {},
|
|
299
|
-
...step.harness !== void 0 ? { harness: step.harness } : {}
|
|
554
|
+
...step.harness !== void 0 ? { harness: step.harness } : {},
|
|
555
|
+
...agentTools !== void 0 ? { agentTools } : {}
|
|
300
556
|
});
|
|
301
557
|
}
|
|
302
558
|
function compileGateStep(step, id) {
|
|
@@ -351,20 +607,23 @@ function compileStep(step, ctx) {
|
|
|
351
607
|
tool,
|
|
352
608
|
candidates: opts.candidates,
|
|
353
609
|
input: (b) => resolveValue(inputs, b),
|
|
354
|
-
context: opts.contextFor ? (b) => opts.contextFor(toolId, b) : void 0
|
|
610
|
+
context: opts.contextFor ? (b) => opts.contextFor(toolId, b) : void 0,
|
|
611
|
+
...step.cacheable ? { cacheable: true } : {}
|
|
355
612
|
};
|
|
356
613
|
}
|
|
357
614
|
case "map": {
|
|
358
615
|
const over = f(step, "over");
|
|
359
616
|
const inner = compileStepList(f(step, "steps"), `${id}__body`, ctx);
|
|
360
617
|
const onError = f(step, "onError");
|
|
618
|
+
const maxConsecutiveSpawnFailures = f(step, "maxConsecutiveSpawnFailures");
|
|
361
619
|
return {
|
|
362
620
|
kind: "map",
|
|
363
621
|
id,
|
|
364
622
|
parallelism: f(step, "parallelism"),
|
|
365
623
|
over: (b) => resolveRef(over, b),
|
|
366
624
|
body: () => inner,
|
|
367
|
-
...onError !== void 0 ? { onError } : {}
|
|
625
|
+
...onError !== void 0 ? { onError } : {},
|
|
626
|
+
...maxConsecutiveSpawnFailures !== void 0 ? { maxConsecutiveSpawnFailures } : {}
|
|
368
627
|
};
|
|
369
628
|
}
|
|
370
629
|
case "loop": {
|
|
@@ -401,7 +660,7 @@ function compileStep(step, ctx) {
|
|
|
401
660
|
approvers,
|
|
402
661
|
...artifacts !== void 0 ? { artifacts } : {},
|
|
403
662
|
...timeoutMs !== void 0 ? { timeoutMs } : {},
|
|
404
|
-
prompt: (b) =>
|
|
663
|
+
prompt: (b) => renderPrompt(prompt, b)
|
|
405
664
|
};
|
|
406
665
|
}
|
|
407
666
|
case "suspend": {
|
|
@@ -447,6 +706,28 @@ function compileStep(step, ctx) {
|
|
|
447
706
|
}
|
|
448
707
|
case "gate":
|
|
449
708
|
return compileGateStep(step, id);
|
|
709
|
+
case "artifact": {
|
|
710
|
+
if (typeof step.key === "function" || typeof step.path === "function") {
|
|
711
|
+
const passthrough = step;
|
|
712
|
+
return passthrough;
|
|
713
|
+
}
|
|
714
|
+
const key = f(step, "key");
|
|
715
|
+
const rawPath = f(step, "path");
|
|
716
|
+
if (typeof key !== "string" || key.trim().length === 0) {
|
|
717
|
+
throw new WorkflowCompileError(`artifact step '${id}' needs a non-empty 'key'`);
|
|
718
|
+
}
|
|
719
|
+
if (typeof rawPath !== "string" || rawPath.trim().length === 0) {
|
|
720
|
+
throw new WorkflowCompileError(`artifact step '${id}' needs a non-empty 'path'`);
|
|
721
|
+
}
|
|
722
|
+
const contentType = f(step, "contentType");
|
|
723
|
+
return {
|
|
724
|
+
kind: "artifact",
|
|
725
|
+
id,
|
|
726
|
+
key,
|
|
727
|
+
path: (b) => resolveRefString(id, "path", rawPath, b, "error"),
|
|
728
|
+
...contentType !== void 0 ? { contentType } : {}
|
|
729
|
+
};
|
|
730
|
+
}
|
|
450
731
|
case "transform": {
|
|
451
732
|
const passthrough = step;
|
|
452
733
|
return passthrough;
|
|
@@ -489,6 +770,9 @@ function resolveRefString(stepId, field, value, b, onUnmatched = "passthrough")
|
|
|
489
770
|
}
|
|
490
771
|
return String(hit.resolved) + hit.rest;
|
|
491
772
|
}
|
|
773
|
+
|
|
774
|
+
// src/types.ts
|
|
775
|
+
var DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES = 3;
|
|
492
776
|
var NodeFsPort = class {
|
|
493
777
|
root;
|
|
494
778
|
constructor(root) {
|
|
@@ -683,12 +967,150 @@ var WorkflowSuspendedError = class extends Error {
|
|
|
683
967
|
stepId;
|
|
684
968
|
on;
|
|
685
969
|
};
|
|
686
|
-
|
|
687
|
-
|
|
970
|
+
var AgentInputRequiredError = class extends Error {
|
|
971
|
+
constructor(stepId, prompt, schema) {
|
|
972
|
+
super(
|
|
973
|
+
`step '${stepId}' requires input ("${prompt}") \u2014 no onInputRequired hook supplied`
|
|
974
|
+
);
|
|
975
|
+
this.stepId = stepId;
|
|
976
|
+
this.prompt = prompt;
|
|
977
|
+
this.schema = schema;
|
|
978
|
+
this.name = "AgentInputRequiredError";
|
|
979
|
+
}
|
|
980
|
+
stepId;
|
|
981
|
+
prompt;
|
|
982
|
+
schema;
|
|
983
|
+
};
|
|
984
|
+
var StepOutcomeError = class extends Error {
|
|
985
|
+
constructor(stepId, code, message, hint) {
|
|
986
|
+
super(message);
|
|
987
|
+
this.stepId = stepId;
|
|
988
|
+
this.code = code;
|
|
989
|
+
this.hint = hint;
|
|
990
|
+
this.name = "StepOutcomeError";
|
|
991
|
+
}
|
|
992
|
+
stepId;
|
|
993
|
+
code;
|
|
994
|
+
hint;
|
|
995
|
+
};
|
|
996
|
+
var MissingArtifactError = class extends Error {
|
|
997
|
+
constructor(key, stepId) {
|
|
998
|
+
super(`outputsFiles.${key} was required but is missing from the run workspace`);
|
|
999
|
+
this.key = key;
|
|
1000
|
+
this.stepId = stepId;
|
|
1001
|
+
this.name = "MissingArtifactError";
|
|
1002
|
+
}
|
|
1003
|
+
key;
|
|
1004
|
+
stepId;
|
|
1005
|
+
code = "missing-artifact";
|
|
1006
|
+
};
|
|
1007
|
+
var AgentSpawnError = class extends Error {
|
|
1008
|
+
constructor(stepId, cause) {
|
|
1009
|
+
super(`step '${stepId}': agent spawn failed \u2014 ${errorMessage(cause)}`);
|
|
1010
|
+
this.stepId = stepId;
|
|
1011
|
+
this.cause = cause;
|
|
1012
|
+
this.name = "AgentSpawnError";
|
|
1013
|
+
}
|
|
1014
|
+
stepId;
|
|
1015
|
+
cause;
|
|
1016
|
+
};
|
|
1017
|
+
function withReleaseScope(ctx) {
|
|
1018
|
+
return { ...ctx, spawned: [] };
|
|
1019
|
+
}
|
|
1020
|
+
async function releaseScope(ctx) {
|
|
1021
|
+
const release = ctx.agents?.releaseSession;
|
|
1022
|
+
if (!release || !ctx.spawned || ctx.spawned.length === 0) return;
|
|
1023
|
+
const ids = ctx.spawned.splice(0);
|
|
1024
|
+
const agents = ctx.agents;
|
|
1025
|
+
await Promise.all(ids.map((id) => Promise.resolve().then(() => release.call(agents, id)).catch(() => void 0)));
|
|
1026
|
+
}
|
|
1027
|
+
function view(ctx, item, index) {
|
|
1028
|
+
return {
|
|
1029
|
+
input: ctx.state.input,
|
|
1030
|
+
steps: ctx.state.steps,
|
|
1031
|
+
item,
|
|
1032
|
+
index,
|
|
1033
|
+
...ctx.workspace !== void 0 ? { run: { workspace: ctx.workspace } } : {}
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
function withIndexedHooks(ctx, index) {
|
|
1037
|
+
const cacheKeySuffix = `${ctx.cacheKeySuffix ?? ""}[${index}]`;
|
|
1038
|
+
if (!ctx.onStepStart && !ctx.onStepComplete && !ctx.onStepSkipped && !ctx.onStepFailed) {
|
|
1039
|
+
return { ...ctx, cacheKeySuffix };
|
|
1040
|
+
}
|
|
1041
|
+
return {
|
|
1042
|
+
...ctx,
|
|
1043
|
+
cacheKeySuffix,
|
|
1044
|
+
onStepStart: ctx.onStepStart ? (id, info) => ctx.onStepStart(`${id}[${index}]`, info) : void 0,
|
|
1045
|
+
onStepComplete: ctx.onStepComplete ? (id, out, info) => ctx.onStepComplete(`${id}[${index}]`, out, info) : void 0,
|
|
1046
|
+
onStepSkipped: ctx.onStepSkipped ? (id, info) => ctx.onStepSkipped(`${id}[${index}]`, info) : void 0,
|
|
1047
|
+
onStepFailed: ctx.onStepFailed ? (id, info) => ctx.onStepFailed(`${id}[${index}]`, info) : void 0
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
var failureOrigin = /* @__PURE__ */ new WeakMap();
|
|
1051
|
+
function reportItemFailure(err, itemCtx, bodyId) {
|
|
1052
|
+
const info = { error: errorMessage(err) };
|
|
1053
|
+
const origin = typeof err === "object" && err !== null ? failureOrigin.get(err) : void 0;
|
|
1054
|
+
if (origin) {
|
|
1055
|
+
failureOrigin.delete(err);
|
|
1056
|
+
origin(info);
|
|
1057
|
+
} else {
|
|
1058
|
+
itemCtx.onStepFailed?.(bodyId, info);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
function spawnBreaker(threshold) {
|
|
1062
|
+
let streak = 0;
|
|
1063
|
+
let streakFirst;
|
|
1064
|
+
let open;
|
|
1065
|
+
return {
|
|
1066
|
+
settle(err) {
|
|
1067
|
+
if (open !== void 0) return;
|
|
1068
|
+
if (!(err instanceof AgentSpawnError)) {
|
|
1069
|
+
streak = 0;
|
|
1070
|
+
streakFirst = void 0;
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
if (streak === 0) streakFirst = err.message;
|
|
1074
|
+
streak++;
|
|
1075
|
+
if (threshold > 0 && streak >= threshold) open = streakFirst;
|
|
1076
|
+
},
|
|
1077
|
+
get open() {
|
|
1078
|
+
return open;
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
function skipUnstartedItems(ctx, fanOutId, from, items, bodiesOf, reason, results) {
|
|
1083
|
+
for (let idx = from; idx < items.length; idx++) {
|
|
1084
|
+
results[idx] = { status: "skipped", index: idx, item: items[idx], reason: `circuit-open: ${reason}` };
|
|
1085
|
+
const itemCtx = withIndexedHooks(ctx, idx);
|
|
1086
|
+
if (!itemCtx.onStepSkipped) continue;
|
|
1087
|
+
let bodies;
|
|
1088
|
+
try {
|
|
1089
|
+
bodies = bodiesOf(items[idx], idx);
|
|
1090
|
+
} catch {
|
|
1091
|
+
continue;
|
|
1092
|
+
}
|
|
1093
|
+
for (const id of new Set(skippableStepIds(bodies))) {
|
|
1094
|
+
itemCtx.onStepSkipped(id, { reason: "circuit-open", branchId: fanOutId, message: reason });
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
function tolerantResult(results, circuitOpen) {
|
|
1099
|
+
const outcomes = results;
|
|
1100
|
+
return {
|
|
1101
|
+
results: outcomes,
|
|
1102
|
+
succeeded: outcomes.filter((r) => r.status === "fulfilled").length,
|
|
1103
|
+
failed: outcomes.filter((r) => r.status === "rejected").length,
|
|
1104
|
+
skipped: outcomes.filter((r) => r.status === "skipped").length,
|
|
1105
|
+
...circuitOpen !== void 0 ? { circuitOpen: { error: circuitOpen } } : {}
|
|
1106
|
+
};
|
|
688
1107
|
}
|
|
689
1108
|
function resolveSel(sel, b) {
|
|
690
1109
|
return typeof sel === "function" ? sel(b) : sel;
|
|
691
1110
|
}
|
|
1111
|
+
function resolveSessionRef(ref, b) {
|
|
1112
|
+
return b.index === void 0 ? ref : ref.replace(/\{\{\s*index\s*\}\}/g, String(b.index));
|
|
1113
|
+
}
|
|
692
1114
|
function extractJsonCandidate(raw) {
|
|
693
1115
|
const jsonFence = /```json\s*([\s\S]*?)```/g;
|
|
694
1116
|
const lastJson = lastMatch(jsonFence, raw);
|
|
@@ -712,14 +1134,21 @@ function spentUsd(state) {
|
|
|
712
1134
|
function hashResolvedInputs(kind, resolved) {
|
|
713
1135
|
return createHash("sha256").update(`${kind}\0${JSON.stringify(resolved) ?? "undefined"}`).digest("hex");
|
|
714
1136
|
}
|
|
715
|
-
function stepJournalKey(
|
|
716
|
-
return `${cacheKey}\0${step.id}\0${step.kind}`;
|
|
1137
|
+
function stepJournalKey(ctx, step) {
|
|
1138
|
+
return `${ctx.cacheKey}\0${step.id}\0${step.kind}${ctx.cacheKeySuffix ?? ""}`;
|
|
717
1139
|
}
|
|
718
1140
|
function isCacheEnabled(ctx, step) {
|
|
719
1141
|
return step.cacheable === true && ctx.cache !== void 0 && ctx.cacheKey !== void 0;
|
|
720
1142
|
}
|
|
1143
|
+
function cachedHitKey(ctx, stepId) {
|
|
1144
|
+
return `${stepId}${ctx.cacheKeySuffix ?? ""}`;
|
|
1145
|
+
}
|
|
1146
|
+
function completeStep(ctx, stepId, out) {
|
|
1147
|
+
const cached = ctx.state.cachedHits.delete(cachedHitKey(ctx, stepId));
|
|
1148
|
+
ctx.onStepComplete?.(stepId, out, cached ? { cached: true } : void 0);
|
|
1149
|
+
}
|
|
721
1150
|
async function readStepCache(ctx, step, resolvedInputs) {
|
|
722
|
-
const key = stepJournalKey(ctx
|
|
1151
|
+
const key = stepJournalKey(ctx, step);
|
|
723
1152
|
const hash = hashResolvedInputs(step.kind, resolvedInputs);
|
|
724
1153
|
const entry = await ctx.cache.get(key);
|
|
725
1154
|
if (entry !== void 0 && entry.resolvedInputHash === hash) {
|
|
@@ -730,19 +1159,72 @@ async function readStepCache(ctx, step, resolvedInputs) {
|
|
|
730
1159
|
function errorMessage(err) {
|
|
731
1160
|
return err instanceof Error ? err.message : String(err);
|
|
732
1161
|
}
|
|
733
|
-
function
|
|
1162
|
+
function formatSchemaError(err) {
|
|
734
1163
|
return err.issues.map((i) => `${i.path.length > 0 ? i.path.join(".") + ": " : ""}${i.message}`).join(", ");
|
|
735
1164
|
}
|
|
1165
|
+
function describeOutputSchemaForPrompt(schema) {
|
|
1166
|
+
if (schema.jsonSchema !== void 0) {
|
|
1167
|
+
return `When done, reply with ONLY a JSON object matching this JSON Schema: ${JSON.stringify(schema.jsonSchema)}`;
|
|
1168
|
+
}
|
|
1169
|
+
try {
|
|
1170
|
+
const jsonSchema = z.toJSONSchema(schema);
|
|
1171
|
+
return `When done, reply with ONLY a JSON object matching this JSON Schema: ${JSON.stringify(jsonSchema)}`;
|
|
1172
|
+
} catch {
|
|
1173
|
+
}
|
|
1174
|
+
const shape = schema.shape;
|
|
1175
|
+
if (shape && typeof shape === "object") {
|
|
1176
|
+
const fields = Object.keys(shape);
|
|
1177
|
+
if (fields.length > 0) {
|
|
1178
|
+
return `When done, reply with ONLY a JSON object with these fields: ${fields.join(", ")}`;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
return void 0;
|
|
1182
|
+
}
|
|
1183
|
+
function skippableStepIds(steps, acc = []) {
|
|
1184
|
+
for (const s of steps) {
|
|
1185
|
+
switch (s.kind) {
|
|
1186
|
+
case "group":
|
|
1187
|
+
skippableStepIds(s.steps, acc);
|
|
1188
|
+
break;
|
|
1189
|
+
case "parallel":
|
|
1190
|
+
for (const br of s.branches) skippableStepIds(br.steps, acc);
|
|
1191
|
+
break;
|
|
1192
|
+
case "branch":
|
|
1193
|
+
skippableStepIds(s.then, acc);
|
|
1194
|
+
if (s.otherwise) skippableStepIds(s.otherwise, acc);
|
|
1195
|
+
break;
|
|
1196
|
+
case "loop":
|
|
1197
|
+
skippableStepIds(s.body, acc);
|
|
1198
|
+
break;
|
|
1199
|
+
default:
|
|
1200
|
+
acc.push(s.id);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
return acc;
|
|
1204
|
+
}
|
|
736
1205
|
async function runSequence(steps, ctx, item, index) {
|
|
737
1206
|
let last;
|
|
738
1207
|
for (const s of steps) {
|
|
739
1208
|
const out = await execStep(s, ctx, item, index);
|
|
740
1209
|
ctx.state.steps[s.id] = out;
|
|
741
|
-
ctx
|
|
1210
|
+
completeStep(ctx, s.id, out);
|
|
742
1211
|
last = out;
|
|
743
1212
|
}
|
|
744
1213
|
return last;
|
|
745
1214
|
}
|
|
1215
|
+
async function sendPromptAndAwaitOutcome(ctx, step, sessionId, prompt) {
|
|
1216
|
+
let next = prompt;
|
|
1217
|
+
for (; ; ) {
|
|
1218
|
+
await ctx.agents.sendPromptAndWait(sessionId, next);
|
|
1219
|
+
const req = ctx.agents.takeInputRequest?.(sessionId);
|
|
1220
|
+
if (!req) return;
|
|
1221
|
+
if (!ctx.onInputRequired) {
|
|
1222
|
+
throw new AgentInputRequiredError(step.id, req.prompt, req.schema);
|
|
1223
|
+
}
|
|
1224
|
+
const payload = await ctx.onInputRequired({ stepId: step.id, prompt: req.prompt, schema: req.schema });
|
|
1225
|
+
next = JSON.stringify(payload);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
746
1228
|
async function execAgentStep(step, ctx, b) {
|
|
747
1229
|
ctx.onStepStart?.(step.id);
|
|
748
1230
|
if (step.adapter && ctx.state.maxTotalCostUsd !== void 0 && spentUsd(ctx.state) >= ctx.state.maxTotalCostUsd) {
|
|
@@ -750,7 +1232,8 @@ async function execAgentStep(step, ctx, b) {
|
|
|
750
1232
|
`step '${step.id}': budget_exceeded \u2014 run spend $${spentUsd(ctx.state).toFixed(4)} >= cap $${ctx.state.maxTotalCostUsd}`
|
|
751
1233
|
);
|
|
752
1234
|
}
|
|
753
|
-
const
|
|
1235
|
+
const stepCwd = step.cwd ? resolveSel(step.cwd, b) : void 0;
|
|
1236
|
+
const cwd = step.harness?.cwd ?? (stepCwd !== void 0 ? ctx.cwd !== void 0 ? resolve(ctx.cwd, stepCwd) : stepCwd : ctx.cwd);
|
|
754
1237
|
let knowledgeOut;
|
|
755
1238
|
let knowledgeWarnings = [];
|
|
756
1239
|
if (step.harness?.knowledge && step.harness.knowledge.length > 0) {
|
|
@@ -782,15 +1265,28 @@ ${inner(b2)}` };
|
|
|
782
1265
|
...step.harness ?? {},
|
|
783
1266
|
...model !== void 0 && step.harness?.model === void 0 ? { model } : {}
|
|
784
1267
|
} : void 0;
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
1268
|
+
let sessionId;
|
|
1269
|
+
if (step.adapter) {
|
|
1270
|
+
if (ctx.signal?.aborted) throw new Error(`step '${step.id}': run cancelled \u2014 not spawning`);
|
|
1271
|
+
try {
|
|
1272
|
+
sessionId = await ctx.agents.spawn(resolveSel(step.adapter, b), {
|
|
1273
|
+
cwd,
|
|
1274
|
+
workspaceSlug: ctx.workspaceSlug,
|
|
1275
|
+
stepId: step.id,
|
|
1276
|
+
...sandbox !== void 0 ? { sandbox } : {},
|
|
1277
|
+
...step.options !== void 0 ? { options: step.options } : {},
|
|
1278
|
+
...harness !== void 0 ? { harness } : {},
|
|
1279
|
+
...step.agentTools !== void 0 ? { agentTools: step.agentTools } : {},
|
|
1280
|
+
...b.index !== void 0 ? { stepKey: `${step.id}[${b.index}]` } : {}
|
|
1281
|
+
});
|
|
1282
|
+
} catch (err) {
|
|
1283
|
+
throw new AgentSpawnError(step.id, err);
|
|
1284
|
+
}
|
|
1285
|
+
} else {
|
|
1286
|
+
sessionId = ctx.agents.resolveByLabel(resolveSessionRef(step.sessionRef, b));
|
|
1287
|
+
}
|
|
793
1288
|
if (!sessionId) throw new Error(`step '${step.id}': no session (adapter and sessionRef both unresolved)`);
|
|
1289
|
+
if (step.adapter) ctx.spawned?.push(sessionId);
|
|
794
1290
|
if (knowledgeWarnings.length > 0 && ctx.agents.emitHarnessWarning) {
|
|
795
1291
|
ctx.agents.emitHarnessWarning({
|
|
796
1292
|
sessionId,
|
|
@@ -802,7 +1298,12 @@ ${inner(b2)}` };
|
|
|
802
1298
|
...harness,
|
|
803
1299
|
...harness.tools && harness.tools.length > 0 ? { toolsApplied: false } : {}
|
|
804
1300
|
} : void 0;
|
|
805
|
-
|
|
1301
|
+
const inputRequestAffordance = ctx.agents.takeInputRequest ? "\n\nIf you need information you don't have, call the run_request_input tool instead of asking in your reply." : "";
|
|
1302
|
+
const outputSchemaNote = step.outputSchema ? describeOutputSchemaForPrompt(step.outputSchema) : void 0;
|
|
1303
|
+
const outputSchemaAffordance = outputSchemaNote ? `
|
|
1304
|
+
|
|
1305
|
+
${outputSchemaNote}` : "";
|
|
1306
|
+
await sendPromptAndAwaitOutcome(ctx, step, sessionId, step.prompt(b) + inputRequestAffordance + outputSchemaAffordance);
|
|
806
1307
|
if (step.policy && ctx.agents.onAwaitingInput) {
|
|
807
1308
|
await ctx.agents.onAwaitingInput(sessionId, step.policy);
|
|
808
1309
|
}
|
|
@@ -824,8 +1325,10 @@ ${inner(b2)}` };
|
|
|
824
1325
|
}
|
|
825
1326
|
const maxRetries = step.maxRetries ?? 2;
|
|
826
1327
|
let lastErr = "";
|
|
1328
|
+
let lastRaw = "";
|
|
827
1329
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
828
1330
|
const raw = await ctx.agents.readFinalMessage(sessionId);
|
|
1331
|
+
lastRaw = raw;
|
|
829
1332
|
const candidate = extractJsonCandidate(raw);
|
|
830
1333
|
let value;
|
|
831
1334
|
try {
|
|
@@ -833,24 +1336,34 @@ ${inner(b2)}` };
|
|
|
833
1336
|
} catch {
|
|
834
1337
|
lastErr = "not valid JSON";
|
|
835
1338
|
if (attempt < maxRetries) {
|
|
836
|
-
await
|
|
1339
|
+
await sendPromptAndAwaitOutcome(
|
|
1340
|
+
ctx,
|
|
1341
|
+
step,
|
|
837
1342
|
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.`
|
|
1343
|
+
`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.` + outputSchemaAffordance
|
|
839
1344
|
);
|
|
840
1345
|
}
|
|
841
1346
|
continue;
|
|
842
1347
|
}
|
|
843
1348
|
const res = step.outputSchema.safeParse(value);
|
|
844
1349
|
if (res.success) return { sessionId, output: res.data, ...harnessOut ? { harness: harnessOut } : {}, ...knowledgeOut ? { knowledgeApplied: knowledgeOut } : {} };
|
|
845
|
-
lastErr =
|
|
1350
|
+
lastErr = formatSchemaError(res.error);
|
|
846
1351
|
if (attempt < maxRetries) {
|
|
847
|
-
await
|
|
1352
|
+
await sendPromptAndAwaitOutcome(
|
|
1353
|
+
ctx,
|
|
1354
|
+
step,
|
|
848
1355
|
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.`
|
|
1356
|
+
`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.` + outputSchemaAffordance
|
|
850
1357
|
);
|
|
851
1358
|
}
|
|
852
1359
|
}
|
|
853
|
-
|
|
1360
|
+
const hint = lastRaw.trim().endsWith("?") ? "possible-input-request" : void 0;
|
|
1361
|
+
throw new StepOutcomeError(
|
|
1362
|
+
step.id,
|
|
1363
|
+
"missing-output",
|
|
1364
|
+
`step '${step.id}': missing-output \u2014 final message never matched outputSchema (${lastErr})`,
|
|
1365
|
+
hint
|
|
1366
|
+
);
|
|
854
1367
|
}
|
|
855
1368
|
function tryParseJson(text) {
|
|
856
1369
|
const trimmed = text.trim();
|
|
@@ -955,10 +1468,25 @@ Please address the findings above, then reply when done.`
|
|
|
955
1468
|
}
|
|
956
1469
|
return last;
|
|
957
1470
|
}
|
|
1471
|
+
function cacheHit(ctx, step, output) {
|
|
1472
|
+
ctx.state.cachedHits.add(cachedHitKey(ctx, step.id));
|
|
1473
|
+
ctx.onStepStart?.(step.id, { cached: true });
|
|
1474
|
+
return output;
|
|
1475
|
+
}
|
|
958
1476
|
async function execStep(step, ctx, item, index) {
|
|
1477
|
+
try {
|
|
1478
|
+
return await execStepBody(step, ctx, item, index);
|
|
1479
|
+
} catch (err) {
|
|
1480
|
+
if (typeof err === "object" && err !== null && !failureOrigin.has(err)) {
|
|
1481
|
+
failureOrigin.set(err, (info) => ctx.onStepFailed?.(step.id, info));
|
|
1482
|
+
}
|
|
1483
|
+
throw err;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
async function execStepBody(step, ctx, item, index) {
|
|
959
1487
|
const { state, signal } = ctx;
|
|
960
|
-
const b = view(
|
|
961
|
-
if (step.kind !== "agent") {
|
|
1488
|
+
const b = view(ctx, item, index);
|
|
1489
|
+
if (step.kind !== "agent" && step.kind !== "tool" && step.kind !== "artifact") {
|
|
962
1490
|
ctx.onStepStart?.(step.id);
|
|
963
1491
|
}
|
|
964
1492
|
switch (step.kind) {
|
|
@@ -973,9 +1501,13 @@ async function execStep(step, ctx, item, index) {
|
|
|
973
1501
|
secrets: step.secrets,
|
|
974
1502
|
signal
|
|
975
1503
|
});
|
|
976
|
-
if (!isCacheEnabled(ctx, step))
|
|
1504
|
+
if (!isCacheEnabled(ctx, step)) {
|
|
1505
|
+
ctx.onStepStart?.(step.id);
|
|
1506
|
+
return runIt();
|
|
1507
|
+
}
|
|
977
1508
|
const c = await readStepCache(ctx, step, input);
|
|
978
|
-
if (c.hit) return c.output;
|
|
1509
|
+
if (c.hit) return cacheHit(ctx, step, c.output);
|
|
1510
|
+
ctx.onStepStart?.(step.id);
|
|
979
1511
|
const out = await runIt();
|
|
980
1512
|
await ctx.cache.set(c.key, { output: out, resolvedInputHash: c.hash });
|
|
981
1513
|
return out;
|
|
@@ -987,81 +1519,110 @@ async function execStep(step, ctx, item, index) {
|
|
|
987
1519
|
const parallelism = Math.max(1, step.parallelism ?? 1);
|
|
988
1520
|
const tolerant = step.onError === "collect";
|
|
989
1521
|
const results = new Array(arr.length);
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
);
|
|
1000
|
-
|
|
1001
|
-
|
|
1522
|
+
let next = 0;
|
|
1523
|
+
let failed = false;
|
|
1524
|
+
const breaker = spawnBreaker(step.maxConsecutiveSpawnFailures ?? DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES);
|
|
1525
|
+
const runItem = async (idx) => {
|
|
1526
|
+
const el = arr[idx];
|
|
1527
|
+
const inner = step.body(el, idx, view(ctx, el, idx));
|
|
1528
|
+
const wrapped = withReleaseScope(withIndexedHooks(ctx, idx));
|
|
1529
|
+
try {
|
|
1530
|
+
const out = await execStep(inner, wrapped, el, idx);
|
|
1531
|
+
completeStep(wrapped, inner.id, out);
|
|
1532
|
+
results[idx] = tolerant ? { status: "fulfilled", index: idx, value: out } : out;
|
|
1533
|
+
breaker.settle();
|
|
1534
|
+
} catch (err) {
|
|
1535
|
+
if (!tolerant) {
|
|
1536
|
+
failed = true;
|
|
1537
|
+
throw err;
|
|
1538
|
+
}
|
|
1539
|
+
results[idx] = { status: "rejected", index: idx, item: el, error: errorMessage(err) };
|
|
1540
|
+
reportItemFailure(err, wrapped, inner.id);
|
|
1541
|
+
breaker.settle(err);
|
|
1542
|
+
} finally {
|
|
1543
|
+
await releaseScope(wrapped);
|
|
1002
1544
|
}
|
|
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;
|
|
1017
|
-
return {
|
|
1018
|
-
results: outcomes,
|
|
1019
|
-
succeeded: outcomes.filter((r) => r.status === "fulfilled").length,
|
|
1020
|
-
failed: outcomes.filter((r) => r.status === "rejected").length
|
|
1021
1545
|
};
|
|
1546
|
+
const worker = async () => {
|
|
1547
|
+
while (!failed && breaker.open === void 0 && next < arr.length) {
|
|
1548
|
+
const idx = next++;
|
|
1549
|
+
await runItem(idx);
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
await Promise.all(Array.from({ length: Math.min(parallelism, arr.length) }, () => worker()));
|
|
1553
|
+
if (!tolerant) return results;
|
|
1554
|
+
if (breaker.open !== void 0) {
|
|
1555
|
+
skipUnstartedItems(ctx, step.id, next, arr, (el, idx) => [step.body(el, idx, view(ctx, el, idx))], breaker.open, results);
|
|
1556
|
+
}
|
|
1557
|
+
return tolerantResult(results, breaker.open);
|
|
1022
1558
|
}
|
|
1023
1559
|
case "pipeline": {
|
|
1024
1560
|
const items = [...step.over(b)];
|
|
1025
1561
|
const tolerant = step.onError === "collect";
|
|
1026
|
-
if (items.length === 0) return tolerant ?
|
|
1562
|
+
if (items.length === 0) return tolerant ? tolerantResult([], void 0) : [];
|
|
1027
1563
|
const cap = Math.max(1, step.concurrency ?? items.length);
|
|
1028
1564
|
const results = new Array(items.length);
|
|
1029
1565
|
let next = 0;
|
|
1566
|
+
const breaker = spawnBreaker(step.maxConsecutiveSpawnFailures ?? DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES);
|
|
1030
1567
|
const runItem = async (idx) => {
|
|
1031
1568
|
let prev = void 0;
|
|
1569
|
+
let stageId = step.id;
|
|
1570
|
+
const wrapped = withReleaseScope(withIndexedHooks(ctx, idx));
|
|
1032
1571
|
try {
|
|
1033
1572
|
for (const stage of step.stages) {
|
|
1034
|
-
|
|
1573
|
+
const inner = stage(items[idx], idx, prev, view(ctx, items[idx], idx));
|
|
1574
|
+
stageId = inner.id;
|
|
1575
|
+
prev = await execStep(inner, wrapped, items[idx], idx);
|
|
1576
|
+
completeStep(wrapped, inner.id, prev);
|
|
1035
1577
|
}
|
|
1036
1578
|
results[idx] = tolerant ? { status: "fulfilled", index: idx, value: prev } : prev;
|
|
1579
|
+
breaker.settle();
|
|
1037
1580
|
} catch (err) {
|
|
1038
1581
|
if (!tolerant) throw err;
|
|
1039
1582
|
results[idx] = { status: "rejected", index: idx, item: items[idx], error: errorMessage(err) };
|
|
1583
|
+
reportItemFailure(err, wrapped, stageId);
|
|
1584
|
+
breaker.settle(err);
|
|
1585
|
+
} finally {
|
|
1586
|
+
await releaseScope(wrapped);
|
|
1040
1587
|
}
|
|
1041
1588
|
};
|
|
1042
1589
|
const worker = async () => {
|
|
1043
|
-
while (next < items.length) {
|
|
1590
|
+
while ((!tolerant || breaker.open === void 0) && next < items.length) {
|
|
1044
1591
|
const idx = next++;
|
|
1045
1592
|
await runItem(idx);
|
|
1046
1593
|
}
|
|
1047
1594
|
};
|
|
1048
1595
|
await Promise.all(Array.from({ length: Math.min(cap, items.length) }, () => worker()));
|
|
1049
1596
|
if (!tolerant) return results;
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1597
|
+
if (breaker.open !== void 0) {
|
|
1598
|
+
skipUnstartedItems(
|
|
1599
|
+
ctx,
|
|
1600
|
+
step.id,
|
|
1601
|
+
next,
|
|
1602
|
+
items,
|
|
1603
|
+
(el, idx) => step.stages.map((stage) => stage(el, idx, void 0, view(ctx, el, idx))),
|
|
1604
|
+
breaker.open,
|
|
1605
|
+
results
|
|
1606
|
+
);
|
|
1607
|
+
}
|
|
1608
|
+
return tolerantResult(results, breaker.open);
|
|
1056
1609
|
}
|
|
1057
1610
|
case "branch": {
|
|
1058
|
-
const
|
|
1611
|
+
const taken = step.cond(b);
|
|
1612
|
+
const chosen = taken ? step.then : step.otherwise ?? [];
|
|
1613
|
+
if (ctx.onStepSkipped) {
|
|
1614
|
+
const onChosenPath = new Set(skippableStepIds(chosen));
|
|
1615
|
+
const untaken = taken ? step.otherwise ?? [] : step.then;
|
|
1616
|
+
for (const id of new Set(skippableStepIds(untaken))) {
|
|
1617
|
+
if (!onChosenPath.has(id)) ctx.onStepSkipped(id, { reason: "branch-not-taken", branchId: step.sourceId ?? step.id });
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1059
1620
|
return runSequence(chosen, ctx, item, index);
|
|
1060
1621
|
}
|
|
1061
1622
|
case "loop": {
|
|
1062
1623
|
let iterations = 0;
|
|
1063
1624
|
let last;
|
|
1064
|
-
while (iterations < step.maxIterations && step.while(view(
|
|
1625
|
+
while (iterations < step.maxIterations && step.while(view(ctx, item, index))) {
|
|
1065
1626
|
last = await runSequence(step.body, ctx, item, index);
|
|
1066
1627
|
iterations++;
|
|
1067
1628
|
}
|
|
@@ -1107,14 +1668,20 @@ async function execStep(step, ctx, item, index) {
|
|
|
1107
1668
|
const child = await runWorkflowInner(step.workflow, childInput, {
|
|
1108
1669
|
approve: ctx.approve,
|
|
1109
1670
|
resume: ctx.resume,
|
|
1671
|
+
onInputRequired: ctx.onInputRequired,
|
|
1110
1672
|
signal,
|
|
1111
1673
|
agents: ctx.agents,
|
|
1112
1674
|
cwd: ctx.cwd,
|
|
1113
1675
|
workspaceSlug: ctx.workspaceSlug,
|
|
1676
|
+
workspace: ctx.workspace,
|
|
1677
|
+
artifactsDir: ctx.artifactsDir,
|
|
1678
|
+
runId: ctx.runId,
|
|
1679
|
+
onArtifact: ctx.onArtifact,
|
|
1114
1680
|
cache: ctx.cache,
|
|
1115
1681
|
cacheKey: ctx.cacheKey,
|
|
1116
1682
|
runGateCommand: ctx.runGateCommand,
|
|
1117
|
-
onGateReport: ctx.onGateReport
|
|
1683
|
+
onGateReport: ctx.onGateReport,
|
|
1684
|
+
spawned: ctx.spawned
|
|
1118
1685
|
});
|
|
1119
1686
|
return child.output;
|
|
1120
1687
|
}
|
|
@@ -1125,29 +1692,147 @@ async function execStep(step, ctx, item, index) {
|
|
|
1125
1692
|
prompt: step.prompt(b),
|
|
1126
1693
|
adapter: step.adapter ? resolveSel(step.adapter, b) : void 0,
|
|
1127
1694
|
model: step.model ? resolveSel(step.model, b) : void 0,
|
|
1128
|
-
sessionRef: step.sessionRef
|
|
1695
|
+
sessionRef: step.sessionRef !== void 0 ? resolveSessionRef(step.sessionRef, b) : void 0
|
|
1129
1696
|
};
|
|
1130
1697
|
const c = await readStepCache(ctx, step, resolved);
|
|
1131
|
-
if (c.hit) return c.output;
|
|
1698
|
+
if (c.hit) return cacheHit(ctx, step, c.output);
|
|
1132
1699
|
const out = await execAgentStep(step, ctx, b);
|
|
1133
1700
|
await ctx.cache.set(c.key, { output: out, resolvedInputHash: c.hash });
|
|
1134
1701
|
return out;
|
|
1135
1702
|
}
|
|
1136
1703
|
case "gate":
|
|
1137
1704
|
return execGateStep(step, ctx, b);
|
|
1705
|
+
case "artifact":
|
|
1706
|
+
return execArtifactStep(step, ctx, b);
|
|
1138
1707
|
}
|
|
1139
1708
|
}
|
|
1709
|
+
function sanitizeArtifactKey(key) {
|
|
1710
|
+
const cleaned = key.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
1711
|
+
return cleaned.length > 0 ? cleaned : "artifact";
|
|
1712
|
+
}
|
|
1713
|
+
async function copyIntoArtifacts(stepId, key, rawPath, workspace, artifactsDir, contentType) {
|
|
1714
|
+
const abs = isAbsolute(rawPath) ? resolve(rawPath) : resolve(workspace, rawPath);
|
|
1715
|
+
const rel = relative(workspace, abs);
|
|
1716
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
1717
|
+
throw new Error(`step '${stepId}': artifact path '${rawPath}' resolves outside the run workspace`);
|
|
1718
|
+
}
|
|
1719
|
+
const buf = await readFile(abs);
|
|
1720
|
+
const sha256 = createHash("sha256").update(buf).digest("hex");
|
|
1721
|
+
const destName = sanitizeArtifactKey(key);
|
|
1722
|
+
await mkdir(artifactsDir, { recursive: true });
|
|
1723
|
+
await writeFile(join(artifactsDir, destName), buf);
|
|
1724
|
+
return {
|
|
1725
|
+
key,
|
|
1726
|
+
path: `artifacts/${destName}`,
|
|
1727
|
+
sha256,
|
|
1728
|
+
size: buf.length,
|
|
1729
|
+
stepId,
|
|
1730
|
+
...contentType !== void 0 ? { contentType } : {}
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
async function execArtifactStep(step, ctx, b) {
|
|
1734
|
+
if (ctx.workspace === void 0 || ctx.artifactsDir === void 0) {
|
|
1735
|
+
throw new Error(`step '${step.id}': kind:"artifact" requires a run workspace (no workspace/artifactsDir wired)`);
|
|
1736
|
+
}
|
|
1737
|
+
const workspace = ctx.workspace;
|
|
1738
|
+
const artifactsDir = ctx.artifactsDir;
|
|
1739
|
+
const key = resolveSel(step.key, b);
|
|
1740
|
+
const rawPath = resolveSel(step.path, b);
|
|
1741
|
+
const contentType = step.contentType ? resolveSel(step.contentType, b) : void 0;
|
|
1742
|
+
const resolvedInputs = { key, path: rawPath };
|
|
1743
|
+
const cacheOn = ctx.cache !== void 0 && ctx.cacheKey !== void 0;
|
|
1744
|
+
const journalKey = cacheOn ? stepJournalKey(ctx, step) : void 0;
|
|
1745
|
+
const hash = cacheOn ? hashResolvedInputs(step.kind, resolvedInputs) : void 0;
|
|
1746
|
+
if (cacheOn) {
|
|
1747
|
+
const entry = await ctx.cache.get(journalKey);
|
|
1748
|
+
if (entry !== void 0 && entry.resolvedInputHash === hash) {
|
|
1749
|
+
const out2 = entry.output;
|
|
1750
|
+
const destName = sanitizeArtifactKey(key);
|
|
1751
|
+
const destAbs = join(artifactsDir, destName);
|
|
1752
|
+
const srcAbs = join(entry.artifactsDirAtCache ?? artifactsDir, destName);
|
|
1753
|
+
if (srcAbs !== destAbs) {
|
|
1754
|
+
await mkdir(dirname(destAbs), { recursive: true });
|
|
1755
|
+
try {
|
|
1756
|
+
await copyFile(srcAbs, destAbs);
|
|
1757
|
+
} catch {
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
ctx.onArtifact?.(out2);
|
|
1761
|
+
return cacheHit(ctx, step, out2);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
ctx.onStepStart?.(step.id);
|
|
1765
|
+
const out = await copyIntoArtifacts(step.id, key, rawPath, workspace, artifactsDir, contentType);
|
|
1766
|
+
if (cacheOn) {
|
|
1767
|
+
await ctx.cache.set(journalKey, { output: out, resolvedInputHash: hash, artifactsDirAtCache: artifactsDir });
|
|
1768
|
+
}
|
|
1769
|
+
ctx.onArtifact?.(out);
|
|
1770
|
+
return out;
|
|
1771
|
+
}
|
|
1772
|
+
function interpolateFileContractPath(path, workflowId, runId) {
|
|
1773
|
+
return path.replace(/<runId>/g, runId ?? "").replace(/<workflowId>/g, workflowId).replace(/<isoDate>/g, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
1774
|
+
}
|
|
1775
|
+
async function checkOutputsFiles(outputsFiles, ctx, workflowId, lastStepId) {
|
|
1776
|
+
if (!outputsFiles) return;
|
|
1777
|
+
if (ctx.workspace === void 0 || ctx.artifactsDir === void 0) return;
|
|
1778
|
+
for (const [key, contract] of Object.entries(outputsFiles)) {
|
|
1779
|
+
const rawPath = interpolateFileContractPath(contract.path, workflowId, ctx.runId);
|
|
1780
|
+
const abs = isAbsolute(rawPath) ? rawPath : resolve(ctx.workspace, rawPath);
|
|
1781
|
+
let out;
|
|
1782
|
+
try {
|
|
1783
|
+
out = await copyIntoArtifacts(lastStepId ?? workflowId, key, abs, ctx.workspace, ctx.artifactsDir, contract.contentType);
|
|
1784
|
+
} catch {
|
|
1785
|
+
if (contract.required === true) {
|
|
1786
|
+
throw new MissingArtifactError(key, lastStepId);
|
|
1787
|
+
}
|
|
1788
|
+
console.warn(
|
|
1789
|
+
`[workflow-runtime] outputsFiles.${key} ('${contract.path}') is missing and not marked required \u2014 the run still succeeds (AIP-58 \xA74)`
|
|
1790
|
+
);
|
|
1791
|
+
continue;
|
|
1792
|
+
}
|
|
1793
|
+
ctx.onArtifact?.(out);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
async function runFinally(steps, ctx, bodyFailed) {
|
|
1797
|
+
const cleanupCtx = { ...ctx, signal: void 0 };
|
|
1798
|
+
let firstError;
|
|
1799
|
+
for (const step of steps) {
|
|
1800
|
+
try {
|
|
1801
|
+
const out = await execStep(step, cleanupCtx, void 0, void 0);
|
|
1802
|
+
cleanupCtx.state.steps[step.id] = out;
|
|
1803
|
+
completeStep(cleanupCtx, step.id, out);
|
|
1804
|
+
} catch (err) {
|
|
1805
|
+
cleanupCtx.onStepFailed?.(step.id, { error: errorMessage(err) });
|
|
1806
|
+
if (firstError === void 0) firstError = err;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
if (!bodyFailed && firstError !== void 0) throw firstError;
|
|
1810
|
+
}
|
|
1140
1811
|
async function runWorkflowInner(workflow, input, hooks, maxTotalCostUsd) {
|
|
1141
|
-
const state = { input, steps: {}, costBySession: /* @__PURE__ */ new Map(), maxTotalCostUsd };
|
|
1142
|
-
const
|
|
1812
|
+
const state = { input, steps: {}, costBySession: /* @__PURE__ */ new Map(), maxTotalCostUsd, cachedHits: /* @__PURE__ */ new Set() };
|
|
1813
|
+
const ownsScope = hooks.spawned === void 0;
|
|
1814
|
+
const ctx = { state, ...hooks, ...ownsScope ? { spawned: [] } : {} };
|
|
1143
1815
|
let lastId;
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1816
|
+
let bodyFailed = false;
|
|
1817
|
+
try {
|
|
1818
|
+
for (const step of workflow.steps) {
|
|
1819
|
+
const out = await execStep(step, ctx, void 0, void 0);
|
|
1820
|
+
state.steps[step.id] = out;
|
|
1821
|
+
completeStep(ctx, step.id, out);
|
|
1822
|
+
lastId = step.id;
|
|
1823
|
+
}
|
|
1824
|
+
await checkOutputsFiles(workflow.outputsFiles, ctx, workflow.id, lastId);
|
|
1825
|
+
} catch (err) {
|
|
1826
|
+
bodyFailed = true;
|
|
1827
|
+
throw err;
|
|
1828
|
+
} finally {
|
|
1829
|
+
try {
|
|
1830
|
+
if (workflow.finally && workflow.finally.length > 0) await runFinally(workflow.finally, ctx, bodyFailed);
|
|
1831
|
+
} finally {
|
|
1832
|
+
if (ownsScope) await releaseScope(ctx);
|
|
1833
|
+
}
|
|
1149
1834
|
}
|
|
1150
|
-
const bindings = view(
|
|
1835
|
+
const bindings = view(ctx);
|
|
1151
1836
|
const output = workflow.output ? workflow.output(bindings) : lastId !== void 0 ? state.steps[lastId] : void 0;
|
|
1152
1837
|
return { output, bindings };
|
|
1153
1838
|
}
|
|
@@ -1155,14 +1840,21 @@ async function runWorkflow(args) {
|
|
|
1155
1840
|
return runWorkflowInner(args.workflow, args.input, {
|
|
1156
1841
|
approve: args.approve,
|
|
1157
1842
|
resume: args.resume,
|
|
1843
|
+
onInputRequired: args.onInputRequired,
|
|
1158
1844
|
signal: args.signal,
|
|
1159
1845
|
agents: args.agents,
|
|
1160
1846
|
cwd: args.cwd,
|
|
1161
1847
|
workspaceSlug: args.workspaceSlug,
|
|
1848
|
+
workspace: args.workspace,
|
|
1849
|
+
artifactsDir: args.artifactsDir,
|
|
1850
|
+
runId: args.runId,
|
|
1851
|
+
onArtifact: args.onArtifact,
|
|
1162
1852
|
cache: args.cache,
|
|
1163
1853
|
cacheKey: args.cacheKey,
|
|
1164
1854
|
onStepStart: args.onStepStart,
|
|
1165
1855
|
onStepComplete: args.onStepComplete,
|
|
1856
|
+
onStepSkipped: args.onStepSkipped,
|
|
1857
|
+
onStepFailed: args.onStepFailed,
|
|
1166
1858
|
runGateCommand: args.runGateCommand,
|
|
1167
1859
|
onGateReport: args.onGateReport
|
|
1168
1860
|
}, args.maxTotalCostUsd);
|
|
@@ -1173,6 +1865,6 @@ function compileWorkflowManifest(source, opts) {
|
|
|
1173
1865
|
return compileWorkflow(handle, opts);
|
|
1174
1866
|
}
|
|
1175
1867
|
|
|
1176
|
-
export { NodeFsPort, WorkflowCompileError, WorkflowSuspendedError, buildAgentStep, compileWorkflow, compileWorkflowManifest, evalPredicate, materializeKnowledge, resolveKnowledgeSelectors, resolveRef, resolveRefPrefixed, resolveValue, runWorkflow };
|
|
1868
|
+
export { AgentInputRequiredError, AgentSpawnError, DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES, MissingArtifactError, NodeFsPort, StepOutcomeError, WorkflowCompileError, WorkflowSuspendedError, buildAgentStep, compileWorkflow, compileWorkflowManifest, evalPredicate, interpolateTemplate, isCompilableJsonSchema, materializeKnowledge, normalizeWorkflowInputsSchema, resolveKnowledgeSelectors, resolveRef, resolveRefPrefixed, resolveValue, runWorkflow, validateAgainstJsonSchema, validateWorkflowInput };
|
|
1177
1869
|
//# sourceMappingURL=index.mjs.map
|
|
1178
1870
|
//# sourceMappingURL=index.mjs.map
|