GPU_GUARD_MONOREPO/docs/superpowers/specs/2026-04-13-skill-system-migration-design.md
2026-05-20 21:39:12 +08:00

17 KiB
Raw Blame History

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 继承 BaseEntitysynchronize: 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无法解析嵌套结构

---
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.tsskills: 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-yamlYAML 解析,替代现有的正则解析)

SkillLoaderService改造

// 多来源扫描
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新增

// 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新增

// 读写 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 操作):

// 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 内容(渐进式披露的运行时入口):

// 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 + 自定义接口混合模式。

@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

安装请求示例:

{
  "url": "https://github.com/user/my-skill",
  "source": "github",
  "branch": "main",
  "version": "v1.0.0"
}

5. 运行时集成

5.1 渐进式披露(对齐 Hermes

System prompt 只注入 skill 索引name + description不注入完整内容

<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.tsWebSocket正确传入 agentInfo.skills,但 chat.tsHTTP硬编码空数组 []。两个入口的 skill 加载逻辑不一致。

改造方案:统一为相同的 skill 加载 + 工具注册逻辑:

// 两个入口共用的逻辑(抽取到 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.vueagent-edit.vueskill-detail.vueskill-config.vue 全部用 fetch() 直接调用 API绕过了 Cool Admin 的 service 代理。违反项目规范。

改造要求:所有 API 调用改为 Cool Admin 方式:

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 — 移除 iconcategoryconfig 字段,新增 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 比较。

// 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 结构

{
  "version": 1,
  "source": "github",
  "url": "https://github.com/user/my-skill",
  "branch": "main",
  "commitHash": "abc123...",
  "installedAt": "2026-04-13T10:00:00Z"
}