@proompteng/temporal-bun-sdk 0.3.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 (170) hide show
  1. package/README.md +54 -11
  2. package/dist/src/bin/replay-command.d.ts.map +1 -1
  3. package/dist/src/bin/replay-command.js +6 -2
  4. package/dist/src/bin/replay-command.js.map +1 -1
  5. package/dist/src/bin/temporal-bun.d.ts +1 -1
  6. package/dist/src/bin/temporal-bun.d.ts.map +1 -1
  7. package/dist/src/bin/temporal-bun.js +75 -1
  8. package/dist/src/bin/temporal-bun.js.map +1 -1
  9. package/dist/src/client/layer.d.ts +2 -2
  10. package/dist/src/client/layer.d.ts.map +1 -1
  11. package/dist/src/client/retries.d.ts.map +1 -1
  12. package/dist/src/client/retries.js +27 -3
  13. package/dist/src/client/retries.js.map +1 -1
  14. package/dist/src/client/serialization.d.ts +4 -1
  15. package/dist/src/client/serialization.d.ts.map +1 -1
  16. package/dist/src/client/serialization.js +2 -2
  17. package/dist/src/client/serialization.js.map +1 -1
  18. package/dist/src/client.d.ts +12 -4
  19. package/dist/src/client.d.ts.map +1 -1
  20. package/dist/src/client.js +270 -63
  21. package/dist/src/client.js.map +1 -1
  22. package/dist/src/common/payloads/codecs.d.ts +38 -0
  23. package/dist/src/common/payloads/codecs.d.ts.map +1 -0
  24. package/dist/src/common/payloads/codecs.js +174 -0
  25. package/dist/src/common/payloads/codecs.js.map +1 -0
  26. package/dist/src/common/payloads/converter.d.ts +52 -3
  27. package/dist/src/common/payloads/converter.d.ts.map +1 -1
  28. package/dist/src/common/payloads/converter.js +340 -2
  29. package/dist/src/common/payloads/converter.js.map +1 -1
  30. package/dist/src/common/payloads/failure.d.ts +5 -6
  31. package/dist/src/common/payloads/failure.d.ts.map +1 -1
  32. package/dist/src/common/payloads/failure.js +3 -52
  33. package/dist/src/common/payloads/failure.js.map +1 -1
  34. package/dist/src/common/payloads/index.d.ts +1 -0
  35. package/dist/src/common/payloads/index.d.ts.map +1 -1
  36. package/dist/src/common/payloads/index.js +1 -0
  37. package/dist/src/common/payloads/index.js.map +1 -1
  38. package/dist/src/common/payloads/json-codec.d.ts.map +1 -1
  39. package/dist/src/common/payloads/json-codec.js +4 -6
  40. package/dist/src/common/payloads/json-codec.js.map +1 -1
  41. package/dist/src/config.d.ts +25 -0
  42. package/dist/src/config.d.ts.map +1 -1
  43. package/dist/src/config.js +111 -1
  44. package/dist/src/config.js.map +1 -1
  45. package/dist/src/interceptors/client.d.ts +28 -0
  46. package/dist/src/interceptors/client.d.ts.map +1 -0
  47. package/dist/src/interceptors/client.js +190 -0
  48. package/dist/src/interceptors/client.js.map +1 -0
  49. package/dist/src/interceptors/types.d.ts +33 -0
  50. package/dist/src/interceptors/types.d.ts.map +1 -0
  51. package/dist/src/interceptors/types.js +44 -0
  52. package/dist/src/interceptors/types.js.map +1 -0
  53. package/dist/src/interceptors/worker.d.ts +22 -0
  54. package/dist/src/interceptors/worker.d.ts.map +1 -0
  55. package/dist/src/interceptors/worker.js +177 -0
  56. package/dist/src/interceptors/worker.js.map +1 -0
  57. package/dist/src/observability/index.d.ts +4 -0
  58. package/dist/src/observability/index.d.ts.map +1 -1
  59. package/dist/src/observability/index.js +4 -0
  60. package/dist/src/observability/index.js.map +1 -1
  61. package/dist/src/observability/metrics.d.ts.map +1 -1
  62. package/dist/src/observability/metrics.js +70 -10
  63. package/dist/src/observability/metrics.js.map +1 -1
  64. package/dist/src/observability/opentelemetry.d.ts +20 -0
  65. package/dist/src/observability/opentelemetry.d.ts.map +1 -0
  66. package/dist/src/observability/opentelemetry.js +334 -0
  67. package/dist/src/observability/opentelemetry.js.map +1 -0
  68. package/dist/src/proto/temporal/api/activity/v1/message_pb.js +1 -1
  69. package/dist/src/proto/temporal/api/batch/v1/message_pb.js +1 -1
  70. package/dist/src/proto/temporal/api/command/v1/message_pb.js +1 -1
  71. package/dist/src/proto/temporal/api/common/v1/message_pb.js +1 -1
  72. package/dist/src/proto/temporal/api/deployment/v1/message_pb.js +1 -1
  73. package/dist/src/proto/temporal/api/enums/v1/batch_operation_pb.js +1 -1
  74. package/dist/src/proto/temporal/api/enums/v1/command_type_pb.js +1 -1
  75. package/dist/src/proto/temporal/api/enums/v1/common_pb.js +1 -1
  76. package/dist/src/proto/temporal/api/enums/v1/deployment_pb.js +1 -1
  77. package/dist/src/proto/temporal/api/enums/v1/event_type_pb.js +1 -1
  78. package/dist/src/proto/temporal/api/enums/v1/failed_cause_pb.js +1 -1
  79. package/dist/src/proto/temporal/api/enums/v1/namespace_pb.js +1 -1
  80. package/dist/src/proto/temporal/api/enums/v1/nexus_pb.js +1 -1
  81. package/dist/src/proto/temporal/api/enums/v1/query_pb.js +1 -1
  82. package/dist/src/proto/temporal/api/enums/v1/reset_pb.js +1 -1
  83. package/dist/src/proto/temporal/api/enums/v1/schedule_pb.js +1 -1
  84. package/dist/src/proto/temporal/api/enums/v1/task_queue_pb.js +1 -1
  85. package/dist/src/proto/temporal/api/enums/v1/update_pb.js +1 -1
  86. package/dist/src/proto/temporal/api/enums/v1/workflow_pb.js +1 -1
  87. package/dist/src/proto/temporal/api/errordetails/v1/message_pb.js +1 -1
  88. package/dist/src/proto/temporal/api/export/v1/message_pb.js +1 -1
  89. package/dist/src/proto/temporal/api/failure/v1/message_pb.js +1 -1
  90. package/dist/src/proto/temporal/api/filter/v1/message_pb.js +1 -1
  91. package/dist/src/proto/temporal/api/history/v1/message_pb.js +1 -1
  92. package/dist/src/proto/temporal/api/namespace/v1/message_pb.js +1 -1
  93. package/dist/src/proto/temporal/api/nexus/v1/message_pb.js +1 -1
  94. package/dist/src/proto/temporal/api/operatorservice/v1/request_response_pb.js +1 -1
  95. package/dist/src/proto/temporal/api/operatorservice/v1/service_pb.js +1 -1
  96. package/dist/src/proto/temporal/api/protocol/v1/message_pb.js +1 -1
  97. package/dist/src/proto/temporal/api/query/v1/message_pb.js +1 -1
  98. package/dist/src/proto/temporal/api/replication/v1/message_pb.js +1 -1
  99. package/dist/src/proto/temporal/api/rules/v1/message_pb.js +1 -1
  100. package/dist/src/proto/temporal/api/sdk/v1/enhanced_stack_trace_pb.js +1 -1
  101. package/dist/src/proto/temporal/api/sdk/v1/task_complete_metadata_pb.js +1 -1
  102. package/dist/src/proto/temporal/api/sdk/v1/user_metadata_pb.js +1 -1
  103. package/dist/src/proto/temporal/api/sdk/v1/worker_config_pb.js +1 -1
  104. package/dist/src/proto/temporal/api/sdk/v1/workflow_metadata_pb.js +1 -1
  105. package/dist/src/proto/temporal/api/taskqueue/v1/message_pb.js +1 -1
  106. package/dist/src/proto/temporal/api/update/v1/message_pb.js +1 -1
  107. package/dist/src/proto/temporal/api/version/v1/message_pb.js +1 -1
  108. package/dist/src/proto/temporal/api/worker/v1/message_pb.js +1 -1
  109. package/dist/src/proto/temporal/api/workflow/v1/message_pb.js +1 -1
  110. package/dist/src/proto/temporal/api/workflowservice/v1/request_response_pb.js +1 -1
  111. package/dist/src/proto/temporal/api/workflowservice/v1/service_pb.js +1 -1
  112. package/dist/src/runtime/cli-layer.d.ts +4 -3
  113. package/dist/src/runtime/cli-layer.d.ts.map +1 -1
  114. package/dist/src/runtime/cli-layer.js +5 -2
  115. package/dist/src/runtime/cli-layer.js.map +1 -1
  116. package/dist/src/runtime/effect-layers.d.ts +9 -0
  117. package/dist/src/runtime/effect-layers.d.ts.map +1 -1
  118. package/dist/src/runtime/effect-layers.js +15 -0
  119. package/dist/src/runtime/effect-layers.js.map +1 -1
  120. package/dist/src/worker/runtime.d.ts +6 -0
  121. package/dist/src/worker/runtime.d.ts.map +1 -1
  122. package/dist/src/worker/runtime.js +853 -70
  123. package/dist/src/worker/runtime.js.map +1 -1
  124. package/dist/src/worker/sticky-cache.d.ts +15 -0
  125. package/dist/src/worker/sticky-cache.d.ts.map +1 -1
  126. package/dist/src/worker/sticky-cache.js +26 -0
  127. package/dist/src/worker/sticky-cache.js.map +1 -1
  128. package/dist/src/worker/update-protocol.d.ts.map +1 -1
  129. package/dist/src/worker/update-protocol.js +15 -0
  130. package/dist/src/worker/update-protocol.js.map +1 -1
  131. package/dist/src/worker.js +1 -0
  132. package/dist/src/worker.js.map +1 -1
  133. package/dist/src/workflow/commands.d.ts +40 -3
  134. package/dist/src/workflow/commands.d.ts.map +1 -1
  135. package/dist/src/workflow/commands.js +161 -7
  136. package/dist/src/workflow/commands.js.map +1 -1
  137. package/dist/src/workflow/context.d.ts +59 -2
  138. package/dist/src/workflow/context.d.ts.map +1 -1
  139. package/dist/src/workflow/context.js +345 -16
  140. package/dist/src/workflow/context.js.map +1 -1
  141. package/dist/src/workflow/definition.js.map +1 -1
  142. package/dist/src/workflow/determinism.d.ts +9 -0
  143. package/dist/src/workflow/determinism.d.ts.map +1 -1
  144. package/dist/src/workflow/determinism.js +145 -16
  145. package/dist/src/workflow/determinism.js.map +1 -1
  146. package/dist/src/workflow/errors.d.ts +3 -0
  147. package/dist/src/workflow/errors.d.ts.map +1 -1
  148. package/dist/src/workflow/errors.js +6 -0
  149. package/dist/src/workflow/errors.js.map +1 -1
  150. package/dist/src/workflow/executor.d.ts +6 -0
  151. package/dist/src/workflow/executor.d.ts.map +1 -1
  152. package/dist/src/workflow/executor.js +147 -18
  153. package/dist/src/workflow/executor.js.map +1 -1
  154. package/dist/src/workflow/inbound.d.ts +1 -0
  155. package/dist/src/workflow/inbound.d.ts.map +1 -1
  156. package/dist/src/workflow/inbound.js +1 -0
  157. package/dist/src/workflow/inbound.js.map +1 -1
  158. package/dist/src/workflow/index.d.ts +5 -0
  159. package/dist/src/workflow/index.d.ts.map +1 -1
  160. package/dist/src/workflow/index.js +3 -0
  161. package/dist/src/workflow/index.js.map +1 -1
  162. package/dist/src/workflow/log.d.ts +18 -0
  163. package/dist/src/workflow/log.d.ts.map +1 -0
  164. package/dist/src/workflow/log.js +36 -0
  165. package/dist/src/workflow/log.js.map +1 -0
  166. package/dist/src/workflow/replay.d.ts +31 -3
  167. package/dist/src/workflow/replay.d.ts.map +1 -1
  168. package/dist/src/workflow/replay.js +763 -67
  169. package/dist/src/workflow/replay.js.map +1 -1
  170. package/package.json +4 -3
@@ -2,14 +2,15 @@ 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';
9
- import { createDefaultDataConverter, decodePayloadsToValues, encodeValuesToPayloads, } from '../common/payloads/converter';
9
+ import { buildCodecsFromConfig, createDefaultDataConverter, decodePayloadsToValues, encodeValuesToPayloads, } from '../common/payloads/converter';
10
10
  import { encodeErrorToFailure, encodeFailurePayloads, failureToError } from '../common/payloads/failure';
11
11
  import { sleep } from '../common/sleep';
12
12
  import { loadTemporalConfig } from '../config';
13
+ import { makeDefaultWorkerInterceptors, runWorkerInterceptors, } from '../interceptors/worker';
13
14
  import { createObservabilityServices } from '../observability';
14
15
  import { CommandSchema, RecordMarkerCommandAttributesSchema, } from '../proto/temporal/api/command/v1/message_pb';
15
16
  import { PayloadsSchema, WorkflowExecutionSchema, } from '../proto/temporal/api/common/v1/message_pb';
@@ -23,10 +24,12 @@ import { HistoryEventFilterType, TimeoutType, VersioningBehavior } from '../prot
23
24
  import { StickyExecutionAttributesSchema, TaskQueueSchema, } from '../proto/temporal/api/taskqueue/v1/message_pb';
