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