@proompteng/temporal-bun-sdk 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +8 -1
  2. package/dist/src/bin/temporal-bun.js +1 -1
  3. package/dist/src/common/payloads/converter.js +1 -1
  4. package/dist/src/common/payloads/converter.js.map +1 -1
  5. package/dist/src/common/payloads/json-codec.d.ts.map +1 -1
  6. package/dist/src/common/payloads/json-codec.js +4 -6
  7. package/dist/src/common/payloads/json-codec.js.map +1 -1
  8. package/dist/src/config.d.ts +16 -0
  9. package/dist/src/config.d.ts.map +1 -1
  10. package/dist/src/config.js +49 -0
  11. package/dist/src/config.js.map +1 -1
  12. package/dist/src/interceptors/client.d.ts.map +1 -1
  13. package/dist/src/interceptors/client.js +24 -3
  14. package/dist/src/interceptors/client.js.map +1 -1
  15. package/dist/src/interceptors/worker.d.ts.map +1 -1
  16. package/dist/src/interceptors/worker.js +24 -3
  17. package/dist/src/interceptors/worker.js.map +1 -1
  18. package/dist/src/observability/index.d.ts +4 -0
  19. package/dist/src/observability/index.d.ts.map +1 -1
  20. package/dist/src/observability/index.js +4 -0
  21. package/dist/src/observability/index.js.map +1 -1
  22. package/dist/src/observability/metrics.d.ts.map +1 -1
  23. package/dist/src/observability/metrics.js +70 -10
  24. package/dist/src/observability/metrics.js.map +1 -1
  25. package/dist/src/observability/opentelemetry.d.ts +20 -0
  26. package/dist/src/observability/opentelemetry.d.ts.map +1 -0
  27. package/dist/src/observability/opentelemetry.js +334 -0
  28. package/dist/src/observability/opentelemetry.js.map +1 -0
  29. package/dist/src/proto/temporal/api/activity/v1/message_pb.js +1 -1
  30. package/dist/src/proto/temporal/api/batch/v1/message_pb.js +1 -1
  31. package/dist/src/proto/temporal/api/command/v1/message_pb.js +1 -1
  32. package/dist/src/proto/temporal/api/common/v1/message_pb.js +1 -1
  33. package/dist/src/proto/temporal/api/deployment/v1/message_pb.js +1 -1
  34. package/dist/src/proto/temporal/api/enums/v1/batch_operation_pb.js +1 -1
  35. package/dist/src/proto/temporal/api/enums/v1/command_type_pb.js +1 -1
  36. package/dist/src/proto/temporal/api/enums/v1/common_pb.js +1 -1
  37. package/dist/src/proto/temporal/api/enums/v1/deployment_pb.js +1 -1
  38. package/dist/src/proto/temporal/api/enums/v1/event_type_pb.js +1 -1
  39. package/dist/src/proto/temporal/api/enums/v1/failed_cause_pb.js +1 -1
  40. package/dist/src/proto/temporal/api/enums/v1/namespace_pb.js +1 -1
  41. package/dist/src/proto/temporal/api/enums/v1/nexus_pb.js +1 -1
  42. package/dist/src/proto/temporal/api/enums/v1/query_pb.js +1 -1
  43. package/dist/src/proto/temporal/api/enums/v1/reset_pb.js +1 -1
  44. package/dist/src/proto/temporal/api/enums/v1/schedule_pb.js +1 -1
  45. package/dist/src/proto/temporal/api/enums/v1/task_queue_pb.js +1 -1
  46. package/dist/src/proto/temporal/api/enums/v1/update_pb.js +1 -1
  47. package/dist/src/proto/temporal/api/enums/v1/workflow_pb.js +1 -1
  48. package/dist/src/proto/temporal/api/errordetails/v1/message_pb.js +1 -1
  49. package/dist/src/proto/temporal/api/export/v1/message_pb.js +1 -1
  50. package/dist/src/proto/temporal/api/failure/v1/message_pb.js +1 -1
  51. package/dist/src/proto/temporal/api/filter/v1/message_pb.js +1 -1
  52. package/dist/src/proto/temporal/api/history/v1/message_pb.js +1 -1
  53. package/dist/src/proto/temporal/api/namespace/v1/message_pb.js +1 -1
  54. package/dist/src/proto/temporal/api/nexus/v1/message_pb.js +1 -1
  55. package/dist/src/proto/temporal/api/operatorservice/v1/request_response_pb.js +1 -1
  56. package/dist/src/proto/temporal/api/operatorservice/v1/service_pb.js +1 -1
  57. package/dist/src/proto/temporal/api/protocol/v1/message_pb.js +1 -1
  58. package/dist/src/proto/temporal/api/query/v1/message_pb.js +1 -1
  59. package/dist/src/proto/temporal/api/replication/v1/message_pb.js +1 -1
  60. package/dist/src/proto/temporal/api/rules/v1/message_pb.js +1 -1
  61. package/dist/src/proto/temporal/api/sdk/v1/enhanced_stack_trace_pb.js +1 -1
  62. package/dist/src/proto/temporal/api/sdk/v1/task_complete_metadata_pb.js +1 -1
  63. package/dist/src/proto/temporal/api/sdk/v1/user_metadata_pb.js +1 -1
  64. package/dist/src/proto/temporal/api/sdk/v1/worker_config_pb.js +1 -1
  65. package/dist/src/proto/temporal/api/sdk/v1/workflow_metadata_pb.js +1 -1
  66. package/dist/src/proto/temporal/api/taskqueue/v1/message_pb.js +1 -1
  67. package/dist/src/proto/temporal/api/update/v1/message_pb.js +1 -1
  68. package/dist/src/proto/temporal/api/version/v1/message_pb.js +1 -1
  69. package/dist/src/proto/temporal/api/worker/v1/message_pb.js +1 -1
  70. package/dist/src/proto/temporal/api/workflow/v1/message_pb.js +1 -1
  71. package/dist/src/proto/temporal/api/workflowservice/v1/request_response_pb.js +1 -1
  72. package/dist/src/proto/temporal/api/workflowservice/v1/service_pb.js +1 -1
  73. package/dist/src/worker/runtime.d.ts +1 -0
  74. package/dist/src/worker/runtime.d.ts.map +1 -1
  75. package/dist/src/worker/runtime.js +649 -61
  76. package/dist/src/worker/runtime.js.map +1 -1
  77. package/dist/src/worker/sticky-cache.d.ts +10 -0
  78. package/dist/src/worker/sticky-cache.d.ts.map +1 -1
  79. package/dist/src/worker/sticky-cache.js.map +1 -1
  80. package/dist/src/workflow/commands.d.ts +2 -1
  81. package/dist/src/workflow/commands.d.ts.map +1 -1
  82. package/dist/src/workflow/commands.js +9 -7
  83. package/dist/src/workflow/commands.js.map +1 -1
  84. package/dist/src/workflow/context.d.ts.map +1 -1
  85. package/dist/src/workflow/context.js +28 -9
  86. package/dist/src/workflow/context.js.map +1 -1
  87. package/dist/src/workflow/definition.js.map +1 -1
  88. package/dist/src/workflow/determinism.d.ts +4 -0
  89. package/dist/src/workflow/determinism.d.ts.map +1 -1
  90. package/dist/src/workflow/determinism.js +104 -17
  91. package/dist/src/workflow/determinism.js.map +1 -1
  92. package/dist/src/workflow/executor.d.ts +3 -0
  93. package/dist/src/workflow/executor.d.ts.map +1 -1
  94. package/dist/src/workflow/executor.js +80 -11
  95. package/dist/src/workflow/executor.js.map +1 -1
  96. package/dist/src/workflow/inbound.d.ts +1 -0
  97. package/dist/src/workflow/inbound.d.ts.map +1 -1
  98. package/dist/src/workflow/inbound.js +1 -0
  99. package/dist/src/workflow/inbound.js.map +1 -1
  100. package/dist/src/workflow/index.d.ts +5 -0
  101. package/dist/src/workflow/index.d.ts.map +1 -1
  102. package/dist/src/workflow/index.js +3 -0
  103. package/dist/src/workflow/index.js.map +1 -1
  104. package/dist/src/workflow/log.d.ts +18 -0
  105. package/dist/src/workflow/log.d.ts.map +1 -0
  106. package/dist/src/workflow/log.js +36 -0
  107. package/dist/src/workflow/log.js.map +1 -0
  108. package/dist/src/workflow/replay.d.ts +31 -3
  109. package/dist/src/workflow/replay.d.ts.map +1 -1
  110. package/dist/src/workflow/replay.js +543 -66
  111. package/dist/src/workflow/replay.js.map +1 -1
  112. package/package.json +4 -3
@@ -2,7 +2,7 @@ import { pathToFileURL } from 'node:url';
2
2
  import { create } from '@bufbuild/protobuf';
3
3
  import { Code, ConnectError, createClient } from '@connectrpc/connect';
4
4
  import { createGrpcTransport } from '@connectrpc/connect-node';
5
- import { Cause, Effect, Exit, Fiber } from 'effect';
5
+ import { Cause, Duration, Effect, Exit, Fiber, Schedule } from 'effect';
6
6
  import { makeActivityLifecycle, } from '../activities/lifecycle';
7
7
  import { buildTransportOptions, normalizeTemporalAddress } from '../client';
8
8
  import { durationFromMillis, durationToMillis } from '../common/duration';
@@ -24,10 +24,12 @@ import { HistoryEventFilterType, TimeoutType, VersioningBehavior } from '../prot
24
24
  import { StickyExecutionAttributesSchema, TaskQueueSchema, } from '../proto/temporal/api/taskqueue/v1/message_pb';
25
25
  import { GetWorkflowExecutionHistoryRequestSchema, PollActivityTaskQueueRequestSchema, PollWorkflowTaskQueueRequestSchema, RespondActivityTaskCanceledRequestSchema, RespondActivityTaskCompletedRequestSchema, RespondActivityTaskFailedRequestSchema, RespondQueryTaskCompletedRequestSchema, RespondWorkflowTaskCompletedRequestSchema, RespondWorkflowTaskFailedRequestSchema, } from '../proto/temporal/api/workflowservice/v1/request_response_pb';
26
26
  import { WorkflowService } from '../proto/temporal/api/workflowservice/v1/service_pb';
27
+ import { intentsEqual, stableStringify } from '../workflow/determinism';
27
28
  import { WorkflowNondeterminismError } from '../workflow/errors';
28
29
  import { WorkflowExecutor } from '../workflow/executor';
30
+ import { CHILD_WORKFLOW_COMPLETED_SIGNAL, } from '../workflow/inbound';
29
31
  import { WorkflowRegistry } from '../workflow/registry';
30
- import { DETERMINISM_MARKER_NAME, diffDeterminismState, encodeDeterminismMarkerDetails, ingestWorkflowHistory, resolveHistoryLastEventId, } from '../workflow/replay';
32
+ import { DETERMINISM_MARKER_NAME, diffDeterminismState, encodeDeterminismMarkerDetailsWithSize, ingestWorkflowHistory, resolveHistoryLastEventId, } from '../workflow/replay';
31
33
  import { runWithActivityContext } from './activity-context';
32
34
  import { checkWorkerVersioningCapability, registerWorkerBuildIdCompatibility } from './build-id';
33
35
  import { makeWorkerScheduler, } from './concurrency';
