完整实现 Swarm 多智能体协作系统

- 新增 CLIPluginAdapter 统一接口 (backend/app/core/agent_adapter.py)
- 新增 LLM 服务层,支持 Anthropic/OpenAI/DeepSeek/Ollama (backend/app/services/llm_service.py)
- 新增 Agent 执行引擎,支持文件锁自动管理 (backend/app/services/agent_executor.py)
- 新增 NativeLLMAgent 原生 LLM 适配器 (backend/app/adapters/native_llm_agent.py)
- 新增进程管理器 (backend/app/services/process_manager.py)
- 新增 Agent 控制 API (backend/app/routers/agents_control.py)
- 新增 WebSocket 实时通信 (backend/app/routers/websocket.py)
- 更新前端 AgentsPage,支持启动/停止 Agent
- 测试通过:Agent 启动、批量操作、栅栏同步

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Code
2026-03-09 17:32:11 +08:00
commit dc398d7c7b
118 changed files with 23120 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
import { test, expect } from '@playwright/test';
// API 接口测试
test.describe('Swarm API 接口测试', () => {
const apiBase = 'http://localhost:8000/api';
test('健康检查接口', async ({ request }) => {
const response = await request.get(`${apiBase}/health`);
expect(response.ok()).toBeTruthy();
const body = await response.json();
expect(body).toHaveProperty('status');
expect(body).toHaveProperty('version');
expect(body.status).toBe('healthy');
});
test('获取 Agent 列表', async ({ request }) => {
const response = await request.get(`${apiBase}/agents`);
expect(response.ok()).toBeTruthy();
const body = await response.json();
expect(body).toHaveProperty('agents');
expect(Array.isArray(body.agents)).toBeTruthy();
});
test('获取文件锁列表', async ({ request }) => {
const response = await request.get(`${apiBase}/locks`);
expect(response.ok()).toBeTruthy();
const body = await response.json();
expect(body).toHaveProperty('locks');
expect(Array.isArray(body.locks)).toBeTruthy();
});
test('获取心跳列表', async ({ request }) => {
const response = await request.get(`${apiBase}/heartbeats`);
expect(response.ok()).toBeTruthy();
const body = await response.json();
expect(body).toHaveProperty('heartbeats');
});
test('注册 Agent', async ({ request }) => {
const testAgent = {
agent_id: `test-agent-${Date.now()}`,
name: 'Test Agent',
role: 'developer',
model: 'test-model',
description: 'Playwright 测试用 Agent',
};
const response = await request.post(`${apiBase}/agents/register`, {
data: testAgent,
});
expect(response.ok()).toBeTruthy();
const body = await response.json();
expect(body.agent_id).toBe(testAgent.agent_id);
expect(body.name).toBe(testAgent.name);
});
test('解析任务文件', async ({ request }) => {
const response = await request.post(`${apiBase}/parse-task`, {
data: { task: '修复 src/auth/login.py 中的 bug' },
});
expect(response.ok()).toBeTruthy();
const body = await response.json();
expect(body).toHaveProperty('files');
expect(Array.isArray(body.files)).toBeTruthy();
});
test('获取任务主要角色', async ({ request }) => {
const response = await request.post(`${apiBase}/roles/primary`, {
data: { task: '实现登录功能并编写测试用例' },
});
expect(response.ok()).toBeTruthy();
const body = await response.json();
expect(body).toHaveProperty('primary_role');
expect(body).toHaveProperty('role_scores');
});
test('列出工作流文件', async ({ request }) => {
const response = await request.get(`${apiBase}/workflows/files`);
expect(response.ok()).toBeTruthy();
const body = await response.json();
expect(body).toHaveProperty('files');
expect(Array.isArray(body.files)).toBeTruthy();
});
});

View File