24
25
  import { GetWorkflowExecutionHistoryRequestSchema, PollActivityTaskQueueRequestSchema, PollWorkflowTaskQueueRequestSchema, RespondActivityTaskCanceledRequestSchema, RespondActivityTaskCompletedRequestSchema, RespondActivityTaskFailedRequestSchema, RespondQueryTaskCompletedRequestSchema, RespondWorkflowTaskCompletedRequestSchema, RespondWorkflowTaskFailedRequestSchema, } from '../proto/temporal/api/workflowservice/v1/request_response_pb';
25
26
  import { WorkflowService } from '../proto/temporal/api/workflowservice/v1/service_pb';
27
+ import { intentsEqual, stableStringify } from '../workflow/determinism';
26
28
  import { WorkflowNondeterminismError } from '../workflow/errors';
27
29
  import { WorkflowExecutor } from '../workflow/executor';
30
+ import { CHILD_WORKFLOW_COMPLETED_SIGNAL, } from '../workflow/inbound';
28
31
  import { WorkflowRegistry } from '../workflow/registry';
29
- import { DETERMINISM_MARKER_NAME, diffDeterminismState, encodeDeterminismMarkerDetails, ingestWorkflowHistory, resolveHistoryLastEventId, } from '../workflow/replay';
32
+ import { DETERMINISM_MARKER_NAME, diffDeterminismState, encodeDeterminismMarkerDetailsWithSize, ingestWorkflowHistory, resolveHistoryLastEventId, } from '../workflow/replay';
30
33
  import { runWithActivityContext } from './activity-context';
31
34
  import { checkWorkerVersioningCapability, registerWorkerBuildIdCompatibility } from './build-id';
32
35
  import { makeWorkerScheduler, } from './concurrency';
@@ -52,11 +55,19 @@ const mergeUpdateInvocations = (historyInvocations, messageInvocations) => {
52
55
  const POLL_TIMEOUT_MS = 60_000;
53
56
  const RESPOND_TIMEOUT_MS = 15_000;
54
57
  const HISTORY_FETCH_TIMEOUT_MS = 60_000;
58
+ const DEFAULT_METRICS_FLUSH_INTERVAL_MS = 10_000;
55
59
  const HEARTBEAT_RETRY_INITIAL_DELAY_MS = 250;
56
60
  const HEARTBEAT_RETRY_MAX_DELAY_MS = 5_000;
57
61
  const HEARTBEAT_RETRY_MAX_ATTEMPTS = 5;
58
62
  const HEARTBEAT_RETRY_BACKOFF = 2;
59
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
+ };
60
71
  const STICKY_QUEUE_PREFIX = 'sticky';
61
72
  const COMPLETION_COMMAND_TYPES = new Set([
62
73
  CommandType.COMPLETE_WORKFLOW_EXECUTION,
@@ -66,7 +77,6 @@ const COMPLETION_COMMAND_TYPES = new Set([
66
77
  export class WorkerRuntime {
67
78
  static async create(options = {}) {
68
79
  const config = options.config ?? (await loadTemporalConfig());
69
- const dataConverter = options.dataConverter ?? createDefaultDataConverter();
70
80
  const namespace = options.namespace ?? config.namespace;
71
81
  if (!namespace) {
72
82
  throw new Error('Temporal namespace must be provided');
@@ -80,12 +90,6 @@ export class WorkerRuntime {
80
90
  if (workflows.length === 0) {
81
91
  throw new Error('No workflow definitions were registered; provide workflows or workflowsPath');
82
92
  }
83
- const registry = new WorkflowRegistry();
84
- registry.registerMany(workflows);
85
- const executor = new WorkflowExecutor({
86
- registry,
87
- dataConverter,
88
- });
89
93
  const activities = options.activities ?? {};
90
94
  const observability = await Effect.runPromise(createObservabilityServices({
91
95
  logLevel: config.logLevel,
@@ -96,7 +100,23 @@ export class WorkerRuntime {
96
100
  metricsRegistry: options.metrics,
97
101
  metricsExporter: options.metricsExporter,
98
102
  }));
99
- 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;
107
+ const dataConverter = options.dataConverter ??
108
+ createDefaultDataConverter({
109
+ payloadCodecs: buildCodecsFromConfig(config.payloadCodecs),
110
+ logger,
111
+ metricsRegistry,
112
+ });
113
+ const registry = new WorkflowRegistry();
114
+ registry.registerMany(workflows);
115
+ const executor = new WorkflowExecutor({
116
+ registry,
117
+ dataConverter,
118
+ logger,
119
+ });
100
120
  const runtimeMetrics = await WorkerRuntime.#initMetrics(metricsRegistry);
101
121
  let workflowService;
102
122
  if (options.workflowService) {
@@ -156,6 +176,11 @@ export class WorkerRuntime {
156
176
  const stickySchedulingEnabled = options.stickyScheduling ??
157
177
  (config.stickySchedulingEnabled &&
158
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;
159
184
  const deploymentName = options.deployment?.name ?? config.workerDeploymentName ?? WorkerRuntime.#defaultDeploymentName(taskQueue);
160
185
  const buildId = options.deployment?.buildId ?? config.workerBuildId ?? identity;
161
186
  const workerVersioningMode = options.deployment?.versioningMode ?? WorkerVersioningMode.UNVERSIONED;
@@ -167,6 +192,7 @@ export class WorkerRuntime {
167
192
  buildId,
168
193
  workerVersioningMode,
169
194
  });
195
+ const rpcDeploymentOptions = workerVersioningMode === WorkerVersioningMode.VERSIONED ? deploymentOptions : undefined;
170
196
  if (workerVersioningMode === WorkerVersioningMode.VERSIONED) {
171
197
  const capability = await checkWorkerVersioningCapability(workflowService, namespace, taskQueue);
172
198
  if (capability.supported) {
@@ -181,6 +207,22 @@ export class WorkerRuntime {
181
207
  }));
182
208
  }
183
209
  }
210
+ const tracingEnabled = options.tracingEnabled ?? config.tracingInterceptorsEnabled ?? false;
211
+ const workerInterceptorBuilder = options.interceptorBuilder ?? {
212
+ build: (input) => makeDefaultWorkerInterceptors(input),
213
+ };
214
+ const defaultWorkerInterceptors = await Effect.runPromise(workerInterceptorBuilder.build({
215
+ namespace,
216
+ taskQueue,
217
+ identity,
218
+ buildId,
219
+ logger,
220
+ metricsRegistry,
221
+ metricsExporter,
222
+ dataConverter,
223
+ tracingEnabled,
224
+ }));
225
+ const workerInterceptors = [...defaultWorkerInterceptors, ...(options.interceptors ?? [])];
184
226
  const activityLifecycle = await Effect.runPromise(makeActivityLifecycle({
185
227
  heartbeatIntervalMs: config.activityHeartbeatIntervalMs,
186
228
  heartbeatRpcTimeoutMs: config.activityHeartbeatRpcTimeoutMs,
@@ -204,11 +246,17 @@ export class WorkerRuntime {
204
246
  workflowConcurrency,
205
247
  activityConcurrency,
206
248
  stickySchedulingEnabled,
249
+ determinismMarkerMode,
250
+ determinismMarkerIntervalTasks,
251
+ determinismMarkerFullSnapshotIntervalTasks,
252
+ determinismMarkerSkipUnchanged,
253
+ determinismMarkerMaxDetailBytes,
207
254
  deploymentName,
208
255
  buildId,
209
256
  logLevel: config.logLevel,
210
257
  logFormat: config.logFormat,
211
258
  metricsExporter: config.metricsExporter.type,
259
+ metricsFlushIntervalMs,
212
260
  }));
213
261
  return new WorkerRuntime({
214
262
  config,
@@ -220,7 +268,9 @@ export class WorkerRuntime {
220
268
  logger,
221
269
  metricsRegistry,
222
270
  metricsExporter,
271
+ metricsFlushIntervalMs,
223
272
  metrics: runtimeMetrics,
273
+ openTelemetry,
224
274
  namespace,
225
275
  taskQueue,
226
276
  identity,
@@ -230,9 +280,16 @@ export class WorkerRuntime {
230
280
  stickyQueue,
231
281
  stickyScheduleToStartTimeoutMs,
232
282
  deploymentOptions,
283
+ rpcDeploymentOptions,
233
284
  versioningBehavior,
234
285
  stickySchedulingEnabled,
286
+ determinismMarkerMode,
287
+ determinismMarkerIntervalTasks,
288
+ determinismMarkerFullSnapshotIntervalTasks,
289
+ determinismMarkerSkipUnchanged,
290
+ determinismMarkerMaxDetailBytes,
235
291
  workflowPollerCount,
292
+ interceptors: workerInterceptors,
236
293
  });
237
294
  }
238
295
  #config;
@@ -245,16 +302,26 @@ export class WorkerRuntime {
245
302
  #metricsRegistry;
246
303
  #metrics;
247
304
  #metricsExporter;
305
+ #metricsFlushIntervalMs;
306
+ #metricsFlushInFlight = false;
307
+ #openTelemetry;
248
308
  #namespace;
249
309
  #taskQueue;
250
310
  #identity;
311
+ #interceptors;
251
312
  #activityLifecycle;
252
313
  #scheduler;
253
314
  #stickyCache;
254
315
  #stickyQueue;
255
316
  #stickySchedulingEnabled;
317
+ #determinismMarkerMode;
318
+ #determinismMarkerIntervalTasks;
319
+ #determinismMarkerFullSnapshotIntervalTasks;
320
+ #determinismMarkerSkipUnchanged;
321
+ #determinismMarkerMaxDetailBytes;
256
322
  #stickyAttributes;
257
323
  #deploymentOptions;
324
+ #rpcDeploymentOptions;
258
325
  #versioningBehavior;
259
326
  #workflowPollerCount;
260
327
  #running = false;
@@ -272,15 +339,24 @@ export class WorkerRuntime {
272
339
  this.#metricsRegistry = params.metricsRegistry;
273
340
  this.#metrics = params.metrics;
274
341
  this.#metricsExporter = params.metricsExporter;
342
+ this.#metricsFlushIntervalMs = Math.max(0, params.metricsFlushIntervalMs);
343
+ this.#openTelemetry = params.openTelemetry;
275
344
  this.#namespace = params.namespace;
276
345
  this.#taskQueue = params.taskQueue;
277
346
  this.#identity = params.identity;
347
+ this.#interceptors = params.interceptors;
278
348
  this.#activityLifecycle = params.activityLifecycle;
279
349
  this.#scheduler = params.scheduler;
280
350
  this.#stickyCache = params.stickyCache;
281
351
  this.#stickyQueue = params.stickyQueue;
282
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);
283
358
  this.#deploymentOptions = params.deploymentOptions;
359
+ this.#rpcDeploymentOptions = params.rpcDeploymentOptions;
284
360
  this.#versioningBehavior = params.versioningBehavior;
285
361
  this.#workflowPollerCount = params.workflowPollerCount;
286
362
  this.#stickyAttributes = create(StickyExecutionAttributesSchema, {
@@ -353,6 +429,10 @@ export class WorkerRuntime {
353
429
  workflowTaskCompleted: await makeCounter('temporal_worker_workflow_tasks_completed_total', 'Workflow tasks completed by the scheduler'),
354
430
  activityTaskStarted: await makeCounter('temporal_worker_activity_tasks_started_total', 'Activity tasks dispatched to the scheduler'),
355
431
  activityTaskCompleted: await makeCounter('temporal_worker_activity_tasks_completed_total', 'Activity tasks completed by the scheduler'),
432
+ queryTaskStarted: await makeCounter('temporal_worker_query_started_total', 'Query-only workflow tasks dispatched to the scheduler'),
433
+ queryTaskCompleted: await makeCounter('temporal_worker_query_completed_total', 'Query-only workflow tasks completed successfully'),
434
+ queryTaskFailed: await makeCounter('temporal_worker_query_failed_total', 'Query-only workflow tasks responded with failure'),
435
+ queryTaskLatency: await makeHistogram('temporal_worker_query_latency_ms', 'End-to-end workflow query latency (ms)'),
356
436
  };
357
437
  }
358
438
  async run() {
@@ -377,6 +457,7 @@ export class WorkerRuntime {
377
457
  if (!runtimeFiber) {
378
458
  await this.#stopScheduler();
379
459
  await this.#flushMetrics();
460
+ await this.#shutdownOpenTelemetry();
380
461
  this.#running = false;
381
462
  this.#log('info', 'temporal worker shutdown complete', this.#runtimeLogFields({ drained: false }));
382
463
  return;
@@ -392,6 +473,7 @@ export class WorkerRuntime {
392
473
  this.#runFiber = null;
393
474
  this.#running = false;
394
475
  }
476
+ await this.#shutdownOpenTelemetry();
395
477
  this.#log('info', 'temporal worker shutdown complete', this.#runtimeLogFields({ drained: true }));
396
478
  }
397
479
  #buildRuntimeEffect() {
@@ -408,6 +490,10 @@ export class WorkerRuntime {
408
490
  workflowPollers: runtime.#workflowPollerCount,
409
491
  stickySchedulingEnabled: runtime.#stickySchedulingEnabled,
410
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
+ }
411
497
  if (pollerLoops.length > 0) {
412
498
  yield* Effect.forEach(pollerLoops, (loop) => Effect.forkScoped(loop), { concurrency: 'unbounded' });
413
499
  }
@@ -415,9 +501,16 @@ export class WorkerRuntime {
415
501
  })).pipe(Effect.ensuring(Effect.promise(async () => {
416
502
  await runtime.#stopScheduler();
417
503
  await runtime.#flushMetrics();
504
+ await runtime.#shutdownOpenTelemetry();
418
505
  runtime.#log('info', 'temporal worker runtime stopped', runtime.#runtimeLogFields());
419
506
  })));
420
507
  }
508
+ async #shutdownOpenTelemetry() {
509
+ if (!this.#openTelemetry) {
510
+ return;
511
+ }
512
+ await this.#openTelemetry.shutdown();
513
+ }
421
514
  async #awaitRuntimeFiber(fiber) {
422
515
  const exit = await Effect.runPromiseExit(Fiber.join(fiber));
423
516
  if (Exit.isFailure(exit)) {
@@ -453,7 +546,7 @@ export class WorkerRuntime {
453
546
  namespace: this.#namespace,
454
547
  taskQueue: create(TaskQueueSchema, { name: queueName }),
455
548
  identity: this.#identity,
456
- deploymentOptions: this.#deploymentOptions,
549
+ deploymentOptions: this.#rpcDeploymentOptions,
457
550
  });
458
551
  const pollOnce = this.#withRpcAbort(async (signal) => {
459
552
  const start = Date.now();
@@ -487,7 +580,7 @@ export class WorkerRuntime {
487
580
  namespace: this.#namespace,
488
581
  taskQueue: create(TaskQueueSchema, { name: this.#taskQueue }),
489
582
  identity: this.#identity,
490
- deploymentOptions: this.#deploymentOptions,
583
+ deploymentOptions: this.#rpcDeploymentOptions,
491
584
  });
492
585
  const pollOnce = this.#withRpcAbort(async (signal) => {
493
586
  const start = Date.now();
@@ -514,17 +607,38 @@ export class WorkerRuntime {
514
607
  #isRpcAbortError(error) {
515
608
  return isAbortError(error) || (error instanceof ConnectError && error.code === Code.Canceled);
516
609
  }
610
+ #isBenignPollTimeout(error) {
611
+ if (error instanceof ConnectError) {
612
+ return error.code === Code.DeadlineExceeded || error.code === Code.Canceled;
613
+ }
614
+ if (error instanceof Error) {
615
+ const msg = error.message.toLowerCase();
616
+ return msg.includes('deadline') && msg.includes('exceeded');
617
+ }
618
+ if (typeof error === 'string') {
619
+ const msg = error.toLowerCase();
620
+ return msg.includes('deadline') && msg.includes('exceeded');
621
+ }
622
+ return false;
623
+ }
517
624
  async #enqueueActivityTask(response) {
518
625
  const taskToken = response.taskToken ?? new Uint8Array();
519
626
  const envelope = {
520
627
  taskToken,
521
- handler: () => this.#processActivityTask(response),
628
+ handler: () => this.#runActivityTask(response),
522
629
  args: [],
523
630
  };
524
631
  await Effect.runPromise(this.#scheduler.enqueueActivity(envelope));
525
632
  }
526
633
  #handleWorkflowPollerError(queueName, error) {
527
634
  return Effect.promise(async () => {
635
+ if (this.#isBenignPollTimeout(error)) {
636
+ this.#log('debug', 'workflow poll timeout (no tasks)', {
637
+ queueName,
638
+ namespace: this.#namespace,
639
+ });
640
+ return;
641
+ }
528
642
  this.#incrementCounter(this.#metrics.workflowPollErrors);
529
643
  this.#log('warn', 'workflow polling failed', {
530
644
  queueName,
@@ -536,6 +650,13 @@ export class WorkerRuntime {
536
650
  }
537
651
  #handleActivityPollerError(error) {
538
652
  return Effect.promise(async () => {
653
+ if (this.#isBenignPollTimeout(error)) {
654
+ this.#log('debug', 'activity poll timeout (no tasks)', {
655
+ namespace: this.#namespace,
656
+ taskQueue: this.#taskQueue,
657
+ });
658
+ return;
659
+ }
539
660
  this.#incrementCounter(this.#metrics.activityPollErrors);
540
661
  this.#log('warn', 'activity polling failed', {
541
662
  namespace: this.#namespace,
@@ -547,10 +668,54 @@ export class WorkerRuntime {
547
668
  }
548
669
  async #handleWorkflowTask(response, nondeterminismRetry = 0) {
549
670
  const execution = this.#resolveWorkflowExecution(response);
671
+ const queryCount = response.queries && typeof response.queries === 'object' && !Array.isArray(response.queries)
672
+ ? Object.keys(response.queries).length
673
+ : Array.isArray(response.queries)
674
+ ? response.queries.length
675
+ : 0;
676
+ const hasQueryRequests = Boolean(response.query) || queryCount > 0;
677
+ const hasUpdateMessages = (response.messages?.length ?? 0) > 0;
678
+ const kind = hasUpdateMessages
679
+ ? 'worker.updateTask'
680
+ : hasQueryRequests
681
+ ? 'worker.queryTask'
682
+ : 'worker.workflowTask';
683
+ const context = {
684
+ kind,
685
+ namespace: this.#namespace,
686
+ taskQueue: this.#taskQueue,
687
+ identity: this.#identity,
688
+ buildId: this.#deploymentOptions.buildId,
689
+ workflowId: execution.workflowId,
690
+ runId: execution.runId,
691
+ attempt: Number(response.attempt ?? 1),
692
+ metadata: { nondeterminismRetry },
693
+ };
694
+ const effect = runWorkerInterceptors(this.#interceptors, context, () => Effect.tryPromise(() => this.#processWorkflowTask(response, nondeterminismRetry, execution)));
695
+ await Effect.runPromise(effect);
696
+ }
697
+ async #processWorkflowTask(response, nondeterminismRetry = 0, executionOverride) {
698
+ const execution = executionOverride ?? this.#resolveWorkflowExecution(response);
550
699
  const workflowTaskAttempt = Number(response.attempt ?? 1);
551
- const historyEvents = await this.#collectWorkflowHistory(execution, response);
700
+ const isLegacyQueryTask = Boolean(response.query);
701
+ const queryStartTime = isLegacyQueryTask ? Date.now() : null;
702
+ if (isLegacyQueryTask) {
703
+ this.#incrementCounter(this.#metrics.queryTaskStarted);
704
+ }
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
+ });
552
718
  const workflowType = this.#resolveWorkflowType(response, historyEvents);
553
- const args = await this.#decodeWorkflowArgs(historyEvents);
554
719
  const workflowInfo = this.#buildWorkflowInfo(workflowType, execution);
555
720
  const collectedUpdates = await collectWorkflowUpdates({
556
721
  messages: response.messages ?? [],
@@ -587,6 +752,26 @@ export class WorkerRuntime {
587
752
  });
588
753
  const stickyKey = this.#buildStickyKey(execution.workflowId, execution.runId);
589
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
+ }
590
775
  const historyReplay = await this.#ingestDeterminismState(workflowInfo, historyEvents, {
591
776
  queryRequests,
592
777
  });
@@ -637,7 +822,21 @@ export class WorkerRuntime {
637
822
  }
638
823
  const expectedDeterminismState = previousState;
639
824
  try {
640
- const activityResults = await this.#extractActivityResolutions(historyEvents);
825
+ const { results: activityResults, scheduledEventIds: activityScheduleEventIds } = await this.#extractActivityResolutions(historyEvents);
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);
641
840
  const replayUpdates = historyReplay?.updates ?? [];
642
841
  const mergedUpdates = mergeUpdateInvocations(replayUpdates, collectedUpdates.invocations);
643
842
  const output = await this.#executor.execute({
@@ -648,10 +847,14 @@ export class WorkerRuntime {
648
847
  taskQueue: this.#taskQueue,
649
848
  arguments: args,
650
849
  determinismState: previousState,
651
- activityResults,
850
+ activityResults: mergedActivityResults,
851
+ activityScheduleEventIds: mergedScheduleEventIds,
852
+ pendingChildWorkflows,
652
853
  signalDeliveries,
854
+ timerResults: mergedTimerResults,
653
855
  queryRequests,
654
856
  updates: mergedUpdates,
857
+ mode: isLegacyQueryTask ? 'query' : 'workflow',
655
858
  });
656
859
  this.#log('debug', 'workflow query evaluation summary', {
657
860
  ...baseLogFields,
@@ -681,10 +884,38 @@ export class WorkerRuntime {
681
884
  legacyQueryResult = entry;
682
885
  }
683
886
  }
887
+ if (isLegacyQueryTask) {
888
+ const target = legacyQueryResult ?? output.queryResults.find((entry) => entry.request.source === 'legacy');
889
+ if (!target) {
890
+ throw new Error('Legacy query result missing from workflow execution');
891
+ }
892
+ await this.#respondLegacyQueryTask(response, target);
893
+ if (queryStartTime !== null) {
894
+ this.#observeHistogram(this.#metrics.queryTaskLatency, Date.now() - queryStartTime);
895
+ }
896
+ this.#incrementCounter(this.#metrics.queryTaskCompleted);
897
+ return;
898
+ }
684
899
  const cacheBaselineEventId = this.#resolveCurrentStartedEventId(response) ?? historyReplay?.lastEventId ?? null;
685
- const shouldRecordMarker = output.completion === 'pending';
686
900
  let commandsForResponse = output.commands;
687
- const dispatchesForNewMessages = (output.updateDispatches ?? []).filter((dispatch) => collectedUpdates.requestsByUpdateId.has(dispatch.updateId));
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;
912
+ const dispatchesForNewMessages = (output.updateDispatches ?? []).filter((dispatch) => {
913
+ if (dispatch.type === 'acceptance' || dispatch.type === 'rejection') {
914
+ return collectedUpdates.requestsByUpdateId.has(dispatch.updateId);
915
+ }
916
+ // Allow completion messages to be emitted even if the request metadata was seen on a prior task.
917
+ return true;
918
+ });
688
919
  const updateProtocolMessages = await buildUpdateProtocolMessages({
689
920
  dispatches: dispatchesForNewMessages,
690
921
  collected: collectedUpdates,
@@ -692,40 +923,153 @@ export class WorkerRuntime {
692
923
  defaultIdentity: this.#identity,
693
924
  log: (level, message, fields) => this.#log(level, message, fields),
694
925
  });
695
- if (stickyKey) {
696
- if (output.completion === 'pending') {
697
- await this.#upsertStickyEntry(stickyKey, output.determinismState, cacheBaselineEventId, workflowType);
698
- 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', {
699
929
  ...baseLogFields,
700
- cacheBaselineEventId,
701
930
  });
931
+ markerType = 'full';
702
932
  }
703
933
  else {
704
- await this.#removeStickyEntry(stickyKey);
705
- 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;
706
941
  }
707
942
  }
708
- if (shouldRecordMarker) {
709
- const lastEventId = historyReplay?.lastEventId ??
710
- this.#resolveWorkflowHistoryLastEventId(response) ??
711
- stickyEntry?.lastEventId ??
712
- null;
713
- const markerDetails = await Effect.runPromise(encodeDeterminismMarkerDetails(this.#dataConverter, {
714
- info: workflowInfo,
715
- determinismState: output.determinismState,
716
- lastEventId,
717
- }));
718
- const markerCommand = this.#buildDeterminismMarkerCommand(markerDetails);
719
- 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
+ }
720
1063
  }
721
1064
  const shouldRespondWorkflowTask = hasMultiQueries || !hasLegacyQueries;
1065
+ let workflowTaskCommitted = false;
722
1066
  if (shouldRespondWorkflowTask) {
723
1067
  const completion = create(RespondWorkflowTaskCompletedRequestSchema, {
724
1068
  taskToken: response.taskToken,
725
1069
  commands: commandsForResponse,
726
1070
  identity: this.#identity,
727
1071
  namespace: this.#namespace,
728
- deploymentOptions: this.#deploymentOptions,
1072
+ deploymentOptions: this.#rpcDeploymentOptions,
729
1073
  queryResults: multiQueryResults,
730
1074
  ...(this.#stickySchedulingEnabled && !hasLegacyQueries ? { stickyAttributes: this.#stickyAttributes } : {}),
731
1075
  ...(this.#versioningBehavior !== null ? { versioningBehavior: this.#versioningBehavior } : {}),
@@ -733,6 +1077,7 @@ export class WorkerRuntime {
733
1077
  });
734
1078
  try {
735
1079
  await this.#workflowService.respondWorkflowTaskCompleted(completion, { timeoutMs: RESPOND_TIMEOUT_MS });
1080
+ workflowTaskCommitted = true;
736
1081
  }
737
1082
  catch (rpcError) {
738
1083
  this.#log('error', 'debug: respondWorkflowTaskCompleted failed', {
@@ -741,6 +1086,10 @@ export class WorkerRuntime {
741
1086
  });
742
1087
  if (this.#isTaskNotFoundError(rpcError)) {
743
1088
  this.#logWorkflowTaskNotFound('respondWorkflowTaskCompleted', execution);
1089
+ if (stickyKey) {
1090
+ await this.#removeStickyEntry(stickyKey);
1091
+ await this.#removeStickyEntriesForWorkflow(stickyKey.workflowId);
1092
+ }
744
1093
  return;
745
1094
  }
746
1095
  throw rpcError;
@@ -749,17 +1098,48 @@ export class WorkerRuntime {
749
1098
  if (legacyQueryResult) {
750
1099
  await this.#respondLegacyQueryTask(response, legacyQueryResult);
751
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
+ }
752
1115
  }
753
1116
  catch (error) {
754
1117
  let stickyEntryCleared = false;
755
1118
  if (stickyKey) {
756
1119
  await this.#removeStickyEntry(stickyKey);
1120
+ await this.#removeStickyEntriesForWorkflow(stickyKey.workflowId);
757
1121
  stickyEntryCleared = true;
758
1122
  }
759
1123
  if (this.#isTaskNotFoundError(error)) {
760
1124
  this.#logWorkflowTaskNotFound('respondWorkflowTaskCompleted', execution);
761
1125
  return;
762
1126
  }
1127
+ if (isLegacyQueryTask) {
1128
+ this.#incrementCounter(this.#metrics.queryTaskFailed);
1129
+ if (queryStartTime !== null) {
1130
+ this.#observeHistogram(this.#metrics.queryTaskLatency, Date.now() - queryStartTime);
1131
+ }
1132
+ if (error instanceof WorkflowNondeterminismError) {
1133
+ const mismatches = await this.#computeNondeterminismMismatches(error, expectedDeterminismState);
1134
+ this.#incrementCounter(this.#metrics.nondeterminism);
1135
+ this.#log('error', 'workflow query nondeterminism detected', {
1136
+ ...baseLogFields,
1137
+ mismatches,
1138
+ });
1139
+ }
1140
+ await this.#respondLegacyQueryFailure(response, error);
1141
+ return;
1142
+ }
763
1143
  if (error instanceof WorkflowNondeterminismError) {
764
1144
  const mismatches = await this.#computeNondeterminismMismatches(error, expectedDeterminismState);
765
1145
  if (stickyKey && stickyEntryCleared) {
@@ -820,24 +1200,98 @@ export class WorkerRuntime {
820
1200
  queries: options?.queryRequests,
821
1201
  }));
822
1202
  }
823
- async #collectWorkflowHistory(execution, _response) {
1203
+ async #collectWorkflowHistory(execution, response, options) {
824
1204
  const events = [];
825
- let token;
826
- 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) {
827
1211
  const page = await this.#fetchWorkflowHistoryPage(execution, token);
828
1212
  if (page.events.length > 0) {
829
1213
  events.push(...page.events);
830
1214
  }
831
- 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) {
832
1265
  break;
833
1266
  }
834
- token = page.nextPageToken;
1267
+ bounded.push(event);
835
1268
  }
