skills + ui fixes
frdel committed
Feb 2, 2026 at 19:45 UTC
5bb41360c255a82a0a7026f700462d2e0cdf5c1d
5 files changed
+37
-128
conf/skill.default.gitignore
new
+10
@@ -0,0 +1,10 @@
1
+# Python environments & cache
2
+venv/
3
+**/__pycache__/
4
+
5
+# Node.js dependencies
6
+**/node_modules/
7
+**/.npm/
8
+
9
+# Version control metadata
10
+**/.git/
prompts/agent.system.tool.skills.md
+3
-48
@@ -116,40 +116,10 @@ Security:
116
- Only files within skill directory accessible
117
- Supports markdown, text, code files
118
119
-### 4. search skills by query
120
-
121
-Searches skills by text matching in name, description, and tags
122
-Returns ranked results by relevance score
123
-Use when: looking for skills without knowing exact name
124
-
125
-~~~json
126
-{
127
- "thoughts": [
128
- "User needs web scraping capability",
129
- "Not sure of exact skill name",
130
- "Searching for web-related skills"
131
- ],
132
- "headline": "Searching for web scraping skills",
133
- "tool_name": "skills_tool",
134
- "tool_args": {
135
- "method": "search",
136
- "query": "web scraping html parsing"
137
- }
138
-}
139
-~~~
140
-
141
-Required args:
142
-- query: search text (searches name, description, tags)
143
-
144
-Scoring:
145
-- Name match: +3 points
146
-- Description match: +2 points
147
-- Tag match: +1 point per tag
148
-- Results sorted by descending score
119
120
## Running skill scripts
121
152
-When a skill includes scripts (listed under its files), use code_execution_tool directly to run them.
122
+When a skill includes scripts (listed under its files), use code_execution_tool with runtime terminal directly to run them.
123
The skill's "load" output shows the skill directory path and lists available scripts.
124
Use read_file to inspect a script before running it if needed.
125
@@ -166,28 +136,13 @@ Example: running a Python script from a skill
136
],
137
"headline": "Converting PDF to images",
138
"tool_name": "code_execution_tool",
169
- "tool_args": {
170
- "runtime": "python",
171
- "code": "import subprocess\nsubprocess.run(['python', '/path/to/skill/scripts/convert_pdf_to_images.py', '/path/to/document.pdf', '/tmp/images'], check=True)"
172
- }
173
-}
174
-~~~
175
-
176
-Example: running a shell script from a skill
177
-~~~json
178
-{
179
- "thoughts": [
180
- "Skill provides a shell script for data processing",
181
- "Running it via terminal runtime"
182
- ],
183
- "headline": "Running data processing script",
184
- "tool_name": "code_execution_tool",
139
"tool_args": {
140
"runtime": "terminal",
187
- "code": "cd /path/to/skill && bash scripts/process.sh /data/input.csv /tmp/output"
141
+ "code": "python /path/to/skill/scripts/convert_pdf_to_images.py /path/to/document.pdf /tmp/images"
142
}
143
}
144
~~~
145
+
146
## Best Practices
147
148
### When to use skills vs other tools
python/helpers/files.py
+1
-1
@@ -352,7 +352,7 @@ def find_file_in_dirs(_filename: str, _directories: list[str]):
352
)
353
354
355
-def get_unique_filenames_in_dirs(dir_paths: list[str], type: Literal["file", "dir", "any"] = "file", pattern: str = "*"):
355
+def get_unique_filenames_in_dirs(dir_paths: list[str], pattern: str = "*", type: Literal["file", "dir", "any"] = "file"):
356
# returns absolute paths for unique filenames, priority by order in dir_paths
357
seen = set()
358
result = []
python/tools/skills_tool.py
+18
-76
@@ -4,8 +4,7 @@ from pathlib import Path
4
from typing import List
5
6
from python.helpers.tool import Tool, Response
7
-from python.helpers import files
8
-from python.helpers import projects
7
+from python.helpers import projects, files, file_tree
8
from python.helpers import skills as skills_helper
9
10
@@ -32,9 +31,9 @@ class SkillsTool(Tool):
31
try:
32
if method == "list":
33
return Response(message=self._list(), break_loop=False)
35
- if method == "search":
36
- query = str(kwargs.get("query") or "").strip()
37
- return Response(message=self._search(query), break_loop=False)
34
+ # if method == "search":
35
+ # query = str(kwargs.get("query") or "").strip()
36
+ # return Response(message=self._search(query), break_loop=False)
37
if method == "load":
38
skill_name = str(kwargs.get("skill_name") or "").strip()
39
return Response(message=self._load(skill_name), break_loop=False)
@@ -159,8 +158,7 @@ class SkillsTool(Tool):
158
159
if referenced_files:
160
lines.append("Files in skill directory (use skills_tool method=read_file to open):")
162
- for p in referenced_files:
163
- lines.append(f"- {p}")
161
+ lines.append(referenced_files)
162
else:
163
lines.append("No additional files found in skill directory.")
164
@@ -196,77 +194,21 @@ class SkillsTool(Tool):
194
text = content.decode("utf-8", errors="replace")
195
return f"File: {file_path}\n\n{text}"
196
199
- def _list_skill_files(self, skill_dir: Path, *, max_files: int = 80) -> List[str]:
197
+ def _list_skill_files(self, skill_dir: Path, *, max_files: int = 80) -> str:
198
if not skill_dir.exists():
199
return []
200
203
- results: List[str] = []
204
-
205
- preferred_dirs = ["scripts", "references", "assets", "templates", "docs"]
206
-
207
- # 1) Root-level files (excluding SKILL.md)
208
- try:
209
- for p in sorted(skill_dir.iterdir(), key=lambda x: x.name):
210
- if len(results) >= max_files:
211
- return results
212
- if p.name.startswith("."):
213
- continue
214
- if p.is_file():
215
- if p.name == "SKILL.md":
216
- continue
217
- results.append(p.name)
218
- except Exception:
219
- pass
220
-
221
- # 2) Preferred optional directories (one level deep)
222
- for dname in preferred_dirs:
223
- dpath = skill_dir / dname
224
- if not dpath.exists() or not dpath.is_dir():
225
- continue
226
- try:
227
- for p in sorted(dpath.iterdir(), key=lambda x: x.name):
228
- if len(results) >= max_files:
229
- return results
230
- if p.name.startswith("."):
231
- continue
232
- if p.is_file():
233
- results.append(f"{dname}/{p.name}")
234
- elif p.is_dir():
235
- # Show one nested level (common in assets/templates/*)
236
- nested_added = False
237
- try:
238
- for sub in sorted(p.iterdir(), key=lambda x: x.name):
239
- if sub.name.startswith("."):
240
- continue
241
- if sub.is_file():
242
- results.append(f"{dname}/{p.name}/{sub.name}")
243
- nested_added = True
244
- break
245
- except Exception:
246
- pass
247
- if not nested_added:
248
- results.append(f"{dname}/{p.name}/")
249
- except Exception:
250
- continue
251
-
252
- # 3) Other directories (one level deep)
253
- try:
254
- for p in sorted(skill_dir.iterdir(), key=lambda x: x.name):
255
- if len(results) >= max_files:
256
- return results
257
- if p.name.startswith(".") or p.name in preferred_dirs:
258
- continue
259
- if p.is_dir():
260
- for sub in sorted(p.iterdir(), key=lambda x: x.name):
261
- if len(results) >= max_files:
262
- return results
263
- if sub.name.startswith("."):
264
- continue
265
- if sub.is_file():
266
- results.append(f"{p.name}/{sub.name}")
267
- except Exception:
268
- pass
269
-
270
- return results
201
+ tree = file_tree.file_tree(
202
+ str(skill_dir),
203
+ max_depth=10,
204
+ folders_first=True,
205
+ max_files=100,
206
+ max_folders=100,
207
+ output_mode="string",
208
+ max_lines=300,
209
+ ignore=files.read_file("conf/skill.default.gitignore")
210
+ )
211
+ return tree
212
+
213
214
webui/components/messages/process-group/process-group.css
+5
-3
@@ -463,11 +463,13 @@
463
margin-top: 0;
464
transition: grid-template-rows 0.2s ease-out, opacity 0.15s ease-out, margin-top 0.2s ease-out;
465
overflow: hidden;
466
- -webkit-overflow-scrolling: touch; /* smooth scrolling on iOS */
467
- overscroll-behavior: contain; /* avoid scroll chaining */
466
max-width: 100%;
467
}
468
469
+.process-step-detail-content{
470
+ white-space: pre-wrap;
471
+}
472
+
473
.process-step-detail,
474
.process-step-detail-scroll {
475
scrollbar-width: thin; /* Firefox */
@@ -534,7 +536,7 @@
536
font-size: var(--font-size-xs);
537
line-height: 1.5;
538
-webkit-overflow-scrolling: touch; /* smooth scrolling on iOS */
537
- overscroll-behavior-x: contain; /* avoid scroll chaining */
539
+ overscroll-behavior-x: contain;
540
}
541
542
/* .process-step-detail-scroll pre {