更新 API 路由,添加获取所有代理、心跳、锁和会议的端点;修改会议队列和前端请求处理;新增前端完整测试脚本。
This commit is contained in:
9
.doc/agents/kimi-001/info.json
Normal file
9
.doc/agents/kimi-001/info.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"agent_id": "kimi-001",
|
||||||
|
"name": "Kimi CLI",
|
||||||
|
"role": "architect",
|
||||||
|
"model": "kimi-k2",
|
||||||
|
"description": "Kimi CLI - architect",
|
||||||
|
"created_at": "2026-03-09T18:23:33.409369",
|
||||||
|
"status": "idle"
|
||||||
|
}
|
||||||
7
.doc/agents/kimi-001/state.json
Normal file
7
.doc/agents/kimi-001/state.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"agent_id": "kimi-001",
|
||||||
|
"current_task": "",
|
||||||
|
"progress": 0,
|
||||||
|
"working_files": [],
|
||||||
|
"last_update": "2026-03-09T18:23:33.413023"
|
||||||
|
}
|
||||||
9
.doc/agents/opencode-001/info.json
Normal file
9
.doc/agents/opencode-001/info.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"agent_id": "opencode-001",
|
||||||
|
"name": "OpenCode",
|
||||||
|
"role": "reviewer",
|
||||||
|
"model": "opencode-v1",
|
||||||
|
"description": "OpenCode - reviewer",
|
||||||
|
"created_at": "2026-03-09T18:23:34.314235",
|
||||||
|
"status": "idle"
|
||||||
|
}
|
||||||
7
.doc/agents/opencode-001/state.json
Normal file
7
.doc/agents/opencode-001/state.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"agent_id": "opencode-001",
|
||||||
|
"current_task": "",
|
||||||
|
"progress": 0,
|
||||||
|
"working_files": [],
|
||||||
|
"last_update": "2026-03-09T18:23:34.317455"
|
||||||
|
}
|
||||||
18
.doc/cache/heartbeats.json
vendored
18
.doc/cache/heartbeats.json
vendored
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"claude-001": {
|
"claude-001": {
|
||||||
"agent_id": "claude-001",
|
"agent_id": "claude-001",
|
||||||
"last_heartbeat": "2026-03-05T10:31:09.197892",
|
"last_heartbeat": "2026-03-09T18:23:29.886176",
|
||||||
"status": "idle",
|
"status": "idle",
|
||||||
"current_task": "",
|
"current_task": "",
|
||||||
"progress": 100
|
"progress": 0
|
||||||
},
|
},
|
||||||
"kimi-002": {
|
"kimi-002": {
|
||||||
"agent_id": "kimi-002",
|
"agent_id": "kimi-002",
|
||||||
@@ -47,5 +47,19 @@
|
|||||||
"status": "waiting",
|
"status": "waiting",
|
||||||
"current_task": "等待会议: meeting-002",
|
"current_task": "等待会议: meeting-002",
|
||||||
"progress": 0
|
"progress": 0
|
||||||
|
},
|
||||||
|
"kimi-001": {
|
||||||
|
"agent_id": "kimi-001",
|
||||||
|
"last_heartbeat": "2026-03-09T18:23:33.415851",
|
||||||
|
"status": "idle",
|
||||||
|
"current_task": "",
|
||||||
|
"progress": 0
|
||||||
|
},
|
||||||
|
"opencode-001": {
|
||||||
|
"agent_id": "opencode-001",
|
||||||
|
"last_heartbeat": "2026-03-09T18:23:34.321019",
|
||||||
|
"status": "idle",
|
||||||
|
"current_task": "",
|
||||||
|
"progress": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
13
.doc/cache/meeting_queue.json
vendored
13
.doc/cache/meeting_queue.json
vendored
@@ -35,16 +35,15 @@
|
|||||||
"meeting_id": "test-meeting-001",
|
"meeting_id": "test-meeting-001",
|
||||||
"title": "测试会议",
|
"title": "测试会议",
|
||||||
"expected_attendees": [
|
"expected_attendees": [
|
||||||
"agent-001",
|
"claude-001",
|
||||||
"agent-002"
|
"kimi-001"
|
||||||
],
|
],
|
||||||
"arrived_attendees": [
|
"arrived_attendees": [
|
||||||
"agent-001",
|
"claude-001"
|
||||||
"agent-002"
|
|
||||||
],
|
],
|
||||||
"status": "ended",
|
"status": "waiting",
|
||||||
"created_at": "2026-03-09T09:28:05.309297",
|
"created_at": "2026-03-09T18:05:28.657165",
|
||||||
"started_at": "2026-03-09T09:28:05.357846",
|
"started_at": "",
|
||||||
"min_required": 2
|
"min_required": 2
|
||||||
},
|
},
|
||||||
"meeting-001": {
|
"meeting-001": {
|
||||||
|
|||||||
@@ -20,7 +20,15 @@ app = FastAPI(
|
|||||||
# 配置 CORS - 允许前端访问
|
# 配置 CORS - 允许前端访问
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=["http://localhost:3000", "http://127.0.0.1:3000"],
|
allow_origins=[ # 必须具体列出,不能使用 * 当 allow_credentials=True
|
||||||
|
"http://localhost:3000",
|
||||||
|
"http://localhost:3001",
|
||||||
|
"http://127.0.0.1:3000",
|
||||||
|
"http://127.0.0.1:3001",
|
||||||
|
"http://localhost:5173",
|
||||||
|
"http://127.0.0.1:5173",
|
||||||
|
"http://localhost:8080",
|
||||||
|
],
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class AgentCreate(BaseModel):
|
|||||||
# Agent状态存储
|
# Agent状态存储
|
||||||
agent_states_db = {}
|
agent_states_db = {}
|
||||||
|
|
||||||
|
@router.get("")
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
async def list_agents():
|
async def list_agents():
|
||||||
"""获取所有 Agent 列表"""
|
"""获取所有 Agent 列表"""
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class Heartbeat(BaseModel):
|
|||||||
is_timeout: bool = False
|
is_timeout: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("")
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
async def list_heartbeats():
|
async def list_heartbeats():
|
||||||
"""获取所有 Agent 心跳"""
|
"""获取所有 Agent 心跳"""
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ def format_elapsed(locked_at: float) -> str:
|
|||||||
else:
|
else:
|
||||||
return f"{elapsed / 3600:.1f}小时"
|
return f"{elapsed / 3600:.1f}小时"
|
||||||
|
|
||||||
|
@router.get("")
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
async def list_locks():
|
async def list_locks():
|
||||||
"""获取所有文件锁列表"""
|
"""获取所有文件锁列表"""
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class MeetingCreate(BaseModel):
|
|||||||
attendees: List[str] = []
|
attendees: List[str] = []
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("")
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
async def list_meetings():
|
async def list_meetings():
|
||||||
"""获取所有会议列表"""
|
"""获取所有会议列表"""
|
||||||
|
|||||||
@@ -33,22 +33,37 @@ async def execute_task(request: TaskRequest):
|
|||||||
@router.get("/status")
|
@router.get("/status")
|
||||||
async def get_all_status():
|
async def get_all_status():
|
||||||
"""获取所有 Agent 状态"""
|
"""获取所有 Agent 状态"""
|
||||||
return {
|
from ..services.agent_registry import get_agent_registry
|
||||||
"agents": [
|
from ..services.heartbeat import get_heartbeat_service
|
||||||
{
|
|
||||||
"agent_id": "claude-001",
|
registry = get_agent_registry()
|
||||||
"status": "working",
|
heartbeat_service = get_heartbeat_service()
|
||||||
"current_task": "开发功能",
|
|
||||||
"progress": 75
|
# 获取所有已注册的 Agent
|
||||||
|
all_agents = await registry.list_agents()
|
||||||
|
agent_map = {a.agent_id: a for a in all_agents}
|
||||||
|
|
||||||
|
# 获取所有心跳
|
||||||
|
heartbeats_data = await heartbeat_service.get_all_heartbeats()
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for agent_id, agent in agent_map.items():
|
||||||
|
heartbeat = heartbeats_data.get(agent_id)
|
||||||
|
result.append({
|
||||||
|
"agent_id": agent_id,
|
||||||
|
"info": {
|
||||||
|
"name": agent.name,
|
||||||
|
"role": agent.role,
|
||||||
|
"model": agent.model
|
||||||
},
|
},
|
||||||
{
|
"heartbeat": {
|
||||||
"agent_id": "kimi-001",
|
"status": heartbeat.status if heartbeat else "offline",
|
||||||
"status": "idle",
|
"current_task": heartbeat.current_task if heartbeat else "",
|
||||||
"current_task": "",
|
"progress": heartbeat.progress if heartbeat else 0
|
||||||
"progress": 0
|
|
||||||
}
|
}
|
||||||
]
|
})
|
||||||
}
|
|
||||||
|
return {"agents": result}
|
||||||
|
|
||||||
|
|
||||||
@router.post("/parse-task")
|
@router.post("/parse-task")
|
||||||
|
|||||||
@@ -1009,7 +1009,7 @@ def human_pending_tasks(
|
|||||||
for t in tasks:
|
for t in tasks:
|
||||||
urgent_mark = "[red]⚠️[/red] " if t.is_urgent else ""
|
urgent_mark = "[red]⚠️[/red] " if t.is_urgent else ""
|
||||||
console.print(f" {urgent_mark}[yellow]{t.id}[/yellow]")
|
console.print(f" {urgent_mark}[yellow]{t.id}[/yellow]")
|
||||||
console.print(f" From: {t.from} | Priority: {t.priority}")
|
console.print(f" From: {t.from_user} | Priority: {t.priority}")
|
||||||
if t.title:
|
if t.title:
|
||||||
console.print(f" Title: {t.title}")
|
console.print(f" Title: {t.title}")
|
||||||
console.print(f" Content: {t.content}")
|
console.print(f" Content: {t.content}")
|
||||||
@@ -1029,7 +1029,7 @@ def human_urgent_tasks():
|
|||||||
if tasks:
|
if tasks:
|
||||||
console.print(Panel(f"[red]⚠️ Urgent Tasks ({len(tasks)}):[/red]", border_style="red"))
|
console.print(Panel(f"[red]⚠️ Urgent Tasks ({len(tasks)}):[/red]", border_style="red"))
|
||||||
for t in tasks:
|
for t in tasks:
|
||||||
console.print(f" [yellow]{t.id}[/yellow] | {t.from}")
|
console.print(f" [yellow]{t.id}[/yellow] | {t.from_user}")
|
||||||
console.print(f" Content: {t.content}")
|
console.print(f" Content: {t.content}")
|
||||||
else:
|
else:
|
||||||
console.print(Panel("[green]No urgent tasks[/green]", border_style="green"))
|
console.print(Panel("[green]No urgent tasks[/green]", border_style="green"))
|
||||||
@@ -1076,7 +1076,7 @@ def human_pending_comments(
|
|||||||
if comments:
|
if comments:
|
||||||
console.print(Panel(f"[cyan]Pending Comments ({len(comments)}):[/cyan]", border_style="cyan"))
|
console.print(Panel(f"[cyan]Pending Comments ({len(comments)}):[/cyan]", border_style="cyan"))
|
||||||
for c in comments:
|
for c in comments:
|
||||||
console.print(f" [yellow]{c.id}[/yellow] | {c.from} -> {c.meeting_id}")
|
console.print(f" [yellow]{c.id}[/yellow] | {c.from_user} -> {c.meeting_id}")
|
||||||
console.print(f" Type: {c.type} | Priority: {c.priority}")
|
console.print(f" Type: {c.type} | Priority: {c.priority}")
|
||||||
console.print(f" Content: {c.content}")
|
console.print(f" Content: {c.content}")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -19,7 +19,19 @@ async function request<T>(
|
|||||||
endpoint: string,
|
endpoint: string,
|
||||||
options?: RequestInit
|
options?: RequestInit
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const url = `${API_BASE}${endpoint}`;
|
// 自动添加末尾斜杠避免 307 重定向
|
||||||
|
let normalizedEndpoint = endpoint;
|
||||||
|
if (endpoint.includes('?')) {
|
||||||
|
// 带查询参数:在 ? 之前添加斜杠
|
||||||
|
const [path, query] = endpoint.split('?');
|
||||||
|
if (!path.endsWith('/')) {
|
||||||
|
normalizedEndpoint = `${path}/?${query}`;
|
||||||
|
}
|
||||||
|
} else if (!endpoint.match(/\/[^/]+$/)) {
|
||||||
|
// 不带查询参数且不以路径结尾:添加斜杠
|
||||||
|
normalizedEndpoint = `${endpoint}/`;
|
||||||
|
}
|
||||||
|
const url = `${API_BASE}${normalizedEndpoint}`;
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
2
resources_errors.txt
Normal file
2
resources_errors.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Total messages: 3 (Errors: 0, Warnings: 0)
|
||||||
|
Returning 0 messages for level "error"
|
||||||
5
test_errors.txt
Normal file
5
test_errors.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Total messages: 5 (Errors: 2, Warnings: 0)
|
||||||
|
Returning 2 messages for level "error"
|
||||||
|
|
||||||
|
[ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:8000/api/agents/control/list:0
|
||||||
|
[ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:8000/api/agents/control/list:0
|
||||||
183
test_swarm_frontend.py
Normal file
183
test_swarm_frontend.py
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
"""
|
||||||
|
Swarm 前端完整测试
|
||||||
|
测试所有页面功能和 API
|
||||||
|
"""
|
||||||
|
from playwright.sync_api import sync_playwright
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
|
BASE_URL = "http://localhost:3000"
|
||||||
|
API_BASE = "http://localhost:8000/api"
|
||||||
|
|
||||||
|
def test_api_endpoints():
|
||||||
|
"""测试后端 API 端点"""
|
||||||
|
print("\n=== 测试后端 API ===")
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
endpoints = [
|
||||||
|
"/health",
|
||||||
|
"/agents/",
|
||||||
|
"/locks/",
|
||||||
|
"/heartbeats/",
|
||||||
|
"/workflows/list",
|
||||||
|
"/workflows/files",
|
||||||
|
"/status",
|
||||||
|
"/humans/summary",
|
||||||
|
]
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
for endpoint in endpoints:
|
||||||
|
try:
|
||||||
|
url = f"{API_BASE}{endpoint}"
|
||||||
|
with urllib.request.urlopen(url, timeout=5) as response:
|
||||||
|
data = response.read().decode('utf-8')
|
||||||
|
results[endpoint] = {"status": response.status, "data": data[:100]}
|
||||||
|
print(f" ✓ {endpoint}: {response.status}")
|
||||||
|
except Exception as e:
|
||||||
|
results[endpoint] = {"status": "error", "error": str(e)}
|
||||||
|
print(f" ✗ {endpoint}: {e}")
|
||||||
|
|
||||||
|
# 测试 agents_control 端点
|
||||||
|
control_endpoints = [
|
||||||
|
"/api/agents/control/summary",
|
||||||
|
"/api/agents/control/list",
|
||||||
|
]
|
||||||
|
|
||||||
|
print("\n=== 测试 agents_control API ===")
|
||||||
|
for endpoint in control_endpoints:
|
||||||
|
try:
|
||||||
|
url = f"http://localhost:8000{endpoint}"
|
||||||
|
with urllib.request.urlopen(url, timeout=5) as response:
|
||||||
|
data = response.read().decode('utf-8')
|
||||||
|
results[endpoint] = {"status": response.status}
|
||||||
|
print(f" ✓ {endpoint}: {response.status}")
|
||||||
|
except Exception as e:
|
||||||
|
results[endpoint] = {"status": "error", "error": str(e)}
|
||||||
|
print(f" ✗ {endpoint}: {e}")
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def test_frontend_pages():
|
||||||
|
"""测试前端页面"""
|
||||||
|
print("\n=== 测试前端页面 ===")
|
||||||
|
|
||||||
|
with sync_playwright() as p:
|
||||||
|
browser = p.chromium.launch(headless=False)
|
||||||
|
context = browser.new_context()
|
||||||
|
page = context.new_page()
|
||||||
|
|
||||||
|
# 收集控制台错误
|
||||||
|
console_errors = []
|
||||||
|
def on_console(msg):
|
||||||
|
if msg.type == "error":
|
||||||
|
console_errors.append(msg.text)
|
||||||
|
|
||||||
|
page.on("console", on_console)
|
||||||
|
|
||||||
|
issues = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. Dashboard 页面
|
||||||
|
print("\n1. 测试 Dashboard 页面...")
|
||||||
|
page.goto(f"{BASE_URL}/")
|
||||||
|
page.wait_for_load_state("networkidle", timeout=10000)
|
||||||
|
page.screenshot(path="D:/ccProg/swarm/test_screenshots/dashboard.png")
|
||||||
|
|
||||||
|
dashboard_errors = [e for e in console_errors if "dashboard" in e.lower()]
|
||||||
|
issues["dashboard"] = {
|
||||||
|
"url": page.url,
|
||||||
|
"title": page.title(),
|
||||||
|
"console_errors": dashboard_errors,
|
||||||
|
"has_content": len(page.content()) > 1000
|
||||||
|
}
|
||||||
|
print(f" 标题: {page.title()}")
|
||||||
|
print(f" 控制台错误数: {len(dashboard_errors)}")
|
||||||
|
|
||||||
|
# 2. Agents 页面
|
||||||
|
print("\n2. 测试 Agents 页面...")
|
||||||
|
page.goto(f"{BASE_URL}/#/agents")
|
||||||
|
page.wait_for_load_state("networkidle", timeout=10000)
|
||||||
|
time.sleep(2) # 等待动态内容加载
|
||||||
|
page.screenshot(path="D:/ccProg/swarm/test_screenshots/agents.png")
|
||||||
|
|
||||||
|
# 检查 agents/control API 调用
|
||||||
|
agents_errors = [e for e in console_errors if "agent" in e.lower()]
|
||||||
|
issues["agents"] = {
|
||||||
|
"url": page.url,
|
||||||
|
"console_errors": agents_errors,
|
||||||
|
"has_agent_cards": page.locator('[class*="agent"]').count() > 0
|
||||||
|
}
|
||||||
|
print(f" 控制台错误数: {len(agents_errors)}")
|
||||||
|
|
||||||
|
# 3. Meetings 页面
|
||||||
|
print("\n3. 测试 Meetings 页面...")
|
||||||
|
page.goto(f"{BASE_URL}/#/meetings")
|
||||||
|
page.wait_for_load_state("networkidle", timeout=10000)
|
||||||
|
time.sleep(2)
|
||||||
|
page.screenshot(path="D:/ccProg/swarm/test_screenshots/meetings.png")
|
||||||
|
|
||||||
|
meeting_errors = [e for e in console_errors if "meeting" in e.lower()]
|
||||||
|
issues["meetings"] = {
|
||||||
|
"url": page.url,
|
||||||
|
"console_errors": meeting_errors
|
||||||
|
}
|
||||||
|
print(f" 控制台错误数: {len(meeting_errors)}")
|
||||||
|
|
||||||
|
# 4. Resources 页面
|
||||||
|
print("\n4. 测试 Resources 页面...")
|
||||||
|
page.goto(f"{BASE_URL}/#/resources")
|
||||||
|
page.wait_for_load_state("networkidle", timeout=10000)
|
||||||
|
time.sleep(2)
|
||||||
|
page.screenshot(path="D:/ccProg/swarm/test_screenshots/resources.png")
|
||||||
|
|
||||||
|
resource_errors = [e for e in console_errors if "resource" in e.lower()]
|
||||||
|
issues["resources"] = {
|
||||||
|
"url": page.url,
|
||||||
|
"console_errors": resource_errors
|
||||||
|
}
|
||||||
|
print(f" 控制台错误数: {len(resource_errors)}")
|
||||||
|
|
||||||
|
# 5. Workflow 页面
|
||||||
|
print("\n5. 测试 Workflow 页面...")
|
||||||
|
page.goto(f"{BASE_URL}/#/workflow")
|
||||||
|
page.wait_for_load_state("networkidle", timeout=10000)
|
||||||
|
time.sleep(2)
|
||||||
|
page.screenshot(path="D:/ccProg/swarm/test_screenshots/workflow.png")
|
||||||
|
|
||||||
|
workflow_errors = [e for e in console_errors if "workflow" in e.lower()]
|
||||||
|
issues["workflow"] = {
|
||||||
|
"url": page.url,
|
||||||
|
"console_errors": workflow_errors
|
||||||
|
}
|
||||||
|
print(f" 控制台错误数: {len(workflow_errors)}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" 错误: {e}")
|
||||||
|
issues["error"] = str(e)
|
||||||
|
|
||||||
|
# 输出所有控制台错误
|
||||||
|
print("\n=== 所有控制台错误 ===")
|
||||||
|
for error in console_errors:
|
||||||
|
print(f" - {error}")
|
||||||
|
|
||||||
|
browser.close()
|
||||||
|
|
||||||
|
return issues
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import os
|
||||||
|
os.makedirs("D:/ccProg/swarm/test_screenshots", exist_ok=True)
|
||||||
|
|
||||||
|
api_results = test_api_endpoints()
|
||||||
|
frontend_issues = test_frontend_pages()
|
||||||
|
|
||||||
|
# 保存结果
|
||||||
|
with open("D:/ccProg/swarm/test_results.json", "w") as f:
|
||||||
|
json.dump({
|
||||||
|
"api": api_results,
|
||||||
|
"frontend": frontend_issues
|
||||||
|
}, f, indent=2)
|
||||||
|
|
||||||
|
print("\n=== 测试结果已保存 ===")
|
||||||
|
print(" 截图: D:/ccProg/swarm/test_screenshots/")
|
||||||
|
print(" 结果: D:/ccProg/swarm/test_results.json")
|
||||||
Reference in New Issue
Block a user