Skip to content

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.

  • Access to the macOS app codebase
  • SwiftUI for the overlay views
  • VoiceWakeRuntime and VoicePushToTalk modules
  • Permission to run sudo for log streaming

You can get the new lifecycle running by following these four steps to move away from global singletons and toward token-based management.

  1. Initialize the VoiceSessionCoordinator: Create this actor to own exactly one VoiceSession at a time. It uses a token-based API—including beginWakeCapture and beginPushToTalk—to ensure that any partial or final updates with stale tokens are dropped.
  2. Define the VoiceSession Model: Your session needs to track the token, the source (wake-word or push-to-talk), and the overlayMode. This model stores both committed and volatile text while managing timers for auto-sending.
  3. Bind the UI via a Publisher: Use VoiceSessionPublisher as an ObservableObject to mirror the active session into SwiftUI. Your VoiceWakeOverlayView should only render via this publisher and never mutate state directly.
  4. Implement the Unified Send Path: On endCapture, check the trimmed text. If it is empty, dismiss the overlay. If there is text, call performSend(session:) to play the send chime once and forward the data.

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:
Terminal window
sudo log stream --predicate 'subsystem == "bot.molt" AND category CONTAINS "voicewake"' --level info --style compact

This 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.

  • VoiceSessionCoordinator Actor API
  • VoiceWakeRuntime Refactoring Guide
  • Integration Testing for Session Adoption
  • SwiftUI Overlay Binding Patterns
OpenClaw

OpenClaw Expert

Still stuck?

If this page didn't answer your case, ask OpenClaw Expert for step-by-step guidance.