@lostgradient/weft 0.2.0 → 0.3.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 (207) hide show
  1. package/README.md +47 -22
  2. package/dist/cli/generated/operation-client.generated.d.ts +28 -1
  3. package/dist/cli/generated/operation-client.generated.js +2 -0
  4. package/dist/cli-main.js +79 -79
  5. package/dist/client/handle-delegation.d.ts +4 -0
  6. package/dist/client/handle-delegation.js +6 -0
  7. package/dist/client/http-client-requests.d.ts +2 -0
  8. package/dist/client/http-client-requests.js +3 -0
  9. package/dist/client/http-client.d.ts +4 -1
  10. package/dist/client/http-client.js +9 -1
  11. package/dist/client/interface.d.ts +57 -2
  12. package/dist/client/local.d.ts +4 -1
  13. package/dist/client/local.js +7 -0
  14. package/dist/client/start-body.d.ts +7 -1
  15. package/dist/client/start-body.js +13 -4
  16. package/dist/core/codec/extension-codec.js +4 -2
  17. package/dist/core/codec/index.d.ts +1 -0
  18. package/dist/core/codec/index.js +1 -0
  19. package/dist/core/codec/serializer-registry.d.ts +122 -0
  20. package/dist/core/codec/serializer-registry.js +51 -0
  21. package/dist/core/context/index.d.ts +10 -0
  22. package/dist/core/context/index.js +3 -0
  23. package/dist/core/context/internals.d.ts +10 -0
  24. package/dist/core/context/internals.js +5 -1
  25. package/dist/core/context/run-operation.d.ts +16 -3
  26. package/dist/core/context/run-operation.js +16 -7
  27. package/dist/core/context/speculative-child.js +2 -0
  28. package/dist/core/context/types.d.ts +6 -0
  29. package/dist/core/engine/bulk-operations-purge.js +1 -0
  30. package/dist/core/engine/bulk-operations.js +1 -1
  31. package/dist/core/engine/callback-creators-bundles.js +2 -1
  32. package/dist/core/engine/callback-creators-core.js +2 -1
  33. package/dist/core/engine/construction.d.ts +1 -1
  34. package/dist/core/engine/construction.js +15 -3
  35. package/dist/core/engine/disposal.js +12 -0
  36. package/dist/core/engine/engine-create-types.d.ts +0 -14
  37. package/dist/core/engine/engine-internal-types.d.ts +17 -0
  38. package/dist/core/engine/engine-leak-warnings.d.ts +6 -0
  39. package/dist/core/engine/engine-leak-warnings.js +4 -0
  40. package/dist/core/engine/engine-runtime-helpers.d.ts +17 -0
  41. package/dist/core/engine/engine-runtime-helpers.js +26 -5
  42. package/dist/core/engine/errors.d.ts +74 -0
  43. package/dist/core/engine/errors.js +25 -1
  44. package/dist/core/engine/handle-result.js +1 -1
  45. package/dist/core/engine/handles.d.ts +89 -40
  46. package/dist/core/engine/handles.js +25 -27
  47. package/dist/core/engine/index.d.ts +122 -4
  48. package/dist/core/engine/index.js +82 -5
  49. package/dist/core/engine/inline-launch-queue.d.ts +14 -0
  50. package/dist/core/engine/inline-launch-queue.js +32 -7
  51. package/dist/core/engine/internals.d.ts +26 -10
  52. package/dist/core/engine/lifecycle/fork-helpers.js +1 -7
  53. package/dist/core/engine/lifecycle/persist.js +5 -20
  54. package/dist/core/engine/lifecycle/recovered-services.d.ts +45 -0
  55. package/dist/core/engine/lifecycle/recovered-services.js +34 -0
  56. package/dist/core/engine/lifecycle/resume.js +33 -5
  57. package/dist/core/engine/lifecycle/shared.d.ts +8 -0
  58. package/dist/core/engine/lifecycle/start-batch.js +23 -12
  59. package/dist/core/engine/lifecycle/start-commit.d.ts +47 -0
  60. package/dist/core/engine/lifecycle/start-commit.js +27 -0
  61. package/dist/core/engine/lifecycle/start-exec.d.ts +30 -2
  62. package/dist/core/engine/lifecycle/start-exec.js +38 -0
  63. package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +79 -0
  64. package/dist/core/engine/lifecycle/start-or-signal-resolution.js +60 -0
  65. package/dist/core/engine/lifecycle/start-or-signal.d.ts +45 -0
  66. package/dist/core/engine/lifecycle/start-or-signal.js +141 -0
  67. package/dist/core/engine/lifecycle/start.d.ts +3 -3
  68. package/dist/core/engine/lifecycle/start.js +42 -37
  69. package/dist/core/engine/lifecycle.d.ts +3 -2
  70. package/dist/core/engine/lifecycle.js +9 -2
  71. package/dist/core/engine/listing.js +1 -1
  72. package/dist/core/engine/operations-data.d.ts +16 -0
  73. package/dist/core/engine/operations-data.js +6 -0
  74. package/dist/core/engine/operations-time.d.ts +3 -2
  75. package/dist/core/engine/operations-time.js +6 -1
  76. package/dist/core/engine/persisted-data-version.d.ts +5 -9
  77. package/dist/core/engine/persisted-data-version.js +4 -5
  78. package/dist/core/engine/schedule-handle.d.ts +45 -0
  79. package/dist/core/engine/schedule-handle.js +26 -0
  80. package/dist/core/engine/schedules.d.ts +1 -1
  81. package/dist/core/engine/schedules.js +7 -3
  82. package/dist/core/engine/second-instance-detector.d.ts +96 -0
  83. package/dist/core/engine/second-instance-detector.js +108 -0
  84. package/dist/core/engine/signals.d.ts +22 -0
  85. package/dist/core/engine/signals.js +15 -0
  86. package/dist/core/engine/termination/cleanup.d.ts +25 -0
  87. package/dist/core/engine/termination/cleanup.js +21 -1
  88. package/dist/core/engine/termination/complete.js +4 -3
  89. package/dist/core/engine/termination/suspend.d.ts +68 -0
  90. package/dist/core/engine/termination/suspend.js +41 -0
  91. package/dist/core/engine/termination.d.ts +4 -2
  92. package/dist/core/engine/termination.js +2 -0
  93. package/dist/core/engine/validation.js +25 -1
  94. package/dist/core/engine/workflow-feed.d.ts +5 -3
  95. package/dist/core/events/event-map.d.ts +2 -1
  96. package/dist/core/events/workflow-events.d.ts +23 -0
  97. package/dist/core/events/workflow-events.js +9 -0
  98. package/dist/core/inline-execution-strategy.d.ts +5 -0
  99. package/dist/core/inline-execution-strategy.js +2 -1
  100. package/dist/core/list-filter-validation.js +2 -1
  101. package/dist/core/start-workflow-validation.d.ts +22 -0
  102. package/dist/core/start-workflow-validation.js +11 -1
  103. package/dist/core/step-context.d.ts +10 -6
  104. package/dist/core/step-context.js +7 -15
  105. package/dist/core/types/activity.d.ts +6 -3
  106. package/dist/core/types/identity.d.ts +8 -1
  107. package/dist/core/types/launch-metadata.d.ts +33 -0
  108. package/dist/core/types/launch-metadata.js +0 -0
  109. package/dist/core/types/message-handles.d.ts +25 -0
  110. package/dist/core/types/options.d.ts +90 -7
  111. package/dist/core/types/reviews.d.ts +2 -1
  112. package/dist/core/types/services-resolution.d.ts +47 -0
  113. package/dist/core/types/services-resolution.js +0 -0
  114. package/dist/core/types/state.d.ts +11 -11
  115. package/dist/core/types/workflow-builder.d.ts +5 -4
  116. package/dist/core/types/workflow-context.d.ts +25 -0
  117. package/dist/core/types/workflow-function.d.ts +17 -0
  118. package/dist/core/types/workflow-snapshot.d.ts +29 -0
  119. package/dist/core/types/workflow-snapshot.js +0 -0
  120. package/dist/core/types.d.ts +3 -0
  121. package/dist/core/types.js +3 -0
  122. package/dist/core/weft-error.d.ts +46 -14
  123. package/dist/core/weft-error.js +12 -1
  124. package/dist/diagnostics/doctor.js +6 -3
  125. package/dist/diagnostics/format.js +2 -2
  126. package/dist/diagnostics/types.d.ts +1 -0
  127. package/dist/diagnostics/version-check.js +6 -4
  128. package/dist/index.d.ts +10 -5
  129. package/dist/index.js +11 -2
  130. package/dist/json-schema.js +3 -3
  131. package/dist/mcp/cli.js +35 -35
  132. package/dist/mcp/list-filter.js +2 -1
  133. package/dist/mcp/session.js +1 -0
  134. package/dist/observability/index.js +2 -2
  135. package/dist/server/handler.js +30 -30
  136. package/dist/server/index.js +33 -33
  137. package/dist/server/interactive-operations.js +1 -0
  138. package/dist/server/operations/resume-workflow.js +2 -2
  139. package/dist/server/operations/start-or-signal-workflow.d.ts +39 -0
  140. package/dist/server/operations/start-or-signal-workflow.js +140 -0
  141. package/dist/server/operations/start-workflow-options.d.ts +32 -0
  142. package/dist/server/operations/start-workflow-options.js +63 -0
  143. package/dist/server/operations/start-workflow.js +7 -69
  144. package/dist/server/operations/suspend-workflow.d.ts +13 -0
  145. package/dist/server/operations/suspend-workflow.js +36 -0
  146. package/dist/server/rest-binding.d.ts +18 -7
  147. package/dist/server/rest-bindings.js +12 -0
  148. package/dist/server/runtime/task-dispatch.js +5 -3
  149. package/dist/server/runtime/task-polling.d.ts +16 -2
  150. package/dist/server/runtime/task-polling.js +20 -5
  151. package/dist/server/runtime/websocket-worker.js +8 -0
  152. package/dist/server/serve-internals.d.ts +8 -0
  153. package/dist/server/serve-internals.js +4 -2
  154. package/dist/server/task-state.d.ts +8 -0
  155. package/dist/service-worker/index.js +28 -28
  156. package/dist/storage/capabilities.d.ts +10 -2
  157. package/dist/storage/capabilities.js +2 -2
  158. package/dist/storage/http.js +2 -2
  159. package/dist/storage/index.d.ts +7 -1
  160. package/dist/storage/indexeddb.js +1 -1
  161. package/dist/storage/interface.d.ts +40 -0
  162. package/dist/storage/interface.js +1 -1
  163. package/dist/storage/key-prefixes.d.ts +1 -1
  164. package/dist/storage/key-prefixes.js +3 -0
  165. package/dist/storage/lmdb.js +1 -1
  166. package/dist/storage/memory.js +1 -1
  167. package/dist/storage/neon-value-mapping.d.ts +47 -0
  168. package/dist/storage/neon-value-mapping.js +11 -0
  169. package/dist/storage/neon.d.ts +108 -0
  170. package/dist/storage/neon.js +10 -0
  171. package/dist/storage/node-sqlite-loader.d.ts +71 -0
  172. package/dist/storage/node-sqlite-loader.js +41 -0
  173. package/dist/storage/node-sqlite.d.ts +1 -19
  174. package/dist/storage/node-sqlite.js +38 -32
  175. package/dist/storage/postgres-key-value-queries.d.ts +79 -0
  176. package/dist/storage/postgres-key-value-queries.js +63 -0
  177. package/dist/storage/resolve.d.ts +2 -165
  178. package/dist/storage/resolve.js +1 -1
  179. package/dist/storage/scoped-storage.js +1 -1
  180. package/dist/storage/storage-configuration.d.ts +209 -0
  181. package/dist/storage/storage-configuration.js +0 -0
  182. package/dist/storage/text-value-store.d.ts +13 -10
  183. package/dist/storage/turso.js +2 -2
  184. package/dist/storage/typed-storage.js +1 -1
  185. package/dist/storage/web-extension.js +1 -1
  186. package/dist/testing/event-loop.d.ts +36 -2
  187. package/dist/testing/index.d.ts +31 -1
  188. package/dist/testing/index.js +33 -33
  189. package/dist/version.d.ts +1 -1
  190. package/dist/version.js +1 -1
  191. package/dist/worker/index.js +9 -5
  192. package/dist/worker/long-poll.js +4 -0
  193. package/dist/worker/protocol-messages.d.ts +20 -0
  194. package/dist/worker/protocol-schemas.d.ts +32 -0
  195. package/dist/worker/protocol-schemas.js +8 -4
  196. package/dist/worker/protocol-task-result.d.ts +28 -0
  197. package/dist/worker/protocol-task-result.js +76 -0
  198. package/dist/worker/protocol.d.ts +4 -15
  199. package/dist/worker/protocol.js +1 -1
  200. package/dist/worker/registry/fair-share.d.ts +29 -0
  201. package/dist/worker/registry/fair-share.js +30 -0
  202. package/dist/worker/registry/routing.d.ts +18 -0
  203. package/dist/worker/registry/routing.js +14 -0
  204. package/dist/worker/registry/types.d.ts +7 -0
  205. package/dist/worker/registry.d.ts +16 -1
  206. package/dist/worker/registry.js +24 -36
  207. package/package.json +17 -4
