macOS companion app

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

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

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

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

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

  • Сессии оверлея теперь используют токен для каждого захвата (wake-word или push-to-talk). Частичные/финальные/отправленные/скрытые обновления и обновления уровня отбрасываются, если токен не совпадает, что позволяет избежать устаревших обратных вызовов.
  • Push-to-talk принимает любой видимый текст оверлея в качестве префикса (так что нажатие горячей клавиши, когда оверлей wake-word активен, сохраняет текст и добавляет новую речь). Он ждет до 1,5 секунд финальной транскрипции, прежде чем вернуться к текущему тексту.
  • Логирование звуковых сигналов/оверлея выводится на уровне info в категориях voicewake.overlay, voicewake.ptt и voicewake.chime (начало сессии, частичный, финальный, отправка, скрытие, причина звукового сигнала).

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

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

Чеклист для отладки

  • Потоковое логирование при воспроизведении зависшего оверлея:

    Копировать

    sudo log stream --predicate 'subsystem == "ai.openclaw" 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 к издателю; удалите прямые вызовы из runtime/PTT.
  5. Добавьте интеграционные тесты для принятия сессии, перерыва и скрытия при пустом тексте.

Voice WakeWebChat