feat: 完成三角色工作台及金租对公Skills全流程演示

This commit is contained in:
NathanAlex7
2026-09-15 10:32:24 +08:00
parent 5f0b1f9409
commit 47ec090345
58 changed files with 2534 additions and 609 deletions
+62 -11
View File
@@ -1,11 +1,64 @@
"""Skills Scanner — Scan skills/ directory and generate SKILLS_SNAPSHOT.md."""
from pathlib import Path
from hashlib import sha256
from pathlib import Path
import re
import yaml
MAX_SKILL_BYTES = 64 * 1024
def parse_skill(content: str) -> dict:
"""Validate the small, declarative SKILL.md format used by the workbench."""
if not isinstance(content, str) or not content.startswith("---") or len(content.encode("utf-8")) > MAX_SKILL_BYTES:
raise ValueError("SKILL.md 缺少 YAML 头或超过 64 KB")
parts = content.split("---", 2)
meta = yaml.safe_load(parts[1]) if len(parts) == 3 else None
if not isinstance(meta, dict) or not isinstance(meta.get("name"), str) or not meta["name"].strip():
raise ValueError("SKILL.md 必须声明 name")
if not isinstance(meta.get("description"), str) or not meta["description"].strip():
raise ValueError("SKILL.md 必须声明 description")
details = meta.get("metadata", {})
if not isinstance(details, dict):
raise ValueError("SKILL.md 的 metadata 必须是对象")
for field in ("inputs", "audience"):
value = meta.get(field, details.get(field))
if value is not None and (not isinstance(value, list) or not all(isinstance(item, str) for item in value)):
raise ValueError(f"SKILL.md 的 {field} 必须是文本列表")
return meta
def skill_attributes(meta: dict) -> dict:
return {**meta, **meta.get("metadata", {})}
def skill_slug(meta: dict) -> str:
slug = re.sub(r"[^a-z0-9-]+", "-", meta["name"].strip().lower().replace("_", "-")).strip("-")
if not slug or len(slug) > 64:
raise ValueError("Skill 名称需使用 1–64 位英文小写字母、数字或连字符")
return slug
def install_skill(base_dir: Path, content: str) -> dict:
"""Install one uploaded SKILL.md under the managed imported-skills directory."""
meta = parse_skill(content)
slug = skill_slug(meta)
root = (base_dir / "skills").resolve()
target = (root / "imported" / slug / "SKILL.md").resolve()
if not target.is_relative_to(root):
raise ValueError("Skill 目录不在允许范围内")
if target.exists():
raise FileExistsError("同名 Skill 已存在,请先归档旧版本或修改 name")
target.parent.mkdir(parents=True, exist_ok=False)
temporary = target.with_suffix(".tmp")
temporary.write_text(content, encoding="utf-8")
temporary.replace(target)
legacy_id = target.relative_to(root).as_posix()
return {"id": legacy_id, "legacy_id": legacy_id, "name": meta["name"], "description": meta["description"], "meta": skill_attributes(meta)}
def discover_skills(base_dir: Path) -> list[dict]:
"""Read the native Skills catalog without changing its generated snapshot."""
root = (base_dir / "skills").resolve()
@@ -16,21 +69,19 @@ def discover_skills(base_dir: Path) -> list[dict]:
continue
try:
content = file.read_text(encoding="utf-8")
if not content.startswith("---"):
continue
parts = content.split("---", 2)
meta = yaml.safe_load(parts[1]) if len(parts) == 3 else None
if not isinstance(meta, dict):
meta = parse_skill(content)
attributes = skill_attributes(meta)
if str(attributes.get("status", "published")) == "archived":
continue
legacy_id = file.relative_to(root).as_posix()
entries.append({
"id": legacy_id,
"component_id": str(meta.get("id", legacy_id)),
"component_id": str(attributes.get("id", legacy_id)),
"legacy_id": legacy_id,
"kind": str(meta.get("kind", "skill")),
"version": str(meta.get("version", "legacy")),
"schema_version": int(meta.get("schema_version", 1)),
"status": str(meta.get("status", "published")),
"kind": str(attributes.get("kind", "skill")),
"version": str(attributes.get("version", "legacy")),
"schema_version": int(attributes.get("schema_version", 1)),
"status": str(attributes.get("status", "published")),
"name": str(meta.get("name", file.parent.name)),
"description": str(meta.get("description", "")),
"content_hash": sha256(content.encode("utf-8")).hexdigest(),