feat(secretaria): roteamento por especialidade, situações, dúvida→humano, imagem, checklist do dono e desligar global
Secretária IA mais segura e contextual: - Roteamento por especialidade: /contexto (dentistas+especialidades) injetado no cérebro; descobrir o motivo e priorizar clínico geral só p/ avaliação/limpeza. - Situações da clínica: regras editáveis injetadas no prompt (siga à risca). - Dúvida/caso incerto → tool encaminhar_humano (não agenda; pede p/ aguardar). - Imagem/áudio: ext:message.transcription religa no fluxo ext:message.new (guard hadCaption evita processar 2x); a IA reage à descrição da imagem. - Checklist do dono × banco → cautelas no /contexto: NÃO assumir clínico geral por padrão p/ quem já é paciente; sem certeza de quem/qual dentista → humano. - Desligar GLOBAL da Secretária: GET/POST /secretaria/power (kill-switch auto_reply via config.set, efeito imediato, sem restart; visível a todos). Inclui a plumbing da sessão (chat.upsert, ownership de conexão, ws-bridge). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -390,6 +390,53 @@ exports.BUILTIN_TOOLS = [
|
||||
}
|
||||
},
|
||||
},
|
||||
// ── encaminhar_humano ───────────────────────────────────────────────────────
|
||||
// Dúvida/caso incerto (ou regra da clínica que mande AVALIAR): a IA NÃO agenda —
|
||||
// encaminha à secretária humana decidir e pede ao cliente para aguardar.
|
||||
{
|
||||
name: 'encaminhar_humano',
|
||||
description: 'Use QUANDO NÃO for caso de agendar direto: dúvida inicial do cliente, caso clínico que você não sabe ' +
|
||||
'resolver, imagem/foto que precise de avaliação humana, ou quando uma REGRA/SITUAÇÃO da clínica mandar ' +
|
||||
'AVALIAR antes (ex.: "pelo plano não temos profissional para canal posterior — ver chance de agendar ' +
|
||||
'avaliação"). NÃO agende: registre com esta tool e peça ao cliente para AGUARDAR, que a clínica retorna ' +
|
||||
'com uma posição. Não mencione "secretária humana" nem garanta nada. Nem sempre o certo é agendar.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
resumo: { type: 'string', description: 'Resumo objetivo da dúvida/situação para a secretária humana decidir (inclua o que o cliente quer, o motivo e, se houve imagem, o que você identificou).' },
|
||||
nome_cliente: { type: 'string', description: 'Nome do cliente, se souber (opcional).' },
|
||||
telefone_cliente: { type: 'string', description: 'Telefone do cliente (opcional).' },
|
||||
},
|
||||
required: ['resumo'],
|
||||
},
|
||||
async execute(args, ctx) {
|
||||
if (!ctx.agenda?.url || !ctx.agenda.clinicaId)
|
||||
return { ok: false, erro: 'Agenda indisponível.' };
|
||||
let telefone = String(args['telefone_cliente'] ?? '').replace(/\D/g, '');
|
||||
if (!telefone && ctx.extChatId) {
|
||||
const jid = ctx.extChatId.split(':').pop() ?? '';
|
||||
telefone = (jid.split('@')[0] ?? '').replace(/\D/g, '');
|
||||
}
|
||||
try {
|
||||
const r = await fetch(`${ctx.agenda.url.replace(/\/$/, '')}/duvida`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'x-nw-agenda-secret': ctx.agenda.secret ?? '' },
|
||||
signal: AbortSignal.timeout(12000),
|
||||
body: JSON.stringify({
|
||||
clinica_id: ctx.agenda.clinicaId, resumo: args['resumo'],
|
||||
nome_cliente: args['nome_cliente'] ?? null, telefone,
|
||||
}),
|
||||
});
|
||||
const j = await r.json().catch(() => ({}));
|
||||
if (!r.ok || j.ok === false)
|
||||
return { ok: false, mensagem: j.error ?? 'Não foi possível encaminhar.' };
|
||||
return { ok: true, registrado: true, instrucao: 'Responda ao cliente que você vai verificar isso com a clínica e retorna em breve com uma posição — peça para aguardar. NÃO agende, NÃO garanta nada e NÃO mencione "secretária".' };
|
||||
}
|
||||
catch (e) {
|
||||
return { ok: false, erro: e.message };
|
||||
}
|
||||
},
|
||||
},
|
||||
// ── confirmar_depois ────────────────────────────────────────────────────────
|
||||
// Parte B: o cliente pede um horário MAIS TARDE que o oferecido. A IA diz "vou
|
||||
// confirmar, aguarde uns minutinhos" e registra um follow-up; um worker retorna
|
||||
|
||||
Reference in New Issue
Block a user