From bc87b496a4cae9be3c3b642846e4994a58c8ece1 Mon Sep 17 00:00:00 2001 From: Rodribm10 Date: Sat, 2 May 2026 14:39:36 -0300 Subject: [PATCH] fix(captain/hermes): SOUL.md vem da Valentina (template) com sed identity + Pix flow no skill --- bin/hermes-provision | 29 +++-- .../captain/mcp/tools/save_agent_spec_tool.rb | 123 ++++++++++++++++-- 2 files changed, 131 insertions(+), 21 deletions(-) diff --git a/bin/hermes-provision b/bin/hermes-provision index 7e0c42b7f..4a2ea2c38 100755 --- a/bin/hermes-provision +++ b/bin/hermes-provision @@ -276,23 +276,34 @@ sed -i "s/X-Captain-Assistant-Id: '6'/X-Captain-Assistant-Id: '$MCP_ASSISTANT_ID sed -i 's/ memory_enabled: true/ memory_enabled: false/' "$PROFILES_DIR/$SLUG/config.yaml" sed -i 's/ user_profile_enabled: true/ user_profile_enabled: false/' "$PROFILES_DIR/$SLUG/config.yaml" -# Write SOUL.md and SKILL.md from spec -echo "$SPEC" | jq -r '.soul_md' > "$PROFILES_DIR/$SLUG/SOUL.md" +# SOUL.md: clona a da Valentina (template canônico) e substitui identidade. +# Tudo que NÃO for identidade/marca/categoria — tom, formatação WhatsApp, [ctx], +# tools, regras de fluxo — vem direto da Valentina e fica em sync conforme +# ela evolui. +BRAND_NAME=$(echo "$SPEC" | jq -r '.marca') +UNIT_NAME=$(echo "$SPEC" | jq -r '.unit_name') +SKILL_NAME=$(echo "$SPEC" | jq -r '.skill_name') +CATEGORIAS_LISTA=$(echo "$SPEC" | jq -r '.categories | map(.key) | join(", ")') + +cp "$TEMPLATE_PROFILE/SOUL.md" "$PROFILES_DIR/$SLUG/SOUL.md" +# Identity replacements (atenção: ordem importa pra strings que se sobrepõem). +sed -i "s|Dolce Amore Motel|$BRAND_NAME — $UNIT_NAME|g" "$PROFILES_DIR/$SLUG/SOUL.md" +sed -i "s|Valentina|$NAME|g" "$PROFILES_DIR/$SLUG/SOUL.md" +sed -i "s|dolce-amore-reservas|$SKILL_NAME|g" "$PROFILES_DIR/$SLUG/SOUL.md" + +# Skill: usa o markdown gerado pelo expand_spec (tabela do banco + regras). echo "$SPEC" | jq -r '.skill_md' > "$PROFILES_DIR/$SLUG/skills/$SKILL_NAME/SKILL.md" -# Adiciona anti-leak guard no fim do SOUL.md (defesa contra contaminação -# de outras unidades via memória persistente do Codex/ChatGPT). -SKILL_PATH="$PROFILES_DIR/$SLUG/skills/$SKILL_NAME/SKILL.md" -CATEGORIAS_LISTA=$(echo "$SPEC" | jq -r '.categories | map(.key) | join(", ")') +# Anti-leak no SOUL.md (proteção contra contaminação cross-unit via Codex). cat >> "$PROFILES_DIR/$SLUG/SOUL.md" < 0, ele é recorrente — trate familiarmente, não peça nome de novo. + + ## Tools MCP disponíveis (use proativamente) + + - **`generate_pix(conversation_id, suite_category, period, total_guests, check_in_date)`** — gera Pix do sinal de reserva. Use SÓ depois que tiver categoria + permanência + dia + horário coletados. + - **`react_to_message(conversation_id, emoji, message_id)`** — reage com emoji à msg do cliente (gesto sutil). + - **`add_label(label)`** — taga a conversa. + - **`send_suite_images(conversation_id, suite_category)`** — manda foto da suíte se cliente pedir. + - **`faq_lookup(query)`** — última opção, com query ESPECÍFICA. Prefira a tabela da skill. + + Pra usar essas tools sempre passe o `conversation_id` correto (vem no `cid` do [ctx]). + + ## NUNCA cite tools, nem "vou consultar" + + Pro cliente, é tudo #{name} respondendo. Tools são bastidor. Frases proibidas: + - ❌ "vou consultar o sistema" + - ❌ "deixa eu verificar" + - ❌ "tabela qui-dom" / "tabela seg-qua" (nomes internos) + - ❌ "como assistente virtual..." (a não ser que perguntem direto) + + ✅ Se você TEM a info na skill, responda direto. MD end # rubocop:enable Metrics/MethodLength + # rubocop:disable Metrics/MethodLength def build_skill_md(name, brand, spec, categories) identity = spec['identity'] || {} rules = spec['rules'] || {} @@ -236,6 +289,27 @@ class Captain::Mcp::Tools::SaveAgentSpecTool < Captain::Mcp::Tools::BaseTool Marca: **#{brand.name}**. + ## 🚨 REGRA DE OURO — Info vs Reserva (LEIA ANTES DE QUALQUER RESPOSTA) + + Classifique a intenção do cliente em **A** ou **B** ANTES de responder: + + ### A) CONSULTA DE INFO (cliente quer SABER, não reservar) + Sinais: "qual o preço?", "quanto custa?", "valores?", "tabela?", "preço da hidro?", "quanto fica a Master?". + + → **AÇÃO:** responda DIRETO com o(s) valor(es) da tabela. Sem questionário. + → Se cliente disse genérico ("preços?"), manda resumo compacto cobrindo TODAS as categorias. + → Se cliente disse específico ("hidro pernoite?"), manda só esse valor. + → **INFERA O DIA**: se cliente não falou data, assume HOJE. Se a tabela varia por dia da semana, usa o bucket correspondente a hoje. Se cliente quiser outro dia, ele dirá ("pra sexta", "quinta-feira", "amanhã"). NÃO pergunte "qual dia?" antes de mandar o preço. + → **NO MÁXIMO 1 pergunta complementar** (categoria) — e só se for ESTRITAMENTE necessário pra dar o preço. + → Termina com convite leve a reservar: *"Quer que eu já reserve?"*. SEM exigir data/horário/permanência ainda. + + ### B) INTENÇÃO DE RESERVA (cliente quer FECHAR) + Sinais: "quero reservar", "quero pegar", "vou querer", "bora", "topo", "pode reservar", "me reserva", ou já dá dados concretos ("quero a master pra sexta às 22h"). + + → **AÇÃO:** AGORA sim entra no fluxo de coleta — pergunta categoria + data + horário + permanência (numa msg só). + + **NUNCA confundir A com B.** Cliente perguntando preço ≠ cliente reservando. Não interrogue quem só quer info. + ## Tabela de Preços ⚠️ Use direto. Não consulte FAQ pra preço. @@ -253,8 +327,33 @@ class Captain::Mcp::Tools::SaveAgentSpecTool < Captain::Mcp::Tools::BaseTool #{identity['address'] ? "- Endereço: #{identity['address']}" : ''} #{identity['phone'] ? "- Contato: #{identity['phone']}" : ''} #{identity['wifi'] && identity.dig('wifi', 'policy') ? "- Wi-Fi: #{identity.dig('wifi', 'policy')}" : ''} + + ## 💳 Fluxo de Pix de Reserva (CRÍTICO) + + Quando cliente confirma reserva ("pode reservar", "pode gerar", "bora", "topo", "sim"), você DEVE gerar Pix imediatamente. **NUNCA responda "Um momento" ou faça handoff nessa hora** — handoff é só pra problemas operacionais. + + **Passos:** + + 1. Tendo categoria + permanência + data + horário (mínimo necessário), chame: + `generate_pix(conversation_id, suite_category, period, total_guests, check_in_date)` + - `conversation_id` = cid do [ctx] + - `suite_category` = nome conforme cadastro (Standard, Luxo, Hidromassagem, etc) + - `period` = "3h", "pernoite_promo", "pernoite_integral", "diaria" + - `total_guests` = número total de hóspedes (default 2) + - `check_in_date` = ISO 8601 (ex: "2026-05-03T20:00:00") + + 2. **Sucesso:** o tool já cuida de mandar o Pix pro cliente em msg separada. Sua resposta final deve ser CURTA e calorosa, confirmando: *"Prontinho! Reserva pré-aprovada — assim que o sinal cair, ela fica garantida. Qualquer coisa me chama 😊"*. SEM repetir o link nem o valor. + + 3. **`requires_input: true`:** o tool pede CPF ou nome. Pegue do `formatted_message` do retorno e mande EXATAMENTE como veio. Não parafraseie. + + 4. **Erro (`success: false` sem requires_input):** chame fallback `generate_reservation_link(marca, unidade, categoria, permanencia, checkin_at)`. Resposta ao cliente: *"Tive um probleminha no Pix 🙏 Mandei o link da reserva — já chegou aí."* + + ## NUNCA fazer handoff em momento de fechamento + + Cliente disse "pode gerar"/"sim"/"pode reservar" = chamar `generate_pix` AGORA. Não defer pra humano. Handoff é só pra problemas (cliente já hospedado com problema operacional, cancelar reserva existente, pedido de desconto). MD end + # rubocop:enable Metrics/MethodLength def format_pricing_block(categories) return '_(sem categorias cadastradas)_' if categories.empty?