@fluidgrids/workflows 2026.1028.2 → 2026.1029.1

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 (118) hide show
  1. package/README.md +72 -10
  2. package/dist/{activation-DnhG7SmF.d.ts → activation-BnVQMv9y.d.ts} +1 -1
  3. package/dist/{activation-BBh1p82z.d.cts → activation-CESehfYi.d.cts} +1 -1
  4. package/dist/{chunk-XHXIDVEC.js → chunk-25BKWR2O.js} +1 -1
  5. package/dist/{chunk-UK3WM7YZ.cjs → chunk-2B37XAFQ.cjs} +44 -8
  6. package/dist/{chunk-DJYMDLV5.js → chunk-2DDULZFM.js} +93 -1
  7. package/dist/{chunk-623FVQSN.cjs → chunk-3FIDHONL.cjs} +104 -0
  8. package/dist/chunk-6QPOJZT6.cjs +1180 -0
  9. package/dist/chunk-7ILGCUI6.js +154 -0
  10. package/dist/chunk-A2OHX3UQ.js +1156 -0
  11. package/dist/{chunk-VOKMAAJP.js → chunk-A36CCOWS.js} +188 -19
  12. package/dist/{chunk-FMSW2HDF.cjs → chunk-ELL2RBML.cjs} +0 -14
  13. package/dist/{chunk-JD2VQIDZ.cjs → chunk-EWYADREB.cjs} +2 -2
  14. package/dist/{chunk-HWJZVOG7.js → chunk-FQSJI5NV.js} +1 -1
  15. package/dist/{chunk-ABFZWYHT.js → chunk-FWBRQWRX.js} +156 -48
  16. package/dist/{chunk-PZCVF76Q.cjs → chunk-I47EENR4.cjs} +209 -40
  17. package/dist/chunk-J3PYFGKA.cjs +166 -0
  18. package/dist/{chunk-C5OLWZ42.js → chunk-KXJDRQ6G.js} +1 -13
  19. package/dist/chunk-M272AHEX.js +25 -0
  20. package/dist/{chunk-VRHHBPT6.js → chunk-MJ5TGYVG.js} +333 -65
  21. package/dist/{chunk-SAOBPYYJ.cjs → chunk-NXFBVDOI.cjs} +3 -3
  22. package/dist/{chunk-XKJA7KX3.js → chunk-OIX4O6UR.js} +38 -2
  23. package/dist/{chunk-FEW3JBYA.cjs → chunk-OKX3FYIM.cjs} +3 -3
  24. package/dist/{chunk-M4BBAERY.cjs → chunk-OMY3CKUZ.cjs} +404 -136
  25. package/dist/chunk-PWIUEWC3.js +81 -0
  26. package/dist/chunk-Q4MWQNJK.cjs +28 -0
  27. package/dist/{chunk-P6TA5EZT.cjs → chunk-RUQIMTN5.cjs} +171 -57
  28. package/dist/{chunk-IISBRUWJ.js → chunk-WXXNCHUY.js} +1 -1
  29. package/dist/chunk-YHBVMOMK.cjs +87 -0
  30. package/dist/codegen/index.d.cts +2 -2
  31. package/dist/codegen/index.d.ts +2 -2
  32. package/dist/collect-BTHjQaSp.d.ts +86 -0
  33. package/dist/collect-CSyOg5XC.d.cts +86 -0
  34. package/dist/{file-BOoHZgoQ.d.cts → file-cgL9l9l8.d.cts} +765 -14
  35. package/dist/{file-yuPCFU5b.d.ts → file-olQudcx5.d.ts} +765 -14
  36. package/dist/history/index.cjs +198 -86
  37. package/dist/history/index.d.cts +49 -10
  38. package/dist/history/index.d.ts +49 -10
  39. package/dist/history/index.js +49 -4
  40. package/dist/index.cjs +64 -51
  41. package/dist/index.d.cts +9 -53
  42. package/dist/index.d.ts +9 -53
  43. package/dist/index.js +5 -4
  44. package/dist/ir/index.cjs +24 -3
  45. package/dist/ir/index.d.cts +43 -4
  46. package/dist/ir/index.d.ts +43 -4
  47. package/dist/ir/index.js +24 -3
  48. package/dist/{lower-BzmUz0sq.d.cts → lower-3S6PsqjJ.d.cts} +40 -6
  49. package/dist/{lower-_-iT4HYs.d.ts → lower-DmZpQ8hJ.d.ts} +40 -6
  50. package/dist/{manifest-C8N6HDYl.d.ts → manifest-BJjrQt77.d.ts} +1 -1
  51. package/dist/{manifest-0OLw0i7k.d.cts → manifest-gRI2Q5Fj.d.cts} +1 -1
  52. package/dist/node-evaluator-SOADN23V.js +14 -0
  53. package/dist/node-evaluator-YGDEXK4U.cjs +24 -0
  54. package/dist/quickjs/index.cjs +25 -24
  55. package/dist/quickjs/index.d.cts +6 -6
  56. package/dist/quickjs/index.d.ts +6 -6
  57. package/dist/quickjs/index.js +8 -7
  58. package/dist/roundtrip/index.cjs +245 -43
  59. package/dist/roundtrip/index.d.cts +86 -9
  60. package/dist/roundtrip/index.d.ts +86 -9
  61. package/dist/roundtrip/index.js +172 -8
  62. package/dist/runtime/index.cjs +21 -20
  63. package/dist/runtime/index.d.cts +49 -7
  64. package/dist/runtime/index.d.ts +49 -7
  65. package/dist/runtime/index.js +8 -7
  66. package/dist/testing/index.cjs +514 -74
  67. package/dist/testing/index.d.cts +127 -10
  68. package/dist/testing/index.d.ts +127 -10
  69. package/dist/testing/index.js +480 -42
  70. package/dist/{types-BGoPzgcl.d.ts → types-Bb2wRnJ5.d.cts} +20 -1
  71. package/dist/{types-BGoPzgcl.d.cts → types-Bb2wRnJ5.d.ts} +20 -1
  72. package/dist/{types-DV-HX_Gt.d.ts → types-CQKOhPTa.d.cts} +122 -14
  73. package/dist/{types-CpBBjaGX.d.cts → types-CnIOinoz.d.ts} +122 -14
  74. package/dist/{workflow-Bpr3vnA_.d.ts → workflow-8uRU9rhL.d.ts} +2 -2
  75. package/dist/{workflow-FXUkpIBc.d.cts → workflow-CZOXUlQ5.d.cts} +2 -2
  76. package/dist/{workflow-C2a05WUJ.d.ts → workflow-DWTPHFbt.d.ts} +124 -12
  77. package/dist/{workflow-J-yYixny.d.cts → workflow-O6e64ZcG.d.cts} +124 -12
  78. package/fixtures/histories/auto-upgrade-patched.history.json +276 -0
  79. package/fixtures/histories/benign-failure.history.json +146 -0
  80. package/fixtures/histories/cancel-activity-try-cancel.history.json +150 -0
  81. package/fixtures/histories/child-cancel-wait-requested.history.json +217 -0
  82. package/fixtures/histories/child-options-roundtrip.history.json +192 -0
  83. package/fixtures/histories/continue-as-new-last-result.history.json +86 -0
  84. package/fixtures/histories/continue-as-new-options.history.json +99 -0
  85. package/fixtures/histories/continue-as-new-r1-long-task-queue.history.json +78 -0
  86. package/fixtures/histories/continue-as-new-r1-retry-options.history.json +83 -0
  87. package/fixtures/histories/execution-timeout-chain.history.json +98 -0
  88. package/fixtures/histories/external-cancel-resolved.history.json +123 -0
  89. package/fixtures/histories/external-cancel-unknown-target.history.json +127 -0
  90. package/fixtures/histories/failure-error-types-fails-run.history.json +144 -0
  91. package/fixtures/histories/handler-drain-warn.history.json +222 -0
  92. package/fixtures/histories/heartbeat-details-on-retry.history.json +145 -0
  93. package/fixtures/histories/local-activity-abandoned-at-completion.history.json +73 -0
  94. package/fixtures/histories/local-activity-cancelled-in-scope.history.json +118 -0
  95. package/fixtures/histories/local-activity-completed.history.json +157 -0
  96. package/fixtures/histories/local-activity-failed.history.json +93 -0
  97. package/fixtures/histories/local-activity-retry-in-task.history.json +88 -0
  98. package/fixtures/histories/run-retry-last-failure.history.json +90 -0
  99. package/fixtures/histories/run-timeout.history.json +159 -0
  100. package/fixtures/histories/schedule-to-start-timeout.history.json +132 -0
  101. package/fixtures/histories/start-delay-signal-ends-delay.history.json +155 -0
  102. package/fixtures/histories/typeerror-task-retry-100.history.json +137 -0
  103. package/package.json +1 -1
  104. package/schema/history-file.schema.json +283 -2
  105. package/schema/ir-v2.schema.json +45 -0
  106. package/schema/run-event.schema.json +264 -2
  107. package/schema/workflow-command.schema.json +178 -1
  108. package/schema/workflow-task.schema.json +392 -4
  109. package/dist/assemble-BLqQ8lSu.d.cts +0 -39
  110. package/dist/assemble-DWgaXHd-.d.ts +0 -39
  111. package/dist/chunk-2J6TF7YI.cjs +0 -86
  112. package/dist/chunk-BO6OVILK.js +0 -79
  113. package/dist/chunk-DHTLALBL.cjs +0 -80
  114. package/dist/chunk-I5LR3D2K.js +0 -869
  115. package/dist/chunk-N23SKIG4.js +0 -73
  116. package/dist/chunk-QQ6ULXHI.cjs +0 -885
  117. package/dist/node-evaluator-4MSDKWOG.cjs +0 -22
  118. package/dist/node-evaluator-SX4LEYQC.js +0 -12
@@ -1,20 +1,21 @@
1
- import { failedTaskResult } from '../chunk-IISBRUWJ.js';
2
- import { isTier1Workflow, toIR } from '../chunk-HWJZVOG7.js';
1
+ import { failedTaskResult } from '../chunk-WXXNCHUY.js';
2
+ import { isTier1Workflow, toIR } from '../chunk-FQSJI5NV.js';
3
3
  import { irHash, CODE_WORKFLOW_NODE_TYPE, normalizeDefinition } from '../chunk-BD7ZPG53.js';
4
4
  import '../chunk-5LUIDMF4.js';
5
5
  import '../chunk-JZ4TOOLN.js';
6
6
  import '../chunk-E2VK7J2C.js';
7
7
  import '../chunk-7AAZGVKD.js';
8
- import { createHistoryFile, validateCommands, parseHistoryFile } from '../chunk-VOKMAAJP.js';
8
+ import { createHistoryFile, validateCommands, parseHistoryFile } from '../chunk-A36CCOWS.js';
9
9
  import { isRecord } from '../chunk-PMCM2OH4.js';
10
- import { buildWorkflowRegistry, createWorkflowRuntime } from '../chunk-VRHHBPT6.js';
11
- import { failureToError, signalName, toMs, errorToFailure, ApplicationError, CancelledFailure, DeterminismViolation } from '../chunk-C5OLWZ42.js';
12
- import '../chunk-LNUQE6D6.js';
10
+ import { buildWorkflowRegistry, createWorkflowRuntime } from '../chunk-MJ5TGYVG.js';
11
+ import { failureToError, signalName, ApplicationError, errorToFailure, CancelledFailure, DeterminismViolation } from '../chunk-KXJDRQ6G.js';
12
+ import '../chunk-M272AHEX.js';
13
13
  import { TASK_PRELUDE } from '../chunk-4LYSEANA.js';
14
- import { isDurableWorkflow } from '../chunk-N23SKIG4.js';
15
- import { initialRunState, applyEvent, eventsForCommands, isActivityOpen, computeRetryDelay, isChildOpen } from '../chunk-ABFZWYHT.js';
16
- import { isInputEvent, HISTORY_PROTOCOL, TERMINAL_COMMANDS } from '../chunk-DJYMDLV5.js';
17
- import '../chunk-BO6OVILK.js';
14
+ import { runRetryPolicyOf, toMs, isDurableWorkflow } from '../chunk-7ILGCUI6.js';
15
+ import '../chunk-LNUQE6D6.js';
16
+ import { initialRunState, applyEvent, isActivityOpen, eventsForCommands, continueAsNewRetryShape, isChildOpen } from '../chunk-FWBRQWRX.js';
17
+ import { RUNTIME_RELEASE, isInputEvent, HISTORY_PROTOCOL, TERMINAL_COMMANDS } from '../chunk-2DDULZFM.js';
18
+ import { computeRetryDelay, computeRunRetryDelay } from '../chunk-PWIUEWC3.js';
18
19
  import { createContext, Script } from 'vm';
19
20
 
20
21
  // src/testing/host.ts
@@ -38,6 +39,32 @@ async function runWorkflowTaskInProcess(registry, request) {
38
39
  }
39
40
 
40
41
  // src/testing/environment.ts
42
+ var WorkflowAlreadyStartedError = class extends Error {
43
+ constructor(workflowKey, workflowId, existingRunId) {
44
+ super(
45
+ `Workflow ${workflowKey} already has a run with workflow id "${workflowId}" (${existingRunId})`
46
+ );
47
+ this.workflowKey = workflowKey;
48
+ this.workflowId = workflowId;
49
+ this.existingRunId = existingRunId;
50
+ this.name = "WorkflowAlreadyStartedError";
51
+ }
52
+ workflowKey;
53
+ workflowId;
54
+ existingRunId;
55
+ };
56
+ var UnfinishedHandlersError = class extends Error {
57
+ constructor(runId, handlers) {
58
+ super(
59
+ `Run ${runId} finished while handlers were still running: ${handlers.map((handler) => `${handler.kind} ${handler.name} x${handler.count}`).join(", ")} (await ctx.allHandlersFinished(), or set unfinishedPolicy: 'ABANDON')`
60
+ );
61
+ this.runId = runId;
62
+ this.handlers = handlers;
63
+ this.name = "UnfinishedHandlersError";
64
+ }
65
+ runId;
66
+ handlers;
67
+ };
41
68
  var WorkflowFailedError = class extends Error {
42
69
  constructor(runId, failure) {
43
70
  super(`Run ${runId} failed: ${failure.message}`);
@@ -113,6 +140,15 @@ function toDate(value) {
113
140
  if (value instanceof Date) return value.getTime();
114
141
  return typeof value === "number" ? value : Date.parse(value);
115
142
  }
143
+ function optionalDuration(value, what) {
144
+ return value === void 0 ? void 0 : toMs(value, what);
145
+ }
146
+ var FAILED_STATUSES = /* @__PURE__ */ new Set([
147
+ "FAILED",
148
+ "CANCELLED",
149
+ "TERMINATED",
150
+ "TIMED_OUT"
151
+ ]);
116
152
  function toJsonValue(value) {
117
153
  if (value === void 0) return null;
118
154
  const text = JSON.stringify(value);
@@ -122,6 +158,10 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
122
158
  clock;
123
159
  timeSkipping;
124
160
  enabled;
161
+ failOnUnfinishedHandlers;
162
+ historyRelease;
163
+ dues = [];
164
+ held = /* @__PURE__ */ new Set();
125
165
  workspaceId;
126
166
  runs = /* @__PURE__ */ new Map();
127
167
  runOrder = [];
@@ -139,6 +179,8 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
139
179
  this.clock = toDate(options.startTime);
140
180
  this.timeSkipping = options.timeSkipping !== false;
141
181
  this.enabled = options.enabled ?? "parity";
182
+ this.failOnUnfinishedHandlers = options.failOnUnfinishedHandlers !== false;
183
+ this.historyRelease = options.historyRelease ?? RUNTIME_RELEASE;
142
184
  this.workspaceId = options.workspaceId ?? "test-workspace";
143
185
  this.workflows = { ...options.workflows ?? {} };
144
186
  this.registry = registryOf(this.workflows);
@@ -162,6 +204,22 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
162
204
  impl
163
205
  );
164
206
  }
207
+ /**
208
+ * Hold the activity queue of a node type (every node type when omitted):
209
+ * attempts wait instead of starting, and a `scheduleToStartTimeout` expires
210
+ * (`ACTIVITY_TIMED_OUT { timeoutType: SCHEDULE_TO_START }`, not retried).
211
+ */
212
+ holdActivities(nodeType = "*") {
213
+ this.held.add(nodeType);
214
+ }
215
+ /** Release held activity queues (all when no node type is given). */
216
+ releaseActivities(nodeType) {
217
+ if (nodeType === void 0) this.held.clear();
218
+ else this.held.delete(nodeType);
219
+ }
220
+ isHeld(nodeType) {
221
+ return this.held.has("*") || this.held.has(nodeType);
222
+ }
165
223
  /** Mock a child workflow started by key (graph or durable). */
166
224
  mockChild(workflowKey, impl) {
167
225
  this.childMocks.set(workflowKey, impl);
@@ -172,17 +230,85 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
172
230
  // A declared workflow is stored by its declaration; its input type is the caller's.
173
231
  this.ensureRegistered(definition)
174
232
  );
233
+ const declared = this.workflows[workflowFn]?.options ?? {};
234
+ const workflowKey = options.workflowKey ?? (typeof definition === "string" ? this.workflows[workflowFn]?.name ?? workflowFn : definition.name);
235
+ if (options.workflowId !== void 0) {
236
+ const existing = this.useExisting(workflowKey, options);
237
+ if (existing !== void 0) {
238
+ for (const signal of options.signals ?? [])
239
+ this.deliverSignal(existing, signal.name, signal.payload);
240
+ await this.runUntilIdle(false);
241
+ return new TestRunHandle(this, existing);
242
+ }
243
+ }
244
+ const executionTimeoutMs = optionalDuration(
245
+ options.executionTimeout ?? declared.executionTimeout,
246
+ "executionTimeout"
247
+ );
248
+ const retry = options.retry ?? declared.retry;
249
+ const config = {
250
+ workflowKey,
251
+ runAttempt: 1,
252
+ ...options.workflowId !== void 0 ? { businessId: options.workflowId } : {},
253
+ ...executionTimeoutMs !== void 0 ? { executionTimeoutMs, executionDeadline: this.clock + executionTimeoutMs } : {},
254
+ ...this.durationConfig(options.runTimeout ?? declared.runTimeout, "runTimeoutMs"),
255
+ ...this.durationConfig(
256
+ options.workflowTaskTimeout ?? declared.workflowTaskTimeout,
257
+ "workflowTaskTimeoutMs"
258
+ ),
259
+ ...this.durationConfig(options.startDelay, "startDelayMs"),
260
+ ...retry !== void 0 ? { retry: runRetryPolicyOf(retry) } : {},
261
+ ...options.summary !== void 0 ? { summary: options.summary } : {},
262
+ ...options.details !== void 0 ? { details: options.details } : {},
263
+ ...options.memo !== void 0 ? { memo: toJsonValue(options.memo) } : {},
264
+ ...(options.versioningBehavior ?? declared.versioningBehavior) !== void 0 ? { versioningBehavior: options.versioningBehavior ?? declared.versioningBehavior } : {}
265
+ };
175
266
  const runId = this.createRun(
176
267
  workflowFn,
177
268
  toJsonValue(options.input),
178
269
  { ...options.info, ...options.runId !== void 0 ? { runId: options.runId } : {} },
179
- void 0
270
+ void 0,
271
+ {},
272
+ config
180
273
  );
181
274
  for (const signal of options.signals ?? [])
182
275
  this.deliverSignal(runId, signal.name, signal.payload);
183
276
  await this.runUntilIdle(false);
184
277
  return new TestRunHandle(this, runId);
185
278
  }
