Fix extension transfer system and browser phone compatibility

Major Fixes:
- Fixed extension transfers going directly to voicemail for available agents
- Resolved browser phone call disconnections during transfers
- Fixed voicemail transcription placeholder text issue
- Added Firefox compatibility with automatic media permissions

Extension Transfer Improvements:
- Changed from active client dialing to proper queue-based system
- Fixed client name generation consistency (user_login vs display_name)
- Added 2-minute timeout with automatic voicemail fallback
- Enhanced agent availability detection for browser phone users

Browser Phone Enhancements:
- Added automatic microphone/speaker permission requests
- Improved Firefox compatibility with explicit getUserMedia calls
- Fixed client naming consistency across capability tokens and call acceptance
- Added comprehensive error handling for permission denials

Database & System Updates:
- Added auto_busy_at column for automatic agent status reversion
- Implemented 1-minute auto-revert system for busy agents with cron job
- Updated database version to 1.6.2 for automatic migration
- Fixed voicemail user_id association for extension voicemails

Call Statistics & Logging:
- Fixed browser phone calls not appearing in agent statistics
- Enhanced call logging with proper agent_id association in JSON format
- Improved customer number detection for complex call topologies
- Added comprehensive debugging for call leg detection

Voicemail & Transcription:
- Replaced placeholder transcription with real Twilio API integration
- Added manual transcription request capability for existing voicemails
- Enhanced voicemail callback handling with user_id support
- Fixed transcription webhook processing for extension voicemails

Technical Improvements:
- Standardized client name generation across all components
- Added ElevenLabs TTS integration to agent connection messages
- Enhanced error handling and logging throughout transfer system
- Fixed TwiML generation syntax errors in dial() methods

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-02 11:03:33 -07:00
parent ae92ea2c81
commit 7cd7f036ff
14 changed files with 1312 additions and 194 deletions

View File

