fix: run computer use post-setup when enabling tool

This commit is contained in:
sgtworkman 2026-05-09 15:42:49 -04:00 committed by Teknium
parent fbdca64f73
commit 70d53d8b75
2 changed files with 105 additions and 11 deletions

View file

@ -482,6 +482,11 @@ TOOLSET_ENV_REQUIREMENTS = {
# ─── Post-Setup Hooks ─────────────────────────────────────────────────────────
def _cua_driver_cmd() -> str:
"""Return the cua-driver executable name/path, honoring non-empty overrides."""
return os.environ.get("HERMES_CUA_DRIVER_CMD", "").strip() or "cua-driver"
def _pip_install(
args: List[str],
*,
@ -628,7 +633,8 @@ def install_cua_driver(upgrade: bool = False) -> bool:
_print_warning(" Computer Use (cua-driver) is macOS-only; skipping.")
return False
binary = shutil.which("cua-driver")
driver_cmd = _cua_driver_cmd()
binary = shutil.which(driver_cmd)
# Not installed → fresh install path (only when caller asked for it).
if not binary and not upgrade:
@ -644,12 +650,12 @@ def install_cua_driver(upgrade: bool = False) -> bool:
if binary and not upgrade:
try:
version = subprocess.run(
["cua-driver", "--version"],
[driver_cmd, "--version"],
capture_output=True, text=True, timeout=5,
).stdout.strip()
_print_success(f" cua-driver already installed: {version or 'unknown version'}")
_print_success(f" {driver_cmd} already installed: {version or 'unknown version'}")
except Exception:
_print_success(" cua-driver already installed.")
_print_success(f" {driver_cmd} already installed.")
_print_info(" Grant macOS permissions if not done yet:")
_print_info(" System Settings > Privacy & Security > Accessibility")
_print_info(" System Settings > Privacy & Security > Screen Recording")
@ -667,7 +673,7 @@ def install_cua_driver(upgrade: bool = False) -> bool:
# Show before/after version when we have a baseline. Best-effort.
try:
before = subprocess.run(
["cua-driver", "--version"],
[driver_cmd, "--version"],
capture_output=True, text=True, timeout=5,
).stdout.strip()
except Exception:
@ -679,13 +685,13 @@ def install_cua_driver(upgrade: bool = False) -> bool:
if ok and before:
try:
after = subprocess.run(
["cua-driver", "--version"],
[driver_cmd, "--version"],
capture_output=True, text=True, timeout=5,
).stdout.strip()
if after and after != before:
_print_success(f" cua-driver upgraded: {before}{after}")
_print_success(f" {driver_cmd} upgraded: {before}{after}")
elif after:
_print_info(f" cua-driver up to date: {after}")
_print_info(f" {driver_cmd} up to date: {after}")
except Exception:
pass
return ok
@ -709,11 +715,12 @@ def _run_cua_driver_installer(label: str = "Installing", verbose: bool = True) -
_print_info(f" {label} cua-driver (macOS background computer-use)...")
else:
_print_info(f" {label} cua-driver...")
driver_cmd = _cua_driver_cmd()
try:
result = subprocess.run(install_cmd, shell=True, timeout=300)
if result.returncode == 0 and shutil.which("cua-driver"):
if result.returncode == 0 and shutil.which(driver_cmd):
if verbose:
_print_success(" cua-driver installed.")
_print_success(f" {driver_cmd} installed.")
_print_info(" IMPORTANT — grant macOS permissions now:")
_print_info(" System Settings > Privacy & Security > Accessibility")
_print_info(" System Settings > Privacy & Security > Screen Recording")
@ -1805,7 +1812,7 @@ _POST_SETUP_INSTALLED: dict = {
# entry when (a) the post_setup is the ONLY install side-effect for
# a no-key provider, and (b) an installed-state check is cheap and
# doesn't trigger a heavy import.
"cua_driver": lambda: bool(shutil.which("cua-driver")),
"cua_driver": lambda: bool(shutil.which(_cua_driver_cmd())),
}

View file

@ -12,8 +12,10 @@ from hermes_cli.tools_config import (
_get_platform_tools,
_platform_toolset_summary,
_reconfigure_tool,
_run_post_setup,
_save_platform_tools,
_toolset_has_keys,
_toolset_needs_configuration_prompt,
CONFIGURABLE_TOOLSETS,
TOOL_CATEGORIES,
_visible_providers,
@ -752,6 +754,91 @@ def test_numeric_mcp_server_name_does_not_crash_sorted():
# ─── Imagegen Backend Picker Wiring ────────────────────────────────────────
def test_toolset_has_keys_treats_no_key_providers_as_configured():
config = {}
assert _toolset_has_keys("computer_use", config) is True
def test_computer_use_needs_configuration_when_cua_driver_post_setup_pending():
"""No-key providers can still need setup when their post_setup is unsatisfied.
Returning users enabling Computer Use through `hermes tools` must reach the
cua-driver post-setup installer even though the provider has no API keys.
"""
with patch("shutil.which", return_value=None):
assert _toolset_needs_configuration_prompt("computer_use", {}) is True
def test_computer_use_skips_configuration_when_cua_driver_already_installed():
"""Installed post_setup dependencies should keep returning-user toggles no-op."""
def fake_which(name: str):
return "/usr/local/bin/cua-driver" if name == "cua-driver" else None
with patch("shutil.which", side_effect=fake_which):
assert _toolset_needs_configuration_prompt("computer_use", {}) is False
def test_computer_use_respects_custom_cua_driver_command():
"""The setup gate should match runtime's HERMES_CUA_DRIVER_CMD override."""
def fake_which(name: str):
return "/opt/bin/custom-cua" if name == "custom-cua" else None
with patch.dict("os.environ", {"HERMES_CUA_DRIVER_CMD": "custom-cua"}), \
patch("shutil.which", side_effect=fake_which):
assert _toolset_needs_configuration_prompt("computer_use", {}) is False
def test_computer_use_blank_custom_driver_command_falls_back_to_default():
"""Blank overrides should not make the setup gate look for an empty command."""
def fake_which(name: str):
return "/usr/local/bin/cua-driver" if name == "cua-driver" else None
with patch.dict("os.environ", {"HERMES_CUA_DRIVER_CMD": " "}), \
patch("shutil.which", side_effect=fake_which):
assert _toolset_needs_configuration_prompt("computer_use", {}) is False
def test_computer_use_post_setup_respects_custom_driver_command_when_installed():
"""post_setup already-installed checks should version-probe the override."""
def fake_which(name: str):
return "/opt/bin/custom-cua" if name == "custom-cua" else None
with patch.dict("os.environ", {"HERMES_CUA_DRIVER_CMD": "custom-cua"}), \
patch("platform.system", return_value="Darwin"), \
patch("shutil.which", side_effect=fake_which), \
patch("subprocess.run") as run:
run.return_value.stdout = "custom 1.2.3\n"
_run_post_setup("cua_driver")
run.assert_called_once()
assert run.call_args.args[0] == ["custom-cua", "--version"]
def test_computer_use_post_setup_missing_override_does_not_accept_default_binary():
"""A default cua-driver binary must not satisfy a missing runtime override."""
seen = []
def fake_which(name: str):
seen.append(name)
if name == "cua-driver":
return "/usr/local/bin/cua-driver"
if name == "curl":
return None
return None
with patch.dict("os.environ", {"HERMES_CUA_DRIVER_CMD": "custom-cua"}), \
patch("platform.system", return_value="Darwin"), \
patch("shutil.which", side_effect=fake_which), \
patch("subprocess.run") as run:
_run_post_setup("cua_driver")
run.assert_not_called()
assert "custom-cua" in seen
assert "curl" in seen
class TestImagegenBackendRegistry:
"""IMAGEGEN_BACKENDS tags drive the model picker flow in tools_config."""