Commit Graph

228 Commits

Author SHA1 Message Date
Zak B. Elep 03cdc3b20c fix(browser): harden npx agent-browser resolution
- --ignore-scripts on every real npx agent-browser invocation.
  AGENT_BROWSER_NPX_SPEC is a floating ^0.26.0 range, not an exact
  pin, and none of these sites passed it (unlike install.sh/
  install.ps1's own npm install of the same package). Verified against
  the real CLI: `npx --ignore-scripts --prefer-offline -y
  "agent-browser@^0.26.0" --version` resolves cleanly on npm
  11.19.0/node 26.
- _resolve_npx_bin() now checks the Hermes-managed/extended search
  before a bare ambient PATH lookup, validating each candidate with
  node_tool_runnable before trusting it — a bare PATH-first lookup let
  a broken system npx shadow a healthy managed one with no recovery.
- warm_agent_browser_npx_cache() now runs a credential-scrubbed,
  PATH-propagated environment (matching every other agent-browser
  subprocess spawn) instead of inheriting the full parent environment
  including every provider/gateway credential Hermes holds, and kills
  the whole process tree (not just the top-level npx PID) on timeout
  via the new _kill_process_tree helper, since a surviving descendant
  can otherwise hold a capture pipe open past the nominal deadline.
2026-08-13 02:38:28 -07:00
Zak B. Elep 675d41fb25 fix(browser): pin npx agent-browser resolution and share a sentinel constant
Git-clone installs resolving agent-browser via bare npx floated latest
with no integrity check, while install.sh/install.ps1 installs stayed
pinned to ^0.26.0. Pin the npx spec to match. Also extract the
"npx agent-browser" sentinel comparison (6 call sites across two
packages) into a named constant/predicate, fix a PATH-priority
inversion where a broken system npx could shadow a healthy
Hermes-managed one at the two real npx launch sites, and stop
`hermes doctor --fix` from counting a bonus npx cache warm as a fixed
issue on an otherwise-healthy run.
2026-08-13 02:38:28 -07:00
Zak B. Elep 5f5f8d5b62 fix(cli): drop agent-browser/@streamdown-math from root npm deps
`hermes update` was pruning root-level Node dependencies (agent-browser)
because npm ci always wipes and reifies node_modules according to its
active filter -- no root-first/workspace-first ordering or flag
combination (--workspaces=false, --include-workspace-root, etc.) can
reliably keep a root-only package.json dependency from being pruned by
a subsequent workspace-scoped npm ci. Confirmed empirically and via
npm/cli source (isArboristCmd hardcodes includeWorkspaceRoot=false for
ci/install), so no amount of install-order juggling fixes this for good.

Instead of chasing install order, remove the root-only dependencies
that made the npm step fragile in the first place:

- agent-browser is no longer a root package.json dependency. It
  resolves lazily via `npx agent-browser` (tools/browser_tool.py
  already had this as a fallback; it's now the primary path).
  warm_agent_browser_npx_cache() is called fire-and-forget from both
  `hermes update` and `hermes doctor --fix` to keep npx's cache warm,
  preserving the "available before any session starts" property
  agent-browser had as an eager dependency without re-entangling it
  with the npm workspace graph.
- @streamdown/math moves to apps/desktop/package.json, where it's
  actually imported (markdown-text.tsx, katex-memo.ts) -- it was
  never used anywhere else and was subject to the same pruning risk.
- _update_node_dependencies() collapses to a single
  `npm ci --workspace ui-tui --workspace web` call now that root has
  no dependencies of its own to protect, and keeps its original spot
  ahead of `_build_web_ui()` at both call sites in update_cmd.py --
  with no root-only dependencies left to protect, there's no reason
  for the Node refresh and the web build to run in any particular
  order relative to each other.
- hermes_cli/tools_config.py's post-setup Chromium-install path and
  hermes_cli/doctor.py's agent-browser check both now resolve through
  the same PATH -> Homebrew/Hermes-managed-node -> npx cascade
  (_find_agent_browser / _resolve_npx_bin) instead of hand-rolling
  their own node_modules/.bin lookups, so they can't diverge from what
  browser tools actually invoke at runtime.
- tests-js/package-json-lazy-deps.test.ts gets a lockfile-level check
  mirroring the existing camofox one, so a future regression that
  reintroduces agent-browser into package-lock.json fails this test
  directly instead of relying on manual review to catch it.

Fixes #43564.
2026-08-13 02:38:28 -07:00
doncazper 85020f2238 fix(plugins): isolate ownership by profile 2026-08-12 19:13:32 -07:00
Teknium baa6b2e34d feat(browser): auto-install the Browser Use CLI instead of silently downgrading
The Browser Use CLI became the default browser backend, but nothing
provisioned it: users without uv/uvx (field report from DongyangHe on
macOS) silently fell back to the built-in browser tools with no notice.

- install_cli() in tools/browser_use_cli.py: uv tool install browser-use
  via the managed uv (bootstrapped on demand), linked into
  $HERMES_HOME/bin (UV_TOOL_BIN_DIR)
- _find_cli() now also probes $HERMES_HOME/bin for browser-use/uvx —
  Hermes' managed uv is not on the user's PATH
- hermes tools post_setup actually installs (Camofox standard) instead
  of printing instructions
- install.sh / install.ps1 provision the CLI at install time
  (best-effort, non-fatal, honors --skip-browser)
- CLI startup shows a one-line notice (24h rate-limited) when the
  default backend downgraded to the built-in tools
2026-08-11 17:06:15 -05:00
Teknium 8d8bc85dca feat(browser): make Browser Use mode the default browser backend
An unset browser.backend ("") now resolves to Browser Use mode whenever
the browser-use CLI is runnable (installed binary or uvx); otherwise the
built-in browser tools are kept so browsing never silently breaks.
Camofox setups always keep the built-in tools (no CDP surface), and
backend: off (including YAML 1.1 bare off -> False) forces the built-in
stack. hermes tools row highlighting follows the same effective-mode
resolution, and tests/tools/ pins CLI discovery off so host uvx installs
can't flip built-in-browser tests.
2026-08-10 12:28:10 -07:00
Teknium 7e04718ec3 feat(browser): Browser Use mode composes with all CDP browser backends
Reframe (per review): browser.backend: browser-use is now a DRIVER over
whatever browser source is configured, not a competing backend choice.

- browser_exec resolves its CDP endpoint through the same chain the
  built-in tools use: BU_* env override > BROWSER_CDP_URL/browser.cdp_url
  (/browser connect) > the configured cloud provider via browser_tool's
  _get_session_info() — sharing the per-task session cache, expiry
  replacement, inactivity reaper, and atexit cleanup instead of
  duplicating them. Live-validated against Browserbase (session created,
  driven, reaped) and gateway-provisioned Browser Use cloud browsers.
- Direct-API Browser Use configs skip provider resolution (the CLI talks
  to their cloud natively via BU_AUTOSPAWN); the Nous-gateway variant
  resolves through the provider, so subscribers get CLI mode without a
  raw BROWSER_USE_API_KEY.
- Camofox: only true fallback — Firefox-based, custom HTTP API, no CDP
  surface (its own health probes fail on CDP-schema calls). Active
  Camofox setups keep the built-in browser tools even with
  backend: browser-use set.
- hermes tools picker: provider rows and the Browser Use row are no
  longer mutually exclusive; selecting a provider keeps the driver
  choice, and both rows highlight when composed.
- Docs updated for driver-over-source semantics.
2026-08-10 10:45:44 -07:00
Laith Weinberger a1835c8c17 feat(browser): integrate Browser Use CLI 3.0 2026-08-10 10:45:44 -07:00
Teknium 55f9e472a0 perf(cli): sub-400ms warm startup — probe-mode check_fns, lazy MCP SDK, banner snapshot, parallel worktree add
Cold CLI time-to-banner was ~1.8s (hermes) / ~2.8s (hermes -w). The banner
path was paying for work the session doesn't need before first input:

- aux availability probes built REAL OpenAI/httpx clients (openai import
  ~0.3s + SSL context) just to answer check_fns. New aux_probe_mode()
  returns a cache-excluded stub; resolution policy unchanged.
- tools/mcp_tool imported the mcp SDK (~260ms, mcp.types pydantic model
  construction) at module import even with zero MCP servers configured.
  SDK import is now lazy behind _ensure_mcp_sdk(); _MCP_AVAILABLE is a
  find_spec probe so every existing gate/test keeps its semantics.
- banner blocked 500ms on the update-check prefetch; now waits 50ms and
  defers the warning line to a daemon thread (prints above the prompt).
- banner recomputed get_tool_definitions + skills scan + git state every
  launch; now snapshotted to ~/.hermes/cache/banner_snapshot.json keyed on
  (config.yaml, .env, checkout rev, toolsets) and replayed on warm launches
  with a background refresh. Agent tool list is still computed fresh.
- _resolve_active_context_length probed the Nous portal /models (~200ms
  network) per launch; the tool-search gate now prefers the on-disk
  context cache when present.
- schema reconciliation re-executed SCHEMA_SQL in a scratch SQLite DB
  (~85ms) per SessionDB(); the reference parse is now disk-memoized by
  DDL hash (live-DB diffing still runs every startup).
- bundled-skills sync (~120-170ms rglob/hash) moved off the startup path
  to a daemon thread; plugin discovery starts in the background and every
  synchronous consumer joins via discover_plugins().
- hermes_cli.auth imported httpx eagerly (~30ms); now a lazy proxy that
  test monkeypatching still reaches (setattr forwards to the real module).
- fast chat launch: unambiguous 'hermes'/'hermes chat' invocations skip
  building all ~40 subcommand parsers (bails to full dispatch on anything
  else, incl. container mode).
- -w path: git worktree add runs with checkout.workers=8 (0.6s→0.2s) and
  overlaps HermesCLI construction; --skills preload runs in the background
  and is folded in at agent init (finalize_preloaded_skills, same
  fail-loud contract for fully-unknown skill lists); stale-worktree prune
  moved off the banner path.

Warm results (PTY time-to-banner, 5-run): hermes 1.80s → 0.38-0.40s;
hermes -w -s hermes-agent-dev --yolo 2.82s → 0.57-0.69s.
2026-08-10 10:40:19 -07:00
Brooklyn Nicholson a60b492e07 feat(gateway): key-addressed plugins.manage rows + portable MCP toolset fold-in
plugins.manage list rows now carry the canonical registry key and a
portable flag (Agent Plugins v1 plugin.json packages), and toggles
address the key — bare names collide across category dirs
(image_gen/fal vs video_gen/fal), so name-addressed toggles flipped
both. Portable packages' in-memory MCP servers also fold into
enabled_mcp_server_names(); without that their tools registered with
the MCP runtime but never reached the model's schema.
2026-08-08 17:24:59 -05:00
Teknium 372b3b7bba fix(cli): decode cua-driver autostart PowerShell output as UTF-8
Widen of the PowerShell codepage cluster: the autostart registration
subprocess in tools_config.py was the last text=True capture in these
modules still decoding with the locale code page. Standardize on
encoding='utf-8', errors='replace' like the rest of the file (#53428).
2026-08-08 12:34:46 -07:00
szzhoujiarui 16bd5d23b4 fix(tools): reuse subscription features for toolset listing 2026-08-03 16:07:16 +05:30
teknium1 64a50ed50a fix(a2a): default the a2a toolset OFF (opt-in), like spotify
The a2a client tools are registered unconditionally by the plugin, but a
newly-registered plugin toolset defaults to ENABLED for every platform until
the user has seen it in 'hermes tools'. That force-injected 'a2a' into every
agent's enabled_toolsets, leaking 3 tool schemas to all users and breaking
tests that assert exact toolset membership
(test_api_server_toolset::test_create_agent_respects_config_override).

Add 'a2a' to _DEFAULT_OFF_TOOLSETS so it stays opt-in (user enables via
'hermes tools'), matching the spotify precedent. The inbound platform
adapter is already opt-in (only instantiated when the a2a platform is
enabled); this aligns the outbound client tools with the same posture.
2026-08-02 15:10:15 -07:00
Teknium ebd61ce5ac fix(secrets): scope tier-3 credential reads (teams_pipeline, deepinfra models, FAL/XAI/VERCEL/DAYTONA/GITHUB presence, HERMES_API_KEY display)
NOTION_API_KEY/LINEAR_API_KEY defaults, DEEPINFRA_API_KEY model gate, FAL_KEY via the file's own _scoped_credential helper, XAI/VERCEL/DAYTONA presence checks, GITHUB_TOKEN/GH_TOKEN + GitHub App creds in tirith/skills_hub, and the masked HERMES_API_KEY display in tui_gateway config.show all route through get_secret.
2026-08-02 10:04:48 -07:00
tachyon-r 76cf19fee1 fix(tools): isolate model tools by multiplex profile 2026-08-02 00:11:50 -07:00
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
rob-maron 126ff7071b
Portal free user vision fix + flux3 polling improvements (#75448)
* flux3 polling improvments

* poll gap to 4s

* back to 5s

* vision model fix

* minor fix
2026-07-31 10:17:55 -04:00
rob-maron 97c6a183af auto populate flux3 in tools for nous portal users 2026-07-30 16:34:08 -07:00
rob-maron 07447bd5db
nous portal video gen (#74963) 2026-07-30 14:52:15 -04:00
Francesco Bonacci 847e401b74 feat(computer_use): align cua-driver 0.9 contracts
Salvaged from PR #67807 by @f-trycua onto current main.

- Foreground gate: discover delivery_mode support from the live tools/list
  inputSchema.properties (fail closed), not the never-shipped
  input.delivery_mode capability token
- bring_to_front: standalone strict-schema MCP tool (inject_session=False),
  separate approval scope, requires foreground
- Verdict precedence: confirmed > unverifiable (verify before retry) >
  suspected_noop/refusal (escalate); surfaced as explicit verdict field
- Typed cua_browser_* route inside computer_use (browser_route.py) with
  exact-binding, adapter-injected session, snapshot-scoped refs
- Per-Hermes-session backend isolation + release_computer_use_session seam
  wired into AIAgent.close()
- Recorded 0.9 tools/list fixture replaces fabricated capability tokens
2026-07-29 12:19:37 -07:00
embwl0x 88c19a4edf fix(cli): repair Windows cua-driver autostart registration for paths with spaces
Detect a missing cua-driver-serve scheduled task after the installer runs
and retry registration via Start-Process -FilePath/-ArgumentList instead of
interpolating the binary path into a PowerShell command string (which splits
at the first space in a username-space path).

Salvaged from #60880 by @embwl0x. Related: #60808.
2026-07-29 11:35:33 -07:00
Teknium 5fdc9d2a9d fix: pin cua-driver refresh to the release check-update confirmed
The upstream cua-driver installer scripts on trycua/cua@main carry a
baked default version that Release Please bumps in the release PR
*before* the release assets are published. During that window an
unpinned installer run 404s on the asset download and the
`hermes update` cua-driver refresh fails with:

    error: download failed: The remote server returned an error: (404) Not Found.
    ⚠ cua-driver refreshing did not complete. Re-run manually: ...

Observed live 2026-07-29: baked version 0.14.0 vs latest published
release 0.13.1 — every `hermes update` run with an out-of-date driver
hit the warning until upstream publishes the assets.

We already know the correct version: `cua-driver check-update --json`
returns `latest_version` straight from the GitHub Releases API, whose
entries by definition have published assets. When the check positively
confirms an update, export that version as CUA_DRIVER_RS_VERSION into
the installer child env (both install.sh and install.ps1 honour it over
their baked default), so the refresh downloads the release that
actually exists instead of racing the upstream release pipeline.

Malformed / missing latest_version values fall back to the previous
unpinned behaviour. The explicit `hermes computer-use install
--upgrade` force path and fresh installs are unchanged.
2026-07-29 09:02:19 -07:00
Teknium 96bf65a6f7 feat(tools): full Speech-to-Text configurability in hermes tools + GUI
STT previously had no configuration surface outside hand-editing
config.yaml — no category in the hermes tools picker, no provider
matrix in the GUI capabilities tab, no status line in hermes setup.

- TOOL_CATEGORIES['stt']: 7 provider rows (Local Whisper, Nous
  Subscription managed, OpenAI, Groq, xAI, ElevenLabs Scribe,
  DeepInfra) with key prompts, badges, and post-setup hooks
- stt_provider marker wired through _write_provider_config,
  _configure_provider, _reconfigure_provider, and
  _is_provider_active — GUI and CLI share one write path
  (apply_provider_selection)
- STT model picker (_configure_stt_model + STT_MODEL_CATALOG) runs
  after provider pick: local sizes, Groq whisper family, OpenAI
  whisper-1/gpt-4o-*/gpt-transcribe, ElevenLabs scribe (model_id key)
- faster_whisper post-setup hook auto-installs the local backend;
  registered in _POST_SETUP_READY
- stt is CONFIG-ONLY (_CONFIG_ONLY_TOOLSETS): it ships no tool
  schemas, so it is excluded from the per-platform enable checklist;
  the GUI toolset toggle writes stt.enabled instead of
  platform_toolsets
- hermes setup shows a Speech-to-Text status line per provider
- Mistral row omitted (mistralai PyPI quarantine), mirroring the
  dashboard stt.provider options

Tests: tests/hermes_cli/test_stt_picker.py (20 cases) incl. invariant
checks against agent.transcription_registry builtins and the runtime
OPENAI_MODELS/GROQ_MODELS sets.
2026-07-29 00:20:50 -07:00
Teknium 2319dbb014 fix(desktop): honest browser-backend readiness + explicit backend activation + full OpenAI TTS voice/model options
Three GUI Capabilities-tab defects reported on Windows:

1. Browser rows stuck on 'Setup required' after a successful setup run.
   Root causes, all in the readiness probe (not the installer):
   - _has_agent_browser() never searched the Hermes-managed Node dir
     (%LOCALAPPDATA%/hermes/node / $HERMES_HOME/node/bin) where the
     Windows install lands, and probed node_modules/.bin/agent-browser
     as the extensionless POSIX shim, which fails exec on Windows
     (WinError 193) — now resolved via PATHEXT-aware shutil.which
     against both rungs, mirroring _find_agent_browser().
   - Cloud rows (Nous Subscription Browser Use, Browserbase, Browser
     Use, Firecrawl) declared post_setup: agent_browser, whose
     readiness gate requires a LOCAL Chromium build the cloud never
     uses — switched to the cloud-scoped 'browserbase' hook (CLI-only).
   - _agent_browser_installed() could read browser_tool's stale cached
     'Chromium missing' result from before the install ran in the
     spawned post-setup process — cache now dropped before probing so
     the pill flips to Ready right after a successful run.

2. No way to tell which backend is active, and clicking a row to read
   its details silently rewrote config. Row click now only
   expands/collapses; activation is an explicit 'Use this backend'
   button, the active row carries an 'Active' pill, and the expanded
   active row says 'This is your active backend'.

3. OpenAI TTS showed one model and one voice. The options were always
   defined but rendered through a native <datalist>, which filters by
   the field's current value — a field already set to a valid option
   suggested only itself. Replaced with a real combobox (Input +
   dropdown) that lists every option, and voice suggestions now track
   the selected model per the OpenAI TTS docs: tts-1/tts-1-hd = 9
   voices, gpt-4o-mini-tts = 13 (adds ballad, verse, marin, cedar).
2026-07-28 23:52:49 -07:00
yuexiong 83dc0b9b85 fix(install): clear stale Windows cua lock 2026-07-26 16:01:54 -07:00
yuexiong 23d0dca832 fix(install): reap Windows cua installer process tree 2026-07-26 16:01:54 -07:00
teknium1 920facdc4c fix(update): never blind-reinstall cua-driver during hermes update
'Refreshing cua-driver (Computer Use)...' could hang for minutes on
Windows: when the driver's native check-update verb returned an
indeterminate result (old driver without the verb, offline, GitHub
rate-limited, or the probe timing out), install_cua_driver(upgrade=True)
fell through to the full upstream installer — a silent, output-captured
run with a 660s ceiling, plus install.ps1's 600s concurrency-lock wait
on Windows on top. Every 'hermes update' paid that cost.

Two changes:

- install_cua_driver() grows require_confirmed_update: with it set, an
  indeterminate check keeps the installed version and returns fast,
  printing the force path (hermes computer-use install --upgrade).
  'hermes update' passes it; the explicit --upgrade CLI keeps the old
  fall-through so a force refresh still works when the check can't
  answer.
- cua_driver_update_check() default timeout is now 25s on Windows
  (8s unchanged on POSIX): first-spawn of the exe under Defender /
  SmartScreen routinely exceeds 8s, and a false timeout is exactly the
  indeterminate result that used to trigger the multi-minute reinstall.
2026-07-26 13:47:35 -07:00
Hermes Agent 92549c9a6e refactor(cli): route every aux picker through one provider-inventory seam
Adds build_aux_picker_rows() + format_aux_picker_entries() to
hermes_cli/inventory.py and converts both auxiliary-task pickers to them,
so custom providers, exclusions, and picker visibility can no longer be
dropped one call site at a time.

The two salvaged commits each fixed one kwarg at these same two sites
(user/custom providers, then for_picker). Both were per-site patches, so
the next aux picker would have reintroduced the gap. The seam makes the
correct behaviour the default a new caller cannot forget:

- user `providers:` and saved `custom_providers:` entries
- `model_catalog.excluded_providers`, matching /model
- exhausted-credential-pool providers stay visible
- only the active custom endpoint is probed, so an offline saved local
  server can't hang the picker
- the virtual `moa` row is excluded (auxiliary_client unwraps it to the
  aggregator anyway, so offering it is a silently-rewritten choice)

The vision picker also gains the current-selection marker it never had.

Also threads for_picker through build_models_payload, which had no way to
express it despite list_authenticated_providers supporting it.
2026-07-25 22:47:12 -07:00
Drexuxux 9aefa4c61c fix(model-picker): show exhausted-pool providers in the aux-task and vision pickers too
credential pool has entries but is entirely rate-limited (exhausted): rate
limits are per-model for many providers (e.g. Google Gemini), so an exhausted
key for model-A may still serve model-B, and the user should stay able to
select it. That fix wired `for_picker=True` into `list_picker_providers`.

The two sibling interactive pickers reached by `hermes model` still called
`list_authenticated_providers()` WITHOUT `for_picker=True`, so they kept
hiding exhausted-pool providers:

- `_aux_select_for_task` (hermes_cli/main.py) — the auxiliary-task
  provider/model picker.
- `_configure_vision_provider_model` (hermes_cli/tools_config.py) — the vision
  provider/model picker.

Both persist a per-task config the user runs *later* (once the momentary
cooldown clears), so hiding an exhausted-pool provider here is exactly the

Tests: two regressions assert each picker requests `for_picker=True` (they
omitted it before the fix); the existing picker/exhausted-pool suite still
passes (6 total).
2026-07-25 22:47:12 -07:00
teknium1 d4b867cf9f fix(windows): sweep remaining unguarded text-mode subprocess sites codebase-wide
AST-driven pass over every subprocess.run/Popen/check_output/check_call/call
with text=True (or universal_newlines=True) and no explicit encoding=:
append encoding='utf-8', errors='replace' at the kwarg site. 136 call
sites across 28 files (cli.py, hermes_cli/main.py, tools_config.py,
environments, computer_use, gateway, scripts, skills helpers, agent/*).

Together with the salvaged #55339/#60741 commits this closes out issue
#53428's bug class; the salvaged #60751 linter rule in
check-windows-footguns.py now enforces it repo-wide (verified: 807 files
scanned, zero findings).
2026-07-24 11:45:57 -07:00
Julien Talbot b9b100da11 docs(xai): clarify x_search vs xurl routing without schema cross-refs
Make the x_search / xurl boundary explicit in the skill, feature docs,
toolset metadata, setup note, and reference pages, while keeping the
model-facing x_search schema generic (no static xurl name).

Regression tests assert behavioral routing invariants rather than frozen
prose snapshots. Drop the stale CI-only plugin/hangup hunks already on
main so this rebases cleanly.
2026-07-23 21:06:47 -07:00
Tianqing Yun 2f9d88caee fix: resolve cua-driver across computer-use surfaces 2026-07-23 00:42:46 -05:00
Teknium 2ae0d67f63
feat(desktop): five Capabilities-tab UX fixes from live testing — hints, vision link, web split, key deep-links (#67482)
* fix(desktop): stop contradicting the Ready pill with the one-time-install hint

When a provider's server-computed status is 'ready' (post_setup install
verifiably satisfied, e.g. cua-driver on PATH), the PostSetupRunner row
still said 'This backend needs a one-time install (…)'. Swap the copy for
a muted installed-confirmation one-liner and keep the Run setup button for
repair re-runs. Gated purely on the provider status prop so it composes
with the server-driven resting state work in the sibling lane.

* feat(tools): surface the web search/extract capability split in the Capabilities UI

The runtime has dispatched web_search and web_extract to independently
configurable backends for a long time (web.search_backend /
web.extract_backend overrides with web.backend as the shared fallback),
but the Capabilities tab still presented one monolithic 'Web Search &
Extract' choice that only wrote web.backend.

Backend:
- GET /api/tools/toolsets/web/config now returns active_search_backend /
  active_extract_backend resolved via the REAL runtime getters
  (tools.web_tools._get_search_backend/_get_extract_backend), plus each
  provider row's web_backend key and supported capabilities (from the
  registry's supports_search/supports_extract flags).
- PUT /api/tools/toolsets/web/provider accepts an optional capability
  ('search'|'extract') that writes web.<capability>_backend without
  touching web.backend; validates the provider actually supports the
  requested capability (ddgs/brave-free are search-only). Omitted →
  unchanged legacy apply_provider_selection path.
- New tools_config.web_provider_capabilities() helper reads the plugin
  registry's capability flags.

Frontend: 'Search: <backend>' / 'Extract: <backend>' pills above the web
provider matrix, per-row 'Search backend'/'Extract backend' assignment
pills, and 'Use for Search'/'Use for Extract' actions gated on each
backend's declared capabilities.

Tests: endpoint tests assert the runtime getters resolve to the written
backend (searxng for search, firecrawl for extract) after the endpoint
write; vitest covers badges, capability-gated buttons, and non-web
toolsets staying untouched.

* feat(desktop): deep-link Capabilities key rows to Settings → API Keys

Set env-var rows in the toolset config panel now offer 'Manage in API
Keys' in the row actions menu — an internal route change to
/settings?tab=keys&key=<ENV_KEY>. KeysSettings consumes the ?key= param
via the shared useDeepLinkHighlight hook (same mechanism as the command
palette's ?field= config deep links and ?session= archived-session
links): scrolls the credential card into view, flashes it, and expands
it. Applies generically to every env-var row, and only when the key is
set (unset keys are managed inline via Set). i18n in en/zh/zh-hant/ja.

* feat(desktop): point the vision Capabilities detail at Settings → Models

The vision toolset has no TOOL_CATEGORIES provider matrix — its
provider/model resolution runs through the auxiliary model config
(agent/auxiliary_client.py), so the Capabilities detail pane looked
empty with no hint of where the model choice lives.

Add a short explainer + an internal deep link
(/settings?tab=config:model&aux=vision) rendered only for
toolset.name === 'vision'. ModelSettings consumes the ?aux= param via
the shared useDeepLinkHighlight hook and scrolls/flashes the matching
auxiliary task row (rows now carry aux-task-<key> anchor ids). No
external URLs. i18n in en/zh/zh-hant/ja.

* test(desktop): use type-alias imports for the react-router mock (lint)

* chore: drop accidentally committed node_modules symlinks

* chore: drop remaining committed node_modules symlinks (apps/desktop, apps/shared)
2026-07-19 05:45:01 -07:00
Teknium aa1ad32191
fix(desktop): Windows browser-setup journey — console flash, idempotent setup, Nous Portal activation (#67473)
* fix(windows): suppress console-window flash in tools post-setup subprocess spawns

The desktop GUI runs post-setup hooks via a detached, console-less
'hermes tools post-setup <key>' child (spawned with windows_detach_flags).
But the hook implementations in tools_config.py ran their inner installers
(npm install, agent-browser install, uv/pip installs, ensurepip, cua-driver
version probes and installer) without Windows creationflags — and on
Windows a console-less parent spawning a console/.cmd child materializes a
brand-new console window, the 'terminal flash' reported on the
Capabilities > Browser Automation setup journey.

Add _post_setup_no_window_flags(), a local wrapper around
windows_hide_flags() (CREATE_NO_WINDOW only — DETACHED_PROCESS would sever
stdio and break capture_output), and pass it at every post-setup subprocess
call site. Spawns that stream live output to the user's console
(verbose cua-driver install) only hide when stdout is not a tty, so
interactive CLI installs keep their output. POSIX behavior is unchanged
(the helper returns 0 off-Windows).

* fix(desktop): make Capabilities post-setup idempotent — Installed state instead of unconditional Run setup

The GUI panel rendered the primary 'Run setup' CTA whenever a provider
declared post_setup, ignoring the server-computed readiness status the
config endpoint already serves. Users on Windows clicked 'Run setup' on
an already-installed Local Browser and watched it 'install' again.

Frontend: PostSetupRunner now takes installed (provider.status === 'ready')
and renders an 'Installed' pill + small 'Re-run setup' text button in that
state; onComplete still refetches the toolset config, so a fresh install
flips the row to Installed once the endpoint reports ready.

Backend:
- _POST_SETUP_READY extended: agent_browser now tracks the FULL local
  install (_local_browser_runnable: CLI + Chromium-or-Lightpanda) instead
  of the bare CLI check; new entries for the cloud 'browserbase' hook
  (CLI only — cloud rows host their own Chromium) and camofox (npm
  package present).
- _run_post_setup prints distinct 'already installed, nothing to do'
  messages for the agent-browser/Chromium/Camofox early-exits so the GUI
  action log tells the truth on re-runs vs fresh installs.

i18n: new postSetupInstalled/postSetupRerun/postSetupInstalledHint strings
in en, ja, zh, zh-hant + types.

* fix(desktop): let managed Nous Subscription rows activate from the GUI via the Portal sign-in flow

PUT /api/tools/toolsets/{name}/provider intentionally skips the Nous
Portal auth gate the CLI runs inline (ensure_nous_portal_access) — but no
desktop surface handled it. Selecting 'Nous Subscription (Browser Use
cloud)' from Capabilities wrote browser.cloud_provider=browser-use +
use_gateway=true and then silently never activated: _is_provider_active
requires feature.managed_by_nous, which stays false without the
entitlement, and the credential was never used.

Backend: after apply_provider_selection, the endpoint now checks the
managed row's entitlement (get_nous_subscription_features force_fresh +
the same per-category coverage gate the CLI applies) and reports the gap
with additive response fields {needs_nous_auth: true, feature}. The
selection is still persisted — activation is what's gated.

Frontend: handleSelect surfaces a 'Sign in to Nous Portal' warning toast
with a Sign-in action instead of the misleading success toast. The action
drives the EXISTING Nous Portal OAuth device-code flow (provider id
'nous' in _OAUTH_PROVIDER_CATALOG): POST /api/providers/oauth/nous/start,
open verification_url, poll /poll/{session}; on approval the panel
refetches the toolset config so is_active/status flip.

i18n: nousAuthNeeded*/nousAuthSignIn/nousAuthDone*/nousAuthFailed strings
in en, ja, zh, zh-hant + types.
2026-07-19 05:22:10 -07:00
teknium1 49167ffe05 fix(tools): apply missing-provider setup pass to per-platform configure flow too
Sibling-site fix for the flaw addressed in the global 'Configure all
platforms' flow: the per-platform checklist also returned to the menu
without opening provider setup when a selected toolset was already
enabled but lacked provider configuration. Adds a matching regression
test.
2026-07-19 03:02:47 -07:00
Nick S 6912e93478 fix(tools): configure selected global tools missing provider setup 2026-07-19 03:02:47 -07:00
Teknium c372c4220b
fix(desktop): truthful per-provider readiness in Capabilities (no more false Ready) (#67201)
* fix(desktop): compute truthful per-provider readiness for Capabilities tool config

Backend: GET /api/tools/toolsets/{name}/config now sends a per-provider
'status' field ('ready' | 'needs_keys' | 'needs_auth' | 'needs_setup')
computed by the new provider_readiness_status() in tools_config:

- env vars declared: all set -> ready, else needs_keys
- Nous-managed rows: Portal login + per-category tool-gateway entitlement
  (MANAGED_FEATURE_COVERAGE_CATEGORY) -> ready, else needs_auth
- post_setup 'xai_grok' rows: Grok OAuth or XAI_API_KEY -> ready, else
  needs_auth
- other keyless post_setup rows: installed-state predicate
  (_POST_SETUP_READY: kittentts/piper/ddgs/langfuse via find_spec,
  agent_browser via _has_agent_browser, cua_driver via PATH probe);
  unknown hooks fall back to is_active as the setup-completed signal
- genuinely-free keyless rows (Edge TTS) stay ready

Existing fields are untouched; 'status' is additive so older desktops
keep working.

* fix(desktop): render server readiness status in Capabilities provider pills

The panel's providerConfigured() heuristic pilled every zero-env-var
provider 'Ready' — including logged-out Nous Subscription rows, xAI TTS
without Grok OAuth, and never-installed KittenTTS/Piper. Render the
backend's per-provider 'status' instead:

- ready       -> existing 'Ready' pill
- needs_auth  -> warn pill 'Needs sign-in'
- needs_setup -> warn pill 'Needs setup'
- needs_keys  -> no pill (env-var fields are the signal)

Keyed rows keep deriving ready/needs_keys from local envState so saving
or clearing a key updates the pill without a refetch. Older backends
without 'status' fall back to the legacy env-var heuristic (narrow
compat path, desktop/runtime update on separate clocks).

Adds the warn tone to the settings Pill primitive and needsSignIn /
needsSetup strings to all locale catalogs (en, zh, zh-hant, ja).
2026-07-19 02:59:55 -07:00
solyanviktor-star f0e6daddce fix(tools): don't report platform-restricted toolsets as enabled
tools_disable_enable_command filters platform-restricted toolsets out of
toolset_targets and prints an error for each, but the success summary at
the end is built from the raw targets list and only excludes unknown
toolsets and failed MCP servers. Running e.g.

    hermes tools enable discord --platform telegram

prints the 'not available on platform' error followed by 'Enabled:
discord' for a toolset that was never written to the config.

Exclude restricted_targets from the success summary, matching how
unknown toolsets and failed MCP servers are already handled.

Two regression tests: a restricted toolset alone must not print
'Enabled', and a mixed allowed+restricted invocation must report only
the allowed toolset (both fail before the fix).
2026-07-16 01:17:11 -07:00
Shannon Sands 3ffd8b3da0 fix(dashboard): persist Discord toolsets to Discord platform 2026-07-16 00:20:33 -07:00
Georgi Atsev fe002eb124 feat(providers): Support DeepInfra as an LLM provider 2026-07-14 02:59:39 +05:30
teknium1 07271a6f62 fix(tools_config): widen null guard to known_plugin_toolsets write path
Sibling of the salvaged #53196 read-path fix: setdefault() does not
replace a present-but-null key, so saving platform tools with
known_plugin_toolsets: null in config.yaml crashed on indexing None.
2026-07-09 20:28:44 -07:00
sonxi df886d0a45 fix(tools_config): guard against None in known_plugin_toolsets config
When config.yaml has known_plugin_toolsets set to null (or any value
mapped to None by the YAML loader), config.get returns None (dict.get
only falls back to the default when the key is absent, not when its
value is None). The subsequent set(known_map.get(platform, [])) then
crashes with TypeError: NoneType object is not iterable and the gateway
fails to start, even though no plugin toolsets are configured.

Add or-empty-dict and or-empty-list guards so a null/None value is
treated as empty instead of crashing the platform-tools resolver.
2026-07-09 20:28:44 -07:00
Tranquil-Flow 8a726e91ba fix(tools): enable platform-native toolsets when their composite is explicitly configured (#35527)
When a user explicitly configures a platform with its native composite
(e.g. platform_toolsets.discord: [hermes-discord]), the discord and
discord_admin toolsets were silently stripped by _DEFAULT_OFF_TOOLSETS
even though the composite contains those tools. The strip could not tell
an explicit composite opt-in apart from the unconfigured default.

Track whether the platform was explicitly configured and, when it was,
exempt toolsets that are both default-off and platform-restricted to the
current platform from the strip. Only discord/discord_admin are affected
(the sole entries in both _DEFAULT_OFF_TOOLSETS and
_TOOLSET_PLATFORM_RESTRICTIONS). Unconfigured and empty-list platforms
keep the security default-off behaviour.
2026-07-07 16:27:28 -07:00
Teknium 24a7546918
fix(cli): drop shell=True from cua-driver installer — download to mkstemp, exec as argv (#58796)
Replaces the POSIX `/bin/bash -c "$(curl …)"` invocation with a
download-then-exec flow: curl the upstream install.sh into a mkstemp
temp file (unpredictable name, 0600) and run it as a plain argv list.
No shell=True, no command substitution. The temp script is removed in
a finally block; download failures return cleanly without exec.

Salvages the intent of #34974 by @ErnestHysa. His original patch
targeted a fixed /tmp/cua-driver-install.sh path (symlink/TOCTOU-prone
on multi-user hosts) and predates Windows/Linux installer support;
this version uses mkstemp and keeps the powershell path untouched.

Co-authored-by: ErnestHysa <takis312@hotmail.com>
2026-07-05 05:35:44 -07:00
Teknium 7fde19afcc
fix(cli): unwedge cua-driver installer timeouts — group-kill, stale-lock pre-clear, 660s ceiling (#58767)
* fix(cli): unwedge cua-driver installer timeouts — group-kill on timeout, stale-lock pre-clear, 660s ceiling

The cua-driver refresh in hermes update could wedge permanently:
subprocess timeout (300s) killed only the outer shell, orphaning the
curl|bash grandchildren and the upstream installer's concurrent-install
lock (~/.cua-driver/packages/.install.lock.d). The installer only
reclaims a stale lock after 600s of waiting — longer than our old
ceiling — so every subsequent run was killed before recovery could
fire: 'always times out'.

- Run the installer in its own process group (start_new_session) and
  SIGKILL the whole group on timeout, so no lock-holding orphans survive.
- Pre-clear a provably-stale lock (dead holder pid, or pid-less and
  older than the upstream 600s window) before invoking the installer.
- Raise the ceiling to 660s (> upstream LOCK_STALE_AFTER_SECONDS=600).
- Timeout message now names the lock path and the manual re-run command.

Fixes #58762

* chore: suppress windows-footgun lint on platform-gated kill calls

Both sites are POSIX-only: _clear_stale_cua_install_lock early-returns
on win32, and os.killpg sits in the 'not is_windows' branch.
2026-07-05 03:16:06 -07:00
Sami Rusani d8b51269ca fix(update): skip cua-driver refresh when Applications is unwritable 2026-07-05 03:15:06 -07:00
Nick Mason 80733413f9 fix(tools): don't drop a toolset from platform inference when a tool is registered into it
_get_platform_tools reverse-maps a platform composite to configurable
toolsets with an all-tools subset test. Because get_toolset() merges
registry-registered tools into a toolset, a tool added to a toolset
(delegate_cli -> delegation; desktop-only read_terminal -> terminal) that the
static composite never listed made the subset test fail, silently dropping the
entire toolset on api_server and other inference-based platforms. Compare the
toolset's static membership at all three reverse-map sites.

Fixes #49622.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 13:25:25 +05:30
Jaaneek 9ce79cd642 feat(xai): Imagine public-URL storage, chaining & video edit/extend
Add durable public-URL output and URL-based chaining to xAI Grok Imagine:

- Store generated media on files-cdn with permanent public HTTPS URLs
  (public_url: true, no expiry by default).
- Chain by URL: generate -> edit -> extend each take a prior result's
  public HTTPS URL (or a data URI / local file for inputs).
- Add provider-specific xai_video_edit and xai_video_extend tools.
- Image generation: public-URL/storage output, multi-reference edits,
  and ~/ local-path support for image edits.

Credentials use xAI Grok device-code OAuth (separate PR).
2026-06-29 21:11:58 -07:00
ygd58 3e16176ba4 fix(tools): reconcile agent.disabled_toolsets when a toolset is enabled
_get_platform_tools() applies agent.disabled_toolsets as a final
override AFTER reading platform_toolsets.<platform>, so a toolset
listed there stays permanently OFF no matter what the toggle write
path saves. Blank Slate installs pre-populate this list with ~27
toolsets, making most of the desktop Toolsets UI un-enableable
(issue #49995).

Fix: _save_platform_tools() now removes any toolset the user just
explicitly enabled FOR THIS PLATFORM from agent.disabled_toolsets.
Toolsets the user did not touch, or that remain disabled on other
platforms, are left alone -- disabled_toolsets keeps working as a
cross-platform suppression list for anything not actively re-enabled.
Disabling a toolset (unchecking it) does not touch disabled_toolsets
at all -- only enables reconcile it.

Verified end-to-end with the exact repro from the issue: Blank Slate
config (disabled_toolsets=['todo','memory','browser'], cli=['file',
'terminal']) -> enable 'todo' via the toggle -> _get_platform_tools()
now resolves 'todo' as enabled while 'memory'/'browser' (untouched)
remain disabled.

Added 4 regression tests. Full tools_config suite: 101 passed
(97 existing + 4 new), no regressions.

Fixes #49995
2026-06-28 21:59:03 +05:30
Teknium d3d621f7c3
revert(windows): roll back terminal-popup PRs #53791 #53810 #53829 (#53853)
* Revert "fix(windows): capture is not a no-window boundary; route flashing spawns through chokepoint (#53829)"

This reverts commit 2ecca1e7d3.

* Revert "fix(windows): stop terminal-window popups from background spawns (#53810)"

This reverts commit 5db1430af9.

* Revert "fix(windows): stop subprocess console-window popups + add CI guard (#53791)"

This reverts commit ef17cd204d.
2026-06-27 15:59:00 -07:00