Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions tests/tools/test_toolsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,33 @@ def test_mcp_alias_uses_live_registry(self, monkeypatch):
assert validate_toolset("mcp-dynserver") is True
assert "mcp__dynserver__ping" in resolve_toolset("dynserver")

def test_plugin_toolset_valid_before_discovery_finishes(self, monkeypatch):
"""A configured plugin toolset must validate even while discovery is still in flight.

The live registry is empty until plugin discovery completes, so validating against it alone
reported a perfectly valid `-t <plugin>` as unknown on every fast startup and printed
"Warning: Unknown toolsets: <name>".
"""
import hermes_cli.plugins as plugins

monkeypatch.setattr("tools.registry.registry", ToolRegistry())
monkeypatch.setattr(
plugins, "get_plugin_toolset_keys_nowait", lambda: {"dsh", "spotify"}, raising=False)

assert validate_toolset("dsh") is True
assert validate_toolset("spotify") is True

def test_declared_plugin_key_still_resolves_to_no_tools(self, monkeypatch):
"""Name validation must not invent tools: an unloaded plugin toolset resolves empty."""
import hermes_cli.plugins as plugins

monkeypatch.setattr("tools.registry.registry", ToolRegistry())
monkeypatch.setattr(
plugins, "get_plugin_toolset_keys_nowait", lambda: {"dsh"}, raising=False)

assert validate_toolset("dsh") is True
assert resolve_toolset("dsh") == []


class TestGetToolsetInfo:
def test_leaf(self):
Expand Down
20 changes: 19 additions & 1 deletion toolsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,23 @@ def _get_plugin_toolset_names() -> Set[str]:
return {n for n in _registry_call("get_registered_toolset_names", ()) if n not in TOOLSETS}


def _declared_plugin_toolset_names() -> Set[str]:
"""Plugin toolset keys that a CONFIG may legitimately name, without joining discovery.

The live registry is empty until plugin discovery finishes, so validating a configured name
against it alone reports a valid plugin toolset (``-t dsh``) as unknown on every fast startup.
``get_plugin_toolset_keys_nowait`` serves last launch's persisted set while a scan is in flight.
Only name validation uses this: a key with no live tools yet must still resolve to NO tools, so
``get_toolset`` keeps reading the registry alone.
"""
try:
from hermes_cli.plugins import get_plugin_toolset_keys_nowait

return set(get_plugin_toolset_keys_nowait())
except Exception:
return set()


def _get_registry_toolset_aliases() -> Dict[str, str]:
return _registry_call("get_registered_toolset_aliases", {})

Expand Down Expand Up @@ -438,7 +455,8 @@ def get_toolset_names() -> List[str]:

def validate_toolset(name: str) -> bool:
return (name in {"all", "*"} or name in TOOLSETS
or name in _get_plugin_toolset_names() or name in _get_registry_toolset_aliases())
or name in _get_plugin_toolset_names() or name in _get_registry_toolset_aliases()
or name in _declared_plugin_toolset_names())


def create_custom_toolset(name: str, description: str, tools: List[str] = None, includes: List[str] = None) -> None:
Expand Down