fixing skill roots
frdel committed
Feb 5, 2026 at 19:05 UTC
84c06592c76a8a533d586c43446a73eb22f15ae4
3 files changed
+75
-51
python/helpers/projects.py
+59
-41
@@ -1,5 +1,5 @@
1
import os
2
-from typing import Literal, TypedDict, TYPE_CHECKING
2
+from typing import Literal, TypedDict, TYPE_CHECKING, cast
3
4
from python.helpers import files, dirty_json, persist_chat, file_tree
5
from python.helpers.print_style import PrintStyle
@@ -106,7 +106,7 @@ def clone_git_project(name: str, git_url: str, git_token: str, data: BasicProjec
106
meta_path = os.path.join(abs_path, PROJECT_META_DIR, PROJECT_HEADER_FILE)
107
if os.path.exists(meta_path):
108
# Merge: keep cloned content, override only user-specified fields
109
- cloned_header = dirty_json.parse(files.read_file(meta_path))
109
+ cloned_header: BasicProjectData = dirty_json.parse(files.read_file(meta_path)) # type: ignore
110
cloned_header["title"] = data.get("title") or cloned_header.get("title", "")
111
cloned_header["color"] = data.get("color") or cloned_header.get("color", "")
112
cloned_header["git_url"] = clean_url
@@ -151,48 +151,63 @@ def _default_file_structure_settings():
151
)
152
153
154
-def _normalizeBasicData(data: BasicProjectData):
155
- return BasicProjectData(
156
- title=data.get("title", ""),
157
- description=data.get("description", ""),
158
- instructions=data.get("instructions", ""),
159
- color=data.get("color", ""),
160
- git_url=data.get("git_url", ""),
161
- memory=data.get("memory", "own"),
162
- file_structure=data.get(
154
+def _normalizeBasicData(data: BasicProjectData) -> BasicProjectData:
155
+ return {
156
+ "title": data.get("title", ""),
157
+ "description": data.get("description", ""),
158
+ "instructions": data.get("instructions", ""),
159
+ "color": data.get("color", ""),
160
+ "git_url": data.get("git_url", ""),
161
+ "memory": data.get("memory", "own"),
162
+ "file_structure": data.get(
163
"file_structure",
164
_default_file_structure_settings(),
165
),
166
- )
166
+ }
167
168
169
-def _normalizeEditData(data: EditProjectData):
170
- return EditProjectData(
171
- name=data.get("name", ""),
172
- title=data.get("title", ""),
173
- description=data.get("description", ""),
174
- instructions=data.get("instructions", ""),
175
- variables=data.get("variables", ""),
176
- color=data.get("color", ""),
177
- git_status=data.get("git_status", {"is_git_repo": False}),
178
- instruction_files_count=data.get("instruction_files_count", 0),
179
- knowledge_files_count=data.get("knowledge_files_count", 0),
180
- secrets=data.get("secrets", ""),
181
- memory=data.get("memory", "own"),
182
- file_structure=data.get(
169
+def _normalizeEditData(data: EditProjectData) -> EditProjectData:
170
+ normalized: EditProjectData = {
171
+ "name": data.get("name", ""),
172
+ "title": data.get("title", ""),
173
+ "description": data.get("description", ""),
174
+ "instructions": data.get("instructions", ""),
175
+ "variables": data.get("variables", ""),
176
+ "color": data.get("color", ""),
177
+ "git_url": data.get("git_url", ""),
178
+ "git_status": data.get("git_status", {"is_git_repo": False}),
179
+ "instruction_files_count": data.get("instruction_files_count", 0),
180
+ "knowledge_files_count": data.get("knowledge_files_count", 0),
181
+ "secrets": data.get("secrets", ""),
182
+ "memory": data.get("memory", "own"),
183
+ "file_structure": data.get(
184
"file_structure",
185
_default_file_structure_settings(),
186
),
186
- subagents=data.get("subagents", {}),
187
- )
187
+ "subagents": data.get("subagents", {}),
188
+ }
189
+ return normalized
190
191
192
def _edit_data_to_basic_data(data: EditProjectData):
193
return _normalizeBasicData(data)
194
195
194
-def _basic_data_to_edit_data(data: BasicProjectData):
195
- return _normalizeEditData(data) # type: ignore
196
+def _basic_data_to_edit_data(data: BasicProjectData) -> EditProjectData:
197
+ base: EditProjectData = cast(
198
+ EditProjectData,
199
+ {
200
+ **data,
201
+ "name": "",
202
+ "instruction_files_count": 0,
203
+ "knowledge_files_count": 0,
204
+ "variables": "",
205
+ "secrets": "",
206
+ "subagents": {},
207
+ "git_status": {"is_git_repo": False},
208
+ },
209
+ )
210
+ return _normalizeEditData(base)
211
212
213
def update_project(name: str, data: EditProjectData):
@@ -215,7 +230,7 @@ def update_project(name: str, data: EditProjectData):
230
231
232
def load_basic_project_data(name: str) -> BasicProjectData:
218
- data = BasicProjectData(**load_project_header(name))
233
+ data = cast(BasicProjectData, load_project_header(name))
234
normalized = _normalizeBasicData(data)
235
return normalized
236
@@ -229,17 +244,20 @@ def load_edit_project_data(name: str) -> EditProjectData:
244
secrets = load_project_secrets_masked(name)
245
subagents = load_project_subagents(name)
246
knowledge_files_count = get_knowledge_files_count(name)
232
- git_status = git.get_repo_status(get_project_folder(name))
247
+ git_status = cast(GitStatusData, git.get_repo_status(get_project_folder(name)))
248
234
- data = EditProjectData(
235
- **data,
236
- name=name,
237
- instruction_files_count=len(additional_instructions),
238
- knowledge_files_count=knowledge_files_count,
239
- variables=variables,
240
- secrets=secrets,
241
- subagents=subagents,
242
- git_status=git_status,
249
+ data = cast(
250
+ EditProjectData,
251
+ {
252
+ **data,
253
+ "name": name,
254
+ "instruction_files_count": len(additional_instructions),
255
+ "knowledge_files_count": knowledge_files_count,
256
+ "variables": variables,
257
+ "secrets": secrets,
258
+ "subagents": subagents,
259
+ "git_status": git_status,
260
+ },
261
)
262
data = _normalizeEditData(data)
263
return data
python/helpers/skills.py
+13
-7
@@ -50,11 +50,18 @@ def get_skill_roots(
50
paths = subagents.get_paths(agent, "skills")
51
else:
52
# skill roots available globally
53
- pr_ag = files.find_existing_paths_by_pattern("projects/*/.a0proj/agents/*/skills") # agents in projects
54
- projects = files.find_existing_paths_by_pattern("projects/*/.a0proj/skills") # projects
53
+ project_agents = files.find_existing_paths_by_pattern("usr/projects/*/.a0proj/agents/*/skills") # agents in projects
54
+ projects = files.find_existing_paths_by_pattern("usr/projects/*/.a0proj/skills") # projects
55
+ usr_agents = files.find_existing_paths_by_pattern("usr/agents/*/skills") # agents
56
agents = files.find_existing_paths_by_pattern("agents/*/skills") # agents
56
- paths = [files.get_abs_path("skills"), files.get_abs_path("usr/skills")] + pr_ag + projects + agents # full scope
57
-
57
+ paths = [
58
+ files.get_abs_path("skills"),
59
+ files.get_abs_path("usr/skills"),
60
+ *project_agents,
61
+ *projects,
62
+ *usr_agents,
63
+ *agents
64
+ ]
65
return paths
66
67
@@ -364,10 +371,9 @@ def load_skill_for_agent(
371
return f"Error: skill '{skill_name}' not found"
372
373
# Get runtime path
367
- if agent and agent.config.code_exec_ssh_enabled:
374
+ runtime_path = str(skill.path)
375
+ if runtime.is_development():
376
runtime_path = files.normalize_a0_path(str(skill.path))
369
- else:
370
- runtime_path = str(skill.path)
377
378
lines = [f"Skill: {skill.name}", f"Path: {runtime_path}"]
379
webui/components/settings/skills/list.html
+3
-3
@@ -18,10 +18,10 @@
18
<div class="skills-list-controls">
19
<div class="field">
20
<div class="field-label">
21
- <div class="field-title">Project scope</div>
22
- <div class="field-description">
21
+ <div class="field-title">Filter by project</div>
22
+ <!-- <div class="field-description">
23
Includes global skills plus the selected project.
24
- </div>
24
+ </div> -->
25
</div>
26
<div class="field-control">
27
<select x-model="$store.skillsListStore.projectName"