Conversation
…me layer Groundwork for the experimental voice mode (OpenAI Realtime, chat-supervisor pattern): a `voice` settings block (schema v14), `ChatSession.sendMessageAndAwait` so a caller can observe a turn's terminal state and final text, and the store-free modules — the wire protocol in one file, the delivery coordinator that holds function results until the model is idle and the user silent, the supervisor bridge that runs a delegated request as a normal chat turn, and the WebSocket client. Co-Authored-By: Claude <noreply@anthropic.com>
…enAI Realtime) Full-duplex voice conversation with OpenAI's realtime speech model, which handles the talking itself and hands anything note-related to the regular agent through one function call (chat-supervisor pattern). A delegated request runs as an ordinary chat turn on the open thread — persisted, streamed, reviewable — and the answer is read back; the user can keep talking while it runs, and the result is delivered once the model is idle and the user silent. While voice is bound to a thread the chat view swaps to an audio-reactive orb with a status line, the last transcript lines, stop, and a show-chat toggle that reveals the message list beneath a compact orb strip. Microphone capture runs in an AudioWorklet shipped as raw text through a blob URL; playback is scheduled gaplessly and reports how much of an item was heard so barge-in can truncate. Settings live under General → Voice (experimental); the composer gets a mic button and there is a "Toggle voice mode" command. Desktop only, reuses the OpenAI provider key, no new dependencies. Co-Authored-By: Claude <noreply@anthropic.com>
Contributor
|
…runcate the audible item Three review findings. The voice session is global and owns the microphone, so the chat view now stops it when it unmounts or moves to another thread instead of leaving capture running behind a surface that is no longer on screen. A delegation that lands while a typed reply is running is refused before the pair is appended, so no phantom turn lingers in the list. Playback tracks timing per item and barge-in reports the item audible at that moment (clamped, whole milliseconds), not whichever item last received audio. Co-Authored-By: Claude <noreply@anthropic.com>
Views now register the thread they show with the voice session, which stops only when the last view showing its bound thread detaches — instead of any one view's cleanup ending it. Covered by voiceSessionViews.test.ts. Co-Authored-By: Claude <noreply@anthropic.com>
The voice group belongs with the agent and chat settings rather than General. In the composer the voice button now sits between the context ring and Send — it is the other way to start a turn — and uses Lucide's audio-lines waveform instead of a microphone, which reads as "dictate into the box" rather than a live spoken exchange. Command renamed to "Toggle voice conversation". Co-Authored-By: Claude <noreply@anthropic.com>
…box is empty With nothing to send — no text, no attachments, not editing — the Send button becomes the waveform that starts a voice conversation, the pattern ChatGPT's composer established. Typing turns it back into the arrow; Enter on an empty box still does nothing; Stop still wins while a reply streams. Replaces the separate voice button. Co-Authored-By: Claude <noreply@anthropic.com>
The orb now comes in blob (morphing outline, default), aurora (drifting colour clouds), spectrum (radial equaliser fed by the analyser's frequency bins), ripple (sonar rings) and pulse (the original disc). The painted looks live in orbRenderers.ts as pure frame painters; the CSS looks key off data-status and the level custom property as before. Colours come from the theme accent, with colour-mixed companions resolved through a probe element for the canvas. Stored as voice.orbStyle (schema v15 fills it in for existing blocks). Co-Authored-By: Claude <noreply@anthropic.com>
Toggling the compact orb changed its displayed size at once but its backing size only on the once-per-60-frame colour re-read, so it could paint oversized or clipped for about a second. Co-Authored-By: Claude <noreply@anthropic.com>
A chat renames itself after its first turn. The registry re-keys the session and the view follows the vault rename, but the voice session kept the old path and — seeing no view on it any more — stopped, dropping the user back into the chat mid-answer. It now follows vault renames itself, decides view detachment after the current flush so the view's same-tick re-attach counts, and resolves the thread path when a queued delegation starts rather than when it was queued. Co-Authored-By: Claude <noreply@anthropic.com>
A delegated turn used to be silence until the answer. Tool starts inside the running turn (with the model's own lead-in sentence when it wrote one, else a readable tool name) now flow from ChatSession through the bridge to the voice session, which asks the speech model for a one-sentence out-of-band update — conversation: none, so it never becomes part of the pending function call's state and its audio is never a truncatable item. Progress lines go through the same delivery gate as results: never over the user, never over a response, a ready answer always wins, only the newest line survives, at most one every four seconds. Co-Authored-By: Claude <noreply@anthropic.com>
…efore the window opens Co-Authored-By: Claude <noreply@anthropic.com>
…auto-title A rename from the file menu (or a drag) went through renameChatFile without touching the session registry, so the live session stayed under its old path while the view — and now voice mode — resolved the new one and found nothing. The registry now follows vault renames of .chat files and updates the session's id; the auto-title path's own re-key stays idempotent with it. Co-Authored-By: Claude <noreply@anthropic.com>
A progress line held for the rate limit could fire after the delegation it described had already finished, and then be spoken at the start of the next request. The hold is cancelled when the turn settles, and a held line that fires with nothing pending is discarded. Co-Authored-By: Claude <noreply@anthropic.com>
… the default The morphing outline becomes a clip; three blurred radial clouds in the accent and its two colour-mixed companions orbit inside it at different speeds, drift faster and brighter with the voice, and the glow follows the outline rather than a circle. Blob and aurora stay available for comparison. Co-Authored-By: Claude <noreply@anthropic.com>
Only the first tool start of a step carried the model's lead-in; every other one fell back to a generic "running <tool>", which the speech model kept rephrasing into the same line. Progress lines are now built from the tool's input when there is no lead-in (the search terms, the note being read, the page host), and stay silent when there is nothing specific to say. The model is told what it already said so it varies its wording, duplicates are dropped, the gap is five seconds, and each request gets at most three lines. Co-Authored-By: Claude <noreply@anthropic.com>
An out-of-band response still sees the whole conversation unless it is given
explicit input, so the speech model kept paraphrasing the user's request
("looking for your tasks this week") instead of the step at hand, and said the
same sentence every time. Narration responses now carry their own two-line
context — the user's last words for language and the progress update — plus
the lines already spoken, and nothing else.
Co-Authored-By: Claude <noreply@anthropic.com>
…ead-ins as they are A narration line with no context is structurally the same every time. The response now gets the context that makes a line good — the user's request, every step so far, the lines already spoken — without the full conversation, whose closing question kept pulling the model into re-answering it. A lead-in sentence the agent wrote itself is spoken close to its meaning (it already carries the reasoning from step to step); a terse tool step is turned into a natural sentence with its detail. Openings must vary; repeats are refused. Co-Authored-By: Claude <noreply@anthropic.com>
The corner radii drift between 46% and 54% over nine seconds, so the disc never quite settles into a circle while keeping its flat, calm look. Nebula keeps the pronounced blob outline for anyone who wants it. Co-Authored-By: Claude <noreply@anthropic.com>
The corner-radius lean is now a custom property the keyframes read: 3% at rest, up to about 13% with the audio level, so the disc leans with the voice and settles when listening. Co-Authored-By: Claude <noreply@anthropic.com>
The request, user words, step history, spoken lines and budget were kept on the session, so a second call queued behind the first was narrated in the wrong request's terms, a later utterance could change the language mid-request, and the three-line budget never reset (the check ran after the call was already pending). Each call now carries its own narration state from function call to delivered output; a held line is tied to its call and dropped when it settles. Also: the properties tool's input field is note_name, not path. Co-Authored-By: Claude <noreply@anthropic.com>
Rest lean 5%, up to about 20% with the voice, six-second cycle. Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Experimental, desktop-only voice mode: a full-duplex conversation with OpenAI's realtime speech model, which does the talking itself and hands anything note-related to the regular agent through a single function call (
ask_second_brain, the chat-supervisor pattern). A delegated request runs as an ordinary chat turn on the open thread — persisted, streamed, reviewable on screen — and the spoken answer is read back. The user can keep talking while the agent works; the result is handed to the speech model once it is idle and the user is silent.While voice is bound to a thread, the chat view swaps to an audio-reactive orb (breathes while listening, pulses with the model's voice, spins a ring while the agent works) with the last transcript lines, a stop button and a "show chat" toggle that reveals the message list under a compact orb strip. Settings: General → Voice (experimental). Composer gets a mic button; there is a "Toggle voice mode" command.
Design notes, all in
src/voice/:realtimeProtocol.tsholds every event name and the PCM16 codecs, so API drift is a one-file fix.turnCoordinator.tsis a pure reducer that holds finished results until the model is idle and the user silent (server VAD auto-creates a response afterspeech_stopped, so a naiveresponse.createcollides).supervisorBridge.tsserialises delegations and maps the turn's outcome to JSON the model reads back;ChatSession.sendMessageAndAwaitis the new completion hook it relies on.?worker&inlineroute yields a constructor, not a URL); playback tracks how much of an item was heard so barge-in can truncate the server's transcript.openai-insecure-api-keyWebSocket subprotocol with the configured OpenAI provider's key (browser sockets cannot set headers; the key already lives on this machine). No new dependencies.Known limitations of the prototype: headphones assumed (Chromium cannot echo-cancel WebAudio output, so on speakers the model can hear itself); starting voice mode unmounts the composer, so an unsent typed draft is lost; only delegated turns land in the
.chatthread (chit-chat since the last delegation is prefixed to the next request as context); no reconnect.No linked issue — scoped in-session with the maintainer.
How I tested it
bun run check,format,lint,test(1893 tests, including new suites for the protocol codecs, the delivery coordinator, the supervisor bridge, the WebSocket client with a fake socket, the awaitable chat turn, and the v13→v14 migration) andmadge --circularwith type imports skipped all pass. A one-shot dev build inlines the worklet (registerProcessorpresent inmain.js).Not yet tried live. A real run needs an OpenAI API key in the slot vault (
S2B WT2) and a microphone; the live checklist is: enable in settings → open a chat → mic → say "hello" (voice-only reply) → ask about a note (filler phrase, orb ring, a delegated user message appears in the chat, answer spoken) → talk over the model → ask "are you done?" during a long delegation → stop. Event names were written against the current GA docs but only a live session proves them.AI assistance: Claude Code (Fable 5.1) designed and wrote the whole change from Leo's brief after a research discussion on speech-to-speech models; Leo chose the backend, platform, UI shape and transcript policy; live verification in a vault is still pending.
Checklist
bun run check,bun run format,bun run lint, andbun run testpass locallymanifest.json: I noted that the docs site needs updating — not applicable yet (experimental, undocumented on the site until it ships)