From 7a5896ff5d02d829654797e79a180bde434a8fe5 Mon Sep 17 00:00:00 2001 From: Etienne <45695613+etiennejouan@users.noreply.github.com> Date: Thu, 2 Jul 2026 13:05:46 +0200 Subject: [PATCH] feat(ai) - add delete_workflow tool (#22432) ## Summary - Add a new `delete_workflow` agent tool that soft-deletes a workflow and cleans up its sub-entities (versions, runs, triggers) via `WorkflowCommonWorkspaceService.handleWorkflowSubEntities` - Update the workflow skill system prompt to document the new capability and instruct the agent to always confirm with the user before deleting - Wire `WorkflowCommonModule` / `WorkflowCommonWorkspaceService` into the workflow-tools dependency graph ## Test plan - [x] Unit tests added (`delete-workflow.tool.spec.ts`) covering successful deletion and error handling - [ ] Verify the agent can resolve a workflow by name via `list_workflows` then delete it with `delete_workflow` - [ ] Confirm the agent asks for user confirmation before executing the deletion - [ ] Confirm sub-entities (versions, runs, triggers) are removed after deletion Review in cubic --- ...reate-standard-flat-skill-metadata.util.ts | 9 ++ .../workflow-tool.workspace-service.ts | 9 ++ .../__tests__/delete-workflow.tool.spec.ts | 75 ++++++++++++++++ .../tools/delete-workflow.tool.ts | 85 +++++++++++++++++++ .../types/workflow-tool-dependencies.type.ts | 2 + .../workflow-tools/workflow-tools.module.ts | 2 + 6 files changed, 182 insertions(+) create mode 100644 packages/twenty-server/src/modules/workflow/workflow-tools/tools/__tests__/delete-workflow.tool.spec.ts create mode 100644 packages/twenty-server/src/modules/workflow/workflow-tools/tools/delete-workflow.tool.ts 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 dcbb25a38d..0e41318466 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 @@ -24,6 +24,7 @@ You help users create and manage automation workflows. - Create workflows from scratch - Modify existing workflows (add, remove, update steps) +- Delete workflows entirely (with their versions, runs and triggers) - IMPORTANT : Always confirm with the user before deleting - Explain workflow structure and suggest improvements - Troubleshoot workflow runs (inspect status, failed steps, and execution logs) @@ -72,6 +73,14 @@ LOGIC_FUNCTION steps execute logic functions provided by installed applications. To discover existing workflows in the workspace, use \`list_workflows\`. Use this before modifying a workflow when the user refers to it by name rather than id — resolve the \`id\` here first, then call \`get_workflow_current_version\` with it. +## Deleting Workflows + +To delete a workflow entirely, use \`delete_workflow\` with its \`workflowId\`. This also removes the workflow's versions, runs and automated triggers, and deactivates any active version — it is a destructive, irreversible operation. + +- If the user refers to the workflow by name, resolve its \`workflowId\` with \`list_workflows\` first. +- IMPORTANT : Always confirm with the user before deleting, and make sure you are deleting the correct workflow. +- To simply stop a workflow from running without removing it, prefer \`deactivate_workflow_version\` instead of deleting. + ## Troubleshooting Workflow Runs When a user reports a failing or misbehaving workflow, diagnose it with two read-only tools: 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 8e38cec735..365d3f43fd 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 @@ -8,6 +8,7 @@ import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadat import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.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'; +import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service'; import { WorkflowValidationWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-validation/workflow-validation.workspace-service'; import { WorkflowVersionEdgeWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service'; @@ -21,6 +22,7 @@ import { createCreateDraftFromWorkflowVersionTool } from 'src/modules/workflow/w import { createCreateWorkflowVersionEdgeTool } from 'src/modules/workflow/workflow-tools/tools/create-workflow-version-edge.tool'; import { createCreateWorkflowVersionStepTool } from 'src/modules/workflow/workflow-tools/tools/create-workflow-version-step.tool'; import { createDeactivateWorkflowVersionTool } from 'src/modules/workflow/workflow-tools/tools/deactivate-workflow-version.tool'; +import { createDeleteWorkflowTool } from 'src/modules/workflow/workflow-tools/tools/delete-workflow.tool'; 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'; @@ -54,6 +56,7 @@ export class WorkflowToolWorkspaceService { logicFunctionFromSourceService: LogicFunctionFromSourceService, flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, agentService: AgentService, + workflowCommonService: WorkflowCommonWorkspaceService, ) { this.deps = { workflowVersionStepService, @@ -68,6 +71,7 @@ export class WorkflowToolWorkspaceService { logicFunctionFromSourceService, flatEntityMapsCacheService, agentService, + workflowCommonService, }; } @@ -139,6 +143,10 @@ export class WorkflowToolWorkspaceService { this.deps, contextWithPermissions, ); + const deleteWorkflow = createDeleteWorkflowTool( + this.deps, + contextWithPermissions, + ); const updateLogicFunctionSource = createUpdateLogicFunctionSourceTool( this.deps, context, @@ -165,6 +173,7 @@ export class WorkflowToolWorkspaceService { [computeStepOutputSchema.name]: computeStepOutputSchema, [getWorkflowCurrentVersion.name]: getWorkflowCurrentVersion, [listWorkflows.name]: listWorkflows, + [deleteWorkflow.name]: deleteWorkflow, [getWorkflowRun.name]: getWorkflowRun, [listWorkflowRuns.name]: listWorkflowRuns, [updateLogicFunctionSource.name]: updateLogicFunctionSource, diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/__tests__/delete-workflow.tool.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/__tests__/delete-workflow.tool.spec.ts new file mode 100644 index 0000000000..09bae1daa5 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/__tests__/delete-workflow.tool.spec.ts @@ -0,0 +1,75 @@ +import { createDeleteWorkflowTool } from 'src/modules/workflow/workflow-tools/tools/delete-workflow.tool'; + +const WORKFLOW_ID = 'b3b8a4f0-0000-4000-8000-000000000000'; + +const buildTool = () => { + const workflowRepository = { + softDelete: jest.fn().mockResolvedValue({ affected: 1 }), + }; + const globalWorkspaceOrmManager = { + executeInWorkspaceContext: jest.fn((callback: () => unknown) => callback()), + getRepository: jest.fn().mockResolvedValue(workflowRepository), + }; + const workflowCommonService = { + handleWorkflowSubEntities: jest.fn().mockResolvedValue(undefined), + }; + + const tool = createDeleteWorkflowTool( + { + globalWorkspaceOrmManager, + workflowCommonService, + } as never, + { + workspaceId: 'workspace-id', + rolePermissionConfig: { shouldBypassPermissionChecks: true }, + } as never, + ); + + return { + tool, + workflowRepository, + globalWorkspaceOrmManager, + workflowCommonService, + }; +}; + +const baseInput = { + workflowId: WORKFLOW_ID, +} as unknown as Parameters< + ReturnType['execute'] +>[0]; + +describe('createDeleteWorkflowTool', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should soft delete the workflow and clean up sub-entities', async () => { + const { tool, workflowRepository, workflowCommonService } = buildTool(); + + const result = (await tool.execute(baseInput)) as Record; + + expect(workflowRepository.softDelete).toHaveBeenCalledWith(WORKFLOW_ID); + expect( + workflowCommonService.handleWorkflowSubEntities, + ).toHaveBeenCalledWith({ + workflowIds: [WORKFLOW_ID], + workspaceId: 'workspace-id', + operation: 'delete', + }); + expect(result.success).toBe(true); + expect(result.workflowId).toBe(WORKFLOW_ID); + }); + + it('should return a failure result when deletion throws', async () => { + const { tool, workflowRepository } = buildTool(); + + workflowRepository.softDelete.mockRejectedValue(new Error('boom')); + + const result = (await tool.execute(baseInput)) as Record; + + expect(result.success).toBe(false); + expect(result.error).toBe('boom'); + expect(result.message).toContain('boom'); + }); +}); diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/delete-workflow.tool.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/delete-workflow.tool.ts new file mode 100644 index 0000000000..192ad5e471 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/delete-workflow.tool.ts @@ -0,0 +1,85 @@ +import { z } from 'zod'; + +import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config'; +import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; +import { type WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity'; +import { + type WorkflowToolContext, + type WorkflowToolDependencies, +} from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type'; +import { isDefined } from 'twenty-shared/utils'; + +type DeleteWorkflowToolContext = WorkflowToolContext & { + rolePermissionConfig: RolePermissionConfig; +}; + +const deleteWorkflowSchema = z.object({ + workflowId: z.string().uuid().describe('The UUID of the workflow to delete'), +}); + +type DeleteWorkflowInput = z.infer; + +export const createDeleteWorkflowTool = ( + deps: Pick< + WorkflowToolDependencies, + 'globalWorkspaceOrmManager' | 'workflowCommonService' + >, + context: DeleteWorkflowToolContext, +) => ({ + name: 'delete_workflow' as const, + description: + 'Delete a workflow by its ID. This also removes its versions, runs and automated triggers, and deactivates any active version. Use list_workflows to find the workflowId.', + inputSchema: deleteWorkflowSchema, + execute: async (parameters: DeleteWorkflowInput) => { + try { + const { workflowId } = parameters; + const { workspaceId } = context; + + const authContext = buildSystemAuthContext(workspaceId); + + const deleteResult = + await deps.globalWorkspaceOrmManager.executeInWorkspaceContext( + async () => { + const workflowRepository = + await deps.globalWorkspaceOrmManager.getRepository( + workspaceId, + 'workflow', + context.rolePermissionConfig, + ); + + return workflowRepository.softDelete(workflowId); + }, + authContext, + ); + + if (!isDefined(deleteResult.affected)) { + return { + success: false, + error: 'Workflow not found', + message: `No workflow found with ID ${workflowId}`, + }; + } + + await deps.workflowCommonService.handleWorkflowSubEntities({ + workflowIds: [workflowId], + workspaceId, + operation: 'delete', + }); + + return { + success: true, + message: `Successfully deleted workflow ${workflowId}`, + workflowId, + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + + return { + success: false, + error: errorMessage, + message: `Failed to delete workflow: ${errorMessage}`, + }; + } + }, +}); 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 67c75eeac7..c27aeb3f65 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 @@ -3,6 +3,7 @@ import type { AgentService } from 'src/engine/metadata-modules/ai/ai-agent/agent import type { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service'; import type { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; import type { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; +import type { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import type { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service'; import type { WorkflowValidationWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-validation/workflow-validation.workspace-service'; import type { WorkflowVersionEdgeWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service'; @@ -24,6 +25,7 @@ export type WorkflowToolDependencies = { logicFunctionFromSourceService: LogicFunctionFromSourceService; flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService; agentService: AgentService; + workflowCommonService: WorkflowCommonWorkspaceService; }; 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 95fcf83fef..8efc77212d 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 @@ -5,6 +5,7 @@ import { WORKFLOW_TOOL_SERVICE_TOKEN } from 'src/engine/core-modules/tool-provid import { AiAgentModule } from 'src/engine/metadata-modules/ai/ai-agent/ai-agent.module'; 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 { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module'; import { WorkflowSchemaModule } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.module'; import { WorkflowValidationModule } from 'src/modules/workflow/workflow-builder/workflow-validation/workflow-validation.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 WorkflowVersionEdgeModule, WorkflowVersionModule, WorkflowTriggerModule, + WorkflowCommonModule, WorkflowSchemaModule, WorkflowValidationModule, RecordPositionModule,