471 lines
17 KiB
Markdown
471 lines
17 KiB
Markdown
|
|
# 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 自主管理 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
|
|||
|
|
<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 配置" 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<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"
|
|||
|
|
}
|
|||
|
|
```
|