     1→/**
     2→ * Twilio Media Streams → Deepgram Transcription
     3→ *
     4→ * This handles audio from BOTH sides of a Twilio call:
     5→ * - Inbound track: What the other person says (customer)
     6→ * - Outbound track: What you say (agent)
     7→ *
     8→ * Twilio sends: mulaw audio, 8kHz, base64 encoded
     9→ * Deepgram needs: mulaw audio, 8kHz (can handle it directly!)
    10→ */
    11→
    12→const WebSocket = require('ws');
    13→const { supabase } = require('../config/database');
    14→
    15→// Store active streams: callSid -> { deepgramWs, transcripts, startTime, etc }
    16→const activeStreams = new Map();
    17→
    18→/**
    19→ * Get Deepgram config for Twilio mulaw audio
    20→ */
    21→function getDeepgramConfigForTwilio() {
    22→  const params = new URLSearchParams({
    23→    model: 'nova-2',
    24→    language: 'en-AU',
    25→    encoding: 'mulaw',        // Twilio sends mulaw
    26→    sample_rate: '8000',      // Twilio uses 8kHz
    27→    channels: '2',            // Stereo: inbound + outbound
    28→    multichannel: 'true',     // Transcribe each channel separately
    29→    smart_format: 'true',
    30→    interim_results: 'true',
    31→    punctuate: 'true',
    32→    diarize: 'false',         // We already know who's speaking from channels
    33→    endpointing: '300',
    34→  });
    35→
    36→  return {
    37→    url: `wss://api.deepgram.com/v1/listen?${params.toString()}`,
    38→    apiKey: process.env.DEEPGRAM_API_KEY,
    39→  };
    40→}
    41→
    42→/**
    43→ * Handle incoming Twilio Media Stream WebSocket connection
    44→ */
    45→function handleTwilioMediaStream(ws, req) {
    46→  console.log('[TwilioMedia] New connection');
    47→
    48→  let callSid = null;
    49→  let streamSid = null;
    50→  let deepgramWs = null;
    51→  let inboundBuffer = [];
    52→  let outboundBuffer = [];
    53→  let sequenceNumbers = { inbound: -1, outbound: -1 };
    54→
    55→  ws.on('message', async (message) => {
    56→    try {
    57→      const msg = JSON.parse(message);
    58→
    59→      switch (msg.event) {
    60→        case 'connected':
    61→          console.log('[TwilioMedia] Connected:', msg.protocol);
    62→          break;
    63→
    64→        case 'start':
    65→          // Stream started - contains metadata
    66→          callSid = msg.start.callSid;
    67→          streamSid = msg.start.streamSid;
    68→          const customParams = msg.start.customParameters || {};
    69→
    70→          console.log('[TwilioMedia] Stream started:', {
    71→            callSid,
    72→            streamSid,
    73→            tracks: msg.start.tracks,
    74→            mediaFormat: msg.start.mediaFormat,
    75→            customParams
    76→          });
    77→
    78→          // Connect to Deepgram
    79→          deepgramWs = await connectToDeepgram(callSid, customParams.leadId);
    80→
    81→          // Store stream info with transcript accumulator
    82→          activeStreams.set(callSid, {
    83→            deepgramWs,
    84→            streamSid,
    85→            twilioWs: ws,
    86→            leadId: customParams.leadId,
    87→            transcripts: [], // Accumulate all transcripts
    88→            startTime: Date.now(),
    89→          });
    90→          break;
    91→
    92→        case 'media':
    93→          // Audio data from Twilio
    94→          if (!deepgramWs || deepgramWs.readyState !== WebSocket.OPEN) {
    95→            return;
    96→          }
    97→
    98→          const track = msg.media.track; // 'inbound' or 'outbound'
    99→          const payload = msg.media.payload; // base64 encoded mulaw audio
   100→          const timestamp = parseInt(msg.media.timestamp);
   101→          const sequence = parseInt(msg.media.chunk);
   102→
   103→          // Decode base64 to buffer
   104→          const audioBuffer = Buffer.from(payload, 'base64');
   105→
   106→          // For stereo mixing, we need to interleave inbound and outbound
   107→          // Each sample is 1 byte (mulaw is 8-bit)
   108→          if (track === 'inbound') {
   109→            inboundBuffer.push({ audio: audioBuffer, seq: sequence, ts: timestamp });
   110→          } else if (track === 'outbound') {
   111→            outboundBuffer.push({ audio: audioBuffer, seq: sequence, ts: timestamp });
   112→          }
   113→
   114→          // Mix and send when we have data from both tracks
   115→          // (or after timeout if one track is silent)
   116→          mixAndSendAudio(deepgramWs, inboundBuffer, outboundBuffer);
   117→          break;
   118→
   119→        case 'stop':
   120→          console.log('[TwilioMedia] Stream stopped:', {
   121→            callSid,
   122→            reason: msg.stop?.reason
   123→          });
   124→
   125→          // Save transcript before cleanup
   126→          await saveCallTranscript(callSid);
   127→
   128→          // Close Deepgram connection
   129→          if (deepgramWs && deepgramWs.readyState === WebSocket.OPEN) {
   130→            deepgramWs.close();
   131→          }
   132→          activeStreams.delete(callSid);
   133→          break;
   134→
   135→        case 'mark':
   136→          // Acknowledgment that audio was played (bidirectional only)
   137→          console.log('[TwilioMedia] Mark:', msg.mark?.name);
   138→          break;
   139→
   140→        default:
   141→          console.log('[TwilioMedia] Unknown event:', msg.event);
   142→      }
   143→    } catch (error) {
   144→      console.error('[TwilioMedia] Error processing message:', error);
   145→    }
   146→  });
   147→
   148→  ws.on('close', async () => {
   149→    console.log('[TwilioMedia] Connection closed');
   150→
   151→    // Save transcript if not already saved
   152→    if (callSid && activeStreams.has(callSid)) {
   153→      await saveCallTranscript(callSid);
   154→      activeStreams.delete(callSid);
   155→    }
   156→
   157→    if (deepgramWs && deepgramWs.readyState === WebSocket.OPEN) {
   158→      deepgramWs.close();
   159→    }
   160→  });
   161→
   162→  ws.on('error', (error) => {
   163→    console.error('[TwilioMedia] WebSocket error:', error);
   164→  });
   165→}
   166→
   167→/**
   168→ * Connect to Deepgram for transcription
   169→ */
   170→async function connectToDeepgram(callSid, leadId) {
   171→  return new Promise((resolve, reject) => {
   172→    const config = getDeepgramConfigForTwilio();
   173→
   174→    console.log('[TwilioMedia] Connecting to Deepgram:', config.url);
   175→
   176→    const ws = new WebSocket(config.url, {
   177→      headers: {
   178→        'Authorization': `Token ${config.apiKey}`,
   179→      },
   180→    });
   181→
   182→    ws.on('open', () => {
   183→      console.log('[TwilioMedia] Deepgram connected for call:', callSid);
   184→      resolve(ws);
   185→    });
   186→
   187→    ws.on('message', (data) => {
   188→      try {
   189→        const response = JSON.parse(data);
   190→
   191→        if (response.type === 'Results') {
   192→          const channelIndex = response.channel_index?.[0] || 0;
   193→          const transcript = response.channel?.alternatives?.[0]?.transcript || '';
   194→          const isFinal = response.is_final;
   195→          const speaker = channelIndex === 0 ? 'customer' : 'agent';
   196→
   197→          if (transcript) {
   198→            console.log(`[TwilioMedia] [${speaker}] ${isFinal ? '✓' : '...'} ${transcript}`);
   199→
   200→            // Broadcast to connected clients
   201→            broadcastTranscript(callSid, {
   202→              speaker,
   203→              transcript,
   204→              isFinal,
   205→              timestamp: response.start,
   206→              duration: response.duration,
   207→              leadId,
   208→            });
   209→          }
   210→        } else if (response.type === 'SpeechStarted') {
   211→          const channelIndex = response.channel?.[0] || 0;
   212→          const speaker = channelIndex === 0 ? 'customer' : 'agent';
   213→          console.log(`[TwilioMedia] Speech started: ${speaker}`);
   214→        }
   215→      } catch (error) {
   216→        console.error('[TwilioMedia] Error parsing Deepgram message:', error);
   217→      }
   218→    });
   219→
   220→    ws.on('close', (code, reason) => {
   221→      console.log('[TwilioMedia] Deepgram closed:', code, reason?.toString());
   222→    });
   223→
   224→    ws.on('error', (error) => {
   225→      console.error('[TwilioMedia] Deepgram error:', error);
   226→      reject(error);
   227→    });
   228→  });
   229→}
   230→
   231→/**
   232→ * Mix inbound and outbound audio into stereo and send to Deepgram
   233→ * Channel 0 = inbound (customer), Channel 1 = outbound (agent)
   234→ */
   235→function mixAndSendAudio(deepgramWs, inboundBuffer, outboundBuffer) {
   236→  // Process when we have at least one chunk from each (or buffer gets too big)
   237→  while (inboundBuffer.length > 0 && outboundBuffer.length > 0) {
   238→    const inbound = inboundBuffer.shift();
   239→    const outbound = outboundBuffer.shift();
   240→
   241→    // Interleave: for each sample position, alternate inbound/outbound
   242→    // mulaw is 8-bit, so each sample is 1 byte
   243→    const inLen = inbound.audio.length;
   244→    const outLen = outbound.audio.length;
   245→    const maxLen = Math.max(inLen, outLen);
   246→
   247→    // Create stereo buffer (2 bytes per sample position: L + R)
   248→    const stereoBuffer = Buffer.alloc(maxLen * 2);
   249→
   250→    for (let i = 0; i < maxLen; i++) {
   251→      // Channel 0 (left) = inbound (customer)
   252→      stereoBuffer[i * 2] = i < inLen ? inbound.audio[i] : 0x7F; // 0x7F = mulaw silence
   253→      // Channel 1 (right) = outbound (agent)
   254→      stereoBuffer[i * 2 + 1] = i < outLen ? outbound.audio[i] : 0x7F;
   255→    }
   256→
   257→    if (deepgramWs.readyState === WebSocket.OPEN) {
   258→      deepgramWs.send(stereoBuffer);
   259→    }
   260→  }
   261→
   262→  // Handle case where only one side is talking (flush old buffers)
   263→  const MAX_BUFFER = 10;
   264→  while (inboundBuffer.length > MAX_BUFFER) {
   265→    const inbound = inboundBuffer.shift();
   266→    // Send as mono on channel 0
   267→    if (deepgramWs.readyState === WebSocket.OPEN) {
   268→      const stereoBuffer = Buffer.alloc(inbound.audio.length * 2);
   269→      for (let i = 0; i < inbound.audio.length; i++) {
   270→        stereoBuffer[i * 2] = inbound.audio[i];
   271→        stereoBuffer[i * 2 + 1] = 0x7F; // silence on outbound channel
   272→      }
   273→      deepgramWs.send(stereoBuffer);
   274→    }
   275→  }
   276→
   277→  while (outboundBuffer.length > MAX_BUFFER) {
   278→    const outbound = outboundBuffer.shift();
   279→    // Send as mono on channel 1
   280→    if (deepgramWs.readyState === WebSocket.OPEN) {
   281→      const stereoBuffer = Buffer.alloc(outbound.audio.length * 2);
   282→      for (let i = 0; i < outbound.audio.length; i++) {
   283→        stereoBuffer[i * 2] = 0x7F; // silence on inbound channel
   284→        stereoBuffer[i * 2 + 1] = outbound.audio[i];
   285→      }
   286→      deepgramWs.send(stereoBuffer);
   287→    }
   288→  }
   289→}
   290→
   291→/**
   292→ * Broadcast transcript and accumulate for saving
   293→ */
   294→function broadcastTranscript(callSid, data) {
   295→  const stream = activeStreams.get(callSid);
   296→  if (!stream) return;
   297→
   298→  // Accumulate final transcripts for saving
   299→  if (data.isFinal && data.transcript) {
   300→    const prefix = data.speaker === 'customer' ? 'Customer: ' : 'Agent: ';
   301→    stream.transcripts.push(prefix + data.transcript);
   302→    console.log(`[TwilioMedia] Accumulated: ${prefix}${data.transcript}`);
   303→  }
   304→
   305→  // Emit to any subscribers
   306→  if (stream.onTranscript) {
   307→    stream.onTranscript(data);
   308→  }
   309→}
   310→
   311→/**
   312→ * Save call transcript to database when stream ends
   313→ */
   314→async function saveCallTranscript(callSid) {
   315→  const stream = activeStreams.get(callSid);
   316→  if (!stream || !stream.leadId) {
   317→    console.log('[TwilioMedia] No stream or leadId to save transcript');
   318→    return;
   319→  }
   320→
   321→  const fullTranscript = stream.transcripts.join('\n');
   322→  const duration = Math.round((Date.now() - stream.startTime) / 1000);
   323→
   324→  if (fullTranscript.length < 10) {
   325→    console.log('[TwilioMedia] Transcript too short, not saving:', fullTranscript.length, 'chars');
   326→    return;
   327→  }
   328→
   329→  console.log(`[TwilioMedia] Saving transcript for lead ${stream.leadId}: ${fullTranscript.length} chars, ${duration}s`);
   330→
   331→  try {
   332→    // Save to copilot_sessions
   333→    const { error } = await supabase
   334→      .from('copilot_sessions')
   335→      .insert({
   336→        lead_id: stream.leadId,
   337→        transcript: fullTranscript,
   338→        duration_seconds: duration,
   339→      });
   340→
   341→    if (error) {
   342→      console.error('[TwilioMedia] Failed to save transcript:', error.message);
   343→    } else {
   344→      console.log('[TwilioMedia] Transcript saved successfully');
   345→    }
   346→  } catch (err) {
   347→    console.error('[TwilioMedia] Error saving transcript:', err.message);
   348→  }
   349→}
   350→
   351→/**
   352→ * Subscribe to transcripts for a specific call
   353→ */
   354→function subscribeToTranscripts(callSid, callback) {
   355→  const stream = activeStreams.get(callSid);
   356→  if (stream) {
   357→    stream.onTranscript = callback;
   358→  }
   359→}
   360→
   361→module.exports = {
   362→  handleTwilioMediaStream,
   363→  subscribeToTranscripts,
   364→  activeStreams,
   365→};
   366→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


