mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
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:
parent
4b16341975
commit
a1e667b9f2
113 changed files with 343 additions and 345 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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) ────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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__)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)],
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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",))
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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 = {}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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, (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue