diff --git a/packages/workflow-executor/src/adapters/server-types.ts b/packages/workflow-executor/src/adapters/server-types.ts index e897f3012e..10382882fd 100644 --- a/packages/workflow-executor/src/adapters/server-types.ts +++ b/packages/workflow-executor/src/adapters/server-types.ts @@ -39,8 +39,9 @@ export enum ServerStepExecutionTypeEnum { interface ServerWorkflowStepBase { type: ServerStepTypeEnum; - title: string; - prompt?: string; + // The orchestrator serializes missing BPMN attributes as null (DOM getAttribute), never omits. + title: string | null; + prompt?: string | null; executionType: ServerStepExecutionTypeEnum; automaticCompletion: boolean; outgoing: ServerWorkflowTransition[]; @@ -50,7 +51,7 @@ export interface ServerWorkflowTaskBase extends ServerWorkflowStepBase { type: ServerStepTypeEnum.Task; taskType: ServerTaskTypeEnum; isSubTask?: boolean; - prompt: string; + prompt: string | null; outgoing: [ServerWorkflowTransition]; } diff --git a/packages/workflow-executor/src/types/validated/step-definition.ts b/packages/workflow-executor/src/types/validated/step-definition.ts index a074778b43..159b5df947 100644 --- a/packages/workflow-executor/src/types/validated/step-definition.ts +++ b/packages/workflow-executor/src/types/validated/step-definition.ts @@ -23,10 +23,17 @@ export enum StepExecutionMode { // Shared fields across all step types. executionType is intentionally excluded — // each schema declares its own valid modes (most with .default().catch() for normalization; // guidance deliberately omits .catch to fail loud on an unknown mode). +// The orchestrator serializes missing BPMN attributes as JSON null (DOM getAttribute), not as +// absent keys — accept both and normalize to undefined. +const optionalString = z + .string() + .nullish() + .transform(value => value ?? undefined); + const sharedFields = { - prompt: z.string().optional(), - aiConfigName: z.string().optional(), - title: z.string().optional(), + prompt: optionalString, + aiConfigName: optionalString, + title: optionalString, }; // Use z.enum(EnumObject), not z.nativeEnum — the latter is deprecated in zod 4. 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 e64cc8073d..d8d11d68b5 100644 --- a/packages/workflow-executor/test/adapters/step-definition-mapper.test.ts +++ b/packages/workflow-executor/test/adapters/step-definition-mapper.test.ts @@ -309,6 +309,38 @@ describe('toStepDefinition', () => { options: ['Valid', 'AlsoValid'], }); }); + + it('accepts a condition whose title and prompt are null (unnamed BPMN gateway)', () => { + const condition = makeCondition( + [ + { stepId: 's1', buttonText: 'Option A' }, + { stepId: 's2', buttonText: 'Option B' }, + ], + { title: null, prompt: null }, + ); + + expect(toStepDefinition(condition)).toMatchObject({ + type: 'condition', + title: undefined, + prompt: undefined, + options: ['Option A', 'Option B'], + }); + }); + }); + + describe('null shared fields', () => { + it('accepts a task whose title and prompt are null (serialized missing BPMN attributes)', () => { + const task = makeTask({ + title: null, + prompt: null, + }); + + expect(toStepDefinition(task)).toMatchObject({ + type: 'read-record', + title: undefined, + prompt: undefined, + }); + }); }); describe('unsupported step types', () => {