# 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 // 渐进式披露:只返回索引(name + description) buildSkillsIndex(skillNames: string[]): string // 按需加载完整 SKILL.md 内容 getSkillContent(name: string): string | null // 条件过滤:根据 agent 可用工具过滤 skill filterByConditions(skills: SkillEntry[], availableTools: string[]): SkillEntry[] // 构建 XML buildSkillsPrompt(skillNames: string[], availableTools: string[]): string ``` #### SkillInstallerService(新增) ```typescript // GitHub 安装 async installFromGitHub(url: string, options?: { branch?: string; tag?: string }): Promise // 依赖安装(node/uv) async installDependencies(skillName: string): Promise<{ success: boolean; logs: string[] }> // 卸载 async uninstall(skillName: string): Promise // 更新(重新拉取) async update(skillName: string): Promise // 检查更新 async checkUpdates(): Promise> ``` 安装器安全校验(对齐 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 async writeLockfile(lockfile: SkillHubLockfile): Promise // 来源追踪 async writeOrigin(name: string, origin: SkillOrigin): Promise async readOrigin(name: string): Promise // 内容指纹 computeFingerprint(skillDir: string): Promise // 数据库同步 async syncToDatabase(): Promise ``` ### 4.2 内置工具:skill_manage + read_skill(新增) **skill_manage** — Agent 自主管理 skill(CRUD 操作): ```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') // 安装 skill(GitHub 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 damage-detector 检测车身视频帧中的旧伤和划痕 claim-audit 理赔订单审核 当你需要使用某个 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 配置" Tab(Tab 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> { // 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" } ```