文件最后提交记录最后更新时间
refactor(tests): re-architect tests + fix CI failures (#5946) * refactor: re-architect tests to mirror the codebase * Update tests.yml * fix: add missing tool_error imports after registry refactor * fix(tests): replace patch.dict with monkeypatch to prevent env var leaks under xdist patch.dict(os.environ) can leak TERMINAL_ENV across xdist workers, causing test_code_execution tests to hit the Modal remote path. * fix(tests): fix update_check and telegram xdist failures - test_update_check: replace patch("hermes_cli.banner.os.getenv") with monkeypatch.setenv("HERMES_HOME") — banner.py no longer imports os directly, it uses get_hermes_home() from hermes_constants. - test_telegram_conflict/approval_buttons: provide real exception classes for telegram.error mock (NetworkError, TimedOut, BadRequest) so the except clause in connect() doesn't fail with "catching classes that do not inherit from BaseException" when xdist pollutes sys.modules. * fix(tests): accept unavailable_models kwarg in _prompt_model_selection mock1 个月前
refactor(session-log): drop branch/compress re-point of session_log_file The attribute no longer exists; nothing to re-point. 14 天前
feat(busy): add 'steer' as a third display.busy_input_mode option (#16279) Enter while the agent is busy can now inject the typed text via /steer — arriving at the agent after the next tool call — instead of interrupting (current default) or queueing for the next turn. Changes: - cli.py: keybinding honors busy_input_mode='steer' by calling agent.steer(text) on the UI thread (thread-safe), with automatic fallback to 'queue' when the agent is missing, steer() is unavailable, images are attached, or steer() rejects the payload. /busy accepts 'steer' as a fourth argument alongside queue/interrupt/status. - gateway/run.py: busy-message handler and the PRIORITY running-agent path both route through running_agent.steer() when the mode is 'steer', with the same fallback-to-queue safety net. Ack wording tells users their message was steered into the current run. Restart-drain queueing now also activates for 'steer' so messages aren't lost across restarts. - agent/onboarding.py: first-touch hint has a steer branch for both CLI and gateway. - hermes_cli/commands.py: /busy args_hint updated to include steer, and 'steer' is registered as a subcommand (completions). - hermes_cli/web_server.py: dashboard select widget offers steer. - hermes_cli/config.py, cli-config.yaml.example, hermes_cli/tips.py: inline docs updated. - website/docs/user-guide/cli.md + messaging/index.md: documented. - Tests: steer set/status path for /busy; onboarding hints; _load_busy_input_mode accepts steer; busy-session ack exercises steer success + two fallback-to-queue branches. Requested on X by @CodingAcct. Default is unchanged (interrupt).1 个月前
feat(openrouter): wire Pareto Code router with min_coding_score knob (#22838) Pick openrouter/pareto-code as your model and OpenRouter auto-routes each request to the cheapest model meeting your coding-quality bar (ranked by Artificial Analysis). The new openrouter.min_coding_score config key (0.0-1.0, default 0.65) tunes the floor. - hermes_cli/models.py: add openrouter/pareto-code to OPENROUTER_MODELS so it shows up in the picker with a description - hermes_cli/config.py: add openrouter.min_coding_score (default 0.65 — lands on a mid-tier coder on the current Pareto frontier) - plugins/model-providers/openrouter: emit extra_body.plugins = [{id: pareto-router, min_coding_score: X}] when model is openrouter/pareto-code AND the score is a valid float in [0.0, 1.0] - agent/transports/chat_completions.py: same emission on the legacy flag path (when no provider profile is loaded) - run_agent.py: openrouter_min_coding_score kwarg + storage; plumbed into both build_kwargs() invocations and the context-summary extra_body path - cli.py: read openrouter.min_coding_score once at init, validate float in [0,1], pass to AIAgent constructions (CLI + background-task paths) - cron/scheduler.py, batch_runner.py, tools/delegate_tool.py, tui_gateway/server.py: propagate the kwarg (mirrors providers_order plumbing — subagents inherit, cron/batch read from config) - tests: profile-level + transport-level coverage of the model gating, unset/empty/out-of-range handling, and the legacy flag path - docs: new 'OpenRouter Pareto Code Router' section in providers.md Verified end-to-end against api.openrouter.ai: at score=0.65 we land on a mid-tier coder, at omission we get the strongest. Score is silently dropped on any model other than openrouter/pareto-code, so it's safe to leave set.25 天前
feat(cli): show ▶ N indicator in status bar when /background tasks are running (#27175) Surface live background-task count in the prompt_toolkit status bar so users can see at a glance that a /background task exists and is running — no need to ask the agent about it (the agent has no visibility into bg sessions by design). - _get_status_bar_snapshot now reports active_background_tasks from len() of the live _background_tasks dict (entries are removed in the task thread's finally block, so this reflects truly-running tasks) - Indicator shown only on medium (<76) and wide (>=76) tiers; narrow (<52) stays minimal since it's already cramped - No invalidate plumbing needed: status bar fragments are pulled via lambda on every redraw, and the bg thread already calls _app.invalidate() on exit Refs #856818 天前
refactor(tests): re-architect tests + fix CI failures (#5946) * refactor: re-architect tests to mirror the codebase * Update tests.yml * fix: add missing tool_error imports after registry refactor * fix(tests): replace patch.dict with monkeypatch to prevent env var leaks under xdist patch.dict(os.environ) can leak TERMINAL_ENV across xdist workers, causing test_code_execution tests to hit the Modal remote path. * fix(tests): fix update_check and telegram xdist failures - test_update_check: replace patch("hermes_cli.banner.os.getenv") with monkeypatch.setenv("HERMES_HOME") — banner.py no longer imports os directly, it uses get_hermes_home() from hermes_constants. - test_telegram_conflict/approval_buttons: provide real exception classes for telegram.error mock (NetworkError, TimedOut, BadRequest) so the except clause in connect() doesn't fail with "catching classes that do not inherit from BaseException" when xdist pollutes sys.modules. * fix(tests): accept unavailable_models kwarg in _prompt_model_selection mock1 个月前
fix(cli): strip leaked bracketed-paste wrappers 1 个月前
test(cli): cover Brave binary CDP launch detection 14 天前
refactor(tests): re-architect tests + fix CI failures (#5946) * refactor: re-architect tests to mirror the codebase * Update tests.yml * fix: add missing tool_error imports after registry refactor * fix(tests): replace patch.dict with monkeypatch to prevent env var leaks under xdist patch.dict(os.environ) can leak TERMINAL_ENV across xdist workers, causing test_code_execution tests to hit the Modal remote path. * fix(tests): fix update_check and telegram xdist failures - test_update_check: replace patch("hermes_cli.banner.os.getenv") with monkeypatch.setenv("HERMES_HOME") — banner.py no longer imports os directly, it uses get_hermes_home() from hermes_constants. - test_telegram_conflict/approval_buttons: provide real exception classes for telegram.error mock (NetworkError, TimedOut, BadRequest) so the except clause in connect() doesn't fail with "catching classes that do not inherit from BaseException" when xdist pollutes sys.modules. * fix(tests): accept unavailable_models kwarg in _prompt_model_selection mock1 个月前
feat: add /copy and /agents 1 个月前
refactor(tests): re-architect tests + fix CI failures (#5946) * refactor: re-architect tests to mirror the codebase * Update tests.yml * fix: add missing tool_error imports after registry refactor * fix(tests): replace patch.dict with monkeypatch to prevent env var leaks under xdist patch.dict(os.environ) can leak TERMINAL_ENV across xdist workers, causing test_code_execution tests to hit the Modal remote path. * fix(tests): fix update_check and telegram xdist failures - test_update_check: replace patch("hermes_cli.banner.os.getenv") with monkeypatch.setenv("HERMES_HOME") — banner.py no longer imports os directly, it uses get_hermes_home() from hermes_constants. - test_telegram_conflict/approval_buttons: provide real exception classes for telegram.error mock (NetworkError, TimedOut, BadRequest) so the except clause in connect() doesn't fail with "catching classes that do not inherit from BaseException" when xdist pollutes sys.modules. * fix(tests): accept unavailable_models kwarg in _prompt_model_selection mock1 个月前
feat(cli): add editor workflow for drafts 1 个月前
fix(cli): catch OSError in _resolve_attachment_path to prevent ENAMETOOLONG dropping long slash commands When the user pastes a long slash command like \/goal <long prose>\ into \hermes chat\, the input flows into \_detect_file_drop()\, whose \starts_like_path\ prefilter accepts anything starting with \/\ and forwards it to \_resolve_attachment_path()\. That helper calls \Path.exists()\ which invokes \os.stat()\, which raises \OSError(errno=ENAMETOOLONG)\ — 63 on macOS, 36 on Linux — when the candidate exceeds NAME_MAX (typically 255 bytes). The OSError propagates up to the broad \except Exception\ in \process_loop\ (cli.py:11798), gets logged at WARNING level, and the user's input is silently dropped. From the user's POV the chat prompt hangs — the only signal is in agent.log: WARNING cli: process_loop unhandled error (msg may be lost): [Errno 63] File name too long: "/goal Drive the space board..." This affects any slash command with prose-length arguments — \/goal\ in particular but also \/skill\, \/cron\, custom user commands. Fix: wrap the \exists()\/\is_file()\ calls in try/except OSError so structurally-invalid path candidates cleanly return None. The slash- command dispatch path downstream (cli.py:11718) then handles the input correctly. Tests: two new regression cases in test_cli_file_drop.py cover the original \/goal\ reproducer and a synthetic long path. All 35 file- drop tests pass. Reproducer (without the fix): python -c "from cli import _detect_file_drop; _detect_file_drop('/goal ' + 'a'*300)" → OSError: [Errno 63] File name too long 28 天前
fix(cli): clamp scrollback box widths + suppress status bar after resize (#25975) When the terminal shrinks, already-printed box-drawing rules (response, reasoning, streaming TTS, background-task Panels) reflow into multiple narrower rows — visible as duplicated horizontal separators / ghost lines in scrollback. Similarly, prompt_toolkit redraws a fresh status bar on SIGWINCH on top of one the terminal just reflowed, producing double-bar artifacts on column shrink. Two surgical changes: 1. Decorative scrollback boxes now use a new HermesCLI._scrollback_box_width() helper that clamps to max(32, min(width, 56)). The live TUI footer is unaffected and still uses the full width. Covers: streaming response box (open + close), reasoning box (open + close, both streaming and post-stream paths), streaming-TTS box close, final-response Rich Panel, and the background-task Rich Panel. 2. _recover_after_resize() now also sets a new _status_bar_suppressed_after_resize flag so the dynamic status bar and both input separator rules stay hidden until the next user input. The flag is cleared in the process loop the moment the user submits their next prompt, restoring chrome cleanly. Tests: - New test_input_rules_hide_after_resize_until_next_input covers the flag's effect on rule heights. - New test_scrollback_box_width_caps_to_resize_safe_value covers the helper at floor / cap / mid-range / overflow. - Existing resize-recovery test extended to assert the flag flips. Refs: #18449 #19280 #22976 Salvage of #24403. Co-authored-by: Szymonclawd <szymonclawd@mac.home>20 天前
revert: roll back /goal checklist + /subgoal feature stack (#23813) * Revert "fix(goals): force judge to use tool calls instead of JSON-text replies (#23547)" This reverts commit a63a2b7c78562cd4eaf33f5f7db81ae0b3938552. * Revert "fix(goals): forward standing /goal state on auto-compression session rotation (#23530)" This reverts commit 4a080b1d5aa7528a679880c93147bc7fffdd267a. * Revert "feat(goals): /goal checklist + /subgoal user controls (#23456)" This reverts commit 404640a2b752f502825dc8b26212204fa890d495.23 天前
fix(termux): harden execute_code and mobile browser/audio UX 1 个月前
chore: ruff auto-fix PLR6201 resweep — tuple → set in membership tests (#27355) Six days after #23937 (608 fixes) the codebase had accumulated 241 new PLR6201 violations. Same mechanical x in (...)x in {...} fix, same zero-risk profile: set lookup is O(1) vs O(n) for tuple and the two are semantically equivalent for hashable scalar membership tests. All 241 instances fixed via `ruff check --select PLR6201 --fix --unsafe-fixes`, zero remaining. Every changed value is a hashable scalar (str/int/None/enum/signal); no risk of unhashable runtime errors. No behavior change. Test plan: - 119 files changed, +244/-244 (net zero) — exactly one-line edits - ruff check clean afterward - Compile checks pass on the largest touched files (cli.py, run_agent.py, gateway/run.py, gateway/platforms/discord.py, model_tools.py) - Subset broad test run on tests/gateway/ tests/hermes_cli/ tests/agent/ tests/tools/: 18187 passed, 59 pre-existing failures (verified against origin/main with the same shape — identical failure count, identical category — all xdist test-order flakes unrelated to this change) Follows the same template as PR #23937 ([tracker: #23972](https://github.com/NousResearch/hermes-agent/issues/23972)).17 天前
fix(cli): parse positional insights days 22 天前
fix: resolve CI test failures — add missing functions, fix stale tests (#9483) Production fixes: - Add clear_session_context() to hermes_logging.py (fixes 48 teardown errors) - Add clear_session() to tools/approval.py (fixes 9 setup errors) - Add SyncError M_UNKNOWN_TOKEN check to Matrix _sync_loop (bug fix) - Fall back to inline api_key in named custom providers when key_env is absent (runtime_provider.py) Test fixes: - test_memory_user_id: use builtin+external provider pair, fix honcho peer_name override test to match production behavior - test_display_config: remove TestHelpers for non-existent functions - test_auxiliary_client: fix OAuth tokens to match _is_oauth_token patterns, replace get_vision_auxiliary_client with resolve_vision_provider_client - test_cli_interrupt_subagent: add missing _execution_thread_id attr - test_compress_focus: add model/provider/api_key/base_url/api_mode to mock compressor - test_auth_provider_gate: add autouse fixture to clean Anthropic env vars that leak from CI secrets - test_opencode_go_in_model_list: accept both 'built-in' and 'hermes' source (models.dev API unavailable in CI) - test_email: verify email Platform enum membership instead of source inspection (build_channel_directory now uses dynamic enum loop) - test_feishu: add bot_added/bot_deleted handler mocks to _Builder - test_ws_auth_retry: add AsyncMock for sync_store.get_next_batch, add _pending_megolm and _joined_rooms to Matrix adapter mocks - test_restart_drain: monkeypatch-delete INVOCATION_ID (systemd sets this in CI, changing the restart call signature) - test_session_hygiene: add user_id to SessionSource - test_session_env: use relative baseline for contextvar clear check (pytest-xdist workers share context)1 个月前
test(cli): cover light-mode detection + SkinConfig.get_color remap Adds 16 unit tests covering the light/dark terminal detection path introduced in the previous commit: - Env override priority (HERMES_LIGHT, HERMES_TUI_LIGHT, HERMES_TUI_THEME, HERMES_TUI_BACKGROUND, COLORFGBG) - Detection cache stickiness - _maybe_remap_for_light_mode() no-op in dark mode - Known dark-mode color remap (#FFF8DC -> #1A1A1A etc) - Case-insensitive lookup - Unknown color passthrough - Status-bar paired colors (#C0C0C0, #888888, #555555, #8B8682) are intentionally NOT remapped — regression guard for the patch-11 fix, since remapping them would produce dark-on-dark on the status bar's navy bg - SkinConfig.get_color() wrapper is installed and idempotent - SkinConfig.get_color() does remap in light mode and passes through in dark mode We don't try to fake an OSC 11 reply — that path is exercised end-to-end in real Terminal.app; the env-override path covers the algorithmic logic. 19 天前
fix: clean up defensive shims and finish CI stabilization from #17660 (#17801) PR #17660 landed a sweep of CI fixes but left three loose ends: 1. tests/cli/test_cli_loading_indicator.py::test_reload_mcp_sets_busy_state_ and_prints_status — /reload-mcp gained a prompt-cache-invalidation confirmation (commit 4d7fc0f37) that was never wired into this test. The test exercises the loading-indicator path, so pre-approve via config and go straight into _reload_mcp(). 2. tools/mcp_tool.py _make_tool_handler — the added getattr(server, '_rpc_lock', None) + 'skip the lock if missing' branch is inconsistent with four sibling call sites that still direct-access server._rpc_lock. The lock is guaranteed by MCPServerTask.__init__; falling through to an unlocked session.call_tool would silently serialize-strip RPCs if the guard ever triggered. Restore direct access. 3. tui_gateway/server.py _messages_as_conversation — the helper existed only to catch 'TypeError: include_ancestors unexpected' from mocked SessionDBs that don't actually exist. The real SessionDB.get_messages_as_conversation has accepted include_ancestors since introduction, and every test FakeDB in the repo already declares the kwarg. Remove the shim, inline the two call sites.1 个月前
fix(cli): preserve cron asterisks in strip mode 16 天前
refactor(tests): re-architect tests + fix CI failures (#5946) * refactor: re-architect tests to mirror the codebase * Update tests.yml * fix: add missing tool_error imports after registry refactor * fix(tests): replace patch.dict with monkeypatch to prevent env var leaks under xdist patch.dict(os.environ) can leak TERMINAL_ENV across xdist workers, causing test_code_execution tests to hit the Modal remote path. * fix(tests): fix update_check and telegram xdist failures - test_update_check: replace patch("hermes_cli.banner.os.getenv") with monkeypatch.setenv("HERMES_HOME") — banner.py no longer imports os directly, it uses get_hermes_home() from hermes_constants. - test_telegram_conflict/approval_buttons: provide real exception classes for telegram.error mock (NetworkError, TimedOut, BadRequest) so the except clause in connect() doesn't fail with "catching classes that do not inherit from BaseException" when xdist pollutes sys.modules. * fix(tests): accept unavailable_models kwarg in _prompt_model_selection mock1 个月前
feat: confirm prompt for destructive slash commands (#4069) (#22687) /clear, /new, /reset, and /undo now ask the user to confirm before discarding conversation state — three-option prompt routed through the existing tools.slash_confirm primitive. Native yes/no buttons render on Telegram, Discord, and Slack (their adapters already implement send_slash_confirm); other platforms get a text-fallback prompt and reply with /approve, /always, or /cancel. The classic prompt_toolkit CLI uses the same three-option flow via the established _prompt_text_input pattern (see _confirm_and_reload_mcp). TUI keeps its existing modal overlay (#12312). Gated by new config key approvals.destructive_slash_confirm (default true). Picking 'Always Approve' flips the gate to false so subsequent destructive commands run silently — matches the established mcp_reload_confirm UX. Out of scope: /cron remove (separate domain — scheduled jobs, not session history). Existing TUI overlay env-var (HERMES_TUI_NO_CONFIRM) left unchanged; cosmetic unification can come later. Closes #4069.25 天前
refactor(tests): re-architect tests + fix CI failures (#5946) * refactor: re-architect tests to mirror the codebase * Update tests.yml * fix: add missing tool_error imports after registry refactor * fix(tests): replace patch.dict with monkeypatch to prevent env var leaks under xdist patch.dict(os.environ) can leak TERMINAL_ENV across xdist workers, causing test_code_execution tests to hit the Modal remote path. * fix(tests): fix update_check and telegram xdist failures - test_update_check: replace patch("hermes_cli.banner.os.getenv") with monkeypatch.setenv("HERMES_HOME") — banner.py no longer imports os directly, it uses get_hermes_home() from hermes_constants. - test_telegram_conflict/approval_buttons: provide real exception classes for telegram.error mock (NetworkError, TimedOut, BadRequest) so the except clause in connect() doesn't fail with "catching classes that do not inherit from BaseException" when xdist pollutes sys.modules. * fix(tests): accept unavailable_models kwarg in _prompt_model_selection mock1 个月前
refactor(tests): re-architect tests + fix CI failures (#5946) * refactor: re-architect tests to mirror the codebase * Update tests.yml * fix: add missing tool_error imports after registry refactor * fix(tests): replace patch.dict with monkeypatch to prevent env var leaks under xdist patch.dict(os.environ) can leak TERMINAL_ENV across xdist workers, causing test_code_execution tests to hit the Modal remote path. * fix(tests): fix update_check and telegram xdist failures - test_update_check: replace patch("hermes_cli.banner.os.getenv") with monkeypatch.setenv("HERMES_HOME") — banner.py no longer imports os directly, it uses get_hermes_home() from hermes_constants. - test_telegram_conflict/approval_buttons: provide real exception classes for telegram.error mock (NetworkError, TimedOut, BadRequest) so the except clause in connect() doesn't fail with "catching classes that do not inherit from BaseException" when xdist pollutes sys.modules. * fix(tests): accept unavailable_models kwarg in _prompt_model_selection mock1 个月前
feat(custom): prompt and persist explicit api_mode for custom providers Adds an explicit API compatibility mode prompt to the hermes model -> custom flow so Codex-compatible third-party endpoints (and any other non-default backend whose URL doesn't match the existing heuristics in _detect_api_mode_for_url) can be selected explicitly instead of silently falling back to chat_completions. Choices: Auto-detect / chat_completions / codex_responses / anthropic_messages. Persists api_mode to: - model.api_mode (active session config) - the matching custom_providers[*] entry (so re-activating the named provider next time replays the same transport) Salvaged from PR #6125 onto current main: kept the new prompt and the _save_custom_provider(api_mode=...) plumbing; the named-custom flow already extracts and applies api_mode from the saved entry on current main so those changes are preserved as-is. Test fixtures updated for the new prompt and the existing display-name prompt. Co-authored-by: littlewwwhite <1095245867@qq.com> 21 天前
refactor(reload-skills): queue note for next turn, drop cache invalidation + agent tool Salvage-follow-up to @shannonsands's /reload-skills PR. Trims the feature to match the design: user-initiated rescan, no prompt-cache reset, no new schema surface, no phantom user turn, and the next-turn note carries each added/removed skill's 60-char description (not just its name). Changes vs the original PR: * Drop the in-process skills prompt-cache clear in reload_skills(). Skills are invoked at runtime via /skill-name, skills_list, or skill_view — they don't need to live in the system prompt for the model to use them. Keeping the cache intact preserves prefix caching across the reload so /reload-skills pays no cache-reset cost. (MCP has to break the cache because tool schemas must be known at conversation start; skills do not.) * Drop the skills_reload agent tool and SKILLS_RELOAD_SCHEMA from tools/skills_tool.py, plus the four skills_reload enumerations in toolsets.py. No new schema surface — agents can already see a freshly- installed skill via skill_view / skills_list the moment it's on disk. * Replace the phantom 'role: user' turn injection with a one-shot queued note. CLI uses self._pending_skills_reload_note (same pattern as _pending_model_switch_note, prepended to the next API call and cleared). Gateway uses self._pending_skills_reload_notes[session_key]. The note is prepended to the NEXT real user message in this session, so message alternation stays intact and nothing out-of-band is persisted to the transcript. * reload_skills() now returns added/removed as [{'name': str, 'description': str}, ...] (description truncated to 60 chars — matches the curator / gateway adapter budget). The injected next-turn note formats each entry as 'name — description' so the model can actually reason about which new skills to call without running skills_list first. * Only emit the note when the diff is non-empty. On empty diff, print 'No new skills detected' and do nothing else. * Tests rewritten to cover the queue semantics, the description payload, and a regression guard that the prompt-cache snapshot is preserved. 1 个月前
refactor(tests): re-architect tests + fix CI failures (#5946) * refactor: re-architect tests to mirror the codebase * Update tests.yml * fix: add missing tool_error imports after registry refactor * fix(tests): replace patch.dict with monkeypatch to prevent env var leaks under xdist patch.dict(os.environ) can leak TERMINAL_ENV across xdist workers, causing test_code_execution tests to hit the Modal remote path. * fix(tests): fix update_check and telegram xdist failures - test_update_check: replace patch("hermes_cli.banner.os.getenv") with monkeypatch.setenv("HERMES_HOME") — banner.py no longer imports os directly, it uses get_hermes_home() from hermes_constants. - test_telegram_conflict/approval_buttons: provide real exception classes for telegram.error mock (NetworkError, TimedOut, BadRequest) so the except clause in connect() doesn't fail with "catching classes that do not inherit from BaseException" when xdist pollutes sys.modules. * fix(tests): accept unavailable_models kwarg in _prompt_model_selection mock1 个月前
fix(cli): preserve config comments on setting writes 25 天前
refactor(tests): re-architect tests + fix CI failures (#5946) * refactor: re-architect tests to mirror the codebase * Update tests.yml * fix: add missing tool_error imports after registry refactor * fix(tests): replace patch.dict with monkeypatch to prevent env var leaks under xdist patch.dict(os.environ) can leak TERMINAL_ENV across xdist workers, causing test_code_execution tests to hit the Modal remote path. * fix(tests): fix update_check and telegram xdist failures - test_update_check: replace patch("hermes_cli.banner.os.getenv") with monkeypatch.setenv("HERMES_HOME") — banner.py no longer imports os directly, it uses get_hermes_home() from hermes_constants. - test_telegram_conflict/approval_buttons: provide real exception classes for telegram.error mock (NetworkError, TimedOut, BadRequest) so the except clause in connect() doesn't fail with "catching classes that do not inherit from BaseException" when xdist pollutes sys.modules. * fix(tests): accept unavailable_models kwarg in _prompt_model_selection mock1 个月前
feat(cli): recognise Shift+Enter as a newline key Closes #5346. Most terminals send the same byte sequence for Enter and Shift+Enter by default, so the application can't tell them apart — this is a terminal protocol limitation, not something Hermes can paper over. But terminals that implement the Kitty keyboard protocol (Kitty / foot / WezTerm / Ghostty by default; iTerm2 / Alacritty / VS Code terminal / Warp once the protocol is enabled) DO emit a distinct sequence for Shift+Enter: - \x1b[13;2u — Kitty / CSI-u, modifier=2 - \x1b[27;2;13~ — xterm modifyOtherKeys=2 Stock prompt_toolkit doesn't have the CSI-u sequence in its ANSI_SEQUENCES table at all, and it maps the modifyOtherKeys variant to plain Keys.ControlM (Enter) — i.e. it strips the Shift modifier, which is the bug users actually hit on iTerm2 and friends. This PR adds hermes_cli/pt_input_extras.install_shift_enter_alias(), called once at CLI startup from cli.py, which inserts/overwrites those sequences in ANSI_SEQUENCES so they decode to (Keys.Escape, Keys.ControlM) — the same key tuple Alt+Enter produces. The existing Alt+Enter newline handler (@kb.add('escape', 'enter') in cli.py) then fires unchanged, so there is no new keybinding to register and no behavioral change for terminals that don't emit the distinct sequences. Files ===== * hermes_cli/pt_input_extras.py — new module hosting the helper. Lives outside cli.py so it's importable in tests without dragging in the full CLI runtime (which depends on fire, rich, etc.). * cli.py — calls install_shift_enter_alias() once at module import. Wrapped in try/except so prompt_toolkit version drift can't break CLI startup. * tests/cli/test_cli_shift_enter_newline.py — 6 tests: - registration of all three byte sequences - overwrite of stock prompt_toolkit's broken modifyOtherKeys mapping - idempotency - parser equivalence: CSI-u Shift+Enter == Alt+Enter - parser equivalence: modifyOtherKeys Shift+Enter == Alt+Enter - plain Enter remains a single key (submit), distinct from the two-key Alt+Enter / Shift+Enter tuple * website/docs/user-guide/cli.md — keybinding table updated; new "Shift+Enter compatibility" subsection with a per-terminal status table noting macOS Terminal / stock Windows Terminal cannot distinguish the keystroke at the protocol level. * website/docs/getting-started/quickstart.md, website/docs/guides/tips.md — short mention pointing readers at the full compatibility note in cli.md. Tested ====== pytest tests/cli/test_cli_shift_enter_newline.py # 6 passed Live-tested by triggering \x1b[13;2u against the running Vt100Parser (see test). Not exercised in a real terminal end-to-end because that requires a Kitty-protocol-capable host; the test exercises the parser path that drives the live terminal too. 26 天前
fix(cli): pass session messages to shutdown_memory_provider (#15165 sibling) The gateway fix in the previous commit forwards _session_messages on gateway session teardown. The CLI exit cleanup path had the same bug: it read getattr(agent, 'conversation_history', None) or [] — but AIAgent has no conversation_history attribute, so providers always received []. Switch to _session_messages (same attribute the gateway now uses), guarded by isinstance(..., list) to preserve the no-arg fallback for MagicMock-based CLI test stubs. Adds tests/cli/test_cli_shutdown_memory_messages.py (4 cases mirroring the gateway suite). 1 个月前
fix(tui): restore macOS copy behavior and theme polish (#17131) This PR groups the TUI fixes that restore macOS Terminal usability and clean up the theme/composer regressions: - copy transcript selections on macOS drag-release so Terminal.app users can copy while mouse tracking is enabled - copy composer selections on macOS drag-release; composer selection is internal to TextInput and does not use the global Ink selection bus - keep IDE Cmd+C forwarding setup macOS-only, and make keybinding conflict checks respect simple when-clause overlap/negation - force truecolor before chalk initializes (unless NO_COLOR / FORCE_COLOR / HERMES_TUI_TRUECOLOR opt-outs apply) so the default banner keeps its gold/amber/bronze gradient in Terminal.app - move TUI surfaces onto semantic theme tokens and preserve skin prompt symbols as bare tokens with renderer-owned spacing - render focused placeholders as dim hint text in TTY mode instead of inverse/selected-looking synthetic cursor text1 个月前
revert(cli): drop scrollback box width clamp (#25975), restore full-width borders (#26163) #25975 (salvaging #24403) clamped decorative scrollback Panels and streaming box rules to max(32, min(width, 56)) as a defense against terminal-emulator reflow when columns shrink. On any modern wide terminal this made the response/reasoning borders look stubby — 56 cols inside a 200-col viewport. #26137 (salvaging #25981, by @OutThisLife) landed a more fundamental fix: prompt_toolkit's _output_screen_diff is monkey-patched so its reserve-vertical-space cursor move no longer pushes chrome into scrollback at all. With that in place, the clamp is no longer load-bearing for the chrome-into-scrollback class of bugs — the remaining risk is purely cosmetic reflow of *already stamped* Panel borders during an aggressive column shrink, which we now accept as a tradeoff for restoring proper full-width rendering. Changes: - _scrollback_box_width() returns max(32, width) (just the floor, no upper cap). All 10 call sites stay valid. - Updated test_scrollback_box_width_caps_to_resize_safe_value to the new test_scrollback_box_width_returns_viewport_width asserting full-width passthrough above the 32-col floor. Floor of 32 is kept so '─' * (w - 2) math stays positive on tiny terminals. Refs #18449 #19280 #22976 (the original reflow class) and #25975 (the clamp this reverts).19 天前
fix(profile): use existing get_active_profile_name() for /profile command Replace inline Path.home() / '.hermes' / 'profiles' detection in both CLI and gateway /profile handlers with the existing get_active_profile_name() from hermes_cli.profiles — which already handles custom-root deployments, standard profiles, and Docker layouts. Fixes /profile incorrectly reporting 'default' when HERMES_HOME points to a custom-root profile path like /opt/data/profiles/coder. Based on PR #10484 by Xowiek. 1 个月前
fix(cli): dispatch /steer inline while agent is running (#13354) Classic-CLI /steer typed during an active agent run was queued through self._pending_input alongside ordinary user input. process_loop, which drains that queue, is blocked inside self.chat() for the entire run, so the queued command was not pulled until AFTER _agent_running had flipped back to False — at which point process_command() took the idle fallback ("No agent running; queued as next turn") and delivered the steer as an ordinary next-turn user message. From Utku's bug report on PR #13205: mid-run /steer arrived minutes later at the end of the turn as a /queue-style message, completely defeating its purpose. Fix: add _should_handle_steer_command_inline() gating — when _agent_running is True and the user typed /steer, dispatch process_command(text) directly from the prompt_toolkit Enter handler on the UI thread instead of queueing. This mirrors the existing _should_handle_model_command_inline() pattern for /model and is safe because agent.steer() is thread-safe (uses _pending_steer_lock, no prompt_toolkit state mutation, instant return). No changes to the idle-path behavior: /steer typed with no active agent still takes the normal queue-and-drain route so the fallback "No agent running; queued as next turn" message is preserved. Validation: - 7 new unit tests in tests/cli/test_cli_steer_busy_path.py covering the detector, dispatch path, and idle-path control behavior. - All 21 existing tests in tests/run_agent/test_steer.py still pass. - Live PTY end-to-end test with real agent + real openrouter model: 22:36:22 API call #1 (model requested execute_code) 22:36:26 ENTER FIRED: agent_running=True, text='/steer ...' 22:36:26 INLINE STEER DISPATCH fired 22:36:43 agent.log: 'Delivered /steer to agent after tool batch' 22:36:44 API call #2 included the steer; response contained marker Same test on the tip of main without this fix shows the steer landing as a new user turn ~20s after the run ended.1 个月前
fix(cli): tighten mouse leak sanitizer Handle unbounded SGR mouse report coordinates and avoid regex work on ordinary prompt-buffer edits by short-circuiting before sanitizer passes. 1 个月前
refactor(tests): re-architect tests + fix CI failures (#5946) * refactor: re-architect tests to mirror the codebase * Update tests.yml * fix: add missing tool_error imports after registry refactor * fix(tests): replace patch.dict with monkeypatch to prevent env var leaks under xdist patch.dict(os.environ) can leak TERMINAL_ENV across xdist workers, causing test_code_execution tests to hit the Modal remote path. * fix(tests): fix update_check and telegram xdist failures - test_update_check: replace patch("hermes_cli.banner.os.getenv") with monkeypatch.setenv("HERMES_HOME") — banner.py no longer imports os directly, it uses get_hermes_home() from hermes_constants. - test_telegram_conflict/approval_buttons: provide real exception classes for telegram.error mock (NetworkError, TimedOut, BadRequest) so the except clause in connect() doesn't fail with "catching classes that do not inherit from BaseException" when xdist pollutes sys.modules. * fix(tests): accept unavailable_models kwarg in _prompt_model_selection mock1 个月前
feat(cli): improve multiline previews 1 个月前
feat: /compress <focus> — guided compression with focus topic (#8017) Adds an optional focus topic to /compress: /compress database schema guides the summariser to preserve information related to the focus topic (60-70% of summary budget) while compressing everything else more aggressively. Inspired by Claude Code's /compact <focus>. Changes: - context_compressor.py: focus_topic parameter on _generate_summary() and compress(); appends FOCUS TOPIC guidance block to the LLM prompt - run_agent.py: focus_topic parameter on _compress_context(), passed through to the compressor - cli.py: _manual_compress() extracts focus topic from command string, preserves existing manual_compression_feedback integration (no regression) - gateway/run.py: _handle_compress_command() extracts focus from event args and passes through — full gateway parity - commands.py: args_hint="[focus topic]" on /compress CommandDef Salvaged from PR #7459 (CLI /compress focus only — /context command deferred). 15 new tests across CLI, compressor, and gateway.1 个月前
fix: preserve ansi output history on resize replay 20 天前
fix(cli): make Ctrl+Enter insert newline on WSL/SSH/Windows Terminal (#22777) Native Windows, WSL, SSH sessions, and Windows Terminal all send Ctrl+Enter as bare LF (c-j). Hermes was binding c-j as submit on every POSIX platform, so Ctrl+Enter submitted instead of inserting a newline on those terminals. Reported in #22379. Add _preserve_ctrl_enter_newline() predicate that detects the environments where Ctrl+Enter must produce a newline (sys.platform == 'win32', SSH_CONNECTION/SSH_CLIENT/SSH_TTY env, WT_SESSION, WSL_DISTRO_NAME, /proc/version 'microsoft' marker). Gate the c-j-as-submit binding off in those environments and gate the c-j-as-newline handler on. Local POSIX TTYs without those markers (docker exec, plain ssh from a Mac) keep c-j as submit so plain Enter still works on thin PTYs. Add install_ctrl_enter_alias() in hermes_cli/pt_input_extras.py mapping the three CSI-u / modifyOtherKeys variants of Ctrl+Enter ('\x1b[13;5u', '\x1b[27;5;13~', '\x1b[27;5;13u') to the (Escape, ControlM) tuple Alt+Enter produces. This lets Kitty / mintty / xterm-with-modifyOtherKeys users over SSH get a Ctrl+Enter newline through the existing Alt+Enter handler. 9 new tests + extended existing test_lf_enter_binds_to_submit_handler_posix to cover bare-local vs SSH branches. Closes #22379.25 天前
fix(cli): local backend CLI always uses launch directory, stops .env sync of TERMINAL_CWD (#19334) The old CWD heuristic was fooled by: 1. TERMINAL_CWD persisted to .env by hermes config set terminal.cwd 2. Inherited TERMINAL_CWD from parent hermes processes 3. Only resolved when config had a placeholder value (not explicit paths) Fix: - load_cli_config() unconditionally uses os.getcwd() for local backend - TERMINAL_CWD always force-exported in CLI mode (overrides stale values) - Gateway sets _HERMES_GATEWAY=1 marker so lazy cli.py imports don't clobber - Remove terminal.cwd from config-set .env sync map (prevents re-poisoning) - Clarify setup wizard label as 'Gateway working directory' Closes #1921430 天前
fix: use TUI modal for slash confirmations 23 天前
feat(cli): add /exit --delete flag to remove session on quit (#27101) Port from google-gemini/gemini-cli#19332. Users can now exit with '/exit --delete' (or '/quit --delete', '/exit -d') to permanently remove the current session's SQLite history plus on-disk transcripts (*.json / *.jsonl / request_dump_*) in one shot. Useful for privacy-sensitive workflows and one-off interactions where leaving a session recording behind is undesirable. Implementation: - New HermesCLI._delete_session_on_exit one-shot flag (defaults False). - process_command() parses --delete / -d after /exit or /quit and arms the flag. Unknown args print a hint and keep the CLI running (prevents typos like '/exit -delete' from accidentally exiting). - Shutdown path calls SessionDB.delete_session(session_id, sessions_dir=...) right after end_session() when the flag is set. That API already existed for 'hermes sessions delete' and handles both SQLite removal (orphaning child sessions so FK constraints hold) and on-disk file cleanup. - /quit CommandDef now advertises '[--delete]' in args_hint so /help and CLI autocomplete surface it. Tests: tests/cli/test_exit_delete_session.py (12 cases covering both aliases, case insensitivity, whitespace, short form, unknown-arg rejection, and registry metadata). E2E-verified with isolated HERMES_HOME: session row deleted, all three transcript/request-dump files removed, second delete_session call correctly returns False.18 天前
fix(anthropic): restrict fast mode to Opus 4.6 (Anthropic API contract) Per https://platform.claude.com/docs/en/build-with-claude/fast-mode: "Fast mode is currently supported on Opus 4.6 only. Sending speed: fast with an unsupported model returns an error." Pre-fix, _is_anthropic_fast_model() returned True for any claude-* model, so /fast on Opus 4.7 (or Sonnet/Haiku) would persist agent.service_tier=fast in config.yaml and the adapter would inject extra_body["speed"] = "fast" on every subsequent request. Opus 4.7 returns: HTTP 400: 'claude-opus-4-7' does not support the speed parameter. This wedged sessions across model upgrades (a user who ran /fast on Opus 4.6 and later switched the default model to 4.7 hit a hard 400 on every turn until they manually edited config.yaml). Changes: - _is_anthropic_fast_model: gate on "opus-4-6" / "opus-4.6" only - anthropic_adapter: add _supports_fast_mode predicate as defensive guard so stale request_overrides on an unsupported model are dropped silently instead of 400'ing - Tests: flip the assertions that mirrored the bug (Sonnet/Haiku/Opus 4.7 asserting fast-mode support) to match the documented API contract 30 天前
fix(cli): sanitize interactive command output 1 个月前
fix(cli): persist manual compress handoff 29 天前
fix(gateway): use profile-aware Hermes paths in runtime hints 1 个月前
fix: use TUI modal for slash confirmations 23 天前
test: stabilize quick-command redaction test against xdist ordering agent.redact._REDACT_ENABLED is snapshotted at import time from HERMES_REDACT_SECRETS env. Under xdist a prior test in the same worker can flip it, so test_exec_command_output_is_redacted was order-dependent. Pin it via monkeypatch like test_terminal_output_transform_still_runs_strip_and_redact does. 23 天前
chore: ruff auto-fix PLR6201 resweep — tuple → set in membership tests (#27355) Six days after #23937 (608 fixes) the codebase had accumulated 241 new PLR6201 violations. Same mechanical x in (...)x in {...} fix, same zero-risk profile: set lookup is O(1) vs O(n) for tuple and the two are semantically equivalent for hashable scalar membership tests. All 241 instances fixed via `ruff check --select PLR6201 --fix --unsafe-fixes`, zero remaining. Every changed value is a hashable scalar (str/int/None/enum/signal); no risk of unhashable runtime errors. No behavior change. Test plan: - 119 files changed, +244/-244 (net zero) — exactly one-line edits - ruff check clean afterward - Compile checks pass on the largest touched files (cli.py, run_agent.py, gateway/run.py, gateway/platforms/discord.py, model_tools.py) - Subset broad test run on tests/gateway/ tests/hermes_cli/ tests/agent/ tests/tools/: 18187 passed, 59 pre-existing failures (verified against origin/main with the same shape — identical failure count, identical category — all xdist test-order flakes unrelated to this change) Follows the same template as PR #23937 ([tracker: #23972](https://github.com/NousResearch/hermes-agent/issues/23972)).17 天前
fix(cli): recover classic CLI output after resize 28 天前
fix(sessions): /save lands under $HERMES_HOME, widen browse+TUI picker, force-refresh ollama-cloud on setup (#16296) Four independent session-UX bugs reported by an external user (#16294). /save wrote hermes_conversation_<ts>.json to CWD — invisible to 'hermes sessions browse' and easy to lose. Snapshots now write under ~/.hermes/sessions/saved/ and the command prints the absolute path plus a 'hermes --resume <id>' hint for the live DB-indexed session. 'hermes sessions browse' default --limit raised from 50 to 500. With the old ceiling, users with moderately long histories saw only the most recent 50 rows and assumed older sessions had been lost. TUI session.list (/resume picker) switched from a hardcoded allow-list of 13 gateway source names to a deny-list of just { 'tool' }. Sessions tagged acp / webhook / user-defined HERMES_SESSION_SOURCE values and any newly-added platform now surface. Default limit 20 → 200. ollama-cloud provider setup passes force_refresh=True to fetch_ollama_cloud_models() so a user entering their API key sees the fresh catalog (e.g. deepseek v4 flash, kimi k2.6) immediately instead of waiting up to an hour for the disk cache TTL to expire. Closes #16294.1 个月前
fix: add gateway coverage for session boundary hooks, move test to tests/cli/ - Fire on_session_finalize and on_session_reset in gateway _handle_reset_command() - Fire on_session_finalize during gateway stop() for each active agent - Move CLI test from tests/ root to tests/cli/ (matches recent restructure) - Add 5 gateway tests covering reset hooks, ordering, shutdown, and error handling - Place on_session_reset after new session is guaranteed to exist (covers the get_or_create_session fallback path) 1 个月前
fix(streaming): prevent <think> in prose from suppressing response output When the model mentions <think> as literal text in its response (e.g. "(/think not producing <think> tags)"), the streaming display treated it as a reasoning block opener and suppressed everything after it. The response box would close with truncated content and no error — the API response was complete but the display ate it. Root cause: _stream_delta() matched <think> anywhere in the text stream regardless of position. Real reasoning blocks always start at the beginning of a line; mentions in prose appear mid-sentence. Fix: track line position across streaming deltas with a _stream_last_was_newline flag. Only enter reasoning suppression when the tag appears at a block boundary (start of stream, after a newline, or after only whitespace on the current line). Add a _flush_stream() safety net that recovers buffered content if no closing tag is found by end-of-stream. Also fixes three related issues discovered during investigation: - anthropic_adapter: _get_anthropic_max_output() now normalizes dots to hyphens so 'claude-opus-4.6' matches the 'claude-opus-4-6' table key (was returning 32K instead of 128K) - run_agent: send explicit max_tokens for Claude models on Nous Portal, same as OpenRouter — both proxy to Anthropic's API which requires it. Without it the backend defaults to a low limit that truncates responses. - run_agent: reset truncated_tool_call_retries after successful tool execution so a single truncation doesn't poison the entire conversation. 1 个月前
fix(surrogates): sanitize reasoning/reasoning_content/reasoning_details fields (#11628) Byte-level reasoning models (xiaomi/mimo-v2-pro, kimi, glm) can emit lone surrogates in reasoning output. The proactive sanitizer walked content/ name/tool_calls but not extra fields like reasoning or the nested reasoning_details array. Surrogates in those fields survived the proactive pass, crashed json.dumps() in the OpenAI SDK, and the recovery block's _sanitize_messages_surrogates(messages) call also didn't check those fields — so 'found' was False, no retry happened, and after 3 attempts the user saw: API call failed after 3 retries. 'utf-8' codec can't encode characters in position N-M: surrogates not allowed Changes: - _sanitize_messages_surrogates: walk any extra string fields (reasoning, reasoning_content, etc.) and recurse into nested dict/list values (reasoning_details). Mirrors _sanitize_messages_non_ascii coverage added in PR #10537. - _sanitize_structure_surrogates: new recursive walker, mirror of _sanitize_structure_non_ascii but for surrogate recovery. - UnicodeEncodeError recovery block: also sanitize api_messages, api_kwargs, and prefill_messages (not just the canonical messages list — the API-copy carries reasoning_content transformed from reasoning and that's what the SDK actually serializes). Always retry on detected surrogate errors, not only when we found something to strip — gate on error type per PR #10537's pattern. Tests: extended tests/cli/test_surrogate_sanitization.py with coverage for reasoning, reasoning_content, reasoning_details (flat and deeply nested), structure walker, and an integration case that reproduces the exact api_messages shape that was crashing.1 个月前
fix(cli): restore stacked tool progress scrollback in TUI (#8201) The TUI transition (4970705, f83e86d) replaced stacked per-tool history lines with a single live-updating spinner widget. While the spinner provides a nice live timer, it removed the scrollback history that users relied on to see what the agent did during a session. This restores stacked tool progress lines in 'all' and 'new' modes by printing persistent scrollback lines via _cprint() when tools complete, in addition to the existing live spinner display. Behavior per mode: - off: no scrollback lines, no spinner (unchanged) - new: scrollback line on completion, skipping consecutive same-tool repeats - all: scrollback line on every tool completion - verbose: no scrollback (run_agent.py handles verbose output directly) Implementation: - Store function_args from tool.started events in _pending_tool_info - On tool.completed, pop stored args and format via get_cute_tool_message() - FIFO queue per function_name handles concurrent tool execution - 'new' mode tracks _last_scrollback_tool for dedup - State cleared at end of agent run Reported by community user Mr.D — the stacked history provides transparency into what the agent is doing, which builds trust. Addresses user report from Discord about lost tool call visibility.1 个月前
feat(cli): add /update slash command to CLI and TUI (#23854) * feat: add /update slash command to CLI and TUI * test(cli): add Python tests for /update slash command Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cli): address Copilot review for /update slash command Route classic CLI /update through prompt_toolkit modal confirmation and defer relaunch to the main-thread cleanup path after app.exit(). Tighten Y/n semantics, add Python wrapper and catalog coverage tests, and assert /update stays visible in the TUI command catalog. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cli): address review feedback on /update command - Replace raw input() with _prompt_text_input_modal in _handle_update_command to avoid EOF/hang/keystroke-leak races with prompt_toolkit's stdin ownership - Fix confirmation logic: only proceed on recognized affirmative aliases (y/yes/1/ok); cancel on everything else including empty string, typos, and unrecognized input — matches all other [Y/n] prompts in the codebase - Route relaunch through main-thread shutdown path: set _pending_relaunch and return False from process_command so process_loop triggers app.exit(); run() then calls relaunch() after prompt_toolkit has restored terminal modes and after cleanup — safe on both POSIX (execvp) and Windows (subprocess+exit) - Fix misleading docstring in test_update_command.py: the Vitest only covers the TypeScript slash handler that emits code 42, not the Python wrapper branch that acts on it - Rewrite tests to use SimpleNamespace pattern (like test_destructive_slash_confirm) so _prompt_text_input_modal can be stubbed directly - Add Python test for _launch_tui exit-code-42 → relaunch branch in main.py Agent-Logs-Url: https://github.com/NousResearch/hermes-agent/sessions/f6da68cf-e7b1-4b7a-aed6-3d4b0f523bdb Co-authored-by: austinpickett <260188+austinpickett@users.noreply.github.com> * fix(cli): polish test fixtures for /update command - Remove unused _prompt_text_input from SimpleNamespace stub - Use pytest.fail sentinel in managed-install guard test to catch unexpected modal invocations Agent-Logs-Url: https://github.com/NousResearch/hermes-agent/sessions/f6da68cf-e7b1-4b7a-aed6-3d4b0f523bdb Co-authored-by: austinpickett <260188+austinpickett@users.noreply.github.com> * chore: re-trigger CI after Copilot review fixes Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: austinpickett <260188+austinpickett@users.noreply.github.com>16 天前
🐛 fix(cli): handle missing remote tracking refs 15 天前
refactor(tests): re-architect tests + fix CI failures (#5946) * refactor: re-architect tests to mirror the codebase * Update tests.yml * fix: add missing tool_error imports after registry refactor * fix(tests): replace patch.dict with monkeypatch to prevent env var leaks under xdist patch.dict(os.environ) can leak TERMINAL_ENV across xdist workers, causing test_code_execution tests to hit the Modal remote path. * fix(tests): fix update_check and telegram xdist failures - test_update_check: replace patch("hermes_cli.banner.os.getenv") with monkeypatch.setenv("HERMES_HOME") — banner.py no longer imports os directly, it uses get_hermes_home() from hermes_constants. - test_telegram_conflict/approval_buttons: provide real exception classes for telegram.error mock (NetworkError, TimedOut, BadRequest) so the except clause in connect() doesn't fail with "catching classes that do not inherit from BaseException" when xdist pollutes sys.modules. * fix(tests): accept unavailable_models kwarg in _prompt_model_selection mock1 个月前