From 50d6ebaaca9c23803877bf8e4affe7d419edc55b Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Mon, 6 Apr 2026 14:05:50 +0400 Subject: [PATCH] fix(agent-bot): Dispatch `conversation_status_changed` event to agent bots (#14002) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent bots assigned to a conversation were not receiving `conversation_status_changed` webhook events. This meant bots could not react to status transitions like moving a conversation to **pending**. The deprecated `conversation_opened` and `conversation_resolved` events were still being delivered, but the newer unified `conversation_status_changed` event was silently dropped because `AgentBotListener` had no handler for it. ## What changed - Added `conversation_status_changed` handler to `AgentBotListener`, matching the pattern already used by `WebhookListener`. The payload includes `changed_attributes` so bots know which status transition occurred. ## How to test 1. Configure an agent bot with an `outgoing_url` (e.g. a webhook.site endpoint). 2. Assign the bot to an inbox or conversation. 3. Change a conversation's status to **pending** (or any other status). 4. Verify the bot receives a `conversation_status_changed` event with the correct `changed_attributes`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) --- app/listeners/agent_bot_listener.rb | 9 ++++++ spec/listeners/agent_bot_listener_spec.rb | 38 +++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/app/listeners/agent_bot_listener.rb b/app/listeners/agent_bot_listener.rb index 1492f50ac..1dc600013 100644 --- a/app/listeners/agent_bot_listener.rb +++ b/app/listeners/agent_bot_listener.rb @@ -15,6 +15,15 @@ class AgentBotListener < BaseListener agent_bots_for(inbox, conversation).each { |agent_bot| process_webhook_bot_event(agent_bot, payload) } end + def conversation_status_changed(event) + conversation = extract_conversation_and_account(event)[0] + changed_attributes = extract_changed_attributes(event) + inbox = conversation.inbox + event_name = __method__.to_s + payload = conversation.webhook_data.merge(event: event_name, changed_attributes: changed_attributes) + agent_bots_for(inbox, conversation).each { |agent_bot| process_webhook_bot_event(agent_bot, payload) } + end + def conversation_updated(event) conversation = extract_conversation_and_account(event)[0] changed_attributes = extract_changed_attributes(event) diff --git a/spec/listeners/agent_bot_listener_spec.rb b/spec/listeners/agent_bot_listener_spec.rb index 6ab4d3b84..c74ca450c 100644 --- a/spec/listeners/agent_bot_listener_spec.rb +++ b/spec/listeners/agent_bot_listener_spec.rb @@ -65,6 +65,44 @@ describe AgentBotListener do end end + describe '#conversation_status_changed' do + let(:event_name) { 'conversation.status_changed' } + let(:changed_attributes) { { status: %w[open pending] } } + let!(:event) { Events::Base.new(event_name, Time.zone.now, conversation: conversation, changed_attributes: changed_attributes) } + + context 'when agent bot is not configured' do + it 'does not send webhook' do + expect(AgentBots::WebhookJob).not_to receive(:perform_later) + listener.conversation_status_changed(event) + end + end + + context 'when agent bot is configured on inbox' do + it 'sends webhook with changed_attributes' do + create(:agent_bot_inbox, inbox: inbox, agent_bot: agent_bot) + expect(AgentBots::WebhookJob).to receive(:perform_later).with( + agent_bot.outgoing_url, + hash_including(event: 'conversation_status_changed', changed_attributes: anything) + ).once + listener.conversation_status_changed(event) + end + end + + context 'when conversation is assigned to an agent bot' do + before do + conversation.update!(assignee_agent_bot: agent_bot, assignee: nil) + end + + it 'sends webhook to the assigned agent bot' do + expect(AgentBots::WebhookJob).to receive(:perform_later).with( + agent_bot.outgoing_url, + hash_including(event: 'conversation_status_changed', changed_attributes: anything) + ).once + listener.conversation_status_changed(event) + end + end + end + describe '#conversation_updated' do let(:event_name) { 'conversation.updated' }