hermes-agent/tools
ethernet 25d0bcd424 fix(runtime): resolve Hermes-managed Node and uv before bare PATH
Hermes installs runtimes for itself — `uv` at `$HERMES_HOME/bin/uv`, Node
at `$HERMES_HOME/node` — and neither directory is on an arbitrary
process's PATH. Every `shutil.which("node"/"npm"/"npx"/"uv")` in Hermes's
own code therefore has two failure modes: the managed runtime is invisible,
so the caller reports "not installed" or degrades to a slower tier on a
machine that has exactly what it needed; and when a system copy also
exists, the one Hermes does not own wins.

Routed the Hermes-owned call sites through managed-aware resolvers:

- `agent/lsp/install.py`, `hermes_cli/dep_ensure.py`, `hermes_cli/main.py`
  (`_make_tui_argv`), `hermes_cli/tools_config.py` (`_run_post_setup`) now
  use `find_node_executable()`.
- `hermes_cli/tools_config.py::_pip_install` and `hermes_cli/setup.py`'s
  vercel install use `ensure_uv()` (installing uv is in scope during setup,
  and the Windows installer's `uv venv` does not seed pip, so the fallback
  tier is "No module named pip"). `tools/lazy_deps.py` uses `resolve_uv()`
  — a lookup, not a bootstrap, because it runs mid-turn for an optional
  dependency and downloading a runtime as a side effect exceeds what the
  caller asked for.
- `hermes_cli/gateway.py`: extracted `_append_node_dir_for_service()`,
  shared by the systemd unit and launchd plist generators, which appends
  the managed dirs before the PATH-resolved one. A service definition is
  written once and survives reboots, so resolving a system Node that
  happens to lead the installing shell's PATH bakes the wrong interpreter
  in permanently. Managed dirs are profile-scoped, so each profile's unit
  still names its own Node; the existing symlink-parent rule (don't
  `.resolve()`) is preserved verbatim.
- `tools/environments/local.py`: the terminal tool's subshell PATH gains
  the managed dirs, appended alongside the sane entries rather than
  prepended — a tool the user deliberately put on their own PATH still
  wins, and the managed one only fills a gap. This is also what makes the
  bare `which("uv")` in `tools/env_probe.py` correct: that probe reports
  the environment the *model* sees, and the model can only run what is on
  that subshell's PATH.

