fix(skills): widen call-time skills-dir resolution to skill_manager_tool

Same bug class as skills_tool: module-level SKILLS_DIR pinned at import
under the launch HERMES_HOME makes skill_manage() write/edit against the
wrong profile in long-lived multi-profile runtimes. Apply the same
_skills_dir() call-time resolution (honoring explicit test patches of
SKILLS_DIR) to _containing_skills_root, _resolve_skill_dir,
_find_skill_in_other_profiles, and create-result path reporting.

Refs #40677
This commit is contained in:
teknium1 2026-07-07 04:18:38 -07:00 committed by Teknium
parent 4a99571d54
commit c6a3d412d4

View file

@ -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},
}