836
- 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;
837
1290
  }
838
1291
  async #extractActivityResolutions(events) {
839
1292
  const resolutions = new Map();
840
1293
  const scheduledActivityIds = new Map();
1294
+ const activityScheduleById = new Map();
841
1295
  const normalizeEventId = (value) => {
842
1296
  if (value === undefined || value === null) {
843
1297
  return undefined;
@@ -868,6 +1322,7 @@ export class WorkerRuntime {
868
1322
  const scheduledKey = normalizeEventId(event.eventId);
869
1323
  if (activityId && scheduledKey) {
870
1324
  scheduledActivityIds.set(scheduledKey, activityId);
1325
+ activityScheduleById.set(activityId, scheduledKey);
871
1326
  }
872
1327
  break;
873
1328
  }
@@ -932,7 +1387,7 @@ export class WorkerRuntime {
932
1387
  break;
933
1388
  }
934
1389
  }
935
- return resolutions;
1390
+ return { results: resolutions, scheduledEventIds: activityScheduleById };
936
1391
  }
937
1392
  async #extractSignalDeliveries(events) {
938
1393
  const deliveries = [];
@@ -945,31 +1400,155 @@ export class WorkerRuntime {
945
1400
  }
946
1401
  return value.toString();
947
1402
  };
948
- for (const event of events) {
949
- if (event.eventType !== EventType.WORKFLOW_EXECUTION_SIGNALED) {
950
- continue;
951
- }
952
- if (event.attributes?.case !== 'workflowExecutionSignaledEventAttributes') {
953
- continue;
1403
+ const recordChildCompletion = (event, status, attrs) => {
1404
+ const workflowId = attrs.workflowExecution?.workflowId;
1405
+ if (!workflowId) {
1406
+ return;
954
1407
  }
955
- const attrs = event.attributes.value;
956
- const args = await decodePayloadsToValues(this.#dataConverter, attrs.input?.payloads ?? []);
957
- const workflowTaskCompletedEventId = 'workflowTaskCompletedEventId' in attrs
958
- ? normalizeEventId(attrs
959
- .workflowTaskCompletedEventId)
960
- : null;
961
1408
  deliveries.push({
962
- name: attrs.signalName ?? 'unknown',
963
- args,
1409
+ name: CHILD_WORKFLOW_COMPLETED_SIGNAL,
1410
+ args: [
1411
+ {
1412
+ workflowId,
1413
+ ...(attrs.workflowExecution?.runId ? { runId: attrs.workflowExecution.runId } : {}),
1414
+ status,
1415
+ },
1416
+ ],
964
1417
  metadata: {
965
1418
  eventId: normalizeEventId(event.eventId),
966
- workflowTaskCompletedEventId,
967
- identity: attrs.identity ?? null,
968
1419
  },
969
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
+ }
970
1483
  }
971
1484
  return deliveries;
972
1485
  }
1486
+ async #extractTimerResolutions(events) {
1487
+ const fired = new Set();
1488
+ for (const event of events) {
1489
+ if (event.eventType !== EventType.TIMER_FIRED) {
1490
+ continue;
1491
+ }
1492
+ if (event.attributes?.case !== 'timerFiredEventAttributes') {
1493
+ continue;
1494
+ }
1495
+ const attrs = event.attributes.value;
1496
+ if (attrs.timerId) {
1497
+ fired.add(attrs.timerId);
1498
+ }
1499
+ }
1500
+ return fired;
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
+ }
973
1552
  async #extractWorkflowQueryRequests(response) {
974
1553
  const requests = [];
975
1554
  const map = response.queries ?? {};
@@ -1035,6 +1614,34 @@ export class WorkerRuntime {
1035
1614
  });
1036
1615
  await this.#workflowService.respondQueryTaskCompleted(request, { timeoutMs: RESPOND_TIMEOUT_MS });
1037
1616
  }
