diff --git a/packages/twenty-front/src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts b/packages/twenty-front/src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts index 5058e453fc..ec9cb7aff1 100644 --- a/packages/twenty-front/src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts +++ b/packages/twenty-front/src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts @@ -25,6 +25,7 @@ export const useMetadataErrorHandler = () => { routeTrigger: t`route trigger`, role: t`role`, roleTarget: t`role target`, + agent: t`agent`, } as const satisfies Record; const handleMetadataError = useCallback( diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1764672601466-change-agent-description-to-text.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1764672601466-change-agent-description-to-text.ts new file mode 100644 index 0000000000..3e35a93208 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1764672601466-change-agent-description-to-text.ts @@ -0,0 +1,19 @@ +import { type MigrationInterface, type QueryRunner } from 'typeorm'; + +export class ChangeAgentDescriptionToText1764672601466 + implements MigrationInterface +{ + name = 'ChangeAgentDescriptionToText1764672601466'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."agent" ALTER COLUMN "description" TYPE text`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "core"."agent" ALTER COLUMN "description" TYPE character varying`, + ); + } +} diff --git a/packages/twenty-server/src/engine/api/rest/core/rest-api-core.module.ts b/packages/twenty-server/src/engine/api/rest/core/rest-api-core.module.ts index 4ffa0c7af8..5fba799576 100644 --- a/packages/twenty-server/src/engine/api/rest/core/rest-api-core.module.ts +++ b/packages/twenty-server/src/engine/api/rest/core/rest-api-core.module.ts @@ -32,7 +32,6 @@ import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role. import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module'; import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; -import { WorkspaceFlatMapCacheModule } from 'src/engine/workspace-flat-map-cache/workspace-flat-map-cache.module'; const restApiCoreResolvers = [ RestApiCreateOneHandler, @@ -63,7 +62,6 @@ const restApiCoreResolvers = [ TwentyORMModule, RecordTransformerModule, WorkspaceManyOrAllFlatEntityMapsCacheModule, - WorkspaceFlatMapCacheModule, ActorModule, FeatureFlagModule, CoreCommonApiModule, diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.exception.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.exception.ts index 0f465a1225..a554cbad68 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.exception.ts @@ -9,4 +9,7 @@ export enum AgentExceptionCode { USER_WORKSPACE_ID_NOT_FOUND = 'USER_WORKSPACE_ID_NOT_FOUND', ROLE_NOT_FOUND = 'ROLE_NOT_FOUND', ROLE_CANNOT_BE_ASSIGNED_TO_AGENTS = 'ROLE_CANNOT_BE_ASSIGNED_TO_AGENTS', + INVALID_AGENT_INPUT = 'INVALID_AGENT_INPUT', + AGENT_ALREADY_EXISTS = 'AGENT_ALREADY_EXISTS', + AGENT_IS_STANDARD = 'AGENT_IS_STANDARD', } diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.resolver.ts index 96d793b6cd..15a8f85169 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.resolver.ts @@ -1,4 +1,4 @@ -import { UseGuards } from '@nestjs/common'; +import { UseGuards, UseInterceptors } from '@nestjs/common'; import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; @@ -11,6 +11,8 @@ import { import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; +import { fromFlatAgentWithRoleIdToAgentDto } from 'src/engine/metadata-modules/flat-agent/utils/from-agent-entity-to-agent-dto.util'; +import { WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/workspace-migration-builder-graphql-api-exception.interceptor'; import { AgentService } from './agent.service'; @@ -18,20 +20,30 @@ import { AgentIdInput } from './dtos/agent-id.input'; import { AgentDTO } from './dtos/agent.dto'; import { CreateAgentInput } from './dtos/create-agent.input'; import { UpdateAgentInput } from './dtos/update-agent.input'; +import { AgentGraphqlApiExceptionInterceptor } from './interceptors/agent-graphql-api-exception.interceptor'; @UseGuards( WorkspaceAuthGuard, FeatureFlagGuard, SettingsPermissionGuard(PermissionFlagType.AI), ) +@UseInterceptors( + WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor, + AgentGraphqlApiExceptionInterceptor, +) @Resolver() export class AgentResolver { constructor(private readonly agentService: AgentService) {} @Query(() => [AgentDTO]) @RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED) - async findManyAgents(@AuthWorkspace() { id: workspaceId }: WorkspaceEntity) { - return this.agentService.findManyAgents(workspaceId); + async findManyAgents( + @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, + ): Promise { + const flatAgentsWithRoleId = + await this.agentService.findManyAgents(workspaceId); + + return flatAgentsWithRoleId.map(fromFlatAgentWithRoleIdToAgentDto); } @Query(() => AgentDTO) @@ -39,8 +51,13 @@ export class AgentResolver { async findOneAgent( @Args('input') { id }: AgentIdInput, @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, - ) { - return this.agentService.findOneAgent(workspaceId, { id }); + ): Promise { + const fatAgentWithRoleId = await this.agentService.findOneAgentById({ + workspaceId, + id, + }); + + return fromFlatAgentWithRoleIdToAgentDto(fatAgentWithRoleId); } @Mutation(() => AgentDTO) @@ -49,11 +66,13 @@ export class AgentResolver { async createOneAgent( @Args('input') input: CreateAgentInput, @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, - ) { - return this.agentService.createOneAgent( + ): Promise { + const createdAgent = await this.agentService.createOneAgent( { ...input, isCustom: true }, workspaceId, ); + + return fromFlatAgentWithRoleIdToAgentDto(createdAgent); } @Mutation(() => AgentDTO) @@ -62,8 +81,13 @@ export class AgentResolver { async updateOneAgent( @Args('input') input: UpdateAgentInput, @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, - ) { - return this.agentService.updateOneAgent(input, workspaceId); + ): Promise { + const updatedAgent = await this.agentService.updateOneAgent({ + input, + workspaceId, + }); + + return fromFlatAgentWithRoleIdToAgentDto(updatedAgent); } @Mutation(() => AgentDTO) @@ -72,7 +96,12 @@ export class AgentResolver { async deleteOneAgent( @Args('input') { id }: AgentIdInput, @AuthWorkspace() { id: workspaceId }: WorkspaceEntity, - ) { - return this.agentService.deleteOneAgent(id, workspaceId); + ): Promise { + const deletedFlatAgent = await this.agentService.deleteOneAgent( + id, + workspaceId, + ); + + return fromFlatAgentWithRoleIdToAgentDto(deletedFlatAgent); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.service.ts index 537cc4b91c..c080d53d82 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.service.ts @@ -1,14 +1,21 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { isNonEmptyString } from '@sniptt/guards'; -import { computeMetadataNameFromLabel } from 'twenty-shared/metadata'; -import { In, Repository } from 'typeorm'; +import { isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; -import { AiAgentRoleService } from 'src/engine/metadata-modules/ai/ai-agent-role/ai-agent-role.service'; +import { ApplicationService } from 'src/engine/core-modules/application/application.service'; import { type CreateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/create-agent.input'; import { type UpdateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/update-agent.input'; -import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity'; +import { fromCreateAgentInputToFlatAgent } from 'src/engine/metadata-modules/ai/ai-agent/utils/from-create-agent-input-to-flat-agent.util'; +import { fromUpdateAgentInputToFlatAgentToUpdate } from 'src/engine/metadata-modules/ai/ai-agent/utils/from-update-agent-input-to-flat-agent-to-update.util'; +import { FlatAgentWithRoleId } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; +import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util'; +import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; +import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; +import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2'; +import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service'; import { AgentException, AgentExceptionCode } from './agent.exception'; @@ -19,110 +26,43 @@ export class AgentService { constructor( @InjectRepository(AgentEntity) private readonly agentRepository: Repository, - @InjectRepository(RoleTargetEntity) - private readonly roleTargetRepository: Repository, - private readonly agentRoleService: AiAgentRoleService, + private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, + private readonly applicationService: ApplicationService, + private readonly workspaceCacheService: WorkspaceCacheService, ) {} - async findManyAgents(workspaceId: string) { - const agents = await this.agentRepository.find({ - where: { workspaceId }, - order: { createdAt: 'DESC' }, - }); + async findManyAgents(workspaceId: string): Promise { + const { flatAgentMaps, flatRoleTargetByAgentIdMaps } = + await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatAgentMaps', + 'flatRoleTargetByAgentIdMaps', + ]); - if (agents.length === 0) { - return []; - } + return Object.values(flatAgentMaps.byId) + .filter(isDefined) + .map((flatAgent) => { + const roleId = flatRoleTargetByAgentIdMaps[flatAgent.id]?.roleId; - const agentRoleMap = await this.buildAgentRoleMap(workspaceId, agents); - - return agents.map((agent) => ({ - ...agent, - roleId: agentRoleMap.get(agent.id) || null, - })); + return { + ...flatAgent, + roleId: roleId ?? null, + }; + }); } - private async buildAgentRoleMap( - workspaceId: string, - agents: AgentEntity[], - ): Promise> { - const roleTargets = await this.roleTargetRepository.find({ - where: { - workspaceId, - agentId: In(agents.map((agent) => agent.id)), - }, - }); - - const agentRoleMap = new Map(); - - roleTargets.forEach((roleTarget) => { - if (roleTarget.agentId) { - agentRoleMap.set(roleTarget.agentId, roleTarget.roleId); - } - }); - - return agentRoleMap; - } - - async findOneByApplicationAndStandardId({ - applicationId, - standardId, + async findOneAgentByName({ + name, workspaceId, }: { - applicationId: string; - standardId: string; workspaceId: string; - }) { - return await this.agentRepository.findOne({ - where: { applicationId, standardId, workspaceId }, - }); - } - - async findOneAgent( - workspaceId: string, - { id, name }: { id?: string; name?: string }, - ) { - this.validateAgentIdentifier(id, name); - - const agent = await this.fetchAgent(workspaceId, id, name); - const roleId = await this.fetchAgentRoleId(workspaceId, agent.id); - - return { - ...agent, - roleId, - }; - } - - private validateAgentIdentifier( - id: string | undefined, - name: string | undefined, - ): void { - if (!isNonEmptyString(id) && !isNonEmptyString(name)) { - throw new AgentException( - 'Either id or name must be provided', - AgentExceptionCode.AGENT_NOT_FOUND, - ); - } - - if (isNonEmptyString(id) && isNonEmptyString(name)) { - throw new AgentException( - 'Cannot specify both id and name', - AgentExceptionCode.AGENT_NOT_FOUND, - ); - } - } - - private async fetchAgent( - workspaceId: string, - id: string | undefined, - name: string | undefined, - ): Promise { + name: string; + }): Promise { const agent = await this.agentRepository.findOne({ - where: id ? { id, workspaceId } : { name, workspaceId }, + where: { name, workspaceId }, }); if (!agent) { - const identifier = id ? `id "${id}"` : `name "${name}"`; + const identifier = `name "${name}"`; throw new AgentException( `Agent with ${identifier} not found`, @@ -133,125 +73,267 @@ export class AgentService { return agent; } - private async fetchAgentRoleId( - workspaceId: string, - agentId: string, - ): Promise { - const roleTarget = await this.roleTargetRepository.findOne({ - where: { - agentId, - workspaceId, - }, - select: ['roleId'], + async findOneAgentById({ + id, + workspaceId, + }: { + workspaceId: string; + id: string; + }): Promise { + const { flatAgentMaps, flatRoleTargetByAgentIdMaps } = + await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatAgentMaps', + 'flatRoleTargetByAgentIdMaps', + ]); + + const flatAgent = findFlatEntityByIdInFlatEntityMaps({ + flatEntityId: id, + flatEntityMaps: flatAgentMaps, }); - return roleTarget?.roleId || null; + if (!isDefined(flatAgent)) { + throw new AgentException( + `Agent not found`, + AgentExceptionCode.AGENT_NOT_FOUND, + ); + } + + const roleId = flatRoleTargetByAgentIdMaps[flatAgent.id]?.roleId; + + return { ...flatAgent, roleId: roleId ?? null }; } async createOneAgent( input: CreateAgentInput & { isCustom: boolean }, workspaceId: string, - ) { - const agent = this.buildNewAgent(input, workspaceId); - const createdAgent = await this.agentRepository.save(agent); + ): Promise { + const { + flatAgentMaps: existingFlatAgentMaps, + flatRoleTargetMaps: existingFlatRoleTargetMaps, + flatRoleMaps: existingFlatRoleMaps, + } = await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatAgentMaps', + 'flatRoleTargetMaps', + 'flatRoleMaps', + ]); - if (isNonEmptyString(input.roleId)) { - await this.assignRoleToNewAgent( + const { workspaceCustomFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { + workspaceId, + }, + ); + + const { flatAgentToCreate, flatRoleTargetToCreate } = + fromCreateAgentInputToFlatAgent({ + createAgentInput: { + ...input, + applicationId: + input.applicationId ?? workspaceCustomFlatApplication.id, + }, workspaceId, - createdAgent.id, - input.roleId, + }); + + const validateAndBuildResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + fromToAllFlatEntityMaps: { + flatAgentMaps: computeFlatEntityMapsFromTo({ + flatEntityMaps: existingFlatAgentMaps, + flatEntityToCreate: [flatAgentToCreate], + flatEntityToDelete: [], + flatEntityToUpdate: [], + }), + flatRoleTargetMaps: computeFlatEntityMapsFromTo({ + flatEntityMaps: existingFlatRoleTargetMaps, + flatEntityToCreate: isDefined(flatRoleTargetToCreate) + ? [flatRoleTargetToCreate] + : [], + flatEntityToDelete: [], + flatEntityToUpdate: [], + }), + }, + dependencyAllFlatEntityMaps: { flatRoleMaps: existingFlatRoleMaps }, + buildOptions: { + isSystemBuild: false, + }, + workspaceId, + }, + ); + + if (isDefined(validateAndBuildResult)) { + throw new WorkspaceMigrationBuilderExceptionV2( + validateAndBuildResult, + 'Multiple validation errors occurred while creating agent', ); } - return this.findOneAgent(workspaceId, { id: createdAgent.id }); - } + const { flatAgentMaps: recomputedFlatAgentMaps } = + await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatAgentMaps', + ]); - private buildNewAgent( - input: CreateAgentInput & { isCustom: boolean }, - workspaceId: string, - ): AgentEntity { - return this.agentRepository.create({ - ...input, - name: isNonEmptyString(input.name) - ? input.name - : computeMetadataNameFromLabel(input.label), - workspaceId, - isCustom: input.isCustom, + const createdAgent = findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId: flatAgentToCreate.id, + flatEntityMaps: recomputedFlatAgentMaps, }); - } - private async assignRoleToNewAgent( - workspaceId: string, - agentId: string, - roleId: string, - ): Promise { - await this.agentRoleService.assignRoleToAgent({ - workspaceId, - agentId, - roleId, - }); - } - - async updateOneAgent(input: UpdateAgentInput, workspaceId: string) { - const agent = await this.findOneAgent(workspaceId, { id: input.id }); - const updateData = this.buildUpdateData(agent, input); - const updatedAgent = await this.agentRepository.save(updateData); - - if (!('roleId' in input)) { - return updatedAgent; - } - - await this.updateAgentRole(workspaceId, agent.id, input.roleId); - - return this.findOneAgent(workspaceId, { id: updatedAgent.id }); - } - - private buildUpdateData( - agent: AgentEntity & { roleId: string | null }, - input: UpdateAgentInput, - ): Partial { - const updateData: Partial = { - ...agent, - ...Object.fromEntries( - Object.entries(input).filter(([_, value]) => value !== undefined), - ), + return { + ...createdAgent, + roleId: flatRoleTargetToCreate?.roleId ?? null, }; - - if (input.label !== undefined) { - updateData.name = computeMetadataNameFromLabel(input.label); - } else if (input.name !== undefined) { - updateData.name = input.name; - } - - return updateData; } - private async updateAgentRole( - workspaceId: string, - agentId: string, - roleId: string | null | undefined, - ): Promise { - if (isNonEmptyString(roleId)) { - await this.agentRoleService.assignRoleToAgent({ - workspaceId, - agentId, - roleId, - }); + async updateOneAgent({ + input, + workspaceId, + }: { + input: UpdateAgentInput; + workspaceId: string; + }): Promise { + const { + flatAgentMaps: existingFlatAgentMaps, + flatRoleTargetMaps: existingFlatRoleTargetMaps, + flatRoleMaps: existingFlatRoleMaps, + flatRoleTargetByAgentIdMaps, + } = await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatAgentMaps', + 'flatRoleTargetMaps', + 'flatRoleMaps', + 'flatRoleTargetByAgentIdMaps', + ]); - return; - } - - await this.agentRoleService.removeRoleFromAgent({ - workspaceId, - agentId, + const { + flatAgentToUpdate, + flatRoleTargetToCreate, + flatRoleTargetToDelete, + flatRoleTargetToUpdate, + } = fromUpdateAgentInputToFlatAgentToUpdate({ + updateAgentInput: input, + flatAgentMaps: existingFlatAgentMaps, + flatRoleTargetByAgentIdMaps, }); + + const validateAndBuildResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + fromToAllFlatEntityMaps: { + flatAgentMaps: computeFlatEntityMapsFromTo({ + flatEntityMaps: existingFlatAgentMaps, + flatEntityToCreate: [], + flatEntityToDelete: [], + flatEntityToUpdate: [flatAgentToUpdate], + }), + flatRoleTargetMaps: computeFlatEntityMapsFromTo({ + flatEntityMaps: existingFlatRoleTargetMaps, + flatEntityToCreate: isDefined(flatRoleTargetToCreate) + ? [flatRoleTargetToCreate] + : [], + flatEntityToDelete: isDefined(flatRoleTargetToDelete) + ? [flatRoleTargetToDelete] + : [], + flatEntityToUpdate: isDefined(flatRoleTargetToUpdate) + ? [flatRoleTargetToUpdate] + : [], + }), + }, + dependencyAllFlatEntityMaps: { flatRoleMaps: existingFlatRoleMaps }, + buildOptions: { + isSystemBuild: false, + inferDeletionFromMissingEntities: { + roleTarget: isDefined(flatRoleTargetToDelete), + }, + }, + workspaceId, + }, + ); + + if (isDefined(validateAndBuildResult)) { + throw new WorkspaceMigrationBuilderExceptionV2( + validateAndBuildResult, + 'Multiple validation errors occurred while updating agent', + ); + } + + const { + flatAgentMaps: recomputedFlatAgentMaps, + flatRoleTargetByAgentIdMaps: recmputedFlatRoleTargetByAgentIdMaps, + } = await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatAgentMaps', + 'flatRoleTargetByAgentIdMaps', + ]); + + const updatedAgent = findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId: input.id, + flatEntityMaps: recomputedFlatAgentMaps, + }); + + const existingRoleTarget = + recmputedFlatRoleTargetByAgentIdMaps[flatAgentToUpdate.id]; + + return { + ...updatedAgent, + roleId: existingRoleTarget?.roleId ?? null, + }; } - async deleteOneAgent(id: string, workspaceId: string) { - const agent = await this.findOneAgent(workspaceId, { id }); + async deleteOneAgent( + id: string, + workspaceId: string, + ): Promise { + const { + flatAgentMaps: existingFlatAgentMaps, + flatRoleTargetByAgentIdMaps, + } = await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatAgentMaps', + 'flatRoleTargetByAgentIdMaps', + ]); - await this.agentRepository.softDelete({ id: agent.id }); + const agentToDelete = findFlatEntityByIdInFlatEntityMaps({ + flatEntityId: id, + flatEntityMaps: existingFlatAgentMaps, + }); - return agent; + if (!isDefined(agentToDelete)) { + throw new AgentException( + `Agent not found`, + AgentExceptionCode.AGENT_NOT_FOUND, + ); + } + + const roleId = flatRoleTargetByAgentIdMaps[agentToDelete.id]?.roleId; + + const validateAndBuildResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + fromToAllFlatEntityMaps: { + flatAgentMaps: computeFlatEntityMapsFromTo({ + flatEntityMaps: existingFlatAgentMaps, + flatEntityToCreate: [], + flatEntityToDelete: [agentToDelete], + flatEntityToUpdate: [], + }), + }, + buildOptions: { + isSystemBuild: false, + inferDeletionFromMissingEntities: { + agent: true, + }, + }, + workspaceId, + }, + ); + + if (isDefined(validateAndBuildResult)) { + throw new WorkspaceMigrationBuilderExceptionV2( + validateAndBuildResult, + 'Multiple validation errors occurred while deleting agent', + ); + } + + return { + ...agentToDelete, + roleId: roleId ?? null, + }; } } diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/ai-agent.module.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/ai-agent.module.ts index fab77c4a76..e54a903cc8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/ai-agent.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/ai-agent.module.ts @@ -1,6 +1,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; import { AuditModule } from 'src/engine/core-modules/audit/audit.module'; import { TokenModule } from 'src/engine/core-modules/auth/token/token.module'; import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module'; @@ -10,18 +11,23 @@ import { FileModule } from 'src/engine/core-modules/file/file.module'; import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.module'; import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module'; import { AiAgentRoleModule } from 'src/engine/metadata-modules/ai/ai-agent-role/ai-agent-role.module'; +import { AgentGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/ai/ai-agent/interceptors/agent-graphql-api-exception.interceptor'; import { AiBillingModule } from 'src/engine/metadata-modules/ai/ai-billing/ai-billing.module'; import { AiChatRouterModule } from 'src/engine/metadata-modules/ai/ai-chat-router/ai-chat-router.module'; import { AiModelsModule } from 'src/engine/metadata-modules/ai/ai-models/ai-models.module'; import { AiToolsModule } from 'src/engine/metadata-modules/ai/ai-tools/ai-tools.module'; +import { FlatAgentModule } from 'src/engine/metadata-modules/flat-agent/flat-agent.module'; +import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module'; import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; -import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; +import { WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/workspace-migration-builder-graphql-api-exception.interceptor'; +import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module'; import { WorkflowToolsModule } from 'src/modules/workflow/workflow-tools/workflow-tools.module'; +import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; import { AgentResolver } from './agent.resolver'; import { AgentService } from './agent.service'; @@ -55,6 +61,11 @@ import { AgentToolGeneratorService } from './services/agent-tool-generator.servi UserWorkspaceModule, UserRoleModule, WorkspaceCacheModule, + WorkspaceManyOrAllFlatEntityMapsCacheModule, + WorkspaceMigrationV2Module, + ApplicationModule, + FlatAgentModule, + WorkspaceCacheModule, ], providers: [ AgentResolver, @@ -65,6 +76,8 @@ import { AgentToolGeneratorService } from './services/agent-tool-generator.servi AgentToolGeneratorService, AgentTitleGenerationService, AgentActorContextService, + WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor, + AgentGraphqlApiExceptionInterceptor, ], exports: [ AgentService, diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto.ts index d52d6cbc32..728968fdc2 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto.ts @@ -21,7 +21,7 @@ export class AgentDTO { id: string; @Field(() => UUIDScalarType, { nullable: true }) - standardId: string | null; + standardId?: string | null; @IsString() @Field() @@ -37,7 +37,7 @@ export class AgentDTO { @IsString() @Field({ nullable: true }) - description: string; + description?: string; @IsString() @IsNotEmpty() @@ -48,7 +48,7 @@ export class AgentDTO { modelId: ModelId; @Field(() => GraphQLJSON, { nullable: true }) - responseFormat: object; + responseFormat?: object; @Field(() => UUIDScalarType, { nullable: true }) roleId?: string; @@ -72,7 +72,7 @@ export class AgentDTO { updatedAt: Date; @Field(() => GraphQLJSON, { nullable: true }) - modelConfiguration: ModelConfiguration; + modelConfiguration?: ModelConfiguration; @Field(() => [String]) evaluationInputs: string[]; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/dtos/create-agent.input.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/dtos/create-agent.input.ts index fe14e66ad5..34d031a123 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/dtos/create-agent.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/dtos/create-agent.input.ts @@ -1,5 +1,6 @@ import { Field, HideField, InputType } from '@nestjs/graphql'; +import { Type } from 'class-transformer'; import { IsArray, IsNotEmpty, @@ -7,12 +8,16 @@ import { IsOptional, IsString, IsUUID, + ValidateNested, } from 'class-validator'; import GraphQLJSON from 'graphql-type-json'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; +import { AgentResponseFormat } from 'src/engine/metadata-modules/ai/ai-agent/types/agent-response-format.type'; import { ModelConfiguration } from 'src/engine/metadata-modules/ai/ai-agent/types/modelConfiguration'; import { ModelId } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const'; +import { AgentResponseFormatJson } from 'src/engine/metadata-modules/ai/ai-agent/validators/agent-response-format-json.validator'; +import { AgentResponseFormatText } from 'src/engine/metadata-modules/ai/ai-agent/validators/agent-response-format-text.validator'; @InputType() export class CreateAgentInput { @@ -51,10 +56,20 @@ export class CreateAgentInput { @Field(() => UUIDScalarType, { nullable: true }) roleId?: string; - @IsObject() @IsOptional() + @ValidateNested() + @Type(() => Object, { + keepDiscriminatorProperty: true, + discriminator: { + property: 'type', + subTypes: [ + { value: AgentResponseFormatText, name: 'text' }, + { value: AgentResponseFormatJson, name: 'json' }, + ], + }, + }) @Field(() => GraphQLJSON, { nullable: true }) - responseFormat?: object; + responseFormat?: AgentResponseFormat; @IsObject() @IsOptional() diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/dtos/update-agent.input.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/dtos/update-agent.input.ts index 3fc71540de..5f149c4007 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/dtos/update-agent.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/dtos/update-agent.input.ts @@ -1,5 +1,6 @@ import { Field, InputType } from '@nestjs/graphql'; +import { Type } from 'class-transformer'; import { IsArray, IsNotEmpty, @@ -7,11 +8,15 @@ import { IsOptional, IsString, IsUUID, + ValidateNested, } from 'class-validator'; import GraphQLJSON from 'graphql-type-json'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; +import { AgentResponseFormat } from 'src/engine/metadata-modules/ai/ai-agent/types/agent-response-format.type'; import { ModelConfiguration } from 'src/engine/metadata-modules/ai/ai-agent/types/modelConfiguration'; +import { AgentResponseFormatJson } from 'src/engine/metadata-modules/ai/ai-agent/validators/agent-response-format-json.validator'; +import { AgentResponseFormatText } from 'src/engine/metadata-modules/ai/ai-agent/validators/agent-response-format-text.validator'; import { ModelId } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const'; @InputType() @@ -54,12 +59,22 @@ export class UpdateAgentInput { @IsUUID() @IsOptional() @Field(() => UUIDScalarType, { nullable: true }) - roleId?: string; + roleId?: string | null; - @IsObject() @IsOptional() + @ValidateNested() + @Type(() => Object, { + keepDiscriminatorProperty: true, + discriminator: { + property: 'type', + subTypes: [ + { value: AgentResponseFormatText, name: 'text' }, + { value: AgentResponseFormatJson, name: 'json' }, + ], + }, + }) @Field(() => GraphQLJSON, { nullable: true }) - responseFormat?: object; + responseFormat?: AgentResponseFormat; @IsObject() @IsOptional() diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/entities/agent.entity.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/entities/agent.entity.ts index 2752d66df5..1e784dbad8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/entities/agent.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/entities/agent.entity.ts @@ -43,11 +43,11 @@ export class AgentEntity @Column({ nullable: false }) label: string; - @Column({ nullable: true }) - icon: string; + @Column({ nullable: true, type: 'varchar' }) + icon: string | null; - @Column({ nullable: true }) - description: string; + @Column({ nullable: true, type: 'text' }) + description: string | null; @Column({ nullable: false, type: 'text' }) prompt: string; @@ -55,6 +55,7 @@ export class AgentEntity @Column({ nullable: false, type: 'varchar', default: DEFAULT_SMART_MODEL }) modelId: ModelId; + // Should not be nullable @Column({ nullable: true, type: 'jsonb', default: { type: 'text' } }) responseFormat: AgentResponseFormat; @@ -80,7 +81,7 @@ export class AgentEntity deletedAt: Date | null; @Column({ nullable: true, type: 'jsonb' }) - modelConfiguration: ModelConfiguration; + modelConfiguration: ModelConfiguration | null; @Column({ type: 'text', array: true, default: '{}' }) evaluationInputs: string[]; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/interceptors/agent-graphql-api-exception.interceptor.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/interceptors/agent-graphql-api-exception.interceptor.ts new file mode 100644 index 0000000000..eac9d1aaa0 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/interceptors/agent-graphql-api-exception.interceptor.ts @@ -0,0 +1,20 @@ +import { + type CallHandler, + type ExecutionContext, + Injectable, + type NestInterceptor, +} from '@nestjs/common'; + +import { type Observable, catchError } from 'rxjs'; + +import { agentGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/ai/ai-agent/utils/agent-graphql-api-exception-handler.util'; + +@Injectable() +export class AgentGraphqlApiExceptionInterceptor implements NestInterceptor { + intercept( + _context: ExecutionContext, + next: CallHandler, + ): Observable { + return next.handle().pipe(catchError(agentGraphqlApiExceptionHandler)); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/services/agent-execution.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/services/agent-execution.service.ts index 62b63d2c96..1c7379fb2a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/services/agent-execution.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/services/agent-execution.service.ts @@ -24,7 +24,6 @@ import { import { AgentService } from 'src/engine/metadata-modules/ai/ai-agent/agent.service'; import { AGENT_CONFIG } from 'src/engine/metadata-modules/ai/ai-agent/constants/agent-config.const'; import { AGENT_SYSTEM_PROMPTS } from 'src/engine/metadata-modules/ai/ai-agent/constants/agent-system-prompts.const'; -import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity'; import { AgentActorContextService } from 'src/engine/metadata-modules/ai/ai-agent/services/agent-actor-context.service'; import { AgentModelConfigService } from 'src/engine/metadata-modules/ai/ai-agent/services/agent-model-config.service'; import { AgentToolGeneratorService } from 'src/engine/metadata-modules/ai/ai-agent/services/agent-tool-generator.service'; @@ -34,6 +33,7 @@ import { AIBillingService } from 'src/engine/metadata-modules/ai/ai-billing/serv import { ToolHints } from 'src/engine/metadata-modules/ai/ai-chat-router/types/tool-hints.interface'; import { AI_TELEMETRY_CONFIG } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-telemetry.const'; import { AiModelRegistryService } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service'; +import { FlatAgentWithRoleId } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; @@ -77,7 +77,7 @@ export class AgentExecutionService { toolHints, }: { system: string; - agent: AgentEntity | null; + agent: FlatAgentWithRoleId | null; messages: UIMessage[]; actorContext?: ActorMetadata; roleIds?: string[]; @@ -300,7 +300,8 @@ export class AgentExecutionService { }; }> { try { - const agent = await this.agentService.findOneAgent(workspace.id, { + const agent = await this.agentService.findOneAgentById({ + workspaceId: workspace.id, id: agentId, }); diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/services/agent-model-config.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/services/agent-model-config.service.ts index 02f25af1e5..8d4348f279 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/services/agent-model-config.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/services/agent-model-config.service.ts @@ -6,9 +6,9 @@ import { ProviderOptions } from '@ai-sdk/provider-utils'; import { ToolSet } from 'ai'; import { AGENT_CONFIG } from 'src/engine/metadata-modules/ai/ai-agent/constants/agent-config.const'; -import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity'; import { ModelProvider } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const'; import { RegisteredAIModel } from 'src/engine/metadata-modules/ai/ai-models/services/ai-model-registry.service'; +import { FlatAgentWithRoleId } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; @Injectable() export class AgentModelConfigService { @@ -16,7 +16,7 @@ export class AgentModelConfigService { getProviderOptions( model: RegisteredAIModel, - agent: AgentEntity, + agent: FlatAgentWithRoleId, ): ProviderOptions { switch (model.provider) { case ModelProvider.XAI: @@ -28,7 +28,10 @@ export class AgentModelConfigService { } } - getNativeModelTools(model: RegisteredAIModel, agent: AgentEntity): ToolSet { + getNativeModelTools( + model: RegisteredAIModel, + agent: FlatAgentWithRoleId, + ): ToolSet { const tools: ToolSet = {}; if (!agent.modelConfiguration) { @@ -51,7 +54,7 @@ export class AgentModelConfigService { return tools; } - private getXaiProviderOptions(agent: AgentEntity): ProviderOptions { + private getXaiProviderOptions(agent: FlatAgentWithRoleId): ProviderOptions { if ( !agent.modelConfiguration || (!agent.modelConfiguration.webSearch?.enabled && diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/services/agent-plan-executor.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/services/agent-plan-executor.service.ts index 0cfb654778..0f716bcd9d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/services/agent-plan-executor.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/services/agent-plan-executor.service.ts @@ -71,10 +71,10 @@ export class AgentPlanExecutorService { ); const agent = - await this.agentExecutionService.agentService.findOneAgent( - workspace.id, - { name: step.agentName }, - ); + await this.agentExecutionService.agentService.findOneAgentByName({ + name: step.agentName, + workspaceId: workspace.id, + }); this.logger.log( `[PLAN EXECUTION] Step ${step.stepNumber}: Found agent "${agent.label}" (${agent.id})`, diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/types/agent-response-format.type.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/types/agent-response-format.type.ts index 17f6086018..d0c2a4eb64 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/types/agent-response-format.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/types/agent-response-format.type.ts @@ -1,10 +1,12 @@ import { type AgentResponseSchema } from 'twenty-shared/ai'; -export type AgentResponseFormatType = 'text' | 'json'; +export type AgentResponseFormatType = AgentResponseFormat['type']; +export type AgentTextResponseFormat = { type: 'text' }; +export type AgentJsonResponseFormat = { + type: 'json'; + schema: AgentResponseSchema; +}; export type AgentResponseFormat = - | { type: 'text' } - | { - type: 'json'; - schema: AgentResponseSchema; - }; + | AgentTextResponseFormat + | AgentJsonResponseFormat; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/agent-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/agent-graphql-api-exception-handler.util.ts new file mode 100644 index 0000000000..7cd7181575 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/agent-graphql-api-exception-handler.util.ts @@ -0,0 +1,38 @@ +import { assertUnreachable } from 'twenty-shared/utils'; + +import { + ConflictError, + ForbiddenError, + NotFoundError, + UserInputError, +} from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; +import { + AgentException, + AgentExceptionCode, +} from 'src/engine/metadata-modules/ai/ai-agent/agent.exception'; + +export const agentGraphqlApiExceptionHandler = (error: Error) => { + if (error instanceof AgentException) { + switch (error.code) { + case AgentExceptionCode.AGENT_NOT_FOUND: + case AgentExceptionCode.ROLE_NOT_FOUND: + throw new NotFoundError(error); + case AgentExceptionCode.INVALID_AGENT_INPUT: + throw new UserInputError(error); + case AgentExceptionCode.AGENT_ALREADY_EXISTS: + throw new ConflictError(error); + case AgentExceptionCode.AGENT_IS_STANDARD: + case AgentExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_AGENTS: + throw new ForbiddenError(error); + case AgentExceptionCode.AGENT_EXECUTION_FAILED: + case AgentExceptionCode.API_KEY_NOT_CONFIGURED: + case AgentExceptionCode.USER_WORKSPACE_ID_NOT_FOUND: + throw error; + default: { + return assertUnreachable(error.code); + } + } + } + + throw error; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/from-create-agent-input-to-flat-agent.util.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/from-create-agent-input-to-flat-agent.util.ts new file mode 100644 index 0000000000..c79390693e --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/from-create-agent-input-to-flat-agent.util.ts @@ -0,0 +1,88 @@ +import { isNonEmptyString } from '@sniptt/guards'; +import { + isDefined, + trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties, +} from 'twenty-shared/utils'; +import { v4 } from 'uuid'; +import { computeMetadataNameFromLabel } from 'twenty-shared/metadata'; + +import { type CreateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/create-agent.input'; +import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; +import { type FlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/types/flat-role-target.type'; + +export type FromCreateAgentInputToFlatAgentArgs = { + createAgentInput: CreateAgentInput & { applicationId: string }; + workspaceId: string; +}; + +export const fromCreateAgentInputToFlatAgent = ({ + createAgentInput: rawCreateAgentInput, + workspaceId, +}: FromCreateAgentInputToFlatAgentArgs): { + flatAgentToCreate: FlatAgent; + flatRoleTargetToCreate: FlatRoleTarget | null; +} => { + const { roleId, ...createAgentInput } = + trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( + rawCreateAgentInput, + [ + 'name', + 'label', + 'icon', + 'description', + 'prompt', + 'modelId', + 'standardId', + 'applicationId', + 'roleId', + ], + ); + + const createdAt = new Date(); + const agentId = v4(); + const standardId = createAgentInput.standardId ?? null; + const universalIdentifier = standardId ?? agentId; + + const flatAgentToCreate: FlatAgent = { + id: agentId, + standardId, + name: isNonEmptyString(createAgentInput.name) + ? createAgentInput.name + : computeMetadataNameFromLabel(createAgentInput.label), + label: createAgentInput.label, + icon: createAgentInput.icon ?? null, + description: createAgentInput.description ?? null, + prompt: createAgentInput.prompt, + modelId: createAgentInput.modelId, + responseFormat: createAgentInput.responseFormat ?? { type: 'text' }, + workspaceId, + isCustom: true, + universalIdentifier, + applicationId: createAgentInput.applicationId, + modelConfiguration: createAgentInput.modelConfiguration ?? null, + evaluationInputs: createAgentInput.evaluationInputs ?? [], + createdAt, + updatedAt: createdAt, + deletedAt: null, + }; + + const flatRoleTargetToCreate: FlatRoleTarget | null = isDefined(roleId) + ? { + id: v4(), + roleId, + userWorkspaceId: null, + agentId, + apiKeyId: null, + createdAt, + updatedAt: createdAt, + universalIdentifier: v4(), + workspaceId, + applicationId: createAgentInput.applicationId, + } + : null; + + return { + flatAgentToCreate, + flatRoleTargetToCreate, + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/from-update-agent-input-to-flat-agent-to-update.util.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/from-update-agent-input-to-flat-agent-to-update.util.ts new file mode 100644 index 0000000000..e6f071a123 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/utils/from-update-agent-input-to-flat-agent-to-update.util.ts @@ -0,0 +1,146 @@ +import { msg } from '@lingui/core/macro'; +import { + extractAndSanitizeObjectStringFields, + isDefined, + trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties, +} from 'twenty-shared/utils'; +import { v4 } from 'uuid'; + +import { + AgentException, + AgentExceptionCode, +} from 'src/engine/metadata-modules/ai/ai-agent/agent.exception'; +import { type UpdateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/update-agent.input'; +import { FLAT_AGENT_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-agent/constants/flat-agent-editable-properties.constant'; +import { type FlatAgentMaps } from 'src/engine/metadata-modules/flat-agent/types/flat-agent-maps.type'; +import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; +import { type FlatRoleTargetByAgentIdMaps } from 'src/engine/metadata-modules/flat-agent/types/flat-role-target-by-agent-id-maps.type'; +import { type FlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/types/flat-role-target.type'; +import { computeMetadataNameFromLabelOrThrow } from 'src/engine/metadata-modules/utils/compute-metadata-name-from-label-or-throw.util'; +import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util'; + +type FlatRoleTargetToUpdateCreateDelete = { + flatRoleTargetToUpdate?: FlatRoleTarget; + flatRoleTargetToCreate?: FlatRoleTarget; + flatRoleTargetToDelete?: FlatRoleTarget; +}; +const computeAgentFlatRoleTargetToUpdate = ({ + roleId, + flatAgent, + flatRoleTargetByAgentIdMaps, +}: { + roleId: string | null | undefined; + flatRoleTargetByAgentIdMaps: FlatRoleTargetByAgentIdMaps; + flatAgent: FlatAgent; +}): FlatRoleTargetToUpdateCreateDelete => { + if (roleId === undefined) { + return {}; + } + + const existingRoleTarget = flatRoleTargetByAgentIdMaps[flatAgent.id]; + const updatedAt = new Date(); + + if (roleId === null) { + if (isDefined(existingRoleTarget)) { + return { + flatRoleTargetToDelete: existingRoleTarget, + }; + } + + return {}; + } + + if (isDefined(existingRoleTarget)) { + return { + flatRoleTargetToUpdate: { + ...existingRoleTarget, + roleId, + updatedAt, + }, + }; + } + + return { + flatRoleTargetToCreate: { + id: v4(), + roleId, + userWorkspaceId: null, + agentId: flatAgent.id, + apiKeyId: null, + createdAt: updatedAt, + updatedAt, + universalIdentifier: v4(), + workspaceId: flatAgent.workspaceId, + applicationId: flatAgent.applicationId, + }, + }; +}; + +export type FromUpdateAgentInputToFlatAgentToUpdateArgs = { + updateAgentInput: UpdateAgentInput; + flatAgentMaps: FlatAgentMaps; + flatRoleTargetByAgentIdMaps: FlatRoleTargetByAgentIdMaps; +}; + +export const fromUpdateAgentInputToFlatAgentToUpdate = ({ + updateAgentInput: rawUpdateAgentInput, + flatAgentMaps, + flatRoleTargetByAgentIdMaps, +}: FromUpdateAgentInputToFlatAgentToUpdateArgs): { + flatAgentToUpdate: FlatAgent; +} & FlatRoleTargetToUpdateCreateDelete => { + const { id: agentIdToUpdate } = + trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties( + rawUpdateAgentInput, + ['id'], + ); + + const existingFlatAgent = flatAgentMaps.byId[agentIdToUpdate]; + + if (!isDefined(existingFlatAgent)) { + throw new AgentException( + 'Agent not found', + AgentExceptionCode.AGENT_NOT_FOUND, + { + userFriendlyMessage: msg`The agent you are looking for could not be found. It may have been deleted or you may not have access to it.`, + }, + ); + } + + const updatedEditableAgentProperties = extractAndSanitizeObjectStringFields( + rawUpdateAgentInput, + FLAT_AGENT_EDITABLE_PROPERTIES, + ); + + if ( + isDefined(updatedEditableAgentProperties.label) && + !isDefined(updatedEditableAgentProperties.name) + ) { + updatedEditableAgentProperties.name = computeMetadataNameFromLabelOrThrow( + updatedEditableAgentProperties.label, + ); + } + + const flatAgentToUpdate: FlatAgent = mergeUpdateInExistingRecord({ + existing: existingFlatAgent, + properties: FLAT_AGENT_EDITABLE_PROPERTIES, + update: updatedEditableAgentProperties, + }); + + const { + flatRoleTargetToUpdate, + flatRoleTargetToCreate, + flatRoleTargetToDelete, + } = computeAgentFlatRoleTargetToUpdate({ + roleId: rawUpdateAgentInput.roleId, + flatAgent: existingFlatAgent, + flatRoleTargetByAgentIdMaps, + }); + + return { + flatAgentToUpdate, + flatRoleTargetToUpdate, + flatRoleTargetToCreate, + flatRoleTargetToDelete, + }; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/validators/agent-response-format-json.validator.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/validators/agent-response-format-json.validator.ts new file mode 100644 index 0000000000..aecb086541 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/validators/agent-response-format-json.validator.ts @@ -0,0 +1,10 @@ +import { IsEnum, IsNotEmpty, IsObject } from 'class-validator'; + +export class AgentResponseFormatJson { + @IsEnum(['json']) + type: 'json'; + + @IsObject() + @IsNotEmpty() + schema: Record; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/validators/agent-response-format-text.validator.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/validators/agent-response-format-text.validator.ts new file mode 100644 index 0000000000..23b0d85674 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/validators/agent-response-format-text.validator.ts @@ -0,0 +1,6 @@ +import { IsEnum } from 'class-validator'; + +export class AgentResponseFormatText { + @IsEnum(['text']) + type: 'text'; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-agent/constants/flat-agent-editable-properties.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-agent/constants/flat-agent-editable-properties.constant.ts new file mode 100644 index 0000000000..9bd250068f --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-agent/constants/flat-agent-editable-properties.constant.ts @@ -0,0 +1,13 @@ +import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; + +export const FLAT_AGENT_EDITABLE_PROPERTIES = [ + 'name', + 'label', + 'icon', + 'description', + 'prompt', + 'modelId', + 'responseFormat', + 'modelConfiguration', + 'evaluationInputs', +] as const satisfies (keyof FlatAgent)[]; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-agent/flat-agent.module.ts b/packages/twenty-server/src/engine/metadata-modules/flat-agent/flat-agent.module.ts new file mode 100644 index 0000000000..354b990ebc --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-agent/flat-agent.module.ts @@ -0,0 +1,24 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity'; +import { WorkspaceFlatAgentMapCacheService } from 'src/engine/metadata-modules/flat-agent/services/workspace-flat-agent-map-cache.service'; +import { WorkspaceFlatRoleTargetByAgentIdService } from 'src/engine/metadata-modules/flat-agent/services/workspace-flat-role-target-by-agent-id.service'; +import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; +import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([AgentEntity, RoleTargetEntity]), + WorkspaceManyOrAllFlatEntityMapsCacheModule, + ], + providers: [ + WorkspaceFlatAgentMapCacheService, + WorkspaceFlatRoleTargetByAgentIdService, + ], + exports: [ + WorkspaceFlatAgentMapCacheService, + WorkspaceFlatRoleTargetByAgentIdService, + ], +}) +export class FlatAgentModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-agent/services/workspace-flat-agent-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/flat-agent/services/workspace-flat-agent-map-cache.service.ts new file mode 100644 index 0000000000..8950c12f1c --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-agent/services/workspace-flat-agent-map-cache.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { Repository } from 'typeorm'; + +import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service'; + +import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity'; +import { type FlatAgentMaps } from 'src/engine/metadata-modules/flat-agent/types/flat-agent-maps.type'; +import { transformAgentEntityToFlatAgent } from 'src/engine/metadata-modules/flat-agent/utils/transform-agent-entity-to-flat-agent.util'; +import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant'; +import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator'; +import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util'; + +@Injectable() +@WorkspaceCache('flatAgentMaps') +export class WorkspaceFlatAgentMapCacheService extends WorkspaceCacheProvider { + constructor( + @InjectRepository(AgentEntity) + private readonly agentRepository: Repository, + ) { + super(); + } + + async computeForCache(workspaceId: string): Promise { + const agents = await this.agentRepository.find({ + where: { workspaceId }, + withDeleted: true, + }); + + const flatAgentMaps = createEmptyFlatEntityMaps(); + + for (const agentEntity of agents) { + const flatAgent = transformAgentEntityToFlatAgent(agentEntity); + + addFlatEntityToFlatEntityMapsThroughMutationOrThrow({ + flatEntity: flatAgent, + flatEntityMapsToMutate: flatAgentMaps, + }); + } + + return flatAgentMaps; + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-agent/services/workspace-flat-role-target-by-agent-id.service.ts b/packages/twenty-server/src/engine/metadata-modules/flat-agent/services/workspace-flat-role-target-by-agent-id.service.ts new file mode 100644 index 0000000000..d44a6b089c --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-agent/services/workspace-flat-role-target-by-agent-id.service.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; + +import { NonNullableRequired } from 'twenty-shared/types'; +import { IsNull, Not, Repository } from 'typeorm'; + +import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/workspace-cache-provider.service'; + +import { FlatRoleTargetByAgentIdMaps } from 'src/engine/metadata-modules/flat-agent/types/flat-role-target-by-agent-id-maps.type'; +import { fromRoleTargetsEntityToFlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/utils/from-role-target-entity-to-flat-role-target.util'; +import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity'; +import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator'; + +@Injectable() +@WorkspaceCache('flatRoleTargetByAgentIdMaps') +export class WorkspaceFlatRoleTargetByAgentIdService extends WorkspaceCacheProvider { + constructor( + @InjectRepository(RoleTargetEntity) + private readonly roleTargetRepository: Repository, + ) { + super(); + } + + async computeForCache( + workspaceId: string, + ): Promise { + const roleTargetEntities = await this.roleTargetRepository.find({ + where: { + workspaceId, + agentId: Not(IsNull()), + }, + withDeleted: true, + }); + + const flatRoleTargetByAgentIdMaps: FlatRoleTargetByAgentIdMaps = {}; + + for (const roleTargetEntity of roleTargetEntities as Array< + Omit & + NonNullableRequired> + >) { + const flatRoleTarget = + fromRoleTargetsEntityToFlatRoleTarget(roleTargetEntity); + + flatRoleTargetByAgentIdMaps[roleTargetEntity.agentId] = flatRoleTarget; + } + + return flatRoleTargetByAgentIdMaps; + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-agent/types/flat-agent-maps.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-agent/types/flat-agent-maps.type.ts new file mode 100644 index 0000000000..6c7241e0e3 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-agent/types/flat-agent-maps.type.ts @@ -0,0 +1,4 @@ +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; + +export type FlatAgentMaps = FlatEntityMaps; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-agent/types/flat-agent.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-agent/types/flat-agent.type.ts index 14350d8f6f..ae1b8eca3d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-agent/types/flat-agent.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-agent/types/flat-agent.type.ts @@ -11,5 +11,7 @@ export type AgentEntityRelationProperties = export type FlatAgent = FlatEntityFrom< AgentEntity, - AgentEntityRelationProperties | 'createdAt' | 'updatedAt' | 'deletedAt' + AgentEntityRelationProperties >; + +export type FlatAgentWithRoleId = FlatAgent & { roleId: string | null }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-agent/types/flat-role-target-by-agent-id-maps.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-agent/types/flat-role-target-by-agent-id-maps.type.ts new file mode 100644 index 0000000000..f48b3538d5 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-agent/types/flat-role-target-by-agent-id-maps.type.ts @@ -0,0 +1,5 @@ +import { type FlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/types/flat-role-target.type'; + +export type FlatRoleTargetByAgentIdMaps = Partial< + Record +>; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/from-agent-entity-to-agent-dto.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/from-agent-entity-to-agent-dto.util.ts new file mode 100644 index 0000000000..6785d655ee --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/from-agent-entity-to-agent-dto.util.ts @@ -0,0 +1,40 @@ +import { type AgentDTO } from 'src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto'; +import { type FlatAgentWithRoleId } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; + +export const fromFlatAgentWithRoleIdToAgentDto = ({ + applicationId, + createdAt, + description, + evaluationInputs, + icon, + id, + isCustom, + label, + modelConfiguration, + modelId, + name, + prompt, + responseFormat, + standardId, + updatedAt, + workspaceId, + roleId, +}: FlatAgentWithRoleId): AgentDTO => ({ + createdAt, + description: description ?? undefined, + evaluationInputs, + id, + isCustom, + label, + modelConfiguration: modelConfiguration ?? undefined, + modelId, + name, + prompt, + responseFormat, + standardId, + updatedAt, + workspaceId, + applicationId: applicationId ?? undefined, + icon: icon ?? undefined, + roleId: roleId ?? undefined, +}); diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/transform-agent-entity-to-flat-agent.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/transform-agent-entity-to-flat-agent.util.ts index f315f8d8c2..04b5b18734 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/transform-agent-entity-to-flat-agent.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/transform-agent-entity-to-flat-agent.util.ts @@ -5,6 +5,9 @@ export const transformAgentEntityToFlatAgent = ( agentEntity: AgentEntity, ): FlatAgent => { return { + createdAt: agentEntity.createdAt, + deletedAt: agentEntity.deletedAt, + updatedAt: agentEntity.updatedAt, id: agentEntity.id, standardId: agentEntity.standardId, name: agentEntity.name, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/transform-standard-agent-definition-to-flat-agent.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/transform-standard-agent-definition-to-flat-agent.util.ts index b8488d4eb4..5273d1749a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/transform-standard-agent-definition-to-flat-agent.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/transform-standard-agent-definition-to-flat-agent.util.ts @@ -1,22 +1,35 @@ import { v4 } from 'uuid'; +import { type AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity'; import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; import { type StandardAgentDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/types/standard-agent-definition.interface'; -export const transformStandardAgentDefinitionToFlatAgent = ( - standardAgentDefinition: StandardAgentDefinition, - workspaceId: string, -): FlatAgent => { +export const transformStandardAgentDefinitionToFlatAgent = ({ + standardAgentDefinition, + workspaceId, + existingAgentEntity, +}: { + standardAgentDefinition: StandardAgentDefinition; + workspaceId: string; + existingAgentEntity?: Pick< + AgentEntity, + 'createdAt' | 'deletedAt' | 'updatedAt' | 'id' + >; +}): FlatAgent => { const { standardRoleId: _standardRoleId, outputStrategy: _outputStrategy, ...agentData } = standardAgentDefinition; + const createdAt = new Date(); return { + id: existingAgentEntity?.id ?? v4(), + createdAt: existingAgentEntity?.createdAt ?? createdAt, + updatedAt: existingAgentEntity?.updatedAt ?? createdAt, + deletedAt: existingAgentEntity?.deletedAt ?? null, ...agentData, - id: v4(), workspaceId, - universalIdentifier: standardAgentDefinition.standardId || v4(), + universalIdentifier: standardAgentDefinition.standardId, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-flat-entity-properties-to-compare-and-stringify.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-flat-entity-properties-to-compare-and-stringify.constant.ts index 6b3ee7f6c2..57da03c534 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-flat-entity-properties-to-compare-and-stringify.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-flat-entity-properties-to-compare-and-stringify.constant.ts @@ -2,6 +2,7 @@ import { type AllMetadataName } from 'twenty-shared/metadata'; import { FLAT_CRON_TRIGGER_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/cron-trigger/constants/flat-cron-trigger-editable-properties.constant'; import { FLAT_DATABASE_EVENT_TRIGGER_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/database-event-trigger/constants/flat-database-event-trigger-editable-properties.constant'; +import { FLAT_AGENT_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-agent/constants/flat-agent-editable-properties.constant'; import { type MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type'; import { FLAT_FIELD_METADATA_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-field-metadata/constants/flat-field-metadata-editable-properties.constant'; import { FLAT_OBJECT_METADATA_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-object-metadata/constants/flat-object-metadata-editable-properties.constant'; @@ -97,6 +98,14 @@ export const ALL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY = { propertiesToCompare: [...FLAT_ROLE_TARGET_EDITABLE_PROPERTIES], propertiesToStringify: [], }, + agent: { + propertiesToCompare: [...FLAT_AGENT_EDITABLE_PROPERTIES], + propertiesToStringify: [ + 'responseFormat', + 'modelConfiguration', + 'evaluationInputs', + ], + }, } as const satisfies { [P in AllMetadataName]: OneFlatEntityConfiguration

; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-many-to-one-relations.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-many-to-one-relations.constant.ts index 469b7b6830..a79c3895ba 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-many-to-one-relations.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-many-to-one-relations.constant.ts @@ -112,4 +112,5 @@ export const ALL_METADATA_RELATED_METADATA_BY_FOREIGN_KEY = { flatEntityForeignKeyAggregator: 'roleTargetIds', }, }, + agent: {}, } as const satisfies MetadataNameAndRelations; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-name.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-name.constant.ts deleted file mode 100644 index ac002623e3..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-name.constant.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const ALL_METADATA_NAME = { - fieldMetadata: 'fieldMetadata', - objectMetadata: 'objectMetadata', - view: 'view', - viewField: 'viewField', - viewGroup: 'viewGroup', - index: 'index', - serverlessFunction: 'serverlessFunction', - cronTrigger: 'cronTrigger', - databaseEventTrigger: 'databaseEventTrigger', - routeTrigger: 'routeTrigger', - viewFilter: 'viewFilter', - role: 'role', - roleTarget: 'roleTarget', -} as const; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant.ts index bd20f06ed8..71f7eb12c1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-metadata-required-metadata-for-validation.constant.ts @@ -53,4 +53,5 @@ export const ALL_METADATA_REQUIRED_METADATA_FOR_VALIDATION = { roleTarget: { role: true, }, + agent: {}, } as const satisfies MetadataRequiredForValidation; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module.ts index 5497448f8a..d5d418a42b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module.ts @@ -1,12 +1,57 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { WorkspaceFlatFieldMetadataMapCacheService } from 'src/engine/metadata-modules/flat-field-metadata/services/workspace-flat-field-metadata-map-cache.service'; +import { WorkspaceFlatIndexMapCacheService } from 'src/engine/metadata-modules/flat-index-metadata/services/workspace-flat-index-map-cache.service'; +import { WorkspaceFlatObjectMetadataMapCacheService } from 'src/engine/metadata-modules/flat-object-metadata/services/workspace-flat-object-metadata-map-cache.service'; +import { WorkspaceFlatViewFieldMapCacheService } from 'src/engine/metadata-modules/flat-view-field/services/workspace-flat-view-field-map-cache.service'; +import { WorkspaceFlatViewFilterMapCacheService } from 'src/engine/metadata-modules/flat-view-filter/services/workspace-flat-view-filter-map-cache.service'; +import { WorkspaceFlatViewGroupMapCacheService } from 'src/engine/metadata-modules/flat-view-group/services/workspace-flat-view-group-map-cache.service'; +import { WorkspaceFlatViewMapCacheService } from 'src/engine/metadata-modules/flat-view/services/workspace-flat-view-map-cache.service'; +import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity'; +import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity'; +import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity'; +import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity'; +import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; -import { WorkspaceFlatMapCacheModule } from 'src/engine/workspace-flat-map-cache/workspace-flat-map-cache.module'; @Module({ - imports: [WorkspaceFlatMapCacheModule, WorkspaceCacheModule], - providers: [WorkspaceManyOrAllFlatEntityMapsCacheService], - exports: [WorkspaceManyOrAllFlatEntityMapsCacheService], + imports: [ + WorkspaceCacheModule, + TypeOrmModule.forFeature([ + ViewEntity, + ViewFieldEntity, + ViewFilterEntity, + ViewGroupEntity, + IndexMetadataEntity, + IndexFieldMetadataEntity, + FieldMetadataEntity, + ObjectMetadataEntity, + ]), + ], + providers: [ + WorkspaceManyOrAllFlatEntityMapsCacheService, + WorkspaceFlatObjectMetadataMapCacheService, + WorkspaceFlatViewMapCacheService, + WorkspaceFlatViewFieldMapCacheService, + WorkspaceFlatViewFilterMapCacheService, + WorkspaceFlatIndexMapCacheService, + WorkspaceFlatFieldMetadataMapCacheService, + WorkspaceFlatViewGroupMapCacheService, + ], + exports: [ + WorkspaceManyOrAllFlatEntityMapsCacheService, + WorkspaceFlatObjectMetadataMapCacheService, + WorkspaceFlatViewMapCacheService, + WorkspaceFlatViewFieldMapCacheService, + WorkspaceFlatViewFilterMapCacheService, + WorkspaceFlatIndexMapCacheService, + WorkspaceFlatFieldMetadataMapCacheService, + WorkspaceFlatViewGroupMapCacheService, + ], }) export class WorkspaceManyOrAllFlatEntityMapsCacheModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name.ts index 4592b42541..2f8034ea53 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/all-flat-entity-types-by-metadata-name.ts @@ -1,8 +1,10 @@ +import { type AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity'; import { type CronTriggerEntity } from 'src/engine/metadata-modules/cron-trigger/entities/cron-trigger.entity'; import { type FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type'; import { type DatabaseEventTriggerEntity } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity'; import { type FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/database-event-trigger/types/flat-database-event-trigger.type'; import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; @@ -24,6 +26,11 @@ import { type ViewFieldEntity } from 'src/engine/metadata-modules/view-field/ent import { type ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity'; import { type ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity'; import { type ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; +import { + type CreateAgentAction, + type DeleteAgentAction, + type UpdateAgentAction, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/types/workspace-migration-v2-agent-action-builder.service'; import { type CreateCronTriggerAction, type DeleteCronTriggerAction, @@ -207,4 +214,13 @@ export type AllFlatEntityTypesByMetadataName = { flatEntity: FlatRoleTarget; entity: RoleTargetEntity; }; + agent: { + actions: { + created: CreateAgentAction; + updated: UpdateAgentAction; + deleted: DeleteAgentAction; + }; + flatEntity: FlatAgent; + entity: AgentEntity; + }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/all-metadata-name.type.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/all-metadata-name.type.ts deleted file mode 100644 index 9e42c67ec7..0000000000 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/all-metadata-name.type.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { type ALL_METADATA_NAME } from 'src/engine/metadata-modules/flat-entity/constant/all-metadata-name.constant'; - -export type AllMetadataName = keyof typeof ALL_METADATA_NAME; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/services/workspace-flat-field-metadata-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/services/workspace-flat-field-metadata-map-cache.service.ts index c21b775aa5..637ae6b4f0 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/services/workspace-flat-field-metadata-map-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/services/workspace-flat-field-metadata-map-cache.service.ts @@ -15,7 +15,7 @@ import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entiti import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity'; import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator'; -import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-flat-map-cache/utils/regroup-entities-by-related-entity-id'; +import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id'; import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util'; @Injectable() diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/workspace-flat-object-metadata-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/workspace-flat-object-metadata-map-cache.service.ts index 2d1bda0a58..057658ce57 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/workspace-flat-object-metadata-map-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/services/workspace-flat-object-metadata-map-cache.service.ts @@ -14,7 +14,7 @@ import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator'; -import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-flat-map-cache/utils/regroup-entities-by-related-entity-id'; +import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id'; import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util'; @Injectable() diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-view/services/workspace-flat-view-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/flat-view/services/workspace-flat-view-map-cache.service.ts index 0f60ebf348..5e10cbdcf4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-view/services/workspace-flat-view-map-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-view/services/workspace-flat-view-map-cache.service.ts @@ -13,7 +13,7 @@ import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entiti import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity'; import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator'; -import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-flat-map-cache/utils/regroup-entities-by-related-entity-id'; +import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id'; import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util'; @Injectable() diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts index 48b42643cf..daa64c843d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts @@ -7,6 +7,7 @@ import { FileModule } from 'src/engine/core-modules/file/file.module'; import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module'; import { AiAgentRoleModule } from 'src/engine/metadata-modules/ai/ai-agent-role/ai-agent-role.module'; +import { FlatAgentModule } from 'src/engine/metadata-modules/flat-agent/flat-agent.module'; import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; import { WorkspaceFlatRoleTargetMapCacheService } from 'src/engine/metadata-modules/flat-role-target/services/workspace-flat-role-target-map-cache.service'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; @@ -51,6 +52,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa FileModule, ApplicationModule, WorkspaceCacheModule, + FlatAgentModule, ], providers: [ RoleService, diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts index d36ddb33a8..6e5d578cc3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts @@ -32,6 +32,7 @@ import { UserAuthGuard } from 'src/engine/guards/user-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { AiAgentRoleService } from 'src/engine/metadata-modules/ai/ai-agent-role/ai-agent-role.service'; import { AgentDTO } from 'src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto'; +import { fromFlatAgentWithRoleIdToAgentDto } from 'src/engine/metadata-modules/flat-agent/utils/from-agent-entity-to-agent-dto.util'; import { FieldPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/field-permission.dto'; import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto'; import { UpsertFieldPermissionsInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-field-permissions.input'; @@ -284,10 +285,13 @@ export class RoleResolver { workspace.id, ); - return agents.map((agent) => ({ - ...agent, - applicationId: agent.applicationId ?? undefined, - })); + return agents.map((agentEntity) => + fromFlatAgentWithRoleIdToAgentDto({ + ...agentEntity, + universalIdentifier: agentEntity.universalIdentifier ?? agentEntity.id, + roleId: role.id, + }), + ); } @ResolveField('apiKeys', () => [ApiKeyForRoleDTO]) diff --git a/packages/twenty-server/src/engine/metadata-modules/role/services/workspace-flat-role-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/role/services/workspace-flat-role-map-cache.service.ts index 65fccf4ad3..b482677787 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/services/workspace-flat-role-map-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/services/workspace-flat-role-map-cache.service.ts @@ -15,7 +15,7 @@ import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-fla import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator'; -import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-flat-map-cache/utils/regroup-entities-by-related-entity-id'; +import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id'; import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util'; @Injectable() diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/services/workspace-flat-serverless-function-map-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/services/workspace-flat-serverless-function-map-cache.service.ts index 1c279cba61..7148afa162 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/services/workspace-flat-serverless-function-map-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/services/workspace-flat-serverless-function-map-cache.service.ts @@ -14,7 +14,7 @@ import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless import { FlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/types/flat-serverless-function.type'; import { fromServerlessFunctionEntityToFlatServerlessFunction } from 'src/engine/metadata-modules/serverless-function/utils/from-serverless-function-entity-to-flat-serverless-function.type'; import { WorkspaceCache } from 'src/engine/workspace-cache/decorators/workspace-cache.decorator'; -import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-flat-map-cache/utils/regroup-entities-by-related-entity-id'; +import { regroupEntitiesByRelatedEntityId } from 'src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id'; import { addFlatEntityToFlatEntityMapsThroughMutationOrThrow } from 'src/engine/workspace-manager/workspace-migration-v2/utils/add-flat-entity-to-flat-entity-maps-through-mutation-or-throw.util'; @Injectable() diff --git a/packages/twenty-server/src/engine/workspace-cache/types/workspace-cache-key.type.ts b/packages/twenty-server/src/engine/workspace-cache/types/workspace-cache-key.type.ts index 113cf58896..7ccfdbf0b2 100644 --- a/packages/twenty-server/src/engine/workspace-cache/types/workspace-cache-key.type.ts +++ b/packages/twenty-server/src/engine/workspace-cache/types/workspace-cache-key.type.ts @@ -2,6 +2,7 @@ import { type ObjectsPermissionsByRoleId } from 'twenty-shared/types'; import { type FlatApplicationCacheMaps } from 'src/engine/core-modules/application/types/flat-application-cache-maps.type'; import { type FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { type FlatRoleTargetByAgentIdMaps } from 'src/engine/metadata-modules/flat-agent/types/flat-role-target-by-agent-id-maps.type'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; import { type UserWorkspaceRoleMap } from 'src/engine/metadata-modules/role-target/services/workspace-user-workspace-role-map-cache.service'; @@ -24,6 +25,8 @@ export const WORKSPACE_CACHE_KEYS_V2 = { flatApplicationMaps: 'flat-maps:flatApplicationMaps', flatRoleMaps: 'flat-maps:role', flatRoleTargetMaps: 'flat-maps:role-target', + flatAgentMaps: 'flat-maps:agent', + flatRoleTargetByAgentIdMaps: 'flat-maps:flatRoleTargetByAgentId', } as const satisfies Record; type AdditionalCacheDataMap = { @@ -32,6 +35,7 @@ type AdditionalCacheDataMap = { userWorkspaceRoleMap: UserWorkspaceRoleMap; apiKeyRoleMap: Record; flatApplicationMaps: FlatApplicationCacheMaps; + flatRoleTargetByAgentIdMaps: FlatRoleTargetByAgentIdMaps; }; export type WorkspaceCacheDataMap = AllFlatEntityMaps & AdditionalCacheDataMap; diff --git a/packages/twenty-server/src/engine/workspace-flat-map-cache/utils/regroup-entities-by-related-entity-id.ts b/packages/twenty-server/src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id.ts similarity index 100% rename from packages/twenty-server/src/engine/workspace-flat-map-cache/utils/regroup-entities-by-related-entity-id.ts rename to packages/twenty-server/src/engine/workspace-cache/utils/regroup-entities-by-related-entity-id.ts diff --git a/packages/twenty-server/src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator.ts b/packages/twenty-server/src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator.ts deleted file mode 100644 index 519326ae78..0000000000 --- a/packages/twenty-server/src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; - -import { type AllMetadataName } from 'twenty-shared/metadata'; - -import { type MetadataToFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/types/metadata-to-flat-entity-maps-key'; - -export const WORKSPACE_FLAT_MAP_CACHE_KEY = 'workspaceFlatMapCacheKey'; - -export const WorkspaceFlatMapCache = ( - cacheKey: MetadataToFlatEntityMapsKey, -) => SetMetadata(WORKSPACE_FLAT_MAP_CACHE_KEY, cacheKey); diff --git a/packages/twenty-server/src/engine/workspace-flat-map-cache/exceptions/workspace-flat-map-cache.exception.ts b/packages/twenty-server/src/engine/workspace-flat-map-cache/exceptions/workspace-flat-map-cache.exception.ts deleted file mode 100644 index 22ccf033bd..0000000000 --- a/packages/twenty-server/src/engine/workspace-flat-map-cache/exceptions/workspace-flat-map-cache.exception.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { - appendCommonExceptionCode, - CustomException, -} from 'src/utils/custom-exception'; - -export class WorkspaceFlatMapCacheException extends CustomException< - keyof typeof WorkspaceFlatMapCacheExceptionCode -> {} - -export const WorkspaceFlatMapCacheExceptionCode = appendCommonExceptionCode({ - MISSING_DECORATOR: 'MISSING_DECORATOR', -} as const); diff --git a/packages/twenty-server/src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache-registry.service.ts b/packages/twenty-server/src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache-registry.service.ts deleted file mode 100644 index cbd57fa806..0000000000 --- a/packages/twenty-server/src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache-registry.service.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; -import { DiscoveryService } from '@nestjs/core'; - -import { isDefined } from 'twenty-shared/utils'; - -import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; -import { WORKSPACE_FLAT_MAP_CACHE_KEY } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator'; -import { - WorkspaceFlatMapCacheException, - WorkspaceFlatMapCacheExceptionCode, -} from 'src/engine/workspace-flat-map-cache/exceptions/workspace-flat-map-cache.exception'; -import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service'; - -@Injectable() -export class WorkspaceFlatMapCacheRegistryService implements OnModuleInit { - private readonly cacheServiceRecord: { - [P in keyof AllFlatEntityMaps]?: WorkspaceFlatMapCacheService< - AllFlatEntityMaps[P] - >; - } = {}; - - constructor(private readonly discoveryService: DiscoveryService) {} - - async onModuleInit() { - this.discoverAndRegisterCacheServices(); - } - - private discoverAndRegisterCacheServices(): void { - const providers = this.discoveryService.getProviders(); - - providers.forEach((wrapper) => { - const { instance, metatype } = wrapper; - - if (!instance || !metatype) { - return; - } - - const cacheKey: keyof AllFlatEntityMaps | undefined = Reflect.getMetadata( - WORKSPACE_FLAT_MAP_CACHE_KEY, - metatype, - ); - - if (cacheKey && instance instanceof WorkspaceFlatMapCacheService) { - this.cacheServiceRecord[cacheKey] = instance; - } - }); - } - - getCacheService(flatMapsKey: K) { - return this.cacheServiceRecord[flatMapsKey]; - } - - getCacheServiceOrThrow(flatMapsKey: K) { - const service = this.getCacheService(flatMapsKey); - - if (!isDefined(service)) { - throw new WorkspaceFlatMapCacheException( - `No cache service found for ${flatMapsKey}`, - WorkspaceFlatMapCacheExceptionCode.INTERNAL_SERVER_ERROR, - ); - } - - return service; - } -} diff --git a/packages/twenty-server/src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service.ts b/packages/twenty-server/src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service.ts deleted file mode 100644 index 46936ab7e0..0000000000 --- a/packages/twenty-server/src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; - -import crypto from 'crypto'; - -import { isDefined } from 'twenty-shared/utils'; - -import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator'; -import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service'; -import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum'; -import { WORKSPACE_FLAT_MAP_CACHE_KEY } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator'; -import { - WorkspaceFlatMapCacheException, - WorkspaceFlatMapCacheExceptionCode, -} from 'src/engine/workspace-flat-map-cache/exceptions/workspace-flat-map-cache.exception'; - -@Injectable() -export abstract class WorkspaceFlatMapCacheService { - protected readonly logger = new Logger(this.constructor.name); - - private readonly localCacheFlatMaps = new Map(); - private readonly localCacheHashes = new Map(); - private readonly reflector = new Reflector(); - - constructor( - @InjectCacheStorage(CacheStorageNamespace.EngineWorkspace) - private readonly cacheStorageService: CacheStorageService, - ) {} - - protected abstract computeFlatMap({ - workspaceId, - }: { - workspaceId: string; - }): Promise; - - async getExistingOrRecomputeFlatMaps({ - workspaceId, - }: { - workspaceId: string; - }): Promise { - const localCacheHash = this.localCacheHashes.get(workspaceId); - const remoteCacheHash = await this.getHashFromRemoteCache({ workspaceId }); - - if ( - isDefined(localCacheHash) && - isDefined(remoteCacheHash) && - localCacheHash === remoteCacheHash - ) { - const localCacheFlatMap = this.localCacheFlatMaps.get(workspaceId); - - if (localCacheFlatMap) { - return localCacheFlatMap; - } - } - - if (remoteCacheHash) { - const remoteCacheFlatMap = await this.getFlatMapFromRemoteCache({ - workspaceId, - }); - - if (remoteCacheFlatMap) { - this.localCacheFlatMaps.set(workspaceId, remoteCacheFlatMap); - this.localCacheHashes.set(workspaceId, remoteCacheHash); - - return remoteCacheFlatMap; - } - } - - const freshFlatMap = await this.recomputeAndStoreInCache({ - workspaceId, - }); - - return freshFlatMap; - } - - async recomputeAndStoreInCache({ - workspaceId, - }: { - workspaceId: string; - }): Promise { - const freshFlatMap = await this.computeFlatMap({ workspaceId }); - const newHash = this.generateHash({ flatMap: freshFlatMap }); - - await this.setFlatMapInRemoteCache({ workspaceId, flatMap: freshFlatMap }); - await this.setHashInRemoteCache({ workspaceId, hash: newHash }); - - this.localCacheFlatMaps.set(workspaceId, freshFlatMap); - this.localCacheHashes.set(workspaceId, newHash); - - return freshFlatMap; - } - - async invalidateCache({ - workspaceId, - }: { - workspaceId: string; - }): Promise { - await this.flushCache({ - workspaceId, - }); - await this.recomputeAndStoreInCache({ workspaceId }); - } - - private getFlatMapCacheKey(): string { - const cacheKey = this.reflector.get( - WORKSPACE_FLAT_MAP_CACHE_KEY, - this.constructor, - ); - - if (!isDefined(cacheKey)) { - throw new WorkspaceFlatMapCacheException( - `${this.constructor.name} must be decorated with @WorkspaceFlatMapCache('cacheKey')`, - WorkspaceFlatMapCacheExceptionCode.MISSING_DECORATOR, - ); - } - - return cacheKey; - } - - private buildRemoteCacheKeys({ workspaceId }: { workspaceId: string }) { - const cacheKey = this.getFlatMapCacheKey(); - - return { - flatMapKey: `flat-maps:${cacheKey}:${workspaceId}:flat-map`, - hashKey: `flat-maps:${cacheKey}:${workspaceId}:hash`, - }; - } - - private generateHash({ flatMap }: { flatMap: T }): string { - return crypto - .createHash('sha256') - .update(JSON.stringify(flatMap)) - .digest('hex'); - } - - private async getHashFromRemoteCache({ - workspaceId, - }: { - workspaceId: string; - }): Promise { - const { hashKey } = this.buildRemoteCacheKeys({ workspaceId }); - - return this.cacheStorageService.get(hashKey); - } - - private async setHashInRemoteCache({ - workspaceId, - hash, - }: { - workspaceId: string; - hash: string; - }): Promise { - const { hashKey } = this.buildRemoteCacheKeys({ workspaceId }); - - await this.cacheStorageService.set(hashKey, hash); - } - - private async getFlatMapFromRemoteCache({ - workspaceId, - }: { - workspaceId: string; - }): Promise { - const { flatMapKey } = this.buildRemoteCacheKeys({ workspaceId }); - - return this.cacheStorageService.get(flatMapKey); - } - - private async setFlatMapInRemoteCache({ - workspaceId, - flatMap, - }: { - workspaceId: string; - flatMap: T; - }): Promise { - const { flatMapKey } = this.buildRemoteCacheKeys({ workspaceId }); - - await this.cacheStorageService.set(flatMapKey, flatMap); - } - - async flushCache({ workspaceId }: { workspaceId: string }): Promise { - const { flatMapKey, hashKey } = this.buildRemoteCacheKeys({ workspaceId }); - - await this.cacheStorageService.del(flatMapKey); - await this.cacheStorageService.del(hashKey); - - this.localCacheFlatMaps.delete(workspaceId); - this.localCacheHashes.delete(workspaceId); - } -} diff --git a/packages/twenty-server/src/engine/workspace-flat-map-cache/workspace-flat-map-cache.module.ts b/packages/twenty-server/src/engine/workspace-flat-map-cache/workspace-flat-map-cache.module.ts deleted file mode 100644 index 173ac248fe..0000000000 --- a/packages/twenty-server/src/engine/workspace-flat-map-cache/workspace-flat-map-cache.module.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Module } from '@nestjs/common'; -import { DiscoveryModule } from '@nestjs/core'; -import { TypeOrmModule } from '@nestjs/typeorm'; - -import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; -import { WorkspaceFlatFieldMetadataMapCacheService } from 'src/engine/metadata-modules/flat-field-metadata/services/workspace-flat-field-metadata-map-cache.service'; -import { WorkspaceFlatIndexMapCacheService } from 'src/engine/metadata-modules/flat-index-metadata/services/workspace-flat-index-map-cache.service'; -import { WorkspaceFlatObjectMetadataMapCacheService } from 'src/engine/metadata-modules/flat-object-metadata/services/workspace-flat-object-metadata-map-cache.service'; -import { WorkspaceFlatViewFieldMapCacheService } from 'src/engine/metadata-modules/flat-view-field/services/workspace-flat-view-field-map-cache.service'; -import { WorkspaceFlatViewFilterMapCacheService } from 'src/engine/metadata-modules/flat-view-filter/services/workspace-flat-view-filter-map-cache.service'; -import { WorkspaceFlatViewGroupMapCacheService } from 'src/engine/metadata-modules/flat-view-group/services/workspace-flat-view-group-map-cache.service'; -import { WorkspaceFlatViewMapCacheService } from 'src/engine/metadata-modules/flat-view/services/workspace-flat-view-map-cache.service'; -import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity'; -import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity'; -import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity'; -import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity'; -import { ViewGroupEntity } from 'src/engine/metadata-modules/view-group/entities/view-group.entity'; -import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity'; -import { WorkspaceFlatMapCacheRegistryService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache-registry.service'; - -@Module({ - imports: [ - DiscoveryModule, - TypeOrmModule.forFeature([ - ViewEntity, - ViewFieldEntity, - ViewFilterEntity, - ViewGroupEntity, - IndexMetadataEntity, - IndexFieldMetadataEntity, - FieldMetadataEntity, - ObjectMetadataEntity, - ]), - ], - providers: [ - WorkspaceFlatMapCacheRegistryService, - WorkspaceFlatObjectMetadataMapCacheService, - WorkspaceFlatViewMapCacheService, - WorkspaceFlatViewFieldMapCacheService, - WorkspaceFlatViewFilterMapCacheService, - WorkspaceFlatIndexMapCacheService, - WorkspaceFlatFieldMetadataMapCacheService, - WorkspaceFlatViewGroupMapCacheService, - ], - exports: [ - WorkspaceFlatMapCacheRegistryService, - WorkspaceFlatObjectMetadataMapCacheService, - WorkspaceFlatViewMapCacheService, - WorkspaceFlatViewFieldMapCacheService, - WorkspaceFlatViewFilterMapCacheService, - WorkspaceFlatIndexMapCacheService, - WorkspaceFlatFieldMetadataMapCacheService, - WorkspaceFlatViewGroupMapCacheService, - ], -}) -export class WorkspaceFlatMapCacheModule {} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-metadata-validation-response-error.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-metadata-validation-response-error.util.ts index dd15c6ee4e..3e8cb2d1dd 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-metadata-validation-response-error.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/interceptors/utils/from-workspace-migration-builder-exception-to-metadata-validation-response-error.util.ts @@ -17,6 +17,7 @@ export const fromWorkspaceMigrationBuilderExceptionToMetadataValidationResponseE return { summary: { + invalidAgent: 0, invalidViewFilter: 0, invalidObjectMetadata: 0, invalidView: 0, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts index 7b5226536d..38b09a4182 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-build-orchestrator.service.ts @@ -14,6 +14,7 @@ import { WorkspaceMigrationOrchestratorSuccessfulResult, } from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type'; import { aggregateOrchestratorActionsReport } from 'src/engine/workspace-manager/workspace-migration-v2/utils/aggregate-orchestrator-actions-report.util'; +import { WorkspaceMigrationV2AgentActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/workspace-migration-v2-agent-actions-builder.service'; import { WorkspaceMigrationV2CronTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/cron-trigger/workspace-migration-v2-cron-trigger-action-builder.service'; import { WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/database-event-trigger/workspace-migration-v2-database-event-trigger-actions-builder.service'; import { WorkspaceMigrationV2FieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/workspace-migration-v2-field-actions-builder.service'; @@ -44,6 +45,7 @@ export class WorkspaceMigrationBuildOrchestratorService { private readonly workspaceMigrationV2RoleTargetActionsBuilderService: WorkspaceMigrationV2RoleTargetActionsBuilderService, private readonly workspaceMigrationV2FieldActionsBuilderService: WorkspaceMigrationV2FieldActionsBuilderService, private readonly workspaceMigrationV2RoleActionsBuilderService: WorkspaceMigrationV2RoleActionsBuilderService, + private readonly workspaceMigrationV2AgentActionsBuilderService: WorkspaceMigrationV2AgentActionsBuilderService, ) {} private setupOptimisticCache({ @@ -131,6 +133,7 @@ export class WorkspaceMigrationBuildOrchestratorService { flatViewGroupMaps, flatRoleMaps, flatRoleTargetMaps, + flatAgentMaps, } = fromToAllFlatEntityMaps; if (isDefined(flatObjectMetadataMaps)) { @@ -578,6 +581,33 @@ export class WorkspaceMigrationBuildOrchestratorService { } } + if (isDefined(flatAgentMaps)) { + const { from: fromFlatAgentMaps, to: toFlatAgentMaps } = flatAgentMaps; + + const agentResult = + this.workspaceMigrationV2AgentActionsBuilderService.validateAndBuild({ + from: fromFlatAgentMaps, + to: toFlatAgentMaps, + buildOptions, + dependencyOptimisticFlatEntityMaps: undefined, + workspaceId, + }); + + this.mergeFlatEntityMapsAndRelatedFlatEntityMapsInAllFlatEntityMapsThroughMutation( + { + allFlatEntityMaps: optimisticAllFlatEntityMaps, + flatEntityMapsAndRelatedFlatEntityMaps: + agentResult.optimisticFlatEntityMapsAndRelatedFlatEntityMaps, + }, + ); + + if (agentResult.status === 'fail') { + orchestratorFailureReport.agent.push(...agentResult.errors); + } else { + orchestratorActionsReport.agent = agentResult.actions; + } + } + const allErrors = Object.values(orchestratorFailureReport); if (allErrors.some((report) => report.length > 0)) { @@ -666,6 +696,12 @@ export class WorkspaceMigrationBuildOrchestratorService { ...aggregatedOrchestratorActionsReport.roleTarget.created, ...aggregatedOrchestratorActionsReport.roleTarget.updated, /// + + // + ...aggregatedOrchestratorActionsReport.agent.deleted, + ...aggregatedOrchestratorActionsReport.agent.created, + ...aggregatedOrchestratorActionsReport.agent.updated, + /// ], workspaceId, }, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/types/workspace-migration-v2-agent-action-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/types/workspace-migration-v2-agent-action-builder.service.ts new file mode 100644 index 0000000000..4a1da496f1 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/types/workspace-migration-v2-agent-action-builder.service.ts @@ -0,0 +1,18 @@ +import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; +import { type FlatEntityPropertiesUpdates } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-properties-updates.type'; + +export type UpdateAgentAction = { + type: 'update_agent'; + flatEntityId: string; + flatEntityUpdates: FlatEntityPropertiesUpdates<'agent'>; +}; + +export type CreateAgentAction = { + type: 'create_agent'; + flatEntity: FlatAgent; +}; + +export type DeleteAgentAction = { + type: 'delete_agent'; + flatEntityId: string; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/workspace-migration-v2-agent-actions-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/workspace-migration-v2-agent-actions-builder.service.ts new file mode 100644 index 0000000000..ff49837bdb --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/workspace-migration-v2-agent-actions-builder.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@nestjs/common'; + +import { ALL_METADATA_NAME } from 'twenty-shared/metadata'; + +import { UpdateAgentAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/types/workspace-migration-v2-agent-action-builder.service'; +import { WorkspaceEntityMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service'; +import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-update-validation-args.type'; +import { FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-args.type'; +import { FlatEntityValidationReturnType } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-result.type'; +import { FlatAgentValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-agent-validator.service'; + +@Injectable() +export class WorkspaceMigrationV2AgentActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service< + typeof ALL_METADATA_NAME.agent +> { + constructor( + private readonly flatAgentValidatorService: FlatAgentValidatorService, + ) { + super(ALL_METADATA_NAME.agent); + } + + protected validateFlatEntityCreation( + args: FlatEntityValidationArgs, + ): FlatEntityValidationReturnType { + const validationResult = + this.flatAgentValidatorService.validateFlatAgentCreation(args); + + if (validationResult.errors.length > 0) { + return { + status: 'fail', + ...validationResult, + }; + } + + const { flatEntityToValidate: flatAgentToValidate } = args; + + return { + status: 'success', + action: { + type: 'create_agent', + flatEntity: flatAgentToValidate, + }, + }; + } + + protected validateFlatEntityDeletion( + args: FlatEntityValidationArgs, + ): FlatEntityValidationReturnType { + const validationResult = + this.flatAgentValidatorService.validateFlatAgentDeletion(args); + + if (validationResult.errors.length > 0) { + return { + status: 'fail', + ...validationResult, + }; + } + + const { flatEntityToValidate: flatAgentToValidate } = args; + + return { + status: 'success', + action: { + type: 'delete_agent', + flatEntityId: flatAgentToValidate.id, + }, + }; + } + + protected validateFlatEntityUpdate( + args: FlatEntityUpdateValidationArgs, + ): FlatEntityValidationReturnType { + const validationResult = + this.flatAgentValidatorService.validateFlatAgentUpdate(args); + + if (validationResult.errors.length > 0) { + return { + status: 'fail', + ...validationResult, + }; + } + + const { flatEntityId, flatEntityUpdates } = args; + + const updateAgentAction: UpdateAgentAction = { + type: 'update_agent', + flatEntityId, + flatEntityUpdates, + }; + + return { + status: 'success', + action: updateAgentAction, + }; + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-agent-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-agent-validator.service.ts new file mode 100644 index 0000000000..c03caf96f4 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-agent-validator.service.ts @@ -0,0 +1,193 @@ +import { Injectable } from '@nestjs/common'; + +import { msg, t } from '@lingui/core/macro'; +import { type ALL_METADATA_NAME } from 'twenty-shared/metadata'; +import { isDefined } from 'twenty-shared/utils'; + +import { AgentExceptionCode } from 'src/engine/metadata-modules/ai/ai-agent/agent.exception'; +import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; +import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; +import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type'; +import { type FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-update-validation-args.type'; +import { type FlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-entity-validation-args.type'; +import { validateAgentNameUniqueness } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-agent-name-uniqueness.util'; +import { validateAgentRequiredProperties } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-agent-required-properties.util'; +import { validateAgentResponseFormat } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-agent-response-format.util'; +import { fromFlatEntityPropertiesUpdatesToPartialFlatEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-flat-entity-properties-updates-to-partial-flat-entity'; + +@Injectable() +export class FlatAgentValidatorService { + public validateFlatAgentCreation({ + flatEntityToValidate: flatAgent, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatAgentMaps: optimisticFlatAgentMaps, + }, + }: FlatEntityValidationArgs< + typeof ALL_METADATA_NAME.agent + >): FailedFlatEntityValidation { + const validationResult: FailedFlatEntityValidation = { + type: 'create_agent', + errors: [], + flatEntityMinimalInformation: { + id: flatAgent.id, + name: flatAgent.name, + }, + }; + + const existingAgents = Object.values(optimisticFlatAgentMaps.byId).filter( + isDefined, + ); + + validationResult.errors.push( + ...validateAgentRequiredProperties({ + flatAgent, + }), + ); + + validationResult.errors.push( + ...validateAgentNameUniqueness({ + name: flatAgent.name, + existingFlatAgents: existingAgents, + }), + ); + + if (isDefined(flatAgent.responseFormat)) { + validationResult.errors.push( + ...validateAgentResponseFormat({ + responseFormat: flatAgent.responseFormat, + }), + ); + } + + return validationResult; + } + + public validateFlatAgentDeletion({ + flatEntityToValidate, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatAgentMaps: optimisticFlatAgentMaps, + }, + }: FlatEntityValidationArgs< + typeof ALL_METADATA_NAME.agent + >): FailedFlatEntityValidation { + const validationResult: FailedFlatEntityValidation = { + type: 'delete_agent', + errors: [], + flatEntityMinimalInformation: { + id: flatEntityToValidate.id, + name: flatEntityToValidate.name, + }, + }; + + const existingAgent = findFlatEntityByIdInFlatEntityMaps({ + flatEntityId: flatEntityToValidate.id, + flatEntityMaps: optimisticFlatAgentMaps, + }); + + if (!isDefined(existingAgent)) { + validationResult.errors.push({ + code: AgentExceptionCode.AGENT_NOT_FOUND, + message: t`Agent not found`, + userFriendlyMessage: msg`Agent not found`, + }); + + return validationResult; + } + + if ( + existingAgent.isCustom === false && + isDefined(existingAgent.standardId) + ) { + validationResult.errors.push({ + code: AgentExceptionCode.AGENT_IS_STANDARD, + message: t`Cannot delete standard agent`, + userFriendlyMessage: msg`Cannot delete standard agent`, + }); + } + + return validationResult; + } + + public validateFlatAgentUpdate({ + flatEntityId, + flatEntityUpdates, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatAgentMaps: optimisticFlatAgentMaps, + }, + }: FlatEntityUpdateValidationArgs< + typeof ALL_METADATA_NAME.agent + >): FailedFlatEntityValidation { + const validationResult: FailedFlatEntityValidation = { + type: 'update_agent', + errors: [], + flatEntityMinimalInformation: { + id: flatEntityId, + }, + }; + + const fromFlatAgent = findFlatEntityByIdInFlatEntityMaps({ + flatEntityId, + flatEntityMaps: optimisticFlatAgentMaps, + }); + + if (!isDefined(fromFlatAgent)) { + validationResult.errors.push({ + code: AgentExceptionCode.AGENT_NOT_FOUND, + message: t`Agent not found`, + userFriendlyMessage: msg`Agent not found`, + }); + + return validationResult; + } + + if ( + fromFlatAgent.isCustom === false && + isDefined(fromFlatAgent.standardId) + ) { + validationResult.errors.push({ + code: AgentExceptionCode.AGENT_IS_STANDARD, + message: t`Cannot update standard agent`, + userFriendlyMessage: msg`Cannot update standard agent`, + }); + } + + const partialFlatAgent: Partial = + fromFlatEntityPropertiesUpdatesToPartialFlatEntity({ + updates: flatEntityUpdates, + }); + + const optimisticFlatAgent: FlatAgent = { + ...fromFlatAgent, + ...partialFlatAgent, + }; + + const existingAgents = Object.values(optimisticFlatAgentMaps.byId) + .filter(isDefined) + .filter((agent) => agent.id !== flatEntityId); + + validationResult.errors.push( + ...validateAgentRequiredProperties({ + flatAgent: optimisticFlatAgent, + }), + ); + + if (isDefined(partialFlatAgent.name)) { + validationResult.errors.push( + ...validateAgentNameUniqueness({ + name: partialFlatAgent.name, + existingFlatAgents: existingAgents, + }), + ); + } + + if (isDefined(partialFlatAgent.responseFormat)) { + validationResult.errors.push( + ...validateAgentResponseFormat({ + responseFormat: partialFlatAgent.responseFormat, + }), + ); + } + + return validationResult; + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-agent-name-uniqueness.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-agent-name-uniqueness.util.ts new file mode 100644 index 0000000000..d9977e81b0 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-agent-name-uniqueness.util.ts @@ -0,0 +1,25 @@ +import { msg, t } from '@lingui/core/macro'; + +import { AgentExceptionCode } from 'src/engine/metadata-modules/ai/ai-agent/agent.exception'; +import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; +import { type FlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type'; + +export const validateAgentNameUniqueness = ({ + name, + existingFlatAgents, +}: { + name: string; + existingFlatAgents: FlatAgent[]; +}): FlatEntityValidationError[] => { + const errors: FlatEntityValidationError[] = []; + + if (existingFlatAgents.some((agent) => agent.name === name)) { + errors.push({ + code: AgentExceptionCode.AGENT_ALREADY_EXISTS, + message: t`Agent with name "${name}" already exists`, + userFriendlyMessage: msg`An agent with this name already exists`, + }); + } + + return errors; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-agent-required-properties.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-agent-required-properties.util.ts new file mode 100644 index 0000000000..1d4a5c7bba --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-agent-required-properties.util.ts @@ -0,0 +1,40 @@ +import { isNonEmptyString } from '@sniptt/guards'; +import { msg, t } from '@lingui/core/macro'; + +import { AgentExceptionCode } from 'src/engine/metadata-modules/ai/ai-agent/agent.exception'; +import { type FlatAgent } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; +import { type FlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type'; + +export const validateAgentRequiredProperties = ({ + flatAgent, +}: { + flatAgent: FlatAgent; +}): FlatEntityValidationError[] => { + const errors: FlatEntityValidationError[] = []; + + if (!isNonEmptyString(flatAgent.label)) { + errors.push({ + code: AgentExceptionCode.INVALID_AGENT_INPUT, + message: t`Label cannot be empty`, + userFriendlyMessage: msg`Label cannot be empty`, + }); + } + + if (!isNonEmptyString(flatAgent.prompt)) { + errors.push({ + code: AgentExceptionCode.INVALID_AGENT_INPUT, + message: t`Prompt cannot be empty`, + userFriendlyMessage: msg`Prompt cannot be empty`, + }); + } + + if (!isNonEmptyString(flatAgent.modelId)) { + errors.push({ + code: AgentExceptionCode.INVALID_AGENT_INPUT, + message: t`Model ID cannot be empty`, + userFriendlyMessage: msg`Model ID cannot be empty`, + }); + } + + return errors; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-agent-response-format.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-agent-response-format.util.ts new file mode 100644 index 0000000000..935c84faa5 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-agent-response-format.util.ts @@ -0,0 +1,47 @@ +import { msg, t } from '@lingui/core/macro'; +import { isDefined } from 'twenty-shared/utils'; + +import { AgentExceptionCode } from 'src/engine/metadata-modules/ai/ai-agent/agent.exception'; +import { + type AgentResponseFormat, + type AgentJsonResponseFormat, +} from 'src/engine/metadata-modules/ai/ai-agent/types/agent-response-format.type'; +import { type FlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type'; + +export const validateAgentResponseFormat = ({ + responseFormat, +}: { + responseFormat: AgentResponseFormat; +}): FlatEntityValidationError[] => { + const errors: FlatEntityValidationError[] = []; + const type = responseFormat.type; + + if (type !== 'text' && type !== 'json') { + errors.push({ + code: AgentExceptionCode.INVALID_AGENT_INPUT, + message: t`Response format type must be either "text" or "json"`, + userFriendlyMessage: msg`Invalid response format type`, + }); + } + + if (type === 'json' && !isDefined(responseFormat.schema)) { + errors.push({ + code: AgentExceptionCode.INVALID_AGENT_INPUT, + message: t`Response format with type "json" must include a schema`, + userFriendlyMessage: msg`JSON response format requires a schema`, + }); + } + + if ( + type === 'text' && + isDefined((responseFormat as unknown as AgentJsonResponseFormat).schema) + ) { + errors.push({ + code: AgentExceptionCode.INVALID_AGENT_INPUT, + message: t`Response format with type "text" should not include a schema`, + userFriendlyMessage: msg`Text response format should not have a schema`, + }); + } + + return errors; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/workspace-migration-builder-validators.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/workspace-migration-builder-validators.module.ts index ec07fcb6c3..076e27af13 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/workspace-migration-builder-validators.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/workspace-migration-builder-validators.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service'; +import { FlatAgentValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-agent-validator.service'; import { FlatCronTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-cron-trigger-validator.service'; import { FlatDatabaseEventTriggerValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-database-event-trigger-validator.service'; import { FlatFieldMetadataValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-field-metadata-validator.service'; @@ -33,6 +34,7 @@ import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace FlatRouteTriggerValidatorService, FlatRoleValidatorService, FlatRoleTargetValidatorService, + FlatAgentValidatorService, ], exports: [ FlatViewValidatorService, @@ -49,6 +51,7 @@ import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace FlatRouteTriggerValidatorService, FlatRoleValidatorService, FlatRoleTargetValidatorService, + FlatAgentValidatorService, ], }) export class WorkspaceMigrationBuilderValidatorsModule {} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module.ts index 4b348901c8..795a99b376 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service'; +import { WorkspaceMigrationV2AgentActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/workspace-migration-v2-agent-actions-builder.service'; import { WorkspaceMigrationV2CronTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/cron-trigger/workspace-migration-v2-cron-trigger-action-builder.service'; import { WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/database-event-trigger/workspace-migration-v2-database-event-trigger-actions-builder.service'; import { WorkspaceMigrationV2FieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/workspace-migration-v2-field-actions-builder.service'; @@ -34,6 +35,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace- WorkspaceMigrationV2RouteTriggerActionsBuilderService, WorkspaceMigrationV2RoleActionsBuilderService, WorkspaceMigrationV2RoleTargetActionsBuilderService, + WorkspaceMigrationV2AgentActionsBuilderService, ], exports: [ WorkspaceMigrationV2ViewActionsBuilderService, @@ -49,6 +51,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace- WorkspaceMigrationV2RouteTriggerActionsBuilderService, WorkspaceMigrationV2RoleActionsBuilderService, WorkspaceMigrationV2RoleTargetActionsBuilderService, + WorkspaceMigrationV2AgentActionsBuilderService, FlatFieldMetadataTypeValidatorService, ], }) diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/create-agent-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/create-agent-action-handler.service.ts new file mode 100644 index 0000000000..06cafe2c1c --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/create-agent-action-handler.service.ts @@ -0,0 +1,59 @@ +import { Injectable } from '@nestjs/common'; + +import { + OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + WorkspaceMigrationRunnerActionHandler, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; + +import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity'; +import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util'; +import { CreateAgentAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/types/workspace-migration-v2-agent-action-builder.service'; +import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; + +@Injectable() +export class CreateAgentActionHandlerService extends WorkspaceMigrationRunnerActionHandler( + 'create_agent', +) { + constructor() { + super(); + } + + optimisticallyApplyActionOnAllFlatEntityMaps({ + action, + allFlatEntityMaps, + }: OptimisticallyApplyActionOnAllFlatEntityMapsArgs): Partial { + const { flatAgentMaps } = allFlatEntityMaps; + const { flatEntity } = action; + + const updatedFlatAgentMaps = addFlatEntityToFlatEntityMapsOrThrow({ + flatEntity, + flatEntityMaps: flatAgentMaps, + }); + + return { + flatAgentMaps: updatedFlatAgentMaps, + }; + } + + async executeForMetadata( + context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + const { action, queryRunner, workspaceId } = context; + const { flatEntity } = action; + + const agentRepository = + queryRunner.manager.getRepository(AgentEntity); + + await agentRepository.save({ + ...flatEntity, + workspaceId, + }); + } + + async executeForWorkspaceSchema( + _context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + return; + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/delete-agent-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/delete-agent-action-handler.service.ts new file mode 100644 index 0000000000..4cde07d58f --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/delete-agent-action-handler.service.ts @@ -0,0 +1,56 @@ +import { Injectable } from '@nestjs/common'; + +import { + OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + WorkspaceMigrationRunnerActionHandler, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; + +import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity'; +import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util'; +import { DeleteAgentAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/types/workspace-migration-v2-agent-action-builder.service'; +import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; + +@Injectable() +export class DeleteAgentActionHandlerService extends WorkspaceMigrationRunnerActionHandler( + 'delete_agent', +) { + constructor() { + super(); + } + + optimisticallyApplyActionOnAllFlatEntityMaps({ + action, + allFlatEntityMaps, + }: OptimisticallyApplyActionOnAllFlatEntityMapsArgs): Partial { + const { flatAgentMaps } = allFlatEntityMaps; + const { flatEntityId } = action; + + const updatedFlatAgentMaps = deleteFlatEntityFromFlatEntityMapsOrThrow({ + entityToDeleteId: flatEntityId, + flatEntityMaps: flatAgentMaps, + }); + + return { + flatAgentMaps: updatedFlatAgentMaps, + }; + } + + async executeForMetadata( + context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + const { action, queryRunner, workspaceId } = context; + const { flatEntityId } = action; + + const agentRepository = + queryRunner.manager.getRepository(AgentEntity); + + await agentRepository.delete({ id: flatEntityId, workspaceId }); + } + + async executeForWorkspaceSchema( + _context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + return; + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/update-agent-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/update-agent-action-handler.service.ts new file mode 100644 index 0000000000..edc15848f0 --- /dev/null +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/update-agent-action-handler.service.ts @@ -0,0 +1,71 @@ +import { Injectable } from '@nestjs/common'; + +import { + OptimisticallyApplyActionOnAllFlatEntityMapsArgs, + WorkspaceMigrationRunnerActionHandler, +} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface'; + +import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity'; +import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; +import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util'; +import { UpdateAgentAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/agent/types/workspace-migration-v2-agent-action-builder.service'; +import { WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type'; +import { fromFlatEntityPropertiesUpdatesToPartialFlatEntity } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/from-flat-entity-properties-updates-to-partial-flat-entity'; + +@Injectable() +export class UpdateAgentActionHandlerService extends WorkspaceMigrationRunnerActionHandler( + 'update_agent', +) { + optimisticallyApplyActionOnAllFlatEntityMaps({ + action, + allFlatEntityMaps, + }: OptimisticallyApplyActionOnAllFlatEntityMapsArgs): Partial { + const { flatAgentMaps } = allFlatEntityMaps; + const { flatEntityId, flatEntityUpdates } = action; + + const existingAgent = findFlatEntityByIdInFlatEntityMapsOrThrow({ + flatEntityId, + flatEntityMaps: flatAgentMaps, + }); + + const updatedAgent = { + ...existingAgent, + ...fromFlatEntityPropertiesUpdatesToPartialFlatEntity({ + updates: flatEntityUpdates, + }), + }; + + const updatedFlatAgentMaps = replaceFlatEntityInFlatEntityMapsOrThrow({ + flatEntity: updatedAgent, + flatEntityMaps: flatAgentMaps, + }); + + return { + flatAgentMaps: updatedFlatAgentMaps, + }; + } + + async executeForMetadata( + context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + const { action, queryRunner, workspaceId } = context; + const { flatEntityId, flatEntityUpdates } = action; + + const agentRepository = + queryRunner.manager.getRepository(AgentEntity); + + await agentRepository.update( + { id: flatEntityId, workspaceId }, + fromFlatEntityPropertiesUpdatesToPartialFlatEntity({ + updates: flatEntityUpdates, + }), + ); + } + + async executeForWorkspaceSchema( + _context: WorkspaceMigrationActionRunnerArgs, + ): Promise { + return; + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/workspace-schema-migration-runner-action-handlers.module.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/workspace-schema-migration-runner-action-handlers.module.ts index 06cb5363e1..3a9f069dfd 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/workspace-schema-migration-runner-action-handlers.module.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/workspace-schema-migration-runner-action-handlers.module.ts @@ -1,6 +1,9 @@ import { Module } from '@nestjs/common'; import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module'; +import { CreateAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/create-agent-action-handler.service'; +import { DeleteAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/delete-agent-action-handler.service'; +import { UpdateAgentActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/agent/services/update-agent-action-handler.service'; import { CreateCronTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/cron-trigger/services/create-cron-trigger-action-handler.service'; import { DeleteCronTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/cron-trigger/services/delete-cron-trigger-action-handler.service'; import { UpdateCronTriggerActionHandlerService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/action-handlers/cron-trigger/services/update-cron-trigger-action-handler.service'; @@ -93,6 +96,10 @@ import { UpdateViewActionHandlerService } from 'src/engine/workspace-manager/wor CreateRoleTargetActionHandlerService, DeleteRoleTargetActionHandlerService, UpdateRoleTargetActionHandlerService, + + CreateAgentActionHandlerService, + UpdateAgentActionHandlerService, + DeleteAgentActionHandlerService, ], }) export class WorkspaceSchemaMigrationRunnerActionHandlersModule {} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service.ts index 3bf42293e6..a7c43e71c0 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/services/workspace-migration-runner-v2.service.ts @@ -122,7 +122,9 @@ export class WorkspaceMigrationRunnerV2Service { this.workspaceCacheService.invalidateAndRecompute(workspaceId, [ 'rolesPermissions', 'userWorkspaceRoleMap', + 'flatRoleTargetMaps', 'apiKeyRoleMap', + 'flatRoleTargetByAgentIdMaps', ]), ); } diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-agent.factory.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-agent.factory.ts index 122a3aaeab..d0c4d8148b 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-agent.factory.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-agent.factory.ts @@ -16,28 +16,18 @@ export class StandardAgentFactory { ): FlatAgent[] { const computedAgents: FlatAgent[] = []; - for (const agentDefinition of agentDefinitions) { - const existingAgent = existingAgents.find( - (agent) => agent.standardId === agentDefinition.standardId, + for (const standardAgentDefinition of agentDefinitions) { + const existingAgentEntity = existingAgents.find( + (agent) => agent.standardId === standardAgentDefinition.standardId, ); - const flatAgent = transformStandardAgentDefinitionToFlatAgent( - agentDefinition, - context.workspaceId, - ); + const flatAgent = transformStandardAgentDefinitionToFlatAgent({ + standardAgentDefinition, + workspaceId: context.workspaceId, + existingAgentEntity, + }); - if (existingAgent) { - computedAgents.push({ - ...flatAgent, - id: existingAgent.id, - universalIdentifier: agentDefinition.standardId, - }); - } else { - computedAgents.push({ - ...flatAgent, - universalIdentifier: agentDefinition.standardId, - }); - } + computedAgents.push(flatAgent); } return computedAgents; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-agents/types/standard-agent-definition.interface.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-agents/types/standard-agent-definition.interface.ts index 8aea81e8a3..061b57ab48 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-agents/types/standard-agent-definition.interface.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/standard-agents/types/standard-agent-definition.interface.ts @@ -4,7 +4,13 @@ export type AgentOutputStrategy = 'direct' | 'synthesize'; export type StandardAgentDefinition = Omit< FlatAgent, - 'id' | 'workspaceId' | 'universalIdentifier' | 'standardId' + | 'id' + | 'workspaceId' + | 'universalIdentifier' + | 'standardId' + | 'updatedAt' + | 'deletedAt' + | 'createdAt' > & { standardId: string; standardRoleId?: string; diff --git a/packages/twenty-server/test/integration/graphql/suites/agent/agent.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/agent/agent.integration-spec.ts deleted file mode 100644 index e5646703c1..0000000000 --- a/packages/twenty-server/test/integration/graphql/suites/agent/agent.integration-spec.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { Test, type TestingModule } from '@nestjs/testing'; -import { getRepositoryToken } from '@nestjs/typeorm'; - -import { AiAgentRoleService } from 'src/engine/metadata-modules/ai/ai-agent-role/ai-agent-role.service'; -import { - AgentException, - AgentExceptionCode, -} from 'src/engine/metadata-modules/ai/ai-agent/agent.exception'; -import { AgentResolver } from 'src/engine/metadata-modules/ai/ai-agent/agent.resolver'; -import { AgentService } from 'src/engine/metadata-modules/ai/ai-agent/agent.service'; -import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity'; -import { AgentChatService } from 'src/engine/metadata-modules/ai/ai-chat/services/agent-chat.service'; -import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service'; -import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity'; - -// Mock the guards and decorators -jest.mock('../../../../../src/engine/guards/feature-flag.guard', () => ({ - FeatureFlagGuard: jest.fn().mockImplementation(() => ({ - canActivate: jest.fn().mockReturnValue(true), - })), - RequireFeatureFlag: () => jest.fn(), -})); - -jest.mock('../../../../../src/engine/guards/workspace-auth.guard', () => ({ - WorkspaceAuthGuard: jest.fn().mockImplementation(() => ({ - canActivate: jest.fn().mockReturnValue(true), - })), -})); - -describe('agentResolver', () => { - let agentService: AgentService; - let agentResolver: AgentResolver; - let module: TestingModule; - - beforeAll(async () => { - // Create a testing module with mocked dependencies - module = await Test.createTestingModule({ - providers: [ - AgentResolver, - { - provide: AgentService, - useValue: { - findOneAgent: jest.fn(), - updateOneAgent: jest.fn(), - }, - }, - { - provide: getRepositoryToken(AgentEntity), - useValue: { - find: jest.fn(), - findOne: jest.fn(), - save: jest.fn(), - softDelete: jest.fn(), - }, - }, - { - provide: getRepositoryToken(RoleTargetEntity), - useValue: { - findOne: jest.fn(), - }, - }, - { - provide: AgentChatService, - useValue: { - createThread: jest.fn(), - }, - }, - { - provide: AiAgentRoleService, - useValue: { - assignRoleToAgent: jest.fn(), - removeRoleFromAgent: jest.fn(), - }, - }, - { - provide: PermissionsService, - useValue: { - userHasWorkspaceSettingPermission: jest - .fn() - .mockResolvedValue(true), - }, - }, - ], - }).compile(); - - // Get the mocked services from the module - agentService = module.get(AgentService); - agentResolver = module.get(AgentResolver); - }); - - afterAll(async () => { - if (module) { - await module.close(); - } - }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - describe('findOneAgent', () => { - const testAgentId = 'test-agent-id'; - const workspaceId = 'test-workspace-id'; - const mockAgent = { - id: testAgentId, - name: 'Test Agent for Find', - description: 'A test agent for find operations', - prompt: 'You are a test agent for finding.', - modelId: 'gpt-4o', - roleId: null, - createdAt: new Date(), - updatedAt: new Date(), - }; - - it('should find agent by ID successfully', async () => { - // Mock the findOneAgent method to return a mock agent - (agentService.findOneAgent as jest.Mock).mockResolvedValueOnce(mockAgent); - - // Call the resolver directly - const result = await agentResolver.findOneAgent({ id: testAgentId }, { - id: workspaceId, - } as any); - - // Verify the service was called with the correct parameters - expect(agentService.findOneAgent).toHaveBeenCalledWith(workspaceId, { - id: testAgentId, - }); - - // Verify the result matches our expectations - expect(result).toBeDefined(); - expect(result.id).toBe(testAgentId); - expect(result.name).toBe('Test Agent for Find'); - }); - - it('should throw an error for non-existent agent', async () => { - const nonExistentId = '00000000-0000-0000-0000-000000000000'; - - // Mock the findOneAgent method to throw an exception - (agentService.findOneAgent as jest.Mock).mockRejectedValueOnce( - new AgentException( - `Agent with id ${nonExistentId} not found`, - AgentExceptionCode.AGENT_NOT_FOUND, - ), - ); - - // Call the resolver and expect it to throw - await expect( - agentResolver.findOneAgent({ id: nonExistentId }, { - id: workspaceId, - } as any), - ).rejects.toThrow(AgentException); - }); - }); - - describe('updateOneAgent', () => { - const testAgentId = 'test-agent-id'; - const workspaceId = 'test-workspace-id'; - - const updatedAgent = { - id: testAgentId, - name: 'Updated Test Agent Admin', - description: 'Updated description', - prompt: 'Updated prompt for admin', - modelId: 'gpt-4o-mini', - roleId: null, - createdAt: new Date(), - updatedAt: new Date(), - }; - - it('should update an agent successfully', async () => { - // Mock the updateOneAgent method to return the updated agent - (agentService.updateOneAgent as jest.Mock).mockResolvedValueOnce( - updatedAgent, - ); - - // Call the resolver directly - const result = await agentResolver.updateOneAgent( - { - id: testAgentId, - name: 'Updated Test Agent Admin', - description: 'Updated description', - prompt: 'Updated prompt for admin', - modelId: 'gpt-4o-mini', - }, - { id: workspaceId } as any, - ); - - // Verify the service was called with the correct parameters - expect(agentService.updateOneAgent).toHaveBeenCalledWith( - { - id: testAgentId, - name: 'Updated Test Agent Admin', - description: 'Updated description', - prompt: 'Updated prompt for admin', - modelId: 'gpt-4o-mini', - }, - workspaceId, - ); - - // Verify the result matches our expectations - expect(result).toBeDefined(); - expect(result.id).toBe(testAgentId); - expect(result.name).toBe('Updated Test Agent Admin'); - expect(result.description).toBe('Updated description'); - expect(result.prompt).toBe('Updated prompt for admin'); - expect(result.modelId).toBe('gpt-4o-mini'); - }); - }); -}); diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/failing-create-many-view-fields-v2.integration-spec.ts.snap b/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/failing-create-many-view-fields-v2.integration-spec.ts.snap index 2a3ad612d2..76a2d48009 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/failing-create-many-view-fields-v2.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/failing-create-many-view-fields-v2.integration-spec.ts.snap @@ -5,6 +5,7 @@ exports[`View Field Resolver - Failing Create Many Operations - v2 should accumu "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -75,6 +76,7 @@ exports[`View Field Resolver - Failing Create Many Operations - v2 should accumu }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/failing-create-view-field-v2.integration-spec.ts.snap b/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/failing-create-view-field-v2.integration-spec.ts.snap index 70b696c15d..c0159ddb79 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/failing-create-view-field-v2.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/failing-create-view-field-v2.integration-spec.ts.snap @@ -5,6 +5,7 @@ exports[`View Field Resolver - Failing Create Operation - v2 Should fail to crea "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -38,6 +39,7 @@ exports[`View Field Resolver - Failing Create Operation - v2 Should fail to crea }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -65,6 +67,7 @@ exports[`View Field Resolver - Failing Create Operation - v2 should fail to crea "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -98,6 +101,7 @@ exports[`View Field Resolver - Failing Create Operation - v2 should fail to crea }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -125,6 +129,7 @@ exports[`View Field Resolver - Failing Create Operation - v2 should fail to crea "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -158,6 +163,7 @@ exports[`View Field Resolver - Failing Create Operation - v2 should fail to crea }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/object-identifier-update-side-effect-on-view-field.integration-spec.ts.snap b/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/object-identifier-update-side-effect-on-view-field.integration-spec.ts.snap index d1c063da78..da90c23709 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/object-identifier-update-side-effect-on-view-field.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-field/__snapshots__/object-identifier-update-side-effect-on-view-field.integration-spec.ts.snap @@ -5,6 +5,7 @@ exports[`View Field Resolver - Successful object metadata identifier update side "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -38,6 +39,7 @@ exports[`View Field Resolver - Successful object metadata identifier update side }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -65,6 +67,7 @@ exports[`View Field Resolver - Successful object metadata identifier update side "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -98,6 +101,7 @@ exports[`View Field Resolver - Successful object metadata identifier update side }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -125,6 +129,7 @@ exports[`View Field Resolver - Successful object metadata identifier update side "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -156,6 +161,7 @@ exports[`View Field Resolver - Successful object metadata identifier update side }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -183,6 +189,7 @@ exports[`View Field Resolver - Successful object metadata identifier update side "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -216,6 +223,7 @@ exports[`View Field Resolver - Successful object metadata identifier update side }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -243,6 +251,7 @@ exports[`View Field Resolver - Successful object metadata identifier update side "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -276,6 +285,7 @@ exports[`View Field Resolver - Successful object metadata identifier update side }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-group/__snapshots__/failing-create-many-view-groups-v2.integration-spec.ts.snap b/packages/twenty-server/test/integration/graphql/suites/view/view-group/__snapshots__/failing-create-many-view-groups-v2.integration-spec.ts.snap index 22e49fb5d2..d9c96d1bc1 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-group/__snapshots__/failing-create-many-view-groups-v2.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-group/__snapshots__/failing-create-many-view-groups-v2.integration-spec.ts.snap @@ -5,6 +5,7 @@ exports[`View Group Resolver - Failing Create Many Operations - v2 should accumu "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -75,6 +76,7 @@ exports[`View Group Resolver - Failing Create Many Operations - v2 should accumu }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/__snapshots__/failing-agent-creation.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/agent/__snapshots__/failing-agent-creation.integration-spec.ts.snap new file mode 100644 index 0000000000..55eea40247 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/__snapshots__/failing-agent-creation.integration-spec.ts.snap @@ -0,0 +1,470 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Agent creation should fail when computed name already exists 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "agent": [ + { + "errors": [ + { + "code": "AGENT_ALREADY_EXISTS", + "message": "Agent with name "existingTestAgent" already exists", + "userFriendlyMessage": "An agent with this name already exists", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "existingTestAgent", + }, + "status": "fail", + "type": "create_agent", + }, + ], + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [], + "index": [], + "objectMetadata": [], + "role": [], + "roleTarget": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidAgent": 0, + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRole": 0, + "invalidRoleTarget": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while creating agent", + "name": "GraphQLError", +} +`; + +exports[`Agent creation should fail when label is empty string 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "agent": [ + { + "errors": [ + { + "code": "INVALID_AGENT_INPUT", + "message": "Label cannot be empty", + "userFriendlyMessage": "Label cannot be empty", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "", + }, + "status": "fail", + "type": "create_agent", + }, + ], + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [], + "index": [], + "objectMetadata": [], + "role": [], + "roleTarget": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidAgent": 0, + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRole": 0, + "invalidRoleTarget": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while creating agent", + "name": "GraphQLError", +} +`; + +exports[`Agent creation should fail when label is missing 1`] = ` +{ + "extensions": { + "code": "BAD_USER_INPUT", + "http": { + "status": 400, + }, + "userFriendlyMessage": "An error occurred.", + }, + "message": "Field "label" of required type "String!" was not provided.", + "name": "GraphQLError", +} +`; + +exports[`Agent creation should fail when modelId is empty string 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "agent": [ + { + "errors": [ + { + "code": "INVALID_AGENT_INPUT", + "message": "Model ID cannot be empty", + "userFriendlyMessage": "Model ID cannot be empty", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "emptyModelidAgent", + }, + "status": "fail", + "type": "create_agent", + }, + ], + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [], + "index": [], + "objectMetadata": [], + "role": [], + "roleTarget": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidAgent": 0, + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRole": 0, + "invalidRoleTarget": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while creating agent", + "name": "GraphQLError", +} +`; + +exports[`Agent creation should fail when modelId is missing 1`] = ` +{ + "extensions": { + "code": "BAD_USER_INPUT", + "http": { + "status": 400, + }, + "userFriendlyMessage": "An error occurred.", + }, + "message": "Field "modelId" of required type "String!" was not provided.", + "name": "GraphQLError", +} +`; + +exports[`Agent creation should fail when prompt is empty string 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "agent": [ + { + "errors": [ + { + "code": "INVALID_AGENT_INPUT", + "message": "Prompt cannot be empty", + "userFriendlyMessage": "Prompt cannot be empty", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "emptyPromptAgent", + }, + "status": "fail", + "type": "create_agent", + }, + ], + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [], + "index": [], + "objectMetadata": [], + "role": [], + "roleTarget": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidAgent": 0, + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRole": 0, + "invalidRoleTarget": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while creating agent", + "name": "GraphQLError", +} +`; + +exports[`Agent creation should fail when prompt is missing 1`] = ` +{ + "extensions": { + "code": "BAD_USER_INPUT", + "http": { + "status": 400, + }, + "userFriendlyMessage": "An error occurred.", + }, + "message": "Field "prompt" of required type "String!" was not provided.", + "name": "GraphQLError", +} +`; + +exports[`Agent creation should fail when responseFormat has invalid type 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "agent": [ + { + "errors": [ + { + "code": "INVALID_AGENT_INPUT", + "message": "Response format type must be either "text" or "json"", + "userFriendlyMessage": "Invalid response format type", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "invalidResponseFormatAgent", + }, + "status": "fail", + "type": "create_agent", + }, + ], + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [], + "index": [], + "objectMetadata": [], + "role": [], + "roleTarget": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidAgent": 0, + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRole": 0, + "invalidRoleTarget": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while creating agent", + "name": "GraphQLError", +} +`; + +exports[`Agent creation should fail when responseFormat type is json but schema is missing 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "agent": [ + { + "errors": [ + { + "code": "INVALID_AGENT_INPUT", + "message": "Response format with type "json" must include a schema", + "userFriendlyMessage": "JSON response format requires a schema", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "jsonWithoutSchemaAgent", + }, + "status": "fail", + "type": "create_agent", + }, + ], + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [], + "index": [], + "objectMetadata": [], + "role": [], + "roleTarget": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidAgent": 0, + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRole": 0, + "invalidRoleTarget": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while creating agent", + "name": "GraphQLError", +} +`; + +exports[`Agent creation should fail when responseFormat type is text but schema is present 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "agent": [ + { + "errors": [ + { + "code": "INVALID_AGENT_INPUT", + "message": "Response format with type "text" should not include a schema", + "userFriendlyMessage": "Text response format should not have a schema", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "textWithSchemaAgent", + }, + "status": "fail", + "type": "create_agent", + }, + ], + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [], + "index": [], + "objectMetadata": [], + "role": [], + "roleTarget": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidAgent": 0, + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRole": 0, + "invalidRoleTarget": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while creating agent", + "name": "GraphQLError", +} +`; diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/__snapshots__/failing-agent-deletion.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/agent/__snapshots__/failing-agent-deletion.integration-spec.ts.snap new file mode 100644 index 0000000000..6966b79fd7 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/__snapshots__/failing-agent-deletion.integration-spec.ts.snap @@ -0,0 +1,74 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Agent deletion should fail should fail when attempting to delete a standard agent 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "agent": [ + { + "errors": [ + { + "code": "AGENT_IS_STANDARD", + "message": "Cannot delete standard agent", + "userFriendlyMessage": "Cannot delete standard agent", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + "name": "dashboard-builder", + }, + "status": "fail", + "type": "delete_agent", + }, + ], + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [], + "index": [], + "objectMetadata": [], + "role": [], + "roleTarget": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidAgent": 0, + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRole": 0, + "invalidRoleTarget": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while deleting agent", + "name": "GraphQLError", +} +`; + +exports[`Agent deletion should fail when deleting a non-existent agent 1`] = ` +{ + "extensions": { + "code": "NOT_FOUND", + "subCode": "AGENT_NOT_FOUND", + "userFriendlyMessage": "An error occurred.", + }, + "message": "Agent not found", + "name": "NotFoundError", +} +`; diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/__snapshots__/failing-agent-update.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/agent/__snapshots__/failing-agent-update.integration-spec.ts.snap new file mode 100644 index 0000000000..2709357335 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/__snapshots__/failing-agent-update.integration-spec.ts.snap @@ -0,0 +1,563 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Agent update should fail should fail when attempting to update a standard agent 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "agent": [ + { + "errors": [ + { + "code": "AGENT_IS_STANDARD", + "message": "Cannot update standard agent", + "userFriendlyMessage": "Cannot update standard agent", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + }, + "status": "fail", + "type": "update_agent", + }, + ], + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [], + "index": [], + "objectMetadata": [], + "role": [], + "roleTarget": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidAgent": 0, + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRole": 0, + "invalidRoleTarget": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while updating agent", + "name": "GraphQLError", +} +`; + +exports[`Agent update should fail should fail when updating a non-existent agent 1`] = ` +{ + "extensions": { + "code": "NOT_FOUND", + "subCode": "AGENT_NOT_FOUND", + "userFriendlyMessage": "The agent you are looking for could not be found. It may have been deleted or you may not have access to it.", + }, + "message": "Agent not found", + "name": "NotFoundError", +} +`; + +exports[`Agent update should fail when settings null required properties 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "agent": [ + { + "errors": [ + { + "code": "INVALID_AGENT_INPUT", + "message": "Label cannot be empty", + "userFriendlyMessage": "Label cannot be empty", + }, + { + "code": "INVALID_AGENT_INPUT", + "message": "Prompt cannot be empty", + "userFriendlyMessage": "Prompt cannot be empty", + }, + { + "code": "INVALID_AGENT_INPUT", + "message": "Model ID cannot be empty", + "userFriendlyMessage": "Model ID cannot be empty", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + }, + "status": "fail", + "type": "update_agent", + }, + ], + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [], + "index": [], + "objectMetadata": [], + "role": [], + "roleTarget": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidAgent": 0, + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRole": 0, + "invalidRoleTarget": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while updating agent", + "name": "GraphQLError", +} +`; + +exports[`Agent update should fail when updating label to one that already exists 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "agent": [ + { + "errors": [ + { + "code": "AGENT_ALREADY_EXISTS", + "message": "Agent with name "existingAgentForDuplicateTest" already exists", + "userFriendlyMessage": "An agent with this name already exists", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + }, + "status": "fail", + "type": "update_agent", + }, + ], + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [], + "index": [], + "objectMetadata": [], + "role": [], + "roleTarget": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidAgent": 0, + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRole": 0, + "invalidRoleTarget": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while updating agent", + "name": "GraphQLError", +} +`; + +exports[`Agent update should fail when updating responseFormat type to json without schema 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "agent": [ + { + "errors": [ + { + "code": "INVALID_AGENT_INPUT", + "message": "Response format with type "json" must include a schema", + "userFriendlyMessage": "JSON response format requires a schema", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + }, + "status": "fail", + "type": "update_agent", + }, + ], + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [], + "index": [], + "objectMetadata": [], + "role": [], + "roleTarget": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidAgent": 0, + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRole": 0, + "invalidRoleTarget": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while updating agent", + "name": "GraphQLError", +} +`; + +exports[`Agent update should fail when updating responseFormat type to text with schema 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "agent": [ + { + "errors": [ + { + "code": "INVALID_AGENT_INPUT", + "message": "Response format with type "text" should not include a schema", + "userFriendlyMessage": "Text response format should not have a schema", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + }, + "status": "fail", + "type": "update_agent", + }, + ], + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [], + "index": [], + "objectMetadata": [], + "role": [], + "roleTarget": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidAgent": 0, + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRole": 0, + "invalidRoleTarget": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while updating agent", + "name": "GraphQLError", +} +`; + +exports[`Agent update should fail when updating responseFormat with invalid type 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "agent": [ + { + "errors": [ + { + "code": "INVALID_AGENT_INPUT", + "message": "Response format type must be either "text" or "json"", + "userFriendlyMessage": "Invalid response format type", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + }, + "status": "fail", + "type": "update_agent", + }, + ], + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [], + "index": [], + "objectMetadata": [], + "role": [], + "roleTarget": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidAgent": 0, + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRole": 0, + "invalidRoleTarget": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while updating agent", + "name": "GraphQLError", +} +`; + +exports[`Agent update should fail when updating with empty label 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "agent": [ + { + "errors": [ + { + "code": "INVALID_AGENT_INPUT", + "message": "Label cannot be empty", + "userFriendlyMessage": "Label cannot be empty", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + }, + "status": "fail", + "type": "update_agent", + }, + ], + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [], + "index": [], + "objectMetadata": [], + "role": [], + "roleTarget": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidAgent": 0, + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRole": 0, + "invalidRoleTarget": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while updating agent", + "name": "GraphQLError", +} +`; + +exports[`Agent update should fail when updating with empty modelId 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "agent": [ + { + "errors": [ + { + "code": "INVALID_AGENT_INPUT", + "message": "Model ID cannot be empty", + "userFriendlyMessage": "Model ID cannot be empty", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + }, + "status": "fail", + "type": "update_agent", + }, + ], + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [], + "index": [], + "objectMetadata": [], + "role": [], + "roleTarget": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidAgent": 0, + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRole": 0, + "invalidRoleTarget": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while updating agent", + "name": "GraphQLError", +} +`; + +exports[`Agent update should fail when updating with empty prompt 1`] = ` +{ + "extensions": { + "code": "METADATA_VALIDATION_FAILED", + "errors": { + "agent": [ + { + "errors": [ + { + "code": "INVALID_AGENT_INPUT", + "message": "Prompt cannot be empty", + "userFriendlyMessage": "Prompt cannot be empty", + }, + ], + "flatEntityMinimalInformation": { + "id": Any, + }, + "status": "fail", + "type": "update_agent", + }, + ], + "cronTrigger": [], + "databaseEventTrigger": [], + "fieldMetadata": [], + "index": [], + "objectMetadata": [], + "role": [], + "roleTarget": [], + "routeTrigger": [], + "serverlessFunction": [], + "view": [], + "viewField": [], + "viewFilter": [], + "viewGroup": [], + }, + "message": "Validation failed for 0 object(s) and 0 field(s)", + "summary": { + "invalidAgent": 0, + "invalidCronTrigger": 0, + "invalidDatabaseEventTrigger": 0, + "invalidFieldMetadata": 0, + "invalidIndex": 0, + "invalidObjectMetadata": 0, + "invalidRole": 0, + "invalidRoleTarget": 0, + "invalidRouteTrigger": 0, + "invalidServerlessFunction": 0, + "invalidView": 0, + "invalidViewField": 0, + "invalidViewFilter": 0, + "invalidViewGroup": 0, + "totalErrors": 0, + }, + "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)", + }, + "message": "Multiple validation errors occurred while updating agent", + "name": "GraphQLError", +} +`; diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/failing-agent-creation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/agent/failing-agent-creation.integration-spec.ts new file mode 100644 index 0000000000..f72594e4cb --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/failing-agent-creation.integration-spec.ts @@ -0,0 +1,177 @@ +import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util'; +import { createOneAgent } from 'test/integration/metadata/suites/agent/utils/create-one-agent.util'; +import { deleteOneAgent } from 'test/integration/metadata/suites/agent/utils/delete-one-agent.util'; +import { + eachTestingContextFilter, + type EachTestingContext, +} from 'twenty-shared/testing'; +import { isDefined } from 'twenty-shared/utils'; + +import { type CreateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/create-agent.input'; + +type TestContext = { + input: CreateAgentInput; +}; + +type GlobalTestContext = { + existingAgentLabel: string; +}; + +const globalTestContext: GlobalTestContext = { + existingAgentLabel: 'Existing Test Agent', +}; + +type CreateOneAgentTestingContext = EachTestingContext[]; + +describe('Agent creation should fail', () => { + let existingAgentId: string | undefined; + + beforeAll(async () => { + const { data } = await createOneAgent({ + expectToFail: false, + input: { + label: globalTestContext.existingAgentLabel, + prompt: 'Existing agent for testing', + modelId: 'gpt-4o', + }, + }); + + existingAgentId = data.createOneAgent.id; + }); + + afterAll(async () => { + if (isDefined(existingAgentId)) { + await deleteOneAgent({ + expectToFail: false, + input: { id: existingAgentId }, + }); + } + }); + + const failingAgentCreationTestCases: CreateOneAgentTestingContext = [ + // Missing required properties tests + { + title: 'when label is missing', + context: { + input: { + prompt: 'Test prompt', + modelId: 'gpt-4o', + } as CreateAgentInput, + }, + }, + { + title: 'when prompt is missing', + context: { + input: { + label: 'Test Agent Missing Prompt', + modelId: 'gpt-4o', + } as CreateAgentInput, + }, + }, + { + title: 'when modelId is missing', + context: { + input: { + label: 'Test Agent Missing ModelId', + prompt: 'Test prompt', + } as CreateAgentInput, + }, + }, + { + title: 'when label is empty string', + context: { + input: { + label: '', + prompt: 'Test prompt', + modelId: 'gpt-4o', + }, + }, + }, + { + title: 'when prompt is empty string', + context: { + input: { + label: 'Empty Prompt Agent', + prompt: '', + modelId: 'gpt-4o', + }, + }, + }, + { + title: 'when modelId is empty string', + context: { + input: { + label: 'Empty ModelId Agent', + prompt: 'Test prompt', + modelId: '' as any, + }, + }, + }, + // Name uniqueness test + { + title: 'when computed name already exists', + context: { + input: { + label: globalTestContext.existingAgentLabel, + prompt: 'Duplicate agent', + modelId: 'gpt-4o', + }, + }, + }, + // Invalid response format tests + { + title: 'when responseFormat has invalid type', + context: { + input: { + label: 'Invalid Response Format Agent', + prompt: 'Test prompt', + modelId: 'gpt-4o', + responseFormat: { + type: 'invalid', + } as any, + }, + }, + }, + { + title: 'when responseFormat type is json but schema is missing', + context: { + input: { + label: 'JSON Without Schema Agent', + prompt: 'Test prompt', + modelId: 'gpt-4o', + responseFormat: { + type: 'json', + } as any, + }, + }, + }, + { + title: 'when responseFormat type is text but schema is present', + context: { + input: { + label: 'Text With Schema Agent', + prompt: 'Test prompt', + modelId: 'gpt-4o', + responseFormat: { + type: 'text', + schema: { type: 'object' }, + } as any, + }, + }, + }, + ]; + + it.each(eachTestingContextFilter(failingAgentCreationTestCases))( + '$title', + async ({ context }) => { + const { errors } = await createOneAgent({ + expectToFail: true, + input: context.input, + }); + + expectOneNotInternalServerErrorSnapshot({ + errors, + }); + }, + ); +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/failing-agent-deletion.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/agent/failing-agent-deletion.integration-spec.ts new file mode 100644 index 0000000000..5d1760a2ed --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/failing-agent-deletion.integration-spec.ts @@ -0,0 +1,68 @@ +import { faker } from '@faker-js/faker'; +import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util'; +import { deleteOneAgent } from 'test/integration/metadata/suites/agent/utils/delete-one-agent.util'; +import { findAgents } from 'test/integration/metadata/suites/agent/utils/find-agents.util'; +import { + eachTestingContextFilter, + type EachTestingContext, +} from 'twenty-shared/testing'; + +type TestContext = { + input: () => { + id: string; + }; +}; + +type DeleteOneAgentTestingContext = EachTestingContext[]; + +describe('Agent deletion should fail', () => { + const failingAgentDeletionTestCases: DeleteOneAgentTestingContext = [ + { + title: 'when deleting a non-existent agent', + context: { + input: () => ({ + id: faker.string.uuid(), + }), + }, + }, + ]; + + it.each(eachTestingContextFilter(failingAgentDeletionTestCases))( + '$title', + async ({ context }) => { + const { id } = context.input(); + + const { errors } = await deleteOneAgent({ + expectToFail: true, + input: { + id, + }, + }); + + expectOneNotInternalServerErrorSnapshot({ errors }); + }, + ); + + it('should fail when attempting to delete a standard agent', async () => { + const { data } = await findAgents({ + expectToFail: false, + input: undefined, + gqlFields: 'id name isCustom', + }); + + const dashboardBuilderAgent = data.findManyAgents.find( + (agent) => agent.name === 'dashboard-builder' && agent.isCustom === false, + ); + + expect(dashboardBuilderAgent).toBeDefined(); + + const { errors } = await deleteOneAgent({ + expectToFail: true, + input: { + id: dashboardBuilderAgent!.id, + }, + }); + + expectOneNotInternalServerErrorSnapshot({ errors }); + }); +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/failing-agent-update.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/agent/failing-agent-update.integration-spec.ts new file mode 100644 index 0000000000..a628a053fa --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/failing-agent-update.integration-spec.ts @@ -0,0 +1,226 @@ +import { faker } from '@faker-js/faker'; +import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util'; +import { createOneAgent } from 'test/integration/metadata/suites/agent/utils/create-one-agent.util'; +import { deleteOneAgent } from 'test/integration/metadata/suites/agent/utils/delete-one-agent.util'; +import { findAgents } from 'test/integration/metadata/suites/agent/utils/find-agents.util'; +import { updateOneAgent } from 'test/integration/metadata/suites/agent/utils/update-one-agent.util'; +import { + eachTestingContextFilter, + type EachTestingContext, +} from 'twenty-shared/testing'; + +import { type UpdateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/update-agent.input'; + +type TestContext = { + input: (testSetup: TestSetup) => UpdateAgentInput; +}; + +type TestSetup = { + testAgentId: string; + existingAgentLabelForDuplicate: string; +}; + +type GlobalTestContext = { + existingAgentLabelForDuplicate: string; +}; + +const globalTestContext: GlobalTestContext = { + existingAgentLabelForDuplicate: 'Existing Agent For Duplicate Test', +}; + +type UpdateOneAgentTestingContext = EachTestingContext[]; + +describe('Agent update should fail', () => { + let testAgentId: string; + let existingAgentIdForDuplicate: string; + + beforeAll(async () => { + const { data: duplicateData } = await createOneAgent({ + expectToFail: false, + input: { + label: globalTestContext.existingAgentLabelForDuplicate, + prompt: 'Existing agent for duplicate test', + modelId: 'gpt-4o', + }, + }); + + existingAgentIdForDuplicate = duplicateData.createOneAgent.id; + }); + + beforeEach(async () => { + const { data } = await createOneAgent({ + expectToFail: false, + input: { + label: 'Test Agent To Update', + description: 'Original description', + icon: 'IconRobot', + prompt: 'Original prompt', + modelId: 'gpt-4o', + }, + }); + + testAgentId = data.createOneAgent.id; + }); + + afterEach(async () => { + await deleteOneAgent({ + expectToFail: false, + input: { id: testAgentId }, + }); + }); + + afterAll(async () => { + await deleteOneAgent({ + expectToFail: false, + input: { id: existingAgentIdForDuplicate }, + }); + }); + + const failingAgentUpdateTestCases: UpdateOneAgentTestingContext = [ + { + title: 'when updating label to one that already exists', + context: { + input: (testSetup) => ({ + id: testSetup.testAgentId, + label: testSetup.existingAgentLabelForDuplicate, + }), + }, + }, + { + title: 'when updating with empty label', + context: { + input: (testSetup) => ({ + id: testSetup.testAgentId, + label: '', + }), + }, + }, + { + title: 'when updating with empty prompt', + context: { + input: (testSetup) => ({ + id: testSetup.testAgentId, + prompt: '', + }), + }, + }, + { + title: 'when updating with empty modelId', + context: { + input: (testSetup) => ({ + id: testSetup.testAgentId, + modelId: '' as any, + }), + }, + }, + { + title: 'when settings null required properties', + context: { + input: (testSetup) => ({ + id: testSetup.testAgentId, + label: null, + prompt: null, + modelId: null, + }), + }, + }, + { + title: 'when updating responseFormat with invalid type', + context: { + input: (testSetup) => ({ + id: testSetup.testAgentId, + responseFormat: { + type: 'invalid', + } as any, + }), + }, + }, + { + title: 'when updating responseFormat type to json without schema', + context: { + input: (testSetup) => ({ + id: testSetup.testAgentId, + responseFormat: { + type: 'json', + } as any, + }), + }, + }, + { + title: 'when updating responseFormat type to text with schema', + context: { + input: (testSetup) => ({ + id: testSetup.testAgentId, + responseFormat: { + type: 'text', + schema: { type: 'object' }, + } as any, + }), + }, + }, + ]; + + it.each(eachTestingContextFilter(failingAgentUpdateTestCases))( + '$title', + async ({ context }) => { + const testSetup: TestSetup = { + testAgentId, + existingAgentLabelForDuplicate: + globalTestContext.existingAgentLabelForDuplicate, + }; + + const input = context.input(testSetup); + + const { errors } = await updateOneAgent({ + expectToFail: true, + input, + }); + + expectOneNotInternalServerErrorSnapshot({ + errors, + }); + }, + ); + + it('should fail when updating a non-existent agent', async () => { + const nonExistentAgentId = faker.string.uuid(); + + const { errors } = await updateOneAgent({ + expectToFail: true, + input: { + id: nonExistentAgentId, + label: 'Updated Label', + }, + }); + + expectOneNotInternalServerErrorSnapshot({ + errors, + }); + }); + + it('should fail when attempting to update a standard agent', async () => { + const { data } = await findAgents({ + expectToFail: false, + input: undefined, + gqlFields: 'id name isCustom', + }); + + const dashboardBuilderAgent = data.findManyAgents.find( + (agent) => agent.name === 'dashboard-builder' && agent.isCustom === false, + ); + + expect(dashboardBuilderAgent).toBeDefined(); + + const { errors } = await updateOneAgent({ + expectToFail: true, + input: { + id: dashboardBuilderAgent!.id, + label: 'Attempted Update to Standard Agent', + }, + }); + + expectOneNotInternalServerErrorSnapshot({ + errors, + }); + }); +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/successful-agent-creation.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/agent/successful-agent-creation.integration-spec.ts new file mode 100644 index 0000000000..e33b4cce14 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/successful-agent-creation.integration-spec.ts @@ -0,0 +1,217 @@ +import { createOneAgent } from 'test/integration/metadata/suites/agent/utils/create-one-agent.util'; +import { deleteOneAgent } from 'test/integration/metadata/suites/agent/utils/delete-one-agent.util'; +import { createOneRole } from 'test/integration/metadata/suites/role/utils/create-one-role.util'; +import { deleteOneRole } from 'test/integration/metadata/suites/role/utils/delete-one-role.util'; + +import { type CreateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/create-agent.input'; + +describe('Agent creation should succeed', () => { + let createdAgentId: string; + + afterEach(async () => { + if (createdAgentId) { + await deleteOneAgent({ + expectToFail: false, + input: { id: createdAgentId }, + }); + } + }); + + it('should create a basic custom agent with minimal input', async () => { + const { data } = await createOneAgent({ + expectToFail: false, + input: { + label: 'Test Agent', + prompt: 'You are a helpful test assistant', + modelId: 'gpt-4o', + }, + }); + + createdAgentId = data?.createOneAgent?.id; + + expect(data.createOneAgent).toMatchObject({ + id: expect.any(String), + name: 'testAgent', + label: 'Test Agent', + icon: null, + description: null, + prompt: 'You are a helpful test assistant', + modelId: 'gpt-4o', + responseFormat: { type: 'text' }, + roleId: null, + isCustom: true, + modelConfiguration: null, + evaluationInputs: [], + }); + }); + + it('should create agent with all optional fields', async () => { + const input = { + name: 'customAgentName', + label: 'Custom Agent Label', + icon: 'IconRobot', + description: 'A custom agent with all fields specified', + prompt: 'You are a specialized assistant for testing', + modelId: 'gpt-4o-mini', + responseFormat: { type: 'text' }, + modelConfiguration: { + webSearch: { + enabled: true, + configuration: { + maxTokens: 1000, + temperature: 0.7, + }, + }, + twitterSearch: { + enabled: true, + configuration: {}, + }, + }, + evaluationInputs: ['test input 1', 'test input 2'], + } as const satisfies CreateAgentInput; + const { data } = await createOneAgent({ + expectToFail: false, + input, + }); + + createdAgentId = data?.createOneAgent?.id; + + expect(data.createOneAgent).toMatchObject({ + id: expect.any(String), + ...input, + }); + }); + + it('should create agent with JSON response format', async () => { + const { data } = await createOneAgent({ + expectToFail: false, + input: { + label: 'JSON Response Agent', + prompt: 'Return structured JSON data', + modelId: 'gpt-4o', + responseFormat: { + type: 'json', + schema: { + type: 'object', + properties: { + result: { type: 'string' }, + confidence: { type: 'number' }, + }, + }, + }, + }, + }); + + createdAgentId = data?.createOneAgent?.id; + + expect(data.createOneAgent).toMatchObject({ + id: expect.any(String), + label: 'JSON Response Agent', + prompt: 'Return structured JSON data', + responseFormat: { + type: 'json', + schema: { + type: 'object', + properties: { + result: { type: 'string' }, + confidence: { type: 'number' }, + }, + }, + }, + }); + }); + + it('should create agent and automatically compute name from label', async () => { + const { data } = await createOneAgent({ + expectToFail: false, + input: { + label: 'My Test Agent With Spaces', + prompt: 'Testing name computation', + modelId: 'gpt-4o', + }, + }); + + createdAgentId = data?.createOneAgent?.id; + + expect(data.createOneAgent).toMatchObject({ + id: expect.any(String), + name: 'myTestAgentWithSpaces', + label: 'My Test Agent With Spaces', + }); + }); + + it('should sanitize input by trimming whitespace', async () => { + const { data } = await createOneAgent({ + expectToFail: false, + input: { + name: ' agentWithSpaces ', + label: ' Agent With Spaces ', + icon: ' IconRobot ', + description: ' Description with spaces ', + prompt: ' Prompt with spaces ', + modelId: 'gpt-4o', + }, + }); + + createdAgentId = data?.createOneAgent?.id; + + expect(data.createOneAgent).toMatchObject({ + id: expect.any(String), + name: 'agentWithSpaces', + label: 'Agent With Spaces', + icon: 'IconRobot', + description: 'Description with spaces', + prompt: 'Prompt with spaces', + }); + }); + + it('should create agent with role assignment', async () => { + // First, create a role that can be assigned to agents + const { data: roleData } = await createOneRole({ + expectToFail: false, + input: { + label: 'Test Agent Role', + description: 'A role for agent testing', + canUpdateAllSettings: false, + canAccessAllTools: true, + canReadAllObjectRecords: true, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canBeAssignedToUsers: false, + canBeAssignedToAgents: true, + canBeAssignedToApiKeys: false, + }, + }); + + const createdRoleId = roleData?.createOneRole?.id; + + // Create agent with role assignment + const { data } = await createOneAgent({ + expectToFail: false, + input: { + label: 'Agent With Role', + prompt: 'Agent with role assignment', + modelId: 'gpt-4o', + roleId: createdRoleId, + }, + }); + + createdAgentId = data?.createOneAgent?.id; + + expect(data.createOneAgent).toMatchObject({ + id: expect.any(String), + label: 'Agent With Role', + prompt: 'Agent with role assignment', + modelId: 'gpt-4o', + roleId: createdRoleId, + isCustom: true, + }); + + // Clean up the role + await deleteOneRole({ + expectToFail: false, + input: { idToDelete: createdRoleId }, + }); + }); +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/successful-agent-deletion.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/agent/successful-agent-deletion.integration-spec.ts new file mode 100644 index 0000000000..c6a4fc20c4 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/successful-agent-deletion.integration-spec.ts @@ -0,0 +1,41 @@ +import { createOneAgent } from 'test/integration/metadata/suites/agent/utils/create-one-agent.util'; +import { deleteOneAgent } from 'test/integration/metadata/suites/agent/utils/delete-one-agent.util'; +import { findOneAgent } from 'test/integration/metadata/suites/agent/utils/find-one-agent.util'; + +describe('Agent deletion should succeed', () => { + it('should successfully delete a custom agent', async () => { + const { data: createData } = await createOneAgent({ + expectToFail: false, + input: { + label: 'Agent To Delete', + prompt: 'This agent will be deleted', + modelId: 'gpt-4o', + }, + }); + + const agentId = createData.createOneAgent.id; + + const { data: findBeforeData } = await findOneAgent({ + expectToFail: false, + input: { id: agentId }, + }); + + expect(findBeforeData.findOneAgent.id).toBe(agentId); + + const { data: deleteData } = await deleteOneAgent({ + expectToFail: false, + input: { id: agentId }, + }); + + expect(deleteData.deleteOneAgent).toMatchObject({ + id: agentId, + }); + + const { errors: findAfterErrors } = await findOneAgent({ + expectToFail: true, + input: { id: agentId }, + }); + + expect(findAfterErrors).toBeDefined(); + }); +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/successful-agent-role-update.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/agent/successful-agent-role-update.integration-spec.ts new file mode 100644 index 0000000000..965d2d589c --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/successful-agent-role-update.integration-spec.ts @@ -0,0 +1,159 @@ +import { createOneAgent } from 'test/integration/metadata/suites/agent/utils/create-one-agent.util'; +import { deleteOneAgent } from 'test/integration/metadata/suites/agent/utils/delete-one-agent.util'; +import { updateOneAgent } from 'test/integration/metadata/suites/agent/utils/update-one-agent.util'; +import { createOneRole } from 'test/integration/metadata/suites/role/utils/create-one-role.util'; +import { deleteOneRole } from 'test/integration/metadata/suites/role/utils/delete-one-role.util'; + +describe('Agent role update should succeed', () => { + let testAgentId: string; + let testRoleId: string; + let secondTestRoleId: string; + + beforeAll(async () => { + const { data: role1Data } = await createOneRole({ + expectToFail: false, + input: { + label: 'Test Agent Role 1', + description: 'First test role for agent assignment', + canUpdateAllSettings: false, + canAccessAllTools: true, + canReadAllObjectRecords: true, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canBeAssignedToUsers: false, + canBeAssignedToAgents: true, + canBeAssignedToApiKeys: false, + }, + }); + + const { data: role2Data } = await createOneRole({ + expectToFail: false, + input: { + label: 'Test Agent Role 2', + description: 'Second test role for agent assignment', + canUpdateAllSettings: false, + canAccessAllTools: false, + canReadAllObjectRecords: true, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canBeAssignedToUsers: false, + canBeAssignedToAgents: true, + canBeAssignedToApiKeys: false, + }, + }); + + testRoleId = role1Data.createOneRole.id; + secondTestRoleId = role2Data.createOneRole.id; + }); + + afterAll(async () => { + await deleteOneRole({ + expectToFail: false, + input: { idToDelete: testRoleId }, + }); + await deleteOneRole({ + expectToFail: false, + input: { idToDelete: secondTestRoleId }, + }); + }); + + afterEach(async () => { + if (testAgentId) { + await deleteOneAgent({ + expectToFail: false, + input: { id: testAgentId }, + }); + } + }); + + it('should add a role to an agent that does not have one initially', async () => { + const { data: createData } = await createOneAgent({ + expectToFail: false, + input: { + label: 'Agent Without Role', + prompt: 'Test prompt', + modelId: 'gpt-4o', + }, + }); + + testAgentId = createData.createOneAgent.id; + + expect(createData.createOneAgent.roleId).toBeNull(); + + const { data: updateData } = await updateOneAgent({ + expectToFail: false, + input: { + id: testAgentId, + roleId: testRoleId, + }, + }); + + expect(updateData.updateOneAgent).toMatchObject({ + id: testAgentId, + roleId: testRoleId, + label: 'Agent Without Role', + }); + }); + + it('should change the role of an agent that already has one', async () => { + const { data: createData } = await createOneAgent({ + expectToFail: false, + input: { + label: 'Agent With Initial Role', + prompt: 'Test prompt', + modelId: 'gpt-4o', + roleId: testRoleId, + }, + }); + + testAgentId = createData.createOneAgent.id; + + expect(createData.createOneAgent.roleId).toBe(testRoleId); + + const { data: updateData } = await updateOneAgent({ + expectToFail: false, + input: { + id: testAgentId, + roleId: secondTestRoleId, + }, + }); + + expect(updateData.updateOneAgent).toMatchObject({ + id: testAgentId, + roleId: secondTestRoleId, + label: 'Agent With Initial Role', + }); + }); + + it('should remove a role from an agent that already has one', async () => { + const { data: createData } = await createOneAgent({ + expectToFail: false, + input: { + label: 'Agent With Role To Remove', + prompt: 'Test prompt', + modelId: 'gpt-4o', + roleId: testRoleId, + }, + }); + + testAgentId = createData.createOneAgent.id; + + expect(createData.createOneAgent.roleId).toBe(testRoleId); + + const { data: updateData } = await updateOneAgent({ + expectToFail: false, + input: { + id: testAgentId, + roleId: null, + }, + }); + + expect(updateData.updateOneAgent).toMatchObject({ + id: testAgentId, + roleId: null, + label: 'Agent With Role To Remove', + }); + }); +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/successful-agent-update.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/agent/successful-agent-update.integration-spec.ts new file mode 100644 index 0000000000..3dd4248b7b --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/successful-agent-update.integration-spec.ts @@ -0,0 +1,305 @@ +import { createOneAgent } from 'test/integration/metadata/suites/agent/utils/create-one-agent.util'; +import { deleteOneAgent } from 'test/integration/metadata/suites/agent/utils/delete-one-agent.util'; +import { updateOneAgent } from 'test/integration/metadata/suites/agent/utils/update-one-agent.util'; + +describe('Agent update should succeed', () => { + let testAgentId: string; + + beforeEach(async () => { + const { data } = await createOneAgent({ + expectToFail: false, + input: { + label: 'Original Agent Label', + description: 'Original description', + icon: 'IconRobot', + prompt: 'Original prompt', + modelId: 'gpt-4o', + responseFormat: { type: 'text' }, + evaluationInputs: ['input 1'], + }, + }); + + testAgentId = data.createOneAgent.id; + }); + + afterEach(async () => { + await deleteOneAgent({ + expectToFail: false, + input: { id: testAgentId }, + }); + }); + + it('should update agent label', async () => { + const { data } = await updateOneAgent({ + expectToFail: false, + input: { + id: testAgentId, + label: 'Updated Agent Label', + }, + }); + + expect(data.updateOneAgent).toMatchObject({ + id: testAgentId, + label: 'Updated Agent Label', + description: 'Original description', + icon: 'IconRobot', + prompt: 'Original prompt', + }); + }); + + it('should update agent description', async () => { + const { data } = await updateOneAgent({ + expectToFail: false, + input: { + id: testAgentId, + description: 'Updated description for the agent', + }, + }); + + expect(data.updateOneAgent).toMatchObject({ + id: testAgentId, + label: 'Original Agent Label', + description: 'Updated description for the agent', + }); + }); + + it('should update agent icon', async () => { + const { data } = await updateOneAgent({ + expectToFail: false, + input: { + id: testAgentId, + icon: 'IconSparkles', + }, + }); + + expect(data.updateOneAgent).toMatchObject({ + id: testAgentId, + icon: 'IconSparkles', + }); + }); + + it('should update agent prompt', async () => { + const { data } = await updateOneAgent({ + expectToFail: false, + input: { + id: testAgentId, + prompt: 'Updated prompt with new instructions', + }, + }); + + expect(data.updateOneAgent).toMatchObject({ + id: testAgentId, + prompt: 'Updated prompt with new instructions', + }); + }); + + it('should update agent modelId', async () => { + const { data } = await updateOneAgent({ + expectToFail: false, + input: { + id: testAgentId, + modelId: 'gpt-4o-mini', + }, + }); + + expect(data.updateOneAgent).toMatchObject({ + id: testAgentId, + modelId: 'gpt-4o-mini', + }); + }); + + it('should update agent responseFormat from text to json', async () => { + const { data } = await updateOneAgent({ + expectToFail: false, + input: { + id: testAgentId, + responseFormat: { + type: 'json', + schema: { + type: 'object', + properties: { + answer: { type: 'string' }, + confidence: { type: 'number' }, + }, + }, + }, + }, + }); + + expect(data.updateOneAgent).toMatchObject({ + id: testAgentId, + responseFormat: { + type: 'json', + schema: { + type: 'object', + properties: { + answer: { type: 'string' }, + confidence: { type: 'number' }, + }, + }, + }, + }); + }); + + it('should update agent responseFormat from json to text', async () => { + await updateOneAgent({ + expectToFail: false, + input: { + id: testAgentId, + responseFormat: { + type: 'json', + schema: { type: 'object', properties: {} }, + }, + }, + }); + + const { data } = await updateOneAgent({ + expectToFail: false, + input: { + id: testAgentId, + responseFormat: { type: 'text' }, + }, + }); + + expect(data.updateOneAgent).toMatchObject({ + id: testAgentId, + responseFormat: { type: 'text' }, + }); + }); + + it('should update agent modelConfiguration', async () => { + const { data } = await updateOneAgent({ + expectToFail: false, + input: { + id: testAgentId, + modelConfiguration: { + twitterSearch: { + enabled: true, + configuration: { + temperature: 0.9, + maxTokens: 2000, + topP: 0.95, + }, + }, + }, + }, + }); + + expect(data.updateOneAgent).toMatchObject({ + id: testAgentId, + modelConfiguration: { + twitterSearch: { + enabled: true, + configuration: { + temperature: 0.9, + maxTokens: 2000, + topP: 0.95, + }, + }, + }, + }); + }); + + it('should update agent evaluationInputs', async () => { + const { data } = await updateOneAgent({ + expectToFail: false, + input: { + id: testAgentId, + evaluationInputs: ['new input 1', 'new input 2', 'new input 3'], + }, + }); + + expect(data.updateOneAgent).toMatchObject({ + id: testAgentId, + evaluationInputs: ['new input 1', 'new input 2', 'new input 3'], + }); + }); + + it('should update multiple agent properties at once', async () => { + const { data } = await updateOneAgent({ + expectToFail: false, + input: { + id: testAgentId, + label: 'Comprehensive Update', + description: 'Updated multiple fields', + icon: 'IconBrain', + prompt: 'New comprehensive prompt', + modelId: 'gpt-4o-mini', + responseFormat: { + type: 'json', + schema: { type: 'object', properties: {} }, + }, + evaluationInputs: ['eval 1', 'eval 2'], + }, + }); + + expect(data.updateOneAgent).toMatchObject({ + id: testAgentId, + label: 'Comprehensive Update', + description: 'Updated multiple fields', + icon: 'IconBrain', + prompt: 'New comprehensive prompt', + modelId: 'gpt-4o-mini', + responseFormat: { + type: 'json', + schema: { type: 'object' }, + }, + evaluationInputs: ['eval 1', 'eval 2'], + }); + }); + + it('should sanitize input by trimming whitespace', async () => { + const { data } = await updateOneAgent({ + expectToFail: false, + input: { + id: testAgentId, + label: ' Updated Label With Spaces ', + icon: ' IconRobot ', + description: ' Description with spaces ', + prompt: ' Prompt with spaces ', + }, + }); + + expect(data.updateOneAgent).toMatchObject({ + id: testAgentId, + label: 'Updated Label With Spaces', + icon: 'IconRobot', + description: 'Description with spaces', + prompt: 'Prompt with spaces', + }); + }); + + it('should clear optional fields by setting to null', async () => { + const { data } = await updateOneAgent({ + expectToFail: false, + input: { + id: testAgentId, + icon: null, + description: null, + modelConfiguration: null, + } as any, + }); + + expect(data.updateOneAgent).toMatchObject({ + id: testAgentId, + icon: null, + description: null, + modelConfiguration: null, + }); + }); + + it('should clear evaluationInputs by setting to empty array', async () => { + const { data } = await updateOneAgent({ + expectToFail: false, + input: { + id: testAgentId, + evaluationInputs: [], + }, + }); + + expect(data.updateOneAgent).toMatchObject({ + id: testAgentId, + evaluationInputs: [], + }); + }); +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/utils/create-one-agent-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/agent/utils/create-one-agent-query-factory.util.ts new file mode 100644 index 0000000000..9e8507ee3c --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/utils/create-one-agent-query-factory.util.ts @@ -0,0 +1,40 @@ +import gql from 'graphql-tag'; +import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; + +import { type CreateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/create-agent.input'; + +export type CreateOneAgentFactoryInput = CreateAgentInput; + +const DEFAULT_AGENT_GQL_FIELDS = ` + id + name + label + icon + description + prompt + modelId + responseFormat + roleId + isCustom + applicationId + modelConfiguration + evaluationInputs + createdAt + updatedAt +`; + +export const createOneAgentQueryFactory = ({ + input, + gqlFields = DEFAULT_AGENT_GQL_FIELDS, +}: PerformMetadataQueryParams) => ({ + query: gql` + mutation CreateOneAgent($input: CreateAgentInput!) { + createOneAgent(input: $input) { + ${gqlFields} + } + } + `, + variables: { + input, + }, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/utils/create-one-agent.util.ts b/packages/twenty-server/test/integration/metadata/suites/agent/utils/create-one-agent.util.ts new file mode 100644 index 0000000000..397ca54cd7 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/utils/create-one-agent.util.ts @@ -0,0 +1,43 @@ +import { + type CreateOneAgentFactoryInput, + createOneAgentQueryFactory, +} from 'test/integration/metadata/suites/agent/utils/create-one-agent-query-factory.util'; +import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; +import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type'; +import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; +import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util'; +import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; + +import { type AgentDTO } from 'src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto'; + +export const createOneAgent = async ({ + input, + gqlFields, + expectToFail = false, + token, +}: PerformMetadataQueryParams): CommonResponseBody<{ + createOneAgent: AgentDTO; +}> => { + const graphqlOperation = createOneAgentQueryFactory({ + input, + gqlFields, + }); + + const response = await makeMetadataAPIRequest(graphqlOperation, token); + + if (expectToFail === true) { + warnIfNoErrorButExpectedToFail({ + response, + errorMessage: 'Agent creation should have failed but did not', + }); + } + + if (expectToFail === false) { + warnIfErrorButNotExpectedToFail({ + response, + errorMessage: 'Agent creation has failed but should not', + }); + } + + return { data: response.body.data, errors: response.body.errors }; +}; diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/utils/delete-one-agent-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/agent/utils/delete-one-agent-query-factory.util.ts new file mode 100644 index 0000000000..6f642c63c8 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/utils/delete-one-agent-query-factory.util.ts @@ -0,0 +1,23 @@ +import gql from 'graphql-tag'; +import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; + +export type DeleteOneAgentFactoryInput = { + id: string; +}; + +export const deleteOneAgentQueryFactory = ({ + input, +}: PerformMetadataQueryParams) => ({ + query: gql` + mutation DeleteOneAgent($input: AgentIdInput!) { + deleteOneAgent(input: $input) { + id + } + } + `, + variables: { + input: { + id: input.id, + }, + }, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/utils/delete-one-agent.util.ts b/packages/twenty-server/test/integration/metadata/suites/agent/utils/delete-one-agent.util.ts new file mode 100644 index 0000000000..19a6653037 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/utils/delete-one-agent.util.ts @@ -0,0 +1,41 @@ +import { + type DeleteOneAgentFactoryInput, + deleteOneAgentQueryFactory, +} from 'test/integration/metadata/suites/agent/utils/delete-one-agent-query-factory.util'; +import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; +import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type'; +import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; +import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util'; +import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; + +export const deleteOneAgent = async ({ + input, + gqlFields, + expectToFail = false, + token, +}: PerformMetadataQueryParams): CommonResponseBody<{ + deleteOneAgent: { id: string }; +}> => { + const graphqlOperation = deleteOneAgentQueryFactory({ + input, + gqlFields, + }); + + const response = await makeMetadataAPIRequest(graphqlOperation, token); + + if (expectToFail === true) { + warnIfNoErrorButExpectedToFail({ + response, + errorMessage: 'Agent deletion should have failed but did not', + }); + } + + if (expectToFail === false) { + warnIfErrorButNotExpectedToFail({ + response, + errorMessage: 'Agent deletion has failed but should not', + }); + } + + return { data: response.body.data, errors: response.body.errors }; +}; diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/utils/find-agents-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/agent/utils/find-agents-query-factory.util.ts new file mode 100644 index 0000000000..e98a6f0e43 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/utils/find-agents-query-factory.util.ts @@ -0,0 +1,32 @@ +import gql from 'graphql-tag'; +import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; + +const DEFAULT_AGENT_GQL_FIELDS = ` + id + name + label + icon + description + prompt + modelId + responseFormat + roleId + isCustom + applicationId + modelConfiguration + evaluationInputs + createdAt + updatedAt +`; + +export const findAgentsQueryFactory = ({ + gqlFields = DEFAULT_AGENT_GQL_FIELDS, +}: PerformMetadataQueryParams) => ({ + query: gql` + query findManyAgents { + findManyAgents { + ${gqlFields} + } + } + `, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/utils/find-agents.util.ts b/packages/twenty-server/test/integration/metadata/suites/agent/utils/find-agents.util.ts new file mode 100644 index 0000000000..bc03dd3a56 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/utils/find-agents.util.ts @@ -0,0 +1,39 @@ +import { findAgentsQueryFactory } from 'test/integration/metadata/suites/agent/utils/find-agents-query-factory.util'; +import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; +import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type'; +import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; +import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util'; +import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; + +import { type AgentDTO } from 'src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto'; + +export const findAgents = async ({ + gqlFields, + expectToFail = false, + token, +}: PerformMetadataQueryParams): CommonResponseBody<{ + findManyAgents: AgentDTO[]; +}> => { + const graphqlOperation = findAgentsQueryFactory({ + gqlFields, + input: undefined, + }); + + const response = await makeMetadataAPIRequest(graphqlOperation, token); + + if (expectToFail === true) { + warnIfNoErrorButExpectedToFail({ + response, + errorMessage: 'Finding agents should have failed but did not', + }); + } + + if (expectToFail === false) { + warnIfErrorButNotExpectedToFail({ + response, + errorMessage: 'Finding agents has failed but should not', + }); + } + + return { data: response.body.data, errors: response.body.errors }; +}; diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/utils/find-one-agent-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/agent/utils/find-one-agent-query-factory.util.ts new file mode 100644 index 0000000000..c77c43bbe6 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/utils/find-one-agent-query-factory.util.ts @@ -0,0 +1,42 @@ +import gql from 'graphql-tag'; +import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; + +export type FindOneAgentFactoryInput = { + id: string; +}; + +const DEFAULT_AGENT_GQL_FIELDS = ` + id + name + label + icon + description + prompt + modelId + responseFormat + roleId + isCustom + applicationId + modelConfiguration + evaluationInputs + createdAt + updatedAt +`; + +export const findOneAgentQueryFactory = ({ + input, + gqlFields = DEFAULT_AGENT_GQL_FIELDS, +}: PerformMetadataQueryParams) => ({ + query: gql` + query FindOneAgent($input: AgentIdInput!) { + findOneAgent(input: $input) { + ${gqlFields} + } + } + `, + variables: { + input: { + id: input.id, + }, + }, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/utils/find-one-agent.util.ts b/packages/twenty-server/test/integration/metadata/suites/agent/utils/find-one-agent.util.ts new file mode 100644 index 0000000000..9cdde5141f --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/utils/find-one-agent.util.ts @@ -0,0 +1,43 @@ +import { + type FindOneAgentFactoryInput, + findOneAgentQueryFactory, +} from 'test/integration/metadata/suites/agent/utils/find-one-agent-query-factory.util'; +import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; +import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type'; +import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; +import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util'; +import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; + +import { type AgentDTO } from 'src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto'; + +export const findOneAgent = async ({ + input, + gqlFields, + expectToFail = false, + token, +}: PerformMetadataQueryParams): CommonResponseBody<{ + findOneAgent: AgentDTO; +}> => { + const graphqlOperation = findOneAgentQueryFactory({ + input, + gqlFields, + }); + + const response = await makeMetadataAPIRequest(graphqlOperation, token); + + if (expectToFail === true) { + warnIfNoErrorButExpectedToFail({ + response, + errorMessage: 'Finding agent should have failed but did not', + }); + } + + if (expectToFail === false) { + warnIfErrorButNotExpectedToFail({ + response, + errorMessage: 'Finding agent has failed but should not', + }); + } + + return { data: response.body.data, errors: response.body.errors }; +}; diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/utils/update-one-agent-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/agent/utils/update-one-agent-query-factory.util.ts new file mode 100644 index 0000000000..8d4e613fd8 --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/utils/update-one-agent-query-factory.util.ts @@ -0,0 +1,40 @@ +import gql from 'graphql-tag'; +import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; + +import { type UpdateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/update-agent.input'; + +export type UpdateOneAgentFactoryInput = UpdateAgentInput; + +const DEFAULT_AGENT_GQL_FIELDS = ` + id + name + label + icon + description + prompt + modelId + responseFormat + roleId + isCustom + applicationId + modelConfiguration + evaluationInputs + createdAt + updatedAt +`; + +export const updateOneAgentQueryFactory = ({ + gqlFields = DEFAULT_AGENT_GQL_FIELDS, + input, +}: PerformMetadataQueryParams) => ({ + query: gql` + mutation UpdateOneAgent($input: UpdateAgentInput!) { + updateOneAgent(input: $input) { + ${gqlFields} + } + } + `, + variables: { + input, + }, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/utils/update-one-agent.util.ts b/packages/twenty-server/test/integration/metadata/suites/agent/utils/update-one-agent.util.ts new file mode 100644 index 0000000000..c53a49ba9a --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/agent/utils/update-one-agent.util.ts @@ -0,0 +1,43 @@ +import { + type UpdateOneAgentFactoryInput, + updateOneAgentQueryFactory, +} from 'test/integration/metadata/suites/agent/utils/update-one-agent-query-factory.util'; +import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; +import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type'; +import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; +import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util'; +import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; + +import { type AgentDTO } from 'src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto'; + +export const updateOneAgent = async ({ + input, + gqlFields, + expectToFail = false, + token, +}: PerformMetadataQueryParams): CommonResponseBody<{ + updateOneAgent: AgentDTO; +}> => { + const graphqlOperation = updateOneAgentQueryFactory({ + input, + gqlFields, + }); + + const response = await makeMetadataAPIRequest(graphqlOperation, token); + + if (expectToFail === true) { + warnIfNoErrorButExpectedToFail({ + response, + errorMessage: 'Agent update should have failed but did not', + }); + } + + if (expectToFail === false) { + warnIfErrorButNotExpectedToFail({ + response, + errorMessage: 'Agent update has failed but should not', + }); + } + + return { data: response.body.data, errors: response.body.errors }; +}; diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/create-one-field-metadata.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/create-one-field-metadata.integration-spec.ts.snap index 2e10dfa43d..615d87bd2f 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/create-one-field-metadata.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/create-one-field-metadata.integration-spec.ts.snap @@ -5,6 +5,7 @@ exports[`createOne FieldMetadataService name/label sync should return an error w "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -39,6 +40,7 @@ exports[`createOne FieldMetadataService name/label sync should return an error w }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/failing-create-one-field-metadata.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/failing-create-one-field-metadata.integration-spec.ts.snap index 8de33c7628..78e6bae2b2 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/failing-create-one-field-metadata.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/failing-create-one-field-metadata.integration-spec.ts.snap @@ -6,6 +6,7 @@ exports[`Failing create field metadata tests suite should fail to create NUMERIC "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -40,6 +41,7 @@ exports[`Failing create field metadata tests suite should fail to create NUMERIC }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -69,6 +71,7 @@ exports[`Failing create field metadata tests suite should fail to create POSITIO "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -103,6 +106,7 @@ exports[`Failing create field metadata tests suite should fail to create POSITIO }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -132,6 +136,7 @@ exports[`Failing create field metadata tests suite should fail to create TS_VECT "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -159,6 +164,7 @@ exports[`Failing create field metadata tests suite should fail to create TS_VECT }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/update-one-field-metadata.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/update-one-field-metadata.integration-spec.ts.snap index 0a7864f32a..fbb21c8ee7 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/update-one-field-metadata.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/update-one-field-metadata.integration-spec.ts.snap @@ -6,6 +6,7 @@ exports[`updateOne failing update should not update a field name if it is not sy "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -39,6 +40,7 @@ exports[`updateOne failing update should not update a field name if it is not sy }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -68,6 +70,7 @@ exports[`updateOne failing update should throw if the field name is not availabl "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -107,6 +110,7 @@ exports[`updateOne failing update should throw if the field name is not availabl }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-field-metadata-enum-v2.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-field-metadata-enum-v2.integration-spec.ts.snap index 5050922fc6..0d9f4be6d8 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-field-metadata-enum-v2.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-create-one-field-metadata-enum-v2.integration-spec.ts.snap @@ -6,6 +6,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -40,6 +41,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -69,6 +71,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -103,6 +106,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -132,6 +136,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -165,6 +170,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -194,6 +200,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -242,6 +249,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -271,6 +279,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -319,6 +328,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -348,6 +358,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -396,6 +407,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -425,6 +437,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -473,6 +486,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -502,6 +516,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -535,6 +550,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -564,6 +580,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -598,6 +615,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -627,6 +645,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -665,6 +684,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -694,6 +714,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -727,6 +748,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -756,6 +778,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -790,6 +813,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -819,6 +843,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -852,6 +877,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -881,6 +907,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -929,6 +956,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -958,6 +986,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -991,6 +1020,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1020,6 +1050,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1053,6 +1084,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1082,6 +1114,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1116,6 +1149,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1145,6 +1179,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1179,6 +1214,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1208,6 +1244,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1241,6 +1278,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1270,6 +1308,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1304,6 +1343,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1333,6 +1373,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1366,6 +1407,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1395,6 +1437,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1433,6 +1476,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1462,6 +1506,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1495,6 +1540,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1524,6 +1570,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1558,6 +1605,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1587,6 +1635,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1620,6 +1669,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1649,6 +1699,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1687,6 +1738,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1716,6 +1768,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1749,6 +1802,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1778,6 +1832,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1812,6 +1867,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1841,6 +1897,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1874,6 +1931,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1903,6 +1961,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1941,6 +2000,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1970,6 +2030,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2003,6 +2064,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2032,6 +2094,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2065,6 +2128,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2094,6 +2158,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2132,6 +2197,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2161,6 +2227,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2194,6 +2261,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2223,6 +2291,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2256,6 +2325,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2285,6 +2355,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2319,6 +2390,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2348,6 +2420,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2381,6 +2454,7 @@ exports[`Failing create field metadata MULTI_SELECT tests suite v2 Create should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2410,6 +2484,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2448,6 +2523,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2477,6 +2553,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2510,6 +2587,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2539,6 +2617,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2572,6 +2651,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2601,6 +2681,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2649,6 +2730,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2678,6 +2760,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2726,6 +2809,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2755,6 +2839,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2803,6 +2888,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2832,6 +2918,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2880,6 +2967,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2909,6 +2997,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2943,6 +3032,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2972,6 +3062,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3010,6 +3101,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3039,6 +3131,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3072,6 +3165,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3101,6 +3195,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3135,6 +3230,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3164,6 +3260,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3197,6 +3294,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3226,6 +3324,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3259,6 +3358,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3288,6 +3388,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3321,6 +3422,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3350,6 +3452,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3384,6 +3487,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3413,6 +3517,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3446,6 +3551,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3475,6 +3581,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3509,6 +3616,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3538,6 +3646,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3571,6 +3680,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3600,6 +3710,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3638,6 +3749,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3667,6 +3779,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3700,6 +3813,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3729,6 +3843,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3763,6 +3878,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3792,6 +3908,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3825,6 +3942,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3854,6 +3972,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3892,6 +4011,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3921,6 +4041,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3954,6 +4075,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3983,6 +4105,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -4017,6 +4140,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -4046,6 +4170,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -4079,6 +4204,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -4108,6 +4234,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -4146,6 +4273,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -4175,6 +4303,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -4208,6 +4337,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -4237,6 +4367,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -4270,6 +4401,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -4299,6 +4431,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -4337,6 +4470,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -4366,6 +4500,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -4399,6 +4534,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -4428,6 +4564,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -4461,6 +4598,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -4490,6 +4628,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -4524,6 +4663,7 @@ exports[`Failing create field metadata SELECT tests suite v2 Create should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-update-one-enum-field-metadata-v2.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-update-one-enum-field-metadata-v2.integration-spec.ts.snap index 3d0fa9a94c..0935f6b827 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-update-one-enum-field-metadata-v2.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/enum/__snapshots__/failing-update-one-enum-field-metadata-v2.integration-spec.ts.snap @@ -6,6 +6,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -40,6 +41,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -69,6 +71,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -103,6 +106,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -132,6 +136,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -165,6 +170,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -194,6 +200,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -242,6 +249,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -271,6 +279,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -319,6 +328,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -348,6 +358,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -396,6 +407,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -425,6 +437,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -473,6 +486,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -502,6 +516,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -535,6 +550,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -564,6 +580,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -598,6 +615,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -627,6 +645,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -665,6 +684,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -694,6 +714,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -727,6 +748,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -756,6 +778,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -790,6 +813,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -819,6 +843,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -852,6 +877,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -881,6 +907,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -929,6 +956,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -958,6 +986,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -991,6 +1020,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1020,6 +1050,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1053,6 +1084,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1082,6 +1114,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1116,6 +1149,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1145,6 +1179,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1179,6 +1214,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1208,6 +1244,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1241,6 +1278,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1270,6 +1308,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1304,6 +1343,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1333,6 +1373,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1366,6 +1407,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1395,6 +1437,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1433,6 +1476,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1462,6 +1506,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1495,6 +1540,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1524,6 +1570,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1557,6 +1604,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1586,6 +1634,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1624,6 +1673,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1653,6 +1703,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1686,6 +1737,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1715,6 +1767,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1749,6 +1802,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1778,6 +1832,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1811,6 +1866,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1840,6 +1896,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1878,6 +1935,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1907,6 +1965,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1940,6 +1999,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1969,6 +2029,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2002,6 +2063,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2031,6 +2093,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2069,6 +2132,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2098,6 +2162,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2131,6 +2196,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2160,6 +2226,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2193,6 +2260,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2222,6 +2290,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2255,6 +2324,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2284,6 +2354,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2317,6 +2388,7 @@ exports[`Failing update field metadata MULTI_SELECT tests suite v2 Update should }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2346,6 +2418,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2384,6 +2457,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2413,6 +2487,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2446,6 +2521,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2475,6 +2551,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2508,6 +2585,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2537,6 +2615,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2585,6 +2664,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2614,6 +2694,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2662,6 +2743,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2691,6 +2773,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2739,6 +2822,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2768,6 +2852,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2816,6 +2901,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2845,6 +2931,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2879,6 +2966,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2908,6 +2996,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2946,6 +3035,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2975,6 +3065,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3008,6 +3099,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3037,6 +3129,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3071,6 +3164,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3100,6 +3194,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3133,6 +3228,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3162,6 +3258,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3195,6 +3292,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3224,6 +3322,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3257,6 +3356,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3286,6 +3386,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3320,6 +3421,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3349,6 +3451,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3382,6 +3485,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3411,6 +3515,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3445,6 +3550,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3474,6 +3580,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3507,6 +3614,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3536,6 +3644,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3574,6 +3683,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3603,6 +3713,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3636,6 +3747,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3665,6 +3777,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3698,6 +3811,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3727,6 +3841,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3765,6 +3880,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3794,6 +3910,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3827,6 +3944,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3856,6 +3974,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3890,6 +4009,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3919,6 +4039,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3952,6 +4073,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3981,6 +4103,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -4019,6 +4142,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -4048,6 +4172,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -4081,6 +4206,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -4110,6 +4236,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -4143,6 +4270,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -4172,6 +4300,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -4210,6 +4339,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -4239,6 +4369,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -4272,6 +4403,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -4301,6 +4433,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -4334,6 +4467,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -4363,6 +4497,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -4396,6 +4531,7 @@ exports[`Failing update field metadata SELECT tests suite v2 Update should fail }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts.snap index 9b5972e1ad..d5c0cbefa7 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-add-one-target-to-metadata-morph-relation-v2.integration-spec.ts.snap @@ -5,6 +5,7 @@ exports[`updateOne FieldMetadataService morph relation fields v2 - Add one targe "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -82,6 +83,7 @@ exports[`updateOne FieldMetadataService morph relation fields v2 - Add one targe }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -109,6 +111,7 @@ exports[`updateOne FieldMetadataService morph relation fields v2 - Add one targe "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -174,6 +177,7 @@ exports[`updateOne FieldMetadataService morph relation fields v2 - Add one targe }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-create-one-field-metadata-morph-relation-v2.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-create-one-field-metadata-morph-relation-v2.integration-spec.ts.snap index 824125e3e8..2f48fcae34 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-create-one-field-metadata-morph-relation-v2.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/morph-relation/__snapshots__/failing-create-one-field-metadata-morph-relation-v2.integration-spec.ts.snap @@ -5,6 +5,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 Morh re "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -87,6 +88,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 Morh re }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -114,6 +116,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 Morh re "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -148,6 +151,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 Morh re }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -175,6 +179,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -203,6 +208,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -230,6 +236,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -258,6 +265,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -285,6 +293,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -341,6 +350,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -368,6 +378,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -418,6 +429,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -445,6 +457,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -495,6 +508,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -522,6 +536,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -578,6 +593,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -605,6 +621,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -641,6 +658,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -668,6 +686,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -704,6 +723,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -731,6 +751,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -766,6 +787,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -793,6 +815,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -821,6 +844,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -848,6 +872,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -876,6 +901,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -903,6 +929,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -931,6 +958,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -958,6 +986,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -989,6 +1018,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1016,6 +1046,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -1045,6 +1076,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1072,6 +1104,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -1100,6 +1133,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-creation.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-creation.integration-spec.ts.snap index 0a91ee3825..ee769c6a29 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-creation.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-creation.integration-spec.ts.snap @@ -5,6 +5,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -39,6 +40,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -66,6 +68,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -100,6 +103,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -127,6 +131,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -167,6 +172,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -194,6 +200,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -228,6 +235,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -255,6 +263,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -295,6 +304,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -322,6 +332,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -356,6 +367,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -383,6 +395,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -417,6 +430,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -444,6 +458,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -477,6 +492,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE (rela }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -504,6 +520,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE when "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -570,6 +587,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE when }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -597,6 +615,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE when "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -631,6 +650,7 @@ exports[`Field metadata relation creation should fail relation MANY_TO_ONE when }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -659,6 +679,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -709,6 +730,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -738,6 +760,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -788,6 +811,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -817,6 +841,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -873,6 +898,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -902,6 +928,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -952,6 +979,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -981,6 +1009,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1037,6 +1066,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1066,6 +1096,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -1100,6 +1131,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1129,6 +1161,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -1163,6 +1196,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1192,6 +1226,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -1225,6 +1260,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY (rela }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1254,6 +1290,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY when "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1320,6 +1357,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY when }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1349,6 +1387,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY when "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -1383,6 +1422,7 @@ exports[`Field metadata relation creation should fail relation ONE_TO_MANY when }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1411,6 +1451,7 @@ exports[`Field metadata relation creation should fail should fail when creating "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1445,6 +1486,7 @@ exports[`Field metadata relation creation should fail should fail when creating }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1472,6 +1514,7 @@ exports[`Field metadata relation creation should fail should fail when creating "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1522,6 +1565,7 @@ exports[`Field metadata relation creation should fail should fail when creating }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1549,6 +1593,7 @@ exports[`Field metadata relation creation should fail should fail when creating "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1615,6 +1660,7 @@ exports[`Field metadata relation creation should fail should fail when creating }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1642,6 +1688,7 @@ exports[`Field metadata relation creation should fail should fail when creating "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1708,6 +1755,7 @@ exports[`Field metadata relation creation should fail should fail when creating }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-update.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-update.integration-spec.ts.snap index 2578e1a661..d7ff250727 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-update.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/relation/__snapshots__/failing-field-metadata-relation-update.integration-spec.ts.snap @@ -6,6 +6,7 @@ exports[`Field metadata relation update should fail relation when name is not in "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -46,6 +47,7 @@ exports[`Field metadata relation update should fail relation when name is not in }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/unique-field/__snapshots__/create-one-unique-field-metadata.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/unique-field/__snapshots__/create-one-unique-field-metadata.integration-spec.ts.snap index 8e228738c1..a779bc6c1a 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/unique-field/__snapshots__/create-one-unique-field-metadata.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/unique-field/__snapshots__/create-one-unique-field-metadata.integration-spec.ts.snap @@ -5,6 +5,7 @@ exports[`failing createOne unique field metadata should fail for composite type "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -37,6 +38,7 @@ exports[`failing createOne unique field metadata should fail for composite type }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -64,6 +66,7 @@ exports[`failing createOne unique field metadata should fail when field has cust "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -96,6 +99,7 @@ exports[`failing createOne unique field metadata should fail when field has cust }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-create-one-object-metadata-v2.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-create-one-object-metadata-v2.integration-spec.ts.snap index e69f384979..41dd852585 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-create-one-object-metadata-v2.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-create-one-object-metadata-v2.integration-spec.ts.snap @@ -5,6 +5,7 @@ exports[`Object metadata creation should fail v2 when labelPlural contains only "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -595,6 +596,7 @@ exports[`Object metadata creation should fail v2 when labelPlural contains only }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -622,6 +624,7 @@ exports[`Object metadata creation should fail v2 when labelPlural exceeds maximu "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1212,6 +1215,7 @@ exports[`Object metadata creation should fail v2 when labelPlural exceeds maximu }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1250,6 +1254,7 @@ exports[`Object metadata creation should fail v2 when labelSingular contains onl "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -1840,6 +1845,7 @@ exports[`Object metadata creation should fail v2 when labelSingular contains onl }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -1867,6 +1873,7 @@ exports[`Object metadata creation should fail v2 when labelSingular exceeds maxi "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -2457,6 +2464,7 @@ exports[`Object metadata creation should fail v2 when labelSingular exceeds maxi }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -2495,6 +2503,7 @@ exports[`Object metadata creation should fail v2 when labels are identical 1`] = "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3086,6 +3095,7 @@ exports[`Object metadata creation should fail v2 when labels are identical 1`] = }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3113,6 +3123,7 @@ exports[`Object metadata creation should fail v2 when labels with whitespaces re "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -3704,6 +3715,7 @@ exports[`Object metadata creation should fail v2 when labels with whitespaces re }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -3731,6 +3743,7 @@ exports[`Object metadata creation should fail v2 when name exceeds maximum lengt "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -4351,6 +4364,7 @@ exports[`Object metadata creation should fail v2 when name exceeds maximum lengt }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -4378,6 +4392,7 @@ exports[`Object metadata creation should fail v2 when namePlural has invalid cha "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -4968,6 +4983,7 @@ exports[`Object metadata creation should fail v2 when namePlural has invalid cha }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -4995,6 +5011,7 @@ exports[`Object metadata creation should fail v2 when namePlural is a reserved k "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -5314,7 +5331,7 @@ exports[`Object metadata creation should fail v2 when namePlural is a reserved k "errors": [ { "code": "INVALID_OBJECT_INPUT", - "message": "This name is reserved. Use a different name or the system will add \"Custom\" suffix.", + "message": "This name is reserved. Use a different name or the system will add "Custom" suffix.", "value": "users", }, ], @@ -5585,6 +5602,7 @@ exports[`Object metadata creation should fail v2 when namePlural is a reserved k }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -5623,6 +5641,7 @@ exports[`Object metadata creation should fail v2 when namePlural is not camelCas "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -6218,6 +6237,7 @@ exports[`Object metadata creation should fail v2 when namePlural is not camelCas }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -6245,6 +6265,7 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -6900,6 +6921,7 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -6927,6 +6949,7 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -7582,6 +7605,7 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -7609,6 +7633,7 @@ exports[`Object metadata creation should fail v2 when nameSingular has invalid c "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -8229,6 +8254,7 @@ exports[`Object metadata creation should fail v2 when nameSingular has invalid c }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -8256,6 +8282,7 @@ exports[`Object metadata creation should fail v2 when nameSingular is a reserved "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -8471,8 +8498,8 @@ exports[`Object metadata creation should fail v2 when nameSingular is a reserved "errors": [ { "code": "INVALID_FIELD_INPUT", - "message": "This name is reserved. Use a different name or the system will add \"Custom\" suffix.", - "userFriendlyMessage": "This name is reserved. Use a different name or the system will add \"Custom\" suffix.", + "message": "This name is reserved. Use a different name or the system will add "Custom" suffix.", + "userFriendlyMessage": "This name is reserved. Use a different name or the system will add "Custom" suffix.", "value": "user", }, { @@ -8493,8 +8520,8 @@ exports[`Object metadata creation should fail v2 when nameSingular is a reserved "errors": [ { "code": "INVALID_FIELD_INPUT", - "message": "This name is reserved. Use a different name or the system will add \"Custom\" suffix.", - "userFriendlyMessage": "This name is reserved. Use a different name or the system will add \"Custom\" suffix.", + "message": "This name is reserved. Use a different name or the system will add "Custom" suffix.", + "userFriendlyMessage": "This name is reserved. Use a different name or the system will add "Custom" suffix.", "value": "user", }, { @@ -8515,8 +8542,8 @@ exports[`Object metadata creation should fail v2 when nameSingular is a reserved "errors": [ { "code": "INVALID_FIELD_INPUT", - "message": "This name is reserved. Use a different name or the system will add \"Custom\" suffix.", - "userFriendlyMessage": "This name is reserved. Use a different name or the system will add \"Custom\" suffix.", + "message": "This name is reserved. Use a different name or the system will add "Custom" suffix.", + "userFriendlyMessage": "This name is reserved. Use a different name or the system will add "Custom" suffix.", "value": "user", }, { @@ -8537,8 +8564,8 @@ exports[`Object metadata creation should fail v2 when nameSingular is a reserved "errors": [ { "code": "INVALID_FIELD_INPUT", - "message": "This name is reserved. Use a different name or the system will add \"Custom\" suffix.", - "userFriendlyMessage": "This name is reserved. Use a different name or the system will add \"Custom\" suffix.", + "message": "This name is reserved. Use a different name or the system will add "Custom" suffix.", + "userFriendlyMessage": "This name is reserved. Use a different name or the system will add "Custom" suffix.", "value": "user", }, { @@ -8559,8 +8586,8 @@ exports[`Object metadata creation should fail v2 when nameSingular is a reserved "errors": [ { "code": "INVALID_FIELD_INPUT", - "message": "This name is reserved. Use a different name or the system will add \"Custom\" suffix.", - "userFriendlyMessage": "This name is reserved. Use a different name or the system will add \"Custom\" suffix.", + "message": "This name is reserved. Use a different name or the system will add "Custom" suffix.", + "userFriendlyMessage": "This name is reserved. Use a different name or the system will add "Custom" suffix.", "value": "user", }, { @@ -8605,7 +8632,7 @@ exports[`Object metadata creation should fail v2 when nameSingular is a reserved "errors": [ { "code": "INVALID_OBJECT_INPUT", - "message": "This name is reserved. Use a different name or the system will add \"Custom\" suffix.", + "message": "This name is reserved. Use a different name or the system will add "Custom" suffix.", "value": "user", }, ], @@ -8876,6 +8903,7 @@ exports[`Object metadata creation should fail v2 when nameSingular is a reserved }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -8914,6 +8942,7 @@ exports[`Object metadata creation should fail v2 when nameSingular is not camelC "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -9569,6 +9598,7 @@ exports[`Object metadata creation should fail v2 when nameSingular is not camelC }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -9596,6 +9626,7 @@ exports[`Object metadata creation should fail v2 when names are identical 1`] = "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -10187,6 +10218,7 @@ exports[`Object metadata creation should fail v2 when names are identical 1`] = }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -10214,6 +10246,7 @@ exports[`Object metadata creation should fail v2 when names with whitespaces res "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [ @@ -10805,6 +10838,7 @@ exports[`Object metadata creation should fail v2 when names with whitespaces res }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap index 8909ae7507..b25588637d 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap @@ -6,6 +6,7 @@ exports[`Object metadata update should fail when labelIdentifier is not a TEXT o "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -56,6 +57,7 @@ exports[`Object metadata update should fail when labelIdentifier is not a TEXT o }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -85,6 +87,7 @@ exports[`Object metadata update should fail when labelIdentifier is not a known "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -140,6 +143,7 @@ exports[`Object metadata update should fail when labelIdentifier is not a known }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -186,6 +190,7 @@ exports[`Object metadata update should fail when labelIdentifier is null 1`] = ` "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -219,6 +224,7 @@ exports[`Object metadata update should fail when labelIdentifier is null 1`] = ` }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-server/test/integration/metadata/suites/role/__snapshots__/failing-role-creation.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/role/__snapshots__/failing-role-creation.integration-spec.ts.snap index a7b83cf948..795c5f77d4 100644 --- a/packages/twenty-server/test/integration/metadata/suites/role/__snapshots__/failing-role-creation.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/role/__snapshots__/failing-role-creation.integration-spec.ts.snap @@ -5,6 +5,7 @@ exports[`Role creation should fail when canReadAllObjectRecords is false but can "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -37,6 +38,7 @@ exports[`Role creation should fail when canReadAllObjectRecords is false but can }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -64,6 +66,7 @@ exports[`Role creation should fail when canReadAllObjectRecords is false but can "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -96,6 +99,7 @@ exports[`Role creation should fail when canReadAllObjectRecords is false but can }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -123,6 +127,7 @@ exports[`Role creation should fail when canReadAllObjectRecords is false but can "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -155,6 +160,7 @@ exports[`Role creation should fail when canReadAllObjectRecords is false but can }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -182,6 +188,7 @@ exports[`Role creation should fail when canReadAllObjectRecords is false but mul "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -214,6 +221,7 @@ exports[`Role creation should fail when canReadAllObjectRecords is false but mul }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -241,6 +249,7 @@ exports[`Role creation should fail when label already exists 1`] = ` "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -273,6 +282,7 @@ exports[`Role creation should fail when label already exists 1`] = ` }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-server/test/integration/metadata/suites/role/__snapshots__/failing-role-update.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/role/__snapshots__/failing-role-update.integration-spec.ts.snap index d6fbf00976..e1468add9f 100644 --- a/packages/twenty-server/test/integration/metadata/suites/role/__snapshots__/failing-role-update.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/role/__snapshots__/failing-role-update.integration-spec.ts.snap @@ -17,6 +17,7 @@ exports[`Role update should fail updating role with existing write permissions s "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -48,6 +49,7 @@ exports[`Role update should fail updating role with existing write permissions s }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -75,6 +77,7 @@ exports[`Role update should fail when updating a non-editable system role 1`] = "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -106,6 +109,7 @@ exports[`Role update should fail when updating a non-editable system role 1`] = }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -133,6 +137,7 @@ exports[`Role update should fail when updating canReadAllObjectRecords to false "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -164,6 +169,7 @@ exports[`Role update should fail when updating canReadAllObjectRecords to false }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -191,6 +197,7 @@ exports[`Role update should fail when updating canReadAllObjectRecords to false "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -222,6 +229,7 @@ exports[`Role update should fail when updating canReadAllObjectRecords to false }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -249,6 +257,7 @@ exports[`Role update should fail when updating canReadAllObjectRecords to false "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -280,6 +289,7 @@ exports[`Role update should fail when updating canReadAllObjectRecords to false }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -307,6 +317,7 @@ exports[`Role update should fail when updating label to one that already exists "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -338,6 +349,7 @@ exports[`Role update should fail when updating label to one that already exists }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, @@ -365,6 +377,7 @@ exports[`Role update should fail when updating to enable write permissions witho "extensions": { "code": "METADATA_VALIDATION_FAILED", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -396,6 +409,7 @@ exports[`Role update should fail when updating to enable write permissions witho }, "message": "Validation failed for 0 object(s) and 0 field(s)", "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-server/test/integration/rest/suites/__snapshots__/view-group.integration-spec.ts.snap b/packages/twenty-server/test/integration/rest/suites/__snapshots__/view-group.integration-spec.ts.snap index 1169682be3..43438590cf 100644 --- a/packages/twenty-server/test/integration/rest/suites/__snapshots__/view-group.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/rest/suites/__snapshots__/view-group.integration-spec.ts.snap @@ -4,6 +4,7 @@ exports[`View Group REST API POST /metadata/viewGroups should fail to create vie { "error": "METADATA_VALIDATION_ERROR", "errors": { + "agent": [], "cronTrigger": [], "databaseEventTrigger": [], "fieldMetadata": [], @@ -37,6 +38,7 @@ exports[`View Group REST API POST /metadata/viewGroups should fail to create vie "message": "Multiple validation errors occurred while creating view groups", "statusCode": 400, "summary": { + "invalidAgent": 0, "invalidCronTrigger": 0, "invalidDatabaseEventTrigger": 0, "invalidFieldMetadata": 0, diff --git a/packages/twenty-shared/src/metadata/all-metadata-name.constant.ts b/packages/twenty-shared/src/metadata/all-metadata-name.constant.ts index ac002623e3..cd9a319778 100644 --- a/packages/twenty-shared/src/metadata/all-metadata-name.constant.ts +++ b/packages/twenty-shared/src/metadata/all-metadata-name.constant.ts @@ -12,4 +12,5 @@ export const ALL_METADATA_NAME = { viewFilter: 'viewFilter', role: 'role', roleTarget: 'roleTarget', + agent: 'agent', } as const;