chore: remove IS_AI_ENABLED feature flag (#19916)

## Summary
- AI is now GA, so the public/lab `IS_AI_ENABLED` flag is removed from
`FeatureFlagKey`, the public flag catalog, and the dev seeder.
- Drops every backend `@RequireFeatureFlag(IS_AI_ENABLED)` guard (agent,
agent chat, chat subscription, role-to-agent assignment, workflow AI
step creation) and the now-unused `FeatureFlagModule`/`FeatureFlagGuard`
wiring in the AI and workflow modules.
- Removes frontend gating from settings nav, role
permissions/assignment/applicability, command menu hotkeys, side panel,
mobile/drawer nav, and the agent chat provider so AI UI is always on.
Tests and generated GraphQL/SDK schemas updated accordingly.

## Test plan
- [x] `npx nx typecheck twenty-shared`
- [x] `npx nx typecheck twenty-server`
- [x] `npx nx typecheck twenty-front`
- [x] `npx nx lint:diff-with-main twenty-server`
- [x] `npx nx lint:diff-with-main twenty-front`
- [x] `npx jest --config=packages/twenty-server/jest.config.mjs
feature-flag`
- [x] `npx jest --config=packages/twenty-server/jest.config.mjs
workspace-entity-manager`
- [ ] Manual smoke test: AI features still accessible without any flag
row in `featureFlag`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Félix Malfait
2026-04-21 09:49:46 +02:00
committed by GitHub
parent 69868a0ab6
commit 30b8663a74
32 changed files with 73 additions and 240 deletions
@@ -12,14 +12,6 @@ export type PublicFeatureFlag = {
};
export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [
{
key: FeatureFlagKey.IS_AI_ENABLED,
metadata: {
label: 'AI',
description: 'Enable AI-powered features including the agent chat',
imagePath: 'https://twenty.com/images/lab/is-ai-enabled.png',
},
},
{
key: FeatureFlagKey.IS_JUNCTION_RELATIONS_ENABLED,
metadata: {
@@ -37,7 +37,7 @@ describe('FeatureFlagService', () => {
};
const workspaceId = 'workspace-id';
const featureFlag = FeatureFlagKey.IS_AI_ENABLED;
const featureFlag = FeatureFlagKey.IS_JUNCTION_RELATIONS_ENABLED;
beforeEach(async () => {
jest.clearAllMocks();
@@ -121,11 +121,11 @@ describe('FeatureFlagService', () => {
// Prepare
mockWorkspaceCacheService.getOrRecompute.mockResolvedValue({
featureFlagsMap: {
[FeatureFlagKey.IS_AI_ENABLED]: false,
[FeatureFlagKey.IS_JUNCTION_RELATIONS_ENABLED]: false,
},
});
const mockFeatureFlags = [
{ key: FeatureFlagKey.IS_AI_ENABLED, value: false },
{ key: FeatureFlagKey.IS_JUNCTION_RELATIONS_ENABLED, value: false },
];
// Act
@@ -144,7 +144,11 @@ describe('FeatureFlagService', () => {
it('should return a map of feature flags for a workspace', async () => {
// Prepare
const mockFeatureFlags = [
{ key: FeatureFlagKey.IS_AI_ENABLED, value: false, workspaceId },
{
key: FeatureFlagKey.IS_JUNCTION_RELATIONS_ENABLED,
value: false,
workspaceId,
},
];
mockFeatureFlagRepository.find.mockResolvedValue(mockFeatureFlags);
@@ -154,7 +158,7 @@ describe('FeatureFlagService', () => {
// Assert
expect(result).toEqual({
[FeatureFlagKey.IS_AI_ENABLED]: false,
[FeatureFlagKey.IS_JUNCTION_RELATIONS_ENABLED]: false,
});
});
});
@@ -162,7 +166,7 @@ describe('FeatureFlagService', () => {
describe('enableFeatureFlags', () => {
it('should enable multiple feature flags for a workspace', async () => {
// Prepare
const keys = [FeatureFlagKey.IS_AI_ENABLED];
const keys = [FeatureFlagKey.IS_JUNCTION_RELATIONS_ENABLED];
mockFeatureFlagRepository.upsert.mockResolvedValue({});
mockWorkspaceCacheService.invalidateAndRecompute.mockResolvedValue(
@@ -8,7 +8,7 @@ describe('featureFlagValidator', () => {
it('should not throw error if featureFlagKey is valid', () => {
expect(() =>
featureFlagValidator.assertIsFeatureFlagKey(
'IS_AI_ENABLED',
'IS_JUNCTION_RELATIONS_ENABLED',
new UnknownException('Error', 'Error', {
userFriendlyMessage: msg`Error`,
}),
@@ -2,10 +2,8 @@ import { UseFilters, UseGuards, UsePipes } from '@nestjs/common';
import { Args, Mutation } from '@nestjs/graphql';
import { PermissionFlagType } from 'twenty-shared/constants';
import { FeatureFlagKey } from 'twenty-shared/types';
import { CoreResolver } from 'src/engine/api/graphql/graphql-config/decorators/core-resolver.decorator';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
import { HttpTool } from 'src/engine/core-modules/tool/tools/http-tool/http-tool';
@@ -27,7 +25,6 @@ import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
import { WorkflowVersionStepWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-step/workflow-version-step.workspace-service';
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { WorkflowRunWorkspaceService } from 'src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service';
import { WorkflowRunnerWorkspaceService } from 'src/modules/workflow/workflow-runner/workspace-services/workflow-runner.workspace-service';
@@ -49,7 +46,6 @@ export class WorkflowVersionStepResolver {
private readonly workflowRunnerWorkspaceService: WorkflowRunnerWorkspaceService,
private readonly workflowRunWorkspaceService: WorkflowRunWorkspaceService,
private readonly httpTool: HttpTool,
private readonly featureFlagService: FeatureFlagService,
) {}
@Mutation(() => WorkflowVersionStepChangesDTO)
@@ -58,19 +54,6 @@ export class WorkflowVersionStepResolver {
@Args('input')
input: CreateWorkflowVersionStepInput,
): Promise<WorkflowVersionStepChangesDTO> {
if (input.stepType === WorkflowActionType.AI_AGENT) {
const isAiEnabled = await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_AI_ENABLED,
workspaceId,
);
if (!isAiEnabled) {
throw new Error(
'AI features are not available in your current workspace. Please contact support to enable them.',
);
}
}
return this.workflowVersionStepWorkspaceService.createWorkflowVersionStep({
workspaceId,
input,
@@ -1,7 +1,6 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { ToolModule } from 'src/engine/core-modules/tool/tool.module';
import { WorkflowTriggerController } from 'src/engine/core-modules/workflow/controllers/workflow-trigger.controller';
import { WorkflowBuilderResolver } from 'src/engine/core-modules/workflow/resolvers/workflow-builder.resolver';
@@ -24,7 +23,6 @@ import { WorkflowTriggerModule } from 'src/modules/workflow/workflow-trigger/wor
@Module({
imports: [
TypeOrmModule.forFeature([WorkspaceEntity]),
FeatureFlagModule,
WorkflowTriggerModule,
WorkflowBuilderModule,
WorkflowCommonModule,