@lionchat/mcp-server 1.14.0 → 1.16.0

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.
@@ -23,7 +23,7 @@ Como criar fluxos do LionChat (FlowBuilder) que abrem certinhos no canvas, sem n
23
23
  | Classificar intenção da mensagem | `ai` mode `intent` | - |
24
24
  | Analisar sentimento | `ai` mode `sentiment` | - |
25
25
  | Encerrar fluxo no meio | `action` com `key: 'deactivate_flow'` OU `exit_conditions` no nível do flow | - |
26
- | Distribuir aleatório entre branches | `randomizer` | - |
26
+ | Dividir na proporção entre branches (A/B) | `randomizer` | - |
27
27
  | Gerir grupo WhatsApp: criar/buscar, nome/foto/descrição, participantes, admins, convite (WAHA) | `update_group` (uma operação por bloco via `groupOperation`) | - |
28
28
  | Iniciar outro fluxo | `action` com `key: 'start_flow'` OU node `activate_flow` | - |
29
29
  | Encerrar ramo / definir retorno de ai_tool | `end` | - |
@@ -53,14 +53,18 @@ Todo node tem essa estrutura base:
53
53
  "position": { "x": 50, "y": 300 },
54
54
  "data": {
55
55
  "label": "Início",
56
- "triggers": [
57
- { "type": "message_received", "keywords": ["oi", "ola"], "match_type": "contains" }
56
+ "items": [
57
+ { "key": "message_received", "config": { "keywords": ["oi", "ola"], "match_type": "contains" } }
58
58
  ]
59
59
  }
60
60
  }
61
61
  ```
62
62
 
63
- **Triggers válidos:** `message_received`, `message_sent`, `conversation_created`, `conversation_resolved`, `conversation_reopened`, `label_added`, `label_removed`, `card_created`, `card_moved`, `conversation_attribute_changed`, `card_attribute_changed`, `contact_attribute_changed`, `date_trigger`, `campaign_trigger`, `cron`, `webhook`.
63
+ **ATENÇÃO — chave `items`, não `triggers` (corrigido 19/08/2026).** Os gatilhos vão em **`data.items`**, cada item no formato **`{ "key": "...", "config": { ...filtros... } }`**. O formato antigo (`data.triggers` + `type` + filtros soltos no topo) ainda é aceito pelo motor por compatibilidade, mas **NÃO deve ser usado em fluxo novo**: o editor do FlowBuilder lê apenas `data.items`, então um fluxo gravado no formato antigo **dispara normalmente mas abre com o bloco Início EM BRANCO no painel do cliente** — e um clique em "Concluir" naquele modal apagava o gatilho em silêncio. Foram 53 fluxos de 9 contas nesse estado até o conserto de 19/08. Alguns leitores do backend (`has_webhook_trigger?` do disparo por webhook, o filtro de Campanha de Fluxo, o gatilho LionTrack e o histórico de execução) também só entendem `items`.
64
+
65
+ **Triggers válidos:** `message_received`, `message_sent`, `conversation_created`, `conversation_resolved`, `conversation_reopened`, `team_changed`, `assignee_changed`, `label_added`, `label_removed`, `sla_missed`, `card_created`, `card_moved`, `card_won`, `card_lost`, `conversation_attribute_changed`, `card_attribute_changed`, `contact_attribute_changed`, `date_trigger`, `campaign_trigger`, `manual_trigger`, `webhook_received`, `page_track`, `group_participant_joined`, `group_participant_left`, `lead_form_completed`, `lead_form_milestone`, `lead_form_abandoned`, `booking_created`, `booking_cancelled`, `booking_rescheduled`, `booking_completed`.
66
+
67
+ **NÃO existem** os gatilhos `webhook` (o correto é **`webhook_received`**) nem `cron` (para disparo por data use `date_trigger`; para disparo em lote use `campaign_trigger` + Campanha de Fluxo). Até 19/08/2026 este guia listava os dois por engano — o fluxo 180 da conta 54 ficou ATIVO com `webhook` e **nunca disparou uma vez em 26 dias**, sem erro nenhum.
64
68
 
65
69
  **Trigger `message_sent` (novo 2026-06-11):** par do message_received, mas pra mensagens de SAÍDA — dispara quando atendente, celular (eco do WhatsApp) ou a própria IA/flow envia mensagem (nota privada NÃO dispara). Config: `keywords` (opcional) + `match_type` (`contains`|`exact`). Caso de uso clássico: "quando eu responder do celular, desligar a IA". ATENÇÃO: mensagem da IA também dispara — se a ação for desativar a IA, use keywords que só humanos digitam ou aceite que a primeira resposta da IA aciona o flow. Protegido por anti-loop (profundidade 5) e sessão única por conversa+flow; nunca alimenta `waiting_input`.
66
70
 
@@ -69,12 +73,14 @@ Todo node tem essa estrutura base:
69
73
  - `conversation_created` / `conversation_reopened`: filtro opcional de keywords via `match_mode` (`'any'`, `'contains'`, `'exact'`, `'customer_initiated'`, `'agent_initiated'`) + `keywords`. Só ESTES dois triggers usam `match_mode`.
70
74
  - `label_added` / `label_removed`: `label_names` (array de slugs). NÃO use `label` (singular) — é ignorado.
71
75
  - `card_created` / `card_moved`: `funnel_ids` (array de STRINGS, ex.: `["37"]` — número puro `[37]` NÃO casa) + `funnel_stages` (array de `"funnel_id:chave_da_etapa"`, ex.: `"37:agendamento_pendente"`). A `chave_da_etapa` é a CHAVE INTERNA da etapa no funil (slug legível em funis de template; pode ser um UUID/`stage_<n>` em etapa criada à mão ou funil duplicado) — NÃO o nome exibido na tela. Sem `funnel_ids` → dispara em qualquer funil; com `funnel_ids` mas sem `funnel_stages` → qualquer etapa daquele(s) funil(is). O card precisa estar num funil listado em `funnel_ids` pra o filtro de etapa valer. ATENÇÃO: dois flows ATIVOS na mesma inbox com `card_created`/`card_moved` de funil/etapa sobrepostos são bloqueados na criação/ativação (ver `flow_trigger_conflict` no fim deste guia).
76
+ - `team_changed`: `team_ids` (array de STRINGS; vazio = qualquer equipe). `assignee_changed`: `agent_ids` (array de STRINGS; vazio = qualquer atendente). `card_won` / `card_lost`: `funnel_ids` (array de STRINGS) — disparam SÓ quando o STATUS do card vira ganho/perdido, nunca por etapa com esse nome.
77
+ - `page_track` (LionTrack, só flow individual): `{ "track_type": "visited_page"|"event_fired", "urls": [...], "operator": "contains"|"exact"|"starts_with", "event_names": [...], "event_operator": "equals"|"contains", "cooldown_hours": 24 }` — `cooldown_hours` (padrão 24, mínimo 1) não re-dispara pro mesmo contato dentro da janela.
72
78
  - `conversation_attribute_changed` / `card_attribute_changed` (novo 2026-07-08): disparam na VIRADA de um atributo (da CONVERSA ou do CARD do kanban) pro valor que casa — RE-ENTRAM toda vez que o atributo muda pro valor alvo. Config: `{ "logic": "and"|"or", "rules": [ { "attr_key": "...", "operator": "...", "value": "..." } ] }` (uma rule pode usar `values: [...]` no lugar de `value` pra multi-valor). O `attrSource` é IMPLÍCITO pela chave do gatilho (conversa vs card) — NÃO informe. Operadores (contexto REATIVO — desde 09/07 `is_empty`/`is_not_empty` NÃO valem aqui, pois "está vazio AGORA" dispararia a cada evento; use-os só no nó Condição): `equal`/`not_equal`/`contains`/`not_contains`/`starts_with`/`ends_with`/`greater_than`/`less_than`/`number_range`. Só `card_attribute_changed` aceita `funnel_id`/`card_source` opcionais dentro da rule (pra achar o card). Rule sem `attr_key` é ignorada.
73
79
  - `contact_attribute_changed` (novo 2026-07-22 — "Atributo do contato muda"): dispara quando um atributo personalizado do **CONTATO** muda e a condição casa — mesmo com o contato fora de flow. Item: `{ "key": "contact_attribute_changed", "config": { "logic": "and"|"or", "rules": [ { "attrSource": "contact", "attr_key": "...", "operator": "...", "value": "..." } ] } }` (1 a 10 rules; `values: [...]` pra multi-valor). **DIFERENTE dos irmãos de 08/07: aqui o `attrSource` VAI na rule e é SEMPRE `"contact"`.** Operadores iguais aos do nó Condição. **VERSÃO SEGURA por decisão de produto:** dispara SÓ na conversa mais recente que JÁ EXISTE numa caixa do flow (reabre se resolvida); contato sem conversa NÃO dispara e NUNCA cria conversa. Dedup de 30s por contato + anti-loop por profundidade de cadeia (flow que muda atributo que dispara flow…). Só flows de conversa.
74
80
 
75
81
  - `date_trigger` — **Gatilho de Data (novo 2026-07-10):** dispara quando uma DATA guardada na ficha do CONTATO chega (aniversário, data de exame, vencimento de plano). Modelo "agendamento de mensagem": a escrita da data já marca o disparo — NÃO existe varredura periódica. Só em flow `conversation` **individual** (a data é de um contato; grupo não tem). Config (item usa `config` ANINHADO, igual `webhook_received`/`attribute_changed`):
76
82
  ```json
77
- { "type": "date_trigger", "config": {
83
+ { "key": "date_trigger", "config": {
78
84
  "attr_key": "_date_of_birth", // "_date_of_birth" = Aniversário nativo; OU a chave de um atributo do contato do tipo Data (ex.: "data_exame")
79
85
  "offset_direction": "on", // "before" | "on" | "after"
80
86
  "offset_days": 0, // 0-365 (relevante só p/ before/after)
@@ -127,17 +133,27 @@ Receita completa: `flows_create`/`flows_update` com o item no start → `flows_l
127
133
  **Trigger `webhook` — Webhook Universal EMBUTIDO (novo 2026-06):** o flow pode ser disparado por um webhook próprio, criado automaticamente. Receita via API:
128
134
  1. Criar o flow normalmente (`flows_create`).
129
135
  2. `POST /custom_webhook_integrations` com `{ "custom_webhook_integration": { "flow_id": <id do flow> } }` — o sistema cria a integração embutida (idempotente: repetir retorna a mesma; nome automático "Flow: <nome>"; auto-mapeia todos os eventos → este flow) e retorna a URL única do webhook.
130
- 3. No node `start`, adicionar item `{ "type": "webhook_received", "config": { "integration_id": <id da integração> } }`.
136
+ 3. No node `start`, adicionar em `data.items` o item `{ "key": "webhook_received", "config": { "integration_id": <id da integração> } }`.
131
137
  4. Salvar o flow (`flows_update`) — o save sincroniza a ativação do webhook embutido (remover o item desativa a integração automaticamente).
132
138
  Webhooks embutidos NÃO aparecem na listagem de integrações standalone; excluir o flow destrói o webhook; duplicar o flow NÃO copia o gatilho embutido. Rate limit do endpoint público: 60/min por token.
133
139
 
140
+ **Gatilhos de AGENDAMENTO — Booking NATIVO do LionChat (2026-08-20):** `booking_created` (alguém marcou pelo link público, pela IA ou pelo painel), `booking_cancelled` (paciente cancelou pelo link, IA cancelou ou a equipe cancelou na Agenda), `booking_rescheduled` (a data/hora mudou — link do paciente, IA, editar/adiar na Agenda), `booking_completed` (equipe concluiu na Agenda; exige a Agenda unificada ligada na conta). **Não é e-Clínica.** Config (tudo opcional, vazio = todos): `booking_event_type_ids` (array de ids de tipo de agendamento, **STRING**), `agent_ids` (array de ids de usuário — o RESPONSÁVEL da tarefa), `create_conversation` (boolean, padrão `false`: só dispara para quem já tem conversa numa caixa do flow; `true` = cria a conversa na 1ª caixa do flow). Exemplo: `{ "key": "booking_created", "config": { "booking_event_type_ids": ["44"], "agent_ids": [], "create_conversation": true } }`. São INERTES a evento de conversa (quem dispara é o `Booking::FlowTriggerDispatcher`, por CONTA — caixa do flow só define onde a conversa é criada/reusada). Variáveis no flow: `{{booking.date}}` (DD/MM/AAAA), `{{booking.time}}`, `{{booking.weekday}}`, `{{booking.datetime}}` (ISO), `{{booking.type}}`, `{{booking.type_format}}` (presencial/videochamada), `{{booking.duration}}`, `{{booking.agent}}`, `{{booking.status}}` (confirmado/cancelado/remarcado/concluído), `{{booking.description}}`, `{{booking.cancel_url}}`, `{{booking.reschedule_url}}`, `{{booking.color}}`, `{{booking.id}}`; só no remarcado: `{{booking.previous_date}}`, `{{booking.previous_time}}`, `{{booking.previous_datetime}}`; `{{booking.meeting_url}}` NÃO vem no criado (nasce vazio). Data/hora vêm da TAREFA da Agenda (sempre atuais). Regras: o evento mais novo SUBSTITUI a rodada ativa do mesmo flow na mesma conversa (2ª consulta do mesmo paciente, ou cancelado chegando com o criado ainda esperando resposta); excluir a tarefa, reabrir cancelada ou desfazer conclusão NÃO disparam; adiar (snooze) conta como remarcado; tipo com confirmação/lembrete próprios ligados + `booking_created` = o paciente pode receber duas mensagens (a tela avisa). Colisão entre flows é por CONTA: mesmo kind + tipos se cruzando + agentes se cruzando (vazio = todos). Há também `{{booking.event}}` (`created`|`cancelled`|`rescheduled`|`completed` — bom pra condição; não aparece no seletor da tela, mas resolve); `{{trigger.type}}` = a chave do gatilho (ex.: `booking_created`), `{{trigger.kind}}` = `created`|`cancelled`|`rescheduled`|`completed` e `{{trigger.booking_id}}`. SÓ flow de conversa **individual** (a tela nem oferece em flow de grupo); **nunca dispara flow `ai_tool`**. Em caixa WhatsApp OFICIAL, conversa criada nova (ou parada há mais de 24h) está FORA da janela — texto livre no 1º bloco cai em `window_closed`/erro; comece por template.
141
+
134
142
  **TRAVA DE GATILHO DUPLICADO (2026-06-16) — leia ANTES de ativar/criar flow ativo:** o sistema BLOQUEIA ter dois flows ATIVOS com o MESMO gatilho na MESMA inbox e mesmo `conversation_mode` (evita o evento disparar dois flows). Colisão = mesmo tipo de gatilho + config cruzando (mesmas keywords/funil/labels/url/etc) + inbox compartilhada + mesmo modo. EXCEÇÕES que podem coexistir: `webhook_received`, `manual_trigger` e `campaign_trigger` (este último porque não dispara por evento — é só uma autorização para a campanha).
135
143
  - Ao **ativar** (`flows_toggle` inativo→ativo) ou **criar já ativo**: qualquer conflito é barrado.
136
144
  - Ao **editar** um flow já ativo: só conflito NOVO é barrado (duplicados que já existiam são preservados).
137
145
  - A API responde **422** com `{ "error_code": "flow_trigger_conflict", "conflicts": [{ flow_id, flow_name, trigger_type, inbox_id, inbox_name }] }`.
138
146
  - Existe `POST /flows/check_conflicts` (mesma assinatura, NÃO salva) pra checar antes.
139
147
 
140
- **Como a IA deve agir:** antes de ativar um flow, confira via `flows_list` se já não há outro flow ativo no mesmo gatilho+inbox. Se receber 422 `flow_trigger_conflict`, NÃO fique reativando — EXPLIQUE o conflito ao usuário (nome do flow conflitante + caixa) e ofereça desativar o outro flow ou ajustar o gatilho/keywords.
148
+ **Como a IA deve agir:** antes de ativar um flow, confira via `flows_check_conflicts` (mesma assinatura do toggle/update, NÃO salva nada e devolve exatamente quem colide) se já não há outro flow ativo no mesmo gatilho+inbox — NÃO tente deduzir isso lendo o `flow_data` de cada item da `flows_list` (a lista é pra escolher/filtrar fluxos, não pra inspecionar blocos). Se receber 422 `flow_trigger_conflict`, NÃO fique reativando — EXPLIQUE o conflito ao usuário (nome do flow conflitante + caixa) e ofereça desativar o outro flow ou ajustar o gatilho/keywords.
149
+
150
+ **O que `flows_list` devolve (leve, desde 20/08/2026):** `id`, `name`, `description`, `flow_type`,
151
+ `conversation_mode`, `active`, `inbox_ids`/`inboxes`, `tags`, `tool_name`/`tool_description` e
152
+ **`trigger_types`** (as chaves de gatilho do bloco Início — serve pra filtrar "quais flows disparam
153
+ por `message_received`" sem abrir cada um). **NÃO vem** `flow_data`, `variables` nem métricas por
154
+ bloco — pra isso use `flows_show`. Contadores de sessão (`session_stats`: `total`, `active`,
155
+ `waiting`, `completed`, `exited`, `error`) só com `with_session_stats=true`. Ignore
156
+ `executions_count`/`errors_count` (sempre 0).
141
157
 
142
158
  **Handles que SAEM:** `success`.
143
159
 
@@ -152,14 +168,16 @@ Webhooks embutidos NÃO aparecem na listagem de integrações standalone; exclui
152
168
  "label": "Boas-vindas",
153
169
  "messageItems": [
154
170
  { "id": "m1", "type": "text", "content": "Oi {{contact.name}}! Como posso ajudar?" },
155
- { "id": "m2", "type": "delay", "seconds": 2 },
171
+ { "id": "m2", "type": "delay", "duration_seconds": 2 },
156
172
  { "id": "m3", "type": "whatsapp_template", "templateId": 123, "params": ["valor1"] }
157
173
  ]
158
174
  }
159
175
  }
160
176
  ```
161
177
 
162
- **Tipos de item válidos:** `text`, `whatsapp_template` (ou `template`), `canned_response`, `user_input` (pausa esperando resposta livre), `delay`, `attachment`, `audio`.
178
+ **Tipos de item válidos:** `text`, `whatsapp_template` (ou `template`), `canned_response`, `user_input` (pausa esperando resposta livre), `delay`, `attachment`, `audio`, `url_media` (`{ "type": "url_media", "url", "caption" }` — mídia por URL; a `url` aceita `{{variável}}` e o motor baixa com proteção SSRF e detecta o tipo; `image`/`video`/`file`/`document` também são aceitos como mídia, com `attachment_url`).
179
+
180
+ **`delay` usa `duration_seconds` (0-30), NÃO `seconds`.** O motor aceita os dois, mas a tela lê/grava só `duration_seconds` — item gravado com `seconds` roda, porém abre como "undefineds" no editor e o cliente não consegue ajustar (mesma família do `percent` x `weight` do randomizador).
163
181
 
164
182
  **ATENÇÃO:** usa `messageItems` (NÃO `items`).
165
183
 
@@ -172,7 +190,7 @@ Webhooks embutidos NÃO aparecem na listagem de integrações standalone; exclui
172
190
  ```
173
191
  → edges: `sourceHandle: "button_sim"`, `sourceHandle: "button_nao"`, e opcionalmente `"no_response"`.
174
192
 
175
- **Handles que SAEM:** `success` (sem botões); com botões → `button_<value>` (um por botão) + `no_response` + `error`.
193
+ **Handles que SAEM:** `success` (sem botões) + `error`; com botões → `button_<value>` (um por botão) + `no_response` + `error`. E, SÓ em caixa WhatsApp OFICIAL com item sujeito à janela de 24h (`text`/`canned_response`/`attachment`/`audio`/`url_media`), **`window_closed`**: o motor segue por ele quando a janela está fechada (ligue ali um bloco com template); sem esse fio a janela fechada cai no caminho de erro. Fora de caixa oficial o handle NÃO existe (edge nele = aresta fantasma). Com botões a janela fechada também vale.
176
194
 
177
195
  **Timeout dos botões (opcional, no MESMO item de botões):**
178
196
  - `buttons_timeout` (número) + `buttons_timeout_unit` (`"minutes"` | `"hours"` | `"days"`) → tempo de espera sem resposta. Só com `buttons_timeout > 0` existe o handle `no_reply_timeout`.
@@ -272,7 +290,26 @@ No editor visual esses destinos só aparecem quando a validação bate (computed
272
290
 
273
291
  **`timeout` vs `retries_exhausted` (DISTINTOS):** `timeout` = cliente ficou em silencio (estourou `waitTime`). `retries_exhausted` = cliente respondeu, mas errou a validacao mais que `maxRetries` vezes. Ligue cada um ao caminho desejado. Se `retries_exhausted` nao tiver edge, ha fallback p/ o edge de `timeout`; sem nenhum dos dois, o flow encerra ao esgotar as tentativas.
274
292
 
275
- **Timeout AGORA dispara de verdade (corrigido 2026-06-09):** `waitTime` + `waitUnit` agendam o estouro — se o cliente não responder no prazo, o flow segue pelo handle `timeout`. Antes dessa data o backend ignorava o waitTime (flows antigos que dependiam do timeout passaram a funcionar). Sempre ligue um edge no handle `timeout` quando definir waitTime; sem edge, o flow simplesmente para ali no estouro.
293
+ **Timeout dispara de verdade — e SEM fio ENCERRA (desde 20/08/2026):** `waitTime` + `waitUnit`
294
+ agendam o estouro. Com fio no handle `timeout`, o flow segue por ele. **Sem fio, a sessão é
295
+ ENCERRADA quando o tempo estoura** — a resposta que chegar depois não avança mais aquele fluxo
296
+ (cai na conversa como mensagem comum e pode disparar o gatilho de novo). Até 20/08 a sessão ficava
297
+ esperando pra sempre, fora do alcance de qualquer vigia e travando o re-disparo do próprio flow
298
+ naquela conversa. Decisão do dono: "se não tá ligado a nada, depois encerra". Se o cliente quer
299
+ "esperar até responder", ponha um `waitTime` longo e ligue o `timeout` num caminho explícito.
300
+ `waitTime` AUSENTE = sem timeout (espera indefinida); `waitTime` vazio (`''`) = 60 (o que a tela
301
+ exibe). `maxRetries` vazio/0 = 3. Vale também pro `no_reply_timeout` dos botões.
302
+
303
+ **Fallback de saída (regra de 20/08/2026, substitui a de 15/08):** fio COM rótulo só é seguido por
304
+ quem CASA o rótulo. O "último recurso" do avanço segue EXCLUSIVAMENTE a ligação SEM `sourceHandle`
305
+ (fluxo legado de bloco simples) — nunca mais "pega o primeiro fio que existir": resposta A num menu
306
+ com só a saída B ligada NÃO entrega no caminho do B, quem não respondeu não cai no botão ligado,
307
+ envio bem-sucedido não desce pelo fio `window_closed`. Sem fio elegível o fluxo TERMINA. Duas pontes
308
+ explícitas que continuam valendo: (a) na RESPOSTA do cliente (`wait_response`/botões),
309
+ `option_X`/`button_X`/`no_response` sem fio próprio cai no fio `success` SE ele existir (o bloco de
310
+ botões não expõe `success` na tela, então na prática isso só vale no `wait_response`); (b) a saída
311
+ `partial` do `update_group` sem fio cai no `success`. Conclusão prática: ligue TODA saída que pode
312
+ acontecer.
276
313
 
277
314
  **REGRA:** depois de wait_response com `options` OU `varied_options`, NUNCA coloque node `condition` pra ramificar — ligue os edges direto nos handles `option_X` (em `varied_options`, `X` é o `id` do grupo).
278
315
 
@@ -510,7 +547,9 @@ headers e sem body (ou erra "URL not configured" se faltar `apiUrl`).
510
547
 
511
548
  **Nomes de campo (exatos):** `apiMethod` (default `GET`), `apiUrl` (obrigatório), `apiHeaders`
512
549
  (array de `{key,value}`), `apiBody` (string; ou `apiBodyMode:"fields"` + `apiBodyFields:[{key,value}]`),
513
- `apiQueryParams`, `apiAuthType`/`apiAuthToken`, `apiTimeout`, `apiResponseVar` (nome da variável de saída).
550
+ `apiQueryParams`, `apiAuthType`/`apiAuthToken`, `apiTimeout`, `apiResponseVar` (nome da variável de saída;
551
+ desde 20/08/2026 nome iniciado por `_` cai no padrão `api_response` — `_` é o prefixo reservado das
552
+ chaves internas da sessão).
514
553
 
515
554
  **Variáveis de conta / segredos:** use `{{account.custom_attribute.NOME}}` — NÃO existe `{{env.X}}`
516
555
  (resolve vazio → 401). Variáveis do tipo *secret* SÓ resolvem dentro do nó `api` (nos campos de
@@ -565,15 +604,17 @@ pra não ser oferecida numa tela e faltar na outra. Também não existem `o1-min
565
604
  `gpt-5.2-pro` nem `gpt-5.5-pro` (a OpenAI recusa).
566
605
 
567
606
  **OBRIGATÓRIO via API — `aiAssistantId`:** os modos `generate`/`intent`/`sentiment`/`extract`
568
- EXIGEM um `aiAssistantId` válido (id de um assistente Captain da conta). Sem ele o nó SAI cedo
569
- (`action: continue`) sem gravar nada — a variável de saída (`aiResponseVar`/`ai_intent`/`ai_sentiment`)
570
- fica vazia e `{{ai_response}}` resolve vazio no nó seguinte. A tela do Flow Builder preenche esse id
571
- pelo dropdown; um nó criado via API/MCP SEM `aiAssistantId` "não funciona" por isso. (Exceção: um modo
572
- `generate` sem assistente cai no LLM cru da conta — mas o caminho recomendado é sempre informar
573
- `aiAssistantId`.) A saída fica em `aiResponseVar` (default `ai_response`); `intent` também em
574
- `ai_intent`, `sentiment` em `ai_sentiment`.
575
-
576
- **Contexto da conversa:** campo `contextMessages` define quantas mensagens recentes a IA enxerga — valores válidos `25`, `50`, `75`, `100` (ampliado em 2026-06; antes o teto era ~20). Os modos `intent`/`sentiment`/`extract` rodam no motor contido (texto puro, sem persona nem ferramentas — mais barato e sem risco de vazamento); `generate` usa o assistente Captain.
607
+ EXIGEM um `aiAssistantId` válido (id de um assistente Captain da conta). **Sem ele o nó sai pela
608
+ saída `error` desde 20/08/2026** (`AI Assistant not configured`), igual a assistente inexistente na
609
+ conta — antes ficava VERDE e seguia como se a IA tivesse respondido, com a variável de saída
610
+ (`aiResponseVar`/`ai_intent`/`ai_sentiment`) vazia. A tela do Flow Builder preenche esse id pelo
611
+ dropdown; um nó criado via API/MCP SEM `aiAssistantId` "não funciona" por isso. Só rodam sem
612
+ assistente: `aiMode: 'custom'` (motor cru com a chave da conta + `aiModel`) e flow `ai_tool` em
613
+ `generate`/`custom`. A saída fica em `aiResponseVar` (default `ai_response`); `intent` também em
614
+ `ai_intent`, `sentiment` em `ai_sentiment`. Nome de variável iniciado por `_` é IGNORADO e cai no
615
+ padrão (prefixo reservado das chaves internas da sessão) — vale pro `apiResponseVar` também.
616
+
617
+ **Contexto da conversa:** a chave é **`aiContextMessages`** — quantas mensagens da conversa a IA enxerga: `0`, `1`, `3`, `5`, `10`, `25`, `50`, `75` ou `100` (default `5`; acima de 100 é cortado; ignorado em `custom`). **`contextMessages` (sem o prefixo) é DESCARTADA em silêncio** (fica 5). Os modos `intent`/`sentiment`/`extract` rodam no motor contido (texto puro, sem persona nem ferramentas — mais barato e sem risco de vazamento); `generate` usa o assistente Captain.
577
618
 
578
619
  **Intent — campo EXATO:** `aiIntents` é um ARRAY DE OBJETOS `{ "name": "..." }`. NÃO use `aiIntentOptions` (array de strings) — é ignorado. A intenção classificada também fica disponível na variável de sessão **`ai_intent`** (use como `{{ai_intent}}` adiante).
579
620
 
@@ -674,6 +715,10 @@ O campo raiz é **`waitMode`**: `"duration"` (default), `"date"` ou `"weekday"`.
674
715
  }
675
716
  ```
676
717
 
718
+ **Nome da variável (desde 20/08/2026):** `name` vazio ou iniciado por `_` é IGNORADO — `_` é o prefixo
719
+ reservado das chaves internas da sessão. A mesma regra vale pro `saveVariable` do `wait_response`
720
+ (não grava) e pro `apiResponseVar`/`aiResponseVar` (caem no padrão). `value` aceita `{{ }}`.
721
+
677
722
  **Handles que SAEM:** `success`.
678
723
 
679
724
  ### 2.10 `randomizer`
@@ -700,7 +745,9 @@ Corrigido em 30/07: o motor lê `percent` e aceita `weight` só como dado antigo
700
745
  `percent`** — randomizador criado com `weight` até roda, mas abre com as porcentagens EM BRANCO na
701
746
  tela do cliente (a tela lê só `percent`).
702
747
 
703
- **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.
748
+ **Divisão EXATA, não sorteio (desde 20/08/2026):** nos DOIS modos o bloco divide o que passa por ele na proporção configurada — 50/50 alterna A,B,A,B; 70/30 dá 7 de cada 10 intercalados. Contador por fluxo+bloco; mexer em agente/porcentagem zera a contagem. (Até 20/08 era cara-ou-coroa — 5 leads seguidos pra mesma pessoa num "50/50" era normal.)
749
+
750
+ **Modo `distribute_agents` (divisão PONDERADA):** `data.mode: 'distribute_agents'` + `data.agents: [{ agent_id, percent }]` — escolhe o agente da vez (percentuais somando 100) e atribui automaticamente. Diferença pra AÇÃO `distribute_agents` do node `action` (seção 2.5): a ação é rodízio SIMPLES (1,2,3 sem porcentagem); este modo aceita pesos diferentes (70/30). Com pesos iguais os dois dão o mesmo resultado.
704
751
 
705
752
  **Handles que SAEM:** o `id` de cada branch (`A`, `B`, ...). Em `mode: 'distribute_agents'` é `success`.
706
753
 
@@ -716,19 +763,85 @@ juntos) — a operação vai em `data.groupOperation`. São **17 operações**,
716
763
  - **Admins:** `promote_admin` (promover a admin), `demote_admin` (rebaixar).
717
764
  - **Convite:** `get_invite` (pegar link/código de convite), `revoke_invite` (revogar), `send_invite` (mandar
718
765
  o convite por mensagem a um telefone).
766
+ - **Mensagem:** `send_message` (a 17ª, 08/08) — manda `messageItems` (MESMO contrato do bloco de mensagem:
767
+ text/delay/attachment/audio/url_media; botões/template não fazem sentido em grupo) NA CONVERSA DO GRUPO
768
+ (`groupTargetId` ou, vazio, o grupo da conversa atual; grupo inexistente = `error`). NÃO grava
769
+ `{{grupo.*}}` — é a única sem variável de resposta. Serve pra "dei ganho no lead → aviso o grupo da
770
+ equipe" em fluxo de qualquer canal.
719
771
 
720
772
  **Disponível em fluxo de QUALQUER canal**, desde que a conta tenha uma caixa **WhatsApp QR Code
721
773
  (`Channel::Waha`)** — informe a caixa em `data.groupInboxId`. Esse campo é **OBRIGATÓRIO em fluxo não-grupo**
722
- (individual/qualquer canal) e **opcional em fluxo de grupo** (ali o padrão é o grupo da própria conversa).
774
+ (individual/qualquer canal; o save recusa caixa de outro tipo) e **opcional em fluxo de grupo** (ali o padrão
775
+ é o grupo da própria conversa). `groupOperation` ausente/vazio = `legacy` (comportamento antigo — NÃO usar em
776
+ node novo). `groupTargetId` aceita `"1203...@g.us"`, só dígitos ou `{{var}}`; é ignorado nas operações sem
777
+ alvo (`create`, `find_by_name`).
723
778
 
724
779
  Cada operação guarda o resultado na variável `data.groupResponseVar` (padrão `"grupo"`) — leia depois com
725
- `{{grupo.CAMPO}}` (ex.: `{{grupo.id}}`, `{{grupo.participants_count}}`, `{{grupo.invite_link}}`).
726
-
727
- Teto de **20 participantes por execução** (trava anti-banimento — adicionar em lote é o que mais rápido
728
- derruba número no WhatsApp não-oficial). Detalhe das chaves de `data` por operação: ver o pattern
729
- "Gestão de Grupos WhatsApp por fluxo" no `flowbuilder-patterns`.
730
-
731
- **Handles que SAEM:** `success`, `error`.
780
+ `{{grupo.CAMPO}}` (ex.: `{{grupo.id}}`, `{{grupo.participants_count}}`, `{{grupo.invite_link}}`). Toda
781
+ operação (menos `send_message`) também devolve `{{grupo.ok}}` (bool) e `{{grupo.error.message}}`.
782
+
783
+ **Campos que cada operação devolve em `{{grupo.*}}`:**
784
+ - `create` → `id`, `name`, `description`, `picture`, `participants`, `participants_count`, `conversation_id`
785
+ (número da conversa do grupo aberta no painel, 08/08), `added`, `added_count`, `not_added`,
786
+ `not_added_count`, `no_whatsapp`, `invited`, `invite_status`, `extras_failed` (só quando descrição/foto/
787
+ admin/conversa falharam — o grupo JÁ existe e isso NUNCA vira erro). Aceita `groupAttributes` (08/08:
788
+ linhas `{attr_source, attr_key, attr_value}` — grava atributos personalizados no contato-grupo
789
+ recém-criado; `{{grupo.id}}`/`{{grupo.conversation_id}}` já valem em `attr_value`; campos nativos
790
+ `name`/`email`/`card` são recusados).
791
+ - `find_by_id` → `id`, `name`, `description`, `picture`, `participants`, `participants_count`.
792
+ `find_by_name` (`groupSearchName` obrig + `groupSearchMode`: `contains` padrão | `does_not_contain` |
793
+ `equal_to` | `not_equal_to` | `starts_with` | `ends_with`) → `results`, `results_count`.
794
+ - `update_subject` (`updateSubject`) → `id`, `name`; `update_description` (`updateDescription`) → `id`,
795
+ `description`; `update_picture` (`updatePicture`, url) → `id`; `leave` → `id` (IRREVERSÍVEL).
796
+ - `list_participants` → `id`, `participants` (até 100; `participants_truncated: true` se cortou),
797
+ `participants_count`.
798
+ - `add_participants` (`groupParticipants` obrig — array de telefones/JIDs ou lista por vírgula; aceita
799
+ `{{var}}`) → `id`, `added` (quem está no grupo agora, inclui quem já estava), `not_added`, `added_count`,
800
+ `not_added_count`, `no_whatsapp`, `invited`, `invite_status`. Ninguém entrou = `error` (`nobody_changed`);
801
+ parte entrou = `partial`.
802
+ - `remove_participants` → `id`, `removed`, `not_removed`, `removed_count`, `not_removed_count` (ninguém saiu
803
+ = `error`; parte = `partial`). `promote_admin` / `demote_admin` → `id`, `participants`.
804
+ - `get_invite` → `id`, `invite_link`; `revoke_invite` → `id`, `invite_link` (IRREVERSÍVEL — invalida o link
805
+ antigo); `send_invite` (`groupInviteTo` obrig, `groupInviteMessage` opcional — `{{link}}` marca onde o
806
+ link entra, sem o marcador vai no fim) → `id`, `invite_link`, `invite_sent_to`.
807
+ - `settings` (pelo menos UMA de `infoAdminOnly`, `messagesAdminOnly`, `membersCanAddNewMember`) → `id`,
808
+ `settings_updated`, `settings_failed`, `settings_unsupported`. **Desde 20/08 cada permissão é aplicada
809
+ SOZINHA:** `settings_updated[]` sempre vem; `settings_failed[{setting, reason}]` = falhou de verdade;
810
+ `settings_unsupported[]` = o servidor WAHA não tem o recurso (ex.: quem-pode-adicionar em servidor
811
+ antigo). Sucesso SÓ se aplicou ao menos uma E nenhuma falhou; senão sai por `error` (código
812
+ `settings_failed` ou `settings_unsupported`) com o que aplicou visível na variável. ATENÇÃO:
813
+ `membersCanAddNewMember` tem semântica INVERTIDA (`true` = TODOS podem adicionar);
814
+ `infoAdminOnly`/`messagesAdminOnly` seguem o padrão (`true` = SÓ admin).
815
+
816
+ Teto de **20 participantes por execução** (`create`/`add`/`remove`/`promote`/`demote` — trava anti-banimento:
817
+ adicionar em lote é o que mais rápido derruba número no WhatsApp não-oficial). Detalhe das chaves de `data`
818
+ por operação: ver o pattern "Gestão de Grupos WhatsApp por fluxo" no `flowbuilder-patterns`.
819
+
820
+ **`create` confere quem foi COLOCADO (2026-08-21 — entra com o próximo deploy do app depois de
821
+ 21/08/2026):** o bloco relê o grupo e compara com o pedido (por equivalência brasileira, com e sem o 9;
822
+ duas leituras concordantes). Quem o WhatsApp não colocou (privacidade "quem pode me adicionar" restrita,
823
+ número inexistente) vai pra `{{grupo.not_added}}` / `{{grupo.not_added_count}}` e a saída vira `partial`;
824
+ `{{grupo.no_whatsapp}}` lista os números que o WhatsApp disse não existir (seguem no pedido — é aviso, não
825
+ erro). Número de celular BR escrito sem o 9 é corrigido pelo WhatsApp antes de adicionar (check-exists na
826
+ sessão do canal, orçamento 8 s). Antes de 21/08 o passo dava SUCESSO mesmo com o lead fora do grupo. A
827
+ mesma correção do 9º dígito vale no `add_participants`.
828
+
829
+ **Convite no privado de quem ficou de fora (opcional, nasce desligado; só `create`/`add_participants`;
830
+ 2026-08-21 — entra com o próximo deploy do app depois de 21/08/2026):** `groupInviteOnFailure: true` +
831
+ `groupInviteMessage` (texto OBRIGATÓRIO nesse modo — sem texto = `invite_status: 'sem_mensagem'` e nada
832
+ sai; link nu de número desconhecido é o formato que mais gera denúncia e derruba número; `{{link}}` marca
833
+ onde o link do grupo entra — sem o marcador, o link vai no fim). Tetos anti-bloqueio: 5 convites por
834
+ execução, 1 por telefone por caixa por dia, 50 por dia por caixa, 20 s entre convites; quem está em
835
+ `no_whatsapp` não recebe; antes de mandar o job reconfere se a pessoa já entrou. `{{grupo.invited}}` lista
836
+ quem recebeu e `{{grupo.invite_status}}` diz `convite_enviado` ou `sem_mensagem`. **Cada convite entregue
837
+ cria contato + conversa no painel** (e dispara os ouvintes de `conversation_created`: automação, outros
838
+ flows, webhook do cliente, Kanban) — avise o cliente antes de ligar.
839
+
840
+ **Handles que SAEM:** `success`, `error` — e **`partial`** nas operações de participante
841
+ (`create`, `add_participants`, `remove_participants`): a operação rodou, mas nem todo mundo
842
+ entrou/saiu (as listas `not_added`/`not_removed` dizem quem). Ligue `partial` num caminho próprio (ex.:
843
+ avisar a equipe) — sem fio, `partial` segue pelo fio `success` (única saída com esse fallback no motor) e
844
+ quem ficou de fora passa em silêncio.
732
845
 
733
846
  ### 2.12 `activate_flow` (LEGADO — prefira action `start_flow`)
734
847
 
@@ -881,9 +994,9 @@ Sempre presente:
881
994
 
882
995
  | Variável | Conteúdo |
883
996
  |---|---|
884
- | `{{trigger.type}}` | chave do evento (`message_created`, `conversation_created`, `label_added`, `kanban_item_stage_changed`, `webhook_received`, `date_trigger`, `flow_campaign`, `manual`, `pagetrack_visited`…) |
997
+ | `{{trigger.type}}` | código do **EVENTO** que disparou — **NÃO é a chave do item do Início**. Valores reais: `message_created` (gatilho `message_received`), `message_sent`, `conversation_created`, `conversation_opened` (gatilho `conversation_reopened`), `conversation_resolved`, `label_added`, `label_removed`, `team_changed`, `assignee_changed`, `sla_missed`, `kanban_item_created`/`card_created`, `kanban_item_stage_changed`/`card_moved`, `card_status_changed` (`card_won` e `card_lost` — olhe `trigger.kind` ou o status do card), `conversation_attributes_changed`, `card_attributes_changed`, `contact_attribute_changed`, `group_participant_joined`/`_left`, `webhook_received`, `date_trigger`, `manual` (sidebar), `kanban_manual` (botão do card), `campaign` (Campanha de Fluxo), `page_track`, `activated_by_flow` (fluxo chamou fluxo), `lead_form` (os 3 gatilhos de formulário — `trigger.kind` diz `completed`/`milestone`/`abandoned`), `booking_created`/`booking_cancelled`/`booking_rescheduled`/`booking_completed`. Compare SEMPRE com esses códigos numa condição |
885
998
  | `{{trigger.name}}` | rótulo legível do MESMO evento ("Mensagem recebida", "Card mudou de etapa") — use este pra ESCREVER pro cliente, e o `type` pra COMPARAR em condição |
886
- | `{{trigger.activated_at}}` | quando disparou (ISO 8601) |
999
+ | `{{trigger.activated_at}}` | quando disparou (ISO 8601). Desde 20/08/2026 `name` e `activated_at` são preenchidos em TODOS os caminhos de disparo (antes só no de evento de conversa) |
887
1000
  | `{{trigger.kanban_item_id}}` / `{{trigger.funnel_id}}` | só quando veio do Kanban |
888
1001
 
889
1002
  Presentes conforme o evento (ausente = resolve vazio, sem erro):
@@ -895,11 +1008,13 @@ Presentes conforme o evento (ausente = resolve vazio, sem erro):
895
1008
  | `{{trigger.contact.*}}` | `id`, `name`, `phone` |
896
1009
  | `{{trigger.ad.*}}` (anúncio/CTWA) | `id`, `name`, `creative_id`, `creative_name`, `adset_id`, `adset_name`, `campaign_id`, `campaign_name`, `source`, `headline`, `description`, `cta`, `url` |
897
1010
  | `{{trigger.attribute.*}}` | `name`, `previous_value`, `current_value`, `changed_at` — o valor ANTES e DEPOIS do atributo que disparou (só nos gatilhos de mudança de atributo) |
898
- | `{{trigger.user_name}}` | nome do usuário/atendente que disparou (gatilho manual ou ativação manual) |
899
- | `{{trigger.campaign_title}}` | título da campanha (quando disparado por Campanha de Fluxo) |
1011
+ | `{{trigger.user_name}}` | nome do usuário/atendente que disparou (gatilho manual ou botão do card) |
1012
+ | `{{trigger.campaign_id}}` / `{{trigger.campaign_title}}` | id e título da campanha (quando disparado por Campanha de Fluxo) |
900
1013
  | `{{trigger.event_name}}` | nome do evento do site que disparou (LionTrack / visita de página) |
901
1014
  | `{{trigger.page_url}}` | URL da página (LionTrack / visita de página) |
902
- | `{{trigger.source_flow_name}}` | nome do flow de origem (quando outro flow iniciou este, ex.: action `start_flow`) |
1015
+ | `{{trigger.source_flow_id}}` / `{{trigger.source_flow_name}}` | id e nome do flow de origem (quando outro flow iniciou este, ex.: action `start_flow`) |
1016
+ | `{{trigger.lead_form_id}}` / `{{trigger.response_id}}` / `{{trigger.kind}}` | formulário público: qual formulário, qual preenchimento e o tipo (`completed`/`milestone`/`abandoned`) |
1017
+ | `{{trigger.kind}}` / `{{trigger.booking_id}}` / `{{trigger.event_type_id}}` | agendamento (Booking nativo): `created`/`cancelled`/`rescheduled`/`completed`, id da reserva e id do tipo |
903
1018
 
904
1019
  Os cinco últimos são **novos 2026-08-07** e passam a aparecer no autocomplete de TODO bloco (ausente para
905
1020
  o gatilho que não os fornece = resolve vazio, sem erro).
@@ -913,9 +1028,20 @@ sai em JSON; valor simples sai cru.
913
1028
  serve pra perguntar "o que veio COM o gatilho", não pra ser um segundo nome do mesmo dado.
914
1029
 
915
1030
  **Variáveis do agente de IA `{{ai_agent.*}}` (novo 2026-08-01):** `{{ai_agent.name}}` e
916
- `{{ai_agent.id}}` trazem o AI Agente atribuído à conversa (vazio se não houver). Prefixo `ai_agent` de
1031
+ `{{ai_agent.id}}` trazem o AI Agente atribuído à conversa (vazio se não houver) — refletem a
1032
+ ATRIBUIÇÃO, não "vai responder agora"; desde 20/08/2026 resolvem também no Liquid. Prefixo `ai_agent` de
917
1033
  propósito — `agent` sozinho é o atendente HUMANO, e confundir os dois seria pior que não ter.
918
1034
 
1035
+ **Outras variáveis padrão que resolvem no caminho direto:** `{{contact.id}}`, `{{contact.identifier}}`,
1036
+ `{{contact.first_name}}`, `{{contact.last_name}}`, `{{contact.label}}` (etiquetas do contato, separadas por
1037
+ vírgula), `{{conversation.team.name}}` (nome da equipe; `{{conversation.team}}` é apelido).
1038
+ `{{last_response}}` = última resposta do cliente; `{{last_agent_response}}` = última mensagem PÚBLICA nossa
1039
+ (nota interna NÃO conta, desde 20/08/2026).
1040
+
1041
+ **Filtro Liquid (desde 20/08/2026):** `{{conversation.id}}`, `{{conversation.status}}` e
1042
+ `{{conversation.team_id}}` respondem o MESMO com e sem filtro (`|`) — antes um filtro em qualquer variável do
1043
+ texto trocava o protocolo pelo id interno do banco.
1044
+
919
1045
  ---
920
1046
 
921
1047
  ## 2-D. Exit conditions (saída automática do flow)
@@ -990,8 +1116,18 @@ tiver um gatilho do MESMO tipo que colide. Resposta: **HTTP 422** `{ "error_code
990
1116
  - `option_<x>` → um por opção em `acceptedOptions`; idem.
991
1117
  - `cond_0..N` → um por condição no array; se tirar uma condição, o `cond_N` do fim some.
992
1118
  - `intent_<name>` → um por intent no `ai` mode intent.
1119
+ - `partial` → só em `update_group` com operação `create`/`add_participants`/`remove_participants`.
993
1120
  Ligar edge num handle condicional inexistente = **aresta fantasma** (sai do nada no canvas, não roteia).
994
1121
 
1122
+ **Fio com rótulo só é seguido por quem casa com o rótulo (desde 20/08/2026).** Não existe mais
1123
+ "pega o primeiro fio que existir": `option_A` não entrega quem respondeu B, `window_closed` não
1124
+ pega envio bem-sucedido, `timeout` não pega resposta válida. O único último recurso é a ligação
1125
+ SEM `sourceHandle` (fluxo legado). Sem fio elegível, o flow TERMINA ali — nunca segue o ramo errado.
1126
+ Consequência prática: ligue TODAS as saídas que o cliente espera que aconteçam; saída sem fio é
1127
+ fim de fluxo, não "cai no próximo". (Duas pontes explícitas continuam: `option_X`/`button_X`/
1128
+ `no_response` sem fio próprio caem no `success` do `wait_response` se ele existir; `partial` do
1129
+ `update_group` sem fio cai no `success`.)
1130
+
995
1131
  ---
996
1132
 
997
1133
  ## 4. Layout e positioning
@@ -1070,7 +1206,13 @@ Nodes nunca devem ficar com a mesma coordenada `(x, y)`. Se dois nodes têm posi
1070
1206
  | `method`/`url`/`headers`/`body` no node api | Runtime NÃO lê → GET vazio (ou erro "URL not configured") | `apiMethod`/`apiUrl`/`apiHeaders`/`apiBody` |
1071
1207
  | `{{env.X}}` no node api | Não existe → resolve vazio → 401 | `{{account.custom_attribute.X}}` (secret resolve só no node api) |
1072
1208
  | `{{api_response.payload.campo}}` | `.payload` não existe → vazio | `{{api_response.campo}}` (o corpo fica direto sob a var); status em `{{api_response_status}}` |
1073
- | node `ai` via API sem `aiAssistantId` | Sai cedo, variável de saída vazia | Informe `aiAssistantId` (obrigatório em generate/intent/sentiment/extract) |
1209
+ | node `ai` via API sem `aiAssistantId` | Sai pela saída `error` (desde 20/08; antes ficava verde com a variável vazia) | Informe `aiAssistantId` (obrigatório em generate/intent/sentiment/extract) |
1210
+ | `contextMessages` no node `ai` | Chave errada → descartada em silêncio (fica 5) | `aiContextMessages` (0, 1, 3, 5, 10, 25, 50, 75, 100) |
1211
+ | Nome de variável começando com `_` (`set_variable`, `saveVariable`, `apiResponseVar`, `aiResponseVar`) | Prefixo reservado → ignorado / cai no padrão | Nome sem `_` na frente |
1212
+ | `{ type: "delay", seconds: 2 }` no send_message | A tela não lê `seconds` → abre como "undefineds" | `duration_seconds` (0-30) |
1213
+ | Caixa oficial sem fio em `window_closed` | Janela de 24h fechada vira erro | Ligue `window_closed` num bloco com template |
1214
+ | Saída com rótulo sem fio (`button_X`, `option_X`, `cond_N`, `timeout`, `partial`) | Desde 20/08 não "cai no próximo": o flow TERMINA ali; espera com timeout sem fio ENCERRA ao estourar | Ligue TODA saída possível |
1215
+ | `update_group` `create`/`add_participants` sem fio em `partial` | Quem ficou de fora passa em silêncio (cai no `success`) | Ligue `partial` e leia `{{grupo.not_added}}`; convite automático exige `groupInviteMessage` |
1074
1216
  | `funnel_stages: ["37:Nome Exibido"]` | Usa o NOME da etapa, não a chave interna → gatilho nunca dispara | `"37:chave_interna"` (slug/UUID da etapa) |
1075
1217
  | `funnel_ids: [37]` (número) no gatilho card | String esperada → `[37].include?("37")` falso → não dispara | `funnel_ids: ["37"]` |
1076
1218
  | `kanban_in_stage` com `value: "37:etapa"` | Formato errado → cai no default silenciosamente | `funnel_id` separado + `value: "etapa"` (slug puro) |
@@ -1089,7 +1231,8 @@ Antes de chamar `flows_create` ou `flows_update`, valide mentalmente:
1089
1231
  - [ ] Nenhum par de nodes tem a mesma posição `(x, y)`
1090
1232
  - [ ] Todo node tem `data` com campos do schema do tipo
1091
1233
  - [ ] Todo node não-start tem pelo menos 1 edge chegando (`edge.target = node.id`)
1092
- - [ ] Todo `edge.sourceHandle` é um handle que o node source EXPÕE NAQUELA config (sem handle condicional inexistente: `no_reply_timeout` só com timeout, `window_closed` só API oficial, `button_<value>`/`option_<x>`/`cond_N`/`intent_<name>` só se o botão/opção/condição/intent existir)
1234
+ - [ ] Todo `edge.sourceHandle` é um handle que o node source EXPÕE NAQUELA config (sem handle condicional inexistente: `no_reply_timeout` só com timeout, `window_closed` só API oficial, `button_<value>`/`option_<x>`/`cond_N`/`intent_<name>` só se o botão/opção/condição/intent existir, `partial` só em `update_group` de participantes)
1235
+ - [ ] TODA saída que pode acontecer tem fio (desde 20/08 saída sem fio é fim de fluxo, não "cai no próximo"): cada `button_X`/`option_X`/`cond_N`, `default`, `timeout` de toda espera com `waitTime` (sem fio, a sessão ENCERRA ao estourar), `window_closed` em caixa oficial, `partial` no `update_group`
1093
1236
  - [ ] Todo edge tem `source`, `target` e `sourceHandle`
1094
1237
  - [ ] Todo `sourceHandle` é um handle real exposto pelo node source (seção 2)
1095
1238
  - [ ] `channel_type` é classe Rails (`Channel::Waha` etc)