# ORACLE-OS VOICE/VIDEO — Feasibility Report + Phased Build Plan

**Status:** SCOPE / PLAN ONLY — read-only investigation, no code written. Review with the owner before building.
**Date:** 2026-06-30
**Goal (owner intent):** Talk to the social bots by **voice** (and eventually **video**) directly through the Oracle-OS dashboard — a new item in the **left icon rail** that opens an in-dashboard voice/video session, as an *alternative path to Discord*. Video would be usable here (unlike Discord). Owner pictures "a separate similar file for the social bots" for this Oracle-OS voice path.

---

## 1. Verdict up front

**Voice-first MVP is very achievable and lower-risk than it looks**, because the hard part — the live conversational brain — is already built and is **not** Discord-specific. Leo's `GeminiLiveBridge` is a clean WebSocket client to Gemini Live native-audio; all the Discord coupling lives *outside* it in `leo.mjs`. We reuse the bridge unchanged and write two small adapters (browser-mic-in, browser-speaker-out).

The **one genuinely new piece of infrastructure** is a browser↔server realtime transport, because today the dashboard has **zero** streaming plumbing — no WebSocket server, no getUserMedia, no audio elements. It's a pure HTTP-polling REST app. That's the main build cost, and it's modest (the `ws` package is already a dependency).

**Video is a real extension, not a toggle.** The bridge does not send image/video frames to Gemini today (its only "image/video" code is *outbound* map tools). Adding vision means a new bridge method plus a Gemini-Live model that accepts video input plus camera-gating work. Worth planning, but clearly Phase 2+.

Recommended transport: **a WebSocket audio bridge, NOT WebRTC.** Rationale in §5.

---

## 2. How bot voice works today (the pipeline)

Traced through `bots/leo.mjs` (6,735 lines) and `shared/gemini-live-bridge.mjs` (2,493 lines).

```
                 ┌─────────────── leo.mjs (DISCORD-SPECIFIC) ───────────────┐
  Discord VC ──► receiver.subscribe(userId)  →  prism.opus.Decoder          │
  (user mic)     (48kHz stereo Opus)            (frameSize 960, 2ch, 48k)    │
                        │                                                     │
                        ▼  decoded 48k stereo PCM                             │
                 bridge.sendAudio(chunk) ───────────────────────────────────►│
                 └──────────────────────────────────────────────────────────┘
                                          │
   ┌────────────────── gemini-live-bridge.mjs (REUSABLE / Discord-free) ─────┐
   │  sendAudio(pcm): _downsample48to16() → 16k mono → WS realtimeInput.audio │
   │  { mimeType:"audio/pcm;rate=16000", data:<base64> }  ──► Gemini Live WS  │
   │                                                                          │
   │  Gemini streams back:  serverContent.modelTurn.parts[].inlineData        │
   │    (audio/pcm ~24kHz)  ──► this.onAudioChunk(base64, mimeType)           │
   │  Also: onTranscript, onInputTranscript, onTurnComplete, onToolCall       │
   └──────────────────────────────────────────────────────────────────────┬─┘
                                                                            │
                 ┌─────────────── leo.mjs (DISCORD-SPECIFIC) ───────────────┤
   Discord VC ◄── audioPlayer / paced feed  ◄── decodeAudioChunk()          │
   (Leo speaks)   createAudioResource(PassThrough, StreamType.Raw)          │
                  leoPacedFeed() upsamples 24k→48k stereo, 20ms frames      │
                 └──────────────────────────────────────────────────────────┘
```

**Key files / anchors:**
- Mic capture (Discord): `leo.mjs` ~L5125 / L6136 — `receiver.subscribe(...)`, `new prism.opus.Decoder({frameSize:960,channels:2,rate:48000})`.
- Feed to brain: `leo.mjs` ~L5205/L5395 — `liveBridge.sendAudio(chunk)`.
- The brain: `gemini-live-bridge.mjs` — `class GeminiLiveBridge` (L641), `sendAudio()` (L1359, downsample 48→16k mono), inbound audio handled at L1234 → `onAudioChunk`.
- Playback (Discord): `leo.mjs` — `bridge.onAudioChunk` handler (L3705), `leoPacedFeed()` (L636), `createAudioPlayer`/`createAudioResource`.
- Social-bot voice wrapper: `shared/gemini-live-voice.mjs` — **this is the model for the "separate similar file" the owner described.** It already reuses the bridge for Gemini/Claudey/Groq/X, but its output sink is `@discordjs/voice`.

**Discord-specific vs reusable — the important split:**

| Concern | Where | Reusable off Discord? |
|---|---|---|
| Live conversational brain (WS to Gemini, VAD signalling, tools, transcripts, key rotation, keepalive) | `gemini-live-bridge.mjs` | ✅ **100% reusable — no Discord imports in the audio path** |
| Mic **input** (Opus decode from a Discord VC) | `leo.mjs` receiver | ❌ Discord-only — replace with browser mic |
| Audio **output** (paced feed → `@discordjs/voice` player) | `leo.mjs` + `gemini-live-voice.mjs` | ❌ Discord-only — replace with browser speaker |
| Format contract: **input** = PCM16 (bridge downsamples 48k→16k mono itself); **output** = PCM16 ~24kHz base64 | bridge | ✅ this is the clean seam to tap |

The takeaway: **the bridge already is the "audio-source-agnostic" core.** It takes a PCM buffer in and hands a PCM buffer out. Discord is just one pair of adapters bolted onto that seam. Oracle-OS becomes a *second* pair of adapters.

---

## 3. Dashboard audit — existing browser↔bot audio/streaming paths

Traced `command-center-server.mjs` (:3001, 4,258 lines) and `oracle.html` (6,519 lines).

**Finding: there is none. The dashboard is a pure HTTP-polling REST app.**
- `command-center-server.mjs` is a plain Node `http` server (`import http`), serving static `oracle.html` + JSON `/api/*` endpoints. **No `WebSocketServer`, no `socket.io`, no SSE (`text/event-stream`)** anywhere. It talks to bots indirectly — writes `.env` + a restart-queue, and HTTP-proxies to the lattice engine on `ENGINE_PORT` (3334). It never holds a live connection to a running bot.
- `oracle.html` has **no `getUserMedia`, no `mediaDevices`, no `WebSocket`, no `RTCPeerConnection`, no `MediaRecorder`, no `<audio>`/`<video>` elements** at all. It fetches `/api/*` via `fetch()` on intervals.
- Good news: **`ws@^8.20.0` is already a dependency** (the bridge uses it as a Gemini client). We can stand up a `WebSocketServer` with zero new installs.

So the browser realtime transport is **net-new**, but the library is present and the pattern is well-understood.

---

## 4. Left-rail integration (low-risk, well-understood)

The rail is trivially extensible. In `oracle.html`:
- Rail markup starts at **L1894** (`<!-- icon rail -->`, `<div class="rail">`). Each item is a `<div class="rail-btn" data-view="X" onclick="setView('X')">` with an inline SVG + `<span class="rail-tip">`. Existing views: `home, transcripts, topology, nervous (=Kaiverse), learning, config`.
- Each view has a matching `<div class="view-pane" id="view-X">` (home L1966, nervous L1978, etc.).
- `setView(v)` (**L4418**) toggles `.rail-btn.active` and shows the one matching pane. It has a **hardcoded list** `['home','transcripts','topology','nervous','learning','config']` and a `VIEW_TITLES` map — both need `'voice'` added.

**To add a Voice/Video panel:** one new `.rail-btn` (icon + tip + `onclick="setView('voice')"`), one new `<div class="view-pane" id="view-voice">`, add `'voice'` to the `VIEW_TITLES` map and the show/hide array in `setView`. Surgical, ~15 lines, no risk to existing views. This is the *easiest* part of the whole feature.

---

## 5. Recommended architecture — WebSocket audio bridge (not WebRTC)

Two credible transports for browser↔server audio:

**Option A — WebSocket carrying raw PCM (RECOMMENDED).**
Browser captures mic via `getUserMedia` → an `AudioWorklet` (or `MediaRecorder`) produces PCM16 → send binary frames over a WebSocket to the server → server hands each frame to `bridge.sendAudio()`. Gemini's reply arrives on `bridge.onAudioChunk` → server relays base64 PCM frames back over the same WebSocket → browser plays them via Web Audio API (`AudioContext`, scheduled buffer sources).

- **Pros:** `ws` already installed; maps 1:1 onto the bridge's existing PCM-in/PCM-out contract; no media-server, no ICE/STUN/TURN, no SDP negotiation; trivially localhost-friendly (dashboard is `http://localhost:3001`); easy to debug (it's just framed bytes). The bridge *already* downsamples input and the current playback code *already* resamples 24k→48k, so the resampling math exists to copy.
- **Cons:** we hand-roll jitter buffering / pacing on the browser side (but `leoPacedFeed` is a working reference to port), and we own audio worklet plumbing.

**Option B — WebRTC.**
Browser `RTCPeerConnection` streams Opus to the server; server decodes to PCM for the bridge and encodes bridge output back to Opus.

- **Pros:** built-in echo cancellation / noise suppression / jitter buffer; Opus is bandwidth-efficient.
- **Cons:** heavy for a localhost tool — needs a server-side WebRTC stack (`werift`/`mediasoup`/`wrtc`), SDP + ICE negotiation, and server-side Opus transcode. Much larger surface for a single-user desktop dashboard. Overkill.

**Recommendation: Option A.** It matches the existing code's seams almost exactly and needs no new heavy dependencies. Echo cancellation is the one thing WebRTC gives for free — but for a headphones-on desktop session that's optional, and the browser's `getUserMedia({audio:{echoCancellation:true}})` constraint covers most of it client-side.

**Where does the bot process live?** Two sub-options:
- **A1 (recommended for MVP):** the `command-center-server.mjs` process (or a small sidecar it spawns) hosts the WebSocketServer **and** owns the `GeminiLiveBridge` session directly — i.e. Oracle-OS voice does **not** require the Discord Leo/social bot processes to be running. This makes it a true independent path (matches "alternative to Discord") and avoids cross-process audio piping. Needs a Gemini Live API key in the dashboard server's env (the same native-audio key the bots use).
- **A2 (later):** route to an already-running bot process over IPC so the Oracle-OS session shares the *same* bot identity/memory as its Discord self. More faithful but needs a real-time IPC audio channel between processes (none exists today) — more work. Start with A1.

**The "separate similar file":** create `shared/oracle-live-voice.mjs`, modeled on `gemini-live-voice.mjs`, but with the audio sink/source swapped from `@discordjs/voice` to the WebSocket relay. Same bridge, same personas/`resolveGeminiVoice`, same tools — just a different mouth and ears. This is exactly the structure the owner described.

---

## 6. Video — what it additionally takes (Phase 2+)

Sending the browser camera to a bot is a real extension:
1. **Bridge does not send vision today.** Its only image/video code is *outbound* map tools (satellite/street/flyover posted to Discord). Gemini Live *does* accept video input (inline JPEG frames in `realtimeInput`), but there is **no `sendVideo`/`sendImage` method** — that's new code on the bridge (small, additive).
2. **Model support.** Must confirm the selected native-audio Live model accepts image/video input (some native-audio preview models are audio-only). May need a different Live model or a periodic-frame approach (send 1 JPEG/second rather than true video).
3. **Browser side:** `getUserMedia({video:true})`, grab frames from a `<video>`/canvas, JPEG-encode, send over the same WebSocket.
4. **Camera gating (owner's explicit tie-in):** the camera must turn on **only** for an active Oracle-OS video session and turn off when it ends. This is a browser-permission + explicit-state concern — a clear "session active" flag drives `getUserMedia`/track `.stop()`. This is net-new UI/state and should be built alongside the video panel, not retrofitted.

Video is clean to layer on *after* the voice transport exists, because it rides the same WebSocket.

---

## 7. Phased build plan

Effort is rough dev-time; risk is likelihood of breaking existing behavior or hitting unknowns.

| Phase | Deliverable | New vs reuse | Effort | Risk | Dependencies |
|---|---|---|---|---|---|
| **0 — Spike** | Prove browser mic → WS → `bridge.sendAudio` → `onAudioChunk` → browser speaker end-to-end with a hardcoded persona. Throwaway HTML page, no rail. | New: WS server + tiny client. Reuse: bridge as-is. | S–M | **Med** (audio worklet + jitter buffer is the unknown) | Gemini Live key in server env; `ws` (already present) |
| **1 — Voice MVP (rail-integrated)** | `WebSocketServer` in/around `command-center-server.mjs`; `shared/oracle-live-voice.mjs` (bridge + WS sink/source); left-rail "Voice" button + `view-voice` pane with connect/disconnect, mic level, live transcript, pick-a-bot. Audio only. | New: transport + voice panel + oracle-live-voice.mjs. Reuse: bridge, personas, tools, transcripts. | **M–L** | Med | Phase 0; server restart via `Start-Dashboard.ps1`; browser mic permission |
| **1.5 — Polish** | Barge-in/half-duplex (port `_leoSpeakingUntil` echo-guard + manual-VAD `activityStart/End`), reconnect, key-rotation surfaced, per-bot voice selection, transcript persistence. | Reuse patterns from leo.mjs. | M | Low–Med | Phase 1 |
| **2 — Video send** | `sendVideo/sendImage` on the bridge (additive); browser camera capture + JPEG frames over the same WS; **camera gating** (on only during active session). | New: bridge vision method + camera UI/state. | **L** | **Med–High** (model must accept video input; verify) | Phase 1; a vision-capable Live model |
| **2.5 — Bot "sees" UX** | Show the bot what it's reacting to, wire vision into tool/transcript flow, tune frame rate for cost. | New UI. | M | Med | Phase 2 |
| **3 — Shared identity (optional)** | Route Oracle-OS session into the *running* bot process (IPC) so it shares Discord memory/identity instead of a standalone session. | New: real-time cross-process audio IPC. | L | High | Phases 1–2; new IPC channel |

**Dependencies summary (what it needs to run):**
- A **Gemini Live native-audio API key** available to whichever process owns the session (dashboard server for A1). Per the handoff notes, native-audio (Charon) is a *separate, working* service even though the paid text key is depleted — so this should be viable with existing keys.
- **Browser permissions:** mic (Phase 1), camera (Phase 2). Localhost `http://` is a secure context for getUserMedia in Chrome, so no HTTPS requirement for local use.
- **Does NOT require the Discord bot processes** in the recommended A1 design — this is a genuinely independent path (matches the "alternative to Discord" intent).
- Applying changes: `command-center-server.mjs` edits → `.\Start-Dashboard.ps1` (restarts :3001 only, leaves engine + bots alone); `oracle.html` edits → hard-refresh, no restart.

---

## 8. Voice-first MVP vs full voice+video

**Voice-first MVP (Phases 0–1, optionally 1.5):** Click the new rail icon → panel opens → "Connect" → pick a bot → talk, hear it reply, see the live transcript, "Disconnect." Audio only. This is the achievable, high-value first target and reuses ~everything that makes conversation *work*. The only real unknown is browser-side audio worklet + jitter buffering, de-risked by Phase 0.

**Full voice+video (Phases 2+):** adds camera→bot vision, camera-gating, and possibly a different Live model. Bigger, with a genuine unknown (model video-input support) — plan it, but gate it behind a working MVP.

---

## 9. Open questions for the owner before building

1. **Session ownership:** standalone Oracle-OS session (A1, simpler, no Discord memory) vs shared with the running bot (A2/Phase 3, faithful but much more work)? Recommend starting A1.
2. **Which bots first** in the dashboard voice panel — Leo, or the social four (Gemini/Claudey/Groq/X), or all?
3. **Video model:** OK to spend a spike confirming a native-audio + video-input Live model exists / picking a frame-rate strategy before committing to Phase 2?
4. **Scope confirmation:** this is all **new** surface area (transport + a new shared module + new panel) — none of it touches `kaiverse.js` or the flight controls, and the `oracle.html` change is additive. Confirm we're OK opening this track alongside the KAIVERSE visual work.

---

## 10. Bottom line

- **Reusable, already built:** the entire conversational brain (`gemini-live-bridge.mjs`) — WS to Gemini, VAD, tools, transcripts, key rotation. Personas + voice selection. The 48↔16k / 24→48k resampling math. The `gemini-live-voice.mjs` structure as a template.
- **Net-new, modest:** a WebSocket transport between browser and server (`ws` already installed), a `shared/oracle-live-voice.mjs` with browser sink/source, and a left-rail Voice panel (trivial).
- **Net-new, larger (Phase 2+):** a bridge vision method, camera capture + gating, and confirming a video-capable Live model.
- **Recommended transport:** WebSocket + raw PCM (Option A), not WebRTC.
- **No build performed** — this is the scope doc for review.

---
---

# OWNER SPEC v2 — Full feature vision (captured verbatim-in-intent)

**Added:** 2026-06-30 · **Status:** SPEC + PLAN ONLY, no code written. This section captures the owner's fuller vision described after the feasibility report above, so nothing is lost. It supersedes/expands §7 where they conflict, and it stays reconciled with the feasibility findings (WebSocket-PCM transport, Discord-free `gemini-live-bridge`, `ws@8.20` already a dep, new `shared/oracle-live-voice.mjs`, ~15-line left-rail add).

## v2.1 The feature in one line

An **in-dashboard, Discord-style VOICE + VIDEO call system on the Oracle-OS side** — NOT Discord. You talk and (eventually) video-call the bots (and/or humans) from inside the Oracle dashboard itself, with a real call UI, participant tiles, active-speaker highlighting, picture-in-picture, device selection, and an invite/accept join flow. All signalling and media are directed to Oracle OS, not routed through Discord.

## v2.2 Detailed requirements

**A. Entry point — a "VIDEO" tab on voice channels.**
On a voice channel (e.g. *Leo Voice*, *ai-social-chat Voice*, etc.), alongside the existing per-channel tabs **CHAT / THREADS / METRICS / SETTINGS**, add a new **"VIDEO"** tab. Despite the name, it means **voice AND video**. Clicking it opens the call.

**B. Call view.**
Opening the call turns the **main window into a Discord-like call view**. You can **also chat inside the call** (text chat remains available in the call view).

**C. Invite / add participants.**
You can **add/invite whoever you want — bots and/or humans**. Invitees receive a **join REQUEST** and must **ACCEPT to join**. All of this is **directed to Oracle OS, not Discord**.

**D. Participant tiles.**
Each participant gets a tile showing, per participant:
- **Live VIDEO** if their camera is on; **otherwise their AVATAR / profile picture**.
- An **ACTIVE-SPEAKER indicator** — highlight (e.g. a glow/border) on whoever is currently talking.
- Show the **camera feed** whenever they have it on.

**E. Picture-in-Picture (PiP) while chatting.**
While in a call, if you click the **CHAT** tab, the **video feed pops out into a small floating window pinned to the top/side** (Twitch-stream style) so you can keep seeing who's talking while you chat.

**F. Device settings.**
A settings area to pick which **CAMERA** and **MIC** to use. **Default selection = "Device Default."** The user can change to **any available device** (enumerated from the browser).

**G. RF device toggle.**
If an **RF camera/device is present**, it gets an **ENABLE/DISABLE toggle inside the Camera options area.** This ties into **camera-privacy gating** — camera/RF are **only on when a session is active AND the user enables it.**

**H. Behavior rule — bots don't reply to text during voice.**
While you're in the VOICE call, the **bots do NOT reply to your TEXT messages** (they're on voice) — but they **CAN SEE the text** (it's in the chat). This is possible precisely because this is **Oracle-OS-side, not Discord.**

## v2.3 How v2 maps onto the feasibility findings

Nothing in v2 changes the recommended architecture; it mostly adds **UI surface and a join-flow**:
- The **call view, tiles, PiP, device pickers, RF toggle, tabbed VIDEO entry** are all **frontend** work in `oracle.html` (additive, hard-refresh-testable — no restart).
- **Live audio** still rides the **WebSocket-PCM transport** into the **unchanged `gemini-live-bridge.mjs`** via the new **`shared/oracle-live-voice.mjs`** (needs `command-center-server.mjs` + a `WebSocketServer`; `ws@8.20` already present; applied via `.\Start-Dashboard.ps1`).
- **Camera/video** rides the **same WebSocket** (JPEG frames) and needs the additive **`sendVideo/sendImage`** bridge method + a **vision-capable Live model** — same caveat as §6.
- The **"bots see text but don't reply during voice"** rule is a **server-side gating flag**: while an Oracle-OS voice session is active for a bot, suppress that bot's text-reply path but still deliver the text into its context. This is Oracle-OS-only behavior and is why it works here but not on Discord.
- **Device selection** = `navigator.mediaDevices.enumerateDevices()` + `getUserMedia({ audio/video: { deviceId } })`, default `deviceId` unset ("Device Default").
- **RF toggle + camera gating** = the same **"session active" flag** from §6 item 4, extended with an explicit per-device enable toggle; tracks are `.stop()`-ed when the session ends or the toggle is off.
- **Invite/accept** and **human participants** are **net-new** beyond the original plan — they need a signalling/presence layer in the dashboard server (join-request queue + accept endpoint over the same WebSocket). Human-to-human media is a larger lift (see risks) and is deliberately last.

## v2.4 Phased build plan (v2)

Reconciled with the feasibility plan; refresh-testable frontend first, live media second, video third, multi-party last.

### Phase 1 — UI foundation (frontend-only, hard-refresh testable, NO live media)
Build the entire interface and device-selection layer with **no live media wired yet** — pure UI + `enumerateDevices`.
- **VIDEO tab** on voice channels, next to CHAT / THREADS / METRICS / SETTINGS.
- **Call-view panel scaffold:** participant tiles (avatar placeholder + **active-speaker glow** styling), chat side-panel, controls bar (mute, camera, leave, invite).
- **Device SETTINGS:** camera + mic pickers via `enumerateDevices()`, **default = "Device Default,"** user-changeable to any enumerated device.
- **RF enable/disable toggle** inside the Camera options area (UI + state only at this phase).
- **Deliverable:** the full interface + device selection, clickable, with mock/placeholder tiles. No mic/cam capture yet.
- **Apply:** `oracle.html` edits → **hard-refresh**, no restart.
- **Dependencies/risks:** low risk, all additive to `oracle.html`; must not disturb existing per-channel tabs or `setView`. Enumerating devices without permission returns unlabeled device IDs in some browsers — labels populate after first `getUserMedia` grant (cosmetic, note in UI).

### Phase 2 — Voice MVP (live)
Make the call actually talk to a bot.
- Browser **mic → WebSocket → `gemini-live-bridge` (via `shared/oracle-live-voice.mjs`) → browser speaker.**
- Wire the **active-speaker indicator** to real audio levels (local mic level + inbound bot audio).
- Implement the **"bots see text but don't reply during voice"** rule (server-side text-reply suppression while session active; text still delivered to context).
- **Deliverable:** connect on the VIDEO tab, talk to a bot, hear it reply, see live transcript in the call chat.
- **Apply:** `command-center-server.mjs` + new `shared/oracle-live-voice.mjs` → **`.\Start-Dashboard.ps1`** (restarts :3001 only); `oracle.html` → hard-refresh.
- **Dependencies/risks:** Gemini Live native-audio key in the dashboard-server env; the browser audio-worklet + jitter-buffer is the main unknown (de-risk with a throwaway spike per §7 Phase 0); half-duplex/barge-in (echo-guard) is a fast follow.

### Phase 3 — Camera VIDEO + PiP + RF feed
Add real video on the transport built in Phase 2.
- **Browser camera tile:** `getUserMedia({video})`, render local tile; if the Live model supports vision, **send JPEG frames** over the same WebSocket (additive `sendVideo/sendImage` on the bridge).
- **PiP-while-chatting:** clicking CHAT pops the video into a **small floating window pinned top/side** (Twitch-style); returning to the call view re-docks it.
- **RF feed display** + activate the **camera-gating** so camera/RF are live **only when a session is active and enabled.**
- **Deliverable:** see your camera and the bot/participant camera tiles; PiP pop-out works; RF toggle gates a real feed.
- **Apply:** bridge/server edits → `.\Start-Dashboard.ps1`; `oracle.html` → hard-refresh.
- **Dependencies/risks:** **must confirm a native-audio + video-input Live model exists** (may need periodic-frame 1 JPEG/s rather than true video) — this is the genuine unknown; frame rate drives cost; camera-permission + gating state must be airtight.

### Phase 4 — Multi-participant INVITE / ACCEPT join flow
Turn it into a real multi-party call.
- **Invite/add** bots and/or humans; invitees get a **join REQUEST** and **ACCEPT** to join — **directed to Oracle OS.**
- Presence + signalling layer in the dashboard server (join-request queue, accept endpoint, participant roster broadcast over the WebSocket).
- Multiple tiles with per-participant camera/avatar + active-speaker highlighting.
- **Deliverable:** invite a second participant (bot first, then human), they accept, both appear as tiles.
- **Apply:** `command-center-server.mjs` (signalling) → `.\Start-Dashboard.ps1`; `oracle.html` → hard-refresh.
- **Dependencies/risks:** **highest.** Bot participants reuse the bridge cleanly; **human↔human media** is a real lift — the Option-A WS-PCM design is server-relayed (fine for a few participants on localhost/LAN, but it's not a mesh/SFU). If remote humans across the internet are ever needed, that's where WebRTC/an SFU would re-enter the conversation. Recommend: bots + local human first; defer remote-human scaling.

## v2.5 Restart-vs-refresh quick reference (v2)

| Change | Apply how |
|---|---|
| `oracle.html` (tabs, call view, tiles, PiP, device pickers, RF toggle, all Phase-1 UI) | **Hard-refresh** the dashboard — no restart |
| `command-center-server.mjs` (WebSocketServer, signalling, text-suppression gating) | **`.\Start-Dashboard.ps1`** — restarts :3001 only, leaves engine + bots + pipeline alone |
| new `shared/oracle-live-voice.mjs` | picked up on the same **`.\Start-Dashboard.ps1`** |
| `gemini-live-bridge.mjs` (additive `sendVideo/sendImage`) | **`.\Start-Dashboard.ps1`** (server owns the session in the A1 design) |

## v2.6 Net-new beyond the original feasibility plan (flag for owner)

The original plan already covers the voice transport, `oracle-live-voice.mjs`, and video-send. **v2 adds:** the tabbed VIDEO entry + full Discord-style call view, participant tiles with avatar-fallback + active-speaker glow, **PiP pop-out**, **device pickers with "Device Default,"** the **RF enable/disable toggle**, the **text-suppression-during-voice** rule, and the **invite/accept multi-party join flow (incl. humans)**. None of this touches `kaiverse.js` or the flight controls; the `oracle.html` work is additive. Human-participant media is the one item with materially higher risk and is intentionally last.

*Documentation only — no code changed, no version bumped.*
