@mastra/inngest 1.0.0-beta.6 → 1.0.0-beta.9

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,7 @@ 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 { step, stepResults, executionContext, resume, timeTravel, prevOutput, inputData, pubsub, startedAt } = params;
136
160
  const isResume = !!resume?.steps?.length;
137
161
  let result;
138
162
  let runId;
@@ -221,13 +245,17 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
221
245
  `workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
222
246
  async () => {
223
247
  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
248
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
249
+ type: "watch",
250
+ runId: executionContext.runId,
251
+ data: {
252
+ type: "workflow-step-result",
253
+ payload: {
254
+ id: step.id,
255
+ status: "failed",
256
+ error: result?.error,
257
+ payload: prevOutput
258
+ }
231
259
  }
232
260
  });
233
261
  return { executionContext, result: { status: "failed", error: result?.error } };
@@ -239,11 +267,15 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
239
267
  for (const [stepName, stepResult] of suspendedSteps) {
240
268
  const suspendPath = [stepName, ...stepResult?.suspendPayload?.__workflow_meta?.path ?? []];
241
269
  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"
270
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
271
+ type: "watch",
272
+ runId: executionContext.runId,
273
+ data: {
274
+ type: "workflow-step-suspended",
275
+ payload: {
276
+ id: step.id,
277
+ status: "suspended"
278
+ }
247
279
  }
248
280
  });
249
281
  return {
@@ -266,13 +298,17 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
266
298
  }
267
299
  };
268
300
  } else if (result.status === "tripwire") {
269
- await emitter.emit("watch", {
270
- type: "workflow-step-result",
271
- payload: {
272
- id: step.id,
273
- status: "tripwire",
274
- error: result?.tripwire?.reason,
275
- payload: prevOutput
301
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
302
+ type: "watch",
303
+ runId: executionContext.runId,
304
+ data: {
305
+ type: "workflow-step-result",
306
+ payload: {
307
+ id: step.id,
308
+ status: "tripwire",
309
+ error: result?.tripwire?.reason,
310
+ payload: prevOutput
311
+ }
276
312
  }
277
313
  });
278
314
  return {
@@ -283,19 +319,27 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
283
319
  }
284
320
  };
285
321
  }
286
- await emitter.emit("watch", {
287
- type: "workflow-step-result",
288
- payload: {
289
- id: step.id,
290
- status: "success",
291
- output: result?.result
322
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
323
+ type: "watch",
324
+ runId: executionContext.runId,
325
+ data: {
326
+ type: "workflow-step-result",
327
+ payload: {
328
+ id: step.id,
329
+ status: "success",
330
+ output: result?.result
331
+ }
292
332
  }
293
333
  });
294
- await emitter.emit("watch", {
295
- type: "workflow-step-finish",
296
- payload: {
297
- id: step.id,
298
- metadata: {}
334
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
335
+ type: "watch",
336
+ runId: executionContext.runId,
337
+ data: {
338
+ type: "workflow-step-finish",
339
+ payload: {
340
+ id: step.id,
341
+ metadata: {}
342
+ }
299
343
  }
300
344
  });
301
345
  return { executionContext, result: { status: "success", output: result?.result } };
@@ -312,6 +356,118 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
312
356
  };
313
357
  }
314
358
  };
359
+ var InngestPubSub = class extends PubSub {
360
+ inngest;
361
+ workflowId;
362
+ publishFn;
363
+ subscriptions = /* @__PURE__ */ new Map();
364
+ constructor(inngest, workflowId, publishFn) {
365
+ super();
366
+ this.inngest = inngest;
367
+ this.workflowId = workflowId;
368
+ this.publishFn = publishFn;
369
+ }
370
+ /**
371
+ * Publish an event to Inngest's realtime system.
372
+ *
373
+ * Topic format: "workflow.events.v2.{runId}"
374
+ * Maps to Inngest channel: "workflow:{workflowId}:{runId}"
375
+ */
376
+ async publish(topic, event) {
377
+ if (!this.publishFn) {
378
+ return;
379
+ }
380
+ const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
381
+ if (!match) {
382
+ return;
383
+ }
384
+ const runId = match[1];
385
+ try {
386
+ await this.publishFn({
387
+ channel: `workflow:${this.workflowId}:${runId}`,
388
+ topic: "watch",
389
+ data: event.data
390
+ });
391
+ } catch (err) {
392
+ console.error("InngestPubSub publish error:", err?.message ?? err);
393
+ }
394
+ }
395
+ /**
396
+ * Subscribe to events from Inngest's realtime system.
397
+ *
398
+ * Topic format: "workflow.events.v2.{runId}"
399
+ * Maps to Inngest channel: "workflow:{workflowId}:{runId}"
400
+ */
401
+ async subscribe(topic, cb) {
402
+ const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
403
+ if (!match || !match[1]) {
404
+ return;
405
+ }
406
+ const runId = match[1];
407
+ if (this.subscriptions.has(topic)) {
408
+ this.subscriptions.get(topic).callbacks.add(cb);
409
+ return;
410
+ }
411
+ const callbacks = /* @__PURE__ */ new Set([cb]);
412
+ const channel = `workflow:${this.workflowId}:${runId}`;
413
+ const streamPromise = subscribe(
414
+ {
415
+ channel,
416
+ topics: ["watch"],
417
+ app: this.inngest
418
+ },
419
+ (message) => {
420
+ const event = {
421
+ id: crypto.randomUUID(),
422
+ type: "watch",
423
+ runId,
424
+ data: message.data,
425
+ createdAt: /* @__PURE__ */ new Date()
426
+ };
427
+ for (const callback of callbacks) {
428
+ callback(event);
429
+ }
430
+ }
431
+ );
432
+ this.subscriptions.set(topic, {
433
+ unsubscribe: () => {
434
+ streamPromise.then((stream) => stream.cancel()).catch((err) => {
435
+ console.error("InngestPubSub unsubscribe error:", err);
436
+ });
437
+ },
438
+ callbacks
439
+ });
440
+ }
441
+ /**
442
+ * Unsubscribe a callback from a topic.
443
+ * If no callbacks remain, the underlying Inngest subscription is cancelled.
444
+ */
445
+ async unsubscribe(topic, cb) {
446
+ const sub = this.subscriptions.get(topic);
447
+ if (!sub) {
448
+ return;
449
+ }
450
+ sub.callbacks.delete(cb);
451
+ if (sub.callbacks.size === 0) {
452
+ sub.unsubscribe();
453
+ this.subscriptions.delete(topic);
454
+ }
455
+ }
456
+ /**
457
+ * Flush any pending operations. No-op for Inngest.
458
+ */
459
+ async flush() {
460
+ }
461
+ /**
462
+ * Clean up all subscriptions during graceful shutdown.
463
+ */
464
+ async close() {
465
+ for (const [, sub] of this.subscriptions) {
466
+ sub.unsubscribe();
467
+ }
468
+ this.subscriptions.clear();
469
+ }
470
+ };
315
471
  var InngestRun = class extends Run {
316
472
  inngest;
317
473
  serializedStepGraph;
@@ -323,27 +479,77 @@ var InngestRun = class extends Run {
323
479
  this.#mastra = params.mastra;
324
480
  }
325
481
  async getRuns(eventId) {
326
- const response = await fetch(`${this.inngest.apiBaseUrl ?? "https://api.inngest.com"}/v1/events/${eventId}/runs`, {
327
- headers: {
328
- Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`
482
+ const maxRetries = 3;
483
+ let lastError = null;
484
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
485
+ try {
486
+ const response = await fetch(
487
+ `${this.inngest.apiBaseUrl ?? "https://api.inngest.com"}/v1/events/${eventId}/runs`,
488
+ {
489
+ headers: {
490
+ Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`
491
+ }
492
+ }
493
+ );
494
+ if (response.status === 429) {
495
+ const retryAfter = parseInt(response.headers.get("retry-after") || "2", 10);
496
+ await new Promise((resolve) => setTimeout(resolve, retryAfter * 1e3));
497
+ continue;
498
+ }
499
+ if (!response.ok) {
500
+ throw new Error(`Inngest API error: ${response.status} ${response.statusText}`);
501
+ }
502
+ const text = await response.text();
503
+ if (!text) {
504
+ await new Promise((resolve) => setTimeout(resolve, 1e3 * (attempt + 1)));
505
+ continue;
506
+ }
507
+ const json = JSON.parse(text);
508
+ return json.data;
509
+ } catch (error) {
510
+ lastError = error;
511
+ if (attempt < maxRetries - 1) {
512
+ await new Promise((resolve) => setTimeout(resolve, 1e3 * Math.pow(2, attempt)));
513
+ }
329
514
  }
330
- });
331
- const json = await response.json();
332
- return json.data;
515
+ }
516
+ throw new NonRetriableError(`Failed to get runs after ${maxRetries} attempts: ${lastError?.message}`);
333
517
  }
334
- async getRunOutput(eventId) {
335
- let runs = await this.getRuns(eventId);
518
+ async getRunOutput(eventId, maxWaitMs = 3e5) {
519
+ const startTime = Date.now();
336
520
  const storage = this.#mastra?.getStorage();
337
- while (runs?.[0]?.status !== "Completed" || runs?.[0]?.event_id !== eventId) {
338
- await new Promise((resolve) => setTimeout(resolve, 1e3));
339
- runs = await this.getRuns(eventId);
521
+ while (Date.now() - startTime < maxWaitMs) {
522
+ let runs;
523
+ try {
524
+ runs = await this.getRuns(eventId);
525
+ } catch (error) {
526
+ if (error instanceof NonRetriableError) {
527
+ throw error;
528
+ }
529
+ throw new NonRetriableError(
530
+ `Failed to poll workflow status: ${error instanceof Error ? error.message : String(error)}`
531
+ );
532
+ }
533
+ if (runs?.[0]?.status === "Completed" && runs?.[0]?.event_id === eventId) {
534
+ return runs[0];
535
+ }
340
536
  if (runs?.[0]?.status === "Failed") {
341
537
  const snapshot = await storage?.loadWorkflowSnapshot({
342
538
  workflowName: this.workflowId,
343
539
  runId: this.runId
344
540
  });
541
+ if (snapshot?.context) {
542
+ snapshot.context = hydrateSerializedStepErrors(snapshot.context);
543
+ }
345
544
  return {
346
- output: { result: { steps: snapshot?.context, status: "failed", error: runs?.[0]?.output?.message } }
545
+ output: {
546
+ result: {
547
+ steps: snapshot?.context,
548
+ status: "failed",
549
+ // Get the original error from NonRetriableError's cause (which contains the workflow result)
550
+ error: getErrorFromUnknown(runs?.[0]?.output?.cause?.error, { serializeStack: false })
551
+ }
552
+ }
347
553
  };
348
554
  }
349
555
  if (runs?.[0]?.status === "Cancelled") {
@@ -353,8 +559,9 @@ var InngestRun = class extends Run {
353
559
  });
354
560
  return { output: { result: { steps: snapshot?.context, status: "canceled" } } };
355
561
  }
562
+ await new Promise((resolve) => setTimeout(resolve, 1e3 + Math.random() * 1e3));
356
563
  }
357
- return runs?.[0];
564
+ throw new NonRetriableError(`Workflow did not complete within ${maxWaitMs}ms`);
358
565
  }
359
566
  async cancel() {
360
567
  const storage = this.#mastra?.getStorage();
@@ -384,6 +591,51 @@ var InngestRun = class extends Run {
384
591
  async start(params) {
385
592
  return this._start(params);
386
593
  }
594
+ /**
595
+ * Starts the workflow execution without waiting for completion (fire-and-forget).
596
+ * Returns immediately with the runId after sending the event to Inngest.
597
+ * The workflow executes independently in Inngest.
598
+ * Use this when you don't need to wait for the result or want to avoid polling failures.
599
+ */
600
+ async startAsync(params) {
601
+ await this.#mastra.getStorage()?.persistWorkflowSnapshot({
602
+ workflowName: this.workflowId,
603
+ runId: this.runId,
604
+ resourceId: this.resourceId,
605
+ snapshot: {
606
+ runId: this.runId,
607
+ serializedStepGraph: this.serializedStepGraph,
608
+ status: "running",
609
+ value: {},
610
+ context: {},
611
+ activePaths: [],
612
+ suspendedPaths: {},
613
+ activeStepsPath: {},
614
+ resumeLabels: {},
615
+ waitingPaths: {},
616
+ timestamp: Date.now()
617
+ }
618
+ });
619
+ const inputDataToUse = await this._validateInput(params.inputData);
620
+ const initialStateToUse = await this._validateInitialState(params.initialState ?? {});
621
+ const eventOutput = await this.inngest.send({
622
+ name: `workflow.${this.workflowId}`,
623
+ data: {
624
+ inputData: inputDataToUse,
625
+ initialState: initialStateToUse,
626
+ runId: this.runId,
627
+ resourceId: this.resourceId,
628
+ outputOptions: params.outputOptions,
629
+ tracingOptions: params.tracingOptions,
630
+ requestContext: params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {}
631
+ }
632
+ });
633
+ const eventId = eventOutput.ids[0];
634
+ if (!eventId) {
635
+ throw new Error("Event ID is not set");
636
+ }
637
+ return { runId: this.runId };
638
+ }
387
639
  async _start({
388
640
  inputData,
389
641
  initialState,
@@ -431,9 +683,7 @@ var InngestRun = class extends Run {
431
683
  }
432
684
  const runOutput = await this.getRunOutput(eventId);
433
685
  const result = runOutput?.output?.result;
434
- if (result.status === "failed") {
435
- result.error = new Error(result.error);
436
- }
686
+ this.hydrateFailedResult(result);
437
687
  if (result.status !== "suspended") {
438
688
  this.cleanup?.();
439
689
  }
@@ -492,9 +742,7 @@ var InngestRun = class extends Run {
492
742
  }
493
743
  const runOutput = await this.getRunOutput(eventId);
494
744
  const result = runOutput?.output?.result;
495
- if (result.status === "failed") {
496
- result.error = new Error(result.error);
497
- }
745
+ this.hydrateFailedResult(result);
498
746
  return result;
499
747
  }
500
748
  async timeTravel(params) {
@@ -584,9 +832,7 @@ var InngestRun = class extends Run {
584
832
  }
585
833
  const runOutput = await this.getRunOutput(eventId);
586
834
  const result = runOutput?.output?.result;
587
- if (result.status === "failed") {
588
- result.error = new Error(result.error);
589
- }
835
+ this.hydrateFailedResult(result);
590
836
  return result;
591
837
  }
592
838
  watch(cb) {
@@ -807,6 +1053,18 @@ var InngestRun = class extends Run {
807
1053
  });
808
1054
  return this.streamOutput;
809
1055
  }
1056
+ /**
1057
+ * Hydrates errors in a failed workflow result back to proper Error instances.
1058
+ * This ensures error.cause chains and custom properties are preserved.
1059
+ */
1060
+ hydrateFailedResult(result) {
1061
+ if (result.status === "failed") {
1062
+ result.error = getErrorFromUnknown(result.error, { serializeStack: false });
1063
+ if (result.steps) {
1064
+ hydrateSerializedStepErrors(result.steps);
1065
+ }
1066
+ }
1067
+ }
810
1068
  };
811
1069
 
812
1070
  // src/workflow.ts
@@ -844,6 +1102,7 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
844
1102
  return run ?? (this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null);
845
1103
  }
846
1104
  __registerMastra(mastra) {
1105
+ super.__registerMastra(mastra);
847
1106
  this.#mastra = mastra;
848
1107
  this.executionEngine.__registerMastra(mastra);
849
1108
  const updateNested = (step) => {
@@ -885,7 +1144,9 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
885
1144
  workflowStatus: run.workflowRunStatus,
886
1145
  stepResults: {}
887
1146
  });
888
- const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse, false);
1147
+ const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse, {
1148
+ withNestedWorkflows: false
1149
+ });
889
1150
  if (!workflowSnapshotInStorage && shouldPersistSnapshot) {
890
1151
  await this.mastra?.getStorage()?.persistWorkflowSnapshot({
891
1152
  workflowName: this.id,
@@ -930,28 +1191,7 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
930
1191
  return randomUUID();
931
1192
  });
932
1193
  }
933
- const emitter = {
934
- emit: async (event2, data) => {
935
- if (!publish) {
936
- return;
937
- }
938
- try {
939
- await publish({
940
- channel: `workflow:${this.id}:${runId}`,
941
- topic: event2,
942
- data
943
- });
944
- } catch (err) {
945
- this.logger.error("Error emitting event: " + (err?.stack ?? err?.message ?? err));
946
- }
947
- },
948
- on: (_event, _callback) => {
949
- },
950
- off: (_event, _callback) => {
951
- },
952
- once: (_event, _callback) => {
953
- }
954
- };
1194
+ const pubsub = new InngestPubSub(this.inngest, this.id, publish);
955
1195
  const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
956
1196
  const result = await engine.execute({
957
1197
  workflowId: this.id,
@@ -961,7 +1201,7 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
961
1201
  serializedStepGraph: this.serializedStepGraph,
962
1202
  input: inputData,
963
1203
  initialState,
964
- emitter,
1204
+ pubsub,
965
1205
  retryConfig: this.retryConfig,
966
1206
  requestContext: new RequestContext(Object.entries(event.data.requestContext ?? {})),
967
1207
  resume,
@@ -971,11 +1211,19 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
971
1211
  // currentSpan: undefined, // TODO: Pass actual parent Span from workflow execution context
972
1212
  outputOptions,
973
1213
  outputWriter: async (chunk) => {
974
- void emitter.emit("watch", chunk).catch(() => {
975
- });
1214
+ try {
1215
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1216
+ type: "watch",
1217
+ runId,
1218
+ data: chunk
1219
+ });
1220
+ } catch (err) {
1221
+ this.logger.debug?.("Failed to publish watch event:", err);
1222
+ }
976
1223
  }
977
1224
  });
978
1225
  await step.run(`workflow.${this.id}.finalize`, async () => {
1226
+ await engine.invokeLifecycleCallbacksInternal(result);
979
1227
  if (result.status === "failed") {
980
1228
  throw new NonRetriableError(`Workflow failed`, {
981
1229
  cause: result
@@ -1060,7 +1308,8 @@ function createStep(params, agentOptions) {
1060
1308
  outputSchema,
1061
1309
  execute: async ({
1062
1310
  inputData,
1063
- [EMITTER_SYMBOL]: emitter,
1311
+ runId,
1312
+ [PUBSUB_SYMBOL]: pubsub,
1064
1313
  [STREAM_FORMAT_SYMBOL]: streamFormat,
1065
1314
  requestContext,
1066
1315
  tracingContext,
@@ -1115,22 +1364,24 @@ function createStep(params, agentOptions) {
1115
1364
  stream = modelOutput.fullStream;
1116
1365
  }
1117
1366
  if (streamFormat === "legacy") {
1118
- await emitter.emit("watch", {
1119
- type: "tool-call-streaming-start",
1120
- ...toolData ?? {}
1367
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1368
+ type: "watch",
1369
+ runId,
1370
+ data: { type: "tool-call-streaming-start", ...toolData ?? {} }
1121
1371
  });
1122
1372
  for await (const chunk of stream) {
1123
1373
  if (chunk.type === "text-delta") {
1124
- await emitter.emit("watch", {
1125
- type: "tool-call-delta",
1126
- ...toolData ?? {},
1127
- argsTextDelta: chunk.textDelta
1374
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1375
+ type: "watch",
1376
+ runId,
1377
+ data: { type: "tool-call-delta", ...toolData ?? {}, argsTextDelta: chunk.textDelta }
1128
1378
  });
1129
1379
  }
1130
1380
  }
1131
- await emitter.emit("watch", {
1132
- type: "tool-call-streaming-finish",
1133
- ...toolData ?? {}
1381
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1382
+ type: "watch",
1383
+ runId,
1384
+ data: { type: "tool-call-streaming-finish", ...toolData ?? {} }
1134
1385
  });
1135
1386
  } else {
1136
1387
  for await (const chunk of stream) {
@@ -1242,6 +1493,6 @@ function init(inngest) {
1242
1493
  };
1243
1494
  }
1244
1495
 
1245
- export { InngestExecutionEngine, InngestRun, InngestWorkflow, _compatibilityCheck, createStep, init, serve };
1496
+ export { InngestExecutionEngine, InngestPubSub, InngestRun, InngestWorkflow, _compatibilityCheck, createStep, init, serve };
1246
1497
  //# sourceMappingURL=index.js.map
1247
1498
  //# sourceMappingURL=index.js.map