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:
alt-glitch 2026-04-23 08:35:34 +05:30
parent 65ca3ba93b
commit 4b16341975
898 changed files with 12494 additions and 12019 deletions

View file

@ -11,7 +11,7 @@ from unittest.mock import MagicMock, patch
import pytest
import yaml
from hermes_cli.plugins_cmd import (
from hermes_agent.cli.plugins_cmd import (
_copy_example_files,
_read_manifest,
_repo_name_from_url,
@ -145,7 +145,7 @@ class TestReadManifest:
def test_invalid_yaml_returns_empty_and_logs(self, tmp_path, caplog):
(tmp_path / "plugin.yaml").write_text(": : : bad yaml [[[")
with caplog.at_level(logging.WARNING, logger="hermes_cli.plugins_cmd"):
with caplog.at_level(logging.WARNING, logger="hermes_agent.cli.plugins_cmd"):
result = _read_manifest(tmp_path)
assert result == {}
assert any("Failed to read plugin.yaml" in r.message for r in caplog.records)
@ -163,15 +163,15 @@ class TestCmdInstall:
"""Test the install command."""
def test_install_requires_identifier(self):
from hermes_cli.plugins_cmd import cmd_install
from hermes_agent.cli.plugins_cmd import cmd_install
import argparse
with pytest.raises(SystemExit):
cmd_install("")
@patch("hermes_cli.plugins_cmd._resolve_git_url")
@patch("hermes_agent.cli.plugins_cmd._resolve_git_url")
def test_install_validates_identifier(self, mock_resolve):
from hermes_cli.plugins_cmd import cmd_install
from hermes_agent.cli.plugins_cmd import cmd_install
mock_resolve.side_effect = ValueError("Invalid identifier")
@ -179,12 +179,12 @@ class TestCmdInstall:
cmd_install("invalid")
assert exc_info.value.code == 1
@patch("hermes_cli.plugins_cmd._display_after_install")
@patch("hermes_cli.plugins_cmd.shutil.move")
@patch("hermes_cli.plugins_cmd.shutil.rmtree")
@patch("hermes_cli.plugins_cmd._plugins_dir")
@patch("hermes_cli.plugins_cmd._read_manifest")
@patch("hermes_cli.plugins_cmd.subprocess.run")
@patch("hermes_agent.cli.plugins_cmd._display_after_install")
@patch("hermes_agent.cli.plugins_cmd.shutil.move")
@patch("hermes_agent.cli.plugins_cmd.shutil.rmtree")
@patch("hermes_agent.cli.plugins_cmd._plugins_dir")
@patch("hermes_agent.cli.plugins_cmd._read_manifest")
@patch("hermes_agent.cli.plugins_cmd.subprocess.run")
def test_install_rejects_manifest_name_pointing_at_plugins_root(
self,
mock_run,
@ -195,7 +195,7 @@ class TestCmdInstall:
mock_display_after_install,
tmp_path,
):
from hermes_cli.plugins_cmd import cmd_install
from hermes_agent.cli.plugins_cmd import cmd_install
plugins_dir = tmp_path / "plugins"
plugins_dir.mkdir()
@ -218,11 +218,11 @@ class TestCmdInstall:
class TestCmdUpdate:
"""Test the update command."""
@patch("hermes_cli.plugins_cmd._sanitize_plugin_name")
@patch("hermes_cli.plugins_cmd._plugins_dir")
@patch("hermes_cli.plugins_cmd.subprocess.run")
@patch("hermes_agent.cli.plugins_cmd._sanitize_plugin_name")
@patch("hermes_agent.cli.plugins_cmd._plugins_dir")
@patch("hermes_agent.cli.plugins_cmd.subprocess.run")
def test_update_git_pull_success(self, mock_run, mock_plugins_dir, mock_sanitize):
from hermes_cli.plugins_cmd import cmd_update
from hermes_agent.cli.plugins_cmd import cmd_update
mock_plugins_dir_val = MagicMock()
mock_plugins_dir.return_value = mock_plugins_dir_val
@ -239,10 +239,10 @@ class TestCmdUpdate:
mock_run.assert_called_once()
@patch("hermes_cli.plugins_cmd._sanitize_plugin_name")
@patch("hermes_cli.plugins_cmd._plugins_dir")
@patch("hermes_agent.cli.plugins_cmd._sanitize_plugin_name")
@patch("hermes_agent.cli.plugins_cmd._plugins_dir")
def test_update_plugin_not_found(self, mock_plugins_dir, mock_sanitize):
from hermes_cli.plugins_cmd import cmd_update
from hermes_agent.cli.plugins_cmd import cmd_update
mock_plugins_dir_val = MagicMock()
mock_plugins_dir_val.iterdir.return_value = []
@ -263,11 +263,11 @@ class TestCmdUpdate:
class TestCmdRemove:
"""Test the remove command."""
@patch("hermes_cli.plugins_cmd._sanitize_plugin_name")
@patch("hermes_cli.plugins_cmd._plugins_dir")
@patch("hermes_cli.plugins_cmd.shutil.rmtree")
@patch("hermes_agent.cli.plugins_cmd._sanitize_plugin_name")
@patch("hermes_agent.cli.plugins_cmd._plugins_dir")
@patch("hermes_agent.cli.plugins_cmd.shutil.rmtree")
def test_remove_deletes_plugin(self, mock_rmtree, mock_plugins_dir, mock_sanitize):
from hermes_cli.plugins_cmd import cmd_remove
from hermes_agent.cli.plugins_cmd import cmd_remove
mock_plugins_dir.return_value = MagicMock()
mock_target = MagicMock()
@ -278,10 +278,10 @@ class TestCmdRemove:
mock_rmtree.assert_called_once_with(mock_target)
@patch("hermes_cli.plugins_cmd._sanitize_plugin_name")
@patch("hermes_cli.plugins_cmd._plugins_dir")
@patch("hermes_agent.cli.plugins_cmd._sanitize_plugin_name")
@patch("hermes_agent.cli.plugins_cmd._plugins_dir")
def test_remove_plugin_not_found(self, mock_plugins_dir, mock_sanitize):
from hermes_cli.plugins_cmd import cmd_remove
from hermes_agent.cli.plugins_cmd import cmd_remove
mock_plugins_dir_val = MagicMock()
mock_plugins_dir_val.iterdir.return_value = []
@ -302,9 +302,9 @@ class TestCmdRemove:
class TestCmdList:
"""Test the list command."""
@patch("hermes_cli.plugins_cmd._plugins_dir")
@patch("hermes_agent.cli.plugins_cmd._plugins_dir")
def test_list_empty_plugins_dir(self, mock_plugins_dir):
from hermes_cli.plugins_cmd import cmd_list
from hermes_agent.cli.plugins_cmd import cmd_list
mock_plugins_dir_val = MagicMock()
mock_plugins_dir_val.iterdir.return_value = []
@ -312,10 +312,10 @@ class TestCmdList:
cmd_list()
@patch("hermes_cli.plugins_cmd._plugins_dir")
@patch("hermes_cli.plugins_cmd._read_manifest")
@patch("hermes_agent.cli.plugins_cmd._plugins_dir")
@patch("hermes_agent.cli.plugins_cmd._read_manifest")
def test_list_with_plugins(self, mock_read_manifest, mock_plugins_dir):
from hermes_cli.plugins_cmd import cmd_list
from hermes_agent.cli.plugins_cmd import cmd_list
mock_plugins_dir_val = MagicMock()
mock_plugin_dir = MagicMock()
@ -338,7 +338,7 @@ class TestCopyExampleFiles:
"""Test example file copying."""
def test_copies_example_files(self, tmp_path):
from hermes_cli.plugins_cmd import _copy_example_files
from hermes_agent.cli.plugins_cmd import _copy_example_files
from unittest.mock import MagicMock
console = MagicMock()
@ -354,7 +354,7 @@ class TestCopyExampleFiles:
console.print.assert_called()
def test_skips_existing_files(self, tmp_path):
from hermes_cli.plugins_cmd import _copy_example_files
from hermes_agent.cli.plugins_cmd import _copy_example_files
from unittest.mock import MagicMock
console = MagicMock()
@ -371,7 +371,7 @@ class TestCopyExampleFiles:
assert real_file.read_text() == "existing: true"
def test_handles_copy_error_gracefully(self, tmp_path):
from hermes_cli.plugins_cmd import _copy_example_files
from hermes_agent.cli.plugins_cmd import _copy_example_files
from unittest.mock import MagicMock, patch
console = MagicMock()
@ -382,7 +382,7 @@ class TestCopyExampleFiles:
# Mock shutil.copy2 to raise an error
with patch(
"hermes_cli.plugins_cmd.shutil.copy2",
"hermes_agent.cli.plugins_cmd.shutil.copy2",
side_effect=OSError("Permission denied"),
):
# Should not raise, just warn
@ -396,7 +396,7 @@ class TestPromptPluginEnvVars:
"""Tests for _prompt_plugin_env_vars."""
def test_skips_when_no_requires_env(self):
from hermes_cli.plugins_cmd import _prompt_plugin_env_vars
from hermes_agent.cli.plugins_cmd import _prompt_plugin_env_vars
from unittest.mock import MagicMock
console = MagicMock()
@ -404,17 +404,17 @@ class TestPromptPluginEnvVars:
console.print.assert_not_called()
def test_skips_already_set_vars(self, monkeypatch):
from hermes_cli.plugins_cmd import _prompt_plugin_env_vars
from hermes_agent.cli.plugins_cmd import _prompt_plugin_env_vars
from unittest.mock import MagicMock, patch
console = MagicMock()
with patch("hermes_cli.config.get_env_value", return_value="already-set"):
with patch("hermes_agent.cli.config.get_env_value", return_value="already-set"):
_prompt_plugin_env_vars({"requires_env": ["MY_KEY"]}, console)
# No prompt should appear — all vars are set
console.print.assert_not_called()
def test_prompts_for_missing_var_simple_format(self):
from hermes_cli.plugins_cmd import _prompt_plugin_env_vars
from hermes_agent.cli.plugins_cmd import _prompt_plugin_env_vars
from unittest.mock import MagicMock, patch
console = MagicMock()
@ -423,15 +423,15 @@ class TestPromptPluginEnvVars:
"requires_env": ["MY_API_KEY"],
}
with patch("hermes_cli.config.get_env_value", return_value=None), \
with patch("hermes_agent.cli.config.get_env_value", return_value=None), \
patch("builtins.input", return_value="sk-test-123"), \
patch("hermes_cli.config.save_env_value") as mock_save:
patch("hermes_agent.cli.config.save_env_value") as mock_save:
_prompt_plugin_env_vars(manifest, console)
mock_save.assert_called_once_with("MY_API_KEY", "sk-test-123")
def test_prompts_for_missing_var_rich_format(self):
from hermes_cli.plugins_cmd import _prompt_plugin_env_vars
from hermes_agent.cli.plugins_cmd import _prompt_plugin_env_vars
from unittest.mock import MagicMock, patch
console = MagicMock()
@ -447,9 +447,9 @@ class TestPromptPluginEnvVars:
],
}
with patch("hermes_cli.config.get_env_value", return_value=None), \
with patch("hermes_agent.cli.config.get_env_value", return_value=None), \
patch("builtins.input", return_value="pk-lf-123"), \
patch("hermes_cli.config.save_env_value") as mock_save:
patch("hermes_agent.cli.config.save_env_value") as mock_save:
_prompt_plugin_env_vars(manifest, console)
mock_save.assert_called_once_with("LANGFUSE_PUBLIC_KEY", "pk-lf-123")
@ -458,7 +458,7 @@ class TestPromptPluginEnvVars:
assert "langfuse.com" in printed
def test_secret_uses_getpass(self):
from hermes_cli.plugins_cmd import _prompt_plugin_env_vars
from hermes_agent.cli.plugins_cmd import _prompt_plugin_env_vars
from unittest.mock import MagicMock, patch
console = MagicMock()
@ -467,37 +467,37 @@ class TestPromptPluginEnvVars:
"requires_env": [{"name": "SECRET_KEY", "secret": True}],
}
with patch("hermes_cli.config.get_env_value", return_value=None), \
with patch("hermes_agent.cli.config.get_env_value", return_value=None), \
patch("getpass.getpass", return_value="s3cret") as mock_gp, \
patch("hermes_cli.config.save_env_value"):
patch("hermes_agent.cli.config.save_env_value"):
_prompt_plugin_env_vars(manifest, console)
mock_gp.assert_called_once()
def test_empty_input_skips(self):
from hermes_cli.plugins_cmd import _prompt_plugin_env_vars
from hermes_agent.cli.plugins_cmd import _prompt_plugin_env_vars
from unittest.mock import MagicMock, patch
console = MagicMock()
manifest = {"name": "test", "requires_env": ["OPTIONAL_VAR"]}
with patch("hermes_cli.config.get_env_value", return_value=None), \
with patch("hermes_agent.cli.config.get_env_value", return_value=None), \
patch("builtins.input", return_value=""), \
patch("hermes_cli.config.save_env_value") as mock_save:
patch("hermes_agent.cli.config.save_env_value") as mock_save:
_prompt_plugin_env_vars(manifest, console)
mock_save.assert_not_called()
def test_keyboard_interrupt_skips_gracefully(self):
from hermes_cli.plugins_cmd import _prompt_plugin_env_vars
from hermes_agent.cli.plugins_cmd import _prompt_plugin_env_vars
from unittest.mock import MagicMock, patch
console = MagicMock()
manifest = {"name": "test", "requires_env": ["KEY1", "KEY2"]}
with patch("hermes_cli.config.get_env_value", return_value=None), \
with patch("hermes_agent.cli.config.get_env_value", return_value=None), \
patch("builtins.input", side_effect=KeyboardInterrupt), \
patch("hermes_cli.config.save_env_value") as mock_save:
patch("hermes_agent.cli.config.save_env_value") as mock_save:
_prompt_plugin_env_vars(manifest, console)
# Should not crash, and not save anything
@ -511,14 +511,14 @@ class TestCursesRadiolist:
"""Test the curses_radiolist function (non-TTY fallback path)."""
def test_non_tty_returns_default(self):
from hermes_cli.curses_ui import curses_radiolist
from hermes_agent.cli.ui.curses import curses_radiolist
with patch("sys.stdin") as mock_stdin:
mock_stdin.isatty.return_value = False
result = curses_radiolist("Pick one", ["a", "b", "c"], selected=1)
assert result == 1
def test_non_tty_returns_cancel_value(self):
from hermes_cli.curses_ui import curses_radiolist
from hermes_agent.cli.ui.curses import curses_radiolist
with patch("sys.stdin") as mock_stdin:
mock_stdin.isatty.return_value = False
result = curses_radiolist("Pick", ["x", "y"], selected=0, cancel_returns=1)
@ -536,7 +536,7 @@ class TestProviderDiscovery:
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
config_file = tmp_path / "config.yaml"
config_file.write_text("memory:\n provider: ''\n")
from hermes_cli.plugins_cmd import _get_current_memory_provider
from hermes_agent.cli.plugins_cmd import _get_current_memory_provider
result = _get_current_memory_provider()
assert result == ""
@ -545,7 +545,7 @@ class TestProviderDiscovery:
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
config_file = tmp_path / "config.yaml"
config_file.write_text("context:\n engine: compressor\n")
from hermes_cli.plugins_cmd import _get_current_context_engine
from hermes_agent.cli.plugins_cmd import _get_current_context_engine
result = _get_current_context_engine()
assert result == "compressor"
@ -554,7 +554,7 @@ class TestProviderDiscovery:
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
config_file = tmp_path / "config.yaml"
config_file.write_text("memory:\n provider: ''\n")
from hermes_cli.plugins_cmd import _save_memory_provider
from hermes_agent.cli.plugins_cmd import _save_memory_provider
_save_memory_provider("honcho")
content = yaml.safe_load(config_file.read_text())
assert content["memory"]["provider"] == "honcho"
@ -564,24 +564,24 @@ class TestProviderDiscovery:
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
config_file = tmp_path / "config.yaml"
config_file.write_text("context:\n engine: compressor\n")
from hermes_cli.plugins_cmd import _save_context_engine
from hermes_agent.cli.plugins_cmd import _save_context_engine
_save_context_engine("lcm")
content = yaml.safe_load(config_file.read_text())
assert content["context"]["engine"] == "lcm"
def test_discover_memory_providers_empty(self):
"""Discovery returns empty list when import fails."""
with patch("plugins.memory.discover_memory_providers",
with patch("hermes_agent.plugins.memory.discover_memory_providers",
side_effect=ImportError("no module")):
from hermes_cli.plugins_cmd import _discover_memory_providers
from hermes_agent.cli.plugins_cmd import _discover_memory_providers
result = _discover_memory_providers()
assert result == []
def test_discover_context_engines_empty(self):
"""Discovery returns empty list when import fails."""
with patch("plugins.context_engine.discover_context_engines",
with patch("hermes_agent.plugins.context_engine.discover_context_engines",
side_effect=ImportError("no module")):
from hermes_cli.plugins_cmd import _discover_context_engines
from hermes_agent.cli.plugins_cmd import _discover_context_engines
result = _discover_context_engines()
assert result == []
@ -597,7 +597,7 @@ class TestNoAutoActivation:
be used only explicit config triggers plugin engines."""
# This tests the run_agent.py logic indirectly by checking that the
# code path for default config doesn't call get_plugin_context_engine.
import run_agent as ra_module
import hermes_agent.agent.loop as ra_module
source = open(ra_module.__file__).read()
# The old code had: "Even with default config, check if a plugin registered one"
# The fix removes this. Verify it's gone.