@lionchat/mcp-server 1.9.0 → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -227,9 +227,15 @@ POST /api/v1/accounts/{account_id}/kanban_items
227
227
  ### Relatório de produtividade
228
228
  ```
229
229
  1. lionchat_reports_summary (visão geral)
230
- 2. lionchat_reports_list (por agente — passa since/until ISO 8601)
230
+ 2. lionchat_reports_list (por agente — passa since/until em Unix EM SEGUNDOS)
231
231
  3. Comparar avg_first_response_time entre agentes
232
232
  ```
233
+ **`since`/`until` dos relatórios só aceitam número Unix em SEGUNDOS** (`1782864000` = 01/07/2026 00:00
234
+ UTC), nunca ISO 8601 nem data por extenso — o formato ISO do resto da plataforma (seção 3) NÃO vale
235
+ aqui. Desde 29/07/2026 a API recusa com HTTP 400 (`invalid_date_params`); antes disso o relatório
236
+ voltava ZERADO com HTTP 200 (a conta 19 tinha 4.396 conversas no período e a resposta vinha zero).
237
+ Converta a data do usuário para segundos antes de chamar. Os exports em CSV (agents, inboxes, labels,
238
+ teams, conversations_summary) exigem as DUAS pontas; nos demais, `since` ou `until` sozinho é válido.
233
239
 
234
240
  ### Criar campanha de WhatsApp (disparo em massa)
235
241
  ```
@@ -292,11 +298,15 @@ e pular evita que ela receba tudo em dobro. Quem está sendo atendido pela equip
292
298
  | Limite | Valor |
293
299
  |---|---|
294
300
  | `per_page` max | 100 |
295
- | Bulk actions (IDs) max | 100 por chamada |
301
+ | Bulk actions (IDs) max | 300 por chamada (desde 18/05/2026; passou disso = HTTP 422 `bulk_ids_limit_exceeded`) |
296
302
  | Rate limit leitura | 1200/min por token |
297
303
  | Rate limit escrita | 600/min por token |
298
304
  | Loop detection | bloqueia chamada idêntica >10x em 10s |
299
305
 
306
+ Exceção ao teto de 300: em ação em massa de **contatos** com `select_all: true` (agir sobre todos os
307
+ contatos do filtro) não há teto de IDs — o servidor refaz o filtro e processa em lotes no fundo. O
308
+ teto de 300 vale para o caminho normal, com a lista de IDs. Kanban usa o mesmo teto no `item_ids`.
309
+
300
310
  ## 7. Confirmar antes de agir — regra de ouro
301
311
 
302
312
  Antes de QUALQUER ação que escreve, modifica, apaga ou envia, pare e avalie se o pedido está claro e completo. Na dúvida, pergunte — **nunca chute**.
@@ -349,17 +349,27 @@ Quando rate-limited:
349
349
 
350
350
  ### Bulk actions
351
351
 
352
- `POST /bulk_actions` aceita arrays de IDs. **Máximo 100 IDs por chamada**.
352
+ `POST /bulk_actions` aceita arrays de IDs. **Máximo 300 IDs por chamada** (`MAX_BULK_IDS`, desde
353
+ 2026-05-15). Acima disso o servidor recusa com `422` + `code: "bulk_ids_limit_exceeded"` — quebre em
354
+ chamadas de 300.
355
+
356
+ **Exceção:** contato com `select_all: true` (agir sobre TODOS os contatos do filtro) **não tem teto** —
357
+ o servidor resolve pelo próprio filtro (`query` / `labels_filter`), em lotes no fundo, e não usa `ids`.
353
358
 
354
359
  ```json
355
360
  {
356
361
  "type": "Conversation",
357
- "ids": [1, 2, 3, ...], // max 100
358
- "action_name": "assign_agent",
359
- "action_attributes": { "assignee_id": 6 }
362
+ "ids": [1, 2, 3, ...], // max 300
363
+ "fields": { "assignee_id": 6 } // conversa: status, assignee_id, team_id, inbox_id, captain_assistant_id
360
364
  }
361
365
  ```
362
366
 
367
+ **⚠️ `action_attributes` NÃO EXISTE.** O servidor não lê esse campo em lugar nenhum (só aparece em
368
+ comentário de TODO no código). Mandar a mudança por ali devolve **200 OK e não faz absolutamente nada** —
369
+ falha silenciosa, sem erro na tela. Conversa se altera por `fields` (+ `labels: {add:[], remove:[]}` e
370
+ `snoozed_until`). Em conversa o `action_name` é **ignorado**; ele só tem efeito em **contato**, onde
371
+ `"delete"` apaga os contatos (sem ele, o que vale é `labels: {add: []}`).
372
+
363
373
  ### Search (overhaul 2026-06-10)
364
374
 
365
375
  `GET /search?q=texto` — busca cross-entidade (conversations, contacts, messages, articles, kanban_items)
@@ -77,7 +77,14 @@ Conversa com 200 mensagens? **Não baixe tudo**:
77
77
  ## Operações em massa
78
78
 
79
79
  Pra atribuir labels, mover cards, etc em vários itens:
80
- - `kanban_bulk_bulk_actions` (cards Kanban)
80
+
81
+ - `kanban_bulk_bulk_actions` — **NÃO é de cards do Kanban**, apesar do nome. Ela bate em
82
+ `POST /bulk_actions` e age em **conversas ou contatos** (o `type` é `Conversation` ou `Contact`).
83
+ Usar ela achando que move card não dá erro visível — só não faz o que você queria.
84
+ - Cards do Kanban têm **três** ferramentas próprias, uma por ação:
85
+ - `kanban_bulk_create` → `bulk_move_items` (mover cards de etapa)
86
+ - `kanban_bulk_create_1` → `bulk_assign_agent` (definir o responsável)
87
+ - `kanban_bulk_create_2` → `bulk_set_priority` (definir a prioridade)
81
88
  - `kanban_items_kanban_agents_create` em massa (passar array)
82
89
  - `automation_rules_*` (criar regras em vez de fazer manualmente)
83
90
 
@@ -127,7 +134,32 @@ O `config` DEVE ser enviado como **objeto** (ex.: `config: { disabled_tools: [..
127
134
  ## Limite do prompt e cache OpenAI (2026-05-22)
128
135
 
129
136
  - `config.instructions` (system prompt do agente) aceita até **20.000 caracteres** (antes 10k). Acima de 15k, o frontend mostra aviso "lost in the middle" — prefira colocar instruções críticas no início ou final.
130
- - **Cache automático** ativo em todos os 15 modelos suportados (GPT-4.1 Nano até GPT-5.2 Pro, o1, o3, o4-mini). Desconto 50-75% no input cachado, sem configuração. Reuso do prompt do agente em múltiplas conversas maximiza a economia.
137
+ - **Cache automático** ativo em todos os 16 modelos oferecidos na plataforma. Desconto 50-75% no input cachado, sem configuração. Reuso do prompt do agente em múltiplas conversas maximiza a economia.
138
+
139
+ ### Lista real de modelos (`config.model`)
140
+
141
+ Estes são os 16 valores que o seletor de modelo do painel oferece (`ModelSelector.vue`). Só recomende
142
+ valores desta lista:
143
+
144
+ | Faixa | Modelos |
145
+ |---|---|
146
+ | Econômicos / rápidos | `gpt-4.1-nano`, `gpt-4o-mini`, `gpt-4.1-mini`, `gpt-5.4-nano`, `gpt-5.4-mini` |
147
+ | Intermediários | `gpt-4o`, `gpt-4.1`, `gpt-5-mini`, `gpt-5.4` |
148
+ | Raciocínio | `o3-mini`, `o4-mini` |
149
+ | Premium | `gpt-5`, `gpt-5.2`, `gpt-5.5`, `o1`, `o3` |
150
+
151
+ **`GPT-5.2 Pro` não existe.** O valor válido é `gpt-5.2`, sem "Pro".
152
+
153
+ > **PERIGO — família `gpt-5.6` (`gpt-5.6-luna`, `gpt-5.6-terra`, `gpt-5.6-sol`): REMOVIDA em 29/07/2026.**
154
+ > Esses modelos **recusam function tools** em `/v1/chat/completions` (erro 400 real: "Function tools with
155
+ > reasoning_effort are not supported"). Como o AI Agente usa ferramentas em **toda** resposta, 100% das
156
+ > chamadas falhavam e a IA ficava **MUDA** — sem erro na tela, sem resposta. Ficou 2 semanas invisível.
157
+ > **Nunca sugira, nunca grave** um `gpt-5.6` — nem se o cliente pedir "o mais novo/mais forte".
158
+ >
159
+ > O campo `config.model` **não tem lista branca no servidor**: ele é `store_accessor` do `config` e
160
+ > aceita qualquer texto sem validar. Modelo inválido salva com 200 e só quebra na hora de responder.
161
+ > Desde 29/07 o erro vira uma **nota privada em português** na conversa (`llm_request_rejected`) — é o
162
+ > primeiro lugar a olhar quando "a IA parou de responder logo depois de mexerem nas configurações".
131
163
 
132
164
  ## Follow-up automático multi-etapa (2026-06)
133
165
 
@@ -137,21 +169,61 @@ O follow-up do AI Agente agora suporta **cadência de até 3 etapas** via `confi
137
169
  { "config": { "feature_follow_up": true, "follow_up_steps": [
138
170
  { "after_minutes": 30, "prompt": "Pergunte gentilmente se ainda tem interesse" },
139
171
  { "after_minutes": 240, "prompt": "Ofereça tirar dúvidas" },
140
- { "after_minutes": 1140, "prompt": "Última tentativa, despeça-se cordialmente" }
172
+ { "after_minutes": 1110, "prompt": "Última tentativa, despeça-se cordialmente" }
141
173
  ] } }
142
174
  ```
143
175
 
144
- Regras: cada etapa ≥ 5 min; soma total ≤ 1440 min (24h); máx 3 etapas. Campos legados
145
- `follow_up_time` + `follow_up_prompt` continuam funcionando como 1 etapa única.
176
+ Regras: cada etapa ≥ 5 min; **soma total ≤ 1380 min (23h)**; máx 3 etapas. Campos legados
177
+ `follow_up_time` + `follow_up_prompt` continuam funcionando como 1 etapa única (e obedecem ao **mesmo
178
+ teto de 1380**).
179
+
180
+ > **O teto é 1380, não 1440** (`Captain::Assistant::FOLLOW_UP_MAX_TOTAL_MINUTES`). Motivo: a janela de
181
+ > 24h do WhatsApp conta a partir da **mensagem do cliente**, não do primeiro acompanhamento — 1h de
182
+ > folga evita agendar uma cobrança que a Meta recusaria na hora de enviar. Montar a cadência somando
183
+ > 1440 faz o salvamento ser **recusado**. Pegadinha: a mensagem de erro do campo legado `follow_up_time`
184
+ > ainda diz "must be between 5 and 1440 minutes", mas a validação usa 1380 — não confie no texto do erro.
185
+
186
+ (O exemplo acima soma exatamente 1380: 30 + 240 + 1110.)
187
+
146
188
  Dispara quando o CLIENTE fica inativo após resposta da IA. O motor de follow-up é dedicado e
147
189
  **só-leitura** (não executa ferramentas, não coleta dado — só redige a mensagem de retomada,
148
190
  com saída estruturada garantida). `paused=true` no assistente corta os follow-ups também.
149
191
 
150
- **Corrigido em 2026-07-24:** quando a IA é ativada por um **Flow** (ação "Ativar IA") ou por uma
151
- **automação**, o follow-up agora é agendado no ato da ativação. Antes, nesses dois caminhos a
152
- cadência simplesmente nunca começava — a conversa ficava com IA ativa e sem nenhum follow-up. Se um
153
- cliente relatar "a IA nunca faz o follow-up", confira primeiro se a ativação veio por flow/automação
154
- e se a conta já está na versão de 24/07.
192
+ ### "A IA nunca faz o follow-up" — o conserto é de 29/07, NÃO de 24/07
193
+
194
+ **Não repita que isso foi corrigido em 24/07.** A versão de 24/07 agendava o follow-up **dentro** do
195
+ bloco do "Responder na hora": ou seja, ela só funcionava quando esse interruptor estava **LIGADO** —
196
+ justamente o caso que menos precisa dela. Com "Responder na hora" **desligado**, a IA não fala, o único
197
+ outro gatilho (o hook de mensagem enviada) também não dispara, e a conversa fica **com IA ativa e sem
198
+ nenhum acompanhamento, para sempre**. Aconteceu em **140 conversas da conta 52** — todas criadas
199
+ DEPOIS do suposto conserto de 24/07. A ativação **manual pela tela** nunca esteve coberta em nenhuma
200
+ das duas versões até 29/07.
201
+
202
+ **"Responder na hora" e acompanhamento são INDEPENDENTES.** São perguntas diferentes:
203
+ - *Responder na hora* (`captain_reply_now` / `proactive`) = "a IA fala agora, sem esperar o cliente?"
204
+ - *Acompanhamento* (`feature_follow_up`) = "a IA cobra depois, se ninguém responder?"
205
+
206
+ Desligar o primeiro **não** deve desligar o segundo. Nunca oriente o cliente a ligar "Responder na
207
+ hora" para o acompanhamento funcionar.
208
+
209
+ **Corrigido de verdade em 2026-07-29**, nos três caminhos que ativam a IA, com o agendamento **fora**
210
+ do bloco do "Responder na hora":
211
+ - Flow, ação "Ativar IA" (`FlowBuilder::ActionDelegator`) — só em ativação nova (a conversa não tinha IA)
212
+ - Automação, ação "Atribuir assistente" (`AutomationRules::ActionService`)
213
+ - Ativação manual pela tela da conversa (`ConversationsController`) — armado **antes** da checagem do
214
+ "Responder na hora"
215
+
216
+ **Ainda descoberto (não corrigido):** ativar a IA em **lote** (ação em massa de conversas com
217
+ `captain_assistant_id`) **não arma o acompanhamento nem dispara resposta imediata**. Se o cliente
218
+ ativou a IA em massa e reclama que ela não cobra de volta, é isso — e o caminho é reativar pela tela
219
+ ou por automação.
220
+
221
+ Diagnóstico, nesta ordem, quando relatarem "a IA nunca faz o follow-up":
222
+ 1. `config.feature_follow_up` está ligado no assistente? E `paused` está desligado? (`paused=true`
223
+ corta os follow-ups)
224
+ 2. A ativação veio por **lote**? Então nunca foi armado.
225
+ 3. Tem `follow_up_skip_conditions` batendo (etiqueta, atributo, janela de silêncio)?
226
+ 4. A soma das etapas passa de 1380 min? Nesse caso o assistente nem salvou a cadência.
155
227
 
156
228
  ## Condições para NÃO fazer follow-up + horário de silêncio (2026-07)
157
229
 
@@ -98,17 +98,65 @@ Avaliação:
98
98
 
99
99
  ### 4. Auto-assignment
100
100
 
101
- Roda após automation rules. Lógica simplificada:
101
+ Roda após automation rules. Existem **DOIS caminhos vivos** (`Inbox#auto_assignment_v2_enabled?`),
102
+ não um moderno e um legado:
102
103
 
103
104
  ```
104
- SE inbox.enable_auto_assignment_v2:
105
- SE inbox.assignment_policy associada:
106
- → Política decide (round-robin, balanced, etc)
107
- SENAO:
108
- → assignee_id fica null (manual)
105
+ SE inbox.assignment_policy vinculada:
106
+ → modo POLÍTICA: round-robin ou balanced, prioridade da fila, capacidade
107
+ SENÃO SE inbox.enable_auto_assignment (botão "Distribuição automática" da própria caixa):
108
+ → modo SIMPLES: round-robin entre os membros da caixa
109
+ SENÃO:
110
+ → assignee_id fica null (atribuição manual)
109
111
  ```
110
112
 
111
- V2 (Assignment Policy) é o motor moderno. V1 (campo `auto_assignment` simples) é legado.
113
+ Os dois caminhos rodam no MESMO serviço (`AutoAssignment::AssignmentService`), usam o MESMO rodízio
114
+ (`AutoAssignment::RoundRobinSelector` — só a política pode trocá-lo pelo modo `balanced`) e passam
115
+ pelo MESMO freio de rajada
116
+ (`AutoAssignment::RateLimiter`). O que muda é de onde vem a configuração: com política vinculada, ela
117
+ manda em tudo (inclusive em "distribuir para agentes offline"); sem política, vale o que está no
118
+ `auto_assignment_config` da própria caixa.
119
+
120
+ **NUNCA escreva que o modo simples é legado, nem recomende criar Política de Atribuição para quem só
121
+ quer o botão da caixa.** Vincular uma política **esconde a seção inteira de Distribuição Automática da
122
+ tela da caixa** (`CollaboratorsPage.vue`: `showAutoDistribution = !inbox.has_assignment_policy`). O
123
+ cliente perde os controles que estava usando, não entende por que sumiram, e o que ele configurou lá
124
+ passa a ser ignorado (o valor fica gravado e só volta a valer se a política for desvinculada).
125
+
126
+ Regra prática:
127
+ - Cliente quer só "dividir os leads entre a equipe" → **botão da própria caixa**. Não crie política.
128
+ - Cliente quer regra compartilhada entre várias caixas, modo balanced, prioridade de fila ou limite de
129
+ capacidade por atendente → aí sim Política de Atribuição.
130
+
131
+ Quem entra no rodízio: só `inbox_members` com `auto_assignable: true` (supervisor de caixa fica de
132
+ fora) e, se "distribuir para agentes offline" estiver desligado, só quem está online.
133
+
134
+ **LEIA o valor da conta antes de responder — NUNCA recite um número de cabeça.** Os valores reais
135
+ vêm na resposta da API:
136
+ - `has_assignment_policy` (resposta da caixa) diz QUAL dos dois caminhos vale ali. `true` = política
137
+ manda; `false` = vale o botão da própria caixa.
138
+ - Com política vinculada, leia `fair_distribution_limit` (quantas conversas por atendente),
139
+ `fair_distribution_window` (tamanho da janela, **em segundos**) e `assign_offline_agents` na
140
+ resposta da política. O `auto_assignment_config` da caixa é **IGNORADO** nesse caso — não o cite.
141
+ - Sem política, leia as MESMAS chaves dentro do bloco `auto_assignment_config` da caixa
142
+ (`fair_distribution_limit`, `fair_distribution_window`, `assign_offline_agents`).
143
+ - Chave ausente/vazia = vale o padrão de fábrica (hoje 10 conversas a cada 600 segundos) — cite isso
144
+ só como "valor de fábrica, confira o da conta", nunca como se fosse a configuração do cliente.
145
+
146
+ **EXCEÇÃO SEM FREIO (não dá pra descobrir lendo a resposta):** caixa **sem** política vinculada, com
147
+ `assign_offline_agents` LIGADO e **sem número escolhido** em `fair_distribution_limit`, roda **SEM
148
+ FREIO NENHUM** (`AutoAssignment::RateLimiter#resolve_limit`: `return 0 if inbox.assign_offline_agents?`).
149
+ Racional (decisão do dono 29/07): com offline ligado todo membro está sempre na roleta, então a divisão
150
+ já é igual por construção e o freio só atrasaria a entrega. Não existe campo dizendo "sem freio" — o
151
+ bloco vazio parece "padrão", mas não é. Efeito prático: **nessas caixas, dizer ao cliente que existe um
152
+ teto por atendente é MENTIRA**. Número escolhido pelo cliente sempre vence o toggle: se
153
+ `fair_distribution_limit` tem um número MAIOR QUE ZERO, o freio existe mesmo com offline ligado.
154
+
155
+ **Zero também é "sem freio":** na caixa (não na política), `fair_distribution_limit: 0` é a saída
156
+ explícita de quem não quer teto nenhum — o freio fica desligado (`RateLimiter#enabled?` exige limite
157
+ positivo). Ou seja, existem DUAS formas de a caixa ficar sem freio: campo vazio com offline ligado, ou
158
+ zero escrito de propósito. Na Política de Atribuição o zero nem é aceito (o cadastro exige maior que
159
+ zero), então política vinculada SEMPRE tem freio.
112
160
 
113
161
  ### 5. Captain (IA Agente)
114
162
 
@@ -138,7 +186,10 @@ handoff consultor→gestor). Pendente só acontece por ação explícita:
138
186
  - Ferramenta nativa da IA (deixar conversa pendente)
139
187
 
140
188
  Comportamentos importantes:
141
- - Mensagem nova do cliente NÃO reabre conversa pendente (segue pendente na lista)
189
+ - Mensagem nova do CLIENTE REABRE conversa pendente (volta pra Aberta, com pílula de aviso na
190
+ conversa). Só a do cliente: resposta do ATENDENTE não reabre. Vale desde 30/07/2026 e REVOGA a
191
+ regra anterior de 20/07 ("pendente não reabre") — decisão do dono depois de a conta 19 acumular
192
+ 258 clientes esperando sem o time ver. NUNCA responda que a conversa fica parada em Pendente
142
193
  - Pendente ATRIBUÍDA notifica o responsável (o gestor recebe o aviso)
143
194
 
144
195
  ### 7. Resolução (status 1)
@@ -186,10 +237,20 @@ Conversation
186
237
 
187
238
  ### "Conversa não atribuiu ninguém"
188
239
 
189
- 1. `inbox.enable_auto_assignment_v2` está true?
190
- 2. `inbox.assignment_policy` está setado?
191
- 3. Tem `InboxMember` ativos pra essa inbox?
192
- 4. Tem `automation_rule` que poderia ter atribuído antes?
240
+ 1. A distribuição está ligada em ALGUM dos dois caminhos? `inbox.assignment_policy` vinculada **ou**
241
+ `inbox.enable_auto_assignment` true. Não existe campo `enable_auto_assignment_v2` — não peça por ele.
242
+ Política vinculada **não é requisito**: caixa sem política e com o botão ligado distribui normalmente.
243
+ 2. Se há política: ela está com `enabled: true`? Política desligada = ninguém é atribuído.
244
+ 3. Tem `InboxMember` com `auto_assignable: true`? Supervisor de caixa é membro mas **não entra** no rodízio.
245
+ 4. Se "distribuir para agentes offline" está desligado, tem alguém **online** agora? Sem ninguém online
246
+ a fila fica parada até alguém logar.
247
+ 5. O atendente pode estar no **teto da janela** do freio de rajada (o robô só entrega N conversas por
248
+ atendente/caixa dentro de uma janela deslizante). A vaga volta quando a conversa é resolvida, adiada
249
+ ou vira pendente — e a fila anda sozinha em até 1 minuto. Antes de culpar o freio, confira os
250
+ valores reais da conta e a exceção SEM FREIO (seção 4) — pode não haver teto nenhum ali.
251
+ 6. Se a caixa tem **uma única** conversa esperando, o freio **nem se aplica** (`should_apply_rate_limit?`
252
+ exige mais de 1 não atribuída). Nesse caso a causa é outra — não culpe o freio.
253
+ 7. Tem `automation_rule` que poderia ter atribuído antes?
193
254
 
194
255
  ### "IA não respondeu"
195
256
 
@@ -359,6 +420,154 @@ POST /api/v1/accounts/{account_id}/automation_rules
359
420
  "event_name": "conversation_updated",
360
421
  "action_types": ["label_added"],
361
422
  "conditions": [{"attribute_key": "labels", "filter_operator": "contains", "values": ["vip"]}],
362
- "actions": [{"action_name": "send_email_to_team", "action_params": {"team_ids": [1], "message": "VIP!"}}]
423
+ "actions": [{"action_name": "send_email_to_team", "action_params": [{"team_ids": [1], "message": "VIP!"}]}]
363
424
  }
364
425
  ```
426
+
427
+ ---
428
+
429
+ ## Condicoes de automacao — e a REGRA DO CONECTOR
430
+
431
+ ```json
432
+ "conditions": [
433
+ {"attribute_key": "status", "filter_operator": "equal_to", "values": ["resolved"], "query_operator": "AND"},
434
+ {"attribute_key": "labels", "filter_operator": "equal_to", "values": ["vip"], "query_operator": null}
435
+ ]
436
+ ```
437
+
438
+ ### O conector (`query_operator`) — leia isto antes de gravar
439
+
440
+ O conector **liga a condicao com a SEGUINTE**. Logo:
441
+
442
+ - A **ULTIMA** condicao **TEM** que ter `query_operator: null`.
443
+ - Condicao **UNICA** = `query_operator: null` (nao ha proxima pra ligar).
444
+ - Valores aceitos nas demais: `"AND"` ou `"OR"`.
445
+
446
+ > **A API valida o VALOR do conector, mas NAO a POSICAO.** Conector na ultima condicao **salva com
447
+ > sucesso**, aparece certo na tela — e a regra nunca dispara.
448
+ >
449
+ > Incidente real: **8 regras criadas pelo conector de IA entre 26 e 30/07/2026, em 3 contas
450
+ > diferentes, todas com conector na ultima condicao. Todas nasceram mortas.** A pergunta ao banco
451
+ > saia pela metade (`... IN ($1) and`), o erro era engolido e a regra respondia "nada casou". A conta
452
+ > 62 ficou com as **6** regras de etiquetagem automatica paradas. Medido em producao em 03/08:
453
+ > **732 erros por hora**.
454
+ >
455
+ > O motor foi corrigido em 03/08 (a limpeza acontece ao montar a consulta), entao regra torta nao
456
+ > quebra mais. **Mas continue gravando certo:** a tela de edicao le o dado cru e formato errado
457
+ > aparece estranho ali — mesmo efeito do campo de template que sumia da tela.
458
+
459
+ ### Campos da condicao
460
+
461
+ | Campo | Obrigatorio | O que e |
462
+ |---|---|---|
463
+ | `attribute_key` | SIM | Campo padrao (ver abaixo) ou a chave de um atributo personalizado que **exista na conta** |
464
+ | `filter_operator` | SIM | Como comparar |
465
+ | `values` | SIM (menos em `is_present`/`is_not_present`) | Array, sempre |
466
+ | `query_operator` | SIM em todas | `"AND"`/`"OR"`; **`null` na ultima** |
467
+ | `custom_attribute_type` | so p/ atributo personalizado | `conversation_attribute` ou `contact_attribute` |
468
+
469
+ ### Operadores de filtro
470
+
471
+ `equal_to`, `not_equal_to`, `contains`, `does_not_contain`, `is_present`, `is_not_present`,
472
+ `is_greater_than`, `is_less_than`, `starts_with`, `days_before`, `days_ago`, `last_days`, `today`,
473
+ `yesterday`.
474
+
475
+ **Cada campo aceita so um subconjunto.** Operador que o campo nao aceita faz a regra inteira falhar
476
+ na validacao. Na duvida, leia uma regra que ja funciona com `lionchat_automation_rules_show` e copie.
477
+
478
+ ### Chave que a conta nao conhece
479
+
480
+ Se `attribute_key` nao for um campo padrao **nem** um atributo personalizado existente na conta, a
481
+ regra **inteira** falha na validacao e nunca dispara — erro
482
+ `Automation conditions <chave> not supported`. Aconteceu de verdade na conta 62 (`tipo_interesse`).
483
+ Antes de usar atributo personalizado numa condicao, confirme que ele existe com
484
+ `lionchat_custom_attributes_list`.
485
+
486
+ ---
487
+
488
+ ## Acoes de automacao — formato do `action_params`
489
+
490
+ > **REGRA DE OURO:** `action_params` e SEMPRE um **array**, mesmo quando so tem um item.
491
+ > O backend le `params[0]`. Passar um objeto solto (`{...}` em vez de `[{...}]`) quebra a acao.
492
+
493
+ > **PERIGO — falha silenciosa:** o `action_params` e um campo livre (JSONB). A API **aceita e grava
494
+ > qualquer formato**, sem validar. Se as chaves estiverem erradas, a regra e salva com sucesso, o
495
+ > painel mostra a acao na tela, e na hora de disparar ela **nao faz nada e nao registra erro nenhum**
496
+ > — sem log, sem mensagem falhada, sem aviso. Por isso: **nunca invente nome de chave**. Se a acao
497
+ > nao estiver documentada abaixo, leia uma regra existente com `lionchat_automation_rules_show` e
498
+ > copie o formato exato dela.
499
+ >
500
+ > Incidente real (conta 39, 02-03/08/2026): uma IA gravou `template_name`/`template_id` em vez de
501
+ > `name`/`id`. A regra ficou 14 horas sem enviar nada, 9 leads pagos entraram e ninguem foi avisado.
502
+
503
+ ### `send_whatsapp_template` — ATENCAO ESPECIAL
504
+
505
+ Envia um modelo aprovado da Meta. **So funciona em caixa WhatsApp API Oficial (Cloud).**
506
+
507
+ ```json
508
+ {
509
+ "action_name": "send_whatsapp_template",
510
+ "action_params": [{
511
+ "name": "confirmacao_pedido",
512
+ "id": "1071396275361941",
513
+ "language": "pt_BR",
514
+ "category": "UTILITY",
515
+ "processed_params": {"body": {"1": "{{contact.first_name}}"}}
516
+ }]
517
+ }
518
+ ```
519
+
520
+ | Chave | Obrigatoria | O que e |
521
+ |---|---|---|
522
+ | `name` | **SIM** | Nome EXATO do modelo aprovado na Meta. E por ele que o envio acontece |
523
+ | `id` | nao | ID do modelo na Meta. Serve so de reserva se `name` faltar |
524
+ | `language` | nao (padrao `pt_BR`) | Precisa bater com o idioma do modelo aprovado |
525
+ | `category` | nao | `UTILITY`, `MARKETING` ou `AUTHENTICATION` |
526
+ | `processed_params` | nao | Valores das variaveis: `{"body": {"1": "...", "2": "..."}}`. Aceita Liquid (`{{contact.first_name}}`) |
527
+
528
+ **NUNCA use `template_name`, `template_id`, `template_category` nem `template_language` aqui.**
529
+ Esse e o formato dos **nos do FlowBuilder**, que e outra coisa. Na automacao, essas chaves sao
530
+ ignoradas e a acao vira um nada silencioso.
531
+
532
+ Antes de gravar, confirme que o modelo existe e esta aprovado com
533
+ `lionchat_inboxes_whatsapp_templates_list` — o envio so acha o modelo se `name` + `language`
534
+ casarem e o status for `approved`.
535
+
536
+ ### Demais acoes
537
+
538
+ Todos os formatos abaixo foram conferidos linha a linha em `app/services/action_service.rb` e
539
+ `app/services/automation_rules/action_service.rb`.
540
+
541
+ | `action_name` | `action_params` |
542
+ |---|---|
543
+ | `send_message` | `["texto da mensagem"]` |
544
+ | `add_private_note` | `["nota interna"]` |
545
+ | `send_canned_response` | `[12]` — id da resposta pronta |
546
+ | `add_label` / `remove_label` | `["etiqueta1", "etiqueta2"]` |
547
+ | `assign_agent` | `[70]` — o agente precisa ser membro da caixa. `["nil"]` desatribui |
548
+ | `assign_team` | `[3]`. `["nil"]` desatribui |
549
+ | `assign_captain_assistant` | `[17]` ou `[{"assistant_id": 17, "proactive": true}]` — `proactive` false = a IA assume mas nao fala na hora |
550
+ | `send_email_to_team` | `[{"team_ids": [1], "message": "texto"}]` |
551
+ | `send_webhook_event` | `["https://..."]` |
552
+ | `send_attachment` | `[blob_ids]` — so funciona se a regra ja tiver arquivo anexado; nao da pra montar so por API |
553
+ | `change_status` | `["resolved"]`, `["open"]`, `["pending"]` ou `["snoozed"]` |
554
+ | `change_priority` | `["urgent"]`, `["high"]`, `["medium"]`, `["low"]`. `["nil"]` limpa |
555
+ | `send_email_transcript` | `["a@b.com,c@d.com"]` — UMA string, varios e-mails separados por virgula |
556
+ | `mute_conversation` | `[]` |
557
+ | `snooze_conversation` | `[]` |
558
+ | `resolve_conversation` / `open_conversation` / `pending_conversation` | `[]` |
559
+ | `update_contact_attribute` / `update_conversation_attribute` | `[{"attribute_key": "chave", "value": "texto ou {{contact.phone_number}}"}]` |
560
+ | `create_kanban_item` | `[{"funnel_id": 31, "funnel_stage": "prospeccao", "allow_duplicates": false}]` |
561
+ | `move_kanban_item_to_stage` | `[{"funnel_id": 31, "funnel_stage": "qualificacao"}]` |
562
+ | `assign_agent_to_kanban_item` | `[{"funnel_id": 31, "agent_id": 70, "mode": "add"}]` — `mode` aceita `add` (padrao) ou `remove_all` |
563
+ | `add_note_to_kanban_item` | `[{"funnel_id": 31, "text": "texto da nota"}]` — a chave e **`text`**, nao `note` |
564
+ | `set_kanban_item_status` | `[{"funnel_id": 31, "status": "won"}]` — `won`, `lost` ou `open`. Status fora disso e ignorado |
565
+ | `start_kanban_item_timer` / `stop_kanban_item_timer` | `[{"funnel_id": 31}]` |
566
+
567
+ **Acoes de Kanban:** todas precisam que a conversa ja tenha um card no funil informado. Sem card,
568
+ a acao e pulada (fica so no log do servidor). Use `create_kanban_item` antes, na mesma regra.
569
+
570
+ Lista autoritativa de nomes validos: `AutomationRule#actions_attributes` no backend. Nome de acao
571
+ fora dessa lista e recusado com 422 ao salvar — isso a API valida. O que ela **nao** valida e o
572
+ conteudo do `action_params`. `apply_kanban_checklist_template` existe no motor mas **nao** e uma
573
+ acao de automacao valida (e so do FlowBuilder) — usar aqui devolve 422.
@@ -245,15 +245,23 @@ Captain::Assistant
245
245
  ├── config (jsonb) — chaves principais:
246
246
  │ model, temperature, instructions (max 20.000 chars), feature_memory, feature_faq,
247
247
  │ feature_follow_up (bool), follow_up_steps (array de {after_minutes, prompt} — ATE 3 etapas,
248
- │ cada uma >= 5 min, soma <= 1440 min/24h; legado follow_up_time+follow_up_prompt = 1 etapa),
248
+ │ cada uma >= 5 min, soma <= 1380 min = 23h (2026-07-29 — NAO 1440: a janela de 24h do WhatsApp
249
+ │ conta da MENSAGEM DO CLIENTE, nao do 1o follow-up, entao 1440 disparava com a janela ja fechada.
250
+ │ Somar 1440 = 422. GOTCHA: no campo legado follow_up_time a mensagem de erro do servidor ainda
251
+ │ diz "must be between 5 and 1440 minutes" por engano — o teto que ele aplica e 1380);
252
+ │ legado follow_up_time+follow_up_prompt = 1 etapa),
249
253
  │ follow_up_skip_conditions (array — condicoes pra NAO fazer follow-up, logica OU, ate 3; tipos:
250
254
  │ label {operator present|absent, labels[]}; contact_attr/conversation_attr {attribute, operator
251
255
  │ equal|not_equal|contains|present|blank|gt|lt, value}; time_window {start, end — horas inteiras
252
256
  │ 0-23; NAO envia follow-up nesse periodo, janela circular ex 22->7; a IA SEGUE respondendo o
253
257
  │ cliente normal — o silencio e so do follow-up}),
