diff --git a/packages/workflow-executor/src/adapters/step-definition-mapper.ts b/packages/workflow-executor/src/adapters/step-definition-mapper.ts index f1723ce26d..f343a390f2 100644 --- a/packages/workflow-executor/src/adapters/step-definition-mapper.ts +++ b/packages/workflow-executor/src/adapters/step-definition-mapper.ts @@ -21,7 +21,7 @@ import { function mapTask(task: ServerWorkflowTask): StepDefinition { // executionType is passed through as-is; each schema's .default().catch() handles // missing or unsupported values without requiring an explicit mapping here. - const base = { prompt: task.prompt, executionType: task.executionType }; + const base = { prompt: task.prompt, executionType: task.executionType, title: task.title }; switch (task.taskType) { case ServerTaskTypeEnum.McpServer: @@ -65,6 +65,7 @@ function mapCondition(condition: ServerWorkflowCondition): ConditionStepDefiniti type: StepType.Condition, prompt: condition.prompt, executionType: condition.executionType, + title: condition.title, options, }); } diff --git a/packages/workflow-executor/src/executors/base-step-executor.ts b/packages/workflow-executor/src/executors/base-step-executor.ts index be790f1035..b48db45905 100644 --- a/packages/workflow-executor/src/executors/base-step-executor.ts +++ b/packages/workflow-executor/src/executors/base-step-executor.ts @@ -224,16 +224,22 @@ export default abstract class BaseStepExecutor { diff --git a/packages/workflow-executor/src/types/validated/step-definition.ts b/packages/workflow-executor/src/types/validated/step-definition.ts index 2292a2914c..c6e7dcf290 100644 --- a/packages/workflow-executor/src/types/validated/step-definition.ts +++ b/packages/workflow-executor/src/types/validated/step-definition.ts @@ -25,6 +25,7 @@ export enum StepExecutionMode { const sharedFields = { prompt: z.string().optional(), aiConfigName: z.string().optional(), + title: z.string().optional(), }; // Use z.enum(EnumObject), not z.nativeEnum — the latter is deprecated in zod 4. diff --git a/packages/workflow-executor/test/adapters/run-to-available-step-mapper.test.ts b/packages/workflow-executor/test/adapters/run-to-available-step-mapper.test.ts index dacf007274..eda00c5af3 100644 --- a/packages/workflow-executor/test/adapters/run-to-available-step-mapper.test.ts +++ b/packages/workflow-executor/test/adapters/run-to-available-step-mapper.test.ts @@ -118,6 +118,7 @@ describe('toAvailableStepExecution', () => { stepDefinition: { type: StepType.ReadRecord, prompt: 'prompt', + title: 'Task', executionType: ServerStepExecutionTypeEnum.FullyAutomated, }, previousSteps: [], @@ -189,6 +190,7 @@ describe('toAvailableStepExecution', () => { expect(result?.stepDefinition).toEqual({ type: StepType.Guidance, prompt: 'follow the guide', + title: 'guidance', executionType: ServerStepExecutionTypeEnum.Manual, }); }); diff --git a/packages/workflow-executor/test/adapters/step-definition-mapper.test.ts b/packages/workflow-executor/test/adapters/step-definition-mapper.test.ts index 594a8b8d07..ae5541702b 100644 --- a/packages/workflow-executor/test/adapters/step-definition-mapper.test.ts +++ b/packages/workflow-executor/test/adapters/step-definition-mapper.test.ts @@ -60,6 +60,7 @@ describe('toStepDefinition', () => { expect(toStepDefinition(task)).toEqual({ type: StepType.ReadRecord, prompt: 'read it', + title: 'Test task', executionType: ServerStepExecutionTypeEnum.FullyAutomated, }); }); @@ -70,6 +71,7 @@ describe('toStepDefinition', () => { expect(toStepDefinition(task)).toEqual({ type: StepType.UpdateRecord, prompt: 'update it', + title: 'Test task', executionType: ServerStepExecutionTypeEnum.AutomatedWithConfirmation, }); }); @@ -80,6 +82,7 @@ describe('toStepDefinition', () => { expect(toStepDefinition(task)).toEqual({ type: StepType.TriggerAction, prompt: 'trigger it', + title: 'Test task', executionType: ServerStepExecutionTypeEnum.AutomatedWithConfirmation, }); }); @@ -93,6 +96,7 @@ describe('toStepDefinition', () => { expect(toStepDefinition(task)).toEqual({ type: StepType.LoadRelatedRecord, prompt: 'load it', + title: 'Test task', executionType: ServerStepExecutionTypeEnum.AutomatedWithConfirmation, }); }); @@ -108,6 +112,7 @@ describe('toStepDefinition', () => { type: StepType.Mcp, prompt: 'run mcp', mcpServerId: 'mcp-abc', + title: 'Test task', executionType: ServerStepExecutionTypeEnum.AutomatedWithConfirmation, }); }); @@ -128,6 +133,7 @@ describe('toStepDefinition', () => { expect(toStepDefinition(task)).toEqual({ type: StepType.Guidance, prompt: 'guide them', + title: 'Test task', executionType: StepExecutionMode.Manual, }); }); @@ -196,6 +202,7 @@ describe('toStepDefinition', () => { expect(toStepDefinition(condition)).toEqual({ type: StepType.Condition, prompt: 'Choose one', + title: 'Test condition', options: ['Yes', 'No'], executionType: StepExecutionMode.FullyAutomated, }); @@ -210,6 +217,7 @@ describe('toStepDefinition', () => { expect(toStepDefinition(condition)).toEqual({ type: StepType.Condition, prompt: 'Choose one', + title: 'Test condition', options: ['Approve', 'Reject'], executionType: StepExecutionMode.FullyAutomated, }); 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 1e2b3c66ae..b7508a3b65 100644 --- a/packages/workflow-executor/test/executors/base-step-executor.test.ts +++ b/packages/workflow-executor/test/executors/base-step-executor.test.ts @@ -61,6 +61,10 @@ class TestableExecutor extends BaseStepExecutor { return super.buildPreviousStepsMessages(); } + override buildContextMessage(): SystemMessage { + return super.buildContextMessage(); + } + override invokeWithTool>( messages: BaseMessage[], tool: DynamicStructuredTool, @@ -175,6 +179,53 @@ function makeContext( } describe('BaseStepExecutor', () => { + describe('buildContextMessage', () => { + it('falls back to the step title when there is no prompt', () => { + const context = makeContext({ + stepDefinition: { + type: StepType.Condition, + executionType: StepExecutionMode.Manual, + options: ['A', 'B'], + title: 'Load the store', + }, + }); + + const message = new TestableExecutor(context).buildContextMessage(); + + expect(message.content as string).toContain('Step title: "Load the store"'); + }); + + it('omits the title when a prompt is present (the prompt is authoritative)', () => { + const context = makeContext({ + stepDefinition: { + type: StepType.Condition, + executionType: StepExecutionMode.Manual, + options: ['A', 'B'], + prompt: 'Pick one', + title: 'Load the store', + }, + }); + + const message = new TestableExecutor(context).buildContextMessage(); + + expect(message.content as string).not.toContain('Step title'); + }); + + it('omits the title line when the step has neither prompt nor title', () => { + const context = makeContext({ + stepDefinition: { + type: StepType.Condition, + executionType: StepExecutionMode.Manual, + options: ['A', 'B'], + }, + }); + + const message = new TestableExecutor(context).buildContextMessage(); + + expect(message.content as string).not.toContain('Step title'); + }); + }); + describe('buildPreviousStepsMessages', () => { it('returns empty array for empty history', async () => { const executor = new TestableExecutor(makeContext());