Commit Graph

9 Commits

Author SHA1 Message Date
Rodribm10
ed21722dc4 fix(captain/hermes): callback aceita ?slug= além de ?inbox_id=
Hermes daemon faz POST /webhooks/captain/hermes_callback?slug=<profile>
mas controller só conhecia ?inbox_id. 404 → resposta do LLM nunca chegava
ao Captain. Cliente via só auto-react.

Fix: fetch_inbox resolve via Captain::Assistant.find_by(hermes_profile_name)
quando slug está presente. Inbox é a primeira CaptainInbox associada a
esse assistant. Suporta o pattern admin de re-apontar uma inbox de teste
(ex: Angelina) entre vários agentes Hermes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:53:12 -03:00
Rodribm10
5f6aed05c9 fix(captain/hermes-builder): callback resolve session via last_session
Bug: Hermes nao propaga chat_id no metadata do callback. Callback
controller nao conseguia resolver qual session armazenar a resposta.
WARN "no session_key resolvable — ignorando" descartava todas as
respostas do Construtor.

Fix: HermesBuilder::Storage.remember_last_session() grava ultima
session por account quando admin chama /start ou /create. Callback
le essa key via last_session_for(account_id). MVP-safe pra 1 admin
por conta.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 22:53:43 -03:00
Rodribm10
40fd0c8f50 feat(captain/hermes-builder): UI Vue + endpoints pra chat com Construtor
Tela "Construtor" no painel Captain (acessivel em sidebar pra admins) que
permite criar novo agente Hermes via chat guiado com agente Construtor
(profile Hermes separado).

Backend (admin scope):
- POST /api/v1/accounts/:id/captain/hermes_builder — manda mensagem do
  admin pro gateway do Construtor (Hermes na porta 8646)
- GET — retorna historico da sessao (Rails.cache, TTL 4h)
- DELETE /reset — limpa sessao
- POST /webhooks/captain/builder_callback — recebe respostas async do
  Construtor via plugin captain-http-callback do Hermes
- HermesBuilder::Storage (Rails.cache) — persiste msgs por session_key
  (account_id + user_id) com role/content/created_at
- HermesBuilder::Dispatcher — encaminha pro webhook do Construtor com
  HMAC opcional via ENV HERMES_BUILDER_WEBHOOK_SECRET

Frontend:
- Pagina Vue HermesBuilder/Index.vue — chat simples com:
  * Lista de mensagens com bubbles user/construtor
  * Indicador "digitando..." enquanto aguarda resposta
  * Input com Enter pra enviar / Shift+Enter pra nova linha
  * Polling 2s pra novas msgs
  * Botao Limpar conversa
- API client em api/captain/hermesBuilder.js
- Rota captain_hermes_builder_index (admin only)
- Item no sidebar Captain "Construtor (Hermes)"
- i18n keys CAPTAIN.HERMES_BUILDER em pt_BR + en

UX flow:
  Admin abre tela → digita "olá" → Construtor pergunta nome → admin
  responde → marca, persona, tabela (com opcao copiar de existente),
  regras, FAQs, identidade → resumo → confirmar → Construtor chama
  save_agent_spec → JSON salvo em /tmp/agent-specs/<slug>.json pra
  revisao posterior. NAO cria filesystem do profile nem registros DB
  (etapa SEPARADA, prox sessao).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 21:00:41 -03:00
Rodribm10
48fad2977b feat(captain/hermes): payload enriquecido + humanizadores + notif Pix proativa
Captain::Hermes::Client (enterprise/app/services/captain/hermes/client.rb):
- text_for_hermes: transcreve audio via Whisper antes de enviar pro Hermes
  (reusa Captain::OpenAiMessageBuilderService)
- image_urls_for_hermes: URLs publicas de imagens da message; plugin
  captain-webhook do Hermes baixa em /tmp/ e popula event.media_urls pra
  vision multimodal (gpt-4o-mini auxiliary)
- contact_history_snapshot: dados eager pro [ctx] (last_reservation_*,
  total_conversations, ultima_suite, etc) — memoria do contato direto no
  prompt sem precisar tool call
- notify_event + build_event_payload: dispara webhook sintetico pro Hermes
  pra eventos do sistema (Pix pago etc) — Valentina manda mensagem
  espontanea sem cliente perguntar

Captain::Payments::ConfirmationService:
- Hook notify_hermes_proactively! enfileira NotifyPaymentConfirmedJob
  apos confirmacao de Pix, somente se inbox estiver no fluxo Hermes
  (Captain interno continua igual sem mudanca)

Captain::Hermes::NotifyPaymentConfirmedJob (NOVO):
- Monta system_message "[SISTEMA: pagamento_confirmado]\n..." e dispara
  webhook pro Hermes Valentina
- Valentina (via SOUL.md) interpreta como evento do Captain e manda
  mensagem celebrativa pro cliente

Captain::Hermes::DelayedReplyJob (NOVO) — humanizadores:
- Liga indicador "digitando..." (composing) via wuzapi
- Aguarda delay configuravel via Captain::Assistant.config['response_delay']
  (modos: none, fixed, typing_simulation com chars_per_second + min/max)
- Posta msg outgoing
- Desliga typing
- Fallback no HermesCallbackController posta direto se class nao carregada

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:15:50 -03:00
Rodribm10
9ed3491d55 feat(captain/mcp): suite de 9 tools MCP + pricing tables Dolce Amore
Tools novas em enterprise/app/services/captain/mcp/tools/:
- generate_pix: Pix Inter via PricingTable + fallback link reserva-1001
- check_pix_payment: consulta Inter + dispara ConfirmationService
- send_suite_images: fotos da galeria (Captain::GalleryItem) via wuzapi
- reschedule_reservation: remarca reserva (regra 3h antecedência Dolce)
- update_contact: persiste nome/CPF/email/notas no Contact
- get_contact_history: markdown do histórico do cliente on-demand
- react_to_message: reage com emoji via wuzapi (is_reaction=true)

Captain::Mcp::PricingTables: tabela hardcoded Dolce Amore (8 categorias x 4
periodos + regras de pessoa extra). Backend e fonte de verdade — LLM nao
inventa preco.

add_label_tool: cria Label oficial automaticamente se nao existir, aceita
conversation_id em arguments.

mcp_controller: aceita X-Captain-Account-Id/Assistant-Id/Inbox-Id como
fallback de contexto.

tool_registry: 9 tools ativas.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:13:16 -03:00
Rodribm10
713bb16012 feat(captain): MCP controller aceita Bearer token além de HMAC
Hermes Agent (cliente MCP) usa `--auth header` que envia
`Authorization: Bearer <token>` — padrão MCP. Antes o Captain MCP server
só aceitava HMAC-SHA256 (X-Hub-Signature-256), incompatível com o que
Hermes manda nativamente.

Agora aceita qualquer um dos 2 modos:
- Bearer token (recomendado, padrão MCP) — Hermes envia automaticamente
- HMAC-SHA256 do body — pra clientes que preferem assinar payload

Ambos validados com ActiveSupport::SecurityUtils.secure_compare contra
o mesmo CAPTAIN_MCP_SECRET. Sem secret = validação desabilitada (PoC/dev).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:42:57 -03:00
Rodribm10
23911ea878 feat(captain): MCP server (HTTP) expondo tools pro Hermes Agent
Implementa servidor MCP (Model Context Protocol) HTTP no Captain pra
o Hermes Agent invocar tools do Captain via `hermes mcp add`. Substrato
pra integração de Nível 2 onde o agente consulta tools quando precisa
executar ações reais (buscar FAQ, adicionar label, futuramente Pix etc).

Arquivos:

- app/controllers/webhooks/captain/mcp_controller.rb
  Endpoint POST /webhooks/captain/mcp. Valida HMAC (CAPTAIN_MCP_SECRET),
  parseia JSON-RPC, despacha pro Server. Extrai params._captain_context
  com multi-tenant ids (conversation_id, inbox_id, account_id, etc).

- enterprise/app/services/captain/mcp/server.rb
  Subset MCP suficiente: initialize, tools/list, tools/call, ping,
  notifications/initialized. JSON-RPC síncrono (sem SSE).

- enterprise/app/services/captain/mcp/tool_registry.rb
  Lista centralizada de tools.

