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
| Surface | File | Role |
|---|---|---|
| Layout + heartbeat | src/layouts/AgentLayout.tsx | shell, nav, heartbeat, call recovery |
| Status toggle | src/components/agent-desk/AgentStatusToggle.tsx | available/away/busy selector |
| Queue ticker + drawer | AgentLayout.tsx (QueueTicker) | scrolling waiting list |
| Queue card | src/components/agent-desk/QueueCard.tsx | accept/decline/reject a call |
| Incoming modal | src/components/agent-desk/IncomingCallModal.tsx | full-screen accept prompt |
| Waiting home | src/pages/AgentQueue.tsx | idle “waiting for call” screen |
| Active call | src/pages/AgentCall.tsx + CallPanel.tsx | live 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):
| Action | API | Effect |
|---|---|---|
| Accept | acceptCall | stores call in sessionStorage, navigates to /agent-console/call |
| Decline | declineCall | removes from this agent’s queue (may terminate if no agents left) |
| Reject | rejectCall | ends the call with a reason |
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.
Related docs
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.