Skip to content
Merged
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
2 changes: 1 addition & 1 deletion agent/prompt_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1164,7 +1164,7 @@ def build_skills_system_prompt(
or get_session_env("HERMES_SESSION_PLATFORM")
or ""
)
disabled = get_disabled_skill_names()
disabled = get_disabled_skill_names(_platform_hint or None)
cache_key = (
str(skills_dir.resolve()),
tuple(str(d) for d in external_dirs),
Expand Down
11 changes: 7 additions & 4 deletions agent/skill_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,10 @@ def get_disabled_skill_names(platform: str | None = None) -> Set[str]:
Args:
platform: Explicit platform name (e.g. ``"telegram"``). When
*None*, resolves from ``HERMES_PLATFORM`` or
``HERMES_SESSION_PLATFORM`` env vars. Falls back to the
global disabled list when no platform is determined.
``HERMES_SESSION_PLATFORM`` env vars. Returns the global
disabled list, unioned with the platform-specific list when a
platform is resolved (a globally-disabled skill stays disabled
on every platform).

Reads the config file directly (no CLI config imports) to stay
lightweight.
Expand All @@ -305,13 +307,14 @@ def get_disabled_skill_names(platform: str | None = None) -> Set[str]:
or os.getenv("HERMES_PLATFORM")
or get_session_env("HERMES_SESSION_PLATFORM")
)
global_disabled = _normalize_string_set(skills_cfg.get("disabled"))
if resolved_platform:
platform_disabled = (skills_cfg.get("platform_disabled") or {}).get(
resolved_platform
)
if platform_disabled is not None:
return _normalize_string_set(platform_disabled)
return _normalize_string_set(skills_cfg.get("disabled"))
return global_disabled | _normalize_string_set(platform_disabled)
return global_disabled


def _normalize_string_set(values) -> Set[str]:
Expand Down
10 changes: 8 additions & 2 deletions hermes_cli/skills_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,21 @@
# ─── Config Helpers ───────────────────────────────────────────────────────────

def get_disabled_skills(config: dict, platform: Optional[str] = None) -> Set[str]:
"""Return disabled skill names. Platform-specific list falls back to global."""
"""Return disabled skill names: the global list unioned with the
platform-specific list when a platform is given.

A globally-disabled skill stays disabled on every platform, so the
platform list adds to the global list rather than replacing it. This
mirrors ``agent.skill_utils.get_disabled_skill_names``.
"""
skills_cfg = config.get("skills", {})
global_disabled = set(skills_cfg.get("disabled", []))
if platform is None:
return global_disabled
platform_disabled = cfg_get(skills_cfg, "platform_disabled", platform)
if platform_disabled is None:
return global_disabled
return set(platform_disabled)
return global_disabled | set(platform_disabled)


def save_disabled_skills(config: dict, disabled: Set[str], platform: Optional[str] = None):
Expand Down
37 changes: 31 additions & 6 deletions tests/hermes_cli/test_skills_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,19 @@ def test_reads_platform_disabled(self):
"disabled": ["skill-a"],
"platform_disabled": {"telegram": ["skill-b"]}
}}
assert get_disabled_skills(config, platform="telegram") == {"skill-b"}
# Union of global + platform: a globally-disabled skill stays disabled
# on every platform, and the platform list adds to it.
assert get_disabled_skills(config, platform="telegram") == {"skill-a", "skill-b"}

def test_platform_list_unions_with_global(self):
from hermes_cli.skills_config import get_disabled_skills
config = {"skills": {
"disabled": ["global-skill"],
"platform_disabled": {"telegram": []}
}}
# An explicit empty platform list does NOT re-enable a globally-disabled
# skill (matches issue #46201 — global disables hold everywhere).
assert get_disabled_skills(config, platform="telegram") == {"global-skill"}

def test_platform_falls_back_to_global(self):
from hermes_cli.skills_config import get_disabled_skills
Expand Down Expand Up @@ -102,14 +114,27 @@ def test_platform_disabled(self, mock_load):
assert _is_skill_disabled("tg-skill", platform="telegram") is True

@patch("hermes_cli.config.load_config")
def test_platform_enabled_overrides_global(self, mock_load):
def test_globally_disabled_stays_disabled_on_platform(self, mock_load):
mock_load.return_value = {"skills": {
"disabled": ["skill-a"],
"platform_disabled": {"telegram": ["tg-skill"]}
}}
from tools.skills_tool import _is_skill_disabled
# Union: a globally-disabled skill stays disabled on a platform that
# has its own platform_disabled list (matches issue #46201).
assert _is_skill_disabled("skill-a", platform="telegram") is True
assert _is_skill_disabled("tg-skill", platform="telegram") is True

@patch("hermes_cli.config.load_config")
def test_empty_platform_list_keeps_global_disabled(self, mock_load):
mock_load.return_value = {"skills": {
"disabled": ["skill-a"],
"platform_disabled": {"telegram": []}
}}
from tools.skills_tool import _is_skill_disabled
# telegram has explicit empty list -> skill-a is NOT disabled for telegram
assert _is_skill_disabled("skill-a", platform="telegram") is False
# An explicit empty platform list does NOT re-enable a globally-disabled
# skill — global disables hold on every platform.
assert _is_skill_disabled("skill-a", platform="telegram") is True

@patch("hermes_cli.config.load_config")
def test_platform_falls_back_to_global(self, mock_load):
Expand Down Expand Up @@ -164,7 +189,7 @@ def test_explicit_platform_param(self, tmp_path, monkeypatch):

from agent.skill_utils import get_disabled_skill_names
result = get_disabled_skill_names(platform="telegram")
assert result == {"tg-only-skill"}
assert result == {"tg-only-skill", "global-skill"}

def test_session_platform_env_var(self, tmp_path, monkeypatch):
"""HERMES_SESSION_PLATFORM should be used when HERMES_PLATFORM is unset."""
Expand All @@ -183,7 +208,7 @@ def test_session_platform_env_var(self, tmp_path, monkeypatch):

from agent.skill_utils import get_disabled_skill_names
result = get_disabled_skill_names()
assert result == {"discord-skill"}
assert result == {"discord-skill", "global-skill"}

def test_hermes_platform_takes_precedence(self, tmp_path, monkeypatch):
"""HERMES_PLATFORM should win over HERMES_SESSION_PLATFORM."""
Expand Down
8 changes: 6 additions & 2 deletions tools/skills_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,11 +583,15 @@ def _is_skill_disabled(name: str, platform: str = None) -> bool:
config = load_config()
skills_cfg = config.get("skills", {})
resolved_platform = platform or os.getenv("HERMES_PLATFORM") or _get_session_platform()
global_disabled = skills_cfg.get("disabled", [])
if resolved_platform:
platform_disabled = cfg_get(skills_cfg, "platform_disabled", resolved_platform)
if platform_disabled is not None:
return name in platform_disabled
return name in skills_cfg.get("disabled", [])
# A globally-disabled skill stays disabled on every platform;
# the platform list adds to it rather than replacing it. Keep
# in sync with agent.skill_utils.get_disabled_skill_names.
return name in platform_disabled or name in global_disabled
return name in global_disabled
except Exception:
return False

Expand Down
Loading