Skip to content

feat(call): support being in a call from multiple tabs/devices#612

Draft
HexaField wants to merge 1 commit into
fix/fullscreen-video-layoutfrom
feat/multi-session-calls
Draft

feat(call): support being in a call from multiple tabs/devices#612
HexaField wants to merge 1 commit into
fix/fullscreen-video-layoutfrom
feat/multi-session-calls

Conversation

@HexaField

Copy link
Copy Markdown
Contributor

Summary

Lets the same agent be in a call from multiple tabs/devices at once. Previously a second tab joining a call hit You are already in a call in another tab. — the call + presence stack was keyed by DID, so two sessions of one agent collided on a single presence entry and a single peer-connection slot.

This changes the unit of call identity from DID to a session key (did::sessionId, reusing the per-tab UUID already in sessionStorage) and decouples calls from tab-coordinator leadership.

Note: multi-device with different DIDs already worked (independent coordinators). This is what makes the same agent work across tabs/devices.

Participant model

Per the agreed design: show every video/screenshare feed separately, dedupe non-video feeds — each person has one representation but all their media is shown.

  • Every session with a camera/screenshare feed → its own tile
  • Sessions with no video collapse to a single avatar tile per person
  • Audio is deduped to one feed per person (no echo)

Changes

  • typesAgentState gains did + sessionId
  • useTabCoordinator — remove call-pinning (call-pinned/otherTabInCall/setInCall); leadership now only dedupes general presence
  • useSignallingService — presence keyed by session; broadcast when leader || inCall; getAgentsInChannel/getAgentsInCall dedupe to one entry per person
  • webrtcStore — session-keyed peer connections, session-precise WebRTC signalling, connects to your own other sessions, removes the error toast
  • call UI (useVideoLayout/VideoGrid/MediaPlayer) — per-session tiles (did:sessionId:streamKind) + audio-dedupe mute path
  • new utils/callSessions.ts — pure helpers (sessionKey, shouldInitiate, dedupeByDid, deriveRemoteSessionTiles) with unit tests

Edge case (handled)

If a person's only mic-on session has its camera off while another of their sessions shows video, an extra avatar tile is emitted so their audio is never silently dropped (see comment in deriveRemoteSessionTiles).

Testing

  • ✅ 10 new unit tests for callSessions pass
  • vue-tsc clean for all touched files
  • ⚠️ Full app run / two-tab + second-device smoke test still needs a real executor (ad4m-flux-launch.sh) — not run in this environment

🤖 Generated with Claude Code

Calls were pinned to a single tab per browser: a second tab joining a
call hit "You are already in a call in another tab." The whole call +
presence stack was keyed by DID, so two sessions of the same agent would
collide on one presence entry and one peer-connection slot.

Change the unit of call identity from DID to a session key
(`did::sessionId`, reusing the per-tab UUID already in sessionStorage)
and decouple calls from tab-coordinator leadership. The same agent can
now be in a call from several tabs/devices at once.

Participant model: every video/screenshare feed renders as its own tile,
no-video sessions collapse to a single avatar per person, and audio is
deduped to one feed per person (no echo).

- types: AgentState gains `did` + `sessionId`
- useTabCoordinator: drop call-pinning; leadership now only dedupes
  general presence
- useSignallingService: key presence by session; broadcast when
  leader OR in a call; dedupe channel/call getters by DID
- webrtcStore: session-keyed peer connections, session-precise WebRTC
  signalling, connect to own other sessions, remove the error toast
- call UI: per-session tiles + audio-dedupe mute path
- add pure callSessions helper module with unit tests

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 13b80d74-2ba0-41b9-a920-faf235382710

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/multi-session-calls

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant