Add load_skill tool to bypass workspace restriction for builtin skills
When restrictToWorkspace is enabled, the agent cannot read builtin skill files via read_file since they live outside the workspace. This adds a dedicated load_skill tool that reads skills by name through the SkillsLoader, which accesses files directly via Python without the workspace restriction. - Add LoadSkillTool to filesystem tools - Register it in the agent loop - Update system prompt to instruct agent to use load_skill instead of read_file - Remove raw filesystem paths from skills summary
This commit is contained in:
@@ -46,7 +46,7 @@ class ContextBuilder:
|
|||||||
if skills_summary:
|
if skills_summary:
|
||||||
parts.append(f"""# Skills
|
parts.append(f"""# Skills
|
||||||
|
|
||||||
The following skills extend your capabilities. To use a skill, read its SKILL.md file using the read_file tool.
|
The following skills extend your capabilities. To use a skill, call the load_skill tool with its name.
|
||||||
Skills with available="false" need dependencies installed first - you can try installing them with apt/brew.
|
Skills with available="false" need dependencies installed first - you can try installing them with apt/brew.
|
||||||
|
|
||||||
{skills_summary}""")
|
{skills_summary}""")
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from nanobot.agent.context import ContextBuilder
|
|||||||
from nanobot.agent.memory import MemoryConsolidator
|
from nanobot.agent.memory import MemoryConsolidator
|
||||||
from nanobot.agent.subagent import SubagentManager
|
from nanobot.agent.subagent import SubagentManager
|
||||||
from nanobot.agent.tools.cron import CronTool
|
from nanobot.agent.tools.cron import CronTool
|
||||||
from nanobot.agent.tools.filesystem import EditFileTool, ListDirTool, ReadFileTool, WriteFileTool
|
from nanobot.agent.tools.filesystem import EditFileTool, ListDirTool, LoadSkillTool, ReadFileTool, WriteFileTool
|
||||||
from nanobot.agent.tools.message import MessageTool
|
from nanobot.agent.tools.message import MessageTool
|
||||||
from nanobot.agent.tools.registry import ToolRegistry
|
from nanobot.agent.tools.registry import ToolRegistry
|
||||||
from nanobot.agent.tools.shell import ExecTool
|
from nanobot.agent.tools.shell import ExecTool
|
||||||
@@ -128,6 +128,7 @@ class AgentLoop:
|
|||||||
self.tools.register(SpawnTool(manager=self.subagents))
|
self.tools.register(SpawnTool(manager=self.subagents))
|
||||||
if self.cron_service:
|
if self.cron_service:
|
||||||
self.tools.register(CronTool(self.cron_service))
|
self.tools.register(CronTool(self.cron_service))
|
||||||
|
self.tools.register(LoadSkillTool(skills_loader=self.context.skills))
|
||||||
|
|
||||||
async def _connect_mcp(self) -> None:
|
async def _connect_mcp(self) -> None:
|
||||||
"""Connect to configured MCP servers (one-time, lazy)."""
|
"""Connect to configured MCP servers (one-time, lazy)."""
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ class SkillsLoader:
|
|||||||
lines = ["<skills>"]
|
lines = ["<skills>"]
|
||||||
for s in all_skills:
|
for s in all_skills:
|
||||||
name = escape_xml(s["name"])
|
name = escape_xml(s["name"])
|
||||||
path = s["path"]
|
|
||||||
desc = escape_xml(self._get_skill_description(s["name"]))
|
desc = escape_xml(self._get_skill_description(s["name"]))
|
||||||
skill_meta = self._get_skill_meta(s["name"])
|
skill_meta = self._get_skill_meta(s["name"])
|
||||||
available = self._check_requirements(skill_meta)
|
available = self._check_requirements(skill_meta)
|
||||||
@@ -126,7 +125,6 @@ class SkillsLoader:
|
|||||||
lines.append(f" <skill available=\"{str(available).lower()}\">")
|
lines.append(f" <skill available=\"{str(available).lower()}\">")
|
||||||
lines.append(f" <name>{name}</name>")
|
lines.append(f" <name>{name}</name>")
|
||||||
lines.append(f" <description>{desc}</description>")
|
lines.append(f" <description>{desc}</description>")
|
||||||
lines.append(f" <location>{path}</location>")
|
|
||||||
|
|
||||||
# Show missing requirements for unavailable skills
|
# Show missing requirements for unavailable skills
|
||||||
if not available:
|
if not available:
|
||||||
|
|||||||
@@ -213,7 +213,7 @@ Stay focused on the assigned task. Your final response will be reported back to
|
|||||||
|
|
||||||
skills_summary = SkillsLoader(self.workspace).build_skills_summary()
|
skills_summary = SkillsLoader(self.workspace).build_skills_summary()
|
||||||
if skills_summary:
|
if skills_summary:
|
||||||
parts.append(f"## Skills\n\nRead SKILL.md with read_file to use a skill.\n\n{skills_summary}")
|
parts.append(f"## Skills\n\nUse load_skill tool to load a skill by name.\n\n{skills_summary}")
|
||||||
|
|
||||||
return "\n\n".join(parts)
|
return "\n\n".join(parts)
|
||||||
|
|
||||||
|
|||||||
@@ -363,3 +363,35 @@ class ListDirTool(_FsTool):
|
|||||||
return f"Error: {e}"
|
return f"Error: {e}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error listing directory: {e}"
|
return f"Error listing directory: {e}"
|
||||||
|
|
||||||
|
|
||||||
|
class LoadSkillTool(Tool):
|
||||||
|
"""Tool to load a skill by name, bypassing workspace restriction."""
|
||||||
|
|
||||||
|
def __init__(self, skills_loader):
|
||||||
|
self._skills_loader = skills_loader
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self) -> str:
|
||||||
|
return "load_skill"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return "Load a skill by name. Returns the full SKILL.md content."
|
||||||
|
|
||||||
|
@property
|
||||||
|
def parameters(self) -> dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {"name": {"type": "string", "description": "The skill name to load"}},
|
||||||
|
"required": ["name"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def execute(self, name: str, **kwargs: Any) -> str:
|
||||||
|
try:
|
||||||
|
content = self._skills_loader.load_skill(name)
|
||||||
|
if content is None:
|
||||||
|
return f"Error: Skill not found: {name}"
|
||||||
|
return content
|
||||||
|
except Exception as e:
|
||||||
|
return f"Error loading skill: {str(e)}"
|
||||||
|
|||||||
Reference in New Issue
Block a user