@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.
- 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 +16 -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
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
type ReplyPayload,
|
|
5
|
-
} from "openclaw/plugin-sdk";
|
|
6
|
-
import { getInfoflowSendLog, formatInfoflowError, logVerbose } from "../../logging.js";
|
|
1
|
+
import { createReplyPrefixOptions } from "../../compat/openclaw-sdk.js";
|
|
2
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/plugin-entry";
|
|
3
|
+
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
|
|
7
4
|
import { prepareInfoflowImageBase64, sendInfoflowImageMessage } from "../../channel/media.js";
|
|
8
|
-
import { getInfoflowRuntime } from "../../runtime.js";
|
|
9
5
|
import { isLikelyLocalPath, sendInfoflowMessage } from "../../channel/outbound.js";
|
|
6
|
+
import type { InfoflowStreamingCardSession } from "../../channel/streaming.js";
|
|
7
|
+
import { getInfoflowSendLog, formatInfoflowError, logVerbose } from "../../logging.js";
|
|
8
|
+
import { getInfoflowRuntime } from "../../runtime.js";
|
|
10
9
|
import type {
|
|
11
10
|
InfoflowAtOptions,
|
|
12
11
|
InfoflowMentionIds,
|
|
13
12
|
InfoflowMessageContentItem,
|
|
13
|
+
InfoflowMessageFormat,
|
|
14
14
|
InfoflowOutboundReply,
|
|
15
15
|
} from "../../types.js";
|
|
16
|
+
import { parseMarkdownForLocalImages } from "./markdown-local-images.js";
|
|
16
17
|
|
|
17
18
|
const PREVIEW_MAX_LENGTH = 100;
|
|
18
19
|
|
|
@@ -33,18 +34,25 @@ export type CreateInfoflowReplyDispatcherParams = {
|
|
|
33
34
|
atOptions?: InfoflowAtOptions;
|
|
34
35
|
/** Mention IDs from inbound message for resolving @id in LLM output */
|
|
35
36
|
mentionIds?: InfoflowMentionIds;
|
|
36
|
-
/** Inbound message ID for outbound reply-to
|
|
37
|
+
/** Inbound message ID for outbound reply-to */
|
|
37
38
|
replyToMessageId?: string;
|
|
38
39
|
/** Preview text of the inbound message for reply context */
|
|
39
40
|
replyToPreview?: string;
|
|
40
41
|
/** IMID of the user who sent the message being replied to */
|
|
41
42
|
replyToImid?: string;
|
|
43
|
+
/** Secondary message ID for private reply (maps to API field "msgid2") */
|
|
44
|
+
replyToMsgid2?: string;
|
|
42
45
|
/**
|
|
43
|
-
* Message format for outbound text
|
|
46
|
+
* Message format for outbound text.
|
|
44
47
|
* Note: "markdown" does not support reply-to (quote) — replyTo will be ignored when using markdown.
|
|
45
|
-
* Default: "text"
|
|
46
48
|
*/
|
|
47
|
-
messageFormat?:
|
|
49
|
+
messageFormat?: InfoflowMessageFormat;
|
|
50
|
+
streamingCard?: InfoflowStreamingCardSession;
|
|
51
|
+
/**
|
|
52
|
+
* Maximum character limit per outbound message chunk.
|
|
53
|
+
* Default: 1800
|
|
54
|
+
*/
|
|
55
|
+
textChunkLimit?: number;
|
|
48
56
|
};
|
|
49
57
|
|
|
50
58
|
/**
|
|
@@ -63,7 +71,10 @@ export function createInfoflowReplyDispatcher(params: CreateInfoflowReplyDispatc
|
|
|
63
71
|
replyToMessageId,
|
|
64
72
|
replyToPreview,
|
|
65
73
|
replyToImid,
|
|
74
|
+
replyToMsgid2,
|
|
66
75
|
messageFormat = "text",
|
|
76
|
+
textChunkLimit = 1800,
|
|
77
|
+
streamingCard,
|
|
67
78
|
} = params;
|
|
68
79
|
const core = getInfoflowRuntime();
|
|
69
80
|
|
|
@@ -78,7 +89,10 @@ export function createInfoflowReplyDispatcher(params: CreateInfoflowReplyDispatc
|
|
|
78
89
|
const isGroup = /^group:\d+$/i.test(to);
|
|
79
90
|
|
|
80
91
|
// markdown format does not support reply-to; fall back to no replyTo when using markdown
|
|
81
|
-
const effectiveReplyToMessageId =
|
|
92
|
+
const effectiveReplyToMessageId =
|
|
93
|
+
messageFormat === "markdown" || messageFormat === "streaming-card"
|
|
94
|
+
? undefined
|
|
95
|
+
: replyToMessageId;
|
|
82
96
|
|
|
83
97
|
// Build id→type map for resolving @id in LLM output (distinguishes user vs agent)
|
|
84
98
|
const mentionIdMap = new Map<string, "user" | "agent">();
|
|
@@ -92,31 +106,21 @@ export function createInfoflowReplyDispatcher(params: CreateInfoflowReplyDispatc
|
|
|
92
106
|
}
|
|
93
107
|
|
|
94
108
|
// Build replyTo context (only used for the first outbound message)
|
|
95
|
-
// Note: replyTo is suppressed when messageFormat is "markdown" (not supported by API)
|
|
96
|
-
logVerbose(`[DEBUG reply-dispatcher] isGroup=${isGroup}, replyToMessageId=${replyToMessageId}, replyToImid=${replyToImid}, replyToPreview=${replyToPreview?.slice(0, 50)}`);
|
|
97
|
-
|
|
109
|
+
// Note: replyTo is suppressed when messageFormat is "markdown" or "streaming-card" (not supported by API)
|
|
98
110
|
const replyTo: InfoflowOutboundReply | undefined =
|
|
99
|
-
|
|
111
|
+
effectiveReplyToMessageId
|
|
100
112
|
? {
|
|
101
113
|
messageid: effectiveReplyToMessageId,
|
|
102
114
|
preview: truncatePreview(replyToPreview),
|
|
103
115
|
...(replyToImid ? { imid: replyToImid } : {}),
|
|
104
|
-
replytype: "2"
|
|
116
|
+
replytype: "2", // "2" = 引用模式
|
|
117
|
+
...(replyToMsgid2 ? { msgid2: replyToMsgid2 } : {}),
|
|
105
118
|
}
|
|
106
119
|
: undefined;
|
|
107
120
|
let replyApplied = false;
|
|
108
121
|
|
|
109
|
-
|
|
110
|
-
logVerbose(`[DEBUG reply-dispatcher] replyTo constructed: ${JSON.stringify(replyTo)}`);
|
|
111
|
-
if (replyTo) {
|
|
112
|
-
logVerbose(`[DEBUG reply-dispatcher] Creating reply context with messageid=${replyTo.messageid}`);
|
|
113
|
-
} else {
|
|
114
|
-
logVerbose(`[DEBUG reply-dispatcher] replyTo is undefined - not creating reply context`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const deliver = async (payload: ReplyPayload) => {
|
|
122
|
+
const deliverStandard = async (payload: ReplyPayload) => {
|
|
118
123
|
const text = payload.text ?? "";
|
|
119
|
-
logVerbose(`[infoflow] deliver called: to=${to}, text=${text}`);
|
|
120
124
|
|
|
121
125
|
// Normalize media URL list (same pattern as Feishu reply-dispatcher)
|
|
122
126
|
const mediaList =
|
|
@@ -126,7 +130,6 @@ export function createInfoflowReplyDispatcher(params: CreateInfoflowReplyDispatc
|
|
|
126
130
|
? [payload.mediaUrl]
|
|
127
131
|
: [];
|
|
128
132
|
|
|
129
|
-
logVerbose(`[infoflow] deliver called: to=${to}, text=${text}, mediaList=${JSON.stringify(mediaList)}`);
|
|
130
133
|
if (!text.trim() && mediaList.length === 0) {
|
|
131
134
|
return;
|
|
132
135
|
}
|
|
@@ -169,52 +172,113 @@ export function createInfoflowReplyDispatcher(params: CreateInfoflowReplyDispatc
|
|
|
169
172
|
// AT nodes will be added to contents for notification, but text stays clean
|
|
170
173
|
let messageText = text;
|
|
171
174
|
|
|
172
|
-
|
|
173
|
-
|
|
175
|
+
if (isGroup && atOptions && messageFormat == "markdown") {
|
|
176
|
+
let atPrefix = "";
|
|
177
|
+
if (hasAtAll) {
|
|
178
|
+
atPrefix = "@all ";
|
|
179
|
+
} else if (atOptions.atUserIds?.length) {
|
|
180
|
+
atPrefix = atOptions.atUserIds.map((id) => `@${id}`).join(" ") + " ";
|
|
181
|
+
}
|
|
182
|
+
if (hasAtAgents) {
|
|
183
|
+
atPrefix += resolvedAgentIds.map((id) => `@${id}`).join(" ") + " ";
|
|
184
|
+
}
|
|
185
|
+
messageText = atPrefix + text;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Chunk text using markdown-aware chunker with configured limit
|
|
189
|
+
const chunks = core.channel.text.chunkMarkdownText(messageText, textChunkLimit);
|
|
174
190
|
// Only include @mentions in the first chunk (avoid duplicate @s)
|
|
175
191
|
let isFirstChunk = true;
|
|
176
192
|
|
|
177
193
|
for (const chunk of chunks) {
|
|
178
|
-
|
|
194
|
+
// Parse chunk for embedded local images (e.g. )
|
|
195
|
+
const segments = parseMarkdownForLocalImages(chunk);
|
|
179
196
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
197
|
+
for (const segment of segments) {
|
|
198
|
+
if (segment.kind === "image") {
|
|
199
|
+
// Send local image as native image message
|
|
200
|
+
const imgReplyTo = !replyApplied ? replyTo : undefined;
|
|
201
|
+
try {
|
|
202
|
+
const prepared = await prepareInfoflowImageBase64({
|
|
203
|
+
mediaUrl: segment.path,
|
|
204
|
+
mediaLocalRoots: [segment.path],
|
|
205
|
+
});
|
|
206
|
+
if (prepared.isImage) {
|
|
207
|
+
const result = await sendInfoflowImageMessage({
|
|
208
|
+
cfg,
|
|
209
|
+
to,
|
|
210
|
+
base64Image: prepared.base64,
|
|
211
|
+
accountId,
|
|
212
|
+
replyTo: imgReplyTo,
|
|
213
|
+
});
|
|
214
|
+
if (result.ok) {
|
|
215
|
+
if (imgReplyTo) replyApplied = true;
|
|
216
|
+
statusSink?.({ lastOutboundAt: Date.now() });
|
|
217
|
+
// AT already consumed on first text segment; skip AT for images
|
|
218
|
+
isFirstChunk = false;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
logVerbose(
|
|
222
|
+
`[infoflow] inline image send failed: ${result.error}, falling back to link`,
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
} catch (err) {
|
|
226
|
+
logVerbose(`[infoflow] inline image prep failed: ${err}`);
|
|
227
|
+
}
|
|
228
|
+
// Fallback: send image path as link
|
|
229
|
+
await sendInfoflowMessage({
|
|
230
|
+
cfg,
|
|
231
|
+
to,
|
|
232
|
+
contents: [{ type: "link", content: segment.path }],
|
|
233
|
+
accountId,
|
|
234
|
+
replyTo: imgReplyTo,
|
|
235
|
+
});
|
|
236
|
+
if (imgReplyTo) replyApplied = true;
|
|
237
|
+
isFirstChunk = false;
|
|
238
|
+
continue;
|
|
189
239
|
}
|
|
190
|
-
}
|
|
191
|
-
isFirstChunk = false;
|
|
192
240
|
|
|
193
|
-
|
|
194
|
-
|
|
241
|
+
// Text segment — send as before
|
|
242
|
+
const contents: InfoflowMessageContentItem[] = [];
|
|
195
243
|
|
|
196
|
-
|
|
197
|
-
|
|
244
|
+
// Add AT content nodes for group messages (first chunk only)
|
|
245
|
+
if (isFirstChunk && isGroup) {
|
|
246
|
+
if (hasAtAll) {
|
|
247
|
+
contents.push({ type: "at", content: "all" });
|
|
248
|
+
} else if (hasAtUsers) {
|
|
249
|
+
contents.push({ type: "at", content: allAtUserIds.join(",") });
|
|
250
|
+
}
|
|
251
|
+
if (hasAtAgents) {
|
|
252
|
+
contents.push({ type: "at-agent", content: resolvedAgentIds.join(",") });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
isFirstChunk = false;
|
|
198
256
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
logVerbose(`[DEBUG deliver] replyApplied=${replyApplied}, replyTo exists=${!!replyTo}`);
|
|
257
|
+
// Add text content using configured format
|
|
258
|
+
contents.push({ type: messageFormat, content: segment.content });
|
|
202
259
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
to,
|
|
206
|
-
contents,
|
|
207
|
-
accountId,
|
|
208
|
-
replyTo: chunkReplyTo,
|
|
209
|
-
});
|
|
210
|
-
if (chunkReplyTo) replyApplied = true;
|
|
260
|
+
// Only include replyTo on the first outbound message
|
|
261
|
+
const chunkReplyTo = !replyApplied ? replyTo : undefined;
|
|
211
262
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
263
|
+
const result = await sendInfoflowMessage({
|
|
264
|
+
cfg,
|
|
265
|
+
to,
|
|
266
|
+
contents,
|
|
267
|
+
accountId,
|
|
268
|
+
replyTo: chunkReplyTo,
|
|
269
|
+
});
|
|
270
|
+
if (chunkReplyTo) replyApplied = true;
|
|
271
|
+
|
|
272
|
+
if (result.ok) {
|
|
273
|
+
getInfoflowSendLog().info(
|
|
274
|
+
`[outbound] to=${to}, len=${segment.content.length}, msgId=${result.messageId ?? "N/A"}, replyTo=${chunkReplyTo?.messageid ?? "none"}`,
|
|
275
|
+
);
|
|
276
|
+
statusSink?.({ lastOutboundAt: Date.now() });
|
|
277
|
+
} else if (result.error) {
|
|
278
|
+
getInfoflowSendLog().error(
|
|
279
|
+
`[outbound:error] to=${to}, accountId=${accountId}: ${result.error}`,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
218
282
|
}
|
|
219
283
|
}
|
|
220
284
|
}
|
|
@@ -223,8 +287,8 @@ export function createInfoflowReplyDispatcher(params: CreateInfoflowReplyDispatc
|
|
|
223
287
|
for (const mediaUrl of mediaList) {
|
|
224
288
|
const mediaReplyTo = !replyApplied ? replyTo : undefined;
|
|
225
289
|
try {
|
|
226
|
-
const paths = isLikelyLocalPath(mediaUrl)?[mediaUrl]:undefined;
|
|
227
|
-
const prepared = await prepareInfoflowImageBase64({ mediaUrl
|
|
290
|
+
const paths = isLikelyLocalPath(mediaUrl) ? [mediaUrl] : undefined;
|
|
291
|
+
const prepared = await prepareInfoflowImageBase64({ mediaUrl, mediaLocalRoots: paths });
|
|
228
292
|
if (prepared.isImage) {
|
|
229
293
|
const result = await sendInfoflowImageMessage({
|
|
230
294
|
cfg,
|
|
@@ -255,10 +319,27 @@ export function createInfoflowReplyDispatcher(params: CreateInfoflowReplyDispatc
|
|
|
255
319
|
}
|
|
256
320
|
};
|
|
257
321
|
|
|
322
|
+
const deliver = async (payload: ReplyPayload, info?: { kind: "tool" | "block" | "final" }) => {
|
|
323
|
+
if (messageFormat === "streaming-card" && streamingCard?.isUsable()) {
|
|
324
|
+
const handled = await streamingCard.handleDeliveredPayload({
|
|
325
|
+
kind: info?.kind ?? "final",
|
|
326
|
+
payload,
|
|
327
|
+
});
|
|
328
|
+
if (handled) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return deliverStandard(payload);
|
|
333
|
+
};
|
|
334
|
+
|
|
258
335
|
const onError = (err: unknown) => {
|
|
259
336
|
getInfoflowSendLog().error(
|
|
260
337
|
`[infoflow] reply error to=${to}, accountId=${accountId}: ${formatInfoflowError(err)}`,
|
|
261
338
|
);
|
|
339
|
+
if (messageFormat === "streaming-card" && streamingCard?.isUsable()) {
|
|
340
|
+
void streamingCard.failWithMessage("处理出错,请稍后重试");
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
262
343
|
// Send error hint so users know the bot didn't silently fail
|
|
263
344
|
sendInfoflowMessage({
|
|
264
345
|
cfg,
|
|
@@ -32,14 +32,15 @@ const USER_PREFIX = "user:";
|
|
|
32
32
|
* "123456" -> "group:123456" (pure digits treated as group)
|
|
33
33
|
*/
|
|
34
34
|
export function normalizeInfoflowTarget(raw: string): string | undefined {
|
|
35
|
-
logVerbose(`[infoflow:normalizeTarget] input: "${raw}"`);
|
|
36
|
-
|
|
37
35
|
const trimmed = raw.trim();
|
|
38
36
|
if (!trimmed) {
|
|
39
|
-
|
|
37
|
+
// Empty targets are common on cron-relay ticks when metadata/payload/session
|
|
38
|
+
// have not yet supplied a `to`; avoid verbose spam every poll interval.
|
|
40
39
|
return undefined;
|
|
41
40
|
}
|
|
42
41
|
|
|
42
|
+
logVerbose(`[infoflow:normalizeTarget] input: "${raw}"`);
|
|
43
|
+
|
|
43
44
|
// Strip infoflow: prefix
|
|
44
45
|
let target = trimmed.replace(/^infoflow:/i, "");
|
|
45
46
|
|
package/src/channel/accounts.ts
CHANGED
|
@@ -3,8 +3,28 @@
|
|
|
3
3
|
* Handles multi-account support with config merging.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { DEFAULT_ACCOUNT_ID, normalizeAccountId
|
|
7
|
-
import type {
|
|
6
|
+
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../compat/openclaw-sdk.js";
|
|
7
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/plugin-entry";
|
|
8
|
+
import { normalizeInfoflowTarget } from "../adapter/outbound/target-resolver.js";
|
|
9
|
+
import type {
|
|
10
|
+
InfoflowAccountConfig,
|
|
11
|
+
InfoflowConnectionMode,
|
|
12
|
+
InfoflowMessageFormat,
|
|
13
|
+
ResolvedInfoflowAccount,
|
|
14
|
+
} from "../types.js";
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_INFOFLOW_API_HOST = "https://api.im.baidu.com";
|
|
17
|
+
export const DEFAULT_INFOFLOW_CONNECTION_MODE: InfoflowConnectionMode = "websocket";
|
|
18
|
+
export const DEFAULT_INFOFLOW_WS_GATEWAY = "infoflow-open-gateway.baidu.com";
|
|
19
|
+
export const DEFAULT_INFOFLOW_GROUP_SESSION_MODE = "group";
|
|
20
|
+
export const DEFAULT_INFOFLOW_FOLLOW_UP_WINDOW = 300;
|
|
21
|
+
export const DEFAULT_INFOFLOW_PROCESSING_HINT_DELAY = 5;
|
|
22
|
+
export const DEFAULT_INFOFLOW_TEXT_CHUNK_LIMIT = 1800;
|
|
23
|
+
export const DEFAULT_INFOFLOW_DM_MESSAGE_FORMAT = "markdown";
|
|
24
|
+
export const DEFAULT_INFOFLOW_GROUP_MESSAGE_FORMAT = "markdown";
|
|
25
|
+
export const DEFAULT_INFOFLOW_PRIVATE_DATA_DIR = "plugins/infoflow-private";
|
|
26
|
+
export const DEFAULT_INFOFLOW_CRON_RELAY_PREFIX = "【定时任务】";
|
|
27
|
+
export const DEFAULT_INFOFLOW_CRON_RELAY_POLL_INTERVAL_MS = 2000;
|
|
8
28
|
|
|
9
29
|
// ---------------------------------------------------------------------------
|
|
10
30
|
// Config Access Helpers
|
|
@@ -70,22 +90,42 @@ function mergeInfoflowAccountConfig(
|
|
|
70
90
|
name?: string;
|
|
71
91
|
connectionMode?: InfoflowConnectionMode;
|
|
72
92
|
wsGateway?: string;
|
|
93
|
+
wsConnectDomain?: string;
|
|
73
94
|
robotName?: string;
|
|
95
|
+
robotId?: string;
|
|
74
96
|
requireMention?: boolean;
|
|
75
97
|
watchMentions?: string[];
|
|
76
|
-
watchRegex?: string;
|
|
98
|
+
watchRegex?: string | string[];
|
|
77
99
|
appAgentId?: number;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
100
|
+
defaultTo?: string;
|
|
101
|
+
privateDataDir?: string;
|
|
102
|
+
cronRelay?: import("../types.js").InfoflowCronRelayConfig;
|
|
103
|
+
processingHint?: boolean;
|
|
104
|
+
processingHintDelay?: number;
|
|
105
|
+
dmMessageFormat?: InfoflowMessageFormat;
|
|
106
|
+
groupMessageFormat?: InfoflowMessageFormat;
|
|
107
|
+
replyMode?: import("../types.js").InfoflowReplyMode;
|
|
108
|
+
groupSessionMode?: import("../types.js").InfoflowGroupSessionMode;
|
|
109
|
+
followUp?: boolean;
|
|
110
|
+
followUpWindow?: number;
|
|
111
|
+
groups?: Record<string, import("../types.js").InfoflowGroupConfig>;
|
|
112
|
+
dmPolicy?: import("../types.js").InfoflowDmPolicy;
|
|
81
113
|
allowFrom?: string[];
|
|
82
|
-
groupPolicy?:
|
|
114
|
+
groupPolicy?: import("../types.js").InfoflowGroupPolicy;
|
|
83
115
|
groupAllowFrom?: string[];
|
|
116
|
+
textChunkLimit?: number;
|
|
84
117
|
} {
|
|
85
118
|
const raw = getChannelSection(cfg) ?? {};
|
|
86
119
|
const { accounts: _ignored, defaultAccount: _ignored2, ...base } = raw;
|
|
87
120
|
const account = raw.accounts?.[accountId] ?? {};
|
|
88
|
-
return {
|
|
121
|
+
return {
|
|
122
|
+
...base,
|
|
123
|
+
...account,
|
|
124
|
+
cronRelay: {
|
|
125
|
+
...(base.cronRelay ?? {}),
|
|
126
|
+
...(account.cronRelay ?? {}),
|
|
127
|
+
},
|
|
128
|
+
} as {
|
|
89
129
|
apiHost: string;
|
|
90
130
|
checkToken: string;
|
|
91
131
|
encodingAESKey: string;
|
|
@@ -95,17 +135,30 @@ function mergeInfoflowAccountConfig(
|
|
|
95
135
|
name?: string;
|
|
96
136
|
connectionMode?: InfoflowConnectionMode;
|
|
97
137
|
wsGateway?: string;
|
|
138
|
+
wsConnectDomain?: string;
|
|
98
139
|
robotName?: string;
|
|
140
|
+
robotId?: string;
|
|
99
141
|
requireMention?: boolean;
|
|
100
142
|
watchMentions?: string[];
|
|
101
|
-
watchRegex?: string;
|
|
143
|
+
watchRegex?: string | string[];
|
|
102
144
|
appAgentId?: number;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
145
|
+
defaultTo?: string;
|
|
146
|
+
privateDataDir?: string;
|
|
147
|
+
cronRelay?: import("../types.js").InfoflowCronRelayConfig;
|
|
148
|
+
processingHint?: boolean;
|
|
149
|
+
processingHintDelay?: number;
|
|
150
|
+
dmMessageFormat?: InfoflowMessageFormat;
|
|
151
|
+
groupMessageFormat?: InfoflowMessageFormat;
|
|
152
|
+
replyMode?: import("../types.js").InfoflowReplyMode;
|
|
153
|
+
groupSessionMode?: import("../types.js").InfoflowGroupSessionMode;
|
|
154
|
+
followUp?: boolean;
|
|
155
|
+
followUpWindow?: number;
|
|
156
|
+
groups?: Record<string, import("../types.js").InfoflowGroupConfig>;
|
|
157
|
+
dmPolicy?: import("../types.js").InfoflowDmPolicy;
|
|
106
158
|
allowFrom?: string[];
|
|
107
|
-
groupPolicy?:
|
|
159
|
+
groupPolicy?: import("../types.js").InfoflowGroupPolicy;
|
|
108
160
|
groupAllowFrom?: string[];
|
|
161
|
+
textChunkLimit?: number;
|
|
109
162
|
};
|
|
110
163
|
}
|
|
111
164
|
|
|
@@ -125,13 +178,16 @@ export function resolveInfoflowAccount(params: {
|
|
|
125
178
|
const merged = mergeInfoflowAccountConfig(params.cfg, accountId);
|
|
126
179
|
const accountEnabled = merged.enabled !== false;
|
|
127
180
|
const enabled = baseEnabled && accountEnabled;
|
|
128
|
-
const apiHost = merged.apiHost
|
|
181
|
+
const apiHost = merged.apiHost?.trim() || DEFAULT_INFOFLOW_API_HOST;
|
|
129
182
|
const checkToken = merged.checkToken ?? "";
|
|
130
183
|
const encodingAESKey = merged.encodingAESKey ?? "";
|
|
131
184
|
const appKey = merged.appKey ?? "";
|
|
132
185
|
const appSecret = merged.appSecret ?? "";
|
|
186
|
+
const effectiveConnectionMode = merged.connectionMode ?? DEFAULT_INFOFLOW_CONNECTION_MODE;
|
|
133
187
|
const configured =
|
|
134
|
-
|
|
188
|
+
effectiveConnectionMode === "websocket"
|
|
189
|
+
? Boolean(appKey) && Boolean(appSecret)
|
|
190
|
+
: Boolean(checkToken) && Boolean(encodingAESKey) && Boolean(appKey) && Boolean(appSecret);
|
|
135
191
|
|
|
136
192
|
return {
|
|
137
193
|
accountId,
|
|
@@ -146,19 +202,38 @@ export function resolveInfoflowAccount(params: {
|
|
|
146
202
|
encodingAESKey,
|
|
147
203
|
appKey,
|
|
148
204
|
appSecret,
|
|
149
|
-
connectionMode:
|
|
150
|
-
wsGateway: merged.wsGateway,
|
|
205
|
+
connectionMode: effectiveConnectionMode,
|
|
206
|
+
wsGateway: merged.wsGateway?.trim() || DEFAULT_INFOFLOW_WS_GATEWAY,
|
|
207
|
+
wsConnectDomain: merged.wsConnectDomain?.trim() || undefined,
|
|
151
208
|
robotName: merged.robotName?.trim() || undefined,
|
|
152
|
-
|
|
209
|
+
robotId: merged.robotId?.trim() || undefined,
|
|
210
|
+
requireMention: merged.requireMention ?? true,
|
|
153
211
|
watchMentions: merged.watchMentions,
|
|
154
212
|
watchRegex: merged.watchRegex,
|
|
155
213
|
appAgentId: merged.appAgentId,
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
214
|
+
defaultTo: normalizeInfoflowTarget(merged.defaultTo ?? "") ?? undefined,
|
|
215
|
+
privateDataDir: merged.privateDataDir?.trim() || DEFAULT_INFOFLOW_PRIVATE_DATA_DIR,
|
|
216
|
+
cronRelay: {
|
|
217
|
+
enabled: merged.cronRelay?.enabled ?? true,
|
|
218
|
+
includeAlreadyDelivered: merged.cronRelay?.includeAlreadyDelivered ?? false,
|
|
219
|
+
prefix: merged.cronRelay?.prefix?.trim() || DEFAULT_INFOFLOW_CRON_RELAY_PREFIX,
|
|
220
|
+
pollIntervalMs:
|
|
221
|
+
merged.cronRelay?.pollIntervalMs ?? DEFAULT_INFOFLOW_CRON_RELAY_POLL_INTERVAL_MS,
|
|
222
|
+
},
|
|
223
|
+
processingHint: merged.processingHint ?? true,
|
|
224
|
+
processingHintDelay: merged.processingHintDelay ?? DEFAULT_INFOFLOW_PROCESSING_HINT_DELAY,
|
|
225
|
+
dmMessageFormat: merged.dmMessageFormat ?? DEFAULT_INFOFLOW_DM_MESSAGE_FORMAT,
|
|
226
|
+
groupMessageFormat: merged.groupMessageFormat ?? DEFAULT_INFOFLOW_GROUP_MESSAGE_FORMAT,
|
|
227
|
+
replyMode: merged.replyMode,
|
|
228
|
+
groupSessionMode: merged.groupSessionMode ?? DEFAULT_INFOFLOW_GROUP_SESSION_MODE,
|
|
229
|
+
followUp: merged.followUp ?? true,
|
|
230
|
+
followUpWindow: merged.followUpWindow ?? DEFAULT_INFOFLOW_FOLLOW_UP_WINDOW,
|
|
231
|
+
groups: merged.groups,
|
|
232
|
+
dmPolicy: merged.dmPolicy ?? "open",
|
|
159
233
|
allowFrom: merged.allowFrom,
|
|
160
|
-
groupPolicy: merged.groupPolicy,
|
|
234
|
+
groupPolicy: merged.groupPolicy ?? "open",
|
|
161
235
|
groupAllowFrom: merged.groupAllowFrom,
|
|
236
|
+
textChunkLimit: merged.textChunkLimit ?? DEFAULT_INFOFLOW_TEXT_CHUNK_LIMIT,
|
|
162
237
|
},
|
|
163
238
|
};
|
|
164
239
|
}
|