Commit Graph

289 Commits

Author SHA1 Message Date
kshitijk4poor b1b8ea4f7d refactor(tools_config): single-pass pruning in _prune_toolsets_stripped_by_disabled
Resolve each remaining toolset once and state the keep-rule positively
(no static tools, or at least one tool survived the runtime subtraction)
instead of building a drop-set in a second resolve loop.
2026-09-05 12:21:28 +05:30
aydnOktay 2494eadb39 fix(tools): align inspection surfaces with disabled composite toolsets
hermes tools --summary and CLI /tools used name-level disabled_toolsets subtraction, so disabling debugging still showed terminal/web/file as enabled while runtime stripped those tools. Prune platform toolsets after tool-level subtraction and pass disabled_toolsets into CLI get_tool_definitions calls.

Fixes #97015

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-05 12:21:28 +05:30
Teknium d63e380324 compat(plugins): warn once per name when a plugin resolves an old import path; lint step restored in CI
Every PLUGIN-COMPAT __getattr__ now calls hermes_cli.plugin_compat.warn_once(facade, name, target) before
resolving, emitting a HermesPluginCompatWarning (FutureWarning) once per process per name: old path, new
path, removal target. Importing a facade for its live API stays silent; only resolving a moved name warns.
COMPAT_MANIFEST.md documents the warning and how to silence it during migration.

Verified the runtime never routes through a pointer: every entry point (run_agent, cli, hermes_cli.main,
gateway.run, tui_gateway.server, web_server, model_tools + tool discovery, hermes_state, cron.scheduler,
browser_tool, mcp_tool, kanban, auth) imports clean and `hermes doctor` runs end to end with the warning
promoted to an error.

Also restores the check_compat_pointers CI step to .github/workflows/lint.yml, which a0be177aac dropped
when the compat layer was regenerated (the lint script itself was present; the workflow step was not).

