Commit Graph

915 Commits

Author SHA1 Message Date
Teknium 94816e9ed7 refactor(hermes_cli): config.py compact strip/merge/env-load walkers, single-line section banners, guard flattening in show/migrate helpers 2026-09-03 00:07:31 -07:00
Teknium 696d6a165d refactor(hermes_cli): config.py AST-neutral layout pass (hug closers, reflow string literals) 2026-09-03 00:01:24 -07:00
Teknium 2f429b5533 refactor(hermes_cli): config.py _issue helper for validation appends, compact nested-key walkers, cron/coercion guard flattening 2026-09-02 23:59:04 -07:00
Teknium adffcbf701 refactor(hermes_cli): config.py table the config get/set/unset usage text, merge check/migrate listing loops, compact manifest/profile injection 2026-09-02 23:54:14 -07:00
Teknium e7034fea17 refactor(hermes_cli): config.py flatten guard ladders and redundant locals (unset walk, deep-merge, env save, terminal bridge) 2026-09-02 23:48:57 -07:00
Teknium 16d700646d refactor(hermes_cli): config.py inline single-use helpers (_run_once, managed-home, floor gate, cron fleet cover), collapse boolean ladders 2026-09-02 23:41:36 -07:00
Teknium 2f7be88154 refactor(hermes_cli): config.py shared _managed_source, packed re-export list 2026-09-02 22:14:28 -07:00
Teknium e7a01c13d5 refactor(hermes_cli): config.py unify list-entry validation, offer-list prompts, banner/tools-suffix helpers 2026-09-02 22:07:57 -07:00
Teknium 6f7c22b9a7 refactor(hermes_cli): config.py docstring compaction and closer hugging (AST-neutral) 2026-09-02 21:37:58 -07:00
Teknium da9a0551e4 refactor(hermes_cli): config.py hoist managed_scope/atomic_yaml_write imports (8 lazy import sites removed) 2026-09-02 21:06:53 -07:00
Teknium f5699c6f60 refactor(hermes_cli): config.py AST-neutral layout compaction (hug short brackets, pack argument lists) 2026-09-02 20:51:13 -07:00
Teknium 8345581dbc refactor(hermes_cli): config.py env-file, display, cron-drift and config-set sections: shared helpers, dispatch tables, docstring compaction 2026-09-02 20:47:01 -07:00
Teknium cd49358836 refactor(hermes_cli): config.py validation/migration/load/save phase helpers, shared _canonicalize_config and refuse-overwrite builder 2026-09-02 20:35:53 -07:00
Teknium 04dfa0a549 refactor(hermes_cli): config.py header/install-method/home-skeleton compaction, dispatch tables for parse-failure and update-command messages 2026-09-02 20:28:28 -07:00
Teknium 55f47d8e95 refactor(config): extract custom-provider resolution to hermes_cli/config_providers.py 2026-09-02 15:31:26 -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 2475335443 fix(config): keep .env publishes inside the routed profile scope under multiplex (#88441)
`save_env_value` / `remove_env_value` already write the right FILE
(`get_env_path()` honors the profile-home override, so a routed turn lands
in `profiles/<p>/.env`, not the root -- #77490's premise), but the
in-process mirror went to `os.environ` unconditionally. Under a
multiplexed gateway a `/pair` grant mirrored into `DISCORD_ALLOWED_USERS`
from profile B therefore published B's allowlist into the SHARED process
env, and B's own installed scope never saw the new value.

Add `_publish_env_value`: when multiplex is active and a secret scope is
installed, update the installed scope mapping (so same-turn scope reads see
the grant) and leave `os.environ` untouched; every other caller keeps the
legacy `os.environ` publish. Replace the stale TODO in gateway/pairing.py.
2026-09-02 06:19:03 -07:00
Teknium b53c50cf8a fix(config): resolve ${VAR} config refs through the profile secret scope (#84079)
`_env_expand_match` read `os.environ` directly, so under a multiplexed
gateway every secondary profile whose config.yaml carried
`${MATRIX_ACCESS_TOKEN}` (or `${env:...}`) expanded to the DEFAULT
profile's token loaded at startup -- each profile "had" the credential and
one inbound message fanned out across all of them. This is the residual
half of #84079 the secondary credential gate cannot see (the expanded
token is non-empty).

Add `_env_ref_lookup`: outside a secret scope it is the same
`os.environ.get`; inside a scope it goes through `get_secret`, which is
authoritative under multiplexing and an environ overlay otherwise -- the
same policy `gateway.config._getenv` and `get_env_value` already follow.
The cache env-snapshot (#58514) uses the same lookup so a scoped load is
not served another scope's cached expansion.
2026-09-02 06:19:03 -07:00
Patryk Kopycinski 1eb71756af toolchain-self-improve: route API_SERVER_KEY to profile env 2026-09-02 06:19:03 -07:00
Teknium 552159d222 feat(cli,tui): collapse bell_on_clarify/approval into display.bell_on_prompt
One key covers every blocking prompt modal: clarify (single + batch),
dangerous-command approval (incl. computer_use), sudo password, and
secret capture. CLI gets a _ring_bell() helper shared with
bell_on_complete; TUI rings on clarify/approval/sudo/secret .request
events (isTTY-gated). 'hermes config' Bell summary shows both flags.
2026-09-02 05:34:35 -07:00
João Vitor Cunha 384fc4bf83 fix(guardrails): preserve interactive platform defaults 2026-09-02 00:26:57 -07:00
João Vitor Cunha ee2147f9e6 fix: hard stop tool loops on non-interactive platforms 2026-09-02 00:26:57 -07:00
Vibe Coder af709b31ca fix(config): redirect platforms.<name>.<display_setting> to display.platforms.<name>.<setting>
Problem A of #71047: 'hermes config set platforms.telegram.streaming false'
wrote to a key the gateway never reads. The connection config
(gateway/config.py) reads only token/extra/overrides from the top-level
platforms.<name> block, while per-platform display settings (streaming,
show_reasoning, tool_progress, ...) are resolved from
display.platforms.<name>.<setting> (gateway/display_config.py).

Redirect a platforms.<name>.<setting> key to
display.platforms.<name>.<setting> only when <setting> is a known per-platform
display setting (gateway.display_config.OVERRIDEABLE_KEYS), leaving real
connection keys (token, extra, channel_overrides, ...) untouched. The
gateway.display_config import is lazy/try-guarded to avoid a circular import
and to keep the CLI working where gateway is not importable.

Adds tests/hermes_cli/test_config_set_platforms_redirect.py covering the
redirect, connection-key non-redirect, and the no-stray-top-level-platforms
case.
2026-09-02 00:07:15 -07: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
Teknium 043c258ac2 fix(web): stale removed-backend config warns at startup and errors by name
A config still pointing at a web backend that no longer ships in-tree
(web.backend: tavily after the #99199 removal) previously failed silently:
no migration, no startup notice, and only a generic 'no registered web
search provider has that name' at the first tool call (reported by keyed
Tavily users upgrading to v0.21.0, see PR #99731 thread).

- tools/tool_backend_helpers.py: REMOVED_BACKENDS registry +
  removed_backend_note(); selection_error() swaps in the specific
  removal explanation (removed in v0.21.0, keyless alternatives) while
  keeping the uniform remediation contract.
- hermes_cli/config.py: validate_config_structure() checks web.backend /
  search_backend / extract_backend against the registry and emits a
  startup warning (deduped per stale value), surfaced by the existing
  print_config_warnings() path in CLI and gateway.
- tests/tools/test_removed_backend_migration.py: startup warning,
  per-capability keys, dedupe, healthy-config negative, live-backend
  failure text preserved.
2026-09-01 10:43:20 -07:00
Teknium 51609a35f6 fix(auth): purge silent OpenRouter paid-default adoption (#81952 class fix)
Three kills at the shared chokepoints:

1. resolve_provider() now REFUSES env-key/pool auto-adoption of openrouter
   while the active config.yaml is corrupt (AuthError code=corrupt_config).
   A broken config falls back to DEFAULT_CONFIG, so tier-2 found no
   model.provider and tier-3/4 silently adopted the PAID openrouter provider
   against the user's real (unparseable) intent. New probe:
   hermes_cli.config.get_active_config_parse_failure(), recorded in the
   existing _warn_config_parse_failure() funnel keyed by (mtime_ns, size) —
   a fixed file clears the block immediately. Explicit provider requests
   are untouched.

2. auxiliary lane built-in OpenRouter fallback model is now a :free SKU
   (nvidia/nemotron-3-ultra-550b-a55b:free) instead of the paid
   google/gemini-3.6-flash. User-configured auxiliary.openrouter_model is
   honored untouched (paid-lane warning retained).

3. env->pool ingestion of OPENROUTER_API_KEY now logs a WARNING (once per
   process per provider) when a credential is newly ingested — ingestion
   itself stays allowed.

Fixes #81952 (silent-paid-default half; sibling PR covers the
non-interactive fail-closed guard).
2026-09-01 07:00:38 -07:00
embwl0x c335dc734a fix(cli): reject corrupt config in noninteractive runs 2026-09-01 07:00:22 -07:00
Teknium a42aee9585 fix(config): greedy literal-key matching + loud phantom-sibling refusal for dotted key names
Builds on webtecnica's escape-aware _split_key_path (#84152, cherry-picked
with authorship preserved; earliest fix in the family was RelaxJonh's #80253
greedy-match approach — both behaviors now ship together):

- _greedy_literal_match: when navigating an EXISTING mapping, prefer an
  existing literal key equal to the dot-join of the next N path segments
  (longest match wins). Dotted model IDs are the norm, so the common
  unescaped command (config set providers.p.models.grok-4.6.supports_vision
  true) now hits the real key across set/get/unset instead of creating a
  phantom sibling. Plain dotted paths with no dotted-key collision split
  exactly as before.
- _phantom_sibling + ValueError in _set_nested: refuse to CREATE a new
  intermediate mapping that would shadow an existing dotted literal sibling
  (Soju06's fail-loudly suggestion on #84064); set_config_value surfaces it
  as a clean CLI error with the escaped spelling to use.
- utils.py::atomic_roundtrip_yaml_update (the second split site, #91607 —
  /model + TUI persistence) now uses the same escape-aware split + greedy
  literal matching.
- CFG-04 empty-segment guard now splits escape-aware so escaped keys are
  not misclassified.
- Tests for every repro shape in the family: #84064 provider model keys,
  #80006 Matrix room IDs, #91095 dotted models under custom_providers list
  index (incl. escaped creation-when-absent), #91607 model_overrides via
  atomic_roundtrip_yaml_update, #99124 dotted leaf keys; plus
  backward-compat coverage. Also fixed the carrier's one stale assertion
  (structured-value coercion landed on main after #84152 branched) and
  removed its dead _MCP_SECRETS_CONFIG fixture flagged in review.
- Docs: 'Dots inside key names' section in website/docs/reference/cli-commands.md.

Fixes #84064, fixes #80006, fixes #91095, fixes #91607, fixes #99124
2026-08-31 12:18:47 -07:00
webtecnica 8951947531 fix(cli): support literal dots in config set/unset key paths (#84064) 2026-08-31 12:18:47 -07:00
phi hu 081030a7dd fix(config): warn for empty platform toolsets 2026-08-31 10:08:31 -07:00
Frowtek 1152d4d3ce fix(cli): recognize whitespace around '=' in .env save/remove
_env_line_defines_key() decides which .env lines the writers may rewrite or
drop. It matched on the `KEY=` prefix, but load_env() splits on the first
`=` and strips the name:

    key, _, value = line.partition('=')
    env_vars[key.strip()] = _parse_env_value(value)

so `OPENAI_API_KEY = sk-...` is a live assignment — the key resolves, the
provider works, and every UI shows it as set. The writers did not see it.

This is the same resurrection hole #40041 fixed for `export KEY=`, still
open for the whitespace form:

- DELETE /api/env 404s ("not found in .env") while the credential stays
  active — a key the user revoked through the UI is never actually revoked
- PUT /api/env appends a SECOND line instead of replacing; a later delete
  removes the appended line and the original value silently comes back

Rotate-then-delete on a spaced line therefore restores exactly the key the
user rotated away from.

Match load_env()'s parse instead of prefix-matching, so the writers accept
precisely what the reader accepts: skip blank/comment/no-'=' lines, strip an
`export ` prefix, then compare the stripped name. Commented-out lines stay
untouched and `KEY_EXTRA=`/`MY_KEY=` still do not match `KEY`.

Verified against the real dashboard endpoints on a temp HERMES_HOME: the
spaced line is now removed, rotation replaces it in place with no duplicate,
and a parity check asserts the writer matches a line iff load_env() does.
2026-08-31 03:37:43 -07:00
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
Stephen Chin c9b9b5e6c7 fix(gateway): preserve native compaction capability on resume 2026-08-30 05:16:10 -07:00
rahlquist d7be3f649d feat: expose terminal.temp_dir to redirect session temp root off tmpfs
Some Linux distros (notably RAM-based tmpfs /tmp on several Arch-based
setups) cap the temp directory at a small size, so Hermes runs out of
space for session temp files (background logs/pid/exit files, code-
execution sandboxes). Add a terminal.temp_dir config key that points
these at real storage.

- Add terminal.temp_dir default (empty) in config_defaults.py
- Bridge it to TERMINAL_TEMP_DIR via TERMINAL_CONFIG_ENV_MAP
- Honor TERMINAL_TEMP_DIR first in LocalEnvironment.get_temp_dir(),
  falling through to TMPDIR//tmp//gettempdir when unset/invalid
- Add tests covering override, process-env, missing-dir, and empty
2026-08-28 07:50:33 -07:00
EndeavorYen 7e34522454 fix(profiles): tombstone deleted named profiles so logging cannot resurrect them
setup_logging and ensure_hermes_home could mkdir profiles/<name> after
hermes profile delete, so empty shells reappeared in profile list and
Desktop Bot Mode. Write a sibling tombstone, refuse mkdir/bootstrap for
tombstoned homes, and skip them in list/serve.
2026-08-28 05:27:15 -07:00
Brooklyn Nicholson d83dcb4c3a fix(model): coerce YAML integer provider names before picker/CRUD
PyYAML loads unquoted names like provider: 2070 as int. GET /api/model/options
then called .lower() on the providers dict key and 500ed, and activate/delete
looked up "2070" and missed. Stringify identity fields so Desktop can assign
and remove those endpoints.

Co-authored-by: xxxigm <tuancanhnguyen706@gmail.com>
2026-08-27 11:31:21 -05:00
Teknium 7105657f2c fix: scope /approve-/deny direct-send confirmations to native-streaming adapters; drop leftover debug log; refresh WeCom docs
- The direct adapter.send() confirmation path in /approve and /deny is only
  needed on native-streaming platforms (WeCom) where the reply stream is
  already finalized; other platforms keep the return-text contract (fixes
  4 approve/deny regression tests, guarded with 'is not True' against
  MagicMock auto-attributes).
- Remove a stray [DEBUG] logger.info left in _deliver_media_from_response.
- website/docs wecom.md: replace the 'does not stream' notes with the native
  msgtype:stream behavior and document the stream keepalive extra keys.
2026-08-27 07:33:36 -07:00
wansui 81aa4f18a8 feat(gateway): per-platform streaming config default for WeCom
Add WeCom to the per-platform streaming defaults (DEFAULT_CONFIG display
plumbing) so native streaming is enabled by default for the WeCom adapter,
alongside the existing per-platform flags. Non-secret config lives in
config.yaml (no HERMES_* env vars).
2026-08-27 07:33:36 -07:00
kshitijk4poor 93a29d110d fix(hermes_cli): surface fail-closed config write refusals cleanly
Follow-ups to the salvaged #71385 guard (which raises RuntimeError from
require_readable_config_before_write on unparseable / non-mapping YAML):

- config_command: catch RuntimeError for set/unset and print a clean
  one-line error + exit(1) instead of a raw traceback on the primary
  'hermes config set/unset' CLI path.
- console_engine._capture_output: convert escaping RuntimeError into a
  ConsoleCommandError so 'hermes console' and the dashboard console
  report the refusal instead of crashing the REPL/websocket session.
- _warn_config_parse_failure: add a dedicated 'refuse-write' wording
  branch — the old fallthrough claimed 'falling back to default config'
  even though the write was refused and the file preserved.
- approval_mode: update the stale SystemExit-only comment.
- Regression tests for the console path and both config_command paths.
2026-08-27 13:27:24 +05:30
fangliquanflq 77d4d23fbc fix(hermes_cli): stop config set/unset from wiping user overrides on invalid YAML
Fail closed when config.yaml is unparseable or non-mapping before set/unset writes, reuse the readable-config guard to return the parsed mapping, and cover refuse/empty-mapping paths with regression tests.
2026-08-27 13:27:24 +05:30
Gille 6defe7eb6c fix(config): preserve lossy decimal values as strings 2026-08-27 11:49:58 +05:30
unsupportedpastels c4f376c19a fix(config): block generic Copilot ACP controls 2026-08-26 09:54:31 -07:00
unsupportedpastels 5425ba14f2 fix(config): harden MCP env policy on Windows 2026-08-26 09:54:31 -07:00
unsupportedpastels 08cf4fea5d fix(mcp): restrict catalog environment writes 2026-08-26 09:54:31 -07:00
fangliquanflq 7a67bd07a7 feat(docker): support shared container identities 2026-08-25 04:00:27 -07:00
Teknium a75ea37dc5 feat: browser snapshots drop LLM summarization — truncate-and-store like web_extract; auxiliary.web_extract slot removed
web_extract stopped using an auxiliary LLM long ago (deterministic
truncate-and-store), but browser snapshots still routed oversized
accessibility trees through the auxiliary web_extract model, keeping a
dead-looking aux slot alive across every config/picker surface.

- tools/browser_tool.py: remove _extract_relevant_content and
  _get_extraction_model; oversized snapshots always truncate at line
  boundaries, store the full tree to cache/web, and append a read_file
  pointer (element refs beyond the cut live in the file)
- tools/browser_camofox.py: same — no LLM path
- Remove auxiliary.web_extract slot: config_defaults (removal note, same
  pattern as session_search/PR #27590), cli.py defaults + env bridge,
  gateway/run.py bridged keys, hermes config display, hermes model picker,
  dashboard REST slots, desktop + web AUX_TASKS, i18n labels (en/zh/
  zh-hant/ja/ar)
- Docs: env-vars, configuration, fallback-providers, browser + zh-Hans
  mirrors (web-search zh-Hans was stale on the old LLM pipeline — synced
  to truncate-and-store truth)
- Tests updated: aux bridge uses approval slot, browser tests assert the
  LLM path is gone and stored files are secret-redacted
2026-08-24 20:11:18 -07:00
Gille b7cb321223 fix(dashboard): preserve placeholder cwd fallback 2026-08-23 15:21:48 -07:00
Teknium 8b86097a62 fix(gateway): honor env-configured local backends and persist Tool Gateway declines
Follow-ups on top of the salvaged #92665 commits, closing the two gaps
called out in #92647:

- SEARXNG_URL / CAMOFOX_URL now count as direct web/browser configuration
  in _get_gateway_direct_credentials(), so env-only keyless local setups
  are offered unchecked instead of pre-checked.
- Submitting the checklist with unconfigured tools left unchecked records
  them in tool_gateway_declined_tools (new known root key) and stops
  pre-checking them on subsequent Nous model swaps; opting in later clears
  the decline. Cancel (Ctrl-C/ESC) records nothing.
- has_direct labels mention SearXNG/Camofox when that's what's detected.
- Docs: tool-gateway.md gains an enablement-checklist section.
2026-08-22 21:36:55 -07:00
Teknium c32119b12c feat(config): default agent.max_turns to unlimited; accept inf/infinity/null spellings
Builds on @fattchris resolve_turn_limit salvage (#67696): flips the default
from a numeric cap to unlimited across all construction paths (CLI, agent_init,
run_agent subagents), adds inf/infinity/null to the unlimited spellings, and
sets DEFAULT_CONFIG agent.max_turns to null. The turn cap caused more problems
than it solved (silent mid-task truncation).
2026-08-20 04:50:39 -07:00
Chris Fontes 5046282867 feat(config): resolve_turn_limit — first-class 'none'/'unlimited' for agent.max_turns
Previously agent.max_turns only accepted positive integers. Setting it to
'none', 'unlimited', or 0 — all natural ways to say 'no limit' — either
crashed int() or was silently skipped by `or` checks, falling back to 90.

This adds resolve_turn_limit() in hermes_cli/config.py as the single
normalization point. It accepts:

  - int/float → int(raw) (floats truncated)
  - numeric string ('120') → int(raw)
  - 'none'/'unlimited'/'infinite'/'∞'/'-1'/'0' (case-insensitive,
    whitespace-tolerant) → sys.maxsize sentinel
  - YAML None/null → default (90)
  - bool/list/dict/garbage → default (with debug log)

All config-reading sites (cli.py, gateway/run.py, cron/scheduler.py) now
call this instead of bare int(), so agent.max_turns: none in config.yaml
becomes a first-class supported spelling of 'unlimited'.

The sentinel (sys.maxsize) survives the str()→int() round-trip through
the HERMES_MAX_ITERATIONS env-var bridge in gateway/run.py and works in
every <, >=, remaining = max - used comparison without requiring call
sites to learn about a special value.

Includes 38 tests covering the full spelling table, the str→int env-var
round-trip, and sentinel properties.
2026-08-20 04:50:39 -07:00