"""load_skill 工具: agent 主动加载某个 skill 的完整 SKILL.md。 这是渐进披露的中间层 (Activation): 启动时只看到 name+description, 认为自己要做某类任务时,调 load_skill 拿到完整指引。 """ from __future__ import annotations from pathlib import Path from typing import Optional from core.skills import SkillRegistry from .base import Tool class LoadSkillTool(Tool): name = "load_skill" description = ( "Load full instructions for a skill. Call this when the current task matches " "a skill's domain (e.g. writing PPT, coding, research proposal). " "Returns the SKILL.md body, which may reference further files in skills//references." ) parameters = { "type": "object", "properties": { "name": { "type": "string", "description": "Skill name as listed in the system prompt's discovery block", } }, "required": ["name"], } def __init__( self, registry: SkillRegistry, base_dir: Optional[Path] = None, user_root: Optional[Path] = None, container_skills_dir: Optional[str] = None, ) -> None: super().__init__(base_dir, user_root=user_root) self.registry = registry # docker backend 下,fs / shell / run_python 都在容器里跑,host skills/ bind # mount 到 /sandbox/skills:ro(pool.py)。header 里的 dir 要给容器内可用路径 # —— 否则 LLM 拿 host 绝对路径(`/home/.../skills/`)去 read references # 时容器看不见,抓瞎报 file not found。POSIX 串(容器恒为 Linux),与 host OS 无关。 # None = host backend,保持 skill.skill_dir 原 host 绝对路径。 self.container_skills_dir = container_skills_dir def execute(self, name: str) -> str: skill = self.registry.get(name) if skill is None: available = ", ".join(self.registry.skills.keys()) or "(none)" return f"[Error] skill '{name}' not found. Available: {available}" body = skill.full_content() if self.container_skills_dir is not None: dir_str = f"{self.container_skills_dir.rstrip('/')}/{skill.name}" else: dir_str = str(skill.skill_dir) header = f"[skill={skill.name}, dir={dir_str}]\n" return header + body