fix(#2422): support packaged Hermes Agent discovery
This commit is contained in:
1 parent
53348df68d
commit
9bd96fb75d
7 files changed
+272
-23
No files matched your search
@@ -325,7 +325,7 @@ If an AI assistant is helping with install, reinstall, bootstrap, provider setup
|
||||
|
||||
| Thing | How it finds it |
|
||||
|---|---|
|
||||
| Hermes agent dir | `HERMES_WEBUI_AGENT_DIR` env, then `$HERMES_HOME/hermes-agent` (Windows default `%LOCALAPPDATA%\hermes\hermes-agent`, POSIX default `~/.hermes/hermes-agent`), then sibling `../hermes-agent` |
|
||||
| Hermes agent dir | `HERMES_WEBUI_AGENT_DIR`, then known checkout paths, the `hermes` launcher on `PATH`, and finally the installed `run_agent` module exposed by `HERMES_WEBUI_PYTHON` |
|
||||
| Python executable | Agent venv first, then `.venv` in this repo, then system `python3` |
|
||||
| State directory | `HERMES_WEBUI_STATE_DIR` env, then `$HERMES_HOME/webui` (Windows default `%LOCALAPPDATA%\hermes\webui`, POSIX default `~/.hermes/webui`) |
|
||||
| Default workspace | `HERMES_WEBUI_DEFAULT_WORKSPACE` env, then `~/workspace`, then state dir |
|
||||
@@ -355,7 +355,7 @@ Full list of environment variables:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `HERMES_WEBUI_AGENT_DIR` | auto-discovered | Path to the hermes-agent checkout |
|
||||
| `HERMES_WEBUI_AGENT_DIR` | auto-discovered | Path to the Hermes Agent source or installed module root |
|
||||
| `HERMES_WEBUI_PYTHON` | auto-discovered | Python executable |
|
||||
| `HERMES_WEBUI_HOST` | `127.0.0.1` | Bind address (`0.0.0.0` for all IPv4, `::` for all IPv6, `::1` for IPv6 loopback) |
|
||||
| `HERMES_WEBUI_PORT` | `8787` | Port |
|
||||
@@ -401,35 +401,38 @@ Then add the module and configure it in `nixosModules`:
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
hermes-agent.url = "github:NousResearch/hermes-agent";
|
||||
hermes-webui.url = "github:nesquena/hermes-webui";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, hermes-webui, ... }: {
|
||||
outputs = { self, nixpkgs, hermes-agent, hermes-webui, ... }: {
|
||||
nixosConfigurations.<host> = nixpkgs.lib.nixosSystem {
|
||||
modules = [
|
||||
hermes-agent.nixosModules.default
|
||||
hermes-webui.nixosModules.default
|
||||
({ pkgs, ... }: {
|
||||
services.hermes-agent.enable = true;
|
||||
services.hermes-webui = {
|
||||
enable = true;
|
||||
host = "127.0.0.1";
|
||||
port = 8787;
|
||||
stateDir = "/var/lib/hermes-webui";
|
||||
user = "hermes";
|
||||
group = "hermes";
|
||||
hermesHome = "/var/lib/hermes/.hermes";
|
||||
agent.package = hermes-agent.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
||||
environmentFiles = [ "/run/secrets/hermes-webui.env" ];
|
||||
};
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Service example:
|
||||
|
||||
```nix
|
||||
services.hermes-webui = {
|
||||
enable = true;
|
||||
host = "127.0.0.1";
|
||||
port = 8787;
|
||||
stateDir = "/var/lib/hermes-webui";
|
||||
agent.dir = "/var/lib/hermes/hermes-agent";
|
||||
environmentFiles = [ "/run/secrets/hermes-webui.env" ];
|
||||
};
|
||||
```
|
||||
|
||||
The module defaults to `127.0.0.1`. Set `host = "0.0.0.0"` and `openFirewall = true` only when you want direct network access, and pair that with auth, for example `HERMES_WEBUI_PASSWORD` via `environmentFiles`.
|
||||
|
||||
You can also set `agent.package` instead of `agent.dir` when you are using a compatible Hermes Agent package layout. This path depends on package metadata that the current published Hermes Agent flake may not expose yet, so explicit `agent.dir` and `agent.python` are the reliable integration options for existing agent packages. When the package exposes `passthru.hermesAgentDir`, the module derives `HERMES_WEBUI_AGENT_DIR` from that path. When it exposes `passthru.hermesVenv`, the module derives `HERMES_WEBUI_PYTHON` from the venv interpreter so bootstrap can use agent dependencies without creating a local `.venv`.
|
||||
The published Hermes Agent package exposes `passthru.hermesVenv`, so the module derives `HERMES_WEBUI_PYTHON` from its interpreter. Bootstrap then locates the installed `run_agent.py` through that interpreter without importing Agent code and exports its parent as `HERMES_WEBUI_AGENT_DIR`. Packages may expose `passthru.hermesAgentDir` as a direct path instead. Use `agent.dir` and `agent.python` as explicit overrides for custom package layouts.
|
||||
|
||||
When WebUI reads shared Hermes Agent state, run the service as a user that can already read that state. For a co-located Hermes Agent service, set `user` and `group` to the agent service account; the module only creates the default `hermes-webui` account and never changes ownership of an existing `hermesHome`.
|
||||
|
||||
|
||||
+29
-1
@@ -182,6 +182,31 @@ def _agent_dir_from_hermes_cli() -> Path | None:
|
||||
return None
|
||||
|
||||
|
||||
def _agent_dir_from_python(python_exe: str) -> Path | None:
|
||||
script = (
|
||||
'import importlib.util\n'
|
||||
'spec = importlib.util.find_spec("run_agent")\n'
|
||||
'print(spec.origin if spec else "")\n'
|
||||
)
|
||||
try:
|
||||
check = subprocess.run(
|
||||
[python_exe, "-c", script],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
except OSError:
|
||||
return None
|
||||
if check.returncode != 0:
|
||||
return None
|
||||
lines = check.stdout.splitlines()
|
||||
if not lines:
|
||||
return None
|
||||
origin = Path(lines[0].strip())
|
||||
if not origin.is_absolute() or origin.name != "run_agent.py" or not origin.is_file():
|
||||
return None
|
||||
return origin.parent.resolve()
|
||||
|
||||
|
||||
def discover_agent_dir() -> Path | None:
|
||||
home = Path(os.getenv("HERMES_HOME", str(Path.home() / ".hermes"))).expanduser()
|
||||
candidates = [
|
||||
@@ -202,7 +227,10 @@ def discover_agent_dir() -> Path | None:
|
||||
candidate = Path(raw).expanduser().resolve()
|
||||
if candidate.exists() and (candidate / "run_agent.py").exists():
|
||||
return candidate
|
||||
return _agent_dir_from_hermes_cli()
|
||||
agent_dir = _agent_dir_from_hermes_cli()
|
||||
if agent_dir:
|
||||
return agent_dir
|
||||
return _agent_dir_from_python(discover_launcher_python(None))
|
||||
|
||||
|
||||
def discover_launcher_python(agent_dir: Path | None) -> str:
|
||||
|
||||
@@ -41,8 +41,32 @@
|
||||
}
|
||||
];
|
||||
};
|
||||
packageOnlyAgentVenv = pkgs.runCommand "hermes-agent-package-only-venv-${system}" { } ''
|
||||
mkdir -p "$out/bin"
|
||||
touch "$out/bin/python3"
|
||||
'';
|
||||
packageOnlyAgentPackage = pkgs.runCommand "hermes-agent-package-only-${system}" {
|
||||
passthru.hermesVenv = packageOnlyAgentVenv;
|
||||
} ''
|
||||
touch "$out"
|
||||
'';
|
||||
packageOnlyModuleConfig = nixpkgs.lib.nixosSystem {
|
||||
inherit system;
|
||||
modules = [
|
||||
hermesModule
|
||||
{
|
||||
services.hermes-webui = {
|
||||
enable = true;
|
||||
package = package;
|
||||
agent.package = packageOnlyAgentPackage;
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
moduleServiceEnvironment = nixpkgs.lib.concatStringsSep "\n" moduleConfig.config.systemd.services.hermes-webui.serviceConfig.Environment;
|
||||
envProbe = pkgs.writeText "hermes-webui-nixos-env-${system}.txt" moduleServiceEnvironment;
|
||||
packageOnlyServiceEnvironment = nixpkgs.lib.concatStringsSep "\n" packageOnlyModuleConfig.config.systemd.services.hermes-webui.serviceConfig.Environment;
|
||||
packageOnlyEnvProbe = pkgs.writeText "hermes-webui-nixos-package-only-env-${system}.txt" packageOnlyServiceEnvironment;
|
||||
in
|
||||
{
|
||||
module-env-mapping = pkgs.runCommand "hermes-webui-nixos-module-${system}" {
|
||||
@@ -52,6 +76,8 @@
|
||||
grep -q 'HERMES_WEBUI_PORT=8787' ${envProbe}
|
||||
grep -q 'HERMES_WEBUI_STATE_DIR=/var/lib/hermes-webui' ${envProbe}
|
||||
grep -q 'HERMES_WEBUI_AGENT_DIR=/var/lib/hermes-agent' ${envProbe}
|
||||
grep -q 'HERMES_WEBUI_PYTHON=${packageOnlyAgentVenv}/bin/python3' ${packageOnlyEnvProbe}
|
||||
! grep -q 'HERMES_WEBUI_AGENT_DIR=' ${packageOnlyEnvProbe}
|
||||
touch "$out"
|
||||
'';
|
||||
runtime-layout = pkgs.runCommand "hermes-webui-runtime-layout-${system}" {
|
||||
|
||||
@@ -147,7 +147,7 @@ in
|
||||
package = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.package;
|
||||
default = null;
|
||||
description = "Package to derive HERMES_WEBUI_AGENT_DIR from passthru.hermesAgentDir and HERMES_WEBUI_PYTHON from passthru.hermesVenv.";
|
||||
description = "Package to derive HERMES_WEBUI_PYTHON from passthru.hermesVenv and optionally HERMES_WEBUI_AGENT_DIR from passthru.hermesAgentDir.";
|
||||
};
|
||||
|
||||
dir = lib.mkOption {
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
"""Tests for `discover_agent_dir` shebang-based fallback.
|
||||
"""Tests for `discover_agent_dir` launcher and interpreter fallbacks.
|
||||
|
||||
When the standard candidate paths (`~/.hermes/hermes-agent`, `~/hermes-agent`,
|
||||
`<webui-parent>/hermes-agent`, `HERMES_WEBUI_AGENT_DIR`) don't match, bootstrap
|
||||
should fall back to introspecting the `hermes` console-script's shebang —
|
||||
that's a reliable pointer to the install root because the installer writes the
|
||||
venv-relative interpreter path there.
|
||||
bootstrap checks the `hermes` launcher, then asks the configured Python for the
|
||||
installed `run_agent` module without importing it.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
|
||||
import bootstrap
|
||||
|
||||
|
||||
@@ -75,6 +76,8 @@ def _isolate_discover_agent_dir(monkeypatch, tmp_path, hermes_path):
|
||||
monkeypatch.setattr(bootstrap.shutil, "which", lambda name: str(hermes_path) if name == "hermes" else None)
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "no-such-hermes-home"))
|
||||
monkeypatch.delenv("HERMES_WEBUI_AGENT_DIR", raising=False)
|
||||
monkeypatch.delenv("HERMES_WEBUI_PYTHON", raising=False)
|
||||
monkeypatch.setattr(bootstrap, "_agent_dir_from_python", lambda _python: None)
|
||||
# Force REPO_ROOT.parent to a dir that won't accidentally contain a
|
||||
# `hermes-agent` sibling on the dev machine running these tests.
|
||||
monkeypatch.setattr(bootstrap, "REPO_ROOT", tmp_path / "isolated-repo-root")
|
||||
@@ -133,6 +136,11 @@ def test_explicit_candidate_takes_precedence_over_shebang(monkeypatch, tmp_path)
|
||||
hermes = _make_hermes_cli(tmp_path, str(venv_python))
|
||||
_isolate_discover_agent_dir(monkeypatch, tmp_path, hermes)
|
||||
monkeypatch.setenv("HERMES_WEBUI_AGENT_DIR", str(explicit_install))
|
||||
monkeypatch.setattr(
|
||||
bootstrap,
|
||||
"_agent_dir_from_python",
|
||||
lambda _python: (_ for _ in ()).throw(AssertionError("Python probe must not run")),
|
||||
)
|
||||
|
||||
assert bootstrap.discover_agent_dir() == explicit_install.resolve()
|
||||
|
||||
@@ -189,3 +197,112 @@ def test_bash_wrapper_without_agent_target_returns_none(monkeypatch, tmp_path):
|
||||
_isolate_discover_agent_dir(monkeypatch, tmp_path, hermes)
|
||||
|
||||
assert bootstrap.discover_agent_dir() is None
|
||||
|
||||
|
||||
def test_discovers_installed_agent_dir_from_configured_python(monkeypatch, tmp_path):
|
||||
agent_dir = tmp_path / "site-packages"
|
||||
python_exe = str(tmp_path / "venv" / "python")
|
||||
run_agent = agent_dir / "run_agent.py"
|
||||
agent_dir.mkdir()
|
||||
run_agent.write_text("raise AssertionError('must not import run_agent')", encoding="utf-8")
|
||||
calls = []
|
||||
|
||||
def fake_run(argv, **kwargs):
|
||||
calls.append((argv, kwargs))
|
||||
return bootstrap.subprocess.CompletedProcess(argv, 0, stdout=f"{run_agent}\n", stderr="")
|
||||
|
||||
monkeypatch.setattr(bootstrap.subprocess, "run", fake_run)
|
||||
|
||||
assert bootstrap._agent_dir_from_python(python_exe) == agent_dir.resolve()
|
||||
argv, kwargs = calls[0]
|
||||
assert argv[:2] == [python_exe, "-c"]
|
||||
assert 'find_spec("run_agent")' in argv[2]
|
||||
assert "import run_agent" not in argv[2]
|
||||
assert kwargs == {"capture_output": True, "text": True}
|
||||
|
||||
|
||||
def test_python_probe_returns_none_when_run_agent_spec_is_missing(monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(
|
||||
bootstrap.subprocess,
|
||||
"run",
|
||||
lambda argv, **kwargs: bootstrap.subprocess.CompletedProcess(argv, 0, stdout="\n", stderr=""),
|
||||
)
|
||||
|
||||
assert bootstrap._agent_dir_from_python(str(tmp_path / "python")) is None
|
||||
|
||||
|
||||
def test_python_probe_rejects_malformed_relative_origin(monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(
|
||||
bootstrap.subprocess,
|
||||
"run",
|
||||
lambda argv, **kwargs: bootstrap.subprocess.CompletedProcess(argv, 0, stdout="run_agent.py\n", stderr=""),
|
||||
)
|
||||
|
||||
assert bootstrap._agent_dir_from_python(str(tmp_path / "python")) is None
|
||||
|
||||
|
||||
def test_python_probe_rejects_non_file_origin(monkeypatch, tmp_path):
|
||||
missing = tmp_path / "site-packages" / "run_agent.py"
|
||||
monkeypatch.setattr(
|
||||
bootstrap.subprocess,
|
||||
"run",
|
||||
lambda argv, **kwargs: bootstrap.subprocess.CompletedProcess(argv, 0, stdout=f"{missing}\n", stderr=""),
|
||||
)
|
||||
|
||||
assert bootstrap._agent_dir_from_python(str(tmp_path / "python")) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("error", [FileNotFoundError(), PermissionError()])
|
||||
def test_python_probe_returns_none_when_interpreter_cannot_start(monkeypatch, tmp_path, error):
|
||||
monkeypatch.setattr(bootstrap.subprocess, "run", lambda *args, **kwargs: (_ for _ in ()).throw(error))
|
||||
|
||||
assert bootstrap._agent_dir_from_python(str(tmp_path / "python")) is None
|
||||
|
||||
|
||||
def test_python_probe_returns_none_on_nonzero_exit(monkeypatch, tmp_path):
|
||||
monkeypatch.setattr(
|
||||
bootstrap.subprocess,
|
||||
"run",
|
||||
lambda argv, **kwargs: bootstrap.subprocess.CompletedProcess(argv, 1, stdout="", stderr="probe failed"),
|
||||
)
|
||||
|
||||
assert bootstrap._agent_dir_from_python(str(tmp_path / "python")) is None
|
||||
|
||||
|
||||
def test_python_probe_accepts_valid_origin_with_benign_stderr(monkeypatch, tmp_path):
|
||||
run_agent = tmp_path / "site-packages" / "run_agent.py"
|
||||
run_agent.parent.mkdir()
|
||||
run_agent.write_text("", encoding="utf-8")
|
||||
monkeypatch.setattr(
|
||||
bootstrap.subprocess,
|
||||
"run",
|
||||
lambda argv, **kwargs: bootstrap.subprocess.CompletedProcess(argv, 0, stdout=f"{run_agent}\n", stderr="warning\n"),
|
||||
)
|
||||
|
||||
assert bootstrap._agent_dir_from_python(str(tmp_path / "python")) == run_agent.parent.resolve()
|
||||
|
||||
|
||||
def test_python_probe_rejects_existing_file_with_wrong_name(monkeypatch, tmp_path):
|
||||
origin = tmp_path / "site-packages" / "agent_entry.py"
|
||||
origin.parent.mkdir()
|
||||
origin.write_text("", encoding="utf-8")
|
||||
monkeypatch.setattr(
|
||||
bootstrap.subprocess,
|
||||
"run",
|
||||
lambda argv, **kwargs: bootstrap.subprocess.CompletedProcess(argv, 0, stdout=f"{origin}\n", stderr=""),
|
||||
)
|
||||
|
||||
assert bootstrap._agent_dir_from_python(str(tmp_path / "python")) is None
|
||||
|
||||
|
||||
def test_hermes_cli_takes_precedence_over_configured_python(monkeypatch, tmp_path):
|
||||
install, venv_python = _make_agent_install(tmp_path)
|
||||
hermes = _make_hermes_cli(tmp_path, str(venv_python))
|
||||
_isolate_discover_agent_dir(monkeypatch, tmp_path, hermes)
|
||||
monkeypatch.setattr(
|
||||
bootstrap,
|
||||
"_agent_dir_from_python",
|
||||
lambda _python: (_ for _ in ()).throw(AssertionError("Python probe must not run")),
|
||||
)
|
||||
|
||||
assert bootstrap.discover_agent_dir() == install.resolve()
|
||||
@@ -92,6 +92,8 @@ def clean_env(monkeypatch):
|
||||
"HERMES_WEBUI_HOST",
|
||||
"HERMES_WEBUI_PORT",
|
||||
"HERMES_WEBUI_AGENT_DIR",
|
||||
"HERMES_WEBUI_PYTHON",
|
||||
"HERMES_WEBUI_DISABLE_LOCAL_VENV",
|
||||
"HERMES_WEBUI_STATE_DIR",
|
||||
"HERMES_WEBUI_SERVER_CWD",
|
||||
"HERMES_HOME",
|
||||
@@ -531,3 +533,63 @@ class TestForegroundExecutabilityGuard:
|
||||
bs.main()
|
||||
# execv must NOT have been called when the guard fires
|
||||
assert len(execv_calls) == 0
|
||||
|
||||
|
||||
def test_package_python_discovers_agent_before_skip_install_gate(import_bootstrap, clean_env, monkeypatch, tmp_path):
|
||||
bs = import_bootstrap
|
||||
agent_dir = tmp_path / "site-packages"
|
||||
agent_dir.mkdir()
|
||||
(agent_dir / "run_agent.py").write_text("class AIAgent:\n pass\n", encoding="utf-8")
|
||||
state_dir = tmp_path / "state"
|
||||
python_exe = sys.executable
|
||||
execv_calls = []
|
||||
|
||||
monkeypatch.setenv("HERMES_WEBUI_PYTHON", python_exe)
|
||||
monkeypatch.setenv("HERMES_WEBUI_DISABLE_LOCAL_VENV", "1")
|
||||
monkeypatch.setenv("HERMES_WEBUI_STATE_DIR", str(state_dir))
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes-home"))
|
||||
monkeypatch.setenv("PYTHONPATH", str(agent_dir))
|
||||
monkeypatch.setenv("PATH", "")
|
||||
monkeypatch.setattr(bs, "REPO_ROOT", tmp_path / "webui")
|
||||
monkeypatch.setattr(bs.Path, "home", classmethod(lambda cls: tmp_path / "home"))
|
||||
monkeypatch.setattr(bs, "ensure_supported_platform", lambda: None)
|
||||
monkeypatch.setattr(sys, "argv", ["bootstrap.py", "--foreground", "--skip-agent-install"])
|
||||
monkeypatch.setattr(sys, "platform", "linux")
|
||||
monkeypatch.setattr(os, "access", lambda path, mode: True)
|
||||
monkeypatch.setattr(os, "chdir", lambda path: None)
|
||||
|
||||
def fake_execv(path, argv):
|
||||
execv_calls.append((path, argv))
|
||||
raise SystemExit(0)
|
||||
|
||||
monkeypatch.setattr(os, "execv", fake_execv)
|
||||
|
||||
with patch.object(bs, "install_hermes_agent") as mock_install, patch.object(bs.venv, "EnvBuilder") as mock_builder, pytest.raises(SystemExit):
|
||||
bs.main()
|
||||
|
||||
assert execv_calls == [(python_exe, [python_exe, str(bs.REPO_ROOT / "server.py")])]
|
||||
assert os.environ["HERMES_WEBUI_AGENT_DIR"] == str(agent_dir.resolve())
|
||||
mock_install.assert_not_called()
|
||||
mock_builder.assert_not_called()
|
||||
|
||||
|
||||
def test_package_python_without_agent_stays_fail_closed(import_bootstrap, clean_env, monkeypatch, tmp_path):
|
||||
bs = import_bootstrap
|
||||
python_exe = sys.executable
|
||||
|
||||
monkeypatch.setenv("HERMES_WEBUI_PYTHON", python_exe)
|
||||
monkeypatch.setenv("HERMES_WEBUI_DISABLE_LOCAL_VENV", "1")
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes-home"))
|
||||
monkeypatch.setenv("PATH", "")
|
||||
monkeypatch.setattr(bs, "REPO_ROOT", tmp_path / "webui")
|
||||
monkeypatch.setattr(bs.Path, "home", classmethod(lambda cls: tmp_path / "home"))
|
||||
monkeypatch.setattr(bs, "ensure_supported_platform", lambda: None)
|
||||
monkeypatch.setattr(sys, "argv", ["bootstrap.py", "--foreground", "--skip-agent-install"])
|
||||
|
||||
with patch.object(bs, "_agent_dir_from_python", return_value=None) as mock_probe, patch.object(bs, "install_hermes_agent") as mock_install, patch.object(bs.venv, "EnvBuilder") as mock_builder, patch.object(os, "execv") as mock_execv, pytest.raises(RuntimeError, match="Hermes Agent was not found"):
|
||||
bs.main()
|
||||
|
||||
mock_probe.assert_called_once_with(python_exe)
|
||||
mock_install.assert_not_called()
|
||||
mock_builder.assert_not_called()
|
||||
mock_execv.assert_not_called()
|
||||
@@ -52,6 +52,19 @@ def test_nixos_module_uses_explicit_agent_dir_passthru_when_available():
|
||||
assert legacy_agent_share_dir not in MODULE_NIX
|
||||
|
||||
|
||||
def test_flake_checks_package_with_only_hermes_venv_metadata():
|
||||
assert "packageOnlyAgentPackage" in FLAKE_NIX
|
||||
assert "passthru.hermesVenv = packageOnlyAgentVenv;" in FLAKE_NIX
|
||||
assert "HERMES_WEBUI_PYTHON=${packageOnlyAgentVenv}/bin/python3" in FLAKE_NIX
|
||||
assert "! grep -q 'HERMES_WEBUI_AGENT_DIR=' ${packageOnlyEnvProbe}" in FLAKE_NIX
|
||||
|
||||
|
||||
def test_readme_wires_published_agent_flake_package():
|
||||
assert 'hermes-agent.url = "github:NousResearch/hermes-agent";' in README
|
||||
assert "agent.package = hermes-agent.packages.${pkgs.stdenv.hostPlatform.system}.default;" in README
|
||||
assert 'hermesHome = "/var/lib/hermes/.hermes";' in README
|
||||
|
||||
|
||||
def test_nixos_module_does_not_chown_existing_hermes_home():
|
||||
assert "d ${cfg.hermesHome}" not in MODULE_NIX
|
||||
|
||||
|
||||
Reference in new issue
Block a user