mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
fix(skills): load symlinked skill slash commands
This commit is contained in:
parent
abf1af5401
commit
ff078738ea
2 changed files with 50 additions and 2 deletions
|
|
@ -58,13 +58,35 @@ def _load_skill_payload(skill_identifier: str, task_id: str | None = None) -> tu
|
|||
|
||||
try:
|
||||
from tools.skills_tool import SKILLS_DIR, skill_view
|
||||
from agent.skill_utils import get_external_skills_dirs
|
||||
|
||||
identifier_path = Path(raw_identifier).expanduser()
|
||||
if identifier_path.is_absolute():
|
||||
normalized = None
|
||||
trusted_roots = [SKILLS_DIR]
|
||||
try:
|
||||
normalized = str(identifier_path.resolve().relative_to(SKILLS_DIR.resolve()))
|
||||
trusted_roots.extend(get_external_skills_dirs())
|
||||
except Exception:
|
||||
normalized = raw_identifier
|
||||
pass
|
||||
|
||||
# Prefer the lexical path under a trusted skill root before
|
||||
# resolving symlinks. Slash-command discovery can legitimately
|
||||
# find a skill via ~/.hermes/skills/<name> where <name> is a
|
||||
# symlink to a checked-out skill elsewhere. Resolving first turns
|
||||
# that trusted visible path into an arbitrary absolute path that
|
||||
# skill_view() refuses to load.
|
||||
for root in trusted_roots:
|
||||
try:
|
||||
normalized = str(identifier_path.relative_to(root))
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
if normalized is None:
|
||||
try:
|
||||
normalized = str(identifier_path.resolve().relative_to(SKILLS_DIR.resolve()))
|
||||
except Exception:
|
||||
normalized = raw_identifier
|
||||
else:
|
||||
normalized = raw_identifier.lstrip("/")
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import os
|
|||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
import tools.skills_tool as skills_tool_module
|
||||
from agent.skill_commands import (
|
||||
build_preloaded_skills_prompt,
|
||||
|
|
@ -125,6 +127,30 @@ class TestScanSkillCommands:
|
|||
assert "/knowledge-brain" in result
|
||||
assert result["/knowledge-brain"]["name"] == "knowledge-brain"
|
||||
|
||||
def test_loads_skill_invocation_from_symlinked_skill_dir(self, tmp_path):
|
||||
"""Slash commands should load skills symlinked under the local skills dir."""
|
||||
external_root = tmp_path / "external"
|
||||
skills_root = tmp_path / "skills"
|
||||
skills_root.mkdir()
|
||||
real_skill_dir = _make_skill(
|
||||
external_root,
|
||||
"impeccable",
|
||||
body="Apply impeccable design craft.",
|
||||
)
|
||||
symlink_path = skills_root / "impeccable"
|
||||
try:
|
||||
symlink_path.symlink_to(real_skill_dir, target_is_directory=True)
|
||||
except (OSError, NotImplementedError) as exc:
|
||||
pytest.skip(f"symlinks unavailable in test environment: {exc}")
|
||||
|
||||
with patch("tools.skills_tool.SKILLS_DIR", skills_root):
|
||||
result = scan_skill_commands()
|
||||
message = build_skill_invocation_message("/impeccable")
|
||||
|
||||
assert "/impeccable" in result
|
||||
assert message is not None
|
||||
assert "Apply impeccable design craft." in message
|
||||
|
||||
def test_get_skill_commands_rescans_when_platform_scope_changes(self, tmp_path):
|
||||
"""Platform-specific disabled-skill caches must not leak across platforms.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue