主题
OpenClaw 源码解读(十九)技能系统与 ClawHub
本篇深入分析 OpenClaw 的技能(Skill)系统——一种基于 Markdown 的声明式 Agent 能力扩展机制。从 SKILL.md 文件格式到六源加载引擎,从 Frontmatter 解析到安全扫描器,从 Prompt 构建到 ClawHub 生态。技能系统让 Agent 从通用 AI 助手进化为领域专家,而无需编写一行代码。
目录
- 一、技能系统全景
- 二、SKILL.md 文件格式
- 三、Frontmatter 解析引擎
- 四、六源加载与优先级合并
- 五、技能资格判定与过滤
- 六、Prompt 构建与上下文预算
- 七、技能安全扫描器
- 八、斜杠命令生成
- 九、沙箱同步机制
- 十、ClawHub 生态
- 十一、内置技能巡览
- 十二、设计模式与架构总结
一、技能系统全景
1.1 技能是什么?
技能(Skill)是 OpenClaw 的声明式能力扩展机制。与插件(Plugin)不同,技能不需要编写任何代码——它只是一个 Markdown 文件(SKILL.md),告诉 Agent "你现在拥有这个领域的知识和能力"。
Plugin(插件)= TypeScript 代码 → 运行时注册新功能
Skill(技能)= Markdown 文档 → 注入到 Agent 的 System Prompt 中核心理念: Agent(LLM)已经非常聪明了,大多数任务它只需要正确的指导(而非新代码)就能完成。技能就是提供这种指导的标准化载体。
1.2 三层渐进式加载
技能使用渐进式上下文披露设计,最大化利用有限的上下文窗口:
Layer 1: 元数据(name + description)
├── 始终存在于 Agent 上下文中
├── ~100 tokens
└── Agent 用它决定"是否需要触发这个技能"
Layer 2: SKILL.md 正文
├── 技能被触发后加载
├── <5000 words
└── 包含具体的操作指令和工作流
Layer 3: 捆绑资源(scripts/ + references/ + assets/)
├── Agent 按需加载
├── 无大小限制(脚本可直接执行而不读入上下文)
└── 只加载当前任务需要的部分技能系统与 ClawHub/01-infographic-skill-system-overview-1775150757559.png)
1.3 核心文件清单
| 文件 | 职责 |
|---|---|
src/agents/skills/types.ts | 核心类型定义(SkillEntry, SkillSnapshot, SkillMetadata 等) |
src/agents/skills/workspace.ts | 主入口 — 六源加载 + 合并 + Prompt 构建 + 沙箱同步 |
src/agents/skills/config.ts | 资格判定 + 配置解析 + 白名单过滤 |
src/agents/skills/frontmatter.ts | YAML Frontmatter 解析 + 元数据提取 |
src/security/skill-scanner.ts | 安全扫描(危险代码检测) |
skills/*/SKILL.md | 52 个内置技能定义 |
skills/clawhub/SKILL.md | ClawHub CLI 技能 |
skills/skill-creator/SKILL.md | 技能创建指南(元技能) |
二、SKILL.md 文件格式
2.1 基本结构
每个技能都是一个目录,包含一个必需的 SKILL.md 文件和可选的捆绑资源:
weather/
├── SKILL.md ← 必需:技能定义
├── scripts/ ← 可选:可执行脚本
├── references/ ← 可选:参考文档
└── assets/ ← 可选:输出资源(模板、图标等)2.2 SKILL.md 格式
markdown
---
name: weather
description: "Get current weather and forecasts via wttr.in. Use when:
user asks about weather, temperature, or forecasts for any location."
homepage: https://wttr.in/:help
metadata: { "openclaw": { "emoji": "🌤️", "requires": { "bins": ["curl"] } } }
---
# Weather Skill
Get current weather conditions and forecasts.
## When to Use
✅ **USE this skill when:**
- "What's the weather?"
- "Will it rain today/tomorrow?"
## Commands
### Current Weather
```bash
curl "wttr.in/London?format=3"
```2.3 Frontmatter 字段说明
| 字段 | 必需 | 说明 |
|---|---|---|
name | ✅ | 技能唯一标识(用于去重、过滤、命令名) |
description | ✅ | 技能描述(LLM 用它决定何时触发,非常重要) |
homepage | ❌ | 技能主页 URL |
metadata | ❌ | OpenClaw 扩展元数据(环境要求、安装方式等) |
command-dispatch | ❌ | 斜杠命令分发方式("tool" = 直接调用工具) |
command-tool | ❌ | 配合 command-dispatch: tool 指定目标工具名 |
2.4 元数据结构 (metadata.openclaw)
jsonc
{
"openclaw": {
"emoji": "🌤️", // UI 显示的表情符号
"primaryEnv": "WEATHER_API_KEY", // 主要环境变量(用于资格判定)
"requires": {
"env": ["WEATHER_API_KEY"], // 必需的环境变量
"bins": ["curl"], // 必需的二进制命令
"platforms": ["darwin", "linux"] // 支持的平台
},
"install": [ // 安装指南
{
"id": "brew",
"kind": "brew",
"package": "curl",
"label": "Install via Homebrew"
}
]
}
}2.5 正文设计原则
技能正文遵循**"少即是多"**原则:
默认假设:Agent 已经非常聪明了。 只添加 Agent 不知道的信息。每一段文字都要问自己:"Agent 真的需要这个解释吗?" "这段话值得它占用的 token 吗?"
最佳实践:
- 正文控制在 500 行以内
- 用简洁的代码示例替代冗长的文字说明
- 复杂内容拆分到
references/目录 - 包含明确的 "When to Use" 和 "When NOT to Use" 章节
技能系统与 ClawHub/02-infographic-skillmd-format-1775150758383.png)
三、Frontmatter 解析引擎
3.1 解析流程 (src/agents/skills/frontmatter.ts)
typescript
export function parseFrontmatter(raw: string): ParsedSkillFrontmatter {
// 1. 提取 YAML frontmatter(--- 包裹的部分)
const match = raw.match(/^---\n([\s\S]*?)\n---/);
if (!match) return {};
// 2. 解析 YAML
const yamlText = match[1];
const parsed = parseYaml(yamlText); // 使用 yaml 库
// 3. 安全验证和类型转换
return {
name: readString(parsed, "name"),
description: readString(parsed, "description"),
homepage: readString(parsed, "homepage"),
metadata: readObject(parsed, "metadata"),
// ... 斜杠命令相关字段
};
}3.2 OpenClaw 元数据提取
typescript
export function resolveOpenClawMetadata(
frontmatter: ParsedSkillFrontmatter,
): OpenClawSkillMetadata | undefined {
const raw = frontmatter?.metadata;
if (!raw || typeof raw !== "object") return undefined;
// metadata.openclaw 是 OpenClaw 特有的命名空间
const openclawMeta = (raw as Record<string, unknown>)["openclaw"];
if (!openclawMeta || typeof openclawMeta !== "object") return undefined;
const oc = openclawMeta as Record<string, unknown>;
return {
emoji: readString(oc, "emoji"),
primaryEnv: readString(oc, "primaryEnv"),
requires: resolveRequires(oc["requires"]), // { env, bins, platforms }
install: resolveInstallSpecs(oc["install"]), // 安装方式数组
};
}3.3 调用策略解析
typescript
export function resolveSkillInvocationPolicy(
frontmatter: ParsedSkillFrontmatter,
): SkillInvocationPolicy {
return {
// disableModelInvocation: true → 技能不出现在 Agent 的 prompt 中
// (仅通过斜杠命令或 API 触发)
disableModelInvocation: readBool(frontmatter, "disable-model-invocation"),
// userInvocable: false → 不生成斜杠命令
userInvocable: readBool(frontmatter, "user-invocable") ?? true,
};
}技能系统与 ClawHub/03-infographic-frontmatter-parser-1775150759172.png)
四、六源加载与优先级合并
4.1 六个技能来源
这是技能系统最核心的设计之一——技能可以来自 6 个不同的来源,按优先级合并:
来源 1: extra(最低优先级)
├── 配置中的 skills.load.extraDirs
└── 插件提供的 skill 目录
来源 2: bundled(内置)
├── OpenClaw 安装目录下的 skills/
└── 52 个内置技能
来源 3: managed(托管)
├── ~/.openclaw/skills/
└── 通过 ClawHub 安装的技能
来源 4: agents-skills-personal(个人全局)
├── ~/.agents/skills/
└── 跨项目的个人技能
来源 5: agents-skills-project(项目级)
├── <工作区>/.agents/skills/
└── 项目特有的技能
来源 6: workspace(最高优先级)
├── <工作区>/skills/
└── 用户手动放置的技能4.2 合并规则
typescript
const merged = new Map<string, Skill>();
// 低优先级先写入,高优先级覆盖
for (const skill of extraSkills) merged.set(skill.name, skill); // 最低
for (const skill of bundledSkills) merged.set(skill.name, skill);
for (const skill of managedSkills) merged.set(skill.name, skill);
for (const skill of personalAgentsSkills) merged.set(skill.name, skill);
for (const skill of projectAgentsSkills) merged.set(skill.name, skill);
for (const skill of workspaceSkills) merged.set(skill.name, skill); // 最高为什么这样设计? 用户最近放置的技能应该覆盖内置技能。例如,用户可以在 <工作区>/skills/weather/SKILL.md 中放置一个自定义的天气技能,覆盖内置的 weather 技能。
4.3 加载安全限制
每个来源的加载都有严格的资源限制:
typescript
const DEFAULT_LIMITS = {
maxSkillsLoadedPerSource: 200, // 每个来源最多 200 个技能
maxCandidatesPerRoot: 500, // 每个根目录最多扫描 500 个子目录
maxSkillFileBytes: 256 * 1024, // 单个 SKILL.md 最大 256KB
maxSkillsInPrompt: 80, // Prompt 中最多包含 80 个技能
maxSkillsPromptChars: 200_000, // Prompt 中技能总字符数上限 200K
};为什么需要限制?
- 防止恶意技能目录(包含数万个子目录)导致加载超时
- 防止超大 SKILL.md 文件(如 1GB 的二进制数据伪装成 Markdown)导致 OOM
- 防止 Prompt 注入超过 LLM 上下文窗口
4.4 异常目录检测
typescript
const childDirs = listChildDirectories(baseDir);
const suspicious = childDirs.length > limits.maxCandidatesPerRoot;
if (suspicious) {
skillsLogger.warn("Skills root looks suspiciously large, truncating discovery.", {
dir: params.dir,
childDirCount: childDirs.length,
});
}如果一个技能根目录下有超过 500 个子目录,会被标记为可疑并截断扫描。
技能系统与 ClawHub/04-infographic-six-source-loading-1775150759950.png)
五、技能资格判定与过滤
5.1 过滤流程 (src/agents/skills/config.ts)
加载完所有技能后,需要根据当前环境决定哪些技能有资格被包含到 Agent 的 Prompt 中:
加载的全部技能
│
├─ Filter 1: skillFilter(配置指定的技能白名单)
│ └─ 如果配置了 skills.filter: ["weather", "github"],只保留这两个
│
├─ Filter 2: bundled allowlist(内置技能白名单)
│ └─ 如果配置了 skills.bundled.allowlist: ["weather"]
│ 则只保留内置技能中的 weather
│
├─ Filter 3: 平台检查
│ └─ requires.platforms 不包含当前 OS?→ 排除
│
├─ Filter 4: 环境变量检查
│ └─ requires.env 中有未设置的变量?→ 排除
│
├─ Filter 5: 二进制命令检查
│ └─ requires.bins 中有未安装的命令?→ 排除
│
└─ 输出:eligible 技能列表5.2 资格上下文
typescript
export type SkillEligibilityContext = {
platform?: NodeJS.Platform; // "darwin" | "linux" | "win32"
envKeys?: Set<string>; // 当前已设置的环境变量名
availableBins?: Set<string>; // 已安装的二进制命令名
remote?: { // 远程执行上下文
note?: string; // 远程注入的说明
envKeys?: Set<string>;
};
};5.3 shouldIncludeSkill 决策函数
typescript
export function shouldIncludeSkill(
entry: SkillEntry,
config?: OpenClawConfig,
skillFilter?: string[],
eligibility?: SkillEligibilityContext,
): boolean {
// 1. 名称过滤
if (skillFilter && !skillFilter.includes(entry.skill.name)) {
return false;
}
// 2. 内置技能白名单
if (entry.skill.source === "openclaw-bundled") {
if (!isBundledSkillAllowed(entry.skill.name, config)) {
return false;
}
}
// 3. 平台要求
const platforms = entry.metadata?.requires?.platforms;
if (platforms?.length && eligibility?.platform) {
if (!platforms.includes(eligibility.platform)) {
return false;
}
}
// 4. 环境变量要求
const envReqs = entry.metadata?.requires?.env;
if (envReqs?.length && eligibility?.envKeys) {
const allMet = envReqs.every(e => eligibility.envKeys!.has(e));
if (!allMet) return false;
}
// 5. 二进制命令要求
const binReqs = entry.metadata?.requires?.bins;
if (binReqs?.length && eligibility?.availableBins) {
const allMet = binReqs.every(b => eligibility.availableBins!.has(b));
if (!allMet) return false;
}
return true;
}技能系统与 ClawHub/05-infographic-eligibility-filter-1775150760821.png)
六、Prompt 构建与上下文预算
6.1 构建流程
eligible 技能列表
│
├─ 排除 disableModelInvocation=true 的技能
│ (这些技能只通过斜杠命令触发,不占用 Prompt 空间)
│
├─ 应用数量上限(maxSkillsInPrompt = 80)
│
├─ 应用字符预算(maxSkillsPromptChars = 200,000)
│ └─ 二分搜索找到不超预算的最大前缀
│
├─ 路径压缩(compactSkillPaths)
│ └─ 将绝对路径压缩为相对路径,节省 token
│
└─ 格式化输出6.2 二分搜索字符预算
当技能总量超过字符预算时,使用二分搜索找到能装入预算的最大技能数:
typescript
function applySkillsPromptLimits(params) {
const { skills, config } = params;
const limits = resolveSkillsLimits(config);
// 先按数量截断
let skillsForPrompt = skills.slice(0, limits.maxSkillsInPrompt);
const fits = (skills: Skill[]): boolean => {
const block = formatSkillsForPrompt(skills);
return block.length <= limits.maxSkillsPromptChars;
};
if (!fits(skillsForPrompt)) {
// 二分搜索:找到最大的 N 使得前 N 个技能的 Prompt 长度 ≤ 预算
let lo = 0;
let hi = skillsForPrompt.length;
while (lo < hi) {
const mid = Math.ceil((lo + hi) / 2);
if (fits(skillsForPrompt.slice(0, mid))) {
lo = mid;
} else {
hi = mid - 1;
}
}
skillsForPrompt = skillsForPrompt.slice(0, lo);
}
return { skillsForPrompt, truncated: ... };
}为什么用二分搜索而非逐个添加? 因为 formatSkillsForPrompt() 需要序列化整个列表(开销较大),二分搜索只需要 O(log N) 次调用。
6.3 SkillSnapshot 快照
Prompt 构建完成后生成一个快照,供运行时使用:
typescript
type SkillSnapshot = {
prompt: string; // 最终注入 Agent Prompt 的文本
skills: Array<{
name: string; // 技能名
primaryEnv?: string; // 主要环境变量(UI 显示用)
requiredEnv?: string[]; // 所需环境变量(UI 显示用)
}>;
skillFilter?: string[]; // 应用的过滤器
resolvedSkills: Skill[]; // 完整的技能对象(含文件路径等)
version?: number; // 快照版本号
};技能系统与 ClawHub/06-infographic-prompt-budget-1775150761549.png)
七、技能安全扫描器
7.1 为什么需要安全扫描?
技能可以来自第三方(ClawHub 或手动安装),可能包含恶意代码。虽然 SKILL.md 本身只是 Markdown,但技能目录中可能包含 scripts/ 下的可执行脚本。
7.2 扫描目标
扫描器检查技能目录中的 JavaScript/TypeScript 文件:
typescript
const SCANNABLE_EXTENSIONS = new Set([
".js", ".ts", ".mjs", ".cjs", ".mts", ".cts", ".jsx", ".tsx",
]);7.3 扫描规则
行级规则(逐行匹配):
| 规则 ID | 严重性 | 检测内容 | 正则模式 |
|---|---|---|---|
dangerous-exec | 🔴 critical | Shell 命令执行 | exec|spawn|execFile + child_process |
dynamic-code-execution | 🔴 critical | 动态代码执行 | eval( | new Function( |
crypto-mining | 🔴 critical | 加密货币挖矿 | stratum+tcp|coinhive|xmrig |
suspicious-network | 🟡 warn | 非标准端口 WebSocket | new WebSocket("wss://...:PORT") |
源码级规则(全文匹配):
| 规则 ID | 严重性 | 检测内容 | 检测逻辑 |
|---|---|---|---|
potential-exfiltration | 🟡 warn | 数据泄露 | readFile + fetch/post/http.request 同时出现 |
obfuscated-code | 🟡 warn | 混淆代码 | 连续 6+ 个 \xHH 或 200+ 字符的 base64 |
env-harvesting | 🔴 critical | 凭证窃取 | process.env + fetch/post/http.request 同时出现 |
7.4 扫描架构
scanSkillDirectory(dirPath)
│
├─ 递归遍历目录(最大深度限制)
│ ├─ 跳过 node_modules/
│ ├─ 跳过 .git/
│ └─ 限制最多 500 个文件
│
├─ 对每个可扫描文件:
│ ├─ 检查文件大小(≤ 1MB)
│ ├─ 检查缓存(mtimeMs + size 未变 → 跳过)
│ ├─ 读取文件内容
│ ├─ 执行行级规则
│ ├─ 执行源码级规则
│ └─ 缓存结果
│
└─ 返回 SkillScanSummary
{ scannedFiles, critical, warn, info, findings }7.5 缓存机制
扫描结果使用文件指纹缓存(size + mtimeMs),避免重复扫描未修改的文件:
typescript
function getCachedFileScanResult(params: {
filePath: string;
size: number;
mtimeMs: number;
maxFileBytes: number;
}): FileScanCacheEntry | undefined {
const cached = FILE_SCAN_CACHE.get(params.filePath);
if (!cached) return undefined;
if (
cached.size !== params.size ||
cached.mtimeMs !== params.mtimeMs ||
cached.maxFileBytes !== params.maxFileBytes
) {
FILE_SCAN_CACHE.delete(params.filePath);
return undefined;
}
return cached;
}缓存上限 5000 条,超出时采用 FIFO 淘汰。
7.6 上下文关联规则
某些规则需要两个模式同时出现才触发(减少误报):
typescript
{
ruleId: "env-harvesting",
severity: "critical",
pattern: /process\.env/, // 主模式
requiresContext: /\bfetch\b|\bpost\b|http\.request/i, // 上下文模式
// 只有当文件同时包含 process.env 和网络请求时才报告
}单独使用 process.env 是完全正常的(读取配置),但如果同时存在网络请求,可能是在窃取环境变量中的凭证。
技能系统与 ClawHub/07-infographic-security-scanner-1775150762287.png)
八、斜杠命令生成
8.1 从技能到斜杠命令
每个有资格的技能都会自动生成一个斜杠命令,供用户在消息渠道中触发:
技能 "weather" → 斜杠命令 /weather
技能 "gh-issues" → 斜杠命令 /gh-issues
技能 "openai-image-gen" → 斜杠命令 /openai-image-gen8.2 命令名归一化
typescript
function sanitizeSkillCommandName(rawName: string): string {
return rawName
.toLowerCase()
.replace(/[^a-z0-9_-]/g, "-") // 非法字符替换为 "-"
.replace(/^-+|-+$/g, "") // 去掉前后 "-"
.slice(0, 32); // 最大 32 字符
}8.3 命令去重
如果多个技能归一化后命名相同,会自动添加数字后缀:
typescript
function resolveUniqueSkillCommandName(base: string, used: Set<string>): string {
if (!used.has(base.toLowerCase())) return base;
for (let i = 2; i < 10_000; i++) {
const candidate = `${base}-${i}`;
if (!used.has(candidate.toLowerCase())) return candidate;
}
// 极端情况的 fallback...
}8.4 工具分发命令
某些技能可以配置为直接调用工具而非注入 Prompt:
yaml
---
name: camsnap
description: Take a photo using the phone camera
command-dispatch: tool
command-tool: nodes
command-arg-mode: raw
---当用户输入 /camsnap front 时,系统直接调用 nodes 工具的 camera_snap 操作,而非将消息发给 Agent 处理。
8.5 保留命令名
系统命令(如 /help、/model、/session)的名称被预留,技能命令不能与它们冲突:
typescript
const specs = buildWorkspaceSkillCommandSpecs(workspaceDir, {
reservedNames: new Set(["help", "model", "session", "status", ...]),
});技能系统与 ClawHub/08-infographic-slash-commands-1775150763304.png)
九、沙箱同步机制
9.1 为什么需要同步?
当 Agent 在 Docker 沙箱中运行时,沙箱内没有主机上的技能文件。syncSkillsToWorkspace 将技能从主机复制到沙箱工作区:
主机文件系统 Docker 沙箱
├── ~/.openclaw/skills/weather/
├── ~/workspace/skills/github/
├── /usr/lib/.../skills/canvas/
│ ├── /sandbox/skills/weather/
│ syncSkillsToWorkspace() ──→ ├── /sandbox/skills/github/
│ └── /sandbox/skills/canvas/9.2 同步流程
typescript
export async function syncSkillsToWorkspace(params: {
sourceWorkspaceDir: string; // 主机工作区
targetWorkspaceDir: string; // 沙箱工作区
config?: OpenClawConfig;
}) {
// 1. 如果源和目标相同,跳过(非沙箱模式)
if (sourceDir === targetDir) return;
// 2. 序列化执行(防止并发同步)
await serializeByKey(`syncSkills:${targetDir}`, async () => {
// 3. 加载所有来源的技能
const entries = loadSkillEntries(sourceDir, { ... });
// 4. 清空目标目录
await fsp.rm(targetSkillsDir, { recursive: true, force: true });
await fsp.mkdir(targetSkillsDir, { recursive: true });
// 5. 逐个复制(安全路径解析 + 名称去重)
for (const entry of entries) {
const dest = resolveSyncedSkillDestinationPath({ ... });
await fsp.cp(entry.skill.baseDir, dest, { recursive: true });
}
});
}9.3 路径安全
目标路径经过沙箱路径解析(resolveSandboxPath),确保技能文件不会被复制到沙箱工作区之外:
typescript
const dest = resolveSandboxPath({
filePath: uniqueDirName,
cwd: targetSkillsDir,
root: targetSkillsDir, // 不能逃出这个目录
}).resolved;技能系统与 ClawHub/09-infographic-sandbox-sync-1775150764104.png)
十、ClawHub 生态
10.1 ClawHub 是什么?
ClawHub(clawhub.com)是 OpenClaw 的技能分发平台——类似于 npm 之于 Node.js,或 Docker Hub 之于容器。用户可以在 ClawHub 上搜索、安装、更新和发布技能。
10.2 ClawHub CLI
ClawHub 本身也是一个技能(skills/clawhub/SKILL.md),通过 npm 安装:
bash
npm i -g clawhub10.3 核心命令
| 命令 | 说明 |
|---|---|
clawhub search "postgres backups" | 搜索技能 |
clawhub install my-skill | 安装技能到 ~/.openclaw/skills/ |
clawhub install my-skill --version 1.2.3 | 安装指定版本 |
clawhub update --all | 更新所有已安装技能 |
clawhub update my-skill --force | 强制更新 |
clawhub list | 列出已安装技能 |
clawhub publish ./my-skill --slug my-skill | 发布技能 |
clawhub login | 登录(发布需要) |
10.4 版本管理
ClawHub 使用基于文件哈希的版本匹配:
bash
clawhub update my-skill
# 1. 计算本地技能文件的哈希
# 2. 在 registry 中找到匹配的版本
# 3. 如果有更新版本,下载并替换10.5 三种分发方式
| 方式 | 目录 | 来源 | 说明 |
|---|---|---|---|
| Bundled | <安装目录>/skills/ | 内置于 OpenClaw | 随版本更新 |
| Managed | ~/.openclaw/skills/ | ClawHub 安装 | 独立于版本更新 |
| Workspace | <工作区>/skills/ | 手动放置 | 最高优先级 |
技能系统与 ClawHub/10-infographic-clawhub-ecosystem-1775150765073.png)
十一、内置技能巡览
11.1 52 个内置技能分类
🖥️ 开发工具(12 个):
| 技能 | 说明 |
|---|---|
github | GitHub CLI 操作(issues/PRs/repos) |
gh-issues | GitHub Issues 管理 |
coding-agent | 编码代理工作流 |
tmux | Tmux 终端复用器操作 |
oracle | 数据库查询辅助 |
sag | Search-and-grep 代码搜索 |
session-logs | 会话日志分析 |
skill-creator | 创建新技能的元技能 |
gog | Go 开发辅助 |
eightctl | 8base 平台操作 |
blucli | Blueink CLI 操作 |
nano-banana-pro | Banana 推理平台 |
🌐 Web & API(6 个):
| 技能 | 说明 |
|---|---|
weather | 天气查询(wttr.in) |
xurl | URL 处理工具 |
blogwatcher | 博客/RSS 监控 |
goplaces | 地点搜索 |
healthcheck | HTTP 健康检查 |
gifgrep | GIF 搜索 |
📱 消息与社交(8 个):
| 技能 | 说明 |
|---|---|
discord | Discord 操作增强 |
slack | Slack 操作增强 |
imsg | iMessage 操作 |
bluebubbles | BlueBubbles iMessage 桥 |
wacli | WhatsApp CLI |
voice-call | 语音通话 |
telegram* | (通过插件提供) |
songsee | 歌曲识别 |
📝 笔记与生产力(7 个):
| 技能 | 说明 |
|---|---|
apple-notes | Apple Notes 操作 |
apple-reminders | Apple Reminders 操作 |
bear-notes | Bear 笔记操作 |
notion | Notion 操作 |
obsidian | Obsidian 笔记操作 |
trello | Trello 看板操作 |
things-mac | Things 任务管理 |
🎨 媒体与 AI(8 个):
| 技能 | 说明 |
|---|---|
openai-image-gen | OpenAI 图像生成(DALL·E) |
openai-whisper | 本地语音转文字 |
openai-whisper-api | Whisper API 语音转文字 |
gemini | Google Gemini 增强 |
canvas | Canvas UI 操作 |
camsnap | 手机拍照 |
peekaboo | 截图分析 |
video-frames | 视频帧提取 |
nano-pdf | PDF 处理 |
🏠 智能家居 & 设备(4 个):
| 技能 | 说明 |
|---|---|
openhue | Philips Hue 灯光控制 |
sonoscli | Sonos 音响控制 |
spotify-player | Spotify 播放控制 |
1password | 1Password 密码管理 |
🔧 系统与运维(5 个):
| 技能 | 说明 |
|---|---|
clawhub | ClawHub 技能管理 |
model-usage | 模型用量统计 |
summarize | 文本总结 |
himalaya | 邮件客户端 |
mcporter | MC Porter 工具 |
ordercli | Order CLI 工具 |
sherpa-onnx-tts | 本地 TTS 引擎 |
11.2 技能结构示例
以 weather 技能为例,展示一个设计良好的技能:
weather/
└── SKILL.md (112 行)
├── Frontmatter:
│ ├── name: weather
│ ├── description: 清晰的使用场景描述
│ └── metadata: { requires: { bins: ["curl"] } }
│
├── When to Use: 5 个匹配场景
├── When NOT to Use: 5 个排除场景
│
└── Commands: 具体的 curl 命令示例
├── 当前天气
├── 预报
├── 格式选项
└── 快速响应模板技能系统与 ClawHub/11-infographic-builtin-skills-1775150765898.png)
十二、设计模式与架构总结
12.1 技能系统数据流
┌──────────────────────────────────────┐
│ 6 个技能来源 │
│ extra < bundled < managed < │
│ personal < project < workspace │
└───────────────┬──────────────────────┘
│ loadSkillEntries()
┌───────────────▼──────────────────────┐
│ 合并 (Map.set 覆盖) │
│ + Frontmatter 解析 │
│ + 元数据提取 │
└───────────────┬──────────────────────┘
│ filterSkillEntries()
┌───────────────▼──────────────────────┐
│ 资格过滤 │
│ 平台 / 环境变量 / 二进制命令 / 白名单 │
└──┬────────────────────────────────┬──┘
│ │
┌────────────▼──────────┐ ┌──────────────▼──────────┐
│ buildSkillsPrompt() │ │ buildSkillCommandSpecs │
│ → Agent System Prompt│ │ → 斜杠命令注册 │
│ │ │ │
│ 二分搜索字符预算 │ │ 名称归一化+去重 │
│ 路径压缩 │ │ 保留名冲突检查 │
└───────────────────────┘ └─────────────────────────┘
┌──────────────────────────────────────┐
│ 安全扫描器 (skill-scanner) │
│ │
│ 行级规则: exec/eval/mining/websocket │
│ 源码级规则: exfiltration/obfuscation │
│ 缓存: 文件指纹 (size+mtime) │
└──────────────────────────────────────┘12.2 关键设计模式
| 模式 | 应用 | 说明 |
|---|---|---|
| 声明式扩展 | SKILL.md | 无需代码,Markdown 即能力 |
| 渐进式披露 | 三层加载 | 元数据 → 正文 → 资源,最小化 token 消耗 |
| 优先级覆盖 | 六源合并 | 用户本地 > 托管 > 内置 |
| 二分搜索 | Prompt 预算 | O(log N) 找到最大装入量 |
| 静态分析 | 安全扫描器 | 无需执行即可检测恶意代码 |
| 资源限制 | 加载限制 | 防止恶意技能目录 DoS |
| 缓存失效 | 文件指纹 | size + mtimeMs 变化即重新扫描 |
| 序列化执行 | 沙箱同步 | serializeByKey 防止并发写入 |
技能系统与 ClawHub/12-infographic-design-patterns-1775150766700.png)
12.3 推荐阅读顺序
| 优先级 | 文件 | 行数 | 难度 |
|---|---|---|---|
| 🔴 必读 | skills/skill-creator/SKILL.md | ~200 | ★☆☆☆☆ |
| 🔴 必读 | agents/skills/types.ts | ~89 | ★★☆☆☆ |
| 🔴 必读 | agents/skills/workspace.ts | ~760 | ★★★★☆ |
| 🟡 推荐 | agents/skills/frontmatter.ts | ~228 | ★★★☆☆ |
| 🟡 推荐 | agents/skills/config.ts | ~102 | ★★☆☆☆ |
| 🟡 推荐 | security/skill-scanner.ts | ~260 | ★★★☆☆ |
| 🟢 选读 | skills/weather/SKILL.md | ~112 | ★☆☆☆☆ |
| 🟢 选读 | skills/clawhub/SKILL.md | ~77 | ★☆☆☆☆ |
| 🟢 选读 | skills/github/SKILL.md | (示例) | ★☆☆☆☆ |
12.4 思考题
为什么技能系统选择 Markdown 文件而非 JSON/YAML 配置?
提示:考虑 LLM 的"母语"——它最擅长理解什么格式的文本?
六源加载中,为什么 workspace(工作区)优先级最高,而 bundled(内置)只排第二?
提示:考虑用户需要覆盖内置技能行为的场景。
安全扫描器为什么用
requiresContext来减少误报?单模式匹配有什么问题?提示:
process.env在正常代码中非常常见,仅匹配它会产生大量误报。Prompt 字符预算为什么用二分搜索而非逐个添加?
提示:考虑
formatSkillsForPrompt()的计算开销——它需要序列化整个列表。如果你要创建一个新技能,应该放在哪个目录?为什么?
提示:根据技能的使用范围选择——个人全局 vs 项目特有 vs 工作区。
📌 小结: 技能系统是 OpenClaw 最具创新性的设计之一——它用最朴素的载体(Markdown 文件)实现了最强大的效果(领域专家能力)。52 个内置技能覆盖了从天气查询到智能家居控制的广泛场景,而 ClawHub 生态则让社区可以共享和分发技能。整个系统的工程设计体现了"简单但不简陋"的哲学——六源加载、渐进式披露、二分搜索预算、静态安全分析,每一个细节都经过精心考量。对于想要理解"如何让 AI Agent 拥有领域知识"的开发者来说,这是一个值得反复学习的参考实现。