refactor: migrate built-in plugins to SDK-only interface

- Six-phase plan complete: webui/cli/healthcheck/pluginmgr/clawhubadapter
  now interact with the kernel exclusively via internal/sdk interfaces;
  all Configure() calls and package-level global injection removed
- buildSDK in internal/plugin/registry.go is the single assembly point
- Add internal/sdk/events.go exporting event types/constants
- Fix ProviderManager cooldown sharing: LuaAdaptedProvider.Name() now
  returns the source name instead of lua_<adapter>, so multiple sources
  sharing an adapter (single script load via shared VM AdapterCache) no
  longer share failure-cooldown state
- Verified: build/vet/tests green, deployed to homeagent.service with
  full plugin capability testing via local OpenAI-compatible mock
This commit is contained in:
JianFeeeee
2026-08-01 12:17:17 +08:00
parent 74d30915ea
commit 0d74efd0ea
47 changed files with 1515 additions and 1137 deletions

View File

@ -12,8 +12,6 @@ import (
"strings"
"sync"
agentCore "gitcode.com/JianFeeeee/HomeAgent/internal/agent/core"
internalConfig "gitcode.com/JianFeeeee/HomeAgent/internal/config"
"gitcode.com/JianFeeeee/HomeAgent/internal/plugin"
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
)
@ -21,22 +19,6 @@ import (
// DefaultSocket 由 main.go 在 Load() 前设置,覆盖默认 socket 路径。
var DefaultSocket string
// 以下通过 Configure() 注入内核依赖
var (
pluginReg *plugin.Registry
cfgReg *internalConfig.ConfigRegistry
statusProv agentCore.StatusProvider
pluginDir string
)
// Configure 由 main.go 在 Load() 前调用,注入内核依赖供结构化命令使用。
func Configure(pr *plugin.Registry, cr *internalConfig.ConfigRegistry, sp agentCore.StatusProvider, pDir string) {
pluginReg = pr
cfgReg = cr
statusProv = sp
pluginDir = pDir
}
func init() {
plugin.RegisterPluginMeta("cli", "CLI", "CLI")
plugin.RegisterFactory("cli", func(name string, config map[string]interface{}) (sdk.Plugin, error) {
@ -189,18 +171,16 @@ func (p *Plugin) cliAPIKey(s *sdk.PluginSDK) string {
}
}
}
return p.webuiAPIKey()
return p.webuiAPIKey(s)
}
func (p *Plugin) webuiAPIKey() string {
if cfgReg == nil {
func (p *Plugin) webuiAPIKey(s *sdk.PluginSDK) string {
if s == nil {
return ""
}
ps := cfgReg.PluginConfig("webui")
if v, _ := ps.Get("api_key"); v != nil {
if s, ok := v.(string); ok {
return s
}
v, _ := s.Settings().GetPlugin("webui", "api_key")
if k, ok := v.(string); ok {
return k
}
return ""
}
@ -215,11 +195,11 @@ func (p *Plugin) handleBuiltin(conn net.Conn, line string, s *sdk.PluginSDK) boo
case "/help":
p.cmdHelp(conn)
case "/status":
p.cmdStatus(conn)
p.cmdStatus(conn, s)
case "/kernel":
p.cmdKernel(conn)
p.cmdKernel(conn, s)
case "/settings":
p.cmdSettings(conn, parts)
p.cmdSettings(conn, parts, s)
case "/plugin":
p.cmdPlugin(conn, parts, s)
case "/memory":
@ -227,7 +207,7 @@ func (p *Plugin) handleBuiltin(conn net.Conn, line string, s *sdk.PluginSDK) boo
case "/knowledge":
p.cmdKnowledge(conn, s)
case "/agents":
p.cmdAgents(conn)
p.cmdAgents(conn, s)
default:
return false
}
@ -260,12 +240,13 @@ func (p *Plugin) cmdHelp(conn net.Conn) {
// ======== /status ========
func (p *Plugin) cmdStatus(conn net.Conn) {
if statusProv == nil {
func (p *Plugin) cmdStatus(conn net.Conn, s *sdk.PluginSDK) {
st := s.Status()
if st == nil {
writeLine(conn, map[string]interface{}{"type": "error", "error": "status provider not available"})
return
}
ks := statusProv.GetKernelStatus()
ks := st.GetKernelStatus()
llmStatus := "不可用"
if ks.LLM.Available {
@ -294,19 +275,21 @@ func (p *Plugin) cmdStatus(conn net.Conn) {
// ======== /kernel ========
func (p *Plugin) cmdKernel(conn net.Conn) {
if statusProv == nil {
func (p *Plugin) cmdKernel(conn net.Conn, s *sdk.PluginSDK) {
st := s.Status()
if st == nil {
writeLine(conn, map[string]interface{}{"type": "error", "error": "status provider not available"})
return
}
data, _ := json.MarshalIndent(statusProv.GetKernelStatus(), "", " ")
data, _ := json.MarshalIndent(st.GetKernelStatus(), "", " ")
writeLine(conn, map[string]interface{}{"type": "response", "content": string(data)})
}
// ======== /settings ========
func (p *Plugin) cmdSettings(conn net.Conn, parts []string) {
if cfgReg == nil {
func (p *Plugin) cmdSettings(conn net.Conn, parts []string, s *sdk.PluginSDK) {
sett := s.Settings()
if sett == nil {
writeLine(conn, map[string]interface{}{"type": "error", "error": "config registry not available"})
return
}
@ -318,7 +301,7 @@ func (p *Plugin) cmdSettings(conn net.Conn, parts []string) {
}
key := parts[2]
val := strings.Join(parts[3:], " ")
if err := cfgReg.Set(key, val); err != nil {
if err := sett.SetCore(key, val); err != nil {
writeLine(conn, map[string]interface{}{"type": "error", "error": err.Error()})
return
}
@ -330,7 +313,7 @@ func (p *Plugin) cmdSettings(conn net.Conn, parts []string) {
if len(parts) >= 2 {
prefix = parts[1]
}
keys := cfgReg.List(prefix)
keys, _ := sett.ListCore(prefix)
sort.Strings(keys)
if len(keys) == 0 {
writeLine(conn, map[string]interface{}{"type": "response", "content": "无匹配配置项"})
@ -338,7 +321,7 @@ func (p *Plugin) cmdSettings(conn net.Conn, parts []string) {
}
var lines []string
for _, k := range keys {
v, _ := cfgReg.Get(k)
v, _ := sett.GetCore(k)
lines = append(lines, fmt.Sprintf(" %s = %v", k, v))
}
writeLine(conn, map[string]interface{}{
@ -409,20 +392,22 @@ func (p *Plugin) cmdPlugin(conn net.Conn, parts []string, s *sdk.PluginSDK) {
writeLine(conn, map[string]interface{}{"type": "response", "content": "用法: /plugin remove <name>"})
return
}
if pmgr == nil {
writeLine(conn, map[string]interface{}{"type": "error", "error": "plugin manager not available"})
return
}
name := parts[2]
if pluginDir == "" {
dir := pmgr.PluginDir()
if dir == "" {
writeLine(conn, map[string]interface{}{"type": "error", "error": "plugin dir not configured"})
return
}
dir := filepath.Join(pluginDir, name)
if err := os.RemoveAll(dir); err != nil {
if err := os.RemoveAll(filepath.Join(dir, name)); err != nil {
writeLine(conn, map[string]interface{}{"type": "error", "error": err.Error()})
return
}
// 同步清理禁用表
if pmgr != nil {
pmgr.EnablePlugin(name)
}
_ = pmgr.EnablePlugin(name)
writeLine(conn, map[string]interface{}{"type": "response", "content": fmt.Sprintf("插件 %s 已删除,执行 /plugin reload 生效", name)})
case "info":
@ -430,20 +415,33 @@ func (p *Plugin) cmdPlugin(conn net.Conn, parts []string, s *sdk.PluginSDK) {
writeLine(conn, map[string]interface{}{"type": "response", "content": "用法: /plugin info <name>"})
return
}
if pluginReg == nil {
writeLine(conn, map[string]interface{}{"type": "error", "error": "plugin registry not available"})
if pmgr == nil {
writeLine(conn, map[string]interface{}{"type": "error", "error": "plugin manager not available"})
return
}
plg := pluginReg.Get(parts[2])
if plg == nil {
if pmgr != nil && pmgr.IsPluginDisabled(parts[2]) {
writeLine(conn, map[string]interface{}{"type": "response", "content": fmt.Sprintf("插件 %q 已禁用", parts[2])})
return
name := parts[2]
metas := pmgr.PluginMetas()
meta, hasMeta := metas[name]
loaded := false
for _, n := range pmgr.ListLoadedPlugins() {
if n == name {
loaded = true
break
}
writeLine(conn, map[string]interface{}{"type": "response", "content": fmt.Sprintf("插件 %q 未安装", parts[2])})
}
if loaded {
display := name
if hasMeta && meta.NameZh != "" {
display = meta.NameZh
}
writeLine(conn, map[string]interface{}{"type": "response", "content": fmt.Sprintf("名称: %s (%s)\n状态: 已加载", display, name)})
return
}
writeLine(conn, map[string]interface{}{"type": "response", "content": fmt.Sprintf("名称: %s\n状态: 已加载", plg.Name())})
if pmgr.IsPluginDisabled(name) {
writeLine(conn, map[string]interface{}{"type": "response", "content": fmt.Sprintf("插件 %q 已禁用", name)})
return
}
writeLine(conn, map[string]interface{}{"type": "response", "content": fmt.Sprintf("插件 %q 未安装", name)})
case "disable":
if len(parts) < 3 {
@ -541,12 +539,13 @@ func (p *Plugin) cmdKnowledge(conn net.Conn, s *sdk.PluginSDK) {
// ======== /agents ========
func (p *Plugin) cmdAgents(conn net.Conn) {
if statusProv == nil {
func (p *Plugin) cmdAgents(conn net.Conn, s *sdk.PluginSDK) {
st := s.Status()
if st == nil {
writeLine(conn, map[string]interface{}{"type": "error", "error": "status provider not available"})
return
}
ks := statusProv.GetKernelStatus()
ks := st.GetKernelStatus()
data, _ := json.MarshalIndent(map[string]string{"agent_id": ks.AgentID}, "", " ")
writeLine(conn, map[string]interface{}{"type": "response", "content": string(data)})
}