OpenClawSkills
GitHub
Platforms β€’ TutorialHeader.readTime

Voice Overlay

Voice overlay lifecycle when wake-word and push-to-talk overlap

Audience: macOS app contributors. Goal: keep voice overlay predictable when wake-word and push-to-talk overlap.

#

Tutorial.step

Current intent

- If overlay is already visible from wake-word and user presses hotkey, hotkey session _adopts_ existing text instead of resetting it. The overlay stays up while hotkey is held. When user releases: send if there is trimmed text, otherwise dismiss.

- Wake-word alone still auto-sends on silence; push-to-talk sends immediately on release.

#

Tutorial.step

Implemented (Dec 9, 2025)

- Overlay sessions now carry a token per capture (wake-word or push-to-talk). Partial/final/send/dismiss/level updates are dropped when token doesn't match, avoiding stale callbacks.

- Push-to-talk adopts any visible overlay text as a prefix (so pressing hotkey while wake overlay is up keeps text and appends new speech). It waits up to 1.5s for a final transcript before falling back to current text.

- Chime/overlay logging is emitted at ''info'' in categories ''voicewake.overlay'', ''voicewake.ptt'', and ''voicewake.chime'' (session start, partial, final, send, dismiss, chime reason).

#

Tutorial.step

Next steps

1. VoiceSessionCoordinator (actor)

- Owns exactly one ''VoiceSession'' at a time.

- API (token-based): ''beginWakeCapture'', ''beginPushToTalk'', ''updatePartial'', ''endCapture'', ''cancel'', ''applyCooldown''.

- Drops callbacks that carry stale tokens (prevents old recognizers from reopening overlay).

2. VoiceSession (model)

- Fields: ''token'', ''source'' (wakeWord|pushToTalk), committed/volatile text, chime flags, timers (auto-send, idle), ''overlayMode'' (display|editing|sending), cooldown deadline.

3. Overlay binding

- ''VoiceSessionPublisher'' (''ObservableObject'') mirrors active session into SwiftUI.

- ''VoiceWakeOverlayView'' renders only via publisher; it never mutates global singletons directly.

- Overlay user actions (''sendNow'', ''dismiss'', ''edit'') call back into coordinator with session token.

4. Unified send path

- On ''endCapture'': if trimmed text is empty β†’ dismiss; else ''performSend(session:)'' (plays send chime once, forwards, dismisses).

- Push-to-talk: no delay; wake-word: optional delay for auto-send.

- Apply a short cooldown to wake runtime after push-to-talk finishes so wake-word doesn't immediately retrigger.

5. Logging

- Coordinator emits ''.info'' logs in subsystem ''bot.molt'', categories ''voicewake.overlay'' and ''voicewake.chime''.

- Key events: ''session_started'', ''adopted_by_push_to_talk'', ''partial'', ''finalized'', ''send'', ''dismiss'', ''cancel'', ''cooldown''.

#

Tutorial.step

Debugging checklist

- Stream logs while reproducing a sticky overlay:

''''`bash", "p3": "sudo log stream --predicate 'subsystem == "bot.molt" AND category CONTAINS "voicewake"' --level info --style compact", "p4": "''''`

sudo log stream --predicate 'subsystem == "bot.molt" AND category CONTAINS "voicewake"' --level info --style compact

''''`

- Verify only one active session token; stale callbacks should be dropped by coordinator.

- Ensure push-to-talk release always calls ''endCapture'' with active token; if text is empty, expect ''dismiss'' without chime or send.

#

Tutorial.step

Migration steps (suggested)

1. Add ''VoiceSessionCoordinator'', ''VoiceSession'', and ''VoiceSessionPublisher''.

2. Refactor ''VoiceWakeRuntime'' to create/update/end sessions instead of touching ''VoiceWakeOverlayController'' directly.

3. Refactor ''VoicePushToTalk'' to adopt existing sessions and call ''endCapture'' on release; apply runtime cooldown.

4. Wire ''VoiceWakeOverlayController'' to publisher; remove direct calls from runtime/PTT.

5. Add integration tests for session adoption, cooldown, and empty-text dismissal.