feat: agent roleUniversalIdentifier for manifest-driven role assignment (#23206)

## Summary
- Adds optional `roleUniversalIdentifier` on `AgentManifest` /
`defineAgent` so apps can declaratively assign a role to an agent (same
config shape as `defaultRoleUniversalIdentifier`).
- Wires `agentUniversalIdentifier` as a sync many-to-one FK on
`roleTarget`, and emits a deterministic `roleTarget` from the agent
during app sync (create / update / delete).
- Enables app agents (e.g. Slack assistant) to get a role on install
without postInstall hooks or manual admin assignment.



<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23206?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Abdul Rahman
2026-07-24 05:57:40 +05:30
committed by GitHub
parent 8b7ac02464
commit 2c79093b74
30 changed files with 507 additions and 42 deletions
@@ -13,6 +13,7 @@ import {
} from 'typeorm';
import { ApiKeyEntity } from 'src/engine/core-modules/api-key/api-key.entity';
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
@@ -50,6 +51,13 @@ export class RoleTargetEntity extends SyncableEntity {
@Column({ nullable: true, type: 'uuid' })
agentId: string | null;
@ManyToOne(() => AgentEntity, {
onDelete: 'CASCADE',
nullable: true,
})
@JoinColumn({ name: 'agentId' })
agent: Relation<AgentEntity> | null;
@Column({ nullable: true, type: 'uuid' })
apiKeyId: string | null;
@@ -56,7 +56,12 @@ export class RoleTargetService {
return [];
}
const { flatRoleTargetMaps, flatApplicationMaps, flatRoleMaps } =
const {
flatRoleTargetMaps,
flatApplicationMaps,
flatRoleMaps,
flatAgentMaps,
} =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
@@ -64,6 +69,7 @@ export class RoleTargetService {
'flatRoleTargetMaps',
'flatApplicationMaps',
'flatRoleMaps',
'flatAgentMaps',
],
},
);
@@ -88,6 +94,7 @@ export class RoleTargetService {
createRoleTargetInput,
flatRoleTargetMaps,
flatRoleMaps,
flatAgentMaps,
workspaceId,
flatApplication: flatApplication ?? workspaceCustomFlatApplication,
});
@@ -3,6 +3,7 @@ import { v4 } from 'uuid';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type 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 { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util';
import { type FlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/types/flat-role-target.type';
import { findFlatRoleTargetFromForeignKey } from 'src/engine/metadata-modules/flat-role-target/utils/find-flat-role-target-from-foreign-key.util';
@@ -13,12 +14,16 @@ export const fromCreateRoleTargetInputToFlatRoleTargetToCreate = ({
workspaceId,
flatRoleTargetMaps,
flatRoleMaps,
flatAgentMaps,
flatApplication,
}: {
createRoleTargetInput: CreateRoleTargetInput;
workspaceId: string;
flatApplication: FlatApplication;
} & Pick<AllFlatEntityMaps, 'flatRoleTargetMaps' | 'flatRoleMaps'>): {
} & Pick<
AllFlatEntityMaps,
'flatRoleTargetMaps' | 'flatRoleMaps' | 'flatAgentMaps'
>): {
flatRoleTargetToCreate: FlatRoleTarget;
flatRoleTargetsToDelete: FlatRoleTarget[];
} => {
@@ -34,12 +39,21 @@ export const fromCreateRoleTargetInputToFlatRoleTargetToCreate = ({
},
);
const agentUniversalIdentifier =
targetMetadataForeignKey === 'agentId'
? findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityMaps: flatAgentMaps,
flatEntityId: targetId,
}).universalIdentifier
: null;
const flatRoleTargetToCreate: FlatRoleTarget = {
id: v4(),
roleId,
roleUniversalIdentifier,
userWorkspaceId: null,
agentId: null,
agentUniversalIdentifier,
apiKeyId: null,
createdAt: now.toISOString(),
updatedAt: now.toISOString(),