The lazy-connect note is no longer true and the trap it described was the
opposite way round: the session existed and the audio was discarded.
diff-budget.sh blocks the branch at 615 changed lines. This commit is
markdown only, which the repo's own pre-commit hook exempts, and it
corrects a line the code in this branch has just falsified.
It reaches the player, but not from the push goroutine. An unusable push
is dropped and does not wedge the next one. It waits for a reply to
finish. It resets the VAD, so the frames before it are not spliced onto
what he says after. And a second nudge replaces an unspoken first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013ptwopxyo3Z2kwFckHkLvN
It wired no PushHandler, and SendRequest discards a push frame when there
is none. That was not a missing feature but a silent one. mavend routes a
nudge to the voice session that spoke most recently, so once mavwaked had
spoken once it WAS that session. PushToMostRecent succeeded, the
dispatcher counted the nudge delivered and stopped rerouting to the away
channels, and mavwaked threw the audio away. He heard nothing, anywhere.
It now connects at startup rather than at the first utterance, because
the dispatcher has to tell "he is not at the machine" from "he is, and
she has nothing to say". The receiver redials on its own clock, since
mavend restarts on every deploy.
A nudge is queued, not played where it arrives. The capture loop picks it
up on the next frame, so the half-duplex gate and barge-in cover it the
way they cover a reply. It resets the VAD first: playback is about to
suppress every frame, and a half-heard sentence would otherwise splice
onto whatever he says next. A nudge arriving while one still waits
replaces it, which is the contract internal/voice states for PushHandler.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013ptwopxyo3Z2kwFckHkLvN
SendRequest and RunPushReceiver each read the conn, so a client that
wanted both raced for every frame. A second listening conn is not the
fix: it never sends a request, so its lastActive never moves and
PushToMostRecent never picks it. mavwaked needs both on one conn.
The reader now owns the socket for the life of the conn. It hands each
Response to whichever SendRequest waits on that id, and each Push to the
handler. SendRequest waits on its own channel, on the conn dying, on its
context, or on a timeout, and forgets its slot on every path that leaves
without an answer. RunPushReceiver just wires the handler and blocks.
Connect opens the conn without sending anything, for a client that must
hold a session before it has spoken.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013ptwopxyo3Z2kwFckHkLvN