Files
X-Agents/server/internal/service/skill_service.go
DESKTOP-72TV0V4\caoxiaozhu 2a9326ef5f refactor: Skill状态字段从int改为string类型
- model/skill.go: Status字段从 int 改为 string,支持 "active"/"inactive"
- handler/skill_handler.go: 适配Status字段的类型变化,处理"1"/"0"和"active"/"inactive"两种格式
- repository/skill_repo.go: 更新Status字段的空值判断逻辑
- service/skill_service.go: 同步更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 08:31:57 +08:00

228 lines
5.5 KiB
Go
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.
package service
import (
"log"
"os"
"path/filepath"
"strings"
"x-agents/server/internal/model"
"x-agents/server/internal/repository"
"gopkg.in/yaml.v3"
)
type SkillService struct {
skillRepo *repository.SkillRepository
}
func NewSkillService(skillRepo *repository.SkillRepository) *SkillService {
return &SkillService{skillRepo: skillRepo}
}
func (s *SkillService) GetAllSkills() ([]model.Skill, error) {
return s.skillRepo.FindAll()
}
func (s *SkillService) GetSkillByID(id string) (*model.Skill, error) {
return s.skillRepo.FindByID(id)
}
func (s *SkillService) GetSkillsByType(skillType string) ([]model.Skill, error) {
return s.skillRepo.FindByType(skillType)
}
func (s *SkillService) CreateSkill(skill *model.Skill) error {
return s.skillRepo.Create(skill)
}
func (s *SkillService) UpdateSkill(skill *model.Skill) error {
return s.skillRepo.Update(skill)
}
func (s *SkillService) DeleteSkill(id string) error {
// 先获取 skill 信息,以便删除本地文件
skill, err := s.skillRepo.FindByID(id)
if err != nil {
return err
}
// 删除数据库记录
if err := s.skillRepo.Delete(id); err != nil {
return err
}
// 删除本地文件skill 目录)
if skill.Path != "" {
// 获取 skill 所在目录SKILL.md 的父目录)
skillDir := filepath.Dir(skill.Path)
if err := os.RemoveAll(skillDir); err != nil {
log.Printf("[SkillService] Warning: failed to delete skill directory %s: %v", skillDir, err)
// 数据库记录已删除,不返回错误
} else {
log.Printf("[SkillService] Deleted skill directory: %s", skillDir)
}
}
return nil
}
// InitSkills 初始化扫描所有 skills 目录
func (s *SkillService) InitSkills() error {
// 获取项目根目录
projectRoot := s.getProjectRoot()
if projectRoot == "" {
return nil
}
var totalCount int
// 扫描 system skills: core/agents/skills/system
systemSkillsPath := filepath.Join(projectRoot, "core", "agents", "skills", "system")
if _, err := os.Stat(systemSkillsPath); err == nil {
systemSkills, err := s.scanSkillsDirectory(systemSkillsPath, "system")
if err == nil && len(systemSkills) > 0 {
s.skillRepo.DeleteByType("system")
s.skillRepo.UpsertBatch(systemSkills)
totalCount += len(systemSkills)
}
}
// 扫描 user skills: core/agents/skills/user
userSkillsPath := filepath.Join(projectRoot, "core", "agents", "skills", "user")
if _, err := os.Stat(userSkillsPath); err == nil {
userSkills, err := s.scanSkillsDirectory(userSkillsPath, "user")
if err == nil && len(userSkills) > 0 {
s.skillRepo.DeleteByType("user")
s.skillRepo.UpsertBatch(userSkills)
totalCount += len(userSkills)
}
}
if totalCount > 0 {
log.Printf("[SkillService] Loaded %d skills", totalCount)
}
return nil
}
// scanSkillsDirectory 扫描指定目录下的所有 skill
func (s *SkillService) scanSkillsDirectory(basePath string, skillType string) ([]model.Skill, error) {
var skills []model.Skill
entries, err := os.ReadDir(basePath)
if err != nil {
return nil, err
}
for _, entry := range entries {
if !entry.IsDir() {
continue
}
skillDir := filepath.Join(basePath, entry.Name())
skillPath := filepath.Join(skillDir, "SKILL.md")
// 尝试 skill.md大小写不敏感
if _, err := os.Stat(skillPath); os.IsNotExist(err) {
skillPath = filepath.Join(skillDir, "skill.md")
}
skillInfo, err := s.parseSkillFile(skillPath)
if err != nil {
log.Printf("[SkillService] Error parsing skill file %s: %v", skillPath, err)
continue
}
// 如果没有从文件解析到名称,使用目录名
if skillInfo.SkillName == "" {
skillInfo.SkillName = entry.Name()
}
skill := model.Skill{
SkillName: skillInfo.SkillName,
SkillType: skillType,
SkillDesc: skillInfo.SkillDesc,
Path: skillPath,
Status: "active",
}
skills = append(skills, skill)
}
return skills, nil
}
// parseSkillFile 解析 SKILL.md 文件,提取 YAML front matter
func (s *SkillService) parseSkillFile(skillPath string) (*model.Skill, error) {
content, err := os.ReadFile(skillPath)
if err != nil {
return nil, err
}
// 检查是否有 YAML front matter
contentStr := string(content)
if !strings.HasPrefix(contentStr, "---") {
// 没有 front matter返回空
return &model.Skill{}, nil
}
// 找到结束标记
lines := strings.Split(contentStr, "\n")
var endIdx int
for i := 1; i < len(lines); i++ {
if strings.TrimSpace(lines[i]) == "---" {
endIdx = i
break
}
}
if endIdx == 0 {
return &model.Skill{}, nil
}
// 提取 YAML 内容
yamlContent := strings.Join(lines[1:endIdx], "\n")
// 解析 YAML
var frontMatter map[string]string
if err := yaml.Unmarshal([]byte(yamlContent), &frontMatter); err != nil {
return nil, err
}
skill := &model.Skill{
SkillName: frontMatter["name"],
SkillDesc: frontMatter["description"],
}
return skill, nil
}
// getProjectRoot 获取项目根目录
func (s *SkillService) getProjectRoot() string {
execPath, _ := os.Getwd()
projectRoot := execPath
// 如果当前目录名为 server向上找一级
baseName := filepath.Base(execPath)
if baseName == "server" {
projectRoot = filepath.Dir(execPath)
}
// 尝试向上查找包含 .git 的目录
if _, err := os.Stat(filepath.Join(projectRoot, ".git")); os.IsNotExist(err) {
for i := 0; i < 3; i++ {
parent := filepath.Dir(projectRoot)
if parent == projectRoot {
break
}
if _, err := os.Stat(filepath.Join(parent, ".git")); err == nil {
projectRoot = parent
break
}
projectRoot = parent
}
}
return projectRoot
}