279
+ durationConfig(value, key) {
280
+ const ms = optionalDuration(value, key);
281
+ if (ms === void 0) return {};
282
+ const out = {};
283
+ out[key] = ms;
284
+ return out;
285
+ }
286
+ /**
287
+ * The workflow-id policies of a start (11 §2.1): the open run to use, or
288
+ * undefined to start a new run (after terminating the open one for
289
+ * `TERMINATE_EXISTING`). Throws {@link WorkflowAlreadyStartedError}.
290
+ */
291
+ useExisting(workflowKey, options) {
292
+ const workflowId = options.workflowId ?? "";
293
+ const runs = this.runOrder.map((id) => this.run(id)).filter(
294
+ (run) => run.config.workflowKey === workflowKey && run.config.businessId === workflowId
295
+ );
296
+ const open = runs.find((run) => run.state.status === "RUNNING");
297
+ if (open) {
298
+ const conflict = options.idConflictPolicy ?? "FAIL";
299
+ if (conflict === "USE_EXISTING") return open.runId;
300
+ if (conflict === "FAIL")
301
+ throw new WorkflowAlreadyStartedError(workflowKey, workflowId, open.runId);
302
+ this.terminate(open.runId, "ID_CONFLICT");
303
+ return void 0;
304
+ }
305
+ const last = runs[runs.length - 1];
306
+ if (!last) return void 0;
307
+ const reuse = options.idReusePolicy ?? "ALLOW_DUPLICATE";
308
+ if (reuse === "REJECT_DUPLICATE" || reuse === "ALLOW_DUPLICATE_FAILED_ONLY" && !FAILED_STATUSES.has(last.state.status))
309
+ throw new WorkflowAlreadyStartedError(workflowKey, workflowId, last.runId);
310
+ return void 0;
311
+ }
186
312
  /** Advance the clock by `duration`, firing timers and running work as it comes due. */
187
313
  async sleep(duration) {
188
314
  const until = this.clock + toMs(duration, "sleep duration");
@@ -233,7 +359,7 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
233
359
  if (!run) throw new Error(`Unknown run ${runId}`);
234
360
  return run;
235
361
  }
