@fluidgrids/workflows 2026.1028.1 → 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 (122) 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-XB53G2DK.js → chunk-25BKWR2O.js} +1 -1
  5. package/dist/{chunk-CBL5JSCC.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-5HDSBTBX.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-3FOTFQ3J.js → chunk-MJ5TGYVG.js} +333 -65
  21. package/dist/{chunk-57MSZ24T.cjs → chunk-NXFBVDOI.cjs} +3 -3
  22. package/dist/{chunk-PDHLTDZB.js → chunk-OIX4O6UR.js} +38 -2
  23. package/dist/{chunk-HR3BN4C4.cjs → chunk-OKX3FYIM.cjs} +3 -3
  24. package/dist/{chunk-CSNWNFKU.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-yuPCFU5b.d.ts → file-cgL9l9l8.d.cts} +765 -14
  35. package/dist/{file-BOoHZgoQ.d.cts → 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-BuSvAFqi.d.cts → lower-3S6PsqjJ.d.cts} +41 -7
  49. package/dist/{lower-C_Qv_34S.d.ts → lower-DmZpQ8hJ.d.ts} +41 -7
  50. package/dist/lower-types-DQNBfWQn.d.cts +37 -0
  51. package/dist/lower-types-DQNBfWQn.d.ts +37 -0
  52. package/dist/{manifest-C8N6HDYl.d.ts → manifest-BJjrQt77.d.ts} +1 -1
  53. package/dist/{manifest-0OLw0i7k.d.cts → manifest-gRI2Q5Fj.d.cts} +1 -1
  54. package/dist/node-evaluator-SOADN23V.js +14 -0
  55. package/dist/node-evaluator-YGDEXK4U.cjs +24 -0
  56. package/dist/quickjs/index.cjs +25 -24
  57. package/dist/quickjs/index.d.cts +7 -7
  58. package/dist/quickjs/index.d.ts +7 -7
  59. package/dist/quickjs/index.js +8 -7
  60. package/dist/roundtrip/index.cjs +245 -43
  61. package/dist/roundtrip/index.d.cts +88 -11
  62. package/dist/roundtrip/index.d.ts +88 -11
  63. package/dist/roundtrip/index.js +172 -8
  64. package/dist/runtime/index.cjs +21 -20
  65. package/dist/runtime/index.d.cts +49 -7
  66. package/dist/runtime/index.d.ts +49 -7
  67. package/dist/runtime/index.js +8 -7
  68. package/dist/testing/index.cjs +514 -74
  69. package/dist/testing/index.d.cts +128 -11
  70. package/dist/testing/index.d.ts +128 -11
  71. package/dist/testing/index.js +480 -42
  72. package/dist/{types-BGoPzgcl.d.ts → types-Bb2wRnJ5.d.cts} +20 -1
  73. package/dist/{types-BGoPzgcl.d.cts → types-Bb2wRnJ5.d.ts} +20 -1
  74. package/dist/{types-DV-HX_Gt.d.ts → types-CQKOhPTa.d.cts} +122 -14
  75. package/dist/{types-CpBBjaGX.d.cts → types-CnIOinoz.d.ts} +122 -14
  76. package/dist/{workflow-Bpr3vnA_.d.ts → workflow-8uRU9rhL.d.ts} +2 -2
  77. package/dist/{workflow-FXUkpIBc.d.cts → workflow-CZOXUlQ5.d.cts} +2 -2
  78. package/dist/{workflow-J-yYixny.d.cts → workflow-DWTPHFbt.d.ts} +124 -12
  79. package/dist/{workflow-C2a05WUJ.d.ts → workflow-O6e64ZcG.d.cts} +124 -12
  80. package/fixtures/histories/auto-upgrade-patched.history.json +276 -0
  81. package/fixtures/histories/benign-failure.history.json +146 -0
  82. package/fixtures/histories/cancel-activity-try-cancel.history.json +150 -0
  83. package/fixtures/histories/child-cancel-wait-requested.history.json +217 -0
  84. package/fixtures/histories/child-options-roundtrip.history.json +192 -0
  85. package/fixtures/histories/continue-as-new-last-result.history.json +86 -0
  86. package/fixtures/histories/continue-as-new-options.history.json +99 -0
  87. package/fixtures/histories/continue-as-new-r1-long-task-queue.history.json +78 -0
  88. package/fixtures/histories/continue-as-new-r1-retry-options.history.json +83 -0
  89. package/fixtures/histories/execution-timeout-chain.history.json +98 -0
  90. package/fixtures/histories/external-cancel-resolved.history.json +123 -0
  91. package/fixtures/histories/external-cancel-unknown-target.history.json +127 -0
  92. package/fixtures/histories/failure-error-types-fails-run.history.json +144 -0
  93. package/fixtures/histories/handler-drain-warn.history.json +222 -0
  94. package/fixtures/histories/heartbeat-details-on-retry.history.json +145 -0
  95. package/fixtures/histories/local-activity-abandoned-at-completion.history.json +73 -0
  96. package/fixtures/histories/local-activity-cancelled-in-scope.history.json +118 -0
  97. package/fixtures/histories/local-activity-completed.history.json +157 -0
  98. package/fixtures/histories/local-activity-failed.history.json +93 -0
  99. package/fixtures/histories/local-activity-retry-in-task.history.json +88 -0
  100. package/fixtures/histories/run-retry-last-failure.history.json +90 -0
  101. package/fixtures/histories/run-timeout.history.json +159 -0
  102. package/fixtures/histories/schedule-to-start-timeout.history.json +132 -0
  103. package/fixtures/histories/start-delay-signal-ends-delay.history.json +155 -0
  104. package/fixtures/histories/typeerror-task-retry-100.history.json +137 -0
  105. package/package.json +1 -1
  106. package/schema/history-file.schema.json +283 -2
  107. package/schema/ir-v2.schema.json +45 -0
  108. package/schema/run-event.schema.json +264 -2
  109. package/schema/workflow-command.schema.json +178 -1
  110. package/schema/workflow-task.schema.json +392 -4
  111. package/dist/assemble-BLqQ8lSu.d.cts +0 -39
  112. package/dist/assemble-DWgaXHd-.d.ts +0 -39
  113. package/dist/chunk-2DP2Y6EU.cjs +0 -832
  114. package/dist/chunk-2J6TF7YI.cjs +0 -86
  115. package/dist/chunk-7NQ37GZU.js +0 -73
  116. package/dist/chunk-BO6OVILK.js +0 -79
  117. package/dist/chunk-FQIHX7YI.cjs +0 -80
  118. package/dist/chunk-H7KEVW2M.js +0 -816
  119. package/dist/lower-types-nofRXw9c.d.cts +0 -15
  120. package/dist/lower-types-nofRXw9c.d.ts +0 -15
  121. package/dist/node-evaluator-CJNP4GHX.js +0 -12
  122. package/dist/node-evaluator-DMKPKUWA.cjs +0 -22
@@ -1,22 +1,23 @@
1
1
  'use strict';
2
2
 
3
- var chunkJD2VQIDZ_cjs = require('../chunk-JD2VQIDZ.cjs');
4
- var chunk57MSZ24T_cjs = require('../chunk-57MSZ24T.cjs');
3
+ var chunkEWYADREB_cjs = require('../chunk-EWYADREB.cjs');
4
+ var chunkNXFBVDOI_cjs = require('../chunk-NXFBVDOI.cjs');
5
5
  var chunkGBQ33SIZ_cjs = require('../chunk-GBQ33SIZ.cjs');
6
6
  require('../chunk-G4UEPUGO.cjs');
7
7
  require('../chunk-GVTZMA5C.cjs');
8
8
  require('../chunk-QKZL3ONM.cjs');
9
9
  require('../chunk-TBDXTV22.cjs');
