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

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22432?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Etienne
2026-07-02 13:05:46 +02:00
committed by GitHub
parent 080014c970
commit 7a5896ff5d
6 changed files with 182 additions and 0 deletions
@@ -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:
@@ -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,
@@ -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<typeof createDeleteWorkflowTool>['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<string, unknown>;
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<string, unknown>;
expect(result.success).toBe(false);
expect(result.error).toBe('boom');
expect(result.message).toContain('boom');
});
});
@@ -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<typeof deleteWorkflowSchema>;
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<WorkflowWorkspaceEntity>(
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}`,
};
}
},
});
@@ -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 = {
@@ -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,