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
This commit is contained in:
2026-08-28 09:58:45 -03:00
parent 6628578c42
commit a05104a05f
21 changed files with 1402 additions and 140 deletions

36
TODO.md
View File

@@ -200,7 +200,41 @@
- [ ] Monitoramento em tempo real (secao 54) — depende de WebSocket
- [ ] Quota de filas — depende de Plans/Entitlements
## PHASE 12+ver `agente.md` seções 45 em diante (Agentes, Tiers, Pausas,
## PHASE 12 — Agentes, Tiers, Pausas (agente.md secao 45-49, 52)
- [x] `agents`/`tiers`/`agent_sessions`/`agent_state_events`/
`pause_reasons`/`agent_pause_events` (tenant-scoped, RLS) — separa
User/Agent/Extension (secao 45)
- [x] Corrigidos bugs reais em `FreeSwitchTelephonyProvider` (nunca
testados antes): `addAgentToQueue`/`removeAgentFromQueue` usavam
"queue add/del member", que **não existe** — comando certo é
`tier add`/`tier del`. Adicionados `addAgent`/`removeAgent`
(`agent add`/`agent del`), que faltavam por completo.
- [x] Confirmado manualmente antes de codar: `agent add`/`tier add`
duplicado dá erro ("already exist", capturado e ignorado no sync);
`agent del`/`tier del` em algo inexistente não dá erro; `agent set
status` só aceita `Available`/`On Break`/`Logged Out`
- [x] Sync 100% dinâmico via ESL (sem arquivo, diferente de Trunks/Queues):
`b2bcall:agents:sync`/`b2bcall:tiers:sync` (Redis pub/sub com payload
por ação, não um resync geral)
- [x] `apps/api`: `/agents` (CRUD provisionamento), `/agents/me/login|
logout|pause|resume` (sempre sobre o agente do usuário autenticado,
nunca um id arbitrário do client), `/pause-reasons`,
`/queues/:id/agents` (tier assignment)
- [x] Achado de corrida real, visto no teste ponta a ponta: atribuir tier
antes do primeiro login falha (agente ainda não existe no
FreeSWITCH) — login sempre re-sincroniza todos os tiers do agente,
autocorrigindo. Confirmado acontecendo exatamente assim no teste.
- [x] Testado ponta a ponta os 4 estados: login→Available, pause→On Break,
resume→Available, logout→Logged Out — todos confirmados batendo
entre `Agent.state` (banco) e `callcenter_config agent list`
(FreeSWITCH)
- [ ] Estados derivados de chamada (RINGING/IN_CALL/WRAP_UP/RESERVED) —
dependem de `callcenter::info` (CUSTOM event), ainda não provado
funcionando (mesma lacuna do `sofia::gateway_state`, ver docs/TRUNKS.md)
- [ ] `PauseReason.maxDuration` não é aplicado automaticamente
- [ ] Quota de agentes — depende de Plans/Entitlements
## PHASE 13+ — ver `agente.md` seções 54 em diante (Monitoramento em tempo real,
Predictive Dialer, Recordings, AI, Billing, Frontend, Reports, Security, Tests)
---