Agent chat: the X on a steer Claude has not read takes it back to edit

Merged
TE
12d ago
+725-19

Leo-ask: 533b34a5ed

Leo, 2026-10-21: "??? BUT WE ALREADY HAVE THE (SHITTY, MISALIGNED AND NEEDS SPRUCING UP) UI AND THE SEND NOW IT'S LIKE THERE'S A CANCEL AND EDIT BY CLICKING THE X BUTTON ON THE SENT MESSAGES TO CLAUDE.. THEY'RE STEERS BUT THEY HAVENT BEEN SENT SOMETIMES WITH THE TOOL CALLS AND WHATNOT WAITING"

What changes

Leo, 2026-10-24: "also after i clicked send now and it worked it should just disappear..."

A message sent into a running Claude turn (a steer) is not read until Claude's current step ends: while tool calls run, it sits beside its bubble with a clock and Send now. Now it also has an X. Clicking the X takes the steer back and puts its text and images in the composer to edit. Once the agent has read it, the X does nothing and the steer stays.

The steer waits in Claude Code's own command queue, and Claude Code 2.1.307 can drop a queued line by its uuid (cancel_async_message, a no-op once dequeued), so acpmux adds no hold of its own:

  • acpmux adapter (claude_stdio/steer_withdraw.rs): _acpmux/steer_withdraw {promptId} sends cancel_async_message for that steer's line. When Claude Code drops it, the steer's session/prompt answers STEER_WITHDRAWN. When Claude Code read it first, or does not know the request, the steer stays and is answered steered as before.
  • acpmux hub: _acpmux/queue_edit gains op withdraw for a waiting steer. It records steer_withdrawn (a transcript record) and answers {stopReason: "withdrawn"}, or ok: false once the steer was read.
  • queued composer: after a successful _acpmux/queue_edit send_now, the row keeps its Steering state but hides the Send now control immediately; a failed edit leaves it available until the next snapshot.
  • agent pane:
    • PendingSteer draws an X after Send now, on the same line beside the bubble, for Claude chats only.
    • takeBackSteer reads the steer's attachments, withdraws it, then restores its text and attachments to the composer.
    • steer_withdrawn drops the steer's row.
    • Only a steer this pane sent is taken back (sentSteerHere): acpmux keeps only a prompt's images, so for another surface's steer, or one from before a reload, the page could not give back a PDF or file it carried, and the steer stays.

Nothing else is restyled, removed or switched off. Send now, the queue rows and every default are unchanged. The "misaligned" part was the queue rows in Leo's screenshot; the open #24221 restyles those. The new X sits on Send now's centred line, in its colour.

Tests (written first, commit 4bc6ea76f90)

  • claude_stdio/steer_withdraw_tests.rs:
    • an unread steer is dropped and answers withdrawn;
    • one read first stays;
    • with none waiting, nothing is asked;
    • a refused drop leaves the steer waiting.
  • tests/hub_integration/steer_now.rs withdraw_takes_back_a_steer_the_agent_has_not_read: real hub with fake_claude's long step. fake_claude now answers cancel_async_message the way Claude Code does.
  • webviews/test/agent-pane-steer-take-back.test.tsx:
    • a steer whose attachments the page does not hold stays; one read only from acpmux's log is not marked sent here;
    • the X sits beside the bubble;
    • a click sends queue_edit withdraw for the steer's promptId;
    • the text goes back to the composer;
    • steer_withdrawn drops the row;
    • a read steer has no X.

Observed / Not run

  • Ran on huge-blue at 1631bf383ba (adopted by prompt-queue-edit-2): cargo test -p acpmux --lib steer_withdraw 7/7 (two new: a read or failed steer leaves no withdraw line), cargo test -p acpmux --test hub_integration withdraw 15/15, bun test test/agent-pane-steer-take-back.test.tsx 6/6, vp fmt --check and vp lint on the changed files, vp check types, acpmux-protocol:check, prepush-gate.py.

  • Ran on cosmic-gray: prepush-gate.py (no new violations); rustfmt and oxfmt 0.73.0 on the changed files.

  • Not run locally: the cargo and bun tests. cosmic-gray runs no test suites, and cmux16's disk is at 95%, so CI runs them.

  • Read, not run: Claude Code's cancel_async_message contract, from the 2.1.307 binary's SDK schema: "Drops a pending async user message from the command queue by uuid. No-op if already dequeued for execution."

Changelog

  • Added: an X beside a message sent into a running Claude turn takes it back to edit while Claude has not read it.

šŸ¤– Generated with Claude Code

Agent session: cosmic-gray


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Adds an X beside a steer (a message sent into a running Claude turn) that takes it back into the composer to edit while Claude has not read it.

  • Clicking the X asks Claude Code to drop the steer from its command queue via cancel_async_message, then restores its text and attachments to the composer.
  • A steer is reported withdrawn only when it was still waiting and actually dropped; one already read, or answered in the meantime, stays. Only steers sent from the current pane are taken back, since acpmux keeps only a steer's images and a file carried elsewhere would be lost. Stale line records are dropped once a steer is read or fails, and the X recovers cleanly if an answer is lost.
  • The withdrawal is recorded as steer_withdrawn, dropping the steer's row in the pane.
  • Only Claude chats show the X; Send now, queue rows, and every other behavior are unchanged.

No capture: the fleet capture of tour:agent-pane at 2a2bbb740b46 ran its steps, but pr-capture refused to upload it because the pane shows the capture mini's machine name (cmux7's Mac mini) in both stills; that tour also never sends a steer, so it could not show the X. The withdraw is covered by the steer_withdraw and queue_edit tests.