254
- │ feature_pause_on_human_reply (bool — a IA se DESLIGA sozinha na conversa quando um HUMANO
255
- │ assume: atendente responde pelo painel (ao vivo) OU mensagem enviada do celular. NAO conta:
256
- │ nota privada, msg da propria IA, automacao, campanha, confirmacao de agendamento, msg agendada),
258
+ │ feature_pause_on_human_reply (bool, nasce DESLIGADO — quando ligado, a IA se DESLIGA sozinha na
259
+ │ conversa quando um HUMANO assume: atendente responde pelo painel (ao vivo) OU mensagem enviada
260
+ │ do celular. NAO conta: nota privada, msg da propria IA, automacao, campanha, confirmacao de
261
+ │ agendamento. MENSAGEM AGENDADA DEPENDE DE QUEM AGENDOU (2026-07-29): agendada pela IA (a tool
262
+ │ carimba source_type='Captain::Assistant' e o disparo assina como o assistente) NAO desliga;
263
+ │ agendada por ATENDENTE assina como User com Current.user setado no disparo = conta como
264
+ │ atendente ao vivo e DESLIGA a IA da conversa),
257
265
  │ activation_label (etiqueta que ativa o agente — unica por conta),
258
266
  │ min_response_time / max_response_time (delay humanizado, 1-60s),
