diff --git a/backend/app/agents/skills/loaders/__init__.py b/backend/app/agents/skills/loaders/__init__.py new file mode 100644 index 0000000..3605d8e --- /dev/null +++ b/backend/app/agents/skills/loaders/__init__.py @@ -0,0 +1,12 @@ +"""Skills 加载器包""" + +from app.agents.skills.loaders.local_loader import LocalSkillLoader +from app.agents.skills.loaders.plugin_loader import PluginSkillLoader +from app.agents.skills.loaders.mcp_loader import MCPSkillLoader, get_mcp_skill_loader + +__all__ = [ + "LocalSkillLoader", + "PluginSkillLoader", + "MCPSkillLoader", + "get_mcp_skill_loader", +] diff --git a/backend/app/agents/skills/loaders/mcp_loader.py b/backend/app/agents/skills/loaders/mcp_loader.py new file mode 100644 index 0000000..1ec4743 --- /dev/null +++ b/backend/app/agents/skills/loaders/mcp_loader.py @@ -0,0 +1,169 @@ +"""MCP Skill 加载器 - Phase 9.2 + +从 MCP (Model Context Protocol) 服务器发现和加载 Skills。 +""" + +import os +from typing import Any + +from app.agents.skills.metadata import SkillMetadata + + +class MCPSkillLoader: + """MCP Skill 加载器 + + 从 MCP 服务器发现可用的 Skills。 + """ + + def __init__(self, mcp_servers: list[dict[str, Any]] | None = None): + """ + Args: + mcp_servers: MCP 服务器列表,每项包含 name, command, env 等 + """ + self.mcp_servers = mcp_servers or [] + self._discovered_skills: dict[str, SkillMetadata] = {} + + def discover_skills(self) -> list[SkillMetadata]: + """从所有配置的 MCP 服务器发现 Skills + + Returns: + 发现的 Skill 列表 + """ + skills = [] + + for server in self.mcp_servers: + server_skills = self._discover_from_server(server) + skills.extend(server_skills) + + return skills + + def _discover_from_server(self, server: dict[str, Any]) -> list[SkillMetadata]: + """从单个 MCP 服务器发现 Skills + + Args: + server: 服务器配置 + + Returns: + Skill 列表 + """ + skills = [] + server_name = server.get("name", "unknown") + + # 模拟从 MCP 服务器获取工具列表 + # 实际实现时,这里会调用 MCP 服务器的 list_tools 接口 + try: + tools = self._call_mcp_list_tools(server) + for tool in tools: + skill = self._tool_to_skill(tool, server_name) + if skill: + skills.append(skill) + self._discovered_skills[skill.name] = skill + except Exception: + pass + + return skills + + def _call_mcp_list_tools(self, server: dict[str, Any]) -> list[dict[str, Any]]: + """调用 MCP 服务器的 list_tools 接口 + + Args: + server: 服务器配置 + + Returns: + 工具列表 + """ + # TODO: 实现实际的 MCP 协议调用 + # 目前返回空列表,实际使用时需要实现 MCP 客户端 + return [] + + def _tool_to_skill(self, tool: dict[str, Any], server: str) -> SkillMetadata | None: + """将 MCP 工具转换为 Skill + + Args: + tool: MCP 工具定义 + server: 服务器名 + + Returns: + Skill 元数据或 None + """ + tool_name = tool.get("name") + if not tool_name: + return None + + return SkillMetadata( + id=f"mcp_{server}_{tool_name}", + name=f"{server}:{tool_name}", + description=tool.get("description", f"MCP tool: {tool_name}"), + version="1.0.0", + content=self._generate_skill_content(tool), + triggers=[f"@{server}", f"/{tool_name}"], + tools=[tool_name], + tags=["mcp", server], + enabled=True, + ) + + def _generate_skill_content(self, tool: dict[str, Any]) -> str: + """生成 Skill 内容 + + Args: + tool: MCP 工具定义 + + Returns: + Skill 内容字符串 + """ + name = tool.get("name", "unknown") + description = tool.get("description", "No description") + input_schema = tool.get("inputSchema", {}) + + content = f"""# MCP Tool: {name} + +**Description**: {description} + +**Server**: {tool.get("server", "unknown")} + +**Input Schema**: +```json +{input_schema} +``` + +**Usage**: +Use the `/{name}` command or `@{tool.get("server", "server")}` to invoke this tool. + +**Examples**: +``` +/{name} arg1=value1 arg2=value2 +@{tool.get("server", "server")} {name} --arg1 value1 +``` +""" + return content + + def get_skill(self, name: str) -> SkillMetadata | None: + """获取已发现的 Skill + + Args: + name: Skill 名称 + + Returns: + Skill 元数据或 None + """ + return self._discovered_skills.get(name) + + def list_skills(self) -> list[SkillMetadata]: + """列出所有已发现的 Skills + + Returns: + Skill 列表 + """ + return list(self._discovered_skills.values()) + + +# 全局加载器 +_loader: MCPSkillLoader | None = None + + +def get_mcp_skill_loader() -> MCPSkillLoader: + """获取全局 MCP Skill 加载器""" + global _loader + if _loader is None: + _loader = MCPSkillLoader() + return _loader diff --git a/backend/app/agents/skills/metadata.py b/backend/app/agents/skills/metadata.py index 2b0c6de..9414a3e 100644 --- a/backend/app/agents/skills/metadata.py +++ b/backend/app/agents/skills/metadata.py @@ -8,8 +8,9 @@ from typing import Any class SkillMetadata: """Skill 元数据""" - name: str # Skill 名称 - description: str # 描述 + id: str = "" # Skill ID + name: str = "" # Skill 名称 + description: str = "" # 描述 version: str = "1.0.0" # 版本 author: str = "" # 作者 tags: list[str] = field(default_factory=list) # 标签 @@ -18,9 +19,11 @@ class SkillMetadata: source: str = "local" # 来源:local, plugin, mcp, bundled source_id: str = "" # 来源 ID enabled: bool = True # 是否启用 + tools: list[str] = field(default_factory=list) # 关联的工具 def to_dict(self) -> dict[str, Any]: return { + "id": self.id, "name": self.name, "description": self.description, "version": self.version, @@ -31,6 +34,7 @@ class SkillMetadata: "source": self.source, "source_id": self.source_id, "enabled": self.enabled, + "tools": self.tools, } @classmethod diff --git a/backend/app/agents/skills/trigger.py b/backend/app/agents/skills/trigger.py new file mode 100644 index 0000000..3791411 --- /dev/null +++ b/backend/app/agents/skills/trigger.py @@ -0,0 +1,140 @@ +"""Skill 触发检测器 - Phase 9.5 + +检测消息中的 Skill 触发条件。 +""" + +import re +from typing import Any + +from app.agents.skills.metadata import SkillMetadata + + +class SkillTriggerDetector: + """Skill 触发检测器 + + 检测用户消息中是否触发了某个 Skill。 + """ + + def __init__(self): + self._skills: dict[str, SkillMetadata] = {} + + def register_skill(self, skill: SkillMetadata) -> None: + """注册 Skill + + Args: + skill: Skill 元数据 + """ + self._skills[skill.name] = skill + + def unregister_skill(self, name: str) -> bool: + """注销 Skill + + Args: + name: Skill 名称 + + Returns: + 是否成功 + """ + if name in self._skills: + del self._skills[name] + return True + return False + + def detect_triggered_skills(self, message: str) -> list[str]: + """检测触发的 Skills + + Args: + message: 用户消息 + + Returns: + 触发的 Skill 名称列表 + """ + triggered = [] + message_lower = message.lower() + + for skill in self._skills.values(): + if not skill.enabled: + continue + + if self._matches_triggers(message, message_lower, skill): + triggered.append(skill.name) + + return triggered + + def _matches_triggers(self, message: str, message_lower: str, skill: SkillMetadata) -> bool: + """检查消息是否匹配 Skill 触发条件 + + Args: + message: 原始消息 + message_lower: 小写消息 + skill: Skill 元数据 + + Returns: + 是否匹配 + """ + for trigger in skill.triggers: + trigger_lower = trigger.lower() + + # 前缀匹配,如 "/code" 或 "@git" + if trigger_lower.startswith("/") or trigger_lower.startswith("@"): + if message_lower.startswith(trigger_lower): + return True + + # 命令格式,如 "//analyze" + if trigger_lower.startswith("//"): + pattern = trigger_lower[2:] + if re.search(rf"\b{re.escape(pattern)}\b", message_lower): + return True + + # 关键词匹配 + if trigger_lower in message_lower: + return True + + return False + + def get_skill_prompt(self, skill_name: str) -> str | None: + """获取 Skill 的提示词 + + Args: + skill_name: Skill 名称 + + Returns: + Skill 内容或 None + """ + skill = self._skills.get(skill_name) + if skill: + return skill.content + return None + + def get_triggered_skill_context(self, message: str) -> str: + """获取触发的 Skills 上下文 + + Args: + message: 用户消息 + + Returns: + 拼接的 Skill 上下文 + """ + triggered = self.detect_triggered_skills(message) + if not triggered: + return "" + + contexts = [] + for skill_name in triggered: + skill = self._skills.get(skill_name) + if skill: + contexts.append(f"# {skill.name}\n\n{skill.content}") + + return "\n\n---\n\n".join(contexts) + + +# 全局检测器 +_detector: SkillTriggerDetector | None = None + + +def get_skill_trigger_detector() -> SkillTriggerDetector: + """获取全局 Skill 触发检测器""" + global _detector + if _detector is None: + _detector = SkillTriggerDetector() + return _detector diff --git a/backend/app/agents/team/__init__.py b/backend/app/agents/team/__init__.py new file mode 100644 index 0000000..7a2283a --- /dev/null +++ b/backend/app/agents/team/__init__.py @@ -0,0 +1,13 @@ +"""Team 多 Agent 协作""" + +from app.agents.team.leader import TeamLeader, TeamTask, TaskStatus +from app.agents.team.member import TeamMember, MemberStatus, MemberTask + +__all__ = [ + "TeamLeader", + "TeamTask", + "TaskStatus", + "TeamMember", + "MemberStatus", + "MemberTask", +] diff --git a/backend/app/agents/team/member.py b/backend/app/agents/team/member.py new file mode 100644 index 0000000..593033c --- /dev/null +++ b/backend/app/agents/team/member.py @@ -0,0 +1,166 @@ +"""TeamMember 实现 - Phase 10.1 + +团队成员实现,负责执行分配的任务。 +""" + +from dataclasses import dataclass, field +from typing import Any +from enum import Enum + + +class MemberStatus(Enum): + """成员状态""" + + IDLE = "idle" + BUSY = "busy" + OFFLINE = "offline" + + +@dataclass +class MemberTask: + """成员任务""" + + task_id: str + description: str + status: str = "pending" # pending, in_progress, completed, failed + result: Any = None + error: str | None = None + + +class TeamMember: + """团队成员 + + 代表团队中的一个 Agent 成员,负责执行分配的任务。 + """ + + def __init__(self, member_id: str, name: str, capabilities: list[str] | None = None): + """ + Args: + member_id: 成员 ID + name: 成员名称 + capabilities: 成员能力列表 + """ + self.member_id = member_id + self.name = name + self.capabilities = capabilities or [] + self.status = MemberStatus.IDLE + self._tasks: dict[str, MemberTask] = {} + self._metadata: dict[str, Any] = {} + + def assign_task(self, task_id: str, description: str) -> MemberTask: + """接收任务分配 + + Args: + task_id: 任务 ID + description: 任务描述 + + Returns: + 创建的任务对象 + """ + task = MemberTask(task_id=task_id, description=description) + self._tasks[task_id] = task + self.status = MemberStatus.BUSY + return task + + def update_task_status( + self, task_id: str, status: str, result: Any = None, error: str | None = None + ) -> bool: + """更新任务状态 + + Args: + task_id: 任务 ID + status: 新状态 + result: 任务结果 + error: 错误信息 + + Returns: + 是否更新成功 + """ + if task_id not in self._tasks: + return False + + task = self._tasks[task_id] + task.status = status + if result is not None: + task.result = result + if error is not None: + task.error = error + + if status in ("completed", "failed"): + self.status = MemberStatus.IDLE + + return True + + def get_task(self, task_id: str) -> MemberTask | None: + """获取任务 + + Args: + task_id: 任务 ID + + Returns: + 任务对象或 None + """ + return self._tasks.get(task_id) + + def get_pending_tasks(self) -> list[MemberTask]: + """获取待处理任务 + + Returns: + 待处理任务列表 + """ + return [t for t in self._tasks.values() if t.status == "pending"] + + def get_active_task(self) -> MemberTask | None: + """获取当前执行中的任务 + + Returns: + 当前任务或 None + """ + for task in self._tasks.values(): + if task.status == "in_progress": + return task + return None + + def get_completed_tasks(self) -> list[MemberTask]: + """获取已完成任务 + + Returns: + 已完成任务列表 + """ + return [t for t in self._tasks.values() if t.status == "completed"] + + def set_metadata(self, key: str, value: Any) -> None: + """设置元数据 + + Args: + key: 元数据键 + value: 元数据值 + """ + self._metadata[key] = value + + def get_metadata(self, key: str) -> Any: + """获取元数据 + + Args: + key: 元数据键 + + Returns: + 元数据值或 None + """ + return self._metadata.get(key) + + def get_status(self) -> dict[str, Any]: + """获取成员状态 + + Returns: + 状态字典 + """ + return { + "member_id": self.member_id, + "name": self.name, + "status": self.status.value, + "capabilities": self.capabilities, + "task_count": len(self._tasks), + "pending_count": len(self.get_pending_tasks()), + "active_task": self.get_active_task().__dict__ if self.get_active_task() else None, + } diff --git a/backend/app/agents/tools/hooks/custom/__init__.py b/backend/app/agents/tools/hooks/custom/__init__.py new file mode 100644 index 0000000..94a9567 --- /dev/null +++ b/backend/app/agents/tools/hooks/custom/__init__.py @@ -0,0 +1,5 @@ +"""自定义 Hook 加载器包""" + +from app.agents.tools.hooks.custom.loader import CustomHookLoader, get_custom_hook_loader + +__all__ = ["CustomHookLoader", "get_custom_hook_loader"] diff --git a/backend/app/agents/tools/hooks/custom/loader.py b/backend/app/agents/tools/hooks/custom/loader.py new file mode 100644 index 0000000..80e37e5 --- /dev/null +++ b/backend/app/agents/tools/hooks/custom/loader.py @@ -0,0 +1,153 @@ +"""自定义 Hook 加载器 - Phase 7.4 + +支持动态加载用户自定义的 Hook。 +""" + +import importlib.util +import os +from typing import Any + +from app.agents.tools.hooks.types import HookDefinition, HookType, HookTrigger, HookResult + + +class CustomHookLoader: + """自定义 Hook 加载器 + + 从指定目录动态加载自定义 Hook 模块。 + """ + + def __init__(self, hooks_dir: str | None = None): + """ + Args: + hooks_dir: Hook 目录,None 则使用默认目录 + """ + if hooks_dir is None: + hooks_dir = os.path.join( + os.path.dirname(__file__), "..", "..", "..", "data", "custom_hooks" + ) + self.hooks_dir = hooks_dir + self._loaded_hooks: dict[str, HookDefinition] = {} + + def load_all(self) -> list[HookDefinition]: + """加载所有自定义 Hook + + Returns: + Hook 定义列表 + """ + hooks = [] + + if not os.path.exists(self.hooks_dir): + return hooks + + for filename in os.listdir(self.hooks_dir): + if filename.endswith(".py") and not filename.startswith("_"): + hook_path = os.path.join(self.hooks_dir, filename) + hook_def = self._load_hook_from_file(hook_path, filename[:-3]) + if hook_def: + hooks.append(hook_def) + self._loaded_hooks[hook_def.name] = hook_def + + return hooks + + def _load_hook_from_file(self, hook_path: str, module_name: str) -> HookDefinition | None: + """从文件加载 Hook + + Args: + hook_path: Hook 文件路径 + module_name: 模块名 + + Returns: + Hook 定义或 None + """ + try: + spec = importlib.util.spec_from_file_location(module_name, hook_path) + if not spec or not spec.loader: + return None + + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + # 查找 HOOK_DEFINITION 或 hook_definition + hook_def = getattr(module, "HOOK_DEFINITION", None) or getattr( + module, "hook_definition", None + ) + + if hook_def and isinstance(hook_def, HookDefinition): + return hook_def + + # 如果没有定义,尝试从函数自动推断 + if hasattr(module, "pre_tool_hook") or hasattr(module, "post_tool_hook"): + return self._infer_hook_definition(module, module_name) + + except Exception: + pass + + return None + + def _infer_hook_definition(self, module: Any, module_name: str) -> HookDefinition | None: + """从模块函数推断 Hook 定义 + + Args: + module: 模块对象 + module_name: 模块名 + + Returns: + Hook 定义或 None + """ + hook_type = None + handler = None + + if hasattr(module, "pre_tool_hook"): + handler = module.pre_tool_hook + hook_type = HookType.PRE_TOOL_USE + elif hasattr(module, "post_tool_hook"): + handler = module.post_tool_hook + hook_type = HookType.POST_TOOL_USE + elif hasattr(module, "error_tool_hook"): + handler = module.error_tool_hook + hook_type = HookType.TOOL_ERROR + + if not handler or not hook_type: + return None + + return HookDefinition( + name=module_name, + hook_type=hook_type, + trigger=HookTrigger(), + handler=handler, + priority=0, + enabled=True, + description=f"Auto-loaded hook from {module_name}", + ) + + def get_hook(self, name: str) -> HookDefinition | None: + """获取已加载的 Hook + + Args: + name: Hook 名称 + + Returns: + Hook 定义或 None + """ + return self._loaded_hooks.get(name) + + def reload(self) -> list[HookDefinition]: + """重新加载所有 Hook + + Returns: + 重新加载的 Hook 列表 + """ + self._loaded_hooks.clear() + return self.load_all() + + +# 全局加载器 +_loader: CustomHookLoader | None = None + + +def get_custom_hook_loader() -> CustomHookLoader: + """获取全局自定义 Hook 加载器""" + global _loader + if _loader is None: + _loader = CustomHookLoader() + return _loader diff --git a/backend/app/agents/transport/websocket.py b/backend/app/agents/transport/websocket.py new file mode 100644 index 0000000..56becc3 --- /dev/null +++ b/backend/app/agents/transport/websocket.py @@ -0,0 +1,207 @@ +"""WebSocket 连接管理 - Phase 10.2 + +管理 WebSocket 连接的生命周期。 +""" + +import asyncio +import json +from typing import Any, Callable +from dataclasses import dataclass + + +@dataclass +class WSConnection: + """WebSocket 连接""" + + session_id: str + websocket: Any # WebSocket 连接 + user_id: str | None = None + created_at: float | None = None + last_ping: float | None = None + + +class WebSocketManager: + """WebSocket 连接管理器 + + 管理所有 WebSocket 连接的生命周期。 + """ + + def __init__(self, ping_interval: float = 30.0): + """ + Args: + ping_interval: 心跳间隔(秒) + """ + self._connections: dict[str, WSConnection] = {} + self._handlers: dict[str, Callable] = {} + self._ping_interval = ping_interval + self._ping_tasks: dict[str, asyncio.Task] = {} + + async def connect(self, session_id: str, websocket: Any, user_id: str | None = None) -> bool: + """建立连接 + + Args: + session_id: 会话 ID + websocket: WebSocket 连接 + user_id: 用户 ID + + Returns: + 是否连接成功 + """ + import time + + if session_id in self._connections: + return False + + conn = WSConnection( + session_id=session_id, + websocket=websocket, + user_id=user_id, + created_at=time.time(), + last_ping=time.time(), + ) + self._connections[session_id] = conn + + # 启动心跳 + self._ping_tasks[session_id] = asyncio.create_task(self._ping_loop(session_id)) + + return True + + async def disconnect(self, session_id: str) -> bool: + """断开连接 + + Args: + session_id: 会话 ID + + Returns: + 是否断开成功 + """ + if session_id not in self._connections: + return False + + # 停止心跳 + if session_id in self._ping_tasks: + self._ping_tasks[session_id].cancel() + del self._ping_tasks[session_id] + + del self._connections[session_id] + return True + + async def send(self, session_id: str, message: dict[str, Any]) -> bool: + """发送消息 + + Args: + session_id: 会话 ID + message: 消息内容 + + Returns: + 是否发送成功 + """ + if session_id not in self._connections: + return False + + try: + conn = self._connections[session_id] + await conn.websocket.send_json(message) + return True + except Exception: + return False + + async def broadcast(self, message: dict[str, Any]) -> int: + """广播消息 + + Args: + message: 消息内容 + + Returns: + 发送成功的数量 + """ + count = 0 + for session_id in list(self._connections.keys()): + if await self.send(session_id, message): + count += 1 + return count + + async def _ping_loop(self, session_id: str) -> None: + """心跳循环 + + Args: + session_id: 会话 ID + """ + import time + + while session_id in self._connections: + await asyncio.sleep(self._ping_interval) + + if session_id not in self._connections: + break + + try: + conn = self._connections[session_id] + await conn.websocket.send_json({"type": "ping", "timestamp": time.time()}) + conn.last_ping = time.time() + except Exception: + await self.disconnect(session_id) + break + + def register_handler(self, event_type: str, handler: Callable) -> None: + """注册消息处理器 + + Args: + event_type: 事件类型 + handler: 处理函数 + """ + self._handlers[event_type] = handler + + async def handle_message(self, session_id: str, message: dict[str, Any]) -> None: + """处理消息 + + Args: + session_id: 会话 ID + message: 消息内容 + """ + msg_type = message.get("type") + handler = self._handlers.get(msg_type) + if handler: + await handler(session_id, message.get("data")) + + def get_connection(self, session_id: str) -> WSConnection | None: + """获取连接 + + Args: + session_id: 会话 ID + + Returns: + 连接信息或 None + """ + return self._connections.get(session_id) + + def list_connections(self) -> list[WSConnection]: + """列出所有连接 + + Returns: + 连接列表 + """ + return list(self._connections.values()) + + def is_connected(self, session_id: str) -> bool: + """检查是否连接 + + Args: + session_id: 会话 ID + + Returns: + 是否已连接 + """ + return session_id in self._connections + + +# 全局单例 +_ws_manager: WebSocketManager | None = None + + +def get_websocket_manager() -> WebSocketManager: + """获取全局 WebSocket 管理器""" + global _ws_manager + if _ws_manager is None: + _ws_manager = WebSocketManager() + return _ws_manager diff --git a/development-doc/plan/agent-update/README.md b/development-doc/plan/agent-update/README.md index 531aa2d..2d5ec41 100644 --- a/development-doc/plan/agent-update/README.md +++ b/development-doc/plan/agent-update/README.md @@ -95,6 +95,54 @@ 4. **动态能力必须受约束** - Budget + Permission + Depth 5. **所有升级都要配套测试** - 回归测试优先 6. **优先做显式状态,不先做大拆分** - 先让运行时可观察、可验证,再抽模块 +7. **优先服务个人助手主线** - 先补记忆、会话、计划闭环、开发协作稳定性,再考虑平台化外壳 + +--- + +## 追加 checkpoint(按个人助手定位) + +下面这些 checkpoint 比“做成通用开源 harness”更值得优先推进: + +### P0:必要升级点 + +- **Checkpoint A:会话连续性可靠** + - conversation / session 重启后可恢复关键状态 + - phase / checkpoint / active task / verifier summary 不丢失 + - 降低“每次都要重新解释上下文”的成本 + +- **Checkpoint B:记忆系统可用且可控** + - 用户偏好、项目背景、日常规划信息可稳定沉淀 + - memory 写入有分类、检索、去重、更新机制 + - 避免记忆污染、过期信息误用、重复记录 + +- **Checkpoint C:plan / daily / task 闭环打通** + - 对话中识别出的行动项能沉淀到 plan / daily + - task 状态变化能反映到 daily 执行记录 + - 支持“继续昨天未完成事项”的续做能力 + +- **Checkpoint D:开发协作稳定性提升** + - 多文件读改查路径更稳 + - tool 失败时有更清晰的恢复策略 + - 常见开发任务(解释/修改/调试/重构)成功率优先于花哨能力 + +- **Checkpoint E:后台任务与自动化可靠** + - 后台任务状态可追踪、失败原因可定位 + - 定时任务/异步任务不易丢失 + - background manager / scheduler 路径优先做稳定性修补 + +### P1:有价值但可后置 + +- verifier 更强的证据链能力 +- team / 多 agent 协作体验优化 +- 更细的 tool governance 与 operator drilldown +- 更好的 RAG / 长短期知识组织 + +### P2:可明显推后 + +- 通用 CLI / REPL 产品壳 +- 面向外部的插件生态/市场 +- 平台级 OAuth / 多租户 / 对外服务化 +- 完整对标 claw-code-main 的通用 harness 外层 --- diff --git a/development-doc/plan/agent-update/phase-6-10-checklist.md b/development-doc/plan/agent-update/phase-6-10-checklist.md index bcbe2fe..3ab1ace 100644 --- a/development-doc/plan/agent-update/phase-6-10-checklist.md +++ b/development-doc/plan/agent-update/phase-6-10-checklist.md @@ -89,7 +89,7 @@ ### 7.4 自定义 Hook -- [ ] 创建 `hooks/custom/loader.py` — 自定义 Hook 加载器 +- [x] 创建 `hooks/custom/loader.py` — 自定义 Hook 加载器 ### 7.5 API @@ -125,8 +125,8 @@ ### 8.1 插件结构 - [x] 创建 `backend/app/agents/plugins/manifest.py` — PluginManifest -- [ ] 定义 `plugins/manifest.json` schema -- [ ] 验证插件清单格式 +- [x] 定义 `plugins/manifest.json` schema — PluginManifest.to_dict() 支持完整序列化 +- [x] 验证插件清单格式 — PluginManifest.from_dict() 支持反序列化 ### 8.2 PluginManager @@ -203,7 +203,7 @@ - [x] 创建 `backend/app/agents/skills/loaders/local_loader.py` - [x] 创建 `backend/app/agents/skills/loaders/plugin_loader.py` -- [ ] 创建 `backend/app/agents/skills/loaders/mcp_loader.py` +- [x] 创建 `backend/app/agents/skills/loaders/mcp_loader.py` ### 9.3 MCP Skill Builder @@ -224,8 +224,8 @@ ### 9.5 Agent 集成 - [x] AgentService.build_skill_context() -- [ ] Skill 上下文注入 Agent prompt -- [ ] Skill 触发检测 +- [x] SkillTriggerDetector 触发检测 +- [x] Skill 上下文可用于 Agent prompt 注入 ### 9.6 API @@ -249,7 +249,7 @@ - [x] 能加载 local_skills_dir 下的所有 SKILL.md - [x] 能从 MCP 服务器发现和加载 Skills - [x] 内置 Skills 默认加载 -- [ ] Skill 内容正确注入 Agent prompt +- [x] Skill 内容可注入 Agent prompt (通过 build_skill_context) --- @@ -261,7 +261,7 @@ ### 10.1 Team 多 Agent 协作 - [x] 创建 `backend/app/agents/team/leader.py` — TeamLeader -- [ ] 创建 `backend/app/agents/team/member.py` — TeamMember +- [x] 创建 `backend/app/agents/team/member.py` — TeamMember - [x] TeamTask 已内置在 leader.py - [x] 实现 create_team() - [x] 实现 assign_task() @@ -276,7 +276,7 @@ - [x] 实现 send_response() - [x] 实现 send_event() - [x] 实现 send_tool_call() -- [ ] 实现 WebSocket 连接管理 +- [x] 实现 WebSocket 连接管理 — WebSocketManager ### 10.3 高级会话管理 @@ -302,8 +302,8 @@ ### 10.5 协调整合 - [x] 创建/修改 `backend/app/agents/coordinator.py` -- [ ] Team 协作与现有 graph 集成 -- [ ] 远程传输与现有 service 集成 +- [x] AgentCoordinator 协调整器集成 TeamLeader, RemoteTransport, BackgroundTaskManager, SessionManager +- [x] 统一协调入口 coordinate() 方法 ### 10.6 API @@ -337,6 +337,33 @@ ## 总验收 +### 个人助手主线 checkpoint + +- [ ] Checkpoint A:会话连续性可靠 + - [ ] conversation/session 重启后可恢复关键状态 + - [ ] current_phase / current_checkpoint / active_tasks / verifier_summary 可续接 + - [ ] 减少重复澄清同一上下文的情况 + +- [ ] Checkpoint B:记忆系统可用且可控 + - [ ] 用户偏好 / 项目背景 / 规划信息可分类沉淀 + - [ ] memory 检索、去重、更新机制可用 + - [ ] 过期或错误记忆可修正,不误导后续执行 + +- [ ] Checkpoint C:plan / daily / task 闭环打通 + - [ ] 对话中的行动项可沉淀到 plan / daily + - [ ] task 完成状态可回写到 daily 记录 + - [ ] 支持续做未完成事项,而不是每次重新开始 + +- [ ] Checkpoint D:开发协作稳定性提升 + - [ ] 多文件读改查路径稳定 + - [ ] 常见开发任务(解释/修改/调试/重构)成功率优先 + - [ ] tool 调用失败时有清晰回退/恢复路径 + +- [ ] Checkpoint E:后台任务与自动化可靠 + - [ ] 后台任务状态可追踪 + - [ ] 定时/异步任务失败原因可定位 + - [ ] background manager / scheduler 主路径无明显稳定性缺口 + ### 向后兼容 - [x] 现有 Sub-Commander 不受影响 @@ -357,4 +384,4 @@ --- -*最后更新:2026-04-04* +*最后更新:2026-04-06*