mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-08 03:01:47 +00:00
refactor(restructure): rewrite all imports for hermes_agent package
Rewrite all import statements, patch() targets, sys.modules keys, importlib.import_module() strings, and subprocess -m references to use hermes_agent.* paths. Strip sys.path.insert hacks from production code (rely on editable install). Update COMPONENT_PREFIXES for logger filtering. Fix 3 hardcoded getLogger() calls to use __name__. Update transport and tool registry discovery paths. Update plugin module path strings. Add legacy process-name patterns for gateway PID detection. Add main() to skills_sync for console_script entry point. Fix _get_bundled_dir() path traversal after move. Part of #14182, #14183
This commit is contained in:
parent
65ca3ba93b
commit
4b16341975
898 changed files with 12494 additions and 12019 deletions
|
|
@ -23,7 +23,7 @@ def session_db(tmp_path):
|
|||
"""Create a real SessionDB for testing."""
|
||||
os.environ["HERMES_HOME"] = str(tmp_path / ".hermes")
|
||||
os.makedirs(tmp_path / ".hermes", exist_ok=True)
|
||||
from hermes_state import SessionDB
|
||||
from hermes_agent.state import SessionDB
|
||||
db = SessionDB(db_path=tmp_path / ".hermes" / "test_sessions.db")
|
||||
yield db
|
||||
db.close()
|
||||
|
|
@ -68,7 +68,7 @@ class TestBranchCommandCLI:
|
|||
|
||||
def test_branch_creates_new_session(self, cli_instance, session_db):
|
||||
"""Branching should create a new session in the DB."""
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
# Call the real method on the mock, using the real implementation
|
||||
HermesCLI._handle_branch_command(cli_instance, "/branch")
|
||||
|
|
@ -80,7 +80,7 @@ class TestBranchCommandCLI:
|
|||
|
||||
def test_branch_copies_history(self, cli_instance, session_db):
|
||||
"""Branching should copy all messages to the new session."""
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
HermesCLI._handle_branch_command(cli_instance, "/branch")
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ class TestBranchCommandCLI:
|
|||
|
||||
def test_branch_preserves_parent_link(self, cli_instance, session_db):
|
||||
"""The new session should reference the original as parent."""
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
original_id = cli_instance.session_id
|
||||
|
||||
HermesCLI._handle_branch_command(cli_instance, "/branch")
|
||||
|
|
@ -99,7 +99,7 @@ class TestBranchCommandCLI:
|
|||
|
||||
def test_branch_ends_original_session(self, cli_instance, session_db):
|
||||
"""The original session should be marked as ended with 'branched' reason."""
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
original_id = cli_instance.session_id
|
||||
|
||||
HermesCLI._handle_branch_command(cli_instance, "/branch")
|
||||
|
|
@ -109,7 +109,7 @@ class TestBranchCommandCLI:
|
|||
|
||||
def test_branch_with_custom_name(self, cli_instance, session_db):
|
||||
"""Custom branch name should be used as the title."""
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
HermesCLI._handle_branch_command(cli_instance, "/branch refactor approach")
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ class TestBranchCommandCLI:
|
|||
|
||||
def test_branch_auto_title_lineage(self, cli_instance, session_db):
|
||||
"""Without a name, branch should auto-generate a title from the parent's title."""
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
HermesCLI._handle_branch_command(cli_instance, "/branch")
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ class TestBranchCommandCLI:
|
|||
|
||||
def test_branch_empty_conversation(self, cli_instance, session_db):
|
||||
"""Branching with no history should show an error."""
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
cli_instance.conversation_history = []
|
||||
|
||||
HermesCLI._handle_branch_command(cli_instance, "/branch")
|
||||
|
|
@ -137,7 +137,7 @@ class TestBranchCommandCLI:
|
|||
|
||||
def test_branch_no_session_db(self, cli_instance):
|
||||
"""Branching without a session DB should show an error."""
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
cli_instance._session_db = None
|
||||
|
||||
HermesCLI._handle_branch_command(cli_instance, "/branch")
|
||||
|
|
@ -147,7 +147,7 @@ class TestBranchCommandCLI:
|
|||
|
||||
def test_branch_syncs_agent(self, cli_instance, session_db):
|
||||
"""If an agent is active, branch should sync it to the new session."""
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
agent = MagicMock()
|
||||
agent._last_flushed_db_idx = 0
|
||||
|
|
@ -162,7 +162,7 @@ class TestBranchCommandCLI:
|
|||
|
||||
def test_branch_sets_resumed_flag(self, cli_instance, session_db):
|
||||
"""Branch should set _resumed=True to prevent auto-title generation."""
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
HermesCLI._handle_branch_command(cli_instance, "/branch")
|
||||
|
||||
|
|
@ -170,7 +170,7 @@ class TestBranchCommandCLI:
|
|||
|
||||
def test_fork_alias(self):
|
||||
"""The /fork alias should resolve to 'branch'."""
|
||||
from hermes_cli.commands import resolve_command
|
||||
from hermes_agent.cli.commands import resolve_command
|
||||
result = resolve_command("fork")
|
||||
assert result is not None
|
||||
assert result.name == "branch"
|
||||
|
|
@ -181,18 +181,18 @@ class TestBranchCommandDef:
|
|||
|
||||
def test_branch_in_registry(self):
|
||||
"""The branch command should be in the command registry."""
|
||||
from hermes_cli.commands import COMMAND_REGISTRY
|
||||
from hermes_agent.cli.commands import COMMAND_REGISTRY
|
||||
names = [c.name for c in COMMAND_REGISTRY]
|
||||
assert "branch" in names
|
||||
|
||||
def test_branch_has_fork_alias(self):
|
||||
"""The branch command should have 'fork' as an alias."""
|
||||
from hermes_cli.commands import COMMAND_REGISTRY
|
||||
from hermes_agent.cli.commands import COMMAND_REGISTRY
|
||||
branch = next(c for c in COMMAND_REGISTRY if c.name == "branch")
|
||||
assert "fork" in branch.aliases
|
||||
|
||||
def test_branch_in_session_category(self):
|
||||
"""The branch command should be in the Session category."""
|
||||
from hermes_cli.commands import COMMAND_REGISTRY
|
||||
from hermes_agent.cli.commands import COMMAND_REGISTRY
|
||||
branch = next(c for c in COMMAND_REGISTRY if c.name == "branch")
|
||||
assert branch.category == "Session"
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import time
|
|||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import cli as cli_module
|
||||
from cli import HermesCLI
|
||||
import hermes_agent.cli.repl as cli_module
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
|
||||
class _FakeBuffer:
|
||||
|
|
@ -169,7 +169,7 @@ class TestCliApprovalUi:
|
|||
# Simulate a compact terminal where the old unbounded panel would overflow.
|
||||
import shutil as _shutil
|
||||
|
||||
with patch("cli.shutil.get_terminal_size",
|
||||
with patch("hermes_agent.cli.repl.shutil.get_terminal_size",
|
||||
return_value=_shutil.os.terminal_size((100, 20))):
|
||||
fragments = cli._get_approval_display_fragments()
|
||||
|
||||
|
|
@ -208,7 +208,7 @@ class TestCliApprovalUi:
|
|||
|
||||
import shutil as _shutil
|
||||
|
||||
with patch("cli.shutil.get_terminal_size",
|
||||
with patch("hermes_agent.cli.repl.shutil.get_terminal_size",
|
||||
return_value=_shutil.os.terminal_size((100, 12))):
|
||||
fragments = cli._get_approval_display_fragments()
|
||||
|
||||
|
|
@ -241,7 +241,7 @@ class TestCliApprovalUi:
|
|||
|
||||
import shutil as _shutil
|
||||
|
||||
with patch("cli.shutil.get_terminal_size",
|
||||
with patch("hermes_agent.cli.repl.shutil.get_terminal_size",
|
||||
return_value=_shutil.os.terminal_size((100, 24))):
|
||||
fragments = cli._get_approval_display_fragments()
|
||||
|
||||
|
|
@ -273,7 +273,7 @@ class TestApprovalCallbackThreadLocalWiring:
|
|||
If this ever starts passing as "visible", the thread-local isolation
|
||||
is gone and the ACP race GHSA-qg5c-hvr5-hjgr may be back.
|
||||
"""
|
||||
from tools.terminal_tool import (
|
||||
from hermes_agent.tools.terminal import (
|
||||
set_approval_callback,
|
||||
_get_approval_callback,
|
||||
)
|
||||
|
|
@ -301,7 +301,7 @@ class TestApprovalCallbackThreadLocalWiring:
|
|||
This is exactly what cli.py's run_agent() closure does. If this test
|
||||
fails, the CLI approval prompt freeze (#13617) has regressed.
|
||||
"""
|
||||
from tools.terminal_tool import (
|
||||
from hermes_agent.tools.terminal import (
|
||||
set_approval_callback,
|
||||
set_sudo_password_callback,
|
||||
_get_approval_callback,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from unittest.mock import MagicMock, patch
|
|||
|
||||
import pytest
|
||||
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
|
||||
def _make_cli():
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import os
|
||||
from unittest.mock import patch
|
||||
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
|
||||
def _assert_chrome_debug_cmd(cmd, expected_chrome, expected_port):
|
||||
|
|
@ -26,8 +26,8 @@ class TestChromeDebugLaunch:
|
|||
captured["kwargs"] = kwargs
|
||||
return object()
|
||||
|
||||
with patch("cli.shutil.which", side_effect=lambda name: r"C:\Chrome\chrome.exe" if name == "chrome.exe" else None), \
|
||||
patch("cli.os.path.isfile", side_effect=lambda path: path == r"C:\Chrome\chrome.exe"), \
|
||||
with patch("hermes_agent.cli.repl.shutil.which", side_effect=lambda name: r"C:\Chrome\chrome.exe" if name == "chrome.exe" else None), \
|
||||
patch("hermes_agent.cli.repl.os.path.isfile", side_effect=lambda path: path == r"C:\Chrome\chrome.exe"), \
|
||||
patch("subprocess.Popen", side_effect=fake_popen):
|
||||
assert HermesCLI._try_launch_chrome_debug(9333, "Windows") is True
|
||||
|
||||
|
|
@ -49,8 +49,8 @@ class TestChromeDebugLaunch:
|
|||
monkeypatch.delenv("ProgramFiles(x86)", raising=False)
|
||||
monkeypatch.delenv("LOCALAPPDATA", raising=False)
|
||||
|
||||
with patch("cli.shutil.which", return_value=None), \
|
||||
patch("cli.os.path.isfile", side_effect=lambda path: path == installed), \
|
||||
with patch("hermes_agent.cli.repl.shutil.which", return_value=None), \
|
||||
patch("hermes_agent.cli.repl.os.path.isfile", side_effect=lambda path: path == installed), \
|
||||
patch("subprocess.Popen", side_effect=fake_popen):
|
||||
assert HermesCLI._try_launch_chrome_debug(9222, "Windows") is True
|
||||
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ def _isolate(tmp_path, monkeypatch):
|
|||
@pytest.fixture
|
||||
def cli_obj(_isolate):
|
||||
"""Create a minimal HermesCLI instance for banner testing."""
|
||||
with patch("cli.load_cli_config", return_value={
|
||||
with patch("hermes_agent.cli.repl.load_cli_config", return_value={
|
||||
"display": {"tool_progress": "new"},
|
||||
"terminal": {},
|
||||
}), patch("cli.get_tool_definitions", return_value=[]), \
|
||||
patch("cli.build_welcome_banner"):
|
||||
from cli import HermesCLI
|
||||
}), patch("hermes_agent.cli.repl.get_tool_definitions", return_value=[]), \
|
||||
patch("hermes_agent.cli.repl.build_welcome_banner"):
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
obj = HermesCLI.__new__(HermesCLI)
|
||||
obj.model = "test-model"
|
||||
obj.enabled_toolsets = ["hermes-core"]
|
||||
|
|
@ -47,8 +47,8 @@ class TestLowContextWarning:
|
|||
def test_no_warning_for_normal_context(self, cli_obj):
|
||||
"""No warning when context is 32k+."""
|
||||
cli_obj.agent.context_compressor.context_length = 32768
|
||||
with patch("cli.get_tool_definitions", return_value=[]), \
|
||||
patch("cli.build_welcome_banner"):
|
||||
with patch("hermes_agent.cli.repl.get_tool_definitions", return_value=[]), \
|
||||
patch("hermes_agent.cli.repl.build_welcome_banner"):
|
||||
cli_obj.show_banner()
|
||||
|
||||
# Check that no yellow warning was printed
|
||||
|
|
@ -59,8 +59,8 @@ class TestLowContextWarning:
|
|||
def test_warning_for_low_context(self, cli_obj):
|
||||
"""Warning shown when context is 4096 (Ollama default)."""
|
||||
cli_obj.agent.context_compressor.context_length = 4096
|
||||
with patch("cli.get_tool_definitions", return_value=[]), \
|
||||
patch("cli.build_welcome_banner"):
|
||||
with patch("hermes_agent.cli.repl.get_tool_definitions", return_value=[]), \
|
||||
patch("hermes_agent.cli.repl.build_welcome_banner"):
|
||||
cli_obj.show_banner()
|
||||
|
||||
calls = [str(c) for c in cli_obj.console.print.call_args_list]
|
||||
|
|
@ -71,8 +71,8 @@ class TestLowContextWarning:
|
|||
def test_warning_for_2048_context(self, cli_obj):
|
||||
"""Warning shown for 2048 tokens (common LM Studio default)."""
|
||||
cli_obj.agent.context_compressor.context_length = 2048
|
||||
with patch("cli.get_tool_definitions", return_value=[]), \
|
||||
patch("cli.build_welcome_banner"):
|
||||
with patch("hermes_agent.cli.repl.get_tool_definitions", return_value=[]), \
|
||||
patch("hermes_agent.cli.repl.build_welcome_banner"):
|
||||
cli_obj.show_banner()
|
||||
|
||||
calls = [str(c) for c in cli_obj.console.print.call_args_list]
|
||||
|
|
@ -82,8 +82,8 @@ class TestLowContextWarning:
|
|||
def test_no_warning_at_boundary(self, cli_obj):
|
||||
"""No warning at exactly 8192 — 8192 is borderline but included in warning."""
|
||||
cli_obj.agent.context_compressor.context_length = 8192
|
||||
with patch("cli.get_tool_definitions", return_value=[]), \
|
||||
patch("cli.build_welcome_banner"):
|
||||
with patch("hermes_agent.cli.repl.get_tool_definitions", return_value=[]), \
|
||||
patch("hermes_agent.cli.repl.build_welcome_banner"):
|
||||
cli_obj.show_banner()
|
||||
|
||||
calls = [str(c) for c in cli_obj.console.print.call_args_list]
|
||||
|
|
@ -93,8 +93,8 @@ class TestLowContextWarning:
|
|||
def test_no_warning_above_boundary(self, cli_obj):
|
||||
"""No warning at 16384."""
|
||||
cli_obj.agent.context_compressor.context_length = 16384
|
||||
with patch("cli.get_tool_definitions", return_value=[]), \
|
||||
patch("cli.build_welcome_banner"):
|
||||
with patch("hermes_agent.cli.repl.get_tool_definitions", return_value=[]), \
|
||||
patch("hermes_agent.cli.repl.build_welcome_banner"):
|
||||
cli_obj.show_banner()
|
||||
|
||||
calls = [str(c) for c in cli_obj.console.print.call_args_list]
|
||||
|
|
@ -105,8 +105,8 @@ class TestLowContextWarning:
|
|||
"""Ollama-specific fix shown when port 11434 detected."""
|
||||
cli_obj.agent.context_compressor.context_length = 4096
|
||||
cli_obj.base_url = "http://localhost:11434/v1"
|
||||
with patch("cli.get_tool_definitions", return_value=[]), \
|
||||
patch("cli.build_welcome_banner"):
|
||||
with patch("hermes_agent.cli.repl.get_tool_definitions", return_value=[]), \
|
||||
patch("hermes_agent.cli.repl.build_welcome_banner"):
|
||||
cli_obj.show_banner()
|
||||
|
||||
calls = [str(c) for c in cli_obj.console.print.call_args_list]
|
||||
|
|
@ -117,8 +117,8 @@ class TestLowContextWarning:
|
|||
"""LM Studio-specific fix shown when port 1234 detected."""
|
||||
cli_obj.agent.context_compressor.context_length = 2048
|
||||
cli_obj.base_url = "http://localhost:1234/v1"
|
||||
with patch("cli.get_tool_definitions", return_value=[]), \
|
||||
patch("cli.build_welcome_banner"):
|
||||
with patch("hermes_agent.cli.repl.get_tool_definitions", return_value=[]), \
|
||||
patch("hermes_agent.cli.repl.build_welcome_banner"):
|
||||
cli_obj.show_banner()
|
||||
|
||||
calls = [str(c) for c in cli_obj.console.print.call_args_list]
|
||||
|
|
@ -129,8 +129,8 @@ class TestLowContextWarning:
|
|||
"""Generic fix shown for unknown servers."""
|
||||
cli_obj.agent.context_compressor.context_length = 4096
|
||||
cli_obj.base_url = "http://localhost:8080/v1"
|
||||
with patch("cli.get_tool_definitions", return_value=[]), \
|
||||
patch("cli.build_welcome_banner"):
|
||||
with patch("hermes_agent.cli.repl.get_tool_definitions", return_value=[]), \
|
||||
patch("hermes_agent.cli.repl.build_welcome_banner"):
|
||||
cli_obj.show_banner()
|
||||
|
||||
calls = [str(c) for c in cli_obj.console.print.call_args_list]
|
||||
|
|
@ -140,8 +140,8 @@ class TestLowContextWarning:
|
|||
def test_no_warning_when_no_context_length(self, cli_obj):
|
||||
"""No warning when context length is not yet known."""
|
||||
cli_obj.agent.context_compressor.context_length = None
|
||||
with patch("cli.get_tool_definitions", return_value=[]), \
|
||||
patch("cli.build_welcome_banner"):
|
||||
with patch("hermes_agent.cli.repl.get_tool_definitions", return_value=[]), \
|
||||
patch("hermes_agent.cli.repl.build_welcome_banner"):
|
||||
cli_obj.show_banner()
|
||||
|
||||
calls = [str(c) for c in cli_obj.console.print.call_args_list]
|
||||
|
|
@ -153,7 +153,7 @@ class TestLowContextWarning:
|
|||
cli_obj.agent.context_compressor.context_length = 4096
|
||||
|
||||
with patch("shutil.get_terminal_size", return_value=os.terminal_size((70, 40))), \
|
||||
patch("cli._build_compact_banner", return_value="compact banner"):
|
||||
patch("hermes_agent.cli.repl._build_compact_banner", return_value="compact banner"):
|
||||
cli_obj.show_banner()
|
||||
|
||||
calls = [str(c) for c in cli_obj.console.print.call_args_list]
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
|
||||
def _make_cli() -> HermesCLI:
|
||||
|
|
@ -64,7 +64,7 @@ def test_copy_invalid_index_does_not_copy():
|
|||
cli_obj = _make_cli()
|
||||
cli_obj.conversation_history = [{"role": "assistant", "content": "only"}]
|
||||
|
||||
with patch.object(cli_obj, "_write_osc52_clipboard") as mock_copy, patch("cli._cprint") as mock_print:
|
||||
with patch.object(cli_obj, "_write_osc52_clipboard") as mock_copy, patch("hermes_agent.cli.repl._cprint") as mock_print:
|
||||
cli_obj.process_command("/copy 99")
|
||||
|
||||
mock_copy.assert_not_called()
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ def _make_cli(**kwargs):
|
|||
with patch.dict(sys.modules, prompt_toolkit_stubs), patch.dict(
|
||||
"os.environ", clean_env, clear=False
|
||||
):
|
||||
import cli as _cli_mod
|
||||
import hermes_agent.cli.repl as _cli_mod
|
||||
|
||||
_cli_mod = importlib.reload(_cli_mod)
|
||||
with patch.object(_cli_mod, "get_tool_definitions", return_value=[]), patch.dict(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
from unittest.mock import patch
|
||||
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
|
||||
class _FakeBuffer:
|
||||
|
|
@ -43,7 +43,7 @@ def test_open_external_editor_uses_prompt_toolkit_buffer_editor():
|
|||
def test_open_external_editor_rejects_when_no_tui():
|
||||
cli_obj = _make_cli(with_app=False)
|
||||
|
||||
with patch("cli._cprint") as mock_cprint:
|
||||
with patch("hermes_agent.cli.repl._cprint") as mock_cprint:
|
||||
assert cli_obj._open_external_editor() is False
|
||||
|
||||
assert mock_cprint.called
|
||||
|
|
@ -54,7 +54,7 @@ def test_open_external_editor_rejects_modal_prompts():
|
|||
cli_obj = _make_cli()
|
||||
cli_obj._approval_state = {"selected": 0}
|
||||
|
||||
with patch("cli._cprint") as mock_cprint:
|
||||
with patch("hermes_agent.cli.repl._cprint") as mock_cprint:
|
||||
assert cli_obj._open_external_editor() is False
|
||||
|
||||
assert mock_cprint.called
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from pathlib import Path
|
|||
|
||||
import pytest
|
||||
|
||||
from cli import _detect_file_drop
|
||||
from hermes_agent.cli.repl import _detect_file_drop
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from cli import (
|
||||
from hermes_agent.cli.repl import (
|
||||
HermesCLI,
|
||||
_collect_query_images,
|
||||
_format_image_attachment_badges,
|
||||
|
|
@ -26,7 +26,7 @@ class TestImageCommand:
|
|||
img = _make_image(tmp_path / "photo.png")
|
||||
cli_obj = _make_cli()
|
||||
|
||||
with patch("cli._cprint"):
|
||||
with patch("hermes_agent.cli.repl._cprint"):
|
||||
cli_obj._handle_image_command(f"/image {img}")
|
||||
|
||||
assert cli_obj._attached_images == [img]
|
||||
|
|
@ -35,7 +35,7 @@ class TestImageCommand:
|
|||
img = _make_image(tmp_path / "my photo.png")
|
||||
cli_obj = _make_cli()
|
||||
|
||||
with patch("cli._cprint"):
|
||||
with patch("hermes_agent.cli.repl._cprint"):
|
||||
cli_obj._handle_image_command(f'/image "{img}"')
|
||||
|
||||
assert cli_obj._attached_images == [img]
|
||||
|
|
@ -45,7 +45,7 @@ class TestImageCommand:
|
|||
file_path.write_text("hello\n", encoding="utf-8")
|
||||
cli_obj = _make_cli()
|
||||
|
||||
with patch("cli._cprint") as mock_print:
|
||||
with patch("hermes_agent.cli.repl._cprint") as mock_print:
|
||||
cli_obj._handle_image_command(f"/image {file_path}")
|
||||
|
||||
assert cli_obj._attached_images == []
|
||||
|
|
@ -84,7 +84,7 @@ class TestCollectQueryImages:
|
|||
class TestTermuxImageHints:
|
||||
def test_termux_example_image_path_prefers_real_shared_storage_root(self, monkeypatch):
|
||||
existing = {"/sdcard", "/storage/emulated/0"}
|
||||
monkeypatch.setattr("cli.os.path.isdir", lambda path: path in existing)
|
||||
monkeypatch.setattr("hermes_agent.cli.repl.os.path.isdir", lambda path: path in existing)
|
||||
|
||||
hint = _termux_example_image_path()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import os
|
|||
import sys
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
|
||||
def _make_cli(env_overrides=None, config_overrides=None, **kwargs):
|
||||
"""Create a HermesCLI instance with minimal mocking."""
|
||||
|
|
@ -46,7 +44,7 @@ def _make_cli(env_overrides=None, config_overrides=None, **kwargs):
|
|||
}
|
||||
with patch.dict(sys.modules, prompt_toolkit_stubs), \
|
||||
patch.dict("os.environ", clean_env, clear=False):
|
||||
import cli as _cli_mod
|
||||
import hermes_agent.cli.repl as _cli_mod
|
||||
_cli_mod = importlib.reload(_cli_mod)
|
||||
with patch.object(_cli_mod, "get_tool_definitions", return_value=[]), \
|
||||
patch.dict(_cli_mod.__dict__, {"CLI_CONFIG": _clean_config}):
|
||||
|
|
@ -266,7 +264,7 @@ class TestRootLevelProviderOverride:
|
|||
},
|
||||
}))
|
||||
|
||||
import cli
|
||||
import hermes_agent.cli.repl
|
||||
monkeypatch.setattr(cli, "_hermes_home", hermes_home)
|
||||
cfg = cli.load_cli_config()
|
||||
|
||||
|
|
@ -289,7 +287,7 @@ class TestRootLevelProviderOverride:
|
|||
},
|
||||
}))
|
||||
|
||||
import cli
|
||||
import hermes_agent.cli.repl
|
||||
monkeypatch.setattr(cli, "_hermes_home", hermes_home)
|
||||
cfg = cli.load_cli_config()
|
||||
|
||||
|
|
@ -298,7 +296,7 @@ class TestRootLevelProviderOverride:
|
|||
|
||||
def test_normalize_root_model_keys_moves_to_model(self):
|
||||
"""_normalize_root_model_keys migrates root keys into model section."""
|
||||
from hermes_cli.config import _normalize_root_model_keys
|
||||
from hermes_agent.cli.config import _normalize_root_model_keys
|
||||
|
||||
config = {
|
||||
"provider": "opencode-go",
|
||||
|
|
@ -317,7 +315,7 @@ class TestRootLevelProviderOverride:
|
|||
|
||||
def test_normalize_root_model_keys_does_not_override_existing(self):
|
||||
"""Existing model.provider is never overridden by root-level key."""
|
||||
from hermes_cli.config import _normalize_root_model_keys
|
||||
from hermes_agent.cli.config import _normalize_root_model_keys
|
||||
|
||||
config = {
|
||||
"provider": "stale-provider",
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import time
|
|||
import unittest
|
||||
from unittest.mock import MagicMock, patch, PropertyMock
|
||||
|
||||
from tools.interrupt import set_interrupt, is_interrupted
|
||||
from hermes_agent.tools.interrupt import set_interrupt, is_interrupted
|
||||
|
||||
|
||||
class TestCLISubagentInterrupt(unittest.TestCase):
|
||||
|
|
@ -32,7 +32,7 @@ class TestCLISubagentInterrupt(unittest.TestCase):
|
|||
|
||||
def test_full_delegate_interrupt_flow(self):
|
||||
"""Full integration: parent runs delegate_task, main thread interrupts."""
|
||||
from run_agent import AIAgent
|
||||
from hermes_agent.agent.loop import AIAgent
|
||||
|
||||
interrupt_detected = threading.Event()
|
||||
child_started = threading.Event()
|
||||
|
|
@ -98,8 +98,8 @@ class TestCLISubagentInterrupt(unittest.TestCase):
|
|||
}
|
||||
|
||||
# Patch AIAgent to use our mock
|
||||
from tools.delegate_tool import _run_single_child
|
||||
from run_agent import IterationBudget
|
||||
from hermes_agent.tools.delegate import _run_single_child
|
||||
from hermes_agent.agent.loop import IterationBudget
|
||||
|
||||
parent.iteration_budget = IterationBudget(max_total=100)
|
||||
|
||||
|
|
@ -109,7 +109,7 @@ class TestCLISubagentInterrupt(unittest.TestCase):
|
|||
|
||||
def run_delegate():
|
||||
try:
|
||||
with patch('run_agent.AIAgent') as MockAgent:
|
||||
with patch('hermes_agent.agent.loop.AIAgent') as MockAgent:
|
||||
mock_instance = MagicMock()
|
||||
mock_instance._interrupt_requested = False
|
||||
mock_instance._interrupt_message = None
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
from unittest.mock import patch
|
||||
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
|
||||
class TestCLILoadingIndicator:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from io import StringIO
|
|||
from rich.console import Console
|
||||
from rich.markdown import Markdown
|
||||
|
||||
from cli import _render_final_assistant_content
|
||||
from hermes_agent.cli.repl import _render_final_assistant_content
|
||||
|
||||
|
||||
def _render_to_text(renderable) -> str:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from unittest.mock import MagicMock, patch
|
|||
|
||||
def _make_cli(tmp_path, mcp_servers=None):
|
||||
"""Create a minimal HermesCLI instance with mocked config."""
|
||||
import cli as cli_mod
|
||||
import hermes_agent.cli.repl as cli_mod
|
||||
obj = object.__new__(cli_mod.HermesCLI)
|
||||
obj.config = {"mcp_servers": mcp_servers or {}}
|
||||
obj._agent_running = False
|
||||
|
|
@ -32,7 +32,7 @@ class TestMCPConfigWatch:
|
|||
"""If mtime and mcp_servers unchanged, _reload_mcp is NOT called."""
|
||||
obj, cfg_file = _make_cli(tmp_path)
|
||||
|
||||
with patch("hermes_cli.config.get_config_path", return_value=cfg_file):
|
||||
with patch("hermes_agent.cli.config.get_config_path", return_value=cfg_file):
|
||||
obj._check_config_mcp_changes()
|
||||
|
||||
obj._reload_mcp.assert_not_called()
|
||||
|
|
@ -47,7 +47,7 @@ class TestMCPConfigWatch:
|
|||
# Force mtime to appear changed
|
||||
obj._config_mtime = 0.0
|
||||
|
||||
with patch("hermes_cli.config.get_config_path", return_value=cfg_file):
|
||||
with patch("hermes_agent.cli.config.get_config_path", return_value=cfg_file):
|
||||
obj._check_config_mcp_changes()
|
||||
|
||||
obj._reload_mcp.assert_not_called()
|
||||
|
|
@ -61,7 +61,7 @@ class TestMCPConfigWatch:
|
|||
cfg_file.write_text(yaml.dump({"mcp_servers": {"github": {"url": "https://mcp.github.com"}}}))
|
||||
obj._config_mtime = 0.0 # force stale mtime
|
||||
|
||||
with patch("hermes_cli.config.get_config_path", return_value=cfg_file):
|
||||
with patch("hermes_agent.cli.config.get_config_path", return_value=cfg_file):
|
||||
obj._check_config_mcp_changes()
|
||||
|
||||
obj._reload_mcp.assert_called_once()
|
||||
|
|
@ -75,7 +75,7 @@ class TestMCPConfigWatch:
|
|||
cfg_file.write_text(yaml.dump({"mcp_servers": {}}))
|
||||
obj._config_mtime = 0.0
|
||||
|
||||
with patch("hermes_cli.config.get_config_path", return_value=cfg_file):
|
||||
with patch("hermes_agent.cli.config.get_config_path", return_value=cfg_file):
|
||||
obj._check_config_mcp_changes()
|
||||
|
||||
obj._reload_mcp.assert_called_once()
|
||||
|
|
@ -85,7 +85,7 @@ class TestMCPConfigWatch:
|
|||
obj, cfg_file = _make_cli(tmp_path)
|
||||
obj._last_config_check = time.monotonic() # just checked
|
||||
|
||||
with patch("hermes_cli.config.get_config_path", return_value=cfg_file), \
|
||||
with patch("hermes_agent.cli.config.get_config_path", return_value=cfg_file), \
|
||||
patch.object(Path, "stat") as mock_stat:
|
||||
obj._check_config_mcp_changes()
|
||||
mock_stat.assert_not_called()
|
||||
|
|
@ -97,7 +97,7 @@ class TestMCPConfigWatch:
|
|||
obj, cfg_file = _make_cli(tmp_path)
|
||||
missing = tmp_path / "nonexistent.yaml"
|
||||
|
||||
with patch("hermes_cli.config.get_config_path", return_value=missing):
|
||||
with patch("hermes_agent.cli.config.get_config_path", return_value=missing):
|
||||
obj._check_config_mcp_changes() # should not raise
|
||||
|
||||
obj._reload_mcp.assert_not_called()
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import sys
|
|||
from datetime import timedelta
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from hermes_state import SessionDB
|
||||
from tools.todo_tool import TodoStore
|
||||
from hermes_agent.state import SessionDB
|
||||
from hermes_agent.tools.todo import TodoStore
|
||||
|
||||
|
||||
class _FakeCompressor:
|
||||
|
|
@ -111,7 +111,7 @@ def _make_cli(env_overrides=None, config_overrides=None, **kwargs):
|
|||
with patch.dict(sys.modules, prompt_toolkit_stubs), patch.dict(
|
||||
"os.environ", clean_env, clear=False
|
||||
):
|
||||
import cli as _cli_mod
|
||||
import hermes_agent.cli.repl as _cli_mod
|
||||
|
||||
_cli_mod = importlib.reload(_cli_mod)
|
||||
with patch.object(_cli_mod, "get_tool_definitions", return_value=[]), patch.dict(
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from agent.skill_commands import scan_skill_commands
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.agent.skill_commands import scan_skill_commands
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
|
||||
def _make_cli():
|
||||
|
|
@ -38,7 +38,7 @@ class TestCLIPlanCommand:
|
|||
def test_plan_command_queues_plan_skill_message(self, tmp_path, monkeypatch):
|
||||
cli_obj = _make_cli()
|
||||
|
||||
with patch("tools.skills_tool.SKILLS_DIR", tmp_path):
|
||||
with patch("hermes_agent.tools.skills.tool.SKILLS_DIR", tmp_path):
|
||||
_make_plan_skill(tmp_path)
|
||||
scan_skill_commands()
|
||||
result = cli_obj.process_command("/plan Add OAuth login")
|
||||
|
|
@ -56,7 +56,7 @@ class TestCLIPlanCommand:
|
|||
def test_plan_without_args_uses_skill_context_guidance(self, tmp_path, monkeypatch):
|
||||
cli_obj = _make_cli()
|
||||
|
||||
with patch("tools.skills_tool.SKILLS_DIR", tmp_path):
|
||||
with patch("hermes_agent.tools.skills.tool.SKILLS_DIR", tmp_path):
|
||||
_make_plan_skill(tmp_path)
|
||||
scan_skill_commands()
|
||||
cli_obj.process_command("/plan")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"""Tests for slash command prefix matching in HermesCLI.process_command."""
|
||||
from unittest.mock import MagicMock, patch
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
|
||||
def _make_cli():
|
||||
|
|
@ -72,7 +72,7 @@ class TestSlashCommandPrefixMatching:
|
|||
def test_ambiguous_prefix_shows_suggestions(self):
|
||||
"""/re matches multiple commands — should show ambiguous message."""
|
||||
cli_obj = _make_cli()
|
||||
with patch("cli._cprint") as mock_cprint:
|
||||
with patch("hermes_agent.cli.repl._cprint") as mock_cprint:
|
||||
cli_obj.process_command("/re")
|
||||
printed = " ".join(str(c) for c in mock_cprint.call_args_list)
|
||||
assert "Ambiguous" in printed or "Did you mean" in printed
|
||||
|
|
@ -80,7 +80,7 @@ class TestSlashCommandPrefixMatching:
|
|||
def test_unknown_command_shows_error(self):
|
||||
"""/xyz should show unknown command error."""
|
||||
cli_obj = _make_cli()
|
||||
with patch("cli._cprint") as mock_cprint:
|
||||
with patch("hermes_agent.cli.repl._cprint") as mock_cprint:
|
||||
cli_obj.process_command("/xyz")
|
||||
printed = " ".join(str(c) for c in mock_cprint.call_args_list)
|
||||
assert "Unknown command" in printed
|
||||
|
|
@ -99,7 +99,7 @@ class TestSlashCommandPrefixMatching:
|
|||
printed = []
|
||||
cli_obj.console.print = lambda *a, **kw: printed.append(str(a))
|
||||
|
||||
import cli as cli_mod
|
||||
import hermes_agent.cli.repl as cli_mod
|
||||
with patch.object(cli_mod, '_skill_commands', fake_skill):
|
||||
cli_obj.process_command("/test-skill-xy")
|
||||
|
||||
|
|
@ -113,7 +113,7 @@ class TestSlashCommandPrefixMatching:
|
|||
# /help-extra is a fake skill that shares /hel prefix with /help
|
||||
fake_skill = {"/help-extra": {"name": "Help Extra", "description": "test"}}
|
||||
|
||||
import cli as cli_mod
|
||||
import hermes_agent.cli.repl as cli_mod
|
||||
with patch.object(cli_mod, '_skill_commands', fake_skill), patch.object(cli_obj, 'show_help') as mock_help:
|
||||
cli_obj.process_command("/help")
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ class TestSlashCommandPrefixMatching:
|
|||
cli_obj = _make_cli()
|
||||
fake_skill = {"/quint-pipeline": {"name": "Quint Pipeline", "description": "test"}}
|
||||
|
||||
import cli as cli_mod
|
||||
import hermes_agent.cli.repl as cli_mod
|
||||
with patch.object(cli_mod, '_skill_commands', fake_skill):
|
||||
# /quit is caught by the exact "/quit" branch → process_command returns False
|
||||
result = cli_obj.process_command("/qui")
|
||||
|
|
@ -141,7 +141,7 @@ class TestSlashCommandPrefixMatching:
|
|||
"""/re matches /reset and /retry (both 6 chars) — no unique shortest, stays ambiguous."""
|
||||
cli_obj = _make_cli()
|
||||
printed = []
|
||||
import cli as cli_mod
|
||||
import hermes_agent.cli.repl as cli_mod
|
||||
with patch.object(cli_mod, '_cprint', side_effect=lambda t: printed.append(t)):
|
||||
cli_obj.process_command("/re")
|
||||
combined = " ".join(printed)
|
||||
|
|
@ -151,7 +151,7 @@ class TestSlashCommandPrefixMatching:
|
|||
"""/help typed with /help-extra skill installed → exact match wins."""
|
||||
cli_obj = _make_cli()
|
||||
fake_skill = {"/help-extra": {"name": "Help Extra", "description": ""}}
|
||||
import cli as cli_mod
|
||||
import hermes_agent.cli.repl as cli_mod
|
||||
with patch.object(cli_mod, '_skill_commands', fake_skill), \
|
||||
patch.object(cli_obj, 'show_help') as mock_help:
|
||||
cli_obj.process_command("/help")
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ def _make_real_cli(**kwargs):
|
|||
with patch.dict(sys.modules, prompt_toolkit_stubs), patch.dict(
|
||||
"os.environ", clean_env, clear=False
|
||||
):
|
||||
import cli as cli_mod
|
||||
import hermes_agent.cli.repl as cli_mod
|
||||
|
||||
cli_mod = importlib.reload(cli_mod)
|
||||
with patch.object(cli_mod, "get_tool_definitions", return_value=[]), patch.dict(
|
||||
|
|
@ -69,7 +69,7 @@ class _DummyCLI:
|
|||
|
||||
|
||||
def test_main_applies_preloaded_skills_to_system_prompt(monkeypatch):
|
||||
import cli as cli_mod
|
||||
import hermes_agent.cli.repl as cli_mod
|
||||
|
||||
created = {}
|
||||
|
||||
|
|
@ -93,7 +93,7 @@ def test_main_applies_preloaded_skills_to_system_prompt(monkeypatch):
|
|||
|
||||
|
||||
def test_main_raises_for_unknown_preloaded_skill(monkeypatch):
|
||||
import cli as cli_mod
|
||||
import hermes_agent.cli.repl as cli_mod
|
||||
|
||||
monkeypatch.setattr(cli_mod, "HermesCLI", lambda **kwargs: _DummyCLI(**kwargs))
|
||||
monkeypatch.setattr(
|
||||
|
|
@ -112,7 +112,7 @@ def test_show_banner_does_not_print_skills():
|
|||
cli_obj.preloaded_skills = ["hermes-agent-dev", "github-auth"]
|
||||
cli_obj.console = MagicMock()
|
||||
|
||||
with patch("cli.build_welcome_banner") as mock_banner, patch(
|
||||
with patch("hermes_agent.cli.repl.build_welcome_banner") as mock_banner, patch(
|
||||
"shutil.get_terminal_size", return_value=os.terminal_size((120, 40))
|
||||
):
|
||||
cli_obj.show_banner()
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ from types import SimpleNamespace
|
|||
|
||||
import pytest
|
||||
|
||||
from hermes_cli.auth import AuthError
|
||||
from hermes_cli import main as hermes_main
|
||||
from hermes_agent.cli.auth.auth import AuthError
|
||||
from hermes_agent.cli import main as hermes_main
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -26,7 +26,7 @@ def _reset_modules(prefixes: tuple[str, ...]):
|
|||
@pytest.fixture(autouse=True)
|
||||
def _restore_cli_and_tool_modules():
|
||||
"""Save and restore tools/cli/run_agent modules around every test."""
|
||||
prefixes = ("tools", "cli", "run_agent")
|
||||
prefixes = ("tools", "cli", "hermes_agent.agent.loop")
|
||||
original_modules = {
|
||||
name: module
|
||||
for name, module in sys.modules.items()
|
||||
|
|
@ -110,7 +110,7 @@ def _install_prompt_toolkit_stubs():
|
|||
|
||||
def _import_cli():
|
||||
for name in list(sys.modules):
|
||||
if name == "cli" or name == "run_agent" or name == "tools" or name.startswith("tools."):
|
||||
if name == "cli" or name == "hermes_agent.agent.loop" or name == "tools" or name.startswith("tools."):
|
||||
sys.modules.pop(name, None)
|
||||
|
||||
if "firecrawl" not in sys.modules:
|
||||
|
|
@ -120,7 +120,7 @@ def _import_cli():
|
|||
importlib.import_module("prompt_toolkit")
|
||||
except ModuleNotFoundError:
|
||||
_install_prompt_toolkit_stubs()
|
||||
return importlib.import_module("cli")
|
||||
return importlib.import_module("hermes_agent.cli.repl")
|
||||
|
||||
|
||||
def test_hermes_cli_init_does_not_eagerly_resolve_runtime_provider(monkeypatch):
|
||||
|
|
@ -131,8 +131,8 @@ def test_hermes_cli_init_does_not_eagerly_resolve_runtime_provider(monkeypatch):
|
|||
calls["count"] += 1
|
||||
raise AssertionError("resolve_runtime_provider should not be called in HermesCLI.__init__")
|
||||
|
||||
monkeypatch.setattr("hermes_cli.runtime_provider.resolve_runtime_provider", _unexpected_runtime_resolve)
|
||||
monkeypatch.setattr("hermes_cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
|
||||
monkeypatch.setattr("hermes_agent.cli.runtime_provider.resolve_runtime_provider", _unexpected_runtime_resolve)
|
||||
monkeypatch.setattr("hermes_agent.cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
|
||||
|
||||
shell = cli.HermesCLI(model="gpt-5", compact=True, max_turns=1)
|
||||
|
||||
|
|
@ -160,8 +160,8 @@ def test_runtime_resolution_failure_is_not_sticky(monkeypatch):
|
|||
def __init__(self, *args, **kwargs):
|
||||
self.kwargs = kwargs
|
||||
|
||||
monkeypatch.setattr("hermes_cli.runtime_provider.resolve_runtime_provider", _runtime_resolve)
|
||||
monkeypatch.setattr("hermes_cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
|
||||
monkeypatch.setattr("hermes_agent.cli.runtime_provider.resolve_runtime_provider", _runtime_resolve)
|
||||
monkeypatch.setattr("hermes_agent.cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
|
||||
monkeypatch.setattr(cli, "AIAgent", _DummyAgent)
|
||||
|
||||
shell = cli.HermesCLI(model="gpt-5", compact=True, max_turns=1)
|
||||
|
|
@ -184,8 +184,8 @@ def test_runtime_resolution_rebuilds_agent_on_routing_change(monkeypatch):
|
|||
"source": "env/config",
|
||||
}
|
||||
|
||||
monkeypatch.setattr("hermes_cli.runtime_provider.resolve_runtime_provider", _runtime_resolve)
|
||||
monkeypatch.setattr("hermes_cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
|
||||
monkeypatch.setattr("hermes_agent.cli.runtime_provider.resolve_runtime_provider", _runtime_resolve)
|
||||
monkeypatch.setattr("hermes_agent.cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
|
||||
|
||||
shell = cli.HermesCLI(model="gpt-5", compact=True, max_turns=1)
|
||||
shell.provider = "openrouter"
|
||||
|
|
@ -253,10 +253,10 @@ def test_codex_provider_replaces_incompatible_default_model(monkeypatch):
|
|||
"source": "env/config",
|
||||
}
|
||||
|
||||
monkeypatch.setattr("hermes_cli.runtime_provider.resolve_runtime_provider", _runtime_resolve)
|
||||
monkeypatch.setattr("hermes_cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
|
||||
monkeypatch.setattr("hermes_agent.cli.runtime_provider.resolve_runtime_provider", _runtime_resolve)
|
||||
monkeypatch.setattr("hermes_agent.cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.codex_models.get_codex_model_ids",
|
||||
"hermes_agent.cli.models.codex.get_codex_model_ids",
|
||||
lambda access_token=None: ["gpt-5.2-codex", "gpt-5.1-codex-mini"],
|
||||
)
|
||||
|
||||
|
|
@ -271,7 +271,7 @@ def test_codex_provider_replaces_incompatible_default_model(monkeypatch):
|
|||
|
||||
|
||||
def test_model_flow_nous_prints_subscription_guidance_without_mutating_explicit_tts(monkeypatch, capsys):
|
||||
monkeypatch.setattr("hermes_cli.nous_subscription.managed_nous_tools_enabled", lambda: True)
|
||||
monkeypatch.setattr("hermes_agent.cli.nous_subscription.managed_nous_tools_enabled", lambda: True)
|
||||
config = {
|
||||
"model": {"provider": "nous", "default": "claude-opus-4-6"},
|
||||
"tts": {"provider": "elevenlabs"},
|
||||
|
|
@ -279,23 +279,23 @@ def test_model_flow_nous_prints_subscription_guidance_without_mutating_explicit_
|
|||
}
|
||||
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.auth.get_provider_auth_state",
|
||||
"hermes_agent.cli.auth.auth.get_provider_auth_state",
|
||||
lambda provider: {"access_token": "nous-token"},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.auth.resolve_nous_runtime_credentials",
|
||||
"hermes_agent.cli.auth.auth.resolve_nous_runtime_credentials",
|
||||
lambda *args, **kwargs: {
|
||||
"base_url": "https://inference.example.com/v1",
|
||||
"api_key": "nous-key",
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.auth.fetch_nous_models",
|
||||
"hermes_agent.cli.auth.auth.fetch_nous_models",
|
||||
lambda *args, **kwargs: ["claude-opus-4-6"],
|
||||
)
|
||||
monkeypatch.setattr("hermes_cli.auth._prompt_model_selection", lambda model_ids, current_model="", pricing=None, **kw: "claude-opus-4-6")
|
||||
monkeypatch.setattr("hermes_cli.auth._save_model_choice", lambda model: None)
|
||||
monkeypatch.setattr("hermes_cli.auth._update_config_for_provider", lambda provider, url: None)
|
||||
monkeypatch.setattr("hermes_agent.cli.auth.auth._prompt_model_selection", lambda model_ids, current_model="", pricing=None, **kw: "claude-opus-4-6")
|
||||
monkeypatch.setattr("hermes_agent.cli.auth.auth._save_model_choice", lambda model: None)
|
||||
monkeypatch.setattr("hermes_agent.cli.auth.auth._update_config_for_provider", lambda provider, url: None)
|
||||
|
||||
hermes_main._model_flow_nous(config, current_model="claude-opus-4-6")
|
||||
|
||||
|
|
@ -306,30 +306,30 @@ def test_model_flow_nous_prints_subscription_guidance_without_mutating_explicit_
|
|||
|
||||
|
||||
def test_model_flow_nous_offers_tool_gateway_prompt_when_unconfigured(monkeypatch, capsys):
|
||||
monkeypatch.setattr("hermes_cli.nous_subscription.managed_nous_tools_enabled", lambda: True)
|
||||
monkeypatch.setattr("hermes_agent.cli.nous_subscription.managed_nous_tools_enabled", lambda: True)
|
||||
config = {
|
||||
"model": {"provider": "nous", "default": "claude-opus-4-6"},
|
||||
"tts": {"provider": "edge"},
|
||||
}
|
||||
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.auth.get_provider_auth_state",
|
||||
"hermes_agent.cli.auth.auth.get_provider_auth_state",
|
||||
lambda provider: {"access_token": "***"},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.auth.resolve_nous_runtime_credentials",
|
||||
"hermes_agent.cli.auth.auth.resolve_nous_runtime_credentials",
|
||||
lambda *args, **kwargs: {
|
||||
"base_url": "https://inference.example.com/v1",
|
||||
"api_key": "***",
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.auth.fetch_nous_models",
|
||||
"hermes_agent.cli.auth.auth.fetch_nous_models",
|
||||
lambda *args, **kwargs: ["claude-opus-4-6"],
|
||||
)
|
||||
monkeypatch.setattr("hermes_cli.auth._prompt_model_selection", lambda model_ids, current_model="", pricing=None, **kw: "claude-opus-4-6")
|
||||
monkeypatch.setattr("hermes_cli.auth._save_model_choice", lambda model: None)
|
||||
monkeypatch.setattr("hermes_cli.auth._update_config_for_provider", lambda provider, url: None)
|
||||
monkeypatch.setattr("hermes_agent.cli.auth.auth._prompt_model_selection", lambda model_ids, current_model="", pricing=None, **kw: "claude-opus-4-6")
|
||||
monkeypatch.setattr("hermes_agent.cli.auth.auth._save_model_choice", lambda model: None)
|
||||
monkeypatch.setattr("hermes_agent.cli.auth.auth._update_config_for_provider", lambda provider, url: None)
|
||||
hermes_main._model_flow_nous(config, current_model="claude-opus-4-6")
|
||||
|
||||
out = capsys.readouterr().out
|
||||
|
|
@ -363,11 +363,11 @@ def test_codex_provider_uses_config_model(monkeypatch):
|
|||
"source": "env/config",
|
||||
}
|
||||
|
||||
monkeypatch.setattr("hermes_cli.runtime_provider.resolve_runtime_provider", _runtime_resolve)
|
||||
monkeypatch.setattr("hermes_cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
|
||||
monkeypatch.setattr("hermes_agent.cli.runtime_provider.resolve_runtime_provider", _runtime_resolve)
|
||||
monkeypatch.setattr("hermes_agent.cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
|
||||
# Prevent live API call from overriding the config model
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.codex_models.get_codex_model_ids",
|
||||
"hermes_agent.cli.models.codex.get_codex_model_ids",
|
||||
lambda access_token=None: ["gpt-5.2-codex"],
|
||||
)
|
||||
|
||||
|
|
@ -406,11 +406,11 @@ def test_codex_config_model_not_replaced_by_normalization(monkeypatch):
|
|||
"source": "env/config",
|
||||
}
|
||||
|
||||
monkeypatch.setattr("hermes_cli.runtime_provider.resolve_runtime_provider", _runtime_resolve)
|
||||
monkeypatch.setattr("hermes_cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
|
||||
monkeypatch.setattr("hermes_agent.cli.runtime_provider.resolve_runtime_provider", _runtime_resolve)
|
||||
monkeypatch.setattr("hermes_agent.cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
|
||||
# API returns a DIFFERENT model than what the user configured
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.codex_models.get_codex_model_ids",
|
||||
"hermes_agent.cli.models.codex.get_codex_model_ids",
|
||||
lambda access_token=None: ["gpt-5.4", "gpt-5.3-codex"],
|
||||
)
|
||||
|
||||
|
|
@ -441,8 +441,8 @@ def test_codex_provider_preserves_explicit_codex_model(monkeypatch):
|
|||
"source": "env/config",
|
||||
}
|
||||
|
||||
monkeypatch.setattr("hermes_cli.runtime_provider.resolve_runtime_provider", _runtime_resolve)
|
||||
monkeypatch.setattr("hermes_cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
|
||||
monkeypatch.setattr("hermes_agent.cli.runtime_provider.resolve_runtime_provider", _runtime_resolve)
|
||||
monkeypatch.setattr("hermes_agent.cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
|
||||
|
||||
shell = cli.HermesCLI(model="gpt-5.1-codex-mini", compact=True, max_turns=1)
|
||||
|
||||
|
|
@ -468,8 +468,8 @@ def test_codex_provider_strips_provider_prefix_from_model(monkeypatch):
|
|||
"source": "env/config",
|
||||
}
|
||||
|
||||
monkeypatch.setattr("hermes_cli.runtime_provider.resolve_runtime_provider", _runtime_resolve)
|
||||
monkeypatch.setattr("hermes_cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
|
||||
monkeypatch.setattr("hermes_agent.cli.runtime_provider.resolve_runtime_provider", _runtime_resolve)
|
||||
monkeypatch.setattr("hermes_agent.cli.runtime_provider.format_runtime_provider_error", lambda exc: str(exc))
|
||||
|
||||
shell = cli.HermesCLI(model="openai/gpt-5.3-codex", compact=True, max_turns=1)
|
||||
|
||||
|
|
@ -479,19 +479,19 @@ def test_codex_provider_strips_provider_prefix_from_model(monkeypatch):
|
|||
|
||||
def test_cmd_model_falls_back_to_auto_on_invalid_provider(monkeypatch, capsys):
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.config.load_config",
|
||||
"hermes_agent.cli.config.load_config",
|
||||
lambda: {"model": {"default": "gpt-5", "provider": "invalid-provider"}},
|
||||
)
|
||||
monkeypatch.setattr("hermes_cli.config.save_config", lambda cfg: None)
|
||||
monkeypatch.setattr("hermes_cli.config.get_env_value", lambda key: "")
|
||||
monkeypatch.setattr("hermes_cli.config.save_env_value", lambda key, value: None)
|
||||
monkeypatch.setattr("hermes_agent.cli.config.save_config", lambda cfg: None)
|
||||
monkeypatch.setattr("hermes_agent.cli.config.get_env_value", lambda key: "")
|
||||
monkeypatch.setattr("hermes_agent.cli.config.save_env_value", lambda key, value: None)
|
||||
|
||||
def _resolve_provider(requested, **kwargs):
|
||||
if requested == "invalid-provider":
|
||||
raise AuthError("Unknown provider 'invalid-provider'.", code="invalid_provider")
|
||||
return "openrouter"
|
||||
|
||||
monkeypatch.setattr("hermes_cli.auth.resolve_provider", _resolve_provider)
|
||||
monkeypatch.setattr("hermes_agent.cli.auth.auth.resolve_provider", _resolve_provider)
|
||||
monkeypatch.setattr(hermes_main, "_prompt_provider_choice", lambda choices, **kwargs: len(choices) - 1)
|
||||
monkeypatch.setattr("sys.stdin", type("FakeTTY", (), {"isatty": lambda self: True})())
|
||||
|
||||
|
|
@ -505,16 +505,16 @@ def test_cmd_model_falls_back_to_auto_on_invalid_provider(monkeypatch, capsys):
|
|||
|
||||
def test_model_flow_custom_saves_verified_v1_base_url(monkeypatch, capsys):
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.config.get_env_value",
|
||||
"hermes_agent.cli.config.get_env_value",
|
||||
lambda key: "" if key in {"OPENAI_BASE_URL", "OPENAI_API_KEY"} else "",
|
||||
)
|
||||
saved_env = {}
|
||||
monkeypatch.setattr("hermes_cli.config.save_env_value", lambda key, value: saved_env.__setitem__(key, value))
|
||||
monkeypatch.setattr("hermes_cli.auth._save_model_choice", lambda model: saved_env.__setitem__("MODEL", model))
|
||||
monkeypatch.setattr("hermes_cli.auth.deactivate_provider", lambda: None)
|
||||
monkeypatch.setattr("hermes_cli.main._save_custom_provider", lambda *args, **kwargs: None)
|
||||
monkeypatch.setattr("hermes_agent.cli.config.save_env_value", lambda key, value: saved_env.__setitem__(key, value))
|
||||
monkeypatch.setattr("hermes_agent.cli.auth.auth._save_model_choice", lambda model: saved_env.__setitem__("MODEL", model))
|
||||
monkeypatch.setattr("hermes_agent.cli.auth.auth.deactivate_provider", lambda: None)
|
||||
monkeypatch.setattr("hermes_agent.cli.main._save_custom_provider", lambda *args, **kwargs: None)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.models.probe_api_models",
|
||||
"hermes_agent.cli.models.models.probe_api_models",
|
||||
lambda api_key, base_url: {
|
||||
"models": ["llm"],
|
||||
"probed_url": "http://localhost:8000/v1/models",
|
||||
|
|
@ -524,10 +524,10 @@ def test_model_flow_custom_saves_verified_v1_base_url(monkeypatch, capsys):
|
|||
},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.config.load_config",
|
||||
"hermes_agent.cli.config.load_config",
|
||||
lambda: {"model": {"default": "", "provider": "custom", "base_url": ""}},
|
||||
)
|
||||
monkeypatch.setattr("hermes_cli.config.save_config", lambda cfg: None)
|
||||
monkeypatch.setattr("hermes_agent.cli.config.save_config", lambda cfg: None)
|
||||
|
||||
# After the probe detects a single model ("llm"), the flow asks
|
||||
# "Use this model? [Y/n]:" — confirm with Enter, then context length,
|
||||
|
|
@ -549,14 +549,14 @@ def test_model_flow_custom_saves_verified_v1_base_url(monkeypatch, capsys):
|
|||
def test_cmd_model_forwards_nous_login_tls_options(monkeypatch):
|
||||
monkeypatch.setattr(hermes_main, "_require_tty", lambda *a: None)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.config.load_config",
|
||||
"hermes_agent.cli.config.load_config",
|
||||
lambda: {"model": {"default": "gpt-5", "provider": "nous"}},
|
||||
)
|
||||
monkeypatch.setattr("hermes_cli.config.save_config", lambda cfg: None)
|
||||
monkeypatch.setattr("hermes_cli.config.get_env_value", lambda key: "")
|
||||
monkeypatch.setattr("hermes_cli.config.save_env_value", lambda key, value: None)
|
||||
monkeypatch.setattr("hermes_cli.auth.resolve_provider", lambda requested, **kwargs: "nous")
|
||||
monkeypatch.setattr("hermes_cli.auth.get_provider_auth_state", lambda provider_id: None)
|
||||
monkeypatch.setattr("hermes_agent.cli.config.save_config", lambda cfg: None)
|
||||
monkeypatch.setattr("hermes_agent.cli.config.get_env_value", lambda key: "")
|
||||
monkeypatch.setattr("hermes_agent.cli.config.save_env_value", lambda key, value: None)
|
||||
monkeypatch.setattr("hermes_agent.cli.auth.auth.resolve_provider", lambda requested, **kwargs: "nous")
|
||||
monkeypatch.setattr("hermes_agent.cli.auth.auth.get_provider_auth_state", lambda provider_id: None)
|
||||
monkeypatch.setattr(hermes_main, "_prompt_provider_choice", lambda choices, **kwargs: 0)
|
||||
|
||||
captured = {}
|
||||
|
|
@ -571,7 +571,7 @@ def test_cmd_model_forwards_nous_login_tls_options(monkeypatch):
|
|||
captured["ca_bundle"] = login_args.ca_bundle
|
||||
captured["insecure"] = login_args.insecure
|
||||
|
||||
monkeypatch.setattr("hermes_cli.auth._login_nous", _fake_login)
|
||||
monkeypatch.setattr("hermes_agent.cli.auth.auth._login_nous", _fake_login)
|
||||
|
||||
hermes_main.cmd_model(
|
||||
SimpleNamespace(
|
||||
|
|
@ -603,18 +603,18 @@ def test_cmd_model_forwards_nous_login_tls_options(monkeypatch):
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_auto_provider_name_localhost():
|
||||
from hermes_cli.main import _auto_provider_name
|
||||
from hermes_agent.cli.main import _auto_provider_name
|
||||
assert _auto_provider_name("http://localhost:11434/v1") == "Local (localhost:11434)"
|
||||
assert _auto_provider_name("http://127.0.0.1:1234/v1") == "Local (127.0.0.1:1234)"
|
||||
|
||||
|
||||
def test_auto_provider_name_runpod():
|
||||
from hermes_cli.main import _auto_provider_name
|
||||
from hermes_agent.cli.main import _auto_provider_name
|
||||
assert "RunPod" in _auto_provider_name("https://xyz.runpod.io/v1")
|
||||
|
||||
|
||||
def test_auto_provider_name_remote():
|
||||
from hermes_cli.main import _auto_provider_name
|
||||
from hermes_agent.cli.main import _auto_provider_name
|
||||
result = _auto_provider_name("https://api.together.xyz/v1")
|
||||
assert result == "Api.together.xyz"
|
||||
|
||||
|
|
@ -622,18 +622,18 @@ def test_auto_provider_name_remote():
|
|||
def test_save_custom_provider_uses_provided_name(monkeypatch, tmp_path):
|
||||
"""When a display name is passed, it should appear in the saved entry."""
|
||||
import yaml
|
||||
from hermes_cli.main import _save_custom_provider
|
||||
from hermes_agent.cli.main import _save_custom_provider
|
||||
|
||||
cfg_path = tmp_path / "config.yaml"
|
||||
cfg_path.write_text(yaml.dump({}))
|
||||
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.config.load_config", lambda: yaml.safe_load(cfg_path.read_text()) or {},
|
||||
"hermes_agent.cli.config.load_config", lambda: yaml.safe_load(cfg_path.read_text()) or {},
|
||||
)
|
||||
saved = {}
|
||||
def _save(cfg):
|
||||
saved.update(cfg)
|
||||
monkeypatch.setattr("hermes_cli.config.save_config", _save)
|
||||
monkeypatch.setattr("hermes_agent.cli.config.save_config", _save)
|
||||
|
||||
_save_custom_provider("http://localhost:11434/v1", name="Ollama")
|
||||
entries = saved.get("custom_providers", [])
|
||||
|
|
|
|||
|
|
@ -21,15 +21,15 @@ class TestSaveConfigValueAtomic:
|
|||
"model": {"default": "test-model", "provider": "openrouter"},
|
||||
"display": {"skin": "default"},
|
||||
}))
|
||||
monkeypatch.setattr("cli._hermes_home", hermes_home)
|
||||
monkeypatch.setattr("hermes_agent.cli.repl._hermes_home", hermes_home)
|
||||
return config_path
|
||||
|
||||
def test_calls_atomic_yaml_write(self, config_env, monkeypatch):
|
||||
"""save_config_value must route through atomic_yaml_write, not bare open()."""
|
||||
mock_atomic = MagicMock()
|
||||
monkeypatch.setattr("utils.atomic_yaml_write", mock_atomic)
|
||||
monkeypatch.setattr("hermes_agent.utils.atomic_yaml_write", mock_atomic)
|
||||
|
||||
from cli import save_config_value
|
||||
from hermes_agent.cli.repl import save_config_value
|
||||
save_config_value("display.skin", "mono")
|
||||
|
||||
mock_atomic.assert_called_once()
|
||||
|
|
@ -39,8 +39,8 @@ class TestSaveConfigValueAtomic:
|
|||
|
||||
def test_preserves_existing_keys(self, config_env):
|
||||
"""Writing a new key must not clobber existing config entries."""
|
||||
from cli import save_config_value
|
||||
save_config_value("agent.max_turns", 50)
|
||||
from hermes_agent.cli.repl import save_config_value
|
||||
save_config_value("hermes_agent.agent.max_turns", 50)
|
||||
|
||||
result = yaml.safe_load(config_env.read_text())
|
||||
assert result["model"]["default"] == "test-model"
|
||||
|
|
@ -50,7 +50,7 @@ class TestSaveConfigValueAtomic:
|
|||
|
||||
def test_creates_nested_keys(self, config_env):
|
||||
"""Dot-separated paths create intermediate dicts as needed."""
|
||||
from cli import save_config_value
|
||||
from hermes_agent.cli.repl import save_config_value
|
||||
save_config_value("auxiliary.compression.model", "google/gemini-3-flash-preview")
|
||||
|
||||
result = yaml.safe_load(config_env.read_text())
|
||||
|
|
@ -58,7 +58,7 @@ class TestSaveConfigValueAtomic:
|
|||
|
||||
def test_overwrites_existing_value(self, config_env):
|
||||
"""Updating an existing key replaces the value."""
|
||||
from cli import save_config_value
|
||||
from hermes_agent.cli.repl import save_config_value
|
||||
save_config_value("display.skin", "ares")
|
||||
|
||||
result = yaml.safe_load(config_env.read_text())
|
||||
|
|
@ -75,7 +75,7 @@ class TestSaveConfigValueAtomic:
|
|||
"model": {"default": "test-model", "provider": "openrouter"},
|
||||
}))
|
||||
|
||||
from cli import save_config_value
|
||||
from hermes_agent.cli.repl import save_config_value
|
||||
save_config_value("model.default", "doubao-pro")
|
||||
|
||||
result = yaml.safe_load(config_env.read_text())
|
||||
|
|
@ -89,9 +89,9 @@ class TestSaveConfigValueAtomic:
|
|||
def exploding_write(*args, **kwargs):
|
||||
raise OSError("disk full")
|
||||
|
||||
monkeypatch.setattr("utils.atomic_yaml_write", exploding_write)
|
||||
monkeypatch.setattr("hermes_agent.utils.atomic_yaml_write", exploding_write)
|
||||
|
||||
from cli import save_config_value
|
||||
from hermes_agent.cli.repl import save_config_value
|
||||
result = save_config_value("display.skin", "broken")
|
||||
|
||||
assert result is False
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ import threading
|
|||
import time
|
||||
from unittest.mock import patch
|
||||
|
||||
import cli as cli_module
|
||||
import tools.skills_tool as skills_tool_module
|
||||
from cli import HermesCLI
|
||||
from hermes_cli.callbacks import prompt_for_secret
|
||||
from tools.skills_tool import set_secret_capture_callback
|
||||
import hermes_agent.cli.repl as cli_module
|
||||
import hermes_agent.tools.skills.tool as skills_tool_module
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
from hermes_agent.cli.ui.callbacks import prompt_for_secret
|
||||
from hermes_agent.tools.skills.tool import set_secret_capture_callback
|
||||
|
||||
|
||||
class _FakeBuffer:
|
||||
|
|
@ -40,7 +40,7 @@ def test_secret_capture_callback_can_be_completed_from_cli_state_machine():
|
|||
cli = _make_cli_stub(with_app=True)
|
||||
results = []
|
||||
|
||||
with patch("hermes_cli.callbacks.save_env_value_secure") as save_secret:
|
||||
with patch("hermes_agent.cli.ui.callbacks.save_env_value_secure") as save_secret:
|
||||
save_secret.return_value = {
|
||||
"success": True,
|
||||
"stored_as": "TENOR_API_KEY",
|
||||
|
|
@ -86,8 +86,8 @@ def test_cancel_secret_capture_marks_setup_skipped():
|
|||
def test_secret_capture_uses_getpass_without_tui():
|
||||
cli = _make_cli_stub()
|
||||
|
||||
with patch("hermes_cli.callbacks.getpass.getpass", return_value="secret-value"), patch(
|
||||
"hermes_cli.callbacks.save_env_value_secure"
|
||||
with patch("hermes_agent.cli.ui.callbacks.getpass.getpass", return_value="secret-value"), patch(
|
||||
"hermes_agent.cli.ui.callbacks.save_env_value_secure"
|
||||
) as save_secret:
|
||||
save_secret.return_value = {
|
||||
"success": True,
|
||||
|
|
@ -110,8 +110,8 @@ def test_secret_capture_timeout_clears_hidden_input_buffer():
|
|||
|
||||
cli._clear_secret_input_buffer = clear_buffer
|
||||
|
||||
with patch("hermes_cli.callbacks.queue.Queue.get", side_effect=queue.Empty), patch(
|
||||
"hermes_cli.callbacks._time.monotonic",
|
||||
with patch("hermes_agent.cli.ui.callbacks.queue.Queue.get", side_effect=queue.Empty), patch(
|
||||
"hermes_agent.cli.ui.callbacks._time.monotonic",
|
||||
side_effect=[0, 121],
|
||||
):
|
||||
result = prompt_for_secret(cli, "TENOR_API_KEY", "Tenor API key")
|
||||
|
|
@ -134,7 +134,7 @@ def test_cli_chat_registers_secret_capture_callback():
|
|||
"terminal": {"env_type": "local"},
|
||||
}
|
||||
|
||||
with patch("cli.get_tool_definitions", return_value=[]), patch.dict(
|
||||
with patch("hermes_agent.cli.repl.get_tool_definitions", return_value=[]), patch.dict(
|
||||
"os.environ", {"LLM_MODEL": "", "HERMES_MAX_ITERATIONS": ""}, clear=False
|
||||
), patch.dict(cli_module.__dict__, {"CLI_CONFIG": clean_config}):
|
||||
cli_obj = HermesCLI()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from cli import HermesCLI, _rich_text_from_ansi
|
||||
from hermes_cli.skin_engine import get_active_skin, set_active_skin
|
||||
from hermes_agent.cli.repl import HermesCLI, _rich_text_from_ansi
|
||||
from hermes_agent.cli.ui.skin_engine import get_active_skin, set_active_skin
|
||||
|
||||
|
||||
def _make_cli_stub():
|
||||
|
|
@ -72,7 +72,7 @@ class TestCliSkinPromptIntegration:
|
|||
cli = _make_cli_stub()
|
||||
cli._secret_state = {"response_queue": object()}
|
||||
|
||||
with patch("hermes_cli.skin_engine.get_active_prompt_symbol", return_value="⚔ "):
|
||||
with patch("hermes_agent.cli.ui.skin_engine.get_active_prompt_symbol", return_value="⚔ "):
|
||||
assert cli._get_tui_prompt_fragments() == [("class:sudo-prompt", "🔑 ⚔ ")]
|
||||
|
||||
def test_build_tui_style_dict_uses_skin_overrides(self):
|
||||
|
|
@ -98,7 +98,7 @@ class TestCliSkinPromptIntegration:
|
|||
def test_handle_skin_command_refreshes_live_tui(self, capsys):
|
||||
cli = _make_cli_stub()
|
||||
|
||||
with patch("cli.save_config_value", return_value=True):
|
||||
with patch("hermes_agent.cli.repl.save_config_value", return_value=True):
|
||||
cli._handle_skin_command("/skin ares")
|
||||
|
||||
output = capsys.readouterr().out
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from datetime import datetime, timedelta
|
|||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
|
||||
def _make_cli(model: str = "anthropic/claude-sonnet-4-20250514"):
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ from pathlib import Path
|
|||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from cli import HermesCLI
|
||||
from hermes_cli.commands import resolve_command
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
from hermes_agent.cli.commands import resolve_command
|
||||
|
||||
|
||||
def _make_cli():
|
||||
|
|
@ -70,7 +70,7 @@ def test_show_session_status_prints_gateway_style_summary():
|
|||
"started_at": 1775791440,
|
||||
}
|
||||
|
||||
with patch("cli.display_hermes_home", return_value="~/.hermes"):
|
||||
with patch("hermes_agent.cli.repl.display_hermes_home", return_value="~/.hermes"):
|
||||
cli_obj._show_session_status()
|
||||
|
||||
printed = "\n".join(str(call.args[0]) for call in cli_obj.console.print.call_args_list)
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ def _make_cli():
|
|||
with patch.dict(sys.modules, prompt_toolkit_stubs), patch.dict(
|
||||
"os.environ", clean_env, clear=False
|
||||
):
|
||||
import cli as _cli_mod
|
||||
import hermes_agent.cli.repl as _cli_mod
|
||||
|
||||
_cli_mod = importlib.reload(_cli_mod)
|
||||
with patch.object(_cli_mod, "get_tool_definitions", return_value=[]), patch.dict(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
from unittest.mock import MagicMock, patch, call
|
||||
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
|
||||
def _make_cli(enabled_toolsets=None):
|
||||
|
|
@ -39,9 +39,9 @@ class TestToolsSlashList:
|
|||
|
||||
def test_list_calls_backend(self, capsys):
|
||||
cli_obj = _make_cli()
|
||||
with patch("hermes_cli.tools_config.load_config",
|
||||
with patch("hermes_agent.cli.tools_config.load_config",
|
||||
return_value={"platform_toolsets": {"cli": ["web"]}}), \
|
||||
patch("hermes_cli.tools_config.save_config"):
|
||||
patch("hermes_agent.cli.tools_config.save_config"):
|
||||
cli_obj._handle_tools_command("/tools list")
|
||||
out = capsys.readouterr().out
|
||||
assert "web" in out
|
||||
|
|
@ -49,7 +49,7 @@ class TestToolsSlashList:
|
|||
def test_list_does_not_modify_enabled_toolsets(self):
|
||||
"""List is read-only — self.enabled_toolsets must not change."""
|
||||
cli_obj = _make_cli(["web", "memory"])
|
||||
with patch("hermes_cli.tools_config.load_config",
|
||||
with patch("hermes_agent.cli.tools_config.load_config",
|
||||
return_value={"platform_toolsets": {"cli": ["web"]}}):
|
||||
cli_obj._handle_tools_command("/tools list")
|
||||
assert cli_obj.enabled_toolsets == {"web", "memory"}
|
||||
|
|
@ -63,11 +63,11 @@ class TestToolsSlashDisableWithReset:
|
|||
def test_disable_applies_directly_and_resets_session(self):
|
||||
"""Disable applies immediately (no confirmation prompt) and resets session."""
|
||||
cli_obj = _make_cli(["web", "memory"])
|
||||
with patch("hermes_cli.tools_config.load_config",
|
||||
with patch("hermes_agent.cli.tools_config.load_config",
|
||||
return_value={"platform_toolsets": {"cli": ["web", "memory"]}}), \
|
||||
patch("hermes_cli.tools_config.save_config"), \
|
||||
patch("hermes_cli.tools_config._get_platform_tools", return_value={"memory"}), \
|
||||
patch("hermes_cli.config.load_config", return_value={}), \
|
||||
patch("hermes_agent.cli.tools_config.save_config"), \
|
||||
patch("hermes_agent.cli.tools_config._get_platform_tools", return_value={"memory"}), \
|
||||
patch("hermes_agent.cli.config.load_config", return_value={}), \
|
||||
patch.object(cli_obj, "new_session") as mock_reset:
|
||||
cli_obj._handle_tools_command("/tools disable web")
|
||||
mock_reset.assert_called_once()
|
||||
|
|
@ -76,11 +76,11 @@ class TestToolsSlashDisableWithReset:
|
|||
def test_disable_does_not_prompt_for_confirmation(self):
|
||||
"""Disable no longer uses input() — it applies directly."""
|
||||
cli_obj = _make_cli(["web", "memory"])
|
||||
with patch("hermes_cli.tools_config.load_config",
|
||||
with patch("hermes_agent.cli.tools_config.load_config",
|
||||
return_value={"platform_toolsets": {"cli": ["web", "memory"]}}), \
|
||||
patch("hermes_cli.tools_config.save_config"), \
|
||||
patch("hermes_cli.tools_config._get_platform_tools", return_value={"memory"}), \
|
||||
patch("hermes_cli.config.load_config", return_value={}), \
|
||||
patch("hermes_agent.cli.tools_config.save_config"), \
|
||||
patch("hermes_agent.cli.tools_config._get_platform_tools", return_value={"memory"}), \
|
||||
patch("hermes_agent.cli.config.load_config", return_value={}), \
|
||||
patch.object(cli_obj, "new_session"), \
|
||||
patch("builtins.input") as mock_input:
|
||||
cli_obj._handle_tools_command("/tools disable web")
|
||||
|
|
@ -89,11 +89,11 @@ class TestToolsSlashDisableWithReset:
|
|||
def test_disable_always_resets_session(self):
|
||||
"""Even without a confirmation prompt, disable always resets the session."""
|
||||
cli_obj = _make_cli(["web", "memory"])
|
||||
with patch("hermes_cli.tools_config.load_config",
|
||||
with patch("hermes_agent.cli.tools_config.load_config",
|
||||
return_value={"platform_toolsets": {"cli": ["web", "memory"]}}), \
|
||||
patch("hermes_cli.tools_config.save_config"), \
|
||||
patch("hermes_cli.tools_config._get_platform_tools", return_value={"memory"}), \
|
||||
patch("hermes_cli.config.load_config", return_value={}), \
|
||||
patch("hermes_agent.cli.tools_config.save_config"), \
|
||||
patch("hermes_agent.cli.tools_config._get_platform_tools", return_value={"memory"}), \
|
||||
patch("hermes_agent.cli.config.load_config", return_value={}), \
|
||||
patch.object(cli_obj, "new_session") as mock_reset:
|
||||
cli_obj._handle_tools_command("/tools disable web")
|
||||
mock_reset.assert_called_once()
|
||||
|
|
@ -113,11 +113,11 @@ class TestToolsSlashEnableWithReset:
|
|||
def test_enable_applies_directly_and_resets_session(self):
|
||||
"""Enable applies immediately (no confirmation prompt) and resets session."""
|
||||
cli_obj = _make_cli(["memory"])
|
||||
with patch("hermes_cli.tools_config.load_config",
|
||||
with patch("hermes_agent.cli.tools_config.load_config",
|
||||
return_value={"platform_toolsets": {"cli": ["memory"]}}), \
|
||||
patch("hermes_cli.tools_config.save_config"), \
|
||||
patch("hermes_cli.tools_config._get_platform_tools", return_value={"memory", "web"}), \
|
||||
patch("hermes_cli.config.load_config", return_value={}), \
|
||||
patch("hermes_agent.cli.tools_config.save_config"), \
|
||||
patch("hermes_agent.cli.tools_config._get_platform_tools", return_value={"memory", "web"}), \
|
||||
patch("hermes_agent.cli.config.load_config", return_value={}), \
|
||||
patch.object(cli_obj, "new_session") as mock_reset:
|
||||
cli_obj._handle_tools_command("/tools enable web")
|
||||
mock_reset.assert_called_once()
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ import os
|
|||
import sys
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
|
||||
_cli_mod = None
|
||||
|
||||
|
|
@ -44,7 +42,7 @@ def _make_cli(user_message_preview=None):
|
|||
"prompt_toolkit.auto_suggest": MagicMock(),
|
||||
}
|
||||
with patch.dict(sys.modules, prompt_toolkit_stubs), patch.dict("os.environ", clean_env, clear=False):
|
||||
import cli as mod
|
||||
import hermes_agent.cli.repl as mod
|
||||
|
||||
mod = importlib.reload(mod)
|
||||
_cli_mod = mod
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ def test_focus_topic_extracted_and_passed(capsys):
|
|||
return 100
|
||||
return 50
|
||||
|
||||
with patch("agent.model_metadata.estimate_messages_tokens_rough", side_effect=_estimate):
|
||||
with patch("hermes_agent.providers.metadata.estimate_messages_tokens_rough", side_effect=_estimate):
|
||||
shell._manual_compress("/compress database schema")
|
||||
|
||||
output = capsys.readouterr().out
|
||||
|
|
@ -55,7 +55,7 @@ def test_no_focus_topic_when_bare_command(capsys):
|
|||
shell.agent._cached_system_prompt = ""
|
||||
shell.agent._compress_context.return_value = (list(history), "")
|
||||
|
||||
with patch("agent.model_metadata.estimate_messages_tokens_rough", return_value=100):
|
||||
with patch("hermes_agent.providers.metadata.estimate_messages_tokens_rough", return_value=100):
|
||||
shell._manual_compress("/compress")
|
||||
|
||||
shell.agent._compress_context.assert_called_once()
|
||||
|
|
@ -73,7 +73,7 @@ def test_empty_focus_after_command_treated_as_none(capsys):
|
|||
shell.agent._cached_system_prompt = ""
|
||||
shell.agent._compress_context.return_value = (list(history), "")
|
||||
|
||||
with patch("agent.model_metadata.estimate_messages_tokens_rough", return_value=100):
|
||||
with patch("hermes_agent.providers.metadata.estimate_messages_tokens_rough", return_value=100):
|
||||
shell._manual_compress("/compress ")
|
||||
|
||||
shell.agent._compress_context.assert_called_once()
|
||||
|
|
@ -92,7 +92,7 @@ def test_focus_topic_printed_in_compression_banner(capsys):
|
|||
shell.agent._cached_system_prompt = ""
|
||||
shell.agent._compress_context.return_value = (compressed, "")
|
||||
|
||||
with patch("agent.model_metadata.estimate_messages_tokens_rough", return_value=100):
|
||||
with patch("hermes_agent.providers.metadata.estimate_messages_tokens_rough", return_value=100):
|
||||
shell._manual_compress("/compress API endpoints")
|
||||
|
||||
output = capsys.readouterr().out
|
||||
|
|
@ -110,7 +110,7 @@ def test_no_focus_prints_standard_banner(capsys):
|
|||
shell.agent._cached_system_prompt = ""
|
||||
shell.agent._compress_context.return_value = (compressed, "")
|
||||
|
||||
with patch("agent.model_metadata.estimate_messages_tokens_rough", return_value=100):
|
||||
with patch("hermes_agent.providers.metadata.estimate_messages_tokens_rough", return_value=100):
|
||||
shell._manual_compress("/compress")
|
||||
|
||||
output = capsys.readouterr().out
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from unittest.mock import MagicMock, patch
|
|||
|
||||
|
||||
def _import_cli():
|
||||
import hermes_cli.config as config_mod
|
||||
import hermes_agent.cli.config as config_mod
|
||||
|
||||
if not hasattr(config_mod, "save_env_value_secure"):
|
||||
config_mod.save_env_value_secure = lambda key, value: {
|
||||
|
|
@ -15,7 +15,7 @@ def _import_cli():
|
|||
"validated": False,
|
||||
}
|
||||
|
||||
import cli as cli_mod
|
||||
import hermes_agent.cli.repl as cli_mod
|
||||
|
||||
return cli_mod
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ class TestHandleFastCommand(unittest.TestCase):
|
|||
):
|
||||
cli_mod.HermesCLI._handle_fast_command(stub, "/fast normal")
|
||||
|
||||
mock_save.assert_called_once_with("agent.service_tier", "normal")
|
||||
mock_save.assert_called_once_with("hermes_agent.agent.service_tier", "normal")
|
||||
self.assertIsNone(stub.service_tier)
|
||||
self.assertIsNone(stub.agent)
|
||||
|
||||
|
|
@ -112,7 +112,7 @@ class TestPriorityProcessingModels(unittest.TestCase):
|
|||
"""Verify the expanded Priority Processing model registry."""
|
||||
|
||||
def test_all_documented_models_supported(self):
|
||||
from hermes_cli.models import model_supports_fast_mode
|
||||
from hermes_agent.cli.models.models import model_supports_fast_mode
|
||||
|
||||
# All models from OpenAI's Priority Processing pricing table
|
||||
supported = [
|
||||
|
|
@ -126,14 +126,14 @@ class TestPriorityProcessingModels(unittest.TestCase):
|
|||
assert model_supports_fast_mode(model), f"{model} should support fast mode"
|
||||
|
||||
def test_vendor_prefix_stripped(self):
|
||||
from hermes_cli.models import model_supports_fast_mode
|
||||
from hermes_agent.cli.models.models import model_supports_fast_mode
|
||||
|
||||
assert model_supports_fast_mode("openai/gpt-5.4") is True
|
||||
assert model_supports_fast_mode("openai/gpt-4.1") is True
|
||||
assert model_supports_fast_mode("openai/o3") is True
|
||||
|
||||
def test_non_priority_models_rejected(self):
|
||||
from hermes_cli.models import model_supports_fast_mode
|
||||
from hermes_agent.cli.models.models import model_supports_fast_mode
|
||||
|
||||
assert model_supports_fast_mode("gpt-5.3-codex") is False
|
||||
assert model_supports_fast_mode("claude-sonnet-4") is False
|
||||
|
|
@ -141,7 +141,7 @@ class TestPriorityProcessingModels(unittest.TestCase):
|
|||
assert model_supports_fast_mode(None) is False
|
||||
|
||||
def test_resolve_overrides_returns_service_tier(self):
|
||||
from hermes_cli.models import resolve_fast_mode_overrides
|
||||
from hermes_agent.cli.models.models import resolve_fast_mode_overrides
|
||||
|
||||
result = resolve_fast_mode_overrides("gpt-5.4")
|
||||
assert result == {"service_tier": "priority"}
|
||||
|
|
@ -150,7 +150,7 @@ class TestPriorityProcessingModels(unittest.TestCase):
|
|||
assert result == {"service_tier": "priority"}
|
||||
|
||||
def test_resolve_overrides_none_for_unsupported(self):
|
||||
from hermes_cli.models import resolve_fast_mode_overrides
|
||||
from hermes_agent.cli.models.models import resolve_fast_mode_overrides
|
||||
|
||||
assert resolve_fast_mode_overrides("gpt-5.3-codex") is None
|
||||
assert resolve_fast_mode_overrides("claude-sonnet-4") is None
|
||||
|
|
@ -218,7 +218,7 @@ class TestAnthropicFastMode(unittest.TestCase):
|
|||
"""Verify Anthropic Fast Mode model support and override resolution."""
|
||||
|
||||
def test_anthropic_opus_supported(self):
|
||||
from hermes_cli.models import model_supports_fast_mode
|
||||
from hermes_agent.cli.models.models import model_supports_fast_mode
|
||||
|
||||
# Native Anthropic format (hyphens)
|
||||
assert model_supports_fast_mode("claude-opus-4-6") is True
|
||||
|
|
@ -229,7 +229,7 @@ class TestAnthropicFastMode(unittest.TestCase):
|
|||
assert model_supports_fast_mode("anthropic/claude-opus-4.6") is True
|
||||
|
||||
def test_anthropic_non_opus_rejected(self):
|
||||
from hermes_cli.models import model_supports_fast_mode
|
||||
from hermes_agent.cli.models.models import model_supports_fast_mode
|
||||
|
||||
assert model_supports_fast_mode("claude-sonnet-4-6") is False
|
||||
assert model_supports_fast_mode("claude-sonnet-4.6") is False
|
||||
|
|
@ -237,14 +237,14 @@ class TestAnthropicFastMode(unittest.TestCase):
|
|||
assert model_supports_fast_mode("anthropic/claude-sonnet-4.6") is False
|
||||
|
||||
def test_anthropic_variant_tags_stripped(self):
|
||||
from hermes_cli.models import model_supports_fast_mode
|
||||
from hermes_agent.cli.models.models import model_supports_fast_mode
|
||||
|
||||
# OpenRouter variant tags after colon should be stripped
|
||||
assert model_supports_fast_mode("claude-opus-4.6:fast") is True
|
||||
assert model_supports_fast_mode("claude-opus-4.6:beta") is True
|
||||
|
||||
def test_resolve_overrides_returns_speed_for_anthropic(self):
|
||||
from hermes_cli.models import resolve_fast_mode_overrides
|
||||
from hermes_agent.cli.models.models import resolve_fast_mode_overrides
|
||||
|
||||
result = resolve_fast_mode_overrides("claude-opus-4-6")
|
||||
assert result == {"speed": "fast"}
|
||||
|
|
@ -254,13 +254,13 @@ class TestAnthropicFastMode(unittest.TestCase):
|
|||
|
||||
def test_resolve_overrides_returns_service_tier_for_openai(self):
|
||||
"""OpenAI models should still get service_tier, not speed."""
|
||||
from hermes_cli.models import resolve_fast_mode_overrides
|
||||
from hermes_agent.cli.models.models import resolve_fast_mode_overrides
|
||||
|
||||
result = resolve_fast_mode_overrides("gpt-5.4")
|
||||
assert result == {"service_tier": "priority"}
|
||||
|
||||
def test_is_anthropic_fast_model(self):
|
||||
from hermes_cli.models import _is_anthropic_fast_model
|
||||
from hermes_agent.cli.models.models import _is_anthropic_fast_model
|
||||
|
||||
assert _is_anthropic_fast_model("claude-opus-4-6") is True
|
||||
assert _is_anthropic_fast_model("claude-opus-4.6") is True
|
||||
|
|
@ -309,7 +309,7 @@ class TestAnthropicFastModeAdapter(unittest.TestCase):
|
|||
"""Verify build_anthropic_kwargs handles fast_mode parameter."""
|
||||
|
||||
def test_fast_mode_adds_speed_and_beta(self):
|
||||
from agent.anthropic_adapter import build_anthropic_kwargs, _FAST_MODE_BETA
|
||||
from hermes_agent.providers.anthropic_adapter import build_anthropic_kwargs, _FAST_MODE_BETA
|
||||
|
||||
kwargs = build_anthropic_kwargs(
|
||||
model="claude-opus-4-6",
|
||||
|
|
@ -325,7 +325,7 @@ class TestAnthropicFastModeAdapter(unittest.TestCase):
|
|||
assert _FAST_MODE_BETA in kwargs["extra_headers"].get("anthropic-beta", "")
|
||||
|
||||
def test_fast_mode_off_no_speed(self):
|
||||
from agent.anthropic_adapter import build_anthropic_kwargs
|
||||
from hermes_agent.providers.anthropic_adapter import build_anthropic_kwargs
|
||||
|
||||
kwargs = build_anthropic_kwargs(
|
||||
model="claude-opus-4-6",
|
||||
|
|
@ -340,7 +340,7 @@ class TestAnthropicFastModeAdapter(unittest.TestCase):
|
|||
assert "extra_headers" not in kwargs
|
||||
|
||||
def test_fast_mode_skipped_for_third_party_endpoint(self):
|
||||
from agent.anthropic_adapter import build_anthropic_kwargs
|
||||
from hermes_agent.providers.anthropic_adapter import build_anthropic_kwargs
|
||||
|
||||
kwargs = build_anthropic_kwargs(
|
||||
model="claude-opus-4-6",
|
||||
|
|
@ -357,7 +357,7 @@ class TestAnthropicFastModeAdapter(unittest.TestCase):
|
|||
assert "extra_headers" not in kwargs
|
||||
|
||||
def test_fast_mode_kwargs_are_safe_for_sdk_unpacking(self):
|
||||
from agent.anthropic_adapter import build_anthropic_kwargs
|
||||
from hermes_agent.providers.anthropic_adapter import build_anthropic_kwargs
|
||||
|
||||
kwargs = build_anthropic_kwargs(
|
||||
model="claude-opus-4-6",
|
||||
|
|
@ -373,7 +373,7 @@ class TestAnthropicFastModeAdapter(unittest.TestCase):
|
|||
|
||||
class TestConfigDefault(unittest.TestCase):
|
||||
def test_default_config_has_service_tier(self):
|
||||
from hermes_cli.config import DEFAULT_CONFIG
|
||||
from hermes_agent.cli.config import DEFAULT_CONFIG
|
||||
|
||||
agent = DEFAULT_CONFIG.get("agent", {})
|
||||
self.assertIn("service_tier", agent)
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ from unittest.mock import MagicMock, patch
|
|||
|
||||
|
||||
def test_gquota_uses_chat_console_when_tui_is_live():
|
||||
from agent.google_oauth import GoogleOAuthError
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.providers.google_oauth import GoogleOAuthError
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
cli = HermesCLI.__new__(HermesCLI)
|
||||
cli.console = MagicMock()
|
||||
|
|
@ -11,10 +11,10 @@ def test_gquota_uses_chat_console_when_tui_is_live():
|
|||
|
||||
live_console = MagicMock()
|
||||
|
||||
with patch("cli.ChatConsole", return_value=live_console), \
|
||||
patch("agent.google_oauth.get_valid_access_token", side_effect=GoogleOAuthError("No Google OAuth credentials found")), \
|
||||
patch("agent.google_oauth.load_credentials", return_value=None), \
|
||||
patch("agent.google_code_assist.retrieve_user_quota"):
|
||||
with patch("hermes_agent.cli.repl.ChatConsole", return_value=live_console), \
|
||||
patch("hermes_agent.providers.google_oauth.get_valid_access_token", side_effect=GoogleOAuthError("No Google OAuth credentials found")), \
|
||||
patch("hermes_agent.providers.google_oauth.load_credentials", return_value=None), \
|
||||
patch("hermes_agent.agent.google_code_assist.retrieve_user_quota"):
|
||||
cli._handle_gquota_command("/gquota")
|
||||
|
||||
assert live_console.print.call_count == 2
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ def test_manual_compress_reports_noop_without_success_banner(capsys):
|
|||
assert messages == history
|
||||
return 100
|
||||
|
||||
with patch("agent.model_metadata.estimate_messages_tokens_rough", side_effect=_estimate):
|
||||
with patch("hermes_agent.providers.metadata.estimate_messages_tokens_rough", side_effect=_estimate):
|
||||
shell._manual_compress()
|
||||
|
||||
output = capsys.readouterr().out
|
||||
|
|
@ -59,7 +59,7 @@ def test_manual_compress_explains_when_token_estimate_rises(capsys):
|
|||
return 120
|
||||
raise AssertionError(f"unexpected transcript: {messages!r}")
|
||||
|
||||
with patch("agent.model_metadata.estimate_messages_tokens_rough", side_effect=_estimate):
|
||||
with patch("hermes_agent.providers.metadata.estimate_messages_tokens_rough", side_effect=_estimate):
|
||||
shell._manual_compress()
|
||||
|
||||
output = capsys.readouterr().out
|
||||
|
|
@ -97,7 +97,7 @@ def test_manual_compress_syncs_session_id_after_split():
|
|||
shell.agent.session_id = old_id # starts in sync
|
||||
shell._pending_title = "stale title"
|
||||
|
||||
with patch("agent.model_metadata.estimate_messages_tokens_rough", return_value=100):
|
||||
with patch("hermes_agent.providers.metadata.estimate_messages_tokens_rough", return_value=100):
|
||||
shell._manual_compress()
|
||||
|
||||
# CLI session_id must now point at the continuation child, not the parent.
|
||||
|
|
@ -122,7 +122,7 @@ def test_manual_compress_no_sync_when_session_id_unchanged():
|
|||
shell.agent._compress_context.return_value = (list(history), "")
|
||||
shell._pending_title = "keep me"
|
||||
|
||||
with patch("agent.model_metadata.estimate_messages_tokens_rough", return_value=100):
|
||||
with patch("hermes_agent.providers.metadata.estimate_messages_tokens_rough", return_value=100):
|
||||
shell._manual_compress()
|
||||
|
||||
# No split → pending title untouched.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import yaml
|
|||
class TestCLIPersonalityNone:
|
||||
|
||||
def _make_cli(self, personalities=None):
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
cli = HermesCLI.__new__(HermesCLI)
|
||||
cli.personalities = personalities or {
|
||||
"helpful": "You are helpful.",
|
||||
|
|
@ -22,37 +22,37 @@ class TestCLIPersonalityNone:
|
|||
|
||||
def test_none_clears_system_prompt(self):
|
||||
cli = self._make_cli()
|
||||
with patch("cli.save_config_value", return_value=True):
|
||||
with patch("hermes_agent.cli.repl.save_config_value", return_value=True):
|
||||
cli._handle_personality_command("/personality none")
|
||||
assert cli.system_prompt == ""
|
||||
|
||||
def test_default_clears_system_prompt(self):
|
||||
cli = self._make_cli()
|
||||
with patch("cli.save_config_value", return_value=True):
|
||||
with patch("hermes_agent.cli.repl.save_config_value", return_value=True):
|
||||
cli._handle_personality_command("/personality default")
|
||||
assert cli.system_prompt == ""
|
||||
|
||||
def test_neutral_clears_system_prompt(self):
|
||||
cli = self._make_cli()
|
||||
with patch("cli.save_config_value", return_value=True):
|
||||
with patch("hermes_agent.cli.repl.save_config_value", return_value=True):
|
||||
cli._handle_personality_command("/personality neutral")
|
||||
assert cli.system_prompt == ""
|
||||
|
||||
def test_none_forces_agent_reinit(self):
|
||||
cli = self._make_cli()
|
||||
with patch("cli.save_config_value", return_value=True):
|
||||
with patch("hermes_agent.cli.repl.save_config_value", return_value=True):
|
||||
cli._handle_personality_command("/personality none")
|
||||
assert cli.agent is None
|
||||
|
||||
def test_none_saves_to_config(self):
|
||||
cli = self._make_cli()
|
||||
with patch("cli.save_config_value", return_value=True) as mock_save:
|
||||
with patch("hermes_agent.cli.repl.save_config_value", return_value=True) as mock_save:
|
||||
cli._handle_personality_command("/personality none")
|
||||
mock_save.assert_called_once_with("agent.system_prompt", "")
|
||||
mock_save.assert_called_once_with("hermes_agent.agent.system_prompt", "")
|
||||
|
||||
def test_known_personality_still_works(self):
|
||||
cli = self._make_cli()
|
||||
with patch("cli.save_config_value", return_value=True):
|
||||
with patch("hermes_agent.cli.repl.save_config_value", return_value=True):
|
||||
cli._handle_personality_command("/personality helpful")
|
||||
assert cli.system_prompt == "You are helpful."
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ class TestGatewayPersonalityNone:
|
|||
return event
|
||||
|
||||
def _make_runner(self, personalities=None):
|
||||
from gateway.run import GatewayRunner
|
||||
from hermes_agent.gateway.run import GatewayRunner
|
||||
runner = GatewayRunner.__new__(GatewayRunner)
|
||||
runner._ephemeral_system_prompt = "You are kawaii~"
|
||||
runner.config = {
|
||||
|
|
@ -98,7 +98,7 @@ class TestGatewayPersonalityNone:
|
|||
config_file = tmp_path / "config.yaml"
|
||||
config_file.write_text(yaml.dump(config_data))
|
||||
|
||||
with patch("gateway.run._hermes_home", tmp_path):
|
||||
with patch("hermes_agent.gateway.run._hermes_home", tmp_path):
|
||||
event = self._make_event("none")
|
||||
result = await runner._handle_personality_command(event)
|
||||
|
||||
|
|
@ -112,7 +112,7 @@ class TestGatewayPersonalityNone:
|
|||
config_file = tmp_path / "config.yaml"
|
||||
config_file.write_text(yaml.dump(config_data))
|
||||
|
||||
with patch("gateway.run._hermes_home", tmp_path):
|
||||
with patch("hermes_agent.gateway.run._hermes_home", tmp_path):
|
||||
event = self._make_event("default")
|
||||
result = await runner._handle_personality_command(event)
|
||||
|
||||
|
|
@ -125,7 +125,7 @@ class TestGatewayPersonalityNone:
|
|||
config_file = tmp_path / "config.yaml"
|
||||
config_file.write_text(yaml.dump(config_data))
|
||||
|
||||
with patch("gateway.run._hermes_home", tmp_path):
|
||||
with patch("hermes_agent.gateway.run._hermes_home", tmp_path):
|
||||
event = self._make_event("")
|
||||
result = await runner._handle_personality_command(event)
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ class TestGatewayPersonalityNone:
|
|||
config_file = tmp_path / "config.yaml"
|
||||
config_file.write_text(yaml.dump(config_data))
|
||||
|
||||
with patch("gateway.run._hermes_home", tmp_path):
|
||||
with patch("hermes_agent.gateway.run._hermes_home", tmp_path):
|
||||
event = self._make_event("nonexistent")
|
||||
result = await runner._handle_personality_command(event)
|
||||
|
||||
|
|
@ -149,8 +149,8 @@ class TestGatewayPersonalityNone:
|
|||
runner = self._make_runner(personalities={})
|
||||
(tmp_path / "config.yaml").write_text(yaml.dump({"agent": {"personalities": {}}}))
|
||||
|
||||
with patch("gateway.run._hermes_home", tmp_path), \
|
||||
patch("hermes_constants.display_hermes_home", return_value="~/.hermes/profiles/coder"):
|
||||
with patch("hermes_agent.gateway.run._hermes_home", tmp_path), \
|
||||
patch("hermes_agent.constants.display_hermes_home", return_value="~/.hermes/profiles/coder"):
|
||||
event = self._make_event("")
|
||||
result = await runner._handle_personality_command(event)
|
||||
|
||||
|
|
@ -161,7 +161,7 @@ class TestPersonalityDictFormat:
|
|||
"""Test dict-format custom personalities with description, tone, style."""
|
||||
|
||||
def _make_cli(self, personalities):
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
cli = HermesCLI.__new__(HermesCLI)
|
||||
cli.personalities = personalities
|
||||
cli.system_prompt = ""
|
||||
|
|
@ -178,7 +178,7 @@ class TestPersonalityDictFormat:
|
|||
"style": "concise",
|
||||
}
|
||||
})
|
||||
with patch("cli.save_config_value", return_value=True):
|
||||
with patch("hermes_agent.cli.repl.save_config_value", return_value=True):
|
||||
cli._handle_personality_command("/personality coder")
|
||||
assert "You are an expert programmer." in cli.system_prompt
|
||||
|
||||
|
|
@ -189,7 +189,7 @@ class TestPersonalityDictFormat:
|
|||
"tone": "technical and precise",
|
||||
}
|
||||
})
|
||||
with patch("cli.save_config_value", return_value=True):
|
||||
with patch("hermes_agent.cli.repl.save_config_value", return_value=True):
|
||||
cli._handle_personality_command("/personality coder")
|
||||
assert "Tone: technical and precise" in cli.system_prompt
|
||||
|
||||
|
|
@ -200,18 +200,18 @@ class TestPersonalityDictFormat:
|
|||
"style": "use code examples",
|
||||
}
|
||||
})
|
||||
with patch("cli.save_config_value", return_value=True):
|
||||
with patch("hermes_agent.cli.repl.save_config_value", return_value=True):
|
||||
cli._handle_personality_command("/personality coder")
|
||||
assert "Style: use code examples" in cli.system_prompt
|
||||
|
||||
def test_string_personality_still_works(self):
|
||||
cli = self._make_cli({"helper": "You are helpful."})
|
||||
with patch("cli.save_config_value", return_value=True):
|
||||
with patch("hermes_agent.cli.repl.save_config_value", return_value=True):
|
||||
cli._handle_personality_command("/personality helper")
|
||||
assert cli.system_prompt == "You are helpful."
|
||||
|
||||
def test_resolve_prompt_dict_no_tone_no_style(self):
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
result = HermesCLI._resolve_personality_prompt({
|
||||
"description": "A helper",
|
||||
"system_prompt": "You are helpful.",
|
||||
|
|
@ -219,6 +219,6 @@ class TestPersonalityDictFormat:
|
|||
assert result == "You are helpful."
|
||||
|
||||
def test_resolve_prompt_string(self):
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
result = HermesCLI._resolve_personality_prompt("You are helpful.")
|
||||
assert result == "You are helpful."
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class TestCLIQuickCommands:
|
|||
return str(call_arg)
|
||||
|
||||
def _make_cli(self, quick_commands):
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
cli = HermesCLI.__new__(HermesCLI)
|
||||
cli.config = {"quick_commands": quick_commands}
|
||||
cli.console = MagicMock()
|
||||
|
|
@ -38,7 +38,7 @@ class TestCLIQuickCommands:
|
|||
cli._app = object()
|
||||
live_console = MagicMock()
|
||||
|
||||
with patch("cli.ChatConsole", return_value=live_console):
|
||||
with patch("hermes_agent.cli.repl.ChatConsole", return_value=live_console):
|
||||
result = cli.process_command("/dn")
|
||||
|
||||
assert result is True
|
||||
|
|
@ -100,7 +100,7 @@ class TestCLIQuickCommands:
|
|||
def test_quick_command_takes_priority_over_skill_commands(self):
|
||||
"""Quick commands must be checked before skill slash commands."""
|
||||
cli = self._make_cli({"mygif": {"type": "exec", "command": "echo overridden"}})
|
||||
with patch("cli._skill_commands", {"/mygif": {"name": "gif-search"}}):
|
||||
with patch("hermes_agent.cli.repl._skill_commands", {"/mygif": {"name": "gif-search"}}):
|
||||
cli.process_command("/mygif")
|
||||
cli.console.print.assert_called_once()
|
||||
printed = self._printed_plain(cli.console.print.call_args[0][0])
|
||||
|
|
@ -108,7 +108,7 @@ class TestCLIQuickCommands:
|
|||
|
||||
def test_unknown_command_still_shows_error(self):
|
||||
cli = self._make_cli({})
|
||||
with patch("cli._cprint") as mock_cprint:
|
||||
with patch("hermes_agent.cli.repl._cprint") as mock_cprint:
|
||||
cli.process_command("/nonexistent")
|
||||
mock_cprint.assert_called()
|
||||
printed = " ".join(str(c) for c in mock_cprint.call_args_list)
|
||||
|
|
@ -143,7 +143,7 @@ class TestGatewayQuickCommands:
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_exec_command_returns_output(self):
|
||||
from gateway.run import GatewayRunner
|
||||
from hermes_agent.gateway.run import GatewayRunner
|
||||
runner = GatewayRunner.__new__(GatewayRunner)
|
||||
runner.config = {"quick_commands": {"limits": {"type": "exec", "command": "echo ok"}}}
|
||||
runner._running_agents = {}
|
||||
|
|
@ -156,7 +156,7 @@ class TestGatewayQuickCommands:
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unsupported_type_returns_error(self):
|
||||
from gateway.run import GatewayRunner
|
||||
from hermes_agent.gateway.run import GatewayRunner
|
||||
runner = GatewayRunner.__new__(GatewayRunner)
|
||||
runner.config = {"quick_commands": {"bad": {"type": "prompt", "command": "echo hi"}}}
|
||||
runner._running_agents = {}
|
||||
|
|
@ -170,7 +170,7 @@ class TestGatewayQuickCommands:
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_timeout_returns_error(self):
|
||||
from gateway.run import GatewayRunner
|
||||
from hermes_agent.gateway.run import GatewayRunner
|
||||
import asyncio
|
||||
runner = GatewayRunner.__new__(GatewayRunner)
|
||||
runner.config = {"quick_commands": {"slow": {"type": "exec", "command": "sleep 100"}}}
|
||||
|
|
@ -186,8 +186,8 @@ class TestGatewayQuickCommands:
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_gateway_config_object_supports_quick_commands(self):
|
||||
from gateway.config import GatewayConfig
|
||||
from gateway.run import GatewayRunner
|
||||
from hermes_agent.gateway.config import GatewayConfig
|
||||
from hermes_agent.gateway.run import GatewayRunner
|
||||
|
||||
runner = GatewayRunner.__new__(GatewayRunner)
|
||||
runner.config = GatewayConfig(
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ class TestParseReasoningConfig(unittest.TestCase):
|
|||
"""Verify _parse_reasoning_config handles all effort levels."""
|
||||
|
||||
def _parse(self, effort):
|
||||
from cli import _parse_reasoning_config
|
||||
from hermes_agent.cli.repl import _parse_reasoning_config
|
||||
return _parse_reasoning_config(effort)
|
||||
|
||||
def test_none_disables(self):
|
||||
|
|
@ -101,7 +101,7 @@ class TestHandleReasoningCommand(unittest.TestCase):
|
|||
|
||||
def test_effort_level_sets_config(self):
|
||||
"""Setting an effort level should update reasoning_config."""
|
||||
from cli import _parse_reasoning_config
|
||||
from hermes_agent.cli.repl import _parse_reasoning_config
|
||||
stub = self._make_cli()
|
||||
arg = "high"
|
||||
parsed = _parse_reasoning_config(arg)
|
||||
|
|
@ -109,7 +109,7 @@ class TestHandleReasoningCommand(unittest.TestCase):
|
|||
self.assertEqual(stub.reasoning_config, {"enabled": True, "effort": "high"})
|
||||
|
||||
def test_effort_none_disables_reasoning(self):
|
||||
from cli import _parse_reasoning_config
|
||||
from hermes_agent.cli.repl import _parse_reasoning_config
|
||||
stub = self._make_cli()
|
||||
parsed = _parse_reasoning_config("none")
|
||||
stub.reasoning_config = parsed
|
||||
|
|
@ -117,7 +117,7 @@ class TestHandleReasoningCommand(unittest.TestCase):
|
|||
|
||||
def test_invalid_argument_rejected(self):
|
||||
"""Invalid arguments should be rejected (parsed returns None)."""
|
||||
from cli import _parse_reasoning_config
|
||||
from hermes_agent.cli.repl import _parse_reasoning_config
|
||||
parsed = _parse_reasoning_config("turbo")
|
||||
self.assertIsNone(parsed)
|
||||
|
||||
|
|
@ -298,7 +298,7 @@ class TestReasoningCallback(unittest.TestCase):
|
|||
|
||||
class TestReasoningPreviewBuffering(unittest.TestCase):
|
||||
def _make_cli(self):
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
cli = HermesCLI.__new__(HermesCLI)
|
||||
cli.verbose = True
|
||||
|
|
@ -307,7 +307,7 @@ class TestReasoningPreviewBuffering(unittest.TestCase):
|
|||
cli._invalidate = lambda *args, **kwargs: None
|
||||
return cli
|
||||
|
||||
@patch("cli._cprint")
|
||||
@patch("hermes_agent.cli.repl._cprint")
|
||||
def test_streamed_reasoning_chunks_wait_for_boundary(self, mock_cprint):
|
||||
cli = self._make_cli()
|
||||
|
||||
|
|
@ -323,7 +323,7 @@ class TestReasoningPreviewBuffering(unittest.TestCase):
|
|||
rendered = mock_cprint.call_args[0][0]
|
||||
self.assertIn("[thinking] Let me think about this.", rendered)
|
||||
|
||||
@patch("cli._cprint")
|
||||
@patch("hermes_agent.cli.repl._cprint")
|
||||
def test_pending_reasoning_flushes_when_thinking_stops(self, mock_cprint):
|
||||
cli = self._make_cli()
|
||||
|
||||
|
|
@ -341,8 +341,8 @@ class TestReasoningPreviewBuffering(unittest.TestCase):
|
|||
rendered = mock_cprint.call_args[0][0]
|
||||
self.assertIn("[thinking] see how this plays out", rendered)
|
||||
|
||||
@patch("cli._cprint")
|
||||
@patch("cli.shutil.get_terminal_size", return_value=SimpleNamespace(columns=50))
|
||||
@patch("hermes_agent.cli.repl._cprint")
|
||||
@patch("hermes_agent.cli.repl.shutil.get_terminal_size", return_value=SimpleNamespace(columns=50))
|
||||
def test_reasoning_preview_compacts_newlines_and_wraps_to_terminal(self, _mock_term, mock_cprint):
|
||||
cli = self._make_cli()
|
||||
|
||||
|
|
@ -357,7 +357,7 @@ class TestReasoningPreviewBuffering(unittest.TestCase):
|
|||
self.assertIn("Second paragraph with more detail here.", normalized)
|
||||
self.assertNotIn("\n\n\n", plain)
|
||||
|
||||
@patch("cli.shutil.get_terminal_size", return_value=SimpleNamespace(columns=60))
|
||||
@patch("hermes_agent.cli.repl.shutil.get_terminal_size", return_value=SimpleNamespace(columns=60))
|
||||
def test_reasoning_flush_threshold_tracks_terminal_width(self, _mock_term):
|
||||
cli = self._make_cli()
|
||||
|
||||
|
|
@ -368,7 +368,7 @@ class TestReasoningPreviewBuffering(unittest.TestCase):
|
|||
|
||||
class TestReasoningDisplayModeSelection(unittest.TestCase):
|
||||
def _make_cli(self, *, show_reasoning=False, streaming_enabled=False, verbose=False):
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
cli = HermesCLI.__new__(HermesCLI)
|
||||
cli.show_reasoning = show_reasoning
|
||||
|
|
@ -406,7 +406,7 @@ class TestExtractReasoningFormats(unittest.TestCase):
|
|||
"""Test _extract_reasoning with real provider response formats."""
|
||||
|
||||
def _get_extractor(self):
|
||||
from run_agent import AIAgent
|
||||
from hermes_agent.agent.loop import AIAgent
|
||||
return AIAgent._extract_reasoning
|
||||
|
||||
def test_openrouter_reasoning_details(self):
|
||||
|
|
@ -466,7 +466,7 @@ class TestInlineThinkBlockExtraction(unittest.TestCase):
|
|||
|
||||
def _make_agent(self):
|
||||
"""Create a minimal agent with _build_assistant_message."""
|
||||
from run_agent import AIAgent
|
||||
from hermes_agent.agent.loop import AIAgent
|
||||
agent = MagicMock(spec=AIAgent)
|
||||
agent._build_assistant_message = AIAgent._build_assistant_message.__get__(agent)
|
||||
agent._extract_reasoning = AIAgent._extract_reasoning.__get__(agent)
|
||||
|
|
@ -539,7 +539,7 @@ class TestConfigDefault(unittest.TestCase):
|
|||
"""Verify config default for show_reasoning."""
|
||||
|
||||
def test_default_config_has_show_reasoning(self):
|
||||
from hermes_cli.config import DEFAULT_CONFIG
|
||||
from hermes_agent.cli.config import DEFAULT_CONFIG
|
||||
display = DEFAULT_CONFIG.get("display", {})
|
||||
self.assertIn("show_reasoning", display)
|
||||
self.assertFalse(display["show_reasoning"])
|
||||
|
|
@ -549,7 +549,7 @@ class TestCommandRegistered(unittest.TestCase):
|
|||
"""Verify /reasoning is in the COMMANDS dict."""
|
||||
|
||||
def test_reasoning_in_commands(self):
|
||||
from hermes_cli.commands import COMMANDS
|
||||
from hermes_agent.cli.commands import COMMANDS
|
||||
self.assertIn("/reasoning", COMMANDS)
|
||||
|
||||
|
||||
|
|
@ -561,7 +561,7 @@ class TestEndToEndPipeline(unittest.TestCase):
|
|||
"""Simulate the full pipeline: extraction -> result dict -> display."""
|
||||
|
||||
def test_openrouter_claude_pipeline(self):
|
||||
from run_agent import AIAgent
|
||||
from hermes_agent.agent.loop import AIAgent
|
||||
|
||||
api_message = SimpleNamespace(
|
||||
role="assistant",
|
||||
|
|
@ -597,7 +597,7 @@ class TestEndToEndPipeline(unittest.TestCase):
|
|||
self.assertIn("Python list methods", result["last_reasoning"])
|
||||
|
||||
def test_no_reasoning_model_pipeline(self):
|
||||
from run_agent import AIAgent
|
||||
from hermes_agent.agent.loop import AIAgent
|
||||
|
||||
api_message = SimpleNamespace(content="Paris.", tool_calls=None)
|
||||
reasoning = AIAgent._extract_reasoning(None, api_message)
|
||||
|
|
@ -616,7 +616,7 @@ class TestReasoningDeltasFiredFlag(unittest.TestCase):
|
|||
reasoning was already streamed via _fire_reasoning_delta."""
|
||||
|
||||
def _make_agent(self):
|
||||
from run_agent import AIAgent
|
||||
from hermes_agent.agent.loop import AIAgent
|
||||
agent = AIAgent.__new__(AIAgent)
|
||||
agent.reasoning_callback = None
|
||||
agent.stream_delta_callback = None
|
||||
|
|
@ -704,7 +704,7 @@ class TestReasoningShownThisTurnFlag(unittest.TestCase):
|
|||
was already shown during streaming in a tool-calling loop."""
|
||||
|
||||
def _make_cli(self):
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
cli = HermesCLI.__new__(HermesCLI)
|
||||
cli.show_reasoning = True
|
||||
cli.streaming_enabled = True
|
||||
|
|
@ -721,14 +721,14 @@ class TestReasoningShownThisTurnFlag(unittest.TestCase):
|
|||
cli._reasoning_preview_buf = ""
|
||||
return cli
|
||||
|
||||
@patch("cli._cprint")
|
||||
@patch("hermes_agent.cli.repl._cprint")
|
||||
def test_streaming_reasoning_sets_turn_flag(self, mock_cprint):
|
||||
cli = self._make_cli()
|
||||
self.assertFalse(cli._reasoning_shown_this_turn)
|
||||
cli._stream_reasoning_delta("Thinking about it...")
|
||||
self.assertTrue(cli._reasoning_shown_this_turn)
|
||||
|
||||
@patch("cli._cprint")
|
||||
@patch("hermes_agent.cli.repl._cprint")
|
||||
def test_turn_flag_survives_reset_stream_state(self, mock_cprint):
|
||||
"""_reasoning_shown_this_turn must NOT be cleared by
|
||||
_reset_stream_state (called at intermediate turn boundaries)."""
|
||||
|
|
@ -742,7 +742,7 @@ class TestReasoningShownThisTurnFlag(unittest.TestCase):
|
|||
# Flag must persist
|
||||
self.assertTrue(cli._reasoning_shown_this_turn)
|
||||
|
||||
@patch("cli._cprint")
|
||||
@patch("hermes_agent.cli.repl._cprint")
|
||||
def test_turn_flag_cleared_before_new_turn(self, mock_cprint):
|
||||
"""The turn flag should be reset at the start of a new user turn.
|
||||
This happens outside _reset_stream_state, at the call site."""
|
||||
|
|
|
|||
|
|
@ -12,13 +12,11 @@ from unittest.mock import MagicMock, patch
|
|||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
|
||||
def _make_cli(config_overrides=None, env_overrides=None, **kwargs):
|
||||
"""Create a HermesCLI instance with minimal mocking."""
|
||||
import cli as _cli_mod
|
||||
from cli import HermesCLI
|
||||
import hermes_agent.cli.repl as _cli_mod
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
_clean_config = {
|
||||
"model": {
|
||||
|
|
@ -41,7 +39,7 @@ def _make_cli(config_overrides=None, env_overrides=None, **kwargs):
|
|||
if env_overrides:
|
||||
clean_env.update(env_overrides)
|
||||
with (
|
||||
patch("cli.get_tool_definitions", return_value=[]),
|
||||
patch("hermes_agent.cli.repl.get_tool_definitions", return_value=[]),
|
||||
patch.dict("os.environ", clean_env, clear=False),
|
||||
patch.dict(_cli_mod.__dict__, {"CLI_CONFIG": _clean_config}),
|
||||
):
|
||||
|
|
@ -627,15 +625,15 @@ class TestResumeDisplayConfig:
|
|||
|
||||
def test_default_config_has_resume_display(self):
|
||||
"""DEFAULT_CONFIG in hermes_cli/config.py includes resume_display."""
|
||||
from hermes_cli.config import DEFAULT_CONFIG
|
||||
from hermes_agent.cli.config import DEFAULT_CONFIG
|
||||
display = DEFAULT_CONFIG.get("display", {})
|
||||
assert "resume_display" in display
|
||||
assert display["resume_display"] == "full"
|
||||
|
||||
def test_cli_defaults_have_resume_display(self):
|
||||
"""cli.py load_cli_config defaults include resume_display."""
|
||||
import cli as _cli_mod
|
||||
from cli import load_cli_config
|
||||
import hermes_agent.cli.repl as _cli_mod
|
||||
from hermes_agent.cli.repl import load_cli_config
|
||||
|
||||
with (
|
||||
patch("pathlib.Path.exists", return_value=False),
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from hermes_cli.plugins import VALID_HOOKS, PluginManager
|
||||
from hermes_agent.cli.plugins import VALID_HOOKS, PluginManager
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
|
||||
def test_session_hooks_in_valid_hooks():
|
||||
|
|
@ -13,7 +13,7 @@ def test_session_hooks_in_valid_hooks():
|
|||
assert "on_session_reset" in VALID_HOOKS
|
||||
|
||||
|
||||
@patch("hermes_cli.plugins.invoke_hook")
|
||||
@patch("hermes_agent.cli.plugins.invoke_hook")
|
||||
def test_session_finalize_on_reset(mock_invoke_hook):
|
||||
"""Verify on_session_finalize fires when /new or /reset is used."""
|
||||
cli = HermesCLI()
|
||||
|
|
@ -33,10 +33,10 @@ def test_session_finalize_on_reset(mock_invoke_hook):
|
|||
)
|
||||
|
||||
|
||||
@patch("hermes_cli.plugins.invoke_hook")
|
||||
@patch("hermes_agent.cli.plugins.invoke_hook")
|
||||
def test_session_finalize_on_cleanup(mock_invoke_hook):
|
||||
"""Verify on_session_finalize fires during CLI exit cleanup."""
|
||||
import cli as cli_mod
|
||||
import hermes_agent.cli.repl as cli_mod
|
||||
|
||||
mock_agent = MagicMock()
|
||||
mock_agent.session_id = "cleanup-session-id"
|
||||
|
|
@ -50,7 +50,7 @@ def test_session_finalize_on_cleanup(mock_invoke_hook):
|
|||
)
|
||||
|
||||
|
||||
@patch("hermes_cli.plugins.invoke_hook")
|
||||
@patch("hermes_agent.cli.plugins.invoke_hook")
|
||||
def test_hook_errors_are_caught(mock_invoke_hook):
|
||||
"""Verify hook exceptions are caught and don't crash the agent."""
|
||||
mgr = PluginManager()
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
"""Tests for _stream_delta's handling of <think> tags in prose vs real reasoning blocks."""
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def _make_cli_stub():
|
||||
"""Create a minimal HermesCLI-like object with stream state."""
|
||||
from cli import HermesCLI
|
||||
from hermes_agent.cli.repl import HermesCLI
|
||||
|
||||
cli = HermesCLI.__new__(HermesCLI)
|
||||
cli.show_reasoning = False
|
||||
|
|
@ -130,7 +128,7 @@ class TestFlushRecovery:
|
|||
from unittest.mock import patch
|
||||
import shutil
|
||||
with patch.object(shutil, "get_terminal_size", return_value=os.terminal_size((80, 24))):
|
||||
with patch("cli._cprint"):
|
||||
with patch("hermes_agent.cli.repl._cprint"):
|
||||
cli._flush_stream()
|
||||
|
||||
assert not cli._in_reasoning_block
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import json
|
|||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from run_agent import (
|
||||
from hermes_agent.agent.loop import (
|
||||
_sanitize_surrogates,
|
||||
_sanitize_messages_surrogates,
|
||||
_sanitize_structure_surrogates,
|
||||
|
|
@ -294,12 +294,12 @@ class TestApiMessagesSurrogateRecovery:
|
|||
class TestRunConversationSurrogateSanitization:
|
||||
"""Integration: verify run_conversation sanitizes user_message."""
|
||||
|
||||
@patch("run_agent.AIAgent._build_system_prompt")
|
||||
@patch("run_agent.AIAgent._interruptible_streaming_api_call")
|
||||
@patch("run_agent.AIAgent._interruptible_api_call")
|
||||
@patch("hermes_agent.agent.loop.AIAgent._build_system_prompt")
|
||||
@patch("hermes_agent.agent.loop.AIAgent._interruptible_streaming_api_call")
|
||||
@patch("hermes_agent.agent.loop.AIAgent._interruptible_api_call")
|
||||
def test_user_message_surrogates_sanitized(self, mock_api, mock_stream, mock_sys):
|
||||
"""Surrogates in user_message are stripped before API call."""
|
||||
from run_agent import AIAgent
|
||||
from hermes_agent.agent.loop import AIAgent
|
||||
|
||||
mock_sys.return_value = "system prompt"
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ import sys
|
|||
import importlib
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
# Module-level reference to the cli module (set by _make_cli on first call)
|
||||
_cli_mod = None
|
||||
|
||||
|
|
@ -49,7 +47,7 @@ def _make_cli(tool_progress="all"):
|
|||
}
|
||||
with patch.dict(sys.modules, prompt_toolkit_stubs), \
|
||||
patch.dict("os.environ", clean_env, clear=False):
|
||||
import cli as mod
|
||||
import hermes_agent.cli.repl as mod
|
||||
mod = importlib.reload(mod)
|
||||
_cli_mod = mod
|
||||
with patch.object(mod, "get_tool_definitions", return_value=[]), \
|
||||
|
|
@ -79,10 +77,10 @@ class TestToolProgressScrollback:
|
|||
cli = _make_cli(tool_progress="all")
|
||||
with patch.object(_cli_mod, "_cprint") as mock_print:
|
||||
# First call
|
||||
cli._on_tool_progress("tool.started", "read_file", "cli.py", {"path": "cli.py"})
|
||||
cli._on_tool_progress("tool.started", "read_file", "hermes_agent/cli/repl.py", {"path": "hermes_agent/cli/repl.py"})
|
||||
cli._on_tool_progress("tool.completed", "read_file", None, None, duration=0.1, is_error=False)
|
||||
# Second call (same tool)
|
||||
cli._on_tool_progress("tool.started", "read_file", "run_agent.py", {"path": "run_agent.py"})
|
||||
cli._on_tool_progress("tool.started", "read_file", "hermes_agent/agent/loop.py", {"path": "hermes_agent/agent/loop.py"})
|
||||
cli._on_tool_progress("tool.completed", "read_file", None, None, duration=0.2, is_error=False)
|
||||
|
||||
assert mock_print.call_count == 2
|
||||
|
|
@ -91,9 +89,9 @@ class TestToolProgressScrollback:
|
|||
"""In 'new' mode, consecutive calls to the same tool only print once."""
|
||||
cli = _make_cli(tool_progress="new")
|
||||
with patch.object(_cli_mod, "_cprint") as mock_print:
|
||||
cli._on_tool_progress("tool.started", "read_file", "cli.py", {"path": "cli.py"})
|
||||
cli._on_tool_progress("tool.started", "read_file", "hermes_agent/cli/repl.py", {"path": "hermes_agent/cli/repl.py"})
|
||||
cli._on_tool_progress("tool.completed", "read_file", None, None, duration=0.1, is_error=False)
|
||||
cli._on_tool_progress("tool.started", "read_file", "run_agent.py", {"path": "run_agent.py"})
|
||||
cli._on_tool_progress("tool.started", "read_file", "hermes_agent/agent/loop.py", {"path": "hermes_agent/agent/loop.py"})
|
||||
cli._on_tool_progress("tool.completed", "read_file", None, None, duration=0.2, is_error=False)
|
||||
|
||||
assert mock_print.call_count == 1 # Only the first read_file
|
||||
|
|
@ -102,11 +100,11 @@ class TestToolProgressScrollback:
|
|||
"""In 'new' mode, a different tool name triggers a new line."""
|
||||
cli = _make_cli(tool_progress="new")
|
||||
with patch.object(_cli_mod, "_cprint") as mock_print:
|
||||
cli._on_tool_progress("tool.started", "read_file", "cli.py", {"path": "cli.py"})
|
||||
cli._on_tool_progress("tool.started", "read_file", "hermes_agent/cli/repl.py", {"path": "hermes_agent/cli/repl.py"})
|
||||
cli._on_tool_progress("tool.completed", "read_file", None, None, duration=0.1, is_error=False)
|
||||
cli._on_tool_progress("tool.started", "search_files", "pattern", {"pattern": "test"})
|
||||
cli._on_tool_progress("tool.completed", "search_files", None, None, duration=0.3, is_error=False)
|
||||
cli._on_tool_progress("tool.started", "read_file", "run_agent.py", {"path": "run_agent.py"})
|
||||
cli._on_tool_progress("tool.started", "read_file", "hermes_agent/agent/loop.py", {"path": "hermes_agent/agent/loop.py"})
|
||||
cli._on_tool_progress("tool.completed", "read_file", None, None, duration=0.2, is_error=False)
|
||||
|
||||
# read_file, search_files, read_file (3rd prints because search_files broke the streak)
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ def _force_remove_worktree(info: dict | None) -> None:
|
|||
|
||||
class TestWorktreeIncludeSecurity:
|
||||
def test_rejects_parent_directory_file_traversal(self, git_repo):
|
||||
import cli as cli_mod
|
||||
import hermes_agent.cli.repl as cli_mod
|
||||
|
||||
outside_file = git_repo.parent / "sensitive.txt"
|
||||
outside_file.write_text("SENSITIVE DATA")
|
||||
|
|
@ -57,7 +57,7 @@ class TestWorktreeIncludeSecurity:
|
|||
_force_remove_worktree(info)
|
||||
|
||||
def test_rejects_parent_directory_directory_traversal(self, git_repo):
|
||||
import cli as cli_mod
|
||||
import hermes_agent.cli.repl as cli_mod
|
||||
|
||||
outside_dir = git_repo.parent / "outside-dir"
|
||||
outside_dir.mkdir()
|
||||
|
|
@ -77,7 +77,7 @@ class TestWorktreeIncludeSecurity:
|
|||
_force_remove_worktree(info)
|
||||
|
||||
def test_rejects_symlink_that_resolves_outside_repo(self, git_repo):
|
||||
import cli as cli_mod
|
||||
import hermes_agent.cli.repl as cli_mod
|
||||
|
||||
outside_file = git_repo.parent / "linked-secret.txt"
|
||||
outside_file.write_text("LINKED SECRET")
|
||||
|
|
@ -94,7 +94,7 @@ class TestWorktreeIncludeSecurity:
|
|||
_force_remove_worktree(info)
|
||||
|
||||
def test_allows_valid_file_include(self, git_repo):
|
||||
import cli as cli_mod
|
||||
import hermes_agent.cli.repl as cli_mod
|
||||
|
||||
(git_repo / ".env").write_text("SECRET=***\n")
|
||||
(git_repo / ".worktreeinclude").write_text(".env\n")
|
||||
|
|
@ -111,7 +111,7 @@ class TestWorktreeIncludeSecurity:
|
|||
_force_remove_worktree(info)
|
||||
|
||||
def test_allows_valid_directory_include(self, git_repo):
|
||||
import cli as cli_mod
|
||||
import hermes_agent.cli.repl as cli_mod
|
||||
|
||||
assets_dir = git_repo / ".venv" / "lib"
|
||||
assets_dir.mkdir(parents=True)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue