@agentproto/workflow-runtime 0.12.0 → 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/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) {
@@ -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
- ...output ? { output } : {}
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
- out.push(compileBranchChain(steps, i, ctx));
286
- return out;
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 compileBranchChain(steps, i, ctx) {
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 resolveTarget = (targetId, label) => {
302
- const idx = steps.findIndex((s) => s.id === targetId);
303
- if (idx === -1 || idx <= i) {
304
- throw new WorkflowCompileError(
305
- `branch '${branchStep.id}' ${label} targets '${targetId}', which is not a sibling later in this step list \u2014 the forward-only branch compiler only supports jumping to a later sibling in the SAME list (not backward, and not into a nested map/loop/parallel body). Hand-author a 'loop' step for retry-style control flow instead.`
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 idx;
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 (options === void 0) options = resolved.options;
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
- ...step.model !== void 0 ? { model: step.model } : {},
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
- ...step.outputSchema !== void 0 ? { outputSchema: step.outputSchema } : {},
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
- function view(state, item, index) {
752
- return { input: state.input, steps: state.steps, item, index };
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);
@@ -777,14 +1134,21 @@ function spentUsd(state) {
777
1134
  function hashResolvedInputs(kind, resolved) {
778
1135
  return createHash("sha256").update(`${kind}\0${JSON.stringify(resolved) ?? "undefined"}`).digest("hex");
779
1136
  }
780
- function stepJournalKey(cacheKey, step) {
781
- 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 ?? ""}`;
782
1139
  }
783
1140
  function isCacheEnabled(ctx, step) {
784
1141
  return step.cacheable === true && ctx.cache !== void 0 && ctx.cacheKey !== void 0;
785
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
+ }
786
1150
  async function readStepCache(ctx, step, resolvedInputs) {
787
- const key = stepJournalKey(ctx.cacheKey, step);
1151
+ const key = stepJournalKey(ctx, step);
788
1152
  const hash = hashResolvedInputs(step.kind, resolvedInputs);
789
1153
  const entry = await ctx.cache.get(key);
790
1154
  if (entry !== void 0 && entry.resolvedInputHash === hash) {
@@ -795,19 +1159,72 @@ async function readStepCache(ctx, step, resolvedInputs) {
795
1159
  function errorMessage(err) {
796
1160
  return err instanceof Error ? err.message : String(err);
797
1161
  }
798
- function formatZodError(err) {
1162
+ function formatSchemaError(err) {
799
1163
  return err.issues.map((i) => `${i.path.length > 0 ? i.path.join(".") + ": " : ""}${i.message}`).join(", ");
800
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
+ }
801
1205
  async function runSequence(steps, ctx, item, index) {
802
1206
  let last;
803
1207
  for (const s of steps) {
804
1208
  const out = await execStep(s, ctx, item, index);
805
1209
  ctx.state.steps[s.id] = out;
806
- ctx.onStepComplete?.(s.id, out);
1210
+ completeStep(ctx, s.id, out);
807
1211
  last = out;
808
1212
  }
809
1213
  return last;
810
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
+ }
811
1228
  async function execAgentStep(step, ctx, b) {
812
1229
  ctx.onStepStart?.(step.id);
813
1230
  if (step.adapter && ctx.state.maxTotalCostUsd !== void 0 && spentUsd(ctx.state) >= ctx.state.maxTotalCostUsd) {
@@ -815,7 +1232,8 @@ async function execAgentStep(step, ctx, b) {
815
1232
  `step '${step.id}': budget_exceeded \u2014 run spend $${spentUsd(ctx.state).toFixed(4)} >= cap $${ctx.state.maxTotalCostUsd}`
816
1233
  );
817
1234
  }
818
- const cwd = step.harness?.cwd ?? (step.cwd ? resolveSel(step.cwd, b) : ctx.cwd);
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);
819
1237
  let knowledgeOut;
820
1238
  let knowledgeWarnings = [];
821
1239
  if (step.harness?.knowledge && step.harness.knowledge.length > 0) {
@@ -847,15 +1265,28 @@ ${inner(b2)}` };
847
1265
  ...step.harness ?? {},
848
1266
  ...model !== void 0 && step.harness?.model === void 0 ? { model } : {}
849
1267
  } : void 0;
850
- const sessionId = step.adapter ? await ctx.agents.spawn(resolveSel(step.adapter, b), {
851
- cwd,
852
- workspaceSlug: ctx.workspaceSlug,
853
- stepId: step.id,
854
- ...sandbox !== void 0 ? { sandbox } : {},
855
- ...step.options !== void 0 ? { options: step.options } : {},
856
- ...harness !== void 0 ? { harness } : {}
857
- }) : ctx.agents.resolveByLabel(step.sessionRef);
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
+ }
858
1288
  if (!sessionId) throw new Error(`step '${step.id}': no session (adapter and sessionRef both unresolved)`);
1289
+ if (step.adapter) ctx.spawned?.push(sessionId);
859
1290
  if (knowledgeWarnings.length > 0 && ctx.agents.emitHarnessWarning) {
860
1291
  ctx.agents.emitHarnessWarning({
861
1292
  sessionId,
@@ -867,7 +1298,12 @@ ${inner(b2)}` };
867
1298
  ...harness,
868
1299
  ...harness.tools && harness.tools.length > 0 ? { toolsApplied: false } : {}
869
1300
  } : void 0;
870
- await ctx.agents.sendPromptAndWait(sessionId, step.prompt(b));
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);
871
1307
  if (step.policy && ctx.agents.onAwaitingInput) {
872
1308
  await ctx.agents.onAwaitingInput(sessionId, step.policy);
873
1309
  }
@@ -889,8 +1325,10 @@ ${inner(b2)}` };
889
1325
  }
890
1326
  const maxRetries = step.maxRetries ?? 2;
891
1327
  let lastErr = "";
1328
+ let lastRaw = "";
892
1329
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
893
1330
  const raw = await ctx.agents.readFinalMessage(sessionId);
1331
+ lastRaw = raw;
894
1332
  const candidate = extractJsonCandidate(raw);
895
1333
  let value;
896
1334
  try {
@@ -898,24 +1336,34 @@ ${inner(b2)}` };
898
1336
  } catch {
899
1337
  lastErr = "not valid JSON";
900
1338
  if (attempt < maxRetries) {
901
- await ctx.agents.sendPromptAndWait(
1339
+ await sendPromptAndAwaitOutcome(
1340
+ ctx,
1341
+ step,
902
1342
  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.`
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
904
1344
  );
905
1345
  }
906
1346
  continue;
907
1347
  }
908
1348
  const res = step.outputSchema.safeParse(value);
909
1349
  if (res.success) return { sessionId, output: res.data, ...harnessOut ? { harness: harnessOut } : {}, ...knowledgeOut ? { knowledgeApplied: knowledgeOut } : {} };
910
- lastErr = formatZodError(res.error);
1350
+ lastErr = formatSchemaError(res.error);
911
1351
  if (attempt < maxRetries) {
912
- await ctx.agents.sendPromptAndWait(
1352
+ await sendPromptAndAwaitOutcome(
1353
+ ctx,
1354
+ step,
913
1355
  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.`
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
915
1357
  );
916
1358
  }
917
1359
  }
918
- throw new Error(`step '${step.id}': output_invalid \u2014 final message never matched outputSchema (${lastErr})`);
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
+ );
919
1367
  }
920
1368
  function tryParseJson(text) {
921
1369
  const trimmed = text.trim();
@@ -1020,10 +1468,25 @@ Please address the findings above, then reply when done.`
1020
1468
  }
1021
1469
  return last;
1022
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
+ }
1023
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) {
1024
1487
  const { state, signal } = ctx;
1025
- const b = view(state, item, index);
1026
- if (step.kind !== "agent") {
1488
+ const b = view(ctx, item, index);
1489
+ if (step.kind !== "agent" && step.kind !== "tool" && step.kind !== "artifact") {
1027
1490
  ctx.onStepStart?.(step.id);
1028
1491
  }
1029
1492
  switch (step.kind) {
@@ -1038,9 +1501,13 @@ async function execStep(step, ctx, item, index) {
1038
1501
  secrets: step.secrets,
1039
1502
  signal
1040
1503
  });
1041
- if (!isCacheEnabled(ctx, step)) return runIt();
1504
+ if (!isCacheEnabled(ctx, step)) {
1505
+ ctx.onStepStart?.(step.id);
1506
+ return runIt();
1507
+ }
1042
1508
  const c = await readStepCache(ctx, step, input);
1043
- if (c.hit) return c.output;
1509
+ if (c.hit) return cacheHit(ctx, step, c.output);
1510
+ ctx.onStepStart?.(step.id);
1044
1511
  const out = await runIt();
1045
1512
  await ctx.cache.set(c.key, { output: out, resolvedInputHash: c.hash });
1046
1513
  return out;
@@ -1052,81 +1519,110 @@ async function execStep(step, ctx, item, index) {
1052
1519
  const parallelism = Math.max(1, step.parallelism ?? 1);
1053
1520
  const tolerant = step.onError === "collect";
1054
1521
  const results = new Array(arr.length);
1055
- for (let i = 0; i < arr.length; i += parallelism) {
1056
- const chunk = arr.slice(i, i + parallelism);
1057
- if (!tolerant) {
1058
- const outs = await Promise.all(
1059
- chunk.map((el, j) => {
1060
- const idx = i + j;
1061
- const inner = step.body(el, idx, view(state, el, idx));
1062
- return execStep(inner, ctx, el, idx);
1063
- })
1064
- );
1065
- for (let j = 0; j < outs.length; j++) results[i + j] = outs[j];
1066
- continue;
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);
1067
1544
  }
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
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);
1087
1558
  }
1088
1559
  case "pipeline": {
1089
1560
  const items = [...step.over(b)];
1090
1561
  const tolerant = step.onError === "collect";
1091
- if (items.length === 0) return tolerant ? { results: [], succeeded: 0, failed: 0 } : [];
1562
+ if (items.length === 0) return tolerant ? tolerantResult([], void 0) : [];
1092
1563
  const cap = Math.max(1, step.concurrency ?? items.length);
1093
1564
  const results = new Array(items.length);
1094
1565
  let next = 0;
1566
+ const breaker = spawnBreaker(step.maxConsecutiveSpawnFailures ?? DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES);
1095
1567
  const runItem = async (idx) => {
1096
1568
  let prev = void 0;
1569
+ let stageId = step.id;
1570
+ const wrapped = withReleaseScope(withIndexedHooks(ctx, idx));
1097
1571
  try {
1098
1572
  for (const stage of step.stages) {
1099
- prev = await execStep(stage(items[idx], idx, prev, view(state, items[idx], idx)), ctx, items[idx], idx);
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);
1100
1577
  }
1101
1578
  results[idx] = tolerant ? { status: "fulfilled", index: idx, value: prev } : prev;
1579
+ breaker.settle();
1102
1580
  } catch (err) {
1103
1581
  if (!tolerant) throw err;
1104
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);
1105
1587
  }
1106
1588
  };
1107
1589
  const worker = async () => {
1108
- while (next < items.length) {
1590
+ while ((!tolerant || breaker.open === void 0) && next < items.length) {
1109
1591
  const idx = next++;
1110
1592
  await runItem(idx);
1111
1593
  }
1112
1594
  };
1113
1595
  await Promise.all(Array.from({ length: Math.min(cap, items.length) }, () => worker()));
1114
1596
  if (!tolerant) return results;
1115
- const outcomes = results;
1116
- return {
1117
- results: outcomes,
1118
- succeeded: outcomes.filter((r) => r.status === "fulfilled").length,
1119
- failed: outcomes.filter((r) => r.status === "rejected").length
1120
- };
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);
1121
1609
  }
1122
1610
  case "branch": {
1123
- const chosen = step.cond(b) ? step.then : step.otherwise ?? [];
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
+ }
1124
1620
  return runSequence(chosen, ctx, item, index);
1125
1621
  }
