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