Merge branch 'main' into rewbs/tool-use-charge-to-subscription
This commit is contained in:
@@ -134,12 +134,19 @@ class TestDerivedDicts:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestGatewayKnownCommands:
|
||||
def test_excludes_cli_only(self):
|
||||
def test_excludes_cli_only_without_config_gate(self):
|
||||
for cmd in COMMAND_REGISTRY:
|
||||
if cmd.cli_only:
|
||||
if cmd.cli_only and not cmd.gateway_config_gate:
|
||||
assert cmd.name not in GATEWAY_KNOWN_COMMANDS, \
|
||||
f"cli_only command '{cmd.name}' should not be in GATEWAY_KNOWN_COMMANDS"
|
||||
|
||||
def test_includes_config_gated_cli_only(self):
|
||||
"""Commands with gateway_config_gate are always in GATEWAY_KNOWN_COMMANDS."""
|
||||
for cmd in COMMAND_REGISTRY:
|
||||
if cmd.gateway_config_gate:
|
||||
assert cmd.name in GATEWAY_KNOWN_COMMANDS, \
|
||||
f"config-gated command '{cmd.name}' should be in GATEWAY_KNOWN_COMMANDS"
|
||||
|
||||
def test_includes_gateway_commands(self):
|
||||
for cmd in COMMAND_REGISTRY:
|
||||
if not cmd.cli_only:
|
||||
@@ -160,11 +167,11 @@ class TestGatewayHelpLines:
|
||||
lines = gateway_help_lines()
|
||||
assert len(lines) > 10
|
||||
|
||||
def test_excludes_cli_only_commands(self):
|
||||
def test_excludes_cli_only_commands_without_config_gate(self):
|
||||
lines = gateway_help_lines()
|
||||
joined = "\n".join(lines)
|
||||
for cmd in COMMAND_REGISTRY:
|
||||
if cmd.cli_only:
|
||||
if cmd.cli_only and not cmd.gateway_config_gate:
|
||||
assert f"`/{cmd.name}" not in joined, \
|
||||
f"cli_only command /{cmd.name} should not be in gateway help"
|
||||
|
||||
@@ -188,10 +195,10 @@ class TestTelegramBotCommands:
|
||||
for name, _ in telegram_bot_commands():
|
||||
assert "-" not in name, f"Telegram command '{name}' contains a hyphen"
|
||||
|
||||
def test_excludes_cli_only(self):
|
||||
def test_excludes_cli_only_without_config_gate(self):
|
||||
names = {name for name, _ in telegram_bot_commands()}
|
||||
for cmd in COMMAND_REGISTRY:
|
||||
if cmd.cli_only:
|
||||
if cmd.cli_only and not cmd.gateway_config_gate:
|
||||
tg_name = cmd.name.replace("-", "_")
|
||||
assert tg_name not in names
|
||||
|
||||
@@ -211,13 +218,84 @@ class TestSlackSubcommandMap:
|
||||
assert "bg" in mapping
|
||||
assert "reset" in mapping
|
||||
|
||||
def test_excludes_cli_only(self):
|
||||
def test_excludes_cli_only_without_config_gate(self):
|
||||
mapping = slack_subcommand_map()
|
||||
for cmd in COMMAND_REGISTRY:
|
||||
if cmd.cli_only:
|
||||
if cmd.cli_only and not cmd.gateway_config_gate:
|
||||
assert cmd.name not in mapping
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Config-gated gateway commands
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestGatewayConfigGate:
|
||||
"""Tests for the gateway_config_gate mechanism on CommandDef."""
|
||||
|
||||
def test_verbose_has_config_gate(self):
|
||||
cmd = resolve_command("verbose")
|
||||
assert cmd is not None
|
||||
assert cmd.cli_only is True
|
||||
assert cmd.gateway_config_gate == "display.tool_progress_command"
|
||||
|
||||
def test_verbose_in_gateway_known_commands(self):
|
||||
"""Config-gated commands are always recognized by the gateway."""
|
||||
assert "verbose" in GATEWAY_KNOWN_COMMANDS
|
||||
|
||||
def test_config_gate_excluded_from_help_when_off(self, tmp_path, monkeypatch):
|
||||
"""When the config gate is falsy, the command should not appear in help."""
|
||||
# Write a config with the gate off (default)
|
||||
config_file = tmp_path / "config.yaml"
|
||||
config_file.write_text("display:\n tool_progress_command: false\n")
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
||||
|
||||
lines = gateway_help_lines()
|
||||
joined = "\n".join(lines)
|
||||
assert "`/verbose" not in joined
|
||||
|
||||
def test_config_gate_included_in_help_when_on(self, tmp_path, monkeypatch):
|
||||
"""When the config gate is truthy, the command should appear in help."""
|
||||
config_file = tmp_path / "config.yaml"
|
||||
config_file.write_text("display:\n tool_progress_command: true\n")
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
||||
|
||||
lines = gateway_help_lines()
|
||||
joined = "\n".join(lines)
|
||||
assert "`/verbose" in joined
|
||||
|
||||
def test_config_gate_excluded_from_telegram_when_off(self, tmp_path, monkeypatch):
|
||||
config_file = tmp_path / "config.yaml"
|
||||
config_file.write_text("display:\n tool_progress_command: false\n")
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
||||
|
||||
names = {name for name, _ in telegram_bot_commands()}
|
||||
assert "verbose" not in names
|
||||
|
||||
def test_config_gate_included_in_telegram_when_on(self, tmp_path, monkeypatch):
|
||||
config_file = tmp_path / "config.yaml"
|
||||
config_file.write_text("display:\n tool_progress_command: true\n")
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
||||
|
||||
names = {name for name, _ in telegram_bot_commands()}
|
||||
assert "verbose" in names
|
||||
|
||||
def test_config_gate_excluded_from_slack_when_off(self, tmp_path, monkeypatch):
|
||||
config_file = tmp_path / "config.yaml"
|
||||
config_file.write_text("display:\n tool_progress_command: false\n")
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
||||
|
||||
mapping = slack_subcommand_map()
|
||||
assert "verbose" not in mapping
|
||||
|
||||
def test_config_gate_included_in_slack_when_on(self, tmp_path, monkeypatch):
|
||||
config_file = tmp_path / "config.yaml"
|
||||
config_file.write_text("display:\n tool_progress_command: true\n")
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
||||
|
||||
mapping = slack_subcommand_map()
|
||||
assert "verbose" in mapping
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Autocomplete (SlashCommandCompleter)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -3,7 +3,7 @@ from io import StringIO
|
||||
import pytest
|
||||
from rich.console import Console
|
||||
|
||||
from hermes_cli.skills_hub import do_check, do_list, do_update, handle_skills_slash
|
||||
from hermes_cli.skills_hub import do_check, do_install, do_list, do_update, handle_skills_slash
|
||||
|
||||
|
||||
class _DummyLockFile:
|
||||
@@ -177,3 +177,57 @@ def test_do_update_reinstalls_outdated_skills(monkeypatch):
|
||||
|
||||
assert installs == [("skills-sh/example/repo/hub-skill", "category", True)]
|
||||
assert "Updated 1 skill" in output
|
||||
|
||||
|
||||
def test_do_install_scans_with_resolved_identifier(monkeypatch, tmp_path, hub_env):
|
||||
import tools.skills_guard as guard
|
||||
import tools.skills_hub as hub
|
||||
|
||||
canonical_identifier = "skills-sh/anthropics/skills/frontend-design"
|
||||
|
||||
class _ResolvedSource:
|
||||
def inspect(self, identifier):
|
||||
return type("Meta", (), {
|
||||
"extra": {},
|
||||
"identifier": canonical_identifier,
|
||||
})()
|
||||
|
||||
def fetch(self, identifier):
|
||||
return type("Bundle", (), {
|
||||
"name": "frontend-design",
|
||||
"files": {"SKILL.md": "# Frontend Design"},
|
||||
"source": "skills.sh",
|
||||
"identifier": canonical_identifier,
|
||||
"trust_level": "trusted",
|
||||
"metadata": {},
|
||||
})()
|
||||
|
||||
q_path = tmp_path / "skills" / ".hub" / "quarantine" / "frontend-design"
|
||||
q_path.mkdir(parents=True)
|
||||
(q_path / "SKILL.md").write_text("# Frontend Design")
|
||||
|
||||
scanned = {}
|
||||
|
||||
def _scan_skill(skill_path, source="community"):
|
||||
scanned["source"] = source
|
||||
return guard.ScanResult(
|
||||
skill_name="frontend-design",
|
||||
source=source,
|
||||
trust_level="trusted",
|
||||
verdict="safe",
|
||||
)
|
||||
|
||||
monkeypatch.setattr(hub, "ensure_hub_dirs", lambda: None)
|
||||
monkeypatch.setattr(hub, "create_source_router", lambda auth: [_ResolvedSource()])
|
||||
monkeypatch.setattr(hub, "quarantine_bundle", lambda bundle: q_path)
|
||||
monkeypatch.setattr(hub, "HubLockFile", lambda: type("Lock", (), {"get_installed": lambda self, name: None})())
|
||||
monkeypatch.setattr(guard, "scan_skill", _scan_skill)
|
||||
monkeypatch.setattr(guard, "format_scan_report", lambda result: "scan ok")
|
||||
monkeypatch.setattr(guard, "should_allow_install", lambda result, force=False: (False, "stop after scan"))
|
||||
|
||||
sink = StringIO()
|
||||
console = Console(file=sink, force_terminal=False, color_system=None)
|
||||
|
||||
do_install("skils-sh/anthropics/skills/frontend-design", console=console, skip_confirm=True)
|
||||
|
||||
assert scanned["source"] == canonical_identifier
|
||||
|
||||
@@ -39,6 +39,39 @@ def test_platform_toolset_summary_uses_explicit_platform_list():
|
||||
assert summary["cli"] == _get_platform_tools(config, "cli")
|
||||
|
||||
|
||||
def test_get_platform_tools_includes_enabled_mcp_servers_by_default():
|
||||
config = {
|
||||
"mcp_servers": {
|
||||
"exa": {"url": "https://mcp.exa.ai/mcp"},
|
||||
"web-search-prime": {"url": "https://api.z.ai/api/mcp/web_search_prime/mcp"},
|
||||
"disabled-server": {"url": "https://example.com/mcp", "enabled": False},
|
||||
}
|
||||
}
|
||||
|
||||
enabled = _get_platform_tools(config, "cli")
|
||||
|
||||
assert "exa" in enabled
|
||||
assert "web-search-prime" in enabled
|
||||
assert "disabled-server" not in enabled
|
||||
|
||||
|
||||
def test_get_platform_tools_keeps_enabled_mcp_servers_with_explicit_builtin_selection():
|
||||
config = {
|
||||
"platform_toolsets": {"cli": ["web", "memory"]},
|
||||
"mcp_servers": {
|
||||
"exa": {"url": "https://mcp.exa.ai/mcp"},
|
||||
"web-search-prime": {"url": "https://api.z.ai/api/mcp/web_search_prime/mcp"},
|
||||
},
|
||||
}
|
||||
|
||||
enabled = _get_platform_tools(config, "cli")
|
||||
|
||||
assert "web" in enabled
|
||||
assert "memory" in enabled
|
||||
assert "exa" in enabled
|
||||
assert "web-search-prime" in enabled
|
||||
|
||||
|
||||
def test_toolset_has_keys_for_vision_accepts_codex_auth(tmp_path, monkeypatch):
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
||||
(tmp_path / "auth.json").write_text(
|
||||
|
||||
@@ -134,6 +134,23 @@ class TestToolsMixedTargets:
|
||||
assert "web" not in saved["platform_toolsets"]["cli"]
|
||||
assert "create_issue" in saved["mcp_servers"]["github"]["tools"]["exclude"]
|
||||
|
||||
def test_builtin_toggle_does_not_persist_implicit_mcp_defaults(self):
|
||||
config = {
|
||||
"platform_toolsets": {"cli": ["web", "memory"]},
|
||||
"mcp_servers": {"exa": {"url": "https://mcp.exa.ai/mcp"}},
|
||||
}
|
||||
with patch("hermes_cli.tools_config.load_config", return_value=config), \
|
||||
patch("hermes_cli.tools_config.save_config") as mock_save:
|
||||
tools_disable_enable_command(Namespace(
|
||||
tools_action="disable",
|
||||
names=["web"],
|
||||
platform="cli",
|
||||
))
|
||||
saved = mock_save.call_args[0][0]
|
||||
assert "web" not in saved["platform_toolsets"]["cli"]
|
||||
assert "memory" in saved["platform_toolsets"]["cli"]
|
||||
assert "exa" not in saved["platform_toolsets"]["cli"]
|
||||
|
||||
|
||||
# ── List output ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user