From c26bc211942c90001d52e028ed3505efbbf81a4f Mon Sep 17 00:00:00 2001 From: t Date: Fri, 10 Jul 2026 19:16:48 +0000 Subject: [PATCH] ux(#3929): disclose wakeup pause in error card + changelog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fable UX-gate follow-up on #5732: after the first credential-exhausted error, automatic wakeups are silently suppressed. Disclose it in the wakeup-source error card ('Automatic retries for this conversation are paused until you send a message, switch the model/provider, or fix the credentials.') so the silence reads as intentional, not a stuck agent — Gmail/Dropbox account-needs-attention convention. Scoped to the process_wakeup + credential_pool_empty seam only. Co-authored-by: franksong2702 --- CHANGELOG.md | 2 ++ api/streaming.py | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f255235..f772b933 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### Fixed +- **Repeated automatic retries after your provider credentials run out no longer spam the chat with identical error bubbles.** When a session's provider credential pool is exhausted, each completed background task used to start a fresh automatic "wakeup" turn that failed the same way, stacking duplicate terminal error messages. The first failure still shows clearly, but subsequent automatic wakeups on the same model/provider are now paused (recorded as auditable suppression metadata, not shown), and the pause clears automatically on recovery — a successful run, a model/provider switch, or when the underlying credential state actually changes. Explicit (non-automatic) recovery turns are never blocked. Thanks @franksong2702. (#5732, #3929) + - **Opening Hermes from a browser-search or launcher deep-link now focuses the composer so you can press Enter immediately.** A `?q=`/`?prompt=` URL prefill populated the composer but left keyboard focus elsewhere, so the prefilled text sat there until you clicked into the box. Boot now focuses the composer after applying a non-empty URL prefill — the focus fires only on that prefill seam (ordinary boots are unchanged) and never auto-sends. Thanks @rodboev. (#5887, #5884) - **A fast session switch can no longer paint the wrong conversation's messages into the pane you're looking at.** The chat pane is shared state, and `loadSession()` previously tracked ownership only by destination session id. If you switched sessions (or reloaded the same one) quickly, a slow transcript response from the earlier load could finish late and populate `S.messages` after a newer navigation had already taken over — leaving a split pane: the current session's header above another session's rows. Each `loadSession()` invocation now carries a monotonically increasing generation token, and every point that mutates the pane after an `await` re-checks it still owns the load (matching both session id *and* generation) before writing — so a stale continuation bails instead of hijacking the view. A redundant same-session click no longer cancels the authoritative in-flight refresh, and a stale response can't collapse a long transcript to its tail window. Thanks @starship-s. (#5889) diff --git a/api/streaming.py b/api/streaming.py index 65b98c5b..9fae1736 100644 --- a/api/streaming.py +++ b/api/streaming.py @@ -8925,6 +8925,15 @@ def _run_agent_streaming( model=_turn_route_model, provider=_turn_route_provider, ) + # Disclose the suppression so the silence reads as + # intentional, not a stuck agent (#3929 UX): further + # automatic wakeups for this session are now paused + # until the user acts or credentials change. + _err_hint = ( + (_err_hint + ' ' if _err_hint else '') + + 'Automatic retries for this conversation are paused until you ' + + 'send a message, switch the model/provider, or fix the credentials.' + ) _materialize_pending_user_turn_before_error(s) s.active_stream_id = None s.pending_user_message = None