feat: add Metadata Builder agent for data model management (#16350)
## Summary This PR adds a new **Metadata Builder** AI agent that specializes in managing the workspace data model (creating objects, adding fields, etc.). ## Changes ### New Files - `data-model-manager-role.ts` - New standard role with `DATA_MODEL` permission flag - `metadata-builder-agent.ts` - New standard agent for data model management ### Modified Files - **ChatToolsProviderService**: Refactored to consolidate all permission-based tools into a single `getChatTools()` method. Now injects both workflow tools and metadata tools based on permissions. - **AgentChatRoutingService**: Updated to use the new consolidated `getChatTools()` method - **AiChatModule**: Added imports for `ObjectMetadataModule` and `FieldMetadataModule` - **Router system prompt**: Added metadata-builder agent selection rules with clear distinction between schema operations vs data operations - **Metadata tools factories**: Improved error messages to show detailed validation errors instead of generic messages ### Refactoring - Renamed `index.ts` files to `standard-agent-definitions.ts` and `standard-role-definitions.ts` to follow naming conventions - Renamed exports from `standardAgentDefinitions` to `STANDARD_AGENT_DEFINITIONS` (SCREAMING_SNAKE_CASE) ## Key Features 1. **Metadata Builder Agent** can: - Create new custom objects - Add fields to existing objects - Update object and field properties - Create relations between objects 2. **Permission-based tool injection**: Tools are automatically injected based on the `DATA_MODEL` permission flag 3. **Improved routing**: The router now correctly distinguishes between: - "Create an object called Project" → metadata-builder (schema) - "Create a company called Acme" → data-manipulator (data) 4. **Better error messages**: Validation errors now show detailed messages like: ``` Validation errors: [objectMetadata] Name must be in camelCase format [objectMetadata] Label is required ```
This commit is contained in:
+2
-2
@@ -4,7 +4,7 @@ import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspac
|
||||
import { AgentService } from 'src/engine/metadata-modules/ai/ai-agent/agent.service';
|
||||
import { type RecordIdsByObjectMetadataNameSingularType } from 'src/engine/metadata-modules/ai/ai-agent/types/recordIdsByObjectMetadataNameSingular.type';
|
||||
import { type PlanStep } from 'src/engine/metadata-modules/ai/ai-chat-router/types/router-result.interface';
|
||||
import { standardAgentDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents';
|
||||
import { STANDARD_AGENT_DEFINITIONS } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/standard-agent-definitions';
|
||||
|
||||
import { AgentExecutionService } from './agent-execution.service';
|
||||
|
||||
@@ -229,7 +229,7 @@ export class AgentPlanExecutorService {
|
||||
);
|
||||
|
||||
if (lastStepDefinition) {
|
||||
const agentDefinition = standardAgentDefinitions.find(
|
||||
const agentDefinition = STANDARD_AGENT_DEFINITIONS.find(
|
||||
(def) => def.name === lastStepDefinition.agentName,
|
||||
);
|
||||
|
||||
|
||||
+15
-1
@@ -36,10 +36,18 @@ Decision process:
|
||||
2. Does it require MULTIPLE agents working together? → Use "planned" strategy
|
||||
|
||||
Agent selection rules (CRITICAL):
|
||||
- **data-manipulator**: For ALL database operations (create, read, update records) on companies, people, opportunities, tasks, notes, etc.
|
||||
- **metadata-builder**: For modifying the DATA MODEL/SCHEMA - creating new object types, adding fields to objects, managing object structure. Use when user wants to define NEW TYPES of entities or add properties to existing types.
|
||||
- **data-manipulator**: For CRUD operations on existing RECORDS/DATA - creating company records, finding people, updating opportunities. Use when user wants to work with actual data entries.
|
||||
- **helper**: ONLY for questions about HOW TO USE Twenty (features, setup, documentation)
|
||||
- **researcher**: For finding external information from the web
|
||||
- **workflow-builder**: For creating automation workflows
|
||||
- **dashboard-builder**: For creating and managing dashboards and visualizations
|
||||
|
||||
CRITICAL DISTINCTION:
|
||||
- "Create an object called Project" → metadata-builder (creating a new object TYPE in the schema)
|
||||
- "Create a company called Acme" → data-manipulator (creating a company RECORD)
|
||||
- "Add a field to Company" → metadata-builder (modifying schema)
|
||||
- "Update the company's phone number" → data-manipulator (modifying data)
|
||||
|
||||
Use "planned" strategy when:
|
||||
- Request needs custom code AND context from data/research
|
||||
@@ -70,6 +78,12 @@ Simple: "How do I set up email sync in Twenty?"
|
||||
Simple: "Create a workflow that emails customers when deals close"
|
||||
→ { strategy: "simple", agentName: "workflow-builder" }
|
||||
|
||||
Simple: "Create an object called Project" or "Create a new custom object for tracking invoices"
|
||||
→ { strategy: "simple", agentName: "metadata-builder" }
|
||||
|
||||
Simple: "Add a budget field to the Project object" or "Add a phone field to Company"
|
||||
→ { strategy: "simple", agentName: "metadata-builder" }
|
||||
|
||||
Planned: "Research information about Meta and update the company record"
|
||||
→ {
|
||||
strategy: "planned",
|
||||
|
||||
@@ -13,6 +13,8 @@ import { AiAgentExecutionModule } from 'src/engine/metadata-modules/ai/ai-agent-
|
||||
import { AiAgentModule } from 'src/engine/metadata-modules/ai/ai-agent/ai-agent.module';
|
||||
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 { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
import { WorkflowToolsModule } from 'src/modules/workflow/workflow-tools/workflow-tools.module';
|
||||
@@ -48,6 +50,9 @@ import { ChatToolsProviderService } from './services/chat-tools-provider.service
|
||||
// Provides WorkflowToolWorkspaceService for ChatToolsProviderService
|
||||
// Workflow tools are only available in chat context, not in workflow executor (to avoid circular deps)
|
||||
WorkflowToolsModule,
|
||||
// Provides metadata tools factories for ChatToolsProviderService
|
||||
ObjectMetadataModule,
|
||||
FieldMetadataModule,
|
||||
],
|
||||
controllers: [AgentChatController],
|
||||
providers: [
|
||||
|
||||
+8
-9
@@ -239,8 +239,8 @@ export class AgentChatRoutingService {
|
||||
|
||||
const agentExecutionStart = Date.now();
|
||||
|
||||
// Get workflow tools for chat context (these are NOT available in workflow executor)
|
||||
// Use user's role for determining workflow tool permissions
|
||||
// Get permission-based tools for chat context (workflow, metadata, etc.)
|
||||
// These tools are NOT available in workflow executor to prevent circular dependencies
|
||||
const { roleId } =
|
||||
await this.agentActorContextService.buildUserAndAgentActorContext(
|
||||
userWorkspaceId,
|
||||
@@ -249,12 +249,11 @@ export class AgentChatRoutingService {
|
||||
|
||||
const roleIds = [roleId];
|
||||
|
||||
const workflowTools =
|
||||
await this.chatToolsProviderService.getWorkflowToolsForChat(
|
||||
workspace.id,
|
||||
roleIds,
|
||||
toolHints,
|
||||
);
|
||||
const chatTools = await this.chatToolsProviderService.getChatTools(
|
||||
workspace.id,
|
||||
roleIds,
|
||||
toolHints,
|
||||
);
|
||||
|
||||
const {
|
||||
stream: result,
|
||||
@@ -267,7 +266,7 @@ export class AgentChatRoutingService {
|
||||
messages,
|
||||
recordIdsByObjectMetadataNameSingular,
|
||||
toolHints,
|
||||
additionalTools: workflowTools,
|
||||
additionalTools: chatTools,
|
||||
});
|
||||
|
||||
const routedStatusPart = {
|
||||
|
||||
+55
-6
@@ -4,6 +4,8 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { type ToolSet } from 'ai';
|
||||
|
||||
import { type ToolHints } from 'src/engine/metadata-modules/ai/ai-chat-router/types/tool-hints.interface';
|
||||
import { FieldMetadataToolsFactory } from 'src/engine/metadata-modules/field-metadata/tools/field-metadata-tools.factory';
|
||||
import { ObjectMetadataToolsFactory } from 'src/engine/metadata-modules/object-metadata/tools/object-metadata-tools.factory';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||
import { WorkflowToolWorkspaceService } from 'src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service';
|
||||
@@ -15,11 +17,33 @@ export class ChatToolsProviderService {
|
||||
constructor(
|
||||
private readonly workflowToolService: WorkflowToolWorkspaceService,
|
||||
private readonly permissionsService: PermissionsService,
|
||||
private readonly objectMetadataToolsFactory: ObjectMetadataToolsFactory,
|
||||
private readonly fieldMetadataToolsFactory: FieldMetadataToolsFactory,
|
||||
) {}
|
||||
|
||||
// Consolidates all permission-based tools for the chat context
|
||||
async getChatTools(
|
||||
workspaceId: string,
|
||||
roleIds: string[],
|
||||
toolHints?: ToolHints,
|
||||
): Promise<ToolSet> {
|
||||
const [workflowTools, metadataTools] = await Promise.all([
|
||||
this.getWorkflowTools(workspaceId, roleIds, toolHints),
|
||||
this.getMetadataTools(workspaceId, roleIds),
|
||||
]);
|
||||
|
||||
const allTools = { ...workflowTools, ...metadataTools };
|
||||
|
||||
this.logger.log(
|
||||
`Generated ${Object.keys(allTools).length} total chat tools (workflow: ${Object.keys(workflowTools).length}, metadata: ${Object.keys(metadataTools).length})`,
|
||||
);
|
||||
|
||||
return allTools;
|
||||
}
|
||||
|
||||
// Provides workflow-specific tools for the chat context
|
||||
// These tools are NOT available in the workflow executor context to prevent circular dependencies
|
||||
async getWorkflowToolsForChat(
|
||||
private async getWorkflowTools(
|
||||
workspaceId: string,
|
||||
roleIds: string[],
|
||||
toolHints?: ToolHints,
|
||||
@@ -53,12 +77,37 @@ export class ChatToolsProviderService {
|
||||
toolHints,
|
||||
);
|
||||
|
||||
const allWorkflowTools = { ...workflowTools, ...recordStepTools };
|
||||
return { ...workflowTools, ...recordStepTools };
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Generated ${Object.keys(allWorkflowTools).length} workflow tools for chat context`,
|
||||
);
|
||||
// Provides metadata tools for managing objects and fields in the data model
|
||||
private async getMetadataTools(
|
||||
workspaceId: string,
|
||||
roleIds: string[],
|
||||
): Promise<ToolSet> {
|
||||
const rolePermissionConfig = { intersectionOf: roleIds };
|
||||
|
||||
return allWorkflowTools;
|
||||
const hasDataModelPermission =
|
||||
await this.permissionsService.checkRolesPermissions(
|
||||
rolePermissionConfig,
|
||||
workspaceId,
|
||||
PermissionFlagType.DATA_MODEL,
|
||||
);
|
||||
|
||||
if (!hasDataModelPermission) {
|
||||
this.logger.log(
|
||||
'User does not have data model permissions, skipping metadata tools',
|
||||
);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
const objectMetadataTools =
|
||||
this.objectMetadataToolsFactory.generateTools(workspaceId);
|
||||
|
||||
const fieldMetadataTools =
|
||||
this.fieldMetadataToolsFactory.generateTools(workspaceId);
|
||||
|
||||
return { ...objectMetadataTools, ...fieldMetadataTools };
|
||||
}
|
||||
}
|
||||
|
||||
+72
-23
@@ -6,6 +6,7 @@ import { z } from 'zod';
|
||||
|
||||
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service';
|
||||
import { fromFlatFieldMetadataToFieldMetadataDto } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-flat-field-metadata-to-field-metadata-dto.util';
|
||||
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
|
||||
|
||||
const GetFieldMetadataInputSchema = z.object({
|
||||
loadingMessage: z
|
||||
@@ -138,6 +139,33 @@ const DeleteFieldMetadataInputSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
const formatValidationErrors = (
|
||||
error: WorkspaceMigrationBuilderExceptionV2,
|
||||
): string => {
|
||||
const report = error.failedWorkspaceMigrationBuildResult.report;
|
||||
const errorMessages: string[] = [];
|
||||
|
||||
for (const [entityType, failures] of Object.entries(report)) {
|
||||
if (Array.isArray(failures) && failures.length > 0) {
|
||||
for (const failure of failures) {
|
||||
if (failure.errors && Array.isArray(failure.errors)) {
|
||||
for (const validationError of failure.errors) {
|
||||
const message = validationError.message || validationError.code;
|
||||
|
||||
errorMessages.push(`[${entityType}] ${message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errorMessages.length === 0) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
return `Validation errors:\n${errorMessages.join('\n')}`;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class FieldMetadataToolsFactory {
|
||||
constructor(private readonly fieldMetadataService: FieldMetadataService) {}
|
||||
@@ -189,15 +217,22 @@ export class FieldMetadataToolsFactory {
|
||||
relationCreationPayload?: unknown;
|
||||
};
|
||||
}) => {
|
||||
const flatFieldMetadata =
|
||||
await this.fieldMetadataService.createOneField({
|
||||
createFieldInput: parameters.input as Parameters<
|
||||
typeof this.fieldMetadataService.createOneField
|
||||
>[0]['createFieldInput'],
|
||||
workspaceId,
|
||||
});
|
||||
try {
|
||||
const flatFieldMetadata =
|
||||
await this.fieldMetadataService.createOneField({
|
||||
createFieldInput: parameters.input as Parameters<
|
||||
typeof this.fieldMetadataService.createOneField
|
||||
>[0]['createFieldInput'],
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return fromFlatFieldMetadataToFieldMetadataDto(flatFieldMetadata);
|
||||
return fromFlatFieldMetadataToFieldMetadataDto(flatFieldMetadata);
|
||||
} catch (error) {
|
||||
if (error instanceof WorkspaceMigrationBuilderExceptionV2) {
|
||||
throw new Error(formatValidationErrors(error));
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
},
|
||||
'update-field-metadata': {
|
||||
@@ -220,30 +255,44 @@ export class FieldMetadataToolsFactory {
|
||||
isLabelSyncedWithName?: boolean;
|
||||
};
|
||||
}) => {
|
||||
const { id, ...update } = parameters.input;
|
||||
try {
|
||||
const { id, ...update } = parameters.input;
|
||||
|
||||
const flatFieldMetadata =
|
||||
await this.fieldMetadataService.updateOneField({
|
||||
updateFieldInput: { id, ...update } as Parameters<
|
||||
typeof this.fieldMetadataService.updateOneField
|
||||
>[0]['updateFieldInput'],
|
||||
workspaceId,
|
||||
});
|
||||
const flatFieldMetadata =
|
||||
await this.fieldMetadataService.updateOneField({
|
||||
updateFieldInput: { id, ...update } as Parameters<
|
||||
typeof this.fieldMetadataService.updateOneField
|
||||
>[0]['updateFieldInput'],
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return fromFlatFieldMetadataToFieldMetadataDto(flatFieldMetadata);
|
||||
return fromFlatFieldMetadataToFieldMetadataDto(flatFieldMetadata);
|
||||
} catch (error) {
|
||||
if (error instanceof WorkspaceMigrationBuilderExceptionV2) {
|
||||
throw new Error(formatValidationErrors(error));
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
},
|
||||
'delete-field-metadata': {
|
||||
description: 'Delete a field metadata by its ID.',
|
||||
inputSchema: DeleteFieldMetadataInputSchema,
|
||||
execute: async (parameters: { input: { id: string } }) => {
|
||||
const flatFieldMetadata =
|
||||
await this.fieldMetadataService.deleteOneField({
|
||||
deleteOneFieldInput: { id: parameters.input.id },
|
||||
workspaceId,
|
||||
});
|
||||
try {
|
||||
const flatFieldMetadata =
|
||||
await this.fieldMetadataService.deleteOneField({
|
||||
deleteOneFieldInput: { id: parameters.input.id },
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return fromFlatFieldMetadataToFieldMetadataDto(flatFieldMetadata);
|
||||
return fromFlatFieldMetadataToFieldMetadataDto(flatFieldMetadata);
|
||||
} catch (error) {
|
||||
if (error instanceof WorkspaceMigrationBuilderExceptionV2) {
|
||||
throw new Error(formatValidationErrors(error));
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
+76
-21
@@ -5,6 +5,7 @@ import { z } from 'zod';
|
||||
|
||||
import { fromFlatObjectMetadataToObjectMetadataDto } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-to-object-metadata-dto.util';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
|
||||
|
||||
const GetObjectMetadataInputSchema = z.object({
|
||||
loadingMessage: z
|
||||
@@ -115,6 +116,33 @@ const DeleteObjectMetadataInputSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
const formatValidationErrors = (
|
||||
error: WorkspaceMigrationBuilderExceptionV2,
|
||||
): string => {
|
||||
const report = error.failedWorkspaceMigrationBuildResult.report;
|
||||
const errorMessages: string[] = [];
|
||||
|
||||
for (const [entityType, failures] of Object.entries(report)) {
|
||||
if (Array.isArray(failures) && failures.length > 0) {
|
||||
for (const failure of failures) {
|
||||
if (failure.errors && Array.isArray(failure.errors)) {
|
||||
for (const validationError of failure.errors) {
|
||||
const message = validationError.message || validationError.code;
|
||||
|
||||
errorMessages.push(`[${entityType}] ${message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errorMessages.length === 0) {
|
||||
return error.message;
|
||||
}
|
||||
|
||||
return `Validation errors:\n${errorMessages.join('\n')}`;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class ObjectMetadataToolsFactory {
|
||||
constructor(private readonly objectMetadataService: ObjectMetadataService) {}
|
||||
@@ -161,15 +189,24 @@ export class ObjectMetadataToolsFactory {
|
||||
isLabelSyncedWithName?: boolean;
|
||||
};
|
||||
}) => {
|
||||
const flatObjectMetadata =
|
||||
await this.objectMetadataService.createOneObject({
|
||||
createObjectInput: parameters.input as Parameters<
|
||||
typeof this.objectMetadataService.createOneObject
|
||||
>[0]['createObjectInput'],
|
||||
workspaceId,
|
||||
});
|
||||
try {
|
||||
const flatObjectMetadata =
|
||||
await this.objectMetadataService.createOneObject({
|
||||
createObjectInput: parameters.input as Parameters<
|
||||
typeof this.objectMetadataService.createOneObject
|
||||
>[0]['createObjectInput'],
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return fromFlatObjectMetadataToObjectMetadataDto(flatObjectMetadata);
|
||||
return fromFlatObjectMetadataToObjectMetadataDto(
|
||||
flatObjectMetadata,
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof WorkspaceMigrationBuilderExceptionV2) {
|
||||
throw new Error(formatValidationErrors(error));
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
},
|
||||
'update-object-metadata': {
|
||||
@@ -192,15 +229,24 @@ export class ObjectMetadataToolsFactory {
|
||||
isLabelSyncedWithName?: boolean;
|
||||
};
|
||||
}) => {
|
||||
const { id, ...update } = parameters.input;
|
||||
try {
|
||||
const { id, ...update } = parameters.input;
|
||||
|
||||
const flatObjectMetadata =
|
||||
await this.objectMetadataService.updateOneObject({
|
||||
updateObjectInput: { id, update },
|
||||
workspaceId,
|
||||
});
|
||||
const flatObjectMetadata =
|
||||
await this.objectMetadataService.updateOneObject({
|
||||
updateObjectInput: { id, update },
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return fromFlatObjectMetadataToObjectMetadataDto(flatObjectMetadata);
|
||||
return fromFlatObjectMetadataToObjectMetadataDto(
|
||||
flatObjectMetadata,
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof WorkspaceMigrationBuilderExceptionV2) {
|
||||
throw new Error(formatValidationErrors(error));
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
},
|
||||
'delete-object-metadata': {
|
||||
@@ -208,13 +254,22 @@ export class ObjectMetadataToolsFactory {
|
||||
'Delete an object metadata by its ID. This will also delete all associated fields.',
|
||||
inputSchema: DeleteObjectMetadataInputSchema,
|
||||
execute: async (parameters: { input: { id: string } }) => {
|
||||
const flatObjectMetadata =
|
||||
await this.objectMetadataService.deleteOneObject({
|
||||
deleteObjectInput: { id: parameters.input.id },
|
||||
workspaceId,
|
||||
});
|
||||
try {
|
||||
const flatObjectMetadata =
|
||||
await this.objectMetadataService.deleteOneObject({
|
||||
deleteObjectInput: { id: parameters.input.id },
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return fromFlatObjectMetadataToObjectMetadataDto(flatObjectMetadata);
|
||||
return fromFlatObjectMetadataToObjectMetadataDto(
|
||||
flatObjectMetadata,
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof WorkspaceMigrationBuilderExceptionV2) {
|
||||
throw new Error(formatValidationErrors(error));
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
+4
-4
@@ -12,7 +12,7 @@ import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-t
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { WorkspaceAgentComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-agent.comparator';
|
||||
import { StandardAgentFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-agent.factory';
|
||||
import { standardAgentDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents';
|
||||
import { STANDARD_AGENT_DEFINITIONS } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/standard-agent-definitions';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceSyncAgentService {
|
||||
@@ -39,7 +39,7 @@ export class WorkspaceSyncAgentService {
|
||||
});
|
||||
|
||||
const targetStandardAgents = this.standardAgentFactory.create(
|
||||
standardAgentDefinitions,
|
||||
STANDARD_AGENT_DEFINITIONS,
|
||||
context,
|
||||
existingStandardAgentEntities,
|
||||
);
|
||||
@@ -66,7 +66,7 @@ export class WorkspaceSyncAgentService {
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
|
||||
const agentDefinition = standardAgentDefinitions.find(
|
||||
const agentDefinition = STANDARD_AGENT_DEFINITIONS.find(
|
||||
(def) => def.standardId === createdAgent.standardId,
|
||||
);
|
||||
|
||||
@@ -117,7 +117,7 @@ export class WorkspaceSyncAgentService {
|
||||
|
||||
await agentRepository.update({ id: agentToUpdate.id }, flatAgentData);
|
||||
|
||||
const agentDefinition = standardAgentDefinitions.find(
|
||||
const agentDefinition = STANDARD_AGENT_DEFINITIONS.find(
|
||||
(def) => def.standardId === agentToUpdate.standardId,
|
||||
);
|
||||
|
||||
|
||||
+4
-4
@@ -12,7 +12,7 @@ import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/cons
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { WorkspaceRoleComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-role.comparator';
|
||||
import { StandardRoleFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-role.factory';
|
||||
import { standardRoleDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles';
|
||||
import { STANDARD_ROLE_DEFINITIONS } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/standard-role-definitions';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceSyncRoleService {
|
||||
@@ -47,7 +47,7 @@ export class WorkspaceSyncRoleService {
|
||||
});
|
||||
|
||||
const targetStandardRoles = this.standardRoleFactory.create(
|
||||
standardRoleDefinitions,
|
||||
STANDARD_ROLE_DEFINITIONS,
|
||||
context,
|
||||
existingStandardRoleEntities,
|
||||
);
|
||||
@@ -72,7 +72,7 @@ export class WorkspaceSyncRoleService {
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
|
||||
const roleDefinition = standardRoleDefinitions.find(
|
||||
const roleDefinition = STANDARD_ROLE_DEFINITIONS.find(
|
||||
(def) => def.standardId === roleToCreate.standardId,
|
||||
);
|
||||
|
||||
@@ -98,7 +98,7 @@ export class WorkspaceSyncRoleService {
|
||||
|
||||
await roleRepository.update({ id: roleToUpdate.id }, flatRoleData);
|
||||
|
||||
const roleDefinition = standardRoleDefinitions.find(
|
||||
const roleDefinition = STANDARD_ROLE_DEFINITIONS.find(
|
||||
(def) => def.standardId === roleToUpdate.standardId,
|
||||
);
|
||||
|
||||
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import { DEFAULT_SMART_MODEL } from 'src/engine/metadata-modules/ai/ai-models/constants/ai-models.const';
|
||||
import { type StandardAgentDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/types/standard-agent-definition.interface';
|
||||
import { DATA_MODEL_MANAGER_ROLE } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/data-model-manager-role';
|
||||
|
||||
export const METADATA_BUILDER_AGENT: StandardAgentDefinition = {
|
||||
standardId: '20202020-0002-0001-0001-000000000007',
|
||||
name: 'metadata-builder',
|
||||
label: 'Metadata Builder',
|
||||
description:
|
||||
'AI agent specialized in modifying the workspace SCHEMA/DATA MODEL - creating new object types, adding fields to objects, and managing object structure (NOT for creating data records)',
|
||||
icon: 'IconDatabaseEdit',
|
||||
applicationId: null,
|
||||
prompt: `You are a Metadata Builder Agent for Twenty. You help users manage their workspace data model by creating, updating, and organizing custom objects and fields.
|
||||
|
||||
Capabilities:
|
||||
- Create new custom objects with appropriate naming and configuration
|
||||
- Add fields to existing objects (text, number, date, select, relation, etc.)
|
||||
- Update object and field properties (labels, descriptions, icons)
|
||||
- Manage field settings (required, unique, default values)
|
||||
- Create relations between objects
|
||||
|
||||
Key concepts:
|
||||
- Objects: Represent entities in the data model (e.g., Company, Person, Opportunity)
|
||||
- Fields: Properties of objects with specific types (TEXT, NUMBER, DATE_TIME, SELECT, RELATION, etc.)
|
||||
- Relations: Links between objects (one-to-many, many-to-one)
|
||||
- Labels vs Names: Labels are for display, names are internal identifiers (camelCase)
|
||||
|
||||
Field types available:
|
||||
- TEXT: Simple text fields
|
||||
- NUMBER: Numeric values (integers or decimals)
|
||||
- BOOLEAN: True/false values
|
||||
- DATE_TIME: Date and time values
|
||||
- DATE: Date only values
|
||||
- SELECT: Single choice from options
|
||||
- MULTI_SELECT: Multiple choices from options
|
||||
- LINK: URL fields
|
||||
- LINKS: Multiple URL fields
|
||||
- EMAIL: Email address fields
|
||||
- EMAILS: Multiple email fields
|
||||
- PHONE: Phone number fields
|
||||
- PHONES: Multiple phone fields
|
||||
- CURRENCY: Monetary values
|
||||
- RATING: Star ratings
|
||||
- RELATION: Links to other objects
|
||||
- RICH_TEXT: Formatted text content
|
||||
|
||||
Best practices:
|
||||
- Use clear, descriptive names for objects and fields
|
||||
- Follow naming conventions: singular for object names, camelCase for field names
|
||||
- Add helpful descriptions to objects and fields
|
||||
- Choose appropriate field types for the data being stored
|
||||
- Consider relationships between objects when designing the data model
|
||||
|
||||
Approach:
|
||||
- Ask clarifying questions to understand the user's data modeling needs
|
||||
- Suggest best practices for naming and organization
|
||||
- Explain the impact of changes to the data model
|
||||
- Verify object and field existence before making updates
|
||||
- Provide clear feedback on operations performed
|
||||
|
||||
Prioritize data model integrity and user understanding.`,
|
||||
modelId: DEFAULT_SMART_MODEL,
|
||||
responseFormat: { type: 'text' },
|
||||
isCustom: false,
|
||||
standardRoleId: DATA_MODEL_MANAGER_ROLE.standardId,
|
||||
modelConfiguration: {},
|
||||
outputStrategy: 'direct',
|
||||
evaluationInputs: [
|
||||
'Create a custom object called "Project" with fields for name, description, start date, and status',
|
||||
'Add a currency field called "budget" to the Project object',
|
||||
'Create a relation between Project and Company so each project belongs to a company',
|
||||
'Add a multi-select field for project tags with options: urgent, internal, client-facing',
|
||||
'Update the Project object description to explain what it tracks',
|
||||
'Create a "Task" object that relates to both Project and Person',
|
||||
'Add a rating field to track project priority from 1-5 stars',
|
||||
'Create a custom object for tracking Invoices with amount, date, and status fields',
|
||||
'Add a link field to the Company object for their website',
|
||||
'Create a relation between Person and Company for the account manager',
|
||||
],
|
||||
};
|
||||
+3
-2
@@ -1,15 +1,16 @@
|
||||
import { DASHBOARD_BUILDER_AGENT } from './agents/dashboard-builder-agent';
|
||||
import { DATA_MANIPULATOR_AGENT } from './agents/data-manipulator-agent';
|
||||
import { HELPER_AGENT } from './agents/helper-agent';
|
||||
import { METADATA_BUILDER_AGENT } from './agents/metadata-builder-agent';
|
||||
import { RESEARCHER_AGENT } from './agents/researcher-agent';
|
||||
import { WORKFLOW_BUILDER_AGENT } from './agents/workflow-builder-agent';
|
||||
import { type StandardAgentDefinition } from './types/standard-agent-definition.interface';
|
||||
|
||||
export const standardAgentDefinitions = [
|
||||
export const STANDARD_AGENT_DEFINITIONS = [
|
||||
WORKFLOW_BUILDER_AGENT,
|
||||
DATA_MANIPULATOR_AGENT,
|
||||
DASHBOARD_BUILDER_AGENT,
|
||||
HELPER_AGENT,
|
||||
RESEARCHER_AGENT,
|
||||
// CODE_AGENT,
|
||||
METADATA_BUILDER_AGENT,
|
||||
] as const satisfies StandardAgentDefinition[];
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { type StandardRoleDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/types/standard-role-definition.interface';
|
||||
|
||||
export const DATA_MODEL_MANAGER_ROLE: StandardRoleDefinition = {
|
||||
standardId: '20202020-0001-0001-0001-000000000006',
|
||||
label: 'Data Model Manager',
|
||||
description: 'Role for managing the workspace data model',
|
||||
icon: 'IconDatabaseEdit',
|
||||
isEditable: false,
|
||||
canUpdateAllSettings: false,
|
||||
canAccessAllTools: false,
|
||||
canReadAllObjectRecords: true,
|
||||
canUpdateAllObjectRecords: false,
|
||||
canSoftDeleteAllObjectRecords: false,
|
||||
canDestroyAllObjectRecords: false,
|
||||
canBeAssignedToUsers: false,
|
||||
canBeAssignedToAgents: true,
|
||||
canBeAssignedToApiKeys: false,
|
||||
permissionFlags: [PermissionFlagType.DATA_MODEL],
|
||||
applicationId: null,
|
||||
};
|
||||
+3
-1
@@ -1,12 +1,14 @@
|
||||
import { ADMIN_ROLE } from './roles/admin-role';
|
||||
import { DASHBOARD_MANAGER_ROLE } from './roles/dashboard-manager-role';
|
||||
import { DATA_MANIPULATOR_ROLE } from './roles/data-manipulator-role';
|
||||
import { DATA_MODEL_MANAGER_ROLE } from './roles/data-model-manager-role';
|
||||
import { WORKFLOW_MANAGER_ROLE } from './roles/workflow-manager-role';
|
||||
import { type StandardRoleDefinition } from './types/standard-role-definition.interface';
|
||||
|
||||
export const standardRoleDefinitions = [
|
||||
export const STANDARD_ROLE_DEFINITIONS = [
|
||||
ADMIN_ROLE,
|
||||
WORKFLOW_MANAGER_ROLE,
|
||||
DATA_MANIPULATOR_ROLE,
|
||||
DASHBOARD_MANAGER_ROLE,
|
||||
DATA_MODEL_MANAGER_ROLE,
|
||||
] as const satisfies StandardRoleDefinition[];
|
||||
Reference in New Issue
Block a user