diff --git a/tests/tools/test_terminal_tool.py b/tests/tools/test_terminal_tool.py index fe2f5e3f514..ea113e63c27 100644 --- a/tests/tools/test_terminal_tool.py +++ b/tests/tools/test_terminal_tool.py @@ -90,6 +90,30 @@ def test_cached_sudo_password_is_used_when_env_is_unset(monkeypatch): assert sudo_stdin == "cached-pass\n" +def test_registered_sudo_callback_is_used_without_interactive_env(monkeypatch): + monkeypatch.delenv("SUDO_PASSWORD", raising=False) + monkeypatch.delenv("HERMES_INTERACTIVE", raising=False) + monkeypatch.setattr(terminal_tool, "_sudo_nopasswd_works", lambda: False) + + calls = [] + + def sudo_callback(): + calls.append("called") + return "callback-pass" + + terminal_tool.set_sudo_password_callback(sudo_callback) + try: + transformed, sudo_stdin = terminal_tool._transform_sudo_command( + "echo ok | sudo tee /tmp/hermes-test" + ) + finally: + terminal_tool.set_sudo_password_callback(None) + + assert calls == ["called"] + assert transformed == "echo ok | sudo -S -p '' tee /tmp/hermes-test" + assert sudo_stdin == "callback-pass\n" + + def test_cached_sudo_password_isolated_by_session_key(monkeypatch): monkeypatch.delenv("SUDO_PASSWORD", raising=False) monkeypatch.delenv("HERMES_INTERACTIVE", raising=False) diff --git a/tools/terminal_tool.py b/tools/terminal_tool.py index d9edd7a5d5d..2ad882fba25 100644 --- a/tools/terminal_tool.py +++ b/tools/terminal_tool.py @@ -777,7 +777,8 @@ def _transform_sudo_command(command: str | None) -> tuple[str | None, str | None the password in the command string themselves; see their execute() methods for how they handle the non-None sudo_stdin case. - If SUDO_PASSWORD is not set and in interactive mode (HERMES_INTERACTIVE=1): + If SUDO_PASSWORD is not set and an interactive UI is available + (HERMES_INTERACTIVE=1 or a registered sudo password callback): Prompts user for password with 45s timeout, caches for session. If SUDO_PASSWORD is not set and NOT interactive: @@ -805,7 +806,11 @@ def _transform_sudo_command(command: str | None) -> tuple[str | None, str | None if not has_configured_password and not sudo_password and _sudo_nopasswd_works(): return command, None - if not has_configured_password and not sudo_password and env_var_enabled("HERMES_INTERACTIVE"): + has_sudo_prompt_callback = _get_sudo_password_callback() is not None + should_prompt_for_sudo = ( + env_var_enabled("HERMES_INTERACTIVE") or has_sudo_prompt_callback + ) + if not has_configured_password and not sudo_password and should_prompt_for_sudo: sudo_password = _prompt_for_sudo_password(timeout_seconds=45) if sudo_password: _set_cached_sudo_password(sudo_password)