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
509 lines
14 KiB
Markdown
509 lines
14 KiB
Markdown
# 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` |
|