mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
Remove unused imports (F401) and duplicate/shadowed import redefinitions (F811) across the codebase using ruff's safe autofixes. No behavioral changes -- imports only. - ~1400 safe autofixes applied across 644 files (net -1072 lines) - __init__.py re-exports preserved (excluded from F401 removal so public re-export surfaces stay intact) - Re-exports that are imported or monkeypatched by tests but look unused in their defining module are kept with explicit # noqa: F401 (gateway/run.py load_dotenv; run_agent re-exports from agent.message_sanitization, agent.context_compressor, agent.retry_utils, agent.prompt_builder, agent.process_bootstrap, agent.codex_responses_adapter) - Unsafe F841 (unused-variable) fixes deliberately skipped -- those can change behavior when the RHS has side effects - ruff lints remain disabled in pyproject.toml (only PLW1514 is selected); this is a one-time cleanup, not a config change Verification: - python -m compileall: clean - pytest --collect-only: all 27161 tests collect (zero import errors) - core entry points import clean (run_agent, model_tools, cli, toolsets, hermes_state, batch_runner, gateway) - static scan: every name any test imports directly from an edited module still resolves
113 lines
4.2 KiB
Python
113 lines
4.2 KiB
Python
"""Tests for the KeyboardInterrupt guard around slash command dispatch.
|
||
|
||
A Ctrl+C during a slow slash command (e.g. /skills browse on a large
|
||
skill tree, or /sessions list against a multi-GB SQLite DB) used to
|
||
unwind to the outer prompt_toolkit loop and kill the entire session.
|
||
The fix wraps `self.process_command(user_input)` in a try/except
|
||
KeyboardInterrupt so the command aborts but the session survives.
|
||
|
||
These tests verify the contract without spinning up the full
|
||
prompt_toolkit input loop. We exercise the same try/except by calling
|
||
through a thin wrapper that mirrors the real dispatch shape.
|
||
"""
|
||
|
||
from unittest.mock import patch
|
||
|
||
from cli import HermesCLI
|
||
|
||
|
||
def _make_cli():
|
||
cli = HermesCLI.__new__(HermesCLI)
|
||
cli._should_exit = False
|
||
cli.conversation_history = []
|
||
cli.agent = None
|
||
cli._session_db = None
|
||
return cli
|
||
|
||
|
||
def _dispatch(cli, user_input: str, process_command_side_effect=None):
|
||
"""Mirror the production dispatch shape from cli.py around line 14236.
|
||
|
||
Real call site:
|
||
if not _file_drop and isinstance(user_input, str) and _looks_like_slash_command(user_input):
|
||
_cprint(f"\\n⚙️ {user_input}")
|
||
try:
|
||
if not self.process_command(user_input):
|
||
self._should_exit = True
|
||
if app.is_running:
|
||
app.exit()
|
||
except KeyboardInterrupt:
|
||
_cprint("\\n[dim]Command interrupted.[/dim]")
|
||
continue
|
||
"""
|
||
if process_command_side_effect is not None:
|
||
with patch.object(cli, "process_command", side_effect=process_command_side_effect) as mock_pc:
|
||
try:
|
||
if not cli.process_command(user_input):
|
||
cli._should_exit = True
|
||
except KeyboardInterrupt:
|
||
# Mirror production: swallow, do NOT raise.
|
||
pass
|
||
return mock_pc
|
||
|
||
|
||
class TestSlashCommandKeyboardInterrupt:
|
||
def test_keyboardinterrupt_in_slash_command_does_not_set_exit(self):
|
||
"""Ctrl+C in the middle of /skills browse must NOT set _should_exit.
|
||
|
||
Before the fix: KeyboardInterrupt unwinds past the dispatch,
|
||
the outer event loop catches it, session dies.
|
||
After the fix: KeyboardInterrupt is caught locally, _should_exit
|
||
stays False, the prompt loop continues.
|
||
"""
|
||
cli = _make_cli()
|
||
|
||
def raises_keyboard_interrupt(_cmd):
|
||
raise KeyboardInterrupt("user pressed Ctrl+C during slow command")
|
||
|
||
_dispatch(cli, "/skills browse", process_command_side_effect=raises_keyboard_interrupt)
|
||
|
||
assert cli._should_exit is False, (
|
||
"KeyboardInterrupt during slash command must not flag exit"
|
||
)
|
||
|
||
def test_normal_slash_command_returns_truthy_keeps_session_alive(self):
|
||
"""A successful slash command (returns truthy) must NOT set _should_exit."""
|
||
cli = _make_cli()
|
||
|
||
_dispatch(cli, "/help", process_command_side_effect=[True])
|
||
|
||
assert cli._should_exit is False
|
||
|
||
def test_slash_command_returning_false_sets_exit(self):
|
||
"""The legitimate exit signal — process_command() returning False —
|
||
still sets _should_exit. This is the path /exit / /quit use."""
|
||
cli = _make_cli()
|
||
|
||
_dispatch(cli, "/exit", process_command_side_effect=[False])
|
||
|
||
assert cli._should_exit is True
|
||
|
||
def test_other_exceptions_propagate(self):
|
||
"""Only KeyboardInterrupt is caught locally. Other exceptions must
|
||
propagate so they show up in logs and the global handler can deal
|
||
with them — silently swallowing all exceptions would mask bugs."""
|
||
cli = _make_cli()
|
||
|
||
class CustomError(Exception):
|
||
pass
|
||
|
||
def raises_custom(_cmd):
|
||
raise CustomError("real bug")
|
||
|
||
try:
|
||
with patch.object(cli, "process_command", side_effect=raises_custom):
|
||
try:
|
||
if not cli.process_command("/something"):
|
||
cli._should_exit = True
|
||
except KeyboardInterrupt:
|
||
pass # would NOT catch CustomError
|
||
except CustomError:
|
||
return # expected — non-KBI exceptions propagate
|
||
|
||
raise AssertionError("CustomError should have propagated")
|