Repository navigation
Description
Problem
When an agent reads a screenshot, captures a browser page or generates an image, I want to see the picture in Chat next to what the agent says about it, the way Kitty or Tern show images inline with the output. I use pi, omp, Claude Code and Codex, and I would like this to work the same way for all four. Line numbers below are from main at 8dc1774.
Today it depends on the agent and on where the image sits in the transcript:
| Agent | Image the user attached | Image a tool returned (read, view, screenshot, generate) |
|---|---|---|
| pi | dropped | shown |
| omp / omo / gjc | dropped | dropped |
| Claude Code | shown | dropped |
| Codex | shown | dropped |
And for every agent, a Markdown image in an answer () renders as ! plus a link.
What the code and real transcripts on my PC show:
- pi and omp user images. They share
parseOmpTranscript, whose user branch keeps text only and skips image-only prompts (server/transcript-records.ts:309-317).opensTurnalso needs text to start a turn (server/conversation.ts:682-684). - omp tool images.
toolImagesis on only forpi-transcript(server/conversation.ts:903-907) andconversationImagehas no omp/omo/gjc branch (server/conversation.ts:1597-1610). Turning the flag on is not enough: real omp image blocks holddata: "blob:sha256:<digest>"with the bytes in~/.omp/agent/blobs/, whilepiTranscriptImagedecodesdataas base64 (server/conversation.ts:1732). Some omp call IDs contain|, whichPI_IMAGE_REFrejects (server/conversation.ts:1739). - Claude Code tool images.
Readof an image file recordstool_result.content[]with an inline base64imageblock. The parser keeps only the result text (server/conversation.ts:389-399); images are taken from top-level user blocks only (server/conversation.ts:400-406). - Codex tool images.
view_image, image generation and wrappedexecoutput recordcustom_tool_call_output.output[]withinput_imagedata URLs.imageValuesreads user records only (server/codex-images.ts:16-28). Generation also writes anitem_completedExtensionevent with the same base64 and asavedPath, so a naive reader would show each generated image twice. - Markdown images. Chat provides no
MarkdownImageContext(src/components/Markdown.tsx:196-199;src/AGENTS.mddocuments this). The file viewer already resolves local Markdown images with a stat check and a 20 MiB limit.
Counts from the transcripts I sampled (a handful of sessions per agent, not a census):
| Agent | Carrier | Bytes | Seen |
|---|---|---|---|
| omp | user image, read/eval tool result |
blob:sha256: |
11 user, 175 tool blocks |
| omp | xd://generate_image |
details.xdev.inner.images[] blob, plus file paths |
4 |
| Claude Code | Read tool result |
inline base64 (duplicated in toolUseResult.file) |
6 |
| Codex | user input_image |
data URL | 16 |
| Codex | custom_tool_call_output.output[] (view_image, image gen, exec_command, write_stdin, apply_patch) |
data URL | 196 blocks in 114 outputs |
| Codex | image gen Extension event |
base64 + savedPath |
19, duplicates of tool output |
I had no local pi sessions or omo/gjc stores, so their record shapes come from the existing parser and tests, not real transcripts.
Proposal
One rule for every agent: an image the agent saw or produced appears in Chat as a thumbnail, using the parts that already exist (kind: "image" for user turns, tool.images for a tool row) and the existing GET /api/pane/conversation/image, answered from the pane's bound transcript. #951's viewer opens all of them. No new endpoints or wire fields.
Common to all agents:
- Refs only in the conversation response, never base64. Bytes are read when a thumbnail is shown; tool images still load only when the work row is open.
- PNG/JPEG/GIF/WebP only, with the same size cap and byte sniffing Codex already has (8 MiB) applied to every source.
- Refs are built from the same identity the fetcher accepts, so the parser never emits a ref that cannot be answered.
- Image-only user turns open a turn for paging in every source.
- History hidden by
/clear, a pi branch switch or a Codex backtrack stays unreachable. - A carrier that cannot be resolved stays text, never a broken image.
Per agent:
- pi: keep user image blocks, image-only prompts included.
- omp / omo / gjc: keep user image blocks; turn on tool-result images with their own fetcher that reads the linear history and resolves
blob:sha256:<hex>against the session's agent blob store with a strict digest check. Inline base64 keeps working. - Claude Code: read images inside
tool_result.content[]intotool.images, with a ref that addresses the nested block. - Codex: read
input_imageblocks in tool output intotool.images, any tool name, and ignore theExtensioncopy. - All agents, answers: a Chat
MarkdownImageContextreusing the file viewer's resolver (stat first, 20 MiB, machine-scoped/api/fs/file, download link when too large). Absolute paths andfile://always; relative paths against the pane's cwd, and a link when no cwd is known.http(s)stays a link (no CSP change). Clicking opens feat(chat): port t3code chat rendering, composer and settings features #951's viewer.
Proposed PRs, each independent:
- Shared: ref/fetch alignment, size cap and sniffing for every source, image-only paging.
- Claude Code tool-result images.
- Codex tool-output images.
- pi and omp-family user images, omp blob tool images.
- Local Markdown images in answers.
Where
- Chat and prompt cards
- Remote PCs
Workarounds and alternatives
- Open the file: feat(chat): open the files a tool's output and a plain code block name #691 made paths in tool output clickable, so a screenshot can be opened in the file viewer, but it is a separate screen and needs the path to be printed.
- Attach with
@path: user attachments show thumbnails, but that covers my own uploads, not what the agent produced. - Use Kitty or Tern: works at the desk, not from the phone, which is why I use the web UI.
- Kitty graphics in the terminal lens is a different feature (herdr's attach stream and xterm) and is not part of this proposal.
Questions
- Is "every agent's native images show in Chat" a direction you want for the project?
- Is the PR split above acceptable, or would you rather have one PR per agent including its share of the common part?
- For omp, is reading the agent's blob store from the server acceptable, or would you rather omp write inline data?
- Should generated images whose bytes live only outside the content array (omp
details.xdev.inner.images) be in scope? Codex already puts them in the tool output. - Should local Markdown images show by default or behind a setting?
If the approach is acceptable, I will open the PRs with parser, contract and browser tests.
Activity
[-]Chat: show agent images inline (omp tool and user images, local Markdown images)[/-][+]Chat: show images agents read or produce inline for pi, omp, Claude Code and Codex[/+]feat(chat): show pictures Claude Code reads, and a picture-only promp…
feat(chat): show pictures sent to pi, and a picture-only prompt as a …
feat(chat): show pictures sent to pi, and a picture-only prompt as a …
feat(chat): show a tool's pictures as small thumbnails on its work line
feat(chat): show a tool's pictures as small thumbnails on its work line
feat(chat): show a tool's pictures as small thumbnails on its work li…
feat(chat): show omp's and Codex's pictures, from omp's blob store an…
feat(chat): show omp's and Codex's pictures, from omp's blob store an…
feat(chat): show omp's pictures from its blob store, and the pictures…
devswha commentedon Oct 27, 2026
Closing: all four agents now show their pictures in Chat on main.
Transcript pictures are served by
conversationImageinserver/conversation.ts(Codex inserver/codex-images.ts), and a Markdown image in an answer renders throughMarkdownImageinsrc/components/Markdown.tsx. omp's blob-store pictures on macOS and Windows still need a descriptor-relative open; that is tracked in #2004. Thanks @zerodice0 for the detailed report.fix(chat): render local Markdown images inline (#2435)
feat(chat): show the pictures a Copilot CLI session interned (#2479)
feat(chat): show the picture attached to a Hermes prompt (#2482)
feat(chat): show the pictures a Grok CLI session sent and read (#2481)