Skip to content

apps/ui, apps/cli: queue follow-up prompts during an active agent turn - #3146

Merged
youknowriad merged 3 commits into
trunkfrom
claude/distracted-einstein-aa1915
Apr 20, 2026
Merged

apps/ui, apps/cli: queue follow-up prompts during an active agent turn#3146
youknowriad merged 3 commits into
trunkfrom
claude/distracted-einstein-aa1915

Conversation

@youknowriad

@youknowriad youknowriad commented Apr 20, 2026

Copy link
Copy Markdown
Contributor

Related issues

Closes STU-1574

How AI was used in this PR

Claude (Opus 4.7) wrote the code end-to-end based on research into how Claude Code, opencode, Cursor, Aider, and pi handle mid-turn input, and a walk-through of how the GUI (useAgentRun) and CLI (AiChatUI) currently block submits. I reviewed each diff before committing and asked for the queued-prompt visual to be restyled once before landing.

Proposed Changes

Today both surfaces block new input while the agent is running — you have to wait for a turn to finish before typing the next instruction. This adds a queue on both sides so follow-ups can be staged mid-turn and auto-dispatch once the current run ends, with a way to discard them before they fire.

apps/ui (useAgentRun + session-view)

  • New queuedPrompts: QueuedPrompt[] in the reducer with queue_append / queue_remove / queue_shift / queue_clear actions. run_ended now preserves the queue across run boundaries.
  • Extracted the old direct-send body into an internal startRun. sendMessage queues whenever phase !== 'idle', questions are pending, or the queue already has items (preserves FIFO). An effect auto-dispatches the head when phase === 'idle', guarded by a dispatchingQueuedRef ref to prevent duplicate runs during the async window between start-call and send_start. On queued-dispatch failure the whole queue is cleared so a broken backend doesn't cascade errors.
  • Composer stays enabled during a run: the primary button switches to "Queue" and a Stop button shows alongside it. Placeholder text swaps between "Set your next instruction…" and "Queue a follow-up instruction…".
  • New QueuedPrompts component renders staged prompts above the composer as faint user-style bubbles (matching .userText shape: left-aligned, 18px 18px 18px 8px corners, 4% accent tint, dashed border, muted text). Each bubble has an × to discard before it fires.

apps/cli (AiChatUI, pi-tui)

  • Added queuedPrompts: string[] and a persistent queuedContainer: Container mounted between messages and the editor.
  • editor.onSubmit now branches: if submitResolve is set (idle), resolve as before; if _inAgentTurn is true, push the prompt onto queuedPrompts and clear the editor.
  • waitForInput drains queuedPrompts.shift() immediately when the queue is non-empty, so the existing while (true) { waitForInput(); runAgentTurn(...) } loop in commands/ai/index.ts picks up follow-ups without any change on the caller side. Queued slash commands still route correctly when dequeued.
  • showLoader re-attaches trailing children in order [loader, queuedContainer, editor] so the thinking loader sits above staged bubbles, which sit above the editor.
  • Backspace on an empty editor pops the most recent queued prompt (discard affordance). Hint bar surfaces backspace to unqueue whenever the queue is non-empty.
  • Queued prompts render as ↳ text with a muted slate-on-very-light-blue style (#e8eef5 bg, #5a6b7d fg) — visually the CLI analogue of the dashed bubble in the GUI.
  • /clear wipes the queue alongside the transcript.
  • Interrupt (Esc / Stop) leaves the queue intact — matches Claude Code / opencode behavior; user can backspace-clear or × to drop individual items.

Testing Instructions

apps/ui

  1. Open a session and send a prompt.
  2. While the agent is running, type a follow-up and press Cmd/Ctrl+Enter (or click "Queue"). Expect a faint dashed bubble to appear above the composer.
  3. Queue 2–3 more prompts. Order should be preserved.
  4. Hover a bubble and click the × — that item should disappear.
  5. Wait for the current turn to finish — remaining queued prompts should fire one after another, in order, each rendering as a normal user message in the conversation.
  6. While a queued batch is running, click Stop. The current turn should be interrupted, but remaining queued items should continue to fire on the next run (this is intentional — discard with × to drop them).
  7. Edge: queue something while the agent is asking a clarifying question. The queued item should wait until the batch is answered and the run fully exits, then dispatch.

apps/cli

  1. npm run cli:build && node apps/cli/dist/cli/main.mjs into an AI chat.
  2. Send a prompt. While the agent is thinking, type another prompt and press Enter. The editor should clear and a faint bubble (↳ …) should appear just above the editor, below the loader.
  3. Queue 2–3 more. The bubbles should stack in FIFO order.
  4. With the editor empty, press Backspace — the most recent queued bubble should pop. Confirm the hint bar shows "backspace to unqueue" while the queue has items.
  5. Let the turn finish — queued items should dispatch one by one automatically (each one shows up as a normal user bubble in the transcript and kicks off its own run).
  6. While mid-turn with queued items, press Esc to interrupt. Confirm the queued items survive and start firing after the interrupt settles.
  7. Run /clear while a queue exists — queue and transcript should both wipe.

Pre-merge Checklist

  • Have you checked for TypeScript, React or other console errors?
  • npm run typecheck clean for apps/ui and apps/cli.
  • npm test -- apps/cli/ai/tests/ui.test.ts (6/6) and apps/cli/commands/ai/tests/ai.test.ts (21/21) pass. The clearTranscript test needed a tiny stub for the new queuedPrompts field (tests use Object.create(AiChatUI.prototype) and stub only the fields they exercise).
  • Manual UI verification — see Testing Instructions above. Worth eyeballing the CLI bubble colors on Terminal.app / iTerm2 since #e8eef5/#5a6b7d relies on truecolor for the muted look.

🤖 Generated with Claude Code

youknowriad and others added 2 commits April 20, 2026 13:07
Let users stage new prompts while the agent is running instead of
having to wait for a turn to end. Staged prompts render as faint
user-style bubbles and auto-dispatch FIFO when the current run ends.
Users can drop them before they fire (× in the GUI, backspace on an
empty editor in the CLI).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror the .userText change from #3145 on the faint queued variant so
the two bubble styles read as the same column on the right, with
queued items sitting just above the composer as drafts-in-waiting.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@youknowriad
youknowriad force-pushed the claude/distracted-einstein-aa1915 branch from a1aab83 to b6fb5d0 Compare April 20, 2026 11:09
As the queue grows, the composer area pushes the scroll container up
and the tail of the transcript can slip out of view. Include the queue
length in the auto-scroll effect so the newest messages stay visible
when a prompt is staged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@youknowriad youknowriad self-assigned this Apr 20, 2026
@wpmobilebot

Copy link
Copy Markdown
Collaborator

📊 Performance Test Results

Comparing 49604e7 vs trunk

app-size

Metric trunk 49604e7 Diff Change
App Size (Mac) 1439.62 MB 1439.62 MB +0.00 MB ⚪ 0.0%

site-editor

Metric trunk 49604e7 Diff Change
load 1825 ms 1945 ms +120 ms 🔴 6.6%

site-startup

Metric trunk 49604e7 Diff Change
siteCreation 8150 ms 9122 ms +972 ms 🔴 11.9%
siteStartup 4948 ms 4946 ms 2 ms ⚪ 0.0%

Results are median values from multiple test runs.

Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff)

@youknowriad
youknowriad merged commit 46cfda8 into trunk Apr 20, 2026
6 of 7 checks passed
@youknowriad
youknowriad deleted the claude/distracted-einstein-aa1915 branch April 20, 2026 11:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants