@mastra/inngest 1.0.0-beta.5 → 1.0.0-beta.8

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 {
@@ -265,20 +297,49 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
265
297
  payload: {}
266
298
  }
267
299
  };
300
+ } else if (result.status === "tripwire") {
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
+ }
312
+ }
313
+ });
314
+ return {
315
+ executionContext,
316
+ result: {
317
+ status: "tripwire",
318
+ tripwire: result?.tripwire
319
+ }
320
+ };
268
321
  }
269
- await emitter.emit("watch", {
270
- type: "workflow-step-result",
271
- payload: {
272
- id: step.id,
273
- status: "success",
274
- 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
+ }
275
332
  }
276
333
  });
277
- await emitter.emit("watch", {
278
- type: "workflow-step-finish",
279
- payload: {
280
- id: step.id,
281
- 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
+ }
282
343
  }
283
344
  });
284
345
  return { executionContext, result: { status: "success", output: result?.result } };
@@ -295,6 +356,118 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
295
356
  };
296
357
  }
297
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
+ };
298
471
  var InngestRun = class extends Run {
299
472
  inngest;
300
473
  serializedStepGraph;
@@ -306,27 +479,77 @@ var InngestRun = class extends Run {
306
479
  this.#mastra = params.mastra;
307
480
  }
308
481
  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}`
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
+ }
312
514
  }
313
- });
314
- const json = await response.json();
315
- return json.data;
515
+ }
516
+ throw new NonRetriableError(`Failed to get runs after ${maxRetries} attempts: ${lastError?.message}`);
316
517
  }
317
- async getRunOutput(eventId) {
318
- let runs = await this.getRuns(eventId);
518
+ async getRunOutput(eventId, maxWaitMs = 3e5) {
519
+ const startTime = Date.now();
319
520
  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);
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
+ }
323
536
  if (runs?.[0]?.status === "Failed") {
324
537
  const snapshot = await storage?.loadWorkflowSnapshot({
325
538
  workflowName: this.workflowId,
326
539
  runId: this.runId
327
540
  });
541
+ if (snapshot?.context) {
542
+ snapshot.context = hydrateSerializedStepErrors(snapshot.context);
543
+ }
328
544
  return {
329
- 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
+ }
330
553
  };
331
554
  }
332
555
  if (runs?.[0]?.status === "Cancelled") {
@@ -336,8 +559,9 @@ var InngestRun = class extends Run {
336
559
  });
337
560
  return { output: { result: { steps: snapshot?.context, status: "canceled" } } };
338
561
  }
562
+ await new Promise((resolve) => setTimeout(resolve, 1e3 + Math.random() * 1e3));
339
563
  }
340
- return runs?.[0];
564
+ throw new NonRetriableError(`Workflow did not complete within ${maxWaitMs}ms`);
341
565
  }
342
566
  async cancel() {
343
567
  const storage = this.#mastra?.getStorage();
@@ -367,6 +591,51 @@ var InngestRun = class extends Run {
367
591
  async start(params) {
368
592
  return this._start(params);
369
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
+ }
370
639
  async _start({
371
640
  inputData,
372
641
  initialState,
@@ -414,9 +683,7 @@ var InngestRun = class extends Run {
414
683
  }
415
684
  const runOutput = await this.getRunOutput(eventId);
416
685
  const result = runOutput?.output?.result;
417
- if (result.status === "failed") {
418
- result.error = new Error(result.error);
419
- }
686
+ this.hydrateFailedResult(result);
420
687
  if (result.status !== "suspended") {
421
688
  this.cleanup?.();
422
689
  }
@@ -475,9 +742,7 @@ var InngestRun = class extends Run {
475
742
  }
476
743
  const runOutput = await this.getRunOutput(eventId);
477
744
  const result = runOutput?.output?.result;
478
- if (result.status === "failed") {
479
- result.error = new Error(result.error);
480
- }
745
+ this.hydrateFailedResult(result);
481
746
  return result;
482
747
  }
483
748
  async timeTravel(params) {
@@ -567,9 +832,7 @@ var InngestRun = class extends Run {
567
832
  }
568
833
  const runOutput = await this.getRunOutput(eventId);
569
834
  const result = runOutput?.output?.result;
570
- if (result.status === "failed") {
571
- result.error = new Error(result.error);
572
- }
835
+ this.hydrateFailedResult(result);
573
836
  return result;
574
837
  }
575
838
  watch(cb) {
@@ -790,6 +1053,18 @@ var InngestRun = class extends Run {
790
1053
  });
791
1054
  return this.streamOutput;
792
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
+ }
793
1068
  };
794
1069
 
795
1070
  // src/workflow.ts
@@ -827,6 +1102,7 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
827
1102
  return run ?? (this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null);
828
1103
  }
829
1104
  __registerMastra(mastra) {
1105
+ super.__registerMastra(mastra);
830
1106
  this.#mastra = mastra;
831
1107
  this.executionEngine.__registerMastra(mastra);
832
1108
  const updateNested = (step) => {
@@ -913,28 +1189,7 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
913
1189
  return randomUUID();
914
1190
  });
915
1191
  }
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
- };
1192
+ const pubsub = new InngestPubSub(this.inngest, this.id, publish);
938
1193
  const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
939
1194
  const result = await engine.execute({
940
1195
  workflowId: this.id,
@@ -944,7 +1199,7 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
944
1199
  serializedStepGraph: this.serializedStepGraph,
945
1200
  input: inputData,
946
1201
  initialState,
947
- emitter,
1202
+ pubsub,
948
1203
  retryConfig: this.retryConfig,
949
1204
  requestContext: new RequestContext(Object.entries(event.data.requestContext ?? {})),
950
1205
  resume,
@@ -954,11 +1209,19 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
954
1209
  // currentSpan: undefined, // TODO: Pass actual parent Span from workflow execution context
955
1210
  outputOptions,
956
1211
  outputWriter: async (chunk) => {
957
- void emitter.emit("watch", chunk).catch(() => {
958
- });
1212
+ try {
1213
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1214
+ type: "watch",
1215
+ runId,
1216
+ data: chunk
1217
+ });
1218
+ } catch (err) {
1219
+ this.logger.debug?.("Failed to publish watch event:", err);
1220
+ }
959
1221
  }
960
1222
  });
961
1223
  await step.run(`workflow.${this.id}.finalize`, async () => {
1224
+ await engine.invokeLifecycleCallbacksInternal(result);
962
1225
  if (result.status === "failed") {
963
1226
  throw new NonRetriableError(`Workflow failed`, {
964
1227
  cause: result
@@ -1043,7 +1306,8 @@ function createStep(params, agentOptions) {
1043
1306
  outputSchema,
1044
1307
  execute: async ({
1045
1308
  inputData,
1046
- [EMITTER_SYMBOL]: emitter,
1309
+ runId,
1310
+ [PUBSUB_SYMBOL]: pubsub,
1047
1311
  [STREAM_FORMAT_SYMBOL]: streamFormat,
1048
1312
  requestContext,
1049
1313
  tracingContext,
@@ -1098,22 +1362,24 @@ function createStep(params, agentOptions) {
1098
1362
  stream = modelOutput.fullStream;
1099
1363
  }
1100
1364
  if (streamFormat === "legacy") {
1101
- await emitter.emit("watch", {
1102
- type: "tool-call-streaming-start",
1103
- ...toolData ?? {}
1365
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1366
+ type: "watch",
1367
+ runId,
1368
+ data: { type: "tool-call-streaming-start", ...toolData ?? {} }
1104
1369
  });
1105
1370
  for await (const chunk of stream) {
1106
1371
  if (chunk.type === "text-delta") {
1107
- await emitter.emit("watch", {
1108
- type: "tool-call-delta",
1109
- ...toolData ?? {},
1110
- argsTextDelta: chunk.textDelta
1372
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1373
+ type: "watch",
1374
+ runId,
1375
+ data: { type: "tool-call-delta", ...toolData ?? {}, argsTextDelta: chunk.textDelta }
1111
1376
  });
1112
1377
  }
1113
1378
  }
1114
- await emitter.emit("watch", {
1115
- type: "tool-call-streaming-finish",
1116
- ...toolData ?? {}
1379
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1380
+ type: "watch",
1381
+ runId,
1382
+ data: { type: "tool-call-streaming-finish", ...toolData ?? {} }
1117
1383
  });
1118
1384
  } else {
1119
1385
  for await (const chunk of stream) {
@@ -1225,6 +1491,6 @@ function init(inngest) {
1225
1491
  };
1226
1492
  }
1227
1493
 
1228
- export { InngestExecutionEngine, InngestRun, InngestWorkflow, _compatibilityCheck, createStep, init, serve };
1494
+ export { InngestExecutionEngine, InngestPubSub, InngestRun, InngestWorkflow, _compatibilityCheck, createStep, init, serve };
1229
1495
  //# sourceMappingURL=index.js.map
1230
1496
  //# sourceMappingURL=index.js.map