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>
65 lines
2.5 KiB
Ruby
65 lines
2.5 KiB
Ruby
# Recebe callback do Hermes Construtor (plugin captain-http-callback).
|
|
#
|
|
# Construtor responde async via POST pra esta URL com:
|
|
# { content: "<resposta>", reply_to: ..., metadata: {...}, timestamp: ... }
|
|
#
|
|
# Este controller identifica a sessão do admin (por session_id no metadata
|
|
# OU pelo cache key derivado de account_id que veio na query string) e
|
|
# armazena a resposta no Rails.cache pra UI poder ler via polling.
|
|
class Webhooks::Captain::HermesBuilderCallbackController < ApplicationController
|
|
skip_before_action :verify_authenticity_token, raise: false
|
|
|
|
def process_payload
|
|
content = params[:content].to_s.strip
|
|
return head :bad_request if content.blank?
|
|
|
|
session_key = resolve_session_key
|
|
if session_key.blank?
|
|
Rails.logger.warn('[HermesBuilder::Callback] no session_key resolvable — ignorando')
|
|
return head :ok
|
|
end
|
|
|
|
HermesBuilder::Storage.append(session_key, role: 'construtor', content: content)
|
|
Rails.logger.info("[HermesBuilder::Callback] reply received for #{session_key} (#{content.length} chars)")
|
|
|
|
head :ok
|
|
rescue StandardError => e
|
|
Rails.logger.error("[HermesBuilder::Callback] error: #{e.class}: #{e.message}")
|
|
head :internal_server_error
|
|
end
|
|
|
|
private
|
|
|
|
# Estratégia: usar o session_id do metadata (Hermes propaga o chat_id).
|
|
# Fallback: account_id da query string + último user que mandou msg
|
|
# (raro, mas evita perder resposta).
|
|
def resolve_session_key
|
|
chat_id = params[:metadata]&.[](:chat_id) || params.dig(:metadata, 'chat_id')
|
|
if chat_id.is_a?(String) && chat_id.include?('builder-')
|
|
# Formato: webhook:construtor-admin:session:builder-<account>-<user>
|
|
session_id = chat_id.split(':').last
|
|
return "hermes_builder:#{session_id}" if session_id.start_with?('builder-')
|
|
end
|
|
|
|
account_id = params[:account_id]
|
|
return nil if account_id.blank?
|
|
|
|
# Fallback: pega últimas 5 sessões do account, retorna a mais recente
|
|
# com mensagens. Aceitável pra MVP com 1 admin testando por vez.
|
|
recent_session_key_for(account_id)
|
|
end
|
|
|
|
def recent_session_key_for(account_id)
|
|
return nil unless Rails.cache.respond_to?(:redis)
|
|
|
|
pattern = "hermes_builder:builder-#{account_id}-*"
|
|
keys = Rails.cache.redis.with { |c| c.keys(pattern) }
|
|
return nil if keys.blank?
|
|
|
|
keys.first.sub(/^.*?(hermes_builder:.*)$/, '\1')
|
|
rescue StandardError => e
|
|
Rails.logger.warn("[HermesBuilder::Callback] recent_session_key fallback failed: #{e.class} - #{e.message}")
|
|
nil
|
|
end
|
|
end
|