feat: Add agent role assignment and database CRUD tools for AI agent nodes (#12888)

This PR introduces a significant enhancement to the role-based
permission system by extending it to support AI agents, enabling them to
perform database operations based on assigned permissions.

## Key Changes

### 1. Database Schema Migration
- **Table Rename**: `userWorkspaceRole` → `roleTargets` to better
reflect its expanded purpose
- **New Column**: Added `agentId` (UUID, nullable) to support AI agent
role assignments
- **Constraint Updates**: 
- Made `userWorkspaceId` nullable to accommodate agent-only role
assignments
- Added check constraint `CHK_role_targets_either_agent_or_user`
ensuring either `agentId` OR `userWorkspaceId` is set (not both)

### 2. Entity & Service Layer Updates
- **RoleTargetsEntity**: Updated with new `agentId` field and constraint
validation
- **AgentRoleService**: New service for managing agent role assignments
with validation
- **AgentService**: Enhanced to include role information when retrieving
agents
- **RoleResolver**: Added GraphQL mutations for `assignRoleToAgent` and
`removeRoleFromAgent`

### 3. AI Agent CRUD Operations
- **Permission-Based Tool Generation**: AI agents now receive database
tools based on their assigned role permissions
- **Dynamic Tool Creation**: The `AgentToolService` generates CRUD tools
(`create_*`, `find_*`, `update_*`, `soft_delete_*`, `destroy_*`) for
each object based on role permissions
- **Granular Permissions**: Supports both global role permissions
(`canReadAllObjectRecords`) and object-specific permissions
(`canReadObjectRecords`)

### 4. Frontend Integration
- **Role Assignment UI**: Added hooks and components for
assigning/removing roles from agents

## Demo


https://github.com/user-attachments/assets/41732267-742e-416c-b423-b687c2614c82

---------

Co-authored-by: Antoine Moreaux <moreaux.antoine@gmail.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: Guillim <guillim@users.noreply.github.com>
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
Co-authored-by: Weiko <corentin@twenty.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Marie <51697796+ijreilly@users.noreply.github.com>
Co-authored-by: martmull <martmull@hotmail.fr>
Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr>
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
Co-authored-by: Baptiste Devessier <baptiste@devessier.fr>
Co-authored-by: nitin <142569587+ehconitin@users.noreply.github.com>
Co-authored-by: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
Co-authored-by: prastoin <paul@twenty.com>
Co-authored-by: Vicky Wang <157669812+vickywxng@users.noreply.github.com>
Co-authored-by: Vicky Wang <vw92@cornell.edu>
Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
This commit is contained in:
Abdul Rahman
2025-06-30 01:48:14 +05:30
committed by GitHub
parent 317336ab71
commit 74b6466a57
53 changed files with 4804 additions and 478 deletions

View File

@ -62,6 +62,7 @@ export type Agent = {
name: Scalars['String'];
prompt: Scalars['String'];
responseFormat?: Maybe<Scalars['JSON']>;
roleId?: Maybe<Scalars['UUID']>;
updatedAt: Scalars['DateTime'];
};
@ -952,6 +953,7 @@ export type Mutation = {
__typename?: 'Mutation';
activateWorkflowVersion: Scalars['Boolean'];
activateWorkspace: Workspace;
assignRoleToAgent: Scalars['Boolean'];
authorizeApp: AuthorizeApp;
checkCustomDomainValidRecords?: Maybe<CustomDomainValidRecords>;
checkoutSession: BillingSessionOutput;
@ -996,6 +998,7 @@ export type Mutation = {
getLoginTokenFromEmailVerificationToken: GetLoginTokenFromEmailVerificationTokenOutput;
impersonate: ImpersonateOutput;
publishServerlessFunction: ServerlessFunction;
removeRoleFromAgent: Scalars['Boolean'];
renewToken: AuthTokens;
resendEmailVerificationToken: ResendEmailVerificationTokenOutput;
resendWorkspaceInvitation: SendInvitationsOutput;
@ -1046,6 +1049,12 @@ export type MutationActivateWorkspaceArgs = {
};
export type MutationAssignRoleToAgentArgs = {
agentId: Scalars['UUID'];
roleId: Scalars['UUID'];
};
export type MutationAuthorizeAppArgs = {
clientId: Scalars['String'];
codeChallenge?: InputMaybe<Scalars['String']>;
@ -1240,6 +1249,11 @@ export type MutationPublishServerlessFunctionArgs = {
};
export type MutationRemoveRoleFromAgentArgs = {
agentId: Scalars['UUID'];
};
export type MutationRenewTokenArgs = {
appToken: Scalars['String'];
};
@ -3180,6 +3194,21 @@ export type UpdateWorkflowVersionStepMutationVariables = Exact<{
export type UpdateWorkflowVersionStepMutation = { __typename?: 'Mutation', updateWorkflowVersionStep: { __typename?: 'WorkflowAction', id: any, name: string, type: string, settings: any, valid: boolean, nextStepIds?: Array<any> | null } };
export type AssignRoleToAgentMutationVariables = Exact<{
agentId: Scalars['UUID'];
roleId: Scalars['UUID'];
}>;
export type AssignRoleToAgentMutation = { __typename?: 'Mutation', assignRoleToAgent: boolean };
export type RemoveRoleFromAgentMutationVariables = Exact<{
agentId: Scalars['UUID'];
}>;
export type RemoveRoleFromAgentMutation = { __typename?: 'Mutation', removeRoleFromAgent: boolean };
export type UpdateOneAgentMutationVariables = Exact<{
input: UpdateAgentInput;
}>;
@ -3192,7 +3221,7 @@ export type FindOneAgentQueryVariables = Exact<{
}>;
export type FindOneAgentQuery = { __typename?: 'Query', findOneAgent: { __typename?: 'Agent', id: any, name: string, description?: string | null, prompt: string, modelId: string, responseFormat?: any | null } };
export type FindOneAgentQuery = { __typename?: 'Query', findOneAgent: { __typename?: 'Agent', id: any, name: string, description?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: any | null } };
export type SubmitFormStepMutationVariables = Exact<{
input: SubmitFormStepInput;
@ -6569,6 +6598,69 @@ export function useUpdateWorkflowVersionStepMutation(baseOptions?: Apollo.Mutati
export type UpdateWorkflowVersionStepMutationHookResult = ReturnType<typeof useUpdateWorkflowVersionStepMutation>;
export type UpdateWorkflowVersionStepMutationResult = Apollo.MutationResult<UpdateWorkflowVersionStepMutation>;
export type UpdateWorkflowVersionStepMutationOptions = Apollo.BaseMutationOptions<UpdateWorkflowVersionStepMutation, UpdateWorkflowVersionStepMutationVariables>;
export const AssignRoleToAgentDocument = gql`
mutation AssignRoleToAgent($agentId: UUID!, $roleId: UUID!) {
assignRoleToAgent(agentId: $agentId, roleId: $roleId)
}
`;
export type AssignRoleToAgentMutationFn = Apollo.MutationFunction<AssignRoleToAgentMutation, AssignRoleToAgentMutationVariables>;
/**
* __useAssignRoleToAgentMutation__
*
* To run a mutation, you first call `useAssignRoleToAgentMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useAssignRoleToAgentMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [assignRoleToAgentMutation, { data, loading, error }] = useAssignRoleToAgentMutation({
* variables: {
* agentId: // value for 'agentId'
* roleId: // value for 'roleId'
* },
* });
*/
export function useAssignRoleToAgentMutation(baseOptions?: Apollo.MutationHookOptions<AssignRoleToAgentMutation, AssignRoleToAgentMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<AssignRoleToAgentMutation, AssignRoleToAgentMutationVariables>(AssignRoleToAgentDocument, options);
}
export type AssignRoleToAgentMutationHookResult = ReturnType<typeof useAssignRoleToAgentMutation>;
export type AssignRoleToAgentMutationResult = Apollo.MutationResult<AssignRoleToAgentMutation>;
export type AssignRoleToAgentMutationOptions = Apollo.BaseMutationOptions<AssignRoleToAgentMutation, AssignRoleToAgentMutationVariables>;
export const RemoveRoleFromAgentDocument = gql`
mutation RemoveRoleFromAgent($agentId: UUID!) {
removeRoleFromAgent(agentId: $agentId)
}
`;
export type RemoveRoleFromAgentMutationFn = Apollo.MutationFunction<RemoveRoleFromAgentMutation, RemoveRoleFromAgentMutationVariables>;
/**
* __useRemoveRoleFromAgentMutation__
*
* To run a mutation, you first call `useRemoveRoleFromAgentMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useRemoveRoleFromAgentMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [removeRoleFromAgentMutation, { data, loading, error }] = useRemoveRoleFromAgentMutation({
* variables: {
* agentId: // value for 'agentId'
* },
* });
*/
export function useRemoveRoleFromAgentMutation(baseOptions?: Apollo.MutationHookOptions<RemoveRoleFromAgentMutation, RemoveRoleFromAgentMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<RemoveRoleFromAgentMutation, RemoveRoleFromAgentMutationVariables>(RemoveRoleFromAgentDocument, options);
}
export type RemoveRoleFromAgentMutationHookResult = ReturnType<typeof useRemoveRoleFromAgentMutation>;
export type RemoveRoleFromAgentMutationResult = Apollo.MutationResult<RemoveRoleFromAgentMutation>;
export type RemoveRoleFromAgentMutationOptions = Apollo.BaseMutationOptions<RemoveRoleFromAgentMutation, RemoveRoleFromAgentMutationVariables>;
export const UpdateOneAgentDocument = gql`
mutation UpdateOneAgent($input: UpdateAgentInput!) {
updateOneAgent(input: $input) {
@ -6616,6 +6708,7 @@ export const FindOneAgentDocument = gql`
prompt
modelId
responseFormat
roleId
}
}
`;