mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
fix: add macOS Homebrew paths to browser and terminal PATH resolution
On macOS with Homebrew (Apple Silicon), Node.js and agent-browser binaries live under /opt/homebrew/bin/ which is not included in the _SANE_PATH fallback used by browser_tool.py and environments/local.py. When Hermes runs with a filtered PATH (e.g. as a systemd service), these binaries are invisible, causing 'env: node: No such file or directory' errors when using browser tools. Changes: - Add /opt/homebrew/bin and /opt/homebrew/sbin to _SANE_PATH in both browser_tool.py and environments/local.py - Add _discover_homebrew_node_dirs() to find versioned Node installs (e.g. brew install node@24) that aren't linked into /opt/homebrew/bin - Extend _find_agent_browser() to search Homebrew and Hermes-managed dirs when agent-browser isn't on the current PATH - Include discovered Homebrew node dirs in subprocess PATH when launching agent-browser - Add 11 new tests covering all Homebrew path discovery logic
This commit is contained in:
parent
6e97a3b338
commit
1345e93393
4 changed files with 357 additions and 8 deletions
|
|
@ -76,8 +76,35 @@ from tools.browser_providers.browser_use import BrowserUseProvider
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Standard PATH entries for environments with minimal PATH (e.g. systemd services)
|
||||
_SANE_PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
# Standard PATH entries for environments with minimal PATH (e.g. systemd services).
|
||||
# Includes macOS Homebrew paths (/opt/homebrew/* for Apple Silicon).
|
||||
_SANE_PATH = (
|
||||
"/opt/homebrew/bin:/opt/homebrew/sbin:"
|
||||
"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
)
|
||||
|
||||
|
||||
def _discover_homebrew_node_dirs() -> list[str]:
|
||||
"""Find Homebrew versioned Node.js bin directories (e.g. node@20, node@24).
|
||||
|
||||
When Node is installed via ``brew install node@24`` and NOT linked into
|
||||
/opt/homebrew/bin, the binary lives only in /opt/homebrew/opt/node@24/bin/.
|
||||
This function discovers those paths so they can be added to subprocess PATH.
|
||||
"""
|
||||
dirs: list[str] = []
|
||||
homebrew_opt = "/opt/homebrew/opt"
|
||||
if not os.path.isdir(homebrew_opt):
|
||||
return dirs
|
||||
try:
|
||||
for entry in os.listdir(homebrew_opt):
|
||||
if entry.startswith("node") and entry != "node":
|
||||
# e.g. node@20, node@24
|
||||
bin_dir = os.path.join(homebrew_opt, entry, "bin")
|
||||
if os.path.isdir(bin_dir):
|
||||
dirs.append(bin_dir)
|
||||
except OSError:
|
||||
pass
|
||||
return dirs
|
||||
|
||||
# Throttle screenshot cleanup to avoid repeated full directory scans.
|
||||
_last_screenshot_cleanup_by_dir: dict[str, float] = {}
|
||||
|
|
@ -619,7 +646,8 @@ def _find_agent_browser() -> str:
|
|||
"""
|
||||
Find the agent-browser CLI executable.
|
||||
|
||||
Checks in order: PATH, local node_modules/.bin/, npx fallback.
|
||||
Checks in order: current PATH, Homebrew/common bin dirs, Hermes-managed
|
||||
node, local node_modules/.bin/, npx fallback.
|
||||
|
||||
Returns:
|
||||
Path to agent-browser executable
|
||||
|
|
@ -632,15 +660,36 @@ def _find_agent_browser() -> str:
|
|||
which_result = shutil.which("agent-browser")
|
||||
if which_result:
|
||||
return which_result
|
||||
|
||||
|
||||
# Build an extended search PATH including Homebrew and Hermes-managed dirs.
|
||||
# This covers macOS where the process PATH may not include Homebrew paths.
|
||||
extra_dirs: list[str] = []
|
||||
for d in ["/opt/homebrew/bin", "/usr/local/bin"]:
|
||||
if os.path.isdir(d):
|
||||
extra_dirs.append(d)
|
||||
extra_dirs.extend(_discover_homebrew_node_dirs())
|
||||
|
||||
hermes_home = Path(os.environ.get("HERMES_HOME", Path.home() / ".hermes"))
|
||||
hermes_node_bin = str(hermes_home / "node" / "bin")
|
||||
if os.path.isdir(hermes_node_bin):
|
||||
extra_dirs.append(hermes_node_bin)
|
||||
|
||||
if extra_dirs:
|
||||
extended_path = os.pathsep.join(extra_dirs)
|
||||
which_result = shutil.which("agent-browser", path=extended_path)
|
||||
if which_result:
|
||||
return which_result
|
||||
|
||||
# Check local node_modules/.bin/ (npm install in repo root)
|
||||
repo_root = Path(__file__).parent.parent
|
||||
local_bin = repo_root / "node_modules" / ".bin" / "agent-browser"
|
||||
if local_bin.exists():
|
||||
return str(local_bin)
|
||||
|
||||
# Check common npx locations
|
||||
# Check common npx locations (also search extended dirs)
|
||||
npx_path = shutil.which("npx")
|
||||
if not npx_path and extra_dirs:
|
||||
npx_path = shutil.which("npx", path=os.pathsep.join(extra_dirs))
|
||||
if npx_path:
|
||||
return "npx agent-browser"
|
||||
|
||||
|
|
@ -742,13 +791,18 @@ def _run_browser_command(
|
|||
|
||||
browser_env = {**os.environ}
|
||||
|
||||
# Ensure PATH includes Hermes-managed Node first, then standard system dirs.
|
||||
# Ensure PATH includes Hermes-managed Node first, Homebrew versioned
|
||||
# node dirs (for macOS ``brew install node@24``), then standard system dirs.
|
||||
hermes_home = Path(os.environ.get("HERMES_HOME", Path.home() / ".hermes"))
|
||||
hermes_node_bin = str(hermes_home / "node" / "bin")
|
||||
|
||||
existing_path = browser_env.get("PATH", "")
|
||||
path_parts = [p for p in existing_path.split(":") if p]
|
||||
candidate_dirs = [hermes_node_bin] + [p for p in _SANE_PATH.split(":") if p]
|
||||
candidate_dirs = (
|
||||
[hermes_node_bin]
|
||||
+ _discover_homebrew_node_dirs()
|
||||
+ [p for p in _SANE_PATH.split(":") if p]
|
||||
)
|
||||
|
||||
for part in reversed(candidate_dirs):
|
||||
if os.path.isdir(part) and part not in path_parts:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue