aff56bb582
Ground truth check before writing this off as too big: Handler is already a ThreadingHTTPServer (SSE-capable for free), and thinking_payload() -- one sic hertz SSH round-trip per call -- was already being hit independently by every open tab every 1.5-3s via client-side setInterval. A naive "just hold the connection open and loop the existing fetch" SSE implementation would have made that worse: N open tabs = N concurrent SSH-polling loops running forever. So: one background daemon thread polls thinking_payload() on the same 1.5s cadence the client already used, into a version-counted shared cache (_THINK_CACHE + _THINK_LOCK). GET /api/thinking (snapshot, unchanged contract) and the new GET /api/thinking/stream (SSE) both read from it -- zero additional sic calls per client, and strictly FEWER total SSH round-trips than todays per-tab polling, not just parity. Cache-update logic pulled out as _update_thinking_cache() specifically so it is unit-testable without a thread/sleep in the way (5 new tests, tests/test_bullseye_thinking.py). Client (page.py): EventSource replaces the hand-rolled setTimeout poll loop -- simpler, not just live, since EventSource reconnects on drop natively. Falls back to the old poll loop when window.EventSource is absent. 69/69 bullseye tests green (64 existing + 5 new). Live-smoke-tested standalone (snapshot + SSE endpoints both verified with curl) before wiring into the real service. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>