Commit Graph

2 Commits

Author SHA1 Message Date
b940ce3e63 fix(events): eventos CUSTOM do ESL nunca eram entregues
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
2026-08-28 11:41:09 -03:00
a05104a05f feat(agents): agentes, tiers e pausas — call center completo
Fecha agente.md secao 45-49/52. Depois desta fase, um usuario autenticado
consegue logar como agente, entrar numa fila real, se pausar e voltar,
tudo refletido de verdade no FreeSWITCH.

Schema (migration 20260828124245_agents):
- agents (tenant-scoped, RLS): User -> Extension -> identidade de agente,
  state (enum AgentState de 8 valores) espelhando o estado real, so
  alterado via login/logout/pause/resume, nunca escrito direto pela API.
- tiers: Queue<->Agent (level/position 1:1 com mod_callcenter).
- agent_sessions: um ciclo login->logout por linha.
- agent_state_events: historico de transicoes de estado.
- pause_reasons / agent_pause_events (secao 48).

Dois bugs reais corrigidos em FreeSwitchTelephonyProvider, presentes desde
a fase de Event Socket original:
- "queue add/del member" nao existe no mod_callcenter — membership de
  fila usa tier add/tier del. So foi pego agora ao confirmar de novo a
  sintaxe via `help callcenter_config` antes de codar esta fase.
- addAgent/removeAgent nao existiam ainda (agent add/del).

Mecanismo de sync: agentes e tiers nao tem representacao em XML, so
comando ESL direto — diferente do padrao "regenera todos os arquivos"
usado em Trunks/Queues. apps/api publica uma mensagem por acao com
payload (b2bcall:agents:sync, b2bcall:tiers:sync); b2bcall-fs-config
aplica o comando correspondente (agent-sync.ts).

Achados confirmados manualmente contra o FreeSWITCH real antes de codar:
- `agent add`/`tier add` nao sao idempotentes (erro em duplicata) — sync
  ignora esse erro (.catch), condicao esperada em resync.
- `agent del`/`tier del` em algo inexistente nao da erro — seguro chamar
  sem checar existencia antes.
- `agent set status` so aceita 3 valores exatos (Available/On Break/
  Logged Out) — testado deliberadamente com valor invalido.
- Corrida real: atribuir tier antes do primeiro login do agente falha
  silenciosamente do lado do FreeSWITCH (agente so existe la a partir do
  `agent add` no login). Login sempre re-sincroniza todos os tiers do
  agente depois de garantir que ele existe — auto-correcao confirmada no
  teste ponta a ponta.

apps/api: AgentsController (CRUD), AgentsMeController (login/logout/
pause/resume — sempre resolve o agente via JWT, nunca um agentId
arbitrario do client), PauseReasonsController (CRUD), QueueAgentsController
(POST/DELETE de tier em /queues/:id/agents).

Verificado ponta a ponta via curl + fs_cli contra o FreeSWITCH real:
login -> Available, pause -> On Break, resume -> Available, logout ->
Logged Out, todos batendo entre Agent.state (banco) e `agent list`
(FreeSWITCH). typecheck do workspace inteiro limpo. ~350MB de memoria
total (docker stats).

Documentado em docs/AGENTS.md, incluindo lacuna conhecida: estados
derivados de chamada (RINGING/IN_CALL/WRAP_UP/RESERVED) dependem do
evento CUSTOM callcenter::info, ainda nao comprovado chegando em
fs-events nesta sessao (mesma lacuna de sofia::gateway_state ja
documentada em docs/TRUNKS.md) — precisa de uma chamada real passando
pela fila pra investigar.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X1HxY46WGU4G1zmVDNKcWw
2026-08-28 09:58:45 -03:00