@orkestrel/workflow 0.0.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.
@@ -0,0 +1,2805 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _orkestrel_agent = require("@orkestrel/agent");
3
+ let _orkestrel_contract = require("@orkestrel/contract");
4
+ let _orkestrel_database = require("@orkestrel/database");
5
+ let _orkestrel_emitter = require("@orkestrel/emitter");
6
+ let _orkestrel_abort = require("@orkestrel/abort");
7
+ let _orkestrel_timeout = require("@orkestrel/timeout");
8
+ let _orkestrel_queue = require("@orkestrel/queue");
9
+ //#region src/core/Scheduler.ts
10
+ /**
11
+ * The safe cross-environment cooperative-yield default — a {@link SchedulerInterface}
12
+ * built on `setTimeout` / `clearTimeout` alone, so it runs unchanged in both the
13
+ * browser and Node.
14
+ *
15
+ * @remarks
16
+ * - **Cross-environment.** Uses ONLY `setTimeout` / `clearTimeout` — universally
17
+ * available. It deliberately avoids env-specific fast paths (`setImmediate`,
18
+ * `scheduler.yield`, `requestAnimationFrame`, `node:timers/promises`,
19
+ * `MessageChannel`); those belong to the environment backends, built with the
20
+ * agent loop that consumes them.
21
+ * - **`yield` is a macrotask host-turn, not a microtask.** `yield()` waits on a
22
+ * `setTimeout(0)`, NOT `queueMicrotask`. A microtask drains before the host
23
+ * regains control, so it would not actually let pending I/O, timers, or
24
+ * rendering run — it only defers within the current task. A zero-delay timer is
25
+ * the correct cross-environment "give the host a turn".
26
+ * - **Abort-aware.** A pending `yield` / `delay` rejects with `signal.reason` when
27
+ * the signal aborts (the standard `AbortSignal` convention). An already-aborted
28
+ * signal rejects immediately without arming a timer. Either settle path clears
29
+ * the timer and removes the abort listener — no leaked timer, no leaked
30
+ * listener, and no double-settle.
31
+ * - **Priority is accepted but uniform.** `options.priority` is part of the
32
+ * contract, but a `setTimeout`-based default cannot act on urgency, so it treats
33
+ * every priority the same. Environment backends honour it.
34
+ * - **Event-free.** A pure functional primitive — no Emitter, no events.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const scheduler = new Scheduler()
39
+ * while (!signal.aborted) {
40
+ * doSomeWork()
41
+ * await scheduler.yield({ signal }) // let the host run between work units
42
+ * }
43
+ * ```
44
+ */
45
+ var Scheduler = class {
46
+ /**
47
+ * Yield control back to the host so other tasks (I/O, timers, rendering) can
48
+ * run, then resume — a macrotask turn via `setTimeout(0)` (NOT a microtask,
49
+ * which would resume before the host regains control).
50
+ */
51
+ yield(options) {
52
+ return this.#sleep(0, options?.signal);
53
+ }
54
+ /**
55
+ * Resume after at least `ms` milliseconds; abort rejects with `signal.reason`.
56
+ *
57
+ * @remarks
58
+ * `ms` should be a non-negative finite number. The primitive stays minimal and
59
+ * does no validation: it passes `ms` straight to the host `setTimeout`, which
60
+ * clamps a negative value or `NaN` to ~0 — so an out-of-domain `ms` resolves on
61
+ * the next host turn rather than throwing.
62
+ */
63
+ delay(ms, options) {
64
+ return this.#sleep(ms, options?.signal);
65
+ }
66
+ #sleep(ms, signal) {
67
+ if (signal?.aborted === true) return Promise.reject(signal.reason);
68
+ return new Promise((resolve, reject) => {
69
+ const onAbort = () => {
70
+ clearTimeout(handle);
71
+ reject(signal?.reason);
72
+ };
73
+ const handle = setTimeout(() => {
74
+ signal?.removeEventListener("abort", onAbort);
75
+ resolve();
76
+ }, ms);
77
+ signal?.addEventListener("abort", onAbort, { once: true });
78
+ });
79
+ }
80
+ };
81
+ //#endregion
82
+ //#region src/core/constants.ts
83
+ /** The default {@link import('./types.js').WorkflowDefinition.bail} — graceful (continue on a leaf failure). */
84
+ var DEFAULT_BAIL = false;
85
+ /**
86
+ * The three task-form mechanisms (the {@link TaskVia} discriminant), frozen.
87
+ *
88
+ * @remarks
89
+ * The runtime source of truth for the `via` axis — drive the contract's literal
90
+ * shape and any guard from this array rather than repeating the literals.
91
+ */
92
+ var TASK_VIAS = Object.freeze([
93
+ "function",
94
+ "tool",
95
+ "agent"
96
+ ]);
97
+ /**
98
+ * Every {@link TaskStatus} value, frozen — the lifecycle vocabulary of a task.
99
+ *
100
+ * @remarks
101
+ * Ordered pending → running → terminal (`completed` / `failed` / `skipped` /
102
+ * `stopped`). The source of truth for the union; compose guards / shapes from it.
103
+ */
104
+ var TASK_STATUSES = Object.freeze([
105
+ "pending",
106
+ "running",
107
+ "completed",
108
+ "failed",
109
+ "skipped",
110
+ "stopped"
111
+ ]);
112
+ /** Every {@link PhaseStatus} value, frozen — the lifecycle vocabulary of a phase. */
113
+ var PHASE_STATUSES = Object.freeze([
114
+ "pending",
115
+ "running",
116
+ "completed",
117
+ "failed",
118
+ "skipped",
119
+ "stopped"
120
+ ]);
121
+ /** Every {@link WorkflowStatus} value, frozen — the lifecycle vocabulary of a workflow. */
122
+ var WORKFLOW_STATUSES = Object.freeze([
123
+ "pending",
124
+ "running",
125
+ "completed",
126
+ "failed",
127
+ "skipped",
128
+ "stopped"
129
+ ]);
130
+ /**
131
+ * The {@link TaskStatus} values that are TERMINAL — a task in one of these will
132
+ * not transition further, frozen.
133
+ *
134
+ * @remarks
135
+ * The source of truth behind {@link import('./helpers.js').isTerminalStatus}.
136
+ * `pending` and `running` are the only non-terminal members.
137
+ */
138
+ var TERMINAL_TASK_STATUSES = Object.freeze([
139
+ "completed",
140
+ "failed",
141
+ "skipped",
142
+ "stopped"
143
+ ]);
144
+ /**
145
+ * The legal {@link TaskStatus} transition graph of the live W-b task state machine —
146
+ * each current status mapped to the statuses it may move to directly, frozen.
147
+ *
148
+ * @remarks
149
+ * The source of truth behind {@link import('./helpers.js').canTransitionTask} and the
150
+ * `TRANSITION` guard ({@link import('./errors.js').WorkflowError}). A `pending` task may
151
+ * `start` (→ `running`), `skip` (→ `skipped`), or `stop` (→ `stopped`); a `running` task
152
+ * may `complete` (→ `completed`), `fail` (→ `failed`), `skip` (→ `skipped`), or `stop`
153
+ * (→ `stopped`). Every terminal status maps to an empty list — a settled task never
154
+ * transitions again. So completing a non-`running` task, or starting a settled one, is
155
+ * rejected.
156
+ */
157
+ var TASK_TRANSITIONS = Object.freeze({
158
+ pending: [
159
+ "running",
160
+ "skipped",
161
+ "stopped"
162
+ ],
163
+ running: [
164
+ "completed",
165
+ "failed",
166
+ "skipped",
167
+ "stopped"
168
+ ],
169
+ completed: [],
170
+ failed: [],
171
+ skipped: [],
172
+ stopped: []
173
+ });
174
+ /**
175
+ * The default per-phase task concurrency the {@link import('./factories.js').createWorkflowRunner}
176
+ * runner applies when a {@link import('./types.js').PhaseDefinition} omits its `concurrency`
177
+ * throttle — a large cap that is effectively unbounded for any realistic phase.
178
+ *
179
+ * @remarks
180
+ * The determinism principle fixes that a phase's tasks run CONCURRENTLY; `concurrency` is
181
+ * only an optional resource throttle (max-in-flight). With none declared, the runner runs
182
+ * all of a phase's tasks at once — modelled as this large finite cap so the value flows
183
+ * straight into the substrate {@link import('./types.js').RunnerInterface}'s
184
+ * `concurrency` (which expects a positive integer) without a special unbounded branch. No
185
+ * realistic phase declares enough tasks to reach it, so it behaves as "run them all".
186
+ */
187
+ var DEFAULT_PHASE_CONCURRENCY = 1e6;
188
+ /**
189
+ * The maximum nesting depth a workflow's `agent` task may spawn into (W-c) — the
190
+ * bound the runner's depth/cycle guard enforces.
191
+ *
192
+ * @remarks
193
+ * The limit lives in ONE place. The `agent` {@link import('./types.js').TaskForm} is
194
+ * bounded by it when the {@link import('./WorkflowRunner.js').WorkflowRunner} resolves a
195
+ * subagent: an agent running at this depth can no longer author + run a nested workflow
196
+ * (that would be depth `MAX_WORKFLOW_DEPTH + 1`), so the over-deep `agent` task is
197
+ * rejected (a typed `DEPTH` `task.fail`). The chain therefore nests workflows down to
198
+ * this depth, and the `agent` task in the depth-`MAX_WORKFLOW_DEPTH` workflow fails.
199
+ */
200
+ var MAX_WORKFLOW_DEPTH = 8;
201
+ /**
202
+ * The name under which the {@link import('./WorkflowRunner.js').WorkflowRunner} BINDS the
203
+ * depth/cycle-aware workflow tool onto a dispatched `agent` task's
204
+ * `AgentContextInterface` (the future `@orkestrel/agent` package, W-c2).
205
+ *
206
+ * @remarks
207
+ * The propagation seam's well-known key: before running an `agent` task, the runner adds a
208
+ * {@link import('./factories.js').createWorkflowTool}-built tool under this name to the
209
+ * resolved agent's `context.tools`, so the subagent can author + run a NESTED workflow
210
+ * (bounded by {@link MAX_WORKFLOW_DEPTH}). A subagent that wants to fan out into a workflow
211
+ * calls this tool by this name; the bound handler runs the nested workflow at depth + 1.
212
+ */
213
+ var WORKFLOW_TOOL_NAME = "workflow";
214
+ /**
215
+ * A complete FLAT authoring example — the PRIMARY way a small model authors a workflow
216
+ * through {@link import('./factories.js').createWorkflowTool}: `{ name, steps: [{ name, via }] }`.
217
+ *
218
+ * @remarks
219
+ * Each step becomes a one-task phase, in order; a step's `name` is a REGISTERED behavior name
220
+ * (not a label) and `via` is the execution mechanism. The tool expands this
221
+ * ({@link import('./helpers.js').expandSteps}) into a valid {@link WorkflowDefinition}. It
222
+ * is embedded VERBATIM in {@link WORKFLOW_TOOL_DESCRIPTION} and guarded by a parity test
223
+ * (it must expand to a tree the STRICT contract accepts), so the doc example can never drift.
224
+ */
225
+ var WORKFLOW_TOOL_FLAT_EXAMPLE = Object.freeze({
226
+ name: "release",
227
+ steps: Object.freeze([Object.freeze({
228
+ name: "compile",
229
+ via: "function"
230
+ }), Object.freeze({
231
+ name: "publish",
232
+ via: "tool"
233
+ })])
234
+ });
235
+ /**
236
+ * A minimal NESTED authoring example — the ADVANCED escape-hatch form a model may use
237
+ * instead of the flat shape: a full {@link WorkflowDefinition}.
238
+ *
239
+ * @remarks
240
+ * The full four-level form, documented in {@link WORKFLOW_TOOL_DESCRIPTION} as the advanced
241
+ * alternative. It is embedded VERBATIM and guarded by a parity test (`createWorkflowContract().is`
242
+ * must accept it), so the doc example can never drift from a valid definition.
243
+ */
244
+ var WORKFLOW_TOOL_NESTED_EXAMPLE = Object.freeze({
245
+ id: "release",
246
+ name: "Release",
247
+ phases: Object.freeze([Object.freeze({
248
+ id: "build",
249
+ name: "Build",
250
+ tasks: Object.freeze([Object.freeze({
251
+ id: "compile",
252
+ name: "Compile",
253
+ run: Object.freeze({
254
+ via: "function",
255
+ name: "compile"
256
+ })
257
+ })])
258
+ })])
259
+ });
260
+ /**
261
+ * The DESCRIPTION {@link import('./factories.js').createWorkflowTool} advertises — a
262
+ * multi-line guide that teaches a small model how to author a complete workflow tree.
263
+ *
264
+ * @remarks
265
+ * Presents the SIMPLE flat shape (`{ name, steps: [{ name, via }] }`) as the PRIMARY way with
266
+ * one complete worked example ({@link WORKFLOW_TOOL_FLAT_EXAMPLE}), names the three `via`
267
+ * values + that a step's `name` is a REGISTERED name (not a human label), and documents the full nested
268
+ * {@link WorkflowDefinition} as the ADVANCED form with a minimal example
269
+ * ({@link WORKFLOW_TOOL_NESTED_EXAMPLE}). Both examples are interpolated VERBATIM from the
270
+ * validated constants, so a parity test pins them — the description can never drift from a
271
+ * real, contract-valid example. The `parameters` the tool advertises are the FLAT shape's
272
+ * schema; the nested form is the documented escape-hatch (the tool accepts both).
273
+ */
274
+ var WORKFLOW_TOOL_DESCRIPTION = [
275
+ "Author and run a workflow (phases run sequentially, the tasks within a phase run concurrently) in one call.",
276
+ "",
277
+ "SIMPLEST way — a flat list of steps. Each step runs one registered behavior; steps run one after another:",
278
+ " { \"name\": \"<workflow name>\", \"steps\": [ { \"name\": \"<registered name>\", \"via\": \"function|tool|agent\" }, ... ] }",
279
+ "- a step's \"name\" is a REGISTERED behavior name (a registry key), NOT a human label.",
280
+ "- \"via\" is how to run it: \"function\" (the default if omitted), \"tool\", or \"agent\".",
281
+ "- the top-level \"name\" (the workflow name) is optional. Ids are filled in for you.",
282
+ "Example:",
283
+ JSON.stringify(WORKFLOW_TOOL_FLAT_EXAMPLE),
284
+ "",
285
+ "ADVANCED — the full nested form, for multi-task phases or explicit ids. A workflow has phases; a phase has tasks; a task has a \"run\" of { \"via\", \"name\" }:",
286
+ JSON.stringify(WORKFLOW_TOOL_NESTED_EXAMPLE),
287
+ "In the nested form you may omit any \"id\"/\"name\" and they are filled in positionally; a provided one is kept."
288
+ ].join("\n");
289
+ //#endregion
290
+ //#region src/core/errors.ts
291
+ /**
292
+ * An error thrown by the workflow entity + W-c2 recursion layer.
293
+ *
294
+ * @remarks
295
+ * Carries a {@link WorkflowErrorCode} and an optional `context` bag naming the
296
+ * offending node id / status. Thrown for an illegal lifecycle transition
297
+ * (`TRANSITION`), a structurally invalid {@link import('./types.js').WorkflowSnapshot}
298
+ * passed to {@link import('./factories.js').restoreWorkflow} (`RESTORE`), an over-deep /
299
+ * cyclic nested-workflow dispatch (`DEPTH`), and a malformed
300
+ * {@link import('./factories.js').createWorkflowTool} args blob (`TOOL`). On the
301
+ * workflow-tool seam the `DEPTH` / `TOOL` throw is ISOLATED by the
302
+ * `@orkestrel/agent` package's `ToolManager` into the tool result's
303
+ * top-level `error` (AGENTS §14 — the universal tool-handler contract).
304
+ */
305
+ var WorkflowError = class extends Error {
306
+ code;
307
+ context;
308
+ constructor(code, message, context) {
309
+ super(message);
310
+ this.name = "WorkflowError";
311
+ this.code = code;
312
+ this.context = context;
313
+ }
314
+ };
315
+ /**
316
+ * Narrow an unknown caught value to a {@link WorkflowError}.
317
+ *
318
+ * @param value - The value to test (typically a `catch` binding)
319
+ * @returns `true` when `value` is a {@link WorkflowError}
320
+ *
321
+ * @example
322
+ * ```ts
323
+ * try {
324
+ * task.complete('done')
325
+ * } catch (error) {
326
+ * if (isWorkflowError(error) && error.code === 'TRANSITION') retry()
327
+ * }
328
+ * ```
329
+ */
330
+ function isWorkflowError(value) {
331
+ return value instanceof WorkflowError;
332
+ }
333
+ //#endregion
334
+ //#region src/core/helpers.ts
335
+ /**
336
+ * Narrow a {@link TaskForm} to the `function` form — a task that runs a registered
337
+ * function.
338
+ *
339
+ * @param form - The task form to test
340
+ * @returns `true` when `form.via` is `'function'`
341
+ */
342
+ function isFunctionTask(form) {
343
+ return form.via === "function";
344
+ }
345
+ /**
346
+ * Narrow a {@link TaskForm} to the `tool` form — a task that runs a registered tool.
347
+ *
348
+ * @param form - The task form to test
349
+ * @returns `true` when `form.via` is `'tool'`
350
+ */
351
+ function isToolTask(form) {
352
+ return form.via === "tool";
353
+ }
354
+ /**
355
+ * Narrow a {@link TaskForm} to the `agent` form — a task that runs a registered
356
+ * agent (a subagent).
357
+ *
358
+ * @param form - The task form to test
359
+ * @returns `true` when `form.via` is `'agent'`
360
+ */
361
+ function isAgentTask(form) {
362
+ return form.via === "agent";
363
+ }
364
+ /**
365
+ * The ancestry identifier of a workflow run — `workflow:<id>`.
366
+ *
367
+ * @remarks
368
+ * The {@link import('./WorkflowRunner.js').WorkflowRunner}'s cycle guard records one of
369
+ * these per workflow in the current nested run chain (carried on
370
+ * {@link import('./types.js').WorkflowRunOptions.ancestry}). Tagging the bare id keeps a
371
+ * workflow id and an {@link agentTag} agent name in ONE namespaced set without collision,
372
+ * so re-entering a workflow OR an agent already in the chain is a single `includes` check.
373
+ *
374
+ * @param id - The workflow definition's `id`
375
+ * @returns The namespaced ancestry tag (`workflow:<id>`)
376
+ */
377
+ function workflowTag(id) {
378
+ return `workflow:${id}`;
379
+ }
380
+ /**
381
+ * The ancestry identifier of an agent in a run chain — `agent:<name>`.
382
+ *
383
+ * @remarks
384
+ * The agent counterpart of {@link workflowTag}: the runner adds one when it dispatches an
385
+ * `agent` task, and rejects the task (a typed `DEPTH` `task.fail`) when the same tag is
386
+ * already in the ancestry (a re-entry cycle). The `agent:` namespace keeps it distinct
387
+ * from a same-string workflow id.
388
+ *
389
+ * @param name - The agent's registry name (the `agent`-form's `name`)
390
+ * @returns The namespaced ancestry tag (`agent:<name>`)
391
+ */
392
+ function agentTag(name) {
393
+ return `agent:${name}`;
394
+ }
395
+ /**
396
+ * Test whether a {@link LifecycleStatus} is TERMINAL — a node in this state will not
397
+ * transition further.
398
+ *
399
+ * @remarks
400
+ * The ONE terminal check across all three tiers (AGENTS §4.4 "one concept = one word"):
401
+ * a task, a phase, and a workflow share the same {@link LifecycleStatus} vocabulary, so a
402
+ * single predicate covers them — {@link derivePhaseStatus} and {@link deriveWorkflowStatus}
403
+ * both consult it to tell a settled node from an in-flight one. Terminal: `completed` /
404
+ * `failed` / `skipped` / `stopped`; the only non-terminal states are `pending` and
405
+ * `running`.
406
+ *
407
+ * @param status - The lifecycle status to test (a task / phase / workflow status)
408
+ * @returns `true` when the status is terminal
409
+ */
410
+ function isTerminalStatus(status) {
411
+ return status === "completed" || status === "failed" || status === "skipped" || status === "stopped";
412
+ }
413
+ /**
414
+ * Derive a phase's status from its tasks' statuses (tasks are concurrent, so this
415
+ * is an order-insensitive reduction).
416
+ *
417
+ * @remarks
418
+ * The truth table (most-severe terminal wins; `bail`-agnostic — a phase surfaces a
419
+ * task failure as `failed` so the workflow's `bail` policy can decide):
420
+ * - no tasks ⇒ `pending`.
421
+ * - any task `running`, OR a mix of started-and-unsettled tasks (some non-`pending`
422
+ * but not all terminal) ⇒ `running`.
423
+ * - every task `pending` ⇒ `pending`.
424
+ * - all terminal: any `failed` ⇒ `failed`; else any `stopped` ⇒ `stopped`; else any
425
+ * `completed` ⇒ `completed`; else (all `skipped`) ⇒ `skipped`.
426
+ *
427
+ * So an all-`skipped` phase is `skipped`, an all-`stopped` phase is `stopped`, a
428
+ * phase with completed tasks and some skips is `completed`, and a single failed
429
+ * task makes the phase `failed`.
430
+ *
431
+ * @param tasks - The phase's task statuses, in any order
432
+ * @returns The derived {@link PhaseStatus}
433
+ */
434
+ function derivePhaseStatus(tasks) {
435
+ if (tasks.length === 0) return "pending";
436
+ if (tasks.every((status) => status === "pending")) return "pending";
437
+ if (!tasks.every((status) => isTerminalStatus(status))) return "running";
438
+ if (tasks.some((status) => status === "failed")) return "failed";
439
+ if (tasks.some((status) => status === "stopped")) return "stopped";
440
+ if (tasks.some((status) => status === "completed")) return "completed";
441
+ return "skipped";
442
+ }
443
+ /**
444
+ * Derive a workflow's status from its phases' {@link PhaseDerivation}s — each phase's status
445
+ * paired with the EFFECTIVE `bail` it ran under (`phase.bail ?? workflow.bail`) — so the
446
+ * failure outcome is PER-PHASE-bail-aware (phases are sequential, but the derivation is an
447
+ * order-insensitive reduction over the settled set).
448
+ *
449
+ * @remarks
450
+ * `bail` is now a per-phase override (AGENTS §4.4), so it is carried on each
451
+ * {@link PhaseDerivation} rather than passed as one scalar. It is the ONLY axis that changes
452
+ * the failure outcome, decided per phase:
453
+ * - **A `failed` phase whose effective `bail` is `true` (halt)** propagates ⇒ the workflow is
454
+ * `failed` (the database-transaction halt) — even when the workflow default is graceful.
455
+ * - **A `failed` phase whose effective `bail` is `false` (graceful)** is DATA, not a workflow
456
+ * failure — it folds into completion like a settled phase. A graceful failed phase NEVER
457
+ * makes the workflow `failed` — even when the workflow default is strict.
458
+ *
459
+ * The rest of the table is shared:
460
+ * - no phases ⇒ `pending`.
461
+ * - any phase `running`, OR a mix of started-and-unsettled phases (some non-`pending`
462
+ * but not all terminal) ⇒ `running`.
463
+ * - every phase `pending` ⇒ `pending`.
464
+ * - all terminal (a `failed` phase counts as terminal here): any `stopped` ⇒ `stopped`; else
465
+ * any `completed` (or any graceful-bail `failed`, folded into completion) ⇒ `completed`;
466
+ * else (all `skipped`) ⇒ `skipped`.
467
+ *
468
+ * @param phases - The workflow's per-phase {@link PhaseDerivation}s (status + effective bail), in any order
469
+ * @returns The derived {@link WorkflowStatus}
470
+ */
471
+ function deriveWorkflowStatus(phases) {
472
+ if (phases.length === 0) return "pending";
473
+ if (phases.some((phase) => phase.status === "failed" && phase.bail)) return "failed";
474
+ if (phases.every((phase) => phase.status === "pending")) return "pending";
475
+ if (!phases.every((phase) => isTerminalStatus(phase.status))) return "running";
476
+ if (phases.some((phase) => phase.status === "stopped")) return "stopped";
477
+ if (phases.some((phase) => phase.status === "completed" || phase.status === "failed" && !phase.bail)) return "completed";
478
+ return "skipped";
479
+ }
480
+ /**
481
+ * Test whether the live W-b task state machine may move directly from one
482
+ * {@link TaskStatus} to another — the legal-transition guard.
483
+ *
484
+ * @remarks
485
+ * Reads the {@link import('./constants.js').TASK_TRANSITIONS} graph: `true` only when
486
+ * `to` is listed under `from`. A settled (terminal) `from` has no legal targets, so any
487
+ * transition off it is `false`. The W-b `Task` consults this before every transition and
488
+ * throws a `TRANSITION` {@link import('./errors.js').WorkflowError} when it returns `false`.
489
+ *
490
+ * @param from - The task's current status
491
+ * @param to - The status the transition would move it to
492
+ * @returns `true` when the move is legal
493
+ */
494
+ function canTransitionTask(from, to) {
495
+ return TASK_TRANSITIONS[from].includes(to);
496
+ }
497
+ /**
498
+ * Build a {@link WorkflowContext} — the identity every level inherits — from a node's
499
+ * `id` / `name` / optional `description`.
500
+ *
501
+ * @remarks
502
+ * The root of the context chain a live {@link import('./Workflow.js').Workflow} exposes;
503
+ * {@link buildPhaseContext} / {@link buildTaskContext} extend it down the tree. Accepts a
504
+ * structural node (a definition or a snapshot node — both carry the three identity fields).
505
+ *
506
+ * @param node - The node's identity (`id` / `name` / optional `description`)
507
+ * @returns The {@link WorkflowContext}
508
+ */
509
+ function buildWorkflowContext(node) {
510
+ return {
511
+ id: node.id,
512
+ name: node.name,
513
+ ...node.description === void 0 ? {} : { description: node.description }
514
+ };
515
+ }
516
+ /**
517
+ * Build a {@link PhaseContext} — a phase's own identity plus a back-reference to its
518
+ * workflow — from the parent {@link WorkflowContext} and the phase node's identity.
519
+ *
520
+ * @param workflow - The parent workflow context (the lineage pointer UP the tree)
521
+ * @param node - The phase's identity (`id` / `name` / optional `description`)
522
+ * @returns The {@link PhaseContext}
523
+ */
524
+ function buildPhaseContext(workflow, node) {
525
+ return {
526
+ ...buildWorkflowContext(node),
527
+ workflow
528
+ };
529
+ }
530
+ /**
531
+ * Build a {@link TaskContext} — a task's own identity plus a back-reference to its phase
532
+ * (and, transitively, its workflow) — from the parent {@link PhaseContext} and the task
533
+ * node's identity.
534
+ *
535
+ * @param phase - The parent phase context (carrying the full lineage UP the tree)
536
+ * @param node - The task's identity (`id` / `name` / optional `description`)
537
+ * @returns The {@link TaskContext}
538
+ */
539
+ function buildTaskContext(phase, node) {
540
+ return {
541
+ ...buildWorkflowContext(node),
542
+ phase
543
+ };
544
+ }
545
+ /**
546
+ * Narrow an `unknown` to a {@link WorkflowSnapshot} — the AGENTS §14 boundary guard for an
547
+ * UNTRUSTED snapshot read (a storage row a {@link import('./stores/DatabaseWorkflowStore.js').DatabaseWorkflowStore}
548
+ * reads back from its opaque JSON column, a snapshot loaded from disk).
549
+ *
550
+ * @remarks
551
+ * A total guard (it NEVER throws — adversarial input returns `false`, AGENTS §14). It checks the
552
+ * snapshot's SHAPE — `id` / `name` / `status` strings, a `boolean` `bail`, an array of `phases`,
553
+ * `created` / `updated` numbers — enough to safely impose the {@link WorkflowSnapshot} type at a
554
+ * storage boundary WITHOUT a cast. It is complementary to
555
+ * {@link import('./factories.js').assertSnapshot}, which validates the DEEPER invariant (every
556
+ * node's status / override drawn from the lifecycle vocabulary) and THROWS a `RESTORE`
557
+ * {@link import('./errors.js').WorkflowError} — the deep gate a {@link import('./factories.js').restoreWorkflow}
558
+ * applies. A boundary read narrows shape with this guard; a restore validates vocabulary with `assertSnapshot`.
559
+ *
560
+ * @param value - The value to test (an opaque storage read)
561
+ * @returns `true` when `value` has the structural shape of a {@link WorkflowSnapshot}
562
+ */
563
+ function isWorkflowSnapshot(value) {
564
+ return (0, _orkestrel_contract.isRecord)(value) && (0, _orkestrel_contract.isString)(value.id) && (0, _orkestrel_contract.isString)(value.name) && (0, _orkestrel_contract.isString)(value.status) && (0, _orkestrel_contract.isBoolean)(value.bail) && (0, _orkestrel_contract.isArray)(value.phases) && (0, _orkestrel_contract.isNumber)(value.created) && (0, _orkestrel_contract.isNumber)(value.updated);
565
+ }
566
+ /**
567
+ * Convert a {@link WorkflowDefinition} into an INITIAL {@link WorkflowSnapshot} — every
568
+ * node `pending`, no results, empty metadata — so the live W-b tree has ONE construction
569
+ * path (snapshot-driven) for both a fresh build and a restore.
570
+ *
571
+ * @remarks
572
+ * The structural fields (`id` / `name` / `description` + the ordered phases / tasks)
573
+ * carry over verbatim; the W-b live tree is the DECLARATIVE state machine, so the
574
+ * execution-only definition fields (per-phase `run` / `concurrency`, per-task `retries` /
575
+ * `timeout`) are intentionally dropped (W-c reads them from the definition when it drives
576
+ * transitions). The `bail` policy carries over — at the workflow tier AND, per phase, the
577
+ * EFFECTIVE policy (`phase.bail ?? workflowBail`) on each {@link PhaseSnapshot} — so the seeded
578
+ * snapshot is self-contained; a fresh seed has no `override`. `created` / `updated` are stamped now.
579
+ * {@link import('./factories.js').createWorkflow} builds from this.
580
+ *
581
+ * The optional `bail` override is the EFFECTIVE workflow policy the tree will run under
582
+ * (`createWorkflow` / the runner resolve `options.bail ?? definition.bail ?? DEFAULT_BAIL` and
583
+ * pass it here), so an `options.bail` override reaches BOTH the workflow tier AND the
584
+ * inheritance default of every phase that declares no `bail` of its own — otherwise the
585
+ * per-phase seeds would silently ignore the override. Omitted ⇒ the definition's own `bail`
586
+ * (defaulting to the graceful {@link import('./constants.js').DEFAULT_BAIL}).
587
+ *
588
+ * @param definition - The workflow definition to seed from
589
+ * @param bail - The EFFECTIVE workflow bail to seed both tiers with (defaults to the definition's)
590
+ * @returns An initial, all-`pending` {@link WorkflowSnapshot}
591
+ */
592
+ function definitionToSnapshot(definition, bail) {
593
+ const now = Date.now();
594
+ const workflowBail = bail ?? definition.bail ?? false;
595
+ return {
596
+ id: definition.id,
597
+ name: definition.name,
598
+ ...definition.description === void 0 ? {} : { description: definition.description },
599
+ status: "pending",
600
+ bail: workflowBail,
601
+ phases: definition.phases.map((phase) => phaseDefinitionToSnapshot(phase, workflowBail)),
602
+ created: now,
603
+ updated: now
604
+ };
605
+ }
606
+ /**
607
+ * Convert one {@link import('./types.js').PhaseDefinition} into an initial, all-`pending`
608
+ * {@link PhaseSnapshot} — the per-phase step of {@link definitionToSnapshot}.
609
+ *
610
+ * @remarks
611
+ * The snapshot persists the EFFECTIVE failure policy this phase runs under: the phase's own
612
+ * `bail` when it declares one, else the `workflowBail` it inherits — so a restore reinstates
613
+ * the same per-phase policy without a silent default (`effectiveBail = phase.bail ?? workflowBail`).
614
+ *
615
+ * @param phase - The phase definition to seed from
616
+ * @param workflowBail - The workflow-level `bail` default the phase inherits when it declares none
617
+ * @returns An initial {@link PhaseSnapshot}
618
+ */
619
+ function phaseDefinitionToSnapshot(phase, workflowBail) {
620
+ return {
621
+ id: phase.id,
622
+ name: phase.name,
623
+ ...phase.description === void 0 ? {} : { description: phase.description },
624
+ status: "pending",
625
+ bail: phase.bail ?? workflowBail,
626
+ tasks: phase.tasks.map((task) => taskDefinitionToSnapshot(task))
627
+ };
628
+ }
629
+ /**
630
+ * Convert one {@link import('./types.js').TaskDefinition} into an initial, `pending`
631
+ * {@link TaskSnapshot} — the per-task leaf step of {@link definitionToSnapshot} (no
632
+ * result yet, empty metadata).
633
+ *
634
+ * @param task - The task definition to seed from
635
+ * @returns An initial {@link TaskSnapshot}
636
+ */
637
+ function taskDefinitionToSnapshot(task) {
638
+ return {
639
+ id: task.id,
640
+ name: task.name,
641
+ ...task.description === void 0 ? {} : { description: task.description },
642
+ status: "pending",
643
+ metadata: {}
644
+ };
645
+ }
646
+ /**
647
+ * Flatten a nested list of per-phase {@link TaskResult} lists into one positional list
648
+ * — the workflow tier of the result tree, built from each phase's `results()`.
649
+ *
650
+ * @remarks
651
+ * Pure and order-preserving: phases in order, each phase's task results in order. The
652
+ * W-b `Workflow.results()` calls this over its phases' `results()`; a phase's own
653
+ * `results()` is the per-phase list this consumes.
654
+ *
655
+ * @param phases - The per-phase result lists, in phase order
656
+ * @returns One flattened {@link TaskResult} list, in positional order
657
+ */
658
+ function collectResults(phases) {
659
+ return phases.flat();
660
+ }
661
+ /**
662
+ * Summarize a terminal {@link WorkflowResult} into the PLAIN value a
663
+ * {@link import('./factories.js').createWorkflowTool} handler returns on success.
664
+ *
665
+ * @remarks
666
+ * This is the run summary the handler returns DIRECTLY — NOT a `ToolResult` (the future `@orkestrel/agent` package).
667
+ * The handler conforms to the universal tool-handler contract (AGENTS §14): it returns the plain value
668
+ * (and throws on failure), so the `@orkestrel/agent` package's `ToolManager` performs
669
+ * the ONE canonical wrap (`{ id, name, value }`) and the model reads exactly this summary — once,
670
+ * identically — over BOTH the agent loop and MCP. The summary is LEAN: the workflow's terminal `status`
671
+ * and the COUNT of settled task results — enough for a caller / model to react without serializing the
672
+ * whole live tree. (It carries no synthetic `id` / `name`: a tool handler has no call id; the manager
673
+ * supplies the canonical envelope's identity.)
674
+ *
675
+ * @param result - The terminal {@link WorkflowResult} the run produced
676
+ * @returns The plain success summary — `{ status, count }`
677
+ */
678
+ function workflowToolSummary(result) {
679
+ return {
680
+ status: result.status,
681
+ count: result.results.length
682
+ };
683
+ }
684
+ /**
685
+ * Complete a {@link WorkflowDraft} into a strict {@link WorkflowDefinition} — synthesize
686
+ * any MISSING `id` deterministically + positionally, and default any MISSING `name` to
687
+ * its (now-resolved) `id`.
688
+ *
689
+ * @remarks
690
+ * The positional id scheme is stable and human-legible: the workflow is `wf`, phase `i`
691
+ * is `phase-<i>`, and task `j` of that phase is `<phaseId>-task-<j>` (so a provided phase
692
+ * id flows into its tasks' synthesized ids). A PROVIDED `id` / `name` at any level is kept
693
+ * VERBATIM — synthesis touches only the omitted ones. A missing `name` defaults to the
694
+ * resolved `id` (never the other way round), so the result always has both. `run`,
695
+ * `description`, the per-phase `concurrency` / `bail`, the per-task `retries` / `timeout`, and
696
+ * the workflow `bail` carry over unchanged. The result is a complete
697
+ * {@link WorkflowDefinition}; the caller still validates it against the STRICT contract.
698
+ *
699
+ * @param draft - The draft workflow (id/name optional at all three levels)
700
+ * @returns A complete {@link WorkflowDefinition} with every id/name filled
701
+ */
702
+ function completeDraft(draft) {
703
+ const id = draft.id ?? "wf";
704
+ return {
705
+ id,
706
+ name: draft.name ?? id,
707
+ ...draft.description === void 0 ? {} : { description: draft.description },
708
+ phases: draft.phases.map((phase, index) => completePhaseDraft(phase, index)),
709
+ ...draft.bail === void 0 ? {} : { bail: draft.bail }
710
+ };
711
+ }
712
+ /**
713
+ * Complete one {@link PhaseDraft} into a strict {@link PhaseDefinition} — the per-phase
714
+ * step of {@link completeDraft} (phase `index` → `phase-<index>` when its id is omitted).
715
+ *
716
+ * @param phase - The draft phase
717
+ * @param index - The phase's positional index in the workflow
718
+ * @returns A complete {@link PhaseDefinition}
719
+ */
720
+ function completePhaseDraft(phase, index) {
721
+ const id = phase.id ?? `phase-${index}`;
722
+ return {
723
+ id,
724
+ name: phase.name ?? id,
725
+ ...phase.description === void 0 ? {} : { description: phase.description },
726
+ tasks: phase.tasks.map((task, taskIndex) => completeTaskDraft(task, id, taskIndex)),
727
+ ...phase.concurrency === void 0 ? {} : { concurrency: phase.concurrency },
728
+ ...phase.bail === void 0 ? {} : { bail: phase.bail }
729
+ };
730
+ }
731
+ /**
732
+ * Complete one {@link TaskDraft} into a strict {@link TaskDefinition} — the per-task leaf
733
+ * step of {@link completeDraft} (task `index` of phase `<phaseId>` → `<phaseId>-task-<index>`
734
+ * when its id is omitted).
735
+ *
736
+ * @param task - The draft task
737
+ * @param phaseId - The (resolved) parent phase id, so the synthesized task id nests under it
738
+ * @param index - The task's positional index within its phase
739
+ * @returns A complete {@link TaskDefinition}
740
+ */
741
+ function completeTaskDraft(task, phaseId, index) {
742
+ const id = task.id ?? `${phaseId}-task-${index}`;
743
+ return {
744
+ id,
745
+ name: task.name ?? id,
746
+ ...task.description === void 0 ? {} : { description: task.description },
747
+ run: task.run,
748
+ ...task.retries === void 0 ? {} : { retries: task.retries },
749
+ ...task.timeout === void 0 ? {} : { timeout: task.timeout }
750
+ };
751
+ }
752
+ /**
753
+ * Expand a flat {@link WorkflowSteps} blob into a strict {@link WorkflowDefinition} — each
754
+ * step becomes a one-task phase, IN ORDER.
755
+ *
756
+ * @remarks
757
+ * The expansion of the tool's ADVERTISED surface (AGENTS §21 — the simplest form a small
758
+ * model can author). Each {@link WorkflowStep} maps to a phase holding exactly one task:
759
+ * the step's `name` becomes the task's `run.name`, and its `via` becomes the task's `run.via`
760
+ * (defaulting to `'function'` when omitted). Ids/names are auto-filled positionally — it
761
+ * builds an ids-omitted {@link WorkflowDraft} and delegates to {@link completeDraft}, so the
762
+ * two lenient surfaces share ONE synthesis path (step `i` → phase `phase-<i>`, its task
763
+ * `phase-<i>-task-0`). The optional `name` becomes the workflow's `name`. The result is a
764
+ * complete definition the caller validates against the STRICT contract before running.
765
+ *
766
+ * @param flat - The flat steps blob (`{ name?, steps: [{ name, via? }] }`)
767
+ * @returns A complete {@link WorkflowDefinition} (one one-task phase per step)
768
+ */
769
+ function expandSteps(flat) {
770
+ return completeDraft({
771
+ ...flat.name === void 0 ? {} : { name: flat.name },
772
+ phases: flat.steps.map((step) => ({ tasks: [{ run: stepToForm(step) }] }))
773
+ });
774
+ }
775
+ /**
776
+ * Convert one flat {@link WorkflowStep} into a {@link TaskForm} — `name` → the form's `name`,
777
+ * `via` → the form's discriminant (defaulting to `'function'`).
778
+ *
779
+ * @param step - The flat step
780
+ * @returns The {@link TaskForm} the step's task runs
781
+ */
782
+ function stepToForm(step) {
783
+ return {
784
+ via: step.via ?? "function",
785
+ name: step.name
786
+ };
787
+ }
788
+ /**
789
+ * Create a {@link DeferredInterface} — a promise whose settlement is driven
790
+ * externally, so a caller can resolve/reject it from outside the executor.
791
+ *
792
+ * @typeParam T - The value the deferred promise resolves
793
+ * @returns A deferred `promise` plus its `resolve` / `reject`
794
+ */
795
+ function createDeferred() {
796
+ let resolve = () => {};
797
+ let reject = () => {};
798
+ return {
799
+ promise: new Promise((res, rej) => {
800
+ resolve = res;
801
+ reject = rej;
802
+ }),
803
+ resolve,
804
+ reject
805
+ };
806
+ }
807
+ //#endregion
808
+ //#region src/core/shapers.ts
809
+ /**
810
+ * The shape of a {@link import('./types.js').TaskForm} — a descriptive tagged union
811
+ * over the three execution mechanisms, discriminated by the `via` literal (never a
812
+ * bare `kind`; AGENTS §4.4). Each variant pairs the `via` discriminant with a `name`
813
+ * (the registry key for the behavior).
814
+ *
815
+ * @remarks
816
+ * The union and each `via` literal + `name` carry a `description` so the emitted JSON
817
+ * Schema spells out what the discriminant means and that `name` is a REGISTERED key
818
+ * (not a human label) — the field-level guidance a small model needs to fill `run`.
819
+ */
820
+ var taskFormShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.objectShape)({
821
+ via: (0, _orkestrel_contract.literalShape)(["function"], { description: "Run a registered workflow FUNCTION by name." }),
822
+ name: (0, _orkestrel_contract.stringShape)({
823
+ min: 1,
824
+ description: "The registered function name to invoke (a registry key, not a label)."
825
+ })
826
+ }), (0, _orkestrel_contract.objectShape)({
827
+ via: (0, _orkestrel_contract.literalShape)(["tool"], { description: "Run a registered TOOL by name." }),
828
+ name: (0, _orkestrel_contract.stringShape)({
829
+ min: 1,
830
+ description: "The registered tool name to invoke (a registry key, not a label)."
831
+ })
832
+ }), (0, _orkestrel_contract.objectShape)({
833
+ via: (0, _orkestrel_contract.literalShape)(["agent"], { description: "Run a registered AGENT (a subagent) by name." }),
834
+ name: (0, _orkestrel_contract.stringShape)({
835
+ min: 1,
836
+ description: "The registered agent name to invoke (a registry key, not a label)."
837
+ })
838
+ }));
839
+ /**
840
+ * The shape of a {@link import('./types.js').TaskDefinition} — identity plus the
841
+ * behavior reference ({@link taskFormShape}). `description` is optional prose.
842
+ */
843
+ var taskShape = (0, _orkestrel_contract.objectShape)({
844
+ id: (0, _orkestrel_contract.stringShape)({
845
+ min: 1,
846
+ description: "Unique task id within its phase."
847
+ }),
848
+ name: (0, _orkestrel_contract.stringShape)({
849
+ min: 1,
850
+ description: "Human-readable task name."
851
+ }),
852
+ description: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "Optional task description." })),
853
+ run: taskFormShape,
854
+ retries: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
855
+ min: 0,
856
+ description: "Extra attempts after the first on failure; overrides the phase default. Omitted means none."
857
+ })),
858
+ timeout: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
859
+ min: 0,
860
+ description: "Per-attempt deadline in milliseconds; overrides the phase default. Omitted means no deadline."
861
+ }))
862
+ });
863
+ /**
864
+ * The shape of a {@link import('./types.js').PhaseDefinition} — identity, its ordered
865
+ * {@link taskShape} tasks, and an optional positive-integer `concurrency` throttle
866
+ * (max tasks in flight; omitted ⇒ unbounded).
867
+ */
868
+ var phaseShape = (0, _orkestrel_contract.objectShape)({
869
+ id: (0, _orkestrel_contract.stringShape)({
870
+ min: 1,
871
+ description: "Unique phase id within the workflow."
872
+ }),
873
+ name: (0, _orkestrel_contract.stringShape)({
874
+ min: 1,
875
+ description: "Human-readable phase name."
876
+ }),
877
+ description: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "Optional phase description." })),
878
+ tasks: (0, _orkestrel_contract.arrayShape)(taskShape, { description: "The phase tasks; they run CONCURRENTLY." }),
879
+ concurrency: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
880
+ min: 1,
881
+ description: "Max tasks in flight at once (a resource throttle); omitted means unbounded."
882
+ })),
883
+ bail: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.literalShape)([true, false], { description: "Per-phase failure-policy override; omitted inherits the workflow bail." }))
884
+ });
885
+ /**
886
+ * The shape of a {@link import('./types.js').WorkflowDefinition} — the contract root:
887
+ * identity, its ordered {@link phaseShape} phases, and the optional `bail` boolean
888
+ * failure policy (the literal pair `true`/`false`, the runtime mirror of the boolean
889
+ * toggle; omitted ⇒ the graceful default).
890
+ */
891
+ var workflowShape = (0, _orkestrel_contract.objectShape)({
892
+ id: (0, _orkestrel_contract.stringShape)({
893
+ min: 1,
894
+ description: "Unique workflow id."
895
+ }),
896
+ name: (0, _orkestrel_contract.stringShape)({
897
+ min: 1,
898
+ description: "Human-readable workflow name."
899
+ }),
900
+ description: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "Optional workflow description." })),
901
+ phases: (0, _orkestrel_contract.arrayShape)(phaseShape, { description: "The workflow phases; they run SEQUENTIALLY, in order." }),
902
+ bail: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.literalShape)([true, false], { description: "Failure policy: false (default) continues gracefully, true halts on the first failure." }))
903
+ });
904
+ /**
905
+ * The shape of a TASK in a draft workflow — identical to {@link taskShape} EXCEPT `id`
906
+ * and `name` are OPTIONAL (the tool synthesizes any missing one positionally).
907
+ *
908
+ * @remarks
909
+ * A PROVIDED `id` / `name` still carries `minLength: 1`, so an explicitly-empty `id: ''`
910
+ * is INVALID (rejected by the draft contract), never auto-filled — keeping "garbage"
911
+ * distinct from "omitted". `run` stays required.
912
+ */
913
+ var taskDraftShape = (0, _orkestrel_contract.objectShape)({
914
+ id: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
915
+ min: 1,
916
+ description: "Task id; auto-filled when omitted."
917
+ })),
918
+ name: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
919
+ min: 1,
920
+ description: "Task name; defaults to the id when omitted."
921
+ })),
922
+ description: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "Optional task description." })),
923
+ run: taskFormShape,
924
+ retries: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
925
+ min: 0,
926
+ description: "Extra attempts after the first on failure; overrides the phase default. Omitted means none."
927
+ })),
928
+ timeout: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
929
+ min: 0,
930
+ description: "Per-attempt deadline in milliseconds; overrides the phase default. Omitted means no deadline."
931
+ }))
932
+ });
933
+ /**
934
+ * The shape of a PHASE in a draft workflow — identical to {@link phaseShape} EXCEPT
935
+ * `id` and `name` are OPTIONAL, and its tasks are {@link taskDraftShape}s.
936
+ */
937
+ var phaseDraftShape = (0, _orkestrel_contract.objectShape)({
938
+ id: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
939
+ min: 1,
940
+ description: "Phase id; auto-filled when omitted."
941
+ })),
942
+ name: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
943
+ min: 1,
944
+ description: "Phase name; defaults to the id when omitted."
945
+ })),
946
+ description: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "Optional phase description." })),
947
+ tasks: (0, _orkestrel_contract.arrayShape)(taskDraftShape, { description: "The phase tasks; they run CONCURRENTLY." }),
948
+ concurrency: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
949
+ min: 1,
950
+ description: "Max tasks in flight at once (a resource throttle); omitted means unbounded."
951
+ })),
952
+ bail: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.literalShape)([true, false], { description: "Per-phase failure-policy override; omitted inherits the workflow bail." }))
953
+ });
954
+ /**
955
+ * The shape of a DRAFT workflow — identical to {@link workflowShape} EXCEPT `id` and
956
+ * `name` are OPTIONAL at all three levels (workflow / phase / task), so a small model
957
+ * can omit the six identity strings and let the tool synthesize them positionally.
958
+ *
959
+ * @remarks
960
+ * The lenient counterpart {@link import('./factories.js').createWorkflowDraftContract}
961
+ * compiles. `run` stays required; a provided `id` / `name` still has `minLength: 1` (so an
962
+ * explicitly-empty `id: ''` is REJECTED, not auto-filled). After
963
+ * {@link import('./helpers.js').completeDraft} fills the missing ids/names, the result is
964
+ * validated against the STRICT {@link import('./factories.js').createWorkflowContract} gate
965
+ * before running.
966
+ */
967
+ var workflowDraftShape = (0, _orkestrel_contract.objectShape)({
968
+ id: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
969
+ min: 1,
970
+ description: "Workflow id; auto-filled when omitted."
971
+ })),
972
+ name: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
973
+ min: 1,
974
+ description: "Workflow name; defaults to the id when omitted."
975
+ })),
976
+ description: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "Optional workflow description." })),
977
+ phases: (0, _orkestrel_contract.arrayShape)(phaseDraftShape, { description: "The workflow phases; they run SEQUENTIALLY, in order." }),
978
+ bail: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.literalShape)([true, false], { description: "Failure policy: false (default) continues gracefully, true halts on the first failure." }))
979
+ });
980
+ /**
981
+ * The shape of ONE flat step — `{ name, via? }` — the building block of
982
+ * {@link workflowStepsShape}.
983
+ *
984
+ * @remarks
985
+ * `name` is the REGISTERED behavior name the step runs (it becomes the task's `run.name`);
986
+ * `via` is the optional execution mechanism (defaults to `'function'` when omitted). The
987
+ * tool expands each step into a one-task phase, in order
988
+ * ({@link import('./helpers.js').expandSteps}).
989
+ */
990
+ var stepShape = (0, _orkestrel_contract.objectShape)({
991
+ name: (0, _orkestrel_contract.stringShape)({
992
+ min: 1,
993
+ description: "The registered behavior name this step runs (becomes the task run.name)."
994
+ }),
995
+ via: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.literalShape)([
996
+ "function",
997
+ "tool",
998
+ "agent"
999
+ ], { description: "How to run it: function (default), tool, or agent." }))
1000
+ });
1001
+ /**
1002
+ * The FLAT authoring shape `createWorkflowTool` advertises as its `parameters` — the
1003
+ * simplest surface a small model can fill: `{ name?, steps: [{ name, via? }] }`.
1004
+ *
1005
+ * @remarks
1006
+ * The deliberately-reduced surface (AGENTS §21): a flat ordered list of steps, each a
1007
+ * `{ name, via? }`. The tool EXPANDS it ({@link import('./helpers.js').expandSteps}) into a
1008
+ * full {@link import('./types.js').WorkflowDefinition} — one one-task phase per step, in
1009
+ * order — then validates against the STRICT
1010
+ * {@link import('./factories.js').createWorkflowContract} gate. The full nested form is
1011
+ * STILL accepted by the tool (it branches on the args' shape) and is documented as the
1012
+ * advanced escape-hatch in the tool's description — but THIS is what `parameters` advertises.
1013
+ */
1014
+ var workflowStepsShape = (0, _orkestrel_contract.objectShape)({
1015
+ name: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
1016
+ min: 1,
1017
+ description: "Optional workflow name."
1018
+ })),
1019
+ steps: (0, _orkestrel_contract.arrayShape)(stepShape, { description: "The ordered steps to run, one after another (each becomes a one-task phase)." })
1020
+ });
1021
+ //#endregion
1022
+ //#region src/core/stores/DatabaseWorkflowStore.ts
1023
+ /**
1024
+ * A {@link WorkflowStoreInterface} backed by one table of the `databases` layer — a
1025
+ * workflow's durable run-state IS a row, so persistence reduces to keyed point-access
1026
+ * (`get` / `set` / `delete`) over a `TableInterface`, the driver-pluggable twin of the
1027
+ * plain-`Map` {@link import('./MemoryWorkflowStore.js').MemoryWorkflowStore}.
1028
+ *
1029
+ * @remarks
1030
+ * The store is driver-agnostic: it holds a single {@link TableInterface} whose backend
1031
+ * (memory, JSON, SQLite, IndexedDB) is chosen by whoever builds it (the factories), so a
1032
+ * JSON / SQLite / IndexedDB backend swaps in WITHOUT touching the runner or the entity tree
1033
+ * — the same seam as `@orkestrel/queue`'s `DatabaseQueueStore`.
1034
+ * The driver defaults to memory ({@link import('../factories.js').createDatabaseWorkflowStore}
1035
+ * passes `createMemoryDriver()`), so it ALSO works in memory out of the box; you opt into the
1036
+ * durable plumbing by passing a JSON / SQLite / IndexedDB driver.
1037
+ *
1038
+ * The {@link WorkflowSnapshot} is stored as ONE OPAQUE JSON COLUMN — the table is a row of
1039
+ * `{ id; snapshot }` ({@link WorkflowSnapshotRow}), the snapshot the whole JSON blob (a `rawShape`
1040
+ * column the factory builds) — exactly as `DatabaseQueueStore` stores its `input`. The snapshot is
1041
+ * already a COMPLETE, self-contained, pure-JSON payload, so storing it whole is lossless AND
1042
+ * sidesteps a TS2589 instantiation-depth blow-up: a structured multi-column table would force the
1043
+ * contract to `Infer` the deeply-nested snapshot shape (workflow → phases → tasks → results),
1044
+ * tripping the compiler — one JSON column keeps the row type flat (`snapshot` reads back as `unknown`).
1045
+ *
1046
+ * - **`set(snapshot)` upserts under the snapshot's OWN `id`** (no separate id param) — it writes
1047
+ * the row `{ id: snapshot.id, snapshot }`.
1048
+ * - **`get(id)` resolves the stored snapshot for an id**, narrowing the opaque JSON column back to
1049
+ * a {@link WorkflowSnapshot} ({@link import('../helpers.js').isWorkflowSnapshot} — the AGENTS §14
1050
+ * boundary narrow for an untrusted storage read), or `undefined` if none is stored.
1051
+ * - **`delete(id)` drops a snapshot by id**; an absent id is a no-op (no throw).
1052
+ *
1053
+ * UNLIKE the server package's `SessionStoreInterface` there is NO
1054
+ * idle-TTL / eviction — a persisted run-state is durable orchestration state that lives until an
1055
+ * explicit `delete`. The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the
1056
+ * §22 method bijection with {@link WorkflowStoreInterface}). Restore stays a caller concern: read a
1057
+ * snapshot back and rebuild the live tree with {@link import('../factories.js').restoreWorkflow}.
1058
+ *
1059
+ * @example
1060
+ * ```ts
1061
+ * import { createDatabaseWorkflowStore, createMemoryDriver, createWorkflow, restoreWorkflow } from '@src/core'
1062
+ *
1063
+ * const store = createDatabaseWorkflowStore(createMemoryDriver()) // a durable driver swaps in here
1064
+ * const workflow = createWorkflow(definition)
1065
+ * await store.set(workflow.snapshot()) // persist the run state (one JSON column)
1066
+ * const snapshot = await store.get(definition.id)
1067
+ * const restored = snapshot && restoreWorkflow(snapshot) // an identical live tree
1068
+ * await store.delete(definition.id) // drop it
1069
+ * ```
1070
+ */
1071
+ var DatabaseWorkflowStore = class {
1072
+ #table;
1073
+ /**
1074
+ * Wrap a table as a workflow store.
1075
+ *
1076
+ * @param table - The {@link TableInterface} holding the snapshots — its row is the
1077
+ * {@link WorkflowSnapshotRow} `{ id; snapshot }` shape (the snapshot one opaque JSON column)
1078
+ */
1079
+ constructor(table) {
1080
+ this.#table = table;
1081
+ }
1082
+ /** Resolve the persisted snapshot for `id`, narrowing the opaque JSON column back to a `WorkflowSnapshot`. */
1083
+ async get(id) {
1084
+ const row = await this.#table.get(id);
1085
+ if (row === void 0) return void 0;
1086
+ return isWorkflowSnapshot(row.snapshot) ? row.snapshot : void 0;
1087
+ }
1088
+ /** Insert or replace under the snapshot's OWN `id` (no separate id param) — the row is `{ id, snapshot }`. */
1089
+ async set(snapshot) {
1090
+ await this.#table.set({
1091
+ id: snapshot.id,
1092
+ snapshot
1093
+ });
1094
+ }
1095
+ /** Drop a snapshot by id; an absent id is a no-op (no throw). */
1096
+ async delete(id) {
1097
+ await this.#table.remove(id);
1098
+ }
1099
+ };
1100
+ //#endregion
1101
+ //#region src/core/stores/MemoryWorkflowStore.ts
1102
+ /**
1103
+ * The in-memory {@link WorkflowStoreInterface} — a process-lifetime `Map` of
1104
+ * {@link WorkflowSnapshot}s keyed by workflow id, the DEFAULT store
1105
+ * {@link import('../factories.js').createMemoryWorkflowStore} builds.
1106
+ *
1107
+ * @remarks
1108
+ * A plain `Map<string, WorkflowSnapshot>` (AGENTS §21 — the snapshot is already pure,
1109
+ * self-contained JSON, so no encoding is needed for the memory tier). UNLIKE the server
1110
+ * package's `SessionStoreInterface`'s memory store there is
1111
+ * NO idle-TTL and NO eviction: a persisted workflow run-state is durable orchestration state
1112
+ * that lives until an explicit `delete`, never silently aging out (a run that vanished
1113
+ * mid-flight would be a silent data loss, not a freed session). A durable backend (JSON /
1114
+ * SQLite / IndexedDB) swaps in through the SAME interface without touching the runner or the
1115
+ * entity tree — its driver-pluggable twin is
1116
+ * {@link import('./DatabaseWorkflowStore.js').DatabaseWorkflowStore} (the snapshot as one opaque
1117
+ * JSON column), exactly as `@orkestrel/queue`'s `MemoryQueueStore`
1118
+ * twins `DatabaseQueueStore`.
1119
+ *
1120
+ * - **`get` resolves the persisted snapshot for an id**, or `undefined` if none is stored.
1121
+ * - **`set` inserts / replaces under the snapshot's OWN `id`** (no separate id param).
1122
+ * - **`delete` drops a snapshot by id**; an absent id is a no-op (no throw).
1123
+ *
1124
+ * The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the §22 method
1125
+ * bijection with {@link WorkflowStoreInterface}). Restore is a caller concern: read a snapshot
1126
+ * back and rebuild the live tree with {@link import('../factories.js').restoreWorkflow}.
1127
+ *
1128
+ * @example
1129
+ * ```ts
1130
+ * import { createMemoryWorkflowStore, createWorkflow, restoreWorkflow } from '@src/core'
1131
+ *
1132
+ * const store = createMemoryWorkflowStore()
1133
+ * const workflow = createWorkflow(definition)
1134
+ * await store.set(workflow.snapshot()) // persist the run state
1135
+ * const snapshot = await store.get(definition.id)
1136
+ * const restored = snapshot && restoreWorkflow(snapshot) // an identical live tree
1137
+ * await store.delete(definition.id) // drop it
1138
+ * ```
1139
+ */
1140
+ var MemoryWorkflowStore = class {
1141
+ #snapshots = /* @__PURE__ */ new Map();
1142
+ get(id) {
1143
+ return Promise.resolve(this.#snapshots.get(id));
1144
+ }
1145
+ set(snapshot) {
1146
+ this.#snapshots.set(snapshot.id, snapshot);
1147
+ return Promise.resolve();
1148
+ }
1149
+ delete(id) {
1150
+ this.#snapshots.delete(id);
1151
+ return Promise.resolve();
1152
+ }
1153
+ };
1154
+ //#endregion
1155
+ //#region src/core/tasks/Task.ts
1156
+ /**
1157
+ * The live leaf state machine (W-b) for one task — an observable (AGENTS §13), guarded
1158
+ * synchronous task whose explicit {@link TaskStatus} advances through the AGENTS §10
1159
+ * transitions, recording a {@link TaskResult} on a terminal outcome.
1160
+ *
1161
+ * @remarks
1162
+ * - **Guarded transitions (AGENTS §10).** `start` (→ `running`), then `complete(value)`
1163
+ * (→ `completed`, records a {@link import('@orkestrel/contract').Success}), `fail(error)`
1164
+ * (→ `failed`, records a {@link import('@orkestrel/contract').Failure}), `skip` (→ `skipped`),
1165
+ * `stop` (→ `stopped`). Each consults {@link canTransitionTask} FIRST and throws a
1166
+ * `TRANSITION` {@link WorkflowError} on an illegal move (e.g. completing a non-`running`
1167
+ * task) — the legal graph is the single source of truth, so the leaf can never reach an
1168
+ * impossible state.
1169
+ * - **Override (snapshot fidelity).** `skip` / `stop` set `#override` to the forced terminal
1170
+ * status, so a RESTORE can tell a forced leaf (`skipped` / `stopped`) from a run-produced
1171
+ * one and reinstate it AS an override — preserving the round-trip.
1172
+ * - **The cascade.** Every status change records its boxed result (when any), fires the leaf's
1173
+ * OWN event, THEN calls the parent phase's `#recompute` (injected at construction) so the
1174
+ * transition propagates UP (Task → Phase → Workflow re-derive). The own-event-before-cascade
1175
+ * order means an observer sees the CAUSE (this leaf changed) before the EFFECT (the parents
1176
+ * re-derive) — the project precedent (`Runner.#settle` emits its own `fail` before propagating).
1177
+ * - **Observable (AGENTS §13).** The owned {@link emitter} ({@link TaskEventMap}) fires the
1178
+ * matching event strictly AFTER the state change, BEFORE the cascade; the emitter isolates
1179
+ * a listener throw and routes it to its `error` handler (the `error` option), so a buggy
1180
+ * observer can never corrupt a transition.
1181
+ */
1182
+ var Task = class {
1183
+ #context;
1184
+ #phase;
1185
+ #workflow;
1186
+ #recompute;
1187
+ #metadata;
1188
+ #emitter;
1189
+ #status;
1190
+ #result;
1191
+ constructor(context, phase, workflow, recompute, options, status = "pending", result) {
1192
+ this.#context = context;
1193
+ this.#phase = phase;
1194
+ this.#workflow = workflow;
1195
+ this.#recompute = recompute;
1196
+ this.#metadata = options?.metadata ?? {};
1197
+ this.#emitter = new _orkestrel_emitter.Emitter({
1198
+ on: options?.on,
1199
+ error: options?.error
1200
+ });
1201
+ this.#status = status;
1202
+ this.#result = result;
1203
+ }
1204
+ get emitter() {
1205
+ return this.#emitter;
1206
+ }
1207
+ get id() {
1208
+ return this.#context.id;
1209
+ }
1210
+ get name() {
1211
+ return this.#context.name;
1212
+ }
1213
+ get description() {
1214
+ return this.#context.description;
1215
+ }
1216
+ get context() {
1217
+ return this.#context;
1218
+ }
1219
+ get phase() {
1220
+ return this.#phase;
1221
+ }
1222
+ get workflow() {
1223
+ return this.#workflow;
1224
+ }
1225
+ get status() {
1226
+ return this.#status;
1227
+ }
1228
+ get result() {
1229
+ return this.#result;
1230
+ }
1231
+ start() {
1232
+ this.#transition("running");
1233
+ this.#emitter.emit("start", this.id);
1234
+ this.#escalate();
1235
+ }
1236
+ complete(value) {
1237
+ this.#transition("completed");
1238
+ const result = this.#record("completed", {
1239
+ success: true,
1240
+ value
1241
+ });
1242
+ this.#emitter.emit("complete", result);
1243
+ this.#escalate();
1244
+ }
1245
+ fail(error) {
1246
+ this.#transition("failed");
1247
+ const reason = error instanceof Error ? error : new Error(String(error), { cause: error });
1248
+ const result = this.#record("failed", {
1249
+ success: false,
1250
+ error: reason
1251
+ });
1252
+ this.#emitter.emit("fail", result);
1253
+ this.#escalate();
1254
+ }
1255
+ skip() {
1256
+ this.#transition("skipped");
1257
+ this.#emitter.emit("skip");
1258
+ this.#escalate();
1259
+ }
1260
+ stop() {
1261
+ this.#transition("stopped");
1262
+ this.#emitter.emit("stop");
1263
+ this.#escalate();
1264
+ }
1265
+ snapshot() {
1266
+ return {
1267
+ id: this.id,
1268
+ name: this.name,
1269
+ ...this.description === void 0 ? {} : { description: this.description },
1270
+ status: this.#status,
1271
+ ...this.#result === void 0 ? {} : { result: this.#result },
1272
+ metadata: this.#metadata
1273
+ };
1274
+ }
1275
+ #transition(to) {
1276
+ if (!canTransitionTask(this.#status, to)) throw new WorkflowError("TRANSITION", `task '${this.id}' cannot transition from '${this.#status}' to '${to}'`, {
1277
+ task: this.id,
1278
+ from: this.#status,
1279
+ to
1280
+ });
1281
+ this.#status = to;
1282
+ }
1283
+ #record(status, result) {
1284
+ const record = {
1285
+ task: this.#context,
1286
+ phase: this.#context.phase,
1287
+ workflow: this.#context.phase.workflow,
1288
+ status,
1289
+ ...result === void 0 ? {} : { result },
1290
+ timestamp: Date.now()
1291
+ };
1292
+ this.#result = record;
1293
+ return record;
1294
+ }
1295
+ #escalate() {
1296
+ this.#recompute();
1297
+ }
1298
+ };
1299
+ //#endregion
1300
+ //#region src/core/tasks/TaskManager.ts
1301
+ /**
1302
+ * The lean child manager (AGENTS §9) of a {@link import('../phases/Phase.js').Phase}'s live
1303
+ * tasks — an insertion-ordered registry keyed by task `id`, so positional order is
1304
+ * preserved across an interior `skip` / `remove`.
1305
+ *
1306
+ * @remarks
1307
+ * - **Positional store.** Tasks live in an insertion-ordered `Map` keyed by `id`;
1308
+ * `append` adds one at the end (the build-time wiring path), `task(id)` looks one up,
1309
+ * `tasks()` lists them in positional order, `count` is the size. A `skip` is a STATUS
1310
+ * change on a stored task (never a removal), so order survives it; a snapshot RESTORE
1311
+ * re-`append`s in the snapshot's order, reproducing it exactly.
1312
+ * - **No batch matrix.** A phase's tasks are a fixed positional set, so AGENTS §9.2 (the
1313
+ * bulk verb overloads) is deliberately omitted — there is no `remove` family here.
1314
+ * - **Event-free.** A purely structural container — the live {@link TaskInterface}s own
1315
+ * their own emitters; the manager observes nothing.
1316
+ *
1317
+ * @example
1318
+ * ```ts
1319
+ * const tasks = new TaskManager()
1320
+ * tasks.append(task) // a live Task
1321
+ * tasks.task(task.id) // the same task
1322
+ * tasks.count // 1
1323
+ * ```
1324
+ */
1325
+ var TaskManager = class {
1326
+ #tasks = /* @__PURE__ */ new Map();
1327
+ get count() {
1328
+ return this.#tasks.size;
1329
+ }
1330
+ append(task) {
1331
+ this.#tasks.set(task.id, task);
1332
+ }
1333
+ task(id) {
1334
+ return this.#tasks.get(id);
1335
+ }
1336
+ tasks() {
1337
+ return [...this.#tasks.values()];
1338
+ }
1339
+ };
1340
+ //#endregion
1341
+ //#region src/core/phases/Phase.ts
1342
+ /**
1343
+ * The live DERIVED state machine (W-b) for one phase — an observable (AGENTS §13) whose
1344
+ * {@link PhaseStatus} is computed from its tasks (never set directly) and recomputed
1345
+ * reactively as a task transitions (the middle tier of the cascade).
1346
+ *
1347
+ * @remarks
1348
+ * - **Derived status.** `status` is `#override` when one is in force, else
1349
+ * {@link derivePhaseStatus} over the live tasks' statuses. {@link #recompute} (passed to
1350
+ * each child {@link Task}) re-derives on every child transition; a CHANGE emits the matching
1351
+ * event AND escalates to the workflow ({@link #escalate}, the upward step of the cascade).
1352
+ * - **Override (AGENTS §10).** `skip` / `stop` FORCE the phase's status (e.g. skipping a whole
1353
+ * phase), overriding the derived value; the override is PERSISTED in the snapshot's own
1354
+ * `override` field and restored DIRECTLY (no divergence guess), so a forced phase round-trips.
1355
+ * - **Children (AGENTS §9).** `tasks` is the lean {@link TaskManager} (an accessor + `count`,
1356
+ * no batch matrix); built positionally from the snapshot so order survives an interior `skip`.
1357
+ * `results()` collects the settled tasks' {@link TaskResult}s (the phase tier of the result
1358
+ * tree); `workflow` navigates UP to the live parent.
1359
+ * - **Observable (AGENTS §13).** The owned {@link emitter} ({@link PhaseEventMap}) fires
1360
+ * `start` / `complete` / `fail` / `stop` on a derived-status CHANGE, strictly AFTER the
1361
+ * recompute + escalate; the emitter isolates a listener throw and routes it to its `error`
1362
+ * handler (the `error` option); `fail` carries the failing task's {@link TaskResult}.
1363
+ */
1364
+ var Phase = class {
1365
+ #context;
1366
+ #workflow;
1367
+ #escalateUp;
1368
+ #tasks = new TaskManager();
1369
+ #bail;
1370
+ #emitter;
1371
+ #status;
1372
+ #override;
1373
+ constructor(snapshot, workflow, escalate, options, bail) {
1374
+ this.#context = {
1375
+ id: snapshot.id,
1376
+ name: snapshot.name,
1377
+ workflow: workflow.context
1378
+ };
1379
+ if (snapshot.description !== void 0) this.#context = {
1380
+ ...this.#context,
1381
+ description: snapshot.description
1382
+ };
1383
+ this.#workflow = workflow;
1384
+ this.#escalateUp = escalate;
1385
+ this.#bail = bail ?? snapshot.bail;
1386
+ this.#emitter = new _orkestrel_emitter.Emitter({
1387
+ on: options?.on,
1388
+ error: options?.error
1389
+ });
1390
+ for (const task of snapshot.tasks) this.#append(task, options);
1391
+ this.#override = snapshot.override;
1392
+ this.#status = this.status;
1393
+ }
1394
+ get emitter() {
1395
+ return this.#emitter;
1396
+ }
1397
+ get id() {
1398
+ return this.#context.id;
1399
+ }
1400
+ get name() {
1401
+ return this.#context.name;
1402
+ }
1403
+ get description() {
1404
+ return this.#context.description;
1405
+ }
1406
+ get context() {
1407
+ return this.#context;
1408
+ }
1409
+ get workflow() {
1410
+ return this.#workflow;
1411
+ }
1412
+ get bail() {
1413
+ return this.#bail;
1414
+ }
1415
+ get status() {
1416
+ return this.#override ?? derivePhaseStatus(this.#statuses());
1417
+ }
1418
+ get tasks() {
1419
+ return this.#tasks;
1420
+ }
1421
+ task(id) {
1422
+ return this.#tasks.task(id);
1423
+ }
1424
+ results() {
1425
+ const results = [];
1426
+ for (const task of this.#tasks.tasks()) if (task.result !== void 0) results.push(task.result);
1427
+ return results;
1428
+ }
1429
+ skip() {
1430
+ this.#force("skipped");
1431
+ }
1432
+ stop() {
1433
+ this.#force("stopped");
1434
+ }
1435
+ snapshot() {
1436
+ return {
1437
+ id: this.id,
1438
+ name: this.name,
1439
+ ...this.description === void 0 ? {} : { description: this.description },
1440
+ status: this.status,
1441
+ ...this.#override === void 0 ? {} : { override: this.#override },
1442
+ bail: this.#bail,
1443
+ tasks: this.#tasks.tasks().map((task) => task.snapshot())
1444
+ };
1445
+ }
1446
+ #recompute() {
1447
+ const next = this.status;
1448
+ if (next === this.#status) {
1449
+ this.#escalateUp();
1450
+ return;
1451
+ }
1452
+ this.#status = next;
1453
+ this.#emitFor(next);
1454
+ this.#escalateUp();
1455
+ }
1456
+ #force(status) {
1457
+ this.#override = status;
1458
+ this.#recompute();
1459
+ }
1460
+ #emitFor(status) {
1461
+ if (status === "running") this.#emitter.emit("start", this.id);
1462
+ else if (status === "completed") this.#emitter.emit("complete");
1463
+ else if (status === "failed") this.#emitter.emit("fail", this.#failure());
1464
+ else if (status === "stopped") this.#emitter.emit("stop");
1465
+ }
1466
+ #failure() {
1467
+ for (const task of this.#tasks.tasks()) {
1468
+ const result = task.result;
1469
+ if (result?.result?.success === false) return result;
1470
+ }
1471
+ throw new Error(`phase '${this.id}' derived failed with no failing task result`);
1472
+ }
1473
+ #append(task, options) {
1474
+ const created = new Task(buildTaskContext(this.#context, task), this, this.#workflow, () => this.#recompute(), options?.tasks?.[task.id], task.status, task.result);
1475
+ this.#tasks.append(created);
1476
+ }
1477
+ #statuses() {
1478
+ return this.#tasks.tasks().map((task) => task.status);
1479
+ }
1480
+ };
1481
+ //#endregion
1482
+ //#region src/core/phases/PhaseManager.ts
1483
+ /**
1484
+ * The lean child manager (AGENTS §9) of a {@link import('../Workflow.js').Workflow}'s
1485
+ * live phases — an insertion-ordered registry keyed by phase `id`, the phase analogue
1486
+ * of {@link import('../tasks/TaskManager.js').TaskManager}.
1487
+ *
1488
+ * @remarks
1489
+ * - **Positional store.** Phases live in an insertion-ordered `Map` keyed by `id`;
1490
+ * `append` adds one at the end, `phase(id)` looks one up, `phases()` lists them in
1491
+ * positional order, `count` is the size. A snapshot RESTORE re-`append`s in the
1492
+ * snapshot's order, reproducing it exactly.
1493
+ * - **No batch matrix.** A workflow's phases are a fixed positional set, so AGENTS §9.2
1494
+ * is deliberately omitted.
1495
+ * - **Event-free.** A purely structural container — the live {@link PhaseInterface}s own
1496
+ * their own emitters.
1497
+ *
1498
+ * @example
1499
+ * ```ts
1500
+ * const phases = new PhaseManager()
1501
+ * phases.append(phase) // a live Phase
1502
+ * phases.phase(phase.id) // the same phase
1503
+ * phases.count // 1
1504
+ * ```
1505
+ */
1506
+ var PhaseManager = class {
1507
+ #phases = /* @__PURE__ */ new Map();
1508
+ get count() {
1509
+ return this.#phases.size;
1510
+ }
1511
+ append(phase) {
1512
+ this.#phases.set(phase.id, phase);
1513
+ }
1514
+ phase(id) {
1515
+ return this.#phases.get(id);
1516
+ }
1517
+ phases() {
1518
+ return [...this.#phases.values()];
1519
+ }
1520
+ };
1521
+ //#endregion
1522
+ //#region src/core/Workflow.ts
1523
+ /**
1524
+ * The live DERIVED state machine (W-b) for a whole workflow — the observable (AGENTS §13)
1525
+ * ROOT whose {@link WorkflowStatus} is computed from its phases under the `bail` policy and
1526
+ * recomputed reactively as the cascade propagates up from a task transition.
1527
+ *
1528
+ * @remarks
1529
+ * - **Construction.** Built from a {@link WorkflowSnapshot} (the unified input —
1530
+ * {@link import('./factories.js').createWorkflow} seeds an initial snapshot from a
1531
+ * {@link import('./types.js').WorkflowDefinition}, {@link import('./factories.js').restoreWorkflow}
1532
+ * passes a persisted one). Each child {@link Phase} is wired to escalate to {@link #recompute}.
1533
+ * - **Derived status.** `status` is `#override` when forced, else
1534
+ * {@link deriveWorkflowStatus} over the live phases' statuses feeding `bail`. `failed` is
1535
+ * reachable ONLY under `bail: true` (a single failed task halts the workflow); under
1536
+ * `bail: false` a failed phase folds into `completed`. {@link #recompute} diffs on each phase
1537
+ * change; a CHANGE emits.
1538
+ * - **Override (AGENTS §10).** `skip` / `stop` FORCE the status; the override is PERSISTED in the
1539
+ * snapshot's own `override` field and restored DIRECTLY (no divergence guess). The snapshot also
1540
+ * persists `bail`, so a restore re-derives status identically without a silent policy default.
1541
+ * - **Result tree.** `results()` flattens every phase's `results()` ({@link collectResults}) — the
1542
+ * workflow tier; `phase(id)` + each `phase.task(id)` navigate DOWN, a task's `phase` / `workflow`
1543
+ * navigate UP.
1544
+ * - **Snapshot.** `snapshot()` serializes the whole live tree to a {@link WorkflowSnapshot} (pure
1545
+ * JSON); {@link import('./factories.js').restoreWorkflow} rebuilds an equivalent live tree.
1546
+ * - **Observable (AGENTS §13).** The owned {@link emitter} ({@link WorkflowEventMap}) fires
1547
+ * `start` / `complete` / `fail` / `stop` on a derived-status CHANGE; the emitter isolates a
1548
+ * listener throw and routes it to its `error` handler (the `error` option); `fail` carries
1549
+ * the failing task's {@link TaskResult}.
1550
+ */
1551
+ var Workflow = class {
1552
+ #context;
1553
+ #bail;
1554
+ #bailOverride;
1555
+ #phases = new PhaseManager();
1556
+ #emitter;
1557
+ #created;
1558
+ #updated;
1559
+ #status;
1560
+ #override;
1561
+ constructor(snapshot, options) {
1562
+ this.#context = buildWorkflowContext(snapshot);
1563
+ this.#bail = options?.bail ?? snapshot.bail;
1564
+ this.#bailOverride = options?.bail;
1565
+ this.#emitter = new _orkestrel_emitter.Emitter({
1566
+ on: options?.on,
1567
+ error: options?.error
1568
+ });
1569
+ this.#created = snapshot.created;
1570
+ this.#updated = snapshot.updated;
1571
+ for (const phase of snapshot.phases) this.#append(phase, options);
1572
+ this.#override = snapshot.override;
1573
+ this.#status = this.status;
1574
+ }
1575
+ get emitter() {
1576
+ return this.#emitter;
1577
+ }
1578
+ get id() {
1579
+ return this.#context.id;
1580
+ }
1581
+ get name() {
1582
+ return this.#context.name;
1583
+ }
1584
+ get description() {
1585
+ return this.#context.description;
1586
+ }
1587
+ get context() {
1588
+ return this.#context;
1589
+ }
1590
+ get bail() {
1591
+ return this.#bail;
1592
+ }
1593
+ get status() {
1594
+ return this.#override ?? deriveWorkflowStatus(this.#statuses());
1595
+ }
1596
+ get phases() {
1597
+ return this.#phases;
1598
+ }
1599
+ phase(id) {
1600
+ return this.#phases.phase(id);
1601
+ }
1602
+ results() {
1603
+ return collectResults(this.#phases.phases().map((phase) => phase.results()));
1604
+ }
1605
+ skip() {
1606
+ this.#force("skipped");
1607
+ }
1608
+ stop() {
1609
+ this.#force("stopped");
1610
+ }
1611
+ complete() {
1612
+ this.#force("completed");
1613
+ }
1614
+ snapshot() {
1615
+ return {
1616
+ id: this.id,
1617
+ name: this.name,
1618
+ ...this.description === void 0 ? {} : { description: this.description },
1619
+ status: this.status,
1620
+ ...this.#override === void 0 ? {} : { override: this.#override },
1621
+ bail: this.#bail,
1622
+ phases: this.#phases.phases().map((phase) => phase.snapshot()),
1623
+ created: this.#created,
1624
+ updated: this.#updated
1625
+ };
1626
+ }
1627
+ #recompute() {
1628
+ const next = this.status;
1629
+ if (next === this.#status) return;
1630
+ this.#status = next;
1631
+ this.#updated = Date.now();
1632
+ this.#emitFor(next);
1633
+ }
1634
+ #force(status) {
1635
+ this.#override = status;
1636
+ this.#recompute();
1637
+ }
1638
+ #emitFor(status) {
1639
+ if (status === "running") this.#emitter.emit("start", this.id);
1640
+ else if (status === "completed") this.#emitter.emit("complete");
1641
+ else if (status === "failed") this.#emitter.emit("fail", this.#failure());
1642
+ else if (status === "stopped") this.#emitter.emit("stop");
1643
+ }
1644
+ #failure() {
1645
+ for (const result of this.results()) if (result.result?.success === false) return result;
1646
+ throw new Error(`workflow '${this.id}' derived failed with no failing task result`);
1647
+ }
1648
+ #append(phase, options) {
1649
+ const created = new Phase(phase, this, () => this.#recompute(), options?.phases?.[phase.id], this.#bailOverride);
1650
+ this.#phases.append(created);
1651
+ }
1652
+ #statuses() {
1653
+ return this.#phases.phases().map((phase) => ({
1654
+ status: phase.status,
1655
+ bail: phase.bail
1656
+ }));
1657
+ }
1658
+ };
1659
+ //#endregion
1660
+ //#region src/core/Controller.ts
1661
+ /**
1662
+ * The per-unit handle a runner handler receives — wraps the unit's identity,
1663
+ * input, cancellation, and the run controls (`wait` / `spawn` / `abort`).
1664
+ *
1665
+ * @remarks
1666
+ * - **Built by the Runner per unit.** The runner constructs one `Controller` per
1667
+ * unit it dispatches, handing it the unit's `id`, `input`, the unit's `Abort`
1668
+ * handle, the queue attempt's `signal`, and a `spawn` callback that launches a
1669
+ * sibling through the same queue.
1670
+ * - **Signal.** `signal` is the queue attempt's signal, which ANY-combines the
1671
+ * unit's own abort, the runner-level abort (the runner aborts every unit), and
1672
+ * the per-attempt timeout — so it fires on any of the three. `aborted` and
1673
+ * `abort(reason)` delegate to the unit's `Abort` (the cancellation source of
1674
+ * truth); since the attempt signal ANY-includes that abort, `abort()` fires
1675
+ * `signal` too.
1676
+ * - **`wait` promise-parks (never a timer).** It resolves the instant the unit's
1677
+ * `signal` fires (immediately if already aborted) via a one-shot listener — no
1678
+ * `setTimeout`, no polling, no busy-yield — so a parked unit costs no CPU.
1679
+ * - **`spawn` is fire-and-track.** It delegates to the runner's launch-a-sibling
1680
+ * callback, which routes the sibling through the queue; the runner's `execute`
1681
+ * awaits the spawn closure, so the sibling runs whether or not its promise is
1682
+ * awaited. (Inline-awaiting a spawn from a slot-holding handler on a bounded
1683
+ * runner can deadlock — fan out instead; see {@link ControllerInterface.spawn}.)
1684
+ * - **Event-free by design.** The per-unit handle carries no Emitter; observe the
1685
+ * {@link RunnerInterface.emitter} instead (`unit` / `spawn` / `settle` / `fail` carry the id).
1686
+ */
1687
+ var Controller = class {
1688
+ id;
1689
+ input;
1690
+ signal;
1691
+ #abort;
1692
+ #spawn;
1693
+ constructor(id, input, abort, signal, spawn) {
1694
+ this.id = id;
1695
+ this.input = input;
1696
+ this.#abort = abort;
1697
+ this.signal = signal;
1698
+ this.#spawn = spawn;
1699
+ }
1700
+ get aborted() {
1701
+ return this.#abort.aborted;
1702
+ }
1703
+ wait() {
1704
+ if (this.signal.aborted) return Promise.resolve();
1705
+ return new Promise((resolve) => {
1706
+ this.signal.addEventListener("abort", () => resolve(), { once: true });
1707
+ });
1708
+ }
1709
+ spawn(input) {
1710
+ return this.#spawn(input);
1711
+ }
1712
+ abort(reason) {
1713
+ this.#abort.abort(reason);
1714
+ }
1715
+ };
1716
+ //#endregion
1717
+ //#region src/core/Runner.ts
1718
+ /**
1719
+ * A thin generic orchestrator that drives declared units — and any they `spawn` —
1720
+ * through a bounded-concurrency {@link createQueue}, collecting ordered results.
1721
+ *
1722
+ * @remarks
1723
+ * - **Drives the Queue (no reimplemented concurrency).** Every unit (declared or
1724
+ * spawned) is `enqueue`d on one internal `Queue`, so backpressure, FIFO ordering,
1725
+ * bounded concurrency, retries, and the per-attempt timeout are all the Queue's —
1726
+ * the Runner adds only orchestration (launching, ordering, draining, fail-fast).
1727
+ * - **Spawns actually run, results stay ordered (the B2 fix).** Declared inputs and
1728
+ * `spawn`ed siblings flow through the SAME `#launch`, which appends the unit's `id`
1729
+ * to an ordered `#order` list and records its settled value into `#values` by `id`.
1730
+ * Results are read back as `#order.map(id => #values.get(id))` — declared first (in
1731
+ * input order), then spawns (in spawn order). There is no one-time task snapshot,
1732
+ * so a unit spawned mid-handler is run and ordered like any other.
1733
+ * - **`execute` awaits the full spawn closure via a count gate.** `#launch` increments
1734
+ * an outstanding-unit `#count` BEFORE enqueuing and every settle decrements it,
1735
+ * resolving the `#drained` deferred at zero. Because `spawn` calls `#launch` (so
1736
+ * `#count += 1`) before the parent handler returns, the count never reaches zero
1737
+ * mid-run — `execute` parks on `#drained` and so awaits the entire transitive
1738
+ * closure, not just the declared units.
1739
+ * - **`spawn` is fire-and-track.** A spawned unit runs through the queue regardless of
1740
+ * whether its promise is awaited; the Runner never awaits a spawned promise from
1741
+ * within a handler's slot (it awaits the count gate instead), so a slot-holding
1742
+ * handler can fan out without the Runner deadlocking it. (An inline `await` of a
1743
+ * spawn by a bounded handler can still deadlock — that caveat is the caller's.)
1744
+ * - **Per-unit Controller + signal.** Each unit gets a `Controller` carrying its `id`,
1745
+ * `input`, the unit's `Abort` (so `aborted` / `abort` delegate to it), and the queue
1746
+ * attempt's `signal` (which ANY-combines the unit abort + runner abort + timeout). A
1747
+ * `spawn` callback is injected so `controller.spawn(input)` delegates to `#launch`.
1748
+ * - **One-shot + fail-fast.** `execute` runs once (a second call throws). The first
1749
+ * unit failure (after its retries) records the error and `abort()`s the run, so every
1750
+ * sibling's signal fires; later failures are ignored and `execute` rejects with the
1751
+ * first error. A user `abort(reason)` likewise rejects a running `execute`.
1752
+ * - **Observable (§13).** The owned {@link emitter} ({@link RunnerEventMap}) carries the run
1753
+ * lifecycle — `start` / `unit` / `spawn` / `settle` / `fail` / `finish` / `abort` — for
1754
+ * fire-and-forget observers. Every event is emitted directly, strictly AFTER the relevant
1755
+ * launch / settle / drain transition; the emitter isolates a listener throw and routes it
1756
+ * to its `error` handler (the `error` option), so a buggy observer can NEVER reorder, throw
1757
+ * into, or corrupt the one-shot / fail-fast / spawn-tracking engine: the outstanding-unit
1758
+ * count gate stays balanced and fail-fast still fires regardless of what a listener does.
1759
+ * Observation is purely a side-channel.
1760
+ */
1761
+ var Runner = class {
1762
+ #handler;
1763
+ #entries;
1764
+ #queue;
1765
+ #emitter;
1766
+ #aborts = /* @__PURE__ */ new Map();
1767
+ #order = [];
1768
+ #values = /* @__PURE__ */ new Map();
1769
+ #count = 0;
1770
+ #drained;
1771
+ #started = false;
1772
+ #running = false;
1773
+ #stopped = false;
1774
+ #failure;
1775
+ constructor(options) {
1776
+ this.#handler = options.handler;
1777
+ this.#entries = options.entries;
1778
+ this.#emitter = new _orkestrel_emitter.Emitter({
1779
+ on: options?.on,
1780
+ error: options?.error
1781
+ });
1782
+ this.#queue = (0, _orkestrel_queue.createQueue)({
1783
+ handler: (unit, execution) => this.#dispatch(unit, execution),
1784
+ concurrency: options.concurrency,
1785
+ retries: options.retries,
1786
+ timeout: options.timeout
1787
+ });
1788
+ }
1789
+ get emitter() {
1790
+ return this.#emitter;
1791
+ }
1792
+ get active() {
1793
+ return this.#count;
1794
+ }
1795
+ get stopped() {
1796
+ return this.#stopped;
1797
+ }
1798
+ async execute(inputs) {
1799
+ if (this.#started) throw new Error("runner has already executed");
1800
+ if (this.#stopped) throw new Error("runner is stopped");
1801
+ this.#started = true;
1802
+ this.#running = true;
1803
+ this.#emitter.emit("start");
1804
+ if (inputs.length === 0) {
1805
+ this.#running = false;
1806
+ this.#emitter.emit("finish", []);
1807
+ return [];
1808
+ }
1809
+ const drained = createDeferred();
1810
+ this.#drained = drained;
1811
+ for (const input of inputs) this.#launch(input);
1812
+ await drained.promise;
1813
+ this.#running = false;
1814
+ if (this.#failure !== void 0) throw this.#failure.error;
1815
+ const results = this.#collect();
1816
+ this.#emitter.emit("finish", results);
1817
+ return results;
1818
+ }
1819
+ abort(reason) {
1820
+ if (this.#stopped) return;
1821
+ if (this.#running && this.#failure === void 0) this.#failure = { error: reason === void 0 ? /* @__PURE__ */ new Error("runner aborted") : reason };
1822
+ this.#cancel(reason);
1823
+ this.#queue.abort(reason);
1824
+ this.#stopped = true;
1825
+ this.#emitter.emit("abort", reason);
1826
+ }
1827
+ destroy() {
1828
+ if (this.#stopped) {
1829
+ this.#queue.destroy();
1830
+ return;
1831
+ }
1832
+ this.abort();
1833
+ this.#queue.destroy();
1834
+ }
1835
+ #launch(input, parent) {
1836
+ const id = crypto.randomUUID();
1837
+ const abort = (0, _orkestrel_abort.createAbort)();
1838
+ this.#aborts.set(id, abort);
1839
+ this.#order.push(id);
1840
+ this.#count += 1;
1841
+ if (parent !== void 0) this.#emitter.emit("spawn", id, parent);
1842
+ const promise = this.#queue.enqueue({
1843
+ id,
1844
+ input
1845
+ }, {
1846
+ id,
1847
+ signal: abort.signal,
1848
+ ...this.#entries?.(input)
1849
+ });
1850
+ promise.then((value) => this.#settle(id, {
1851
+ ok: true,
1852
+ value
1853
+ }), (error) => this.#settle(id, {
1854
+ ok: false,
1855
+ error
1856
+ }));
1857
+ return promise;
1858
+ }
1859
+ #dispatch(unit, execution) {
1860
+ const abort = this.#aborts.get(unit.id);
1861
+ if (abort === void 0) throw new Error("unit abort missing");
1862
+ const controller = new Controller(unit.id, unit.input, abort, execution.signal, (input) => this.#spawn(input, unit.id));
1863
+ this.#emitter.emit("unit", unit.id);
1864
+ return this.#handler(controller);
1865
+ }
1866
+ #spawn(input, parent) {
1867
+ if (!this.#running) throw new Error("spawn is unavailable outside an active run");
1868
+ return this.#launch(input, parent);
1869
+ }
1870
+ #settle(id, outcome) {
1871
+ if (outcome.ok) {
1872
+ this.#values.set(id, { value: outcome.value });
1873
+ this.#emitter.emit("settle", id);
1874
+ } else if (this.#failure === void 0) {
1875
+ this.#failure = { error: outcome.error };
1876
+ this.#emitter.emit("fail", id, outcome.error);
1877
+ this.abort(outcome.error);
1878
+ }
1879
+ this.#count -= 1;
1880
+ if (this.#count === 0) this.#drained?.resolve();
1881
+ }
1882
+ #collect() {
1883
+ const results = [];
1884
+ for (const id of this.#order) {
1885
+ const box = this.#values.get(id);
1886
+ if (box !== void 0) results.push(box.value);
1887
+ }
1888
+ return results;
1889
+ }
1890
+ #cancel(reason) {
1891
+ for (const abort of this.#aborts.values()) abort.abort(reason);
1892
+ }
1893
+ };
1894
+ //#endregion
1895
+ //#region src/core/tasks/TaskController.ts
1896
+ /**
1897
+ * The lean per-task handle a {@link import('./types.js').WorkflowFunction} receives — the
1898
+ * running task's folded cancellation, its input, its lineage, and read-UP access to the
1899
+ * result tree.
1900
+ *
1901
+ * @remarks
1902
+ * - **A leaf handle, NOT the runner `Controller`.** A workflow task is a leaf of the
1903
+ * declarative W-b tree, not a fan-out unit, so this carries none of the runner
1904
+ * `Controller`'s `spawn` / `wait` — only what a leaf needs.
1905
+ * - **Folded signal.** `signal` is the cancellation the runner folds for THIS run: it fires
1906
+ * on a workflow-level abort / timeout / budget ceiling, or — under `bail: true` — when a
1907
+ * sibling task fails (the runner aborts the in-flight siblings via the substrate's
1908
+ * fail-fast). A handler races its work against it; `aborted` reads it.
1909
+ * - **Input + lineage.** `input` is the task's open `metadata` bag (its
1910
+ * {@link import('./types.js').TaskInput} payload, `{}` when none); `task` is the full
1911
+ * {@link TaskContext}, so `task.phase` / `task.phase.workflow` navigate UP the lineage.
1912
+ * - **Read-up results.** `results()` returns every settled task's {@link TaskResult} across
1913
+ * the phases that have already finished (a closure over the live
1914
+ * {@link import('./types.js').WorkflowInterface}), so a `function` task can read an earlier
1915
+ * phase's output. Read-only — a task records its OWN outcome by returning / throwing, not
1916
+ * by mutating the tree.
1917
+ * - **Event-free.** Like the runner `Controller`, the per-task handle carries no Emitter;
1918
+ * observe the W-b entities' own emitters (`task.emitter` / `phase.emitter`) instead.
1919
+ */
1920
+ var TaskController = class {
1921
+ signal;
1922
+ input;
1923
+ task;
1924
+ #results;
1925
+ constructor(signal, input, task, results) {
1926
+ this.signal = signal;
1927
+ this.input = input;
1928
+ this.task = task;
1929
+ this.#results = results;
1930
+ }
1931
+ get aborted() {
1932
+ return this.signal.aborted;
1933
+ }
1934
+ results() {
1935
+ return this.#results();
1936
+ }
1937
+ };
1938
+ //#endregion
1939
+ //#region src/core/WorkflowRunner.ts
1940
+ /**
1941
+ * The thin orchestrator that EXECUTES a live W-b workflow tree by COMPOSING the shipped
1942
+ * substrate — phases sequential, tasks concurrent — dispatching each task BY NAME under the
1943
+ * `bail` policy, including the W-c2 `agent` form behind a depth + cycle guard.
1944
+ *
1945
+ * @remarks
1946
+ * - **Composes, never re-implements.** Per-phase bounded concurrency is one
1947
+ * {@link createRunner} per phase (the substrate {@link RunnerInterface} over the workers
1948
+ * `Queue`); `bail` maps onto that Runner's fail-fast vs settle-all; the run-level abort /
1949
+ * timeout / budget fold through {@link createAbort} / {@link createTimeout} +
1950
+ * `AbortSignal.any` (exactly as the agent runtime folds its bounds); pacing is the shipped
1951
+ * {@link SchedulerInterface}. The runner writes ZERO concurrency / retry / abort logic of
1952
+ * its own — it only sequences phases, dispatches a task, and drives the live entity.
1953
+ * - **Phases sequential, tasks concurrent.** `#execute` awaits the phases in order (phase
1954
+ * N+1 starts only once phase N has fully settled). Within a phase, ALL its tasks are the
1955
+ * one Runner's `inputs`, run at `concurrency` = the phase's
1956
+ * {@link PhaseDefinition.concurrency} (default {@link DEFAULT_PHASE_CONCURRENCY}).
1957
+ * - **Dispatch by name.** `#dispatch` branches on the task's
1958
+ * {@link import('./types.js').TaskForm} (read from the `definition`, correlated by `id`):
1959
+ * `function` → the {@link WorkflowFunctions} registry, `tool` → the
1960
+ * {@link ToolManagerInterface}, `agent` → the {@link WorkflowAgents} resolver (W-c2). A
1961
+ * handler that is NOT found (an unregistered name for ANY form) AUTO-COMPLETES — the
1962
+ * ROADMAP no-handler rule.
1963
+ * - **`agent` form + depth/cycle guard (W-c2).** An `agent` task resolves its subagent via
1964
+ * `agents`, BINDS a depth/cycle-aware workflow tool onto the subagent's `context.tools`
1965
+ * (the propagation seam), folds the task's cancellation into the agent run (a workflow
1966
+ * cancel `abort`s the subagent), and drives it: success → `complete(result)`, throw →
1967
+ * `fail(error)`. Before running, the guard REJECTS the task into a typed `DEPTH`
1968
+ * {@link WorkflowError} (`fail`) when running it would push the nested chain past
1969
+ * {@link MAX_WORKFLOW_DEPTH}, OR when its target agent is already an ancestor (a cycle).
1970
+ * The rejected task never runs the agent.
1971
+ * - **`bail` → substrate.** Under `bail: true` (halt) a genuine task failure `fail`s the leaf
1972
+ * THEN re-throws, so the substrate Runner fail-fasts — it aborts the in-flight siblings
1973
+ * (their `controller.signal` fires; a mid-flight sibling `skip`s) and rejects the phase run;
1974
+ * `#execute` then `skip`s the remaining tasks / phases (the workflow derives `failed`).
1975
+ * Under `bail: false` (graceful) a failure `fail`s the leaf and RESOLVES (never throws), so
1976
+ * the Runner settles every unit (allSettled) and the run finishes (the workflow derives
1977
+ * `completed`, the failure recorded in the result tree).
1978
+ * - **Abort / Timeout / Budget fold.** `#execute` folds the run's external `signal`, a
1979
+ * {@link TimeoutInterface}, and the `@orkestrel/budget` package's `BudgetInterface`'s
1980
+ * `signal` into one `runSignal` (`AbortSignal.any`); a fire aborts the active phase's Runner
1981
+ * (cancelling every in-flight task) and HALTS the run — the remaining tasks / phases `skip`
1982
+ * and the workflow is force-`stop`ped (settles `stopped`). Each task's
1983
+ * {@link TaskController} signal `AbortSignal.any`-combines the substrate per-unit signal with
1984
+ * `runSignal`, so a handler observes either cause directly.
1985
+ * - **Re-entrant-safe.** No shared per-run mutable field: the active-Runner holder is LOCAL to
1986
+ * each `#execute`, so a nested `execute` (the bound workflow tool re-entering this instance
1987
+ * while the outer run is suspended on an `agent` task) cannot clobber the outer run's state.
1988
+ */
1989
+ var WorkflowRunner = class {
1990
+ #functions;
1991
+ #tools;
1992
+ #agents;
1993
+ #scheduler;
1994
+ #workflowTool;
1995
+ constructor(functions, tools, agents, scheduler, workflowTool) {
1996
+ this.#functions = functions;
1997
+ this.#tools = tools;
1998
+ this.#agents = agents;
1999
+ this.#scheduler = scheduler;
2000
+ this.#workflowTool = workflowTool;
2001
+ }
2002
+ execute(definition, options) {
2003
+ const workflow = new Workflow(definitionToSnapshot(definition, options?.bail ?? definition.bail ?? false), options);
2004
+ const depth = options?.depth ?? 0;
2005
+ const ancestry = [...options?.ancestry ?? [], workflowTag(definition.id)];
2006
+ return this.#execute(workflow, definition, options, depth, ancestry);
2007
+ }
2008
+ async #execute(workflow, definition, options, depth, ancestry) {
2009
+ const ms = options?.timeout;
2010
+ const timeout = ms !== void 0 && ms > 0 ? (0, _orkestrel_timeout.createTimeout)({ ms }) : void 0;
2011
+ timeout?.start();
2012
+ options?.budget?.start();
2013
+ const runSignal = this.#fold(options, timeout);
2014
+ const holder = { runner: void 0 };
2015
+ const onCancel = () => holder.runner?.abort(runSignal?.reason);
2016
+ if (runSignal !== void 0) if (runSignal.aborted) onCancel();
2017
+ else runSignal.addEventListener("abort", onCancel, { once: true });
2018
+ try {
2019
+ const phases = workflow.phases.phases();
2020
+ for (let index = 0; index < phases.length; index += 1) {
2021
+ const phase = phases[index];
2022
+ if (phase === void 0) continue;
2023
+ if (this.#cancelled(runSignal) || this.#halted(workflow)) {
2024
+ this.#skipFrom(phases, index);
2025
+ break;
2026
+ }
2027
+ if (await this.#runPhase(workflow, phase, this.#phaseOf(definition, phase.id), runSignal, holder, depth, ancestry)) {
2028
+ this.#skipFrom(phases, index + 1);
2029
+ break;
2030
+ }
2031
+ if (index < phases.length - 1 && !this.#cancelled(runSignal)) try {
2032
+ await this.#scheduler.yield(runSignal === void 0 ? void 0 : { signal: runSignal });
2033
+ } catch {}
2034
+ }
2035
+ if (this.#cancelled(runSignal)) {
2036
+ this.#skipFrom(workflow.phases.phases(), 0);
2037
+ if (this.#stoppable(workflow)) workflow.stop();
2038
+ } else if (this.#completable(workflow)) workflow.complete();
2039
+ return {
2040
+ workflow,
2041
+ status: workflow.status,
2042
+ results: workflow.results()
2043
+ };
2044
+ } finally {
2045
+ timeout?.clear();
2046
+ runSignal?.removeEventListener("abort", onCancel);
2047
+ }
2048
+ }
2049
+ async #runPhase(workflow, phase, definition, runSignal, holder, depth, ancestry) {
2050
+ const tasks = phase.tasks.tasks();
2051
+ if (tasks.length === 0) return false;
2052
+ const bail = definition?.bail ?? workflow.bail;
2053
+ const concurrency = definition?.concurrency !== void 0 && definition.concurrency > 0 ? definition.concurrency : DEFAULT_PHASE_CONCURRENCY;
2054
+ const attempts = /* @__PURE__ */ new Map();
2055
+ const runner = new Runner({
2056
+ concurrency,
2057
+ entries: (task) => {
2058
+ const def = this.#taskOf(definition, task.id);
2059
+ return {
2060
+ retries: def?.retries,
2061
+ timeout: def?.timeout
2062
+ };
2063
+ },
2064
+ handler: (controller) => this.#runTask(workflow, controller.input, this.#taskOf(definition, controller.input.id), controller, runSignal, bail, attempts, depth, ancestry)
2065
+ });
2066
+ holder.runner = runner;
2067
+ try {
2068
+ await runner.execute(tasks);
2069
+ return false;
2070
+ } catch {
2071
+ return !this.#cancelled(runSignal);
2072
+ } finally {
2073
+ runner.destroy();
2074
+ holder.runner = void 0;
2075
+ }
2076
+ }
2077
+ async #runTask(workflow, task, definition, controller, runSignal, bail, attempts, depth, ancestry) {
2078
+ const signal = this.#taskSignal(controller.signal, runSignal);
2079
+ const attempt = (attempts.get(task.id) ?? 0) + 1;
2080
+ attempts.set(task.id, attempt);
2081
+ const last = attempt > Math.max(0, definition?.retries ?? 0);
2082
+ if (task.status === "pending") task.start();
2083
+ if (this.#skipping(controller, runSignal)) {
2084
+ this.#skip(task);
2085
+ return;
2086
+ }
2087
+ const handle = new TaskController(signal, task.snapshot().metadata, task.context, () => workflow.results());
2088
+ try {
2089
+ const value = await this.#dispatch(definition, handle, depth, ancestry);
2090
+ if (task.status !== "running" || this.#skipping(controller, runSignal)) {
2091
+ this.#skip(task);
2092
+ return;
2093
+ }
2094
+ if (signal.aborted) {
2095
+ this.#timedOut(task, last);
2096
+ return;
2097
+ }
2098
+ task.complete(value);
2099
+ } catch (error) {
2100
+ if (task.status !== "running" || this.#skipping(controller, runSignal)) {
2101
+ this.#skip(task);
2102
+ return;
2103
+ }
2104
+ if (signal.aborted) {
2105
+ this.#timedOut(task, last);
2106
+ return;
2107
+ }
2108
+ if (!last) throw error;
2109
+ task.fail(error);
2110
+ if (bail) throw error;
2111
+ }
2112
+ }
2113
+ #timedOut(task, last) {
2114
+ if (!last) return;
2115
+ task.fail(/* @__PURE__ */ new Error(`task '${task.id}' timed out`));
2116
+ }
2117
+ async #dispatch(definition, controller, depth, ancestry) {
2118
+ const form = definition?.run;
2119
+ if (form !== void 0 && isFunctionTask(form)) {
2120
+ const handler = this.#functions[form.name];
2121
+ if (handler !== void 0) return handler(controller);
2122
+ return;
2123
+ }
2124
+ if (form !== void 0 && isToolTask(form)) {
2125
+ const tools = this.#tools;
2126
+ if (tools === void 0) return void 0;
2127
+ if (tools.tool(form.name) === void 0) return void 0;
2128
+ const result = await tools.execute({
2129
+ id: controller.task.id,
2130
+ name: form.name,
2131
+ arguments: controller.input
2132
+ });
2133
+ if (result.error !== void 0) throw new Error(result.error);
2134
+ return result.value;
2135
+ }
2136
+ if (form !== void 0 && isAgentTask(form)) return this.#dispatchAgent(form.name, controller, depth, ancestry);
2137
+ }
2138
+ async #dispatchAgent(name, controller, depth, ancestry) {
2139
+ const resolve = this.#agents;
2140
+ if (resolve === void 0) return void 0;
2141
+ const agent = resolve(name);
2142
+ if (agent === void 0) return void 0;
2143
+ if (depth + 1 > 8) throw new WorkflowError("DEPTH", `agent '${name}' exceeds max workflow depth`, {
2144
+ agent: name,
2145
+ depth,
2146
+ max: 8
2147
+ });
2148
+ const tag = agentTag(name);
2149
+ if (ancestry.includes(tag)) throw new WorkflowError("DEPTH", `agent '${name}' is already an ancestor (cycle)`, {
2150
+ agent: name,
2151
+ ancestry: [...ancestry]
2152
+ });
2153
+ this.#bindWorkflowTool(agent, depth, [...ancestry, tag], controller.task.phase.workflow.id);
2154
+ return this.#runAgent(agent, controller.signal);
2155
+ }
2156
+ #bindWorkflowTool(agent, depth, ancestry, workflowId) {
2157
+ const bind = this.#workflowTool;
2158
+ if (bind === void 0) return;
2159
+ const wrapped = {
2160
+ id: workflowId,
2161
+ name: workflowId,
2162
+ phases: []
2163
+ };
2164
+ agent.context.tools.add(bind(wrapped, this, {
2165
+ depth,
2166
+ ancestry
2167
+ }));
2168
+ }
2169
+ async #runAgent(agent, signal) {
2170
+ const onAbort = () => agent.abort(signal.reason);
2171
+ if (signal.aborted) agent.abort(signal.reason);
2172
+ else signal.addEventListener("abort", onAbort, { once: true });
2173
+ try {
2174
+ return await agent.generate();
2175
+ } finally {
2176
+ signal.removeEventListener("abort", onAbort);
2177
+ }
2178
+ }
2179
+ #taskSignal(unitSignal, runSignal) {
2180
+ if (runSignal === void 0) return unitSignal;
2181
+ return (0, _orkestrel_abort.createAbort)({ signal: AbortSignal.any([unitSignal, runSignal]) }).signal;
2182
+ }
2183
+ #fold(options, timeout) {
2184
+ const signals = [];
2185
+ if (options?.signal !== void 0) signals.push(options.signal);
2186
+ if (timeout !== void 0) signals.push(timeout.signal);
2187
+ if (options?.budget !== void 0) signals.push(options.budget.signal);
2188
+ if (signals.length === 0) return void 0;
2189
+ if (signals.length === 1) return signals[0];
2190
+ return AbortSignal.any(signals);
2191
+ }
2192
+ #skipFrom(phases, index) {
2193
+ for (let cursor = index; cursor < phases.length; cursor += 1) {
2194
+ const phase = phases[cursor];
2195
+ if (phase === void 0) continue;
2196
+ for (const task of phase.tasks.tasks()) this.#skip(task);
2197
+ }
2198
+ }
2199
+ #skip(task) {
2200
+ if (task.status === "pending" || task.status === "running") task.skip();
2201
+ }
2202
+ #skipping(controller, runSignal) {
2203
+ return controller.aborted || runSignal?.aborted === true;
2204
+ }
2205
+ #cancelled(runSignal) {
2206
+ return runSignal?.aborted === true;
2207
+ }
2208
+ #halted(workflow) {
2209
+ const status = workflow.status;
2210
+ return status === "failed" || status === "skipped" || status === "stopped";
2211
+ }
2212
+ #stoppable(workflow) {
2213
+ const status = workflow.status;
2214
+ return status !== "failed" && status !== "stopped";
2215
+ }
2216
+ #completable(workflow) {
2217
+ return workflow.status === "pending";
2218
+ }
2219
+ #phaseOf(definition, id) {
2220
+ return definition.phases.find((phase) => phase.id === id);
2221
+ }
2222
+ #taskOf(phase, id) {
2223
+ return phase?.tasks.find((task) => task.id === id);
2224
+ }
2225
+ };
2226
+ //#endregion
2227
+ //#region src/core/factories.ts
2228
+ /**
2229
+ * Compile the workflow definition contract — the JSON Schema, guard, parser, and
2230
+ * seeded generator for a {@link WorkflowDefinition}, all derived from one shape and
2231
+ * kept in lockstep.
2232
+ *
2233
+ * @remarks
2234
+ * The returned {@link ContractInterface}:
2235
+ * - `schema` — the emitted JSON Schema for a workflow definition.
2236
+ * - `is` — a total guard that narrows `unknown` to a valid {@link WorkflowDefinition}
2237
+ * (malformed input returns `false`, never throws).
2238
+ * - `parse` — coerces `unknown` to a {@link WorkflowDefinition}, or `undefined` when
2239
+ * it does not match.
2240
+ * - `generate` — produces a deterministic valid {@link WorkflowDefinition} from an
2241
+ * optional seeded random source.
2242
+ *
2243
+ * @returns The compiled {@link WorkflowDefinition} contract
2244
+ *
2245
+ * @example
2246
+ * ```ts
2247
+ * import { createWorkflowContract } from '@src/core'
2248
+ *
2249
+ * const contract = createWorkflowContract()
2250
+ * const definition = contract.generate() // a valid WorkflowDefinition
2251
+ * contract.is(definition) // true
2252
+ * contract.parse({ id: '', phases: [] }) // undefined (malformed)
2253
+ * ```
2254
+ */
2255
+ function createWorkflowContract() {
2256
+ const contract = (0, _orkestrel_contract.createContract)(workflowShape);
2257
+ return {
2258
+ schema: contract.schema,
2259
+ is: contract.is,
2260
+ generate: (random) => contract.generate(random),
2261
+ parse: (value) => contract.parse(value)
2262
+ };
2263
+ }
2264
+ /**
2265
+ * Compile the LENIENT workflow DRAFT contract — identical to
2266
+ * {@link createWorkflowContract} EXCEPT `id` and `name` are OPTIONAL at all three levels
2267
+ * (workflow / phase / task), so a small model can omit the six identity strings.
2268
+ *
2269
+ * @remarks
2270
+ * The widened authoring surface {@link createWorkflowTool} parses an authored blob through
2271
+ * before {@link import('./helpers.js').completeDraft} fills the missing ids/names. It does
2272
+ * NOT relax the canonical contract — {@link createWorkflowContract} stays byte-for-byte
2273
+ * unchanged and STRICT, and the completed draft is re-validated against THAT strict gate
2274
+ * before running (soundness preserved). A PROVIDED `id` / `name` still carries `minLength: 1`,
2275
+ * so an explicitly-empty `id: ''` is REJECTED (parses to `undefined`), never auto-filled —
2276
+ * keeping "garbage" distinct from "omitted". `run` stays required.
2277
+ *
2278
+ * @returns The compiled {@link WorkflowDraft} contract
2279
+ *
2280
+ * @example
2281
+ * ```ts
2282
+ * import { createWorkflowDraftContract, completeDraft } from '@src/core'
2283
+ *
2284
+ * const draft = createWorkflowDraftContract()
2285
+ * const parsed = draft.parse({ phases: [{ tasks: [{ run: { via: 'function', name: 'f' } }] }] })
2286
+ * const definition = parsed && completeDraft(parsed) // ids/names filled positionally
2287
+ * draft.parse({ id: '', phases: [] }) // undefined — an explicit empty id is rejected
2288
+ * ```
2289
+ */
2290
+ function createWorkflowDraftContract() {
2291
+ const contract = (0, _orkestrel_contract.createContract)(workflowDraftShape);
2292
+ return {
2293
+ schema: contract.schema,
2294
+ is: contract.is,
2295
+ generate: (random) => contract.generate(random),
2296
+ parse: (value) => contract.parse(value)
2297
+ };
2298
+ }
2299
+ /**
2300
+ * Build the live W-b entity tree from a {@link WorkflowDefinition} — the whole
2301
+ * {@link WorkflowInterface} → {@link import('./types.js').PhaseInterface} →
2302
+ * {@link import('./types.js').TaskInterface} tree, each level wired with its lineage
2303
+ * context, its emitter, and the cascade.
2304
+ *
2305
+ * @remarks
2306
+ * The definition is the DECLARATIVE blueprint; this seeds an initial all-`pending`
2307
+ * {@link WorkflowSnapshot} from it ({@link definitionToSnapshot}) and constructs the live
2308
+ * tree over that one path. The `bail` failure policy resolves to `options.bail`, else the
2309
+ * definition's `bail`, else the graceful {@link import('./constants.js').DEFAULT_BAIL}; it
2310
+ * feeds {@link import('./helpers.js').deriveWorkflowStatus}. Per-phase / per-task initial
2311
+ * listeners + metadata travel through `options.phases[id].on` /
2312
+ * `options.phases[id].tasks[id]` (the AGENTS §8 nested-by-id bag). The W-b tree is the
2313
+ * state machine ONLY — it does not execute tasks (W-c drives the transitions).
2314
+ *
2315
+ * @param definition - The workflow definition to bring to life
2316
+ * @param options - Runtime options (initial listeners, `bail` override, per-node options)
2317
+ * @returns The live {@link WorkflowInterface} root
2318
+ *
2319
+ * @example
2320
+ * ```ts
2321
+ * import { createWorkflow } from '@src/core'
2322
+ *
2323
+ * const workflow = createWorkflow(definition, { on: { complete: () => done() } })
2324
+ * const phase = workflow.phase('phase-build')
2325
+ * phase?.task('task-compile')?.start() // pending → running (cascades up)
2326
+ * ```
2327
+ */
2328
+ function createWorkflow(definition, options) {
2329
+ return new Workflow(definitionToSnapshot(definition, options?.bail ?? definition.bail ?? false), options);
2330
+ }
2331
+ /**
2332
+ * Rebuild an equivalent live W-b entity tree from a {@link WorkflowSnapshot} — the
2333
+ * inverse of {@link WorkflowInterface.snapshot}, restoring structure + each node's status
2334
+ * + recorded results + positional order + the persisted `#override`.
2335
+ *
2336
+ * @remarks
2337
+ * Round-trip fidelity is paramount: a `snapshot()` → `restoreWorkflow()` reproduces the
2338
+ * same status at every node (each `#override` restored DIRECTLY from the snapshot's own
2339
+ * `override` field, not guessed from a status divergence), the same recorded
2340
+ * {@link import('./types.js').TaskResult}s, and the same positional order (an interior
2341
+ * `skip` / `remove` survives). The snapshot is SELF-CONTAINED — it persists the `bail`
2342
+ * policy it ran under, so the restore re-derives status IDENTICALLY without a silent
2343
+ * default; the snapshot's `bail` is the source of truth, while an explicit `options.bail`
2344
+ * still wins when supplied (to deliberately re-run under a different policy). A structurally
2345
+ * invalid snapshot (a status — or override — outside the lifecycle vocabulary, or a
2346
+ * non-boolean `bail`) throws a `RESTORE` {@link WorkflowError}.
2347
+ *
2348
+ * @param snapshot - The snapshot to restore (carries its own `bail` + `override`)
2349
+ * @param options - Runtime options (initial listeners, an optional `bail` override, per-node options)
2350
+ * @returns The restored live {@link WorkflowInterface} root
2351
+ *
2352
+ * @example
2353
+ * ```ts
2354
+ * import { restoreWorkflow } from '@src/core'
2355
+ *
2356
+ * const restored = restoreWorkflow(workflow.snapshot()) // bail comes from the snapshot
2357
+ * restored.status === workflow.status // true
2358
+ * ```
2359
+ */
2360
+ function restoreWorkflow(snapshot, options) {
2361
+ assertSnapshot(snapshot);
2362
+ return new Workflow(snapshot, options);
2363
+ }
2364
+ /**
2365
+ * Assert that a {@link WorkflowSnapshot} carries a `boolean` `bail` — at the workflow tier AND
2366
+ * on every phase — and that its every node's status (and its `override`, when present) is drawn
2367
+ * from the lifecycle vocabulary, throwing a `RESTORE` {@link WorkflowError} otherwise.
2368
+ *
2369
+ * @remarks
2370
+ * The boundary-narrowing guard (AGENTS §14) for {@link restoreWorkflow}: a snapshot is
2371
+ * untrusted JSON, so a status (or an override) outside
2372
+ * {@link import('./constants.js').WORKFLOW_STATUSES} /
2373
+ * {@link import('./constants.js').PHASE_STATUSES} / {@link import('./constants.js').TASK_STATUSES},
2374
+ * or a non-boolean `bail` (the workflow's OR any phase's — both are REQUIRED persisted policy),
2375
+ * is rejected loudly (naming the offending node) rather than silently producing a broken tree.
2376
+ * The `override` is optional, so it is only checked WHEN present. Structural shape beyond these
2377
+ * fields is the contract's concern; this guards exactly the fields the live state machine reads back.
2378
+ *
2379
+ * @param snapshot - The snapshot to validate
2380
+ */
2381
+ function assertSnapshot(snapshot) {
2382
+ if (typeof snapshot.bail !== "boolean") throw new WorkflowError("RESTORE", `workflow '${snapshot.id}' has a non-boolean bail`, {
2383
+ workflow: snapshot.id,
2384
+ bail: snapshot.bail
2385
+ });
2386
+ if (!WORKFLOW_STATUSES.includes(snapshot.status)) throw new WorkflowError("RESTORE", `workflow '${snapshot.id}' has an invalid status`, {
2387
+ workflow: snapshot.id,
2388
+ status: snapshot.status
2389
+ });
2390
+ if (snapshot.override !== void 0 && !WORKFLOW_STATUSES.includes(snapshot.override)) throw new WorkflowError("RESTORE", `workflow '${snapshot.id}' has an invalid override`, {
2391
+ workflow: snapshot.id,
2392
+ override: snapshot.override
2393
+ });
2394
+ for (const phase of snapshot.phases) {
2395
+ if (typeof phase.bail !== "boolean") throw new WorkflowError("RESTORE", `phase '${phase.id}' has a non-boolean bail`, {
2396
+ phase: phase.id,
2397
+ bail: phase.bail
2398
+ });
2399
+ if (!PHASE_STATUSES.includes(phase.status)) throw new WorkflowError("RESTORE", `phase '${phase.id}' has an invalid status`, {
2400
+ phase: phase.id,
2401
+ status: phase.status
2402
+ });
2403
+ if (phase.override !== void 0 && !PHASE_STATUSES.includes(phase.override)) throw new WorkflowError("RESTORE", `phase '${phase.id}' has an invalid override`, {
2404
+ phase: phase.id,
2405
+ override: phase.override
2406
+ });
2407
+ for (const task of phase.tasks) if (!TASK_STATUSES.includes(task.status)) throw new WorkflowError("RESTORE", `task '${task.id}' has an invalid status`, {
2408
+ task: task.id,
2409
+ status: task.status
2410
+ });
2411
+ }
2412
+ }
2413
+ /**
2414
+ * Create the in-memory durable {@link WorkflowStoreInterface} — a process-lifetime
2415
+ * {@link MemoryWorkflowStore} persisting {@link WorkflowSnapshot}s by workflow id, the DEFAULT
2416
+ * backend behind the W-d persistence seam.
2417
+ *
2418
+ * @remarks
2419
+ * The snapshot analogue of the server package's `createMemorySessionStore`
2420
+ * (and the {@link createMemoryQueueStore} family), but LEANER — there is no idle-TTL, so no
2421
+ * options bag (AGENTS §21 minimal): a persisted run-state lives until an explicit `delete`. This is
2422
+ * the zero-plumbing DEFAULT (a plain `Map`); its driver-pluggable twin is
2423
+ * {@link createDatabaseWorkflowStore} (the snapshot as one opaque JSON column over a `databases`
2424
+ * table) — for a DURABLE store (run-state surviving a restart) pass it a JSON / SQLite / IndexedDB
2425
+ * driver, and it swaps in WITHOUT touching the runner or the entity tree. Restore stays a caller
2426
+ * concern: read a snapshot back and rebuild the live tree with {@link restoreWorkflow}.
2427
+ *
2428
+ * @returns A memory-backed {@link WorkflowStoreInterface}
2429
+ *
2430
+ * @example
2431
+ * ```ts
2432
+ * import { createMemoryWorkflowStore, createWorkflow, restoreWorkflow } from '@src/core'
2433
+ *
2434
+ * const store = createMemoryWorkflowStore()
2435
+ * const workflow = createWorkflow(definition)
2436
+ * await store.set(workflow.snapshot()) // persist the run state
2437
+ * const snapshot = await store.get(definition.id)
2438
+ * const restored = snapshot && restoreWorkflow(snapshot) // an identical live tree
2439
+ * ```
2440
+ */
2441
+ function createMemoryWorkflowStore() {
2442
+ return new MemoryWorkflowStore();
2443
+ }
2444
+ /**
2445
+ * Create a {@link DatabaseWorkflowStore} over any {@link DriverInterface} — the durable,
2446
+ * driver-pluggable backing for the W-d persistence seam, the opt-in twin of
2447
+ * {@link createMemoryWorkflowStore}.
2448
+ *
2449
+ * @remarks
2450
+ * Builds a one-table database (`snapshots`, keyed by `id`) over the supplied driver, the snapshot
2451
+ * held as ONE OPAQUE JSON COLUMN — the column map is `{ id; snapshot }` where `snapshot` is a
2452
+ * `rawShape` (a JSON blob), exactly as {@link createDatabaseQueueStore} stores its `input`. The
2453
+ * snapshot is already a COMPLETE, self-contained, pure-JSON payload, so storing it whole is lossless
2454
+ * AND keeps the row type FLAT — a structured multi-column snapshot table would force the contract to
2455
+ * `Infer` the deeply-nested snapshot shape (workflow → phases → tasks → results) and trip TS2589;
2456
+ * the opaque column sidesteps it (the column reads back as `unknown`, narrowed on `get` by
2457
+ * {@link import('./helpers.js').isWorkflowSnapshot}). The `driver` DEFAULTS to
2458
+ * {@link createMemoryDriver}, so the store ALSO works in memory out of the box; pass a server
2459
+ * `createJSONDriver` / `createSQLiteDriver` (or a browser IndexedDB driver) for a persistent one —
2460
+ * the durability is the driver's job, the store engine is shared. It swaps in behind
2461
+ * {@link WorkflowStoreInterface} WITHOUT touching the runner or the entity tree.
2462
+ *
2463
+ * @param driver - The storage backend the snapshots persist to (defaults to {@link createMemoryDriver})
2464
+ * @returns A {@link WorkflowStoreInterface} over the driver
2465
+ *
2466
+ * @example
2467
+ * ```ts
2468
+ * import { createDatabaseWorkflowStore, createMemoryDriver, createWorkflow, restoreWorkflow } from '@src/core'
2469
+ *
2470
+ * const store = createDatabaseWorkflowStore(createMemoryDriver()) // a durable driver swaps in here
2471
+ * const workflow = createWorkflow(definition)
2472
+ * await store.set(workflow.snapshot()) // persist the run state (one JSON column)
2473
+ * const snapshot = await store.get(definition.id)
2474
+ * const restored = snapshot && restoreWorkflow(snapshot) // an identical live tree
2475
+ * ```
2476
+ */
2477
+ function createDatabaseWorkflowStore(driver = (0, _orkestrel_database.createMemoryDriver)()) {
2478
+ return new DatabaseWorkflowStore((0, _orkestrel_database.createDatabase)({
2479
+ driver,
2480
+ tables: { snapshots: {
2481
+ id: (0, _orkestrel_contract.stringShape)(),
2482
+ snapshot: (0, _orkestrel_contract.rawShape)({})
2483
+ } }
2484
+ }).table("snapshots"));
2485
+ }
2486
+ /**
2487
+ * Create a workflow runner — a {@link WorkflowRunnerInterface} that EXECUTES a live W-b
2488
+ * workflow tree by COMPOSING the shipped substrate: phases sequential, tasks concurrent,
2489
+ * each task dispatched BY NAME under the workflow's `bail` policy.
2490
+ *
2491
+ * @remarks
2492
+ * The runner is THIN — it re-implements no concurrency / retry / abort logic. Per-phase
2493
+ * bounded concurrency is one {@link createRunner} per phase;
2494
+ * `bail` maps onto that Runner's fail-fast (`true` — the first failure aborts the in-flight
2495
+ * siblings + skips the rest) vs settle-all (`false` — failures are recorded, the run
2496
+ * finishes); the run-level abort / timeout / budget ({@link import('./types.js').WorkflowRunOptions})
2497
+ * fold through `AbortSignal.any` (the agent runtime's pattern); pacing is the shipped
2498
+ * scheduler. `execute(definition, options?)` BUILDS the live tree from the definition itself
2499
+ * (via {@link createWorkflow} — one source of truth, returned in `WorkflowResult.workflow`),
2500
+ * drives the live entity (`start` → `complete` / `fail`), and resolves a
2501
+ * {@link import('./types.js').WorkflowResult}.
2502
+ *
2503
+ * A task is dispatched on its {@link import('./types.js').TaskForm}: `function` → the
2504
+ * `functions` registry, `tool` → the `tools` {@link ToolManagerInterface}, `agent` → the
2505
+ * `agents` {@link import('./types.js').WorkflowAgents} resolver (W-c2), behind a depth + cycle
2506
+ * guard. A task whose handler is NOT found (an unregistered name for ANY form) AUTO-COMPLETES
2507
+ * (the ROADMAP no-handler rule).
2508
+ *
2509
+ * The runner is constructed with a reference to {@link createWorkflowTool} (the workflow-tool
2510
+ * binder) so it can BIND a depth/cycle-aware workflow tool onto a dispatched subagent's context
2511
+ * — the propagation seam — WITHOUT this module's classes importing its own `factories.ts` (the
2512
+ * factories→classes direction; the binder is injected as a value at construction).
2513
+ *
2514
+ * @param options - The behavior registries (`functions` / `tools` / `agents`) the runner
2515
+ * dispatches a task by name through, plus an optional pacing `scheduler` (default the shipped
2516
+ * cross-environment one). Omitting `functions` / `tools` / `agents` makes those task forms
2517
+ * auto-complete (no handler). See {@link WorkflowRunnerOptions}.
2518
+ * @returns A working {@link WorkflowRunnerInterface}
2519
+ *
2520
+ * @example
2521
+ * ```ts
2522
+ * import { createWorkflowRunner, createToolManager } from '@src/core'
2523
+ *
2524
+ * const tools = createToolManager()
2525
+ * const runner = createWorkflowRunner({
2526
+ * functions: { compile: async (controller) => `built ${controller.task.id}` },
2527
+ * tools,
2528
+ * })
2529
+ * const definition = { id: 'w', name: 'W', phases: [{ id: 'p', name: 'P', tasks: [
2530
+ * { id: 't', name: 'T', run: { via: 'function', name: 'compile' } },
2531
+ * ] }] }
2532
+ * const result = await runner.execute(definition) // builds + drives the tree
2533
+ * result.status // 'completed'
2534
+ * result.workflow.phase('p')?.task('t')?.status // 'completed'
2535
+ * ```
2536
+ */
2537
+ function createWorkflowRunner(options) {
2538
+ return new WorkflowRunner(options?.functions ?? {}, options?.tools, options?.agents, options?.scheduler ?? createScheduler(), createWorkflowTool);
2539
+ }
2540
+ /**
2541
+ * Wrap a {@link WorkflowDefinition} as an LLM-callable {@link ToolInterface} — it ADVERTISES
2542
+ * the SIMPLE flat authoring shape (`{ name?, steps: [{ name, via? }] }`) as its `parameters` so
2543
+ * even a small model can author a complete tree, and its handler EXPANDS / COMPLETES the
2544
+ * authored blob, validates it against the STRICT contract, runs it through `runner`, and
2545
+ * returns the run SUMMARY (throwing a typed {@link WorkflowError} on failure).
2546
+ *
2547
+ * @remarks
2548
+ * A plain {@link ToolManagerInterface}-compatible tool (so `createMCPServer` / `createMCPRoutes`
2549
+ * expose it for free — nothing MCP is wired here). It is ALSO the propagation carrier the
2550
+ * {@link WorkflowRunner} binds onto a dispatched subagent (W-c2): because a tool handler receives
2551
+ * ONLY the model-supplied `args` (no ambient context, no signal), the run's depth + ancestry are
2552
+ * CLOSED OVER at bind time via {@link WorkflowToolOptions}, and the handler runs the nested
2553
+ * workflow at `depth + 1` with the extended ancestry.
2554
+ *
2555
+ * **Widened authoring surface (additive — the canonical contract + runner stay STRICT and
2556
+ * unchanged).** A 2B model reliably CALLS the tool but cannot reliably emit the full four-level
2557
+ * nested {@link WorkflowDefinition} (six required `id`/`name` strings, a nested tagged union,
2558
+ * all-or-nothing). So the tool ACCEPTS three authoring forms and converges them on the SAME
2559
+ * strict {@link createWorkflowContract} gate before running (soundness preserved):
2560
+ * - the FLAT shape `{ name?, steps: [{ name, via? }] }` — the ADVERTISED `parameters` (the simplest
2561
+ * form, {@link import('./helpers.js').expandSteps}'d into one one-task phase per step);
2562
+ * - a nested DRAFT with any `id`/`name` OMITTED — {@link createWorkflowDraftContract}-parsed then
2563
+ * {@link import('./helpers.js').completeDraft}'d (missing ids synthesized positionally);
2564
+ * - the full nested {@link WorkflowDefinition} — the advanced escape-hatch (documented in the
2565
+ * description), accepted as the draft super-set.
2566
+ *
2567
+ * The handler conforms to the universal tool-handler contract (AGENTS §14): it returns the PLAIN
2568
+ * run-summary VALUE on success and THROWS a typed {@link WorkflowError} on every failure path. It
2569
+ * does NOT build a {@link ToolResult} itself — the `@orkestrel/agent` package's `ToolManager`
2570
+ * performs the ONE canonical wrap (`{ id, name, value }` on a return; `{ id, name, error }` on a
2571
+ * throw, ISOLATED so nothing escapes the run), so the outcome appears EXACTLY ONCE, identically,
2572
+ * over BOTH the agent loop and MCP (a throw → MCP `isError: true`):
2573
+ * - **No authored args** (an empty `arguments`) ⇒ runs the WRAPPED `definition`.
2574
+ * - **A `steps` array** ⇒ the FLAT form: parse it, {@link import('./helpers.js').expandSteps} it.
2575
+ * - **Otherwise** ⇒ the nested form: {@link createWorkflowDraftContract}-parse it,
2576
+ * {@link import('./helpers.js').completeDraft} it.
2577
+ * - **Strict gate** ⇒ the expanded / completed result is validated against
2578
+ * {@link createWorkflowContract}.`is`; a blob that can't expand, or whose result fails the strict
2579
+ * gate (e.g. an explicit empty `id`, `concurrency: 0`) ⇒ THROW a `TOOL` {@link WorkflowError} (no run).
2580
+ * - **Over-deep / cyclic** ⇒ THROW a `DEPTH` {@link WorkflowError} when the nested run would exceed
2581
+ * {@link MAX_WORKFLOW_DEPTH}, or the target workflow id is already an ancestor (a cycle) — the
2582
+ * same `code` the agent-task guard raises.
2583
+ * - **Otherwise** ⇒ `runner.execute(target, { depth: depth + 1, ancestry: … })`, RETURNING the
2584
+ * plain summary of the terminal run (`{ status, count }`, via {@link workflowToolSummary}).
2585
+ *
2586
+ * @param definition - The workflow the tool runs when called with no authored args
2587
+ * @param runner - The {@link WorkflowRunnerInterface} that executes the (nested) workflow
2588
+ * @param options - The depth + ancestry to run the nested workflow under (see
2589
+ * {@link WorkflowToolOptions}); omitted ⇒ depth `0` / empty ancestry (a top-level wrap)
2590
+ * @returns A {@link ToolInterface} (named {@link import('./constants.js').WORKFLOW_TOOL_NAME})
2591
+ * whose `parameters` advertise the FLAT authoring schema (the nested form stays accepted)
2592
+ *
2593
+ * @example
2594
+ * ```ts
2595
+ * import { createWorkflowRunner, createWorkflowTool, createToolManager } from '@src/core'
2596
+ *
2597
+ * const runner = createWorkflowRunner()
2598
+ * const tool = createWorkflowTool(definition, runner)
2599
+ * const tools = createToolManager()
2600
+ * tools.add(tool) // a model can now author + run a workflow in one call
2601
+ * ```
2602
+ */
2603
+ function createWorkflowTool(definition, runner, options) {
2604
+ const strict = createWorkflowContract();
2605
+ const draft = createWorkflowDraftContract();
2606
+ const steps = (0, _orkestrel_contract.createContract)(workflowStepsShape);
2607
+ const depth = options?.depth ?? 0;
2608
+ const ancestry = options?.ancestry ?? [];
2609
+ return (0, _orkestrel_agent.createTool)({
2610
+ name: WORKFLOW_TOOL_NAME,
2611
+ description: WORKFLOW_TOOL_DESCRIPTION,
2612
+ parameters: (0, _orkestrel_contract.schemaToParameters)(steps.schema),
2613
+ execute: async (args) => {
2614
+ let target;
2615
+ if (Object.keys(args).length === 0) target = definition;
2616
+ else if (Array.isArray(args.steps)) {
2617
+ const flat = steps.parse(args);
2618
+ target = flat === void 0 ? void 0 : expandSteps(flat);
2619
+ } else {
2620
+ const parsed = draft.parse(args);
2621
+ target = parsed === void 0 ? void 0 : completeDraft(parsed);
2622
+ }
2623
+ if (target === void 0 || !strict.is(target)) throw new WorkflowError("TOOL", "malformed workflow definition", { workflow: definition.id });
2624
+ if (depth + 1 > 8) throw new WorkflowError("DEPTH", `nested workflow exceeds max depth 8`, {
2625
+ workflow: target.id,
2626
+ depth,
2627
+ max: 8
2628
+ });
2629
+ const tag = workflowTag(target.id);
2630
+ if (ancestry.includes(tag)) throw new WorkflowError("DEPTH", `workflow '${target.id}' is already an ancestor (cycle)`, {
2631
+ workflow: target.id,
2632
+ ancestry: [...ancestry]
2633
+ });
2634
+ return workflowToolSummary(await runner.execute(target, {
2635
+ depth: depth + 1,
2636
+ ancestry: [...ancestry, tag]
2637
+ }));
2638
+ }
2639
+ });
2640
+ }
2641
+ /**
2642
+ * Create the safe cross-environment cooperative-yield default — a
2643
+ * {@link SchedulerInterface} built on `setTimeout` / `clearTimeout` alone, so it
2644
+ * runs unchanged in both the browser and Node.
2645
+ *
2646
+ * @remarks
2647
+ * `yield()` gives the host a turn via a zero-delay macrotask (so pending I/O,
2648
+ * timers, and rendering actually run — a microtask would not); `delay(ms)` resumes
2649
+ * after at least `ms`. Pass `options.signal` to make a pending yield/delay reject
2650
+ * with the signal's `reason` on abort (with full timer/listener cleanup).
2651
+ * `options.priority` is accepted for contract compliance but treated uniformly by
2652
+ * this default — environment backends honour it.
2653
+ *
2654
+ * @returns A working {@link SchedulerInterface}
2655
+ *
2656
+ * @example
2657
+ * ```ts
2658
+ * import { createAbort, createScheduler } from '@src/core'
2659
+ *
2660
+ * const abort = createAbort()
2661
+ * const scheduler = createScheduler()
2662
+ *
2663
+ * // A cooperative loop: do a unit of work, then hand the host a turn.
2664
+ * while (!abort.signal.aborted) {
2665
+ * doSomeWork()
2666
+ * await scheduler.yield({ signal: abort.signal })
2667
+ * }
2668
+ * ```
2669
+ *
2670
+ * @example
2671
+ * ```ts
2672
+ * import { createScheduler } from '@src/core'
2673
+ *
2674
+ * // A backoff: wait a growing interval between retries.
2675
+ * const scheduler = createScheduler()
2676
+ * for (let attempt = 0; attempt < 5; attempt += 1) {
2677
+ * if (await tryOnce()) break
2678
+ * await scheduler.delay(2 ** attempt * 100)
2679
+ * }
2680
+ * ```
2681
+ */
2682
+ function createScheduler() {
2683
+ return new Scheduler();
2684
+ }
2685
+ /**
2686
+ * Create a thin generic orchestrator that drives declared units — and any they
2687
+ * `spawn` — through a bounded-concurrency queue, collecting their results in order.
2688
+ *
2689
+ * @remarks
2690
+ * The Runner composes the workers `Queue` for backpressure, FIFO ordering, bounded
2691
+ * concurrency, retries, and the per-attempt timeout — it adds only orchestration, not
2692
+ * a second concurrency engine. `execute(inputs)` runs the unit set ONCE (a second call
2693
+ * throws) and resolves the units' results in order: the declared inputs first, then
2694
+ * any `spawn`ed siblings in spawn order. Each unit's handler gets a `Controller` — its
2695
+ * `id` / `input`, a `signal` that fires on the unit's `abort`, a runner-level `abort`,
2696
+ * or the attempt's timeout, a promise-parked `wait()`, and `spawn(input)` to fan out
2697
+ * sibling units. The run is **fail-fast**: the first unit failure (after retries)
2698
+ * aborts every other unit and rejects `execute` with that error. **Observable (§13):** a
2699
+ * typed `emitter` surfaces `start` / `unit` / `spawn` / `settle` / `fail` / `finish` / `abort`.
2700
+ *
2701
+ * Because `spawn` is fire-and-track (the runner awaits the whole spawn closure via an
2702
+ * outstanding-unit count, not a one-time snapshot), a handler need NOT await its spawns
2703
+ * for them to run — and on a bounded runner it should NOT `await` a spawn inline (a
2704
+ * slot-holding handler awaiting its own spawn can deadlock); fan out and return instead.
2705
+ *
2706
+ * @typeParam TInput - The work input each unit carries
2707
+ * @typeParam TResult - The value a unit's handler resolves
2708
+ * @param options - The `handler` plus optional `concurrency` (default `1`), `retries`
2709
+ * (default `0`), and a default per-attempt `timeout` in milliseconds
2710
+ * @returns A working {@link RunnerInterface}
2711
+ *
2712
+ * @example
2713
+ * ```ts
2714
+ * import { createRunner } from '@src/core'
2715
+ *
2716
+ * // A handler that fans out one sibling per declared unit, then returns its own value.
2717
+ * const runner = createRunner<number, number>({
2718
+ * concurrency: 4,
2719
+ * handler: (controller) => {
2720
+ * if (controller.input < 10) controller.spawn(controller.input + 100) // fire-and-track
2721
+ * return controller.input
2722
+ * },
2723
+ * })
2724
+ *
2725
+ * const results = await runner.execute([1, 2, 3])
2726
+ * // [1, 2, 3, 101, 102, 103] — declared inputs first (in order), then spawns (in order)
2727
+ * ```
2728
+ */
2729
+ function createRunner(options) {
2730
+ return new Runner(options);
2731
+ }
2732
+ //#endregion
2733
+ exports.Controller = Controller;
2734
+ exports.DEFAULT_BAIL = DEFAULT_BAIL;
2735
+ exports.DEFAULT_PHASE_CONCURRENCY = DEFAULT_PHASE_CONCURRENCY;
2736
+ exports.DatabaseWorkflowStore = DatabaseWorkflowStore;
2737
+ exports.MAX_WORKFLOW_DEPTH = MAX_WORKFLOW_DEPTH;
2738
+ exports.MemoryWorkflowStore = MemoryWorkflowStore;
2739
+ exports.PHASE_STATUSES = PHASE_STATUSES;
2740
+ exports.Phase = Phase;
2741
+ exports.PhaseManager = PhaseManager;
2742
+ exports.Runner = Runner;
2743
+ exports.Scheduler = Scheduler;
2744
+ exports.TASK_STATUSES = TASK_STATUSES;
2745
+ exports.TASK_TRANSITIONS = TASK_TRANSITIONS;
2746
+ exports.TASK_VIAS = TASK_VIAS;
2747
+ exports.TERMINAL_TASK_STATUSES = TERMINAL_TASK_STATUSES;
2748
+ exports.Task = Task;
2749
+ exports.TaskController = TaskController;
2750
+ exports.TaskManager = TaskManager;
2751
+ exports.WORKFLOW_STATUSES = WORKFLOW_STATUSES;
2752
+ exports.WORKFLOW_TOOL_DESCRIPTION = WORKFLOW_TOOL_DESCRIPTION;
2753
+ exports.WORKFLOW_TOOL_FLAT_EXAMPLE = WORKFLOW_TOOL_FLAT_EXAMPLE;
2754
+ exports.WORKFLOW_TOOL_NAME = WORKFLOW_TOOL_NAME;
2755
+ exports.WORKFLOW_TOOL_NESTED_EXAMPLE = WORKFLOW_TOOL_NESTED_EXAMPLE;
2756
+ exports.Workflow = Workflow;
2757
+ exports.WorkflowError = WorkflowError;
2758
+ exports.WorkflowRunner = WorkflowRunner;
2759
+ exports.agentTag = agentTag;
2760
+ exports.assertSnapshot = assertSnapshot;
2761
+ exports.buildPhaseContext = buildPhaseContext;
2762
+ exports.buildTaskContext = buildTaskContext;
2763
+ exports.buildWorkflowContext = buildWorkflowContext;
2764
+ exports.canTransitionTask = canTransitionTask;
2765
+ exports.collectResults = collectResults;
2766
+ exports.completeDraft = completeDraft;
2767
+ exports.completePhaseDraft = completePhaseDraft;
2768
+ exports.completeTaskDraft = completeTaskDraft;
2769
+ exports.createDatabaseWorkflowStore = createDatabaseWorkflowStore;
2770
+ exports.createDeferred = createDeferred;
2771
+ exports.createMemoryWorkflowStore = createMemoryWorkflowStore;
2772
+ exports.createRunner = createRunner;
2773
+ exports.createScheduler = createScheduler;
2774
+ exports.createWorkflow = createWorkflow;
2775
+ exports.createWorkflowContract = createWorkflowContract;
2776
+ exports.createWorkflowDraftContract = createWorkflowDraftContract;
2777
+ exports.createWorkflowRunner = createWorkflowRunner;
2778
+ exports.createWorkflowTool = createWorkflowTool;
2779
+ exports.definitionToSnapshot = definitionToSnapshot;
2780
+ exports.derivePhaseStatus = derivePhaseStatus;
2781
+ exports.deriveWorkflowStatus = deriveWorkflowStatus;
2782
+ exports.expandSteps = expandSteps;
2783
+ exports.isAgentTask = isAgentTask;
2784
+ exports.isFunctionTask = isFunctionTask;
2785
+ exports.isTerminalStatus = isTerminalStatus;
2786
+ exports.isToolTask = isToolTask;
2787
+ exports.isWorkflowError = isWorkflowError;
2788
+ exports.isWorkflowSnapshot = isWorkflowSnapshot;
2789
+ exports.phaseDefinitionToSnapshot = phaseDefinitionToSnapshot;
2790
+ exports.phaseDraftShape = phaseDraftShape;
2791
+ exports.phaseShape = phaseShape;
2792
+ exports.restoreWorkflow = restoreWorkflow;
2793
+ exports.stepShape = stepShape;
2794
+ exports.stepToForm = stepToForm;
2795
+ exports.taskDefinitionToSnapshot = taskDefinitionToSnapshot;
2796
+ exports.taskDraftShape = taskDraftShape;
2797
+ exports.taskFormShape = taskFormShape;
2798
+ exports.taskShape = taskShape;
2799
+ exports.workflowDraftShape = workflowDraftShape;
2800
+ exports.workflowShape = workflowShape;
2801
+ exports.workflowStepsShape = workflowStepsShape;
2802
+ exports.workflowTag = workflowTag;
2803
+ exports.workflowToolSummary = workflowToolSummary;
2804
+
2805
+ //# sourceMappingURL=index.cjs.map