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:
root
2026-08-02 11:40:13 +08:00
parent 1013aa0aa9
commit c7ee45d6e1
13 changed files with 10 additions and 333 deletions

View File

@ -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,

View File

@ -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,
)

View File

@ -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()
}

View File

@ -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

View File

@ -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)
}

View File

@ -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) {

View File

@ -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),

View File

@ -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"})

View File

@ -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 }

View File

@ -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:"-"`
}

View File

@ -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"`

View File

@ -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 ""
}