259
267
  │ disabled_tools (array de IDs de tools desativadas pro assistente),
@@ -388,11 +396,12 @@ Booking (agendamento confirmado)
388
396
 
389
397
  ## e-Clínicas (Efficient) — integração de clínicas
390
398
 
391
- 9 eventos de webhook, cada um mapeável a automação OU flow na tela Integrações > e-Clínica:
399
+ 13 eventos de webhook, cada um mapeável a automação OU flow na tela Integrações > e-Clínica:
392
400
  `cliente_novo`, `agendamento_novo`, `falta`, `pagamento` (os 4 da doc oficial) +
393
401
  `agendamento_atendido`, `agendamento_alterado`, `agendamento_desmarcado`,
394
- `agendamento_transferido`, `cliente_baixa_pagamento` (capturados ao vivo, não documentados
395
- pelo e-Clínica).
402
+ `agendamento_transferido`, `cliente_baixa_pagamento`, `cliente_alteracao`,
403
+ `cliente_inclusao_pagamento`, `controle_laboratorio_novo`, `controle_laboratorio_alterado`
404
+ (capturados ao vivo, não documentados pelo e-Clínica).
396
405
 
397
406
  Atributos de sistema no CONTATO (prefixo `eclinica_`, protegidos, usáveis como variável):