Bash

git log --oneline --all --grep="call\|voice\|twilio" -i -- src/services/twilio.js src/routes/voice.js src/routes/calls.js admin/src/components/EliteCallUI.jsx 2>/dev/null | head -30


     1→/**
     2→ * Setup Twilio Media Stream WebSocket endpoint
     3→ * Call this after creating your HTTP server
     4→ */
     5→
     6→const WebSocket = require('ws');
     7→const { handleTwilioMediaStream } = require('./twilioMedia');
     8→
     9→function setupTwilioMediaStreams(server) {
    10→  // Create WebSocket server for Twilio Media Streams
    11→  // Using same pattern as Deepgram proxy (server + path) for consistency
    12→  const wss = new WebSocket.Server({
    13→    server,
    14→    path: '/api/twilio-media',
    15→  });
    16→
    17→  // Handle new Twilio Media Stream connections
    18→  wss.on('connection', (ws, request) => {
    19→    console.log('[TwilioMedia] New WebSocket connection from Twilio');
    20→    console.log('[TwilioMedia] Request URL:', request.url);
    21→    console.log('[TwilioMedia] Headers:', JSON.stringify(request.headers, null, 2));
    22→    handleTwilioMediaStream(ws, request);
    23→  });
    24→
    25→  wss.on('error', (error) => {
    26→    console.error('[TwilioMedia] WebSocket server error:', error);
    27→  });
    28→
    29→  console.log('[TwilioMedia] WebSocket endpoint ready: /api/twilio-media');
    30→
    31→  return wss;
    32→}
    33→
    34→module.exports = { setupTwilioMediaStreams };
    35→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


5cb8779 feat: Add full intel extraction to post-call transcription
249b886 feat: Add transcript backfill endpoint and script
cf015f3 fix: SMS phone lookup missing 61XXXXXXXXX format
0e4ffc7 fix: Transcription not saving - timing issue between recording webhook and call logging
79f9dea Feature: Enhanced lead context header in conversation thread
8fca5f4 Add Full-Text Search and denormalize comm metadata
000280f [Micro-opt Phase 4] Replace SELECT * with specific columns
19578c5 [Micro-opt Phase 3] API efficiency improvements
8c54d4f [Micro-Opt] Quick wins batch: Frontend + Backend optimizations
d4e0eba Add lead assignment system with database-driven user management
d0c7517 Fix handled_by column not being set in call logging
3cd6041 Add Weekly Rep Performance Report system
d139664 Auto-enroll leads in SMS sequence on no-answer calls
e9bd8a4 Fix sequence processor trade column + dossier RLS
890a0c3 Add 'Adapt in your own voice' labels to AI suggestions
9c31e4a Feature: Use preloaded intel for instant Ice Breakers on call start
3f43c8a Feature: Ice Breakers section at top of active call UI
7c5b617 fix: Transcription failing - Deepgram couldn't access Twilio recordings
ab82e04 fix: Enable recording for inbound calls
7f4ee1d Docs: Update BUILD-GUIDE.md and LESSONS.md
d3d4a9a Improve voicemail greeting to sound more professional
52621d2 Add call recording playback feature
27906fd Fix circular dependency warning (callOpenAI)
b178262 Add Lead Dossier - structured living document per lead
cef02b6 Link call transcript to communication record via CallSid
efe6df8 Switch to post-call recording transcription
e6c5452 Fix TwiML parameter chaining - cannot chain .parameter() calls
c89c693 Fix Twilio Media Streams WebSocket setup
8db4189 Temporarily disable Media Stream to debug call issue
817f5ce Add Twilio Media Streams for two-way call transcription


Perfect! Now let me get more details on the recent changes and find any component files that handle the calling UI:


find /home/ccuser/rateright-growth/admin/src -type f -name "*.jsx" | grep -iE "(call|voice|dial|phone)" | head -20


grep -r "import.*EliteCallUI\|import.*CallUI" /home/ccuser/rateright-growth/admin/src --include="*.jsx" --include="*.tsx" | head -10


