- 新增 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>
262 lines
7.1 KiB
Python
262 lines
7.1 KiB
Python
"""
|
||
Agent 注册服务 - 管理 Agent 的注册信息和状态
|
||
每个 Agent 有独立的目录存储其配置和状态
|
||
"""
|
||
|
||
import asyncio
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
from typing import Dict, List, Optional
|
||
from dataclasses import dataclass, asdict
|
||
from enum import Enum
|
||
|
||
from .storage import get_storage
|
||
|
||
|
||
class AgentRole(str, Enum):
|
||
"""Agent 角色枚举"""
|
||
ARCHITECT = "architect"
|
||
PRODUCT_MANAGER = "pm"
|
||
DEVELOPER = "developer"
|
||
QA = "qa"
|
||
REVIEWER = "reviewer"
|
||
HUMAN = "human"
|
||
|
||
|
||
@dataclass
|
||
class AgentInfo:
|
||
"""Agent 基本信息"""
|
||
agent_id: str # 唯一标识符,如 claude-001
|
||
name: str # 显示名称,如 Claude Code
|
||
role: str # 角色:architect, pm, developer, qa, reviewer, human
|
||
model: str # 模型:claude-opus-4.6, gpt-4o, human 等
|
||
description: str = "" # 描述
|
||
created_at: str = "" # 注册时间
|
||
status: str = "idle" # 当前状态
|
||
|
||
def __post_init__(self):
|
||
if not self.created_at:
|
||
self.created_at = datetime.now().isoformat()
|
||
|
||
|
||
@dataclass
|
||
class AgentState:
|
||
"""Agent 运行时状态"""
|
||
agent_id: str
|
||
current_task: str = ""
|
||
progress: int = 0
|
||
working_files: List[str] = None
|
||
last_update: str = ""
|
||
|
||
def __post_init__(self):
|
||
if self.working_files is None:
|
||
self.working_files = []
|
||
if not self.last_update:
|
||
self.last_update = datetime.now().isoformat()
|
||
|
||
|
||
class AgentRegistry:
|
||
"""
|
||
Agent 注册服务
|
||
|
||
管理所有 Agent 的注册信息和运行时状态
|
||
"""
|
||
|
||
def __init__(self):
|
||
self._storage = get_storage()
|
||
self._lock = asyncio.Lock()
|
||
|
||
def _get_agent_dir(self, agent_id: str) -> str:
|
||
"""获取 Agent 目录路径"""
|
||
return f"agents/{agent_id}"
|
||
|
||
def _get_agent_info_file(self, agent_id: str) -> str:
|
||
"""获取 Agent 信息文件路径"""
|
||
return f"{self._get_agent_dir(agent_id)}/info.json"
|
||
|
||
def _get_agent_state_file(self, agent_id: str) -> str:
|
||
"""获取 Agent 状态文件路径"""
|
||
return f"{self._get_agent_dir(agent_id)}/state.json"
|
||
|
||
async def register_agent(
|
||
self,
|
||
agent_id: str,
|
||
name: str,
|
||
role: str,
|
||
model: str,
|
||
description: str = ""
|
||
) -> AgentInfo:
|
||
"""
|
||
注册新 Agent
|
||
|
||
Args:
|
||
agent_id: Agent ID
|
||
name: 显示名称
|
||
role: 角色
|
||
model: 模型
|
||
description: 描述
|
||
|
||
Returns:
|
||
注册的 Agent 信息
|
||
"""
|
||
async with self._lock:
|
||
agent_info = AgentInfo(
|
||
agent_id=agent_id,
|
||
name=name,
|
||
role=role,
|
||
model=model,
|
||
description=description
|
||
)
|
||
|
||
# 确保 Agent 目录存在
|
||
await self._storage.ensure_dir(self._get_agent_dir(agent_id))
|
||
|
||
# 保存 Agent 信息
|
||
await self._storage.write_json(
|
||
self._get_agent_info_file(agent_id),
|
||
asdict(agent_info)
|
||
)
|
||
|
||
# 初始化状态
|
||
await self._storage.write_json(
|
||
self._get_agent_state_file(agent_id),
|
||
asdict(AgentState(agent_id=agent_id))
|
||
)
|
||
|
||
return agent_info
|
||
|
||
async def get_agent(self, agent_id: str) -> Optional[AgentInfo]:
|
||
"""
|
||
获取 Agent 信息
|
||
|
||
Args:
|
||
agent_id: Agent ID
|
||
|
||
Returns:
|
||
Agent 信息,不存在返回 None
|
||
"""
|
||
data = await self._storage.read_json(self._get_agent_info_file(agent_id))
|
||
if data:
|
||
return AgentInfo(**data)
|
||
return None
|
||
|
||
async def list_agents(self) -> List[AgentInfo]:
|
||
"""
|
||
列出所有已注册的 Agent
|
||
|
||
Returns:
|
||
Agent 信息列表
|
||
"""
|
||
agents = []
|
||
agents_dir = Path(self._storage.base_path) / "agents"
|
||
|
||
if not agents_dir.exists():
|
||
return []
|
||
|
||
for agent_dir in agents_dir.iterdir():
|
||
if agent_dir.is_dir():
|
||
info_file = agent_dir / "info.json"
|
||
if info_file.exists():
|
||
data = await self._storage.read_json(f"agents/{agent_dir.name}/info.json")
|
||
if data:
|
||
agents.append(AgentInfo(**data))
|
||
|
||
return agents
|
||
|
||
async def update_state(
|
||
self,
|
||
agent_id: str,
|
||
task: str = "",
|
||
progress: int = 0,
|
||
working_files: List[str] = None
|
||
) -> None:
|
||
"""
|
||
更新 Agent 状态
|
||
|
||
Args:
|
||
agent_id: Agent ID
|
||
task: 当前任务
|
||
progress: 进度 0-100
|
||
working_files: 正在处理的文件列表
|
||
"""
|
||
async with self._lock:
|
||
state_file = self._get_agent_state_file(agent_id)
|
||
|
||
# 读取现有状态
|
||
existing = await self._storage.read_json(state_file)
|
||
|
||
# 更新状态
|
||
state = AgentState(
|
||
agent_id=agent_id,
|
||
current_task=task or existing.get("current_task", ""),
|
||
progress=progress or existing.get("progress", 0),
|
||
working_files=working_files or existing.get("working_files", []),
|
||
last_update=datetime.now().isoformat()
|
||
)
|
||
|
||
await self._storage.write_json(state_file, asdict(state))
|
||
|
||
async def get_state(self, agent_id: str) -> Optional[AgentState]:
|
||
"""
|
||
获取 Agent 状态
|
||
|
||
Args:
|
||
agent_id: Agent ID
|
||
|
||
Returns:
|
||
Agent 状态,不存在返回 None
|
||
"""
|
||
data = await self._storage.read_json(self._get_agent_state_file(agent_id))
|
||
if data:
|
||
return AgentState(**data)
|
||
return None
|
||
|
||
async def unregister_agent(self, agent_id: str) -> bool:
|
||
"""
|
||
注销 Agent
|
||
|
||
Args:
|
||
agent_id: Agent ID
|
||
|
||
Returns:
|
||
是否成功注销
|
||
"""
|
||
async with self._lock:
|
||
agent_info = await self.get_agent(agent_id)
|
||
if not agent_info:
|
||
return False
|
||
|
||
# 删除 Agent 目录
|
||
agent_dir = self._get_agent_dir(agent_id)
|
||
# 实际实现中可能需要递归删除
|
||
# 这里简化处理,只删除 info.json 和 state.json
|
||
await self._storage.delete(f"{agent_dir}/info.json")
|
||
await self._storage.delete(f"{agent_dir}/state.json")
|
||
|
||
return True
|
||
|
||
async def get_agents_by_role(self, role: str) -> List[AgentInfo]:
|
||
"""
|
||
获取指定角色的所有 Agent
|
||
|
||
Args:
|
||
role: 角色
|
||
|
||
Returns:
|
||
符合条件的 Agent 列表
|
||
"""
|
||
all_agents = await self.list_agents()
|
||
return [agent for agent in all_agents if agent.role == role]
|
||
|
||
|
||
# 全局单例
|
||
_registry_instance: Optional[AgentRegistry] = None
|
||
|
||
|
||
def get_agent_registry() -> AgentRegistry:
|
||
"""获取 Agent 注册服务单例"""
|
||
global _registry_instance
|
||
if _registry_instance is None:
|
||
_registry_instance = AgentRegistry()
|
||
return _registry_instance
|