fix(plugins): only prompt for declared capabilities on enable
This commit is contained in:
@@ -1087,8 +1087,9 @@ def _set_plugin_entry_flag(plugin_id: str, key: str, value: bool) -> None:
|
||||
def cmd_enable(name: str, allow_tool_override: Optional[bool] = None) -> None:
|
||||
"""Add a plugin to the enabled allow-list (and remove it from disabled).
|
||||
|
||||
Non-bundled plugins are asked about the privileged ``allow_tool_override`` grant;
|
||||
tri-state: ``True``/``False`` skip the prompt, ``None`` asks. Bundled plugins are trusted.
|
||||
Non-bundled plugins request consent for declared capabilities. The legacy
|
||||
``allow_tool_override`` grant changes only with an explicit True/False flag;
|
||||
None leaves it unchanged. Bundled plugins are trusted.
|
||||
"""
|
||||
from hermes_cli.relay_plugin_cutover import LEGACY_RELAY_PLUGIN_KEYS, RELAY_PLUGINS_CONFIG_ENV
|
||||
console = _console()
|
||||
@@ -1132,10 +1133,10 @@ def cmd_enable(name: str, allow_tool_override: Optional[bool] = None) -> None:
|
||||
declared_caps = _declared_capabilities_for_key(key)
|
||||
if declared_caps:
|
||||
_run_capability_consent(console, key, declared_caps, context="enable")
|
||||
if allow_tool_override is not None:
|
||||
_resolve_tool_override_grant(console, key, allow_tool_override)
|
||||
return
|
||||
_resolve_tool_override_grant(console, key, allow_tool_override)
|
||||
# Enabling a plugin is not a request for undeclared privileges. Keep existing
|
||||
# grants unchanged unless the operator explicitly grants or revokes one.
|
||||
if allow_tool_override is not None:
|
||||
_resolve_tool_override_grant(console, key, allow_tool_override)
|
||||
|
||||
|
||||
# ── Capability consent flow ──────────────────────────────────────────────────
|
||||
|
||||
44
tests/hermes_cli/test_enable_no_capabilities.py
Normal file
44
tests/hermes_cli/test_enable_no_capabilities.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Enable asks for declared privileges, not an unsolicited override grant."""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"caps,flag,existing,answer,expected,prompts",
|
||||
[
|
||||
(None, None, None, "n", None, 0),
|
||||
([], None, None, "n", None, 0),
|
||||
(None, True, None, "n", True, 0),
|
||||
(None, False, True, "n", False, 0),
|
||||
(None, None, True, "n", True, 0),
|
||||
(["tools.override"], None, None, "y", True, 1),
|
||||
],
|
||||
)
|
||||
def test_enable_consent(tmp_path, monkeypatch, caps, flag, existing, answer, expected, prompts):
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
||||
plugin = tmp_path / "plugins" / "hook-only-probe"
|
||||
plugin.mkdir(parents=True)
|
||||
manifest = {"name": "hook-only-probe", "version": "0.1.0", "hooks": ["transform_llm_output"]}
|
||||
if caps is not None:
|
||||
manifest["capabilities"] = caps
|
||||
(plugin / "plugin.yaml").write_text(yaml.safe_dump(manifest), encoding="utf-8")
|
||||
(plugin / "__init__.py").write_text("def register(ctx):\n pass\n", encoding="utf-8")
|
||||
initial = {} if existing is None else {
|
||||
"plugins": {"entries": {"hook-only-probe": {"allow_tool_override": existing}}}
|
||||
}
|
||||
(tmp_path / "config.yaml").write_text(yaml.safe_dump(initial), encoding="utf-8")
|
||||
from hermes_cli import plugins_cmd
|
||||
|
||||
console = MagicMock()
|
||||
console.input.return_value = answer
|
||||
monkeypatch.setattr(plugins_cmd, "_console", lambda: console)
|
||||
monkeypatch.setattr("sys.stdin.isatty", lambda: True)
|
||||
monkeypatch.setattr("sys.stdout.isatty", lambda: True)
|
||||
plugins_cmd.cmd_enable("hook-only-probe", allow_tool_override=flag)
|
||||
assert console.input.call_count == prompts
|
||||
config = yaml.safe_load((tmp_path / "config.yaml").read_text(encoding="utf-8"))
|
||||
assert "hook-only-probe" in config["plugins"]["enabled"]
|
||||
entry = config["plugins"].get("entries", {}).get("hook-only-probe", {})
|
||||
assert entry.get("allow_tool_override") is expected
|
||||
Reference in New Issue
Block a user