# Event Socket `b2bcall-fs-events` (`apps/freeswitch-events`) mantém a conexão ESL permanente com o FreeSWITCH (agente.md secao 21) — nenhum outro serviço deve rodar `fs_cli` via shell pra ações operacionais. ## Biblioteca Usa [`esl`](https://www.npmjs.com/package/esl) (v11, mantida ativamente, TypeScript nativo, zero dependências de `libesl`). A classe `FreeSwitchClient` já resolve reconexão com backoff sozinha (agente.md secao 195) — só precisamos reagir a `connect`/`reconnecting`/`error` e resubscrever a cada `connect` (a lib entrega um objeto de chamada novo a cada reconexão). ## `packages/telephony` - `TelephonyProvider` (interface, agente.md secao 25) + `FreeSwitchTelephonyProvider` (implementação sobre `esl`). Métodos testados manualmente contra o FreeSWITCH rodando: `originate`, `killCall`, `getChannels`, `getCalls`, `getGateways`, `getRegistrations`, `reloadXml`. Os métodos de fila/agente (`setAgentStatus`, `addAgentToQueue`, ...) seguem a sintaxe documentada do `mod_callcenter` mas não foram exercitados contra uma fila real ainda — não existe nenhuma (fase Queues). - `normalizeEslEvent()`: traduz eventos ESL crus pro vocabulário interno (agente.md secao 24). Mapeamento de `callcenter::info`/`sofia::*` com nomes de campo confirmados contra uma fila real na fase Realtime Monitoring — ver "Achados" abaixo e docs/AGENTS.md. ## Achados durante os testes 1. **ACL implícita do Event Socket**: sem `apply-inbound-acl` explícito, o FreeSWITCH 1.11 rejeita ("Access Denied, go away.") qualquer conexão que não seja loopback — mesmo com a senha certa. Descoberto porque `b2bcall-fs-events` (outro container) não conseguia conectar. Corrigido criando uma ACL própria (`b2bcall_internal`, em `overrides/autoload_configs/acl.conf.xml`) cobrindo loopback + a rede interna do Docker Compose (`172.16.0.0/12`, nunca `0.0.0.0/0`). 2. Nessa mesma correção, um erro de digitação inicial (usar só `localnet.auto`, que cobre a rede Docker mas **não** loopback) quebrou até o `fs_cli` local — corrigido combinando as duas faixas na mesma ACL. 3. O logger JSON de `packages/shared` quebrava (`TypeError: Do not know how to serialize a BigInt`) porque a lib `esl` usa `bigint` nos campos de estatística de erro. Corrigido com um `replacer` no `JSON.stringify`. 4. **Bug real, só descoberto na fase Realtime Monitoring**: nenhum evento CUSTOM (`sofia::gateway_state`, `sofia::register`, `callcenter::info`) nunca chegou nesta sessão até então, apesar da subscrição incluir `"CUSTOM"` na lista de `SUBSCRIBED_EVENTS`. Causa: `event_json(...events)` manda `"CUSTOM"` como último token do comando `event json`, sem nenhum subclass depois — o mod_event_socket do FreeSWITCH exige que os subclasses venham imediatamente depois do token `CUSTOM` no mesmo comando pra serem entregues. Corrigido: `SUBSCRIBED_EVENTS` agora termina em `"CUSTOM", "sofia::register", "sofia::unregister", "sofia::expire", "sofia::gateway_state", "callcenter::info"`, e o loop que registra listeners `.on()` só usa os nomes "planos" (`PLAIN_EVENTS`) mais um único `.on("CUSTOM", ...)` — os nomes de subclass nunca viram listener, só compõem o comando de subscrição. ## Eventos consumidos e publicados Lista completa em `apps/freeswitch-events/src/main.ts` (`SUBSCRIBED_EVENTS`), cobrindo a secao 23 do `agente.md`. `HEARTBEAT` só é logado em debug, nunca normalizado. Eventos normalizados são publicados em JSON no canal Redis `b2bcall:events` (pub/sub simples — vira a base pra WebSocket multi-tenant na fase Realtime Monitoring, que ainda não existe). ## Verificado ponta a ponta Sem SIP real disponível ainda, a verificação usou uma chamada loopback local: ```bash docker exec b2bcall-freeswitch fs_cli -p "$ESL_PASSWORD" -x "originate null/_test_ &park()" # ... uuid_kill pra encerrar ``` Resultado observado no canal Redis: `CALL_CREATED` → `CALL_ANSWERED` → `CALL_ENDED` (com `hangupCause`), todos com o `callUuid` correto. ## Limitações desta fase - Reconciliação pós-reconexão (secao 195: "reconcile calls, agents, queues, registrations, gateways") não é possível ainda — não existem tabelas de `calls`/`agents`/`queues` persistidas pra reconciliar contra. Só a resubscrição de eventos está implementada. Revisitar quando essas tabelas existirem. - `b2bcall-fs-events` roda via `tsx` direto (sem etapa de build/`dist`) — simples mas ~99MB de RAM em runtime (razoável no orçamento atual, mas vale revisar se muitos workers assim rodarem juntos mais pra frente).