diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/create-standard-flat-skill-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/create-standard-flat-skill-metadata.util.ts index 01ea3133b2..49036b2765 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/create-standard-flat-skill-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/create-standard-flat-skill-metadata.util.ts @@ -29,7 +29,7 @@ You help users create and manage automation workflows. ## Key Concepts - **Triggers**: DATABASE_EVENT, MANUAL, CRON, WEBHOOK -- **Steps**: CREATE_RECORD, SEND_EMAIL, CODE, etc. +- **Steps**: CREATE_RECORD, SEND_EMAIL, CODE, LOGIC_FUNCTION, etc. - **Data flow**: Use {{stepId.fieldName}} to reference previous step outputs - **Relationships**: Use nested objects like {"company": {"id": "{{reference}}"}} @@ -56,7 +56,16 @@ For CRON triggers, settings.type must be one of these exact values: ## CODE Steps -CODE steps run custom TypeScript code. Load the \`update_logic_function_source\` tool to modify the source code. +Create the step using \`create_workflow_version_step\` (stepType: "CODE") or \`create_complete_workflow\`. This returns a step with a \`logicFunctionId\` in settings.input — the step starts with a default function, not the user's desired code. + +## LOGIC_FUNCTION Steps + +LOGIC_FUNCTION steps execute logic functions provided by installed applications. To add one: + +1. Call \`list_logic_function_tools\` to discover available logic function tools with their IDs. +2. Use \`create_workflow_version_step\` with stepType "LOGIC_FUNCTION" and pass the logicFunctionId in defaultSettings: + { "stepType": "LOGIC_FUNCTION", "workflowVersionId": "", "defaultSettings": { "input": { "logicFunctionId": "" } } } +3. Or when using \`create_complete_workflow\`, include a step with type "LOGIC_FUNCTION" and settings.input.logicFunctionId. ## Critical Notes @@ -68,6 +77,7 @@ Always rely on tool schema definitions: ## Approach - Ask clarifying questions to understand user needs +- List logic function tools. Present relevant ones to the user as options before defaulting to CODE steps. - Suggest appropriate actions for the use case - Explain each step and why it's needed - For modifications, understand current structure first diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts index ba7f0ba612..a8eb5ff307 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts @@ -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, }; } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/create-complete-workflow.tool.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/create-complete-workflow.tool.ts index ea04b86533..4fcd3d5ef0 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/create-complete-workflow.tool.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/create-complete-workflow.tool.ts @@ -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 diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/create-workflow-version-step.tool.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/create-workflow-version-step.tool.ts index 8a338e93f0..9de720f7c3 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/create-workflow-version-step.tool.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/create-workflow-version-step.tool.ts @@ -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 { diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/list-logic-function-tools.tool.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/list-logic-function-tools.tool.ts new file mode 100644 index 0000000000..834c2cb3a2 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/list-logic-function-tools.tool.ts @@ -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, + })), + }; + }, +}); diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type.ts index ca65be3d06..33f48bd634 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type.ts @@ -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 = { diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/workflow-tools.module.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/workflow-tools.module.ts index 176b349fc8..ea0a1f4cc3 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/workflow-tools.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/workflow-tools.module.ts @@ -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, diff --git a/packages/twenty-shared/src/workflow/schemas/code-action-settings-schema.ts b/packages/twenty-shared/src/workflow/schemas/code-action-settings-schema.ts index e6b9b728b3..ffe00c5126 100644 --- a/packages/twenty-shared/src/workflow/schemas/code-action-settings-schema.ts +++ b/packages/twenty-shared/src/workflow/schemas/code-action-settings-schema.ts @@ -4,7 +4,15 @@ import { baseWorkflowActionSettingsSchema } from './base-workflow-action-setting export const workflowCodeActionSettingsSchema = baseWorkflowActionSettingsSchema.extend({ input: z.object({ - logicFunctionId: z.string(), - logicFunctionInput: z.record(z.string(), z.any()), + logicFunctionId: z + .string() + .describe( + 'The ID of the logic function that holds the code. This is auto-generated when a CODE step is created via create_workflow_version_step — do NOT set this manually.', + ), + logicFunctionInput: z + .record(z.string(), z.any()) + .describe( + 'Key-value map of input parameters to pass to the logic function at runtime.', + ), }), });