diff --git a/pkgbuilds/hermes-desktop/PKGBUILD b/pkgbuilds/hermes-desktop/PKGBUILD index b37a12a..2bb7fdc 100644 --- a/pkgbuilds/hermes-desktop/PKGBUILD +++ b/pkgbuilds/hermes-desktop/PKGBUILD @@ -5,7 +5,7 @@ pkgname=hermes-desktop pkgver=2026.9.7 -pkgrel=2 +pkgrel=3 pkgdesc='Native desktop shell for Hermes Agent' arch=('x86_64') url='https://github.com/NousResearch/hermes-agent' @@ -75,11 +75,11 @@ source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/refs/tags/v${pkgver}.tar.gz 'runtime.patch' 'runtime-test.py') sha256sums=('907c2a72db1c5dd637ea8eeae97f4cb5b32cef615c17258f6b190924ec5bf688' - 'c68233f93387251f08537559c072c9ec36ba9a304b1669decc56df17c464b252' + 'f7519cee8e0f64d9c4859dc1d2eb7e223f94ab22ab522a66cbaada05944ef71b' '3ef685bfcf366776b025d26c37d32854d8d4aa2023b2bd07c8e08b001ef1e8c4' 'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52' '9d5015d1be762a901f8f64319981ae862e9852fa5cb9a22a2ba1e691f90430a2' - '514a5e7ab2b7262141a2588c5b5036832cb4ba789a9578b8b50b6d79a9d63deb') + '84373e503dc5ba5ce099c57150f281247dda941b477c0cd679c50fe0d3a0bbb1') build() { cd "${srcdir}/${_srcdir}" diff --git a/pkgbuilds/hermes-desktop/hermes-desktop.sh b/pkgbuilds/hermes-desktop/hermes-desktop.sh old mode 100644 new mode 100755 index db23758..f361d5a --- a/pkgbuilds/hermes-desktop/hermes-desktop.sh +++ b/pkgbuilds/hermes-desktop/hermes-desktop.sh @@ -2,7 +2,6 @@ set -euo pipefail unset ELECTRON_RUN_AS_NODE PYTHONPATH PYTHONHOME -export HERMES_DESKTOP_IGNORE_EXISTING=1 hermes_home=$(realpath -ms -- "${HERMES_HOME:-$HOME/.hermes}") parent=${hermes_home%/*} @@ -15,6 +14,9 @@ runtime="$hermes_home/hermes-agent" native="$runtime/apps/desktop/release/linux-unpacked/Hermes" if [[ ! -x $native || ! -x $runtime/venv/bin/hermes ]]; then native=/opt/hermes-desktop/Hermes + # Only the packaged app, built from the release commit rather than the runtime's + # checkout, is told to skip an existing Hermes. + export HERMES_DESKTOP_IGNORE_EXISTING=${HERMES_DESKTOP_IGNORE_EXISTING:-1} fi # Both app locations use namespaces, never a user-writable setuid helper. @@ -36,7 +38,7 @@ import sys native, runtime, *args = sys.argv[1:] env = os.environ.copy() -flags, gpu, store, ozone = [], "auto", "auto", "auto" +flags, gpu, store, ozone, a11y = [], "auto", "auto", "auto", True if runtime: sys.path.insert(0, runtime) try: @@ -47,7 +49,10 @@ if runtime: from hermes_cli.main import _desktop_launch_options from hermes_constants import with_hermes_node_path - flags, gpu, store, ozone = _desktop_launch_options() + # Newer runtimes append renderer_accessibility; the packaged release returns four. + flags, gpu, store, ozone, *extra = _desktop_launch_options() + if extra: + a11y = extra[0] env = with_hermes_node_path(env) except ImportError: print("Could not load Hermes desktop settings; using launch defaults.", file=sys.stderr) @@ -58,6 +63,9 @@ if gpu != "auto": if ozone != "auto": env.setdefault("ELECTRON_OZONE_PLATFORM_HINT", ozone) env.setdefault("HERMES_DESKTOP_PASSWORD_STORE", store if store != "auto" else "gnome-libsecret") +# The app keeps its accessibility tree on unless told otherwise, so bridge only the opt-out. +if not a11y: + env.setdefault("HERMES_DESKTOP_RENDERER_ACCESSIBILITY", "0") # Explicit config, environment and command-line choices override the Wayland default. if (env.get("WAYLAND_DISPLAY") or env.get("XDG_SESSION_TYPE") == "wayland") and ( diff --git a/pkgbuilds/hermes-desktop/runtime-test.py b/pkgbuilds/hermes-desktop/runtime-test.py index 1f0fe91..c4e6bae 100644 --- a/pkgbuilds/hermes-desktop/runtime-test.py +++ b/pkgbuilds/hermes-desktop/runtime-test.py @@ -31,6 +31,9 @@ Path(os.environ["TEST_OUTPUT"]).write_text(json.dumps({ "gpu": os.environ.get("HERMES_DESKTOP_DISABLE_GPU"), "ozone": os.environ.get("ELECTRON_OZONE_PLATFORM_HINT"), "cwd": os.environ.get("HERMES_DESKTOP_CWD"), + "a11y": os.environ.get("HERMES_DESKTOP_RENDERER_ACCESSIBILITY"), + "ignore": os.environ.get("HERMES_DESKTOP_IGNORE_EXISTING"), + "root": os.environ.get("HERMES_DESKTOP_HERMES_ROOT"), "inherited": [name for name in ("ELECTRON_RUN_AS_NODE", "PYTHONPATH", "PYTHONHOME") if name in os.environ], })) ''') @@ -88,17 +91,22 @@ def with_hermes_node_path(env=None): "TEST_OUTPUT": str(output), "TEST_FORBIDDEN": str(forbidden_output)} launch = ["bash", str(launcher.resolve())] - def check_launch(args=(), overrides=None, expected_args=(), store="gnome-libsecret", gpu=None, ozone=None): + def check_launch(args=(), overrides=None, expected_args=(), store="gnome-libsecret", gpu=None, ozone=None, + a11y=None, ignore=None): subprocess.run(launch + list(args), env={**env, **(overrides or {})}, cwd=home, check=True) result = json.loads(output.read_text()) assert result == {"args": ["--disable-setuid-sandbox", *expected_args], "home": str(home / ".hermes"), "store": store, "gpu": gpu, - "ozone": ozone, "cwd": str(home), "inherited": []}, result + "ozone": ozone, "cwd": str(home), "a11y": a11y, "ignore": ignore, "root": None, + "inherited": []}, result assert not forbidden_output.exists(), "launcher invoked the Omarchy installer or sudo" output.unlink() wayland = {"WAYLAND_DISPLAY": "wayland-1"} check_launch(overrides=wayland, expected_args=["--ozone-platform=wayland"]) + # The runtime's own app finds its runtime unaided; an explicit request to skip it still passes through. + check_launch(overrides={**wayland, "HERMES_DESKTOP_IGNORE_EXISTING": "1"}, + expected_args=["--ozone-platform=wayland"], ignore="1") url = "hermes://open?text=a%20b" check_launch(["--ozone-platform=x11", url], {**wayland, "HERMES_DESKTOP_PASSWORD_STORE": "kwallet6"}, ["--ozone-platform=x11", url], store="kwallet6") @@ -122,6 +130,19 @@ def with_hermes_node_path(env=None): (module / "main_desktop.py").write_text(helper) (module / "main.py").write_text('raise AssertionError("old helper import after update")\n') check_launch([url], wayland, ["--ozone-platform=x11", url], gpu="0") + # Later runtimes return a fifth option, the renderer accessibility switch. + (module / "main_desktop.py").write_text(helper + ''' +_first_four = _desktop_launch_options +def _desktop_launch_options(): + from hermes_cli.config import load_config + return (*_first_four(), load_config()["desktop"].get("renderer_accessibility", True)) +''') + check_launch([url], wayland, ["--ozone-platform=x11", url], gpu="0") + config.write_text(json.dumps({"desktop": {"renderer_accessibility": False}})) + check_launch(overrides=wayland, expected_args=["--ozone-platform=wayland"], a11y="0") + check_launch(overrides={**wayland, "HERMES_DESKTOP_RENDERER_ACCESSIBILITY": "1"}, + expected_args=["--ozone-platform=wayland"], a11y="1") + (module / "main_desktop.py").write_text(helper) config.unlink() (module / "main_desktop.py").write_text(helper + '\nimport os\nos.environ.update(' + repr({ "ELECTRON_RUN_AS_NODE": "1", "PYTHONPATH": "/invalid", "PYTHONHOME": "/invalid", @@ -149,11 +170,15 @@ def with_hermes_node_path(env=None): fallback_launcher.write_text(launcher.read_text().replace("/opt/hermes-desktop/Hermes", str(fallback))) launch = ["bash", str(fallback_launcher)] executable.unlink() - check_launch([url], wayland, ["--ozone-platform=wayland", url]) + # Only the packaged app is told to skip an existing Hermes, unless the environment already says. + check_launch([url], wayland, ["--ozone-platform=wayland", url], ignore="1") + check_launch([url], {**wayland, "HERMES_DESKTOP_IGNORE_EXISTING": "0"}, ["--ozone-platform=wayland", url], + ignore="0") (module / "main_desktop.py").write_text('raise ImportError("incomplete Python dependencies")\n') - check_launch([url], wayland, ["--ozone-platform=wayland", url]) + check_launch([url], wayland, ["--ozone-platform=wayland", url], ignore="1") shutil.rmtree(runtime / "venv") - check_launch(overrides={"ELECTRON_RUN_AS_NODE": "1", "PYTHONPATH": "/invalid", "PYTHONHOME": "/invalid"}) + check_launch(overrides={"ELECTRON_RUN_AS_NODE": "1", "PYTHONPATH": "/invalid", "PYTHONHOME": "/invalid"}, + ignore="1") check_namespace_failure() gate = ["bash", str(destination), "--self-test-gate", "--install-root", str(runtime),