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`,
}),