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:
+9
@@ -24,6 +24,7 @@ You help users create and manage automation workflows.
|
|||||||
|
|
||||||
- Create workflows from scratch
|
- Create workflows from scratch
|
||||||
- Modify existing workflows (add, remove, update steps)
|
- 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
|
- Explain workflow structure and suggest improvements
|
||||||
- Troubleshoot workflow runs (inspect status, failed steps, and execution logs)
|
- 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.
|
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
|
## Troubleshooting Workflow Runs
|
||||||
|
|
||||||
When a user reports a failing or misbehaving workflow, diagnose it with two read-only tools:
|
When a user reports a failing or misbehaving workflow, diagnose it with two read-only tools:
|
||||||
|
|||||||
+9
@@ -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 { 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 { 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 { 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 { 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 { 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';
|
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 { 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 { 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 { 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 { 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 { 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 { createGetWorkflowCurrentVersionTool } from 'src/modules/workflow/workflow-tools/tools/get-workflow-current-version.tool';
|
||||||
@@ -54,6 +56,7 @@ export class WorkflowToolWorkspaceService {
|
|||||||
logicFunctionFromSourceService: LogicFunctionFromSourceService,
|
logicFunctionFromSourceService: LogicFunctionFromSourceService,
|
||||||
flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||||
agentService: AgentService,
|
agentService: AgentService,
|
||||||
|
workflowCommonService: WorkflowCommonWorkspaceService,
|
||||||
) {
|
) {
|
||||||
this.deps = {
|
this.deps = {
|
||||||
workflowVersionStepService,
|
workflowVersionStepService,
|
||||||
@@ -68,6 +71,7 @@ export class WorkflowToolWorkspaceService {
|
|||||||
logicFunctionFromSourceService,
|
logicFunctionFromSourceService,
|
||||||
flatEntityMapsCacheService,
|
flatEntityMapsCacheService,
|
||||||
agentService,
|
agentService,
|
||||||
|
workflowCommonService,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,6 +143,10 @@ export class WorkflowToolWorkspaceService {
|
|||||||
this.deps,
|
this.deps,
|
||||||
contextWithPermissions,
|
contextWithPermissions,
|
||||||
);
|
);
|
||||||
|
const deleteWorkflow = createDeleteWorkflowTool(
|
||||||
|
this.deps,
|
||||||
|
contextWithPermissions,
|
||||||
|
);
|
||||||
const updateLogicFunctionSource = createUpdateLogicFunctionSourceTool(
|
const updateLogicFunctionSource = createUpdateLogicFunctionSourceTool(
|
||||||
this.deps,
|
this.deps,
|
||||||
context,
|
context,
|
||||||
@@ -165,6 +173,7 @@ export class WorkflowToolWorkspaceService {
|
|||||||
[computeStepOutputSchema.name]: computeStepOutputSchema,
|
[computeStepOutputSchema.name]: computeStepOutputSchema,
|
||||||
[getWorkflowCurrentVersion.name]: getWorkflowCurrentVersion,
|
[getWorkflowCurrentVersion.name]: getWorkflowCurrentVersion,
|
||||||
[listWorkflows.name]: listWorkflows,
|
[listWorkflows.name]: listWorkflows,
|
||||||
|
[deleteWorkflow.name]: deleteWorkflow,
|
||||||
[getWorkflowRun.name]: getWorkflowRun,
|
[getWorkflowRun.name]: getWorkflowRun,
|
||||||
[listWorkflowRuns.name]: listWorkflowRuns,
|
[listWorkflowRuns.name]: listWorkflowRuns,
|
||||||
[updateLogicFunctionSource.name]: updateLogicFunctionSource,
|
[updateLogicFunctionSource.name]: updateLogicFunctionSource,
|
||||||
|
|||||||
+75
@@ -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');
|
||||||
|
});
|
||||||
|
});
|
||||||
+85
@@ -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}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
+2
@@ -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 { 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 { 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 { 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 { 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 { 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';
|
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;
|
logicFunctionFromSourceService: LogicFunctionFromSourceService;
|
||||||
flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService;
|
flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService;
|
||||||
agentService: AgentService;
|
agentService: AgentService;
|
||||||
|
workflowCommonService: WorkflowCommonWorkspaceService;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkflowToolContext = {
|
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 { 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 { 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 { 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 { 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 { 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';
|
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,
|
WorkflowVersionEdgeModule,
|
||||||
WorkflowVersionModule,
|
WorkflowVersionModule,
|
||||||
WorkflowTriggerModule,
|
WorkflowTriggerModule,
|
||||||
|
WorkflowCommonModule,
|
||||||
WorkflowSchemaModule,
|
WorkflowSchemaModule,
|
||||||
WorkflowValidationModule,
|
WorkflowValidationModule,
|
||||||
RecordPositionModule,
|
RecordPositionModule,
|
||||||
|
|||||||
Reference in New Issue
Block a user