cac9db7caf
When a watchdog (TTFB / stream-idle / stale-call) force-closes a Codex Responses request, the worker thread can still be draining SSE frames. `_consume_codex_event_stream` returns `status=terminal_status`, which defaults to `"completed"`, and its only truncation guard is `if not saw_terminal and not output`. A mid-stream kill leaves `saw_terminal=False` but `output`/text non-empty, so the partial text came back as a `finish_reason=stop` response and got persisted as a finished assistant turn — a long reply just stops mid-sentence with no error surfaced. Observed as a long generation dying at `1. Create (6/6)` and never emitting its end marker, with the truncated text already stored in state.db. Fix: publish a per-request retirement token so the worker can tell it has been retired. - `agent/chat_completion_helpers.py`: `interruptible_api_call` installs `agent._active_codex_stream_request_token` before handing off to the worker (codex_responses only) and clears it at all four kill sites plus the worker's own `finally`. Retirement is cleared BEFORE `_close_request_client_once`, which can raise — every other call site wraps it in try/except, and a leaked token would let a later worker mistake itself for the owning attempt. The request-local `_codex_request_retired` mirror also swallows the transport error our own force-close causes, so the worker's local error cannot replace the watchdog's retryable TimeoutError (same split as `_request_cancelled`). - `agent/codex_runtime.py`: `run_codex_stream` captures the token and raises `TimeoutError` from `interrupt_check` when it no longer owns the request — raising rather than breaking, because a break returns the partial `final`. The four stream callbacks also drop post-retirement frames so an abandoned attempt cannot stream tokens into the live turn's bubble (the gateway caches AIAgent instances per session). `TimeoutError` is not an httpx / ConnectionError / RuntimeError subclass, so it passes through the four `except` clauses around the consume call untouched. No token installed (auxiliary callers such as `handle_max_iterations` drive `_run_codex_stream` directly) means every check passes — behavior unchanged. Tests: 5 new cases. Retirement raises instead of returning partial output; post-retirement deltas stop reaching callbacks; the no-token path keeps its existing terminal-frame tolerance; the watchdog installs and clears the token; non-codex api_modes install nothing. A `_LazyCreateStream` helper is needed because `_FakeCreateStream` materializes events in __init__, which would run the retirement side effect before consumption starts.