macOS companion app
Sobreposição de voz
Ciclo de vida da sobreposição de voz (macOS)
Público: colaboradores do app para macOS. Objetivo: manter a sobreposição de voz previsível quando a palavra de ativação e o pressionar para falar se sobrepõem.
Intenção atual
- Se a sobreposição já estiver visível por causa da palavra de ativação e o usuário pressionar a tecla de atalho, a sessão da tecla de atalho adota o texto existente em vez de redefini-lo. A sobreposição permanece visível enquanto a tecla de atalho estiver pressionada. Quando o usuário solta: envia se houver texto aparado; caso contrário, dispensa.
- Apenas a palavra de ativação ainda envia automaticamente no silêncio; pressionar para falar envia imediatamente ao soltar.
Implementado (9 de dez. de 2025)
- As sessões de sobreposição agora carregam um token por captura (palavra de ativação ou pressionar para falar). Atualizações parciais/finais/de envio/de dispensa/de nível são descartadas quando o token não corresponde, evitando callbacks obsoletos.
- Pressionar para falar adota qualquer texto de sobreposição visível como prefixo (então pressionar a tecla de atalho enquanto a sobreposição de ativação está visível mantém o texto e acrescenta a nova fala). Ele aguarda até 1,5 s por uma transcrição final antes de recorrer ao texto atual.
- O registro de chime/sobreposição é emitido em
infonas categoriasvoicewake.overlay,voicewake.pttevoicewake.chime(início da sessão, parcial, final, envio, dispensa, motivo do chime).
Próximos passos
- VoiceSessionCoordinator (actor)
- Possui exatamente uma
VoiceSessionpor vez. - API (baseada em token):
beginWakeCapture,beginPushToTalk,updatePartial,endCapture,cancel,applyCooldown. - Descarta callbacks que carregam tokens obsoletos (impede que reconhecedores antigos reabram a sobreposição).
- Possui exatamente uma
- VoiceSession (modelo)
- Campos:
token,source(wakeWord|pushToTalk), texto confirmado/volátil, flags de chime, temporizadores (envio automático, ocioso),overlayMode(display|editing|sending), prazo de cooldown.
- Campos:
- Vínculo da sobreposição
VoiceSessionPublisher(ObservableObject) espelha a sessão ativa no SwiftUI.VoiceWakeOverlayViewrenderiza apenas via publisher; nunca altera singletons globais diretamente.- Ações do usuário na sobreposição (
sendNow,dismiss,edit) chamam de volta o coordenador com o token da sessão.
- Caminho unificado de envio
- Em
endCapture: se o texto aparado estiver vazio → dispensar; caso contrário,performSend(session:)(toca o chime de envio uma vez, encaminha, dispensa). - Pressionar para falar: sem atraso; palavra de ativação: atraso opcional para envio automático.
- Aplique um cooldown curto ao runtime de ativação após o pressionar para falar terminar, para que a palavra de ativação não seja acionada novamente de imediato.
- Em
- Registro
- O coordenador emite logs
.infono subsistemaai.openclaw, categoriasvoicewake.overlayevoicewake.chime. - Eventos principais:
session_started,adopted_by_push_to_talk,partial,finalized,send,dismiss,cancel,cooldown.
- O coordenador emite logs
Checklist de depuração
-
Faça streaming dos logs ao reproduzir uma sobreposição presa:
sudo log stream --predicate 'subsystem == "ai.openclaw" AND category CONTAINS "voicewake"' --level info --style compact -
Verifique que há apenas um token de sessão ativo; callbacks obsoletos devem ser descartados pelo coordenador.
-
Garanta que soltar o pressionar para falar sempre chame
endCapturecom o token ativo; se o texto estiver vazio, esperedismisssem chime nem envio.
Etapas de migração (sugeridas)
- Adicione
VoiceSessionCoordinator,VoiceSessioneVoiceSessionPublisher. - Refatore
VoiceWakeRuntimepara criar/atualizar/encerrar sessões em vez de tocar diretamente emVoiceWakeOverlayController. - Refatore
VoicePushToTalkpara adotar sessões existentes e chamarendCaptureao soltar; aplique cooldown ao runtime. - Conecte
VoiceWakeOverlayControllerao publisher; remova chamadas diretas do runtime/PTT. - Adicione testes de integração para adoção de sessão, cooldown e dispensa com texto vazio.