@@ -9,6 +9,20 @@ export declare function queueInlineWorkflowExecutionStart(internals: EngineInter
9
9
  export declare function flushQueuedInlineWorkflowStarts(internals: EngineInternals, callbacks: InlineLaunchQueueCallbacks): Promise<void>;
10
10
  /** Used by scheduler-driven direct backfill flushes. Clears the scheduled flag first. */
11
11
  export declare function flushQueuedInlineWorkflowStartsDirectly(internals: EngineInternals, callbacks: InlineLaunchQueueCallbacks): Promise<void>;
12
+ /**
13
+ * Drain every pending inline launch before engine teardown. Called from
14
+ * `[Symbol.asyncDispose]` *before* `disposeEngine` aborts the signal, so the
15
+ * flush actually executes the queued starts (the abort check in
16
+ * {@link flushQueuedInlineWorkflowStarts} would otherwise discard them). This
17
+ * turns a deferred-launch macrotask into work that completes before
18
+ * `asyncDispose` returns, so a disposed engine leaves no dangling pending
19
+ * launch — the fix for the test-runner macrotask-starvation footgun.
20
+ *
21
+ * A queued start that was already aborted (signal set before this is reached)
22
+ * is left for the synchronous `disposeQueuedInlineWorkflowStarts` path, which
23
+ * discards it and settles its `defer: false` awaiter.
24
+ */
25
+ export declare function drainQueuedInlineWorkflowStarts(internals: EngineInternals, callbacks: InlineLaunchQueueCallbacks): Promise<void>;
12
26
  export declare function dropQueuedInlineWorkflowStart(internals: EngineInternals, workflowId: string): boolean;
13
27
  export declare function disposeQueuedInlineWorkflowStarts(internals: EngineInternals): void;
14
28
  export declare function hasQueuedInlineWorkflowStart(internals: EngineInternals, workflowId: string): boolean;
@@ -17,9 +17,18 @@ export function queueInlineWorkflowExecutionStart(internals, start, callbacks) {
17
17
  callbacks.swallowPromiseRejection(flushQueuedInlineWorkflowStarts(internals, callbacks));
18
18
  }, 0);
19
19
  }
20
+ function settleDiscardedInlineStarts(internals, discarded) {
21
+ for (const start of discarded) {
22
+ internals.queuedInlineWorkflowStartIds.delete(start.workflowId);
23
+ internals.queuedOrLaunchingInlineWorkflowStartIds.delete(start.workflowId);
24
+ start.onStarted?.();
25
+ }
26
+ }
20
27
  export async function flushQueuedInlineWorkflowStarts(internals, callbacks) {
21
28
  if (internals.abortController.signal.aborted) {
29
+ const discarded = internals.queuedInlineWorkflowStarts;
22
30
  internals.queuedInlineWorkflowStarts = [];
31
+ settleDiscardedInlineStarts(internals, discarded);
23
32
  return;
24
33
  }
25
34
  if (internals.queuedInlineWorkflowStarts.length === 0)
@@ -27,12 +36,21 @@ export async function flushQueuedInlineWorkflowStarts(internals, callbacks) {
27
36
  const pendingStarts = internals.queuedInlineWorkflowStarts;
28
37
  internals.queuedInlineWorkflowStarts = [];
29
38
  for (const start of pendingStarts)
30
- await startQueuedInlineWorkflowExecution(internals, start, callbacks);
39
+ await callbacks.swallowPromiseRejection(startQueuedInlineWorkflowExecution(internals, start, callbacks));
31
40
  }
32
41
  export async function flushQueuedInlineWorkflowStartsDirectly(internals, callbacks) {
33
42
  internals.queuedInlineWorkflowStartFlushScheduled = !1;
34
43
  await flushQueuedInlineWorkflowStarts(internals, callbacks);
35
44
  }
45
+ export async function drainQueuedInlineWorkflowStarts(internals, callbacks) {
46
+ internals.queuedInlineWorkflowStartFlushScheduled = !1;
47
+ let passes = 0;
48
+ const maxPasses = 1000;
49
+ while (internals.queuedInlineWorkflowStarts.length > 0 && !internals.abortController.signal.aborted && passes < maxPasses) {
50
+ passes += 1;
51
+ await callbacks.swallowPromiseRejection(flushQueuedInlineWorkflowStarts(internals, callbacks));
52
+ }
53
+ }
36
54
  async function startQueuedInlineWorkflowExecution(internals, start, callbacks) {
37
55
  try {
38
56
  const state = await loadWorkflowState(internals, start.workflowId);
@@ -45,22 +63,29 @@ async function startQueuedInlineWorkflowExecution(internals, start, callbacks) {
45
63
  } finally {
46
64
  internals.queuedInlineWorkflowStartIds.delete(start.workflowId);
47
65
  internals.queuedOrLaunchingInlineWorkflowStartIds.delete(start.workflowId);
66
+ start.onStarted?.();
48
67
  }
49
68
  }
50
69
  export function dropQueuedInlineWorkflowStart(internals, workflowId) {
51
70
  if (internals.queuedInlineWorkflowStarts.length === 0)
52
71
  return !1;
53
- const initialLength = internals.queuedInlineWorkflowStarts.length;
54
- internals.queuedInlineWorkflowStarts = internals.queuedInlineWorkflowStarts.filter((start) => start.workflowId !== workflowId);
55
- if (internals.queuedInlineWorkflowStarts.length !== initialLength) {
56
- internals.queuedInlineWorkflowStartIds.delete(workflowId);
57
- internals.queuedOrLaunchingInlineWorkflowStartIds.delete(workflowId);
58
- }
72
+ const initialLength = internals.queuedInlineWorkflowStarts.length, dropped = [];
73
+ internals.queuedInlineWorkflowStarts = internals.queuedInlineWorkflowStarts.filter((start) => {
74
+ if (start.workflowId === workflowId) {
75
+ dropped.push(start);
76
+ return !1;
77
+ }
78
+ return !0;
79
+ });
80
+ if (internals.queuedInlineWorkflowStarts.length !== initialLength)
81
+ settleDiscardedInlineStarts(internals, dropped);
59
82
  return internals.queuedInlineWorkflowStarts.length !== initialLength;
60
83
  }
61
84
  export function disposeQueuedInlineWorkflowStarts(internals) {
62
85
  internals.queuedInlineWorkflowStartFlushScheduled = !1;
86
+ const discarded = internals.queuedInlineWorkflowStarts;
63
87
  internals.queuedInlineWorkflowStarts = [];
88
+ settleDiscardedInlineStarts(internals, discarded);
64
89
  internals.queuedInlineWorkflowStartIds.clear();
65
90
  internals.queuedOrLaunchingInlineWorkflowStartIds.clear();
66
91
  const channel = internals.queuedInlineWorkflowStartChannel;
@@ -32,8 +32,11 @@ import type { Checkpoint } from '../types.ts';
32
32
  import type { UpdateCoordinator } from '../updates.ts';
33
33
  import type { WorkflowVersionTuple } from '../workflow-version-tuple.ts';
34
34
  import type { PendingTimelineEntry, QueuedInlineWorkflowExecutionStart, RegistrationEntry, ResolvedOptions, TrackedWaiterKeys, WorkflowResultWaiter } from './engine-internal-types.ts';
35
- import type { ScheduleHandleEngine, WorkflowHandle, WorkflowHandleEngine } from './handles.ts';
35
+ import type { EngineCleanupIntervalDisposalTracker } from './engine-leak-warnings.ts';
36
+ import type { WorkflowHandle, WorkflowHandleEngine } from './handles.ts';
36
37
  import type { WorkflowFeedListener } from './index.ts';
38
+ import type { ScheduleHandleEngine } from './schedule-handle.ts';
39
+ import type { SecondInstanceDetector } from './second-instance-detector.ts';
37
40
  type EngineRuntime = WorkflowHandleEngine & ScheduleHandleEngine;
38
41
  export interface EngineInternals {
39
42
  engine: EngineRuntime;
@@ -75,11 +78,15 @@ export interface EngineInternals {
75
78
  activityRegistry: ActivityRegistry;
76
79
  /**
77
80
  * Per-workflow activity registries built from
78
- * `workflow({ name }).activities({ ... }).execute(...)`. Indexed by workflow
79
- * type. Phase 3 wires lookups via `activityRegistriesByWorkflow.get(type)`
80
- * first, falling back to {@link EngineInternals.activityRegistry} so the
81
- * legacy global path keeps working until Phase 6 removes it. Each registry
82
- * is constructed from a defensive deep clone+freeze of the workflow's
81
+ * `workflow({ name }).activities({ ... }).execute(...)`, indexed by workflow
82
+ * type. Activity lookup is per-activity: it consults the workflow's
83
+ * `activityRegistriesByWorkflow.get(type)` registry first and, for any activity
84
+ * that registry does not contain, falls back to the engine-wide
85
+ * {@link EngineInternals.activityRegistry}. Both sources are first-class a
86
+ * workflow can resolve some activities from its own `activities(...)` map and
87
+ * others (shared/globally-registered) from the global registry, and a workflow
88
+ * with no per-workflow map resolves entirely from the global one. Each
89
+ * per-workflow registry is a defensive deep clone+freeze of the workflow's
83
90
  * `activities` map so post-registration mutation cannot reach the engine.
84
91
  */
85
92
  activityRegistriesByWorkflow: Map<string, ActivityRegistry>;
@@ -108,6 +115,14 @@ export interface EngineInternals {
108
115
  workflowHeaders: Map<string, Map<string, string>>;
109
116
  workflowStateWriteChains: Map<string, Promise<void>>;
110
117
  heartbeatDetails: Map<string, unknown>;
118
+ /**
119
+ * Per-run, non-serialized `services` value exposed to the workflow body as
120
+ * `ctx.services`. Set at `engine.start({ services })` and re-provided on
121
+ * recovery by `resolveWorkflowServices`. Never checkpointed — held only in
122
+ * engine memory, keyed by workflowId, and cleared on terminal cleanup (the
123
+ * same lifecycle as {@link heartbeatDetails}).
124
+ */
125
+ workflowServices: Map<string, unknown>;
111
126
  /**
112
127
  * Activities that called `ctx.completeAsync()` and are awaiting out-of-band
113
128
  * completion via `engine.completeAsyncActivity` / `failAsyncActivity`, keyed
@@ -119,13 +134,14 @@ export interface EngineInternals {
119
134
  pendingScheduleCreations: Set<string>;
120
135
  workflowsNeedingTerminalCleanup: Set<string>;
121
136
  cleanupInterval: ReturnType<typeof setInterval> | null;
122
- cleanupIntervalDisposalTracker: {
123
- disposed: boolean;
124
- cleanupInterval: ReturnType<typeof setInterval> | null;
125
- } | null;
137
+ cleanupIntervalDisposalTracker: EngineCleanupIntervalDisposalTracker | null;
126
138
  retentionSweepInterval: ReturnType<typeof setInterval> | null;
127
139
  retentionSweepInFlight: Promise<void> | null;
128
140
  nextRetentionSweepAt: number | null;
141
+ /** Interval driving the best-effort second-instance detector; `null` when off. */
142
+ secondInstanceDetectionInterval: ReturnType<typeof setInterval> | null;
143
+ /** The active second-instance detector; `null` when detection is disabled. */
144
+ secondInstanceDetector: SecondInstanceDetector | null;
129
145
  reviewCoordinator: ReviewCoordinator;
130
146
  reviewWaiters: Map<string, (decision: HumanReviewResult) => void>;
131
147
  reviewWaitersByWorkflow: Map<string, TrackedWaiterKeys>;
@@ -22,17 +22,11 @@ export function createForkedWorkflowState(_internals, workflowId, sourceState, v
22
22
  type: sourceState.type,
23
23
  status: "running",
24
24
  input: sourceState.input,
25
- version: versionTuple.workflowVersion,
25
+ versionTuple,
26
26
  executionStateOwnerId: workflowId,
27
27
  createdAt: forkedAt,
28
28
  startedAt: forkedAt,
29
29
  updatedAt: forkedAt,
30
- ...versionTuple.agentVersion !== void 0 && {
31
- agentVersion: versionTuple.agentVersion
32
- },
33
- ...versionTuple.toolVersions !== void 0 && {
34
- toolVersions: versionTuple.toolVersions
35
- },
36
30
  forkedFrom: lineage
37
31
  };
38
32
  }
@@ -16,28 +16,13 @@ export function createWorkflowVersionTuple(_internals, registration, _callbacks)
16
16
  };
17
17
  }
18
18
  export function workflowVersionTupleFromState(_internals, state, _callbacks) {
19
- return {
20
- workflowVersion: state.version,
21
- ...state.agentVersion !== void 0 && { agentVersion: state.agentVersion },
22
- ...state.toolVersions !== void 0 && { toolVersions: state.toolVersions }
23
- };
19
+ return state.versionTuple;
24
20
  }
25
21
  export function workflowStateWithVersionTuple(internals, state, versionTuple, _callbacks) {
26
- const {
27
- agentVersion: _existingAgentVersion,
28
- toolVersions: _existingToolVersions,
29
- ...rest
30
- } = state;
31
22
  return {
32
- ...rest,
33
- version: versionTuple.workflowVersion,
34
- updatedAt: internals.options.getNow(),
35
- ...versionTuple.agentVersion !== void 0 && {
36
- agentVersion: versionTuple.agentVersion
37
- },
38
- ...versionTuple.toolVersions !== void 0 && {
39
- toolVersions: versionTuple.toolVersions
40
- }
23
+ ...state,
24
+ versionTuple,
25
+ updatedAt: internals.options.getNow()
41
26
  };
42
27
  }
43
28
  export function derivePreparedExecutionState(internals, workflowId, state, checkpoint, registration, callbacks) {
@@ -76,5 +61,5 @@ export async function prepareResumeState(internals, workflowId, state, checkpoin
76
61
  };
77
62
  }
78
63
  export function throwVersionMismatch(_internals, workflowId, state, registration, versionDiff, _callbacks) {
79
- throw new VersionMismatchError(workflowId, state.type, state.version, registration.version, void 0, versionDiff);
64
+ throw new VersionMismatchError(workflowId, state.type, state.versionTuple.workflowVersion, registration.version, void 0, versionDiff);
80
65
  }
@@ -0,0 +1,45 @@
1
+ import type { WorkflowState } from '../../types.ts';
2
+ import type { EngineInternals } from '../internals.ts';
3
+ /**
4
+ * Re-provide a recovered inline workflow's non-serialized `services` before its
5
+ * generator is driven forward, and decide whether execution should proceed.
6
+ *
7
+ * Both recovery entry points use this: `resumeWorkflowFromStorage` (for a run
8
+ * left `running`) and the delayed-start timer handler (for a `startAfter`/
9
+ * `startAt` run that crashed `pending` before its timer fired). On a fresh
10
+ * process the in-memory `workflowServices` map is empty, so without this a
11
+ * recovered run that originally had services would silently execute with
12
+ * `ctx.services === undefined`.
13
+ *
14
+ * The resolver is only consulted for runs launched WITH services, detected by
15
+ * the durable `KEYS.workflowHasServices` marker. A run that never had services
16
+ * has no marker and proceeds without touching the resolver — so a fail-closed
17
+ * resolver does not fail healthy no-services runs.
18
+ *
19
+ * Returns `false` to proceed (services available, none expected, or no resolver
20
+ * is configured). Returns `true` to STOP — the run was terminally
21
+ * failed (services unavailable), or the terminal commit faulted and the run was
22
+ * left for a later boot to retry. Either way the generator must not advance:
23
+ * driving it without services would crash the body and that throw would escape
24
+ * into the recovery loop, aborting sibling runs.
25
+ *
26
+ * Worker mode skips this (services are inline-only, rejected at `engine.start()`).
27
+ * A resolver throw is treated as `unavailable` with the error as the reason,
28
+ * for the same sibling-isolation reason.
29
+ *
30
+ * @param failRun - Terminally fails just this run with `reason`. Supplied by the
31
+ * caller because the two entry points reach the termination machinery through
32
+ * different callback bundles. It receives the canonical terminal error built
33
+ * by {@link unavailableServicesError}, so both recovery paths fail the run
34
+ * with an identical message and (via `failWorkflow`'s default) the `system`
35
+ * failure category.
36
+ * @param onCommitError - Records a fail-warn when `failRun` itself throws, so the
37
+ * swallowed terminal-commit fault is still observable.
38
+ */
39
+ export declare function reprovideRecoveredServices(internals: EngineInternals, state: WorkflowState, failRun: (workflowId: string, error: Error) => Promise<void>, onCommitError: (source: string, error: unknown, workflowId: string) => void): Promise<boolean>;
40
+ /**
41
+ * The canonical terminal error for a recovered run whose services could not be
42
+ * re-provided. Shared by both recovery paths so they fail with an identical
43
+ * message (the failure category is `system`, the default for `failWorkflow`).
44
+ */
45
+ export declare function unavailableServicesError(workflowId: string, reason: string): Error;
@@ -0,0 +1,34 @@
1
+ import { KEYS } from "../../../storage/interface.js";
2
+ export async function reprovideRecoveredServices(internals, state, failRun, onCommitError) {
3
+ const resolver = internals.options.resolveWorkflowServices;
4
+ if (internals.inlineStrategy === null || !resolver)
5
+ return !1;
6
+ if (internals.workflowServices.has(state.id))
7
+ return !1;
8
+ if (await internals.storage.get(KEYS.workflowHasServices(state.id)) === null)
9
+ return !1;
10
+ let reason;
11
+ try {
12
+ const resolution = await resolver({
13
+ workflowId: state.id,
14
+ workflowType: state.type,
15
+ input: state.input
16
+ });
17
+ if (resolution.status === "available") {
18
+ internals.workflowServices.set(state.id, resolution.services);
19
+ return !1;
20
+ }
21
+ reason = resolution.reason;
22
+ } catch (error) {
23
+ reason = error instanceof Error ? error.message : String(error);
24
+ }
25
+ try {
26
+ await failRun(state.id, unavailableServicesError(state.id, reason));
27
+ } catch (error) {
28
+ onCommitError("reprovideRecoveredServices", error, state.id);
29
+ }
30
+ return !0;
31
+ }
32
+ export function unavailableServicesError(workflowId, reason) {
33
+ return Error(`Recovered workflow "${workflowId}" services unavailable: ${reason}`);
34
+ }
@@ -1,21 +1,28 @@
1
1
  import { KEYS } from "../../../storage/interface.js";
2
2
  import { deserializeCheckpoint, serializeCheckpoint } from "../../checkpoint.js";
3
+ import { encode } from "../../codec.js";
3
4
  import { Context, setContextWorkflowInterceptor } from "../../context.js";
4
5
  import { EventLog } from "../../event-log.js";
5
6
  import { WorkflowResumedEvent } from "../../events.js";
7
+ import { buildTimerBatchOperations } from "../../scheduler.js";
6
8
  import { createCancelHandlerRegistration, resetCancelHandlers } from "../cancel-handlers.js";
7
9
  import { rememberCommittedCheckpointBytes } from "../checkpoint-commit-snapshots.js";
8
10
  import { getWorkflowExecutionStartedAt } from "../handles.js";
9
11
  import { loadWorkflowState } from "../storage-io.js";
10
12
  import { getComposedWorkflowInterceptor } from "../strategy-helpers.js";
11
13
  import { decodeWorkflowState } from "../validation.js";
14
+ import { buildWorkflowVisibilityIndexTransition } from "../workflow-indexes.js";
12
15
  import { prepareResumeState } from "./persist.js";
16
+ import { reprovideRecoveredServices } from "./recovered-services.js";
13
17
  import {
14
18
  enforceHistoryPolicyBeforeReplay,
15
19
  loadTerminalCleanupTrackedState,
16
20
  loadWorkflowStartHeaders,
17
21
  setWorkflowStartHeaders
18
22
  } from "./shared.js";
23
+ async function prepareRecoveredServicesOrFail(internals, state, callbacks) {
24
+ return reprovideRecoveredServices(internals, state, callbacks.failWorkflowForUnavailableServices, callbacks.handleCleanupError);
25
+ }
19
26
  function assertResumeNotTerminating(internals, workflowId) {
20
27
  if (internals.terminalizingWorkflows.has(workflowId))
21
28
  throw Error(`Cannot resume workflow "${workflowId}": termination is in progress`);
@@ -57,7 +64,8 @@ function relaunchInlineWorkflowAfterResume(internals, latestState, args) {
57
64
  sleepReferenceTime: resumeCheckpoint.createdAt,
58
65
  ...latestState.executionDeadline !== void 0 && {
59
66
  deadline: latestState.executionDeadline
60
- }
67
+ },
68
+ services: internals.workflowServices.get(workflowId)
61
69
  });
62
70
  setContextWorkflowInterceptor(context, getComposedWorkflowInterceptor(internals));
63
71
  if (internals.options.development)
@@ -90,8 +98,9 @@ async function performSerializedResume(internals, args) {
90
98
  assertResumeNotTerminating(internals, workflowId);
91
99
  if (!latestState)
92
100
  throw Error(`Workflow "${workflowId}" not found in storage`);
93
- if (latestState.status !== "running")
94
- throw Error(`Cannot resume workflow "${workflowId}": status is "${latestState.status}", expected "running"`);
101
+ if (latestState.status !== "running" && latestState.status !== "suspended")
102
+ throw Error(`Cannot resume workflow "${workflowId}": status is "${latestState.status}", expected "running" or "suspended"`);
103
+ await reactivateSuspendedWorkflowState(internals, latestState);
95
104
  commitSerializedResumeState(internals, args);
96
105
  if (internals.inlineStrategy) {
97
106
  relaunchInlineWorkflowAfterResume(internals, latestState, args);
@@ -99,13 +108,30 @@ async function performSerializedResume(internals, args) {
99
108
  }
100
109
  relaunchWorkerWorkflowAfterResume(internals, latestState, args);
101
110
  }
111
+ async function reactivateSuspendedWorkflowState(internals, state) {
112
+ if (state.status !== "suspended")
113
+ return;
114
+ const previousState = { ...state };
115
+ state.status = "running";
116
+ state.updatedAt = internals.options.getNow();
117
+ await internals.storage.batch([
118
+ { type: "put", key: KEYS.workflow(state.id), value: encode(state) },
119
+ ...buildWorkflowVisibilityIndexTransition(state.id, previousState, state).batchOps,
120
+ ...state.executionDeadline !== void 0 ? buildTimerBatchOperations({
121
+ id: `deadline:${state.id}`,
122
+ workflowId: state.id,
123
+ fireAt: state.executionDeadline,
124
+ kind: "execution-deadline"
125
+ }) : []
126
+ ]);
127
+ }
102
128
  export async function resumeWorkflowFromStorage(internals, workflowId, dispatchResumedEvent, callbacks) {
103
129
  const stateBytes = await internals.storage.get(KEYS.workflow(workflowId));
104
130
  if (!stateBytes)
105
131
  throw Error(`Workflow "${workflowId}" not found in storage`);
106
132
  const state = decodeWorkflowState(stateBytes);
107
- if (state.status !== "running")
108
- throw Error(`Cannot resume workflow "${workflowId}": status is "${state.status}", expected "running"`);
133
+ if (state.status !== "running" && state.status !== "suspended")
134
+ throw Error(`Cannot resume workflow "${workflowId}": status is "${state.status}", expected "running" or "suspended"`);
109
135
  const checkpointBytes = await internals.storage.get(KEYS.checkpoint(workflowId));
110
136
  if (!checkpointBytes)
111
137
  throw Error(`Checkpoint not found for workflow "${workflowId}"`);
@@ -117,6 +143,8 @@ export async function resumeWorkflowFromStorage(internals, workflowId, dispatchR
117
143
  return callbacks.getHandle(workflowId);
118
144
  const workflowStartHeaders = await loadWorkflowStartHeaders(internals, workflowId, callbacks);
119
145
  await loadTerminalCleanupTrackedState(internals, workflowId, callbacks);
146
+ if (await prepareRecoveredServicesOrFail(internals, state, callbacks))
147
+ return callbacks.getHandle(workflowId);
120
148
  const handle = callbacks.getHandle(workflowId);
121
149
  await callbacks.runSerializedWorkflowStateWrite(workflowId, () => performSerializedResume(internals, {
122
150
  workflowId,
@@ -48,6 +48,14 @@ export type LifecycleCallbacks = {
48
48
  * history is terminated without being replayed.
49
49
  */
50
50
  enforceHistoryCircuitBreaker: (workflowId: string) => Promise<void>;
51
+ /**
52
+ * Force a recovered workflow to a terminal `failed` state because its
53
+ * non-serialized `services` could not be re-provided (the engine's
54
+ * `resolveWorkflowServices` reported `unavailable`). Fails just this run with
55
+ * a `system` failure category; the engine and other recovered runs continue.
56
+ * `error` is the canonical {@link unavailableServicesError}.
57
+ */
58
+ failWorkflowForUnavailableServices: (workflowId: string, error: Error) => Promise<void>;
51
59
  };
52
60
  /**
53
61
  * Pre-replay history circuit breaker. Called at every restore-from-checkpoint
@@ -19,18 +19,7 @@ export function buildStartBatchOperations(_internals, workflowId, state, checkpo
19
19
  ...visibilityIndexOperations,
20
20
  ...buildWorkflowTagIndexOperations(workflowId, void 0, state.tags),
21
21
  ...buildInitialSearchAttributeOperations(_internals, workflowId, registration, options?.searchAttributes, callbacks),
22
- ...workflowStartHeaders && workflowStartHeaders.size > 0 ? [
23
- {
24
- type: "put",
25
- key: KEYS.workflowHeaders(workflowId),
26
- value: encodeWorkflowStartHeaders(workflowStartHeaders)
27
- },
28
- {
29
- type: "put",
30
- key: KEYS.terminalCleanupNeeded(workflowId),
31
- value: EMPTY_STORAGE_VALUE
32
- }
33
- ] : [],
22
+ ...buildPerRunScratchOperations(workflowId, options, workflowStartHeaders),
34
23
  ...additionalOperations ?? []
35
24
  ];
36
25
  if (executionDeadline !== void 0)
@@ -44,6 +33,28 @@ export function buildStartBatchOperations(_internals, workflowId, state, checkpo
44
33
  operations.push(...buildTimerBatchOperations(delayedStartTimer));
45
34
  return operations;
46
35
  }
36
+ function buildPerRunScratchOperations(workflowId, options, workflowStartHeaders) {
37
+ const hasHeaders = workflowStartHeaders !== void 0 && workflowStartHeaders.size > 0, hasServices = options?.services !== void 0, operations = [];
38
+ if (hasHeaders)
39
+ operations.push({
40
+ type: "put",
41
+ key: KEYS.workflowHeaders(workflowId),
42
+ value: encodeWorkflowStartHeaders(workflowStartHeaders)
43
+ });
44
+ if (hasServices)
45
+ operations.push({
46
+ type: "put",
47
+ key: KEYS.workflowHasServices(workflowId),
48
+ value: EMPTY_STORAGE_VALUE
49
+ });
50
+ if (hasHeaders || hasServices)
51
+ operations.push({
52
+ type: "put",
53
+ key: KEYS.terminalCleanupNeeded(workflowId),
54
+ value: EMPTY_STORAGE_VALUE
55
+ });
56
+ return operations;
57
+ }
47
58
  export function buildInitialSearchAttributeOperations(_internals, workflowId, registration, searchAttributes, callbacks) {
48
59
  if (!searchAttributes || Object.keys(searchAttributes).length === 0)
49
60
  return [];
@@ -0,0 +1,47 @@
1
+ import type { BatchOperation, ConditionalBatchCondition } from '../../../storage/interface.ts';
2
+ import type { Checkpoint, StartOptions, TimerEntry, WorkflowState } from '../../types.ts';
3
+ import type { EngineInternals } from '../internals.ts';
4
+ import { type LifecycleCallbacks, type RegistrationEntry } from './shared.ts';
5
+ /**
6
+ * Builds the id-dependent operations and compare-and-swap preconditions for an
7
+ * idempotent start or `startOrSignal`. Invoked by `startWorkflow` with the real
8
+ * `workflowId` once it has been generated, so the idempotency mapping put (and
9
+ * any create-batch signal) can carry that id. The whole start batch then commits
10
+ * through a single `storageConditionalBatch` gated on the returned conditions; a
11
+ * lost CAS rolls back the start and throws {@link StartIdempotencyRaceLostError}
12
+ * so the caller resolves to the winner.
13
+ */
14
+ export type BuildIdempotentStartOperations = (workflowId: string) => {
15
+ operations: BatchOperation[];
16
+ conditions: ConditionalBatchCondition[];
17
+ };
18
+ /**
19
+ * Internal sentinel: the idempotent create batch lost its compare-and-swap to a
20
+ * concurrent caller holding the same idempotency key. Never surfaced to users —
21
+ * `start` / `startOrSignal` catch it and resolve to the winning run's handle.
22
+ */
23
+ export declare class StartIdempotencyRaceLostError extends Error {
24
+ constructor();
25
+ }
26
+ /** Everything {@link buildAndCommitStartBatch} needs to assemble the start batch. */
27
+ export type StartBatchContext = {
28
+ internals: EngineInternals;
29
+ workflowId: string;
30
+ state: WorkflowState;
31
+ checkpoint: Checkpoint;
32
+ registration: RegistrationEntry;
33
+ options: StartOptions | undefined;
34
+ delayedStartTimer: TimerEntry | undefined;
35
+ persistedWorkflowStartHeaders: Map<string, string> | undefined;
36
+ additionalStartOperations: BatchOperation[] | undefined;
37
+ callbacks: LifecycleCallbacks;
38
+ };
39
+ /**
40
+ * Assemble the start batch — folding in the id-dependent idempotency mapping and
41
+ * create-batch signal once the real workflow id exists — and commit it, gated on
42
+ * any idempotency preconditions. Throws {@link StartIdempotencyRaceLostError}
43
+ * when a concurrent same-key caller won the compare-and-swap, so the calling
44
+ * `startWorkflow` rolls back its transient state and the wrapper resolves to the
45
+ * winning run.
46
+ */
47
+ export declare function buildAndCommitStartBatch(context: StartBatchContext, buildIdempotentStartOperations: BuildIdempotentStartOperations | undefined): Promise<void>;
@@ -0,0 +1,27 @@
1
+ import { requireStorageCapability, storageConditionalBatch } from "../../../storage/interface.js";
2
+ import { buildStartBatchOperations } from "./start-batch.js";
3
+
4
+ export class StartIdempotencyRaceLostError extends Error {
5
+ constructor() {
6
+ super("start idempotency compare-and-swap lost to a concurrent caller");
7
+ this.name = "StartIdempotencyRaceLostError";
8
+ }
9
+ }
10
+ async function persistStartBatch(internals, startOperations, conditions) {
11
+ if (conditions === void 0) {
12
+ await internals.storage.batch(startOperations);
13
+ return !0;
14
+ }
15
+ requireStorageCapability(internals.storage, "conditionalBatch", "start idempotency");
16
+ return storageConditionalBatch(internals.storage, conditions, startOperations);
17
+ }
18
+ function mergeAdditionalStartOperations(additional, idempotent) {
19
+ if (idempotent === void 0 || idempotent.length === 0)
20
+ return additional;
21
+ return [...additional ?? [], ...idempotent];
22
+ }
23
+ export async function buildAndCommitStartBatch(context, buildIdempotentStartOperations) {
24
+ const { internals, workflowId, state, checkpoint, registration, options } = context, idempotent = buildIdempotentStartOperations?.(workflowId), startOperations = buildStartBatchOperations(internals, workflowId, state, checkpoint, registration, options, state.executionDeadline, context.delayedStartTimer, context.persistedWorkflowStartHeaders, mergeAdditionalStartOperations(context.additionalStartOperations, idempotent?.operations), context.callbacks);
25
+ if (!await persistStartBatch(internals, startOperations, idempotent?.conditions))
26
+ throw new StartIdempotencyRaceLostError;
27
+ }
@@ -1,5 +1,33 @@
1
- import type { Checkpoint } from '../../types.ts';
1
+ import type { Checkpoint, StartOptions, WorkflowState } from '../../types.ts';
2
2
  import type { EngineInternals } from '../internals.ts';
3
- import { type LifecycleCallbacks } from './shared.ts';
3
+ import { type LifecycleCallbacks, type RegistrationEntry } from './shared.ts';
4
4
  export declare function runWorkflowStartInterceptor(_internals: EngineInternals, workflowId: string, workflowType: string, input: unknown, parentHeaders: Map<string, string> | undefined, callbacks: LifecycleCallbacks): Map<string, string> | undefined;
5
5
  export declare function startWorkflowExecution(internals: EngineInternals, workflowId: string, workflowType: string, input: unknown, checkpoint: Checkpoint, nestingDepth: number, executionDeadline: number | undefined, executionStateOwnerId: string, _callbacks?: LifecycleCallbacks): void;
6
+ export declare function beginWorkflowExecution(internals: EngineInternals, workflowId: string, workflowType: string, input: unknown, checkpoint: Checkpoint, executionDeadline: number | undefined, executionStateOwnerId: string, _registration: RegistrationEntry, callbacks: LifecycleCallbacks, onStarted?: () => void): void;
7
+ /**
8
+ * `defer: false` is an inline-only liveness gate: it awaits the moment the
9
+ * generator is first driven. A worker-mode start queues to the Worker transport
10
+ * (no inline liveness to await), and a delayed start has not begun executing at
11
+ * all — so both reject rather than silently behaving like `defer: true`.
12
+ */
13
+ export declare function assertDeferSupported(internals: EngineInternals, options: StartOptions | undefined, isDelayedStart: boolean): void;
14
+ type BeginExecutionParams = {
15
+ type: string;
16
+ input: unknown;
17
+ checkpoint: Checkpoint;
18
+ state: WorkflowState;
19
+ registration: RegistrationEntry;
20
+ options: StartOptions | undefined;
21
+ isDelayed: boolean;
22
+ };
23
+ /**
24
+ * Drive the initial execution for a freshly-started workflow and, when
25
+ * `defer: false`, await the run actually beginning before resolving. A delayed
26
+ * start does not begin executing now, so it neither begins execution here nor
27
+ * awaits liveness. The liveness promise is settled by the inline-launch queue
28
+ * once the generator is driven (or by dispose if the queued start is discarded),
29
+ * so a `defer: false` caller can rely on the run being live without a macrotask
30
+ * round-trip the moment `engine.start()` resolves.
31
+ */
32
+ export declare function beginExecutionAwaitingLiveness(internals: EngineInternals, params: BeginExecutionParams, workflowId: string, callbacks: LifecycleCallbacks): Promise<void>;
33
+ export {};
@@ -1,4 +1,6 @@
1
1
  import { serializeCheckpoint } from "../../checkpoint.js";
2
+ import { WorkflowStartedEvent } from "../../events.js";
3
+ import { StartWorkflowValidationError } from "../../start-workflow-validation.js";
2
4
  export function runWorkflowStartInterceptor(_internals, workflowId, workflowType, input, parentHeaders, callbacks) {
3
5
  const composedInterceptor = callbacks.getComposedWorkflowInterceptor();
4
6
  if (!composedInterceptor)
@@ -37,3 +39,39 @@ export function startWorkflowExecution(internals, workflowId, workflowType, inpu
37
39
  }
38
40
  });
39
41
  }
42
+ export function beginWorkflowExecution(internals, workflowId, workflowType, input, checkpoint, executionDeadline, executionStateOwnerId, _registration, callbacks, onStarted) {
43
+ const nestingDepth = internals.pendingNestingDepth ?? 0;
44
+ internals.pendingNestingDepth = void 0;
45
+ if (internals.inlineStrategy !== null) {
46
+ callbacks.queueInlineWorkflowExecutionStart({
47
+ workflowId,
48
+ workflowType,
49
+ input,
50
+ checkpoint,
51
+ nestingDepth,
52
+ executionDeadline,
53
+ executionStateOwnerId,
54
+ ...onStarted !== void 0 && { onStarted }
55
+ });
56
+ return;
57
+ }
58
+ callbacks.dispatchEvent(new WorkflowStartedEvent(workflowId, workflowType, input));
59
+ startWorkflowExecution(internals, workflowId, workflowType, input, checkpoint, nestingDepth, executionDeadline, executionStateOwnerId, callbacks);
60
+ onStarted?.();
61
+ }
62
+ export function assertDeferSupported(internals, options, isDelayedStart) {
63
+ if (options?.defer !== !1)
64
+ return;
65
+ if (internals.inlineStrategy === null)
66
+ throw new StartWorkflowValidationError('options.defer: false is only supported in inline execution mode; a worker-mode start cannot be awaited for inline liveness. Use workflowExecutionMode: "inline" or remove defer.');
67
+ if (isDelayedStart)
68
+ throw new StartWorkflowValidationError("options.defer: false is incompatible with a delayed start (startAt/startAfter): a scheduled run has not begun executing, so there is no liveness to await. Remove defer or the delayed-start option.");
69
+ }
70
+ export async function beginExecutionAwaitingLiveness(internals, params, workflowId, callbacks) {
71
+ if (params.isDelayed)
72
+ return;
73
+ const liveness = params.options?.defer === !1 ? Promise.withResolvers() : void 0;
74
+ beginWorkflowExecution(internals, workflowId, params.type, params.input, params.checkpoint, params.state.executionDeadline, params.state.executionStateOwnerId ?? workflowId, params.registration, callbacks, liveness ? () => liveness.resolve() : void 0);
75
+ if (liveness)
76
+ await liveness.promise;
77
+ }