Skip to content
Open
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
10 changes: 10 additions & 0 deletions cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2727,6 +2727,16 @@ def __init__(
# but discovery hasn't run yet at this point, so exclude them.
mcp_names = set((CLI_CONFIG.get("mcp_servers") or {}).keys())
invalid = [t for t in toolsets if not validate_toolset(t) and t not in mcp_names]
if invalid:
# Plugins haven't been discovered yet at this point β€” try
# loading them and re-validate before warning. Mirrors the
# logic in hermes_cli/oneshot.py and tui_gateway/server.py.
try:
from hermes_cli.plugins import discover_plugins
discover_plugins()
invalid = [t for t in invalid if not validate_toolset(t)]
except Exception:
pass
if invalid:
self._console_print(f"[bold red]Warning: Unknown toolsets: {', '.join(invalid)}[/]")

Expand Down
1 change: 1 addition & 0 deletions scripts/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@

# Auto-extracted from noreply emails + manual overrides
AUTHOR_MAP = {
"rewalu@gmail.com": "rewasa",
# teknium (multiple emails)
"teknium1@gmail.com": "teknium1",
"30366221+WorldWriter@users.noreply.github.com": "WorldWriter",
Expand Down
35 changes: 33 additions & 2 deletions tests/cli/test_cli_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))


def _make_cli(env_overrides=None, config_overrides=None, **kwargs):
def _make_cli(env_overrides=None, config_overrides=None, module_overrides=None, **kwargs):
"""Create a HermesCLI instance with minimal mocking."""
import importlib

Expand Down Expand Up @@ -50,7 +50,7 @@ def _make_cli(env_overrides=None, config_overrides=None, **kwargs):
import cli as _cli_mod
_cli_mod = importlib.reload(_cli_mod)
with patch.object(_cli_mod, "get_tool_definitions", return_value=[]), \
patch.dict(_cli_mod.__dict__, {"CLI_CONFIG": _clean_config}):
patch.dict(_cli_mod.__dict__, {"CLI_CONFIG": _clean_config, **(module_overrides or {})}):
return _cli_mod.HermesCLI(**kwargs)


Expand Down Expand Up @@ -91,6 +91,37 @@ def test_max_turns_never_none_for_agent(self):
assert isinstance(cli.max_turns, int) and cli.max_turns == 90


class TestToolsetValidation:
def test_plugin_toolsets_are_discovered_before_warning(self):
"""Plugin-contributed toolsets should not emit false unknown-toolset warnings."""
discover_plugins = MagicMock()

def validate_toolset(name):
return discover_plugins.called and name == "plugin_toolset"

with patch("hermes_cli.plugins.discover_plugins", discover_plugins):
cli = _make_cli(
module_overrides={"validate_toolset": validate_toolset},
toolsets=["plugin_toolset"],
)

discover_plugins.assert_called_once_with()
assert cli.enabled_toolsets == ["plugin_toolset"]

def test_genuinely_unknown_toolsets_still_warn_after_discovery(self, capsys):
discover_plugins = MagicMock()
with patch("hermes_cli.plugins.discover_plugins", discover_plugins):
cli = _make_cli(
module_overrides={"validate_toolset": lambda _name: False},
toolsets=["missing_toolset"],
)

discover_plugins.assert_called_once_with()
assert cli.enabled_toolsets == ["missing_toolset"]
output = capsys.readouterr().out
assert "Warning: Unknown toolsets: missing_toolset" in output


class TestVerboseAndToolProgress:
def test_default_verbose_is_bool(self):
cli = _make_cli()
Expand Down
Loading