Files
ai-terminal-assistant/packages/core/tests/unit/agent/registry.test.ts
T
kurihada 5e32375f0e feat: 重构为 Monorepo 架构并实现 HTTP Server
架构变更:
- 采用 pnpm workspaces 实现 Monorepo 结构
- 将现有代码迁移到 packages/core
- 新增 packages/server HTTP 服务层

Server 功能:
- REST API: 会话管理、工具管理、配置管理
- WebSocket: 实时双向通信支持
- SSE: 服务端事件推送
- Hono + Bun 作为运行时

API 端点:
- GET/POST /api/sessions - 会话 CRUD
- GET/POST /api/sessions/:id/messages - 消息管理
- GET /api/sessions/:id/events - SSE 事件流
- WS /api/ws/:sessionId - WebSocket 连接
- GET/POST /api/tools - 工具管理
- GET/PUT /api/config - 配置管理
2025-12-12 10:42:20 +08:00

351 lines
9.2 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { AgentRegistry } from '../../../src/agent/registry.js';
import type { AgentInfo, AgentConfigFile } from '../../../src/agent/types.js';
// Mock config-loader
vi.mock('../../../src/agent/config-loader.js', () => ({
loadAgentConfig: vi.fn(),
}));
// Mock presets
vi.mock('../../../src/agent/presets/index.js', () => ({
presetAgents: {
explore: {
description: '代码探索 Agent',
mode: 'subagent' as const,
prompt: '你是代码探索助手',
maxSteps: 5,
},
'code-reviewer': {
description: '代码审查 Agent',
mode: 'subagent' as const,
prompt: '你是代码审查助手',
},
build: {
description: '构建 Agent',
mode: 'all' as const,
prompt: '你是构建助手',
},
},
}));
import { loadAgentConfig } from '../../../src/agent/config-loader.js';
describe('AgentRegistry - Agent 注册表', () => {
let registry: AgentRegistry;
beforeEach(() => {
registry = new AgentRegistry();
vi.clearAllMocks();
vi.mocked(loadAgentConfig).mockResolvedValue(null);
});
describe('init - 初始化', () => {
it('初始化后注册预设 Agent', async () => {
await registry.init('/test/project');
expect(registry.has('explore')).toBe(true);
expect(registry.has('code-reviewer')).toBe(true);
expect(registry.has('build')).toBe(true);
});
it('初始化加载用户配置', async () => {
const userConfig: AgentConfigFile = {
defaults: {
maxSteps: 20,
},
agents: {
'custom-agent': {
description: '自定义 Agent',
mode: 'subagent',
prompt: '你是自定义助手',
},
},
};
vi.mocked(loadAgentConfig).mockResolvedValue(userConfig);
await registry.init('/test/project');
expect(registry.has('custom-agent')).toBe(true);
});
it('重复初始化只执行一次', async () => {
await registry.init('/test/project');
await registry.init('/test/project');
expect(loadAgentConfig).toHaveBeenCalledTimes(1);
});
});
describe('get - 获取 Agent', () => {
beforeEach(async () => {
await registry.init('/test/project');
});
it('获取存在的 Agent', () => {
const agent = registry.get('explore');
expect(agent).toBeDefined();
expect(agent?.name).toBe('explore');
expect(agent?.description).toBe('代码探索 Agent');
});
it('获取不存在的 Agent 返回 undefined', () => {
const agent = registry.get('non-existent');
expect(agent).toBeUndefined();
});
it('获取的 Agent 应用全局配置', async () => {
vi.mocked(loadAgentConfig).mockResolvedValue({
defaults: {
maxSteps: 25,
},
});
const newRegistry = new AgentRegistry();
await newRegistry.init('/test/project');
const agent = newRegistry.get('code-reviewer');
// code-reviewer 没有设置 maxSteps,应该使用全局默认值
expect(agent?.maxSteps).toBe(25);
});
it('Agent 自己的配置优先于全局配置', async () => {
vi.mocked(loadAgentConfig).mockResolvedValue({
defaults: {
maxSteps: 25,
},
});
const newRegistry = new AgentRegistry();
await newRegistry.init('/test/project');
const agent = newRegistry.get('explore');
// explore 设置了 maxSteps: 5
expect(agent?.maxSteps).toBe(5);
});
});
describe('list - 列出 Agent', () => {
beforeEach(async () => {
await registry.init('/test/project');
});
it('列出所有 Agent', () => {
const agents = registry.list();
expect(agents.length).toBeGreaterThan(0);
expect(agents.some(a => a.name === 'explore')).toBe(true);
expect(agents.some(a => a.name === 'code-reviewer')).toBe(true);
});
it('按 mode 过滤 Agent', () => {
const subagents = registry.list('subagent');
expect(subagents.every(a => a.mode === 'subagent' || a.mode === 'all')).toBe(true);
});
});
describe('listSubagents - 列出子 Agent', () => {
beforeEach(async () => {
await registry.init('/test/project');
});
it('排除 primary-only 的 Agent', () => {
const subagents = registry.listSubagents();
expect(subagents.every(a => a.mode !== 'primary')).toBe(true);
});
});
describe('listPrimaryAgents - 列出主 Agent', () => {
beforeEach(async () => {
await registry.init('/test/project');
});
it('排除 subagent-only 的 Agent', () => {
const primaryAgents = registry.listPrimaryAgents();
expect(primaryAgents.every(a => a.mode !== 'subagent')).toBe(true);
});
});
describe('register - 动态注册', () => {
beforeEach(async () => {
await registry.init('/test/project');
});
it('注册新 Agent', () => {
const newAgent: AgentInfo = {
name: 'dynamic-agent',
description: '动态注册的 Agent',
mode: 'subagent',
prompt: '你是动态 Agent',
};
registry.register(newAgent);
expect(registry.has('dynamic-agent')).toBe(true);
expect(registry.get('dynamic-agent')?.description).toBe('动态注册的 Agent');
});
it('覆盖已有 Agent', () => {
const updatedAgent: AgentInfo = {
name: 'explore',
description: '更新后的探索 Agent',
mode: 'all',
prompt: '更新后的提示',
};
registry.register(updatedAgent);
const agent = registry.get('explore');
expect(agent?.description).toBe('更新后的探索 Agent');
});
});
describe('remove - 移除 Agent', () => {
beforeEach(async () => {
await registry.init('/test/project');
});
it('移除存在的 Agent', () => {
const result = registry.remove('explore');
expect(result).toBe(true);
expect(registry.has('explore')).toBe(false);
});
it('移除不存在的 Agent 返回 false', () => {
const result = registry.remove('non-existent');
expect(result).toBe(false);
});
});
describe('has - 检查 Agent 是否存在', () => {
beforeEach(async () => {
await registry.init('/test/project');
});
it('存在的 Agent 返回 true', () => {
expect(registry.has('explore')).toBe(true);
});
it('不存在的 Agent 返回 false', () => {
expect(registry.has('non-existent')).toBe(false);
});
});
describe('size - 获取 Agent 数量', () => {
beforeEach(async () => {
await registry.init('/test/project');
});
it('返回正确的数量', () => {
expect(registry.size).toBeGreaterThan(0);
});
it('添加后数量增加', () => {
const initialSize = registry.size;
registry.register({
name: 'new-agent',
description: 'New',
mode: 'subagent',
prompt: 'New agent',
});
expect(registry.size).toBe(initialSize + 1);
});
it('移除后数量减少', () => {
const initialSize = registry.size;
registry.remove('explore');
expect(registry.size).toBe(initialSize - 1);
});
});
describe('getNames - 获取所有 Agent 名称', () => {
beforeEach(async () => {
await registry.init('/test/project');
});
it('返回所有 Agent 名称', () => {
const names = registry.getNames();
expect(names).toContain('explore');
expect(names).toContain('code-reviewer');
expect(names).toContain('build');
});
});
describe('getGlobalConfig - 获取全局配置', () => {
it('无用户配置时返回 null', async () => {
vi.mocked(loadAgentConfig).mockResolvedValue(null);
await registry.init('/test/project');
expect(registry.getGlobalConfig()).toBeNull();
});
it('有用户配置时返回 defaults', async () => {
vi.mocked(loadAgentConfig).mockResolvedValue({
defaults: {
maxSteps: 30,
model: {
temperature: 0.5,
},
},
});
const newRegistry = new AgentRegistry();
await newRegistry.init('/test/project');
const globalConfig = newRegistry.getGlobalConfig();
expect(globalConfig?.maxSteps).toBe(30);
expect(globalConfig?.model?.temperature).toBe(0.5);
});
});
describe('generateSubagentDescription - 生成子 Agent 描述', () => {
beforeEach(async () => {
await registry.init('/test/project');
});
it('生成包含所有子 Agent 的描述', () => {
const description = registry.generateSubagentDescription();
expect(description).toContain('explore');
expect(description).toContain('代码探索');
});
it('无子 Agent 时返回提示信息', async () => {
// 移除所有 Agent
for (const name of registry.getNames()) {
registry.remove(name);
}
const description = registry.generateSubagentDescription();
expect(description).toContain('没有可用');
});
});
});
describe('agentRegistry 单例', () => {
it('导出单例实例', async () => {
const { agentRegistry } = await import('../../../src/agent/registry.js');
expect(agentRegistry).toBeDefined();
expect(agentRegistry).toBeInstanceOf(AgentRegistry);
});
});