- enterprise/app/services/captain/mcp/tools/base_tool.rb
  Interface base pras tools (.name, .description, .input_schema, #call).

- enterprise/app/services/captain/mcp/tools/add_label_tool.rb
  Tool 1: aplica label na conversation atual.

- enterprise/app/services/captain/mcp/tools/faq_lookup_tool.rb
  Tool 2: busca semântica em FAQs (Captain::AssistantResponse via pgvector
  cosine). Reaproveita SearchReplyDocumentationService pra paridade com
  o caminho legado do Captain.

- config/routes.rb
  Rota POST /webhooks/captain/mcp.

Conexão pelo Hermes:
  hermes mcp add captain-tools --url http://CAPTAIN_HOST/webhooks/captain/mcp

Auth: HMAC X-Hub-Signature-256 quando CAPTAIN_MCP_SECRET setado.

TODO próxima sprint: generate_pix_tool, send_suite_images_tool. Handoff
fica via automation hoje (UI Chatwoot).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:32:38 -03:00
Rodribm10
cd519a73c4 fix(captain): converte markdown bold pra formato WhatsApp no callback Hermes
Hermes (e LLMs default em geral) emitem **negrito** no formato markdown
padrão. WhatsApp usa formato próprio: *negrito* (single asterisk). Sem
conversão, o cliente vê asteriscos literais no WhatsApp, parecendo bug.

Defesa em camadas:
1. SOUL.md da Valentina foi atualizado com regra explícita de formato
   WhatsApp (single asterisk pra bold, underscore pra itálico, etc).
2. Este controller faz normalização defensiva no callback recebido do
   Hermes: regex `**texto**` -> `*texto*` antes de criar a mensagem
   outgoing. Não afeta o resto do conteúdo.

normalize_for_whatsapp() é trivialmente reversível e idempotente
(executar 2x é igual a 1x).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:24:57 -03:00
Rodribm10
35de8b7fde feat(captain): cliente Captain ↔ Hermes (outgoing job + callback endpoint)
Implementa o lado Captain da integração Nível 2 (Hermes como cérebro).
Ativação por inbox via env var CAPTAIN_HERMES_INBOX_IDS — inboxes não
listadas seguem usando o orquestrador interno do Captain (Daniela_Reservas
etc) sem mudança alguma. Princípio "só adiciona, não retira".

Componentes:

- enterprise/app/services/captain/hermes.rb
  Módulo helper de config (env vars, URLs, secrets per-inbox).

- enterprise/app/services/captain/hermes/client.rb
  Service que monta payload (msg + contexto da conversa/inbox/contato) e
  faz POST autenticado via HMAC-SHA256 (X-Hub-Signature-256) no webhook
  do Hermes Agent (porta 8644). DispatchError em falha de rede/HTTP.

- enterprise/app/jobs/captain/hermes/outgoing_job.rb
  Wrapper Sidekiq do Client. Retry 3x em DispatchError.

- app/controllers/webhooks/captain/hermes_callback_controller.rb
  Recebe callback do plugin captain-http-callback do Hermes. Valida HMAC
  se CAPTAIN_HERMES_CALLBACK_SECRET setado, identifica conversation pela
  última pending da inbox (janela 5min) e cria mensagem outgoing.

- config/routes.rb
  Rota POST /webhooks/captain/hermes_callback (fora de /api/v1/accounts).

- enterprise/app/services/enterprise/message_templates/hook_execution_service.rb
  Branch novo no schedule_captain_response: se Hermes habilitado pra inbox,
  dispara HermesOutgoingJob; senão, fluxo Captain interno como antes.

Env vars (todas opcionais; sem set = Hermes desabilitado em todas inboxes):
- CAPTAIN_HERMES_INBOX_IDS (CSV de inbox.id)
- CAPTAIN_HERMES_WEBHOOK_BASE_URL (default http://172.17.0.1:8644)
- CAPTAIN_HERMES_CALLBACK_SECRET (HMAC validar callbacks de entrada)
- CAPTAIN_HERMES_SUBSCRIPTION_SECRET_INBOX_<id> (HMAC assinar saídas)

Limitação: identificação da conversation no callback usa última pending
da inbox dentro de 5min. OK pra PoC com 1 conversa de teste por vez. Em
produção, melhorar mapeando delivery_id ↔ conversation_id em Redis.

Próximo passo manual (admin VPS): criar subscription no Hermes:
  hermes webhook subscribe captain-inbox-1 \\
    --prompt 'Cliente disse: {message}. Responda como Daniela ...' \\
    --deliver http_callback \\
    --deliver-chat-id 'http://CAPTAIN_HOST/webhooks/captain/hermes_callback?inbox_id=1'

Depois set CAPTAIN_HERMES_INBOX_IDS=1 + CAPTAIN_HERMES_SUBSCRIPTION_SECRET_INBOX_1
no stack do Captain e testar pela inbox Angelina.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 13:22:22 -03:00