Commit graph

433 commits

Author SHA1 Message Date
Teknium
d82580b25b fix: add all_profiles param + narrow exception handling
- add all_profiles=False to find_gateway_pids() and
  kill_gateway_processes() so hermes update and gateway stop --all
  can still discover processes across all profiles
- narrow bare 'except Exception' to (OSError, subprocess.TimeoutExpired)
- update test mocks to match new signatures
2026-04-11 14:44:29 -07:00
Dominic Grieco
b80e318168 fix: scope gateway status to the active profile 2026-04-11 14:44:29 -07:00
Teknium
4bede272cf fix: propagate model through credential pool path + add tests
The cherry-picked fix from PR #7916 placed model propagation after
the credential pool early-return in _resolve_named_custom_runtime(),
making it dead code when a pool is active (which happens whenever
custom_providers has an api_key that auto-seeds the pool).

- Inject model into pool_result before returning
- Add 5 regression tests covering direct path, pool path, empty
  model, and absent model scenarios
- Add 'model' to _VALID_CUSTOM_PROVIDER_FIELDS for config validation
2026-04-11 14:09:40 -07:00
Brooklyn Nicholson
9ccb490cf3 Merge branch 'main' of github.com:NousResearch/hermes-agent into feat/ink-refactor 2026-04-11 15:30:23 -05:00
Brooklyn Nicholson
32302c37dd feat: fix types and add type checking plus lazybundle on launch andddd dev flag 2026-04-11 14:42:28 -05:00
Teknium
4e3e87b677 feat(migration): preview-then-confirm UX + docs updates
hermes claw migrate now always shows a full dry-run preview before
making any changes. The user reviews what would be imported, then
confirms to proceed. --dry-run stops after the preview. --yes skips
the confirmation prompt.

This matches the existing setup wizard flow (_offer_openclaw_migration)
which already did preview-then-confirm.

Docs updated across both docs/migration/openclaw.md and
website/docs/guides/migrate-from-openclaw.md to reflect:
- New preview-first UX flow
- workspace-main/ fallback paths
- accounts.default channel token layout
- TTS edge/microsoft rename
- openclaw.json env sub-object as API key source
- Hyphenated provider API types
- Matrix accessToken field
- SecretRef file/exec warnings
- Skills session restart note
- WhatsApp re-pairing note
- Archive cleanup step
2026-04-11 11:35:23 -07:00
Teknium
d4bb44d4b9 docs: add Xiaomi MiMo to all provider docs + fix MiMo-V2-Flash ctx len
- environment-variables.md: XIAOMI_API_KEY, XIAOMI_BASE_URL, provider list
- cli-commands.md: --provider choices
- integrations/providers.md: provider table, Chinese providers section,
  config example, base URL list, choosing table, fallback providers list
- fallback-providers.md: supported providers table, auto-detection chain
- Fix XiaomiMiMo/MiMo-V2-Flash context length 32768 → 256000 (OpenRouter entry)
2026-04-11 11:17:52 -07:00
kshitijk4poor
6693e2a497 feat(xiaomi): add Xiaomi MiMo as first-class provider
Cherry-picked from PR #7702 by kshitijk4poor.

Adds Xiaomi MiMo as a direct provider (XIAOMI_API_KEY) with models:
- mimo-v2-pro (1M context), mimo-v2-omni (256K, multimodal), mimo-v2-flash (256K, cheapest)

Standard OpenAI-compatible provider checklist: auth.py, config.py, models.py,
main.py, providers.py, doctor.py, model_normalize.py, model_metadata.py,
models_dev.py, auxiliary_client.py, .env.example, cli-config.yaml.example.

Follow-up: vision tasks use mimo-v2-omni (multimodal) instead of the user's
main model. Non-vision aux uses the user's selected model. Added
_PROVIDER_VISION_MODELS dict for provider-specific vision model overrides.
On failure, falls back to aggregators (gemini flash) via existing fallback chain.

Corrects pre-existing context lengths: mimo-v2-pro 1048576→1000000,
mimo-v2-omni 1048576→256000, adds mimo-v2-flash 256000.

36 tests covering registry, aliases, auto-detect, credentials, models.dev,
normalization, URL mapping, providers module, doctor, aux client, vision
model override, and agent init.
2026-04-11 11:17:52 -07:00
Brooklyn Nicholson
b04248f4d5 Merge branch 'main' of github.com:NousResearch/hermes-agent into feat/ink-refactor
# Conflicts:
#	gateway/platforms/base.py
#	gateway/run.py
#	tests/gateway/test_command_bypass_active_session.py
2026-04-11 11:39:47 -05:00
kagura-agent
4d1f1dccf9 fix: normalize numeric MCP server names to str (fixes #6901)
YAML parses bare numeric keys (e.g. `12306:`) as int, causing
TypeError when sorted() is called on mixed int/str collections.

Changes:
- Normalize toolset_names entries to str in _get_platform_tools()
- Cast MCP server name to str(name) when building enabled_mcp_servers
- Add regression test
2026-04-11 02:03:20 -07:00
kshitijk4poor
c89719ad9c fix: warn and clear stale OPENAI_BASE_URL on provider switch (#5161) 2026-04-11 01:52:58 -07:00
jamesarch
704488b207 fix(setup): relaunch chat in a fresh process 2026-04-11 01:47:48 -07:00
Kenny Xie
ecfae98152 fix(gateway): address restart review feedback 2026-04-10 21:18:34 -07:00
aquaright1
a55c044ca8 fix(gateway): self-request service restarts when invoked in-process 2026-04-10 21:18:34 -07:00
Kenny Xie
3163731289 fix(gateway): drain in-flight work before restart 2026-04-10 21:18:34 -07:00
alt-glitch
d5be23aed7 docs(matrix): update all references from matrix-nio to mautrix 2026-04-10 21:15:59 -07:00
Teknium
a8fd7257b1
feat(gateway): WSL-aware gateway with smart systemd detection (#7510)
- Add shared is_wsl() to hermes_constants (like is_termux)
- Update supports_systemd_services() to verify systemd is actually
  running on WSL before returning True
- Add WSL-specific guidance in gateway install/start/setup/status
  for both cases: WSL+systemd and WSL without systemd
- Improve help strings: 'run' now says recommended for WSL/Docker,
  'start'/'install' now mention systemd/launchd explicitly
- Add WSL gateway FAQ section with tmux/nohup/Task Scheduler tips
- Update CLI commands docs with WSL tip
- Deduplicate _is_wsl() from clipboard.py to shared hermes_constants
- Fix clipboard tests to reset hermes_constants cache
- 20 new WSL-specific tests covering detection, systemd check,
  supports_systemd_services integration, and command output

Motivated by user feedback: took 1 hour to figure out run vs start
on WSL, Telegram bot kept disconnecting due to flaky WSL systemd.
2026-04-10 21:15:47 -07:00
Teknium
436dfd5ab5 fix: no auto-activation + unified hermes plugins UI with provider categories
- Remove auto-activation: when context.engine is 'compressor' (default),
  plugin-registered engines are NOT used. Users must explicitly set
  context.engine to a plugin name to activate it.

- Add curses_radiolist() to curses_ui.py: single-select radio picker
  with keyboard nav + text fallback, matching curses_checklist pattern.

- Rewrite cmd_toggle() as composite plugins UI:
  Top section: general plugins with checkboxes (existing behavior)
  Bottom section: provider plugin categories (Memory Provider, Context Engine)
  with current selection shown inline. ENTER/SPACE on a category opens
  a radiolist sub-screen for single-select configuration.

- Add provider discovery helpers: _discover_memory_providers(),
  _discover_context_engines(), config read/save for memory.provider
  and context.engine.

- Add tests: radiolist non-TTY fallback, provider config save/load,
  discovery error handling, auto-activation removal verification.
2026-04-10 19:15:50 -07:00
Teknium
496e378b10
fix: resolve overlay provider slug mismatch in /model picker (#7373)
HERMES_OVERLAYS keys use models.dev IDs (e.g. 'github-copilot') but
_PROVIDER_MODELS curated lists and config.yaml use Hermes provider IDs
('copilot'). list_authenticated_providers() Section 2 was using the
overlay key directly for model lookups and is_current checks, causing:
- 0 models shown for copilot, kimi, kilo, opencode, vercel
- is_current never matching the config provider

Fix: build reverse mapping from PROVIDER_TO_MODELS_DEV to translate
overlay keys to Hermes slugs before curated list lookup and result
construction. Also adds 'kimi-for-coding' alias in auth.py so the
picker's returned slug resolves correctly in resolve_provider().

Fixes #5223. Based on work by HearthCore (#6492) and linxule (#6287).

Co-authored-by: HearthCore <HearthCore@users.noreply.github.com>
Co-authored-by: linxule <linxule@users.noreply.github.com>
2026-04-10 14:46:57 -07:00
Julien Talbot
8bcb8b8e87 feat(providers): add native xAI provider
Adds xAI as a first-class provider: ProviderConfig in auth.py,
HermesOverlay in providers.py, 11 curated Grok models, URL mapping
in model_metadata.py, aliases (x-ai, x.ai), and env var tests.
Uses standard OpenAI-compatible chat completions.

Closes #7050
2026-04-10 13:40:38 -07:00
Teknium
f72faf191c
fix: fall back to default certs when CA bundle path doesn't exist (#7352)
_resolve_verify() returned stale CA bundle paths from auth.json without
checking if the file exists. When a user logs into Nous Portal on their
host (where SSL_CERT_FILE points to a valid cert), that path gets
persisted in auth.json. Running hermes model later in Docker where the
host path doesn't exist caused FileNotFoundError bubbling up as
'Could not verify credentials: [Errno 2] No such file or directory'.

Now _resolve_verify validates the path exists before returning it. If
missing, logs a warning and falls back to True (default certifi-based
TLS verification).
2026-04-10 12:51:19 -07:00
Teknium
4a65c9cd08
fix: profile paths broken in Docker — profiles go to /root/.hermes instead of mounted volume (#7170)
In Docker, HERMES_HOME=/opt/data (set in Dockerfile) and users mount
their .hermes directory to /opt/data. However, profile operations used
Path.home() / '.hermes' which resolves to /root/.hermes in Docker —
an ephemeral container path, not the mounted volume.

This caused:
- Profiles created at /root/.hermes/profiles/ (lost on container recreate)
- active_profile sticky file written to wrong location
- profile list looking at wrong directory

Fix: Add get_default_hermes_root() to hermes_constants.py that detects
Docker/custom deployments (HERMES_HOME outside ~/.hermes) and returns
HERMES_HOME as the root. Also handles Docker profiles correctly
(<root>/profiles/<name> → root is grandparent).

Files changed:
- hermes_constants.py: new get_default_hermes_root()
- hermes_cli/profiles.py: _get_default_hermes_home() delegates to shared fn
- hermes_cli/main.py: _apply_profile_override() + _invalidate_update_cache()
- hermes_cli/gateway.py: _profile_suffix() + _profile_arg()
- Tests: 12 new tests covering Docker scenarios
2026-04-10 05:53:10 -07:00
Kenny Xie
916fbf362c fix(model): tighten direct-provider fallback normalization 2026-04-10 05:52:45 -07:00
Kenny Xie
fd5cc6e1b4 fix(model): normalize native provider-prefixed model ids 2026-04-10 05:52:45 -07:00
r266-tech
1662b7f82a fix(test): correct mock target for fetch_api_models in custom provider tests
fetch_api_models is imported locally inside _model_flow_named_custom from
hermes_cli.models, not defined as a module-level attribute of hermes_cli.main.
Patch the source module so the local import picks up the mock.

Also force simple_term_menu ImportError so tests reliably use the input()
fallback path regardless of environment.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-10 05:52:45 -07:00
r266-tech
e3b395e17d test: add regression tests for custom provider model switching
Covers: probe always called, model switch works, probe failure fallback,
first-time flow unchanged.
2026-04-10 05:52:45 -07:00
Ronald Reis
49bba1096e fix: opencode-go missing from /model list and improve HERMES_OVERLAYS credential check
When opencode-go API key is set, it should appear in the /model list.
The provider was already in PROVIDER_TO_MODELS_DEV and PROVIDER_REGISTRY,
so it appears via Part 1 (built-in source).

Also fixes a potential issue in Part 2 (HERMES_OVERLAYS) where providers
with auth_type=api_key but no extra_env_vars would not be detected:
- Now also checks api_key_env_vars from PROVIDER_REGISTRY for api_key auth_type

- Add test verifying opencode-go appears when OPENCODE_GO_API_KEY is set
2026-04-10 05:52:45 -07:00
xwp
419b719c2b fix(auth): make 'auth remove' for claude_code prevent re-seeding
Previously, removing a claude_code credential from the anthropic pool
only printed a note — the next load_pool() re-seeded it from
~/.claude/.credentials.json.  Now writes a 'suppressed_sources' flag
to auth.json that _seed_from_singletons checks before seeding.

Follows the pattern of env: source removal (clears .env var) and
device_code removal (clears auth store state).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 05:19:21 -07:00
xwp
d7164603da feat(auth): add is_provider_explicitly_configured() helper
Gate function for checking whether a user has explicitly selected a
provider via hermes model/setup, auth.json active_provider, or env
vars.  Used in subsequent commits to prevent unauthorized credential
auto-discovery.  Follows the pattern from PR #4210.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 05:19:21 -07:00
H-5-Isminiz
00dd5cc491 fix(gateway): implement platform-aware PID termination 2026-04-10 03:52:00 -07:00
Teknium
437feabb74
fix(gateway): launchd_stop uses bootout so KeepAlive doesn't respawn (#7119)
launchd_stop() previously used `launchctl kill SIGTERM` which only
signals the process. Because the plist has KeepAlive.SuccessfulExit=false,
launchd immediately respawns the gateway — making `hermes gateway stop`
a no-op that prints '✓ Service stopped' while the service keeps running.

Switch to `launchctl bootout` which unloads the service definition so
KeepAlive can't trigger. The process exits and stays stopped until
`hermes gateway start` (which already handles re-bootstrapping unloaded
jobs via error codes 3/113).

Also adds _wait_for_gateway_exit() after bootout to ensure the process
is fully gone before returning, and tolerates 'already unloaded' errors.

Fixes: .env changes not taking effect after gateway stop+restart on macOS.
The root cause was that stop didn't actually stop — the respawned process
loaded the old env before the user's restart command ran.
2026-04-10 03:45:34 -07:00
alt-glitch
cff9b7ffab fix: restore 6 tests that tested live code but used deleted helpers 2026-04-10 03:44:43 -07:00
alt-glitch
96c060018a fix: remove 115 verified dead code symbols across 46 production files
Automated dead code audit using vulture + coverage.py + ast-grep intersection,
confirmed by Opus deep verification pass. Every symbol verified to have zero
production callers (test imports excluded from reachability analysis).

Removes ~1,534 lines of dead production code across 46 files and ~1,382 lines
of stale test code. 3 entire files deleted (agent/builtin_memory_provider.py,
hermes_cli/checklist.py, tests/hermes_cli/test_setup_model_selection.py).

Co-authored-by: alt-glitch <balyan.sid@gmail.com>
2026-04-10 03:44:43 -07:00
Evi Nova
8dd738c2e6 fix(gateway): remap all paths in system service unit to target user's home
When installing a system service via sudo, ExecStart, WorkingDirectory,
VIRTUAL_ENV, and PATH entries were not remapped to the target user's
home — only HERMES_HOME was. This caused the service to fail with
status=200/CHDIR because the target user cannot access /root/.

Adds _remap_path_for_user() helper and applies it to all path variables
in the system branch of generate_systemd_unit().

Closes #6989
2026-04-10 03:30:36 -07:00
donrhmexe
a2f46e4665 fix: include custom_providers in /model command listings and resolution
Custom providers defined in config.yaml under  were
completely invisible to the /model command in both gateway (Telegram,
Discord, etc.) and CLI. The provider listing skipped them and explicit
switching via --provider failed with "Unknown provider".

Root cause: gateway/run.py, cli.py, and model_switch.py only read the
 dict from config, ignoring  entirely.

Changes:
- providers.py: add resolve_custom_provider() and extend
  resolve_provider_full() to check custom_providers after user_providers
- model_switch.py: propagate custom_providers through switch_model(),
  list_authenticated_providers(), and get_authenticated_provider_slugs();
  add custom provider section to provider listings
- gateway/run.py: read custom_providers from config, pass to all
  model-switch calls
- cli.py: hoist config loading, pass custom_providers to listing and
  switch calls

Tests: 4 new regression tests covering listing, resolution, and gateway
command handler. All 71 tests pass.
2026-04-10 03:07:00 -07:00
Carlos
7368854398 Refresh OpenRouter model catalog
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-10 02:57:39 -07:00
Carlos
38ccd9eb95 Harden setup provider flows
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-10 02:57:39 -07:00
Teknium
0848a79476
fix(update): always reset on stash conflict — never leave conflict markers (#7010)
When `hermes update` stashes local changes and the restore hits merge
conflicts, the old code prompted the user to reset or keep conflict
markers.  If the user declined the reset, git conflict markers
(<<<<<<< Updated upstream) were left in source files, making hermes
completely unrunnable with a SyntaxError on the next invocation.

Additionally, the interactive path called sys.exit(1), which killed
the entire update process before pip dependency install, skill sync,
and gateway restart could finish — even though the code pull itself
had succeeded.

Changes:
- Always auto-reset to clean state when stash restore conflicts
- Remove the "Reset working tree?" prompt (footgun)
- Remove sys.exit(1) — return False so cmd_update continues normally
- User's changes remain safely in the stash for manual recovery

Also fixes a secondary bug where the conflict handling prompt used
bare input() instead of the input_fn parameter, which would hang
in gateway mode.

Tests updated: replaced prompt/sys.exit assertions with auto-reset
behavior checks; removed the "user declines reset" test (path no
longer exists).
2026-04-10 00:32:20 -07:00
g-guthrie
d416a69288 feat: add Codex fast mode toggle (/fast command)
Add /fast slash command to toggle OpenAI Codex service_tier between
normal and priority ('fast') inference. Only exposed for models
registered in _FAST_MODE_BACKEND_CONFIG (currently gpt-5.4).

- Registry-based backend config for extensibility
- Dynamic command visibility (hidden from help/autocomplete for
  non-supported models) via command_filter on SlashCommandCompleter
- service_tier flows through request_overrides from route resolution
- Omit max_output_tokens for Codex backend (rejects it)
- Persists to config.yaml under agent.service_tier

Salvage cleanup: removed simple_term_menu/input() menu (banned),
bare /fast now shows status like /reasoning. Removed redundant
override resolution in _build_api_kwargs — single source of truth
via request_overrides from route.

Co-authored-by: Hermes Agent <hermes@nousresearch.com>
2026-04-09 21:54:32 -07:00
Austin Pickett
f805323517 chore: merge main 2026-04-09 20:00:34 -04:00
adybag14-cyber
c3141429b7 fix(termux): tighten voice setup and mobile chat UX 2026-04-09 16:24:53 -07:00
adybag14-cyber
3237733ca5 fix(termux): harden execute_code and mobile browser/audio UX 2026-04-09 16:24:53 -07:00
adybag14-cyber
096b3f9f12 fix(termux): add local image chat route 2026-04-09 16:24:53 -07:00
adybag14-cyber
2194425918 fix(termux): make setup-hermes use android path 2026-04-09 16:24:53 -07:00
adybag14-cyber
3878495972 fix(termux): disable gateway service flows on android 2026-04-09 16:24:53 -07:00
adybag14-cyber
4e40e93b98 fix(termux): improve status and install UX 2026-04-09 16:24:53 -07:00
adybag14-cyber
e79cc88985 feat: add tested Termux install path and EOF-aware gh auth 2026-04-09 16:24:53 -07:00
Brooklyn Nicholson
99fd3b518d feat: add /copy and /agents 2026-04-09 17:19:36 -05:00
Greer Guthrie
775a46ce75 fix: normalize reasoning effort ordering in UI 2026-04-09 14:20:16 -07:00
Dylan Socolobsky
c6dba918b3
fix(tests): fix several failing/flaky tests on main (#6777)
* fix(tests): mock is_safe_url in tests that use example.com

Tests using example.com URLs were failing because is_safe_url does a real DNS lookup which fails in environments where example.com doesn't resolve, causing the request to be blocked before reaching the already-mocked HTTP client. This should fix around 17 failing tests.

These tests test logic, caching, etc. so mocking this method should not modify them in any way. TestMattermostSendUrlAsFile was already doing this so we follow the same pattern.

* fix(test): use case-insensitive lookup for model context length check

DEFAULT_CONTEXT_LENGTHS uses inconsistent casing (MiniMax keys are lowercase, Qwen keys are mixed-case) so the test was broken in some cases since it couldn't find the model.

* fix(test): patch is_linux in systemd gateway restart test

The test only patched is_macos to False but didn't patch is_linux to True. On macOS hosts, is_linux() returns False and the systemd restart code path is skipped entirely, making the assertion fail.

* fix(test): use non-blocklisted env var in docker forward_env tests

GITHUB_TOKEN is in api_key_env_vars and thus in _HERMES_PROVIDER_ENV_BLOCKLIST so the env var is silently dropped, we replace it with a non-blocked one like DATABASE_URL so the tests actually work.

* fix(test): fully isolate _has_any_provider_configured from host env

_has_any_provider_configured() checks all env vars from PROVIDER_REGISTRY (not just the 5 the tests were clearing) and also calls get_auth_status() which detects gh auth token for Copilot. On machines with any of these set, the function returns True before reaching the code path under test.

Clear all registry vars and mock get_auth_status so host credentials don't interfere.

* fix(test): correct path to hermes_base_env.py in tool parser tests

Path(__file__).parent.parent resolved to tests/, not the project root.
The file lives at environments/hermes_base_env.py so we need one more parent level.

* fix(test): accept optional HTML fields in Matrix send payload

_send_matrix sometimes adds format and formatted_body when the markdown library is installed. The test was doing an exact dict equality check which broke. Check required fields instead.

* fix(test): add config.yaml to codex vision requirements test

The test only wrote auth.json but not config.yaml, so _read_main_provider() returned empty and vision auto-detect never tried the codex provider. Add a config.yaml pointing at openai-codex so the fallback path actually resolves the client.

* fix(test): clear OPENROUTER_API_KEY in _isolate_hermes_home

run_agent.py calls load_hermes_dotenv() at import time, which injects API keys from ~/.hermes/.env into os.environ before any test fixture runs. This caused test_agent_loop_tool_calling to make real API calls instead of skipping, which ends up making some tests fail.

* fix(test): add get_rate_limit_state to agent mock in usage report tests

_show_usage now calls agent.get_rate_limit_state() for rate limit
  display. The SimpleNamespace mock was missing this method.

* fix(test): update expected Camofox config version from 12 to 13

* fix(test): mock _get_enabled_platforms in nous managed defaults test

Importing gateway.run leaks DISCORD_BOT_TOKEN into os.environ, which makes _get_enabled_platforms() return ["cli", "discord"] instead of just ["cli"]. tools_command loops per platform, so apply_nous_managed_defaults
  runs twice: the first call sets config values, the second sees them as
  already configured and returns an empty set, causing the assertion to
  fail.
2026-04-09 13:17:06 -07:00