From 42b46e0813677f0eab9f7c36b60e07e00cfafcaa Mon Sep 17 00:00:00 2001 From: Milton Sosa Date: Mon, 16 Feb 2026 04:30:13 -0300 Subject: [PATCH 1/2] fix(chatbot): closed session should not block bot re-activation When a chatbot session exists with status='closed', the emit() method returned early, preventing the bot from re-activating on new messages. Root cause: the guard 'if (session.status === closed) return' was meant to skip sessions not awaiting user input, but it also prevented new conversations from starting after a bot flow completed. Fix: nullify the session instead of returning, so processBot enters the '!session' branch and creates a fresh session. Also adds null guards: - getConversationMessage: return empty string instead of undefined - findBotByTrigger: handle null/undefined content gracefully --- src/api/integrations/chatbot/base-chatbot.controller.ts | 6 +++--- src/utils/findBotByTrigger.ts | 8 ++++++++ src/utils/getConversationMessage.ts | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/api/integrations/chatbot/base-chatbot.controller.ts b/src/api/integrations/chatbot/base-chatbot.controller.ts index a5b83e257..4108785e4 100644 --- a/src/api/integrations/chatbot/base-chatbot.controller.ts +++ b/src/api/integrations/chatbot/base-chatbot.controller.ts @@ -797,7 +797,7 @@ export abstract class BaseChatbotController { + const normalizedContent = content?.trim() || ''; + // Check for triggerType 'all' or 'none' (both should match any message) const findTriggerAllOrNone = await botRepository.findFirst({ where: { @@ -16,6 +18,12 @@ export const findBotByTrigger = async (botRepository: any, content: string, inst return findTriggerAllOrNone; } + // If content is empty (null, undefined, whitespace-only, or media-only messages), + // only 'all'/'none' triggers apply — skip keyword/regex matching + if (!normalizedContent) { + return null; + } + const findTriggerAdvanced = await botRepository.findMany({ where: { enabled: true, diff --git a/src/utils/getConversationMessage.ts b/src/utils/getConversationMessage.ts index eca23b454..bd13f89e2 100644 --- a/src/utils/getConversationMessage.ts +++ b/src/utils/getConversationMessage.ts @@ -76,5 +76,5 @@ export const getConversationMessage = (msg: any) => { const messageContent = getMessageContent(types); - return messageContent; + return messageContent ?? ''; }; From cb4a14d1ef0e279aed5042ec3a569d0cbd678423 Mon Sep 17 00:00:00 2001 From: Milton Sosa Date: Mon, 16 Feb 2026 04:30:40 -0300 Subject: [PATCH 2/2] fix(meta): normalize execution order and fix chatwootIds in Cloud API Two bugs in BusinessStartupService message processing: 1. Execution order: Chatwoot was processed AFTER the bot emit(), but Baileys channel processes Chatwoot FIRST. This inconsistency meant the bot could not access chatwootConversationId/chatwootInboxId when processing messages from the Cloud API. 2. chatwootIds assignment: chatwootInboxId and chatwootConversationId were both incorrectly set to chatwootSentMessage.id instead of .inbox_id and .conversation_id respectively. Fix: reorder to Chatwoot-first (consistent with Baileys) and use the correct property names from the Chatwoot response object. --- .../channel/meta/whatsapp.business.service.ts | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/api/integrations/channel/meta/whatsapp.business.service.ts b/src/api/integrations/channel/meta/whatsapp.business.service.ts index 1e4808c15..1f820d0e5 100644 --- a/src/api/integrations/channel/meta/whatsapp.business.service.ts +++ b/src/api/integrations/channel/meta/whatsapp.business.service.ts @@ -668,15 +668,7 @@ export class BusinessStartupService extends ChannelStartupService { sendTelemetry(`received.message.${messageRaw.messageType ?? 'unknown'}`); - this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw); - - await chatbotController.emit({ - instance: { instanceName: this.instance.name, instanceId: this.instanceId }, - remoteJid: messageRaw.key.remoteJid, - msg: messageRaw, - pushName: messageRaw.pushName, - }); - + // Normalized order: Chatwoot first, then bot (consistent with Baileys channel) if (this.configService.get('CHATWOOT').ENABLED && this.localChatwoot?.enabled) { const chatwootSentMessage = await this.chatwootService.eventWhatsapp( Events.MESSAGES_UPSERT, @@ -684,13 +676,22 @@ export class BusinessStartupService extends ChannelStartupService { messageRaw, ); - if (chatwootSentMessage?.id) { + if (chatwootSentMessage) { messageRaw.chatwootMessageId = chatwootSentMessage.id; - messageRaw.chatwootInboxId = chatwootSentMessage.id; - messageRaw.chatwootConversationId = chatwootSentMessage.id; + messageRaw.chatwootInboxId = chatwootSentMessage.inbox_id; + messageRaw.chatwootConversationId = chatwootSentMessage.conversation_id; } } + this.sendDataWebhook(Events.MESSAGES_UPSERT, messageRaw); + + await chatbotController.emit({ + instance: { instanceName: this.instance.name, instanceId: this.instanceId }, + remoteJid: messageRaw.key.remoteJid, + msg: messageRaw, + pushName: messageRaw.pushName, + }); + if (!this.isMediaMessage(message) && message.type !== 'sticker') { await this.prismaRepository.message.create({ data: messageRaw,