@outputai/core 0.7.1-next.bd6bd49.0 → 0.7.1-next.c005dac.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 (50) hide show
  1. package/bin/worker.sh +6 -0
  2. package/package.json +1 -1
  3. package/src/consts.js +0 -4
  4. package/src/errors.js +6 -2
  5. package/src/interface/evaluator.js +7 -20
  6. package/src/interface/evaluator.spec.js +117 -1
  7. package/src/interface/step.js +8 -9
  8. package/src/interface/step.spec.js +124 -0
  9. package/src/interface/validations/index.js +108 -0
  10. package/src/interface/validations/index.spec.js +182 -0
  11. package/src/interface/validations/schemas.js +113 -0
  12. package/src/interface/validations/schemas.spec.js +209 -0
  13. package/src/interface/webhook.js +1 -1
  14. package/src/interface/webhook.spec.js +1 -1
  15. package/src/interface/workflow.d.ts +10 -9
  16. package/src/interface/workflow.js +76 -164
  17. package/src/interface/workflow.spec.js +637 -521
  18. package/src/interface/workflow_activity_options.js +16 -0
  19. package/src/interface/workflow_utils.js +1 -1
  20. package/src/interface/zod_integration.spec.js +2 -2
  21. package/src/internal_utils/aggregations.js +0 -10
  22. package/src/internal_utils/aggregations.spec.js +1 -48
  23. package/src/internal_utils/errors.js +14 -8
  24. package/src/internal_utils/errors.spec.js +73 -27
  25. package/src/worker/bundle.js +26 -0
  26. package/src/worker/bundle.spec.js +52 -0
  27. package/src/worker/check.js +24 -0
  28. package/src/worker/index.js +1 -1
  29. package/src/worker/index.spec.js +1 -1
  30. package/src/worker/interceptors/activity.js +7 -24
  31. package/src/worker/interceptors/activity.spec.js +97 -66
  32. package/src/worker/interceptors/index.js +4 -7
  33. package/src/worker/interceptors/modules.js +15 -0
  34. package/src/worker/interceptors/workflow.js +4 -7
  35. package/src/worker/interceptors/workflow.spec.js +49 -42
  36. package/src/worker/loader_tools.js +1 -1
  37. package/src/worker/loader_tools.spec.js +36 -0
  38. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.js +5 -109
  39. package/src/worker/webpack_loaders/workflow_rewriter/collect_target_imports.spec.js +31 -103
  40. package/src/worker/webpack_loaders/workflow_rewriter/index.mjs +5 -6
  41. package/src/worker/webpack_loaders/workflow_rewriter/index.spec.js +11 -83
  42. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.js +8 -11
  43. package/src/worker/webpack_loaders/workflow_rewriter/rewrite_fn_bodies.spec.js +9 -9
  44. package/src/interface/validations/runtime.js +0 -20
  45. package/src/interface/validations/runtime.spec.js +0 -29
  46. package/src/interface/validations/schema_utils.js +0 -8
  47. package/src/interface/validations/schema_utils.spec.js +0 -67
  48. package/src/interface/validations/static.js +0 -137
  49. package/src/interface/validations/static.spec.js +0 -397
  50. package/src/interface/workflow.replay_compatibility.spec.js +0 -254
@@ -0,0 +1,16 @@
1
+ import { FatalError, ValidationError } from '#errors';
2
+
3
+ export const defaultOptions = {
4
+ activityOptions: {
5
+ startToCloseTimeout: '20m',
6
+ heartbeatTimeout: '5m',
7
+ retry: {
8
+ initialInterval: '10s',
9
+ backoffCoefficient: 2.0,
10
+ maximumInterval: '2m',
11
+ maximumAttempts: 3,
12
+ nonRetryableErrorTypes: [ ValidationError.name, FatalError.name ]
13
+ }
14
+ },
15
+ disableTrace: false
16
+ };
@@ -1,5 +1,5 @@
1
1
  // THIS RUNS IN THE TEMPORAL'S SANDBOX ENVIRONMENT
2
- import { validateExecuteInParallel } from './validations/static.js';
2
+ import { validateExecuteInParallel } from './validations/index.js';
3
3
 
