54aad0d670
The gateway turn-inactivity watchdog (gateway/run.py::_watch_gateway_turn_inactivity) abandons a turn once seconds_since_activity exceeds the inactivity timeout (default 30 min). Activity was only stamped when a tool started and when it completed, so a tool call that runs silently for 30+ minutes (quiet builds, long pytest suites, large downloads, network waits with no output) froze the clock and the watchdog hard-abandoned a turn that was still making progress, reaping the tool's processes mid-execution (issue #84491). Add a daemon-thread heartbeat inside _run_agent_tool_execution_middleware that touches agent._touch_activity every 30s while the tool is in flight, until the call returns. Both the sequential and concurrent execution paths funnel through this single middleware, so one heartbeat covers every tool. The thread is stopped in a try/finally so it always tears down even if execute() raises, and is never started when a guardrail/authorization block short-circuits before dispatch. A genuinely hung tool remains bounded by the tool layer's own timeouts (terminal default 180s, concurrent batch deadline ~420s), so the heartbeat only extends the turn's life while the call is legitimately running. Verified by an independent reviewer (no security/logic defects); 5 unit/integration tests pass on Python 3.12 (upstream CI). The 30-min gateway backstop remains for turns whose agent loop itself stalls.