From 6f5a322840fba355ecaecb99b010360210f1531b Mon Sep 17 00:00:00 2001 From: Cameron Date: Mon, 9 Feb 2026 16:25:52 -0800 Subject: [PATCH] fix: clear WhatsApp typing indicator after response (#243) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WhatsApp's "typing..." indicator lingers for 15-25 seconds after the bot finishes responding because there was no way to clear it. This adds stopTypingIndicator() which sends a "paused" presence update to immediately dismiss it. - stopTypingIndicator?() added to ChannelAdapter interface (optional) - WhatsApp adapter implements it via sendPresenceUpdate("paused") - bot.ts calls it in the finally block after stream processing Written by Cameron ◯ Letta Code "First, solve the problem. Then, write the code." - John Johnson --- src/channels/types.ts | 1 + src/channels/whatsapp/index.ts | 6 ++++++ src/channels/whatsapp/outbound.ts | 18 ++++++++++++++++++ src/core/bot.ts | 1 + 4 files changed, 26 insertions(+) diff --git a/src/channels/types.ts b/src/channels/types.ts index c75fcc3..b4ee877 100644 --- a/src/channels/types.ts +++ b/src/channels/types.ts @@ -22,6 +22,7 @@ export interface ChannelAdapter { sendMessage(msg: OutboundMessage): Promise<{ messageId: string }>; editMessage(chatId: string, messageId: string, text: string): Promise; sendTypingIndicator(chatId: string): Promise; + stopTypingIndicator?(chatId: string): Promise; // Capabilities (optional) supportsEditing?(): boolean; diff --git a/src/channels/whatsapp/index.ts b/src/channels/whatsapp/index.ts index 4c37a85..7fd85f9 100644 --- a/src/channels/whatsapp/index.ts +++ b/src/channels/whatsapp/index.ts @@ -47,6 +47,7 @@ import { sendWhatsAppMessage, sendWhatsAppFile, sendTypingIndicator, + stopTypingIndicator, sendReadReceipt, type LidMapper, } from "./outbound.js"; @@ -1017,6 +1018,11 @@ export class WhatsAppAdapter implements ChannelAdapter { if (!this.sock) return; await sendTypingIndicator(this.sock, chatId); } + + async stopTypingIndicator(chatId: string): Promise { + if (!this.sock) return; + await stopTypingIndicator(this.sock, chatId); + } } // Export types and config diff --git a/src/channels/whatsapp/outbound.ts b/src/channels/whatsapp/outbound.ts index 83b02d6..8ce9f6d 100644 --- a/src/channels/whatsapp/outbound.ts +++ b/src/channels/whatsapp/outbound.ts @@ -168,6 +168,24 @@ export async function sendTypingIndicator( } } +/** + * Stop typing indicator for a chat. + * Sends "paused" presence to immediately clear the "typing..." indicator + * instead of waiting for WhatsApp's built-in timeout (~15-25s). + */ +export async function stopTypingIndicator( + sock: import("@whiskeysockets/baileys").WASocket, + chatId: string +): Promise { + if (!sock) return; + + try { + await sock.sendPresenceUpdate("paused", chatId); + } catch { + // Ignore presence errors + } +} + /** * Send read receipt for a message. * diff --git a/src/core/bot.ts b/src/core/bot.ts index d399099..dfd68a8 100644 --- a/src/core/bot.ts +++ b/src/core/bot.ts @@ -725,6 +725,7 @@ export class LettaBot implements AgentSession { } } finally { clearInterval(typingInterval); + adapter.stopTypingIndicator?.(msg.chatId)?.catch(() => {}); } // Handle no-reply marker