@@ -53,11 +55,19 @@ const mergeUpdateInvocations = (historyInvocations, messageInvocations) => {
53
55
  const POLL_TIMEOUT_MS = 60_000;
54
56
  const RESPOND_TIMEOUT_MS = 15_000;
55
57
  const HISTORY_FETCH_TIMEOUT_MS = 60_000;
58
+ const DEFAULT_METRICS_FLUSH_INTERVAL_MS = 10_000;
56
59
  const HEARTBEAT_RETRY_INITIAL_DELAY_MS = 250;
57
60
  const HEARTBEAT_RETRY_MAX_DELAY_MS = 5_000;
58
61
  const HEARTBEAT_RETRY_MAX_ATTEMPTS = 5;
59
62
  const HEARTBEAT_RETRY_BACKOFF = 2;
60
63
  const HEARTBEAT_RETRY_JITTER = 0.2;
64
+ const parseMetricsFlushInterval = (value) => {
65
+ if (!value) {
66
+ return undefined;
67
+ }
68
+ const parsed = Number.parseInt(value, 10);
69
+ return Number.isFinite(parsed) ? parsed : undefined;
70
+ };
61
71
  const STICKY_QUEUE_PREFIX = 'sticky';
62
72
  const COMPLETION_COMMAND_TYPES = new Set([
63
73
  CommandType.COMPLETE_WORKFLOW_EXECUTION,
@@ -90,7 +100,10 @@ export class WorkerRuntime {
90
100
  metricsRegistry: options.metrics,
91
101
  metricsExporter: options.metricsExporter,
92
102
  }));
93
- const { logger, metricsRegistry, metricsExporter } = observability;
103
+ const { logger, metricsRegistry, metricsExporter, openTelemetry } = observability;
104
+ const metricsFlushIntervalMs = options.metricsFlushIntervalMs ??
105
+ parseMetricsFlushInterval(process.env.TEMPORAL_METRICS_FLUSH_INTERVAL_MS) ??
106
+ DEFAULT_METRICS_FLUSH_INTERVAL_MS;
94
107
  const dataConverter = options.dataConverter ??
95
108
  createDefaultDataConverter({
96
109
  payloadCodecs: buildCodecsFromConfig(config.payloadCodecs),
@@ -102,6 +115,7 @@ export class WorkerRuntime {
102
115
  const executor = new WorkflowExecutor({
103
116
  registry,
104
117
  dataConverter,
118
+ logger,
105
119
  });
106
120
  const runtimeMetrics = await WorkerRuntime.#initMetrics(metricsRegistry);
107
121
  let workflowService;
@@ -162,6 +176,11 @@ export class WorkerRuntime {
162
176
  const stickySchedulingEnabled = options.stickyScheduling ??
163
177
  (config.stickySchedulingEnabled &&
164
178
  (hasStickyCacheInstance ? config.workerStickyCacheSize > 0 : stickyCacheSize > 0));
179
+ const determinismMarkerMode = config.determinismMarkerMode;
180
+ const determinismMarkerIntervalTasks = config.determinismMarkerIntervalTasks;
181
+ const determinismMarkerFullSnapshotIntervalTasks = config.determinismMarkerFullSnapshotIntervalTasks;
182
+ const determinismMarkerSkipUnchanged = config.determinismMarkerSkipUnchanged;
183
+ const determinismMarkerMaxDetailBytes = config.determinismMarkerMaxDetailBytes;
165
184
  const deploymentName = options.deployment?.name ?? config.workerDeploymentName ?? WorkerRuntime.#defaultDeploymentName(taskQueue);
166
185
  const buildId = options.deployment?.buildId ?? config.workerBuildId ?? identity;
167
186
  const workerVersioningMode = options.deployment?.versioningMode ?? WorkerVersioningMode.UNVERSIONED;
@@ -173,6 +192,7 @@ export class WorkerRuntime {
173
192
  buildId,
174
193
  workerVersioningMode,
175
194
  });
195
+ const rpcDeploymentOptions = workerVersioningMode === WorkerVersioningMode.VERSIONED ? deploymentOptions : undefined;
176
196
  if (workerVersioningMode === WorkerVersioningMode.VERSIONED) {
177
197
  const capability = await checkWorkerVersioningCapability(workflowService, namespace, taskQueue);
178
198
  if (capability.supported) {
@@ -226,11 +246,17 @@ export class WorkerRuntime {
226
246
  workflowConcurrency,
227
247
  activityConcurrency,
228
248
  stickySchedulingEnabled,
249
+ determinismMarkerMode,
250
+ determinismMarkerIntervalTasks,
251
+ determinismMarkerFullSnapshotIntervalTasks,
252
+ determinismMarkerSkipUnchanged,
253
+ determinismMarkerMaxDetailBytes,
229
254
  deploymentName,
230
255
  buildId,
231
256
  logLevel: config.logLevel,
232
257
  logFormat: config.logFormat,
233
258
  metricsExporter: config.metricsExporter.type,
259
+ metricsFlushIntervalMs,
234
260
  }));
235
261
  return new WorkerRuntime({
236
262
  config,
@@ -242,7 +268,9 @@ export class WorkerRuntime {
242
268
  logger,
243
269
  metricsRegistry,
244
270
  metricsExporter,
271
+ metricsFlushIntervalMs,
245
272
  metrics: runtimeMetrics,
273
+ openTelemetry,
246
274
  namespace,
247
275
  taskQueue,
248
276
  identity,
@@ -252,8 +280,14 @@ export class WorkerRuntime {
252
280
  stickyQueue,
253
281
  stickyScheduleToStartTimeoutMs,
254
282
  deploymentOptions,
283
+ rpcDeploymentOptions,
255
284
  versioningBehavior,
256
285
  stickySchedulingEnabled,
286
+ determinismMarkerMode,
287
+ determinismMarkerIntervalTasks,
288
+ determinismMarkerFullSnapshotIntervalTasks,
289
+ determinismMarkerSkipUnchanged,
290
+ determinismMarkerMaxDetailBytes,
257
291
  workflowPollerCount,
258
292
  interceptors: workerInterceptors,
259
293
  });
@@ -268,6 +302,9 @@ export class WorkerRuntime {
268
302
  #metricsRegistry;
269
303
  #metrics;
270
304
  #metricsExporter;
305
+ #metricsFlushIntervalMs;
306
+ #metricsFlushInFlight = false;
307
+ #openTelemetry;
271
308
  #namespace;
272
309
  #taskQueue;
273
310
  #identity;
@@ -277,8 +314,14 @@ export class WorkerRuntime {
277
314
  #stickyCache;
278
315
  #stickyQueue;
279
316
  #stickySchedulingEnabled;
317
+ #determinismMarkerMode;
318
+ #determinismMarkerIntervalTasks;
319
+ #determinismMarkerFullSnapshotIntervalTasks;
320
+ #determinismMarkerSkipUnchanged;
321
+ #determinismMarkerMaxDetailBytes;
280
322
  #stickyAttributes;
281
323
  #deploymentOptions;
324
+ #rpcDeploymentOptions;
282
325
  #versioningBehavior;
283
326
  #workflowPollerCount;
284
327
  #running = false;
@@ -296,6 +339,8 @@ export class WorkerRuntime {
296
339
  this.#metricsRegistry = params.metricsRegistry;
297
340
  this.#metrics = params.metrics;
298
341
  this.#metricsExporter = params.metricsExporter;
342
+ this.#metricsFlushIntervalMs = Math.max(0, params.metricsFlushIntervalMs);
343
+ this.#openTelemetry = params.openTelemetry;
299
344
  this.#namespace = params.namespace;
300
345
  this.#taskQueue = params.taskQueue;
301
346
  this.#identity = params.identity;
@@ -305,7 +350,13 @@ export class WorkerRuntime {
305
350
  this.#stickyCache = params.stickyCache;
306
351
  this.#stickyQueue = params.stickyQueue;
307
352
  this.#stickySchedulingEnabled = params.stickySchedulingEnabled;
353
+ this.#determinismMarkerMode = params.determinismMarkerMode;
354
+ this.#determinismMarkerIntervalTasks = Math.max(1, params.determinismMarkerIntervalTasks);
355
+ this.#determinismMarkerFullSnapshotIntervalTasks = Math.max(1, params.determinismMarkerFullSnapshotIntervalTasks);
356
+ this.#determinismMarkerSkipUnchanged = params.determinismMarkerSkipUnchanged;
357
+ this.#determinismMarkerMaxDetailBytes = Math.max(0, params.determinismMarkerMaxDetailBytes);
308
358
  this.#deploymentOptions = params.deploymentOptions;
359
+ this.#rpcDeploymentOptions = params.rpcDeploymentOptions;
309
360
  this.#versioningBehavior = params.versioningBehavior;
310
361
  this.#workflowPollerCount = params.workflowPollerCount;
311
362
  this.#stickyAttributes = create(StickyExecutionAttributesSchema, {
@@ -406,6 +457,7 @@ export class WorkerRuntime {
406
457
  if (!runtimeFiber) {
407
458
  await this.#stopScheduler();
408
459
  await this.#flushMetrics();
460
+ await this.#shutdownOpenTelemetry();
409
461
  this.#running = false;
410
462
  this.#log('info', 'temporal worker shutdown complete', this.#runtimeLogFields({ drained: false }));
411
463
  return;
@@ -421,6 +473,7 @@ export class WorkerRuntime {
421
473
  this.#runFiber = null;
422
474
  this.#running = false;
423
475
  }
476
+ await this.#shutdownOpenTelemetry();
424
477
  this.#log('info', 'temporal worker shutdown complete', this.#runtimeLogFields({ drained: true }));
425
478
  }
426
479
  #buildRuntimeEffect() {
@@ -437,6 +490,10 @@ export class WorkerRuntime {
437
490
  workflowPollers: runtime.#workflowPollerCount,
438
491
  stickySchedulingEnabled: runtime.#stickySchedulingEnabled,
439
492
  }));
493
+ if (runtime.#metricsFlushIntervalMs > 0) {
494
+ const flushLoop = Effect.repeat(Effect.promise(() => runtime.#flushMetrics()), Schedule.spaced(Duration.millis(runtime.#metricsFlushIntervalMs)));
495
+ yield* Effect.forkScoped(flushLoop);
496
+ }
440
497
  if (pollerLoops.length > 0) {
441
498
  yield* Effect.forEach(pollerLoops, (loop) => Effect.forkScoped(loop), { concurrency: 'unbounded' });
442
499
  }
@@ -444,9 +501,16 @@ export class WorkerRuntime {
444
501
  })).pipe(Effect.ensuring(Effect.promise(async () => {
445
502
  await runtime.#stopScheduler();
446
503
  await runtime.#flushMetrics();
504
+ await runtime.#shutdownOpenTelemetry();
447
505
  runtime.#log('info', 'temporal worker runtime stopped', runtime.#runtimeLogFields());
448
506
  })));
449
507
  }
508
+ async #shutdownOpenTelemetry() {
509
+ if (!this.#openTelemetry) {
510
+ return;
511
+ }
512
+ await this.#openTelemetry.shutdown();
513
+ }
450
514
  async #awaitRuntimeFiber(fiber) {
451
515
  const exit = await Effect.runPromiseExit(Fiber.join(fiber));
452
516
  if (Exit.isFailure(exit)) {
@@ -482,7 +546,7 @@ export class WorkerRuntime {
482
546
  namespace: this.#namespace,
483
547
  taskQueue: create(TaskQueueSchema, { name: queueName }),
484
548
  identity: this.#identity,
485
- deploymentOptions: this.#deploymentOptions,
549
+ deploymentOptions: this.#rpcDeploymentOptions,
486
550
  });
487
551
  const pollOnce = this.#withRpcAbort(async (signal) => {
488
552
  const start = Date.now();
@@ -516,7 +580,7 @@ export class WorkerRuntime {
516
580
  namespace: this.#namespace,
517
581
  taskQueue: create(TaskQueueSchema, { name: this.#taskQueue }),
518
582
  identity: this.#identity,
519
- deploymentOptions: this.#deploymentOptions,
583
+ deploymentOptions: this.#rpcDeploymentOptions,
520
584
  });
521
585
  const pollOnce = this.#withRpcAbort(async (signal) => {
522
586
  const start = Date.now();
@@ -638,9 +702,20 @@ export class WorkerRuntime {
638
702
  if (isLegacyQueryTask) {
639
703
  this.#incrementCounter(this.#metrics.queryTaskStarted);
640
704
  }
641
- const historyEvents = await this.#collectWorkflowHistory(execution, response);
705
+ const queryCount = response.queries && typeof response.queries === 'object' && !Array.isArray(response.queries)
706
+ ? Object.keys(response.queries).length
707
+ : Array.isArray(response.queries)
708
+ ? response.queries.length
709
+ : 0;
710
+ const hasHistoryEvents = (response.history?.events?.length ?? 0) > 0;
711
+ const hasMoreHistory = (response.nextPageToken?.length ?? 0) > 0;
712
+ const isLegacyQueryOnly = isLegacyQueryTask && !hasHistoryEvents && !hasMoreHistory;
713
+ const hasQueryPayloads = Boolean(response.query) || queryCount > 0;
714
+ const historyEvents = await this.#collectWorkflowHistory(execution, response, {
715
+ forceFullHistory: (hasQueryPayloads && !isLegacyQueryOnly) || nondeterminismRetry > 0,
716
+ skipFetchOnMissingStart: isLegacyQueryOnly,
717
+ });
642
718
  const workflowType = this.#resolveWorkflowType(response, historyEvents);
643
- const args = await this.#decodeWorkflowArgs(historyEvents);
644
719
  const workflowInfo = this.#buildWorkflowInfo(workflowType, execution);
645
720
  const collectedUpdates = await collectWorkflowUpdates({
646
721
  messages: response.messages ?? [],
@@ -677,6 +752,26 @@ export class WorkerRuntime {
677
752
  });
678
753
  const stickyKey = this.#buildStickyKey(execution.workflowId, execution.runId);
679
754
  const stickyEntry = stickyKey ? await this.#getStickyEntry(stickyKey) : undefined;
755
+ const decodedArgs = await this.#decodeWorkflowArgs(historyEvents);
756
+ let args = decodedArgs.args;
757
+ let argsSource = decodedArgs.hasStartEvent ? 'history' : 'unknown';
758
+ if (argsSource === 'unknown' && stickyEntry?.workflowArguments !== undefined) {
759
+ args = stickyEntry.workflowArguments;
760
+ argsSource = 'sticky';
761
+ }
762
+ if (argsSource === 'unknown' && !isLegacyQueryOnly) {
763
+ const fetchedArgs = await this.#fetchWorkflowStartArguments(execution);
764
+ if (fetchedArgs !== undefined) {
765
+ args = fetchedArgs;
766
+ argsSource = 'fetch';
767
+ }
768
+ }
769
+ if (argsSource !== 'history') {
770
+ this.#log('debug', 'workflow arguments resolved from fallback source', {
771
+ ...baseLogFields,
772
+ argsSource,
773
+ });
774
+ }
680
775
  const historyReplay = await this.#ingestDeterminismState(workflowInfo, historyEvents, {
681
776
  queryRequests,
682
777
  });
@@ -729,6 +824,19 @@ export class WorkerRuntime {
729
824
  try {
730
825
  const { results: activityResults, scheduledEventIds: activityScheduleEventIds } = await this.#extractActivityResolutions(historyEvents);
731
826
  const timerResults = await this.#extractTimerResolutions(historyEvents);
827
+ const mergedActivityResults = new Map(stickyEntry?.activityResults ?? []);
828
+ for (const [activityId, resolution] of activityResults.entries()) {
829
+ mergedActivityResults.set(activityId, resolution);
830
+ }
831
+ const mergedScheduleEventIds = new Map(stickyEntry?.activityScheduleEventIds ?? []);
832
+ for (const [activityId, scheduleId] of activityScheduleEventIds.entries()) {
833
+ mergedScheduleEventIds.set(activityId, scheduleId);
834
+ }
835
+ const mergedTimerResults = new Set(stickyEntry?.timerResults ?? []);
836
+ for (const timerId of timerResults) {
837
+ mergedTimerResults.add(timerId);
838
+ }
839
+ const pendingChildWorkflows = await this.#extractPendingChildWorkflows(historyEvents);
732
840
  const replayUpdates = historyReplay?.updates ?? [];
733
841
  const mergedUpdates = mergeUpdateInvocations(replayUpdates, collectedUpdates.invocations);
734
842
  const output = await this.#executor.execute({
@@ -739,10 +847,11 @@ export class WorkerRuntime {
739
847
  taskQueue: this.#taskQueue,
740
848
  arguments: args,
741
849
  determinismState: previousState,
742
- activityResults,
743
- activityScheduleEventIds,
850
+ activityResults: mergedActivityResults,
851
+ activityScheduleEventIds: mergedScheduleEventIds,
852
+ pendingChildWorkflows,
744
853
  signalDeliveries,
745
- timerResults,
854
+ timerResults: mergedTimerResults,
746
855
  queryRequests,
747
856
  updates: mergedUpdates,
748
857
  mode: isLegacyQueryTask ? 'query' : 'workflow',
@@ -788,8 +897,18 @@ export class WorkerRuntime {
788
897
  return;
789
898
  }
790
899
  const cacheBaselineEventId = this.#resolveCurrentStartedEventId(response) ?? historyReplay?.lastEventId ?? null;
791
- const shouldRecordMarker = output.completion === 'pending';
792
900
  let commandsForResponse = output.commands;
901
+ const workflowTaskCount = output.completion === 'pending'
902
+ ? (stickyEntry?.workflowTaskCount ?? 0) + 1
903
+ : (stickyEntry?.workflowTaskCount ?? 0);
904
+ let markerHash = stickyEntry?.lastDeterminismMarkerHash;
905
+ let lastMarkerTask = stickyEntry?.lastDeterminismMarkerTask;
906
+ let lastFullSnapshotTask = stickyEntry?.lastDeterminismFullSnapshotTask;
907
+ let lastMarkerState = stickyEntry?.lastDeterminismMarkerState;
908
+ let markerType = output.completion === 'pending' ? this.#resolveDeterminismMarkerType(workflowTaskCount, stickyEntry) : null;
909
+ let determinismDelta;
910
+ let markerLastEventId = null;
911
+ const markerBaseState = historyReplay?.markerState ?? lastMarkerState;
793
912
  const dispatchesForNewMessages = (output.updateDispatches ?? []).filter((dispatch) => {
794
913
  if (dispatch.type === 'acceptance' || dispatch.type === 'rejection') {
795
914
  return collectedUpdates.requestsByUpdateId.has(dispatch.updateId);
@@ -804,41 +923,153 @@ export class WorkerRuntime {
804
923
  defaultIdentity: this.#identity,
805
924
  log: (level, message, fields) => this.#log(level, message, fields),
806
925
  });
807
- if (stickyKey) {
808
- if (output.completion === 'pending') {
809
- await this.#upsertStickyEntry(stickyKey, output.determinismState, cacheBaselineEventId, workflowType);
810
- this.#log('debug', 'sticky cache snapshot persisted', {
926
+ if (markerType === 'delta') {
927
+ if (!markerBaseState) {
928
+ this.#log('warn', 'determinism delta base unavailable; falling back to full snapshot', {
811
929
  ...baseLogFields,
812
- cacheBaselineEventId,
813
930
  });
931
+ markerType = 'full';
814
932
  }
815
933
  else {
816
- await this.#removeStickyEntry(stickyKey);
817
- await this.#removeStickyEntriesForWorkflow(stickyKey.workflowId);
818
- this.#log('debug', 'sticky cache entry cleared (workflow completed)', baseLogFields);
934
+ determinismDelta = this.#buildDeterminismDelta(markerBaseState, output.determinismState);
935
+ }
936
+ if (!determinismDelta) {
937
+ markerType = 'full';
938
+ }
939
+ else if (this.#determinismMarkerSkipUnchanged && this.#isDeterminismDeltaEmpty(determinismDelta)) {
940
+ markerType = null;
819
941
  }
820
942
  }
821
- if (shouldRecordMarker) {
822
- const lastEventId = historyReplay?.lastEventId ??
823
- this.#resolveWorkflowHistoryLastEventId(response) ??
824
- stickyEntry?.lastEventId ??
825
- null;
826
- const markerDetails = await Effect.runPromise(encodeDeterminismMarkerDetails(this.#dataConverter, {
827
- info: workflowInfo,
828
- determinismState: output.determinismState,
829
- lastEventId,
830
- }));
831
- const markerCommand = this.#buildDeterminismMarkerCommand(markerDetails);
832
- commandsForResponse = this.#injectDeterminismMarker(commandsForResponse, markerCommand);
943
+ if (markerType) {
944
+ markerLastEventId =
945
+ historyReplay?.lastEventId ??
946
+ this.#resolveWorkflowHistoryLastEventId(response) ??
947
+ stickyEntry?.lastEventId ??
948
+ null;
949
+ let resolvedMarkerType = markerType;
950
+ let markerDetails = null;
951
+ let markerSizeBytes = 0;
952
+ const limitBytes = this.#determinismMarkerMaxDetailBytes;
953
+ const markerHashCandidate = this.#hashDeterminismMarker(this.#buildDeterminismMarkerSignature(output.determinismState));
954
+ if (this.#determinismMarkerSkipUnchanged && markerHashCandidate === stickyEntry?.lastDeterminismMarkerHash) {
955
+ resolvedMarkerType = null;
956
+ }
957
+ if (resolvedMarkerType) {
958
+ const buildMarkerDetails = async (targetType) => Effect.runPromise(encodeDeterminismMarkerDetailsWithSize(this.#dataConverter, {
959
+ info: workflowInfo,
960
+ determinismState: output.determinismState,
961
+ determinismDelta: targetType === 'delta' ? determinismDelta : undefined,
962
+ markerType: targetType,
963
+ lastEventId: markerLastEventId,
964
+ }));
965
+ if (resolvedMarkerType === 'full') {
966
+ const fullResult = await buildMarkerDetails('full');
967
+ if (fullResult.sizeBytes > limitBytes && determinismDelta) {
968
+ this.#log('debug', 'determinism full marker payload exceeds size limit; falling back to delta', {
969
+ ...baseLogFields,
970
+ sizeBytes: fullResult.sizeBytes,
971
+ limitBytes,
972
+ });
973
+ const deltaResult = await buildMarkerDetails('delta');
974
+ if (deltaResult.sizeBytes <= limitBytes) {
975
+ resolvedMarkerType = 'delta';
976
+ markerDetails = deltaResult.details;
977
+ markerSizeBytes = deltaResult.sizeBytes;
978
+ }
979
+ else {
980
+ this.#log('warn', 'determinism marker payload exceeds size limit', {
981
+ ...baseLogFields,
982
+ markerType: 'delta',
983
+ sizeBytes: deltaResult.sizeBytes,
984
+ limitBytes,
985
+ });
986
+ resolvedMarkerType = null;
987
+ }
988
+ }
989
+ else if (fullResult.sizeBytes <= limitBytes) {
990
+ markerDetails = fullResult.details;
991
+ markerSizeBytes = fullResult.sizeBytes;
992
+ }
993
+ else {
994
+ this.#log('warn', 'determinism marker payload exceeds size limit', {
995
+ ...baseLogFields,
996
+ markerType: 'full',
997
+ sizeBytes: fullResult.sizeBytes,
998
+ limitBytes,
999
+ });
1000
+ resolvedMarkerType = null;
1001
+ }
1002
+ }
1003
+ else if (resolvedMarkerType === 'delta') {
1004
+ const deltaResult = await buildMarkerDetails('delta');
1005
+ if (deltaResult.sizeBytes <= limitBytes) {
1006
+ markerDetails = deltaResult.details;
1007
+ markerSizeBytes = deltaResult.sizeBytes;
1008
+ }
1009
+ else {
1010
+ this.#log('warn', 'determinism marker payload exceeds size limit', {
1011
+ ...baseLogFields,
1012
+ markerType: 'delta',
1013
+ sizeBytes: deltaResult.sizeBytes,
1014
+ limitBytes,
1015
+ });
1016
+ resolvedMarkerType = null;
1017
+ }
1018
+ }
1019
+ }
1020
+ if (resolvedMarkerType) {
1021
+ markerHash = markerHashCandidate;
1022
+ lastMarkerTask = workflowTaskCount;
1023
+ if (resolvedMarkerType === 'full') {
1024
+ lastFullSnapshotTask = workflowTaskCount;
1025
+ }
1026
+ lastMarkerState = output.determinismState;
1027
+ if (markerDetails) {
1028
+ this.#log('debug', 'determinism marker recorded', {
1029
+ ...baseLogFields,
1030
+ markerType: resolvedMarkerType,
1031
+ sizeBytes: markerSizeBytes,
1032
+ });
1033
+ const markerCommand = this.#buildDeterminismMarkerCommand(markerDetails);
1034
+ commandsForResponse = this.#injectDeterminismMarker(commandsForResponse, markerCommand);
1035
+ }
1036
+ }
1037
+ }
1038
+ let pendingStickyUpdate = null;
1039
+ let shouldClearSticky = false;
1040
+ if (stickyKey) {
1041
+ if (output.completion === 'pending') {
1042
+ const resolvedWorkflowArgs = argsSource === 'unknown' ? stickyEntry?.workflowArguments : args;
1043
+ pendingStickyUpdate = {
1044
+ determinismState: output.determinismState,
1045
+ cacheBaselineEventId,
1046
+ workflowType,
1047
+ metadata: {
1048
+ workflowTaskCount,
1049
+ lastDeterminismMarkerHash: markerHash,
1050
+ lastDeterminismMarkerTask: lastMarkerTask,
1051
+ lastDeterminismFullSnapshotTask: lastFullSnapshotTask,
1052
+ ...(lastMarkerState ? { lastDeterminismMarkerState: lastMarkerState } : {}),
1053
+ ...(resolvedWorkflowArgs !== undefined ? { workflowArguments: resolvedWorkflowArgs } : {}),
1054
+ activityResults: mergedActivityResults,
1055
+ activityScheduleEventIds: mergedScheduleEventIds,
1056
+ timerResults: mergedTimerResults,
1057
+ },
1058
+ };
1059
+ }
1060
+ else {
1061
+ shouldClearSticky = true;
1062
+ }
833
1063
  }
