Fix code step and logic function step in workflows (#17856)

- AI still often forgets to update the code step after creating it.
Adding a next step
- Starting by loading logic functions, so it avoids creating code steps
when a function exists
- Fix create complete workflow logic. Should not create code steps
directly
This commit is contained in:
Thomas Trompette
2026-02-11 16:08:23 +01:00
committed by GitHub
parent d0c1841f0f
commit 1e01f15182
8 changed files with 147 additions and 15 deletions
@@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
import { type ToolSet } from 'ai';
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { LogicFunctionService } from 'src/engine/metadata-modules/logic-function/services/logic-function.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config';
@@ -21,6 +22,7 @@ import { createDeactivateWorkflowVersionTool } from 'src/modules/workflow/workfl
import { createDeleteWorkflowVersionEdgeTool } from 'src/modules/workflow/workflow-tools/tools/delete-workflow-version-edge.tool';
import { createDeleteWorkflowVersionStepTool } from 'src/modules/workflow/workflow-tools/tools/delete-workflow-version-step.tool';
import { createGetWorkflowCurrentVersionTool } from 'src/modules/workflow/workflow-tools/tools/get-workflow-current-version.tool';
import { createListLogicFunctionToolsTool } from 'src/modules/workflow/workflow-tools/tools/list-logic-function-tools.tool';
import { createUpdateLogicFunctionSourceTool } from 'src/modules/workflow/workflow-tools/tools/update-logic-function-source.tool';
import { createUpdateWorkflowVersionPositionsTool } from 'src/modules/workflow/workflow-tools/tools/update-workflow-version-positions.tool';
import { createUpdateWorkflowVersionStepTool } from 'src/modules/workflow/workflow-tools/tools/update-workflow-version-step.tool';
@@ -42,6 +44,7 @@ export class WorkflowToolWorkspaceService {
globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
recordPositionService: RecordPositionService,
logicFunctionService: LogicFunctionService,
flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
) {
this.deps = {
workflowVersionStepService,
@@ -53,6 +56,7 @@ export class WorkflowToolWorkspaceService {
globalWorkspaceOrmManager,
recordPositionService,
logicFunctionService,
flatEntityMapsCacheService,
};
}
@@ -116,6 +120,10 @@ export class WorkflowToolWorkspaceService {
this.deps,
context,
);
const listLogicFunctionTools = createListLogicFunctionToolsTool(
this.deps,
context,
);
return {
[createCompleteWorkflow.name]: createCompleteWorkflow,
@@ -132,6 +140,7 @@ export class WorkflowToolWorkspaceService {
[computeStepOutputSchema.name]: computeStepOutputSchema,
[getWorkflowCurrentVersion.name]: getWorkflowCurrentVersion,
[updateLogicFunctionSource.name]: updateLogicFunctionSource,
[listLogicFunctionTools.name]: listLogicFunctionTools,
};
}
}
@@ -92,6 +92,7 @@ Common mistakes to avoid:
- Missing the "name" and "valid" fields in steps
- Missing the "objectRecord" field in CREATE_RECORD actions
- Using "fieldsToUpdate" instead of "objectRecord" in CREATE_RECORD actions
- Including CODE steps in this tool — this tool does NOT create the underlying logic function needed by CODE steps. Instead, create the workflow without CODE steps first, then add CODE steps individually using create_workflow_version_step (which properly creates the logic function), then call update_logic_function_source to define the code.
IMPORTANT: The tool schema provides comprehensive field descriptions, examples, and validation rules. Always refer to the schema for:
- Field requirements and data types
@@ -3,19 +3,17 @@ import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { z } from 'zod';
import type { CreateWorkflowVersionStepInput } from 'src/engine/core-modules/workflow/dtos/create-workflow-version-step-input.dto';
import { type WorkflowVersionStepChangesDTO } from 'src/engine/core-modules/workflow/dtos/workflow-version-step-changes.dto';
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-type.enum';
import {
type WorkflowToolContext,
type WorkflowToolDependencies,
} from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type';
const createWorkflowVersionStepSchema = z.object({
const baseStepFields = {
workflowVersionId: z
.string()
.describe('The ID of the workflow version to add the step to'),
stepType: z
.enum(Object.values(WorkflowActionType) as [string, ...string[]])
.describe('The type of step to create'),
parentStepId: z
.string()
.optional()
@@ -40,7 +38,58 @@ const createWorkflowVersionStepSchema = z.object({
})
.optional()
.describe('Optional position coordinates for the step'),
});
};
const nonLogicFunctionStepTypes = Object.values(WorkflowActionType).filter(
(type) => type !== WorkflowActionType.LOGIC_FUNCTION,
) as [string, ...string[]];
const createWorkflowVersionStepSchema = z.discriminatedUnion('stepType', [
z.object({
...baseStepFields,
stepType: z.literal(WorkflowActionType.LOGIC_FUNCTION),
defaultSettings: z
.object({
input: z.object({
logicFunctionId: z
.string()
.describe(
'The ID of the logic function. Use list_logic_function_tools to discover available IDs.',
),
}),
})
.describe(
'Settings for the LOGIC_FUNCTION step. Must include input.logicFunctionId.',
),
}),
z.object({
...baseStepFields,
stepType: z.enum(nonLogicFunctionStepTypes),
defaultSettings: z
.record(z.string(), z.unknown())
.optional()
.describe('Optional default settings for the step.'),
}),
]);
const enrichResultWithNextStep = ({
result,
stepType,
}: {
result: WorkflowVersionStepChangesDTO;
stepType: WorkflowActionType;
}) => {
switch (stepType) {
case WorkflowActionType.CODE:
return {
...result,
nextStep:
'This CODE step was created with a default placeholder function. You MUST now call update_logic_function_source with the logicFunctionId from this step to define the actual code.',
};
default:
return result;
}
};
export const createCreateWorkflowVersionStepTool = (
deps: Pick<
@@ -84,12 +133,18 @@ export const createCreateWorkflowVersionStepTool = (
}
}
return await deps.workflowVersionStepService.createWorkflowVersionStep({
workspaceId: context.workspaceId,
input: {
...parameters,
parentStepId: effectiveParentStepId,
},
const result =
await deps.workflowVersionStepService.createWorkflowVersionStep({
workspaceId: context.workspaceId,
input: {
...parameters,
parentStepId: effectiveParentStepId,
},
});
return enrichResultWithNextStep({
result,
stepType: parameters.stepType,
});
} catch (error) {
return {
@@ -0,0 +1,45 @@
import { isDefined } from 'twenty-shared/utils';
import { z } from 'zod';
import { type WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
import { type WorkflowToolContext } from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type';
const listLogicFunctionToolsSchema = z.object({});
export const createListLogicFunctionToolsTool = (
deps: {
flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService;
},
context: WorkflowToolContext,
) => ({
name: 'list_logic_function_tools' as const,
description:
'List all logic functions marked as tools that can be added as LOGIC_FUNCTION steps in workflows. Returns their IDs, names, and descriptions.',
inputSchema: listLogicFunctionToolsSchema,
execute: async () => {
const { flatLogicFunctionMaps } =
await deps.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId: context.workspaceId,
flatMapsKeys: ['flatLogicFunctionMaps'],
},
);
const toolFunctions = Object.values(
flatLogicFunctionMaps.byUniversalIdentifier,
).filter(
(fn): fn is FlatLogicFunction =>
isDefined(fn) && fn.isTool === true && fn.deletedAt === null,
);
return {
success: true,
logicFunctions: toolFunctions.map((fn) => ({
id: fn.id,
name: fn.name,
description: fn.description,
})),
};
},
});
@@ -1,4 +1,5 @@
import type { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
import type { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import type { LogicFunctionService } from 'src/engine/metadata-modules/logic-function/services/logic-function.service';
import type { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import type { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service';
@@ -18,6 +19,7 @@ export type WorkflowToolDependencies = {
globalWorkspaceOrmManager: GlobalWorkspaceOrmManager;
recordPositionService: RecordPositionService;
logicFunctionService: LogicFunctionService;
flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService;
};
export type WorkflowToolContext = {
@@ -2,6 +2,7 @@ import { Global, Module } from '@nestjs/common';
import { RecordPositionModule } from 'src/engine/core-modules/record-position/record-position.module';
import { WORKFLOW_TOOL_SERVICE_TOKEN } from 'src/engine/core-modules/tool-provider/constants/workflow-tool-service.token';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module';
import { WorkflowSchemaModule } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.module';
import { WorkflowVersionEdgeModule } from 'src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.module';
@@ -23,6 +24,7 @@ import { WorkflowToolWorkspaceService } from './services/workflow-tool.workspace
WorkflowSchemaModule,
RecordPositionModule,
LogicFunctionModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
],
providers: [
WorkflowToolWorkspaceService,