Aponta para o merge de reconciliação no scoreodonto.com (branch
reconcile-onboarding-plugins): linha local (onboarding/plugins/RX) já
contendo o trabalho do origin/main (multi-tenancy/score/imports).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SEC-06: ChatbotService reescrito com abstração aiCall() que despacha para
GEMINI ou OPENAI baseado em bot.credential.provider. Fallback automático:
se o provider primário falha (quota, rate limit), tenta a próxima credencial
da instância com provider diferente.
SEC-07: Divergência entre LLMProvider Prisma (GEMINI/OPENAI) e Secretária
(gemini/openai/anthropic/ollama) documentada com comentário explícito no
topo do service. Chatbot Rápido é intencionalmente mais simples.
SEC-13 (bônus): logger.info no ChatbotService agora inclui inputTokens e
outputTokens por chamada para observabilidade de consumo.
SEC-14: sendPresenceUpdate('composing', jid) antes de chamar a IA e
sendPresenceUpdate('paused', jid) após enviar a resposta — tanto no
ChatbotService quanto no listener ext:message.new da Secretária.
O usuário no WhatsApp vê "digitando..." enquanto a IA processa.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
SEC-01+SEC-11: /secretaria/ask agora envia reply via WhatsApp (sock.sendMessage)
e persiste a mensagem no banco Prisma após brain.chat(), com notify Socket.IO.
SEC-02+SEC-03: MessageHandler emite hookBus 'ext:message.new' para todo msg
recebido. Listener no ext-api auto-dispara a Secretária apenas se NÃO
houver AIBot ativo na instância (exclusão mútua — chatbot tem prioridade).
SEC-04: ChatListItem exibe ícone BotOff (âmbar) quando chat.bot_paused = true,
com tooltip "Bot pausado — aguardando atendente".
SEC-05: chatToLegacy() mapeia c.botPaused → bot_paused na bridge legada.
NewWhatsChat recebe campo bot_paused opcional.
SEC-08: POST /secretaria/ask valida que existe instância CONNECTED antes
de criar conversa. Retorna 503 se nenhuma instância disponível.
SEC-10: POST /api/chatbot/bots/:instanceId valida que credentialId pertence
ao mesmo tenantId antes de criar o bot. Retorna 403 se não pertencer.
SEC-12: chatBotState.resumeBot() limpa botSummary (= null) ao retomar,
evitando contexto defasado após escalação humana.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Adiciona soc/promtail/config.yml (antes fora do git, em /opt/soc/)
- Adiciona soc/grafana/provisioning/alerting/alerts.yml (antes fora do git)
- Adiciona soc/grafana/provisioning/datasources/datasources.yml (antes fora do git)
- Corrige docker-compose.yml: volumes do Promtail e Grafana agora apontam
para paths dentro do repo (/home/deploy/stack/soc/) ao invés de /opt/soc/
- Corrige mount dos containers Docker: host path mapeia para /var/lib/docker/containers
(path padrão que o Promtail espera dentro do container)
- Corrige .gitignore: ignora apenas dados binários (grafana-data, loki-data, prom-data),
não mais todos os subdiretórios do soc/
- Alertas Loki usam count_over_time() para retornar métrica numérica compatível
processHistoryBatch não incluía mediaPayload no createMany, tornando
impossível o re-download de mídias recebidas via history sync (que é a
maioria das mensagens ao reconectar a sessão).
1. Inclui mediaPayload no objeto de cada row para mensagens de mídia.
2. Após o createMany, dispara updateMany para preencher mediaPayload em
registros que já existiam no DB com o campo null (mensagens antigas
recebidas antes desta correção), sem bloquear o event loop.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Estende o tratamento de erro 400/404 do redownload para todos os tipos
de mídia. Quando mediaUnavailable=true (payload ausente no backend),
cada tipo exibe sua mensagem específica e desabilita o clique:
- Imagem: 'Imagem não disponível'
- Vídeo: 'Vídeo não disponível'
- Documento: 'Arquivo não disponível' + cursor-default
- Sticker: 'Figurinha não disponível'
DocumentBubble recebe nova prop mediaUnavailable para controle interno.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>