mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-02 02:01:47 +00:00
fix(profiles): migrate Honcho host on rename
This commit is contained in:
parent
c69310c625
commit
d8c5573ffe
2 changed files with 121 additions and 2 deletions
|
|
@ -954,6 +954,59 @@ def import_profile(archive_path: str, name: Optional[str] = None) -> Path:
|
|||
# Rename
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _migrate_honcho_profile_host(old_name: str, new_name: str, new_dir: Path) -> None:
|
||||
"""Rename Honcho host blocks for a renamed profile without changing peers."""
|
||||
old_host = f"hermes.{old_name}"
|
||||
new_host = f"hermes.{new_name}"
|
||||
|
||||
candidates = [
|
||||
new_dir / "honcho.json",
|
||||
_get_default_hermes_home() / "honcho.json",
|
||||
Path.home() / ".honcho" / "config.json",
|
||||
]
|
||||
|
||||
seen: set[Path] = set()
|
||||
for path in candidates:
|
||||
try:
|
||||
resolved = path.resolve()
|
||||
except OSError:
|
||||
resolved = path
|
||||
if resolved in seen or not path.is_file():
|
||||
continue
|
||||
seen.add(resolved)
|
||||
|
||||
try:
|
||||
raw = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
continue
|
||||
|
||||
hosts = raw.get("hosts")
|
||||
if not isinstance(hosts, dict) or old_host not in hosts:
|
||||
continue
|
||||
|
||||
if new_host in hosts:
|
||||
print(f"⚠ Honcho host block not migrated: {new_host} already exists in {path}")
|
||||
continue
|
||||
|
||||
block = hosts[old_host]
|
||||
if isinstance(block, dict) and "aiPeer" not in block:
|
||||
bare = old_host.split(".", 1)[1] if "." in old_host else old_host
|
||||
block["aiPeer"] = bare
|
||||
hosts[new_host] = hosts.pop(old_host)
|
||||
tmp = path.with_suffix(path.suffix + ".tmp")
|
||||
try:
|
||||
tmp.write_text(json.dumps(raw, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
||||
tmp.replace(path)
|
||||
except OSError:
|
||||
try:
|
||||
tmp.unlink(missing_ok=True)
|
||||
except OSError:
|
||||
pass
|
||||
continue
|
||||
|
||||
print(f"✓ Honcho host updated: {old_host} → {new_host}")
|
||||
|
||||
|
||||
def rename_profile(old_name: str, new_name: str) -> Path:
|
||||
"""Rename a profile: directory, wrapper script, service, active_profile.
|
||||
|
||||
|
|
@ -984,7 +1037,10 @@ def rename_profile(old_name: str, new_name: str) -> Path:
|
|||
old_dir.rename(new_dir)
|
||||
print(f"✓ Renamed {old_dir.name} → {new_dir.name}")
|
||||
|
||||
# 3. Update wrapper script
|
||||
# 3. Update profile-scoped Honcho host blocks, preserving aiPeer identity
|
||||
_migrate_honcho_profile_host(old_name, new_name, new_dir)
|
||||
|
||||
# 4. Update wrapper script
|
||||
remove_wrapper_script(old_name)
|
||||
collision = check_alias_collision(new_name)
|
||||
if not collision:
|
||||
|
|
@ -993,7 +1049,7 @@ def rename_profile(old_name: str, new_name: str) -> Path:
|
|||
else:
|
||||
print(f"⚠ Cannot create alias '{new_name}' — {collision}")
|
||||
|
||||
# 4. Update active_profile if it pointed to old name
|
||||
# 5. Update active_profile if it pointed to old name
|
||||
try:
|
||||
if get_active_profile() == old_name:
|
||||
set_active_profile(new_name)
|
||||
|
|
|
|||
|
|
@ -384,6 +384,69 @@ class TestRenameProfile:
|
|||
assert new_dir.is_dir()
|
||||
assert new_dir == tmp_path / ".hermes" / "profiles" / "newname"
|
||||
|
||||
def test_renames_root_honcho_host_without_changing_ai_peer(self, profile_env):
|
||||
tmp_path = profile_env
|
||||
create_profile("ssi_health", no_alias=True)
|
||||
honcho_path = tmp_path / ".hermes" / "honcho.json"
|
||||
honcho_path.write_text(json.dumps({
|
||||
"hosts": {
|
||||
"hermes.ssi_health": {
|
||||
"recallMode": "hybrid",
|
||||
"writeFrequency": "async",
|
||||
"sessionStrategy": "per-session",
|
||||
"saveMessages": True,
|
||||
"peerName": "user-peer",
|
||||
"aiPeer": "ssi_health",
|
||||
"workspace": "hermes",
|
||||
"enabled": True,
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
with patch("hermes_cli.profiles.check_alias_collision", return_value="skip"):
|
||||
rename_profile("ssi_health", "heimdall")
|
||||
|
||||
cfg = json.loads(honcho_path.read_text())
|
||||
assert "hermes.ssi_health" not in cfg["hosts"]
|
||||
assert cfg["hosts"]["hermes.heimdall"]["aiPeer"] == "ssi_health"
|
||||
assert cfg["hosts"]["hermes.heimdall"]["peerName"] == "user-peer"
|
||||
|
||||
def test_pins_ai_peer_when_absent_on_honcho_host_rename(self, profile_env):
|
||||
tmp_path = profile_env
|
||||
create_profile("ssi_health", no_alias=True)
|
||||
honcho_path = tmp_path / ".hermes" / "honcho.json"
|
||||
honcho_path.write_text(json.dumps({
|
||||
"hosts": {
|
||||
"hermes.ssi_health": {"workspace": "hermes", "enabled": True}
|
||||
}
|
||||
}))
|
||||
|
||||
with patch("hermes_cli.profiles.check_alias_collision", return_value="skip"):
|
||||
rename_profile("ssi_health", "heimdall")
|
||||
|
||||
cfg = json.loads(honcho_path.read_text())
|
||||
assert "hermes.ssi_health" not in cfg["hosts"]
|
||||
assert cfg["hosts"]["hermes.heimdall"]["aiPeer"] == "ssi_health"
|
||||
assert cfg["hosts"]["hermes.heimdall"]["workspace"] == "hermes"
|
||||
|
||||
def test_does_not_overwrite_existing_honcho_host_on_rename(self, profile_env):
|
||||
tmp_path = profile_env
|
||||
create_profile("ssi_health", no_alias=True)
|
||||
honcho_path = tmp_path / ".hermes" / "honcho.json"
|
||||
honcho_path.write_text(json.dumps({
|
||||
"hosts": {
|
||||
"hermes.ssi_health": {"aiPeer": "ssi_health"},
|
||||
"hermes.heimdall": {"aiPeer": "heimdall"},
|
||||
}
|
||||
}))
|
||||
|
||||
with patch("hermes_cli.profiles.check_alias_collision", return_value="skip"):
|
||||
rename_profile("ssi_health", "heimdall")
|
||||
|
||||
cfg = json.loads(honcho_path.read_text())
|
||||
assert cfg["hosts"]["hermes.ssi_health"]["aiPeer"] == "ssi_health"
|
||||
assert cfg["hosts"]["hermes.heimdall"]["aiPeer"] == "heimdall"
|
||||
|
||||
def test_default_raises_value_error(self, profile_env):
|
||||
with pytest.raises(ValueError, match="default"):
|
||||
rename_profile("default", "newname")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue