ForcePilot/test/test_skills_backend.py
肖泽涛 46b676db07 feat(skills): add skills management module
实现 Skills 管理功能,支持超级管理员导入、编辑、导出和删除技能包。

- 新增 Skill 数据模型(元数据存数据库,内容存文件系统)
- 新增 /api/system/skills/* 路由(权限分级:admin 可列表,superadmin 可管理)
- 新增 SelectedSkillsReadonlyBackend,运行时只读挂载到 /skills
- Agent 配置新增 skills 字段,运行时注入技能提示词
- 前端新增 Skills 管理页面(设置弹窗 + 独立路由)
- 新增测试覆盖:service/router/backend 单测

未实现功能:
 - 关联使用其他技能
 - 沙箱执行代码
 - 在技能中绑定工具
2026-02-21 02:48:20 +08:00

69 lines
2.3 KiB
Python

from __future__ import annotations
from pathlib import Path
from types import SimpleNamespace
from src.agents.common.backends import skills_backend
def _prepare_skills_dir(root: Path) -> None:
(root / "alpha").mkdir(parents=True, exist_ok=True)
(root / "alpha" / "SKILL.md").write_text(
"---\nname: alpha\ndescription: alpha\n---\n# alpha\n",
encoding="utf-8",
)
(root / "beta").mkdir(parents=True, exist_ok=True)
(root / "beta" / "SKILL.md").write_text(
"---\nname: beta\ndescription: beta\n---\n# beta\n",
encoding="utf-8",
)
def test_selected_skills_backend_readonly_and_visible_only_selected(tmp_path, monkeypatch):
_prepare_skills_dir(tmp_path)
monkeypatch.setattr(skills_backend, "get_skills_root_dir", lambda: tmp_path)
backend = skills_backend.SelectedSkillsReadonlyBackend(selected_slugs=["alpha"])
root_entries = backend.ls_info("/")
paths = sorted(entry.get("path") for entry in root_entries)
assert paths == ["/alpha/"]
ok_read = backend.read("/alpha/SKILL.md")
assert "alpha" in ok_read
denied_read = backend.read("/beta/SKILL.md")
assert "Access denied" in denied_read
write_result = backend.write("/alpha/new.md", "x")
assert write_result.error and "read-only" in write_result.error
edit_result = backend.edit("/alpha/SKILL.md", "alpha", "changed")
assert edit_result.error and "read-only" in edit_result.error
upload_result = backend.upload_files([("/alpha/a.txt", b"a")])
assert len(upload_result) == 1
assert upload_result[0].error == "permission_denied"
def test_composite_backend_mounts_skills_under_prefix(tmp_path, monkeypatch):
_prepare_skills_dir(tmp_path)
monkeypatch.setattr(skills_backend, "get_skills_root_dir", lambda: tmp_path)
runtime = SimpleNamespace(
context=SimpleNamespace(skills=["alpha"]),
state={},
)
composite = skills_backend.create_agent_composite_backend(runtime)
root = composite.ls_info("/")
all_paths = [entry.get("path") for entry in root]
assert "/skills/" in all_paths
skills_root = composite.ls_info("/skills/")
skill_paths = sorted(entry.get("path") for entry in skills_root)
assert skill_paths == ["/skills/alpha/"]
denied = composite.write("/skills/alpha/new.md", "x")
assert denied.error and "read-only" in denied.error