Skip to content

fix(widget): match WhatsApp's conversation reuse/reopen semantics - #321

Open
mateusbellozupko wants to merge 1 commit into
evolution-foundation:developfrom
mateusbellozupko:fix/widget-conversation-reuse-and-reopen-session
Open

fix(widget): match WhatsApp's conversation reuse/reopen semantics#321
mateusbellozupko wants to merge 1 commit into
evolution-foundation:developfrom
mateusbellozupko:fix/widget-conversation-reuse-and-reopen-session

Conversation

@mateusbellozupko

@mateusbellozupko mateusbellozupko commented Aug 28, 2026

Copy link
Copy Markdown

Summary

Three related fixes, mirroring how Whatsapp::IncomingMessageBaseService#set_conversation already handles this:

  1. Api::V1::Widget::BaseController#conversation: previously always fell back to the contact's most recent conversation regardless of status, meaning a resolved widget conversation was silently reused/reopened forever, ignoring the inbox's lock_to_single_conversation setting entirely (the widget controller never consulted it). Now: when lock_to_single_conversation is on, resume the last conversation regardless of status (unchanged, matches WhatsApp locked behavior); when it's off, never resume a resolved one — a new conversation is created instead.
  2. Conversation#bump_ai_session_epoch_if_reopened: bumps a counter in custom_attributes whenever a conversation transitions from resolved back to open/pending.
  3. AgentBots::HttpRequestService#extract_context_id: folds that epoch into the AI processor's contextId (which is used to derive the ADK session id). Without this, a conversation kept alive via lock_to_single_conversation (or simply reopened before this PR's fix feat: Runtime Config — admin panel for system configurations #1) would carry forward the AI's full prior session history — so the bot kept repeating stale replies like "you're already in the queue" / "you were already transferred" instead of treating the reopened conversation as a fresh interaction.

Testing notes

  • Verified CONVERSATION_OPENED/status dirty-tracking hook fires correctly on resolved -> open transitions specifically (not pending -> open or other transitions) via saved_change_to_status? / status_before_last_save.
  • contextId is confirmed (via extract_context_id's existing logic) to always be the conversation UUID, so folding in the epoch only changes it on genuine reopens, not on every message.

🤖 Generated with Claude Code

Summary by Sourcery

Match widget conversation reuse and AI session behavior to WhatsApp semantics when conversations are reopened.

Bug Fixes:

  • Align widget conversation reuse with inbox locking settings so resolved conversations are only resumed when single-conversation locking is enabled.
  • Start a fresh AI session when a resolved conversation is reopened, preventing stale prior-session responses.

Enhancements:

  • Track conversation reopen epochs and incorporate them into agent-bot context identifiers.

- base_controller#conversation: mirrors Whatsapp::IncomingMessageBaseService
  #set_conversation — when lock_to_single_conversation is on, always resume
  the contact's last conversation regardless of status; otherwise never
  resume a resolved one (a new conversation is created instead).
- Conversation#bump_ai_session_epoch_if_reopened: bumps a counter in
  custom_attributes whenever a conversation is reopened after being
  resolved.
- AgentBots::HttpRequestService#extract_context_id: folds that epoch into
  the AI processor's contextId, so a reopened conversation gets a fresh AI
  session (no more repeated "you're already in the queue"/"already
  transferred" replies) even when lock_to_single_conversation keeps every
  exchange in the same Chatwoot conversation thread (EVO-2241).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR aligns widget conversation selection with WhatsApp-style lock and resolution semantics, then prevents stale AI session history from carrying across genuine resolved-to-open reopens by versioning the agent-bot contextId.

Sequence diagram for fresh AI context after conversation reopen

sequenceDiagram
    participant Widget
    participant Conversation
    participant AgentBot as AgentBots::HttpRequestService
    participant AI as AI processor

    Widget->>Conversation: update status from resolved to open/pending
    Conversation-->>Conversation: bump_ai_session_epoch_if_reopened
    Widget->>AgentBot: extract_context_id
    AgentBot->>Conversation: read custom_attributes[ai_session_epoch]
    Conversation-->>AgentBot: conversation UUID and reopen epoch
    AgentBot->>AI: request with contextId conversation UUID plus _r epoch
    AI-->>AgentBot: process as fresh ADK session
Loading

Flow diagram for widget conversation reuse and reopening

flowchart TD
    A[Widget requests conversation] --> B{Inbox lock_to_single_conversation?}
    B -->|Yes| C[Resume most recent conversation]
    B -->|No| D[Find most recent non-resolved conversation]
    D --> E{Conversation found?}
    E -->|Yes| F[Reuse conversation]
    E -->|No| G[Create new conversation]
    C --> H{Conversation status resolved?}
    H -->|Yes| I[Reopen conversation]
    H -->|No| J[Continue conversation]
    I --> K[bump_ai_session_epoch_if_reopened]
    K --> L[Next bot request uses new contextId]
    F --> J
    G --> J
Loading

File-Level Changes

Change Details Files
Align widget conversation reuse with inbox locking and resolution state.
  • Resume the latest conversation regardless of status when single-conversation locking is enabled.
  • When locking is disabled, exclude resolved conversations so a new thread is created.
app/controllers/api/v1/widget/base_controller.rb
Track genuine conversation reopens with a persistent AI session epoch.
  • Add an after-commit hook that detects resolved-to-open/pending transitions using saved status changes.
  • Increment the epoch in custom attributes without affecting unrelated status transitions.
app/models/conversation.rb
Start a fresh AI/ADK session after a conversation is reopened.
  • Append the reopen epoch to the conversation UUID used as the agent-bot contextId.
  • Preserve the UUID unchanged for conversations that have not been reopened.
app/services/agent_bots/http_request_service.rb

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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Needs a human reviewer. If the reuse or reopen logic is wrong, new messages could be attached to an incorrect existing conversation or continue with the wrong AI session context, and the epoch is persisted in conversation attributes. Reverting stops the new behavior, but messages and session records created under the wrong association would require bounded cleanup or replay.


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.

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.

1 participant