Voice Overlay Lifecycle (macOS)

Zielgruppe: macOS App Contributors. Ziel: Das Voice Overlay vorhersehbar halten, wenn Wake-Word und Push-to-Talk sich überlappen.

Aktuelle Absicht

  • Wenn das Overlay bereits durch das Wake-Word sichtbar ist und du den Hotkey drückst, übernimmt die Hotkey-Session den vorhandenen Text, anstatt ihn zurückzusetzen. Das Overlay bleibt offen, solange du den Hotkey gedrückt hältst. Beim Loslassen: Senden, falls getrimmter Text vorhanden ist, sonst schließen.
  • Wake-Word allein sendet weiterhin automatisch bei Stille; Push-to-Talk sendet sofort beim Loslassen.

Implementiert (9. Dezember 2025)

  • Overlay Sessions tragen jetzt ein Token pro Capture (Wake-Word oder Push-to-Talk). Partial/Final/Send/Dismiss/Level Updates werden verworfen, wenn das Token nicht übereinstimmt – so werden veraltete Callbacks vermieden.
  • Push-to-Talk übernimmt sichtbaren Overlay-Text als Präfix (wenn du also den Hotkey drückst, während das Wake-Overlay offen ist, bleibt der Text erhalten und neue Sprache wird angehängt). Es wartet bis zu 1,5 Sekunden auf ein finales Transkript, bevor es auf den aktuellen Text zurückfällt.
  • Chime/Overlay Logging wird auf info-Level in den Kategorien voicewake.overlay, voicewake.ptt und voicewake.chime ausgegeben (Session Start, Partial, Final, Send, Dismiss, Chime Reason).

Nächste Schritte

  1. VoiceSessionCoordinator (Actor)
    • Verwaltet genau eine VoiceSession gleichzeitig.
    • API (Token-basiert): beginWakeCapture, beginPushToTalk, updatePartial, endCapture, cancel, applyCooldown.
    • Verwirft Callbacks mit veralteten Tokens (verhindert, dass alte Recognizer das Overlay erneut öffnen).
  2. VoiceSession (Model)
    • Felder: token, source (wakeWord|pushToTalk), committed/volatile Text, Chime Flags, Timer (auto-send, idle), overlayMode (display|editing|sending), Cooldown Deadline.
  3. Overlay Binding
    • VoiceSessionPublisher (ObservableObject) spiegelt die aktive Session in SwiftUI.
    • VoiceWakeOverlayView rendert nur über den Publisher; es mutiert niemals direkt globale Singletons.
    • Overlay User Actions (sendNow, dismiss, edit) rufen den Coordinator mit dem Session Token auf.
  4. Unified Send Path
    • Bei endCapture: Wenn getrimmter Text leer ist → schließen; sonst performSend(session:) (spielt Send Chime einmal ab, leitet weiter, schließt).
    • Push-to-Talk: keine Verzögerung; Wake-Word: optionale Verzögerung für Auto-Send.
    • Wende eine kurze Cooldown-Phase auf die Wake Runtime an, nachdem Push-to-Talk beendet ist, damit Wake-Word nicht sofort erneut triggert.
  5. Logging
    • Coordinator gibt .info Logs im Subsystem bot.molt aus, Kategorien voicewake.overlay und voicewake.chime.
    • Wichtige Events: session_started, adopted_by_push_to_talk, partial, finalized, send, dismiss, cancel, cooldown.

Debugging Checklist

  • Streame Logs, während du ein klebendes Overlay reproduzierst:

    sudo log stream --predicate 'subsystem == "bot.molt" AND category CONTAINS "voicewake"' --level info --style compact
  • Prüfe, dass nur ein aktives Session Token existiert; veraltete Callbacks sollten vom Coordinator verworfen werden.

  • Stelle sicher, dass Push-to-Talk Release immer endCapture mit dem aktiven Token aufruft; wenn Text leer ist, erwarte dismiss ohne Chime oder Send.

Migrations-Schritte (Vorschlag)

  1. Füge VoiceSessionCoordinator, VoiceSession und VoiceSessionPublisher hinzu.
  2. Refaktoriere VoiceWakeRuntime, um Sessions zu erstellen/aktualisieren/beenden, anstatt VoiceWakeOverlayController direkt zu berühren.
  3. Refaktoriere VoicePushToTalk, um bestehende Sessions zu übernehmen und endCapture beim Loslassen aufzurufen; wende Runtime Cooldown an.
  4. Verbinde VoiceWakeOverlayController mit dem Publisher; entferne direkte Aufrufe von Runtime/PTT.
  5. Füge Integrationstests für Session Adoption, Cooldown und Empty-Text Dismissal hinzu.