@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,308 @@
1
+ import { Effect, Metric } from "effect";
2
+ import { z } from "zod";
3
+ import { buildOutputRow, stripAutoColumns, validateOutput } from "@smithers-orchestrator/db/output";
4
+ import { EventBus } from "../events.js";
5
+ import { makeAbortError, wireAbortSignal } from "./bridge-utils.js";
6
+ import { logDebug, logError, logInfo } from "@smithers-orchestrator/observability/logging";
7
+ import { attemptDuration, nodeDuration } from "@smithers-orchestrator/observability/metrics";
8
+ import { errorToJson } from "@smithers-orchestrator/errors/errorToJson";
9
+ import { SmithersError } from "@smithers-orchestrator/errors/SmithersError";
10
+ import { nowMs } from "@smithers-orchestrator/scheduler/nowMs";
11
+ import { getJjPointer } from "@smithers-orchestrator/vcs/jj";
12
+ import * as BunContext from "@effect/platform-bun/BunContext";
13
+ /** @typedef {import("@smithers-orchestrator/db/adapter").SmithersDb} _SmithersDb */
14
+ /**
15
+ * @typedef {{ rootDir: string; }} StaticTaskBridgeToolConfig
16
+ */
17
+ /** @typedef {import("@smithers-orchestrator/graph/TaskDescriptor").TaskDescriptor} _TaskDescriptor */
18
+
19
+ /**
20
+ * @param {unknown} err
21
+ * @returns {boolean}
22
+ */
23
+ function isAbortError(err) {
24
+ if (!err)
25
+ return false;
26
+ if (err.name === "AbortError")
27
+ return true;
28
+ if (typeof DOMException !== "undefined" &&
29
+ err instanceof DOMException &&
30
+ err.name === "AbortError") {
31
+ return true;
32
+ }
33
+ if (err instanceof Error) {
34
+ return /aborted|abort/i.test(err.message);
35
+ }
36
+ return false;
37
+ }
38
+ /**
39
+ * @param {_TaskDescriptor} desc
40
+ * @param {boolean} cacheEnabled
41
+ * @returns {boolean}
42
+ */
43
+ export const canExecuteBridgeManagedStaticTask = (desc, cacheEnabled) => {
44
+ if (cacheEnabled || desc.cachePolicy) {
45
+ return false;
46
+ }
47
+ if (desc.agent || desc.computeFn || desc.staticPayload === undefined) {
48
+ return false;
49
+ }
50
+ if (desc.worktreePath) {
51
+ return false;
52
+ }
53
+ return !desc.scorers || Object.keys(desc.scorers).length === 0;
54
+ };
55
+ /**
56
+ * @param {_SmithersDb} adapter
57
+ * @param {string} runId
58
+ * @param {_TaskDescriptor} desc
59
+ * @param {EventBus} eventBus
60
+ * @param {StaticTaskBridgeToolConfig} toolConfig
61
+ * @param {string} workflowName
62
+ * @param {AbortSignal} [signal]
63
+ * @returns {Promise<void>}
64
+ */
65
+ export const executeStaticTaskBridge = async (adapter, runId, desc, eventBus, toolConfig, workflowName, signal) => {
66
+ const taskStartMs = performance.now();
67
+ const attempts = await Effect.runPromise(adapter.listAttempts(runId, desc.nodeId, desc.iteration));
68
+ const attemptNo = (attempts[0]?.attempt ?? 0) + 1;
69
+ const taskAbortController = new AbortController();
70
+ const removeAbortForwarder = wireAbortSignal(taskAbortController, signal);
71
+ const taskSignal = taskAbortController.signal;
72
+ const startedAtMs = nowMs();
73
+ const attemptMeta = {
74
+ kind: "static",
75
+ prompt: desc.prompt ?? null,
76
+ staticPayload: desc.staticPayload ?? null,
77
+ label: desc.label ?? null,
78
+ outputTable: desc.outputTableName,
79
+ needsApproval: desc.needsApproval,
80
+ retries: desc.retries,
81
+ timeoutMs: desc.timeoutMs,
82
+ heartbeatTimeoutMs: desc.heartbeatTimeoutMs,
83
+ lastHeartbeat: null,
84
+ agentId: null,
85
+ agentModel: null,
86
+ agentEngine: null,
87
+ agentResume: null,
88
+ agentConversation: null,
89
+ resumedFromSession: null,
90
+ resumedFromConversation: false,
91
+ hijackHandoff: null,
92
+ };
93
+ await adapter.withTransaction("task-start", Effect.gen(function* () {
94
+ yield* adapter.insertAttempt({
95
+ runId,
96
+ nodeId: desc.nodeId,
97
+ iteration: desc.iteration,
98
+ attempt: attemptNo,
99
+ state: "in-progress",
100
+ startedAtMs,
101
+ finishedAtMs: null,
102
+ heartbeatAtMs: null,
103
+ heartbeatDataJson: null,
104
+ errorJson: null,
105
+ jjPointer: null,
106
+ jjCwd: toolConfig.rootDir,
107
+ cached: false,
108
+ metaJson: JSON.stringify(attemptMeta),
109
+ });
110
+ yield* adapter.insertNode({
111
+ runId,
112
+ nodeId: desc.nodeId,
113
+ iteration: desc.iteration,
114
+ state: "in-progress",
115
+ lastAttempt: attemptNo,
116
+ updatedAtMs: nowMs(),
117
+ outputTable: desc.outputTableName,
118
+ label: desc.label ?? null,
119
+ });
120
+ }));
121
+ await Effect.runPromise(eventBus.emitEventWithPersist({
122
+ type: "NodeStarted",
123
+ runId,
124
+ nodeId: desc.nodeId,
125
+ iteration: desc.iteration,
126
+ attempt: attemptNo,
127
+ timestampMs: nowMs(),
128
+ }));
129
+ try {
130
+ if (taskSignal.aborted) {
131
+ throw taskSignal.reason ?? makeAbortError();
132
+ }
133
+ logDebug("bridge-managed static task execution starting", {
134
+ runId,
135
+ nodeId: desc.nodeId,
136
+ iteration: desc.iteration,
137
+ attempt: attemptNo,
138
+ workflowName,
139
+ }, "engine:task");
140
+ let payload = stripAutoColumns(desc.staticPayload);
141
+ const payloadWithKeys = buildOutputRow(desc.outputTable, runId, desc.nodeId, desc.iteration, payload);
142
+ let validation = validateOutput(desc.outputTable, payloadWithKeys);
143
+ if (validation.ok && desc.outputSchema) {
144
+ const zodResult = desc.outputSchema.safeParse(payload);
145
+ if (!zodResult.success) {
146
+ validation = { ok: false, error: zodResult.error };
147
+ }
148
+ }
149
+ if (!validation.ok) {
150
+ attemptMeta.failureRetryable = false;
151
+ throw new SmithersError("INVALID_OUTPUT", `Task output failed validation for ${desc.outputTableName}`, {
152
+ attempt: attemptNo,
153
+ nodeId: desc.nodeId,
154
+ iteration: desc.iteration,
155
+ outputTable: desc.outputTableName,
156
+ issues: validation.error?.issues,
157
+ }, { cause: validation.error });
158
+ }
159
+ payload = validation.data;
160
+ const completedAtMs = nowMs();
161
+ const jjPointer = await Effect.runPromise(getJjPointer(toolConfig.rootDir).pipe(Effect.provide(BunContext.layer)));
162
+ await adapter.withTransaction("task-completion", Effect.gen(function* () {
163
+ yield* adapter.upsertOutputRow(desc.outputTable, { runId, nodeId: desc.nodeId, iteration: desc.iteration }, payload);
164
+ yield* adapter.updateAttempt(runId, desc.nodeId, desc.iteration, attemptNo, {
165
+ state: "finished",
166
+ finishedAtMs: completedAtMs,
167
+ jjPointer,
168
+ cached: false,
169
+ metaJson: JSON.stringify(attemptMeta),
170
+ responseText: null,
171
+ });
172
+ yield* adapter.insertNode({
173
+ runId,
174
+ nodeId: desc.nodeId,
175
+ iteration: desc.iteration,
176
+ state: "finished",
177
+ lastAttempt: attemptNo,
178
+ updatedAtMs: completedAtMs,
179
+ outputTable: desc.outputTableName,
180
+ label: desc.label ?? null,
181
+ });
182
+ }));
183
+ await Effect.runPromise(eventBus.emitEventWithPersist({
184
+ type: "NodeFinished",
185
+ runId,
186
+ nodeId: desc.nodeId,
187
+ iteration: desc.iteration,
188
+ attempt: attemptNo,
189
+ timestampMs: nowMs(),
190
+ }));
191
+ const taskElapsedMs = performance.now() - taskStartMs;
192
+ void Effect.runPromise(Effect.all([
193
+ Metric.update(nodeDuration, taskElapsedMs),
194
+ Metric.update(attemptDuration, taskElapsedMs),
195
+ ], { discard: true }));
196
+ logInfo("bridge-managed static task execution finished", {
197
+ runId,
198
+ nodeId: desc.nodeId,
199
+ iteration: desc.iteration,
200
+ attempt: attemptNo,
201
+ durationMs: Math.round(taskElapsedMs),
202
+ jjPointer,
203
+ }, "engine:task");
204
+ }
205
+ catch (err) {
206
+ const aborted = taskSignal.aborted || isAbortError(err);
207
+ const effectiveError = aborted && taskSignal.reason !== undefined
208
+ ? taskSignal.reason
209
+ : aborted
210
+ ? makeAbortError()
211
+ : err;
212
+ if (aborted) {
213
+ const cancelledAtMs = nowMs();
214
+ await adapter.withTransaction("task-cancel", Effect.gen(function* () {
215
+ yield* adapter.updateAttempt(runId, desc.nodeId, desc.iteration, attemptNo, {
216
+ state: "cancelled",
217
+ finishedAtMs: cancelledAtMs,
218
+ errorJson: JSON.stringify(errorToJson(effectiveError)),
219
+ metaJson: JSON.stringify(attemptMeta),
220
+ responseText: null,
221
+ });
222
+ yield* adapter.insertNode({
223
+ runId,
224
+ nodeId: desc.nodeId,
225
+ iteration: desc.iteration,
226
+ state: "cancelled",
227
+ lastAttempt: attemptNo,
228
+ updatedAtMs: cancelledAtMs,
229
+ outputTable: desc.outputTableName,
230
+ label: desc.label ?? null,
231
+ });
232
+ }));
233
+ await Effect.runPromise(eventBus.emitEventWithPersist({
234
+ type: "NodeCancelled",
235
+ runId,
236
+ nodeId: desc.nodeId,
237
+ iteration: desc.iteration,
238
+ attempt: attemptNo,
239
+ reason: "aborted",
240
+ timestampMs: nowMs(),
241
+ }));
242
+ logInfo("bridge-managed static task execution cancelled", {
243
+ runId,
244
+ nodeId: desc.nodeId,
245
+ iteration: desc.iteration,
246
+ attempt: attemptNo,
247
+ error: effectiveError instanceof Error
248
+ ? effectiveError.message
249
+ : String(effectiveError),
250
+ }, "engine:task");
251
+ return;
252
+ }
253
+ logError("bridge-managed static task execution failed", {
254
+ runId,
255
+ nodeId: desc.nodeId,
256
+ iteration: desc.iteration,
257
+ attempt: attemptNo,
258
+ maxAttempts: Number.isFinite(desc.retries) ? desc.retries + 1 : "infinite",
259
+ error: effectiveError instanceof Error
260
+ ? effectiveError.message
261
+ : String(effectiveError),
262
+ }, "engine:task");
263
+ const failedAtMs = nowMs();
264
+ await adapter.withTransaction("task-fail", Effect.gen(function* () {
265
+ yield* adapter.updateAttempt(runId, desc.nodeId, desc.iteration, attemptNo, {
266
+ state: "failed",
267
+ finishedAtMs: failedAtMs,
268
+ errorJson: JSON.stringify(errorToJson(effectiveError)),
269
+ metaJson: JSON.stringify(attemptMeta),
270
+ responseText: null,
271
+ });
272
+ yield* adapter.insertNode({
273
+ runId,
274
+ nodeId: desc.nodeId,
275
+ iteration: desc.iteration,
276
+ state: "failed",
277
+ lastAttempt: attemptNo,
278
+ updatedAtMs: failedAtMs,
279
+ outputTable: desc.outputTableName,
280
+ label: desc.label ?? null,
281
+ });
282
+ }));
283
+ await Effect.runPromise(eventBus.emitEventWithPersist({
284
+ type: "NodeFailed",
285
+ runId,
286
+ nodeId: desc.nodeId,
287
+ iteration: desc.iteration,
288
+ attempt: attemptNo,
289
+ error: errorToJson(effectiveError),
290
+ timestampMs: nowMs(),
291
+ }));
292
+ const updatedAttempts = await Effect.runPromise(adapter.listAttempts(runId, desc.nodeId, desc.iteration));
293
+ const failedAttempts = updatedAttempts.filter((attempt) => attempt.state === "failed");
294
+ if (attemptMeta.failureRetryable !== false && failedAttempts.length <= desc.retries) {
295
+ await Effect.runPromise(eventBus.emitEventWithPersist({
296
+ type: "NodeRetrying",
297
+ runId,
298
+ nodeId: desc.nodeId,
299
+ iteration: desc.iteration,
300
+ attempt: attemptNo + 1,
301
+ timestampMs: nowMs(),
302
+ }));
303
+ }
304
+ }
305
+ finally {
306
+ removeAbortForwarder();
307
+ }
308
+ };
@@ -0,0 +1,123 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import React from "react";
3
+ /** @typedef {import("./WorkflowPatchDecisionRecord.ts").WorkflowPatchDecisionRecord} WorkflowPatchDecisionRecord */
4
+ /** @typedef {import("./WorkflowPatchDecisions.ts").WorkflowPatchDecisions} WorkflowPatchDecisions */
5
+ /** @typedef {import("./WorkflowVersioningRuntime.ts").WorkflowVersioningRuntime} WorkflowVersioningRuntime */
6
+ /**
7
+ * @typedef {{ baseConfig: Record<string, unknown>; initialDecisions?: WorkflowPatchDecisions; isNewRun: boolean; persist: (config: Record<string, unknown>) => Promise<void>; recordDecision?: (record: WorkflowPatchDecisionRecord) => Promise<void>; }} WorkflowVersioningRuntimeOptions
8
+ */
9
+
10
+ const storage = new AsyncLocalStorage();
11
+ /**
12
+ * @param {string} value
13
+ * @returns {string}
14
+ */
15
+ function normalizePatchId(value) {
16
+ return value.trim();
17
+ }
18
+ /**
19
+ * @param {unknown} value
20
+ * @returns {WorkflowPatchDecisions}
21
+ */
22
+ function normalizePatchDecisions(value) {
23
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
24
+ return {};
25
+ }
26
+ const decisions = {};
27
+ for (const [key, entry] of Object.entries(value)) {
28
+ const patchId = normalizePatchId(String(key));
29
+ if (!patchId)
30
+ continue;
31
+ if (typeof entry === "boolean") {
32
+ decisions[patchId] = entry;
33
+ }
34
+ }
35
+ return decisions;
36
+ }
37
+ /**
38
+ * @param {WorkflowVersioningRuntimeOptions} options
39
+ * @returns {WorkflowVersioningRuntime}
40
+ */
41
+ export function createWorkflowVersioningRuntime(options) {
42
+ const decisions = new Map(Object.entries(normalizePatchDecisions(options.initialDecisions)));
43
+ let currentConfig = { ...options.baseConfig };
44
+ let dirty = false;
45
+ const pendingRecords = [];
46
+ return {
47
+ /**
48
+ * @param {string} patchId
49
+ * @returns {boolean}
50
+ */
51
+ resolve(patchId) {
52
+ const normalized = normalizePatchId(patchId);
53
+ if (!normalized) {
54
+ return false;
55
+ }
56
+ const existing = decisions.get(normalized);
57
+ if (typeof existing === "boolean") {
58
+ return existing;
59
+ }
60
+ const decision = options.isNewRun;
61
+ decisions.set(normalized, decision);
62
+ dirty = true;
63
+ pendingRecords.push({ patchId: normalized, decision });
64
+ return decision;
65
+ },
66
+ async flush() {
67
+ if (!dirty && pendingRecords.length === 0) {
68
+ return;
69
+ }
70
+ const nextConfig = dirty
71
+ ? {
72
+ ...currentConfig,
73
+ workflowPatches: Object.fromEntries(decisions.entries()),
74
+ }
75
+ : currentConfig;
76
+ if (dirty) {
77
+ await options.persist(nextConfig);
78
+ currentConfig = nextConfig;
79
+ dirty = false;
80
+ }
81
+ if (pendingRecords.length > 0 && options.recordDecision) {
82
+ const records = pendingRecords.slice();
83
+ for (const record of records) {
84
+ await options.recordDecision(record);
85
+ }
86
+ pendingRecords.splice(0, records.length);
87
+ }
88
+ },
89
+ snapshot() {
90
+ return Object.fromEntries(decisions.entries());
91
+ },
92
+ };
93
+ }
94
+ /**
95
+ * @template T
96
+ * @param {WorkflowVersioningRuntime} runtime
97
+ * @param {() => T} execute
98
+ * @returns {T}
99
+ */
100
+ export function withWorkflowVersioningRuntime(runtime, execute) {
101
+ return storage.run(runtime, execute);
102
+ }
103
+ /**
104
+ * @returns {| WorkflowVersioningRuntime | undefined}
105
+ */
106
+ export function getWorkflowVersioningRuntime() {
107
+ return storage.getStore();
108
+ }
109
+ /**
110
+ * @param {Record<string, unknown> | null | undefined} config
111
+ * @returns {WorkflowPatchDecisions}
112
+ */
113
+ export function getWorkflowPatchDecisions(config) {
114
+ return normalizePatchDecisions(config?.workflowPatches);
115
+ }
116
+ /**
117
+ * @param {string} patchId
118
+ * @returns {boolean}
119
+ */
120
+ export function usePatched(patchId) {
121
+ const runtime = getWorkflowVersioningRuntime();
122
+ return React.useMemo(() => runtime?.resolve(patchId) ?? false, [runtime, patchId]);
123
+ }
@@ -0,0 +1,260 @@
1
+ import { Effect } from "effect";
2
+ import { SmithersDb } from "@smithers-orchestrator/db/adapter";
3
+ import { EventBus } from "../events.js";
4
+ import { toSmithersError } from "@smithers-orchestrator/errors/toSmithersError";
5
+ import { makeWorkerTask, } from "./entity-worker.js";
6
+ import { executeTaskActivity, makeTaskBridgeKey, RetriableTaskFailure, } from "./activity-bridge.js";
7
+ import { parseAttemptMetaJson } from "./bridge-utils.js";
8
+ import { canExecuteBridgeManagedComputeTask, executeComputeTaskBridge, } from "./compute-task-bridge.js";
9
+ import { canExecuteBridgeManagedStaticTask, executeStaticTaskBridge, } from "./static-task-bridge.js";
10
+ import { dispatchWorkerTask } from "./single-runner.js";
11
+ /** @typedef {import("../HijackState.ts").HijackState} HijackState */
12
+ /** @typedef {import("./LegacyExecuteTaskFn.ts").LegacyExecuteTaskFn} LegacyExecuteTaskFn */
13
+ /** @typedef {import("./TaskBridgeToolConfig.ts").TaskBridgeToolConfig} TaskBridgeToolConfig */
14
+ /** @typedef {import("@smithers-orchestrator/graph/TaskDescriptor").TaskDescriptor} _TaskDescriptor */
15
+ /** @typedef {import("./TaskActivityContext.ts").TaskActivityContext} _TaskActivityContext */
16
+ /** @typedef {import("drizzle-orm/bun-sqlite").BunSQLiteDatabase<Record<string, unknown>>} _BunSQLiteDatabase */
17
+ /** @typedef {import("drizzle-orm/sqlite-core").SQLiteTable} SQLiteTable */
18
+ /**
19
+ * @typedef {"compute" | "static" | "legacy"} BridgeManagedTaskKind
20
+ */
21
+
22
+ export { bridgeApprovalResolve, bridgeSignalResolve, bridgeWaitForEventResolve, awaitApprovalDurableDeferred, awaitWaitForEventDurableDeferred, makeApprovalDurableDeferred, makeDurableDeferredBridgeExecutionId, makeWaitForEventDurableDeferred, } from "./durable-deferred-bridge.js";
23
+ export { cancelPendingTimersBridge, isBridgeManagedTimerTask, isBridgeManagedWaitForEventTask, resolveDeferredTaskStateBridge, } from "./deferred-state-bridge.js";
24
+ export { createSchedulerWakeQueue, getWorkflowMakeBridgeRuntime, runWorkflowWithMakeBridge, withWorkflowMakeBridgeRuntime, } from "./workflow-make-bridge.js";
25
+ export { SqlMessageStorage, ensureSqlMessageStorage, ensureSqlMessageStorageEffect, getSqlMessageStorage, } from "./sql-message-storage.js";
26
+ export { SandboxEntity, SandboxEntityExecutor, makeSandboxEntityId, makeSandboxTransportServiceEffect, } from "@smithers-orchestrator/sandbox/effect/sandbox-entity";
27
+ export { CodeplaneSandboxExecutorLive, DockerSandboxExecutorLive, SandboxHttpRunner, } from "./http-runner.js";
28
+ export { BubblewrapSandboxExecutorLive, SandboxSocketRunner, } from "@smithers-orchestrator/sandbox/effect/socket-runner";
29
+ export { isTaskResultFailure, makeWorkerTask, TaskResult, WorkerDispatchKind, WorkerTask, WorkerTaskKind, TaskWorkerEntity, } from "./entity-worker.js";
30
+ export { dispatchWorkerTask, subscribeTaskWorkerDispatches, } from "./single-runner.js";
31
+ /**
32
+ * Phase 0 Seam Adapter
33
+ *
34
+ * This file establishes the interface boundaries for bridging the legacy Smithers engine
35
+ * with the Effect ecosystem.
36
+ *
37
+ * Currently, it delegates to the legacy implementations exactly as they are.
38
+ * In Phase 1, `executeTaskBridge` will be replaced by `Activity.make()`.
39
+ * In subsequent phases, other engine boundaries will be modeled as Workflows.
40
+ */
41
+ const inflightTaskExecutions = new Map();
42
+ const completedTaskExecutions = new Map();
43
+ /**
44
+ * @template A
45
+ * @param {Effect.Effect<A, unknown, never> | PromiseLike<A> | A} value
46
+ * @returns {Promise<A>}
47
+ */
48
+ const runEffectOrPromise = async (value) => {
49
+ if (Effect.isEffect?.(value)) {
50
+ return Effect.runPromise(value);
51
+ }
52
+ return await value;
53
+ };
54
+ /**
55
+ * @param {string | null} [errorJson]
56
+ * @returns {string | null}
57
+ */
58
+ function parseAttemptErrorCode(errorJson) {
59
+ if (!errorJson)
60
+ return null;
61
+ try {
62
+ const parsed = JSON.parse(errorJson);
63
+ return typeof parsed?.code === "string" ? parsed.code : null;
64
+ }
65
+ catch {
66
+ return null;
67
+ }
68
+ }
69
+ /**
70
+ * @param {{ errorJson?: string | null; metaJson?: string | null } | null} [attempt]
71
+ */
72
+ function isRetryableBridgeTaskFailure(attempt) {
73
+ const meta = parseAttemptMetaJson(attempt?.metaJson);
74
+ if (meta?.failureRetryable === false) {
75
+ return false;
76
+ }
77
+ const kind = typeof meta?.kind === "string" ? meta.kind : null;
78
+ return !(kind !== "agent" && parseAttemptErrorCode(attempt?.errorJson) === "INVALID_OUTPUT");
79
+ }
80
+ /**
81
+ * @param {SmithersDb} adapter
82
+ * @param {string} runId
83
+ * @param {_TaskDescriptor} desc
84
+ * @param {_TaskActivityContext} context
85
+ */
86
+ const classifyTaskAttempt = async (adapter, runId, desc, context) => {
87
+ const attempts = await runEffectOrPromise(adapter.listAttempts(runId, desc.nodeId, desc.iteration));
88
+ const latest = attempts[0];
89
+ const latestAttempt = latest?.attempt ?? context.attempt;
90
+ const latestState = latest?.state ?? null;
91
+ if (latestState === "failed") {
92
+ const failedAttempts = attempts.filter((attempt) => attempt.state === "failed");
93
+ const hasNonRetryableFailure = failedAttempts.some((attempt) => !isRetryableBridgeTaskFailure(attempt));
94
+ if (!hasNonRetryableFailure && failedAttempts.length <= desc.retries) {
95
+ throw new RetriableTaskFailure(desc.nodeId, latestAttempt);
96
+ }
97
+ }
98
+ return {
99
+ state: latestState,
100
+ attempt: latestAttempt,
101
+ idempotencyKey: context.idempotencyKey,
102
+ };
103
+ };
104
+ /**
105
+ * @param {SmithersDb} adapter
106
+ * @param {string} runId
107
+ * @param {_TaskDescriptor} desc
108
+ */
109
+ const getNextTaskActivityAttempt = async (adapter, runId, desc) => {
110
+ const attempts = await runEffectOrPromise(adapter.listAttempts(runId, desc.nodeId, desc.iteration));
111
+ const latestAttempt = attempts[0]?.attempt ?? 0;
112
+ return latestAttempt + 1;
113
+ };
114
+ /**
115
+ * @param {SmithersDb} adapter
116
+ * @param {_BunSQLiteDatabase} db
117
+ * @param {string} runId
118
+ * @param {_TaskDescriptor} desc
119
+ * @param {Map<string, _TaskDescriptor>} descriptorMap
120
+ * @param {SQLiteTable} inputTable
121
+ * @param {EventBus} eventBus
122
+ * @param {TaskBridgeToolConfig} toolConfig
123
+ * @param {string} workflowName
124
+ * @param {boolean} cacheEnabled
125
+ * @param {BridgeManagedTaskKind} bridgeManagedExecution
126
+ * @param {_TaskActivityContext} context
127
+ * @param {AbortSignal} [signal]
128
+ * @param {Set<string>} [disabledAgents]
129
+ * @param {AbortController} [runAbortController]
130
+ * @param {HijackState} [hijackState]
131
+ * @param {LegacyExecuteTaskFn} [legacyExecuteTaskFn]
132
+ */
133
+ const executeBridgeAttempt = async (adapter, db, runId, desc, descriptorMap, inputTable, eventBus, toolConfig, workflowName, cacheEnabled, bridgeManagedExecution, context, signal, disabledAgents, runAbortController, hijackState, legacyExecuteTaskFn) => {
134
+ if (bridgeManagedExecution === "static") {
135
+ await executeStaticTaskBridge(adapter, runId, desc, eventBus, toolConfig, workflowName, signal);
136
+ }
137
+ else if (bridgeManagedExecution === "compute") {
138
+ await executeComputeTaskBridge(adapter, db, runId, desc, eventBus, toolConfig, workflowName, signal);
139
+ }
140
+ else {
141
+ await legacyExecuteTaskFn(adapter, db, runId, desc, descriptorMap, inputTable, eventBus, toolConfig, workflowName, cacheEnabled, signal, disabledAgents, runAbortController, hijackState);
142
+ }
143
+ return classifyTaskAttempt(adapter, runId, desc, context);
144
+ };
145
+ /**
146
+ * @param {SmithersDb} adapter
147
+ * @param {_BunSQLiteDatabase} db
148
+ * @param {string} runId
149
+ * @param {_TaskDescriptor} desc
150
+ * @param {Map<string, _TaskDescriptor>} descriptorMap
151
+ * @param {SQLiteTable} inputTable
152
+ * @param {EventBus} eventBus
153
+ * @param {TaskBridgeToolConfig} toolConfig
154
+ * @param {string} workflowName
155
+ * @param {boolean} cacheEnabled
156
+ * @param {BridgeManagedTaskKind} bridgeManagedExecution
157
+ * @param {string} bridgeKey
158
+ * @param {AbortSignal} [signal]
159
+ * @param {Set<string>} [disabledAgents]
160
+ * @param {AbortController} [runAbortController]
161
+ * @param {HijackState} [hijackState]
162
+ * @param {LegacyExecuteTaskFn} [legacyExecuteTaskFn]
163
+ */
164
+ const runTaskBridgeExecution = async (adapter, db, runId, desc, descriptorMap, inputTable, eventBus, toolConfig, workflowName, cacheEnabled, bridgeManagedExecution, bridgeKey, signal, disabledAgents, runAbortController, hijackState, legacyExecuteTaskFn) => {
165
+ const initialAttempt = await getNextTaskActivityAttempt(adapter, runId, desc);
166
+ return dispatchWorkerTask(makeWorkerTask(bridgeKey, workflowName, runId, desc, bridgeManagedExecution), async () => {
167
+ try {
168
+ await executeTaskActivity(adapter, workflowName, runId, desc, (context) => executeBridgeAttempt(adapter, db, runId, desc, descriptorMap, inputTable, eventBus, toolConfig, workflowName, cacheEnabled, bridgeManagedExecution, context, signal, disabledAgents, runAbortController, hijackState, legacyExecuteTaskFn), {
169
+ initialAttempt,
170
+ retry: false,
171
+ });
172
+ return { terminal: true };
173
+ }
174
+ catch (error) {
175
+ if (error instanceof RetriableTaskFailure) {
176
+ return { terminal: false };
177
+ }
178
+ throw error;
179
+ }
180
+ });
181
+ };
182
+ /**
183
+ * @param {SmithersDb} adapter
184
+ * @param {_BunSQLiteDatabase} db
185
+ * @param {string} runId
186
+ * @param {_TaskDescriptor} desc
187
+ * @param {Map<string, _TaskDescriptor>} descriptorMap
188
+ * @param {SQLiteTable} inputTable
189
+ * @param {EventBus} eventBus
190
+ * @param {TaskBridgeToolConfig} toolConfig
191
+ * @param {string} workflowName
192
+ * @param {boolean} cacheEnabled
193
+ * @param {AbortSignal} [signal]
194
+ * @param {Set<string>} [disabledAgents]
195
+ * @param {AbortController} [runAbortController]
196
+ * @param {HijackState} [hijackState]
197
+ * @param {LegacyExecuteTaskFn} [legacyExecuteTaskFn]
198
+ * @returns {Promise<void>}
199
+ */
200
+ export const executeTaskBridge = (adapter, db, runId, desc, descriptorMap, inputTable, eventBus, toolConfig, workflowName, cacheEnabled, signal, disabledAgents, runAbortController, hijackState, legacyExecuteTaskFn) => {
201
+ const bridgeManagedExecution = canExecuteBridgeManagedComputeTask(desc, cacheEnabled)
202
+ ? "compute"
203
+ : canExecuteBridgeManagedStaticTask(desc, cacheEnabled)
204
+ ? "static"
205
+ : "legacy";
206
+ if (bridgeManagedExecution === "legacy" && typeof legacyExecuteTaskFn !== "function") {
207
+ return Promise.reject(new TypeError("legacyExecuteTaskFn must be provided"));
208
+ }
209
+ const bridgeKey = makeTaskBridgeKey(adapter, workflowName, runId, desc);
210
+ const completed = completedTaskExecutions.get(bridgeKey);
211
+ if (completed) {
212
+ return completed;
213
+ }
214
+ const existing = inflightTaskExecutions.get(bridgeKey);
215
+ if (existing) {
216
+ return existing;
217
+ }
218
+ const execution = runTaskBridgeExecution(adapter, db, runId, desc, descriptorMap, inputTable, eventBus, toolConfig, workflowName, cacheEnabled, bridgeManagedExecution, bridgeKey, signal, disabledAgents, runAbortController, hijackState, legacyExecuteTaskFn)
219
+ .then((result) => {
220
+ if (!result.terminal) {
221
+ return undefined;
222
+ }
223
+ completedTaskExecutions.set(bridgeKey, execution);
224
+ setTimeout(() => {
225
+ if (completedTaskExecutions.get(bridgeKey) === execution) {
226
+ completedTaskExecutions.delete(bridgeKey);
227
+ }
228
+ }, 0);
229
+ return undefined;
230
+ })
231
+ .finally(() => {
232
+ if (inflightTaskExecutions.get(bridgeKey) === execution) {
233
+ inflightTaskExecutions.delete(bridgeKey);
234
+ }
235
+ });
236
+ inflightTaskExecutions.set(bridgeKey, execution);
237
+ return execution;
238
+ };
239
+ /**
240
+ * @param {SmithersDb} adapter
241
+ * @param {_BunSQLiteDatabase} db
242
+ * @param {string} runId
243
+ * @param {_TaskDescriptor} desc
244
+ * @param {Map<string, _TaskDescriptor>} descriptorMap
245
+ * @param {SQLiteTable} inputTable
246
+ * @param {EventBus} eventBus
247
+ * @param {TaskBridgeToolConfig} toolConfig
248
+ * @param {string} workflowName
249
+ * @param {boolean} cacheEnabled
250
+ * @param {AbortSignal} [signal]
251
+ * @param {Set<string>} [disabledAgents]
252
+ * @param {AbortController} [runAbortController]
253
+ * @param {HijackState} [hijackState]
254
+ * @param {LegacyExecuteTaskFn} [legacyExecuteTaskFn]
255
+ * @returns {Effect.Effect<void, import("@smithers-orchestrator/errors/SmithersError").SmithersError, never>}
256
+ */
257
+ export const executeTaskBridgeEffect = (adapter, db, runId, desc, descriptorMap, inputTable, eventBus, toolConfig, workflowName, cacheEnabled, signal, disabledAgents, runAbortController, hijackState, legacyExecuteTaskFn) => Effect.tryPromise({
258
+ try: () => executeTaskBridge(adapter, db, runId, desc, descriptorMap, inputTable, eventBus, toolConfig, workflowName, cacheEnabled, signal, disabledAgents, runAbortController, hijackState, legacyExecuteTaskFn),
259
+ catch: (cause) => toSmithersError(cause, "execute task bridge"),
260
+ });