From c669b3bb2447528b99880f29749f86bbece3e7f7 Mon Sep 17 00:00:00 2001 From: Titan Date: Wed, 11 Mar 2026 15:41:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0uploads=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E6=96=87=E4=BB=B6=E5=88=B0skill=E6=89=AB?= =?UTF-8?q?=E6=8F=8F=E8=8C=83=E5=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/agents/lead_agent/prompt.py | 2 +- backend/src/config/extensions_config.py | 4 ++-- backend/src/skills/loader.py | 19 +++++++++++++++---- backend/src/skills/parser.py | 2 +- backend/src/skills/types.py | 6 +++++- 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/backend/src/agents/lead_agent/prompt.py b/backend/src/agents/lead_agent/prompt.py index dfe53bef..35a4f4bc 100644 --- a/backend/src/agents/lead_agent/prompt.py +++ b/backend/src/agents/lead_agent/prompt.py @@ -315,7 +315,7 @@ def get_skills_prompt_section() -> str: Returns the ... block listing all enabled skills, suitable for injection into any agent's system prompt. """ - skills = load_skills(enabled_only=True) + skills = load_skills(enabled_only=False) # Load all skills, we'll indicate enabled status in the prompt try: from src.config import get_app_config diff --git a/backend/src/config/extensions_config.py b/backend/src/config/extensions_config.py index 61e26680..692ff31b 100644 --- a/backend/src/config/extensions_config.py +++ b/backend/src/config/extensions_config.py @@ -161,8 +161,8 @@ class ExtensionsConfig(BaseModel): """ skill_config = self.skills.get(skill_name) if skill_config is None: - # Default to enable for public & custom skill - return skill_category in ("public", "custom") + # Default to enable for public/custom/uploads skills + return skill_category in ("public", "custom", "uploads") return skill_config.enabled diff --git a/backend/src/skills/loader.py b/backend/src/skills/loader.py index 1052108b..67e7ff48 100644 --- a/backend/src/skills/loader.py +++ b/backend/src/skills/loader.py @@ -4,6 +4,9 @@ from .parser import parse_skill_file from .types import Skill +UPLOADS_SKILLS_PATH = Path("/mnt/user-data/uploads") + + def get_skills_root_path() -> Path: """ Get the root path of the skills directory. @@ -22,7 +25,9 @@ def load_skills(skills_path: Path | None = None, use_config: bool = True, enable """ Load all skills from the skills directory. - Scans both public and custom skill directories, parsing SKILL.md files + Scans public/custom skill directories under the skills root, and also + scans user uploads skill directory in the virtual personal folder + (/mnt/user-data/uploads), parsing SKILL.md files to extract metadata. The enabled state is determined by the skills_state_config.json file. Args: @@ -53,9 +58,15 @@ def load_skills(skills_path: Path | None = None, use_config: bool = True, enable skills = [] - # Scan public and custom directories - for category in ["public", "custom"]: - category_path = skills_path / category + # Scan public/custom directories under skills root, and uploads skills + # under the virtual personal folder. + scan_targets: list[tuple[str, Path]] = [ + ("public", skills_path / "public"), + ("custom", skills_path / "custom"), + ("uploads", UPLOADS_SKILLS_PATH), + ] + + for category, category_path in scan_targets: if not category_path.exists() or not category_path.is_dir(): continue diff --git a/backend/src/skills/parser.py b/backend/src/skills/parser.py index eb96c2a2..a5e53bc0 100644 --- a/backend/src/skills/parser.py +++ b/backend/src/skills/parser.py @@ -10,7 +10,7 @@ def parse_skill_file(skill_file: Path, category: str) -> Skill | None: Args: skill_file: Path to the SKILL.md file - category: Category of the skill ('public' or 'custom') + category: Category of the skill ('public', 'custom', or 'uploads') Returns: Skill object if parsing succeeds, None otherwise diff --git a/backend/src/skills/types.py b/backend/src/skills/types.py index 183a406c..83759c0d 100644 --- a/backend/src/skills/types.py +++ b/backend/src/skills/types.py @@ -11,7 +11,7 @@ class Skill: license: str | None skill_dir: Path skill_file: Path - category: str # 'public' or 'custom' + category: str # 'public', 'custom', or 'uploads' enabled: bool = False # Whether this skill is enabled @property @@ -29,6 +29,8 @@ class Skill: Returns: Full container path to the skill directory """ + if self.category == "uploads": + return f"/mnt/user-data/uploads/{self.skill_dir.name}" return f"{container_base_path}/{self.category}/{self.skill_dir.name}" def get_container_file_path(self, container_base_path: str = "/mnt/skills") -> str: @@ -41,6 +43,8 @@ class Skill: Returns: Full container path to the skill's SKILL.md file """ + if self.category == "uploads": + return f"/mnt/user-data/uploads/{self.skill_dir.name}/SKILL.md" return f"{container_base_path}/{self.category}/{self.skill_dir.name}/SKILL.md" def __repr__(self) -> str: