fix: captain talking over support agent (#13673)
This commit is contained in:
@@ -310,6 +310,7 @@ class Message < ApplicationRecord
|
||||
def execute_after_create_commit_callbacks
|
||||
# rails issue with order of active record callbacks being executed https://github.com/rails/rails/issues/20911
|
||||
reopen_conversation
|
||||
mark_pending_conversation_as_open_for_human_response
|
||||
set_conversation_activity
|
||||
dispatch_create_events
|
||||
send_reply
|
||||
@@ -390,6 +391,18 @@ class Message < ApplicationRecord
|
||||
reopen_resolved_conversation if conversation.resolved?
|
||||
end
|
||||
|
||||
def mark_pending_conversation_as_open_for_human_response
|
||||
return unless captain_pending_conversation?
|
||||
return unless human_response?
|
||||
return if private?
|
||||
|
||||
conversation.open!
|
||||
end
|
||||
|
||||
def captain_pending_conversation?
|
||||
false
|
||||
end
|
||||
|
||||
def reopen_resolved_conversation
|
||||
# mark resolved bot conversation as pending to be reopened by bot processor service
|
||||
if conversation.inbox.active_bot?
|
||||
|
||||
@@ -236,6 +236,7 @@ en:
|
||||
resolved: 'Conversation was marked resolved by %{user_name} due to inactivity'
|
||||
resolved_by_tool: 'Conversation was marked resolved by %{user_name}: %{reason}'
|
||||
open: 'Conversation was marked open by %{user_name}'
|
||||
auto_opened_after_agent_reply: 'Conversation was marked open automatically after an agent reply'
|
||||
agent_bot:
|
||||
error_moved_to_open: 'Conversation was marked open by system due to an error with the agent bot.'
|
||||
status:
|
||||
|
||||
@@ -8,6 +8,8 @@ class Captain::Conversation::ResponseBuilderJob < ApplicationJob
|
||||
@inbox = conversation.inbox
|
||||
@assistant = assistant
|
||||
|
||||
return unless conversation_pending?
|
||||
|
||||
Current.executed_by = @assistant
|
||||
|
||||
if captain_v2_enabled?
|
||||
@@ -15,9 +17,10 @@ class Captain::Conversation::ResponseBuilderJob < ApplicationJob
|
||||
else
|
||||
generate_and_process_response
|
||||
end
|
||||
rescue ActiveStorage::FileNotFoundError, Faraday::BadRequestError => e
|
||||
handle_error(e)
|
||||
raise e
|
||||
rescue StandardError => e
|
||||
raise e if e.is_a?(ActiveStorage::FileNotFoundError) || e.is_a?(Faraday::BadRequestError)
|
||||
|
||||
handle_error(e)
|
||||
ensure
|
||||
Current.executed_by = nil
|
||||
@@ -42,6 +45,8 @@ class Captain::Conversation::ResponseBuilderJob < ApplicationJob
|
||||
end
|
||||
|
||||
def process_response
|
||||
return unless conversation_pending?
|
||||
|
||||
if handoff_requested?
|
||||
process_action('handoff')
|
||||
else
|
||||
@@ -144,4 +149,9 @@ class Captain::Conversation::ResponseBuilderJob < ApplicationJob
|
||||
def captain_v2_enabled?
|
||||
account.feature_enabled?('captain_integration_v2')
|
||||
end
|
||||
|
||||
def conversation_pending?
|
||||
status = Conversation.where(id: @conversation.id).pick(:status)
|
||||
status == 'pending' || status == Conversation.statuses[:pending]
|
||||
end
|
||||
end
|
||||
|
||||
40
enterprise/app/models/enterprise/message.rb
Normal file
40
enterprise/app/models/enterprise/message.rb
Normal file
@@ -0,0 +1,40 @@
|
||||
module Enterprise::Message
|
||||
private
|
||||
|
||||
def mark_pending_conversation_as_open_for_human_response
|
||||
return unless captain_pending_conversation?
|
||||
return unless human_response?
|
||||
return if private?
|
||||
|
||||
previous_user = Current.user
|
||||
previous_executed_by = Current.executed_by
|
||||
Current.user = nil
|
||||
Current.executed_by = nil
|
||||
|
||||
begin
|
||||
conversation.open!
|
||||
return unless conversation.saved_change_to_status?
|
||||
|
||||
create_captain_auto_open_activity_message
|
||||
ensure
|
||||
Current.user = previous_user
|
||||
Current.executed_by = previous_executed_by
|
||||
end
|
||||
end
|
||||
|
||||
def captain_pending_conversation?
|
||||
return false unless conversation.pending?
|
||||
|
||||
::CaptainInbox.exists?(inbox_id: conversation.inbox_id)
|
||||
end
|
||||
|
||||
def create_captain_auto_open_activity_message
|
||||
::Conversations::ActivityMessageJob.perform_later(
|
||||
conversation,
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: :activity,
|
||||
content: I18n.t('conversations.activity.captain.auto_opened_after_agent_reply', locale: conversation.account.locale)
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -7,7 +7,7 @@ RSpec.describe Captain::Conversation::ResponseBuilderJob, type: :job do
|
||||
let(:captain_inbox_association) { create(:captain_inbox, captain_assistant: assistant, inbox: inbox) }
|
||||
|
||||
describe '#perform' do
|
||||
let(:conversation) { create(:conversation, inbox: inbox, account: account) }
|
||||
let(:conversation) { create(:conversation, inbox: inbox, account: account, status: :pending) }
|
||||
let(:mock_llm_chat_service) { instance_double(Captain::Llm::AssistantChatService) }
|
||||
let(:mock_agent_runner_service) { instance_double(Captain::Assistant::AgentRunnerService) }
|
||||
|
||||
@@ -47,6 +47,15 @@ RSpec.describe Captain::Conversation::ResponseBuilderJob, type: :job do
|
||||
account.reload
|
||||
expect(account.usage_limits[:captain][:responses][:consumed]).to eq(1)
|
||||
end
|
||||
|
||||
it 'does not send a response when the conversation is no longer pending' do
|
||||
conversation.open!
|
||||
|
||||
expect(mock_llm_chat_service).not_to receive(:generate_response)
|
||||
expect do
|
||||
described_class.perform_now(conversation, assistant)
|
||||
end.not_to(change { conversation.messages.outgoing.count })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when captain_v2 is enabled' do
|
||||
@@ -157,7 +166,7 @@ RSpec.describe Captain::Conversation::ResponseBuilderJob, type: :job do
|
||||
end
|
||||
|
||||
describe 'retry mechanisms for image processing' do
|
||||
let(:conversation) { create(:conversation, inbox: inbox, account: account) }
|
||||
let(:conversation) { create(:conversation, inbox: inbox, account: account, status: :pending) }
|
||||
let(:mock_llm_chat_service) { instance_double(Captain::Llm::AssistantChatService) }
|
||||
let(:mock_message_builder) { instance_double(Captain::OpenAiMessageBuilderService) }
|
||||
|
||||
|
||||
@@ -23,4 +23,69 @@ RSpec.describe Message do
|
||||
expect(conversation.first_reply_created_at).not_to be_nil
|
||||
expect(conversation.waiting_since).to be_nil
|
||||
end
|
||||
|
||||
describe '#mark_pending_conversation_as_open_for_human_response' do
|
||||
let(:conversation) { create(:conversation, status: :pending) }
|
||||
let(:captain_assistant) { create(:captain_assistant, account: conversation.account) }
|
||||
let(:auto_open_activity_content) { I18n.t('conversations.activity.captain.auto_opened_after_agent_reply', locale: conversation.account.locale) }
|
||||
|
||||
before do
|
||||
create(:captain_inbox, inbox: conversation.inbox, captain_assistant: captain_assistant)
|
||||
end
|
||||
|
||||
it 'marks the conversation open when a human sends a public outgoing message' do
|
||||
create(:message, message_type: :outgoing, conversation: conversation)
|
||||
|
||||
expect(conversation.reload.open?).to be true
|
||||
end
|
||||
|
||||
it 'creates an activity message when a human sends a public outgoing message' do
|
||||
expect do
|
||||
create(:message, message_type: :outgoing, conversation: conversation)
|
||||
end.to have_enqueued_job(Conversations::ActivityMessageJob).with(
|
||||
conversation,
|
||||
{
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: :activity,
|
||||
content: auto_open_activity_content
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'creates an activity message for external echo replies' do
|
||||
message = build(
|
||||
:message,
|
||||
message_type: :outgoing,
|
||||
conversation: conversation,
|
||||
content_attributes: { external_echo: true }
|
||||
)
|
||||
message.sender = nil
|
||||
|
||||
expect do
|
||||
message.save!
|
||||
end.to have_enqueued_job(Conversations::ActivityMessageJob).with(
|
||||
conversation,
|
||||
{
|
||||
account_id: conversation.account_id,
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: :activity,
|
||||
content: auto_open_activity_content
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not mark the conversation open for private outgoing messages' do
|
||||
create(:message, message_type: :outgoing, conversation: conversation, private: true)
|
||||
|
||||
expect(conversation.reload.pending?).to be true
|
||||
end
|
||||
|
||||
it 'does not mark the conversation open for bot outgoing messages' do
|
||||
agent_bot = create(:agent_bot, account: conversation.account)
|
||||
create(:message, message_type: :outgoing, conversation: conversation, sender: agent_bot)
|
||||
|
||||
expect(conversation.reload.pending?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -271,6 +271,15 @@ RSpec.describe Message do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#mark_pending_conversation_as_open_for_human_response' do
|
||||
let(:conversation) { create(:conversation, status: :pending) }
|
||||
|
||||
it 'does not mark the conversation open when pending is used without captain' do
|
||||
create(:message, message_type: :outgoing, conversation: conversation)
|
||||
expect(conversation.reload.pending?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe '#waiting since' do
|
||||
let(:conversation) { create(:conversation) }
|
||||
let(:agent) { create(:user, account: conversation.account) }
|
||||
|
||||
Reference in New Issue
Block a user