Port from PrimeIntellect-ai/prime-agent#630: report version transition after hermes update
'✓ Update complete!' now shows what the update actually delivered: '✓ Update complete! (v0.19.4 → v0.20.0)' when the pyproject version changed, '(v0.20.0)' when commits landed within one release, and the plain message when the version cannot be read. Reads the on-disk pyproject.toml (not importlib.metadata, which still describes the old install after a pull). Applied to both the git and Windows-ZIP paths.
This commit is contained in:
@@ -838,6 +838,42 @@ def _print_update_completion(message: str) -> None:
|
||||
print(f"=== hermes-update completed {action_id} ===")
|
||||
|
||||
|
||||
def _read_project_version() -> str | None:
|
||||
"""Read the ``version`` field from the checkout's pyproject.toml.
|
||||
|
||||
Reads the on-disk file (not importlib.metadata) because after a git
|
||||
pull the installed distribution metadata still describes the OLD
|
||||
version; the file is the only source that reflects what was just
|
||||
pulled. Returns None on any failure — version reporting is cosmetic
|
||||
and must never break an update.
|
||||
"""
|
||||
try:
|
||||
import tomllib
|
||||
|
||||
with open(_m().PROJECT_ROOT / "pyproject.toml", "rb") as fh:
|
||||
version = tomllib.load(fh).get("project", {}).get("version")
|
||||
return str(version) if version else None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _update_complete_message(pre_version: str | None) -> str:
|
||||
"""Completion line with the version transition when it is known.
|
||||
|
||||
Ported from PrimeIntellect-ai/prime-agent#630: after a successful
|
||||
self-update, show both versions (``v0.19.4 → v0.20.0``) so the user
|
||||
can see what they actually got. Falls back to the plain message when
|
||||
either side is unknown or the version did not change (e.g. several
|
||||
commits landed within one release).
|
||||
"""
|
||||
post_version = _read_project_version()
|
||||
if pre_version and post_version and pre_version != post_version:
|
||||
return f"✓ Update complete! (v{pre_version} → v{post_version})"
|
||||
if post_version:
|
||||
return f"✓ Update complete! (v{post_version})"
|
||||
return "✓ Update complete!"
|
||||
|
||||
|
||||
def _update_via_zip(args, *, had_desktop_app_before_update: bool = False):
|
||||
"""Update Hermes Agent by downloading a ZIP archive.
|
||||
|
||||
@@ -850,6 +886,10 @@ def _update_via_zip(args, *, had_desktop_app_before_update: bool = False):
|
||||
import zipfile
|
||||
from urllib.request import urlretrieve
|
||||
|
||||
# Snapshot the pre-update version before files are replaced so the
|
||||
# completion line can report the transition (prime-agent#630 port).
|
||||
pre_update_version = _read_project_version()
|
||||
|
||||
# The ZIP fallback exists for Windows git-file-I/O breakage. It pulls a
|
||||
# static archive from GitHub, which is fine for the default "main"
|
||||
# channel but would silently ignore --branch and update from main even
|
||||
@@ -1205,7 +1245,7 @@ def _update_via_zip(args, *, had_desktop_app_before_update: bool = False):
|
||||
print(" Code and Python deps are updated, but the dashboard/TUI may")
|
||||
print(" be in a mixed state until the Node deps are rebuilt.")
|
||||
else:
|
||||
_print_update_completion("✓ Update complete!")
|
||||
_print_update_completion(_update_complete_message(pre_update_version))
|
||||
try:
|
||||
_print_curator_first_run_notice()
|
||||
except Exception as e:
|
||||
@@ -4406,6 +4446,9 @@ def _cmd_update_impl(args, gateway_mode: bool):
|
||||
active_lazy_features = _m()._capture_active_lazy_features()
|
||||
active_tool_dependencies = _m()._capture_active_tool_dependencies()
|
||||
|
||||
# Snapshot the pre-update version before any code is pulled so the
|
||||
# completion line can report the transition (prime-agent#630 port).
|
||||
pre_update_version = _read_project_version()
|
||||
# In gateway mode, use file-based IPC for prompts instead of stdin
|
||||
gw_input_fn = (
|
||||
(lambda prompt, default="": _gateway_prompt(prompt, default))
|
||||
@@ -5582,7 +5625,7 @@ def _cmd_update_impl(args, gateway_mode: bool):
|
||||
print(" Code and Python deps are updated, but the dashboard/TUI may")
|
||||
print(" be in a mixed state until the Node deps are rebuilt.")
|
||||
else:
|
||||
_print_update_completion("✓ Update complete!")
|
||||
_print_update_completion(_update_complete_message(pre_update_version))
|
||||
|
||||
# Search-index optimization notice (v23). Existing installs keep their
|
||||
# working search index untouched on update; the compact v23 layout —
|
||||
|
||||
67
tests/hermes_cli/test_update_version_report.py
Normal file
67
tests/hermes_cli/test_update_version_report.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""Version transition reporting after ``hermes update``.
|
||||
|
||||
Ported from PrimeIntellect-ai/prime-agent#630: a successful self-update
|
||||
reports both versions (``v0.19.4 → v0.20.0``) when the pyproject version
|
||||
changed, and degrades gracefully when either side is unknown.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from hermes_cli import update_cmd
|
||||
|
||||
|
||||
def _write_pyproject(root: Path, version: str) -> None:
|
||||
(root / "pyproject.toml").write_text(
|
||||
f'[project]\nname = "hermes-agent"\nversion = "{version}"\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def fake_root(tmp_path, monkeypatch):
|
||||
class _FakeMain:
|
||||
PROJECT_ROOT = tmp_path
|
||||
|
||||
monkeypatch.setattr(update_cmd, "_m", lambda: _FakeMain)
|
||||
return tmp_path
|
||||
|
||||
|
||||
class TestReadProjectVersion:
|
||||
def test_reads_version(self, fake_root):
|
||||
_write_pyproject(fake_root, "0.20.0")
|
||||
assert update_cmd._read_project_version() == "0.20.0"
|
||||
|
||||
def test_missing_file_returns_none(self, fake_root):
|
||||
assert update_cmd._read_project_version() is None
|
||||
|
||||
def test_malformed_toml_returns_none(self, fake_root):
|
||||
(fake_root / "pyproject.toml").write_text("not [ toml", encoding="utf-8")
|
||||
assert update_cmd._read_project_version() is None
|
||||
|
||||
|
||||
class TestUpdateCompleteMessage:
|
||||
def test_reports_transition_when_version_changed(self, fake_root):
|
||||
_write_pyproject(fake_root, "0.20.0")
|
||||
assert (
|
||||
update_cmd._update_complete_message("0.19.4")
|
||||
== "✓ Update complete! (v0.19.4 → v0.20.0)"
|
||||
)
|
||||
|
||||
def test_same_version_reports_single_version(self, fake_root):
|
||||
_write_pyproject(fake_root, "0.20.0")
|
||||
assert (
|
||||
update_cmd._update_complete_message("0.20.0")
|
||||
== "✓ Update complete! (v0.20.0)"
|
||||
)
|
||||
|
||||
def test_unknown_pre_version_still_shows_current(self, fake_root):
|
||||
_write_pyproject(fake_root, "0.20.0")
|
||||
assert (
|
||||
update_cmd._update_complete_message(None)
|
||||
== "✓ Update complete! (v0.20.0)"
|
||||
)
|
||||
|
||||
def test_unknown_post_version_falls_back_to_plain(self, fake_root):
|
||||
assert update_cmd._update_complete_message("0.19.4") == "✓ Update complete!"
|
||||
Reference in New Issue
Block a user