mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
feat: wire skills.external_dirs into all remaining discovery paths
The config key skills.external_dirs and core resolution (get_all_skills_dirs,
get_external_skills_dirs in agent/skill_utils.py) already existed but several
code paths still only scanned SKILLS_DIR. Now external dirs are respected
everywhere:
- skills_categories(): scan all dirs for category discovery
- _get_category_from_path(): resolve categories against any skills root
- skill_manager_tool._find_skill(): search all dirs for edit/patch/delete
- credential_files.get_skills_directory_mount(): mount all dirs into
Docker/Singularity containers (external dirs at external_skills/<idx>)
- credential_files.iter_skills_files(): list files from all dirs for
Modal/Daytona upload
- tools/environments/ssh.py: rsync all skill dirs to remote hosts
- gateway _check_unavailable_skill(): check disabled skills across all dirs
Usage in config.yaml:
skills:
external_dirs:
- ~/repos/agent-skills/hermes
- /shared/team-skills
This commit is contained in:
parent
5a98ce5973
commit
ad4feeaf0d
8 changed files with 149 additions and 86 deletions
|
|
@ -427,15 +427,25 @@ def _get_category_from_path(skill_path: Path) -> Optional[str]:
|
|||
Extract category from skill path based on directory structure.
|
||||
|
||||
For paths like: ~/.hermes/skills/mlops/axolotl/SKILL.md -> "mlops"
|
||||
Also works for external skill dirs configured via skills.external_dirs.
|
||||
"""
|
||||
# Try the module-level SKILLS_DIR first (respects monkeypatching in tests),
|
||||
# then fall back to external dirs from config.
|
||||
dirs_to_check = [SKILLS_DIR]
|
||||
try:
|
||||
rel_path = skill_path.relative_to(SKILLS_DIR)
|
||||
parts = rel_path.parts
|
||||
if len(parts) >= 3:
|
||||
return parts[0]
|
||||
return None
|
||||
except ValueError:
|
||||
return None
|
||||
from agent.skill_utils import get_external_skills_dirs
|
||||
dirs_to_check.extend(get_external_skills_dirs())
|
||||
except Exception:
|
||||
pass
|
||||
for skills_dir in dirs_to_check:
|
||||
try:
|
||||
rel_path = skill_path.relative_to(skills_dir)
|
||||
parts = rel_path.parts
|
||||
if len(parts) >= 3:
|
||||
return parts[0]
|
||||
except ValueError:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def _estimate_tokens(content: str) -> int:
|
||||
|
|
@ -645,7 +655,14 @@ def skills_categories(verbose: bool = False, task_id: str = None) -> str:
|
|||
JSON string with list of categories and their descriptions
|
||||
"""
|
||||
try:
|
||||
if not SKILLS_DIR.exists():
|
||||
# Use module-level SKILLS_DIR (respects monkeypatching) + external dirs
|
||||
all_dirs = [SKILLS_DIR] if SKILLS_DIR.exists() else []
|
||||
try:
|
||||
from agent.skill_utils import get_external_skills_dirs
|
||||
all_dirs.extend(d for d in get_external_skills_dirs() if d.exists())
|
||||
except Exception:
|
||||
pass
|
||||
if not all_dirs:
|
||||
return json.dumps(
|
||||
{
|
||||
"success": True,
|
||||
|
|
@ -657,25 +674,26 @@ def skills_categories(verbose: bool = False, task_id: str = None) -> str:
|
|||
|
||||
category_dirs = {}
|
||||
category_counts: Dict[str, int] = {}
|
||||
for skill_md in SKILLS_DIR.rglob("SKILL.md"):
|
||||
if any(part in _EXCLUDED_SKILL_DIRS for part in skill_md.parts):
|
||||
continue
|
||||
for scan_dir in all_dirs:
|
||||
for skill_md in scan_dir.rglob("SKILL.md"):
|
||||
if any(part in _EXCLUDED_SKILL_DIRS for part in skill_md.parts):
|
||||
continue
|
||||
|
||||
try:
|
||||
frontmatter, _ = _parse_frontmatter(
|
||||
skill_md.read_text(encoding="utf-8")[:4000]
|
||||
)
|
||||
except Exception:
|
||||
frontmatter = {}
|
||||
try:
|
||||
frontmatter, _ = _parse_frontmatter(
|
||||
skill_md.read_text(encoding="utf-8")[:4000]
|
||||
)
|
||||
except Exception:
|
||||
frontmatter = {}
|
||||
|
||||
if not skill_matches_platform(frontmatter):
|
||||
continue
|
||||
if not skill_matches_platform(frontmatter):
|
||||
continue
|
||||
|
||||
category = _get_category_from_path(skill_md)
|
||||
if category:
|
||||
category_counts[category] = category_counts.get(category, 0) + 1
|
||||
if category not in category_dirs:
|
||||
category_dirs[category] = SKILLS_DIR / category
|
||||
category = _get_category_from_path(skill_md)
|
||||
if category:
|
||||
category_counts[category] = category_counts.get(category, 0) + 1
|
||||
if category not in category_dirs:
|
||||
category_dirs[category] = skill_md.parent.parent
|
||||
|
||||
categories = []
|
||||
for name in sorted(category_dirs.keys()):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue