Voice-Overlay-Lebenszyklus (macOS)

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

Aktuelle Absicht

  • Wenn das Overlay bereits durch ein Wake-Word sichtbar ist und der Benutzer die Hotkey-Taste drückt, übernimmt die Hotkey-Session den bestehenden Text, statt ihn zurückzusetzen. Das Overlay bleibt sichtbar, solange die Taste gehalten wird. Beim Loslassen: senden wenn getrimmter Text vorhanden ist, andernfalls schließen.
  • Wake-Word allein sendet weiterhin automatisch bei Stille; Push-to-Talk sendet sofort beim Loslassen.

Implementiert (9. Dez. 2025)

  • Overlay-Sessions tragen jetzt ein Token pro Erfassung (Wake-Word oder Push-to-Talk). Partial/Final/Send/Dismiss/Level-Updates werden verworfen, wenn das Token nicht übereinstimmt, was veraltete Callbacks vermeidet.
  • Push-to-Talk übernimmt sichtbaren Overlay-Text als Präfix (das Drücken der Hotkey-Taste während das Wake-Overlay oben ist, behält den Text und hängt neue Sprache an). Es wartet bis zu 1,5s 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-Grund).

Nächste Schritte

  1. VoiceSessionCoordinator (Actor)
    • Besitzt 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 nach SwiftUI.
    • VoiceWakeOverlayView rendert nur über den Publisher; es ändert niemals globale Singletons direkt.
    • Overlay-Benutzeraktionen (sendNow, dismiss, edit) rufen zurück in den Coordinator mit dem Session-Token.
  4. Einheitlicher Send-Pfad
    • Bei endCapture: wenn getrimmter Text leer ist → schließen; sonst performSend(session:) (spielt Send-Chime einmal, leitet weiter, schließt).
    • Push-to-Talk: kein Delay; Wake-Word: optionales Delay für Auto-Send.
    • Kurzen Cooldown auf die Wake-Runtime anwenden, nachdem Push-to-Talk beendet ist, damit Wake-Word nicht sofort erneut auslöst.
  5. Logging
    • Coordinator gibt .info-Logs im Subsystem ai.openclaw aus, Kategorien voicewake.overlay und voicewake.chime.
    • Schlüssel-Events: session_started, adopted_by_push_to_talk, partial, finalized, send, dismiss, cancel, cooldown.

Debugging-Checkliste

  • Logs streamen, während ein hängendes Overlay reproduziert wird:

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

  • Stelle sicher, dass das Loslassen von Push-to-Talk immer endCapture mit dem aktiven Token aufruft; bei leerem Text erwarte dismiss ohne Chime oder Send.

Migrationsschritte (vorgeschlagen)

  1. VoiceSessionCoordinator, VoiceSession und VoiceSessionPublisher hinzufügen.
  2. VoiceWakeRuntime refaktorisieren, um Sessions zu erstellen/aktualisieren/beenden, statt VoiceWakeOverlayController direkt anzusprechen.
  3. VoicePushToTalk refaktorisieren, um bestehende Sessions zu übernehmen und endCapture beim Loslassen aufzurufen; Runtime-Cooldown anwenden.
  4. VoiceWakeOverlayController an den Publisher binden; direkte Aufrufe aus Runtime/PTT entfernen.
  5. Integrationstests für Session-Übernahme, Cooldown und Dismiss bei leerem Text hinzufügen.