834
1064
  const shouldRespondWorkflowTask = hasMultiQueries || !hasLegacyQueries;
1065
+ let workflowTaskCommitted = false;
835
1066
  if (shouldRespondWorkflowTask) {
836
1067
  const completion = create(RespondWorkflowTaskCompletedRequestSchema, {
837
1068
  taskToken: response.taskToken,
838
1069
  commands: commandsForResponse,
839
1070
  identity: this.#identity,
840
1071
  namespace: this.#namespace,
841
- deploymentOptions: this.#deploymentOptions,
1072
+ deploymentOptions: this.#rpcDeploymentOptions,
842
1073
  queryResults: multiQueryResults,
843
1074
  ...(this.#stickySchedulingEnabled && !hasLegacyQueries ? { stickyAttributes: this.#stickyAttributes } : {}),
844
1075
  ...(this.#versioningBehavior !== null ? { versioningBehavior: this.#versioningBehavior } : {}),
@@ -846,6 +1077,7 @@ export class WorkerRuntime {
846
1077
  });
847
1078
  try {
848
1079
  await this.#workflowService.respondWorkflowTaskCompleted(completion, { timeoutMs: RESPOND_TIMEOUT_MS });
1080
+ workflowTaskCommitted = true;
849
1081
  }
850
1082
  catch (rpcError) {
851
1083
  this.#log('error', 'debug: respondWorkflowTaskCompleted failed', {
@@ -854,6 +1086,10 @@ export class WorkerRuntime {
854
1086
  });
855
1087
  if (this.#isTaskNotFoundError(rpcError)) {
856
1088
  this.#logWorkflowTaskNotFound('respondWorkflowTaskCompleted', execution);
1089
+ if (stickyKey) {
1090
+ await this.#removeStickyEntry(stickyKey);
1091
+ await this.#removeStickyEntriesForWorkflow(stickyKey.workflowId);
1092
+ }
857
1093
  return;
858
1094
  }
859
1095
  throw rpcError;
@@ -862,6 +1098,20 @@ export class WorkerRuntime {
862
1098
  if (legacyQueryResult) {
863
1099
  await this.#respondLegacyQueryTask(response, legacyQueryResult);
864
1100
  }
1101
+ if (stickyKey && workflowTaskCommitted) {
1102
+ if (pendingStickyUpdate) {
1103
+ await this.#upsertStickyEntry(stickyKey, pendingStickyUpdate.determinismState, pendingStickyUpdate.cacheBaselineEventId, pendingStickyUpdate.workflowType, pendingStickyUpdate.metadata);
1104
+ this.#log('debug', 'sticky cache snapshot persisted', {
1105
+ ...baseLogFields,
1106
+ cacheBaselineEventId: pendingStickyUpdate.cacheBaselineEventId,
1107
+ });
1108
+ }
1109
+ else if (shouldClearSticky) {
1110
+ await this.#removeStickyEntry(stickyKey);
1111
+ await this.#removeStickyEntriesForWorkflow(stickyKey.workflowId);
1112
+ this.#log('debug', 'sticky cache entry cleared (workflow completed)', baseLogFields);
1113
+ }
1114
+ }
865
1115
  }
866
1116
  catch (error) {
867
1117
  let stickyEntryCleared = false;
@@ -950,20 +1200,93 @@ export class WorkerRuntime {
950
1200
  queries: options?.queryRequests,
951
1201
  }));
952
1202
  }
953
- async #collectWorkflowHistory(execution, _response) {
1203
+ async #collectWorkflowHistory(execution, response, options) {
954
1204
  const events = [];
955
- let token;
956
- while (true) {
1205
+ const initialEvents = response.history?.events ?? [];
1206
+ if (initialEvents.length > 0) {
1207
+ events.push(...initialEvents);
1208
+ }
1209
+ let token = response.nextPageToken && response.nextPageToken.length > 0 ? response.nextPageToken : undefined;
1210
+ while (token && token.length > 0) {
957
1211
  const page = await this.#fetchWorkflowHistoryPage(execution, token);
958
1212
  if (page.events.length > 0) {
959
1213
  events.push(...page.events);
960
1214
  }
961
- if (!page.nextPageToken || page.nextPageToken.length === 0) {
1215
+ token = page.nextPageToken && page.nextPageToken.length > 0 ? page.nextPageToken : undefined;
1216
+ }
1217
+ let sorted = this.#sortHistoryEvents(events);
1218
+ const shouldFetchFullHistory = options?.forceFullHistory || (!options?.skipFetchOnMissingStart && !this.#findWorkflowStartedEvent(sorted));
1219
+ if (shouldFetchFullHistory) {
1220
+ const fullHistory = await this.#fetchWorkflowHistoryFromStart(execution);
1221
+ if (fullHistory.length > 0) {
1222
+ sorted = this.#sortHistoryEvents([...fullHistory, ...sorted]);
1223
+ }
1224
+ }
1225
+ const bounded = this.#limitHistoryToStartedEvent(sorted, response.startedEventId);
1226
+ return this.#dedupeHistoryEvents(bounded);
1227
+ }
1228
+ #sortHistoryEvents(events) {
1229
+ return events.slice().sort((left, right) => {
1230
+ const leftId = this.#resolveEventIdForSort(left.eventId);
1231
+ const rightId = this.#resolveEventIdForSort(right.eventId);
1232
+ if (leftId === rightId) {
1233
+ return 0;
1234
+ }
1235
+ return leftId < rightId ? -1 : 1;
1236
+ });
1237
+ }
1238
+ #dedupeHistoryEvents(events) {
1239
+ if (events.length <= 1) {
1240
+ return events;
1241
+ }
1242
+ const deduped = [];
1243
+ let lastId = null;
1244
+ for (const event of events) {
1245
+ const currentId = this.#resolveEventIdForSort(event.eventId);
1246
+ if (currentId !== 0n) {
1247
+ if (lastId !== null && currentId === lastId) {
1248
+ continue;
1249
+ }
1250
+ lastId = currentId;
1251
+ }
1252
+ deduped.push(event);
1253
+ }
1254
+ return deduped;
1255
+ }
1256
+ #limitHistoryToStartedEvent(events, startedEventId) {
1257
+ const limit = this.#resolveEventIdForSort(startedEventId);
1258
+ if (limit <= 0n) {
1259
+ return events;
1260
+ }
1261
+ const bounded = [];
1262
+ for (const event of events) {
1263
+ const currentId = this.#resolveEventIdForSort(event.eventId);
1264
+ if (currentId !== 0n && currentId > limit) {
962
1265
  break;
963
1266
  }
964
- token = page.nextPageToken;
1267
+ bounded.push(event);
965
1268
  }
966
- return events;
1269
+ return bounded;
1270
+ }
1271
+ #resolveEventIdForSort(value) {
1272
+ if (value === undefined || value === null) {
1273
+ return 0n;
1274
+ }
1275
+ if (typeof value === 'bigint') {
1276
+ return value;
1277
+ }
1278
+ if (typeof value === 'number' && Number.isFinite(value)) {
1279
+ return BigInt(value);
1280
+ }
1281
+ if (typeof value === 'string' && value.length > 0) {
1282
+ try {
1283
+ return BigInt(value);
1284
+ }
1285
+ catch {
1286
+ return 0n;
1287
+ }
1288
+ }
1289
+ return 0n;
967
1290
  }
968
1291
  async #extractActivityResolutions(events) {
969
1292
  const resolutions = new Map();
@@ -1077,28 +1400,86 @@ export class WorkerRuntime {
1077
1400
  }
1078
1401
  return value.toString();
1079
1402
  };
1080
- for (const event of events) {
1081
- if (event.eventType !== EventType.WORKFLOW_EXECUTION_SIGNALED) {
1082
- continue;
1083
- }
1084
- if (event.attributes?.case !== 'workflowExecutionSignaledEventAttributes') {
1085
- continue;
1403
+ const recordChildCompletion = (event, status, attrs) => {
1404
+ const workflowId = attrs.workflowExecution?.workflowId;
1405
+ if (!workflowId) {
1406
+ return;
1086
1407
  }
1087
- const attrs = event.attributes.value;
1088
- const args = await decodePayloadsToValues(this.#dataConverter, attrs.input?.payloads ?? []);
1089
- const workflowTaskCompletedEventId = 'workflowTaskCompletedEventId' in attrs
1090
- ? normalizeEventId(attrs
1091
- .workflowTaskCompletedEventId)
1092
- : null;
1093
1408
  deliveries.push({
1094
- name: attrs.signalName ?? 'unknown',
1095
- args,
1409
+ name: CHILD_WORKFLOW_COMPLETED_SIGNAL,
1410
+ args: [
1411
+ {
1412
+ workflowId,
1413
+ ...(attrs.workflowExecution?.runId ? { runId: attrs.workflowExecution.runId } : {}),
1414
+ status,
1415
+ },
1416
+ ],
1096
1417
  metadata: {
1097
1418
  eventId: normalizeEventId(event.eventId),
1098
- workflowTaskCompletedEventId,
1099
- identity: attrs.identity ?? null,
1100
1419
  },
1101
1420
  });
1421
+ };
1422
+ for (const event of events) {
1423
+ switch (event.eventType) {
1424
+ case EventType.WORKFLOW_EXECUTION_SIGNALED: {
1425
+ if (event.attributes?.case !== 'workflowExecutionSignaledEventAttributes') {
1426
+ break;
1427
+ }
1428
+ const attrs = event.attributes.value;
1429
+ const args = await decodePayloadsToValues(this.#dataConverter, attrs.input?.payloads ?? []);
1430
+ const workflowTaskCompletedEventId = 'workflowTaskCompletedEventId' in attrs
1431
+ ? normalizeEventId(attrs
1432
+ .workflowTaskCompletedEventId)
1433
+ : null;
1434
+ deliveries.push({
1435
+ name: attrs.signalName ?? 'unknown',
1436
+ args,
1437
+ metadata: {
1438
+ eventId: normalizeEventId(event.eventId),
1439
+ workflowTaskCompletedEventId,
1440
+ identity: attrs.identity ?? null,
1441
+ },
1442
+ });
1443
+ break;
1444
+ }
1445
+ case EventType.CHILD_WORKFLOW_EXECUTION_COMPLETED: {
1446
+ if (event.attributes?.case !== 'childWorkflowExecutionCompletedEventAttributes') {
1447
+ break;
1448
+ }
1449
+ recordChildCompletion(event, 'completed', event.attributes.value);
1450
+ break;
1451
+ }
1452
+ case EventType.CHILD_WORKFLOW_EXECUTION_FAILED: {
1453
+ if (event.attributes?.case !== 'childWorkflowExecutionFailedEventAttributes') {
1454
+ break;
1455
+ }
1456
+ recordChildCompletion(event, 'failed', event.attributes.value);
1457
+ break;
1458
+ }
1459
+ case EventType.CHILD_WORKFLOW_EXECUTION_CANCELED: {
1460
+ if (event.attributes?.case !== 'childWorkflowExecutionCanceledEventAttributes') {
1461
+ break;
1462
+ }
1463
+ recordChildCompletion(event, 'canceled', event.attributes.value);
1464
+ break;
1465
+ }
1466
+ case EventType.CHILD_WORKFLOW_EXECUTION_TERMINATED: {
1467
+ if (event.attributes?.case !== 'childWorkflowExecutionTerminatedEventAttributes') {
1468
+ break;
1469
+ }
1470
+ recordChildCompletion(event, 'terminated', event.attributes.value);
1471
+ break;
1472
+ }
1473
+ case EventType.CHILD_WORKFLOW_EXECUTION_TIMED_OUT: {
1474
+ if (event.attributes?.case !== 'childWorkflowExecutionTimedOutEventAttributes') {
1475
+ break;
1476
+ }
1477
+ recordChildCompletion(event, 'timed_out', event.attributes.value);
1478
+ break;
1479
+ }
1480
+ default:
1481
+ break;
1482
+ }
1102
1483
  }
1103
1484
  return deliveries;
1104
1485
  }
@@ -1118,6 +1499,56 @@ export class WorkerRuntime {
1118
1499
  }
1119
1500
  return fired;
1120
1501
  }
1502
+ async #extractPendingChildWorkflows(events) {
1503
+ const pending = new Map();
1504
+ const normalizeEventId = (value) => {
1505
+ if (value === undefined || value === null) {
1506
+ return null;
1507
+ }
1508
+ if (typeof value === 'string') {
1509
+ return value;
1510
+ }
1511
+ return value.toString();
1512
+ };
1513
+ for (const event of events) {
1514
+ switch (event.eventType) {
1515
+ case EventType.START_CHILD_WORKFLOW_EXECUTION_INITIATED: {
1516
+ if (event.attributes?.case !== 'startChildWorkflowExecutionInitiatedEventAttributes') {
1517
+ break;
1518
+ }
1519
+ const attrs = event.attributes.value;
1520
+ const initiatedId = normalizeEventId(event.eventId);
1521
+ if (initiatedId && attrs.workflowId) {
1522
+ pending.set(initiatedId, attrs.workflowId);
1523
+ }
1524
+ break;
1525
+ }
1526
+ case EventType.CHILD_WORKFLOW_EXECUTION_STARTED: {
1527
+ if (event.attributes?.case !== 'childWorkflowExecutionStartedEventAttributes') {
1528
+ break;
1529
+ }
1530
+ const initiatedId = normalizeEventId(event.attributes.value.initiatedEventId);
1531
+ if (initiatedId) {
1532
+ pending.delete(initiatedId);
1533
+ }
1534
+ break;
1535
+ }
1536
+ case EventType.START_CHILD_WORKFLOW_EXECUTION_FAILED: {
1537
+ if (event.attributes?.case !== 'startChildWorkflowExecutionFailedEventAttributes') {
1538
+ break;
1539
+ }
1540
+ const initiatedId = normalizeEventId(event.attributes.value.initiatedEventId);
1541
+ if (initiatedId) {
1542
+ pending.delete(initiatedId);
1543
+ }
1544
+ break;
1545
+ }
1546
+ default:
1547
+ break;
1548
+ }
1549
+ }
1550
+ return new Set(pending.values());
1551
+ }
1121
1552
  async #extractWorkflowQueryRequests(response) {
1122
1553
  const requests = [];
1123
1554
  const map = response.queries ?? {};
@@ -1235,6 +1666,18 @@ export class WorkerRuntime {
1235
1666
  nextPageToken: historyResponse.nextPageToken ?? new Uint8Array(),
1236
1667
  };
1237
1668
  }
