wacrm/src/lib/flows
Arnas Donauskas d0b50f4e61
feat(flows): runner engine + webhook integration + agent-handoff + cron sweep (PR 2/4)
Brings the Flows feature alive end-to-end for beta-opted-in accounts.
Still no UI — flows are defined via direct SQL inserts in v1; the
no-code builder lands in PR 3. Everything in this PR is gated behind
`isFlowsEnabled(profile)` so non-beta accounts behave identically to
before.

What landed:

  1. `src/lib/flows/types.ts` — DB row + discriminated-union node
     config types, plus the `ParsedInbound` shape and a
     `DEFAULT_FALLBACK_POLICY`.

  2. `src/lib/flows/fallback.ts` — pure policy resolver. Decides
     whether an unknown reply triggers a reprompt (up to N times),
     a handoff, or end. 11 unit tests.

  3. `src/lib/flows/engine.ts` — `dispatchInboundToFlows()` entry
     point. Loads the active run (if any), checks idempotency via
     `flow_run_events`, advances the run through a synchronous
     auto-advance loop that suspends only at nodes that need
     customer input. Each suspending node calls
     `engineSendInteractive{Buttons,List}` and persists the new
     current_node_key via an optimistic UPDATE with a precondition
     on the OLD key — concurrent inbounds collide cleanly.
     11 unit tests for the pure-logic helpers.

  4. `src/app/api/whatsapp/webhook/route.ts` — invokes the runner
     between the broadcast-reply flag and the automation dispatch.
     When the runner consumes the inbound (started a flow, advanced
     one, applied a fallback), suppresses `new_message_received`
     and `keyword_match` automation triggers — the customer is
     navigating the bot menu, not throwing a keyword. Relationship
     triggers (new_contact_created, first_inbound_message) still
     fire regardless.

  5. `src/app/api/whatsapp/send/route.ts` — after a successful
     agent message, marks any active flow run for that contact as
     `paused_by_agent`. Best-effort: a write failure is logged but
     doesn't block the send response.

  6. `src/app/api/flows/cron/route.ts` — sweeps active runs whose
     `last_advanced_at` is older than the flow's
     `fallback_policy.on_timeout_hours` (default 24h). Marks them
     `timed_out` and writes a `flow_run_events.timeout` row. Re-uses
     `AUTOMATION_CRON_SECRET` so operators don't have to provision
     a second secret.

Test surface in this PR:
  - 142/142 tests pass (added 22 new unit tests across fallback +
    engine pure logic)
  - typecheck clean
  - 0 new lint errors
  - build clean

Manual end-to-end verification arrives via the SQL fixture in the
PR description (a 3-node Welcome → buttons → handoff flow).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 13:36:39 +03:00
..
admin-client.ts feat(flows): foundation for interactive WhatsApp messages (PR 1/4) 2026-05-22 13:12:27 +03:00
engine.test.ts feat(flows): runner engine + webhook integration + agent-handoff + cron sweep (PR 2/4) 2026-05-22 13:36:39 +03:00
engine.ts feat(flows): runner engine + webhook integration + agent-handoff + cron sweep (PR 2/4) 2026-05-22 13:36:39 +03:00
fallback.test.ts feat(flows): runner engine + webhook integration + agent-handoff + cron sweep (PR 2/4) 2026-05-22 13:36:39 +03:00
fallback.ts feat(flows): runner engine + webhook integration + agent-handoff + cron sweep (PR 2/4) 2026-05-22 13:36:39 +03:00
feature-flag.test.ts feat(beta): per-account beta_features flag on profiles, gating Flows 2026-05-22 12:57:33 +03:00
feature-flag.ts feat(beta): per-account beta_features flag on profiles, gating Flows 2026-05-22 12:57:33 +03:00
meta-send.ts feat(flows): foundation for interactive WhatsApp messages (PR 1/4) 2026-05-22 13:12:27 +03:00
types.ts feat(flows): runner engine + webhook integration + agent-handoff + cron sweep (PR 2/4) 2026-05-22 13:36:39 +03:00