diff --git a/tools/skill_manager_tool.py b/tools/skill_manager_tool.py index 2d7d9fa9abe..3045474b1c9 100644 --- a/tools/skill_manager_tool.py +++ b/tools/skill_manager_tool.py @@ -150,6 +150,22 @@ import yaml # All skills live in ~/.hermes/skills/ (single source of truth) HERMES_HOME = get_hermes_home() SKILLS_DIR = HERMES_HOME / "skills" +_SKILLS_DIR_AT_IMPORT = SKILLS_DIR + + +def _skills_dir() -> Path: + """Return the active profile's skills directory at call time. + + Long-lived multi-profile runtimes (Dashboard/TUI/Desktop backend, cron, + kanban workers) import this module once under the launch HERMES_HOME and + later bind a different profile per session (#40677). Honor an explicitly + patched module-level ``SKILLS_DIR`` (tests), otherwise resolve from the + live profile-scoped HERMES_HOME on every call. + """ + configured = Path(SKILLS_DIR) + if configured != _SKILLS_DIR_AT_IMPORT: + return configured + return get_hermes_home() / "skills" MAX_NAME_LENGTH = 64 MAX_DESCRIPTION_LENGTH = 1024 @@ -174,7 +190,7 @@ def _containing_skills_root(skill_path: Path) -> Path: return root except (ValueError, OSError): continue - return SKILLS_DIR + return _skills_dir() def _is_path_redirect(path: Path) -> bool: @@ -562,8 +578,8 @@ def _validate_content_size(content: str, label: str = "SKILL.md") -> Optional[st def _resolve_skill_dir(name: str, category: str = None) -> Path: """Build the directory path for a new skill, optionally under a category.""" if category: - return SKILLS_DIR / category / name - return SKILLS_DIR / name + return _skills_dir() / category / name + return _skills_dir() / name def _find_skill(name: str) -> Optional[Dict[str, Any]]: @@ -608,8 +624,9 @@ def _find_skill_in_other_profiles(name: str) -> List[Tuple[str, Path]]: return matches # Collect (profile_name, skills_dir) for every profile EXCEPT the - # one whose SKILLS_DIR we already searched in _find_skill(). - active_dir = SKILLS_DIR.resolve() if SKILLS_DIR.exists() else SKILLS_DIR + # one whose skills dir we already searched in _find_skill(). + _active = _skills_dir() + active_dir = _active.resolve() if _active.exists() else _active candidates: List[Tuple[str, Path]] = [] # Default profile (~/.hermes/skills) — only consider when active is non-default. @@ -828,7 +845,7 @@ def _create_skill(name: str, content: str, category: str = None) -> Dict[str, An result = { "success": True, "message": f"Skill '{name}' created.", - "path": str(skill_dir.relative_to(SKILLS_DIR)), + "path": str(skill_dir.relative_to(_skills_dir())), "skill_md": str(skill_md), "_change": {"description": _desc}, }