@gobing-ai/ts-dual-workflow-engine 0.3.16 → 0.3.17
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 +22 -6
- package/dist/action-step.d.ts +61 -0
- package/dist/action-step.d.ts.map +1 -0
- package/dist/action-step.js +81 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +9 -0
- package/dist/events.d.ts +41 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/host.d.ts +10 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +41 -2
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/persistence.d.ts +35 -1
- package/dist/persistence.d.ts.map +1 -1
- package/dist/persistence.js +112 -5
- package/dist/run-lifecycle.d.ts +21 -2
- package/dist/run-lifecycle.d.ts.map +1 -1
- package/dist/run-lifecycle.js +80 -16
- package/dist/schema-sql.d.ts.map +1 -1
- package/dist/schema-sql.js +5 -0
- package/dist/schema.d.ts +4 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +4 -0
- package/dist/service.d.ts +36 -2
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +184 -0
- package/dist/state-machine.d.ts +2 -7
- package/dist/state-machine.d.ts.map +1 -1
- package/dist/state-machine.js +72 -71
- package/dist/transition-flow.d.ts +2 -0
- package/dist/transition-flow.d.ts.map +1 -1
- package/dist/transition-flow.js +46 -43
- package/dist/types.d.ts +59 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/schemas/state-machine-workflow.schema.json +5 -1
- package/schemas/transition-flow-workflow.schema.json +5 -1
- package/src/action-step.ts +130 -0
- package/src/errors.ts +11 -0
- package/src/events.ts +54 -4
- package/src/host.ts +53 -3
- package/src/index.ts +7 -1
- package/src/persistence.ts +137 -6
- package/src/run-lifecycle.ts +113 -15
- package/src/schema-sql.ts +5 -0
- package/src/schema.ts +4 -0
- package/src/service.ts +255 -1
- package/src/state-machine.ts +86 -114
- package/src/transition-flow.ts +59 -46
- package/src/types.ts +61 -2
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,UAAU,CAAC;AAEhD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAErD,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/ts-dual-workflow-engine",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.17",
|
|
4
4
|
"description": "@gobing-ai/ts-dual-workflow-engine — State-machine and transition-flow workflow runtime.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
"release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-dual-workflow-engine-v<version> && git push --tags' && exit 1"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@gobing-ai/ts-db": "^0.3.
|
|
52
|
-
"@gobing-ai/ts-infra": "^0.3.
|
|
53
|
-
"@gobing-ai/ts-runtime": "^0.3.
|
|
51
|
+
"@gobing-ai/ts-db": "^0.3.17",
|
|
52
|
+
"@gobing-ai/ts-infra": "^0.3.17",
|
|
53
|
+
"@gobing-ai/ts-runtime": "^0.3.17",
|
|
54
54
|
"zod": "^4.1.0"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
@@ -36,7 +36,11 @@
|
|
|
36
36
|
"id": { "type": "string", "minLength": 1 },
|
|
37
37
|
"description": { "type": "string" },
|
|
38
38
|
"onEnter": { "type": "array", "items": { "$ref": "#/$defs/action" } },
|
|
39
|
-
"onExit": { "type": "array", "items": { "$ref": "#/$defs/action" } }
|
|
39
|
+
"onExit": { "type": "array", "items": { "$ref": "#/$defs/action" } },
|
|
40
|
+
"pause": {
|
|
41
|
+
"type": "boolean",
|
|
42
|
+
"description": "When true, the engine pauses the run at this state instead of auto-advancing."
|
|
43
|
+
}
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
46
|
},
|
|
@@ -36,7 +36,11 @@
|
|
|
36
36
|
"id": { "type": "string", "minLength": 1 },
|
|
37
37
|
"description": { "type": "string" },
|
|
38
38
|
"type": { "enum": ["action", "gate", "parallel", "decision"] },
|
|
39
|
-
"action": { "$ref": "#/$defs/action" }
|
|
39
|
+
"action": { "$ref": "#/$defs/action" },
|
|
40
|
+
"pause": {
|
|
41
|
+
"type": "boolean",
|
|
42
|
+
"description": "When true, the engine pauses the run at this node instead of auto-advancing."
|
|
43
|
+
}
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
46
|
},
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import type { WorkflowEngineHost } from './host';
|
|
2
|
+
import type { RunLifecycle } from './run-lifecycle';
|
|
3
|
+
import { runtimeBuiltins, type WorkflowMode } from './run-lifecycle';
|
|
4
|
+
import type { ActionDef, ActionResult, OnErrorPolicy, WorkflowPersistenceAdapter, WorkflowRunOptions } from './types';
|
|
5
|
+
import { resolveOnErrorPolicy, resolveTemplates } from './variables';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Outcome discriminator for a single action step. `terminal` means the action
|
|
9
|
+
* declared terminal success (the run is done); `fail` means a failure under a
|
|
10
|
+
* 'fail' policy (the caller must halt); `completed` means the step ran (possibly
|
|
11
|
+
* a failure that the 'continue' policy logged past) and the loop may proceed.
|
|
12
|
+
*/
|
|
13
|
+
export type ActionStepOutcome = 'completed' | 'terminal' | 'fail';
|
|
14
|
+
|
|
15
|
+
/** Result of running one or more action steps: last result plus a control-flow discriminator. */
|
|
16
|
+
export interface ActionStepResult {
|
|
17
|
+
readonly outcome: ActionStepOutcome;
|
|
18
|
+
readonly result: ActionResult | undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Everything an action step needs that does not change between the two drivers:
|
|
23
|
+
* the host that owns the runner registry, the persistence sink, the lifecycle for
|
|
24
|
+
* observability, plus the resolution inputs (vars/env/builtins source) and the
|
|
25
|
+
* onError precedence chain. Drivers differ only in how many actions they feed in
|
|
26
|
+
* and how they map the outcome onto their control loop — not in this mechanism.
|
|
27
|
+
*/
|
|
28
|
+
export interface ActionStepDeps {
|
|
29
|
+
readonly host: WorkflowEngineHost;
|
|
30
|
+
readonly persistence: WorkflowPersistenceAdapter;
|
|
31
|
+
readonly lifecycle: RunLifecycle;
|
|
32
|
+
readonly workflowName: string;
|
|
33
|
+
readonly stateOrNodeId: string;
|
|
34
|
+
readonly runId: string;
|
|
35
|
+
readonly mode: WorkflowMode;
|
|
36
|
+
readonly transitionsTaken: number;
|
|
37
|
+
readonly env: Record<string, string>;
|
|
38
|
+
readonly options: WorkflowRunOptions;
|
|
39
|
+
readonly defaultOnError: OnErrorPolicy | undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Run one action through its full lifecycle: resolve templates, persist the
|
|
44
|
+
* start row, time the host invocation inside try/finally, persist + emit the
|
|
45
|
+
* finalize, then classify terminal/fail/continue. `vars` is read-only here;
|
|
46
|
+
* callers thread any `setVars` from the returned result back into their own
|
|
47
|
+
* vars map (the merge stays caller-owned because the two drivers carry vars
|
|
48
|
+
* differently across their loops).
|
|
49
|
+
*
|
|
50
|
+
* This is the single seam both drivers share for action execution (ADR-006 §7
|
|
51
|
+
* keeps the *control loops* dialect-specific; only this per-action mechanism is
|
|
52
|
+
* shared). Returns the action result plus an outcome discriminator.
|
|
53
|
+
*/
|
|
54
|
+
export async function runActionStep(
|
|
55
|
+
action: ActionDef,
|
|
56
|
+
vars: Record<string, string>,
|
|
57
|
+
deps: ActionStepDeps,
|
|
58
|
+
): Promise<ActionStepResult> {
|
|
59
|
+
const { host, persistence, lifecycle, stateOrNodeId, runId } = deps;
|
|
60
|
+
const resolved = resolveTemplates(action.options ?? {}, {
|
|
61
|
+
vars,
|
|
62
|
+
env: deps.env,
|
|
63
|
+
builtins: runtimeBuiltins(deps.workflowName, stateOrNodeId, runId, deps.transitionsTaken, deps.mode),
|
|
64
|
+
});
|
|
65
|
+
const actionId = await persistence.saveActionStart(runId, stateOrNodeId, action.kind);
|
|
66
|
+
const actionStartMs = Date.now();
|
|
67
|
+
lifecycle.actionStart(stateOrNodeId, action.kind);
|
|
68
|
+
let result: ActionResult | undefined;
|
|
69
|
+
try {
|
|
70
|
+
result = await host.runAction(action.kind, resolved, {
|
|
71
|
+
runId,
|
|
72
|
+
workdir: deps.options.workdir,
|
|
73
|
+
stateOrNodeId,
|
|
74
|
+
vars,
|
|
75
|
+
env: deps.env,
|
|
76
|
+
metadata: deps.options.metadata,
|
|
77
|
+
events: deps.options.events,
|
|
78
|
+
});
|
|
79
|
+
} finally {
|
|
80
|
+
const durationMs = Date.now() - actionStartMs;
|
|
81
|
+
lifecycle.actionDone(stateOrNodeId, action.kind, durationMs, result?.ok ?? false);
|
|
82
|
+
// Fire-and-forget the finalize write: the action row already exists (saveActionStart
|
|
83
|
+
// was awaited), and finalize only updates audit fields — the run's correctness never
|
|
84
|
+
// depends on it, so it must not block or fail the control loop. A swallowed `.catch`
|
|
85
|
+
// keeps a rejected write from surfacing as an unhandledRejection (repo convention,
|
|
86
|
+
// see runtime/process-executor.ts).
|
|
87
|
+
void persistence
|
|
88
|
+
.saveActionFinalize(
|
|
89
|
+
actionId,
|
|
90
|
+
result?.ok !== false ? 'done' : 'failed',
|
|
91
|
+
durationMs,
|
|
92
|
+
result?.ok ?? false,
|
|
93
|
+
result,
|
|
94
|
+
)
|
|
95
|
+
.catch(() => undefined);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (result.terminal === true) return { outcome: 'terminal', result };
|
|
99
|
+
if (!result.ok) {
|
|
100
|
+
const policy = resolveOnErrorPolicy(action.onError, deps.defaultOnError, deps.options.onError);
|
|
101
|
+
if (policy === 'fail') return { outcome: 'fail', result };
|
|
102
|
+
lifecycle.warnActionFailed(stateOrNodeId, deps.transitionsTaken, result.error);
|
|
103
|
+
}
|
|
104
|
+
return { outcome: 'completed', result };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Run a sequence of actions in declaration order, stopping at the first
|
|
109
|
+
* `terminal` or `fail` outcome. Every action resolves its templates against the
|
|
110
|
+
* same `vars` snapshot the caller passed in — `setVars` does not affect later
|
|
111
|
+
* actions within the same sequence; it is the caller (the state-machine driver)
|
|
112
|
+
* that merges the returned result's `setVars` into its run-local vars after the
|
|
113
|
+
* sequence settles. Returns the last action result (retained even when a failure
|
|
114
|
+
* was continued past, so downstream guards can inspect it) plus the controlling
|
|
115
|
+
* outcome.
|
|
116
|
+
*/
|
|
117
|
+
export async function runActionSequence(
|
|
118
|
+
actions: readonly ActionDef[],
|
|
119
|
+
vars: Record<string, string>,
|
|
120
|
+
deps: ActionStepDeps,
|
|
121
|
+
): Promise<ActionStepResult> {
|
|
122
|
+
let last: ActionResult | undefined;
|
|
123
|
+
for (const action of actions) {
|
|
124
|
+
const step = await runActionStep(action, vars, deps);
|
|
125
|
+
if (step.result !== undefined) last = step.result;
|
|
126
|
+
if (step.outcome === 'terminal') return { outcome: 'terminal', result: last };
|
|
127
|
+
if (step.outcome === 'fail') return { outcome: 'fail', result: last };
|
|
128
|
+
}
|
|
129
|
+
return { outcome: 'completed', result: last };
|
|
130
|
+
}
|
package/src/errors.ts
CHANGED
|
@@ -27,3 +27,14 @@ export class RunCollisionError extends Error {
|
|
|
27
27
|
this.name = 'RunCollisionError';
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
+
|
|
31
|
+
/** Error raised when attempting to resume a run that is not paused. */
|
|
32
|
+
export class WorkflowResumeError extends Error {
|
|
33
|
+
constructor(
|
|
34
|
+
message: string,
|
|
35
|
+
readonly details?: unknown,
|
|
36
|
+
) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.name = 'WorkflowResumeError';
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/events.ts
CHANGED
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
/** Typed event map for workflow-engine run observability. All events prefixed `workflow.`. */
|
|
2
2
|
export type WorkflowEngineEvents = {
|
|
3
3
|
/** Emitted when a run begins (inside the span). */
|
|
4
|
-
'workflow.run.started': (data: {
|
|
4
|
+
'workflow.run.started': (data: {
|
|
5
|
+
workflowName: string;
|
|
6
|
+
mode: string;
|
|
7
|
+
runId: string;
|
|
8
|
+
dryRun: boolean;
|
|
9
|
+
externalKey?: string;
|
|
10
|
+
}) => void;
|
|
5
11
|
/** Emitted when a run completes successfully. */
|
|
6
|
-
'workflow.run.done': (data: {
|
|
12
|
+
'workflow.run.done': (data: {
|
|
13
|
+
runId: string;
|
|
14
|
+
finalState: string;
|
|
15
|
+
transitionsTaken: number;
|
|
16
|
+
externalKey?: string;
|
|
17
|
+
}) => void;
|
|
7
18
|
/** Emitted when a run fails. */
|
|
8
|
-
'workflow.run.failed': (data: { runId: string; finalState: string; reason: string }) => void;
|
|
19
|
+
'workflow.run.failed': (data: { runId: string; finalState: string; reason: string; externalKey?: string }) => void;
|
|
9
20
|
/** Emitted when entering a state or node. */
|
|
10
21
|
'workflow.node.enter': (data: { runId: string; node: string; transitionsTaken: number }) => void;
|
|
11
22
|
/** Emitted on a state/node transition. */
|
|
12
|
-
'workflow.node.transition': (data: {
|
|
23
|
+
'workflow.node.transition': (data: {
|
|
24
|
+
runId: string;
|
|
25
|
+
from: string;
|
|
26
|
+
to: string;
|
|
27
|
+
trigger: string | null;
|
|
28
|
+
externalKey?: string;
|
|
29
|
+
}) => void;
|
|
13
30
|
/** Emitted when an action starts executing. */
|
|
14
31
|
'workflow.action.start': (data: { runId: string; node: string; kind: string }) => void;
|
|
15
32
|
/** Emitted when an action finishes executing (success or failure). */
|
|
@@ -38,9 +55,42 @@ export type WorkflowEngineEvents = {
|
|
|
38
55
|
to: string;
|
|
39
56
|
kind: string;
|
|
40
57
|
passed: boolean;
|
|
58
|
+
externalKey?: string;
|
|
41
59
|
}) => void;
|
|
42
60
|
/** Emitted when an interactive HITL prompt is presented and the engine waits for input. */
|
|
43
61
|
'workflow.hitl.ask': (data: { runId: string; node: string; kind: string; message: string }) => void;
|
|
44
62
|
/** Emitted when an interactive HITL prompt receives a response. */
|
|
45
63
|
'workflow.hitl.response': (data: { runId: string; node: string; ok: boolean }) => void;
|
|
64
|
+
/** Emitted when a run's state is force-set via reseed (consumer-side authority reconciliation). */
|
|
65
|
+
'workflow.run.reseeded': (data: {
|
|
66
|
+
runId: string;
|
|
67
|
+
fromState: string;
|
|
68
|
+
toState: string;
|
|
69
|
+
externalKey?: string;
|
|
70
|
+
}) => void;
|
|
71
|
+
/** Emitted when an external transition request is allowed and committed. */
|
|
72
|
+
'workflow.transition.requested': (data: {
|
|
73
|
+
runId: string;
|
|
74
|
+
from: string;
|
|
75
|
+
to: string;
|
|
76
|
+
trigger: string | null;
|
|
77
|
+
externalKey?: string;
|
|
78
|
+
}) => void;
|
|
79
|
+
/** Emitted when an external transition request is denied. */
|
|
80
|
+
'workflow.transition.denied': (data: {
|
|
81
|
+
runId: string;
|
|
82
|
+
from: string;
|
|
83
|
+
to: string;
|
|
84
|
+
reason: string;
|
|
85
|
+
externalKey?: string;
|
|
86
|
+
}) => void;
|
|
87
|
+
/** Emitted when a run pauses at a declared pause point. */
|
|
88
|
+
'workflow.run.paused': (data: {
|
|
89
|
+
runId: string;
|
|
90
|
+
node: string;
|
|
91
|
+
transitionsTaken: number;
|
|
92
|
+
externalKey?: string;
|
|
93
|
+
}) => void;
|
|
94
|
+
/** Emitted when a paused run is resumed. */
|
|
95
|
+
'workflow.run.resumed': (data: { runId: string; node: string; externalKey?: string }) => void;
|
|
46
96
|
};
|
package/src/host.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { NodeProcessExecutor, type ProcessExecutor } from '@gobing-ai/ts-runtime';
|
|
2
2
|
import { type CapabilityOrigin, CapabilityRegistry } from '@gobing-ai/ts-runtime/extension';
|
|
3
3
|
import { WorkflowValidationError } from './errors';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
ActionResult,
|
|
6
|
+
ActionRunContext,
|
|
7
|
+
ActionRunner,
|
|
8
|
+
GuardContext,
|
|
9
|
+
GuardEvaluationResult,
|
|
10
|
+
GuardRunner,
|
|
11
|
+
} from './types';
|
|
5
12
|
|
|
6
13
|
/** Registry owner for workflow actions and guards. */
|
|
7
14
|
export class WorkflowEngineHost {
|
|
@@ -60,8 +67,18 @@ export class WorkflowEngineHost {
|
|
|
60
67
|
|
|
61
68
|
/** Evaluate a registered guard. */
|
|
62
69
|
async evaluateGuard(kind: string, options: Record<string, unknown>, context: GuardContext): Promise<boolean> {
|
|
70
|
+
return (await this.evaluateGuardResult(kind, options, context)).passed;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Evaluate a registered guard and preserve any machine-readable report it returns. */
|
|
74
|
+
async evaluateGuardResult(
|
|
75
|
+
kind: string,
|
|
76
|
+
options: Record<string, unknown>,
|
|
77
|
+
context: GuardContext,
|
|
78
|
+
): Promise<GuardEvaluationResult> {
|
|
63
79
|
if (!this.guards.has(kind)) throw new WorkflowValidationError(`Unknown workflow guard "${kind}"`);
|
|
64
|
-
|
|
80
|
+
const result = await this.guards.get(kind).evaluate(options, context);
|
|
81
|
+
return typeof result === 'boolean' ? { passed: result } : result;
|
|
65
82
|
}
|
|
66
83
|
}
|
|
67
84
|
|
|
@@ -73,6 +90,7 @@ export function createDefaultWorkflowEngineHost(
|
|
|
73
90
|
host.registerAction(new NoteActionRunner(), 'builtin');
|
|
74
91
|
host.registerAction(new ShellActionRunner(options.processExecutor ?? new NodeProcessExecutor()), 'builtin');
|
|
75
92
|
host.registerAction(new EventEmitActionRunner(), 'builtin');
|
|
93
|
+
host.registerGuard(new ShellGuardRunner(options.processExecutor ?? new NodeProcessExecutor()), 'builtin');
|
|
76
94
|
host.registerGuard({ kind: 'always', evaluate: async () => true }, 'builtin');
|
|
77
95
|
host.registerGuard({ kind: 'never', evaluate: async () => false }, 'builtin');
|
|
78
96
|
host.registerGuard(
|
|
@@ -137,7 +155,7 @@ export class ShellActionRunner implements ActionRunner {
|
|
|
137
155
|
const result = await this.processExecutor.run({
|
|
138
156
|
command: spawn.command,
|
|
139
157
|
args: spawn.args,
|
|
140
|
-
cwd:
|
|
158
|
+
cwd: optionalStringOption(options, 'cwd', context.workdir),
|
|
141
159
|
rejectOnError: false,
|
|
142
160
|
forceBuffered: true,
|
|
143
161
|
});
|
|
@@ -149,6 +167,31 @@ export class ShellActionRunner implements ActionRunner {
|
|
|
149
167
|
}
|
|
150
168
|
}
|
|
151
169
|
|
|
170
|
+
/** Built-in shell guard backed by ts-runtime ProcessExecutor; passes when the command exits with 0. */
|
|
171
|
+
export class ShellGuardRunner implements GuardRunner {
|
|
172
|
+
readonly kind = 'shell';
|
|
173
|
+
|
|
174
|
+
constructor(private readonly processExecutor: ProcessExecutor) {}
|
|
175
|
+
|
|
176
|
+
async evaluate(options: Record<string, unknown>, context: GuardContext): Promise<GuardEvaluationResult> {
|
|
177
|
+
const command = stringOption(options, 'command');
|
|
178
|
+
const explicitArgs = arrayOption(options, 'args');
|
|
179
|
+
const usesShell = explicitArgs.length === 0;
|
|
180
|
+
const spawn = usesShell ? { command: '/bin/sh', args: ['-c', command] } : { command, args: explicitArgs };
|
|
181
|
+
const result = await this.processExecutor.run({
|
|
182
|
+
command: spawn.command,
|
|
183
|
+
args: spawn.args,
|
|
184
|
+
cwd: optionalStringOption(options, 'cwd', context.workdir),
|
|
185
|
+
rejectOnError: false,
|
|
186
|
+
forceBuffered: true,
|
|
187
|
+
});
|
|
188
|
+
return {
|
|
189
|
+
passed: result.exitCode === 0,
|
|
190
|
+
report: { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode },
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
152
195
|
function stringOption(options: Record<string, unknown>, key: string, fallback?: string): string {
|
|
153
196
|
const value = options[key];
|
|
154
197
|
if (typeof value === 'string') return value;
|
|
@@ -156,6 +199,13 @@ function stringOption(options: Record<string, unknown>, key: string, fallback?:
|
|
|
156
199
|
throw new WorkflowValidationError(`Action option "${key}" must be a string`);
|
|
157
200
|
}
|
|
158
201
|
|
|
202
|
+
function optionalStringOption(options: Record<string, unknown>, key: string, fallback?: string): string | undefined {
|
|
203
|
+
const value = options[key];
|
|
204
|
+
if (typeof value === 'string') return value;
|
|
205
|
+
if (value === undefined) return fallback;
|
|
206
|
+
throw new WorkflowValidationError(`Action option "${key}" must be a string`);
|
|
207
|
+
}
|
|
208
|
+
|
|
159
209
|
function arrayOption(options: Record<string, unknown>, key: string): string[] {
|
|
160
210
|
const value = options[key];
|
|
161
211
|
if (value === undefined) return [];
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { loadWorkflowDef, loadWorkflowDefFromText, validateWorkflowDef } from './config';
|
|
2
|
-
export { FSMError, RunCollisionError, WorkflowValidationError } from './errors';
|
|
2
|
+
export { FSMError, RunCollisionError, WorkflowResumeError, WorkflowValidationError } from './errors';
|
|
3
3
|
export type { WorkflowEngineEvents } from './events';
|
|
4
4
|
export {
|
|
5
5
|
type LoadWorkflowExtensionsOptions,
|
|
@@ -14,6 +14,7 @@ export {
|
|
|
14
14
|
EventEmitActionRunner,
|
|
15
15
|
NoteActionRunner,
|
|
16
16
|
ShellActionRunner,
|
|
17
|
+
ShellGuardRunner,
|
|
17
18
|
WorkflowEngineHost,
|
|
18
19
|
} from './host';
|
|
19
20
|
export {
|
|
@@ -52,12 +53,17 @@ export type {
|
|
|
52
53
|
FlowNodeDef,
|
|
53
54
|
GuardContext,
|
|
54
55
|
GuardDef,
|
|
56
|
+
GuardEvaluationResult,
|
|
55
57
|
GuardRunner,
|
|
56
58
|
OnErrorPolicy,
|
|
57
59
|
StateDef,
|
|
58
60
|
StateMachineWorkflowDef,
|
|
61
|
+
TransitionAllowed,
|
|
59
62
|
TransitionDef,
|
|
63
|
+
TransitionDenied,
|
|
64
|
+
TransitionDeniedReason,
|
|
60
65
|
TransitionFlowWorkflowDef,
|
|
66
|
+
TransitionRequestResult,
|
|
61
67
|
Vars,
|
|
62
68
|
WorkflowDef,
|
|
63
69
|
WorkflowPersistenceAdapter,
|
package/src/persistence.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type { DbAdapter } from '@gobing-ai/ts-db';
|
|
2
2
|
import { RunCollisionError } from './errors';
|
|
3
3
|
import { WORKFLOW_ENGINE_SCHEMA_SQL } from './schema-sql';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
ActionRedactor,
|
|
6
|
+
WorkflowPersistenceAdapter,
|
|
7
|
+
WorkflowReseedResult,
|
|
8
|
+
WorkflowRunRecord,
|
|
9
|
+
WorkflowStatus,
|
|
10
|
+
} from './types';
|
|
5
11
|
|
|
6
12
|
/** Apply workflow-engine-owned schema to a database adapter. */
|
|
7
13
|
export async function applyWorkflowEngineSchema(db: DbAdapter): Promise<void> {
|
|
@@ -13,20 +19,34 @@ export async function applyWorkflowEngineSchema(db: DbAdapter): Promise<void> {
|
|
|
13
19
|
|
|
14
20
|
/** SQLite/D1-compatible workflow persistence adapter backed by ts-db. */
|
|
15
21
|
export class DbWorkflowPersistenceAdapter implements WorkflowPersistenceAdapter {
|
|
22
|
+
/** Memoized schema-ensure; the DDL runs at most once per adapter instance. */
|
|
23
|
+
private schemaReady: Promise<void> | undefined;
|
|
24
|
+
|
|
16
25
|
constructor(private readonly db: DbAdapter) {}
|
|
17
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Apply the workflow-engine schema once per adapter, latching the in-flight
|
|
29
|
+
* promise so concurrent first calls share a single DDL pass. Every public
|
|
30
|
+
* read/write awaits this instead of re-running the idempotent DDL per call.
|
|
31
|
+
*/
|
|
32
|
+
private ensureSchema(): Promise<void> {
|
|
33
|
+
this.schemaReady ??= applyWorkflowEngineSchema(this.db);
|
|
34
|
+
return this.schemaReady;
|
|
35
|
+
}
|
|
36
|
+
|
|
18
37
|
/** Create a run row, rejecting duplicate run ids. */
|
|
19
38
|
async createRun(record: WorkflowRunRecord): Promise<void> {
|
|
20
39
|
const existing = await this.loadRun(record.id);
|
|
21
40
|
if (existing !== undefined) throw new RunCollisionError(record.id);
|
|
22
|
-
await
|
|
41
|
+
await this.ensureSchema();
|
|
23
42
|
await this.db.run(
|
|
24
|
-
`INSERT INTO runs (id, workflow_name, mode, status, started_at, completed_at, metadata_json, created_at, updated_at)
|
|
25
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
43
|
+
`INSERT INTO runs (id, workflow_name, mode, status, external_key, started_at, completed_at, metadata_json, created_at, updated_at)
|
|
44
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
26
45
|
record.id,
|
|
27
46
|
record.workflow_name,
|
|
28
47
|
record.mode,
|
|
29
48
|
record.status,
|
|
49
|
+
record.external_key ?? null,
|
|
30
50
|
record.started_at,
|
|
31
51
|
record.completed_at,
|
|
32
52
|
record.metadata_json,
|
|
@@ -140,16 +160,84 @@ export class DbWorkflowPersistenceAdapter implements WorkflowPersistenceAdapter
|
|
|
140
160
|
|
|
141
161
|
/** Load a single run by id. */
|
|
142
162
|
async loadRun(runId: string): Promise<WorkflowRunRecord | undefined> {
|
|
143
|
-
await
|
|
163
|
+
await this.ensureSchema();
|
|
144
164
|
const row = await this.db.queryFirst<WorkflowRunRecord>('SELECT * FROM runs WHERE id = ?', runId);
|
|
145
165
|
return row ?? undefined;
|
|
146
166
|
}
|
|
147
167
|
|
|
148
168
|
/** List persisted workflow runs. */
|
|
149
169
|
async listRuns(): Promise<readonly WorkflowRunRecord[]> {
|
|
150
|
-
await
|
|
170
|
+
await this.ensureSchema();
|
|
151
171
|
return await this.db.queryAll<WorkflowRunRecord>('SELECT * FROM runs ORDER BY started_at DESC');
|
|
152
172
|
}
|
|
173
|
+
|
|
174
|
+
/** Look up a run by its external key within a workflow definition. */
|
|
175
|
+
async findRunByKey(workflowName: string, externalKey: string): Promise<WorkflowRunRecord | undefined> {
|
|
176
|
+
await this.ensureSchema();
|
|
177
|
+
const row = await this.db.queryFirst<WorkflowRunRecord>(
|
|
178
|
+
'SELECT * FROM runs WHERE workflow_name = ? AND external_key = ?',
|
|
179
|
+
workflowName,
|
|
180
|
+
externalKey,
|
|
181
|
+
);
|
|
182
|
+
return row ?? undefined;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Create a run or attach to an existing one by external key. */
|
|
186
|
+
async createOrAttachRun(record: WorkflowRunRecord): Promise<WorkflowRunRecord> {
|
|
187
|
+
await this.ensureSchema();
|
|
188
|
+
if (record.external_key) {
|
|
189
|
+
const existing = await this.findRunByKey(record.workflow_name, record.external_key);
|
|
190
|
+
if (existing) return existing;
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
await this.createRun(record);
|
|
194
|
+
} catch (error) {
|
|
195
|
+
if (record.external_key) {
|
|
196
|
+
const existing = await this.findRunByKey(record.workflow_name, record.external_key);
|
|
197
|
+
if (existing) return existing;
|
|
198
|
+
}
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
return { ...record };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Force-set the current state of a run (reseed). */
|
|
205
|
+
async reseedRun(runId: string, newState: string): Promise<WorkflowReseedResult> {
|
|
206
|
+
const now = Date.now();
|
|
207
|
+
await this.ensureSchema();
|
|
208
|
+
const previous = await this.db.queryFirst<{ state: string }>(
|
|
209
|
+
'SELECT state FROM workflow_states WHERE run_id = ? ORDER BY created_at DESC, rowid DESC LIMIT 1',
|
|
210
|
+
runId,
|
|
211
|
+
);
|
|
212
|
+
await this.saveWorkflowState(runId, newState, { reseeded: true, reseededAt: new Date(now).toISOString() });
|
|
213
|
+
await this.saveTransition(runId, previous?.state ?? '', newState, '__reseed__');
|
|
214
|
+
return { fromState: previous?.state ?? null, toState: newState };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Load the current state name for a run. */
|
|
218
|
+
async loadCurrentState(runId: string): Promise<string | undefined> {
|
|
219
|
+
await this.ensureSchema();
|
|
220
|
+
const row = await this.db.queryFirst<{ state: string }>(
|
|
221
|
+
'SELECT state FROM workflow_states WHERE run_id = ? ORDER BY created_at DESC, rowid DESC LIMIT 1',
|
|
222
|
+
runId,
|
|
223
|
+
);
|
|
224
|
+
return row?.state;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** List runs with status 'paused'. Ordered most-recent-first. */
|
|
228
|
+
async listPausedRuns(options?: { workflowName?: string; limit?: number }): Promise<readonly WorkflowRunRecord[]> {
|
|
229
|
+
await this.ensureSchema();
|
|
230
|
+
const where = ["status = 'paused'"];
|
|
231
|
+
const params: unknown[] = [];
|
|
232
|
+
if (options?.workflowName !== undefined) {
|
|
233
|
+
where.push('workflow_name = ?');
|
|
234
|
+
params.push(options.workflowName);
|
|
235
|
+
}
|
|
236
|
+
const limit = options?.limit ?? 100;
|
|
237
|
+
const sql = `SELECT * FROM runs WHERE ${where.join(' AND ')} ORDER BY updated_at DESC, rowid DESC LIMIT ?`;
|
|
238
|
+
params.push(limit);
|
|
239
|
+
return await this.db.queryAll<WorkflowRunRecord>(sql, ...params);
|
|
240
|
+
}
|
|
153
241
|
}
|
|
154
242
|
|
|
155
243
|
/** In-memory persistence adapter for tests and embedding. */
|
|
@@ -239,4 +327,47 @@ export class MemoryWorkflowPersistenceAdapter implements WorkflowPersistenceAdap
|
|
|
239
327
|
async listRuns(): Promise<readonly WorkflowRunRecord[]> {
|
|
240
328
|
return [...this.runs.values()];
|
|
241
329
|
}
|
|
330
|
+
|
|
331
|
+
/** Look up a run by its external key within a workflow definition. */
|
|
332
|
+
async findRunByKey(workflowName: string, externalKey: string): Promise<WorkflowRunRecord | undefined> {
|
|
333
|
+
for (const run of this.runs.values()) {
|
|
334
|
+
if (run.workflow_name === workflowName && run.external_key === externalKey) return run;
|
|
335
|
+
}
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Create a run or attach to an existing one by external key. */
|
|
340
|
+
async createOrAttachRun(record: WorkflowRunRecord): Promise<WorkflowRunRecord> {
|
|
341
|
+
if (record.external_key) {
|
|
342
|
+
const existing = await this.findRunByKey(record.workflow_name, record.external_key);
|
|
343
|
+
if (existing) return existing;
|
|
344
|
+
}
|
|
345
|
+
await this.createRun(record);
|
|
346
|
+
return { ...record };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** Force-set the current state of a run (reseed). */
|
|
350
|
+
async reseedRun(runId: string, newState: string): Promise<WorkflowReseedResult> {
|
|
351
|
+
const previous = this.states.findLast((state) => state.runId === runId);
|
|
352
|
+
await this.saveWorkflowState(runId, newState, { reseeded: true, reseededAt: new Date().toISOString() });
|
|
353
|
+
await this.saveTransition(runId, previous?.state ?? '', newState, '__reseed__');
|
|
354
|
+
return { fromState: previous?.state ?? null, toState: newState };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/** Load the current state name for a run. */
|
|
358
|
+
async loadCurrentState(runId: string): Promise<string | undefined> {
|
|
359
|
+
const last = this.states.findLast((s) => s.runId === runId);
|
|
360
|
+
return last?.state;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** List runs with status 'paused'. Ordered most-recent-first. */
|
|
364
|
+
async listPausedRuns(options?: { workflowName?: string; limit?: number }): Promise<readonly WorkflowRunRecord[]> {
|
|
365
|
+
let runs = [...this.runs.values()].filter((r) => r.status === 'paused');
|
|
366
|
+
if (options?.workflowName !== undefined) {
|
|
367
|
+
runs = runs.filter((r) => r.workflow_name === options.workflowName);
|
|
368
|
+
}
|
|
369
|
+
// Memory adapter has no updated_at tracking; use insertion order (reverse = most-recent-first).
|
|
370
|
+
runs.reverse();
|
|
371
|
+
return runs.slice(0, options?.limit ?? 100);
|
|
372
|
+
}
|
|
242
373
|
}
|