10
- var chunkPZCVF76Q_cjs = require('../chunk-PZCVF76Q.cjs');
10
+ var chunkI47EENR4_cjs = require('../chunk-I47EENR4.cjs');
11
11
  var chunkSAENRNBJ_cjs = require('../chunk-SAENRNBJ.cjs');
12
- var chunkCSNWNFKU_cjs = require('../chunk-CSNWNFKU.cjs');
13
- var chunkFMSW2HDF_cjs = require('../chunk-FMSW2HDF.cjs');
14
- require('../chunk-FR7KYWRO.cjs');
12
+ var chunkOMY3CKUZ_cjs = require('../chunk-OMY3CKUZ.cjs');
13
+ var chunkELL2RBML_cjs = require('../chunk-ELL2RBML.cjs');
14
+ require('../chunk-Q4MWQNJK.cjs');
15
15
  var chunkL24YIZY7_cjs = require('../chunk-L24YIZY7.cjs');
16
- var chunkFQIHX7YI_cjs = require('../chunk-FQIHX7YI.cjs');
17
- var chunkP6TA5EZT_cjs = require('../chunk-P6TA5EZT.cjs');
18
- var chunk623FVQSN_cjs = require('../chunk-623FVQSN.cjs');
19
- require('../chunk-2J6TF7YI.cjs');
16
+ var chunkJ3PYFGKA_cjs = require('../chunk-J3PYFGKA.cjs');
17
+ require('../chunk-FR7KYWRO.cjs');
18
+ var chunkRUQIMTN5_cjs = require('../chunk-RUQIMTN5.cjs');
19
+ var chunk3FIDHONL_cjs = require('../chunk-3FIDHONL.cjs');
20
+ var chunkYHBVMOMK_cjs = require('../chunk-YHBVMOMK.cjs');
20
21
  require('../chunk-Z2Y65YOY.cjs');
21
22
  var vm = require('vm');
22
23
 
@@ -25,7 +26,7 @@ function drainJobs() {
25
26
  return new Promise((resolve) => setImmediate(resolve));
26
27
  }
27
28
  function registryOf(workflows) {
28
- return chunkCSNWNFKU_cjs.buildWorkflowRegistry([["in-process", { ...workflows }]]);
29
+ return chunkOMY3CKUZ_cjs.buildWorkflowRegistry([["in-process", { ...workflows }]]);
29
30
  }
30
31
  async function hostLoop(abi, requestJson) {
31
32
  abi.start(requestJson);
@@ -35,19 +36,45 @@ async function hostLoop(abi, requestJson) {
35
36
  return abi.complete();
36
37
  }
37
38
  async function runWorkflowTaskInProcess(registry, request) {
38
- const abi = chunkCSNWNFKU_cjs.createWorkflowRuntime(registry);
39
+ const abi = chunkOMY3CKUZ_cjs.createWorkflowRuntime(registry);
39
40
  const json = await hostLoop(abi, JSON.stringify(request));
40
41
  return JSON.parse(json);
41
42
  }
42
43
 
43
44
  // src/testing/environment.ts
45
+ var WorkflowAlreadyStartedError = class extends Error {
46
+ constructor(workflowKey, workflowId, existingRunId) {
47
+ super(
48
+ `Workflow ${workflowKey} already has a run with workflow id "${workflowId}" (${existingRunId})`
49
+ );
50
+ this.workflowKey = workflowKey;
51
+ this.workflowId = workflowId;
52
+ this.existingRunId = existingRunId;
53
+ this.name = "WorkflowAlreadyStartedError";
54
+ }
55
+ workflowKey;
56
+ workflowId;
57
+ existingRunId;
58
+ };
59
+ var UnfinishedHandlersError = class extends Error {
60
+ constructor(runId, handlers) {
61
+ super(
62
+ `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')`
63
+ );
64
+ this.runId = runId;
65
+ this.handlers = handlers;
66
+ this.name = "UnfinishedHandlersError";
67
+ }
68
+ runId;
69
+ handlers;
70
+ };
44
71
  var WorkflowFailedError = class extends Error {
45
72
  constructor(runId, failure) {
46
73
  super(`Run ${runId} failed: ${failure.message}`);
47
74
  this.runId = runId;
48
75
  this.failure = failure;
49
76
  this.name = "WorkflowFailedError";
50
- Object.defineProperty(this, "cause", { value: chunkFMSW2HDF_cjs.failureToError(failure), enumerable: false });
77
+ Object.defineProperty(this, "cause", { value: chunkELL2RBML_cjs.failureToError(failure), enumerable: false });
51
78
  }
52
79
  runId;
53
80
  failure;
@@ -89,7 +116,7 @@ var TestRunHandle = class {
89
116
  }
90
117
  /** Deliver a signal and run until idle (without skipping time). */
91
118
  async signal(signal, payload) {
92
- this.env.deliverSignal(this.runId, chunkFMSW2HDF_cjs.signalName(signal), payload);
119
+ this.env.deliverSignal(this.runId, chunkELL2RBML_cjs.signalName(signal), payload);
93
120
  await this.env.runUntilIdle(false);
94
121
  }
95
122
  /** Request cancellation and run until idle (without skipping time). */
@@ -116,6 +143,15 @@ function toDate(value) {
116
143
  if (value instanceof Date) return value.getTime();
117
144
  return typeof value === "number" ? value : Date.parse(value);
118
145
  }
146
+ function optionalDuration(value, what) {
147
+ return value === void 0 ? void 0 : chunkJ3PYFGKA_cjs.toMs(value, what);
148
+ }
149
+ var FAILED_STATUSES = /* @__PURE__ */ new Set([
150
+ "FAILED",
151
+ "CANCELLED",
152
+ "TERMINATED",
153
+ "TIMED_OUT"
154
+ ]);
119
155
  function toJsonValue(value) {
120
156
  if (value === void 0) return null;
121
157
  const text = JSON.stringify(value);
@@ -125,6 +161,10 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
125
161
  clock;
126
162
  timeSkipping;
127
163
  enabled;
164
+ failOnUnfinishedHandlers;
165
+ historyRelease;
166
+ dues = [];
167
+ held = /* @__PURE__ */ new Set();
128
168
  workspaceId;
129
169
  runs = /* @__PURE__ */ new Map();
130
170
  runOrder = [];
@@ -142,6 +182,8 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
142
182
  this.clock = toDate(options.startTime);
143
183
  this.timeSkipping = options.timeSkipping !== false;
144
184
  this.enabled = options.enabled ?? "parity";
185
+ this.failOnUnfinishedHandlers = options.failOnUnfinishedHandlers !== false;
186
+ this.historyRelease = options.historyRelease ?? chunk3FIDHONL_cjs.RUNTIME_RELEASE;
145
187
  this.workspaceId = options.workspaceId ?? "test-workspace";
146
188
  this.workflows = { ...options.workflows ?? {} };
147
189
  this.registry = registryOf(this.workflows);
@@ -165,6 +207,22 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
165
207
  impl
166
208
  );
167
209
  }
210
+ /**
211
+ * Hold the activity queue of a node type (every node type when omitted):
212
+ * attempts wait instead of starting, and a `scheduleToStartTimeout` expires
213
+ * (`ACTIVITY_TIMED_OUT { timeoutType: SCHEDULE_TO_START }`, not retried).
214
+ */
215
+ holdActivities(nodeType = "*") {
216
+ this.held.add(nodeType);
217
+ }
218
+ /** Release held activity queues (all when no node type is given). */
219
+ releaseActivities(nodeType) {
220
+ if (nodeType === void 0) this.held.clear();
221
+ else this.held.delete(nodeType);
222
+ }
223
+ isHeld(nodeType) {
224
+ return this.held.has("*") || this.held.has(nodeType);
225
+ }
168
226
  /** Mock a child workflow started by key (graph or durable). */
169
227
  mockChild(workflowKey, impl) {
170
228
  this.childMocks.set(workflowKey, impl);
@@ -175,20 +233,88 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
175
233
  // A declared workflow is stored by its declaration; its input type is the caller's.
176
234
  this.ensureRegistered(definition)
177
235
  );
236
+ const declared = this.workflows[workflowFn]?.options ?? {};
237
+ const workflowKey = options.workflowKey ?? (typeof definition === "string" ? this.workflows[workflowFn]?.name ?? workflowFn : definition.name);
238
+ if (options.workflowId !== void 0) {
239
+ const existing = this.useExisting(workflowKey, options);
240
+ if (existing !== void 0) {
241
+ for (const signal of options.signals ?? [])
242
+ this.deliverSignal(existing, signal.name, signal.payload);
243
+ await this.runUntilIdle(false);
244
+ return new TestRunHandle(this, existing);
245
+ }
246
+ }
247
+ const executionTimeoutMs = optionalDuration(
248
+ options.executionTimeout ?? declared.executionTimeout,
249
+ "executionTimeout"
250
+ );
251
+ const retry = options.retry ?? declared.retry;
252
+ const config = {
253
+ workflowKey,
254
+ runAttempt: 1,
255
+ ...options.workflowId !== void 0 ? { businessId: options.workflowId } : {},
256
+ ...executionTimeoutMs !== void 0 ? { executionTimeoutMs, executionDeadline: this.clock + executionTimeoutMs } : {},
257
+ ...this.durationConfig(options.runTimeout ?? declared.runTimeout, "runTimeoutMs"),
258
+ ...this.durationConfig(
259
+ options.workflowTaskTimeout ?? declared.workflowTaskTimeout,
260
+ "workflowTaskTimeoutMs"
261
+ ),
262
+ ...this.durationConfig(options.startDelay, "startDelayMs"),
263
+ ...retry !== void 0 ? { retry: chunkJ3PYFGKA_cjs.runRetryPolicyOf(retry) } : {},
264
+ ...options.summary !== void 0 ? { summary: options.summary } : {},
265
+ ...options.details !== void 0 ? { details: options.details } : {},
266
+ ...options.memo !== void 0 ? { memo: toJsonValue(options.memo) } : {},
267
+ ...(options.versioningBehavior ?? declared.versioningBehavior) !== void 0 ? { versioningBehavior: options.versioningBehavior ?? declared.versioningBehavior } : {}
268
+ };
178
269
  const runId = this.createRun(
179
270
  workflowFn,
180
271
  toJsonValue(options.input),
181
272
  { ...options.info, ...options.runId !== void 0 ? { runId: options.runId } : {} },
182
- void 0
273
+ void 0,
274
+ {},
275
+ config
183
276
  );
184
277
  for (const signal of options.signals ?? [])
185
278
  this.deliverSignal(runId, signal.name, signal.payload);
186
279
  await this.runUntilIdle(false);
187
280
  return new TestRunHandle(this, runId);
188
281
  }
