feat(workflow) - Add validation layer (#21422)

Add workflow validation framework and consolidate output schema
types/search logic into twenty-shared

This PR introduces a comprehensive workflow validation system that
catches configuration errors at build-time, and consolidates the
fragmented output-schema type definitions and variable-search logic from
the front-end into twenty-shared

**Workflow validation** — A new system that checks workflows for errors
before activation: graph connectivity (unreachable steps, dangling
references), step parameter schemas (via Zod), variable references
(typos, wrong step order), and workspace metadata (non-existent
objects). Returns structured errors/warnings with "did you mean?"
suggestions. Runs automatically after create_complete_workflow and
update_workflow_version_step, and is also available as a standalone
validate_workflow tool.

**Output schema consolidation** — Moves all output schema types and the
variable-search logic from scattered front-end files into twenty-shared,
replacing ~800 lines of duplicated per-schema-type code with a single
unified searchVariableInOutputSchema dispatcher.


To do : 
- validation on CODE and AGENT step

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Etienne
2026-06-12 10:23:03 +02:00
committed by GitHub
parent 2538239e05
commit fefd9d7704
120 changed files with 4445 additions and 1192 deletions
@@ -31,7 +31,7 @@ import {
WorkflowStatus,
type WorkflowWorkspaceEntity,
} from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { WorkflowActionType } from 'twenty-shared/workflow';
import {
WorkflowTriggerException,
WorkflowTriggerExceptionCode,
@@ -1,8 +1,6 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
import { computeWorkflowVersionStepChanges } from 'src/modules/workflow/workflow-builder/utils/compute-workflow-version-step-updates.util';
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import {
type WorkflowTrigger,
WorkflowTriggerType,
@@ -1,11 +1,16 @@
import { Module } from '@nestjs/common';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module';
import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service';
@Module({
imports: [WorkflowCommonModule, FeatureFlagModule],
imports: [
WorkflowCommonModule,
FeatureFlagModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
],
providers: [WorkflowSchemaWorkspaceService],
exports: [WorkflowSchemaWorkspaceService],
})
@@ -10,10 +10,12 @@ import {
navigateOutputSchemaProperty,
SingleRecordAvailability,
TRIGGER_STEP_ID,
WorkflowActionType,
} from 'twenty-shared/workflow';
import { type DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { checkStringIsDatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/utils/check-string-is-database-event-action';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { generateFakeValue } from 'src/engine/utils/generate-fake-value';
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
import { DEFAULT_ITERATOR_CURRENT_ITEM } from 'src/modules/workflow/workflow-builder/workflow-schema/constants/default-iterator-current-item.const';
@@ -29,10 +31,7 @@ import { generateFakeObjectRecord } from 'src/modules/workflow/workflow-builder/
import { generateFakeObjectRecordEvent } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/generate-fake-object-record-event';
import { inferArrayItemSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/utils/infer-array-item-schema';
import { type FormFieldMetadata } from 'src/modules/workflow/workflow-executor/workflow-actions/form/types/workflow-form-action-settings.type';
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import {
WorkflowTrigger,
WorkflowTriggerType,
@@ -42,6 +41,7 @@ import {
export class WorkflowSchemaWorkspaceService {
constructor(
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
) {}
async computeStepOutputSchema({
@@ -0,0 +1,124 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
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 WorkflowAiAgentAction,
type WorkflowFindRecordsAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { WorkflowValidationWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-validation/workflow-validation.workspace-service';
const WORKSPACE_ID = 'workspace-id';
const ERROR_HANDLING_OPTIONS = {
retryOnFailure: { value: false },
continueOnFailure: { value: false },
};
const buildService = (objectIdByNameSingular: Record<string, string> = {}) => {
const workflowCommonWorkspaceService = {
getWorkflowVersionOrFail: jest.fn(),
getFlatEntityMaps: jest.fn().mockResolvedValue({ objectIdByNameSingular }),
} as unknown as jest.Mocked<WorkflowCommonWorkspaceService>;
const workflowSchemaWorkspaceService = {
computeStepOutputSchema: jest.fn().mockResolvedValue(undefined),
} as unknown as jest.Mocked<WorkflowSchemaWorkspaceService>;
return new WorkflowValidationWorkspaceService(
workflowCommonWorkspaceService,
workflowSchemaWorkspaceService,
);
};
const buildAiAgentStep = (input: {
agentId?: string;
prompt?: string;
}): WorkflowAiAgentAction => ({
id: 'ai-agent-step',
name: 'AI Agent',
type: WorkflowActionType.AI_AGENT,
valid: true,
settings: {
input,
outputSchema: {
result: { isLeaf: true, type: 'string', label: 'result', value: '' },
},
errorHandlingOptions: ERROR_HANDLING_OPTIONS,
},
});
const buildFindRecordsStep = (
objectName: string,
): WorkflowFindRecordsAction => ({
id: 'find-records-step',
name: 'Find Records',
type: WorkflowActionType.FIND_RECORDS,
valid: true,
settings: {
input: { objectName },
outputSchema: {},
errorHandlingOptions: ERROR_HANDLING_OPTIONS,
},
});
describe('WorkflowValidationWorkspaceService', () => {
it('should flag an AI Agent step that has no agent selected', async () => {
const service = buildService();
const result = await service.validateWorkflowDefinition({
workspaceId: WORKSPACE_ID,
trigger: null,
steps: [buildAiAgentStep({})],
});
expect(result.errors.map((issue) => issue.code)).toContain(
'AI_AGENT_MISSING_AGENT',
);
});
it('should not flag an AI Agent step that has an agent selected', async () => {
const service = buildService();
const result = await service.validateWorkflowDefinition({
workspaceId: WORKSPACE_ID,
trigger: null,
steps: [buildAiAgentStep({ agentId: 'agent-1' })],
});
expect(result.errors.map((issue) => issue.code)).not.toContain(
'AI_AGENT_MISSING_AGENT',
);
});
it('should flag a record step targeting an object that does not exist in the workspace', async () => {
const service = buildService({});
const result = await service.validateWorkflowDefinition({
workspaceId: WORKSPACE_ID,
trigger: null,
steps: [buildFindRecordsStep('ghost')],
});
expect(
result.errors.some((issue) =>
issue.message.includes('does not exist in this workspace'),
),
).toBe(true);
});
it('should not flag a record step targeting an existing object', async () => {
const service = buildService({ person: 'object-id-1' });
const result = await service.validateWorkflowDefinition({
workspaceId: WORKSPACE_ID,
trigger: null,
steps: [buildFindRecordsStep('person')],
});
expect(
result.errors.some((issue) =>
issue.message.includes('does not exist in this workspace'),
),
).toBe(false);
});
});
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-common.module';
import { WorkflowSchemaModule } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.module';
import { WorkflowValidationWorkspaceService } from './workflow-validation.workspace-service';
@Module({
imports: [WorkflowCommonModule, WorkflowSchemaModule],
providers: [WorkflowValidationWorkspaceService],
exports: [WorkflowValidationWorkspaceService],
})
export class WorkflowValidationModule {}
@@ -0,0 +1,286 @@
import { Injectable } from '@nestjs/common';
import { isNonEmptyString, isObject, isString } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
import {
validateWorkflowStructure,
type WorkflowValidationIssue,
type WorkflowValidationResult,
WorkflowActionType,
} from 'twenty-shared/workflow';
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 {
type WorkflowAction,
type WorkflowAiAgentAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
const RECORD_CRUD_ACTION_TYPES = new Set<WorkflowActionType>([
WorkflowActionType.CREATE_RECORD,
WorkflowActionType.UPDATE_RECORD,
WorkflowActionType.DELETE_RECORD,
WorkflowActionType.UPSERT_RECORD,
WorkflowActionType.FIND_RECORDS,
]);
@Injectable()
export class WorkflowValidationWorkspaceService {
constructor(
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
private readonly workflowSchemaWorkspaceService: WorkflowSchemaWorkspaceService,
) {}
async validateWorkflowVersion({
workspaceId,
workflowVersionId,
}: {
workspaceId: string;
workflowVersionId: string;
}): Promise<WorkflowValidationResult> {
const workflowVersion =
await this.workflowCommonWorkspaceService.getWorkflowVersionOrFail({
workspaceId,
workflowVersionId,
});
return this.validateWorkflowDefinition({
workspaceId,
workflowVersionId,
trigger: workflowVersion.trigger,
steps: workflowVersion.steps,
});
}
async validateWorkflowDefinition({
workspaceId,
workflowVersionId,
trigger,
steps,
}: {
workspaceId: string;
workflowVersionId?: string;
trigger: WorkflowTrigger | null;
steps: WorkflowAction[] | null;
}): Promise<WorkflowValidationResult> {
const { trigger: enrichedTrigger, steps: enrichedSteps } =
await this.enrichOutputSchemas({
workspaceId,
workflowVersionId,
trigger,
steps,
});
const staticResult = validateWorkflowStructure({
trigger: enrichedTrigger,
steps: enrichedSteps,
});
const semanticIssues = this.validateStepTypeRequirements({
steps: enrichedSteps ?? [],
});
const metadataIssues = await this.validateWorkspaceMetadata({
workspaceId,
steps: enrichedSteps ?? [],
});
return mergeValidationResults(staticResult, [
...semanticIssues,
...metadataIssues,
]);
}
private async enrichOutputSchemas({
workspaceId,
workflowVersionId,
trigger,
steps,
}: {
workspaceId: string;
workflowVersionId?: string;
trigger: WorkflowTrigger | null;
steps: WorkflowAction[] | null;
}): Promise<{
trigger: WorkflowTrigger | null;
steps: WorkflowAction[] | null;
}> {
const enrichedTrigger = isDefined(trigger)
? await this.withComputedOutputSchema({
step: trigger,
workspaceId,
workflowVersionId,
})
: trigger;
const enrichedSteps = isDefined(steps)
? await Promise.all(
steps.map((step) =>
this.withComputedOutputSchema({
step,
workspaceId,
workflowVersionId,
}),
),
)
: steps;
return { trigger: enrichedTrigger, steps: enrichedSteps };
}
private async withComputedOutputSchema<
TStep extends WorkflowTrigger | WorkflowAction,
>({
step,
workspaceId,
workflowVersionId,
}: {
step: TStep;
workspaceId: string;
workflowVersionId?: string;
}): Promise<TStep> {
try {
const computedSchema =
await this.workflowSchemaWorkspaceService.computeStepOutputSchema({
step,
workspaceId,
workflowVersionId,
});
if (
!isDefined(computedSchema) ||
Object.keys(computedSchema).length === 0
) {
return step;
}
return {
...step,
settings: { ...step.settings, outputSchema: computedSchema },
};
} catch {
// Output schema enrichment is best-effort: if it cannot be computed,
// validation still runs against the step's existing settings rather
// than failing the whole validation.
return step;
}
}
private validateStepTypeRequirements({
steps,
}: {
steps: WorkflowAction[];
}): WorkflowValidationIssue[] {
const issues: WorkflowValidationIssue[] = [];
for (const step of steps) {
switch (step.type) {
case WorkflowActionType.AI_AGENT:
issues.push(...this.validateAiAgentStep(step));
break;
}
}
return issues;
}
private validateAiAgentStep(
step: WorkflowAiAgentAction,
): WorkflowValidationIssue[] {
const issues: WorkflowValidationIssue[] = [];
if (!isNonEmptyString(step.settings?.input?.agentId)) {
issues.push({
severity: 'error',
code: 'AI_AGENT_MISSING_AGENT',
message: `AI Agent step "${step.name ?? step.id}" has no agent selected.`,
stepId: step.id,
});
}
const outputSchema = step.settings?.outputSchema;
const hasOutputSchema =
isDefined(outputSchema) && Object.keys(outputSchema).length > 0;
if (!hasOutputSchema) {
issues.push({
severity: 'warning',
code: 'AI_AGENT_MISSING_OUTPUT_VARIABLE',
message: `AI Agent step "${step.name ?? step.id}" has no output variable defined. Downstream steps won't be able to reference its result.`,
stepId: step.id,
});
}
return issues;
}
private async validateWorkspaceMetadata({
workspaceId,
steps,
}: {
workspaceId: string;
steps: WorkflowAction[];
}): Promise<WorkflowValidationIssue[]> {
const recordSteps = steps.filter((step) =>
RECORD_CRUD_ACTION_TYPES.has(step.type),
);
if (recordSteps.length === 0) {
return [];
}
const { objectIdByNameSingular } =
await this.workflowCommonWorkspaceService.getFlatEntityMaps(workspaceId);
const issues: WorkflowValidationIssue[] = [];
for (const step of recordSteps) {
const input = step.settings.input;
const objectName =
isObject(input) && 'objectName' in input ? input.objectName : undefined;
if (!isString(objectName)) {
issues.push({
severity: 'error',
code: 'INVALID_STEP_PARAMS',
message: `Step "${step.name ?? step.id}" has an invalid object name.`,
stepId: step.id,
});
continue;
}
if (!isDefined(objectIdByNameSingular[objectName])) {
issues.push({
severity: 'error',
code: 'INVALID_STEP_PARAMS',
message: `Step "${step.name ?? step.id}" targets object "${objectName}" which does not exist in this workspace.`,
stepId: step.id,
});
}
}
return issues;
}
}
const mergeValidationResults = (
baseResult: WorkflowValidationResult,
additionalIssues: WorkflowValidationIssue[],
): WorkflowValidationResult => {
const errors = [
...baseResult.errors,
...additionalIssues.filter((issue) => issue.severity === 'error'),
];
const warnings = [
...baseResult.warnings,
...additionalIssues.filter((issue) => issue.severity === 'warning'),
];
return {
valid: errors.length === 0,
errors,
warnings,
};
};
@@ -1,16 +1,13 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { TRIGGER_STEP_ID, WorkflowActionType } from 'twenty-shared/workflow';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { type WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
import { WorkflowVersionEdgeWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service';
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { WorkflowTriggerType } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
type MockWorkspaceRepository = Partial<
@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { TRIGGER_STEP_ID, WorkflowActionType } from 'twenty-shared/workflow';
import { type WorkflowVersionStepChangesDTO } from 'src/engine/core-modules/workflow/dtos/workflow-version-step-changes.dto';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
@@ -16,10 +16,7 @@ import { assertWorkflowVersionIsDraft } from 'src/modules/workflow/common/utils/
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
import { computeWorkflowVersionStepChanges } from 'src/modules/workflow/workflow-builder/utils/compute-workflow-version-step-updates.util';
import { WorkflowStepConnectionOptions } from 'src/modules/workflow/workflow-builder/workflow-version-step/types/WorkflowStepCreationOptions';
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
@Injectable()
@@ -1,6 +1,7 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { WorkflowActionType } from 'twenty-shared/workflow';
import { SEED_WORKFLOW_ACTION_TRIGGER_SETTINGS } from 'twenty-shared/logic-function';
import { AiAgentRoleService } from 'src/engine/metadata-modules/ai/ai-agent-role/ai-agent-role.service';
@@ -17,10 +18,7 @@ import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/works
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
import { CodeStepBuildService } from 'src/modules/workflow/workflow-builder/workflow-version-step/code-step/services/code-step-build.service';
import { WorkflowVersionStepOperationsWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service';
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
const mockWorkspaceId = 'workspace-id';
@@ -1,6 +1,6 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { TRIGGER_STEP_ID, WorkflowActionType } from 'twenty-shared/workflow';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { type WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
@@ -13,10 +13,7 @@ import { WorkflowVersionStepHelpersWorkspaceService } from 'src/modules/workflow
import { WorkflowVersionStepOperationsWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service';
import { WorkflowVersionStepUpdateWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-update.workspace-service';
import { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service';
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { WorkflowTriggerType } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
jest.mock(
@@ -4,7 +4,7 @@ import {
extractCodeStepLogicFunctionIdsFromWorkflowSteps,
type NonCodeStepForLogicFunctionIdExtraction,
} from 'src/modules/workflow/workflow-builder/workflow-version-step/code-step/utils/extract-code-step-logic-function-ids-from-workflow-steps.util';
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { WorkflowActionType } from 'twenty-shared/workflow';
const buildCodeStep = (
logicFunctionId: string,
@@ -4,11 +4,11 @@ import {
WorkflowVersionStepException,
WorkflowVersionStepExceptionCode,
} from 'src/modules/workflow/common/exceptions/workflow-version-step.exception';
import { WorkflowActionType } from 'twenty-shared/workflow';
import { type WorkflowCodeActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/code/types/workflow-code-action-input.type';
import {
type WorkflowAction,
type WorkflowCodeAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
export type CodeStepForLogicFunctionIdExtraction = Pick<
@@ -1,4 +1,4 @@
import { type WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowActionType } from 'twenty-shared/workflow';
type WorkflowIteratorStepConnectionOptions = {
connectedStepType: WorkflowActionType.ITERATOR;
@@ -1,10 +1,9 @@
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { TRIGGER_STEP_ID, WorkflowActionType } from 'twenty-shared/workflow';
import { insertStep } from 'src/modules/workflow/workflow-builder/workflow-version-step/utils/insert-step';
import {
type WorkflowAction,
type WorkflowIteratorAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import {
type WorkflowTrigger,
@@ -2,13 +2,10 @@
// @ts-nocheck
// Disabled type checking due to tsgo performance issue with deep spread operations
// See: https://github.com/microsoft/typescript-go/issues/2551
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { TRIGGER_STEP_ID, WorkflowActionType } from 'twenty-shared/workflow';
import { removeStep } from 'src/modules/workflow/workflow-builder/workflow-version-step/utils/remove-step';
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import {
type WorkflowTrigger,
WorkflowTriggerType,
@@ -1,5 +1,5 @@
import { isDefined } from 'twenty-shared/utils';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { TRIGGER_STEP_ID, WorkflowActionType } from 'twenty-shared/workflow';
import {
WorkflowVersionStepException,
@@ -7,10 +7,7 @@ import {
} from 'src/modules/workflow/common/exceptions/workflow-version-step.exception';
import { type WorkflowStepConnectionOptions } from 'src/modules/workflow/workflow-builder/workflow-version-step/types/WorkflowStepCreationOptions';
import { type WorkflowIteratorActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/iterator/types/workflow-iterator-action-settings.type';
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowTrigger } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
export const insertStep = ({
@@ -3,13 +3,13 @@ import {
IF_ELSE_BRANCH_POSITION_OFFSETS,
TRIGGER_STEP_ID,
type StepIfElseBranch,
WorkflowActionType,
} from 'twenty-shared/workflow';
import { v4 } from 'uuid';
import { isWorkflowEmptyAction } from 'src/modules/workflow/workflow-executor/workflow-actions/empty/guards/is-workflow-empty-action.guard';
import { isWorkflowIfElseAction } from 'src/modules/workflow/workflow-executor/workflow-actions/if-else/guards/is-workflow-if-else-action.guard';
import {
WorkflowActionType,
type WorkflowAction,
type WorkflowIteratorAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
@@ -12,6 +12,7 @@ import {
IF_ELSE_BRANCH_POSITION_OFFSETS,
getFunctionInputFromInputSchema,
type StepIfElseBranch,
WorkflowActionType,
} from 'twenty-shared/workflow';
import { Repository } from 'typeorm';
import { v4 } from 'uuid';
@@ -44,7 +45,6 @@ import { type OutputSchema } from 'src/modules/workflow/workflow-builder/workflo
import { CodeStepBuildService } from 'src/modules/workflow/workflow-builder/workflow-version-step/code-step/services/code-step-build.service';
import { type BaseWorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type';
import {
WorkflowActionType,
type WorkflowAction,
type WorkflowEmptyAction,
type WorkflowFormAction,
@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { TRIGGER_STEP_ID, WorkflowActionType } from 'twenty-shared/workflow';
import { WithLock } from 'src/engine/core-modules/cache-lock/with-lock.decorator';
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
@@ -25,10 +25,7 @@ import { assertWorkflowVersionIsDraft } from 'src/modules/workflow/common/utils/
import { assertWorkflowVersionTriggerIsDefined } from 'src/modules/workflow/common/utils/assert-workflow-version-trigger-is-defined.util';
import { WorkflowVersionStepOperationsWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-operations.workspace-service';
import { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service';
import {
WorkflowActionType,
type WorkflowAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
@Injectable()
export class WorkflowVersionWorkspaceService {
@@ -23,7 +23,7 @@ import { DeleteRecordWorkflowAction } from 'src/modules/workflow/workflow-execut
import { FindRecordsWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action';
import { UpdateRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action';
import { UpsertRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/upsert-record.workflow-action';
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { WorkflowActionType } from 'twenty-shared/workflow';
@Injectable()
export class WorkflowActionFactory {
@@ -1,10 +1,7 @@
import { StepStatus } from 'twenty-shared/workflow';
import { StepStatus, WorkflowActionType } from 'twenty-shared/workflow';
import { shouldExecuteChildStep } from 'src/modules/workflow/workflow-executor/utils/should-execute-child-step.util';
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
describe('shouldExecuteChildStep', () => {
const parentSteps = [
@@ -1,4 +1,4 @@
import { StepStatus } from 'twenty-shared/workflow';
import { StepStatus, WorkflowActionType } from 'twenty-shared/workflow';
import { WorkflowRunStatus } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity';
import {
@@ -6,10 +6,7 @@ import {
createMockIfElseStep,
} from 'src/modules/workflow/workflow-executor/utils/create-mock-workflow-steps.util';
import { shouldExecuteStep } from 'src/modules/workflow/workflow-executor/utils/should-execute-step.util';
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
describe('shouldExecuteStep', () => {
const steps = [
@@ -1,10 +1,12 @@
import { type StepIfElseBranch } from 'twenty-shared/workflow';
import {
type StepIfElseBranch,
WorkflowActionType,
} from 'twenty-shared/workflow';
import { type WorkflowCodeActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/code/types/workflow-code-action-settings.type';
import { type WorkflowIfElseActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/if-else/types/workflow-if-else-action-settings.type';
import { type WorkflowIteratorActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/iterator/types/workflow-iterator-action-settings.type';
import {
WorkflowActionType,
type WorkflowCodeAction,
type WorkflowIfElseAction,
type WorkflowIteratorAction,
@@ -1,6 +1,6 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
import {
type WorkflowAction,
WorkflowActionType,
type WorkflowAiAgentAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
@@ -1,6 +1,6 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
import {
type WorkflowAction,
WorkflowActionType,
type WorkflowCodeAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
@@ -1,6 +1,6 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
import {
type WorkflowAction,
WorkflowActionType,
type WorkflowDelayAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
@@ -1,7 +1,5 @@
import {
WorkflowActionType,
type WorkflowAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { WorkflowActionType } from 'twenty-shared/workflow';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
export const isWorkflowEmptyAction = (
action: WorkflowAction,
@@ -1,6 +1,6 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
import {
type WorkflowAction,
WorkflowActionType,
type WorkflowFilterAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
@@ -1,6 +1,6 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
import {
type WorkflowAction,
WorkflowActionType,
type WorkflowFormAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
@@ -1,12 +1,10 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { WorkflowActionType } from 'twenty-shared/workflow';
import { HttpTool } from 'src/engine/core-modules/tool/tools/http-tool/http-tool';
import { HttpRequestWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/http-request/http-request.workflow-action';
import { type WorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type';
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { WorkflowRunStepLogWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run-step-log.workspace-service';
const baseSettings: WorkflowActionSettings = {
@@ -1,6 +1,6 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
import {
type WorkflowAction,
WorkflowActionType,
type WorkflowHttpRequestAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
@@ -1,6 +1,6 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
import {
type WorkflowAction,
WorkflowActionType,
type WorkflowIfElseAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
@@ -1,15 +1,12 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { StepStatus } from 'twenty-shared/workflow';
import { StepStatus, WorkflowActionType } from 'twenty-shared/workflow';
import { WorkflowStepExecutorException } from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception';
import { IteratorWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/iterator/iterator.workflow-action';
import { type WorkflowIteratorActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/iterator/types/workflow-iterator-action-settings.type';
import { type WorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type';
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service';
describe('IteratorWorkflowAction', () => {
@@ -1,6 +1,6 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
import {
type WorkflowAction,
WorkflowActionType,
type WorkflowIteratorAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
@@ -1,7 +1,5 @@
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { WorkflowActionType } from 'twenty-shared/workflow';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
export const isWorkflowLogicFunctionAction = (
action: WorkflowAction,
@@ -1,12 +1,10 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { WorkflowActionType } from 'twenty-shared/workflow';
import { DraftEmailTool } from 'src/engine/core-modules/tool/tools/email-tool/draft-email-tool';
import { DraftEmailWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/draft-email.workflow-action';
import { type WorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type';
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { WorkflowRunStepLogWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run-step-log.workspace-service';
jest.mock(
@@ -1,12 +1,10 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { WorkflowActionType } from 'twenty-shared/workflow';
import { SendEmailTool } from 'src/engine/core-modules/tool/tools/email-tool/send-email-tool';
import { SendEmailWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/mail-sender/send-email.workflow-action';
import { type WorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type';
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { WorkflowRunStepLogWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run-step-log.workspace-service';
jest.mock(
@@ -1,6 +1,6 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
import {
type WorkflowAction,
WorkflowActionType,
type WorkflowDraftEmailAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
@@ -1,6 +1,6 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
import {
type WorkflowAction,
WorkflowActionType,
type WorkflowSendEmailAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
@@ -1,6 +1,6 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
import {
type WorkflowAction,
WorkflowActionType,
type WorkflowDeleteRecordAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
@@ -1,6 +1,6 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
import {
type WorkflowAction,
WorkflowActionType,
type WorkflowFindRecordsAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
@@ -1,6 +1,6 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
import {
type WorkflowAction,
WorkflowActionType,
type WorkflowUpdateRecordAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
@@ -1,6 +1,6 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
import {
type WorkflowAction,
WorkflowActionType,
type WorkflowUpsertRecordAction,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
@@ -1,19 +0,0 @@
export enum WorkflowActionType {
CODE = 'CODE',
LOGIC_FUNCTION = 'LOGIC_FUNCTION',
SEND_EMAIL = 'SEND_EMAIL',
DRAFT_EMAIL = 'DRAFT_EMAIL',
CREATE_RECORD = 'CREATE_RECORD',
UPDATE_RECORD = 'UPDATE_RECORD',
DELETE_RECORD = 'DELETE_RECORD',
UPSERT_RECORD = 'UPSERT_RECORD',
FIND_RECORDS = 'FIND_RECORDS',
FORM = 'FORM',
FILTER = 'FILTER',
IF_ELSE = 'IF_ELSE',
HTTP_REQUEST = 'HTTP_REQUEST',
AI_AGENT = 'AI_AGENT',
ITERATOR = 'ITERATOR',
EMPTY = 'EMPTY',
DELAY = 'DELAY',
}
@@ -1,3 +1,5 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
import { type WorkflowAiAgentActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/ai-agent/types/workflow-ai-agent-action-settings.type';
import { type WorkflowCodeActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/code/types/workflow-code-action-settings.type';
import { type WorkflowDelayActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/delay/types/workflow-delay-action-settings.type';
@@ -17,12 +19,6 @@ import {
} from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-settings.type';
import { type WorkflowActionSettings } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-settings.type';
// Import the enum from its dedicated file to avoid circular dependencies
import { WorkflowActionType } from './workflow-action-type.enum';
// Re-export for consumers
export { WorkflowActionType };
type BaseWorkflowAction = {
id: string;
name: string;
@@ -1,6 +1,10 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { getWorkflowRunContext, StepStatus } from 'twenty-shared/workflow';
import {
getWorkflowRunContext,
StepStatus,
WorkflowActionType,
} from 'twenty-shared/workflow';
import { BillingUsageService } from 'src/engine/core-modules/billing/services/billing-usage.service';
import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
@@ -17,10 +21,7 @@ import { WorkflowActionFactory } from 'src/modules/workflow/workflow-executor/fa
import { shouldExecuteStep } from 'src/modules/workflow/workflow-executor/utils/should-execute-step.util';
import { shouldFailSafely } from 'src/modules/workflow/workflow-executor/utils/should-fail-safely.util';
import { shouldSkipStepExecution } from 'src/modules/workflow/workflow-executor/utils/should-skip-step-execution.util';
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { WorkflowExecutorWorkspaceService } from 'src/modules/workflow/workflow-executor/workspace-services/workflow-executor.workspace-service';
import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service';
@@ -8,6 +8,7 @@ import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadat
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 { 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';
import { WorkflowVersionStepHelpersWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-helpers.workspace-service';
import { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service';
@@ -27,6 +28,7 @@ import { createUpdateLogicFunctionSourceTool } from 'src/modules/workflow/workfl
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';
import { createUpdateWorkflowVersionTriggerTool } from 'src/modules/workflow/workflow-tools/tools/update-workflow-version-trigger.tool';
import { createValidateWorkflowTool } from 'src/modules/workflow/workflow-tools/tools/validate-workflow.tool';
import { type WorkflowToolDependencies } from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type';
import { WorkflowTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/workspace-services/workflow-trigger.workspace-service';
@@ -41,6 +43,7 @@ export class WorkflowToolWorkspaceService {
workflowVersionService: WorkflowVersionWorkspaceService,
workflowTriggerService: WorkflowTriggerWorkspaceService,
workflowSchemaService: WorkflowSchemaWorkspaceService,
workflowValidationService: WorkflowValidationWorkspaceService,
globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
recordPositionService: RecordPositionService,
logicFunctionFromSourceService: LogicFunctionFromSourceService,
@@ -53,6 +56,7 @@ export class WorkflowToolWorkspaceService {
workflowVersionService,
workflowTriggerService,
workflowSchemaService,
workflowValidationService,
globalWorkspaceOrmManager,
recordPositionService,
logicFunctionFromSourceService,
@@ -124,6 +128,7 @@ export class WorkflowToolWorkspaceService {
this.deps,
context,
);
const validateWorkflow = createValidateWorkflowTool(this.deps, context);
return {
[createCompleteWorkflow.name]: createCompleteWorkflow,
@@ -141,6 +146,7 @@ export class WorkflowToolWorkspaceService {
[getWorkflowCurrentVersion.name]: getWorkflowCurrentVersion,
[updateLogicFunctionSource.name]: updateLogicFunctionSource,
[listLogicFunctionTools.name]: listLogicFunctionTools,
[validateWorkflow.name]: validateWorkflow,
};
}
}
@@ -7,12 +7,12 @@ 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 { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
import { WorkflowStatus } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import {
WorkflowVersionStepException,
WorkflowVersionStepExceptionCode,
} from 'src/modules/workflow/common/exceptions/workflow-version-step.exception';
import { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
import { WorkflowStatus } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import {
type WorkflowToolContext,
@@ -70,6 +70,7 @@ type CreateCompleteWorkflowToolDeps = Pick<
| 'workflowTriggerService'
| 'globalWorkspaceOrmManager'
| 'recordPositionService'
| 'workflowValidationService'
>;
type CreateCompleteWorkflowToolContext = WorkflowToolContext & {
@@ -182,6 +183,13 @@ This is the most efficient way for AI to create workflows as it handles all the
});
}
const validation =
await deps.workflowValidationService.validateWorkflowDefinition({
workspaceId: context.workspaceId,
trigger: parameters.trigger,
steps: parameters.steps,
});
return {
success: true,
message: `Workflow "${parameters.name}" created successfully with ${parameters.steps.length} steps`,
@@ -191,6 +199,7 @@ This is the most efficient way for AI to create workflows as it handles all the
name: parameters.name,
trigger: parameters.trigger,
steps: parameters.steps,
validation,
},
recordReferences: [
{
@@ -1,6 +1,6 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
import { z } from 'zod';
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-type.enum';
import {
type WorkflowToolContext,
type WorkflowToolDependencies,
@@ -1,10 +1,9 @@
import { isDefined } from 'twenty-shared/utils';
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
import { TRIGGER_STEP_ID, WorkflowActionType } from 'twenty-shared/workflow';
import { z } from 'zod';
import type { CreateWorkflowVersionStepInput } from 'src/engine/core-modules/workflow/dtos/create-workflow-version-step.input';
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,
@@ -85,7 +84,7 @@ const enrichResultWithNextStep = ({
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.',
'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. IMPORTANT: Also provide outputSchema (an example return value, e.g. { datePlus7: "2026-06-16" }) so downstream steps can reference this step\'s output variables via {{stepId.fieldName}}.',
};
default:
return result;
@@ -1,6 +1,6 @@
import { WorkflowActionType } from 'twenty-shared/workflow';
import { z } from 'zod';
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-type.enum';
import {
type WorkflowToolContext,
type WorkflowToolDependencies,
@@ -18,7 +18,10 @@ const updateWorkflowVersionStepSchema = z.object({
});
export const createUpdateWorkflowVersionStepTool = (
deps: Pick<WorkflowToolDependencies, 'workflowVersionStepService'>,
deps: Pick<
WorkflowToolDependencies,
'workflowVersionStepService' | 'workflowValidationService'
>,
context: WorkflowToolContext,
) => ({
name: 'update_workflow_version_step' as const,
@@ -26,8 +29,10 @@ export const createUpdateWorkflowVersionStepTool = (
'Update an existing step in a workflow version. This modifies the step configuration.',
inputSchema: updateWorkflowVersionStepSchema,
execute: async (parameters: UpdateWorkflowVersionStepInput) => {
let result;
try {
return await deps.workflowVersionStepService.updateWorkflowVersionStep({
result = await deps.workflowVersionStepService.updateWorkflowVersionStep({
workspaceId: context.workspaceId,
workflowVersionId: parameters.workflowVersionId,
step: parameters.step,
@@ -39,5 +44,23 @@ export const createUpdateWorkflowVersionStepTool = (
message: `Failed to update workflow version step: ${error.message}`,
};
}
try {
const validation =
await deps.workflowValidationService.validateWorkflowVersion({
workspaceId: context.workspaceId,
workflowVersionId: parameters.workflowVersionId,
});
return {
...result,
validation,
};
} catch (error) {
return {
...result,
validationError: error.message,
message: `Step updated successfully, but validation could not be computed: ${error.message}`,
};
}
},
});
@@ -0,0 +1,50 @@
import { z } from 'zod';
import { type WorkflowValidationWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-validation/workflow-validation.workspace-service';
import { type WorkflowToolContext } from 'src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type';
const validateWorkflowSchema = z.object({
workflowVersionId: z
.string()
.uuid()
.describe('The UUID of the workflow version to validate'),
});
type ValidateWorkflowInput = z.infer<typeof validateWorkflowSchema>;
export const createValidateWorkflowTool = (
deps: {
workflowValidationService: WorkflowValidationWorkspaceService;
},
context: WorkflowToolContext,
) => ({
name: 'validate_workflow' as const,
description:
'Validate a workflow version for correctness. Checks graph topology (connections, reachability, branches, loops), per-step configuration, references to other objects, and variable references between steps. Returns a list of errors and warnings to fix. Does not block or modify the workflow.',
inputSchema: validateWorkflowSchema,
execute: async (parameters: ValidateWorkflowInput) => {
try {
const result =
await deps.workflowValidationService.validateWorkflowVersion({
workspaceId: context.workspaceId,
workflowVersionId: parameters.workflowVersionId,
});
return {
success: true,
valid: result.valid,
errors: result.errors,
warnings: result.warnings,
message: result.valid
? 'The workflow is valid.'
: `The workflow has ${result.errors.length} error(s) that should be fixed.`,
};
} catch (error) {
return {
success: false,
error: error.message,
message: `Failed to validate workflow: ${error.message}`,
};
}
},
});
@@ -3,6 +3,7 @@ import type { LogicFunctionFromSourceService } from 'src/engine/metadata-modules
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 { 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';
import type { WorkflowVersionStepHelpersWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step-helpers.workspace-service';
import type { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service';
@@ -16,6 +17,7 @@ export type WorkflowToolDependencies = {
workflowVersionService: WorkflowVersionWorkspaceService;
workflowTriggerService: WorkflowTriggerWorkspaceService;
workflowSchemaService: WorkflowSchemaWorkspaceService;
workflowValidationService: WorkflowValidationWorkspaceService;
globalWorkspaceOrmManager: GlobalWorkspaceOrmManager;
recordPositionService: RecordPositionService;
logicFunctionFromSourceService: LogicFunctionFromSourceService;
@@ -5,6 +5,7 @@ import { WORKFLOW_TOOL_SERVICE_TOKEN } from 'src/engine/core-modules/tool-provid
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 { 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 { WorkflowVersionStepModule } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.module';
import { WorkflowVersionModule } from 'src/modules/workflow/workflow-builder/workflow-version/workflow-version.module';
@@ -22,6 +23,7 @@ import { WorkflowToolWorkspaceService } from './services/workflow-tool.workspace
WorkflowVersionModule,
WorkflowTriggerModule,
WorkflowSchemaModule,
WorkflowValidationModule,
RecordPositionModule,
LogicFunctionModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
@@ -4,11 +4,9 @@ import {
WorkflowVersionStatus,
type WorkflowVersionWorkspaceEntity,
} from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
import { WorkflowActionType } from 'twenty-shared/workflow';
import { type WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import {
type WorkflowAction,
WorkflowActionType,
} from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { type WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import {
WorkflowTriggerException,
WorkflowTriggerExceptionCode,