mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-03-31 08:19:54 +02:00
240 lines
6.0 KiB
Go
240 lines
6.0 KiB
Go
package skills
|
||
|
||
import (
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"sync"
|
||
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
// Manager Skills管理器
|
||
type Manager struct {
|
||
skillsDir string
|
||
logger *zap.Logger
|
||
skills map[string]*Skill // 缓存已加载的skills
|
||
mu sync.RWMutex // 保护skills map的并发访问
|
||
}
|
||
|
||
// Skill Skill定义
|
||
type Skill struct {
|
||
Name string // Skill名称
|
||
Description string // Skill描述
|
||
Content string // Skill内容(从SKILL.md中提取)
|
||
Path string // Skill路径
|
||
}
|
||
|
||
// NewManager 创建新的Skills管理器
|
||
func NewManager(skillsDir string, logger *zap.Logger) *Manager {
|
||
return &Manager{
|
||
skillsDir: skillsDir,
|
||
logger: logger,
|
||
skills: make(map[string]*Skill),
|
||
}
|
||
}
|
||
|
||
// LoadSkill 加载单个skill
|
||
func (m *Manager) LoadSkill(skillName string) (*Skill, error) {
|
||
// 先尝试读锁检查缓存
|
||
m.mu.RLock()
|
||
if skill, exists := m.skills[skillName]; exists {
|
||
m.mu.RUnlock()
|
||
return skill, nil
|
||
}
|
||
m.mu.RUnlock()
|
||
|
||
// 构建skill路径
|
||
skillPath := filepath.Join(m.skillsDir, skillName)
|
||
|
||
// 检查目录是否存在
|
||
if _, err := os.Stat(skillPath); os.IsNotExist(err) {
|
||
return nil, fmt.Errorf("skill %s not found", skillName)
|
||
}
|
||
|
||
// 查找SKILL.md文件
|
||
skillFile := filepath.Join(skillPath, "SKILL.md")
|
||
if _, err := os.Stat(skillFile); os.IsNotExist(err) {
|
||
// 尝试其他可能的文件名
|
||
alternatives := []string{
|
||
filepath.Join(skillPath, "skill.md"),
|
||
filepath.Join(skillPath, "README.md"),
|
||
filepath.Join(skillPath, "readme.md"),
|
||
}
|
||
found := false
|
||
for _, alt := range alternatives {
|
||
if _, err := os.Stat(alt); err == nil {
|
||
skillFile = alt
|
||
found = true
|
||
break
|
||
}
|
||
}
|
||
if !found {
|
||
return nil, fmt.Errorf("skill file not found for %s", skillName)
|
||
}
|
||
}
|
||
|
||
// 读取skill文件
|
||
content, err := os.ReadFile(skillFile)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to read skill file: %w", err)
|
||
}
|
||
|
||
// 解析skill内容
|
||
skill := m.parseSkillContent(string(content), skillName, skillPath)
|
||
|
||
// 使用写锁缓存skill(双重检查,避免重复加载)
|
||
m.mu.Lock()
|
||
// 再次检查,可能其他goroutine已经加载了
|
||
if existing, exists := m.skills[skillName]; exists {
|
||
m.mu.Unlock()
|
||
return existing, nil
|
||
}
|
||
m.skills[skillName] = skill
|
||
m.mu.Unlock()
|
||
|
||
return skill, nil
|
||
}
|
||
|
||
// LoadSkills 批量加载skills
|
||
func (m *Manager) LoadSkills(skillNames []string) ([]*Skill, error) {
|
||
var skills []*Skill
|
||
var errors []string
|
||
|
||
for _, name := range skillNames {
|
||
skill, err := m.LoadSkill(name)
|
||
if err != nil {
|
||
errors = append(errors, fmt.Sprintf("failed to load skill %s: %v", name, err))
|
||
m.logger.Warn("加载skill失败", zap.String("skill", name), zap.Error(err))
|
||
continue
|
||
}
|
||
skills = append(skills, skill)
|
||
}
|
||
|
||
if len(errors) > 0 && len(skills) == 0 {
|
||
return nil, fmt.Errorf("failed to load any skills: %s", strings.Join(errors, "; "))
|
||
}
|
||
|
||
return skills, nil
|
||
}
|
||
|
||
// ListSkills 列出所有可用的skills
|
||
func (m *Manager) ListSkills() ([]string, error) {
|
||
if _, err := os.Stat(m.skillsDir); os.IsNotExist(err) {
|
||
return []string{}, nil
|
||
}
|
||
|
||
entries, err := os.ReadDir(m.skillsDir)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to read skills directory: %w", err)
|
||
}
|
||
|
||
var skills []string
|
||
for _, entry := range entries {
|
||
if !entry.IsDir() {
|
||
continue
|
||
}
|
||
|
||
skillName := entry.Name()
|
||
// 检查是否有SKILL.md文件
|
||
skillFile := filepath.Join(m.skillsDir, skillName, "SKILL.md")
|
||
if _, err := os.Stat(skillFile); err == nil {
|
||
skills = append(skills, skillName)
|
||
continue
|
||
}
|
||
|
||
// 尝试其他可能的文件名
|
||
alternatives := []string{
|
||
filepath.Join(m.skillsDir, skillName, "skill.md"),
|
||
filepath.Join(m.skillsDir, skillName, "README.md"),
|
||
filepath.Join(m.skillsDir, skillName, "readme.md"),
|
||
}
|
||
for _, alt := range alternatives {
|
||
if _, err := os.Stat(alt); err == nil {
|
||
skills = append(skills, skillName)
|
||
break
|
||
}
|
||
}
|
||
}
|
||
|
||
return skills, nil
|
||
}
|
||
|
||
// parseSkillContent 解析skill内容
|
||
// 支持YAML front matter格式,类似goskills
|
||
func (m *Manager) parseSkillContent(content, skillName, skillPath string) *Skill {
|
||
skill := &Skill{
|
||
Name: skillName,
|
||
Path: skillPath,
|
||
}
|
||
|
||
// 检查是否有YAML front matter
|
||
if strings.HasPrefix(content, "---") {
|
||
parts := strings.SplitN(content, "---", 3)
|
||
if len(parts) >= 3 {
|
||
// 解析front matter(简单实现,只提取name和description)
|
||
frontMatter := parts[1]
|
||
lines := strings.Split(frontMatter, "\n")
|
||
for _, line := range lines {
|
||
line = strings.TrimSpace(line)
|
||
if strings.HasPrefix(line, "name:") {
|
||
name := strings.TrimSpace(strings.TrimPrefix(line, "name:"))
|
||
name = strings.Trim(name, `"'"`)
|
||
if name != "" {
|
||
skill.Name = name
|
||
}
|
||
} else if strings.HasPrefix(line, "description:") {
|
||
desc := strings.TrimSpace(strings.TrimPrefix(line, "description:"))
|
||
desc = strings.Trim(desc, `"'"`)
|
||
skill.Description = desc
|
||
}
|
||
}
|
||
// 剩余部分是内容
|
||
if len(parts) == 3 {
|
||
skill.Content = strings.TrimSpace(parts[2])
|
||
}
|
||
} else {
|
||
// 没有front matter,整个内容就是skill内容
|
||
skill.Content = content
|
||
}
|
||
} else {
|
||
// 没有front matter,整个内容就是skill内容
|
||
skill.Content = content
|
||
}
|
||
|
||
// 如果内容为空,使用描述作为内容
|
||
if skill.Content == "" {
|
||
skill.Content = skill.Description
|
||
}
|
||
|
||
return skill
|
||
}
|
||
|
||
// GetSkillContent 获取skill的完整内容(用于注入到系统提示词)
|
||
func (m *Manager) GetSkillContent(skillNames []string) (string, error) {
|
||
skills, err := m.LoadSkills(skillNames)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
if len(skills) == 0 {
|
||
return "", nil
|
||
}
|
||
|
||
var builder strings.Builder
|
||
builder.WriteString("## 可用Skills\n\n")
|
||
builder.WriteString("在执行任务前,请仔细阅读以下skills内容,这些内容包含了相关的专业知识和方法:\n\n")
|
||
|
||
for _, skill := range skills {
|
||
builder.WriteString(fmt.Sprintf("### Skill: %s\n", skill.Name))
|
||
if skill.Description != "" {
|
||
builder.WriteString(fmt.Sprintf("**描述**: %s\n\n", skill.Description))
|
||
}
|
||
builder.WriteString(skill.Content)
|
||
builder.WriteString("\n\n---\n\n")
|
||
}
|
||
|
||
return builder.String(), nil
|
||
}
|