@osolmaz/pi-workflows 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +182 -0
- package/dist/extension/executor.d.ts +58 -0
- package/dist/extension/executor.js +201 -0
- package/dist/extension/executor.js.map +1 -0
- package/dist/extension/index.d.ts +17 -0
- package/dist/extension/index.js +504 -0
- package/dist/extension/index.js.map +1 -0
- package/dist/extension/widget.d.ts +21 -0
- package/dist/extension/widget.js +142 -0
- package/dist/extension/widget.js.map +1 -0
- package/dist/render/ansi.d.ts +16 -0
- package/dist/render/ansi.js +42 -0
- package/dist/render/ansi.js.map +1 -0
- package/dist/render/canvas.d.ts +40 -0
- package/dist/render/canvas.js +177 -0
- package/dist/render/canvas.js.map +1 -0
- package/dist/render/format.d.ts +3 -0
- package/dist/render/format.js +17 -0
- package/dist/render/format.js.map +1 -0
- package/dist/render/graph-render.d.ts +22 -0
- package/dist/render/graph-render.js +520 -0
- package/dist/render/graph-render.js.map +1 -0
- package/dist/render/graph.d.ts +46 -0
- package/dist/render/graph.js +272 -0
- package/dist/render/graph.js.map +1 -0
- package/dist/viewer/cli.d.ts +10 -0
- package/dist/viewer/cli.js +132 -0
- package/dist/viewer/cli.js.map +1 -0
- package/dist/viewer/render.d.ts +19 -0
- package/dist/viewer/render.js +162 -0
- package/dist/viewer/render.js.map +1 -0
- package/dist/viewer/tui.d.ts +11 -0
- package/dist/viewer/tui.js +140 -0
- package/dist/viewer/tui.js.map +1 -0
- package/dist/viewer/watch.d.ts +9 -0
- package/dist/viewer/watch.js +46 -0
- package/dist/viewer/watch.js.map +1 -0
- package/dist/workflows/decision.d.ts +25 -0
- package/dist/workflows/decision.js +96 -0
- package/dist/workflows/decision.js.map +1 -0
- package/dist/workflows/definition.d.ts +9 -0
- package/dist/workflows/definition.js +61 -0
- package/dist/workflows/definition.js.map +1 -0
- package/dist/workflows/engine.d.ts +65 -0
- package/dist/workflows/engine.js +574 -0
- package/dist/workflows/engine.js.map +1 -0
- package/dist/workflows/errors.d.ts +9 -0
- package/dist/workflows/errors.js +24 -0
- package/dist/workflows/errors.js.map +1 -0
- package/dist/workflows/graph.d.ts +17 -0
- package/dist/workflows/graph.js +127 -0
- package/dist/workflows/graph.js.map +1 -0
- package/dist/workflows/index.d.ts +11 -0
- package/dist/workflows/index.js +11 -0
- package/dist/workflows/index.js.map +1 -0
- package/dist/workflows/json.d.ts +14 -0
- package/dist/workflows/json.js +134 -0
- package/dist/workflows/json.js.map +1 -0
- package/dist/workflows/loader.d.ts +28 -0
- package/dist/workflows/loader.js +94 -0
- package/dist/workflows/loader.js.map +1 -0
- package/dist/workflows/schema.d.ts +7 -0
- package/dist/workflows/schema.js +176 -0
- package/dist/workflows/schema.js.map +1 -0
- package/dist/workflows/shell.d.ts +9 -0
- package/dist/workflows/shell.js +177 -0
- package/dist/workflows/shell.js.map +1 -0
- package/dist/workflows/store.d.ts +35 -0
- package/dist/workflows/store.js +181 -0
- package/dist/workflows/store.js.map +1 -0
- package/dist/workflows/text.d.ts +10 -0
- package/dist/workflows/text.js +32 -0
- package/dist/workflows/text.js.map +1 -0
- package/dist/workflows/types.d.ts +280 -0
- package/dist/workflows/types.js +2 -0
- package/dist/workflows/types.js.map +1 -0
- package/docs/development.md +130 -0
- package/docs/run-bundles.md +114 -0
- package/docs/workflows.md +311 -0
- package/examples/workflows/autoimplement.workflow.ts +92 -0
- package/examples/workflows/autoresearch.workflow.ts +139 -0
- package/examples/workflows/branch.workflow.ts +63 -0
- package/examples/workflows/echo.workflow.ts +23 -0
- package/examples/workflows/elegant-solution.workflow.ts +95 -0
- package/examples/workflows/shell.workflow.ts +31 -0
- package/examples/workflows/two-turn.workflow.ts +64 -0
- package/package.json +80 -0
- package/src/extension/executor.ts +251 -0
- package/src/extension/index.ts +627 -0
- package/src/extension/widget.ts +183 -0
- package/src/render/ansi.ts +47 -0
- package/src/render/canvas.ts +196 -0
- package/src/render/format.ts +19 -0
- package/src/render/graph-render.ts +738 -0
- package/src/render/graph.ts +341 -0
- package/src/viewer/cli.ts +150 -0
- package/src/viewer/render.ts +236 -0
- package/src/viewer/tui.ts +159 -0
- package/src/viewer/watch.ts +55 -0
- package/src/workflows/decision.ts +127 -0
- package/src/workflows/definition.ts +104 -0
- package/src/workflows/engine.ts +793 -0
- package/src/workflows/errors.ts +27 -0
- package/src/workflows/graph.ts +161 -0
- package/src/workflows/index.ts +76 -0
- package/src/workflows/json.ts +155 -0
- package/src/workflows/loader.ts +123 -0
- package/src/workflows/schema.ts +218 -0
- package/src/workflows/shell.ts +199 -0
- package/src/workflows/store.ts +234 -0
- package/src/workflows/text.ts +34 -0
- package/src/workflows/types.ts +318 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { assertValidWorkflowDefinitionShape } from "./schema.js";
|
|
2
|
+
/**
|
|
3
|
+
* Validate the full graph: shape, known start node, known edge targets, and
|
|
4
|
+
* at most one outgoing edge per node.
|
|
5
|
+
*/
|
|
6
|
+
export function validateWorkflowDefinition(workflow) {
|
|
7
|
+
assertValidWorkflowDefinitionShape(workflow);
|
|
8
|
+
if (!Object.hasOwn(workflow.nodes, workflow.startAt)) {
|
|
9
|
+
throw new Error(`Workflow start node is missing: ${workflow.startAt}`);
|
|
10
|
+
}
|
|
11
|
+
const outgoingEdges = new Set();
|
|
12
|
+
for (const edge of workflow.edges) {
|
|
13
|
+
validateWorkflowEdge(workflow, edge, outgoingEdges);
|
|
14
|
+
}
|
|
15
|
+
assertAllNodesReachable(workflow);
|
|
16
|
+
}
|
|
17
|
+
/** Reject nodes that no path from `startAt` can ever reach. */
|
|
18
|
+
function assertAllNodesReachable(workflow) {
|
|
19
|
+
const reachable = new Set([workflow.startAt]);
|
|
20
|
+
const queue = [workflow.startAt];
|
|
21
|
+
while (queue.length > 0) {
|
|
22
|
+
const nodeId = queue.shift();
|
|
23
|
+
for (const edge of workflow.edges) {
|
|
24
|
+
if (edge.from !== nodeId) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const targets = "to" in edge ? [edge.to] : Object.values(edge.switch.cases);
|
|
28
|
+
for (const target of targets) {
|
|
29
|
+
if (!reachable.has(target)) {
|
|
30
|
+
reachable.add(target);
|
|
31
|
+
queue.push(target);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const unreachable = Object.keys(workflow.nodes).filter((nodeId) => !reachable.has(nodeId));
|
|
37
|
+
if (unreachable.length > 0) {
|
|
38
|
+
throw new Error(`Workflow has unreachable nodes: ${unreachable.join(", ")}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function assertKnownNode(workflow, nodeId, description) {
|
|
42
|
+
// Own-property check so ids like "toString" cannot resolve through the
|
|
43
|
+
// Object prototype and later dispatch an inherited function as a node.
|
|
44
|
+
if (!Object.hasOwn(workflow.nodes, nodeId)) {
|
|
45
|
+
throw new Error(`${description}: ${nodeId}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function validateWorkflowEdge(workflow, edge, outgoingEdges) {
|
|
49
|
+
assertKnownNode(workflow, edge.from, "Workflow edge references unknown from-node");
|
|
50
|
+
if (workflow.nodes[edge.from]?.nodeType === "checkpoint") {
|
|
51
|
+
// A checkpoint terminates the run as `waiting` and nothing resumes it,
|
|
52
|
+
// so an outgoing edge would make its targets silently unreachable.
|
|
53
|
+
throw new Error(`Workflow checkpoint node must not declare an outgoing edge: ${edge.from}`);
|
|
54
|
+
}
|
|
55
|
+
if (outgoingEdges.has(edge.from)) {
|
|
56
|
+
throw new Error(`Workflow node must not declare multiple outgoing edges: ${edge.from}`);
|
|
57
|
+
}
|
|
58
|
+
outgoingEdges.add(edge.from);
|
|
59
|
+
if ("to" in edge) {
|
|
60
|
+
assertKnownNode(workflow, edge.to, "Workflow edge references unknown to-node");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
for (const target of Object.values(edge.switch.cases)) {
|
|
64
|
+
assertKnownNode(workflow, target, "Workflow switch references unknown to-node");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resolve the next node after `from` completed with `output`. Switch edges
|
|
69
|
+
* route on a JSON path into the output (`$.field` or `$output.field`) or the
|
|
70
|
+
* node result (`$result.outcome`).
|
|
71
|
+
*/
|
|
72
|
+
export function resolveNext(edges, from, output, result) {
|
|
73
|
+
const edge = edges.find((candidate) => candidate.from === from);
|
|
74
|
+
if (!edge) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
if ("to" in edge) {
|
|
78
|
+
return edge.to;
|
|
79
|
+
}
|
|
80
|
+
return resolveSwitchTarget(edge, output, result);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Resolve routing for a failed node. Only `$result.` switch edges apply, so a
|
|
84
|
+
* workflow can explicitly route on outcomes like `failed` or `timed_out`.
|
|
85
|
+
*/
|
|
86
|
+
export function resolveNextForOutcome(edges, from, result) {
|
|
87
|
+
const edge = edges.find((candidate) => candidate.from === from);
|
|
88
|
+
if (!edge || "to" in edge || !edge.switch.on.startsWith("$result.")) {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return resolveSwitchTarget(edge, undefined, result);
|
|
92
|
+
}
|
|
93
|
+
function resolveSwitchTarget(edge, output, result) {
|
|
94
|
+
const value = getBySwitchPath(output, result, edge.switch.on);
|
|
95
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
|
|
96
|
+
throw new Error(`Workflow switch value must be scalar for ${edge.switch.on}`);
|
|
97
|
+
}
|
|
98
|
+
const next = edge.switch.cases[String(value)];
|
|
99
|
+
if (!next) {
|
|
100
|
+
throw new Error(`No workflow switch case for ${edge.switch.on}=${JSON.stringify(value)}`);
|
|
101
|
+
}
|
|
102
|
+
return next;
|
|
103
|
+
}
|
|
104
|
+
function getBySwitchPath(output, result, jsonPath) {
|
|
105
|
+
if (jsonPath.startsWith("$result.")) {
|
|
106
|
+
return getByPath(result, `$.${jsonPath.slice("$result.".length)}`);
|
|
107
|
+
}
|
|
108
|
+
if (jsonPath.startsWith("$output.")) {
|
|
109
|
+
return getByPath(output, `$.${jsonPath.slice("$output.".length)}`);
|
|
110
|
+
}
|
|
111
|
+
return getByPath(output, jsonPath);
|
|
112
|
+
}
|
|
113
|
+
function getByPath(value, jsonPath) {
|
|
114
|
+
if (!jsonPath.startsWith("$.")) {
|
|
115
|
+
throw new Error(`Unsupported JSON path: ${jsonPath}`);
|
|
116
|
+
}
|
|
117
|
+
return jsonPath
|
|
118
|
+
.slice(2)
|
|
119
|
+
.split(".")
|
|
120
|
+
.reduce((current, key) => {
|
|
121
|
+
if (current == null || typeof current !== "object") {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
return current[key];
|
|
125
|
+
}, value);
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/workflows/graph.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { agent, action, checkpoint, compute, defineWorkflow, isWorkflowDefinition, shell, } from "./definition.js";
|
|
2
|
+
export { decision, decisionEdge, type DecisionDefinition } from "./decision.js";
|
|
3
|
+
export { WorkflowEngine, appendStepContract } from "./engine.js";
|
|
4
|
+
export { CancelledError, TimeoutError } from "./errors.js";
|
|
5
|
+
export { extractJsonValue, parseJsonValue, parseStrictJsonValue, type JsonParseMode, } from "./json.js";
|
|
6
|
+
export { resolveNext, resolveNextForOutcome, validateWorkflowDefinition } from "./graph.js";
|
|
7
|
+
export { discoverWorkflows, loadWorkflowFile, resolveWorkflowRef, workflowFileStem, workflowSearchDirs, type DiscoveredWorkflow, type WorkflowSearchPaths, } from "./loader.js";
|
|
8
|
+
export { renderShellCommand, runShellAction } from "./shell.js";
|
|
9
|
+
export { sanitizeText, stripAnsi } from "./text.js";
|
|
10
|
+
export { DEFINITION_SNAPSHOT_SCHEMA, RUN_BUNDLE_SCHEMA, TRACE_EVENT_SCHEMA, WorkflowRunStore, createDefinitionSnapshot, createRunId, listRunBundles, readRunBundle, workflowRunsBaseDir, type LoadedRunBundle, } from "./store.js";
|
|
11
|
+
export type { AgentNodeDefinition, AgentStepContract, AgentStepExecutor, AgentStepRequest, AgentStepSubmission, ActionNodeDefinition, CheckpointNodeDefinition, ComputeNodeDefinition, FunctionActionNodeDefinition, MaybePromise, ShellActionExecution, ShellActionNodeDefinition, ShellActionResult, WorkflowActionReceipt, WorkflowDefinition, WorkflowDefinitionSnapshot, WorkflowEdge, WorkflowEngineOptions, WorkflowNodeCommon, WorkflowNodeContext, WorkflowNodeDefinition, WorkflowNodeOutcome, WorkflowNodeResult, WorkflowNodeSnapshot, WorkflowPresentationContext, WorkflowRunManifest, WorkflowRunResult, WorkflowRunState, WorkflowRunStatus, WorkflowStepRecord, WorkflowTraceEvent, WorkflowTraceEventDraft, } from "./types.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { agent, action, checkpoint, compute, defineWorkflow, isWorkflowDefinition, shell, } from "./definition.js";
|
|
2
|
+
export { decision, decisionEdge } from "./decision.js";
|
|
3
|
+
export { WorkflowEngine, appendStepContract } from "./engine.js";
|
|
4
|
+
export { CancelledError, TimeoutError } from "./errors.js";
|
|
5
|
+
export { extractJsonValue, parseJsonValue, parseStrictJsonValue, } from "./json.js";
|
|
6
|
+
export { resolveNext, resolveNextForOutcome, validateWorkflowDefinition } from "./graph.js";
|
|
7
|
+
export { discoverWorkflows, loadWorkflowFile, resolveWorkflowRef, workflowFileStem, workflowSearchDirs, } from "./loader.js";
|
|
8
|
+
export { renderShellCommand, runShellAction } from "./shell.js";
|
|
9
|
+
export { sanitizeText, stripAnsi } from "./text.js";
|
|
10
|
+
export { DEFINITION_SNAPSHOT_SCHEMA, RUN_BUNDLE_SCHEMA, TRACE_EVENT_SCHEMA, WorkflowRunStore, createDefinitionSnapshot, createRunId, listRunBundles, readRunBundle, workflowRunsBaseDir, } from "./store.js";
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/workflows/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EACL,MAAM,EACN,UAAU,EACV,OAAO,EACP,cAAc,EACd,oBAAoB,EACpB,KAAK,GACN,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,QAAQ,EAAE,YAAY,EAA2B,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,oBAAoB,GAErB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAC5F,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,GAGnB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EACL,0BAA0B,EAC1B,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,wBAAwB,EACxB,WAAW,EACX,cAAc,EACd,aAAa,EACb,mBAAmB,GAEpB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type JsonParseMode = "strict" | "fenced" | "compat";
|
|
2
|
+
/**
|
|
3
|
+
* Parse a JSON value out of model text. `strict` requires the whole text to
|
|
4
|
+
* be JSON. `fenced` additionally accepts a ```json fenced block. `compat`
|
|
5
|
+
* (the default) also scans for the first balanced JSON object or array
|
|
6
|
+
* embedded in chatty output.
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseJsonValue(text: string, options?: {
|
|
9
|
+
mode?: JsonParseMode;
|
|
10
|
+
}): unknown;
|
|
11
|
+
/** Strict parse: the whole text must be a JSON value. */
|
|
12
|
+
export declare function parseStrictJsonValue(text: string): unknown;
|
|
13
|
+
/** Default tolerant parser: direct JSON, then fenced, then embedded object. */
|
|
14
|
+
export declare function extractJsonValue(text: string): unknown;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a JSON value out of model text. `strict` requires the whole text to
|
|
3
|
+
* be JSON. `fenced` additionally accepts a ```json fenced block. `compat`
|
|
4
|
+
* (the default) also scans for the first balanced JSON object or array
|
|
5
|
+
* embedded in chatty output.
|
|
6
|
+
*/
|
|
7
|
+
export function parseJsonValue(text, options = {}) {
|
|
8
|
+
const trimmed = typeof text === "string" ? text.trim() : "";
|
|
9
|
+
if (!trimmed) {
|
|
10
|
+
throw new Error("Expected JSON output, got empty text");
|
|
11
|
+
}
|
|
12
|
+
const mode = options.mode ?? "compat";
|
|
13
|
+
const direct = tryParse(trimmed);
|
|
14
|
+
if (direct.ok) {
|
|
15
|
+
return direct.value;
|
|
16
|
+
}
|
|
17
|
+
if (mode === "fenced" || mode === "compat") {
|
|
18
|
+
const fencedText = extractFencedJsonText(trimmed);
|
|
19
|
+
if (fencedText !== null) {
|
|
20
|
+
const fenced = tryParse(fencedText);
|
|
21
|
+
if (fenced.ok) {
|
|
22
|
+
return fenced.value;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (mode === "compat") {
|
|
27
|
+
for (const candidate of extractBalancedJsonCandidates(trimmed)) {
|
|
28
|
+
const parsed = tryParse(candidate);
|
|
29
|
+
if (parsed.ok) {
|
|
30
|
+
return parsed.value;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Could not parse JSON from text:\n${trimmed}`);
|
|
35
|
+
}
|
|
36
|
+
/** Strict parse: the whole text must be a JSON value. */
|
|
37
|
+
export function parseStrictJsonValue(text) {
|
|
38
|
+
return parseJsonValue(text, { mode: "strict" });
|
|
39
|
+
}
|
|
40
|
+
/** Default tolerant parser: direct JSON, then fenced, then embedded object. */
|
|
41
|
+
export function extractJsonValue(text) {
|
|
42
|
+
return parseJsonValue(text, { mode: "compat" });
|
|
43
|
+
}
|
|
44
|
+
function tryParse(text) {
|
|
45
|
+
try {
|
|
46
|
+
return { ok: true, value: JSON.parse(text) };
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return { ok: false };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function extractFencedJsonText(text) {
|
|
53
|
+
const openingFenceIndex = text.indexOf("```");
|
|
54
|
+
if (openingFenceIndex === -1) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
let contentStart = openingFenceIndex + 3;
|
|
58
|
+
if (text.slice(contentStart, contentStart + 4).toLowerCase() === "json" &&
|
|
59
|
+
isFenceWhitespace(text[contentStart + 4])) {
|
|
60
|
+
contentStart += 4;
|
|
61
|
+
}
|
|
62
|
+
while (isFenceWhitespace(text[contentStart])) {
|
|
63
|
+
contentStart += 1;
|
|
64
|
+
}
|
|
65
|
+
const closingFenceIndex = text.indexOf("```", contentStart);
|
|
66
|
+
if (closingFenceIndex === -1) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return text.slice(contentStart, closingFenceIndex).trim();
|
|
70
|
+
}
|
|
71
|
+
function isFenceWhitespace(char) {
|
|
72
|
+
return char === " " || char === "\n" || char === "\r" || char === "\t";
|
|
73
|
+
}
|
|
74
|
+
// Bound on balanced-scan attempts. Without it, pathological text such as
|
|
75
|
+
// twenty thousand opening braces makes candidate extraction quadratic and
|
|
76
|
+
// blocks the event loop, which also prevents node timeouts from firing.
|
|
77
|
+
const MAX_CANDIDATE_SCANS = 64;
|
|
78
|
+
function extractBalancedJsonCandidates(text) {
|
|
79
|
+
const candidates = [];
|
|
80
|
+
let scans = 0;
|
|
81
|
+
for (let index = 0; index < text.length && scans < MAX_CANDIDATE_SCANS; index += 1) {
|
|
82
|
+
if (text[index] !== "{" && text[index] !== "[") {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
scans += 1;
|
|
86
|
+
const candidate = scanBalanced(text, index);
|
|
87
|
+
if (candidate !== null) {
|
|
88
|
+
candidates.push(candidate);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return candidates;
|
|
92
|
+
}
|
|
93
|
+
function scanBalanced(text, startIndex) {
|
|
94
|
+
const stack = [];
|
|
95
|
+
let inString = false;
|
|
96
|
+
let escaped = false;
|
|
97
|
+
for (let index = startIndex; index < text.length; index += 1) {
|
|
98
|
+
const char = text[index];
|
|
99
|
+
if (inString) {
|
|
100
|
+
if (escaped) {
|
|
101
|
+
escaped = false;
|
|
102
|
+
}
|
|
103
|
+
else if (char === "\\") {
|
|
104
|
+
escaped = true;
|
|
105
|
+
}
|
|
106
|
+
else if (char === '"') {
|
|
107
|
+
inString = false;
|
|
108
|
+
}
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (char === '"') {
|
|
112
|
+
inString = true;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (char === "{" || char === "[") {
|
|
116
|
+
stack.push(char);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (char !== "}" && char !== "]") {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const open = stack.at(-1);
|
|
123
|
+
const matches = (open === "{" && char === "}") || (open === "[" && char === "]");
|
|
124
|
+
if (!matches) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
stack.pop();
|
|
128
|
+
if (stack.length === 0) {
|
|
129
|
+
return text.slice(startIndex, index + 1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/workflows/json.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,OAAO,GAA6B,EAAE;IACjF,MAAM,OAAO,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5D,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,QAAQ,CAAC;IAEtC,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;IACjC,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;QACd,OAAO,MAAM,CAAC,KAAK,CAAC;IACtB,CAAC;IAED,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC3C,MAAM,UAAU,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;QAClD,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;YACxB,MAAM,MAAM,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC;YACpC,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;gBACd,OAAO,MAAM,CAAC,KAAK,CAAC;YACtB,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,KAAK,MAAM,SAAS,IAAI,6BAA6B,CAAC,OAAO,CAAC,EAAE,CAAC;YAC/D,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;YACnC,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;gBACd,OAAO,MAAM,CAAC,KAAK,CAAC;YACtB,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,oCAAoC,OAAO,EAAE,CAAC,CAAC;AACjE,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,OAAO,cAAc,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;AAClD,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,cAAc,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB,CAAC,IAAY;IACzC,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9C,IAAI,iBAAiB,KAAK,CAAC,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,YAAY,GAAG,iBAAiB,GAAG,CAAC,CAAC;IACzC,IACE,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,YAAY,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM;QACnE,iBAAiB,CAAC,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,EACzC,CAAC;QACD,YAAY,IAAI,CAAC,CAAC;IACpB,CAAC;IACD,OAAO,iBAAiB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;QAC7C,YAAY,IAAI,CAAC,CAAC;IACpB,CAAC;IAED,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IAC5D,IAAI,iBAAiB,KAAK,CAAC,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,iBAAiB,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5D,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAwB;IACjD,OAAO,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,CAAC;AACzE,CAAC;AAED,yEAAyE;AACzE,0EAA0E;AAC1E,wEAAwE;AACxE,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAE/B,SAAS,6BAA6B,CAAC,IAAY;IACjD,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,IAAI,KAAK,GAAG,mBAAmB,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACnF,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC;YAC/C,SAAS;QACX,CAAC;QACD,KAAK,IAAI,CAAC,CAAC;QACX,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC5C,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;YACvB,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,YAAY,CAAC,IAAY,EAAE,UAAkB;IACpD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,KAAK,IAAI,KAAK,GAAG,UAAU,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAW,CAAC;QAEnC,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,GAAG,KAAK,CAAC;YAClB,CAAC;iBAAM,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBACzB,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;iBAAM,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACxB,QAAQ,GAAG,KAAK,CAAC;YACnB,CAAC;YACD,SAAS;QACX,CAAC;QAED,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjB,QAAQ,GAAG,IAAI,CAAC;YAChB,SAAS;QACX,CAAC;QACD,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjB,SAAS;QACX,CAAC;QACD,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjC,SAAS;QACX,CAAC;QAED,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1B,MAAM,OAAO,GAAG,CAAC,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,CAAC,CAAC;QACjF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,IAAI,CAAC;QACd,CAAC;QACD,KAAK,CAAC,GAAG,EAAE,CAAC;QACZ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { WorkflowDefinition } from "./types.js";
|
|
2
|
+
export type DiscoveredWorkflow = {
|
|
3
|
+
name: string;
|
|
4
|
+
path: string;
|
|
5
|
+
source: "project" | "global" | "path";
|
|
6
|
+
};
|
|
7
|
+
export type WorkflowSearchPaths = {
|
|
8
|
+
cwd: string;
|
|
9
|
+
homeDir?: string;
|
|
10
|
+
};
|
|
11
|
+
/** Directories scanned for `*.workflow.ts` files, in precedence order. */
|
|
12
|
+
export declare function workflowSearchDirs(options: WorkflowSearchPaths): {
|
|
13
|
+
dir: string;
|
|
14
|
+
source: "project" | "global";
|
|
15
|
+
}[];
|
|
16
|
+
export declare function workflowFileStem(filePath: string): string;
|
|
17
|
+
/** Load a workflow module from disk. The default export must be `defineWorkflow(...)`. */
|
|
18
|
+
export declare function loadWorkflowFile(filePath: string): Promise<WorkflowDefinition>;
|
|
19
|
+
/** Discover named workflows in the project and global workflow directories. */
|
|
20
|
+
export declare function discoverWorkflows(options: WorkflowSearchPaths): Promise<DiscoveredWorkflow[]>;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve a `/workflow` argument to a workflow file. Accepts a discovered
|
|
23
|
+
* workflow name or a direct path to a `*.workflow.ts` file.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveWorkflowRef(ref: string, options: WorkflowSearchPaths): Promise<{
|
|
26
|
+
path: string;
|
|
27
|
+
source: DiscoveredWorkflow["source"];
|
|
28
|
+
}>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
import { createJiti } from "jiti";
|
|
6
|
+
import { isWorkflowDefinition } from "./definition.js";
|
|
7
|
+
const WORKFLOW_FILE_SUFFIXES = [".workflow.ts", ".workflow.js", ".workflow.mts", ".workflow.mjs"];
|
|
8
|
+
/** Directories scanned for `*.workflow.ts` files, in precedence order. */
|
|
9
|
+
export function workflowSearchDirs(options) {
|
|
10
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
11
|
+
return [
|
|
12
|
+
{ dir: path.join(options.cwd, ".pi", "workflows"), source: "project" },
|
|
13
|
+
{ dir: path.join(homeDir, ".pi", "agent", "workflows"), source: "global" },
|
|
14
|
+
];
|
|
15
|
+
}
|
|
16
|
+
function isWorkflowFile(fileName) {
|
|
17
|
+
return WORKFLOW_FILE_SUFFIXES.some((suffix) => fileName.endsWith(suffix));
|
|
18
|
+
}
|
|
19
|
+
export function workflowFileStem(filePath) {
|
|
20
|
+
const base = path.basename(filePath);
|
|
21
|
+
const suffix = WORKFLOW_FILE_SUFFIXES.find((candidate) => base.endsWith(candidate));
|
|
22
|
+
return suffix ? base.slice(0, -suffix.length) : base;
|
|
23
|
+
}
|
|
24
|
+
// Alias the package name to this module's own entry so workflow files can
|
|
25
|
+
// `import { agent } from "@osolmaz/pi-workflows"` whether the engine runs from src
|
|
26
|
+
// (tests, tsx) or from the built dist inside the installed package.
|
|
27
|
+
const SELF_ENTRY = path.join(path.dirname(fileURLToPath(import.meta.url)), "index");
|
|
28
|
+
/** Load a workflow module from disk. The default export must be `defineWorkflow(...)`. */
|
|
29
|
+
export async function loadWorkflowFile(filePath) {
|
|
30
|
+
const absolutePath = path.resolve(filePath);
|
|
31
|
+
const jiti = createJiti(pathToFileURL(absolutePath).href, {
|
|
32
|
+
interopDefault: true,
|
|
33
|
+
moduleCache: false,
|
|
34
|
+
alias: { "@osolmaz/pi-workflows": SELF_ENTRY },
|
|
35
|
+
});
|
|
36
|
+
const loaded = (await jiti.import(absolutePath, { default: true }));
|
|
37
|
+
if (!isWorkflowDefinition(loaded)) {
|
|
38
|
+
throw new Error(`Workflow module must default-export defineWorkflow(...): ${absolutePath}`);
|
|
39
|
+
}
|
|
40
|
+
return loaded;
|
|
41
|
+
}
|
|
42
|
+
/** Discover named workflows in the project and global workflow directories. */
|
|
43
|
+
export async function discoverWorkflows(options) {
|
|
44
|
+
const discovered = [];
|
|
45
|
+
const seenNames = new Set();
|
|
46
|
+
for (const { dir, source } of workflowSearchDirs(options)) {
|
|
47
|
+
for (const filePath of await listWorkflowFiles(dir)) {
|
|
48
|
+
const name = workflowFileStem(filePath);
|
|
49
|
+
if (seenNames.has(name)) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
seenNames.add(name);
|
|
53
|
+
discovered.push({ name, path: filePath, source });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return discovered;
|
|
57
|
+
}
|
|
58
|
+
async function listWorkflowFiles(dir) {
|
|
59
|
+
let entries;
|
|
60
|
+
try {
|
|
61
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
return entries
|
|
67
|
+
.filter((entry) => entry.isFile() && isWorkflowFile(entry.name))
|
|
68
|
+
.map((entry) => path.join(dir, entry.name))
|
|
69
|
+
.sort();
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Resolve a `/workflow` argument to a workflow file. Accepts a discovered
|
|
73
|
+
* workflow name or a direct path to a `*.workflow.ts` file.
|
|
74
|
+
*/
|
|
75
|
+
export async function resolveWorkflowRef(ref, options) {
|
|
76
|
+
if (looksLikePath(ref)) {
|
|
77
|
+
const absolutePath = path.resolve(options.cwd, ref);
|
|
78
|
+
await fs.access(absolutePath);
|
|
79
|
+
return { path: absolutePath, source: "path" };
|
|
80
|
+
}
|
|
81
|
+
const discovered = await discoverWorkflows(options);
|
|
82
|
+
const match = discovered.find((workflow) => workflow.name === ref);
|
|
83
|
+
if (!match) {
|
|
84
|
+
const available = discovered.map((workflow) => workflow.name).join(", ") || "(none)";
|
|
85
|
+
throw new Error(`Unknown workflow ${JSON.stringify(ref)}. Available workflows: ${available}`);
|
|
86
|
+
}
|
|
87
|
+
return { path: match.path, source: match.source };
|
|
88
|
+
}
|
|
89
|
+
function looksLikePath(ref) {
|
|
90
|
+
return (ref.includes("/") ||
|
|
91
|
+
ref.includes("\\") ||
|
|
92
|
+
WORKFLOW_FILE_SUFFIXES.some((suffix) => ref.endsWith(suffix)));
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../src/workflows/loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AgentNodeDefinition, ActionNodeDefinition, CheckpointNodeDefinition, ComputeNodeDefinition, ShellActionNodeDefinition, WorkflowDefinition } from "./types.js";
|
|
2
|
+
export declare function assertValidAgentNode(node: AgentNodeDefinition, nodeId?: string): void;
|
|
3
|
+
export declare function assertValidComputeNode(node: ComputeNodeDefinition, nodeId?: string): void;
|
|
4
|
+
export declare function assertValidActionNode(node: ActionNodeDefinition, nodeId?: string): void;
|
|
5
|
+
export declare function assertValidShellActionNode(node: ShellActionNodeDefinition, nodeId?: string): void;
|
|
6
|
+
export declare function assertValidCheckpointNode(node: CheckpointNodeDefinition, nodeId?: string): void;
|
|
7
|
+
export declare function assertValidWorkflowDefinitionShape(definition: WorkflowDefinition): void;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
const NODE_ID_PATTERN = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
2
|
+
function fail(message) {
|
|
3
|
+
throw new Error(`Invalid workflow definition: ${message}`);
|
|
4
|
+
}
|
|
5
|
+
function assertRecord(value, description) {
|
|
6
|
+
if (value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
7
|
+
fail(`${description} must be an object`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function assertOptionalFunction(value, description) {
|
|
11
|
+
if (value !== undefined && typeof value !== "function") {
|
|
12
|
+
fail(`${description} must be a function when provided`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function assertCommonNodeFields(node, nodeId) {
|
|
16
|
+
if (node.timeoutMs !== undefined &&
|
|
17
|
+
(typeof node.timeoutMs !== "number" || !Number.isFinite(node.timeoutMs) || node.timeoutMs <= 0)) {
|
|
18
|
+
fail(`node ${nodeId} timeoutMs must be a finite positive number`);
|
|
19
|
+
}
|
|
20
|
+
if (node.statusDetail !== undefined && typeof node.statusDetail !== "string") {
|
|
21
|
+
fail(`node ${nodeId} statusDetail must be a string`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function assertValidAgentNode(node, nodeId = "agent") {
|
|
25
|
+
if (typeof node.prompt !== "function") {
|
|
26
|
+
fail(`node ${nodeId} requires a prompt function`);
|
|
27
|
+
}
|
|
28
|
+
if (node.expectedOutput !== undefined && typeof node.expectedOutput !== "string") {
|
|
29
|
+
fail(`node ${nodeId} expectedOutput must be a string`);
|
|
30
|
+
}
|
|
31
|
+
assertOptionalFunction(node.validate, `node ${nodeId} validate`);
|
|
32
|
+
assertCommonNodeFields(node, nodeId);
|
|
33
|
+
}
|
|
34
|
+
export function assertValidComputeNode(node, nodeId = "compute") {
|
|
35
|
+
if (typeof node.run !== "function") {
|
|
36
|
+
fail(`node ${nodeId} requires a run function`);
|
|
37
|
+
}
|
|
38
|
+
assertCommonNodeFields(node, nodeId);
|
|
39
|
+
}
|
|
40
|
+
export function assertValidActionNode(node, nodeId = "action") {
|
|
41
|
+
// Dispatch discriminates with `"exec" in node`, so validation must use the
|
|
42
|
+
// same property semantics: a present-but-invalid `exec` is an error even
|
|
43
|
+
// when a `run` function exists.
|
|
44
|
+
const hasExec = "exec" in node;
|
|
45
|
+
const hasRun = "run" in node;
|
|
46
|
+
if (hasExec === hasRun) {
|
|
47
|
+
fail(`node ${nodeId} requires exactly one of run or exec`);
|
|
48
|
+
}
|
|
49
|
+
if (hasExec) {
|
|
50
|
+
if (typeof node.exec !== "function") {
|
|
51
|
+
fail(`node ${nodeId} exec must be a function`);
|
|
52
|
+
}
|
|
53
|
+
assertOptionalFunction(node.parse, `node ${nodeId} parse`);
|
|
54
|
+
}
|
|
55
|
+
else if (typeof node.run !== "function") {
|
|
56
|
+
fail(`node ${nodeId} run must be a function`);
|
|
57
|
+
}
|
|
58
|
+
assertCommonNodeFields(node, nodeId);
|
|
59
|
+
}
|
|
60
|
+
export function assertValidShellActionNode(node, nodeId = "shell") {
|
|
61
|
+
if (typeof node.exec !== "function") {
|
|
62
|
+
fail(`node ${nodeId} requires an exec function`);
|
|
63
|
+
}
|
|
64
|
+
assertOptionalFunction(node.parse, `node ${nodeId} parse`);
|
|
65
|
+
assertCommonNodeFields(node, nodeId);
|
|
66
|
+
}
|
|
67
|
+
export function assertValidCheckpointNode(node, nodeId = "checkpoint") {
|
|
68
|
+
if (node.summary !== undefined && typeof node.summary !== "string") {
|
|
69
|
+
fail(`node ${nodeId} summary must be a string`);
|
|
70
|
+
}
|
|
71
|
+
assertOptionalFunction(node.run, `node ${nodeId} run`);
|
|
72
|
+
assertCommonNodeFields(node, nodeId);
|
|
73
|
+
}
|
|
74
|
+
function assertValidNode(node, nodeId) {
|
|
75
|
+
switch (node.nodeType) {
|
|
76
|
+
case "agent":
|
|
77
|
+
assertValidAgentNode(node, nodeId);
|
|
78
|
+
return;
|
|
79
|
+
case "compute":
|
|
80
|
+
assertValidComputeNode(node, nodeId);
|
|
81
|
+
return;
|
|
82
|
+
case "action":
|
|
83
|
+
assertValidActionNode(node, nodeId);
|
|
84
|
+
return;
|
|
85
|
+
case "checkpoint":
|
|
86
|
+
assertValidCheckpointNode(node, nodeId);
|
|
87
|
+
return;
|
|
88
|
+
default:
|
|
89
|
+
fail(`node ${nodeId} has unknown nodeType ${String(node.nodeType)}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function assertValidEdgeShape(edge, index) {
|
|
93
|
+
assertRecord(edge, `edge ${index}`);
|
|
94
|
+
if (typeof edge.from !== "string" || edge.from.length === 0) {
|
|
95
|
+
fail(`edge ${index} requires a from node id`);
|
|
96
|
+
}
|
|
97
|
+
if ("to" in edge) {
|
|
98
|
+
if (typeof edge.to !== "string" || edge.to.length === 0) {
|
|
99
|
+
fail(`edge ${index} requires a to node id`);
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
assertRecord(edge.switch, `edge ${index} switch`);
|
|
104
|
+
if (typeof edge.switch.on !== "string" || edge.switch.on.length === 0) {
|
|
105
|
+
fail(`edge ${index} switch.on must be a JSON path string`);
|
|
106
|
+
}
|
|
107
|
+
// Routing only understands these prefixes; rejecting others here prevents
|
|
108
|
+
// the source node from executing its side effects before a routing error.
|
|
109
|
+
const on = edge.switch.on;
|
|
110
|
+
if (!on.startsWith("$.") && !on.startsWith("$output.") && !on.startsWith("$result.")) {
|
|
111
|
+
fail(`edge ${index} switch.on must start with "$.", "$output.", or "$result."`);
|
|
112
|
+
}
|
|
113
|
+
assertRecord(edge.switch.cases, `edge ${index} switch.cases`);
|
|
114
|
+
if (Object.keys(edge.switch.cases).length === 0) {
|
|
115
|
+
fail(`edge ${index} switch.cases must not be empty`);
|
|
116
|
+
}
|
|
117
|
+
for (const [caseKey, target] of Object.entries(edge.switch.cases)) {
|
|
118
|
+
if (typeof target !== "string" || target.length === 0) {
|
|
119
|
+
fail(`edge ${index} switch case ${JSON.stringify(caseKey)} must map to a node id`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Names claimed by `/workflow` subcommands; a workflow with one of these
|
|
125
|
+
* names could never be started because the keyword wins the argument slot.
|
|
126
|
+
*/
|
|
127
|
+
const RESERVED_WORKFLOW_NAMES = new Set(["cancel", "list", "pause", "resume"]);
|
|
128
|
+
export function assertValidWorkflowDefinitionShape(definition) {
|
|
129
|
+
assertRecord(definition, "workflow");
|
|
130
|
+
if (typeof definition.name !== "string" || definition.name.length === 0) {
|
|
131
|
+
fail("workflow requires a name");
|
|
132
|
+
}
|
|
133
|
+
if (RESERVED_WORKFLOW_NAMES.has(definition.name)) {
|
|
134
|
+
fail(`workflow name ${JSON.stringify(definition.name)} is reserved for /workflow subcommands`);
|
|
135
|
+
}
|
|
136
|
+
if (definition.title !== undefined &&
|
|
137
|
+
typeof definition.title !== "string" &&
|
|
138
|
+
typeof definition.title !== "function") {
|
|
139
|
+
fail("workflow title must be a string or function");
|
|
140
|
+
}
|
|
141
|
+
if (definition.presentationPrompt !== undefined &&
|
|
142
|
+
typeof definition.presentationPrompt !== "string" &&
|
|
143
|
+
typeof definition.presentationPrompt !== "function") {
|
|
144
|
+
fail("workflow presentationPrompt must be a string or function");
|
|
145
|
+
}
|
|
146
|
+
if (typeof definition.startAt !== "string" || definition.startAt.length === 0) {
|
|
147
|
+
fail("workflow requires startAt");
|
|
148
|
+
}
|
|
149
|
+
if (definition.maxSteps !== undefined &&
|
|
150
|
+
(typeof definition.maxSteps !== "number" ||
|
|
151
|
+
!Number.isInteger(definition.maxSteps) ||
|
|
152
|
+
definition.maxSteps <= 0)) {
|
|
153
|
+
fail("workflow maxSteps must be a positive integer");
|
|
154
|
+
}
|
|
155
|
+
assertRecord(definition.nodes, "workflow nodes");
|
|
156
|
+
if (Object.keys(definition.nodes).length === 0) {
|
|
157
|
+
fail("workflow requires at least one node");
|
|
158
|
+
}
|
|
159
|
+
for (const [nodeId, node] of Object.entries(definition.nodes)) {
|
|
160
|
+
if (!NODE_ID_PATTERN.test(nodeId)) {
|
|
161
|
+
fail(`node id ${JSON.stringify(nodeId)} must match ${NODE_ID_PATTERN.source}`);
|
|
162
|
+
}
|
|
163
|
+
// Ids like __proto__ or toString would collide with Object prototype
|
|
164
|
+
// members in the plain-object maps used for outputs and results.
|
|
165
|
+
if (nodeId in Object.prototype) {
|
|
166
|
+
fail(`node id ${JSON.stringify(nodeId)} shadows an Object prototype member`);
|
|
167
|
+
}
|
|
168
|
+
assertRecord(node, `node ${nodeId}`);
|
|
169
|
+
assertValidNode(node, nodeId);
|
|
170
|
+
}
|
|
171
|
+
if (!Array.isArray(definition.edges)) {
|
|
172
|
+
fail("workflow edges must be an array");
|
|
173
|
+
}
|
|
174
|
+
definition.edges.forEach(assertValidEdgeShape);
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=schema.js.map
|