@lostgradient/weft 0.2.1 → 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 (188) 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 +9 -0
  22. package/dist/core/context/internals.d.ts +9 -0
  23. package/dist/core/context/internals.js +3 -0
  24. package/dist/core/context/run-operation.d.ts +16 -3
  25. package/dist/core/context/run-operation.js +16 -7
  26. package/dist/core/engine/bulk-operations.js +1 -1
  27. package/dist/core/engine/construction.d.ts +0 -1
  28. package/dist/core/engine/construction.js +10 -1
  29. package/dist/core/engine/disposal.js +12 -0
  30. package/dist/core/engine/engine-create-types.d.ts +0 -14
  31. package/dist/core/engine/engine-internal-types.d.ts +12 -0
  32. package/dist/core/engine/engine-leak-warnings.d.ts +6 -0
  33. package/dist/core/engine/engine-leak-warnings.js +4 -0
  34. package/dist/core/engine/engine-runtime-helpers.d.ts +17 -0
  35. package/dist/core/engine/engine-runtime-helpers.js +26 -5
  36. package/dist/core/engine/errors.d.ts +74 -0
  37. package/dist/core/engine/errors.js +25 -1
  38. package/dist/core/engine/handle-result.js +1 -1
  39. package/dist/core/engine/handles.d.ts +89 -40
  40. package/dist/core/engine/handles.js +25 -27
  41. package/dist/core/engine/index.d.ts +96 -4
  42. package/dist/core/engine/index.js +75 -4
  43. package/dist/core/engine/inline-launch-queue.d.ts +14 -0
  44. package/dist/core/engine/inline-launch-queue.js +32 -7
  45. package/dist/core/engine/internals.d.ts +18 -10
  46. package/dist/core/engine/lifecycle/fork-helpers.js +1 -7
  47. package/dist/core/engine/lifecycle/persist.js +5 -20
  48. package/dist/core/engine/lifecycle/resume.js +25 -4
  49. package/dist/core/engine/lifecycle/start-commit.d.ts +47 -0
  50. package/dist/core/engine/lifecycle/start-commit.js +27 -0
  51. package/dist/core/engine/lifecycle/start-exec.d.ts +30 -2
  52. package/dist/core/engine/lifecycle/start-exec.js +38 -0
  53. package/dist/core/engine/lifecycle/start-or-signal-resolution.d.ts +79 -0
  54. package/dist/core/engine/lifecycle/start-or-signal-resolution.js +60 -0
  55. package/dist/core/engine/lifecycle/start-or-signal.d.ts +45 -0
  56. package/dist/core/engine/lifecycle/start-or-signal.js +141 -0
  57. package/dist/core/engine/lifecycle/start.d.ts +3 -3
  58. package/dist/core/engine/lifecycle/start.js +31 -37
  59. package/dist/core/engine/lifecycle.d.ts +3 -2
  60. package/dist/core/engine/lifecycle.js +9 -2
  61. package/dist/core/engine/listing.js +1 -1
  62. package/dist/core/engine/persisted-data-version.d.ts +5 -9
  63. package/dist/core/engine/persisted-data-version.js +4 -5
  64. package/dist/core/engine/schedule-handle.d.ts +45 -0
  65. package/dist/core/engine/schedule-handle.js +26 -0
  66. package/dist/core/engine/schedules.d.ts +1 -1
  67. package/dist/core/engine/schedules.js +7 -3
  68. package/dist/core/engine/second-instance-detector.d.ts +96 -0
  69. package/dist/core/engine/second-instance-detector.js +108 -0
  70. package/dist/core/engine/signals.d.ts +22 -0
  71. package/dist/core/engine/signals.js +15 -0
  72. package/dist/core/engine/termination/cleanup.d.ts +25 -0
  73. package/dist/core/engine/termination/cleanup.js +19 -1
  74. package/dist/core/engine/termination/complete.js +4 -3
  75. package/dist/core/engine/termination/suspend.d.ts +68 -0
  76. package/dist/core/engine/termination/suspend.js +41 -0
  77. package/dist/core/engine/termination.d.ts +4 -2
  78. package/dist/core/engine/termination.js +2 -0
  79. package/dist/core/engine/validation.js +25 -1
  80. package/dist/core/engine/workflow-feed.d.ts +5 -3
  81. package/dist/core/events/event-map.d.ts +2 -1
  82. package/dist/core/events/workflow-events.d.ts +23 -0
  83. package/dist/core/events/workflow-events.js +9 -0
  84. package/dist/core/list-filter-validation.js +2 -1
  85. package/dist/core/start-workflow-validation.d.ts +22 -0
  86. package/dist/core/start-workflow-validation.js +11 -1
  87. package/dist/core/step-context.d.ts +10 -6
  88. package/dist/core/step-context.js +7 -15
  89. package/dist/core/types/activity.d.ts +6 -3
  90. package/dist/core/types/identity.d.ts +8 -1
  91. package/dist/core/types/launch-metadata.d.ts +33 -0
  92. package/dist/core/types/launch-metadata.js +0 -0
  93. package/dist/core/types/message-handles.d.ts +25 -0
  94. package/dist/core/types/options.d.ts +48 -54
  95. package/dist/core/types/reviews.d.ts +2 -1
  96. package/dist/core/types/services-resolution.d.ts +47 -0
  97. package/dist/core/types/services-resolution.js +0 -0
  98. package/dist/core/types/state.d.ts +11 -11
  99. package/dist/core/types/workflow-builder.d.ts +5 -4
  100. package/dist/core/types/workflow-function.d.ts +17 -0
  101. package/dist/core/types/workflow-snapshot.d.ts +29 -0
  102. package/dist/core/types/workflow-snapshot.js +0 -0
  103. package/dist/core/types.d.ts +3 -0
  104. package/dist/core/types.js +3 -0
  105. package/dist/core/weft-error.d.ts +1 -1
  106. package/dist/core/weft-error.js +3 -1
  107. package/dist/diagnostics/doctor.js +6 -3
  108. package/dist/diagnostics/format.js +2 -2
  109. package/dist/diagnostics/types.d.ts +1 -0
  110. package/dist/diagnostics/version-check.js +6 -4
  111. package/dist/index.d.ts +4 -4
  112. package/dist/index.js +10 -1
  113. package/dist/json-schema.js +1 -1
  114. package/dist/mcp/cli.js +35 -35
  115. package/dist/mcp/list-filter.js +2 -1
  116. package/dist/mcp/session.js +1 -0
  117. package/dist/observability/index.js +2 -2
  118. package/dist/server/handler.js +30 -30
  119. package/dist/server/index.js +33 -33
  120. package/dist/server/interactive-operations.js +1 -0
  121. package/dist/server/operations/resume-workflow.js +2 -2
  122. package/dist/server/operations/start-or-signal-workflow.d.ts +39 -0
  123. package/dist/server/operations/start-or-signal-workflow.js +140 -0
  124. package/dist/server/operations/start-workflow-options.d.ts +32 -0
  125. package/dist/server/operations/start-workflow-options.js +63 -0
  126. package/dist/server/operations/start-workflow.js +7 -69
  127. package/dist/server/operations/suspend-workflow.d.ts +13 -0
  128. package/dist/server/operations/suspend-workflow.js +36 -0
  129. package/dist/server/rest-binding.d.ts +18 -7
  130. package/dist/server/rest-bindings.js +12 -0
  131. package/dist/server/runtime/task-dispatch.js +5 -3
  132. package/dist/server/runtime/task-polling.d.ts +16 -2
  133. package/dist/server/runtime/task-polling.js +20 -5
  134. package/dist/server/runtime/websocket-worker.js +8 -0
  135. package/dist/server/serve-internals.d.ts +8 -0
  136. package/dist/server/serve-internals.js +4 -2
  137. package/dist/server/task-state.d.ts +8 -0
  138. package/dist/service-worker/index.js +28 -28
  139. package/dist/storage/capabilities.d.ts +10 -2
  140. package/dist/storage/capabilities.js +2 -2
  141. package/dist/storage/http.js +2 -2
  142. package/dist/storage/index.d.ts +6 -1
  143. package/dist/storage/indexeddb.js +1 -1
  144. package/dist/storage/interface.d.ts +26 -0
  145. package/dist/storage/interface.js +1 -1
  146. package/dist/storage/key-prefixes.d.ts +1 -1
  147. package/dist/storage/key-prefixes.js +2 -0
  148. package/dist/storage/lmdb.js +1 -1
  149. package/dist/storage/memory.js +1 -1
  150. package/dist/storage/neon-value-mapping.d.ts +47 -0
  151. package/dist/storage/neon-value-mapping.js +11 -0
  152. package/dist/storage/neon.d.ts +108 -0
  153. package/dist/storage/neon.js +10 -0
  154. package/dist/storage/node-sqlite-loader.d.ts +71 -0
  155. package/dist/storage/node-sqlite-loader.js +41 -0
  156. package/dist/storage/node-sqlite.d.ts +1 -19
  157. package/dist/storage/node-sqlite.js +38 -32
  158. package/dist/storage/postgres-key-value-queries.d.ts +79 -0
  159. package/dist/storage/postgres-key-value-queries.js +63 -0
  160. package/dist/storage/resolve.d.ts +2 -165
  161. package/dist/storage/resolve.js +1 -1
  162. package/dist/storage/scoped-storage.js +1 -1
  163. package/dist/storage/storage-configuration.d.ts +209 -0
  164. package/dist/storage/storage-configuration.js +0 -0
  165. package/dist/storage/text-value-store.d.ts +9 -9
  166. package/dist/storage/turso.js +2 -2
  167. package/dist/storage/typed-storage.js +1 -1
  168. package/dist/storage/web-extension.js +1 -1
  169. package/dist/testing/index.js +33 -33
  170. package/dist/version.d.ts +1 -1
  171. package/dist/version.js +1 -1
  172. package/dist/worker/index.js +9 -5
  173. package/dist/worker/long-poll.js +4 -0
  174. package/dist/worker/protocol-messages.d.ts +20 -0
  175. package/dist/worker/protocol-schemas.d.ts +32 -0
  176. package/dist/worker/protocol-schemas.js +8 -4
  177. package/dist/worker/protocol-task-result.d.ts +28 -0
  178. package/dist/worker/protocol-task-result.js +76 -0
  179. package/dist/worker/protocol.d.ts +4 -15
  180. package/dist/worker/protocol.js +1 -1
  181. package/dist/worker/registry/fair-share.d.ts +29 -0
  182. package/dist/worker/registry/fair-share.js +30 -0
  183. package/dist/worker/registry/routing.d.ts +18 -0
  184. package/dist/worker/registry/routing.js +14 -0
  185. package/dist/worker/registry/types.d.ts +7 -0
  186. package/dist/worker/registry.d.ts +16 -1
  187. package/dist/worker/registry.js +24 -36
  188. package/package.json +17 -4
