@core-workspace/infoflow-openclaw-plugin 2026.3.8 → 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.
- package/CHANGELOG.md +91 -0
- package/CLAUDE.md +135 -0
- package/COLLABORATION_REPORT.md +209 -0
- package/PROJECT_GUIDE.md +355 -0
- package/README.md +158 -66
- package/docs/dev-guide.md +63 -50
- package/docs/qa-feature-list.md +452 -0
- package/docs/webhook-guide.md +178 -0
- package/index.ts +28 -2
- package/openclaw.plugin.json +131 -21
- package/package.json +20 -3
- package/scripts/deploy.sh +66 -7
- package/scripts/postinstall.cjs +80 -0
- package/skills/infoflow-dev/SKILL.md +2 -2
- package/skills/infoflow-dev/references/api.md +1 -1
- package/src/adapter/inbound/webhook-parser.ts +27 -5
- package/src/adapter/inbound/ws-receiver.ts +304 -43
- package/src/adapter/outbound/markdown-local-images.ts +80 -0
- package/src/adapter/outbound/reply-dispatcher.ts +146 -65
- package/src/adapter/outbound/target-resolver.ts +4 -3
- package/src/channel/accounts.ts +97 -22
- package/src/channel/channel.ts +456 -12
- package/src/channel/media.ts +20 -6
- package/src/channel/monitor.ts +8 -3
- package/src/channel/outbound.ts +358 -21
- package/src/channel/streaming.ts +740 -0
- package/src/commands/changelog.ts +80 -0
- package/src/commands/doctor.ts +545 -0
- package/src/commands/logs.ts +449 -0
- package/src/commands/version.ts +20 -0
- package/src/compat/openclaw-sdk.ts +218 -0
- package/src/handler/message-handler.ts +673 -166
- package/src/logging.ts +1 -1
- package/src/runtime.ts +1 -1
- package/src/security/dm-policy.ts +1 -4
- package/src/security/group-policy.ts +174 -51
- package/src/tools/actions/index.ts +15 -13
- package/src/tools/cron/relay.ts +1154 -0
- package/src/tools/hooks/index.ts +13 -1
- package/src/tools/index.ts +714 -32
- package/src/types.ts +144 -25
- package/src/utils/audio/g722/dct_tables.ts +381 -0
- package/src/utils/audio/g722/decoder.ts +919 -0
- package/src/utils/audio/g722/defs.ts +105 -0
- package/src/utils/audio/g722/hd-parser.ts +247 -0
- package/src/utils/audio/g722/huff_tables.ts +240 -0
- package/src/utils/audio/g722/index.ts +78 -0
- package/src/utils/audio/g722/output_decoded.pcm +0 -0
- package/src/utils/audio/g722/output_decoded.wav +0 -0
- package/src/utils/audio/g722/tables.ts +173 -0
- package/src/utils/audio/g722/test_api.ts +31 -0
- package/src/utils/audio/g722/test_voice.hd +0 -0
- package/src/utils/bos/im-bos-client.ts +219 -0
- package/src/utils/group-agent-cache.ts +142 -0
- package/src/utils/token-adapter.ts +120 -51
package/src/channel/channel.ts
CHANGED
|
@@ -6,23 +6,38 @@ import {
|
|
|
6
6
|
migrateBaseNameToDefaultAccount,
|
|
7
7
|
normalizeAccountId,
|
|
8
8
|
setAccountEnabledInConfigSection,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from "openclaw/plugin-sdk";
|
|
9
|
+
} from "../compat/openclaw-sdk.js";
|
|
10
|
+
import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
|
|
11
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/plugin-entry";
|
|
12
12
|
import {
|
|
13
|
+
normalizeInfoflowTarget,
|
|
14
|
+
looksLikeInfoflowId,
|
|
15
|
+
} from "../adapter/outbound/target-resolver.js";
|
|
16
|
+
import { logVerbose } from "../logging.js";
|
|
17
|
+
import { getInfoflowRuntime } from "../runtime.js";
|
|
18
|
+
import { infoflowMessageActions } from "../tools/actions/index.js";
|
|
19
|
+
import type { ResolvedInfoflowAccount } from "../types.js";
|
|
20
|
+
import {
|
|
21
|
+
DEFAULT_INFOFLOW_API_HOST,
|
|
22
|
+
DEFAULT_INFOFLOW_CONNECTION_MODE,
|
|
23
|
+
DEFAULT_INFOFLOW_CRON_RELAY_POLL_INTERVAL_MS,
|
|
24
|
+
DEFAULT_INFOFLOW_CRON_RELAY_PREFIX,
|
|
25
|
+
DEFAULT_INFOFLOW_DM_MESSAGE_FORMAT,
|
|
26
|
+
DEFAULT_INFOFLOW_FOLLOW_UP_WINDOW,
|
|
27
|
+
DEFAULT_INFOFLOW_GROUP_MESSAGE_FORMAT,
|
|
28
|
+
DEFAULT_INFOFLOW_GROUP_SESSION_MODE,
|
|
29
|
+
DEFAULT_INFOFLOW_PRIVATE_DATA_DIR,
|
|
30
|
+
DEFAULT_INFOFLOW_PROCESSING_HINT_DELAY,
|
|
31
|
+
DEFAULT_INFOFLOW_TEXT_CHUNK_LIMIT,
|
|
32
|
+
DEFAULT_INFOFLOW_WS_GATEWAY,
|
|
13
33
|
getChannelSection,
|
|
14
34
|
listInfoflowAccountIds,
|
|
15
35
|
resolveDefaultInfoflowAccountId,
|
|
16
36
|
resolveInfoflowAccount,
|
|
17
37
|
} from "./accounts.js";
|
|
18
|
-
import { infoflowMessageActions } from "../tools/actions/index.js";
|
|
19
|
-
import { logVerbose } from "../logging.js";
|
|
20
38
|
import { prepareInfoflowImageBase64, sendInfoflowImageMessage } from "./media.js";
|
|
21
39
|
import { startInfoflowMonitor, startInfoflowWSMonitor } from "./monitor.js";
|
|
22
|
-
import { getInfoflowRuntime } from "../runtime.js";
|
|
23
40
|
import { sendInfoflowMessage } from "./outbound.js";
|
|
24
|
-
import { normalizeInfoflowTarget, looksLikeInfoflowId } from "../adapter/outbound/target-resolver.js";
|
|
25
|
-
import type { ResolvedInfoflowAccount } from "../types.js";
|
|
26
41
|
|
|
27
42
|
// Re-export types and account functions for external consumers
|
|
28
43
|
export type { InfoflowAccountConfig, ResolvedInfoflowAccount } from "../types.js";
|
|
@@ -32,6 +47,434 @@ export { resolveInfoflowAccount } from "./accounts.js";
|
|
|
32
47
|
// Channel plugin
|
|
33
48
|
// ---------------------------------------------------------------------------
|
|
34
49
|
|
|
50
|
+
const INFOFLOW_REPLY_MODE_ENUM = [
|
|
51
|
+
"ignore",
|
|
52
|
+
"record",
|
|
53
|
+
"mention-only",
|
|
54
|
+
"mention-and-watch",
|
|
55
|
+
"proactive",
|
|
56
|
+
] as const;
|
|
57
|
+
|
|
58
|
+
const infoflowGroupConfigSchemaProperties = {
|
|
59
|
+
replyMode: {
|
|
60
|
+
type: "string",
|
|
61
|
+
enum: [...INFOFLOW_REPLY_MODE_ENUM],
|
|
62
|
+
description: "群级回复模式覆盖",
|
|
63
|
+
},
|
|
64
|
+
groupSessionMode: {
|
|
65
|
+
type: "string",
|
|
66
|
+
enum: ["group", "user"],
|
|
67
|
+
default: DEFAULT_INFOFLOW_GROUP_SESSION_MODE,
|
|
68
|
+
description: "群聊会话拆分模式:group=按群,user=按群+人",
|
|
69
|
+
},
|
|
70
|
+
watchMentions: {
|
|
71
|
+
type: "array",
|
|
72
|
+
items: { type: "string" },
|
|
73
|
+
description: "当这些人被 @ 时触发观察模式",
|
|
74
|
+
},
|
|
75
|
+
watchRegex: {
|
|
76
|
+
oneOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
|
|
77
|
+
description: "群消息匹配任一正则时触发观察模式(兼容单个字符串或数组)",
|
|
78
|
+
},
|
|
79
|
+
followUp: {
|
|
80
|
+
type: "boolean",
|
|
81
|
+
default: true,
|
|
82
|
+
description: "机器人回复后是否允许一段时间内连续追问",
|
|
83
|
+
},
|
|
84
|
+
followUpWindow: {
|
|
85
|
+
type: "integer",
|
|
86
|
+
minimum: 0,
|
|
87
|
+
default: DEFAULT_INFOFLOW_FOLLOW_UP_WINDOW,
|
|
88
|
+
description: "追问窗口时长(秒)",
|
|
89
|
+
},
|
|
90
|
+
systemPrompt: {
|
|
91
|
+
type: "string",
|
|
92
|
+
description: "该群专属的附加系统提示词",
|
|
93
|
+
},
|
|
94
|
+
} as const;
|
|
95
|
+
|
|
96
|
+
const infoflowCronRelaySchema = {
|
|
97
|
+
type: "object",
|
|
98
|
+
additionalProperties: false,
|
|
99
|
+
properties: {
|
|
100
|
+
enabled: {
|
|
101
|
+
type: "boolean",
|
|
102
|
+
default: true,
|
|
103
|
+
description: "是否把 OpenClaw cron 运行结果转发到如流",
|
|
104
|
+
},
|
|
105
|
+
includeAlreadyDelivered: {
|
|
106
|
+
type: "boolean",
|
|
107
|
+
default: false,
|
|
108
|
+
description: "是否也转发已由 OpenClaw delivery 投递过的任务",
|
|
109
|
+
},
|
|
110
|
+
prefix: {
|
|
111
|
+
type: "string",
|
|
112
|
+
default: DEFAULT_INFOFLOW_CRON_RELAY_PREFIX,
|
|
113
|
+
description: "定时任务转发前缀",
|
|
114
|
+
},
|
|
115
|
+
pollIntervalMs: {
|
|
116
|
+
type: "integer",
|
|
117
|
+
minimum: 500,
|
|
118
|
+
default: DEFAULT_INFOFLOW_CRON_RELAY_POLL_INTERVAL_MS,
|
|
119
|
+
description: "cron 运行日志轮询间隔(毫秒)",
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
} as const;
|
|
123
|
+
|
|
124
|
+
const infoflowAccountConfigSchemaProperties = {
|
|
125
|
+
enabled: {
|
|
126
|
+
type: "boolean",
|
|
127
|
+
default: true,
|
|
128
|
+
description: "是否启用当前 Infoflow 账号",
|
|
129
|
+
},
|
|
130
|
+
name: {
|
|
131
|
+
type: "string",
|
|
132
|
+
description: "账号展示名称,仅用于控制面板区分账号",
|
|
133
|
+
},
|
|
134
|
+
apiHost: {
|
|
135
|
+
type: "string",
|
|
136
|
+
default: DEFAULT_INFOFLOW_API_HOST,
|
|
137
|
+
description: "如流发送 API 域名",
|
|
138
|
+
},
|
|
139
|
+
connectionMode: {
|
|
140
|
+
type: "string",
|
|
141
|
+
enum: ["webhook", "websocket"],
|
|
142
|
+
default: DEFAULT_INFOFLOW_CONNECTION_MODE,
|
|
143
|
+
description: "消息接收模式,默认 websocket",
|
|
144
|
+
},
|
|
145
|
+
wsGateway: {
|
|
146
|
+
type: "string",
|
|
147
|
+
default: DEFAULT_INFOFLOW_WS_GATEWAY,
|
|
148
|
+
description: "WebSocket 网关域名,仅 websocket 模式使用",
|
|
149
|
+
},
|
|
150
|
+
checkToken: {
|
|
151
|
+
type: "string",
|
|
152
|
+
description: "Webhook 校验 token,仅 webhook 模式必填",
|
|
153
|
+
},
|
|
154
|
+
encodingAESKey: {
|
|
155
|
+
type: "string",
|
|
156
|
+
description: "Webhook 加密密钥,仅 webhook 模式必填",
|
|
157
|
+
},
|
|
158
|
+
appKey: {
|
|
159
|
+
type: "string",
|
|
160
|
+
description: "如流应用 AppKey",
|
|
161
|
+
},
|
|
162
|
+
appSecret: {
|
|
163
|
+
type: "string",
|
|
164
|
+
description: "如流应用 AppSecret",
|
|
165
|
+
},
|
|
166
|
+
robotName: {
|
|
167
|
+
type: "string",
|
|
168
|
+
description: "群聊中用于识别 @机器人的显示名",
|
|
169
|
+
},
|
|
170
|
+
appAgentId: {
|
|
171
|
+
type: "integer",
|
|
172
|
+
minimum: 1,
|
|
173
|
+
description: "如流应用 ID,私聊撤回依赖该字段",
|
|
174
|
+
},
|
|
175
|
+
dmPolicy: {
|
|
176
|
+
type: "string",
|
|
177
|
+
enum: ["open", "pairing", "allowlist"],
|
|
178
|
+
default: "open",
|
|
179
|
+
description: "单聊访问策略",
|
|
180
|
+
},
|
|
181
|
+
allowFrom: {
|
|
182
|
+
type: "array",
|
|
183
|
+
items: { type: "string" },
|
|
184
|
+
description: "单聊白名单,dmPolicy=allowlist 时生效",
|
|
185
|
+
},
|
|
186
|
+
groupPolicy: {
|
|
187
|
+
type: "string",
|
|
188
|
+
enum: ["open", "allowlist", "disabled"],
|
|
189
|
+
default: "open",
|
|
190
|
+
description: "群聊访问策略",
|
|
191
|
+
},
|
|
192
|
+
groupAllowFrom: {
|
|
193
|
+
type: "array",
|
|
194
|
+
items: { type: "string" },
|
|
195
|
+
description: "群聊白名单,groupPolicy=allowlist 时生效",
|
|
196
|
+
},
|
|
197
|
+
requireMention: {
|
|
198
|
+
type: "boolean",
|
|
199
|
+
default: true,
|
|
200
|
+
description: "未显式设置 replyMode 时,是否要求先 @机器人",
|
|
201
|
+
},
|
|
202
|
+
replyMode: {
|
|
203
|
+
type: "string",
|
|
204
|
+
enum: [...INFOFLOW_REPLY_MODE_ENUM],
|
|
205
|
+
description: "群聊回复模式;不填时按 requireMention/watch 配置推导",
|
|
206
|
+
},
|
|
207
|
+
groupSessionMode: {
|
|
208
|
+
type: "string",
|
|
209
|
+
enum: ["group", "user"],
|
|
210
|
+
default: DEFAULT_INFOFLOW_GROUP_SESSION_MODE,
|
|
211
|
+
description: "群聊会话拆分模式:group=按群,user=按群+人",
|
|
212
|
+
},
|
|
213
|
+
watchMentions: {
|
|
214
|
+
type: "array",
|
|
215
|
+
items: { type: "string" },
|
|
216
|
+
description: "当这些人被 @ 时触发观察模式",
|
|
217
|
+
},
|
|
218
|
+
watchRegex: {
|
|
219
|
+
oneOf: [{ type: "string" }, { type: "array", items: { type: "string" } }],
|
|
220
|
+
description: "群消息匹配任一正则时触发观察模式(兼容单个字符串或数组)",
|
|
221
|
+
},
|
|
222
|
+
followUp: {
|
|
223
|
+
type: "boolean",
|
|
224
|
+
default: true,
|
|
225
|
+
description: "机器人回复后是否允许一段时间内连续追问",
|
|
226
|
+
},
|
|
227
|
+
followUpWindow: {
|
|
228
|
+
type: "integer",
|
|
229
|
+
minimum: 0,
|
|
230
|
+
default: DEFAULT_INFOFLOW_FOLLOW_UP_WINDOW,
|
|
231
|
+
description: "追问窗口时长(秒)",
|
|
232
|
+
},
|
|
233
|
+
dmMessageFormat: {
|
|
234
|
+
type: "string",
|
|
235
|
+
enum: ["text", "markdown", "streaming-card"],
|
|
236
|
+
default: DEFAULT_INFOFLOW_DM_MESSAGE_FORMAT,
|
|
237
|
+
description: "单聊回复格式",
|
|
238
|
+
},
|
|
239
|
+
groupMessageFormat: {
|
|
240
|
+
type: "string",
|
|
241
|
+
enum: ["text", "markdown", "streaming-card"],
|
|
242
|
+
default: DEFAULT_INFOFLOW_GROUP_MESSAGE_FORMAT,
|
|
243
|
+
description: "群聊回复格式;markdown 和 streaming-card 不支持引用回复",
|
|
244
|
+
},
|
|
245
|
+
processingHint: {
|
|
246
|
+
type: "boolean",
|
|
247
|
+
default: true,
|
|
248
|
+
description: "处理较慢时先发送一条“收到啦”提示",
|
|
249
|
+
},
|
|
250
|
+
processingHintDelay: {
|
|
251
|
+
type: "integer",
|
|
252
|
+
minimum: 0,
|
|
253
|
+
default: DEFAULT_INFOFLOW_PROCESSING_HINT_DELAY,
|
|
254
|
+
description: "发送处理中提示前的等待秒数",
|
|
255
|
+
},
|
|
256
|
+
textChunkLimit: {
|
|
257
|
+
type: "integer",
|
|
258
|
+
minimum: 1,
|
|
259
|
+
default: DEFAULT_INFOFLOW_TEXT_CHUNK_LIMIT,
|
|
260
|
+
description: "长消息拆分的单段字符上限",
|
|
261
|
+
},
|
|
262
|
+
defaultTo: {
|
|
263
|
+
type: "string",
|
|
264
|
+
description: "主动发送或兼容场景的默认目标,例如 alice 或 group:12345",
|
|
265
|
+
},
|
|
266
|
+
privateDataDir: {
|
|
267
|
+
type: "string",
|
|
268
|
+
default: DEFAULT_INFOFLOW_PRIVATE_DATA_DIR,
|
|
269
|
+
description: "插件私有数据目录,相对 OpenClaw stateDir",
|
|
270
|
+
},
|
|
271
|
+
cronRelay: infoflowCronRelaySchema,
|
|
272
|
+
} as const;
|
|
273
|
+
|
|
274
|
+
const infoflowChannelConfigSchema = {
|
|
275
|
+
schema: {
|
|
276
|
+
type: "object",
|
|
277
|
+
additionalProperties: false,
|
|
278
|
+
properties: {
|
|
279
|
+
...infoflowAccountConfigSchemaProperties,
|
|
280
|
+
defaultAccount: {
|
|
281
|
+
type: "string",
|
|
282
|
+
description: "多账号模式下的默认账号 ID",
|
|
283
|
+
},
|
|
284
|
+
groups: {
|
|
285
|
+
type: "object",
|
|
286
|
+
additionalProperties: {
|
|
287
|
+
type: "object",
|
|
288
|
+
additionalProperties: false,
|
|
289
|
+
properties: infoflowGroupConfigSchemaProperties,
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
accounts: {
|
|
293
|
+
type: "object",
|
|
294
|
+
additionalProperties: {
|
|
295
|
+
type: "object",
|
|
296
|
+
additionalProperties: false,
|
|
297
|
+
properties: infoflowAccountConfigSchemaProperties,
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
uiHints: {
|
|
303
|
+
appKey: {
|
|
304
|
+
label: "AppKey",
|
|
305
|
+
help: "如流应用的 AppKey。",
|
|
306
|
+
},
|
|
307
|
+
appSecret: {
|
|
308
|
+
label: "AppSecret",
|
|
309
|
+
help: "如流应用的 AppSecret。",
|
|
310
|
+
sensitive: true,
|
|
311
|
+
},
|
|
312
|
+
connectionMode: {
|
|
313
|
+
label: "接收模式",
|
|
314
|
+
help: "默认 websocket,本地开发无需公网域名。",
|
|
315
|
+
},
|
|
316
|
+
wsGateway: {
|
|
317
|
+
label: "WS 网关",
|
|
318
|
+
help: "仅 websocket 模式使用。",
|
|
319
|
+
advanced: true,
|
|
320
|
+
},
|
|
321
|
+
apiHost: {
|
|
322
|
+
label: "API 域名",
|
|
323
|
+
help: "发送消息使用的 REST API 域名。",
|
|
324
|
+
advanced: true,
|
|
325
|
+
},
|
|
326
|
+
checkToken: {
|
|
327
|
+
label: "Webhook Token",
|
|
328
|
+
help: "仅 webhook 模式需要。",
|
|
329
|
+
sensitive: true,
|
|
330
|
+
advanced: true,
|
|
331
|
+
},
|
|
332
|
+
encodingAESKey: {
|
|
333
|
+
label: "Webhook AES Key",
|
|
334
|
+
help: "仅 webhook 模式需要。",
|
|
335
|
+
sensitive: true,
|
|
336
|
+
advanced: true,
|
|
337
|
+
},
|
|
338
|
+
enabled: {
|
|
339
|
+
label: "启用通道",
|
|
340
|
+
help: "关闭后停止该账号的收发。",
|
|
341
|
+
},
|
|
342
|
+
name: {
|
|
343
|
+
label: "账号名称",
|
|
344
|
+
help: "仅用于控制台展示。",
|
|
345
|
+
},
|
|
346
|
+
robotName: {
|
|
347
|
+
label: "机器人名称",
|
|
348
|
+
help: "用于识别群里是否 @到了机器人。",
|
|
349
|
+
},
|
|
350
|
+
appAgentId: {
|
|
351
|
+
label: "应用 ID",
|
|
352
|
+
help: "私聊撤回消息需要该值。",
|
|
353
|
+
},
|
|
354
|
+
dmPolicy: {
|
|
355
|
+
label: "单聊策略",
|
|
356
|
+
},
|
|
357
|
+
allowFrom: {
|
|
358
|
+
label: "单聊白名单",
|
|
359
|
+
placeholder: "alice",
|
|
360
|
+
},
|
|
361
|
+
groupPolicy: {
|
|
362
|
+
label: "群聊策略",
|
|
363
|
+
},
|
|
364
|
+
groupAllowFrom: {
|
|
365
|
+
label: "群聊白名单",
|
|
366
|
+
placeholder: "12345678",
|
|
367
|
+
},
|
|
368
|
+
requireMention: {
|
|
369
|
+
label: "要求先 @机器人",
|
|
370
|
+
help: "只在未显式设置 replyMode 时作为推导条件。",
|
|
371
|
+
},
|
|
372
|
+
replyMode: {
|
|
373
|
+
label: "回复模式",
|
|
374
|
+
help: "不填则沿用现有兼容推导逻辑。",
|
|
375
|
+
},
|
|
376
|
+
groupSessionMode: {
|
|
377
|
+
label: "群聊会话模式",
|
|
378
|
+
help: "group=整个群共用 session,user=群内每人独立 session。",
|
|
379
|
+
},
|
|
380
|
+
watchMentions: {
|
|
381
|
+
label: "观察 @名单",
|
|
382
|
+
placeholder: "alice",
|
|
383
|
+
},
|
|
384
|
+
watchRegex: {
|
|
385
|
+
label: "观察正则",
|
|
386
|
+
placeholder: "incident|alert, error.*timeout",
|
|
387
|
+
},
|
|
388
|
+
followUp: {
|
|
389
|
+
label: "允许追问窗口",
|
|
390
|
+
},
|
|
391
|
+
followUpWindow: {
|
|
392
|
+
label: "追问窗口秒数",
|
|
393
|
+
},
|
|
394
|
+
dmMessageFormat: {
|
|
395
|
+
label: "单聊消息格式",
|
|
396
|
+
help: "支持普通文本、Markdown,或如流流式卡片。",
|
|
397
|
+
},
|
|
398
|
+
groupMessageFormat: {
|
|
399
|
+
label: "群聊消息格式",
|
|
400
|
+
help: "支持普通文本、Markdown,或如流流式卡片;markdown 和 streaming-card 不带引用回复。",
|
|
401
|
+
},
|
|
402
|
+
processingHint: {
|
|
403
|
+
label: "处理中提示",
|
|
404
|
+
},
|
|
405
|
+
processingHintDelay: {
|
|
406
|
+
label: "提示延迟秒数",
|
|
407
|
+
},
|
|
408
|
+
textChunkLimit: {
|
|
409
|
+
label: "拆分长度上限",
|
|
410
|
+
advanced: true,
|
|
411
|
+
},
|
|
412
|
+
defaultTo: {
|
|
413
|
+
label: "默认目标",
|
|
414
|
+
help: "仅主动发送和兼容场景使用;infoflow_cron 不依赖该值。",
|
|
415
|
+
advanced: true,
|
|
416
|
+
placeholder: "alice 或 group:12345",
|
|
417
|
+
},
|
|
418
|
+
privateDataDir: {
|
|
419
|
+
label: "私有数据目录",
|
|
420
|
+
help: "cron 可观测文件会写到该目录下,例如 cron-relay-status.md 和 cron-relay-state.json。",
|
|
421
|
+
advanced: true,
|
|
422
|
+
},
|
|
423
|
+
"cronRelay.enabled": {
|
|
424
|
+
label: "启用定时转发",
|
|
425
|
+
help: "开启后会把状态摘要写入私有数据目录,便于直接查看执行结果。",
|
|
426
|
+
advanced: true,
|
|
427
|
+
},
|
|
428
|
+
"cronRelay.includeAlreadyDelivered": {
|
|
429
|
+
label: "包含已投递任务",
|
|
430
|
+
advanced: true,
|
|
431
|
+
},
|
|
432
|
+
"cronRelay.prefix": {
|
|
433
|
+
label: "定时前缀",
|
|
434
|
+
advanced: true,
|
|
435
|
+
},
|
|
436
|
+
"cronRelay.pollIntervalMs": {
|
|
437
|
+
label: "定时轮询间隔",
|
|
438
|
+
help: "影响 relay 扫描 cron runs 和刷新状态摘要文件的频率。",
|
|
439
|
+
advanced: true,
|
|
440
|
+
},
|
|
441
|
+
defaultAccount: {
|
|
442
|
+
label: "默认账号",
|
|
443
|
+
advanced: true,
|
|
444
|
+
},
|
|
445
|
+
groups: {
|
|
446
|
+
label: "群级覆盖",
|
|
447
|
+
help: "按 groupId 配置群级策略覆盖。",
|
|
448
|
+
advanced: true,
|
|
449
|
+
itemTemplate: {
|
|
450
|
+
replyMode: "mention-only",
|
|
451
|
+
groupSessionMode: DEFAULT_INFOFLOW_GROUP_SESSION_MODE,
|
|
452
|
+
followUp: true,
|
|
453
|
+
followUpWindow: DEFAULT_INFOFLOW_FOLLOW_UP_WINDOW,
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
accounts: {
|
|
457
|
+
label: "多账号配置",
|
|
458
|
+
help: "按账号 ID 配置额外机器人。",
|
|
459
|
+
advanced: true,
|
|
460
|
+
itemTemplate: {
|
|
461
|
+
enabled: true,
|
|
462
|
+
connectionMode: DEFAULT_INFOFLOW_CONNECTION_MODE,
|
|
463
|
+
apiHost: DEFAULT_INFOFLOW_API_HOST,
|
|
464
|
+
wsGateway: DEFAULT_INFOFLOW_WS_GATEWAY,
|
|
465
|
+
groupSessionMode: DEFAULT_INFOFLOW_GROUP_SESSION_MODE,
|
|
466
|
+
followUp: true,
|
|
467
|
+
followUpWindow: DEFAULT_INFOFLOW_FOLLOW_UP_WINDOW,
|
|
468
|
+
processingHint: true,
|
|
469
|
+
processingHintDelay: DEFAULT_INFOFLOW_PROCESSING_HINT_DELAY,
|
|
470
|
+
dmMessageFormat: DEFAULT_INFOFLOW_DM_MESSAGE_FORMAT,
|
|
471
|
+
groupMessageFormat: DEFAULT_INFOFLOW_GROUP_MESSAGE_FORMAT,
|
|
472
|
+
textChunkLimit: DEFAULT_INFOFLOW_TEXT_CHUNK_LIMIT,
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
} as const;
|
|
477
|
+
|
|
35
478
|
export const infoflowPlugin: ChannelPlugin<ResolvedInfoflowAccount> = {
|
|
36
479
|
id: "infoflow",
|
|
37
480
|
meta: {
|
|
@@ -48,6 +491,7 @@ export const infoflowPlugin: ChannelPlugin<ResolvedInfoflowAccount> = {
|
|
|
48
491
|
unsend: true,
|
|
49
492
|
},
|
|
50
493
|
reload: { configPrefixes: ["channels.infoflow"] },
|
|
494
|
+
configSchema: infoflowChannelConfigSchema,
|
|
51
495
|
actions: infoflowMessageActions,
|
|
52
496
|
agentPrompt: {
|
|
53
497
|
messageToolHints: () => [
|
|
@@ -205,10 +649,10 @@ export const infoflowPlugin: ChannelPlugin<ResolvedInfoflowAccount> = {
|
|
|
205
649
|
},
|
|
206
650
|
},
|
|
207
651
|
outbound: {
|
|
208
|
-
deliveryMode: "
|
|
652
|
+
deliveryMode: "gateway",
|
|
209
653
|
chunkerMode: "markdown",
|
|
210
|
-
textChunkLimit:
|
|
211
|
-
chunker: (text, limit) => getInfoflowRuntime().channel.text.
|
|
654
|
+
textChunkLimit: DEFAULT_INFOFLOW_TEXT_CHUNK_LIMIT,
|
|
655
|
+
chunker: (text, limit) => getInfoflowRuntime().channel.text.chunkMarkdownText(text, limit),
|
|
212
656
|
sendText: async ({ cfg, to, text, accountId }) => {
|
|
213
657
|
logVerbose(`[infoflow:sendText] to=${to}, accountId=${accountId}`);
|
|
214
658
|
// Use "markdown" type even though param is named `text`: LLM outputs are often markdown,
|
|
@@ -322,7 +766,7 @@ export const infoflowPlugin: ChannelPlugin<ResolvedInfoflowAccount> = {
|
|
|
322
766
|
gateway: {
|
|
323
767
|
startAccount: async (ctx) => {
|
|
324
768
|
const account = ctx.account;
|
|
325
|
-
const connectionMode = account.config.connectionMode ?? "
|
|
769
|
+
const connectionMode = account.config.connectionMode ?? "websocket";
|
|
326
770
|
ctx.log?.info(`[${account.accountId}] starting Infoflow ${connectionMode}`);
|
|
327
771
|
ctx.setStatus({
|
|
328
772
|
accountId: account.accountId,
|
package/src/channel/media.ts
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
* Infoflow native image sending: compress, base64-encode, and POST via Infoflow API.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
6
|
-
import { resolveInfoflowAccount } from "./accounts.js";
|
|
5
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/plugin-entry";
|
|
7
6
|
import { recordSentMessageId } from "../adapter/inbound/webhook-parser.js";
|
|
7
|
+
import { coreEvents } from "../events.js";
|
|
8
8
|
import { getInfoflowSendLog, formatInfoflowError, logVerbose } from "../logging.js";
|
|
9
9
|
import { getInfoflowRuntime } from "../runtime.js";
|
|
10
|
+
import type { ResolvedInfoflowAccount, InfoflowOutboundReply } from "../types.js";
|
|
11
|
+
import { resolveInfoflowAccount } from "./accounts.js";
|
|
10
12
|
import {
|
|
11
13
|
getAppAccessToken,
|
|
12
14
|
ensureHttps,
|
|
@@ -15,8 +17,6 @@ import {
|
|
|
15
17
|
INFOFLOW_PRIVATE_SEND_PATH,
|
|
16
18
|
INFOFLOW_GROUP_SEND_PATH,
|
|
17
19
|
} from "./outbound.js";
|
|
18
|
-
import { coreEvents } from "../events.js";
|
|
19
|
-
import type { ResolvedInfoflowAccount, InfoflowOutboundReply } from "../types.js";
|
|
20
20
|
|
|
21
21
|
/** Infoflow API image size limit: 1MB raw bytes */
|
|
22
22
|
const INFOFLOW_IMAGE_MAX_BYTES = 1 * 1024 * 1024;
|
|
@@ -227,7 +227,10 @@ export async function sendInfoflowGroupImage(params: {
|
|
|
227
227
|
coreEvents.emit("message:sent", {
|
|
228
228
|
accountId: account.accountId,
|
|
229
229
|
target: `group:${groupId}`,
|
|
230
|
-
from:
|
|
230
|
+
from:
|
|
231
|
+
account.config.appAgentId != null
|
|
232
|
+
? `agent:${account.config.appAgentId}`
|
|
233
|
+
: "agent:unknown",
|
|
231
234
|
messageid,
|
|
232
235
|
msgseqid: msgseqid ?? "",
|
|
233
236
|
contents: [{ type: "image", content: "image" }],
|
|
@@ -298,6 +301,14 @@ export async function sendInfoflowPrivateImage(params: {
|
|
|
298
301
|
const data = JSON.parse(responseText) as Record<string, unknown>;
|
|
299
302
|
logVerbose(`[infoflow:sendPrivateImage] response: status=${res.status}, data=${responseText}`);
|
|
300
303
|
|
|
304
|
+
// Check outer code first (same format as text message API)
|
|
305
|
+
const code = typeof data.code === "string" ? data.code : "";
|
|
306
|
+
if (code && code !== "ok") {
|
|
307
|
+
const errMsg = String(data.message ?? data.errmsg ?? `code=${code}`);
|
|
308
|
+
getInfoflowSendLog().error(`[infoflow:sendPrivateImage] failed: ${errMsg}`);
|
|
309
|
+
return { ok: false, error: errMsg };
|
|
310
|
+
}
|
|
311
|
+
// Also check inner errcode
|
|
301
312
|
if (data.errcode && data.errcode !== 0) {
|
|
302
313
|
const errMsg = String(data.errmsg ?? `errcode ${data.errcode}`);
|
|
303
314
|
getInfoflowSendLog().error(`[infoflow:sendPrivateImage] failed: ${errMsg}`);
|
|
@@ -313,7 +324,10 @@ export async function sendInfoflowPrivateImage(params: {
|
|
|
313
324
|
coreEvents.emit("message:sent", {
|
|
314
325
|
accountId: account.accountId,
|
|
315
326
|
target: toUser,
|
|
316
|
-
from:
|
|
327
|
+
from:
|
|
328
|
+
account.config.appAgentId != null
|
|
329
|
+
? `agent:${account.config.appAgentId}`
|
|
330
|
+
: "agent:unknown",
|
|
317
331
|
messageid: msgkey,
|
|
318
332
|
msgseqid: "",
|
|
319
333
|
contents: [{ type: "image", content: "image" }],
|
package/src/channel/monitor.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
-
import type { OpenClawConfig } from "openclaw/plugin-sdk";
|
|
3
|
-
import type { ResolvedInfoflowAccount } from "../types.js";
|
|
2
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/plugin-entry";
|
|
4
3
|
import {
|
|
5
4
|
parseAndDispatchInfoflowRequest,
|
|
6
5
|
loadRawBody,
|
|
7
6
|
type WebhookTarget,
|
|
8
7
|
} from "../adapter/inbound/webhook-parser.js";
|
|
8
|
+
import { InfoflowWSReceiver } from "../adapter/inbound/ws-receiver.js";
|
|
9
9
|
import { getInfoflowWebhookLog, formatInfoflowError, logVerbose } from "../logging.js";
|
|
10
10
|
import { getInfoflowRuntime } from "../runtime.js";
|
|
11
|
-
import {
|
|
11
|
+
import type { ResolvedInfoflowAccount } from "../types.js";
|
|
12
12
|
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
// Types
|
|
@@ -97,16 +97,21 @@ export async function handleInfoflowWebhookRequest(
|
|
|
97
97
|
|
|
98
98
|
// Check if path matches Infoflow webhook pattern
|
|
99
99
|
if (!isInfoflowPath(requestPath)) {
|
|
100
|
+
logVerbose(
|
|
101
|
+
`[infoflow] skipping: path=${requestPath} does not match any registered webhook path`,
|
|
102
|
+
);
|
|
100
103
|
return false;
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
// Get registered targets for the actual request path
|
|
104
107
|
const targets = webhookTargets.get(requestPath);
|
|
105
108
|
if (!targets || targets.length === 0) {
|
|
109
|
+
logVerbose(`[infoflow] skipping: no registered targets for path=${requestPath}`);
|
|
106
110
|
return false;
|
|
107
111
|
}
|
|
108
112
|
|
|
109
113
|
if (req.method !== "POST") {
|
|
114
|
+
logVerbose(`[infoflow] rejecting: method=${req.method} is not POST for path=${requestPath}`);
|
|
110
115
|
res.statusCode = 405;
|
|
111
116
|
res.setHeader("Allow", "POST");
|
|
112
117
|
res.end("Method Not Allowed");
|