The agent console is the workspace for human agents handling escalated/handoff calls. It is gated by the ADK feature flag and the agent/supervisor roles, and is served under /agent-console by src/layouts/AgentLayout.tsx. The console persists across page refresh and is built from several pieces.

Surfaces

SurfaceFileRole
Layout + heartbeatsrc/layouts/AgentLayout.tsxshell, nav, heartbeat, call recovery
Status togglesrc/components/agent-desk/AgentStatusToggle.tsxavailable/away/busy selector
Queue ticker + drawerAgentLayout.tsx (QueueTicker)scrolling waiting list
Queue cardsrc/components/agent-desk/QueueCard.tsxaccept/decline/reject a call
Incoming modalsrc/components/agent-desk/IncomingCallModal.tsxfull-screen accept prompt
Waiting homesrc/pages/AgentQueue.tsxidle “waiting for call” screen
Active callsrc/pages/AgentCall.tsx + CallPanel.tsxlive LiveKit call + wrap-up

Status toggle

AgentStatusToggle reads/writes status via getAgentStatus / setAgentStatus. Agents can select Available, Away, or Busy. The control locks (read-only) while in a non-selectable state — on_call, wrap_up, offline, or disconnected — and shows a colored dot per status. On a failed update it re-reads the server status and surfaces an error.

Heartbeat and call recovery

AgentLayout calls heartbeatAgent every 15 seconds and updates the agent status from the response. On pagehide/unmount it marks the agent offline. When the status is active (on_call, wrap_up, disconnected) and the agent is not already on the call page, the layout recovers the current call via fetchCurrentCall and redirects to /agent-console/call, so a refresh or stray navigation cannot abandon a live call or skip a pending wrap-up.

Queue ticker, drawer, and incoming modal

QueueTicker polls fetchQueue every 3 seconds. It shows a “N waiting” label and a scrolling ticker of callers. Clicking opens a drawer of QueueCards. The oldest waiting call is treated as primary and — when the agent is available and not on the call page — pops the IncomingCallModal. Per-call actions (shared by card and modal):
ActionAPIEffect
AcceptacceptCallstores call in sessionStorage, navigates to /agent-console/call
DeclinedeclineCallremoves from this agent’s queue (may terminate if no agents left)
RejectrejectCallends the call with a reason
The incoming modal renders rich handoff context: customer name, escalation reason, summary, lead classification, transfer tags, flags, ordered customer fields, and a variables preview.

Active call and wrap-up

AgentCall restores call state from location.state or sessionStorage (and recovers from fetchCurrentCall on refresh). It captures mic audio, mixes in remote tracks, and records the call locally. CallPanel joins the LiveKit room, lists connected participants (filtering out service identities like hold-worker, voxcore-bot, ai-copilot), and offers mute and end-call controls. When Agent Assist is enabled it shows the AgentAssistPanel (live suggestions + transcript over LiveKit data messages). Wrap-up is enforced: when the call ends (agent hangup or customer disconnect, detected via LiveKit callbacks with a 3-second state-poll fallback), a modal requires a Disposition (Resolved, Transferred, Escalated, No Resolution, Callback Scheduled) and Comments before submit. Finalize tears down via endCall, then best-effort uploads the recording via uploadRecording.

Agent history & analytics

An agent’s handled calls and personal stats.

Supervisor dashboard

Listen, barge in, and force-end.

Agent Assist

Phrase-triggered suggestion moments.

Agent Desk (VoxBridge)

Queue, rooms, and supervisor controls server-side.