fix(restructure): fix test regressions from import rewrite

Fix variable name breakage (run_agent, hermes_constants, etc.) where
import rewriter changed 'import X' to 'import hermes_agent.Y' but
test code still referenced 'X' as a variable name.

Fix package-vs-module confusion (cli.auth, cli.models, cli.ui) where
single files became directories.

Fix hardcoded file paths in tests pointing to old locations.
Fix tool registry to discover tools in subpackage directories.
Fix stale import in hermes_agent/tools/__init__.py.

Part of #14182, #14183
This commit is contained in:
alt-glitch 2026-04-23 12:05:10 +05:30
parent 4b16341975
commit a1e667b9f2
113 changed files with 343 additions and 345 deletions

View file

@ -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)

View file

@ -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:

View file

@ -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")

View file

@ -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,

View file

@ -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.

View file

@ -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

View file

@ -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 (

View file

@ -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]
# ---------------------------------------------------------------------------

View file

@ -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())

View file

@ -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),

View file

@ -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

View file

@ -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,

View file

@ -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"

View file

@ -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) ────────────────────────────────────────

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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:

View file

@ -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():

View file

@ -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,

View file

@ -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()

View file

@ -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__)

View file

@ -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)

View file

@ -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:

View file

@ -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"

View file

@ -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:

View file

@ -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"

View file

@ -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

View file

@ -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",

View file

@ -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(

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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
# ---------------------------------------------------------------------------

View file

@ -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"
)],

View file

@ -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()

View file

@ -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
# ---------------------------------------------------------------------------

View file

@ -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
# ---------------------------------------------------------------------------

View file

@ -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):

View file

@ -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()

View file

@ -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()

View file

@ -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,

View file

@ -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")

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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:

View file

@ -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:

View file

@ -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
# =============================================================================

View file

@ -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):

View file

@ -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

View file

@ -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",))

View file

@ -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):

View file

@ -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()

View file

@ -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")

View file

@ -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)

View file

@ -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):

View file

@ -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 = {}

View file

@ -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
# ---------------------------------------------------------------------------

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -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(

View file

@ -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",
]

View file

@ -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

View file

@ -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."""

View file

@ -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": {

View file

@ -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",

View file

@ -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,

View file

@ -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

View file

@ -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
# ---------------------------------------------------------------------------

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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):

View file

@ -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)

View file

@ -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:

View file

@ -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:

View file

@ -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, (

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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"]

View file

@ -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

View file

@ -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)

View file

@ -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():

View file

@ -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()

View file

@ -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

View file

@ -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):

View file

@ -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):

View file

@ -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

View file

@ -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."""

View file

@ -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)

View file

@ -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"

Some files were not shown because too many files have changed in this diff Show more