1617
+ async #respondLegacyQueryFailure(response, cause) {
1618
+ const failure = await encodeErrorToFailure(this.#dataConverter, cause);
1619
+ const message = cause instanceof Error ? cause.message : 'Workflow query failed';
1620
+ const request = create(RespondQueryTaskCompletedRequestSchema, {
1621
+ taskToken: response.taskToken ?? new Uint8Array(),
1622
+ completedType: QueryResultType.FAILED,
1623
+ errorMessage: message,
1624
+ namespace: this.#namespace,
1625
+ failure,
1626
+ cause: WorkflowTaskFailedCause.UNSPECIFIED,
1627
+ });
1628
+ try {
1629
+ await this.#workflowService.respondQueryTaskCompleted(request, { timeoutMs: RESPOND_TIMEOUT_MS });
1630
+ }
1631
+ catch (rpcError) {
1632
+ this.#log('error', 'respondQueryTaskCompleted failed for legacy query', {
1633
+ namespace: this.#namespace,
1634
+ workflowId: response.workflowExecution?.workflowId,
1635
+ runId: response.workflowExecution?.runId,
1636
+ error: rpcError instanceof Error ? rpcError.message : String(rpcError),
1637
+ });
1638
+ if (this.#isTaskNotFoundError(rpcError)) {
1639
+ this.#logWorkflowTaskNotFound('respondQueryTaskCompleted', this.#resolveWorkflowExecution(response));
1640
+ return;
1641
+ }
1642
+ throw rpcError;
1643
+ }
1644
+ }
1038
1645
  async #fetchWorkflowHistoryPage(execution, nextPageToken) {
1039
1646
  if (!execution.workflowId || !execution.runId) {
1040
1647
  return { events: [], nextPageToken: new Uint8Array() };
@@ -1059,6 +1666,18 @@ export class WorkerRuntime {
1059
1666
  nextPageToken: historyResponse.nextPageToken ?? new Uint8Array(),
1060
1667
  };
1061
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
+ }
1062
1681
  #buildWorkflowInfo(workflowType, execution) {
