* fix(cli): route error messages through ChatConsole inside patch_stdout Cherry-pick of PR #5798 by @icn5381. Replace self.console.print() with ChatConsole().print() for 11 error/status messages reachable during the interactive session. Inside patch_stdout, self.console (plain Rich Console) writes raw ANSI escapes that StdoutProxy mangles into garbled text. ChatConsole uses prompt_toolkit's native print_formatted_text which renders correctly. Same class of bug as #2262 — that fix covered agent output but missed these error paths in _ensure_runtime_credentials, _init_agent, quick commands, skill loading, and plan mode. * fix(model-picker): add scrolling viewport to curses provider menu Cherry-pick of PR #5790 by @Lempkey. Fixes #5755. _curses_prompt_choice rendered items starting unconditionally from index 0 with no scroll offset. The 'More providers' submenu has 13 entries. On terminals shorter than ~16 rows, items past the fold were never drawn. When UP-arrow wrapped cursor from 0 to the last item (Cancel, index 12), the highlight rendered off-screen — appearing as if only Cancel existed. Adds scroll_offset tracking that adjusts each frame to keep the cursor inside the visible window. * feat(cli): skin-aware compact banner + git state in startup banner Combined salvage of PR #5922 by @ASRagab and PR #5877 by @xinbenlv. Compact banner changes (from #5922): - Read active skin colors and branding instead of hardcoding gold/NOUS HERMES - Default skin preserves backward-compatible legacy branding - Non-default skins use their own agent_name and colors Git state in banner (from #5877): - New format_banner_version_label() shows upstream/local git hashes - Full banner title now includes git state (upstream hash, carried commits) - Compact banner line2 shows the version label with git state - Widen compact banner max width from 64 to 88 to fit version info Both the full Rich banner and compact fallback are now skin-aware and show git state.
64 lines
2.0 KiB
Python
64 lines
2.0 KiB
Python
from unittest.mock import MagicMock, patch
|
|
|
|
|
|
def test_format_banner_version_label_without_git_state():
|
|
from hermes_cli import banner
|
|
|
|
with patch.object(banner, "get_git_banner_state", return_value=None):
|
|
value = banner.format_banner_version_label()
|
|
|
|
assert value == f"Hermes Agent v{banner.VERSION} ({banner.RELEASE_DATE})"
|
|
|
|
|
|
def test_format_banner_version_label_on_upstream_main():
|
|
from hermes_cli import banner
|
|
|
|
with patch.object(
|
|
banner,
|
|
"get_git_banner_state",
|
|
return_value={"upstream": "b2f477a3", "local": "b2f477a3", "ahead": 0},
|
|
):
|
|
value = banner.format_banner_version_label()
|
|
|
|
assert value.endswith("· upstream b2f477a3")
|
|
assert "local" not in value
|
|
|
|
|
|
def test_format_banner_version_label_with_carried_commits():
|
|
from hermes_cli import banner
|
|
|
|
with patch.object(
|
|
banner,
|
|
"get_git_banner_state",
|
|
return_value={"upstream": "b2f477a3", "local": "af8aad31", "ahead": 3},
|
|
):
|
|
value = banner.format_banner_version_label()
|
|
|
|
assert "upstream b2f477a3" in value
|
|
assert "local af8aad31" in value
|
|
assert "+3 carried commits" in value
|
|
|
|
|
|
def test_get_git_banner_state_reads_origin_and_head(tmp_path):
|
|
from hermes_cli import banner
|
|
|
|
repo_dir = tmp_path / "repo"
|
|
(repo_dir / ".git").mkdir(parents=True)
|
|
|
|
results = {
|
|
("git", "rev-parse", "--short=8", "origin/main"): MagicMock(returncode=0, stdout="b2f477a3\n"),
|
|
("git", "rev-parse", "--short=8", "HEAD"): MagicMock(returncode=0, stdout="af8aad31\n"),
|
|
("git", "rev-list", "--count", "origin/main..HEAD"): MagicMock(returncode=0, stdout="3\n"),
|
|
}
|
|
|
|
def fake_run(cmd, **kwargs):
|
|
key = tuple(cmd)
|
|
if key not in results:
|
|
raise AssertionError(f"unexpected command: {cmd}")
|
|
return results[key]
|
|
|
|
with patch("hermes_cli.banner.subprocess.run", side_effect=fake_run):
|
|
state = banner.get_git_banner_state(repo_dir)
|
|
|
|
assert state == {"upstream": "b2f477a3", "local": "af8aad31", "ahead": 3}
|