fix(secretaria): corrige 7 inconsistências do issue #14
SEC-01+SEC-11: /secretaria/ask agora envia reply via WhatsApp (sock.sendMessage) e persiste a mensagem no banco Prisma após brain.chat(), com notify Socket.IO. SEC-02+SEC-03: MessageHandler emite hookBus 'ext:message.new' para todo msg recebido. Listener no ext-api auto-dispara a Secretária apenas se NÃO houver AIBot ativo na instância (exclusão mútua — chatbot tem prioridade). SEC-04: ChatListItem exibe ícone BotOff (âmbar) quando chat.bot_paused = true, com tooltip "Bot pausado — aguardando atendente". SEC-05: chatToLegacy() mapeia c.botPaused → bot_paused na bridge legada. NewWhatsChat recebe campo bot_paused opcional. SEC-08: POST /secretaria/ask valida que existe instância CONNECTED antes de criar conversa. Retorna 503 se nenhuma instância disponível. SEC-10: POST /api/chatbot/bots/:instanceId valida que credentialId pertence ao mesmo tenantId antes de criar o bot. Retorna 403 se não pertencer. SEC-12: chatBotState.resumeBot() limpa botSummary (= null) ao retomar, evitando contexto defasado após escalação humana. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -108,7 +108,7 @@ export const chatBotState = {
|
||||
prisma.chat.update({ where: { id: chatId }, data: { botPaused: true } }),
|
||||
|
||||
resumeBot: (chatId: string) =>
|
||||
prisma.chat.update({ where: { id: chatId }, data: { botPaused: false } }),
|
||||
prisma.chat.update({ where: { id: chatId }, data: { botPaused: false, botSummary: null } }),
|
||||
|
||||
updateSummary: (chatId: string, botSummary: string) =>
|
||||
prisma.chat.update({ where: { id: chatId }, data: { botSummary } }),
|
||||
|
||||
@@ -118,6 +118,12 @@ export function buildChatbotRoutes(): Router {
|
||||
res.status(409).json({ error: 'Já existe um bot nesta instância. Use PUT para atualizar.' })
|
||||
return
|
||||
}
|
||||
// SEC-10: garante que a credencial pertence a este tenant
|
||||
const cred = await prisma.aICredential.findFirst({ where: { id: body.credentialId, tenantId } })
|
||||
if (!cred) {
|
||||
res.status(403).json({ error: 'Credencial não encontrada ou não pertence a este tenant.' })
|
||||
return
|
||||
}
|
||||
const bot = await botRepo.create({ tenantId, instanceId, ...body } as any)
|
||||
res.status(201).json(bot)
|
||||
} catch (err: any) {
|
||||
|
||||
@@ -712,6 +712,19 @@ export class MessageHandler {
|
||||
}).catch((err) => logger.error({ err }, '[Chatbot] Erro assíncrono'))
|
||||
)
|
||||
}
|
||||
|
||||
// ── 9. Emite para plugins externos (ex: Secretária IA via ext:message.new) ─
|
||||
if (!fromMe && body) {
|
||||
setImmediate(() =>
|
||||
hookBus.emit('ext:message.new', {
|
||||
tenantId: this.tenantId,
|
||||
instanceId: this.instanceId,
|
||||
chatId: chat.id,
|
||||
jid: remoteJid,
|
||||
text: body,
|
||||
}).catch((err: unknown) => logger.error({ err }, '[HookBus] Erro em ext:message.new'))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user