Files
hermes-agent/tests/tui_gateway/test_session_images_dir.py
T
Austin Pickett e444d16580 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>
2026-07-31 17:43:37 -04:00

55 lines
2.1 KiB
Python

"""Write-side scoping for desktop/clipboard image uploads (#69575).
Attach RPCs (``image.attach_bytes``, ``clipboard.paste``, ``pdf.attach``) run
before ``prompt.submit`` installs the session's profile HERMES_HOME override, so
the upload must be written under the session's *stored* ``profile_home`` — the
same scope the Docker mount and the vision host-read allowlist resolve at run
time. Otherwise, in a multi-profile / root-gateway deployment, the file is
written to the launch home while the sandbox mounts (and vision reads) the
profile home, and the agent can never see the upload it was handed.
"""
from pathlib import Path
from unittest.mock import patch
from tui_gateway.server import _session_images_dir
def test_profile_home_session_writes_under_profile(tmp_path):
"""A session pinned to a profile writes uploads under that profile's home."""
profile_home = tmp_path / ".hermes" / "profiles" / "coder"
session = {"profile_home": str(profile_home)}
assert _session_images_dir(session) == profile_home / "images"
def test_launch_home_fallback_when_no_profile(tmp_path):
"""No ``profile_home`` on the session → the gateway launch home is used."""
launch_home = tmp_path / ".hermes"
session = {}
with patch("tui_gateway.server._hermes_home", launch_home):
assert _session_images_dir(session) == launch_home / "images"
def test_empty_profile_home_falls_back_to_launch_home(tmp_path):
"""An empty-string ``profile_home`` is treated as absent, not as ``/images``."""
launch_home = tmp_path / ".hermes"
session = {"profile_home": ""}
with patch("tui_gateway.server._hermes_home", launch_home):
assert _session_images_dir(session) == launch_home / "images"
def test_two_profiles_are_isolated(tmp_path):
"""Uploads from different profile sessions never share an images dir."""
home_a = tmp_path / ".hermes" / "profiles" / "a"
home_b = tmp_path / ".hermes" / "profiles" / "b"
dir_a = _session_images_dir({"profile_home": str(home_a)})
dir_b = _session_images_dir({"profile_home": str(home_b)})
assert dir_a == home_a / "images"
assert dir_b == home_b / "images"
assert dir_a != dir_b