> ## Documentation Index
> Fetch the complete documentation index at: https://openclaw.veiseule.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Голосовой оверлей

# Жизненный цикл голосового оверлея (macOS)

Аудитория: участники разработки приложения для macOS. Цель: сделать поведение голосового оверлея предсказуемым, когда пересекаются ключевое слово активации и push-to-talk.

## Текущий замысел

* Если оверлей уже виден из‑за ключевого слова активации и пользователь нажимает горячую клавишу, сеанс по горячей клавише *подхватывает* существующий текст вместо его сброса. Оверлей остаётся на экране, пока удерживается горячая клавиша. Когда пользователь отпускает её: отправить, если есть обрезанный текст, иначе закрыть.
* Ключевое слово активации само по себе по‑прежнему автоматически отправляет по тишине; push-to-talk отправляет сразу при отпускании.

## Реализовано (9 декабря 2025)

* Сеансы оверлея теперь несут токен на каждый захват (ключевое слово активации или push-to-talk). Обновления partial/final/send/dismiss/level отбрасываются, если токен не совпадает, что предотвращает устаревшие колбэки.
* Push-to-talk подхватывает любой видимый текст оверлея как префикс (поэтому нажатие горячей клавиши, когда оверлей от ключевого слова уже открыт, сохраняет текст и добавляет новую речь). Он ждёт до 1,5 с финальной транскрипции, прежде чем откатиться к текущему тексту.
* Логирование звукового сигнала/оверлея выводится под `info` в категориях `voicewake.overlay`, `voicewake.ptt` и `voicewake.chime` (начало сеанса, partial, final, send, dismiss, причина звукового сигнала).

## Следующие шаги

1. **VoiceSessionCoordinator (actor)**
   * Владеет ровно одним `VoiceSession` в каждый момент времени.
   * API (на основе токенов): `beginWakeCapture`, `beginPushToTalk`, `updatePartial`, `endCapture`, `cancel`, `applyCooldown`.
   * Отбрасывает колбэки со старыми токенами (предотвращает повторное открытие оверлея старыми распознавателями).
2. **VoiceSession (model)**
   * Поля: `token`, `source` (wakeWord|pushToTalk), зафиксированный/временный текст, флаги звуковых сигналов, таймеры (автоотправка, простой), `overlayMode` (display|editing|sending), дедлайн кулдауна.
3. **Привязка оверлея**
   * `VoiceSessionPublisher` (`ObservableObject`) зеркалирует активный сеанс в SwiftUI.
   * `VoiceWakeOverlayView` рендерит только через publisher; он никогда напрямую не мутирует глобальные синглтоны.
   * Действия пользователя в оверлее (`sendNow`, `dismiss`, `edit`) вызывают координатор обратно с токеном сеанса.
4. **Единый путь отправки**
   * При `endCapture`: если обрезанный текст пуст → закрыть; иначе `performSend(session:)` (проигрывает звуковой сигнал отправки один раз, пересылает, закрывает).
   * Push-to-talk: без задержки; ключевое слово активации: опциональная задержка для автоотправки.
   * Применить короткий кулдаун к рантайму ключевого слова активации после завершения push-to-talk, чтобы ключевое слово не срабатывало сразу же повторно.
5. **Логирование**
   * Координатор выводит логи `.info` в подсистеме `bot.molt`, категориях `voicewake.overlay` и `voicewake.chime`.
   * Ключевые события: `session_started`, `adopted_by_push_to_talk`, `partial`, `finalized`, `send`, `dismiss`, `cancel`, `cooldown`.

## Контрольный список отладки

* Стримьте логи при воспроизведении «залипшего» оверлея:

  ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
  sudo log stream --predicate 'subsystem == "bot.molt" AND category CONTAINS "voicewake"' --level info --style compact
  ```

* Убедитесь, что активен только один токен сеанса; устаревшие колбэки должны отбрасываться координатором.

* Убедитесь, что отпускание push-to-talk всегда вызывает `endCapture` с активным токеном; если текст пуст, ожидайте `dismiss` без звукового сигнала и отправки.

## Шаги миграции (рекомендуется)

1. Добавить `VoiceSessionCoordinator`, `VoiceSession` и `VoiceSessionPublisher`.
2. Рефакторить `VoiceWakeRuntime`, чтобы создавать/обновлять/завершать сеансы вместо прямого обращения к `VoiceWakeOverlayController`.
3. Рефакторить `VoicePushToTalk`, чтобы подхватывать существующие сеансы и вызывать `endCapture` при отпускании; применить кулдаун рантайма.
4. Подключить `VoiceWakeOverlayController` к publisher; убрать прямые вызовы из рантайма/PTT.
5. Добавить интеграционные тесты для подхвата сеанса, кулдауна и закрытия при пустом тексте.
