@gobing-ai/ts-dual-workflow-engine 0.4.7 → 0.4.9
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/dist/host.d.ts +4 -0
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +8 -4
- package/dist/run-lifecycle.d.ts +3 -1
- package/dist/run-lifecycle.d.ts.map +1 -1
- package/dist/run-lifecycle.js +4 -2
- package/dist/service.d.ts +13 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +38 -13
- package/package.json +4 -4
- package/src/host.ts +14 -5
- package/src/run-lifecycle.ts +13 -2
- package/src/service.ts +45 -16
package/dist/host.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import type { BusLifecycleEvents, EventBus } from '@gobing-ai/ts-infra';
|
|
1
2
|
import { type ProcessExecutor } from '@gobing-ai/ts-runtime';
|
|
2
3
|
import { type CapabilityOrigin } from '@gobing-ai/ts-runtime/extension';
|
|
3
4
|
import type { ActionResult, ActionRunContext, ActionRunner, GuardContext, GuardEvaluationResult, GuardRunner } from './types';
|
|
4
5
|
/** Registry owner for workflow actions and guards. */
|
|
5
6
|
export declare class WorkflowEngineHost {
|
|
7
|
+
readonly lifecycleBus?: EventBus<BusLifecycleEvents> | undefined;
|
|
6
8
|
private readonly actions;
|
|
7
9
|
private readonly guards;
|
|
10
|
+
constructor(lifecycleBus?: EventBus<BusLifecycleEvents> | undefined);
|
|
8
11
|
/** Register or replace an action runner. */
|
|
9
12
|
registerAction(action: ActionRunner, origin?: CapabilityOrigin): this;
|
|
10
13
|
/** Register or replace a guard runner. */
|
|
@@ -31,6 +34,7 @@ export declare class WorkflowEngineHost {
|
|
|
31
34
|
/** Create a workflow host with built-in note, shell, always, and action-ok capabilities. */
|
|
32
35
|
export declare function createDefaultWorkflowEngineHost(options?: {
|
|
33
36
|
processExecutor?: ProcessExecutor;
|
|
37
|
+
lifecycleBus?: EventBus<BusLifecycleEvents>;
|
|
34
38
|
}): WorkflowEngineHost;
|
|
35
39
|
/** Built-in action that records a note and emits a workflow.hitl.note event. */
|
|
36
40
|
export declare class NoteActionRunner implements ActionRunner {
|
package/dist/host.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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"}
|
package/dist/host.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { nodeBunFactory } from '@gobing-ai/ts-runtime';
|
|
2
2
|
import { CapabilityRegistry } from '@gobing-ai/ts-runtime/extension';
|
|
3
3
|
import { WorkflowValidationError } from './errors.js';
|
|
4
4
|
/** Registry owner for workflow actions and guards. */
|
|
5
5
|
export class WorkflowEngineHost {
|
|
6
|
+
lifecycleBus;
|
|
6
7
|
actions = new CapabilityRegistry('workflow action');
|
|
7
8
|
guards = new CapabilityRegistry('workflow guard');
|
|
9
|
+
constructor(lifecycleBus) {
|
|
10
|
+
this.lifecycleBus = lifecycleBus;
|
|
11
|
+
}
|
|
8
12
|
/** Register or replace an action runner. */
|
|
9
13
|
registerAction(action, origin = 'extension') {
|
|
10
14
|
this.actions.register(action.kind, action, origin);
|
|
@@ -61,11 +65,11 @@ export class WorkflowEngineHost {
|
|
|
61
65
|
}
|
|
62
66
|
/** Create a workflow host with built-in note, shell, always, and action-ok capabilities. */
|
|
63
67
|
export function createDefaultWorkflowEngineHost(options = {}) {
|
|
64
|
-
const host = new WorkflowEngineHost();
|
|
68
|
+
const host = new WorkflowEngineHost(options.lifecycleBus);
|
|
65
69
|
host.registerAction(new NoteActionRunner(), 'builtin');
|
|
66
|
-
host.registerAction(new ShellActionRunner(options.processExecutor ??
|
|
70
|
+
host.registerAction(new ShellActionRunner(options.processExecutor ?? nodeBunFactory.createProcessExecutor()), 'builtin');
|
|
67
71
|
host.registerAction(new EventEmitActionRunner(), 'builtin');
|
|
68
|
-
host.registerGuard(new ShellGuardRunner(options.processExecutor ??
|
|
72
|
+
host.registerGuard(new ShellGuardRunner(options.processExecutor ?? nodeBunFactory.createProcessExecutor()), 'builtin');
|
|
69
73
|
host.registerGuard({ kind: 'always', evaluate: async () => true }, 'builtin');
|
|
70
74
|
host.registerGuard({ kind: 'never', evaluate: async () => false }, 'builtin');
|
|
71
75
|
host.registerGuard({
|
package/dist/run-lifecycle.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type EventBus, type Logger } from '@gobing-ai/ts-infra';
|
|
1
|
+
import { type BusLifecycleEvents, EventBus, type Logger } from '@gobing-ai/ts-infra';
|
|
2
2
|
import type { WorkflowEngineEvents } from './events';
|
|
3
3
|
import type { WorkflowPersistenceAdapter, WorkflowRunOptions, WorkflowRunResult, WorkflowStatus } from './types';
|
|
4
4
|
/** Workflow dialect carried on every run, span, and persisted record. */
|
|
@@ -16,6 +16,8 @@ export interface RunLifecycleDeps {
|
|
|
16
16
|
readonly logger?: Logger;
|
|
17
17
|
/** Optional event bus for structured in-process run observability. */
|
|
18
18
|
readonly events?: EventBus<WorkflowEngineEvents>;
|
|
19
|
+
/** Parent bus used when RunLifecycle must construct its own workflow event bus. */
|
|
20
|
+
readonly lifecycleBus?: EventBus<BusLifecycleEvents>;
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
23
|
* Owns the run-level bookkeeping shared by both drivers: run identity, the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-lifecycle.d.ts","sourceRoot":"","sources":["../src/run-lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"run-lifecycle.d.ts","sourceRoot":"","sources":["../src/run-lifecycle.ts"],"names":[],"mappings":"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"}
|
package/dist/run-lifecycle.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { addSpanEvent, getLogger, traceAsync } from '@gobing-ai/ts-infra';
|
|
1
|
+
import { addSpanEvent, EventBus, getLogger, traceAsync, } from '@gobing-ai/ts-infra';
|
|
2
2
|
import { getProcessEnv } from '@gobing-ai/ts-runtime';
|
|
3
3
|
/** Keys of the runtime builtin namespace, single-sourced for resolver + validator. */
|
|
4
4
|
export const RUNTIME_BUILTIN_KEYS = [
|
|
@@ -48,7 +48,9 @@ export class RunLifecycle {
|
|
|
48
48
|
this.runId = runId;
|
|
49
49
|
this.externalKey = externalKey;
|
|
50
50
|
this.persistence = deps.persistence;
|
|
51
|
-
this.events =
|
|
51
|
+
this.events =
|
|
52
|
+
deps.events ??
|
|
53
|
+
(deps.lifecycleBus ? new EventBus({ lifecycleBus: deps.lifecycleBus }) : undefined);
|
|
52
54
|
this.logger = (deps.logger ?? getLogger('workflow')).child({ runId, workflow: workflowName, mode });
|
|
53
55
|
}
|
|
54
56
|
/**
|
package/dist/service.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type BusLifecycleEvents, EventBus } from '@gobing-ai/ts-infra';
|
|
1
2
|
import type { WorkflowEngineHost } from './host';
|
|
2
3
|
import type { StateMachineWorkflowDef, TransitionRequestResult, WorkflowDef, WorkflowPersistenceAdapter, WorkflowRunOptions, WorkflowRunRecord, WorkflowRunResult } from './types';
|
|
3
4
|
/** High-level workflow service for loading, running, and listing persisted workflow runs. */
|
|
@@ -6,7 +7,18 @@ export declare class WorkflowService {
|
|
|
6
7
|
private readonly persistence;
|
|
7
8
|
/** Per-run serialization: ensures concurrent requestTransition calls serialize. */
|
|
8
9
|
private readonly runLocks;
|
|
9
|
-
|
|
10
|
+
private readonly lifecycleBus;
|
|
11
|
+
constructor(host: WorkflowEngineHost, persistence: WorkflowPersistenceAdapter,
|
|
12
|
+
/**
|
|
13
|
+
* Optional lifecycle bus to bridge `workflow.*` events into the
|
|
14
|
+
* application System Events stream (R3). When a run's `events` is
|
|
15
|
+
* omitted the service constructs an internal
|
|
16
|
+
* `EventBus<WorkflowEngineEvents>` parented to this bus so workflow
|
|
17
|
+
* events appear in the JSONL log.
|
|
18
|
+
*/
|
|
19
|
+
lifecycleBus?: EventBus<BusLifecycleEvents>);
|
|
20
|
+
/** Resolve a run's event bus: caller-supplied wins; otherwise parent to the service lifecycle bus. */
|
|
21
|
+
private resolveEvents;
|
|
10
22
|
/** Load a workflow file and validate it. */
|
|
11
23
|
load(path: string): Promise<WorkflowDef>;
|
|
12
24
|
/** Run an already-loaded workflow definition. */
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAIjD,OAAO,KAAK,EACR,uBAAuB,EAEvB,uBAAuB,EACvB,WAAW,EACX,0BAA0B,EAC1B,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACpB,MAAM,SAAS,CAAC;AAGjB,6FAA6F;AAC7F,qBAAa,eAAe;IAMpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,WAAW;IANhC,mFAAmF;IACnF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;IAC7D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA2C;gBAGnD,IAAI,EAAE,kBAAkB,EACxB,WAAW,EAAE,0BAA0B;IACxD;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,QAAQ,CAAC,kBAAkB,CAAC;IAK/C,sGAAsG;IACtG,OAAO,CAAC,aAAa;IASrB,4CAA4C;IACtC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAI9C,iDAAiD;IAC3C,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAc9F,oCAAoC;IAC9B,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAIzF,oCAAoC;IAC9B,QAAQ;IAId,mEAAmE;IAC7D,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC;IAIrG,gFAAgF;IAC1E,iBAAiB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAI9E,qFAAqF;IAC/E,SAAS,CACX,QAAQ,EAAE,WAAW,EACrB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,kBAAkB,GAC7B,OAAO,CAAC,IAAI,CAAC;IACV,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IA8B7F,iGAAiG;IACjG,OAAO,CAAC,0BAA0B;IASlC,oFAAoF;YACtE,YAAY;IAY1B,uEAAuE;IACjE,SAAS,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAiC/G,iEAAiE;IAC3D,cAAc,CAAC,OAAO,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC;IAIhH;;;;;OAKG;IACG,iBAAiB,CACnB,QAAQ,EAAE,uBAAuB,EACjC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,kBAAkB,GAC7B,OAAO,CAAC,uBAAuB,CAAC;YAsBrB,iBAAiB;IAgF/B;;;;OAIG;IACH,OAAO,CAAC,cAAc;CAiBzB"}
|
package/dist/service.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EventBus } from '@gobing-ai/ts-infra';
|
|
1
2
|
import { loadWorkflowDef } from './config.js';
|
|
2
3
|
import { FSMError, WorkflowResumeError } from './errors.js';
|
|
3
4
|
import { RunLifecycle } from './run-lifecycle.js';
|
|
@@ -10,9 +11,24 @@ export class WorkflowService {
|
|
|
10
11
|
persistence;
|
|
11
12
|
/** Per-run serialization: ensures concurrent requestTransition calls serialize. */
|
|
12
13
|
runLocks = new Map();
|
|
13
|
-
|
|
14
|
+
lifecycleBus;
|
|
15
|
+
constructor(host, persistence,
|
|
16
|
+
/**
|
|
17
|
+
* Optional lifecycle bus to bridge `workflow.*` events into the
|
|
18
|
+
* application System Events stream (R3). When a run's `events` is
|
|
19
|
+
* omitted the service constructs an internal
|
|
20
|
+
* `EventBus<WorkflowEngineEvents>` parented to this bus so workflow
|
|
21
|
+
* events appear in the JSONL log.
|
|
22
|
+
*/
|
|
23
|
+
lifecycleBus) {
|
|
14
24
|
this.host = host;
|
|
15
25
|
this.persistence = persistence;
|
|
26
|
+
this.lifecycleBus = lifecycleBus ?? host.lifecycleBus;
|
|
27
|
+
}
|
|
28
|
+
/** Resolve a run's event bus: caller-supplied wins; otherwise parent to the service lifecycle bus. */
|
|
29
|
+
resolveEvents(events) {
|
|
30
|
+
return (events ??
|
|
31
|
+
(this.lifecycleBus ? new EventBus({ lifecycleBus: this.lifecycleBus }) : undefined));
|
|
16
32
|
}
|
|
17
33
|
/** Load a workflow file and validate it. */
|
|
18
34
|
async load(path) {
|
|
@@ -20,10 +36,17 @@ export class WorkflowService {
|
|
|
20
36
|
}
|
|
21
37
|
/** Run an already-loaded workflow definition. */
|
|
22
38
|
async run(workflow, options = {}) {
|
|
39
|
+
const events = this.resolveEvents(options.events);
|
|
23
40
|
if (workflow.kind === 'transition-flow') {
|
|
24
|
-
return await new TransitionFlowDriver({ host: this.host, persistence: this.persistence }).run(workflow,
|
|
41
|
+
return await new TransitionFlowDriver({ host: this.host, persistence: this.persistence }).run(workflow, {
|
|
42
|
+
...options,
|
|
43
|
+
events,
|
|
44
|
+
});
|
|
25
45
|
}
|
|
26
|
-
return await new StateMachineDriver({ host: this.host, persistence: this.persistence }).run(workflow,
|
|
46
|
+
return await new StateMachineDriver({ host: this.host, persistence: this.persistence }).run(workflow, {
|
|
47
|
+
...options,
|
|
48
|
+
events,
|
|
49
|
+
});
|
|
27
50
|
}
|
|
28
51
|
/** Load and run a workflow file. */
|
|
29
52
|
async runFile(path, options = {}) {
|
|
@@ -76,7 +99,7 @@ export class WorkflowService {
|
|
|
76
99
|
const run = await this.persistence.loadRun(runId);
|
|
77
100
|
const extKey = run?.external_key ?? undefined;
|
|
78
101
|
const result = await this.persistence.reseedRun(runId, newState);
|
|
79
|
-
void options?.events?.emit('workflow.run.reseeded', {
|
|
102
|
+
void this.resolveEvents(options?.events)?.emit('workflow.run.reseeded', {
|
|
80
103
|
runId,
|
|
81
104
|
fromState: result.fromState ?? '',
|
|
82
105
|
toState: result.toState,
|
|
@@ -99,18 +122,19 @@ export class WorkflowService {
|
|
|
99
122
|
// Re-open the run as running.
|
|
100
123
|
const extKey = run.external_key ?? undefined;
|
|
101
124
|
await this.persistence.finalizeRun(runId, 'running', '');
|
|
102
|
-
|
|
125
|
+
const events = this.resolveEvents(options?.events);
|
|
126
|
+
void events?.emit('workflow.run.resumed', { runId, node: currentState, externalKey: extKey });
|
|
103
127
|
// Resume through the appropriate driver, starting from the paused state (skip on-enter).
|
|
104
128
|
if (workflow.kind === 'transition-flow') {
|
|
105
129
|
return await new TransitionFlowDriver({
|
|
106
130
|
host: this.host,
|
|
107
131
|
persistence: this.persistence,
|
|
108
|
-
}).resume(workflow, runId, currentState, extKey, options);
|
|
132
|
+
}).resume(workflow, runId, currentState, extKey, { ...options, events });
|
|
109
133
|
}
|
|
110
134
|
return await new StateMachineDriver({
|
|
111
135
|
host: this.host,
|
|
112
136
|
persistence: this.persistence,
|
|
113
|
-
}).resume(workflow, runId, currentState, extKey, options);
|
|
137
|
+
}).resume(workflow, runId, currentState, extKey, { ...options, events });
|
|
114
138
|
}
|
|
115
139
|
/** List runs currently paused. Optional filters and ordering. */
|
|
116
140
|
async listPausedRuns(options) {
|
|
@@ -154,14 +178,15 @@ export class WorkflowService {
|
|
|
154
178
|
detail: `No state recorded for run "${runId}"`,
|
|
155
179
|
};
|
|
156
180
|
}
|
|
181
|
+
const events = this.resolveEvents(options?.events);
|
|
157
182
|
// An external transition is a single guarded hop on an existing run, not a
|
|
158
183
|
// run itself — borrow a span-free lifecycle so the transition persist+emit
|
|
159
184
|
// mechanics reuse the same seam the drivers use (no new workflow.run span).
|
|
160
|
-
const lifecycle = RunLifecycle.forExternalTransition(workflow.name, runId, { persistence: this.persistence, events
|
|
185
|
+
const lifecycle = RunLifecycle.forExternalTransition(workflow.name, runId, { persistence: this.persistence, events }, extKey);
|
|
161
186
|
// Find the matching transition from current state to requested state.
|
|
162
187
|
const transition = workflow.transitions.find((t) => t.from === currentState && t.to === toState);
|
|
163
188
|
if (transition === undefined) {
|
|
164
|
-
return this.denyTransition(
|
|
189
|
+
return this.denyTransition(events, runId, currentState, toState, extKey, {
|
|
165
190
|
reason: 'no-such-transition',
|
|
166
191
|
detail: `No transition from "${currentState}" to "${toState}"`,
|
|
167
192
|
});
|
|
@@ -183,7 +208,7 @@ export class WorkflowService {
|
|
|
183
208
|
});
|
|
184
209
|
lifecycle.guardEvaluated(currentState, toState, transition.guard.kind, guardResult.passed);
|
|
185
210
|
if (!guardResult.passed) {
|
|
186
|
-
return this.denyTransition(
|
|
211
|
+
return this.denyTransition(events, runId, currentState, toState, extKey, {
|
|
187
212
|
reason: 'guard-failed',
|
|
188
213
|
detail: `Guard "${transition.guard.kind}" denied transition from "${currentState}" to "${toState}"`,
|
|
189
214
|
guardKind: transition.guard.kind,
|
|
@@ -196,7 +221,7 @@ export class WorkflowService {
|
|
|
196
221
|
// record — external transitions don't drive phase tracking.
|
|
197
222
|
const trigger = transition.trigger ?? null;
|
|
198
223
|
await lifecycle.commitHop(currentState, toState, trigger, 0);
|
|
199
|
-
void
|
|
224
|
+
void events?.emit('workflow.transition.requested', {
|
|
200
225
|
runId,
|
|
201
226
|
from: currentState,
|
|
202
227
|
to: toState,
|
|
@@ -210,8 +235,8 @@ export class WorkflowService {
|
|
|
210
235
|
* event in one place — the single denial seam for the external-transition path,
|
|
211
236
|
* so the event payload and the returned reason can never drift apart.
|
|
212
237
|
*/
|
|
213
|
-
denyTransition(
|
|
214
|
-
void
|
|
238
|
+
denyTransition(events, runId, from, to, externalKey, denial) {
|
|
239
|
+
void events?.emit('workflow.transition.denied', {
|
|
215
240
|
runId,
|
|
216
241
|
from,
|
|
217
242
|
to,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/ts-dual-workflow-engine",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
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.4.
|
|
52
|
-
"@gobing-ai/ts-infra": "^0.4.
|
|
53
|
-
"@gobing-ai/ts-runtime": "^0.4.
|
|
51
|
+
"@gobing-ai/ts-db": "^0.4.9",
|
|
52
|
+
"@gobing-ai/ts-infra": "^0.4.9",
|
|
53
|
+
"@gobing-ai/ts-runtime": "^0.4.9",
|
|
54
54
|
"zod": "^4.1.0"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
package/src/host.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { BusLifecycleEvents, EventBus } from '@gobing-ai/ts-infra';
|
|
2
|
+
import { nodeBunFactory, type ProcessExecutor } from '@gobing-ai/ts-runtime';
|
|
2
3
|
import { type CapabilityOrigin, CapabilityRegistry } from '@gobing-ai/ts-runtime/extension';
|
|
3
4
|
import { WorkflowValidationError } from './errors';
|
|
4
5
|
import type {
|
|
@@ -15,6 +16,8 @@ export class WorkflowEngineHost {
|
|
|
15
16
|
private readonly actions = new CapabilityRegistry<ActionRunner>('workflow action');
|
|
16
17
|
private readonly guards = new CapabilityRegistry<GuardRunner>('workflow guard');
|
|
17
18
|
|
|
19
|
+
constructor(readonly lifecycleBus?: EventBus<BusLifecycleEvents>) {}
|
|
20
|
+
|
|
18
21
|
/** Register or replace an action runner. */
|
|
19
22
|
registerAction(action: ActionRunner, origin: CapabilityOrigin = 'extension'): this {
|
|
20
23
|
this.actions.register(action.kind, action, origin);
|
|
@@ -84,13 +87,19 @@ export class WorkflowEngineHost {
|
|
|
84
87
|
|
|
85
88
|
/** Create a workflow host with built-in note, shell, always, and action-ok capabilities. */
|
|
86
89
|
export function createDefaultWorkflowEngineHost(
|
|
87
|
-
options: { processExecutor?: ProcessExecutor } = {},
|
|
90
|
+
options: { processExecutor?: ProcessExecutor; lifecycleBus?: EventBus<BusLifecycleEvents> } = {},
|
|
88
91
|
): WorkflowEngineHost {
|
|
89
|
-
const host = new WorkflowEngineHost();
|
|
92
|
+
const host = new WorkflowEngineHost(options.lifecycleBus);
|
|
90
93
|
host.registerAction(new NoteActionRunner(), 'builtin');
|
|
91
|
-
host.registerAction(
|
|
94
|
+
host.registerAction(
|
|
95
|
+
new ShellActionRunner(options.processExecutor ?? nodeBunFactory.createProcessExecutor()),
|
|
96
|
+
'builtin',
|
|
97
|
+
);
|
|
92
98
|
host.registerAction(new EventEmitActionRunner(), 'builtin');
|
|
93
|
-
host.registerGuard(
|
|
99
|
+
host.registerGuard(
|
|
100
|
+
new ShellGuardRunner(options.processExecutor ?? nodeBunFactory.createProcessExecutor()),
|
|
101
|
+
'builtin',
|
|
102
|
+
);
|
|
94
103
|
host.registerGuard({ kind: 'always', evaluate: async () => true }, 'builtin');
|
|
95
104
|
host.registerGuard({ kind: 'never', evaluate: async () => false }, 'builtin');
|
|
96
105
|
host.registerGuard(
|
package/src/run-lifecycle.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
addSpanEvent,
|
|
3
|
+
type BusLifecycleEvents,
|
|
4
|
+
EventBus,
|
|
5
|
+
getLogger,
|
|
6
|
+
type Logger,
|
|
7
|
+
traceAsync,
|
|
8
|
+
} from '@gobing-ai/ts-infra';
|
|
2
9
|
import { getProcessEnv } from '@gobing-ai/ts-runtime';
|
|
3
10
|
import type { WorkflowEngineEvents } from './events';
|
|
4
11
|
import type {
|
|
@@ -61,6 +68,8 @@ export interface RunLifecycleDeps {
|
|
|
61
68
|
readonly logger?: Logger;
|
|
62
69
|
/** Optional event bus for structured in-process run observability. */
|
|
63
70
|
readonly events?: EventBus<WorkflowEngineEvents>;
|
|
71
|
+
/** Parent bus used when RunLifecycle must construct its own workflow event bus. */
|
|
72
|
+
readonly lifecycleBus?: EventBus<BusLifecycleEvents>;
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
/**
|
|
@@ -86,7 +95,9 @@ export class RunLifecycle {
|
|
|
86
95
|
this.runId = runId;
|
|
87
96
|
this.externalKey = externalKey;
|
|
88
97
|
this.persistence = deps.persistence;
|
|
89
|
-
this.events =
|
|
98
|
+
this.events =
|
|
99
|
+
deps.events ??
|
|
100
|
+
(deps.lifecycleBus ? new EventBus<WorkflowEngineEvents>({ lifecycleBus: deps.lifecycleBus }) : undefined);
|
|
90
101
|
this.logger = (deps.logger ?? getLogger('workflow')).child({ runId, workflow: workflowName, mode });
|
|
91
102
|
}
|
|
92
103
|
|
package/src/service.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { type BusLifecycleEvents, EventBus } from '@gobing-ai/ts-infra';
|
|
1
2
|
import { loadWorkflowDef } from './config';
|
|
2
3
|
import { FSMError, WorkflowResumeError } from './errors';
|
|
4
|
+
import type { WorkflowEngineEvents } from './events';
|
|
3
5
|
import type { WorkflowEngineHost } from './host';
|
|
4
6
|
import { RunLifecycle } from './run-lifecycle';
|
|
5
7
|
import { StateMachineDriver } from './state-machine';
|
|
@@ -20,11 +22,32 @@ import { resolveTemplates } from './variables';
|
|
|
20
22
|
export class WorkflowService {
|
|
21
23
|
/** Per-run serialization: ensures concurrent requestTransition calls serialize. */
|
|
22
24
|
private readonly runLocks = new Map<string, Promise<void>>();
|
|
25
|
+
private readonly lifecycleBus: EventBus<BusLifecycleEvents> | undefined;
|
|
23
26
|
|
|
24
27
|
constructor(
|
|
25
28
|
private readonly host: WorkflowEngineHost,
|
|
26
29
|
private readonly persistence: WorkflowPersistenceAdapter,
|
|
27
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Optional lifecycle bus to bridge `workflow.*` events into the
|
|
32
|
+
* application System Events stream (R3). When a run's `events` is
|
|
33
|
+
* omitted the service constructs an internal
|
|
34
|
+
* `EventBus<WorkflowEngineEvents>` parented to this bus so workflow
|
|
35
|
+
* events appear in the JSONL log.
|
|
36
|
+
*/
|
|
37
|
+
lifecycleBus?: EventBus<BusLifecycleEvents>,
|
|
38
|
+
) {
|
|
39
|
+
this.lifecycleBus = lifecycleBus ?? host.lifecycleBus;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Resolve a run's event bus: caller-supplied wins; otherwise parent to the service lifecycle bus. */
|
|
43
|
+
private resolveEvents(
|
|
44
|
+
events: EventBus<WorkflowEngineEvents> | undefined,
|
|
45
|
+
): EventBus<WorkflowEngineEvents> | undefined {
|
|
46
|
+
return (
|
|
47
|
+
events ??
|
|
48
|
+
(this.lifecycleBus ? new EventBus<WorkflowEngineEvents>({ lifecycleBus: this.lifecycleBus }) : undefined)
|
|
49
|
+
);
|
|
50
|
+
}
|
|
28
51
|
|
|
29
52
|
/** Load a workflow file and validate it. */
|
|
30
53
|
async load(path: string): Promise<WorkflowDef> {
|
|
@@ -33,13 +56,17 @@ export class WorkflowService {
|
|
|
33
56
|
|
|
34
57
|
/** Run an already-loaded workflow definition. */
|
|
35
58
|
async run(workflow: WorkflowDef, options: WorkflowRunOptions = {}): Promise<WorkflowRunResult> {
|
|
59
|
+
const events = this.resolveEvents(options.events);
|
|
36
60
|
if (workflow.kind === 'transition-flow') {
|
|
37
|
-
return await new TransitionFlowDriver({ host: this.host, persistence: this.persistence }).run(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
);
|
|
61
|
+
return await new TransitionFlowDriver({ host: this.host, persistence: this.persistence }).run(workflow, {
|
|
62
|
+
...options,
|
|
63
|
+
events,
|
|
64
|
+
});
|
|
41
65
|
}
|
|
42
|
-
return await new StateMachineDriver({ host: this.host, persistence: this.persistence }).run(workflow,
|
|
66
|
+
return await new StateMachineDriver({ host: this.host, persistence: this.persistence }).run(workflow, {
|
|
67
|
+
...options,
|
|
68
|
+
events,
|
|
69
|
+
});
|
|
43
70
|
}
|
|
44
71
|
|
|
45
72
|
/** Load and run a workflow file. */
|
|
@@ -114,7 +141,7 @@ export class WorkflowService {
|
|
|
114
141
|
const run = await this.persistence.loadRun(runId);
|
|
115
142
|
const extKey = run?.external_key ?? undefined;
|
|
116
143
|
const result = await this.persistence.reseedRun(runId, newState);
|
|
117
|
-
void options?.events?.emit('workflow.run.reseeded', {
|
|
144
|
+
void this.resolveEvents(options?.events)?.emit('workflow.run.reseeded', {
|
|
118
145
|
runId,
|
|
119
146
|
fromState: result.fromState ?? '',
|
|
120
147
|
toState: result.toState,
|
|
@@ -140,19 +167,20 @@ export class WorkflowService {
|
|
|
140
167
|
// Re-open the run as running.
|
|
141
168
|
const extKey = run.external_key ?? undefined;
|
|
142
169
|
await this.persistence.finalizeRun(runId, 'running', '');
|
|
143
|
-
|
|
170
|
+
const events = this.resolveEvents(options?.events);
|
|
171
|
+
void events?.emit('workflow.run.resumed', { runId, node: currentState, externalKey: extKey });
|
|
144
172
|
|
|
145
173
|
// Resume through the appropriate driver, starting from the paused state (skip on-enter).
|
|
146
174
|
if (workflow.kind === 'transition-flow') {
|
|
147
175
|
return await new TransitionFlowDriver({
|
|
148
176
|
host: this.host,
|
|
149
177
|
persistence: this.persistence,
|
|
150
|
-
}).resume(workflow, runId, currentState, extKey, options);
|
|
178
|
+
}).resume(workflow, runId, currentState, extKey, { ...options, events });
|
|
151
179
|
}
|
|
152
180
|
return await new StateMachineDriver({
|
|
153
181
|
host: this.host,
|
|
154
182
|
persistence: this.persistence,
|
|
155
|
-
}).resume(workflow as StateMachineWorkflowDef, runId, currentState, extKey, options);
|
|
183
|
+
}).resume(workflow as StateMachineWorkflowDef, runId, currentState, extKey, { ...options, events });
|
|
156
184
|
}
|
|
157
185
|
|
|
158
186
|
/** List runs currently paused. Optional filters and ordering. */
|
|
@@ -211,6 +239,7 @@ export class WorkflowService {
|
|
|
211
239
|
detail: `No state recorded for run "${runId}"`,
|
|
212
240
|
};
|
|
213
241
|
}
|
|
242
|
+
const events = this.resolveEvents(options?.events);
|
|
214
243
|
|
|
215
244
|
// An external transition is a single guarded hop on an existing run, not a
|
|
216
245
|
// run itself — borrow a span-free lifecycle so the transition persist+emit
|
|
@@ -218,14 +247,14 @@ export class WorkflowService {
|
|
|
218
247
|
const lifecycle = RunLifecycle.forExternalTransition(
|
|
219
248
|
workflow.name,
|
|
220
249
|
runId,
|
|
221
|
-
{ persistence: this.persistence, events
|
|
250
|
+
{ persistence: this.persistence, events },
|
|
222
251
|
extKey,
|
|
223
252
|
);
|
|
224
253
|
|
|
225
254
|
// Find the matching transition from current state to requested state.
|
|
226
255
|
const transition = workflow.transitions.find((t) => t.from === currentState && t.to === toState);
|
|
227
256
|
if (transition === undefined) {
|
|
228
|
-
return this.denyTransition(
|
|
257
|
+
return this.denyTransition(events, runId, currentState, toState, extKey, {
|
|
229
258
|
reason: 'no-such-transition',
|
|
230
259
|
detail: `No transition from "${currentState}" to "${toState}"`,
|
|
231
260
|
});
|
|
@@ -248,7 +277,7 @@ export class WorkflowService {
|
|
|
248
277
|
});
|
|
249
278
|
lifecycle.guardEvaluated(currentState, toState, transition.guard.kind, guardResult.passed);
|
|
250
279
|
if (!guardResult.passed) {
|
|
251
|
-
return this.denyTransition(
|
|
280
|
+
return this.denyTransition(events, runId, currentState, toState, extKey, {
|
|
252
281
|
reason: 'guard-failed',
|
|
253
282
|
detail: `Guard "${transition.guard.kind}" denied transition from "${currentState}" to "${toState}"`,
|
|
254
283
|
guardKind: transition.guard.kind,
|
|
@@ -262,7 +291,7 @@ export class WorkflowService {
|
|
|
262
291
|
// record — external transitions don't drive phase tracking.
|
|
263
292
|
const trigger = transition.trigger ?? null;
|
|
264
293
|
await lifecycle.commitHop(currentState, toState, trigger, 0);
|
|
265
|
-
void
|
|
294
|
+
void events?.emit('workflow.transition.requested', {
|
|
266
295
|
runId,
|
|
267
296
|
from: currentState,
|
|
268
297
|
to: toState,
|
|
@@ -278,14 +307,14 @@ export class WorkflowService {
|
|
|
278
307
|
* so the event payload and the returned reason can never drift apart.
|
|
279
308
|
*/
|
|
280
309
|
private denyTransition(
|
|
281
|
-
|
|
310
|
+
events: EventBus<WorkflowEngineEvents> | undefined,
|
|
282
311
|
runId: string,
|
|
283
312
|
from: string,
|
|
284
313
|
to: string,
|
|
285
314
|
externalKey: string | undefined,
|
|
286
315
|
denial: Omit<TransitionDenied, 'allowed'>,
|
|
287
316
|
): TransitionDenied {
|
|
288
|
-
void
|
|
317
|
+
void events?.emit('workflow.transition.denied', {
|
|
289
318
|
runId,
|
|
290
319
|
from,
|
|
291
320
|
to,
|