@@ -0,0 +1,267 @@
import { test, expect, Page } from '@playwright/test';
/**
* Swarm Command Center - 端到端工作流测试
* 测试完整用户场景和交互流程
*/
// 辅助函数:等待页面加载完成
async function waitForPageLoad(page: Page) {
await page.waitForLoadState('networkidle');
await page.waitForSelector('text=Swarm Center', { state: 'visible' });
}
// 辅助函数:导航到指定页面
async function navigateTo(page: Page, menuText: string) {
await page.click(`text=${menuText}`);
await page.waitForLoadState('networkidle');
}
test.describe('端到端工作流测试', () => {
const baseUrl = 'http://localhost:3000';
test.beforeEach(async ({ page }) => {
await page.goto(baseUrl);
await waitForPageLoad(page);
});
test('完整用户旅程 - 从仪表盘到各功能模块', async ({ page }) => {
// 1. 验证仪表盘首页
await expect(page.locator('h1:has-text("仪表盘")')).toBeVisible();
await expect(page.locator('text=系统概览与实时监控')).toBeVisible();
// 2. 点击统计卡片导航到 Agent 管理
await page.click('text=Agent 总数');
await expect(page).toHaveURL(/\/agents/);
await expect(page.locator('h1:has-text("Agent 管理")')).toBeVisible();
// 3. 返回仪表盘
await navigateTo(page, '仪表盘');
await expect(page).toHaveURL(/\/$/);
// 4. 点击会议统计卡片
await page.click('text=今日会议');
await expect(page).toHaveURL(/\/meetings/);
await expect(page.locator('h1:has-text("会议管理")')).toBeVisible();
});
test('Agent 管理 - 完整 CRUD 流程', async ({ page }) => {
await navigateTo(page, 'Agent 管理');
// 1. 检查页面元素
await expect(page.locator('button:has-text("注册 Agent")')).toBeVisible();
await expect(page.locator('input[placeholder="搜索 Agent..."]')).toBeVisible();
// 2. 打开注册对话框
await page.click('button:has-text("注册 Agent")');
await expect(page.locator('text=注册新 Agent')).toBeVisible();
// 3. 填写表单
const testAgentName = `TestAgent-${Date.now()}`;
await page.fill('input[name="name"]', testAgentName);
await page.fill('input[name="model"]', 'gpt-4');
await page.selectOption('select[name="role"]', 'developer');
await page.fill('textarea[name="description"]', 'Playwright 端到端测试用 Agent');
// 4. 验证表单填写成功
await expect(page.locator('input[name="name"]')).toHaveValue(testAgentName);
await expect(page.locator('select[name="role"]')).toHaveValue('developer');
// 5. 关闭对话框(取消提交,避免影响实际数据)
await page.click('button:has-text("取消")');
await expect(page.locator('text=注册新 Agent')).not.toBeVisible();
// 6. 测试搜索功能
await page.fill('input[placeholder="搜索 Agent..."]', 'nonexistent');
await expect(page.locator('text=没有找到匹配的 Agent')).toBeVisible();
});
test('会议管理 - 创建会议流程', async ({ page }) => {
await navigateTo(page, '会议管理');
// 1. 检查页面元素
await expect(page.locator('button:has-text("创建会议")')).toBeVisible();
// 2. 打开创建会议对话框
await page.click('button:has-text("创建会议")');
await expect(page.locator('text=创建新会议')).toBeVisible();
// 3. 填写会议表单
const testMeetingTitle = `TestMeeting-${Date.now()}`;
await page.fill('input[name="title"]', testMeetingTitle);
await page.fill('textarea[name="agenda"]', '测试议程内容\n1. 测试项1\n2. 测试项2');
await page.selectOption('select[name="type"]', 'design_review');
// 4. 添加参与者
await page.click('button:has-text("添加参与者")');
// 注:实际实现可能需要选择具体的 Agent
// 5. 验证表单填写
await expect(page.locator('input[name="title"]')).toHaveValue(testMeetingTitle);
// 6. 关闭对话框
await page.click('button:has-text("取消")');
await expect(page.locator('text=创建新会议')).not.toBeVisible();
});
test('资源监控 - 切换标签页和数据展示', async ({ page }) => {
await navigateTo(page, '资源监控');
// 1. 验证标签页存在
const tabs = ['文件锁', '心跳状态', 'Agent 状态'];
for (const tab of tabs) {
await expect(page.locator(`button:has-text("${tab}")`)).toBeVisible();
}
// 2. 切换标签页
await page.click('button:has-text("心跳状态")');
await page.waitForTimeout(300); // 等待切换动画
await page.click('button:has-text("Agent 状态")');
await page.waitForTimeout(300);
await page.click('button:has-text("文件锁")');
await page.waitForTimeout(300);
// 3. 验证刷新按钮
await expect(page.locator('button:has-text("刷新")')).toBeVisible();
await page.click('button:has-text("刷新")');
await expect(page.locator('text=加载中...')).not.toBeVisible();
});
test('工作流页面 - 工作流操作', async ({ page }) => {
await navigateTo(page, '工作流');
// 1. 验证页面元素
await expect(page.locator('h1:has-text("工作流")')).toBeVisible();
await expect(page.locator('button:has-text("新建工作流")')).toBeVisible();
// 2. 打开新建工作流对话框
await page.click('button:has-text("新建工作流")');
await expect(page.locator('text=新建工作流')).toBeVisible();
// 3. 填写工作流名称
const workflowName = `TestWorkflow-${Date.now()}`;
await page.fill('input[name="name"]', workflowName);
// 4. 关闭对话框
await page.click('button:has-text("取消")');
});
test('系统配置 - 保存设置', async ({ page }) => {
await navigateTo(page, '配置');
// 1. 验证页面元素
await expect(page.locator('h1:has-text("系统配置")')).toBeVisible();
await expect(page.locator('text=API 基础地址')).toBeVisible();
// 2. 修改配置
await page.fill('input[name="apiBaseUrl"]', 'http://localhost:9000');
await page.fill('input[name="heartbeatInterval"]', '15');
// 3. 保存配置
await page.click('button:has-text("保存配置")');
// 4. 验证成功提示
await expect(page.locator('text=配置已保存')).toBeVisible();
// 5. 恢复原始配置
await page.fill('input[name="apiBaseUrl"]', 'http://localhost:8000');
await page.fill('input[name="heartbeatInterval"]', '30');
await page.click('button:has-text("保存配置")');
});
test('响应式布局 - 侧边栏折叠展开', async ({ page }) => {
// 1. 验证侧边栏默认展开
await expect(page.locator('text=仪表盘')).toBeVisible();
await expect(page.locator('text=Agent 管理')).toBeVisible();
// 2. 测试移动端视图(如果支持)
await page.setViewportSize({ width: 375, height: 667 });
await page.waitForTimeout(500);
// 3. 恢复桌面视图
await page.setViewportSize({ width: 1280, height: 720 });
await page.waitForTimeout(500);
// 4. 验证侧边栏仍然可见
await expect(page.locator('text=仪表盘')).toBeVisible();
});
});
test.describe('错误处理和边界情况', () => {
const baseUrl = 'http://localhost:3000';
test('后端连接失败时的错误显示', async ({ page }) => {
// 模拟后端不可用的情况
await page.route('http://localhost:8000/api/**', (route) => {
route.abort('failed');
});
await page.goto(baseUrl);
await waitForPageLoad(page);
// 验证错误提示显示
await expect(page.locator('text=连接后端失败')).toBeVisible();
});
test('404 页面处理', async ({ page }) => {
await page.goto(`${baseUrl}/nonexistent-page`);
await waitForPageLoad(page);
// 应该重定向到首页
await expect(page).toHaveURL(/\/$/);
await expect(page.locator('h1:has-text("仪表盘")')).toBeVisible();
});
test('快速导航切换', async ({ page }) => {
await page.goto(baseUrl);
await waitForPageLoad(page);
// 快速切换多个页面
const pages = ['Agent 管理', '会议管理', '资源监控', '工作流', '配置'];
for (const pageName of pages) {
await navigateTo(page, pageName);
}
// 最后回到仪表盘
await navigateTo(page, '仪表盘');
await expect(page.locator('h1:has-text("仪表盘")')).toBeVisible();
});
});
test.describe('性能和加载测试', () => {
const baseUrl = 'http://localhost:3000';
test('页面加载性能', async ({ page }) => {
const startTime = Date.now();
await page.goto(baseUrl);
await waitForPageLoad(page);
const loadTime = Date.now() - startTime;
// 验证页面加载时间在合理范围内5秒内
expect(loadTime).toBeLessThan(5000);
// 验证关键元素渲染
await expect(page.locator('h1:has-text("仪表盘")')).toBeVisible();
});
test('数据刷新功能', async ({ page }) => {
await page.goto(baseUrl);
await waitForPageLoad(page);
// 等待自动刷新间隔10秒
// 这里我们只是验证初始加载成功
await expect(page.locator('text=自动刷新')).toBeVisible();
await expect(page.locator('text=10 秒')).toBeVisible();
});
test('大量数据渲染性能', async ({ page }) => {
// 这个测试需要后端返回大量数据
// 目前只是验证页面结构可以支持列表渲染
await navigateTo(page, 'Agent 管理');
// 验证列表容器存在
await expect(page.locator('[data-testid="agent-list"]').or(page.locator('text=暂无 Agent 数据'))).toBeVisible();
});
});

View File

@@ -0,0 +1,77 @@
import { test, expect } from '@playwright/test';
// 前端导航测试
test.describe('Swarm Command Center - 前端导航测试', () => {
const baseUrl = 'http://localhost:3000';
test.beforeEach(async ({ page }) => {
await page.goto(baseUrl);
});
test('页面标题正确显示', async ({ page }) => {
await expect(page).toHaveTitle(/Swarm/);
});
test('侧边栏导航显示正确', async ({ page }) => {
// 检查 Logo 和标题
await expect(page.locator('text=Swarm Center')).toBeVisible();
await expect(page.locator('text=多智能体协作系统')).toBeVisible();
// 检查导航链接
await expect(page.locator('text=仪表盘')).toBeVisible();
await expect(page.locator('text=Agent 管理')).toBeVisible();
await expect(page.locator('text=会议管理')).toBeVisible();
await expect(page.locator('text=资源监控')).toBeVisible();
await expect(page.locator('text=工作流')).toBeVisible();
await expect(page.locator('text=配置')).toBeVisible();
});
test('仪表盘页面加载', async ({ page }) => {
await expect(page.locator('h1:has-text("仪表盘")')).toBeVisible();
await expect(page.locator('text=系统概览与实时监控')).toBeVisible();
// 检查统计卡片
await expect(page.locator('text=Agent 总数')).toBeVisible();
await expect(page.locator('text=今日会议')).toBeVisible();
await expect(page.locator('text=文件锁')).toBeVisible();
await expect(page.locator('text=在线 Agent')).toBeVisible();
});
test('Agent 管理页面导航', async ({ page }) => {
await page.click('text=Agent 管理');
await expect(page).toHaveURL(/\/agents/);
await expect(page.locator('h1:has-text("Agent 管理")')).toBeVisible();
await expect(page.locator('button:has-text("注册 Agent")')).toBeVisible();
});
test('会议管理页面导航', async ({ page }) => {
await page.click('text=会议管理');
await expect(page).toHaveURL(/\/meetings/);
await expect(page.locator('h1:has-text("会议管理")')).toBeVisible();
await expect(page.locator('button:has-text("创建会议")')).toBeVisible();
});
test('资源监控页面导航', async ({ page }) => {
await page.click('text=资源监控');
await expect(page).toHaveURL(/\/resources/);
await expect(page.locator('h1:has-text("资源监控")')).toBeVisible();
// 检查标签页
await expect(page.locator('button:has-text("文件锁")')).toBeVisible();
await expect(page.locator('button:has-text("心跳状态")')).toBeVisible();
await expect(page.locator('button:has-text("Agent 状态")')).toBeVisible();
});
test('工作流页面导航', async ({ page }) => {
await page.click('text=工作流');
await expect(page).toHaveURL(/\/workflow/);
await expect(page.locator('h1:has-text("工作流")')).toBeVisible();
});
test('配置页面导航', async ({ page }) => {
await page.click('text=配置');
await expect(page).toHaveURL(/\/settings/);
await expect(page.locator('h1:has-text("系统配置")')).toBeVisible();
await expect(page.locator('text=API 基础地址')).toBeVisible();
});
});