398
407
 
@@ -107,8 +107,16 @@ Item (sem `config`): `{ "key": "campaign_trigger" }`
107
107
  - Pode conviver com outros gatilhos no mesmo flow (ex.: um flow que responde a mensagem E também pode
108
108
  ser disparado em campanha). É **isento** da trava de gatilho duplicado.
109
109
  - Só flow de conversa (`flow_type: conversation`); flow de grupo funciona, mas o público é de contatos.
110
- - Em caixa WhatsApp **oficial**, o 1º bloco de mensagem do flow precisa ser um **template aprovado** —
111
- a campanha é recusada na criação (422) se não for. Em QR Code não existe essa regra.
110
+ - Em caixa WhatsApp **oficial**, o primeiro bloco de mensagem de **CADA CAMINHO** que sai do Início
111
+ precisa abrir com **template aprovado** — não só o primeiro bloco que o flow encontra. Com
112
+ randomizador (teste A/B) ou condição logo no começo, **todas** as variações são conferidas: basta
113
+ UMA sem template pra campanha ser recusada na criação (422). O erro nomeia os blocos culpados pelo
114
+ rótulo, teto de 5: `must start with an approved WhatsApp template on official inboxes (no template
115
+ in: <rótulos>)`. O que vale é o **primeiro item** do `messageItems` daquele bloco, e os tipos
116
+ aceitos são `template` (o que a tela grava) e `whatsapp_template` (legado). Flow sem nenhum bloco
117
+ de mensagem não tem o que validar. Em QR Code não existe essa regra.
118
+ (Corrigido em 2026-07-29: até então a checagem aceitava só `whatsapp_template` — nome que a tela
119
+ NUNCA gravou — e olhava um caminho só; recusava 100% das caixas oficiais.)
112
120
  - Como a campanha inicia o flow com o contato **sem ter mandado nada**, escreva o 1º bloco assumindo
113
121
  contato frio (apresente-se, dê contexto) — diferente de um flow de atendimento, que responde a alguém.
114
122
 
@@ -418,6 +426,10 @@ o botão "Usar variável" ao lado do campo alterna lista fixa ↔ variável.
418
426
 
419
427
  **`update_attribute` — campos EXATOS:** `attr_source` (`'contact'`, `'conversation'` ou `'card'`), `attr_key` (nome do atributo), `attr_value` (valor). NÃO existem `entity`/`key`/`value` — esses são ignorados e não salvam nada.
420
428
 
429
+ **Coerção por TIPO (2026-07-29):** em `attr_source: 'contact'|'conversation'`, o `attr_value` de atributo personalizado é coagido pelo tipo da definição antes de gravar: número/moeda/porcentagem viram numérico ("1.234,56" → 1234.56; inteiro fica inteiro), checkbox aceita sim/não/true/false e grava BOOLEANO, lista casa caixa-insensível e grava a opção canônica, data aceita DD/MM/AAAA ou ISO, hora aceita "14:30"/"14h30". Valor irreconhecível grava como veio e o passo mostra nota âmbar no histórico. Card NÃO coage (atributo de card não tem definição tipada no backend).
430
+
431
+ **Campos NATIVOS do contato (2026-07-29):** com `attr_source: 'contact'`, `attr_key` também aceita os paths canônicos `contact.name`, `contact.email`, `contact.cpf`, `contact.cnpj`, `contact.rg`, `contact.passport`, `contact.date_of_birth`, `contact.gender`, `contact.marital_status`, `contact.profession` e `contact.address.{cep,street,number,complement,neighborhood,city,state,country}` — gravam na ficha nativa do contato (não em custom_attributes). Regras: telefone/identifier/etiquetas NÃO são aceitos (mapa fechado — chave fora dele vira atributo personalizado comum); documentos, nascimento, gênero e **e-mail** só preenchem campo VAZIO; nome/profissão/estado civil/endereço sobrescrevem; valor em branco nunca grava; gênero aceita `m|f|o|na` e estado civil `solteiro|casado|uniao_estavel|divorciado|separado|viuvo`; data de nascimento em `dd/mm/aaaa` ou `aaaa-mm-dd`. Recusas aparecem no histórico de execução (passo 'skipped'/'error' com mensagem).
432
+
421
433
  **Somar/subtrair NÃO é operação dedicada** — é filtro Liquid no próprio `attr_value`. O valor é resolvido como template antes de salvar. Exemplos:
