@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Changelog
2
2
 
3
- ## 2026.3.14
3
+ ## 2026.3.15
4
4
 
5
5
  ### 新功能
6
6
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chbo297/infoflow",
3
- "version": "2026.3.14",
3
+ "version": "2026.3.16",
4
4
  "description": "OpenClaw Infoflow (如流) channel plugin for Baidu enterprise messaging",
5
5
  "type": "module",
6
6
  "main": "index.ts",
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 "../../../src/media/local-roots.js";
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[];