1669
+ async #fetchWorkflowHistoryFromStart(execution) {
1670
+ const events = [];
1671
+ let token;
1672
+ do {
1673
+ const page = await this.#fetchWorkflowHistoryPage(execution, token);
1674
+ if (page.events.length > 0) {
1675
+ events.push(...page.events);
1676
+ }
1677
+ token = page.nextPageToken && page.nextPageToken.length > 0 ? page.nextPageToken : undefined;
1678
+ } while (token && token.length > 0);
1679
+ return events;
1680
+ }
1238
1681
  #buildWorkflowInfo(workflowType, execution) {
1239
1682
  return {
1240
1683
  namespace: this.#namespace,
@@ -1247,19 +1690,145 @@ export class WorkerRuntime {
1247
1690
  async #getStickyEntry(key) {
1248
1691
  return await Effect.runPromise(this.#stickyCache.get(key));
1249
1692
  }
1250
- async #upsertStickyEntry(key, state, lastEventId, workflowType) {
1693
+ async #upsertStickyEntry(key, state, lastEventId, workflowType, metadata) {
1251
1694
  const entry = {
1252
1695
  key,
1253
1696
  determinismState: state,
1254
1697
  lastEventId,
1255
1698
  lastAccessed: Date.now(),
1256
1699
  workflowType,
1700
+ ...metadata,
1257
1701
  };
1258
1702
  await Effect.runPromise(this.#stickyCache.upsert(entry));
1259
1703
  }
1260
1704
  #resolveWorkflowHistoryLastEventId(response) {
1261
1705
  return resolveHistoryLastEventId(response.history?.events ?? []);
1262
1706
  }
1707
+ #resolveDeterminismMarkerType(taskIndex, stickyEntry) {
1708
+ if (this.#determinismMarkerMode === 'never') {
1709
+ return null;
1710
+ }
1711
+ if (this.#determinismMarkerMode === 'always') {
1712
+ return 'full';
1713
+ }
1714
+ const interval = Math.max(1, this.#determinismMarkerIntervalTasks);
1715
+ const shouldRecord = taskIndex === 1 || interval === 1 || taskIndex % interval === 0;
1716
+ if (!shouldRecord) {
1717
+ return null;
1718
+ }
1719
+ if (this.#determinismMarkerMode === 'delta') {
1720
+ const lastFullSnapshot = stickyEntry?.lastDeterminismFullSnapshotTask ?? 0;
1721
+ const fullInterval = Math.max(1, this.#determinismMarkerFullSnapshotIntervalTasks);
1722
+ const fullDue = taskIndex === 1 || fullInterval === 1 || taskIndex - lastFullSnapshot >= fullInterval;
1723
+ return fullDue ? 'full' : 'delta';
1724
+ }
1725
+ return 'full';
1726
+ }
1727
+ #buildDeterminismDelta(previous, current) {
1728
+ if (!previous) {
1729
+ return undefined;
1730
+ }
1731
+ const normalizeOptional = (value) => (value === undefined ? null : value);
1732
+ const optionalEquals = (left, right) => normalizeOptional(left) === normalizeOptional(right);
1733
+ const signalsEqual = (left, right) => left.signalName === right.signalName &&
1734
+ left.handlerName === right.handlerName &&
1735
+ left.payloadHash === right.payloadHash &&
1736
+ optionalEquals(left.eventId, right.eventId) &&
1737
+ optionalEquals(left.workflowTaskCompletedEventId, right.workflowTaskCompletedEventId) &&
1738
+ optionalEquals(left.identity, right.identity);
1739
+ const queriesEqual = (left, right) => left.queryName === right.queryName &&
1740
+ left.handlerName === right.handlerName &&
1741
+ left.requestHash === right.requestHash &&
1742
+ optionalEquals(left.identity, right.identity) &&
1743
+ optionalEquals(left.queryId, right.queryId) &&
1744
+ left.resultHash === right.resultHash &&
1745
+ left.failureHash === right.failureHash;
1746
+ const updatesEqual = (left, right) => left.updateId === right.updateId &&
1747
+ left.stage === right.stage &&
1748
+ left.handlerName === right.handlerName &&
1749
+ left.identity === right.identity &&
1750
+ left.sequencingEventId === right.sequencingEventId &&
1751
+ left.messageId === right.messageId &&
1752
+ left.acceptedEventId === right.acceptedEventId &&
1753
+ left.outcome === right.outcome &&
1754
+ left.failureMessage === right.failureMessage &&
1755
+ left.historyEventId === right.historyEventId;
1756
+ const sliceAppend = (currentList, previousList, equals = Object.is) => {
1757
+ if (currentList.length < previousList.length) {
1758
+ return undefined;
1759
+ }
1760
+ for (let index = 0; index < previousList.length; index += 1) {
1761
+ if (!equals(previousList[index], currentList[index])) {
1762
+ return undefined;
1763
+ }
1764
+ }
1765
+ return currentList.slice(previousList.length);
1766
+ };
1767
+ const commandHistory = sliceAppend(current.commandHistory, previous.commandHistory, (left, right) => intentsEqual(left?.intent, right?.intent));
1768
+ const randomValues = sliceAppend(current.randomValues, previous.randomValues);
1769
+ const timeValues = sliceAppend(current.timeValues, previous.timeValues);
1770
+ const signals = sliceAppend(current.signals, previous.signals, signalsEqual);
1771
+ const queries = sliceAppend(current.queries, previous.queries, queriesEqual);
1772
+ if (!commandHistory || !randomValues || !timeValues || !signals || !queries) {
1773
+ return undefined;
1774
+ }
1775
+ const updatesPrev = previous.updates ?? [];
1776
+ const updatesNext = current.updates ?? [];
1777
+ if (updatesNext.length < updatesPrev.length) {
1778
+ return undefined;
1779
+ }
1780
+ const updates = sliceAppend(updatesNext, updatesPrev, updatesEqual);
1781
+ if (!updates) {
1782
+ return undefined;
1783
+ }
1784
+ const logCount = current.logCount !== undefined && current.logCount !== previous.logCount ? current.logCount : undefined;
1785
+ const failureMetadata = current.failureMetadata && previous.failureMetadata
1786
+ ? current.failureMetadata.eventId !== previous.failureMetadata.eventId ||
1787
+ current.failureMetadata.eventType !== previous.failureMetadata.eventType ||
1788
+ current.failureMetadata.failureType !== previous.failureMetadata.failureType ||
1789
+ current.failureMetadata.failureMessage !== previous.failureMetadata.failureMessage ||
1790
+ current.failureMetadata.retryState !== previous.failureMetadata.retryState
1791
+ ? current.failureMetadata
1792
+ : undefined
1793
+ : (current.failureMetadata ?? undefined);
1794
+ return {
1795
+ commandHistory,
1796
+ randomValues,
1797
+ timeValues,
1798
+ signals,
1799
+ queries,
1800
+ ...(updates.length > 0 ? { updates } : {}),
1801
+ ...(logCount !== undefined ? { logCount } : {}),
1802
+ ...(failureMetadata ? { failureMetadata } : {}),
1803
+ };
1804
+ }
1805
+ #isDeterminismDeltaEmpty(delta) {
1806
+ const updatesCount = delta.updates ? delta.updates.length : 0;
1807
+ return ((delta.commandHistory?.length ?? 0) === 0 &&
1808
+ (delta.randomValues?.length ?? 0) === 0 &&
1809
+ (delta.timeValues?.length ?? 0) === 0 &&
1810
+ (delta.signals?.length ?? 0) === 0 &&
1811
+ (delta.queries?.length ?? 0) === 0 &&
1812
+ updatesCount === 0 &&
1813
+ delta.logCount === undefined &&
1814
+ delta.failureMetadata === undefined);
1815
+ }
1816
+ #buildDeterminismMarkerSignature(state) {
1817
+ const failureMetadataHash = state.failureMetadata ? stableStringify(state.failureMetadata) : undefined;
1818
+ return {
1819
+ commandHistoryLength: state.commandHistory.length,
1820
+ randomValuesLength: state.randomValues.length,
1821
+ timeValuesLength: state.timeValues.length,
1822
+ signalsLength: state.signals.length,
1823
+ queriesLength: state.queries.length,
1824
+ updatesLength: state.updates?.length ?? 0,
1825
+ logCount: state.logCount ?? 0,
1826
+ ...(failureMetadataHash ? { failureMetadataHash } : {}),
1827
+ };
1828
+ }
1829
+ #hashDeterminismMarker(signature) {
1830
+ return stableStringify(signature);
1831
+ }
1263
1832
  #isTaskNotFoundError(error) {
