mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
fix(update): skip cua-driver refresh when Applications is unwritable
This commit is contained in:
parent
c13281ab57
commit
d8b51269ca
5 changed files with 98 additions and 4 deletions
|
|
@ -2952,6 +2952,11 @@ DEFAULT_CONFIG = {
|
|||
# ignored paths — node_modules, venv, build outputs —
|
||||
# are never touched.
|
||||
"non_interactive_local_changes": "stash",
|
||||
# Refresh an already-installed cua-driver during `hermes update`.
|
||||
# The refresh is best-effort and macOS-only. Turn this off if the
|
||||
# upstream installer is not appropriate for the machine, for example
|
||||
# on non-admin accounts where `/Applications` is not writable.
|
||||
"refresh_cua_driver": True,
|
||||
},
|
||||
|
||||
# Language Server Protocol — semantic diagnostics from real
|
||||
|
|
|
|||
|
|
@ -10243,7 +10243,23 @@ def _cmd_update_impl(args, gateway_mode: bool):
|
|||
# predictable cadence (matches when they pull new agent code) without
|
||||
# adding startup latency or a per-launch GitHub API call.
|
||||
try:
|
||||
if sys.platform in ("darwin", "win32", "linux") and shutil.which("cua-driver"):
|
||||
refresh_cua_driver = True
|
||||
try:
|
||||
from hermes_cli.config import load_config
|
||||
|
||||
_update_cfg = (load_config() or {}).get("updates", {})
|
||||
if isinstance(_update_cfg, dict):
|
||||
refresh_cua_driver = bool(
|
||||
_update_cfg.get("refresh_cua_driver", True)
|
||||
)
|
||||
except Exception as cfg_exc:
|
||||
logger.debug("Could not read updates.refresh_cua_driver: %s", cfg_exc)
|
||||
|
||||
if (
|
||||
refresh_cua_driver
|
||||
and sys.platform in ("darwin", "win32", "linux")
|
||||
and shutil.which("cua-driver")
|
||||
):
|
||||
from hermes_cli.tools_config import install_cua_driver
|
||||
|
||||
print()
|
||||
|
|
|
|||
|
|
@ -705,6 +705,19 @@ def _pip_install(
|
|||
# no Python-side duplication.
|
||||
|
||||
|
||||
def _cua_install_target_writable() -> bool:
|
||||
"""Return whether the upstream installer can write its app bundle target."""
|
||||
if sys.platform != "darwin":
|
||||
return True
|
||||
applications_dir = "/Applications"
|
||||
try:
|
||||
if not os.path.isdir(applications_dir):
|
||||
return True
|
||||
return os.access(applications_dir, os.W_OK)
|
||||
except Exception:
|
||||
return True
|
||||
|
||||
|
||||
def install_cua_driver(upgrade: bool = False) -> bool:
|
||||
"""Install or refresh the cua-driver binary used by Computer Use.
|
||||
|
||||
|
|
@ -747,6 +760,14 @@ def install_cua_driver(upgrade: bool = False) -> bool:
|
|||
|
||||
# Not installed → fresh install path (only when caller asked for it).
|
||||
if not binary and not upgrade:
|
||||
if not _cua_install_target_writable():
|
||||
_print_info(
|
||||
" /Applications is not writable; skipping cua-driver install."
|
||||
)
|
||||
_print_info(
|
||||
" Run from an admin account or install cua-driver manually."
|
||||
)
|
||||
return False
|
||||
if not shutil.which(fetch_tool):
|
||||
_print_warning(f" {fetch_tool} not found — install manually:")
|
||||
_print_info(" https://github.com/trycua/cua/blob/main/libs/cua-driver/README.md")
|
||||
|
|
@ -778,6 +799,15 @@ def install_cua_driver(upgrade: bool = False) -> bool:
|
|||
return True
|
||||
|
||||
# upgrade=True path — refresh to the latest upstream release.
|
||||
if not _cua_install_target_writable():
|
||||
_print_info(
|
||||
" /Applications is not writable; skipping cua-driver refresh."
|
||||
)
|
||||
_print_info(
|
||||
" Run `hermes computer-use install --upgrade` from an admin account to update it."
|
||||
)
|
||||
return bool(binary)
|
||||
|
||||
if not shutil.which(fetch_tool):
|
||||
_print_warning(f" {fetch_tool} not found — cannot refresh cua-driver.")
|
||||
return bool(binary)
|
||||
|
|
|
|||
|
|
@ -708,6 +708,14 @@ _SCHEMA_OVERRIDES: Dict[str, Dict[str, Any]] = {
|
|||
),
|
||||
"options": ["stash", "discard"],
|
||||
},
|
||||
"updates.refresh_cua_driver": {
|
||||
"type": "bool",
|
||||
"description": (
|
||||
"Refresh an already-installed cua-driver during hermes update. "
|
||||
"Disable this on non-admin macOS accounts where /Applications is "
|
||||
"not writable."
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
# Categories with fewer fields get merged into "general" to avoid tab sprawl.
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ The cua-driver upstream installer always pulls the latest release tag, so
|
|||
re-running it is the canonical upgrade path. ``install_cua_driver(upgrade=True)``
|
||||
must:
|
||||
|
||||
* Be macOS-only — no-op silently on Linux/Windows so ``hermes update`` can
|
||||
call it unconditionally without warning every non-macOS user.
|
||||
* Be supported-platform-only — no-op silently elsewhere so ``hermes update``
|
||||
can call it unconditionally without warning unsupported-platform users.
|
||||
* Re-run the installer even when the binary is already on PATH (this is the
|
||||
fix for the "we only pulled cua-driver once on enable" complaint).
|
||||
* Preserve original ``upgrade=False`` behaviour for the toolset-enable flow:
|
||||
skip if installed, install otherwise, warn on non-macOS.
|
||||
skip if installed, install otherwise, warn on unsupported platforms.
|
||||
|
||||
The pre-install arch probe that used to live alongside this function was
|
||||
deleted (see top-of-file comment in tools_config.py) — the upstream
|
||||
|
|
@ -67,6 +67,41 @@ class TestInstallCuaDriverUpgrade:
|
|||
assert tools_config.install_cua_driver(upgrade=True) is True
|
||||
runner.assert_called_once()
|
||||
|
||||
def test_upgrade_on_macos_non_writable_applications_skips_refresh(self):
|
||||
from hermes_cli import tools_config
|
||||
|
||||
with patch("platform.system", return_value="Darwin"), \
|
||||
patch.object(tools_config.shutil, "which",
|
||||
side_effect=lambda n: "/usr/local/bin/" + n
|
||||
if n in {"cua-driver", "curl"} else None), \
|
||||
patch.object(tools_config, "_cua_install_target_writable",
|
||||
return_value=False), \
|
||||
patch.object(tools_config, "_run_cua_driver_installer") as runner, \
|
||||
patch.object(tools_config, "_print_info") as info:
|
||||
assert tools_config.install_cua_driver(upgrade=True) is True
|
||||
runner.assert_not_called()
|
||||
assert any(
|
||||
"/Applications is not writable" in call.args[0]
|
||||
for call in info.call_args_list
|
||||
)
|
||||
|
||||
def test_fresh_install_on_macos_non_writable_applications_skips_install(self):
|
||||
from hermes_cli import tools_config
|
||||
|
||||
with patch("platform.system", return_value="Darwin"), \
|
||||
patch.object(tools_config.shutil, "which",
|
||||
side_effect=lambda n: "/usr/bin/curl" if n == "curl" else None), \
|
||||
patch.object(tools_config, "_cua_install_target_writable",
|
||||
return_value=False), \
|
||||
patch.object(tools_config, "_run_cua_driver_installer") as runner, \
|
||||
patch.object(tools_config, "_print_info") as info:
|
||||
assert tools_config.install_cua_driver(upgrade=False) is False
|
||||
runner.assert_not_called()
|
||||
assert any(
|
||||
"/Applications is not writable" in call.args[0]
|
||||
for call in info.call_args_list
|
||||
)
|
||||
|
||||
def test_non_upgrade_on_macos_with_binary_skips_install(self):
|
||||
from hermes_cli import tools_config
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue