diff --git a/hermes_agent/acp/server.py b/hermes_agent/acp/server.py index e5fb8cea7..9081fa694 100644 --- a/hermes_agent/acp/server.py +++ b/hermes_agent/acp/server.py @@ -572,7 +572,7 @@ class HermesACPAgent(acp.Agent): nonlocal previous_approval_cb, previous_interactive if approval_cb: try: - from hermes_agent.tools import terminal_tool as _terminal_tool + from hermes_agent.tools import terminal as _terminal_tool previous_approval_cb = _terminal_tool._get_approval_callback() _terminal_tool.set_approval_callback(approval_cb) except Exception: @@ -599,7 +599,7 @@ class HermesACPAgent(acp.Agent): os.environ["HERMES_INTERACTIVE"] = previous_interactive if approval_cb: try: - from hermes_agent.tools import terminal_tool as _terminal_tool + from hermes_agent.tools import terminal as _terminal_tool _terminal_tool.set_approval_callback(previous_approval_cb) except Exception: logger.debug("Could not restore approval callback", exc_info=True) diff --git a/hermes_agent/cli/auth/commands.py b/hermes_agent/cli/auth/commands.py index f5fdf26d7..db594deb2 100644 --- a/hermes_agent/cli/auth/commands.py +++ b/hermes_agent/cli/auth/commands.py @@ -197,7 +197,7 @@ def auth_add_command(args) -> None: return if provider == "anthropic": - from hermes_agent.agent import anthropic_adapter as anthropic_mod + from hermes_agent.providers import anthropic_adapter as anthropic_mod creds = anthropic_mod.run_hermes_oauth_login_pure() if not creds: diff --git a/hermes_agent/cli/claw.py b/hermes_agent/cli/claw.py index 42a3b1f9e..829832c49 100644 --- a/hermes_agent/cli/claw.py +++ b/hermes_agent/cli/claw.py @@ -30,7 +30,7 @@ from hermes_agent.cli.setup_wizard import ( logger = logging.getLogger(__name__) -PROJECT_ROOT = Path(__file__).parent.parent.resolve() +PROJECT_ROOT = Path(__file__).resolve().parents[2].resolve() _OPENCLAW_SCRIPT = ( get_optional_skills_dir(PROJECT_ROOT / "optional-skills") diff --git a/hermes_agent/cli/commands.py b/hermes_agent/cli/commands.py index 689a3f282..0fa5beac9 100644 --- a/hermes_agent/cli/commands.py +++ b/hermes_agent/cli/commands.py @@ -131,7 +131,7 @@ COMMAND_REGISTRY: list[CommandDef] = [ # Tools & Skills CommandDef("tools", "Manage tools: /tools [list|disable|enable] [name...]", "Tools & Skills", args_hint="[list|disable|enable] [name...]", cli_only=True), - CommandDef("hermes_agent.tools.toolsets", "List available toolsets", "Tools & Skills", + CommandDef("toolsets", "List available toolsets", "Tools & Skills", cli_only=True), CommandDef("skills", "Search, install, inspect, or manage skills", "Tools & Skills", cli_only=True, diff --git a/hermes_agent/cli/config.py b/hermes_agent/cli/config.py index 9e9f78f94..643f87042 100644 --- a/hermes_agent/cli/config.py +++ b/hermes_agent/cli/config.py @@ -217,7 +217,7 @@ def get_env_path() -> Path: def get_project_root() -> Path: """Get the project installation directory.""" - return Path(__file__).parent.parent.resolve() + return Path(__file__).resolve().parents[2].resolve() def _secure_dir(path): """Set directory to owner-only access (0700 by default). No-op on Windows. diff --git a/hermes_agent/cli/cron.py b/hermes_agent/cli/cron.py index 69075b85a..5d1c7b254 100644 --- a/hermes_agent/cli/cron.py +++ b/hermes_agent/cli/cron.py @@ -10,7 +10,7 @@ import sys from pathlib import Path from typing import Iterable, List, Optional -PROJECT_ROOT = Path(__file__).parent.parent.resolve() +PROJECT_ROOT = Path(__file__).resolve().parents[2].resolve() from hermes_agent.cli.ui.colors import Colors, color diff --git a/hermes_agent/cli/gateway.py b/hermes_agent/cli/gateway.py index 71ddcc1a6..477747319 100644 --- a/hermes_agent/cli/gateway.py +++ b/hermes_agent/cli/gateway.py @@ -13,7 +13,7 @@ import sys from dataclasses import dataclass from pathlib import Path -PROJECT_ROOT = Path(__file__).parent.parent.resolve() +PROJECT_ROOT = Path(__file__).resolve().parents[2].resolve() from hermes_agent.gateway.status import terminate_pid from hermes_agent.gateway.restart import ( diff --git a/hermes_agent/cli/main.py b/hermes_agent/cli/main.py index f41456b25..8339b06c3 100644 --- a/hermes_agent/cli/main.py +++ b/hermes_agent/cli/main.py @@ -82,7 +82,7 @@ def _require_tty(command_name: str) -> None: sys.exit(1) -PROJECT_ROOT = Path(__file__).parent.parent.resolve() +PROJECT_ROOT = Path(__file__).resolve().parents[2] # --------------------------------------------------------------------------- diff --git a/hermes_agent/cli/nous_subscription.py b/hermes_agent/cli/nous_subscription.py index a69a965fd..45a77f6fd 100644 --- a/hermes_agent/cli/nous_subscription.py +++ b/hermes_agent/cli/nous_subscription.py @@ -123,7 +123,7 @@ def _has_agent_browser() -> bool: agent_browser_bin = shutil.which("agent-browser") local_bin = ( - Path(__file__).parent.parent / "node_modules" / ".bin" / "agent-browser" + Path(__file__).resolve().parents[2] / "node_modules" / ".bin" / "agent-browser" ) return bool(agent_browser_bin or local_bin.exists()) diff --git a/hermes_agent/cli/profiles.py b/hermes_agent/cli/profiles.py index 322704b57..ac7903342 100644 --- a/hermes_agent/cli/profiles.py +++ b/hermes_agent/cli/profiles.py @@ -469,11 +469,11 @@ def seed_profile_skills(profile_dir: Path, quiet: bool = False) -> Optional[dict Uses subprocess because sync_skills() caches HERMES_HOME at module level. Returns the sync result dict, or None on failure. """ - project_root = Path(__file__).parent.parent.resolve() + project_root = Path(__file__).resolve().parents[2].resolve() try: result = subprocess.run( [sys.executable, "-c", - "import json; from tools.skills_sync import sync_skills; " + "import json; from hermes_agent.tools.skills.sync import sync_skills; " "r = sync_skills(quiet=True); print(json.dumps(r))"], env={**os.environ, "HERMES_HOME": str(profile_dir)}, cwd=str(project_root), diff --git a/hermes_agent/cli/repl.py b/hermes_agent/cli/repl.py index ce89a7021..570e1b512 100644 --- a/hermes_agent/cli/repl.py +++ b/hermes_agent/cli/repl.py @@ -5425,7 +5425,7 @@ class HermesCLI: """Show Google Gemini Code Assist quota usage for the current OAuth account.""" try: from hermes_agent.providers.google_oauth import get_valid_access_token, GoogleOAuthError, load_credentials - from hermes_agent.agent.google_code_assist import retrieve_user_quota, CodeAssistError + from hermes_agent.providers.google_code_assist import retrieve_user_quota, CodeAssistError except ImportError as exc: self._console_print(f" [red]Gemini modules unavailable: {exc}[/]") return diff --git a/hermes_agent/cli/runtime_provider.py b/hermes_agent/cli/runtime_provider.py index cba8e0d4a..0c25f1054 100644 --- a/hermes_agent/cli/runtime_provider.py +++ b/hermes_agent/cli/runtime_provider.py @@ -9,7 +9,7 @@ from typing import Any, Dict, Optional logger = logging.getLogger(__name__) -from hermes_agent.cli import auth as auth_mod +from hermes_agent.cli.auth import auth as auth_mod from hermes_agent.providers.credential_pool import CredentialPool, PooledCredential, get_custom_provider_pool_key, load_pool from hermes_agent.cli.auth.auth import ( AuthError, diff --git a/hermes_agent/cli/setup_wizard.py b/hermes_agent/cli/setup_wizard.py index 2fa8e0d60..9231dfb14 100644 --- a/hermes_agent/cli/setup_wizard.py +++ b/hermes_agent/cli/setup_wizard.py @@ -27,7 +27,7 @@ from hermes_agent.constants import get_optional_skills_dir logger = logging.getLogger(__name__) -PROJECT_ROOT = Path(__file__).parent.parent.resolve() +PROJECT_ROOT = Path(__file__).resolve().parents[2].resolve() _DOCS_BASE = "https://hermes-agent.nousresearch.com/docs" diff --git a/hermes_agent/cli/tools_config.py b/hermes_agent/cli/tools_config.py index 066d246b9..20c22a1d3 100644 --- a/hermes_agent/cli/tools_config.py +++ b/hermes_agent/cli/tools_config.py @@ -29,7 +29,7 @@ from hermes_agent.utils import base_url_hostname logger = logging.getLogger(__name__) -PROJECT_ROOT = Path(__file__).parent.parent.resolve() +PROJECT_ROOT = Path(__file__).resolve().parents[2].resolve() # ─── UI Helpers (shared with setup.py) ──────────────────────────────────────── diff --git a/hermes_agent/cli/ui/banner.py b/hermes_agent/cli/ui/banner.py index 88fe43f70..5597c8bb2 100644 --- a/hermes_agent/cli/ui/banner.py +++ b/hermes_agent/cli/ui/banner.py @@ -136,7 +136,7 @@ def check_for_updates() -> Optional[int]: # Must be a git repo — fall back to project root for dev installs if not (repo_dir / ".git").exists(): - repo_dir = Path(__file__).parent.parent.resolve() + repo_dir = Path(__file__).resolve().parents[3].resolve() if not (repo_dir / ".git").exists(): return None @@ -188,7 +188,7 @@ def _resolve_repo_dir() -> Optional[Path]: hermes_home = get_hermes_home() repo_dir = hermes_home / "hermes-agent" if not (repo_dir / ".git").exists(): - repo_dir = Path(__file__).parent.parent.resolve() + repo_dir = Path(__file__).resolve().parents[3].resolve() return repo_dir if (repo_dir / ".git").exists() else None diff --git a/hermes_agent/cli/ui/status.py b/hermes_agent/cli/ui/status.py index 0143ff4e7..0d0f82d12 100644 --- a/hermes_agent/cli/ui/status.py +++ b/hermes_agent/cli/ui/status.py @@ -9,7 +9,7 @@ import sys import subprocess from pathlib import Path -PROJECT_ROOT = Path(__file__).parent.parent.resolve() +PROJECT_ROOT = Path(__file__).resolve().parents[3].resolve() from hermes_agent.cli.auth.auth import AuthError, resolve_provider from hermes_agent.cli.ui.colors import Colors, color diff --git a/hermes_agent/cli/uninstall.py b/hermes_agent/cli/uninstall.py index e32a688af..c50235857 100644 --- a/hermes_agent/cli/uninstall.py +++ b/hermes_agent/cli/uninstall.py @@ -26,7 +26,7 @@ def log_warn(msg: str): def get_project_root() -> Path: """Get the project installation directory.""" - return Path(__file__).parent.parent.resolve() + return Path(__file__).resolve().parents[2].resolve() def find_shell_configs() -> list: diff --git a/hermes_agent/cli/web_server.py b/hermes_agent/cli/web_server.py index 42e7f7630..2a0fee54d 100644 --- a/hermes_agent/cli/web_server.py +++ b/hermes_agent/cli/web_server.py @@ -27,7 +27,7 @@ from typing import Any, Dict, List, Optional import yaml -PROJECT_ROOT = Path(__file__).parent.parent.resolve() +PROJECT_ROOT = Path(__file__).resolve().parents[2].resolve() from hermes_agent.cli import __version__, __release_date__ from hermes_agent.cli.config import ( @@ -1198,7 +1198,7 @@ def _resolve_provider_status(provider_id: str, status_fn) -> Dict[str, Any]: except Exception as e: return {"logged_in": False, "error": str(e)} try: - from hermes_agent.cli import auth as hauth + from hermes_agent.cli.auth import auth as hauth if provider_id == "nous": raw = hauth.get_nous_auth_status() return { @@ -1537,7 +1537,7 @@ async def _start_device_code_flow(provider_id: str) -> Dict[str, Any]: then spawns a background poller. Returns the user-facing display fields so the UI can render the verification page link + user code. """ - from hermes_agent.cli import auth as hauth + from hermes_agent.cli.auth import auth as hauth if provider_id == "nous": from hermes_agent.cli.auth.auth import _request_device_code, PROVIDER_REGISTRY import httpx diff --git a/hermes_agent/gateway/platforms/api_server.py b/hermes_agent/gateway/platforms/api_server.py index 83cc22745..4e5c64547 100644 --- a/hermes_agent/gateway/platforms/api_server.py +++ b/hermes_agent/gateway/platforms/api_server.py @@ -32,7 +32,14 @@ import sqlite3 import time import uuid from typing import Any, Dict, List, Optional -from aiohttp import web +try: + from aiohttp import web + + AIOHTTP_AVAILABLE = True +except ImportError: + AIOHTTP_AVAILABLE = False + web = None # type: ignore[assignment] + from hermes_agent.gateway.config import Platform, PlatformConfig from hermes_agent.gateway.platforms.base import ( BasePlatformAdapter, @@ -52,6 +59,11 @@ MAX_NORMALIZED_TEXT_LENGTH = 65_536 # 64 KB cap for normalized content parts MAX_CONTENT_LIST_SIZE = 1_000 # Max items when content is an array +def check_api_server_requirements() -> bool: + """Check if API server adapter dependencies are available.""" + return AIOHTTP_AVAILABLE + + def _normalize_chat_content( content: Any, *, _max_depth: int = 10, _depth: int = 0, ) -> str: diff --git a/hermes_agent/gateway/run.py b/hermes_agent/gateway/run.py index 443aa8ac4..5b38c220e 100644 --- a/hermes_agent/gateway/run.py +++ b/hermes_agent/gateway/run.py @@ -7673,7 +7673,7 @@ class GatewayRunner: if is_managed(): return f"✗ {format_managed_message('update Hermes Agent')}" - project_root = Path(__file__).parent.parent.resolve() + project_root = Path(__file__).resolve().parents[2].resolve() git_dir = project_root / '.git' if not git_dir.exists(): diff --git a/hermes_agent/providers/gemini_cloudcode_adapter.py b/hermes_agent/providers/gemini_cloudcode_adapter.py index 96a6be7ee..14cdef114 100644 --- a/hermes_agent/providers/gemini_cloudcode_adapter.py +++ b/hermes_agent/providers/gemini_cloudcode_adapter.py @@ -38,9 +38,9 @@ from typing import Any, Dict, Iterator, List, Optional import httpx -from hermes_agent.agent import google_oauth +from hermes_agent.providers import google_oauth from hermes_agent.providers.gemini_schema import sanitize_gemini_tool_parameters -from hermes_agent.agent.google_code_assist import ( +from hermes_agent.providers.google_code_assist import ( CODE_ASSIST_ENDPOINT, FREE_TIER_ID, CodeAssistError, diff --git a/hermes_agent/tools/__init__.py b/hermes_agent/tools/__init__.py index d0dbe3818..fc943decf 100644 --- a/hermes_agent/tools/__init__.py +++ b/hermes_agent/tools/__init__.py @@ -17,7 +17,7 @@ to be re-exported here. def check_file_requirements(): """File tools only require terminal backend availability.""" - from .terminal_tool import check_terminal_requirements + from .terminal import check_terminal_requirements return check_terminal_requirements() diff --git a/hermes_agent/tools/browser/camofox.py b/hermes_agent/tools/browser/camofox.py index f18d0f946..66ec91a59 100644 --- a/hermes_agent/tools/browser/camofox.py +++ b/hermes_agent/tools/browser/camofox.py @@ -33,7 +33,7 @@ from typing import Any, Dict, Optional import requests from hermes_agent.cli.config import load_config -from hermes_agent.tools.browser_camofox_state import get_camofox_identity +from hermes_agent.tools.browser.camofox_state import get_camofox_identity from hermes_agent.tools.registry import tool_error logger = logging.getLogger(__name__) diff --git a/hermes_agent/tools/browser/tool.py b/hermes_agent/tools/browser/tool.py index 78fe139ea..668a236b2 100644 --- a/hermes_agent/tools/browser/tool.py +++ b/hermes_agent/tools/browser/tool.py @@ -1046,7 +1046,7 @@ def _find_agent_browser() -> str: return which_result # Check local node_modules/.bin/ (npm install in repo root) - repo_root = Path(__file__).parent.parent + repo_root = Path(__file__).resolve().parents[3] local_bin = repo_root / "node_modules" / ".bin" / "agent-browser" if local_bin.exists(): _cached_agent_browser = str(local_bin) diff --git a/hermes_agent/tools/registry.py b/hermes_agent/tools/registry.py index 3a74cce90..949e7a95f 100644 --- a/hermes_agent/tools/registry.py +++ b/hermes_agent/tools/registry.py @@ -56,13 +56,28 @@ def _module_registers_tools(module_path: Path) -> bool: def discover_builtin_tools(tools_dir: Optional[Path] = None) -> List[str]: """Import built-in self-registering tool modules and return their module names.""" tools_path = Path(tools_dir) if tools_dir is not None else Path(__file__).resolve().parent + + _SKIP_FILES = {"__init__.py", "registry.py", "mcp_tool.py"} + + # Top-level modules module_names = [ f"hermes_agent.tools.{path.stem}" for path in sorted(tools_path.glob("*.py")) - if path.name not in {"__init__.py", "registry.py", "mcp_tool.py"} + if path.name not in _SKIP_FILES and _module_registers_tools(path) ] + # Subpackage modules (e.g. browser/, files/, media/, skills/) + for subdir in sorted(tools_path.iterdir()): + if not subdir.is_dir() or not (subdir / "__init__.py").exists(): + continue + pkg_name = subdir.name + for path in sorted(subdir.glob("*.py")): + if path.name in _SKIP_FILES: + continue + if _module_registers_tools(path): + module_names.append(f"hermes_agent.tools.{pkg_name}.{path.stem}") + imported: List[str] = [] for mod_name in module_names: try: diff --git a/hermes_agent/tools/rl_training.py b/hermes_agent/tools/rl_training.py index 3f0ed0997..988d5ac36 100644 --- a/hermes_agent/tools/rl_training.py +++ b/hermes_agent/tools/rl_training.py @@ -53,7 +53,7 @@ logger = logging.getLogger(__name__) # ============================================================================ # Path to tinker-atropos submodule (relative to hermes-agent root) -HERMES_ROOT = Path(__file__).parent.parent +HERMES_ROOT = Path(__file__).resolve().parents[2] TINKER_ATROPOS_ROOT = HERMES_ROOT / "tinker-atropos" ENVIRONMENTS_DIR = TINKER_ATROPOS_ROOT / "tinker_atropos" / "environments" CONFIGS_DIR = TINKER_ATROPOS_ROOT / "configs" diff --git a/hermes_agent/tools/skills/hub.py b/hermes_agent/tools/skills/hub.py index fac862b57..3109c3e5e 100644 --- a/hermes_agent/tools/skills/hub.py +++ b/hermes_agent/tools/skills/hub.py @@ -2164,7 +2164,7 @@ class OptionalSkillSource(SkillSource): from hermes_agent.constants import get_optional_skills_dir self._optional_dir = get_optional_skills_dir( - Path(__file__).parent.parent / "optional-skills" + Path(__file__).resolve().parents[3] / "optional-skills" ) def source_id(self) -> str: diff --git a/skills/productivity/google-workspace/scripts/setup.py b/skills/productivity/google-workspace/scripts/setup.py index bf4fb39ca..d1bf8fa2a 100644 --- a/skills/productivity/google-workspace/scripts/setup.py +++ b/skills/productivity/google-workspace/scripts/setup.py @@ -29,12 +29,12 @@ import sys from pathlib import Path try: - from hermes_constants import display_hermes_home, get_hermes_home + from hermes_agent.constants import display_hermes_home, get_hermes_home except ModuleNotFoundError: HERMES_AGENT_ROOT = Path(__file__).resolve().parents[4] if HERMES_AGENT_ROOT.exists(): sys.path.insert(0, str(HERMES_AGENT_ROOT)) - from hermes_constants import display_hermes_home, get_hermes_home + from hermes_agent.constants import display_hermes_home, get_hermes_home HERMES_HOME = get_hermes_home() TOKEN_PATH = HERMES_HOME / "google_token.json" diff --git a/tests/agent/test_auxiliary_config_bridge.py b/tests/agent/test_auxiliary_config_bridge.py index 03735b575..401ec79cc 100644 --- a/tests/agent/test_auxiliary_config_bridge.py +++ b/tests/agent/test_auxiliary_config_bridge.py @@ -197,7 +197,7 @@ class TestGatewayBridgeCodeParity: def test_gateway_has_auxiliary_bridge(self): """The gateway config bridge must include auxiliary.* bridging.""" - gateway_path = Path(__file__).parent.parent.parent / "gateway" / "run.py" + gateway_path = Path(__file__).parent.parent.parent / "hermes_agent" / "gateway" / "run.py" content = gateway_path.read_text() # Check for key patterns that indicate the bridge is present assert "AUXILIARY_VISION_PROVIDER" in content @@ -211,7 +211,7 @@ class TestGatewayBridgeCodeParity: def test_gateway_no_compression_env_bridge(self): """Gateway should NOT bridge compression config to env vars (config-only).""" - gateway_path = Path(__file__).parent.parent.parent / "gateway" / "run.py" + gateway_path = Path(__file__).parent.parent.parent / "hermes_agent" / "gateway" / "run.py" content = gateway_path.read_text() assert "CONTEXT_COMPRESSION_PROVIDER" not in content assert "CONTEXT_COMPRESSION_MODEL" not in content diff --git a/tests/agent/test_codex_cloudflare_headers.py b/tests/agent/test_codex_cloudflare_headers.py index d04d0b674..67836d17f 100644 --- a/tests/agent/test_codex_cloudflare_headers.py +++ b/tests/agent/test_codex_cloudflare_headers.py @@ -210,7 +210,7 @@ class TestAuxiliaryClientWiring: def test_try_codex_passes_codex_headers(self, monkeypatch): """_try_codex builds the OpenAI client used for compression / vision / title generation when routed through Codex. Must emit codex headers.""" - from hermes_agent.agent import auxiliary_client + from hermes_agent.providers import auxiliary as auxiliary_client token = _make_codex_jwt("acct-aux-try-codex") # Force _select_pool_entry to return "no pool" so we fall through to @@ -235,7 +235,7 @@ class TestAuxiliaryClientWiring: def test_resolve_provider_client_raw_codex_passes_codex_headers(self, monkeypatch): """The ``raw_codex=True`` branch (used by the main agent loop for direct responses.stream() access) must also emit codex headers.""" - from hermes_agent.agent import auxiliary_client + from hermes_agent.providers import auxiliary as auxiliary_client token = _make_codex_jwt("acct-aux-raw-codex") monkeypatch.setattr( auxiliary_client, "_read_codex_access_token", diff --git a/tests/agent/test_gemini_cloudcode.py b/tests/agent/test_gemini_cloudcode.py index 86975fe80..6b5d8e130 100644 --- a/tests/agent/test_gemini_cloudcode.py +++ b/tests/agent/test_gemini_cloudcode.py @@ -131,7 +131,7 @@ class TestClientCredResolution: def test_falls_back_to_scrape_when_defaults_wiped(self, tmp_path, monkeypatch): """Forks that wipe the shipped defaults should still work with gemini-cli.""" - from hermes_agent.agent import google_oauth + from hermes_agent.providers import google_oauth monkeypatch.setattr(google_oauth, "_DEFAULT_CLIENT_ID", "") monkeypatch.setattr(google_oauth, "_DEFAULT_CLIENT_SECRET", "") @@ -153,7 +153,7 @@ class TestClientCredResolution: def test_missing_everything_raises_with_install_hint(self, monkeypatch): """When env + defaults + scrape all fail, raise with install instructions.""" - from hermes_agent.agent import google_oauth + from hermes_agent.providers import google_oauth monkeypatch.setattr(google_oauth, "_DEFAULT_CLIENT_ID", "") monkeypatch.setattr(google_oauth, "_DEFAULT_CLIENT_SECRET", "") @@ -165,13 +165,13 @@ class TestClientCredResolution: assert exc_info.value.code == "google_oauth_client_id_missing" def test_locate_gemini_cli_oauth_js_when_absent(self, monkeypatch): - from hermes_agent.agent import google_oauth + from hermes_agent.providers import google_oauth monkeypatch.setattr("shutil.which", lambda _: None) assert google_oauth._locate_gemini_cli_oauth_js() is None def test_scrape_client_credentials_parses_id_and_secret(self, tmp_path, monkeypatch): - from hermes_agent.agent import google_oauth + from hermes_agent.providers import google_oauth # Create a fake gemini binary and oauth2.js fake_gemini_bin = tmp_path / "bin" / "gemini" @@ -297,7 +297,7 @@ class TestGetValidAccessToken: assert get_valid_access_token() == "cached-token" def test_refreshes_when_near_expiry(self, monkeypatch): - from hermes_agent.agent import google_oauth + from hermes_agent.providers import google_oauth self._save(expires_ms=int((time.time() + 30) * 1000)) monkeypatch.setattr( @@ -307,7 +307,7 @@ class TestGetValidAccessToken: assert google_oauth.get_valid_access_token() == "refreshed" def test_invalid_grant_clears_credentials(self, monkeypatch): - from hermes_agent.agent import google_oauth + from hermes_agent.providers import google_oauth self._save(expires_ms=int((time.time() - 10) * 1000)) @@ -325,7 +325,7 @@ class TestGetValidAccessToken: assert google_oauth.load_credentials() is None def test_preserves_refresh_when_google_omits(self, monkeypatch): - from hermes_agent.agent import google_oauth + from hermes_agent.providers import google_oauth self._save(expires_ms=int((time.time() + 30) * 1000), refresh_token="original-rt") monkeypatch.setattr( @@ -386,7 +386,7 @@ class TestHeadlessDetection: class TestCodeAssistVpcScDetection: def test_detects_vpc_sc_in_json(self): - from hermes_agent.agent.google_code_assist import _is_vpc_sc_violation + from hermes_agent.providers.google_code_assist import _is_vpc_sc_violation body = json.dumps({ "error": { @@ -397,13 +397,13 @@ class TestCodeAssistVpcScDetection: assert _is_vpc_sc_violation(body) is True def test_detects_vpc_sc_in_message(self): - from hermes_agent.agent.google_code_assist import _is_vpc_sc_violation + from hermes_agent.providers.google_code_assist import _is_vpc_sc_violation body = '{"error": {"message": "SECURITY_POLICY_VIOLATED"}}' assert _is_vpc_sc_violation(body) is True def test_non_vpc_sc_returns_false(self): - from hermes_agent.agent.google_code_assist import _is_vpc_sc_violation + from hermes_agent.providers.google_code_assist import _is_vpc_sc_violation assert _is_vpc_sc_violation('{"error": {"message": "not found"}}') is False assert _is_vpc_sc_violation("") is False @@ -411,7 +411,7 @@ class TestCodeAssistVpcScDetection: class TestLoadCodeAssist: def test_parses_response(self, monkeypatch): - from hermes_agent.agent import google_code_assist + from hermes_agent.providers import google_code_assist fake = { "currentTier": {"id": "free-tier"}, @@ -427,7 +427,7 @@ class TestLoadCodeAssist: assert "standard-tier" in info.allowed_tiers def test_vpc_sc_forces_standard_tier(self, monkeypatch): - from hermes_agent.agent import google_code_assist + from hermes_agent.providers import google_code_assist def boom(*a, **kw): raise google_code_assist.CodeAssistError( @@ -443,7 +443,7 @@ class TestLoadCodeAssist: class TestOnboardUser: def test_paid_tier_requires_project_id(self): - from hermes_agent.agent import google_code_assist + from hermes_agent.providers import google_code_assist with pytest.raises(google_code_assist.ProjectIdRequiredError): google_code_assist.onboard_user( @@ -451,7 +451,7 @@ class TestOnboardUser: ) def test_free_tier_no_project_required(self, monkeypatch): - from hermes_agent.agent import google_code_assist + from hermes_agent.providers import google_code_assist monkeypatch.setattr( google_code_assist, "_post_json", @@ -462,7 +462,7 @@ class TestOnboardUser: def test_lro_polling(self, monkeypatch): """Simulate a long-running operation that completes on the second poll.""" - from hermes_agent.agent import google_code_assist + from hermes_agent.providers import google_code_assist call_count = {"n": 0} @@ -484,7 +484,7 @@ class TestOnboardUser: class TestRetrieveUserQuota: def test_parses_buckets(self, monkeypatch): - from hermes_agent.agent import google_code_assist + from hermes_agent.providers import google_code_assist fake = { "buckets": [ @@ -511,24 +511,24 @@ class TestRetrieveUserQuota: class TestResolveProjectContext: def test_configured_shortcircuits(self, monkeypatch): - from hermes_agent.agent.google_code_assist import resolve_project_context + from hermes_agent.providers.google_code_assist import resolve_project_context # Should NOT call loadCodeAssist when configured_project_id is set def should_not_be_called(*a, **kw): raise AssertionError("should short-circuit") monkeypatch.setattr( - "hermes_agent.agent.google_code_assist._post_json", should_not_be_called, + "hermes_agent.providers.google_code_assist._post_json", should_not_be_called, ) ctx = resolve_project_context("at", configured_project_id="proj-abc") assert ctx.project_id == "proj-abc" assert ctx.source == "config" def test_env_shortcircuits(self, monkeypatch): - from hermes_agent.agent.google_code_assist import resolve_project_context + from hermes_agent.providers.google_code_assist import resolve_project_context monkeypatch.setattr( - "hermes_agent.agent.google_code_assist._post_json", + "hermes_agent.providers.google_code_assist._post_json", lambda *a, **kw: (_ for _ in ()).throw(AssertionError("nope")), ) ctx = resolve_project_context("at", env_project_id="env-proj") @@ -536,7 +536,7 @@ class TestResolveProjectContext: assert ctx.source == "env" def test_discovers_via_load_code_assist(self, monkeypatch): - from hermes_agent.agent import google_code_assist + from hermes_agent.providers import google_code_assist monkeypatch.setattr( google_code_assist, "_post_json", @@ -1179,7 +1179,7 @@ class TestGquotaCommand: class TestRunGeminiOauthLoginPure: def test_returns_pool_compatible_dict(self, monkeypatch): - from hermes_agent.agent import google_oauth + from hermes_agent.providers import google_oauth def fake_start(**kw): return google_oauth.GoogleCredentials( diff --git a/tests/agent/test_image_gen_registry.py b/tests/agent/test_image_gen_registry.py index 7c96ece28..39450af4f 100644 --- a/tests/agent/test_image_gen_registry.py +++ b/tests/agent/test_image_gen_registry.py @@ -4,7 +4,7 @@ from __future__ import annotations import pytest -from hermes_agent.agent import image_gen_registry +from hermes_agent.agent.image_gen import registry as image_gen_registry from hermes_agent.agent.image_gen.provider import ImageGenProvider diff --git a/tests/cli/test_cli_init.py b/tests/cli/test_cli_init.py index 76ea8d8e9..a88f50c8a 100644 --- a/tests/cli/test_cli_init.py +++ b/tests/cli/test_cli_init.py @@ -264,7 +264,7 @@ class TestRootLevelProviderOverride: }, })) - import hermes_agent.cli.repl + from hermes_agent.cli import repl as cli monkeypatch.setattr(cli, "_hermes_home", hermes_home) cfg = cli.load_cli_config() @@ -287,7 +287,7 @@ class TestRootLevelProviderOverride: }, })) - import hermes_agent.cli.repl + from hermes_agent.cli import repl as cli monkeypatch.setattr(cli, "_hermes_home", hermes_home) cfg = cli.load_cli_config() diff --git a/tests/cli/test_gquota_command.py b/tests/cli/test_gquota_command.py index e3fe8f5ac..5f810b141 100644 --- a/tests/cli/test_gquota_command.py +++ b/tests/cli/test_gquota_command.py @@ -14,7 +14,7 @@ def test_gquota_uses_chat_console_when_tui_is_live(): 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"): + patch("hermes_agent.providers.google_code_assist.retrieve_user_quota"): cli._handle_gquota_command("/gquota") assert live_console.print.call_count == 2 diff --git a/tests/cron/test_codex_execution_paths.py b/tests/cron/test_codex_execution_paths.py index 9803cee62..51c5d1a86 100644 --- a/tests/cron/test_codex_execution_paths.py +++ b/tests/cron/test_codex_execution_paths.py @@ -10,7 +10,7 @@ sys.modules.setdefault("fal_client", types.SimpleNamespace()) import hermes_agent.cron.scheduler as cron_scheduler import hermes_agent.gateway.run as gateway_run -import hermes_agent.agent.loop +from hermes_agent.agent import loop as run_agent from hermes_agent.gateway.config import Platform from hermes_agent.gateway.session import SessionSource diff --git a/tests/gateway/test_agent_cache.py b/tests/gateway/test_agent_cache.py index 3e86f0041..ee834396d 100644 --- a/tests/gateway/test_agent_cache.py +++ b/tests/gateway/test_agent_cache.py @@ -888,7 +888,7 @@ class TestAgentCacheIdleResume: def test_release_clients_does_not_touch_terminal_or_browser(self, monkeypatch): """release_clients must not call cleanup_vm or cleanup_browser.""" from hermes_agent.agent.loop import AIAgent - from hermes_agent.tools import terminal_tool as _tt + from hermes_agent.tools import terminal as _tt from hermes_agent.tools.browser import tool as _bt agent = AIAgent( @@ -950,7 +950,7 @@ class TestAgentCacheIdleResume: release_clients() (soft — session may resume). """ from hermes_agent.agent.loop import AIAgent - from hermes_agent.tools import terminal_tool as _tt + from hermes_agent.tools import terminal as _tt # Agent A: evicted from cache (soft) — terminal survives. # Agent B: session expired (hard) — terminal torn down. diff --git a/tests/gateway/test_feishu_approval_buttons.py b/tests/gateway/test_feishu_approval_buttons.py index 380ae0599..dfdcfc5c7 100644 --- a/tests/gateway/test_feishu_approval_buttons.py +++ b/tests/gateway/test_feishu_approval_buttons.py @@ -9,12 +9,6 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest -# --------------------------------------------------------------------------- -# Ensure the repo root is importable -# --------------------------------------------------------------------------- -_repo = str(Path(__file__).resolve().parents[2]) -if _repo not in sys.path: - # --------------------------------------------------------------------------- # Minimal Feishu mock so FeishuAdapter can be imported without lark-oapi # --------------------------------------------------------------------------- diff --git a/tests/gateway/test_matrix.py b/tests/gateway/test_matrix.py index 26d36222e..2edc83fec 100644 --- a/tests/gateway/test_matrix.py +++ b/tests/gateway/test_matrix.py @@ -711,7 +711,7 @@ class TestMatrixModuleImport: "sys.meta_path.insert(0, _Blocker())\n" "for k in list(sys.modules):\n" " if k.startswith('mautrix'): del sys.modules[k]\n" - "from gateway.platforms.matrix import check_matrix_requirements\n" + "from hermes_agent.gateway.platforms.matrix import check_matrix_requirements\n" "assert not check_matrix_requirements()\n" "print('OK')\n" )], diff --git a/tests/gateway/test_session_state_cleanup.py b/tests/gateway/test_session_state_cleanup.py index 0877f2711..34ff4e9ac 100644 --- a/tests/gateway/test_session_state_cleanup.py +++ b/tests/gateway/test_session_state_cleanup.py @@ -124,7 +124,7 @@ class TestNoMoreBareDeleteSites: from pathlib import Path import re - gateway_run = (Path(__file__).parent.parent.parent / "gateway" / "run.py").read_text() + gateway_run = (Path(__file__).parent.parent.parent / "hermes_agent" / "gateway" / "run.py").read_text() # Match `del self._running_agents[...]` that is NOT inside a # triple-quoted docstring. We scan non-docstring lines only. lines = gateway_run.splitlines() diff --git a/tests/gateway/test_slack_approval_buttons.py b/tests/gateway/test_slack_approval_buttons.py index 2526af8cf..43c896a47 100644 --- a/tests/gateway/test_slack_approval_buttons.py +++ b/tests/gateway/test_slack_approval_buttons.py @@ -8,12 +8,6 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest -# --------------------------------------------------------------------------- -# Ensure the repo root is importable -# --------------------------------------------------------------------------- -_repo = str(Path(__file__).resolve().parents[2]) -if _repo not in sys.path: - # --------------------------------------------------------------------------- # Minimal Slack SDK mock so SlackAdapter can be imported # --------------------------------------------------------------------------- diff --git a/tests/gateway/test_telegram_approval_buttons.py b/tests/gateway/test_telegram_approval_buttons.py index cfea4c0c0..2cc780212 100644 --- a/tests/gateway/test_telegram_approval_buttons.py +++ b/tests/gateway/test_telegram_approval_buttons.py @@ -8,12 +8,6 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest -# --------------------------------------------------------------------------- -# Ensure the repo root is importable -# --------------------------------------------------------------------------- -_repo = str(Path(__file__).resolve().parents[2]) -if _repo not in sys.path: - # --------------------------------------------------------------------------- # Minimal Telegram mock so TelegramAdapter can be imported # --------------------------------------------------------------------------- diff --git a/tests/gateway/test_telegram_webhook_secret.py b/tests/gateway/test_telegram_webhook_secret.py index 3bdcdb538..08b94f2e1 100644 --- a/tests/gateway/test_telegram_webhook_secret.py +++ b/tests/gateway/test_telegram_webhook_secret.py @@ -16,7 +16,6 @@ from pathlib import Path import pytest _repo = str(Path(__file__).resolve().parents[2]) -if _repo not in sys.path: class TestTelegramWebhookSecretRequired: """Direct source-level check of the webhook-secret guard. @@ -30,7 +29,7 @@ class TestTelegramWebhookSecretRequired: """ def _get_source(self) -> str: - path = Path(_repo) / "gateway" / "platforms" / "telegram.py" + path = Path(_repo) / "hermes_agent" / "gateway" / "platforms" / "telegram.py" return path.read_text(encoding="utf-8") def test_webhook_branch_checks_secret(self): diff --git a/tests/gateway/test_update_command.py b/tests/gateway/test_update_command.py index 8e8ebf4ca..3864c9e75 100644 --- a/tests/gateway/test_update_command.py +++ b/tests/gateway/test_update_command.py @@ -88,9 +88,9 @@ class TestHandleUpdateCommand: pass # Actually, simplest: just patch the specific file attr - fake_file = str(fake_root / "gateway" / "run.py") - (fake_root / "gateway").mkdir(parents=True) - (fake_root / "gateway" / "run.py").touch() + fake_file = str(fake_root / "hermes_agent" / "gateway" / "run.py") + (fake_root / "hermes_agent" / "gateway").mkdir(parents=True) + (fake_root / "hermes_agent" / "gateway" / "run.py").touch() with patch("hermes_agent.gateway.run.__file__", fake_file): result = await runner._handle_update_command(event) @@ -107,9 +107,9 @@ class TestHandleUpdateCommand: fake_root = tmp_path / "project" fake_root.mkdir() (fake_root / ".git").mkdir() - (fake_root / "gateway").mkdir() - (fake_root / "gateway" / "run.py").touch() - fake_file = str(fake_root / "gateway" / "run.py") + (fake_root / "hermes_agent" / "gateway").mkdir(parents=True) + (fake_root / "hermes_agent" / "gateway" / "run.py").touch() + fake_file = str(fake_root / "hermes_agent" / "gateway" / "run.py") with patch("hermes_agent.gateway.run._hermes_home", tmp_path), \ patch("hermes_agent.gateway.run.__file__", fake_file), \ @@ -129,9 +129,9 @@ class TestHandleUpdateCommand: fake_root = tmp_path / "project" fake_root.mkdir() (fake_root / ".git").mkdir() - (fake_root / "gateway").mkdir() - (fake_root / "gateway" / "run.py").touch() - fake_file = str(fake_root / "gateway" / "run.py") + (fake_root / "hermes_agent" / "gateway").mkdir(parents=True) + (fake_root / "hermes_agent" / "gateway" / "run.py").touch() + fake_file = str(fake_root / "hermes_agent" / "gateway" / "run.py") hermes_home = tmp_path / "hermes" hermes_home.mkdir() @@ -194,9 +194,9 @@ class TestHandleUpdateCommand: fake_root = tmp_path / "project" fake_root.mkdir() (fake_root / ".git").mkdir() - (fake_root / "gateway").mkdir() - (fake_root / "gateway" / "run.py").touch() - fake_file = str(fake_root / "gateway" / "run.py") + (fake_root / "hermes_agent" / "gateway").mkdir(parents=True) + (fake_root / "hermes_agent" / "gateway" / "run.py").touch() + fake_file = str(fake_root / "hermes_agent" / "gateway" / "run.py") hermes_home = tmp_path / "hermes" hermes_home.mkdir() @@ -223,9 +223,9 @@ class TestHandleUpdateCommand: fake_root = tmp_path / "project" fake_root.mkdir() (fake_root / ".git").mkdir() - (fake_root / "gateway").mkdir() - (fake_root / "gateway" / "run.py").touch() - fake_file = str(fake_root / "gateway" / "run.py") + (fake_root / "hermes_agent" / "gateway").mkdir(parents=True) + (fake_root / "hermes_agent" / "gateway" / "run.py").touch() + fake_file = str(fake_root / "hermes_agent" / "gateway" / "run.py") hermes_home = tmp_path / "hermes" hermes_home.mkdir() @@ -252,9 +252,9 @@ class TestHandleUpdateCommand: fake_root = tmp_path / "project" fake_root.mkdir() (fake_root / ".git").mkdir() - (fake_root / "gateway").mkdir() - (fake_root / "gateway" / "run.py").touch() - fake_file = str(fake_root / "gateway" / "run.py") + (fake_root / "hermes_agent" / "gateway").mkdir(parents=True) + (fake_root / "hermes_agent" / "gateway" / "run.py").touch() + fake_file = str(fake_root / "hermes_agent" / "gateway" / "run.py") hermes_home = tmp_path / "hermes" hermes_home.mkdir() @@ -292,9 +292,9 @@ class TestHandleUpdateCommand: fake_root = tmp_path / "project" fake_root.mkdir() (fake_root / ".git").mkdir() - (fake_root / "gateway").mkdir() - (fake_root / "gateway" / "run.py").touch() - fake_file = str(fake_root / "gateway" / "run.py") + (fake_root / "hermes_agent" / "gateway").mkdir(parents=True) + (fake_root / "hermes_agent" / "gateway" / "run.py").touch() + fake_file = str(fake_root / "hermes_agent" / "gateway" / "run.py") hermes_home = tmp_path / "hermes" hermes_home.mkdir() @@ -318,9 +318,9 @@ class TestHandleUpdateCommand: fake_root = tmp_path / "project" fake_root.mkdir() (fake_root / ".git").mkdir() - (fake_root / "gateway").mkdir() - (fake_root / "gateway" / "run.py").touch() - fake_file = str(fake_root / "gateway" / "run.py") + (fake_root / "hermes_agent" / "gateway").mkdir(parents=True) + (fake_root / "hermes_agent" / "gateway" / "run.py").touch() + fake_file = str(fake_root / "hermes_agent" / "gateway" / "run.py") hermes_home = tmp_path / "hermes" hermes_home.mkdir() diff --git a/tests/gateway/test_update_streaming.py b/tests/gateway/test_update_streaming.py index febd20def..310b4be1a 100644 --- a/tests/gateway/test_update_streaming.py +++ b/tests/gateway/test_update_streaming.py @@ -211,9 +211,9 @@ class TestUpdateCommandGatewayFlag: fake_root = tmp_path / "project" fake_root.mkdir() (fake_root / ".git").mkdir() - (fake_root / "gateway").mkdir() - (fake_root / "gateway" / "run.py").touch() - fake_file = str(fake_root / "gateway" / "run.py") + (fake_root / "hermes_agent" / "gateway").mkdir(parents=True) + (fake_root / "hermes_agent" / "gateway" / "run.py").touch() + fake_file = str(fake_root / "hermes_agent" / "gateway" / "run.py") hermes_home = tmp_path / "hermes" hermes_home.mkdir() diff --git a/tests/hermes_cli/test_ai_gateway_models.py b/tests/hermes_cli/test_ai_gateway_models.py index 144e72c66..b4f651160 100644 --- a/tests/hermes_cli/test_ai_gateway_models.py +++ b/tests/hermes_cli/test_ai_gateway_models.py @@ -8,7 +8,7 @@ pin the translation and the curated-list filtering. import json from unittest.mock import patch, MagicMock -from hermes_agent.cli import models as models_module +from hermes_agent.cli.models import models as models_module from hermes_agent.cli.models.models import ( VERCEL_AI_GATEWAY_MODELS, _ai_gateway_model_is_free, diff --git a/tests/hermes_cli/test_backup.py b/tests/hermes_cli/test_backup.py index 848abdee6..885b8201a 100644 --- a/tests/hermes_cli/test_backup.py +++ b/tests/hermes_cli/test_backup.py @@ -51,7 +51,8 @@ def _make_hermes_tree(root: Path) -> None: # hermes-agent repo (should be EXCLUDED) (root / "hermes-agent").mkdir(exist_ok=True) - (root / "hermes-agent" / "hermes_agent/agent/loop.py").write_text("# big file\n") + (root / "hermes-agent" / "hermes_agent" / "agent").mkdir(parents=True, exist_ok=True) + (root / "hermes-agent" / "hermes_agent" / "agent" / "loop.py").write_text("# big file\n") (root / "hermes-agent" / ".git").mkdir() (root / "hermes-agent" / ".git" / "HEAD").write_text("ref: refs/heads/main\n") diff --git a/tests/hermes_cli/test_banner.py b/tests/hermes_cli/test_banner.py index 834d1d065..883b19ff9 100644 --- a/tests/hermes_cli/test_banner.py +++ b/tests/hermes_cli/test_banner.py @@ -5,8 +5,8 @@ from unittest.mock import patch from rich.console import Console import hermes_agent.cli.ui.banner as banner -import hermes_agent.tools.dispatch -import hermes_agent.tools.mcp.tool +from hermes_agent.tools import dispatch as model_tools +from hermes_agent.tools.mcp import tool as mcp_tool def test_display_toolset_name_strips_legacy_suffix(): @@ -42,7 +42,7 @@ def test_build_welcome_banner_uses_normalized_toolset_names(): ), patch.object(banner, "get_available_skills", return_value={}), patch.object(banner, "get_update_result", return_value=None), - patch.object(tools.mcp_tool, "get_mcp_status", return_value=[]), + patch.object(mcp_tool, "get_mcp_status", return_value=[]), ): console = Console( record=True, force_terminal=False, color_system=None, width=160 diff --git a/tests/hermes_cli/test_banner_git_state.py b/tests/hermes_cli/test_banner_git_state.py index 2bc18f6dc..c7c6e43cb 100644 --- a/tests/hermes_cli/test_banner_git_state.py +++ b/tests/hermes_cli/test_banner_git_state.py @@ -2,7 +2,7 @@ from unittest.mock import MagicMock, patch def test_format_banner_version_label_without_git_state(): - from hermes_agent.cli import banner + from hermes_agent.cli.ui import banner with patch.object(banner, "get_git_banner_state", return_value=None): value = banner.format_banner_version_label() @@ -11,7 +11,7 @@ def test_format_banner_version_label_without_git_state(): def test_format_banner_version_label_on_upstream_main(): - from hermes_agent.cli import banner + from hermes_agent.cli.ui import banner with patch.object( banner, @@ -25,7 +25,7 @@ def test_format_banner_version_label_on_upstream_main(): def test_format_banner_version_label_with_carried_commits(): - from hermes_agent.cli import banner + from hermes_agent.cli.ui import banner with patch.object( banner, @@ -40,7 +40,7 @@ def test_format_banner_version_label_with_carried_commits(): def test_get_git_banner_state_reads_origin_and_head(tmp_path): - from hermes_agent.cli import banner + from hermes_agent.cli.ui import banner repo_dir = tmp_path / "repo" (repo_dir / ".git").mkdir(parents=True) diff --git a/tests/hermes_cli/test_dingtalk_auth.py b/tests/hermes_cli/test_dingtalk_auth.py index 6701a7363..4b6c99a12 100644 --- a/tests/hermes_cli/test_dingtalk_auth.py +++ b/tests/hermes_cli/test_dingtalk_auth.py @@ -165,7 +165,7 @@ class TestWaitForSuccess: class TestRenderQR: def test_returns_false_when_qrcode_missing(self, monkeypatch): - from hermes_agent.cli import dingtalk_auth + from hermes_agent.cli.auth import dingtalk as dingtalk_auth # Simulate qrcode import failure monkeypatch.setitem(sys.modules, "qrcode", None) diff --git a/tests/hermes_cli/test_doctor.py b/tests/hermes_cli/test_doctor.py index 74bcdf26c..d17f943f6 100644 --- a/tests/hermes_cli/test_doctor.py +++ b/tests/hermes_cli/test_doctor.py @@ -191,7 +191,7 @@ class TestDoctorMemoryProviderSection: # Stub auth checks to avoid real API calls try: - from hermes_agent.cli import auth as _auth_mod + from hermes_agent.cli.auth import auth as _auth_mod monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {}) monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {}) except Exception: @@ -279,7 +279,7 @@ def test_run_doctor_termux_does_not_mark_browser_available_without_agent_browser monkeypatch.setitem(sys.modules, "hermes_agent.tools.dispatch", fake_model_tools) try: - from hermes_agent.cli import auth as _auth_mod + from hermes_agent.cli.auth import auth as _auth_mod monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {}) monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {}) except Exception: @@ -318,7 +318,7 @@ def test_run_doctor_kimi_cn_env_is_detected_and_probe_is_null_safe(monkeypatch, monkeypatch.setitem(sys.modules, "hermes_agent.tools.dispatch", fake_model_tools) try: - from hermes_agent.cli import auth as _auth_mod + from hermes_agent.cli.auth import auth as _auth_mod monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {}) monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {}) except Exception: @@ -370,7 +370,7 @@ def test_run_doctor_opencode_go_skips_invalid_models_probe(monkeypatch, tmp_path monkeypatch.setitem(sys.modules, "hermes_agent.tools.dispatch", fake_model_tools) try: - from hermes_agent.cli import auth as _auth_mod + from hermes_agent.cli.auth import auth as _auth_mod monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {}) monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {}) except ImportError: diff --git a/tests/hermes_cli/test_doctor_command_install.py b/tests/hermes_cli/test_doctor_command_install.py index c4c678074..8cd8f7d9a 100644 --- a/tests/hermes_cli/test_doctor_command_install.py +++ b/tests/hermes_cli/test_doctor_command_install.py @@ -40,7 +40,7 @@ def _setup_doctor_env(monkeypatch, tmp_path, venv_name="venv"): # Stub auth checks try: - from hermes_agent.cli import auth as _auth_mod + from hermes_agent.cli.auth import auth as _auth_mod monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {}) monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {}) except Exception: @@ -175,7 +175,7 @@ class TestDoctorCommandInstallation: ) monkeypatch.setitem(sys.modules, "hermes_agent.tools.dispatch", fake_model_tools) try: - from hermes_agent.cli import auth as _auth_mod + from hermes_agent.cli.auth import auth as _auth_mod monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {}) monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {}) except Exception: @@ -260,7 +260,7 @@ class TestDoctorCommandInstallation: ) monkeypatch.setitem(sys.modules, "hermes_agent.tools.dispatch", fake_model_tools) try: - from hermes_agent.cli import auth as _auth_mod + from hermes_agent.cli.auth import auth as _auth_mod monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {}) monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {}) except Exception: diff --git a/tests/hermes_cli/test_gateway_wsl.py b/tests/hermes_cli/test_gateway_wsl.py index 71efd4a95..ea7cd6246 100644 --- a/tests/hermes_cli/test_gateway_wsl.py +++ b/tests/hermes_cli/test_gateway_wsl.py @@ -9,7 +9,7 @@ from unittest.mock import patch, MagicMock, mock_open import pytest import hermes_agent.cli.gateway as gateway -import hermes_agent.constants +from hermes_agent import constants as hermes_constants # ============================================================================= diff --git a/tests/hermes_cli/test_gemini_provider.py b/tests/hermes_cli/test_gemini_provider.py index eb26965af..3865eeccb 100644 --- a/tests/hermes_cli/test_gemini_provider.py +++ b/tests/hermes_cli/test_gemini_provider.py @@ -183,7 +183,7 @@ class TestGeminiAgentInit: def test_agent_imports_without_error(self): """Verify run_agent.py has no SyntaxError (the critical bug).""" import importlib - import hermes_agent.agent.loop + from hermes_agent.agent import loop as run_agent importlib.reload(run_agent) def test_gemini_agent_uses_chat_completions(self, monkeypatch): diff --git a/tests/hermes_cli/test_image_gen_picker.py b/tests/hermes_cli/test_image_gen_picker.py index 3a16f29bd..83539372d 100644 --- a/tests/hermes_cli/test_image_gen_picker.py +++ b/tests/hermes_cli/test_image_gen_picker.py @@ -8,7 +8,7 @@ from __future__ import annotations import pytest -from hermes_agent.agent import image_gen_registry +from hermes_agent.agent.image_gen import registry as image_gen_registry from hermes_agent.agent.image_gen.provider import ImageGenProvider diff --git a/tests/hermes_cli/test_logs.py b/tests/hermes_cli/test_logs.py index adf798628..6ae7d18eb 100644 --- a/tests/hermes_cli/test_logs.py +++ b/tests/hermes_cli/test_logs.py @@ -86,27 +86,27 @@ class TestExtractLevel: class TestExtractLoggerName: def test_standard_line(self): - line = "2026-04-11 10:23:45 INFO gateway.run: Starting gateway" + line = "2026-04-11 10:23:45 INFO hermes_agent.gateway.run: Starting gateway" assert _extract_logger_name(line) == "hermes_agent.gateway.run" def test_nested_logger(self): - line = "2026-04-11 10:23:45 INFO gateway.platforms.telegram: connected" + line = "2026-04-11 10:23:45 INFO hermes_agent.gateway.platforms.telegram: connected" assert _extract_logger_name(line) == "hermes_agent.gateway.platforms.telegram" def test_warning_level(self): - line = "2026-04-11 10:23:45 WARNING tools.terminal_tool: timeout" + line = "2026-04-11 10:23:45 WARNING hermes_agent.tools.terminal: timeout" assert _extract_logger_name(line) == "hermes_agent.tools.terminal" def test_with_session_tag(self): - line = "2026-04-11 10:23:45 INFO [abc123] tools.file_tools: reading file" + line = "2026-04-11 10:23:45 INFO [abc123] hermes_agent.tools.files.tools: reading file" assert _extract_logger_name(line) == "hermes_agent.tools.files.tools" def test_with_session_tag_and_error(self): - line = "2026-04-11 10:23:45 ERROR [sess_xyz] agent.context_compressor: failed" + line = "2026-04-11 10:23:45 ERROR [sess_xyz] hermes_agent.agent.context.compressor: failed" assert _extract_logger_name(line) == "hermes_agent.agent.context.compressor" def test_top_level_module(self): - line = "2026-04-11 10:23:45 INFO run_agent: starting conversation" + line = "2026-04-11 10:23:45 INFO hermes_agent.agent.loop: starting conversation" assert _extract_logger_name(line) == "hermes_agent.agent.loop" def test_no_match(self): @@ -115,33 +115,33 @@ class TestExtractLoggerName: class TestLineMatchesComponent: def test_gateway_component(self): - line = "2026-04-11 10:23:45 INFO gateway.run: msg" - assert _line_matches_component(line, ("gateway",)) + line = "2026-04-11 10:23:45 INFO hermes_agent.gateway.run: msg" + assert _line_matches_component(line, ("hermes_agent.gateway",)) def test_gateway_nested(self): - line = "2026-04-11 10:23:45 INFO gateway.platforms.telegram: msg" - assert _line_matches_component(line, ("gateway",)) + line = "2026-04-11 10:23:45 INFO hermes_agent.gateway.platforms.telegram: msg" + assert _line_matches_component(line, ("hermes_agent.gateway",)) def test_tools_component(self): - line = "2026-04-11 10:23:45 INFO tools.terminal_tool: msg" - assert _line_matches_component(line, ("tools",)) + line = "2026-04-11 10:23:45 INFO hermes_agent.tools.terminal: msg" + assert _line_matches_component(line, ("hermes_agent.tools",)) def test_agent_with_multiple_prefixes(self): - prefixes = ("agent", "hermes_agent.agent.loop", "hermes_agent.tools.dispatch") + prefixes = ("hermes_agent.agent", "hermes_agent.tools.dispatch") assert _line_matches_component( - "2026-04-11 10:23:45 INFO agent.context_compressor: msg", prefixes) + "2026-04-11 10:23:45 INFO hermes_agent.agent.context.compressor: msg", prefixes) assert _line_matches_component( - "2026-04-11 10:23:45 INFO run_agent: msg", prefixes) + "2026-04-11 10:23:45 INFO hermes_agent.agent.loop: msg", prefixes) assert _line_matches_component( - "2026-04-11 10:23:45 INFO model_tools: msg", prefixes) + "2026-04-11 10:23:45 INFO hermes_agent.tools.dispatch: msg", prefixes) def test_no_match(self): - line = "2026-04-11 10:23:45 INFO tools.browser: msg" - assert not _line_matches_component(line, ("gateway",)) + line = "2026-04-11 10:23:45 INFO hermes_agent.tools.browser: msg" + assert not _line_matches_component(line, ("hermes_agent.gateway",)) def test_with_session_tag(self): - line = "2026-04-11 10:23:45 INFO [abc] gateway.run: msg" - assert _line_matches_component(line, ("gateway",)) + line = "2026-04-11 10:23:45 INFO [abc] hermes_agent.gateway.run: msg" + assert _line_matches_component(line, ("hermes_agent.gateway",)) def test_unparseable_line(self): assert not _line_matches_component("random text", ("gateway",)) diff --git a/tests/hermes_cli/test_ollama_cloud_provider.py b/tests/hermes_cli/test_ollama_cloud_provider.py index d5071e9e2..633ca2264 100644 --- a/tests/hermes_cli/test_ollama_cloud_provider.py +++ b/tests/hermes_cli/test_ollama_cloud_provider.py @@ -351,7 +351,7 @@ class TestOllamaCloudAgentInit: def test_agent_imports_without_error(self): """Verify run_agent.py has no SyntaxError.""" import importlib - import hermes_agent.agent.loop + from hermes_agent.agent import loop as run_agent importlib.reload(run_agent) def test_ollama_cloud_agent_uses_chat_completions(self, monkeypatch): diff --git a/tests/hermes_cli/test_plugin_scanner_recursion.py b/tests/hermes_cli/test_plugin_scanner_recursion.py index 1cda75b7e..7e7565473 100644 --- a/tests/hermes_cli/test_plugin_scanner_recursion.py +++ b/tests/hermes_cli/test_plugin_scanner_recursion.py @@ -293,7 +293,7 @@ class TestBundledBackendAutoLoad: class TestRegisterImageGenProvider: def test_accepts_valid_provider(self, tmp_path, monkeypatch): - from hermes_agent.agent import image_gen_registry + from hermes_agent.agent.image_gen import registry as image_gen_registry from hermes_agent.agent.image_gen.provider import ImageGenProvider image_gen_registry._reset_for_tests() @@ -332,7 +332,7 @@ class TestRegisterImageGenProvider: image_gen_registry._reset_for_tests() def test_rejects_non_provider(self, tmp_path, monkeypatch, caplog): - from hermes_agent.agent import image_gen_registry + from hermes_agent.agent.image_gen import registry as image_gen_registry image_gen_registry._reset_for_tests() diff --git a/tests/hermes_cli/test_sessions_delete.py b/tests/hermes_cli/test_sessions_delete.py index 0865f3be0..c2353067d 100644 --- a/tests/hermes_cli/test_sessions_delete.py +++ b/tests/hermes_cli/test_sessions_delete.py @@ -3,8 +3,7 @@ import sys def test_sessions_delete_accepts_unique_id_prefix(monkeypatch, capsys): import hermes_agent.cli.main as main_mod - import hermes_agent.state - + from hermes_agent import state as hermes_state captured = {} class FakeDB: @@ -39,8 +38,7 @@ def test_sessions_delete_accepts_unique_id_prefix(monkeypatch, capsys): def test_sessions_delete_reports_not_found_when_prefix_is_unknown(monkeypatch, capsys): import hermes_agent.cli.main as main_mod - import hermes_agent.state - + from hermes_agent import state as hermes_state class FakeDB: def resolve_session_id(self, session_id): return None @@ -67,8 +65,7 @@ def test_sessions_delete_reports_not_found_when_prefix_is_unknown(monkeypatch, c def test_sessions_delete_handles_eoferror_on_confirm(monkeypatch, capsys): """sessions delete should not crash when stdin is closed (non-TTY).""" import hermes_agent.cli.main as main_mod - import hermes_agent.state - + from hermes_agent import state as hermes_state class FakeDB: def resolve_session_id(self, session_id): return "20260315_092437_c9a6ff" @@ -95,8 +92,7 @@ def test_sessions_delete_handles_eoferror_on_confirm(monkeypatch, capsys): def test_sessions_prune_handles_eoferror_on_confirm(monkeypatch, capsys): """sessions prune should not crash when stdin is closed (non-TTY).""" import hermes_agent.cli.main as main_mod - import hermes_agent.state - + from hermes_agent import state as hermes_state class FakeDB: def prune_sessions(self, **kwargs): raise AssertionError("prune_sessions should not be called when cancelled") diff --git a/tests/hermes_cli/test_setup.py b/tests/hermes_cli/test_setup.py index 68465d611..4ac537dcc 100644 --- a/tests/hermes_cli/test_setup.py +++ b/tests/hermes_cli/test_setup.py @@ -7,7 +7,7 @@ import pytest from hermes_agent.cli.auth.auth import get_active_provider from hermes_agent.cli.config import load_config, save_config -from hermes_agent.cli import setup as setup_mod +from hermes_agent.cli import setup_wizard as setup_mod from hermes_agent.cli.setup_wizard import setup_model_provider @@ -212,7 +212,7 @@ def test_setup_gateway_in_container_shows_docker_guidance(monkeypatch, capsys): monkeypatch.setattr(gateway_mod, "_is_service_running", lambda: False) # Patch is_container at the import location in setup.py - import hermes_agent.constants + from hermes_agent import constants as hermes_constants monkeypatch.setattr(hermes_constants, "is_container", lambda: True) setup_mod.setup_gateway({}) @@ -446,7 +446,7 @@ def test_modal_setup_persists_direct_mode_when_user_chooses_their_own_account(tm def test_resolve_hermes_chat_argv_prefers_which(monkeypatch): - from hermes_agent.cli import setup as setup_mod + from hermes_agent.cli import setup_wizard as setup_mod monkeypatch.setattr(setup_mod.shutil, "which", lambda name: "/usr/local/bin/hermes" if name == "hermes" else None) @@ -454,7 +454,7 @@ def test_resolve_hermes_chat_argv_prefers_which(monkeypatch): def test_resolve_hermes_chat_argv_falls_back_to_module(monkeypatch): - from hermes_agent.cli import setup as setup_mod + from hermes_agent.cli import setup_wizard as setup_mod monkeypatch.setattr(setup_mod.shutil, "which", lambda _name: None) monkeypatch.setattr(setup_mod.importlib.util, "find_spec", lambda name: object() if name == "hermes_agent.cli" else None) @@ -463,7 +463,7 @@ def test_resolve_hermes_chat_argv_falls_back_to_module(monkeypatch): def test_offer_launch_chat_execs_fresh_process(monkeypatch): - from hermes_agent.cli import setup as setup_mod + from hermes_agent.cli import setup_wizard as setup_mod monkeypatch.setattr(setup_mod, "prompt_yes_no", lambda *_args, **_kwargs: True) monkeypatch.setattr(setup_mod, "_resolve_hermes_chat_argv", lambda: ["/usr/local/bin/hermes", "chat"]) @@ -483,7 +483,7 @@ def test_offer_launch_chat_execs_fresh_process(monkeypatch): def test_offer_launch_chat_manual_fallback_when_unresolvable(monkeypatch, capsys): - from hermes_agent.cli import setup as setup_mod + from hermes_agent.cli import setup_wizard as setup_mod monkeypatch.setattr(setup_mod, "prompt_yes_no", lambda *_args, **_kwargs: True) monkeypatch.setattr(setup_mod, "_resolve_hermes_chat_argv", lambda: None) diff --git a/tests/hermes_cli/test_setup_matrix_e2ee.py b/tests/hermes_cli/test_setup_matrix_e2ee.py index d965e354a..ba89fc436 100644 --- a/tests/hermes_cli/test_setup_matrix_e2ee.py +++ b/tests/hermes_cli/test_setup_matrix_e2ee.py @@ -6,7 +6,7 @@ import pytest def _parse_setup_imports(): """Parse setup.py and return top-level import names.""" - with open("hermes_cli/setup.py") as f: + with open("hermes_agent/cli/setup_wizard.py") as f: tree = ast.parse(f.read()) names = set() for node in ast.walk(tree): diff --git a/tests/hermes_cli/test_setup_noninteractive.py b/tests/hermes_cli/test_setup_noninteractive.py index b2afb7206..dfc3e0566 100644 --- a/tests/hermes_cli/test_setup_noninteractive.py +++ b/tests/hermes_cli/test_setup_noninteractive.py @@ -146,7 +146,7 @@ class TestNonInteractiveSetup: def test_returning_user_terminal_menu_choice_dispatches_terminal_section(self, tmp_path): """Returning-user menu should map Terminal Backend to the terminal setup, not TTS.""" - from hermes_agent.cli import setup as setup_mod + from hermes_agent.cli import setup_wizard as setup_mod args = _make_setup_args() config = {} @@ -191,7 +191,7 @@ class TestNonInteractiveSetup: def test_returning_user_menu_does_not_show_separator_rows(self, tmp_path): """Returning-user menu should only show selectable actions.""" - from hermes_agent.cli import setup as setup_mod + from hermes_agent.cli import setup_wizard as setup_mod args = _make_setup_args() captured = {} diff --git a/tests/hermes_cli/test_setup_openclaw_migration.py b/tests/hermes_cli/test_setup_openclaw_migration.py index 757fd588a..cf2239e29 100644 --- a/tests/hermes_cli/test_setup_openclaw_migration.py +++ b/tests/hermes_cli/test_setup_openclaw_migration.py @@ -4,7 +4,7 @@ from argparse import Namespace from types import ModuleType from unittest.mock import MagicMock, patch -from hermes_agent.cli import setup as setup_mod +from hermes_agent.cli import setup_wizard as setup_mod # --------------------------------------------------------------------------- diff --git a/tests/hermes_cli/test_setup_prompt_menus.py b/tests/hermes_cli/test_setup_prompt_menus.py index e082fa747..ef29a417d 100644 --- a/tests/hermes_cli/test_setup_prompt_menus.py +++ b/tests/hermes_cli/test_setup_prompt_menus.py @@ -1,4 +1,4 @@ -from hermes_agent.cli import setup as setup_mod +from hermes_agent.cli import setup_wizard as setup_mod def test_prompt_choice_uses_curses_helper(monkeypatch): diff --git a/tests/hermes_cli/test_skin_engine.py b/tests/hermes_cli/test_skin_engine.py index b1fb27445..b52d7fbcf 100644 --- a/tests/hermes_cli/test_skin_engine.py +++ b/tests/hermes_cli/test_skin_engine.py @@ -10,7 +10,7 @@ from unittest.mock import patch @pytest.fixture(autouse=True) def reset_skin_state(): """Reset skin engine state between tests.""" - from hermes_agent.cli import skin_engine + from hermes_agent.cli.ui import skin_engine skin_engine._active_skin = None skin_engine._active_skin_name = "default" yield diff --git a/tests/hermes_cli/test_status.py b/tests/hermes_cli/test_status.py index d445fbbaf..993492e5a 100644 --- a/tests/hermes_cli/test_status.py +++ b/tests/hermes_cli/test_status.py @@ -15,7 +15,7 @@ def test_show_status_includes_tavily_key(monkeypatch, capsys, tmp_path): def test_show_status_termux_gateway_section_skips_systemctl(monkeypatch, capsys, tmp_path): - from hermes_agent.cli import status as status_mod + from hermes_agent.cli.ui import status as status_mod import hermes_agent.cli.auth.auth as auth_mod import hermes_agent.cli.gateway as gateway_mod diff --git a/tests/hermes_cli/test_status_model_provider.py b/tests/hermes_cli/test_status_model_provider.py index 3f1a22a4f..001a05cd7 100644 --- a/tests/hermes_cli/test_status_model_provider.py +++ b/tests/hermes_cli/test_status_model_provider.py @@ -27,7 +27,7 @@ def _patch_common_status_deps(monkeypatch, status_mod, tmp_path, *, openai_base_ def test_show_status_displays_configured_dict_model_and_provider_label(monkeypatch, capsys, tmp_path): - from hermes_agent.cli import status as status_mod + from hermes_agent.cli.ui import status as status_mod _patch_common_status_deps(monkeypatch, status_mod, tmp_path) monkeypatch.setattr( @@ -48,7 +48,7 @@ def test_show_status_displays_configured_dict_model_and_provider_label(monkeypat def test_show_status_displays_legacy_string_model_and_custom_endpoint(monkeypatch, capsys, tmp_path): - from hermes_agent.cli import status as status_mod + from hermes_agent.cli.ui import status as status_mod _patch_common_status_deps(monkeypatch, status_mod, tmp_path, openai_base_url="http://localhost:8080/v1") monkeypatch.setattr(status_mod, "load_config", lambda: {"model": "qwen3:latest"}, raising=False) @@ -65,7 +65,7 @@ def test_show_status_displays_legacy_string_model_and_custom_endpoint(monkeypatc def test_show_status_reports_managed_nous_features(monkeypatch, capsys, tmp_path): monkeypatch.setattr("hermes_agent.cli.ui.status.managed_nous_tools_enabled", lambda: True) - from hermes_agent.cli import status as status_mod + from hermes_agent.cli.ui import status as status_mod _patch_common_status_deps(monkeypatch, status_mod, tmp_path) monkeypatch.setattr( @@ -105,7 +105,7 @@ def test_show_status_reports_managed_nous_features(monkeypatch, capsys, tmp_path def test_show_status_hides_nous_subscription_section_when_feature_flag_is_off(monkeypatch, capsys, tmp_path): monkeypatch.setattr("hermes_agent.cli.ui.status.managed_nous_tools_enabled", lambda: False) - from hermes_agent.cli import status as status_mod + from hermes_agent.cli.ui import status as status_mod _patch_common_status_deps(monkeypatch, status_mod, tmp_path) monkeypatch.setattr( diff --git a/tests/hermes_cli/test_subprocess_timeouts.py b/tests/hermes_cli/test_subprocess_timeouts.py index 47146aac4..a83b4d71b 100644 --- a/tests/hermes_cli/test_subprocess_timeouts.py +++ b/tests/hermes_cli/test_subprocess_timeouts.py @@ -7,10 +7,10 @@ import pytest # Parameterise over every CLI module that calls subprocess.run _CLI_MODULES = [ - "hermes_cli/doctor.py", - "hermes_cli/status.py", - "hermes_cli/clipboard.py", - "hermes_cli/banner.py", + "hermes_agent/cli/doctor.py", + "hermes_agent/cli/ui/status.py", + "hermes_agent/cli/clipboard.py", + "hermes_agent/cli/ui/banner.py", ] diff --git a/tests/hermes_cli/test_web_server.py b/tests/hermes_cli/test_web_server.py index fe7c9ada1..ad54bceee 100644 --- a/tests/hermes_cli/test_web_server.py +++ b/tests/hermes_cli/test_web_server.py @@ -108,7 +108,7 @@ class TestWebServerEndpoints: except ImportError: pytest.skip("fastapi/starlette not installed") - import hermes_agent.state + from hermes_agent import state as hermes_state from hermes_agent.constants import get_hermes_home from hermes_agent.cli.web_server import app, _SESSION_TOKEN @@ -522,7 +522,7 @@ class TestNewEndpoints: except ImportError: pytest.skip("fastapi/starlette not installed") - import hermes_agent.state + from hermes_agent import state as hermes_state from hermes_agent.constants import get_hermes_home from hermes_agent.cli.web_server import app, _SESSION_TOKEN diff --git a/tests/hermes_cli/test_web_server_host_header.py b/tests/hermes_cli/test_web_server_host_header.py index 829db9733..fafd2e6a6 100644 --- a/tests/hermes_cli/test_web_server_host_header.py +++ b/tests/hermes_cli/test_web_server_host_header.py @@ -14,9 +14,6 @@ from pathlib import Path import pytest -_repo = str(Path(__file__).resolve().parents[1]) -if _repo not in sys.path: - class TestHostHeaderValidator: """Unit test the _is_accepted_host helper directly — cheaper and more thorough than spinning up the full FastAPI app.""" diff --git a/tests/hermes_cli/test_xiaomi_provider.py b/tests/hermes_cli/test_xiaomi_provider.py index ae37b5154..172f28354 100644 --- a/tests/hermes_cli/test_xiaomi_provider.py +++ b/tests/hermes_cli/test_xiaomi_provider.py @@ -148,7 +148,7 @@ class TestXiaomiModelCatalog: def test_list_agentic_models_mock(self, monkeypatch): """When models.dev returns Xiaomi data, list_agentic_models should return models.""" - from hermes_agent.agent import models_dev as md + from hermes_agent.providers import metadata_dev as md fake_data = { "xiaomi": { diff --git a/tests/plugins/test_disk_cleanup_plugin.py b/tests/plugins/test_disk_cleanup_plugin.py index d4e110871..993e0dfa6 100644 --- a/tests/plugins/test_disk_cleanup_plugin.py +++ b/tests/plugins/test_disk_cleanup_plugin.py @@ -37,7 +37,7 @@ def _isolate_env(tmp_path, monkeypatch): def _load_lib(): """Import the plugin's library module directly from the repo path.""" repo_root = Path(__file__).resolve().parents[2] - lib_path = repo_root / "plugins" / "disk-cleanup" / "disk_cleanup.py" + lib_path = repo_root / "hermes_agent" / "plugins" / "disk-cleanup" / "disk_cleanup.py" spec = importlib.util.spec_from_file_location( "disk_cleanup_under_test", lib_path ) @@ -49,7 +49,7 @@ def _load_lib(): def _load_plugin_init(): """Import the plugin's __init__.py (which depends on the library).""" repo_root = Path(__file__).resolve().parents[2] - plugin_dir = repo_root / "plugins" / "disk-cleanup" + plugin_dir = repo_root / "hermes_agent" / "plugins" / "disk-cleanup" # Use the PluginManager's module naming convention so relative imports work. spec = importlib.util.spec_from_file_location( "hermes_plugins.disk_cleanup", diff --git a/tests/plugins/test_retaindb_plugin.py b/tests/plugins/test_retaindb_plugin.py index 85e12b2ad..e7bde7cf9 100644 --- a/tests/plugins/test_retaindb_plugin.py +++ b/tests/plugins/test_retaindb_plugin.py @@ -58,8 +58,6 @@ def _cap_retaindb_sleeps(monkeypatch): # We need the repo root on sys.path so the plugin can import agent.memory_provider import sys -_repo_root = str(Path(__file__).resolve().parents[2]) -if _repo_root not in sys.path: from hermes_agent.plugins.memory.retaindb import ( _Client, _WriteQueue, diff --git a/tests/run_agent/conftest.py b/tests/run_agent/conftest.py index b6b38924e..f1f56f309 100644 --- a/tests/run_agent/conftest.py +++ b/tests/run_agent/conftest.py @@ -27,7 +27,7 @@ import pytest def _fast_retry_backoff(monkeypatch): """Short-circuit retry backoff for all tests in this directory.""" try: - import hermes_agent.agent.loop + from hermes_agent.agent import loop as run_agent except ImportError: return diff --git a/tests/run_agent/test_413_compression.py b/tests/run_agent/test_413_compression.py index 4565d1c67..faf2ea419 100644 --- a/tests/run_agent/test_413_compression.py +++ b/tests/run_agent/test_413_compression.py @@ -19,7 +19,7 @@ import pytest from hermes_agent.agent.context.compressor import SUMMARY_PREFIX from hermes_agent.agent.loop import AIAgent -import hermes_agent.agent.loop +from hermes_agent.agent import loop as run_agent # --------------------------------------------------------------------------- diff --git a/tests/run_agent/test_agent_loop_tool_calling.py b/tests/run_agent/test_agent_loop_tool_calling.py index 8d54fbd15..027be5cd9 100644 --- a/tests/run_agent/test_agent_loop_tool_calling.py +++ b/tests/run_agent/test_agent_loop_tool_calling.py @@ -32,7 +32,6 @@ import pytest # Ensure repo root is importable _repo_root = Path(__file__).resolve().parent.parent.parent -if str(_repo_root) not in sys.path: try: from environments.agent_loop import AgentResult, HermesAgentLoop from atroposlib.envs.server_handling.openai_server import OpenAIServer # noqa: F401 diff --git a/tests/run_agent/test_agent_loop_vllm.py b/tests/run_agent/test_agent_loop_vllm.py index 91e3515ce..6728975cd 100644 --- a/tests/run_agent/test_agent_loop_vllm.py +++ b/tests/run_agent/test_agent_loop_vllm.py @@ -31,7 +31,6 @@ import requests # Ensure repo root is importable _repo_root = Path(__file__).resolve().parent.parent.parent -if str(_repo_root) not in sys.path: try: from environments.agent_loop import AgentResult, HermesAgentLoop except ImportError: diff --git a/tests/run_agent/test_anthropic_error_handling.py b/tests/run_agent/test_anthropic_error_handling.py index c54de5b8a..aa29f47de 100644 --- a/tests/run_agent/test_anthropic_error_handling.py +++ b/tests/run_agent/test_anthropic_error_handling.py @@ -22,7 +22,7 @@ sys.modules.setdefault("firecrawl", types.SimpleNamespace(Firecrawl=object)) sys.modules.setdefault("fal_client", types.SimpleNamespace()) import hermes_agent.gateway.run as gateway_run -import hermes_agent.agent.loop +from hermes_agent.agent import loop as run_agent from hermes_agent.gateway.config import Platform from hermes_agent.gateway.session import SessionSource diff --git a/tests/run_agent/test_context_token_tracking.py b/tests/run_agent/test_context_token_tracking.py index f4c0abe88..11ad93370 100644 --- a/tests/run_agent/test_context_token_tracking.py +++ b/tests/run_agent/test_context_token_tracking.py @@ -14,7 +14,7 @@ sys.modules.setdefault("fire", types.SimpleNamespace(Fire=lambda *a, **k: None)) sys.modules.setdefault("firecrawl", types.SimpleNamespace(Firecrawl=object)) sys.modules.setdefault("fal_client", types.SimpleNamespace()) -import hermes_agent.agent.loop +from hermes_agent.agent import loop as run_agent def _patch_bootstrap(monkeypatch): diff --git a/tests/run_agent/test_fallback_model.py b/tests/run_agent/test_fallback_model.py index 25fea0c3a..3badf9ae6 100644 --- a/tests/run_agent/test_fallback_model.py +++ b/tests/run_agent/test_fallback_model.py @@ -11,7 +11,7 @@ from unittest.mock import MagicMock, patch import pytest from hermes_agent.agent.loop import AIAgent -import hermes_agent.agent.loop +from hermes_agent.agent import loop as run_agent @pytest.fixture(autouse=True) diff --git a/tests/run_agent/test_flush_memories_codex.py b/tests/run_agent/test_flush_memories_codex.py index 9d52cfa29..00bdeb8ba 100644 --- a/tests/run_agent/test_flush_memories_codex.py +++ b/tests/run_agent/test_flush_memories_codex.py @@ -17,7 +17,7 @@ sys.modules.setdefault("fire", types.SimpleNamespace(Fire=lambda *a, **k: None)) sys.modules.setdefault("firecrawl", types.SimpleNamespace(Firecrawl=object)) sys.modules.setdefault("fal_client", types.SimpleNamespace()) -import hermes_agent.agent.loop +from hermes_agent.agent import loop as run_agent class _FakeOpenAI: diff --git a/tests/run_agent/test_openai_client_lifecycle.py b/tests/run_agent/test_openai_client_lifecycle.py index aca206f1a..9af69cf30 100644 --- a/tests/run_agent/test_openai_client_lifecycle.py +++ b/tests/run_agent/test_openai_client_lifecycle.py @@ -11,7 +11,7 @@ sys.modules.setdefault("fire", types.SimpleNamespace(Fire=lambda *a, **k: None)) sys.modules.setdefault("firecrawl", types.SimpleNamespace(Firecrawl=object)) sys.modules.setdefault("fal_client", types.SimpleNamespace()) -import hermes_agent.agent.loop +from hermes_agent.agent import loop as run_agent class FakeRequestClient: diff --git a/tests/run_agent/test_percentage_clamp.py b/tests/run_agent/test_percentage_clamp.py index 1216311c2..1fee148d6 100644 --- a/tests/run_agent/test_percentage_clamp.py +++ b/tests/run_agent/test_percentage_clamp.py @@ -81,7 +81,7 @@ class TestSourceLinesAreClamped: return f.read() def test_gateway_run_clamped(self): - src = self._read_file("gateway/run.py") + src = self._read_file("hermes_agent/gateway/run.py") # Check that the stats handler has min(100, ...) assert "min(100, ctx.last_prompt_tokens" in src, ( "gateway/run.py stats pct is not clamped with min(100, ...)" @@ -94,7 +94,7 @@ class TestSourceLinesAreClamped: ) def test_memory_tool_clamped(self): - src = self._read_file("tools/memory_tool.py") + src = self._read_file("hermes_agent/tools/memory.py") # Both _success_response and _render_block should have min(100, ...) count = src.count("min(100, int((current / limit)") assert count >= 2, ( diff --git a/tests/run_agent/test_run_agent.py b/tests/run_agent/test_run_agent.py index b2bfee911..1444baa4d 100644 --- a/tests/run_agent/test_run_agent.py +++ b/tests/run_agent/test_run_agent.py @@ -18,7 +18,7 @@ from unittest.mock import AsyncMock, MagicMock, patch import pytest from hermes_agent.providers.codex_adapter import _chat_messages_to_responses_input, _normalize_codex_response, _preflight_codex_input_items -import hermes_agent.agent.loop +from hermes_agent.agent import loop as run_agent from hermes_agent.agent.loop import AIAgent from hermes_agent.providers.errors import FailoverReason from hermes_agent.agent.prompt_builder import DEFAULT_AGENT_IDENTITY diff --git a/tests/run_agent/test_run_agent_codex_responses.py b/tests/run_agent/test_run_agent_codex_responses.py index 45688cb9a..0f7742340 100644 --- a/tests/run_agent/test_run_agent_codex_responses.py +++ b/tests/run_agent/test_run_agent_codex_responses.py @@ -9,7 +9,7 @@ sys.modules.setdefault("fire", types.SimpleNamespace(Fire=lambda *a, **k: None)) sys.modules.setdefault("firecrawl", types.SimpleNamespace(Firecrawl=object)) sys.modules.setdefault("fal_client", types.SimpleNamespace()) -import hermes_agent.agent.loop +from hermes_agent.agent import loop as run_agent @pytest.fixture(autouse=True) diff --git a/tests/skills/test_memento_cards.py b/tests/skills/test_memento_cards.py index 94c70845d..c1e29039c 100644 --- a/tests/skills/test_memento_cards.py +++ b/tests/skills/test_memento_cards.py @@ -13,6 +13,8 @@ import pytest # Add the scripts dir so we can import the module directly SCRIPTS_DIR = Path(__file__).resolve().parents[2] / "optional-skills" / "productivity" / "memento-flashcards" / "scripts" +sys.path.insert(0, str(SCRIPTS_DIR)) + import memento_cards diff --git a/tests/skills/test_openclaw_migration.py b/tests/skills/test_openclaw_migration.py index 671d764f0..c7550f6b6 100644 --- a/tests/skills/test_openclaw_migration.py +++ b/tests/skills/test_openclaw_migration.py @@ -28,7 +28,7 @@ def load_module(): def load_skills_guard(): spec = importlib.util.spec_from_file_location( "skills_guard_local", - Path(__file__).resolve().parents[2] / "tools" / "skills_guard.py", + Path(__file__).resolve().parents[2] / "hermes_agent" / "tools" / "skills" / "guard.py", ) module = importlib.util.module_from_spec(spec) assert spec.loader is not None diff --git a/tests/skills/test_youtube_quiz.py b/tests/skills/test_youtube_quiz.py index 15a912f3e..182889ff6 100644 --- a/tests/skills/test_youtube_quiz.py +++ b/tests/skills/test_youtube_quiz.py @@ -9,6 +9,8 @@ from unittest import mock import pytest SCRIPTS_DIR = Path(__file__).resolve().parents[2] / "optional-skills" / "productivity" / "memento-flashcards" / "scripts" +sys.path.insert(0, str(SCRIPTS_DIR)) + import youtube_quiz diff --git a/tests/test_hermes_constants.py b/tests/test_hermes_constants.py index cde90dd7d..0bbe47d6e 100644 --- a/tests/test_hermes_constants.py +++ b/tests/test_hermes_constants.py @@ -6,7 +6,7 @@ from unittest.mock import patch import pytest -import hermes_agent.constants +from hermes_agent import constants as hermes_constants from hermes_agent.constants import get_default_hermes_root, is_container diff --git a/tests/test_hermes_logging.py b/tests/test_hermes_logging.py index ff85265b4..f8f21fb36 100644 --- a/tests/test_hermes_logging.py +++ b/tests/test_hermes_logging.py @@ -10,7 +10,7 @@ from unittest.mock import patch import pytest -import hermes_agent.logging +from hermes_agent import logging as hermes_logging @pytest.fixture(autouse=True) @@ -461,28 +461,28 @@ class TestComponentFilter: """Unit tests for _ComponentFilter.""" def test_passes_matching_prefix(self): - f = hermes_logging._ComponentFilter(("gateway",)) + f = hermes_logging._ComponentFilter(("hermes_agent.gateway",)) record = logging.LogRecord( "hermes_agent.gateway.run", logging.INFO, "", 0, "msg", (), None ) assert f.filter(record) is True def test_passes_nested_matching_prefix(self): - f = hermes_logging._ComponentFilter(("gateway",)) + f = hermes_logging._ComponentFilter(("hermes_agent.gateway",)) record = logging.LogRecord( "hermes_agent.gateway.platforms.telegram", logging.INFO, "", 0, "msg", (), None ) assert f.filter(record) is True def test_blocks_non_matching(self): - f = hermes_logging._ComponentFilter(("gateway",)) + f = hermes_logging._ComponentFilter(("hermes_agent.gateway",)) record = logging.LogRecord( "hermes_agent.tools.terminal", logging.INFO, "", 0, "msg", (), None ) assert f.filter(record) is False def test_multiple_prefixes(self): - f = hermes_logging._ComponentFilter(("agent", "hermes_agent.agent.loop", "hermes_agent.tools.dispatch")) + f = hermes_logging._ComponentFilter(("hermes_agent.agent", "hermes_agent.tools.dispatch")) assert f.filter(logging.LogRecord( "hermes_agent.agent.compressor", logging.INFO, "", 0, "", (), None )) @@ -501,14 +501,12 @@ class TestComponentPrefixes: """COMPONENT_PREFIXES covers the expected components.""" def test_gateway_prefix(self): - assert "hermes_agent.gateway" in hermes_logging.COMPONENT_PREFIXES + assert "gateway" in hermes_logging.COMPONENT_PREFIXES assert ("hermes_agent.gateway",) == hermes_logging.COMPONENT_PREFIXES["gateway"] def test_agent_prefix(self): prefixes = hermes_logging.COMPONENT_PREFIXES["agent"] - assert "agent" in prefixes - assert "hermes_agent.agent.loop" in prefixes - assert "hermes_agent.tools.dispatch" in prefixes + assert "hermes_agent.agent" in prefixes def test_tools_prefix(self): assert ("hermes_agent.tools",) == hermes_logging.COMPONENT_PREFIXES["tools"] diff --git a/tests/test_ipv4_preference.py b/tests/test_ipv4_preference.py index 93db09338..19311c502 100644 --- a/tests/test_ipv4_preference.py +++ b/tests/test_ipv4_preference.py @@ -9,7 +9,7 @@ import pytest def _reload_constants(): """Reload hermes_constants to get a fresh apply_ipv4_preference.""" - import hermes_agent.constants + from hermes_agent import constants as hermes_constants importlib.reload(hermes_constants) return hermes_constants diff --git a/tests/test_mcp_serve.py b/tests/test_mcp_serve.py index ba5b09be4..63f51a267 100644 --- a/tests/test_mcp_serve.py +++ b/tests/test_mcp_serve.py @@ -29,7 +29,7 @@ def _isolate_hermes_home(tmp_path, monkeypatch): """Redirect HERMES_HOME to a temp directory.""" monkeypatch.setenv("HERMES_HOME", str(tmp_path)) try: - import hermes_agent.constants + from hermes_agent import constants as hermes_constants monkeypatch.setattr(hermes_constants, "get_hermes_home", lambda: tmp_path) except (ImportError, AttributeError): pass @@ -213,13 +213,13 @@ def mock_session_db(tmp_path, populated_sessions_dir): class TestImports: def test_import_module(self): - import hermes_agent.tools.mcp.serve + from hermes_agent.tools.mcp import serve as mcp_serve assert hasattr(mcp_serve, "create_mcp_server") assert hasattr(mcp_serve, "run_mcp_server") assert hasattr(mcp_serve, "EventBridge") def test_mcp_available_flag(self): - import hermes_agent.tools.mcp.serve + from hermes_agent.tools.mcp import serve as mcp_serve assert isinstance(mcp_serve._MCP_SERVER_AVAILABLE, bool) @@ -230,19 +230,19 @@ class TestHelpers: assert result == tmp_path / "sessions" def test_load_sessions_index_empty(self, sessions_dir, monkeypatch): - import hermes_agent.tools.mcp.serve + from hermes_agent.tools.mcp import serve as mcp_serve monkeypatch.setattr(mcp_serve, "_get_sessions_dir", lambda: sessions_dir) assert mcp_serve._load_sessions_index() == {} def test_load_sessions_index_with_data(self, populated_sessions_dir, monkeypatch): - import hermes_agent.tools.mcp.serve + from hermes_agent.tools.mcp import serve as mcp_serve monkeypatch.setattr(mcp_serve, "_get_sessions_dir", lambda: populated_sessions_dir) result = mcp_serve._load_sessions_index() assert len(result) == 3 def test_load_sessions_index_corrupt(self, sessions_dir, monkeypatch): (sessions_dir / "sessions.json").write_text("not json!") - import hermes_agent.tools.mcp.serve + from hermes_agent.tools.mcp import serve as mcp_serve monkeypatch.setattr(mcp_serve, "_get_sessions_dir", lambda: sessions_dir) assert mcp_serve._load_sessions_index() == {} @@ -442,7 +442,7 @@ class TestEventBridge: def mcp_server_e2e(populated_sessions_dir, mock_session_db, monkeypatch): """Create a fully wired MCP server for E2E testing.""" mcp = pytest.importorskip("mcp", reason="MCP SDK not installed") - import hermes_agent.tools.mcp.serve + from hermes_agent.tools.mcp import serve as mcp_serve monkeypatch.setattr(mcp_serve, "_get_sessions_dir", lambda: populated_sessions_dir) monkeypatch.setattr(mcp_serve, "_get_session_db", lambda: mock_session_db) monkeypatch.setattr(mcp_serve, "_load_channel_directory", lambda: {}) @@ -727,7 +727,7 @@ class TestE2EChannelsList: assert result["channels"][0]["target"] == "slack:C1234" def test_channels_with_directory(self, mcp_server_e2e, _event_loop, monkeypatch): - import hermes_agent.tools.mcp.serve + from hermes_agent.tools.mcp import serve as mcp_serve monkeypatch.setattr(mcp_serve, "_load_channel_directory", lambda: { "telegram": [ {"id": "123456", "name": "Alice", "type": "dm"}, @@ -823,19 +823,19 @@ class TestToolRegistration: class TestServerCreation: def test_create_server(self, populated_sessions_dir, monkeypatch): pytest.importorskip("mcp", reason="MCP SDK not installed") - import hermes_agent.tools.mcp.serve + from hermes_agent.tools.mcp import serve as mcp_serve monkeypatch.setattr(mcp_serve, "_get_sessions_dir", lambda: populated_sessions_dir) assert mcp_serve.create_mcp_server() is not None def test_create_with_bridge(self, populated_sessions_dir, monkeypatch): pytest.importorskip("mcp", reason="MCP SDK not installed") - import hermes_agent.tools.mcp.serve + from hermes_agent.tools.mcp import serve as mcp_serve monkeypatch.setattr(mcp_serve, "_get_sessions_dir", lambda: populated_sessions_dir) bridge = mcp_serve.EventBridge() assert mcp_serve.create_mcp_server(event_bridge=bridge) is not None def test_create_without_mcp_sdk(self, monkeypatch): - import hermes_agent.tools.mcp.serve + from hermes_agent.tools.mcp import serve as mcp_serve monkeypatch.setattr(mcp_serve, "_MCP_SERVER_AVAILABLE", False) with pytest.raises(ImportError, match="MCP server requires"): mcp_serve.create_mcp_server() @@ -843,7 +843,7 @@ class TestServerCreation: class TestRunMcpServer: def test_run_without_mcp_exits(self, monkeypatch): - import hermes_agent.tools.mcp.serve + from hermes_agent.tools.mcp import serve as mcp_serve monkeypatch.setattr(mcp_serve, "_MCP_SERVER_AVAILABLE", False) with pytest.raises(SystemExit) as exc_info: mcp_serve.run_mcp_server() @@ -895,7 +895,7 @@ class TestCliIntegration: class TestEdgeCases: def test_empty_sessions_json(self, sessions_dir, monkeypatch): (sessions_dir / "sessions.json").write_text("{}") - import hermes_agent.tools.mcp.serve + from hermes_agent.tools.mcp import serve as mcp_serve monkeypatch.setattr(mcp_serve, "_get_sessions_dir", lambda: sessions_dir) assert mcp_serve._load_sessions_index() == {} @@ -907,7 +907,7 @@ class TestEdgeCases: "updated_at": "2026-03-29T12:00:00", }} (sessions_dir / "sessions.json").write_text(json.dumps(data)) - import hermes_agent.tools.mcp.serve + from hermes_agent.tools.mcp import serve as mcp_serve monkeypatch.setattr(mcp_serve, "_get_sessions_dir", lambda: sessions_dir) entries = mcp_serve._load_sessions_index() assert entries["agent:main:telegram:dm:111"]["platform"] == "telegram" @@ -933,7 +933,7 @@ class TestEventBridgePollE2E: def test_poll_detects_new_messages(self, tmp_path, monkeypatch): """Write to SQLite + sessions.json, verify EventBridge picks it up.""" - import hermes_agent.tools.mcp.serve + from hermes_agent.tools.mcp import serve as mcp_serve sessions_dir = tmp_path / "sessions" sessions_dir.mkdir() monkeypatch.setattr(mcp_serve, "_get_sessions_dir", lambda: sessions_dir) @@ -991,7 +991,7 @@ class TestEventBridgePollE2E: def test_poll_skips_when_unchanged(self, tmp_path, monkeypatch): """Second poll with no file changes should be a no-op.""" - import hermes_agent.tools.mcp.serve + from hermes_agent.tools.mcp import serve as mcp_serve sessions_dir = tmp_path / "sessions" sessions_dir.mkdir() monkeypatch.setattr(mcp_serve, "_get_sessions_dir", lambda: sessions_dir) @@ -1043,7 +1043,7 @@ class TestEventBridgePollE2E: def test_poll_detects_new_message_after_db_write(self, tmp_path, monkeypatch): """Write a new message to the DB after first poll, verify it's detected.""" - import hermes_agent.tools.mcp.serve + from hermes_agent.tools.mcp import serve as mcp_serve sessions_dir = tmp_path / "sessions" sessions_dir.mkdir() monkeypatch.setattr(mcp_serve, "_get_sessions_dir", lambda: sessions_dir) diff --git a/tests/test_timezone.py b/tests/test_timezone.py index e293c8e20..29ed236f2 100644 --- a/tests/test_timezone.py +++ b/tests/test_timezone.py @@ -17,7 +17,7 @@ from datetime import datetime, timedelta, timezone from unittest.mock import patch, MagicMock from zoneinfo import ZoneInfo -import hermes_agent.time +from hermes_agent import time as hermes_time def _reset_hermes_time_cache(): diff --git a/tests/test_transform_tool_result_hook.py b/tests/test_transform_tool_result_hook.py index 92feaf262..b7f253d27 100644 --- a/tests/test_transform_tool_result_hook.py +++ b/tests/test_transform_tool_result_hook.py @@ -11,7 +11,7 @@ from pathlib import Path from unittest.mock import MagicMock import hermes_agent.cli.plugins as plugins_mod -import hermes_agent.tools.dispatch +from hermes_agent.tools import dispatch as model_tools _UNSET = object() diff --git a/tests/tools/test_approval.py b/tests/tools/test_approval.py index 9f17a0006..b8fe78c79 100644 --- a/tests/tools/test_approval.py +++ b/tests/tools/test_approval.py @@ -153,7 +153,7 @@ class TestSessionKeyContext: approval_module.reset_current_session_key(token) def test_gateway_runner_binds_session_key_to_context_before_agent_run(self): - run_py = Path(__file__).resolve().parents[2] / "gateway" / "run.py" + run_py = Path(__file__).resolve().parents[2] / "hermes_agent" / "gateway" / "run.py" module = ast.parse(run_py.read_text(encoding="utf-8")) run_sync = None diff --git a/tests/tools/test_browser_camofox_persistence.py b/tests/tools/test_browser_camofox_persistence.py index d828e5b41..6274ae33e 100644 --- a/tests/tools/test_browser_camofox_persistence.py +++ b/tests/tools/test_browser_camofox_persistence.py @@ -20,7 +20,7 @@ from hermes_agent.tools.browser.camofox import ( check_camofox_available, get_vnc_url, ) -from hermes_agent.tools.browser_camofox_state import get_camofox_identity +from hermes_agent.tools.browser.camofox_state import get_camofox_identity def _mock_response(status=200, json_data=None): diff --git a/tests/tools/test_browser_content_none_guard.py b/tests/tools/test_browser_content_none_guard.py index 2e21fd45e..e5658a3ee 100644 --- a/tests/tools/test_browser_content_none_guard.py +++ b/tests/tools/test_browser_content_none_guard.py @@ -90,7 +90,7 @@ class TestBrowserSourceLinesAreGuarded: def _read_file() -> str: import os base = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) - with open(os.path.join(base, "tools", "browser_tool.py")) as f: + with open(os.path.join(base, "hermes_agent", "tools", "browser", "tool.py")) as f: return f.read() def test_extract_relevant_content_guarded(self): diff --git a/tests/tools/test_clipboard.py b/tests/tools/test_clipboard.py index 2db71fdf6..aafd4b1a9 100644 --- a/tests/tools/test_clipboard.py +++ b/tests/tools/test_clipboard.py @@ -206,7 +206,7 @@ class TestMacosOsascript: class TestIsWsl: def setup_method(self): # _is_wsl is now hermes_constants.is_wsl — reset its cache - import hermes_agent.constants + from hermes_agent import constants as hermes_constants hermes_constants._wsl_detected = None def test_wsl2_detected(self): @@ -229,7 +229,7 @@ class TestIsWsl: assert _is_wsl() is False def test_result_is_cached(self): - import hermes_agent.constants + from hermes_agent import constants as hermes_constants content = "Linux version 5.15.0 (microsoft-standard-WSL2)" with patch("builtins.open", mock_open(read_data=content)) as m: assert _is_wsl() is True diff --git a/tests/tools/test_code_execution.py b/tests/tools/test_code_execution.py index dc5252211..22e93bf63 100644 --- a/tests/tools/test_code_execution.py +++ b/tests/tools/test_code_execution.py @@ -186,9 +186,9 @@ class TestExecuteCode(unittest.TestCase): def test_repo_root_modules_are_importable(self): """Sandboxed scripts can import modules that live at the repo root.""" - result = self._run('import hermes_constants; print(hermes_constants.__file__)') + result = self._run('import hermes_agent.constants; print(hermes_agent.constants.__file__)') self.assertEqual(result["status"], "success") - self.assertIn("hermes_agent.constants.py", result["output"]) + self.assertIn("constants.py", result["output"]) def test_single_tool_call(self): """Script calls terminal and prints the result.""" diff --git a/tests/tools/test_delegate.py b/tests/tools/test_delegate.py index 4406d0fe3..da76abfc6 100644 --- a/tests/tools/test_delegate.py +++ b/tests/tools/test_delegate.py @@ -313,8 +313,7 @@ class TestToolNamePreservation(unittest.TestCase): """The process-global _last_resolved_tool_names must be restored after a subagent completes so the parent's execute_code sandbox generates correct imports.""" - import hermes_agent.tools.dispatch - + from hermes_agent.tools import dispatch as model_tools parent = _make_mock_parent(depth=0) original_tools = ["terminal", "read_file", "web_search", "execute_code", "delegate_task"] model_tools._last_resolved_tool_names = list(original_tools) @@ -332,8 +331,7 @@ class TestToolNamePreservation(unittest.TestCase): def test_global_tool_names_restored_after_child_failure(self): """Even when the child agent raises, the global must be restored.""" - import hermes_agent.tools.dispatch - + from hermes_agent.tools import dispatch as model_tools parent = _make_mock_parent(depth=0) original_tools = ["terminal", "read_file", "web_search"] model_tools._last_resolved_tool_names = list(original_tools) @@ -379,8 +377,7 @@ class TestToolNamePreservation(unittest.TestCase): def test_saved_tool_names_set_on_child_before_run(self): """_run_single_child must set _delegate_saved_tool_names on the child from model_tools._last_resolved_tool_names before run_conversation.""" - import hermes_agent.tools.dispatch - + from hermes_agent.tools import dispatch as model_tools parent = _make_mock_parent(depth=0) expected_tools = ["read_file", "web_search", "execute_code"] model_tools._last_resolved_tool_names = list(expected_tools) diff --git a/tests/tools/test_llm_content_none_guard.py b/tests/tools/test_llm_content_none_guard.py index d048ea80e..6b9bc4b9a 100644 --- a/tests/tools/test_llm_content_none_guard.py +++ b/tests/tools/test_llm_content_none_guard.py @@ -190,7 +190,7 @@ class TestSourceLinesAreGuarded: return f.read() def test_mixture_of_agents_reference_model_guarded(self): - src = self._read_file("tools/mixture_of_agents_tool.py") + src = self._read_file("hermes_agent/tools/mixture_of_agents.py") # The unguarded pattern should NOT exist assert ".message.content.strip()" not in src, ( "tools/mixture_of_agents_tool.py still has unguarded " @@ -198,28 +198,28 @@ class TestSourceLinesAreGuarded: ) def test_web_tools_guarded(self): - src = self._read_file("tools/web_tools.py") + src = self._read_file("hermes_agent/tools/web.py") assert ".message.content.strip()" not in src, ( "tools/web_tools.py still has unguarded " ".content.strip() — apply `(... or \"\").strip()` guard" ) def test_vision_tools_guarded(self): - src = self._read_file("tools/vision_tools.py") + src = self._read_file("hermes_agent/tools/vision.py") assert ".message.content.strip()" not in src, ( "tools/vision_tools.py still has unguarded " ".content.strip() — apply `(... or \"\").strip()` guard" ) def test_skills_guard_guarded(self): - src = self._read_file("tools/skills_guard.py") + src = self._read_file("hermes_agent/tools/skills/guard.py") assert ".message.content.strip()" not in src, ( "tools/skills_guard.py still has unguarded " ".content.strip() — apply `(... or \"\").strip()` guard" ) def test_session_search_tool_guarded(self): - src = self._read_file("tools/session_search_tool.py") + src = self._read_file("hermes_agent/tools/session_search.py") assert ".message.content.strip()" not in src, ( "tools/session_search_tool.py still has unguarded " ".content.strip() — apply `(... or \"\").strip()` guard" diff --git a/tests/tools/test_managed_browserbase_and_modal.py b/tests/tools/test_managed_browserbase_and_modal.py index 1e765ee28..4ebb488d6 100644 --- a/tests/tools/test_managed_browserbase_and_modal.py +++ b/tests/tools/test_managed_browserbase_and_modal.py @@ -10,7 +10,8 @@ from unittest.mock import patch import pytest -TOOLS_DIR = Path(__file__).resolve().parents[2] / "tools" +REPO_ROOT = Path(__file__).resolve().parents[2] +TOOLS_DIR = REPO_ROOT / "hermes_agent" / "tools" def _load_tool_module(module_name: str, filename: str): @@ -66,7 +67,7 @@ def _install_fake_tools_package(): sys.modules["tools"] = tools_package env_package = types.ModuleType("hermes_agent.backends") - env_package.__path__ = [str(TOOLS_DIR / "environments")] # type: ignore[attr-defined] + env_package.__path__ = [str(REPO_ROOT / "hermes_agent" / "backends")] # type: ignore[attr-defined] sys.modules["hermes_agent.backends"] = env_package agent_package = types.ModuleType("agent") @@ -78,7 +79,7 @@ def _install_fake_tools_package(): sys.modules["hermes_agent.tools.managed_gateway"] = _load_tool_module( "hermes_agent.tools.managed_gateway", - "managed_tool_gateway.py", + "managed_gateway.py", ) interrupt_event = threading.Event() @@ -137,7 +138,7 @@ def test_browser_use_explicit_local_mode_stays_local_even_when_managed_gateway_i }) with patch.dict(os.environ, env, clear=True): - browser_tool = _load_tool_module("hermes_agent.tools.browser.tool", "browser_tool.py") + browser_tool = _load_tool_module("hermes_agent.tools.browser.tool", "browser/tool.py") local_mode = browser_tool._is_local_mode() provider = browser_tool._get_cloud_provider() @@ -159,7 +160,7 @@ def test_browserbase_does_not_use_gateway_only_configuration(): with patch.dict(os.environ, env, clear=True): browserbase_module = _load_tool_module( "hermes_agent.tools.browser.providers.browserbase", - "browser_providers/browserbase.py", + "browser/providers/browserbase.py", ) provider = browserbase_module.BrowserbaseProvider() @@ -190,7 +191,7 @@ def test_browser_use_managed_gateway_adds_idempotency_key_and_persists_external_ with patch.dict(os.environ, env, clear=True): browser_use_module = _load_tool_module( "hermes_agent.tools.browser.providers.browser_use", - "browser_providers/browser_use.py", + "browser/providers/browser_use.py", ) with patch.object(browser_use_module.requests, "post", return_value=_Response()) as post: @@ -230,7 +231,7 @@ def test_browser_use_managed_gateway_reuses_pending_idempotency_key_after_timeou with patch.dict(os.environ, env, clear=True): browser_use_module = _load_tool_module( "hermes_agent.tools.browser.providers.browser_use", - "browser_providers/browser_use.py", + "browser/providers/browser_use.py", ) provider = browser_use_module.BrowserUseProvider() timeout = browser_use_module.requests.Timeout("timed out") @@ -292,7 +293,7 @@ def test_browser_use_managed_gateway_preserves_pending_idempotency_key_for_in_pr with patch.dict(os.environ, env, clear=True): browser_use_module = _load_tool_module( "hermes_agent.tools.browser.providers.browser_use", - "browser_providers/browser_use.py", + "browser/providers/browser_use.py", ) provider = browser_use_module.BrowserUseProvider() @@ -339,7 +340,7 @@ def test_browser_use_managed_gateway_uses_new_idempotency_key_for_a_new_session_ with patch.dict(os.environ, env, clear=True): browser_use_module = _load_tool_module( "hermes_agent.tools.browser.providers.browser_use", - "browser_providers/browser_use.py", + "browser/providers/browser_use.py", ) provider = browser_use_module.BrowserUseProvider() @@ -359,7 +360,7 @@ def test_terminal_tool_prefers_managed_modal_when_gateway_ready_and_no_direct_cr env.pop("MODAL_TOKEN_SECRET", None) with patch.dict(os.environ, env, clear=True): - terminal_tool = _load_tool_module("hermes_agent.tools.terminal", "terminal_tool.py") + terminal_tool = _load_tool_module("hermes_agent.tools.terminal", "terminal.py") with ( patch.object(terminal_tool, "is_managed_tool_gateway_ready", return_value=True), @@ -396,7 +397,7 @@ def test_terminal_tool_auto_mode_prefers_managed_modal_when_available(): }) with patch.dict(os.environ, env, clear=True): - terminal_tool = _load_tool_module("hermes_agent.tools.terminal", "terminal_tool.py") + terminal_tool = _load_tool_module("hermes_agent.tools.terminal", "terminal.py") with ( patch.object(terminal_tool, "is_managed_tool_gateway_ready", return_value=True), @@ -432,7 +433,7 @@ def test_terminal_tool_auto_mode_falls_back_to_direct_modal_when_managed_unavail }) with patch.dict(os.environ, env, clear=True): - terminal_tool = _load_tool_module("hermes_agent.tools.terminal", "terminal_tool.py") + terminal_tool = _load_tool_module("hermes_agent.tools.terminal", "terminal.py") with ( patch.object(terminal_tool, "is_managed_tool_gateway_ready", return_value=False), @@ -466,7 +467,7 @@ def test_terminal_tool_respects_direct_modal_mode_without_falling_back_to_manage env.pop("MODAL_TOKEN_SECRET", None) with patch.dict(os.environ, env, clear=True): - terminal_tool = _load_tool_module("hermes_agent.tools.terminal", "terminal_tool.py") + terminal_tool = _load_tool_module("hermes_agent.tools.terminal", "terminal.py") with ( patch.object(terminal_tool, "is_managed_tool_gateway_ready", return_value=True), diff --git a/tests/tools/test_managed_media_gateways.py b/tests/tools/test_managed_media_gateways.py index 23b1d6f42..2cfe59ae5 100644 --- a/tests/tools/test_managed_media_gateways.py +++ b/tests/tools/test_managed_media_gateways.py @@ -6,7 +6,7 @@ from pathlib import Path import pytest -TOOLS_DIR = Path(__file__).resolve().parents[2] / "tools" +TOOLS_DIR = Path(__file__).resolve().parents[2] / "hermes_agent" / "tools" def _load_tool_module(module_name: str, filename: str): @@ -67,7 +67,7 @@ def _install_fake_tools_package(): ) sys.modules["hermes_agent.tools.managed_gateway"] = _load_tool_module( "hermes_agent.tools.managed_gateway", - "managed_tool_gateway.py", + "managed_gateway.py", ) @@ -174,7 +174,7 @@ def test_managed_fal_submit_uses_gateway_origin_and_nous_token(monkeypatch): image_generation_tool = _load_tool_module( "hermes_agent.tools.media.image_gen", - "image_generation_tool.py", + "media/image_gen.py", ) monkeypatch.setattr(image_generation_tool.uuid, "uuid4", lambda: "fal-submit-123") @@ -202,7 +202,7 @@ def test_managed_fal_submit_reuses_cached_sync_client(monkeypatch): image_generation_tool = _load_tool_module( "hermes_agent.tools.media.image_gen", - "image_generation_tool.py", + "media/image_gen.py", ) image_generation_tool._submit_fal_request("fal-ai/flux-2-pro", {"prompt": "first"}) @@ -222,7 +222,7 @@ def test_openai_tts_uses_managed_audio_gateway_when_direct_key_absent(monkeypatc monkeypatch.setenv("TOOL_GATEWAY_DOMAIN", "nousresearch.com") monkeypatch.setenv("TOOL_GATEWAY_USER_TOKEN", "nous-token") - tts_tool = _load_tool_module("hermes_agent.tools.media.tts", "tts_tool.py") + tts_tool = _load_tool_module("hermes_agent.tools.media.tts", "media/tts.py") monkeypatch.setattr(tts_tool.uuid, "uuid4", lambda: "tts-call-123") output_path = tmp_path / "speech.mp3" tts_tool._generate_openai_tts("hello world", str(output_path), {"openai": {}}) @@ -244,7 +244,7 @@ def test_openai_tts_accepts_openai_api_key_as_direct_fallback(monkeypatch, tmp_p monkeypatch.setenv("TOOL_GATEWAY_DOMAIN", "nousresearch.com") monkeypatch.setenv("TOOL_GATEWAY_USER_TOKEN", "nous-token") - tts_tool = _load_tool_module("hermes_agent.tools.media.tts", "tts_tool.py") + tts_tool = _load_tool_module("hermes_agent.tools.media.tts", "media/tts.py") output_path = tmp_path / "speech.mp3" tts_tool._generate_openai_tts("hello world", str(output_path), {"openai": {}}) @@ -266,7 +266,7 @@ def test_transcription_uses_model_specific_response_formats(monkeypatch, tmp_pat transcription_tools = _load_tool_module( "hermes_agent.tools.media.transcription", - "transcription_tools.py", + "media/transcription.py", ) transcription_tools._load_stt_config = lambda: {"provider": "openai"} audio_path = tmp_path / "audio.wav" @@ -285,7 +285,7 @@ def test_transcription_uses_model_specific_response_formats(monkeypatch, tmp_pat ) transcription_tools = _load_tool_module( "hermes_agent.tools.media.transcription", - "transcription_tools.py", + "media/transcription.py", ) json_result = transcription_tools.transcribe_audio( diff --git a/tests/tools/test_managed_modal_environment.py b/tests/tools/test_managed_modal_environment.py index 595d12d44..853ee598a 100644 --- a/tests/tools/test_managed_modal_environment.py +++ b/tests/tools/test_managed_modal_environment.py @@ -9,7 +9,8 @@ from pathlib import Path import pytest -TOOLS_DIR = Path(__file__).resolve().parents[2] / "tools" +REPO_ROOT = Path(__file__).resolve().parents[2] +TOOLS_DIR = REPO_ROOT / "hermes_agent" def _load_tool_module(module_name: str, filename: str): @@ -56,11 +57,11 @@ def _install_fake_tools_package(*, credential_mounts=None): ) tools_package = types.ModuleType("tools") - tools_package.__path__ = [str(TOOLS_DIR)] # type: ignore[attr-defined] + tools_package.__path__ = [str(REPO_ROOT / "hermes_agent" / "tools")] # type: ignore[attr-defined] sys.modules["tools"] = tools_package env_package = types.ModuleType("hermes_agent.backends") - env_package.__path__ = [str(TOOLS_DIR / "environments")] # type: ignore[attr-defined] + env_package.__path__ = [str(REPO_ROOT / "hermes_agent" / "backends")] # type: ignore[attr-defined] sys.modules["hermes_agent.backends"] = env_package interrupt_event = threading.Event() @@ -109,7 +110,7 @@ class _FakeResponse: def test_managed_modal_execute_polls_until_completed(monkeypatch): _install_fake_tools_package() - managed_modal = _load_tool_module("hermes_agent.backends.managed_modal", "environments/managed_modal.py") + managed_modal = _load_tool_module("hermes_agent.backends.managed_modal", "backends/managed_modal.py") modal_common = sys.modules["hermes_agent.backends.modal_utils"] calls = [] @@ -148,7 +149,7 @@ def test_managed_modal_execute_polls_until_completed(monkeypatch): def test_managed_modal_create_sends_a_stable_idempotency_key(monkeypatch): _install_fake_tools_package() - managed_modal = _load_tool_module("hermes_agent.backends.managed_modal", "environments/managed_modal.py") + managed_modal = _load_tool_module("hermes_agent.backends.managed_modal", "backends/managed_modal.py") create_headers = [] @@ -172,7 +173,7 @@ def test_managed_modal_create_sends_a_stable_idempotency_key(monkeypatch): def test_managed_modal_execute_cancels_on_interrupt(monkeypatch): interrupt_event = _install_fake_tools_package() - managed_modal = _load_tool_module("hermes_agent.backends.managed_modal", "environments/managed_modal.py") + managed_modal = _load_tool_module("hermes_agent.backends.managed_modal", "backends/managed_modal.py") modal_common = sys.modules["hermes_agent.backends.modal_utils"] calls = [] @@ -214,7 +215,7 @@ def test_managed_modal_execute_cancels_on_interrupt(monkeypatch): def test_managed_modal_execute_returns_descriptive_error_on_missing_exec(monkeypatch): _install_fake_tools_package() - managed_modal = _load_tool_module("hermes_agent.backends.managed_modal", "environments/managed_modal.py") + managed_modal = _load_tool_module("hermes_agent.backends.managed_modal", "backends/managed_modal.py") modal_common = sys.modules["hermes_agent.backends.modal_utils"] def fake_request(method, url, headers=None, json=None, timeout=None): @@ -241,7 +242,7 @@ def test_managed_modal_execute_returns_descriptive_error_on_missing_exec(monkeyp def test_managed_modal_create_and_cleanup_preserve_gateway_persistence_fields(monkeypatch): _install_fake_tools_package() - managed_modal = _load_tool_module("hermes_agent.backends.managed_modal", "environments/managed_modal.py") + managed_modal = _load_tool_module("hermes_agent.backends.managed_modal", "backends/managed_modal.py") create_payloads = [] terminate_payloads = [] @@ -284,7 +285,7 @@ def test_managed_modal_rejects_host_credential_passthrough(): "container_path": "/root/.hermes/token.json", }] ) - managed_modal = _load_tool_module("hermes_agent.backends.managed_modal", "environments/managed_modal.py") + managed_modal = _load_tool_module("hermes_agent.backends.managed_modal", "backends/managed_modal.py") with pytest.raises(ValueError, match="credential-file passthrough"): managed_modal.ManagedModalEnvironment(image="python:3.11") @@ -292,7 +293,7 @@ def test_managed_modal_rejects_host_credential_passthrough(): def test_managed_modal_execute_times_out_and_cancels(monkeypatch): _install_fake_tools_package() - managed_modal = _load_tool_module("hermes_agent.backends.managed_modal", "environments/managed_modal.py") + managed_modal = _load_tool_module("hermes_agent.backends.managed_modal", "backends/managed_modal.py") modal_common = sys.modules["hermes_agent.backends.modal_utils"] calls = [] diff --git a/tests/tools/test_managed_tool_gateway.py b/tests/tools/test_managed_tool_gateway.py index f7450ab47..453be2a5d 100644 --- a/tests/tools/test_managed_tool_gateway.py +++ b/tests/tools/test_managed_tool_gateway.py @@ -6,7 +6,7 @@ from pathlib import Path import sys from unittest.mock import patch -MODULE_PATH = Path(__file__).resolve().parents[2] / "tools" / "managed_tool_gateway.py" +MODULE_PATH = Path(__file__).resolve().parents[2] / "hermes_agent" / "tools" / "managed_gateway.py" MODULE_SPEC = spec_from_file_location("managed_tool_gateway_test_module", MODULE_PATH) assert MODULE_SPEC and MODULE_SPEC.loader managed_tool_gateway = module_from_spec(MODULE_SPEC) diff --git a/tests/tools/test_modal_sandbox_fixes.py b/tests/tools/test_modal_sandbox_fixes.py index acd6e36cd..916407881 100644 --- a/tests/tools/test_modal_sandbox_fixes.py +++ b/tests/tools/test_modal_sandbox_fixes.py @@ -16,7 +16,6 @@ import pytest # Ensure repo root is importable _repo_root = Path(__file__).resolve().parent.parent.parent -if str(_repo_root) not in sys.path: try: import hermes_agent.tools.terminal # noqa: F401 _tt_mod = sys.modules["hermes_agent.tools.terminal"] diff --git a/tests/tools/test_modal_snapshot_isolation.py b/tests/tools/test_modal_snapshot_isolation.py index ab4115424..1ad6ed826 100644 --- a/tests/tools/test_modal_snapshot_isolation.py +++ b/tests/tools/test_modal_snapshot_isolation.py @@ -9,7 +9,7 @@ import pytest REPO_ROOT = Path(__file__).resolve().parents[2] -TOOLS_DIR = REPO_ROOT / "tools" +TOOLS_DIR = REPO_ROOT / "hermes_agent" def _load_module(module_name: str, path: Path): @@ -69,11 +69,11 @@ def _install_modal_test_modules( ) tools_package = types.ModuleType("tools") - tools_package.__path__ = [str(TOOLS_DIR)] # type: ignore[attr-defined] + tools_package.__path__ = [str(TOOLS_DIR / "tools")] # type: ignore[attr-defined] sys.modules["tools"] = tools_package env_package = types.ModuleType("hermes_agent.backends") - env_package.__path__ = [str(TOOLS_DIR / "environments")] # type: ignore[attr-defined] + env_package.__path__ = [str(TOOLS_DIR / "backends")] # type: ignore[attr-defined] sys.modules["hermes_agent.backends"] = env_package class _DummyBaseEnvironment: @@ -203,7 +203,7 @@ def test_modal_environment_migrates_legacy_snapshot_key_and_uses_snapshot_id(tmp snapshot_store.parent.mkdir(parents=True, exist_ok=True) snapshot_store.write_text(json.dumps({"task-legacy": "im-legacy123"})) - modal_module = _load_module("hermes_agent.backends.modal", TOOLS_DIR / "environments" / "modal.py") + modal_module = _load_module("hermes_agent.backends.modal", TOOLS_DIR / "backends" / "modal.py") env = modal_module.ModalEnvironment(image="python:3.11", task_id="task-legacy") try: @@ -220,7 +220,7 @@ def test_modal_environment_prunes_stale_direct_snapshot_and_retries_base_image(t snapshot_store.parent.mkdir(parents=True, exist_ok=True) snapshot_store.write_text(json.dumps({"direct:task-stale": "im-stale123"})) - modal_module = _load_module("hermes_agent.backends.modal", TOOLS_DIR / "environments" / "modal.py") + modal_module = _load_module("hermes_agent.backends.modal", TOOLS_DIR / "backends" / "modal.py") env = modal_module.ModalEnvironment(image="python:3.11", task_id="task-stale") try: @@ -237,7 +237,7 @@ def test_modal_environment_cleanup_writes_namespaced_snapshot_key(tmp_path): state = _install_modal_test_modules(tmp_path, snapshot_id="im-cleanup456") snapshot_store = state["snapshot_store"] - modal_module = _load_module("hermes_agent.backends.modal", TOOLS_DIR / "environments" / "modal.py") + modal_module = _load_module("hermes_agent.backends.modal", TOOLS_DIR / "backends" / "modal.py") env = modal_module.ModalEnvironment(image="python:3.11", task_id="task-cleanup") env.cleanup() @@ -246,7 +246,7 @@ def test_modal_environment_cleanup_writes_namespaced_snapshot_key(tmp_path): def test_resolve_modal_image_uses_snapshot_ids_and_registry_images(tmp_path): state = _install_modal_test_modules(tmp_path) - modal_module = _load_module("hermes_agent.backends.modal", TOOLS_DIR / "environments" / "modal.py") + modal_module = _load_module("hermes_agent.backends.modal", TOOLS_DIR / "backends" / "modal.py") snapshot_image = modal_module._resolve_modal_image("im-snapshot123") registry_image = modal_module._resolve_modal_image("python:3.11") diff --git a/tests/tools/test_registry.py b/tests/tools/test_registry.py index a5f5f3d86..a20d28f56 100644 --- a/tests/tools/test_registry.py +++ b/tests/tools/test_registry.py @@ -319,7 +319,7 @@ class TestBuiltinDiscovery: } with patch("hermes_agent.tools.registry.importlib.import_module"): - imported = discover_builtin_tools(Path(__file__).resolve().parents[2] / "tools") + imported = discover_builtin_tools(Path(__file__).resolve().parents[2] / "hermes_agent" / "tools") assert set(imported) == expected diff --git a/tests/tools/test_send_message_tool.py b/tests/tools/test_send_message_tool.py index ce04c7cb2..5eccb4d34 100644 --- a/tests/tools/test_send_message_tool.py +++ b/tests/tools/test_send_message_tool.py @@ -1376,7 +1376,7 @@ class TestSendDiscordForumMedia: def test_forum_with_media_uses_multipart(self, tmp_path, monkeypatch): """Forum + media → single multipart POST to /threads carrying the starter + files.""" - from hermes_agent.tools import send_message_tool as smt + from hermes_agent.tools import send_message as smt img = tmp_path / "photo.png" img.write_bytes(b"\x89PNGbytes") @@ -1478,7 +1478,7 @@ class TestForumProbeCache: """_DISCORD_CHANNEL_TYPE_PROBE_CACHE memoizes forum detection results.""" def setup_method(self): - from hermes_agent.tools import send_message_tool as smt + from hermes_agent.tools import send_message as smt smt._DISCORD_CHANNEL_TYPE_PROBE_CACHE.clear() def test_cache_round_trip(self): @@ -1522,7 +1522,7 @@ class TestForumProbeCache: thread_session.post = MagicMock(return_value=thread_resp) # Two _send_discord calls: first does probe + thread-create; second should skip probe - from hermes_agent.tools import send_message_tool as smt + from hermes_agent.tools import send_message as smt sessions_created = [] diff --git a/tests/tools/test_voice_cli_integration.py b/tests/tools/test_voice_cli_integration.py index 5132def89..19bf2f9aa 100644 --- a/tests/tools/test_voice_cli_integration.py +++ b/tests/tools/test_voice_cli_integration.py @@ -500,7 +500,7 @@ class TestEdgeTTSLazyImport: reference bare 'edge_tts' module name.""" import ast as _ast - with open("tools/tts_tool.py") as f: + with open("hermes_agent/tools/media/tts.py") as f: tree = _ast.parse(f.read()) for node in _ast.walk(tree): @@ -538,7 +538,7 @@ class TestStreamingTTSOutputStreamCleanup: output_stream even on exception.""" import ast as _ast - with open("tools/tts_tool.py") as f: + with open("hermes_agent/tools/media/tts.py") as f: tree = _ast.parse(f.read()) for node in _ast.walk(tree): @@ -687,7 +687,7 @@ class TestBrowserToolSignalHandlerRemoved: def test_no_signal_handler_registration(self): """Source check: browser_tool.py must not call signal.signal() for SIGINT or SIGTERM.""" - with open("tools/browser_tool.py") as f: + with open("hermes_agent/tools/browser/tool.py") as f: source = f.read() lines = source.split("\n") diff --git a/tests/tools/test_web_tools_config.py b/tests/tools/test_web_tools_config.py index 133c3e4c0..123e5960c 100644 --- a/tests/tools/test_web_tools_config.py +++ b/tests/tools/test_web_tools_config.py @@ -22,9 +22,9 @@ class TestFirecrawlClientConfig: def setup_method(self): """Reset client and env vars before each test.""" - import hermes_agent.tools.web - tools.web_tools._firecrawl_client = None - tools.web_tools._firecrawl_client_config = None + from hermes_agent.tools import web as web_tools_mod + web_tools_mod._firecrawl_client = None + web_tools_mod._firecrawl_client_config = None for key in ( "FIRECRAWL_API_KEY", "FIRECRAWL_API_URL", @@ -46,9 +46,9 @@ class TestFirecrawlClientConfig: def teardown_method(self): """Reset client after each test.""" - import hermes_agent.tools.web - tools.web_tools._firecrawl_client = None - tools.web_tools._firecrawl_client_config = None + from hermes_agent.tools import web as web_tools_mod + web_tools_mod._firecrawl_client = None + web_tools_mod._firecrawl_client_config = None for key in ( "FIRECRAWL_API_KEY", "FIRECRAWL_API_URL", @@ -156,30 +156,30 @@ class TestFirecrawlClientConfig: "HOME": str(real_home), "HERMES_HOME": str(hermes_home), }, clear=False): - import hermes_agent.tools.web - importlib.reload(tools.web_tools) - assert tools.web_tools._read_nous_access_token() == "nous-token" + from hermes_agent.tools import web as web_tools_mod + importlib.reload(web_tools_mod) + assert web_tools_mod._read_nous_access_token() == "nous-token" def test_check_auxiliary_model_re_resolves_backend_each_call(self): """Availability checks should not be pinned to module import state.""" - import hermes_agent.tools.web + from hermes_agent.tools import web as web_tools_mod # Simulate the pre-fix import-time cache slot for regression coverage. - tools.web_tools.__dict__["_aux_async_client"] = None + web_tools_mod.__dict__["_aux_async_client"] = None with patch( "hermes_agent.tools.web.get_async_text_auxiliary_client", side_effect=[(None, None), (MagicMock(base_url="https://api.openrouter.ai/v1"), "test-model")], ): - assert tools.web_tools.check_auxiliary_model() is False - assert tools.web_tools.check_auxiliary_model() is True + assert web_tools_mod.check_auxiliary_model() is False + assert web_tools_mod.check_auxiliary_model() is True @pytest.mark.asyncio async def test_summarizer_re_resolves_backend_after_initial_unavailable_state(self): """Summarization should pick up a backend that becomes available later in-process.""" - import hermes_agent.tools.web + from hermes_agent.tools import web as web_tools_mod - tools.web_tools.__dict__["_aux_async_client"] = None + web_tools_mod.__dict__["_aux_async_client"] = None response = MagicMock() response.choices = [MagicMock(message=MagicMock(content="summary text"))] @@ -191,8 +191,8 @@ class TestFirecrawlClientConfig: "hermes_agent.tools.web.async_call_llm", new=AsyncMock(return_value=response), ) as mock_async_call: - assert tools.web_tools.check_auxiliary_model() is False - result = await tools.web_tools._call_summarizer_llm( + assert web_tools_mod.check_auxiliary_model() is False + result = await web_tools_mod._call_summarizer_llm( "Some content worth summarizing", "Source: https://example.com\n\n", None, @@ -215,7 +215,7 @@ class TestFirecrawlClientConfig: def test_constructor_failure_allows_retry(self): """If Firecrawl() raises, next call should retry (not return None).""" - import hermes_agent.tools.web + from hermes_agent.tools import web as web_tools_mod with patch.dict(os.environ, {"FIRECRAWL_API_KEY": "fc-test"}): with patch("hermes_agent.tools.web.Firecrawl") as mock_fc: mock_fc.side_effect = [RuntimeError("init failed"), MagicMock()] @@ -225,7 +225,7 @@ class TestFirecrawlClientConfig: _get_firecrawl_client() # Client stayed None, so retry should work - assert tools.web_tools._firecrawl_client is None + assert web_tools_mod._firecrawl_client is None result = _get_firecrawl_client() assert result is not None @@ -401,8 +401,8 @@ class TestParallelClientConfig: """Test suite for Parallel client initialization.""" def setup_method(self): - import hermes_agent.tools.web - tools.web_tools._parallel_client = None + from hermes_agent.tools import web as web_tools_mod + web_tools_mod._parallel_client = None os.environ.pop("PARALLEL_API_KEY", None) fake_parallel = types.ModuleType("parallel") @@ -419,8 +419,8 @@ class TestParallelClientConfig: sys.modules["parallel"] = fake_parallel def teardown_method(self): - import hermes_agent.tools.web - tools.web_tools._parallel_client = None + from hermes_agent.tools import web as web_tools_mod + web_tools_mod._parallel_client = None os.environ.pop("PARALLEL_API_KEY", None) sys.modules.pop("parallel", None) @@ -452,7 +452,7 @@ class TestWebSearchErrorHandling: """Test suite for web_search_tool() error responses.""" def test_search_error_response_does_not_expose_diagnostics(self): - import hermes_agent.tools.web + from hermes_agent.tools import web as web_tools_mod firecrawl_client = MagicMock() firecrawl_client.search.side_effect = RuntimeError("boom") @@ -460,9 +460,9 @@ class TestWebSearchErrorHandling: with patch("hermes_agent.tools.web._get_backend", return_value="firecrawl"), \ patch("hermes_agent.tools.web._get_firecrawl_client", return_value=firecrawl_client), \ patch("hermes_agent.tools.interrupt.is_interrupted", return_value=False), \ - patch.object(tools.web_tools._debug, "log_call") as mock_log_call, \ - patch.object(tools.web_tools._debug, "save"): - result = json.loads(tools.web_tools.web_search_tool("test query", limit=3)) + patch.object(web_tools_mod._debug, "log_call") as mock_log_call, \ + patch.object(web_tools_mod._debug, "save"): + result = json.loads(web_tools_mod.web_search_tool("test query", limit=3)) assert result == {"error": "Error searching web: boom"} diff --git a/tests/tools/test_website_policy.py b/tests/tools/test_website_policy.py index 7dfc8e27e..83e9f0772 100644 --- a/tests/tools/test_website_policy.py +++ b/tests/tools/test_website_policy.py @@ -349,7 +349,7 @@ def test_browser_navigate_allows_when_shared_file_missing(monkeypatch, tmp_path) @pytest.mark.asyncio async def test_web_extract_short_circuits_blocked_url(monkeypatch): - from hermes_agent.tools import web_tools + from hermes_agent.tools import web as web_tools # Allow test URLs past SSRF check so website policy is what gets tested monkeypatch.setattr(web_tools, "is_safe_url", lambda url: True) @@ -397,7 +397,7 @@ def test_check_website_access_fails_open_on_malformed_config(tmp_path, monkeypat @pytest.mark.asyncio async def test_web_extract_blocks_redirected_final_url(monkeypatch): - from hermes_agent.tools import web_tools + from hermes_agent.tools import web as web_tools # Allow test URLs past SSRF check so website policy is what gets tested monkeypatch.setattr(web_tools, "is_safe_url", lambda url: True) @@ -437,7 +437,7 @@ async def test_web_extract_blocks_redirected_final_url(monkeypatch): @pytest.mark.asyncio async def test_web_crawl_short_circuits_blocked_url(monkeypatch): - from hermes_agent.tools import web_tools + from hermes_agent.tools import web as web_tools # web_crawl_tool checks for Firecrawl env before website policy monkeypatch.setenv("FIRECRAWL_API_KEY", "fake-key") @@ -468,7 +468,7 @@ async def test_web_crawl_short_circuits_blocked_url(monkeypatch): @pytest.mark.asyncio async def test_web_crawl_blocks_redirected_final_url(monkeypatch): - from hermes_agent.tools import web_tools + from hermes_agent.tools import web as web_tools # web_crawl_tool checks for Firecrawl env before website policy monkeypatch.setenv("FIRECRAWL_API_KEY", "fake-key") diff --git a/tests/tools/test_windows_compat.py b/tests/tools/test_windows_compat.py index ec04d2095..6a0c91783 100644 --- a/tests/tools/test_windows_compat.py +++ b/tests/tools/test_windows_compat.py @@ -10,10 +10,10 @@ from pathlib import Path # Files that must have Windows-safe process management GUARDED_FILES = [ - "tools/environments/local.py", - "tools/process_registry.py", - "tools/code_execution_tool.py", - "gateway/platforms/whatsapp.py", + "hermes_agent/backends/local.py", + "hermes_agent/tools/process_registry.py", + "hermes_agent/tools/code_execution.py", + "hermes_agent/gateway/platforms/whatsapp.py", ] PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent diff --git a/tests/tools/test_yolo_mode.py b/tests/tools/test_yolo_mode.py index ad599cfde..f5845d1ad 100644 --- a/tests/tools/test_yolo_mode.py +++ b/tests/tools/test_yolo_mode.py @@ -4,7 +4,7 @@ import os import pytest import hermes_agent.tools.security.approval as approval_module -import hermes_agent.tools.security.tirith +from hermes_agent.tools.security import tirith as tirith_security from hermes_agent.tools.security.approval import ( check_all_command_guards, @@ -93,7 +93,7 @@ class TestYoloMode: called["value"] = True return {"action": "block", "findings": [], "summary": "should never run"} - monkeypatch.setattr(tools.tirith_security, "check_command_security", fake_check) + monkeypatch.setattr(tirith_security, "check_command_security", fake_check) result = check_all_command_guards("rm -rf /", "local") assert result["approved"]