diff --git a/packages/workflow-executor/src/executors/base-step-executor.ts b/packages/workflow-executor/src/executors/base-step-executor.ts index 59ba338f2..4c7bc5481 100644 --- a/packages/workflow-executor/src/executors/base-step-executor.ts +++ b/packages/workflow-executor/src/executors/base-step-executor.ts @@ -35,8 +35,27 @@ export default abstract class BaseStepExecutor { + const { runId, stepId, stepIndex, stepDefinition, baseRecordRef } = this.context; + + this.context.logger.info('Step execution started', { + runId, + stepId, + stepIndex, + stepType: stepDefinition.type, + collection: baseRecordRef.collectionName, + }); + try { - return await this.doExecute(); + const result = await this.doExecute(); + + this.context.logger.info('Step execution completed', { + runId, + stepId, + stepIndex, + status: result.stepOutcome.status, + }); + + return result; } catch (error) { if (error instanceof WorkflowExecutorError) { if (error.cause !== undefined) { diff --git a/packages/workflow-executor/src/ports/logger-port.ts b/packages/workflow-executor/src/ports/logger-port.ts index ed2acd393..e615eb7e4 100644 --- a/packages/workflow-executor/src/ports/logger-port.ts +++ b/packages/workflow-executor/src/ports/logger-port.ts @@ -1,4 +1,4 @@ export interface Logger { error(message: string, context: Record): void; - info?(message: string, context: Record): void; + info(message: string, context: Record): void; } diff --git a/packages/workflow-executor/test/executors/base-step-executor.test.ts b/packages/workflow-executor/test/executors/base-step-executor.test.ts index 45a4582c4..5825d13da 100644 --- a/packages/workflow-executor/test/executors/base-step-executor.test.ts +++ b/packages/workflow-executor/test/executors/base-step-executor.test.ts @@ -87,7 +87,7 @@ function makeMockRunStore(stepExecutions: StepExecutionData[] = []): RunStore { } function makeMockLogger(): Logger { - return { error: jest.fn() }; + return { info: jest.fn(), error: jest.fn() }; } function makeContext(overrides: Partial = {}): ExecutionContext { diff --git a/packages/workflow-executor/test/executors/condition-step-executor.test.ts b/packages/workflow-executor/test/executors/condition-step-executor.test.ts index a8ecbabd6..3a9e13405 100644 --- a/packages/workflow-executor/test/executors/condition-step-executor.test.ts +++ b/packages/workflow-executor/test/executors/condition-step-executor.test.ts @@ -69,7 +69,7 @@ function makeContext( }, schemaCache: new SchemaCache(), previousSteps: [], - logger: { error: jest.fn() }, + logger: { info: jest.fn(), error: jest.fn() }, ...overrides, }; } diff --git a/packages/workflow-executor/test/executors/load-related-record-step-executor.test.ts b/packages/workflow-executor/test/executors/load-related-record-step-executor.test.ts index 7d2f7ec46..e42757ca3 100644 --- a/packages/workflow-executor/test/executors/load-related-record-step-executor.test.ts +++ b/packages/workflow-executor/test/executors/load-related-record-step-executor.test.ts @@ -140,7 +140,7 @@ function makeContext( }, schemaCache: new SchemaCache(), previousSteps: [], - logger: { error: jest.fn() }, + logger: { info: jest.fn(), error: jest.fn() }, ...overrides, }; } @@ -1295,7 +1295,7 @@ describe('LoadRelatedRecordStepExecutor', () => { }); it('returns user message and logs cause when agentPort.getRelatedData throws an infra error', async () => { - const logger = { error: jest.fn() }; + const logger = { info: jest.fn(), error: jest.fn() }; const agentPort = makeMockAgentPort(); (agentPort.getRelatedData as jest.Mock).mockRejectedValue(new Error('DB connection lost')); const mockModel = makeMockModel({ relationName: 'Order', reasoning: 'test' }); diff --git a/packages/workflow-executor/test/executors/mcp-task-step-executor.test.ts b/packages/workflow-executor/test/executors/mcp-task-step-executor.test.ts index f23b45bd4..ff97f87a6 100644 --- a/packages/workflow-executor/test/executors/mcp-task-step-executor.test.ts +++ b/packages/workflow-executor/test/executors/mcp-task-step-executor.test.ts @@ -107,7 +107,7 @@ function makeContext( }, schemaCache: new SchemaCache(), previousSteps: [], - logger: { error: jest.fn() }, + logger: { info: jest.fn(), error: jest.fn() }, ...overrides, }; } @@ -218,7 +218,7 @@ describe('McpTaskStepExecutor', () => { tool_calls: [{ name: 'send_notification', args: { message: 'Hi' }, id: 'call_1' }], }) .mockResolvedValueOnce({ tool_calls: [] }); - const logger = { error: jest.fn() }; + const logger = { info: jest.fn(), error: jest.fn() }; const runStore = makeMockRunStore(); const context = makeContext({ model, @@ -299,7 +299,7 @@ describe('McpTaskStepExecutor', () => { it('returns error when saveStepExecution fails (Branch C)', async () => { const { model } = makeMockModel('send_notification', { message: 'Hello' }); - const logger = { error: jest.fn() }; + const logger = { info: jest.fn(), error: jest.fn() }; const runStore = makeMockRunStore({ saveStepExecution: jest.fn().mockRejectedValue(new Error('DB unavailable')), }); @@ -491,7 +491,7 @@ describe('McpTaskStepExecutor', () => { invoke: invokeFn, }); const { model } = makeMockModel('send_notification', { message: 'Hello' }); - const logger = { error: jest.fn() }; + const logger = { info: jest.fn(), error: jest.fn() }; const runStore = makeMockRunStore({ saveStepExecution: jest.fn().mockRejectedValue(new Error('Disk full')), }); @@ -529,7 +529,7 @@ describe('McpTaskStepExecutor', () => { userConfirmed: true, }, }; - const logger = { error: jest.fn() }; + const logger = { info: jest.fn(), error: jest.fn() }; const runStore = makeMockRunStore({ getStepExecutions: jest.fn().mockResolvedValue([execution]), saveStepExecution: jest.fn().mockRejectedValue(new Error('Disk full')), @@ -638,7 +638,7 @@ describe('McpTaskStepExecutor', () => { invoke: invokeFn, }); const { model } = makeMockModel('send_notification', {}); - const logger = { error: jest.fn() }; + const logger = { info: jest.fn(), error: jest.fn() }; const context = makeContext({ model, stepDefinition: makeStep({ automaticExecution: true }), diff --git a/packages/workflow-executor/test/executors/read-record-step-executor.test.ts b/packages/workflow-executor/test/executors/read-record-step-executor.test.ts index 2578b856d..218784679 100644 --- a/packages/workflow-executor/test/executors/read-record-step-executor.test.ts +++ b/packages/workflow-executor/test/executors/read-record-step-executor.test.ts @@ -129,7 +129,7 @@ function makeContext( }, schemaCache: new SchemaCache(), previousSteps: [], - logger: { error: jest.fn() }, + logger: { info: jest.fn(), error: jest.fn() }, ...overrides, }; } @@ -668,7 +668,7 @@ describe('ReadRecordStepExecutor', () => { }); it('returns user message and logs cause when agentPort.getRecord throws an infra error', async () => { - const logger = { error: jest.fn() }; + const logger = { info: jest.fn(), error: jest.fn() }; const agentPort = makeMockAgentPort(); (agentPort.getRecord as jest.Mock).mockRejectedValue(new Error('DB connection lost')); const mockModel = makeMockModel({ fieldNames: ['email'] }); diff --git a/packages/workflow-executor/test/executors/trigger-record-action-step-executor.test.ts b/packages/workflow-executor/test/executors/trigger-record-action-step-executor.test.ts index 39ee40a14..15b775dc1 100644 --- a/packages/workflow-executor/test/executors/trigger-record-action-step-executor.test.ts +++ b/packages/workflow-executor/test/executors/trigger-record-action-step-executor.test.ts @@ -128,7 +128,7 @@ function makeContext( }, schemaCache: new SchemaCache(), previousSteps: [], - logger: { error: jest.fn() }, + logger: { info: jest.fn(), error: jest.fn() }, ...overrides, }; } @@ -518,7 +518,7 @@ describe('TriggerRecordActionStepExecutor', () => { }); it('returns user message and logs cause when agentPort.executeAction throws an infra error', async () => { - const logger = { error: jest.fn() }; + const logger = { info: jest.fn(), error: jest.fn() }; const agentPort = makeMockAgentPort(); (agentPort.executeAction as jest.Mock).mockRejectedValue(new Error('DB connection lost')); const mockModel = makeMockModel({ diff --git a/packages/workflow-executor/test/executors/update-record-step-executor.test.ts b/packages/workflow-executor/test/executors/update-record-step-executor.test.ts index 14377287e..5cc90e67e 100644 --- a/packages/workflow-executor/test/executors/update-record-step-executor.test.ts +++ b/packages/workflow-executor/test/executors/update-record-step-executor.test.ts @@ -130,7 +130,7 @@ function makeContext( }, schemaCache: new SchemaCache(), previousSteps: [], - logger: { error: jest.fn() }, + logger: { info: jest.fn(), error: jest.fn() }, ...overrides, }; } @@ -681,7 +681,7 @@ describe('UpdateRecordStepExecutor', () => { }); it('returns user message and logs cause when agentPort.updateRecord throws an infra error', async () => { - const logger = { error: jest.fn() }; + const logger = { info: jest.fn(), error: jest.fn() }; const agentPort = makeMockAgentPort(); (agentPort.updateRecord as jest.Mock).mockRejectedValue(new Error('DB connection lost')); const mockModel = makeMockModel({ diff --git a/packages/workflow-executor/test/http/executor-http-server.test.ts b/packages/workflow-executor/test/http/executor-http-server.test.ts index 6fb5067c0..9981b96be 100644 --- a/packages/workflow-executor/test/http/executor-http-server.test.ts +++ b/packages/workflow-executor/test/http/executor-http-server.test.ts @@ -45,7 +45,7 @@ function createServer( overrides: { runner?: Runner; workflowPort?: WorkflowPort; - logger?: { error: jest.Mock }; + logger?: { info: jest.Mock; error: jest.Mock }; } = {}, ) { return new ExecutorHttpServer({ @@ -252,7 +252,7 @@ describe('ExecutorHttpServer', () => { }); it('returns 503 when hasRunAccess throws', async () => { - const logger = { error: jest.fn() }; + const logger = { info: jest.fn(), error: jest.fn() }; const workflowPort = createMockWorkflowPort({ hasRunAccess: jest.fn().mockRejectedValue(new Error('orchestrator down')), }); diff --git a/packages/workflow-executor/test/runner.test.ts b/packages/workflow-executor/test/runner.test.ts index fadb7c6ef..9adf128e0 100644 --- a/packages/workflow-executor/test/runner.test.ts +++ b/packages/workflow-executor/test/runner.test.ts @@ -58,7 +58,7 @@ function createMockAiClient() { } function createMockLogger(): jest.Mocked> { - return { error: jest.fn(), info: jest.fn() }; + return { info: jest.fn(), error: jest.fn() }; } const VALID_ENV_SECRET = 'a'.repeat(64); @@ -708,7 +708,7 @@ describe('StepExecutorFactory.create — factory', () => { workflowPort: {} as WorkflowPort, runStore: {} as RunStore, schemaCache: new SchemaCache(), - logger: { error: jest.fn() }, + logger: { info: jest.fn(), error: jest.fn() }, }); it('dispatches Condition steps to ConditionStepExecutor', async () => { @@ -774,7 +774,7 @@ describe('StepExecutorFactory.create — factory', () => { const rootCause = new Error('root cause'); const error = new Error('wrapper'); (error as Error & { cause: Error }).cause = rootCause; - const logger = { error: jest.fn() }; + const logger = { info: jest.fn(), error: jest.fn() }; const contextConfig: StepContextConfig = { ...makeContextConfig(), aiClient: { @@ -796,7 +796,7 @@ describe('StepExecutorFactory.create — factory', () => { it('logs cause as undefined when construction error cause is not an Error instance', async () => { const error = new Error('wrapper'); (error as Error & { cause: string }).cause = 'plain string'; - const logger = { error: jest.fn() }; + const logger = { info: jest.fn(), error: jest.fn() }; const contextConfig: StepContextConfig = { ...makeContextConfig(), aiClient: { diff --git a/packages/workflow-executor/test/stores/database-store.test.ts b/packages/workflow-executor/test/stores/database-store.test.ts index 4f60ea60e..cab80c9ba 100644 --- a/packages/workflow-executor/test/stores/database-store.test.ts +++ b/packages/workflow-executor/test/stores/database-store.test.ts @@ -118,7 +118,7 @@ describe('DatabaseStore (SQLite)', () => { .spyOn(badSequelize.getQueryInterface(), 'createTable') .mockRejectedValueOnce(new Error('disk full')); - const logger = { error: jest.fn() }; + const logger = { info: jest.fn(), error: jest.fn() }; await expect(badStore.init(logger)).rejects.toThrow('disk full'); expect(logger.error).toHaveBeenCalledWith( 'Database migration failed', @@ -129,7 +129,7 @@ describe('DatabaseStore (SQLite)', () => { }); it('close() catches and logs errors instead of throwing', async () => { - const logger = { error: jest.fn() }; + const logger = { info: jest.fn(), error: jest.fn() }; jest.spyOn(sequelize, 'close').mockRejectedValueOnce(new Error('close failed')); await expect(store.close(logger)).resolves.toBeUndefined();