mirror of
https://github.com/melgarafael/DeskcommCRM.git
synced 2026-10-02 01:28:34 +08:00
refactor(mcp): remove a description morta do catálogo — 51 cópias que ninguém lia
`lib/mcp/tools/catalogo/*.ts` declarava `description` em 51 capacidades; o tipo a documentava como "Texto tecnico entregue ao MODELO" e sete cabeçalhos repetiam "`description` fala com o modelo". Nenhum consumidor lia esse campo: a ponte do turno (`lib/ai/runtime/tools.ts`) monta `def.description`, do HANDLER, e a rota `/api/v1/mcp/tools` também. As duas fontes divergiam em 48 das 51. ## Por que remover, e não sincronizar Fonte única mudaria o que 48 tools dizem ao modelo — risco alto, ganho zero. Um gate de paridade obrigaria manter dois textos sincronizados para sempre, com a duplicata seguindo lá para ser editada por engano. Remover mata a armadilha na raiz: não dá para editar o lugar errado se o lugar não existe. E a remoção SE AUTO-VERIFICA. Tirei o campo do tipo e o `tsc` apontou cada leitura — prova mais forte que grep. ## O que o typecheck achou: a dívida não era teórica Um leitor, e o pior possível: `evidence/ia-360-w4/medicao-vazamento/remedir-com-operador.ts`, o script que mede vazamento de vocabulário do agente. A função se chama `descreverFerramentas` e o comentário diz "A ferramenta como o modelo a vê: nome + descrição, que é o que pode vazar" — e lia `TOOL_CATALOG.description`, exatamente o texto que o modelo NÃO vê. NÃO MEDIDO: se isso muda o resultado daquela medição. O `name` é idêntico nas duas fontes e é o vetor principal de vazamento, então o efeito pode ser nulo, mas não rodei. Corrigi a fonte para `allTools` e deixei a ressalva no script. Não reabri a medição arquivada de outra branch. ## O buraco que a remoção expôs `tests/unit/catalogo-servido.test.ts` testava a junção com FIXTURES (`description: "faz algo"`), nunca com o catálogo real — nenhum gate garantia que uma capacidade servida tem descrição. Esvaziar a de um handler passaria calado: a tela sem explicação e o modelo com uma ferramenta sem contrato. Caso novo: toda capacidade servida tem descrição não-vazia E ela é IDÊNTICA à do handler. A segunda metade é a que importa — se reaparecer uma cópia no catálogo e a junção preferi-la, reprova. Sabotagens: `description` de um handler vira "" → 1 → 1 (a primeira tentativa não sabotou nada: escrevi `description: "" ||`, e `"" || "texto"` devolve o texto — instrumento quebrado, não gate fraco); junção servindo outro texto → 1 → 2. ## Correção de um número que publiquei A mensagem do commitc56416aadiz "1819 unitários". O real naquele SHA é 1818, medido depois com `git stash` e a árvore limpa emc56416aa. Rodei a suíte com a árvore ligeiramente diferente da commitada (antes do `rm` do protótipo e do stage final) e publiquei como se fosse do commit. Régua daqui em diante: número que sai em artefato público é medido DEPOIS do stage. ## Saldo 130 linhas removidas, 63 acrescentadas, 9 arquivos. Os 7 cabeçalhos falsos foram reescritos com o que é verdade e POR QUE o campo não existe mais — para ninguém "completar" o catálogo de volta. O BRIEFING-ia-360.md acompanhou. Provado na tela: `/app/ai/agents/<mcp_agent>`, Supabase local (HTML de /login com 2× `127.0.0.1:54321`, 0× `*.supabase.co`), a capacidade segue com rótulo, explicação, categoria e área — 6/6, zero erro de console. typecheck 0 · lint 0 errors · 1819 unitários (1818 + 1 caso novo) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkS3mzwtXughmjVC5FCoNo
This commit is contained in:
co-authored by
Claude Opus 5
parent
c56416aad9
commit
02d9aceacf
@@ -117,7 +117,10 @@ export type ToolBundle = "atender" | "vender" | "reter" | "escalar" | "organizar
|
||||
export interface McpToolCatalogEntry {
|
||||
name: string; // CONTRATO DE WIRE — imutável depois de publicado
|
||||
category: McpToolCategory; // dirige requiresScope/requiresRole — não é rótulo
|
||||
description: string; // técnica, vai para o MODELO
|
||||
// `description` VIVIA AQUI e foi REMOVIDA em 2026-08-07: nenhum consumidor a
|
||||
// lia (a ponte do turno monta a do HANDLER) e 48 das 51 divergiam do que o
|
||||
// modelo recebia de verdade. O texto que vai ao modelo é a `description` de
|
||||
// `lib/mcp/tools/<dominio>.ts`, e não há cópia dele neste catálogo.
|
||||
|
||||
// camada de apresentação (pilar 3) — vai para o HUMANO:
|
||||
rotulo: string; // "Mover lead de etapa" — verbo no infinitivo, pt-BR
|
||||
|
||||
@@ -711,9 +711,85 @@ container → 1 → **1**.
|
||||
| 2 | Crases dentro de template literal na sonda | `tsc` reprovou — mesmo erro que já cometi com SQL em template literal |
|
||||
| 3 | **Duas imagens versionadas sem citação** no commit `3227bf81` | O gate `evidencia-citada` reprovou. O pre-commit não roda a suíte, então aquele commit deixou o CI vermelho e eu não vi |
|
||||
|
||||
### Correção de um número que publiquei
|
||||
|
||||
A mensagem do commit `c56416aa` diz **1819 unitários**. O real naquele SHA é
|
||||
**1818** — medido depois, com `git stash` e a árvore limpa em `c56416aa`.
|
||||
|
||||
A causa não é aritmética: rodei a suíte com a árvore **ligeiramente diferente**
|
||||
da que foi commitada (antes do `rm` do protótipo e do stage final) e publiquei o
|
||||
número como se fosse do commit. Medir contra árvore em movimento é medir contra
|
||||
nada, e o erro passou porque o número vinha cercado de material medido com
|
||||
rigor. Régua para a próxima: número que sai num artefato público (commit, PR,
|
||||
handoff) é medido **depois** do stage, não antes.
|
||||
|
||||
---
|
||||
|
||||
## A `description` morta do catálogo — resolvida por remoção (2026-08-07)
|
||||
|
||||
A dívida declarada duas seções acima virou trabalho. `lib/mcp/tools/catalogo/*.ts`
|
||||
declarava `description` em **51 capacidades**, o tipo a documentava como
|
||||
*"Texto tecnico entregue ao MODELO"*, os cabeçalhos de 7 arquivos repetiam
|
||||
*"`description` fala com o modelo"* — e **ninguém lia esse campo**.
|
||||
|
||||
### Por que remover, e não sincronizar
|
||||
|
||||
| caminho | por que não |
|
||||
|---|---|
|
||||
| fonte única (runtime passa a ler o catálogo) | mudaria o que **48 tools** dizem ao modelo. Risco alto, ganho zero |
|
||||
| gate de paridade com a dívida congelada | obriga manter dois textos sincronizados **para sempre**; a duplicata continua existindo para ser editada por engano |
|
||||
| **remover o campo** | mata a armadilha na raiz — não dá para editar o lugar errado se o lugar não existe |
|
||||
|
||||
E a remoção **se auto-verifica**: tirei o campo do tipo e o `tsc` apontou cada
|
||||
leitura. Prova mais forte que qualquer grep.
|
||||
|
||||
### O que o typecheck encontrou — a dívida não era teórica
|
||||
|
||||
Um leitor, e o pior possível:
|
||||
`evidence/ia-360-w4/medicao-vazamento/remedir-com-operador.ts`, o script que
|
||||
mede vazamento de vocabulário do agente. A função dele se chama
|
||||
`descreverFerramentas` e o comentário diz **"A ferramenta como o modelo a vê:
|
||||
nome + descrição, que é o que pode vazar"** — e ele lia
|
||||
`TOOL_CATALOG.description`, exatamente o texto que o modelo **não** vê.
|
||||
|
||||
**O que NÃO medi:** se isso muda o resultado daquela medição. O `name` da tool é
|
||||
idêntico nas duas fontes e é o vetor principal de vazamento, então o efeito pode
|
||||
ser nulo — mas não rodei. Corrigi a fonte (`allTools`) para a próxima rodada ler
|
||||
o que vai ao modelo, e deixei a ressalva escrita no próprio script. Não reabri a
|
||||
medição arquivada de outra branch.
|
||||
|
||||
### O buraco que a remoção expôs
|
||||
|
||||
`tests/unit/catalogo-servido.test.ts` testava a junção com **fixtures**
|
||||
(`description: "faz algo"`), nunca com o catálogo real. Ou seja: **nenhum gate
|
||||
garantia que uma capacidade servida tem descrição.** Esvaziar a `description` de
|
||||
um handler passaria calado — a tela mostraria a capacidade sem explicação e o
|
||||
modelo receberia uma ferramenta sem contrato, dois silêncios de uma vez.
|
||||
|
||||
Caso novo: toda capacidade servida tem descrição não-vazia **e ela é idêntica à
|
||||
do handler**. A segunda metade é a que importa: se um dia reaparecer uma cópia no
|
||||
catálogo e a junção preferi-la, o gate reprova.
|
||||
|
||||
| Sabotagem | Previsão | Resultado | |
|
||||
|---|---|---|---|
|
||||
| `description` de um handler vira `""` | 1 | **1** ✅ | primeira tentativa não sabotou nada: escrevi `description: "" \|\|`, e `"" \|\| "texto"` devolve o texto — instrumento quebrado, não gate fraco |
|
||||
| a junção passa a servir outro texto | 1 | **2** | reprovou o caso novo e mais um |
|
||||
|
||||
### Saldo
|
||||
|
||||
130 linhas removidas contra 63 acrescentadas em 9 arquivos. Os 7 cabeçalhos que
|
||||
afirmavam a falsidade foram reescritos com o que é verdade e **por que** o campo
|
||||
não existe mais — para ninguém "completar" o catálogo de volta. O
|
||||
`BRIEFING-ia-360.md`, que também declarava o campo como "vai para o MODELO",
|
||||
acompanhou.
|
||||
|
||||
`typecheck 0` · `lint 0 errors` · **1819 unitários** (1818 no `c56416aa` + 1
|
||||
caso novo — desta vez medido com a árvore parada).
|
||||
|
||||
### Pendente
|
||||
|
||||
- **A `description` morta do catálogo** — decisão de desenho (48 de 51 divergem).
|
||||
- Nada desta dívida. As pendências vivas são as do topo do arquivo
|
||||
(`database.types.ts`, mapa de arquitetura) e as duas issues pré-existentes.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
|
||||
import { detectarVazamentoInterno } from '@/lib/agent-engine/guardrails/vazamento-interno';
|
||||
import { TOOL_CATALOG } from '@/lib/mcp/tools/catalog';
|
||||
import { allTools } from '@/lib/mcp/tools';
|
||||
import { AGENT_TOOL_DEFS } from '@/lib/agent-engine/agent/inbound-turn';
|
||||
import { capacidadesEntreguesAoOperador } from '@/lib/agent-engine/agent/entrega-de-capacidade';
|
||||
|
||||
@@ -119,7 +119,16 @@ function cenariosDaLinhaDeBase(): Cenario[] {
|
||||
/** A ferramenta como o modelo a vê: nome + descrição, que é o que pode vazar. */
|
||||
function descreverFerramentas(nativas: string[], catalogo: string[]): Array<Record<string, unknown>> {
|
||||
const defs = AGENT_TOOL_DEFS as Record<string, { description: string }>;
|
||||
const doCatalogo = new Map(TOOL_CATALOG.map((t) => [t.name, t.description]));
|
||||
// A descrição vem do HANDLER, não do catálogo. Esta linha lia
|
||||
// `TOOL_CATALOG.description` — um campo que NENHUM consumidor lê: a ponte do
|
||||
// turno (`lib/ai/runtime/tools.ts`) monta `def.description`, do handler, e as
|
||||
// duas divergiam em 48 das 51 capacidades. O comentário acima diz "como o
|
||||
// modelo a vê", e era exatamente o texto que o modelo NÃO vê.
|
||||
//
|
||||
// Não reabri a medição arquivada: o nome da tool, que é o vetor principal de
|
||||
// vazamento, é idêntico nas duas fontes, então o efeito no resultado NÃO foi
|
||||
// medido. Corrijo a fonte para que a próxima rodada leia o que vai ao modelo.
|
||||
const doCatalogo = new Map(allTools.map((t) => [t.name, t.description]));
|
||||
return [
|
||||
...nativas
|
||||
.filter((n) => defs[n] !== undefined)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 157 KiB |
@@ -1,8 +1,15 @@
|
||||
/**
|
||||
* Capacidades de ATENDIMENTO — cliente, conversa, mensagem.
|
||||
*
|
||||
* `description` fala com o modelo; `rotulo`/`explicacao`/`oQueToca` falam com o
|
||||
* humano que configura o agente. Ver `docs/handoffs/BRIEFING-ia-360.md` §4.
|
||||
* ESTE ARQUIVO FALA COM O HUMANO que configura o agente — `rotulo`,
|
||||
* `explicacao` e `oQueToca`. O texto que vai ao MODELO é a `description` do
|
||||
* HANDLER (`lib/mcp/tools/<dominio>.ts`), e ela NÃO tem cópia aqui: até
|
||||
* 2026-08-07 tinha, ninguém lia essa cópia, e 48 das 51 divergiam do que o
|
||||
* modelo realmente recebia. O campo foi removido em vez de sincronizado —
|
||||
* duplicata que ninguém lê não é documentação, é armadilha: um script de
|
||||
* medição de vazamento chegou a montar o prompt com o texto errado, sob um
|
||||
* comentário dizendo "a ferramenta como o modelo a vê".
|
||||
* Ver `docs/handoffs/BRIEFING-ia-360.md` §4.
|
||||
*/
|
||||
import { declararTools } from "./tipos";
|
||||
|
||||
@@ -10,7 +17,6 @@ export const TOOLS_ATENDIMENTO = declararTools([
|
||||
{
|
||||
name: "crm_search_contacts",
|
||||
category: "read",
|
||||
description: "Busca contatos por nome/telefone/email",
|
||||
rotulo: "Procurar cliente",
|
||||
explicacao:
|
||||
"Encontra um cliente pelo nome, telefone ou e-mail, para o agente saber com quem está falando antes de responder.",
|
||||
@@ -21,7 +27,6 @@ export const TOOLS_ATENDIMENTO = declararTools([
|
||||
{
|
||||
name: "crm_get_contact",
|
||||
category: "read",
|
||||
description: "Detalhe de um contato",
|
||||
rotulo: "Ver ficha do cliente",
|
||||
explicacao:
|
||||
"Abre a ficha completa de um cliente: dados de contato, histórico e por onde ele chegou até a empresa.",
|
||||
@@ -32,8 +37,6 @@ export const TOOLS_ATENDIMENTO = declararTools([
|
||||
{
|
||||
name: "crm_list_conversations",
|
||||
category: "read",
|
||||
description:
|
||||
"Lista conversas (com assignee_kind, assigned_to_user_name, tags, queue_position)",
|
||||
rotulo: "Listar conversas",
|
||||
explicacao:
|
||||
"Mostra as conversas em andamento, quem está cuidando de cada uma e a posição de cada cliente na fila de espera.",
|
||||
@@ -44,8 +47,6 @@ export const TOOLS_ATENDIMENTO = declararTools([
|
||||
{
|
||||
name: "crm_get_conversation",
|
||||
category: "read",
|
||||
description:
|
||||
"Detalhe de conversa (com assignee_kind, assigned_to_user_name, tags, queue_position)",
|
||||
rotulo: "Ver uma conversa",
|
||||
explicacao:
|
||||
"Abre os detalhes de uma conversa: quem está atendendo, marcadores aplicados e há quanto tempo o cliente espera.",
|
||||
@@ -56,7 +57,6 @@ export const TOOLS_ATENDIMENTO = declararTools([
|
||||
{
|
||||
name: "crm_get_conversation_history",
|
||||
category: "read",
|
||||
description: "Historico de mensagens de uma conversa",
|
||||
rotulo: "Ler o histórico da conversa",
|
||||
explicacao:
|
||||
"Lê as mensagens já trocadas com o cliente, para o agente responder sem pedir que ele repita o que já contou.",
|
||||
@@ -67,7 +67,6 @@ export const TOOLS_ATENDIMENTO = declararTools([
|
||||
{
|
||||
name: "crm_send_whatsapp_message",
|
||||
category: "write",
|
||||
description: "Envia mensagem WhatsApp",
|
||||
rotulo: "Enviar mensagem no WhatsApp",
|
||||
explicacao:
|
||||
"Envia uma mensagem de WhatsApp para o cliente. Ele recebe de verdade, no celular dele, e não dá para desfazer.",
|
||||
|
||||
@@ -10,9 +10,6 @@ export const TOOLS_COMERCIO = declararTools([
|
||||
{
|
||||
name: "crm_list_contact_orders",
|
||||
category: "read",
|
||||
description:
|
||||
"Lista os pedidos de um contato, do mais recente para o mais antigo, com status, valor, " +
|
||||
"forma de pagamento, situação de entrega e código de rastreio.",
|
||||
rotulo: "Ver as compras do cliente",
|
||||
explicacao:
|
||||
"Mostra o que este cliente já comprou, quanto pagou e como está a entrega, para o assistente não prometer prazo no escuro nem repetir uma oferta já aceita.",
|
||||
@@ -23,9 +20,6 @@ export const TOOLS_COMERCIO = declararTools([
|
||||
{
|
||||
name: "crm_search_products",
|
||||
category: "read",
|
||||
description:
|
||||
"Busca produtos do catálogo da loja por parte do nome. Devolve preço, quantidade " +
|
||||
"disponível e link.",
|
||||
rotulo: "Procurar produto na loja",
|
||||
explicacao:
|
||||
"Procura um produto pelo nome e devolve preço e quantidade em estoque, para o assistente responder com o dado da loja em vez de estimar.",
|
||||
@@ -36,9 +30,6 @@ export const TOOLS_COMERCIO = declararTools([
|
||||
{
|
||||
name: "crm_list_privacy_requests",
|
||||
category: "read",
|
||||
description:
|
||||
"Lista pedidos de privacidade (LGPD) da organização, com tipo, situação, chegada e prazo. " +
|
||||
"NÃO executa nada: é leitura.",
|
||||
rotulo: "Ver pedidos de privacidade",
|
||||
explicacao:
|
||||
"Mostra quem pediu para exportar ou apagar os próprios dados e qual o prazo, para o assistente parar de insistir com quem pediu para sair.",
|
||||
|
||||
@@ -10,8 +10,15 @@
|
||||
* As seis capacidades abaixo fecham as duas direções: o agente participa do
|
||||
* atendimento humano em vez de terminar nele.
|
||||
*
|
||||
* `description` fala com o modelo; `rotulo`/`explicacao`/`oQueToca` falam com o
|
||||
* dono da clínica que configura o agente. Ver `docs/handoffs/BRIEFING-ia-360.md` §4.
|
||||
* ESTE ARQUIVO FALA COM O HUMANO que configura o agente — `rotulo`,
|
||||
* `explicacao` e `oQueToca`. O texto que vai ao MODELO é a `description` do
|
||||
* HANDLER (`lib/mcp/tools/<dominio>.ts`), e ela NÃO tem cópia aqui: até
|
||||
* 2026-08-07 tinha, ninguém lia essa cópia, e 48 das 51 divergiam do que o
|
||||
* modelo realmente recebia. O campo foi removido em vez de sincronizado —
|
||||
* duplicata que ninguém lê não é documentação, é armadilha: um script de
|
||||
* medição de vazamento chegou a montar o prompt com o texto errado, sob um
|
||||
* comentário dizendo "a ferramenta como o modelo a vê".
|
||||
* Ver `docs/handoffs/BRIEFING-ia-360.md` §4.
|
||||
*/
|
||||
import { declararTools } from "./tipos";
|
||||
|
||||
@@ -19,12 +26,6 @@ export const TOOLS_ESCALACAO = declararTools([
|
||||
{
|
||||
name: "crm_list_available_attendants",
|
||||
category: "read",
|
||||
description:
|
||||
"Lista os atendentes da org com disponibilidade, capacidade, carga atual e se podem " +
|
||||
"receber uma conversa AGORA (mesmo predicado do worker de roteamento: disponível ∧ com " +
|
||||
"folga ∧ dentro do horário). Consulta livre: o caminho de escalação JÁ devolve a " +
|
||||
"expectativa sozinho, então isto serve para planejar, não para cumprir obrigação. " +
|
||||
"Não devolve e-mail nem telefone.",
|
||||
rotulo: "Ver quem pode assumir agora",
|
||||
explicacao:
|
||||
"Mostra quais pessoas da equipe estão em atendimento neste momento, quantas conversas cada uma já tem e quem ainda tem espaço para receber mais uma.",
|
||||
@@ -35,10 +36,6 @@ export const TOOLS_ESCALACAO = declararTools([
|
||||
{
|
||||
name: "crm_list_human_cases",
|
||||
category: "read",
|
||||
description:
|
||||
"Lista os casos humanos da org por estado (abertos = awaiting_human|awaiting_lead; " +
|
||||
"fechados = resolved|escalated|cancelled), com título, bloqueio, estado e a conversa de " +
|
||||
"origem. Use para saber o que já foi pedido a uma pessoa antes de pedir de novo.",
|
||||
rotulo: "Ver os chamados em aberto",
|
||||
explicacao:
|
||||
"Lista os assuntos que já foram passados para uma pessoa resolver, com o estado de cada um, para o agente não pedir duas vezes a mesma coisa.",
|
||||
@@ -49,10 +46,6 @@ export const TOOLS_ESCALACAO = declararTools([
|
||||
{
|
||||
name: "crm_get_human_case",
|
||||
category: "read",
|
||||
description:
|
||||
"Detalhe de UM caso humano: título, resumo, bloqueio, estado e a timeline completa de " +
|
||||
"eventos, incluindo o que o humano decidiu (human_action + texto) e o que o agente " +
|
||||
"registrou. Devolve também o bloco de continuidade pronto para retomar a conversa.",
|
||||
rotulo: "Ler um chamado e o que a pessoa decidiu",
|
||||
explicacao:
|
||||
"Abre um chamado e mostra tudo que aconteceu nele, inclusive a decisão que a pessoa tomou e o texto que ela escreveu ao decidir.",
|
||||
@@ -63,10 +56,6 @@ export const TOOLS_ESCALACAO = declararTools([
|
||||
{
|
||||
name: "crm_add_case_note",
|
||||
category: "write",
|
||||
description:
|
||||
"Registra no caso o que aconteceu depois da abertura (kind='agent_noted', actor='agent') " +
|
||||
"— o que o lead respondeu, o que já foi tentado, o que mudou. Só funciona em caso ABERTO. " +
|
||||
"Serve para o próximo atendente não começar do zero.",
|
||||
rotulo: "Registrar o que aconteceu num chamado",
|
||||
explicacao:
|
||||
"Deixa escrito no chamado o que mudou desde que ele foi aberto, para a pessoa que for atender não precisar começar do zero.",
|
||||
@@ -77,10 +66,6 @@ export const TOOLS_ESCALACAO = declararTools([
|
||||
{
|
||||
name: "crm_close_human_case",
|
||||
category: "write",
|
||||
description:
|
||||
"Encerra um caso aberto com o desfecho registrado: 'resolvido' (o bloqueio foi superado) " +
|
||||
"ou 'sem_necessidade' (deixou de fazer sentido). Grava actor='agent' — NUNCA se passa por " +
|
||||
"decisão humana. Irreversível pela própria capacidade.",
|
||||
rotulo: "Encerrar um chamado com o desfecho",
|
||||
explicacao:
|
||||
"Fecha um chamado deixando registrado como ele terminou, para ele parar de ocupar a fila de quem atende. Não dá para reabrir por aqui.",
|
||||
@@ -91,12 +76,6 @@ export const TOOLS_ESCALACAO = declararTools([
|
||||
{
|
||||
name: "crm_resume_ai_attendance",
|
||||
category: "write",
|
||||
description:
|
||||
"Devolve o atendimento de uma conversa ao agente: solta o dono humano, limpa " +
|
||||
"contacts.force_human e o silêncio do bot, e devolve o CONTEXTO do que a pessoa fez " +
|
||||
"(decisões nos casos + notas internas). Grava esse contexto no checkpoint do lead, de " +
|
||||
"onde o próximo turno o lê. Reativar automação para um cliente que pediu uma pessoa é " +
|
||||
"decisão de peso — por isso é capacidade de risco crítico.",
|
||||
rotulo: "Retomar o atendimento automático",
|
||||
explicacao:
|
||||
"Devolve a conversa para o atendimento automático depois que uma pessoa atendeu, levando junto o que ficou combinado com o cliente. Só uma pessoa pode acionar.",
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
/**
|
||||
* Capacidades de EVOLUCAO — o que a empresa ja sabe e o que o agente aprende.
|
||||
*
|
||||
* `description` fala com o modelo; `rotulo`/`explicacao`/`oQueToca` falam com o
|
||||
* humano que configura o agente. Ver `docs/handoffs/BRIEFING-ia-360.md` §4.
|
||||
* ESTE ARQUIVO FALA COM O HUMANO que configura o agente — `rotulo`,
|
||||
* `explicacao` e `oQueToca`. O texto que vai ao MODELO é a `description` do
|
||||
* HANDLER (`lib/mcp/tools/<dominio>.ts`), e ela NÃO tem cópia aqui: até
|
||||
* 2026-08-07 tinha, ninguém lia essa cópia, e 48 das 51 divergiam do que o
|
||||
* modelo realmente recebia. O campo foi removido em vez de sincronizado —
|
||||
* duplicata que ninguém lê não é documentação, é armadilha: um script de
|
||||
* medição de vazamento chegou a montar o prompt com o texto errado, sob um
|
||||
* comentário dizendo "a ferramenta como o modelo a vê".
|
||||
* Ver `docs/handoffs/BRIEFING-ia-360.md` §4.
|
||||
*
|
||||
* DECISAO DELIBERADA — a IA nao aprova a propria melhoria. As propostas do
|
||||
* flywheel (`flywheel_distiller_proposals`) sao expostas para LEITURA. Nenhuma
|
||||
@@ -17,10 +24,6 @@ export const TOOLS_EVOLUCAO = declararTools([
|
||||
{
|
||||
name: "crm_search_knowledge",
|
||||
category: "read",
|
||||
description:
|
||||
"Busca semantica no acervo de conhecimento da organizacao (RAG). Devolve os trechos mais " +
|
||||
"relevantes com a similaridade de cada um. Quando nada passa do limiar, devolve " +
|
||||
"`melhor_similaridade` para distinguir 'nao ha nada sobre isso' de 'ha algo perto, mas fraco'.",
|
||||
rotulo: "Consultar o que a empresa já sabe",
|
||||
explicacao:
|
||||
"Procura a resposta nos materiais que você cadastrou, para o assistente responder com a informação da sua empresa em vez de inventar.",
|
||||
@@ -31,10 +34,6 @@ export const TOOLS_EVOLUCAO = declararTools([
|
||||
{
|
||||
name: "crm_list_knowledge_sources",
|
||||
category: "read",
|
||||
description:
|
||||
"Lista os materiais que compoem o acervo de conhecimento da organizacao, com estado de " +
|
||||
"indexacao e contagem de trechos. Util para saber se uma resposta faltou por ausencia de " +
|
||||
"material ou por falha de indexacao.",
|
||||
rotulo: "Ver os materiais cadastrados",
|
||||
explicacao:
|
||||
"Mostra quais materiais estão no acervo da empresa e se foram processados, para saber se faltou conteúdo ou se algo falhou.",
|
||||
@@ -45,9 +44,6 @@ export const TOOLS_EVOLUCAO = declararTools([
|
||||
{
|
||||
name: "crm_list_improvement_proposals",
|
||||
category: "read",
|
||||
description:
|
||||
"Lista as propostas de melhoria que o ciclo de aprendizado gerou, com tipo, alvo e a " +
|
||||
"evidência que as motivou. LEITURA apenas: aprovar continua sendo decisão de uma pessoa.",
|
||||
rotulo: "Ver sugestões de melhoria",
|
||||
explicacao:
|
||||
"Mostra as melhorias que o sistema sugeriu a partir dos atendimentos, com o motivo de cada uma. Aprovar continua sendo decisão sua.",
|
||||
@@ -58,9 +54,6 @@ export const TOOLS_EVOLUCAO = declararTools([
|
||||
{
|
||||
name: "crm_get_org_memory",
|
||||
category: "read",
|
||||
description:
|
||||
"Lê o que a empresa registrou sobre si mesma — políticas, combinados e aprendizados que " +
|
||||
"valem para todo atendimento.",
|
||||
rotulo: "Consultar as regras da empresa",
|
||||
explicacao:
|
||||
"Lê as políticas e combinados que valem para todo atendimento, para o assistente seguir a regra da casa em vez de inventar uma.",
|
||||
@@ -71,9 +64,6 @@ export const TOOLS_EVOLUCAO = declararTools([
|
||||
{
|
||||
name: "crm_save_org_memory",
|
||||
category: "write",
|
||||
description:
|
||||
"Registra um aprendizado que vale para toda a operação. Nasce com origem 'agent' para o " +
|
||||
"humano distinguir o que a IA anotou do que ele mesmo escreveu.",
|
||||
rotulo: "Anotar uma regra aprendida",
|
||||
explicacao:
|
||||
"Guarda um aprendizado que vale para todos os atendimentos, marcado como escrito pelo assistente para você distinguir do que anotou.",
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
/**
|
||||
* Capacidades de FUNIL — oportunidades de venda e as etapas por onde passam.
|
||||
*
|
||||
* `description` fala com o modelo; `rotulo`/`explicacao`/`oQueToca` falam com o
|
||||
* humano que configura o agente. Ver `docs/handoffs/BRIEFING-ia-360.md` §4.
|
||||
* ESTE ARQUIVO FALA COM O HUMANO que configura o agente — `rotulo`,
|
||||
* `explicacao` e `oQueToca`. O texto que vai ao MODELO é a `description` do
|
||||
* HANDLER (`lib/mcp/tools/<dominio>.ts`), e ela NÃO tem cópia aqui: até
|
||||
* 2026-08-07 tinha, ninguém lia essa cópia, e 48 das 51 divergiam do que o
|
||||
* modelo realmente recebia. O campo foi removido em vez de sincronizado —
|
||||
* duplicata que ninguém lê não é documentação, é armadilha: um script de
|
||||
* medição de vazamento chegou a montar o prompt com o texto errado, sob um
|
||||
* comentário dizendo "a ferramenta como o modelo a vê".
|
||||
* Ver `docs/handoffs/BRIEFING-ia-360.md` §4.
|
||||
*/
|
||||
import { declararTools } from "./tipos";
|
||||
|
||||
@@ -10,7 +17,6 @@ export const TOOLS_FUNIL = declararTools([
|
||||
{
|
||||
name: "crm_list_leads",
|
||||
category: "read",
|
||||
description: "Lista leads de um pipeline (com owner_user_name, stage, tags)",
|
||||
rotulo: "Listar oportunidades do funil",
|
||||
explicacao:
|
||||
"Lista as oportunidades de venda de um funil, com a etapa em que cada uma está e quem é o responsável por ela.",
|
||||
@@ -21,7 +27,6 @@ export const TOOLS_FUNIL = declararTools([
|
||||
{
|
||||
name: "crm_get_lead",
|
||||
category: "read",
|
||||
description: "Detalhe de lead (com owner_user_name, stage, tags)",
|
||||
rotulo: "Ver uma oportunidade",
|
||||
explicacao:
|
||||
"Abre os detalhes de uma oportunidade de venda: etapa atual, responsável, marcadores e valor do negócio.",
|
||||
@@ -32,7 +37,6 @@ export const TOOLS_FUNIL = declararTools([
|
||||
{
|
||||
name: "crm_list_pipelines",
|
||||
category: "read",
|
||||
description: "Lista pipelines da org",
|
||||
rotulo: "Listar funis",
|
||||
explicacao:
|
||||
"Mostra os funis de venda existentes e suas etapas, para o agente saber onde pode colocar uma oportunidade.",
|
||||
@@ -43,7 +47,6 @@ export const TOOLS_FUNIL = declararTools([
|
||||
{
|
||||
name: "crm_create_lead",
|
||||
category: "write",
|
||||
description: "Cria um lead",
|
||||
rotulo: "Criar oportunidade no funil",
|
||||
explicacao:
|
||||
"Registra uma nova oportunidade de venda no funil, para que o interesse demonstrado pelo cliente não se perca.",
|
||||
@@ -54,7 +57,6 @@ export const TOOLS_FUNIL = declararTools([
|
||||
{
|
||||
name: "crm_update_lead",
|
||||
category: "write",
|
||||
description: "Atualiza campos de um lead",
|
||||
rotulo: "Atualizar uma oportunidade",
|
||||
explicacao:
|
||||
"Altera dados de uma oportunidade de venda: valor do negócio, responsável e informações colhidas na conversa.",
|
||||
@@ -65,7 +67,6 @@ export const TOOLS_FUNIL = declararTools([
|
||||
{
|
||||
name: "crm_move_lead_stage",
|
||||
category: "write",
|
||||
description: "Move lead para outro stage",
|
||||
rotulo: "Mover oportunidade de etapa",
|
||||
explicacao:
|
||||
"Move a oportunidade para outra etapa do funil, registrando o avanço da negociação ou a perda do negócio.",
|
||||
|
||||
@@ -2,8 +2,15 @@
|
||||
* Capacidades de GOVERNANCA — fila, direcionamento, marcadores e a passagem
|
||||
* do atendimento automatico para uma pessoa.
|
||||
*
|
||||
* `description` fala com o modelo; `rotulo`/`explicacao`/`oQueToca` falam com o
|
||||
* humano que configura o agente. Ver `docs/handoffs/BRIEFING-ia-360.md` §4.
|
||||
* ESTE ARQUIVO FALA COM O HUMANO que configura o agente — `rotulo`,
|
||||
* `explicacao` e `oQueToca`. O texto que vai ao MODELO é a `description` do
|
||||
* HANDLER (`lib/mcp/tools/<dominio>.ts`), e ela NÃO tem cópia aqui: até
|
||||
* 2026-08-07 tinha, ninguém lia essa cópia, e 48 das 51 divergiam do que o
|
||||
* modelo realmente recebia. O campo foi removido em vez de sincronizado —
|
||||
* duplicata que ninguém lê não é documentação, é armadilha: um script de
|
||||
* medição de vazamento chegou a montar o prompt com o texto errado, sob um
|
||||
* comentário dizendo "a ferramenta como o modelo a vê".
|
||||
* Ver `docs/handoffs/BRIEFING-ia-360.md` §4.
|
||||
*/
|
||||
import { declararTools } from "./tipos";
|
||||
|
||||
@@ -11,7 +18,6 @@ export const TOOLS_GOVERNANCA = declararTools([
|
||||
{
|
||||
name: "crm_get_queue_status",
|
||||
category: "read",
|
||||
description: "Snapshot da fila de atendimento da org",
|
||||
rotulo: "Ver a fila de atendimento",
|
||||
explicacao:
|
||||
"Mostra quantas pessoas estão esperando atendimento agora e há quanto tempo, para priorizar quem espera mais.",
|
||||
@@ -22,7 +28,6 @@ export const TOOLS_GOVERNANCA = declararTools([
|
||||
{
|
||||
name: "crm_assign_conversation",
|
||||
category: "write",
|
||||
description: "Atribui/transfere/libera uma conversa",
|
||||
rotulo: "Direcionar conversa para alguém",
|
||||
explicacao:
|
||||
"Passa a conversa para um atendente, transfere para outra pessoa ou devolve o cliente para a fila de espera.",
|
||||
@@ -33,7 +38,6 @@ export const TOOLS_GOVERNANCA = declararTools([
|
||||
{
|
||||
name: "crm_manage_tags",
|
||||
category: "write",
|
||||
description: "Adiciona/remove tags em conversation/contact/lead",
|
||||
rotulo: "Aplicar marcadores",
|
||||
explicacao:
|
||||
"Adiciona ou remove marcadores numa conversa, cliente ou oportunidade, para organizar e filtrar a operação depois.",
|
||||
@@ -44,7 +48,6 @@ export const TOOLS_GOVERNANCA = declararTools([
|
||||
{
|
||||
name: "crm_request_human_handoff",
|
||||
category: "handoff",
|
||||
description: "Solicita handoff para atendente humano",
|
||||
rotulo: "Chamar um atendente humano",
|
||||
explicacao:
|
||||
"Interrompe o atendimento automático e chama uma pessoa, entregando um resumo do que já aconteceu na conversa.",
|
||||
|
||||
@@ -3,8 +3,15 @@
|
||||
* etapas do funil, marcadores, respostas prontas, entradas automáticas de
|
||||
* contatos, regras automáticas e time.
|
||||
*
|
||||
* `description` fala com o modelo; `rotulo`/`explicacao`/`oQueToca` falam com o
|
||||
* humano que configura o agente. Ver `docs/handoffs/BRIEFING-ia-360.md` §4.
|
||||
* ESTE ARQUIVO FALA COM O HUMANO que configura o agente — `rotulo`,
|
||||
* `explicacao` e `oQueToca`. O texto que vai ao MODELO é a `description` do
|
||||
* HANDLER (`lib/mcp/tools/<dominio>.ts`), e ela NÃO tem cópia aqui: até
|
||||
* 2026-08-07 tinha, ninguém lia essa cópia, e 48 das 51 divergiam do que o
|
||||
* modelo realmente recebia. O campo foi removido em vez de sincronizado —
|
||||
* duplicata que ninguém lê não é documentação, é armadilha: um script de
|
||||
* medição de vazamento chegou a montar o prompt com o texto errado, sob um
|
||||
* comentário dizendo "a ferramenta como o modelo a vê".
|
||||
* Ver `docs/handoffs/BRIEFING-ia-360.md` §4.
|
||||
*
|
||||
* ⚠️ SOBRE O RISCO `critico` DESTE ARQUIVO. O gate mecânico
|
||||
* (`tests/unit/catalogo-tools-leigo-friendly.test.ts`) só sabe conferir que uma
|
||||
@@ -51,7 +58,6 @@ export const TOOLS_OPERACAO = declararTools([
|
||||
{
|
||||
name: "crm_list_stages",
|
||||
category: "read",
|
||||
description: "Lista as etapas ativas de um pipeline, na ordem do quadro",
|
||||
rotulo: "Ver as etapas de um funil",
|
||||
explicacao:
|
||||
"Mostra as colunas de um funil na ordem em que aparecem no quadro, para o agente saber onde pode colocar cada negócio.",
|
||||
@@ -62,7 +68,6 @@ export const TOOLS_OPERACAO = declararTools([
|
||||
{
|
||||
name: "crm_create_stage",
|
||||
category: "write",
|
||||
description: "Cria uma etapa no fim do pipeline",
|
||||
rotulo: "Criar etapa no funil",
|
||||
explicacao:
|
||||
"Acrescenta uma coluna nova no fim do funil, quando o jeito de trabalhar da empresa tem um passo que ainda não está no quadro.",
|
||||
@@ -74,7 +79,6 @@ export const TOOLS_OPERACAO = declararTools([
|
||||
{
|
||||
name: "crm_update_stage",
|
||||
category: "write",
|
||||
description: "Renomeia, reordena ou muda o papel de desfecho de uma etapa",
|
||||
rotulo: "Renomear ou reordenar uma etapa",
|
||||
explicacao:
|
||||
"Troca o nome de uma coluna do funil, muda o lugar dela na ordem ou define em qual delas o negócio é dado como fechado ou perdido.",
|
||||
@@ -86,7 +90,6 @@ export const TOOLS_OPERACAO = declararTools([
|
||||
{
|
||||
name: "crm_archive_stage",
|
||||
category: "write",
|
||||
description: "Arquiva uma etapa e move os leads dela para outra",
|
||||
rotulo: "Arquivar uma etapa do funil",
|
||||
explicacao:
|
||||
"Tira uma coluna do quadro e leva os negócios que estavam nela para outra coluna que você escolher. O histórico continua guardado.",
|
||||
@@ -100,7 +103,6 @@ export const TOOLS_OPERACAO = declararTools([
|
||||
{
|
||||
name: "crm_list_tags",
|
||||
category: "read",
|
||||
description: "Lista as tags em uso na org, com contagem e origem",
|
||||
rotulo: "Ver os marcadores em uso",
|
||||
explicacao:
|
||||
"Mostra quais marcadores a empresa já usa e em quantas conversas, para o agente reaproveitar em vez de inventar outro parecido.",
|
||||
@@ -113,7 +115,6 @@ export const TOOLS_OPERACAO = declararTools([
|
||||
{
|
||||
name: "crm_list_message_templates",
|
||||
category: "read",
|
||||
description: "Lista os message_templates da org",
|
||||
rotulo: "Ver as respostas prontas",
|
||||
explicacao:
|
||||
"Lista os textos que a empresa já escreveu para responder as situações de sempre, com o atalho de cada um.",
|
||||
@@ -124,7 +125,6 @@ export const TOOLS_OPERACAO = declararTools([
|
||||
{
|
||||
name: "crm_render_message_template",
|
||||
category: "read",
|
||||
description: "Preenche um template com dados do contato/lead e devolve o texto",
|
||||
rotulo: "Preencher uma resposta pronta",
|
||||
explicacao:
|
||||
"Pega uma resposta pronta e troca as lacunas pelos dados do cliente, avisando se sobrou alguma sem preencher. Não envia nada.",
|
||||
@@ -137,7 +137,6 @@ export const TOOLS_OPERACAO = declararTools([
|
||||
{
|
||||
name: "crm_list_webhook_sources",
|
||||
category: "read",
|
||||
description: "Lista as webhook_sources da org com destino e último recebimento",
|
||||
rotulo: "Ver as entradas automáticas de contatos",
|
||||
explicacao:
|
||||
"Mostra por onde chegam sozinhos os contatos vindos do site ou de outro sistema, se cada uma está ligada e quando recebeu pela última vez.",
|
||||
@@ -148,7 +147,6 @@ export const TOOLS_OPERACAO = declararTools([
|
||||
{
|
||||
name: "crm_list_webhook_source_events",
|
||||
category: "read",
|
||||
description: "Últimos recebimentos de uma webhook_source (só as chaves do corpo)",
|
||||
rotulo: "Ver o que chegou por uma entrada",
|
||||
explicacao:
|
||||
"Mostra os últimos contatos que entraram por uma origem e quais informações vieram, para descobrir por que algo não chegou como devia.",
|
||||
@@ -159,7 +157,6 @@ export const TOOLS_OPERACAO = declararTools([
|
||||
{
|
||||
name: "crm_create_webhook_source",
|
||||
category: "write",
|
||||
description: "Cria uma webhook_source e devolve o path_token da URL",
|
||||
rotulo: "Criar uma entrada automática de contatos",
|
||||
explicacao:
|
||||
"Abre um endereço novo para o site da empresa mandar contatos direto para um funil. A partir daí ele passa a receber gente de fora sozinho.",
|
||||
@@ -171,7 +168,6 @@ export const TOOLS_OPERACAO = declararTools([
|
||||
{
|
||||
name: "crm_set_webhook_source_active",
|
||||
category: "write",
|
||||
description: "Liga/desliga uma webhook_source (is_active)",
|
||||
rotulo: "Ligar ou desligar uma entrada de contatos",
|
||||
explicacao:
|
||||
"Faz uma origem voltar a receber contatos, ou parar. Desligada, o formulário do seu site continua no ar e ninguém do outro lado é avisado.",
|
||||
@@ -185,7 +181,6 @@ export const TOOLS_OPERACAO = declararTools([
|
||||
{
|
||||
name: "crm_list_automation_rules",
|
||||
category: "read",
|
||||
description: "Lista as automation_rules com gatilho e tipos de ação",
|
||||
rotulo: "Ver as regras automáticas",
|
||||
explicacao:
|
||||
"Mostra o que a empresa deixou configurado para acontecer sozinho, o que dispara cada regra e se ela está ligada.",
|
||||
@@ -196,7 +191,6 @@ export const TOOLS_OPERACAO = declararTools([
|
||||
{
|
||||
name: "crm_list_automation_runs",
|
||||
category: "read",
|
||||
description: "Histórico de automation_rule_runs, com detalhe só das falhas",
|
||||
rotulo: "Ver o que as regras dispararam",
|
||||
explicacao:
|
||||
"Mostra o que rodou sozinho nos últimos tempos e o que deu errado, para descobrir o que parou de funcionar sem ninguém perceber.",
|
||||
@@ -207,7 +201,6 @@ export const TOOLS_OPERACAO = declararTools([
|
||||
{
|
||||
name: "crm_set_automation_rule_active",
|
||||
category: "write",
|
||||
description: "Liga/desliga uma automation_rule (is_active)",
|
||||
rotulo: "Ligar ou desligar uma regra automática",
|
||||
explicacao:
|
||||
"Faz uma regra passar a rodar sozinha, sempre que o gatilho dela acontecer, ou parar de rodar. Ligada, ela pode falar com clientes de verdade.",
|
||||
@@ -221,7 +214,6 @@ export const TOOLS_OPERACAO = declararTools([
|
||||
{
|
||||
name: "crm_list_team_members",
|
||||
category: "read",
|
||||
description: "Lista user_organizations ativos com papel (sem PII)",
|
||||
rotulo: "Ver quem trabalha na empresa",
|
||||
explicacao:
|
||||
"Lista as pessoas do time e o que cada uma pode fazer aqui dentro, para o agente saber para quem passar um atendimento.",
|
||||
|
||||
@@ -11,8 +11,15 @@
|
||||
* cancelar, listar retorno) e registrar o desfecho (encerrar a demanda). Sem a
|
||||
* segunda, o anti-morte fica pela metade e o radar enche de negócio que já acabou.
|
||||
*
|
||||
* `description` fala com o modelo; `rotulo`/`explicacao`/`oQueToca` falam com o
|
||||
* humano que configura o agente. Ver `docs/handoffs/BRIEFING-ia-360.md` §4.
|
||||
* ESTE ARQUIVO FALA COM O HUMANO que configura o agente — `rotulo`,
|
||||
* `explicacao` e `oQueToca`. O texto que vai ao MODELO é a `description` do
|
||||
* HANDLER (`lib/mcp/tools/<dominio>.ts`), e ela NÃO tem cópia aqui: até
|
||||
* 2026-08-07 tinha, ninguém lia essa cópia, e 48 das 51 divergiam do que o
|
||||
* modelo realmente recebia. O campo foi removido em vez de sincronizado —
|
||||
* duplicata que ninguém lê não é documentação, é armadilha: um script de
|
||||
* medição de vazamento chegou a montar o prompt com o texto errado, sob um
|
||||
* comentário dizendo "a ferramenta como o modelo a vê".
|
||||
* Ver `docs/handoffs/BRIEFING-ia-360.md` §4.
|
||||
*
|
||||
* ⚠️ "FOLLOW-UP" NÃO APARECE NO TEXTO DO HUMANO. Para o dono da clínica isso se
|
||||
* chama RETORNO ou acompanhamento — o gate mecânico
|
||||
@@ -25,11 +32,6 @@ export const TOOLS_RETENCAO = declararTools([
|
||||
{
|
||||
name: "crm_schedule_followup",
|
||||
category: "write",
|
||||
description:
|
||||
"Agenda um retorno futuro ao cliente (cron_job one-shot kind='at', job_kind='followup_turn'). " +
|
||||
"Informe lead_id OU contact_id; promised_at é ISO 8601 ABSOLUTO e no futuro, dentro da janela " +
|
||||
"aceitável da organização. Recusa se já houver retorno vivo para o contato (1 por contato — " +
|
||||
"anti-empilhamento). Emite atividade na timeline do negócio.",
|
||||
rotulo: "Agendar um retorno para o cliente",
|
||||
explicacao:
|
||||
"Marca um horário para o agente voltar a falar com o cliente, para que a conversa não morra sem resposta.",
|
||||
@@ -44,10 +46,6 @@ export const TOOLS_RETENCAO = declararTools([
|
||||
{
|
||||
name: "crm_cancel_followup",
|
||||
category: "write",
|
||||
description:
|
||||
"Cancela um retorno agendado que ainda não disparou (marca cancelled_at + cancel_reason). " +
|
||||
"Use quando o cliente já respondeu ou o motivo do retorno deixou de existir. " +
|
||||
"Recusa se o retorno já disparou ou já foi cancelado. Emite atividade na timeline.",
|
||||
rotulo: "Cancelar um retorno agendado",
|
||||
explicacao:
|
||||
"Desmarca um retorno que ainda não aconteceu, para o agente não insistir com quem já respondeu.",
|
||||
@@ -58,10 +56,6 @@ export const TOOLS_RETENCAO = declararTools([
|
||||
{
|
||||
name: "crm_list_followups",
|
||||
category: "read",
|
||||
description:
|
||||
"Lista os retornos de um cliente (informe lead_id OU contact_id), do mais próximo para o mais " +
|
||||
"antigo, com a situação de cada um: agendado, disparado ou cancelado (com o motivo). " +
|
||||
"É por aqui que o agente descobre que um humano desmarcou um retorno.",
|
||||
rotulo: "Ver os retornos de um cliente",
|
||||
explicacao:
|
||||
"Mostra os retornos combinados com o cliente: o que está marcado, o que já aconteceu e o que foi desmarcado.",
|
||||
@@ -72,12 +66,6 @@ export const TOOLS_RETENCAO = declararTools([
|
||||
{
|
||||
name: "crm_list_at_risk_leads",
|
||||
category: "read",
|
||||
description:
|
||||
"Radar de risco: oportunidades ABERTAS que passaram da janela de esfriamento do próprio " +
|
||||
"estágio, classificadas em critico / em_risco / em_voo (em_voo = há retorno agendado). " +
|
||||
"Ordenado por urgência. Aceita limit e min_hours. Traz também `sem_proximo_passo`: " +
|
||||
"demandas abertas sem nada marcado para acontecer, resolvíveis com crm_schedule_followup " +
|
||||
"(por contact_id) ou crm_close_demand.",
|
||||
rotulo: "Ver quem esfriou e quem ficou sem próximo passo",
|
||||
explicacao:
|
||||
"Lista as oportunidades abertas que passaram do prazo sem movimento, das mais críticas para as menos " +
|
||||
@@ -89,10 +77,6 @@ export const TOOLS_RETENCAO = declararTools([
|
||||
{
|
||||
name: "crm_close_demand",
|
||||
category: "write",
|
||||
description:
|
||||
"Encerra a demanda movendo a oportunidade para o estágio terminal do funil: outcome='won' ou " +
|
||||
"outcome='lost' (neste, reason é obrigatório). O status e a data de fechamento são do trigger. " +
|
||||
"Idempotente. Emite atividade na timeline.",
|
||||
rotulo: "Encerrar o negócio como ganho ou perdido",
|
||||
explicacao:
|
||||
"Fecha a oportunidade dizendo se ela foi ganha ou perdida e por quê, para o que já acabou parar de ser cobrado.",
|
||||
@@ -106,10 +90,6 @@ export const TOOLS_RETENCAO = declararTools([
|
||||
{
|
||||
name: "crm_propose_reactivation",
|
||||
category: "write",
|
||||
description:
|
||||
"Cria uma proposta de reativação (crm_lead_reactivations, status='pending') para uma " +
|
||||
"oportunidade que esfriou. A proposta VENCE sozinha na mesma janela do esfriamento e o envio " +
|
||||
"só acontece se um humano aprovar. Recusa se já houver proposta viva ou se não houver contato.",
|
||||
rotulo: "Sugerir retomar contato com quem sumiu",
|
||||
explicacao:
|
||||
"Cria uma sugestão de retomar o contato com um cliente que esfriou, para uma pessoa aprovar antes de qualquer envio.",
|
||||
|
||||
@@ -13,8 +13,6 @@ export interface McpToolCatalogEntry {
|
||||
name: string;
|
||||
/** Dirige requiresScope/requiresRole. Nao e rotulo de tela. */
|
||||
category: McpToolCategory;
|
||||
/** Texto tecnico entregue ao MODELO. */
|
||||
description: string;
|
||||
|
||||
// ---- camada de apresentacao: o que o HUMANO le ----
|
||||
/** Verbo no infinitivo, pt-BR. Ex: "Mover oportunidade de etapa". */
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
import { chromium } from "@playwright/test";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
const BASE = "http://127.0.0.1:3100";
|
||||
const BASE = process.env.E2E_PORT ? `http://127.0.0.1:${process.env.E2E_PORT}` : "http://127.0.0.1:3100";
|
||||
const AGENTE = "36ed74d7-3505-46ab-bc2b-9656626e118c"; // kind=mcp_agent — o rag_bot cai no editor legado, sem ToolPicker
|
||||
const c = JSON.parse(readFileSync("/Users/rafaelmelgaco/DeskcommCRM/.e2e-creds.json", "utf8"));
|
||||
|
||||
|
||||
@@ -100,4 +100,27 @@ describe("catálogo real ↔ handlers reais", () => {
|
||||
expect(s.pacotes.length, `${s.id} sem pacote`).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
it("toda capacidade servida tem descrição — e ela vem do HANDLER", () => {
|
||||
// Esta guarda faltava, e a falta ficou visível em 2026-08-07: o catálogo
|
||||
// tinha uma `description` própria que NENHUM consumidor lia (a ponte do
|
||||
// turno monta `def.description`, do handler) e que divergia da real em 48
|
||||
// das 51 capacidades. Ela foi removida; o campo do catálogo não existe mais.
|
||||
//
|
||||
// Sem este caso, esvaziar a descrição de um handler passaria calado: a tela
|
||||
// mostraria a capacidade sem explicação técnica e o modelo receberia uma
|
||||
// ferramenta sem contrato — dois silêncios de uma vez.
|
||||
const servidas = juntarCatalogoComHandlers(allTools, TOOL_CATALOG);
|
||||
const porNome = new Map(allTools.map((t) => [t.name, t.description]));
|
||||
expect(servidas.length, "nada servido — guarda de vacuidade").toBeGreaterThan(0);
|
||||
for (const s of servidas) {
|
||||
expect(s.description.trim().length, `${s.id} servido sem descrição`).toBeGreaterThan(0);
|
||||
// A IDENTIDADE é o que importa: não basta ter texto, tem de ser o MESMO
|
||||
// que chega ao modelo. Se um dia reaparecer uma cópia no catálogo e a
|
||||
// junção passar a preferi-la, este caso reprova.
|
||||
expect(s.description, `${s.id}: a tela mostra texto diferente do que vai ao modelo`).toBe(
|
||||
porNome.get(s.id),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user