diff --git a/CHANGELOG.md b/CHANGELOG.md index aa467af2ccd..94826af8513 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -145,6 +145,7 @@ Docs: https://docs.openclaw.ai - Feishu/Duplicate replies: suppress same-target reply dispatch when message-tool sends use generic provider metadata (`provider: "message"`) and normalize `lark`/`feishu` provider aliases during duplicate-target checks, preventing double-delivery in Feishu sessions. (#31526) - Feishu/Plugin sdk compatibility: add safe webhook default fallbacks when loading Feishu monitor state so mixed-version installs no longer crash if older `openclaw/plugin-sdk` builds omit webhook default constants. (#31606) - Feishu/Inbound debounce: debounce rapid same-chat sender bursts into one ordered dispatch turn, skip already-processed retries when composing merged text, and preserve bot-mention intent across merged entries to reduce duplicate or late inbound handling. (#31548) +- Feishu/Inbound ordering: serialize message handling per chat while preserving cross-chat concurrency to avoid same-chat race drops under bursty inbound traffic. (#31807) - BlueBubbles/Message metadata: harden send response ID extraction, include sender identity in DM context, and normalize inbound `message_id` selection to avoid duplicate ID metadata. (#23970) Thanks @tyler6204. - Docker/Image health checks: add Dockerfile `HEALTHCHECK` that probes gateway `GET /healthz` so container runtimes can mark unhealthy instances without requiring auth credentials in the probe command. (#11478) Thanks @U-C4N and @vincentkoc. - Docker/Sandbox bootstrap hardening: make `OPENCLAW_SANDBOX` opt-in parsing explicit (`1|true|yes|on`), support custom Docker socket paths via `OPENCLAW_DOCKER_SOCKET`, defer docker.sock exposure until sandbox prerequisites pass, and reset/roll back persisted sandbox mode to `off` when setup is skipped or partially fails to avoid stale broken sandbox state. (#29974) Thanks @jamtujest and @vincentkoc. diff --git a/extensions/feishu/src/monitor.account.ts b/extensions/feishu/src/monitor.account.ts index 7c86814b135..655a2510234 100644 --- a/extensions/feishu/src/monitor.account.ts +++ b/extensions/feishu/src/monitor.account.ts @@ -133,6 +133,25 @@ type RegisterEventHandlersContext = { fireAndForget?: boolean; }; +/** + * Per-chat serial queue that ensures messages from the same chat are processed + * in arrival order while allowing different chats to run concurrently. + */ +function createChatQueue() { + const queues = new Map>(); + return (chatId: string, task: () => Promise): Promise => { + const prev = queues.get(chatId) ?? Promise.resolve(); + const next = prev.then(task, task); + queues.set(chatId, next); + void next.finally(() => { + if (queues.get(chatId) === next) { + queues.delete(chatId); + } + }); + return next; + }; +} + function mergeFeishuDebounceMentions( entries: FeishuMessageEvent[], ): FeishuMessageEvent["message"]["mentions"] | undefined { @@ -219,15 +238,19 @@ function registerEventHandlers( }); const log = runtime?.log ?? console.log; const error = runtime?.error ?? console.error; + const enqueue = createChatQueue(); const dispatchFeishuMessage = async (event: FeishuMessageEvent) => { - await handleFeishuMessage({ - cfg, - event, - botOpenId: botOpenIds.get(accountId), - runtime, - chatHistories, - accountId, - }); + const chatId = event.message.chat_id?.trim() || "unknown"; + const task = () => + handleFeishuMessage({ + cfg, + event, + botOpenId: botOpenIds.get(accountId), + runtime, + chatHistories, + accountId, + }); + await enqueue(chatId, task); }; const resolveSenderDebounceId = (event: FeishuMessageEvent): string | undefined => { const senderId =