feat(agents): Phase 7-10 hook system, plugins, skills, orchestration
Phase 7: Built-in Hooks (audit_log, dangerous_confirmation, security_scan) Phase 8: Plugin system (PluginManager, PluginSandbox, PluginManifest) Phase 9: Skills registry (SkillRegistry, local/plugin/MCP loaders) Phase 10: TeamLeader, RemoteTransport, BackgroundTaskManager
This commit is contained in:
119
backend/app/agents/background/manager.py
Normal file
119
backend/app/agents/background/manager.py
Normal file
@@ -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
|
||||
20
backend/app/agents/orchestration/__init__.py
Normal file
20
backend/app/agents/orchestration/__init__.py
Normal file
@@ -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",
|
||||
]
|
||||
12
backend/app/agents/plugins/__init__.py
Normal file
12
backend/app/agents/plugins/__init__.py
Normal file
@@ -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",
|
||||
]
|
||||
207
backend/app/agents/plugins/manager.py
Normal file
207
backend/app/agents/plugins/manager.py
Normal file
@@ -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
|
||||
73
backend/app/agents/plugins/manifest.py
Normal file
73
backend/app/agents/plugins/manifest.py
Normal file
@@ -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)
|
||||
111
backend/app/agents/plugins/sandbox.py
Normal file
111
backend/app/agents/plugins/sandbox.py
Normal file
@@ -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
|
||||
16
backend/app/agents/skills/__init__.py
Normal file
16
backend/app/agents/skills/__init__.py
Normal file
@@ -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",
|
||||
]
|
||||
100
backend/app/agents/skills/loaders/local_loader.py
Normal file
100
backend/app/agents/skills/loaders/local_loader.py
Normal file
@@ -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
|
||||
51
backend/app/agents/skills/loaders/plugin_loader.py
Normal file
51
backend/app/agents/skills/loaders/plugin_loader.py
Normal file
@@ -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
|
||||
100
backend/app/agents/skills/mcp_builder.py
Normal file
100
backend/app/agents/skills/mcp_builder.py
Normal file
@@ -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}",
|
||||
)
|
||||
38
backend/app/agents/skills/metadata.py
Normal file
38
backend/app/agents/skills/metadata.py
Normal file
@@ -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)
|
||||
133
backend/app/agents/skills/registry.py
Normal file
133
backend/app/agents/skills/registry.py
Normal file
@@ -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
|
||||
121
backend/app/agents/team/leader.py
Normal file
121
backend/app/agents/team/leader.py
Normal file
@@ -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),
|
||||
}
|
||||
11
backend/app/agents/tools/hooks/builtins/__init__.py
Normal file
11
backend/app/agents/tools/hooks/builtins/__init__.py
Normal file
@@ -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",
|
||||
]
|
||||
115
backend/app/agents/tools/hooks/builtins/audit_log.py
Normal file
115
backend/app/agents/tools/hooks/builtins/audit_log.py
Normal file
@@ -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()
|
||||
@@ -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)
|
||||
183
backend/app/agents/tools/hooks/builtins/security_scan.py
Normal file
183
backend/app/agents/tools/hooks/builtins/security_scan.py
Normal file
@@ -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
|
||||
105
backend/app/agents/tools/hooks/config.py
Normal file
105
backend/app/agents/tools/hooks/config.py
Normal file
@@ -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
|
||||
113
backend/app/agents/transport/remote.py
Normal file
113
backend/app/agents/transport/remote.py
Normal file
@@ -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"))
|
||||
282
development-doc/plan/agent-update/README.md
Normal file
282
development-doc/plan/agent-update/README.md
Normal file
@@ -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 的核心基线,当前工作重点已经从‘是否可行’转向‘如何把已存在能力继续工程化、可视化、隔离化’。”
|
||||
|
||||
这也是后续测试、验收和继续升级的正确前提。
|
||||
@@ -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 详细设计文档**
|
||||
|
||||
因为它最贴近当前代码,也最容易直接转成开发任务。
|
||||
@@ -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 的方向,都能继续演进。
|
||||
@@ -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天 | 有社区需求时 |
|
||||
|
||||
---
|
||||
|
||||
##
|
||||
@@ -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] 不直接在本阶段进行代码改造
|
||||
|
||||
215
development-doc/plan/agent-update/phase-1-safe-foundation.md
Normal file
215
development-doc/plan/agent-update/phase-1-safe-foundation.md
Normal file
@@ -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 的已落地基础层。**
|
||||
@@ -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` |
|
||||
@@ -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。
|
||||
@@ -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 成熟
|
||||
- 再考虑更强自治
|
||||
@@ -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/<conversation-or-run-id>/<worker-id>/`
|
||||
|
||||
建议 branch 模式:
|
||||
|
||||
- `jarvis/<conversation-or-run-id>/<worker-id>`
|
||||
|
||||
最小回收物:
|
||||
|
||||
- `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。
|
||||
624
development-doc/plan/agent-update/phase-5-advanced-features.md
Normal file
624
development-doc/plan/agent-update/phase-5-advanced-features.md
Normal file
@@ -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 为可选特性,根据实际需求选择性实施。**
|
||||
339
development-doc/plan/agent-update/phase-6-10-checklist.md
Normal file
339
development-doc/plan/agent-update/phase-6-10-checklist.md
Normal file
@@ -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*
|
||||
@@ -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` |
|
||||
@@ -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` |
|
||||
628
development-doc/plan/agent-update/phase-8-plugin-ecosystem.md
Normal file
628
development-doc/plan/agent-update/phase-8-plugin-ecosystem.md
Normal file
@@ -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/*` |
|
||||
508
development-doc/plan/agent-update/phase-9-skills-registry.md
Normal file
508
development-doc/plan/agent-update/phase-9-skills-registry.md
Normal file
@@ -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` |
|
||||
Reference in New Issue
Block a user