422
434
 
423
435
  ```json
@@ -497,14 +509,22 @@ NÃO existe `.payload`/`.response` (`{{api_response.payload.x}}` resolve vazio).
497
509
 
498
510
  **Modelo por ação — `aiModel` + `aiModelExplicit` (2026-07-13):** TODO modo aceita escolher o modelo
499
511
  LLM no próprio nó, com ou sem assistente, em qualquer tipo de flow. Enviar o par:
500
- `"aiModel": "gpt-4.1-mini"` (whitelist de `FlowBuilder::RawLlmService::SUPPORTED_MODELS` — ex.
501
- gpt-4o-mini, gpt-4o, gpt-4.1, gpt-4.1-mini, gpt-4.1-nano, gpt-5*, o1, o3*, o4-mini) JUNTO de
502
- `"aiModelExplicit": true`. SEM a flag o backend ignora o aiModel nos modos novos (proteção de nós
503
- antigos que carregam aiModel de default nunca honrado). `aiModel` vazio/ausente = modelo padrão da
504
- conta. Com assistente selecionado, o override também vale (sobrepõe o modelo que o caminho do
505
- assistente usaria). Modelo fora da whitelist = ignorado (cai no padrão da conta).
512
+ `"aiModel": "gpt-4.1-mini"` JUNTO de `"aiModelExplicit": true`. SEM a flag o backend ignora o
513
+ aiModel nos modos novos (proteção de nós antigos que carregam aiModel de default nunca honrado).
514
+ `aiModel` vazio/ausente = modelo padrão da conta. Com assistente selecionado, o override também vale
515
+ (sobrepõe o modelo que o caminho do assistente usaria). Modelo fora da whitelist = ignorado (cai no
516
+ padrão da conta).
506
517
  Exemplo: `{"aiMode":"sentiment","aiAssistantId":"12","aiModel":"gpt-4.1-mini","aiModelExplicit":true}`.
507
518
 
519
+ **A whitelist (`FlowBuilder::RawLlmService::SUPPORTED_MODELS`) é uma lista FECHADA de 16 nomes, SEM
520
+ curinga** — não existe "a família gpt-5 inteira":
521
+ `gpt-4o-mini`, `gpt-4o`, `gpt-4.1-nano`, `gpt-4.1-mini`, `gpt-4.1`, `gpt-5-mini`, `gpt-5`,
522
+ `gpt-5.2`, `gpt-5.4-nano`, `gpt-5.4-mini`, `gpt-5.4`, `gpt-5.5`, `o1`, `o3`, `o3-mini`, `o4-mini`.
523
+ A família **`gpt-5.6` (luna/terra/sol) foi REMOVIDA em 2026-07-29**: ela recusa function tools
524
+ (erro 400 na OpenAI), o que deixava o Agente de IA **mudo em 100% das chamadas** — saiu também daqui
525
+ pra não ser oferecida numa tela e faltar na outra. Também não existem `o1-mini`, `gpt-4.5-preview`,
526
+ `gpt-5.2-pro` nem `gpt-5.5-pro` (a OpenAI recusa).
527
+
508
528
  **OBRIGATÓRIO via API — `aiAssistantId`:** os modos `generate`/`intent`/`sentiment`/`extract`
509
529
  EXIGEM um `aiAssistantId` válido (id de um assistente Captain da conta). Sem ele o nó SAI cedo
510
530
  (`action: continue`) sem gravar nada — a variável de saída (`aiResponseVar`/`ai_intent`/`ai_sentiment`)
@@ -581,15 +601,22 @@ O campo raiz é **`waitMode`**: `"duration"` (default), `"date"` ou `"weekday"`.
581
601
  "waitMode": "weekday",
582
602
  "waitWeekday": 1,
583
603
  "waitWeekdayTime": "09:00",
604
+ "waitWeekdayTimeMode": "fixed",
584
605
  "waitTimezone": "America/Sao_Paulo"
585
606
  }
586
607
  }
587
608
  ```
