fix(vision): make desktop image uploads reachable from profile Docker sandboxes (#69575) (#75671)

* fix(vision): mount images/ upload dir into sandboxes and permit host read (#69575)

Desktop, clipboard, and PDF uploads land in the flat top-level
HERMES_HOME/images/ dir, but Docker sandboxes only mounted the cache/
subtree and the vision resolver only permitted host reads from the media
caches. So vision_analyze on any desktop-app upload failed under a Docker
backend with "not reachable inside the sandbox".

- Add ("images", "images") to _CACHE_DIRS so the uploads dir is bind-mounted
  into sandbox containers through the existing profile-scoped cache-mount and
  reverse-mapping mechanism.
- Add home/"images" to _media_cache_roots() so the non-local host-read
  allowlist permits reading uploads directly from the host filesystem.
- Cover the mount entry, the container path mapping, and the Docker-mode
  resolver read for a profile-scoped upload.

Co-authored-by: JonthanaHanh <92574114+JonthanaHanh@users.noreply.github.com>
Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com>

* fix(tui_gateway): write image uploads under the session's profile home (#69575)

The attach RPCs (image.attach_bytes, clipboard.paste, pdf.attach) wrote
uploads to the gateway's module-cached launch home via _hermes_home/"images".
Those RPCs run before prompt.submit installs the session's profile HERMES_HOME
override, so in a multi-profile / root-gateway deployment the file landed in
the launch home while the sandbox mount and the vision host-read allowlist
both resolve the session profile's images/ at run time — the agent could
never see the upload it was handed.

Add _session_images_dir(session), which anchors the write on the session's
stored profile_home when present (matching the mount/read scope) and falls
back to the launch home otherwise. Route both write sites through it, keeping
per-profile isolation.

Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com>

---------

Co-authored-by: JonthanaHanh <92574114+JonthanaHanh@users.noreply.github.com>
Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com>
This commit is contained in:
Austin Pickett
2026-07-31 17:43:37 -04:00
committed by GitHub
parent 4b60979dc1
commit e444d16580
7 changed files with 137 additions and 2 deletions

View File

@@ -362,6 +362,39 @@ class TestCacheDirectoryMounts:
assert get_cache_directory_mounts() == []
def test_images_upload_dir_is_mounted(self, tmp_path, monkeypatch):
"""The flat top-level ``images/`` upload dir is mounted (#69575).
Desktop / clipboard / PDF uploads land in ``HERMES_HOME/images``, not
under ``cache/``. Without this entry vision_analyze on a desktop upload
fails because the file is not reachable inside the sandbox.
"""
hermes_home = tmp_path / ".hermes"
(hermes_home / "images").mkdir(parents=True)
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
mounts = get_cache_directory_mounts()
by_container = {m["container_path"]: m["host_path"] for m in mounts}
assert "/root/.hermes/images" in by_container
assert by_container["/root/.hermes/images"] == str(hermes_home / "images")
def test_images_upload_file_maps_into_container(self, tmp_path, monkeypatch):
"""A concrete upload under ``images/`` maps to its container path.
This is the reverse mapping vision uses to translate a container-visible
path back to the host mount; it must recognise the ``images/`` dir.
"""
hermes_home = tmp_path / ".hermes"
(hermes_home / "images").mkdir(parents=True)
upload = hermes_home / "images" / "upload_20260722_181019_1.png"
upload.write_bytes(bytes.fromhex("89504e470d0a1a0a"))
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
assert (
map_cache_path_to_container(str(upload))
== "/root/.hermes/images/upload_20260722_181019_1.png"
)
class TestMapCachePathToContainer:
"""Tests for map_cache_path_to_container() — the backend-agnostic mapper."""