remove skills activation state
3clyp50 committed
Feb 4, 2026 at 18:50 UTC
804125a00c3cb485b86749dfaee160426ff01ddd
6 files changed
+4
-204
python/api/skills.py
-27
@@ -2,14 +2,6 @@ from python.helpers.api import ApiHandler, Input, Output, Request, Response
2
from python.helpers import skills
3
4
5
-def _coerce_bool(value) -> bool:
6
- if isinstance(value, bool):
7
- return value
8
- if isinstance(value, str):
9
- return value.strip().lower() in ("1", "true", "yes", "on")
10
- return bool(value)
11
-
12
-
5
class Skills(ApiHandler):
6
async def process(self, input: Input, request: Request) -> Output:
7
action = input.get("action", "")
@@ -17,8 +9,6 @@ class Skills(ApiHandler):
9
try:
10
if action == "list":
11
data = self.list_skills(input)
20
- elif action == "toggle":
21
- data = self.toggle_skill(input)
12
elif action == "delete":
13
data = self.delete_skill(input)
14
else:
@@ -42,23 +32,6 @@ class Skills(ApiHandler):
32
profile_name=profile_name
33
)
34
45
- def toggle_skill(self, input: Input):
46
- skill_id = str(input.get("skill_id") or "").strip()
47
- if not skill_id:
48
- raise Exception("skill_id is required")
49
-
50
- enabled = _coerce_bool(input.get("enabled", True))
51
- project_name = (input.get("project_name") or "").strip() or None
52
- profile_name = (input.get("profile_name") or "").strip() or None
53
-
54
- skills.set_skill_activation(
55
- skill_id,
56
- enabled=enabled,
57
- project_name=project_name,
58
- profile_name=profile_name,
59
- )
60
- return {"enabled": enabled}
61
-
35
def delete_skill(self, input: Input):
36
skill_id = str(input.get("skill_id") or "").strip()
37
if not skill_id:
python/extensions/system_prompt/_10_system_prompt.py
+1
-1
@@ -85,7 +85,7 @@ def get_project_prompt(agent: Agent):
85
return result
86
87
def get_skills_prompt(agent: Agent):
88
- available = skills.list_skills(agent=agent, enabled_only=True)
88
+ available = skills.list_skills(agent=agent)
89
result = []
90
for skill in available:
91
name = skill.name.strip().replace("\n", " ")[:100]
python/helpers/skills.py
+3
-134
@@ -286,9 +286,8 @@ def skill_from_markdown(
286
def list_skills(
287
agent:Agent|None=None,
288
include_content: bool = False,
289
- enabled_only: bool = False,
289
) -> List[Skill]:
291
- """List skills, optionally filtered by agent scope and enabled status."""
290
+ """List skills, optionally filtered by agent scope."""
291
skills: List[Skill] = []
292
293
roots = get_skill_roots(agent)
@@ -301,8 +300,6 @@ def list_skills(
300
301
# no deduplication for global skills
302
if not agent:
304
- if enabled_only:
305
- skills = filter_enabled_skills(skills, agent=None)
303
return skills
304
305
# Dedupe by normalized name, preserving root_order priority (earlier wins)
@@ -312,47 +309,7 @@ def list_skills(
309
if key and key not in by_name:
310
by_name[key] = s
311
315
- result = list(by_name.values())
316
-
317
- if enabled_only:
318
- result = filter_enabled_skills(result, agent=agent)
319
-
320
- return result
321
-
322
-
323
-def _get_activation_file(project_name: str | None, profile_name: str | None) -> str:
324
- """Get the activation file path for the given scope."""
325
- if project_name and profile_name:
326
- return files.deabsolute_path(
327
- projects.get_project_meta_folder(project_name, "agents", profile_name, "skills.json")
328
- )
329
- elif project_name:
330
- return files.deabsolute_path(
331
- projects.get_project_meta_folder(project_name, "skills.json")
332
- )
333
- elif profile_name:
334
- return files.get_abs_path(subagents.USER_AGENTS_DIR, profile_name, "skills.json")
335
- return files.get_abs_path(subagents.USER_DIR, "skills", "skills.json")
336
-
337
-
338
-def _load_activation_map(path: str) -> Dict[str, bool]:
339
- try:
340
- if not files.exists(path):
341
- return {}
342
- parsed = dirty_json.parse(files.read_file(path))
343
- if not isinstance(parsed, dict):
344
- return {}
345
- result: Dict[str, bool] = {}
346
- for key, value in parsed.items():
347
- result[str(key)] = bool(value)
348
- return result
349
- except Exception:
350
- return {}
351
-
352
-
353
-def _write_activation_map(path: str, data: Dict[str, bool]) -> None:
354
- content = dirty_json.stringify(data, indent=2)
355
- files.write_file(path, content)
312
+ return list(by_name.values())
313
314
315
def _get_skill_roots_for_list(
@@ -424,10 +381,8 @@ def get_skills_list(
381
project_name: str | None = None,
382
profile_name: str | None = None,
383
) -> List[Dict[str, Any]]:
427
- """Get list of all skills with activation status."""
384
+ """Get list of all skills."""
385
roots = _get_skill_roots_for_list(project_name, profile_name)
429
- activation_file = _get_activation_file(project_name, profile_name)
430
- activation_map = _load_activation_map(activation_file)
386
387
entries: List[Dict[str, Any]] = []
388
for root in roots:
@@ -441,7 +396,6 @@ def get_skills_list(
396
# generate skill_id
397
rel_path = os.path.relpath(str(skill.path), root).replace("\\", "/")
398
skill_id = f"{root}:{rel_path}"
444
- enabled = activation_map.get(skill_id, True)
399
400
entries.append(
401
{
@@ -453,39 +407,11 @@ def get_skills_list(
407
"scope": scope_info["scope"],
408
"scope_name": scope_info["scope_name"],
409
"origin": scope_info["origin"],
456
- "enabled": bool(enabled),
410
}
411
)
412
return entries
413
414
462
-def set_skill_activation(
463
- skill_id: str,
464
- enabled: bool,
465
- project_name: str | None = None,
466
- profile_name: str | None = None,
467
-) -> None:
468
- """Toggle skill activation."""
469
- if not skill_id or ":" not in skill_id:
470
- raise ValueError("Invalid skill_id")
471
-
472
- root, _ = skill_id.split(":", 1)
473
- allowed_roots = _get_skill_roots_for_list(project_name, profile_name)
474
-
475
- if root not in allowed_roots:
476
- raise ValueError("Skill root not in current scope")
477
-
478
- activation_file = _get_activation_file(project_name, profile_name)
479
- activation_map = _load_activation_map(activation_file)
480
-
481
- if enabled:
482
- activation_map.pop(skill_id, None)
483
- else:
484
- activation_map[skill_id] = False
485
-
486
- _write_activation_map(activation_file, activation_map)
487
-
488
-
415
def delete_skill(
416
skill_id: str,
417
project_name: str | None = None,
@@ -517,63 +443,6 @@ def delete_skill(
443
# delete directory
444
files.delete_dir(skill_path)
445
520
- # clean up activation map
521
- activation_file = _get_activation_file(project_name, profile_name)
522
- activation_map = _load_activation_map(activation_file)
523
- activation_map.pop(skill_id, None)
524
- _write_activation_map(activation_file, activation_map)
525
-
526
-
527
-def filter_enabled_skills(skills: List[Skill], agent: Agent|None=None) -> List[Skill]:
528
- """Filter skills based on activation status."""
529
- if not skills:
530
- return skills
531
-
532
- roots = get_skill_roots(agent)
533
- if not roots:
534
- return skills
535
-
536
- # sort by path length (longest first) for proper matching
537
- roots.sort(key=len, reverse=True)
538
-
539
- # cache activation files to avoid repeated reads
540
- activation_cache: Dict[str, Dict[str, bool]] = {}
541
-
542
- enabled_skills: List[Skill] = []
543
- for skill in skills:
544
- # find matching root
545
- skill_root = None
546
- for root in roots:
547
- try:
548
- Path(str(skill.path)).relative_to(Path(root))
549
- skill_root = root
550
- break
551
- except Exception:
552
- continue
553
-
554
- if not skill_root:
555
- # skill not in any known root, include by default
556
- enabled_skills.append(skill)
557
- continue
558
-
559
- # determine which activation file to use for this root
560
- # note: we use global scope here since agent context doesn't map cleanly to project/profile
561
- activation_file = _get_activation_file(None, None)
562
-
563
- if activation_file not in activation_cache:
564
- activation_cache[activation_file] = _load_activation_map(activation_file)
565
-
566
- activation_map = activation_cache[activation_file]
567
-
568
- # check activation
569
- rel_path = os.path.relpath(str(skill.path), skill_root).replace("\\", "/")
570
- skill_id = f"{skill_root}:{rel_path}"
571
-
572
- if activation_map.get(skill_id, True):
573
- enabled_skills.append(skill)
574
-
575
- return enabled_skills
576
-
446
447
def find_skill(
448
skill_name: str,
python/tools/skills_tool.py
-1
@@ -60,7 +60,6 @@ class SkillsTool(Tool):
60
skills = skills_helper.list_skills(
61
agent=self.agent,
62
include_content=False,
63
- enabled_only=True,
63
)
64
if not skills:
65
return "No skills found."
webui/components/settings/skills/list.html
-12
@@ -61,18 +61,6 @@
61
<div class="skill-header">
62
<div class="skill-title" x-text="skill.name || '(unnamed skill)'"></div>
63
<div class="skill-actions">
64
- <template x-if="skill.enabled">
65
- <button type="button" class="button cancel" title="Disable" style="width:9em"
66
- @click="$store.skillsListStore.toggleSkill(skill, false)">
67
- <span class="icon material-symbols-outlined">close</span> Disable
68
- </button>
69
- </template>
70
- <template x-if="!skill.enabled">
71
- <button type="button" class="button confirm" title="Enable" style="width:9em"
72
- @click="$store.skillsListStore.toggleSkill(skill, true)">
73
- <span class="icon material-symbols-outlined">play_arrow</span> Enable
74
- </button>
75
- </template>
64
<button type="button" class="button confirm" title="Open in browser"
65
@click="$store.skillsListStore.openSkill(skill)">
66
<span class="icon material-symbols-outlined">folder_open</span> Open
webui/components/settings/skills/skills-list-store.js
-29
@@ -89,35 +89,6 @@ const model = {
89
}
90
},
91
92
- async toggleSkill(skill, enabled) {
93
- if (!skill) return;
94
- const previous = skill.enabled;
95
- skill.enabled = enabled;
96
- try {
97
- const response = await fetchApi("/skills", {
98
- method: "POST",
99
- headers: { "Content-Type": "application/json" },
100
- body: JSON.stringify({
101
- action: "toggle",
102
- skill_id: skill.skill_id,
103
- enabled,
104
- project_name: this.projectName || null,
105
- profile_name: this.profileName || null,
106
- }),
107
- });
108
- const result = await response.json().catch(() => ({}));
109
- if (!result.ok) {
110
- throw new Error(result.error || "Toggle failed");
111
- }
112
- } catch (e) {
113
- skill.enabled = previous;
114
- const msg = e?.message || "Toggle failed";
115
- if (window.toastFrontendError) {
116
- window.toastFrontendError(msg, "Skills");
117
- }
118
- }
119
- },
120
-
92
async deleteSkill(skill) {
93
if (!skill) return;
94
try {