feat(memory): improve OpenViking setup UX

This commit is contained in:
Hao Zhe
2026-06-17 01:04:26 +08:00
parent 3c76dac4fd
commit 2c2ca0443b
8 changed files with 1930 additions and 756 deletions
+70
View File
@@ -45,6 +45,22 @@ def test_curses_select_accepts_explicit_cancel_value(monkeypatch):
assert captured["cancel_returns"] == _CANCELLED
def test_curses_select_clears_after_picker_returns(monkeypatch):
events = []
def fake_radiolist(title, items, selected=0, *, cancel_returns=None):
events.append("picker")
return selected
monkeypatch.setattr("hermes_cli.curses_ui.curses_radiolist", fake_radiolist)
monkeypatch.setattr(memory_setup, "_clear_interactive_transition", lambda: events.append("clear"))
result = _curses_select("Pick one", [("first", "")], default=0)
assert result == 0
assert events == ["picker", "clear"]
def test_cmd_setup_top_level_cancel_writes_nothing(monkeypatch):
save_config = MagicMock()
load_config = MagicMock(side_effect=AssertionError("cancel should not load config"))
@@ -95,6 +111,60 @@ def test_cmd_setup_clears_interactive_picker_before_provider_post_setup(monkeypa
assert events == ["select", "clear", "install", "post_setup"]
def test_cmd_setup_provider_clears_before_provider_post_setup(monkeypatch):
events = []
class PostSetupProvider:
def post_setup(self, hermes_home, config):
events.append("post_setup")
monkeypatch.setattr(memory_setup, "_get_available_providers", lambda: [("openviking", "local", PostSetupProvider())])
monkeypatch.setattr(memory_setup, "_clear_interactive_transition", lambda: events.append("clear"), raising=False)
monkeypatch.setattr(memory_setup, "_install_dependencies", lambda name: events.append("install"))
monkeypatch.setattr(memory_setup, "get_hermes_home", lambda: "/tmp/hermes-test")
monkeypatch.setattr("hermes_cli.config.load_config", lambda: {"memory": {}})
memory_setup.cmd_setup_provider("openviking")
assert events == ["clear", "install", "post_setup"]
def test_cmd_status_prefers_provider_status_config(monkeypatch, capsys):
class StatusProvider:
def get_status_config(self, provider_config):
assert provider_config["endpoint"] == "http://stale.local"
return {
"use_ovcli_config": True,
"ovcli_config_path": "/tmp/ovcli.conf.VPS_ROOT",
"endpoint": "https://vps.example",
"account": "acct",
"user": "alice",
"agent": "hermes",
}
def is_available(self):
return True
config = {
"memory": {
"provider": "openviking",
"openviking": {
"use_ovcli_config": True,
"ovcli_config_path": "/tmp/ovcli.conf.VPS_ROOT",
"endpoint": "http://stale.local",
},
}
}
monkeypatch.setattr("hermes_cli.config.load_config", lambda: config)
monkeypatch.setattr(memory_setup, "_get_available_providers", lambda: [("openviking", "API key / local", StatusProvider())])
memory_setup.cmd_status(SimpleNamespace())
output = capsys.readouterr().out
assert "endpoint: https://vps.example" in output
assert "http://stale.local" not in output
def test_cmd_setup_generic_choice_cancel_writes_nothing(tmp_path, monkeypatch):
class ChoiceProvider:
def __init__(self):
+3 -3
View File
@@ -25,7 +25,7 @@ def test_collect_masked_input_shows_feedback_without_echoing_secret():
value, output = _run_collect("secret\n")
assert value == "secret"
assert output == "API key: ******\n"
assert output == "API key: ******\r\n"
assert "secret" not in output
@@ -33,7 +33,7 @@ def test_collect_masked_input_handles_backspace():
value, output = _run_collect("sec\x7fret\r")
assert value == "seret"
assert output == "API key: ***\b \b***\n"
assert output == "API key: ***\b \b***\r\n"
assert "secret" not in output
@@ -47,7 +47,7 @@ def test_collect_masked_input_raises_keyboard_interrupt():
"API key: ",
)
assert "".join(output) == "API key: \n"
assert "".join(output) == "API key: \r\n"
def test_masked_secret_prompt_falls_back_to_getpass_for_non_tty(monkeypatch):
File diff suppressed because it is too large Load Diff
@@ -90,6 +90,8 @@ def test_aiagent_forwards_user_id_alt_to_memory_provider():
assert provider.init_kwargs["user_id"] == "open-id"
assert provider.init_kwargs["user_id_alt"] == "union-id"
assert provider.init_kwargs["platform"] == "feishu"
assert "warning_callback" not in provider.init_kwargs
assert "status_callback" not in provider.init_kwargs
class CoreShadowProvider:
@@ -132,3 +134,34 @@ def test_core_tool_names_rejected_from_memory_routing_table():
assert "clarify" not in schema_names
assert "delegate_task" not in schema_names
assert "honcho_search" in schema_names
def test_aiagent_forwards_warning_callback_to_cli_memory_provider():
provider = RecordingMemoryProvider()
cfg = {"memory": {"provider": "recording"}, "agent": {}}
with (
patch("hermes_cli.config.load_config", return_value=cfg),
patch("plugins.memory.load_memory_provider", return_value=provider),
patch("agent.model_metadata.get_model_context_length", return_value=204_800),
patch("run_agent.get_tool_definitions", return_value=[]),
patch("run_agent.check_toolset_requirements", return_value={}),
patch("run_agent.OpenAI"),
):
from run_agent import AIAgent
agent = AIAgent(
api_key="test-key-1234567890",
base_url="https://openrouter.ai/api/v1",
quiet_mode=True,
skip_context_files=True,
skip_memory=False,
session_id="sess-cli",
platform="cli",
)
assert agent._memory_manager is not None
assert provider.init_session_id == "sess-cli"
assert provider.init_kwargs["platform"] == "cli"
assert provider.init_kwargs["warning_callback"] == agent._emit_warning
assert provider.init_kwargs["status_callback"] == agent._emit_status