@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,52 @@
1
+ import type { BuilderStepHandle } from "./BuilderStepHandle.ts";
2
+
3
+ type SequenceNode = {
4
+ kind: "sequence";
5
+ children: BuilderNode[];
6
+ };
7
+ type ParallelNode = {
8
+ kind: "parallel";
9
+ children: BuilderNode[];
10
+ maxConcurrency?: number;
11
+ };
12
+ type LoopNode = {
13
+ kind: "loop";
14
+ id?: string;
15
+ children: BuilderNode;
16
+ until: (outputs: Record<string, unknown>) => boolean;
17
+ maxIterations?: number;
18
+ onMaxReached?: "fail" | "return-last";
19
+ handles?: BuilderStepHandle[];
20
+ };
21
+ type MatchNode = {
22
+ kind: "match";
23
+ source: BuilderStepHandle;
24
+ when: (value: unknown) => boolean;
25
+ then: BuilderNode;
26
+ else?: BuilderNode;
27
+ };
28
+ type BranchNode = {
29
+ kind: "branch";
30
+ condition: (ctx: Record<string, unknown>) => boolean;
31
+ needs?: Record<string, BuilderStepHandle>;
32
+ then: BuilderNode;
33
+ else?: BuilderNode;
34
+ };
35
+ type WorktreeNode = {
36
+ kind: "worktree";
37
+ id?: string;
38
+ path: string;
39
+ branch?: string;
40
+ skipIf?: (ctx: Record<string, unknown>) => boolean;
41
+ needs?: Record<string, BuilderStepHandle>;
42
+ children: BuilderNode;
43
+ };
44
+
45
+ export type BuilderNode =
46
+ | BuilderStepHandle
47
+ | SequenceNode
48
+ | ParallelNode
49
+ | LoopNode
50
+ | MatchNode
51
+ | BranchNode
52
+ | WorktreeNode;
@@ -0,0 +1,47 @@
1
+ import type { Effect, Schema } from "effect";
2
+ import type { SQLiteTable } from "drizzle-orm/sqlite-core";
3
+ import type { CachePolicy } from "@smithers-orchestrator/scheduler/CachePolicy";
4
+ import type { RetryPolicy } from "@smithers-orchestrator/scheduler/RetryPolicy";
5
+
6
+ type AnySchema = Schema.Schema<unknown, unknown, never>;
7
+ type AnyEffect = unknown | Promise<unknown> | Effect.Effect<unknown, unknown, unknown>;
8
+
9
+ type BuilderStepContext = Record<string, unknown> & {
10
+ input: unknown;
11
+ executionId: string;
12
+ stepId: string;
13
+ attempt: number;
14
+ signal: AbortSignal;
15
+ iteration: number;
16
+ heartbeat: (data?: unknown) => void;
17
+ lastHeartbeat: unknown | null;
18
+ };
19
+
20
+ type ApprovalOptions = {
21
+ needs?: Record<string, BuilderStepHandle>;
22
+ request: (ctx: Record<string, unknown>) => {
23
+ title: string;
24
+ summary?: string | null;
25
+ };
26
+ onDeny?: "fail" | "continue" | "skip";
27
+ };
28
+
29
+ export type BuilderStepHandle = {
30
+ kind: "step" | "approval";
31
+ id: string;
32
+ localId: string;
33
+ tableKey: string;
34
+ tableName: string;
35
+ table: SQLiteTable;
36
+ output: AnySchema;
37
+ needs: Record<string, BuilderStepHandle>;
38
+ run?: (ctx: BuilderStepContext) => AnyEffect;
39
+ request?: ApprovalOptions["request"];
40
+ onDeny?: "fail" | "continue" | "skip";
41
+ retries: number;
42
+ retryPolicy?: RetryPolicy;
43
+ timeoutMs: number | null;
44
+ skipIf?: (ctx: BuilderStepContext) => boolean;
45
+ loopId?: string;
46
+ cache?: CachePolicy;
47
+ };
@@ -0,0 +1,3 @@
1
+ export type CancelPayload = {
2
+ runId: string;
3
+ };
@@ -0,0 +1,4 @@
1
+ export type CancelResult = {
2
+ runId: string;
3
+ status: "cancelling" | "cancelled";
4
+ };
@@ -0,0 +1,7 @@
1
+ import type { Exit } from "effect";
2
+ import type { ApprovalDeferredResolution } from "./ApprovalDeferredResolution.ts";
3
+
4
+ export type DeferredResolution = Exit.Exit<
5
+ ApprovalDeferredResolution | void,
6
+ never
7
+ >;
@@ -0,0 +1,7 @@
1
+ import type { FilePatch } from "./FilePatch.ts";
2
+
3
+ export type DiffBundle = {
4
+ seq: number;
5
+ baseRef: string;
6
+ patches: FilePatch[];
7
+ };
@@ -0,0 +1,7 @@
1
+ import type { TaskActivityRetryOptions } from "./TaskActivityRetryOptions.ts";
2
+
3
+ export type ExecuteTaskActivityOptions = {
4
+ initialAttempt?: number;
5
+ retry?: false | TaskActivityRetryOptions;
6
+ includeAttemptInIdempotencyKey?: boolean;
7
+ };
@@ -0,0 +1,6 @@
1
+ export type FilePatch = {
2
+ path: string;
3
+ operation: "add" | "modify" | "delete";
4
+ diff: string;
5
+ binaryContent?: string;
6
+ };
@@ -0,0 +1,3 @@
1
+ export type GetRunPayload = {
2
+ runId: string;
3
+ };
@@ -0,0 +1,3 @@
1
+ import type { RunSummary } from "./RunSummary.ts";
2
+
3
+ export type GetRunResult = RunSummary | null;
@@ -0,0 +1,24 @@
1
+ import type { BunSQLiteDatabase } from "drizzle-orm/bun-sqlite";
2
+ import type { SQLiteTable } from "drizzle-orm/sqlite-core";
3
+ import type { TaskDescriptor } from "@smithers-orchestrator/graph/TaskDescriptor";
4
+ import type { SmithersDb } from "@smithers-orchestrator/db/adapter";
5
+ import type { EventBus } from "../events.js";
6
+ import type { HijackState } from "../HijackState.ts";
7
+ import type { TaskBridgeToolConfig } from "./TaskBridgeToolConfig.ts";
8
+
9
+ export type LegacyExecuteTaskFn = (
10
+ adapter: SmithersDb,
11
+ db: BunSQLiteDatabase<Record<string, unknown>>,
12
+ runId: string,
13
+ desc: TaskDescriptor,
14
+ descriptorMap: Map<string, TaskDescriptor>,
15
+ inputTable: SQLiteTable,
16
+ eventBus: EventBus,
17
+ toolConfig: TaskBridgeToolConfig,
18
+ workflowName: string,
19
+ cacheEnabled: boolean,
20
+ signal?: AbortSignal,
21
+ disabledAgents?: Set<string>,
22
+ runAbortController?: AbortController,
23
+ hijackState?: HijackState,
24
+ ) => Promise<void>;
@@ -0,0 +1,6 @@
1
+ import type { RunStatusSchema } from "./RunStatusSchema.ts";
2
+
3
+ export type ListRunsPayload = {
4
+ limit?: number;
5
+ status?: RunStatusSchema;
6
+ };
@@ -0,0 +1,9 @@
1
+ export type RunStatusSchema =
2
+ | "running"
3
+ | "waiting-approval"
4
+ | "waiting-event"
5
+ | "waiting-timer"
6
+ | "finished"
7
+ | "continued"
8
+ | "failed"
9
+ | "cancelled";
@@ -0,0 +1,23 @@
1
+ import type { RunStatusSchema } from "./RunStatusSchema.ts";
2
+
3
+ export type RunSummary = {
4
+ runId: string;
5
+ parentRunId: string | null;
6
+ workflowName: string;
7
+ workflowPath: string | null;
8
+ workflowHash: string | null;
9
+ status: RunStatusSchema;
10
+ createdAtMs: number;
11
+ startedAtMs: number | null;
12
+ finishedAtMs: number | null;
13
+ heartbeatAtMs: number | null;
14
+ runtimeOwnerId: string | null;
15
+ cancelRequestedAtMs: number | null;
16
+ hijackRequestedAtMs: number | null;
17
+ hijackTarget: string | null;
18
+ vcsType: string | null;
19
+ vcsRoot: string | null;
20
+ vcsRevision: string | null;
21
+ errorJson: string | null;
22
+ configJson: string | null;
23
+ };
@@ -0,0 +1,7 @@
1
+ export type SignalPayload = {
2
+ runId: string;
3
+ signalName: string;
4
+ data?: unknown;
5
+ correlationId?: string;
6
+ sentBy?: string;
7
+ };
@@ -0,0 +1,6 @@
1
+ export type SignalResult = {
2
+ runId: string;
3
+ signalName: string;
4
+ delivered: boolean;
5
+ status: "signalled" | "ignored";
6
+ };
@@ -0,0 +1,3 @@
1
+ export type SmithersSqliteOptions = {
2
+ filename: string;
3
+ };
@@ -0,0 +1,7 @@
1
+ export type SqlMessageStorageEventHistoryQuery = {
2
+ afterSeq?: number;
3
+ limit?: number;
4
+ nodeId?: string;
5
+ types?: readonly string[];
6
+ sinceTimestampMs?: number;
7
+ };
@@ -0,0 +1,46 @@
1
+ export type TaggedWorkerError =
2
+ | {
3
+ _tag: "TaskAborted";
4
+ message: string;
5
+ details?: Record<string, unknown>;
6
+ name?: string;
7
+ }
8
+ | {
9
+ _tag: "TaskTimeout";
10
+ message: string;
11
+ nodeId: string;
12
+ attempt: number;
13
+ timeoutMs: number;
14
+ }
15
+ | {
16
+ _tag: "TaskHeartbeatTimeout";
17
+ message: string;
18
+ nodeId: string;
19
+ iteration: number;
20
+ attempt: number;
21
+ timeoutMs: number;
22
+ staleForMs: number;
23
+ lastHeartbeatAtMs: number;
24
+ }
25
+ | { _tag: "RunNotFound"; message: string; runId: string }
26
+ | {
27
+ _tag: "InvalidInput";
28
+ message: string;
29
+ details?: Record<string, unknown>;
30
+ }
31
+ | {
32
+ _tag: "DbWriteFailed";
33
+ message: string;
34
+ details?: Record<string, unknown>;
35
+ }
36
+ | {
37
+ _tag: "AgentCliError";
38
+ message: string;
39
+ details?: Record<string, unknown>;
40
+ }
41
+ | {
42
+ _tag: "WorkflowFailed";
43
+ message: string;
44
+ details?: Record<string, unknown>;
45
+ status?: number;
46
+ };
@@ -0,0 +1,4 @@
1
+ export type TaskActivityContext = {
2
+ attempt: number;
3
+ idempotencyKey: string;
4
+ };
@@ -0,0 +1,4 @@
1
+ export type TaskActivityRetryOptions = {
2
+ times: number;
3
+ while?: (error: unknown) => boolean;
4
+ };
@@ -0,0 +1,6 @@
1
+ export type TaskBridgeToolConfig = {
2
+ rootDir: string;
3
+ allowNetwork: boolean;
4
+ maxOutputBytes: number;
5
+ toolTimeoutMs: number;
6
+ };
@@ -0,0 +1,3 @@
1
+ import type { TaskResult } from "./TaskResult.ts";
2
+
3
+ export type TaskFailure = Extract<TaskResult, { _tag: "Failure" }>;
@@ -0,0 +1,5 @@
1
+ import type { WorkerTaskError } from "./WorkerTaskError.ts";
2
+
3
+ export type TaskResult =
4
+ | { _tag: "Success"; executionId: string; terminal: boolean }
5
+ | { _tag: "Failure"; executionId: string; error: WorkerTaskError };
@@ -0,0 +1,5 @@
1
+ export type UnknownWorkerError = {
2
+ _tag: "UnknownWorkerError";
3
+ errorId: string;
4
+ message: string;
5
+ };
@@ -0,0 +1,11 @@
1
+ import type { Schema } from "effect";
2
+
3
+ export type WaitForEventDurableDeferredResolution = Schema.Schema.Type<
4
+ Schema.Struct<{
5
+ signalName: typeof Schema.String;
6
+ correlationId: Schema.NullOr<typeof Schema.String>;
7
+ payloadJson: typeof Schema.String;
8
+ seq: typeof Schema.Number;
9
+ receivedAtMs: typeof Schema.Number;
10
+ }>
11
+ >;
@@ -0,0 +1 @@
1
+ export type WorkerDispatchKind = "compute" | "static" | "legacy";
@@ -0,0 +1,14 @@
1
+ import type { WorkerTaskKind } from "./WorkerTaskKind.ts";
2
+ import type { WorkerDispatchKind } from "./WorkerDispatchKind.ts";
3
+
4
+ export type WorkerTask = {
5
+ executionId: string;
6
+ bridgeKey: string;
7
+ workflowName: string;
8
+ runId: string;
9
+ nodeId: string;
10
+ iteration: number;
11
+ retries: number;
12
+ taskKind: WorkerTaskKind;
13
+ dispatchKind: WorkerDispatchKind;
14
+ };
@@ -0,0 +1,4 @@
1
+ import type { TaggedWorkerError } from "./TaggedWorkerError.ts";
2
+ import type { UnknownWorkerError } from "./UnknownWorkerError.ts";
3
+
4
+ export type WorkerTaskError = TaggedWorkerError | UnknownWorkerError;
@@ -0,0 +1 @@
1
+ export type WorkerTaskKind = "agent" | "compute" | "static";
@@ -0,0 +1,4 @@
1
+ export type WorkflowPatchDecisionRecord = {
2
+ patchId: string;
3
+ decision: boolean;
4
+ };
@@ -0,0 +1 @@
1
+ export type WorkflowPatchDecisions = Record<string, boolean>;
@@ -0,0 +1,7 @@
1
+ import type { WorkflowPatchDecisions } from "./WorkflowPatchDecisions.ts";
2
+
3
+ export type WorkflowVersioningRuntime = {
4
+ resolve(patchId: string): boolean;
5
+ flush(): Promise<void>;
6
+ snapshot(): WorkflowPatchDecisions;
7
+ };
@@ -0,0 +1,131 @@
1
+ // @smithers-type-exports-begin
2
+ /** @typedef {import("./TaskActivityRetryOptions.ts").TaskActivityRetryOptions} TaskActivityRetryOptions */
3
+ /** @typedef {import("./ExecuteTaskActivityOptions.ts").ExecuteTaskActivityOptions} ExecuteTaskActivityOptions */
4
+ /** @typedef {import("./TaskActivityContext.ts").TaskActivityContext} TaskActivityContext */
5
+ // @smithers-type-exports-end
6
+
7
+ import * as Activity from "@effect/workflow/Activity";
8
+ import { Effect, Schema } from "effect";
9
+ /** @typedef {import("@smithers-orchestrator/db/adapter").SmithersDb} _SmithersDb */
10
+ /** @typedef {import("@smithers-orchestrator/graph/TaskDescriptor").TaskDescriptor} _TaskDescriptor */
11
+
12
+ const adapterNamespaces = new WeakMap();
13
+ const completedActivityResults = new Map();
14
+ let nextAdapterNamespace = 0;
15
+ /**
16
+ * @param {_SmithersDb} adapter
17
+ * @returns {string}
18
+ */
19
+ const getAdapterNamespace = (adapter) => {
20
+ const existing = adapterNamespaces.get(adapter);
21
+ if (existing) {
22
+ return existing;
23
+ }
24
+ const created = `adapter-${++nextAdapterNamespace}`;
25
+ adapterNamespaces.set(adapter, created);
26
+ return created;
27
+ };
28
+ export class RetriableTaskFailure extends Error {
29
+ nodeId;
30
+ attempt;
31
+ /**
32
+ * @param {string} nodeId
33
+ * @param {number} attempt
34
+ */
35
+ constructor(nodeId, attempt) {
36
+ super(`Task ${nodeId} failed on attempt ${attempt} and should be retried`);
37
+ this.name = "RetriableTaskFailure";
38
+ this.nodeId = nodeId;
39
+ this.attempt = attempt;
40
+ }
41
+ }
42
+ /**
43
+ * @param {unknown} error
44
+ * @returns {error is RetriableTaskFailure}
45
+ */
46
+ const isRetriableTaskFailure = (error) => error instanceof RetriableTaskFailure;
47
+ /**
48
+ * @param {_SmithersDb} adapter
49
+ * @param {string} workflowName
50
+ * @param {string} runId
51
+ * @param {_TaskDescriptor} desc
52
+ * @returns {string}
53
+ */
54
+ export const makeTaskBridgeKey = (adapter, workflowName, runId, desc) => [
55
+ "smithers-task-bridge",
56
+ getAdapterNamespace(adapter),
57
+ workflowName,
58
+ runId,
59
+ desc.nodeId,
60
+ String(desc.iteration),
61
+ ].join(":");
62
+ /**
63
+ * @param {_SmithersDb} adapter
64
+ * @param {string} workflowName
65
+ * @param {string} runId
66
+ * @param {_TaskDescriptor} desc
67
+ * @param {number} attempt
68
+ * @param {boolean} [includeAttempt]
69
+ * @returns {string}
70
+ */
71
+ const makeActivityIdempotencyKey = (adapter, workflowName, runId, desc, attempt, includeAttempt) => {
72
+ const base = makeTaskBridgeKey(adapter, workflowName, runId, desc);
73
+ return includeAttempt ? `${base}:attempt:${attempt}` : base;
74
+ };
75
+ /**
76
+ * @template A
77
+ * @param {_TaskDescriptor} desc
78
+ * @param {(context: TaskActivityContext) => Promise<A> | A} executeFn
79
+ * @param {Pick<ExecuteTaskActivityOptions, "includeAttemptInIdempotencyKey">} [options]
80
+ */
81
+ export const makeTaskActivity = (desc, executeFn, options) => Activity.make({
82
+ name: desc.nodeId,
83
+ success: Schema.Unknown,
84
+ error: Schema.Unknown,
85
+ execute: Effect.gen(function* () {
86
+ const attempt = yield* Activity.CurrentAttempt;
87
+ const idempotencyKey = yield* Activity.idempotencyKey(desc.nodeId, {
88
+ includeAttempt: options?.includeAttemptInIdempotencyKey,
89
+ });
90
+ return yield* Effect.tryPromise({
91
+ try: () => Promise.resolve(executeFn({ attempt, idempotencyKey })),
92
+ catch: (error) => error,
93
+ });
94
+ }),
95
+ });
96
+ /**
97
+ * @template A
98
+ * @param {_SmithersDb} adapter
99
+ * @param {string} workflowName
100
+ * @param {string} runId
101
+ * @param {_TaskDescriptor} desc
102
+ * @param {(context: TaskActivityContext) => Promise<A> | A} executeFn
103
+ * @param {ExecuteTaskActivityOptions} [options]
104
+ * @returns {Promise<A>}
105
+ */
106
+ export const executeTaskActivity = async (adapter, workflowName, runId, desc, executeFn, options) => {
107
+ const initialAttempt = Math.max(1, options?.initialAttempt ?? 1);
108
+ const retry = options?.retry === undefined
109
+ ? { times: desc.retries, while: isRetriableTaskFailure }
110
+ : options.retry;
111
+ let attempt = initialAttempt;
112
+ while (true) {
113
+ const idempotencyKey = makeActivityIdempotencyKey(adapter, workflowName, runId, desc, attempt, options?.includeAttemptInIdempotencyKey);
114
+ if (completedActivityResults.has(idempotencyKey)) {
115
+ return completedActivityResults.get(idempotencyKey);
116
+ }
117
+ try {
118
+ const result = await Promise.resolve(executeFn({ attempt, idempotencyKey }));
119
+ completedActivityResults.set(idempotencyKey, result);
120
+ return result;
121
+ }
122
+ catch (error) {
123
+ if (retry === false ||
124
+ attempt - initialAttempt >= retry.times ||
125
+ !(retry.while ?? isRetriableTaskFailure)(error)) {
126
+ throw error;
127
+ }
128
+ attempt += 1;
129
+ }
130
+ }
131
+ };
@@ -0,0 +1,45 @@
1
+ import { TaskAborted } from "@smithers-orchestrator/errors/TaskAborted";
2
+ /**
3
+ * @returns {TaskAborted}
4
+ */
5
+ export function makeAbortError(message = "Task aborted") {
6
+ return new TaskAborted({
7
+ message,
8
+ name: "AbortError",
9
+ });
10
+ }
11
+ /**
12
+ * @param {AbortController} controller
13
+ * @param {AbortSignal} [signal]
14
+ */
15
+ export function wireAbortSignal(controller, signal) {
16
+ if (!signal) {
17
+ return () => { };
18
+ }
19
+ const forwardAbort = () => {
20
+ controller.abort(signal.reason ?? makeAbortError());
21
+ };
22
+ if (signal.aborted) {
23
+ forwardAbort();
24
+ return () => { };
25
+ }
26
+ signal.addEventListener("abort", forwardAbort, { once: true });
27
+ return () => signal.removeEventListener("abort", forwardAbort);
28
+ }
29
+ /**
30
+ * @param {string | null} [metaJson]
31
+ * @returns {Record<string, unknown>}
32
+ */
33
+ export function parseAttemptMetaJson(metaJson) {
34
+ if (!metaJson)
35
+ return {};
36
+ try {
37
+ const parsed = JSON.parse(metaJson);
38
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
39
+ ? parsed
40
+ : {};
41
+ }
42
+ catch {
43
+ return {};
44
+ }
45
+ }