Achado ao iniciar a fase de Realtime Monitoring: nenhum evento CUSTOM
(sofia::register, sofia::gateway_state, callcenter::info) jamais chegou em
b2bcall-fs-events nesta sessao, apesar do estado real do FreeSWITCH mudar de
verdade (confirmado originando uma chamada de teste pra dentro de uma fila
real com agente logado). Isso deixava dois gaps documentados como "nao
verificado" em docs/TRUNKS.md e docs/AGENTS.md.
Causa raiz: `event_json(...SUBSCRIBED_EVENTS)` mandava "CUSTOM" como ultimo
token do comando `event json`, sem nenhum subclass depois. O
mod_event_socket do FreeSWITCH exige que os subclasses (callcenter::info,
sofia::register, ...) venham imediatamente depois do token CUSTOM no mesmo
comando — sem isso, zero eventos CUSTOM sao entregues, de qualquer
subclass.
Corrigido separando PLAIN_EVENTS (nomes normais, cada um vira um listener
.on()) de CUSTOM_SUBCLASSES (so compoe o comando de subscricao — o client
ESL sempre emite "CUSTOM" como nome de evento, com o subclass real no
header Event-Subclass). Corrigido tambem um bug de nome de campo:
normalizeCustomEvent lia CC-Agent-Status, que nao existe; o campo real e
CC-Agent-State.
Com o pipeline corrigido, chegam eventos ricos de callcenter::info nunca
antes observados: agent-offering, bridge-agent-fail, members-count (fila em
tempo real) e member-queue-end (com CC-Cause/CC-Cancel-Reason e timestamps
de entrada/saida — atendida vs. abandonada). Adicionados como novos tipos
normalizados: AGENT_OFFERED_CALL, AGENT_BRIDGE_FAILED, QUEUE_MEMBER_COUNT,
QUEUE_MEMBER_LEFT.
De quebra, achado e corrigido um segundo bug real ao reverificar Trunks com
o pipeline de eventos funcionando: `sofia profile external rescan` nunca
descarregava um gateway cujo arquivo .xml foi apagado (fica fantasma na
memoria do Sofia indefinidamente). trunk-sync.ts agora roda `sofia profile
external killgw <nome>` pra cada gateway removido, antes do rescan.
Reverificado ponta a ponta pra ambos os bugs:
- Trunk com register:true apontando pra host inexistente: Trunk.status no
banco passa de UNKNOWN pra FAILED sozinho, via evento, sem polling.
- Trunk apagado via API: gateway some imediatamente de `sofia status
gateway`, sem esperar reinicio de profile.
- Chamada de teste real numa fila com agente logado: members-count,
agent-offering, agent-state-change (CC-Agent-State correto: Waiting/
Receiving), bridge-agent-fail, todos chegando certos no canal Redis
b2bcall:events.
typecheck do workspace inteiro limpo.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X1HxY46WGU4G1zmVDNKcWw
- trunks table (tenant-scoped, RLS): host/proxy/realm, register,
username/password_enc (AES-256-GCM), dtmf_mode, ping, transport, and a
status/status_updated_at pair meant to be driven by FreeSWITCH events
- apps/api/src/trunks: CRUD (POST/GET/GET:id/DELETE), same RBAC/tenant
pattern as Extensions, password never exposed in any GET
- packages/telephony: buildGatewayXml() generates a Sofia gateway XML file
- b2bcall-fs-config now writes sip_profiles/external/<trunk_id>.xml (shared
Docker volume with FreeSWITCH -- the vanilla external profile already
includes external/*.xml) and runs 'sofia profile external rescan' over
ESL; syncs on boot and on demand via Redis pub/sub
(b2bcall:trunks:sync), since apps/api runs on the host and fs-config has
no port published to reach directly
- added FreeSwitchTelephonyProvider.waitUntilConnected() to fix a startup
race: the first sync ran before the ESL connection had settled, logging
a harmless but noisy error
- verified end-to-end with a fake host: create trunk -> gateway file
written -> FreeSWITCH shows the real gateway (FAIL_WAIT, expected) ->
delete -> file removed (cleanup also correctly swept the stale
'example.com' gateway that had been copied into the volume from the
vanilla image)
- apps/freeswitch-events/src/trunk-status.ts: written to update Trunk.status
from sofia::gateway_state events, using the same normalizeEslEvent path
already proven for CHANNEL_* events
- KNOWN GAP, documented rather than glossed over: monitored fs-events for
~90s while the gateway visibly transitioned states in FreeSWITCH
(FAIL_WAIT/DOWN) and no sofia::gateway_state event was observed arriving.
CUSTOM/sofia::* events have not actually been proven working end-to-end
in this session -- only CHANNEL_* events have been. Needs verification
against a real SIP target before the status auto-update can be trusted
in production. See docs/TRUNKS.md and TODO.md.
- docs/TRUNKS.md