282
+ durationConfig(value, key) {
283
+ const ms = optionalDuration(value, key);
284
+ if (ms === void 0) return {};
285
+ const out = {};
286
+ out[key] = ms;
287
+ return out;
288
+ }
289
+ /**
290
+ * The workflow-id policies of a start (11 §2.1): the open run to use, or
291
+ * undefined to start a new run (after terminating the open one for
292
+ * `TERMINATE_EXISTING`). Throws {@link WorkflowAlreadyStartedError}.
293
+ */
294
+ useExisting(workflowKey, options) {
295
+ const workflowId = options.workflowId ?? "";
296
+ const runs = this.runOrder.map((id) => this.run(id)).filter(
297
+ (run) => run.config.workflowKey === workflowKey && run.config.businessId === workflowId
298
+ );
299
+ const open = runs.find((run) => run.state.status === "RUNNING");
300
+ if (open) {
301
+ const conflict = options.idConflictPolicy ?? "FAIL";
302
+ if (conflict === "USE_EXISTING") return open.runId;
303
+ if (conflict === "FAIL")
304
+ throw new WorkflowAlreadyStartedError(workflowKey, workflowId, open.runId);
305
+ this.terminate(open.runId, "ID_CONFLICT");
306
+ return void 0;
307
+ }
308
+ const last = runs[runs.length - 1];
309
+ if (!last) return void 0;
310
+ const reuse = options.idReusePolicy ?? "ALLOW_DUPLICATE";
311
+ if (reuse === "REJECT_DUPLICATE" || reuse === "ALLOW_DUPLICATE_FAILED_ONLY" && !FAILED_STATUSES.has(last.state.status))
312
+ throw new WorkflowAlreadyStartedError(workflowKey, workflowId, last.runId);
313
+ return void 0;
314
+ }
189
315
  /** Advance the clock by `duration`, firing timers and running work as it comes due. */
190
316
  async sleep(duration) {
191
- const until = this.clock + chunkFMSW2HDF_cjs.toMs(duration, "sleep duration");
317
+ const until = this.clock + chunkJ3PYFGKA_cjs.toMs(duration, "sleep duration");
192
318
  await this.runUntilIdle(false);
193
319
  for (; ; ) {
194
320
  const next = this.nextDue();
@@ -210,7 +336,7 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
210
336
  }
211
337
  historyFileOf(runId, extra = {}) {
212
338
  const run = this.run(runId);
213
- return chunkPZCVF76Q_cjs.createHistoryFile(run.workflowFn, this.historyOf(runId), {
339
+ return chunkI47EENR4_cjs.createHistoryFile(run.workflowFn, this.historyOf(runId), {
214
340
  info: { ...run.info },
215
341
  ...extra
216
342
  });
@@ -236,7 +362,7 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
236
362
  if (!run) throw new Error(`Unknown run ${runId}`);
237
363
  return run;
238
364
  }
239
- createRun(workflowFn, input, info, parent, extraStarted = {}) {
365
+ createRun(workflowFn, input, info, parent, extraStarted = {}, config = { workflowKey: workflowFn, runAttempt: 1 }) {
240
366
  this.runCounter += 1;
241
367
  const runId = info.runId ?? `run-${this.runCounter}`;
242
368
  const snapshot = {
@@ -253,22 +379,66 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
253
379
  runId,
254
380
  workflowFn,
255
381
  history: [],
256
- state: chunkP6TA5EZT_cjs.initialRunState(),
382
+ state: chunkRUQIMTN5_cjs.initialRunState(),
257
383
  taskCounter: 0,
258
384
  info: snapshot,
259
385
  children: /* @__PURE__ */ new Map(),
386
+ config,
260
387
  ...parent ? { parent } : {}
261
388
  };
389
+ const firstTaskDelay = config.startDelayMs ?? config.firstTaskDelayMs;
390
+ if (firstTaskDelay !== void 0 && firstTaskDelay > 0) {
391
+ run.delayedUntil = this.clock + firstTaskDelay;
392
+ this.dues.push({ runId, kind: "start", at: run.delayedUntil });
393
+ }
394
+ if (config.runTimeoutMs !== void 0)
395
+ this.dues.push({ runId, kind: "runTimeout", at: this.clock + config.runTimeoutMs });
396
+ if (config.executionDeadline !== void 0)
397
+ this.dues.push({ runId, kind: "executionTimeout", at: config.executionDeadline });
262
398
  this.runs.set(runId, run);
263
399
  this.runOrder.push(runId);
264
400
  this.append(run, {
265
401
  type: "WORKFLOW_STARTED",
266
402
  // The service writes the seed of ctx.random()/ctx.uuid() into the start
267
403
  // event so the history replays on its own; it uses the run id.
268
- payload: { workflowFn, input, taskQueue: "managed", randomnessSeed: runId, ...extraStarted }
404
+ payload: {
405
+ workflowFn,
406
+ input,
407
+ taskQueue: config.taskQueue ?? "managed",
408
+ randomnessSeed: runId,
409
+ ...extraStarted,
410
+ ...this.startedFields(config)
411
+ }
269
412
  });
270
413
  return runId;
271
414
  }
415
+ /** The R2 fields of `WORKFLOW_STARTED`, each written only when it is set. */
416
+ startedFields(config) {
417
+ const out = {};
418
+ if (config.businessId !== void 0) {
419
+ out["businessId"] = config.businessId;
420
+ out["workflowId"] = config.businessId;
421
+ }
422
+ if (config.startDelayMs !== void 0) out["startDelayMs"] = config.startDelayMs;
423
+ if (config.retry !== void 0 || config.runAttempt > 1) out["runAttempt"] = config.runAttempt;
424
+ if (config.summary !== void 0) out["summary"] = config.summary;
425
+ if (config.details !== void 0) out["details"] = config.details;
426
+ if (config.memo !== void 0) out["context"] = config.memo;
427
+ const timeouts = {};
428
+ if (config.executionTimeoutMs !== void 0)
429
+ timeouts["executionTimeout"] = config.executionTimeoutMs;
430
+ if (config.runTimeoutMs !== void 0) timeouts["runTimeout"] = config.runTimeoutMs;
431
+ if (config.workflowTaskTimeoutMs !== void 0)
432
+ timeouts["workflowTaskTimeout"] = config.workflowTaskTimeoutMs;
433
+ if (Object.keys(timeouts).length > 0) out["timeouts"] = timeouts;
434
+ if (config.retry !== void 0) out["retry"] = toJsonValue(config.retry);
435
+ if (config.versioningBehavior !== void 0)
436
+ out["versioningBehavior"] = config.versioningBehavior;
437
+ if (config.lastCompletionResult !== void 0 && this.historyRelease >= 2)
438
+ out["lastCompletionResult"] = config.lastCompletionResult;
439
+ if (config.lastFailure !== void 0) out["lastFailure"] = toJsonValue(config.lastFailure);
440
+ return out;
441
+ }
272
442
  append(run, draft) {
273
443
  const event = {
274
444
  seq: run.history.length + 1,
@@ -277,9 +447,10 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
277
447
  createdAt: new Date(this.clock).toISOString(),
278
448
  ...draft.taskId !== void 0 ? { taskId: draft.taskId } : {}
279
449
  };
280
- chunkP6TA5EZT_cjs.applyEvent(run.state, event);
450
+ chunkRUQIMTN5_cjs.applyEvent(run.state, event);
281
451
  run.history.push(event);
282
- if (chunk623FVQSN_cjs.isInputEvent(event) && run.state.status === "RUNNING" && run.state.task.state === "NONE") {
452
+ if (run.delayedUntil !== void 0 && event.type === "SIGNAL_RECEIVED") this.endDelay(run);
453
+ if (run.delayedUntil === void 0 && chunk3FIDHONL_cjs.isInputEvent(event) && run.state.status === "RUNNING" && run.state.task.state === "NONE") {
283
454
  run.taskCounter += 1;
284
455
  const taskId = `${run.runId}-task-${run.taskCounter}`;
285
456
  const scheduled = {
@@ -288,11 +459,27 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
288
459
  payload: { taskId },
289
460
  createdAt: event.createdAt
290
461
  };
291
- chunkP6TA5EZT_cjs.applyEvent(run.state, scheduled);
462
+ chunkRUQIMTN5_cjs.applyEvent(run.state, scheduled);
292
463
  run.history.push(scheduled);
293
464
  }
294
465
  return event;
295
466
  }
467
+ /** End a start delay: the first workflow task is scheduled now. */
468
+ endDelay(run) {
469
+ run.delayedUntil = void 0;
470
+ this.dues = this.dues.filter((due) => !(due.runId === run.runId && due.kind === "start"));
471
+ if (run.state.status !== "RUNNING" || run.state.task.state !== "NONE") return;
472
+ run.taskCounter += 1;
473
+ const taskId = `${run.runId}-task-${run.taskCounter}`;
474
+ const scheduled = {
475
+ seq: run.history.length + 1,
476
+ type: "WORKFLOW_TASK_SCHEDULED",
477
+ payload: { taskId },
478
+ createdAt: new Date(this.clock).toISOString()
479
+ };
480
+ chunkRUQIMTN5_cjs.applyEvent(run.state, scheduled);
481
+ run.history.push(scheduled);
482
+ }
296
483
  deliverSignal(runId, name, payload) {
297
484
  const run = this.runs.get(runId);
298
485
  if (!run || run.state.status !== "RUNNING") return false;
@@ -314,6 +501,11 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
314
501
  type: "WORKFLOW_CANCEL_REQUESTED",
315
502
  payload: reason !== void 0 ? { reason } : {}
316
503
  });
504
+ if (run.delayedUntil !== void 0) {
505
+ run.delayedUntil = void 0;
506
+ this.append(run, { type: "WORKFLOW_CANCELLED", payload: {} });
507
+ this.onClosed(run);
508
+ }
317
509
  return true;
318
510
  }
319
511
  terminate(runId, reason) {
@@ -333,7 +525,9 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
333
525
  await this.runTask(task);
334
526
  continue;
335
527
  }
336
- const attempt = this.attempts.find((entry) => entry.dueAt <= this.clock);
528
+ const attempt = this.attempts.find(
529
+ (entry) => entry.dueAt <= this.clock && !this.isHeld(entry.nodeType)
530
+ );
337
531
  if (attempt) {
338
532
  this.attempts = this.attempts.filter((entry) => entry !== attempt);
339
533
  await this.runAttempt(attempt);
@@ -352,16 +546,78 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
352
546
  }
353
547
  throw new Error("The test environment did not settle after 100000 steps");
354
548
  }
549
+ /** When a held attempt's schedule-to-start timeout expires, if it has one. */
550
+ heldDeadline(entry) {
551
+ return this.isHeld(entry.nodeType) && entry.scheduleToStart !== void 0 ? entry.dueAt + entry.scheduleToStart : void 0;
552
+ }
355
553
  nextDue() {
356
- const due = [
357
- ...this.timers.map((timer) => timer.fireAt),
358
- ...this.attempts.map((entry) => entry.dueAt)
359
- ];
554
+ const due = [...this.timers.map((timer) => timer.fireAt)];
555
+ for (const entry of this.attempts) {
556
+ if (!this.isHeld(entry.nodeType)) due.push(entry.dueAt);
557
+ const deadline = this.heldDeadline(entry);
558
+ if (deadline !== void 0) due.push(deadline);
559
+ }
560
+ for (const entry of this.dues) due.push(entry.at);
360
561
  return due.length === 0 ? void 0 : Math.min(...due);
361
562
  }
563
+ /** Expire held attempts whose schedule-to-start timeout passed (not retried, 11 §2.3). */
564
+ expireHeld() {
565
+ const expired = this.attempts.filter((entry) => {
566
+ const deadline = this.heldDeadline(entry);
567
+ return deadline !== void 0 && deadline <= this.clock;
568
+ });
569
+ if (expired.length === 0) return false;
570
+ this.attempts = this.attempts.filter((entry) => !expired.includes(entry));
571
+ for (const entry of expired) {
572
+ const run = this.runs.get(entry.runId);
573
+ const activity = run?.state.activities[entry.activityId];
574
+ if (!run || run.state.status !== "RUNNING" || !activity || !chunkRUQIMTN5_cjs.isActivityOpen(activity))
575
+ continue;
576
+ this.append(run, {
577
+ type: "ACTIVITY_TIMED_OUT",
578
+ payload: {
579
+ activityId: entry.activityId,
580
+ attempt: entry.attempt,
581
+ timeoutType: "SCHEDULE_TO_START"
582
+ }
583
+ });
584
+ }
585
+ return true;
586
+ }
587
+ /** Fire run-level deadlines: the end of a start delay, run and execution timeouts. */
588
+ fireRunDues() {
589
+ const due = this.dues.filter((entry) => entry.at <= this.clock).sort((a, b) => a.at - b.at);
590
+ if (due.length === 0) return false;
591
+ this.dues = this.dues.filter((entry) => !due.includes(entry));
592
+ for (const entry of due) {
593
+ const run = this.runs.get(entry.runId);
594
+ if (!run || run.state.status !== "RUNNING") continue;
595
+ if (entry.kind === "start") {
596
+ if (run.delayedUntil !== void 0) this.endDelay(run);
597
+ continue;
598
+ }
599
+ const timeoutType = entry.kind === "runTimeout" ? "WORKFLOW_RUN" : "WORKFLOW_EXECUTION";
600
+ const failure = {
601
+ errorType: "TIMEOUT",
602
+ message: timeoutType === "WORKFLOW_RUN" ? "The run timeout expired" : "The execution timeout of the chain expired",
603
+ type: "TimeoutFailure",
604
+ timeoutType,
605
+ source: "engine"
606
+ };
607
+ run.delayedUntil = void 0;
608
+ this.append(run, {
609
+ type: "WORKFLOW_TIMED_OUT",
610
+ payload: { timeoutType, failure: toJsonValue(failure) }
611
+ });
612
+ this.onClosed(run);
613
+ }
614
+ return true;
615
+ }
362
616
  fireDue() {
617
+ const expired = this.expireHeld();
618
+ const fired = this.fireRunDues();
363
619
  const due = this.timers.filter((timer) => timer.fireAt <= this.clock).sort((a, b) => a.fireAt - b.fireAt);
364
- if (due.length === 0) return false;
620
+ if (due.length === 0) return expired || fired;
365
621
  this.timers = this.timers.filter((timer) => !due.includes(timer));
366
622
  for (const timer of due) {
367
623
  const run = this.runs.get(timer.runId);
@@ -375,14 +631,14 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
375
631
  const taskId = run.state.task.taskId ?? `${run.runId}-task-${run.taskCounter}`;
376
632
  this.append(run, { type: "WORKFLOW_TASK_STARTED", payload: { taskId, attempt: 1 } });
377
633
  const request = {
378
- protocol: chunk623FVQSN_cjs.HISTORY_PROTOCOL,
634
+ protocol: chunk3FIDHONL_cjs.HISTORY_PROTOCOL,
379
635
  kind: "task",
380
636
  workflowFn: run.workflowFn,
381
637
  info: run.info,
382
638
  history: run.history,
383
639
  nowMs: this.clock
384
640
  };
385
- const result = this.executor ? await this.executor(request) : await runWorkflowTaskInProcess(this.registry, request);
641
+ const result = await this.executeWithLocalActivities(run, request);
386
642
  const fail = (cause, message) => {
387
643
  this.append(run, {
388
644
  type: "WORKFLOW_TASK_FAILED",
@@ -395,12 +651,12 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
395
651
  run.taskFailure = result;
396
652
  return;
397
653
  }
398
- const checked = chunkPZCVF76Q_cjs.validateCommands(result.commands, { enabled: this.enabled });
654
+ const checked = chunkI47EENR4_cjs.validateCommands(result.commands, { enabled: this.enabled });
399
655
  if (!checked.ok) {
400
656
  fail(checked.cause, checked.issues.map((issue) => issue.message).join("; "));
401
657
  return;
402
658
  }
403
- const drafts = chunkP6TA5EZT_cjs.eventsForCommands(run.state, checked.commands, {
659
+ const drafts = chunkRUQIMTN5_cjs.eventsForCommands(run.state, checked.commands, {
404
660
  taskId,
405
661
  nowMs: this.clock,
406
662
  enabled: this.enabled
@@ -409,12 +665,69 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
409
665
  fail(drafts.cause, drafts.issues.map((issue) => issue.message).join("; "));
410
666
  return;
411
667
  }
668
+ if (result.unfinishedHandlers !== void 0 && result.unfinishedHandlers.length > 0)
669
+ run.unfinishedHandlers = result.unfinishedHandlers;
412
670
  this.append(run, { type: "WORKFLOW_TASK_COMPLETED", taskId, payload: { taskId } });
413
671
  for (const draft of drafts.events) this.append(run, draft);
414
672
  for (const command of checked.commands)
415
- if (run.state.status === "RUNNING" || chunk623FVQSN_cjs.TERMINAL_COMMANDS.has(command.type))
673
+ if (run.state.status === "RUNNING" || chunk3FIDHONL_cjs.TERMINAL_COMMANDS.has(command.type))
416
674
  this.afterCommand(run, command);
417
675
  }
676
+ execute(request) {
677
+ return this.executor ? this.executor(request) : runWorkflowTaskInProcess(this.registry, request);
678
+ }
679
+ /**
680
+ * Run a workflow task as the worker does (11 §2.12): while it asks for
681
+ * local activities, run each through its mock and run the same task again
682
+ * with the outcomes, up to 1,000 rounds.
683
+ */
684
+ async executeWithLocalActivities(run, request) {
685
+ const outcomes = /* @__PURE__ */ new Map();
686
+ let result = await this.execute(request);
687
+ for (let round = 0; result.outcome === "localActivityRequested"; round++) {
688
+ if (round >= 1e3)
689
+ return {
690
+ ...result,
691
+ outcome: "failed",
692
+ commands: [],
693
+ failure: { cause: "INTERNAL", message: "The task asked for local activities 1000 times" }
694
+ };
695
+ for (const wanted of result.localActivityRequests ?? [])
696
+ outcomes.set(wanted.localActivityId, await this.runLocalActivity(run, wanted));
697
+ result = await this.execute({ ...request, localActivityResults: [...outcomes.values()] });
698
+ }
699
+ return result;
700
+ }
701
+ async runLocalActivity(run, wanted) {
702
+ const mock = this.mocks.get(`${wanted.nodeType}#${wanted.operation}`) ?? this.mocks.get(wanted.nodeType);
703
+ try {
704
+ if (!mock)
705
+ throw new chunkELL2RBML_cjs.ApplicationError(
706
+ `No mock for ${wanted.nodeType} ${wanted.operation} (use env.mock)`,
707
+ { errorType: "VALIDATION", nonRetryable: true, type: "UnmockedActivity" }
708
+ );
709
+ const result = toJsonValue(
710
+ await mock(wanted.args, {
711
+ runId: run.runId,
712
+ activityId: wanted.localActivityId,
713
+ attempt: wanted.attempt,
714
+ nodeType: wanted.nodeType,
715
+ operation: wanted.operation,
716
+ local: true,
717
+ ...wanted.connection !== void 0 ? { connection: wanted.connection } : {}
718
+ })
719
+ );
720
+ return { localActivityId: wanted.localActivityId, result, attempts: wanted.attempt };
721
+ } catch (error) {
722
+ const failure = chunkELL2RBML_cjs.errorToFailure(error, `node:${wanted.nodeType}`);
723
+ delete failure.stackTrace;
724
+ return {
725
+ localActivityId: wanted.localActivityId,
726
+ failure: toJsonValue(failure),
727
+ attempts: wanted.attempt
728
+ };
729
+ }
730
+ }
418
731
  afterCommand(run, command) {
419
732
  switch (command.type) {
420
733
  case "SCHEDULE_ACTIVITY": {
@@ -430,7 +743,8 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
430
743
  retry: command.retry,
431
744
  cancelRequested: false,
432
745
  ...command.connection !== void 0 ? { connection: command.connection } : {},
433
- ...command.scheduleToCloseTimeout !== void 0 ? { scheduleToClose: command.scheduleToCloseTimeout } : {}
746
+ ...command.scheduleToCloseTimeout !== void 0 ? { scheduleToClose: command.scheduleToCloseTimeout } : {},
747
+ ...command.scheduleToStartTimeout !== void 0 ? { scheduleToStart: command.scheduleToStartTimeout } : {}
434
748
  };
435
749
  this.attempts.push(attempt);
436
750
  return;
@@ -505,18 +819,25 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
505
819
  return;
506
820
  }
507
821
  case "CONTINUE_AS_NEW": {
508
- const next = this.createRun(
509
- run.workflowFn,
510
- command.input,
511
- {
512
- ...run.info,
513
- runId: void 0,
514
- continuedFromRunId: run.runId,
515
- firstRunId: run.info.firstRunId ?? run.runId
516
- },
517
- run.parent
518
- );
519
- run.continuedTo = next;
822
+ const options = command.options;
823
+ const config = {
824
+ ...this.chainConfig(run.config),
825
+ // The run timeout restarts and the attempt count resets; the
826
+ // execution timeout of the chain carries over (11 §2.20).
827
+ lastCompletionResult: command.input
828
+ };
829
+ if (typeof options["taskQueue"] === "string") config.taskQueue = options["taskQueue"];
830
+ if (typeof options["workflowTaskTimeout"] === "number")
831
+ config.workflowTaskTimeoutMs = options["workflowTaskTimeout"];
832
+ if (options["versioningBehavior"] === "PINNED" || options["versioningBehavior"] === "AUTO_UPGRADE")
833
+ config.versioningBehavior = options["versioningBehavior"];
834
+ const memo = options["context"];
835
+ if (typeof memo === "object" && memo !== null && !Array.isArray(memo)) config.memo = memo;
836
+ else if (Object.keys(run.state.context).length > 0) config.memo = { ...run.state.context };
837
+ const retry = options["retry"];
838
+ if (chunkRUQIMTN5_cjs.continueAsNewRetryShape(options) === "run" && typeof retry === "object" && retry !== null && !Array.isArray(retry))
839
+ config.retry = retry;
840
+ this.startSuccessor(run, command.input, config);
520
841
  this.onClosed(run);
521
842
  return;
522
843
  }
@@ -529,11 +850,62 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
529
850
  return;
530
851
  }
531
852
  }
853
+ /** What a successor of a run inherits (continue-as-new, run retry). */
854
+ chainConfig(config) {
855
+ const out = { workflowKey: config.workflowKey, runAttempt: 1 };
856
+ for (const key of [
857
+ "businessId",
858
+ "executionDeadline",
859
+ "executionTimeoutMs",
860
+ "runTimeoutMs",
861
+ "workflowTaskTimeoutMs",
862
+ "retry",
863
+ "versioningBehavior",
864
+ "taskQueue",
865
+ "memo"
866
+ ])
867
+ if (config[key] !== void 0) Object.assign(out, { [key]: config[key] });
868
+ return out;
869
+ }
870
+ /** Start the next run of a chain; the closed run's parent and handles follow it. */
871
+ startSuccessor(run, input, config) {
872
+ const next = this.createRun(
873
+ run.workflowFn,
874
+ input,
875
+ {
876
+ ...run.info,
877
+ runId: void 0,
878
+ continuedFromRunId: run.runId,
879
+ firstRunId: run.info.firstRunId ?? run.runId
880
+ },
881
+ run.parent,
882
+ {},
883
+ config
884
+ );
885
+ run.continuedTo = next;
886
+ return next;
887
+ }
532
888
  resolveTarget(run, target) {
533
889
  if ("runId" in target) return target.runId;
534
890
  if ("parent" in target) return run.parent?.runId ?? run.info.parentRunId ?? void 0;
535
- if ("childId" in target) return run.children.get(target.childId);
536
- return void 0;
891
+ if ("childId" in target) {
892
+ let childRunId = run.children.get(target.childId);
893
+ while (childRunId !== void 0) {
894
+ const next = this.runs.get(childRunId)?.continuedTo;
895
+ if (next === void 0) break;
896
+ childRunId = next;
897
+ }
898
+ return childRunId;
899
+ }
900
+ return this.resolveWorkflowId(target.workflowKey, target.workflowId, target.firstRunId);
901
+ }
902
+ /** The open run of a workflow id, else its latest run (11 §2.1). */
903
+ resolveWorkflowId(workflowKey, workflowId, firstRunId) {
904
+ const matching = this.runOrder.map((id) => this.run(id)).filter(
905
+ (candidate) => candidate.config.workflowKey === workflowKey && candidate.config.businessId === workflowId && (firstRunId === void 0 || (candidate.info.firstRunId ?? candidate.runId) === firstRunId)
906
+ );
907
+ const open = matching.find((candidate) => candidate.state.status === "RUNNING");
908
+ return (open ?? matching[matching.length - 1])?.runId;
537
909
  }
538
910
  startChild(run, command) {
539
911
  const byFn = command.workflowFn !== void 0 && (this.executor !== void 0 || this.registry.byName.has(command.workflowFn));
@@ -566,6 +938,28 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
566
938
  });
567
939
  return;
568
940
  }
941
+ const options = command.options;
942
+ const ms = (key) => typeof options[key] === "number" ? options[key] : void 0;
943
+ const executionTimeoutMs = ms("executionTimeout");
944
+ const childKey = command.workflowKey ?? this.registry.byName.get(workflowFn)?.name ?? workflowFn;
945
+ const config = { workflowKey: childKey, runAttempt: 1 };
946
+ if (typeof options.workflowId === "string") config.businessId = options.workflowId;
947
+ if (executionTimeoutMs !== void 0) {
948
+ config.executionTimeoutMs = executionTimeoutMs;
949
+ config.executionDeadline = this.clock + executionTimeoutMs;
950
+ }
951
+ const runTimeoutMs = ms("runTimeout");
952
+ if (runTimeoutMs !== void 0) config.runTimeoutMs = runTimeoutMs;
953
+ const taskTimeoutMs = ms("workflowTaskTimeout");
954
+ if (taskTimeoutMs !== void 0) config.workflowTaskTimeoutMs = taskTimeoutMs;
955
+ const startDelayMs = ms("startDelay");
956
+ if (startDelayMs !== void 0) config.startDelayMs = startDelayMs;
957
+ if (typeof options.summary === "string") config.summary = options.summary;
958
+ if (typeof options.details === "string") config.details = options.details;
959
+ if (options.context !== void 0) config.memo = options.context;
960
+ if (options.versioningBehavior !== void 0)
961
+ config.versioningBehavior = options.versioningBehavior;
962
+ if (options.retry !== void 0) config.retry = options.retry;
569
963
  const childRunId = this.createRun(
570
964
  workflowFn,
571
965
  command.input,
@@ -574,7 +968,8 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
574
968
  {
575
969
  parentRunId: run.runId,
576
970
  parentClosePolicy: command.options.parentClosePolicy
577
- }
971
+ },
972
+ config
578
973
  );
579
974
  run.children.set(command.childId, childRunId);
580
975
  this.append(run, {
@@ -607,7 +1002,7 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
607
1002
  payload: {
608
1003
  childId: entry.childId,
609
1004
  childRunId,
610
- failure: toJsonValue(chunkFMSW2HDF_cjs.errorToFailure(error, "engine"))
1005
+ failure: toJsonValue(chunkELL2RBML_cjs.errorToFailure(error, "engine"))
611
1006
  }
612
1007
  });
613
1008
  }
@@ -616,7 +1011,7 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
616
1011
  const run = this.runs.get(attempt.runId);
617
1012
  if (!run || run.state.status !== "RUNNING") return;
618
1013
  const activity = run.state.activities[attempt.activityId];
619
- if (!activity || !chunkP6TA5EZT_cjs.isActivityOpen(activity)) return;
1014
+ if (!activity || !chunkRUQIMTN5_cjs.isActivityOpen(activity)) return;
620
1015
  if (attempt.scheduleToClose !== void 0 && this.clock - attempt.scheduledAt > attempt.scheduleToClose) {
621
1016
  this.append(run, {
622
1017
  type: "ACTIVITY_TIMED_OUT",
@@ -631,9 +1026,10 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
631
1026
  const mock = this.mocks.get(`${attempt.nodeType}#${attempt.operation}`) ?? this.mocks.get(attempt.nodeType);
632
1027
  let failure;
633
1028
  let result = null;
1029
+ let heartbeatDetails = attempt.heartbeatDetails;
634
1030
  try {
635
1031
  if (!mock)
636
- throw new chunkFMSW2HDF_cjs.ApplicationError(
1032
+ throw new chunkELL2RBML_cjs.ApplicationError(
637
1033
  `No mock for ${attempt.nodeType} ${attempt.operation} (use env.mock)`,
638
1034
  {
639
1035
  errorType: "VALIDATION",
@@ -648,14 +1044,18 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
648
1044
  attempt: attempt.attempt,
649
1045
  nodeType: attempt.nodeType,
650
1046
  operation: attempt.operation,
651
- ...attempt.connection !== void 0 ? { connection: attempt.connection } : {}
1047
+ ...attempt.connection !== void 0 ? { connection: attempt.connection } : {},
1048
+ ...attempt.heartbeatDetails !== void 0 ? { heartbeatDetails: attempt.heartbeatDetails } : {},
1049
+ heartbeat: (details) => {
1050
+ heartbeatDetails = toJsonValue(details);
1051
+ }
652
1052
  })
653
1053
  );
654
1054
  } catch (error) {
655
- failure = chunkFMSW2HDF_cjs.errorToFailure(error, `node:${attempt.nodeType}`);
1055
+ failure = chunkELL2RBML_cjs.errorToFailure(error, `node:${attempt.nodeType}`);
656
1056
  delete failure.stackTrace;
657
1057
  }
658
- if (run.state.status !== "RUNNING" || !chunkP6TA5EZT_cjs.isActivityOpen(run.state.activities[attempt.activityId] ?? activity))
1058
+ if (run.state.status !== "RUNNING" || !chunkRUQIMTN5_cjs.isActivityOpen(run.state.activities[attempt.activityId] ?? activity))
659
1059
  return;
660
1060
  const started = () => {
661
1061
  this.append(run, {
@@ -672,7 +1072,7 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
672
1072
  return;
673
1073
  }
674
1074
  if (failure.timeoutType !== void 0 || failure.type === "TimeoutFailure") {
675
- const decision2 = chunkP6TA5EZT_cjs.computeRetryDelay(attempt.retry, attempt.attempt, failure);
1075
+ const decision2 = chunkYHBVMOMK_cjs.computeRetryDelay(attempt.retry, attempt.attempt, failure);
676
1076
  if (!decision2.retry) {
677
1077
  started();
678
1078
  this.append(run, {
@@ -689,16 +1089,18 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
689
1089
  this.attempts.push({
690
1090
  ...attempt,
691
1091
  attempt: attempt.attempt + 1,
692
- dueAt: this.clock + decision2.delayMs
1092
+ dueAt: this.clock + decision2.delayMs,
1093
+ ...heartbeatDetails !== void 0 ? { heartbeatDetails } : {}
693
1094
  });
694
1095
  return;
695
1096
  }
696
- const decision = chunkP6TA5EZT_cjs.computeRetryDelay(attempt.retry, attempt.attempt, failure);
1097
+ const decision = chunkYHBVMOMK_cjs.computeRetryDelay(attempt.retry, attempt.attempt, failure);
697
1098
  if (decision.retry) {
698
1099
  this.attempts.push({
699
1100
  ...attempt,
700
1101
  attempt: attempt.attempt + 1,
701
- dueAt: this.clock + decision.delayMs
1102
+ dueAt: this.clock + decision.delayMs,
1103
+ ...heartbeatDetails !== void 0 ? { heartbeatDetails } : {}
702
1104
  });
703
1105
  return;
704
1106
  }
@@ -712,12 +1114,34 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
712
1114
  }
713
1115
  });
714
1116
  }
1117
+ /**
1118
+ * Run-level retry (11 §2.17): a failed or run-timed-out run whose policy
1119
+ * allows another attempt is followed by a new run of the chain after the
1120
+ * backoff, with `runAttempt + 1` and the failure as `lastFailure`.
1121
+ */
1122
+ retryRun(run) {
1123
+ if (run.continuedTo !== void 0) return;
1124
+ const last = run.history[run.history.length - 1];
1125
+ if (!last || last.type !== "WORKFLOW_FAILED" && last.type !== "WORKFLOW_TIMED_OUT") return;
1126
+ const failure = last.payload["failure"] ?? { errorType: "TIMEOUT", message: "timed out", timeoutType: "WORKFLOW_RUN" };
1127
+ const decision = chunkYHBVMOMK_cjs.computeRunRetryDelay(run.config.retry, run.config.runAttempt, failure);
1128
+ if (!decision.retry) return;
1129
+ const config = {
1130
+ ...this.chainConfig(run.config),
1131
+ runAttempt: run.config.runAttempt + 1,
1132
+ lastFailure: failure
1133
+ };
1134
+ if (decision.delayMs > 0) config.firstTaskDelayMs = decision.delayMs;
1135
+ this.startSuccessor(run, run.state.input ?? null, config);
1136
+ }
715
1137
  onClosed(run) {
716
1138
  this.timers = this.timers.filter((timer) => timer.runId !== run.runId);
717
1139
  this.attempts = this.attempts.filter((entry) => entry.runId !== run.runId);
1140
+ this.dues = this.dues.filter((entry) => entry.runId !== run.runId);
1141
+ this.retryRun(run);
718
1142
  for (const [childId2, childRunId] of run.children) {
719
1143
  const child = run.state.children[childId2];
720
- if (!child || !chunkP6TA5EZT_cjs.isChildOpen(child) || childRunId === void 0) continue;
1144
+ if (!child || !chunkRUQIMTN5_cjs.isChildOpen(child) || childRunId === void 0) continue;
721
1145
  if (child.parentClosePolicy === "TERMINATE") this.terminate(childRunId, "parent closed");
722
1146
  else if (child.parentClosePolicy === "REQUEST_CANCEL")
723
1147
  this.requestCancel(childRunId, "parent closed");
@@ -726,7 +1150,7 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
726
1150
  const parent = this.runs.get(run.parent.runId);
727
1151
  if (!parent || parent.state.status !== "RUNNING") return;
728
1152
  const childId = run.parent.childId;
729
- if (!parent.state.children[childId] || !chunkP6TA5EZT_cjs.isChildOpen(parent.state.children[childId])) return;
1153
+ if (!parent.state.children[childId] || !chunkRUQIMTN5_cjs.isChildOpen(parent.state.children[childId])) return;
730
1154
  const last = run.history[run.history.length - 1];
731
1155
  if (!last) return;
732
1156
  if (run.continuedTo !== void 0) {
@@ -769,7 +1193,10 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
769
1193
  default:
770
1194
  this.append(parent, {
771
1195
  type: "CHILD_RUN_TIMED_OUT",
772
- payload: { ...base, timeoutType: "WORKFLOW_RUN" }
1196
+ payload: {
1197
+ ...base,
1198
+ timeoutType: last.payload["timeoutType"] === "WORKFLOW_EXECUTION" ? "WORKFLOW_EXECUTION" : "WORKFLOW_RUN"
1199
+ }
773
1200
  });
774
1201
  }
775
1202
  }
@@ -778,17 +1205,28 @@ var TestWorkflowEnvironment = class _TestWorkflowEnvironment {
778
1205
  let run = this.run(runId);
779
1206
  while (run.continuedTo !== void 0) run = this.run(run.continuedTo);
780
1207
  if (run.taskFailure) throw new WorkflowTaskFailedError(run.runId, run.taskFailure);
1208
+ if (this.failOnUnfinishedHandlers && run.unfinishedHandlers !== void 0)
1209
+ throw new UnfinishedHandlersError(run.runId, run.unfinishedHandlers);
781
1210
  const last = run.history[run.history.length - 1];
782
1211
  switch (run.state.status) {
783
1212
  case "COMPLETED":
784
1213
  return run.state.output ?? null;
1214
+ case "TIMED_OUT":
1215
+ throw new WorkflowFailedError(
1216
+ run.runId,
1217
+ last?.payload["failure"] ?? {
1218
+ errorType: "TIMEOUT",
1219
+ message: "timed out",
1220
+ type: "TimeoutFailure"
1221
+ }
1222
+ );
785
1223
  case "FAILED":
786
1224
  throw new WorkflowFailedError(
787
1225
  run.runId,
788
1226
  run.state.failure ?? { errorType: "UNKNOWN", message: "failed" }
789
1227
  );
790
1228
  case "CANCELLED":
791
- throw new chunkFMSW2HDF_cjs.CancelledFailure(`Run ${run.runId} was cancelled`);
1229
+ throw new chunkELL2RBML_cjs.CancelledFailure(`Run ${run.runId} was cancelled`);
792
1230
  case "TERMINATED":
793
1231
  throw new WorkflowFailedError(run.runId, {
794
1232
  errorType: "PERMANENT",
@@ -819,7 +1257,7 @@ var ReplayError = class extends Error {
819
1257
  function replayRequest(workflowFn, history, info = {}, nowMs) {
820
1258
  const last = history[history.length - 1];
821
1259
  return {
822
- protocol: chunk623FVQSN_cjs.HISTORY_PROTOCOL,
1260
+ protocol: chunk3FIDHONL_cjs.HISTORY_PROTOCOL,
823
1261
  kind: "verify",
824
1262
  workflowFn,
825
1263
  info: { runId: "replay", workspaceId: "replay", ...info },
@@ -829,14 +1267,14 @@ function replayRequest(workflowFn, history, info = {}, nowMs) {
829
1267
  }
830
1268
  function eventsOf(input) {
831
1269
  if (Array.isArray(input)) return { events: input };
832
- const file = typeof input === "string" ? chunkPZCVF76Q_cjs.parseHistoryFile(input) : input;
1270
+ const file = typeof input === "string" ? chunkI47EENR4_cjs.parseHistoryFile(input) : input;
833
1271
  return { events: file.events, file };
834
1272
  }
835
1273
  async function replay(workflows, input, options = {}) {
836
1274
  const { events, file } = eventsOf(input);
837
1275
  const started = events[0]?.type === "WORKFLOW_STARTED" ? events[0].payload["workflowFn"] : void 0;
838
1276
  const recordedFn = options.workflowFn ?? file?.workflowFn ?? (typeof started === "string" ? started : void 0);
839
- const map = chunkFQIHX7YI_cjs.isDurableWorkflow(workflows) ? { [recordedFn ?? workflows.name]: workflows } : { ...workflows };
1277
+ const map = chunkJ3PYFGKA_cjs.isDurableWorkflow(workflows) ? { [recordedFn ?? workflows.name]: workflows } : { ...workflows };
840
1278
  const workflowFn = recordedFn ?? Object.keys(map)[0] ?? "";
841
1279
  const result = await runWorkflowTaskInProcess(
842
1280
  registryOf(map),
@@ -849,7 +1287,7 @@ async function replay(workflows, input, options = {}) {
849
1287
  );
850
1288
  if (result.outcome === "failed") {
851
1289
  if (result.failure?.cause === "NON_DETERMINISM")
852
- throw new chunkFMSW2HDF_cjs.DeterminismViolation(result.failure.message);
1290
+ throw new chunkELL2RBML_cjs.DeterminismViolation(result.failure.message);
853
1291
  throw new ReplayError(result);
854
1292
  }
855
1293
  return result;
@@ -898,7 +1336,7 @@ function assertHistory(history, expected, options = {}) {
898
1336
  );
899
1337
  }
900
1338
  function definitionOf(value) {
901
- return chunkGBQ33SIZ_cjs.normalizeDefinition(chunk57MSZ24T_cjs.isTier1Workflow(value) ? chunk57MSZ24T_cjs.toIR(value) : value);
1339
+ return chunkGBQ33SIZ_cjs.normalizeDefinition(chunkNXFBVDOI_cjs.isTier1Workflow(value) ? chunkNXFBVDOI_cjs.toIR(value) : value);
902
1340
  }
903
1341
  function expectIR(value) {
904
1342
  const definition = definitionOf(value);
@@ -1018,22 +1456,22 @@ async function runWorkflowTaskInVm(script, request, options = {}) {
1018
1456
  await settle();
1019
1457
  if (step !== "more") break;
1020
1458
  if (Date.now() > deadline)
1021
- return chunkJD2VQIDZ_cjs.failedTaskResult(
1459
+ return chunkEWYADREB_cjs.failedTaskResult(
1022
1460
  "TASK_TIMEOUT",
1023
1461
  `The workflow task ran past its ${timeout} ms budget`
1024
1462
  );
1025
1463
  }
1026
1464
  const json = run("globalThis.__fluidgridsRuntime.complete()", "complete.js");
1027
1465
  if (typeof json !== "string")
1028
- return chunkJD2VQIDZ_cjs.failedTaskResult("INTERNAL", "The runtime returned no result");
1466
+ return chunkEWYADREB_cjs.failedTaskResult("INTERNAL", "The runtime returned no result");
1029
1467
  return JSON.parse(json);
1030
1468
  } catch (error) {
1031
1469
  const fields = typeof error === "object" && error !== null ? error : {};
1032
1470
  const message = typeof fields.message === "string" ? fields.message : String(error);
1033
1471
  const name = typeof fields.name === "string" ? fields.name : "";
1034
- if (/timed out/i.test(message)) return chunkJD2VQIDZ_cjs.failedTaskResult("TASK_TIMEOUT", message);
1035
- if (name === "DeterminismViolation") return chunkJD2VQIDZ_cjs.failedTaskResult("NON_DETERMINISM", message);
1036
- return chunkJD2VQIDZ_cjs.failedTaskResult("WORKFLOW_CODE_ERROR", `${name || "Error"}: ${message}`);
1472
+ if (/timed out/i.test(message)) return chunkEWYADREB_cjs.failedTaskResult("TASK_TIMEOUT", message);
1473
+ if (name === "DeterminismViolation") return chunkEWYADREB_cjs.failedTaskResult("NON_DETERMINISM", message);
1474
+ return chunkEWYADREB_cjs.failedTaskResult("WORKFLOW_CODE_ERROR", `${name || "Error"}: ${message}`);
1037
1475
  }
1038
1476
  }
1039
1477
 
@@ -1042,6 +1480,8 @@ exports.ReplayError = ReplayError;
1042
1480
  exports.RunBlockedError = RunBlockedError;
1043
1481
  exports.TestRunHandle = TestRunHandle;
1044
1482
  exports.TestWorkflowEnvironment = TestWorkflowEnvironment;
1483
+ exports.UnfinishedHandlersError = UnfinishedHandlersError;
1484
+ exports.WorkflowAlreadyStartedError = WorkflowAlreadyStartedError;
1045
1485
  exports.WorkflowFailedError = WorkflowFailedError;
1046
1486
  exports.WorkflowTaskFailedError = WorkflowTaskFailedError;
1047
1487
  exports.assertHistory = assertHistory;