Skip to content

Agents can show local images in chat: show_image, and Markdown images in replies - #1036

Open
alexandre-vl wants to merge 3 commits into
zeronsh:mainfrom
alexandre-vl:zeron/identify-and-reproduce-bug
Open

alexandre-vl wants to merge 3 commits into
zeronsh:mainfrom
alexandre-vl:zeron/identify-and-reproduce-bug

Conversation

@alexandre-vl

@alexandre-vl alexandre-vl commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1035. Refs #631 on desktop: its /tmp/… screenshot is outside the chat's folder, so it still shows as a link.

Two native ways for an agent to show a local image, both drawn as the image row Codex's generated images use.

show_image {path} ![alt](path) in a reply
show_image Markdown images

show_image (Zeron MCP) copies the file into uploads and publishes an image part, so desktop and iOS already render it and it outlives the file. PNG/JPEG/WebP/GIF by their bytes, ≤ 10 MB, ≤ 4096 px a side; a refusal says why. With real harnesses, Claude Code and OpenCode used it unprompted (4/4 and 6/6 images byte-identical); Codex defers MCP tools behind its tool search and writes Markdown instead.

Markdown images in an agent's reply are drawn in place, read live by the chat's host, never copied. No prompt or instruction mentions them. Refused paths keep today's rendering, a link:

  • Nothing is fetched: URLs (http:, file:, data: …), ~, UNC and device paths are dropped before any request.
  • ReadChatImageChunk {chatId, path} is answered only by the chat's host (relayed with targetDeviceId), strictly inside that chat's folder. A chat working in home or above, /, or around Zeron's data has none.
  • Jailed lexically before any filesystem call; each component below the folder is lstated and a symlink or junction refuses unfollowed (a planted junction to \\host\share would send NTLM credentials). Windows device names are refused.
  • A regular file (a FIFO is never opened), ≤ 10 MB, extension and first bytes agreeing; decoded within the shared 4096 px / 64 MiB limits, 4 reads at a time.
  • The agent learns nothing back, so a reply can't probe which files exist.

Limits: the image follows the file (show_image copies); images outside the chat's folder (/tmp) or behind a symlink stay links; iOS still shows links.

  • cargo test -p zeron-ui --lib: 1897 passed, incl. reply_markdown_images_draw_only_from_their_own_chat.
  • cargo test -p zeron-engine --lib -- uploads:: rpc::tests::: 35 passed.
  • cargo test -p zeron-engine --test e2e --test device_routing: 31 + 11 passed, incl. publish_image_lands_in_the_live_turn and chat_images_come_only_from_their_chats_own_folder.
  • cargo test -p zeron-mcp: 36 passed.
  • reply-images-fixture example + docs/screenshots/agent-images/ reproduce the captures.

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

… in replies

show_image {path} (Zeron MCP) copies a PNG/JPEG/WebP/GIF into uploads and
publishes the image part Codex's generated images use, so every client
already renders it and it outlives the file.

An agent reply's ![alt](path) now draws the image in place, read live by the
chat's host through ReadChatImageChunk: strictly inside that chat's folder,
no link followed, no URL fetched, extension and bytes checked, decoded within
the shared raster limits. Anything refused keeps today's link.
On Windows `/` isn't absolute, so it was refused as "not an absolute
path" instead of as the root. Test the root of the temp dir's own path
(`/`, or a drive like `C:\`) instead.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Agents can't show a local image in the chat

1 participant