fix(desktop): reliable composer message queue#40221
Merged
Merged
Conversation
Contributor
🔎 Lint report:
|
The queue felt 'dumb' because of three real bugs: 1. Drained-after-interrupt sends went silent. cancelRun sets interrupted:true and nothing reset it; submitPromptText's optimistic seed preserved it, and the message stream drops every delta while interrupted. So Send-now-while-busy and any interrupt+drain submitted the next turn into a muted session. Fix: a fresh submit is a new turn — seed interrupted:false. 2. Back-to-back queue drains stalled. The drain fires on the busy->false settle edge, but busyRef (synced from the busy store by a separate effect) can still read true on that same edge, so the drained send hit the busy guard, returned false, and the entry was never removed. Fix: fromQueue sends bypass the busyRef guard (the queue drain lock serializes them); the user path keeps the guard. 3. Double-enter-to-interrupt killed single non-queue turns. The hidden 450ms timer meant a natural double-tap after sending stopped the agent. Fix: empty Enter while busy is a no-op; interrupting is explicit — Stop button or Esc. Also: clean stop (no [interrupted] marker), Send-now works while busy (promote + interrupt + auto-drain), settle on the interrupted completion path. Adds regression tests and unblocks the prompt-actions suite by completing its stale @/hermes mock.
… resize The queue list rendered in-flow inside the composer root, so its height fed --composer-measured-height (the composer rect drives the thread's bottom padding + last-message clearance). Queuing a message grew that rect and the whole chat visibly resized. Anchor the panel out of flow above the composer (absolute bottom-full, capped at 40vh with internal scroll). It no longer contributes to the measured height, so the thread layout stays put and the list overlays the (already faded) chat. Still collapsible via the panel's own disclosure header.
…omposer - Default the queue disclosure to collapsed (compact 'N queued' pill) instead of expanded. - Drop the gap and merge the panel into the composer: square bottom corners, no bottom border/radius, and overlap down by the Root's pt-2 (-mb-2) so the panel's borderless bottom lands on the composer surface's top border — one continuous bordered shape.
f7c8a7b to
3d2b253
Compare
changman
pushed a commit
to changman/hermes-agent
that referenced
this pull request
Jun 10, 2026
* fix(desktop): make composer message queue reliable The queue felt 'dumb' because of three real bugs: 1. Drained-after-interrupt sends went silent. cancelRun sets interrupted:true and nothing reset it; submitPromptText's optimistic seed preserved it, and the message stream drops every delta while interrupted. So Send-now-while-busy and any interrupt+drain submitted the next turn into a muted session. Fix: a fresh submit is a new turn — seed interrupted:false. 2. Back-to-back queue drains stalled. The drain fires on the busy->false settle edge, but busyRef (synced from the busy store by a separate effect) can still read true on that same edge, so the drained send hit the busy guard, returned false, and the entry was never removed. Fix: fromQueue sends bypass the busyRef guard (the queue drain lock serializes them); the user path keeps the guard. 3. Double-enter-to-interrupt killed single non-queue turns. The hidden 450ms timer meant a natural double-tap after sending stopped the agent. Fix: empty Enter while busy is a no-op; interrupting is explicit — Stop button or Esc. Also: clean stop (no [interrupted] marker), Send-now works while busy (promote + interrupt + auto-drain), settle on the interrupted completion path. Adds regression tests and unblocks the prompt-actions suite by completing its stale @/hermes mock. * fix(desktop): float the queue panel as an overlay so the chat doesn't resize The queue list rendered in-flow inside the composer root, so its height fed --composer-measured-height (the composer rect drives the thread's bottom padding + last-message clearance). Queuing a message grew that rect and the whole chat visibly resized. Anchor the panel out of flow above the composer (absolute bottom-full, capped at 40vh with internal scroll). It no longer contributes to the measured height, so the thread layout stays put and the list overlays the (already faded) chat. Still collapsible via the panel's own disclosure header. * fix(desktop): queue panel collapsed by default + shared border with composer - Default the queue disclosure to collapsed (compact 'N queued' pill) instead of expanded. - Drop the gap and merge the panel into the composer: square bottom corners, no bottom border/radius, and overlap down by the Root's pt-2 (-mb-2) so the panel's borderless bottom lands on the composer surface's top border — one continuous bordered shape. * style(desktop): tighten queue panel padding * style(desktop): trim queue-ux comments to house style * style(desktop): drop 'Cursor' references from comments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Make the desktop composer message queue actually reliable, and stop it from shoving the chat around.
The queue felt "dumb" — double-enter clunked, single non-queue messages could get interrupted, and multiple queued messages often produced no reply. Root-caused to three real bugs, plus a layout issue where the queue panel resized the whole chat.
Behavior fixes
Drained-after-interrupt sends went silent.
cancelRunsetsinterrupted: trueand nothing reset it; the optimistic seed preserved it, and the message stream drops every delta while interrupted. So "Send now" while busy (and any interrupt → drain) submitted the next turn into a muted session — the agent appeared to never respond. Fix: a fresh submit is a new turn, so seedinterrupted: false.Back-to-back drains stalled. The drain fires on the
busy → falsesettle edge, butbusyRef(synced from the busy store by a separate effect) can still readtrueon that same edge, so the drained send hit the busy guard, returnedfalse, and the entry was never removed. Fix:fromQueuesends bypass thebusyRefguard (the drain lock serializes them); the user path keeps the guard.Double-enter interrupted single turns. A hidden 450ms timer meant a natural double-tap after sending stopped the agent. Fix: empty Enter while busy is a no-op; interrupting is explicit — Stop button or Esc. "Send now" while busy promotes to head + interrupts + auto-drains.
Also: clean stop (no
[interrupted]marker), and the interrupted completion path now settlesbusy.Layout
The queue list rendered in-flow inside the composer root, so its height fed
--composer-measured-height(which drives the thread's bottom padding) — queuing a message visibly resized the chat. Now the panel:Tests
use-prompt-actions.test.tsx: interrupted-reset,fromQueuebusy-bypass, guarded user path. Stash-toggle teeth-checked (fix-asserting tests go red with the fix removed).@/hermesmock.tsc -bclean; queue + prompt-action suites green (20/20).We can't use assistant-ui's native queue primitive — we're on
ExternalStoreRuntime— so the nanostore queue is owned here; the bugs were all control-flow.