`scripts/install.ps1`: the persisted User PATH update becomes
`Set-ManagedNodeFirstOnUserPath`, a move-to-front rather than an
add-if-missing. Installs made by an older install.ps1 already have the
managed dir in User PATH — at the tail, behind a system Node — and an
add-if-missing check sees it present and leaves that ordering in place
forever, so the users the bug hurt would never be repaired. Unrelated
entries keep their relative order (empty segments included; a trailing
`;` is legal and the installer's other PATH code preserves them),
duplicates collapse, and it writes only when the string actually changes.

Tests:

- `tests/test_managed_runtime_resolution.py` — AST guard that fails any
  new bare `which()` for a managed runtime, with a short justified
  allow-list and a companion test that fails when an allow-list entry goes
  stale. Reading source is banned by AGENTS.md and this is the documented
  exception: the property is "no call site anywhere spells it this way",
  which no runtime seam can observe.
- `scripts/ci/test_install_ps1_path_migration.ps1` — behavioral, not a
  source regex: it lifts the real `Set-ManagedNodeFirstOnUserPath` out of
  install.ps1's AST and rewrites only the two registry calls into an
  in-memory store, so the shipped split/dedupe/prepend/change-detection
  logic executes for real. Not in the default lane (Linux runners have no
  PowerShell host); runs under `pwsh`. 13/13 assertions pass.
2026-08-01 21:17:51 -04:00
..
computer_use fix(computer_use): resolve gateway session-key namespace in permission-mode lookup 2026-07-29 12:19:37 -07:00
environments fix(runtime): resolve Hermes-managed Node and uv before bare PATH 2026-08-01 21:17:51 -04:00
neutts_samples
wakewords feat(voice): bundle the trained "hey hermes" model as the out-of-the-box default 2026-07-28 07:58:16 -07:00
__init__.py
ansi_strip.py fix(cli): sanitize terminal escapes when replaying stored history (/resume recap, /status recap) 2026-07-17 04:53:38 -07:00
approval.py Revert "remove Vercel AI Gateway and Vercel Sandbox (#33067)" 2026-07-29 19:48:37 -07:00
async_delegation.py fix(state): restore async_delegation.py symbols, keep only journal_mode routing 2026-07-29 18:13:09 -07:00
audio_container.py refactor: extract shared audio container sniffer to tools/audio_container.py 2026-07-28 11:52:44 -07:00
binary_extensions.py
blueprints.py chore: remove unused imports and dead locals (ruff F401/F841 sweep) 2026-07-29 11:53:39 -07:00
browser_camofox.py fix(browser): block Camofox input on private pages 2026-07-03 03:27:47 -07:00
browser_camofox_state.py
browser_cdp_tool.py fix(browser): stop stale cdp_url from stalling every startup by 10+ seconds 2026-07-27 14:32:05 -07:00
browser_dialog_tool.py
browser_supervisor.py perf(imports): lazy-load heavy SDKs off the cold-start waterfall 2026-07-29 10:54:04 -07:00
browser_tool.py perf: lazy mcp SDK import + tool-discovery mtime cache + browser_tool import diet 2026-07-29 10:02:03 -07:00
budget_config.py
checkpoint_manager.py refactor: single build_subprocess_env() factory for all child-process spawns (profile + secret-scrub single owner) 2026-07-29 10:14:11 -07:00
clarify_gateway.py feat(clarify): extend multi-select to gateway text fallback and TUI bridge 2026-07-26 17:46:55 -07:00
clarify_tool.py refactor: migrate hand-rolled error envelopes to shared tool_error() 2026-07-29 10:14:00 -07:00
close_terminal_tool.py chore: remove unused imports and dead locals (ruff F401/F841 sweep) 2026-07-29 11:53:39 -07:00
code_execution_tool.py Revert "remove Vercel AI Gateway and Vercel Sandbox (#33067)" 2026-07-29 19:48:37 -07:00
computer_use_tool.py feat(computer_use): align cua-driver 0.10 permission modes 2026-07-29 12:19:37 -07:00
credential_files.py fix(vision): make desktop image uploads reachable from profile Docker sandboxes (#69575) (#75671) 2026-07-31 17:43:37 -04:00
cronjob_tools.py fix(cron): close GitHub auth-header exemption abuse in prompt scanner 2026-07-31 22:33:00 -07:00
daemon_pool.py fix(cli): reliable interrupts, bounded exit, and exit feedback (#57000) 2026-07-02 04:20:43 -07:00
debug_helpers.py
delegate_tool.py fix(delegation): prevent child HERMES_SESSION_ID leak into parent process env 2026-07-31 22:32:55 -07:00
delegation_live_log.py fix(delegation): redact credentials in live subagent transcripts 2026-07-20 06:50:31 -07:00
desktop_ui.py feat(desktop): agent can focus panes + shared desktop-UI event bridge 2026-07-22 12:13:01 -05:00
discord_tool.py chore: remove unused imports and dead locals (ruff F401/F841 sweep) 2026-07-29 11:53:39 -07:00
env_passthrough.py fix(security): strip dynamic Hermes secrets from all subprocess spawn env 2026-07-01 14:37:22 +05:30
env_probe.py fix(runtime): resolve Hermes-managed Node and uv before bare PATH 2026-08-01 21:17:51 -04:00
fal_common.py
feishu_doc_tool.py
feishu_drive_tool.py
file_operations.py fix(file-ops): prevent non-UTF-8 corruption and symlink data-loss 2026-08-01 15:39:33 -07:00
file_state.py
file_tools.py fix(tools): validate timeout, reject whitespace old_string, narrow /private/var block 2026-08-01 15:41:21 -07:00
flux3_video_tool.py Portal free user vision fix + flux3 polling improvements (#75448) 2026-07-31 10:17:55 -04:00
focus_pane_tool.py feat(desktop): agent can focus panes + shared desktop-UI event bridge 2026-07-22 12:13:01 -05:00
fuzzy_match.py fix(tools): validate timeout, reject whitespace old_string, narrow /private/var block 2026-08-01 15:41:21 -07:00
homeassistant_tool.py refactor: migrate hand-rolled error envelopes to shared tool_error() 2026-07-29 10:14:00 -07:00
hook_output_spill.py fix: route stray HERMES_HOME hardcodes through get_hermes_home() (profile + native-Windows safety) 2026-07-29 09:33:48 -07:00
image_generation_tool.py fix(deepinfra): harden multimodal provider routing 2026-07-14 02:59:39 +05:30
image_source.py fix(vision): make desktop image uploads reachable from profile Docker sandboxes (#69575) (#75671) 2026-07-31 17:43:37 -04:00
interrupt.py fix(interrupt): run user-approved commands from a clean interrupt slate 2026-07-06 04:58:42 -07:00
kanban_tools.py feat(kanban): talk to a running worker without a restart 2026-07-30 07:18:08 -05:00
lazy_deps.py fix(runtime): resolve Hermes-managed Node and uv before bare PATH 2026-08-01 21:17:51 -04:00
managed_tool_gateway.py Portal free user vision fix + flux3 polling improvements (#75448) 2026-07-31 10:17:55 -04:00
mcp_dashboard_oauth.py fix(mcp): preserve live OAuth state during reauth 2026-07-17 04:50:47 -07:00
mcp_oauth.py perf: lazy mcp SDK import + tool-discovery mtime cache + browser_tool import diet 2026-07-29 10:02:03 -07:00
mcp_oauth_manager.py fix(mcp): make Figma remote OAuth work via DCR allowlist defaults 2026-07-28 00:53:16 -05:00
mcp_stdio_watchdog.py fix(mcp): use direct parent identity in stdio watchdog 2026-07-18 04:15:13 -07:00
mcp_tool.py fix(mcp): guard against duplicate spawns and stale connecting entries (#58862) 2026-08-01 11:35:59 +05:30
memory_tool.py refactor: extract atomic_write_text to utils.py; fix write-failure error handling 2026-07-29 16:49:07 +05:30
microsoft_graph_auth.py
microsoft_graph_client.py refactor: single shared Retry-After parser 2026-07-29 10:13:50 -07:00
neutts_synth.py fix(voice): reconcile NeuTTS backbone/codec GPU device strings 2026-07-28 14:07:21 -07:00
open_preview_tool.py feat(desktop): agent can focus panes + shared desktop-UI event bridge 2026-07-22 12:13:01 -05:00
openrouter_client.py
osv_check.py fix(security): cache OSV malware preflight verdicts and stop double component discovery (#75485) 2026-08-01 10:47:20 -07:00
patch_parser.py fix(patch-parser): tolerate CRLF patch bodies and Move-then-Update 2026-08-01 15:40:39 -07:00
path_security.py refactor: migrate hand-rolled error envelopes to shared tool_error() 2026-07-29 10:14:00 -07:00
process_registry.py fix(process): decode background process output with incremental UTF-8 decoders 2026-07-31 21:21:13 -07:00
project_tools.py
react_to_message_tool.py feat(desktop): reactions are opt-in under Settings → Appearance, off by default 2026-07-30 00:08:28 -05:00
read_extract.py
read_terminal_tool.py chore: remove unused imports and dead locals (ruff F401/F841 sweep) 2026-07-29 11:53:39 -07:00
registry.py refactor: migrate hand-rolled error envelopes to shared tool_error() 2026-07-29 10:14:00 -07:00
schema_sanitizer.py fix(schema): preserve dependentRequired property names 2026-07-28 14:37:19 -07:00
send_message_tool.py chore: remove unused imports and dead locals (ruff F401/F841 sweep) 2026-07-29 11:53:39 -07:00
session_search_tool.py fix(sessions): keep kanban worker runs out of the session lists 2026-07-31 13:53:04 -05:00
skill_manager_tool.py Merge origin/main into feat/hsp-sync-client 2026-07-29 13:01:36 -07:00
skill_provenance.py
skill_usage.py Merge origin/main into feat/hsp-sync-client 2026-07-29 13:01:36 -07:00
skills_ast_audit.py
skills_guard.py fix(security): extend secret redaction to GitLab token families 2026-07-31 21:31:10 -07:00
skills_hub.py fix(skills-hub): include owner in ClawHub source URLs and add retry on 429 (#51236) 2026-07-31 22:33:11 -07:00
skills_sync.py fix(skills): avoid redundant bind-mount scans (#72622) 2026-07-28 04:24:50 -05:00
skills_sync_client.py fix(sync): read org state from the org endpoints, not the personal ones (#75237) 2026-07-30 22:31:42 -07:00
skills_tool.py Revert "remove Vercel AI Gateway and Vercel Sandbox (#33067)" 2026-07-29 19:48:37 -07:00
slash_confirm.py
terminal_tool.py fix(tools): validate timeout, reject whitespace old_string, narrow /private/var block 2026-08-01 15:41:21 -07:00
thread_context.py
threat_patterns.py
tirith_security.py fix: add explicit UTF-8 encoding to all subprocess text=True calls (#53428) 2026-07-24 11:45:57 -07:00
todo_tool.py fix(compression): preserve synthetic user provenance 2026-07-22 06:56:53 -07:00
tool_backend_helpers.py fix(voice): single scoped resolver — STT/TTS keys fall back to the credential pool 2026-07-28 11:53:09 -07:00
tool_output_limits.py
tool_result_storage.py
tool_search.py refactor: migrate hand-rolled error envelopes to shared tool_error() 2026-07-29 10:14:00 -07:00
transcription_tools.py fix(stt): kill faster-whisper silence hallucinations at the source 2026-07-29 00:01:33 -07:00
tts_streaming.py fix(tts): route streaming-provider secrets through resolve_provider_secret; bound per-sentence stream bodies at 16 MiB 2026-07-28 22:31:40 -07:00
tts_text_normalize.py fix(tts): unify TTS text preprocessing behind one shared cleaner 2026-07-28 11:55:01 -07:00
tts_tool.py feat(voice): calm ambient "thinking" sound while the agent works in voice chat 2026-07-29 08:24:00 -07:00
url_safety.py fix: scope private URL policy per profile 2026-07-28 14:17:45 -07:00
video_generation_tool.py feat(providers): Support DeepInfra as an LLM provider 2026-07-14 02:59:39 +05:30
vision_tools.py perf(imports): lazy-load heavy SDKs off the cold-start waterfall 2026-07-29 10:54:04 -07:00
voice_mode.py fix(voice): full-duplex agent-turn listener — interrupt by voice during generation AND playback 2026-07-29 10:08:53 -07:00
wake_word.py fix(wake): route desktop control and select input devices 2026-07-29 14:04:21 -06:00
web_tools.py chore: remove unused imports and dead locals (ruff F401/F841 sweep) 2026-07-29 11:53:39 -07:00
website_policy.py fix(website-policy): key blocklist cache on the real default config path 2026-07-04 15:08:49 -07:00
working_diff.py feat(diff): cross-surface /diff with staged/all/session modes 2026-07-26 18:28:20 -07:00
write_approval.py
x_search_tool.py docs(xai): clarify x_search vs xurl routing without schema cross-refs 2026-07-23 21:06:47 -07:00
xai_http.py fix(voice): single scoped resolver — STT/TTS keys fall back to the credential pool 2026-07-28 11:53:09 -07:00
xai_video_tools.py
yuanbao_tools.py