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)();
// ── Helper: envia reply da IA via WA e persiste no banco principal ─────────
async function sendSecretariaReply(opts) {
const { instanceId, tenantId, chatId, jid, reply } = opts;
const { instanceId, tenantId, chatId, jid, reply, agentName } = opts;
const sock = manager?.getSocket(instanceId);
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({
data: {
tenantId,
@@ -219,7 +225,7 @@ function buildExtRoutes(prisma, manager, db, config, hooks, io) {
messageId: sent?.key.id ?? `sec-${Date.now()}`,
fromMe: true,
type: 'TEXT',
body: reply,
body: text,
status: 'SENT',
timestamp: new Date(),
},
@@ -345,7 +351,8 @@ function buildExtRoutes(prisma, manager, db, config, hooks, io) {
const brain = new brain_1.ProtocolEngine(db, config);
const reply = await brain.chat(conv.id, text.trim(), { tenantId, hooks, clinicaId: channel?.clinica_id, instanceId });
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 ──────────────────────────────────────
// 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 },
});
if (chat) {
const secAgent = await db('sec_agents').where({ id: conv.agent_id }).select('name').first();
await sendSecretariaReply({
instanceId: chat.instanceId,
tenantId,
chatId: chat.id,
jid: chatId,
reply,
agentName: secAgent?.name,
});
}
res.json({
File diff suppressed because one or more lines are too long
@@ -182,11 +182,18 @@ export function buildExtRoutes(
chatId: string
jid: string
reply: string
agentName?: string
}): Promise<void> {
const { instanceId, tenantId, chatId, jid, reply } = opts
const { instanceId, tenantId, chatId, jid, reply, agentName } = opts
const sock = manager?.getSocket(instanceId)
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({
data: {
tenantId,
@@ -196,7 +203,7 @@ export function buildExtRoutes(
messageId: sent?.key.id ?? `sec-${Date.now()}`,
fromMe: true,
type: 'TEXT',
body: reply,
body: text,
status: 'SENT',
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 })
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 ──────────────────────────────────────
@@ -1662,12 +1670,14 @@ export function buildExtRoutes(
select: { id: true, instanceId: true },
})
if (chat) {
const secAgent = await db('sec_agents').where({ id: conv.agent_id }).select('name').first()
await sendSecretariaReply({
instanceId: chat.instanceId,
tenantId,
chatId: chat.id,
jid: chatId,
reply,
agentName: secAgent?.name,
})
}