From 22d628bcb3df53a632811b5095e724f2b0d99c18 Mon Sep 17 00:00:00 2001 From: Drol Date: Wed, 25 Mar 2026 11:46:59 +0800 Subject: [PATCH] fix(ai): fixed breaking test of ai (#8941) * fix(ai): fixed breaking test of ai * refactor(ai): move skill,ai-employee.mcp init action to ai plugin --- .../ai/src/__tests__/ai-employees.test.ts | 10 ++++-- packages/core/ai/src/__tests__/mcp.test.ts | 3 +- packages/core/ai/src/__tests__/skills.test.ts | 2 +- .../core/ai/src/ai-employee-manager/index.ts | 13 +++---- .../core/ai/src/ai-employee-manager/types.ts | 1 + packages/core/ai/src/loader/employee.ts | 8 ++--- packages/core/ai/src/loader/skills.ts | 22 ++++++------ packages/core/ai/src/mcp-manager/index.ts | 25 ++++++------- packages/core/ai/src/mcp-manager/types.ts | 1 + packages/core/ai/src/skills-manager/index.ts | 13 +++---- packages/core/ai/src/skills-manager/types.ts | 2 +- .../client/__tests__/mcp/MCPSettings.test.ts | 36 ------------------- .../@nocobase/plugin-ai/src/server/plugin.ts | 5 +++ 13 files changed, 58 insertions(+), 83 deletions(-) delete mode 100644 packages/plugins/@nocobase/plugin-ai/src/client/__tests__/mcp/MCPSettings.test.ts diff --git a/packages/core/ai/src/__tests__/ai-employees.test.ts b/packages/core/ai/src/__tests__/ai-employees.test.ts index 20e259a42b1..f81a28fded7 100644 --- a/packages/core/ai/src/__tests__/ai-employees.test.ts +++ b/packages/core/ai/src/__tests__/ai-employees.test.ts @@ -26,7 +26,7 @@ describe('AI employee loader test cases', () => { beforeEach(async () => { app = await createMockServer({ - plugins: ['nocobase', 'field-sort', 'workflow'], + plugins: ['nocobase'], }); await app.pm.enable('ai'); aiManager = app.aiManager; @@ -34,7 +34,13 @@ describe('AI employee loader test cases', () => { loader = new AIEmployeeLoader(aiManager, { scan: { basePath, - pattern: ['**/ai-employees/*.ts', '**/ai-employees/*/index.ts', '!**/ai-employees/**/*.d.ts'], + pattern: [ + '**/ai-employees/*.ts', + '**/ai-employees/*/index.ts', + '**/ai-employees/*.js', + '**/ai-employees/*/index.js', + '!**/ai-employees/**/*.d.ts', + ], }, }); }); diff --git a/packages/core/ai/src/__tests__/mcp.test.ts b/packages/core/ai/src/__tests__/mcp.test.ts index db400977427..d84da030598 100644 --- a/packages/core/ai/src/__tests__/mcp.test.ts +++ b/packages/core/ai/src/__tests__/mcp.test.ts @@ -22,7 +22,7 @@ describe('MCP loader test cases', () => { beforeEach(async () => { app = await createMockServer({ - plugins: ['nocobase', 'field-sort', 'workflow'], + plugins: ['nocobase'], }); await app.pm.enable('ai'); aiManager = app.aiManager; @@ -41,7 +41,6 @@ describe('MCP loader test cases', () => { it('should load mcp definitions in mcp root directory', async () => { await loader.load(); - await mcpManager.persistence(); const entry = await mcpManager.getMCP('weather'); expect(entry).toBeDefined(); diff --git a/packages/core/ai/src/__tests__/skills.test.ts b/packages/core/ai/src/__tests__/skills.test.ts index eb88bcabed2..40aab357816 100644 --- a/packages/core/ai/src/__tests__/skills.test.ts +++ b/packages/core/ai/src/__tests__/skills.test.ts @@ -24,7 +24,7 @@ describe('Skills loader test cases', () => { beforeEach(async () => { app = await createMockServer({ - plugins: ['nocobase', 'field-sort', 'workflow'], + plugins: ['nocobase'], }); // with mysql add ai plugin in createMockServer will occur error with create usersAiEmployees collection at app startup await app.pm.enable('ai'); diff --git a/packages/core/ai/src/ai-employee-manager/index.ts b/packages/core/ai/src/ai-employee-manager/index.ts index 52f61a9996a..0e2a8167af2 100644 --- a/packages/core/ai/src/ai-employee-manager/index.ts +++ b/packages/core/ai/src/ai-employee-manager/index.ts @@ -28,12 +28,13 @@ export class DefaultAIEmployeeManager implements AIEmployeeManager { constructor(private readonly app: any) { this.provideCollectionManager = () => app.mainDataSource; - this.app.on('afterStart', async () => { - if (this.mode === 'memory') { - await this.persistence(); - this.mode = 'database'; - } - }); + } + + async init() { + if (this.mode === 'memory') { + await this.persistence(); + this.mode = 'database'; + } } async getEmployee(username: string): Promise { diff --git a/packages/core/ai/src/ai-employee-manager/types.ts b/packages/core/ai/src/ai-employee-manager/types.ts index 76ac14de00f..e27700671ea 100644 --- a/packages/core/ai/src/ai-employee-manager/types.ts +++ b/packages/core/ai/src/ai-employee-manager/types.ts @@ -55,6 +55,7 @@ export type AIEmployeeFilter = { }; export interface AIEmployeeManager { + init(): Promise; getEmployee(username: string): Promise; listEmployees(filter?: AIEmployeeFilter): Promise; registerEmployee(options: AIEmployeeOptions): Promise; diff --git a/packages/core/ai/src/loader/employee.ts b/packages/core/ai/src/loader/employee.ts index bf51e814fb5..bf475421e48 100644 --- a/packages/core/ai/src/loader/employee.ts +++ b/packages/core/ai/src/loader/employee.ts @@ -92,11 +92,9 @@ export class AIEmployeeLoader extends LoadAndRegister { return; } const { employeeManager } = this.ai; - await Promise.all( - this.employeeDescriptors.map(async (descriptor) => { - await employeeManager.registerEmployee(descriptor.options); - }), - ); + for (const descriptor of this.employeeDescriptors) { + await employeeManager.registerEmployee(descriptor.options); + } } } diff --git a/packages/core/ai/src/loader/skills.ts b/packages/core/ai/src/loader/skills.ts index 21ef159549b..41694bb2d99 100644 --- a/packages/core/ai/src/loader/skills.ts +++ b/packages/core/ai/src/loader/skills.ts @@ -101,18 +101,16 @@ export class SkillsLoader extends LoadAndRegister { return; } const { skillsManager } = this.ai; - await Promise.all( - this.skillsDescriptors.map(async (descriptor) => { - await skillsManager.registerSkills({ - scope: descriptor.scope, - name: descriptor.name, - description: descriptor.description, - content: descriptor.content, - tools: descriptor.tools, - introduction: descriptor.introduction, - }); - }), - ); + for (const descriptor of this.skillsDescriptors) { + await skillsManager.registerSkills({ + scope: descriptor.scope, + name: descriptor.name, + description: descriptor.description, + content: descriptor.content, + tools: descriptor.tools, + introduction: descriptor.introduction, + }); + } } } diff --git a/packages/core/ai/src/mcp-manager/index.ts b/packages/core/ai/src/mcp-manager/index.ts index a4e50fb1f6f..8e42231eae8 100644 --- a/packages/core/ai/src/mcp-manager/index.ts +++ b/packages/core/ai/src/mcp-manager/index.ts @@ -26,17 +26,18 @@ export class DefaultMCPManager implements MCPManager { constructor(private readonly app: any) { this.provideCollectionManager = () => app.mainDataSource; - this.app.on('afterStart', async () => { - if (this.mode === 'memory') { - await this.persistence(); - this.mode = 'database'; - } - try { - await this.rebuildClient(); - } catch (e) { - this.app.log.error('fail to init mcp clients', e); - } - }); + } + + async init() { + if (this.mode === 'memory') { + await this.persistence(); + this.mode = 'database'; + } + try { + await this.rebuildClient(); + } catch (e) { + this.app.log.error('fail to init mcp clients', e); + } } async registerMCP(registration: { [key: string | symbol]: MCPOptions }): Promise { @@ -46,7 +47,7 @@ export class DefaultMCPManager implements MCPManager { } } else { for (const [name, options] of Object.entries(registration)) { - this.persistenceEntry({ + await this.persistenceEntry({ name, ...this.normalizeEntry(name, options), }); diff --git a/packages/core/ai/src/mcp-manager/types.ts b/packages/core/ai/src/mcp-manager/types.ts index fd542acbdff..ea432b0bef8 100644 --- a/packages/core/ai/src/mcp-manager/types.ts +++ b/packages/core/ai/src/mcp-manager/types.ts @@ -11,6 +11,7 @@ import type { MultiServerMCPClient } from '@langchain/mcp-adapters'; import type { DynamicToolsProvider, Permission } from '../tools-manager/types'; export interface MCPManager extends MCPRegistration { + init(): Promise; getMCP(name: string): Promise; listMCP(filter: MCPFilter): Promise; testConnection(options: MCPOptions): Promise; diff --git a/packages/core/ai/src/skills-manager/index.ts b/packages/core/ai/src/skills-manager/index.ts index ca842f63015..ff848c8042f 100644 --- a/packages/core/ai/src/skills-manager/index.ts +++ b/packages/core/ai/src/skills-manager/index.ts @@ -20,12 +20,13 @@ export class DefaultSkillsManager implements SkillsManager { constructor(private readonly app: any) { this.provideCollectionManager = () => app.mainDataSource; - this.app.on('afterStart', async () => { - if (this.mode === 'memory') { - await this.persistence(); - this.mode = 'database'; - } - }); + } + + async init() { + if (this.mode === 'memory') { + await this.persistence(); + this.mode = 'database'; + } } getSkills(name: string[]): Promise; diff --git a/packages/core/ai/src/skills-manager/types.ts b/packages/core/ai/src/skills-manager/types.ts index c7798b46e82..b2356ccdd4b 100644 --- a/packages/core/ai/src/skills-manager/types.ts +++ b/packages/core/ai/src/skills-manager/types.ts @@ -8,11 +8,11 @@ */ export interface SkillsManager { + init(): Promise; getSkills(name: string[]): Promise; getSkills(name: string): Promise; listSkills(filter?: SkillsFilter): Promise; registerSkills(options: SkillsOptions): Promise; - persistence(): Promise; } export type SkillsOptions = { diff --git a/packages/plugins/@nocobase/plugin-ai/src/client/__tests__/mcp/MCPSettings.test.ts b/packages/plugins/@nocobase/plugin-ai/src/client/__tests__/mcp/MCPSettings.test.ts deleted file mode 100644 index bfac17a13e2..00000000000 --- a/packages/plugins/@nocobase/plugin-ai/src/client/__tests__/mcp/MCPSettings.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -import { describe, expect, it } from 'vitest'; -import { getMCPToolsByServer } from '../../ai-employees/admin/mcp/MCPToolsList'; - -describe('getMCPToolsByServer', () => { - it('returns tools for the target MCP server only', () => { - expect( - getMCPToolsByServer( - { - weather: [ - { name: 'mcp-weather-current', title: 'current', serverName: 'weather', permission: 'ALLOW' }, - { name: 'mcp-weather-forecast', title: 'forecast', serverName: 'weather', permission: 'ASK' }, - ], - maps: [{ name: 'mcp-maps-search', title: 'search', serverName: 'maps', permission: 'ASK' }], - }, - 'weather', - ), - ).toEqual([ - { name: 'mcp-weather-current', title: 'current', serverName: 'weather', permission: 'ALLOW' }, - { name: 'mcp-weather-forecast', title: 'forecast', serverName: 'weather', permission: 'ASK' }, - ]); - }); - - it('returns an empty array for unknown servers', () => { - expect(getMCPToolsByServer(undefined, 'weather')).toEqual([]); - expect(getMCPToolsByServer({}, 'weather')).toEqual([]); - }); -}); diff --git a/packages/plugins/@nocobase/plugin-ai/src/server/plugin.ts b/packages/plugins/@nocobase/plugin-ai/src/server/plugin.ts index d08b14b9cb6..2affd0b1c37 100644 --- a/packages/plugins/@nocobase/plugin-ai/src/server/plugin.ts +++ b/packages/plugins/@nocobase/plugin-ai/src/server/plugin.ts @@ -90,6 +90,11 @@ export class PluginAIServer extends Plugin { } }, }); + this.app.on('afterStart', async () => { + await this.ai.skillsManager.init(); + await this.ai.employeeManager.init(); + await this.ai.mcpManager.init(); + }); } async load() {