Skip to content

Conversation

Vitordotpy
Copy link
Contributor

@Vitordotpy Vitordotpy commented Sep 29, 2025

This Pull Request resolves two distinct issues in the Chatwoot integration, aiming to improve the agent experience and increase service stability. The fixes ensure that resolved conversations are reopened when new messages are received, if and only if there are no open conversations for that contact, and eliminate a loop of connection status notifications.

Fixes:

  1. Automatic Reopening of Resolved Conversations in Chatwoot:
  • Problem: When a customer sent a new message to a conversation that had already been marked as “resolved” in the Chatwoot panel, the conversation was automatically reopened even if there were open conversations for the same contact. As a result, the new message ended up being associated with an old conversation.

  • Solution: A check has been implemented in the Chatwoot service that, before forwarding a new message, queries the status of the corresponding conversations. If all conversations are marked as resolved, it reopens the last conversation used. If there are any open conversations, it will use the first open conversation it finds.

  1. Elimination of Infinite Loop in Connection Status Message:

    • Problem: A bug was identified in the connection event handling logic that caused the message “Connection successfully established” to be sent continuously to the Chatwoot conversation. This polluted the connection chat history with repetitive messages.

Summary by Sourcery

Ensure resolved Chatwoot conversations are only reopened when no open conversation exists, and throttle Chatwoot connection notifications to prevent message loops.

Bug Fixes:

  • Only reopen resolved Chatwoot conversations when no open conversation exists and prefer existing open conversations.
  • Prevent infinite "Connection successfully established" messages by adding a 30-second cooldown for connection notifications.

Este commit aborda duas questões críticas na integração com o Chatwoot para melhorar a estabilidade e a experiência do agente.

Primeiro, as conversas que já estavam marcadas como "resolvidas" no Chatwoot não eram reabertas automaticamente quando o cliente enviava uma nova mensagem. Isso foi corrigido para que o sistema verifique o status da conversa e a reabra, garantindo que nenhuma nova interação seja perdida.

Segundo, um bug no tratamento do evento de conexão fazia com que a mensagem de status "Conexão estabelecida com sucesso" fosse enviada repetidamente, poluindo o histórico da conversa. A lógica foi ajustada para garantir que esta notificação seja enviada apenas uma vez por evento de conexão.
Copy link
Contributor

sourcery-ai bot commented Sep 29, 2025

Reviewer's Guide

Implements conditional reopening of resolved Chatwoot conversations only when no open ones exist, and adds rate-limiting to connection status notifications to prevent infinite loops.

Sequence diagram for conditional reopening of Chatwoot conversations

sequenceDiagram
    participant User
    participant ChatwootService
    participant ChatwootClient
    participant Cache
    User->>ChatwootService: Send new message
    ChatwootService->>ChatwootClient: Query contact conversations
    ChatwootClient-->>ChatwootService: Return conversations
    alt There is an open conversation
        ChatwootService->>Cache: Cache conversation ID
        ChatwootService-->>User: Use open conversation
    else All conversations are resolved
        ChatwootService->>ChatwootClient: Reopen last resolved conversation
        ChatwootService->>Cache: Cache conversation ID
        ChatwootService-->>User: Use reopened conversation
    end
Loading

Sequence diagram for rate-limited connection status notification

sequenceDiagram
    participant "WhatsApp Instance"
    participant ChatwootService
    participant ChatwootClient
    "WhatsApp Instance"->>ChatwootService: connection.update (status: open)
    alt QR code count > 0
        ChatwootService->>ChatwootClient: Send 'Connection established' message
        ChatwootService->>"WhatsApp Instance": Reset QR code count
        ChatwootService->>"WhatsApp Instance": Set lastConnectionNotification timestamp
    else QR code count == 0
        ChatwootService->>"WhatsApp Instance": Check lastConnectionNotification
        alt Notification sent too recently
            ChatwootService-->>ChatwootClient: Skip notification
        end
    end
Loading

Class diagram for updated ChatwootService connection handling

classDiagram
    class ChatwootService {
        +reopenConversation: boolean
        +conversationPending: boolean
        +waMonitor: object
        +cache: object
        +getConversationId()
        +createBotMessage()
    }
    class waInstance {
        +qrCode: { count: number }
        +lastConnectionNotification: number
    }
    ChatwootService --> waInstance : uses
    ChatwootService --> cache : uses
