@agentproto/workflow-runtime 0.13.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -0
- package/dist/index.d.ts +45 -10
- package/dist/index.mjs +165 -35
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -76,6 +76,38 @@ whenever the host wires `RunWorkflowArgs.workspace` — see **Run workspace
|
|
|
76
76
|
| `group` | Run a list of steps as one unit; output is the last step's. |
|
|
77
77
|
| `subworkflow` | Run a nested workflow with its own isolated bindings. |
|
|
78
78
|
|
|
79
|
+
### Step timeouts (`tool` / `gate`)
|
|
80
|
+
|
|
81
|
+
A `tool` or `gate` step's dispatch is bounded by `timeout_ms` (`timeoutMs` on
|
|
82
|
+
the compiled `ToolStep`/`GateStep`) — default `DEFAULT_STEP_TIMEOUT_MS` (10
|
|
83
|
+
minutes) when the step declares none. On expiry the step fails with `step
|
|
84
|
+
'<id>': timed out after <n>ms`; the underlying subprocess (a `kind: cli`
|
|
85
|
+
driver's spawn, or a `gate` step's command) is killed by its WHOLE process
|
|
86
|
+
group, not just the direct child, so a subprocess that spawned its own
|
|
87
|
+
children (e.g. headless Chrome) can't leave orphans running past the step.
|
|
88
|
+
|
|
89
|
+
This exists because completion for both used to depend on the child's stdio
|
|
90
|
+
pipes closing (`close`), not the direct child's own `exit` — a grandchild
|
|
91
|
+
that inherited an fd (inherited/piped stdout or stderr) and outlived its
|
|
92
|
+
parent (e.g. reparented to pid 1) could hold that pipe open forever even
|
|
93
|
+
after the tool's actual work was done, hanging the step indefinitely with no
|
|
94
|
+
timeout to bound it. Both the CLI driver's `runSubprocess` and this
|
|
95
|
+
package's `defaultRunGateCommand` now settle on `exit` (with a short drain
|
|
96
|
+
window for any already-in-flight stdio) instead.
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
steps:
|
|
100
|
+
- id: pdf-render
|
|
101
|
+
kind: tool
|
|
102
|
+
tool: pdf.render
|
|
103
|
+
timeout_ms: 120000 # override the 10-minute default
|
|
104
|
+
- id: lint
|
|
105
|
+
kind: gate
|
|
106
|
+
command: pnpm
|
|
107
|
+
args: ["lint"]
|
|
108
|
+
timeout_ms: 60000
|
|
109
|
+
```
|
|
110
|
+
|
|
79
111
|
### WORKFLOW.md `kind: branch` — exclusive arms + join
|
|
80
112
|
|
|
81
113
|
`compileWorkflow` compiles a declarative AIP-15 `kind: branch` step (goto-style
|
package/dist/index.d.ts
CHANGED
|
@@ -51,6 +51,12 @@ interface ToolStep<TInput = unknown, TOutput = unknown, TContext extends ToolCon
|
|
|
51
51
|
/** Cache this step's output under the run's cacheKey; only its `input` is hashed
|
|
52
52
|
* (not `context`/secrets). Default false. */
|
|
53
53
|
cacheable?: boolean;
|
|
54
|
+
/** Hard wall-clock cap for this step's dispatch (driver resolve + execute),
|
|
55
|
+
* in ms. Bounds a driver that never settles its own promise (e.g. a `kind:
|
|
56
|
+
* cli` subprocess whose stdio pipe is held open by an orphaned grandchild
|
|
57
|
+
* process — F45) rather than relying on the driver to self-timeout.
|
|
58
|
+
* Default {@link DEFAULT_STEP_TIMEOUT_MS} when unset. */
|
|
59
|
+
timeoutMs?: number;
|
|
54
60
|
}
|
|
55
61
|
/** A pure in-run computation (combine / filter / shape) — no tool dispatch. */
|
|
56
62
|
interface TransformStep {
|
|
@@ -74,12 +80,14 @@ type FanOutOutcome<T = unknown> = {
|
|
|
74
80
|
/** `err.message` if the throw was an `Error`, else `String(err)`. */
|
|
75
81
|
readonly error: string;
|
|
76
82
|
} | {
|
|
77
|
-
/** Never started: the fan-out's spawn circuit breaker opened
|
|
78
|
-
* (see {@link MapStep.maxConsecutiveSpawnFailures})
|
|
83
|
+
/** Never started: either the fan-out's spawn circuit breaker opened
|
|
84
|
+
* first (see {@link MapStep.maxConsecutiveSpawnFailures}), or the run
|
|
85
|
+
* was cancelled while earlier items were still in flight. */
|
|
79
86
|
readonly status: "skipped";
|
|
80
87
|
readonly index: number;
|
|
81
88
|
readonly item: unknown;
|
|
82
|
-
/** `circuit-open: <first error of the failure streak
|
|
89
|
+
/** `circuit-open: <first error of the failure streak>`, or
|
|
90
|
+
* `run-cancelled: run cancelled`. */
|
|
83
91
|
readonly reason: string;
|
|
84
92
|
};
|
|
85
93
|
/**
|
|
@@ -102,6 +110,10 @@ interface TolerantFanOutResult<T = unknown> {
|
|
|
102
110
|
/** Default for {@link MapStep.maxConsecutiveSpawnFailures} /
|
|
103
111
|
* {@link PipelineStep.maxConsecutiveSpawnFailures}. */
|
|
104
112
|
declare const DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES = 3;
|
|
113
|
+
/** Default for {@link ToolStep.timeoutMs} / {@link GateStep.timeoutMs} when
|
|
114
|
+
* the step declares none — 10 minutes. Bounds an otherwise-unbounded `tool`
|
|
115
|
+
* or `gate` step dispatch (F45). */
|
|
116
|
+
declare const DEFAULT_STEP_TIMEOUT_MS: number;
|
|
105
117
|
/**
|
|
106
118
|
* Run a sub-step once per element of an array, optionally with bounded
|
|
107
119
|
* concurrency. The element + index are exposed to the body via `bindings.item`
|
|
@@ -492,7 +504,9 @@ interface GateStep {
|
|
|
492
504
|
/** Path (relative to `cwd`), of a JSON report file, consulted when stdout
|
|
493
505
|
* doesn't itself parse as JSON. */
|
|
494
506
|
reportPath?: string;
|
|
495
|
-
/** Hard wall-clock cap for a single command invocation, in ms.
|
|
507
|
+
/** Hard wall-clock cap for a single command invocation, in ms. Default
|
|
508
|
+
* {@link DEFAULT_STEP_TIMEOUT_MS} when unset — a gate's subprocess is
|
|
509
|
+
* never left to run unbounded (F45). */
|
|
496
510
|
timeoutMs?: number;
|
|
497
511
|
/** Re-run on a failing exit code, up to `maxAttempts` (default 1 = no
|
|
498
512
|
* retry — a single attempt, fail immediately on a non-zero exit). */
|
|
@@ -525,6 +539,12 @@ type GateCommandRunner = (spec: {
|
|
|
525
539
|
args: readonly string[];
|
|
526
540
|
cwd: string;
|
|
527
541
|
timeoutMs?: number;
|
|
542
|
+
/** The run's cancel signal — a cancelled run kills an in-flight gate
|
|
543
|
+
* command's child process the same way it kills an agent step's session.
|
|
544
|
+
* A host-injected runner should pass this through to its own subprocess
|
|
545
|
+
* spawn (e.g. `child_process.execFile`'s `signal` option) so the process
|
|
546
|
+
* is signalled, not merely orphaned. */
|
|
547
|
+
signal?: AbortSignal;
|
|
528
548
|
}) => Promise<GateCommandResult>;
|
|
529
549
|
/** One `kind: "gate"` command attempt's outcome, reported as it happens
|
|
530
550
|
* (every attempt, not just the last) — the `gate-report` lifecycle event. */
|
|
@@ -733,13 +753,16 @@ interface StepSkippedInfo {
|
|
|
733
753
|
/** `"branch-not-taken"`: the step sits in an untaken `branch` arm.
|
|
734
754
|
* `"circuit-open"`: a `map`/`pipeline` item never started because the
|
|
735
755
|
* fan-out's spawn circuit breaker opened
|
|
736
|
-
* ({@link MapStep.maxConsecutiveSpawnFailures}).
|
|
737
|
-
|
|
756
|
+
* ({@link MapStep.maxConsecutiveSpawnFailures}). `"run-cancelled"`: a
|
|
757
|
+
* `map`/`pipeline` item never started because the run was cancelled
|
|
758
|
+
* (`RunWorkflowArgs.signal` aborted) while earlier items were still in
|
|
759
|
+
* flight. */
|
|
760
|
+
reason: "branch-not-taken" | "circuit-open" | "run-cancelled";
|
|
738
761
|
/** Id of the authored step whose decision skipped the step — the `branch`
|
|
739
|
-
* step, or (circuit-open) the `map`/`pipeline` step. */
|
|
762
|
+
* step, or (circuit-open/run-cancelled) the `map`/`pipeline` step. */
|
|
740
763
|
branchId: string;
|
|
741
|
-
/** `"circuit-open"
|
|
742
|
-
*
|
|
764
|
+
/** `"circuit-open"`: the first error of the spawn-failure streak that
|
|
765
|
+
* tripped the breaker. `"run-cancelled"`: a fixed "run cancelled" message. */
|
|
743
766
|
message?: string;
|
|
744
767
|
}
|
|
745
768
|
/** What `onStepFailed` reports. */
|
|
@@ -898,6 +921,18 @@ declare class AgentSpawnError extends Error {
|
|
|
898
921
|
readonly cause: unknown;
|
|
899
922
|
constructor(stepId: string, cause: unknown);
|
|
900
923
|
}
|
|
924
|
+
/**
|
|
925
|
+
* Thrown instead of dispatching a step whose run was cancelled
|
|
926
|
+
* (`ctx.signal.aborted`) — checked BEFORE any work for the step begins (see
|
|
927
|
+
* {@link execStepBody}'s entry guard), so a cancel stops the run from
|
|
928
|
+
* starting any further step, including a sibling within the SAME stage/
|
|
929
|
+
* fan-out, not just a later one. Never journaled: it's thrown before a
|
|
930
|
+
* cacheable step's `cache.set` call, so `workflow_retry` re-executes it.
|
|
931
|
+
*/
|
|
932
|
+
declare class WorkflowCancelledError extends Error {
|
|
933
|
+
readonly stepId: string;
|
|
934
|
+
constructor(stepId: string);
|
|
935
|
+
}
|
|
901
936
|
declare function runWorkflow(args: RunWorkflowArgs): Promise<WorkflowRunResult>;
|
|
902
937
|
|
|
903
938
|
/**
|
|
@@ -1180,4 +1215,4 @@ declare class NodeFsPort implements FsPort {
|
|
|
1180
1215
|
lock(): Promise<FsLockHandle>;
|
|
1181
1216
|
}
|
|
1182
1217
|
|
|
1183
|
-
export { type AgentHarness, AgentInputRequiredError, type AgentRefResolution, type AgentSandboxRef, type AgentSessionHost, AgentSpawnError, type AgentStep, type AgentStepFields, type ApprovalDecision, type ApprovalRequest, type ApprovalStep, type ArtifactEntry, type ArtifactStep, type Bindings, type BranchStep, type CompileWorkflowOptions, DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES, type FanOutOutcome, type GateCommandResult, type GateCommandRunner, type GateReportEvent, type GateStep, type GroupStep, type HarnessKnowledgeSelector, type InputRequiredRequest, type KnowledgeAppliedRecord, type LoopStep, type MapStep, type MaterializedKnowledge, MissingArtifactError, NodeFsPort, type OutputSchemaLike, type OutputsFileContract, type ParallelStep, type PipelineStep, type ResumeRequest, type RunStep, type RunWorkflowArgs, type RuntimeWorkflow, type SchemaValidationIssue, type Selector, type StepCache, type StepCacheEntry, type StepFailedInfo, type StepHookInfo, StepOutcomeError, type StepSkippedInfo, type SubworkflowStep, type SuspendStep, type TolerantFanOutResult, type ToolStep, type TransformStep, WorkflowCompileError, type WorkflowInputValidation, type WorkflowRunResult, WorkflowSuspendedError, buildAgentStep, compileWorkflow, compileWorkflowManifest, evalPredicate, interpolateTemplate, isCompilableJsonSchema, materializeKnowledge, normalizeWorkflowInputsSchema, resolveKnowledgeSelectors, resolveRef, resolveRefPrefixed, resolveValue, runWorkflow, validateAgainstJsonSchema, validateWorkflowInput };
|
|
1218
|
+
export { type AgentHarness, AgentInputRequiredError, type AgentRefResolution, type AgentSandboxRef, type AgentSessionHost, AgentSpawnError, type AgentStep, type AgentStepFields, type ApprovalDecision, type ApprovalRequest, type ApprovalStep, type ArtifactEntry, type ArtifactStep, type Bindings, type BranchStep, type CompileWorkflowOptions, DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES, DEFAULT_STEP_TIMEOUT_MS, type FanOutOutcome, type GateCommandResult, type GateCommandRunner, type GateReportEvent, type GateStep, type GroupStep, type HarnessKnowledgeSelector, type InputRequiredRequest, type KnowledgeAppliedRecord, type LoopStep, type MapStep, type MaterializedKnowledge, MissingArtifactError, NodeFsPort, type OutputSchemaLike, type OutputsFileContract, type ParallelStep, type PipelineStep, type ResumeRequest, type RunStep, type RunWorkflowArgs, type RuntimeWorkflow, type SchemaValidationIssue, type Selector, type StepCache, type StepCacheEntry, type StepFailedInfo, type StepHookInfo, StepOutcomeError, type StepSkippedInfo, type SubworkflowStep, type SuspendStep, type TolerantFanOutResult, type ToolStep, type TransformStep, WorkflowCancelledError, WorkflowCompileError, type WorkflowInputValidation, type WorkflowRunResult, WorkflowSuspendedError, buildAgentStep, compileWorkflow, compileWorkflowManifest, evalPredicate, interpolateTemplate, isCompilableJsonSchema, materializeKnowledge, normalizeWorkflowInputsSchema, resolveKnowledgeSelectors, resolveRef, resolveRefPrefixed, resolveValue, runWorkflow, validateAgainstJsonSchema, validateWorkflowInput };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { runTool } from '@agentproto/driver';
|
|
2
2
|
import { createHash } from 'crypto';
|
|
3
|
-
import {
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
4
|
import { stat, readFile, mkdir, writeFile, appendFile, readdir, copyFile, cp } from 'fs/promises';
|
|
5
5
|
import { join, dirname, isAbsolute, basename, resolve, relative } from 'path';
|
|
6
6
|
import { z } from 'zod';
|
|
@@ -323,6 +323,15 @@ function normalizeToolId(ref) {
|
|
|
323
323
|
return slash === -1 ? s : s.slice(slash + 1);
|
|
324
324
|
}
|
|
325
325
|
var f = (step, key) => step[key];
|
|
326
|
+
function assertStepTimeoutMs(id, kind, value) {
|
|
327
|
+
if (value === void 0) return void 0;
|
|
328
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
|
|
329
|
+
throw new WorkflowCompileError(
|
|
330
|
+
`${kind} step '${id}' has an invalid 'timeout_ms' (${JSON.stringify(value)}) \u2014 must be a number >= 1`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
return value;
|
|
334
|
+
}
|
|
326
335
|
function assertLinear(steps) {
|
|
327
336
|
steps.forEach((s, i) => {
|
|
328
337
|
const next = s.next;
|
|
@@ -563,7 +572,7 @@ function compileGateStep(step, id) {
|
|
|
563
572
|
const args = f(step, "args");
|
|
564
573
|
const cwd = f(step, "cwd");
|
|
565
574
|
const reportPath = f(step, "report");
|
|
566
|
-
const timeoutMs =
|
|
575
|
+
const timeoutMs = assertStepTimeoutMs(id, "gate", step.timeout_ms);
|
|
567
576
|
const retry = f(step, "retry");
|
|
568
577
|
const onFail = f(
|
|
569
578
|
step,
|
|
@@ -601,6 +610,7 @@ function compileStep(step, ctx) {
|
|
|
601
610
|
assertKnownStepRefs(inputs, ctx.knownStepIds, `tool step '${id}' inputs`, {
|
|
602
611
|
makeError: (message) => new WorkflowCompileError(message)
|
|
603
612
|
});
|
|
613
|
+
const timeoutMs = assertStepTimeoutMs(id, "tool", step.timeout_ms);
|
|
604
614
|
return {
|
|
605
615
|
kind: "tool",
|
|
606
616
|
id,
|
|
@@ -608,7 +618,8 @@ function compileStep(step, ctx) {
|
|
|
608
618
|
candidates: opts.candidates,
|
|
609
619
|
input: (b) => resolveValue(inputs, b),
|
|
610
620
|
context: opts.contextFor ? (b) => opts.contextFor(toolId, b) : void 0,
|
|
611
|
-
...step.cacheable ? { cacheable: true } : {}
|
|
621
|
+
...step.cacheable ? { cacheable: true } : {},
|
|
622
|
+
...timeoutMs !== void 0 ? { timeoutMs } : {}
|
|
612
623
|
};
|
|
613
624
|
}
|
|
614
625
|
case "map": {
|
|
@@ -773,6 +784,7 @@ function resolveRefString(stepId, field, value, b, onUnmatched = "passthrough")
|
|
|
773
784
|
|
|
774
785
|
// src/types.ts
|
|
775
786
|
var DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES = 3;
|
|
787
|
+
var DEFAULT_STEP_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
776
788
|
var NodeFsPort = class {
|
|
777
789
|
root;
|
|
778
790
|
constructor(root) {
|
|
@@ -1014,6 +1026,14 @@ var AgentSpawnError = class extends Error {
|
|
|
1014
1026
|
stepId;
|
|
1015
1027
|
cause;
|
|
1016
1028
|
};
|
|
1029
|
+
var WorkflowCancelledError = class extends Error {
|
|
1030
|
+
constructor(stepId) {
|
|
1031
|
+
super(`step '${stepId}': run cancelled \u2014 not started`);
|
|
1032
|
+
this.stepId = stepId;
|
|
1033
|
+
this.name = "WorkflowCancelledError";
|
|
1034
|
+
}
|
|
1035
|
+
stepId;
|
|
1036
|
+
};
|
|
1017
1037
|
function withReleaseScope(ctx) {
|
|
1018
1038
|
return { ...ctx, spawned: [] };
|
|
1019
1039
|
}
|
|
@@ -1079,9 +1099,9 @@ function spawnBreaker(threshold) {
|
|
|
1079
1099
|
}
|
|
1080
1100
|
};
|
|
1081
1101
|
}
|
|
1082
|
-
function skipUnstartedItems(ctx, fanOutId, from, items, bodiesOf, reason, results) {
|
|
1102
|
+
function skipUnstartedItems(ctx, fanOutId, from, items, bodiesOf, kind, reason, results) {
|
|
1083
1103
|
for (let idx = from; idx < items.length; idx++) {
|
|
1084
|
-
results[idx] = { status: "skipped", index: idx, item: items[idx], reason:
|
|
1104
|
+
results[idx] = { status: "skipped", index: idx, item: items[idx], reason: `${kind}: ${reason}` };
|
|
1085
1105
|
const itemCtx = withIndexedHooks(ctx, idx);
|
|
1086
1106
|
if (!itemCtx.onStepSkipped) continue;
|
|
1087
1107
|
let bodies;
|
|
@@ -1091,7 +1111,7 @@ function skipUnstartedItems(ctx, fanOutId, from, items, bodiesOf, reason, result
|
|
|
1091
1111
|
continue;
|
|
1092
1112
|
}
|
|
1093
1113
|
for (const id of new Set(skippableStepIds(bodies))) {
|
|
1094
|
-
itemCtx.onStepSkipped(id, { reason:
|
|
1114
|
+
itemCtx.onStepSkipped(id, { reason: kind, branchId: fanOutId, message: reason });
|
|
1095
1115
|
}
|
|
1096
1116
|
}
|
|
1097
1117
|
}
|
|
@@ -1333,7 +1353,7 @@ ${inner(b2)}` };
|
|
|
1333
1353
|
} : void 0;
|
|
1334
1354
|
let sessionId;
|
|
1335
1355
|
if (step.adapter) {
|
|
1336
|
-
if (ctx.signal?.aborted) throw new
|
|
1356
|
+
if (ctx.signal?.aborted) throw new WorkflowCancelledError(step.id);
|
|
1337
1357
|
try {
|
|
1338
1358
|
sessionId = await ctx.agents.spawn(resolveSel(step.adapter, b), {
|
|
1339
1359
|
cwd,
|
|
@@ -1440,23 +1460,83 @@ function tryParseJson(text) {
|
|
|
1440
1460
|
return void 0;
|
|
1441
1461
|
}
|
|
1442
1462
|
}
|
|
1463
|
+
var GATE_STDIO_DRAIN_MS = 2e3;
|
|
1464
|
+
var GATE_KILL_GRACE_MS = 2e3;
|
|
1443
1465
|
function defaultRunGateCommand(spec) {
|
|
1466
|
+
const timeoutMs = spec.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
|
|
1444
1467
|
return new Promise((resolve2) => {
|
|
1445
|
-
|
|
1446
|
-
spec.
|
|
1447
|
-
[
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1468
|
+
const child = spawn(spec.command, [...spec.args], {
|
|
1469
|
+
cwd: spec.cwd,
|
|
1470
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1471
|
+
detached: process.platform !== "win32"
|
|
1472
|
+
});
|
|
1473
|
+
let stdout = "";
|
|
1474
|
+
let stderr = "";
|
|
1475
|
+
let settled = false;
|
|
1476
|
+
let timedOut = false;
|
|
1477
|
+
let stdoutEnded = !child.stdout;
|
|
1478
|
+
let stderrEnded = !child.stderr;
|
|
1479
|
+
let exitCode;
|
|
1480
|
+
let drainTimer;
|
|
1481
|
+
const killGroup = (sig) => {
|
|
1482
|
+
if (typeof child.pid !== "number") return;
|
|
1483
|
+
try {
|
|
1484
|
+
if (process.platform !== "win32") process.kill(-child.pid, sig);
|
|
1485
|
+
else child.kill(sig);
|
|
1486
|
+
} catch {
|
|
1458
1487
|
}
|
|
1459
|
-
|
|
1488
|
+
};
|
|
1489
|
+
let killTimer;
|
|
1490
|
+
const kill = () => {
|
|
1491
|
+
killGroup("SIGTERM");
|
|
1492
|
+
killTimer = setTimeout(() => killGroup("SIGKILL"), GATE_KILL_GRACE_MS).unref();
|
|
1493
|
+
};
|
|
1494
|
+
const onAbort = () => kill();
|
|
1495
|
+
if (spec.signal?.aborted) kill();
|
|
1496
|
+
else spec.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1497
|
+
const timeoutTimer = setTimeout(() => {
|
|
1498
|
+
timedOut = true;
|
|
1499
|
+
kill();
|
|
1500
|
+
}, timeoutMs).unref();
|
|
1501
|
+
const finish = (code) => {
|
|
1502
|
+
if (settled) return;
|
|
1503
|
+
settled = true;
|
|
1504
|
+
clearTimeout(killTimer);
|
|
1505
|
+
clearTimeout(timeoutTimer);
|
|
1506
|
+
clearTimeout(drainTimer);
|
|
1507
|
+
spec.signal?.removeEventListener("abort", onAbort);
|
|
1508
|
+
child.stdout?.destroy();
|
|
1509
|
+
child.stderr?.destroy();
|
|
1510
|
+
resolve2({ exitCode: code, stdout, stderr, ...timedOut ? { timedOut: true } : {} });
|
|
1511
|
+
};
|
|
1512
|
+
const maybeFinish = () => {
|
|
1513
|
+
if (exitCode !== void 0 && stdoutEnded && stderrEnded) finish(exitCode);
|
|
1514
|
+
};
|
|
1515
|
+
child.stdout?.on("data", (chunk) => {
|
|
1516
|
+
stdout += chunk.toString();
|
|
1517
|
+
});
|
|
1518
|
+
child.stdout?.on("end", () => {
|
|
1519
|
+
stdoutEnded = true;
|
|
1520
|
+
maybeFinish();
|
|
1521
|
+
});
|
|
1522
|
+
child.stderr?.on("data", (chunk) => {
|
|
1523
|
+
stderr += chunk.toString();
|
|
1524
|
+
});
|
|
1525
|
+
child.stderr?.on("end", () => {
|
|
1526
|
+
stderrEnded = true;
|
|
1527
|
+
maybeFinish();
|
|
1528
|
+
});
|
|
1529
|
+
child.on("error", () => {
|
|
1530
|
+
clearTimeout(timeoutTimer);
|
|
1531
|
+
exitCode = 1;
|
|
1532
|
+
drainTimer = setTimeout(() => finish(1), GATE_STDIO_DRAIN_MS).unref();
|
|
1533
|
+
maybeFinish();
|
|
1534
|
+
});
|
|
1535
|
+
child.on("exit", (code, signal) => {
|
|
1536
|
+
exitCode = code ?? (signal ? 1 : 0);
|
|
1537
|
+
drainTimer = setTimeout(() => finish(exitCode), GATE_STDIO_DRAIN_MS).unref();
|
|
1538
|
+
maybeFinish();
|
|
1539
|
+
});
|
|
1460
1540
|
});
|
|
1461
1541
|
}
|
|
1462
1542
|
async function resolveGateReport(cmd, cwd, reportPath) {
|
|
@@ -1485,6 +1565,7 @@ async function execGateStep(step, ctx, b) {
|
|
|
1485
1565
|
const maxAttempts = Math.max(1, step.retry?.maxAttempts ?? 1);
|
|
1486
1566
|
let last;
|
|
1487
1567
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1568
|
+
if (ctx.signal?.aborted) throw new WorkflowCancelledError(step.id);
|
|
1488
1569
|
if (attempt > 1 && step.onFail?.reprompt) {
|
|
1489
1570
|
if (!ctx.agents) {
|
|
1490
1571
|
throw new Error(`step '${step.id}': on_fail.reprompt requires a host agents implementation`);
|
|
@@ -1516,8 +1597,10 @@ Please address the findings above, then reply when done.`
|
|
|
1516
1597
|
command: step.command,
|
|
1517
1598
|
args,
|
|
1518
1599
|
cwd,
|
|
1519
|
-
timeoutMs: step.timeoutMs
|
|
1600
|
+
timeoutMs: step.timeoutMs,
|
|
1601
|
+
signal: ctx.signal
|
|
1520
1602
|
});
|
|
1603
|
+
if (ctx.signal?.aborted) throw new WorkflowCancelledError(step.id);
|
|
1521
1604
|
const report = await resolveGateReport(cmdResult, cwd, step.reportPath);
|
|
1522
1605
|
const ok = cmdResult.exitCode === 0;
|
|
1523
1606
|
last = { ok, exitCode: cmdResult.exitCode, report };
|
|
@@ -1534,6 +1617,27 @@ Please address the findings above, then reply when done.`
|
|
|
1534
1617
|
}
|
|
1535
1618
|
return last;
|
|
1536
1619
|
}
|
|
1620
|
+
async function withStepTimeout(stepId, timeoutMs, parentSignal, fn) {
|
|
1621
|
+
const controller = new AbortController();
|
|
1622
|
+
const onParentAbort = (sig) => controller.abort(sig.reason);
|
|
1623
|
+
const onParentAbortEvent = () => onParentAbort(parentSignal);
|
|
1624
|
+
if (parentSignal?.aborted) onParentAbort(parentSignal);
|
|
1625
|
+
else parentSignal?.addEventListener("abort", onParentAbortEvent, { once: true });
|
|
1626
|
+
let timedOut = false;
|
|
1627
|
+
const timer = setTimeout(() => {
|
|
1628
|
+
timedOut = true;
|
|
1629
|
+
controller.abort(new Error(`step '${stepId}': timed out after ${timeoutMs}ms`));
|
|
1630
|
+
}, timeoutMs);
|
|
1631
|
+
try {
|
|
1632
|
+
return await fn(controller.signal);
|
|
1633
|
+
} catch (err) {
|
|
1634
|
+
if (timedOut) throw new Error(`step '${stepId}': timed out after ${timeoutMs}ms`);
|
|
1635
|
+
throw err;
|
|
1636
|
+
} finally {
|
|
1637
|
+
clearTimeout(timer);
|
|
1638
|
+
parentSignal?.removeEventListener("abort", onParentAbortEvent);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1537
1641
|
function cacheHit(ctx, step, output) {
|
|
1538
1642
|
ctx.state.cachedHits.add(cachedHitKey(ctx, step.id));
|
|
1539
1643
|
ctx.onStepStart?.(step.id, { cached: true });
|
|
@@ -1551,6 +1655,7 @@ async function execStep(step, ctx, item, index) {
|
|
|
1551
1655
|
}
|
|
1552
1656
|
async function execStepBody(step, ctx, item, index) {
|
|
1553
1657
|
const { state, signal } = ctx;
|
|
1658
|
+
if (signal?.aborted) throw new WorkflowCancelledError(step.id);
|
|
1554
1659
|
const b = view(ctx, item, index);
|
|
1555
1660
|
if (step.kind !== "agent" && step.kind !== "tool" && step.kind !== "artifact") {
|
|
1556
1661
|
ctx.onStepStart?.(step.id);
|
|
@@ -1558,15 +1663,20 @@ async function execStepBody(step, ctx, item, index) {
|
|
|
1558
1663
|
switch (step.kind) {
|
|
1559
1664
|
case "tool": {
|
|
1560
1665
|
const input = step.input(b);
|
|
1561
|
-
const runIt = () =>
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1666
|
+
const runIt = () => withStepTimeout(
|
|
1667
|
+
step.id,
|
|
1668
|
+
step.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS,
|
|
1669
|
+
signal,
|
|
1670
|
+
(sig) => runTool({
|
|
1671
|
+
tool: step.tool,
|
|
1672
|
+
candidates: step.candidates,
|
|
1673
|
+
input,
|
|
1674
|
+
context: step.context ? step.context(b) : void 0,
|
|
1675
|
+
resolverContext: step.resolverContext,
|
|
1676
|
+
secrets: step.secrets,
|
|
1677
|
+
signal: sig
|
|
1678
|
+
})
|
|
1679
|
+
);
|
|
1570
1680
|
if (!isCacheEnabled(ctx, step)) {
|
|
1571
1681
|
ctx.onStepStart?.(step.id);
|
|
1572
1682
|
return runIt();
|
|
@@ -1610,15 +1720,20 @@ async function execStepBody(step, ctx, item, index) {
|
|
|
1610
1720
|
}
|
|
1611
1721
|
};
|
|
1612
1722
|
const worker = async () => {
|
|
1613
|
-
while (!failed && breaker.open === void 0 && next < arr.length) {
|
|
1723
|
+
while (!failed && breaker.open === void 0 && signal?.aborted !== true && next < arr.length) {
|
|
1614
1724
|
const idx = next++;
|
|
1615
1725
|
await runItem(idx);
|
|
1616
1726
|
}
|
|
1617
1727
|
};
|
|
1618
1728
|
await Promise.all(Array.from({ length: Math.min(parallelism, arr.length) }, () => worker()));
|
|
1729
|
+
if (signal?.aborted === true && next < arr.length) {
|
|
1730
|
+
if (!tolerant) throw new WorkflowCancelledError(step.id);
|
|
1731
|
+
skipUnstartedItems(ctx, step.id, next, arr, (el, idx) => [step.body(el, idx, view(ctx, el, idx))], "run-cancelled", "run cancelled", results);
|
|
1732
|
+
return tolerantResult(results, breaker.open);
|
|
1733
|
+
}
|
|
1619
1734
|
if (!tolerant) return results;
|
|
1620
1735
|
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);
|
|
1736
|
+
skipUnstartedItems(ctx, step.id, next, arr, (el, idx) => [step.body(el, idx, view(ctx, el, idx))], "circuit-open", breaker.open, results);
|
|
1622
1737
|
}
|
|
1623
1738
|
return tolerantResult(results, breaker.open);
|
|
1624
1739
|
}
|
|
@@ -1653,12 +1768,26 @@ async function execStepBody(step, ctx, item, index) {
|
|
|
1653
1768
|
}
|
|
1654
1769
|
};
|
|
1655
1770
|
const worker = async () => {
|
|
1656
|
-
while ((!tolerant || breaker.open === void 0) && next < items.length) {
|
|
1771
|
+
while ((!tolerant || breaker.open === void 0) && signal?.aborted !== true && next < items.length) {
|
|
1657
1772
|
const idx = next++;
|
|
1658
1773
|
await runItem(idx);
|
|
1659
1774
|
}
|
|
1660
1775
|
};
|
|
1661
1776
|
await Promise.all(Array.from({ length: Math.min(cap, items.length) }, () => worker()));
|
|
1777
|
+
if (signal?.aborted === true && next < items.length) {
|
|
1778
|
+
if (!tolerant) throw new WorkflowCancelledError(step.id);
|
|
1779
|
+
skipUnstartedItems(
|
|
1780
|
+
ctx,
|
|
1781
|
+
step.id,
|
|
1782
|
+
next,
|
|
1783
|
+
items,
|
|
1784
|
+
(el, idx) => step.stages.map((stage) => stage(el, idx, void 0, view(ctx, el, idx))),
|
|
1785
|
+
"run-cancelled",
|
|
1786
|
+
"run cancelled",
|
|
1787
|
+
results
|
|
1788
|
+
);
|
|
1789
|
+
return tolerantResult(results, breaker.open);
|
|
1790
|
+
}
|
|
1662
1791
|
if (!tolerant) return results;
|
|
1663
1792
|
if (breaker.open !== void 0) {
|
|
1664
1793
|
skipUnstartedItems(
|
|
@@ -1667,6 +1796,7 @@ async function execStepBody(step, ctx, item, index) {
|
|
|
1667
1796
|
next,
|
|
1668
1797
|
items,
|
|
1669
1798
|
(el, idx) => step.stages.map((stage) => stage(el, idx, void 0, view(ctx, el, idx))),
|
|
1799
|
+
"circuit-open",
|
|
1670
1800
|
breaker.open,
|
|
1671
1801
|
results
|
|
1672
1802
|
);
|
|
@@ -1950,6 +2080,6 @@ function compileWorkflowManifest(source, opts) {
|
|
|
1950
2080
|
return compileWorkflow(handle, opts);
|
|
1951
2081
|
}
|
|
1952
2082
|
|
|
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 };
|
|
2083
|
+
export { AgentInputRequiredError, AgentSpawnError, DEFAULT_MAX_CONSECUTIVE_SPAWN_FAILURES, DEFAULT_STEP_TIMEOUT_MS, MissingArtifactError, NodeFsPort, StepOutcomeError, WorkflowCancelledError, WorkflowCompileError, WorkflowSuspendedError, buildAgentStep, compileWorkflow, compileWorkflowManifest, evalPredicate, interpolateTemplate, isCompilableJsonSchema, materializeKnowledge, normalizeWorkflowInputsSchema, resolveKnowledgeSelectors, resolveRef, resolveRefPrefixed, resolveValue, runWorkflow, validateAgainstJsonSchema, validateWorkflowInput };
|
|
1954
2084
|
//# sourceMappingURL=index.mjs.map
|
|
1955
2085
|
//# sourceMappingURL=index.mjs.map
|