@smithers-orchestrator/engine 0.16.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/package.json +50 -0
- package/src/AlertHumanRequestOptions.ts +8 -0
- package/src/AlertRuntimeServices.ts +10 -0
- package/src/ChildWorkflowDefinition.ts +5 -0
- package/src/ChildWorkflowExecuteOptions.ts +14 -0
- package/src/ContinuationRequest.ts +3 -0
- package/src/HijackState.ts +19 -0
- package/src/HumanRequestKind.ts +1 -0
- package/src/HumanRequestStatus.ts +1 -0
- package/src/PlanNode.ts +29 -0
- package/src/RalphMeta.ts +7 -0
- package/src/RalphState.ts +4 -0
- package/src/RalphStateMap.ts +3 -0
- package/src/ScheduleResult.ts +15 -0
- package/src/SignalRunOptions.ts +5 -0
- package/src/alert-runtime.js +22 -0
- package/src/approvals.js +220 -0
- package/src/child-workflow.js +163 -0
- package/src/effect/ApprovalDeferredResolution.ts +13 -0
- package/src/effect/ApprovalDurableDeferredResolution.ts +11 -0
- package/src/effect/ApprovalPayload.ts +7 -0
- package/src/effect/ApprovalResult.ts +6 -0
- package/src/effect/BuilderNode.ts +52 -0
- package/src/effect/BuilderStepHandle.ts +47 -0
- package/src/effect/CancelPayload.ts +3 -0
- package/src/effect/CancelResult.ts +4 -0
- package/src/effect/DeferredResolution.ts +7 -0
- package/src/effect/DiffBundle.ts +7 -0
- package/src/effect/ExecuteTaskActivityOptions.ts +7 -0
- package/src/effect/FilePatch.ts +6 -0
- package/src/effect/GetRunPayload.ts +3 -0
- package/src/effect/GetRunResult.ts +3 -0
- package/src/effect/LegacyExecuteTaskFn.ts +24 -0
- package/src/effect/ListRunsPayload.ts +6 -0
- package/src/effect/RunStatusSchema.ts +9 -0
- package/src/effect/RunSummary.ts +23 -0
- package/src/effect/SignalPayload.ts +7 -0
- package/src/effect/SignalResult.ts +6 -0
- package/src/effect/SmithersSqliteOptions.ts +3 -0
- package/src/effect/SqlMessageStorageEventHistoryQuery.ts +7 -0
- package/src/effect/TaggedWorkerError.ts +46 -0
- package/src/effect/TaskActivityContext.ts +4 -0
- package/src/effect/TaskActivityRetryOptions.ts +4 -0
- package/src/effect/TaskBridgeToolConfig.ts +6 -0
- package/src/effect/TaskFailure.ts +3 -0
- package/src/effect/TaskResult.ts +5 -0
- package/src/effect/UnknownWorkerError.ts +5 -0
- package/src/effect/WaitForEventDurableDeferredResolution.ts +11 -0
- package/src/effect/WorkerDispatchKind.ts +1 -0
- package/src/effect/WorkerTask.ts +14 -0
- package/src/effect/WorkerTaskError.ts +4 -0
- package/src/effect/WorkerTaskKind.ts +1 -0
- package/src/effect/WorkflowPatchDecisionRecord.ts +4 -0
- package/src/effect/WorkflowPatchDecisions.ts +1 -0
- package/src/effect/WorkflowVersioningRuntime.ts +7 -0
- package/src/effect/activity-bridge.js +131 -0
- package/src/effect/bridge-utils.js +45 -0
- package/src/effect/builder.js +837 -0
- package/src/effect/compute-task-bridge.js +734 -0
- package/src/effect/deferred-bridge.js +63 -0
- package/src/effect/deferred-state-bridge.js +1343 -0
- package/src/effect/diff-bundle.js +352 -0
- package/src/effect/durable-deferred-bridge.js +282 -0
- package/src/effect/entity-worker.js +154 -0
- package/src/effect/http-runner.js +86 -0
- package/src/effect/rpc-schema.js +101 -0
- package/src/effect/single-runner.js +189 -0
- package/src/effect/sql-message-storage.js +817 -0
- package/src/effect/static-task-bridge.js +308 -0
- package/src/effect/versioning.js +123 -0
- package/src/effect/workflow-bridge.js +260 -0
- package/src/effect/workflow-make-bridge.js +233 -0
- package/src/engine.js +6933 -0
- package/src/events.js +237 -0
- package/src/external/json-schema-to-zod.js +214 -0
- package/src/getDefinedToolMetadata.js +10 -0
- package/src/hot/HotReloadEvent.ts +21 -0
- package/src/hot/HotWorkflowController.js +220 -0
- package/src/hot/OverlayOptions.ts +4 -0
- package/src/hot/WatchTreeOptions.ts +6 -0
- package/src/hot/index.js +9 -0
- package/src/hot/overlay.js +177 -0
- package/src/hot/watch.js +174 -0
- package/src/human-requests.js +120 -0
- package/src/index.d.ts +1597 -0
- package/src/index.js +41 -0
- package/src/runtime-owner.js +36 -0
- package/src/scheduler.js +31 -0
- package/src/signals.js +82 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import * as Workflow from "@effect/workflow/Workflow";
|
|
2
|
+
import * as WorkflowEngine from "@effect/workflow/WorkflowEngine";
|
|
3
|
+
import { Effect, Exit, Layer, Schema, Scope } from "effect";
|
|
4
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
|
+
import { SmithersDb } from "@smithers-orchestrator/db/adapter";
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {RunResult | (RunResult & { status: "continued"; nextRunId: string; })} RunBodyResult
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {<Schema>(workflow: SmithersWorkflow<Schema>, opts: RunOptions) => Promise<RunBodyResult>} RunBodyExecutor
|
|
12
|
+
*/
|
|
13
|
+
/** @typedef {import("@smithers-orchestrator/driver/RunOptions").RunOptions} RunOptions */
|
|
14
|
+
/** @typedef {import("@smithers-orchestrator/driver/RunResult").RunResult} RunResult */
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {{ notify(): void; wait(): Promise<void>; }} SchedulerWakeQueue
|
|
17
|
+
*/
|
|
18
|
+
/** @typedef {import("@smithers-orchestrator/components/SmithersWorkflow").SmithersWorkflow} SmithersWorkflow */
|
|
19
|
+
/** @typedef {import("effect").Context.Context<WorkflowEngine.WorkflowEngine>} WorkflowEngineContext */
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {{ readonly engineContext: WorkflowEngineContext; readonly scope: Scope.CloseableScope; readonly parentInstance: WorkflowEngine.WorkflowInstance["Type"]; readonly executeBody: RunBodyExecutor; executeChildWorkflow: <Schema>(workflow: SmithersWorkflow<Schema>, opts: RunOptions & { runId: string; }) => Promise<RunResult>; }} WorkflowMakeBridgeRuntime
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const runtimeStorage = new AsyncLocalStorage();
|
|
25
|
+
const workflowNamespaces = new WeakMap();
|
|
26
|
+
let nextWorkflowNamespace = 0;
|
|
27
|
+
/**
|
|
28
|
+
* @param {SmithersWorkflow<unknown>} workflow
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
function getWorkflowNamespace(workflow) {
|
|
32
|
+
const existing = workflowNamespaces.get(workflow);
|
|
33
|
+
if (existing) {
|
|
34
|
+
return existing;
|
|
35
|
+
}
|
|
36
|
+
const created = `workflow-${++nextWorkflowNamespace}`;
|
|
37
|
+
workflowNamespaces.set(workflow, created);
|
|
38
|
+
return created;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* @param {SmithersWorkflow<unknown>} workflow
|
|
42
|
+
* @param {string} runId
|
|
43
|
+
*/
|
|
44
|
+
function makeBridgeWorkflow(workflow, runId) {
|
|
45
|
+
return Workflow.make({
|
|
46
|
+
name: `SmithersWorkflowBridge:${getWorkflowNamespace(workflow)}:${runId}`,
|
|
47
|
+
payload: {
|
|
48
|
+
executionId: Schema.String,
|
|
49
|
+
},
|
|
50
|
+
success: Schema.Unknown,
|
|
51
|
+
idempotencyKey: ({ executionId }) => executionId,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* @param {RunResult["status"] | "continued"} status
|
|
56
|
+
* @returns {status is "waiting-approval" | "waiting-event" | "waiting-timer"}
|
|
57
|
+
*/
|
|
58
|
+
function isSuspendingStatus(status) {
|
|
59
|
+
return (status === "waiting-approval" ||
|
|
60
|
+
status === "waiting-event" ||
|
|
61
|
+
status === "waiting-timer");
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* @param {ReturnType<typeof makeBridgeWorkflow>} workflowBridge
|
|
65
|
+
* @param {Scope.CloseableScope} scope
|
|
66
|
+
* @param {WorkflowEngineContext} engineContext
|
|
67
|
+
* @param {Effect.Effect<RunResult, unknown, any>} execute
|
|
68
|
+
*/
|
|
69
|
+
async function registerBridgeWorkflow(workflowBridge, scope, engineContext, execute) {
|
|
70
|
+
await Effect.runPromise(Layer.buildWithScope(workflowBridge.toLayer(() => execute), scope).pipe(Effect.provide(engineContext)));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* @param {ReturnType<typeof makeBridgeWorkflow>} workflowBridge
|
|
74
|
+
* @param {string} runId
|
|
75
|
+
* @param {Scope.CloseableScope} scope
|
|
76
|
+
* @param {WorkflowEngineContext} engineContext
|
|
77
|
+
* @param {WorkflowMakeBridgeRuntime["parentInstance"]} parentInstance
|
|
78
|
+
*/
|
|
79
|
+
async function executeRegisteredChildWorkflow(workflowBridge, runId, scope, engineContext, parentInstance) {
|
|
80
|
+
return Effect.runPromise(Effect.gen(function* () {
|
|
81
|
+
const engine = yield* WorkflowEngine.WorkflowEngine;
|
|
82
|
+
return yield* engine.execute(workflowBridge, {
|
|
83
|
+
executionId: runId,
|
|
84
|
+
payload: { executionId: runId },
|
|
85
|
+
});
|
|
86
|
+
}).pipe(Effect.provideService(WorkflowEngine.WorkflowInstance, parentInstance), Effect.provideService(Scope.Scope, scope), Effect.provide(engineContext)));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* @template Schema
|
|
90
|
+
* @param {SmithersWorkflow<Schema>} workflow
|
|
91
|
+
* @param {RunOptions & { runId: string }} initialOpts
|
|
92
|
+
* @param {Omit<WorkflowMakeBridgeRuntime, "parentInstance" | "executeChildWorkflow">} services
|
|
93
|
+
* @param {{ current: string }} lastRunIdRef
|
|
94
|
+
*/
|
|
95
|
+
function createWorkflowExecutionEffect(workflow, initialOpts, services, lastRunIdRef) {
|
|
96
|
+
return Effect.gen(function* () {
|
|
97
|
+
const instance = yield* WorkflowEngine.WorkflowInstance;
|
|
98
|
+
const runtime = createWorkflowMakeBridgeRuntime({
|
|
99
|
+
...services,
|
|
100
|
+
parentInstance: instance,
|
|
101
|
+
});
|
|
102
|
+
let nextOpts = initialOpts;
|
|
103
|
+
while (true) {
|
|
104
|
+
lastRunIdRef.current = nextOpts.runId;
|
|
105
|
+
const result = yield* Effect.tryPromise({
|
|
106
|
+
try: () => withWorkflowMakeBridgeRuntime(runtime, () => services.executeBody(workflow, nextOpts)),
|
|
107
|
+
catch: (error) => error,
|
|
108
|
+
});
|
|
109
|
+
lastRunIdRef.current = result.runId;
|
|
110
|
+
if (isSuspendingStatus(result.status)) {
|
|
111
|
+
return yield* Workflow.suspend(instance);
|
|
112
|
+
}
|
|
113
|
+
if (result.status !== "continued" || !result.nextRunId) {
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
nextOpts = {
|
|
117
|
+
...nextOpts,
|
|
118
|
+
runId: result.nextRunId,
|
|
119
|
+
resume: true,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* @param {Omit<WorkflowMakeBridgeRuntime, "executeChildWorkflow">} services
|
|
126
|
+
* @returns {WorkflowMakeBridgeRuntime}
|
|
127
|
+
*/
|
|
128
|
+
function createWorkflowMakeBridgeRuntime(services) {
|
|
129
|
+
return {
|
|
130
|
+
...services,
|
|
131
|
+
executeChildWorkflow: async (workflow, opts) => {
|
|
132
|
+
const workflowBridge = makeBridgeWorkflow(workflow, opts.runId);
|
|
133
|
+
const lastRunIdRef = { current: opts.runId };
|
|
134
|
+
const execute = createWorkflowExecutionEffect(workflow, opts, services, lastRunIdRef);
|
|
135
|
+
await registerBridgeWorkflow(workflowBridge, services.scope, services.engineContext, execute);
|
|
136
|
+
return executeRegisteredChildWorkflow(workflowBridge, opts.runId, services.scope, services.engineContext, services.parentInstance);
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* @template T
|
|
142
|
+
* @param {WorkflowMakeBridgeRuntime} runtime
|
|
143
|
+
* @param {() => T} execute
|
|
144
|
+
* @returns {T}
|
|
145
|
+
*/
|
|
146
|
+
export function withWorkflowMakeBridgeRuntime(runtime, execute) {
|
|
147
|
+
return runtimeStorage.run(runtime, execute);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* @returns {| WorkflowMakeBridgeRuntime | undefined}
|
|
151
|
+
*/
|
|
152
|
+
export function getWorkflowMakeBridgeRuntime() {
|
|
153
|
+
return runtimeStorage.getStore();
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* @returns {SchedulerWakeQueue}
|
|
157
|
+
*/
|
|
158
|
+
export function createSchedulerWakeQueue() {
|
|
159
|
+
let pending = 0;
|
|
160
|
+
let resolver = null;
|
|
161
|
+
return {
|
|
162
|
+
notify() {
|
|
163
|
+
if (resolver) {
|
|
164
|
+
const current = resolver;
|
|
165
|
+
resolver = null;
|
|
166
|
+
current();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
pending += 1;
|
|
170
|
+
},
|
|
171
|
+
wait() {
|
|
172
|
+
if (pending > 0) {
|
|
173
|
+
pending -= 1;
|
|
174
|
+
return Promise.resolve();
|
|
175
|
+
}
|
|
176
|
+
return new Promise((resolve) => {
|
|
177
|
+
resolver = () => {
|
|
178
|
+
if (pending > 0) {
|
|
179
|
+
pending -= 1;
|
|
180
|
+
}
|
|
181
|
+
resolve();
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* @template Schema
|
|
189
|
+
* @param {SmithersWorkflow<Schema>} workflow
|
|
190
|
+
* @param {RunOptions & { runId: string }} opts
|
|
191
|
+
* @param {RunBodyExecutor} executeBody
|
|
192
|
+
* @returns {Promise<RunResult>}
|
|
193
|
+
*/
|
|
194
|
+
export async function runWorkflowWithMakeBridge(workflow, opts, executeBody) {
|
|
195
|
+
const adapter = new SmithersDb(workflow.db);
|
|
196
|
+
const scope = await Effect.runPromise(Scope.make());
|
|
197
|
+
let closed = false;
|
|
198
|
+
try {
|
|
199
|
+
const engineContext = await Effect.runPromise(Layer.buildWithScope(WorkflowEngine.layerMemory, scope));
|
|
200
|
+
const workflowBridge = makeBridgeWorkflow(workflow, opts.runId);
|
|
201
|
+
const instance = WorkflowEngine.WorkflowInstance.initial(workflowBridge, opts.runId);
|
|
202
|
+
const lastRunIdRef = { current: opts.runId };
|
|
203
|
+
const execute = createWorkflowExecutionEffect(workflow, opts, {
|
|
204
|
+
engineContext,
|
|
205
|
+
scope,
|
|
206
|
+
executeBody,
|
|
207
|
+
}, lastRunIdRef);
|
|
208
|
+
await registerBridgeWorkflow(workflowBridge, scope, engineContext, execute);
|
|
209
|
+
const result = await Effect.runPromise(execute.pipe(Workflow.intoResult, Effect.provideService(WorkflowEngine.WorkflowInstance, instance), Effect.provide(engineContext)));
|
|
210
|
+
if (result._tag === "Complete") {
|
|
211
|
+
if (Exit.isSuccess(result.exit)) {
|
|
212
|
+
return result.exit.value;
|
|
213
|
+
}
|
|
214
|
+
throw result.exit;
|
|
215
|
+
}
|
|
216
|
+
const run = await Effect.runPromise(adapter.getRun(lastRunIdRef.current));
|
|
217
|
+
const status = run?.status === "waiting-approval" ||
|
|
218
|
+
run?.status === "waiting-event" ||
|
|
219
|
+
run?.status === "waiting-timer"
|
|
220
|
+
? run.status
|
|
221
|
+
: "cancelled";
|
|
222
|
+
return {
|
|
223
|
+
runId: lastRunIdRef.current,
|
|
224
|
+
status,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
finally {
|
|
228
|
+
if (!closed) {
|
|
229
|
+
closed = true;
|
|
230
|
+
await Effect.runPromise(Scope.close(scope, Exit.void));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|