Queue: queued rows show Edit, Remove and Send now; Send now stops only the agent's step, never the turn

Merged
TE
16d ago
+639-77

Summary

Queued messages above the composer get their actions, and Send now hurries a message without ever stopping the turn. Leo, 2026-10-20: "where the hell is the edit button?"

  • Every queued row shows Edit, Remove (×) and Send now, each a button with a tooltip; the row's menu has them too. Before, a row showed only Send now on hover (the hover-actions experiment's default arm), and Edit and Remove were on its menu and keys only. The actions rest at one strength on every row (they were faint until hovered, so one ↑ looked boxed and the next faint).
  • Send now steers the message into the running turn and stops only the agent's current step, so Claude reads it at once and the turn runs on. This is Claude desktop's Send now. A steer Claude Code has not read yet (sent with Return) gets the same through the same op: the hub keeps each steer the agent has not answered, and queue_edit send_now on one asks the adapter to stop the step (_acpmux/step_now, answered sent: "now"). A queued prompt sent now carries _meta.steerNow, and the adapter writes the step's interrupt after its line. That interrupt leaves the turn's stop flag unset, so the steer Claude reads after the stopped step runs in the same turn, which ends as the agent ends it. An agent without step_now reads the steer when its step ends (sent: "steer"). To an agent that cannot steer, Send now puts the message next to run once the turn ends (sent: "next").
  • Send now never stops the turn. Before, Send now to an agent that cannot steer stopped the turn, and its tool call came back "rejected by the user". In claude-sr2 (a chat adopted after the first upgrade, so it read as unable to steer) every queue-row Send now did this. And because focus moved to the next row after each send, a held or repeated ⌘↩ walked down the queue and stopped one turn per row.
  • A held ⌘↩ on a row sends that row once, and focus stays on it.
  • _acpmux/queue_edit gets an internal op interrupt: the message goes next and the running turn stops. No queue control uses it.

The composer's keys and hints (Return = Send now, ⌘Return = Queue) belong to the send-mode lanes. The Send now button on a pending steered message is steer-default's; it calls this PR's queue_edit send_now. Still to do in the queue work: undo after Remove, drag to reorder, attachments while editing, and glass legibility.

Changelog

  • Changed: each queued message shows Edit, Remove and Send now; Send now delivers it at once without stopping the running turn.

Testing

Each fix's failing test is committed first:

  • queue_ops_interrupt_stops_a_turn_its_agent_can_steer (hub_integration/steer_only.rs): red at 8d8b93a, green at 65d232e.
  • queue_ops_send_now_puts_next_and_never_stops_a_turn_its_agent_cannot_steer: red at 3e65c80, green at 657f0f4.
  • steer_now (claude_stdio/steer_now_tests.rs and hub_integration/steer_now.rs, steer-default's tests, with fake_claude.py's "long-step" matching Claude Code 2.1's order of lines): red at f1e491f, green at fd4543e.
  • bun test src/agent-session/acpmux/ComposerQueue.test.tsx (4 tests): fails on the base component (4 fail) and passes on the head (4 pass).

Observed / Not run

  • Observed: a live Claude Code (stream-json, --replay-user-messages) run with a steer line written during sleep 25, then an interrupt. The step's tool call ends "[Request interrupted by user for tool use]", then Claude reads the steer and answers in the same session. The ComposerQueue tests red then green. vp fmt --check, vp lint and vp check --no-fmt --no-lint show nothing for the changed webview files, and rustfmt --check is clean on the changed Rust files.
  • Fleet focused runs (cmux-tui.yml): results are added below as they finish.
  • Known: the step Claude stops still reports, in its own words, "The user doesn't want to proceed with this tool use". acpmux can't change Claude Code's tool result.
  • Known (review): if the turn ends just as a queued Send now steers in, the prompt goes next through the old interrupt fallback, which can stop a turn that started in that instant.
  • Not run: a live dogfood in a tagged build, and a gallery before/after.

Agent session: cosmic-gray

🤖 Generated with Claude Code