Skip to content

Voice transcription lost when agent message arrives mid-dictation #175

Description

@serge-ivo

Bug

Speak a turn in hands-free; if an agent message arrives before your transcript lands, everything you said is silently discarded.

Root cause — found, and it is not a re-render

The original "fix ideas" pointed at React state being wiped by a re-render. That is not it: the in-progress transcript lives in pendingTextRef (a ref) and, in Whisper mode, in the MediaRecorder itself. A re-render cannot touch either.

The actual path:

  1. An agent message arrives → maybeSpeakResponse (use-voice.ts:626) → speakAndResume (:600).
  2. speakAndResume sets pausedForThinkingRef.current = true and calls sttRef.current.stop() (:608-609).
  3. stop() — unlike stopDiscard()does finalise: the clip uploads and transcribes, so the user's words survive the network round-trip.
  4. The transcript comes back and hits if (shouldIgnoreResult(readGuard(), Date.now())) return; (:772). shouldIgnoreResult is isEchoing(...) || s.paused (machine.ts:51), and paused is still true because the agent is mid-sentence.

So the turn is transcribed successfully and then dropped one line before it would have been sent. Nothing logs it; the user sees their words vanish.

Why the guard exists (do not simply remove it)

That paused check is load-bearing and its comment says so: a late Whisper transcript must not "fall through and send a turn the user already abandoned" — e.g. after the mode was switched off. And the echo half stops the agent transcribing its own TTS. A naive if (!paused) removal reintroduces both.

The distinction the guard cannot currently make: audio captured BEFORE the pause began is the user's real turn; audio captured DURING the pause is echo or an abandoned turn. Same flag, two meanings.

Suggested direction

Stamp the capture, not the result. Record when the current recording STARTED (lastListenStartRef already exists, :1029) and when the pause began; a result whose capture started before the pause is the user's speech and should be delivered even if it lands late. Only results whose capture overlaps the pause are echo/abandoned.

Deliver it rather than send it blind: appending the recovered transcript to the composer, so the user can see it survived and press send, is safer than auto-sending a turn into a conversation that has since moved on.

Acceptance criteria

  • Dictating in hands-free while an agent reply arrives mid-turn preserves the spoken text.
  • The agent still never transcribes its own TTS (the echo half of the guard is unaffected).
  • A transcript from a capture that started AFTER the pause (mode switched off, abandoned turn) is still dropped.
  • Unit tests on the pure guard: capture-before-pause survives, capture-during-pause does not.

Reproduce

Hands-free, sttMode: openai. Start a long sentence, and have the agent emit any reply (e.g. a second browser tab sending a message to the same instance) before you stop speaking.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions