129 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""技能集skill pack管理 + 安装能力。
技能目录结构(单一技能树 skills/所有机构共享读2026-08-21 重构):
skills/
├── global/ # 平台通用技能
├── projects/ # 项目通用技能(所有项目共享)
├── pipelines/{pid}/common/ # 产线通用技能
├── pipelines/{pid}/roles/{role}/ # 角色技能
├── packs/ # 技能集定义manifest 引用 global 里的技能名)
├── orgs/{org_id}/ # 机构技能(+ {project_id}/ 项目私有)
│ └── {project_id}/
└── users/{user_id}/ # 用户技能
安装 = 按技能集 manifest把 global/ 里的技能复制到 orgs/{org_id}/ 下(机构显式选装,覆盖 global 同名)。
"""
import os
import json
import shutil
def get_skills_base():
"""技能根目录 skills/(相对 cwd与 skill_loader 的 base_dir="skills" 一致。
优先环境变量 PIPELINE_SKILLS_BASE 覆盖。
"""
env = os.environ.get("PIPELINE_SKILLS_BASE", "")
if env:
return os.path.normpath(env)
return os.path.normpath(os.path.join(os.getcwd(), "skills"))
SKILLS_BASE = get_skills_base()
PACKS_DIR = os.path.join(SKILLS_BASE, "packs")
ALL_DIR = os.path.join(SKILLS_BASE, "global") # 公共技能 = 全局模板(源头)
PIPELINES_DIR = os.path.join(SKILLS_BASE, "pipelines") # 产线技能common/roles源头在 build.sh 6b 复制
def list_packs():
"""列出所有可安装的技能集manifest 摘要)。"""
packs = []
if not os.path.isdir(PACKS_DIR):
return packs
for d in sorted(os.listdir(PACKS_DIR)):
mf = os.path.join(PACKS_DIR, d, "manifest.json")
if os.path.isfile(mf):
try:
with open(mf, "r", encoding="utf-8") as f:
m = json.load(f)
packs.append({
"name": m.get("name", d),
"title": m.get("title", d),
"description": m.get("description", ""),
"vendor": m.get("vendor", ""),
"version": m.get("version", "1.0.0"),
"skill_count": len(m.get("skills", [])),
})
except Exception:
continue
return packs
def get_pack(pack_name):
"""返回技能集 manifest含技能列表不存在返回 None。"""
mf = os.path.join(PACKS_DIR, pack_name, "manifest.json")
if not os.path.isfile(mf):
return None
with open(mf, "r", encoding="utf-8") as f:
return json.load(f)
def install_pack(pack_name, target_base_dir, org_id):
"""把技能集安装到目标技能根目录的 orgs/{org_id}/ 下。
target_base_dir: skill_loader 的 base_dir运行时技能根目录如 .../skills
org_id: 机构 ID安装到该机构的 org scope
返回 {success, installed:[...], skipped:[...], error}
"""
pack = get_pack(pack_name)
if not pack:
return {"success": False, "error": f"技能集不存在: {pack_name}"}
org_dir = os.path.join(target_base_dir, "orgs", str(org_id))
os.makedirs(org_dir, exist_ok=True)
installed, skipped = [], []
for skill_name in pack.get("skills", []):
src = os.path.join(ALL_DIR, skill_name)
if not os.path.isdir(src):
skipped.append(skill_name)
continue
dst = os.path.join(org_dir, skill_name)
if os.path.exists(dst):
shutil.rmtree(dst)
shutil.copytree(src, dst)
installed.append(skill_name)
return {"success": True, "installed": installed, "skipped": skipped}
def uninstall_pack(pack_name, target_base_dir, org_id):
"""卸载技能集:删除该机构下该技能集引用的技能目录。"""
pack = get_pack(pack_name)
if not pack:
return {"success": False, "error": f"技能集不存在: {pack_name}"}
org_dir = os.path.join(target_base_dir, "orgs", str(org_id))
removed = []
for skill_name in pack.get("skills", []):
dst = os.path.join(org_dir, skill_name)
if os.path.isdir(dst):
shutil.rmtree(dst)
removed.append(skill_name)
return {"success": True, "removed": removed}
def installed_packs(target_base_dir, org_id):
"""返回某机构已安装的技能集名列表。"""
packs = []
for p in list_packs():
pack = get_pack(p["name"])
org_dir = os.path.join(target_base_dir, "orgs", str(org_id))
cnt = 0
for skill_name in pack.get("skills", []):
if os.path.isdir(os.path.join(org_dir, skill_name)):
cnt += 1
if cnt > 0:
packs.append({"name": p["name"], "title": p["title"],
"installed_skills": cnt, "total_skills": p["skill_count"]})
return packs