From c7ee45d6e1b363e63ebc9b917230a5786a499f20 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 2 Aug 2026 11:40:13 +0800 Subject: [PATCH] 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_ tables (fixes stray config_today_task table from SKILL directory name being used as a scope) --- cmd/homed/main.go | 9 -- internal/agent/core/agent.go | 4 - internal/agent/core/status.go | 17 --- internal/agent/core/tooldefs.go | 6 - internal/config/registry.go | 4 +- internal/config/registry_test.go | 1 + internal/plugin/lua_plugin_test.go | 7 +- internal/plugin/registry.go | 4 - internal/plugins/webui/handler.go | 44 +----- internal/sdk/plugin.go | 4 - internal/sdk/skill.go | 22 --- internal/sdk/status.go | 6 - internal/skill/manager.go | 215 ----------------------------- 13 files changed, 10 insertions(+), 333 deletions(-) delete mode 100644 internal/sdk/skill.go delete mode 100644 internal/skill/manager.go diff --git a/cmd/homed/main.go b/cmd/homed/main.go index 956588d..f9860a7 100644 --- a/cmd/homed/main.go +++ b/cmd/homed/main.go @@ -36,7 +36,6 @@ import ( _ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/pluginmgr" _ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins/webui" sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk" - "gitcode.com/JianFeeeee/HomeAgent/internal/skill" "gitcode.com/JianFeeeee/HomeAgent/internal/supervisor" "gitcode.com/JianFeeeee/HomeAgent/internal/tracker" _ "gitcode.com/JianFeeeee/HomeAgent/internal/plugins" @@ -88,7 +87,6 @@ func main() { dirs := []string{ *dataDir, filepath.Join(*dataDir, "snapshots"), - filepath.Join(*dataDir, "skills"), filepath.Join(*dataDir, "plugins"), filepath.Join(*dataDir, "changesets"), filepath.Join(*dataDir, "memory"), @@ -131,11 +129,6 @@ func main() { defer distiller.Stop() } - skMgr := skill.NewManager(filepath.Join(*dataDir, "skills")) - if err := skMgr.Init(); err != nil { - log.Printf("[homed] warning: skill init failed: %v", err) - } - // ======================================================================== // 配置中心(SQLite 持久化,唯一配置源) // ======================================================================== @@ -391,7 +384,6 @@ func main() { IO: iom, Memory: memDB, Indexer: memIdx, - Skills: skMgr, Tracker: trk, DocStore: docStore, Knowledge: ks, @@ -416,7 +408,6 @@ func main() { pluginReg.SetLuaVM(luaVM) pluginReg.SetBaseAPIKey(baseAPIKey) pluginReg.SetSupervisor(supervisor.NewSDKAdapter(sup)) - pluginReg.SetSkillManager(skMgr) pluginReg.SetTracker(trk) pluginReg.SetConfig(cfg) pluginReg.SetStageHost(stageHost) diff --git a/internal/agent/core/agent.go b/internal/agent/core/agent.go index ec669cc..597b8ac 100644 --- a/internal/agent/core/agent.go +++ b/internal/agent/core/agent.go @@ -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, diff --git a/internal/agent/core/status.go b/internal/agent/core/status.go index de243f1..c67f2f9 100644 --- a/internal/agent/core/status.go +++ b/internal/agent/core/status.go @@ -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, ) diff --git a/internal/agent/core/tooldefs.go b/internal/agent/core/tooldefs.go index 02b3408..c1af1f3 100644 --- a/internal/agent/core/tooldefs.go +++ b/internal/agent/core/tooldefs.go @@ -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() } diff --git a/internal/config/registry.go b/internal/config/registry.go index eba7354..6965516 100644 --- a/internal/config/registry.go +++ b/internal/config/registry.go @@ -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 diff --git a/internal/config/registry_test.go b/internal/config/registry_test.go index 817aa79..09aafc4 100644 --- a/internal/config/registry_test.go +++ b/internal/config/registry_test.go @@ -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) } diff --git a/internal/plugin/lua_plugin_test.go b/internal/plugin/lua_plugin_test.go index f799530..1827f52 100644 --- a/internal/plugin/lua_plugin_test.go +++ b/internal/plugin/lua_plugin_test.go @@ -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) { diff --git a/internal/plugin/registry.go b/internal/plugin/registry.go index 729693c..0e7e8db 100644 --- a/internal/plugin/registry.go +++ b/internal/plugin/registry.go @@ -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), diff --git a/internal/plugins/webui/handler.go b/internal/plugins/webui/handler.go index 17a8b93..308c4ab 100644 --- a/internal/plugins/webui/handler.go +++ b/internal/plugins/webui/handler.go @@ -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"}) diff --git a/internal/sdk/plugin.go b/internal/sdk/plugin.go index 509c472..9afa0c2 100644 --- a/internal/sdk/plugin.go +++ b/internal/sdk/plugin.go @@ -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 } diff --git a/internal/sdk/skill.go b/internal/sdk/skill.go deleted file mode 100644 index 2c73248..0000000 --- a/internal/sdk/skill.go +++ /dev/null @@ -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:"-"` -} diff --git a/internal/sdk/status.go b/internal/sdk/status.go index 5772c94..5d5b2b7 100644 --- a/internal/sdk/status.go +++ b/internal/sdk/status.go @@ -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"` diff --git a/internal/skill/manager.go b/internal/skill/manager.go deleted file mode 100644 index 67eb635..0000000 --- a/internal/skill/manager.go +++ /dev/null @@ -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 "" -}