Files
DeskcommCRM/lib/agent-engine
Rafael Melgaço b71cea9626 feat(pacing): a janela de RESPOSTA sai da janela de DISPARO (0495)
Um cliente escreveu as 22h e ficou sem resposta ate as 7h da manha. O log
diz por que:

    turno adiado - fora da janela anti-ban de envio
    janela: 7h-22h  abertura: 2026-09-30T10:00:00Z

A janela era UM par de horas (`channel_knobs.window_start_hour`/
`window_end_hour`, da 0010) e ela atendia a tres coisas: a resposta do
agente, o disparo em massa e a cutucar de conversa parada. Abrir o
atendimento para 24h abria o disparo junto - e `followup_turn`, que e a
cutucar, e o tipo de envio que mais incomoda quando sai de hora: "e ai,
tudo certo?" as 4h para quem dormiu.

Agora sao duas janelas, por numero (migration 0495,
`channel_knobs.reengajar_start_hour`/`reengajar_end_hour`):

    RESPOSTA a quem escreveu .... 0h-24h
    DISPARO em massa ............ 7h-22h  (inalterado)
    Cutucar conversa parada .... 7h-22h  (inalterado)

O que separa as duas e o TIPO do envio. `inbound_turn` e `case_reply_turn`
sao resposta e leem `reengajar*`; `followup_turn` continua na janela
comercial. O `PacingInput.resposta` default `false` (disparo) mantem todo
chamador anterior a 0495 no comportamento velho, e e a direcao que fecha o
numero: quem esquece o campo nao abre nada.

## O anti-ban continua inteiro

Abrir o horario nao abre o limite. Cap diario, degraus de warm-up por idade
do numero e throttle rodam para os dois lados, e o veto por cap as 3h
continua sendo "cap" e nao "fora da janela". Um numero novo segue com 20
mensagens por dia ate completar o aquecimento.

`nextDayOpen` fica na janela de DISPARO de proposito: cap diario e
protecao anti-ban, e o "amanha" que se anuncia a quem pagou as 3h e o
numero de novo as 7h.

## Migration

Colunas soltas, nao jsonb: `window_*_hour` e coluna desde a 0010 e a ficha
Anti-ban ja os edita. Default NULL = a RESPOSTA herda a janela de DISPARO,
que e o comportamento de antes - um clone que aplicou a 0495 sem gravar as
colunas nao muda de operacao por omissao. `drop ... if exists` antes do
`add constraint` nos dois arquivos porque o `update.sh` reaplica o
apendice inteiro e `add constraint` sem guarda quebra com 'already exists'
no segundo clone que atualizar.

Par pela metade e tratado como ausente: misturar `reengajar_start_hour=0`
com `window_end_hour=22` daria `0h-22h`, que abre a madrugada pelo caminho
que parece conservador. O editor tambem recusa `start >= end` no par
resultante, senão o motor traduz em "nunca responde" e o operador so
descobre quando o cliente para de receber resposta.

## Numeracao

A 0381 ja era da captura de UTM (`20260921080000`) e o MANIFEST ia ate
0494 na 1.65.0 - por isso 0495, e com a linha do MANIFEST, que e o que o
gate `manifest-x-migrations` exige.

## Verificacao

Medido nesta VPS, com o numero real: "Boa noite" recebido 23:25, resposta
"Boa noite! Tou por aqui. O que tu precisa?" 00:02; "Quero uber" recebido
22:27, "Boa, mano! Conta nova Uber fica R$ 380." 00:03 - com o preco vindo
do RAG, sem passar pelo prompt.

- `lib/agent-engine/pacing/janela-de-resposta.test.ts` - 12 casos: as 3h a
  resposta passa e o disparo nao; `resposta` omitido barra; par incompleto
  nao abre; cap diario continua barrando; domingo continua calando a
  resposta; o veto da resposta atrasa para a abertura DA resposta, nao 7h
- 10/10 nos gates de migration/baseline contra a 1.65.0, incluindo
  `baseline-reaplicavel`, que reprovou a primeira versao deste apendice
  por `add constraint` sem guarda

`tests/unit/janela-24h-recusa-quem-envia-por-token` e
`lib/ai/dispatcher/rate-limit` falham tambem no `main` limpo - provado
rodando contra `git show fork/main:` sem nenhuma mudanca deste PR.
2026-09-30 04:07:02 +00:00
..