@@ -4,7 +4,7 @@ import type { StoredStreamChunk } from '../context.ts';
4
4
  import type { Interceptor } from '../interceptor.ts';
5
5
  import { type ReviewRequest } from '../review/index.ts';
6
6
  import { Scheduler } from '../scheduler.ts';
7
- import { type AnyActivityDefinition, type AnyWorkflowDefinition, type AttributeFilterKey, type BulkCancelResult, type BulkDeleteResult, type BulkOperationCommitOptions, type BulkOperationDryRunOptions, type BulkOperationDryRunResult, type BulkSignalAllCommitOptions, type BulkSignalAllDryRunOptions, type BulkSignalResult, type BulkTagResult, type CheckpointState, type CheckpointSummary, type CoordinatedUpdateResult, type DefaultActivityTypes, type DefaultWorkflowRegistry, type ForkOptions, type InferActivityEntries, type InferActivityEntry, type InferWorkflowEntries, type InferWorkflowEntry, type ListFilter, type ListOptions, type PaginatedResult, type PurgeResult, type QueryDefinition, type RegisteredWorkflowDefinition, type RetentionOverview, type ReviewListEntry, type ReviewListFilter, type ScheduleDefinition, type ScheduleFilter, type ScheduleOptions, type ScheduleSpec, type ScheduleSummary, type SearchAttributeValue, type SignalDefinition, type SignalDeliveryOptions, type StartOptions, type SubmitReviewOptions, type TypedListFilter, type UpdateDefinition, type WorkflowEvent, type WorkflowInput, type WorkflowOutput, type WorkflowReplay, type WorkflowState, type WorkflowSummary, type WorkflowTimelineEntry } from '../types.ts';
7
+ import { type AnyActivityDefinition, type AnyWorkflowDefinition, type AttributeFilterKey, type BulkCancelResult, type BulkDeleteResult, type BulkOperationCommitOptions, type BulkOperationDryRunOptions, type BulkOperationDryRunResult, type BulkSignalAllCommitOptions, type BulkSignalAllDryRunOptions, type BulkSignalResult, type BulkTagResult, type CheckpointState, type CheckpointSummary, type CoordinatedUpdateResult, type DefaultActivityTypes, type DefaultWorkflowRegistry, type ForkOptions, type InferActivityEntries, type InferActivityEntry, type InferWorkflowEntries, type InferWorkflowEntry, type ListFilter, type ListOptions, type PaginatedResult, type PurgeResult, type QueryDefinition, type RegisteredWorkflowDefinition, type RetentionOverview, type ReviewListEntry, type ReviewListFilter, type ScheduleDefinition, type ScheduleFilter, type ScheduleOptions, type ScheduleSpec, type ScheduleSummary, type SearchAttributeValue, type SignalDefinition, type SignalDeliveryOptions, type StartOptions, type StartOrSignalSignal, type SubmitReviewOptions, type TypedListFilter, type UpdateDefinition, type WorkflowEvent, type WorkflowInput, type WorkflowOutput, type WorkflowReplay, type WorkflowState, type WorkflowSummary, type WorkflowTimelineEntry } from '../types.ts';
8
8
  import type { TimerEntry } from '../types/checkpoint.ts';
