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>
76 lines
2.9 KiB
Ruby
76 lines
2.9 KiB
Ruby
# Configuração compartilhada da integração Captain ↔ Hermes Agent.
|
|
#
|
|
# A integração usa o Hermes como cérebro do atendimento (Nível 2):
|
|
# - Captain recebe msg WhatsApp
|
|
# - Dispara webhook do Hermes (POST /webhooks/captain-inbox-<id>)
|
|
# - Hermes processa via subscription Codex/etc dele
|
|
# - Hermes invoca plugin captain-http-callback que POSTa de volta no Captain
|
|
# - Captain cria mensagem outgoing e envia pro WhatsApp
|
|
#
|
|
# A ativação é por inbox via env var. As 9 outras inboxes do Captain seguem
|
|
# usando o orquestrador interno (Daniela_Reservas, etc) sem mudança.
|
|
#
|
|
# Env vars:
|
|
# CAPTAIN_HERMES_INBOX_IDS CSV de inbox.id (ex: "1,5"). Se vazio,
|
|
# desativa em todas. Inboxes não listadas
|
|
# continuam no fluxo Captain interno.
|
|
# CAPTAIN_HERMES_WEBHOOK_BASE_URL Base URL do gateway Hermes
|
|
# (default http://172.17.0.1:8644).
|
|
# CAPTAIN_HERMES_CALLBACK_SECRET HMAC-SHA256 secret pra validar callback
|
|
# do Hermes (X-Hermes-Callback-Signature).
|
|
# Se vazio, validação é desabilitada (NÃO
|
|
# recomendado em prod).
|
|
# CAPTAIN_HERMES_SUBSCRIPTION_SECRET_INBOX_<id>
|
|
# Per-inbox secret retornado pelo
|
|
# `hermes webhook subscribe`. Usado pra
|
|
# assinar o POST OUTGOING. Sem ele, o
|
|
# Hermes vai rejeitar o webhook.
|
|
module Captain::Hermes
|
|
DEFAULT_BASE_URL = 'http://172.17.0.1:8644'.freeze
|
|
|
|
module_function
|
|
|
|
def enabled_for?(inbox)
|
|
return false if inbox.blank?
|
|
return false unless inbox.respond_to?(:id)
|
|
|
|
inbox_ids.include?(inbox.id)
|
|
end
|
|
|
|
def inbox_ids
|
|
@inbox_ids ||= ENV.fetch('CAPTAIN_HERMES_INBOX_IDS', '')
|
|
.split(',')
|
|
.map { |s| s.strip.to_i }
|
|
.reject(&:zero?)
|
|
.freeze
|
|
end
|
|
|
|
def webhook_base_url
|
|
@webhook_base_url ||= (ENV['CAPTAIN_HERMES_WEBHOOK_BASE_URL'].presence || DEFAULT_BASE_URL).chomp('/')
|
|
end
|
|
|
|
def webhook_url_for(inbox)
|
|
"#{webhook_base_url}/webhooks/#{subscription_name_for(inbox)}"
|
|
end
|
|
|
|
# Convenção de nome de subscription no Hermes: precisa bater com o que o
|
|
# admin criou via `hermes webhook subscribe captain-inbox-<id> ...`.
|
|
def subscription_name_for(inbox)
|
|
"captain-inbox-#{inbox.id}"
|
|
end
|
|
|
|
def subscription_signing_secret(inbox)
|
|
ENV.fetch("CAPTAIN_HERMES_SUBSCRIPTION_SECRET_INBOX_#{inbox.id}", nil)
|
|
end
|
|
|
|
def callback_signing_secret
|
|
ENV.fetch('CAPTAIN_HERMES_CALLBACK_SECRET', nil)
|
|
end
|
|
|
|
# Reseta caches. Útil em specs ou após reload de config.
|
|
def reset_cache!
|
|
@inbox_ids = nil
|
|
@webhook_base_url = nil
|
|
end
|
|
end
|