1264
1833
  return error instanceof ConnectError && error.code === Code.NotFound;
1265
1834
  }
@@ -1506,7 +2075,7 @@ export class WorkerRuntime {
1506
2075
  failure: encoded,
1507
2076
  identity: this.#identity,
1508
2077
  namespace: this.#namespace,
1509
- deploymentOptions: this.#deploymentOptions,
2078
+ deploymentOptions: this.#rpcDeploymentOptions,
1510
2079
  });
1511
2080
  try {
1512
2081
  await this.#workflowService.respondWorkflowTaskFailed(failed, { timeoutMs: RESPOND_TIMEOUT_MS });
@@ -1606,7 +2175,7 @@ export class WorkerRuntime {
1606
2175
  identity: this.#identity,
1607
2176
  namespace: this.#namespace,
1608
2177
  result: payloads && payloads.length > 0 ? create(PayloadsSchema, { payloads }) : undefined,
1609
- deploymentOptions: this.#deploymentOptions,
2178
+ deploymentOptions: this.#rpcDeploymentOptions,
1610
2179
  });
1611
2180
  await this.#workflowService.respondActivityTaskCompleted(completion, { timeoutMs: RESPOND_TIMEOUT_MS });
1612
2181
  break;
@@ -1662,7 +2231,7 @@ export class WorkerRuntime {
1662
2231
  namespace: this.#namespace,
1663
2232
  failure: encoded,
1664
2233
  lastHeartbeatDetails,
1665
- deploymentOptions: this.#deploymentOptions,
2234
+ deploymentOptions: this.#rpcDeploymentOptions,
1666
2235
  });
1667
2236
  await this.#workflowService.respondActivityTaskFailed(request, { timeoutMs: RESPOND_TIMEOUT_MS });
1668
2237
  this.#incrementCounter(this.#metrics.activityFailures);
@@ -1674,7 +2243,7 @@ export class WorkerRuntime {
1674
2243
  identity: this.#identity,
1675
2244
  namespace: this.#namespace,
1676
2245
  details,
1677
- deploymentOptions: this.#deploymentOptions,
2246
+ deploymentOptions: this.#rpcDeploymentOptions,
1678
2247
  });
1679
2248
  await this.#workflowService.respondActivityTaskCanceled(request, { timeoutMs: RESPOND_TIMEOUT_MS });
1680
2249
  }
@@ -1789,6 +2358,10 @@ export class WorkerRuntime {
1789
2358
  return scheduleDeadline ?? startDeadline ?? undefined;
1790
2359
  }
1791
2360
  async #flushMetrics() {
2361
+ if (this.#metricsFlushInFlight) {
2362
+ return;
2363
+ }
2364
+ this.#metricsFlushInFlight = true;
1792
2365
  try {
1793
2366
  await Effect.runPromise(this.#metricsExporter.flush());
1794
2367
  }
@@ -1797,21 +2370,36 @@ export class WorkerRuntime {
1797
2370
  error: error instanceof Error ? error.message : String(error),
1798
2371
  });
1799
2372
  }
2373
+ finally {
2374
+ this.#metricsFlushInFlight = false;
2375
+ }
1800
2376
  }
1801
2377
  async #decodeWorkflowArgs(events) {
1802
2378
  const startEvent = this.#findWorkflowStartedEvent(events);
1803
2379
  if (!startEvent) {
1804
- return [];
2380
+ return { args: [], hasStartEvent: false };
1805
2381
  }
2382
+ const args = (await this.#decodeWorkflowArgsFromStartEvent(startEvent)) ?? [];
2383
+ return { args, hasStartEvent: true };
2384
+ }
2385
+ async #decodeWorkflowArgsFromStartEvent(startEvent) {
1806
2386
  const attributes = startEvent.attributes?.case === 'workflowExecutionStartedEventAttributes'
1807
2387
  ? startEvent.attributes.value
1808
2388
  : undefined;
1809
2389
  if (!attributes) {
1810
- return [];
2390
+ return undefined;
1811
2391
  }
1812
2392
  const inputPayloads = attributes.input?.payloads ?? [];
1813
2393
  return await decodePayloadsToValues(this.#dataConverter, inputPayloads);
1814
2394
  }
2395
+ async #fetchWorkflowStartArguments(execution) {
2396
+ const page = await this.#fetchWorkflowHistoryPage(execution);
2397
+ const startEvent = this.#findWorkflowStartedEvent(page.events);
2398
+ if (!startEvent) {
2399
+ return undefined;
2400
+ }
2401
+ return await this.#decodeWorkflowArgsFromStartEvent(startEvent);
2402
+ }
1815
2403
  #resolveWorkflowType(response, events) {
1816
2404
  if (response.workflowType?.name) {
1817
2405
  return response.workflowType.name;