hermes_cli/plugin_compat.py, tests/test_plugin_compat_warning.py and the two-line insert per facade are
part of the compat layer and go away with it.
2026-09-04 00:15:16 -07:00
Teknium 2776813df3 compat(plugins): temporary import-path shims for external plugins — ONE commit, revert on schedule
The Sep 2026 decomposition (PR #102117) makes internal import paths a non-API: names now live in
the focused modules that define them. This commit is the ONLY thing keeping the old paths alive,
so external plugins have time to update. It is deliberately a single, unsquashed commit:

    git revert <this sha>

removes every shim, stub and manifest at once on the announced date. Nothing in-tree may depend on
these pointers: scripts/check_compat_pointers.py (wired into lint.yml) fails CI if it does.

What it adds (see COMPAT_MANIFEST.md, compat_manifest.json):
- 332 facade modules get one delimited `PLUGIN-COMPAT` block appended at the end of the file
- 1,172 moved names resolved lazily via a module `__getattr__` (PEP 562) — never a top-level import,
  so no import cycles; facades that already had `__getattr__` get a chained one
- 592 third-party/stdlib names the old modules used to expose, with their original import statements
- 266 public definitions that had been deleted as unused, restored byte-for-byte from the pre-decomposition
  tree (+40 private helpers and 16 imports pulled in only because a restored definition needs them)
- 3 deleted modules recreated as re-export stubs (gateway/startup_watchdog, hermes_cli/observability/
  relay_runtime, tools/environments/modal_utils)
- private names (`_x`) get no pointer: they were never API (3,792 skipped)

Verified: all 335 touched modules import under a fresh HERMES_HOME and every manifest name resolves;
the lint reports zero in-tree uses; ruff clean; targeted suites unchanged.
2026-09-03 17:13:22 -07:00
Teknium e83816a4d1 review-fix(comments): restore lost #NNNN rationale comments across non-test source (mechanical sweep, condensed, code unchanged)
For each issue anchor present in BASE 63279301bc non-test .py and absent on HEAD, the BASE comment/docstring block was re-attached at the HEAD location of the code it explained (matched by the distinctive code line / enclosing def). Sentences already covered by an existing HEAD comment were deduped; the issue number always survives. Insert-only: no code lines changed.
2026-09-03 09:44:26 -07:00
Teknium 36131e1400 refactor(hermes_cli): tools_config — set-comprehension subset, simpler mcp merge/flag parse, menu row builder 2026-09-03 00:03:30 -07:00
Teknium 6ef7605fbb refactor(hermes_cli): tools_config — drop 43 unreferenced re-exports, collapse prompt/one-use temporaries; tools_config_cua — _fail()/_print_output_tail()/_report_repair_or_upgrade() helpers, install_cua_driver + _run_cua_driver_installer phase extraction (tests green) 2026-09-02 23:22:50 -07:00
Teknium ec4583f970 refactor(hermes_cli): tools_config — _select_into() unifies 6 provider-selection writes, gen-model picker partials, table-driven menu loop, _configure_platforms() merges one/all-platform flows (fuzz-verified byte-identical) 2026-09-02 23:11:10 -07:00
Teknium 86b65bbacb refactor(hermes_cli): tools_config — TOOL_CATEGORIES rows via _row() builder (ordered-JSON identical) 2026-09-02 21:42:33 -07:00
Teknium ea07321366 refactor(hermes_cli): tools_config group — hand-compacted docstrings/comments (every WHY kept) 2026-09-02 21:35:34 -07:00
Teknium 316427f215 refactor(hermes_cli): tools_config group — AST-neutral closer hugging and item packing (data tables kept expanded) 2026-09-02 21:22:11 -07:00
Teknium d7589b3200 refactor(hermes_cli): tools_config — _key keeps original env_vars key order (ordered JSON parity) 2026-09-02 21:18:46 -07:00
Teknium 3715d600e0 refactor(hermes_cli): tools_config — set-comprehension resolvers, compact _save_platform_tools preservation (fuzz-verified) 2026-09-02 21:14:50 -07:00
Teknium 3da41d7117 refactor(hermes_cli): tools_config — _key() row builder for TOOL_CATEGORIES env_vars (deep-equal), shared checklist apply step 2026-09-02 21:04:49 -07:00
Teknium f332d5b2ba refactor(hermes_cli): tools_config group — AST-neutral bracket hugging 2026-09-02 20:38:18 -07:00
Teknium c7970c3957 refactor(hermes_cli): tools_config_cua — shared _run_text/_UPGRADE_CMD, single Popen path, folded ladders; tools_config header + small collapses 2026-09-02 20:33:43 -07:00
Teknium 2df3d507c9 refactor(tools_config): reflow data tables and compact docstrings/comments in tools_config 2026-09-02 16:40:13 -07:00
Teknium 80b2cc4814 refactor(tools_config): reflow re-import blocks and TOOL_CATEGORIES env_vars rows 2026-09-02 16:31:32 -07:00
Teknium 55ba3cbd61 refactor(tools_config): extract MCP checklist + enable/disable command into tools_config_mcp; compact checklist/reconfigure helpers 2026-09-02 16:22:15 -07:00
Teknium 4d8d479e41 refactor(tools_config): split _get_platform_tools into explicit/composite resolvers; compact header comments 2026-09-02 16:18:39 -07:00
Teknium ffed5b1503 refactor(tools_config): split tools_command into flow helpers; drop unused imports 2026-09-02 16:14:49 -07:00
Teknium 13e7cb8eb9 refactor(tools_config): merge provider-selection extraction (tools_config_providers) 2026-09-02 16:11:56 -07:00
Teknium 3f0e1165b3 refactor(tools_config): extract post-setup hooks into tools_config_post_setup 2026-09-02 16:11:26 -07:00
Teknium c11fa53d92 refactor(tools_config): extract provider selection cluster into tools_config_providers 2026-09-02 16:05:46 -07:00
Teknium 3c7cd69abb refactor(tools_config): extract cua-driver installer cluster into tools_config_cua 2026-09-02 15:43:44 -07:00
Teknium 5d4b97939e refactor(hclib): config — config/config_migrations/tools_config/toolset_* dispatch tables and dedupe 2026-09-02 14:42:18 -07:00
Teknium bd7cdd7c53 Merge origin/main into core-tool-deferral (resolve show_tip test seam onto the check_tips_enabled gate) 2026-09-01 21:49:14 -07:00
Ben Barclay 180291162f feat(telemetry): opt-in shared-metrics exporter (#95278)
feat(telemetry): opt-in shared-metrics exporter
2026-09-02 08:35:36 +10:00
Lakshya Agarwal 428e084dcd feat(web): add Tavily web search and extract provider
This commit re-introduces the Tavily provider, which supports both search and content extraction capabilities, which was removed in #99199.
2026-09-01 10:56:49 -07:00
phi hu 081030a7dd fix(config): warn for empty platform toolsets 2026-08-31 10:08:31 -07:00
Adrià Arrufat e3a85ae5a0 feat(browser): honor browser.engine=lightpanda in Browser Use mode
Browser Use mode never read browser.engine: _resolve_backend_cdp() went
BU_CDP_* env -> CDP override -> cloud provider -> local Chrome, so
`engine: lightpanda` was a silent no-op on the default backend, and on
the built-in path it was skipped whenever a cloud provider, Camofox or a
CDP override was active without anyone saying so.

- browser_use_cli: when the engine is lightpanda and nothing with higher
  precedence claimed the session, get a session from _get_session_info()
  and export its endpoint as BU_CDP_URL; the browser is private to the
  session key, so the own-tab preamble is skipped. The browser_exec
  description gains a Lightpanda header (text-first, new_tab once then
  goto_url — lightpanda-io/browser#1962).
- browser_tool: _create_local_session() spawns `lightpanda serve
  --host 127.0.0.1 --port <free>` per session key (new
  tools/browser_lightpanda.py), reusing the session cache, inactivity
  reaper and atexit cleanup; a dead process is respawned on the next call;
  orphans from a crashed Hermes are reaped through per-process records in
  $HERMES_HOME/cache/browser-use/lightpanda/. New lightpanda_engine_status()
  reports whether the engine is in effect or what shadows it.
- tools_config: "Lightpanda" row in the Browser Automation picker
  (cloud_provider: local + engine: lightpanda; "Local Browser" resets the
  engine to auto) with a binary-check post-setup.
- /browser status and hermes doctor print the engine state and, when it
  is shadowed, the reason.
2026-08-31 21:59:11 +05:30
Teknium d6773cf26f refactor: remove the Tavily web backend; keyless ring is exa/parallel/firecrawl/keenable
- Tavily plugin deleted (plugins/web/tavily), keyless endpoints and
  ring entry removed from keyless_mcp, legacy backend set / credential
  ladder / preference walks / rescue key map scrubbed.
- TAVILY_API_KEY deregistered across config, setup, status, dump, and
  nous_subscription surfaces. The tvly- redaction pattern stays --
  legacy keys in user envs still deserve masking.
- Sibling test pins migrated (keenable/exa stand in where tavily was
  the fixture vendor); tavily test suite deleted.
- Docs updated: web-search, configuration, integrations,
  environment-variables, tools-reference, web-dashboard, provider
  plugin dev guide.

Live-verified from an isolated HERMES_HOME with all web creds blanked:
zero-config resolution lands in the 4-vendor ring, live keyless ring
search succeeds, no tavily anywhere in resolution order.
2026-08-31 00:56:41 -07:00
Teknium e16ad33a9d feat(tool-search): core-tool deferral — curated 19-tool set behind the bridge by default; renames todo_list/cronjob_manage/process_manage/gui_tour/show_tip with legacy aliases (13.4K -> 6.9K desktop schemas, -49%) 2026-08-29 08:26:24 -07:00
Ben Barclay 49757d5e39 fix(telemetry): address review findings on the shared-metrics sender
Independent review found the claim mechanism did not work. Reproduced
against the real store: two senders POSTed the same package.

The claim wrote next_attempt_at = now, but selection requires
next_attempt_at <= now, so a concurrent pass matched the same row
immediately. It now writes a LEASE INTO THE FUTURE
(_CLAIM_LEASE_SECONDS), which is what actually excludes another pass,
and expires by itself if a process dies mid-send. _mark is additionally
guarded on send_state so a straggler whose lease lapsed cannot
overwrite a completed send back to pending.

The old concurrency test could not fail: it raised AssertionError from
inside a transport, and _send_one catches every exception as a
retryable transport error. It now records what the second pass saw.

Also from review:

- shutdown() never joined the send thread; the join was only wired into
  deactivate(). A short-lived CLI therefore killed an in-flight send at
  exit, on the only cadence this feature has.
- Removed HERMES_TELEMETRY_ENDPOINT. AGENTS.md reserves HERMES_* for
  secrets, and a behavioural override here was a consent hazard: an
  inherited variable could silently redirect telemetry a user agreed to
  send to Nous. The staging E2E writes the endpoint into its throwaway
  profile instead, which also exercises the real config path.
- Added the  shared-metrics toggle that AGENTS.md requires
  as the third opt-in surface, delegating to the setup prompt so the
  consent rules stay in one place.
- Non-429 4xx (401/403/404/413/422) are now permanent. Only 400 was,
  so a wrong path or oversized body retried every 15 minutes for 30
  days until retention pruned it.
- The opt-in day is stamped when the user consents, not on the first
  send pass, which silently dropped the opt-in day whenever the next
  export crossed midnight UTC.
- gzip now uses mtime=0. The embedded timestamp made two sends of one
  package differ on the wire, so the 'byte-identical retry' E2E was
  comparing parsed bodies and could not have caught it. It now compares
  raw request bytes.
- Reconciled the three stale claims in relay-shared-metrics.md that
  said no remote-delivery path exists.

233 tests pass (was 213). Staging E2E re-run through the config path:
both packages 202, and the service logged both objects written to S3.
2026-08-26 16:31:52 +10:00
Teknium 23c3f5086e feat(update): unattended-safe cua-driver refresh with fail-fast preflights
Re-enables the routine confirmed-upgrade path on Windows that #95008
deferred wholesale, now that every unattended-hostile surface is closed:

- stdin=DEVNULL (salvaged #79871): upstream's Read-Host consent prompt
  can't block a hidden console.
- Bounded post-kill drain (salvaged #87720): a kill-surviving descendant
  holding the stdout pipe can't strand the update past its ceiling.
- 120s background ceiling (salvaged #87196): safe now that a legitimate
  600s lock wait can't occur on this path.
- NEW lock preflight: upstream's install lock held by a live process ->
  skip in ~0s instead of eating its 600s stale-lock window (the actual
  11-minute hang observed 2026-08-25; UAC was a red herring — base
  install is no-admin by upstream design).
- NEW 5s network preflight: github.com unreachable -> skip immediately.
- Windows unattended runs pass -NoAutoStart, skipping the ONLY
  install.ps1 branch that self-elevates (autostart task re-registration).
- Timeout diagnosability: partial installer output is logged on kill so
  the next hang names its stage instead of dying silently.

Contract repairs and fresh installs stay interactive-only (SmartScreen /
first-time elevation legitimately need a human).
2026-08-25 21:59:52 -07:00
blunkjamie-dev 105cea64f7 fix(update): bound optional cua-driver refresh 2026-08-25 21:59:52 -07:00
Jack Lau 65fada0945 fix(update): bound the cua-driver installer drain after a failed kill
On Windows, `hermes update` can hang past its own 660s cua-driver timeout
until the user kills an orphaned PowerShell by hand. The timeout ceiling is
not the problem; the code that runs after it is.

`_run_cua_driver_installer` handles `TimeoutExpired` by killing the process
tree and then draining the pipes with a bare `proc.communicate()`. The kill
is best-effort by construction: every `psutil.Error` in `_kill_installer_tree`
is logged at debug level and stepped over, on the reasoning that a partly
killed tree beats none. That is the right call, but it means the drain has to
survive a partial kill, and an unbounded drain does not.

The concrete case is the one reported. `install.ps1` self-elevates through
`Start-Process -Verb RunAs`, so the descendant runs at High integrity and a
medium-integrity `child.kill()` raises `AccessDenied`. The per-child handler
logs it and continues. That survivor is still holding the `stdout=PIPE` write
handle it inherited, so the following `communicate()` waits for an EOF that
arrives only when somebody kills that process manually. A bounded 660s wait
becomes an unbounded one, after the warning has already printed.

Bound the drain instead. A kill that landed closes the pipe immediately, so
this costs nothing on the normal path; a kill that did not costs 15s rather
than forever. The original `TimeoutExpired` is re-raised either way, so the
existing manual re-run hint still prints and the update unwinds. Losing the
tail of a timed-out installer's log is the cheaper half of that trade, and it
is only lost in the case where the run already failed.

The drain deliberately does not close the pipe handles. `communicate()`'s
reader threads are still blocked on them and closing underneath them races;
they are daemon threads, so abandoning them does not hold the interpreter
open.

Both timeout handlers (streaming and captured) now go through one helper.
The streaming child inherits the console rather than a pipe, so it is much
harder to stall there, but the two branches should not drift on a rule this
small.

Tests: 5, in a new `TestInstallerTimeoutDrainIsBounded`. Two fail without the
fix, including the reported scenario end to end (a child kill refused with
`psutil.AccessDenied`, asserting the drain still carries a deadline). The
deadline is asserted as a kwarg rather than by timing, because a test that
proved the hang by hanging would be the same defect wearing a test's name.

Scope note: this does not touch the `stdin` inheritance that lets
`install.ps1`'s `Read-Host` block in the first place. That is #79684 and open
PR #79871 already carries the one-line `stdin=DEVNULL` fix; the two are
independent and neither subsumes the other, since `DEVNULL` cannot unblock a
UAC elevation dialog.

Fixes #87703
2026-08-25 21:59:52 -07:00
RelaxJonh 5c5e8492e0 fix: redirect stdin to DEVNULL in non-verbose cua-driver installer (#79684)
When `hermes update` runs the cua-driver installer non-interactively,
stdout is captured (PIPE) but stdin is inherited from the parent process.
The upstream install.ps1 prompts `[Y/n]` when it detects a stale
cua-driver daemon, but the prompt goes to captured stdout (invisible)
while stdin waits for input — causing an 11-minute hang until the
watchdog timeout.

Fix: redirect stdin to subprocess.DEVNULL on the non-verbose path so the
installer reads EOF immediately instead of blocking. The installer exits
quickly with a non-zero code, and the existing handler shows the manual
re-run hint with the installer's captured output.

Fixes #79684
2026-08-25 21:59:52 -07:00
Teknium f751a8c546 fix(update): also defer the missing-binary CUA install on Windows
Follow-up to the salvaged #94296: the two guards covered the repair and
confirmed-update branches, but when cua-driver is enabled yet not
installed at all, control still reached _run_cua_driver_installer() and
an automatic 'hermes update' would launch the interactive install.ps1
anyway. Add the same defer before the installer run, keep POSIX
behavior unchanged, and give the confirmed-update message a natural
fallback when latest_version is unknown.
2026-08-25 16:53:01 -07:00
Royalaid 0c23bf19af fix(update): defer interactive CUA installs on Windows 2026-08-25 16:53:01 -07:00
Teknium 45b35f962f fix(mcp): tool-selection UIs stay in exclude mode instead of freezing include lists
Closes the two config-UI halves of the exclude-mode review (GottZ findings
5-7 on #94513):

- hermes mcp configure: on an exclude-mode server, unchecking a tool now
  APPENDS a literal exclude and re-checking drops it — glob patterns are
  preserved so future vendor tools keep getting filtered. Previously one
  uncheck converted the whole config to a frozen include list (globs
  silently deleted, new vendor tools invisible). Re-checked tools still
  shadowed by a kept glob get an explicit warning instead of a silent
  no-op.
- hermes tools MCP checklist: same exclude-mode write-back, plus display
  now matches excludes via matches_name_filter (fnmatch) — glob excludes
  previously rendered as if nothing were excluded.
- klaviyo manifest: post_install no longer tells users to append a param
  the URL already pins; now documents how to get the FULL surface.

Live-verified through the real cmd_mcp_configure path: exclude-mode server
with ['*_secret_*', 'docs'], uncheck beta + re-check docs ->
exclude becomes ['*_secret_*', 'beta'], no include written.
171 tests green across test_mcp_catalog/test_mcp_config/test_mcp_tool.
2026-08-25 04:21:37 -07:00
Teknium 76e306c458 refactor(tools): remove expired BFL FLUX 3 promo core tools (migration v39); FLUX 3 stays via video_gen/FAL for subscribers (#94599)
* refactor(tools): remove expired bfl_flux3_* promo tools; FLUX 3 rides the video_gen provider surface

* test: relay-cutover migration asserts >= v38, not the version literal
2026-08-25 02:45:10 -07:00
Teknium 095f003377 Merge remote-tracking branch 'origin/main' into feat/keyless-web-search-fallback
# Conflicts:
#	hermes_cli/tools_config.py
2026-08-19 16:48:40 -07:00
Teknium aa3c5e59d3 fix(tools): honor raw stt.provider: local; finish _reconfigure_provider provider-string migration
Two real gaps the CI-red sibling tests exposed:

- read_selection() treated EVERY raw stt.provider: local as the legacy
  DEFAULT_CONFIG seed and reported no-selection — but the seed never
  reached config.yaml (save_config strips schema defaults), so a
  picker- or hand-written local pick was silently discarded and the
  autodetect ladder could route an explicit local user to cloud STT.
  A raw 'local' is now a genuine selection; the merged-view ambiguity
  note replaces the over-broad shim (mirror comment updated in
  nous_subscription._selected_provider and _get_provider).

- _reconfigure_provider was half-migrated: the tts/stt/browser/web
  branches and the managed-category fallthrough still wrote
  use_gateway flags and vendor names for managed rows. They now write
  the single provider string ('nous' for managed rows) and pop the
  legacy key, matching _write_provider_config.
2026-08-19 16:10:01 -07:00
Teknium b10c5a8084 fix(cli): persist one provider string per picker row; mirror strict routing in status
Every hermes tools row now writes exactly one selection value per
category — managed 'Nous Subscription' rows write 'nous', BYOK rows the
vendor name (including the historically-unset BYOK-FAL image row) — and
use_gateway is no longer written; fresh picks drop any legacy key so the
read-time shim cannot override them. The non-managed clear now resolves
the category from the row's own markers, covering plugin-injected rows
the TOOL_CATEGORIES loop missed. Setup-flow writers (managed defaults,
gateway enablement) store 'nous', and the feature-state mirrors in
nous_subscription.py compute per-category selections with the same
legacy interpretation so hermes status matches runtime: a stored vendor
selection pins direct (managed availability no longer lights it up) and
an explicit non-camofox selection beats a stray CAMOFOX_URL.
2026-08-19 16:10:01 -07:00
Teknium f08d3e400f feat: hermes tools lets Exa/Parallel users pick the free keyless or paid keyed endpoint
Exa and Parallel now each render as two picker rows in hermes tools —
'Free (keyless)' and 'Paid (API key)'. Selection persists to
web.provider_tier.<name>:
- free: always the anonymous public endpoint, even with a key set
- paid: always the keyed SDK path; missing key errors instead of
  silently downgrading to the free tier (is_keyless_available also
  returns False so the auto-fallback walk can't route there)
- unset: auto (key present -> paid, else keyless)

Mechanism: get_setup_schema() gains a 'variants' list the picker
flattens into sibling rows sharing one web_backend; selection writes
the tier via both _write_provider_config sites; active-row detection
matches the tier (auto mirrors use_keyless). Routing goes through a
single use_keyless() chokepoint shared by search+extract in both
providers.

Live E2E: tier=free with a fake key present searched keyless OK (a
keyed call would have 401'd); tier=paid without key errored naming
PARALLEL_API_KEY; picker rows verified for both vendors x both tiers.
2026-08-19 15:36:20 -07:00
Teknium e6ff4eacdb fix(tools-config): widen stale-model picker guard to legacy image and video pickers
Same bug class as the plugin image picker crash (#77238): an unguarded
`current_model = default_model` fallback that can index the catalog with a
key it doesn't contain when the provider's default drifts from its catalog.
Applies the `default if default in catalog else next(iter(catalog))` guard
to _configure_imagegen_model and _configure_videogen_model_for_plugin.
2026-08-19 01:19:37 -07:00
zhuermu c6b680c445 fix(image-gen): handle stale OpenRouter model defaults 2026-08-19 01:19:37 -07:00
Teknium 00c12dac61 fix(computer-use): auto-repair an installed driver that fails the runtime contract
A same-day version-floor bump (0.20 runtime contract) left every install
with an older cua-driver hard-failing on all computer_use calls: the
start() gate fails closed, while the `hermes update` refresh defers to the
driver's own check-update verb — whose ~20h cache routinely answers "no
update available" right after we raise the floor. Hermes knew it required
0.20+ but never acted on that knowledge.

Two changes:

- tools_config.install_cua_driver(): a contract-failed installed driver is
  repaired on the upgrade=True path too (previously only upgrade=False).
  The contract failure itself is the confirmation, so the
  require_confirmed_update gate and the check-update short-circuit are
  bypassed for repairs — an indeterminate or stale-cached check can no
  longer pin users on an unusable driver.

- cua_backend.CuaDriverBackend.start(): when the contract gate fails on an
  installed binary, attempt one automatic repair per process via the
  standard install path, then re-probe. HERMES_CUA_DRIVER_CMD overrides
  are never repaired (explicit override is authoritative even when broken)
  and a missing binary still just reports the install hint. A failing
  installer can't loop: the second start() surfaces the original error.

Tests: contract-repair coverage in test_computer_use.py (auto-repair
success, failed repair surfaces the original error, once-per-process
guard, override never repaired, missing binary never repaired) and
test_install_cua_driver.py (incompatible driver repairs despite an
indeterminate check-update, check-update not consulted). All new tests
verified to fail against the unfixed source (sabotage run).
2026-08-16 13:01:20 -07:00
f-trycua c83ea25dd4 fix(computer-use): preserve missing driver overrides 2026-08-16 11:34:40 -07:00