1126
1622
  case "loop": {
1127
1623
  let iterations = 0;
1128
1624
  let last;
1129
- while (iterations < step.maxIterations && step.while(view(state, item, index))) {
1625
+ while (iterations < step.maxIterations && step.while(view(ctx, item, index))) {
1130
1626
  last = await runSequence(step.body, ctx, item, index);
1131
1627
  iterations++;
1132
1628
  }
@@ -1172,14 +1668,20 @@ async function execStep(step, ctx, item, index) {
1172
1668
  const child = await runWorkflowInner(step.workflow, childInput, {
1173
1669
  approve: ctx.approve,
1174
1670
  resume: ctx.resume,
1671
+ onInputRequired: ctx.onInputRequired,
1175
1672
  signal,
1176
1673
  agents: ctx.agents,
1177
1674
  cwd: ctx.cwd,
1178
1675
  workspaceSlug: ctx.workspaceSlug,
1676
+ workspace: ctx.workspace,
1677
+ artifactsDir: ctx.artifactsDir,
1678
+ runId: ctx.runId,
1679
+ onArtifact: ctx.onArtifact,
1179
1680
  cache: ctx.cache,
1180
1681
  cacheKey: ctx.cacheKey,
1181
1682
  runGateCommand: ctx.runGateCommand,
1182
- onGateReport: ctx.onGateReport
1683
+ onGateReport: ctx.onGateReport,
1684
+ spawned: ctx.spawned
1183
1685
  });
1184
1686
  return child.output;
1185
1687
  }
@@ -1190,29 +1692,147 @@ async function execStep(step, ctx, item, index) {
1190
1692
  prompt: step.prompt(b),
1191
1693
  adapter: step.adapter ? resolveSel(step.adapter, b) : void 0,
1192
1694
  model: step.model ? resolveSel(step.model, b) : void 0,
1193
- sessionRef: step.sessionRef
1695
+ sessionRef: step.sessionRef !== void 0 ? resolveSessionRef(step.sessionRef, b) : void 0
1194
1696
  };
1195
1697
  const c = await readStepCache(ctx, step, resolved);
1196
- if (c.hit) return c.output;
1698
+ if (c.hit) return cacheHit(ctx, step, c.output);
1197
1699
  const out = await execAgentStep(step, ctx, b);
1198
1700
  await ctx.cache.set(c.key, { output: out, resolvedInputHash: c.hash });
1199
1701
  return out;
1200
1702
  }
1201
1703
  case "gate":
1202
1704
  return execGateStep(step, ctx, b);
1705
+ case "artifact":
1706
+ return execArtifactStep(step, ctx, b);
1707
+ }
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
+ }
1203
1808
  }
1809
+ if (!bodyFailed && firstError !== void 0) throw firstError;
1204
1810
  }
1205
1811
  async function runWorkflowInner(workflow, input, hooks, maxTotalCostUsd) {
1206
- const state = { input, steps: {}, costBySession: /* @__PURE__ */ new Map(), maxTotalCostUsd };
1207
- const ctx = { state, ...hooks };
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: [] } : {} };
1208
1815
  let lastId;
1209
- for (const step of workflow.steps) {
1210
- const out = await execStep(step, ctx, void 0, void 0);
1211
- state.steps[step.id] = out;
1212
- ctx.onStepComplete?.(step.id, out);
1213
- lastId = step.id;
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
+ }
1214
1834
  }
1215
- const bindings = view(state);
1835
+ const bindings = view(ctx);
1216
1836
  const output = workflow.output ? workflow.output(bindings) : lastId !== void 0 ? state.steps[lastId] : void 0;
1217
1837
  return { output, bindings };
1218
1838
  }
@@ -1220,14 +1840,21 @@ async function runWorkflow(args) {
1220
1840
  return runWorkflowInner(args.workflow, args.input, {
1221
1841
  approve: args.approve,
1222
1842
  resume: args.resume,
1843
+ onInputRequired: args.onInputRequired,
1223
1844
  signal: args.signal,
1224
1845
  agents: args.agents,
1225
1846
  cwd: args.cwd,
1226
1847
  workspaceSlug: args.workspaceSlug,
1848
+ workspace: args.workspace,
1849
+ artifactsDir: args.artifactsDir,
1850
+ runId: args.runId,
1851
+ onArtifact: args.onArtifact,
1227
1852
  cache: args.cache,
1228
1853
  cacheKey: args.cacheKey,
1229
1854
  onStepStart: args.onStepStart,
1230
1855
  onStepComplete: args.onStepComplete,
1856
+ onStepSkipped: args.onStepSkipped,
1857
+ onStepFailed: args.onStepFailed,
1231
1858
  runGateCommand: args.runGateCommand,
1232
1859
  onGateReport: args.onGateReport
1233
1860
  }, args.maxTotalCostUsd);
@@ -1238,6 +1865,6 @@ function compileWorkflowManifest(source, opts) {
1238
1865
  return compileWorkflow(handle, opts);
1239
1866
  }
1240
1867
 
1241
- export { NodeFsPort, WorkflowCompileError, WorkflowSuspendedError, buildAgentStep, compileWorkflow, compileWorkflowManifest, evalPredicate, interpolateTemplate, 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 };
1242
1869
  //# sourceMappingURL=index.mjs.map
1243
1870
  //# sourceMappingURL=index.mjs.map