skills extras cleanup

3clyp50 committed Feb 5, 2026 at 17:17 UTC 7c949f58f0d2be1079d74bc5d13b634356ead61b
3 files changed +79 -167
prompts/agent.system.skill.loaded.md deleted
-4
@@ -1,4 +0,0 @@
1 -# Loaded skill
2 -- Explicitly loaded via skills_tool, persists each turn
3 -
4 -{{skill}}
python/helpers/skills.py
+66 -4
@@ -6,7 +6,7 @@ from dataclasses import dataclass, field
6 from pathlib import Path
7 from typing import Any, Dict, Iterable, List, Literal, Optional, Tuple, TYPE_CHECKING
8
9 -from python.helpers import files, subagents, projects
9 +from python.helpers import files, subagents, projects, file_tree, runtime
10
11 if TYPE_CHECKING:
12 from agent import Agent
@@ -356,12 +356,74 @@ def find_skill(
356
357 def load_skill_for_agent(
358 skill_name: str,
359 - agent:Agent|None=None,
359 + agent: Agent | None = None,
360 ) -> str:
361 + """Load skill and format it as a complete string for agent context."""
362 skill = find_skill(skill_name, agent=agent, include_content=True)
363 if not skill:
363 - return "TODO"
364 - return "TODO"
364 + return f"Error: skill '{skill_name}' not found"
365 +
366 + # Get runtime path
367 + if agent and agent.config.code_exec_ssh_enabled:
368 + runtime_path = files.normalize_a0_path(str(skill.path))
369 + else:
370 + runtime_path = str(skill.path)
371 +
372 + lines = [f"Skill: {skill.name}", f"Path: {runtime_path}"]
373 +
374 + # Metadata
375 + metadata = [
376 + ("Version", skill.version),
377 + ("Author", skill.author),
378 + ("License", skill.license),
379 + ("Compatibility", skill.compatibility),
380 + ("Tags", ", ".join(skill.tags) if skill.tags else None),
381 + ("Allowed tools", ", ".join(skill.allowed_tools) if skill.allowed_tools else None),
382 + ("Triggers", ", ".join(skill.triggers) if skill.triggers else None),
383 + ]
384 + lines.extend(f"{label}: {value}" for label, value in metadata if value)
385 +
386 + # Description and content
387 + if skill.description:
388 + lines.extend(["", "Description:", skill.description.strip()])
389 +
390 + lines.extend(["", "Content (SKILL.md body):", skill.content.strip() or "(empty)"])
391 +
392 + # File tree
393 + files_tree = _get_skill_files(skill.path)
394 + lines.append("")
395 + if files_tree:
396 + lines.append("Files (use skills_tool method=read_file to open):")
397 + lines.append(files_tree)
398 + else:
399 + lines.append("No additional files found.")
400 +
401 + return "\n".join(lines)
402 +
403 +
404 +def _get_skill_files(skill_dir: Path) -> str:
405 + """Get file tree for skill directory."""
406 + if not skill_dir.exists():
407 + return ""
408 +
409 + tree = str(
410 + file_tree.file_tree(
411 + str(skill_dir),
412 + max_depth=10,
413 + folders_first=True,
414 + max_files=100,
415 + max_folders=100,
416 + output_mode="string",
417 + max_lines=300,
418 + ignore=files.read_file("conf/skill.default.gitignore"),
419 + )
420 + )
421 +
422 + if tree and runtime.is_development():
423 + runtime_path = files.normalize_a0_path(str(skill_dir))
424 + tree = tree.replace(str(skill_dir), runtime_path)
425 +
426 + return str(tree)
427
428 def search_skills(
429 query: str,
python/tools/skills_tool.py
+13 -159
@@ -48,10 +48,7 @@ class SkillsTool(Tool):
48 # )
49
50 return Response(
51 - message=(
52 - "Error: missing/invalid 'method'. Supported methods: "
53 - "list, load."
54 - ),
51 + message="Error: missing/invalid 'method'. Supported: list, load.",
52 break_loop=False,
53 )
54 except (
@@ -111,164 +108,21 @@ class SkillsTool(Tool):
108 def _load(self, skill_name: str) -> str:
109 skill_name = skill_name.strip()
110 if skill_name.startswith("**") and skill_name.endswith("**"):
114 - skill_name = skill_name[
115 - 2:-2
116 - ] # remove markdown bold markers if used by agent
111 + skill_name = skill_name[2:-2]
112
113 if not skill_name:
114 return "Error: 'skill_name' is required for method=load."
115
121 - self.agent.data[DATA_NAME_LOADED_SKILLS] = [skill_name]
122 -
123 - # skill = skills_helper.find_skill(
124 - # skill_name,
125 - # include_content=True,
126 - # agent=self.agent,
127 - # )
128 - # if not skill:
129 - # return f"Error: skill not found: {skill_name!r}. Try skills_tool method=list or method=search."
130 -
131 - # # Build skill content block
132 - # files_tree = self._list_skill_files(skill.path, max_files=80)
133 - # if self.agent.config.code_exec_ssh_enabled:
134 - # runtime_path = files.normalize_a0_path(str(skill.path))
135 - # else:
136 - # runtime_path = str(skill.path)
137 -
138 - # content_block = self._build_loaded_skill_block(
139 - # skill=skill,
140 - # runtime_path=runtime_path,
141 - # files_tree=files_tree,
142 - # )
143 -
144 - # # Store single skill in agent.data (replaces previous)
145 - # self.agent.data[DATA_NAME_LOADED_SKILL] = {
146 - # "name": skill.name,
147 - # "content": content_block,
148 - # }
149 -
150 - return f"Loaded skill '{skill_name}' into EXTRAS."
151 -
152 - def _build_loaded_skill_block(
153 - self, *, skill: skills_helper.Skill, runtime_path: str, files_tree: str
154 - ) -> str:
155 - lines: List[str] = []
156 - lines.append(f"Skill: {skill.name}")
157 - lines.append(f"Path: {runtime_path}")
158 - if skill.version:
159 - lines.append(f"Version: {skill.version}")
160 - if skill.author:
161 - lines.append(f"Author: {skill.author}")
162 - if skill.license:
163 - lines.append(f"License: {skill.license}")
164 - if skill.compatibility:
165 - lines.append(f"Compatibility: {skill.compatibility}")
166 - if skill.tags:
167 - lines.append(f"Tags: {', '.join(skill.tags)}")
168 - if skill.allowed_tools:
169 - lines.append(f"Allowed tools: {', '.join(skill.allowed_tools)}")
170 - if skill.triggers:
171 - lines.append(f"Triggers: {', '.join(skill.triggers)}")
172 -
173 - lines.append("")
174 - if skill.description:
175 - lines.append("Description:")
176 - lines.append(skill.description.strip())
177 - lines.append("")
178 -
179 - lines.append("Content (SKILL.md body):")
180 - lines.append(skill.content.strip() or "(empty)")
181 - lines.append("")
182 -
183 - if files_tree:
184 - lines.append(
185 - "Files in skill directory (use skills_tool method=read_file to open):"
186 - )
187 - lines.append(files_tree)
188 - else:
189 - lines.append("No additional files found in skill directory.")
190 -
191 - return "\n".join(lines)
192 -
193 - # def _read_file(self, skill_name: str, file_path: str) -> str:
194 - # if not skill_name:
195 - # return "Error: 'skill_name' is required for method=read_file."
196 - # if not file_path:
197 - # return "Error: 'file_path' is required for method=read_file."
198 -
199 - # skill = skills_helper.find_skill(
200 - # skill_name,
201 - # include_content=False,
202 - # agent=self.agent,
203 - # )
204 - # if not skill:
205 - # return f"Error: skill not found: {skill_name!r}."
206 -
207 - # try:
208 - # target = skills_helper.safe_path_within_dir(skill.path, file_path)
209 - # except Exception as e:
210 - # return f"Error: invalid file_path: {e}"
211 -
212 - # if not target.exists() or not target.is_file():
213 - # return f"Error: file not found: {file_path!r} (within skill {skill.name})"
214 -
215 - # # Basic binary guard: if null byte present, do not dump
216 - # content = target.read_bytes()
217 - # if b"\x00" in content[:4096]:
218 - # return f"Error: file appears to be binary; refusing to print raw bytes ({file_path})."
219 -
220 - # text = content.decode("utf-8", errors="replace")
221 - # return f"File: {file_path}\n\n{text}"
222 -
223 - def _list_skill_files(self, skill_dir: Path, *, max_files: int = 80) -> str:
224 - if not skill_dir.exists():
225 - return ""
226 -
227 - tree = str(file_tree.file_tree(
228 - str(skill_dir),
229 - max_depth=10,
230 - folders_first=True,
231 - max_files=100,
232 - max_folders=100,
233 - output_mode="string",
234 - max_lines=300,
235 - ignore=files.read_file("conf/skill.default.gitignore"),
236 - ))
237 -
238 - # replace absolute path with runtime path (for dev env only)
239 - if tree and runtime.is_development():
240 - runtime_path = files.normalize_a0_path(str(skill_dir))
241 - tree = tree.replace(str(skill_dir), runtime_path)
242 -
243 - return str(tree)
244 -
245 -
246 - # def _read_file(self, skill_name: str, file_path: str) -> str:
247 - # if not skill_name:
248 - # return "Error: 'skill_name' is required for method=read_file."
249 - # if not file_path:
250 - # return "Error: 'file_path' is required for method=read_file."
251 -
252 - # skill = skills_helper.find_skill(
253 - # skill_name,
254 - # include_content=False,
255 - # agent=self.agent,
256 - # )
257 - # if not skill:
258 - # return f"Error: skill not found: {skill_name!r}."
259 -
260 - # try:
261 - # target = skills_helper.safe_path_within_dir(skill.path, file_path)
262 - # except Exception as e:
263 - # return f"Error: invalid file_path: {e}"
264 -
265 - # if not target.exists() or not target.is_file():
266 - # return f"Error: file not found: {file_path!r} (within skill {skill.name})"
116 + # Verify skill exists
117 + skill = skills_helper.find_skill(
118 + skill_name,
119 + include_content=False,
120 + agent=self.agent,
121 + )
122 + if not skill:
123 + return f"Error: skill not found: {skill_name!r}. Try skills_tool method=list or method=search."
124
268 - # # Basic binary guard: if null byte present, do not dump
269 - # content = target.read_bytes()
270 - # if b"\x00" in content[:4096]:
271 - # return f"Error: file appears to be binary; refusing to print raw bytes ({file_path})."
125 + # Store skill name for fresh loading each turn
126 + self.agent.data[DATA_NAME_LOADED_SKILLS] = [skill.name]
127
273 - # text = content.decode("utf-8", errors="replace")
274 - # return f"File: {file_path}\n\n{text}"
\ No newline at end of file
128 + return f"Loaded skill '{skill.name}' into persistent extras."
\ No newline at end of file