97ef8a6ba885d339ed087681bceef154145ab849
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 97ef8a6ba8 |
feat: edição de rotas de entrada, fix de 2 bugs reais no ESL, diagnóstico de NAT/áudio e softphone WebRTC (PHASE 65/66)
Três achados reportados pelo usuário numa mensagem só: (1) Rotas de Entrada não tinha edição depois de criada — implementada no mesmo padrão de Filas; (2) telas de Platform > Infraestrutura sempre davam "Timeout no ESL" nesta VM — não era limitação permanente como o comentário antigo dizia, e sim ESL_HOST=freeswitch (nome DNS que só existe dentro da rede do Docker) mais um segundo bug independente (`show gateways as json` não é comando válido nesta versão do FreeSWITCH); (3) ramal externo registrava mas sem áudio — diagnosticado com contadores de pacote do iptables: a VM está atrás de um roteador sem port-forward pra faixa de RTP, achado de infraestrutura de rede, não bug de código. Também integra o softphone WebRTC (handphone.js/OpenSIPS, já em produção): código-fonte encontrado em git.falehandix.com.br/Handix/handphone-2.0, patch mínimo pra aceitar o endereço do proxy em runtime (era build-time), nova config global (Platform > Infraestrutura > Softphone WebRTC) e widget na topbar do tenant que pega usuário/domínio/senha do ramal vinculado ao agente logado. Adiciona docs/QA_SETUP.md — runbook completo pra subir o ambiente do zero numa máquina nova (Docker, migrations, seed, systemd), e completa o .env.example que estava faltando a maioria das variáveis reais. Testado ponta a ponta com Playwright: edição de rota (criar/editar/F5), as 3 telas de Infraestrutura com dado real, e um tenant/ramal/agente de teste criados na hora confirmando que o script do softphone recebe as credenciais certas. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8 |
|||
| 1b05d93c48 |
feat(agents): widget de status na topbar (entrar/pausar/retomar/sair)
Achado real reportado pelo usuário: "não achei como deixar o agente
online, tem algum perfil ou tipo de usuario especifico?" — não era
permissão nenhuma. POST /agents/me/login|pause|resume|logout sempre
funcionaram sem exigir permissão (só JwtAuthGuard, resolvem "meu
agente" pelo userId do próprio token). O problema: nenhuma tela chamava
esses endpoints — só existia o CRUD admin de Agentes (criar o vínculo
usuário+ramal), nunca um controle pro próprio usuário logado.
Adicionado GET /agents/me (404 = usuário sem Agent neste tenant, não um
erro — é assim que o widget decide se aparece) e GET /agents/me/pause-
reasons (motivos de pausa sem exigir agents.view, que listaria TODOS os
agentes do tenant — permissão que o role "agent" nunca teve e não devia
ganhar só pra isso).
AgentStatusWidget na topbar (visível em qualquer página do app do
tenant) só aparece pra quem tem Agent vinculado: Offline -> Entrar ->
Disponível -> Pausar (escolhe motivo) -> Em pausa -> Retomar/Sair.
Achado real construindo o widget: as Server Actions de login/logout/
pause/resume exportadas como arrow function que só repassavam
argumentos quebravam em runtime ("Server Actions must be async
functions") — o mesmo bug já documentado antes nesta sessão (wizard de
campanhas). Corrigido declarando como async function de verdade.
Testado ponta a ponta com Playwright, logado como um agente de teste
real (usuário novo, role "agent", Agent vinculado a um ramal real): o
fluxo completo (entrar/pausar/retomar/sair) funciona pela tela, sem
nenhuma chamada de API manual.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
|
|||
| f051fe3162 |
feat(realtime): monitoramento em tempo real via WebSocket multi-tenant
Fecha agente.md secao 54-55 (infraestrutura) e 161 (WebSocket multi-tenant).
Entrega o pipeline de push em tempo real completo — o consumo visual
("Monitoramento -> Filas/Ramais") fica pra fase Frontend.
Requisito central da secao 161 ("nao transmitir tudo e filtrar so no
browser"): RealtimeGateway tem um unico ponto de emissao,
broadcastToTenant(), sempre server.to(`tenant:<id>`), nunca broadcast
global. Cada socket entra na room do proprio tenant no handshake, nunca
escolhe a room.
Autenticacao na conexao (handshake.auth.token, nao Authorization header):
valida o JWT (mesmo verifyAccessToken do JwtAuthGuard), exige tenantId no
token e a permission monitoring.view (ja existia desde RBAC, sem
consumidor ate agora) — mesmo principio de nunca confiar em tenant_id do
client, so do JWT ja emitido por /auth/select-tenant.
Origem dos eventos: canal Redis unico b2bcall:events (o mesmo desde Event
Socket). Dois produtores: b2bcall-fs-events (eventos do FreeSWITCH,
resolvendo tenantId por fan-out quando nao ha channel variable, ver
tenant-resolve.ts) e apps/api (mudancas no nosso Agent.state via
agents-me.controller, tenantId direto do JWT, sem fan-out).
Bug real achado e corrigido ao construir esta fase: nenhum evento CUSTOM do
ESL (sofia::register, sofia::gateway_state, callcenter::info) jamais
chegava em b2bcall-fs-events nesta sessao inteira. Causa: event_json(...)
mandava "CUSTOM" como ultimo token do comando `event json`, sem subclass
depois — mod_event_socket exige os subclasses logo depois do token CUSTOM
no mesmo comando pra serem entregues. Corrigido separando PLAIN_EVENTS
(viram listener .on()) de CUSTOM_SUBCLASSES (so compoem o comando de
assinatura). Resolve as lacunas ja documentadas em docs/TRUNKS.md e
docs/AGENTS.md. De quebra, corrigido um bug de nome de campo
(CC-Agent-Status, que nao existe -> CC-Agent-State) e um segundo bug real
em trunk-sync.ts (rescan nunca descarregava gateway removido -> agora roda
`killgw` antes do rescan).
Novos tipos normalizados a partir de callcenter::info, com nomes de campo
confirmados contra uma fila real: AGENT_OFFERED_CALL, AGENT_BRIDGE_FAILED,
QUEUE_MEMBER_COUNT (chamadas esperando, secao 54), QUEUE_MEMBER_LEFT (com
cause/cancelReason e timestamps — base pra Service Level/Abandon Rate
quando CDR existir).
Verificado ponta a ponta com um client socket.io real: login/pause/resume/
logout emitindo AGENT_STATE_CHANGED; chamada de teste numa fila com agente
logado emitindo QUEUE_MEMBER_COUNT/LEFT, AGENT_OFFERED_CALL,
AGENT_BRIDGE_FAILED, AGENT_STATUS_CHANGED (CC-Agent-State correto); token
ausente/invalido desconectado na hora, sem vazar nenhum evento.
Achado sistemico durante o teste (documentado, nao corrigido nesta fase):
@@unique combinado com soft delete, sem excluir deletedAt, em
Agent/Extension/Trunk/Queue/PauseReason — nao da pra reusar numero/nome/
codigo depois de apagar. Precisa de indice unico parcial em cada um, fora
do escopo desta fase.
typecheck do workspace inteiro limpo. ~144MB de memoria total nos
containers (fs-events 44MB, fs-config 45MB, freeswitch 26MB, postgres
21MB, redis 8MB).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X1HxY46WGU4G1zmVDNKcWw
|
|||
| 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 |