@core-workspace/infoflow-openclaw-plugin 2026.3.9 → 2026.3.32

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.
@@ -31,6 +31,7 @@ import type {
31
31
  InfoflowMessageEvent,
32
32
  InfoflowMentionIds,
33
33
  InfoflowReplyMode,
34
+ InfoflowGroupSessionMode,
34
35
  InfoflowGroupConfig,
35
36
  HandleInfoflowMessageParams,
36
37
  HandlePrivateChatParams,
@@ -78,12 +79,8 @@ export async function handlePrivateChatMessage(params: HandlePrivateChatParams):
78
79
  imageUrls.push(picUrl.trim());
79
80
  }
80
81
 
81
- logVerbose(
82
- `[infoflow] private chat: fromuser=${fromuser}, senderName=${senderName}, mes=${mes}, msgType=${msgType}, raw msgData: ${JSON.stringify(msgData)}`,
83
- );
84
-
85
- logVerbose(
86
- `[DEBUG private] content字段诊断: Content=${JSON.stringify(msgData.Content)}, content=${JSON.stringify(msgData.content)}, mes=${JSON.stringify(msgData.mes)}, MsgType=${JSON.stringify(msgData.MsgType)}, msgtype=${JSON.stringify(msgData.msgtype)}, PicUrl=${JSON.stringify(msgData.PicUrl)}, picurl=${JSON.stringify(msgData.picurl)}, imageUrls=${JSON.stringify(imageUrls)}`,
82
+ getInfoflowBotLog().info(
83
+ `[inbound:dm] from=${fromuser}, name=${senderName}, msgType=${msgType}, msgId=${messageIdStr ?? "?"}, text=${mes.slice(0, 80)}${mes.length > 80 ? "..." : ""}, images=${imageUrls.length}`,
87
84
  );
88
85
 
89
86
  if (!fromuser || (!mes.trim() && imageUrls.length === 0)) {
@@ -161,21 +158,7 @@ export async function handleGroupChatMessage(params: HandleGroupChatParams): Pro
161
158
  const timestamp = rawTime != null ? Number(rawTime) : Date.now();
162
159
 
163
160
  // Debug: 打印完整的原始消息数据
164
- logVerbose(`[DEBUG bot.groupchat] 完整 msgData: ${JSON.stringify(msgData, null, 2)}`);
165
- logVerbose(`[DEBUG bot.groupchat] 完整 header: ${JSON.stringify(header, null, 2)}`);
166
-
167
- // Debug: 输出所有可能的 ID 字段
168
- logVerbose(`[DEBUG bot.groupchat] 查找 imid (期望值: 102752365):`);
169
- logVerbose(` - header.imid: ${header?.imid}`);
170
- logVerbose(` - header.fromimid: ${header?.fromimid}`);
171
- logVerbose(` - header.fromuserid: ${header?.fromuserid}`);
172
- logVerbose(` - msgData.imid: ${msgData.imid}`);
173
- logVerbose(` - msgData.fromimid: ${msgData.fromimid}`);
174
- logVerbose(` - msgData.fromuserid: ${msgData.fromuserid}`);
175
- logVerbose(` - msgData.from: ${msgData.from}`);
176
- logVerbose(` - msgData.userid: ${msgData.userid}`);
177
- logVerbose(` - fromuser: ${fromuser}`);
178
- logVerbose(` - senderImidStr: ${senderImidStr}`);
161
+ logVerbose(`[inbound:group:raw] msgData: ${JSON.stringify(msgData, null, 2)}`);
179
162
 
180
163
  if (!fromuser) {
181
164
  return;
@@ -246,13 +229,9 @@ export async function handleGroupChatMessage(params: HandleGroupChatParams): Pro
246
229
  }
247
230
  } else if (item.type === "IMAGE") {
248
231
  // 提取图片下载地址
249
- logVerbose(`[DEBUG bot.groupchat] IMAGE item: ${JSON.stringify(item, null, 2)}`);
250
232
  const url = item.downloadurl;
251
233
  if (typeof url === "string" && url.trim()) {
252
- logVerbose(`[DEBUG bot.groupchat] 提取到图片URL: ${url}`);
253
234
  imageUrls.push(url.trim());
254
- } else {
255
- logVerbose(`[DEBUG bot.groupchat] WARNING: IMAGE item 缺少有效的 downloadurl 字段`);
256
235
  }
257
236
  }
258
237
  }
@@ -268,7 +247,6 @@ export async function handleGroupChatMessage(params: HandleGroupChatParams): Pro
268
247
  }
269
248
  // 纯图片消息:设置占位符
270
249
  if (!mes && imageUrls.length > 0) {
271
- logVerbose(`[DEBUG bot.groupchat] 纯图片消息: ${imageUrls.length} 张图片`);
272
250
  mes = `<media:image>${imageUrls.length > 1 ? ` (${imageUrls.length} images)` : ""}`;
273
251
  }
274
252
  // If mes is empty but replyContext exists, use a placeholder so the message is not dropped
@@ -279,6 +257,10 @@ export async function handleGroupChatMessage(params: HandleGroupChatParams): Pro
279
257
  // Extract sender name from header or fallback to fromuser
280
258
  const senderName = String(header?.username ?? header?.nickname ?? msgData.username ?? fromuser);
281
259
 
260
+ getInfoflowBotLog().info(
261
+ `[inbound:group] from=${fromuser}, name=${senderName}, group=${groupid}, msgId=${messageIdStr ?? "?"}, mentioned=${wasMentioned}, text=${mes.slice(0, 80)}${mes.length > 80 ? "..." : ""}, images=${imageUrls.length}, reply=${replyContextItems.length > 0}`,
262
+ );
263
+
282
264
  // Delegate to the common message handler (group chat)
283
265
  await handleInfoflowMessage({
284
266
  cfg,
@@ -316,10 +298,8 @@ export async function handleInfoflowMessage(params: HandleInfoflowMessageParams)
316
298
  const core = getInfoflowRuntime();
317
299
 
318
300
  const isGroup = chatType === "group";
319
- // Convert groupId (number) to string for peerId since routing expects string
320
- const peerId = isGroup ? (groupId !== undefined ? String(groupId) : fromuser) : fromuser;
321
301
 
322
- // Resolve per-group config for replyMode gating
302
+ // Resolve per-group config for replyMode gating (needed for groupSessionMode)
323
303
  const groupCfg = isGroup ? resolveGroupConfig(account, groupId) : undefined;
324
304
 
325
305
  // "ignore" mode: discard immediately, no save, no think, no reply
@@ -327,6 +307,18 @@ export async function handleInfoflowMessage(params: HandleInfoflowMessageParams)
327
307
  return;
328
308
  }
329
309
 
310
+ // Determine group session mode for peerId generation (group config overrides account config)
311
+ const groupSessionMode = isGroup ? groupCfg!.groupSessionMode : "group";
312
+ // Convert groupId (number) to string for peerId since routing expects string
313
+ // When groupSessionMode is "user", peerId includes the user ID to split sessions by user within group
314
+ const peerId = isGroup
315
+ ? groupId !== undefined
316
+ ? groupSessionMode === "user"
317
+ ? `${groupId}:${fromuser}`
318
+ : String(groupId)
319
+ : fromuser
320
+ : fromuser;
321
+
330
322
  // Resolve route based on chat type
331
323
  const route = core.channel.routing.resolveAgentRoute({
332
324
  cfg,
@@ -348,7 +340,11 @@ export async function handleInfoflowMessage(params: HandleInfoflowMessageParams)
348
340
  });
349
341
 
350
342
  // Build conversation label and from address based on chat type
351
- const fromLabel = isGroup ? `group:${groupId}` : senderName || fromuser;
343
+ const fromLabel = isGroup
344
+ ? groupSessionMode === "user"
345
+ ? `group:${groupId}:${senderName || fromuser}`
346
+ : `group:${groupId}`
347
+ : `infoflow-${senderName || fromuser}`;
352
348
  const fromAddress = isGroup ? `infoflow:group:${groupId}` : `infoflow:${fromuser}`;
353
349
  const toAddress = isGroup ? `infoflow:group:${groupId}` : `infoflow:${fromuser}`;
354
350
 
@@ -362,7 +358,11 @@ export async function handleInfoflowMessage(params: HandleInfoflowMessageParams)
362
358
  });
363
359
 
364
360
  // Inject accumulated group chat history into the body for context
365
- const historyKey = isGroup && groupId !== undefined ? String(groupId) : undefined;
361
+ const historyKey = isGroup && groupId !== undefined
362
+ ? groupSessionMode === "user"
363
+ ? `${groupId}:${fromuser}`
364
+ : String(groupId)
365
+ : undefined;
366
366
  let combinedBody = body;
367
367
  if (isGroup && historyKey) {
368
368
  combinedBody = buildPendingHistoryContextFromMap({
@@ -395,9 +395,8 @@ export async function handleInfoflowMessage(params: HandleInfoflowMessageParams)
395
395
  const mediaList: Array<{ path: string; contentType?: string }> = [];
396
396
  const failReasons: string[] = [];
397
397
 
398
- logVerbose(`[DEBUG bot] 图片处理开始: imageUrls数量=${event.imageUrls?.length ?? 0}`);
398
+ logVerbose(`[inbound] 图片处理: urls=${event.imageUrls?.length ?? 0}`);
399
399
  if (event.imageUrls && event.imageUrls.length > 0) {
400
- logVerbose(`[DEBUG bot] 待下载图片URLs: ${JSON.stringify(event.imageUrls)}`);
401
400
  // Collect unique hostnames from image URLs for SSRF allowlist.
402
401
  // Infoflow image servers (e.g. xp2.im.baidu.com, e4hi.im.baidu.com) resolve to
403
402
  // internal IPs on Baidu's network, so they need to be explicitly allowed.
@@ -417,38 +416,33 @@ export async function handleInfoflowMessage(params: HandleInfoflowMessageParams)
417
416
  const urls = event.imageUrls.slice(0, INFOFLOW_MAX_IMAGES);
418
417
  const results = await Promise.allSettled(
419
418
  urls.map(async (imageUrl) => {
420
- logVerbose(`[DEBUG bot] 开始下载图片: ${imageUrl}`);
421
419
  const fetched = await core.channel.media.fetchRemoteMedia({
422
420
  url: imageUrl,
423
421
  maxBytes: mediaMaxBytes,
424
422
  ssrfPolicy,
425
423
  });
426
- logVerbose(`[DEBUG bot] 图片下载成功: size=${fetched.buffer.length}, contentType=${fetched.contentType}`);
427
424
  const saved = await core.channel.media.saveMediaBuffer(
428
425
  fetched.buffer,
429
426
  fetched.contentType ?? undefined,
430
427
  "inbound",
431
428
  mediaMaxBytes,
432
429
  );
433
- logVerbose(`[infoflow] downloaded image from ${imageUrl}, saved to ${saved.path}`);
434
- logVerbose(`[DEBUG bot] 图片保存成功: path=${saved.path}`);
430
+ logVerbose(`[inbound] image downloaded: url=${imageUrl}, size=${fetched.buffer.length}, saved=${saved.path}`);
435
431
  return { path: saved.path, contentType: saved.contentType ?? fetched.contentType };
436
432
  }),
437
433
  );
438
434
  for (const result of results) {
439
435
  if (result.status === "fulfilled") {
440
436
  mediaList.push(result.value);
441
- logVerbose(`[DEBUG bot] 图片处理成功: ${result.value.path}`);
442
437
  } else {
443
438
  const reason = String(result.reason);
444
- logVerbose(`[infoflow] failed to download image: ${reason}`);
445
- logVerbose(`[DEBUG bot] 图片下载失败: ${reason}`);
439
+ logVerbose(`[inbound] image download failed: ${reason}`);
446
440
  failReasons.push(reason);
447
441
  }
448
442
  }
449
443
  }
450
444
 
451
- logVerbose(`[DEBUG bot] 图片处理完成: 成功=${mediaList.length}, 失败=${failReasons.length}`);
445
+ logVerbose(`[inbound] 图片处理完成: ok=${mediaList.length}, fail=${failReasons.length}`);
452
446
 
453
447
  const mediaPayload = buildAgentMediaPayload(mediaList);
454
448
 
@@ -521,17 +515,22 @@ export async function handleInfoflowMessage(params: HandleInfoflowMessageParams)
521
515
  let triggerReason = "direct-message";
522
516
  if (isGroup && groupCfg) {
523
517
  const { replyMode } = groupCfg;
524
- const groupIdStr = groupId !== undefined ? String(groupId) : undefined;
518
+ // Generate key for history tracking and follow-up window based on groupSessionMode
519
+ const historyReplyKey = groupId !== undefined
520
+ ? groupSessionMode === "user"
521
+ ? `${groupId}:${fromuser}`
522
+ : String(groupId)
523
+ : undefined;
525
524
 
526
525
  // "record" mode: save to session only, no think, no reply
527
526
  if (replyMode === "record") {
528
- if (groupIdStr) {
527
+ if (historyReplyKey) {
529
528
  logVerbose(
530
529
  `[infoflow:bot] pending: from=${fromuser}, group=${groupId}, reason=record-mode`,
531
530
  );
532
531
  recordPendingHistoryEntryIfEnabled({
533
532
  historyMap: chatHistories,
534
- historyKey: groupIdStr,
533
+ historyKey: historyReplyKey,
535
534
  entry: { sender: senderName || fromuser, body: mes, timestamp: Date.now() },
536
535
  limit: DEFAULT_GROUP_HISTORY_LIMIT,
537
536
  });
@@ -543,27 +542,29 @@ export async function handleInfoflowMessage(params: HandleInfoflowMessageParams)
543
542
  const wasMentioned = event.wasMentioned === true;
544
543
 
545
544
  if (replyMode === "mention-only") {
546
- // Only reply if bot was @mentioned
547
- const shouldReply = canDetectMention && wasMentioned;
545
+ // Only reply if bot was @mentioned.
546
+ // If robotName is not configured, @mention detection is impossible —
547
+ // fall back to treating every message as a trigger (same as proactive).
548
+ const shouldReply = !canDetectMention || wasMentioned;
548
549
  if (shouldReply) {
549
550
  triggerReason = "bot-mentioned";
550
551
  } else {
551
552
  // Check follow-up window: if bot recently replied, allow LLM to decide
552
553
  if (
553
554
  groupCfg.followUp &&
554
- groupIdStr &&
555
- isWithinFollowUpWindow(groupIdStr, groupCfg.followUpWindow)
555
+ historyReplyKey &&
556
+ isWithinFollowUpWindow(historyReplyKey, groupCfg.followUpWindow)
556
557
  ) {
557
558
  triggerReason = "followUp";
558
559
  ctxPayload.GroupSystemPrompt = buildFollowUpPrompt();
559
560
  } else {
560
- if (groupIdStr) {
561
+ if (historyReplyKey) {
561
562
  logVerbose(
562
563
  `[infoflow:bot] pending: from=${fromuser}, group=${groupId}, reason=mention-only-not-mentioned`,
563
564
  );
564
565
  recordPendingHistoryEntryIfEnabled({
565
566
  historyMap: chatHistories,
566
- historyKey: groupIdStr,
567
+ historyKey: historyReplyKey,
567
568
  entry: { sender: senderName || fromuser, body: mes, timestamp: Date.now() },
568
569
  limit: DEFAULT_GROUP_HISTORY_LIMIT,
569
570
  });
@@ -572,8 +573,9 @@ export async function handleInfoflowMessage(params: HandleInfoflowMessageParams)
572
573
  }
573
574
  }
574
575
  } else if (replyMode === "mention-and-watch") {
575
- // Reply if bot @mentioned, or if watched person @mentioned, or follow-up
576
- const botMentioned = canDetectMention && wasMentioned;
576
+ // Reply if bot @mentioned, or if watched person @mentioned, or follow-up.
577
+ // If robotName is not configured, fall back to treating every message as a trigger.
578
+ const botMentioned = !canDetectMention || wasMentioned;
577
579
  if (botMentioned) {
578
580
  triggerReason = "bot-mentioned";
579
581
  } else {
@@ -594,20 +596,20 @@ export async function handleInfoflowMessage(params: HandleInfoflowMessageParams)
594
596
  ctxPayload.GroupSystemPrompt = buildWatchRegexPrompt(groupCfg.watchRegex);
595
597
  } else if (
596
598
  groupCfg.followUp &&
597
- groupIdStr &&
598
- isWithinFollowUpWindow(groupIdStr, groupCfg.followUpWindow)
599
+ historyReplyKey &&
600
+ isWithinFollowUpWindow(historyReplyKey, groupCfg.followUpWindow)
599
601
  ) {
600
602
  triggerReason = "followUp";
601
603
  // Follow-up window: let LLM decide if this is a follow-up
602
604
  ctxPayload.GroupSystemPrompt = buildFollowUpPrompt();
603
605
  } else {
604
- if (groupIdStr) {
606
+ if (historyReplyKey) {
605
607
  logVerbose(
606
608
  `[infoflow:bot] pending: from=${fromuser}, group=${groupId}, reason=mention-and-watch-no-trigger`,
607
609
  );
608
610
  recordPendingHistoryEntryIfEnabled({
609
611
  historyMap: chatHistories,
610
- historyKey: groupIdStr,
612
+ historyKey: historyReplyKey,
611
613
  entry: { sender: senderName || fromuser, body: mes, timestamp: Date.now() },
612
614
  limit: DEFAULT_GROUP_HISTORY_LIMIT,
613
615
  });
@@ -663,21 +665,11 @@ export async function handleInfoflowMessage(params: HandleInfoflowMessageParams)
663
665
  }
664
666
  }
665
667
 
666
- logVerbose(
667
- `[infoflow:bot] dispatching to LLM: from=${fromuser}, group=${groupId ?? "N/A"}, trigger=${triggerReason}, replyMode=${groupCfg?.replyMode ?? "N/A"}`,
668
+ getInfoflowBotLog().info(
669
+ `[dispatch] from=${fromuser}, to=${to}, chatType=${chatType}, trigger=${triggerReason}, replyMode=${groupCfg?.replyMode ?? "N/A"}, session=${route.sessionKey}`,
668
670
  );
669
671
 
670
- // Debug: Log reply-to context
671
- logVerbose(`[DEBUG bot] event.messageId=${event.messageId}, event.senderImid=${event.senderImid}, isGroup=${isGroup}, mes=${mes.slice(0, 50)}`);
672
- if (!event.messageId) {
673
- logVerbose(`[DEBUG bot] WARNING: event.messageId is undefined/null!`);
674
- }
675
- if (!event.senderImid) {
676
- logVerbose(`[DEBUG bot] WARNING: event.senderImid is undefined/null!`);
677
- }
678
- if (!isGroup) {
679
- logVerbose(`[DEBUG bot] Not a group message, skipping reply-to`);
680
- }
672
+ logVerbose(`[dispatch:detail] messageId=${event.messageId}, senderImid=${event.senderImid}, images=${mediaList.length}`);
681
673
 
682
674
  // Send "processing" hint if LLM takes longer than processingHintDelay seconds
683
675
  // (default: 5s). Gives users feedback without spamming fast responses.
@@ -704,7 +696,7 @@ export async function handleInfoflowMessage(params: HandleInfoflowMessageParams)
704
696
  sendInfoflowMessage({
705
697
  cfg,
706
698
  to,
707
- contents: [{ type: "text", content: "⏳ 处理中..." }],
699
+ contents: [{ type: "text", content: "👌收到啦" }],
708
700
  accountId: account.accountId,
709
701
  replyTo: processingReplyTo,
710
702
  }).catch(() => {});
@@ -729,6 +721,8 @@ export async function handleInfoflowMessage(params: HandleInfoflowMessageParams)
729
721
  messageFormat: isGroup
730
722
  ? (account.config.groupMessageFormat ?? "text")
731
723
  : (account.config.dmMessageFormat ?? "text"),
724
+ // Chunk size: per-account config, default 1800
725
+ textChunkLimit: account.config.textChunkLimit ?? 1800,
732
726
  });
733
727
 
734
728
  // Cancel processing hint the moment the first real message starts being delivered,
@@ -756,15 +750,15 @@ export async function handleInfoflowMessage(params: HandleInfoflowMessageParams)
756
750
  cancelProcessingHint?.();
757
751
 
758
752
  // If hint was shown to the user, send "搞定" so they know the task is done
759
- if (hintWasSent) {
760
- const elapsedSec = Math.round((Date.now() - dispatchStartTime) / 1000);
761
- sendInfoflowMessage({
762
- cfg,
763
- to,
764
- contents: [{ type: "text", content: `任务完成 ✨ (${elapsedSec}s)` }],
765
- accountId: account.accountId,
766
- }).catch(() => {});
767
- }
753
+ // if (hintWasSent) {
754
+ // const elapsedSec = Math.round((Date.now() - dispatchStartTime) / 1000);
755
+ // sendInfoflowMessage({
756
+ // cfg,
757
+ // to,
758
+ // contents: [{ type: "text", content: `任务完成 ✨ (${elapsedSec}s)` }],
759
+ // accountId: account.accountId,
760
+ // }).catch(() => {});
761
+ // }
768
762
 
769
763
  const didReply = dispatchResult?.queuedFinal ?? false;
770
764
 
@@ -779,11 +773,12 @@ export async function handleInfoflowMessage(params: HandleInfoflowMessageParams)
779
773
 
780
774
  // Record bot reply timestamp for follow-up window tracking
781
775
  if (didReply && isGroup && groupId !== undefined) {
782
- recordGroupReply(String(groupId));
776
+ const replyKey = groupSessionMode === "user" ? `${groupId}:${fromuser}` : String(groupId);
777
+ recordGroupReply(replyKey);
783
778
  }
784
779
 
785
- logVerbose(
786
- `[infoflow] dispatch complete: ${chatType} from ${fromuser}, replied=${didReply}, finalCount=${dispatchResult?.counts.final ?? 0}, hasGroupSystemPrompt=${Boolean(ctxPayload.GroupSystemPrompt)}`,
780
+ getInfoflowBotLog().info(
781
+ `[dispatch:done] from=${fromuser}, to=${to}, replied=${didReply}, blocks=${dispatchResult?.counts.final ?? 0}, elapsed=${Date.now() - dispatchStartTime}ms`,
787
782
  );
788
783
  }
789
784
 
@@ -150,6 +150,7 @@ export function isWithinFollowUpWindow(groupId: string, windowSeconds: number):
150
150
 
151
151
  export type ResolvedGroupConfig = {
152
152
  replyMode: InfoflowReplyMode;
153
+ groupSessionMode: InfoflowGroupSessionMode;
153
154
  followUp: boolean;
154
155
  followUpWindow: number;
155
156
  watchMentions: string[];
@@ -175,6 +176,7 @@ export function resolveGroupConfig(
175
176
  groupId != null ? account.config.groups?.[String(groupId)] : undefined;
176
177
  return {
177
178
  replyMode: groupCfg?.replyMode ?? account.config.replyMode ?? inferLegacyReplyMode(account),
179
+ groupSessionMode: groupCfg?.groupSessionMode ?? account.config.groupSessionMode ?? "group",
178
180
  followUp: groupCfg?.followUp ?? account.config.followUp ?? true,
179
181
  followUpWindow: groupCfg?.followUpWindow ?? account.config.followUpWindow ?? 300,
180
182
  watchMentions: groupCfg?.watchMentions ?? account.config.watchMentions ?? [],
package/src/types.ts CHANGED
@@ -19,9 +19,14 @@ export type InfoflowReplyMode =
19
19
  | "mention-and-watch"
20
20
  | "proactive";
21
21
 
22
+ /** Group session mode: whether to split sessions by group or by group+user */
23
+ export type InfoflowGroupSessionMode = "group" | "user";
24
+
22
25
  /** Per-group configuration overrides */
23
26
  export type InfoflowGroupConfig = {
24
27
  replyMode?: InfoflowReplyMode;
28
+ /** Group session mode: "group" = one session per group, "user" = one session per user in group */
29
+ groupSessionMode?: InfoflowGroupSessionMode;
25
30
  systemPrompt?: string;
26
31
  /** Enable follow-up replies after bot responds (default: true) */
27
32
  followUp?: boolean;
@@ -128,6 +133,8 @@ export type InfoflowAccountConfig = {
128
133
  watchRegex?: string;
129
134
  /** Reply mode controlling bot engagement level in groups */
130
135
  replyMode?: InfoflowReplyMode;
136
+ /** Group session mode: "group" = one session per group, "user" = one session per user in group (default: "group") */
137
+ groupSessionMode?: InfoflowGroupSessionMode;
131
138
  /** Enable follow-up replies after bot responds to a mention (default: true) */
132
139
  followUp?: boolean;
133
140
  /** Follow-up window in seconds after last bot reply (default: 300) */
@@ -162,6 +169,12 @@ export type InfoflowAccountConfig = {
162
169
  * Default: "text"
163
170
  */
164
171
  groupMessageFormat?: "text" | "markdown";
172
+ /**
173
+ * Maximum character limit per outbound message chunk.
174
+ * Long messages are automatically split into multiple messages each within this limit.
175
+ * Default: 1800
176
+ */
177
+ textChunkLimit?: number;
165
178
  };
166
179
 
167
180
  export type ResolvedInfoflowAccount = {
@@ -195,6 +208,8 @@ export type ResolvedInfoflowAccount = {
195
208
  watchRegex?: string;
196
209
  /** Reply mode controlling bot engagement level in groups */
197
210
  replyMode?: InfoflowReplyMode;
211
+ /** Group session mode: "group" = one session per group, "user" = one session per user in group (default: "group") */
212
+ groupSessionMode?: InfoflowGroupSessionMode;
198
213
  /** Enable follow-up replies after bot responds to a mention (default: true) */
199
214
  followUp?: boolean;
200
215
  /** Follow-up window in seconds after last bot reply (default: 300) */
@@ -214,10 +229,11 @@ export type ResolvedInfoflowAccount = {
214
229
  * Default: "text"
215
230
  */
216
231
  groupMessageFormat?: "text" | "markdown";
217
- dmPolicy?: string;
218
- allowFrom?: string[];
219
- groupPolicy?: string;
220
- groupAllowFrom?: string[];
232
+ /**
233
+ * Maximum character limit per outbound message chunk.
234
+ * Default: 1800
235
+ */
236
+ textChunkLimit?: number;
221
237
  };
222
238
  };
223
239