fix(browser): add termux PATH fallbacks

Refactor browser tool PATH construction to include Termux directories
(/data/data/com.termux/files/usr/bin, /data/data/com.termux/files/usr/sbin)
so agent-browser and npx are discoverable on Android/Termux.

Extracts _browser_candidate_path_dirs() and _merge_browser_path() helpers
to centralize PATH construction shared between _find_agent_browser() and
_run_browser_command(), replacing duplicated inline logic.

Also fixes os.pathsep usage (was hardcoded ':') for cross-platform correctness.

Cherry-picked from PR #9846.
This commit is contained in:
adybag14-cyber
2026-04-14 16:47:36 -07:00
committed by Teknium
parent 3ca7417c2a
commit 56c34ac4f7
2 changed files with 145 additions and 46 deletions

View File

@@ -94,11 +94,21 @@ except ImportError:
logger = logging.getLogger(__name__)
# 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"
# Includes Android/Termux and macOS Homebrew locations needed for agent-browser,
# npx, node, and Android's glibc runner (grun).
_SANE_PATH_DIRS = (
"/data/data/com.termux/files/usr/bin",
"/data/data/com.termux/files/usr/sbin",
"/opt/homebrew/bin",
"/opt/homebrew/sbin",
"/usr/local/sbin",
"/usr/local/bin",
"/usr/sbin",
"/usr/bin",
"/sbin",
"/bin",
)
_SANE_PATH = os.pathsep.join(_SANE_PATH_DIRS)
@functools.lru_cache(maxsize=1)
@@ -123,6 +133,28 @@ def _discover_homebrew_node_dirs() -> tuple[str, ...]:
pass
return tuple(dirs)
def _browser_candidate_path_dirs() -> list[str]:
"""Return ordered browser CLI PATH candidates shared by discovery and execution."""
hermes_home = get_hermes_home()
hermes_node_bin = str(hermes_home / "node" / "bin")
return [hermes_node_bin, *list(_discover_homebrew_node_dirs()), *_SANE_PATH_DIRS]
def _merge_browser_path(existing_path: str = "") -> str:
"""Prepend browser-specific PATH fallbacks without reordering existing entries."""
path_parts = [p for p in (existing_path or "").split(os.pathsep) if p]
existing_parts = set(path_parts)
prefix_parts: list[str] = []
for part in _browser_candidate_path_dirs():
if not part or part in existing_parts or part in prefix_parts:
continue
if os.path.isdir(part):
prefix_parts.append(part)
return os.pathsep.join(prefix_parts + path_parts)
# Throttle screenshot cleanup to avoid repeated full directory scans.
_last_screenshot_cleanup_by_dir: dict[str, float] = {}
@@ -895,21 +927,10 @@ def _find_agent_browser() -> str:
_agent_browser_resolved = True
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 = get_hermes_home()
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)
# Build an extended search PATH including Hermes-managed Node, macOS
# versioned Homebrew installs, and fallback system dirs like Termux.
extended_path = _merge_browser_path("")
if extended_path:
which_result = shutil.which("agent-browser", path=extended_path)
if which_result:
_cached_agent_browser = which_result
@@ -924,10 +945,10 @@ def _find_agent_browser() -> str:
_agent_browser_resolved = True
return _cached_agent_browser
# Check common npx locations (also search extended dirs)
# Check common npx locations (also search the extended fallback PATH)
npx_path = shutil.which("npx")
if not npx_path and extra_dirs:
npx_path = shutil.which("npx", path=os.pathsep.join(extra_dirs))
if not npx_path and extended_path:
npx_path = shutil.which("npx", path=extended_path)
if npx_path:
_cached_agent_browser = "npx agent-browser"
_agent_browser_resolved = True
@@ -1046,24 +1067,9 @@ def _run_browser_command(
browser_env = {**os.environ}
# Ensure PATH includes Hermes-managed Node first, Homebrew versioned
# node dirs (for macOS ``brew install node@24``), then standard system dirs.
hermes_home = get_hermes_home()
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]
+ list(_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:
path_parts.insert(0, part)
browser_env["PATH"] = ":".join(path_parts)
# Ensure subprocesses inherit the same browser-specific PATH fallbacks
# used during CLI discovery.
browser_env["PATH"] = _merge_browser_path(browser_env.get("PATH", ""))
browser_env["AGENT_BROWSER_SOCKET_DIR"] = task_socket_dir
# Use temp files for stdout/stderr instead of pipes.