@mastra/inngest 0.0.0-fix-prompt-enhance-route-20251210210827 → 0.0.0-fix-11329-windows-path-20251222155941

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.
package/dist/index.js CHANGED
@@ -1,12 +1,14 @@
1
1
  import { Tool } from '@mastra/core/tools';
2
- import { DefaultExecutionEngine, createTimeTravelExecutionParams, Run, Workflow } from '@mastra/core/workflows';
3
- import { EMITTER_SYMBOL, STREAM_FORMAT_SYMBOL } from '@mastra/core/workflows/_constants';
2
+ import { DefaultExecutionEngine, createTimeTravelExecutionParams, Run, hydrateSerializedStepErrors, Workflow } from '@mastra/core/workflows';
3
+ import { PUBSUB_SYMBOL, STREAM_FORMAT_SYMBOL } from '@mastra/core/workflows/_constants';
4
4
  import { z } from 'zod';
5
5
  import { randomUUID } from 'crypto';
6
6
  import { RequestContext } from '@mastra/core/di';
7
7
  import { RetryAfterError, NonRetriableError } from 'inngest';
8
- import { ReadableStream } from 'stream/web';
8
+ import { getErrorFromUnknown } from '@mastra/core/error';
9
9
  import { subscribe } from '@inngest/realtime';
10
+ import { PubSub } from '@mastra/core/events';
11
+ import { ReadableStream } from 'stream/web';
10
12
  import { ChunkFrom, WorkflowRunOutput } from '@mastra/core/stream';
11
13
  import { serve as serve$1 } from 'inngest/hono';
12
14
 
@@ -23,17 +25,18 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
23
25
  // Hook Overrides
24
26
  // =============================================================================
25
27
  /**
26
- * Format errors with stack traces for better debugging in Inngest
28
+ * Format errors while preserving Error instances and their custom properties.
29
+ * Uses getErrorFromUnknown to ensure all error properties are preserved.
27
30
  */
28
31
  formatResultError(error, lastOutput) {
29
- if (error instanceof Error) {
30
- return error.stack ?? error.message;
31
- }
32
32
  const outputError = lastOutput?.error;
33
- if (outputError instanceof Error) {
34
- return outputError.message;
35
- }
36
- return outputError ?? error ?? "Unknown error";
33
+ const errorSource = error || outputError;
34
+ const errorInstance = getErrorFromUnknown(errorSource, {
35
+ serializeStack: true,
36
+ // Include stack in JSON for better debugging in Inngest
37
+ fallbackMessage: "Unknown workflow error"
38
+ });
39
+ return errorInstance.toJSON();
37
40
  }
