From fcc4ef59d6af9cc250fadf455e167835c488a91e Mon Sep 17 00:00:00 2001 From: Jaakko Husso Date: Mon, 10 Aug 2026 10:14:01 +0300 Subject: [PATCH] feat(core): Enable 'instance-ai' module by default and remove startup warning (#35670) --- .../src/modules/__tests__/module-registry.test.ts | 15 +++++++++++++-- .../backend-common/src/modules/module-registry.ts | 1 + .../src/modules/instance-ai/instance-ai.module.ts | 11 +---------- packages/testing/playwright/pages/n8nPage.ts | 12 ++++++++++++ .../tests/e2e/auth/authenticated.spec.ts | 12 +++++++----- .../playwright/tests/e2e/auth/signin.spec.ts | 2 +- .../tests/e2e/chat-hub/chat-hub-chat-user.spec.ts | 4 ++-- 7 files changed, 37 insertions(+), 20 deletions(-) diff --git a/packages/@n8n/backend-common/src/modules/__tests__/module-registry.test.ts b/packages/@n8n/backend-common/src/modules/__tests__/module-registry.test.ts index 9513e43bbc0..7bbc2c66c48 100644 --- a/packages/@n8n/backend-common/src/modules/__tests__/module-registry.test.ts +++ b/packages/@n8n/backend-common/src/modules/__tests__/module-registry.test.ts @@ -30,7 +30,16 @@ describe('getModuleEntryUrl', () => { describe('eligibleModules', () => { it('should not include opt-in modules by default', () => { const eligible = Container.get(ModuleRegistry).eligibleModules; - expect(eligible).not.toContain('instance-ai'); + expect(eligible).not.toContain('agents'); + }); + + it('should include instance-ai by default', () => { + expect(Container.get(ModuleRegistry).eligibleModules).toContain('instance-ai'); + }); + + it('should allow opting out of a default module via env var', () => { + process.env.N8N_DISABLED_MODULES = 'instance-ai'; + expect(Container.get(ModuleRegistry).eligibleModules).not.toContain('instance-ai'); }); it('should consider a module ineligible if it was disabled via env var', () => { @@ -64,11 +73,12 @@ describe('eligibleModules', () => { 'runtime-credentials', 'mcp-registry', 'workflow-reviews', + 'instance-ai', ]); }); it('should consider a module eligible if it was enabled via env var', () => { - process.env.N8N_ENABLED_MODULES = 'instance-ai'; + process.env.N8N_ENABLED_MODULES = 'agents'; expect(Container.get(ModuleRegistry).eligibleModules).toEqual([ 'insights', 'external-secrets', @@ -100,6 +110,7 @@ describe('eligibleModules', () => { 'mcp-registry', 'workflow-reviews', 'instance-ai', + 'agents', ]); }); diff --git a/packages/@n8n/backend-common/src/modules/module-registry.ts b/packages/@n8n/backend-common/src/modules/module-registry.ts index 689f38798cc..ff56438a421 100644 --- a/packages/@n8n/backend-common/src/modules/module-registry.ts +++ b/packages/@n8n/backend-common/src/modules/module-registry.ts @@ -72,6 +72,7 @@ export class ModuleRegistry { 'runtime-credentials', 'mcp-registry', 'workflow-reviews', + 'instance-ai', ]; private readonly activeModules: string[] = []; diff --git a/packages/cli/src/modules/instance-ai/instance-ai.module.ts b/packages/cli/src/modules/instance-ai/instance-ai.module.ts index 0d034800dd5..a35e56f29aa 100644 --- a/packages/cli/src/modules/instance-ai/instance-ai.module.ts +++ b/packages/cli/src/modules/instance-ai/instance-ai.module.ts @@ -3,19 +3,9 @@ import type { ModuleInterface } from '@n8n/decorators'; import { BackendModule, OnShutdown } from '@n8n/decorators'; import { Container } from '@n8n/di'; -const YELLOW = '\x1b[33m'; -const CLEAR = '\x1b[0m'; -const WARNING_MESSAGE = - "[Instance AI] 'instance-ai' module is experimental, undocumented and subject to change. " + - 'Before its official release any features may become inaccessible at any point, ' + - 'and using the module could compromise the stability of your system. Use at your own risk!'; - @BackendModule({ name: 'instance-ai', instanceTypes: ['main'] }) export class InstanceAiModule implements ModuleInterface { async init() { - const logger = Container.get(Logger).scoped('instance-ai'); - logger.warn(`${YELLOW}${WARNING_MESSAGE}${CLEAR}`); - const { InstanceCredentialBroker } = await import( '@/credentials/instance-credential-broker.js' ); @@ -48,6 +38,7 @@ export class InstanceAiModule implements ModuleInterface { if (Container.get(GlobalConfig).instanceAi.durableLog) { const { InterruptedRunSweeper } = await import('./event-bus/interrupted-run-sweeper.js'); const { InstanceAiService } = await import('./instance-ai.service.js'); + const logger = Container.get(Logger).scoped('instance-ai'); const sweeper = Container.get(InterruptedRunSweeper); sweeper.setResumeHost(Container.get(InstanceAiService)); void sweeper.sweep().catch((error: unknown) => { diff --git a/packages/testing/playwright/pages/n8nPage.ts b/packages/testing/playwright/pages/n8nPage.ts index 4913461d76f..e51249475b0 100644 --- a/packages/testing/playwright/pages/n8nPage.ts +++ b/packages/testing/playwright/pages/n8nPage.ts @@ -239,7 +239,19 @@ export class n8nPage { this.clipboard = new ClipboardHelper(page); } + /** + * Navigate to the workflow overview. Goes there directly rather than via `/`, + * because the root route lands users on the AI Assistant when the `instance-ai` + * module is active. Use {@link goToRoot} to exercise that root routing itself. + */ async goHome() { + await this.page.goto('/home/workflows'); + } + + /** + * Navigate to the app root and let it decide where the user lands. + */ + async goToRoot() { await this.page.goto('/'); } } diff --git a/packages/testing/playwright/tests/e2e/auth/authenticated.spec.ts b/packages/testing/playwright/tests/e2e/auth/authenticated.spec.ts index 037bad48b45..8c3297ddd13 100644 --- a/packages/testing/playwright/tests/e2e/auth/authenticated.spec.ts +++ b/packages/testing/playwright/tests/e2e/auth/authenticated.spec.ts @@ -6,17 +6,19 @@ test.describe( annotation: [{ type: 'owner', description: 'Identity & Access' }], }, () => { + // Every signed-in role holds `instanceAi:message`, so the root route lands + // them on the AI Assistant while the `instance-ai` module is active. const testCases = [ - { role: 'default', expectedUrl: /\/workflow/, auth: '' }, - { role: 'owner', expectedUrl: /\/workflow/, auth: '@auth:owner' }, - { role: 'admin', expectedUrl: /\/workflow/, auth: '@auth:admin' }, - { role: 'member', expectedUrl: /\/workflow/, auth: '@auth:member' }, + { role: 'default', expectedUrl: /\/assistant/, auth: '' }, + { role: 'owner', expectedUrl: /\/assistant/, auth: '@auth:owner' }, + { role: 'admin', expectedUrl: /\/assistant/, auth: '@auth:admin' }, + { role: 'member', expectedUrl: /\/assistant/, auth: '@auth:member' }, { role: 'none', expectedUrl: /\/signin/, auth: '@auth:none' }, ]; for (const { role, expectedUrl, auth } of testCases) { test(`${role} authentication ${auth}`, async ({ n8n }) => { - await n8n.goHome(); + await n8n.goToRoot(); await expect(n8n.page).toHaveURL(expectedUrl); }); } diff --git a/packages/testing/playwright/tests/e2e/auth/signin.spec.ts b/packages/testing/playwright/tests/e2e/auth/signin.spec.ts index 8689432c1d7..8d8bb92d1b1 100644 --- a/packages/testing/playwright/tests/e2e/auth/signin.spec.ts +++ b/packages/testing/playwright/tests/e2e/auth/signin.spec.ts @@ -8,7 +8,7 @@ test.describe( }, () => { test('should login and logout @auth:none', async ({ n8n }) => { - await n8n.goHome(); + await n8n.goToRoot(); await n8n.signIn.goto(); diff --git a/packages/testing/playwright/tests/e2e/chat-hub/chat-hub-chat-user.spec.ts b/packages/testing/playwright/tests/e2e/chat-hub/chat-hub-chat-user.spec.ts index 881a8af4c49..acfa0098835 100644 --- a/packages/testing/playwright/tests/e2e/chat-hub/chat-hub-chat-user.spec.ts +++ b/packages/testing/playwright/tests/e2e/chat-hub/chat-hub-chat-user.spec.ts @@ -22,8 +22,8 @@ test.describe( }, }); - await n8n.goHome(); - await n8n.page.waitForURL('/home/chat'); // home is chat UI for chat users + await n8n.goToRoot(); + await n8n.page.waitForURL('/home/chat'); // root is the chat UI for chat users // Verify global credential is available and pre-selected await expect(n8n.chatHubChat.getModelSelectorButton()).toContainText(/claude/i); // pre-selected