mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-16 14:01:36 +00:00
Source-grounded rewrite of 529 published docs pages with per-unit information-loss verification: 1,713 factual corrections cited to src/**, generated surfaces regenerated, frontmatter titles preserved for i18n, release notes pages untouched. All docs gates green. Closes #100141
56 lines
3.4 KiB
Markdown
56 lines
3.4 KiB
Markdown
---
|
|
summary: "Voice overlay lifecycle when wake-word and push-to-talk overlap"
|
|
read_when:
|
|
- Adjusting voice overlay behavior
|
|
title: "Voice overlay"
|
|
---
|
|
|
|
# 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 @Observable` singleton, not an actor. API: `startSession`, `updatePartial`, `finalize`, `sendNow`, `dismiss`, `updateLevel`, `snapshot`. Each session carries a `UUID` token; 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 as `adoptedPrefix` (via `VoiceSessionCoordinator.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 calls `VoiceSessionCoordinator.overlayDidDismiss`, which triggers `VoiceWakeRuntime.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 `sendNow` plays the send chime once, forwards via `VoiceWakeForwarder`, then dismisses.
|
|
|
|
## Logging
|
|
|
|
Voice subsystem is `ai.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:
|
|
|
|
```bash
|
|
sudo log stream --predicate 'subsystem == "ai.openclaw" AND category CONTAINS "voicewake"' --level info --style compact
|
|
```
|
|
|
|
- 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.
|
|
|
|
## Related
|
|
|
|
- [macOS app](/platforms/macos)
|
|
- [Voice wake (macOS)](/platforms/mac/voicewake)
|
|
- [Talk mode](/nodes/talk)
|