feat(secretaria): Secretária IA separada por sessão (agente por número) #20

Merged
ruicesar merged 12 commits from feat/secretaria-por-sessao into main 2026-07-06 16:07:06 +02:00
Owner

Separa a Secretária/nós/agenda por número de WhatsApp via sec_numbers.agent_id (runtime resolve o agente pelo número que recebeu a mensagem; fallback = 1º agente ativo). Migração aditiva (agent_id em sec_numbers, instance_id em sec_calendar) + rotas admin. Testado em dev (newwhats.dev refrescado): agentes resolvidos por instância, fallback e FK ON DELETE SET NULL ok.

Obs.: por partilharem o working tree do newwhats.local, ext-api/routes.ts e secretaria/tools.ts também trazem trabalho acumulado da integração do satélite.

🤖 Generated with Claude Code


Atualização (06/jul) — inteligência da agenda + segurança

  • Roteamento por especialidade + descobrir o motivo (prioriza clínico geral só p/ avaliação/limpeza/dúvida).
  • Situações da clínica (regras editáveis) injetadas no prompt.
  • Dúvida → humano (encaminhar_humano): não agenda, pede p/ aguardar.
  • Imagem/áudio: ext:message.transcription religa no fluxo ext:message.new (guard hadCaption anti-duplicação).
  • Checklist do dono × banco → cautelas: não assume clínico geral nem identidade pelo telefone; telefone compartilhado (mãe×filho) e profissional não cadastrado → encaminha ao humano.
  • Desligar GLOBAL da Secretária: GET/POST /secretaria/power (kill-switch auto_reply, efeito imediato, sem restart).
  • Fix: não injetar sec_calendar (agenda interna/de teste) no prompt quando há agenda real.

Commits novos: eca65bf, 9b4280c.

Separa a Secretária/nós/agenda por número de WhatsApp via `sec_numbers.agent_id` (runtime resolve o agente pelo número que recebeu a mensagem; fallback = 1º agente ativo). Migração aditiva (`agent_id` em sec_numbers, `instance_id` em sec_calendar) + rotas admin. Testado em dev (newwhats.dev refrescado): agentes resolvidos por instância, fallback e FK ON DELETE SET NULL ok. Obs.: por partilharem o working tree do newwhats.local, `ext-api/routes.ts` e `secretaria/tools.ts` também trazem trabalho acumulado da integração do satélite. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- ### Atualização (06/jul) — inteligência da agenda + segurança - **Roteamento por especialidade** + descobrir o motivo (prioriza clínico geral só p/ avaliação/limpeza/dúvida). - **Situações da clínica** (regras editáveis) injetadas no prompt. - **Dúvida → humano** (`encaminhar_humano`): não agenda, pede p/ aguardar. - **Imagem/áudio**: `ext:message.transcription` religa no fluxo `ext:message.new` (guard `hadCaption` anti-duplicação). - **Checklist do dono × banco → cautelas**: não assume clínico geral nem identidade pelo telefone; telefone compartilhado (mãe×filho) e profissional não cadastrado → encaminha ao humano. - **Desligar GLOBAL** da Secretária: `GET/POST /secretaria/power` (kill-switch `auto_reply`, efeito imediato, sem restart). - **Fix**: não injetar `sec_calendar` (agenda interna/de teste) no prompt quando há agenda real. Commits novos: `eca65bf`, `9b4280c`.
ruicesar added 1 commit 2026-07-05 19:58:05 +02:00
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>
ruicesar added 1 commit 2026-07-05 23:59:03 +02:00
O `update` do `chat.upsert` no MessageHandler passava `contactId: contact.id`
escalar, que o Prisma rejeita ("Unknown argument `contactId`. Did you mean
`contact`?") → PrismaClientValidationError. Isso derrubava o processMessage
ANTES de emitir o hook ext:message.new, quebrando o tempo real (inclusive o
auto-trigger da Secretária IA) para qualquer contato cujo chat precisasse
revincular o contato. Corrigido nos dois upserts (notify + histórico) para a
sintaxe de relação `contact: { connect: { id: contact.id } }`.

Verificado em dev: a Secretária respondeu ponta-a-ponta após o fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ruicesar added 1 commit 2026-07-06 00:19:39 +02:00
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>
ruicesar added 1 commit 2026-07-06 01:24:25 +02:00
brain.ts busca GET /status da agenda-bridge (aberto hoje?, próximo dia aberto,
grade da semana, feriados) e injeta no system prompt via formatClinicStatus,
junto da data já existente. Regra: a Secretária só oferece horário/agenda quando
a clínica está ABERTA — em dia fechado/feriado, informa o horário e oferece o
próximo dia disponível. (Par do satélite: scoreodonto #2.)

Testado em dev: ao pedirem "hoje" num domingo, ofereceu "amanhã" nos horários
configurados.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ruicesar added 1 commit 2026-07-06 02:13:39 +02:00
listar_horarios inclui `local` (nome da sala alugada ou "Clínica") e codifica o
sala_id no slot_id; agendar_horario repassa sala_id ao /book para reservar no
quarto certo. Par do satélite (scoreodonto): folga do dentista (A) + salas (B).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ruicesar added 1 commit 2026-07-06 05:29:50 +02:00
Quando o cliente pede um horário PONTUAL fora dos disponíveis (mais cedo/mais
tarde), a IA usa solicitar_horario_especial → registra um pedido na agenda do
satélite (POST /pedido) e responde "vou confirmar com a secretária e retorno"
(sem garantir o horário). brain.ts injeta essa regra no prompt. Par do satélite
(scoreodonto): janela flex + fila da secretária humana.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ruicesar added 1 commit 2026-07-06 05:34:13 +02:00
Ao registrar um pedido de horário fora do padrão, a IA passa a responder no tom:
"No momento esse horário não está disponível, mas ainda não confirmei a agenda —
posso te confirmar daqui a pouco?" (antes: "vou verificar com a secretária").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ruicesar added 1 commit 2026-07-06 05:59:40 +02:00
Regras injetadas no prompt: NUNCA listar vários horários — primeiro perguntar
"manhã ou tarde?", depois oferecer UM único horário (o primeiro livre do período,
via listar_horarios com periodo), um de cada vez, mantendo 1h entre atendimentos.
listar_horarios ganhou o parâmetro periodo (manha|tarde).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ruicesar added 1 commit 2026-07-06 06:15:32 +02:00
Quando o cliente insiste num horário mais tarde, a IA usa confirmar_depois:
diz "vou confirmar, aguarde uns minutinhos" e registra um follow-up
(sec_agenda_followups, fire_at = now + followup_delay_min, default 15). Um worker
no ext-api (a cada 30s) processa os vencidos: consulta /slots perto do horário
desejado e manda ao paciente "Oie, consegui! Posso às X?" (assinado pelo agente).

Testado em dev: worker processa follow-up vencido, consulta a agenda e envia.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ruicesar added 1 commit 2026-07-06 06:31:44 +02:00
Encaixe que quebra o espaçamento de 1h (ex.: 09:45): a IA usa perguntar_dentista
→ resolve o WhatsApp do dentista (bridge /dentista-contato), registra
sec_agenda_approvals (aguardando) e manda a pergunta ao dentista ("consegue
encaixar às X? SIM/NÃO", via hook ext:agenda.notify-dentist). Quando o dentista
responde, ext:message.new detecta (roda mesmo com auto_reply off), interpreta
sim/não; se SIM agenda (/book) e avisa o paciente automaticamente ("o Dr
confirmou, agendei às X"); se NÃO oferece outro horário.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ruicesar added 2 commits 2026-07-06 16:03:32 +02:00
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>
O nó `calendar` despejava o sec_calendar (agenda interna/de teste do motor) no
prompt, criando uma "agenda-fantasma" que conflitava com a agenda real do
satélite e podia fazer a IA oferecer horários inexistentes. Agora, quando há
ponte configurada (agenda_url/clinica_id), getCalendarContext instrui a usar a
ferramenta listar_horarios em vez de listar o sec_calendar. Sem satélite, o
fallback interno continua igual.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ruicesar merged commit cdc27b8a68 into main 2026-07-06 16:07:06 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ruicesar/clube67_newwhats.local#20