@a2hmarket/a2hmarket 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +7 -0
- package/docs/installation-design.md +175 -0
- package/docs/openclaw-plugin-development-guide.md +651 -0
- package/harness/docs/arch/architecture.md +345 -0
- package/harness/docs/pm/requirements.md +249 -0
- package/index.ts +172 -0
- package/openclaw.plugin.json +12 -0
- package/package.json +25 -0
- package/plugin-mechanism-report.md +1323 -0
- package/scripts/install.mjs +425 -0
- package/skills/a2hmarket/SKILL.md +39 -0
- package/skills/a2hmarket/references/commands.md +343 -0
- package/src/agent-service.ts +218 -0
- package/src/api-client.ts +154 -0
- package/src/channel-state.ts +11 -0
- package/src/credentials.ts +131 -0
- package/src/feishu-notify.ts +157 -0
- package/src/last-channel.ts +39 -0
- package/src/mqtt-listener.ts +125 -0
- package/src/mqtt-token.ts +115 -0
- package/src/mqtt-transport.ts +202 -0
- package/src/oss.ts +140 -0
- package/src/protocol.ts +98 -0
- package/src/reply-bridge.ts +106 -0
- package/src/runtime.ts +14 -0
- package/src/signer.ts +19 -0
- package/src/tools/file.ts +27 -0
- package/src/tools/order.ts +108 -0
- package/src/tools/profile.ts +71 -0
- package/src/tools/send.ts +95 -0
- package/src/tools/status.ts +26 -0
- package/src/tools/works.ts +167 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,1323 @@
|
|
|
1
|
+
# OpenClaw Plugin 机制深度研究报告
|
|
2
|
+
|
|
3
|
+
> 生成日期:2026-03-24
|
|
4
|
+
> 基于 `main` 分支 commit `17c1ee7716` 的代码分析
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 目录
|
|
9
|
+
|
|
10
|
+
- [一、通信机制](#一通信机制)
|
|
11
|
+
- [1.1 架构总览](#11-架构总览)
|
|
12
|
+
- [1.2 消息入站流程](#12-消息入站流程)
|
|
13
|
+
- [1.3 访问控制前置检查](#13-访问控制前置检查)
|
|
14
|
+
- [1.4 消息上下文构建](#14-消息上下文构建)
|
|
15
|
+
- [1.5 路由解析与派发](#15-路由解析与派发)
|
|
16
|
+
- [1.6 出站投递与跨 Channel 转发](#16-出站投递与跨-channel-转发)
|
|
17
|
+
- [1.7 核心通信 API 参考](#17-核心通信-api-参考)
|
|
18
|
+
- [1.8 消息缓存与去重](#18-消息缓存与去重)
|
|
19
|
+
- [二、Plugin SDK 能力全景](#二plugin-sdk-能力全景)
|
|
20
|
+
- [2.1 Plugin 注册入口](#21-plugin-注册入口)
|
|
21
|
+
- [2.2 可实现的 Plugin 类型](#22-可实现的-plugin-类型)
|
|
22
|
+
- [2.3 Hook 生命周期事件](#23-hook-生命周期事件)
|
|
23
|
+
- [2.4 SDK 工具库](#24-sdk-工具库)
|
|
24
|
+
- [2.5 Plugin 清单格式](#25-plugin-清单格式)
|
|
25
|
+
- [2.6 导入边界规则](#26-导入边界规则)
|
|
26
|
+
- [2.7 二次开发路径指南](#27-二次开发路径指南)
|
|
27
|
+
- [三、Skill 运行机制与记忆系统](#三skill-运行机制与记忆系统)
|
|
28
|
+
- [3.1 Skill 定义格式](#31-skill-定义格式)
|
|
29
|
+
- [3.2 发现与加载管线](#32-发现与加载管线)
|
|
30
|
+
- [3.3 运行时执行流程](#33-运行时执行流程)
|
|
31
|
+
- [3.4 Skill 快照与缓存](#34-skill-快照与缓存)
|
|
32
|
+
- [3.5 记忆与持久化系统](#35-记忆与持久化系统)
|
|
33
|
+
- [3.6 调试 Skill 的方法](#36-调试-skill-的方法)
|
|
34
|
+
- [3.7 安全限制与保护机制](#37-安全限制与保护机制)
|
|
35
|
+
- [四、关键源码索引](#四关键源码索引)
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 一、通信机制
|
|
40
|
+
|
|
41
|
+
### 1.1 架构总览
|
|
42
|
+
|
|
43
|
+
OpenClaw 的 Plugin 通信架构是**同进程、回调驱动**的设计。Plugin 与 Core 运行在同一个 Node.js 进程中,不存在 IPC 或跨进程通信。通信通过三种方式实现:
|
|
44
|
+
|
|
45
|
+
| 通信方式 | 方向 | 说明 |
|
|
46
|
+
|----------|------|------|
|
|
47
|
+
| **Webhook HTTP POST** | 外部 → Plugin | 外部平台(如 BlueBubbles Server)向 Gateway 发送 webhook |
|
|
48
|
+
| **函数调用** | Plugin → Core | Plugin 调用 `core.channel.reply.*`、`core.channel.routing.*` 等 SDK 方法 |
|
|
49
|
+
| **回调函数** | Core → Plugin | Core 通过 Plugin 注册的 `deliver()` 回调将回复交还给 Plugin |
|
|
50
|
+
|
|
51
|
+
**端到端消息流向图:**
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
55
|
+
│ 外部平台 (Telegram / Discord / ...) │
|
|
56
|
+
└────────────┬─────────────────────────────────────────────────┬───────────┘
|
|
57
|
+
│ HTTP Webhook POST ▲
|
|
58
|
+
▼ │ 原生 API 发送
|
|
59
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
60
|
+
│ PLUGIN 层 │
|
|
61
|
+
│ ┌─────────────┐ ┌──────────────┐ ┌───────────────┐ ┌─────────────┐ │
|
|
62
|
+
│ │ 认证 & 解析 │→│ 访问控制检查 │→│ 上下文构建 │→│ deliver() │ │
|
|
63
|
+
│ │ (webhook) │ │ (allowlist) │ │ (envelope) │ │ (回调发送) │ │
|
|
64
|
+
│ └─────────────┘ └──────────────┘ └───────────────┘ └─────────────┘ │
|
|
65
|
+
└────────────────────────────┬─────────────────────────────────▲──────────┘
|
|
66
|
+
│ dispatchReply...() │ deliver()
|
|
67
|
+
▼ │
|
|
68
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
69
|
+
│ CORE 层 │
|
|
70
|
+
│ ┌──────────────┐ ┌───────────────┐ ┌──────────────┐ ┌───────────┐ │
|
|
71
|
+
│ │ 路由解析 │→│ Session 记录 │→│ Agent 推理 │→│ Reply 管线 │ │
|
|
72
|
+
│ │ (agentRoute) │ │ (store) │ │ (LLM 调用) │ │ (stream) │ │
|
|
73
|
+
│ └──────────────┘ └───────────────┘ └──────────────┘ └───────────┘ │
|
|
74
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 1.2 消息入站流程
|
|
78
|
+
|
|
79
|
+
以 BlueBubbles Plugin (`extensions/bluebubbles/`) 为例,完整流程分为六个阶段:
|
|
80
|
+
|
|
81
|
+
#### 阶段 1:Webhook 接收与认证
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// extensions/bluebubbles/src/monitor.ts
|
|
85
|
+
registerBlueBubblesWebhookTarget(target: WebhookTarget)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- Gateway 在注册的路径上监听 HTTP POST 请求
|
|
89
|
+
- 使用 `timingSafeEqual` 进行常量时间认证(防止时序攻击)
|
|
90
|
+
- 支持 JSON 和 URL-encoded 两种 payload 格式
|
|
91
|
+
- 认证方式为 "plugin" auth mode(Plugin 特有的 webhook 路由)
|
|
92
|
+
|
|
93
|
+
#### 阶段 2:消息标准化
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// extensions/bluebubbles/src/monitor.ts
|
|
97
|
+
parseBlueBubblesWebhookPayload() // 解析原始 payload
|
|
98
|
+
normalizeWebhookMessage() // 标准化为 NormalizedWebhookMessage
|
|
99
|
+
normalizeWebhookReaction() // 处理表情回应
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
每个 Channel Plugin 负责将平台特定的消息格式转换为统一的 `NormalizedWebhookMessage` 结构。
|
|
103
|
+
|
|
104
|
+
#### 阶段 3:访问控制(详见 1.3)
|
|
105
|
+
|
|
106
|
+
#### 阶段 4:上下文构建(详见 1.4)
|
|
107
|
+
|
|
108
|
+
#### 阶段 5:路由解析(详见 1.5)
|
|
109
|
+
|
|
110
|
+
#### 阶段 6:派发到 Core Reply Engine
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// extensions/bluebubbles/src/monitor-processing.ts (lines 1264-1450+)
|
|
114
|
+
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
115
|
+
ctx: ctxPayload, // 构建好的消息上下文
|
|
116
|
+
cfg: config, // OpenClaw 配置
|
|
117
|
+
dispatcherOptions: {
|
|
118
|
+
...replyPipeline, // typing indicator + send handler
|
|
119
|
+
deliver: async (payload, info) => {
|
|
120
|
+
// Plugin 自定义的投递函数
|
|
121
|
+
await sendBlueBubblesMedia(...) // 或
|
|
122
|
+
await sendMessageBlueBubbles(...)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Core Reply Engine 处理流程:
|
|
129
|
+
1. `recordInboundSession()` — 写入 session store
|
|
130
|
+
2. `dispatchReplyFromConfig()` — Agent 处理消息(LLM 推理 + 工具调用)
|
|
131
|
+
3. 流式回复通过 `deliver()` 回调交还给 Plugin
|
|
132
|
+
4. Plugin 通过平台原生 API 发送到终端用户
|
|
133
|
+
|
|
134
|
+
### 1.3 访问控制前置检查
|
|
135
|
+
|
|
136
|
+
**关键设计:安全检查在 Plugin 层完成,在消息到达 Core 之前。**
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// extensions/bluebubbles/src/monitor-processing.ts:processMessage() (lines 568-696)
|
|
140
|
+
|
|
141
|
+
// 1. 确定策略
|
|
142
|
+
const dmPolicy = account.config.dmPolicy ?? "pairing"; // DM 默认需要配对
|
|
143
|
+
const groupPolicy = account.config.groupPolicy ?? "allowlist"; // 群组默认走白名单
|
|
144
|
+
|
|
145
|
+
// 2. 执行访问判断
|
|
146
|
+
const accessDecision = resolveDmGroupAccessWithLists({
|
|
147
|
+
isGroup,
|
|
148
|
+
dmPolicy, // "open" | "pairing" | "allowlist" | "block"
|
|
149
|
+
groupPolicy, // "open" | "allowlist" | "block"
|
|
150
|
+
allowFrom, // 配置的白名单
|
|
151
|
+
storeAllowFrom, // 动态存储的白名单
|
|
152
|
+
isSenderAllowed: (allowFrom) => isAllowedBlueBubblesSender({...})
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// 3. 根据判断结果处理
|
|
156
|
+
switch (accessDecision.decision) {
|
|
157
|
+
case "allow": break; // 继续处理
|
|
158
|
+
case "block": return; // 静默丢弃
|
|
159
|
+
case "pairing": await issuePairingChallenge(); // 发起配对挑战
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**访问策略矩阵:**
|
|
164
|
+
|
|
165
|
+
| 场景 | DM Policy | Group Policy | 行为 |
|
|
166
|
+
|------|-----------|-------------|------|
|
|
167
|
+
| 私信,白名单内 | `allowlist` | — | 允许 |
|
|
168
|
+
| 私信,白名单外 | `allowlist` | — | 阻止 |
|
|
169
|
+
| 私信,任意 | `open` | — | 允许 |
|
|
170
|
+
| 私信,需配对 | `pairing` | — | 发起配对挑战 |
|
|
171
|
+
| 群组,白名单内 | — | `allowlist` | 允许(可能还需要 @mention) |
|
|
172
|
+
| 群组,白名单外 | — | `allowlist` | 阻止 |
|
|
173
|
+
| 群组,任意 | — | `open` | 允许 |
|
|
174
|
+
|
|
175
|
+
群组消息还有额外的 **Mention Gating**(`core.channel.groups.resolveRequireMention()`),即使通过白名单也可能需要 @mention 才响应。
|
|
176
|
+
|
|
177
|
+
### 1.4 消息上下文构建
|
|
178
|
+
|
|
179
|
+
Plugin 负责构建完整的消息上下文,供 Core 的 Agent 使用:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// extensions/bluebubbles/src/monitor-processing.ts (lines 1159-1197)
|
|
183
|
+
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
|
184
|
+
// === 消息本体 ===
|
|
185
|
+
Body: body, // 带信封格式的消息(含元数据)
|
|
186
|
+
BodyForAgent: rawBody, // 纯文本(供 Agent 直接读取)
|
|
187
|
+
|
|
188
|
+
// === 路由信息 ===
|
|
189
|
+
From: `bluebubbles:${message.senderId}`,
|
|
190
|
+
To: `bluebubbles:${outboundTarget}`,
|
|
191
|
+
SessionKey: route.sessionKey,
|
|
192
|
+
|
|
193
|
+
// === 聊天类型 ===
|
|
194
|
+
ChatType: isGroup ? "group" : "direct",
|
|
195
|
+
|
|
196
|
+
// === 回复上下文 ===
|
|
197
|
+
ReplyToId, // 被回复消息的 ID
|
|
198
|
+
ReplyToBody, // 被回复消息的内容
|
|
199
|
+
ReplyToSender, // 被回复消息的发送者
|
|
200
|
+
|
|
201
|
+
// === 群组信息 ===
|
|
202
|
+
GroupMembers, // 群成员列表
|
|
203
|
+
GroupSubject, // 群名称/主题
|
|
204
|
+
|
|
205
|
+
// === 附件 ===
|
|
206
|
+
// 下载附件并保存到 media buffer
|
|
207
|
+
// 支持图片、视频、音频、文档
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**消息信封格式**会包含 channel 元数据、时间戳等,让 Agent 能理解消息来源。
|
|
212
|
+
|
|
213
|
+
### 1.5 路由解析与派发
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// 路由解析
|
|
217
|
+
const route = core.channel.routing.resolveAgentRoute({
|
|
218
|
+
channel: "bluebubbles",
|
|
219
|
+
peer: { kind: "direct" | "group", id: peerId },
|
|
220
|
+
accountId,
|
|
221
|
+
config
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// 返回值
|
|
225
|
+
{
|
|
226
|
+
agentId: string, // 目标 Agent ID
|
|
227
|
+
sessionKey: string, // 会话键(决定对话上下文隔离)
|
|
228
|
+
mainSessionKey: string, // 主会话键
|
|
229
|
+
matchedBy: string // 匹配方式(路由规则 / 默认)
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
路由规则在 OpenClaw 配置中定义,支持:
|
|
234
|
+
- 按 channel + peer 精确匹配
|
|
235
|
+
- 按 channel 通配
|
|
236
|
+
- 按 Agent ID 指定
|
|
237
|
+
- 默认 Agent 回退
|
|
238
|
+
|
|
239
|
+
### 1.6 出站投递与跨 Channel 转发
|
|
240
|
+
|
|
241
|
+
#### 同 Channel 回复
|
|
242
|
+
|
|
243
|
+
最常见的场景:消息从哪个 Channel 进来,就从哪个 Channel 回复。回复通过 Plugin 注册的 `deliver()` 回调完成。
|
|
244
|
+
|
|
245
|
+
#### 跨 Channel 转发
|
|
246
|
+
|
|
247
|
+
当 Agent 使用 `message.send()` 工具并指定不同的 `channel` 参数时,触发跨 Channel 路由:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
// src/infra/outbound/channel-selection.ts
|
|
251
|
+
const { channel, source } = await resolveMessageChannelSelection({
|
|
252
|
+
cfg: config,
|
|
253
|
+
channel: "telegram", // 显式指定目标 channel
|
|
254
|
+
fallbackChannel: null
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// 优先级:显式指定 > 工具上下文推断 > 唯一已配置 channel
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**跨 Channel 投递管线:**
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
// src/infra/outbound/message.ts
|
|
264
|
+
async function sendMessage(params) {
|
|
265
|
+
// 1. 解析目标 channel
|
|
266
|
+
const selection = await resolveMessageChannelSelection(params);
|
|
267
|
+
|
|
268
|
+
// 2. 获取目标 channel 的 Plugin
|
|
269
|
+
const plugin = await resolveOutboundChannelPlugin(selection.channel);
|
|
270
|
+
|
|
271
|
+
// 3. 标准化投递 payload
|
|
272
|
+
const payloads = normalizeReplyPayloadsForDelivery(message, media);
|
|
273
|
+
|
|
274
|
+
// 4. 执行投递
|
|
275
|
+
await deliverOutboundPayloads(plugin, payloads);
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
**具体示例:**
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
BlueBubbles 收到消息 "帮我发个 Telegram 消息给 Alice"
|
|
283
|
+
→ Agent 推理后调用 message.send({ channel: "telegram", to: "alice", body: "..." })
|
|
284
|
+
→ resolveMessageChannelSelection() 选择 Telegram Plugin
|
|
285
|
+
→ resolveOutboundChannelPlugin("telegram") 获取 Telegram adapter
|
|
286
|
+
→ Telegram Plugin.outbound.sendText({ to: "alice", body: "..." })
|
|
287
|
+
→ Telegram Bot API 发送消息
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### 1.7 核心通信 API 参考
|
|
291
|
+
|
|
292
|
+
#### Plugin → Core(入站方向)
|
|
293
|
+
|
|
294
|
+
| API | 源文件 | 用途 |
|
|
295
|
+
|-----|--------|------|
|
|
296
|
+
| `core.channel.routing.resolveAgentRoute()` | `src/plugin-sdk/resolve-route` | 将消息路由到指定 Agent |
|
|
297
|
+
| `core.channel.reply.dispatchReplyWithBufferedBlockDispatcher()` | `src/channels/reply/provider-dispatcher` | 通过 Reply 管线派发消息 |
|
|
298
|
+
| `core.channel.reply.finalizeInboundContext()` | `src/auto-reply/templating` | 构建标准化消息上下文 |
|
|
299
|
+
| `core.channel.session.recordInboundSession()` | `src/channels/session` | 记录会话历史 |
|
|
300
|
+
| `core.channel.mentions.buildMentionRegexes()` | `src/channels/mention-gating` | 构建 @mention 匹配规则 |
|
|
301
|
+
| `core.channel.groups.resolveRequireMention()` | `src/channels/plugins/group-policy` | 判断群组是否需要 @mention |
|
|
302
|
+
| `core.system.enqueueSystemEvent()` | `src/system` | 记录系统级事件 |
|
|
303
|
+
|
|
304
|
+
#### Core → Channel(出站方向)
|
|
305
|
+
|
|
306
|
+
| API | 源文件 | 用途 |
|
|
307
|
+
|-----|--------|------|
|
|
308
|
+
| `resolveMessageChannelSelection()` | `src/infra/outbound/channel-selection.ts` | 选择目标 channel |
|
|
309
|
+
| `resolveOutboundChannelPlugin()` | `src/infra/outbound/channel-resolution.ts` | 获取 channel adapter |
|
|
310
|
+
| `deliverOutboundPayloads()` | `src/infra/outbound/deliver.ts` | 执行出站投递 |
|
|
311
|
+
| `sendMessage()` / `sendMedia()` / `sendPoll()` | `src/infra/outbound/message.ts` | 高级发送接口 |
|
|
312
|
+
|
|
313
|
+
### 1.8 消息缓存与去重
|
|
314
|
+
|
|
315
|
+
Plugin 内部维护多级缓存以确保消息正确性:
|
|
316
|
+
|
|
317
|
+
| 缓存类型 | 用途 |
|
|
318
|
+
|----------|------|
|
|
319
|
+
| **Reply Cache** | 缓存消息 ID,用于解析回复引用关系 |
|
|
320
|
+
| **Self-Chat Cache** | 检测并去重 Plugin 自身发出的消息(防止回环) |
|
|
321
|
+
| **Pending Outbound** | 追踪已发出的消息 ID,用于回声检测 |
|
|
322
|
+
| **History Buffer** | 滚动的内存消息历史,支持 API 回填重试 |
|
|
323
|
+
| **Debounce Registry** | `createBlueBubblesDebounceRegistry()` — 防止重复处理 |
|
|
324
|
+
| **Channel Inbound Debouncer** | `createChannelInboundDebouncer()` — 每 channel 去抖策略 |
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## 二、Plugin SDK 能力全景
|
|
329
|
+
|
|
330
|
+
### 2.1 Plugin 注册入口
|
|
331
|
+
|
|
332
|
+
每个 Plugin 通过导出一个入口定义来注册:
|
|
333
|
+
|
|
334
|
+
```typescript
|
|
335
|
+
// 非 Channel Plugin(Provider / Tool / Service 等)
|
|
336
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
|
337
|
+
|
|
338
|
+
export default definePluginEntry({
|
|
339
|
+
id: "my-provider",
|
|
340
|
+
name: "My Provider",
|
|
341
|
+
description: "A custom LLM provider",
|
|
342
|
+
kind: "memory", // 可选:排他性插槽
|
|
343
|
+
configSchema: { /* JSON Schema */ }, // 或 () => schema(延迟加载)
|
|
344
|
+
|
|
345
|
+
register: (api: OpenClawPluginApi) => {
|
|
346
|
+
// 在这里注册各种能力
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Channel Plugin(包含 channel 能力的完整接线)
|
|
351
|
+
import { defineChannelPluginEntry } from "openclaw/plugin-sdk/core";
|
|
352
|
+
|
|
353
|
+
export default defineChannelPluginEntry({
|
|
354
|
+
id: "my-channel",
|
|
355
|
+
name: "My Channel",
|
|
356
|
+
// ... channel 特有的配置
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
`api: OpenClawPluginApi` 提供以下所有注册方法:
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
interface OpenClawPluginApi {
|
|
364
|
+
// === Channel & Messaging ===
|
|
365
|
+
registerChannel(registration: ChannelPlugin)
|
|
366
|
+
|
|
367
|
+
// === AI Provider ===
|
|
368
|
+
registerProvider(provider: ProviderPlugin)
|
|
369
|
+
registerSpeechProvider(provider: SpeechProviderPlugin)
|
|
370
|
+
registerMediaUnderstandingProvider(provider: MediaUnderstandingProviderPlugin)
|
|
371
|
+
registerImageGenerationProvider(provider: ImageGenerationProviderPlugin)
|
|
372
|
+
registerWebSearchProvider(provider: WebSearchProviderPlugin)
|
|
373
|
+
|
|
374
|
+
// === Agent Capabilities ===
|
|
375
|
+
registerTool(tool: AnyAgentTool | OpenClawPluginToolFactory, opts?)
|
|
376
|
+
registerCommand(command: OpenClawPluginCommandDefinition)
|
|
377
|
+
registerContextEngine(id: string, factory: ContextEngineFactory)
|
|
378
|
+
registerMemoryPromptSection(builder: MemoryPromptSectionBuilder)
|
|
379
|
+
|
|
380
|
+
// === Infrastructure ===
|
|
381
|
+
registerHook(events: string[], handler: InternalHookHandler, opts?)
|
|
382
|
+
registerHttpRoute(params: OpenClawPluginHttpRouteParams)
|
|
383
|
+
registerService(service: OpenClawPluginService)
|
|
384
|
+
registerGatewayMethod(method: string, handler)
|
|
385
|
+
registerCli(registrar: OpenClawPluginCliRegistrar, opts?)
|
|
386
|
+
|
|
387
|
+
// === Event Hooks ===
|
|
388
|
+
on(hookName: PluginHookName, handler, opts?)
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### 2.2 可实现的 Plugin 类型
|
|
393
|
+
|
|
394
|
+
#### 2.2.1 Channel Plugin(消息渠道)
|
|
395
|
+
|
|
396
|
+
实现新消息平台的接入(如 Telegram、Discord、Slack、Matrix 等)。
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
interface ChannelPlugin<Account = any> {
|
|
400
|
+
id: string;
|
|
401
|
+
label: string;
|
|
402
|
+
capabilities: ChannelCapabilities;
|
|
403
|
+
docsPath?: string;
|
|
404
|
+
|
|
405
|
+
// 适配器接口
|
|
406
|
+
config: ChannelConfigAdapter<Account>; // 账户配置解析
|
|
407
|
+
outbound: ChannelOutboundAdapter<Account>; // 消息发送
|
|
408
|
+
security: ChannelSecurityAdapter<Account>; // 白名单 & DM 策略
|
|
409
|
+
pairing: ChannelPairingAdapter<Account>; // 配对认证
|
|
410
|
+
status: ChannelStatusAdapter<Account>; // 健康检查 & 探针
|
|
411
|
+
}
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
**参考实现:** `extensions/bluebubbles/src/channel.ts`, `extensions/twitch/src/plugin.ts`, `extensions/matrix/`
|
|
415
|
+
|
|
416
|
+
#### 2.2.2 Provider Plugin(LLM 推理提供者)
|
|
417
|
+
|
|
418
|
+
接入新的 LLM 模型服务。
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
interface ProviderPlugin {
|
|
422
|
+
id: string;
|
|
423
|
+
label: string;
|
|
424
|
+
auth: ProviderAuthMethod[]; // 认证方式(API Key / OAuth)
|
|
425
|
+
|
|
426
|
+
catalog: {
|
|
427
|
+
run: (ctx) => Promise<ModelCatalogEntry[]>; // 模型目录发现
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// 运行时钩子
|
|
431
|
+
prepareRuntimeAuth?: (ctx) => Promise<AuthResult>;
|
|
432
|
+
augmentModelCatalog?: (ctx) => Promise<ModelEntry[]>;
|
|
433
|
+
resolveUsageAuth?: (ctx) => Promise<UsageAuth>;
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
**参考实现:** `extensions/moonshot/`, `extensions/openai/`(如存在)
|
|
438
|
+
|
|
439
|
+
#### 2.2.3 Speech Provider(语音合成)
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
interface SpeechProviderPlugin {
|
|
443
|
+
id: string;
|
|
444
|
+
label: string;
|
|
445
|
+
listVoices: () => Promise<Voice[]>;
|
|
446
|
+
synthesize: (text: string, voice: Voice, opts?) => Promise<AudioBuffer>;
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
#### 2.2.4 Image Generation Provider(图像生成)
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
interface ImageGenerationProvider {
|
|
454
|
+
id: string;
|
|
455
|
+
label: string;
|
|
456
|
+
generate: (prompt: string, params: ImageGenParams) => Promise<ImageResult>;
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
#### 2.2.5 Web Search Provider(网页搜索)
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
interface WebSearchProviderPlugin {
|
|
464
|
+
id: string;
|
|
465
|
+
label: string;
|
|
466
|
+
search: (query: string, opts?) => Promise<SearchResult[]>;
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
#### 2.2.6 Memory Plugin(记忆/向量检索)
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
// 排他性插槽 — 同时只能有一个活跃
|
|
474
|
+
{
|
|
475
|
+
kind: "memory",
|
|
476
|
+
// 方法:capture(记忆写入)、recall(记忆召回)、maintenance(维护)
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**参考实现:** `extensions/memory-lancedb/`
|
|
481
|
+
|
|
482
|
+
#### 2.2.7 Context Engine(上下文引擎)
|
|
483
|
+
|
|
484
|
+
```typescript
|
|
485
|
+
// 排他性插槽 — 同时只能有一个活跃
|
|
486
|
+
{
|
|
487
|
+
kind: "context-engine",
|
|
488
|
+
// 自定义 transcript 改写和上下文注入
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
#### 2.2.8 Tool(Agent 工具)
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
api.registerTool({
|
|
496
|
+
name: "my_tool",
|
|
497
|
+
description: "Tool description for the agent",
|
|
498
|
+
parameters: { /* JSON Schema */ },
|
|
499
|
+
execute: async (params, ctx) => {
|
|
500
|
+
return { result: "..." };
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// 或使用工厂模式动态创建
|
|
505
|
+
api.registerTool((ctx) => ({
|
|
506
|
+
name: "dynamic_tool",
|
|
507
|
+
// ...
|
|
508
|
+
}));
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
#### 2.2.9 Command(CLI 命令)
|
|
512
|
+
|
|
513
|
+
绕过 LLM Agent 的直接命令执行:
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
api.registerCommand({
|
|
517
|
+
name: "my-command",
|
|
518
|
+
description: "Description",
|
|
519
|
+
execute: async (args, ctx) => {
|
|
520
|
+
// 直接执行逻辑,不经过 Agent
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
#### 2.2.10 Service(后台服务)
|
|
526
|
+
|
|
527
|
+
长期运行的后台进程:
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
api.registerService({
|
|
531
|
+
id: "my-background-service",
|
|
532
|
+
start: async (ctx) => { /* 启动逻辑 */ },
|
|
533
|
+
stop: async () => { /* 停止逻辑 */ }
|
|
534
|
+
});
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### 2.3 Hook 生命周期事件
|
|
538
|
+
|
|
539
|
+
OpenClaw 提供了覆盖 Agent 全生命周期的 Hook 事件:
|
|
540
|
+
|
|
541
|
+
#### 模型与提示词
|
|
542
|
+
|
|
543
|
+
| 事件 | 触发时机 | 典型用途 |
|
|
544
|
+
|------|----------|----------|
|
|
545
|
+
| `before_model_resolve` | 模型选择前 | 覆盖模型/Provider 选择 |
|
|
546
|
+
| `before_prompt_build` | System Prompt 构建前 | 注入自定义系统提示 |
|
|
547
|
+
| `before_agent_start` | Agent 启动前 | 最终的前置修改 |
|
|
548
|
+
|
|
549
|
+
```typescript
|
|
550
|
+
// 示例:覆盖模型选择
|
|
551
|
+
api.on('before_model_resolve', (ctx, event) => {
|
|
552
|
+
return {
|
|
553
|
+
modelOverride: 'claude-opus-4.6',
|
|
554
|
+
providerOverride: 'anthropic'
|
|
555
|
+
};
|
|
556
|
+
}, { priority: 100 });
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
#### 执行过程
|
|
560
|
+
|
|
561
|
+
| 事件 | 触发时机 | 典型用途 |
|
|
562
|
+
|------|----------|----------|
|
|
563
|
+
| `llm_input` | 发送到模型前 | 拦截/修改输入 |
|
|
564
|
+
| `llm_output` | 模型返回后 | 拦截/修改输出 |
|
|
565
|
+
| `agent_end` | Agent 运行结束 | 后处理、统计 |
|
|
566
|
+
|
|
567
|
+
#### 会话管理
|
|
568
|
+
|
|
569
|
+
| 事件 | 触发时机 | 典型用途 |
|
|
570
|
+
|------|----------|----------|
|
|
571
|
+
| `session_start` | 新会话开始 | 初始化会话状态 |
|
|
572
|
+
| `session_end` | 会话结束 | 清理、归档 |
|
|
573
|
+
| `before_compaction` | Transcript 压缩前 | 保留关键信息 |
|
|
574
|
+
| `after_compaction` | 压缩完成后 | 后处理 |
|
|
575
|
+
| `before_reset` | 会话重置前 | 保存状态 |
|
|
576
|
+
|
|
577
|
+
#### 消息处理
|
|
578
|
+
|
|
579
|
+
| 事件 | 触发时机 | 典型用途 |
|
|
580
|
+
|------|----------|----------|
|
|
581
|
+
| `inbound_claim` | 入站消息到达 | 路由/抢占消息 |
|
|
582
|
+
| `message_received` | 消息已接收 | 日志、分析 |
|
|
583
|
+
| `message_sending` | 消息即将发送 | 修改发送内容 |
|
|
584
|
+
| `message_sent` | 消息已发送 | 确认、回执 |
|
|
585
|
+
| `before_message_write` | 写入历史前 | 过滤、转换 |
|
|
586
|
+
|
|
587
|
+
#### 工具调用
|
|
588
|
+
|
|
589
|
+
| 事件 | 触发时机 | 典型用途 |
|
|
590
|
+
|------|----------|----------|
|
|
591
|
+
| `before_tool_call` | 工具执行前 | 拦截、授权检查 |
|
|
592
|
+
| `after_tool_call` | 工具执行后 | 结果修改、审计 |
|
|
593
|
+
| `tool_result_persist` | 结果持久化前 | 转换存储格式 |
|
|
594
|
+
|
|
595
|
+
#### 子代理
|
|
596
|
+
|
|
597
|
+
| 事件 | 触发时机 | 典型用途 |
|
|
598
|
+
|------|----------|----------|
|
|
599
|
+
| `subagent_spawning` | 子代理生成前 | 修改配置 |
|
|
600
|
+
| `subagent_delivery_target` | 确定投递目标 | 路由子代理输出 |
|
|
601
|
+
| `subagent_spawned` | 子代理已创建 | 追踪 |
|
|
602
|
+
| `subagent_ended` | 子代理结束 | 收集结果 |
|
|
603
|
+
|
|
604
|
+
#### 网关
|
|
605
|
+
|
|
606
|
+
| 事件 | 触发时机 | 典型用途 |
|
|
607
|
+
|------|----------|----------|
|
|
608
|
+
| `gateway_start` | Gateway 启动 | 初始化全局资源 |
|
|
609
|
+
| `gateway_stop` | Gateway 停止 | 清理 |
|
|
610
|
+
|
|
611
|
+
### 2.4 SDK 工具库
|
|
612
|
+
|
|
613
|
+
SDK 通过 160+ 子路径导出,以下按功能分类列举核心模块:
|
|
614
|
+
|
|
615
|
+
#### 配置与存储
|
|
616
|
+
|
|
617
|
+
| 模块路径 | 核心导出 | 用途 |
|
|
618
|
+
|----------|----------|------|
|
|
619
|
+
| `openclaw/plugin-sdk/config-runtime` | `loadConfig()`, `writeConfigFile()` | 配置读写 |
|
|
620
|
+
| `openclaw/plugin-sdk/runtime-store` | `createPluginRuntimeStore<T>()` | Plugin 作用域的运行时状态 |
|
|
621
|
+
| `openclaw/plugin-sdk/json-store` | `readJsonFileWithFallback()`, `writeJsonFileAtomically()` | JSON 文件持久化 |
|
|
622
|
+
| `openclaw/plugin-sdk/runtime-env` | 运行时环境变量 | 环境探测 |
|
|
623
|
+
|
|
624
|
+
#### 认证
|
|
625
|
+
|
|
626
|
+
| 模块路径 | 核心导出 | 用途 |
|
|
627
|
+
|----------|----------|------|
|
|
628
|
+
| `openclaw/plugin-sdk/provider-auth` | `createProviderApiKeyAuthMethod()` | API Key 认证流程 |
|
|
629
|
+
| `openclaw/plugin-sdk/provider-auth` | `buildOauthProviderAuthResult()` | OAuth 认证处理 |
|
|
630
|
+
| `openclaw/plugin-sdk/provider-auth` | `resolveApiKeyForProvider()` | 凭据解析 |
|
|
631
|
+
| `openclaw/plugin-sdk/provider-auth` | `ensureAuthProfileStore()` | 认证 Profile 管理 |
|
|
632
|
+
|
|
633
|
+
#### 日志与文本
|
|
634
|
+
|
|
635
|
+
| 模块路径 | 核心导出 | 用途 |
|
|
636
|
+
|----------|----------|------|
|
|
637
|
+
| `openclaw/plugin-sdk/text-runtime` | `PluginLogger` (info/warn/error/debug) | 结构化日志 |
|
|
638
|
+
| `openclaw/plugin-sdk/text-runtime` | Markdown 渲染、文本格式化 | 内容处理 |
|
|
639
|
+
| `openclaw/plugin-sdk/runtime` | `createLoggerBackedRuntime()` | 日志运行时适配 |
|
|
640
|
+
|
|
641
|
+
#### Channel 工具
|
|
642
|
+
|
|
643
|
+
| 模块路径 | 核心导出 | 用途 |
|
|
644
|
+
|----------|----------|------|
|
|
645
|
+
| `openclaw/plugin-sdk/channel-pairing` | `createTextPairingAdapter()` | 配对流程帮助器 |
|
|
646
|
+
| `openclaw/plugin-sdk/channel-status` | `describeAccountSnapshot()` | 账户状态描述 |
|
|
647
|
+
| `openclaw/plugin-sdk/channel-lifecycle` | `runPassiveAccountLifecycle()` | 生命周期管理 |
|
|
648
|
+
| `openclaw/plugin-sdk/channel-inbound` | 入站消息解析 & 派发 | 消息处理 |
|
|
649
|
+
| `openclaw/plugin-sdk/channel-config-*` | 配置 schema & 帮助器 | 配置管理 |
|
|
650
|
+
| `openclaw/plugin-sdk/direct-dm` | DM 访问控制 | 安全 |
|
|
651
|
+
| `openclaw/plugin-sdk/security-runtime` | DM 策略、信任逻辑 | 安全 |
|
|
652
|
+
|
|
653
|
+
#### Agent & 模型
|
|
654
|
+
|
|
655
|
+
| 模块路径 | 核心导出 | 用途 |
|
|
656
|
+
|----------|----------|------|
|
|
657
|
+
| `openclaw/plugin-sdk/agent-runtime` | Agent/模型帮助器 | Agent 操作 |
|
|
658
|
+
| `openclaw/plugin-sdk/provider-catalog` | 模型目录发现 | Provider 集成 |
|
|
659
|
+
| `openclaw/plugin-sdk/provider-models` | 运行时模型处理 | 模型操作 |
|
|
660
|
+
| `openclaw/plugin-sdk/speech` | TTS/语音合成帮助器 | 语音 |
|
|
661
|
+
|
|
662
|
+
#### 基础设施
|
|
663
|
+
|
|
664
|
+
| 模块路径 | 核心导出 | 用途 |
|
|
665
|
+
|----------|----------|------|
|
|
666
|
+
| `openclaw/plugin-sdk/gateway-runtime` | Gateway/服务器上下文 | 基础设施 |
|
|
667
|
+
| `openclaw/plugin-sdk/cli-runtime` | CLI 命令注册 | CLI 扩展 |
|
|
668
|
+
| `openclaw/plugin-sdk/ssrf-runtime` | SSRF 策略 | 安全 |
|
|
669
|
+
| `openclaw/plugin-sdk/hook-runtime` | 内部 Hook 基础设施 | 事件系统 |
|
|
670
|
+
| `openclaw/plugin-sdk/infra-runtime` | 基础设施工具 | 通用 |
|
|
671
|
+
|
|
672
|
+
#### 通用工具
|
|
673
|
+
|
|
674
|
+
| 功能 | 函数/类型 | 用途 |
|
|
675
|
+
|------|-----------|------|
|
|
676
|
+
| Promise 延迟 | `createDeferred<T>()` | 异步控制流 |
|
|
677
|
+
| 信号等待 | `waitUntilAbort()` | AbortSignal 处理 |
|
|
678
|
+
| HTTP 服务 | `keepHttpServerTaskAlive()` | 服务器生命周期 |
|
|
679
|
+
| 安全正则 | Safe regex utilities | 防 ReDoS |
|
|
680
|
+
| Web 请求 | `fetch` wrappers + SSRF guard | 安全 HTTP 请求 |
|
|
681
|
+
|
|
682
|
+
### 2.5 Plugin 清单格式
|
|
683
|
+
|
|
684
|
+
每个 Plugin 根目录的 `openclaw.plugin.json`:
|
|
685
|
+
|
|
686
|
+
```jsonc
|
|
687
|
+
{
|
|
688
|
+
// === 必填 ===
|
|
689
|
+
"id": "my-plugin", // 全局唯一标识
|
|
690
|
+
"configSchema": { // JSON Schema 格式
|
|
691
|
+
"type": "object",
|
|
692
|
+
"additionalProperties": false,
|
|
693
|
+
"properties": {
|
|
694
|
+
"apiKey": { "type": "string" },
|
|
695
|
+
"model": { "type": "string", "default": "gpt-5.4" }
|
|
696
|
+
},
|
|
697
|
+
"required": ["apiKey"]
|
|
698
|
+
},
|
|
699
|
+
|
|
700
|
+
// === 可选:类型 ===
|
|
701
|
+
"kind": "memory", // 或 "context-engine"(排他性插槽)
|
|
702
|
+
|
|
703
|
+
// === 可选:能力声明 ===
|
|
704
|
+
"channels": ["my-channel"], // 提供的 Channel ID 列表
|
|
705
|
+
"providers": ["my-provider"], // 提供的 Provider ID 列表
|
|
706
|
+
"skills": ["my-skill"], // 关联的 Skill 列表
|
|
707
|
+
|
|
708
|
+
// === 可选:认证 ===
|
|
709
|
+
"providerAuthEnvVars": {
|
|
710
|
+
"my-provider": ["MY_API_KEY", "MY_ORG_ID"]
|
|
711
|
+
},
|
|
712
|
+
"providerAuthChoices": [
|
|
713
|
+
{
|
|
714
|
+
"provider": "my-provider",
|
|
715
|
+
"method": "api-key",
|
|
716
|
+
"choiceId": "my-api-key",
|
|
717
|
+
"choiceLabel": "My API Key",
|
|
718
|
+
"groupId": "my-group",
|
|
719
|
+
"groupLabel": "My Service",
|
|
720
|
+
"optionKey": "apiKey",
|
|
721
|
+
"cliFlag": "--api-key",
|
|
722
|
+
"cliOption": "--api-key <key>",
|
|
723
|
+
"cliDescription": "API key for My Service",
|
|
724
|
+
"onboardingScopes": ["text-inference"]
|
|
725
|
+
}
|
|
726
|
+
],
|
|
727
|
+
|
|
728
|
+
// === 可选:UI ===
|
|
729
|
+
"uiHints": {
|
|
730
|
+
"apiKey": {
|
|
731
|
+
"label": "API Key",
|
|
732
|
+
"sensitive": true, // 输入时遮蔽
|
|
733
|
+
"placeholder": "sk-...",
|
|
734
|
+
"help": "Your API key",
|
|
735
|
+
"advanced": false // true 则默认折叠
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
|
|
739
|
+
// === 可选:元数据 ===
|
|
740
|
+
"name": "My Plugin",
|
|
741
|
+
"description": "Plugin description",
|
|
742
|
+
"version": "1.0.0",
|
|
743
|
+
"enabledByDefault": true
|
|
744
|
+
}
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
### 2.6 导入边界规则
|
|
748
|
+
|
|
749
|
+
```
|
|
750
|
+
✅ 允许的导入路径:
|
|
751
|
+
import { ... } from "openclaw/plugin-sdk/<subpath>"
|
|
752
|
+
import { ... } from "./api" // 本地 barrel
|
|
753
|
+
import { ... } from "./runtime-api" // 本地 barrel
|
|
754
|
+
|
|
755
|
+
❌ 禁止的导入路径:
|
|
756
|
+
import { ... } from "../../src/**" // 不得引用 Core 内部
|
|
757
|
+
import { ... } from "../../src/plugin-sdk-internal/**" // 不得引用 SDK 内部实现
|
|
758
|
+
import { ... } from "../other-extension/src/**" // 不得跨 extension 引用
|
|
759
|
+
import { ... } from "openclaw/plugin-sdk/my-extension" // 不得在 extension 内自引用
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
### 2.7 二次开发路径指南
|
|
763
|
+
|
|
764
|
+
| 开发目标 | 注册方式 | 需要实现的接口 | 参考实现 |
|
|
765
|
+
|----------|----------|---------------|----------|
|
|
766
|
+
| 接入新消息平台 | `api.registerChannel()` | outbound / inbound / pairing / status / config / security adapter | `extensions/bluebubbles/`, `extensions/twitch/` |
|
|
767
|
+
| 接入新 LLM | `api.registerProvider()` | auth / catalog / runtime hooks | `extensions/moonshot/` |
|
|
768
|
+
| 添加 Agent 工具 | `api.registerTool()` | name / description / parameters / execute | SDK 工具定义 |
|
|
769
|
+
| 消息拦截/修改 | `api.on('message_received')` | hook handler | — |
|
|
770
|
+
| 覆盖模型选择 | `api.on('before_model_resolve')` | hook handler returning override | — |
|
|
771
|
+
| 注入系统提示 | `api.on('before_prompt_build')` | hook handler | — |
|
|
772
|
+
| 自定义记忆后端 | `kind: "memory"` | capture / recall / maintenance | `extensions/memory-lancedb/` |
|
|
773
|
+
| 添加后台任务 | `api.registerService()` | start / stop | — |
|
|
774
|
+
| 扩展 CLI | `api.registerCli()` | CLI registrar | — |
|
|
775
|
+
| 添加 HTTP 端点 | `api.registerHttpRoute()` | route handler | — |
|
|
776
|
+
| 自定义语音 | `api.registerSpeechProvider()` | listVoices / synthesize | — |
|
|
777
|
+
| 自定义搜索 | `api.registerWebSearchProvider()` | search | `extensions/moonshot/` |
|
|
778
|
+
|
|
779
|
+
---
|
|
780
|
+
|
|
781
|
+
## 三、Skill 运行机制与记忆系统
|
|
782
|
+
|
|
783
|
+
### 3.1 Skill 定义格式
|
|
784
|
+
|
|
785
|
+
Skill 是 **Markdown 文件 + YAML frontmatter** 的组合,不是可执行代码。Skill 的"执行"本质上是**提示注入** — 其内容被注入到 Agent 的系统提示中,Agent "阅读并遵循"指令。
|
|
786
|
+
|
|
787
|
+
#### 文件结构
|
|
788
|
+
|
|
789
|
+
```
|
|
790
|
+
.agents/skills/
|
|
791
|
+
└── my-skill/
|
|
792
|
+
└── SKILL.md
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
#### SKILL.md 格式
|
|
796
|
+
|
|
797
|
+
```markdown
|
|
798
|
+
---
|
|
799
|
+
# === 基础字段 ===
|
|
800
|
+
name: my-skill # 必填:技能标识
|
|
801
|
+
description: Does something useful # 必填:描述(用于判断是否适用)
|
|
802
|
+
user-invocable: true # 可选:用户能否直接调用(默认 true)
|
|
803
|
+
disable-model-invocation: false # 可选:禁止模型主动调用(默认 false)
|
|
804
|
+
|
|
805
|
+
# === 命令派发(可选)===
|
|
806
|
+
command-dispatch: tool # 派发到工具
|
|
807
|
+
command-tool: my_tool_name # 目标工具名
|
|
808
|
+
command-arg-mode: raw # 参数传递模式
|
|
809
|
+
|
|
810
|
+
# === OpenClaw 特有元数据(可选)===
|
|
811
|
+
openclaw:
|
|
812
|
+
always: false # 始终加载,不检查可用性
|
|
813
|
+
skillKey: my-skill # 配置查找的替代键
|
|
814
|
+
primaryEnv: MY_API_KEY # 主要环境变量
|
|
815
|
+
emoji: "🔧" # 视觉标识
|
|
816
|
+
homepage: https://example.com # 文档链接
|
|
817
|
+
|
|
818
|
+
# 运行平台限制
|
|
819
|
+
os: [darwin, linux, win32]
|
|
820
|
+
|
|
821
|
+
# 依赖声明
|
|
822
|
+
requires:
|
|
823
|
+
bins: [my-binary] # 必须全部存在
|
|
824
|
+
anyBins: [binary-a, binary-b] # 至少一个存在
|
|
825
|
+
env: [MY_API_KEY, MY_SECRET] # 必须的环境变量
|
|
826
|
+
config: [some.config.path] # 必须的配置路径
|
|
827
|
+
|
|
828
|
+
# 安装方式
|
|
829
|
+
install:
|
|
830
|
+
- kind: brew
|
|
831
|
+
formula: my-binary
|
|
832
|
+
- kind: node
|
|
833
|
+
package: my-npm-package
|
|
834
|
+
- kind: go
|
|
835
|
+
module: github.com/user/repo
|
|
836
|
+
- kind: uv
|
|
837
|
+
package: my-python-package
|
|
838
|
+
- kind: download
|
|
839
|
+
url: https://example.com/binary
|
|
840
|
+
extract: true
|
|
841
|
+
---
|
|
842
|
+
|
|
843
|
+
# Skill 内容
|
|
844
|
+
|
|
845
|
+
这里是 Agent 将阅读和遵循的指令。支持完整的 Markdown 格式。
|
|
846
|
+
|
|
847
|
+
## 步骤
|
|
848
|
+
1. 首先做 X
|
|
849
|
+
2. 然后做 Y
|
|
850
|
+
3. 最后做 Z
|
|
851
|
+
|
|
852
|
+
## 注意事项
|
|
853
|
+
- 不要做 A
|
|
854
|
+
- 始终确认 B
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
### 3.2 发现与加载管线
|
|
858
|
+
|
|
859
|
+
#### 发现优先级
|
|
860
|
+
|
|
861
|
+
Skill 从多个目录加载,**后者覆盖前者**:
|
|
862
|
+
|
|
863
|
+
```
|
|
864
|
+
优先级 1 (最低):extensions/**/skills/ ← Plugin 提供的 Skill
|
|
865
|
+
优先级 2: 内置 bundled skills ← OpenClaw 自带的
|
|
866
|
+
优先级 3: ~/.openclaw/skills/ ← 全局安装的
|
|
867
|
+
优先级 4: ~/.agents/skills/ ← 个人 Agent 级
|
|
868
|
+
优先级 5: <workspace>/.agents/skills/ ← 项目级(本仓库使用此级)
|
|
869
|
+
优先级 6 (最高):<workspace>/skills/ ← 工作区级
|
|
870
|
+
额外: config skills.load.extraDirs ← 配置中指定的额外目录
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
**同名 Skill 的合并规则:** 高优先级来源的同名 Skill 完全覆盖低优先级的。
|
|
874
|
+
|
|
875
|
+
#### 加载管线
|
|
876
|
+
|
|
877
|
+
核心实现位于 `src/agents/skills/workspace.ts` — `loadSkillEntries()`:
|
|
878
|
+
|
|
879
|
+
```
|
|
880
|
+
1. 目录扫描
|
|
881
|
+
├── 遍历所有配置的 skill root 目录
|
|
882
|
+
├── 自动检测嵌套的 skills/ 子目录
|
|
883
|
+
├── 执行大小限制(默认 256KB/SKILL.md)
|
|
884
|
+
└── 验证符号链接的包含性(防止目录逃逸)
|
|
885
|
+
|
|
886
|
+
2. Frontmatter 解析
|
|
887
|
+
├── parseFrontmatter()(src/agents/skills/frontmatter.ts)
|
|
888
|
+
├── 解析 YAML frontmatter(--- 分隔)
|
|
889
|
+
├── 类型验证各字段
|
|
890
|
+
└── 清理包名、formula、URL、路径
|
|
891
|
+
|
|
892
|
+
3. 合并去重
|
|
893
|
+
├── 按名称分组
|
|
894
|
+
├── 按优先级覆盖
|
|
895
|
+
└── 输出最终的 SkillEntry[]
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
#### 核心类型定义
|
|
899
|
+
|
|
900
|
+
```typescript
|
|
901
|
+
type SkillEntry = {
|
|
902
|
+
skill: Skill; // 底层 Skill 对象
|
|
903
|
+
frontmatter: ParsedSkillFrontmatter; // 原始解析的 YAML
|
|
904
|
+
metadata?: OpenClawSkillMetadata; // 解析验证后的 OpenClaw 元数据
|
|
905
|
+
invocation?: SkillInvocationPolicy; // 用户/模型调用资格
|
|
906
|
+
};
|
|
907
|
+
```
|
|
908
|
+
|
|
909
|
+
### 3.3 运行时执行流程
|
|
910
|
+
|
|
911
|
+
```
|
|
912
|
+
Session Start
|
|
913
|
+
│
|
|
914
|
+
▼
|
|
915
|
+
resolveEmbeddedRunSkillEntries() ← 加载所有 SkillEntry
|
|
916
|
+
│
|
|
917
|
+
▼
|
|
918
|
+
shouldIncludeSkill() ← 逐个过滤
|
|
919
|
+
├── 检查 OS 兼容性
|
|
920
|
+
├── 检查二进制可用性
|
|
921
|
+
├── 验证环境变量
|
|
922
|
+
├── 检查配置路径
|
|
923
|
+
├── 应用 allowlist/denylist
|
|
924
|
+
└── 应用 skill 级别的 disable 标志
|
|
925
|
+
│
|
|
926
|
+
▼
|
|
927
|
+
buildWorkspaceSkillSnapshot() ← 构建快照
|
|
928
|
+
│
|
|
929
|
+
▼
|
|
930
|
+
buildWorkspaceSkillsPrompt() ← 渲染 Markdown 目录
|
|
931
|
+
├── compactSkillPaths() ← 压缩路径(~/... 代替完整路径)
|
|
932
|
+
├── 字符预算:默认 30,000 字符
|
|
933
|
+
├── 数量限制:默认 150 个 Skill
|
|
934
|
+
├── 全量格式:name + description
|
|
935
|
+
├── 紧凑格式:name + location(超预算时降级)
|
|
936
|
+
└── 截断警告(如果需要)
|
|
937
|
+
│
|
|
938
|
+
▼
|
|
939
|
+
buildSkillsSection() ← 注入 System Prompt
|
|
940
|
+
│
|
|
941
|
+
▼
|
|
942
|
+
注入为 System Prompt 的一部分:
|
|
943
|
+
|
|
944
|
+
## Skills (mandatory)
|
|
945
|
+
Before replying: scan <available_skills> <description> entries.
|
|
946
|
+
- If exactly one skill clearly applies: read its SKILL.md at <location>
|
|
947
|
+
- If multiple could apply: choose most specific, then read/follow it
|
|
948
|
+
- If none clearly apply: do not read any SKILL.md
|
|
949
|
+
|
|
950
|
+
<available_skills>
|
|
951
|
+
- my-skill: Does something useful (at ~/.agents/skills/my-skill/SKILL.md)
|
|
952
|
+
- another-skill: Another description (at .agents/skills/another/SKILL.md)
|
|
953
|
+
</available_skills>
|
|
954
|
+
│
|
|
955
|
+
▼
|
|
956
|
+
Agent 根据用户请求,使用 Read 工具读取 SKILL.md
|
|
957
|
+
│
|
|
958
|
+
▼
|
|
959
|
+
Agent 遵循 SKILL.md 中的指令执行任务
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
**关键理解:Skill 本身不是代码执行。** Skill 是通过 System Prompt 注入的指令模板,Agent 读取后自主遵循。这意味着:
|
|
963
|
+
- Skill 的"执行"完全依赖 Agent 的理解和遵从能力
|
|
964
|
+
- Skill 可以包含任意复杂的指令流程
|
|
965
|
+
- Skill 无法直接调用 API 或执行代码(只能指导 Agent 使用工具)
|
|
966
|
+
|
|
967
|
+
### 3.4 Skill 快照与缓存
|
|
968
|
+
|
|
969
|
+
为避免每次 Agent 调用都重新扫描文件系统,Skill 系统使用快照机制:
|
|
970
|
+
|
|
971
|
+
```typescript
|
|
972
|
+
type SkillSnapshot = {
|
|
973
|
+
prompt: string; // 预渲染的 Markdown 目录
|
|
974
|
+
skills: Array<{
|
|
975
|
+
name: string;
|
|
976
|
+
primaryEnv?: string;
|
|
977
|
+
requiredEnv?: string[];
|
|
978
|
+
}>;
|
|
979
|
+
skillFilter?: string[]; // 应用的过滤器
|
|
980
|
+
resolvedSkills?: Skill[]; // 完整 Skill 对象(供运行时使用)
|
|
981
|
+
version?: number; // Schema 版本(迁移用)
|
|
982
|
+
};
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
快照在 session 开始时构建(`buildWorkspaceSkillSnapshot()`),在同一 session 的所有工具调用中复用。
|
|
986
|
+
|
|
987
|
+
### 3.5 记忆与持久化系统
|
|
988
|
+
|
|
989
|
+
#### 架构概览
|
|
990
|
+
|
|
991
|
+
```
|
|
992
|
+
┌───────────────────────────────────────────────┐
|
|
993
|
+
│ Agent 运行时 │
|
|
994
|
+
│ ┌─────────────┐ ┌──────────────────────┐ │
|
|
995
|
+
│ │ memory_search│ │ memory_get │ │
|
|
996
|
+
│ │ (语义搜索) │ │ (文件读取) │ │
|
|
997
|
+
│ └──────┬──────┘ └──────────┬───────────┘ │
|
|
998
|
+
└─────────┼──────────────────────┼──────────────┘
|
|
999
|
+
▼ ▼
|
|
1000
|
+
┌───────────────────────────────────────────────┐
|
|
1001
|
+
│ 记忆存储层 │
|
|
1002
|
+
│ ┌─────────────────────────────────────────┐ │
|
|
1003
|
+
│ │ MEMORY.md (索引文件) │ │
|
|
1004
|
+
│ │ memory/*.md (各记忆文件) │ │
|
|
1005
|
+
│ └─────────────────────────────────────────┘ │
|
|
1006
|
+
│ ┌─────────────────────────────────────────┐ │
|
|
1007
|
+
│ │ memory.db (SQLite) │ │
|
|
1008
|
+
│ │ ├── meta (元数据) │ │
|
|
1009
|
+
│ │ ├── files (文件索引) │ │
|
|
1010
|
+
│ │ ├── chunks (文本块 + embedding) │ │
|
|
1011
|
+
│ │ ├── embedding_cache (向量缓存) │ │
|
|
1012
|
+
│ │ └── fts5_index (全文搜索索引) │ │
|
|
1013
|
+
│ └─────────────────────────────────────────┘ │
|
|
1014
|
+
└───────────────────────────────────────────────┘
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
#### 存储位置
|
|
1018
|
+
|
|
1019
|
+
```
|
|
1020
|
+
~/.openclaw/agents/<agentId>/
|
|
1021
|
+
├── MEMORY.md # 记忆索引(Markdown,始终加载到上下文)
|
|
1022
|
+
├── memory/ # 记忆内容目录
|
|
1023
|
+
│ ├── user_role.md # 用户信息记忆
|
|
1024
|
+
│ ├── feedback_testing.md # 反馈记忆
|
|
1025
|
+
│ └── project_auth.md # 项目记忆
|
|
1026
|
+
├── memory.db # SQLite 索引数据库
|
|
1027
|
+
└── sessions/ # 会话日志
|
|
1028
|
+
└── *.jsonl # JSON Lines 格式
|
|
1029
|
+
```
|
|
1030
|
+
|
|
1031
|
+
#### SQLite Schema
|
|
1032
|
+
|
|
1033
|
+
```sql
|
|
1034
|
+
-- 元数据表
|
|
1035
|
+
CREATE TABLE meta (
|
|
1036
|
+
key TEXT PRIMARY KEY,
|
|
1037
|
+
value TEXT
|
|
1038
|
+
);
|
|
1039
|
+
|
|
1040
|
+
-- 文件索引
|
|
1041
|
+
CREATE TABLE files (
|
|
1042
|
+
path TEXT PRIMARY KEY, -- 相对路径
|
|
1043
|
+
source TEXT, -- 来源类型
|
|
1044
|
+
hash TEXT, -- 内容哈希
|
|
1045
|
+
mtime INTEGER, -- 修改时间
|
|
1046
|
+
size INTEGER -- 文件大小
|
|
1047
|
+
);
|
|
1048
|
+
|
|
1049
|
+
-- 文本块(含向量嵌入)
|
|
1050
|
+
CREATE TABLE chunks (
|
|
1051
|
+
id TEXT,
|
|
1052
|
+
path TEXT, -- 所属文件
|
|
1053
|
+
source TEXT,
|
|
1054
|
+
start_line INTEGER,
|
|
1055
|
+
end_line INTEGER,
|
|
1056
|
+
hash TEXT,
|
|
1057
|
+
model TEXT, -- 嵌入模型
|
|
1058
|
+
text TEXT, -- 原始文本
|
|
1059
|
+
embedding BLOB, -- 向量嵌入
|
|
1060
|
+
updated_at INTEGER
|
|
1061
|
+
);
|
|
1062
|
+
|
|
1063
|
+
-- 嵌入缓存(避免重复计算)
|
|
1064
|
+
CREATE TABLE embedding_cache (
|
|
1065
|
+
provider TEXT,
|
|
1066
|
+
model TEXT,
|
|
1067
|
+
provider_key TEXT,
|
|
1068
|
+
hash TEXT,
|
|
1069
|
+
embedding BLOB,
|
|
1070
|
+
dims INTEGER,
|
|
1071
|
+
updated_at INTEGER
|
|
1072
|
+
);
|
|
1073
|
+
|
|
1074
|
+
-- FTS5 全文搜索索引
|
|
1075
|
+
CREATE VIRTUAL TABLE fts5_index USING fts5(
|
|
1076
|
+
text,
|
|
1077
|
+
id UNINDEXED,
|
|
1078
|
+
path UNINDEXED,
|
|
1079
|
+
source UNINDEXED,
|
|
1080
|
+
model,
|
|
1081
|
+
start_line,
|
|
1082
|
+
end_line
|
|
1083
|
+
);
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
#### Agent 可用的记忆工具
|
|
1087
|
+
|
|
1088
|
+
| 工具名 | 参数 | 返回 |
|
|
1089
|
+
|--------|------|------|
|
|
1090
|
+
| `memory_search` | `query: string` (语义搜索) | 匹配片段 + 路径 + 行号 + 相关度分数 |
|
|
1091
|
+
| `memory_get` | `path: string`, `lines?: range` | 指定文件/行范围的内容 |
|
|
1092
|
+
|
|
1093
|
+
**搜索策略:** 先向量语义搜索,再 FTS5 关键词搜索,合并去重排序。
|
|
1094
|
+
|
|
1095
|
+
#### 可选后端:qmd (Quantum Markdown)
|
|
1096
|
+
|
|
1097
|
+
```typescript
|
|
1098
|
+
memory: {
|
|
1099
|
+
backend: "qmd", // 切换到 qmd 后端
|
|
1100
|
+
qmd: {
|
|
1101
|
+
command: "qmd", // 外部命令
|
|
1102
|
+
searchMode: "vsearch", // query | search | vsearch
|
|
1103
|
+
paths: [...], // 索引路径
|
|
1104
|
+
sessions: {
|
|
1105
|
+
enabled: true,
|
|
1106
|
+
exportDir: "...",
|
|
1107
|
+
retentionDays: 30
|
|
1108
|
+
},
|
|
1109
|
+
update: {
|
|
1110
|
+
interval: "1h",
|
|
1111
|
+
debounceMs: 5000,
|
|
1112
|
+
onBoot: true
|
|
1113
|
+
},
|
|
1114
|
+
limits: {
|
|
1115
|
+
maxResults: 20,
|
|
1116
|
+
maxSnippetChars: 2000,
|
|
1117
|
+
maxInjectedChars: 10000
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
```
|
|
1122
|
+
|
|
1123
|
+
### 3.6 调试 Skill 的方法
|
|
1124
|
+
|
|
1125
|
+
#### 步骤 1:检查 Skill 加载状态
|
|
1126
|
+
|
|
1127
|
+
```bash
|
|
1128
|
+
# 完整状态报告(每个 skill 的详细信息)
|
|
1129
|
+
openclaw skills check
|
|
1130
|
+
|
|
1131
|
+
# 输出包含:
|
|
1132
|
+
# - name, description, source
|
|
1133
|
+
# - bundled 状态
|
|
1134
|
+
# - 文件路径和根目录
|
|
1135
|
+
# - eligible 状态(是否通过所有检查)
|
|
1136
|
+
# - 缺失的依赖明细
|
|
1137
|
+
# - 可用的安装选项
|
|
1138
|
+
# - 配置覆盖
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
#### 步骤 2:快速状态摘要
|
|
1142
|
+
|
|
1143
|
+
```bash
|
|
1144
|
+
openclaw skills status
|
|
1145
|
+
```
|
|
1146
|
+
|
|
1147
|
+
#### 步骤 3:安装缺失依赖
|
|
1148
|
+
|
|
1149
|
+
```bash
|
|
1150
|
+
openclaw skills install
|
|
1151
|
+
```
|
|
1152
|
+
|
|
1153
|
+
#### 步骤 4:验证 Frontmatter 语法
|
|
1154
|
+
|
|
1155
|
+
直接读取 SKILL.md 文件,检查:
|
|
1156
|
+
- YAML 块语法正确(`---` 分隔符)
|
|
1157
|
+
- 必填字段(`name`、`description`)存在
|
|
1158
|
+
- `openclaw.requires` 中的路径/环境变量拼写正确
|
|
1159
|
+
- `openclaw.install` 中的 formula/package 名有效
|
|
1160
|
+
|
|
1161
|
+
#### 步骤 5:查看运行时日志
|
|
1162
|
+
|
|
1163
|
+
```bash
|
|
1164
|
+
# Agent 会话日志(最详细,包含 Agent 的工具调用和推理过程)
|
|
1165
|
+
cat ~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl
|
|
1166
|
+
|
|
1167
|
+
# Gateway 日志中的 skill 相关信息
|
|
1168
|
+
tail -f ~/.openclaw/gateway.log | grep -i skill
|
|
1169
|
+
|
|
1170
|
+
# macOS 统一日志
|
|
1171
|
+
./scripts/clawlog.sh
|
|
1172
|
+
```
|
|
1173
|
+
|
|
1174
|
+
#### 步骤 6:验证 Skill 在 Prompt 中的呈现
|
|
1175
|
+
|
|
1176
|
+
通过 Agent 会话日志中的 system prompt 部分,确认:
|
|
1177
|
+
- Skill 是否出现在 `<available_skills>` 列表中
|
|
1178
|
+
- 描述是否完整(还是被截断为紧凑格式)
|
|
1179
|
+
- 路径是否正确
|
|
1180
|
+
|
|
1181
|
+
#### 步骤 7:测试记忆系统
|
|
1182
|
+
|
|
1183
|
+
```bash
|
|
1184
|
+
# 搜索记忆
|
|
1185
|
+
openclaw memory search "query terms"
|
|
1186
|
+
|
|
1187
|
+
# 读取特定记忆文件
|
|
1188
|
+
openclaw memory get "MEMORY.md"
|
|
1189
|
+
openclaw memory get "memory/user_role.md"
|
|
1190
|
+
```
|
|
1191
|
+
|
|
1192
|
+
#### 步骤 8:配置覆盖调试
|
|
1193
|
+
|
|
1194
|
+
```typescript
|
|
1195
|
+
// 在 OpenClaw 配置中为特定 skill 设置覆盖
|
|
1196
|
+
{
|
|
1197
|
+
skills: {
|
|
1198
|
+
"my-skill": {
|
|
1199
|
+
enabled: true, // 强制启用/禁用
|
|
1200
|
+
env: {
|
|
1201
|
+
MY_API_KEY: "test-key" // 覆盖环境变量
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
```
|
|
1207
|
+
|
|
1208
|
+
#### 常见问题排查
|
|
1209
|
+
|
|
1210
|
+
| 现象 | 可能原因 | 排查方法 |
|
|
1211
|
+
|------|----------|----------|
|
|
1212
|
+
| Skill 不出现在列表中 | 目录结构错误 | 确认路径为 `skills/<name>/SKILL.md` |
|
|
1213
|
+
| Skill 标记为 ineligible | 依赖不满足 | `openclaw skills check` 查看 missing 部分 |
|
|
1214
|
+
| Skill 被截断 | 超出字符/数量预算 | 检查 `skills.limits` 配置 |
|
|
1215
|
+
| Agent 不遵循 Skill | 描述不够精准 | 优化 `description` 字段使其更具区分性 |
|
|
1216
|
+
| SKILL.md 加载失败 | 文件过大 | 检查是否超过 256KB 限制 |
|
|
1217
|
+
| Skill 被覆盖 | 同名 skill 在更高优先级目录 | `openclaw skills check` 查看 source |
|
|
1218
|
+
|
|
1219
|
+
### 3.7 安全限制与保护机制
|
|
1220
|
+
|
|
1221
|
+
| 限制项 | 默认值 | 配置路径 |
|
|
1222
|
+
|--------|--------|----------|
|
|
1223
|
+
| 单个 SKILL.md 最大大小 | 256 KB | `skills.limits.maxSkillFileBytes` |
|
|
1224
|
+
| 每个根目录最大 skill 数 | 200 | `skills.limits.maxSkillsLoadedPerSource` |
|
|
1225
|
+
| 每个根目录最大扫描候选数 | 300 | `skills.limits.maxCandidatesPerRoot` |
|
|
1226
|
+
| Prompt 中最大 skill 数 | 150 | `skills.limits.maxSkillsInPrompt` |
|
|
1227
|
+
| Prompt 中最大字符数 | 30,000 | `skills.limits.maxSkillsPromptChars` |
|
|
1228
|
+
|
|
1229
|
+
**额外安全机制:**
|
|
1230
|
+
|
|
1231
|
+
| 机制 | 说明 |
|
|
1232
|
+
|------|------|
|
|
1233
|
+
| **符号链接包含性检查** | 所有符号链接必须解析到其根目录内 |
|
|
1234
|
+
| **路径逃逸检测** | 如果 skill 路径解析到配置根目录外,发出警告 |
|
|
1235
|
+
| **目录截断** | 根目录下子目录数量过多时停止扫描 |
|
|
1236
|
+
| **Prompt 预算强制** | 使用二分查找将 skill 适配到 token 预算内 |
|
|
1237
|
+
| **格式降级** | 超预算时自动从全量格式降级为紧凑格式 |
|
|
1238
|
+
| **必填字段验证** | 拒绝缺少必填字段的安装规格 |
|
|
1239
|
+
| **安装规格清理** | 验证并清理包名、formula、URL |
|
|
1240
|
+
|
|
1241
|
+
---
|
|
1242
|
+
|
|
1243
|
+
## 四、关键源码索引
|
|
1244
|
+
|
|
1245
|
+
### Plugin 通信层
|
|
1246
|
+
|
|
1247
|
+
| 文件 | 职责 |
|
|
1248
|
+
|------|------|
|
|
1249
|
+
| `src/channels/plugins/types.plugin.ts` | ChannelPlugin 接口定义 |
|
|
1250
|
+
| `src/channels/plugins/types.core.ts` | 核心适配器类型 |
|
|
1251
|
+
| `src/channels/plugins/types.adapters.ts` | 适配器定义 |
|
|
1252
|
+
| `src/channels/plugins/catalog.ts` | Channel 注册表 |
|
|
1253
|
+
| `src/channels/plugins/load.ts` | Plugin 加载器 |
|
|
1254
|
+
| `src/channels/session.ts` | 会话记录 |
|
|
1255
|
+
| `src/channels/routing/` | 消息路由 |
|
|
1256
|
+
| `src/auto-reply/dispatch.ts` | `dispatchInboundMessage()` |
|
|
1257
|
+
| `src/auto-reply/reply/reply-dispatcher.ts` | ReplyDispatcher 实现 |
|
|
1258
|
+
| `src/auto-reply/reply/dispatch-from-config.ts` | 回复生成 |
|
|
1259
|
+
| `src/infra/outbound/channel-selection.ts` | 出站 channel 选择 |
|
|
1260
|
+
| `src/infra/outbound/channel-resolution.ts` | Plugin adapter 解析 |
|
|
1261
|
+
| `src/infra/outbound/deliver.ts` | `deliverOutboundPayloads()` |
|
|
1262
|
+
| `src/infra/outbound/message.ts` | 高级发送接口 |
|
|
1263
|
+
|
|
1264
|
+
### Plugin SDK
|
|
1265
|
+
|
|
1266
|
+
| 文件 | 职责 |
|
|
1267
|
+
|------|------|
|
|
1268
|
+
| `src/plugin-sdk/index.ts` | 主导出入口 |
|
|
1269
|
+
| `src/plugin-sdk/core.ts` | ChannelPlugin 接口 & 帮助器 |
|
|
1270
|
+
| `src/plugin-sdk/channel-inbound.ts` | 入站消息处理 |
|
|
1271
|
+
| `src/plugin-sdk/direct-dm.ts` | DM 访问控制 |
|
|
1272
|
+
| `src/plugin-sdk/inbound-reply-dispatch.ts` | 记录 session + 派发 |
|
|
1273
|
+
| `src/plugins/types.ts` | 核心 Plugin 类型定义(1500+ 行) |
|
|
1274
|
+
| `src/plugins/manifest.ts` | Plugin 清单加载/验证 |
|
|
1275
|
+
|
|
1276
|
+
### Skill 系统
|
|
1277
|
+
|
|
1278
|
+
| 文件 | 职责 |
|
|
1279
|
+
|------|------|
|
|
1280
|
+
| `src/agents/skills/workspace.ts` | Skill 加载、过滤、Prompt 构建(主文件) |
|
|
1281
|
+
| `src/agents/skills/frontmatter.ts` | Frontmatter 解析与验证 |
|
|
1282
|
+
| `src/agents/skills/types.ts` | 类型定义 |
|
|
1283
|
+
| `src/agents/skills/config.ts` | 配置解析 |
|
|
1284
|
+
| `src/agents/skills/filter.ts` | 资格过滤 |
|
|
1285
|
+
| `src/agents/skills/plugin-skills.ts` | Plugin 提供的 Skill |
|
|
1286
|
+
| `src/agents/skills/bundled-dir.ts` | 内置 Skill 发现 |
|
|
1287
|
+
| `src/agents/skills/env-overrides.ts` | 环境变量处理 |
|
|
1288
|
+
| `src/agents/skills/refresh.ts` | Skill 刷新/重载 |
|
|
1289
|
+
| `src/agents/pi-embedded-runner/skills-runtime.ts` | 运行时 Skill 加载 |
|
|
1290
|
+
| `src/agents/system-prompt.ts` | System Prompt 注入 |
|
|
1291
|
+
|
|
1292
|
+
### 记忆系统
|
|
1293
|
+
|
|
1294
|
+
| 文件 | 职责 |
|
|
1295
|
+
|------|------|
|
|
1296
|
+
| `src/memory/memory-schema.ts` | SQLite Schema 定义 |
|
|
1297
|
+
| `src/agents/tools/memory-tool.ts` | Agent 记忆工具(search/get) |
|
|
1298
|
+
| `src/agents/memory-search.ts` | 搜索协调 |
|
|
1299
|
+
|
|
1300
|
+
### CLI 调试
|
|
1301
|
+
|
|
1302
|
+
| 文件 | 职责 |
|
|
1303
|
+
|------|------|
|
|
1304
|
+
| `src/cli/skills-cli.ts` | Skill CLI 命令 |
|
|
1305
|
+
| `src/agents/skills-status.ts` | 状态报告 |
|
|
1306
|
+
| `src/agents/skills-install.ts` | 安装执行 |
|
|
1307
|
+
|
|
1308
|
+
### 参考实现
|
|
1309
|
+
|
|
1310
|
+
| Plugin | 路径 | 类型 |
|
|
1311
|
+
|--------|------|------|
|
|
1312
|
+
| BlueBubbles | `extensions/bluebubbles/` | Channel (iMessage 桥接) |
|
|
1313
|
+
| Twitch | `extensions/twitch/` | Channel (直播聊天) |
|
|
1314
|
+
| Matrix | `extensions/matrix/` | Channel (Matrix 协议) |
|
|
1315
|
+
| Moonshot | `extensions/moonshot/` | Provider (LLM + Web Search) |
|
|
1316
|
+
| Memory LanceDB | `extensions/memory-lancedb/` | Memory (向量检索) |
|
|
1317
|
+
| MS Teams | `extensions/msteams/` | Channel |
|
|
1318
|
+
| Zalo | `extensions/zalo/` | Channel |
|
|
1319
|
+
| Voice Call | `extensions/voice-call/` | Channel (语音) |
|
|
1320
|
+
|
|
1321
|
+
---
|
|
1322
|
+
|
|
1323
|
+
> **总结:** OpenClaw 的 Plugin 机制采用**同进程回调驱动**架构,Plugin 通过 webhook 接收外部消息,经安全前置检查后通过 SDK 函数派发到 Core Reply Engine,Core 处理后通过回调让 Plugin 发送回复。SDK 提供 160+ 子路径模块,支持 Channel、Provider、Tool、Hook 等全场景二次开发。Skill 本质上是**提示注入**而非代码执行,通过 YAML frontmatter 管理元数据和依赖,记忆系统基于 SQLite + 向量嵌入 + FTS5 实现语义和关键词双路检索。
|