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 {