@core-workspace/infoflow-openclaw-plugin 2026.3.9 → 2026.3.27-beta.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/CLAUDE.md +135 -0
  3. package/COLLABORATION_REPORT.md +209 -0
  4. package/PROJECT_GUIDE.md +355 -0
  5. package/README.md +158 -66
  6. package/docs/dev-guide.md +63 -50
  7. package/docs/qa-feature-list.md +452 -0
  8. package/docs/webhook-guide.md +178 -0
  9. package/index.ts +28 -2
  10. package/openclaw.plugin.json +131 -21
  11. package/package.json +16 -3
  12. package/scripts/deploy.sh +66 -7
  13. package/scripts/postinstall.cjs +80 -0
  14. package/skills/infoflow-dev/SKILL.md +2 -2
  15. package/skills/infoflow-dev/references/api.md +1 -1
  16. package/src/adapter/inbound/webhook-parser.ts +27 -5
  17. package/src/adapter/inbound/ws-receiver.ts +304 -43
  18. package/src/adapter/outbound/markdown-local-images.ts +80 -0
  19. package/src/adapter/outbound/reply-dispatcher.ts +146 -65
  20. package/src/adapter/outbound/target-resolver.ts +4 -3
  21. package/src/channel/accounts.ts +97 -22
  22. package/src/channel/channel.ts +456 -12
  23. package/src/channel/media.ts +20 -6
  24. package/src/channel/monitor.ts +8 -3
  25. package/src/channel/outbound.ts +358 -21
  26. package/src/channel/streaming.ts +740 -0
  27. package/src/commands/changelog.ts +80 -0
  28. package/src/commands/doctor.ts +545 -0
  29. package/src/commands/logs.ts +449 -0
  30. package/src/commands/version.ts +20 -0
  31. package/src/compat/openclaw-sdk.ts +218 -0
  32. package/src/handler/message-handler.ts +673 -166
  33. package/src/logging.ts +1 -1
  34. package/src/runtime.ts +1 -1
  35. package/src/security/dm-policy.ts +1 -4
  36. package/src/security/group-policy.ts +174 -51
  37. package/src/tools/actions/index.ts +15 -13
  38. package/src/tools/cron/relay.ts +1154 -0
  39. package/src/tools/hooks/index.ts +13 -1
  40. package/src/tools/index.ts +714 -32
  41. package/src/types.ts +144 -25
  42. package/src/utils/audio/g722/dct_tables.ts +381 -0
  43. package/src/utils/audio/g722/decoder.ts +919 -0
  44. package/src/utils/audio/g722/defs.ts +105 -0
  45. package/src/utils/audio/g722/hd-parser.ts +247 -0
  46. package/src/utils/audio/g722/huff_tables.ts +240 -0
  47. package/src/utils/audio/g722/index.ts +78 -0
  48. package/src/utils/audio/g722/output_decoded.pcm +0 -0
  49. package/src/utils/audio/g722/output_decoded.wav +0 -0
  50. package/src/utils/audio/g722/tables.ts +173 -0
  51. package/src/utils/audio/g722/test_api.ts +31 -0
  52. package/src/utils/audio/g722/test_voice.hd +0 -0
  53. package/src/utils/bos/im-bos-client.ts +219 -0
  54. package/src/utils/group-agent-cache.ts +142 -0
  55. package/src/utils/token-adapter.ts +120 -51
package/src/types.ts CHANGED
@@ -10,6 +10,18 @@ export type InfoflowDmPolicy = "open" | "pairing" | "allowlist";
10
10
  export type InfoflowGroupPolicy = "open" | "allowlist" | "disabled";
11
11
  export type InfoflowChatType = "direct" | "group";
12
12
  export type InfoflowConnectionMode = "webhook" | "websocket";
13
+ export type InfoflowMessageFormat = "text" | "markdown" | "streaming-card";
14
+
15
+ export type InfoflowCronRelayConfig = {
16
+ /** Whether to relay cron run results into Infoflow. Default: true */
17
+ enabled?: boolean;
18
+ /** Skip runs already delivered by cron delivery config. Default: false */
19
+ includeAlreadyDelivered?: boolean;
20
+ /** Message prefix for relayed cron notifications. Default: "【定时任务】" */
21
+ prefix?: string;
22
+ /** Poll interval for scanning cron run logs (ms). Default: 2000 */
23
+ pollIntervalMs?: number;
24
+ };
13
25
 
14
26
  /** Reply mode controlling bot behavior per group */
15
27
  export type InfoflowReplyMode =
@@ -19,9 +31,14 @@ export type InfoflowReplyMode =
19
31
  | "mention-and-watch"
20
32
  | "proactive";
21
33
 
34
+ /** Group session mode: whether to split sessions by group or by group+user */
35
+ export type InfoflowGroupSessionMode = "group" | "user";
36
+
22
37
  /** Per-group configuration overrides */
23
38
  export type InfoflowGroupConfig = {
24
39
  replyMode?: InfoflowReplyMode;
40
+ /** Group session mode: "group" = one session per group, "user" = one session per user in group */
41
+ groupSessionMode?: InfoflowGroupSessionMode;
25
42
  systemPrompt?: string;
26
43
  /** Enable follow-up replies after bot responds (default: true) */
27
44
  followUp?: boolean;
@@ -29,8 +46,8 @@ export type InfoflowGroupConfig = {
29
46
  followUpWindow?: number;
30
47
  /** Names to watch for @mentions */
31
48
  watchMentions?: string[];
32
- /** Regex pattern to watch for in message content */
33
- watchRegex?: string;
49
+ /** Regex pattern(s) to watch for in message content (string or array; any match triggers) */
50
+ watchRegex?: string | string[];
34
51
  };
35
52
 
36
53
  // ---------------------------------------------------------------------------
@@ -50,6 +67,8 @@ export type InfoflowInboundBodyItem = {
50
67
  userid?: string;
51
68
  /** IMAGE 类型 body item 的图片下载地址 */
52
69
  downloadurl?: string;
70
+ /** Message ID in replyData items (always string; converted at inbound boundary) */
71
+ messageid?: string;
53
72
  };
54
73
 
55
74
  /** Mention IDs extracted from inbound group AT items (excluding the bot itself) */
@@ -60,6 +79,12 @@ export type InfoflowMentionIds = {
60
79
  agentIds: number[];
61
80
  };
62
81
 
82
+ /** Single agent (robot) info from the group member list API */
83
+ export type InfoflowGroupAgentInfo = {
84
+ agentId: number;
85
+ name: string;
86
+ };
87
+
63
88
  // ---------------------------------------------------------------------------
64
89
  // AT mention types
65
90
  // ---------------------------------------------------------------------------
@@ -76,7 +101,7 @@ export type InfoflowAtOptions = {
76
101
  export type InfoflowGroupMessageBodyItem =
77
102
  | { type: "TEXT"; content: string }
78
103
  | { type: "MD"; content: string }
79
- | { type: "AT"; atall?: boolean; atuserids: string[]; atagentids?: number[] }
104
+ | { type: "AT"; atall?: boolean; atuserids?: string[]; atagentids?: number[] }
80
105
  | { type: "LINK"; href: string }
81
106
  | { type: "IMAGE"; content: string };
82
107
 
@@ -86,7 +111,7 @@ export type InfoflowMessageContentItem = {
86
111
  content: string;
87
112
  };
88
113
 
89
- /** Outbound reply/quote context for group messages */
114
+ /** Outbound reply/quote context for group and private messages */
90
115
  export type InfoflowOutboundReply = {
91
116
  /** Message ID of the message being replied to (string to preserve large integer precision) */
92
117
  messageid: string;
@@ -96,6 +121,8 @@ export type InfoflowOutboundReply = {
96
121
  replytype?: "1" | "2";
97
122
  /** User IMID (optional, if not provided, shows robot name) */
98
123
  imid?: string;
124
+ /** Secondary message ID for private message reply (maps to API field "msgid2") */
125
+ msgid2?: string;
99
126
  };
100
127
 
101
128
  // ---------------------------------------------------------------------------
@@ -112,8 +139,10 @@ export type InfoflowAccountConfig = {
112
139
  appSecret?: string;
113
140
  /** 连接方式:webhook(默认)或 websocket */
114
141
  connectionMode?: InfoflowConnectionMode;
115
- /** WebSocket Gateway 域名(仅 websocket 模式使用) */
142
+ /** WebSocket Gateway 域名,用于 Phase 1 端点分配请求(仅 websocket 模式使用) */
116
143
  wsGateway?: string;
144
+ /** WebSocket 连接域名,用于 Phase 2 实际 WS 握手(不填则使用服务端返回的地址) */
145
+ wsConnectDomain?: string;
117
146
  dmPolicy?: InfoflowDmPolicy;
118
147
  allowFrom?: string[];
119
148
  groupPolicy?: InfoflowGroupPolicy;
@@ -121,19 +150,29 @@ export type InfoflowAccountConfig = {
121
150
  requireMention?: boolean;
122
151
  /** Robot name for matching @mentions in group messages */
123
152
  robotName?: string;
153
+ /** Actual robot id from Infoflow (auto-discovered when bot is @mentioned; used to ignore own messages). Not user-configured. */
154
+ robotId?: string;
124
155
  /** Names to watch for @mentions; when someone @mentions a person in this list,
125
156
  * the bot analyzes the message and replies only if confident. */
126
157
  watchMentions?: string[];
127
- /** Regex pattern to watch for in message content; triggers bot activation when matched */
128
- watchRegex?: string;
158
+ /** Regex pattern(s) to watch for in message content; triggers bot activation when any pattern matches */
159
+ watchRegex?: string | string[];
129
160
  /** Reply mode controlling bot engagement level in groups */
130
161
  replyMode?: InfoflowReplyMode;
162
+ /** Group session mode: "group" = one session per group, "user" = one session per user in group (default: "group") */
163
+ groupSessionMode?: InfoflowGroupSessionMode;
131
164
  /** Enable follow-up replies after bot responds to a mention (default: true) */
132
165
  followUp?: boolean;
133
166
  /** Follow-up window in seconds after last bot reply (default: 300) */
134
167
  followUpWindow?: number;
135
168
  /** 如流企业后台的应用ID(私聊消息撤回依赖此字段) */
136
169
  appAgentId?: number;
170
+ /** Default Infoflow target used by cron relay when the run has no explicit target */
171
+ defaultTo?: string;
172
+ /** Private plugin data directory under OpenClaw stateDir */
173
+ privateDataDir?: string;
174
+ /** Cron relay options */
175
+ cronRelay?: InfoflowCronRelayConfig;
137
176
  /** Per-group configuration overrides, keyed by group ID */
138
177
  groups?: Record<string, InfoflowGroupConfig>;
139
178
  accounts?: Record<string, InfoflowAccountConfig>;
@@ -154,14 +193,20 @@ export type InfoflowAccountConfig = {
154
193
  * Message format for outbound private (DM) messages: "text" or "markdown".
155
194
  * Default: "text"
156
195
  */
157
- dmMessageFormat?: "text" | "markdown";
196
+ dmMessageFormat?: InfoflowMessageFormat;
158
197
  /**
159
198
  * Message format for outbound group messages: "text" or "markdown".
160
199
  * Note: "markdown" does not support reply-to (quote) context — the bot
161
200
  * will still reply but without quoting the user's message.
162
201
  * Default: "text"
163
202
  */
164
- groupMessageFormat?: "text" | "markdown";
203
+ groupMessageFormat?: InfoflowMessageFormat;
204
+ /**
205
+ * Maximum character limit per outbound message chunk.
206
+ * Long messages are automatically split into multiple messages each within this limit.
207
+ * Default: 1800
208
+ */
209
+ textChunkLimit?: number;
165
210
  };
166
211
 
167
212
  export type ResolvedInfoflowAccount = {
@@ -179,8 +224,10 @@ export type ResolvedInfoflowAccount = {
179
224
  appSecret: string;
180
225
  /** 连接方式:webhook(默认)或 websocket */
181
226
  connectionMode?: InfoflowConnectionMode;
182
- /** WebSocket Gateway 域名(仅 websocket 模式使用) */
227
+ /** WebSocket Gateway 域名,用于 Phase 1 端点分配请求(仅 websocket 模式使用) */
183
228
  wsGateway?: string;
229
+ /** WebSocket 连接域名,用于 Phase 2 实际 WS 握手(不填则使用服务端返回的地址) */
230
+ wsConnectDomain?: string;
184
231
  dmPolicy?: InfoflowDmPolicy;
185
232
  allowFrom?: string[];
186
233
  groupPolicy?: InfoflowGroupPolicy;
@@ -188,36 +235,62 @@ export type ResolvedInfoflowAccount = {
188
235
  requireMention?: boolean;
189
236
  /** Robot name for matching @mentions in group messages */
190
237
  robotName?: string;
238
+ /** Actual robot id from Infoflow (auto-discovered when bot is @mentioned; used to ignore own messages). Not user-configured. */
239
+ robotId?: string;
191
240
  /** Names to watch for @mentions; when someone @mentions a person in this list,
192
241
  * the bot analyzes the message and replies only if confident. */
193
242
  watchMentions?: string[];
194
- /** Regex pattern to watch for in message content; triggers bot activation when matched */
195
- watchRegex?: string;
243
+ /** Regex pattern(s) to watch for in message content; triggers bot activation when any pattern matches */
244
+ watchRegex?: string | string[];
196
245
  /** Reply mode controlling bot engagement level in groups */
197
246
  replyMode?: InfoflowReplyMode;
247
+ /** Group session mode: "group" = one session per group, "user" = one session per user in group (default: "group") */
248
+ groupSessionMode?: InfoflowGroupSessionMode;
198
249
  /** Enable follow-up replies after bot responds to a mention (default: true) */
199
250
  followUp?: boolean;
200
251
  /** Follow-up window in seconds after last bot reply (default: 300) */
201
252
  followUpWindow?: number;
202
253
  /** 如流企业后台的应用ID(私聊消息撤回依赖此字段) */
203
254
  appAgentId?: number;
255
+ /** Default Infoflow target used by cron relay when the run has no explicit target */
256
+ defaultTo?: string;
257
+ /** Private plugin data directory under OpenClaw stateDir */
258
+ privateDataDir: string;
259
+ /** Cron relay options */
260
+ cronRelay: {
261
+ enabled: boolean;
262
+ includeAlreadyDelivered: boolean;
263
+ prefix: string;
264
+ pollIntervalMs: number;
265
+ };
204
266
  /** Per-group configuration overrides, keyed by group ID */
205
267
  groups?: Record<string, InfoflowGroupConfig>;
268
+ /**
269
+ * Send a "processing" hint message before the real reply when processing is slow.
270
+ * Default: true
271
+ */
272
+ processingHint?: boolean;
273
+ /**
274
+ * Delay in seconds before sending the "processing" hint message.
275
+ * Default: 5
276
+ */
277
+ processingHintDelay?: number;
206
278
  /**
207
279
  * Message format for outbound private (DM) messages: "text" or "markdown".
208
280
  * Default: "text"
209
281
  */
210
- dmMessageFormat?: "text" | "markdown";
282
+ dmMessageFormat?: InfoflowMessageFormat;
211
283
  /**
212
284
  * Message format for outbound group messages: "text" or "markdown".
213
285
  * Note: "markdown" does not support reply-to (quote) context.
214
286
  * Default: "text"
215
287
  */
216
- groupMessageFormat?: "text" | "markdown";
217
- dmPolicy?: string;
218
- allowFrom?: string[];
219
- groupPolicy?: string;
220
- groupAllowFrom?: string[];
288
+ groupMessageFormat?: InfoflowMessageFormat;
289
+ /**
290
+ * Maximum character limit per outbound message chunk.
291
+ * Default: 1800
292
+ */
293
+ textChunkLimit?: number;
221
294
  };
222
295
  };
223
296
 
@@ -225,16 +298,50 @@ export type ResolvedInfoflowAccount = {
225
298
  // Message types
226
299
  // ---------------------------------------------------------------------------
227
300
 
301
+ /** 如流消息发送者身份(判别联合) */
302
+ export type InfoflowSender =
303
+ | {
304
+ /** 普通账户 */
305
+ kind: "regular";
306
+ /** UUAP 用户名(fromuserid) */
307
+ userid: string;
308
+ /** 显示名称 */
309
+ name?: string;
310
+ /** 数字 IMID(fromid) */
311
+ imid?: string;
312
+ }
313
+ | {
314
+ /** 机器人账户 */
315
+ kind: "robot";
316
+ /** 机器人 agent ID(数字,对应 robotid / fromid);群聊 ALL_MESSAGE_FORWARD 场景暂无法获取,可能为空 */
317
+ agentid?: string;
318
+ /** 显示名称 */
319
+ name?: string;
320
+ /** 数字 IMID(fromid,机器人的 fromid = robotid) */
321
+ imid?: string;
322
+ };
323
+
324
+ /** 输出 InfoflowSender 的描述字符串,用于日志和调试 */
325
+ export function descSender(s: InfoflowSender): string {
326
+ if (s.kind === "regular") {
327
+ return `regular(${s.userid}/${s.name ?? "-"}/${s.imid ?? "-"})`;
328
+ }
329
+ return `robot(${s.agentid ?? "-"}/${s.name ?? "-"}/${s.imid ?? "-"})`;
330
+ }
331
+
228
332
  export type InfoflowMessageEvent = {
229
- fromuser: string;
333
+ /** 发送者身份信息(包含 kind/id/name/imid) */
334
+ sender: InfoflowSender;
230
335
  mes: string;
231
336
  chatType: InfoflowChatType;
232
337
  groupId?: number;
233
- senderName?: string;
338
+
234
339
  /** Whether the bot was @mentioned in the message */
235
340
  wasMentioned?: boolean;
236
341
  /** Original message ID from Infoflow */
237
342
  messageId?: string;
343
+ /** Secondary message ID from Infoflow (used for private message reply as "msgid2") */
344
+ msgid2?: string;
238
345
  /** Unix millisecond timestamp of the message */
239
346
  timestamp?: number;
240
347
  /** Raw message text preserving @mentions (for RawBody) */
@@ -247,8 +354,20 @@ export type InfoflowMessageEvent = {
247
354
  replyContext?: string[];
248
355
  /** Image download URLs extracted from IMAGE body items (group) or PicUrl (private) */
249
356
  imageUrls?: string[];
250
- /** Sender's IMID (numeric user ID) for reply context */
251
- senderImid?: string;
357
+ /** Agent-visible body text with robotid annotations (e.g. @name (robotid:N)) for LLM */
358
+ bodyForAgent?: string;
359
+ /** Whether the message is a quoted reply to one of the bot's own previous messages */
360
+ isReplyToBot?: boolean;
361
+ /** 语音消息的音频下载地址 */
362
+ voiceUrl?: string;
363
+ /** 本地 hd→wav 转换后的 WAV 文件路径 */
364
+ localVoicePath?: string;
365
+ /** 消息来源平台标识 */
366
+ fromPlatform?: string;
367
+ /** 如流应用的 agentId */
368
+ agentId?: string;
369
+ /** 如流开放平台的 OpenCode(加密的用户/应用凭证) */
370
+ openCode?: string;
252
371
  };
253
372
 
254
373
  // ---------------------------------------------------------------------------
@@ -256,21 +375,21 @@ export type InfoflowMessageEvent = {
256
375
  // ---------------------------------------------------------------------------
257
376
 
258
377
  export type HandleInfoflowMessageParams = {
259
- cfg: import("openclaw/plugin-sdk").OpenClawConfig;
378
+ cfg: import("openclaw/plugin-sdk/plugin-entry").OpenClawConfig;
260
379
  event: InfoflowMessageEvent;
261
380
  accountId: string;
262
381
  statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void;
263
382
  };
264
383
 
265
384
  export type HandlePrivateChatParams = {
266
- cfg: import("openclaw/plugin-sdk").OpenClawConfig;
385
+ cfg: import("openclaw/plugin-sdk/plugin-entry").OpenClawConfig;
267
386
  msgData: Record<string, unknown>;
268
387
  accountId: string;
269
388
  statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void;
270
389
  };
271
390
 
272
391
  export type HandleGroupChatParams = {
273
- cfg: import("openclaw/plugin-sdk").OpenClawConfig;
392
+ cfg: import("openclaw/plugin-sdk/plugin-entry").OpenClawConfig;
274
393
  msgData: Record<string, unknown>;
275
394
  accountId: string;
276
395
  statusSink?: (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void;