GPU_GUARD_MONOREPO/docs/superpowers/specs/2026-04-13-skill-system-migration-design.md

471 lines
17 KiB
Markdown
Raw Normal View History

2026-05-20 21:39:12 +08:00
# Skill 系统迁移设计:对齐 OpenClaw + Hermes 架构
> **日期**: 2026-04-13
> **状态**: 待审核
> **参考项目**: OpenClaw (小龙虾), Hermes Agent
---
## 1. 目标
将 Neta 的 skill 系统从"文档占位"状态迁移为完整可用的 prompt-based skill 系统:
- 移除老的 13 个 skill旧框架产物及其配套的老组件/字段
- 对齐 OpenClaw 的 SKILL.md 标准、多来源加载、安装机制
- 引入 Hermes 的渐进式披露、条件激活、Agent 自主管理 skill
- 支持 GitHub 地址安装 + Node/Python 生态依赖安装
- 前后端完整实现,前端必须遵循 Cool Admin service 代理规范
## 2. 核心机制Prompt-Based Skill
Skill 本质是 **prompt 指导**,不是可执行工具:
- SKILL.md 内容注入 system prompt索引模式或按需加载完整模式
- LLM 读取 skill 指令后自主决定如何执行
- 与 OpenClaw、Hermes 机制一致
## 3. 数据模型
### 3.1 数据库表 `netaclaw_skill`(改造)
**移除字段**老框架产物prompt-based skill 不需要):
- `icon` → 改用 frontmatter 的 `metadata.emoji`
- `category` → 改用 `tags` 分类
- `config` → prompt-based skill 无执行参数配置
**保留字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| name | varchar(100) UNIQUE | skill 唯一标识 |
| label | varchar(200) | 显示名称 |
| description | text | 功能描述 |
| skillType | varchar(20) | compute / llm / multimodal |
| tags | json | 标签数组 |
| status | int | 0=禁用 1=启用 |
| version | varchar(20) | 版本号 |
**新增字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| source | varchar(20) | 来源local / github |
| sourceUrl | varchar(500) | GitHub URL |
| installSpec | json | 安装规格OpenClaw SkillInstallSpec 兼容) |
| metadata | json | 完整 frontmatter 元数据emoji/requires/conditions 等) |
| installedAt | datetime | 安装时间 |
| fingerprint | varchar(64) | SHA256 内容指纹(用于更新检测) |
> **注意**Entity 继承 BaseEntity`synchronize: true` 开发环境自动同步表结构。`entities.ts` 无需改动(已注册)。
### 3.2 文件存储结构
```
packages/backend/
├── skills/ # skill 安装目录
│ └── {skill-name}/
│ ├── SKILL.md # 主定义文件
│ ├── references/ # 参考文档(可选)
│ ├── templates/ # 模板(可选)
│ └── scripts/ # 脚本(可选)
├── .skillhub/ # 安装管理
│ ├── lock.json # 锁文件
│ └── origins/
│ └── {skill-name}.json # 来源追踪
```
### 3.3 SKILL.md Frontmatter 标准
需要引入 `js-yaml` 库做完整 YAML 解析(现有实现用简单正则只提取 name/description无法解析嵌套结构
```yaml
---
name: my-skill
description: 技能描述
version: 1.0.0
metadata:
skillType: compute
emoji: "🔧"
tags: [检测, 视觉]
requires:
bins: ["ffmpeg"]
anyBins: ["node", "bun"]
tools: ["bash"]
install:
- id: node-dep
kind: node
package: "some-package"
label: "安装 Node 依赖"
- id: python-dep
kind: uv
package: "some-python-pkg"
label: "安装 Python 依赖"
conditions:
requires_tools: ["bash", "file_read"]
fallback_for_tools: ["custom_tool"]
---
# Skill 标题
## 何时使用
...
## 执行流程
...
```
### 3.4 Agent Entity 的 skills 字段(保持简单)
`agent.ts``skills: string[]` 保持不变——只存 skill 名称列表。条件激活规则只在 skill 自身 frontmatter 中定义,不支持 Agent 维度覆盖。理由:降低复杂度,避免 Agent 和 Skill 两处配置冲突。
## 4. 后端架构
### 4.1 服务层(新增/改造)
```
modules/netaclaw/service/
├── skill_loader.ts # 改造:完整 YAML 解析 + 渐进式加载 + 条件过滤
├── skill_installer.ts # 新增GitHub clone + 依赖安装
└── skill_registry.ts # 新增lockfile + 指纹 + 来源追踪
```
**依赖新增**`js-yaml`YAML 解析,替代现有的正则解析)
#### SkillLoaderService改造
```typescript
// 多来源扫描
async scanSkills(): Promise<void>
// 渐进式披露只返回索引name + description
buildSkillsIndex(skillNames: string[]): string
// 按需加载完整 SKILL.md 内容
getSkillContent(name: string): string | null
// 条件过滤:根据 agent 可用工具过滤 skill
filterByConditions(skills: SkillEntry[], availableTools: string[]): SkillEntry[]
// 构建 <available_skills> XML
buildSkillsPrompt(skillNames: string[], availableTools: string[]): string
```
#### SkillInstallerService新增
```typescript
// GitHub 安装
async installFromGitHub(url: string, options?: { branch?: string; tag?: string }): Promise<SkillMeta>
// 依赖安装node/uv
async installDependencies(skillName: string): Promise<{ success: boolean; logs: string[] }>
// 卸载
async uninstall(skillName: string): Promise<void>
// 更新(重新拉取)
async update(skillName: string): Promise<SkillMeta>
// 检查更新
async checkUpdates(): Promise<Array<{ name: string; hasUpdate: boolean }>>
```
安装器安全校验(对齐 OpenClaw
- npm 包名正则:`/^(@[a-z0-9._-]+\/)?[a-z0-9._-]+(@[a-z0-9^~>=<.*|-]+)?$/`
- uv 包名正则:`/^[a-z0-9][a-z0-9._-]*(\[[a-z0-9,._-]+\])?(([><=!~]=?|===?)[a-z0-9.*_-]+)?$/i`
- GitHub URL 校验:必须是 `github.com` 域名
#### SkillRegistryService新增
```typescript
// 读写 lockfile
async readLockfile(): Promise<SkillHubLockfile>
async writeLockfile(lockfile: SkillHubLockfile): Promise<void>
// 来源追踪
async writeOrigin(name: string, origin: SkillOrigin): Promise<void>
async readOrigin(name: string): Promise<SkillOrigin | null>
// 内容指纹
computeFingerprint(skillDir: string): Promise<string>
// 数据库同步
async syncToDatabase(): Promise<void>
```
### 4.2 内置工具skill_manage + read_skill新增
**skill_manage** — Agent 自主管理 skillCRUD 操作):
```typescript
// tools/builtin/skill_manage.ts
{
name: 'skill_manage',
description: 'Agent 自主创建/编辑/删除 skill',
parameters: Type.Object({
action: Type.Union([
Type.Literal('create'),
Type.Literal('edit'),
Type.Literal('delete'),
]),
name: Type.String(),
content: Type.Optional(Type.String()), // SKILL.md 完整内容
}),
execute: async (id, params) => { ... }
}
```
**read_skill** — Agent 按需读取完整 SKILL.md 内容(渐进式披露的运行时入口):
```typescript
// tools/builtin/read_skill.ts
{
name: 'read_skill',
description: '读取指定 skill 的完整 SKILL.md 内容',
parameters: Type.Object({
name: Type.String({ description: 'skill 名称' }),
}),
execute: async (id, params) => {
return skillLoader.getSkillContent(params.name);
}
}
```
> **设计决策**将管理操作skill_manage和运行时读取read_skill拆分为两个工具职责清晰。不复用 file_read 工具,避免 agent 直接访问文件系统路径。
### 4.3 Controller 改造
**现有问题**:当前 `skill.ts` 用原生 `@Controller`,只有 2 个手写接口。需要改为 `@CoolController` + 自定义接口混合模式。
```typescript
@CoolController({
api: ['info', 'list', 'page'], // 自动生成:详情/列表/分页
entity: NetaClawSkillEntity,
pageQueryOp: {
keyWordLikeFields: ['name', 'label', 'description'],
fieldEq: ['status', 'source', 'skillType'],
addOrderBy: { createTime: 'DESC' },
},
})
export class AdminNetaClawSkillController extends BaseController {
// 以下为自定义接口(手写)
@Get('/metas') // 已启用 skill 元数据快照
@Post('/install') // 安装 skillGitHub URL
@Post('/uninstall') // 卸载 skill
@Post('/update') // 更新 skill重新拉取
@Post('/setStatus') // 启用/禁用
@Get('/content') // 按需获取完整 SKILL.md运行时用
@Post('/checkUpdates') // 检查所有 skill 是否有更新
@Post('/installDeps') // 手动触发依赖安装
@Post('/create') // Agent 自主创建 skill
@Post('/edit') // Agent 自主编辑 skill
}
```
> **注意**:不生成 `add`/`delete`/`update` CRUD——skill 的增删改通过 `install`/`uninstall`/`update` 自定义接口处理,语义更清晰。
### 4.4 API 路由总览
| 方法 | 路径 | 来源 | 说明 |
|------|------|------|------|
| POST | `/admin/netaclaw/skill/page` | @CoolController | 分页列表keyWord/status/source/skillType 筛选) |
| POST | `/admin/netaclaw/skill/list` | @CoolController | 全量列表 |
| GET | `/admin/netaclaw/skill/info` | @CoolController | 单条详情query: id |
| GET | `/admin/netaclaw/skill/metas` | 自定义 | 已启用 skill 元数据快照 |
| POST | `/admin/netaclaw/skill/install` | 自定义 | 安装 skill |
| POST | `/admin/netaclaw/skill/uninstall` | 自定义 | 卸载 skill |
| POST | `/admin/netaclaw/skill/update` | 自定义 | 更新 skill重新拉取 |
| POST | `/admin/netaclaw/skill/setStatus` | 自定义 | 启用/禁用 |
| GET | `/admin/netaclaw/skill/content` | 自定义 | 按需获取完整 SKILL.md运行时用 |
| POST | `/admin/netaclaw/skill/checkUpdates` | 自定义 | 检查更新 |
| POST | `/admin/netaclaw/skill/installDeps` | 自定义 | 手动触发依赖安装 |
| POST | `/admin/netaclaw/skill/create` | 自定义 | Agent 自主创建 skill |
| POST | `/admin/netaclaw/skill/edit` | 自定义 | Agent 自主编辑 skill |
**安装请求示例:**
```json
{
"url": "https://github.com/user/my-skill",
"source": "github",
"branch": "main",
"version": "v1.0.0"
}
```
## 5. 运行时集成
### 5.1 渐进式披露(对齐 Hermes
System prompt 只注入 skill 索引name + description不注入完整内容
```xml
<available_skills>
<skill>
<name>damage-detector</name>
<description>检测车身视频帧中的旧伤和划痕</description>
</skill>
<skill>
<name>claim-audit</name>
<description>理赔订单审核</description>
</skill>
</available_skills>
当你需要使用某个 skill 时,调用 read_skill 工具读取完整指令后再执行。
```
Agent 按需加载完整内容的方式:调用 `read_skill` 内置工具(见 4.2),返回完整 SKILL.md 正文。
> **与现有实现的差异**:现有 `getSkillPrompt()` 把完整 content 拼进 system prompt需要改为只拼索引。
### 5.2 条件激活(对齐 Hermes
Skill frontmatter 可声明:
- `requires_tools`: 必须全部可用才显示在索引中
- `fallback_for_tools`: 当指定工具不可用时才显示
SkillLoaderService 在构建 prompt 时根据 agent 配置的工具集过滤。
### 5.3 双入口统一改造Gateway + Chat Controller
**现有问题**`gateway/server.ts`WebSocket正确传入 `agentInfo.skills`,但 `chat.ts`HTTP硬编码空数组 `[]`。两个入口的 skill 加载逻辑不一致。
**改造方案**:统一为相同的 skill 加载 + 工具注册逻辑:
```typescript
// 两个入口共用的逻辑(抽取到 service 或 helper
function buildSkillContext(skillLoader, skillInstaller, agentInfo, defaultTools) {
const skillNames = agentInfo?.skills || [];
const availableToolNames = defaultTools.map(t => t.name);
const skillPrompt = skillLoader.buildSkillsPrompt(skillNames, availableToolNames);
// 注册 skill 相关内置工具
const skillTools = [
createSkillManageTool(skillLoader, skillInstaller),
createReadSkillTool(skillLoader),
];
return { skillPrompt, skillTools };
}
// gateway/server.ts
const { skillPrompt, skillTools } = buildSkillContext(...);
agentConfig.systemPrompt = (agentInfo.systemPrompt || '') + skillPrompt;
tools = [...this.defaultTools, ...memoryTools, ...skillTools];
// chat.ts — 同样的逻辑,不再硬编码空数组
const { skillPrompt, skillTools } = buildSkillContext(...);
```
## 6. 前端设计
### 6.1 API 调用规范修正
**现有问题**`skills.vue``agent-edit.vue``skill-detail.vue``skill-config.vue` 全部用 `fetch()` 直接调用 API绕过了 Cool Admin 的 service 代理。违反项目规范。
**改造要求**:所有 API 调用改为 Cool Admin 方式:
```typescript
const { service } = useCool();
// @CoolController 自动生成的接口
await service.netaclaw.skill.page({ page: 1, size: 20, keyWord: '...', status: 1 });
await service.netaclaw.skill.info({ id: 1 });
// 自定义接口用 service.request
await service.request({
url: '/admin/netaclaw/skill/install',
method: 'POST',
data: { url: 'https://github.com/user/my-skill' }
});
await service.request({ url: '/admin/netaclaw/skill/metas' });
```
### 6.2 Skill 管理页面(`/agent/skills` 改造)
**顶部操作栏:**
- 安装按钮 → 弹出对话框输入 GitHub URL
- 筛选来源local/github、状态启用/禁用、类型compute/llm/multimodal
**Skill 卡片列表:**
- 卡片emoji + name + description + skillType 标签 + source 标签 + 状态开关
- 操作:启用/禁用、更新、卸载、查看详情
**安装对话框:**
- GitHub URL 输入框
- 可选分支/tag
- 安装进度实时反馈(通过 WebSocket 复用现有 Socket.IO 连接,发送 `skill_install_progress` 事件)
**详情抽屉(替代老的 skill-detail + skill-config + skill-prompts**
- SKILL.md 渲染markdown-it
- 依赖状态列表(已安装/未安装,可手动触发安装)
- 来源信息URL、安装时间、版本、指纹
- 条件激活规则展示
### 6.3 Agent 编辑页面集成(`/agent/agent-edit` 改造)
现有 "Skill 配置" TabTab 2改造
- 左侧:可用 skill 列表(带搜索和分类筛选)
- 右侧:已选 skill 列表(可移除)
- 每个 skill 卡片显示 emoji + name + description + skillType 标签
- API 调用同样改为 Cool Admin service 代理
### 6.4 安装进度实时反馈
复用现有 Socket.IO 连接,新增事件类型:
| 事件 | 数据 | 说明 |
|------|------|------|
| `skill_install_progress` | `{step, percent, message}` | 安装进度 |
| `skill_install_done` | `{name, success, error?}` | 安装完成 |
| `skill_deps_progress` | `{name, step, percent}` | 依赖安装进度 |
## 7. 清理工作
### 7.1 移除老 skill 及相关代码
**后端删除:**
- `skills/` 下所有老 skill 目录(保留目录本身,清空内容)
- 数据库 `netaclaw_skill` 表中老数据(清空)
**后端改造(非删除):**
- `entity/skill.ts` — 移除 `icon``category``config` 字段,新增 6 个字段
- `service/skill_loader.ts` — 完全重写
- `controller/skill.ts` — 改为 @CoolController + 自定义接口
**前端删除:**
- `components/skill-config.vue` — 老的参数配置表单prompt-based skill 不需要)
- `components/skill-prompts.vue` — 老的提示词编辑prompt-based skill 不需要)
- `components/skill-model.vue` — 老的模型配置(如果存在)
**前端改造(非删除):**
- `views/skills.vue` — 重写,改用 Cool Admin service 代理
- `views/agent-edit.vue` — Skill Tab 改造
- `components/skill-detail.vue` — 改为新的详情抽屉
### 7.2 保留的基础设施
- `netaclaw_skill` Entity 文件(字段改造)
- `entities.ts` 中的 Entity 注册(无需改动)
- skill controller 路由前缀 `/admin/netaclaw/skill`
- 前端 `/agent/skills` 路由和模块配置(`config.ts` 无需改动)
## 8. 安全考虑
- GitHub clone 只允许 `github.com` 域名
- npm/uv 包名正则白名单校验(防命令注入,对齐 OpenClaw 的 SAFE_*_PACKAGE 正则)
- SKILL.md 文件大小限制256KB
- skill 目录数量限制(最多 200 个)
- system prompt 中 skill 索引总量限制30,000 字符)
- 路径遍历防护skill 目录不能逃逸出 skills/ 根目录,使用 realpath 校验)
## 9. GitHub 版本更新检测
**机制**`checkUpdates()` 通过 GitHub API 查询最新 commit hash与本地 `.skillhub/origins/{name}.json` 中记录的 commit hash 比较。
```typescript
// skill_installer.ts
async checkUpdates(): Promise<Array<{ name: string; hasUpdate: boolean; currentHash: string; latestHash: string }>> {
// 1. 遍历所有 source=github 的 skill
// 2. 读取 origin.json 获取 sourceUrl + 安装时的 commit hash
// 3. 调用 GitHub API: GET /repos/{owner}/{repo}/commits?per_page=1
// 4. 比较 commit hash
}
```
**origin.json 结构**
```json
{
"version": 1,
"source": "github",
"url": "https://github.com/user/my-skill",
"branch": "main",
"commitHash": "abc123...",
"installedAt": "2026-04-13T10:00:00Z"
}
```