@chbo297/infoflow 2026.3.14 → 2026.3.16
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 +1 -1
- package/openclaw.plugin.json +8 -0
- package/package.json +1 -1
- package/src/accounts.ts +3 -0
- package/src/bot.ts +64 -5
- package/src/types.ts +4 -0
package/CHANGELOG.md
CHANGED
package/openclaw.plugin.json
CHANGED
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
"appKey": { "type": "string" },
|
|
14
14
|
"appSecret": { "type": "string" },
|
|
15
15
|
"robotName": { "type": "string" },
|
|
16
|
+
"robotId": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "不用填,该字段自动生成,用来兼容如流服务的某些特性,是如流机器人实际id(根据robotName在收到@时自动发现,用于忽略自己发出的消息)"
|
|
19
|
+
},
|
|
16
20
|
"appAgentId": {
|
|
17
21
|
"type": "number",
|
|
18
22
|
"description": "如流企业后台的应用ID,私聊消息撤回依赖此字段"
|
|
@@ -61,6 +65,10 @@
|
|
|
61
65
|
"appKey": { "type": "string" },
|
|
62
66
|
"appSecret": { "type": "string" },
|
|
63
67
|
"robotName": { "type": "string" },
|
|
68
|
+
"robotId": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"description": "不用填,该字段自动生成,用来兼容如流服务的某些特性,是如流机器人实际id(根据robotName在收到@时自动发现,用于忽略自己发出的消息)"
|
|
71
|
+
},
|
|
64
72
|
"appAgentId": {
|
|
65
73
|
"type": "number",
|
|
66
74
|
"description": "如流企业后台的应用ID,私聊消息撤回依赖此字段"
|
package/package.json
CHANGED
package/src/accounts.ts
CHANGED
|
@@ -69,6 +69,7 @@ function mergeInfoflowAccountConfig(
|
|
|
69
69
|
enabled?: boolean;
|
|
70
70
|
name?: string;
|
|
71
71
|
robotName?: string;
|
|
72
|
+
robotId?: string;
|
|
72
73
|
requireMention?: boolean;
|
|
73
74
|
watchMentions?: string[];
|
|
74
75
|
watchRegex?: string | string[];
|
|
@@ -86,6 +87,7 @@ function mergeInfoflowAccountConfig(
|
|
|
86
87
|
enabled?: boolean;
|
|
87
88
|
name?: string;
|
|
88
89
|
robotName?: string;
|
|
90
|
+
robotId?: string;
|
|
89
91
|
requireMention?: boolean;
|
|
90
92
|
watchMentions?: string[];
|
|
91
93
|
watchRegex?: string | string[];
|
|
@@ -136,6 +138,7 @@ export function resolveInfoflowAccount(params: {
|
|
|
136
138
|
appKey,
|
|
137
139
|
appSecret,
|
|
138
140
|
robotName: merged.robotName?.trim() || undefined,
|
|
141
|
+
robotId: merged.robotId?.trim() || undefined,
|
|
139
142
|
requireMention: merged.requireMention,
|
|
140
143
|
watchMentions: merged.watchMentions,
|
|
141
144
|
watchRegex: normalizeWatchRegex(merged.watchRegex),
|
package/src/bot.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
recordPendingHistoryEntryIfEnabled,
|
|
7
7
|
buildAgentMediaPayload,
|
|
8
8
|
} from "openclaw/plugin-sdk";
|
|
9
|
-
import { getAgentScopedMediaLocalRoots } from "
|
|
9
|
+
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/mattermost";
|
|
10
10
|
import { resolveInfoflowAccount } from "./accounts.js";
|
|
11
11
|
import { getInfoflowBotLog, formatInfoflowError, logVerbose } from "./logging.js";
|
|
12
12
|
import { createInfoflowReplyDispatcher } from "./reply-dispatcher.js";
|
|
@@ -66,6 +66,24 @@ function checkBotMentioned(bodyItems: InfoflowBodyItem[], robotName?: string): b
|
|
|
66
66
|
return false;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* When the bot is @mentioned (item.name matches robotName), return that AT item's robotid.
|
|
71
|
+
* Used to discover and persist the account's robotId from incoming group messages.
|
|
72
|
+
*/
|
|
73
|
+
function getBotRobotidFromBody(
|
|
74
|
+
bodyItems: InfoflowBodyItem[],
|
|
75
|
+
robotName?: string,
|
|
76
|
+
): number | undefined {
|
|
77
|
+
if (!robotName) return undefined;
|
|
78
|
+
const normalizedRobotName = robotName.toLowerCase();
|
|
79
|
+
for (const item of bodyItems) {
|
|
80
|
+
if (item.type !== "AT") continue;
|
|
81
|
+
if (item.name?.toLowerCase() === normalizedRobotName && item.robotid != null)
|
|
82
|
+
return item.robotid;
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
69
87
|
/**
|
|
70
88
|
* Check if any entry in the watchlist was @mentioned in the message body.
|
|
71
89
|
* Matching priority: userid > robotid (parsed as number) > name (fallback).
|
|
@@ -483,6 +501,8 @@ export async function handlePrivateChatMessage(params: HandlePrivateChatParams):
|
|
|
483
501
|
export async function handleGroupChatMessage(params: HandleGroupChatParams): Promise<void> {
|
|
484
502
|
const { cfg, msgData, accountId, statusSink } = params;
|
|
485
503
|
|
|
504
|
+
logVerbose(`[infoflow] group chat: raw msgData: ${JSON.stringify(msgData)}`);
|
|
505
|
+
|
|
486
506
|
// Extract sender from nested structure or flat fields.
|
|
487
507
|
// Some Infoflow events (including bot-authored forwards) only populate `fromid` on the root,
|
|
488
508
|
// so include msgData.fromid as a final fallback.
|
|
@@ -505,10 +525,6 @@ export async function handleGroupChatMessage(params: HandleGroupChatParams): Pro
|
|
|
505
525
|
const rawTime = msgData.time ?? header?.servertime;
|
|
506
526
|
const timestamp = rawTime != null ? Number(rawTime) : Date.now();
|
|
507
527
|
|
|
508
|
-
logVerbose(
|
|
509
|
-
`[infoflow] group chat: fromuser=${fromuser}, groupid=${groupid}, raw msgData: ${JSON.stringify(msgData)}`,
|
|
510
|
-
);
|
|
511
|
-
|
|
512
528
|
if (!fromuser) {
|
|
513
529
|
return;
|
|
514
530
|
}
|
|
@@ -524,6 +540,48 @@ export async function handleGroupChatMessage(params: HandleGroupChatParams): Pro
|
|
|
524
540
|
// Check if bot was @mentioned (by robotName)
|
|
525
541
|
const wasMentioned = checkBotMentioned(bodyItems, robotName);
|
|
526
542
|
|
|
543
|
+
// When bot is @mentioned, discover and persist robotId from the AT item so we can ignore our own messages later.
|
|
544
|
+
let effectiveRobotId = account.config.robotId?.trim() || undefined;
|
|
545
|
+
const discoveredRobotid = getBotRobotidFromBody(bodyItems, robotName);
|
|
546
|
+
if (wasMentioned && discoveredRobotid != null) {
|
|
547
|
+
const newRobotId = String(discoveredRobotid);
|
|
548
|
+
if (newRobotId !== effectiveRobotId) {
|
|
549
|
+
try {
|
|
550
|
+
const runtime = getInfoflowRuntime();
|
|
551
|
+
const cfg = runtime.config.loadConfig();
|
|
552
|
+
const channel = (cfg.channels ?? {}) as Record<string, unknown>;
|
|
553
|
+
const infoflow = (channel.infoflow ?? {}) as Record<string, unknown>;
|
|
554
|
+
const accounts = { ...((infoflow.accounts ?? {}) as Record<string, unknown>) };
|
|
555
|
+
const accountCfg = {
|
|
556
|
+
...((accounts[accountId] ?? {}) as Record<string, unknown>),
|
|
557
|
+
robotId: newRobotId,
|
|
558
|
+
};
|
|
559
|
+
accounts[accountId] = accountCfg;
|
|
560
|
+
(infoflow as Record<string, unknown>).accounts = accounts;
|
|
561
|
+
(channel as Record<string, unknown>).infoflow = infoflow;
|
|
562
|
+
(cfg as Record<string, unknown>).channels = channel;
|
|
563
|
+
await runtime.config.writeConfigFile(cfg);
|
|
564
|
+
logVerbose(
|
|
565
|
+
`[infoflow] group chat: persisted robotId=${newRobotId} for account ${accountId}`,
|
|
566
|
+
);
|
|
567
|
+
} catch (e) {
|
|
568
|
+
getInfoflowBotLog().warn(`[infoflow] failed to persist robotId: ${formatInfoflowError(e)}`);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
effectiveRobotId = newRobotId;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Ignore our own bot messages: only when robotId is set, treat fromid === robotId as own message.
|
|
575
|
+
const fromid = msgData.fromid;
|
|
576
|
+
if (effectiveRobotId != null && effectiveRobotId !== "" && fromid != null && fromid !== "") {
|
|
577
|
+
if (String(fromid) === effectiveRobotId) {
|
|
578
|
+
logVerbose(
|
|
579
|
+
`[infoflow] group chat: ignoring own bot message (fromid=${fromid}, robotId=${effectiveRobotId})`,
|
|
580
|
+
);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
527
585
|
// Extract non-bot mention IDs (userIds + agentIds) for LLM-driven @mentions
|
|
528
586
|
const mentionIds = extractMentionIds(bodyItems, robotName);
|
|
529
587
|
|
|
@@ -1034,6 +1092,7 @@ export async function handleInfoflowMessage(params: HandleInfoflowMessageParams)
|
|
|
1034
1092
|
|
|
1035
1093
|
/** @internal — Check if bot was mentioned in message body. Only exported for tests. */
|
|
1036
1094
|
export const _checkBotMentioned = checkBotMentioned;
|
|
1095
|
+
export const _getBotRobotidFromBody = getBotRobotidFromBody;
|
|
1037
1096
|
|
|
1038
1097
|
/** @internal — Check if any watch-list name was @mentioned. Only exported for tests. */
|
|
1039
1098
|
export const _checkWatchMentioned = checkWatchMentioned;
|
package/src/types.ts
CHANGED
|
@@ -112,6 +112,8 @@ export type InfoflowAccountConfig = {
|
|
|
112
112
|
requireMention?: boolean;
|
|
113
113
|
/** Robot name for matching @mentions in group messages */
|
|
114
114
|
robotName?: string;
|
|
115
|
+
/** Actual robot id from Infoflow (discovered when bot is @mentioned; used to ignore own messages). Not user-configured. */
|
|
116
|
+
robotId?: string;
|
|
115
117
|
/** Names to watch for @mentions; when someone @mentions a person in this list,
|
|
116
118
|
* the bot analyzes the message and replies only if confident. */
|
|
117
119
|
watchMentions?: string[];
|
|
@@ -151,6 +153,8 @@ export type ResolvedInfoflowAccount = {
|
|
|
151
153
|
requireMention?: boolean;
|
|
152
154
|
/** Robot name for matching @mentions in group messages */
|
|
153
155
|
robotName?: string;
|
|
156
|
+
/** Actual robot id from Infoflow (discovered when @mentioned; used to ignore own messages). */
|
|
157
|
+
robotId?: string;
|
|
154
158
|
/** Names to watch for @mentions; when someone @mentions a person in this list,
|
|
155
159
|
* the bot analyzes the message and replies only if confident. */
|
|
156
160
|
watchMentions?: string[];
|