4
4
  /**
5
5
  * Execute jobs in parallel with optional concurrency limit.
@@ -495,7 +495,7 @@ describe( 'Zod Schema Integration Tests', () => {
495
495
  await errorStep( { age: 16, email: 'invalid' } );
496
496
  expect.fail( 'Should have thrown an error' );
497
497
  } catch ( error ) {
498
- expect( error.message ).toContain( 'Step error_test input validation failed' );
498
+ expect( error.message ).toContain( 'Step "error_test" input validation failed' );
499
499
  }
500
500
  } );
501
501
 
@@ -527,7 +527,7 @@ describe( 'Zod Schema Integration Tests', () => {
527
527
  const metadata = testStep[METADATA_ACCESS_SYMBOL];
528
528
  expect( metadata.inputSchema ).toBe( zodSchema );
529
529
  expect( metadata.inputSchema ).not.toBe( null );
530
- expect( metadata.inputSchema._def ).toBeDefined(); // Zod-specific property
530
+ expect( metadata.inputSchema._zod?.def ).toBeDefined(); // Zod v4-specific property
531
531
  } );
532
532
 
533
533
  it( 'should handle deeply nested Zod schemas', async () => {
@@ -1,4 +1,3 @@
1
- import { deepMergeWithResolver } from '#utils';
2
1
  import { Attribute } from '#trace_attribute';
3
2
  import Decimal from 'decimal.js';
4
3
 
@@ -43,12 +42,3 @@ export const aggregateAttributes = attributes => ( {
43
42
  total: attributes.filter( a => Attribute.HTTPRequestCount.TYPE === a.type ).length
44
43
  }
45
44
  } );
46
-
47
- /**
48
- * Combine two or more aggregation objects into a single Aggregation, adding up the totals and merging keys.
49
- *
50
- * @param {Aggregation} aggregations
51
- * @returns {Aggregation}
52
- */
53
- export const mergeAggregations = ( ...aggregations ) =>
54
- aggregations.reduce( ( final, item ) => deepMergeWithResolver( final, item, ( a, b ) => Decimal( a ?? 0 ).add( b ?? 0 ).toNumber() ), {} );
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import { Attribute } from '#trace_attribute';
3
- import { aggregateAttributes, mergeAggregations } from './aggregations.js';
3
+ import { aggregateAttributes } from './aggregations.js';
4
4
 