1063
1682
  return {
1064
1683
  namespace: this.#namespace,
@@ -1071,19 +1690,145 @@ export class WorkerRuntime {
1071
1690
  async #getStickyEntry(key) {
1072
1691
  return await Effect.runPromise(this.#stickyCache.get(key));
1073
1692
  }
1074
- async #upsertStickyEntry(key, state, lastEventId, workflowType) {
1693
+ async #upsertStickyEntry(key, state, lastEventId, workflowType, metadata) {
1075
1694
  const entry = {
1076
1695
  key,
1077
1696
  determinismState: state,
1078
1697
  lastEventId,
1079
1698
  lastAccessed: Date.now(),
1080
1699
  workflowType,
1700
+ ...metadata,
1081
1701
  };
1082
1702
  await Effect.runPromise(this.#stickyCache.upsert(entry));
1083
1703
  }
1084
1704
  #resolveWorkflowHistoryLastEventId(response) {
1085
1705
  return resolveHistoryLastEventId(response.history?.events ?? []);
1086
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
+ }
1087
1832
  #isTaskNotFoundError(error) {
1088
1833
  return error instanceof ConnectError && error.code === Code.NotFound;
1089
1834
  }
@@ -1316,6 +2061,11 @@ export class WorkerRuntime {
1316
2061
  async #removeStickyEntry(key) {
1317
2062
  await Effect.runPromise(this.#stickyCache.remove(key));
1318
2063
  }
2064
+ async #removeStickyEntriesForWorkflow(workflowId) {
2065
+ if (!workflowId)
2066
+ return;
2067
+ await Effect.runPromise(this.#stickyCache.removeByWorkflow({ namespace: this.#namespace, workflowId }));
2068
+ }
1319
2069
  async #failWorkflowTask(response, execution, error, cause = WorkflowTaskFailedCause.UNSPECIFIED) {
1320
2070
  const failure = await encodeErrorToFailure(this.#dataConverter, error);
1321
2071
  const encoded = await encodeFailurePayloads(this.#dataConverter, failure);
@@ -1325,7 +2075,7 @@ export class WorkerRuntime {
1325
2075
  failure: encoded,
1326
2076
  identity: this.#identity,
1327
2077
  namespace: this.#namespace,
1328
- deploymentOptions: this.#deploymentOptions,
2078
+ deploymentOptions: this.#rpcDeploymentOptions,
1329
2079
  });
1330
2080
  try {
1331
2081
  await this.#workflowService.respondWorkflowTaskFailed(failed, { timeoutMs: RESPOND_TIMEOUT_MS });
@@ -1339,6 +2089,20 @@ export class WorkerRuntime {
1339
2089
  throw rpcError;
1340
2090
  }
1341
2091
  }
2092
+ async #runActivityTask(response) {
2093
+ const context = {
2094
+ kind: 'worker.activityTask',
2095
+ namespace: this.#namespace,
2096
+ taskQueue: this.#taskQueue,
2097
+ identity: this.#identity,
2098
+ buildId: this.#deploymentOptions.buildId,
2099
+ workflowId: response.workflowExecution?.workflowId ?? undefined,
2100
+ runId: response.workflowExecution?.runId ?? undefined,
2101
+ attempt: Number(response.attempt ?? 1),
2102
+ };
2103
+ const effect = runWorkerInterceptors(this.#interceptors, context, () => Effect.tryPromise(() => this.#processActivityTask(response)));
2104
+ await Effect.runPromise(effect);
2105
+ }
1342
2106
  async #processActivityTask(response) {
1343
2107
  const cancelRequested = isActivityCancelRequested(response);
1344
2108
  if (cancelRequested) {
@@ -1411,7 +2175,7 @@ export class WorkerRuntime {
1411
2175
  identity: this.#identity,
1412
2176
  namespace: this.#namespace,
1413
2177
  result: payloads && payloads.length > 0 ? create(PayloadsSchema, { payloads }) : undefined,
1414
- deploymentOptions: this.#deploymentOptions,
2178
+ deploymentOptions: this.#rpcDeploymentOptions,
1415
2179
  });
1416
2180
  await this.#workflowService.respondActivityTaskCompleted(completion, { timeoutMs: RESPOND_TIMEOUT_MS });
1417
2181
  break;
@@ -1467,7 +2231,7 @@ export class WorkerRuntime {
1467
2231
  namespace: this.#namespace,
1468
2232
  failure: encoded,
1469
2233
  lastHeartbeatDetails,
1470
- deploymentOptions: this.#deploymentOptions,
2234
+ deploymentOptions: this.#rpcDeploymentOptions,
1471
2235
  });
1472
2236
  await this.#workflowService.respondActivityTaskFailed(request, { timeoutMs: RESPOND_TIMEOUT_MS });
1473
2237
  this.#incrementCounter(this.#metrics.activityFailures);
@@ -1479,7 +2243,7 @@ export class WorkerRuntime {
1479
2243
  identity: this.#identity,
1480
2244
  namespace: this.#namespace,
1481
2245
  details,
1482
- deploymentOptions: this.#deploymentOptions,
2246
+ deploymentOptions: this.#rpcDeploymentOptions,
1483
2247
  });
1484
2248
  await this.#workflowService.respondActivityTaskCanceled(request, { timeoutMs: RESPOND_TIMEOUT_MS });
1485
2249
  }
@@ -1594,6 +2358,10 @@ export class WorkerRuntime {
1594
2358
  return scheduleDeadline ?? startDeadline ?? undefined;
1595
2359
  }
1596
2360
  async #flushMetrics() {
2361
+ if (this.#metricsFlushInFlight) {
2362
+ return;
2363
+ }
2364
+ this.#metricsFlushInFlight = true;
1597
2365
  try {
1598
2366
  await Effect.runPromise(this.#metricsExporter.flush());
1599
2367
  }
@@ -1602,21 +2370,36 @@ export class WorkerRuntime {
1602
2370
  error: error instanceof Error ? error.message : String(error),
1603
2371
  });
1604
2372
  }
2373
+ finally {
2374
+ this.#metricsFlushInFlight = false;
2375
+ }
1605
2376
  }
1606
2377
  async #decodeWorkflowArgs(events) {
1607
2378
  const startEvent = this.#findWorkflowStartedEvent(events);
1608
2379
  if (!startEvent) {
1609
- return [];
2380
+ return { args: [], hasStartEvent: false };
1610
2381
  }
2382
+ const args = (await this.#decodeWorkflowArgsFromStartEvent(startEvent)) ?? [];
2383
+ return { args, hasStartEvent: true };
2384
+ }
2385
+ async #decodeWorkflowArgsFromStartEvent(startEvent) {
1611
2386
  const attributes = startEvent.attributes?.case === 'workflowExecutionStartedEventAttributes'
1612
2387
  ? startEvent.attributes.value
1613
2388
  : undefined;
1614
2389
  if (!attributes) {
1615
- return [];
2390
+ return undefined;
1616
2391
  }
1617
2392
  const inputPayloads = attributes.input?.payloads ?? [];
1618
2393
  return await decodePayloadsToValues(this.#dataConverter, inputPayloads);
1619
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
+ }
1620
2403
  #resolveWorkflowType(response, events) {
1621
2404
  if (response.workflowType?.name) {
1622
2405
  return response.workflowType.name;