Agent pane: keep a sent prompt's images; bigger, clearer composer thumbnails
Leo's dogfood bug (16): images attached to a prompt vanished once it was sent. acpmux's user_message carried only text, so the sent bubble had no images, and Edit or Retry after a reload sent the prompt without them.
Captures
Real cmux NEXT DEV builds on a fleet capture mini (base = feat-cmux-next 8d6b1526, head = 92aca21f), mock chat, three dark 1200×800 terminal screenshots pasted. Left is base, right is this PR. The machine name is masked.
![]()
Composer, dark: base crops each screenshot to a small square with a faint edge; head shows 80 px chips in the image's own shape with a visible 1 px edge.
![]()
Composer, light: same comparison; the edge still separates the dark images from the light composer.

Sent prompt, dark: base drops the images once sent; head shows them as thumbnails above the bubble.

Sent prompt, light: same.

Head only (dark, light): clicking a sent thumbnail opens the image viewer. Base has no sent thumbnails, so there is nothing to open there.

Head, dark, sped up: paste three screenshots, type, send, then open the first sent image. Full quality:
What changes
acpmux
- Each image in a prompt is kept once under
<ACPMUX_HOME>/attachments/<sha256>(written aside and renamed). The prompt'suser_messagenames its images:images: [{id, mimeType, name}]. Steered prompts do the same. The log never holds the base64. - New
_acpmux/attachment {sessionId, id}returns{mimeType, data}, but only for an id that the session's own log names. An unknown id, or anything that isn't a 64-hex hash, such as a path, gets not-found. - Since the image refs live in the event log and the bytes live under the acpmux home, the images survive a restart and are carried into a fork (which copies the log).
- To keep the god files from growing,
_acpmux/eventsand_acpmux/attachmentmove toserver/requests/events.rs.hub/turns.rsandserver/requests.rsboth end smaller than on base.
webviews
- A sent prompt's bubble shows its images as thumbnails above the text, each read once by id and cached. Clicking one opens the image viewer. They use the same hairline and checker backing as the composer chips, so a dark screenshot still shows.
- Edit, Retry and Retry with another model send the prompt's images again. They use the attachments this page sent, or read them back from acpmux after a reload or from another window.
- The mock daemon keeps images and answers
_acpmux/attachment, and its new chats now carryfamilyas real acpmux summaries do. That lets Edit show in the dev page.
app relay (Swift)
AcpmuxPaneMethodsallowlists_acpmux/attachmentwith paramssessionIdandidonly. Without it the app's host transport refuses the read (default deny), so the app showed nothing.- It is a read like
_acpmux/events, which is also not insessionScoped, since a read-only view shows its bubbles too. - acpmux answers only for an id that the chat's own log names, and the id is a 64-hex content hash, never a path.
- It grants nothing, so there is no gesture rule. @ad349 (protocol/origin lead): this changes the list you review.
- It is a read like
Also: composer image thumbnails, bug (19) (folded in from #20433)
Leo's dogfood bug (19), a follow-up to (5) in #20378: dark screenshot thumbnails in the composer were still tiny and nearly invisible.
Cause: thumbnail() cropped every image to a centered 112 px square, drawn at 56 px. A wide screenshot became a sliver of its text. The edge was a 0.5 px hairline in the border color, which disappears on a dark composer, especially on Retina.
Fix:
- Image chips are 80 px tall and keep the image's own shape. Aspect ratios between 0.7:1 and 2:1 show uncropped; anything wider or taller is center-cropped to those bounds, and the width is held between 56 and 160 px. The thumbnail is drawn at 2× (160 px tall).
- The edge is a 1 px line in the theme's text color at 45%, drawn over the image. It stands out from both a dark image and the composer, in dark and light themes.
Test (red → green): CMUX_BROWSER_TESTS=1 bun test test/agent-pane-attach-typing.test.ts in Chromium. Three pasted 1200×800 near-black screenshots must each be:
- ≥ 72 px tall;
- wider than 1.3:1;
- edged by pixels that differ by > 45 luminance from both the composer around the chip and the image's middle.
Results:
- Red at the test commit: height 56, expected ≥ 72. The old edge measured 65 against a composer at 34, a margin of 31, below the new bar of 45.
- Green after the fix, three runs, and also passing in local WebKit (run with the engine swapped). The img has an explicit 80 px height so WebKit sizes its width from the image shape, as the review suggested.
bun run check, ui-rules, theme-colors and tooltip-rules pass.
Sent-bubble thumbnails use the same 1 px edge.
Known limits (follow-ups)
- SSH-remote chats (
RemoteAcpmuxWire) don't relay the method yet, so their bubbles show no images. - Kept image bytes are shared by hash and never deleted, including by
_acpmux/kill {purge}. - A queued prompt shows its images only once its turn starts (that is when
user_messageis written).
Tests (red, then green)
-
Daemon:
tests/prompt_images.rs(a_prompts_images_are_named_in_its_message_and_read_back_after_a_restart), hosted cmux-tui focused. -
Web:
test/agent-pane-sent-images.test.ts(Chromium). Paste an image, send, then check:- the bubble shows one thumbnail with a
data:image/pngsrc; - clicking it opens the viewer;
- Edit → Send forks with the edited text and the image.
Results, all with
CMUX_BROWSER_TESTS=1 bun test test/agent-pane-sent-images.test.tson the merged head:- Red with the web fix reversed: times out waiting for
.cv-user__images img. - Green: 3 of 3 runs passed.
- the bubble shows one thumbnail with a
-
Relay (Swift):
AgentPaneAttachmentRelayTests, fleetrun-swift-testing-suites.sh. Fleet steps on aws-m4pro, Xcode 26.6:- Red at a1b0de2 (step d319a83b0aae): the transport refuses the read, and nothing reaches acpmux.
- Green at 5ce1ba6 (step 3ed3dc3baad6): the test passes.
- Both steps exit 1 on the same-tree guard (the head's cmux-tui tree isn't published yet). Their other suite failures differ between runs (NewTabStyleSwitchTests passed on red and failed on green) and none involve this change.
-
bun run check, crash ratchet, check-concurrency, check-system-blue and the Rust god-file check pass locally.
Before / after
Real cmux NEXT DEV builds on a capture mini (--showcase, in-page mock daemon), merge base a5341bc5d774 (before) against this branch at 92aca21fbbd0 (after). Two screenshots are pasted into a new chat, then sent. The host name in the location chip is masked.
Composer thumbnails. Top row is before, bottom row is after; light on the left, dark on the right.
![]()
The sent prompt, right after Send. Before (top), the images are gone; after (bottom), the bubble keeps them.

Paste, type and send. Before on the left, after on the right.

Full quality:
Changelog
- Fixed: Images sent with a prompt now show in its message, open in the image viewer, and are sent again by Edit and Retry.
- Fixed: Image thumbnails in the agent composer are larger, keep the screenshot's shape, and have a visible edge on dark images.
Verification note: after the last base merge (0a4f5df), big-red was out of memory (earlyoom killing every large node process), so bun run check and the browser tests could not run locally on that merge. They passed on the pre-merge commits, the merge had no conflicts, and hosted react-apps-check covers the merged head.
🤖 Generated with Claude Code