588
609
 
589
- - `waitWeekday`: 0=domingo, 1=segunda... 6=sábado. `waitWeekdayTime` = `"HH:MM"` 24h, **SEMPRE fixo** (este campo NÃO aceita variável — decisão de produto). `waitTimezone` = mesmo campo do modo date.
610
+ - `waitWeekday`: 0=domingo, 1=segunda... 6=sábado. `waitWeekdayTime` = `"HH:MM"` 24h. `waitTimezone` = mesmo campo do modo date.
611
+ - **Variável no Horário (novo 2026-07-29 — reverte a proibição de 06/07):** o campo `waitWeekdayTime` ACEITA variável `{{ }}`, com o mesmo contrato dos campos do modo `date`. O modo mora em **`waitWeekdayTimeMode`**: `"fixed"` (default, e o que vale quando a chave está ausente) ou `"variable"` — espelho exato do `waitTimeMode`.
612
+ - **Mandar a variável SEM `"waitWeekdayTimeMode": "variable"` não funciona e não avisa:** o motor trata o campo como texto fixo, a chave `{{ }}` nunca é resolvida e o horário calculado não é o pretendido. Não há erro de validação, nem na API nem na tela. Como o MCP escreve o JSON do fluxo direto, sem passar pela tela, esse par é obrigatório.
613
+ - Exemplo: `{"waitMode":"weekday","waitWeekday":1,"waitWeekdayTime":"{{contact.custom_attribute.horario_retorno}}","waitWeekdayTimeMode":"variable","waitTimezone":"America/Sao_Paulo"}`.
614
+ - Resolve UMA única vez, na entrada do node (quem já está esperando mantém o valor capturado). Aceita `"14:30"`, `"9:30"`, `"14h30"`, `"14h"`, `"14:30:00"` — ou seja, atributo tipo `time` (Hora 24h). Valor ISO de atributo `date`/`datetime` é rejeitado (mesma regra do campo Horário do modo `date`).
615
+ - Variável que não resolve pra horário válido → saída `error` (payload `invalid_variable_datetime`, `field: waitWeekdayTime`).
616
+ - Modo `fixed` NUNCA emite erro: horário em branco cai no default `09:00` — flow antigo intacto.
590
617
  - (Os nomes `targetWeekday`/`targetHour` NÃO existem — eram um erro de documentação antiga.)
591
618
 
592
- **Handles que SAEM:** `success` (+ `error`, emitido apenas quando um campo em modo `variable` resolve pra valor inválido no modo `date`).
619
+ **Handles que SAEM:** `success` (+ `error`, emitido quando um campo em modo `variable` resolve pra valor inválido — vale nos DOIS modos, `date` e `weekday`; em modo `fixed` o `error` nunca é emitido).
593
620
 
594
621
  ### 2.9 `set_variable`
595
622
 
@@ -621,13 +648,19 @@ O campo raiz é **`waitMode`**: `"duration"` (default), `"date"` ou `"weekday"`.
621
648
  "label": "A/B test mensagem",
622
649
  "mode": "branches",
623
650
  "branches": [
624
- { "id": "A", "label": "Variante A", "weight": 50 },
625
- { "id": "B", "label": "Variante B", "weight": 50 }
651
+ { "id": "A", "label": "Variante A", "percent": 50 },
652
+ { "id": "B", "label": "Variante B", "percent": 50 }
626
653
  ]
627
654
  }
628
655
  }
629
656
  ```
630
657
 
658
+ **O campo é `percent`, NÃO `weight` (2026-07-30):** a tela sempre gravou `percent` (`randomizerBranches`
659
+ do FlowNodeConfigModal) e o motor lia só `weight` — nenhum node configurado pela tela funcionava.
660
+ Corrigido em 30/07: o motor lê `percent` e aceita `weight` só como dado antigo. **Escreva sempre
661
+ `percent`** — randomizador criado com `weight` até roda, mas abre com as porcentagens EM BRANCO na
662
+ tela do cliente (a tela lê só `percent`).
663
+
631
664
  **Modo `distribute_agents` (sorteio PONDERADO):** `data.mode: 'distribute_agents'` + `data.agents: [{ agent_id, percent }]` — sorteia UM agente por probabilidade (percentuais somando 100) e atribui automaticamente. Isso é SORTEIO, não rodízio: no curto prazo pode cair no mesmo agente várias vezes seguidas. Pra RODÍZIO EXATO (cada um na vez), use a AÇÃO `distribute_agents` no node `action` (ver seção 2.5) — não este modo.
632
665
 
633
666
  **Handles que SAEM:** o `id` de cada branch (`A`, `B`, ...). Em `mode: 'distribute_agents'` é `success`.