mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-01 01:51:44 +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
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue