diff --git a/extensions/feishu/src/bot.ts b/extensions/feishu/src/bot.ts index 181294dd13d..f6e4e488735 100644 --- a/extensions/feishu/src/bot.ts +++ b/extensions/feishu/src/bot.ts @@ -168,6 +168,7 @@ export type FeishuMessageEvent = { chat_type: "p2p" | "group"; message_type: string; content: string; + create_time?: string; mentions?: Array<{ key: string; id: { @@ -1168,6 +1169,11 @@ export async function handleFeishuMessage(params: { ...mediaPayload, }); + // Parse message create_time (Feishu uses millisecond epoch string). + const messageCreateTimeMs = event.message.create_time + ? parseInt(event.message.create_time, 10) + : undefined; + const { dispatcher, replyOptions, markDispatchIdle } = createFeishuReplyDispatcher({ cfg, agentId: route.agentId, @@ -1179,6 +1185,7 @@ export async function handleFeishuMessage(params: { rootId: ctx.rootId, mentionTargets: ctx.mentionTargets, accountId: account.accountId, + messageCreateTimeMs, }); log(`feishu[${account.accountId}]: dispatching to agent (session=${route.sessionKey})`); diff --git a/extensions/feishu/src/reply-dispatcher.ts b/extensions/feishu/src/reply-dispatcher.ts index bd59003aad9..d508459f102 100644 --- a/extensions/feishu/src/reply-dispatcher.ts +++ b/extensions/feishu/src/reply-dispatcher.ts @@ -22,6 +22,10 @@ function shouldUseCard(text: string): boolean { return /```[\s\S]*?```/.test(text) || /\|.+\|[\r\n]+\|[-:| ]+\|/.test(text); } +/** Maximum age (ms) for a message to receive a typing indicator reaction. + * Messages older than this are likely replays after context compaction (#30418). */ +const TYPING_INDICATOR_MAX_AGE_MS = 2 * 60_000; + export type CreateFeishuReplyDispatcherParams = { cfg: ClawdbotConfig; agentId: string; @@ -34,6 +38,9 @@ export type CreateFeishuReplyDispatcherParams = { rootId?: string; mentionTargets?: MentionTarget[]; accountId?: string; + /** Epoch ms when the inbound message was created. Used to suppress typing + * indicators on old/replayed messages after context compaction (#30418). */ + messageCreateTimeMs?: number; }; export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherParams) { @@ -63,6 +70,14 @@ export function createFeishuReplyDispatcher(params: CreateFeishuReplyDispatcherP if (!replyToMessageId) { return; } + // Skip typing indicator for old messages — likely replays after context + // compaction that would flood users with stale notifications (#30418). + if ( + params.messageCreateTimeMs && + Date.now() - params.messageCreateTimeMs > TYPING_INDICATOR_MAX_AGE_MS + ) { + return; + } typingState = await addTypingIndicator({ cfg, messageId: replyToMessageId,