3aeb1736c5
Rapid distinct events on the same logical entity (five pushes to one PR, a burst of ticket edits, a flapping alert) each carry a fresh delivery ID, so the idempotency cache cannot suppress them and every event wakes a separate agent run. Roomote solved this for PR review tasks by keeping one durable review task per PR and superseding stale heads; this ports the same debounce-and-supersede pattern to the generic webhook adapter. New opt-in per-route 'coalesce' block: events group by a payload-derived key, each new event replaces the pending one and re-arms a quiet-window timer (window_seconds, default 30), bounded by max_wait_seconds (default 300) past the group's first event so a steady stream cannot starve dispatch. The settled group dispatches ONE agent run on the latest event's payload/prompt/delivery templates, with a note when earlier events were superseded. Pending groups flush on disconnect. Startup validation rejects missing keys, non-positive windows, and the deliver_only+coalesce combination. Rebase onto the decomposed webhook adapter (salvage, #92066): - Coalescing lives in a topical sibling, gateway/platforms/webhook_coalesce.py (WebhookCoalescer + validate_coalesce_config); webhook.py only wires it in (__init__, _validate_route, disconnect, _handle_webhook) and splits main's _dispatch_agent_run into the HTTP-response wrapper plus _spawn_agent_run, shared by the immediate and coalesced paths. - Review finding (unresolved key fields collapsed unrelated entities into one group): an event whose rendered key still contains a {placeholder} is now dispatched immediately instead of coalesced; documented. - Review finding (flush-on-disconnect vs process exit): disconnect() awaits the handoff of flushed runs; the docs claim is scoped to adapter disconnect and states that a hard kill loses the current window's buffer. - cron_job + coalesce is rejected like deliver_only + coalesce (cron_job landed on main after the PR branched). - Tests trimmed from 17 to 4 (validation parametrized; debounce/supersede/ independent groups/duplicate-first in one behavioural test; max-wait + unresolved-key; flush-on-disconnect).