7c10c249ce
`_retrigger_typing` awaited `sendChatAction` inline on the send path, and streaming re-arms after *every* intermediate send. `sendChatAction` is a fire-and-forget UI hint whose result nobody reads, but awaiting it ran its TLS round-trip on the same event loop as the `getUpdates` long-polls. With several agents streaming concurrently the loop stayed pinned, the long-polls were never serviced, and they decayed into CLOSE-WAIT while the adapter still reported `connected` — a gateway that is deaf but healthy, which `Restart=always` cannot recover because the process never exits. py-spy put 20 of 20 MainThread samples in `send_typing` -> `send_chat_action` -> `start_tls`. The handshakes are what cost: with `max_keepalive_connections=4`, a re-arm per chunk churns the pool so most calls pay a fresh TLS handshake on the loop thread. Three changes, all in the re-arm path: - Schedule the re-arm as a tracked task rather than awaiting it, so a round-trip never delays a send or a poll. It joins `_background_tasks`, so shutdown cancels it and it cannot outlive the adapter. - One in-flight re-arm per chat, and at most one per `typing_retrigger_min_interval_seconds` (default 2s, `extra` knob; 0 restores a call per send). Telegram's bubble lasts ~5s and `_keep_typing` already refreshes every 2s, so the re-arm only has to cover the gap left by a landed message. - Honour `typing_indicator: false`. Only `_keep_typing` consulted it, so the documented workaround still paid for a `sendChatAction` on every intermediate send. Simulating 200 streamed chunks with a 10ms loop-blocking handshake: 200 `sendChatAction` calls and 2037ms of send-path time before, 1 call and 13ms after. Fixes #111727 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>