feat: add telephony layer and asterisk-events worker

- packages/telephony: cliente AMI proprio sobre TCP puro (sem dependencia
  de terceiros pouco mantida) + interface TelephonyProvider +
  AsteriskTelephonyProvider (Originate, Hangup, QueuePause/Add/Remove,
  QueueStatus, ExtensionState, DeviceState, PJSIPShowEndpoints/Contacts,
  Reload, runCommand, stream de eventos). Testado contra o Asterisk real —
  o formato de resposta do Command mudou entre versoes do Asterisk
  (headers 'Output:' repetidos em vez de 'Response: Follows'/'--END
  COMMAND--'), corrigido apos inspecionar os bytes crus do protocolo
- apps/asterisk-events: worker dedicado a manter a conexao AMI viva,
  normalizar eventos (Newchannel, DialBegin/End, Hangup, DeviceStateChange,
  ContactStatus, eventos de fila/agente), persistir ExtensionState no
  Postgres e publicar em Redis pub/sub para consumo em tempo real.
  Containerizado, alcanca o Asterisk (host network) via
  host.docker.internal a partir da rede bridge. Heartbeat no Redis para
  health check
- packages/database: novos modelos Trunk, Extension, ExtensionState
  (migration aplicada)
- packages/shared: secret-crypto.ts (AES-256-GCM para credenciais de trunk
  e senha SIP em repouso, master key externa ao banco)

Testado ponta a ponta: chamada real originada -> eventos normalizados
recebidos via Redis SUBSCRIBE, heartbeat renovando no TTL correto.
This commit is contained in:
2026-08-27 12:41:41 -03:00
parent a2898fa566
commit 6f3d731581
20 changed files with 1059 additions and 4 deletions

18
TODO.md
View File

@@ -92,11 +92,23 @@ mestre original (`agente.md`, seções 90-93).
externo — trocar MailerService por nodemailer quando houver
## Fase 4 — Telefonia (camada de aplicação)
- [ ] packages/telephony: TelephonyProvider + AsteriskTelephonyProvider
- [ ] apps/asterisk-events (AMI listener, normalização, persistência, pub/sub)
- [x] packages/telephony: cliente AMI próprio (TCP raw, sem dependência de
terceiros) + TelephonyProvider + AsteriskTelephonyProvider — testado
contra o Asterisk real: login, PJSIPShowEndpoints/QueueStatus (lista
vazia tratada corretamente), Command (parsing do formato real do
Asterisk 22: múltiplos headers "Output:", não mais "Follows"/"--END
COMMAND--"), Originate, stream de eventos
- [x] apps/asterisk-events (worker dedicado, containerizado) — conecta via
host.docker.internal (rede bridge -> Asterisk em host network),
normaliza eventos relevantes, persiste ExtensionState (Postgres),
publica em Redis pub/sub (b2bcall:events:asterisk e :extensions),
heartbeat para health check — testado ponta a ponta com chamada real
- [x] Schema Prisma: Trunk, Extension, ExtensionState + criptografia de
segredos AES-256-GCM (packages/shared/secret-crypto.ts)
- [ ] CRUD Troncos (com CPS máximo, ACL, teste de status)
- [ ] CRUD Ramais (senha SIP gerada, reset, status tempo real)
- [ ] Painel visual de Ramais (WebSocket, prioridade de cores)
- [ ] Painel visual de Ramais (WebSocket, prioridade de cores) — backend
(gateway WS) pendente; ExtensionState/pub-sub já prontos como base
- [ ] Dialplan estruturado (versionado, modo Advanced, validação+rollback)
- [ ] Administração do Asterisk (abas: geral, pjsip, rtp, filas, cdr, cel, logs,
ami, ari, modules, diagnóstico com allowlist de comandos)