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

@@ -95,7 +95,7 @@ class TWP_Agent_Manager {
/**
* Set agent status
*/
public static function set_agent_status($user_id, $status, $call_sid = null) {
public static function set_agent_status($user_id, $status, $call_sid = null, $auto_set = false) {
global $wpdb;
$table_name = $wpdb->prefix . 'twp_agent_status';
@@ -108,6 +108,18 @@ class TWP_Agent_Manager {
$is_logged_in = $existing ? $existing->is_logged_in : 0;
$logged_in_at = $existing ? $existing->logged_in_at : null;
// Set auto_busy_at timestamp if automatically setting to busy
$auto_busy_at = null;
if ($auto_set && $status === 'busy') {
$auto_busy_at = current_time('mysql');
} elseif ($status !== 'busy') {
// Clear auto_busy_at when changing from busy to any other status
$auto_busy_at = null;
} else {
// Preserve existing auto_busy_at if manually setting to busy or not changing
$auto_busy_at = $existing ? $existing->auto_busy_at : null;
}
if ($existing) {
return $wpdb->update(
$table_name,
@@ -116,10 +128,11 @@ class TWP_Agent_Manager {
'current_call_sid' => $call_sid,
'last_activity' => current_time('mysql'),
'is_logged_in' => $is_logged_in,
'logged_in_at' => $logged_in_at
'logged_in_at' => $logged_in_at,
'auto_busy_at' => $auto_busy_at
),
array('user_id' => $user_id),
array('%s', '%s', '%s', '%d', '%s'),
array('%s', '%s', '%s', '%d', '%s', '%s'),
array('%d')
);
} else {
@@ -131,9 +144,10 @@ class TWP_Agent_Manager {
'current_call_sid' => $call_sid,
'last_activity' => current_time('mysql'),
'is_logged_in' => 0,
'logged_in_at' => null
'logged_in_at' => null,
'auto_busy_at' => $auto_busy_at
),
array('%d', '%s', '%s', '%s', '%d', '%s')
array('%d', '%s', '%s', '%s', '%d', '%s', '%s')
);
}
}
@@ -172,10 +186,11 @@ class TWP_Agent_Manager {
'is_logged_in' => $is_logged_in ? 1 : 0,
'logged_in_at' => $logged_in_at,
'last_activity' => current_time('mysql'),
'status' => $is_logged_in ? 'available' : 'offline'
'status' => $is_logged_in ? 'available' : 'offline',
'auto_busy_at' => null // Clear auto_busy_at when changing login status
),
array('user_id' => $user_id),
array('%d', '%s', '%s', '%s'),
array('%d', '%s', '%s', '%s', '%s'),
array('%d')
);
} else {
@@ -186,9 +201,10 @@ class TWP_Agent_Manager {
'status' => $is_logged_in ? 'available' : 'offline',
'is_logged_in' => $is_logged_in ? 1 : 0,
'logged_in_at' => $logged_in_at,
'last_activity' => current_time('mysql')
'last_activity' => current_time('mysql'),
'auto_busy_at' => null
),
array('%d', '%s', '%d', '%s', '%s')
array('%d', '%s', '%d', '%s', '%s', '%s')
);
}
}
@@ -316,7 +332,7 @@ class TWP_Agent_Manager {
);
// Set agent status to busy
self::set_agent_status($user_id, 'busy', $call->call_sid);
self::set_agent_status($user_id, 'busy', $call->call_sid, true);
// Make a new call to the agent with proper caller ID
$twilio = new TWP_Twilio_API();
@@ -362,15 +378,23 @@ class TWP_Agent_Manager {
// For browser mode, redirect the existing call to the browser client
$current_user = get_userdata($user_id);
// Twilio requires alphanumeric characters only - must match generate_capability_token
$clean_name = preg_replace('/[^a-zA-Z0-9]/', '', $current_user->display_name);
// Use user_login for consistency with capability token generation
$clean_name = preg_replace('/[^a-zA-Z0-9]/', '', $current_user->user_login);
if (empty($clean_name)) {
$clean_name = 'user';
}
$client_name = 'agent' . $user_id . $clean_name;
error_log("TWP Accept: Redirecting call {$call->call_sid} to browser client '{$client_name}' for user {$user_id}");
// Create TwiML to redirect call to browser client
$twiml = new \Twilio\TwiML\VoiceResponse();
$twiml->say('Connecting you to an agent.', ['voice' => 'alice']);
// Use TTS helper for ElevenLabs integration
require_once plugin_dir_path(__FILE__) . 'class-twp-tts-helper.php';
$tts_helper = TWP_TTS_Helper::get_instance();
$tts_helper->add_tts_to_twiml($twiml, 'Connecting you to an agent.');
$dial = $twiml->dial();
$dial->setAttribute('timeout', 30);
$dial->client($client_name);
@@ -594,6 +618,57 @@ class TWP_Agent_Manager {
return $result;
}
/**
* Check and revert agents from auto-busy to available after 1 minute
*/
public static function revert_auto_busy_agents() {
global $wpdb;
$table_name = $wpdb->prefix . 'twp_agent_status';
// Find agents who have been auto-busy for more than 1 minute and are still logged in
$cutoff_time = date('Y-m-d H:i:s', strtotime('-1 minute'));
$auto_busy_agents = $wpdb->get_results($wpdb->prepare(
"SELECT user_id, current_call_sid FROM $table_name
WHERE status = 'busy'
AND auto_busy_at IS NOT NULL
AND auto_busy_at < %s
AND is_logged_in = 1",
$cutoff_time
));
foreach ($auto_busy_agents as $agent) {
// Verify the call is actually finished before reverting
$call_sid = $agent->current_call_sid;
$call_active = false;
if ($call_sid) {
// Check if call is still active using Twilio API
try {
$api = new TWP_Twilio_API();
$call_status = $api->get_call_status($call_sid);
// If call is still in progress, don't revert yet
if (in_array($call_status, ['queued', 'ringing', 'in-progress'])) {
$call_active = true;
error_log("TWP Auto-Revert: Call {$call_sid} still active for user {$agent->user_id}, keeping busy");
}
} catch (Exception $e) {
error_log("TWP Auto-Revert: Could not check call status for {$call_sid}: " . $e->getMessage());
// If we can't check call status, assume it's finished and proceed with revert
}
}
// Only revert if call is not active
if (!$call_active) {
error_log("TWP Auto-Revert: Reverting user {$agent->user_id} from auto-busy to available");
self::set_agent_status($agent->user_id, 'available', null, false);
}
}
return count($auto_busy_agents);
}
/**
* Validate phone number format
*/