mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
fix(skills): apply disabled-skill gate to CLI/TUI preloaded skills
build_preloaded_skills_prompt() (hermes -s <skill>, and tui_gateway's HERMES_TUI_SKILLS deployment env var) loads skills via _load_skill_payload() with a raw identifier, bypassing get_skill_commands()' scan-time disabled filter entirely. Result: a skill an operator disabled via skills.disabled still gets force-loaded and injected into every session — including every session on a shared tui_gateway deployment where the operator set HERMES_TUI_SKILLS. The bundle-invocation path (#59156) already re-checks get_disabled_skill_names() for exactly this reason; preloaded-skill loading was the other _load_skill_payload call site still missing it. Fix: check each resolved skill's name (and raw identifier) against get_disabled_skill_names() before injecting it. A disabled skill is now reported the same way an unknown one already is (skipped, listed in the returned missing_identifiers) — no return-shape or caller changes needed. No behavior change when no skill is disabled.
This commit is contained in:
parent
1a2885535b
commit
b5158442f0
2 changed files with 55 additions and 1 deletions
|
|
@ -696,14 +696,27 @@ def build_preloaded_skills_prompt(
|
|||
skill_identifiers: list[str],
|
||||
task_id: str | None = None,
|
||||
) -> tuple[str, list[str], list[str]]:
|
||||
"""Load one or more skills for session-wide CLI preloading.
|
||||
"""Load one or more skills for session-wide CLI/TUI preloading.
|
||||
|
||||
Returns (prompt_text, loaded_skill_names, missing_identifiers).
|
||||
|
||||
Disabled skills are treated the same as missing ones: this loads via a
|
||||
raw identifier straight into ``_load_skill_payload``, bypassing
|
||||
``get_skill_commands()``'s scan-time disabled filter — mirrors the
|
||||
bundle-invocation gate (#59156). Without this, ``hermes -s <skill>`` or
|
||||
a deployment's ``HERMES_TUI_SKILLS`` env var could force-load a skill an
|
||||
operator disabled via ``skills.disabled``/``skills.platform_disabled``.
|
||||
"""
|
||||
prompt_parts: list[str] = []
|
||||
loaded_names: list[str] = []
|
||||
missing: list[str] = []
|
||||
|
||||
try:
|
||||
from agent.skill_utils import get_disabled_skill_names
|
||||
disabled_names = get_disabled_skill_names()
|
||||
except Exception:
|
||||
disabled_names = set()
|
||||
|
||||
seen: set[str] = set()
|
||||
for raw_identifier in skill_identifiers:
|
||||
identifier = (raw_identifier or "").strip()
|
||||
|
|
@ -718,6 +731,10 @@ def build_preloaded_skills_prompt(
|
|||
|
||||
loaded_skill, skill_dir, skill_name = loaded
|
||||
|
||||
if skill_name in disabled_names or identifier in disabled_names:
|
||||
missing.append(identifier)
|
||||
continue
|
||||
|
||||
# Track active usage for Curator lifecycle management (#17782)
|
||||
try:
|
||||
from tools.skill_usage import bump_use
|
||||
|
|
|
|||
|
|
@ -451,6 +451,43 @@ class TestBuildPreloadedSkillsPrompt:
|
|||
assert loaded == ["present-skill"]
|
||||
assert missing == ["missing-skill"]
|
||||
|
||||
def test_skips_disabled_skill(self, tmp_path, monkeypatch):
|
||||
"""A globally-disabled skill must not be force-loaded via -s /
|
||||
HERMES_TUI_SKILLS preloading (mirrors the bundle gate, #59156)."""
|
||||
with patch("tools.skills_tool.SKILLS_DIR", tmp_path):
|
||||
_make_skill(tmp_path, "enabled-skill", body="Enabled content.")
|
||||
_make_skill(tmp_path, "disabled-skill", body="SECRET DISABLED CONTENT.")
|
||||
|
||||
import agent.skill_utils as su_module
|
||||
monkeypatch.setattr(
|
||||
su_module, "get_disabled_skill_names", lambda platform=None: {"disabled-skill"}
|
||||
)
|
||||
|
||||
prompt, loaded, missing = build_preloaded_skills_prompt(
|
||||
["enabled-skill", "disabled-skill"]
|
||||
)
|
||||
|
||||
assert loaded == ["enabled-skill"]
|
||||
assert missing == ["disabled-skill"]
|
||||
assert "SECRET DISABLED CONTENT." not in prompt
|
||||
assert "enabled-skill" in prompt
|
||||
|
||||
def test_loads_normally_when_nothing_disabled(self, tmp_path, monkeypatch):
|
||||
"""Positive control: without a disabled-skills config, both load."""
|
||||
with patch("tools.skills_tool.SKILLS_DIR", tmp_path):
|
||||
_make_skill(tmp_path, "first-skill")
|
||||
_make_skill(tmp_path, "second-skill")
|
||||
|
||||
import agent.skill_utils as su_module
|
||||
monkeypatch.setattr(su_module, "get_disabled_skill_names", lambda platform=None: set())
|
||||
|
||||
prompt, loaded, missing = build_preloaded_skills_prompt(
|
||||
["first-skill", "second-skill"]
|
||||
)
|
||||
|
||||
assert missing == []
|
||||
assert loaded == ["first-skill", "second-skill"]
|
||||
|
||||
|
||||
class TestBuildSkillInvocationMessage:
|
||||
def test_loads_skill_by_stored_path_when_frontmatter_name_differs(self, tmp_path):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue