@orkestrel/workflow 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/src/core/index.cjs +125 -639
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +239 -622
- package/dist/src/core/index.d.ts +239 -622
- package/dist/src/core/index.js +125 -620
- package/dist/src/core/index.js.map +1 -1
- package/package.json +1 -2
package/dist/src/core/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { arrayShape, compileGuard, createContract, integerShape, isArray, isBoolean, isNumber, isRecord, isString, literalShape, objectShape, optionalShape, rawShape, schemaToParameters, stringShape } from "@orkestrel/contract";
|
|
1
|
+
import { arrayShape, compileGuard, createContract, integerShape, isArray, isBoolean, isNumber, isRecord, isString, literalShape, objectShape, optionalShape, rawShape, stringShape } from "@orkestrel/contract";
|
|
3
2
|
import { createDatabase, createMemoryDriver } from "@orkestrel/database";
|
|
4
3
|
import { createAbort } from "@orkestrel/abort";
|
|
5
4
|
import { Emitter } from "@orkestrel/emitter";
|
|
@@ -178,102 +177,6 @@ var TASK_TRANSITIONS = Object.freeze({
|
|
|
178
177
|
* phase may still DECLARE a larger explicit `concurrency` and pays that allocation knowingly.
|
|
179
178
|
*/
|
|
180
179
|
var DEFAULT_PHASE_CONCURRENCY = 1024;
|
|
181
|
-
/**
|
|
182
|
-
* The maximum nesting depth a workflow → agent → workflow chain may reach — the bound
|
|
183
|
-
* the {@link import('./factories.js').createAgentFunction} and
|
|
184
|
-
* {@link import('./factories.js').createWorkflowTool} adapters' depth/cycle guards enforce.
|
|
185
|
-
*
|
|
186
|
-
* @remarks
|
|
187
|
-
* The limit lives in ONE place. An {@link import('./factories.js').createAgentFunction}-wrapped
|
|
188
|
-
* agent running at this depth can no longer author + run a NESTED workflow through its bound
|
|
189
|
-
* workflow tool (that would be depth `MAX_WORKFLOW_DEPTH + 1`), so the over-deep invocation is
|
|
190
|
-
* REJECTED (a typed `DEPTH` {@link import('./errors.js').WorkflowError} throw). The chain
|
|
191
|
-
* therefore nests workflows down to this depth, and the nested run at
|
|
192
|
-
* depth `MAX_WORKFLOW_DEPTH` fails.
|
|
193
|
-
*/
|
|
194
|
-
var MAX_WORKFLOW_DEPTH = 8;
|
|
195
|
-
/**
|
|
196
|
-
* The name under which {@link import('./factories.js').createAgentFunction} BINDS the
|
|
197
|
-
* depth/cycle-aware workflow tool onto a wrapped agent's `context.tools` (`AgentContextInterface`,
|
|
198
|
-
* `@orkestrel/agent`).
|
|
199
|
-
*
|
|
200
|
-
* @remarks
|
|
201
|
-
* The propagation seam's well-known key: when its `runner` option is supplied, the adapter adds a
|
|
202
|
-
* {@link import('./factories.js').createWorkflowTool}-built tool under this name to the
|
|
203
|
-
* agent's `context.tools`, so it can author + run a NESTED workflow (bounded by
|
|
204
|
-
* {@link MAX_WORKFLOW_DEPTH}). An agent that wants to fan out into a workflow calls this tool by
|
|
205
|
-
* this name; the bound handler runs the nested workflow at depth + 1.
|
|
206
|
-
*/
|
|
207
|
-
var WORKFLOW_TOOL_NAME = "workflow";
|
|
208
|
-
/**
|
|
209
|
-
* A complete FLAT authoring example — the PRIMARY way a small model authors a workflow
|
|
210
|
-
* through {@link import('./factories.js').createWorkflowTool}: `{ name, steps: [{ name }] }`.
|
|
211
|
-
*
|
|
212
|
-
* @remarks
|
|
213
|
-
* Each step becomes a one-task phase, in order; a step's `name` is a REGISTERED behavior name
|
|
214
|
-
* (not a label) — the registry key its task's `run` resolves against. The tool expands this
|
|
215
|
-
* ({@link import('./helpers.js').expandSteps}) into a valid {@link WorkflowDefinition}. It
|
|
216
|
-
* is embedded VERBATIM in {@link WORKFLOW_TOOL_DESCRIPTION} and guarded by a parity test
|
|
217
|
-
* (it must expand to a tree the STRICT contract accepts), so the doc example can never drift.
|
|
218
|
-
*/
|
|
219
|
-
var WORKFLOW_TOOL_FLAT_EXAMPLE = Object.freeze({
|
|
220
|
-
name: "release",
|
|
221
|
-
steps: Object.freeze([Object.freeze({ name: "compile" }), Object.freeze({ name: "publish" })])
|
|
222
|
-
});
|
|
223
|
-
/**
|
|
224
|
-
* A minimal NESTED authoring example — the ADVANCED escape-hatch form a model may use
|
|
225
|
-
* instead of the flat shape: a full {@link WorkflowDefinition}.
|
|
226
|
-
*
|
|
227
|
-
* @remarks
|
|
228
|
-
* The full four-level form, documented in {@link WORKFLOW_TOOL_DESCRIPTION} as the advanced
|
|
229
|
-
* alternative. It is embedded VERBATIM and guarded by a parity test (`createWorkflowContract().is`
|
|
230
|
-
* must accept it), so the doc example can never drift from a valid definition.
|
|
231
|
-
*/
|
|
232
|
-
var WORKFLOW_TOOL_NESTED_EXAMPLE = Object.freeze({
|
|
233
|
-
id: "release",
|
|
234
|
-
name: "Release",
|
|
235
|
-
phases: Object.freeze([Object.freeze({
|
|
236
|
-
id: "build",
|
|
237
|
-
name: "Build",
|
|
238
|
-
tasks: Object.freeze([Object.freeze({
|
|
239
|
-
id: "compile",
|
|
240
|
-
name: "Compile",
|
|
241
|
-
run: "compile"
|
|
242
|
-
})])
|
|
243
|
-
})])
|
|
244
|
-
});
|
|
245
|
-
/**
|
|
246
|
-
* The DESCRIPTION {@link import('./factories.js').createWorkflowTool} advertises — a
|
|
247
|
-
* multi-line guide that teaches a small model how to author a complete workflow tree.
|
|
248
|
-
*
|
|
249
|
-
* @remarks
|
|
250
|
-
* Presents the SIMPLE flat shape (`{ name, steps: [{ name }] }`) as the PRIMARY way with
|
|
251
|
-
* one complete worked example ({@link WORKFLOW_TOOL_FLAT_EXAMPLE}), names that a step's
|
|
252
|
-
* `name` is a REGISTERED name (not a human label), and documents the full nested
|
|
253
|
-
* {@link WorkflowDefinition} as the ADVANCED form with a minimal example
|
|
254
|
-
* ({@link WORKFLOW_TOOL_NESTED_EXAMPLE}). Both examples are interpolated VERBATIM from the
|
|
255
|
-
* validated constants, so a parity test pins them — the description can never drift from a
|
|
256
|
-
* real, contract-valid example. The `parameters` the tool advertises are the FLAT shape's
|
|
257
|
-
* schema; the nested form is the documented escape-hatch (the tool accepts both). NOTE: a step's
|
|
258
|
-
* "registered behavior name" is authored STRUCTURE only —
|
|
259
|
-
* {@link import('./factories.js').createWorkflowTool} runs the authored tree with no
|
|
260
|
-
* {@link WorkflowFunctions} registry of its own, so every one of its tasks auto-completes under
|
|
261
|
-
* the no-handler rule; the tool validates/synthesizes shape, it does not dispatch behavior.
|
|
262
|
-
*/
|
|
263
|
-
var WORKFLOW_TOOL_DESCRIPTION = [
|
|
264
|
-
"Author and run a workflow (phases run sequentially, the tasks within a phase run concurrently) in one call.",
|
|
265
|
-
"",
|
|
266
|
-
"SIMPLEST way — a flat list of steps. Each step runs one registered behavior; steps run one after another:",
|
|
267
|
-
" { \"name\": \"<workflow name>\", \"steps\": [ { \"name\": \"<registered name>\" }, ... ] }",
|
|
268
|
-
"- a step's \"name\" is a REGISTERED behavior name (a registry key), NOT a human label.",
|
|
269
|
-
"- the top-level \"name\" (the workflow name) is optional. Ids are filled in for you.",
|
|
270
|
-
"Example:",
|
|
271
|
-
JSON.stringify(WORKFLOW_TOOL_FLAT_EXAMPLE),
|
|
272
|
-
"",
|
|
273
|
-
"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\" (a registered behavior name):",
|
|
274
|
-
JSON.stringify(WORKFLOW_TOOL_NESTED_EXAMPLE),
|
|
275
|
-
"In the nested form you may omit any \"id\"/\"name\" and they are filled in positionally; a provided one is kept."
|
|
276
|
-
].join("\n");
|
|
277
180
|
//#endregion
|
|
278
181
|
//#region src/core/errors.ts
|
|
279
182
|
/**
|
|
@@ -284,11 +187,11 @@ var WORKFLOW_TOOL_DESCRIPTION = [
|
|
|
284
187
|
* offending node id / status. Thrown for an illegal lifecycle transition
|
|
285
188
|
* (`TRANSITION`), a structurally invalid {@link import('./types.js').WorkflowSnapshot}
|
|
286
189
|
* passed to {@link import('./factories.js').restoreWorkflow} (`RESTORE`), an over-deep /
|
|
287
|
-
* cyclic nested-workflow dispatch (`DEPTH`), and a malformed
|
|
288
|
-
*
|
|
289
|
-
* workflow-tool
|
|
290
|
-
*
|
|
291
|
-
*
|
|
190
|
+
* cyclic nested-workflow dispatch (`DEPTH`), and a malformed workflow-authoring-tool args
|
|
191
|
+
* blob (`TOOL`). `DEPTH` and `TOOL` are public type surface constructed by the
|
|
192
|
+
* `@orkestrel/tool` package's workflow-tool / agent-function adapters; on that seam the
|
|
193
|
+
* throw is ISOLATED by its `ToolManager` into the tool result's top-level `error`
|
|
194
|
+
* (AGENTS §14 — the universal tool-handler contract).
|
|
292
195
|
*/
|
|
293
196
|
var WorkflowError = class extends Error {
|
|
294
197
|
code;
|
|
@@ -321,37 +224,6 @@ function isWorkflowError(value) {
|
|
|
321
224
|
//#endregion
|
|
322
225
|
//#region src/core/helpers.ts
|
|
323
226
|
/**
|
|
324
|
-
* The ancestry identifier of a workflow run — `workflow:<id>`.
|
|
325
|
-
*
|
|
326
|
-
* @remarks
|
|
327
|
-
* The {@link import('./WorkflowRunner.js').WorkflowRunner}'s cycle guard records one of
|
|
328
|
-
* these per workflow in the current nested run chain (carried on
|
|
329
|
-
* {@link import('./types.js').WorkflowRunOptions.ancestry}). Tagging the bare id keeps a
|
|
330
|
-
* workflow id and an {@link agentTag} agent name in ONE namespaced set without collision,
|
|
331
|
-
* so re-entering a workflow OR an agent already in the chain is a single `includes` check.
|
|
332
|
-
*
|
|
333
|
-
* @param id - The workflow definition's `id`
|
|
334
|
-
* @returns The namespaced ancestry tag (`workflow:<id>`)
|
|
335
|
-
*/
|
|
336
|
-
function workflowTag(id) {
|
|
337
|
-
return `workflow:${id}`;
|
|
338
|
-
}
|
|
339
|
-
/**
|
|
340
|
-
* The ancestry identifier of an agent in a run chain — `agent:<name>`.
|
|
341
|
-
*
|
|
342
|
-
* @remarks
|
|
343
|
-
* The agent counterpart of {@link workflowTag}: the runner adds one when it dispatches an
|
|
344
|
-
* `agent` task, and rejects the task (a typed `DEPTH` `task.fail`) when the same tag is
|
|
345
|
-
* already in the ancestry (a re-entry cycle). The `agent:` namespace keeps it distinct
|
|
346
|
-
* from a same-string workflow id.
|
|
347
|
-
*
|
|
348
|
-
* @param name - The agent's registry name (the `agent`-form's `name`)
|
|
349
|
-
* @returns The namespaced ancestry tag (`agent:<name>`)
|
|
350
|
-
*/
|
|
351
|
-
function agentTag(name) {
|
|
352
|
-
return `agent:${name}`;
|
|
353
|
-
}
|
|
354
|
-
/**
|
|
355
227
|
* Test whether a {@link LifecycleStatus} is TERMINAL — a node in this state will not
|
|
356
228
|
* transition further.
|
|
357
229
|
*
|
|
@@ -721,120 +593,6 @@ function collectResults(phases) {
|
|
|
721
593
|
return phases.flat();
|
|
722
594
|
}
|
|
723
595
|
/**
|
|
724
|
-
* Summarize a terminal {@link WorkflowResult} into the PLAIN value a
|
|
725
|
-
* {@link import('./factories.js').createWorkflowTool} handler returns on success.
|
|
726
|
-
*
|
|
727
|
-
* @remarks
|
|
728
|
-
* This is the run summary the handler returns DIRECTLY — NOT a `ToolResult` (the future `@orkestrel/agent` package).
|
|
729
|
-
* The handler conforms to the universal tool-handler contract (AGENTS §14): it returns the plain value
|
|
730
|
-
* (and throws on failure), so the `@orkestrel/agent` package's `ToolManager` performs
|
|
731
|
-
* the ONE canonical wrap (`{ id, name, value }`) and the model reads exactly this summary — once,
|
|
732
|
-
* identically — over BOTH the agent loop and MCP. The summary is LEAN: the workflow's terminal `status`
|
|
733
|
-
* and the COUNT of settled task results — enough for a caller / model to react without serializing the
|
|
734
|
-
* whole live tree. (It carries no synthetic `id` / `name`: a tool handler has no call id; the manager
|
|
735
|
-
* supplies the canonical envelope's identity.)
|
|
736
|
-
*
|
|
737
|
-
* @param result - The terminal {@link WorkflowResult} the run produced
|
|
738
|
-
* @returns The plain success summary — `{ status, count }`
|
|
739
|
-
*/
|
|
740
|
-
function workflowToolSummary(result) {
|
|
741
|
-
return {
|
|
742
|
-
status: result.status,
|
|
743
|
-
count: result.results.length
|
|
744
|
-
};
|
|
745
|
-
}
|
|
746
|
-
/**
|
|
747
|
-
* Complete a {@link WorkflowDraft} into a strict {@link WorkflowDefinition} — synthesize
|
|
748
|
-
* any MISSING `id` deterministically + positionally, and default any MISSING `name` to
|
|
749
|
-
* its (now-resolved) `id`.
|
|
750
|
-
*
|
|
751
|
-
* @remarks
|
|
752
|
-
* The positional id scheme is stable and human-legible: the workflow is `wf`, phase `i`
|
|
753
|
-
* is `phase-<i>`, and task `j` of that phase is `<phaseId>-task-<j>` (so a provided phase
|
|
754
|
-
* id flows into its tasks' synthesized ids). A PROVIDED `id` / `name` at any level is kept
|
|
755
|
-
* VERBATIM — synthesis touches only the omitted ones. A missing `name` defaults to the
|
|
756
|
-
* resolved `id` (never the other way round), so the result always has both. `run`,
|
|
757
|
-
* `description`, the per-phase `concurrency` / `bail`, the per-task `retries` / `timeout`, and
|
|
758
|
-
* the workflow `bail` carry over unchanged. The result is a complete
|
|
759
|
-
* {@link WorkflowDefinition}; the caller still validates it against the STRICT contract.
|
|
760
|
-
*
|
|
761
|
-
* @param draft - The draft workflow (id/name optional at all three levels)
|
|
762
|
-
* @returns A complete {@link WorkflowDefinition} with every id/name filled
|
|
763
|
-
*/
|
|
764
|
-
function completeDraft(draft) {
|
|
765
|
-
const id = draft.id ?? "wf";
|
|
766
|
-
return {
|
|
767
|
-
id,
|
|
768
|
-
name: draft.name ?? id,
|
|
769
|
-
...draft.description === void 0 ? {} : { description: draft.description },
|
|
770
|
-
phases: draft.phases.map((phase, index) => completePhaseDraft(phase, index)),
|
|
771
|
-
...draft.bail === void 0 ? {} : { bail: draft.bail }
|
|
772
|
-
};
|
|
773
|
-
}
|
|
774
|
-
/**
|
|
775
|
-
* Complete one {@link PhaseDraft} into a strict {@link PhaseDefinition} — the per-phase
|
|
776
|
-
* step of {@link completeDraft} (phase `index` → `phase-<index>` when its id is omitted).
|
|
777
|
-
*
|
|
778
|
-
* @param phase - The draft phase
|
|
779
|
-
* @param index - The phase's positional index in the workflow
|
|
780
|
-
* @returns A complete {@link PhaseDefinition}
|
|
781
|
-
*/
|
|
782
|
-
function completePhaseDraft(phase, index) {
|
|
783
|
-
const id = phase.id ?? `phase-${index}`;
|
|
784
|
-
return {
|
|
785
|
-
id,
|
|
786
|
-
name: phase.name ?? id,
|
|
787
|
-
...phase.description === void 0 ? {} : { description: phase.description },
|
|
788
|
-
tasks: phase.tasks.map((task, taskIndex) => completeTaskDraft(task, id, taskIndex)),
|
|
789
|
-
...phase.concurrency === void 0 ? {} : { concurrency: phase.concurrency },
|
|
790
|
-
...phase.bail === void 0 ? {} : { bail: phase.bail }
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
/**
|
|
794
|
-
* Complete one {@link TaskDraft} into a strict {@link TaskDefinition} — the per-task leaf
|
|
795
|
-
* step of {@link completeDraft} (task `index` of phase `<phaseId>` → `<phaseId>-task-<index>`
|
|
796
|
-
* when its id is omitted).
|
|
797
|
-
*
|
|
798
|
-
* @param task - The draft task
|
|
799
|
-
* @param phaseId - The (resolved) parent phase id, so the synthesized task id nests under it
|
|
800
|
-
* @param index - The task's positional index within its phase
|
|
801
|
-
* @returns A complete {@link TaskDefinition}
|
|
802
|
-
*/
|
|
803
|
-
function completeTaskDraft(task, phaseId, index) {
|
|
804
|
-
const id = task.id ?? `${phaseId}-task-${index}`;
|
|
805
|
-
return {
|
|
806
|
-
id,
|
|
807
|
-
name: task.name ?? id,
|
|
808
|
-
...task.description === void 0 ? {} : { description: task.description },
|
|
809
|
-
...task.run === void 0 ? {} : { run: task.run },
|
|
810
|
-
...task.retries === void 0 ? {} : { retries: task.retries },
|
|
811
|
-
...task.timeout === void 0 ? {} : { timeout: task.timeout }
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
/**
|
|
815
|
-
* Expand a flat {@link WorkflowSteps} blob into a strict {@link WorkflowDefinition} — each
|
|
816
|
-
* step becomes a one-task phase, IN ORDER.
|
|
817
|
-
*
|
|
818
|
-
* @remarks
|
|
819
|
-
* The expansion of the tool's ADVERTISED surface (AGENTS §21 — the simplest form a small
|
|
820
|
-
* model can author). Each {@link WorkflowStep} maps to a phase holding exactly one task:
|
|
821
|
-
* the step's `name` becomes the task's `run` (the behavior-registry key). Ids/names are
|
|
822
|
-
* auto-filled positionally — it builds an ids-omitted {@link WorkflowDraft} and delegates
|
|
823
|
-
* to {@link completeDraft}, so the two lenient surfaces share ONE synthesis path (step `i`
|
|
824
|
-
* → phase `phase-<i>`, its task `phase-<i>-task-0`). The optional `name` becomes the
|
|
825
|
-
* workflow's `name`. The result is a complete definition the caller validates against the
|
|
826
|
-
* STRICT contract before running.
|
|
827
|
-
*
|
|
828
|
-
* @param flat - The flat steps blob (`{ name?, steps: [{ name }] }`)
|
|
829
|
-
* @returns A complete {@link WorkflowDefinition} (one one-task phase per step)
|
|
830
|
-
*/
|
|
831
|
-
function expandSteps(flat) {
|
|
832
|
-
return completeDraft({
|
|
833
|
-
...flat.name === void 0 ? {} : { name: flat.name },
|
|
834
|
-
phases: flat.steps.map((step) => ({ tasks: [{ run: step.name }] }))
|
|
835
|
-
});
|
|
836
|
-
}
|
|
837
|
-
/**
|
|
838
596
|
* Insert one `[key, value]` entry at a positional index into a readonly entries array —
|
|
839
597
|
* the pure splice-in step behind an insertion-ordered registry's `add`.
|
|
840
598
|
*
|
|
@@ -1012,118 +770,6 @@ var workflowShape = objectShape({
|
|
|
1012
770
|
bail: optionalShape(literalShape([true, false], { description: "Failure policy: false (default) continues gracefully, true halts on the first failure." }))
|
|
1013
771
|
});
|
|
1014
772
|
/**
|
|
1015
|
-
* The shape of a TASK in a draft workflow — identical to {@link taskShape} EXCEPT `id`
|
|
1016
|
-
* and `name` are OPTIONAL (the tool synthesizes any missing one positionally).
|
|
1017
|
-
*
|
|
1018
|
-
* @remarks
|
|
1019
|
-
* A PROVIDED `id` / `name` still carries `minLength: 1`, so an explicitly-empty `id: ''`
|
|
1020
|
-
* is INVALID (rejected by the draft contract), never auto-filled — keeping "garbage"
|
|
1021
|
-
* distinct from "omitted". `run` stays optional, mirroring {@link taskShape}.
|
|
1022
|
-
*/
|
|
1023
|
-
var taskDraftShape = objectShape({
|
|
1024
|
-
id: optionalShape(stringShape({
|
|
1025
|
-
min: 1,
|
|
1026
|
-
description: "Task id; auto-filled when omitted."
|
|
1027
|
-
})),
|
|
1028
|
-
name: optionalShape(stringShape({
|
|
1029
|
-
min: 1,
|
|
1030
|
-
description: "Task name; defaults to the id when omitted."
|
|
1031
|
-
})),
|
|
1032
|
-
description: optionalShape(stringShape({ description: "Optional task description." })),
|
|
1033
|
-
run: optionalShape(stringShape({
|
|
1034
|
-
min: 1,
|
|
1035
|
-
description: "The registered behavior name to invoke (a registry key, not a label); omitted has no handler."
|
|
1036
|
-
})),
|
|
1037
|
-
retries: optionalShape(integerShape({
|
|
1038
|
-
min: 0,
|
|
1039
|
-
description: "Extra attempts after the first on failure; overrides the phase default. Omitted means none."
|
|
1040
|
-
})),
|
|
1041
|
-
timeout: optionalShape(integerShape({
|
|
1042
|
-
min: 0,
|
|
1043
|
-
description: "Per-attempt deadline in milliseconds; overrides the phase default. Omitted means no deadline."
|
|
1044
|
-
}))
|
|
1045
|
-
});
|
|
1046
|
-
/**
|
|
1047
|
-
* The shape of a PHASE in a draft workflow — identical to {@link phaseShape} EXCEPT
|
|
1048
|
-
* `id` and `name` are OPTIONAL, and its tasks are {@link taskDraftShape}s.
|
|
1049
|
-
*/
|
|
1050
|
-
var phaseDraftShape = objectShape({
|
|
1051
|
-
id: optionalShape(stringShape({
|
|
1052
|
-
min: 1,
|
|
1053
|
-
description: "Phase id; auto-filled when omitted."
|
|
1054
|
-
})),
|
|
1055
|
-
name: optionalShape(stringShape({
|
|
1056
|
-
min: 1,
|
|
1057
|
-
description: "Phase name; defaults to the id when omitted."
|
|
1058
|
-
})),
|
|
1059
|
-
description: optionalShape(stringShape({ description: "Optional phase description." })),
|
|
1060
|
-
tasks: arrayShape(taskDraftShape, { description: "The phase tasks; they run CONCURRENTLY." }),
|
|
1061
|
-
concurrency: optionalShape(integerShape({
|
|
1062
|
-
min: 1,
|
|
1063
|
-
description: "Max tasks in flight at once (a resource throttle); omitted means unbounded."
|
|
1064
|
-
})),
|
|
1065
|
-
bail: optionalShape(literalShape([true, false], { description: "Per-phase failure-policy override; omitted inherits the workflow bail." }))
|
|
1066
|
-
});
|
|
1067
|
-
/**
|
|
1068
|
-
* The shape of a DRAFT workflow — identical to {@link workflowShape} EXCEPT `id` and
|
|
1069
|
-
* `name` are OPTIONAL at all three levels (workflow / phase / task), so a small model
|
|
1070
|
-
* can omit the six identity strings and let the tool synthesize them positionally.
|
|
1071
|
-
*
|
|
1072
|
-
* @remarks
|
|
1073
|
-
* The lenient counterpart {@link import('./factories.js').createWorkflowDraftContract}
|
|
1074
|
-
* compiles. `run` stays required; a provided `id` / `name` still has `minLength: 1` (so an
|
|
1075
|
-
* explicitly-empty `id: ''` is REJECTED, not auto-filled). After
|
|
1076
|
-
* {@link import('./helpers.js').completeDraft} fills the missing ids/names, the result is
|
|
1077
|
-
* validated against the STRICT {@link import('./factories.js').createWorkflowContract} gate
|
|
1078
|
-
* before running.
|
|
1079
|
-
*/
|
|
1080
|
-
var workflowDraftShape = objectShape({
|
|
1081
|
-
id: optionalShape(stringShape({
|
|
1082
|
-
min: 1,
|
|
1083
|
-
description: "Workflow id; auto-filled when omitted."
|
|
1084
|
-
})),
|
|
1085
|
-
name: optionalShape(stringShape({
|
|
1086
|
-
min: 1,
|
|
1087
|
-
description: "Workflow name; defaults to the id when omitted."
|
|
1088
|
-
})),
|
|
1089
|
-
description: optionalShape(stringShape({ description: "Optional workflow description." })),
|
|
1090
|
-
phases: arrayShape(phaseDraftShape, { description: "The workflow phases; they run SEQUENTIALLY, in order." }),
|
|
1091
|
-
bail: optionalShape(literalShape([true, false], { description: "Failure policy: false (default) continues gracefully, true halts on the first failure." }))
|
|
1092
|
-
});
|
|
1093
|
-
/**
|
|
1094
|
-
* The shape of ONE flat step — `{ name }` — the building block of
|
|
1095
|
-
* {@link workflowStepsShape}.
|
|
1096
|
-
*
|
|
1097
|
-
* @remarks
|
|
1098
|
-
* `name` is the REGISTERED behavior name the step runs (it becomes the task's `run`). The
|
|
1099
|
-
* tool expands each step into a one-task phase, in order
|
|
1100
|
-
* ({@link import('./helpers.js').expandSteps}).
|
|
1101
|
-
*/
|
|
1102
|
-
var stepShape = objectShape({ name: stringShape({
|
|
1103
|
-
min: 1,
|
|
1104
|
-
description: "The registered behavior name this step runs (becomes the task run)."
|
|
1105
|
-
}) });
|
|
1106
|
-
/**
|
|
1107
|
-
* The FLAT authoring shape `createWorkflowTool` advertises as its `parameters` — the
|
|
1108
|
-
* simplest surface a small model can fill: `{ name?, steps: [{ name }] }`.
|
|
1109
|
-
*
|
|
1110
|
-
* @remarks
|
|
1111
|
-
* The deliberately-reduced surface (AGENTS §21): a flat ordered list of steps, each a
|
|
1112
|
-
* `{ name }`. The tool EXPANDS it ({@link import('./helpers.js').expandSteps}) into a
|
|
1113
|
-
* full {@link import('./types.js').WorkflowDefinition} — one one-task phase per step, in
|
|
1114
|
-
* order — then validates against the STRICT
|
|
1115
|
-
* {@link import('./factories.js').createWorkflowContract} gate. The full nested form is
|
|
1116
|
-
* STILL accepted by the tool (it branches on the args' shape) and is documented as the
|
|
1117
|
-
* advanced escape-hatch in the tool's description — but THIS is what `parameters` advertises.
|
|
1118
|
-
*/
|
|
1119
|
-
var workflowStepsShape = objectShape({
|
|
1120
|
-
name: optionalShape(stringShape({
|
|
1121
|
-
min: 1,
|
|
1122
|
-
description: "Optional workflow name."
|
|
1123
|
-
})),
|
|
1124
|
-
steps: arrayShape(stepShape, { description: "The ordered steps to run, one after another (each becomes a one-task phase)." })
|
|
1125
|
-
});
|
|
1126
|
-
/**
|
|
1127
773
|
* The shape of a {@link import('./types.js').TaskUpdate} — a partial edit to a
|
|
1128
774
|
* `pending` task's `name` / `description`, both optional.
|
|
1129
775
|
*
|
|
@@ -2197,6 +1843,91 @@ var Workflow = class {
|
|
|
2197
1843
|
}
|
|
2198
1844
|
};
|
|
2199
1845
|
//#endregion
|
|
1846
|
+
//#region src/core/WorkflowManager.ts
|
|
1847
|
+
/**
|
|
1848
|
+
* The store-backed registry of {@link WorkflowInterface}s keyed by `id`, in insertion order —
|
|
1849
|
+
* the additive manager tier mirroring the `@orkestrel/agent` line's `ConversationManager` /
|
|
1850
|
+
* `WorkspaceManager`. Event-free (a registry, like its twins); the observability lives on each
|
|
1851
|
+
* {@link WorkflowInterface}.
|
|
1852
|
+
*
|
|
1853
|
+
* @remarks
|
|
1854
|
+
* - **Registry.** Workflows live in an insertion-ordered `Map` keyed by `id`. `add(definition)`
|
|
1855
|
+
* mints a live {@link WorkflowInterface} through {@link createWorkflow} (flowing the manager's
|
|
1856
|
+
* `functions` registry in) and stores it under `definition.id` — an already-present id
|
|
1857
|
+
* OVERWRITES (last write wins). `count` is the map size, `workflow(id)` looks one up,
|
|
1858
|
+
* `workflows()` lists them in insertion order.
|
|
1859
|
+
* - **Durable open / save.** `open(id)` returns an already-registered workflow directly; on a
|
|
1860
|
+
* registry MISS with a `store` set it rehydrates through {@link restoreWorkflow} (flowing the
|
|
1861
|
+
* manager's `functions` registry in so the rehydrated tree is RUNNABLE), registers it, and
|
|
1862
|
+
* returns it — lenient (`undefined`) with no store or a store miss. `save(id)` persists a
|
|
1863
|
+
* registered workflow's `snapshot()` to the `store` — lenient (`false`) with no store or an
|
|
1864
|
+
* unknown id.
|
|
1865
|
+
* - **Removal.** `remove` drops one by id, or a batch (§9.2, array overload FIRST) — `true` when
|
|
1866
|
+
* any was removed. `clear` empties the registry.
|
|
1867
|
+
* - **No active pointer.** Unlike its `ConversationManager` / `WorkspaceManager` twins, there is
|
|
1868
|
+
* no `active` / `switch` — nothing in the workflow domain renders "the current workflow".
|
|
1869
|
+
*
|
|
1870
|
+
* @example
|
|
1871
|
+
* ```ts
|
|
1872
|
+
* const manager = new WorkflowManager({
|
|
1873
|
+
* functions: { compile: async (controller) => `built ${controller.task.id}` },
|
|
1874
|
+
* })
|
|
1875
|
+
* const workflow = manager.add(definition) // minted, registered, RUNNABLE
|
|
1876
|
+
* manager.workflow(workflow.id) // the same workflow
|
|
1877
|
+
* manager.count // 1
|
|
1878
|
+
* ```
|
|
1879
|
+
*/
|
|
1880
|
+
var WorkflowManager = class {
|
|
1881
|
+
#workflows = /* @__PURE__ */ new Map();
|
|
1882
|
+
#functions;
|
|
1883
|
+
#store;
|
|
1884
|
+
constructor(options) {
|
|
1885
|
+
this.#functions = options?.functions;
|
|
1886
|
+
this.#store = options?.store;
|
|
1887
|
+
}
|
|
1888
|
+
get count() {
|
|
1889
|
+
return this.#workflows.size;
|
|
1890
|
+
}
|
|
1891
|
+
workflow(id) {
|
|
1892
|
+
return this.#workflows.get(id);
|
|
1893
|
+
}
|
|
1894
|
+
workflows() {
|
|
1895
|
+
return [...this.#workflows.values()];
|
|
1896
|
+
}
|
|
1897
|
+
add(definition) {
|
|
1898
|
+
const workflow = createWorkflow(definition, { functions: this.#functions });
|
|
1899
|
+
this.#workflows.set(workflow.id, workflow);
|
|
1900
|
+
return workflow;
|
|
1901
|
+
}
|
|
1902
|
+
async open(id) {
|
|
1903
|
+
const existing = this.#workflows.get(id);
|
|
1904
|
+
if (existing !== void 0) return existing;
|
|
1905
|
+
if (this.#store === void 0) return void 0;
|
|
1906
|
+
const snapshot = await this.#store.get(id);
|
|
1907
|
+
if (snapshot === void 0) return void 0;
|
|
1908
|
+
const workflow = restoreWorkflow(snapshot, { functions: this.#functions });
|
|
1909
|
+
this.#workflows.set(workflow.id, workflow);
|
|
1910
|
+
return workflow;
|
|
1911
|
+
}
|
|
1912
|
+
async save(id) {
|
|
1913
|
+
const workflow = this.#workflows.get(id);
|
|
1914
|
+
if (this.#store === void 0 || workflow === void 0) return false;
|
|
1915
|
+
await this.#store.set(workflow.snapshot());
|
|
1916
|
+
return true;
|
|
1917
|
+
}
|
|
1918
|
+
remove(ids) {
|
|
1919
|
+
if (isArray(ids)) {
|
|
1920
|
+
let removed = false;
|
|
1921
|
+
for (const id of ids) if (this.#workflows.delete(id)) removed = true;
|
|
1922
|
+
return removed;
|
|
1923
|
+
}
|
|
1924
|
+
return this.#workflows.delete(ids);
|
|
1925
|
+
}
|
|
1926
|
+
clear() {
|
|
1927
|
+
this.#workflows.clear();
|
|
1928
|
+
}
|
|
1929
|
+
};
|
|
1930
|
+
//#endregion
|
|
2200
1931
|
//#region src/core/Controller.ts
|
|
2201
1932
|
/**
|
|
2202
1933
|
* The per-unit handle a runner handler receives — wraps the unit's identity,
|
|
@@ -2580,10 +2311,10 @@ var TaskController = class {
|
|
|
2580
2311
|
* {@link import('./types.js').TaskInterface.handler} ONCE at construction (build, restore,
|
|
2581
2312
|
* or a live mint all resolve it identically, from {@link WorkflowOptions.functions}), so
|
|
2582
2313
|
* dispatch is simply "invoke the task's own handler". Static tool / agent calling is an
|
|
2583
|
-
* OPT-IN concern of `
|
|
2584
|
-
* {@link import('./
|
|
2585
|
-
*
|
|
2586
|
-
*
|
|
2314
|
+
* OPT-IN concern of the `@orkestrel/tool` package's adapter factories — plain
|
|
2315
|
+
* {@link import('./types.js').WorkflowFunction}s a caller wires into
|
|
2316
|
+
* {@link WorkflowOptions.functions} like any other behavior. This module never imports
|
|
2317
|
+
* any tool/agent package.
|
|
2587
2318
|
* - **Two `execute` forms, one engine.** `execute(definition, options)` BUILDS the live tree
|
|
2588
2319
|
* from a {@link WorkflowDefinition} (single source of truth for the `run` / `concurrency`
|
|
2589
2320
|
* metadata); `execute(workflow, options)` DRIVES a caller-owned, ALREADY-BUILT
|
|
@@ -2896,41 +2627,6 @@ function createWorkflowContract() {
|
|
|
2896
2627
|
};
|
|
2897
2628
|
}
|
|
2898
2629
|
/**
|
|
2899
|
-
* Compile the LENIENT workflow DRAFT contract — identical to
|
|
2900
|
-
* {@link createWorkflowContract} EXCEPT `id` and `name` are OPTIONAL at all three levels
|
|
2901
|
-
* (workflow / phase / task), so a small model can omit the six identity strings.
|
|
2902
|
-
*
|
|
2903
|
-
* @remarks
|
|
2904
|
-
* The widened authoring surface {@link createWorkflowTool} parses an authored blob through
|
|
2905
|
-
* before {@link import('./helpers.js').completeDraft} fills the missing ids/names. It does
|
|
2906
|
-
* NOT relax the canonical contract — {@link createWorkflowContract} stays byte-for-byte
|
|
2907
|
-
* unchanged and STRICT, and the completed draft is re-validated against THAT strict gate
|
|
2908
|
-
* before running (soundness preserved). A PROVIDED `id` / `name` still carries `minLength: 1`,
|
|
2909
|
-
* so an explicitly-empty `id: ''` is REJECTED (parses to `undefined`), never auto-filled —
|
|
2910
|
-
* keeping "garbage" distinct from "omitted". `run` stays required.
|
|
2911
|
-
*
|
|
2912
|
-
* @returns The compiled {@link WorkflowDraft} contract
|
|
2913
|
-
*
|
|
2914
|
-
* @example
|
|
2915
|
-
* ```ts
|
|
2916
|
-
* import { createWorkflowDraftContract, completeDraft } from '@src/core'
|
|
2917
|
-
*
|
|
2918
|
-
* const draft = createWorkflowDraftContract()
|
|
2919
|
-
* const parsed = draft.parse({ phases: [{ tasks: [{ run: { via: 'function', name: 'f' } }] }] })
|
|
2920
|
-
* const definition = parsed && completeDraft(parsed) // ids/names filled positionally
|
|
2921
|
-
* draft.parse({ id: '', phases: [] }) // undefined — an explicit empty id is rejected
|
|
2922
|
-
* ```
|
|
2923
|
-
*/
|
|
2924
|
-
function createWorkflowDraftContract() {
|
|
2925
|
-
const contract = createContract(workflowDraftShape);
|
|
2926
|
-
return {
|
|
2927
|
-
schema: contract.schema,
|
|
2928
|
-
is: contract.is,
|
|
2929
|
-
generate: (random) => contract.generate(random),
|
|
2930
|
-
parse: (value) => contract.parse(value)
|
|
2931
|
-
};
|
|
2932
|
-
}
|
|
2933
|
-
/**
|
|
2934
2630
|
* Build the live W-b entity tree from a {@link WorkflowDefinition} — the whole
|
|
2935
2631
|
* {@link WorkflowInterface} → {@link import('./types.js').PhaseInterface} →
|
|
2936
2632
|
* {@link import('./types.js').TaskInterface} tree, each level wired with its lineage
|
|
@@ -3164,11 +2860,11 @@ function createDatabaseWorkflowStore(driver = createMemoryDriver()) {
|
|
|
3164
2860
|
* the live entity (`start` → `complete` / `fail`), and resolves a
|
|
3165
2861
|
* {@link import('./types.js').WorkflowResult}.
|
|
3166
2862
|
*
|
|
3167
|
-
* Static tool / agent calling is OPT-IN
|
|
3168
|
-
* {@link
|
|
3169
|
-
*
|
|
3170
|
-
*
|
|
3171
|
-
*
|
|
2863
|
+
* Static tool / agent calling is OPT-IN: a caller wires a plain
|
|
2864
|
+
* {@link import('./types.js').WorkflowFunction} into its OWN {@link WorkflowOptions.functions}
|
|
2865
|
+
* registry, same as any other behavior — the `@orkestrel/tool` package ships the
|
|
2866
|
+
* tool/agent adapter factories for that. A task with no resolved handler AUTO-COMPLETES
|
|
2867
|
+
* (the ROADMAP no-handler rule).
|
|
3172
2868
|
*
|
|
3173
2869
|
* @param options - An optional pacing `scheduler` (default the shipped cross-environment one).
|
|
3174
2870
|
* See {@link WorkflowRunnerOptions}.
|
|
@@ -3193,229 +2889,38 @@ function createWorkflowRunner(options) {
|
|
|
3193
2889
|
return new WorkflowRunner(options?.scheduler ?? createScheduler());
|
|
3194
2890
|
}
|
|
3195
2891
|
/**
|
|
3196
|
-
*
|
|
3197
|
-
*
|
|
3198
|
-
*
|
|
3199
|
-
* @remarks
|
|
3200
|
-
* Composes into a caller's {@link WorkflowOptions.functions} registry like any other behavior
|
|
3201
|
-
* (`{ publish: createToolFunction(tools, 'publish') }`); the PURE
|
|
3202
|
-
* {@link import('./WorkflowRunner.js').WorkflowRunner} has no knowledge of tools itself. The
|
|
3203
|
-
* returned function executes `name` against `tools` with the task's `controller.input` as the
|
|
3204
|
-
* call arguments, id-correlated to the task's own id. A `ToolManagerInterface.execute` NEVER
|
|
3205
|
-
* throws (a handler throw is isolated into `result.error`), so a failing tool is surfaced here
|
|
3206
|
-
* as a THROWN `Error` carrying the original message as `cause` — the leaf `fail`s, honouring
|
|
3207
|
-
* `bail`. An UNREGISTERED tool name is a programmer error (an explicit binding to a name that
|
|
3208
|
-
* doesn't exist) — unlike the engine's own silent auto-complete of an unresolved task handler,
|
|
3209
|
-
* this THROWS a typed `TOOL` {@link WorkflowError}.
|
|
3210
|
-
*
|
|
3211
|
-
* @param tools - The {@link ToolManagerInterface} the named tool is registered on
|
|
3212
|
-
* @param name - The registered tool's name
|
|
3213
|
-
* @returns A {@link WorkflowFunction} that runs the named tool
|
|
3214
|
-
*
|
|
3215
|
-
* @example
|
|
3216
|
-
* ```ts
|
|
3217
|
-
* import { createToolFunction, createToolManager, createWorkflowRunner } from '@src/core'
|
|
3218
|
-
*
|
|
3219
|
-
* const tools = createToolManager()
|
|
3220
|
-
* tools.add(myPublishTool)
|
|
3221
|
-
* const runner = createWorkflowRunner()
|
|
3222
|
-
* await runner.execute(definition, { functions: { publish: createToolFunction(tools, 'publish') } })
|
|
3223
|
-
* ```
|
|
3224
|
-
*/
|
|
3225
|
-
function createToolFunction(tools, name) {
|
|
3226
|
-
return async (controller) => {
|
|
3227
|
-
if (tools.tool(name) === void 0) throw new WorkflowError("TOOL", `tool '${name}' is not registered`, { tool: name });
|
|
3228
|
-
const result = await tools.execute({
|
|
3229
|
-
id: controller.task.id,
|
|
3230
|
-
name,
|
|
3231
|
-
arguments: controller.input
|
|
3232
|
-
});
|
|
3233
|
-
if (result.error !== void 0) throw new Error(result.error, { cause: result.error });
|
|
3234
|
-
return result.value;
|
|
3235
|
-
};
|
|
3236
|
-
}
|
|
3237
|
-
/**
|
|
3238
|
-
* Wrap a live `AgentInterface` (`@orkestrel/agent`) as a {@link WorkflowFunction} — the OPT-IN
|
|
3239
|
-
* adapter that runs the agent to a settled result, folding a nested workflow-authoring
|
|
3240
|
-
* depth / cycle guard into its own closure.
|
|
3241
|
-
*
|
|
3242
|
-
* @remarks
|
|
3243
|
-
* Composes into a caller's {@link WorkflowOptions.functions} registry like any other behavior;
|
|
3244
|
-
* the PURE {@link import('./WorkflowRunner.js').WorkflowRunner} has no knowledge of agents
|
|
3245
|
-
* itself. Before running the agent, the depth/cycle guard REJECTS the call (a THROWN typed
|
|
3246
|
-
* `DEPTH` {@link WorkflowError}, which the leaf `fail`s) when running it would push a nested
|
|
3247
|
-
* chain past {@link MAX_WORKFLOW_DEPTH}, OR when this agent is already an ancestor (a cycle) —
|
|
3248
|
-
* ported from the former engine-side guard. When {@link AgentFunctionOptions.runner} is
|
|
3249
|
-
* supplied, the adapter BINDS a depth/cycle-aware {@link createWorkflowTool} onto the agent's
|
|
3250
|
-
* `context.tools` (the propagation seam) — closed over `depth` and the extended ancestry (the
|
|
3251
|
-
* tool itself computes `depth + 1` internally) — so the agent can author + run a NESTED
|
|
3252
|
-
* workflow through it; the wrapped default is the CURRENT task's own workflow id (used only on
|
|
3253
|
-
* a no-args tool call). The task's cancellation folds into the agent run: an already-aborted
|
|
3254
|
-
* `controller.signal` cancels the agent up front; otherwise a one-shot listener fires
|
|
3255
|
-
* `agent.abort(reason)` when the task cancels, removed in `finally`. `agent.generate()` resolves
|
|
3256
|
-
* a partial `AgentResult` on a cancel (never rejects), returned as the task's completed value.
|
|
3257
|
-
*
|
|
3258
|
-
* A bound agent is effectively SINGLE-RUN: `context.tools.add` binds one {@link ToolInterface}
|
|
3259
|
-
* under the fixed {@link import('./constants.js').WORKFLOW_TOOL_NAME}, and `agent.generate()` /
|
|
3260
|
-
* `agent.abort()` are per-agent state. Two CONCURRENT tasks sharing the SAME `agent` instance
|
|
3261
|
-
* race on that one tool binding (last-write-wins) and on generate/abort — give each concurrent
|
|
3262
|
-
* task its OWN agent instance.
|
|
3263
|
-
*
|
|
3264
|
-
* @param agent - The live `AgentInterface` to run
|
|
3265
|
-
* @param options - The nested-workflow binding + depth/cycle bookkeeping (see {@link AgentFunctionOptions})
|
|
3266
|
-
* @returns A {@link WorkflowFunction} that runs `agent` to its settled result
|
|
3267
|
-
*
|
|
3268
|
-
* @example
|
|
3269
|
-
* ```ts
|
|
3270
|
-
* import { createAgentFunction, createWorkflowRunner } from '@src/core'
|
|
3271
|
-
*
|
|
3272
|
-
* const runner = createWorkflowRunner()
|
|
3273
|
-
* const review = createAgentFunction(myAgent, { runner })
|
|
3274
|
-
* await runner.execute(definition, { functions: { review } })
|
|
3275
|
-
* ```
|
|
3276
|
-
*/
|
|
3277
|
-
function createAgentFunction(agent, options) {
|
|
3278
|
-
return async (controller) => {
|
|
3279
|
-
const depth = options?.depth ?? 0;
|
|
3280
|
-
const ancestry = options?.ancestry ?? [];
|
|
3281
|
-
if (depth + 1 > 8) throw new WorkflowError("DEPTH", `agent '${agent.id}' exceeds max workflow depth`, {
|
|
3282
|
-
agent: agent.id,
|
|
3283
|
-
depth,
|
|
3284
|
-
max: 8
|
|
3285
|
-
});
|
|
3286
|
-
const tag = agentTag(agent.id);
|
|
3287
|
-
if (ancestry.includes(tag)) throw new WorkflowError("DEPTH", `agent '${agent.id}' is already an ancestor (cycle)`, {
|
|
3288
|
-
agent: agent.id,
|
|
3289
|
-
ancestry: [...ancestry]
|
|
3290
|
-
});
|
|
3291
|
-
const runner = options?.runner;
|
|
3292
|
-
if (runner !== void 0) {
|
|
3293
|
-
const workflowId = controller.task.phase.workflow.id;
|
|
3294
|
-
const wrapped = {
|
|
3295
|
-
id: workflowId,
|
|
3296
|
-
name: workflowId,
|
|
3297
|
-
phases: []
|
|
3298
|
-
};
|
|
3299
|
-
agent.context.tools.add(createWorkflowTool(wrapped, runner, {
|
|
3300
|
-
depth,
|
|
3301
|
-
ancestry: [...ancestry, tag]
|
|
3302
|
-
}));
|
|
3303
|
-
}
|
|
3304
|
-
const signal = controller.signal;
|
|
3305
|
-
const onAbort = () => agent.abort(signal.reason);
|
|
3306
|
-
if (signal.aborted) agent.abort(signal.reason);
|
|
3307
|
-
else signal.addEventListener("abort", onAbort, { once: true });
|
|
3308
|
-
try {
|
|
3309
|
-
return await agent.generate();
|
|
3310
|
-
} finally {
|
|
3311
|
-
signal.removeEventListener("abort", onAbort);
|
|
3312
|
-
}
|
|
3313
|
-
};
|
|
3314
|
-
}
|
|
3315
|
-
/**
|
|
3316
|
-
* Wrap a {@link WorkflowDefinition} as an LLM-callable {@link ToolInterface} — it ADVERTISES
|
|
3317
|
-
* the SIMPLE flat authoring shape (`{ name?, steps: [{ name }] }`) as its `parameters` so
|
|
3318
|
-
* even a small model can author a complete tree, and its handler EXPANDS / COMPLETES the
|
|
3319
|
-
* authored blob, validates it against the STRICT contract, runs it through `runner`, and
|
|
3320
|
-
* returns the run SUMMARY (throwing a typed {@link WorkflowError} on failure).
|
|
2892
|
+
* Create a {@link WorkflowManagerInterface} — the store-backed registry of
|
|
2893
|
+
* {@link WorkflowInterface}s, the additive manager tier mirroring the `@orkestrel/agent`
|
|
2894
|
+
* line's `createConversationManager` / `createWorkspaceManager`.
|
|
3321
2895
|
*
|
|
3322
2896
|
* @remarks
|
|
3323
|
-
*
|
|
3324
|
-
*
|
|
3325
|
-
* {@link
|
|
3326
|
-
*
|
|
3327
|
-
*
|
|
3328
|
-
*
|
|
3329
|
-
*
|
|
3330
|
-
*
|
|
3331
|
-
*
|
|
3332
|
-
*
|
|
3333
|
-
*
|
|
3334
|
-
* So the tool ACCEPTS three authoring forms and converges them on the SAME strict
|
|
3335
|
-
* {@link createWorkflowContract} gate before running (soundness preserved):
|
|
3336
|
-
* - the FLAT shape `{ name?, steps: [{ name }] }` — the ADVERTISED `parameters` (the simplest
|
|
3337
|
-
* form, {@link import('./helpers.js').expandSteps}'d into one one-task phase per step);
|
|
3338
|
-
* - a nested DRAFT with any `id`/`name` OMITTED — {@link createWorkflowDraftContract}-parsed then
|
|
3339
|
-
* {@link import('./helpers.js').completeDraft}'d (missing ids synthesized positionally);
|
|
3340
|
-
* - the full nested {@link WorkflowDefinition} — the advanced escape-hatch (documented in the
|
|
3341
|
-
* description), accepted as the draft super-set.
|
|
3342
|
-
*
|
|
3343
|
-
* The handler conforms to the universal tool-handler contract (AGENTS §14): it returns the PLAIN
|
|
3344
|
-
* run-summary VALUE on success and THROWS a typed {@link WorkflowError} on every failure path. It
|
|
3345
|
-
* does NOT build a {@link ToolResult} itself — the `@orkestrel/agent` package's `ToolManager`
|
|
3346
|
-
* performs the ONE canonical wrap (`{ id, name, value }` on a return; `{ id, name, error }` on a
|
|
3347
|
-
* throw, ISOLATED so nothing escapes the run), so the outcome appears EXACTLY ONCE, identically,
|
|
3348
|
-
* over BOTH the agent loop and MCP (a throw → MCP `isError: true`):
|
|
3349
|
-
* - **No authored args** (an empty `arguments`) ⇒ runs the WRAPPED `definition`.
|
|
3350
|
-
* - **A `steps` array** ⇒ the FLAT form: parse it, {@link import('./helpers.js').expandSteps} it.
|
|
3351
|
-
* - **Otherwise** ⇒ the nested form: {@link createWorkflowDraftContract}-parse it,
|
|
3352
|
-
* {@link import('./helpers.js').completeDraft} it.
|
|
3353
|
-
* - **Strict gate** ⇒ the expanded / completed result is validated against
|
|
3354
|
-
* {@link createWorkflowContract}.`is`; a blob that can't expand, or whose result fails the strict
|
|
3355
|
-
* gate (e.g. an explicit empty `id`, `concurrency: 0`) ⇒ THROW a `TOOL` {@link WorkflowError} (no run).
|
|
3356
|
-
* - **Over-deep / cyclic** ⇒ THROW a `DEPTH` {@link WorkflowError} when the nested run would exceed
|
|
3357
|
-
* {@link MAX_WORKFLOW_DEPTH}, or the target workflow id is already an ancestor (a cycle) — the
|
|
3358
|
-
* SAME `code` {@link createAgentFunction}'s own guard raises. Enforced HERE, INSIDE this
|
|
3359
|
-
* handler, before ever calling `runner.execute` — the engine itself performs no such check.
|
|
3360
|
-
* - **Otherwise** ⇒ `runner.execute(target)`, RETURNING the plain summary of the terminal run
|
|
3361
|
-
* (`{ status, count }`, via {@link workflowToolSummary}).
|
|
3362
|
-
*
|
|
3363
|
-
* The tool executes AUTHORED STRUCTURE, not consumer behavior: a nested tree authored through
|
|
3364
|
-
* it (flat, draft, or full form) carries no {@link WorkflowFunctions} registry, so EVERY one of
|
|
3365
|
-
* its tasks auto-completes under the no-handler rule. This handler validates and synthesizes
|
|
3366
|
-
* shape — it never runs a caller's handlers.
|
|
3367
|
-
*
|
|
3368
|
-
* @param definition - The workflow the tool runs when called with no authored args
|
|
3369
|
-
* @param runner - The {@link WorkflowRunnerInterface} that executes the (nested) workflow
|
|
3370
|
-
* @param options - The depth + ancestry to run the nested workflow under (see
|
|
3371
|
-
* {@link WorkflowToolOptions}); omitted ⇒ depth `0` / empty ancestry (a top-level wrap)
|
|
3372
|
-
* @returns A {@link ToolInterface} (named {@link import('./constants.js').WORKFLOW_TOOL_NAME})
|
|
3373
|
-
* whose `parameters` advertise the FLAT authoring schema (the nested form stays accepted)
|
|
2897
|
+
* `options.functions` flows into every workflow the manager mints (`add`, via
|
|
2898
|
+
* {@link createWorkflow}) or hydrates (`open`'s registry-miss path, via
|
|
2899
|
+
* {@link restoreWorkflow}), so a hydrated workflow is RUNNABLE rather than a dead snapshot
|
|
2900
|
+
* mirror. `options.store` is the EXACT analogue of the twins' `store` seam — omitted ⇒ the
|
|
2901
|
+
* manager is registry-only (`open` resolves only what is registered, `save` is a no-op). This
|
|
2902
|
+
* is PURELY ADDITIVE: direct {@link WorkflowStoreInterface} use and
|
|
2903
|
+
* {@link restoreWorkflow} remain valid — the manager is one more caller-driven persistence
|
|
2904
|
+
* seam, not a replacement.
|
|
2905
|
+
*
|
|
2906
|
+
* @param options - The optional `store` seam and the `functions` registry threaded into every mint/hydrate
|
|
2907
|
+
* @returns A working {@link WorkflowManagerInterface}
|
|
3374
2908
|
*
|
|
3375
2909
|
* @example
|
|
3376
2910
|
* ```ts
|
|
3377
|
-
* import {
|
|
2911
|
+
* import { createMemoryWorkflowStore, createWorkflowManager } from '@src/core'
|
|
3378
2912
|
*
|
|
3379
|
-
* const
|
|
3380
|
-
*
|
|
3381
|
-
*
|
|
3382
|
-
*
|
|
2913
|
+
* const manager = createWorkflowManager({
|
|
2914
|
+
* store: createMemoryWorkflowStore(),
|
|
2915
|
+
* functions: { compile: async (controller) => `built ${controller.task.id}` },
|
|
2916
|
+
* })
|
|
2917
|
+
* const workflow = manager.add(definition) // minted, registered, RUNNABLE
|
|
2918
|
+
* await manager.save(workflow.id) // persisted to the store
|
|
2919
|
+
* const reopened = await manager.open(workflow.id) // already registered — no store hit
|
|
3383
2920
|
* ```
|
|
3384
2921
|
*/
|
|
3385
|
-
function
|
|
3386
|
-
|
|
3387
|
-
const draft = createWorkflowDraftContract();
|
|
3388
|
-
const steps = createContract(workflowStepsShape);
|
|
3389
|
-
const depth = options?.depth ?? 0;
|
|
3390
|
-
const ancestry = options?.ancestry ?? [];
|
|
3391
|
-
return createTool({
|
|
3392
|
-
name: WORKFLOW_TOOL_NAME,
|
|
3393
|
-
description: WORKFLOW_TOOL_DESCRIPTION,
|
|
3394
|
-
parameters: schemaToParameters(steps.schema),
|
|
3395
|
-
execute: async (args) => {
|
|
3396
|
-
let target;
|
|
3397
|
-
if (Object.keys(args).length === 0) target = definition;
|
|
3398
|
-
else if (Array.isArray(args.steps)) {
|
|
3399
|
-
const flat = steps.parse(args);
|
|
3400
|
-
target = flat === void 0 ? void 0 : expandSteps(flat);
|
|
3401
|
-
} else {
|
|
3402
|
-
const parsed = draft.parse(args);
|
|
3403
|
-
target = parsed === void 0 ? void 0 : completeDraft(parsed);
|
|
3404
|
-
}
|
|
3405
|
-
if (target === void 0 || !strict.is(target)) throw new WorkflowError("TOOL", "malformed workflow definition", { workflow: definition.id });
|
|
3406
|
-
if (depth + 1 > 8) throw new WorkflowError("DEPTH", `nested workflow exceeds max depth 8`, {
|
|
3407
|
-
workflow: target.id,
|
|
3408
|
-
depth,
|
|
3409
|
-
max: 8
|
|
3410
|
-
});
|
|
3411
|
-
const tag = workflowTag(target.id);
|
|
3412
|
-
if (ancestry.includes(tag)) throw new WorkflowError("DEPTH", `workflow '${target.id}' is already an ancestor (cycle)`, {
|
|
3413
|
-
workflow: target.id,
|
|
3414
|
-
ancestry: [...ancestry]
|
|
3415
|
-
});
|
|
3416
|
-
return workflowToolSummary(await runner.execute(target));
|
|
3417
|
-
}
|
|
3418
|
-
});
|
|
2922
|
+
function createWorkflowManager(options) {
|
|
2923
|
+
return new WorkflowManager(options);
|
|
3419
2924
|
}
|
|
3420
2925
|
/**
|
|
3421
2926
|
* Create the safe cross-environment cooperative-yield default — a
|
|
@@ -3509,6 +3014,6 @@ function createRunner(options) {
|
|
|
3509
3014
|
return new Runner(options);
|
|
3510
3015
|
}
|
|
3511
3016
|
//#endregion
|
|
3512
|
-
export { Controller, DEFAULT_BAIL, DEFAULT_PHASE_CONCURRENCY, DatabaseWorkflowStore,
|
|
3017
|
+
export { Controller, DEFAULT_BAIL, DEFAULT_PHASE_CONCURRENCY, DatabaseWorkflowStore, MemoryWorkflowStore, PHASE_STATUSES, Phase, PhaseManager, Runner, Scheduler, TASK_STATUSES, TASK_TRANSITIONS, TERMINAL_TASK_STATUSES, Task, TaskController, TaskManager, WORKFLOW_STATUSES, Workflow, WorkflowError, WorkflowManager, WorkflowRunner, assertSnapshot, buildPhaseContext, buildTaskContext, buildWorkflowContext, canTransitionTask, collectResults, createDatabaseWorkflowStore, createDeferred, createMemoryWorkflowStore, createRunner, createScheduler, createWorkflow, createWorkflowContract, createWorkflowManager, createWorkflowRunner, definitionToSnapshot, deriveBoundary, derivePhaseStatus, deriveWorkflowStatus, failure, findFailure, insertEntry, isTerminalStatus, isWorkflowError, isWorkflowSnapshot, moveEntry, parkSignal, phaseDefinitionToSnapshot, phaseShape, phaseUpdateShape, restoreWorkflow, success, taskDefinitionToSnapshot, taskShape, taskUpdateShape, workflowShape };
|
|
3513
3018
|
|
|
3514
3019
|
//# sourceMappingURL=index.js.map
|