fix(update): refresh active memory provider deps after venv rebuild
Surgical reapply of #53505 by @LeonSGP43 onto current main (stale-base cherry-pick conflicted in main.py): - _refresh_active_memory_provider_dependencies() in hermes_cli/main.py, wired into BOTH the git-pull update path (after lazy refresh) and the ZIP update path — the provider's plugin.yaml bridge packages are not in extras or LAZY_DEPS, so the core reinstall could strip/downgrade them and the update flow never healed them (#53272 mem0ai). - _install_dependencies(force=True) in memory_setup.py: hand every declared spec to the resolver on update so missing AND version-drifted packages are restored (no-op when satisfied). - Skip guards: no provider / builtin store / memory.enabled=false. Widened beyond the original PR for the #70636 half: - _provider_pip_dependencies(): mode-aware expansion — Hindsight in local/local_embedded mode needs hindsight-all (daemon + embedder), not just the declared hindsight-client; setup installs it but plugin.yaml can't express it, so update-time healing previously missed hindsight-embed and the daemon stayed broken. - Spec-aware import probing: version ranges in pip_dependencies (mem0ai>=2.0.10,<3) no longer break the pip-name -> import-name mapping. Fixes #53272. Fixes the hindsight-embed half of #70636.
This commit is contained in:
@@ -480,6 +480,94 @@ def test_cmd_update_succeeds_with_extras(monkeypatch, tmp_path):
|
||||
assert ".[all]" in install_cmds[0]
|
||||
|
||||
|
||||
def test_refresh_active_memory_provider_dependencies_reinstalls_active_provider(monkeypatch):
|
||||
"""#53272/#70636: update must re-run the active provider's dep install."""
|
||||
recorded = []
|
||||
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.config.load_config",
|
||||
lambda: {"memory": {"provider": "mem0"}},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.memory_setup._install_dependencies",
|
||||
lambda provider_name, force=False: recorded.append((provider_name, force)),
|
||||
)
|
||||
|
||||
hermes_main._refresh_active_memory_provider_dependencies()
|
||||
|
||||
assert recorded == [("mem0", True)]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"memory_cfg",
|
||||
[
|
||||
{}, # no provider configured
|
||||
{"provider": ""}, # empty provider
|
||||
{"provider": "default"}, # built-in store
|
||||
{"provider": "mem0", "enabled": False}, # memory disabled
|
||||
],
|
||||
)
|
||||
def test_refresh_active_memory_provider_dependencies_skips_inactive(monkeypatch, memory_cfg):
|
||||
recorded = []
|
||||
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.config.load_config",
|
||||
lambda: {"memory": memory_cfg},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.memory_setup._install_dependencies",
|
||||
lambda provider_name, force=False: recorded.append((provider_name, force)),
|
||||
)
|
||||
|
||||
hermes_main._refresh_active_memory_provider_dependencies()
|
||||
|
||||
assert recorded == []
|
||||
|
||||
|
||||
def test_refresh_active_memory_provider_dependencies_never_raises(monkeypatch):
|
||||
"""A provider install failure must not block the rest of the update."""
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.config.load_config",
|
||||
lambda: {"memory": {"provider": "hindsight"}},
|
||||
)
|
||||
|
||||
def boom(provider_name, force=False):
|
||||
raise RuntimeError("pip exploded")
|
||||
|
||||
monkeypatch.setattr("hermes_cli.memory_setup._install_dependencies", boom)
|
||||
|
||||
hermes_main._refresh_active_memory_provider_dependencies() # must not raise
|
||||
|
||||
|
||||
def test_cmd_update_refreshes_active_memory_provider_dependencies(monkeypatch, tmp_path):
|
||||
"""The git-pull update path must invoke the memory-provider refresh."""
|
||||
_setup_update_mocks(monkeypatch, tmp_path)
|
||||
monkeypatch.setattr("shutil.which", lambda name: "/usr/bin/uv" if name == "uv" else None)
|
||||
monkeypatch.setattr(hermes_main, "_is_termux_env", lambda env=None: False)
|
||||
|
||||
refresh_calls = []
|
||||
monkeypatch.setattr(
|
||||
hermes_main,
|
||||
"_refresh_active_memory_provider_dependencies",
|
||||
lambda: refresh_calls.append(True),
|
||||
)
|
||||
|
||||
def fake_run(cmd, **kwargs):
|
||||
if cmd == ["git", "rev-parse", "--abbrev-ref", "HEAD"]:
|
||||
return SimpleNamespace(stdout="main\n", stderr="", returncode=0)
|
||||
if cmd == ["git", "rev-list", "HEAD..origin/main", "--count"]:
|
||||
return SimpleNamespace(stdout="1\n", stderr="", returncode=0)
|
||||
if cmd == ["git", "pull", "--ff-only", "origin", "main"]:
|
||||
return SimpleNamespace(stdout="Updating\n", stderr="", returncode=0)
|
||||
return SimpleNamespace(returncode=0, stdout="", stderr="")
|
||||
|
||||
monkeypatch.setattr(hermes_main.subprocess, "run", fake_run)
|
||||
|
||||
hermes_main.cmd_update(SimpleNamespace())
|
||||
|
||||
assert refresh_calls == [True]
|
||||
|
||||
|
||||
def test_install_with_optional_fallback_honors_custom_group(monkeypatch):
|
||||
"""Termux update path should target .[termux-all] when requested."""
|
||||
calls = []
|
||||
|
||||
Reference in New Issue
Block a user