From 38aacc374ed029084da6e91769b23b8e0be7e08b Mon Sep 17 00:00:00 2001 From: saridsa2 Date: Sun, 12 Apr 2026 14:06:50 +0530 Subject: [PATCH] docs: barge/whisper/listen design spec SIP-only supervisor barge with DTMF mode switching (4=listen, 5=whisper, 6=barge). Live monitor split layout with context panel. Agent indicator on whisper/barge only. Auto-disconnect on call end. Dynamic SIP from Ozonetel pool. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../2026-04-12-barge-whisper-listen-design.md | 385 ++++++++++++++++++ 1 file changed, 385 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-12-barge-whisper-listen-design.md diff --git a/docs/superpowers/specs/2026-04-12-barge-whisper-listen-design.md b/docs/superpowers/specs/2026-04-12-barge-whisper-listen-design.md new file mode 100644 index 0000000..2cea34e --- /dev/null +++ b/docs/superpowers/specs/2026-04-12-barge-whisper-listen-design.md @@ -0,0 +1,385 @@ +# Supervisor Barge / Whisper / Listen — Design Spec + +**Date:** 2026-04-12 +**Branch:** `feature/barge-whisper` +**Prereq:** QA validates barge flow in Ozonetel's own admin UI first + +--- + +## Overview + +Enable supervisors to monitor and intervene in live agent calls directly from Helix Engage's live monitor. Three modes: **Listen** (silent), **Whisper** (agent hears supervisor, patient doesn't), **Barge** (both hear supervisor). Supervisor connects via SIP WebRTC in the browser. Mode switching via DTMF tones. + +## Design Decisions + +| Decision | Choice | Rationale | +|----------|--------|-----------| +| Connection method | SIP only (PSTN later) | Supervisors are already on browser with headset | +| Agent indicator | Whisper/barge only (listen is silent) | Spec says show indicator; listen should be undetectable | +| SIP number | Dynamic from Ozonetel pool (apiId 139) | No need to pre-assign per supervisor. 3 SIP IDs available. | +| Barge UI location | Live monitor + context panel + barge controls | Supervisor needs call context to intervene effectively | +| Access control | Any admin can barge any agent | Flat RBAC, no team hierarchy | +| Call end behavior | Auto-disconnect supervisor | No orphaned sessions | + +## Architecture + +``` +┌─────────────────────────────────────────────────────────┐ +│ Supervisor Browser │ +│ │ +│ ┌──────────────┐ ┌────────────────────────────────┐ │ +│ │ Live Monitor │ │ Context Panel + Barge Controls│ │ +│ │ │ │ │ │ +│ │ Agent list │ │ Patient summary / AI insight │ │ +│ │ Active calls │──│ Appointments / Recent calls │ │ +│ │ Click → │ │ ─────────────────────────────│ │ +│ │ │ │ [Connect] │ │ +│ │ │ │ [Listen] [Whisper] [Barge] │ │ +│ │ │ │ [Hang up] │ │ +│ └──────────────┘ └────────────────────────────────┘ │ +│ │ │ │ +│ │ poll /active-calls │ SIP WebRTC (kSip) │ +│ │ every 5s │ DTMF 4/5/6 │ +└─────────┼───────────────────────┼────────────────────────┘ + │ │ + ▼ ▼ +┌─────────────────────┐ ┌──────────────────────────┐ +│ Sidecar │ │ Ozonetel SIP Gateway │ +│ │ │ (blr-pub-rtc4.ozonetel) │ +│ POST /api/supervisor│ │ │ +│ /barge │ │ SIP INVITE → supervisor │ +│ /barge-mode │ │ audio mixing │ +│ │ │ DTMF routing │ +│ → Ozonetel admin API│ └──────────────────────────┘ +│ dashboardApi │ +│ apiId 63, 139 │ +└─────────────────────┘ + │ + ▼ +┌─────────────────────┐ +│ Ozonetel Cloud │ +│ api.cloudagent. │ +│ ozonetel.com │ +│ │ +│ /dashboardApi/ │ +│ monitor/api │ +│ apiId 63 → barge │ +│ apiId 139 → SIP# │ +│ /auth/login → JWT │ +└─────────────────────┘ +``` + +## Components + +### 1. Sidecar — Ozonetel Admin Auth Service + +**New file:** `src/ozonetel/ozonetel-admin-auth.service.ts` + +Manages a persistent Ozonetel admin session for supervisor APIs. Credentials from TelephonyConfig. + +**Config extension** (`telephony.defaults.ts`): +```typescript +ozonetel: { + // ...existing fields + adminUsername: string; // NEW + adminPassword: string; // NEW +}; +``` + +**Flow:** +1. On startup, read `adminUsername` + `adminPassword` from TelephonyConfig +2. `GET /api/auth/public-key` → `{ publicKey, keyId }` +3. RSA-encrypt credentials using `jsencrypt` +4. `POST /auth/login` → JWT token +5. Cache token in memory, decode expiry via `jwt-decode` +6. Auto-refresh before expiry +7. Expose `getAuthHeaders()` for other services + +**Auth headers for all admin API calls:** +```typescript +{ + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${jwt}`, + 'userId': userId, + 'userName': userName, + 'isSuperAdmin': 'true', + 'dAccessType': 'false' +} +``` + +### 2. Sidecar — Supervisor Barge Endpoints + +**New file:** `src/supervisor/supervisor-barge.controller.ts` + +Three endpoints proxying to Ozonetel admin API: + +#### `POST /api/supervisor/barge` + +Initiates barge-in on an active call. + +```typescript +// Request +{ ucid: string, agentNumber: string } + +// Sidecar calls: +POST https://api.cloudagent.ozonetel.com/dashboardApi/monitor/api +{ + apiId: 63, + ucid: "", + action: "CALL_BARGEIN", + isSip: true, + phoneno: "", + agentNumber: "", + cbURL: "" +} + +// Response +{ status: "success", sipNumber: "19810", sipPassword: "19810", sipDomain: "blr-sbc1.ozonetel.com", sipPort: "442" } +``` + +Before calling barge, fetches an available SIP number: + +#### `GET /api/supervisor/barge/sip-credentials` + +```typescript +// Sidecar calls: +POST https://api.cloudagent.ozonetel.com/ca-admin-Api/CloudAgentAPI/endpoint/sipnumber/sipSubscribe +{ apiId: 139, sipURL: "" } + +// Response +{ sip_number: "19810", password: "19810", pop_location: "blr-sbc1.ozonetel.com" } +``` + +#### `POST /api/supervisor/barge/end` + +Cleanup: disconnect SIP, clear Redis tracking. + +```typescript +// Request +{ agentId: string, sipId: string } + +// Sidecar calls: +POST https://api.cloudagent.ozonetel.com/dashboardApi/monitor/api +{ apiId: 158, Action: "delete", AgentId: "", Sip: "" } +``` + +### 3. Frontend — Supervisor SIP Client + +**New file:** `src/lib/supervisor-sip-client.ts` + +Lightweight SIP client for supervisor barge sessions. Modeled on Ozonetel's `kSip.tsx` — separate from the agent's `sip-client.ts`. + +```typescript +type SupervisorSipClient = { + init(domain: string, port: string, number: string, password: string): void; + register(): void; + isRegistered(): boolean; + isCallActive(): boolean; + sendDTMF(digit: string): void; // "4"=listen, "5"=whisper, "6"=barge + hangup(): void; + close(): void; + on(event: string, callback: Function): void; + off(event: string, callback: Function): void; +}; +``` + +**Events emitted:** +- `registered` — SIP registration successful +- `registrationFailed` — SIP registration error +- `callReceived` — incoming call from Ozonetel (auto-answer) +- `callConnected` — barge session active +- `callEnded` — call terminated (agent hung up or supervisor hung up) + +**Audio:** Remote audio plays through a hidden `