Mande uma mensagem de voz no Telegram e a resposta volta como voice note — transcrição na entrada (Whisper embarcado por padrão, zero config), síntese na saída, com qualquer backend TTS configurado. Tudo aqui é agnóstico a provedor: do say do macOS ao motor embarcado Kokoro.
Modos (CHATCLI_GATEWAY_VOICE_REPLY)
| Valor | Comportamento |
|---|
auto (padrão) | Responde na mesma moeda: mensagem de voz → resposta com áudio; texto → só texto. |
always | Toda resposta final sai com áudio anexado. |
never | Respostas só em texto. |
Valores booleanos legados seguem válidos: true → always, false → never. Um valor desconhecido cai em auto — um typo nunca silencia o gateway.
# padrão já é auto; só é preciso exportar para always/never
export CHATCLI_GATEWAY_VOICE_REPLY=always
O daemon herda o ambiente da shell que executou /gateway start — e o .env nunca sobrescreve variável já exportada. Se o comportamento não bater com o .env, confira o modo efetivo no boot do daemon: gateway.log registra voice replies enabled (mode=...).
Cada conversa manda na própria preferência pedindo em linguagem natural: o modelo chama a tool @voice, e a escolha fica gravada por sessão (sobrevive a restart do daemon) com precedência sobre o modo global.
| Pedido na conversa | Efeito |
|---|
| ”responde em áudio” / “quero ouvir sua voz” | @voice on — toda resposta desta conversa sai com voz |
| ”para de mandar áudio” | @voice off — só texto nesta conversa |
| ”volta ao normal” | @voice auto — de volta ao padrão (voz responde voz) |
A preferência fica em ~/.chatcli/gateway_voice_prefs.json (escrita atômica), chaveada por plataforma:chat. Hierarquia de decisão: preferência da conversa → modo global → in-kind.
Escrita para o ouvido (speech-aware)
Quando a resposta vai virar áudio, duas camadas garantem fala natural:
- O modelo sabe antes de escrever: uma diretiva avisa que a resposta será falada — prosa conversacional, frases curtas, sem emojis, sem listas/tabelas/markdown.
- Garantia dura no sanitizador: antes da síntese,
StripForSpeech achata markdown (código removido, links viram rótulo, tabelas viram prosa) e remove emojis e pictogramas — motores de TTS leem o nome Unicode deles em voz alta, enterrando a mensagem. Acentos do português ficam intactos.
O texto visual no chat continua com a formatação completa; só o áudio é higienizado.
Voice note que toca (transcode)
Backends que ignoram o formato pedido (o say do macOS emite aiff, espeak emite wav) produziam um arquivo que o Telegram mostra com tamanho mas não reproduz. Com ffmpeg no PATH, o gateway transcoda wav/aiff → OGG/Opus (perfil de voice note: 48 kHz mono) para qualquer provedor; formatos já comprimidos passam direto, e sem ffmpeg o clipe original segue como arquivo de áudio — degradação visível, nunca resposta perdida.
Pipeline completo
voice note (Telegram) ─→ transcrição (whisper local-first) ─→ agent loop
│ diretiva speech-aware
▼
texto da resposta ─→ StripForSpeech ─→ TTS (qualquer backend) ─→ transcode ogg/opus ─→ sendVoice
A decisão “esta resposta fala?” é uma regra única (preferência da conversa → modo global → in-kind) compartilhada entre o runner e o agent loop — os dois nunca divergem.
Solução de problemas
| Sintoma | Causa provável | Verificação |
|---|
| Tudo volta com áudio, até texto | Modo always ativo (env exportada na shell do daemon) | gateway.log: voice replies enabled (mode=...) |
| Áudio chega mas não toca | Formato cru sem ffmpeg para transcodar | which ffmpeg; instale para voice notes nativas |
| Nunca responde em voz | Modo never, ou nenhum backend TTS | /config → Integrações · Gateway · Resposta em voz |
| Voz errada para o idioma | Vozes do motor embarcado | CHATCLI_TTS_VOICE (en) / CHATCLI_TTS_VOICE_PT (pt) |
Relacionado