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