feat: update status on agent bot assignment (#14870)

Assigning a conversation to an Agent Bot now moves it to pending.
Assigning a bot-owned pending conversation to a human opens it again,
while other assignment changes preserve the existing status. This makes
existing Agent Bot ownership behave like an AI handoff without depending
on the assignment dropdown UI work.

Closes:
https://linear.app/chatwoot/issue/CW-7448/apply-agent-bot-assignment-behavior

## Why
Agent Bot ownership should remove conversations from the main open queue
while the bot is handling them. Explicit human takeover should bring a
bot-owned pending conversation back to the open queue and clear the bot
owner.

## What changed
- Agent Bot assignment clears the human assignee and marks the
conversation pending.
- Human assignment clears the Agent Bot owner and opens the conversation
only when it was bot-owned and pending.
- Ordinary human assignment, non-pending bot takeover, and unassignment
preserve the existing conversation status.
- Manual human takeover uses the existing assignment and status events.
Bot-initiated handoffs continue to use the existing bot-handoff event
path.

## Validation
- Assign an open conversation to an Agent Bot through the assignment API
and verify it becomes pending.
- Assign that bot-owned pending conversation to a human and verify it
becomes open.
- Verify ordinary human assignment, non-pending bot takeover, and
unassignment do not force a status change.
This commit is contained in:
Sojan Jose
2026-08-07 12:08:58 -07:00
committed by GitHub
parent f770c585bd
commit 35a5f3390c
2 changed files with 63 additions and 4 deletions

View File

@@ -19,20 +19,55 @@ describe Conversations::AssignmentService do
expect(conversation.assignee_id).to be_nil
expect(conversation.assignee_agent_bot_id).to be_nil
end
it 'preserves conversation status' do
conversation.update!(status: :snoozed, snoozed_until: 1.day.from_now)
described_class.new(conversation: conversation, assignee_id: nil).perform
expect(conversation.reload.status).to eq('snoozed')
end
end
context 'when assigning a user' do
before do
conversation.update!(assignee_agent_bot: agent_bot, assignee: nil)
conversation.update!(assignee_agent_bot: agent_bot, assignee: nil, status: :pending)
end
it 'sets the agent and clears agent bot' do
it 'sets the agent, clears agent bot and opens the conversation' do
result = described_class.new(conversation: conversation, assignee_id: agent.id).perform
conversation.reload
expect(result).to eq(agent)
expect(conversation.assignee_id).to eq(agent.id)
expect(conversation.assignee_agent_bot_id).to be_nil
expect(conversation.status).to eq('open')
end
it 'starts the waiting clock when opening a bot-owned pending conversation' do
conversation.update!(waiting_since: nil)
freeze_time do
described_class.new(conversation: conversation, assignee_id: agent.id).perform
expect(conversation.reload.waiting_since).to eq(Time.current)
end
end
it 'preserves status for ordinary human assignment changes' do
conversation.update!(assignee_agent_bot: nil, status: :resolved)
described_class.new(conversation: conversation, assignee_id: agent.id).perform
expect(conversation.reload.status).to eq('resolved')
end
it 'preserves status when taking over a bot-owned non-pending conversation' do
conversation.update!(assignee_agent_bot: agent_bot, status: :resolved)
described_class.new(conversation: conversation, assignee_id: agent.id).perform
expect(conversation.reload.status).to eq('resolved')
end
end
@@ -45,8 +80,8 @@ describe Conversations::AssignmentService do
)
end
it 'sets the agent bot and clears human assignee' do
conversation.update!(assignee: agent, assignee_agent_bot: nil)
it 'sets the agent bot, clears human assignee and marks the conversation pending' do
conversation.update!(assignee: agent, assignee_agent_bot: nil, status: :open)
result = service.perform
@@ -54,6 +89,25 @@ describe Conversations::AssignmentService do
expect(result).to eq(agent_bot)
expect(conversation.assignee_agent_bot_id).to eq(agent_bot.id)
expect(conversation.assignee_id).to be_nil
expect(conversation.status).to eq('pending')
end
it 'marks a resolved conversation pending' do
conversation.update!(status: :resolved)
service.perform
expect(conversation.reload.status).to eq('pending')
end
it 'marks a snoozed conversation pending and clears the snooze timestamp' do
conversation.update!(status: :snoozed, snoozed_until: 1.day.from_now)
service.perform
conversation.reload
expect(conversation.status).to eq('pending')
expect(conversation.snoozed_until).to be_nil
end
end
end