@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.
@@ -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 实现语义和关键词双路检索。