@core-workspace/infoflow-openclaw-plugin 2026.3.8

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,423 @@
1
+ # 如流 Openclaw 插件 - 技术架构文档
2
+
3
+ ## 项目概述
4
+
5
+ 这是一个如流(百度企业沟通平台)的 Openclaw 插件项目,采用 Webhook 方式实现消息收发,支持单聊和群聊,支持多账号和丰富的参数配置。
6
+
7
+ ---
8
+
9
+ ## 项目结构
10
+
11
+ ```
12
+ infoflow/
13
+ ├── index.ts # 插件入口,注册Channel和Webhook路由
14
+ ├── openclaw.plugin.json # 插件元数据和配置schema
15
+ ├── package.json # 项目依赖和元数据
16
+ ├── tsconfig.json # TypeScript配置
17
+ ├── src/
18
+ │ ├── channel.ts # Channel插件定义(核心)
19
+ │ ├── monitor.ts # Webhook监听器
20
+ │ ├── infoflow-req-parse.ts # Webhook请求解析、解密、去重
21
+ │ ├── bot.ts # 消息处理核心逻辑(replyMode决策)
22
+ │ ├── send.ts # 消息发送API(私聊/群聊)
23
+ │ ├── reply-dispatcher.ts # 回复分发器(@mentions解析、分块)
24
+ │ ├── media.ts # 图片处理(下载、压缩、Base64)
25
+ │ ├── actions.ts # LLM Tool适配(send/delete)
26
+ │ ├── accounts.ts # 多账号支持、配置合并
27
+ │ ├── targets.ts # 目标规范化(用户/群)
28
+ │ ├── sent-message-store.ts # 已发送消息存储(SQLite)
29
+ │ ├── types.ts # TypeScript类型定义
30
+ │ ├── logging.ts # 日志模块
31
+ │ └── runtime.ts # Runtime访问器
32
+ ├── docs/
33
+ │ └── architecture.md # 本技术架构文档
34
+ └── README.md # 完整文档
35
+ ```
36
+
37
+ ---
38
+
39
+ ## 核心模块职责
40
+
41
+ | 模块 | 文件 | 核心职责 |
42
+ |------|------|---------|
43
+ | **插件入口** | [index.ts](../index.ts) | 注册插件、Channel定义、Webhook路由 |
44
+ | **Channel定义** | [src/channel.ts](../src/channel.ts) | 定义插件结构、生命周期、Actions、配置管理 |
45
+ | **Webhook监听** | [src/monitor.ts](../src/monitor.ts) | Webhook目标注册、HTTP请求路由 |
46
+ | **请求解析** | [src/infoflow-req-parse.ts](../src/infoflow-req-parse.ts) | AES-ECB解密、消息去重、私聊/群聊路由 |
47
+ | **消息处理** | [src/bot.ts](../src/bot.ts) | replyMode决策、历史注入、LLM调用 |
48
+ | **消息发送** | [src/send.ts](../src/send.ts) | Token管理、私聊/群聊发送、消息撤回 |
49
+ | **回复分发** | [src/reply-dispatcher.ts](../src/reply-dispatcher.ts) | @mentions解析、文本分块、replyTo构造 |
50
+ | **图片处理** | [src/media.ts](../src/media.ts) | 图片下载、压缩到1MB、Base64编码 |
51
+ | **Actions** | [src/actions.ts](../src/actions.ts) | LLM Tool(send/delete消息) |
52
+ | **多账号** | [src/accounts.ts](../src/accounts.ts) | 账号解析、配置合并 |
53
+ | **存储** | [src/sent-message-store.ts](../src/sent-message-store.ts) | 已发送消息记录(SQLite) |
54
+
55
+ ---
56
+
57
+ ## 技术栈
58
+
59
+ | 类别 | 技术 |
60
+ |------|------|
61
+ | **语言** | TypeScript |
62
+ | **运行时** | Node.js >= 18 |
63
+ | **框架** | OpenClaw Plugin SDK (>= 2026.3.2) |
64
+ | **加密** | node:crypto (AES-128/192/256-ECB) |
65
+ | **存储** | node:sqlite (同步API) |
66
+ | **HTTP** | fetch API |
67
+
68
+ ---
69
+
70
+ ## 配置管理
71
+
72
+ ### 配置文件
73
+ - [openclaw.plugin.json](../openclaw.plugin.json) - 插件元数据和配置 schema
74
+
75
+ ### 核心配置项
76
+
77
+ | 配置项 | 类型 | 必填 | 说明 |
78
+ |--------|------|------|------|
79
+ | `checkToken` | string | ✅ | Webhook验证token |
80
+ | `encodingAESKey` | string | ✅ | AES-ECB加密密钥 |
81
+ | `appKey` | string | ✅ | 应用Key |
82
+ | `appSecret` | string | ✅ | 应用Secret |
83
+ | `apiHost` | string | ❌ | 如流API地址 |
84
+ | `robotName` | string | ❌ | 机器人名称,用于@检测 |
85
+ | `appAgentId` | number | ❌ | 应用ID,私聊消息撤回需要 |
86
+ | `replyMode` | string | ❌ | 回复模式(默认: mention-and-watch) |
87
+ | `followUp` | boolean | ❌ | 是否启用跟进回复 |
88
+ | `followUpWindow` | number | ❌ | 跟进窗口(秒) |
89
+ | `watchMentions` | string[] | ❌ | 关注提及的人员列表 |
90
+ | `watchRegex` | string | ❌ | 正则匹配规则 |
91
+ | `dmPolicy` | string | ❌ | 私聊策略 |
92
+ | `groupPolicy` | string | ❌ | 群聊策略 |
93
+ | `defaultAccount` | string | ❌ | 默认账号ID |
94
+ | `accounts` | object | ❌ | 多账号配置 |
95
+ | `groups` | object | ❌ | 按群配置 |
96
+
97
+ ### DEFAULT_ACCOUNT_ID
98
+ - 来自 `openclaw/plugin-sdk`,值为 `"default"`
99
+ - 用于单账号场景和向后兼容
100
+ - 多账号场景下,通过 `defaultAccount` 字段指定默认账号
101
+
102
+ ---
103
+
104
+ ## 系统架构图
105
+
106
+ ```
107
+ ┌─────────────────────────────────────────────────────────┐
108
+ │ 如流服务器 │
109
+ │ (发送 Webhook) │
110
+ └────────────────────────┬────────────────────────────────┘
111
+
112
+
113
+ ┌─────────────────────────────────────────────────────────┐
114
+ │ Webhook 层 │
115
+ │ ┌──────────────┐ ┌─────────────────────────────┐ │
116
+ │ │ monitor.ts │───▶│ infoflow-req-parse.ts │ │
117
+ │ │ (路由监听) │ │ (解密/去重/解析) │ │
118
+ │ └──────────────┘ └─────────────────────────────┘ │
119
+ └────────────────────────┬────────────────────────────────┘
120
+
121
+
122
+ ┌─────────────────────────────────────────────────────────┐
123
+ │ 业务层 │
124
+ │ ┌──────────────┐ ┌─────────────────────────────┐ │
125
+ │ │ bot.ts │───▶│ accounts.ts / targets.ts │ │
126
+ │ │(replyMode/ │ │ (多账号/目标规范化) │ │
127
+ │ │ LLM调用) │ └─────────────────────────────┘ │
128
+ │ └──────────────┘ │
129
+ └────────────────────────┬────────────────────────────────┘
130
+
131
+
132
+ ┌─────────────────────────────────────────────────────────┐
133
+ │ 发送层 │
134
+ │ ┌──────────────┐ ┌─────────────────────────────┐ │
135
+ │ │ send.ts │───▶│ reply-dispatcher.ts │ │
136
+ │ │(Token/消息 │ │ (@mentions/分块) │ │
137
+ │ │ 发送/撤回) │ └─────────────────────────────┘ │
138
+ │ └──────────────┘ ┌──────────────────────┐ │
139
+ │ │ media.ts │ │
140
+ │ │ (图片处理/压缩) │ │
141
+ │ └──────────────────────┘ │
142
+ └────────────────────────┬────────────────────────────────┘
143
+
144
+
145
+ ┌─────────────────────────────────────────────────────────┐
146
+ │ 存储层 │
147
+ │ ┌─────────────────────────────────────────────────┐ │
148
+ │ │ sent-message-store.ts (SQLite) │ │
149
+ │ │ - 已发送消息记录 │ │
150
+ │ │ - 内存缓存 (chatHistories, 20条/群) │ │
151
+ │ │ - 去重缓存 (5分钟) │ │
152
+ │ └─────────────────────────────────────────────────┘ │
153
+ └─────────────────────────────────────────────────────────┘
154
+ ```
155
+
156
+ ---
157
+
158
+ ## Webhook 处理流程
159
+
160
+ ### 消息接收流程图
161
+
162
+ ```
163
+ 用户发送消息
164
+
165
+
166
+ 如流服务器 → POST /webhook/infoflow
167
+
168
+
169
+ ┌──────────────────────────────────────────────────┐
170
+ │ handleInfoflowWebhookRequest (monitor.ts) │
171
+ │ - 检查路径匹配 │
172
+ │ - 验证HTTP方法(POST only) │
173
+ │ - 读取原始body │
174
+ └────────────────────┬─────────────────────────────┘
175
+
176
+
177
+ ┌──────────────────────────────────────────────────┐
178
+ │ parseAndDispatchInfoflowRequest │
179
+ │ (infoflow-req-parse.ts) │
180
+ │ │
181
+ │ 根据 Content-Type 分流: │
182
+ │ │
183
+ │ application/x-www-form-urlencoded: │
184
+ │ - echostr验证 (Webhook验证) │
185
+ │ - 私聊消息 │
186
+ │ │
187
+ │ text/plain: │
188
+ │ - 群聊消息 │
189
+ └────────────────────┬─────────────────────────────┘
190
+
191
+
192
+ ┌──────────────────────────────────────────────────┐
193
+ │ AES-ECB 解密 │
194
+ │ - 私聊: 解密 messageJson.Encrypt │
195
+ │ - 群聊: 解密整个body │
196
+ └────────────────────┬─────────────────────────────┘
197
+
198
+
199
+ ┌──────────────────────────────────────────────────┐
200
+ │ 消息去重 │
201
+ │ - 5分钟缓存 │
202
+ │ - 避免重复处理 │
203
+ └────────────────────┬─────────────────────────────┘
204
+
205
+
206
+ ┌──────────────────────────────────────────────────┐
207
+ │ dispatch到bot处理 │
208
+ │ - handlePrivateChatMessage() │
209
+ │ - handleGroupChatMessage() │
210
+ └──────────────────────────────────────────────────┘
211
+ ```
212
+
213
+ ### 加密方式详情
214
+
215
+ | 消息类型 | Content-Type | 加密内容 |
216
+ |----------|--------------|----------|
217
+ | **私聊** | application/x-www-form-urlencoded | `messageJson` 字段中的 `Encrypt` 字段(AES-ECB) |
218
+ | **群聊** | text/plain | 整个body(AES-ECB) |
219
+
220
+ ---
221
+
222
+ ## 单聊/群聊支持
223
+
224
+ ### 消息接收
225
+
226
+ | 类型 | 处理函数 | 文件 |
227
+ |------|----------|------|
228
+ | 私聊 | `handlePrivateChatMessage()` | [src/bot.ts](../src/bot.ts) |
229
+ | 群聊 | `handleGroupChatMessage()` | [src/bot.ts](../src/bot.ts) |
230
+
231
+ ### 消息发送
232
+
233
+ | 类型 | 发送函数 | 文件 |
234
+ |------|----------|------|
235
+ | 私聊 | `sendInfoflowPrivateMessage()` | [src/send.ts](../src/send.ts) |
236
+ | 群聊 | `sendInfoflowGroupMessage()` | [src/send.ts](../src/send.ts) |
237
+
238
+ ### 消息撤回
239
+
240
+ | 类型 | 撤回函数 | 依赖 |
241
+ |------|----------|------|
242
+ | 私聊 | `recallInfoflowPrivateMessage()` | 需要 `appAgentId` |
243
+ | 群聊 | `recallInfoflowGroupMessage()` | - |
244
+
245
+ ---
246
+
247
+ ## 回复模式 (replyMode)
248
+
249
+ | 模式 | 说明 |
250
+ |------|------|
251
+ | `ignore` | 丢弃消息,不做任何处理 |
252
+ | `record` | 仅保存历史记录,不触发回复 |
253
+ | `mention-only` | 仅被@机器人时回复 |
254
+ | `mention-and-watch` | 被@或关注的人被@时回复(默认) |
255
+ | `proactive` | 主动回复所有消息 |
256
+
257
+ ### replyMode 决策流程
258
+
259
+ ```
260
+ 收到消息
261
+
262
+
263
+ ┌──────────────────────────────────┐
264
+ │ 配置级别判断 │
265
+ │ 1. 按群配置 (groups.groupId) │
266
+ │ 2. 按账号配置 (accounts.accountId)│
267
+ │ 3. 全局默认配置 │
268
+ └────────────┬─────────────────────┘
269
+
270
+
271
+ ┌──────────────────────────────────┐
272
+ │ 消息类型判断 │
273
+ │ - 私聊使用 dmPolicy │
274
+ │ - 群聊使用 groupPolicy │
275
+ └────────────┬─────────────────────┘
276
+
277
+
278
+ ┌──────────────────────────────────┐
279
+ │ 执行策略 │
280
+ │ - ignore: 跳过 │
281
+ │ - record: 仅记录 │
282
+ │ - mention-*: 检查@条件 │
283
+ │ - proactive: 直接处理 │
284
+ └──────────────────────────────────┘
285
+ ```
286
+
287
+ ---
288
+
289
+ ## 支持的消息类型
290
+
291
+ | 类型 | 私聊 | 群聊 | 说明 |
292
+ |------|------|------|------|
293
+ | `text` | ✅ | ✅ | 纯文本 |
294
+ | `markdown` | ✅ | ✅ | Markdown格式 |
295
+ | `at` | - | ✅ | @用户 |
296
+ | `at-agent` | - | ✅ | @机器人 |
297
+ | `link` | ✅ | ✅ | 链接 |
298
+ | `image` | ✅ | ✅ | 图片 |
299
+
300
+ ### 特殊功能
301
+
302
+ | 功能 | 说明 |
303
+ |------|------|
304
+ | **@all** | 群聊中@所有成员 |
305
+ | **watchMentions** | 监控指定人员被@触发 |
306
+ | **watchRegex** | 正则匹配触发 |
307
+ | **replyTo** | 引用回复(支持messageid、preview、imid) |
308
+ | **引用回复** | 群聊中支持引用原消息回复 |
309
+
310
+ ---
311
+
312
+ ## 数据流
313
+
314
+ ### 入站消息流程
315
+
316
+ ```
317
+ Webhook 接收
318
+
319
+
320
+ AES-ECB 解密
321
+
322
+
323
+ 消息去重 (5分钟缓存)
324
+
325
+
326
+ 解析消息内容
327
+
328
+
329
+ replyMode 决策
330
+
331
+
332
+ 构造 LLM Payload
333
+
334
+ ├──────────▶ 记录 Session
335
+
336
+
337
+ 调用 LLM
338
+
339
+
340
+ 生成回复
341
+ ```
342
+
343
+ ### 出站消息流程
344
+
345
+ ```
346
+ LLM 输出
347
+
348
+
349
+ 解析 @mentions
350
+
351
+
352
+ 文本分块 (4000字/块)
353
+
354
+
355
+ 构造 replyTo (仅第一块)
356
+
357
+
358
+ 获取访问 Token
359
+
360
+
361
+ 循环发送各块消息
362
+
363
+
364
+ 记录已发送消息 (SQLite)
365
+ ```
366
+
367
+ ---
368
+
369
+ ## 关键设计决策
370
+
371
+ | 设计决策 | 说明 |
372
+ |----------|------|
373
+ | **Token缓存** | 按appKey缓存,提前5分钟刷新,避免频繁请求 |
374
+ | **消息分批** | LINK/IMAGE类型单独发送,避免与其他类型混排 |
375
+ | **文本分块** | 单条消息限制4000字符,超长自动分块 |
376
+ | **replyTo复用** | 只在第一条消息中使用,避免重复引用 |
377
+ | **@mentions复用** | 只在第一块文本中使用,后续块自动继承 |
378
+ | **大整数处理** | 手动拼接JSON避免JS Number精度丢失(messageId等) |
379
+ | **图片SSRF防护** | 白名单域名+路径检查,防止SSRF攻击 |
380
+ | **内存缓存** | chatHistories最多20条/群,避免内存溢出 |
381
+ | **持久化存储** | session transcript无限制,支持完整对话历史 |
382
+
383
+ ---
384
+
385
+ ## 扩展点
386
+
387
+ ### 1. 添加新消息类型
388
+
389
+ **步骤**:
390
+ 1. 在 [src/types.ts](../src/types.ts) 中添加新类型定义
391
+ 2. 在 [src/send.ts](../src/send.ts) 中实现发送逻辑
392
+ 3. 在 [src/reply-dispatcher.ts](../src/reply-dispatcher.ts) 中添加处理分支
393
+
394
+ ### 2. 添加新回复模式
395
+
396
+ **步骤**:
397
+ 1. 在 [src/bot.ts](../src/bot.ts) 中的 `InfoflowReplyMode` 类型添加新值
398
+ 2. 在 `shouldReply` 函数中添加判断逻辑
399
+
400
+ ### 3. 添加新媒体类型
401
+
402
+ **步骤**:
403
+ 1. 在 [src/media.ts](../src/media.ts) 中扩展下载和处理函数
404
+ 2. 在 [src/types.ts](../src/types.ts) 中添加类型定义
405
+
406
+ ### 4. 添加新Action
407
+
408
+ **步骤**:
409
+ 1. 在 [src/actions.ts](../src/actions.ts) 中添加新的 `ChannelMessageActionName`
410
+ 2. 在 [src/channel.ts](../src/channel.ts) 的 `actions` 中注册
411
+
412
+ ---
413
+
414
+ ## 总结
415
+
416
+ 这是一个功能完善的企业级聊天机器人插件,采用清晰的模块化设计:
417
+
418
+ - **Webhook层**: 处理消息接收、解密、去重
419
+ - **业务层**: replyMode决策、LLM调用
420
+ - **发送层**: Token管理、消息发送、回复分发
421
+ - **存储层**: SQLite持久化、内存缓存
422
+
423
+ 支持多账号、私聊/群聊、图片处理、消息撤回等丰富功能,并具备良好的扩展性。