@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,154 @@
1
+ // @smithers-type-exports-begin
2
+ /** @typedef {import("./TaggedWorkerError.ts").TaggedWorkerError} TaggedWorkerError */
3
+ /** @typedef {import("./TaskFailure.ts").TaskFailure} TaskFailure */
4
+ /** @typedef {import("./TaskResult.ts").TaskResult} TaskResult */
5
+ /** @typedef {import("./UnknownWorkerError.ts").UnknownWorkerError} UnknownWorkerError */
6
+ /** @typedef {import("./WorkerDispatchKind.ts").WorkerDispatchKind} WorkerDispatchKind */
7
+ /** @typedef {import("./WorkerTask.ts").WorkerTask} WorkerTask */
8
+ /** @typedef {import("./WorkerTaskError.ts").WorkerTaskError} WorkerTaskError */
9
+ /** @typedef {import("./WorkerTaskKind.ts").WorkerTaskKind} WorkerTaskKind */
10
+ // @smithers-type-exports-end
11
+
12
+ import * as Entity from "@effect/cluster/Entity";
13
+ import * as Rpc from "@effect/rpc/Rpc";
14
+ import { Schema } from "effect";
15
+ /** @typedef {import("@smithers-orchestrator/graph/TaskDescriptor").TaskDescriptor} _TaskDescriptor */
16
+
17
+ export const WorkerTaskKind = Schema.Literal("agent", "compute", "static");
18
+ export const WorkerDispatchKind = Schema.Literal("compute", "static", "legacy");
19
+ export const WorkerTask = Schema.Struct({
20
+ executionId: Schema.String,
21
+ bridgeKey: Schema.String,
22
+ workflowName: Schema.String,
23
+ runId: Schema.String,
24
+ nodeId: Schema.String,
25
+ iteration: Schema.Number,
26
+ retries: Schema.Number,
27
+ taskKind: WorkerTaskKind,
28
+ dispatchKind: WorkerDispatchKind,
29
+ });
30
+ const WorkerErrorDetails = Schema.Record({
31
+ key: Schema.String,
32
+ value: Schema.Unknown,
33
+ });
34
+ const TaskAbortedError = Schema.Struct({
35
+ _tag: Schema.Literal("TaskAborted"),
36
+ message: Schema.String,
37
+ details: Schema.optional(WorkerErrorDetails),
38
+ name: Schema.optional(Schema.String),
39
+ });
40
+ const TaskTimeoutError = Schema.Struct({
41
+ _tag: Schema.Literal("TaskTimeout"),
42
+ message: Schema.String,
43
+ nodeId: Schema.String,
44
+ attempt: Schema.Number,
45
+ timeoutMs: Schema.Number,
46
+ });
47
+ const TaskHeartbeatTimeoutError = Schema.Struct({
48
+ _tag: Schema.Literal("TaskHeartbeatTimeout"),
49
+ message: Schema.String,
50
+ nodeId: Schema.String,
51
+ iteration: Schema.Number,
52
+ attempt: Schema.Number,
53
+ timeoutMs: Schema.Number,
54
+ staleForMs: Schema.Number,
55
+ lastHeartbeatAtMs: Schema.Number,
56
+ });
57
+ const RunNotFoundError = Schema.Struct({
58
+ _tag: Schema.Literal("RunNotFound"),
59
+ message: Schema.String,
60
+ runId: Schema.String,
61
+ });
62
+ const InvalidInputError = Schema.Struct({
63
+ _tag: Schema.Literal("InvalidInput"),
64
+ message: Schema.String,
65
+ details: Schema.optional(WorkerErrorDetails),
66
+ });
67
+ const DbWriteFailedError = Schema.Struct({
68
+ _tag: Schema.Literal("DbWriteFailed"),
69
+ message: Schema.String,
70
+ details: Schema.optional(WorkerErrorDetails),
71
+ });
72
+ const AgentCliError = Schema.Struct({
73
+ _tag: Schema.Literal("AgentCliError"),
74
+ message: Schema.String,
75
+ details: Schema.optional(WorkerErrorDetails),
76
+ });
77
+ const WorkflowFailedError = Schema.Struct({
78
+ _tag: Schema.Literal("WorkflowFailed"),
79
+ message: Schema.String,
80
+ details: Schema.optional(WorkerErrorDetails),
81
+ status: Schema.optional(Schema.Number),
82
+ });
83
+ export const TaggedWorkerError = Schema.Union(TaskAbortedError, TaskTimeoutError, TaskHeartbeatTimeoutError, RunNotFoundError, InvalidInputError, DbWriteFailedError, AgentCliError, WorkflowFailedError);
84
+ const UnknownWorkerError = Schema.Struct({
85
+ _tag: Schema.Literal("UnknownWorkerError"),
86
+ errorId: Schema.String,
87
+ message: Schema.String,
88
+ });
89
+ export const WorkerTaskError = Schema.Union(TaggedWorkerError, UnknownWorkerError);
90
+ const TaskSuccess = Schema.Struct({
91
+ _tag: Schema.Literal("Success"),
92
+ executionId: Schema.String,
93
+ terminal: Schema.Boolean,
94
+ });
95
+ const TaskFailure = Schema.Struct({
96
+ _tag: Schema.Literal("Failure"),
97
+ executionId: Schema.String,
98
+ error: WorkerTaskError,
99
+ });
100
+ export const TaskResult = Schema.Union(TaskSuccess, TaskFailure);
101
+ export const TaskWorkerEntity = Entity.make("TaskWorker", [
102
+ Rpc.make("execute", {
103
+ payload: WorkerTask,
104
+ success: TaskResult,
105
+ }),
106
+ ]);
107
+ /**
108
+ * @param {_TaskDescriptor} desc
109
+ * @returns {WorkerTaskKind}
110
+ */
111
+ function getWorkerTaskKind(desc) {
112
+ if (desc.agent) {
113
+ return "agent";
114
+ }
115
+ if (desc.computeFn) {
116
+ return "compute";
117
+ }
118
+ return "static";
119
+ }
120
+ /**
121
+ * @param {string} bridgeKey
122
+ * @param {string} workflowName
123
+ * @param {string} runId
124
+ * @param {_TaskDescriptor} desc
125
+ * @param {WorkerDispatchKind} dispatchKind
126
+ * @returns {WorkerTask}
127
+ */
128
+ export function makeWorkerTask(bridgeKey, workflowName, runId, desc, dispatchKind) {
129
+ return {
130
+ executionId: bridgeKey,
131
+ bridgeKey,
132
+ workflowName,
133
+ runId,
134
+ nodeId: desc.nodeId,
135
+ iteration: desc.iteration,
136
+ retries: desc.retries,
137
+ taskKind: getWorkerTaskKind(desc),
138
+ dispatchKind,
139
+ };
140
+ }
141
+ /**
142
+ * @param {TaskResult} result
143
+ * @returns {result is TaskFailure}
144
+ */
145
+ export function isTaskResultFailure(result) {
146
+ return result._tag === "Failure";
147
+ }
148
+ /**
149
+ * @param {WorkerTaskError} error
150
+ * @returns {error is UnknownWorkerError}
151
+ */
152
+ export function isUnknownWorkerError(error) {
153
+ return error._tag === "UnknownWorkerError";
154
+ }
@@ -0,0 +1,86 @@
1
+ import { HttpRunner } from "@effect/cluster";
2
+ import { mkdir, cp, rm } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { Effect, Layer } from "effect";
5
+ import { SmithersError } from "@smithers-orchestrator/errors/SmithersError";
6
+ import { spawnCaptureEffect } from "@smithers-orchestrator/driver/child-process";
7
+ import { toSmithersError } from "@smithers-orchestrator/errors/toSmithersError";
8
+ import { SandboxEntityExecutor } from "@smithers-orchestrator/sandbox/effect/sandbox-entity";
9
+ /**
10
+ * @param {SandboxTransportConfig} config
11
+ * @returns {SandboxHandle}
12
+ */
13
+ function baseHandle(config) {
14
+ const sandboxRoot = join(config.rootDir, ".smithers", "sandboxes", config.runId, config.sandboxId);
15
+ return {
16
+ runtime: config.runtime,
17
+ runId: config.runId,
18
+ sandboxId: config.sandboxId,
19
+ sandboxRoot,
20
+ requestPath: join(sandboxRoot, "request"),
21
+ resultPath: join(sandboxRoot, "result"),
22
+ };
23
+ }
24
+ export const DockerSandboxExecutorLive = Layer.succeed(SandboxEntityExecutor, SandboxEntityExecutor.of({
25
+ create: (config) => Effect.gen(function* () {
26
+ const handle = baseHandle(config);
27
+ yield* spawnCaptureEffect("docker", ["info"], {
28
+ cwd: config.rootDir,
29
+ env: process.env,
30
+ timeoutMs: 10_000,
31
+ maxOutputBytes: 200_000,
32
+ }).pipe(Effect.catchAll(() => Effect.fail(new SmithersError("PROCESS_SPAWN_FAILED", "Docker daemon not reachable.", { runtime: "docker" }))));
33
+ yield* Effect.tryPromise({
34
+ try: async () => {
35
+ await mkdir(handle.requestPath, { recursive: true });
36
+ await mkdir(handle.resultPath, { recursive: true });
37
+ },
38
+ catch: (cause) => toSmithersError(cause, "create docker sandbox workspace"),
39
+ });
40
+ return handle;
41
+ }),
42
+ ship: (bundlePath, handle) => Effect.tryPromise({
43
+ try: async () => {
44
+ await rm(handle.requestPath, { recursive: true, force: true });
45
+ await mkdir(handle.requestPath, { recursive: true });
46
+ await cp(bundlePath, handle.requestPath, { recursive: true });
47
+ },
48
+ catch: (cause) => toSmithersError(cause, "ship docker bundle"),
49
+ }),
50
+ execute: (_command, _handle) => Effect.succeed({ exitCode: 0 }),
51
+ collect: (handle) => Effect.succeed({ bundlePath: handle.resultPath }),
52
+ cleanup: (_handle) => Effect.void,
53
+ }));
54
+ export const CodeplaneSandboxExecutorLive = Layer.succeed(SandboxEntityExecutor, SandboxEntityExecutor.of({
55
+ create: (config) => Effect.gen(function* () {
56
+ const apiUrl = process.env.CODEPLANE_API_URL;
57
+ const apiKey = process.env.CODEPLANE_API_KEY;
58
+ if (!apiUrl || !apiKey) {
59
+ yield* Effect.fail(new SmithersError("INVALID_INPUT", "Codeplane runtime requires CODEPLANE_API_URL and CODEPLANE_API_KEY."));
60
+ }
61
+ const handle = baseHandle(config);
62
+ yield* Effect.tryPromise({
63
+ try: async () => {
64
+ await mkdir(handle.requestPath, { recursive: true });
65
+ await mkdir(handle.resultPath, { recursive: true });
66
+ },
67
+ catch: (cause) => toSmithersError(cause, "create codeplane sandbox workspace"),
68
+ });
69
+ return {
70
+ ...handle,
71
+ workspaceId: `${config.runId}:${config.sandboxId}`,
72
+ };
73
+ }),
74
+ ship: (bundlePath, handle) => Effect.tryPromise({
75
+ try: async () => {
76
+ await rm(handle.requestPath, { recursive: true, force: true });
77
+ await mkdir(handle.requestPath, { recursive: true });
78
+ await cp(bundlePath, handle.requestPath, { recursive: true });
79
+ },
80
+ catch: (cause) => toSmithersError(cause, "ship codeplane bundle"),
81
+ }),
82
+ execute: (_command, _handle) => Effect.succeed({ exitCode: 0 }),
83
+ collect: (handle) => Effect.succeed({ bundlePath: handle.resultPath }),
84
+ cleanup: (_handle) => Effect.void,
85
+ }));
86
+ export const SandboxHttpRunner = HttpRunner;
@@ -0,0 +1,101 @@
1
+ // @smithers-type-exports-begin
2
+ /** @typedef {import("./ApprovalPayload.ts").ApprovalPayload} ApprovalPayload */
3
+ /** @typedef {import("./ApprovalResult.ts").ApprovalResult} ApprovalResult */
4
+ /** @typedef {import("./CancelPayload.ts").CancelPayload} CancelPayload */
5
+ /** @typedef {import("./CancelResult.ts").CancelResult} CancelResult */
6
+ /** @typedef {import("./GetRunPayload.ts").GetRunPayload} GetRunPayload */
7
+ /** @typedef {import("./GetRunResult.ts").GetRunResult} GetRunResult */
8
+ /** @typedef {import("./ListRunsPayload.ts").ListRunsPayload} ListRunsPayload */
9
+ /** @typedef {import("./RunStatusSchema.ts").RunStatusSchema} RunStatusSchema */
10
+ /** @typedef {import("./RunSummary.ts").RunSummary} RunSummary */
11
+ /** @typedef {import("./SignalPayload.ts").SignalPayload} SignalPayload */
12
+ /** @typedef {import("./SignalResult.ts").SignalResult} SignalResult */
13
+ // @smithers-type-exports-end
14
+
15
+ import * as Rpc from "@effect/rpc/Rpc";
16
+ import * as RpcGroup from "@effect/rpc/RpcGroup";
17
+ import { Schema } from "effect";
18
+ export const RunStatusSchema = Schema.Literal("running", "waiting-approval", "waiting-event", "waiting-timer", "finished", "continued", "failed", "cancelled");
19
+ export const ApprovalPayloadSchema = Schema.Struct({
20
+ runId: Schema.String,
21
+ nodeId: Schema.String,
22
+ iteration: Schema.optional(Schema.Number),
23
+ note: Schema.optional(Schema.String),
24
+ decidedBy: Schema.optional(Schema.String),
25
+ });
26
+ export const ApprovalResultSchema = Schema.Struct({
27
+ runId: Schema.String,
28
+ nodeId: Schema.String,
29
+ iteration: Schema.Number,
30
+ approved: Schema.Boolean,
31
+ });
32
+ export const CancelPayloadSchema = Schema.Struct({
33
+ runId: Schema.String,
34
+ });
35
+ export const CancelResultSchema = Schema.Struct({
36
+ runId: Schema.String,
37
+ status: Schema.Literal("cancelling", "cancelled"),
38
+ });
39
+ export const SignalPayloadSchema = Schema.Struct({
40
+ runId: Schema.String,
41
+ signalName: Schema.String,
42
+ data: Schema.optional(Schema.Unknown),
43
+ correlationId: Schema.optional(Schema.String),
44
+ sentBy: Schema.optional(Schema.String),
45
+ });
46
+ export const SignalResultSchema = Schema.Struct({
47
+ runId: Schema.String,
48
+ signalName: Schema.String,
49
+ delivered: Schema.Boolean,
50
+ status: Schema.Literal("signalled", "ignored"),
51
+ });
52
+ export const ListRunsPayloadSchema = Schema.Struct({
53
+ limit: Schema.optional(Schema.Number),
54
+ status: Schema.optional(RunStatusSchema),
55
+ });
56
+ export const RunSummarySchema = Schema.Struct({
57
+ runId: Schema.String,
58
+ parentRunId: Schema.NullOr(Schema.String),
59
+ workflowName: Schema.String,
60
+ workflowPath: Schema.NullOr(Schema.String),
61
+ workflowHash: Schema.NullOr(Schema.String),
62
+ status: RunStatusSchema,
63
+ createdAtMs: Schema.Number,
64
+ startedAtMs: Schema.NullOr(Schema.Number),
65
+ finishedAtMs: Schema.NullOr(Schema.Number),
66
+ heartbeatAtMs: Schema.NullOr(Schema.Number),
67
+ runtimeOwnerId: Schema.NullOr(Schema.String),
68
+ cancelRequestedAtMs: Schema.NullOr(Schema.Number),
69
+ hijackRequestedAtMs: Schema.NullOr(Schema.Number),
70
+ hijackTarget: Schema.NullOr(Schema.String),
71
+ vcsType: Schema.NullOr(Schema.String),
72
+ vcsRoot: Schema.NullOr(Schema.String),
73
+ vcsRevision: Schema.NullOr(Schema.String),
74
+ errorJson: Schema.NullOr(Schema.String),
75
+ configJson: Schema.NullOr(Schema.String),
76
+ });
77
+ export const GetRunPayloadSchema = Schema.Struct({
78
+ runId: Schema.String,
79
+ });
80
+ export const GetRunResultSchema = Schema.NullOr(RunSummarySchema);
81
+ export const approve = Rpc.make("approve", {
82
+ payload: ApprovalPayloadSchema,
83
+ success: ApprovalResultSchema,
84
+ });
85
+ export const cancel = Rpc.make("cancel", {
86
+ payload: CancelPayloadSchema,
87
+ success: CancelResultSchema,
88
+ });
89
+ export const signal = Rpc.make("signal", {
90
+ payload: SignalPayloadSchema,
91
+ success: SignalResultSchema,
92
+ });
93
+ export const listRuns = Rpc.make("listRuns", {
94
+ payload: ListRunsPayloadSchema,
95
+ success: Schema.Array(RunSummarySchema),
96
+ });
97
+ export const getRun = Rpc.make("getRun", {
98
+ payload: GetRunPayloadSchema,
99
+ success: GetRunResultSchema,
100
+ });
101
+ export const SmithersRpcGroup = RpcGroup.make(approve, cancel, signal, listRuns, getRun);
@@ -0,0 +1,189 @@
1
+ import * as SingleRunner from "@effect/cluster/SingleRunner";
2
+ import * as SqliteClient from "@effect/sql-sqlite-bun/SqliteClient";
3
+ import { Effect, Layer, Scope } from "effect";
4
+ import { fromTaggedErrorPayload } from "@smithers-orchestrator/errors/fromTaggedErrorPayload";
5
+ import { toTaggedErrorPayload } from "@smithers-orchestrator/errors/toTaggedErrorPayload";
6
+ import { isUnknownWorkerError, isTaskResultFailure, TaskWorkerEntity, } from "./entity-worker.js";
7
+ /**
8
+ * @typedef {(task: WorkerTask) => void} TaskWorkerDispatchSubscriber
9
+ */
10
+ /**
11
+ * @typedef {{ terminal: boolean; }} WorkerExecutionResult
12
+ */
13
+ /** @typedef {import("./WorkerTask.ts").WorkerTask} WorkerTask */
14
+ /** @typedef {import("./TaskResult.ts").TaskResult} TaskResult */
15
+ /** @typedef {import("./TaskFailure.ts").TaskFailure} TaskFailure */
16
+ /** @typedef {import("./WorkerTaskError.ts").WorkerTaskError} WorkerTaskError */
17
+
18
+ const workerExecutions = new Map();
19
+ const workerErrors = new Map();
20
+ const dispatchSubscribers = new Set();
21
+ let singleRunnerRuntimePromise;
22
+ /**
23
+ * @param {WorkerTask} task
24
+ */
25
+ function notifyDispatchSubscribers(task) {
26
+ for (const subscriber of dispatchSubscribers) {
27
+ try {
28
+ subscriber(task);
29
+ }
30
+ catch {
31
+ // Dispatch observers are best-effort and should not affect execution.
32
+ }
33
+ }
34
+ }
35
+ /**
36
+ * @param {WorkerTask} task
37
+ * @returns {Extract<TaskResult, { _tag: "Failure" }>}
38
+ */
39
+ function buildMissingExecutionResult(task) {
40
+ return {
41
+ _tag: "Failure",
42
+ executionId: task.executionId,
43
+ error: {
44
+ _tag: "UnknownWorkerError",
45
+ errorId: `missing:${task.executionId}`,
46
+ message: `No worker execution registered for ${task.executionId}`,
47
+ },
48
+ };
49
+ }
50
+ /**
51
+ * @param {string} executionId
52
+ * @param {unknown} error
53
+ * @returns {string}
54
+ */
55
+ function storeWorkerError(executionId, error) {
56
+ const errorId = `${executionId}:error`;
57
+ workerErrors.set(errorId, error);
58
+ return errorId;
59
+ }
60
+ /**
61
+ * @param {string} executionId
62
+ * @param {unknown} error
63
+ * @returns {WorkerTaskError}
64
+ */
65
+ function toWorkerTaskError(executionId, error) {
66
+ const taggedError = toTaggedErrorPayload(error);
67
+ if (taggedError) {
68
+ return taggedError;
69
+ }
70
+ return {
71
+ _tag: "UnknownWorkerError",
72
+ errorId: storeWorkerError(executionId, error),
73
+ message: error instanceof Error ? error.message : String(error),
74
+ };
75
+ }
76
+ /**
77
+ * @param {TaskFailure} result
78
+ * @returns {unknown}
79
+ */
80
+ function consumeWorkerError(result) {
81
+ if (!isUnknownWorkerError(result.error)) {
82
+ return fromTaggedErrorPayload(result.error);
83
+ }
84
+ const error = workerErrors.get(result.error.errorId);
85
+ workerErrors.delete(result.error.errorId);
86
+ if (error !== undefined) {
87
+ return error;
88
+ }
89
+ return new Error(result.error.message);
90
+ }
91
+ /**
92
+ * @param {WorkerTask} task
93
+ * @returns {Promise<TaskResult>}
94
+ */
95
+ async function runRegisteredExecution(task) {
96
+ const registered = workerExecutions.get(task.executionId);
97
+ if (!registered) {
98
+ return buildMissingExecutionResult(task);
99
+ }
100
+ try {
101
+ notifyDispatchSubscribers(registered.task);
102
+ const result = await registered.execute();
103
+ return {
104
+ _tag: "Success",
105
+ executionId: task.executionId,
106
+ terminal: result.terminal,
107
+ };
108
+ }
109
+ catch (error) {
110
+ return {
111
+ _tag: "Failure",
112
+ executionId: task.executionId,
113
+ error: toWorkerTaskError(task.executionId, error),
114
+ };
115
+ }
116
+ finally {
117
+ if (workerExecutions.get(task.executionId) === registered) {
118
+ workerExecutions.delete(task.executionId);
119
+ }
120
+ }
121
+ }
122
+ /**
123
+ * @returns {Promise<SingleRunnerRuntime>}
124
+ */
125
+ async function buildSingleRunnerRuntime() {
126
+ const runnerLayer = SingleRunner.layer({ runnerStorage: "memory" }).pipe(Layer.provide(Layer.orDie(SqliteClient.layer({
127
+ filename: ":memory:",
128
+ disableWAL: true,
129
+ }))));
130
+ const layer = TaskWorkerEntity.toLayer(TaskWorkerEntity.of({
131
+ execute: (request) => Effect.promise(() => runRegisteredExecution(request.payload)),
132
+ }), { concurrency: "unbounded" }).pipe(Layer.provideMerge(runnerLayer));
133
+ const scope = await Effect.runPromise(Scope.make());
134
+ const context = await Effect.runPromise(Layer.buildWithScope(layer, scope));
135
+ const client = await Effect.runPromise(TaskWorkerEntity.client.pipe(Effect.provide(context)));
136
+ return {
137
+ client: client,
138
+ context,
139
+ };
140
+ }
141
+ /**
142
+ * @returns {Promise<SingleRunnerRuntime>}
143
+ */
144
+ async function getSingleRunnerRuntime() {
145
+ if (!singleRunnerRuntimePromise) {
146
+ singleRunnerRuntimePromise = buildSingleRunnerRuntime().catch((error) => {
147
+ singleRunnerRuntimePromise = undefined;
148
+ throw error;
149
+ });
150
+ }
151
+ return singleRunnerRuntimePromise;
152
+ }
153
+ /**
154
+ * @param {WorkerTask} task
155
+ * @param {() => Promise<WorkerExecutionResult>} execute
156
+ * @returns {Promise<WorkerExecutionResult>}
157
+ */
158
+ export async function dispatchWorkerTask(task, execute) {
159
+ const runtime = await getSingleRunnerRuntime();
160
+ const registered = {
161
+ task,
162
+ execute,
163
+ };
164
+ workerExecutions.set(task.executionId, registered);
165
+ try {
166
+ const result = await Effect.runPromise(runtime.client(task.bridgeKey).execute(task).pipe(Effect.provide(runtime.context)));
167
+ if (isTaskResultFailure(result)) {
168
+ throw consumeWorkerError(result);
169
+ }
170
+ return {
171
+ terminal: result.terminal,
172
+ };
173
+ }
174
+ finally {
175
+ if (workerExecutions.get(task.executionId) === registered) {
176
+ workerExecutions.delete(task.executionId);
177
+ }
178
+ }
179
+ }
180
+ /**
181
+ * @param {TaskWorkerDispatchSubscriber} subscriber
182
+ * @returns {() => void}
183
+ */
184
+ export function subscribeTaskWorkerDispatches(subscriber) {
185
+ dispatchSubscribers.add(subscriber);
186
+ return () => {
187
+ dispatchSubscribers.delete(subscriber);
188
+ };
189
+ }