feat(secretaria): Secretária IA separada por sessão (agente por número)
Núcleo desta mudança (separação por sessão): - migrate.ts: sec_numbers.agent_id (FK→sec_agents, ON DELETE SET NULL) e sec_calendar.instance_id, com índices (aditivo/idempotente). - ext-api routes.ts: runtime resolve o agente pelo NÚMERO que recebeu a mensagem (sec_numbers[instanceId].agent_id, fallback: 1º ativo); rotas admin /secretaria/numbers e /secretaria/calendar aceitam os novos campos. - brain.ts/tools.ts: instanceId no contexto; sec_calendar interno filtrado por sessão; parse de jid robusto (.pop(), suporta 2 e 3 partes). - secretaria/routes.ts: paridade (agent_id em numbers, instance_id no calendar). Testado em dev (newwhats.dev refrescado): agentes resolvidos por instância; fallback e FK ON DELETE SET NULL verificados via ext-api. Obs.: por estarem no mesmo working tree não-commitado do newwhats.local, ext-api/routes.ts e secretaria/tools.ts também trazem o trabalho acumulado da integração do satélite (ações de inbox: delete/react/typing/labels/forward e admin /secretaria/*; tools identificar_numero/cadastrar_paciente do odonto). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -122,6 +122,18 @@ async function runMigrations(db) {
|
||||
t.timestamps(true, true);
|
||||
});
|
||||
}
|
||||
// ── sec_calendar — instance_id (agenda interna por sessão) ────────────────
|
||||
// Separa o calendário interno por número de WhatsApp. Nulo = slot legado/global
|
||||
// (visível a todas as sessões, retrocompatível). No odonto a agenda real vem da
|
||||
// ponte (clinica_id por número); este é o calendário interno/fallback.
|
||||
if (await db.schema.hasTable('sec_calendar')) {
|
||||
if (!(await db.schema.hasColumn('sec_calendar', 'instance_id'))) {
|
||||
await db.schema.alterTable('sec_calendar', (t) => {
|
||||
t.string('instance_id', 100).nullable();
|
||||
});
|
||||
await db.raw('CREATE INDEX IF NOT EXISTS idx_sec_calendar_instance ON sec_calendar (instance_id)');
|
||||
}
|
||||
}
|
||||
// ── sec_numbers ───────────────────────────────────────────────────────────
|
||||
if (!(await db.schema.hasTable('sec_numbers'))) {
|
||||
await db.schema.createTable('sec_numbers', (t) => {
|
||||
@@ -142,6 +154,16 @@ async function runMigrations(db) {
|
||||
// Tabela pré-existente: adiciona o mapa canal→clínica.
|
||||
await db.schema.alterTable('sec_numbers', (t) => t.string('clinica_id', 100).nullable());
|
||||
}
|
||||
// ── sec_numbers — agent_id (agente/cérebro deste número) ──────────────────
|
||||
// Amarra cada número de WhatsApp a um agente (pool reutilizável). Nulo = usa o
|
||||
// primeiro agente ativo (comportamento legado). É a chave da separação por
|
||||
// sessão: o runtime resolve o agente pelo número que recebeu a mensagem.
|
||||
if (await db.schema.hasTable('sec_numbers') && !(await db.schema.hasColumn('sec_numbers', 'agent_id'))) {
|
||||
await db.schema.alterTable('sec_numbers', (t) => {
|
||||
t.uuid('agent_id').nullable().references('id').inTable('sec_agents').onDelete('SET NULL');
|
||||
});
|
||||
await db.raw('CREATE INDEX IF NOT EXISTS idx_sec_numbers_agent ON sec_numbers (agent_id)');
|
||||
}
|
||||
// ── sec_knowledge_chunks (RAG sem pgvector) ───────────────────────────────
|
||||
// Chunks de conhecimento + embedding (JSON em text). Busca por similaridade
|
||||
// roda na aplicação (cosseno), então não exige a extensão pgvector.
|
||||
|
||||
Reference in New Issue
Block a user