236
- createRun(workflowFn, input, info, parent, extraStarted = {}) {
362
+ createRun(workflowFn, input, info, parent, extraStarted = {}, config = { workflowKey: workflowFn, runAttempt: 1 }) {
237
363
  this.runCounter += 1;
238
364
  const runId = info.runId ?? `run-${this.runCounter}`;
239
365
  const snapshot = {
@@ -254,18 +380,62 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
254
380
  taskCounter: 0,
255
381
  info: snapshot,
256
382
  children: /* @__PURE__ */ new Map(),
383
+ config,
257
384
  ...parent ? { parent } : {}
258
385
  };
386
+ const firstTaskDelay = config.startDelayMs ?? config.firstTaskDelayMs;
387
+ if (firstTaskDelay !== void 0 && firstTaskDelay > 0) {
388
+ run.delayedUntil = this.clock + firstTaskDelay;
389
+ this.dues.push({ runId, kind: "start", at: run.delayedUntil });
390
+ }
391
+ if (config.runTimeoutMs !== void 0)
392
+ this.dues.push({ runId, kind: "runTimeout", at: this.clock + config.runTimeoutMs });
393
+ if (config.executionDeadline !== void 0)
394
+ this.dues.push({ runId, kind: "executionTimeout", at: config.executionDeadline });
259
395
  this.runs.set(runId, run);
260
396
  this.runOrder.push(runId);
261
397
  this.append(run, {
262
398
  type: "WORKFLOW_STARTED",
263
399
  // The service writes the seed of ctx.random()/ctx.uuid() into the start
264
400
  // event so the history replays on its own; it uses the run id.
265
- payload: { workflowFn, input, taskQueue: "managed", randomnessSeed: runId, ...extraStarted }
401
+ payload: {
402
+ workflowFn,
403
+ input,
404
+ taskQueue: config.taskQueue ?? "managed",
405
+ randomnessSeed: runId,
406
+ ...extraStarted,
407
+ ...this.startedFields(config)
408
+ }
266
409
  });
267
410
  return runId;
268
411
  }
412
+ /** The R2 fields of `WORKFLOW_STARTED`, each written only when it is set. */
413
+ startedFields(config) {
414
+ const out = {};
415
+ if (config.businessId !== void 0) {
416
+ out["businessId"] = config.businessId;
417
+ out["workflowId"] = config.businessId;
418
+ }
419
+ if (config.startDelayMs !== void 0) out["startDelayMs"] = config.startDelayMs;
420
+ if (config.retry !== void 0 || config.runAttempt > 1) out["runAttempt"] = config.runAttempt;
421
+ if (config.summary !== void 0) out["summary"] = config.summary;
422
+ if (config.details !== void 0) out["details"] = config.details;
423
+ if (config.memo !== void 0) out["context"] = config.memo;
424
+ const timeouts = {};
425
+ if (config.executionTimeoutMs !== void 0)
426
+ timeouts["executionTimeout"] = config.executionTimeoutMs;
427
+ if (config.runTimeoutMs !== void 0) timeouts["runTimeout"] = config.runTimeoutMs;
428
+ if (config.workflowTaskTimeoutMs !== void 0)
429
+ timeouts["workflowTaskTimeout"] = config.workflowTaskTimeoutMs;
430
+ if (Object.keys(timeouts).length > 0) out["timeouts"] = timeouts;
431
+ if (config.retry !== void 0) out["retry"] = toJsonValue(config.retry);
432
+ if (config.versioningBehavior !== void 0)
433
+ out["versioningBehavior"] = config.versioningBehavior;
434
+ if (config.lastCompletionResult !== void 0 && this.historyRelease >= 2)
435
+ out["lastCompletionResult"] = config.lastCompletionResult;
436
+ if (config.lastFailure !== void 0) out["lastFailure"] = toJsonValue(config.lastFailure);
437
+ return out;
438
+ }
269
439
  append(run, draft) {
270
440
  const event = {
271
441
  seq: run.history.length + 1,
@@ -276,7 +446,8 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
276
446
  };
277
447
  applyEvent(run.state, event);
278
448
  run.history.push(event);
279
- if (isInputEvent(event) && run.state.status === "RUNNING" && run.state.task.state === "NONE") {
449
+ if (run.delayedUntil !== void 0 && event.type === "SIGNAL_RECEIVED") this.endDelay(run);
450
+ if (run.delayedUntil === void 0 && isInputEvent(event) && run.state.status === "RUNNING" && run.state.task.state === "NONE") {
280
451
  run.taskCounter += 1;
281
452
  const taskId = `${run.runId}-task-${run.taskCounter}`;
282
453
  const scheduled = {
@@ -290,6 +461,22 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
290
461
  }
291
462
  return event;
292
463
  }
464
+ /** End a start delay: the first workflow task is scheduled now. */
465
+ endDelay(run) {
466
+ run.delayedUntil = void 0;
467
+ this.dues = this.dues.filter((due) => !(due.runId === run.runId && due.kind === "start"));
468
+ if (run.state.status !== "RUNNING" || run.state.task.state !== "NONE") return;
469
+ run.taskCounter += 1;
470
+ const taskId = `${run.runId}-task-${run.taskCounter}`;
471
+ const scheduled = {
472
+ seq: run.history.length + 1,
473
+ type: "WORKFLOW_TASK_SCHEDULED",
474
+ payload: { taskId },
475
+ createdAt: new Date(this.clock).toISOString()
476
+ };
477
+ applyEvent(run.state, scheduled);
478
+ run.history.push(scheduled);
479
+ }
293
480
  deliverSignal(runId, name, payload) {
294
481
  const run = this.runs.get(runId);
295
482
  if (!run || run.state.status !== "RUNNING") return false;
@@ -311,6 +498,11 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
311
498
  type: "WORKFLOW_CANCEL_REQUESTED",
312
499
  payload: reason !== void 0 ? { reason } : {}
313
500
  });
501
+ if (run.delayedUntil !== void 0) {
502
+ run.delayedUntil = void 0;
503
+ this.append(run, { type: "WORKFLOW_CANCELLED", payload: {} });
504
+ this.onClosed(run);
505
+ }
314
506
  return true;
315
507
  }
316
508
  terminate(runId, reason) {
@@ -330,7 +522,9 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
330
522
  await this.runTask(task);
331
523
  continue;
332
524
  }
333
- const attempt = this.attempts.find((entry) => entry.dueAt <= this.clock);
525
+ const attempt = this.attempts.find(
526
+ (entry) => entry.dueAt <= this.clock && !this.isHeld(entry.nodeType)
527
+ );
334
528
  if (attempt) {
335
529
  this.attempts = this.attempts.filter((entry) => entry !== attempt);
336
530
  await this.runAttempt(attempt);
@@ -349,16 +543,78 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
349
543
  }
350
544
  throw new Error("The test environment did not settle after 100000 steps");
351
545
  }
546
+ /** When a held attempt's schedule-to-start timeout expires, if it has one. */
547
+ heldDeadline(entry) {
548
+ return this.isHeld(entry.nodeType) && entry.scheduleToStart !== void 0 ? entry.dueAt + entry.scheduleToStart : void 0;
549
+ }
352
550
  nextDue() {
353
- const due = [
354
- ...this.timers.map((timer) => timer.fireAt),
355
- ...this.attempts.map((entry) => entry.dueAt)
356
- ];
551
+ const due = [...this.timers.map((timer) => timer.fireAt)];
552
+ for (const entry of this.attempts) {
553
+ if (!this.isHeld(entry.nodeType)) due.push(entry.dueAt);
554
+ const deadline = this.heldDeadline(entry);
555
+ if (deadline !== void 0) due.push(deadline);
556
+ }
557
+ for (const entry of this.dues) due.push(entry.at);
357
558
  return due.length === 0 ? void 0 : Math.min(...due);
358
559
  }
