mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-22 18:08:04 +00:00
refactor: remove core skill direct loading, skills owned by clawhubadapter only
- Drop skill.NewManager from homed bootstrap; skills dir no longer core-managed - Remove GetInjectedPrompt system-prompt injection (skills are not first-class) - Delete internal/skill package, SkillAPI, webui /api/v1/skills, status skills block - ConfigRegistry: plugin config tables now created only via RegisterDef; arbitrary scope Set/Get no longer implicitly creates config_<name> tables (fixes stray config_today_task table from SKILL directory name being used as a scope)
This commit is contained in:
@ -17,7 +17,6 @@ import (
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/social"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/text"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/plugin"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/skill"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/tracker"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/pkg/types"
|
||||
)
|
||||
@ -33,7 +32,6 @@ type Agent struct {
|
||||
io *agentIO.IOManager
|
||||
memory *memory.GraphDB
|
||||
indexer *memory.Indexer
|
||||
skills *skill.Manager
|
||||
tracker *tracker.Tracker
|
||||
context *RelevanceContext
|
||||
systemPrompt string
|
||||
@ -122,7 +120,6 @@ type AgentConfig struct {
|
||||
IO *agentIO.IOManager
|
||||
Memory *memory.GraphDB
|
||||
Indexer *memory.Indexer
|
||||
Skills *skill.Manager
|
||||
Tracker *tracker.Tracker
|
||||
|
||||
DocStore *document.Store
|
||||
@ -190,7 +187,6 @@ func New(cfg AgentConfig) *Agent {
|
||||
io: cfg.IO,
|
||||
memory: cfg.Memory,
|
||||
indexer: cfg.Indexer,
|
||||
skills: cfg.Skills,
|
||||
tracker: cfg.Tracker,
|
||||
context: rc,
|
||||
systemPrompt: cfg.SystemPrompt,
|
||||
|
||||
@ -11,7 +11,6 @@ import (
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/social"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/memory/text"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/plugin"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/skill"
|
||||
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/tracker"
|
||||
)
|
||||
@ -30,7 +29,6 @@ type KnowledgeStatus = sdk.KnowledgeStatus
|
||||
type DocumentStatus = sdk.DocumentStatus
|
||||
type TextMemoryStatus = sdk.TextMemoryStatus
|
||||
type SocialStatus = sdk.SocialStatus
|
||||
type SkillsStatus = sdk.SkillsStatus
|
||||
type LLMStatus = sdk.LLMStatus
|
||||
type ContextStatus = sdk.ContextStatus
|
||||
type RuntimeStatus = sdk.RuntimeStatus
|
||||
@ -59,7 +57,6 @@ func collectKernelStatus(
|
||||
docStore *document.Store,
|
||||
textMem *text.Memory,
|
||||
socialStore *social.SocialStore,
|
||||
skMgr *skill.Manager,
|
||||
trk *tracker.Tracker,
|
||||
) *KernelStatus {
|
||||
status := &KernelStatus{
|
||||
@ -146,15 +143,6 @@ func collectKernelStatus(
|
||||
}
|
||||
}
|
||||
|
||||
// Skills
|
||||
if skMgr != nil {
|
||||
status.Skills.Available = true
|
||||
skills := skMgr.List()
|
||||
status.Skills.SkillList = make([]string, len(skills))
|
||||
for i, sk := range skills {
|
||||
status.Skills.SkillList[i] = sk.Name
|
||||
}
|
||||
}
|
||||
|
||||
// Tracker
|
||||
if trk != nil {
|
||||
@ -191,10 +179,6 @@ func (a *Agent) GetKernelStatus() *KernelStatus {
|
||||
socialStore = a.social
|
||||
}
|
||||
|
||||
var skMgr *skill.Manager
|
||||
if a.skills != nil {
|
||||
skMgr = a.skills
|
||||
}
|
||||
|
||||
var trk *tracker.Tracker
|
||||
if a.tracker != nil {
|
||||
@ -214,7 +198,6 @@ func (a *Agent) GetKernelStatus() *KernelStatus {
|
||||
a.docStore,
|
||||
textMem,
|
||||
socialStore,
|
||||
skMgr,
|
||||
trk,
|
||||
)
|
||||
|
||||
|
||||
@ -56,12 +56,6 @@ func (a *Agent) buildSystemPrompt(memContext string, userInput string) string {
|
||||
prompt += "- 直接返回纯文本不会到达任何用户端。\n"
|
||||
prompt += "- 需要多步执行的长任务:**必须先**用 output_send__ 发一条确认消息告诉用户已收到(如「好的我去看看~」),**然后再**执行具体排查工具。确认消息不代表任务完成,发出后仍需继续执行实际工具并最终汇报结果。"
|
||||
|
||||
if a.skills != nil {
|
||||
if sp := a.skills.GetInjectedPrompt(); sp != "" {
|
||||
prompt += "\n\n" + sp
|
||||
}
|
||||
}
|
||||
|
||||
if a.indexer != nil {
|
||||
prompt += "\n\n" + a.indexer.BuildToolPrompt()
|
||||
}
|
||||
|
||||
@ -711,7 +711,6 @@ func (r *ConfigRegistry) ListPlugins() []string {
|
||||
}
|
||||
|
||||
func (r *ConfigRegistry) PluginConfig(name string) *PluginSettings {
|
||||
r.ensurePluginTable(name)
|
||||
return &PluginSettings{
|
||||
registry: r,
|
||||
table: r.pluginTableName(name),
|
||||
@ -768,6 +767,9 @@ func (p *PluginSettings) List(prefix string) ([]string, error) {
|
||||
func (p *PluginSettings) RegisterDef(def ConfigDef) {
|
||||
p.registry.mu.Lock()
|
||||
defer p.registry.mu.Unlock()
|
||||
// 只有注册配置定义才创建插件配置表:任意 scope 的读写不得隐式建表,
|
||||
// 避免非插件(如 SKILL 目录名)被注册成配置命名空间。
|
||||
p.registry.ensurePluginTable(p.name)
|
||||
p.registry.db.Exec(fmt.Sprintf(`INSERT OR IGNORE INTO %s (key, value) VALUES (?, ?)`, p.table), def.Key, def.Default)
|
||||
qualified := "plugin." + p.name + "." + def.Key
|
||||
def.Key = qualified
|
||||
|
||||
@ -121,6 +121,7 @@ func TestPluginConfig(t *testing.T) {
|
||||
r := NewConfigRegistry(path)
|
||||
|
||||
ps := r.PluginConfig("test_deepseek")
|
||||
ps.RegisterDef(ConfigDef{Key: "api_key", Default: "sk-test123"})
|
||||
if err := ps.Set("api_key", "sk-test123"); err != nil {
|
||||
t.Fatalf("PluginSettings.Set: %v", err)
|
||||
}
|
||||
|
||||
@ -394,8 +394,8 @@ function plugin.start(sdk)
|
||||
sdk.settings.set_plugin("other", "okey", "oval")
|
||||
_G.res.plugin_val = sdk.settings.get_plugin("other", "okey")
|
||||
_G.res.plugin_list = sdk.settings.list_plugin("other", "")
|
||||
_G.res.sett_list_type = type(sdk.settings.list(""))
|
||||
sdk.settings.register_def({key="def_key", type="string", display_name="DK", default="dv"})
|
||||
_G.res.sett_list_type = type(sdk.settings.list(""))
|
||||
_G.res.def_val = sdk.get_setting("def_key")
|
||||
_G.res.defs_type = type(sdk.settings.defs(""))
|
||||
_G.res.dump_type = type(sdk.settings.dump())
|
||||
@ -415,7 +415,10 @@ return plugin
|
||||
lp := plg.(*luaPlugin)
|
||||
|
||||
var capturedHandler sdk.StageHandler
|
||||
sett := sdk.NewSettings("aligned", internalConfig.NewConfigRegistry(""))
|
||||
reg := internalConfig.NewConfigRegistry("")
|
||||
sett := sdk.NewSettings("aligned", reg)
|
||||
// 目标插件须先注册配置定义才会建表(任意 scope 不再隐式建表)
|
||||
sdk.NewSettings("other", reg).RegisterDef(sdk.ConfigDef{Key: "okey", Default: "oval", Type: "string"})
|
||||
s := sdk.New("aligned", sdk.SDKConfig{
|
||||
Settings: sett,
|
||||
RegStage: func(stage sdk.Stage, handler sdk.StageHandler) {
|
||||
|
||||
@ -19,7 +19,6 @@ import (
|
||||
agentAPI "gitcode.com/JianFeeeee/HomeAgent/internal/agent/api"
|
||||
internalConfig "gitcode.com/JianFeeeee/HomeAgent/internal/config"
|
||||
luaVM "gitcode.com/JianFeeeee/HomeAgent/internal/lua"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/skill"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/internal/tracker"
|
||||
"gitcode.com/JianFeeeee/HomeAgent/pkg/types"
|
||||
)
|
||||
@ -90,7 +89,6 @@ type Registry struct {
|
||||
|
||||
status sdk.StatusAPI
|
||||
sup sdk.SupervisorAPI
|
||||
skMgr *skill.Manager
|
||||
trk *tracker.Tracker
|
||||
cfg *types.Config
|
||||
stageHost sdk.ToolSource
|
||||
@ -126,7 +124,6 @@ func (r *Registry) SetAPIRegistrar(fn sdk.APIRegistrar) { r.regAPI = fn
|
||||
func (r *Registry) SetToolCleaner(tc PluginToolCleaner) { r.toolCleaner = tc }
|
||||
func (r *Registry) SetStatusProvider(sp sdk.StatusAPI) { r.status = sp }
|
||||
func (r *Registry) SetSupervisor(sup sdk.SupervisorAPI) { r.sup = sup }
|
||||
func (r *Registry) SetSkillManager(skMgr *skill.Manager) { r.skMgr = skMgr }
|
||||
func (r *Registry) SetTracker(trk *tracker.Tracker) { r.trk = trk }
|
||||
func (r *Registry) SetConfig(cfg *types.Config) { r.cfg = cfg }
|
||||
func (r *Registry) SetStageHost(sh sdk.ToolSource) { r.stageHost = sh }
|
||||
@ -216,7 +213,6 @@ func (r *Registry) buildSDK(name string) *sdk.PluginSDK {
|
||||
|
||||
Status: r.status,
|
||||
Supervisor: r.sup,
|
||||
Skill: r.skMgr,
|
||||
Adapter: sdk.NewAdapter(r.lua),
|
||||
Tracker: r.trk,
|
||||
Config: sdk.NewConfig(r.cfg),
|
||||
|
||||
@ -38,7 +38,6 @@ type Handler struct {
|
||||
supervisor sdk.SupervisorAPI
|
||||
memory sdk.MemoryAPI
|
||||
indexer sdk.IndexerAPI
|
||||
skills sdk.SkillAPI
|
||||
adapter sdk.AdapterAPI
|
||||
config sdk.ConfigAPI
|
||||
startTime time.Time
|
||||
@ -97,7 +96,6 @@ func NewHandler(s *sdk.PluginSDK) *Handler {
|
||||
sup sdk.SupervisorAPI
|
||||
mem sdk.MemoryAPI
|
||||
idx sdk.IndexerAPI
|
||||
sk sdk.SkillAPI
|
||||
ad sdk.AdapterAPI
|
||||
cfg sdk.ConfigAPI
|
||||
tm sdk.TextMemoryAPI
|
||||
@ -110,7 +108,7 @@ func NewHandler(s *sdk.PluginSDK) *Handler {
|
||||
)
|
||||
if s != nil {
|
||||
sup, mem, idx = s.Supervisor(), s.Memory(), s.Indexer()
|
||||
sk, ad, cfg = s.Skill(), s.Adapter(), s.Config()
|
||||
ad, cfg = s.Adapter(), s.Config()
|
||||
tm, ks, tr = s.TextMemory(), s.Knowledge(), s.Tracker()
|
||||
se, pm = s.Settings(), s.PluginMgr()
|
||||
st, llm = s.Status(), s.LLM()
|
||||
@ -120,7 +118,6 @@ func NewHandler(s *sdk.PluginSDK) *Handler {
|
||||
supervisor: sup,
|
||||
memory: mem,
|
||||
indexer: idx,
|
||||
skills: sk,
|
||||
adapter: ad,
|
||||
config: cfg,
|
||||
startTime: time.Now(),
|
||||
@ -378,7 +375,6 @@ func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/v1/status", h.requireAPI(h.handleStatus))
|
||||
mux.HandleFunc("/api/v1/agents", h.requireAPI(h.handleAgents))
|
||||
mux.HandleFunc("/api/v1/agents/", h.requireAPI(h.handleAgentByID))
|
||||
mux.HandleFunc("/api/v1/skills", h.requireAPI(h.handleSkills))
|
||||
mux.HandleFunc("/api/v1/memory", h.requireAPI(h.handleMemory))
|
||||
mux.HandleFunc("/api/v1/memory/", h.requireAPI(h.handleMemory))
|
||||
mux.HandleFunc("/api/v1/memory/graph", h.requireAPI(h.handleMemoryGraph))
|
||||
@ -603,44 +599,6 @@ func (h *Handler) handleAgentAction(w http.ResponseWriter, r *http.Request, agen
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": fmt.Sprintf("%s_requested", action), "agent": string(agentID)})
|
||||
}
|
||||
|
||||
func (h *Handler) handleSkills(w http.ResponseWriter, r *http.Request) {
|
||||
if h.skills == nil {
|
||||
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "skills not available"})
|
||||
return
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{"skills": h.skills.List()})
|
||||
case http.MethodPost:
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
||||
return
|
||||
}
|
||||
if err := h.skills.Install(req.Name, req.Content); err != nil {
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusCreated, map[string]string{"status": "installed", "name": req.Name})
|
||||
case http.MethodDelete:
|
||||
name := r.URL.Query().Get("name")
|
||||
if name == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "name query param required"})
|
||||
return
|
||||
}
|
||||
if err := h.skills.Uninstall(name); err != nil {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]string{"status": "uninstalled", "name": name})
|
||||
default:
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) handleMemory(w http.ResponseWriter, r *http.Request) {
|
||||
if h.memory == nil {
|
||||
writeJSON(w, http.StatusServiceUnavailable, map[string]string{"error": "memory system not available"})
|
||||
|
||||
@ -90,7 +90,6 @@ type PluginSDK struct {
|
||||
|
||||
status StatusAPI
|
||||
supervisor SupervisorAPI
|
||||
skill SkillAPI
|
||||
adapter AdapterAPI
|
||||
tracker TrackerAPI
|
||||
config ConfigAPI
|
||||
@ -150,7 +149,6 @@ type SDKConfig struct {
|
||||
|
||||
Status StatusAPI
|
||||
Supervisor SupervisorAPI
|
||||
Skill SkillAPI
|
||||
Adapter AdapterAPI
|
||||
Tracker TrackerAPI
|
||||
Config ConfigAPI
|
||||
@ -187,7 +185,6 @@ func New(name string, cfg SDKConfig) *PluginSDK {
|
||||
|
||||
status: cfg.Status,
|
||||
supervisor: cfg.Supervisor,
|
||||
skill: cfg.Skill,
|
||||
adapter: cfg.Adapter,
|
||||
tracker: cfg.Tracker,
|
||||
config: cfg.Config,
|
||||
@ -198,7 +195,6 @@ func New(name string, cfg SDKConfig) *PluginSDK {
|
||||
|
||||
func (s *PluginSDK) Status() StatusAPI { return s.status }
|
||||
func (s *PluginSDK) Supervisor() SupervisorAPI { return s.supervisor }
|
||||
func (s *PluginSDK) Skill() SkillAPI { return s.skill }
|
||||
func (s *PluginSDK) Adapter() AdapterAPI { return s.adapter }
|
||||
func (s *PluginSDK) Tracker() TrackerAPI { return s.tracker }
|
||||
func (s *PluginSDK) Config() ConfigAPI { return s.config }
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
package sdk
|
||||
|
||||
// SkillAPI provides access to the skill manager.
|
||||
type SkillAPI interface {
|
||||
List() []*Skill
|
||||
Get(name string) *Skill
|
||||
Install(name, content string) error
|
||||
Uninstall(name string) error
|
||||
Toggle(name string, enabled bool) error
|
||||
}
|
||||
|
||||
// Skill is a neutral description of an installed skill.
|
||||
type Skill struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Version string `json:"version"`
|
||||
Author string `json:"author,omitempty"`
|
||||
Entry string `json:"entry,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Enabled bool `json:"enabled"`
|
||||
RawContent string `json:"-"`
|
||||
}
|
||||
@ -21,7 +21,6 @@ type KernelStatus struct {
|
||||
Documents DocumentStatus `json:"documents"`
|
||||
TextMemory TextMemoryStatus `json:"text_memory"`
|
||||
Social SocialStatus `json:"social"`
|
||||
Skills SkillsStatus `json:"skills"`
|
||||
|
||||
LLM LLMStatus `json:"llm"`
|
||||
|
||||
@ -72,11 +71,6 @@ type SocialStatus struct {
|
||||
PersonCount int `json:"person_count"`
|
||||
}
|
||||
|
||||
type SkillsStatus struct {
|
||||
Available bool `json:"available"`
|
||||
SkillList []string `json:"skill_list,omitempty"`
|
||||
}
|
||||
|
||||
type LLMStatus struct {
|
||||
Available bool `json:"available"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
|
||||
@ -1,215 +0,0 @@
|
||||
package skill
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
|
||||
)
|
||||
|
||||
// Skill 已迁入内置 SDK,此处保留别名以兼容现有调用方。
|
||||
type Skill = sdk.Skill
|
||||
|
||||
type Manager struct {
|
||||
mu sync.RWMutex
|
||||
skillsDir string
|
||||
skills map[string]*Skill
|
||||
}
|
||||
|
||||
func NewManager(skillsDir string) *Manager {
|
||||
return &Manager{
|
||||
skillsDir: skillsDir,
|
||||
skills: make(map[string]*Skill),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Init() error {
|
||||
if err := os.MkdirAll(m.skillsDir, 0755); err != nil {
|
||||
return fmt.Errorf("create skills dir: %w", err)
|
||||
}
|
||||
return m.loadAll()
|
||||
}
|
||||
|
||||
func (m *Manager) loadAll() error {
|
||||
entries, err := os.ReadDir(m.skillsDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
skillDir := filepath.Join(m.skillsDir, entry.Name())
|
||||
skill, err := m.loadSkill(skillDir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
m.skills[skill.Name] = skill
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) loadSkill(dir string) (*Skill, error) {
|
||||
skill := &Skill{
|
||||
Name: filepath.Base(dir),
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
skillFilePath := filepath.Join(dir, "SKILL.md")
|
||||
if data, err := os.ReadFile(skillFilePath); err == nil {
|
||||
skill.RawContent = string(data)
|
||||
skill.Description = extractDescription(skill.RawContent)
|
||||
skill.Version = extractField(skill.RawContent, "version")
|
||||
skill.Author = extractField(skill.RawContent, "author")
|
||||
}
|
||||
|
||||
metaPath := filepath.Join(dir, "skill.json")
|
||||
if data, err := os.ReadFile(metaPath); err == nil {
|
||||
var meta struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Version string `json:"version"`
|
||||
Author string `json:"author"`
|
||||
Entry string `json:"entry"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &meta); err == nil {
|
||||
if meta.Name != "" {
|
||||
skill.Name = meta.Name
|
||||
}
|
||||
if meta.Description != "" {
|
||||
skill.Description = meta.Description
|
||||
}
|
||||
if meta.Version != "" {
|
||||
skill.Version = meta.Version
|
||||
}
|
||||
if meta.Author != "" {
|
||||
skill.Author = meta.Author
|
||||
}
|
||||
if meta.Entry != "" {
|
||||
skill.Entry = meta.Entry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return skill, nil
|
||||
}
|
||||
|
||||
func (m *Manager) Install(name string, content string) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
skillDir := filepath.Join(m.skillsDir, name)
|
||||
if err := os.MkdirAll(skillDir, 0755); err != nil {
|
||||
return fmt.Errorf("create skill dir: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filepath.Join(skillDir, "SKILL.md"), []byte(content), 0644); err != nil {
|
||||
return fmt.Errorf("write SKILL.md: %w", err)
|
||||
}
|
||||
|
||||
skill, err := m.loadSkill(skillDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load installed skill: %w", err)
|
||||
}
|
||||
|
||||
m.skills[name] = skill
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Uninstall(name string) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if _, ok := m.skills[name]; !ok {
|
||||
return fmt.Errorf("skill %s not found", name)
|
||||
}
|
||||
|
||||
skillDir := filepath.Join(m.skillsDir, name)
|
||||
if err := os.RemoveAll(skillDir); err != nil {
|
||||
return fmt.Errorf("remove skill dir: %w", err)
|
||||
}
|
||||
|
||||
delete(m.skills, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) List() []*Skill {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
skills := make([]*Skill, 0, len(m.skills))
|
||||
for _, s := range m.skills {
|
||||
skills = append(skills, s)
|
||||
}
|
||||
|
||||
sort.Slice(skills, func(i, j int) bool {
|
||||
return skills[i].Name < skills[j].Name
|
||||
})
|
||||
|
||||
return skills
|
||||
}
|
||||
|
||||
func (m *Manager) Get(name string) *Skill {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
return m.skills[name]
|
||||
}
|
||||
|
||||
func (m *Manager) Toggle(name string, enabled bool) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if _, ok := m.skills[name]; !ok {
|
||||
return fmt.Errorf("skill %s not found", name)
|
||||
}
|
||||
m.skills[name].Enabled = enabled
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) GetInjectedPrompt() string {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
var parts []string
|
||||
for _, s := range m.skills {
|
||||
if s.Enabled && s.RawContent != "" {
|
||||
parts = append(parts, fmt.Sprintf("=== Skill: %s ===\n%s", s.Name, s.RawContent))
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, "\n\n")
|
||||
}
|
||||
|
||||
// Manager 直接满足内置 SDK 的 SkillAPI(复用优先,无需独立适配器)。
|
||||
var _ sdk.SkillAPI = (*Manager)(nil)
|
||||
|
||||
func extractDescription(content string) string {
|
||||
lines := strings.Split(content, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line != "" && !strings.HasPrefix(line, "#") {
|
||||
return line
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractField(content string, field string) string {
|
||||
prefix := fmt.Sprintf("%s:", field)
|
||||
for _, line := range strings.Split(content, "\n") {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(strings.ToLower(trimmed), prefix) {
|
||||
return strings.TrimSpace(strings.TrimPrefix(trimmed, prefix))
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user