9
9
  import type { WorkflowAlreadyRegistered } from '../types/workflow-builder.ts';
10
10
  import { type AggregateOptions, type AggregateResult } from './aggregate.ts';
@@ -12,17 +12,19 @@ import { type EmptyActivityDefinitions, type EmptyWorkflowDefinitions, type Know
12
12
  import { type ActivityDefinitionName, type EngineCreateOptions, type RegisteredActivityDefinitionExecute, type UnknownWorkflowNameWhenDefaultRegistryIsEmpty } from './engine-create-types.ts';
13
13
  import type { EngineConstructorOptions } from './engine-internal-types.ts';
14
14
  import type { EngineStateNamespace } from './engine-state-namespace.ts';
15
- import { HANDLE_RESULT_PROMISE, ScheduleHandle, WorkflowHandle } from './handles.ts';
15
+ import { HANDLE_RESULT_PROMISE, WorkflowHandle } from './handles.ts';
16
16
  import { type RecoverAllOptions } from './lifecycle.ts';
17
17
  import { assertCompatiblePersistedDataVersion } from './persisted-data-version.ts';
18
+ import { ScheduleHandle } from './schedule-handle.ts';
18
19
  import { type WorkflowFeedListener, type WorkflowFeedRecord, type WorkflowFeedSelector } from './workflow-feed.ts';
19
20
  export { ActivityReconciliationCapabilityError, ActivityReconciliationConflictError, ActivityReconciliationIndeterminateError, } from './activity-reconciliation.ts';
20
21
  export { AsyncActivityTokenNotFoundError } from './async-activity-completion.ts';
21
22
  export type { PendingAsyncActivity } from './async-activity-completion.ts';
22
23
  export type { PendingTimelineEntry, RegistrationEntry, ResolvedOptions, TrackedWaiterKeys, WorkflowResultWaiter, } from './engine-internal-types.ts';
23
- export { ActivityResolutionError, BulkDeleteRequiresTerminalWorkflowsError, BulkOperationConfirmationError, EngineCreateNameMismatchError, EngineDisposedError, PersistedDataIncompatibleError, WorkflowAlreadyExistsError, WorkflowNotFoundError, WorkflowNotRegisteredError, WorkflowTypeNotRegisteredForRecoveryError, } from './errors.ts';
24
- export { HANDLE_RESULT_PROMISE, ScheduleHandle, WorkflowHandle } from './handles.ts';
24
+ export { ActivityResolutionError, BulkDeleteRequiresTerminalWorkflowsError, BulkOperationConfirmationError, EngineCreateNameMismatchError, EngineDisposedError, IdempotencyKeyPurgedError, PersistedDataIncompatibleError, StartOrSignalConflictError, WorkflowAlreadyExistsError, WorkflowNotFoundError, WorkflowNotRegisteredError, WorkflowSuspendNotSupportedError, WorkflowTypeNotRegisteredForRecoveryError, } from './errors.ts';
25
+ export { HANDLE_RESULT_PROMISE, WorkflowHandle } from './handles.ts';
25
26
  export type { RecoverAllOptions } from './lifecycle.ts';
27
+ export { ScheduleHandle } from './schedule-handle.ts';
26
28
  export type { WorkflowFeedListener, WorkflowFeedRecord, WorkflowFeedSelector, } from './workflow-feed.ts';
27
29
  export type { EngineCreateOptions } from './engine-create-types.ts';
28
30
  export { clearEngineLeakWarningTokenForTesting, getEngineLeakCollectionCountForTesting, hasEngineLeakWarningTokenForTesting, setEngineLeakWarningOverrideForTesting, setNextEngineLeakWarningTokenForTesting, shouldEmitEngineLeakWarningForTesting, } from './engine-leak-warnings.ts';
@@ -30,6 +32,7 @@ export type { EngineStateNamespace } from './engine-state-namespace.ts';
30
32
  export { assertCompatiblePersistedDataVersion };
31
33
  export declare const ENGINE_PARKED_WORKFLOW_COUNT_FOR_TESTING: unique symbol;
32
34
  export declare const ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING: unique symbol;
35
+ export declare const ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING: unique symbol;
33
36
  /**
34
37
  * Durable execution engine.
35
38
  *
@@ -185,6 +188,25 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
185
188
  listActivityDefinitions(): ActivityMetadata[];
186
189
  start<TName extends KnownWorkflowNames<TWorkflows>>(type: TName, input: WorkflowInput<TWorkflows, TName>, options?: StartOptions): Promise<WorkflowHandle<WorkflowOutput<TWorkflows, TName>>>;
187
190
  start<TName extends string>(type: UnknownWorkflowNameWhenDefaultRegistryIsEmpty<TWorkflows, TName>, input: unknown, options?: StartOptions): Promise<WorkflowHandle>;
191
+ /**
192
+ * Atomically start a workflow or signal it if it already exists
193
+ * (signal-with-start). With an absent target, the workflow record and the
194
+ * first signal commit in one batch and the freshly-launched run consumes the
195
+ * signal on its first drive. A non-terminal target (running, pending, or
196
+ * suspended) is signalled through the normal signal path; a terminal target
197
+ * throws {@link StartOrSignalConflictError} rather than starting a new run or
198
+ * dropping the signal.
199
+ *
200
+ * Concurrent callers converge on one workflow and one delivered signal. Pass
201
+ * `options.idempotencyKey` to dedup independent callers (e.g. retried
202
+ * webhooks); the signal id derives from the key when `signal.signalId` is
203
+ * omitted, so callers that share only the key still converge. `signal.signalId`
204
+ * and `options.idempotencyKey` are mutually exclusive (provide exactly one), as
205
+ * are `options.id` and `options.idempotencyKey`. Requires a storage backend
206
+ * with `conditionalBatch`.
207
+ */
208
+ startOrSignal<TName extends KnownWorkflowNames<TWorkflows>>(type: TName, input: WorkflowInput<TWorkflows, TName>, signal: StartOrSignalSignal, options?: StartOptions): Promise<WorkflowHandle<WorkflowOutput<TWorkflows, TName>>>;
209
+ startOrSignal<TName extends string>(type: UnknownWorkflowNameWhenDefaultRegistryIsEmpty<TWorkflows, TName>, input: unknown, signal: StartOrSignalSignal, options?: StartOptions): Promise<WorkflowHandle>;
188
210
  getHandle(workflowId: string): WorkflowHandle;
189
211
  list<const TAttributeKeys extends readonly AttributeFilterKey[] = readonly AttributeFilterKey[]>(filter?: TypedListFilter<TAttributeKeys>, options?: ListOptions): Promise<PaginatedResult<WorkflowSummary>>;
190
212
  aggregate(filter: ListFilter | undefined, options: AggregateOptions): Promise<AggregateResult>;
@@ -201,6 +223,37 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
201
223
  tagAll(filter: ListFilter, tags: string[], options?: BulkOperationCommitOptions): Promise<BulkTagResult>;
202
224
  untagAll(filter: ListFilter, tags: string[], options: BulkOperationDryRunOptions): Promise<BulkOperationDryRunResult>;
203
225
  untagAll(filter: ListFilter, tags: string[], options?: BulkOperationCommitOptions): Promise<BulkTagResult>;
226
+ /**
227
+ * Register a recurring schedule that starts a workflow on a cron expression or
228
+ * fixed interval. Returns a {@link ScheduleHandle} for pausing, resuming,
229
+ * updating, or cancelling the schedule.
230
+ *
231
+ * Two call forms:
232
+ * - A {@link ScheduleDefinition} object: `engine.schedule({ workflow, cron, input })`.
233
+ * Carries the workflow (definition or type name), the `cron`/`every` spec,
234
+ * optional `input`, `id`, `overlapPolicy`, and `backfill`.
235
+ * - Positional: `engine.schedule(type, input, spec, options?)` where `spec` is
236
+ * a cron string or a {@link ScheduleSpec} (`{ cron }` or `{ every }`).
237
+ *
238
+ * The {@link ScheduleOptions.overlap} policy governs what happens when a tick
239
+ * fires while the previous run is still in flight.
240
+ *
241
+ * @example
242
+ * ```ts
243
+ * import { workflow, Engine } from '@lostgradient/weft';
244
+ *
245
+ * const engine = new Engine();
246
+ * engine.register(workflow({ name: 'sweep' }).execute(async function* () { return 'ok'; }));
247
+ *
248
+ * // Definition form: every day at 09:00, skip a tick if the prior run is still running.
249
+ * const handle = await engine.schedule({
250
+ * workflow: 'sweep',
251
+ * cron: '0 9 * * *',
252
+ * overlapPolicy: 'skip',
253
+ * });
254
+ * await handle.pause();
255
+ * ```
256
+ */
204
257
  schedule<TInput>(definition: ScheduleDefinition<TInput>): Promise<ScheduleHandle>;
205
258
  schedule(type: string, input: unknown, spec: string | ScheduleSpec, options?: ScheduleOptions): Promise<ScheduleHandle>;
206
259
  getSchedule(scheduleId: string): Promise<ScheduleSummary | null>;
@@ -212,6 +265,7 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
212
265
  [HANDLE_RESULT_PROMISE](workflowId: string): Promise<unknown>;
213
266
  [ENGINE_PARKED_WORKFLOW_COUNT_FOR_TESTING](): number;
214
267
  [ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING](): number;
268
+ [ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING](): number;
215
269
  signal(workflowId: string, name: SignalDefinition): Promise<void>;
216
270
  signal<TInput>(workflowId: string, name: SignalDefinition<TInput>, payload: TInput, options?: SignalDeliveryOptions): Promise<void>;
217
271
  signal(workflowId: string, name: string, payload?: unknown, options?: SignalDeliveryOptions): Promise<void>;
@@ -257,6 +311,13 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
257
311
  */
258
312
  getOffload(workflowId: string, key: string): Promise<unknown>;
259
313
  fork(sourceWorkflowId: string, options?: ForkOptions): Promise<WorkflowHandle>;
314
+ /**
315
+ * Re-drive a workflow from its persisted checkpoint and return a live handle.
316
+ * Accepts a workflow left `'running'` (e.g. recovered after a process restart)
317
+ * or one explicitly `'suspended'` via {@link Engine.suspend} — a suspended
318
+ * workflow is durably flipped back to `'running'` as part of resuming. Throws
319
+ * if the workflow is in any other status (terminal, pending) or not found.
320
+ */
260
321
  resume(workflowId: string): Promise<WorkflowHandle>;
261
322
  /**
262
323
  * Recover every running workflow found in storage. By default, recovery
@@ -291,8 +352,23 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
291
352
  */
292
353
  failAsyncActivity(token: string, error: unknown): Promise<void>;
293
354
  cancel(workflowId: string): Promise<void>;
355
+ /**
356
+ * Suspend a running workflow without terminating it. The workflow transitions
357
+ * to the non-terminal `'suspended'` status, keeps its durable checkpoint, and
358
+ * is later resumable via {@link Engine.resume} (or `handle.resume()`). Unlike
359
+ * {@link Engine.cancel}, this does not run cancel handlers and does not settle
360
+ * the result promise — `handle.result()` stays pending until a later `resume()`
361
+ * drives the run to completion.
362
+ *
363
+ * Suspension is client-driven preemption, so a suspended workflow is NOT
364
+ * auto-recovered by {@link Engine.recoverAll}; resume it explicitly. Calling
365
+ * `suspend` on a workflow that is not running (already terminal, or never
366
+ * started) is a no-op.
367
+ */
368
+ suspend(workflowId: string): Promise<void>;
294
369
  timeout(workflowId: string): Promise<void>;
295
370
  get(workflowId: string): Promise<WorkflowState | null>;
371
+ getCurrentCheckpointStep(workflowId: string): Promise<number | null>;
296
372
  getAttributes(workflowId: string): Promise<Record<string, SearchAttributeValue> | null>;
297
373
  setAttributes(workflowId: string, attributes: Record<string, SearchAttributeValue>): Promise<void>;
298
374
  addTags(workflowId: string, ...tags: string[]): Promise<void>;
@@ -313,7 +389,23 @@ export declare class Engine<TWorkflows extends object = DefaultWorkflowRegistry,
313
389
  timeout?: number;
314
390
  idempotencyKey?: string;
315
391
  }): Promise<CoordinatedUpdateResult>;
392
+ /**
393
+ * Synchronous teardown (`using engine = ...`). Pending inline launches that
394
+ * have not yet run are **discarded**, not executed. When you need queued
395
+ * starts to complete before teardown — or want a clean event loop with no
396
+ * dangling deferred-launch macrotask — prefer {@link Engine[Symbol.asyncDispose]}
397
+ * via `await using`.
398
+ */
316
399
  [Symbol.dispose](): void;
400
+ /**
401
+ * Async teardown (`await using engine = ...`). Drains pending inline launches
402
+ * so each queued workflow completes its first turn before disposal, leaving no
403
+ * deferred-launch macrotask to fire against torn-down state. The drain is
404
+ * bounded (a pass cap and the abort signal); in the pathological case where it
405
+ * cannot converge, anything still queued is discarded by the synchronous
406
+ * teardown that always follows. Prefer this over the synchronous
407
+ * {@link Engine[Symbol.dispose]} in async contexts and tests.
408
+ */
317
409
  [Symbol.asyncDispose](): Promise<void>;
318
410
  get storage(): WeftStorage;
319
411
  get scheduler(): Scheduler;
@@ -3,6 +3,7 @@ import {
3
3
  ActivityRegistry
4
4
  } from "../activity-registry.js";
5
5
  import { AtomicState } from "../atomic-state.js";
6
+ import { deserializeCheckpoint } from "../checkpoint.js";
6
7
  import { createHandleCacheFinalizer } from "../engine-helpers.js";
7
8
  import { ReviewCoordinator } from "../review/index.js";
8
9
  import { Scheduler } from "../scheduler.js";
@@ -63,6 +64,8 @@ import {
63
64
  import {
64
65
  createCleanupIntervalTick,
65
66
  createQueuedInlineWorkflowStartHandler,
67
+ createSecondInstanceDetectorResolver,
68
+ drainQueuedInlineWorkflowStartsForEngine,
66
69
  isActivityDefinition
67
70
  } from "./engine-runtime-helpers.js";
68
71
  import { EngineCreateNameMismatchError } from "./errors.js";
@@ -70,7 +73,7 @@ import {
70
73
  createWorkflowHandleWithResultPromise as createWorkflowHandleWithResultPromiseFromInternals,
71
74
  getWorkflowResultPromise as getWorkflowResultPromiseFromInternals
72
75
  } from "./handle-result.js";
73
- import { HANDLE_RESULT_PROMISE, ScheduleHandle, WorkflowHandle } from "./handles.js";
76
+ import { HANDLE_RESULT_PROMISE, WorkflowHandle } from "./handles.js";
74
77
  import { hasQueuedInlineWorkflowStart } from "./inline-launch-queue.js";
75
78
  import {
76
79
  handleStrategyMessage as handleStrategyMessageFromInternals,
@@ -81,6 +84,8 @@ import {
81
84
  fork as forkFromLifecycle,
82
85
  recoverAll as recoverAllFromLifecycle,
83
86
  resume as resumeFromLifecycle,
87
+ startOrSignal as startOrSignalFromLifecycle,
88
+ startWithIdempotency as startWithIdempotencyFromLifecycle,
84
89
  startWorkflow as startWorkflowFromLifecycle
85
90
  } from "./lifecycle.js";
86
91
  import {
@@ -114,6 +119,7 @@ import {
114
119
  listReviews as listReviewsFromInternals,
115
120
  submitReview as submitReviewFromInternals
116
121
  } from "./reviews.js";
122
+ import { ScheduleHandle } from "./schedule-handle.js";
117
123
  import {
118
124
  cancelSchedule as cancelScheduleFromInternals,
119
125
  listSchedules as listSchedulesFromInternals,
@@ -123,6 +129,10 @@ import {
123
129
  toScheduleSummary,
124
130
  updateSchedule as updateScheduleFromInternals
125
131
  } from "./schedules.js";
132
+ import {
133
+ createSecondInstanceDetectionTick,
134
+ createSecondInstanceDetector
135
+ } from "./second-instance-detector.js";
126
136
  import { signal as signalWorkflow } from "./signals.js";
127
137
  import { loadScheduleState, loadWorkflowState } from "./storage-io.js";
128
138
  import {
@@ -134,6 +144,7 @@ import {
134
144
  cancelWorkflow as cancelWorkflowFromTermination,
135
145
  cleanupWaiters as cleanupWaitersFromTermination,
136
146
  finalizePendingTimelineEntry,
147
+ suspendWorkflow as suspendWorkflowFromTermination,
137
148
  timeoutWorkflow as timeoutWorkflowFromTermination
138
149
  } from "./termination.js";
139
150
  import {
@@ -159,13 +170,17 @@ export {
159
170
  BulkOperationConfirmationError,
160
171
  EngineCreateNameMismatchError,
161
172
  EngineDisposedError,
173
+ IdempotencyKeyPurgedError,
162
174
  PersistedDataIncompatibleError,
175
+ StartOrSignalConflictError,
163
176
  WorkflowAlreadyExistsError,
164
177
  WorkflowNotFoundError,
165
178
  WorkflowNotRegisteredError,
179
+ WorkflowSuspendNotSupportedError,
166
180
  WorkflowTypeNotRegisteredForRecoveryError
167
181
  } from "./errors.js";
168
- export { HANDLE_RESULT_PROMISE, ScheduleHandle, WorkflowHandle } from "./handles.js";
182
+ export { HANDLE_RESULT_PROMISE, WorkflowHandle } from "./handles.js";
183
+ export { ScheduleHandle } from "./schedule-handle.js";
169
184
  export {
170
185
  clearEngineLeakWarningTokenForTesting,
171
186
  getEngineLeakCollectionCountForTesting,
@@ -175,13 +190,13 @@ export {
175
190
  shouldEmitEngineLeakWarningForTesting
176
191
  } from "./engine-leak-warnings.js";
177
192
  export { assertCompatiblePersistedDataVersion };
178
- export const ENGINE_PARKED_WORKFLOW_COUNT_FOR_TESTING = Symbol("engineParkedWorkflowCountForTesting"), ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING = Symbol("engineSignalWaiterCountForTesting");
193
+ export const ENGINE_PARKED_WORKFLOW_COUNT_FOR_TESTING = Symbol("engineParkedWorkflowCountForTesting"), ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING = Symbol("engineSignalWaiterCountForTesting"), ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING = Symbol("engineSleepResolverCountForTesting");
179
194
 
180
195
  export class Engine extends EventTarget {
181
196
  static async create(options) {
182
197
  const engine = new Engine(options);
183
198
  try {
184
- await assertCompatiblePersistedDataVersion(getInternals(engine).storage, options.allowLegacyData === void 0 ? {} : { allowLegacyData: options.allowLegacyData });
199
+ await assertCompatiblePersistedDataVersion(getInternals(engine).storage);
185
200
  for (const [name, definition] of definitionEntries(options.activities)) {
186
201
  if (name !== definition.name)
187
202
  throw new EngineCreateNameMismatchError("activity", name, definition.name);
@@ -283,6 +298,7 @@ export class Engine extends EventTarget {
283
298
  const cleanupIntervalDisposalTracker = {
284
299
  disposed: !1,
285
300
  cleanupInterval: null,
301
+ secondInstanceDetectionInterval: null,
286
302
  testToken: consumeNextEngineLeakWarningTokenForTesting()
287
303
  }, cleanupInterval = setInterval(createCleanupIntervalTick(weakEngine, cleanupIntervalDisposalTracker), 60000);
288
304
  cleanupIntervalDisposalTracker.cleanupInterval = cleanupInterval;
@@ -292,6 +308,8 @@ export class Engine extends EventTarget {
292
308
  getInternals(this).retentionSweepInterval = null;
293
309
  getInternals(this).retentionSweepInFlight = null;
294
310
  getInternals(this).nextRetentionSweepAt = null;
311
+ getInternals(this).secondInstanceDetectionInterval = null;
312
+ getInternals(this).secondInstanceDetector = null;
295
313
  getInternals(this).eventLogHeads = new Map;
296
314
  getInternals(this).workflowFeedListeners = new Map;
297
315
  getInternals(this).workflowVersionTuples = new Map;
@@ -299,6 +317,25 @@ export class Engine extends EventTarget {
299
317
  getInternals(this).strategy.onMessage(this.#handleStrategyMessage.bind(this));
300
318
  getInternals(this).alertManager = createAlertManagerForEngine(this, options?.alerts, getNow);
301
319
  this.#ensureRetentionSweepInterval();
320
+ this.#startSecondInstanceDetection();
321
+ }
322
+ #startSecondInstanceDetection() {
323
+ const internals = getInternals(this);
324
+ if (!internals.options.secondInstanceDetectionEnabled)
325
+ return;
326
+ const detector = createSecondInstanceDetector({
327
+ storage: internals.storage,
328
+ instanceId: crypto.randomUUID(),
329
+ getNow: internals.options.getNow,
330
+ intervalMs: internals.options.secondInstanceHeartbeatIntervalMs
331
+ });
332
+ internals.secondInstanceDetector = detector;
333
+ const tracker = internals.cleanupIntervalDisposalTracker;
334
+ if (tracker === null)
335
+ return;
336
+ const weakEngine = new WeakRef(this), detectionInterval = setInterval(createSecondInstanceDetectionTick(createSecondInstanceDetectorResolver(weakEngine), tracker), internals.options.secondInstanceHeartbeatIntervalMs);
337
+ internals.secondInstanceDetectionInterval = detectionInterval;
338
+ tracker.secondInstanceDetectionInterval = detectionInterval;
302
339
  }
303
340
  #hasConfiguredRetention() {
304
341
  return hasConfiguredRetention(getInternals(this));
@@ -400,8 +437,19 @@ export class Engine extends EventTarget {
400
437
  return getInternals(this).activityRegistry.listDefinitions();
401
438
  }
402
439
  async start(type, input, options) {
440
+ if (options?.idempotencyKey !== void 0)
441
+ return startWithIdempotencyFromLifecycle(getInternals(this), type, input, options, this.#createLifecycleCallbacks());
403
442
  return startWorkflowFromLifecycle(getInternals(this), type, input, options, void 0, this.#createLifecycleCallbacks());
404
443
  }
444
+ async startOrSignal(type, input, signal, options) {
445
+ return startOrSignalFromLifecycle(getInternals(this), type, input, signal, options, this.#createStartOrSignalCallbacks());
446
+ }
447
+ #createStartOrSignalCallbacks() {
448
+ return {
449
+ ...this.#createLifecycleCallbacks(),
450
+ signalExistingWorkflow: (workflowId, signalName, payload, signalId) => this.signal(workflowId, signalName, payload, { signalId })
451
+ };
452
+ }
405
453
  getHandle(workflowId) {
406
454
  const entry = getInternals(this).handleCache.get(workflowId);
407
455
  if (entry) {
@@ -481,6 +529,9 @@ export class Engine extends EventTarget {
481
529
  [ENGINE_SIGNAL_WAITER_COUNT_FOR_TESTING]() {
482
530
  return getInternals(this).signalWaiters.size;
483
531
  }
532
+ [ENGINE_SLEEP_RESOLVER_COUNT_FOR_TESTING]() {
533
+ return getInternals(this).sleepResolvers.size;
534
+ }
484
535
  async signal(workflowId, nameOrDefinition, payload, options) {
485
536
  return signalWorkflow(getInternals(this), workflowId, messageName(nameOrDefinition), payload, {
486
537
  loadWorkflowState: (id) => loadWorkflowState(getInternals(this), id),
@@ -521,6 +572,9 @@ export class Engine extends EventTarget {
521
572
  async cancel(workflowId) {
522
573
  await cancelWorkflowFromTermination(getInternals(this), workflowId, this.#createTerminationCallbacks());
523
574
  }
575
+ async suspend(workflowId) {
576
+ await suspendWorkflowFromTermination(getInternals(this), workflowId, this.#createTerminationCallbacks());
577
+ }
524
578
  async timeout(workflowId) {
525
579
  await timeoutWorkflowFromTermination(getInternals(this), workflowId, this.#createTerminationCallbacks());
526
580
  }
@@ -530,6 +584,15 @@ export class Engine extends EventTarget {
530
584
  return { ...state, status: "pending" };
531
585
  return state;
532
586
  }
587
+ async getCurrentCheckpointStep(workflowId) {
588
+ const inMemory = getInternals(this).checkpoints.get(workflowId);
589
+ if (inMemory !== void 0)
590
+ return inMemory.step;
591
+ const bytes = await getInternals(this).storage.get(KEYS.checkpoint(workflowId));
592
+ if (bytes === null)
593
+ return null;
594
+ return deserializeCheckpoint(bytes).step;
595
+ }
533
596
  async getAttributes(workflowId) {
534
597
  return getWorkflowAttributes(getInternals(this), workflowId);
535
598
  }
@@ -587,6 +650,14 @@ export class Engine extends EventTarget {
587
650
  disposeEngine(getInternals(this));
588
651
  }
589
652
  async[Symbol.asyncDispose]() {
653
+ if (!getInternals(this).disposed) {
654
+ try {
655
+ await drainQueuedInlineWorkflowStartsForEngine(this);
656
+ } finally {
657
+ this[Symbol.dispose]();
658
+ }
659
+ return;
660
+ }
590
661
  this[Symbol.dispose]();
591
662
  }
592
663
  get storage() {
@@ -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>;
@@ -127,13 +134,14 @@ export interface EngineInternals {
127
134
  pendingScheduleCreations: Set<string>;
128
135
  workflowsNeedingTerminalCleanup: Set<string>;
129
136
  cleanupInterval: ReturnType<typeof setInterval> | null;
130
- cleanupIntervalDisposalTracker: {
131
- disposed: boolean;
132
- cleanupInterval: ReturnType<typeof setInterval> | null;
133
- } | null;
137
+ cleanupIntervalDisposalTracker: EngineCleanupIntervalDisposalTracker | null;
134
138
  retentionSweepInterval: ReturnType<typeof setInterval> | null;
135
139
  retentionSweepInFlight: Promise<void> | null;
136
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;
137
145
  reviewCoordinator: ReviewCoordinator;
138
146
  reviewWaiters: Map<string, (decision: HumanReviewResult) => void>;
139
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
  }