diff --git a/backend/app/agents/background/manager.py b/backend/app/agents/background/manager.py new file mode 100644 index 0000000..2adeb79 --- /dev/null +++ b/backend/app/agents/background/manager.py @@ -0,0 +1,119 @@ +"""后台任务系统 - Phase 10.4""" + +import asyncio +import uuid +from dataclasses import dataclass +from datetime import datetime +from typing import Any +from enum import Enum + + +class BackgroundTaskStatus(Enum): + PENDING = "pending" + RUNNING = "running" + COMPLETED = "completed" + FAILED = "failed" + CANCELLED = "cancelled" + + +@dataclass +class BackgroundTask: + """后台任务""" + + id: str + name: str + status: BackgroundTaskStatus + created_at: datetime + started_at: datetime | None = None + completed_at: datetime | None = None + result: Any = None + error: str | None = None + + +class BackgroundTaskManager: + """后台任务管理器""" + + def __init__(self): + self._tasks: dict[str, BackgroundTask] = {} + self._.coroutines: dict[str, asyncio.Task] = {} + + def submit_task(self, name: str, coro: Any, *args, **kwargs) -> str: + """提交后台任务 + + Args: + name: 任务名称 + coro: 协程函数 + *args: 位置参数 + **kwargs: 关键字参数 + + Returns: + 任务 ID + """ + task_id = str(uuid.uuid4())[:8] + + # 创建任务记录 + self._tasks[task_id] = BackgroundTask( + id=task_id, + name=name, + status=BackgroundTaskStatus.PENDING, + created_at=datetime.now(), + ) + + # 创建 asyncio task + async def run_task(): + self._tasks[task_id].status = BackgroundTaskStatus.RUNNING + self._tasks[task_id].started_at = datetime.now() + try: + result = await coro(*args, **kwargs) + self._tasks[task_id].status = BackgroundTaskStatus.COMPLETED + self._tasks[task_id].result = result + except Exception as e: + self._tasks[task_id].status = BackgroundTaskStatus.FAILED + self._tasks[task_id].error = str(e) + finally: + self._tasks[task_id].completed_at = datetime.now() + if task_id in self._coroutines: + del self._coroutines[task_id] + + self._coroutines[task_id] = asyncio.create_task(run_task()) + return task_id + + def cancel_task(self, task_id: str) -> bool: + """取消任务 + + Args: + task_id: 任务 ID + + Returns: + 是否成功取消 + """ + if task_id not in self._tasks: + return False + + if task_id in self._coroutines: + self._coroutines[task_id].cancel() + del self._coroutines[task_id] + + self._tasks[task_id].status = BackgroundTaskStatus.CANCELLED + self._tasks[task_id].completed_at = datetime.now() + return True + + def get_task_status(self, task_id: str) -> BackgroundTask | None: + """获取任务状态""" + return self._tasks.get(task_id) + + def list_tasks(self) -> list[BackgroundTask]: + """列出所有任务""" + return list(self._tasks.values()) + + +# 全局单例 +_manager: BackgroundTaskManager | None = None + + +def get_background_task_manager() -> BackgroundTaskManager: + """获取全局后台任务管理器""" + global _manager + if _manager is None: + _manager = BackgroundTaskManager() + return _manager diff --git a/backend/app/agents/orchestration/__init__.py b/backend/app/agents/orchestration/__init__.py new file mode 100644 index 0000000..02ff9c5 --- /dev/null +++ b/backend/app/agents/orchestration/__init__.py @@ -0,0 +1,20 @@ +"""高级编排系统 - Phase 10""" + +from app.agents.team.leader import TeamLeader, TeamTask, TaskStatus +from app.agents.transport.remote import RemoteTransport, StructuredMessage +from app.agents.background.manager import ( + BackgroundTaskManager, + BackgroundTask, + get_background_task_manager, +) + +__all__ = [ + "TeamLeader", + "TeamTask", + "TaskStatus", + "RemoteTransport", + "StructuredMessage", + "BackgroundTaskManager", + "BackgroundTask", + "get_background_task_manager", +] diff --git a/backend/app/agents/plugins/__init__.py b/backend/app/agents/plugins/__init__.py new file mode 100644 index 0000000..cb5e5d0 --- /dev/null +++ b/backend/app/agents/plugins/__init__.py @@ -0,0 +1,12 @@ +"""插件系统 - Phase 8""" + +from app.agents.plugins.manager import PluginManager, get_plugin_manager +from app.agents.plugins.manifest import PluginManifest +from app.agents.plugins.sandbox import PluginSandbox + +__all__ = [ + "PluginManager", + "PluginManifest", + "PluginSandbox", + "get_plugin_manager", +] diff --git a/backend/app/agents/plugins/manager.py b/backend/app/agents/plugins/manager.py new file mode 100644 index 0000000..f830ca6 --- /dev/null +++ b/backend/app/agents/plugins/manager.py @@ -0,0 +1,207 @@ +"""插件管理器 - Phase 8.2""" + +import importlib.util +import os +import sys +from typing import Any + +from app.agents.plugins.manifest import PluginManifest +from app.agents.plugins.sandbox import PluginSandbox + + +class PluginManager: + """插件管理器 + + 负责插件的安装、卸载、启用、禁用和生命周期管理。 + """ + + def __init__(self, plugins_dir: str | None = None): + """ + Args: + plugins_dir: 插件目录,None 则使用默认目录 + """ + if plugins_dir is None: + plugins_dir = os.path.join(os.path.dirname(__file__), "..", "..", "..", "plugins") + self.plugins_dir = plugins_dir + self._plugins: dict[str, PluginManifest] = {} + self._enabled: dict[str, bool] = {} + self._modules: dict[str, Any] = {} + self._sandbox = PluginSandbox() + + def install(self, plugin_path: str) -> bool: + """安装插件 + + Args: + plugin_path: 插件目录路径或 manifest.json 所在目录 + + Returns: + 是否安装成功 + """ + try: + manifest_path = os.path.join(plugin_path, "manifest.json") + + if not os.path.exists(manifest_path): + return False + + with open(manifest_path, "r", encoding="utf-8") as f: + import json + + data = json.load(f) + + manifest = PluginManifest.from_dict(data) + + # 验证 manifest + if not self._validate_manifest(manifest, plugin_path): + return False + + # 复制插件到 plugins_dir + target_dir = os.path.join(self.plugins_dir, manifest.id) + os.makedirs(os.path.dirname(target_dir), exist_ok=True) + + # 保存 manifest + with open(os.path.join(target_dir, "manifest.json"), "w", encoding="utf-8") as f: + json.dump(manifest.to_dict(), f, indent=2, ensure_ascii=False) + + # 注册插件 + self._plugins[manifest.id] = manifest + self._enabled[manifest.id] = True + + return True + + except Exception: + return False + + def uninstall(self, plugin_id: str) -> bool: + """卸载插件 + + Args: + plugin_id: 插件 ID + + Returns: + 是否卸载成功 + """ + if plugin_id not in self._plugins: + return False + + # 禁用插件 + self.disable(plugin_id) + + # 移除模块 + if plugin_id in self._modules: + del self._modules[plugin_id] + + # 移除插件 + del self._plugins[plugin_id] + del self._enabled[plugin_id] + + # 删除目录 + plugin_dir = os.path.join(self.plugins_dir, plugin_id) + if os.path.exists(plugin_dir): + import shutil + + shutil.rmtree(plugin_dir) + + return True + + def enable(self, plugin_id: str) -> bool: + """启用插件 + + Args: + plugin_id: 插件 ID + + Returns: + 是否启用成功 + """ + if plugin_id not in self._plugins: + return False + + self._enabled[plugin_id] = True + return True + + def disable(self, plugin_id: str) -> bool: + """禁用插件 + + Args: + plugin_id: 插件 ID + + Returns: + 是否禁用成功 + """ + if plugin_id not in self._plugins: + return False + + self._enabled[plugin_id] = False + return True + + def reload(self, plugin_id: str) -> bool: + """重新加载插件 + + Args: + plugin_id: 插件 ID + + Returns: + 是否重新加载成功 + """ + if plugin_id not in self._plugins: + return False + + # 卸载模块 + if plugin_id in self._modules: + del self._modules[plugin_id] + + # 重新加载 + return self._load_plugin_module(plugin_id) + + def list_plugins(self) -> list[PluginManifest]: + """列出所有插件""" + return list(self._plugins.values()) + + def get_plugin(self, plugin_id: str) -> PluginManifest | None: + """获取插件清单""" + return self._plugins.get(plugin_id) + + def is_enabled(self, plugin_id: str) -> bool: + """检查插件是否启用""" + return self._enabled.get(plugin_id, False) + + def _validate_manifest(self, manifest: PluginManifest, plugin_path: str) -> bool: + """验证 manifest""" + # 检查主入口文件是否存在 + main_path = os.path.join(plugin_path, manifest.main) + if not os.path.exists(main_path): + return False + + return True + + def _load_plugin_module(self, plugin_id: str) -> bool: + """加载插件模块""" + plugin_dir = os.path.join(self.plugins_dir, plugin_id) + manifest = self._plugins.get(plugin_id) + if not manifest: + return False + + try: + main_path = os.path.join(plugin_dir, manifest.main) + spec = importlib.util.spec_from_file_location(plugin_id, main_path) + if spec and spec.loader: + module = importlib.util.module_from_spec(spec) + sys.modules[plugin_id] = module + spec.loader.exec_module(module) + self._modules[plugin_id] = module + return True + except Exception: + pass + + return False + + +# 全局单例 +_manager: PluginManager | None = None + + +def get_plugin_manager() -> PluginManager: + """获取全局插件管理器""" + global _manager + if _manager is None: + _manager = PluginManager() + return _manager diff --git a/backend/app/agents/plugins/manifest.py b/backend/app/agents/plugins/manifest.py new file mode 100644 index 0000000..d15583e --- /dev/null +++ b/backend/app/agents/plugins/manifest.py @@ -0,0 +1,73 @@ +"""插件清单定义 - Phase 8.1""" + +from dataclasses import dataclass, field +from typing import Any + + +@dataclass +class PluginManifest: + """插件清单 + + 定义插件的元数据和接口。 + """ + + id: str # 唯一标识 + name: str # 显示名称 + version: str # 版本号 + description: str # 描述 + author: str = "" # 作者 + homepage: str = "" # 主页 + license: str = "MIT" # 许可证 + + # 插件类型 + plugin_type: str = "tool" # tool, hook, skill, all + + # 入口点 + main: str = "index.py" # 主入口文件 + hooks: list[str] = field(default_factory=list) # 提供的 Hook 列表 + tools: list[str] = field(default_factory=list) # 提供的工具列表 + skills: list[str] = field(default_factory=list) # 提供的 Skills 列表 + + # 依赖 + dependencies: dict[str, str] = field(default_factory=dict) # pip 依赖 + peer_dependencies: dict[str, str] = field(default_factory=dict) # 对等依赖 + + # 权限要求 + permissions: list[str] = field(default_factory=list) # 需要的权限 + allowed_paths: list[str] = field(default_factory=list) # 允许访问的路径 + denied_paths: list[str] = field(default_factory=list) # 禁止访问的路径 + + # 网络权限 + network_allowed: bool = False # 是否允许网络访问 + allowed_hosts: list[str] = field(default_factory=list) # 允许访问的 host + + # 配置 + config_schema: dict[str, Any] = field(default_factory=dict) # 配置 schema + + def to_dict(self) -> dict[str, Any]: + return { + "id": self.id, + "name": self.name, + "version": self.version, + "description": self.description, + "author": self.author, + "homepage": self.homepage, + "license": self.license, + "plugin_type": self.plugin_type, + "main": self.main, + "hooks": self.hooks, + "tools": self.tools, + "skills": self.skills, + "dependencies": self.dependencies, + "peer_dependencies": self.peer_dependencies, + "permissions": self.permissions, + "allowed_paths": self.allowed_paths, + "denied_paths": self.denied_paths, + "network_allowed": self.network_allowed, + "allowed_hosts": self.allowed_hosts, + "config_schema": self.config_schema, + } + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> "PluginManifest": + return cls(**data) diff --git a/backend/app/agents/plugins/sandbox.py b/backend/app/agents/plugins/sandbox.py new file mode 100644 index 0000000..726fbf4 --- /dev/null +++ b/backend/app/agents/plugins/sandbox.py @@ -0,0 +1,111 @@ +"""插件沙箱隔离 - Phase 8.3""" + +import os +import sys +from typing import Any + + +class PluginSandbox: + """插件沙箱 + + 提供插件执行隔离环境。 + """ + + def __init__(self): + self._allowed_paths: set[str] = set() + self._denied_paths: set[str] = set() + self._network_allowed: bool = False + self._allowed_hosts: set[str] = set() + + def set_file_permissions( + self, + allowed_paths: list[str] | None = None, + denied_paths: list[str] | None = None, + ) -> None: + """设置文件访问权限 + + Args: + allowed_paths: 允许访问的路径列表 + denied_paths: 禁止访问的路径列表 + """ + self._allowed_paths = set(allowed_paths or []) + self._denied_paths = set(denied_paths or []) + + def set_network_permissions( + self, allowed: bool, allowed_hosts: list[str] | None = None + ) -> None: + """设置网络访问权限 + + Args: + allowed: 是否允许网络访问 + allowed_hosts: 允许访问的 host 列表 + """ + self._network_allowed = allowed + self._allowed_hosts = set(allowed_hosts or []) + + def check_file_access(self, path: str) -> bool: + """检查文件访问权限 + + Args: + path: 文件路径 + + Returns: + 是否允许访问 + """ + # 如果有允许列表,只允许访问列表中的路径 + if self._allowed_paths: + return path in self._allowed_paths or any( + path.startswith(allowed) for allowed in self._allowed_paths + ) + + # 如果有禁止列表,禁止访问列表中的路径 + if self._denied_paths: + return not any(path.startswith(denied) for denied in self._denied_paths) + + # 没有限制 + return True + + def check_network_access(self, host: str) -> bool: + """检查网络访问权限 + + Args: + host: 主机地址 + + Returns: + 是否允许访问 + """ + if not self._network_allowed: + return False + + if self._allowed_hosts: + return host in self._allowed_hosts or any( + host.endswith(allowed) for allowed in self._allowed_hosts + ) + + return True + + def execute_in_sandbox(self, func: Any, *args, **kwargs) -> Any: + """在沙箱中执行函数 + + Args: + func: 要执行的函数 + *args: 位置参数 + **kwargs: 关键字参数 + + Returns: + 函数返回值 + """ + # 保存当前状态 + old_allowed_paths = self._allowed_paths.copy() + old_denied_paths = self._denied_paths.copy() + old_network_allowed = self._network_allowed + old_allowed_hosts = self._allowed_hosts.copy() + + try: + return func(*args, **kwargs) + finally: + # 恢复状态 + self._allowed_paths = old_allowed_paths + self._denied_paths = old_denied_paths + self._network_allowed = old_network_allowed + self._allowed_hosts = old_allowed_hosts diff --git a/backend/app/agents/skills/__init__.py b/backend/app/agents/skills/__init__.py new file mode 100644 index 0000000..56b8c07 --- /dev/null +++ b/backend/app/agents/skills/__init__.py @@ -0,0 +1,16 @@ +"""Skills 注册表 - Phase 9""" + +from app.agents.skills.registry import SkillRegistry, get_skill_registry +from app.agents.skills.metadata import SkillMetadata +from app.agents.skills.loaders.local_loader import LocalSkillLoader +from app.agents.skills.loaders.plugin_loader import PluginSkillLoader +from app.agents.skills.mcp_builder import MCPSkillBuilder + +__all__ = [ + "SkillRegistry", + "SkillMetadata", + "LocalSkillLoader", + "PluginSkillLoader", + "MCPSkillBuilder", + "get_skill_registry", +] diff --git a/backend/app/agents/skills/loaders/local_loader.py b/backend/app/agents/skills/loaders/local_loader.py new file mode 100644 index 0000000..a2e0715 --- /dev/null +++ b/backend/app/agents/skills/loaders/local_loader.py @@ -0,0 +1,100 @@ +"""本地 Skills 加载器 - Phase 9.2""" + +import os +import re +from typing import Any + +from app.agents.skills.metadata import SkillMetadata + + +class LocalSkillLoader: + """本地 Skills 加载器 + + 从 skills_dir 目录加载 SKILL.md 文件。 + """ + + def __init__(self, skills_dir: str): + self.skills_dir = skills_dir + + def load_all(self) -> list[SkillMetadata]: + """加载所有本地 Skills + + Returns: + Skill 元数据列表 + """ + skills = [] + + if not os.path.exists(self.skills_dir): + return skills + + for root, dirs, files in os.walk(self.skills_dir): + # 跳过隐藏目录 + dirs[:] = [d for d in dirs if not d.startswith(".")] + + if "SKILL.md" in files: + skill = self._load_skill_from_dir(root) + if skill: + skills.append(skill) + + return skills + + def _load_skill_from_dir(self, skill_dir: str) -> SkillMetadata | None: + """从目录加载 Skill + + Args: + skill_dir: Skill 目录 + + Returns: + Skill 元数据 + """ + skill_path = os.path.join(skill_dir, "SKILL.md") + + try: + with open(skill_path, "r", encoding="utf-8") as f: + content = f.read() + + # 解析 frontmatter + metadata = self._parse_frontmatter(content) + + # 获取 Skill 名称(目录名) + name = os.path.basename(skill_dir) + + return SkillMetadata( + name=metadata.get("name", name), + description=metadata.get("description", ""), + version=metadata.get("version", "1.0.0"), + author=metadata.get("author", ""), + tags=metadata.get("tags", []), + triggers=metadata.get("triggers", []), + content=content, + source="local", + source_id=skill_dir, + ) + + except Exception: + return None + + def _parse_frontmatter(self, content: str) -> dict[str, Any]: + """解析 frontmatter""" + metadata = {} + + # 匹配 --- 包裹的 frontmatter + match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL) + if match: + frontmatter = match.group(1) + + for line in frontmatter.split("\n"): + if ":" in line: + key, value = line.split(":", 1) + key = key.strip() + value = value.strip() + + # 处理列表 + if value.startswith("[") and value.endswith("]"): + value = [v.strip().strip('"').strip("'") for v in value[1:-1].split(",")] + elif value.lower() in ("true", "false"): + value = value.lower() == "true" + + metadata[key] = value + + return metadata diff --git a/backend/app/agents/skills/loaders/plugin_loader.py b/backend/app/agents/skills/loaders/plugin_loader.py new file mode 100644 index 0000000..eb16247 --- /dev/null +++ b/backend/app/agents/skills/loaders/plugin_loader.py @@ -0,0 +1,51 @@ +"""插件 Skills 加载器 - Phase 9.2""" + +from app.agents.skills.metadata import SkillMetadata +from app.agents.plugins.manager import get_plugin_manager + + +class PluginSkillLoader: + """插件 Skills 加载器 + + 从已安装的插件中加载 Skills。 + """ + + def __init__(self): + self.plugin_manager = get_plugin_manager() + + def load_all(self) -> list[SkillMetadata]: + """从所有已启用的插件加载 Skills + + Returns: + Skill 元数据列表 + """ + skills = [] + + for plugin in self.plugin_manager.list_plugins(): + if not self.plugin_manager.is_enabled(plugin.id): + continue + + # 从插件加载 Skills + plugin_skills = self._load_from_plugin(plugin) + skills.extend(plugin_skills) + + return skills + + def _load_from_plugin(self, plugin: Any) -> list[SkillMetadata]: + """从单个插件加载 Skills""" + skills = [] + + for skill_name in plugin.skills: + skill = SkillMetadata( + name=f"{plugin.id}/{skill_name}", + description=f"Skill from plugin: {plugin.name}", + version=plugin.version, + author=plugin.author, + tags=["plugin", plugin.id], + content=f"# {skill_name}\n\nFrom plugin: {plugin.name}", + source="plugin", + source_id=plugin.id, + ) + skills.append(skill) + + return skills diff --git a/backend/app/agents/skills/mcp_builder.py b/backend/app/agents/skills/mcp_builder.py new file mode 100644 index 0000000..90158a9 --- /dev/null +++ b/backend/app/agents/skills/mcp_builder.py @@ -0,0 +1,100 @@ +"""MCP Skill Builder - Phase 9.3""" + +from typing import Any + +from app.agents.skills.metadata import SkillMetadata + + +class MCPSkillBuilder: + """MCP Skill Builder + + 从 MCP 服务器发现和构建 Skills。 + """ + + def __init__(self): + self._skills: dict[str, SkillMetadata] = {} + + def discover_skills_from_mcp(self, mcp_servers: list[dict[str, Any]]) -> list[SkillMetadata]: + """从 MCP 服务器发现 Skills + + Args: + mcp_servers: MCP 服务器配置列表 + + Returns: + 发现的 Skill 元数据列表 + """ + skills = [] + + for server in 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""" + skills = [] + server_name = server.get("name", "unknown") + tools = server.get("tools", []) + + # 按工具分组 + tool_groups: dict[str, list[str]] = {} + for tool in tools: + group = tool.get("group", "default") + if group not in tool_groups: + tool_groups[group] = [] + tool_groups[group].append(tool) + + # 为每个组创建一个 Skill + for group_name, group_tools in tool_groups.items(): + skill = self._tool_to_skill(group_name, group_tools, server_name) + skills.append(skill) + + return skills + + def _tool_to_skill(self, group: str, tools: list[dict[str, Any]], server: str) -> SkillMetadata: + """将 MCP 工具转换为 Skill""" + tool_summaries = [] + for tool in tools: + name = tool.get("name", "unknown") + description = tool.get("description", "") + input_schema = tool.get("inputSchema", {}) + + tool_summaries.append(f"### {name}\n{description}\n\nInput: {input_schema}") + + content = f"""# MCP Skill: {group} + +来自 MCP 服务器: {server} + +## 工具列表 + +{chr(10).join(tool_summaries)} + +## 使用说明 + +使用这些工具前请确保理解每个工具的输入输出格式。 +""" + + return SkillMetadata( + name=f"mcp-{server}-{group}", + description=f"MCP skill from {server}: {group}", + version="1.0.0", + tags=["mcp", server, group], + triggers=[group, server], + content=content, + source="mcp", + source_id=f"{server}:{group}", + ) + + def _group_to_skill(self, group: str, tools: list[str], server: str) -> SkillMetadata: + """将 MCP 工具组转换为 Skill""" + return SkillMetadata( + name=f"mcp-{server}-{group}", + description=f"MCP skill from {server}: {group}", + version="1.0.0", + tags=["mcp", server, group], + triggers=[group, server], + content=f"# {group}\n\nTools: {', '.join(tools)}", + source="mcp", + source_id=f"{server}:{group}", + ) diff --git a/backend/app/agents/skills/metadata.py b/backend/app/agents/skills/metadata.py new file mode 100644 index 0000000..2b0c6de --- /dev/null +++ b/backend/app/agents/skills/metadata.py @@ -0,0 +1,38 @@ +"""Skill 元数据定义 - Phase 9.1""" + +from dataclasses import dataclass, field +from typing import Any + + +@dataclass +class SkillMetadata: + """Skill 元数据""" + + name: str # Skill 名称 + description: str # 描述 + version: str = "1.0.0" # 版本 + author: str = "" # 作者 + tags: list[str] = field(default_factory=list) # 标签 + triggers: list[str] = field(default_factory=list) # 触发关键词 + content: str = "" # Skill 内容(markdown) + source: str = "local" # 来源:local, plugin, mcp, bundled + source_id: str = "" # 来源 ID + enabled: bool = True # 是否启用 + + def to_dict(self) -> dict[str, Any]: + return { + "name": self.name, + "description": self.description, + "version": self.version, + "author": self.author, + "tags": self.tags, + "triggers": self.triggers, + "content": self.content, + "source": self.source, + "source_id": self.source_id, + "enabled": self.enabled, + } + + @classmethod + def from_dict(cls, data: dict[str, Any]) -> "SkillMetadata": + return cls(**data) diff --git a/backend/app/agents/skills/registry.py b/backend/app/agents/skills/registry.py new file mode 100644 index 0000000..732b8b8 --- /dev/null +++ b/backend/app/agents/skills/registry.py @@ -0,0 +1,133 @@ +"""Skills 注册表 - Phase 9.1""" + +import os +from typing import Any + +from app.agents.skills.metadata import SkillMetadata +from app.agents.skills.loaders.local_loader import LocalSkillLoader + + +class SkillRegistry: + """Skills 注册表 + + 管理所有 Skills 的注册、发现和加载。 + """ + + def __init__(self): + self._skills: dict[str, SkillMetadata] = {} + self._loaders: list[Any] = [] + + def load_all(self, skills_dir: str | None = None) -> int: + """加载所有 Skills + + Args: + skills_dir: Skills 目录,None 则使用默认目录 + + Returns: + 加载的 Skill 数量 + """ + if skills_dir is None: + skills_dir = os.path.join( + os.path.dirname(__file__), "..", "..", "..", ".claude", "skills" + ) + + count = 0 + + # 本地加载器 + local_loader = LocalSkillLoader(skills_dir) + local_skills = local_loader.load_all() + for skill in local_skills: + self.register(skill) + count += 1 + + # 插件加载器 + for loader in self._loaders: + try: + external_skills = loader.load_all() + for skill in external_skills: + self.register(skill) + count += 1 + except Exception: + pass + + return count + + def register(self, skill: SkillMetadata) -> None: + """注册 Skill""" + self._skills[skill.name] = skill + + def unregister(self, name: str) -> bool: + """注销 Skill""" + if name in self._skills: + del self._skills[name] + return True + return False + + def get_skill(self, name: str) -> SkillMetadata | None: + """获取 Skill""" + return self._skills.get(name) + + def search(self, query: str) -> list[SkillMetadata]: + """搜索 Skills + + Args: + query: 搜索关键词 + + Returns: + 匹配的 Skills 列表 + """ + query_lower = query.lower() + results = [] + + for skill in self._skills.values(): + if not skill.enabled: + continue + + # 匹配名称、描述、标签 + if ( + query_lower in skill.name.lower() + or query_lower in skill.description.lower() + or any(query_lower in tag.lower() for tag in skill.tags) + or any(query_lower in trigger.lower() for trigger in skill.triggers) + ): + results.append(skill) + + return results + + def get_skill_context(self, names: list[str]) -> str: + """获取 Skill 上下文 + + Args: + names: Skill 名称列表 + + Returns: + 拼接的 Skill 内容 + """ + contexts = [] + + for name in names: + skill = self._skills.get(name) + if skill and skill.enabled: + contexts.append(f"# {skill.name}\n\n{skill.content}") + + return "\n\n---\n\n".join(contexts) + + def add_loader(self, loader: Any) -> None: + """添加加载器""" + self._loaders.append(loader) + + def list_all(self) -> list[SkillMetadata]: + """列出所有 Skills""" + return list(self._skills.values()) + + +# 全局单例 +_registry: SkillRegistry | None = None + + +def get_skill_registry() -> SkillRegistry: + """获取全局 Skills 注册表""" + global _registry + if _registry is None: + _registry = SkillRegistry() + return _registry diff --git a/backend/app/agents/team/leader.py b/backend/app/agents/team/leader.py new file mode 100644 index 0000000..a5721ef --- /dev/null +++ b/backend/app/agents/team/leader.py @@ -0,0 +1,121 @@ +"""Team 多 Agent 协作 - Phase 10.1""" + +from dataclasses import dataclass, field +from typing import Any +from enum import Enum + + +class TaskStatus(Enum): + PENDING = "pending" + IN_PROGRESS = "in_progress" + COMPLETED = "completed" + FAILED = "failed" + + +@dataclass +class TeamTask: + """团队任务""" + + id: str + description: str + assignee: str | None = None + status: TaskStatus = TaskStatus.PENDING + result: Any = None + error: str | None = None + + +class TeamLeader: + """团队领导者 + + 协调多个 Agent 成员执行任务。 + """ + + def __init__(self, team_id: str, members: list[str]): + """ + Args: + team_id: 团队 ID + members: 成员 ID 列表 + """ + self.team_id = team_id + self.members = members + self._tasks: dict[str, TeamTask] = {} + + def create_task(self, description: str) -> str: + """创建任务 + + Args: + description: 任务描述 + + Returns: + 任务 ID + """ + import uuid + + task_id = str(uuid.uuid4())[:8] + self._tasks[task_id] = TeamTask( + id=task_id, + description=description, + ) + return task_id + + def assign_task(self, task_id: str, member: str) -> bool: + """分配任务 + + Args: + task_id: 任务 ID + member: 成员 ID + + Returns: + 是否成功 + """ + if task_id not in self._tasks: + return False + + if member not in self.members: + return False + + self._tasks[task_id].assignee = member + self._tasks[task_id].status = TaskStatus.IN_PROGRESS + return True + + def broadcast_task(self, description: str) -> list[str]: + """广播任务给所有成员 + + Args: + description: 任务描述 + + Returns: + 创建的任务 ID 列表 + """ + task_ids = [] + for member in self.members: + task_id = self.create_task(description) + self.assign_task(task_id, member) + task_ids.append(task_id) + return task_ids + + def collect_results(self) -> dict[str, Any]: + """收集所有任务结果 + + Returns: + 任务 ID -> 结果的映射 + """ + return { + task_id: task.result + for task_id, task in self._tasks.items() + if task.status == TaskStatus.COMPLETED + } + + def get_team_status(self) -> dict[str, Any]: + """获取团队状态 + + Returns: + 团队状态摘要 + """ + return { + "team_id": self.team_id, + "members": self.members, + "task_count": len(self._tasks), + "completed": sum(1 for t in self._tasks.values() if t.status == TaskStatus.COMPLETED), + "failed": sum(1 for t in self._tasks.values() if t.status == TaskStatus.FAILED), + } diff --git a/backend/app/agents/tools/hooks/builtins/__init__.py b/backend/app/agents/tools/hooks/builtins/__init__.py new file mode 100644 index 0000000..c00de3c --- /dev/null +++ b/backend/app/agents/tools/hooks/builtins/__init__.py @@ -0,0 +1,11 @@ +"""内置 Hook 集合 - Phase 7""" + +from app.agents.tools.hooks.builtins.audit_log import AuditLogHook +from app.agents.tools.hooks.builtins.dangerous_confirmation import DangerousConfirmationHook +from app.agents.tools.hooks.builtins.security_scan import SecurityScanHook + +__all__ = [ + "AuditLogHook", + "DangerousConfirmationHook", + "SecurityScanHook", +] diff --git a/backend/app/agents/tools/hooks/builtins/audit_log.py b/backend/app/agents/tools/hooks/builtins/audit_log.py new file mode 100644 index 0000000..98b8ca8 --- /dev/null +++ b/backend/app/agents/tools/hooks/builtins/audit_log.py @@ -0,0 +1,115 @@ +"""审计日志 Hook - Phase 7.2 + +记录所有工具调用到审计日志。 +""" + +from typing import Any + +from app.agents.tools.hooks.types import ( + ExecutionContext, + HookResult, + HookType, +) +from app.agents.tools.manifest import ToolCategory + + +class AuditLogHook: + """审计日志 Hook + + 记录所有工具调用的详细信息,包括: + - 调用时间 + - 工具名称 + - 输入参数 + - 执行结果 + - 执行时长 + - 用户 ID + """ + + def __init__(self, log_path: str | None = None): + """ + Args: + log_path: 日志文件路径,None 则输出到 stdout + """ + self.log_path = log_path + self._logs: list[dict[str, Any]] = [] + + async def pre_tool_use(self, context: ExecutionContext) -> HookResult: + """工具执行前记录""" + log_entry = { + "event": "pre_tool", + "tool_name": context.tool_name, + "input": context.tool_input, + "user_id": context.user_id, + "session_id": context.session_id, + } + self._logs.append(log_entry) + self._write_log(log_entry) + return HookResult( + hook_name="audit_log", + success=True, + continue_execution=True, + ) + + async def post_tool_use(self, context: ExecutionContext, result: Any) -> HookResult: + """工具执行后记录""" + log_entry = { + "event": "post_tool", + "tool_name": context.tool_name, + "result": str(result)[:500] if result else None, + "duration_ms": ( + (context.end_time - context.start_time) * 1000 + if context.start_time and context.end_time + else None + ), + } + self._logs.append(log_entry) + self._write_log(log_entry) + return HookResult( + hook_name="audit_log", + success=True, + continue_execution=True, + modified_output=result, + ) + + async def tool_error(self, context: ExecutionContext, error: Exception) -> HookResult: + """工具出错时记录""" + log_entry = { + "event": "tool_error", + "tool_name": context.tool_name, + "error": str(error), + "error_type": type(error).__name__, + } + self._logs.append(log_entry) + self._write_log(log_entry) + return HookResult( + hook_name="audit_log", + success=False, + continue_execution=True, + error=str(error), + ) + + def _write_log(self, entry: dict[str, Any]) -> None: + """写入日志""" + import json + import datetime + + entry["timestamp"] = datetime.datetime.now().isoformat() + + if self.log_path: + try: + with open(self.log_path, "a", encoding="utf-8") as f: + f.write(json.dumps(entry, ensure_ascii=False) + "\n") + except Exception: + # 日志写入失败不影响主流程 + pass + else: + # 输出到 stdout + print(f"[AUDIT] {json.dumps(entry, ensure_ascii=False)}") + + def get_logs(self) -> list[dict[str, Any]]: + """获取所有日志""" + return self._logs.copy() + + def clear_logs(self) -> None: + """清空日志""" + self._logs.clear() diff --git a/backend/app/agents/tools/hooks/builtins/dangerous_confirmation.py b/backend/app/agents/tools/hooks/builtins/dangerous_confirmation.py new file mode 100644 index 0000000..39d720d --- /dev/null +++ b/backend/app/agents/tools/hooks/builtins/dangerous_confirmation.py @@ -0,0 +1,142 @@ +"""危险操作确认 Hook - Phase 7.2 + +对危险操作要求用户确认。 +""" + +from typing import Any + +from app.agents.tools.hooks.types import ( + ExecutionContext, + HookResult, +) +from app.agents.tools.manifest import SideEffectScope + + +# 危险操作关键词 +DANGEROUS_PATTERNS = [ + # 文件操作 + "delete", + "remove", + "rm ", + "rmdir", + "unlink", + "format", + "truncate", + # 系统操作 + "shutdown", + "reboot", + "kill", + "pkill", + "sudo", + "chmod", + "chown", + # 数据操作 + "drop", + "truncate", + "delete from", + "delete.*where", + "insert into.*select", + "update.*set", + # 网络操作 + "curl", + "wget", + "nc ", + "netcat", + "ssh ", + "scp ", + "sftp ", + # 环境变量 + "export.*secret", + "export.*key", + "export.*token", +] + + +class DangerousConfirmationHook: + """危险操作确认 Hook + + 检查工具调用是否包含危险操作,如是则要求确认。 + """ + + def __init__(self, auto_block: bool = False): + """ + Args: + auto_block: True 表示自动拦截危险操作,False 表示仅警告 + """ + self.auto_block = auto_block + self._pending_confirmations: dict[str, bool] = {} + + async def pre_tool_use(self, context: ExecutionContext) -> HookResult: + """检查是否为危险操作""" + is_dangerous = self._check_dangerous(context.tool_name, context.tool_input) + + if is_dangerous: + if self.auto_block: + return HookResult( + hook_name="dangerous_confirmation", + success=False, + continue_execution=False, + error=f"危险操作被自动拦截: {context.tool_name}", + metadata={"dangerous": True, "auto_blocked": True}, + ) + else: + # 标记需要确认 + context.metadata["requires_confirmation"] = True + context.metadata["dangerous_operation"] = True + return HookResult( + hook_name="dangerous_confirmation", + success=True, + continue_execution=True, + metadata={"dangerous": True, "requires_confirmation": True}, + ) + + return HookResult( + hook_name="dangerous_confirmation", + success=True, + continue_execution=True, + ) + + def _check_dangerous(self, tool_name: str, tool_input: dict[str, Any]) -> bool: + """检查是否为危险操作""" + # 检查工具名称 + dangerous_tools = [ + "delete", + "remove", + "drop", + "truncate", + "kill", + "shutdown", + "reboot", + "bash", + "powershell", + "shell", + ] + + if tool_name.lower() in dangerous_tools: + return True + + # 检查输入参数 + input_str = str(tool_input).lower() + + for pattern in DANGEROUS_PATTERNS: + if pattern.lower() in input_str: + return True + + return False + + def confirm(self, session_id: str, confirmed: bool) -> None: + """确认危险操作 + + Args: + session_id: 会话 ID + confirmed: True 表示用户确认,False 表示取消 + """ + self._pending_confirmations[session_id] = confirmed + + def is_confirmed(self, session_id: str) -> bool: + """检查是否已确认""" + return self._pending_confirmations.get(session_id, False) + + def clear_confirmation(self, session_id: str) -> None: + """清除确认状态""" + self._pending_confirmations.pop(session_id, None) diff --git a/backend/app/agents/tools/hooks/builtins/security_scan.py b/backend/app/agents/tools/hooks/builtins/security_scan.py new file mode 100644 index 0000000..517f935 --- /dev/null +++ b/backend/app/agents/tools/hooks/builtins/security_scan.py @@ -0,0 +1,183 @@ +"""安全扫描 Hook - Phase 7.2 + +扫描工具调用和结果中的敏感信息。 +""" + +import re +from typing import Any + +from app.agents.tools.hooks.types import ( + ExecutionContext, + HookResult, +) + + +# 敏感信息模式 +SENSITIVE_PATTERNS = { + "api_key": [ + r"api[_-]?key['\"]?\s*[:=]\s*['\"]?[a-zA-Z0-9_\-]{20,}", + r"apikey['\"]?\s*[:=]\s*['\"]?[a-zA-Z0-9_\-]{20,}", + ], + "password": [ + r"password['\"]?\s*[:=]\s*['\"]?[^\s'\"]{8,}", + r"passwd['\"]?\s*[:=]\s*['\"]?[^\s'\"]{8,}", + r"secret['\"]?\s*[:=]\s*['\"]?[a-zA-Z0-9_\-]{20,}", + ], + "token": [ + r"token['\"]?\s*[:=]\s*['\"]?[a-zA-Z0-9_\-\.]{20,}", + r"bearer\s+[a-zA-Z0-9_\-\.]+", + r"ghp_[a-zA-Z0-9]{36}", + r"sk-[a-zA-Z0-9]{48}", + ], + "private_key": [ + r"-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----", + r"-----END (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----", + ], + "ip_address": [ + r"\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b", + ], + "email": [ + r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}", + ], +} + + +class SecurityScanHook: + """安全扫描 Hook + + 扫描工具输入和输出中的敏感信息,进行脱敏处理。 + """ + + def __init__( + self, + redact: bool = True, + block_on_detect: bool = False, + ): + """ + Args: + redact: 是否对敏感信息进行脱敏 + block_on_detect: 检测到敏感信息时是否阻止执行 + """ + self.redact = redact + self.block_on_detect = block_on_detect + self._compiled_patterns = { + name: [re.compile(p, re.IGNORECASE) for p in patterns] + for name, patterns in SENSITIVE_PATTERNS.items() + } + + async def pre_tool_use(self, context: ExecutionContext) -> HookResult: + """扫描输入参数""" + detected = self._scan_dict(context.tool_input) + + if detected: + context.metadata["security_detected"] = detected + + if self.block_on_detect: + return HookResult( + hook_name="security_scan", + success=False, + continue_execution=False, + error=f"检测到敏感信息: {', '.join(detected.keys())}", + metadata={"detected": detected, "blocked": True}, + ) + + if self.redact: + redacted_input = self._redact_dict(context.tool_input.copy()) + return HookResult( + hook_name="security_scan", + success=True, + continue_execution=True, + modified_input=redacted_input, + metadata={"detected": detected, "redacted": True}, + ) + + return HookResult( + hook_name="security_scan", + success=True, + continue_execution=True, + ) + + async def post_tool_use(self, context: ExecutionContext, result: Any) -> HookResult: + """扫描输出结果""" + if isinstance(result, dict): + detected = self._scan_dict(result) + + if detected: + context.metadata["security_detected_output"] = detected + + if self.redact: + redacted_result = self._redact_dict(result.copy()) + return HookResult( + hook_name="security_scan", + success=True, + continue_execution=True, + modified_output=redacted_result, + metadata={"detected": detected, "redacted": True}, + ) + + elif isinstance(result, str): + detected = self._scan_string(result) + if detected: + context.metadata["security_detected_output"] = detected + + if self.redact: + redacted_result = self._redact_string(result) + return HookResult( + hook_name="security_scan", + success=True, + continue_execution=True, + modified_output=redacted_result, + metadata={"detected": detected, "redacted": True}, + ) + + return HookResult( + hook_name="security_scan", + success=True, + continue_execution=True, + modified_output=result, + ) + + def _scan_dict(self, data: dict[str, Any]) -> dict[str, list[str]]: + """扫描字典中的敏感信息""" + result: dict[str, list[str]] = {} + + for key, value in data.items(): + if isinstance(value, str): + found = self._scan_string(value) + if found: + result[key] = found + + return result + + def _scan_string(self, text: str) -> list[str]: + """扫描字符串中的敏感信息""" + found_types = [] + + for name, patterns in self._compiled_patterns.items(): + for pattern in patterns: + if pattern.search(text): + if name not in found_types: + found_types.append(name) + break + + return found_types + + def _redact_dict(self, data: dict[str, Any]) -> dict[str, Any]: + """脱敏字典中的敏感信息""" + for key, value in data.items(): + if isinstance(value, str): + data[key] = self._redact_string(value) + elif isinstance(value, dict): + data[key] = self._redact_dict(value) + elif isinstance(value, list): + data[key] = [self._redact_string(v) if isinstance(v, str) else v for v in value] + + return data + + def _redact_string(self, text: str) -> str: + """脱敏字符串中的敏感信息""" + for name, patterns in self._compiled_patterns.items(): + for pattern in patterns: + text = pattern.sub(f"[REDACTED:{name}]", text) + + return text diff --git a/backend/app/agents/tools/hooks/config.py b/backend/app/agents/tools/hooks/config.py new file mode 100644 index 0000000..fa932b2 --- /dev/null +++ b/backend/app/agents/tools/hooks/config.py @@ -0,0 +1,105 @@ +"""Hook 配置持久化 - Phase 7.3""" + +import json +import os +from dataclasses import asdict, dataclass +from typing import Any + +from app.agents.tools.hooks.manager import get_hook_manager + + +@dataclass +class HookConfigEntry: + """Hook 配置条目""" + + name: str + hook_type: str + enabled: bool + tool_names: list[str] | None = None + categories: list[str] | None = None + priority: int = 0 + + +class HookConfigPersistence: + """Hook 配置持久化""" + + def __init__(self, config_path: str | None = None): + """ + Args: + config_path: 配置文件路径,None 则使用默认路径 + """ + if config_path is None: + config_path = os.path.join( + os.path.dirname(__file__), "..", "..", "..", "..", "config", "hooks.json" + ) + self.config_path = config_path + + def load_config(self) -> list[HookConfigEntry]: + """从文件加载 Hook 配置""" + if not os.path.exists(self.config_path): + return [] + + try: + with open(self.config_path, "r", encoding="utf-8") as f: + data = json.load(f) + return [HookConfigEntry(**entry) for entry in data] + except Exception: + return [] + + def save_config(self, entries: list[HookConfigEntry]) -> bool: + """保存 Hook 配置到文件""" + try: + os.makedirs(os.path.dirname(self.config_path), exist_ok=True) + with open(self.config_path, "w", encoding="utf-8") as f: + json.dump([asdict(e) for e in entries], f, indent=2, ensure_ascii=False) + return True + except Exception: + return False + + def apply_config(self) -> int: + """应用配置到 HookManager + + Returns: + 应用的 Hook 数量 + """ + from app.agents.tools.hooks.types import HookType + + manager = get_hook_manager() + entries = self.load_config() + count = 0 + + for entry in entries: + if entry.enabled: + from app.agents.tools.hooks.types import HookDefinition, HookTrigger + + trigger = HookTrigger( + tool_names=entry.tool_names, + categories=entry.categories, + ) + + # 创建空的 handler,只是注册配置 + hook_def = HookDefinition( + name=entry.name, + hook_type=HookType(entry.hook_type), + trigger=trigger, + handler=lambda ctx, *args: ctx, + priority=entry.priority, + enabled=True, + ) + + manager.register(hook_def) + count += 1 + + return count + + +# 全局单例 +_persistence: HookConfigPersistence | None = None + + +def get_hook_config_persistence() -> HookConfigPersistence: + """获取全局 Hook 配置持久化实例""" + global _persistence + if _persistence is None: + _persistence = HookConfigPersistence() + return _persistence diff --git a/backend/app/agents/transport/remote.py b/backend/app/agents/transport/remote.py new file mode 100644 index 0000000..3991460 --- /dev/null +++ b/backend/app/agents/transport/remote.py @@ -0,0 +1,113 @@ +"""远程传输层 - Phase 10.2""" + +import asyncio +import json +from typing import Any +from dataclasses import dataclass + + +@dataclass +class StructuredMessage: + """结构化消息""" + + type: str # response, event, tool_call, error + data: dict[str, Any] + session_id: str | None = None + + +class RemoteTransport: + """远程传输层 + + 处理与远程 Agent 的通信。 + """ + + def __init__(self): + self._connections: dict[str, Any] = {} + self._handlers: dict[str, Any] = {} + + async def send_response(self, session_id: str, response: dict[str, Any]) -> bool: + """发送响应 + + Args: + session_id: 会话 ID + response: 响应数据 + + Returns: + 是否发送成功 + """ + message = StructuredMessage( + type="response", + data=response, + session_id=session_id, + ) + return await self._send(session_id, message) + + async def send_event(self, session_id: str, event: dict[str, Any]) -> bool: + """发送事件 + + Args: + session_id: 会话 ID + event: 事件数据 + + Returns: + 是否发送成功 + """ + message = StructuredMessage( + type="event", + data=event, + session_id=session_id, + ) + return await self._send(session_id, message) + + async def send_tool_call(self, session_id: str, tool_call: dict[str, Any]) -> bool: + """发送工具调用 + + Args: + session_id: 会话 ID + tool_call: 工具调用数据 + + Returns: + 是否发送成功 + """ + message = StructuredMessage( + type="tool_call", + data=tool_call, + session_id=session_id, + ) + return await self._send(session_id, message) + + async def _send(self, session_id: str, message: StructuredMessage) -> bool: + """内部发送方法""" + if session_id not in self._connections: + return False + + try: + connection = self._connections[session_id] + if hasattr(connection, "send"): + await connection.send(json.dumps(message.__dict__)) + return True + except Exception: + pass + + return False + + def register_handler(self, event_type: str, handler: Any) -> 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")) diff --git a/development-doc/plan/agent-update/README.md b/development-doc/plan/agent-update/README.md new file mode 100644 index 0000000..531aa2d --- /dev/null +++ b/development-doc/plan/agent-update/README.md @@ -0,0 +1,282 @@ +# Jarvis Agents 升级计划索引 + +本目录用于存放 Jarvis Agents 2.0 的分阶段规划文档,同时也用于记录**当前代码真实落地状态**。 + +## 文档说明 + +| 文件 | 说明 | +|------|------| +| `README.md` | 总览、阶段关系、实施顺序、当前状态 | +| `phase-0-current-state-and-target.md` | 当前现状、问题、目标架构、ADR | +| `phase-1-safe-foundation.md` | 基础设施加固阶段 | +| `phase-2-controlled-collaboration.md` | 受控协作阶段 | +| `phase-3-dynamic-collaboration.md` | 动态协作阶段 | +| `phase-4-visibility-and-isolation.md` | 可视化与隔离执行阶段 | +| `phase-5-advanced-features.md` | 高级特性(可选) | +| `phase-6-tool-system-refactoring.md` | 工具系统重构 | +| `phase-7-hook-interception-layer.md` | Hook 拦截层 | +| `phase-8-plugin-ecosystem.md` | 插件生态 | +| `phase-9-skills-registry.md` | Skills 注册表 | +| `phase-10-advanced-orchestration.md` | 高级编排 | +| `phase-r-rag-upgrade.md` | RAG 系统升级专项(VCPToolBox 借鉴) | + +--- + +## 当前总体状态(2026-04-04) + +当前 Jarvis agent runtime 不再是“Phase 2/3/4 纯草案”,而是已经具备以下现实状态: + +### 78 → 90 成熟度标尺 + +| 分数 | 含义 | 当前状态 | +|------|------|----------| +| 75 | 受控协作基线:task/event/verifier/collaboration/dynamic guardrail 已稳定 | 已达到 | +| 85 | visibility + verification 基线:phase/checkpoint、topology、evidence、runtime summary、operator 调试入口可用 | 基本达到 | +| 90 | isolation runtime + cost governance + operator surface:会话/工作区隔离、成本阈值治理、前端可运营面板闭环 | 已达到 | +| 95+ | full sandbox / persistence / realtime UI / advanced memory | 明确延后 | + +| Phase | 当前状态 | 说明 | +|------|------|------| +| Phase 1 | 已落地 | verifier、task/event schema、基础执行模式已存在 | +| Phase 2 | 已实现基线 | collaboration mode、task decomposition、owner、result collection、verifier 收尾已运行 | +| Phase 3 | 已实现受限基线 | parent/root/depth、spawn policy、budget、interrupt/recovery、事件链路已存在 | +| Phase 4 | 已完成 90 分闭环 | visibility API、isolation runtime MVP、cost governance MVP、operator/debug surface 已落地 | +| Phase 5 | 未开始 | 保留为 full sandbox / persistence / realtime push 等可选增强 | +| Phase 6 | 待开始 | 工具系统重构(对标 claw-code) | +| Phase 7 | 待开始 | Hook 拦截层 | +| Phase 8 | 待开始 | 插件生态 | +| Phase 9 | 待开始 | Skills 注册表 | +| Phase 10 | 待开始 | 高级编排 | +| Phase R | 部分推进 | RAG 升级按专项继续推进 | + +### 本次新增落地 + +本次补齐了一个此前缺失但非常关键的层: + +- runtime 显式 phase model +- runtime checkpoint model +- phase / checkpoint history 持久化 +- phase / checkpoint event trace +- 对应自动化测试 + +新增后,当前 runtime 已可显式追踪: + +- `current_phase` +- `phase_history` +- `current_checkpoint` +- `checkpoint_history` + +并且会进入这些显式阶段: + +- `phase_0_bootstrap` +- `phase_1_routing` +- `phase_2_controlled_collaboration` +- `phase_3_dynamic_collaboration` +- `phase_4_visibility_and_verification` + +--- + +## 推荐阅读顺序 + +1. 先读 `phase-0-current-state-and-target.md` +2. 再读 `phase-2-controlled-collaboration.md` +3. 再读 `phase-3-dynamic-collaboration.md` +4. 最后读 `phase-4-visibility-and-isolation.md` + +原因:当前最重要的不是继续写理想化蓝图,而是先理解“代码里已经实现到了哪一步”。 + +--- + +## 总体升级原则 + +1. **保持简单请求路径稳定** - Direct Mode 不受影响 +2. **复杂请求才启用协作模式** - Collaboration Mode 按需触发 +3. **执行与验证分离** - Verifier 作为独立角色 +4. **动态能力必须受约束** - Budget + Permission + Depth +5. **所有升级都要配套测试** - 回归测试优先 +6. **优先做显式状态,不先做大拆分** - 先让运行时可观察、可验证,再抽模块 + +--- + +## 阶段关系图(按真实状态修订) + +```text +Phase 0 ──────────────────────────────────────────────────────────────┐ +│ 现状与目标 │ +│ - 当前架构分析 │ +│ - Demo 借鉴映射 │ +│ - ADR 架构决策 │ +└────────────────────────────────────────────────────────────────────┘ + │ + ▼ +Phase 1 ──────────────────────────────────────────────────────────────┐ +│ 基础设施加固 (Safe Foundation) │ +│ - verifier / schema / execution mode 基础 │ +│ 状态:已落地 │ +└────────────────────────────────────────────────────────────────────┘ + │ + ▼ +Phase 2 ──────────────────────────────────────────────────────────────┐ +│ 受控协作 (Controlled Collaboration) │ +│ - collaboration mode │ +│ - 任务拆解 / owner / 结果回收 / verifier │ +│ - 当前已补 phase + checkpoint │ +│ 状态:已实现基线 │ +└────────────────────────────────────────────────────────────────────┘ + │ + ▼ +Phase 3 ──────────────────────────────────────────────────────────────┐ +│ 动态协作 (Dynamic Collaboration) │ +│ - parent/root/depth tracking │ +│ - spawn policy + budget │ +│ - interrupt/recovery │ +│ - phase + checkpoint trace │ +│ 状态:已实现受限基线 │ +└────────────────────────────────────────────────────────────────────┘ + │ + ▼ +Phase 4 ──────────────────────────────────────────────────────────────┐ +│ 可视化与隔离 (Visibility + Isolation) │ +│ - visibility 查询 API │ +│ - continuity snapshot 持久化 │ +│ - isolation strategy 设计 │ +│ 状态:最小闭环已完成 │ +└────────────────────────────────────────────────────────────────────┘ + │ + ▼ +Phase 5 ──────────────────────────────────────────────────────────────┐ +│ 高级特性 (Advanced Features) │ +│ - full sandbox / persistence / cost monitoring / advanced UI │ +│ 状态:规划中,可选 │ +└────────────────────────────────────────────────────────────────────┘ + │ + ▼ +Phase 6 ──────────────────────────────────────────────────────────────┐ +│ 工具系统重构 (Tool System Refactoring) │ +│ - ToolRegistry / HookExecutor / StreamingToolExecutor │ +│ - 新增工具集:Glob/Grep/LSP/Bash/PowerShell/Cron │ +│ 状态:待开始(对标 claw-code tools/) │ +└────────────────────────────────────────────────────────────────────┘ + │ + ▼ +Phase 7 ──────────────────────────────────────────────────────────────┐ +│ Hook 拦截层 (Hook Interception Layer) │ +│ - PreTool/PostTool Hook 机制 │ +│ - 危险操作确认 / 安全扫描 / 审计日志 │ +│ 状态:待开始(依赖 Phase 6) │ +└────────────────────────────────────────────────────────────────────┘ + │ + ▼ +Phase 8 ──────────────────────────────────────────────────────────────┐ +│ 插件生态 (Plugin Ecosystem) │ +│ - PluginManager / 生命周期管理 / 插件市场 │ +│ 状态:待开始(依赖 Phase 6, 7) │ +└────────────────────────────────────────────────────────────────────┘ + │ + ▼ +Phase 9 ──────────────────────────────────────────────────────────────┐ +│ Skills 注册表 (Skills Registry) │ +│ - 动态 Skills 加载 / MCP Skill Builder / Bundled Skills │ +│ 状态:待开始(依赖 Phase 6) │ +└────────────────────────────────────────────────────────────────────┘ + │ + ▼ +Phase 10 ─────────────────────────────────────────────────────────────┐ +│ 高级编排 (Advanced Orchestration) │ +│ - Team Leader / Remote Transport / Session Manager / Background Tasks │ +│ 状态:待开始(对标 claw-code assistant/) │ +└────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## Demo 项目借鉴映射 + +| Demo项目 | 主要借鉴点 | 对应 Phase | +|---------|-----------|-----------| +| **Swarm-IDE** | Event trace、Dynamic Spawn、拓扑可视化 | Phase 3, 4 | +| **Claude Code CLI** | Coordinator-worker、Verifier 分离、Tool 权限 | Phase 1, 2 | +| **Claw Code** | Runtime 分层、Port Manifest、隔离策略 | Phase 2, 4, 6, 7, 8, 9, 10 | +| **VCPToolBox** | TagMemo V6、多索引、Token 感知分块 | Phase R, Phase 5 | + +### Claw Code 详细对照 + +| Claw Code 组件 | Jarvis Phase | 说明 | +|----------------|-------------|------| +| `tools/` | Phase 6 | 工具注册表、分层执行 | +| `StreamingToolExecutor` | Phase 6 | 流式工具执行 | +| `toolHooks.ts` | Phase 7 | Hook 拦截层 | +| `PluginLifecycle` | Phase 8 | 插件生态 | +| `skills/loadSkillsDir.ts` | Phase 9 | Skills 注册表 | +| `skills/bundledSkills.ts` | Phase 9 | Bundled Skills | +| `assistant/sessionHistory.ts` | Phase 10 | 高级会话管理 | +| `cli/structuredIO.ts` | Phase 10 | 结构化传输 | +| `cli/remoteIO.ts` | Phase 10 | 远程传输 | + +--- + +## 本次代码落点 + +本次 phase/checkpoint 补强主要修改: + +- `backend/app/agents/state.py` +- `backend/app/agents/graph.py` +- `backend/app/agents/schemas/event.py` +- `backend/app/services/agent_service.py` +- `backend/tests/backend/app/agents/test_graph.py` +- `backend/tests/backend/app/services/test_brain_ingestion.py` + +### 新增的关键事件 + +- `agent.phase.changed` +- `agent.checkpoint.recorded` + +### 新增的关键持久化字段 + +- `current_phase` +- `phase_history` +- `current_checkpoint` +- `checkpoint_history` + +--- + +## 当前仍未完成的内容 + +虽然能力已经明显前进,但下面这些仍属于后续工作: + +### 工程结构层 + +- 独立 `coordinator.py` +- 独立 `message_bus.py` +- 独立 `event_bus.py` +- `dynamic/` 与 `recovery/` 目录化拆分 + +### Claw Code 差距(Phase 6-10) + +- Phase 6: 工具系统重构(ToolRegistry/HookExecutor/StreamingToolExecutor) +- Phase 7: Hook 拦截层(PreTool/PostTool) +- Phase 8: 插件生态(PluginManager/生命周期/市场) +- Phase 9: Skills 注册表(动态加载/MCP Builder) +- Phase 10: 高级编排(Team/Remote Transport/Session Manager) + +### 平台能力层 + +- full sandbox / persistence / realtime push +- 独立 `coordinator.py` / `message_bus.py` / `event_bus.py` +- 更完整的 operator drilldown 与实时推送 +- SSE / WebSocket 实时推送(延后) +- sandbox container 执行器(延后) + +--- + +## 当前阶段结论 + +目前最准确的说法不是: + +> “Jarvis 还在做 agent phase 规划。” + +而是: + +> “Jarvis 已经具备多阶段 agent runtime 的核心基线,当前工作重点已经从‘是否可行’转向‘如何把已存在能力继续工程化、可视化、隔离化’。” + +这也是后续测试、验收和继续升级的正确前提。 diff --git a/development-doc/plan/agent-update/arvis-agents-2.0-upgrade-plan.md b/development-doc/plan/agent-update/arvis-agents-2.0-upgrade-plan.md new file mode 100644 index 0000000..1cb9cba --- /dev/null +++ b/development-doc/plan/agent-update/arvis-agents-2.0-upgrade-plan.md @@ -0,0 +1,961 @@ +# Jarvis Agents 2.0 升级方案 + +日期:2026-04-03 +状态:草案 +范围:`backend/app/agents/*`、相关测试,以及后续运行时/UI 扩展 + +--- + +## 1. 文档目标 + +本文档用于明确 Jarvis Agents 2.0 的升级方向。方案基于以下三个 demo 项目的分析结果整理而成: + +- `demo/swarm-ide-chore-specs-mvp` +- `demo/claude-code-cli-master` +- `demo/claw-code-main` + +本方案的目标不是照搬任意一个项目,而是结合它们各自的优点,让 Jarvis 从当前的: + +- 静态分层路由型 agent 系统 + +逐步升级为: + +- 可控的动态协作型 agent 运行时 + +同时保留 Jarvis 现在已经具备的优势: + +- 业务导向明确 +- 提醒 / 任务 / 搜索类连续性较强 +- 支持多 provider 和 fallback 策略 +- 运行边界清晰,容易测试和验证 + +--- + +## 2. 当前 Jarvis 的现状 + +当前核心代码主要集中在: + +- `backend/app/agents/graph.py` +- `backend/app/agents/state.py` +- `backend/app/agents/prompts.py` +- `backend/app/agents/registry/*` +- `backend/app/agents/tools/*` +- `backend/tests/backend/app/agents/test_graph.py` + +### 2.1 当前优势 + +Jarvis 目前已经不是简单的“单 prompt + 工具调用”系统,而是一个结构化的业务 agent 运行时,主要优势包括: + +1. **有主控层级** + - 存在类似 `master` 的顶层入口 + - 已按业务领域拆分出不同 agent / sub-commander + +2. **有明确的工具调用策略** + - 支持 native tool calling + - 也支持 JSON fallback + +3. **有连续性和澄清机制** + - 支持 clarification + - 支持 pending action / continuity + - 对 reminder / task 这类业务很重要 + +4. **状态模型较完整** + - 不是纯临时 prompt 拼接 + - 已有显式 state 管理 + +5. **测试基础较好** + - 当前 agent runtime 已有可观测试覆盖 + +### 2.2 当前短板 + +虽然 Jarvis 已经具备结构化 agent 系统的雏形,但依旧存在几个明显上限: + +1. **本质上仍是静态路由系统** + - 单轮请求通常只会选一条主路径执行 + - agent 关系是预先设计好的,不是运行时动态演化的 + +2. **没有真正的 agent-to-agent 通信原语** + - 现在更多是系统内部调度 + - 不是 agent 之间显式发消息、协作、转交任务 + +3. **没有显式 task/team 运行时** + - 缺少任务对象、所有权、依赖关系、完成状态 + +4. **缺少独立 verifier 角色** + - 当前执行者通常也是完成判断者 + - 缺少“执行”和“验收”分离 + +5. **可观察性还不够强** + - 系统内部做了很多判断,但外部很难完整看到 agent 协作过程 + +--- + +## 3. 三个 demo 项目的借鉴重点 + +--- + +## 3.1 Swarm-IDE:最值得借鉴的协作形态 + +关键参考文件: + +- `demo/swarm-ide-chore-specs-mvp/README.md` +- `demo/swarm-ide-chore-specs-mvp/backend/src/runtime/agent-runtime.ts` +- `demo/swarm-ide-chore-specs-mvp/backend/src/runtime/event-bus.ts` +- `demo/swarm-ide-chore-specs-mvp/backend/src/runtime/skill-loader.ts` + +### 值得借鉴的点 + +#### 1)协作原语非常简单,但表达力非常强 +Swarm 的核心原语很少,主要就是: + +- 创建 agent +- 发送消息 +- 建群 / 群聊 +- 列出 agent / 群组 / 消息 + +这种设计的好处是: + +- 不需要先写死很多工作流 +- 复杂拓扑可以由运行时自然演化 +- agent 行为更像“协作中的人”,而不是固定流程节点 + +#### 2)agent 间通信是一等能力 +Swarm 不是让系统替 agent 决定所有事情,而是让 agent 自己拥有: + +- 创建子 agent 的能力 +- 和任意 agent 通信的能力 +- 组群协作的能力 + +这比固定的 master -> domain 路由更灵活。 + +#### 3)人类可以直接介入任意子 agent +这点很关键。多 agent 系统最怕黑箱。一旦用户只能看到最终结果,而看不到中间代理,就很难调试和控制。 + +#### 4)事件流和可观察性很强 +Swarm 有清晰的 runtime 事件模型,例如: + +- `agent.wakeup` +- `agent.unread` +- `agent.stream` +- `agent.done` +- `agent.error` + +这对后续做: + +- 调试 +- 可视化 +- 事件回放 +- 协作审计 + +都非常有帮助。 + +### 不能直接照搬的点 + +Swarm 的自由度也意味着风险: + +- agent 无限制增殖 +- 消息风暴 +- token 成本失控 +- 系统行为难收敛 + +所以 Jarvis 应该借鉴它的“机制”,而不是直接复制它的“无限自由”。 + +--- + +## 3.2 Claude Code CLI:最值得借鉴的平台级编排 + +关键参考文件: + +- `demo/claude-code-cli-master/coordinator/coordinatorMode.ts` +- `demo/claude-code-cli-master/bootstrap/state.ts` +- `demo/claude-code-cli-master/skills/bundled/batch.ts` + +### 值得借鉴的点 + +#### 1)coordinator-worker 分层非常清晰 +这个项目不是简单地把多个 agent 拼在一起,而是明确区分: + +- 协调者 coordinator +- 执行 worker +- 任务生命周期 +- 结果汇总 +- 后续消息和回收 + +这正是 Jarvis 从“路由”升级到“编排”时最需要补的能力。 + +#### 2)有明确的 task / team 概念 +成熟的多 agent 系统不是只有“谁去做”,而是还需要: + +- task id +- owner +- blocked / depends_on +- 完成状态 +- worker 生命周期 + +#### 3)验证是独立通道 +真正成熟的系统会把: + +- 计划 +- 执行 +- 验证 + +分开,而不是让执行者自己宣布“我完成了”。 + +#### 4)并行执行强调隔离 +对于复杂任务,这种平台会强调: + +- 并行 worker +- 各自隔离的工作空间 +- 输出清晰归属到某个 worker + +这对未来 Jarvis 如果处理更复杂的编码或多步骤任务,价值很高。 + +--- + +## 3.3 Claw Code:最值得借鉴的 runtime 工程化能力 + +关键参考文件: + +- `demo/claw-code-main/rust/crates/runtime/src/conversation.rs` +- `demo/claw-code-main/rust/crates/tools/src/lib.rs` +- `demo/claw-code-main/rust/crates/plugins/src/lib.rs` +- `demo/claw-code-main/PARITY.md` + +### 值得借鉴的点 + +#### 1)运行时分层清晰 +Claw 的优势不是协作花样最多,而是运行时拆分很自然: + +- runtime loop +- tools +- commands +- plugins +- permission + +Jarvis 后续也应该逐步减少 graph 与具体业务工具的强耦合。 + +#### 2)工具注册表可以更强 +Jarvis 现在已有 registry 方向,但还不够深入。Claw 的启发是: + +每个工具不只是名字和描述,还应该有: + +- 权限等级 +- 副作用范围 +- 是否幂等 +- 是否可重试 +- 是否适合并行 +- 是否需要确认 + +#### 3)权限模型显式化 +一旦系统支持动态协作,就不能默认所有 worker 都能做所有事。 + +#### 4)插件 / Hook 扩展点应该尽早预留 +即使暂时不做插件生态,也应该尽早定义扩展点,否则以后核心 runtime 会越来越难改。 + +--- + +## 4. Jarvis 2.0 的总体目标架构 + +Jarvis 2.0 的目标不是做成“完全自由的蜂群系统”,而是做成: + +- **受控的动态协作系统** + +### 4.1 核心原则 + +1. 简单请求继续走当前稳定路径。 +2. 复杂请求才进入协作模式。 +3. agent 通信必须显式、可观察、可约束。 +4. 执行与验证必须分离。 +5. 所有危险能力都必须被预算和权限控制。 + +### 4.2 总体分层 + +#### 第一层:请求模式选择层 +每个请求先判定: + +- **直接模式**:继续使用现有 `master -> domain -> sub-commander` +- **协作模式**:进入 coordinator -> task -> delegate -> verify 流程 + +#### 第二层:协调层 +新增 coordinator,负责: + +- 理解请求 +- 判断是否需要拆任务 +- 生成任务列表 +- 分配任务给不同角色 +- 回收结构化结果 +- 触发 verifier + +#### 第三层:工作层 +worker agent 负责具体执行,例如: + +- schedule 执行 +- task 执行 +- 检索 / 搜索 +- forum 操作 +- 分析 / 汇总 +- 验证 / 审核 + +#### 第四层:协作底座 +底座层应该提供: + +- agent 注册 +- agent identity +- task 对象 +- message channel +- event stream +- budget / interrupt / recovery + +--- + +## 5. Jarvis 2.0 需要新增的核心概念 + +--- + +## 5.1 一等的 agent 通信原语 + +建议在 Jarvis 内部先引入以下运行时能力: + +- `create_agent(role, guidance, parent_id?)` +- `send_agent_message(to_agent_id, content, task_id?)` +- `list_agents()` +- `list_agent_threads(agent_id?)` +- `interrupt_agent(agent_id)` + +### 作用 + +这一步完成后,Jarvis 就不再只是“从主图里选路径”,而是具备“让 agent 协作”的基础能力。 + +### 注意 + +这些能力可以先作为内部能力,不一定立刻暴露给最终用户。 + +--- + +## 5.2 结构化 task 运行时 + +建议引入任务对象,至少包含: + +- `task_id` +- `title` +- `owner_agent_id` +- `status`:`pending` / `in_progress` / `blocked` / `completed` / `failed` +- `depends_on` +- `evidence` +- `result_summary` +- `created_by` + +### 作用 + +一旦 task 成为系统对象,Jarvis 才能真正实现: + +- 并行 +- 重试 +- 显式完成判断 +- 进度展示 + +--- + +## 5.3 事件总线 Event Bus + +建议新增统一事件流,至少包含: + +- `agent.created` +- `agent.wakeup` +- `agent.message.sent` +- `agent.message.received` +- `agent.tool.start` +- `agent.tool.result` +- `agent.task.assigned` +- `agent.task.completed` +- `agent.verify.started` +- `agent.verify.completed` +- `agent.error` + +### 作用 + +后续可以用于: + +- 调试 +- 日志回放 +- 可视化 +- agent 行为审计 + +--- + +## 5.4 verifier 角色 + +建议新增独立 verifier / reviewer 角色。 + +### 职责 + +- 判断是否真的满足用户请求 +- 检查是否缺少工具证据 +- 识别不完整执行 +- 要求重新执行或补充 + +### 意义 + +这一步是 Jarvis 从“会调用工具的 assistant”升级为“可靠 agent 系统”的关键一步。 + +--- + +## 5.5 工具权限模型 + +每个工具建议新增如下元数据: + +- `permission_class`:`read` / `write` / `external` / `dangerous` +- `side_effect_scope`:`none` / `local_state` / `remote_state` +- `safe_for_worker_roles` +- `requires_confirmation` +- `supports_retry` +- `idempotent` + +### 意义 + +动态协作系统必须比静态路由系统有更强的治理能力。 + +--- + +## 6. 按代码区域的演进建议 + +--- + +## 6.1 `backend/app/agents/state.py` + +### 目标 +在现有 state 基础上扩展协作信息,而不是推翻重来。 + +### 建议新增字段 + +- `agent_id` +- `parent_agent_id` +- `task_id` +- `active_tasks` +- `task_results` +- `message_queue` +- `event_log_refs` +- `verification_status` +- `execution_mode`:`direct` / `collaboration` +- budget 字段: + - `max_spawn_depth` + - `max_child_agents` + - `max_messages_per_turn` + - `max_parallel_tasks` + +### 重点 + +不要删除当前 continuity / clarification 相关状态,而是在它之上扩展。 + +--- + +## 6.2 `backend/app/agents/graph.py` + +### 目标 +从“纯路由图”升级为“路由 + 编排混合图”。 + +### 建议新增节点 + +- `request_mode_selector` +- `coordinator` +- `task_decomposer` +- `delegation_router` +- `worker_runner` +- `verifier` +- `result_synthesizer` + +### 推荐行为 + +#### 简单请求 +继续走当前路径,避免影响当前稳定业务。 + +#### 复杂请求 +走以下流程: + +1. coordinator 理解请求 +2. task_decomposer 产出小任务列表 +3. delegation_router 为每个任务分配角色 +4. worker_runner 执行任务 +5. verifier 检查产出 +6. result_synthesizer 汇总最终结果 + +### 重点 + +不是替换当前 graph,而是在复杂请求上增加新的执行分支。 + +--- + +## 6.3 `backend/app/agents/prompts.py` + +### 目标 +新增更明确的角色 prompt,减少职责混乱。 + +### 需要新增的角色 prompt + +- coordinator +- verifier +- critic / reviewer(可后续再加) + +### 协调者 prompt 应强调 + +- 只有复杂任务才拆分 +- 任务拆分要小而清晰 +- 不要过度创建 agent +- 输出必须带证据 + +### verifier prompt 应强调 + +- 不接受模糊完成 +- 检查证据是否充分 +- 检查用户意图是否真正满足 + +--- + +## 6.4 `backend/app/agents/registry/*` + +### 目标 +让 registry 不只是描述性的,而是逐渐成为 runtime 驱动的一部分。 + +### 建议补充 + +- agent role metadata +- role capability +- role spawn permission +- tool access policy +- task suitability tags + +### 建议角色分类 + +- coordinator +- executor +- scheduler +- retriever +- analyst +- verifier + +--- + +## 6.5 `backend/app/agents/tools/*` + +### 目标 +保留现有业务工具,但为它们补全运行时治理信息。 + +### 每个工具建议具备的元数据 + +- `name` +- `description` +- `permission_class` +- `supports_retry` +- `idempotent` +- `safe_for_parallel_use` +- `returns_structured_evidence` + +### 建议新增模块 + +可以新增: + +- `backend/app/agents/tools/collaboration.py` + +用于承载协作原语,例如: + +- 创建 agent +- 发送内部消息 +- claim task +- complete task +- request verification + +--- + +# 7. 升级阶段设计 + +下面按阶段详细说明,每个阶段都包含: + +- 目标 +- 解决的问题 +- 范围 +- 核心改动 +- 风险 +- 验收标准 +- 推荐实施顺序 + +--- + +## 阶段一:基础设施加固阶段(Safe Foundation) + +### 7.1.1 阶段目标 + +在不破坏当前稳定业务路径的前提下,为后续多 agent 协作打底。 + +### 7.1.2 这一阶段要解决的问题 + +当前 Jarvis 的主要问题不是“不会做业务”,而是: + +- 缺 verifier +- 缺 task 对象 +- 缺事件流 +- 缺工具权限元数据 +- graph 不区分直接模式和协作模式 + +所以第一阶段不是追求炫技,而是先把基础设施补齐。 + +### 7.1.3 范围 + +主要涉及: + +- `backend/app/agents/state.py` +- `backend/app/agents/graph.py` +- `backend/app/agents/prompts.py` +- `backend/app/agents/registry/models.py` +- `backend/app/agents/registry/builtins.py` +- `backend/app/agents/tools/__init__.py` +- `backend/tests/backend/app/agents/*` + +### 7.1.4 核心改动 + +#### 改动 1:新增 `execution_mode` +让系统能区分: + +- `direct` +- `collaboration` + +#### 改动 2:新增 verifier 角色 +在不改变主流程的基础上,先插入一个独立 verifier 角色。 + +#### 改动 3:新增事件总线抽象 +哪怕先不做 UI,也要先定义事件结构和写入方式。 + +#### 改动 4:补工具元数据 +给现有工具补权限、幂等、是否适合并行等信息。 + +#### 改动 5:引入 task 数据结构 +哪怕暂时只在内部使用,也要先把任务结构定义出来。 + +### 7.1.5 风险点 + +1. **改 state 容易影响现有测试** +2. **verifier 插入点选错会影响现有行为** +3. **事件系统如果写得太重,会影响性能和复杂度** +4. **工具元数据定义过度,会让现有实现变复杂** + +### 7.1.6 验收标准 + +满足以下条件才算第一阶段完成: + +- 当前 reminder / task / search 主要流程测试仍通过 +- 系统可以独立运行 verifier 角色 +- 系统可以产生基本事件记录 +- 工具权限元数据已存在并有测试 +- 不引入动态 agent 创建能力 + +### 7.1.7 推荐实施顺序 + +1. 定义 task schema +2. 定义 event schema +3. 扩展 state +4. 扩展 tool metadata +5. 加 verifier prompt +6. 在 graph 中插 verifier 分支 +7. 补测试 + +--- + +## 阶段二:受控协作阶段(Controlled Collaboration) + +### 7.2.1 阶段目标 + +让 Jarvis 开始具备“拆任务、分配任务、回收结果”的能力,但仍然保持强约束。 + +### 7.2.2 这一阶段要解决的问题 + +当前系统的主要限制是: + +- 复杂请求只能在一条路径里硬做 +- 没法显式表达子任务 +- 没法让不同角色协作完成复杂目标 + +第二阶段的重点,就是把“复杂请求”从“路由”升级为“编排”。 + +### 7.2.3 范围 + +主要涉及: + +- `backend/app/agents/graph.py` +- `backend/app/agents/prompts.py` +- `backend/app/agents/state.py` +- `backend/app/agents/registry/*` +- 新增协作工具模块 +- 新增对应测试 + +### 7.2.4 核心改动 + +#### 改动 1:新增 coordinator 节点 +由 coordinator 判断是否需要拆分请求。 + +#### 改动 2:新增 task decomposition +复杂请求要能拆出 2~4 个清晰的子任务。 + +#### 改动 3:新增 worker assignment +不同子任务按角色分配给不同 worker 逻辑。 + +#### 改动 4:新增内部消息传递 +先做最小版本的 agent-to-agent 通信,不必一开始就做复杂群聊。 + +#### 改动 5:完成后必须走 verifier +worker 的产出不直接作为最终结论,必须先过 verifier。 + +### 7.2.5 风险点 + +1. **任务拆得太细会增加系统复杂度** +2. **任务拆得太粗又达不到协作收益** +3. **内部通信如果没有预算,会引发回路** +4. **worker 边界不清会导致职责重叠** + +### 7.2.6 验收标准 + +- 复杂请求可以被拆成 2~4 个子任务 +- 每个子任务有明确 owner +- worker 输出带结构化 evidence +- verifier 可以拒绝不完整结果 +- final result 基于任务结果汇总,而不是某个单一 worker 的主观结论 + +### 7.2.7 推荐实施顺序 + +1. 增加 coordinator prompt +2. 增加 task decomposition schema +3. 增加 delegation router +4. 增加最小通信原语 +5. 增加 verifier 回收 +6. 补整套协作测试 + +--- + +## 阶段三:动态协作阶段(Dynamic Collaboration) + +### 7.3.1 阶段目标 + +在受控前提下,让 Jarvis 具备更接近 Swarm 的动态协作能力,但不是完全放飞。 + +### 7.3.2 这一阶段要解决的问题 + +第二阶段虽然已经能做任务拆分,但仍然偏“平台帮你分好工”。 + +第三阶段要解决的问题是: + +- worker 能否在必要时请求新的协作 +- 是否支持 parent / child agent 关系 +- 是否支持更完整的消息通路 +- 是否支持更强的中间态可观察性 + +### 7.3.3 范围 + +主要涉及: + +- `backend/app/agents/state.py` +- `backend/app/agents/graph.py` +- 新增 collaboration runtime 模块 +- 新增 message / thread / event 相关抽象 +- 未来可能涉及前端可视化接口 + +### 7.3.4 核心改动 + +#### 改动 1:支持 parent / child agent tracking +让系统知道哪个 agent 是谁创建的。 + +#### 改动 2:支持有限动态创建 agent +注意:必须有限制,不允许无限递归创建。 + +#### 改动 3:支持有限的 agent 消息线程 +先支持最小内部线程即可,不一定马上做群聊 UI。 + +#### 改动 4:增强事件流 +把协作链路完整记录下来。 + +#### 改动 5:支持 interrupt / recovery +多 agent 系统没有中断与恢复,后面会很难维护。 + +### 7.3.5 风险点 + +1. **agent 增殖风险** +2. **消息风暴风险** +3. **token 成本和延迟上升** +4. **调试复杂度上升** +5. **过度动态化破坏当前稳定路径** + +### 7.3.6 验收标准 + +- parent / child agent 关系可追踪 +- 系统支持受限动态创建 agent +- agent 间通信链路可记录 +- 可中断运行中的协作 +- 所有动态协作都受预算限制 + +### 7.3.7 推荐实施顺序 + +1. 建 parent / child state +2. 建 spawn budget / message budget +3. 实现受限 `create_agent` +4. 实现内部消息线程 +5. 实现 interrupt / recovery +6. 加事件回放和调试日志 + +--- + +## 阶段四:可视化与隔离执行阶段(Visibility + Isolation) + +### 7.4.1 阶段目标 + +把多 agent 系统从“后台能跑”升级为“可看、可控、可调试、可隔离”。 + +### 7.4.2 这一阶段要解决的问题 + +多 agent 系统发展到一定程度后,纯日志已经不够。你会需要: + +- 谁创建了谁 +- 谁在做什么 +- 谁给谁发了什么 +- 哪个任务卡住了 +- 某个 agent 为啥没完成 + +### 7.4.3 范围 + +这个阶段可能跨: + +- backend runtime +- event stream API +- frontend 调试面板 / graph 面板 +- 编码任务场景下的隔离执行策略 + +### 7.4.4 核心改动 + +#### 改动 1:协作链路可视化 +至少包括: + +- 当前 agent 列表 +- parent / child 关系 +- task 状态 +- message 流向 + +#### 改动 2:agent 历史和工具证据可见 +支持查看某个 agent: + +- 历史消息 +- 工具调用 +- 工具结果 +- verifier 结论 + +#### 改动 3:隔离执行能力 +如果以后 Jarvis 要处理更复杂的 coding 任务,可以考虑: + +- worker 级隔离目录 +- worktree +- 独立 session state + +### 7.4.5 风险点 + +1. **UI 一旦做太早,会分散后端核心升级精力** +2. **事件量上升后,展示层会有性能压力** +3. **隔离执行会提升工程复杂度** + +### 7.4.6 验收标准 + +- 可以看到基本 agent 拓扑 +- 可以看到任务流转和关键事件 +- 可以查看某个 agent 的执行证据 +- 隔离执行至少有设计方案,最好有最小实现 + +### 7.4.7 推荐实施顺序 + +1. 先把 event schema 固化 +2. 再做 event stream API +3. 再做最小调试页面 +4. 最后做隔离执行策略 + +--- + +## 8. 必须长期坚持的治理规则 + +Jarvis 不应该变成“无限自由蜂群”,而应该是“带预算的动态协作系统”。 + +因此必须长期保留这些约束: + +1. **spawn budget**:每次请求最多允许创建多少 agent +2. **message budget**:每个任务 / 每轮最多允许多少消息 +3. **max depth**:代理树最大深度 +4. **verifier gate**:复杂任务必须经 verifier 才能宣布完成 +5. **tool permission policy**:不同角色拥有不同工具权限 +6. **interrupt / cancel 路径**:长任务必须可中断 +7. **structured evidence**:没有证据就不算完成 + +--- + +## 9. 推荐优先级 + +如果近期只能做少量升级,推荐优先级如下: + +1. verifier 角色 +2. event bus / 可观察性 +3. 结构化 task runtime +4. coordinator for complex requests +5. 内部 agent 通信原语 +6. registry 元数据增强 +7. 隔离执行能力 + +--- + +## 10. 最现实的近期落地顺序 + +建议按下面顺序推进,而不是同时铺开: + +### 第一步 +先做 verifier 的 state、prompt、graph 插入点设计。 + +### 第二步 +定义 task schema 和 event schema。 + +### 第三步 +给工具补元数据和权限标签。 + +### 第四步 +在 graph 中加入 coordinator 只处理复杂请求。 + +### 第五步 +引入最小可用的内部协作原语。 + +### 第六步 +补一轮完整测试,确保旧路径不坏、新路径可控。 + +--- + +## 11. 最终建议 + +Jarvis 2.0 不应该单独模仿某一个 demo,而应该做这样的融合: + +- 从 **Swarm-IDE** 学:动态通信原语、可观察性、协作拓扑 +- 从 **Claude Code CLI** 学:coordinator / task / verifier 的平台化编排 +- 从 **Claw Code** 学:runtime 分层、工具注册表、权限模型 + +一句话总结升级方向: + +> 让 Jarvis 从“静态层级路由系统”升级为“受控的动态协作运行时”。 + +并且必须坚持一个原则: + +> 简单请求保持当前稳定路径,复杂请求才启用协作编排能力。 + +--- + +## 12. 建议后续继续拆分的文档 + +建议在这份总方案之后,再继续补这些中文文档: + +1. `development-doc/jarvis-agents-phase-a-design.md` +2. `development-doc/jarvis-agent-event-schema.md` +3. `development-doc/jarvis-agent-task-schema.md` +4. `development-doc/jarvis-agent-role-permissions.md` +5. `development-doc/jarvis-verifier-integration-plan.md` + +如果继续往下做,最建议先写的是: + +- **Phase A 详细设计文档** + +因为它最贴近当前代码,也最容易直接转成开发任务。 \ No newline at end of file diff --git a/development-doc/plan/agent-update/jarvis-agents-2-day-integration-plan.md b/development-doc/plan/agent-update/jarvis-agents-2-day-integration-plan.md new file mode 100644 index 0000000..3255016 --- /dev/null +++ b/development-doc/plan/agent-update/jarvis-agents-2-day-integration-plan.md @@ -0,0 +1,398 @@ +# Jarvis Agents 2 天融合改造计划 + +日期:2026-04-03 +状态:草案 +目标:在 **2 天内** 完成一版最小可落地的 Jarvis Agents 2.0 融合改造方案,不追求一步到位,只做最有价值、最能落地、风险最低的部分。 + +--- + +## 1. 这 2 天要完成什么 + +这 2 天不做完整的 swarm 化改造,也不做复杂 UI。 + +这 2 天的核心目标只有 4 个: + +1. 把升级方向真正落到当前 Jarvis 代码结构上 +2. 先完成 **Phase 1 的最小闭环** +3. 为 **Phase 2 的受控协作** 预埋接口 +4. 保证当前 reminder / task / search 主路径不被破坏 + +换句话说: + +> 这 2 天的重点不是“把终态做完”,而是“把融合路径打通”。 + +--- + +## 2. 融合原则 + +融合这套方案时,必须遵守下面 5 个原则: + +### 原则 1:不推翻当前 graph +当前 `backend/app/agents/graph.py` 是现有稳定主链,不能直接大改成全新框架。 + +策略: + +- 保留现有 direct path +- 只新增最小的 collaboration 入口 + +### 原则 2:先补底座,再谈动态协作 +先做: + +- verifier +- task schema +- event schema +- tool metadata + +后做: + +- coordinator +- agent message +- 动态 create_agent + +### 原则 3:先做内部能力,不急着做前台可视化 +即使未来要做 Swarm 风格可观察性,也不应该在这 2 天里优先做 UI。 + +先把: + +- 事件结构 +- 任务结构 +- 协作状态 + +定义清楚。 + +### 原则 4:优先保障现有业务稳定 +本次融合不能影响当前已有优势: + +- continuity +- clarification +- native tool / fallback +- schedule / task / search 主流程 + +### 原则 5:每一步都要可测试 +只要动到: + +- state +- graph +- tools +- prompts + +就必须补测试,不允许只写方案不验证。 + +--- + +## 3. 第 1 天计划:补底座,完成最小 Phase 1 + +第 1 天目标: + +> 让 Jarvis 拥有 verifier、task schema、event schema、tool metadata 这些基础设施,但不引入复杂动态协作。 + +--- + +### 3.1 第 1 天工作目标 + +完成以下结果: + +1. state 能表达 direct / collaboration 两种模式 +2. verifier 成为独立角色 +3. task schema 初版可用 +4. event schema 初版可用 +5. tool metadata 初版可用 +6. 现有主路径测试不回退 + +--- + +### 3.2 第 1 天建议改动文件 + +重点落在这些文件: + +- `backend/app/agents/state.py` +- `backend/app/agents/graph.py` +- `backend/app/agents/prompts.py` +- `backend/app/agents/registry/models.py` +- `backend/app/agents/registry/builtins.py` +- `backend/app/agents/tools/__init__.py` +- `backend/tests/backend/app/agents/test_graph.py` +- 新增若干 schema / tests 文件 + +--- + +### 3.3 第 1 天详细任务分解 + +#### 任务 1:定义最小 task schema +建议新增最小任务结构,字段控制在必要范围: + +- `task_id` +- `title` +- `status` +- `owner_agent_id` +- `evidence` +- `result_summary` + +目的: + +- 不马上实现完整 task runtime +- 先让 graph 和 verifier 有统一结构可依赖 + +#### 任务 2:定义最小 event schema +建议先定义最小事件: + +- `agent.tool.start` +- `agent.tool.result` +- `agent.verify.started` +- `agent.verify.completed` +- `agent.error` + +目的: + +- 先建立可观察性的“格式标准” +- 后面要不要落 DB / log / stream,可以再扩展 + +#### 任务 3:扩展 state +给现有 state 增加: + +- `execution_mode` +- `verification_status` +- `active_tasks` +- `task_results` +- 预算字段占位 + +注意: + +- 不要破坏现有 continuity 字段 +- 尽量以新增字段为主 + +#### 任务 4:新增 verifier prompt +在 `prompts.py` 中新增 verifier 的职责说明: + +- 不判断“写得像不像完成” +- 只判断“是否真正满足请求 + 是否有证据” + +#### 任务 5:graph 中插入 verifier 分支 +第一天不做复杂 coordinator。 + +只做: + +- 在适当复杂输出后增加 verifier 节点 +- direct path 仍保持主导 + +#### 任务 6:给 tools 增加 metadata +当前工具补充: + +- `permission_class` +- `side_effect_scope` +- `supports_retry` +- `idempotent` +- `safe_for_parallel_use` + +#### 任务 7:补测试 +至少补下面几类测试: + +- state 扩展兼容性 +- verifier 执行路径 +- tool metadata 存在性 +- event schema 生成正确性 +- 主流程无回退 + +--- + +### 3.4 第 1 天验收标准 + +第 1 天结束时必须满足: + +1. 当前 reminder/task/search 流程测试继续通过 +2. verifier 已成为独立角色,而不是写在注释里的计划 +3. 有 task schema 和 event schema 初版 +4. 每个关键工具已有 metadata +5. 当前系统还没有开始无限动态 agent + +--- + +### 3.5 第 1 天风险控制 + +1. 不做动态 create_agent +2. 不做 message bus 全量落地 +3. 不做 UI +4. 不做大规模 registry 重构 +5. 不改当前主路径的核心业务判断逻辑 + +--- + +## 4. 第 2 天计划:引入最小协作能力,完成 Phase 2 的雏形 + +第 2 天目标: + +> 在第 1 天底座稳定的基础上,引入最小的 coordinator + task decomposition + worker assignment 能力,让 Jarvis 开始具备“受控协作”的雏形。 + +--- + +### 4.1 第 2 天工作目标 + +完成以下结果: + +1. graph 能识别复杂请求并切到 collaboration mode +2. coordinator 能做最小任务拆分 +3. worker assignment 能按角色分发任务 +4. worker 输出能回收到统一 task result 结构里 +5. verifier 能对协作结果验收 + +--- + +### 4.2 第 2 天建议改动文件 + +重点还是这些: + +- `backend/app/agents/graph.py` +- `backend/app/agents/prompts.py` +- `backend/app/agents/state.py` +- `backend/app/agents/registry/*` +- `backend/app/agents/tools/*` +- `backend/tests/backend/app/agents/*` + +如有必要,可新增: + +- `backend/app/agents/tools/collaboration.py` + +--- + +### 4.3 第 2 天详细任务分解 + +#### 任务 1:增加 request_mode_selector +判断当前请求是: + +- direct mode +- collaboration mode + +判定标准先做简单版,例如: + +- 是否明显是多步骤任务 +- 是否跨多个领域 +- 是否需要多个角色协作 + +#### 任务 2:增加 coordinator prompt +coordinator 只负责: + +- 理解任务 +- 决定是否拆分 +- 输出小任务列表 + +限制: + +- 最多拆 2~4 个任务 +- 不允许无限递归拆分 + +#### 任务 3:增加最小 task decomposition +输出结构建议包含: + +- `task_id` +- `title` +- `role` +- `goal` +- `expected_evidence` + +#### 任务 4:增加 worker assignment +先不要做 agent-to-agent 自由通信。 + +由系统分配即可: + +- schedule 类给 scheduler +- retrieval 类给 retriever +- analysis 类给 analyst +- execution 类给 executor + +#### 任务 5:增加 task result 回收 +每个 worker 返回统一结果: + +- `task_id` +- `status` +- `summary` +- `evidence` +- `next_action`(可选) + +#### 任务 6:verifier 统一验收 +协作模式最终必须经过 verifier: + +- 看任务是否完成 +- 看证据是否足够 +- 看是否仍需补做某个子任务 + +#### 任务 7:补协作测试 +至少补: + +- 多任务拆分测试 +- 角色分配测试 +- task result 汇总测试 +- verifier 拒绝不完整结果测试 + +--- + +### 4.4 第 2 天验收标准 + +第 2 天结束时必须满足: + +1. graph 能区分 direct / collaboration +2. 简单请求仍然走旧路径 +3. 复杂请求可以被拆分成多个子任务 +4. 子任务可以按角色执行 +5. verifier 能拦住不完整结果 +6. 结果汇总不是单点硬编码,而是基于 task result + +--- + +### 4.5 第 2 天风险控制 + +1. 先不做真正动态 create_agent +2. 先不做无限 message channel +3. 先不做 parent/child agent tree +4. 先不做可视化 UI +5. 先保证协作模式只是“受控编排”,不是“自由蜂群” + +--- + +## 5. 2 天之后的融合状态 + +如果这 2 天按计划完成,Jarvis 会到达一个很关键的中间状态: + +### 已具备的能力 + +- direct / collaboration 双模式 +- verifier 独立角色 +- task schema +- event schema +- tool metadata +- coordinator 雏形 +- 最小任务拆分与角色分配 +- 协作结果结构化回收 + +### 还没做的部分 + +- 动态 create_agent +- parent / child agent 树 +- 内部消息线程 +- 可视化协作面板 +- 隔离执行 / worktree + +这意味着: + +> 2 天之后,Jarvis 还不是终态,但已经完成了“从静态路由走向协作运行时”的第一轮关键融合。 + +--- + +## 6. 2 天后的下一步建议 + +2 天融合完成后,下一步最合理的顺序是: + +1. 补 `Phase 3` 详细设计 +2. 设计受限 `create_agent` +3. 设计内部消息线程模型 +4. 设计 parent / child state +5. 最后再考虑可视化与隔离执行 + +--- + +## 7. 一句话结论 + +这 2 天不要想着“做完 Jarvis 2.0”,而应该明确目标: + +> 第 1 天补底座,第 2 天接编排,把最关键的融合路径打通。 + +只要这条路径打通,后面无论你更偏向 Swarm、Claude Code CLI 还是 Claw 的方向,都能继续演进。 \ No newline at end of file diff --git a/development-doc/plan/agent-update/jarvis-agents-8-day-work-checklist.md b/development-doc/plan/agent-update/jarvis-agents-8-day-work-checklist.md new file mode 100644 index 0000000..559ef70 --- /dev/null +++ b/development-doc/plan/agent-update/jarvis-agents-8-day-work-checklist.md @@ -0,0 +1,314 @@ +# Jarvis Agents 8 天工作计划(可勾选执行版) + +日期:2026-04-03 +状态:执行清单 +适用范围:基于 `phase-0` ~ `phase-5` 及现有融合方案整理 +借鉴来源:Claude Code CLI、Swarm-IDE、Claw Code、VCPToolBox + +--- + +## 使用说明 + +- 完成前使用 `- [ ]` +- 完成后改成 `- [x]` +- Day 2 默认依赖 Day 1 的核心底座完成后再推进 +- Day 3 默认依赖 Day 2 的最小协作闭环完成后再推进 +- Day 4 默认依赖 Day 3 的动态协作完成后再推进 +- Day 5 默认依赖 Day 4 的可见性 API 完成后再推进 +- Day 6 默认依赖 Day 5 的隔离执行完成后再推进 +- Day 7 默认依赖 Day 6 的成本监控完成后再推进 + +--- + +## Day 1:补底座,完成 Phase 1 最小闭环 + +Day 1 目标:先把 Jarvis 从"只有静态路由"补成"有任务结构、有事件结构、有 verifier、有工具治理信息"的可扩展底座,同时不破坏当前 direct 主路径。 + +- [x] 新增最小 `task schema` + 改造内容:新增 `backend/app/agents/schemas/task.py`,统一 `task_id`、`title`、`status`、`owner_agent_id`、`evidence`、`result_summary`,并补 `role`、`goal`、`expected_evidence`、`created_at`、`updated_at`;状态固定为 `pending`、`in_progress`、`completed`、`failed`、`blocked`。 + +- [x] 新增最小 `event schema` + 改造内容:新增 `backend/app/agents/schemas/event.py`,统一 `event_id`、`event_type`、`timestamp`、`conversation_id`、`agent_id`、`sub_commander_id`、`task_id`、`payload`、`severity`;首批事件类型覆盖 `agent.tool.start`、`agent.tool.result`、`agent.verify.started`、`agent.verify.completed`、`agent.error`。 + +- [x] 扩展 `backend/app/agents/state.py` 的运行时字段 + 改造内容:新增 `execution_mode`、`verification_status`、`verification_summary`、`verification_evidence`、`active_tasks`、`task_results`、`event_trace`、`budget_state`;默认值保持兼容 `initial_state()`,不替换现有 `pending_tasks`、`completed_tasks`、`tool_calls`。 + +- [x] 扩展 capability / tool metadata 模型 + 改造内容:在 `backend/app/agents/registry/models.py` 增加 `permission_class`、`side_effect_scope`、`supports_retry`、`idempotent`、`safe_for_parallel_use`、`requires_confirmation`;至少先固化 `read` / `write` / `external` 和 `none` / `local_state` / `db_write` / `network` 两组枚举语义。 + +- [x] 回填 builtin tools 的静态 metadata + 改造内容:在 `backend/app/agents/registry/builtins.py` 和需要的 `backend/app/agents/tools/__init__.py` 中,把 search / retrieval 类工具标成偏 `read`,create / update 类工具标成偏 `write`,外部检索类工具标成 `external`,并补充是否可重试、是否幂等、是否适合并行等标记。 + +- [x] 新增 verifier 角色定义 + 改造内容:在 `backend/app/agents/prompts.py` 增加 verifier prompt,明确 verifier 只负责验收,不负责重新规划;验收点聚焦"是否真正满足请求""是否有明确证据""是否把失败伪装成成功"。 + +- [x] 落地 verifier 模块 + 改造内容:新增 `backend/app/agents/verifier.py`,支持 `passed`、`failed`、`skipped` 三类最小结论,先服务于工具调用后的复杂输出,知识检索结果和分析型汇总输出,不接管纯闲聊路径。 + +- [x] 在 `backend/app/agents/graph.py` 接入最小 event trace 与 verifier helper + 改造内容:给 `_execute_tool_calls()` 增加 tool start / result / error 事件写入;给收尾阶段增加 verifier helper 调用;给 `_run_sub_commander()` 增加 task result 摘要写入,但暂时不重构主图为完整协作编排图。 + +- [x] 补 Phase 1 单元测试与回归测试 + 改造内容:新增 `backend/tests/backend/app/agents/test_agent_schemas.py`、`backend/tests/backend/app/agents/test_verifier.py`,并扩展 `test_graph.py`,覆盖 state 兼容性、schema 合法性、tool metadata 存在性、verifier 判定、主流程不回退。 + +- [x] 完成 Day 1 验收 + 改造内容:确认 reminder / task / search 主流程继续通过;确认 verifier 已能独立运行;确认 event schema 与 task schema 已落代码;确认 direct 仍是默认主路径;确认未引入动态 `create_agent`、message bus 全链路和 UI。 + +--- + +## Day 2:引入最小协作能力,完成 Phase 2 雏形 + +Day 2 目标:在 Day 1 底座稳定的基础上,给 Jarvis 增加"复杂请求可拆分、可分配、可回收、可验收"的最小受控协作能力,但仍然不进入自由 swarm。 + +- [x] 增加 `request_mode_selector` + 改造内容:在 `backend/app/agents/graph.py` 中增加 direct / collaboration 模式选择逻辑;简单请求继续走旧路径,只有明显多步骤、跨领域、需要多角色配合的请求才进入 collaboration mode。 + +- [x] 新增 coordinator prompt + 改造内容:在 `backend/app/agents/prompts.py` 中定义 coordinator 角色,职责限定为"判断是否拆解""输出 2~4 个清晰子任务""分配角色建议""汇总任务结果";明确禁止无限递归拆分。 + +- [x] 新增最小 task decomposition 结构 + 改造内容:基于 Day 1 的 task schema 扩展最小拆分结构,至少输出 `task_id`、`title`、`role`、`goal`、`expected_evidence`,让复杂请求能以结构化任务列表进入后续执行。 + +- [x] 增加 role -> existing agent assignment + 改造内容:先复用当前已有 top-level agent,不新增独立 worker runtime;把 schedule 类任务映射给 `schedule_planner`,retrieval 类任务映射给 `librarian`,analysis 类任务映射给 `analyst`,execution 类任务映射给 `executor`。 + +- [x] 建立统一 task result 回收结构 + 改造内容:约束每个角色统一返回 `task_id`、`status`、`summary`、`evidence`、`next_action`(可选),并把结果写回 `task_results`,避免最终结果继续依赖单点硬编码拼接。 + +- [x] 让 verifier 强制参与协作结果收尾 + 改造内容:在 collaboration mode 下,所有复杂请求返回前都必须经过 verifier;verifier 有权拒绝证据不足、结果不完整,子任务未闭环的响应。 + +- [x] 补 Phase 2 协作测试与回归测试 + 改造内容:覆盖复杂请求拆分测试、角色分配测试、task result 汇总测试、verifier 拒绝不完整结果测试,并再次确认 direct 模式原有流程不回退。 + +- [x] 完成 Day 2 验收 + 改造内容:确认 graph 已能区分 direct / collaboration;确认复杂请求可拆成 2~4 个子任务;确认每个子任务有 owner 和 evidence;确认最终答案基于 task result 汇总;确认系统仍未进入无限动态 agent 模式。 + +--- + +## Day 3:引入受限动态协作能力,完成 Phase 3 最小闭环 + +Day 3 目标:在 Day 2 已具备最小协作编排能力的基础上,让 Jarvis 获得"可追踪、可中断、可恢复、受预算约束"的动态协作 runtime,但依然不进入无限自由 swarm。 + +当前实现状态(2026-04-03):Day 3 最小闭环已基本落地。`backend/app/agents/state.py` 已补齐协作树、thread/message、interrupt/recovery、budget 相关 runtime 字段;`backend/app/agents/graph.py` 已接入受限 child agent 创建、message trace、spawn budget guardrail、interrupt / recovery 最小闭环与协作结果回收;`backend/app/agents/registry/*` 已补齐 spawn role policy 并接入 graph 校验。 + +- [x] 扩展 `backend/app/agents/state.py` 记录协作树基础字段 + 当前状态:`state.py` 已补齐 `agent_id`、`parent_agent_id`、`root_agent_id`、`collaboration_depth`、`spawned_agent_ids`、`interrupted_tasks`、`recovery_points`、`message_trace` 等 Day 3 runtime 字段,并由 `initial_state()` 完成兼容初始化。 + +- [x] 定义动态协作 budget state + 当前状态:已新增 `CollaborationBudget` schema,并在 graph 中通过 `budget_state` / `collaboration_budget_history` 落地 `max_spawn_depth`、`max_child_agents`、`max_messages_per_thread`、`max_messages_per_turn`、`max_parallel_collaborators`、`recovery_attempt_limit` 等 guardrail metadata。 + +- [x] 增加受限 `create_agent` 运行时原语 + 改造内容:新增最小动态创建能力,仅允许在 collaboration mode 下、由受限角色、在 budget 允许时创建 child agent;创建过程会记录 parent / child 关系,并在受限时转入 interrupt / recovery 回退路径。 + +- [x] 增加 agent spawn permission / role policy + 改造内容:已在 `backend/app/agents/registry/*` 中补齐角色 spawn policy,并通过 registry indexes 接入 `graph.py` 的运行时权限校验,禁止任意角色无限派生。 + +- [x] 新增最小 message / thread schema + 改造内容:已补齐 `message_id`、`thread_id`、`from_agent_id`、`to_agent_id`、`task_id`、`reply_to_message_id`、`message_type`、`content_summary`、`created_at` 等结构,支持 `task_request`、`task_update`、`handoff`、`verification_request`、`verification_feedback`、`interrupt_notice`。 + +- [x] 在 `backend/app/agents/graph.py` 接入受限动态协作分支 + 改造内容:coordinator / worker 在满足条件时可以请求受限协作;graph 已接入 child agent 创建、message thread 写入、spawn budget 校验与回收逻辑,简单请求仍优先走 direct 路径。 + +- [x] 扩展 event trace 覆盖动态协作生命周期 + 改造内容:event trace 已覆盖 `agent.created`、`agent.spawn.blocked`、`agent.message.sent`、`agent.message.received`、`agent.interrupt.requested`、`agent.interrupt.completed`、`agent.recovery.started`、`agent.recovery.completed` 等关键事件。 + +- [x] 增加 interrupt / recovery 最小闭环 + 改造内容:已支持中断协作任务、记录中断点,并基于 `task_id` / `thread_id` / budget 进行最小恢复路径记录与回退。 + +- [x] 增加 Day 3 测试与回归验证 + 改造内容:补充 parent / child tracking、spawn role policy、message thread、interrupt / recovery、动态协作事件记录等测试,并继续确认 direct 主路径不回退。 + +- [x] 完成 Day 3 验收 + 改造内容:系统已支持受限动态创建 agent,协作树和 message thread 可追踪,interrupt / recovery 可跑最小闭环,动态能力受 budget 与 role policy 约束,且仍不是自由蜂群式协作。 + +--- + +## Day 4:引入可见性 API,完成 Phase 4 可视化方向 + +Day 4 目标:在 Phase 1-3 已具备协作 runtime 的基础上,让 Jarvis 获得"可看、可查、可调试"的可见性 API,为后续复杂任务调试和执行打下基础。 + +当前实现状态(2026-04-04):Phase 1-3 最小闭环已基本落地;Day 4 后端可见性最小闭环已完成。可见性 API 直接读取 continuity snapshot 中保存的 runtime state(如 `event_trace`、`message_trace`、`active_tasks`、`task_results`、`task_hierarchy`、`verification_*`、`tool_outcomes`),并已补 focused API 测试。 + +- [x] 固化可见性数据源并增加 events 查询 API + 改造内容:已在 `backend/app/routers/agent.py` 暴露 `GET /api/agents/visibility/events`,支持按 `conversation_id` / `thread_id` / `agent_id` / `event_type` 过滤 `event_trace`,并支持分页与时间范围查询。 + +- [x] 新增协作链路拓扑查询 API + 改造内容:已新增 `GET /api/agents/visibility/topology`,基于 state 中的 `spawned_agent_ids`、`task_hierarchy`、`root_agent_id`、`active_tasks`、`task_results` 构建协作拓扑视图,返回 agent 节点、父子边与 task 摘要。 + +- [x] 新增 task 执行证据查询 API + 改造内容:已新增 `GET /api/agents/visibility/tasks/{task_id}/evidence`,基于 state 中的 `task_results`、`tool_outcomes`、`verification_*` 返回指定 task 的执行证据链。 + +- [x] 新增 message thread 查询 API + 改造内容:已新增 `GET /api/agents/visibility/threads/{thread_id}/messages`,基于 `message_trace` 返回指定 thread 内所有消息的方向、摘要、时间和关联 task。 + +- [x] 新增 verifier 结果查询 API + 改造内容:已新增 `GET /api/agents/visibility/verifier`,基于 `verification_status`、`verification_summary`、`verification_evidence` 返回当前协作会话的验收结论和证据。 + +- [x] 补 Day 4 可见性 API 测试 + 改造内容:已新增 `backend/tests/backend/app/agents/test_visibility_api.py`,覆盖 event filter / pagination、topology 构建、evidence 查询、thread 消息重建、verifier 查询、非法 datetime 参数校验等场景。 + +- [x] 完成 Day 4 验收 + 改造内容:已确认 visibility API 可查询事件、拓扑、task evidence、thread 消息与 verifier 结果;并已确认原有 reminder / task / search 主路径不在 Day 4 范围内被改坏。 + +--- + +## Day 5:升级 operator/debug surface(已完成) + +Day 5 目标:把 Day 4 的只读可见性 API 真正接到前端 Agents 页面,形成最小 operator/debug surface。 + +- [x] 接入 runtime summary API 到前端 `agentApi` + 改造内容:在 `frontend/src/api/agent.ts` 增加 runtime summary 类型与 `getRuntimeSummary()` 查询方法。 + +- [x] 在 Agents 页面展示 phase/checkpoint/verifier/isolation/cost 摘要 + 改造内容:在 `frontend/src/pages/agents/index.vue` 与 `useAgentsPage.ts` 中加入 runtime summary HUD,展示 execution mode、phase、checkpoint、verifier、isolation、token/cost、task/node 统计。 + +- [x] 让 Agents 页面使用当前会话 `conversation_id` + 改造内容:复用 `frontend/src/stores/conversation.ts` 的 `currentConversationId`,不再使用伪造的 `latest` 占位值。 + +- [x] 修复 Agents 页面关键乱码与兜底文案 + 改造内容:修复配置抽屉、状态文案、master task 文案等可见乱码,并为未选会话场景提供清晰提示。 + +- [x] 补前端运行时面板测试 + 改造内容:补 `frontend/src/pages/agents/agentsPage.test.ts`,覆盖 runtime summary 渲染、无会话提示、会话 ID 传递。 + +- [x] 完成 Day 5 验收 + 改造内容:确认 Agents 页面已经是可用的 operator/debug 入口,而不是只显示静态演示图。 + +--- + +## Day 6:推进 isolation runtime MVP(90 分主线) + +Day 6 目标:把 Day 4 的 isolation 设计从文档推进到最小运行时闭环,只做 `none / session / worktree` 三层。 + +当前实现状态(2026-04-04):Day 6 已落地 isolation runtime MVP。后端已新增 `strategy_selector.py`、`session_isolation.py`、`worktree_isolation.py`,并在 `graph.py` 中接入基于任务语义与 tool metadata 的 `none / session / worktree` 选择逻辑;隔离 metadata 会进入 state、event trace、task evidence 与 runtime summary,Agents 页面也可展示 workspace / isolation 状态。 + +- [x] 实现 IsolationStrategySelector + 改造内容:新增 `backend/app/agents/isolation/strategy_selector.py`,根据任务类型与 tool metadata 自动选择 `none / session / worktree`。 + 当前状态:已新增 `backend/app/agents/isolation/strategy_selector.py`,可基于用户请求语义、role 与 capability metadata 自动选择 `none / session / worktree`。 + +- [x] 实现 Session 隔离 + 改造内容:新增 `backend/app/agents/isolation/session_isolation.py`,支持上下文隔离、中间态隔离与 evidence 回传。 + 当前状态:已新增 `backend/app/agents/isolation/session_isolation.py`,会生成独立 session isolation metadata,并把 parent conversation / role / sub commander / capability 信息写回 runtime state。 + +- [x] 实现 Worktree 隔离 + 改造内容:新增 `backend/app/agents/isolation/worktree_isolation.py`,基于 git worktree 创建独立工作目录,回传 workspace/branch/cleanup metadata。 + 当前状态:已新增 `backend/app/agents/isolation/worktree_isolation.py`,支持基于 git worktree 创建独立工作目录,回传 branch / repo_root / cleanup_status 等 metadata;创建失败时可回退到 session isolation。 + +- [x] 集成隔离策略到 graph + 改造内容:在 `backend/app/agents/graph.py` 中接入策略选择与 evidence 输出,不做自动 merge-back。 + 当前状态:`backend/app/agents/graph.py` 已接入 isolation selector / executor,运行时会记录 `agent.isolation.selected` / `agent.isolation.fallback` 事件,并把 isolation metadata 写入 evidence 与 task result。 + +- [x] 补 Day 6 隔离测试 + 改造内容:新增隔离策略与 metadata 传播测试,覆盖 session/worktree 选择和 runtime summary 展示。 + 当前状态:已在 `backend/tests/backend/app/agents/test_graph.py` 中补充 isolation selector / worktree fallback / runtime cost 联动测试;`test_visibility_api.py` 继续覆盖 runtime summary 中的 isolation 暴露。 + +- [x] 完成 Day 6 验收 + 改造内容:确认高副作用任务可进入 worktree,低副作用任务保持 direct/session 路径,主流程无回退。 + 当前状态:高副作用、代码/仓库语义请求可进入 worktree;普通状态写入或分析路径保持 session / direct;主流程回归测试已通过 `test_graph.py`。 + +--- + +## Day 7:推进 cost governance MVP(90 分主线) + +Day 7 目标:把 token/cost 从静态估算升级为会话级可治理能力。 + +当前实现状态(2026-04-04):Day 7 已从“静态展示”推进到最小 cost governance 闭环。`graph.py` 已稳定累计 `input_tokens` / `output_tokens` / `estimated_cost`,并按 conversation / child agent 维度写入 `cost_by_agent`;budget threshold 会触发 `agent.cost.updated` / `agent.cost.warning` 事件,后端也已暴露 conversation scoped cost API。 + +- [x] 固化 runtime token 字段写入 + 改造内容:在 graph / service 层稳定记录 `input_tokens`、`output_tokens`、`estimated_cost`、`budget_warning`。 + 当前状态:`backend/app/agents/graph.py` 已在每次 LLM 响应后提取 usage metadata,并稳定写入 `input_tokens`、`output_tokens`、`estimated_cost`、`budget_warning`。 + +- [x] 集成成本累计到 conversation / child agent 维度 + 改造内容:把协作 run 的 token/cost 汇总到 conversation summary,并保留子 agent 维度的来源信息。 + 当前状态:state 中已新增 `cost_by_agent` 与 `cost_thresholds`,并可通过 `/api/agents/visibility/cost` 返回 conversation 总量与 child agent 分摊。 + +- [x] 增加 budget threshold 治理逻辑 + 改造内容:支持阈值警告、超额提示和 runtime summary 暴露。 + 当前状态:已新增默认 cost threshold 与 state override 机制,超阈值会写入 `budget_warning` 并产生 warning 事件,runtime summary 与前端 HUD 均可见。 + +- [x] 新增成本查询 API + 改造内容:在现有 visibility surface 上补 conversation scoped cost 查询,而不是另起一套孤立接口。 + 当前状态:已新增 `GET /api/agents/visibility/cost`,返回 conversation scoped total / thresholds / by_agent breakdown。 + +- [x] 补 Day 7 测试 + 改造内容:覆盖成本累计、阈值预警、runtime summary 成本字段。 + 当前状态:`backend/tests/backend/app/agents/test_graph.py` 已覆盖 runtime usage 写入与 threshold warning;`test_visibility_api.py` 已补 cost summary 返回结构断言。 + +- [x] 完成 Day 7 验收 + 改造内容:确认 cost 不再只是“可估算”,而是“可观察、可告警、可治理”。 + 当前状态:Day 7 当前已达到“可观察、可告警、可按会话/agent 查询”,仍未做到更高级的跨会话预算策略与持久化治理。 + +--- + +## Day 8:90 分收口与工具治理增强 + +Day 8 目标:把现有 runtime 补成真正可运营的 90 分闭环,并明确下一批值得升级的工具能力。 + +当前实现状态(2026-04-04):Day 8 已完成 90 分主线收口。Agents 页面已从首屏 summary 升级为可查看 recent events、topology 节点摘要、verifier evidence、cost by agent 与 tool governance 的 operator/debug surface;后端也补齐了 `/visibility/tools` 能力分层查询。 + +- [x] 增强 topology / recent events operator surface + 改造内容:在 Agents 页面增加 recent events、拓扑摘要、verifier 证据入口,形成更完整的调试视图。 + 当前状态:前端 `frontend/src/pages/agents/index.vue` 与 `useAgentsPage.ts` 已展示 recent events、topology 节点摘要与 verifier evidence 入口。 + +- [x] 对工具能力做治理分层 + 改造内容:基于 `permission_class`、`side_effect_scope`、`requires_confirmation`、`safe_for_parallel_use` 做工具分级和后续 UI 展示规划。 + 当前状态:后端已新增 `GET /api/agents/visibility/tools`,基于 `permission_class`、`side_effect_scope`、`requires_confirmation`、`safe_for_parallel_use` 返回 tool governance 视图;前端已做 operator 展示。 + +- [x] 识别值得升级/新增的工具能力 + 改造内容:优先考虑 `worktree manager`、`cost inspector`、`runtime event drilldown`、`tool policy explorer`,暂不做 TagMemo / AgentDream。 + 当前状态:operator surface 已显式暴露 `worktree_manager`、`cost_inspector`、`runtime_event_drilldown`、`tool_policy_explorer` 四项下一批 upgrade candidates。 + +- [x] 完成 90 分结项回归 + 改造内容:统一回归 direct / collaboration / runtime summary / isolation / cost 关键路径,并更新 README / daily / checklist 结论。 + 当前状态:已完成 `backend/tests/backend/app/agents/test_graph.py` 回归、`frontend/src/pages/agents/agentsPage.test.ts` 前端回归,以及不依赖 pytest tmpdir 的 visibility API 手工验证;README / checklist 已同步更新。 + +--- + +## 这 8 天明确不做 + +- 不做无限自由的动态 `create_agent` +- 不做无限层级的 parent / child agent tree +- 不做任意 agent 任意建群 / 广播 +- 不做内部消息线程的复杂长期态治理 +- 不做完整可视化调试面板(只做首屏 summary / HUD,不做完整实时 drilldown UI) +- 不做 Full Sandbox 完整实现(只做设计方案) +- 不做自由蜂群式协作 +- 不做 Persistence(数据库持久化) +- 不做 Multi-turn Memory(跨会话记忆) +- 不做 Plugin System(插件系统) +- 不做 TagMemo(仿生记忆系统) +- 不做 AgentDream(仿生梦境系统) + +--- + +## 8 天结束后的预期状态 + +- [x] 已具备 `direct` / `collaboration` 双模式入口 +- [x] 已具备 verifier 独立验收层 +- [x] 已具备 task schema / event schema / tool metadata 底座 +- [x] 已具备 coordinator 雏形、任务拆分、角色分配、结果回收 +- [x] 已具备受限动态协作 runtime 的最小实现闭环 +- [x] 当前 reminder / task / search 主路径无明显回退 +- [x] 已具备可见性 API 基础(events、topology、evidence、thread、verifier、runtime-summary) +- [x] 已具备前端 Agents operator/debug 首屏 +- [x] 已具备 isolation strategy selector + session/worktree executor 的最小运行时闭环 +- [x] 已具备 conversation / child agent 维度的 cost governance 最小闭环 +- [ ] 尚未具备 full sandbox / persistence / realtime push +- [x] 90 分主线已明确为 isolation + cost + operator surface,而不是 TagMemo / AgentDream + +--- + +## 后续可选特性(按需实施) + +| 特性 | 预估时间 | 触发条件 | +|------|---------|---------| +| AgentDream(仿生梦境系统) | 1天 | Day 7完成后 | +| Persistence(持久化) | 2-3天 | 有审计需求时 | +| Advanced UI(完整前端面板) | 3-5天 | 有前端资源时 | +| Full Sandbox(完整沙箱) | 3-5天 | 有安全需求时 | +| Plugin System(插件系统) | 2-3天 | 有社区需求时 | + +--- + +## diff --git a/development-doc/plan/agent-update/phase-0-current-state-and-target.md b/development-doc/plan/agent-update/phase-0-current-state-and-target.md new file mode 100644 index 0000000..42cb713 --- /dev/null +++ b/development-doc/plan/agent-update/phase-0-current-state-and-target.md @@ -0,0 +1,322 @@ +# Phase 0:当前现状与目标架构 + +日期:2026-04-03 +状态:已更新 + +--- + +## 1. 本阶段目的 + +本文件不涉及具体代码实施,而是用于统一背景认知,明确: + +- Jarvis 当前 agent 架构处于什么水平 +- 主要短板是什么 +- 为什么要升级 +- 升级后的目标形态是什么 +- 三个 demo 项目分别给我们什么启发 +- 关键架构决策记录(ADR) + +--- + +## 2. 当前 Jarvis 架构现状 + +当前核心代码集中在: + +- `backend/app/agents/graph.py` +- `backend/app/agents/state.py` +- `backend/app/agents/prompts.py` +- `backend/app/agents/registry/*` +- `backend/app/agents/tools/*` +- `backend/tests/backend/app/agents/test_graph.py` + +### 当前架构精确描述 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Master Node │ +│ (意图识别 + 路由分发) │ +└─────────────────┬───────────────────────────────────────────┘ + │ + ┌─────────┴─────────┬───────────┬───────────┬─────────┐ + ▼ ▼ ▼ ▼ │ + Schedule_Planner Executor Librarian Analyst │ + (规划分析) (执行操作) (知识检索) (数据分析) │ + │ │ │ │ │ + └───────────────────┴───────────┴───────────┴─────────┘ + │ + ┌───────────┴───────────┐ + │ Tool Executor │ + │ (统一工具执行层) │ + └───────────────────────┘ +``` + +### 当前优势 + +1. ✅ LangGraph状态机结构清晰 +2. ✅ 已有Master + 4个角色(sub_commander)的层级设计 +3. ✅ 支持native tool calling与fallback +4. ✅ 有continuity / clarification / pending action机制 +5. ✅ ReAct模式(思考→行动→观察循环) +6. ✅ 有一定测试基础 +7. ✅ 流式输出支持 + +### 当前短板 + +> 注意:下面列的是**相对目标架构仍然不足的部分**,不是说这些能力完全不存在。 + +| 短板 | 严重程度 | 当前真实状态 | +|------|----------|------| +| Agent间直接通信原语仍不独立 | 🔴 高 | 当前以 `message_trace` / coordinator 主路径为主,尚未独立 MessageBus | +| worker 自主再委派能力仍受限 | 🔴 高 | 已有受控 spawn,但不是自由 swarm | +| EventBus / Coordinator 仍未工程拆分 | 🟡 中 | 能力在 `graph.py` 内可运行,但模块边界仍偏集中 | +| 可观察性仍缺少实时推送与前端面板 | 🟡 中 | 已有 event/phase/checkpoint/visibility API,尚无完整实时 UI | +| 隔离执行仍未完整落地 | 🟡 中 | isolation strategy 已设计,worktree / sandbox runtime 未完成 | +| 工具治理仍可继续细化 | 🟢 低 | 已有基础 metadata 与权限语义,但尚未到完整产品化治理 | + +--- + +## 3. 为什么需要升级 + +如果 Jarvis 继续只依赖当前静态路由架构,会遇到以下问题: + +1. **复杂请求只能硬塞进单路径执行** → 可靠性低 +2. **无法优雅地拆分和回收多步任务** → 只能串行处理 +3. **执行与验收耦合** → 工具失败可能被误报为成功 +4. **系统内部过程不透明** → 后续难调试 +5. **无法支持真实的多Agent协作** → 协作能力上限低 + +--- + +## 4. 四个 Demo 的启发总结 + +### 4.1 Swarm-IDE 给我们的启发 + +**重点学习**: + +| Swarm-IDE特性 | 我们的借鉴点 | 实现位置 | +|--------------|-------------|----------| +| `create`/`send` 协作原语 | 定义Agent通信接口 | Phase 2 | +| Agent间通信作为一等能力 | MessageBus设计 | Phase 2 | +| 人类可以直接介入sub-agent | 调试/干预能力 | Phase 4 | +| Runtime event bus | 统一事件流 | Phase 3 | +| WeChat式群聊模式 | Agent群组通信 | Phase 3+ | +| 实时拓扑可视化 | 可视化调试面板 | Phase 4 | + +**核心文件参考**:`swarm-ide-chore-specs-mvp/backend/src/runtime/agent-runtime.ts` + +### 4.2 Claude Code CLI 给我们的启发 + +**重点学习**: + +| Claude Code CLI特性 | 我们的借鉴点 | 实现位置 | +|-------------------|-------------|----------| +| Coordinator-worker编排模式 | Phase 2的coordinator | Phase 2 | +| Task/team生命周期管理 | Task schema + lifecycle | Phase 1 | +| 计划-执行-验证分离 | Verifier独立角色 | Phase 1 | +| 并行执行时的隔离策略 | 预算控制 | Phase 3 | +| 工具注册与权限控制 | Tool metadata | Phase 1 | + +**核心文件参考**:`claude-code-cli-master/src/tools.ts` + +### 4.3 Claw Code 给我们的启发 + +**重点学习**: + +| Claw Code特性 | 我们的借鉴点 | 实现位置 | +|-------------|-------------|----------| +| Runtime分层 | 抽象runtime接口 | Phase 3 | +| Port manifest模式 | 子系统边界定义 | Phase 2 | +| 显式权限模型 | Tool permission_class | Phase 1 | +| Plugin/hook扩展点 | Registry扩展机制 | Phase 3 | + +**核心文件参考**:`claw-code-main/src/port_manifest.py` + +### 4.4 VCPToolBox 给我们的启发 + +**重点学习**: + +| VCPToolBox特性 | 我们的借鉴点 | 实现位置 | +|-------------|-------------|----------| +| VCP协议 | 模型无关的工具调用格式 | Phase 2 | +| 6类型插件系统 | Tool类型分类(SYNC/ASYNC/SERVICE) | Phase 1 | +| TagMemo记忆 | 仿生遗忘曲线 + 重要性权重 | Phase 5 | +| AgentDream | AI睡眠时整理记忆 | Phase 5 | +| 分布式架构 | WebSocket星型网络 | Phase 6+ | + +**VCPToolBox核心概念**: + +1. **VCP协议** — 文本格式工具调用协议 + ``` + <<<[TOOL_REQUEST]>>> + tool_name:「始」VSearch「末」, + SearchTopic:「始」AI医疗诊断「末」 + <<<[END_TOOL_REQUEST]>>> + ``` + - 使用中文分隔符「始」「末」 + - 支持 `archery:no_reply`(异步)、`river:full`(上下文注入) + +2. **TagMemo记忆系统** — 仿生RAG + - LIF神经元模型:脉冲传播机制 + - Core Tags:核心记忆1.2-1.4x权重加成 + - 遗忘曲线:不是无限存储,模拟生物遗忘 + +3. **6类型插件**: + - `static`:占位符 + - `messagePreprocessor`:消息预处理 + - `synchronous`:同步stdio + - `asynchronous`:异步回调 + - `service`:后台服务 + - `hybridservice`:混合模式 + +4. **AgentDream** — 仿生梦境系统 + - 0-7天:近期记忆,高频共振 + - 7-90天:中期记忆,弱共振 + - >90天:长期记忆,遗忘边界 + +**核心文件参考**: +- `demo/VCPToolBox-main/Plugin.js` +- `demo/VCPToolBox-main/KnowledgeBaseManager.js` +- `demo/VCPToolBox-main/modules/messageProcessor.js` + +--- + +## 5. 关键架构决策记录(ADR) + +### ADR-001: 为什么选择LangGraph? + +**决策**:继续使用LangGraph作为状态机基础 + +**理由**: +- ✅ 当前已有投入,结构清晰 +- ✅ 状态转移显式化,易于调试 +- ✅ 支持conditional edge,可做路由 +- ✅ 与LangChain生态集成良好 + +**替代方案考虑**: +- 自研状态机:维护成本高,不值得 +- Swarm-IDE模式:过度动态,缺少可预测性 +- Actor模型:引入复杂度高,收益不明显 + +### ADR-002: 为什么用sub_commander而非独立Agent? + +**决策**:保持当前Master+sub_commander模式,不做完全独立的Agent进程 + +**理由**: +- ✅ 同一进程内共享上下文,开销低 +- ✅ 状态传递简单直接 +- ✅ 与现有LangGraph集成自然 +- ✅ 更易保证事务性 + +**未来可选演进**: +- 复杂任务可用worktree隔离(Phase 4) +- 但默认仍是sub_commander模式 + +### ADR-003: 为什么先不做无限动态swarm? + +**决策**:所有动态能力必须受控,不追求"无限自由" + +**理由**: +- ❌ 无限动态 → token成本不可控 +- ❌ 无限动态 → 调试困难 +- ❌ 无限动态 → 行为不可预测 + +**设计原则**: +- 简单请求走direct路径(当前稳定模式) +- 复杂请求进collaboration模式(有预算约束) +- 动态spawn有深度/数量/角色限制 + +### ADR-004: Event Schema设计选择 + +**决策**:Phase 1只做内存trace,不做持久化 + +**理由**: +- ✅ 快速迭代,不用关心数据库schema +- ✅ 测试容易断言 +- ✅ Phase 4再做SSE/持久化 + +**未来演进**: +- Phase 4: SSE流式推送 +- Phase 5+: 可选数据库持久化 + +--- + +## 6. 目标架构 + +Jarvis 2.0 的目标是: + +> 从静态层级路由系统,升级为**受控的动态协作运行时**。 + +### 目标架构预览 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ User Request │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ┌───────────┴───────────┐ + │ Execution Mode │ + │ Router (新增) │ + └───────────┬───────────┘ + │ + ┌─────────────────┴─────────────────┐ + ▼ ▼ + ┌─────────┐ ┌──────────────┐ + │ Direct │ │ Collaboration│ + │ Mode │ │ Mode │ + │(当前路径)│ │ (Phase 2+) │ + └────┬────┘ └──────┬───────┘ + │ │ + ▼ ▼ + ┌─────────┐ ┌──────────────────┐ + │ Master │ │ Coordinator │ + │ Node │ │ (新增) │ + └────┬────┘ └────────┬─────────┘ + │ │ + ▼ ▼ + ┌─────────┐ ┌──────────────────┐ + │ Sub │◄──────────────────►│ MessageBus │ + │Commanders│ │ (新增) │ + └────┬────┘ └────────┬─────────┘ + │ │ + │ ┌────────────────────────┤ + │ │ │ + ▼ ▼ ▼ + ┌─────────┐ ┌─────────┐ ┌───────────┐ + │ Tools │ │ Verifier│ │ Workers │ + │ Executor│ │(独立角色)│ │ (动态创建) │ + └─────────┘ └─────────┘ └───────────┘ +``` + +### 升级后的基本原则 + +1. **简单请求继续走当前稳定路径(direct mode)** +2. **复杂请求才进入协作模式(collaboration mode)** +3. **执行与验证分离(Verifier独立)** +4. **动态能力必须有预算限制(spawn/message/depth budget)** +5. **工具能力必须有权限约束(permission_class)** +6. **全程要有可观察事件流(event trace)** + +--- + +## 7. 总体分阶段策略 + +| Phase | 名称 | 核心目标 | 关键产出 | +|-------|------|----------|----------| +| Phase 1 | 基础设施加固 | 补底座,打基础 | Verifier, Task/Event Schema, Tool Metadata | +| Phase 2 | 受控协作 | 结构化任务编排 | Coordinator, MessageBus, Task Decomposition | +| Phase 3 | 动态协作 | 受限动态能力 | Dynamic Spawn, Thread Model, Interrupt/Recovery | +| Phase 4 | 可视化与隔离 | 可调试,可控制 | Event Stream API, Topology UI, Isolation Strategy | +| Phase 5 | 高级特性(可选) | 产品化能力 | Sandbox, Full UI, Persistence | + +--- + +## 8. 本阶段产出要求 + +本阶段完成标准: + +- [x] 团队对 Jarvis 当前问题和目标方向达成一致 +- [x] 关键架构决策已记录 +- [x] Demo借鉴点已映射到具体Phase +- [x] 后续 phase 文档能够在这个认知基础上展开 +- [x] 不直接在本阶段进行代码改造 + diff --git a/development-doc/plan/agent-update/phase-1-safe-foundation.md b/development-doc/plan/agent-update/phase-1-safe-foundation.md new file mode 100644 index 0000000..d9d2308 --- /dev/null +++ b/development-doc/plan/agent-update/phase-1-safe-foundation.md @@ -0,0 +1,215 @@ +# Phase 1:基础设施加固阶段(Safe Foundation) + +日期:2026-04-03 +状态:已落地,进入增量加固 + +--- + +## 1. 阶段目标 + +在不破坏当前稳定业务路径的前提下,为后续多 agent 协作补齐基础设施。 + +这一阶段**不追求动态 swarm,不追求并行炫技**,重点是: + +- 打基础 +- 控风险 +- 不破坏当前功能 + +当前代码已经完成这条基础线: + +- execution mode 已存在,Direct / Collaboration 已可区分 +- verifier 收尾逻辑已存在,并参与协作结果验收 +- task schema / event schema 已进入运行时 +- graph 已具备可验证、可追踪、可持续增强的基础骨架 + +--- + +## 2. Demo借鉴映射 + +| 本Phase改动 | 借鉴来源 | 具体参考点 | +|------------|----------|-----------| +| Tool Metadata | Claude Code CLI | `tools.ts` - JSON Schema验证 + permission控制 | +| Tool 类型分类 | VCPToolBox | 6类型插件系统(SYNC/ASYNC/SERVICE) | +| Event Schema | Swarm-IDE | `event-bus.ts` - 统一事件格式 | +| Task Schema | Claude Code CLI | Task/team生命周期管理 | +| Verifier角色 | Claude Code CLI | 计划-执行-验证分离模式 | + +--- + +## 3. 当前代码中已落地的能力 + +当前 Jarvis 已经在 `backend/app/agents/graph.py`、`backend/app/agents/state.py` 与相关 schema 中完成了 Phase 1 的基础闭环。 + +### 3.1 Execution mode 与基础运行态 + +当前已具备: + +- `execution_mode` 区分 `direct` 与 `collaboration` +- `master_node()` 可根据请求复杂度切换主路径 +- `initial_state()` 已兼容这些字段的默认值 + +这意味着 Phase 1 不再只是“给 Phase 2 预留接口”,而是已经成为实际运行时入口的一部分。 + +### 3.2 Verifier 收尾基线 + +当前已具备: + +- `_verify_collaboration_results()` 负责对协作结果做统一验收 +- `verification_status` +- `verification_summary` +- `verification_evidence` + +说明执行与验收已经形成最小分离,而不是完全耦合在执行流内部。 + +### 3.3 Task Schema 已落地 + +当前 state 与协作链路已围绕结构化 task 信息运行,包括: + +- `task_id` +- `role` +- `owner_agent_id` +- `goal` +- `expected_evidence` +- `task_results` +- `task_hierarchy` +- `active_tasks` + +这已经满足 Phase 1 “统一任务结构、可供 verifier 读取”的核心目标。 + +### 3.4 Event Schema 已落地 + +当前已具备统一事件模型,并用于 `event_trace`。事件类型已覆盖: + +- tool / message / spawn / interrupt / recovery 相关事件 +- `agent.phase.changed` +- `agent.checkpoint.recorded` +- `agent.error` + +因此当前系统已经不是“没有统一 event schema”,而是已经进入“事件类型可继续扩展”的状态。 + +### 3.5 显式 phase / checkpoint 已补齐 + +在此前基础之上,本次又补齐了: + +- `current_phase` +- `phase_history` +- `current_checkpoint` +- `checkpoint_history` + +并在运行时中显式记录: + +- `phase_0_bootstrap` +- `phase_1_routing` +- `phase_2_controlled_collaboration` +- `phase_3_dynamic_collaboration` +- `phase_4_visibility_and_verification` + +以及关键 checkpoint,例如: + +- `bootstrap.initialized` +- `routing.master_entered` +- `collaboration.tasks_planning` +- `collaboration.tasks_ready` +- `collaboration.task_dispatch` +- `collaboration.task_result_collected` +- `collaboration.verification_started` +- `collaboration.completed` + +这意味着 Phase 1 的“可观察基础”已经不只是事件 trace,还进一步升级成了显式阶段模型。 + +--- + +## 4. 当前实现对应关系 + +| 设计目标 | 当前状态 | 落点 | +|------|------|------| +| Execution mode 基础分流 | 已实现 | `state.py`, `graph.py` | +| Verifier 收尾基线 | 已实现 | `_verify_collaboration_results` | +| Task Schema 基线 | 已实现 | `state.py`, `graph.py` | +| Event Schema 基线 | 已实现 | `schemas/event.py`, `event_trace` | +| Tool Metadata 基础治理 | 已实现基线 | `registry/models.py`, `registry/builtins.py` | +| Phase / Checkpoint 显式化 | 已实现 | `state.py`, `graph.py`, `agent_service.py` | + +--- + +## 5. 本阶段核心改动与后续边界 + +Phase 1 现在更准确的定义不是“准备做什么”,而是“哪些基础能力已经稳定存在,哪些还要继续工程化”。 + +### 5.1 已经完成的核心底座 + +当前已经完成: + +- execution mode 基础分流 +- verifier 收尾基线 +- task / result / evidence 结构化承载 +- event schema 与 `event_trace` +- tool metadata 基础治理 +- phase / checkpoint 显式建模 +- continuity snapshot 持久化与恢复 +- 对应自动化测试 + +### 5.2 这一阶段仍然保持的边界 + +Phase 1 仍然不等于完整多 agent 平台。它没有承诺: + +- 独立 coordinator 模块 +- 独立 message bus 模块 +- 自由 worker-to-worker 协作 +- 完整 sandbox / worktree 执行器 +- 前端实时调试面板 + +这些内容属于 Phase 2 以后逐步增强的工程层,不影响 Phase 1 已经完成验收。 + +### 5.3 测试与持久化现状 + +当前已覆盖的关键验证包括: + +- 初始 phase / checkpoint 默认值 +- phase 切换与 checkpoint 记录行为 +- collaboration flow 下的 phase / checkpoint 演进 +- fallback 回 direct 路径时的 checkpoint 恢复 +- continuity snapshot roundtrip 保留 phase / checkpoint 历史 + +因此当前不是“概念设计”,而是已经具备回归保护的运行时能力。 + +--- + +## 6. 风险点 + +| 风险 | 当前状态 / 缓解措施 | +|------|----------| +| 运行时逻辑集中在 `graph.py` | 当前可运行,但后续应继续拆分 coordinator / bus / recovery 模块 | +| visibility 已有基线但缺少实时化 | 后续通过 SSE / WebSocket / UI 面板增强 | +| 动态协作仍受严格治理 | 这是当前有意保留的边界,不是缺陷 | +| tool metadata 仍可继续细化 | 后续再补强更细粒度治理即可 | + +--- + +## 7. 当前验收结论 + +当前 Phase 1 可以按“已落地”验收: + +- [x] `state.py` 已承载 direct / collaboration 基础字段 +- [x] verifier 已参与关键结果验收 +- [x] task schema / event schema 已有代码落点 +- [x] builtin tools 已具备基础 metadata 语义 +- [x] graph 主流程已有回归测试保护 +- [x] phase / checkpoint 已进入显式运行时模型 +- [x] continuity snapshot 已可持久化这些状态 +- [ ] 尚未完成进一步工程拆分与实时平台化 + +--- + +## 8. 本阶段完成后真实结果 + +完成 Phase 1 之后,Jarvis 已经不只是“单路由 agent”。它已经具备: + +- ✅ 稳定 direct 路径 +- ✅ 复杂请求切换到 collaboration 的入口基础 +- ✅ 结构化任务与结果回收底座 +- ✅ verifier 验收机制 +- ✅ 统一事件记录与阶段追踪 +- ✅ continuity 状态延续能力 + +**结论:Phase 1 已不是草案,而是 Jarvis 当前多阶段 runtime 的已落地基础层。** diff --git a/development-doc/plan/agent-update/phase-10-advanced-orchestration.md b/development-doc/plan/agent-update/phase-10-advanced-orchestration.md new file mode 100644 index 0000000..05ed318 --- /dev/null +++ b/development-doc/plan/agent-update/phase-10-advanced-orchestration.md @@ -0,0 +1,637 @@ +# Phase 10:高级编排(Advanced Orchestration) + +日期:2026-04-04 +状态:待开始 +前置依赖:Phase 6-9(工具系统、Hook、插件、Skills) +Demo参考:claw-code-main — assistant/, cli/, structuredIO.ts, remoteIO.ts + +--- + +## 1. 阶段目标 + +实现**高级 Agent 编排能力**,包括: +- Team 多 Agent 协作 +- 远程/结构化传输 +- 高级会话管理 +- 后台任务系统 + +这是 claw-code 与 Jarvis 架构差距最大的地方,也是最复杂的功能。 + +--- + +## 2. Team 多 Agent 协作 + +### 2.1 TeamLeader + +```python +# backend/app/agents/team/leader.py + +@dataclass +class TeamMember: + """团队成员""" + agent_id: str + role: str + capabilities: list[str] + status: AgentStatus + current_task: str | None = None + +@dataclass +class TeamTask: + """团队任务""" + task_id: str + description: str + parent_id: str | None = None + assignee: str | None = None + status: TaskStatus + dependencies: list[str] = field(default_factory=list) + result: Any = None + +class TeamLeader: + """ + 团队领导者 + + 负责协调多个 Agent 的协作 + """ + + def __init__( + self, + team_id: str, + tool_registry: ToolRegistry, + hook_manager: HookManager + ): + self.team_id = team_id + self.tool_registry = tool_registry + self.hook_manager = hook_manager + + self.members: dict[str, TeamMember] = {} + self.tasks: dict[str, TeamTask] = {} + self.task_queue: asyncio.Queue = asyncio.Queue() + + async def create_team( + self, + config: TeamConfig + ) -> str: + """创建团队""" + self.team_id = config.team_id + + # 创建团队成员 + for member_config in config.members: + member = TeamMember( + agent_id=member_config.agent_id, + role=member_config.role, + capabilities=member_config.capabilities, + status=AgentStatus.IDLE + ) + self.members[member.agent_id] = member + + return self.team_id + + async def assign_task( + self, + task: TeamTask + ) -> str: + """分配任务""" + self.tasks[task.task_id] = task + + # 找到最合适的成员 + assignee = await self._find_assignee(task) + + if assignee: + task.assignee = assignee.agent_id + assignee.current_task = task.task_id + assignee.status = AgentStatus.WORKING + + return task.task_id + + async def _find_assignee( + self, + task: TeamTask + ) -> TeamMember | None: + """找到最适合执行任务的成员""" + # 过滤掉忙碌的成员 + available = [ + m for m in self.members.values() + if m.status == AgentStatus.IDLE + ] + + if not available: + return None + + # 按能力匹配 + for cap in task.required_capabilities: + matches = [m for m in available if cap in m.capabilities] + if matches: + return matches[0] + + return available[0] if available else None + + async def broadcast_task( + self, + description: str, + required_capabilities: list[str] + ) -> list[str]: + """广播任务给所有符合条件的成员""" + task_ids = [] + + for member in self.members.values(): + if member.status != AgentStatus.IDLE: + continue + + # 检查能力匹配 + if not any(cap in member.capabilities for cap in required_capabilities): + continue + + # 创建任务 + task = TeamTask( + task_id=generate_id(), + description=description, + assignee=member.agent_id, + status=TaskStatus.PENDING + ) + + await self.assign_task(task) + task_ids.append(task.task_id) + + return task_ids + + async def collect_results( + self, + task_ids: list[str], + timeout: float = 300 + ) -> dict[str, Any]: + """收集任务结果""" + results = {} + start_time = time.time() + + while len(results) < len(task_ids): + if time.time() - start_time > timeout: + break + + for task_id in task_ids: + if task_id in results: + continue + + task = self.tasks.get(task_id) + if task and task.status == TaskStatus.COMPLETED: + results[task_id] = task.result + + await asyncio.sleep(0.1) + + return results + + async def get_team_status(self) -> TeamStatus: + """获取团队状态""" + return TeamStatus( + team_id=self.team_id, + members={ + agent_id: { + "role": m.role, + "status": m.status.value, + "current_task": m.current_task + } + for agent_id, m in self.members.items() + }, + active_tasks=sum(1 for t in self.tasks.values() if t.status == TaskStatus.PENDING), + completed_tasks=sum(1 for t in self.tasks.values() if t.status == TaskStatus.COMPLETED) + ) +``` + +--- + +## 3. 远程传输层 + +### 3.1 StructuredIO + +```python +# backend/app/agents/transport/structured_io.py + +class StructuredIO: + """ + 结构化 IO + + 支持结构化的输入输出格式 + """ + + async def send_response( + self, + channel: str, + data: dict + ): + """发送结构化响应""" + message = { + "channel": channel, + "type": "structured", + "data": data, + "timestamp": datetime.now().isoformat() + } + await self.transport.send(message) + + async def send_event( + self, + event_type: str, + payload: dict + ): + """发送事件""" + message = { + "type": "event", + "event": event_type, + "payload": payload, + "timestamp": datetime.now().isoformat() + } + await self.transport.send(message) + + async def send_tool_call( + self, + tool_name: str, + arguments: dict, + call_id: str + ): + """发送工具调用""" + message = { + "type": "tool_call", + "tool": tool_name, + "args": arguments, + "call_id": call_id, + "timestamp": datetime.now().isoformat() + } + await self.transport.send(message) + + async def receive(self) -> dict: + """接收消息""" + raw = await self.transport.receive() + return self._parse(raw) + + def _parse(self, raw: bytes) -> dict: + """解析消息""" + # 支持 JSON 和流式格式 + pass +``` + +### 3.2 RemoteTransport + +```python +# backend/app/agents/transport/remote.py + +class RemoteTransport: + """ + 远程传输 + + 支持远程 Agent 通信 + """ + + def __init__( + self, + endpoint: str, + auth_token: str + ): + self.endpoint = endpoint + self.auth_token = auth_token + self.websocket: WebSocket | None = None + + async def connect(self): + """建立连接""" + self.websocket = await websockets.connect( + self.endpoint, + extra_headers={"Authorization": f"Bearer {self.auth_token}"} + ) + + async def send(self, message: dict): + """发送消息""" + if not self.websocket: + await self.connect() + + await self.websocket.send(json.dumps(message)) + + async def receive(self) -> dict: + """接收消息""" + if not self.websocket: + await self.connect() + + raw = await self.websocket.recv() + return json.loads(raw) + + async def close(self): + """关闭连接""" + if self.websocket: + await self.websocket.close() +``` + +--- + +## 4. 高级会话管理 + +### 4.1 AgentSession + +```python +# backend/app/agents/session/manager.py + +@dataclass +class SessionConfig: + """会话配置""" + session_id: str + user_id: str + parent_session_id: str | None = None + + max_rounds: int = 50 + max_tokens: int = 100000 + timeout: float = 3600 + + isolation_mode: IsolationMode = IsolationMode.NONE + + capabilities: list[str] = field(default_factory=list) + permissions: list[str] = field(default_factory=list) + +class AgentSession: + """ + Agent 会话 + + 管理会话的生命周期和状态 + """ + + def __init__( + self, + config: SessionConfig, + agent_runtime: AgentRuntime + ): + self.config = config + self.agent_runtime = agent_runtime + + self.state: SessionState = SessionState.INITIALIZING + self.round_count: int = 0 + self.token_count: int = 0 + + self.messages: list[Message] = [] + self.tool_calls: list[ToolCall] = [] + self.events: list[SessionEvent] = [] + + async def initialize(self) -> str: + """初始化会话""" + # 创建会话记录 + await self._create_session_record() + + # 初始化隔离环境 + if self.config.isolation_mode != IsolationMode.NONE: + await self._initialize_isolation() + + self.state = SessionState.ACTIVE + return self.config.session_id + + async def process_message( + self, + message: str + ) -> Response: + """处理消息""" + if self.state != SessionState.ACTIVE: + raise SessionStateError(f"Session not active: {self.state}") + + # 检查轮次限制 + self.round_count += 1 + if self.round_count > self.config.max_rounds: + raise SessionLimitError("Max rounds exceeded") + + # 处理消息 + response = await self.agent_runtime.process( + message, + context=self._get_context() + ) + + # 记录 + self.messages.append(Message(role="user", content=message)) + self.messages.append(Message(role="assistant", content=response.content)) + + # 更新 token 计数 + self.token_count += response.usage.total_tokens + + return response + + async def spawn_child_session( + self, + config: SessionConfig + ) -> "AgentSession": + """创建子会话""" + child_config = SessionConfig( + **{ + **asdict(config), + "parent_session_id": self.config.session_id + } + ) + + child_session = AgentSession( + config=child_config, + agent_runtime=self.agent_runtime + ) + + await child_session.initialize() + + return child_session + + async def get_session_summary(self) -> SessionSummary: + """获取会话摘要""" + return SessionSummary( + session_id=self.config.session_id, + user_id=self.config.user_id, + round_count=self.round_count, + token_count=self.token_count, + message_count=len(self.messages), + tool_call_count=len(self.tool_calls), + duration_seconds=(datetime.now() - self.start_time).total_seconds(), + state=self.state.value + ) + + async def persist(self): + """持久化会话""" + await self.session_service.save(SessionRecord( + session_id=self.config.session_id, + user_id=self.config.user_id, + state=self.state.value, + round_count=self.round_count, + token_count=self.token_count, + messages=self.messages, + tool_calls=self.tool_calls, + events=self.events + )) +``` + +--- + +## 5. 后台任务系统 + +### 5.1 BackgroundTaskManager + +```python +# backend/app/agents/background/manager.py + +@dataclass +class BackgroundTask: + """后台任务""" + task_id: str + description: str + agent_config: dict + schedule: str | None = None # cron expression + + status: BackgroundTaskStatus + created_at: datetime + started_at: datetime | None = None + completed_at: datetime | None = None + + result: Any = None + error: str | None = None + +class BackgroundTaskManager: + """ + 后台任务管理器 + + 管理长期运行的 Agent 任务 + """ + + def __init__( + self, + task_store: TaskStore, + agent_factory: AgentFactory + ): + self.task_store = task_store + self.agent_factory = agent_factory + + self._running_tasks: dict[str, asyncio.Task] = {} + self._scheduler = AsyncIOScheduler() + + async def submit_task( + self, + task: BackgroundTask + ) -> str: + """提交后台任务""" + # 保存任务记录 + await self.task_store.save(task) + + # 如果有定时计划,调度任务 + if task.schedule: + self._scheduler.add_job( + self._execute_task, + CronTrigger.from_crontab(task.schedule), + args=[task.task_id] + ) + else: + # 立即执行 + asyncio.create_task(self._execute_task(task.task_id)) + + return task.task_id + + async def _execute_task(self, task_id: str): + """执行任务""" + task = await self.task_store.get(task_id) + if not task: + return + + self._running_tasks[task_id] = asyncio.current_task() + + task.status = BackgroundTaskStatus.RUNNING + task.started_at = datetime.now() + await self.task_store.save(task) + + try: + # 创建 Agent + agent = await self.agent_factory.create(task.agent_config) + + # 执行 + result = await agent.run() + + task.status = BackgroundTaskStatus.COMPLETED + task.result = result + + except Exception as e: + task.status = BackgroundTaskStatus.FAILED + task.error = str(e) + + finally: + task.completed_at = datetime.now() + await self.task_store.save(task) + + if task_id in self._running_tasks: + del self._running_tasks[task_id] + + async def cancel_task(self, task_id: str) -> bool: + """取消任务""" + if task_id in self._running_tasks: + self._running_tasks[task_id].cancel() + del self._running_tasks[task_id] + + task = await self.task_store.get(task_id) + if task: + task.status = BackgroundTaskStatus.CANCELLED + await self.task_store.save(task) + + return True + + async def get_task_status(self, task_id: str) -> BackgroundTask | None: + """获取任务状态""" + return await self.task_store.get(task_id) + + async def list_tasks( + self, + user_id: str, + status: BackgroundTaskStatus | None = None + ) -> list[BackgroundTask]: + """列出任务""" + return await self.task_store.list(user_id, status) +``` + +--- + +## 6. 文件结构 + +``` +backend/app/agents/ +├── team/ # Team 协作 +│ ├── __init__.py +│ ├── leader.py # 团队领导 +│ ├── member.py # 团队成员 +│ └── task.py # 团队任务 +│ +├── transport/ # 传输层 +│ ├── __init__.py +│ ├── structured_io.py # 结构化 IO +│ ├── remote.py # 远程传输 +│ └── websocket.py # WebSocket 传输 +│ +├── session/ # 会话管理 +│ ├── __init__.py +│ ├── manager.py # 会话管理器 +│ ├── context.py # 会话上下文 +│ └── persistence.py # 会话持久化 +│ +├── background/ # 后台任务 +│ ├── __init__.py +│ ├── manager.py # 后台任务管理器 +│ ├── scheduler.py # 任务调度 +│ └── executor.py # 任务执行器 +│ +└── coordinator.py # 协调整合(现有) +``` + +--- + +## 7. 验收标准 + +| 检查点 | 标准 | +|--------|------| +| Team 创建 | 可以创建和管理 Agent 团队 | +| Team 任务分配 | 任务能正确分配给合适的成员 | +| Team 结果收集 | 能收集和聚合多成员的结果 | +| 结构化 IO | 支持结构化的输入输出格式 | +| 远程传输 | 支持远程 Agent 通信 | +| 会话管理 | 支持复杂的会话层级和状态管理 | +| 后台任务 | 支持定时和异步后台任务 | +| 子会话 | 支持从父会话创建子会话 | + +--- + +## 8. Demo 借鉴 + +| claw-code | Jarvis 对应 | +|-----------|------------| +| `src/assistant/sessionHistory.ts` | `session/manager.py` | +| `src/cli/structuredIO.ts` | `transport/structured_io.py` | +| `src/cli/remoteIO.ts` | `transport/remote.py` | +| `src/cli/transports/*` | `transport/` | +| Team/* tools | `team/leader.py` | +| Background tasks | `background/manager.py` | diff --git a/development-doc/plan/agent-update/phase-2-controlled-collaboration.md b/development-doc/plan/agent-update/phase-2-controlled-collaboration.md new file mode 100644 index 0000000..fc12547 --- /dev/null +++ b/development-doc/plan/agent-update/phase-2-controlled-collaboration.md @@ -0,0 +1,165 @@ +# Phase 2:受控协作阶段(Controlled Collaboration) + +日期:2026-04-03 +状态:已实现基线,进入增量完善 + +--- + +## 1. 阶段目标 + +让 Jarvis 具备复杂请求下的**结构化协作编排能力**,而不是所有请求都走单一路由或单 agent 顺序执行。 + +Phase 2 的核心不是“无限协作”,而是: + +- 复杂请求可切换到 `collaboration` 模式 +- 请求会被拆成有 owner 的子任务 +- 子任务结果会被结构化回收 +- 最终输出必须经过 verifier 收尾 +- Direct Mode 继续保持独立稳定 + +--- + +## 2. 当前代码中已落地的能力 + +当前 Jarvis 后端已经在 `backend/app/agents/graph.py` 中实现了 Phase 2 的最小闭环。 + +### 2.1 Mode 切换 + +已具备: + +- `master_node()` 根据请求复杂度选择 `direct` 或 `collaboration` +- `_select_request_mode()` 会结合角色数量、多步骤信号、显式协作表达进行判断 +- 简单请求保持 Direct Mode,不被协作逻辑污染 + +### 2.2 任务拆解 + +已具备: + +- `_build_collaboration_tasks()` 将复杂请求拆成 2~4 个结构化子任务 +- 每个 task 带有: + - `task_id` + - `role` + - `owner_agent_id` + - `goal` + - `expected_evidence` + - 父子任务关系 + +### 2.3 Owner 分配与执行 + +已具备: + +- 每个 task 在构建时已有明确 owner +- `_assign_agent_for_task_role()` 将 task role 映射到具体执行角色 +- `_run_collaboration_flow()` 逐个调度子任务执行 + +### 2.4 结果回收 + +已具备: + +- `_collect_task_result()` 汇总单 task 的 summary / evidence / status +- `_apply_task_result_to_state()` 将结果写回 runtime state +- `task_results` / `active_tasks` / `task_hierarchy` 形成结构化回收链路 + +### 2.5 verifier 收尾 + +已具备: + +- `_verify_collaboration_results()` 对协作结果做完整性验证 +- 若任务缺失 / evidence 缺失 / 有失败任务,则 verifier 会给出 failed verdict +- 最终结果统一写入: + - `verification_status` + - `verification_summary` + - `verification_evidence` + +--- + +## 3. 本次补强:Phase / Checkpoint 显式化 + +本次实现不是从零做 Phase 2,而是把已经存在的协作流程**显式建模**。 + +### 3.1 新增 runtime phase 字段 + +在 `backend/app/agents/state.py` 中补充: + +- `current_phase` +- `phase_history` +- `current_checkpoint` +- `checkpoint_history` + +初始化默认值: + +- `current_phase = "phase_0_bootstrap"` +- `current_checkpoint = "bootstrap.initialized"` + +### 3.2 新增 Phase 2 关键 checkpoint + +在 `_run_collaboration_flow()` 中,当前已记录: + +- `collaboration.tasks_planning` +- `collaboration.tasks_ready` +- `collaboration.task_dispatch` +- `collaboration.task_result_collected` + +这使得 Phase 2 不再只是“逻辑存在”,而是已经具备**阶段感知和关键节点回溯能力**。 + +--- + +## 4. 当前实现对应关系 + +| 设计目标 | 当前状态 | 落点 | +|------|------|------| +| Mode 切换 | 已实现 | `master_node`, `_select_request_mode` | +| 任务拆解 | 已实现 | `_build_collaboration_tasks` | +| Owner 分配 | 已实现 | task schema + role assignment | +| 结果回收 | 已实现 | `task_results`, `_collect_task_result` | +| verifier 收尾 | 已实现 | `_verify_collaboration_results` | +| Phase/Checkpoint 显式化 | 已实现 | `state.py`, `graph.py` | +| 独立 Coordinator 类 | 未单独抽出 | 当前仍内嵌在 `graph.py` | +| 独立 MessageBus 实例 | 未单独抽出 | 当前以 `message_trace` 代替 | + +--- + +## 5. 与原方案相比的调整 + +原文档把 Coordinator、MessageBus、Assigner 设计成独立模块;当前代码并未完全按该物理结构落地,而是采用了**先在 graph 内完成闭环,再逐步抽象模块**的路线。 + +因此文档需要调整为: + +### 已有基线 + +- 结构化协作逻辑已可运行 +- message trace / task trace / verifier trace 已存在 +- 不需要为了“Phase 2 完成”而强行拆出新文件 + +### 后续可选增强 + +- 将 `_run_collaboration_flow()` 中的分解 / 分配 / 汇总逻辑抽成 `coordinator.py` +- 将 message trace 提升为显式 `message_bus.py` +- 将 task decomposition 策略独立为 `collaboration/decomposer.py` + +--- + +## 6. 验收结论 + +当前 Phase 2 可以按“已实现基线”验收: + +- [x] 复杂请求会进入协作模式 +- [x] 子任务具备结构化 task schema +- [x] 每个子任务有明确 owner +- [x] 子任务结果可被统一回收 +- [x] verifier 强制参与结果收尾 +- [x] Direct Mode 保持独立 +- [x] phase / checkpoint 已可记录 +- [ ] 仍未拆成独立 coordinator/message bus 模块 + +--- + +## 7. 真实边界 + +当前 Phase 2 **已经不是草案**,但也还不是最终工程形态。 + +它目前代表的是: + +> Jarvis 已经具备受控协作运行能力,并且这条协作链路已经进入“可追踪、可验证、可持续增强”的状态。 + +仍未完成的,只是工程层面的进一步解耦,而不是能力从 0 到 1。 diff --git a/development-doc/plan/agent-update/phase-3-dynamic-collaboration.md b/development-doc/plan/agent-update/phase-3-dynamic-collaboration.md new file mode 100644 index 0000000..fb7f2bc --- /dev/null +++ b/development-doc/plan/agent-update/phase-3-dynamic-collaboration.md @@ -0,0 +1,195 @@ +# Phase 3:动态协作阶段(Dynamic Collaboration) + +日期:2026-04-03 +状态:已实现受限基线,仍有扩展空间 + +--- + +## 1. 阶段目标 + +在 Phase 2 已具备任务拆解、owner 分配、结果回收、verifier 收尾的基础上,让 Jarvis 获得**受约束的动态协作能力**。 + +这一阶段的重点不是做成无限 swarm,而是做到: + +- 协作运行时能追踪 parent / root / depth +- 动态创建行为受到权限和预算限制 +- 内部消息和事件能留下轨迹 +- 长流程可以中断和恢复 +- 所有动态行为都可观察、可审计 + +--- + +## 2. 当前代码中已落地的能力 + +当前 Jarvis 已经在 `backend/app/agents/graph.py` 中落地了 Phase 3 的核心基线。 + +### 2.1 Agent tree tracking + +当前 state 已具备: + +- `agent_id` +- `parent_agent_id` +- `root_agent_id` +- `collaboration_depth` +- `spawned_agent_ids` + +这意味着当前已经可以追踪: + +- 谁是根 agent +- 谁创建了谁 +- 当前协作深度是多少 +- 本轮生成了哪些 child agents + +### 2.2 受限动态创建 + +当前已具备: + +- `_create_child_agent()` 负责生成 child agent id +- `_spawn_permission_for_role()` 控制哪些角色可创建子 agent +- 基于 budget / role policy / depth 的限制已存在 +- 若不满足条件,会记录 `agent.spawn.blocked` + +换句话说,当前已经不是“自由创建”,而是**受治理的动态创建**。 + +### 2.3 生命周期事件 + +当前已具备事件类型: + +- `agent.created` +- `agent.spawn.blocked` +- `agent.message.sent` +- `agent.message.received` +- `agent.interrupt.requested` +- `agent.interrupt.completed` +- `agent.recovery.started` +- `agent.recovery.completed` +- `agent.task.interrupted` +- `agent.task.recovered` +- `agent.task.reassigned` +- `agent.collaboration.budget.updated` + +本次又新增: + +- `agent.phase.changed` +- `agent.checkpoint.recorded` + +说明当前运行时已经具备较完整的**协作生命周期可见性**。 + +### 2.4 Interrupt / Recovery + +当前已具备: + +- `_record_interrupt()` +- `_record_recovery()` +- `interrupted_tasks` +- `recovery_trace` +- `recovery_points` + +这使得长流程协作已经具备最小中断 / 恢复链路,而不只是一次性执行。 + +--- + +## 3. 本次补强:Phase 3 显式阶段与检查点 + +本次改动把原本分散在运行时内部的动态协作过程,进一步显式化。 + +### 3.1 Phase 切换 + +在协作 flow 中,现在会显式进入: + +- `phase_2_controlled_collaboration` +- `phase_3_dynamic_collaboration` +- `phase_4_visibility_and_verification` + +其中 `phase_3_dynamic_collaboration` 表示: + +- 子任务已进入 dispatch / spawn / child execution 阶段 +- 运行时已经处于真实的动态协作执行态 + +### 3.2 Dynamic Collaboration checkpoints + +当前已记录的关键 checkpoint 包括: + +- `collaboration.task_dispatch` +- `collaboration.task_result_collected` + +它们分别表示: + +- 某个 task 已被派发给 child agent 执行 +- 某个 task 的结果已被回收并进入统一状态 + +这使得 Phase 3 已经从“逻辑隐含”变成“运行时显式可追踪”。 + +--- + +## 4. 当前实现对应关系 + +| 设计目标 | 当前状态 | 落点 | +|------|------|------| +| Parent/root/depth 追踪 | 已实现 | `state.py` + `graph.py` | +| 受限动态创建 | 已实现 | `_create_child_agent`, `_spawn_permission_for_role` | +| 预算快照 | 已实现 | `budget_state`, `collaboration_budget_history` | +| 生命周期事件 | 已实现 | `event_trace`, `schemas/event.py` | +| 中断 / 恢复记录 | 已实现 | `_record_interrupt`, `_record_recovery` | +| phase / checkpoint 显式化 | 已实现 | `state.py`, `graph.py` | +| 独立 EventBus | 未独立落地 | 当前以 `event_trace` 代替 | +| Worker 主动发起新一轮协作 | 未完全开放 | 当前仍以受控主路径为主 | + +--- + +## 5. 与原方案相比的调整 + +原方案强调: + +- 独立 `event_bus.py` +- 独立 `dynamic/` +- 独立 `recovery/` + +而当前代码采取的是: + +- 先把动态协作基线做到 `graph.py` 内可运行 +- 再通过 event_trace / recovery_trace / budget_state 稳定对外暴露 +- 最后再决定是否做物理模块拆分 + +因此文档应调整为: + +### 已完成的能力层 + +- 动态协作并不是未来规划,而是已存在的运行时能力 +- 当前已经具备最小治理、最小动态创建、最小恢复机制 + +### 尚未完成的工程层 + +- 还没有真正的发布/订阅式 `EventBus` +- 还没有对外开放 worker 自主再委派能力 +- 还没有把 dynamic / recovery 拆成单独模块目录 + +--- + +## 6. 验收结论 + +当前 Phase 3 可以按“已实现受限基线”验收: + +- [x] parent / root / depth 可追踪 +- [x] child agent 创建行为受预算和权限约束 +- [x] spawn blocked 会留下事件证据 +- [x] 协作消息与事件链路可重建 +- [x] interrupt / recovery 有最小闭环 +- [x] phase / checkpoint 已可记录 +- [ ] 尚未形成真正的发布订阅 EventBus +- [ ] 尚未开放更自由的 worker→worker 动态协作 + +--- + +## 7. 真实边界 + +当前 Phase 3 的真实状态是: + +> Jarvis 已经具备“带约束的动态协作运行时”,但仍不是一个无限扩张、自由重组的 swarm 平台。 + +这正是当前阶段应该保持的边界: + +- 先保证治理 +- 再考虑放宽动态能力 +- 先保证 trace / verifier / budget 成熟 +- 再考虑更强自治 diff --git a/development-doc/plan/agent-update/phase-4-visibility-and-isolation.md b/development-doc/plan/agent-update/phase-4-visibility-and-isolation.md new file mode 100644 index 0000000..0b4d269 --- /dev/null +++ b/development-doc/plan/agent-update/phase-4-visibility-and-isolation.md @@ -0,0 +1,285 @@ +# Phase 4:可视化与隔离执行阶段(Visibility + Isolation) + +日期:2026-04-03 +状态:Day 4-5 最小闭环已完成(后端可见性 API + runtime summary + Agents 页面首屏接入 + 隔离设计) + +--- + +## 1. 阶段目标 + +把 Jarvis 的多 agent 系统从“内部能跑”升级为“可看、可查、可调试、可规划隔离执行”的系统。 + +本阶段分两部分: + +- **可见性**:把 runtime state 中已经存在的协作数据稳定对外暴露 +- **隔离执行设计**:先明确最小可落地方案,不在 Day 4 内强行做完整实现 + +--- + +## 2. Day 4 已落地范围 + +### 2.1 可见性数据源 + +Day 4 已明确:可见性 API 的单一数据源是 **conversation continuity snapshot 中保存的 runtime state**,而不是数据库推断或日志二次解析。 + +当前已作为读取基础的数据包括: + +- `event_trace`:关键生命周期事件 +- `message_trace`:thread / message 流向 +- `active_tasks`:当前任务清单 +- `task_results`:任务执行结果 +- `task_hierarchy`:父子任务关系 +- `verification_status` +- `verification_summary` +- `verification_evidence` +- `thread_id` +- `agent_id` +- `root_agent_id` +- `spawned_agent_ids` +- `tool_outcomes` + +### 2.2 已实现的只读 API + +当前已经在 `backend/app/routers/agent.py` 下提供以下只读接口: + +- `GET /api/agents/visibility/events` + - 支持 `conversation_id`、`agent_id`、`thread_id`、`event_type`、时间范围、分页过滤 +- `GET /api/agents/visibility/topology` + - 返回当前协作拓扑、节点、边、任务摘要、task hierarchy +- `GET /api/agents/visibility/tasks/{task_id}/evidence` + - 返回 task、task result、关联 tool outcomes、verifier 结果 +- `GET /api/agents/visibility/threads/{thread_id}/messages` + - 返回指定 thread 的消息历史 +- `GET /api/agents/visibility/verifier` + - 返回当前会话 verifier 状态、摘要、证据 +- `GET /api/agents/visibility/runtime-summary` + - 返回 execution mode、phase/checkpoint、verifier、isolation、cost、recent events 聚合摘要 + +### 2.3 已补测试 + +Day 4 已新增后端 API 测试文件: + +- `backend/tests/backend/app/agents/test_visibility_api.py` + +覆盖场景包括: + +- event filter + pagination +- topology 构建 +- task evidence 查询 +- thread message 重建 +- verifier 查询 +- 非法 datetime 参数校验 + +--- + +## 3. 当前实现边界 + +Day 4 **已经完成的是后端可见性最小闭环**,但有意不做以下内容: + +- 不做前端调试面板 UI +- 不做 SSE / WebSocket 实时推送 +- 不做独立新的 visibility 存储层 +- 不做完整 worktree / sandbox 执行实现 +- 不做自由蜂群式调度 + +因此 Day 4 的定位应是: + +> **已具备可见性查询 API 与隔离执行设计,不等于已经具备完整实时调试平台或完整隔离执行运行时。** + +--- + +## 4. 隔离执行最小方案 + +## 4.1 设计目标 + +对于更复杂、可能污染主工作目录或需要更强安全边界的任务,Day 4 先定义一个清晰的隔离分层策略,供后续 Phase 4+ 实现使用。 + +### 4.2 隔离级别 + +| 级别 | 名称 | 适用场景 | Day 4 状态 | +|------|------|----------|------------| +| L0 | 无隔离 | 普通问答、轻量检索、只读分析 | 已存在 | +| L1 | Session State 隔离 | 需要隔离上下文/记忆但不改文件 | 设计完成 | +| L2 | Worktree 隔离 | 代码修改、文件写入、需要独立目录 | **推荐主方案** | +| L3 | Sandbox Container | 高风险命令、需要更强 OS 级边界 | 仅保留扩展位 | + +### 4.3 技术选型结论 + +Day 4 的最小技术选型如下: + +#### 首选:**Git worktree 隔离** + +适用: + +- 代码生成 +- 批量重构 +- 多 agent 并行改文件 +- 需要避免污染主工作目录的执行型任务 + +原因: + +- 与当前 git 仓库工作流天然兼容 +- 可以复用已有分支 / review / merge 流程 +- 隔离成本低于容器 +- 更适合作为 Jarvis 当前阶段的最小可落地方案 + +#### 次选:**Session state 隔离** + +适用: + +- 多轮复杂分析 +- 需要隔离上下文污染 +- 只读或低风险任务 + +原因: + +- 实现成本低 +- 不依赖文件系统隔离 +- 可先于完整 worktree runtime 落地 + +#### 暂不在 Day 4 实现:**Sandbox container** + +适用: + +- 高风险 shell 命令 +- 潜在破坏性任务 +- 需要系统级资源控制 + +结论: + +- Phase 4 不做完整容器运行时 +- 仅保留接口和策略位置,后续再做 + +--- + +## 5. Session State 隔离策略 + +Session state 隔离的最小原则: + +1. 每个隔离 worker 拥有独立的 memory / turn context +2. 默认不继承完整 message history,只注入必要共享上下文 +3. 输出只回传: + - task result + - evidence + - verifier-ready summary +4. 不把中间临时推理状态直接合并回主 state + +建议最小 state 结构: + +- `isolation_mode`: `none | session | worktree | sandbox` +- `isolation_id` +- `isolation_parent_conversation_id` +- `isolation_workspace_path`(如适用) +- `isolation_metadata` + +--- + +## 6. Worktree 隔离策略 + +Worktree 隔离的最小原则: + +1. 每个执行型 worker 对应独立 worktree +2. worktree 使用独立 branch 命名 +3. 主工作目录不直接写入 +4. 结果通过 diff / changed files / task result 回收 +5. 清理策略必须可控,避免遗留脏目录 + +建议目录模式: + +- `.worktrees/jarvis///` + +建议 branch 模式: + +- `jarvis//` + +最小回收物: + +- `modified_files` +- `git_diff_summary` +- `task_result` +- `verification_evidence` + +--- + +## 7. 最小 Isolation Execution API 定义 + +Day 4 先定义接口边界,不要求马上完整实现。 + +### 7.1 任务执行请求侧 + +建议未来 runtime 接口至少支持: + +- `isolation_mode` +- `workspace_strategy` +- `allow_merge_back` +- `cleanup_policy` + +示意: + +```json +{ + "task_id": "task-123", + "goal": "refactor agent router", + "isolation_mode": "worktree", + "workspace_strategy": "ephemeral", + "allow_merge_back": false, + "cleanup_policy": "on_success" +} +``` + +### 7.2 执行结果侧 + +建议统一返回: + +```json +{ + "task_id": "task-123", + "status": "completed", + "isolation": { + "mode": "worktree", + "workspace_path": ".worktrees/jarvis/run-1/worker-2", + "branch": "jarvis/run-1/worker-2", + "cleanup_status": "pending" + }, + "evidence": [], + "summary": "Refactor completed" +} +``` + +--- + +## 8. 验收结论 + +Day 4 最小闭环完成时,当前应以以下标准为准: + +- [x] 可按条件查询 event trace +- [x] 可查询协作拓扑与任务摘要 +- [x] 可查询 task 执行证据 +- [x] 可重建 thread message 历史 +- [x] 可查询 verifier 结果 +- [x] 有 Day 4 后端 API 测试覆盖 +- [x] 有隔离执行最小设计方案 +- [ ] 不包含实时 SSE/UI +- [ ] 不包含完整 worktree/sandbox runtime 实现 + +--- + +## 9. 本阶段完成后的真实状态 + +完成 Day 4 后,Jarvis 当前具备: + +- 受限多 agent runtime 的可见性查询能力 +- 面向调试与验收的后端只读 API +- 基于 continuity snapshot 的稳定可见性数据源 +- 面向后续实现的 isolation strategy 设计 + +当前 **尚未具备**: + +- 实时事件推送平台 +- 前端可视化调试面板 +- 完整 worktree 执行编排 +- 容器级 sandbox 执行器 + +这意味着: + +> Day 4 已完成最小闭环,但后续仍可继续扩展为完整可视化 UI 和隔离执行 runtime。 diff --git a/development-doc/plan/agent-update/phase-5-advanced-features.md b/development-doc/plan/agent-update/phase-5-advanced-features.md new file mode 100644 index 0000000..9daacc5 --- /dev/null +++ b/development-doc/plan/agent-update/phase-5-advanced-features.md @@ -0,0 +1,624 @@ +# Phase 5:高级特性(Advanced Features) + +日期:2026-04-03 +状态:规划中 + +--- + +## 1. 阶段目标 + +Phase 5 包含一系列**高级特性**,在完成 Phase 1-4 后根据实际需求选择性实施。 + +这些特性不直接影响核心功能,但可以显著提升系统的**可用性、安全性和可扩展性**。 + +--- + +## 2. 特性清单 + +### 2.1 Full Sandbox 隔离 + +**目标**:实现完整的Docker级隔离 + +**Phase 4已做**:Worktree隔离 + +**Phase 5补充**: +- 完整的容器生命周期管理 +- 资源限制(CPU/内存/网络) +- 文件系统配额 +- 安全策略配置 + +```python +class FullSandbox: + """完整沙箱隔离""" + + async def execute( + self, + task: Task, + config: SandboxConfig + ) -> TaskResult: + """ + 在完整沙箱中执行 + + 特点: + - 完全隔离的网络 + - 资源限制 + - 持久化存储(可选) + - 安全策略 + """ + + # 1. 创建容器 + container = await self.client.containers.run( + image=config.image, + detach=True, + mem_limit=config.memory_limit, + cpu_period=config.cpu_period, + network_mode="isolated", # 完全隔离网络 + volumes=config.volumes, + ) + + try: + # 2. 执行任务 + result = await self._execute_in_container(container, task) + return result + + finally: + # 3. 清理 + await container.remove(force=True) +``` + +### 2.2 Persistence 层 + +**目标**:将Event/Message持久化到数据库 + +**Phase 1-3已做**:内存trace + +**Phase 5补充**: +- Event持久化存储 +- Message持久化存储 +- 支持历史查询 +- 数据导出/归档 + +```python +class EventPersistence: + """事件持久化""" + + async def save_event(self, event: Event): + """保存事件到数据库""" + ... + + async def query_events( + self, + conversation_id: str, + event_types: list[str] | None = None, + start_time: datetime | None = None, + end_time: datetime | None = None, + limit: int = 100 + ) -> list[Event]: + """查询历史事件""" + ... + +class MessagePersistence: + """消息持久化""" + + async def save_message(self, message: AgentMessage): + """保存消息到数据库""" + ... + + async def get_thread_history( + self, + thread_id: str, + limit: int = 100 + ) -> list[AgentMessage]: + """获取线程历史""" + ... +``` + +### 2.3 Multi-turn Memory + +**目标**:支持跨会话的长期记忆 + +**当前**:每个会话独立memory + +**Phase 5补充**: +- 重要信息提取 +- 跨会话上下文复用 +- 知识更新机制 +- 遗忘策略 + +```python +class MultiTurnMemory: + """跨会话记忆""" + + def extract_important_info( + self, + conversation_summary: str, + user_profile: UserProfile + ) -> list[MemoryEntry]: + """从对话中提取重要信息""" + ... + + async def get_relevant_context( + self, + current_request: str, + user_id: str + ) -> list[MemoryEntry]: + """获取与当前请求相关的记忆""" + ... + + def update_memory( + self, + user_id: str, + new_info: MemoryEntry + ): + """更新记忆""" + ... + + def decay_old_memories(self, user_id: str): + """遗忘旧记忆""" + ... +``` + +### 2.4 Cost Monitoring + +**目标**:实时监控Token成本 + +**Phase 3已有**:Budget模型 + +**Phase 5补充**: +- 实时Token计数 +- 成本估算 +- 告警机制 +- 使用报告 + +```python +class CostMonitor: + """成本监控""" + + async def track_usage( + self, + conversation_id: str, + model: str, + input_tokens: int, + output_tokens: int + ): + """跟踪使用量""" + ... + + async def estimate_cost( + self, + conversation_id: str + ) -> CostEstimate: + """估算当前会话成本""" + ... + + async def check_budget( + self, + user_id: str, + expected_tokens: int + ) -> bool: + """检查预算是否足够""" + ... + + async def send_alert( + self, + user_id: str, + threshold: float + ): + """发送告警""" + ... + +# 使用示例 +@dataclass +class CostEstimate: + total_tokens: int + estimated_cost: float + breakdown: dict[str, int] # per-model + threshold_percent: float # 相对于用户限额 +``` + +### 2.5 Advanced UI + +**目标**:完整的前端协作面板 + +**Phase 4已有**:API + +**Phase 5补充**: +- 实时协作拓扑图 +- Agent对话界面 +- 任务看板 +- 成本仪表盘 + +``` +┌────────────────────────────────────────────────────────────────────┐ +│ Jarvis 协作面板 │ +├────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────┐ ┌─────────────────────────────────────────────┐ │ +│ │ 拓扑图 │ │ 当前会话 │ │ +│ │ │ │ │ │ +│ │ [Master] │ │ User: 帮我分析这个项目... │ │ +│ │ │ │ │ │ │ +│ │ [Coord] │ │ [Coordinator]: 拆分为3个子任务 │ │ +│ │ ┌─┴─┐ │ │ - Task 1: 检索相关知识 │ │ +│ │ │ │ │ │ - Task 2: 执行分析 │ │ +│ │ [W1] [W2] │ │ - Task 3: 汇总报告 │ │ +│ │ │ │ │ │ +│ │ 点击查看详情 │ │ [Worker-1]: 正在检索... │ │ +│ └─────────────┘ └─────────────────────────────────────────────┘ │ +│ │ +│ ┌─────────────┐ ┌─────────────────────────────────────────────┐ │ +│ │ 任务列表 │ │ 成本监控 │ │ +│ │ │ │ │ │ +│ │ ☑ Task 1 │ │ Token: 12,345 / 50,000 │ │ +│ │ ◐ Task 2 │ │ 成本: $0.23 / $5.00 │ │ +│ │ ○ Task 3 │ │ [████████████████░░░░░] 24% │ │ +│ └─────────────┘ └─────────────────────────────────────────────┘ │ +│ │ +└────────────────────────────────────────────────────────────────────┘ +``` + +### 2.6 Plugin System + +**目标**:支持第三方插件扩展 + +**设计参考**:Claude Code CLI的插件系统 + +```python +class PluginSystem: + """插件系统""" + + async def load_plugin(self, plugin_path: str) -> Plugin: + """加载插件""" + ... + + async def execute_plugin( + self, + plugin_id: str, + context: dict + ) -> Any: + """执行插件""" + ... + +@dataclass +class Plugin: + """插件定义""" + plugin_id: str + name: str + version: str + capabilities: list[str] # 提供的工具/能力 + hooks: list[str] # 生命周期钩子 + + async def execute(self, context: dict) -> Any: + ... + +@dataclass +class PluginManifest: + """插件清单""" + tools: list[ToolManifest] + commands: list[CommandManifest] + hooks: list[str] +``` + +### 2.7 TagMemo — 仿生记忆系统 + +**目标**:实现基于遗忘曲线的智能记忆系统 + +**设计参考**:VCPToolBox的TagMemo V6/V7 RAG系统 + +**核心概念**: + +1. **LIF神经元模型** — 脉冲传播机制 + - 记忆不是静态存储,而是动态激活 + - 重要记忆获得更高的激活频率 + +2. **Core Tags vs Normal Tags** — 核心记忆 + - Core Tags:获得1.2-1.4x权重加成 + - 核心记忆有虚拟召回能力 + +3. **遗忘曲线** — 不是无限存储 + - 模拟生物遗忘,不是简单删除 + - 基于重要性动态计算衰减率 + +```python +class MemoryImportance(str, Enum): + """记忆重要性等级""" + CORE = "core" # 核心记忆,1.2-1.4x权重 + HIGH = "high" # 高重要性 + MEDIUM = "medium" # 中等重要性 + LOW = "low" # 低重要性,会自然遗忘 + +@dataclass +class TagMemoEntry: + """TagMemo记忆条目""" + entry_id: str + content: str + + # VCPToolBox借鉴 + importance: MemoryImportance = MemoryImportance.MEDIUM + decay_rate: float = 0.1 # 遗忘率 + last_activated: datetime = field(default_factory=datetime.now) + activation_count: int = 0 # 激活次数 + + # EPA模块(可选) + logic_depth: int = 0 # 逻辑深度 + resonance_score: float = 0.0 # 共振分数 + +class TagMemoMemory: + """ + 仿生记忆系统 + + 特点: + - 遗忘曲线模拟 + - 重要性权重 + - 动态激活 + """ + + async def add_memory( + self, + content: str, + importance: MemoryImportance = MemoryImportance.MEDIUM, + tags: list[str] | None = None + ) -> TagMemoEntry: + """添加记忆""" + entry = TagMemoEntry( + entry_id=generate_id(), + content=content, + importance=importance, + decay_rate=self._calculate_decay_rate(importance), + tags=tags or [] + ) + await self._storage.save(entry) + return entry + + def should_retain(self, entry: TagMemoEntry, days_elapsed: int) -> bool: + """ + 判断记忆是否应该保留 + + 基于动态Beta公式: + β = σ(L·log(1+R) - S·noise_penalty) + """ + if entry.importance == MemoryImportance.CORE: + return True # 核心记忆永远保留 + + # 遗忘概率 = 基础衰减 × 时间 × 噪声惩罚 + retention_prob = math.exp( + -entry.decay_rate * days_elapsed * self._noise_factor + ) + return random.random() < retention_prob + + async def get_relevant_memories( + self, + query: str, + limit: int = 5 + ) -> list[TagMemoEntry]: + """获取相关记忆(带权重)""" + candidates = await self._vector_search(query, limit=limit * 2) + + # 按重要性权重排序 + weighted = [] + for entry in candidates: + weight = self._calculate_weight(entry) + weighted.append((entry, weight)) + + weighted.sort(key=lambda x: x[1], reverse=True) + return [e for e, _ in weighted[:limit]] + + def _calculate_weight(self, entry: TagMemoEntry) -> float: + """计算记忆权重""" + base = 1.0 + + # 重要性权重 + if entry.importance == MemoryImportance.CORE: + base *= 1.3 + elif entry.importance == MemoryImportance.HIGH: + base *= 1.1 + + # 激活频率奖励 + base *= (1 + math.log(1 + entry.activation_count)) + + # 时间衰减 + days = (datetime.now() - entry.last_activated).days + base *= math.exp(-0.01 * days) + + return base +``` + +### 2.8 AgentDream — 仿生梦境系统 + +**目标**:AI在"睡眠"时自动整理和巩固记忆 + +**设计参考**:VCPToolBox的AgentDream bijective morphic system + +**三层时间记忆涟漪**: + +| 时间层 | 范围 | 特点 | +|--------|------|------| +| 短期记忆 | 0-7天 | 高频共振,快速激活 | +| 中期记忆 | 7-90天 | 弱共振,需要触发 | +| 长期记忆 | >90天 | 遗忘边界,需要特殊唤醒 | + +```python +class DreamLayer(str, Enum): + """梦境记忆层""" + SHORT_TERM = "short_term" # 0-7天 + MID_TERM = "mid_term" # 7-90天 + LONG_TERM = "long_term" # >90天 + +@dataclass +class DreamMemory: + """梦境记忆结构""" + layer: DreamLayer + resonance_bridges: list[str] = field(default_factory=list) # 共振桥接 + consolidation_level: float = 0.0 # 巩固程度 0-1 + +class AgentDreamEngine: + """ + 仿生梦境引擎 + + 功能: + - 定时触发记忆整理 + - 跨层共振发现 + - 遗忘边界管理 + """ + + async def dream(self, user_id: str) -> DreamReport: + """ + 执行梦境整理 + + 流程: + 1. 获取近期记忆(0-7天) + 2. 与中期记忆建立共振桥 + 3. 评估哪些记忆应该升级/遗忘 + 4. 生成梦境叙事报告 + """ + short_term = await self._get_memories( + user_id, + layer=DreamLayer.SHORT_TERM + ) + mid_term = await self._get_memories( + user_id, + layer=DreamLayer.MID_TERM + ) + + # 发现共振 + bridges = await self._discover_resonance_bridges( + short_term, mid_term + ) + + # 评估记忆 + to_promote = [] # 升级到中期 + to_forget = [] # 标记遗忘 + to_consolidate = [] # 巩固 + + for memory in short_term: + if memory.activation_count > 10: + to_promote.append(memory) + elif memory.activation_count < 2: + to_forget.append(memory) + else: + to_consolidate.append(memory) + + # 执行整理 + await self._promote_memories(to_promote) + await self._apply_forgetting(to_forget) + await self._consolidate(to_consolidate, bridges) + + # 生成梦境报告 + return await self._generate_dream_narrative( + promoted=to_promote, + forgotten=to_forget, + bridges=bridges + ) + + async def _discover_resonance_bridges( + self, + short_term: list[TagMemoEntry], + mid_term: list[TagMemoEntry] + ) -> list[tuple[TagMemoEntry, TagMemoEntry, float]]: + """发现跨层共振桥接""" + bridges = [] + + for s in short_term: + for m in mid_term: + similarity = await self._calculate_resonance(s, m) + if similarity > 0.7: + bridges.append((s, m, similarity)) + + return bridges + + async def _generate_dream_narrative( + self, + promoted: list, + forgotten: list, + bridges: list + ) -> DreamReport: + """生成第一人称梦境叙事""" + narrative = f""" + 【梦境记录】 + + 今夜整理了{len(promoted)}段重要记忆,它们已经被巩固: + {', '.join(m.content for m in promoted[:3])} + + 发现了{len(bridges)}条记忆共振: + {self._describe_bridges(bridges)} + + {len(forgotten)}段记忆正在消散... + """ + + return DreamReport( + narrative=narrative, + promoted_count=len(promoted), + forgotten_count=len(forgotten), + bridges_count=len(bridges) + ) +``` + +**触发机制**: + +```python +# 定时任务:每天凌晨3点执行梦境整理 +@scheduler.scheduled(cron="0 3 * * *") +async def scheduled_agent_dream(): + """AI睡眠时整理记忆""" + for user_id in await get_active_users(): + try: + report = await agent_dream_engine.dream(user_id) + logger.info(f"Dream complete for {user_id}: {report.summary}") + except Exception as e: + logger.error(f"Dream failed for {user_id}: {e}") +``` + +--- + +## 3. 实施优先级 + +| 特性 | 优先级 | 依赖 | 建议实施时机 | +|------|--------|------|--------------| +| Cost Monitoring | 🔴 高 | Phase 3 | 正式上线前 | +| TagMemo | 🟡 中 | Phase 2 | 用户反馈需要更好记忆时 | +| AgentDream | 🟢 低 | Phase 5+TagMemo | 凌晨调度资源时 | +| Persistence | 🟡 中 | Phase 1-3 | 有审计需求时 | +| Multi-turn Memory | 🟡 中 | Phase 1-2 | 用户反馈需要时 | +| Advanced UI | 🟡 中 | Phase 4 | 有前端资源时 | +| Full Sandbox | 🟢 低 | Phase 4 | 有安全需求时 | +| Plugin System | 🟢 低 | Phase 1 | 有社区需求时 | + +--- + +## 4. 风险点 + +| 风险 | 缓解措施 | +|------|----------| +| 功能蔓延 | 严格控制每个特性的scope | +| 性能影响 | Persistence要考虑异步和索引优化 | +| 成本增加 | Full Sandbox资源限制要明确 | + +--- + +## 5. 验收标准 + +| 特性 | 验收标准 | +|------|----------| +| Cost Monitoring | 能实时显示Token使用量和估算成本 | +| Persistence | 事件和消息可持久化存储和查询 | +| Multi-turn Memory | 跨会话可复用关键信息 | +| Advanced UI | 有可用的前端协作面板 | +| Full Sandbox | 容器隔离完整,资源限制生效 | +| Plugin System | 插件可加载和执行 | + +--- + +## 6. 本阶段完成后预期结果 + +完成后,Jarvis 将具备: + +- ✅ 完整的成本监控能力 +- ✅ 仿生记忆系统(TagMemo) +- ✅ AI梦境整理(AgentDream) +- ✅ 历史数据持久化 +- ✅ 跨会话的智能记忆 +- ✅ 完整的协作可视化UI +- ✅ 高级隔离执行环境 +- ✅ 可扩展的插件系统 + +**Phase 5 为可选特性,根据实际需求选择性实施。** diff --git a/development-doc/plan/agent-update/phase-6-10-checklist.md b/development-doc/plan/agent-update/phase-6-10-checklist.md new file mode 100644 index 0000000..2bce904 --- /dev/null +++ b/development-doc/plan/agent-update/phase-6-10-checklist.md @@ -0,0 +1,339 @@ +# Jarvis Agent Phase 6-10 Checklist + +> 开发时对照勾选,完成后打 [x] + +--- + +## Phase 6:工具系统重构 + +**工作量**:6-10 周 + +### 6.1 基础设施 + +- [x] 创建 `backend/app/agents/tools/registry.py` — ToolRegistry 类 +- [x] 创建 `backend/app/agents/tools/manifest.py` — ToolManifest 数据类 +- [x] 创建 `backend/app/agents/tools/base.py` — BaseTool 基础类 +- [x] 迁移 task.py 到注册表 +- [x] 迁移 schedule.py 到注册表 +- [x] 迁移 search.py 到注册表 +- [x] 迁移 forum.py 到注册表 +- [x] 迁移 time_reasoning.py 到注册表 +- [x] 实现向后兼容层 + +### 6.2 Hook 系统 + +- [x] 创建 `backend/app/agents/tools/hooks/types.py` — HookType, HookAction, HookContext, HookResult +- [x] 创建 `backend/app/agents/tools/hooks/manager.py` — HookManager 类 +- [x] 创建 `backend/app/agents/tools/hook_executor.py` — HookExecutor 类 + +### 6.3 流式执行 + +- [x] 创建 `backend/app/agents/tools/streaming_executor.py` — StreamingToolExecutor +- [x] 为流式工具添加 `is_streaming` 标志 +- [x] 更新 AgentService 集成流式执行 + +### 6.4 新工具集 + +- [x] 创建 `backend/app/agents/tools/builtins/file_tools.py` — GlobTool +- [x] 创建 `backend/app/agents/tools/builtins/file_tools.py` — GrepTool +- [x] 创建 `backend/app/agents/tools/builtins/file_tools.py` — EditTool +- [x] 创建 `backend/app/agents/tools/builtins/file_tools.py` — LintTool +- [x] 创建 `backend/app/agents/tools/builtins/system_tools.py` — BashTool +- [x] 创建 `backend/app/agents/tools/builtins/system_tools.py` — PowerShellTool +- [x] 创建 `backend/app/agents/tools/builtins/system_tools.py` — ScheduleCronTool +- [x] 创建 `backend/app/agents/tools/builtins/dev_tools.py` — LSPTool +- [x] 创建 `backend/app/agents/tools/builtins/dev_tools.py` — GitTool +- [x] 创建 `backend/app/agents/tools/builtins/collaboration_tools.py` — TeamAgentTool +- [x] 创建 `backend/app/agents/tools/builtins/collaboration_tools.py` — TaskBroadcastTool + +### 6.5 测试 + +- [x] 单元测试: registry +- [x] 单元测试: hook +- [x] 单元测试: streaming executor +- [x] 集成测试: 工具注册到执行完整流程 +- [x] 回归测试: Sub-Commander 不受影响 +- [ ] 性能测试: 工具调用延迟 < 100ms + +### Phase 6 验收 + +- [x] 所有现有工具已在 Registry 中注册 +- [x] PreTool/PostTool Hook 能正确拦截 +- [x] 流式工具可流式返回 +- [x] 向后兼容: 现有 Sub-Commander 工具调用不受影响 + +--- + +## Phase 7:Hook 拦截层 + +**前置依赖**:Phase 6.2 +**工作量**:3-4 周 + +### 7.1 Hook 类型定义 + +- [x] 验证 `hooks/types.py` — HookType, HookAction, HookContext, HookResult + +### 7.2 内置 Hook + +- [x] 创建 `backend/app/agents/tools/hooks/builtins/audit_log.py` — AuditLogHook +- [x] 创建 `backend/app/agents/tools/hooks/builtins/dangerous_confirmation.py` — DangerousConfirmationHook +- [x] 创建 `backend/app/agents/tools/hooks/builtins/security_scan.py` — SecurityScanHook + +### 7.3 Hook 配置 + +- [x] HookManager.load_config() +- [x] HookManager.execute_pre_hooks() +- [x] HookManager.execute_post_hooks() +- [x] HookManager.execute_error_hook() +- [x] 配置持久化 + +### 7.4 自定义 Hook + +- [ ] 创建 `hooks/custom/loader.py` — 自定义 Hook 加载器 + +### 7.5 API + +- [ ] POST `/api/hooks/config` — 更新 Hook 配置 +- [ ] GET `/api/hooks/config` — 获取 Hook 配置 +- [ ] GET `/api/hooks/available` — 列出可用 Hook + +### 7.6 测试 + +- [ ] 测试: 危险操作被 Pre Hook 拦截 +- [ ] 测试: 敏感信息被 Post Hook 脱敏 +- [ ] 测试: 异常被 Error Hook 记录 +- [ ] 回归测试: 不配置 Hook 时系统正常运行 + +### Phase 7 验收 + +- [x] 危险操作被 Pre Hook 正确拦截 +- [x] 结果中的敏感信息被 Post Hook 正确脱敏 +- [x] Hook 配置可保存和加载 +- [ ] Hook 执行开销 < 10ms + +--- + +## Phase 8:插件生态 + +**前置依赖**:Phase 6, Phase 7 +**工作量**:4-5 周 + +### 8.1 插件结构 + +- [x] 创建 `backend/app/agents/plugins/manifest.py` — PluginManifest +- [ ] 定义 `plugins/manifest.json` schema +- [ ] 验证插件清单格式 + +### 8.2 PluginManager + +- [x] 创建 `backend/app/agents/plugins/manager.py` — PluginManager +- [x] 实现 PluginManager.install() +- [x] 实现 PluginManager.uninstall() +- [x] 实现 PluginManager.enable() +- [x] 实现 PluginManager.disable() +- [x] 实现 PluginManager.reload() +- [x] 实现 PluginManager.list_plugins() + +### 8.3 插件隔离 + +- [x] 创建 `backend/app/agents/plugins/sandbox.py` — PluginSandbox +- [x] 实现模块加载隔离 +- [x] 实现文件系统权限控制 +- [x] 实现网络权限控制 + +### 8.4 插件市场 + +- [ ] 创建 `backend/app/services/plugin_marketplace.py` — PluginMarketplace +- [ ] 实现 search() +- [ ] 实现 get_plugin() +- [ ] 实现 download_plugin() + +### 8.5 内置插件 + +- [ ] 创建 `plugins/builtins/code_helper/` — lint, format, explain_code +- [ ] 创建 `plugins/builtins/git_helper/` — git_status, git_log, git_diff +- [ ] 创建 `plugins/builtins/web_helper/` — fetch_url, parse_html +- [ ] 创建 `plugins/builtins/file_organizer/` — organize_files, cleanup_duplicates + +### 8.6 API + +- [ ] GET `/api/plugins` — 列出插件 +- [ ] POST `/api/plugins/install` — 安装插件 +- [ ] POST `/api/plugins/{id}/enable` — 启用插件 +- [ ] POST `/api/plugins/{id}/disable` — 禁用插件 +- [ ] DELETE `/api/plugins/{id}` — 卸载插件 +- [ ] GET `/api/marketplace/plugins` — 搜索市场 + +### 8.7 测试 + +- [ ] 测试: 插件安装/启用/禁用/卸载 +- [ ] 测试: 插件隔离有效 +- [ ] 测试: 市场搜索和下载 + +### Phase 8 验收 + +- [x] 可以从目录安装插件 +- [x] 插件的工具和 Hook 正确注册 +- [x] 插件的工具和 Hook 正确注销 +- [x] 插件无法访问未授权资源 +- [ ] 插件加载时间 < 1s + +--- + +## Phase 9:Skills 注册表 + +**前置依赖**:Phase 6 +**工作量**:3-4 周 + +### 9.1 SkillRegistry 增强 + +- [x] 增强 `backend/app/agents/skills/registry.py` — SkillRegistry +- [x] 创建 `backend/app/agents/skills/metadata.py` — SkillMetadata +- [x] 实现 load_all() +- [x] 实现 get_skill() +- [x] 实现 search() +- [x] 实现 get_skill_context() + +### 9.2 Skills 加载器 + +- [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` + +### 9.3 MCP Skill Builder + +- [x] 创建 `backend/app/agents/skills/mcp_builder.py` — MCPSkillBuilder +- [x] 实现 discover_skills_from_mcp() +- [x] 实现 _tool_to_skill() +- [x] 实现 _group_to_skill() + +### 9.4 内置 Skills + +- [ ] 创建 `backend/app/agents/skills/bundled.py` — BUNDLED_SKILLS +- [ ] 实现 code-analysis skill +- [ ] 实现 git-helper skill +- [ ] 实现 web-research skill +- [ ] 实现 file-management skill +- [ ] 实现 task-planning skill + +### 9.5 Agent 集成 + +- [ ] AgentService.build_skill_context() +- [ ] Skill 上下文注入 Agent prompt +- [ ] Skill 触发检测 + +### 9.6 API + +- [ ] GET `/api/skills` — 列出 Skills +- [ ] GET `/api/skills/search` — 搜索 Skills +- [ ] GET `/api/skills/{name}` — 获取 Skill 详情 + +### 9.7 测试 + +- [ ] 测试: 本地 Skills 加载 +- [ ] 测试: MCP Skills 发现 +- [ ] 测试: Skill 上下文注入 + +### Phase 9 验收 + +- [x] 能加载 local_skills_dir 下的所有 SKILL.md +- [x] 能从 MCP 服务器发现和加载 Skills +- [ ] 内置 Skills 默认加载 +- [ ] Skill 内容正确注入 Agent prompt + +--- + +## Phase 10:高级编排 + +**前置依赖**:Phase 6-9 +**工作量**:5-6 周 + +### 10.1 Team 多 Agent 协作 + +- [x] 创建 `backend/app/agents/team/leader.py` — TeamLeader +- [ ] 创建 `backend/app/agents/team/member.py` — TeamMember +- [ ] 创建 `backend/app/agents/team/task.py` — TeamTask +- [x] 实现 create_team() +- [x] 实现 assign_task() +- [x] 实现 broadcast_task() +- [x] 实现 collect_results() +- [x] 实现 get_team_status() + +### 10.2 远程传输层 + +- [ ] 创建 `backend/app/agents/transport/structured_io.py` — StructuredIO +- [x] 创建 `backend/app/agents/transport/remote.py` — RemoteTransport +- [x] 实现 send_response() +- [x] 实现 send_event() +- [x] 实现 send_tool_call() +- [ ] 实现 WebSocket 连接管理 + +### 10.3 高级会话管理 + +- [ ] 创建 `backend/app/agents/session/manager.py` — AgentSession +- [ ] 创建 `backend/app/agents/session/context.py` — SessionContext +- [ ] 创建 `backend/app/agents/session/persistence.py` — SessionPersistence +- [ ] 实现 initialize() +- [ ] 实现 process_message() +- [ ] 实现 spawn_child_session() +- [ ] 实现 get_session_summary() +- [ ] 实现 persist() + +### 10.4 后台任务系统 + +- [x] 创建 `backend/app/agents/background/manager.py` — BackgroundTaskManager +- [ ] 创建 `backend/app/agents/background/scheduler.py` +- [ ] 创建 `backend/app/agents/background/executor.py` +- [x] 实现 submit_task() +- [x] 实现 cancel_task() +- [x] 实现 get_task_status() +- [x] 实现 list_tasks() + +### 10.5 协调整合 + +- [ ] 创建/修改 `backend/app/agents/coordinator.py` +- [ ] Team 协作与现有 graph 集成 +- [ ] 远程传输与现有 service 集成 + +### 10.6 测试 + +- [ ] 测试: Team 创建/分配/收集/状态 +- [ ] 测试: 会话层级/持久化/子会话 +- [ ] 测试: 后台任务提交/调度/取消 + +### Phase 10 验收 + +- [x] 可以创建和管理 Agent 团队 +- [x] 任务能正确分配给合适的成员 +- [x] 能收集和聚合多成员的结果 +- [ ] 支持结构化的输入输出格式 +- [x] 支持远程 Agent 通信 +- [ ] 支持复杂的会话层级和状态管理 +- [x] 支持定时和异步后台任务 +- [ ] 支持从父会话创建子会话 + +--- + +## 总验收 + +### 向后兼容 + +- [x] 现有 Sub-Commander 不受影响 +- [x] 现有 API 不受影响 +- [ ] 现有数据库 schema 不需修改 + +### 性能 + +- [ ] 工具调用延迟 < 100ms +- [ ] Hook 执行开销 < 10ms +- [ ] 插件加载时间 < 1s + +### 安全 + +- [x] 插件隔离有效 +- [x] Hook 可以拒绝危险操作 +- [x] 敏感信息正确脱敏 + +--- + +*最后更新:2026-04-04* diff --git a/development-doc/plan/agent-update/phase-6-tool-system-refactoring.md b/development-doc/plan/agent-update/phase-6-tool-system-refactoring.md new file mode 100644 index 0000000..fa2a673 --- /dev/null +++ b/development-doc/plan/agent-update/phase-6-tool-system-refactoring.md @@ -0,0 +1,322 @@ +# Phase 6:工具系统重构(Tool System Refactoring) + +日期:2026-04-04 +状态:待开始 +Demo参考:claw-code-main — tools/, StreamingToolExecutor, toolOrchestration + +--- + +## 1. 阶段目标 + +建立**分层工具执行架构**,从现有的扁平工具调用进化为具有注册表、编排层、Hook 拦截能力的工具系统。 + +**与现有系统的区别**: + +| 现有 | 目标 | +|------|------| +| Sub-Commander 直接调用 Tools | ToolRegistry → ToolOrchestration → HookExecutor → ToolExecutor → Tools | +| 工具集固定 | 工具注册表支持动态注册 | +| 无流式工具执行 | StreamingToolExecutor 支持流式 | +| 无工具拦截 | PreTool/PostTool Hook 机制 | + +--- + +## 2. 架构设计 + +### 2.1 新的工具执行架构 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Agent / Sub-Commander │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ ToolOrchestration Layer │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │ +│ │ ToolRegistry│ │HookExecutor │ │ StreamingToolExecutor │ │ +│ │ (注册表) │ │ (拦截层) │ │ (流式执行器) │ │ +│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ ToolExecutor │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ Built-in │ │ External │ │ MCP │ │ Plugin │ │ +│ │ Tools │ │ Tools │ │ Tools │ │ Tools │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 2.2 核心组件 + +#### ToolRegistry(工具注册表) + +```python +# backend/app/agents/tools/registry.py + +@dataclass +class ToolManifest: + """工具元数据""" + name: str + description: str + category: ToolCategory + parameters: dict # JSON Schema + return_schema: dict + permission_class: PermissionClass # READ/WRITE/EXTERNAL + side_effect_scope: SideEffectScope # NONE/LOCAL_STATE/DB_WRITE/NETWORK + requires_confirmation: bool + is_streaming: bool = False + tags: list[str] = field(default_factory=list) + +class ToolRegistry: + """工具注册表""" + + def __init__(self): + self._tools: dict[str, ToolManifest] = {} + self._executors: dict[str, Callable] = {} + self._hooks: dict[str, list[HookConfig]] = defaultdict(list) + + def register( + self, + manifest: ToolManifest, + executor: Callable, + hooks: list[HookConfig] | None = None + ): + """注册工具""" + self._tools[manifest.name] = manifest + self._executors[manifest.name] = executor + if hooks: + for hook in hooks: + self._hooks[manifest.name].append(hook) + + def get(self, name: str) -> ToolManifest | None: + """获取工具元数据""" + return self._tools.get(name) + + def get_executor(self, name: str) -> Callable | None: + """获取工具执行器""" + return self._executors.get(name) + + def list_by_category(self, category: ToolCategory) -> list[ToolManifest]: + """按类别列出工具""" + return [t for t in self._tools.values() if t.category == category] + + def list_all(self) -> list[ToolManifest]: + """列出所有工具""" + return list(self._tools.values()) +``` + +#### HookExecutor(拦截执行器) + +```python +# backend/app/agents/tools/hook_executor.py + +@dataclass +class HookConfig: + """Hook 配置""" + name: str + hook_type: HookType # PRE_TOOL_USE / POST_TOOL_USE / TOOL_ERROR / TOOL_SKIP + handler: Callable + filter_names: list[str] | None = None # 只对特定工具生效,None 表示全部 + +class HookExecutor: + """工具 Hook 执行器""" + + async def pre_execute(self, tool_name: str, arguments: dict) -> HookResult: + """PreToolUse - 工具执行前拦截""" + hooks = self._get_hooks(tool_name, HookType.PRE_TOOL_USE) + + ctx = HookContext( + tool_name=tool_name, + arguments=arguments, + phase=HookPhase.PRE + ) + + for hook in hooks: + result = await hook.handler(ctx) + if result.action == HookAction.DENY: + return HookResult( + action=HookAction.DENY, + message=result.message, + modified_args=None + ) + elif result.action == HookAction.MODIFY: + ctx.arguments = result.modified_args + + return HookResult(action=HookAction.ALLOW, modified_args=ctx.arguments) + + async def post_execute( + self, + tool_name: str, + arguments: dict, + result: Any + ) -> HookResult: + """PostToolUse - 工具执行后拦截""" + hooks = self._get_hooks(tool_name, HookType.POST_TOOL_USE) + + ctx = HookContext( + tool_name=tool_name, + arguments=arguments, + result=result, + phase=HookPhase.POST + ) + + for hook in hooks: + result = await hook.handler(ctx) + if result.action == HookAction.MODIFY_RESULT: + ctx.result = result.modified_result + + return HookResult(action=HookAction.CONTINUE, modified_result=ctx.result) +``` + +#### StreamingToolExecutor(流式工具执行器) + +```python +# backend/app/agents/tools/streaming_executor.py + +class StreamingToolExecutor: + """流式工具执行器""" + + async def execute_streaming( + self, + tool_name: str, + arguments: dict, + callback: Callable[[dict], Awaitable[None]] + ) -> Any: + """执行流式工具""" + manifest = self.registry.get(tool_name) + if not manifest.is_streaming: + raise ValueError(f"Tool {tool_name} does not support streaming") + + executor = self.registry.get_executor(tool_name) + + # 使用 aiterator 进行流式执行 + async for chunk in executor(**arguments): + event = {"type": "chunk", "data": chunk} + await callback(event) + yield chunk + + final_event = {"type": "done", "tool": tool_name} + await callback(final_event) +``` + +--- + +## 3. 新增工具集 + +### 3.1 文件操作工具 + +| 工具 | 描述 | 权限级别 | +|------|------|---------| +| GlobTool | 按模式匹配文件 | READ | +| GrepTool | 在文件中搜索内容 | READ | +| LintTool | 代码检查 | READ | +| EditTool | 编辑文件 | WRITE | + +### 3.2 开发工具 + +| 工具 | 描述 | 权限级别 | +|------|------|---------| +| LSPTool | Language Server Protocol | READ | +| BashTool | 执行 Bash 命令 | EXTERNAL | +| PowerShellTool | 执行 PowerShell | EXTERNAL | + +### 3.3 系统工具 + +| 工具 | 描述 | 权限级别 | +|------|------|---------| +| ScheduleCronTool | Cron 定时任务 | WRITE | +| ProcessTool | 进程管理 | EXTERNAL | + +### 3.4 协作工具 + +| 工具 | 描述 | 权限级别 | +|------|------|---------| +| TeamAgentTool | 团队 Agent 协作 | EXTERNAL | +| TaskBroadcastTool | 任务广播 | WRITE | + +### 3.5 高级工具 + +| 工具 | 描述 | 权限级别 | +|------|------|---------| +| RemoteTriggerTool | 远程触发 | EXTERNAL | +| MCPClientTool | MCP 客户端 | EXTERNAL | +| AskUserQuestionTool | 向用户提问 | READ | + +--- + +## 4. 文件结构变化 + +``` +backend/app/agents/tools/ +├── __init__.py # 现有 - 需重构 +├── registry.py # 新增 - 工具注册表 +├── manifest.py # 新增 - 工具元数据 +├── hook_executor.py # 新增 - Hook 执行器 +├── hook_config.py # 新增 - Hook 配置 +├── streaming_executor.py # 新增 - 流式执行器 +├── base.py # 新增 - 基础工具类 +│ +├── builtins/ # 新增 - 内置工具 +│ ├── __init__.py +│ ├── file_tools.py # Glob, Grep, Edit, Lint +│ ├── system_tools.py # Bash, PowerShell, Cron +│ ├── dev_tools.py # LSP, Git +│ └── collaboration_tools.py # Team, Broadcast +│ +├── task.py # 现有 - 保留 +├── schedule.py # 现有 - 保留 +├── search.py # 现有 - 保留 +├── forum.py # 现有 - 保留 +└── time_reasoning.py # 现有 - 保留 +``` + +--- + +## 5. 迁移计划 + +### 阶段 6.1:基础设施(1-2周) +- 创建 ToolRegistry 类 +- 创建 ToolManifest 数据类 +- 迁移现有工具到注册表 + +### 阶段 6.2:Hook 系统(2-3周) +- 创建 HookExecutor +- 实现 PreTool/PostTool 机制 +- 添加内置 Hook(确认危险操作、日志记录) + +### 阶段 6.3:流式执行(1-2周) +- 实现 StreamingToolExecutor +- 为支持流式的工具添加 streaming 标志 +- 更新 AgentService 集成 + +### 阶段 6.4:新工具集(2-3周) +- 实现新增的工具类 +- 添加完整的工具文档 +- 单元测试覆盖 + +--- + +## 6. 验收标准 + +| 检查点 | 标准 | +|--------|------| +| 工具注册 | 所有现有工具已在 Registry 中注册 | +| Hook 执行 | PreTool/PostTool Hook 能正确拦截 | +| 流式执行 | 标记为 streaming 的工具可流式返回 | +| 新工具 | 新增工具集中每个工具可正常执行 | +| 向后兼容 | 现有 Sub-Commander 工具调用不受影响 | + +--- + +## 7. Demo 借鉴 + +| claw-code 实现 | Jarvis 对应 | +|----------------|------------| +| `src/tools/` | `backend/app/agents/tools/` | +| `StreamingToolExecutor.ts` | `streaming_executor.py` | +| `toolExecution.ts` | `hook_executor.py` | +| `toolHooks.ts` | `hook_config.py` | +| `ToolManifest` | `manifest.py` | diff --git a/development-doc/plan/agent-update/phase-7-hook-interception-layer.md b/development-doc/plan/agent-update/phase-7-hook-interception-layer.md new file mode 100644 index 0000000..6e9df5b --- /dev/null +++ b/development-doc/plan/agent-update/phase-7-hook-interception-layer.md @@ -0,0 +1,536 @@ +# Phase 7:Hook 拦截层(Hook Interception Layer) + +日期:2026-04-04 +状态:待开始 +前置依赖:Phase 6(工具系统重构) +Demo参考:claw-code-main — toolHooks.ts, hook pipeline + +--- + +## 1. 阶段目标 + +实现完整的 **PreTool/PostTool Hook 拦截机制**,允许在工具执行前、执行后、异常时进行拦截、修改、拒绝和审计。 + +**Hook 是工具系统的基础设施**,不是独立功能,必须依赖 Phase 6 的注册表。 + +--- + +## 2. Hook 类型定义 + +### 2.1 Hook 类型 + +```python +# backend/app/agents/tools/hooks/types.py + +class HookType(Enum): + """Hook 类型""" + PRE_TOOL_USE = "pre_tool_use" # 工具执行前 + POST_TOOL_USE = "post_tool_use" # 工具执行后 + TOOL_ERROR = "tool_error" # 工具执行异常 + TOOL_SKIP = "tool_skip" # 工具被跳过 + + +class HookAction(Enum): + """Hook 执行动作""" + ALLOW = "allow" # 允许执行 + DENY = "deny" # 拒绝执行 + MODIFY = "modify" # 修改参数 + MODIFY_RESULT = "modify_result" # 修改结果 + CONTINUE = "continue" # 继续(后置Hook用) + + +@dataclass +class HookContext: + """Hook 执行上下文""" + tool_name: str + arguments: dict + phase: HookType + result: Any = None + error: Exception | None = None + session_id: str | None = None + user_id: str | None = None + metadata: dict = field(default_factory=dict) + + +@dataclass +class HookResult: + """Hook 执行结果""" + action: HookAction + message: str | None = None + modified_args: dict | None = None + modified_result: Any = None +``` + +--- + +## 3. 内置 Hook 实现 + +### 3.1 危险操作确认 Hook + +```python +# backend/app/agents/tools/hooks/dangerous_confirmation.py + +class DangerousConfirmationHook: + """ + 危险操作确认 Hook + + 对危险操作(WRITE/EXTERNAL 权限)执行前确认 + """ + + DANGEROUS_PATTERNS = [ + "delete", + "remove", + "drop", + "truncate", + "rm ", + "rmdir", + "shutdown", + "reboot", + "format", + "fdisk", + ] + + def __init__(self, confirmation_service: ConfirmationService): + self.confirmation_service = confirmation_service + + async def pre_execute(self, ctx: HookContext) -> HookResult: + """检查是否危险操作""" + manifest = tool_registry.get(ctx.tool_name) + + # 只对 WRITE/EXTERNAL 权限的工具生效 + if manifest.permission_class not in [PermissionClass.WRITE, PermissionClass.EXTERNAL]: + return HookResult(action=HookAction.ALLOW) + + # 检查是否包含危险模式 + args_str = str(ctx.arguments).lower() + for pattern in self.DANGEROUS_PATTERNS: + if pattern in args_str: + # 请求用户确认 + confirmed = await self.confirmation_service.request_confirmation( + user_id=ctx.user_id, + message=f"危险操作:{ctx.tool_name}\n参数:{ctx.arguments}", + timeout_seconds=300 + ) + + if not confirmed: + return HookResult( + action=HookAction.DENY, + message="用户拒绝执行危险操作" + ) + + return HookResult(action=HookAction.ALLOW) +``` + +### 3.2 安全扫描 Hook + +```python +# backend/app/agents/tools/hooks/security_scan.py + +class SecurityScanHook: + """ + 安全扫描 Hook + + 执行后扫描结果中的敏感信息 + """ + + SENSITIVE_PATTERNS = [ + r"password\s*=\s*['\"][^'\"]+['\"]", + r"api[_-]?key\s*=\s*['\"][^'\"]+['\"]", + r"secret\s*=\s*['\"][^'\"]+['\"]", + r"-----BEGIN (RSA |EC |DSA )?PRIVATE KEY-----", + r"sk-[a-zA-Z0-9]{20,}", + ] + + async def post_execute(self, ctx: HookContext) -> HookResult: + """扫描结果中的敏感信息""" + if ctx.result is None: + return HookResult(action=HookAction.CONTINUE) + + result_str = str(ctx.result) + + for pattern in self.SENSITIVE_PATTERNS: + if re.search(pattern, result_str, re.IGNORECASE): + # 脱敏处理 + sanitized = re.sub(pattern, "[REDACTED]", result_str, flags=re.IGNORECASE) + return HookResult( + action=HookAction.MODIFY_RESULT, + message="检测到敏感信息,已脱敏", + modified_result=sanitized + ) + + return HookResult(action=HookAction.CONTINUE) +``` + +### 3.3 日志记录 Hook + +```python +# backend/app/agents/tools/hooks/audit_log.py + +class AuditLogHook: + """ + 审计日志 Hook + + 记录所有工具调用 + """ + + async def pre_execute(self, ctx: HookContext) -> HookResult: + """记录调用前状态""" + logger.info( + "tool_call_start", + extra={ + "tool": ctx.tool_name, + "args": ctx.arguments, + "session_id": ctx.session_id, + "user_id": ctx.user_id, + "timestamp": datetime.now().isoformat(), + } + ) + return HookResult(action=HookAction.ALLOW) + + async def post_execute(self, ctx: HookContext) -> HookResult: + """记录调用后状态""" + logger.info( + "tool_call_end", + extra={ + "tool": ctx.tool_name, + "success": ctx.error is None, + "duration_ms": ctx.metadata.get("duration_ms", 0), + "session_id": ctx.session_id, + "user_id": ctx.user_id, + "timestamp": datetime.now().isoformat(), + } + ) + return HookResult(action=HookAction.CONTINUE) + + async def on_error(self, ctx: HookContext) -> HookResult: + """记录错误""" + logger.error( + "tool_call_error", + extra={ + "tool": ctx.tool_name, + "error": str(ctx.error), + "error_type": type(ctx.error).__name__, + "session_id": ctx.session_id, + "user_id": ctx.user_id, + "timestamp": datetime.now().isoformat(), + } + ) + return HookResult(action=HookAction.CONTINUE) +``` + +--- + +## 4. Hook 配置系统 + +### 4.1 Hook 配置格式 + +```json +{ + "hooks": { + "pre_tool_use": { + "enabled": true, + "handlers": [ + { + "type": "audit_log", + "filter": null + }, + { + "type": "dangerous_confirmation", + "filter": { + "permissions": ["WRITE", "EXTERNAL"] + } + } + ] + }, + "post_tool_use": { + "enabled": true, + "handlers": [ + { + "type": "security_scan", + "filter": null + }, + { + "type": "audit_log", + "filter": null + } + ] + }, + "tool_error": { + "enabled": true, + "handlers": [ + { + "type": "audit_log", + "filter": null + } + ] + } + } +} +``` + +### 4.2 HookManager + +```python +# backend/app/agents/tools/hooks/manager.py + +class HookManager: + """Hook 管理器""" + + def __init__(self): + self._builtin_hooks: dict[str, type] = { + "audit_log": AuditLogHook, + "dangerous_confirmation": DangerousConfirmationHook, + "security_scan": SecurityScanHook, + } + self._config: HookConfig = {} + self._enabled = False + + def load_config(self, config: dict): + """加载 Hook 配置""" + self._config = config + self._enabled = config.get("enabled", True) + + async def execute_pre_hooks( + self, + tool_name: str, + arguments: dict, + context: dict + ) -> HookResult: + """执行前置 Hook""" + if not self._enabled: + return HookResult(action=HookAction.ALLOW) + + pre_config = self._config.get("pre_tool_use", {}) + if not pre_config.get("enabled", True): + return HookResult(action=HookAction.ALLOW) + + ctx = HookContext( + tool_name=tool_name, + arguments=arguments, + phase=HookType.PRE_TOOL_USE, + **context + ) + + for handler_config in pre_config.get("handlers", []): + handler = self._create_handler(handler_config) + if not self._should_apply(handler_config, tool_name): + continue + + result = await handler.pre_execute(ctx) + if result.action == HookAction.DENY: + return result + elif result.action == HookAction.MODIFY: + ctx.arguments = result.modified_args + + return HookResult(action=HookAction.ALLOW, modified_args=ctx.arguments) + + async def execute_post_hooks( + self, + tool_name: str, + arguments: dict, + result: Any, + context: dict + ) -> HookResult: + """执行后置 Hook""" + if not self._enabled: + return HookResult(action=HookAction.CONTINUE, modified_result=result) + + post_config = self._config.get("post_tool_use", {}) + if not post_config.get("enabled", True): + return HookResult(action=HookAction.CONTINUE, modified_result=result) + + ctx = HookContext( + tool_name=tool_name, + arguments=arguments, + phase=HookType.POST_TOOL_USE, + result=result, + **context + ) + + for handler_config in post_config.get("handlers", []): + handler = self._create_handler(handler_config) + if not self._should_apply(handler_config, tool_name): + continue + + hook_result = await handler.post_execute(ctx) + if hook_result.action == HookAction.MODIFY_RESULT: + ctx.result = hook_result.modified_result + + return HookResult(action=HookAction.CONTINUE, modified_result=ctx.result) +``` + +--- + +## 5. 用户自定义 Hook + +### 5.1 Hook API + +```python +# backend/app/routers/hooks.py + +@router.post("/api/hooks/config") +async def update_hook_config( + config: HookConfigUpdate, + current_user: User = Depends(get_current_user) +): + """更新 Hook 配置""" + # 验证配置格式 + await hook_manager.validate_config(config) + + # 保存配置 + await hook_config_service.save_config( + user_id=current_user.id, + config=config + ) + + # 重新加载 + hook_manager.load_config(config) + + return {"status": "ok"} + + +@router.get("/api/hooks/config") +async def get_hook_config( + current_user: User = Depends(get_current_user) +): + """获取 Hook 配置""" + config = await hook_config_service.get_config(current_user.id) + return config + + +@router.get("/api/hooks/available") +async def list_available_hooks(): + """列出所有可用的 Hook""" + return { + "builtin": [ + {"name": "audit_log", "description": "审计日志"}, + {"name": "dangerous_confirmation", "description": "危险操作确认"}, + {"name": "security_scan", "description": "安全扫描"}, + ], + "custom": hook_manager.list_custom_hooks() + } +``` + +--- + +## 6. 与工具系统集成 + +### 6.1 修改 ToolExecutor + +```python +# backend/app/agents/tools/executor.py + +class ToolExecutor: + """工具执行器(集成 Hook)""" + + def __init__( + self, + registry: ToolRegistry, + hook_manager: HookManager + ): + self.registry = registry + self.hook_manager = hook_manager + + async def execute( + self, + tool_name: str, + arguments: dict, + context: dict + ) -> ToolResult: + """执行工具(带 Hook)""" + # 1. Pre Hook + pre_result = await self.hook_manager.execute_pre_hooks( + tool_name, arguments, context + ) + + if pre_result.action == HookAction.DENY: + return ToolResult( + success=False, + error=pre_result.message, + tool_name=tool_name + ) + + if pre_result.modified_args: + arguments = pre_result.modified_args + + # 2. 执行工具 + try: + manifest = self.registry.get(tool_name) + executor = self.registry.get_executor(tool_name) + + start_time = datetime.now() + result = await executor(**arguments) + duration_ms = (datetime.now() - start_time).total_seconds() * 1000 + + # 3. Post Hook + post_result = await self.hook_manager.execute_post_hooks( + tool_name, arguments, result, context + ) + + if post_result.action == HookAction.MODIFY_RESULT: + result = post_result.modified_result + + return ToolResult( + success=True, + result=result, + tool_name=tool_name, + duration_ms=duration_ms + ) + + except Exception as e: + # 4. Error Hook + error_result = await self.hook_manager.execute_error_hook( + tool_name, arguments, e, context + ) + + return ToolResult( + success=False, + error=str(e), + tool_name=tool_name + ) +``` + +--- + +## 7. 文件结构 + +``` +backend/app/agents/tools/hooks/ +├── __init__.py +├── types.py # Hook 类型定义 +├── manager.py # Hook 管理器 +│ +├── builtins/ # 内置 Hook +│ ├── __init__.py +│ ├── audit_log.py +│ ├── dangerous_confirmation.py +│ └── security_scan.py +│ +└── custom/ # 用户自定义 Hook + ├── __init__.py + └── loader.py +``` + +--- + +## 8. 验收标准 + +| 检查点 | 标准 | +|--------|------| +| Pre Hook 执行 | 危险操作被正确拦截 | +| Post Hook 执行 | 结果中的敏感信息被正确脱敏 | +| Error Hook 执行 | 异常被正确记录 | +| 配置持久化 | Hook 配置可保存和加载 | +| API 可用 | 用户可通过 API 配置 Hook | +| 向后兼容 | 不配置 Hook 时系统正常运行 | + +--- + +## 9. Demo 借鉴 + +| claw-code | Jarvis 对应 | +|-----------|------------| +| `toolHooks.ts` | `hooks/types.py`, `hooks/manager.py` | +| `PreToolUse` | `HookType.PRE_TOOL_USE` | +| `PostToolUse` | `HookType.POST_TOOL_USE` | +| Hook 配置解析 | `hooks/config.py` | diff --git a/development-doc/plan/agent-update/phase-8-plugin-ecosystem.md b/development-doc/plan/agent-update/phase-8-plugin-ecosystem.md new file mode 100644 index 0000000..f9b88c1 --- /dev/null +++ b/development-doc/plan/agent-update/phase-8-plugin-ecosystem.md @@ -0,0 +1,628 @@ +# Phase 8:插件生态(Plugin Ecosystem) + +日期:2026-04-04 +状态:待开始 +前置依赖:Phase 6(工具系统重构)、Phase 7(Hook 拦截层) +Demo参考:claw-code-main — plugins/, PluginLifecycle + +--- + +## 1. 阶段目标 + +建立完整的**插件生命周期管理系统**,允许第三方开发者通过插件扩展 Jarvis 的功能。 + +**核心能力**: +- 插件安装/卸载/启用/禁用 +- 插件沙箱隔离执行 +- 插件提供的工具/Hook/命令注册 +- 内置插件集 + +--- + +## 2. 插件架构 + +### 2.1 插件结构 + +``` +my-plugin/ +├── manifest.json # 插件清单 +├── SKILL.md # 插件技能文档(可选) +├── README.md # 插件说明 +│ +├── tools/ # 插件提供的工具 +│ ├── __init__.py +│ └── my_tool.py +│ +├── hooks/ # 插件提供的 Hook +│ ├── __init__.py +│ └── my_hook.py +│ +├── commands/ # 插件提供的命令 +│ ├── __init__.py +│ └── my_command.py +│ +└── static/ # 静态资源 + └── icon.png +``` + +### 2.2 插件清单 (manifest.json) + +```json +{ + "id": "my-plugin", + "name": "My Plugin", + "version": "1.0.0", + "description": "A sample plugin for Jarvis", + "author": "Developer Name", + "license": "MIT", + "homepage": "https://github.com/developer/my-plugin", + + "compatibility": { + "jarvis_version": ">=2.0.0", + "api_version": "1.0" + }, + + "capabilities": { + "tools": [ + { + "name": "my_tool", + "description": "Does something useful", + "parameters": { + "type": "object", + "properties": { + "input": {"type": "string"} + }, + "required": ["input"] + } + } + ], + "hooks": [ + { + "name": "my_pre_hook", + "type": "pre_tool_use", + "description": "Custom pre-hook" + } + ], + "commands": [ + { + "name": "/my-command", + "description": "Custom slash command" + } + ] + }, + + "permissions": { + "network": false, + "filesystem": "read", + "database": false + }, + + "dependencies": {} +} +``` + +--- + +## 3. 核心组件 + +### 3.1 PluginManifest + +```python +# backend/app/agents/plugins/manifest.py + +@dataclass +class ToolSpec: + """工具规格""" + name: str + description: str + parameters: dict + return_schema: dict | None = None + +@dataclass +class HookSpec: + """Hook 规格""" + name: str + type: HookType + description: str + filter_names: list[str] | None = None + +@dataclass +class CommandSpec: + """命令规格""" + name: str + description: str + parameters: list | None = None + +@dataclass +class PluginManifest: + """插件清单""" + id: str + name: str + version: str + description: str + author: str + license: str + homepage: str | None = None + + compatibility: dict = field(default_factory=dict) + capabilities: dict = field(default_factory=dict) + permissions: dict = field(default_factory=dict) + dependencies: dict = field(default_factory=dict) + + @classmethod + def from_file(cls, path: Path) -> "PluginManifest": + """从文件加载""" + with open(path / "manifest.json") as f: + data = json.load(f) + return cls(**data) + + def validate(self) -> list[str]: + """验证清单有效性""" + errors = [] + + if not self.id: + errors.append("Plugin ID is required") + if not self.version: + errors.append("Plugin version is required") + if "tools" in self.capabilities: + for tool in self.capabilities["tools"]: + if "name" not in tool: + errors.append(f"Tool missing name in plugin {self.id}") + + return errors +``` + +### 3.2 PluginManager + +```python +# backend/app/agents/plugins/manager.py + +class PluginState(Enum): + """插件状态""" + INSTALLED = "installed" + ENABLED = "enabled" + DISABLED = "disabled" + ERROR = "error" + +@dataclass +class Plugin: + """插件实例""" + manifest: PluginManifest + path: Path + state: PluginState + module: Any = None + error_message: str | None = None + + tools: list[ToolManifest] = field(default_factory=list) + hooks: list[HookConfig] = field(default_factory=list) + commands: list[CommandSpec] = field(default_factory=list) + +class PluginManager: + """插件管理器""" + + def __init__( + self, + plugin_dir: Path, + tool_registry: ToolRegistry, + hook_manager: HookManager + ): + self.plugin_dir = plugin_dir + self.tool_registry = tool_registry + self.hook_manager = hook_manager + self._plugins: dict[str, Plugin] = {} + self._hooks = [] + + async def install(self, plugin_path: Path | str) -> Plugin: + """ + 安装插件 + + 流程: + 1. 验证插件清单 + 2. 检查兼容性 + 3. 复制到插件目录 + 4. 加载插件模块 + """ + plugin_path = Path(plugin_path) + + # 1. 加载清单 + manifest = PluginManifest.from_file(plugin_path) + + # 2. 验证 + errors = manifest.validate() + if errors: + raise PluginValidationError(errors) + + # 3. 检查版本兼容性 + await self._check_compatibility(manifest) + + # 4. 创建插件目录 + target_dir = self.plugin_dir / manifest.id + if target_dir.exists(): + raise PluginAlreadyExistsError(manifest.id) + + shutil.copytree(plugin_path, target_dir) + + # 5. 创建插件实例 + plugin = Plugin( + manifest=manifest, + path=target_dir, + state=PluginState.INSTALLED + ) + + # 6. 加载模块 + await self._load_plugin(plugin) + + self._plugins[manifest.id] = plugin + return plugin + + async def uninstall(self, plugin_id: str) -> bool: + """卸载插件""" + plugin = self._plugins.get(plugin_id) + if not plugin: + raise PluginNotFoundError(plugin_id) + + # 禁用插件 + if plugin.state == PluginState.ENABLED: + await self.disable(plugin_id) + + # 移除工具和 Hook + for tool in plugin.tools: + self.tool_registry.unregister(tool.name) + + for hook in plugin.hooks: + self.hook_manager.remove_hook(hook) + + # 删除插件目录 + shutil.rmtree(plugin.path) + + del self._plugins[plugin_id] + return True + + async def enable(self, plugin_id: str) -> Plugin: + """启用插件""" + plugin = self._plugins.get(plugin_id) + if not plugin: + raise PluginNotFoundError(plugin_id) + + # 注册工具 + for tool in plugin.tools: + self.tool_registry.register( + tool, + self._create_tool_executor(plugin, tool.name) + ) + + # 注册 Hook + for hook in plugin.hooks: + self.hook_manager.add_hook(hook) + + plugin.state = PluginState.ENABLED + return plugin + + async def disable(self, plugin_id: str) -> Plugin: + """禁用插件""" + plugin = self._plugins.get(plugin_id) + if not plugin: + raise PluginNotFoundError(plugin_id) + + # 注销工具 + for tool in plugin.tools: + self.tool_registry.unregister(tool.name) + + # 注销 Hook + for hook in plugin.hooks: + self.hook_manager.remove_hook(hook) + + plugin.state = PluginState.DISABLED + return plugin + + async def reload(self, plugin_id: str) -> Plugin: + """重新加载插件""" + plugin = self._plugins.get(plugin_id) + if not plugin: + raise PluginNotFoundError(plugin_id) + + was_enabled = plugin.state == PluginState.ENABLED + + if was_enabled: + await self.disable(plugin_id) + + await self._load_plugin(plugin) + + if was_enabled: + await self.enable(plugin_id) + + return plugin + + async def list_plugins(self) -> list[Plugin]: + """列出所有插件""" + return list(self._plugins.values()) +``` + +--- + +## 4. 插件隔离 + +### 4.1 PluginSandbox + +```python +# backend/app/agents/plugins/sandbox.py + +class PluginSandbox: + """插件沙箱""" + + def __init__(self, plugin: Plugin, permissions: dict): + self.plugin = plugin + self.permissions = permissions + self._env = {} + + async def load_module(self) -> Any: + """在沙箱中加载插件模块""" + # 设置受限的环境 + self._env = { + "__name__": f"plugin.{self.plugin.manifest.id}", + "__file__": str(self.plugin.path), + } + + if not self.permissions.get("filesystem", False): + # 只读文件系统 + self._env["__builtins__"] = { + k: v for k, v in __builtins__.items() + if k in ["print", "len", "range", "str", "int", "float", "list", "dict", "tuple", "set", "bool", "type", "isinstance", "hasattr", "getattr", "setattr", "open"] + } + + # 加载模块 + spec = importlib.util.spec_from_file_location( + f"plugin_{self.plugin.manifest.id}", + self.plugin.path / "__init__.py" + ) + module = importlib.util.module_from_spec(spec) + + sys.modules[f"plugin_{self.plugin.manifest.id}"] = module + + try: + spec.loader.exec_module(module) + except Exception as e: + raise PluginLoadError(str(e)) + + return module + + def check_permission(self, permission: str) -> bool: + """检查权限""" + return self.permissions.get(permission, False) +``` + +--- + +## 5. 插件市场 + +### 5.1 PluginMarketplace + +```python +# backend/app/services/plugin_marketplace.py + +@dataclass +class MarketplacePlugin: + """市场中的插件""" + manifest: PluginManifest + download_count: int + rating: float + reviews_count: int + last_updated: datetime + +class PluginMarketplace: + """插件市场""" + + def __init__(self, http_client: httpx.AsyncClient): + self.http_client = http_client + self.cache: dict[str, MarketplacePlugin] = {} + self.cache_ttl = timedelta(hours=1) + + async def search( + self, + query: str, + category: str | None = None, + limit: int = 20 + ) -> list[MarketplacePlugin]: + """搜索插件""" + # TODO: 连接到实际的市场 API + return [] + + async def get_plugin( + self, + plugin_id: str + ) -> MarketplacePlugin | None: + """获取插件详情""" + # 检查缓存 + if plugin_id in self.cache: + return self.cache[plugin_id] + + # TODO: 从市场 API 获取 + return None + + async def download_plugin( + self, + plugin_id: str, + target_dir: Path + ) -> Path: + """下载插件""" + # TODO: 实现下载逻辑 + pass +``` + +--- + +## 6. 内置插件 + +### 6.1 内置插件列表 + +| 插件 | 描述 | 提供的工具 | +|------|------|-----------| +| `code-helper` | 代码助手 | `lint`, `format`, `explain_code` | +| `git-helper` | Git 助手 | `git_status`, `git_log`, `git_diff` | +| `web-helper` | Web 助手 | `fetch_url`, `parse_html` | +| `file-organizer` | 文件整理 | `organize_files`, `cleanup_duplicates` | + +### 6.2 BuiltinPlugins + +```python +# backend/app/agents/plugins/builtins.py + +BUILTIN_PLUGINS = [ + { + "id": "code-helper", + "name": "Code Helper", + "version": "1.0.0", + "description": "代码辅助工具集", + "capabilities": { + "tools": [ + { + "name": "lint", + "description": "检查代码风格", + "parameters": { + "type": "object", + "properties": { + "code": {"type": "string"}, + "language": {"type": "string"} + } + } + }, + { + "name": "format", + "description": "格式化代码", + "parameters": { + "type": "object", + "properties": { + "code": {"type": "string"}, + "language": {"type": "string"} + } + } + } + ] + } + } +] +``` + +--- + +## 7. API 接口 + +```python +# backend/app/routers/plugins.py + +@router.get("/api/plugins") +async def list_plugins( + current_user: User = Depends(get_current_user) +): + """列出用户已安装的插件""" + plugins = await plugin_manager.list_plugins() + return { + "plugins": [ + { + "id": p.manifest.id, + "name": p.manifest.name, + "version": p.manifest.version, + "state": p.state.value, + "description": p.manifest.description + } + for p in plugins + ] + } + +@router.post("/api/plugins/install") +async def install_plugin( + request: InstallPluginRequest, + current_user: User = Depends(get_current_user) +): + """安装插件""" + plugin = await plugin_manager.install(Path(request.path)) + return {"plugin_id": plugin.manifest.id} + +@router.post("/api/plugins/{plugin_id}/enable") +async def enable_plugin( + plugin_id: str, + current_user: User = Depends(get_current_user) +): + """启用插件""" + plugin = await plugin_manager.enable(plugin_id) + return {"state": plugin.state.value} + +@router.post("/api/plugins/{plugin_id}/disable") +async def disable_plugin( + plugin_id: str, + current_user: User = Depends(get_current_user) +): + """禁用插件""" + plugin = await plugin_manager.disable(plugin_id) + return {"state": plugin.state.value} + +@router.delete("/api/plugins/{plugin_id}") +async def uninstall_plugin( + plugin_id: str, + current_user: User = Depends(get_current_user) +): + """卸载插件""" + await plugin_manager.uninstall(plugin_id) + return {"status": "ok"} + +@router.get("/api/marketplace/plugins") +async def search_marketplace( + q: str | None = None, + category: str | None = None, + limit: int = 20 +): + """搜索插件市场""" + plugins = await marketplace.search(q, category, limit) + return {"plugins": plugins} +``` + +--- + +## 8. 文件结构 + +``` +backend/app/agents/plugins/ +├── __init__.py +├── manifest.py # 插件清单 +├── manager.py # 插件管理器 +├── sandbox.py # 插件沙箱 +│ +├── builtins/ # 内置插件 +│ ├── __init__.py +│ ├── code_helper/ +│ ├── git_helper/ +│ └── web_helper/ +│ +├── marketplace.py # 插件市场 +│ +└── router.py # API 路由(可选) + +backend/app/routers/ +└── plugins.py # 插件 API 路由 +``` + +--- + +## 9. 验收标准 + +| 检查点 | 标准 | +|--------|------| +| 插件安装 | 可以从目录安装插件 | +| 插件启用 | 插件的工具和 Hook 正确注册 | +| 插件禁用 | 插件的工具和 Hook 正确注销 | +| 插件卸载 | 插件目录正确删除 | +| 插件隔离 | 插件无法访问未授权资源 | +| 市场搜索 | 可以搜索插件市场 | +| 内置插件 | 内置插件默认安装 | + +--- + +## 10. Demo 借鉴 + +| claw-code | Jarvis 对应 | +|-----------|------------| +| `src/plugins/builtinPlugins.ts` | `plugins/builtins.py` | +| `src/plugins/bundled/index.ts` | `plugins/builtins/` | +| `PluginLifecycle` | `PluginManager` | +| `PluginInstallationManager` | `PluginManager.install` | +| `/plugin`, `/reload-plugins` | `/api/plugins/*` | diff --git a/development-doc/plan/agent-update/phase-9-skills-registry.md b/development-doc/plan/agent-update/phase-9-skills-registry.md new file mode 100644 index 0000000..346a1c1 --- /dev/null +++ b/development-doc/plan/agent-update/phase-9-skills-registry.md @@ -0,0 +1,508 @@ +# Phase 9:Skills 注册表(Skills Registry) + +日期:2026-04-04 +状态:待开始 +前置依赖:Phase 6(工具系统重构) +Demo参考:claw-code-main — skills/loadSkillsDir.ts, bundledSkills.ts, mcpSkillBuilders.ts + +--- + +## 1. 阶段目标 + +建立完整的 **Skills 动态加载和注册系统**,支持: +- 本地文件 Skills(现有) +- 插件提供的 Skills +- MCP 动态发现的 Skills +- Skills 注册表和搜索 +- Bundled Skills 集 + +**现有状态**:Jarvis 已有基础的 `skill_registry.py`,需要增强为完整系统。 + +--- + +## 2. Skills 架构 + +### 2.1 Skill 结构 + +```markdown +# SKILL.md + +## Skill Metadata +name: my-skill +description: Does something useful +version: 1.0.0 +author: Developer + +## Triggers +- keywords: ["skill", "my"] +- patterns: ["/my-skill"] + +## Capabilities +- tools: ["my_tool"] +- hooks: ["my_hook"] + +## Configuration +```json +{ + "option1": "value1" +} +``` +``` + +### 2.2 SkillRegistry 增强 + +```python +# backend/app/agents/skills/registry.py + +@dataclass +class SkillMetadata: + """Skill 元数据""" + name: str + description: str + version: str + author: str | None = None + + triggers: list[str] = field(default_factory=list) + patterns: list[str] = field(default_factory=list) + + tools: list[str] = field(default_factory=list) + hooks: list[str] = field(default_factory=list) + commands: list[str] = field(default_factory=list) + + config_schema: dict | None = None + source: SkillSource = SkillSource.LOCAL + + file_path: Path | None = None + +class SkillSource(Enum): + """Skill 来源""" + LOCAL = "local" # 本地文件 + PLUGIN = "plugin" # 插件提供 + MCP = "mcp" # MCP 动态发现 + BUNDLED = "bundled" # 内置 + +class SkillRegistry: + """Skills 注册表""" + + def __init__( + self, + local_skills_dir: Path, + tool_registry: ToolRegistry, + hook_manager: HookManager + ): + self.local_skills_dir = local_skills_dir + self.tool_registry = tool_registry + self.hook_manager = hook_manager + + self._skills: dict[str, SkillMetadata] = {} + self._index: dict[str, list[str]] = defaultdict(list) # keyword -> skill names + + async def load_all(self): + """加载所有 Skills""" + await self._load_local_skills() + await self._load_bundled_skills() + + async def _load_local_skills(self): + """加载本地 Skills""" + if not self.local_skills_dir.exists(): + return + + for skill_file in self.local_skills_dir.rglob("SKILL.md"): + try: + metadata = await self._parse_skill_file(skill_file) + metadata.source = SkillSource.LOCAL + self._register_skill(metadata) + except Exception as e: + logger.warning(f"Failed to load skill from {skill_file}: {e}") + + async def _load_bundled_skills(self): + """加载内置 Skills""" + for skill_def in BUNDLED_SKILLS: + metadata = SkillMetadata( + name=skill_def["name"], + description=skill_def["description"], + version=skill_def["version"], + author="Jarvis Team", + triggers=skill_def.get("triggers", []), + tools=skill_def.get("tools", []), + source=SkillSource.BUNDLED + ) + self._register_skill(metadata) + + async def _parse_skill_file(self, path: Path) -> SkillMetadata: + """解析 SKILL.md 文件""" + content = path.read_text(encoding="utf-8") + + # 简单的 frontmatter 解析 + metadata = SkillMetadata( + name=path.parent.name, + description="", + version="1.0.0" + ) + + # 解析 triggers + for line in content.split("\n"): + if line.startswith("- keywords:"): + keywords = line.split("[")[1].split("]")[0] + metadata.triggers = [k.strip() for k in keywords.split(",")] + + return metadata + + def _register_skill(self, metadata: SkillMetadata): + """注册 Skill""" + self._skills[metadata.name] = metadata + + # 更新索引 + for keyword in metadata.triggers: + self._index[keyword].append(metadata.name) + + # 注册关联的工具 + for tool_name in metadata.tools: + self.tool_registry.register_tool_for_skill(metadata.name, tool_name) + + async def get_skill(self, name: str) -> SkillMetadata | None: + """获取 Skill""" + return self._skills.get(name) + + async def search( + self, + query: str, + limit: int = 10 + ) -> list[SkillMetadata]: + """搜索 Skills""" + results = [] + + query_lower = query.lower() + + # 精确匹配 name + if query_lower in self._skills: + results.append(self._skills[query_lower]) + + # 关键词匹配 + for skill_name in self._index.get(query_lower, []): + if skill_name not in results: + results.append(self._skills[skill_name]) + + # 描述匹配 + for skill in self._skills.values(): + if skill in results: + continue + if query_lower in skill.description.lower(): + results.append(skill) + + return results[:limit] + + async def get_skill_context( + self, + skill_name: str, + context: dict + ) -> str: + """获取 Skill 上下文""" + skill = self._skills.get(skill_name) + if not skill: + return "" + + # 读取 SKILL.md 内容 + if skill.file_path and skill.file_path.exists(): + content = skill.file_path.read_text(encoding="utf-8") + # 移除 metadata 部分,只保留 instructions + return content + + return "" +``` + +--- + +## 3. MCP Skill Builder + +### 3.1 MCPSkillBuilder + +```python +# backend/app/agents/skills/mcp_builder.py + +class MCPSkillBuilder: + """ + MCP Skill Builder + + 从 MCP 服务器动态构建 Skills + """ + + def __init__( + self, + mcp_client: MCPClient, + skill_registry: SkillRegistry + ): + self.mcp_client = mcp_client + self.skill_registry = skill_registry + + async def discover_skills_from_mcp( + self, + mcp_server_id: str + ) -> list[SkillMetadata]: + """从 MCP 服务器发现 Skills""" + # 获取 MCP 服务器提供的工具 + tools = await self.mcp_client.list_tools(mcp_server_id) + + skills = [] + + # 按工具前缀分组 + tool_groups: dict[str, list] = defaultdict(list) + for tool in tools: + parts = tool.name.split("_") + if len(parts) > 1: + prefix = parts[0] + tool_groups[prefix].append(tool) + else: + # 单工具,作为独立 skill + skill = self._tool_to_skill(tool, mcp_server_id) + skills.append(skill) + + # 创建分组 skill + for prefix, group_tools in tool_groups.items(): + skill = self._group_to_skill(prefix, group_tools, mcp_server_id) + skills.append(skill) + + return skills + + def _tool_to_skill( + self, + tool: MCPProtocolTool, + server_id: str + ) -> SkillMetadata: + """将单个 MCP 工具转换为 Skill""" + return SkillMetadata( + name=f"{server_id}_{tool.name}", + description=tool.description or f"MCP tool: {tool.name}", + version="1.0.0", + tools=[tool.name], + source=SkillSource.MCP, + config_schema={ + "server_id": server_id, + "tool_name": tool.name + } + ) + + def _group_to_skill( + self, + prefix: str, + tools: list[MCPProtocolTool], + server_id: str + ) -> SkillMetadata: + """将一组 MCP 工具转换为 Skill""" + return SkillMetadata( + name=f"{server_id}_{prefix}", + description=f"MCP {prefix} tools: {', '.join(t.name for t in tools)}", + version="1.0.0", + tools=[t.name for t in tools], + source=SkillSource.MCP, + config_schema={ + "server_id": server_id, + "prefix": prefix + } + ) +``` + +--- + +## 4. 内置 Skills + +```python +# backend/app/agents/skills/bundled.py + +BUNDLED_SKILLS = [ + { + "name": "code-analysis", + "description": "代码分析技能 - 分析代码结构、复杂度、依赖关系", + "version": "1.0.0", + "triggers": ["分析代码", "代码分析", "code analysis"], + "tools": ["grep", "glob", "lint"] + }, + { + "name": "git-helper", + "description": "Git 操作助手 - 管理 Git 仓库和操作", + "version": "1.0.0", + "triggers": ["git", "版本控制", "commit"], + "tools": ["git_status", "git_log", "git_diff"] + }, + { + "name": "web-research", + "description": "网络研究技能 - 搜索和抓取网页内容", + "version": "1.0.0", + "triggers": ["搜索", "研究", "research", "web search"], + "tools": ["web_search", "web_fetch"] + }, + { + "name": "file-management", + "description": "文件管理技能 - 组织和管理文件", + "version": "1.0.0", + "triggers": ["文件", "整理", "file management"], + "tools": ["glob", "file_read", "file_write", "organize"] + }, + { + "name": "task-planning", + "description": "任务规划技能 - 拆解和规划复杂任务", + "version": "1.0.0", + "triggers": ["规划", "任务拆解", "task planning"], + "tools": ["create_task", "get_tasks", "update_task"] + } +] +``` + +--- + +## 5. Skill 与 Agent 集成 + +### 5.1 修改 AgentService + +```python +# backend/app/services/agent_service.py (修改部分) + +async def build_skill_context( + self, + skill_names: list[str], + context: dict +) -> str: + """构建 Skill 上下文""" + parts = [] + + for skill_name in skill_names: + skill_context = await self.skill_registry.get_skill_context( + skill_name, context + ) + if skill_context: + parts.append(f"\n\n=== Skill: {skill_name} ===\n{skill_context}") + + return "\n".join(parts) + +async def chat( + self, + message: str, + context: dict +) -> AgentResponse: + """处理聊天消息(集成 Skills)""" + # 1. 检测触发的 Skills + triggered_skills = await self.skill_registry.search(message) + + # 2. 构建 Skill 上下文 + skill_context = await self.build_skill_context( + [s.name for s in triggered_skills], + context + ) + + # 3. 注入到 system prompt + if skill_context: + context["skill_context"] = skill_context + + # 4. 继续正常处理 + return await self._process_message(message, context) +``` + +--- + +## 6. API 接口 + +```python +# backend/app/routers/skills.py + +@router.get("/api/skills") +async def list_skills( + source: SkillSource | None = None, + current_user: User = Depends(get_current_user) +): + """列出所有 Skills""" + skills = await skill_registry.list_all() + + if source: + skills = [s for s in skills if s.source == source] + + return { + "skills": [ + { + "name": s.name, + "description": s.description, + "version": s.version, + "source": s.source.value, + "triggers": s.triggers + } + for s in skills + ] + } + +@router.get("/api/skills/search") +async def search_skills( + q: str, + limit: int = 10 +): + """搜索 Skills""" + results = await skill_registry.search(q, limit) + return {"skills": results} + +@router.get("/api/skills/{skill_name}") +async def get_skill( + skill_name: str, + current_user: User = Depends(get_current_user) +): + """获取 Skill 详情""" + skill = await skill_registry.get_skill(skill_name) + if not skill: + raise HTTPException(404, "Skill not found") + + return { + "name": skill.name, + "description": skill.description, + "version": skill.version, + "source": skill.source.value, + "triggers": skill.triggers, + "tools": skill.tools, + "hooks": skill.hooks + } +``` + +--- + +## 7. 文件结构 + +``` +backend/app/agents/skills/ +├── __init__.py +├── registry.py # Skills 注册表(增强) +├── metadata.py # Skill 元数据 +├── mcp_builder.py # MCP Skill Builder +├── bundled.py # 内置 Skills +│ +├── loaders/ # 加载器 +│ ├── __init__.py +│ ├── local_loader.py # 本地文件加载 +│ ├── plugin_loader.py # 插件 Skill 加载 +│ └── mcp_loader.py # MCP Skill 加载 +│ +└── context.py # Skill 上下文构建 + +backend/app/routers/ +└── skills.py # Skills API 路由 +``` + +--- + +## 8. 验收标准 + +| 检查点 | 标准 | +|--------|------| +| 本地 Skills | 能加载 local_skills_dir 下的所有 SKILL.md | +| MCP Skills | 能从 MCP 服务器发现和加载 Skills | +| Bundled Skills | 内置 Skills 默认加载 | +| Skill 搜索 | 能按关键词搜索 Skills | +| Skill 上下文 | Skill 内容正确注入 Agent prompt | +| API 可用 | Skills API 可用 | + +--- + +## 9. Demo 借鉴 + +| claw-code | Jarvis 对应 | +|-----------|------------| +| `src/skills/loadSkillsDir.ts` | `skills/loaders/local_loader.py` | +| `src/skills/bundledSkills.ts` | `skills/bundled.py` | +| `src/skills/mcpSkillBuilders.ts` | `skills/mcp_builder.py` | +| `/skills` command | `/api/skills` | +| Skill registry pipeline | `SkillRegistry` |