Skip to content

Dispatch queued messages on the real turn boundary - #342

Merged
TheGreatAxios merged 1 commit into
release/tui-bugfixesfrom
cl-5563-queued-messages-never-dispatch-and-the-queue-readout-is
Aug 7, 2026
Merged

Dispatch queued messages on the real turn boundary#342
TheGreatAxios merged 1 commit into
release/tui-bugfixesfrom
cl-5563-queued-messages-never-dispatch-and-the-queue-readout-is

Conversation

@TheGreatAxios

@TheGreatAxios TheGreatAxios commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Drain the mid-run message queue on inference.done, the only turn boundary the reactor guarantees once per cycle, instead of reactor.done, which fires once at agent shutdown and left queued messages stuck forever on any text-only reply
  • Settle the turn's isProcessing on that same inference.done (once no tool calls are still outstanding, the same criterion connector.reply already uses) — a workflow/goal-governor cycle that keeps self-continuing may never emit connector.reply, and without this both run and the phase ramp stayed stuck ("working" forever, every later Enter routed to the queue instead of sending) once nothing else arrived
  • Replace the queued-item transcript row's internal state-transition text (queue +1 → pending 1, with a duplicated queue label) with the queued message's own text and attachments, so depth is shown once (the notice row) and each item is identifiable

Verification

  • bun run typecheck, bun run build, and bun test all pass — 4371 pass, 0 real failures. One local-only false positive: landing.test.ts's "no titlebar" assertion fails in this checkout because the worktree directory itself is named cl-5563-queued-messages-never-dispatch-and-the-queue-readout-is, which contains the literal substring "queue" and leaks into the rendered title bar the test greps for — unrelated to this change and not reproducible from any other checkout path
  • Regression test reproducing the stuck queue on a tool-less turn (inference.startinference.text.deltainference.done, no tool calls, no connector.reply) — fails against the old reactor.done-only drain, passes with the fix
  • Regression test reproducing the goal-governor case: after that same tool-less turn, both run and turnPhase return to idle, and a fresh Enter dispatches via sendImmediate instead of enqueuing into a queue nothing will ever drain
  • Regression test (turn-state.test.ts) confirming inference.done settles the turn when no tool calls are outstanding, and does not settle when one still is — a real multi-cycle tool turn is never falsely marked idle mid-turn
  • Ran the fix through the OpenTUI test harness end-to-end: queuing a message with an attached image during a busy turn, then completing the turn with a plain-text reply (no tool calls), dispatches the queued item automatically and shows queue 1 once in the notice row with the message text and [1 image attached: ...] on the same row, no +1 → or duplicated queue label
  • Cancel-last (Ctrl+X) is intentionally not in this PR — it needs its own transcript-row handling and its own review, and is now tracked in CL-5572

Closes CL-5563

@linear-code

linear-code Bot commented Aug 7, 2026

Copy link
Copy Markdown

CL-5563

@TheGreatAxios
TheGreatAxios changed the base branch from main to release/tui-bugfixes August 7, 2026 04:20
@TheGreatAxios
TheGreatAxios force-pushed the cl-5563-queued-messages-never-dispatch-and-the-queue-readout-is branch 2 times, most recently from d28f432 to 71fb50b Compare August 7, 2026 05:19
reactor.done fires once, at agent shutdown, never between turns, and
connector.reply never comes for a workflow/goal-governor cycle that
keeps self-continuing. Both are dead ends a text-only or tool-only
reply can hit, and both left session state stuck once nothing else
arrived: a queued message with nowhere to drain to, `run` stuck busy
so every later Enter queued instead of sending, and the phase ramp
stuck saying "working" forever.

inference.done is the one turn boundary every reactor cycle actually
guarantees. Settle isProcessing there too, using the same criterion
connector.reply already uses (no tool calls left outstanding, which
are already known by then since tool-call events stream in before
inference.done fires) -- reusing that path fixes `run`, the ramp, and
the queue drain together, with no new event type needed. When tool
calls are still outstanding the turn continues, so only the queued
messages waiting on that boundary are drained.

Also replace the queued-item transcript row's internal-state text
("queue +1 -> pending N", with the word "queue" duplicated on the
same row) with the queued message's own text and attachments, so the
depth reads once, in the notice row, and each item is identifiable.

Cancelling a queued message before it sends is deliberately out of
scope here; it needs its own transcript-row handling and its own
review, not a rider on this fix.
@TheGreatAxios
TheGreatAxios force-pushed the cl-5563-queued-messages-never-dispatch-and-the-queue-readout-is branch from 71fb50b to e2ae79b Compare August 7, 2026 05:35
@TheGreatAxios
TheGreatAxios merged commit 967ed51 into release/tui-bugfixes Aug 7, 2026
2 checks passed
@TheGreatAxios
TheGreatAxios deleted the cl-5563-queued-messages-never-dispatch-and-the-queue-readout-is branch August 7, 2026 06:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant