diff --git a/config/agents/tools.yml b/config/agents/tools.yml index 9286377f2..c2faf75e7 100644 --- a/config/agents/tools.yml +++ b/config/agents/tools.yml @@ -29,3 +29,8 @@ title: 'FAQ Lookup' description: 'Search FAQ responses using semantic similarity' icon: 'search' + +- id: handoff + title: 'Handoff to Human' + description: 'Hand off the conversation to a human agent' + icon: 'user-switch' diff --git a/enterprise/lib/captain/tools/handoff_tool.rb b/enterprise/lib/captain/tools/handoff_tool.rb new file mode 100644 index 000000000..a780ad255 --- /dev/null +++ b/enterprise/lib/captain/tools/handoff_tool.rb @@ -0,0 +1,63 @@ +class Captain::Tools::HandoffTool < Captain::Tools::BasePublicTool + description 'Hand off the conversation to a human agent when unable to assist further' + param :reason, type: 'string', desc: 'The reason why handoff is needed (optional)', required: false + + def perform(tool_context, reason: nil) + conversation = find_conversation(tool_context.state) + return 'Conversation not found' unless conversation + + # Log the handoff with reason + log_tool_usage('manual_handoff', { + conversation_id: conversation.id, + reason: reason || 'Agent requested handoff' + }) + + # Use existing handoff mechanism from ResponseBuilderJob + trigger_handoff(conversation, reason) + + "Conversation handed off to human support team#{" (Reason: #{reason})" if reason}" + rescue StandardError => e + ChatwootExceptionTracker.new(e).capture_exception + 'Failed to handoff conversation' + end + + private + + def trigger_handoff(conversation, reason) + # Create handoff message (using existing logic from ResponseBuilderJob) + handoff_message = build_handoff_message(reason) + + conversation.messages.create!( + message_type: :outgoing, + account: conversation.account, + inbox: conversation.inbox, + content: handoff_message + ) + + # Trigger the bot handoff (sets status to open + dispatches events) + conversation.bot_handoff! + end + + def build_handoff_message(reason) + base_message = @assistant.config['handoff_message'].presence || + I18n.t('conversations.messages.handoff_message') + + if reason.present? + "#{base_message}\n\nReason: #{reason}" + else + base_message + end + end + + # TODO: Future enhancement - Add team assignment capability + # This tool could be enhanced to: + # 1. Accept team_id parameter for routing to specific teams + # 2. Set conversation priority based on handoff reason + # 3. Add metadata for intelligent agent assignment + # 4. Support escalation levels (L1 -> L2 -> L3) + # + # Example future signature: + # param :team_id, type: 'string', desc: 'ID of team to assign conversation to', required: false + # param :priority, type: 'string', desc: 'Priority level (low/medium/high/urgent)', required: false + # param :escalation_level, type: 'string', desc: 'Support level (L1/L2/L3)', required: false +end