fix(update): also defer the missing-binary CUA install on Windows
Follow-up to the salvaged #94296: the two guards covered the repair and confirmed-update branches, but when cua-driver is enabled yet not installed at all, control still reached _run_cua_driver_installer() and an automatic 'hermes update' would launch the interactive install.ps1 anyway. Add the same defer before the installer run, keep POSIX behavior unchanged, and give the confirmed-update message a natural fallback when latest_version is unknown.
This commit is contained in:
@@ -1190,12 +1190,19 @@ def install_cua_driver(
|
||||
return True
|
||||
if _state is not None and _state.get("update_available"):
|
||||
if is_windows and require_confirmed_update:
|
||||
_latest = _state.get("latest_version") or "a newer version"
|
||||
_print_info(
|
||||
f" {driver_cmd} {_latest} is available; keeping the "
|
||||
"installed version because its Windows installer may "
|
||||
"require interactive consent."
|
||||
)
|
||||
_latest = _state.get("latest_version")
|
||||
if _latest:
|
||||
_print_info(
|
||||
f" {driver_cmd} {_latest} is available; keeping the "
|
||||
"installed version because its Windows installer may "
|
||||
"require interactive consent."
|
||||
)
|
||||
else:
|
||||
_print_info(
|
||||
f" A newer {driver_cmd} release is available; "
|
||||
"keeping the installed version because its Windows "
|
||||
"installer may require interactive consent."
|
||||
)
|
||||
_print_info(
|
||||
" Update it from an interactive terminal with: "
|
||||
"hermes computer-use install --upgrade"
|
||||
@@ -1214,6 +1221,22 @@ def install_cua_driver(
|
||||
if _re.fullmatch(r"\d+(\.\d+)*", _latest):
|
||||
confirmed_version = _latest
|
||||
|
||||
if is_windows and require_confirmed_update and not binary:
|
||||
# Missing-binary path (driver enabled in config but never installed,
|
||||
# or wiped by a failed install). Same rule as the repair and
|
||||
# confirmed-update branches above: an automatic Windows update must
|
||||
# never launch install.ps1, which can demand console/UAC consent the
|
||||
# hidden updater cannot provide (#87703).
|
||||
_print_info(
|
||||
" cua-driver is not installed; automatic Windows updates "
|
||||
"cannot safely run its interactive installer."
|
||||
)
|
||||
_print_info(
|
||||
" Install it from an interactive terminal with: "
|
||||
"hermes computer-use install --upgrade"
|
||||
)
|
||||
return False
|
||||
|
||||
if binary:
|
||||
# Show before/after version when we have a baseline. Best-effort.
|
||||
try:
|
||||
|
||||
@@ -440,7 +440,8 @@ class TestRequireConfirmedUpdate:
|
||||
still reinstall when the check can't answer.
|
||||
"""
|
||||
|
||||
def _install(self, check_state, require_confirmed, contract_status=None):
|
||||
def _install(self, check_state, require_confirmed, contract_status=None,
|
||||
binary_missing=False):
|
||||
"""Drive ``install_cua_driver`` on the host, whatever it is.
|
||||
|
||||
The old signature took a ``system`` string and faked
|
||||
@@ -454,11 +455,15 @@ class TestRequireConfirmedUpdate:
|
||||
|
||||
from hermes_cli import tools_config
|
||||
|
||||
_which_names = {"curl", "powershell"}
|
||||
if not binary_missing:
|
||||
_which_names.add("cua-driver")
|
||||
with patch.object(tools_config.shutil, "which",
|
||||
side_effect=lambda n: "/x/" + n
|
||||
if n in {"cua-driver", "curl", "powershell"} else None), \
|
||||
if n in _which_names else None), \
|
||||
patch.object(tools_config, "_resolved_cua_driver_cmd",
|
||||
return_value="/x/cua-driver"), \
|
||||
return_value=None if binary_missing
|
||||
else "/x/cua-driver"), \
|
||||
patch.object(tools_config, "_cua_install_target_writable",
|
||||
return_value=True), \
|
||||
patch.object(
|
||||
@@ -546,6 +551,38 @@ class TestRequireConfirmedUpdate:
|
||||
for call in info.call_args_list
|
||||
)
|
||||
|
||||
@pytest.mark.windows_only
|
||||
def test_windows_missing_binary_defers_interactive_install(self):
|
||||
"""Driver enabled but never installed (or wiped by a failed install):
|
||||
the automatic update must not launch install.ps1 either — this path
|
||||
reached the installer before the top-level guard (#94296 review)."""
|
||||
ok, runner, info = self._install(
|
||||
None,
|
||||
require_confirmed=True,
|
||||
binary_missing=True,
|
||||
)
|
||||
|
||||
assert ok is False
|
||||
runner.assert_not_called()
|
||||
assert any(
|
||||
"computer-use install --upgrade" in call.args[0]
|
||||
for call in info.call_args_list
|
||||
)
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.platform == "win32",
|
||||
reason="POSIX installers are non-interactive; missing binary installs",
|
||||
)
|
||||
def test_posix_missing_binary_still_installs(self):
|
||||
ok, runner, _ = self._install(
|
||||
None,
|
||||
require_confirmed=True,
|
||||
binary_missing=True,
|
||||
)
|
||||
|
||||
assert ok is True
|
||||
runner.assert_called_once()
|
||||
|
||||
def test_up_to_date_short_circuits(self):
|
||||
state = {"current_version": "0.6.0", "latest_version": "0.6.0",
|
||||
"update_available": False}
|
||||
|
||||
Reference in New Issue
Block a user