Addresses @helix4u's review on #91493:
- conversation_loop now stamps failure_retryable (the real ClassifiedError
verdict) next to failure_reason; error_surface prefers it and only falls
back to the reason set for older results. Fallback set corrected to match
classify_api_error (auth, format_error, billing_unverified now
non-retryable).
- The descriptor carries the failing session's provider/model captured at
classification time; Copy error details prefers them over the foreground
composer atoms.
- Open logs is labeled 'Open Desktop logs' on remote/cloud connections —
the local folder holds transport logs, not the remote runtime's.
- API-exception module allowlist widened to botocore/boto3/google/grpc/
requests/aiohttp so other adapter SDKs don't misclassify as gateway.
Turn errors now carry a structured {layer, code, retryable} descriptor
(agent/error_surface.py) built from the same classifier the retry loop
uses. The tui_gateway stamps it on terminal error frames, retained
failed-turn snapshots, and resume replay; the Desktop error card renders
the layer title (provider / endpoint / streaming / auth / billing /
gateway / runtime / disk) plus matched actions: Retry, Switch provider,
Open logs, Copy diagnostics.
Older backends that omit the descriptor keep today's behavior (generic
title, string-sniff fallbacks) — the field is advisory on both sides.
A turn that ended in error cleared inflight_turn and emitted its terminal
frame in the same breath. If the client was disconnected during that window
(the exact case for a failure like a network drop), the frame went to the
detached drop-transport and the in-memory state was already gone — the
desktop reconnected to a session with no trace of the failure.
Failed turns now retain a compact error snapshot (user prompt, partial
assistant text, error, recoverable) that session.resume's inflight payload
carries to a reconnecting client. Covers all three loss sites: the
returned-error result path, the turn exception path (which now closes with
the same status:"error" message.complete frame shape instead of a bare
error event), and agent-init failure. The snapshot lives until the next
turn starts or the session closes; _run_prompt_submit replaces a retained
error leftover instead of appending onto it.
Co-authored-by: Reza Sayar <rsayar@uvic.ca>