Managing Voice Overlay Transitions on macOS
I have spent a lot of time debugging UI overlays that fight each other. It is a common headache: one trigger starts a process, another trigger interrupts it, and suddenly the UI is stuck in a weird state or data gets wiped. When you are building voice interfaces on macOS, this usually happens when a wake-word and a push-to-talk hotkey overlap.
I want to show you how we are using session tokens and a central coordinator to make these transitions predictable. Instead of the overlay flickering or resetting, the hotkey session now “adopts” the existing text so the user never loses their place.
What You’ll Need
Section titled “What You’ll Need”- Access to the macOS app codebase
- SwiftUI for the overlay views
VoiceWakeRuntimeandVoicePushToTalkmodules- Permission to run
sudofor log streaming
Quick Start
Section titled “Quick Start”You can get the new lifecycle running by following these four steps to move away from global singletons and toward token-based management.
- Initialize the VoiceSessionCoordinator: Create this actor to own exactly one
VoiceSessionat a time. It uses a token-based API—includingbeginWakeCaptureandbeginPushToTalk—to ensure that any partial or final updates with stale tokens are dropped. - Define the VoiceSession Model: Your session needs to track the
token, thesource(wake-word or push-to-talk), and theoverlayMode. This model stores both committed and volatile text while managing timers for auto-sending. - Bind the UI via a Publisher: Use
VoiceSessionPublisheras anObservableObjectto mirror the active session into SwiftUI. YourVoiceWakeOverlayViewshould only render via this publisher and never mutate state directly. - Implement the Unified Send Path: On
endCapture, check the trimmed text. If it is empty, dismiss the overlay. If there is text, callperformSend(session:)to play the send chime once and forward the data.
Troubleshooting
Section titled “Troubleshooting”If you run into issues with a sticky overlay or unexpected behavior, check these two areas:
- Stale Callbacks: If the overlay stays up when it should be gone, verify that only one active session token exists. The coordinator is designed to drop updates if the token does not match the current session.
- Log Inspection: You can stream logs in real-time to see exactly when sessions start, adopt, or dismiss. Use this command in your terminal:
sudo log stream --predicate 'subsystem == "bot.molt" AND category CONTAINS "voicewake"' --level info --style compactThis will show you specific events like adopted_by_push_to_talk or cooldown in the voicewake.overlay and voicewake.chime categories.
If you have questions about specific implementation details, check the AI Setup Assistant.
What’s Next
Section titled “What’s Next”VoiceSessionCoordinatorActor APIVoiceWakeRuntimeRefactoring Guide- Integration Testing for Session Adoption
- SwiftUI Overlay Binding Patterns
OpenClaw Expert
Still stuck?
If this page didn't answer your case, ask OpenClaw Expert for step-by-step guidance.