Compare commits

...
Author SHA1 Message Date
Sojan Jose 2750321830 refactor: tidy connected bot ownership flow 2026-07-22 17:06:52 -07:00
Sojan Jose af6389bdd8 refactor: clarify conversation open paths 2026-07-22 16:40:11 -07:00
Sojan Jose bb37face88 Merge remote-tracking branch 'origin/develop' into codex/connected-agent-bot-owner 2026-07-22 15:35:08 -07:00
Sojan Jose 078c85e067 Merge remote-tracking branch 'origin/codex/connected-agent-bot-owner' into codex/connected-agent-bot-owner 2026-07-22 15:34:58 -07:00
Sojan Jose e19799d473 fix: clear bot owner when humans open conversations 2026-07-22 15:34:52 -07:00
Sojan JoseandGitHub f2e2d24ce6 Merge branch 'develop' into codex/connected-agent-bot-owner 2026-07-22 07:51:29 -07:00
Sojan Jose c1ef2c3039 Merge remote-tracking branch 'origin/develop' into codex/connected-agent-bot-owner 2026-07-20 18:21:59 -07:00
Sojan Jose 26b8650f92 Revert "fix: classify bot-owned conversations as assigned"
This reverts commit 365935c440.
2026-07-20 18:16:01 -07:00
Sojan Jose 365935c440 fix: classify bot-owned conversations as assigned 2026-07-07 19:52:11 -07:00
Sojan Jose d96415c814 fix: clear bot owner on handoff 2026-07-07 19:21:19 -07:00
Sojan JoseandGitHub 4f237add73 Merge branch 'develop' into codex/connected-agent-bot-owner 2026-07-07 19:18:29 -07:00
Sojan Jose 7bde6f23b9 fix: clear human assignee for connected bot owner 2026-07-06 22:52:37 -07:00
Sojan Jose 99d4ad8f99 refactor: trim active bot conversation setup 2026-07-06 22:31:12 -07:00
Sojan JoseandGitHub 68f941df30 Merge branch 'develop' into codex/connected-agent-bot-owner 2026-07-06 22:27:40 -07:00
Sojan Jose 73054c6a94 refactor: clarify connected agent bot conversation setup 2026-07-06 22:25:59 -07:00
Sojan Jose 3ace6f01eb fix(deps): refresh audited lockfile gems 2026-07-06 22:12:53 -07:00
Sojan Jose 5f70fd4454 fix: preserve explicit assignee for bot inbox conversations 2026-06-28 20:46:51 -07:00
Sojan Jose f5a7d78180 feat: assign connected agent bot owner 2026-06-28 19:47:23 -07:00
4 changed files with 49 additions and 16 deletions
@@ -80,7 +80,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
def toggle_status
# FIXME: move this logic into a service object
if pending_to_open_by_bot?
if bot_handoff?
@conversation.bot_handoff!
elsif params[:status].present?
set_conversation_status
@@ -88,19 +88,15 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
else
@status = @conversation.toggle_status
end
assign_conversation if should_assign_conversation?
handle_human_open if @conversation.open? && Current.user.is_a?(User)
end
def pending_to_open_by_bot?
def bot_handoff?
return false unless Current.user.is_a?(AgentBot)
@conversation.status == 'pending' && params[:status] == 'open'
end
def should_assign_conversation?
@conversation.status == 'open' && Current.user.is_a?(User) && Current.user&.agent?
end
def toggle_priority
@conversation.toggle_priority(params[:priority])
head :ok
@@ -183,8 +179,9 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
@conversation.snoozed_until = parse_date_time(params[:snoozed_until].to_s) if params[:snoozed_until]
end
def assign_conversation
@conversation.assignee = current_user
def handle_human_open
@conversation.assignee_agent_bot = nil
@conversation.assignee = Current.user if Current.user.agent?
@conversation.save!
end
+11 -4
View File
@@ -173,6 +173,7 @@ class Conversation < ApplicationRecord
def bot_handoff!
update(waiting_since: Time.current) if waiting_since.blank?
self.assignee_agent_bot = nil
open!
dispatcher_dispatch(CONVERSATION_BOT_HANDOFF)
end
@@ -291,13 +292,19 @@ class Conversation < ApplicationRecord
return handle_campaign_status if campaign.present?
# TODO: make this an inbox config instead of assuming bot conversations should start as pending
self.status = :pending if inbox.active_bot?
set_active_bot_conversation if inbox.active_bot?
end
def handle_campaign_status
# If campaign has no sender (bot-initiated) and inbox has active bot, let bot handle it
self.status = :pending if campaign.sender_id.nil? && inbox.active_bot?
set_active_bot_conversation if campaign.sender_id.nil? && inbox.active_bot?
end
def set_active_bot_conversation
# TODO: make this an inbox config instead of assuming bot conversations should start as pending
self.status = :pending
return unless inbox.agent_bot_inbox&.active? && assignee_id.blank?
self.assignee_agent_bot = inbox.agent_bot
end
def notify_conversation_creation
@@ -565,15 +565,17 @@ RSpec.describe 'Conversations API', type: :request do
expect(conversation.reload.assignee_id).to eq(agent.id)
end
it 'disbale self assign if admin changes the conversation status to open' do
conversation.update!(status: 'pending')
conversation.update!(assignee_id: nil)
it 'does not self assign and clears the agent bot owner if admin changes the conversation status to open' do
conversation.update!(status: 'pending', assignee: nil, assignee_agent_bot: agent_bot)
post "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}/toggle_status",
headers: administrator.create_new_auth_token,
as: :json
expect(response).to have_http_status(:success)
expect(conversation.reload.status).to eq('open')
expect(conversation.reload.assignee_id).not_to eq(administrator.id)
expect(conversation.reload.assignee_agent_bot).to be_nil
end
it 'toggles the conversation status to specific status when parameter is passed' do
+27
View File
@@ -408,6 +408,14 @@ RSpec.describe Conversation do
expect(conversation.reload.status).to eq('open')
end
it 'clears agent bot ownership' do
conversation.update!(assignee_agent_bot: create(:agent_bot, account: conversation.account))
conversation.bot_handoff!
expect(conversation.reload.assignee_agent_bot).to be_nil
end
it 'dispatches CONVERSATION_BOT_HANDOFF event' do
expect(Rails.configuration.dispatcher).to receive(:dispatch)
.with(described_class::CONVERSATION_BOT_HANDOFF, anything, hash_including(conversation: conversation))
@@ -719,6 +727,19 @@ RSpec.describe Conversation do
expect(conversation.status).to eq('pending')
end
it 'sets connected agent bot as the conversation owner' do
expect(conversation.assignee_agent_bot).to eq(bot_inbox.agent_bot)
expect(conversation.assignee).to be_nil
end
it 'preserves explicit human assignee' do
agent = create(:user, account: bot_inbox.inbox.account)
conversation = create(:conversation, inbox: bot_inbox.inbox, assignee: agent)
expect(conversation.assignee).to eq(agent)
expect(conversation.assignee_agent_bot).to be_nil
end
context 'with campaigns' do
let(:user) { create(:user, account: bot_inbox.inbox.account) }
@@ -726,12 +747,14 @@ RSpec.describe Conversation do
campaign = create(:campaign, inbox: bot_inbox.inbox, account: bot_inbox.inbox.account, sender: user)
conversation = create(:conversation, inbox: bot_inbox.inbox, campaign: campaign)
expect(conversation.status).to eq('open')
expect(conversation.assignee_agent_bot).to be_nil
end
it 'returns conversation as pending if campaign has no sender (bot-initiated) and bot is active' do
campaign = create(:campaign, inbox: bot_inbox.inbox, account: bot_inbox.inbox.account, sender: nil)
conversation = create(:conversation, inbox: bot_inbox.inbox, campaign: campaign)
expect(conversation.status).to eq('pending')
expect(conversation.assignee_agent_bot).to eq(bot_inbox.agent_bot)
end
end
@@ -762,6 +785,10 @@ RSpec.describe Conversation do
it 'returns conversation status as pending' do
expect(conversation.status).to eq('pending')
end
it 'does not set agent bot ownership' do
expect(conversation.assignee_agent_bot).to be_nil
end
end
describe '#delete conversation' do