5
5
  describe( 'aggregateAttributes', () => {
6
6
  it( 'returns zeroed aggregations when there are no attributes', () => {
@@ -90,50 +90,3 @@ describe( 'aggregateAttributes', () => {
90
90
  } );
91
91
  } );
92
92
 
93
- describe( 'mergeAggregations', () => {
94
- it( 'returns an empty object when no aggregations are provided', () => {
95
- expect( mergeAggregations() ).toEqual( {} );
96
- } );
97
-
98
- it( 'sums nested aggregation values', () => {
99
- expect( mergeAggregations(
100
- {
101
- cost: { total: 1.2 },
102
- tokens: { total: 10, input: 6 },
103
- httpRequests: { total: 2 }
104
- },
105
- {
106
- cost: { total: 0.3 },
107
- tokens: { total: 5, input: 2, output: 3 },
108
- httpRequests: { total: 1 }
109
- }
110
- ) ).toEqual( {
111
- cost: { total: 1.5 },
112
- tokens: { total: 15, input: 8, output: 3 },
113
- httpRequests: { total: 3 }
114
- } );
115
- } );
116
-
117
- it( 'handles undefined or partial aggregation objects', () => {
118
- expect( mergeAggregations(
119
- undefined,
120
- { cost: { total: 2 } },
121
- { tokens: { total: 4, reasoning: 1 } },
122
- { httpRequests: { total: 3 } }
123
- ) ).toEqual( {
124
- cost: { total: 2 },
125
- tokens: { total: 4, reasoning: 1 },
126
- httpRequests: { total: 3 }
127
- } );
128
- } );
129
-
130
- it( 'does not mutate source aggregation objects', () => {
131
- const first = { cost: { total: 1 }, tokens: { total: 2 }, httpRequests: { total: 3 } };
132
- const second = { cost: { total: 4 }, tokens: { total: 5 }, httpRequests: { total: 6 } };
133
-
134
- mergeAggregations( first, second );
135
-
136
- expect( first ).toEqual( { cost: { total: 1 }, tokens: { total: 2 }, httpRequests: { total: 3 } } );
137
- expect( second ).toEqual( { cost: { total: 4 }, tokens: { total: 5 }, httpRequests: { total: 6 } } );
138
- } );
139
- } );
@@ -1,10 +1,16 @@
1
+ import { ApplicationFailure } from '@temporalio/common';
2
+
1
3
  /**
2
- * Extract a property from the error .details.
3
- * If error does not have details, navigate up the .cause chain.
4
- *
5
- * @param {Error} e
6
- * @param {string} key
7
- * @returns {any} The value of the property
4
+ * Builds a Temporal ApplicationFailure based on an error attaching info to its details
5
+ * @param {Error} error
6
+ * @param {unknown} info
7
+ * @returns {ApplicationFailure}
8
8
  */
9
- export const extractErrorDetail = ( e, key ) =>
10
- e ? ( e.details?.find?.( d => d[key] )?.[key] ?? extractErrorDetail( e.cause, key ) ) : null;
9
+ export const buildApplicationFailureWithDetails = ( error, info ) =>
10
+ ApplicationFailure.create( {
11
+ message: error.message,
12
+ type: error.type ?? error.constructor?.name ?? error.name,
13
+ nonRetryable: error.nonRetryable,
14
+ details: ( Array.isArray( error.details ) ? error.details : [] ).concat( info ),
15
+ cause: error
16
+ } );
@@ -1,43 +1,89 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { extractErrorDetail } from './errors.js';
2
+ import { ApplicationFailure } from '@temporalio/common';
3
+ import { buildApplicationFailureWithDetails } from './errors.js';
3
4
 
4
- describe( 'extractErrorDetail', () => {
5
- it( 'returns a matching value from error details', () => {
6
- const error = new Error( 'failed' );
7
- error.details = [
8
- { requestId: 'req-1' },
9
- { workflowId: 'workflow-1' }
10
- ];
5
+ class CustomFailure extends Error {}
11
6
 
12
- expect( extractErrorDetail( error, 'workflowId' ) ).toBe( 'workflow-1' );
7
+ describe( 'buildApplicationFailureWithDetails', () => {
8
+ it( 'wraps a regular error in an ApplicationFailure with appended details', () => {
9
+ const error = new Error( 'step failed' );
10
+ const info = { aggregations: { cost: { total: 1 } } };
11
+
12
+ const failure = buildApplicationFailureWithDetails( error, info );
13
+
14
+ expect( failure ).toBeInstanceOf( ApplicationFailure );
15
+ expect( failure ).toMatchObject( {
16
+ message: 'step failed',
17
+ type: 'Error',
18
+ nonRetryable: false,
19
+ details: [ info ],
20
+ cause: error
21
+ } );
13
22
  } );
14
23
 
15
- it( 'walks the cause chain until it finds matching details', () => {
16
- const root = new Error( 'root' );
17
- root.details = [ { traceId: 'trace-1' } ];
18
- const wrapped = new Error( 'wrapped', { cause: root } );
24
+ it( 'uses the original constructor name for custom errors', () => {
25
+ const error = new CustomFailure( 'custom failed' );
26
+ const info = { trace: { destinations: { local: '/tmp/trace' } } };
19
27
 
20
- expect( extractErrorDetail( wrapped, 'traceId' ) ).toBe( 'trace-1' );
28
+ const failure = buildApplicationFailureWithDetails( error, info );
29
+
30
+ expect( failure ).toMatchObject( {
31
+ message: 'custom failed',
32
+ type: 'CustomFailure',
33
+ details: [ info ],
34
+ cause: error
35
+ } );
21
36
  } );
22
37
 
23
- it( 'prefers details from the current error over causes', () => {
24
- const root = new Error( 'root' );
25
- root.details = [ { traceId: 'root-trace' } ];
26
- const wrapped = new Error( 'wrapped', { cause: root } );
27
- wrapped.details = [ { traceId: 'wrapped-trace' } ];
38
+ it( 'preserves existing details and appends new info without mutating the original error', () => {
39
+ const existingDetails = [ { domain: { reason: 'bad-input' } } ];
40
+ const error = new Error( 'step failed' );
41
+ error.details = existingDetails;
42
+ const info = { aggregations: { httpRequests: { total: 1 } } };
43
+
44
+ const failure = buildApplicationFailureWithDetails( error, info );
28
45
 
29
- expect( extractErrorDetail( wrapped, 'traceId' ) ).toBe( 'wrapped-trace' );
46
+ expect( failure.details ).toEqual( [
47
+ { domain: { reason: 'bad-input' } },
48
+ info
49
+ ] );
50
+ expect( error.details ).toBe( existingDetails );
51
+ expect( error.details ).toEqual( [ { domain: { reason: 'bad-input' } } ] );
30
52
  } );
31
53
 
32
- it( 'returns null when the key is not found', () => {
33
- const root = new Error( 'root' );
34
- root.details = [ { traceId: 'trace-1' } ];
35
- const wrapped = new Error( 'wrapped', { cause: root } );
54
+ it( 'ignores non-array details on the original error', () => {
55
+ const error = new Error( 'step failed' );
56
+ error.details = { domain: { reason: 'bad-input' } };
57
+ const info = { aggregations: { tokens: { total: 3 } } };
36
58
 
37
- expect( extractErrorDetail( wrapped, 'missing' ) ).toBeNull();
59
+ const failure = buildApplicationFailureWithDetails( error, info );
60
+
61
+ expect( failure.details ).toEqual( [ info ] );
38
62
  } );
39
63
 
40
- it( 'returns null for empty errors', () => {
41
- expect( extractErrorDetail( null, 'traceId' ) ).toBeNull();
64
+ it( 'preserves ApplicationFailure type, nonRetryable flag, and details while avoiding self-cause', () => {
65
+ const original = ApplicationFailure.create( {
66
+ message: 'application failed',
67
+ type: 'DomainFailure',
68
+ nonRetryable: true,
69
+ details: [ { domain: { reason: 'bad-input' } } ]
70
+ } );
71
+ const info = { aggregations: { cost: { total: 2 } } };
72
+
73
+ const failure = buildApplicationFailureWithDetails( original, info );
74
+
75
+ expect( failure ).toBeInstanceOf( ApplicationFailure );
76
+ expect( failure ).not.toBe( original );
77
+ expect( failure.cause ).toBe( original );
78
+ expect( failure.cause ).not.toBe( failure );
79
+ expect( failure ).toMatchObject( {
80
+ message: 'application failed',
81
+ type: 'DomainFailure',
82
+ nonRetryable: true,
83
+ details: [
84
+ { domain: { reason: 'bad-input' } },
85
+ info
86
+ ]
87
+ } );
42
88
  } );
43
89
  } );
@@ -0,0 +1,26 @@
1
+ import { bundleWorkflowCode } from '@temporalio/worker';
2
+ import { loadWorkflows, loadActivities, createWorkflowsEntryPoint } from './loader.js';
3
+ import { webpackConfigHook } from './bundler_options.js';
4
+ import { workflowInterceptorModules } from './interceptors/modules.js';
5
+
6
+ /**
7
+ * Bundle a project's workflows exactly as the worker does, without a Temporal server.
8
+ *
9
+ * Mirrors the worker's startup prep (`loadWorkflows` -> `loadActivities` ->
10
+ * `createWorkflowsEntryPoint`) and then runs the same bundler (`bundleWorkflowCode`)
11
+ * with the same inputs `Worker.create` derives — `webpackConfigHook` and
12
+ * `workflowInterceptorModules` — so it stays in parity with worker startup. Rejects if
13
+ * the Temporal webpack bundler fails, e.g. a `node:` built-in in the workflow's
14
+ * transitive import graph.
15
+ *
16
+ * @param {string} rootDir directory to discover workflows in
17
+ * @returns {Promise<import('@temporalio/worker').WorkflowBundleWithSourceMap>}
18
+ */
19
+ export async function bundleWorkflows( rootDir ) {
20
+ const workflows = await loadWorkflows( rootDir );
21
+ // Writes worker/temp/__activity_options.js, which the workflow interceptor module
22
+ // imports — the worker generates it via loadActivities() before Worker.create bundles.
23
+ await loadActivities( rootDir, workflows );
24
+ const workflowsPath = createWorkflowsEntryPoint( workflows );
25
+ return bundleWorkflowCode( { workflowsPath, workflowInterceptorModules, webpackConfigHook } );
26
+ }
@@ -0,0 +1,52 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+
3
+ // Stub the activity interceptor so the real interceptors/index.js imports cleanly.
4
+ vi.mock( './interceptors/activity.js', () => ( { ActivityExecutionInterceptor: class {} } ) );
5
+
6
+ vi.mock( '@temporalio/worker', () => ( {
7
+ bundleWorkflowCode: vi.fn().mockResolvedValue( { code: '', sourceMap: '' } )
8
+ } ) );
9
+
10
+ vi.mock( './loader.js', () => ( {
11
+ loadWorkflows: vi.fn().mockResolvedValue( [] ),
12
+ loadActivities: vi.fn().mockResolvedValue( {} ),
13
+ createWorkflowsEntryPoint: vi.fn().mockReturnValue( '/fake/workflows/entrypoint.js' )
14
+ } ) );
15
+
16
+ vi.mock( './bundler_options.js', () => ( { webpackConfigHook: vi.fn() } ) );
17
+
18
+ import { bundleWorkflowCode } from '@temporalio/worker';
19
+ import { loadWorkflows, loadActivities, createWorkflowsEntryPoint } from './loader.js';
20
+ import { webpackConfigHook } from './bundler_options.js';
21
+ import { initInterceptors } from './interceptors/index.js';
22
+ import { workflowInterceptorModules } from './interceptors/modules.js';
23
+ import { bundleWorkflows } from './bundle.js';
24
+
25
+ describe( 'output-worker --check parity', () => {
26
+ beforeEach( () => {
27
+ vi.clearAllMocks();
28
+ loadWorkflows.mockResolvedValue( [] );
29
+ loadActivities.mockResolvedValue( {} );
30
+ createWorkflowsEntryPoint.mockReturnValue( '/fake/workflows/entrypoint.js' );
31
+ bundleWorkflowCode.mockResolvedValue( { code: '', sourceMap: '' } );
32
+ } );
33
+
34
+ it( 'worker registers the shared workflow interceptor modules', () => {
35
+ const { workflowModules } = initInterceptors( { activities: {}, workflows: [], connection: {} } );
36
+ // The check (bundleWorkflows) and the worker must register the very same modules.
37
+ expect( workflowModules ).toBe( workflowInterceptorModules );
38
+ } );
39
+
40
+ it( 'check bundles with the same inputs Worker.create derives', async () => {
41
+ await bundleWorkflows( '/project' );
42
+
43
+ expect( loadWorkflows ).toHaveBeenCalledWith( '/project' );
44
+ expect( loadActivities ).toHaveBeenCalledWith( '/project', [] );
45
+ expect( createWorkflowsEntryPoint ).toHaveBeenCalledWith( [] );
46
+ expect( bundleWorkflowCode ).toHaveBeenCalledWith( {
47
+ workflowsPath: '/fake/workflows/entrypoint.js',
48
+ workflowInterceptorModules,
49
+ webpackConfigHook
50
+ } );
51
+ } );
52
+ } );
@@ -0,0 +1,24 @@
1
+ import { bundleWorkflows } from './bundle.js';
2
+
3
+ // `output-worker --check` entry: bundle the workflows exactly as the worker would, then
4
+ // exit 0 (ok) / 1 (fail). The exit code is the signal. Workflow-discovery logs write to
5
+ // stdout via the worker logger, so mute stdout to keep output clean for CI/tooling; only
6
+ // real errors surface, on stderr. Catches bad workflow imports (e.g. `node:` built-ins)
7
+ // at build/CI time instead of crash-looping the worker at startup.
8
+ const callerDir = process.argv[2] ?? process.cwd();
9
+
10
+ process.stdout.write = ( ...args ) => {
11
+ const callback = args.find( arg => typeof arg === 'function' );
12
+ if ( callback ) {
13
+ callback();
14
+ }
15
+ return true;
16
+ };
17
+
18
+ ( async () => {
19
+ await bundleWorkflows( callerDir );
20
+ process.exit( 0 );
21
+ } )().catch( error => {
22
+ console.error( error );
23
+ process.exit( 1 );
24
+ } );
@@ -74,7 +74,7 @@ const callerDir = process.argv[2];
74
74
  workflowsPath,
75
75
  activities,
76
76
  sinks,
77
- interceptors: initInterceptors( { activities, workflows, connection } ),
77
+ interceptors: initInterceptors( { activities, workflows } ),
78
78
  maxConcurrentWorkflowTaskExecutions,
79
79
  maxConcurrentActivityTaskExecutions,
80
80
  maxCachedWorkflows,
@@ -130,7 +130,7 @@ describe( 'worker/index', () => {
130
130
  maxConcurrentActivityTaskPolls: configValues.maxConcurrentActivityTaskPolls,
131
131
  maxConcurrentWorkflowTaskPolls: configValues.maxConcurrentWorkflowTaskPolls
132
132
  } ) );
133
- expect( initInterceptorsMock ).toHaveBeenCalledWith( { activities: {}, workflows: [], connection: mockConnection } );
133
+ expect( initInterceptorsMock ).toHaveBeenCalledWith( { activities: {}, workflows: [] } );
134
134
  expect( registerShutdownMock ).toHaveBeenCalledWith( { worker: mockWorker, log: mockLog } );
135
135
  expect( setupTelemetryMock ).toHaveBeenCalledWith( { worker: mockWorker } );
136
136
  expect( startCatalogMock ).toHaveBeenCalledWith( {
@@ -2,14 +2,11 @@ import { Context, activityInfo as activityInfoFn } from '@temporalio/activity';
2
2
  import { Storage } from '#async_storage';
3
3
  import * as Tracing from '#tracing';
4
4
  import { headersToObject } from './headers.js';
5
- import { ACTIVITY_WRAPPER_VERSION_FIELD, BusEventType, METADATA_ACCESS_SYMBOL, Signal } from '#consts';
6
- import { activityHeartbeatEnabled, activityHeartbeatIntervalMs, namespace } from '../configs.js';
5
+ import { ACTIVITY_WRAPPER_VERSION_FIELD, BusEventType, METADATA_ACCESS_SYMBOL } from '#consts';
6
+ import { activityHeartbeatEnabled, activityHeartbeatIntervalMs } from '../configs.js';
7
7
  import { messageBus } from '#bus';
8
- import { Client } from '@temporalio/client';
9
- import { createChildLogger } from '#logger';
10
8
  import { aggregateAttributes } from '#internal_utils/aggregations';
11
-
12
- const log = createChildLogger( 'ActivityInterceptor' );
9
+ import { buildApplicationFailureWithDetails } from '#internal_utils/errors';
13
10
 
14
11
  /*
15
12
  This interceptor wraps every activity execution with cross-cutting concerns:
@@ -28,7 +25,7 @@ const log = createChildLogger( 'ActivityInterceptor' );
28
25
  - Headers injected by the workflow interceptor
29
26
  */
30
27
  export class ActivityExecutionInterceptor {
31
- constructor( { activities, workflows, connection } ) {
28
+ constructor( { activities, workflows } ) {
32
29
  // convert activities{} object to a map: activityType:kind
33
30
  this.activityKindMap = new Map( Object.entries( activities )
34
31
  .map( ( [ type, fn ] ) => ( [ type, fn[METADATA_ACCESS_SYMBOL].type ] ) ) );
@@ -37,12 +34,11 @@ export class ActivityExecutionInterceptor {
37
34
  this.workflowsPathMap = new Map( workflows.flatMap( ( { name, aliases, path } ) =>
38
35
  [ name, ...aliases ?? [] ].map( a => ( [ a, path ] ) )
39
36
  ) );
40
- this.connection = connection;
41
37
  };
42
38
 
43
39
  async execute( input, next ) {
44
40
  const activityInfo = activityInfoFn();
45
- const { workflowExecution: { workflowId, runId }, activityId, activityType, workflowType } = activityInfo;
41
+ const { workflowExecution: { runId }, activityId, activityType, workflowType } = activityInfo;
46
42
  const { traceInfo, workflowDetails } = headersToObject( input.headers );
47
43
  const outputActivityKind = this.activityKindMap.get( activityType );
48
44
  const workflowFilename = this.workflowsPathMap.get( workflowType );
@@ -61,19 +57,6 @@ export class ActivityExecutionInterceptor {
61
57
 
62
58
  const addAttribute = attribute => state.attributes.push( attribute );
63
59
 
64
- const sendAggregationsViaSignal = async () => {
65
- if ( state.attributes.length > 0 ) {
66
- try {
67
- const client = new Client( { connection: this.connection, namespace } );
68
- const workflowHandle = client.workflow.getHandle( workflowId );
69
- await workflowHandle.signal( Signal.SEND_AGGREGATIONS, aggregateAttributes( state.attributes ) );
70
- } catch ( error ) {
71
- const errorContext = { message: error.message, stack: error.stack, activityId, activityType, workflowId, workflowType, runId };
72
- log.warn( `Signal "${Signal.SEND_AGGREGATIONS}" failed`, errorContext );
73
- }
74
- }
75
- };
76
-
77
60
  // Adds context accessible information
78
61
  const storageContext = {
79
62
  parentId: activityId,
@@ -107,9 +90,9 @@ export class ActivityExecutionInterceptor {
107
90
  messageBus.emit( BusEventType.ACTIVITY_ERROR, { activityInfo, workflowDetails, outputActivityKind, error } );
108
91
  Tracing.addEventError( { id: activityId, details: error, traceInfo } );
109
92
 
110
- await sendAggregationsViaSignal();
93
+ const aggregations = state.attributes.length > 0 ? aggregateAttributes( state.attributes ) : null;
111
94
 
112
- throw error;
95
+ throw aggregations ? buildApplicationFailureWithDetails( error, { aggregations } ) : error;
113
96
  } finally {
114
97
  clearInterval( state.heartbeat );
115
98
  }