Merge branch 'main' of github.com:NousResearch/hermes-agent into feat/ink-refactor
This commit is contained in:
@@ -260,7 +260,7 @@ class TestWaitForGatewayExit:
|
||||
def test_kill_gateway_processes_force_uses_helper(self, monkeypatch):
|
||||
calls = []
|
||||
|
||||
monkeypatch.setattr(gateway, "find_gateway_pids", lambda exclude_pids=None: [11, 22])
|
||||
monkeypatch.setattr(gateway, "find_gateway_pids", lambda exclude_pids=None, all_profiles=False: [11, 22])
|
||||
monkeypatch.setattr(gateway, "terminate_pid", lambda pid, force=False: calls.append((pid, force)))
|
||||
|
||||
killed = gateway.kill_gateway_processes(force=True)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Tests for gateway service management helpers."""
|
||||
|
||||
import os
|
||||
import pwd
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
@@ -129,7 +130,7 @@ class TestGatewayStopCleanup:
|
||||
monkeypatch.setattr(
|
||||
gateway_cli,
|
||||
"kill_gateway_processes",
|
||||
lambda force=False: kill_calls.append(force) or 2,
|
||||
lambda force=False, all_profiles=False: kill_calls.append(force) or 2,
|
||||
)
|
||||
|
||||
gateway_cli.gateway_command(SimpleNamespace(gateway_command="stop"))
|
||||
@@ -155,7 +156,7 @@ class TestGatewayStopCleanup:
|
||||
monkeypatch.setattr(
|
||||
gateway_cli,
|
||||
"kill_gateway_processes",
|
||||
lambda force=False: kill_calls.append(force) or 2,
|
||||
lambda force=False, all_profiles=False: kill_calls.append(force) or 2,
|
||||
)
|
||||
|
||||
gateway_cli.gateway_command(SimpleNamespace(gateway_command="stop", **{"all": True}))
|
||||
@@ -924,6 +925,23 @@ class TestProfileArg:
|
||||
assert "<string>--profile</string>" in plist
|
||||
assert "<string>mybot</string>" in plist
|
||||
|
||||
def test_launchd_plist_path_uses_real_user_home_not_profile_home(self, tmp_path, monkeypatch):
|
||||
profile_dir = tmp_path / ".hermes" / "profiles" / "orcha"
|
||||
profile_dir.mkdir(parents=True)
|
||||
machine_home = tmp_path / "machine-home"
|
||||
machine_home.mkdir()
|
||||
profile_home = profile_dir / "home"
|
||||
profile_home.mkdir()
|
||||
|
||||
monkeypatch.setattr(Path, "home", lambda: profile_home)
|
||||
monkeypatch.setenv("HERMES_HOME", str(profile_dir))
|
||||
monkeypatch.setattr(gateway_cli, "get_hermes_home", lambda: profile_dir)
|
||||
monkeypatch.setattr(pwd, "getpwuid", lambda uid: SimpleNamespace(pw_dir=str(machine_home)))
|
||||
|
||||
plist_path = gateway_cli.get_launchd_plist_path()
|
||||
|
||||
assert plist_path == machine_home / "Library" / "LaunchAgents" / "ai.hermes.gateway-orcha.plist"
|
||||
|
||||
|
||||
class TestRemapPathForUser:
|
||||
"""Unit tests for _remap_path_for_user()."""
|
||||
|
||||
@@ -1214,3 +1214,115 @@ def test_openrouter_provider_not_affected_by_custom_fix(monkeypatch):
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="openrouter")
|
||||
assert resolved["provider"] == "openrouter"
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# fix #7828 — custom_providers model field must propagate to runtime
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_get_named_custom_provider_includes_model(monkeypatch):
|
||||
"""_get_named_custom_provider should include the model field from config."""
|
||||
monkeypatch.setattr(rp, "load_config", lambda: {
|
||||
"custom_providers": [{
|
||||
"name": "my-dashscope",
|
||||
"base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
|
||||
"api_key": "test-key",
|
||||
"api_mode": "chat_completions",
|
||||
"model": "qwen3.6-plus",
|
||||
}],
|
||||
})
|
||||
|
||||
result = rp._get_named_custom_provider("my-dashscope")
|
||||
assert result is not None
|
||||
assert result["model"] == "qwen3.6-plus"
|
||||
|
||||
|
||||
def test_get_named_custom_provider_excludes_empty_model(monkeypatch):
|
||||
"""Empty or whitespace-only model field should not appear in result."""
|
||||
for model_val in ["", " ", None]:
|
||||
entry = {
|
||||
"name": "test-ep",
|
||||
"base_url": "https://example.com/v1",
|
||||
"api_key": "key",
|
||||
}
|
||||
if model_val is not None:
|
||||
entry["model"] = model_val
|
||||
|
||||
monkeypatch.setattr(rp, "load_config", lambda e=entry: {
|
||||
"custom_providers": [e],
|
||||
})
|
||||
|
||||
result = rp._get_named_custom_provider("test-ep")
|
||||
assert result is not None
|
||||
assert "model" not in result, (
|
||||
f"model field {model_val!r} should not be included in result"
|
||||
)
|
||||
|
||||
|
||||
def test_named_custom_runtime_propagates_model_direct_path(monkeypatch):
|
||||
"""Model should propagate through the direct (non-pool) resolution path."""
|
||||
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "my-server")
|
||||
monkeypatch.setattr(
|
||||
rp, "_get_named_custom_provider",
|
||||
lambda p: {
|
||||
"name": "my-server",
|
||||
"base_url": "http://localhost:8000/v1",
|
||||
"api_key": "test-key",
|
||||
"model": "qwen3.6-plus",
|
||||
},
|
||||
)
|
||||
# Ensure pool doesn't intercept
|
||||
monkeypatch.setattr(rp, "_try_resolve_from_custom_pool", lambda *a, **k: None)
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="my-server")
|
||||
assert resolved["model"] == "qwen3.6-plus"
|
||||
assert resolved["provider"] == "custom"
|
||||
|
||||
|
||||
def test_named_custom_runtime_propagates_model_pool_path(monkeypatch):
|
||||
"""Model should propagate even when credential pool handles credentials."""
|
||||
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "my-server")
|
||||
monkeypatch.setattr(
|
||||
rp, "_get_named_custom_provider",
|
||||
lambda p: {
|
||||
"name": "my-server",
|
||||
"base_url": "http://localhost:8000/v1",
|
||||
"api_key": "test-key",
|
||||
"model": "qwen3.6-plus",
|
||||
},
|
||||
)
|
||||
# Pool returns a result (intercepting the normal path)
|
||||
monkeypatch.setattr(
|
||||
rp, "_try_resolve_from_custom_pool",
|
||||
lambda *a, **k: {
|
||||
"provider": "custom",
|
||||
"api_mode": "chat_completions",
|
||||
"base_url": "http://localhost:8000/v1",
|
||||
"api_key": "pool-key",
|
||||
"source": "pool:custom:my-server",
|
||||
},
|
||||
)
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="my-server")
|
||||
assert resolved["model"] == "qwen3.6-plus", (
|
||||
"model must be injected into pool result"
|
||||
)
|
||||
assert resolved["api_key"] == "pool-key", "pool credentials should be used"
|
||||
|
||||
|
||||
def test_named_custom_runtime_no_model_when_absent(monkeypatch):
|
||||
"""When custom_providers entry has no model field, runtime should not either."""
|
||||
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "my-server")
|
||||
monkeypatch.setattr(
|
||||
rp, "_get_named_custom_provider",
|
||||
lambda p: {
|
||||
"name": "my-server",
|
||||
"base_url": "http://localhost:8000/v1",
|
||||
"api_key": "test-key",
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(rp, "_try_resolve_from_custom_pool", lambda *a, **k: None)
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="my-server")
|
||||
assert "model" not in resolved
|
||||
|
||||
@@ -191,6 +191,19 @@ class TestLaunchdPlistPath:
|
||||
raise AssertionError("PATH key not found in plist")
|
||||
|
||||
|
||||
class TestLaunchdPlistCurrentness:
|
||||
def test_launchd_plist_is_current_ignores_path_drift(self, tmp_path, monkeypatch):
|
||||
plist_path = tmp_path / "ai.hermes.gateway.plist"
|
||||
monkeypatch.setattr(gateway_cli, "get_launchd_plist_path", lambda: plist_path)
|
||||
|
||||
monkeypatch.setenv("PATH", "/custom/bin:/usr/bin:/bin")
|
||||
plist_path.write_text(gateway_cli.generate_launchd_plist(), encoding="utf-8")
|
||||
|
||||
monkeypatch.setenv("PATH", "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin")
|
||||
|
||||
assert gateway_cli.launchd_plist_is_current() is True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# cmd_update — macOS launchd detection
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -536,7 +549,7 @@ class TestServicePidExclusion:
|
||||
gateway_cli, "_get_service_pids", return_value={SERVICE_PID}
|
||||
), patch.object(
|
||||
gateway_cli, "find_gateway_pids",
|
||||
side_effect=lambda exclude_pids=None: (
|
||||
side_effect=lambda exclude_pids=None, all_profiles=False: (
|
||||
[SERVICE_PID] if not exclude_pids else
|
||||
[p for p in [SERVICE_PID] if p not in exclude_pids]
|
||||
),
|
||||
@@ -579,7 +592,7 @@ class TestServicePidExclusion:
|
||||
gateway_cli, "_get_service_pids", return_value={SERVICE_PID}
|
||||
), patch.object(
|
||||
gateway_cli, "find_gateway_pids",
|
||||
side_effect=lambda exclude_pids=None: (
|
||||
side_effect=lambda exclude_pids=None, all_profiles=False: (
|
||||
[SERVICE_PID] if not exclude_pids else
|
||||
[p for p in [SERVICE_PID] if p not in exclude_pids]
|
||||
),
|
||||
@@ -618,7 +631,7 @@ class TestServicePidExclusion:
|
||||
launchctl_loaded=True,
|
||||
)
|
||||
|
||||
def fake_find(exclude_pids=None):
|
||||
def fake_find(exclude_pids=None, all_profiles=False):
|
||||
_exclude = exclude_pids or set()
|
||||
return [p for p in [SERVICE_PID, MANUAL_PID] if p not in _exclude]
|
||||
|
||||
@@ -760,3 +773,28 @@ class TestFindGatewayPidsExclude:
|
||||
pids = gateway_cli.find_gateway_pids()
|
||||
assert 100 in pids
|
||||
assert 200 in pids
|
||||
|
||||
def test_filters_to_current_profile(self, monkeypatch, tmp_path):
|
||||
profile_dir = tmp_path / ".hermes" / "profiles" / "orcha"
|
||||
profile_dir.mkdir(parents=True)
|
||||
monkeypatch.setattr(gateway_cli, "is_windows", lambda: False)
|
||||
monkeypatch.setattr(gateway_cli, "get_hermes_home", lambda: profile_dir)
|
||||
|
||||
def fake_run(cmd, **kwargs):
|
||||
return subprocess.CompletedProcess(
|
||||
cmd, 0,
|
||||
stdout=(
|
||||
"100 /Users/dgrieco/.hermes/hermes-agent/venv/bin/python -m hermes_cli.main --profile orcha gateway run --replace\n"
|
||||
"200 /Users/dgrieco/.hermes/hermes-agent/venv/bin/python -m hermes_cli.main --profile other gateway run --replace\n"
|
||||
),
|
||||
stderr="",
|
||||
)
|
||||
|
||||
monkeypatch.setattr(gateway_cli.subprocess, "run", fake_run)
|
||||
monkeypatch.setattr("os.getpid", lambda: 999)
|
||||
monkeypatch.setattr(gateway_cli, "_get_service_pids", lambda: set())
|
||||
monkeypatch.setattr(gateway_cli, "_profile_arg", lambda hermes_home=None: "--profile orcha")
|
||||
|
||||
pids = gateway_cli.find_gateway_pids()
|
||||
|
||||
assert pids == [100]
|
||||
|
||||
Reference in New Issue
Block a user