@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.
Files changed (90) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +50 -0
  3. package/src/AlertHumanRequestOptions.ts +8 -0
  4. package/src/AlertRuntimeServices.ts +10 -0
  5. package/src/ChildWorkflowDefinition.ts +5 -0
  6. package/src/ChildWorkflowExecuteOptions.ts +14 -0
  7. package/src/ContinuationRequest.ts +3 -0
  8. package/src/HijackState.ts +19 -0
  9. package/src/HumanRequestKind.ts +1 -0
  10. package/src/HumanRequestStatus.ts +1 -0
  11. package/src/PlanNode.ts +29 -0
  12. package/src/RalphMeta.ts +7 -0
  13. package/src/RalphState.ts +4 -0
  14. package/src/RalphStateMap.ts +3 -0
  15. package/src/ScheduleResult.ts +15 -0
  16. package/src/SignalRunOptions.ts +5 -0
  17. package/src/alert-runtime.js +22 -0
  18. package/src/approvals.js +220 -0
  19. package/src/child-workflow.js +163 -0
  20. package/src/effect/ApprovalDeferredResolution.ts +13 -0
  21. package/src/effect/ApprovalDurableDeferredResolution.ts +11 -0
  22. package/src/effect/ApprovalPayload.ts +7 -0
  23. package/src/effect/ApprovalResult.ts +6 -0
  24. package/src/effect/BuilderNode.ts +52 -0
  25. package/src/effect/BuilderStepHandle.ts +47 -0
  26. package/src/effect/CancelPayload.ts +3 -0
  27. package/src/effect/CancelResult.ts +4 -0
  28. package/src/effect/DeferredResolution.ts +7 -0
  29. package/src/effect/DiffBundle.ts +7 -0
  30. package/src/effect/ExecuteTaskActivityOptions.ts +7 -0
  31. package/src/effect/FilePatch.ts +6 -0
  32. package/src/effect/GetRunPayload.ts +3 -0
  33. package/src/effect/GetRunResult.ts +3 -0
  34. package/src/effect/LegacyExecuteTaskFn.ts +24 -0
  35. package/src/effect/ListRunsPayload.ts +6 -0
  36. package/src/effect/RunStatusSchema.ts +9 -0
  37. package/src/effect/RunSummary.ts +23 -0
  38. package/src/effect/SignalPayload.ts +7 -0
  39. package/src/effect/SignalResult.ts +6 -0
  40. package/src/effect/SmithersSqliteOptions.ts +3 -0
  41. package/src/effect/SqlMessageStorageEventHistoryQuery.ts +7 -0
  42. package/src/effect/TaggedWorkerError.ts +46 -0
  43. package/src/effect/TaskActivityContext.ts +4 -0
  44. package/src/effect/TaskActivityRetryOptions.ts +4 -0
  45. package/src/effect/TaskBridgeToolConfig.ts +6 -0
  46. package/src/effect/TaskFailure.ts +3 -0
  47. package/src/effect/TaskResult.ts +5 -0
  48. package/src/effect/UnknownWorkerError.ts +5 -0
  49. package/src/effect/WaitForEventDurableDeferredResolution.ts +11 -0
  50. package/src/effect/WorkerDispatchKind.ts +1 -0
  51. package/src/effect/WorkerTask.ts +14 -0
  52. package/src/effect/WorkerTaskError.ts +4 -0
  53. package/src/effect/WorkerTaskKind.ts +1 -0
  54. package/src/effect/WorkflowPatchDecisionRecord.ts +4 -0
  55. package/src/effect/WorkflowPatchDecisions.ts +1 -0
  56. package/src/effect/WorkflowVersioningRuntime.ts +7 -0
  57. package/src/effect/activity-bridge.js +131 -0
  58. package/src/effect/bridge-utils.js +45 -0
  59. package/src/effect/builder.js +837 -0
  60. package/src/effect/compute-task-bridge.js +734 -0
  61. package/src/effect/deferred-bridge.js +63 -0
  62. package/src/effect/deferred-state-bridge.js +1343 -0
  63. package/src/effect/diff-bundle.js +352 -0
  64. package/src/effect/durable-deferred-bridge.js +282 -0
  65. package/src/effect/entity-worker.js +154 -0
  66. package/src/effect/http-runner.js +86 -0
  67. package/src/effect/rpc-schema.js +101 -0
  68. package/src/effect/single-runner.js +189 -0
  69. package/src/effect/sql-message-storage.js +817 -0
  70. package/src/effect/static-task-bridge.js +308 -0
  71. package/src/effect/versioning.js +123 -0
  72. package/src/effect/workflow-bridge.js +260 -0
  73. package/src/effect/workflow-make-bridge.js +233 -0
  74. package/src/engine.js +6933 -0
  75. package/src/events.js +237 -0
  76. package/src/external/json-schema-to-zod.js +214 -0
  77. package/src/getDefinedToolMetadata.js +10 -0
  78. package/src/hot/HotReloadEvent.ts +21 -0
  79. package/src/hot/HotWorkflowController.js +220 -0
  80. package/src/hot/OverlayOptions.ts +4 -0
  81. package/src/hot/WatchTreeOptions.ts +6 -0
  82. package/src/hot/index.js +9 -0
  83. package/src/hot/overlay.js +177 -0
  84. package/src/hot/watch.js +174 -0
  85. package/src/human-requests.js +120 -0
  86. package/src/index.d.ts +1597 -0
  87. package/src/index.js +41 -0
  88. package/src/runtime-owner.js +36 -0
  89. package/src/scheduler.js +31 -0
  90. 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
+ }