Skip to content

Capture live meeting audio through the call-host bridge - #5374

Merged
atomantic merged 2 commits into
mainfrom
next/issue-5311
Aug 29, 2026
Merged

Capture live meeting audio through the call-host bridge#5374
atomantic merged 2 commits into
mainfrom
next/issue-5311

Conversation

@atomantic

Copy link
Copy Markdown
Owner

Summary

  • Adds Capture system audio as a second mode on the FaceTime call-host page (/voice/call-host?mode=capture), reusing the same BlackHole 16ch device, PCM wire format, and whisper STT endpointing as the call bridge — but STT only, never the LLM/tools pipeline, and no BlackHole 2ch reply path required.
  • On stop, the timestamped transcript is appended to the daily journal under a "Meeting capture" heading and filed as a Brain inbox item with auto-classify off — no AI provider is called until the user asks for a summary from the inbox.
  • New server/services/voice/captureSession.js (single-attach host, start/stop, journal + inbox write) mirrors the existing callSession.js. server/sockets/voice.js adds voice:capture:start|stop and routes the existing voice:call:audio event to whichever host (call or capture) owns the socket; each mode is refused while the other is active on the same tab, since both want the same physical device.
  • client/src/lib/callAudioBridge.js: describeDeviceProblem's outputLabel is now optional, since capture mode never plays a reply back.
  • Drive-by fix in callSession.js: endCall() passed the async getToday() into appendJournal as an unresolved Promise, so isIsoDate's typeof guard always rejected it and the FaceTime call transcript was silently never written to the journal.

Test plan

  • cd server && npx vitest run — full suite green (1729 files / 35489 tests).
  • cd client && npx vitest run — green except one pre-existing, unrelated flake (GitTab.test.jsx, passes in isolation and on an untouched file).
  • cd client && npm run lint && npm run build — clean.
  • New coverage: server/services/voice/captureSession.test.js (session state machine, journal/inbox writes, error resilience), additions to server/sockets/voice.test.js (mutual exclusion, end-to-end audio → STT → transcript → journal/inbox routing, confirms runTurn/voice:llm:done never fires for a capture), additions to client/src/pages/VoiceCallHost.test.jsx (mode toggle, no-output-device requirement, live state, unmount cleanup) and client/src/lib/callAudioBridge.test.js (optional outputLabel).
  • Regenerated server/lib/socketEventCatalog.generated.json for the new/changed events.

Closes #5311

Adds "Capture system audio" as a second mode on the FaceTime call-host
page (/voice/call-host?mode=capture). It reuses the same BlackHole
16ch device, PCM wire format, and whisper STT endpointing as the call
bridge, but only transcribes — it never runs the LLM/tools pipeline
and needs no BlackHole 2ch reply path. Stopping a capture writes the
timestamped transcript to the daily journal under a "Meeting capture"
heading and files it as a Brain inbox item with auto-classify off, so
no AI provider is ever called until the user asks for a summary.

- server/services/voice/captureSession.js: the capture session state
  machine (single-attach host, start/stop, timestamped transcript,
  journal + inbox write on stop), mirroring callSession.js.
- server/sockets/voice.js: voice:capture:start/stop handlers; the
  existing voice:call:audio handler now routes to whichever host (call
  or capture) owns the socket, with each side mutually exclusive of
  the other since both want the same physical device.
- client/src/pages/VoiceCallHost.jsx: mode toggle (deep-linkable via
  ?mode=), capture-specific device check (no output device required),
  and capture-specific start/stop/state wiring.
- client/src/lib/callAudioBridge.js: describeDeviceProblem's
  outputLabel is now optional, since capture mode never plays a reply
  back.

Also fixes a bug in callSession.js: endCall() passed the async
getToday() as a Promise into appendJournal without awaiting it,
so isIsoDate's typeof guard always rejected it and the FaceTime call
transcript was silently never written to the journal.

Closes #5311
… the fix

The suite mocked brainJournal.getToday as a sync function while production has
it async, so the assertion passed against the unawaited call this PR fixes — a
pending Promise that appendJournal's isIsoDate guard silently rejected, leaving
FaceTime call transcripts unwritten. Matching the real signature makes the case
fail without the await and pass with it.
@atomantic
atomantic merged commit 4c87192 into main Aug 29, 2026
7 checks passed
@atomantic
atomantic deleted the next/issue-5311 branch August 29, 2026 06:34
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.

[repo-study-facetime-bridge-meeting-capture] Capture live meeting audio through the call-host bridge into the daily journal and Brain inbox

1 participant