diff --git a/packages/twenty-server/src/engine/core-modules/ai/services/tool.service.ts b/packages/twenty-server/src/engine/core-modules/ai/services/tool.service.ts index bc4239a795..dd0af3e389 100644 --- a/packages/twenty-server/src/engine/core-modules/ai/services/tool.service.ts +++ b/packages/twenty-server/src/engine/core-modules/ai/services/tool.service.ts @@ -14,6 +14,7 @@ import { getRecordInputSchema, } from 'src/engine/metadata-modules/agent/utils/agent-tool-schema.utils'; import { isWorkflowRunObject } from 'src/engine/metadata-modules/agent/utils/is-workflow-run-object.util'; +import { type ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; @@ -30,7 +31,11 @@ export class ToolService { private readonly findRecordsService: FindRecordsService, ) {} - async listTools(roleId: string, workspaceId: string): Promise { + async listTools( + roleId: string, + workspaceId: string, + actorContext?: ActorMetadata, + ): Promise { const tools: ToolSet = {}; const { data: rolesPermissions } = @@ -70,6 +75,7 @@ export class ToolService { objectRecord: parameters.input, workspaceId, roleId, + createdBy: actorContext, }); }, }; diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/create-record.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/create-record.service.ts index 2b35c6b60c..1a094feb99 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/create-record.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/create-record.service.ts @@ -85,9 +85,9 @@ export class CreateRecordService { const insertResult = await repository.insert({ ...transformedObjectRecord, position, - createdBy: { - source: roleId ? FieldActorSource.AGENT : FieldActorSource.WORKFLOW, - name: roleId ? 'Agent' : 'Workflow', + createdBy: params.createdBy ?? { + source: FieldActorSource.WORKFLOW, + name: 'Workflow', }, }); diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/types/create-record-params.type.ts b/packages/twenty-server/src/engine/core-modules/record-crud/types/create-record-params.type.ts index 2d83228991..8e6912662f 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/types/create-record-params.type.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/types/create-record-params.type.ts @@ -1,8 +1,10 @@ import { type ObjectRecordProperties } from 'src/engine/core-modules/record-crud/types/object-record-properties.type'; +import { type ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; export type CreateRecordParams = { objectName: string; objectRecord: ObjectRecordProperties; workspaceId: string; roleId?: string; + createdBy?: ActorMetadata; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/agent/agent-execution.service.ts b/packages/twenty-server/src/engine/metadata-modules/agent/agent-execution.service.ts index 4dcbf970cd..573fbdd78f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/agent/agent-execution.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/agent/agent-execution.service.ts @@ -24,7 +24,9 @@ import { type Workspace } from 'src/engine/core-modules/workspace/workspace.enti import { AgentHandoffToolService } from 'src/engine/metadata-modules/agent/agent-handoff-tool.service'; import { AGENT_CONFIG } from 'src/engine/metadata-modules/agent/constants/agent-config.const'; import { AGENT_SYSTEM_PROMPTS } from 'src/engine/metadata-modules/agent/constants/agent-system-prompts.const'; +import { AgentActorContextService } from 'src/engine/metadata-modules/agent/services/agent-actor-context.service'; import { type RecordIdsByObjectMetadataNameSingularType } from 'src/engine/metadata-modules/agent/types/recordIdsByObjectMetadataNameSingular.type'; +import { type ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util'; import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; @@ -54,6 +56,7 @@ export class AgentExecutionService implements AgentExecutionContext { private readonly agentToolGeneratorService: AgentToolGeneratorService, private readonly agentModelConfigService: AgentModelConfigService, private readonly aiBillingService: AIBillingService, + private readonly agentActorContextService: AgentActorContextService, @InjectRepository(AgentEntity) private readonly agentRepository: Repository, ) {} @@ -62,11 +65,15 @@ export class AgentExecutionService implements AgentExecutionContext { messages, system, agent, + actorContext, + roleIdOverride, excludeHandoffTools = false, }: { system: string; agent: AgentEntity | null; messages: UIMessage[]; + actorContext?: ActorMetadata; + roleIdOverride?: string; excludeHandoffTools?: boolean; }) { try { @@ -87,6 +94,8 @@ export class AgentExecutionService implements AgentExecutionContext { await this.agentToolGeneratorService.generateToolsForAgent( agent.id, agent.workspaceId, + actorContext, + roleIdOverride, ); let handoffTools = {}; @@ -261,10 +270,18 @@ export class AgentExecutionService implements AgentExecutionContext { contextString = `\n\nCONTEXT:\n${contextPart}`; } + const { actorContext, roleId } = + await this.agentActorContextService.buildUserActorContext( + userWorkspaceId, + workspace.id, + ); + const aiRequestConfig = await this.prepareAIRequestConfig({ system: `${AGENT_SYSTEM_PROMPTS.AGENT_CHAT}\n\n${agent.prompt}${contextString}`, agent, messages, + actorContext, + roleIdOverride: roleId, }); this.logger.log( diff --git a/packages/twenty-server/src/engine/metadata-modules/agent/agent-tool-generator.service.ts b/packages/twenty-server/src/engine/metadata-modules/agent/agent-tool-generator.service.ts index 7823a0b990..d013fbe7ce 100644 --- a/packages/twenty-server/src/engine/metadata-modules/agent/agent-tool-generator.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/agent/agent-tool-generator.service.ts @@ -7,6 +7,7 @@ import { Repository } from 'typeorm'; import { ToolAdapterService } from 'src/engine/core-modules/ai/services/tool-adapter.service'; import { ToolService } from 'src/engine/core-modules/ai/services/tool.service'; import { AgentService } from 'src/engine/metadata-modules/agent/agent.service'; +import { type ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants'; import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; @@ -29,6 +30,8 @@ export class AgentToolGeneratorService { async generateToolsForAgent( agentId: string, workspaceId: string, + actorContext?: ActorMetadata, + roleIdOverride?: string, ): Promise { let tools: ToolSet = {}; @@ -38,15 +41,15 @@ export class AgentToolGeneratorService { tools = { ...actionTools }; - const roleId = agent.roleId; + const effectiveRoleId = roleIdOverride || agent.roleId; - if (!roleId) { + if (!effectiveRoleId) { return tools; } const role = await this.roleRepository.findOne({ where: { - id: roleId, + id: effectiveRoleId, workspaceId, }, relations: ['permissionFlags'], @@ -65,21 +68,22 @@ export class AgentToolGeneratorService { if (hasWorkflowPermission) { const workflowTools = this.workflowToolService.generateWorkflowTools( workspaceId, - roleId, + effectiveRoleId, ); tools = { ...tools, ...workflowTools }; } const databaseTools = await this.toolService.listTools( - roleId, + effectiveRoleId, workspaceId, + actorContext, ); tools = { ...tools, ...databaseTools }; const roleActionTools = await this.toolAdapterService.getTools( - roleId, + effectiveRoleId, workspaceId, ); diff --git a/packages/twenty-server/src/engine/metadata-modules/agent/agent.module.ts b/packages/twenty-server/src/engine/metadata-modules/agent/agent.module.ts index 6083ab44d1..bd40799abb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/agent/agent.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/agent/agent.module.ts @@ -11,12 +11,14 @@ import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file- import { FileModule } from 'src/engine/core-modules/file/file.module'; import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.module'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module'; import { AgentRoleModule } from 'src/engine/metadata-modules/agent-role/agent-role.module'; import { AgentChatController } from 'src/engine/metadata-modules/agent/agent-chat.controller'; import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; +import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module'; import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module'; import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; import { WorkflowToolsModule } from 'src/modules/workflow/workflow-tools/workflow-tools.module'; @@ -39,6 +41,8 @@ import { AgentEntity } from './agent.entity'; import { AgentResolver } from './agent.resolver'; import { AgentService } from './agent.service'; +import { AgentActorContextService } from './services/agent-actor-context.service'; + @Module({ imports: [ TypeOrmModule.forFeature([ @@ -66,6 +70,8 @@ import { AgentService } from './agent.service'; TokenModule, DomainManagerModule, WorkflowToolsModule, + UserWorkspaceModule, + UserRoleModule, ], controllers: [AgentChatController], providers: [ @@ -81,6 +87,7 @@ import { AgentService } from './agent.service'; AgentTitleGenerationService, AgentHandoffExecutorService, AgentHandoffService, + AgentActorContextService, ], exports: [ AgentService, diff --git a/packages/twenty-server/src/engine/metadata-modules/agent/services/agent-actor-context.service.ts b/packages/twenty-server/src/engine/metadata-modules/agent/services/agent-actor-context.service.ts new file mode 100644 index 0000000000..36d56dc0b4 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/agent/services/agent-actor-context.service.ts @@ -0,0 +1,71 @@ +import { Injectable } from '@nestjs/common'; + +import { buildCreatedByFromFullNameMetadata } from 'src/engine/core-modules/actor/utils/build-created-by-from-full-name-metadata.util'; +import { UserWorkspaceService as UserService } from 'src/engine/core-modules/user-workspace/user-workspace.service'; +import { + AgentException, + AgentExceptionCode, +} from 'src/engine/metadata-modules/agent/agent.exception'; +import { type ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; +import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; + +export type AgentActorContext = { + actorContext: ActorMetadata; + roleId: string | undefined; +}; + +@Injectable() +export class AgentActorContextService { + constructor( + private readonly userService: UserService, + private readonly userRoleService: UserRoleService, + private readonly twentyORMGlobalManager: TwentyORMGlobalManager, + ) {} + + async buildUserActorContext( + userWorkspaceId: string, + workspaceId: string, + ): Promise { + const userWorkspace = await this.userService.findById(userWorkspaceId); + + if (!userWorkspace) { + throw new AgentException( + 'User workspace not found', + AgentExceptionCode.AGENT_EXECUTION_FAILED, + ); + } + + const workspaceMemberRepository = + await this.twentyORMGlobalManager.getRepositoryForWorkspace( + workspaceId, + 'workspaceMember', + { shouldBypassPermissionChecks: true }, + ); + + const workspaceMember = await workspaceMemberRepository.findOne({ + where: { + userId: userWorkspace.userId, + }, + }); + + if (!workspaceMember) { + throw new AgentException( + 'Workspace member not found for user', + AgentExceptionCode.AGENT_EXECUTION_FAILED, + ); + } + + const roleId = await this.userRoleService.getRoleIdForUserWorkspace({ + userWorkspaceId, + workspaceId, + }); + + const actorContext = buildCreatedByFromFullNameMetadata({ + fullNameMetadata: workspaceMember.name, + workspaceMemberId: workspaceMember.id, + }); + + return { actorContext, roleId }; + } +} diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/services/workflow-execution-context.service.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/services/workflow-execution-context.service.ts new file mode 100644 index 0000000000..3b72431924 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/services/workflow-execution-context.service.ts @@ -0,0 +1,58 @@ +import { Injectable } from '@nestjs/common'; + +import { isDefined } from 'twenty-shared/utils'; + +import { UserWorkspaceService as UserService } from 'src/engine/core-modules/user-workspace/user-workspace.service'; +import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; +import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; +import { type WorkflowExecutionContext } from 'src/modules/workflow/workflow-executor/types/workflow-execution-context.type'; +import { WorkflowRunWorkspaceService as WorkflowRunService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service'; + +@Injectable() +export class WorkflowExecutionContextService { + constructor( + private readonly workflowRunService: WorkflowRunService, + private readonly userService: UserService, + private readonly userRoleService: UserRoleService, + ) {} + + async getExecutionContext(runInfo: { + workflowRunId: string; + workspaceId: string; + }): Promise { + const workflowRun = await this.workflowRunService.getWorkflowRunOrFail({ + workflowRunId: runInfo.workflowRunId, + workspaceId: runInfo.workspaceId, + }); + + const isActingOnBehalfOfUser = + workflowRun.createdBy.source === FieldActorSource.MANUAL && + isDefined(workflowRun.createdBy.workspaceMemberId); + + let roleId: string | undefined; + + if (isActingOnBehalfOfUser) { + const workspaceMember = await this.userService.getWorkspaceMemberOrThrow({ + workspaceMemberId: workflowRun.createdBy.workspaceMemberId!, + workspaceId: runInfo.workspaceId, + }); + + const userWorkspace = + await this.userService.getUserWorkspaceForUserOrThrow({ + userId: workspaceMember.userId, + workspaceId: runInfo.workspaceId, + }); + + roleId = await this.userRoleService.getRoleIdForUserWorkspace({ + userWorkspaceId: userWorkspace.id, + workspaceId: runInfo.workspaceId, + }); + } + + return { + isActingOnBehalfOfUser, + initiator: workflowRun.createdBy, + roleId, + }; + } +} diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-execution-context.type.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-execution-context.type.ts new file mode 100644 index 0000000000..f989210e59 --- /dev/null +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-execution-context.type.ts @@ -0,0 +1,7 @@ +import { type ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; + +export type WorkflowExecutionContext = { + isActingOnBehalfOfUser: boolean; + initiator: ActorMetadata; + roleId?: string; +}; diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent-action.module.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent-action.module.ts index d93eb04a69..e2bafae682 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent-action.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent-action.module.ts @@ -2,21 +2,28 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { AiModule } from 'src/engine/core-modules/ai/ai.module'; +import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module'; import { AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity'; import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity'; -import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; +import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; +import { WorkflowExecutionContextService } from 'src/modules/workflow/workflow-executor/services/workflow-execution-context.service'; import { AiAgentExecutorService } from 'src/modules/workflow/workflow-executor/workflow-actions/ai-agent/services/ai-agent-executor.service'; +import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.module'; import { AiAgentWorkflowAction } from './ai-agent.workflow-action'; @Module({ imports: [ AiModule, - TypeOrmModule.forFeature([AgentEntity, RoleTargetsEntity, RoleEntity]), + TypeOrmModule.forFeature([AgentEntity, RoleTargetsEntity]), + WorkflowRunModule, + UserWorkspaceModule, + UserRoleModule, ], providers: [ ScopedWorkspaceContextFactory, + WorkflowExecutionContextService, AiAgentWorkflowAction, AiAgentExecutorService, ], diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent.workflow-action.ts index 0178709eaa..64a1e32173 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/ai-agent.workflow-action.ts @@ -16,6 +16,7 @@ import { WorkflowStepExecutorException, WorkflowStepExecutorExceptionCode, } from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception'; +import { WorkflowExecutionContextService } from 'src/modules/workflow/workflow-executor/services/workflow-execution-context.service'; import { type WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input'; import { type WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type'; import { findStepOrThrow } from 'src/modules/workflow/workflow-executor/utils/find-step-or-throw.util'; @@ -28,6 +29,7 @@ export class AiAgentWorkflowAction implements WorkflowAction { constructor( private readonly aiAgentExecutionService: AiAgentExecutorService, private readonly aiBillingService: AIBillingService, + private readonly workflowExecutionContextService: WorkflowExecutionContextService, @InjectRepository(AgentEntity) private readonly agentRepository: Repository, ) {} @@ -36,6 +38,7 @@ export class AiAgentWorkflowAction implements WorkflowAction { currentStepId, steps, context, + runInfo, }: WorkflowActionInput): Promise { const step = findStepOrThrow({ stepId: currentStepId, @@ -71,11 +74,18 @@ export class AiAgentWorkflowAction implements WorkflowAction { ); } + const executionContext = + await this.workflowExecutionContextService.getExecutionContext(runInfo); + const { result, usage } = await this.aiAgentExecutionService.executeAgent( { agent, schema: step.settings.outputSchema, userPrompt: resolveInput(prompt, context) as string, + actorContext: executionContext.isActingOnBehalfOfUser + ? executionContext.initiator + : undefined, + roleId: executionContext.roleId, }, ); diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/services/ai-agent-executor.service.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/services/ai-agent-executor.service.ts index 9c67fbd226..44e2ebcd8d 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/services/ai-agent-executor.service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/ai-agent/services/ai-agent-executor.service.ts @@ -16,8 +16,8 @@ import { import { AGENT_CONFIG } from 'src/engine/metadata-modules/agent/constants/agent-config.const'; import { AGENT_SYSTEM_PROMPTS } from 'src/engine/metadata-modules/agent/constants/agent-system-prompts.const'; import { convertOutputSchemaToZod } from 'src/engine/metadata-modules/agent/utils/convert-output-schema-to-zod'; +import { type ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity'; -import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { OutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type'; @Injectable() @@ -28,14 +28,14 @@ export class AiAgentExecutorService { private readonly toolAdapterService: ToolAdapterService, @InjectRepository(RoleTargetsEntity) private readonly roleTargetsRepository: Repository, - @InjectRepository(RoleEntity) - private readonly roleRepository: Repository, private readonly toolService: ToolService, ) {} private async getTools( agentId: string, workspaceId: string, + actorContext?: ActorMetadata, + roleIdOverride?: string, ): Promise { const roleTarget = await this.roleTargetsRepository.findOne({ where: { @@ -45,27 +45,23 @@ export class AiAgentExecutorService { select: ['roleId'], }); - const role = await this.roleRepository.findOne({ - where: { - id: roleTarget?.roleId, - workspaceId, - }, - }); + const effectiveRoleId = roleIdOverride || roleTarget?.roleId; - if (!roleTarget?.roleId || !role) { + if (!effectiveRoleId) { const actionTools = await this.toolAdapterService.getTools(); return { ...actionTools }; } const actionTools = await this.toolAdapterService.getTools( - role.id, + effectiveRoleId, workspaceId, ); const databaseTools = await this.toolService.listTools( - role.id, + effectiveRoleId, workspaceId, + actorContext, ); return { @@ -78,17 +74,21 @@ export class AiAgentExecutorService { agent, schema, userPrompt, + actorContext, + roleId, }: { agent: AgentEntity | null; schema: OutputSchema; userPrompt: string; + actorContext?: ActorMetadata; + roleId?: string; }): Promise { try { const registeredModel = await this.aiModelRegistryService.resolveModelForAgent(agent); const tools = agent - ? await this.getTools(agent.id, agent.workspaceId) + ? await this.getTools(agent.id, agent.workspaceId, actorContext, roleId) : {}; this.logger.log(`Generated ${Object.keys(tools).length} tools for agent`); diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts index cb0792ff45..19693ef9b0 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action.ts @@ -9,9 +9,15 @@ import { RecordCrudExceptionCode, } from 'src/engine/core-modules/record-crud/exceptions/record-crud.exception'; import { CreateRecordService } from 'src/engine/core-modules/record-crud/services/create-record.service'; +import { + type ActorMetadata, + FieldActorSource, +} from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; +import { WorkflowExecutionContextService } from 'src/modules/workflow/workflow-executor/services/workflow-execution-context.service'; import { type WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input'; import { type WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type'; +import { type WorkflowExecutionContext } from 'src/modules/workflow/workflow-executor/types/workflow-execution-context.type'; import { findStepOrThrow } from 'src/modules/workflow/workflow-executor/utils/find-step-or-throw.util'; import { type WorkflowCreateRecordActionInput } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/types/workflow-record-crud-action-input.type'; @@ -20,12 +26,14 @@ export class CreateRecordWorkflowAction implements WorkflowAction { constructor( private readonly createRecordService: CreateRecordService, private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, + private readonly workflowExecutionContextService: WorkflowExecutionContextService, ) {} async execute({ currentStepId, steps, context, + runInfo, }: WorkflowActionInput): Promise { const step = findStepOrThrow({ steps, @@ -46,10 +54,17 @@ export class CreateRecordWorkflowAction implements WorkflowAction { context, ) as WorkflowCreateRecordActionInput; + const executionContext = + await this.workflowExecutionContextService.getExecutionContext(runInfo); + + const createdBy = this.buildCreatedByActor(executionContext); + const toolOutput = await this.createRecordService.execute({ objectName: workflowActionInput.objectName, objectRecord: workflowActionInput.objectRecord, workspaceId, + createdBy, + roleId: executionContext.roleId, }); if (!toolOutput.success) { @@ -63,4 +78,19 @@ export class CreateRecordWorkflowAction implements WorkflowAction { result: toolOutput.result, }; } + + private buildCreatedByActor( + executionContext: WorkflowExecutionContext, + ): ActorMetadata { + if (executionContext.isActingOnBehalfOfUser) { + return executionContext.initiator; + } + + return { + source: FieldActorSource.WORKFLOW, + name: 'Workflow', + workspaceMemberId: null, + context: {}, + }; + } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action.ts index a37cb06b01..7fc755e2d2 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action.ts @@ -14,6 +14,7 @@ import { WorkflowStepExecutorException, WorkflowStepExecutorExceptionCode, } from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception'; +import { WorkflowExecutionContextService } from 'src/modules/workflow/workflow-executor/services/workflow-execution-context.service'; import { type WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input'; import { type WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type'; import { findStepOrThrow } from 'src/modules/workflow/workflow-executor/utils/find-step-or-throw.util'; @@ -25,12 +26,14 @@ export class DeleteRecordWorkflowAction implements WorkflowAction { constructor( private readonly deleteRecordService: DeleteRecordService, private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, + private readonly workflowExecutionContextService: WorkflowExecutionContextService, ) {} async execute({ currentStepId, steps, context, + runInfo, }: WorkflowActionInput): Promise { const step = findStepOrThrow({ steps, @@ -69,10 +72,14 @@ export class DeleteRecordWorkflowAction implements WorkflowAction { ); } + const executionContext = + await this.workflowExecutionContextService.getExecutionContext(runInfo); + const toolOutput = await this.deleteRecordService.execute({ objectName: workflowActionInput.objectName, objectRecordId: workflowActionInput.objectRecordId, workspaceId, + roleId: executionContext.roleId, soft: true, }); diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action.ts index 7801990ed3..02f89a4707 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action.ts @@ -14,6 +14,7 @@ import { WorkflowStepExecutorException, WorkflowStepExecutorExceptionCode, } from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception'; +import { WorkflowExecutionContextService } from 'src/modules/workflow/workflow-executor/services/workflow-execution-context.service'; import { type WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input'; import { type WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type'; import { findStepOrThrow } from 'src/modules/workflow/workflow-executor/utils/find-step-or-throw.util'; @@ -25,12 +26,14 @@ export class FindRecordsWorkflowAction implements WorkflowAction { constructor( private readonly findRecordsService: FindRecordsService, private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, + private readonly workflowExecutionContextService: WorkflowExecutionContextService, ) {} async execute({ currentStepId, steps, context, + runInfo, }: WorkflowActionInput): Promise { const step = findStepOrThrow({ steps, @@ -58,12 +61,16 @@ export class FindRecordsWorkflowAction implements WorkflowAction { ); } + const executionContext = + await this.workflowExecutionContextService.getExecutionContext(runInfo); + const toolOutput = await this.findRecordsService.execute({ objectName: workflowActionInput.objectName, filter: workflowActionInput.filter?.gqlOperationFilter, orderBy: workflowActionInput.orderBy, limit: workflowActionInput.limit, workspaceId, + roleId: executionContext.roleId, }); if (!toolOutput.success) { diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud-action.module.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud-action.module.ts index 7021dade20..366d95b3a9 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud-action.module.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/record-crud-action.module.ts @@ -1,16 +1,26 @@ import { Module } from '@nestjs/common'; import { RecordCrudModule } from 'src/engine/core-modules/record-crud/record-crud.module'; +import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module'; +import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; +import { WorkflowExecutionContextService } from 'src/modules/workflow/workflow-executor/services/workflow-execution-context.service'; import { CreateRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/create-record.workflow-action'; import { DeleteRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/delete-record.workflow-action'; import { FindRecordsWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/find-records.workflow-action'; import { UpdateRecordWorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action'; +import { WorkflowRunModule } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.module'; @Module({ - imports: [RecordCrudModule], + imports: [ + RecordCrudModule, + WorkflowRunModule, + UserWorkspaceModule, + UserRoleModule, + ], providers: [ ScopedWorkspaceContextFactory, + WorkflowExecutionContextService, CreateRecordWorkflowAction, UpdateRecordWorkflowAction, DeleteRecordWorkflowAction, diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts index f38c8cbace..cfdde28e3e 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/workflow-actions/record-crud/update-record.workflow-action.ts @@ -14,6 +14,7 @@ import { WorkflowStepExecutorException, WorkflowStepExecutorExceptionCode, } from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception'; +import { WorkflowExecutionContextService } from 'src/modules/workflow/workflow-executor/services/workflow-execution-context.service'; import { type WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input'; import { type WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type'; import { findStepOrThrow } from 'src/modules/workflow/workflow-executor/utils/find-step-or-throw.util'; @@ -25,12 +26,14 @@ export class UpdateRecordWorkflowAction implements WorkflowAction { constructor( private readonly updateRecordService: UpdateRecordService, private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory, + private readonly workflowExecutionContextService: WorkflowExecutionContextService, ) {} async execute({ currentStepId, steps, context, + runInfo, }: WorkflowActionInput): Promise { const step = findStepOrThrow({ steps, @@ -69,12 +72,16 @@ export class UpdateRecordWorkflowAction implements WorkflowAction { ); } + const executionContext = + await this.workflowExecutionContextService.getExecutionContext(runInfo); + const toolOutput = await this.updateRecordService.execute({ objectName: workflowActionInput.objectName, objectRecordId: workflowActionInput.objectRecordId, objectRecord: workflowActionInput.objectRecord, fieldsToUpdate: workflowActionInput.fieldsToUpdate, workspaceId, + roleId: executionContext.roleId, }); if (!toolOutput.success) {