38
41
  /**
39
42
  * Detect InngestWorkflow instances for special nested workflow handling
@@ -65,18 +68,24 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
65
68
  error: e,
66
69
  attributes: { status: "failed" }
67
70
  });
71
+ if (cause.error && !(cause.error instanceof Error)) {
72
+ cause.error = getErrorFromUnknown(cause.error, { serializeStack: false });
73
+ }
68
74
  return { ok: false, error: cause };
69
75
  }
70
- const errorMessage = e instanceof Error ? e.message : String(e);
76
+ const errorInstance = getErrorFromUnknown(e, {
77
+ serializeStack: false,
78
+ fallbackMessage: "Unknown step execution error"
79
+ });
71
80
  params.stepSpan?.error({
72
- error: e,
81
+ error: errorInstance,
73
82
  attributes: { status: "failed" }
74
83
  });
75
84
  return {
76
85
  ok: false,
77
86
  error: {
78
87
  status: "failed",
79
- error: `Error: ${errorMessage}`,
88
+ error: errorInstance,
80
89
  endedAt: Date.now()
81
90
  }
82
91
  };
@@ -105,11 +114,14 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
105
114
  return await operationFn();
106
115
  } catch (e) {
107
116
  if (retryConfig) {
108
- const errorMessage = e instanceof Error ? e.message : String(e);
109
- throw new RetryAfterError(errorMessage, retryConfig.delay, {
117
+ const errorInstance = getErrorFromUnknown(e, {
118
+ serializeStack: false,
119
+ fallbackMessage: "Unknown step execution error"
120
+ });
121
+ throw new RetryAfterError(errorInstance.message, retryConfig.delay, {
110
122
  cause: {
111
123
  status: "failed",
112
- error: `Error: ${errorMessage}`,
124
+ error: errorInstance,
113
125
  endedAt: Date.now()
114
126
  }
115
127
  });
@@ -124,6 +136,18 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
124
136
  getEngineContext() {
125
137
  return { step: this.inngestStep };
126
138
  }
139
+ /**
140
+ * For Inngest, lifecycle callbacks are invoked in the workflow's finalize step
141
+ * (wrapped in step.run for durability), not in execute(). Override to skip.
142
+ */
143
+ async invokeLifecycleCallbacks(_result) {
144
+ }
145
+ /**
146
+ * Actually invoke the lifecycle callbacks. Called from workflow.ts finalize step.
147
+ */
148
+ async invokeLifecycleCallbacksInternal(result) {
149
+ return super.invokeLifecycleCallbacks(result);
150
+ }
127
151
  /**
128
152
  * Execute nested InngestWorkflow using inngestStep.invoke() for durability.
129
153
  * This MUST be called directly (not inside step.run()) due to Inngest constraints.
@@ -132,7 +156,18 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
132
156
  if (!(params.step instanceof InngestWorkflow)) {
133
157
  return null;
134
158
  }
135
- const { step, stepResults, executionContext, resume, timeTravel, prevOutput, inputData, emitter, startedAt } = params;
159
+ const {
160
+ step,
161
+ stepResults,
162
+ executionContext,
163
+ resume,
164
+ timeTravel,
165
+ prevOutput,
166
+ inputData,
167
+ pubsub,
168
+ startedAt,
169
+ perStep
170
+ } = params;
136
171
  const isResume = !!resume?.steps?.length;
137
172
  let result;
138
173
  let runId;
@@ -157,7 +192,8 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
157
192
  resumePayload: resume.resumePayload,
158
193
  resumePath: resume.steps?.[1] ? snapshot?.suspendedPaths?.[resume.steps?.[1]] : void 0
159
194
  },
160
- outputOptions: { includeState: true }
195
+ outputOptions: { includeState: true },
196
+ perStep
161
197
  }
162
198
  });
163
199
  result = invokeResp.result;
@@ -183,7 +219,8 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
183
219
  timeTravel: timeTravelParams,
184
220
  initialState: executionContext.state ?? {},
185
221
  runId: executionContext.runId,
186
- outputOptions: { includeState: true }
222
+ outputOptions: { includeState: true },
223
+ perStep
187
224
  }
188
225
  });
189
226
  result = invokeResp.result;
@@ -195,7 +232,8 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
195
232
  data: {
196
233
  inputData,
197
234
  initialState: executionContext.state ?? {},
198
- outputOptions: { includeState: true }
235
+ outputOptions: { includeState: true },
236
+ perStep
199
237
  }
200
238
  });
201
239
  result = invokeResp.result;
@@ -221,16 +259,20 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
221
259
  `workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
222
260
  async () => {
223
261
  if (result.status === "failed") {
224
- await emitter.emit("watch", {
225
- type: "workflow-step-result",
226
- payload: {
227
- id: step.id,
228
- status: "failed",
229
- error: result?.error,
230
- payload: prevOutput
262
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
263
+ type: "watch",
264
+ runId: executionContext.runId,
265
+ data: {
266
+ type: "workflow-step-result",
267
+ payload: {
268
+ id: step.id,
269
+ status: "failed",
270
+ error: result?.error,
271
+ payload: prevOutput
272
+ }
231
273
  }
232
274
  });
233
- return { executionContext, result: { status: "failed", error: result?.error } };
275
+ return { executionContext, result: { status: "failed", error: result?.error, endedAt: Date.now() } };
234
276
  } else if (result.status === "suspended") {
235
277
  const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
236
278
  const stepRes = stepResult;
@@ -239,17 +281,22 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
239
281
  for (const [stepName, stepResult] of suspendedSteps) {
240
282
  const suspendPath = [stepName, ...stepResult?.suspendPayload?.__workflow_meta?.path ?? []];
241
283
  executionContext.suspendedPaths[step.id] = executionContext.executionPath;
242
- await emitter.emit("watch", {
243
- type: "workflow-step-suspended",
244
- payload: {
245
- id: step.id,
246
- status: "suspended"
284
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
285
+ type: "watch",
286
+ runId: executionContext.runId,
287
+ data: {
288
+ type: "workflow-step-suspended",
289
+ payload: {
290
+ id: step.id,
291
+ status: "suspended"
292
+ }
247
293
  }
248
294
  });
249
295
  return {
250
296
  executionContext,
251
297
  result: {
252
298
  status: "suspended",
299
+ suspendedAt: Date.now(),
253
300
  payload: stepResult.payload,
254
301
  suspendPayload: {
255
302
  ...stepResult?.suspendPayload,
@@ -262,39 +309,205 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
262
309
  executionContext,
263
310
  result: {
264
311
  status: "suspended",
312
+ suspendedAt: Date.now(),
265
313
  payload: {}
266
314
  }
267
315
  };
316
+ } else if (result.status === "tripwire") {
317
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
318
+ type: "watch",
319
+ runId: executionContext.runId,
320
+ data: {
321
+ type: "workflow-step-result",
322
+ payload: {
323
+ id: step.id,
324
+ status: "tripwire",
325
+ error: result?.tripwire?.reason,
326
+ payload: prevOutput
327
+ }
328
+ }
329
+ });
330
+ return {
331
+ executionContext,
332
+ result: {
333
+ status: "tripwire",
334
+ tripwire: result?.tripwire,
335
+ endedAt: Date.now()
336
+ }
337
+ };
338
+ } else if (perStep || result.status === "paused") {
339
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
340
+ type: "watch",
341
+ runId: executionContext.runId,
342
+ data: {
343
+ type: "workflow-step-result",
344
+ payload: {
345
+ id: step.id,
346
+ status: "paused"
347
+ }
348
+ }
349
+ });
350
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
351
+ type: "watch",
352
+ runId: executionContext.runId,
353
+ data: {
354
+ type: "workflow-step-finish",
355
+ payload: {
356
+ id: step.id,
357
+ metadata: {}
358
+ }
359
+ }
360
+ });
361
+ return { executionContext, result: { status: "paused" } };
268
362
  }
269
- await emitter.emit("watch", {
270
- type: "workflow-step-result",
271
- payload: {
272
- id: step.id,
273
- status: "success",
274
- output: result?.result
363
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
364
+ type: "watch",
365
+ runId: executionContext.runId,
366
+ data: {
367
+ type: "workflow-step-result",
368
+ payload: {
369
+ id: step.id,
370
+ status: "success",
371
+ output: result?.result
372
+ }
275
373
  }
276
374
  });
277
- await emitter.emit("watch", {
278
- type: "workflow-step-finish",
279
- payload: {
280
- id: step.id,
281
- metadata: {}
375
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
376
+ type: "watch",
377
+ runId: executionContext.runId,
378
+ data: {
379
+ type: "workflow-step-finish",
380
+ payload: {
381
+ id: step.id,
382
+ metadata: {}
383
+ }
282
384
  }
283
385
  });
284
- return { executionContext, result: { status: "success", output: result?.result } };
386
+ return { executionContext, result: { status: "success", output: result?.result, endedAt: Date.now() } };
285
387
  }
286
388
  );
287
389
  Object.assign(executionContext, res.executionContext);
288
390
  return {
289
391
  ...res.result,
290
392
  startedAt,
291
- endedAt: Date.now(),
292
393
  payload: inputData,
293
394
  resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
294
395
  resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
295
396
  };
296
397
  }
297
398
  };
399
+ var InngestPubSub = class extends PubSub {
400
+ inngest;
401
+ workflowId;
402
+ publishFn;
403
+ subscriptions = /* @__PURE__ */ new Map();
404
+ constructor(inngest, workflowId, publishFn) {
405
+ super();
406
+ this.inngest = inngest;
407
+ this.workflowId = workflowId;
408
+ this.publishFn = publishFn;
409
+ }
410
+ /**
411
+ * Publish an event to Inngest's realtime system.
412
+ *
413
+ * Topic format: "workflow.events.v2.{runId}"
414
+ * Maps to Inngest channel: "workflow:{workflowId}:{runId}"
415
+ */
416
+ async publish(topic, event) {
417
+ if (!this.publishFn) {
418
+ return;
419
+ }
420
+ const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
421
+ if (!match) {
422
+ return;
423
+ }
424
+ const runId = match[1];
425
+ try {
426
+ await this.publishFn({
427
+ channel: `workflow:${this.workflowId}:${runId}`,
428
+ topic: "watch",
429
+ data: event.data
430
+ });
431
+ } catch (err) {
432
+ console.error("InngestPubSub publish error:", err?.message ?? err);
433
+ }
434
+ }
435
+ /**
436
+ * Subscribe to events from Inngest's realtime system.
437
+ *
438
+ * Topic format: "workflow.events.v2.{runId}"
439
+ * Maps to Inngest channel: "workflow:{workflowId}:{runId}"
440
+ */
441
+ async subscribe(topic, cb) {
442
+ const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
443
+ if (!match || !match[1]) {
444
+ return;
445
+ }
446
+ const runId = match[1];
447
+ if (this.subscriptions.has(topic)) {
448
+ this.subscriptions.get(topic).callbacks.add(cb);
449
+ return;
450
+ }
451
+ const callbacks = /* @__PURE__ */ new Set([cb]);
452
+ const channel = `workflow:${this.workflowId}:${runId}`;
453
+ const streamPromise = subscribe(
454
+ {
455
+ channel,
456
+ topics: ["watch"],
457
+ app: this.inngest
458
+ },
459
+ (message) => {
460
+ const event = {
461
+ id: crypto.randomUUID(),
462
+ type: "watch",
463
+ runId,
464
+ data: message.data,
465
+ createdAt: /* @__PURE__ */ new Date()
466
+ };
467
+ for (const callback of callbacks) {
468
+ callback(event);
469
+ }
470
+ }
471
+ );
472
+ this.subscriptions.set(topic, {
473
+ unsubscribe: () => {
474
+ streamPromise.then((stream) => stream.cancel()).catch((err) => {
475
+ console.error("InngestPubSub unsubscribe error:", err);
476
+ });
477
+ },
478
+ callbacks
479
+ });
480
+ }
481
+ /**
482
+ * Unsubscribe a callback from a topic.
483
+ * If no callbacks remain, the underlying Inngest subscription is cancelled.
484
+ */
485
+ async unsubscribe(topic, cb) {
486
+ const sub = this.subscriptions.get(topic);
487
+ if (!sub) {
488
+ return;
489
+ }
490
+ sub.callbacks.delete(cb);
491
+ if (sub.callbacks.size === 0) {
492
+ sub.unsubscribe();
493
+ this.subscriptions.delete(topic);
494
+ }
495
+ }
496
+ /**
497
+ * Flush any pending operations. No-op for Inngest.
498
+ */
499
+ async flush() {
500
+ }
501
+ /**
502
+ * Clean up all subscriptions during graceful shutdown.
503
+ */
504
+ async close() {
505
+ for (const [, sub] of this.subscriptions) {
506
+ sub.unsubscribe();
507
+ }
508
+ this.subscriptions.clear();
509
+ }
510
+ };
298
511
  var InngestRun = class extends Run {
299
512
  inngest;
300
513
  serializedStepGraph;
@@ -306,27 +519,77 @@ var InngestRun = class extends Run {
306
519
  this.#mastra = params.mastra;
307
520
  }
308
521
  async getRuns(eventId) {
309
- const response = await fetch(`${this.inngest.apiBaseUrl ?? "https://api.inngest.com"}/v1/events/${eventId}/runs`, {
310
- headers: {
311
- Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`
522
+ const maxRetries = 3;
523
+ let lastError = null;
524
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
525
+ try {
526
+ const response = await fetch(
527
+ `${this.inngest.apiBaseUrl ?? "https://api.inngest.com"}/v1/events/${eventId}/runs`,
528
+ {
529
+ headers: {
530
+ Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`
531
+ }
532
+ }
533
+ );
534
+ if (response.status === 429) {
535
+ const retryAfter = parseInt(response.headers.get("retry-after") || "2", 10);
536
+ await new Promise((resolve) => setTimeout(resolve, retryAfter * 1e3));
537
+ continue;
538
+ }
539
+ if (!response.ok) {
540
+ throw new Error(`Inngest API error: ${response.status} ${response.statusText}`);
541
+ }
542
+ const text = await response.text();
543
+ if (!text) {
544
+ await new Promise((resolve) => setTimeout(resolve, 1e3 * (attempt + 1)));
545
+ continue;
546
+ }
547
+ const json = JSON.parse(text);
548
+ return json.data;
549
+ } catch (error) {
550
+ lastError = error;
551
+ if (attempt < maxRetries - 1) {
552
+ await new Promise((resolve) => setTimeout(resolve, 1e3 * Math.pow(2, attempt)));
553
+ }
312
554
  }
313
- });
314
- const json = await response.json();
315
- return json.data;
555
+ }
556
+ throw new NonRetriableError(`Failed to get runs after ${maxRetries} attempts: ${lastError?.message}`);
316
557
  }
317
- async getRunOutput(eventId) {
318
- let runs = await this.getRuns(eventId);
558
+ async getRunOutput(eventId, maxWaitMs = 3e5) {
559
+ const startTime = Date.now();
319
560
  const storage = this.#mastra?.getStorage();
320
- while (runs?.[0]?.status !== "Completed" || runs?.[0]?.event_id !== eventId) {
321
- await new Promise((resolve) => setTimeout(resolve, 1e3));
322
- runs = await this.getRuns(eventId);
561
+ while (Date.now() - startTime < maxWaitMs) {
562
+ let runs;
563
+ try {
564
+ runs = await this.getRuns(eventId);
565
+ } catch (error) {
566
+ if (error instanceof NonRetriableError) {
567
+ throw error;
568
+ }
569
+ throw new NonRetriableError(
570
+ `Failed to poll workflow status: ${error instanceof Error ? error.message : String(error)}`
571
+ );
572
+ }
573
+ if (runs?.[0]?.status === "Completed" && runs?.[0]?.event_id === eventId) {
574
+ return runs[0];
575
+ }
323
576
  if (runs?.[0]?.status === "Failed") {
324
577
  const snapshot = await storage?.loadWorkflowSnapshot({
325
578
  workflowName: this.workflowId,
326
579
  runId: this.runId
327
580
  });
581
+ if (snapshot?.context) {
582
+ snapshot.context = hydrateSerializedStepErrors(snapshot.context);
583
+ }
328
584
  return {
329
- output: { result: { steps: snapshot?.context, status: "failed", error: runs?.[0]?.output?.message } }
585
+ output: {
586
+ result: {
587
+ steps: snapshot?.context,
588
+ status: "failed",
589
+ // Get the original error from NonRetriableError's cause (which contains the workflow result)
590
+ error: getErrorFromUnknown(runs?.[0]?.output?.cause?.error, { serializeStack: false })
591
+ }
592
+ }
330
593
  };
331
594
  }
332
595
  if (runs?.[0]?.status === "Cancelled") {
@@ -336,8 +599,9 @@ var InngestRun = class extends Run {
336
599
  });
337
600
  return { output: { result: { steps: snapshot?.context, status: "canceled" } } };
338
601
  }
602
+ await new Promise((resolve) => setTimeout(resolve, 1e3 + Math.random() * 1e3));
339
603
  }
340
- return runs?.[0];
604
+ throw new NonRetriableError(`Workflow did not complete within ${maxWaitMs}ms`);
341
605
  }
342
606
  async cancel() {
343
607
  const storage = this.#mastra?.getStorage();
@@ -367,13 +631,60 @@ var InngestRun = class extends Run {
367
631
  async start(params) {
368
632
  return this._start(params);
369
633
  }
634
+ /**
635
+ * Starts the workflow execution without waiting for completion (fire-and-forget).
636
+ * Returns immediately with the runId after sending the event to Inngest.
637
+ * The workflow executes independently in Inngest.
638
+ * Use this when you don't need to wait for the result or want to avoid polling failures.
639
+ */
640
+ async startAsync(params) {
641
+ await this.#mastra.getStorage()?.persistWorkflowSnapshot({
642
+ workflowName: this.workflowId,
643
+ runId: this.runId,
644
+ resourceId: this.resourceId,
645
+ snapshot: {
646
+ runId: this.runId,
647
+ serializedStepGraph: this.serializedStepGraph,
648
+ status: "running",
649
+ value: {},
650
+ context: {},
651
+ activePaths: [],
652
+ suspendedPaths: {},
653
+ activeStepsPath: {},
654
+ resumeLabels: {},
655
+ waitingPaths: {},
656
+ timestamp: Date.now()
657
+ }
658
+ });
659
+ const inputDataToUse = await this._validateInput(params.inputData);
660
+ const initialStateToUse = await this._validateInitialState(params.initialState ?? {});
661
+ const eventOutput = await this.inngest.send({
662
+ name: `workflow.${this.workflowId}`,
663
+ data: {
664
+ inputData: inputDataToUse,
665
+ initialState: initialStateToUse,
666
+ runId: this.runId,
667
+ resourceId: this.resourceId,
668
+ outputOptions: params.outputOptions,
669
+ tracingOptions: params.tracingOptions,
670
+ requestContext: params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {},
671
+ perStep: params.perStep
672
+ }
673
+ });
674
+ const eventId = eventOutput.ids[0];
675
+ if (!eventId) {
676
+ throw new Error("Event ID is not set");
677
+ }
678
+ return { runId: this.runId };
679
+ }
370
680
  async _start({
371
681
  inputData,
372
682
  initialState,
373
683
  outputOptions,
374
684
  tracingOptions,
375
685
  format,
376
- requestContext
686
+ requestContext,
687
+ perStep
377
688
  }) {
378
689
  await this.#mastra.getStorage()?.persistWorkflowSnapshot({
379
690
  workflowName: this.workflowId,
@@ -405,7 +716,8 @@ var InngestRun = class extends Run {
405
716
  outputOptions,
406
717
  tracingOptions,
407
718
  format,
408
- requestContext: requestContext ? Object.fromEntries(requestContext.entries()) : {}
719
+ requestContext: requestContext ? Object.fromEntries(requestContext.entries()) : {},
720
+ perStep
409
721
  }
410
722
  });
411
723
  const eventId = eventOutput.ids[0];
@@ -414,9 +726,7 @@ var InngestRun = class extends Run {
414
726
  }
415
727
  const runOutput = await this.getRunOutput(eventId);
416
728
  const result = runOutput?.output?.result;
417
- if (result.status === "failed") {
418
- result.error = new Error(result.error);
419
- }
729
+ this.hydrateFailedResult(result);
420
730
  if (result.status !== "suspended") {
421
731
  this.cleanup?.();
422
732
  }
@@ -466,7 +776,8 @@ var InngestRun = class extends Run {
466
776
  resumePayload: resumeDataToUse,
467
777
  resumePath: steps?.[0] ? snapshot?.suspendedPaths?.[steps?.[0]] : void 0
468
778
  },
469
- requestContext: mergedRequestContext
779
+ requestContext: mergedRequestContext,
780
+ perStep: params.perStep
470
781
  }
471
782
  });
472
783
  const eventId = eventOutput.ids[0];
@@ -475,9 +786,7 @@ var InngestRun = class extends Run {
475
786
  }
476
787
  const runOutput = await this.getRunOutput(eventId);
477
788
  const result = runOutput?.output?.result;
478
- if (result.status === "failed") {
479
- result.error = new Error(result.error);
480
- }
789
+ this.hydrateFailedResult(result);
481
790
  return result;
482
791
  }
483
792
  async timeTravel(params) {
@@ -558,7 +867,8 @@ var InngestRun = class extends Run {
558
867
  timeTravel: timeTravelData,
559
868
  tracingOptions: params.tracingOptions,
560
869
  outputOptions: params.outputOptions,
561
- requestContext: params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {}
870
+ requestContext: params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {},
871
+ perStep: params.perStep
562
872
  }
563
873
  });
564
874
  const eventId = eventOutput.ids[0];
@@ -567,9 +877,7 @@ var InngestRun = class extends Run {
567
877
  }
568
878
  const runOutput = await this.getRunOutput(eventId);
569
879
  const result = runOutput?.output?.result;
570
- if (result.status === "failed") {
571
- result.error = new Error(result.error);
572
- }
880
+ this.hydrateFailedResult(result);
573
881
  return result;
574
882
  }
575
883
  watch(cb) {
@@ -651,7 +959,8 @@ var InngestRun = class extends Run {
651
959
  tracingOptions,
652
960
  closeOnSuspend = true,
653
961
  initialState,
654
- outputOptions
962
+ outputOptions,
963
+ perStep
655
964
  } = {}) {
656
965
  if (this.closeStreamAction && this.streamOutput) {
657
966
  return this.streamOutput;
@@ -687,7 +996,8 @@ var InngestRun = class extends Run {
687
996
  initialState,
688
997
  tracingOptions,
689
998
  outputOptions,
690
- format: "vnext"
999
+ format: "vnext",
1000
+ perStep
691
1001
  });
692
1002
  let executionResults;
693
1003
  try {
@@ -730,7 +1040,8 @@ var InngestRun = class extends Run {
730
1040
  nestedStepsContext,
731
1041
  requestContext,
732
1042
  tracingOptions,
733
- outputOptions
1043
+ outputOptions,
1044
+ perStep
734
1045
  }) {
735
1046
  this.closeStreamAction = async () => {
736
1047
  };
@@ -765,7 +1076,8 @@ var InngestRun = class extends Run {
765
1076
  initialState,
766
1077
  requestContext,
767
1078
  tracingOptions,
768
- outputOptions
1079
+ outputOptions,
1080
+ perStep
769
1081
  });
770
1082
  self.executionResults = executionResultsPromise;
771
1083
  let executionResults;
@@ -790,6 +1102,18 @@ var InngestRun = class extends Run {
790
1102
  });
791
1103
  return this.streamOutput;
792
1104
  }
1105
+ /**
1106
+ * Hydrates errors in a failed workflow result back to proper Error instances.
1107
+ * This ensures error.cause chains and custom properties are preserved.
1108
+ */
1109
+ hydrateFailedResult(result) {
1110
+ if (result.status === "failed") {
1111
+ result.error = getErrorFromUnknown(result.error, { serializeStack: false });
1112
+ if (result.steps) {
1113
+ hydrateSerializedStepErrors(result.steps);
1114
+ }
1115
+ }
1116
+ }
793
1117
  };
794
1118
 
795
1119
  // src/workflow.ts
@@ -827,6 +1151,7 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
827
1151
  return run ?? (this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null);
828
1152
  }
829
1153
  __registerMastra(mastra) {
1154
+ super.__registerMastra(mastra);
830
1155
  this.#mastra = mastra;
831
1156
  this.executionEngine.__registerMastra(mastra);
832
1157
  const updateNested = (step) => {
@@ -868,7 +1193,9 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
868
1193
  workflowStatus: run.workflowRunStatus,
869
1194
  stepResults: {}
870
1195
  });
871
- const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse, false);
1196
+ const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse, {
1197
+ withNestedWorkflows: false
1198
+ });
872
1199
  if (!workflowSnapshotInStorage && shouldPersistSnapshot) {
873
1200
  await this.mastra?.getStorage()?.persistWorkflowSnapshot({
874
1201
  workflowName: this.id,
@@ -907,34 +1234,13 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
907
1234
  },
908
1235
  { event: `workflow.${this.id}` },
909
1236
  async ({ event, step, attempt, publish }) => {
910
- let { inputData, initialState, runId, resourceId, resume, outputOptions, format, timeTravel } = event.data;
1237
+ let { inputData, initialState, runId, resourceId, resume, outputOptions, format, timeTravel, perStep } = event.data;
911
1238
  if (!runId) {
912
1239
  runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
913
1240
  return randomUUID();
914
1241
  });
915
1242
  }
916
- const emitter = {
917
- emit: async (event2, data) => {
918
- if (!publish) {
919
- return;
920
- }
921
- try {
922
- await publish({
923
- channel: `workflow:${this.id}:${runId}`,
924
- topic: event2,
925
- data
926
- });
927
- } catch (err) {
928
- this.logger.error("Error emitting event: " + (err?.stack ?? err?.message ?? err));
929
- }
930
- },
931
- on: (_event, _callback) => {
932
- },
933
- off: (_event, _callback) => {
934
- },
935
- once: (_event, _callback) => {
936
- }
937
- };
1243
+ const pubsub = new InngestPubSub(this.inngest, this.id, publish);
938
1244
  const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
939
1245
  const result = await engine.execute({
940
1246
  workflowId: this.id,
@@ -944,21 +1250,32 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
944
1250
  serializedStepGraph: this.serializedStepGraph,
945
1251
  input: inputData,
946
1252
  initialState,
947
- emitter,
1253
+ pubsub,
948
1254
  retryConfig: this.retryConfig,
949
1255
  requestContext: new RequestContext(Object.entries(event.data.requestContext ?? {})),
950
1256
  resume,
951
1257
  timeTravel,
1258
+ perStep,
952
1259
  format,
953
1260
  abortController: new AbortController(),
954
1261
  // currentSpan: undefined, // TODO: Pass actual parent Span from workflow execution context
955
1262
  outputOptions,
956
1263
  outputWriter: async (chunk) => {
957
- void emitter.emit("watch", chunk).catch(() => {
958
- });
1264
+ try {
1265
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1266
+ type: "watch",
1267
+ runId,
1268
+ data: chunk
1269
+ });
1270
+ } catch (err) {
1271
+ this.logger.debug?.("Failed to publish watch event:", err);
1272
+ }
959
1273
  }
960
1274
  });
961
1275
  await step.run(`workflow.${this.id}.finalize`, async () => {
1276
+ if (result.status !== "paused") {
1277
+ await engine.invokeLifecycleCallbacksInternal(result);
1278
+ }
962
1279
  if (result.status === "failed") {
963
1280
  throw new NonRetriableError(`Workflow failed`, {
964
1281
  cause: result
@@ -1043,7 +1360,8 @@ function createStep(params, agentOptions) {
1043
1360
  outputSchema,
1044
1361
  execute: async ({
1045
1362
  inputData,
1046
- [EMITTER_SYMBOL]: emitter,
1363
+ runId,
1364
+ [PUBSUB_SYMBOL]: pubsub,
1047
1365
  [STREAM_FORMAT_SYMBOL]: streamFormat,
1048
1366
  requestContext,
1049
1367
  tracingContext,
@@ -1098,22 +1416,24 @@ function createStep(params, agentOptions) {
1098
1416
  stream = modelOutput.fullStream;
1099
1417
  }
1100
1418
  if (streamFormat === "legacy") {
1101
- await emitter.emit("watch", {
1102
- type: "tool-call-streaming-start",
1103
- ...toolData ?? {}
1419
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1420
+ type: "watch",
1421
+ runId,
1422
+ data: { type: "tool-call-streaming-start", ...toolData ?? {} }
1104
1423
  });
1105
1424
  for await (const chunk of stream) {
1106
1425
  if (chunk.type === "text-delta") {
1107
- await emitter.emit("watch", {
1108
- type: "tool-call-delta",
1109
- ...toolData ?? {},
1110
- argsTextDelta: chunk.textDelta
1426
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1427
+ type: "watch",
1428
+ runId,
1429
+ data: { type: "tool-call-delta", ...toolData ?? {}, argsTextDelta: chunk.textDelta }
1111
1430
  });
1112
1431
  }
1113
1432
  }
1114
- await emitter.emit("watch", {
1115
- type: "tool-call-streaming-finish",
1116
- ...toolData ?? {}
1433
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1434
+ type: "watch",
1435
+ runId,
1436
+ data: { type: "tool-call-streaming-finish", ...toolData ?? {} }
1117
1437
  });
1118
1438
  } else {
1119
1439
  for await (const chunk of stream) {
@@ -1225,6 +1545,6 @@ function init(inngest) {
1225
1545
  };
1226
1546
  }
1227
1547
 
1228
- export { InngestExecutionEngine, InngestRun, InngestWorkflow, _compatibilityCheck, createStep, init, serve };
1548
+ export { InngestExecutionEngine, InngestPubSub, InngestRun, InngestWorkflow, _compatibilityCheck, createStep, init, serve };
1229
1549
  //# sourceMappingURL=index.js.map
1230
1550
  //# sourceMappingURL=index.js.map