MÓDULO 3.1

📡 Conectar canais

Como o OpenHuman aparece dentro do Slack, do WhatsApp, do Gmail e companhia — sem token, sem servidor intermediário, e (nos provedores migrados) sem injetar uma única linha de JavaScript na página de terceiro.

6
Tópicos
50
Minutos
Intermed.
Nível
Prático
Tipo
1

🌐 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
CHAVE
Webview account
CHAVE
Migrado vs legacy
CHAVE
CEF embarcado
CHAVE
Desktop-only
2

🔐 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.

1

Você abre Channels e escolhe um provedor

A UI cria uma acct_<id> e abre a janela do cliente oficial naquele perfil.

2

Você faz login normalmente

QR Code, e-mail+senha, OAuth — o que o provedor pedir. O OpenHuman só hospeda a janela.

3

Cookies e storage persistem

Na próxima abertura o cliente entra direto. Sessão expira só quando o provedor decide expirar.

4

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.

CHAVE
CEF profile
CHAVE
Isolamento por conta
CHAVE
Persistência de cookies
CHAVE
Re-login sob demanda
3

🛰️ 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 carregamento
  • Emulation.* — viewport, user agent, condições de rede
  • Input.* — 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 .js em webview_accounts/
  • Usar Page.addScriptToEvaluateOnNewDocument em provedor migrado
  • Anexar handlers em build_init_script
  • "Só uma linha de JS para resolver" — rejeitado em review
CHAVE
CDP > injeção
CHAVE
Scanner por provedor
CHAVE
Lógica em Rust
CHAVE
Zero injection
4

🧱 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.

CHAVE
Shrink, not grow
CHAVE
Recipe files
CHAVE
Plugin audit
CHAVE
Migrar > remendar
5

📬 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
CHAVE
Event bus tipado
CHAVE
Fan-out
CHAVE
Subscriber por domínio
CHAVE
Logs grep-friendly
6

🛠️ 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.

1

A janela do provedor está logada?

Abra Channels, clique na conta. Se o cliente pedir login/QR, o scanner não está vendo nada.

2

Status no painel de Channels

Cada conta mostra "conectado", "expirado" ou "erro". Expirado = re-login. Erro = log do scanner.

3

Logs do scanner

Procure por [scanner] + nome do provedor. CDP desconectado? Origem mudou? Página em outro idioma?

4

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.

CHAVE
Status do canal
CHAVE
Log do scanner
CHAVE
Re-login
CHAVE
Reset de perfil

📡 Resumo do módulo

Webview por conta — cada provedor roda em um perfil CEF isolado, com login feito pelo usuário.
CDP > injeção — provedores migrados não recebem JS nosso. Tudo é observado a partir do Rust.
Legacy não cresce — Gmail, LinkedIn, Meet ainda usam runtime.js, mas só para encolher.
Event bus distribui — mensagem chega no scanner, vira DomainEvent, vai para memória/agente/regras.
Problema = pareamento — comece pela janela do provedor antes de culpar o agente.

Próximo módulo:

3.2 — 🧠 Memória e contexto