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
This commit is contained in:
Drol
2026-03-25 11:46:59 +08:00
committed by GitHub
parent e481b533a3
commit 22d628bcb3
13 changed files with 58 additions and 83 deletions
@@ -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',
],
},
});
});
+1 -2
View File
@@ -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();
@@ -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');
@@ -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<AIEmployeeEntry> {
@@ -55,6 +55,7 @@ export type AIEmployeeFilter = {
};
export interface AIEmployeeManager {
init(): Promise<void>;
getEmployee(username: string): Promise<AIEmployeeEntry>;
listEmployees(filter?: AIEmployeeFilter): Promise<AIEmployeeEntry[]>;
registerEmployee(options: AIEmployeeOptions): Promise<void>;
+3 -5
View File
@@ -92,11 +92,9 @@ export class AIEmployeeLoader extends LoadAndRegister<AIEmployeeLoaderOptions> {
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);
}
}
}
+10 -12
View File
@@ -101,18 +101,16 @@ export class SkillsLoader extends LoadAndRegister<SkillsLoaderOptions> {
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,
});
}
}
}
+13 -12
View File
@@ -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<void> {
@@ -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),
});
@@ -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<void>;
getMCP(name: string): Promise<MCPEntry>;
listMCP(filter: MCPFilter): Promise<MCPEntry[]>;
testConnection(options: MCPOptions): Promise<MCPTestResult>;
+7 -6
View File
@@ -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<SkillsEntry[]>;
+1 -1
View File
@@ -8,11 +8,11 @@
*/
export interface SkillsManager {
init(): Promise<void>;
getSkills(name: string[]): Promise<SkillsEntry[]>;
getSkills(name: string): Promise<SkillsEntry>;
listSkills(filter?: SkillsFilter): Promise<SkillsEntry[]>;
registerSkills(options: SkillsOptions): Promise<void>;
persistence(): Promise<void>;
}
export type SkillsOptions = {
@@ -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([]);
});
});
@@ -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() {