feat(secretaria): assina as respostas da IA com "*Nome:*" em negrito

A Secretária passa a prefixar cada resposta enviada com o PRIMEIRO nome do
agente em negrito (ex.: "Ana — Atendente Virtual" → "*Ana:*\n<resposta>"),
igual à assinatura do operador humano — identifica a IA na caixa compartilhada
e é por-sessão (número com agente "Ana Clínica A" assina "*Ana:*"). Aplicado no
envio e no corpo persistido, nos dois caminhos (auto-reply e painel /ask).

Testado em dev: a Ana respondeu no WhatsApp com "*Ana:*" em negrito.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Deploy Agent
2026-07-06 00:19:30 +02:00
parent 00ec9ed933
commit 9bbfe498e2
3 changed files with 28 additions and 9 deletions
@@ -205,11 +205,17 @@ function buildExtRoutes(prisma, manager, db, config, hooks, io) {
const router = (0, express_1.Router)(); const router = (0, express_1.Router)();
// ── Helper: envia reply da IA via WA e persiste no banco principal ───────── // ── Helper: envia reply da IA via WA e persiste no banco principal ─────────
async function sendSecretariaReply(opts) { async function sendSecretariaReply(opts) {
const { instanceId, tenantId, chatId, jid, reply } = opts; const { instanceId, tenantId, chatId, jid, reply, agentName } = opts;
const sock = manager?.getSocket(instanceId); const sock = manager?.getSocket(instanceId);
if (!sock) if (!sock)
return; return;
const sent = await sock.sendMessage(jid, { text: reply }).catch(() => null); // Assinatura da Secretária: prefixa "*Nome:*\n" (negrito) com o PRIMEIRO nome
// do agente (ex.: "Ana — Atendente Virtual" → "Ana"). Identifica a IA na caixa
// compartilhada — como a assinatura do operador humano. O ":" fica DENTRO do
// negrito p/ todos os dispositivos verem "Ana:" em negrito consistente.
const first = (agentName ?? '').trim().split(/[\s—–-]+/).filter(Boolean)[0];
const text = first ? `*${first}:*\n${reply}` : reply;
const sent = await sock.sendMessage(jid, { text }).catch(() => null);
const persisted = await prisma.message.create({ const persisted = await prisma.message.create({
data: { data: {
tenantId, tenantId,
@@ -219,7 +225,7 @@ function buildExtRoutes(prisma, manager, db, config, hooks, io) {
messageId: sent?.key.id ?? `sec-${Date.now()}`, messageId: sent?.key.id ?? `sec-${Date.now()}`,
fromMe: true, fromMe: true,
type: 'TEXT', type: 'TEXT',
body: reply, body: text,
status: 'SENT', status: 'SENT',
timestamp: new Date(), timestamp: new Date(),
}, },
@@ -345,7 +351,8 @@ function buildExtRoutes(prisma, manager, db, config, hooks, io) {
const brain = new brain_1.ProtocolEngine(db, config); const brain = new brain_1.ProtocolEngine(db, config);
const reply = await brain.chat(conv.id, text.trim(), { tenantId, hooks, clinicaId: channel?.clinica_id, instanceId }); const reply = await brain.chat(conv.id, text.trim(), { tenantId, hooks, clinicaId: channel?.clinica_id, instanceId });
await sock?.sendPresenceUpdate('paused', jid).catch(() => { }); await sock?.sendPresenceUpdate('paused', jid).catch(() => { });
await sendSecretariaReply({ instanceId, tenantId, chatId, jid, reply }); const secAgent = await db('sec_agents').where({ id: conv.agent_id }).select('name').first();
await sendSecretariaReply({ instanceId, tenantId, chatId, jid, reply, agentName: secAgent?.name });
}); });
// ── Escalation notification listener ────────────────────────────────────── // ── Escalation notification listener ──────────────────────────────────────
// When escalar_humano tool fires, send a WA message to the configured admin phone. // When escalar_humano tool fires, send a WA message to the configured admin phone.
@@ -1795,12 +1802,14 @@ function buildExtRoutes(prisma, manager, db, config, hooks, io) {
select: { id: true, instanceId: true }, select: { id: true, instanceId: true },
}); });
if (chat) { if (chat) {
const secAgent = await db('sec_agents').where({ id: conv.agent_id }).select('name').first();
await sendSecretariaReply({ await sendSecretariaReply({
instanceId: chat.instanceId, instanceId: chat.instanceId,
tenantId, tenantId,
chatId: chat.id, chatId: chat.id,
jid: chatId, jid: chatId,
reply, reply,
agentName: secAgent?.name,
}); });
} }
res.json({ res.json({
File diff suppressed because one or more lines are too long
@@ -182,11 +182,18 @@ export function buildExtRoutes(
chatId: string chatId: string
jid: string jid: string
reply: string reply: string
agentName?: string
}): Promise<void> { }): Promise<void> {
const { instanceId, tenantId, chatId, jid, reply } = opts const { instanceId, tenantId, chatId, jid, reply, agentName } = opts
const sock = manager?.getSocket(instanceId) const sock = manager?.getSocket(instanceId)
if (!sock) return if (!sock) return
const sent = await sock.sendMessage(jid, { text: reply }).catch(() => null) // Assinatura da Secretária: prefixa "*Nome:*\n" (negrito) com o PRIMEIRO nome
// do agente (ex.: "Ana — Atendente Virtual" → "Ana"). Identifica a IA na caixa
// compartilhada — como a assinatura do operador humano. O ":" fica DENTRO do
// negrito p/ todos os dispositivos verem "Ana:" em negrito consistente.
const first = (agentName ?? '').trim().split(/[\s—–-]+/).filter(Boolean)[0]
const text = first ? `*${first}:*\n${reply}` : reply
const sent = await sock.sendMessage(jid, { text }).catch(() => null)
const persisted = await prisma.message.create({ const persisted = await prisma.message.create({
data: { data: {
tenantId, tenantId,
@@ -196,7 +203,7 @@ export function buildExtRoutes(
messageId: sent?.key.id ?? `sec-${Date.now()}`, messageId: sent?.key.id ?? `sec-${Date.now()}`,
fromMe: true, fromMe: true,
type: 'TEXT', type: 'TEXT',
body: reply, body: text,
status: 'SENT', status: 'SENT',
timestamp: new Date(), timestamp: new Date(),
}, },
@@ -328,7 +335,8 @@ export function buildExtRoutes(
const reply = await brain.chat(conv.id as string, text.trim(), { tenantId, hooks, clinicaId: channel?.clinica_id, instanceId }) const reply = await brain.chat(conv.id as string, text.trim(), { tenantId, hooks, clinicaId: channel?.clinica_id, instanceId })
await sock?.sendPresenceUpdate('paused', jid).catch(() => {}) await sock?.sendPresenceUpdate('paused', jid).catch(() => {})
await sendSecretariaReply({ instanceId, tenantId, chatId, jid, reply }) const secAgent = await db('sec_agents').where({ id: conv.agent_id }).select('name').first()
await sendSecretariaReply({ instanceId, tenantId, chatId, jid, reply, agentName: secAgent?.name })
}) })
// ── Escalation notification listener ────────────────────────────────────── // ── Escalation notification listener ──────────────────────────────────────
@@ -1662,12 +1670,14 @@ export function buildExtRoutes(
select: { id: true, instanceId: true }, select: { id: true, instanceId: true },
}) })
if (chat) { if (chat) {
const secAgent = await db('sec_agents').where({ id: conv.agent_id }).select('name').first()
await sendSecretariaReply({ await sendSecretariaReply({
instanceId: chat.instanceId, instanceId: chat.instanceId,
tenantId, tenantId,
chatId: chat.id, chatId: chat.id,
jid: chatId, jid: chatId,
reply, reply,
agentName: secAgent?.name,
}) })
} }