diff --git a/tests/tools/test_lazy_deps.py b/tests/tools/test_lazy_deps.py index 195095543303..0063e7680603 100644 --- a/tests/tools/test_lazy_deps.py +++ b/tests/tools/test_lazy_deps.py @@ -320,7 +320,7 @@ class TestActiveFeatures: monkeypatch.setattr(ld, "_is_present", lambda spec: False) assert ld.active_features() == [] - def test_finds_features_with_at_least_one_package_installed(self, monkeypatch): + def test_finds_features_with_anchor_package_installed(self, monkeypatch): # Pretend only honcho-ai is installed; nothing else. monkeypatch.setattr( ld, "_is_present", @@ -332,16 +332,24 @@ class TestActiveFeatures: assert "memory.hindsight" not in active assert "platform.slack" not in active - def test_multi_package_feature_active_if_any_present(self, monkeypatch): - # platform.slack has 3 packages; only one needs to be present - # for the feature to count as active (user activated it before, - # one transitive may have been uninstalled separately). + def test_multi_package_feature_active_if_anchor_present(self, monkeypatch): + # platform.slack has multiple packages; the first spec is its anchor. monkeypatch.setattr( ld, "_is_present", lambda spec: ld._pkg_name_from_spec(spec) == "slack-bolt", ) assert "platform.slack" in ld.active_features() + def test_shared_dependency_does_not_activate_feature(self, monkeypatch): + # asyncpg is a generic dependency that may be installed for unrelated + # reasons. It must not make hermes update try to refresh Matrix unless + # the Matrix anchor package (mautrix) is present. + monkeypatch.setattr( + ld, "_is_present", + lambda spec: ld._pkg_name_from_spec(spec) == "asyncpg", + ) + assert "platform.matrix" not in ld.active_features() + class TestRefreshActiveFeatures: def test_no_active_features_returns_empty(self, monkeypatch): diff --git a/tools/lazy_deps.py b/tools/lazy_deps.py index 3d59ba84f925..c22d870ec04e 100644 --- a/tools/lazy_deps.py +++ b/tools/lazy_deps.py @@ -877,8 +877,12 @@ def feature_install_command(feature: str) -> Optional[str]: def active_features() -> list[str]: """Return the list of features the user has ever lazy-installed. - A feature counts as "active" if at least one of its declared packages - is currently installed in the venv (presence check, ignoring version). + A feature counts as "active" if its anchor package (the first declared + spec) is currently installed in the venv (presence check, ignoring + version). We intentionally do NOT treat shared helper packages as proof + that a backend was enabled: for example ``platform.matrix`` depends on + generic packages like ``asyncpg``/``aiosqlite`` that can be installed for + unrelated reasons, while the actual Matrix adapter anchor is ``mautrix``. Features the user has never enabled stay quiet. Used by ``hermes update`` to figure out which lazy backends need a @@ -886,7 +890,7 @@ def active_features() -> list[str]: """ active = [] for feature, specs in LAZY_DEPS.items(): - if any(_is_present(s) for s in specs): + if specs and _is_present(specs[0]): active.append(feature) return active