diff --git a/hermes_cli/plugins.py b/hermes_cli/plugins.py index 3de41edf4629..87e46de82906 100644 --- a/hermes_cli/plugins.py +++ b/hermes_cli/plugins.py @@ -1129,6 +1129,7 @@ class PluginManager: self._hooks.clear() self._middleware.clear() self._plugin_tool_names.clear() + self._plugin_platform_names.clear() self._cli_commands.clear() self._plugin_commands.clear() self._plugin_skills.clear() diff --git a/tests/hermes_cli/test_plugins.py b/tests/hermes_cli/test_plugins.py index 6e424e98450e..cd6922a6d80b 100644 --- a/tests/hermes_cli/test_plugins.py +++ b/tests/hermes_cli/test_plugins.py @@ -439,6 +439,50 @@ class TestPluginDiscovery: assert "ep_plugin" in mgr._plugins + def test_force_rediscover_clears_all_plugin_registries(self, monkeypatch): + """force=True must clear every plugin-populated registry. + + Regression: ``_plugin_platform_names`` was populated by + ``register_platform`` but omitted from the ``discover_and_load(force=True)`` + clear block, so a platform plugin disabled between force-rediscovers + left a stale entry behind forever (the set diverged from the real + platform_registry / _plugins truth). This asserts the clear block + empties the full set of per-plugin registries so no future addition + silently leaks across a force pass either. + """ + mgr = PluginManager() + + # Seed every registry that a plugin's register() can populate, then + # mark discovery done so force=True takes the clear path (we stub the + # inner sweep so the test doesn't depend on any on-disk plugins). + mgr._plugins["p"] = MagicMock() + mgr._hooks["pre_tool_call"] = [lambda **_: None] + mgr._middleware["llm_request"] = [lambda **_: None] + mgr._plugin_tool_names.add("some_tool") + mgr._plugin_platform_names.add("irc") + mgr._cli_commands["c"] = {"plugin": "p"} + mgr._plugin_commands["cmd"] = {"plugin": "p"} + mgr._plugin_skills["p:skill"] = {} + mgr._aux_tasks["task"] = {"plugin": "p"} + mgr._slack_action_handlers.append(("aid", lambda **_: None, "p")) + mgr._discovered = True + + monkeypatch.setattr(PluginManager, "_discover_and_load_inner", lambda self_inner: None) + mgr.discover_and_load(force=True) + + assert mgr._plugins == {} + assert mgr._hooks == {} + assert mgr._middleware == {} + assert mgr._plugin_tool_names == set() + assert mgr._plugin_platform_names == set(), ( + "_plugin_platform_names was not cleared on force-rediscover" + ) + assert mgr._cli_commands == {} + assert mgr._plugin_commands == {} + assert mgr._plugin_skills == {} + assert mgr._aux_tasks == {} + assert mgr._slack_action_handlers == [] + # ── TestPluginLoading ──────────────────────────────────────────────────────