/home/ccuser/rateright-growth/admin/src/components/LiveCopilot.jsx:import EliteCallUI from './EliteCallUI';


     1→import { createContext, useContext, useState, useCallback, useRef, useEffect } from 'react';
     2→import { useNavigate } from 'react-router-dom';
     3→
     4→const PowerDialerContext = createContext(null);
     5→
     6→// Study time options in seconds
     7→export const STUDY_TIME_OPTIONS = [
     8→  { value: 30, label: '30s' },
     9→  { value: 60, label: '1 min' },
    10→  { value: 120, label: '2 min' },
    11→  { value: 180, label: '3 min' },
    12→  { value: 300, label: '5 min' },
    13→];
    14→
    15→export function PowerDialerProvider({ children }) {
    16→  // Power dialer state
    17→  const [isActive, setIsActive] = useState(false);
    18→  const [isPaused, setIsPaused] = useState(false);
    19→  const [queue, setQueue] = useState([]); // Array of lead objects
    20→  const [currentIndex, setCurrentIndex] = useState(0);
    21→  const [studyTime, setStudyTime] = useState(120); // Default 2 minutes
    22→  const [timeRemaining, setTimeRemaining] = useState(0);
    23→  const [sessionStats, setSessionStats] = useState({
    24→    callsMade: 0,
    25→    totalDuration: 0,
    26→    conversions: 0,
    27→    startedAt: null,
    28→  });
    29→
    30→  const timerRef = useRef(null);
    31→  const navigateRef = useRef(null);
    32→
    33→  // Store navigate function from component that uses this
    34→  const setNavigate = useCallback((nav) => {
    35→    navigateRef.current = nav;
    36→  }, []);
    37→
    38→  // Start power dialer session
    39→  const startSession = useCallback((leads, startIndex = 0) => {
    40→    if (!leads || leads.length === 0) return;
    41→
    42→    setQueue(leads);
    43→    setCurrentIndex(startIndex);
    44→    setIsActive(true);
    45→    setIsPaused(false);
    46→    setSessionStats({
    47→      callsMade: 0,
    48→      totalDuration: 0,
    49→      conversions: 0,
    50→      startedAt: new Date().toISOString(),
    51→    });
    52→
    53→    // Navigate to first lead's prep page
    54→    const firstLead = leads[startIndex];
    55→    if (firstLead && navigateRef.current) {
    56→      navigateRef.current(`/call-prep/${firstLead.id}`);
    57→    }
    58→  }, []);
    59→
    60→  // Start the study countdown timer
    61→  const startStudyTimer = useCallback(() => {
    62→    if (!isActive || isPaused) return;
    63→
    64→    setTimeRemaining(studyTime);
    65→
    66→    // Clear any existing timer
    67→    if (timerRef.current) {
    68→      clearInterval(timerRef.current);
    69→    }
    70→
    71→    timerRef.current = setInterval(() => {
    72→      setTimeRemaining(prev => {
    73→        if (prev <= 1) {
    74→          clearInterval(timerRef.current);
    75→          timerRef.current = null;
    76→          return 0;
    77→        }
    78→        return prev - 1;
    79→      });
    80→    }, 1000);
    81→  }, [isActive, isPaused, studyTime]);
    82→
    83→  // Pause/resume the timer
    84→  const togglePause = useCallback(() => {
    85→    setIsPaused(prev => {
    86→      if (prev) {
    87→        // Resuming - restart timer with remaining time
    88→        if (timeRemaining > 0 && timerRef.current === null) {
    89→          timerRef.current = setInterval(() => {
    90→            setTimeRemaining(p => {
    91→              if (p <= 1) {
    92→                clearInterval(timerRef.current);
    93→                timerRef.current = null;
    94→                return 0;
    95→              }
    96→              return p - 1;
    97→            });
    98→          }, 1000);
    99→        }
   100→      } else {

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


Now let me check the main entry points and how calling is integrated:


     1→import { useState, useEffect, useRef } from 'react';
     2→import { motion, AnimatePresence } from 'framer-motion';
     3→import { Phone, PhoneOff, Mic, MicOff, Volume2, User, Radio, Voicemail, X, Check, Loader2 } from 'lucide-react';
     4→import { Device } from '@twilio/voice-sdk';
     5→import { useCall } from '../context/CallContext';
     6→import { voiceApi, voicemailApi } from '../api/client';
     7→import CallSalesFunnel from './CallSalesFunnel';
     8→// Note: Can't use Tooltip here - TwilioCall renders in CallProvider which is outside TooltipProviders
     9→
    10→export default function TwilioCall({ lead, inboundCall, isInbound, onClose, onCallEnd, onCallConnected }) {
    11→  const { isTranscribing } = useCall();
    12→  const [status, setStatus] = useState(isInbound ? 'in-progress' : 'initializing'); // initializing, ready, connecting, ringing, in-progress, ended
    13→  const [duration, setDuration] = useState(0);
    14→  const [isMuted, setIsMuted] = useState(false);
    15→  const [error, setError] = useState(null);
    16→
    17→  // Voicemail drop state
    18→  const [showVMPicker, setShowVMPicker] = useState(false);
    19→  const [vmRecordings, setVmRecordings] = useState([]);
    20→  const [vmLoading, setVmLoading] = useState(false);
    21→  const [vmDropping, setVmDropping] = useState(false);
    22→  const [vmDropped, setVmDropped] = useState(false);
    23→
    24→  const deviceRef = useRef(null);
    25→  const callRef = useRef(null);
    26→  const timerRef = useRef(null);
    27→  const startTimeRef = useRef(null);
    28→  const callEndedRef = useRef(false);
    29→
    30→  useEffect(() => {
    31→    if (isInbound && inboundCall) {
    32→      // Inbound call - attach to existing call
    33→      attachToInboundCall(inboundCall);
    34→    } else {
    35→      // Outbound call - initialize device and make call
    36→      initializeDevice();
    37→    }
    38→
    39→    return () => {
    40→      cleanup();
    41→    };
    42→  }, []);
    43→
    44→  // Attach to an existing inbound call
    45→  const attachToInboundCall = (call) => {
    46→    console.log('[TwilioCall] Attaching to inbound call');
    47→    callRef.current = call;
    48→    setStatus('in-progress');
    49→    startTimeRef.current = Date.now();
    50→    startTimer();
    51→
    52→    // Get CallSid and trigger callback
    53→    const callSid = call.parameters?.CallSid || null;
    54→    console.log('[TwilioCall] Inbound call CallSid:', callSid);
    55→    onCallConnected?.(callSid);
    56→
    57→    // Handle disconnect
    58→    call.on('disconnect', () => {
    59→      const finalDuration = startTimeRef.current
    60→        ? Math.floor((Date.now() - startTimeRef.current) / 1000)
    61→        : 0;
    62→      console.log('[TwilioCall] Inbound call disconnected, duration:', finalDuration);
    63→      setStatus('ended');
    64→      stopTimer();
    65→      if (!callEndedRef.current) {
    66→        callEndedRef.current = true;
    67→        setTimeout(() => {
    68→          onCallEnd?.(finalDuration);
    69→        }, 500);
    70→      }
    71→    });
    72→
    73→    call.on('error', (err) => {
    74→      console.error('[TwilioCall] Inbound call error:', err);
    75→      setError(err.message);
    76→      setStatus('ended');
    77→      stopTimer();
    78→    });
    79→  };
    80→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→import { useState, useEffect, useRef, useCallback } from 'react';
     2→import { ChevronDown } from 'lucide-react';
     3→import { aiApi, scriptsApi } from '../api/client';
     4→import ContractorFoundModal from './ContractorFoundModal';
     5→import EliteCallUI from './EliteCallUI';
     6→import { useCall } from '../context/CallContext';
     7→
     8→// Keywords that trigger script stage advancement
     9→const SCRIPT_STAGE_KEYWORDS = {
    10→  discovery: ['tell me about', 'what do you', 'how many', 'when did', 'how long', 'what\'s your', 'who is'],
    11→  value_prop: ['we can help', 'our platform', 'rateright', 'workers available', 'save you', 'percentage'],
    12→  objections: ['price', 'cost', 'expensive', 'not sure', 'competitor', 'think about'],
    13→  close: ['next step', 'sign up', 'get started', 'send you', 'email', 'ready to'],
    14→};
    15→
    16→// Keywords that might indicate objections
    17→const OBJECTION_KEYWORDS = [
    18→  'expensive', 'cost', 'price', 'too much', 'can\'t afford',
    19→  'not sure', 'think about', 'need to check', 'competitor',
    20→  'already have', 'not interested', 'busy', 'later', 'call back',
    21→  'happy with', 'current provider', 'contract', 'locked in'
    22→];
    23→
    24→// Patterns that indicate contractor intel from workers
    25→const CONTRACTOR_PATTERNS = [
    26→  /works?\s+for\s+(.+?)\s+on\s+(?:the\s+)?(.+?)(?:\s+project|\s+site|\s+job|[.!?]|$)/i,
    27→  /(?:i|we)\s+work\s+for\s+(.+?)(?:\s+on|\s+at|[.!?]|$)/i,
    28→  /(?:my|the)\s+boss\s+(?:is\s+)?(?:at\s+)?(.+?)(?:[.!?]|$)/i,
    29→  /working\s+(?:for|with)\s+(.+?)(?:\s+on|\s+at|[.!?]|$)/i,
    30→];
    31→
    32→// Command prefixes for in-call voice commands
    33→const COMMAND_PREFIXES = [
    34→  /^hey system[,:]?\s*(.+)/i,
    35→  /^command[,:]?\s*(.+)/i,
    36→  /^note[,:]?\s*(.+)/i,
    37→  /^mark\s+(hot|warm|cold|engaged|converted|lost)/i,
    38→  /^callback\s+(.+)/i,
    39→  /^skip$/i,
    40→];
    41→
    42→// Keywords that indicate buying signals (positive intent to purchase)
    43→const BUYING_SIGNAL_KEYWORDS = [
    44→  // High intent - ready to buy
    45→  'when can you start', 'when can we start', 'how do i sign up', 'how do we sign up',
    46→  'what\'s the next step', 'what is the next step', 'next steps',
    47→  'let\'s do it', 'lets do it', 'i\'m ready', 'we\'re ready', 'sign me up', 'sign us up',
    48→  'send me the details', 'send the details', 'send me a quote',
    49→  'need workers asap', 'need someone today', 'starting monday', 'starting next week',
    50→  // Medium intent - actively considering
    51→  'that\'s reasonable', 'sounds fair', 'cheaper than', 'better than',
    52→  'have a job starting', 'got a project', 'need workers',
    53→  'how many workers', 'do you have electricians', 'do you have carpenters',
    54→];
    55→
    56→// Get buying signal suggestion based on detected phrase
    57→const BUYING_SIGNAL_RESPONSES = {
    58→  'next step': {
    59→    type: 'closing',
    60→    response: "Great question! I can get you set up right now. I just need your email to send the signup link.",
    61→    followUp: "What's the best email to send that to?",
    62→    keyPoint: "They're ready to move forward!",
    63→  },
    64→  'sign': {
    65→    type: 'closing',
    66→    response: "Perfect! Let me get you started. The signup takes about 2 minutes.",
    67→    followUp: "Do you have your ABN handy?",
    68→    keyPoint: "Close the deal now!",
    69→  },
    70→  'when can': {
    71→    type: 'urgency',
    72→    response: "We can have workers available within 24-48 hours once you're set up.",
    73→    followUp: "What trade do you need first?",
    74→    keyPoint: "Match their urgency!",
    75→  },
    76→  'need workers': {
    77→    type: 'qualification',
    78→    response: "We've got a great pool of pre-vetted workers. Most contractors find someone within a day.",
    79→    followUp: "How many workers are you looking for?",
    80→    keyPoint: "Qualify the opportunity!",
    81→  },
    82→  'cheaper': {
    83→    type: 'value',
    84→    response: "Yes, at 9.9% we're typically 30-50% less than traditional agencies, with no lock-in contracts.",
    85→    followUp: "Would you like me to calculate the savings based on your team size?",
    86→    keyPoint: "Reinforce the value!",
    87→  },
    88→  default: {
    89→    type: 'interest',
    90→    response: "I can hear you're interested! Let me show you how easy it is to get started.",
    91→    followUp: "What's your biggest pain point with finding workers right now?",
    92→    keyPoint: "Keep the momentum!",
    93→  },
    94→};
    95→
    96→export default function LiveCopilot({ lead, onClose, onEnd }) {
    97→  // Get context first so we can use preloadedIntel for initial state
    98→  const { scriptSessionId, setScriptSessionId, preloadedIntel } = useCall();
    99→
   100→  const [isListening, setIsListening] = useState(false);

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


grep -r "generateVoiceToken\|device.connect\|Twilio.Device" /home/ccuser/rateright-growth/admin/src --include="*.jsx" --include="*.tsx" -A 2 -B 2 | head -50


/home/ccuser/rateright-growth/admin/src/components/TwilioCall.jsx-      const { token } = await voiceApi.getToken();
/home/ccuser/rateright-growth/admin/src/components/TwilioCall.jsx-
/home/ccuser/rateright-growth/admin/src/components/TwilioCall.jsx:      // Initialize Twilio Device with US region edge
/home/ccuser/rateright-growth/admin/src/components/TwilioCall.jsx-      const device = new Device(token, {
/home/ccuser/rateright-growth/admin/src/components/TwilioCall.jsx-        logLevel: 'debug',
--
/home/ccuser/rateright-growth/admin/src/components/TwilioCall.jsx-
/home/ccuser/rateright-growth/admin/src/components/TwilioCall.jsx-      device.on('error', (err) => {
/home/ccuser/rateright-growth/admin/src/components/TwilioCall.jsx:        console.error('Twilio Device error:', err);
/home/ccuser/rateright-growth/admin/src/components/TwilioCall.jsx-        setError(err.message);
/home/ccuser/rateright-growth/admin/src/components/TwilioCall.jsx-        setStatus('ended');


Perfect! Now let me get the git log for recent changes to these files:


2af5563 Docs: Add SMS duplicate prevention deployment note
b4cd1e2 Enable fully autonomous CC with --dangerously-skip-permissions
57d2eab Feature: Telegram commands for Clawdbot CC control
fa36a66 Docs: Update SYSTEM-INTEL with documentation consolidation
e990a6a Docs: Add Clawdbot scripts and SYSTEM-REFERENCE.md
1956156 Fix: Mount internal leads router before auth middleware
cba25e8 Fix: Move /internal/hot route before /:id
ba2bbe0 Add internal hot leads endpoint + sync script
265bb7c Add lesson: agents must share knowledge base
6e434e9 Update cc-read-lessons.sh to read from both sources
f9b2807 Add lesson capture reminder to cc-read-lessons.sh
73627f5 Add automatic lesson capture to self-improvement loop
48933ee Add meta-lesson: documenting isn't enough
993282e Add MCP database IDs to CLAUDE.md
4f390a5 Add Notion MCP ID gotcha to LESSONS.md
9d35f6e Fix cc-read-lessons.sh heredoc stdin conflict
f4469e2 Add CC Self-Improvement Loop to CLAUDE.md
ded66ae Add /api/health endpoint alias for Clawdbot monitoring
57151b7 [Bug Fix] Re-enable real-time call transcription (P1)
03d6a77 Fix duplicate SMS: 3-layer protection against multiple sequence enrollment


git log --oneline -n 5 --format="%h %s" -- admin/src/components/EliteCallUI.jsx admin/src/components/TwilioCall.jsx admin/src/components/LiveCopilot.jsx src/routes/voice.js


57151b7 [Bug Fix] Re-enable real-time call transcription (P1)
1c87211 fix: Move recording_url to metadata for copilot_sessions
5cb8779 feat: Add full intel extraction to post-call transcription
487f654 fix: Improve Deepgram transcription reliability
713e750 fix: Move backfill endpoint to webhook router with API key auth


commit 5cb87797a0438941e83d05284efa5fa55f0f3e48
Author: Rivet <rivet@rateright.com.au>
Date:   Thu Jan 29 10:36:46 2026 +0000

    feat: Add full intel extraction to post-call transcription

    - Import generatePostCallSummary from ai service
    - New extractFullCallIntel() function that:
      - Generates AI summary with buying signals
      - Updates lead score based on AI recommendation
      - Updates lead status if recommended
      - Saves buying_signals to lead metadata
    - Replaces basic extractInboundIntel with full extraction
    - Same analysis as copilot session save

 src/routes/voice.js | 99 +++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 92 insertions(+), 7 deletions(-)


Excellent! Now let me create a comprehensive summary. Let me just check one more thing about the architecture:


grep -r "app.get\|app.post\|router.get\|router.post" /home/ccuser/rateright-growth/src/routes/voice.js | head -15


router.get('/token', (req, res) => {
router.get('/lookup-caller', async (req, res) => {
router.get('/status', (req, res) => {


grep -r "setupTwilioMediaStreams\|voiceWebhooks" /home/ccuser/rateright-growth/src --include="*.js" | head -10


/home/ccuser/rateright-growth/src/index.js:const { voiceWebhooks } = require('./routes/voice');
/home/ccuser/rateright-growth/src/index.js:app.use('/api/voice', webhookLimiter, voiceWebhooks);
/home/ccuser/rateright-growth/src/services/twilioMediaSetup.js:function setupTwilioMediaStreams(server) {
/home/ccuser/rateright-growth/src/services/twilioMediaSetup.js:module.exports = { setupTwilioMediaStreams };
/home/ccuser/rateright-growth/src/routes/voice.js:const voiceWebhooks = express.Router(); // Public routes for Twilio callbacks
/home/ccuser/rateright-growth/src/routes/voice.js:voiceWebhooks.get('/twiml', (req, res) => {
/home/ccuser/rateright-growth/src/routes/voice.js:voiceWebhooks.post('/twiml', (req, res) => {
/home/ccuser/rateright-growth/src/routes/voice.js:voiceWebhooks.post('/inbound', async (req, res) => {
/home/ccuser/rateright-growth/src/routes/voice.js:voiceWebhooks.post('/inbound-status', async (req, res) => {
/home/ccuser/rateright-growth/src/routes/voice.js:voiceWebhooks.post('/voicemail', async (req, res) => {


Perfect! Now I have all the information I need. Let me create a comprehensive summary:

## CALL SYSTEM ARCHITECTURE SUMMARY

I've mapped out the complete calling/voice system in RateRight's Growth Engine. Here's what I found:

---

## KEY FILES INVOLVED

### Core Twilio Integration
- `/home/ccuser/rateright-growth/src/services/twilio.js` (172 lines)
  - Voice token generation for browser-based Twilio SDK
  - SMS sending functions
  - Webhook signature validation

- `/home/ccuser/rateright-growth/src/services/twilioMedia.js` (366 lines)
  - Real-time Media Streams WebSocket handler
  - Deepgram transcription for stereo audio (inbound + outbound channels)
  - Audio mixing and buffering logic

- `/home/ccuser/rateright-growth/src/services/twilioMediaSetup.js` (35 lines)
  - WebSocket server setup for `/api/twilio-media` endpoint

### Voice API Routes
- `/home/ccuser/rateright-growth/src/routes/voice.js` (1024 lines) - MAIN VOICE ROUTER
  - 16+ endpoints for calling and recording management
  - Outbound call TwiML generation
  - Inbound call handling with lead scoring
  - Voicemail recording and transcription
  - Call recording status webhooks
  - Recording transcription via Deepgram

- `/home/ccuser/rateright-growth/src/routes/calls.js` (646 lines) - CALL LOGGING & OUTCOMES
  - Call outcome logging
  - Communication record creation
  - Lead status/score updates
  - XP awards, callback creation
  - Auto-research and AI call scoring
  - Call history retrieval
  - Recording playback streaming

- `/home/ccuser/rateright-growth/src/routes/voicemail.js` - Voicemail handling

### Frontend Components
- `/home/ccuser/rateright-growth/admin/src/components/EliteCallUI.jsx` (554 lines) - MAIN CALL INTERFACE
  - Real-time call UI during active call
  - Call timer, mute button, end call button
  - Script stage progression (opener → discovery → value props → objections → close)
  - Ice breakers section (conversation starters + news talking points)
  - Dynamic context cards for objections/buying signals
  - Intel modal for lead background
  - AI suggestions in real-time

- `/home/ccuser/rateright-growth/admin/src/components/TwilioCall.jsx` - Low-level Twilio SDK wrapper
  - Device initialization
  - Call connection/disconnection
  - Muting, voicemail drop
  - Timer management

- `/home/ccuser/rateright-growth/admin/src/components/LiveCopilot.jsx` (250+ lines)
  - Orchestrates TwilioCall + EliteCallUI
  - Manages script stage progression
  - Detects buying signals and objections
  - Generates AI suggestions
  - Handles voice command parsing
  - Manages contractor intel extraction

- `/home/ccuser/rateright-growth/admin/src/context/CallContext.jsx` - Call state management
- `/home/ccuser/rateright-growth/admin/src/context/PowerDialerContext.jsx` - Multi-lead queuing for power dialing

---

## CALL FLOW: HOW A CALL GETS INITIATED, CONNECTED, AND COMPLETED

### OUTBOUND CALL FLOW:
```
1. INITIATION (Frontend)
   └─ User clicks "Call" on lead
      └─ LiveCopilot component mounts
         └─ TwilioCall.initializeDevice() called
            └─ GET /api/voice/token → JWT token from backend
               └─ Device = new Device(token)
                  └─ device.on('ready', ...) fires

2. CONNECTION
   └─ User clicks "Dial" in EliteCallUI
      └─ Device.connect({ To: leadPhone, leadId, CallSid })
         └─ Twilio initiates outbound call
            └─ POST /api/voice/twiml (Twilio → Backend)
               ├─ TwiML response with dial() instruction
               ├─ Caches leadId → CallSid mapping (for recording linkage)
               └─ Record: 'record-from-answer-dual' (both sides)
                  └─ recordingStatusCallback: /api/voice/recording-status

3. RINGING & ANSWER
   └─ Lead phone rings
      └─ Lead answers
         └─ device.on('connect', call) fires (frontend)
            ├─ TwilioCall attaches to call
            ├─ Duration timer starts
            ├─ LiveCopilot starts listening for keywords
            └─ EliteCallUI displays script stage prompts

4. DURING CALL
   └─ Audio streams both ways
      └─ POST /api/voice/recording-status (when recording complete)
         ├─ transcribeRecording(callSid, recordingUrl) async
         ├─ Fetch audio from Twilio with Basic Auth
         └─ Send to Deepgram for transcription
            └─ Deepgram returns full transcript
               ├─ Find or create communications record
               └─ Run extractFullCallIntel() for AI analysis
                  ├─ generatePostCallSummary()
                  ├─ Extract buying signals
                  ├─ Update lead score
                  └─ Save to lead metadata

5. END CALL
   └─ User clicks "End" or lead hangs up
      └─ device.on('disconnect', call) fires
         ├─ Stop timer
         ├─ Capture final duration
         └─ Show POST-CALL SUMMARY SHEET
            ├─ Call outcome dropdown
            ├─ Auto-filled notes (if transcribed)
            └─ User selects outcome → POST /api/calls/log

6. POST-CALL LOGGING
   └─ POST /api/calls/log
      ├─ Create communications record (call_outbound)
      ├─ Link recording transcript if orphaned
      ├─ Update lead status/score
      ├─ Create callback if follow-up scheduled
      ├─ Award XP to user
      ├─ Run auto-research on transcript
      ├─ Run AI call scoring
      ├─ Update lead dossier
      ├─ Auto-enroll in SMS sequence (if no-answer)
      └─ Fire battleground/challenge updates
```

### INBOUND CALL FLOW:
```
1. INCOMING CALL
   └─ Caller dials RateRight number (Twilio)
      └─ Twilio webhooks POST /api/voice/inbound
         ├─ Normalize caller phone number
         ├─ Look up lead in database (4 phone formats)
         ├─ Award scoring: +20 points for inbound call
         ├─ Track inbound_call_count in metadata
         ├─ Mark as high_intent if 3+ calls
         ├─ Log to communications table
         ├─ Cache leadId → CallSid
         └─ Return TwiML dial() to Twilio Client (browser)

2. BROWSER RINGS
   └─ Backend returns TwiML with dial.client('agent')
      ├─ Browser client receives inbound ring
      └─ IncomingCallHandler modal appears
         ├─ Shows caller name + lead details
         ├─ Shows hot lead indicator (score ≥ 80)
         └─ User can ACCEPT or DECLINE

3. ACCEPT CALL
   └─ User clicks "Accept"
      └─ TwilioCall.attachToInboundCall()
         ├─ Call connects immediately
         ├─ EliteCallUI/LiveCopilot mounts
         └─ Call duration timer starts

4. DECLINE / MISS
   └─ Call goes to voicemail
      └─ POST /api/voice/inbound-status (Twilio)
         ├─ Return voicemail TwiML greeting
         ├─ Create urgent callback (if hot lead)
         ├─ Auto-send SMS to hot lead: "Sorry I missed your call..."
         └─ Send Slack alert

5. VOICEMAIL LEFT
   └─ POST /api/voice/voicemail-complete (recording saved)
      └─ Immediately save to communications (don't wait for transcription)
         ├─ Save RecordingUrl to metadata
         ├─ Set transcription_pending flag
         └─ Send Slack notification

6. VOICEMAIL TRANSCRIPTION
   └─ Twilio transcribes async → POST /api/voice/voicemail
      ├─ Update communications with transcription
      └─ extractInboundIntel() for buying signals/intel
```

---

## ARCHITECTURE DIAGRAM

```
┌─────────────────────────────────────────────────────────────┐
│                    BROWSER / FRONTEND                       │
├─────────────────────────────────────────────────────────────┤
│  LiveCopilot Component                                      │
│  ├─ TwilioCall (Twilio SDK wrapper)                        │
│  │  └─ Device.connect() / Device.listen()                  │
│  ├─ EliteCallUI (Call UI during active call)               │
│  │  ├─ Script stages                                        │
│  │  ├─ Ice breakers                                         │
│  │  └─ Context cards (suggestions)                          │
│  └─ CallContext (state management)                         │
└─────────────────────────────────────────────────────────────┘
                            │
         (WebRTC / TLS)     │  (Twilio SDK over HTTPS)
                            ▼
┌─────────────────────────────────────────────────────────────┐
│               TWILIO CLOUD                                   │
├─────────────────────────────────────────────────────────────┤
│  Outbound: Device.connect() → calls lead phone              │
│  Inbound:  Lead calls RateRight number                      │
│  Recording: Captures both sides (mulaw, 8kHz)               │
└─────────────────────────────────────────────────────────────┘
         │                              │
         │ (webhooks)                   │ (webhooks)
         ▼                              ▼
┌──────────────────┐          ┌──────────────────┐
│  /api/voice/twiml│          │ /api/voice/inbound│
│  (Outbound TwiML)│          │ (Inbound handler) │
└──────────────────┘          └──────────────────┘
         │                              │
         ▼                              ▼
    Recording callback:          Lead lookup:
    /api/voice/recording-status  - Phone normalization
         │                        - 4 format search
         │                        - Score update (+20)
         └─────┬──────────────────┬───────────────┐
               ▼                  ▼               ▼
         ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
         │ Deepgram     │  │ Supabase DB  │  │ Slack Alert  │
         │ (transcribe) │  │ (save comm)  │  │ (inbound)    │
         └──────────────┘  └──────────────┘  └──────────────┘
                │
                ▼ (transcript)
         ┌──────────────────────────────┐
         │ extractFullCallIntel()       │
         │ ├─ AI summary generation    │
         │ ├─ Score adjustment         │
         │ ├─ Buying signal extraction │
         │ └─ Metadata save            │
         └──────────────────────────────┘
                │
         POST /api/calls/log
         (call outcome, notes)
                │
                ▼
         ┌──────────────────────────────┐
         │ Call Logging + Post-Call     │
         │ ├─ XP award                  │
         │ ├─ Auto-research             │
         │ ├─ AI call scoring           │
         │ ├─ Dossier update            │
         │ ├─ Sequence auto-enroll      │
         │ └─ Challenge updates         │
         └──────────────────────────────┘
```

---

## RECENT CHANGES (Last 5 commits to calling system)

| Commit | Date | Change |
|--------|------|--------|
| `57151b7` | Jan 28 | [Bug Fix] Re-enable real-time call transcription (P1) |
| `5cb8779` | Jan 29 | feat: Add full intel extraction to post-call transcription |
| `487f654` | Jan 28 | fix: Improve Deepgram transcription reliability |
| `1c87211` | Jan 27 | fix: Move recording_url to metadata for copilot_sessions |
| `713e750` | Jan 27 | fix: Move backfill endpoint to webhook router with API key auth |

**Latest major feature (5cb8779):**
- Imports `generatePostCallSummary` from AI service
- New `extractFullCallIntel()` function that:
  - Generates AI summary with buying signals
  - Updates lead score based on recommendation
  - Updates lead status if recommended
  - Saves buying_signals array to lead metadata
  - Replaces basic inbound intel with full extraction

---

## CRITICAL INTEGRATION POINTS

1. **Voice Token Generation** (`/api/voice/token`)
   - Frontend requests JWT token
   - Backend generates with 1-hour TTL
   - Used to authenticate Twilio Device SDK

2. **TwiML Endpoints** (Public, no auth)
   - `/api/voice/twiml` - Outbound dial instructions
   - `/api/voice/inbound` - Incoming call routing
   - `/api/voice/inbound-status` - Voicemail fallback
   - `/api/voice/voicemail` - Transcription callback
   - `/api/voice/voicemail-complete` - Recording save
   - `/api/voice/recording-status` - Recording completion trigger

3. **Call Lead Caching**
   - `callLeadCache` (in-memory Map)
   - Stores callSid → leadId mapping
   - TTL: 1 hour
   - Used to link recordings to leads before call is logged

4. **Deepgram Integration**
   - Receives mulaw audio (8kHz, stereo)
   - Returns transcript with timestamps
   - Used for both real-time (Media Streams) and post-call recordings

5. **Database Linkage**
   - `communications` table (call records)
   - `lead` metadata (buying signals, inbound_call_count)
   - `copilot_sessions` (orphaned transcripts)
   - `callbacks` (missed call follow-ups)

---

## SUMMARY

The calling system is **fully integrated** from browser to Twilio cloud to backend transcription. The flow is:

1. **Initiation**: Get token → Initialize Device → Connect call
2. **Connection**: TwiML routing → Browser ring → Accept
3. **Recording**: Twilio captures dual-track audio
4. **Transcription**: Deepgram transcribes async
5. **Intel**: AI extracts buying signals, scores, updates lead
6. **Logging**: User logs outcome → Triggers downstream systems (XP, sequences, etc.)

All calling-related files are recent (within last week) with active bug fixes on P1 transcription issues.