@@ -79,6 +79,13 @@ class TWP_Webhooks {
'permission_callback' => '__return_true'
));
// Extension voicemail webhook (when extension transfer times out)
register_rest_route('twilio-webhook/v1', '/extension-voicemail', array(
'methods' => 'POST',
'callback' => array($this, 'handle_extension_voicemail'),
'permission_callback' => '__return_true'
));
// Agent call status webhook (detect voicemail/no-answer)
register_rest_route('twilio-webhook/v1', '/agent-call-status', array(
'methods' => 'POST',
@@ -272,13 +279,32 @@ class TWP_Webhooks {
'CallStatus' => isset($params['CallStatus']) ? $params['CallStatus'] : ''
);
// Log the browser call
// Log the browser call with agent information
$agent_id = null;
$agent_name = '';
// Extract agent from client identifier (client:agentname format)
if (isset($call_data['From']) && strpos($call_data['From'], 'client:') === 0) {
$agent_username = substr($call_data['From'], 7); // Remove 'client:' prefix
$agent_user = get_user_by('login', $agent_username);
if ($agent_user) {
$agent_id = $agent_user->ID;
$agent_name = $agent_user->display_name;
}
}
TWP_Call_Logger::log_call(array(
'call_sid' => $call_data['CallSid'],
'from_number' => $call_data['From'],
'to_number' => $call_data['To'],
'status' => 'browser_call_initiated',
'actions_taken' => 'Browser phone call initiated'
'workflow_name' => 'Browser Phone Call',
'actions_taken' => json_encode(array(
'agent_id' => $agent_id,
'agent_name' => $agent_name,
'type' => 'browser_phone_outbound',
'from_client' => $call_data['From']
))
));
// For outbound calls from browser, handle caller ID properly
@@ -1138,6 +1164,61 @@ class TWP_Webhooks {
return $this->send_twiml_response($twiml);
}
/**
* Handle extension voicemail when transfer times out or agent doesn't answer
*/
public function handle_extension_voicemail($request) {
$params = $request->get_params();
$user_id = isset($params['user_id']) ? intval($params['user_id']) : 0;
$extension = isset($params['extension']) ? sanitize_text_field($params['extension']) : '';
$dial_status = isset($params['DialCallStatus']) ? $params['DialCallStatus'] : '';
error_log("TWP Extension Voicemail: user_id=$user_id, extension=$extension, dial_status=$dial_status");
// Check if the call was answered
if ($dial_status === 'completed' || $dial_status === 'answered') {
// Call was answered, just hang up
$twiml = '<?xml version="1.0" encoding="UTF-8"?>';
$twiml .= '<Response><Hangup/></Response>';
return $this->send_twiml_response($twiml);
}
// Call was not answered - send to voicemail
$twiml = new \Twilio\TwiML\VoiceResponse();
// Get user details for voicemail prompt
$user = get_user_by('id', $user_id);
$display_name = $user ? $user->display_name : "Extension $extension";
// Get voicemail prompt from personal queue if available
global $wpdb;
$personal_queue = $wpdb->get_row($wpdb->prepare(
"SELECT voicemail_prompt FROM {$wpdb->prefix}twp_call_queues
WHERE user_id = %d AND queue_type = 'personal'",
$user_id
));
$voicemail_prompt = $personal_queue && $personal_queue->voicemail_prompt
? $personal_queue->voicemail_prompt
: sprintf('%s is not available. Please leave a message after the tone.', $display_name);
// Use TTS helper for ElevenLabs support
require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-twp-tts-helper.php';
$tts_helper = TWP_TTS_Helper::get_instance();
$tts_helper->add_tts_to_twiml($twiml, $voicemail_prompt);
// Record voicemail with proper callback to save to database
$twiml->record([
'action' => home_url('/wp-json/twilio-webhook/v1/voicemail-callback?user_id=' . $user_id),
'maxLength' => 120, // 2 minutes max
'playBeep' => true,
'transcribe' => true,
'transcribeCallback' => home_url('/wp-json/twilio-webhook/v1/transcription?user_id=' . $user_id)
]);
return $this->send_twiml_response($twiml->asXML());
}
/**
* Proxy voicemail audio through WordPress
*/
@@ -1296,6 +1377,7 @@ class TWP_Webhooks {
$call_sid = isset($params['CallSid']) ? $params['CallSid'] : '';
$from = isset($params['From']) ? $params['From'] : '';
$workflow_id = isset($params['workflow_id']) ? intval($params['workflow_id']) : 0;
$user_id = isset($params['user_id']) ? intval($params['user_id']) : 0; // For extension voicemails
// Enhanced customer number detection for voicemails
$customer_number = $from;
@@ -1388,22 +1470,32 @@ class TWP_Webhooks {
error_log('TWP Voicemail Callback: recording_url=' . $recording_url . ', from=' . $from . ', workflow_id=' . $workflow_id . ', call_sid=' . $call_sid);
if ($recording_url) {
// Ensure database schema is up to date for extension voicemails
TWP_Activator::force_table_updates();
// Save voicemail record
global $wpdb;
$table_name = $wpdb->prefix . 'twp_voicemails';
$voicemail_id = $wpdb->insert(
$table_name,
array(
'workflow_id' => $workflow_id,
'from_number' => $from,
'recording_url' => $recording_url,
'duration' => $recording_duration,
'created_at' => current_time('mysql')
),
array('%d', '%s', '%s', '%d', '%s')
$insert_data = array(
'workflow_id' => $workflow_id,
'user_id' => $user_id, // For extension voicemails
'from_number' => $from,
'recording_url' => $recording_url,
'duration' => $recording_duration,
'created_at' => current_time('mysql')
);
$format = array('%d', '%d', '%s', '%s', '%d', '%s');
// If user_id is 0 (not an extension voicemail), set to NULL
if ($user_id === 0) {
$insert_data['user_id'] = null;
$format[1] = 'NULL';
}
$voicemail_id = $wpdb->insert($table_name, $insert_data, $format);
// Log voicemail action
if ($call_sid) {
TWP_Call_Logger::log_action($call_sid, 'Voicemail recorded (' . $recording_duration . 's)');
@@ -2171,7 +2263,7 @@ class TWP_Webhooks {
);
// Set agent to busy
TWP_Agent_Manager::set_agent_status($user_id, 'busy', $call_result['call_sid']);
TWP_Agent_Manager::set_agent_status($user_id, 'busy', $call_result['call_sid'], true);
return true;
}