From bd1c782456b15afadf43d7da7fb1001a583be6ae Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Tue, 28 Jul 2026 17:11:18 +0500 Subject: [PATCH] fix: fire-and-forget read receipts, add docs (#70340 salvage) - Change await self._send_read_receipt to asyncio.create_task to avoid blocking message dispatch on slow bridge responses (matches BlueBubbles pattern). Up to 5s per-message latency eliminated. - Update tests: assert_called_once_with instead of assert_awaited_once_with since the receipt is now scheduled, not directly awaited. - Add send_read_receipts documentation to whatsapp.md following the BlueBubbles docs pattern. --- plugins/platforms/whatsapp/adapter.py | 5 ++++- tests/gateway/test_whatsapp_reply_prefix.py | 6 +++--- website/docs/user-guide/messaging/whatsapp.md | 3 +++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/platforms/whatsapp/adapter.py b/plugins/platforms/whatsapp/adapter.py index 62689281b50..6c2be63df02 100644 --- a/plugins/platforms/whatsapp/adapter.py +++ b/plugins/platforms/whatsapp/adapter.py @@ -1271,7 +1271,10 @@ class WhatsAppAdapter(WhatsAppBehaviorMixin, BasePlatformAdapter): for msg_data in messages: event = await self._build_message_event(msg_data) if event: - await self._send_read_receipt(msg_data) + # Fire-and-forget: a slow bridge /read must not + # delay message dispatch (matches BlueBubbles + # asyncio.create_task pattern for mark_read). + asyncio.create_task(self._send_read_receipt(msg_data)) if event.message_type == MessageType.TEXT: self._enqueue_text_event(event) else: diff --git a/tests/gateway/test_whatsapp_reply_prefix.py b/tests/gateway/test_whatsapp_reply_prefix.py index ba6084096ba..6ba12a06ca4 100644 --- a/tests/gateway/test_whatsapp_reply_prefix.py +++ b/tests/gateway/test_whatsapp_reply_prefix.py @@ -200,10 +200,10 @@ class TestReadReceiptPolicyOrdering: await adapter._poll_messages() - adapter._send_read_receipt.assert_not_awaited() + adapter._send_read_receipt.assert_not_called() @pytest.mark.asyncio - async def test_policy_accepted_message_is_marked_read_before_dispatch(self, monkeypatch): + async def test_policy_accepted_message_is_marked_read_fire_and_forget(self, monkeypatch): from plugins.platforms.whatsapp.adapter import WhatsAppAdapter adapter = WhatsAppAdapter( @@ -230,7 +230,7 @@ class TestReadReceiptPolicyOrdering: await adapter._poll_messages() - adapter._send_read_receipt.assert_awaited_once_with(raw) + adapter._send_read_receipt.assert_called_once_with(raw) adapter.handle_message.assert_awaited_once_with(event) diff --git a/website/docs/user-guide/messaging/whatsapp.md b/website/docs/user-guide/messaging/whatsapp.md index b447b534707..a80333d3967 100644 --- a/website/docs/user-guide/messaging/whatsapp.md +++ b/website/docs/user-guide/messaging/whatsapp.md @@ -180,8 +180,11 @@ Hermes supports voice on WhatsApp: whatsapp: reply_prefix: "" # Empty string disables the header # reply_prefix: "šŸ¤– *My Bot*\n──────\n" # Custom prefix (supports \n for newlines) + send_read_receipts: false # Mark accepted inbound messages as read (blue ticks) ``` +When `send_read_receipts` is `true`, the adapter marks policy-accepted inbound messages as read after DM/group/mention filtering passes. Rejected messages (e.g., from non-allowlisted senders) are not marked read. Disabled by default for privacy. Changing this setting automatically restarts the bridge subprocess on the next connection. + --- ## Message Formatting & Delivery