🌐 Canais suportados
O OpenHuman fala com pessoas onde elas já estão. Em vez de inventar um chat próprio, ele
embarca o cliente web oficial de cada provedor dentro de
uma janela CEF — e o agente lê e escreve a partir dali. Hoje há dois grupos: os migrados
(lidos via CDP, sem JS injetado) e os legacy (ainda com runtime.js).
📋 Catálogo atual
- •Migrados (zero injection): WhatsApp, Telegram, Slack, Discord, browserscan
- •Legacy (com runtime.js): Gmail, LinkedIn, Google Meet
- •Mensagens nativas: iMessage e Google Messages (via scanners dedicados)
- •Voz/vídeo: Meet (audio, call, video) via módulos próprios no Tauri shell
✓ Vantagens dessa abordagem
- ✓Funciona com provedores sem API pública (WhatsApp Web, Telegram Web)
- ✓Reutiliza autenticação do cliente oficial — 2FA, SSO, biometria
- ✓Nada sai da sua máquina sem você decidir
✗ Limites a aceitar
- ✗Quando o provedor muda o HTML, o scanner pode quebrar
- ✗Headless puro não é opção — precisa de janela CEF rodando
- ✗Não há "modo servidor 24/7" — é desktop por design
🔐 Pareamento via webview
Pareamento é simplesmente login dentro da janela embarcada. Você escaneia o QR do WhatsApp, faz OAuth do Gmail, digita seu Slack — exatamente como faria no navegador. A sessão fica em um perfil CEF persistente, isolada por conta.
Você abre Channels e escolhe um provedor
A UI cria uma acct_<id> e abre a janela do cliente oficial naquele perfil.
Você faz login normalmente
QR Code, e-mail+senha, OAuth — o que o provedor pedir. O OpenHuman só hospeda a janela.
Cookies e storage persistem
Na próxima abertura o cliente entra direto. Sessão expira só quando o provedor decide expirar.
Scanner começa a observar
CDP conecta no perfil, lê requests/responses e emite eventos para o domínio channels.
💡 Dica prática
Uma conta = um perfil CEF. Quer rodar dois Slacks (pessoal + trabalho)? Crie duas acct_*. Não tente compartilhar perfil — cookies se misturam e a sessão quebra.
🛰️ Scanners CDP nativos
Aqui está o pulo do gato. Os provedores migrados — WhatsApp, Telegram, Slack, Discord — carregam com zero JavaScript injetado. Toda observação acontece via Chrome DevTools Protocol, falando direto com o motor do navegador a partir do Rust.
📊 O que o CDP cobre
Network.*— capturar requests e responses (mensagens chegando via WS, REST)Page.*— navegação, recarga, eventos de carregamentoEmulation.*— viewport, user agent, condições de redeInput.*— sintetizar clique/digitação quando precisar mandar mensagem
Estrutura no código
app/src-tauri/src/ ├── whatsapp_scanner/ ← CDP, zero JS ├── telegram_scanner/ ← CDP, zero JS ├── slack_scanner/ ← CDP, zero JS ├── discord_scanner/ ← CDP, zero JS ├── gmessages_scanner/ ← mensagens nativas └── imessage_scanner/ ← mensagens nativas
✓ Por que CDP em vez de injetar JS
- ✓Nada do nosso código roda dentro de origem de terceiro
- ✓Provedor não consegue detectar/bloquear injeção
- ✓Lógica fica em Rust, com testes e tipagem
- ✓Atualização do provedor quebra menos coisa
✗ O que NÃO fazer
- ✗Adicionar novos
.jsemwebview_accounts/ - ✗Usar
Page.addScriptToEvaluateOnNewDocumentem provedor migrado - ✗Anexar handlers em
build_init_script - ✗"Só uma linha de JS para resolver" — rejeitado em review
🧱 Provedores legacy (Gmail, LinkedIn, Meet)
Antes da migração, alguns provedores usavam um runtime.js injetado para observar a página. Esses estão grandfathered: continuam funcionando, mas a regra é clara — devem encolher, não crescer.
⚠️ Atenção em PRs
Qualquer PR que adicione novos blocos de JS injetado em provedores migrados será rejeitado. Para legacy, novo comportamento só entra se justificar — caso contrário, migre o provedor para CDP.
Olho também em plugins Tauri que injetam JS por default (tauri-plugin-opener ships init-iife.js). Configure com .open_js_links_on_click(false) ou opt-out equivalente.
Onde o legacy mora
- •
webview_accounts/— recipe files de Gmail, LinkedIn - •
runtime.js— bridge que entra na página - •
build_init_script— composição dos blocos
Caminho de migração
- • Replicar observação via CDP
- • Mover scraping para
*_scanner/ - • Remover script da página de origem
💡 Dica prática
Se você "precisa" injetar JS para resolver algo (ex.: interceptar um click cujo preventDefault roda no JS da própria página), o caminho correto é documentar a limitação, não burlar a regra.
📬 Fluxo de mensagens
Mensagem chegou no Slack. Como ela vira "o agente respondeu"? A jornada passa pelo event bus tipado e por um subscriber específico do domínio.
Pipeline
[scanner CDP]
↓ raw event
[channels domain]
↓ DomainEvent::Channel(...)
[event bus] ──── publish_global ────►
↓
[ChannelInboundSubscriber]
↓
[memory] + [agent runtime] + [regras / cron]
📊 Pontos de log úteis
[scanner]— chegou no nível de protocolo[channels]— virou modelo de domínio[bus]— foi publicado para subscribers[memory]— entrou no pipeline de ingest[agent]— agente decidiu o que fazer
✓ Vantagens do event bus
- ✓Vários subscribers sem acoplamento
- ✓Tipado —
DomainEventé enum - ✓Substituível em teste (re-registrar handler)
✗ Onde costuma "sumir"
- ✗Scanner não conectou (perfil errado)
- ✗Subscriber registrou tarde demais
- ✗Domínio não publicou o evento
🛠️ Troubleshooting de pareamento
"O agente não responde no Slack." Quase sempre é pareamento, não bug no agente. Esta é a sequência de checagem rápida.
A janela do provedor está logada?
Abra Channels, clique na conta. Se o cliente pedir login/QR, o scanner não está vendo nada.
Status no painel de Channels
Cada conta mostra "conectado", "expirado" ou "erro". Expirado = re-login. Erro = log do scanner.
Logs do scanner
Procure por [scanner] + nome do provedor. CDP desconectado? Origem mudou? Página em outro idioma?
Reset do perfil CEF
Último recurso: remova o perfil da conta e refaça login. Cookies corrompidos somem.
💡 Dica prática
Antes de "reportar bug", reproduza com a janela do provedor visível. 80% das vezes o problema fica óbvio: notificação de 2FA pendente, sessão suspensa, captcha.
📡 Resumo do módulo
DomainEvent, vai para memória/agente/regras.Próximo módulo:
3.2 — 🧠 Memória e contexto