* fix: cut the content-stall dead-wait and auto-retry a mid-tool-call stall
Root-caused (via a verified multi-agent audit) the chatgpt/gpt-5.5 "stuck"
reports: the ChatGPT Codex backend pauses real generation mid-stream on
large single tool-call outputs (a ~1000-line write_file) while continuing
SSE keep-alive heartbeats. That is an upstream limitation, not a Zero bug
or a connection-pooling issue (the darwin DisableKeepAlives fix is applied
and confirmed not the cause). Zero already detected it, but two UX gaps
made it feel like a hang:
1. Faster abort. The content-stall watchdog fired at idle*2 = 10 minutes.
A heartbeat-pause stall on gpt-5.x rarely recovers, so 10 minutes of a
frozen token counter was a terrible wait for a doomed turn. Replaced the
StreamContentStallFactor=2 const with ContentStallTimeout(idle)=idle*1.2
(6 minutes at the default). It still only ever fires on a TRUE zero-output
freeze — any real data line resets it — so healthy slow-reasoning runs are
unaffected. Scales with ZERO_STREAM_IDLE_TIMEOUT; returns 0 (disabled)
when idle is disabled.
2. Auto-retry the actual stall. The existing stall-retry only fired when
NOTHING had been forwarded — so the common case (model streamed transient
reasoning + began the big write_file, then froze mid-arguments) was NOT
retried, because the incomplete tool call is collected and transient
output was forwarded. Relaxed the gate to retry when no visible PROSE was
shown (forwardedVisibleText, marked only by OnText) and no final text was
collected — dropping the len(ToolCalls)==0 exclusion, since an incomplete
tool call from a timed-out stream is never executed (the turn returns on
the error before dispatch) and never appended to messages, so the retry
re-sends clean context with no duplication. A visible
"[no output — model stalled; retrying N/max]" notice fires per attempt.
Forwarded prose still blocks the retry (it would duplicate answer text).
Verified: full -race suite green; a 4-dimension root-cause audit and a
3-dimension adversarial review of this diff both passed with no confirmed
defects. New tests: TestRunRetriesStallAfterIncompleteToolCall (the exact
gpt-5.x case), TestRunRetriesStallAfterOnlyReasoning; preserved
TestRunDoesNotRetryStallAfterPartialOutput (prose still blocks retry).
* fix(providerio): make ContentStallTimeout overflow-safe
Address PR #362 review: idleTimeout*6/5 could overflow int64 for a
pathologically large ZERO_STREAM_IDLE_TIMEOUT and wrap to a NEGATIVE
duration, which would arm the content-stall timer to fire immediately and
abort every stream. Compute 1.2× as idle + idle/5 with a clamp to the max
duration on overflow (which just means "effectively no content watchdog",
the sane result for an absurd idle timeout). The normal 1.2× values are
unchanged (5m->6m, 30s->36s, 100ms->120ms).
New test: TestContentStallTimeout covers the scaled values, the
zero/disabled case, and the MaxInt64 overflow guard.