import { type Dictation } from "./machine.js"; import { type VoiceMode } from "./convo.js"; /** * Voice hook for chat β€” three modes: * * Push-to-talk (🎀): live transcript in input β†’ auto-sends on pause. * Auto-speak (πŸ”Š): reads every assistant response aloud. * Conversation (πŸŽ™οΈ): continuous hands-free loop: * 1. Chime β†’ you talk β†’ words appear live in input * 2. You pause β†’ chime β†’ message sends, mic pauses * 3. Agent responds β†’ response spoken aloud * 4. TTS finishes β†’ chime β†’ mic re-opens β†’ step 1 */ export type { VoiceMode }; export declare function useVoice(instanceId: string | undefined, opts: { /** Send a transcript. `meta.audioKey` is set for voice turns whose audio was saved; * `meta.dictation` is what the live recognizer heard, when that differs from the * transcript (#319) β€” persist it beside the message so the two can be compared. * `meta.suspect` is set when the platform observed the recognizer replacing or truncating * what was heard live (#626) β€” the agent is told the transcript may not match what was said. */ onSend: (text: string, meta?: { audioKey?: string; dictation?: string; suspect?: boolean; }) => void; /** * A turn the user really spoke, transcribed too late to send (#175) β€” the agent replied (or * the mode changed) while they were still talking. Hand it back instead of dropping it: put * it in the composer so they can see it survived, edit it, and send it. NOT auto-sent β€” the * conversation has moved on since they said it. Without a handler the text is discarded, i.e. * exactly the old behaviour. */ onRecoveredText?: (text: string) => void; /** * The user said "next" (#277) β€” move them to the agent that is asking for them. * * Passing a handler is what ENABLES the command: this hook knows nothing about agent * rosters, so on a surface with no switcher (the Coder's Co-pilot) "next" stays an * ordinary word and reaches the agent as speech. `carryMode` is the voice mode to resume * on the other side, so hands-free survives the move rather than dumping the user on a * silent screen they then have to touch β€” which would defeat the whole point. * * The session is already torn down when this fires: TTS cut, the pending utterance * resolved (recovered via `onRecoveredText` if it held real words). See * {@link prepareConversationSwitch} and {@link leaveForSwitch} β€” the same teardown #279's * agent-mediated transfer takes, so the three triggers can never disagree about the mic. */ onNext?: (carry: { carryMode: VoiceMode | null; }) => void; /** * The user said "go back" (#279) β€” return them to the agent they were with before this one. * * The reversal of an agent-mediated transfer, and the reason the transfer is not a one-way * door: an agent hands you over, and without this the only way back in hands-free is the * screen. Enabled by passing a handler, exactly as `onNext` is, and torn down identically β€” * these are two destinations for one move, not two moves. * * It is NOT `next` with a different resolver: `next` returns you to the previous agent only * when that agent has an unread notification, and being transferred away does not raise one. */ onBack?: (carry: { carryMode: VoiceMode | null; }) => void; /** * The user said "scrap that" (#342) β€” they want the last turn gone. * * Passing a handler is what ENABLES the command, exactly as `onNext` does, and for one extra * reason: this is the first DESTRUCTIVE word in the vocabulary. Everything else here is * recoverable β€” mute, unmute, repeat, switch agent β€” so a false positive costs a moment. This * one removes a message, and nothing errors afterwards, which is precisely the shape #328 gave * a confirmation to. * * So the hook does NOT delete anything, and the name says so: it STAGES. The consumer is * expected to raise a confirmation naming the turn, not to act. An "undo" toast would be the * wrong safety net here β€” the hands-free user this command exists for is not looking at the * screen, so a thing that expires in ten seconds protects nobody. The gate belongs BEFORE. * * Fired only on a FINAL utterance and only when the whole utterance is the phrase: an interim * transcript of "scrap that idea and let's move on" is momentarily exactly "scrap that", so the * live-partial paths deliberately withhold the flag that turns the word on. */ onScrap?: () => void; /** Vocabulary-bias prompt for transcription (see voice/prompt.ts) so domain words * aren't mis-heard (a developer's "bugs" shouldn't transcribe as "bars"). */ transcribePrompt?: string; /** Technical agent (code explainer / coding): keep identifiers + file basenames in * SPOKEN output instead of stripping them to "a file". Default false. */ technical?: boolean; }): { /** The active interaction mode + the ONLY setter the UI needs. */ mode: VoiceMode; setVoiceMode: (next: VoiceMode) => Promise; /** The voice interaction phase (idle/starting/listening/transcribing/speaking/muted). */ phase: import("./machine.js").VoicePhase; micOn: boolean; speakOn: boolean; convoOn: boolean; muted: boolean; /** Transient NOTICE only (mic errors, the wrong-language nudge) β€” the user's words live in * `dictation`, not here (#281). Render it as its own banner: putting it in the composer's * `value` is what left the input claiming to be the live-speech surface (#364). Bind it * through `resolveComposer` and the shape is decided in one tested place. */ notice: string; /** * The utterance in flight: `{ text, status, startedAt, heard, note }`, or null between * turns. Render it as a PENDING message in the thread β€” it appears the moment speech * starts, carries a transcribing status while the clip uploads, and survives a failure * with its words intact. The final transcript arrives separately via `onSend`, which is * what replaces it. */ dictation: Dictation | null; /** The clip is being transcribed (was a string-compare against a composer sentinel). */ transcribing: boolean; /** A voice session is opening the mic β€” bind a spinner to acknowledge the press (#284). */ starting: boolean; /** Dismiss a failed utterance the user has finished reading. */ clearDictation: () => void; /** Re-send the same clip after a failure that a second attempt could change (#421). Render * the button only when `canRetryDictation` β€” a 400/401 will fail identically and the * attempt costs the user's own credit. */ retryDictation: () => void; canRetryDictation: boolean; /** 0-1 audio level from mic β€” use to render waveform */ audioLevel: number; /** True while the agent is talking aloud (TTS) β€” drives the "Speaking…" status. */ speaking: boolean; /** True while a manual push-to-talk turn is open (hands-free tap-to-talk). */ talking: boolean; toggleMic: () => Promise; toggleSpeak: () => void; toggleConvo: () => Promise; toggleMute: () => void; cancelSpeak: () => void; /** Push-to-talk within hands-free: start/stop a manual turn, or toggle it. */ beginTalk: () => Promise; endTalk: () => void; toggleTalk: () => void; maybeSpeakResponse: (text: string) => void; /** Speak text on demand (message replay), independent of auto-speak mode. */ speak: (text: string, lang?: string) => Promise; /** * End this voice session because the conversation is moving, and get back the mode to * reopen in on the other side (#279). For the AGENT-MEDIATED transfer, which arrives on a * chat response rather than as a spoken command β€” so the consumer, not this hook, is what * knows a move is happening. Identical teardown to "next" and "go back": the same TTS cut, * the same #175 recovery of a half-spoken turn, the same release of the hands-free slot. */ leaveForSwitch: () => VoiceMode | null; }; //# sourceMappingURL=use-voice.d.ts.map