91 lines
4.1 KiB
Python
91 lines
4.1 KiB
Python
"""``hermes update`` / ``hermes migrate relay``: legacy ``HERMES_NEMO_RELAY_ATIF_*``/``ATOF_*`` vars
|
|
become a validated ``relay-plugins.toml`` per profile home, selected from ``.env``."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import tomllib
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from hermes_cli.relay_plugin_migrate import (
|
|
RELAY_PLUGINS_TOML_NAME, migrate_all_profile_relay_envs, migrate_profile_relay_env)
|
|
from hermes_cli.relay_plugin_cutover import RELAY_PLUGINS_CONFIG_ENV, configured_legacy_relay_env_vars
|
|
|
|
nemo_relay = pytest.importorskip("nemo_relay")
|
|
|
|
LEGACY_ENV = """OPENAI_API_KEY=sk-test
|
|
HERMES_NEMO_RELAY_ATOF_ENABLED=1
|
|
HERMES_NEMO_RELAY_ATOF_OUTPUT_DIRECTORY={home}/telemetry/atof
|
|
HERMES_NEMO_RELAY_ATOF_FILENAME=hermes-atof.jsonl
|
|
HERMES_NEMO_RELAY_ATOF_MODE=append
|
|
HERMES_NEMO_RELAY_ATIF_ENABLED=1
|
|
HERMES_NEMO_RELAY_ATIF_OUTPUT_DIRECTORY={home}/telemetry/atif
|
|
HERMES_NEMO_RELAY_ATIF_FILENAME_TEMPLATE=trajectory-{{session_id}}.json
|
|
HERMES_NEMO_RELAY_ATIF_SUBAGENT_EXPORT_MODE=all
|
|
"""
|
|
|
|
|
|
def _parse_env(path: Path) -> dict[str, str]:
|
|
out = {}
|
|
for line in path.read_text(encoding="utf-8").splitlines():
|
|
if line.strip() and not line.startswith("#") and "=" in line:
|
|
k, _, v = line.partition("=")
|
|
out[k.strip()] = v.strip().strip('"')
|
|
return out
|
|
|
|
|
|
@pytest.fixture
|
|
def profile_env(tmp_path, monkeypatch):
|
|
home = tmp_path / ".hermes"
|
|
home.mkdir()
|
|
monkeypatch.setattr(Path, "home", lambda: tmp_path)
|
|
monkeypatch.setenv("HERMES_HOME", str(home))
|
|
return home
|
|
|
|
|
|
def test_legacy_env_becomes_validated_toml_selected_from_env(profile_env):
|
|
(profile_env / ".env").write_text(LEGACY_ENV.format(home=profile_env), encoding="utf-8")
|
|
|
|
result = migrate_profile_relay_env(profile_env)
|
|
|
|
assert result.migrated, (result.skipped_reason, result.validation_error)
|
|
assert result.diagnostics == []
|
|
toml_path = profile_env / RELAY_PLUGINS_TOML_NAME
|
|
document = tomllib.loads(toml_path.read_text(encoding="utf-8"))
|
|
sink = document["components"][0]["config"]["atof"]["sinks"][0]
|
|
assert sink["type"] == "file" and sink["filename"] == "hermes-atof.jsonl"
|
|
assert document["components"][0]["config"]["atif"]["filename_template"] == "trajectory-{session_id}.json"
|
|
# Relay itself accepts the file Hermes will load at runtime.
|
|
report = asyncio.run(nemo_relay.plugin.initialize(document))
|
|
asyncio.run(nemo_relay.plugin.clear_async())
|
|
assert report.get("diagnostics") == []
|
|
# .env now selects the file; the legacy lines survive as comments (not deleted), so the
|
|
# runtime warning + doctor finding go quiet and a second run is a no-op.
|
|
env = _parse_env(profile_env / ".env")
|
|
assert env[RELAY_PLUGINS_CONFIG_ENV] == str(toml_path)
|
|
assert env["OPENAI_API_KEY"] == "sk-test"
|
|
assert configured_legacy_relay_env_vars(env) == ()
|
|
assert "# migrated to relay-plugins.toml: HERMES_NEMO_RELAY_ATOF_ENABLED=1" in (profile_env / ".env").read_text(encoding="utf-8")
|
|
assert migrate_profile_relay_env(profile_env).skipped_reason == "no legacy exporter variables"
|
|
|
|
|
|
def test_update_migrates_every_profile_home_separately(profile_env):
|
|
"""Multiplex: each profile keeps its own TOML; a profile without legacy vars is untouched."""
|
|
(profile_env / ".env").write_text("OPENAI_API_KEY=x\n", encoding="utf-8")
|
|
work = profile_env / "profiles" / "work"
|
|
idle = profile_env / "profiles" / "idle"
|
|
for p in (work, idle):
|
|
p.mkdir(parents=True)
|
|
(work / ".env").write_text(LEGACY_ENV.format(home=work), encoding="utf-8")
|
|
(idle / ".env").write_text("SLACK_BOT_TOKEN=y\n", encoding="utf-8")
|
|
|
|
results = {r.home: r for r in migrate_all_profile_relay_envs(validate=False)}
|
|
|
|
assert results[work].migrated and results[work].toml_path == work / RELAY_PLUGINS_TOML_NAME
|
|
assert not results[idle].migrated and not (idle / RELAY_PLUGINS_TOML_NAME).exists()
|
|
assert not results[profile_env].migrated and not (profile_env / RELAY_PLUGINS_TOML_NAME).exists()
|
|
assert _parse_env(work / ".env")[RELAY_PLUGINS_CONFIG_ENV] == str(work / RELAY_PLUGINS_TOML_NAME)
|
|
assert (idle / ".env").read_text(encoding="utf-8") == "SLACK_BOT_TOKEN=y\n"
|