560
+ /** Expire held attempts whose schedule-to-start timeout passed (not retried, 11 §2.3). */
561
+ expireHeld() {
562
+ const expired = this.attempts.filter((entry) => {
563
+ const deadline = this.heldDeadline(entry);
564
+ return deadline !== void 0 && deadline <= this.clock;
565
+ });
566
+ if (expired.length === 0) return false;
567
+ this.attempts = this.attempts.filter((entry) => !expired.includes(entry));
568
+ for (const entry of expired) {
569
+ const run = this.runs.get(entry.runId);
570
+ const activity = run?.state.activities[entry.activityId];
571
+ if (!run || run.state.status !== "RUNNING" || !activity || !isActivityOpen(activity))
572
+ continue;
573
+ this.append(run, {
574
+ type: "ACTIVITY_TIMED_OUT",
575
+ payload: {
576
+ activityId: entry.activityId,
577
+ attempt: entry.attempt,
578
+ timeoutType: "SCHEDULE_TO_START"
579
+ }
580
+ });
581
+ }
582
+ return true;
583
+ }
584
+ /** Fire run-level deadlines: the end of a start delay, run and execution timeouts. */
585
+ fireRunDues() {
586
+ const due = this.dues.filter((entry) => entry.at <= this.clock).sort((a, b) => a.at - b.at);
587
+ if (due.length === 0) return false;
588
+ this.dues = this.dues.filter((entry) => !due.includes(entry));
589
+ for (const entry of due) {
590
+ const run = this.runs.get(entry.runId);
591
+ if (!run || run.state.status !== "RUNNING") continue;
592
+ if (entry.kind === "start") {
593
+ if (run.delayedUntil !== void 0) this.endDelay(run);
594
+ continue;
595
+ }
596
+ const timeoutType = entry.kind === "runTimeout" ? "WORKFLOW_RUN" : "WORKFLOW_EXECUTION";
597
+ const failure = {
598
+ errorType: "TIMEOUT",
599
+ message: timeoutType === "WORKFLOW_RUN" ? "The run timeout expired" : "The execution timeout of the chain expired",
600
+ type: "TimeoutFailure",
601
+ timeoutType,
602
+ source: "engine"
603
+ };
604
+ run.delayedUntil = void 0;
605
+ this.append(run, {
606
+ type: "WORKFLOW_TIMED_OUT",
607
+ payload: { timeoutType, failure: toJsonValue(failure) }
608
+ });
609
+ this.onClosed(run);
610
+ }
611
+ return true;
612
+ }
359
613
  fireDue() {
614
+ const expired = this.expireHeld();
615
+ const fired = this.fireRunDues();
360
616
  const due = this.timers.filter((timer) => timer.fireAt <= this.clock).sort((a, b) => a.fireAt - b.fireAt);
361
- if (due.length === 0) return false;
617
+ if (due.length === 0) return expired || fired;
362
618
  this.timers = this.timers.filter((timer) => !due.includes(timer));
363
619
  for (const timer of due) {
364
620
  const run = this.runs.get(timer.runId);
@@ -379,7 +635,7 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
379
635
  history: run.history,
380
636
  nowMs: this.clock
381
637
  };
382
- const result = this.executor ? await this.executor(request) : await runWorkflowTaskInProcess(this.registry, request);
638
+ const result = await this.executeWithLocalActivities(run, request);
383
639
  const fail = (cause, message) => {
384
640
  this.append(run, {
385
641
  type: "WORKFLOW_TASK_FAILED",
@@ -406,12 +662,69 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
406
662
  fail(drafts.cause, drafts.issues.map((issue) => issue.message).join("; "));
407
663
  return;
408
664
  }
665
+ if (result.unfinishedHandlers !== void 0 && result.unfinishedHandlers.length > 0)
666
+ run.unfinishedHandlers = result.unfinishedHandlers;
409
667
  this.append(run, { type: "WORKFLOW_TASK_COMPLETED", taskId, payload: { taskId } });
410
668
  for (const draft of drafts.events) this.append(run, draft);
411
669
  for (const command of checked.commands)
412
670
  if (run.state.status === "RUNNING" || TERMINAL_COMMANDS.has(command.type))
413
671
  this.afterCommand(run, command);
414
672
  }
673
+ execute(request) {
674
+ return this.executor ? this.executor(request) : runWorkflowTaskInProcess(this.registry, request);
675
+ }
676
+ /**
677
+ * Run a workflow task as the worker does (11 §2.12): while it asks for
678
+ * local activities, run each through its mock and run the same task again
679
+ * with the outcomes, up to 1,000 rounds.
680
+ */
681
+ async executeWithLocalActivities(run, request) {
682
+ const outcomes = /* @__PURE__ */ new Map();
683
+ let result = await this.execute(request);
684
+ for (let round = 0; result.outcome === "localActivityRequested"; round++) {
685
+ if (round >= 1e3)
686
+ return {
687
+ ...result,
688
+ outcome: "failed",
689
+ commands: [],
690
+ failure: { cause: "INTERNAL", message: "The task asked for local activities 1000 times" }
691
+ };
692
+ for (const wanted of result.localActivityRequests ?? [])
693
+ outcomes.set(wanted.localActivityId, await this.runLocalActivity(run, wanted));
694
+ result = await this.execute({ ...request, localActivityResults: [...outcomes.values()] });
695
+ }
696
+ return result;
697
+ }
698
+ async runLocalActivity(run, wanted) {
699
+ const mock = this.mocks.get(`${wanted.nodeType}#${wanted.operation}`) ?? this.mocks.get(wanted.nodeType);
700
+ try {
701
+ if (!mock)
702
+ throw new ApplicationError(
703
+ `No mock for ${wanted.nodeType} ${wanted.operation} (use env.mock)`,
704
+ { errorType: "VALIDATION", nonRetryable: true, type: "UnmockedActivity" }
705
+ );
706
+ const result = toJsonValue(
707
+ await mock(wanted.args, {
708
+ runId: run.runId,
709
+ activityId: wanted.localActivityId,
710
+ attempt: wanted.attempt,
711
+ nodeType: wanted.nodeType,
712
+ operation: wanted.operation,
713
+ local: true,
714
+ ...wanted.connection !== void 0 ? { connection: wanted.connection } : {}
715
+ })
716
+ );
717
+ return { localActivityId: wanted.localActivityId, result, attempts: wanted.attempt };
718
+ } catch (error) {
719
+ const failure = errorToFailure(error, `node:${wanted.nodeType}`);
720
+ delete failure.stackTrace;
721
+ return {
722
+ localActivityId: wanted.localActivityId,
723
+ failure: toJsonValue(failure),
724
+ attempts: wanted.attempt
725
+ };
726
+ }
727
+ }
415
728
  afterCommand(run, command) {
416
729
  switch (command.type) {
417
730
  case "SCHEDULE_ACTIVITY": {
@@ -427,7 +740,8 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
427
740
  retry: command.retry,
428
741
  cancelRequested: false,
429
742
  ...command.connection !== void 0 ? { connection: command.connection } : {},
430
- ...command.scheduleToCloseTimeout !== void 0 ? { scheduleToClose: command.scheduleToCloseTimeout } : {}
743
+ ...command.scheduleToCloseTimeout !== void 0 ? { scheduleToClose: command.scheduleToCloseTimeout } : {},
744
+ ...command.scheduleToStartTimeout !== void 0 ? { scheduleToStart: command.scheduleToStartTimeout } : {}
431
745
  };
432
746
  this.attempts.push(attempt);
433
747
  return;
@@ -502,18 +816,25 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
502
816
  return;
503
817
  }
504
818
  case "CONTINUE_AS_NEW": {
505
- const next = this.createRun(
506
- run.workflowFn,
507
- command.input,
508
- {
509
- ...run.info,
510
- runId: void 0,
511
- continuedFromRunId: run.runId,
512
- firstRunId: run.info.firstRunId ?? run.runId
513
- },
514
- run.parent
515
- );
516
- run.continuedTo = next;
819
+ const options = command.options;
820
+ const config = {
821
+ ...this.chainConfig(run.config),
822
+ // The run timeout restarts and the attempt count resets; the
823
+ // execution timeout of the chain carries over (11 §2.20).
824
+ lastCompletionResult: command.input
825
+ };
826
+ if (typeof options["taskQueue"] === "string") config.taskQueue = options["taskQueue"];
827
+ if (typeof options["workflowTaskTimeout"] === "number")
828
+ config.workflowTaskTimeoutMs = options["workflowTaskTimeout"];
829
+ if (options["versioningBehavior"] === "PINNED" || options["versioningBehavior"] === "AUTO_UPGRADE")
830
+ config.versioningBehavior = options["versioningBehavior"];
831
+ const memo = options["context"];
832
+ if (typeof memo === "object" && memo !== null && !Array.isArray(memo)) config.memo = memo;
833
+ else if (Object.keys(run.state.context).length > 0) config.memo = { ...run.state.context };
834
+ const retry = options["retry"];
835
+ if (continueAsNewRetryShape(options) === "run" && typeof retry === "object" && retry !== null && !Array.isArray(retry))
836
+ config.retry = retry;
837
+ this.startSuccessor(run, command.input, config);
517
838
  this.onClosed(run);
518
839
  return;
519
840
  }
@@ -526,11 +847,62 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
526
847
  return;
527
848
  }
528
849
  }
850
+ /** What a successor of a run inherits (continue-as-new, run retry). */
851
+ chainConfig(config) {
852
+ const out = { workflowKey: config.workflowKey, runAttempt: 1 };
853
+ for (const key of [
854
+ "businessId",
855
+ "executionDeadline",
856
+ "executionTimeoutMs",
857
+ "runTimeoutMs",
858
+ "workflowTaskTimeoutMs",
859
+ "retry",
860
+ "versioningBehavior",
861
+ "taskQueue",
862
+ "memo"
863
+ ])
864
+ if (config[key] !== void 0) Object.assign(out, { [key]: config[key] });
865
+ return out;
866
+ }
867
+ /** Start the next run of a chain; the closed run's parent and handles follow it. */
868
+ startSuccessor(run, input, config) {
869
+ const next = this.createRun(
870
+ run.workflowFn,
871
+ input,
872
+ {
873
+ ...run.info,
874
+ runId: void 0,
875
+ continuedFromRunId: run.runId,
876
+ firstRunId: run.info.firstRunId ?? run.runId
877
+ },
878
+ run.parent,
879
+ {},
880
+ config
881
+ );
882
+ run.continuedTo = next;
883
+ return next;
884
+ }
529
885
  resolveTarget(run, target) {
530
886
  if ("runId" in target) return target.runId;
531
887
  if ("parent" in target) return run.parent?.runId ?? run.info.parentRunId ?? void 0;
532
- if ("childId" in target) return run.children.get(target.childId);
533
- return void 0;
888
+ if ("childId" in target) {
889
+ let childRunId = run.children.get(target.childId);
890
+ while (childRunId !== void 0) {
891
+ const next = this.runs.get(childRunId)?.continuedTo;
892
+ if (next === void 0) break;
893
+ childRunId = next;
894
+ }
895
+ return childRunId;
896
+ }
897
+ return this.resolveWorkflowId(target.workflowKey, target.workflowId, target.firstRunId);
898
+ }
899
+ /** The open run of a workflow id, else its latest run (11 §2.1). */
900
+ resolveWorkflowId(workflowKey, workflowId, firstRunId) {
901
+ const matching = this.runOrder.map((id) => this.run(id)).filter(
902
+ (candidate) => candidate.config.workflowKey === workflowKey && candidate.config.businessId === workflowId && (firstRunId === void 0 || (candidate.info.firstRunId ?? candidate.runId) === firstRunId)
903
+ );
904
+ const open = matching.find((candidate) => candidate.state.status === "RUNNING");
905
+ return (open ?? matching[matching.length - 1])?.runId;
534
906
  }
535
907
  startChild(run, command) {
536
908
  const byFn = command.workflowFn !== void 0 && (this.executor !== void 0 || this.registry.byName.has(command.workflowFn));
@@ -563,6 +935,28 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
563
935
  });
564
936
  return;
565
937
  }
938
+ const options = command.options;
939
+ const ms = (key) => typeof options[key] === "number" ? options[key] : void 0;
940
+ const executionTimeoutMs = ms("executionTimeout");
941
+ const childKey = command.workflowKey ?? this.registry.byName.get(workflowFn)?.name ?? workflowFn;
942
+ const config = { workflowKey: childKey, runAttempt: 1 };
943
+ if (typeof options.workflowId === "string") config.businessId = options.workflowId;
944
+ if (executionTimeoutMs !== void 0) {
945
+ config.executionTimeoutMs = executionTimeoutMs;
946
+ config.executionDeadline = this.clock + executionTimeoutMs;
947
+ }
948
+ const runTimeoutMs = ms("runTimeout");
949
+ if (runTimeoutMs !== void 0) config.runTimeoutMs = runTimeoutMs;
950
+ const taskTimeoutMs = ms("workflowTaskTimeout");
951
+ if (taskTimeoutMs !== void 0) config.workflowTaskTimeoutMs = taskTimeoutMs;
952
+ const startDelayMs = ms("startDelay");
953
+ if (startDelayMs !== void 0) config.startDelayMs = startDelayMs;
954
+ if (typeof options.summary === "string") config.summary = options.summary;
955
+ if (typeof options.details === "string") config.details = options.details;
956
+ if (options.context !== void 0) config.memo = options.context;
957
+ if (options.versioningBehavior !== void 0)
958
+ config.versioningBehavior = options.versioningBehavior;
959
+ if (options.retry !== void 0) config.retry = options.retry;
566
960
  const childRunId = this.createRun(
567
961
  workflowFn,
568
962
  command.input,
@@ -571,7 +965,8 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
571
965
  {
572
966
  parentRunId: run.runId,
573
967
  parentClosePolicy: command.options.parentClosePolicy
574
- }
968
+ },
969
+ config
575
970
  );
576
971
  run.children.set(command.childId, childRunId);
577
972
  this.append(run, {
@@ -628,6 +1023,7 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
628
1023
  const mock = this.mocks.get(`${attempt.nodeType}#${attempt.operation}`) ?? this.mocks.get(attempt.nodeType);
629
1024
  let failure;
630
1025
  let result = null;
1026
+ let heartbeatDetails = attempt.heartbeatDetails;
631
1027
  try {
632
1028
  if (!mock)
633
1029
  throw new ApplicationError(
@@ -645,7 +1041,11 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
645
1041
  attempt: attempt.attempt,
646
1042
  nodeType: attempt.nodeType,
647
1043
  operation: attempt.operation,
648
- ...attempt.connection !== void 0 ? { connection: attempt.connection } : {}
1044
+ ...attempt.connection !== void 0 ? { connection: attempt.connection } : {},
1045
+ ...attempt.heartbeatDetails !== void 0 ? { heartbeatDetails: attempt.heartbeatDetails } : {},
1046
+ heartbeat: (details) => {
1047
+ heartbeatDetails = toJsonValue(details);
1048
+ }
649
1049
  })
650
1050
  );
651
1051
  } catch (error) {
@@ -686,7 +1086,8 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
686
1086
  this.attempts.push({
687
1087
  ...attempt,
688
1088
  attempt: attempt.attempt + 1,
689
- dueAt: this.clock + decision2.delayMs
1089
+ dueAt: this.clock + decision2.delayMs,
1090
+ ...heartbeatDetails !== void 0 ? { heartbeatDetails } : {}
690
1091
  });
691
1092
  return;
692
1093
  }
@@ -695,7 +1096,8 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
695
1096
  this.attempts.push({
696
1097
  ...attempt,
697
1098
  attempt: attempt.attempt + 1,
698
- dueAt: this.clock + decision.delayMs
1099
+ dueAt: this.clock + decision.delayMs,
1100
+ ...heartbeatDetails !== void 0 ? { heartbeatDetails } : {}
699
1101
  });
700
1102
  return;
701
1103
  }
@@ -709,9 +1111,31 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
709
1111
  }
710
1112
  });
711
1113
  }
1114
+ /**
1115
+ * Run-level retry (11 §2.17): a failed or run-timed-out run whose policy
1116
+ * allows another attempt is followed by a new run of the chain after the
1117
+ * backoff, with `runAttempt + 1` and the failure as `lastFailure`.
1118
+ */
1119
+ retryRun(run) {
1120
+ if (run.continuedTo !== void 0) return;
1121
+ const last = run.history[run.history.length - 1];
1122
+ if (!last || last.type !== "WORKFLOW_FAILED" && last.type !== "WORKFLOW_TIMED_OUT") return;
1123
+ const failure = last.payload["failure"] ?? { errorType: "TIMEOUT", message: "timed out", timeoutType: "WORKFLOW_RUN" };
1124
+ const decision = computeRunRetryDelay(run.config.retry, run.config.runAttempt, failure);
1125
+ if (!decision.retry) return;
1126
+ const config = {
1127
+ ...this.chainConfig(run.config),
1128
+ runAttempt: run.config.runAttempt + 1,
1129
+ lastFailure: failure
1130
+ };
1131
+ if (decision.delayMs > 0) config.firstTaskDelayMs = decision.delayMs;
1132
+ this.startSuccessor(run, run.state.input ?? null, config);
1133
+ }
712
1134
  onClosed(run) {
713
1135
  this.timers = this.timers.filter((timer) => timer.runId !== run.runId);
714
1136
  this.attempts = this.attempts.filter((entry) => entry.runId !== run.runId);
1137
+ this.dues = this.dues.filter((entry) => entry.runId !== run.runId);
1138
+ this.retryRun(run);
715
1139
  for (const [childId2, childRunId] of run.children) {
716
1140
  const child = run.state.children[childId2];
717
1141
  if (!child || !isChildOpen(child) || childRunId === void 0) continue;
@@ -766,7 +1190,10 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
766
1190
  default:
767
1191
  this.append(parent, {
768
1192
  type: "CHILD_RUN_TIMED_OUT",
769
- payload: { ...base, timeoutType: "WORKFLOW_RUN" }
1193
+ payload: {
1194
+ ...base,
1195
+ timeoutType: last.payload["timeoutType"] === "WORKFLOW_EXECUTION" ? "WORKFLOW_EXECUTION" : "WORKFLOW_RUN"
1196
+ }
770
1197
  });
771
1198
  }
772
1199
  }
@@ -775,10 +1202,21 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
775
1202
  let run = this.run(runId);
776
1203
  while (run.continuedTo !== void 0) run = this.run(run.continuedTo);
777
1204
  if (run.taskFailure) throw new WorkflowTaskFailedError(run.runId, run.taskFailure);
1205
+ if (this.failOnUnfinishedHandlers && run.unfinishedHandlers !== void 0)
1206
+ throw new UnfinishedHandlersError(run.runId, run.unfinishedHandlers);
778
1207
  const last = run.history[run.history.length - 1];
779
1208
  switch (run.state.status) {
780
1209
  case "COMPLETED":
781
1210
  return run.state.output ?? null;
1211
+ case "TIMED_OUT":
1212
+ throw new WorkflowFailedError(
1213
+ run.runId,
1214
+ last?.payload["failure"] ?? {
1215
+ errorType: "TIMEOUT",
1216
+ message: "timed out",
1217
+ type: "TimeoutFailure"
1218
+ }
1219
+ );
782
1220
  case "FAILED":
783
1221
  throw new WorkflowFailedError(
784
1222
  run.runId,
@@ -1034,4 +1472,4 @@ async function runWorkflowTaskInVm(script, request, options = {}) {
1034
1472
  }
1035
1473
  }
1036
1474
 
1037
- export { HistoryAssertionError, ReplayError, RunBlockedError, TestRunHandle, TestWorkflowEnvironment, WorkflowFailedError, WorkflowTaskFailedError, assertHistory, bundleToScript, drainJobs, expectIR, jsonCopy, registryOf, replay, replayRequest, runWorkflowTaskInProcess, runWorkflowTaskInVm, simulate };
1475
+ export { HistoryAssertionError, ReplayError, RunBlockedError, TestRunHandle, TestWorkflowEnvironment, UnfinishedHandlersError, WorkflowAlreadyStartedError, WorkflowFailedError, WorkflowTaskFailedError, assertHistory, bundleToScript, drainJobs, expectIR, jsonCopy, registryOf, replay, replayRequest, runWorkflowTaskInProcess, runWorkflowTaskInVm, simulate };