@cchevli/inngest 1.8.1-walton.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 (44) hide show
  1. package/CHANGELOG.md +3981 -0
  2. package/dist/__tests__/adapters/_utils.d.ts +18 -0
  3. package/dist/__tests__/adapters/_utils.d.ts.map +1 -0
  4. package/dist/__tests__/durable-agent.test.utils.d.ts +43 -0
  5. package/dist/__tests__/durable-agent.test.utils.d.ts.map +1 -0
  6. package/dist/chunk-MICZJLEE.cjs +1977 -0
  7. package/dist/chunk-MICZJLEE.cjs.map +1 -0
  8. package/dist/chunk-SE7QPBOX.js +1970 -0
  9. package/dist/chunk-SE7QPBOX.js.map +1 -0
  10. package/dist/connect.cjs +12 -0
  11. package/dist/connect.cjs.map +1 -0
  12. package/dist/connect.d.ts +43 -0
  13. package/dist/connect.d.ts.map +1 -0
  14. package/dist/connect.js +3 -0
  15. package/dist/connect.js.map +1 -0
  16. package/dist/durable-agent/create-inngest-agent.d.ts +425 -0
  17. package/dist/durable-agent/create-inngest-agent.d.ts.map +1 -0
  18. package/dist/durable-agent/create-inngest-agentic-workflow.d.ts +56 -0
  19. package/dist/durable-agent/create-inngest-agentic-workflow.d.ts.map +1 -0
  20. package/dist/durable-agent/index.d.ts +48 -0
  21. package/dist/durable-agent/index.d.ts.map +1 -0
  22. package/dist/execution-engine.d.ts +209 -0
  23. package/dist/execution-engine.d.ts.map +1 -0
  24. package/dist/functions.d.ts +7 -0
  25. package/dist/functions.d.ts.map +1 -0
  26. package/dist/index.cjs +1576 -0
  27. package/dist/index.cjs.map +1 -0
  28. package/dist/index.d.ts +117 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +1548 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/nested-workflow-output.d.ts +21 -0
  33. package/dist/nested-workflow-output.d.ts.map +1 -0
  34. package/dist/pubsub.d.ts +60 -0
  35. package/dist/pubsub.d.ts.map +1 -0
  36. package/dist/run.d.ts +234 -0
  37. package/dist/run.d.ts.map +1 -0
  38. package/dist/serve.d.ts +77 -0
  39. package/dist/serve.d.ts.map +1 -0
  40. package/dist/types.d.ts +16 -0
  41. package/dist/types.d.ts.map +1 -0
  42. package/dist/workflow.d.ts +58 -0
  43. package/dist/workflow.d.ts.map +1 -0
  44. package/package.json +98 -0
@@ -0,0 +1,1970 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { emitErrorEvent } from '@mastra/core/agent/durable';
3
+ import { RequestContext } from '@mastra/core/di';
4
+ import { EntityType, SpanType } from '@mastra/core/observability';
5
+ import { DefaultExecutionEngine, createTimeTravelExecutionParams, Run, hydrateSerializedStepErrors, Workflow } from '@mastra/core/workflows';
6
+ import { NonRetriableError } from 'inngest';
7
+ import { getErrorFromUnknown } from '@mastra/core/error';
8
+ import { PubSub } from '@mastra/core/events';
9
+ import { subscribe } from 'inngest/realtime';
10
+ import { ReadableStream } from 'stream/web';
11
+ import { ChunkFrom, WorkflowRunOutput } from '@mastra/core/stream';
12
+
13
+ // src/workflow.ts
14
+
15
+ // src/nested-workflow-output.ts
16
+ var NESTED_WORKFLOW_OUTPUT_MODE = {
17
+ DEFAULT: "default",
18
+ COMPACT: "compact"
19
+ };
20
+ function resolveNestedWorkflowOutputMode(mode = NESTED_WORKFLOW_OUTPUT_MODE.DEFAULT) {
21
+ return mode === NESTED_WORKFLOW_OUTPUT_MODE.COMPACT ? NESTED_WORKFLOW_OUTPUT_MODE.COMPACT : NESTED_WORKFLOW_OUTPUT_MODE.DEFAULT;
22
+ }
23
+ function compactNestedWorkflowResult(result) {
24
+ switch (result.status) {
25
+ case "success":
26
+ return { status: result.status, result: result.result, state: result.state };
27
+ case "failed":
28
+ return { status: result.status, error: result.error, state: result.state };
29
+ case "tripwire":
30
+ return { status: result.status, tripwire: result.tripwire, state: result.state };
31
+ case "suspended":
32
+ return { status: result.status, steps: result.steps, state: result.state };
33
+ case "paused":
34
+ return { status: result.status, state: result.state };
35
+ }
36
+ }
37
+
38
+ // src/execution-engine.ts
39
+ var InngestExecutionEngine = class extends DefaultExecutionEngine {
40
+ inngestStep;
41
+ inngestAttempts;
42
+ constructor(mastra, inngestStep, inngestAttempts = 0, options) {
43
+ super({ mastra, options });
44
+ this.inngestStep = inngestStep;
45
+ this.inngestAttempts = inngestAttempts;
46
+ }
47
+ // =============================================================================
48
+ // Hook Overrides
49
+ // =============================================================================
50
+ /**
51
+ * Format errors while preserving Error instances and their custom properties.
52
+ * Uses getErrorFromUnknown to ensure all error properties are preserved.
53
+ */
54
+ formatResultError(error, lastOutput) {
55
+ const outputError = lastOutput?.error;
56
+ const errorSource = error || outputError;
57
+ const errorInstance = getErrorFromUnknown(errorSource, {
58
+ serializeStack: true,
59
+ // Include stack in JSON for better debugging in Inngest
60
+ fallbackMessage: "Unknown workflow error"
61
+ });
62
+ return errorInstance.toJSON();
63
+ }
64
+ /**
65
+ * Detect InngestWorkflow instances for special nested workflow handling
66
+ */
67
+ isNestedWorkflowStep(step) {
68
+ return step instanceof InngestWorkflow;
69
+ }
70
+ /**
71
+ * Inngest requires requestContext serialization for memoization.
72
+ * When steps are replayed, the original function doesn't re-execute,
73
+ * so requestContext modifications must be captured and restored.
74
+ */
75
+ requiresDurableContextSerialization() {
76
+ return true;
77
+ }
78
+ /**
79
+ * Execute a step with retry logic for Inngest.
80
+ * Retries are handled via step-level retry (RetryAfterError thrown INSIDE step.run()).
81
+ * After retries exhausted, error propagates here and we return a failed result.
82
+ */
83
+ async executeStepWithRetry(stepId, runStep, params) {
84
+ for (let i = 0; i < params.retries + 1; i++) {
85
+ if (i > 0 && params.delay) {
86
+ await new Promise((resolve) => setTimeout(resolve, params.delay));
87
+ }
88
+ try {
89
+ const result = await this.wrapDurableOperation(stepId, runStep);
90
+ return { ok: true, result };
91
+ } catch (e) {
92
+ if (i === params.retries) {
93
+ const cause = e?.cause;
94
+ if (cause?.status === "failed") {
95
+ params.stepSpan?.error({
96
+ error: e,
97
+ attributes: { status: "failed" }
98
+ });
99
+ if (cause.error && !(cause.error instanceof Error)) {
100
+ cause.error = getErrorFromUnknown(cause.error, { serializeStack: false });
101
+ }
102
+ return { ok: false, error: cause };
103
+ }
104
+ const errorInstance = getErrorFromUnknown(e, {
105
+ serializeStack: false,
106
+ fallbackMessage: "Unknown step execution error"
107
+ });
108
+ params.stepSpan?.error({
109
+ error: errorInstance,
110
+ attributes: { status: "failed" }
111
+ });
112
+ return {
113
+ ok: false,
114
+ error: {
115
+ status: "failed",
116
+ error: errorInstance,
117
+ endedAt: Date.now()
118
+ }
119
+ };
120
+ }
121
+ }
122
+ }
123
+ return { ok: false, error: { status: "failed", error: new Error("Unknown error"), endedAt: Date.now() } };
124
+ }
125
+ /**
126
+ * Use Inngest's sleep primitive for durability
127
+ */
128
+ async executeSleepDuration(duration, sleepId, workflowId) {
129
+ await this.inngestStep.sleep(`workflow.${workflowId}.sleep.${sleepId}`, duration < 0 ? 0 : duration);
130
+ }
131
+ /**
132
+ * Use Inngest's sleepUntil primitive for durability
133
+ */
134
+ async executeSleepUntilDate(date, sleepUntilId, workflowId) {
135
+ await this.inngestStep.sleepUntil(`workflow.${workflowId}.sleepUntil.${sleepUntilId}`, date);
136
+ }
137
+ /**
138
+ * Wrap durable operations in Inngest step.run() for durability.
139
+ *
140
+ * IMPORTANT: Errors are wrapped with a cause structure before throwing.
141
+ * This is necessary because Inngest's error serialization (serialize-error-cjs)
142
+ * only captures standard Error properties (message, name, stack, code, cause).
143
+ * Custom properties like statusCode, responseHeaders from AI SDK errors would
144
+ * be lost. By putting our serialized error (via getErrorFromUnknown with toJSON())
145
+ * in the cause property, we ensure custom properties survive serialization.
146
+ * The cause property is in serialize-error-cjs's allowlist, and when the cause
147
+ * object is finally JSON.stringify'd, our error's toJSON() is called.
148
+ */
149
+ async wrapDurableOperation(operationId, operationFn) {
150
+ const result = await this.inngestStep.run(operationId, async () => {
151
+ try {
152
+ const fnResult = await operationFn();
153
+ return fnResult;
154
+ } catch (e) {
155
+ const errorInstance = getErrorFromUnknown(e, {
156
+ serializeStack: false,
157
+ fallbackMessage: "Unknown step execution error"
158
+ });
159
+ throw new Error(errorInstance.message, {
160
+ cause: {
161
+ status: "failed",
162
+ error: errorInstance,
163
+ endedAt: Date.now()
164
+ }
165
+ });
166
+ }
167
+ });
168
+ return result;
169
+ }
170
+ /**
171
+ * Provide Inngest step primitive in engine context
172
+ */
173
+ getEngineContext() {
174
+ return { step: this.inngestStep };
175
+ }
176
+ /**
177
+ * For Inngest, lifecycle callbacks are invoked in the workflow's finalize step
178
+ * (wrapped in step.run for durability), not in execute(). Override to skip.
179
+ */
180
+ async invokeLifecycleCallbacks(_result) {
181
+ }
182
+ /**
183
+ * Actually invoke the lifecycle callbacks. Called from workflow.ts finalize step.
184
+ */
185
+ async invokeLifecycleCallbacksInternal(result) {
186
+ return super.invokeLifecycleCallbacks(result);
187
+ }
188
+ // =============================================================================
189
+ // Durable Span Lifecycle Hooks
190
+ // =============================================================================
191
+ /**
192
+ * Create a step span durably - on first execution, creates and exports span.
193
+ * On replay, returns cached span data without re-creating.
194
+ */
195
+ async createStepSpan(params) {
196
+ const { executionContext, operationId, options, parentSpan } = params;
197
+ const parentSpanId = parentSpan?.id ?? executionContext.tracingIds?.workflowSpanId;
198
+ const exportedSpan = await this.wrapDurableOperation(operationId, async () => {
199
+ const observability = this.mastra?.observability?.getSelectedInstance({});
200
+ if (!observability) return void 0;
201
+ const span = observability.startSpan({
202
+ ...options,
203
+ entityType: options.entityType,
204
+ traceId: executionContext.tracingIds?.traceId,
205
+ parentSpanId
206
+ });
207
+ return span?.exportSpan();
208
+ });
209
+ if (exportedSpan) {
210
+ const observability = this.mastra?.observability?.getSelectedInstance({});
211
+ return observability?.rebuildSpan(exportedSpan);
212
+ }
213
+ return void 0;
214
+ }
215
+ /**
216
+ * End a step span durably.
217
+ */
218
+ async endStepSpan(params) {
219
+ const { span, operationId, endOptions } = params;
220
+ if (!span) return;
221
+ await this.wrapDurableOperation(operationId, async () => {
222
+ span.end(endOptions);
223
+ });
224
+ }
225
+ /**
226
+ * Record error on step span durably.
227
+ */
228
+ async errorStepSpan(params) {
229
+ const { span, operationId, errorOptions } = params;
230
+ if (!span) return;
231
+ await this.wrapDurableOperation(operationId, async () => {
232
+ span.error(errorOptions);
233
+ });
234
+ }
235
+ /**
236
+ * Create a generic child span durably (for control-flow operations).
237
+ * On first execution, creates and exports span. On replay, returns cached span data.
238
+ */
239
+ async createChildSpan(params) {
240
+ const { executionContext, operationId, options, parentSpan } = params;
241
+ const parentSpanId = parentSpan?.id ?? executionContext.tracingIds?.workflowSpanId;
242
+ const exportedSpan = await this.wrapDurableOperation(operationId, async () => {
243
+ const observability = this.mastra?.observability?.getSelectedInstance({});
244
+ if (!observability) return void 0;
245
+ const span = observability.startSpan({
246
+ ...options,
247
+ traceId: executionContext.tracingIds?.traceId,
248
+ parentSpanId,
249
+ tracingPolicy: this.options?.tracingPolicy
250
+ });
251
+ return span?.exportSpan();
252
+ });
253
+ if (exportedSpan) {
254
+ const observability = this.mastra?.observability?.getSelectedInstance({});
255
+ return observability?.rebuildSpan(exportedSpan);
256
+ }
257
+ return void 0;
258
+ }
259
+ /**
260
+ * End a generic child span durably (for control-flow operations).
261
+ */
262
+ async endChildSpan(params) {
263
+ const { span, operationId, endOptions } = params;
264
+ if (!span) return;
265
+ await this.wrapDurableOperation(operationId, async () => {
266
+ span.end(endOptions);
267
+ });
268
+ }
269
+ /**
270
+ * Record error on a generic child span durably (for control-flow operations).
271
+ */
272
+ async errorChildSpan(params) {
273
+ const { span, operationId, errorOptions } = params;
274
+ if (!span) return;
275
+ await this.wrapDurableOperation(operationId, async () => {
276
+ span.error(errorOptions);
277
+ });
278
+ }
279
+ /**
280
+ * Execute nested InngestWorkflow using inngestStep.invoke() for durability.
281
+ * This MUST be called directly (not inside step.run()) due to Inngest constraints.
282
+ */
283
+ async executeWorkflowStep(params) {
284
+ if (!(params.step instanceof InngestWorkflow)) {
285
+ return null;
286
+ }
287
+ const {
288
+ step,
289
+ stepResults,
290
+ executionContext,
291
+ resume,
292
+ timeTravel,
293
+ prevOutput,
294
+ inputData,
295
+ pubsub,
296
+ startedAt,
297
+ perStep,
298
+ stepSpan,
299
+ actor,
300
+ requestContext: parentRequestContext
301
+ } = params;
302
+ const forwardedRequestContext = parentRequestContext ? this.serializeRequestContext(parentRequestContext) : inputData?.requestContextEntries ?? {};
303
+ const nestedTracingContext = executionContext.tracingIds?.traceId ? {
304
+ traceId: executionContext.tracingIds.traceId,
305
+ parentSpanId: stepSpan?.id
306
+ } : void 0;
307
+ const isResume = !!resume?.steps?.length;
308
+ let result;
309
+ let runId;
310
+ const isTimeTravel = !!(timeTravel && timeTravel.steps?.length > 1 && timeTravel.steps[0] === step.id);
311
+ try {
312
+ if (isResume) {
313
+ runId = stepResults[resume?.steps?.[0] ?? ""]?.suspendPayload?.__workflow_meta?.runId ?? randomUUID();
314
+ const workflowsStore = await this.mastra?.getStorage()?.getStore("workflows");
315
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
316
+ workflowName: step.id,
317
+ runId
318
+ });
319
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
320
+ function: step.getFunction(),
321
+ data: {
322
+ inputData,
323
+ initialState: executionContext.state ?? snapshot?.value ?? {},
324
+ requestContext: forwardedRequestContext,
325
+ runId,
326
+ resume: {
327
+ runId,
328
+ steps: resume.steps.slice(1),
329
+ stepResults: snapshot?.context,
330
+ resumePayload: resume.resumePayload,
331
+ resumePath: resume.steps?.[1] ? snapshot?.suspendedPaths?.[resume.steps?.[1]] : void 0
332
+ },
333
+ outputOptions: { includeState: true },
334
+ nestedWorkflowOutputMode: NESTED_WORKFLOW_OUTPUT_MODE.COMPACT,
335
+ perStep,
336
+ tracingOptions: nestedTracingContext,
337
+ actor
338
+ }
339
+ });
340
+ result = invokeResp.result;
341
+ runId = invokeResp.runId;
342
+ executionContext.state = invokeResp.result.state;
343
+ } else if (isTimeTravel) {
344
+ const workflowsStoreForTimeTravel = await this.mastra?.getStorage()?.getStore("workflows");
345
+ const snapshot = await workflowsStoreForTimeTravel?.loadWorkflowSnapshot({
346
+ workflowName: step.id,
347
+ runId: executionContext.runId
348
+ }) ?? { context: {} };
349
+ const timeTravelParams = createTimeTravelExecutionParams({
350
+ steps: timeTravel.steps.slice(1),
351
+ inputData: timeTravel.inputData,
352
+ resumeData: timeTravel.resumeData,
353
+ context: timeTravel.nestedStepResults?.[step.id] ?? {},
354
+ nestedStepsContext: timeTravel.nestedStepResults ?? {},
355
+ snapshot,
356
+ graph: step.buildExecutionGraph()
357
+ });
358
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
359
+ function: step.getFunction(),
360
+ data: {
361
+ timeTravel: timeTravelParams,
362
+ initialState: executionContext.state ?? {},
363
+ requestContext: forwardedRequestContext,
364
+ runId: executionContext.runId,
365
+ outputOptions: { includeState: true },
366
+ nestedWorkflowOutputMode: NESTED_WORKFLOW_OUTPUT_MODE.COMPACT,
367
+ perStep,
368
+ tracingOptions: nestedTracingContext,
369
+ actor
370
+ }
371
+ });
372
+ result = invokeResp.result;
373
+ runId = invokeResp.runId;
374
+ executionContext.state = invokeResp.result.state;
375
+ } else {
376
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
377
+ function: step.getFunction(),
378
+ data: {
379
+ inputData,
380
+ initialState: executionContext.state ?? {},
381
+ requestContext: forwardedRequestContext,
382
+ outputOptions: { includeState: true },
383
+ nestedWorkflowOutputMode: NESTED_WORKFLOW_OUTPUT_MODE.COMPACT,
384
+ perStep,
385
+ tracingOptions: nestedTracingContext,
386
+ actor
387
+ }
388
+ });
389
+ result = invokeResp.result;
390
+ runId = invokeResp.runId;
391
+ executionContext.state = invokeResp.result.state;
392
+ }
393
+ } catch (e) {
394
+ const errorCause = e && typeof e === "object" && "cause" in e ? e.cause : void 0;
395
+ if (errorCause && typeof errorCause === "object" && "status" in errorCause && errorCause.status === "failed") {
396
+ result = errorCause;
397
+ runId = "runId" in errorCause && typeof errorCause.runId === "string" ? errorCause.runId : randomUUID();
398
+ } else {
399
+ runId = randomUUID();
400
+ result = {
401
+ status: "failed",
402
+ error: e instanceof Error ? e : new Error(String(e))
403
+ };
404
+ }
405
+ }
406
+ const res = await this.inngestStep.run(
407
+ `workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
408
+ async () => {
409
+ if (result.status === "failed") {
410
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
411
+ type: "watch",
412
+ runId: executionContext.runId,
413
+ data: {
414
+ type: "workflow-step-result",
415
+ payload: {
416
+ id: step.id,
417
+ status: "failed",
418
+ error: result?.error,
419
+ payload: prevOutput
420
+ }
421
+ }
422
+ });
423
+ return { executionContext, result: { status: "failed", error: result?.error, endedAt: Date.now() } };
424
+ } else if (result.status === "suspended") {
425
+ const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
426
+ const stepRes = stepResult;
427
+ return stepRes?.status === "suspended";
428
+ });
429
+ for (const [stepName, stepResult] of suspendedSteps) {
430
+ const suspendPath = [stepName, ...stepResult?.suspendPayload?.__workflow_meta?.path ?? []];
431
+ executionContext.suspendedPaths[step.id] = executionContext.executionPath;
432
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
433
+ type: "watch",
434
+ runId: executionContext.runId,
435
+ data: {
436
+ type: "workflow-step-suspended",
437
+ payload: {
438
+ id: step.id,
439
+ status: "suspended"
440
+ }
441
+ }
442
+ });
443
+ return {
444
+ executionContext,
445
+ result: {
446
+ status: "suspended",
447
+ suspendedAt: Date.now(),
448
+ payload: stepResult.payload,
449
+ suspendPayload: {
450
+ ...stepResult?.suspendPayload,
451
+ __workflow_meta: { runId, path: suspendPath }
452
+ }
453
+ }
454
+ };
455
+ }
456
+ return {
457
+ executionContext,
458
+ result: {
459
+ status: "suspended",
460
+ suspendedAt: Date.now(),
461
+ payload: {}
462
+ }
463
+ };
464
+ } else if (result.status === "tripwire") {
465
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
466
+ type: "watch",
467
+ runId: executionContext.runId,
468
+ data: {
469
+ type: "workflow-step-result",
470
+ payload: {
471
+ id: step.id,
472
+ status: "tripwire",
473
+ error: result?.tripwire?.reason,
474
+ payload: prevOutput
475
+ }
476
+ }
477
+ });
478
+ return {
479
+ executionContext,
480
+ result: {
481
+ status: "tripwire",
482
+ tripwire: result?.tripwire,
483
+ endedAt: Date.now()
484
+ }
485
+ };
486
+ } else if (perStep || result.status === "paused") {
487
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
488
+ type: "watch",
489
+ runId: executionContext.runId,
490
+ data: {
491
+ type: "workflow-step-result",
492
+ payload: {
493
+ id: step.id,
494
+ status: "paused"
495
+ }
496
+ }
497
+ });
498
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
499
+ type: "watch",
500
+ runId: executionContext.runId,
501
+ data: {
502
+ type: "workflow-step-finish",
503
+ payload: {
504
+ id: step.id,
505
+ metadata: {}
506
+ }
507
+ }
508
+ });
509
+ return { executionContext, result: { status: "paused" } };
510
+ }
511
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
512
+ type: "watch",
513
+ runId: executionContext.runId,
514
+ data: {
515
+ type: "workflow-step-result",
516
+ payload: {
517
+ id: step.id,
518
+ status: "success",
519
+ output: result?.result
520
+ }
521
+ }
522
+ });
523
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
524
+ type: "watch",
525
+ runId: executionContext.runId,
526
+ data: {
527
+ type: "workflow-step-finish",
528
+ payload: {
529
+ id: step.id,
530
+ metadata: {}
531
+ }
532
+ }
533
+ });
534
+ return { executionContext, result: { status: "success", output: result?.result, endedAt: Date.now() } };
535
+ }
536
+ );
537
+ Object.assign(executionContext, res.executionContext);
538
+ return {
539
+ ...res.result,
540
+ startedAt,
541
+ payload: inputData,
542
+ resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
543
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
544
+ };
545
+ }
546
+ };
547
+ function buildTopicRef(channel, topic) {
548
+ return { channel, topic, config: {} };
549
+ }
550
+ function parseTopic(topic) {
551
+ const workflowMatch = topic.match(/^workflow\.events\.v2\.(.+)$/);
552
+ if (workflowMatch && workflowMatch[1]) {
553
+ return { runId: workflowMatch[1], topicType: "workflow" };
554
+ }
555
+ const agentMatch = topic.match(/^agent\.stream\.(.+)$/);
556
+ if (agentMatch && agentMatch[1]) {
557
+ return { runId: agentMatch[1], topicType: "agent" };
558
+ }
559
+ return null;
560
+ }
561
+ var InngestPubSub = class extends PubSub {
562
+ inngest;
563
+ workflowId;
564
+ subscriptions = /* @__PURE__ */ new Map();
565
+ constructor(inngest, workflowId) {
566
+ super();
567
+ this.inngest = inngest;
568
+ this.workflowId = workflowId;
569
+ }
570
+ /**
571
+ * Publish an event to Inngest's realtime system.
572
+ *
573
+ * Supported topic formats:
574
+ * - "workflow.events.v2.{runId}" - workflow events
575
+ * -> channel: "workflow:{workflowId}:{runId}", topic: "watch"
576
+ * - "agent.stream.{runId}" - agent stream events
577
+ * -> channel: "agent:{runId}", topic: "agent-stream"
578
+ * (Note: agent stream uses runId-only channel so nested workflows can publish to same channel)
579
+ */
580
+ async publish(topic, event) {
581
+ const parsed = parseTopic(topic);
582
+ if (!parsed) {
583
+ return;
584
+ }
585
+ const { runId, topicType } = parsed;
586
+ const inngestTopic = topicType === "agent" ? "agent-stream" : "watch";
587
+ const channel = topicType === "agent" ? `agent:${runId}` : `workflow:${this.workflowId}:${runId}`;
588
+ try {
589
+ const dataToSend = topicType === "agent" ? event : event.data;
590
+ await this.inngest.realtime.publish(buildTopicRef(channel, inngestTopic), dataToSend);
591
+ } catch (err) {
592
+ if (topicType === "agent" && (event.type === "finish" || event.type === "error")) {
593
+ throw err;
594
+ }
595
+ console.error("InngestPubSub publish error:", err?.message ?? err);
596
+ }
597
+ }
598
+ /**
599
+ * Subscribe to events from Inngest's realtime system.
600
+ *
601
+ * Supported topic formats:
602
+ * - "workflow.events.v2.{runId}" - workflow events
603
+ * -> channel: "workflow:{workflowId}:{runId}", topic: "watch"
604
+ * - "agent.stream.{runId}" - agent stream events
605
+ * -> channel: "agent:{runId}", topic: "agent-stream"
606
+ * (Note: agent stream uses runId-only channel so nested workflows can publish to same channel)
607
+ */
608
+ async subscribe(topic, cb) {
609
+ const parsed = parseTopic(topic);
610
+ if (!parsed) {
611
+ return;
612
+ }
613
+ const { runId, topicType } = parsed;
614
+ if (this.subscriptions.has(topic)) {
615
+ this.subscriptions.get(topic).callbacks.add(cb);
616
+ return;
617
+ }
618
+ const callbacks = /* @__PURE__ */ new Set([cb]);
619
+ const inngestTopic = topicType === "agent" ? "agent-stream" : "watch";
620
+ const channel = topicType === "agent" ? `agent:${runId}` : `workflow:${this.workflowId}:${runId}`;
621
+ const stream = await subscribe(
622
+ {
623
+ channel,
624
+ topics: [inngestTopic],
625
+ app: this.inngest
626
+ },
627
+ (message) => {
628
+ let event;
629
+ if (topicType === "agent" && message.data?.type && message.data?.runId) {
630
+ event = {
631
+ id: crypto.randomUUID(),
632
+ createdAt: /* @__PURE__ */ new Date(),
633
+ ...message.data
634
+ };
635
+ } else {
636
+ event = {
637
+ id: crypto.randomUUID(),
638
+ type: inngestTopic,
639
+ runId,
640
+ data: message.data,
641
+ createdAt: /* @__PURE__ */ new Date()
642
+ };
643
+ }
644
+ for (const callback of callbacks) {
645
+ callback(event);
646
+ }
647
+ }
648
+ );
649
+ this.subscriptions.set(topic, {
650
+ unsubscribe: () => {
651
+ try {
652
+ void stream.cancel();
653
+ } catch (err) {
654
+ console.error("InngestPubSub unsubscribe error:", err);
655
+ }
656
+ },
657
+ callbacks
658
+ });
659
+ }
660
+ /**
661
+ * Unsubscribe a callback from a topic.
662
+ * If no callbacks remain, the underlying Inngest subscription is cancelled.
663
+ */
664
+ async unsubscribe(topic, cb) {
665
+ const sub = this.subscriptions.get(topic);
666
+ if (!sub) {
667
+ return;
668
+ }
669
+ sub.callbacks.delete(cb);
670
+ if (sub.callbacks.size === 0) {
671
+ sub.unsubscribe();
672
+ this.subscriptions.delete(topic);
673
+ }
674
+ }
675
+ /**
676
+ * Flush any pending operations. No-op for Inngest.
677
+ */
678
+ async flush() {
679
+ }
680
+ /**
681
+ * Clean up all subscriptions during graceful shutdown.
682
+ */
683
+ async close() {
684
+ for (const [, sub] of this.subscriptions) {
685
+ sub.unsubscribe();
686
+ }
687
+ this.subscriptions.clear();
688
+ }
689
+ };
690
+ var InngestRun = class extends Run {
691
+ inngest;
692
+ serializedStepGraph;
693
+ #mastra;
694
+ constructor(params, inngest) {
695
+ super(params);
696
+ this.inngest = inngest;
697
+ this.serializedStepGraph = params.serializedStepGraph;
698
+ this.#mastra = params.mastra;
699
+ }
700
+ /**
701
+ * Get run output using hybrid approach: realtime subscription + polling fallback.
702
+ * Resolves as soon as either method detects completion.
703
+ */
704
+ async getRunOutput(_eventId, maxWaitMs = 3e5) {
705
+ const storage = this.#mastra?.getStorage();
706
+ const workflowsStore = await storage?.getStore("workflows");
707
+ if (!workflowsStore) {
708
+ throw new NonRetriableError(`Workflow storage is required to retrieve output for run ${this.runId}`);
709
+ }
710
+ return new Promise((resolve, reject) => {
711
+ let resolved = false;
712
+ let unsubscribe = null;
713
+ let pollTimeoutId = null;
714
+ const cleanup = () => {
715
+ if (unsubscribe) {
716
+ try {
717
+ unsubscribe();
718
+ } catch {
719
+ }
720
+ }
721
+ if (pollTimeoutId) {
722
+ clearTimeout(pollTimeoutId);
723
+ }
724
+ };
725
+ const handleResult = (result, _source) => {
726
+ if (!resolved) {
727
+ resolved = true;
728
+ cleanup();
729
+ resolve(result);
730
+ }
731
+ };
732
+ const handleError = (error, _source) => {
733
+ if (!resolved) {
734
+ resolved = true;
735
+ cleanup();
736
+ reject(error);
737
+ }
738
+ };
739
+ let realtimeStreamPromise = null;
740
+ const startRealtimeSubscription = async () => {
741
+ try {
742
+ realtimeStreamPromise = subscribe(
743
+ {
744
+ channel: `workflow:${this.workflowId}:${this.runId}`,
745
+ topics: ["watch"],
746
+ app: this.inngest
747
+ },
748
+ async (message) => {
749
+ if (resolved) return;
750
+ const event = message.data;
751
+ if (event?.type === "workflow-finish") {
752
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
753
+ workflowName: this.workflowId,
754
+ runId: this.runId
755
+ });
756
+ if (snapshot?.context) {
757
+ snapshot.context = hydrateSerializedStepErrors(snapshot.context);
758
+ }
759
+ const realtimeResult = {
760
+ steps: snapshot?.context,
761
+ status: event.payload?.status ?? snapshot?.status,
762
+ input: snapshot?.context?.input
763
+ };
764
+ const resultValue = event.payload?.result ?? snapshot?.result;
765
+ if (resultValue !== void 0) realtimeResult.result = resultValue;
766
+ const rawError = event.payload?.error ?? snapshot?.error;
767
+ if (rawError) {
768
+ realtimeResult.error = getErrorFromUnknown(rawError, { serializeStack: false });
769
+ }
770
+ if (snapshot?.value !== void 0) realtimeResult.state = snapshot.value;
771
+ const result = { output: { result: realtimeResult } };
772
+ handleResult(result);
773
+ }
774
+ }
775
+ );
776
+ unsubscribe = () => {
777
+ realtimeStreamPromise?.then((stream) => stream.cancel().catch(() => {
778
+ })).catch(() => {
779
+ });
780
+ };
781
+ await realtimeStreamPromise;
782
+ } catch {
783
+ }
784
+ };
785
+ const startPolling = async () => {
786
+ const startTime = Date.now();
787
+ const poll = async () => {
788
+ if (resolved) {
789
+ return;
790
+ }
791
+ if (Date.now() - startTime >= maxWaitMs) {
792
+ handleError(new NonRetriableError(`Workflow did not complete within ${maxWaitMs}ms`));
793
+ return;
794
+ }
795
+ try {
796
+ const snapshot = await workflowsStore.loadWorkflowSnapshot({
797
+ workflowName: this.workflowId,
798
+ runId: this.runId
799
+ });
800
+ if (!snapshot || snapshot.status === "running" || snapshot.status === "waiting" || snapshot.status === "pending") {
801
+ pollTimeoutId = setTimeout(poll, 150 + Math.random() * 100);
802
+ return;
803
+ }
804
+ if (snapshot.context) {
805
+ snapshot.context = hydrateSerializedStepErrors(snapshot.context);
806
+ }
807
+ const pollingResult = {
808
+ steps: snapshot.context,
809
+ status: snapshot.status,
810
+ input: snapshot.context?.input
811
+ };
812
+ if (snapshot.result !== void 0) pollingResult.result = snapshot.result;
813
+ if (snapshot.error !== void 0) {
814
+ pollingResult.error = getErrorFromUnknown(snapshot.error, { serializeStack: false });
815
+ }
816
+ if (snapshot.value !== void 0) pollingResult.state = snapshot.value;
817
+ handleResult({ output: { result: pollingResult } });
818
+ } catch (error) {
819
+ if (error instanceof NonRetriableError) {
820
+ handleError(error);
821
+ return;
822
+ }
823
+ handleError(
824
+ new NonRetriableError(
825
+ `Failed to poll workflow status: ${error instanceof Error ? error.message : String(error)}`
826
+ ));
827
+ }
828
+ };
829
+ void poll();
830
+ };
831
+ void startRealtimeSubscription();
832
+ void startPolling();
833
+ });
834
+ }
835
+ async cancel() {
836
+ const storage = this.#mastra?.getStorage();
837
+ await this.inngest.send({
838
+ name: `cancel.workflow.${this.workflowId}`,
839
+ data: {
840
+ runId: this.runId
841
+ }
842
+ });
843
+ const workflowsStore = await storage?.getStore("workflows");
844
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
845
+ workflowName: this.workflowId,
846
+ runId: this.runId
847
+ });
848
+ if (snapshot) {
849
+ await workflowsStore?.persistWorkflowSnapshot({
850
+ workflowName: this.workflowId,
851
+ runId: this.runId,
852
+ resourceId: this.resourceId,
853
+ snapshot: {
854
+ ...snapshot,
855
+ status: "canceled",
856
+ value: snapshot.value
857
+ }
858
+ });
859
+ }
860
+ }
861
+ async start(args) {
862
+ return this._start(args);
863
+ }
864
+ /**
865
+ * Starts the workflow execution without waiting for completion (fire-and-forget).
866
+ * Returns immediately with the runId after sending the event to Inngest.
867
+ * The workflow executes independently in Inngest.
868
+ * Use this when you don't need to wait for the result or want to avoid polling failures.
869
+ */
870
+ async startAsync(args) {
871
+ const workflowsStore = await this.#mastra.getStorage()?.getStore("workflows");
872
+ await workflowsStore?.persistWorkflowSnapshot({
873
+ workflowName: this.workflowId,
874
+ runId: this.runId,
875
+ resourceId: this.resourceId,
876
+ snapshot: {
877
+ runId: this.runId,
878
+ serializedStepGraph: this.serializedStepGraph,
879
+ status: "running",
880
+ value: {},
881
+ context: {},
882
+ activePaths: [],
883
+ suspendedPaths: {},
884
+ activeStepsPath: {},
885
+ resumeLabels: {},
886
+ waitingPaths: {},
887
+ timestamp: Date.now()
888
+ }
889
+ });
890
+ const inputDataToUse = await this._validateInput(args.inputData);
891
+ const initialStateToUse = await this._validateInitialState(args.initialState ?? {});
892
+ const eventOutput = await this.inngest.send({
893
+ name: `workflow.${this.workflowId}`,
894
+ data: {
895
+ inputData: inputDataToUse,
896
+ initialState: initialStateToUse,
897
+ runId: this.runId,
898
+ resourceId: this.resourceId,
899
+ outputOptions: args.outputOptions,
900
+ tracingOptions: args.tracingOptions,
901
+ requestContext: args.requestContext ? Object.fromEntries(args.requestContext.entries()) : {},
902
+ actor: args.actor,
903
+ perStep: args.perStep
904
+ }
905
+ });
906
+ const eventId = eventOutput.ids[0];
907
+ if (!eventId) {
908
+ throw new Error("Event ID is not set");
909
+ }
910
+ return { runId: this.runId };
911
+ }
912
+ async _start({
913
+ inputData,
914
+ initialState,
915
+ outputOptions,
916
+ tracingOptions,
917
+ format,
918
+ requestContext,
919
+ actor,
920
+ perStep
921
+ }) {
922
+ const workflowsStore = await this.#mastra.getStorage()?.getStore("workflows");
923
+ await workflowsStore?.persistWorkflowSnapshot({
924
+ workflowName: this.workflowId,
925
+ runId: this.runId,
926
+ resourceId: this.resourceId,
927
+ snapshot: {
928
+ runId: this.runId,
929
+ serializedStepGraph: this.serializedStepGraph,
930
+ status: "running",
931
+ value: {},
932
+ context: {},
933
+ activePaths: [],
934
+ suspendedPaths: {},
935
+ activeStepsPath: {},
936
+ resumeLabels: {},
937
+ waitingPaths: {},
938
+ timestamp: Date.now()
939
+ }
940
+ });
941
+ const inputDataToUse = await this._validateInput(inputData);
942
+ const initialStateToUse = await this._validateInitialState(initialState ?? {});
943
+ const eventName = `workflow.${this.workflowId}`;
944
+ const eventOutput = await this.inngest.send({
945
+ name: eventName,
946
+ data: {
947
+ inputData: inputDataToUse,
948
+ initialState: initialStateToUse,
949
+ runId: this.runId,
950
+ resourceId: this.resourceId,
951
+ outputOptions,
952
+ tracingOptions,
953
+ format,
954
+ requestContext: requestContext ? Object.fromEntries(requestContext.entries()) : {},
955
+ actor,
956
+ perStep
957
+ }
958
+ });
959
+ const eventId = eventOutput.ids[0];
960
+ if (!eventId) {
961
+ throw new Error("Event ID is not set");
962
+ }
963
+ const runOutput = await this.getRunOutput(eventId);
964
+ const result = runOutput?.output?.result;
965
+ this.hydrateFailedResult(result);
966
+ if (!outputOptions?.includeState) {
967
+ delete result.state;
968
+ }
969
+ if (result.status !== "suspended") {
970
+ this.cleanup?.();
971
+ }
972
+ return result;
973
+ }
974
+ async resume(params) {
975
+ const p = this._resume(params).then((result) => {
976
+ if (result.status !== "suspended") {
977
+ this.closeStreamAction?.().catch(() => {
978
+ });
979
+ }
980
+ return result;
981
+ });
982
+ this.executionResults = p;
983
+ return p;
984
+ }
985
+ /**
986
+ * Performs all resume preparation and dispatches the resume event to Inngest,
987
+ * but does NOT wait for the workflow result. Shared by `_resume()` (which polls
988
+ * for the result afterwards) and `resumeAsync()` (which returns immediately).
989
+ *
990
+ * Send-time failures (invalid resume data, event send failure) reject synchronously,
991
+ * and the snapshot is rolled back to its prior state on send failure.
992
+ */
993
+ async _resumeAndSendEvent(params) {
994
+ const storage = this.#mastra?.getStorage();
995
+ const workflowsStore = await storage?.getStore("workflows");
996
+ if (!workflowsStore) {
997
+ throw new NonRetriableError(`Workflow storage is required to resume run ${this.runId}`);
998
+ }
999
+ const snapshot = await workflowsStore.loadWorkflowSnapshot({
1000
+ workflowName: this.workflowId,
1001
+ runId: this.runId
1002
+ });
1003
+ if (!snapshot) {
1004
+ throw new NonRetriableError(`Cannot resume run ${this.runId}: snapshot not found`);
1005
+ }
1006
+ const snapshotResumeLabel = params.label ? snapshot.resumeLabels?.[params.label] : void 0;
1007
+ const stepParam = snapshotResumeLabel?.stepId ?? params.step;
1008
+ let steps = [];
1009
+ if (stepParam) {
1010
+ if (typeof stepParam === "string") {
1011
+ steps = stepParam.split(".");
1012
+ } else {
1013
+ steps = (Array.isArray(stepParam) ? stepParam : [stepParam]).map(
1014
+ (step) => typeof step === "string" ? step : step?.id
1015
+ );
1016
+ }
1017
+ }
1018
+ const suspendedStep = this.workflowSteps[steps?.[0] ?? ""];
1019
+ const resumeDataToUse = await this._validateResumeData(params.resumeData, suspendedStep);
1020
+ const persistedRequestContext = snapshot?.requestContext ?? {};
1021
+ const newRequestContext = params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {};
1022
+ const mergedRequestContext = { ...persistedRequestContext, ...newRequestContext };
1023
+ await workflowsStore.persistWorkflowSnapshot({
1024
+ workflowName: this.workflowId,
1025
+ runId: this.runId,
1026
+ resourceId: this.resourceId,
1027
+ snapshot: {
1028
+ ...snapshot,
1029
+ status: "running",
1030
+ result: void 0,
1031
+ error: void 0,
1032
+ timestamp: Date.now()
1033
+ }
1034
+ });
1035
+ let eventOutput;
1036
+ try {
1037
+ eventOutput = await this.inngest.send({
1038
+ name: `workflow.${this.workflowId}`,
1039
+ data: {
1040
+ inputData: resumeDataToUse,
1041
+ initialState: snapshot?.value ?? {},
1042
+ runId: this.runId,
1043
+ workflowId: this.workflowId,
1044
+ stepResults: snapshot?.context,
1045
+ resume: {
1046
+ steps,
1047
+ stepResults: snapshot?.context,
1048
+ resumePayload: resumeDataToUse,
1049
+ resumePath: steps?.[0] ? snapshot?.suspendedPaths?.[steps?.[0]] : void 0
1050
+ },
1051
+ requestContext: mergedRequestContext,
1052
+ // `actor` is a per-call trust signal, not rehydrated from the snapshot like
1053
+ // `requestContext` is above. This intentionally matches the default engine,
1054
+ // which passes `actor: params.actor` on resume and never reads it from the
1055
+ // snapshot (see packages/core/src/workflows/workflow.ts `_resume`). The caller
1056
+ // (a trusted background system) re-supplies `actor` on each resume; we never
1057
+ // persist a membership-bypass signal into durable storage.
1058
+ actor: params.actor,
1059
+ perStep: params.perStep
1060
+ }
1061
+ });
1062
+ } catch (err) {
1063
+ try {
1064
+ await workflowsStore.persistWorkflowSnapshot({
1065
+ workflowName: this.workflowId,
1066
+ runId: this.runId,
1067
+ resourceId: this.resourceId,
1068
+ snapshot
1069
+ });
1070
+ } catch (rollbackErr) {
1071
+ console.error("Failed to rollback snapshot during resume error recovery:", rollbackErr);
1072
+ }
1073
+ throw err;
1074
+ }
1075
+ const eventId = eventOutput.ids[0];
1076
+ if (!eventId) {
1077
+ throw new Error("Event ID is not set");
1078
+ }
1079
+ return { eventId };
1080
+ }
1081
+ async _resume(params) {
1082
+ const { eventId } = await this._resumeAndSendEvent(params);
1083
+ const runOutput = await this.getRunOutput(eventId);
1084
+ const result = runOutput?.output?.result;
1085
+ this.hydrateFailedResult(result);
1086
+ return result;
1087
+ }
1088
+ /**
1089
+ * Resumes a suspended workflow without waiting for completion (fire-and-forget).
1090
+ * Returns immediately with the runId after sending the resume event to Inngest.
1091
+ * The workflow continues executing independently in Inngest.
1092
+ *
1093
+ * Mirrors `startAsync()`: send-time failures (invalid resume data, event send
1094
+ * failure) still reject synchronously and roll back the snapshot, but the result
1095
+ * is never polled via `getRunOutput()`. This avoids the polling-based 404 race when
1096
+ * you don't need the resolved result inline.
1097
+ *
1098
+ * NOTE: this is exposed over HTTP / the client SDK as `resume-no-wait` / `resumeNoWait()`,
1099
+ * not `resumeAsync`, because the existing `resumeAsync()` client/server surface awaits the
1100
+ * full workflow result. TODO(v2): consolidate so `resumeAsync` consistently means
1101
+ * fire-and-forget across core, client SDK and HTTP routes (breaking change deferred to v2).
1102
+ */
1103
+ async resumeAsync(params) {
1104
+ await this._resumeAndSendEvent(params);
1105
+ return { runId: this.runId };
1106
+ }
1107
+ async timeTravel(params) {
1108
+ const p = this._timeTravel(params).then((result) => {
1109
+ if (result.status !== "suspended") {
1110
+ this.closeStreamAction?.().catch(() => {
1111
+ });
1112
+ }
1113
+ return result;
1114
+ });
1115
+ this.executionResults = p;
1116
+ return p;
1117
+ }
1118
+ async _timeTravel(params) {
1119
+ if (!params.step || Array.isArray(params.step) && params.step?.length === 0) {
1120
+ throw new Error("Step is required and must be a valid step or array of steps");
1121
+ }
1122
+ let steps = [];
1123
+ if (typeof params.step === "string") {
1124
+ steps = params.step.split(".");
1125
+ } else {
1126
+ steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
1127
+ (step) => typeof step === "string" ? step : step?.id
1128
+ );
1129
+ }
1130
+ if (steps.length === 0) {
1131
+ throw new Error("No steps provided to timeTravel");
1132
+ }
1133
+ const storage = this.#mastra?.getStorage();
1134
+ const workflowsStore = await storage?.getStore("workflows");
1135
+ if (!workflowsStore) {
1136
+ throw new NonRetriableError(`Workflow storage is required to time-travel run ${this.runId}`);
1137
+ }
1138
+ const snapshot = await workflowsStore.loadWorkflowSnapshot({
1139
+ workflowName: this.workflowId,
1140
+ runId: this.runId
1141
+ });
1142
+ let snapshotForRollback = snapshot;
1143
+ if (!snapshot) {
1144
+ const pendingSnapshot = {
1145
+ runId: this.runId,
1146
+ serializedStepGraph: this.serializedStepGraph,
1147
+ status: "pending",
1148
+ value: {},
1149
+ context: {},
1150
+ activePaths: [],
1151
+ suspendedPaths: {},
1152
+ activeStepsPath: {},
1153
+ resumeLabels: {},
1154
+ waitingPaths: {},
1155
+ timestamp: Date.now()
1156
+ };
1157
+ await workflowsStore.persistWorkflowSnapshot({
1158
+ workflowName: this.workflowId,
1159
+ runId: this.runId,
1160
+ resourceId: this.resourceId,
1161
+ snapshot: pendingSnapshot
1162
+ });
1163
+ snapshotForRollback = pendingSnapshot;
1164
+ }
1165
+ if (snapshot?.status === "running") {
1166
+ throw new Error("This workflow run is still running, cannot time travel");
1167
+ }
1168
+ let inputDataToUse = params.inputData;
1169
+ if (inputDataToUse && steps.length === 1) {
1170
+ inputDataToUse = await this._validateTimetravelInputData(params.inputData, this.workflowSteps[steps[0]]);
1171
+ }
1172
+ const timeTravelData = createTimeTravelExecutionParams({
1173
+ steps,
1174
+ inputData: inputDataToUse,
1175
+ resumeData: params.resumeData,
1176
+ context: params.context,
1177
+ nestedStepsContext: params.nestedStepsContext,
1178
+ snapshot: snapshot ?? { context: {} },
1179
+ graph: this.executionGraph,
1180
+ initialState: params.initialState,
1181
+ perStep: params.perStep
1182
+ });
1183
+ const previousSnapshot = snapshotForRollback;
1184
+ await workflowsStore.persistWorkflowSnapshot({
1185
+ workflowName: this.workflowId,
1186
+ runId: this.runId,
1187
+ resourceId: this.resourceId,
1188
+ snapshot: {
1189
+ runId: this.runId,
1190
+ serializedStepGraph: this.serializedStepGraph,
1191
+ status: "running",
1192
+ value: {},
1193
+ context: {},
1194
+ activePaths: [],
1195
+ suspendedPaths: {},
1196
+ activeStepsPath: {},
1197
+ resumeLabels: {},
1198
+ waitingPaths: {},
1199
+ timestamp: Date.now()
1200
+ }
1201
+ });
1202
+ let eventOutput;
1203
+ try {
1204
+ eventOutput = await this.inngest.send({
1205
+ name: `workflow.${this.workflowId}`,
1206
+ data: {
1207
+ initialState: timeTravelData.state,
1208
+ runId: this.runId,
1209
+ workflowId: this.workflowId,
1210
+ stepResults: timeTravelData.stepResults,
1211
+ timeTravel: timeTravelData,
1212
+ tracingOptions: params.tracingOptions,
1213
+ outputOptions: params.outputOptions,
1214
+ requestContext: params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {},
1215
+ actor: params.actor,
1216
+ perStep: params.perStep
1217
+ }
1218
+ });
1219
+ } catch (err) {
1220
+ if (previousSnapshot) {
1221
+ try {
1222
+ await workflowsStore.persistWorkflowSnapshot({
1223
+ workflowName: this.workflowId,
1224
+ runId: this.runId,
1225
+ resourceId: this.resourceId,
1226
+ snapshot: previousSnapshot
1227
+ });
1228
+ } catch (rollbackErr) {
1229
+ console.error("Failed to rollback snapshot during time-travel error recovery:", rollbackErr);
1230
+ }
1231
+ }
1232
+ throw err;
1233
+ }
1234
+ const eventId = eventOutput.ids[0];
1235
+ if (!eventId) {
1236
+ throw new Error("Event ID is not set");
1237
+ }
1238
+ const runOutput = await this.getRunOutput(eventId);
1239
+ const result = runOutput?.output?.result;
1240
+ this.hydrateFailedResult(result);
1241
+ if (!params.outputOptions?.includeState) {
1242
+ delete result.state;
1243
+ }
1244
+ return result;
1245
+ }
1246
+ watch(cb) {
1247
+ let active = true;
1248
+ const streamPromise = subscribe(
1249
+ {
1250
+ channel: `workflow:${this.workflowId}:${this.runId}`,
1251
+ topics: ["watch"],
1252
+ app: this.inngest
1253
+ },
1254
+ (message) => {
1255
+ if (active) {
1256
+ cb(message.data);
1257
+ }
1258
+ }
1259
+ );
1260
+ return () => {
1261
+ active = false;
1262
+ streamPromise.then(async (stream) => {
1263
+ return stream.cancel();
1264
+ }).catch((err) => {
1265
+ console.error(err);
1266
+ });
1267
+ };
1268
+ }
1269
+ streamLegacy({
1270
+ inputData,
1271
+ requestContext,
1272
+ actor
1273
+ } = {}) {
1274
+ const { readable, writable } = new TransformStream();
1275
+ const writer = writable.getWriter();
1276
+ void writer.write({
1277
+ // @ts-expect-error - stream event type mismatch
1278
+ type: "start",
1279
+ payload: { runId: this.runId }
1280
+ });
1281
+ const unwatch = this.watch(async (event) => {
1282
+ try {
1283
+ const e = {
1284
+ ...event,
1285
+ type: event.type.replace("workflow-", "")
1286
+ };
1287
+ if (e.type === "step-output") {
1288
+ e.type = e.payload.output.type;
1289
+ e.payload = e.payload.output.payload;
1290
+ }
1291
+ await writer.write(e);
1292
+ } catch {
1293
+ }
1294
+ });
1295
+ this.closeStreamAction = async () => {
1296
+ await writer.write({
1297
+ type: "finish",
1298
+ // @ts-expect-error - stream event type mismatch
1299
+ payload: { runId: this.runId }
1300
+ });
1301
+ unwatch();
1302
+ try {
1303
+ await writer.close();
1304
+ } catch (err) {
1305
+ console.error("Error closing stream:", err);
1306
+ } finally {
1307
+ writer.releaseLock();
1308
+ }
1309
+ };
1310
+ this.executionResults = this._start({ inputData, requestContext, actor, format: "legacy" }).then((result) => {
1311
+ if (result.status !== "suspended") {
1312
+ this.closeStreamAction?.().catch(() => {
1313
+ });
1314
+ }
1315
+ return result;
1316
+ });
1317
+ return {
1318
+ stream: readable,
1319
+ getWorkflowState: () => this.executionResults
1320
+ };
1321
+ }
1322
+ stream({
1323
+ inputData,
1324
+ requestContext,
1325
+ actor,
1326
+ tracingOptions,
1327
+ closeOnSuspend = true,
1328
+ initialState,
1329
+ outputOptions,
1330
+ perStep
1331
+ } = {}) {
1332
+ if (this.closeStreamAction && this.streamOutput) {
1333
+ return this.streamOutput;
1334
+ }
1335
+ this.closeStreamAction = async () => {
1336
+ };
1337
+ const self = this;
1338
+ const stream = new ReadableStream({
1339
+ async start(controller) {
1340
+ const unwatch = self.watch(async ({ type, from = ChunkFrom.WORKFLOW, payload }) => {
1341
+ controller.enqueue({
1342
+ type,
1343
+ runId: self.runId,
1344
+ from,
1345
+ payload: {
1346
+ stepName: payload?.id,
1347
+ ...payload
1348
+ }
1349
+ });
1350
+ });
1351
+ self.closeStreamAction = async () => {
1352
+ unwatch();
1353
+ try {
1354
+ await controller.close();
1355
+ } catch (err) {
1356
+ console.error("Error closing stream:", err);
1357
+ }
1358
+ };
1359
+ const executionResultsPromise = self._start({
1360
+ inputData,
1361
+ requestContext,
1362
+ actor,
1363
+ // tracingContext, // We are not able to pass a reference to a span here, what to do?
1364
+ initialState,
1365
+ tracingOptions,
1366
+ outputOptions,
1367
+ format: "vnext",
1368
+ perStep
1369
+ });
1370
+ let executionResults;
1371
+ try {
1372
+ executionResults = await executionResultsPromise;
1373
+ if (closeOnSuspend) {
1374
+ self.closeStreamAction?.().catch(() => {
1375
+ });
1376
+ } else if (executionResults.status !== "suspended") {
1377
+ self.closeStreamAction?.().catch(() => {
1378
+ });
1379
+ }
1380
+ if (self.streamOutput) {
1381
+ self.streamOutput.updateResults(
1382
+ executionResults
1383
+ );
1384
+ }
1385
+ } catch (err) {
1386
+ self.streamOutput?.rejectResults(err);
1387
+ self.closeStreamAction?.().catch(() => {
1388
+ });
1389
+ }
1390
+ }
1391
+ });
1392
+ this.streamOutput = new WorkflowRunOutput({
1393
+ runId: this.runId,
1394
+ workflowId: this.workflowId,
1395
+ stream
1396
+ });
1397
+ return this.streamOutput;
1398
+ }
1399
+ timeTravelStream({
1400
+ inputData,
1401
+ resumeData,
1402
+ initialState,
1403
+ step,
1404
+ context,
1405
+ nestedStepsContext,
1406
+ requestContext,
1407
+ actor,
1408
+ // tracingContext,
1409
+ tracingOptions,
1410
+ outputOptions,
1411
+ perStep
1412
+ }) {
1413
+ this.closeStreamAction = async () => {
1414
+ };
1415
+ const self = this;
1416
+ const stream = new ReadableStream({
1417
+ async start(controller) {
1418
+ const unwatch = self.watch(async ({ type, from = ChunkFrom.WORKFLOW, payload }) => {
1419
+ controller.enqueue({
1420
+ type,
1421
+ runId: self.runId,
1422
+ from,
1423
+ payload: {
1424
+ stepName: payload?.id,
1425
+ ...payload
1426
+ }
1427
+ });
1428
+ });
1429
+ self.closeStreamAction = async () => {
1430
+ unwatch();
1431
+ try {
1432
+ controller.close();
1433
+ } catch (err) {
1434
+ console.error("Error closing stream:", err);
1435
+ }
1436
+ };
1437
+ const executionResultsPromise = self._timeTravel({
1438
+ inputData,
1439
+ step,
1440
+ context,
1441
+ nestedStepsContext,
1442
+ resumeData,
1443
+ initialState,
1444
+ requestContext,
1445
+ actor,
1446
+ tracingOptions,
1447
+ outputOptions,
1448
+ perStep
1449
+ });
1450
+ self.executionResults = executionResultsPromise;
1451
+ let executionResults;
1452
+ try {
1453
+ executionResults = await executionResultsPromise;
1454
+ self.closeStreamAction?.().catch(() => {
1455
+ });
1456
+ if (self.streamOutput) {
1457
+ self.streamOutput.updateResults(executionResults);
1458
+ }
1459
+ } catch (err) {
1460
+ self.streamOutput?.rejectResults(err);
1461
+ self.closeStreamAction?.().catch(() => {
1462
+ });
1463
+ }
1464
+ }
1465
+ });
1466
+ this.streamOutput = new WorkflowRunOutput({
1467
+ runId: this.runId,
1468
+ workflowId: this.workflowId,
1469
+ stream
1470
+ });
1471
+ return this.streamOutput;
1472
+ }
1473
+ /**
1474
+ * Hydrates errors in a failed workflow result back to proper Error instances.
1475
+ * This ensures error.cause chains and custom properties are preserved.
1476
+ */
1477
+ hydrateFailedResult(result) {
1478
+ if (result.status === "failed") {
1479
+ result.error = getErrorFromUnknown(result.error, { serializeStack: false });
1480
+ if (result.steps) {
1481
+ hydrateSerializedStepErrors(result.steps);
1482
+ }
1483
+ }
1484
+ }
1485
+ };
1486
+
1487
+ // src/workflow.ts
1488
+ var InngestWorkflow = class _InngestWorkflow extends Workflow {
1489
+ #mastra;
1490
+ inngest;
1491
+ function;
1492
+ cronFunction;
1493
+ flowControlConfig;
1494
+ cronConfig;
1495
+ /**
1496
+ * Optional override that lets a host (e.g. `createInngestAgent`) provide the
1497
+ * PubSub instance used by workflow steps for publishing chunk/finish events.
1498
+ * When set, the workflow function uses this factory instead of constructing
1499
+ * a fresh `InngestPubSub`. This is what lets `DurableAgent.observe()` see
1500
+ * cached history when the agent wraps its PubSub in a `CachingPubSub`.
1501
+ */
1502
+ #pubsubFactory;
1503
+ constructor(params, inngest) {
1504
+ const { concurrency, rateLimit, throttle, debounce, priority, cron, inputData, initialState, ...workflowParams } = params;
1505
+ super(workflowParams);
1506
+ this.engineType = "inngest";
1507
+ const flowControlEntries = Object.entries({ concurrency, rateLimit, throttle, debounce, priority }).filter(
1508
+ ([_, value]) => value !== void 0
1509
+ );
1510
+ this.flowControlConfig = flowControlEntries.length > 0 ? Object.fromEntries(flowControlEntries) : void 0;
1511
+ this.#mastra = params.mastra;
1512
+ this.inngest = inngest;
1513
+ if (cron) {
1514
+ this.cronConfig = { cron, inputData, initialState };
1515
+ }
1516
+ }
1517
+ async listWorkflowRuns(args) {
1518
+ const storage = this.#mastra?.getStorage();
1519
+ if (!storage) {
1520
+ this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
1521
+ return { runs: [], total: 0 };
1522
+ }
1523
+ const workflowsStore = await storage.getStore("workflows");
1524
+ if (!workflowsStore) {
1525
+ return { runs: [], total: 0 };
1526
+ }
1527
+ return workflowsStore.listWorkflowRuns({ workflowName: this.id, ...args ?? {} });
1528
+ }
1529
+ /**
1530
+ * Override the PubSub used inside the durable workflow function. Callers like
1531
+ * `createInngestAgent` use this to route workflow event publishes through the
1532
+ * agent's `CachingPubSub`, so `observe()` can replay cached history.
1533
+ *
1534
+ * The factory receives the workflow's own default `InngestPubSub` (constructed
1535
+ * with this workflow's id) as input. Hosts should wrap that instance rather
1536
+ * than substitute it, so workflow-event channels (which encode the workflow
1537
+ * id) remain workflow-local. Returning a `CachingPubSub` wrapping the default
1538
+ * is the canonical pattern.
1539
+ *
1540
+ * The factory is propagated to every nested `InngestWorkflow` in the step
1541
+ * graph. Nested workflows run as their own Inngest functions and resolve
1542
+ * their own pubsub at runtime; each invocation passes its own workflow-local
1543
+ * default into the same factory, so the host can share cross-workflow state
1544
+ * (e.g. a single agent-scoped cache) without collapsing per-workflow channel
1545
+ * isolation.
1546
+ */
1547
+ __setPubsubFactory(factory) {
1548
+ this.#pubsubFactory = factory;
1549
+ const updateNested = (step) => {
1550
+ if ((step.type === "step" || step.type === "loop" || step.type === "foreach") && step.step instanceof _InngestWorkflow) {
1551
+ step.step.__setPubsubFactory(factory);
1552
+ } else if (step.type === "parallel" || step.type === "conditional") {
1553
+ for (const subStep of step.steps) {
1554
+ updateNested(subStep);
1555
+ }
1556
+ }
1557
+ };
1558
+ for (const step of this.executionGraph.steps) {
1559
+ updateNested(step);
1560
+ }
1561
+ }
1562
+ /**
1563
+ * Test-only accessor for the configured pubsub factory. Lets tests verify that
1564
+ * a host (e.g. `createInngestAgent`) wired the workflow to its agent pubsub
1565
+ * without having to drive a real Inngest invocation.
1566
+ */
1567
+ __getPubsubFactory() {
1568
+ return this.#pubsubFactory;
1569
+ }
1570
+ __registerMastra(mastra) {
1571
+ super.__registerMastra(mastra);
1572
+ this.#mastra = mastra;
1573
+ this.executionEngine.__registerMastra(mastra);
1574
+ const updateNested = (step) => {
1575
+ if ((step.type === "step" || step.type === "loop" || step.type === "foreach") && step.step instanceof _InngestWorkflow) {
1576
+ step.step.__registerMastra(mastra);
1577
+ } else if (step.type === "parallel" || step.type === "conditional") {
1578
+ for (const subStep of step.steps) {
1579
+ updateNested(subStep);
1580
+ }
1581
+ }
1582
+ };
1583
+ if (this.executionGraph.steps.length) {
1584
+ for (const step of this.executionGraph.steps) {
1585
+ updateNested(step);
1586
+ }
1587
+ }
1588
+ }
1589
+ async createRun(options) {
1590
+ const runIdToUse = options?.runId || randomUUID();
1591
+ const existingInMemoryRun = this.runs.get(runIdToUse);
1592
+ const newRun = new InngestRun(
1593
+ {
1594
+ workflowId: this.id,
1595
+ runId: runIdToUse,
1596
+ resourceId: options?.resourceId,
1597
+ executionEngine: this.executionEngine,
1598
+ executionGraph: this.executionGraph,
1599
+ serializedStepGraph: this.serializedStepGraph,
1600
+ mastra: this.#mastra,
1601
+ retryConfig: this.retryConfig,
1602
+ cleanup: () => this.runs.delete(runIdToUse),
1603
+ workflowSteps: this.steps,
1604
+ workflowEngineType: this.engineType,
1605
+ validateInputs: this.options.validateInputs
1606
+ },
1607
+ this.inngest
1608
+ );
1609
+ const run = existingInMemoryRun ?? newRun;
1610
+ this.runs.set(runIdToUse, run);
1611
+ const shouldPersistSnapshot = this.options.shouldPersistSnapshot({
1612
+ workflowStatus: run.workflowRunStatus,
1613
+ stepResults: {}
1614
+ });
1615
+ const existingStoredRun = await this.getWorkflowRunById(runIdToUse, {
1616
+ withNestedWorkflows: false
1617
+ });
1618
+ const existsInStorage = existingStoredRun && !existingStoredRun.isFromInMemory;
1619
+ if (!existsInStorage && shouldPersistSnapshot) {
1620
+ const workflowsStore = await this.mastra?.getStorage()?.getStore("workflows");
1621
+ await workflowsStore?.persistWorkflowSnapshot({
1622
+ workflowName: this.id,
1623
+ runId: runIdToUse,
1624
+ resourceId: options?.resourceId,
1625
+ snapshot: {
1626
+ runId: runIdToUse,
1627
+ status: "pending",
1628
+ value: {},
1629
+ context: {},
1630
+ activePaths: [],
1631
+ activeStepsPath: {},
1632
+ waitingPaths: {},
1633
+ serializedStepGraph: this.serializedStepGraph,
1634
+ suspendedPaths: {},
1635
+ resumeLabels: {},
1636
+ result: void 0,
1637
+ error: void 0,
1638
+ timestamp: Date.now()
1639
+ }
1640
+ });
1641
+ }
1642
+ return run;
1643
+ }
1644
+ //createCronFunction is only called if cronConfig.cron is defined.
1645
+ createCronFunction() {
1646
+ if (this.cronFunction) {
1647
+ return this.cronFunction;
1648
+ }
1649
+ this.cronFunction = this.inngest.createFunction(
1650
+ {
1651
+ id: `workflow.${this.id}.cron`,
1652
+ retries: 0,
1653
+ cancelOn: [{ event: `cancel.workflow.${this.id}` }],
1654
+ triggers: { cron: this.cronConfig?.cron ?? "" },
1655
+ ...this.flowControlConfig
1656
+ },
1657
+ async () => {
1658
+ const run = await this.createRun();
1659
+ const result = await run.start({
1660
+ inputData: this.cronConfig?.inputData,
1661
+ initialState: this.cronConfig?.initialState
1662
+ });
1663
+ return { result, runId: run.runId };
1664
+ }
1665
+ );
1666
+ return this.cronFunction;
1667
+ }
1668
+ getFunction() {
1669
+ if (this.function) {
1670
+ return this.function;
1671
+ }
1672
+ this.function = this.inngest.createFunction(
1673
+ {
1674
+ id: `workflow.${this.id}`,
1675
+ retries: 0,
1676
+ cancelOn: [{ event: `cancel.workflow.${this.id}` }],
1677
+ triggers: { event: `workflow.${this.id}` },
1678
+ // Spread flow control configuration
1679
+ ...this.flowControlConfig
1680
+ },
1681
+ async ({ event, step, attempt }) => {
1682
+ let {
1683
+ inputData,
1684
+ initialState,
1685
+ runId,
1686
+ resourceId,
1687
+ resume,
1688
+ outputOptions,
1689
+ format,
1690
+ timeTravel,
1691
+ perStep,
1692
+ tracingOptions,
1693
+ actor,
1694
+ nestedWorkflowOutputMode: requestedNestedWorkflowOutputMode
1695
+ } = event.data;
1696
+ const nestedWorkflowOutputMode = resolveNestedWorkflowOutputMode(requestedNestedWorkflowOutputMode);
1697
+ const shouldCompactNestedWorkflowOutput = nestedWorkflowOutputMode === NESTED_WORKFLOW_OUTPUT_MODE.COMPACT;
1698
+ if (!runId) {
1699
+ runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
1700
+ return randomUUID();
1701
+ });
1702
+ }
1703
+ const defaultPubsub = new InngestPubSub(this.inngest, this.id);
1704
+ const pubsub = this.#pubsubFactory?.(defaultPubsub) ?? defaultPubsub;
1705
+ const requestContext = new RequestContext(Object.entries(event.data.requestContext ?? {}));
1706
+ const mastra = this.#mastra;
1707
+ const tracingPolicy = this.options.tracingPolicy;
1708
+ const workflowSpanData = await step.run(`workflow.${this.id}.span.start`, async () => {
1709
+ const observability = mastra?.observability?.getSelectedInstance({ requestContext });
1710
+ if (!observability) return void 0;
1711
+ const span = observability.startSpan({
1712
+ type: SpanType.WORKFLOW_RUN,
1713
+ name: `workflow run: '${this.id}'`,
1714
+ entityType: EntityType.WORKFLOW_RUN,
1715
+ entityId: this.id,
1716
+ entityName: this.id,
1717
+ input: inputData,
1718
+ metadata: {
1719
+ resourceId,
1720
+ runId
1721
+ },
1722
+ tracingPolicy,
1723
+ tracingOptions,
1724
+ requestContext
1725
+ });
1726
+ return span?.exportSpan();
1727
+ });
1728
+ const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
1729
+ let result;
1730
+ try {
1731
+ result = await engine.execute({
1732
+ workflowId: this.id,
1733
+ runId,
1734
+ resourceId,
1735
+ graph: this.executionGraph,
1736
+ serializedStepGraph: this.serializedStepGraph,
1737
+ input: inputData,
1738
+ initialState,
1739
+ pubsub,
1740
+ retryConfig: this.retryConfig,
1741
+ requestContext,
1742
+ actor,
1743
+ resume,
1744
+ timeTravel,
1745
+ perStep,
1746
+ format,
1747
+ abortController: new AbortController(),
1748
+ // For Inngest, we don't pass workflowSpan - step spans use tracingIds instead
1749
+ workflowSpan: void 0,
1750
+ // Pass tracing IDs for durable span operations
1751
+ tracingIds: workflowSpanData ? {
1752
+ traceId: workflowSpanData.traceId,
1753
+ workflowSpanId: workflowSpanData.id
1754
+ } : void 0,
1755
+ outputOptions,
1756
+ outputWriter: async (chunk) => {
1757
+ try {
1758
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1759
+ type: "watch",
1760
+ runId,
1761
+ data: chunk
1762
+ });
1763
+ } catch (err) {
1764
+ this.logger.debug?.("Failed to publish watch event:", err);
1765
+ }
1766
+ }
1767
+ });
1768
+ } catch (executionError) {
1769
+ result = {
1770
+ status: "failed",
1771
+ steps: {},
1772
+ state: initialState ?? {},
1773
+ error: executionError instanceof Error ? executionError : new Error(String(executionError))
1774
+ };
1775
+ }
1776
+ const returnedResult = shouldCompactNestedWorkflowOutput ? compactNestedWorkflowResult(result) : result;
1777
+ let finalizeError;
1778
+ let finalizeErrored = false;
1779
+ try {
1780
+ await step.run(`workflow.${this.id}.finalize`, async () => {
1781
+ if (result.status === "failed" && inputData?.__workflowKind === "durable-agent" && inputData?.runId) {
1782
+ const error = result.error instanceof Error ? result.error : new Error(String(result.error));
1783
+ try {
1784
+ await emitErrorEvent(pubsub, inputData.runId, error);
1785
+ } catch (e) {
1786
+ this.logger.debug?.("Failed to emit error event:", e);
1787
+ }
1788
+ }
1789
+ if (result.status !== "paused") {
1790
+ await engine.invokeLifecycleCallbacksInternal({
1791
+ status: result.status,
1792
+ result: "result" in result ? result.result : void 0,
1793
+ error: "error" in result ? result.error : void 0,
1794
+ steps: result.steps,
1795
+ tripwire: "tripwire" in result ? result.tripwire : void 0,
1796
+ runId,
1797
+ workflowId: this.id,
1798
+ resourceId,
1799
+ input: inputData,
1800
+ requestContext,
1801
+ state: result.state ?? initialState ?? {}
1802
+ });
1803
+ }
1804
+ if (workflowSpanData) {
1805
+ const observability = mastra?.observability?.getSelectedInstance({ requestContext });
1806
+ if (observability) {
1807
+ const workflowSpan = observability.rebuildSpan(workflowSpanData);
1808
+ if (result.status === "failed") {
1809
+ workflowSpan.error({
1810
+ error: result.error instanceof Error ? result.error : new Error(String(result.error)),
1811
+ attributes: { status: "failed" }
1812
+ });
1813
+ } else {
1814
+ workflowSpan.end({
1815
+ output: result.status === "success" ? result.result : void 0,
1816
+ attributes: { status: result.status }
1817
+ });
1818
+ }
1819
+ }
1820
+ }
1821
+ const shouldPersistFinalSnapshot = this.options.shouldPersistSnapshot({
1822
+ workflowStatus: result.status,
1823
+ stepResults: result.steps
1824
+ });
1825
+ if (shouldPersistFinalSnapshot) {
1826
+ const workflowsStore = await mastra?.getStorage()?.getStore("workflows");
1827
+ if (workflowsStore) {
1828
+ let existingSnapshot;
1829
+ if (result.status === "suspended") {
1830
+ existingSnapshot = await workflowsStore.loadWorkflowSnapshot({
1831
+ workflowName: this.id,
1832
+ runId
1833
+ }) ?? void 0;
1834
+ }
1835
+ await workflowsStore.persistWorkflowSnapshot({
1836
+ workflowName: this.id,
1837
+ runId,
1838
+ resourceId,
1839
+ snapshot: {
1840
+ runId,
1841
+ status: result.status,
1842
+ value: result.state ?? initialState ?? {},
1843
+ context: toSnapshotContext(result.steps),
1844
+ activePaths: [],
1845
+ activeStepsPath: {},
1846
+ serializedStepGraph: this.serializedStepGraph,
1847
+ suspendedPaths: existingSnapshot?.suspendedPaths ?? {},
1848
+ waitingPaths: {},
1849
+ resumeLabels: existingSnapshot?.resumeLabels ?? result.resumeLabels ?? {},
1850
+ result: result.status === "success" ? toSnapshotResult(result.result) : void 0,
1851
+ error: result.status === "failed" ? result.error : void 0,
1852
+ timestamp: Date.now()
1853
+ }
1854
+ });
1855
+ }
1856
+ }
1857
+ try {
1858
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1859
+ type: "watch",
1860
+ runId,
1861
+ data: {
1862
+ type: "workflow-finish",
1863
+ payload: {
1864
+ status: result.status,
1865
+ result: result.status === "success" ? result.result : void 0,
1866
+ error: result.status === "failed" ? result.error : void 0
1867
+ }
1868
+ }
1869
+ });
1870
+ } catch (publishError) {
1871
+ this.logger.debug?.("Failed to publish workflow-finish event:", publishError);
1872
+ }
1873
+ if (result.status === "failed") {
1874
+ throw new NonRetriableError(`Workflow failed`, {
1875
+ cause: shouldCompactNestedWorkflowOutput ? { ...returnedResult, runId } : result
1876
+ });
1877
+ }
1878
+ return shouldCompactNestedWorkflowOutput ? { status: result.status } : result;
1879
+ });
1880
+ } catch (error) {
1881
+ finalizeErrored = true;
1882
+ finalizeError = error;
1883
+ } finally {
1884
+ const observability = mastra?.observability?.getSelectedInstance({ requestContext });
1885
+ if (observability) {
1886
+ try {
1887
+ await observability.flush();
1888
+ } catch (flushError) {
1889
+ this.logger.debug?.("Failed to flush observability:", flushError);
1890
+ }
1891
+ }
1892
+ }
1893
+ if (finalizeErrored) {
1894
+ throw finalizeError;
1895
+ }
1896
+ return { result: returnedResult, runId };
1897
+ }
1898
+ );
1899
+ return this.function;
1900
+ }
1901
+ getNestedFunctions(steps) {
1902
+ return steps.flatMap((step) => {
1903
+ if (step.type === "step" || step.type === "loop" || step.type === "foreach") {
1904
+ if (step.step instanceof _InngestWorkflow) {
1905
+ return [step.step.getFunction(), ...step.step.getNestedFunctions(step.step.executionGraph.steps)];
1906
+ }
1907
+ return [];
1908
+ } else if (step.type === "parallel" || step.type === "conditional") {
1909
+ return this.getNestedFunctions(step.steps);
1910
+ }
1911
+ return [];
1912
+ });
1913
+ }
1914
+ getFunctions() {
1915
+ return [
1916
+ this.getFunction(),
1917
+ ...this.cronConfig?.cron ? [this.createCronFunction()] : [],
1918
+ ...this.getNestedFunctions(this.executionGraph.steps)
1919
+ ];
1920
+ }
1921
+ };
1922
+ function toSnapshotContext(steps) {
1923
+ return steps;
1924
+ }
1925
+ function toSnapshotResult(output) {
1926
+ return output;
1927
+ }
1928
+
1929
+ // src/functions.ts
1930
+ function collectInngestFunctions({
1931
+ mastra,
1932
+ functions: userFunctions = []
1933
+ }) {
1934
+ const workflows = mastra.listWorkflows();
1935
+ const workflowFunctions = Array.from(
1936
+ new Set(
1937
+ Object.values(workflows).flatMap((workflow) => {
1938
+ if (workflow instanceof InngestWorkflow) {
1939
+ workflow.__registerMastra(mastra);
1940
+ return workflow.getFunctions();
1941
+ }
1942
+ return [];
1943
+ })
1944
+ )
1945
+ );
1946
+ return [...workflowFunctions, ...userFunctions];
1947
+ }
1948
+
1949
+ // src/connect.ts
1950
+ async function connect(options) {
1951
+ const { mastra, inngest, functions, registerOptions, ...connectOptions } = options;
1952
+ const appFunctions = collectInngestFunctions({ mastra, functions });
1953
+ if (appFunctions.length === 0) {
1954
+ console.warn(
1955
+ "[@mastra/inngest] connect() was called with no Inngest workflows and no additional functions. The worker will connect to Inngest but has nothing to execute. Register at least one InngestWorkflow on the Mastra instance or pass `functions: [...]`."
1956
+ );
1957
+ }
1958
+ const { connect: connectWorker } = await import('inngest/connect');
1959
+ return connectWorker({
1960
+ // Top-level Connect options first, then registerOptions so they take precedence
1961
+ // for any overlapping keys (e.g. `signingKey`). This matches serve()'s behavior.
1962
+ ...connectOptions,
1963
+ ...registerOptions,
1964
+ apps: [{ client: inngest, functions: appFunctions }]
1965
+ });
1966
+ }
1967
+
1968
+ export { InngestExecutionEngine, InngestPubSub, InngestRun, InngestWorkflow, collectInngestFunctions, connect };
1969
+ //# sourceMappingURL=chunk-SE7QPBOX.js.map
1970
+ //# sourceMappingURL=chunk-SE7QPBOX.js.map