@agentproto/workflow-runtime 0.12.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +237 -5
- package/dist/index.d.ts +453 -13
- package/dist/index.mjs +822 -110
- 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, cp } 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) {
|
|
@@ -87,6 +173,7 @@ function resolveTagName(name, b) {
|
|
|
87
173
|
if (head === "steps" && segs.length > 1)
|
|
88
174
|
return dig(b.steps[segs[1]], segs.slice(2));
|
|
89
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));
|
|
90
177
|
return dig(b.input, segs);
|
|
91
178
|
}
|
|
92
179
|
function stringifyValue(v) {
|
|
@@ -121,7 +208,7 @@ function interpolateTemplate(template, b) {
|
|
|
121
208
|
replacement = replacement.replace(/^\n/, "").replace(/\n[ \t]*$/, "");
|
|
122
209
|
}
|
|
123
210
|
}
|
|
124
|
-
out = before.slice(0, lineStart) + replacement + (standalone ? truthy ? nl === -1 ? after : after.slice(nl) : after.slice(nl + 1) : after);
|
|
211
|
+
out = (standalone ? before.slice(0, lineStart) : before) + replacement + (standalone ? truthy ? nl === -1 ? after : after.slice(nl) : after.slice(nl + 1) : after);
|
|
125
212
|
}
|
|
126
213
|
return out.replace(TAG_RE, (whole, name) => {
|
|
127
214
|
const v = resolveTagName(name, b);
|
|
@@ -166,7 +253,7 @@ function refPathExists(ref, b) {
|
|
|
166
253
|
segs.length = 0;
|
|
167
254
|
segs.push(...rest);
|
|
168
255
|
} else {
|
|
169
|
-
v = m[1] === "input" ? b.input : b.item;
|
|
256
|
+
v = m[1] === "input" ? b.input : m[1] === "run" ? b.run : b.item;
|
|
170
257
|
}
|
|
171
258
|
for (const seg of segs) {
|
|
172
259
|
if (v === null || typeof v !== "object") return false;
|
|
@@ -269,11 +356,16 @@ function compileWorkflow(handle, opts) {
|
|
|
269
356
|
const ctx = { opts, knownStepIds: collectStepIds(steps) };
|
|
270
357
|
const result = handle.result;
|
|
271
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;
|
|
272
362
|
return {
|
|
273
363
|
id: handle.id,
|
|
274
364
|
description: handle.description,
|
|
275
365
|
steps: compileSiblingsToSteps(steps, ctx),
|
|
276
|
-
...
|
|
366
|
+
...finallySteps?.length ? { finally: compileSiblingsToSteps(finallySteps, finallyCtx) } : {},
|
|
367
|
+
...output ? { output } : {},
|
|
368
|
+
...outputsFiles ? { outputsFiles } : {}
|
|
277
369
|
};
|
|
278
370
|
}
|
|
279
371
|
function compileSiblingsToSteps(steps, ctx) {
|
|
@@ -282,8 +374,14 @@ function compileSiblingsToSteps(steps, ctx) {
|
|
|
282
374
|
for (let i = 0; i < steps.length; i++) {
|
|
283
375
|
const s = steps[i];
|
|
284
376
|
if (s.kind === "branch") {
|
|
285
|
-
|
|
286
|
-
|
|
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;
|
|
287
385
|
}
|
|
288
386
|
out.push(compileStep(s, ctx));
|
|
289
387
|
}
|
|
@@ -294,19 +392,74 @@ function compileStepList(steps, id, ctx) {
|
|
|
294
392
|
if (compiled.length === 1) return compiled[0];
|
|
295
393
|
return { kind: "group", id, steps: compiled };
|
|
296
394
|
}
|
|
297
|
-
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) {
|
|
298
405
|
const branchStep = steps[i];
|
|
299
406
|
const branches = f(branchStep, "branches");
|
|
300
407
|
const defaultTarget = f(branchStep, "default");
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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);
|
|
307
434
|
}
|
|
308
|
-
return
|
|
435
|
+
return body;
|
|
309
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);
|
|
310
463
|
const defaultIdx = defaultTarget !== void 0 ? resolveTarget(defaultTarget, "default") : i + 1;
|
|
311
464
|
let otherwise = compileSiblingsToSteps(steps.slice(defaultIdx), ctx);
|
|
312
465
|
for (let k = branches.length - 1; k >= 0; k--) {
|
|
@@ -319,6 +472,7 @@ function compileBranchChain(steps, i, ctx) {
|
|
|
319
472
|
{
|
|
320
473
|
kind: "branch",
|
|
321
474
|
id: nodeId,
|
|
475
|
+
...k === 0 ? {} : { sourceId: branchStep.id },
|
|
322
476
|
cond: (b) => evalPredicate(condExpr, b),
|
|
323
477
|
then: thenSteps,
|
|
324
478
|
...otherwise.length > 0 ? { otherwise } : {}
|
|
@@ -327,6 +481,25 @@ function compileBranchChain(steps, i, ctx) {
|
|
|
327
481
|
}
|
|
328
482
|
return otherwise[0];
|
|
329
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
|
+
}
|
|
330
503
|
function compileAgentStep(step, id, ctx) {
|
|
331
504
|
const opts = ctx.opts;
|
|
332
505
|
const prompt = f(step, "prompt");
|
|
@@ -335,6 +508,8 @@ function compileAgentStep(step, id, ctx) {
|
|
|
335
508
|
}
|
|
336
509
|
let adapter = typeof step.adapter === "string" ? step.adapter : void 0;
|
|
337
510
|
let options = step.options !== void 0 ? step.options : void 0;
|
|
511
|
+
let model = step.model;
|
|
512
|
+
let agentTools;
|
|
338
513
|
const agentRef = step.agent?.ref;
|
|
339
514
|
if (agentRef !== void 0) {
|
|
340
515
|
if (typeof agentRef !== "string" || agentRef.trim().length === 0) {
|
|
@@ -348,20 +523,36 @@ function compileAgentStep(step, id, ctx) {
|
|
|
348
523
|
);
|
|
349
524
|
}
|
|
350
525
|
if (adapter === void 0) adapter = resolved.adapter;
|
|
351
|
-
if (
|
|
526
|
+
if (model === void 0) model = resolved.model;
|
|
527
|
+
if (options === void 0 && adapter === resolved.adapter) options = resolved.options;
|
|
528
|
+
agentTools = resolved.tools;
|
|
352
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);
|
|
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;
|
|
353
542
|
return buildAgentStep(id, {
|
|
354
543
|
prompt: (b) => renderPrompt(prompt, b),
|
|
355
544
|
...adapter !== void 0 ? { adapter } : {},
|
|
545
|
+
...cwd !== void 0 ? { cwd } : {},
|
|
356
546
|
...step.sessionRef !== void 0 ? { sessionRef: step.sessionRef } : {},
|
|
357
|
-
...
|
|
547
|
+
...model !== void 0 ? { model } : {},
|
|
358
548
|
...step.sandbox !== void 0 ? { sandbox: step.sandbox } : {},
|
|
359
549
|
...step.cacheable ? { cacheable: true } : {},
|
|
360
550
|
...options !== void 0 ? { options } : {},
|
|
361
551
|
policy: step.policy,
|
|
362
|
-
...
|
|
552
|
+
...outputSchema !== void 0 ? { outputSchema } : {},
|
|
363
553
|
...step.maxRetries !== void 0 ? { maxRetries: step.maxRetries } : {},
|
|
364
|
-
...step.harness !== void 0 ? { harness: step.harness } : {}
|
|
554
|
+
...step.harness !== void 0 ? { harness: step.harness } : {},
|
|
555
|
+
...agentTools !== void 0 ? { agentTools } : {}
|
|
365
556
|
});
|
|
366
557
|
}
|
|
367
558
|
function compileGateStep(step, id) {
|
|
@@ -416,20 +607,23 @@ function compileStep(step, ctx) {
|
|
|
416
607
|
tool,
|
|
417
608
|
candidates: opts.candidates,
|
|
418
609
|
input: (b) => resolveValue(inputs, b),
|
|
419
|
-
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 } : {}
|
|
420
612
|
};
|
|
421
613
|
}
|
|
422
614
|
case "map": {
|
|
423
615
|
const over = f(step, "over");
|
|
424
616
|
const inner = compileStepList(f(step, "steps"), `${id}__body`, ctx);
|
|
425
617
|
const onError = f(step, "onError");
|
|
618
|
+
const maxConsecutiveSpawnFailures = f(step, "maxConsecutiveSpawnFailures");
|
|
426
619
|
return {
|
|
427
620
|
kind: "map",
|
|
428
621
|
id,
|
|
429
622
|
parallelism: f(step, "parallelism"),
|
|
430
623
|
over: (b) => resolveRef(over, b),
|
|
431
624
|
body: () => inner,
|
|
432
|
-
...onError !== void 0 ? { onError } : {}
|
|
625
|
+
...onError !== void 0 ? { onError } : {},
|
|
626
|
+
...maxConsecutiveSpawnFailures !== void 0 ? { maxConsecutiveSpawnFailures } : {}
|
|
433
627
|
};
|
|
434
628
|
}
|
|
435
629
|
case "loop": {
|
|
@@ -512,6 +706,28 @@ function compileStep(step, ctx) {
|
|
|
512
706
|
}
|
|
513
707
|
case "gate":
|
|
514
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
|
+
}
|
|
515
731
|
case "transform": {
|
|
516
732
|
const passthrough = step;
|
|
517
733
|
return passthrough;
|
|
@@ -554,6 +770,9 @@ function resolveRefString(stepId, field, value, b, onUnmatched = "passthrough")
|
|
|
554
770
|
}
|
|
555
771
|
return String(hit.resolved) + hit.rest;
|
|
556
772
|
}
|
|
773
|
+
|
|
774
|
+
// src/types.ts
|
|
775
|
+
var DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES = 3;
|
|
557
776
|
var NodeFsPort = class {
|
|
558
777
|
root;
|
|
559
778
|
constructor(root) {
|
|
@@ -748,12 +967,150 @@ var WorkflowSuspendedError = class extends Error {
|
|
|
748
967
|
stepId;
|
|
749
968
|
on;
|
|
750
969
|
};
|
|
751
|
-
|
|
752
|
-
|
|
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
|
+
};
|
|
753
1107
|
}
|
|
754
1108
|
function resolveSel(sel, b) {
|
|
755
1109
|
return typeof sel === "function" ? sel(b) : sel;
|
|
756
1110
|
}
|
|
1111
|
+
function resolveSessionRef(ref, b) {
|
|
1112
|
+
return b.index === void 0 ? ref : ref.replace(/\{\{\s*index\s*\}\}/g, String(b.index));
|
|
1113
|
+
}
|
|
757
1114
|
function extractJsonCandidate(raw) {
|
|
758
1115
|
const jsonFence = /```json\s*([\s\S]*?)```/g;
|
|
759
1116
|
const lastJson = lastMatch(jsonFence, raw);
|
|
@@ -774,40 +1131,166 @@ function spentUsd(state) {
|
|
|
774
1131
|
for (const c of state.costBySession.values()) total += c;
|
|
775
1132
|
return total;
|
|
776
1133
|
}
|
|
777
|
-
|
|
778
|
-
|
|
1134
|
+
var WORKSPACE_HASH_PLACEHOLDER = "\0$run.workspace\0";
|
|
1135
|
+
function hashResolvedInputs(kind, resolved, workspace) {
|
|
1136
|
+
const serialized = `${kind}\0${JSON.stringify(resolved) ?? "undefined"}`;
|
|
1137
|
+
const normalized = workspace ? serialized.split(workspace).join(WORKSPACE_HASH_PLACEHOLDER) : serialized;
|
|
1138
|
+
return createHash("sha256").update(normalized).digest("hex");
|
|
1139
|
+
}
|
|
1140
|
+
function rewriteWorkspacePaths(value, from, to) {
|
|
1141
|
+
if (typeof value === "string") return value.split(from).join(to);
|
|
1142
|
+
if (Array.isArray(value)) return value.map((v) => rewriteWorkspacePaths(v, from, to));
|
|
1143
|
+
if (value !== null && typeof value === "object" && value.constructor === Object) {
|
|
1144
|
+
const out = {};
|
|
1145
|
+
for (const [k, v] of Object.entries(value)) out[k] = rewriteWorkspacePaths(v, from, to);
|
|
1146
|
+
return out;
|
|
1147
|
+
}
|
|
1148
|
+
return value;
|
|
1149
|
+
}
|
|
1150
|
+
function collectWorkspaceCandidates(value, workspace, out) {
|
|
1151
|
+
if (typeof value === "string") {
|
|
1152
|
+
if (value === workspace || value.startsWith(`${workspace}/`)) {
|
|
1153
|
+
const rel = relative(workspace, value);
|
|
1154
|
+
if (rel !== "" && !rel.startsWith("..") && !isAbsolute(rel)) out.add(rel);
|
|
1155
|
+
}
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
if (Array.isArray(value)) {
|
|
1159
|
+
for (const v of value) collectWorkspaceCandidates(v, workspace, out);
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
if (value !== null && typeof value === "object" && value.constructor === Object) {
|
|
1163
|
+
for (const v of Object.values(value)) collectWorkspaceCandidates(v, workspace, out);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
async function collectWorkspaceFiles(out, workspace) {
|
|
1167
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
1168
|
+
collectWorkspaceCandidates(out, workspace, candidates);
|
|
1169
|
+
const files = [];
|
|
1170
|
+
for (const rel of candidates) {
|
|
1171
|
+
try {
|
|
1172
|
+
await stat(join(workspace, rel));
|
|
1173
|
+
files.push(rel);
|
|
1174
|
+
} catch {
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
return files;
|
|
1178
|
+
}
|
|
1179
|
+
async function buildCacheEntry(ctx, out, hash) {
|
|
1180
|
+
if (ctx.workspace === void 0) return { output: out, resolvedInputHash: hash };
|
|
1181
|
+
const workspaceFiles = await collectWorkspaceFiles(out, ctx.workspace);
|
|
1182
|
+
return {
|
|
1183
|
+
output: out,
|
|
1184
|
+
resolvedInputHash: hash,
|
|
1185
|
+
workspaceAtCache: ctx.workspace,
|
|
1186
|
+
...workspaceFiles.length > 0 ? { workspaceFiles } : {}
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
async function relocateCachedOutput(ctx, entry) {
|
|
1190
|
+
const from = entry.workspaceAtCache;
|
|
1191
|
+
const to = ctx.workspace;
|
|
1192
|
+
if (from === void 0 || to === void 0 || from === to) return entry.output;
|
|
1193
|
+
for (const rel of entry.workspaceFiles ?? []) {
|
|
1194
|
+
const dest = join(to, rel);
|
|
1195
|
+
try {
|
|
1196
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
1197
|
+
await cp(join(from, rel), dest, { recursive: true });
|
|
1198
|
+
} catch {
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return rewriteWorkspacePaths(entry.output, from, to);
|
|
779
1202
|
}
|
|
780
|
-
function stepJournalKey(
|
|
781
|
-
return `${cacheKey}\0${step.id}\0${step.kind}`;
|
|
1203
|
+
function stepJournalKey(ctx, step) {
|
|
1204
|
+
return `${ctx.cacheKey}\0${step.id}\0${step.kind}${ctx.cacheKeySuffix ?? ""}`;
|
|
782
1205
|
}
|
|
783
1206
|
function isCacheEnabled(ctx, step) {
|
|
784
1207
|
return step.cacheable === true && ctx.cache !== void 0 && ctx.cacheKey !== void 0;
|
|
785
1208
|
}
|
|
1209
|
+
function cachedHitKey(ctx, stepId) {
|
|
1210
|
+
return `${stepId}${ctx.cacheKeySuffix ?? ""}`;
|
|
1211
|
+
}
|
|
1212
|
+
function completeStep(ctx, stepId, out) {
|
|
1213
|
+
const cached = ctx.state.cachedHits.delete(cachedHitKey(ctx, stepId));
|
|
1214
|
+
ctx.onStepComplete?.(stepId, out, cached ? { cached: true } : void 0);
|
|
1215
|
+
}
|
|
786
1216
|
async function readStepCache(ctx, step, resolvedInputs) {
|
|
787
|
-
const key = stepJournalKey(ctx
|
|
788
|
-
const hash = hashResolvedInputs(step.kind, resolvedInputs);
|
|
1217
|
+
const key = stepJournalKey(ctx, step);
|
|
1218
|
+
const hash = hashResolvedInputs(step.kind, resolvedInputs, ctx.workspace);
|
|
789
1219
|
const entry = await ctx.cache.get(key);
|
|
790
1220
|
if (entry !== void 0 && entry.resolvedInputHash === hash) {
|
|
791
|
-
return { hit: true, output: entry
|
|
1221
|
+
return { hit: true, output: await relocateCachedOutput(ctx, entry) };
|
|
792
1222
|
}
|
|
793
1223
|
return { hit: false, key, hash };
|
|
794
1224
|
}
|
|
795
1225
|
function errorMessage(err) {
|
|
796
1226
|
return err instanceof Error ? err.message : String(err);
|
|
797
1227
|
}
|
|
798
|
-
function
|
|
1228
|
+
function formatSchemaError(err) {
|
|
799
1229
|
return err.issues.map((i) => `${i.path.length > 0 ? i.path.join(".") + ": " : ""}${i.message}`).join(", ");
|
|
800
1230
|
}
|
|
1231
|
+
function describeOutputSchemaForPrompt(schema) {
|
|
1232
|
+
if (schema.jsonSchema !== void 0) {
|
|
1233
|
+
return `When done, reply with ONLY a JSON object matching this JSON Schema: ${JSON.stringify(schema.jsonSchema)}`;
|
|
1234
|
+
}
|
|
1235
|
+
try {
|
|
1236
|
+
const jsonSchema = z.toJSONSchema(schema);
|
|
1237
|
+
return `When done, reply with ONLY a JSON object matching this JSON Schema: ${JSON.stringify(jsonSchema)}`;
|
|
1238
|
+
} catch {
|
|
1239
|
+
}
|
|
1240
|
+
const shape = schema.shape;
|
|
1241
|
+
if (shape && typeof shape === "object") {
|
|
1242
|
+
const fields = Object.keys(shape);
|
|
1243
|
+
if (fields.length > 0) {
|
|
1244
|
+
return `When done, reply with ONLY a JSON object with these fields: ${fields.join(", ")}`;
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
return void 0;
|
|
1248
|
+
}
|
|
1249
|
+
function skippableStepIds(steps, acc = []) {
|
|
1250
|
+
for (const s of steps) {
|
|
1251
|
+
switch (s.kind) {
|
|
1252
|
+
case "group":
|
|
1253
|
+
skippableStepIds(s.steps, acc);
|
|
1254
|
+
break;
|
|
1255
|
+
case "parallel":
|
|
1256
|
+
for (const br of s.branches) skippableStepIds(br.steps, acc);
|
|
1257
|
+
break;
|
|
1258
|
+
case "branch":
|
|
1259
|
+
skippableStepIds(s.then, acc);
|
|
1260
|
+
if (s.otherwise) skippableStepIds(s.otherwise, acc);
|
|
1261
|
+
break;
|
|
1262
|
+
case "loop":
|
|
1263
|
+
skippableStepIds(s.body, acc);
|
|
1264
|
+
break;
|
|
1265
|
+
default:
|
|
1266
|
+
acc.push(s.id);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
return acc;
|
|
1270
|
+
}
|
|
801
1271
|
async function runSequence(steps, ctx, item, index) {
|
|
802
1272
|
let last;
|
|
803
1273
|
for (const s of steps) {
|
|
804
1274
|
const out = await execStep(s, ctx, item, index);
|
|
805
1275
|
ctx.state.steps[s.id] = out;
|
|
806
|
-
ctx
|
|
1276
|
+
completeStep(ctx, s.id, out);
|
|
807
1277
|
last = out;
|
|
808
1278
|
}
|
|
809
1279
|
return last;
|
|
810
1280
|
}
|
|
1281
|
+
async function sendPromptAndAwaitOutcome(ctx, step, sessionId, prompt) {
|
|
1282
|
+
let next = prompt;
|
|
1283
|
+
for (; ; ) {
|
|
1284
|
+
await ctx.agents.sendPromptAndWait(sessionId, next);
|
|
1285
|
+
const req = ctx.agents.takeInputRequest?.(sessionId);
|
|
1286
|
+
if (!req) return;
|
|
1287
|
+
if (!ctx.onInputRequired) {
|
|
1288
|
+
throw new AgentInputRequiredError(step.id, req.prompt, req.schema);
|
|
1289
|
+
}
|
|
1290
|
+
const payload = await ctx.onInputRequired({ stepId: step.id, prompt: req.prompt, schema: req.schema });
|
|
1291
|
+
next = JSON.stringify(payload);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
811
1294
|
async function execAgentStep(step, ctx, b) {
|
|
812
1295
|
ctx.onStepStart?.(step.id);
|
|
813
1296
|
if (step.adapter && ctx.state.maxTotalCostUsd !== void 0 && spentUsd(ctx.state) >= ctx.state.maxTotalCostUsd) {
|
|
@@ -815,7 +1298,8 @@ async function execAgentStep(step, ctx, b) {
|
|
|
815
1298
|
`step '${step.id}': budget_exceeded \u2014 run spend $${spentUsd(ctx.state).toFixed(4)} >= cap $${ctx.state.maxTotalCostUsd}`
|
|
816
1299
|
);
|
|
817
1300
|
}
|
|
818
|
-
const
|
|
1301
|
+
const stepCwd = step.cwd ? resolveSel(step.cwd, b) : void 0;
|
|
1302
|
+
const cwd = step.harness?.cwd ?? (stepCwd !== void 0 ? ctx.cwd !== void 0 ? resolve(ctx.cwd, stepCwd) : stepCwd : ctx.cwd);
|
|
819
1303
|
let knowledgeOut;
|
|
820
1304
|
let knowledgeWarnings = [];
|
|
821
1305
|
if (step.harness?.knowledge && step.harness.knowledge.length > 0) {
|
|
@@ -847,15 +1331,28 @@ ${inner(b2)}` };
|
|
|
847
1331
|
...step.harness ?? {},
|
|
848
1332
|
...model !== void 0 && step.harness?.model === void 0 ? { model } : {}
|
|
849
1333
|
} : void 0;
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1334
|
+
let sessionId;
|
|
1335
|
+
if (step.adapter) {
|
|
1336
|
+
if (ctx.signal?.aborted) throw new Error(`step '${step.id}': run cancelled \u2014 not spawning`);
|
|
1337
|
+
try {
|
|
1338
|
+
sessionId = await ctx.agents.spawn(resolveSel(step.adapter, b), {
|
|
1339
|
+
cwd,
|
|
1340
|
+
workspaceSlug: ctx.workspaceSlug,
|
|
1341
|
+
stepId: step.id,
|
|
1342
|
+
...sandbox !== void 0 ? { sandbox } : {},
|
|
1343
|
+
...step.options !== void 0 ? { options: step.options } : {},
|
|
1344
|
+
...harness !== void 0 ? { harness } : {},
|
|
1345
|
+
...step.agentTools !== void 0 ? { agentTools: step.agentTools } : {},
|
|
1346
|
+
...b.index !== void 0 ? { stepKey: `${step.id}[${b.index}]` } : {}
|
|
1347
|
+
});
|
|
1348
|
+
} catch (err) {
|
|
1349
|
+
throw new AgentSpawnError(step.id, err);
|
|
1350
|
+
}
|
|
1351
|
+
} else {
|
|
1352
|
+
sessionId = ctx.agents.resolveByLabel(resolveSessionRef(step.sessionRef, b));
|
|
1353
|
+
}
|
|
858
1354
|
if (!sessionId) throw new Error(`step '${step.id}': no session (adapter and sessionRef both unresolved)`);
|
|
1355
|
+
if (step.adapter) ctx.spawned?.push(sessionId);
|
|
859
1356
|
if (knowledgeWarnings.length > 0 && ctx.agents.emitHarnessWarning) {
|
|
860
1357
|
ctx.agents.emitHarnessWarning({
|
|
861
1358
|
sessionId,
|
|
@@ -867,7 +1364,12 @@ ${inner(b2)}` };
|
|
|
867
1364
|
...harness,
|
|
868
1365
|
...harness.tools && harness.tools.length > 0 ? { toolsApplied: false } : {}
|
|
869
1366
|
} : void 0;
|
|
870
|
-
|
|
1367
|
+
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." : "";
|
|
1368
|
+
const outputSchemaNote = step.outputSchema ? describeOutputSchemaForPrompt(step.outputSchema) : void 0;
|
|
1369
|
+
const outputSchemaAffordance = outputSchemaNote ? `
|
|
1370
|
+
|
|
1371
|
+
${outputSchemaNote}` : "";
|
|
1372
|
+
await sendPromptAndAwaitOutcome(ctx, step, sessionId, step.prompt(b) + inputRequestAffordance + outputSchemaAffordance);
|
|
871
1373
|
if (step.policy && ctx.agents.onAwaitingInput) {
|
|
872
1374
|
await ctx.agents.onAwaitingInput(sessionId, step.policy);
|
|
873
1375
|
}
|
|
@@ -889,8 +1391,10 @@ ${inner(b2)}` };
|
|
|
889
1391
|
}
|
|
890
1392
|
const maxRetries = step.maxRetries ?? 2;
|
|
891
1393
|
let lastErr = "";
|
|
1394
|
+
let lastRaw = "";
|
|
892
1395
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
893
1396
|
const raw = await ctx.agents.readFinalMessage(sessionId);
|
|
1397
|
+
lastRaw = raw;
|
|
894
1398
|
const candidate = extractJsonCandidate(raw);
|
|
895
1399
|
let value;
|
|
896
1400
|
try {
|
|
@@ -898,24 +1402,34 @@ ${inner(b2)}` };
|
|
|
898
1402
|
} catch {
|
|
899
1403
|
lastErr = "not valid JSON";
|
|
900
1404
|
if (attempt < maxRetries) {
|
|
901
|
-
await
|
|
1405
|
+
await sendPromptAndAwaitOutcome(
|
|
1406
|
+
ctx,
|
|
1407
|
+
step,
|
|
902
1408
|
sessionId,
|
|
903
|
-
`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.`
|
|
1409
|
+
`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
|
|
904
1410
|
);
|
|
905
1411
|
}
|
|
906
1412
|
continue;
|
|
907
1413
|
}
|
|
908
1414
|
const res = step.outputSchema.safeParse(value);
|
|
909
1415
|
if (res.success) return { sessionId, output: res.data, ...harnessOut ? { harness: harnessOut } : {}, ...knowledgeOut ? { knowledgeApplied: knowledgeOut } : {} };
|
|
910
|
-
lastErr =
|
|
1416
|
+
lastErr = formatSchemaError(res.error);
|
|
911
1417
|
if (attempt < maxRetries) {
|
|
912
|
-
await
|
|
1418
|
+
await sendPromptAndAwaitOutcome(
|
|
1419
|
+
ctx,
|
|
1420
|
+
step,
|
|
913
1421
|
sessionId,
|
|
914
|
-
`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.`
|
|
1422
|
+
`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
|
|
915
1423
|
);
|
|
916
1424
|
}
|
|
917
1425
|
}
|
|
918
|
-
|
|
1426
|
+
const hint = lastRaw.trim().endsWith("?") ? "possible-input-request" : void 0;
|
|
1427
|
+
throw new StepOutcomeError(
|
|
1428
|
+
step.id,
|
|
1429
|
+
"missing-output",
|
|
1430
|
+
`step '${step.id}': missing-output \u2014 final message never matched outputSchema (${lastErr})`,
|
|
1431
|
+
hint
|
|
1432
|
+
);
|
|
919
1433
|
}
|
|
920
1434
|
function tryParseJson(text) {
|
|
921
1435
|
const trimmed = text.trim();
|
|
@@ -1020,10 +1534,25 @@ Please address the findings above, then reply when done.`
|
|
|
1020
1534
|
}
|
|
1021
1535
|
return last;
|
|
1022
1536
|
}
|
|
1537
|
+
function cacheHit(ctx, step, output) {
|
|
1538
|
+
ctx.state.cachedHits.add(cachedHitKey(ctx, step.id));
|
|
1539
|
+
ctx.onStepStart?.(step.id, { cached: true });
|
|
1540
|
+
return output;
|
|
1541
|
+
}
|
|
1023
1542
|
async function execStep(step, ctx, item, index) {
|
|
1543
|
+
try {
|
|
1544
|
+
return await execStepBody(step, ctx, item, index);
|
|
1545
|
+
} catch (err) {
|
|
1546
|
+
if (typeof err === "object" && err !== null && !failureOrigin.has(err)) {
|
|
1547
|
+
failureOrigin.set(err, (info) => ctx.onStepFailed?.(step.id, info));
|
|
1548
|
+
}
|
|
1549
|
+
throw err;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
async function execStepBody(step, ctx, item, index) {
|
|
1024
1553
|
const { state, signal } = ctx;
|
|
1025
|
-
const b = view(
|
|
1026
|
-
if (step.kind !== "agent") {
|
|
1554
|
+
const b = view(ctx, item, index);
|
|
1555
|
+
if (step.kind !== "agent" && step.kind !== "tool" && step.kind !== "artifact") {
|
|
1027
1556
|
ctx.onStepStart?.(step.id);
|
|
1028
1557
|
}
|
|
1029
1558
|
switch (step.kind) {
|
|
@@ -1038,11 +1567,15 @@ async function execStep(step, ctx, item, index) {
|
|
|
1038
1567
|
secrets: step.secrets,
|
|
1039
1568
|
signal
|
|
1040
1569
|
});
|
|
1041
|
-
if (!isCacheEnabled(ctx, step))
|
|
1570
|
+
if (!isCacheEnabled(ctx, step)) {
|
|
1571
|
+
ctx.onStepStart?.(step.id);
|
|
1572
|
+
return runIt();
|
|
1573
|
+
}
|
|
1042
1574
|
const c = await readStepCache(ctx, step, input);
|
|
1043
|
-
if (c.hit) return c.output;
|
|
1575
|
+
if (c.hit) return cacheHit(ctx, step, c.output);
|
|
1576
|
+
ctx.onStepStart?.(step.id);
|
|
1044
1577
|
const out = await runIt();
|
|
1045
|
-
await ctx.cache.set(c.key,
|
|
1578
|
+
await ctx.cache.set(c.key, await buildCacheEntry(ctx, out, c.hash));
|
|
1046
1579
|
return out;
|
|
1047
1580
|
}
|
|
1048
1581
|
case "transform":
|
|
@@ -1052,81 +1585,110 @@ async function execStep(step, ctx, item, index) {
|
|
|
1052
1585
|
const parallelism = Math.max(1, step.parallelism ?? 1);
|
|
1053
1586
|
const tolerant = step.onError === "collect";
|
|
1054
1587
|
const results = new Array(arr.length);
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
);
|
|
1065
|
-
|
|
1066
|
-
|
|
1588
|
+
let next = 0;
|
|
1589
|
+
let failed = false;
|
|
1590
|
+
const breaker = spawnBreaker(step.maxConsecutiveSpawnFailures ?? DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES);
|
|
1591
|
+
const runItem = async (idx) => {
|
|
1592
|
+
const el = arr[idx];
|
|
1593
|
+
const inner = step.body(el, idx, view(ctx, el, idx));
|
|
1594
|
+
const wrapped = withReleaseScope(withIndexedHooks(ctx, idx));
|
|
1595
|
+
try {
|
|
1596
|
+
const out = await execStep(inner, wrapped, el, idx);
|
|
1597
|
+
completeStep(wrapped, inner.id, out);
|
|
1598
|
+
results[idx] = tolerant ? { status: "fulfilled", index: idx, value: out } : out;
|
|
1599
|
+
breaker.settle();
|
|
1600
|
+
} catch (err) {
|
|
1601
|
+
if (!tolerant) {
|
|
1602
|
+
failed = true;
|
|
1603
|
+
throw err;
|
|
1604
|
+
}
|
|
1605
|
+
results[idx] = { status: "rejected", index: idx, item: el, error: errorMessage(err) };
|
|
1606
|
+
reportItemFailure(err, wrapped, inner.id);
|
|
1607
|
+
breaker.settle(err);
|
|
1608
|
+
} finally {
|
|
1609
|
+
await releaseScope(wrapped);
|
|
1067
1610
|
}
|
|
1068
|
-
const settled = await Promise.allSettled(
|
|
1069
|
-
chunk.map((el, j) => {
|
|
1070
|
-
const idx = i + j;
|
|
1071
|
-
const inner = step.body(el, idx, view(state, el, idx));
|
|
1072
|
-
return execStep(inner, ctx, el, idx);
|
|
1073
|
-
})
|
|
1074
|
-
);
|
|
1075
|
-
settled.forEach((s, j) => {
|
|
1076
|
-
const idx = i + j;
|
|
1077
|
-
results[idx] = s.status === "fulfilled" ? { status: "fulfilled", index: idx, value: s.value } : { status: "rejected", index: idx, item: chunk[j], error: errorMessage(s.reason) };
|
|
1078
|
-
});
|
|
1079
|
-
}
|
|
1080
|
-
if (!tolerant) return results;
|
|
1081
|
-
const outcomes = results;
|
|
1082
|
-
return {
|
|
1083
|
-
results: outcomes,
|
|
1084
|
-
succeeded: outcomes.filter((r) => r.status === "fulfilled").length,
|
|
1085
|
-
failed: outcomes.filter((r) => r.status === "rejected").length
|
|
1086
1611
|
};
|
|
1612
|
+
const worker = async () => {
|
|
1613
|
+
while (!failed && breaker.open === void 0 && next < arr.length) {
|
|
1614
|
+
const idx = next++;
|
|
1615
|
+
await runItem(idx);
|
|
1616
|
+
}
|
|
1617
|
+
};
|
|
1618
|
+
await Promise.all(Array.from({ length: Math.min(parallelism, arr.length) }, () => worker()));
|
|
1619
|
+
if (!tolerant) return results;
|
|
1620
|
+
if (breaker.open !== void 0) {
|
|
1621
|
+
skipUnstartedItems(ctx, step.id, next, arr, (el, idx) => [step.body(el, idx, view(ctx, el, idx))], breaker.open, results);
|
|
1622
|
+
}
|
|
1623
|
+
return tolerantResult(results, breaker.open);
|
|
1087
1624
|
}
|
|
1088
1625
|
case "pipeline": {
|
|
1089
1626
|
const items = [...step.over(b)];
|
|
1090
1627
|
const tolerant = step.onError === "collect";
|
|
1091
|
-
if (items.length === 0) return tolerant ?
|
|
1628
|
+
if (items.length === 0) return tolerant ? tolerantResult([], void 0) : [];
|
|
1092
1629
|
const cap = Math.max(1, step.concurrency ?? items.length);
|
|
1093
1630
|
const results = new Array(items.length);
|
|
1094
1631
|
let next = 0;
|
|
1632
|
+
const breaker = spawnBreaker(step.maxConsecutiveSpawnFailures ?? DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES);
|
|
1095
1633
|
const runItem = async (idx) => {
|
|
1096
1634
|
let prev = void 0;
|
|
1635
|
+
let stageId = step.id;
|
|
1636
|
+
const wrapped = withReleaseScope(withIndexedHooks(ctx, idx));
|
|
1097
1637
|
try {
|
|
1098
1638
|
for (const stage of step.stages) {
|
|
1099
|
-
|
|
1639
|
+
const inner = stage(items[idx], idx, prev, view(ctx, items[idx], idx));
|
|
1640
|
+
stageId = inner.id;
|
|
1641
|
+
prev = await execStep(inner, wrapped, items[idx], idx);
|
|
1642
|
+
completeStep(wrapped, inner.id, prev);
|
|
1100
1643
|
}
|
|
1101
1644
|
results[idx] = tolerant ? { status: "fulfilled", index: idx, value: prev } : prev;
|
|
1645
|
+
breaker.settle();
|
|
1102
1646
|
} catch (err) {
|
|
1103
1647
|
if (!tolerant) throw err;
|
|
1104
1648
|
results[idx] = { status: "rejected", index: idx, item: items[idx], error: errorMessage(err) };
|
|
1649
|
+
reportItemFailure(err, wrapped, stageId);
|
|
1650
|
+
breaker.settle(err);
|
|
1651
|
+
} finally {
|
|
1652
|
+
await releaseScope(wrapped);
|
|
1105
1653
|
}
|
|
1106
1654
|
};
|
|
1107
1655
|
const worker = async () => {
|
|
1108
|
-
while (next < items.length) {
|
|
1656
|
+
while ((!tolerant || breaker.open === void 0) && next < items.length) {
|
|
1109
1657
|
const idx = next++;
|
|
1110
1658
|
await runItem(idx);
|
|
1111
1659
|
}
|
|
1112
1660
|
};
|
|
1113
1661
|
await Promise.all(Array.from({ length: Math.min(cap, items.length) }, () => worker()));
|
|
1114
1662
|
if (!tolerant) return results;
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1663
|
+
if (breaker.open !== void 0) {
|
|
1664
|
+
skipUnstartedItems(
|
|
1665
|
+
ctx,
|
|
1666
|
+
step.id,
|
|
1667
|
+
next,
|
|
1668
|
+
items,
|
|
1669
|
+
(el, idx) => step.stages.map((stage) => stage(el, idx, void 0, view(ctx, el, idx))),
|
|
1670
|
+
breaker.open,
|
|
1671
|
+
results
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
return tolerantResult(results, breaker.open);
|
|
1121
1675
|
}
|
|
1122
1676
|
case "branch": {
|
|
1123
|
-
const
|
|
1677
|
+
const taken = step.cond(b);
|
|
1678
|
+
const chosen = taken ? step.then : step.otherwise ?? [];
|
|
1679
|
+
if (ctx.onStepSkipped) {
|
|
1680
|
+
const onChosenPath = new Set(skippableStepIds(chosen));
|
|
1681
|
+
const untaken = taken ? step.otherwise ?? [] : step.then;
|
|
1682
|
+
for (const id of new Set(skippableStepIds(untaken))) {
|
|
1683
|
+
if (!onChosenPath.has(id)) ctx.onStepSkipped(id, { reason: "branch-not-taken", branchId: step.sourceId ?? step.id });
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1124
1686
|
return runSequence(chosen, ctx, item, index);
|
|
1125
1687
|
}
|
|
1126
1688
|
case "loop": {
|
|
1127
1689
|
let iterations = 0;
|
|
1128
1690
|
let last;
|
|
1129
|
-
while (iterations < step.maxIterations && step.while(view(
|
|
1691
|
+
while (iterations < step.maxIterations && step.while(view(ctx, item, index))) {
|
|
1130
1692
|
last = await runSequence(step.body, ctx, item, index);
|
|
1131
1693
|
iterations++;
|
|
1132
1694
|
}
|
|
@@ -1172,14 +1734,21 @@ async function execStep(step, ctx, item, index) {
|
|
|
1172
1734
|
const child = await runWorkflowInner(step.workflow, childInput, {
|
|
1173
1735
|
approve: ctx.approve,
|
|
1174
1736
|
resume: ctx.resume,
|
|
1737
|
+
onInputRequired: ctx.onInputRequired,
|
|
1175
1738
|
signal,
|
|
1176
1739
|
agents: ctx.agents,
|
|
1177
1740
|
cwd: ctx.cwd,
|
|
1178
1741
|
workspaceSlug: ctx.workspaceSlug,
|
|
1742
|
+
workspace: ctx.workspace,
|
|
1743
|
+
artifactsDir: ctx.artifactsDir,
|
|
1744
|
+
runId: ctx.runId,
|
|
1745
|
+
onArtifact: ctx.onArtifact,
|
|
1179
1746
|
cache: ctx.cache,
|
|
1180
1747
|
cacheKey: ctx.cacheKey,
|
|
1181
1748
|
runGateCommand: ctx.runGateCommand,
|
|
1182
|
-
onGateReport: ctx.onGateReport
|
|
1749
|
+
onGateReport: ctx.onGateReport,
|
|
1750
|
+
spawned: ctx.spawned,
|
|
1751
|
+
usedArtifactNames: ctx.usedArtifactNames
|
|
1183
1752
|
});
|
|
1184
1753
|
return child.output;
|
|
1185
1754
|
}
|
|
@@ -1190,29 +1759,165 @@ async function execStep(step, ctx, item, index) {
|
|
|
1190
1759
|
prompt: step.prompt(b),
|
|
1191
1760
|
adapter: step.adapter ? resolveSel(step.adapter, b) : void 0,
|
|
1192
1761
|
model: step.model ? resolveSel(step.model, b) : void 0,
|
|
1193
|
-
sessionRef: step.sessionRef
|
|
1762
|
+
sessionRef: step.sessionRef !== void 0 ? resolveSessionRef(step.sessionRef, b) : void 0
|
|
1194
1763
|
};
|
|
1195
1764
|
const c = await readStepCache(ctx, step, resolved);
|
|
1196
|
-
if (c.hit) return c.output;
|
|
1765
|
+
if (c.hit) return cacheHit(ctx, step, c.output);
|
|
1197
1766
|
const out = await execAgentStep(step, ctx, b);
|
|
1198
|
-
await ctx.cache.set(c.key,
|
|
1767
|
+
await ctx.cache.set(c.key, await buildCacheEntry(ctx, out, c.hash));
|
|
1199
1768
|
return out;
|
|
1200
1769
|
}
|
|
1201
1770
|
case "gate":
|
|
1202
1771
|
return execGateStep(step, ctx, b);
|
|
1772
|
+
case "artifact":
|
|
1773
|
+
return execArtifactStep(step, ctx, b);
|
|
1203
1774
|
}
|
|
1204
1775
|
}
|
|
1776
|
+
function sanitizeArtifactFilename(name) {
|
|
1777
|
+
const cleaned = name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
1778
|
+
return cleaned.length > 0 ? cleaned : "artifact";
|
|
1779
|
+
}
|
|
1780
|
+
function reserveArtifactDestName(rawPath, key, usedNames) {
|
|
1781
|
+
const base = sanitizeArtifactFilename(basename(rawPath));
|
|
1782
|
+
let candidate = base;
|
|
1783
|
+
if (usedNames.has(candidate)) {
|
|
1784
|
+
const keyPart = sanitizeArtifactFilename(key);
|
|
1785
|
+
candidate = `${keyPart}-${base}`;
|
|
1786
|
+
for (let n = 2; usedNames.has(candidate); n++) {
|
|
1787
|
+
candidate = `${keyPart}-${n}-${base}`;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
usedNames.add(candidate);
|
|
1791
|
+
return candidate;
|
|
1792
|
+
}
|
|
1793
|
+
async function copyIntoArtifacts(stepId, key, rawPath, workspace, artifactsDir, contentType, usedNames) {
|
|
1794
|
+
const abs = isAbsolute(rawPath) ? resolve(rawPath) : resolve(workspace, rawPath);
|
|
1795
|
+
const rel = relative(workspace, abs);
|
|
1796
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
1797
|
+
throw new Error(`step '${stepId}': artifact path '${rawPath}' resolves outside the run workspace`);
|
|
1798
|
+
}
|
|
1799
|
+
const buf = await readFile(abs);
|
|
1800
|
+
const sha256 = createHash("sha256").update(buf).digest("hex");
|
|
1801
|
+
const destName = reserveArtifactDestName(rawPath, key, usedNames);
|
|
1802
|
+
await mkdir(artifactsDir, { recursive: true });
|
|
1803
|
+
await writeFile(join(artifactsDir, destName), buf);
|
|
1804
|
+
return {
|
|
1805
|
+
key,
|
|
1806
|
+
path: `artifacts/${destName}`,
|
|
1807
|
+
sha256,
|
|
1808
|
+
size: buf.length,
|
|
1809
|
+
stepId,
|
|
1810
|
+
...contentType !== void 0 ? { contentType } : {}
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
async function execArtifactStep(step, ctx, b) {
|
|
1814
|
+
if (ctx.workspace === void 0 || ctx.artifactsDir === void 0) {
|
|
1815
|
+
throw new Error(`step '${step.id}': kind:"artifact" requires a run workspace (no workspace/artifactsDir wired)`);
|
|
1816
|
+
}
|
|
1817
|
+
const workspace = ctx.workspace;
|
|
1818
|
+
const artifactsDir = ctx.artifactsDir;
|
|
1819
|
+
const key = resolveSel(step.key, b);
|
|
1820
|
+
const rawPath = resolveSel(step.path, b);
|
|
1821
|
+
const contentType = step.contentType ? resolveSel(step.contentType, b) : void 0;
|
|
1822
|
+
const resolvedInputs = { key, path: rawPath };
|
|
1823
|
+
const cacheOn = ctx.cache !== void 0 && ctx.cacheKey !== void 0;
|
|
1824
|
+
const journalKey = cacheOn ? stepJournalKey(ctx, step) : void 0;
|
|
1825
|
+
const hash = cacheOn ? hashResolvedInputs(step.kind, resolvedInputs, ctx.workspace) : void 0;
|
|
1826
|
+
if (cacheOn) {
|
|
1827
|
+
const entry = await ctx.cache.get(journalKey);
|
|
1828
|
+
if (entry !== void 0 && entry.resolvedInputHash === hash) {
|
|
1829
|
+
const out2 = entry.output;
|
|
1830
|
+
const destName = out2.path.slice("artifacts/".length);
|
|
1831
|
+
ctx.usedArtifactNames?.add(destName);
|
|
1832
|
+
const destAbs = join(artifactsDir, destName);
|
|
1833
|
+
const srcAbs = join(entry.artifactsDirAtCache ?? artifactsDir, destName);
|
|
1834
|
+
if (srcAbs !== destAbs) {
|
|
1835
|
+
await mkdir(dirname(destAbs), { recursive: true });
|
|
1836
|
+
try {
|
|
1837
|
+
await copyFile(srcAbs, destAbs);
|
|
1838
|
+
} catch {
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
ctx.onArtifact?.(out2);
|
|
1842
|
+
return cacheHit(ctx, step, out2);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
ctx.onStepStart?.(step.id);
|
|
1846
|
+
const out = await copyIntoArtifacts(step.id, key, rawPath, workspace, artifactsDir, contentType, ctx.usedArtifactNames);
|
|
1847
|
+
if (cacheOn) {
|
|
1848
|
+
await ctx.cache.set(journalKey, { output: out, resolvedInputHash: hash, artifactsDirAtCache: artifactsDir });
|
|
1849
|
+
}
|
|
1850
|
+
ctx.onArtifact?.(out);
|
|
1851
|
+
return out;
|
|
1852
|
+
}
|
|
1853
|
+
function interpolateFileContractPath(path, workflowId, runId) {
|
|
1854
|
+
return path.replace(/<runId>/g, runId ?? "").replace(/<workflowId>/g, workflowId).replace(/<isoDate>/g, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
1855
|
+
}
|
|
1856
|
+
async function checkOutputsFiles(outputsFiles, ctx, workflowId, lastStepId) {
|
|
1857
|
+
if (!outputsFiles) return;
|
|
1858
|
+
if (ctx.workspace === void 0 || ctx.artifactsDir === void 0) return;
|
|
1859
|
+
for (const [key, contract] of Object.entries(outputsFiles)) {
|
|
1860
|
+
const rawPath = interpolateFileContractPath(contract.path, workflowId, ctx.runId);
|
|
1861
|
+
const abs = isAbsolute(rawPath) ? rawPath : resolve(ctx.workspace, rawPath);
|
|
1862
|
+
let out;
|
|
1863
|
+
try {
|
|
1864
|
+
out = await copyIntoArtifacts(lastStepId ?? workflowId, key, abs, ctx.workspace, ctx.artifactsDir, contract.contentType, ctx.usedArtifactNames);
|
|
1865
|
+
} catch {
|
|
1866
|
+
if (contract.required === true) {
|
|
1867
|
+
throw new MissingArtifactError(key, lastStepId);
|
|
1868
|
+
}
|
|
1869
|
+
console.warn(
|
|
1870
|
+
`[workflow-runtime] outputsFiles.${key} ('${contract.path}') is missing and not marked required \u2014 the run still succeeds (AIP-58 \xA74)`
|
|
1871
|
+
);
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
1874
|
+
ctx.onArtifact?.(out);
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
async function runFinally(steps, ctx, bodyFailed) {
|
|
1878
|
+
const cleanupCtx = { ...ctx, signal: void 0 };
|
|
1879
|
+
let firstError;
|
|
1880
|
+
for (const step of steps) {
|
|
1881
|
+
try {
|
|
1882
|
+
const out = await execStep(step, cleanupCtx, void 0, void 0);
|
|
1883
|
+
cleanupCtx.state.steps[step.id] = out;
|
|
1884
|
+
completeStep(cleanupCtx, step.id, out);
|
|
1885
|
+
} catch (err) {
|
|
1886
|
+
cleanupCtx.onStepFailed?.(step.id, { error: errorMessage(err) });
|
|
1887
|
+
if (firstError === void 0) firstError = err;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
if (!bodyFailed && firstError !== void 0) throw firstError;
|
|
1891
|
+
}
|
|
1205
1892
|
async function runWorkflowInner(workflow, input, hooks, maxTotalCostUsd) {
|
|
1206
|
-
const state = { input, steps: {}, costBySession: /* @__PURE__ */ new Map(), maxTotalCostUsd };
|
|
1207
|
-
const
|
|
1893
|
+
const state = { input, steps: {}, costBySession: /* @__PURE__ */ new Map(), maxTotalCostUsd, cachedHits: /* @__PURE__ */ new Set() };
|
|
1894
|
+
const ownsScope = hooks.spawned === void 0;
|
|
1895
|
+
const ctx = {
|
|
1896
|
+
state,
|
|
1897
|
+
...hooks,
|
|
1898
|
+
...ownsScope ? { spawned: [], usedArtifactNames: /* @__PURE__ */ new Set() } : {}
|
|
1899
|
+
};
|
|
1208
1900
|
let lastId;
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1901
|
+
let bodyFailed = false;
|
|
1902
|
+
try {
|
|
1903
|
+
for (const step of workflow.steps) {
|
|
1904
|
+
const out = await execStep(step, ctx, void 0, void 0);
|
|
1905
|
+
state.steps[step.id] = out;
|
|
1906
|
+
completeStep(ctx, step.id, out);
|
|
1907
|
+
lastId = step.id;
|
|
1908
|
+
}
|
|
1909
|
+
await checkOutputsFiles(workflow.outputsFiles, ctx, workflow.id, lastId);
|
|
1910
|
+
} catch (err) {
|
|
1911
|
+
bodyFailed = true;
|
|
1912
|
+
throw err;
|
|
1913
|
+
} finally {
|
|
1914
|
+
try {
|
|
1915
|
+
if (workflow.finally && workflow.finally.length > 0) await runFinally(workflow.finally, ctx, bodyFailed);
|
|
1916
|
+
} finally {
|
|
1917
|
+
if (ownsScope) await releaseScope(ctx);
|
|
1918
|
+
}
|
|
1214
1919
|
}
|
|
1215
|
-
const bindings = view(
|
|
1920
|
+
const bindings = view(ctx);
|
|
1216
1921
|
const output = workflow.output ? workflow.output(bindings) : lastId !== void 0 ? state.steps[lastId] : void 0;
|
|
1217
1922
|
return { output, bindings };
|
|
1218
1923
|
}
|
|
@@ -1220,14 +1925,21 @@ async function runWorkflow(args) {
|
|
|
1220
1925
|
return runWorkflowInner(args.workflow, args.input, {
|
|
1221
1926
|
approve: args.approve,
|
|
1222
1927
|
resume: args.resume,
|
|
1928
|
+
onInputRequired: args.onInputRequired,
|
|
1223
1929
|
signal: args.signal,
|
|
1224
1930
|
agents: args.agents,
|
|
1225
1931
|
cwd: args.cwd,
|
|
1226
1932
|
workspaceSlug: args.workspaceSlug,
|
|
1933
|
+
workspace: args.workspace,
|
|
1934
|
+
artifactsDir: args.artifactsDir,
|
|
1935
|
+
runId: args.runId,
|
|
1936
|
+
onArtifact: args.onArtifact,
|
|
1227
1937
|
cache: args.cache,
|
|
1228
1938
|
cacheKey: args.cacheKey,
|
|
1229
1939
|
onStepStart: args.onStepStart,
|
|
1230
1940
|
onStepComplete: args.onStepComplete,
|
|
1941
|
+
onStepSkipped: args.onStepSkipped,
|
|
1942
|
+
onStepFailed: args.onStepFailed,
|
|
1231
1943
|
runGateCommand: args.runGateCommand,
|
|
1232
1944
|
onGateReport: args.onGateReport
|
|
1233
1945
|
}, args.maxTotalCostUsd);
|
|
@@ -1238,6 +1950,6 @@ function compileWorkflowManifest(source, opts) {
|
|
|
1238
1950
|
return compileWorkflow(handle, opts);
|
|
1239
1951
|
}
|
|
1240
1952
|
|
|
1241
|
-
export { NodeFsPort, WorkflowCompileError, WorkflowSuspendedError, buildAgentStep, compileWorkflow, compileWorkflowManifest, evalPredicate, interpolateTemplate, materializeKnowledge, resolveKnowledgeSelectors, resolveRef, resolveRefPrefixed, resolveValue, runWorkflow };
|
|
1953
|
+
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 };
|
|
1242
1954
|
//# sourceMappingURL=index.mjs.map
|
|
1243
1955
|
//# sourceMappingURL=index.mjs.map
|