Loading

File-Level Changes

Change Details Files
Refactor conversation selection and reopening to handle resolved vs open states
  • Split logic based on provider.reopenConversation flag
  • First search for non-resolved (open) conversations
  • If none found, select and reopen last resolved conversation
  • Add verbose logs for each selection and reopening step
  • Cache and return the conversation ID after determining status
src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts
Prevent repeated “Connection successfully established” messages
  • Extract waInstance to a local variable
  • Reset qrCode.count and record lastConnectionNotification timestamp
  • Add conditional to skip sending notifications if within 30s of last
  • Log a warning when skipping overly frequent notifications
src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts

Possibly linked issues

  • Docker folder is deleted #1: The PR implements the logic to only reopen a resolved Chatwoot conversation if no other open conversations exist for the contact, as discussed in the issue.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • Refactor the reopenConversation block by extracting the conversation lookup and reopen logic into smaller helper methods to reduce nesting and improve readability.
  • Extract the hardcoded minIntervalMs value into a named constant or configuration parameter to make the notification throttling interval more transparent and adjustable.
  • Double-check the connection update logic to ensure that notifications are correctly sent again once the throttle interval has elapsed rather than dropped indefinitely.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Refactor the `reopenConversation` block by extracting the conversation lookup and reopen logic into smaller helper methods to reduce nesting and improve readability.
- Extract the hardcoded `minIntervalMs` value into a named constant or configuration parameter to make the notification throttling interval more transparent and adjustable.
- Double-check the connection update logic to ensure that notifications are correctly sent again once the throttle interval has elapsed rather than dropped indefinitely.

## Individual Comments

### Comment 1
<location> `src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts:753-751` </location>
<code_context>
+        let inboxConversation = null;
+
+        if (this.provider.reopenConversation) {
+          inboxConversation = contactConversations.payload.find(
+            (conversation) =>
+              conversation && conversation.status !== 'resolved' && conversation.inbox_id == filterInbox.id,
+          );
+
+          if (inboxConversation) {
+            this.logger.verbose(
+              `Found open conversation in reopenConversation mode: ${JSON.stringify(inboxConversation)}`,
+            );
           } else {
             inboxConversation = contactConversations.payload.find(
-              (conversation) =>
-                conversation && conversation.status !== 'resolved' && conversation.inbox_id == filterInbox.id,
+              (conversation) => conversation.inbox_id == filterInbox.id,
             );
-            this.logger.verbose(`Found conversation: ${JSON.stringify(inboxConversation)}`);
</code_context>

<issue_to_address>
**suggestion (bug_risk):** The fallback search for resolved conversations may return conversations with unexpected statuses.

The fallback logic does not filter for 'resolved' status, so it may select conversations with unintended statuses. Please add a check to ensure only conversations with 'resolved' status are considered for reopening.

Suggested implementation:

```typescript
            inboxConversation = contactConversations.payload.find(
              (conversation) =>
                conversation &&
                conversation.status === 'resolved' &&
                conversation.inbox_id == filterInbox.id,
            );

```

```typescript
            if (inboxConversation) {
              this.logger.verbose(`Found resolved conversation to reopen: ${JSON.stringify(inboxConversation)}`);
              if (this.provider.conversationPending && inboxConversation.status !== 'open') {
                await client.conversations.toggleStatus({

```
</issue_to_address>

### Comment 2
<location> `src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts:2425-2427` </location>
<code_context>

       if (event === 'connection.update') {
         if (body.status === 'open') {
+          const waInstance = this.waMonitor.waInstances[instance.instanceName];
           // if we have qrcode count then we understand that a new connection was established
-          if (this.waMonitor.waInstances[instance.instanceName].qrCode.count > 0) {
+          if (waInstance && waInstance.qrCode.count > 0) {
             const msgConnection = i18next.t('cw.inbox.connected');
             await this.createBotMessage(instance, msgConnection, 'incoming');
</code_context>

<issue_to_address>
**issue (bug_risk):** Accessing waInstance.qrCode.count assumes qrCode is always defined.

Add a check to confirm waInstance.qrCode is defined before accessing its count property to prevent runtime errors.
</issue_to_address>

### Comment 3
<location> `src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts:2432` </location>
<code_context>
-            this.waMonitor.waInstances[instance.instanceName].qrCode.count = 0;
+            waInstance.qrCode.count = 0;
+
+            waInstance.lastConnectionNotification = Date.now();
+
             chatwootImport.clearAll(instance);
</code_context>

<issue_to_address>
**suggestion (bug_risk):** lastConnectionNotification is set only when qrCode.count > 0.

Currently, lastConnectionNotification is only updated when qrCode.count > 0, which may cause repeated notifications if the count stays at zero. To maintain correct throttling, update lastConnectionNotification in both branches.
</issue_to_address>

### Comment 4
<location> `src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts:2423-2446` </location>
<code_context>
      if (event === 'connection.update') {
        if (body.status === 'open') {
          const waInstance = this.waMonitor.waInstances[instance.instanceName];
          // if we have qrcode count then we understand that a new connection was established
          if (waInstance && waInstance.qrCode.count > 0) {
            const msgConnection = i18next.t('cw.inbox.connected');
            await this.createBotMessage(instance, msgConnection, 'incoming');
            waInstance.qrCode.count = 0;

            waInstance.lastConnectionNotification = Date.now();

            chatwootImport.clearAll(instance);
          } else if (waInstance) {
            const timeSinceLastNotification = Date.now() - (waInstance.lastConnectionNotification || 0);
            const minIntervalMs = 30000; // 30 seconds

            if (timeSinceLastNotification < minIntervalMs) {
              this.logger.warn(
                `Connection notification skipped for ${instance.instanceName} - too frequent (${timeSinceLastNotification}ms since last)`,
              );
            }
          }
        }
      }

</code_context>

<issue_to_address>
**suggestion (code-quality):** Merge nested if conditions ([`merge-nested-ifs`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/TypeScript/Default-Rules/merge-nested-ifs))

```suggestion
      if (event === 'connection.update' && body.status === 'open') {
            const waInstance = this.waMonitor.waInstances[instance.instanceName];
            // if we have qrcode count then we understand that a new connection was established
            if (waInstance && waInstance.qrCode.count > 0) {
              const msgConnection = i18next.t('cw.inbox.connected');
              await this.createBotMessage(instance, msgConnection, 'incoming');
              waInstance.qrCode.count = 0;

              waInstance.lastConnectionNotification = Date.now();

              chatwootImport.clearAll(instance);
            } else if (waInstance) {
              const timeSinceLastNotification = Date.now() - (waInstance.lastConnectionNotification || 0);
              const minIntervalMs = 30000; // 30 seconds

              if (timeSinceLastNotification < minIntervalMs) {
                this.logger.warn(
                  `Connection notification skipped for ${instance.instanceName} - too frequent (${timeSinceLastNotification}ms since last)`,
                );
              }
            }
      }

```

<br/><details><summary>Explanation</summary>Reading deeply nested conditional code is confusing, since you have to keep track of which
conditions relate to which levels. We therefore strive to reduce nesting where
possible, and the situation where two `if` conditions can be combined using
`and` is an easy win.
</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

…ão de conexão

Este commit introduz melhorias na integração com o Chatwoot, focando na reabertura de conversas e na notificação de conexão. A lógica foi refatorada para centralizar a busca por conversas abertas e a reabertura de conversas resolvidas, garantindo que interações não sejam perdidas. Além disso, foi implementado um intervalo mínimo para notificações de conexão, evitando mensagens excessivas e melhorando a experiência do usuário.
Este commit modifica a lógica de verificação de conversas no serviço Chatwoot, garantindo que a busca por conversas ativas seja priorizada em relação ao uso de cache. A verificação de cache foi removida em pontos críticos para evitar que conversas desatualizadas sejam utilizadas, melhorando a precisão na recuperação de dados. Além disso, a lógica de reabertura de conversas foi refinada para garantir que as interações sejam tratadas corretamente, mantendo a experiência do usuário mais fluida.
@DavidsonGomes DavidsonGomes merged commit ad8df44 into EvolutionAPI:develop Sep 29, 2025
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants