Voice Overlay Lifecycle (macOS)
Audience: macOS app contributors. Goal: keep the voice overlay predictable when wake-word and push-to-talk overlap.Behavior
- If the overlay is already visible from wake-word and the user presses the hotkey, the hotkey session adopts the existing text instead of resetting it. The overlay stays up while the hotkey is held. On release: send if there is trimmed text, otherwise dismiss.
- Wake-word alone still auto-sends on silence; push-to-talk sends immediately on release.
Implementation
VoiceSessionCoordinator(apps/macos/Sources/OpenClaw/VoiceSessionCoordinator.swift) is the single owner of the active voice session. It is a@MainActor @Observablesingleton, not an actor. API:startSession,updatePartial,finalize,sendNow,dismiss,updateLevel,snapshot. Each session carries aUUIDtoken; calls with a stale or mismatched token are dropped.VoiceWakeOverlayController(VoiceWakeOverlayController+Session.swift) renders the overlay and forwards user actions (requestSend,dismiss) back through the coordinator via the session token. It never owns the session state itself.- Push-to-talk (
VoicePushToTalk.begin()) adopts any visible overlay text asadoptedPrefix(viaVoiceSessionCoordinator.shared.snapshot()) so pressing the hotkey while the wake overlay is up keeps the text and appends new speech. On release, it waits up to 1.5s for a final transcript before falling back to the current text. - On
dismiss, the overlay callsVoiceSessionCoordinator.overlayDidDismiss, which triggersVoiceWakeRuntime.refresh(state:)so manual X-dismiss, empty-text dismiss, and post-send dismiss all resume wake-word listening. - Unified send path: if trimmed text is empty, dismiss; otherwise
sendNowplays the send chime once, forwards viaVoiceWakeForwarder, then dismisses.
Logging
Voice subsystem isai.openclaw; each component logs under its own category:
| Category | Component |
|---|---|
voicewake.coordinator | VoiceSessionCoordinator |
voicewake.overlay | VoiceWakeOverlayController/VoiceWakeOverlay |
voicewake.ptt | Push-to-talk hotkey and capture |
voicewake.runtime | Wake-word runtime |
voicewake.chime | Chime playback |
voicewake.sync | Global settings sync |
voicewake.forward | Transcript forwarding |
voicewake.meter | Mic level monitor |
Debugging checklist
-
Stream logs while reproducing a sticky overlay:
- Verify only one active session token; stale callbacks are dropped by the coordinator.
-
Confirm push-to-talk release always calls
end()with the active token; if text is empty, expect a dismiss without chime or send.