From 796a48dae5977d89073574c475f1f02b2f0fb05e Mon Sep 17 00:00:00 2001 From: root Date: Wed, 29 Jul 2026 15:07:32 +0800 Subject: [PATCH] =?UTF-8?q?plugin=20disable=20system:=20kernel=E2=86=92SDK?= =?UTF-8?q?=20PluginManager=20+=20WebUI/CLI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New disabled_plugins table (name, disabled_at, disabled_by) - SDK.PluginManager interface: DisablePlugin/EnablePlugin/ListDisabledPlugins - Registry implements PluginManager, wired into PluginSDK - WebUI: POST /api/v1/plugins//disable|enable + plugins page with toggle - Disabling webui shows confirmation dialog - CLI: /plugin disable / /plugin enable - pluginmgr removePlugin sync-cleanup from disabled_plugins table --- internal/config/registry.go | 52 +++++++++++++++ internal/plugin/registry.go | 75 ++++++++++++++++++--- internal/plugins/cli/plugin.go | 95 ++++++++++++++++++++++----- internal/plugins/pluginmgr/plugin.go | 5 ++ internal/plugins/webui/dashboard.html | 66 ++++++++++++++++--- internal/plugins/webui/handler.go | 58 ++++++++++++++-- internal/plugins/webui/plugin.go | 1 + internal/sdk/plugin.go | 26 +++++++- 8 files changed, 339 insertions(+), 39 deletions(-) diff --git a/internal/config/registry.go b/internal/config/registry.go index 7292001..c32c825 100644 --- a/internal/config/registry.go +++ b/internal/config/registry.go @@ -53,6 +53,58 @@ func (r *ConfigRegistry) initCoreTable() { key TEXT PRIMARY KEY, value TEXT NOT NULL )`) + r.db.Exec(`CREATE TABLE IF NOT EXISTS disabled_plugins ( + name TEXT PRIMARY KEY, + disabled_at TEXT NOT NULL, + disabled_by TEXT NOT NULL DEFAULT '' + )`) +} + +func (r *ConfigRegistry) AddDisabledPlugin(name, by string) error { + r.mu.Lock() + defer r.mu.Unlock() + _, err := r.db.Exec(`INSERT OR REPLACE INTO disabled_plugins (name, disabled_at, disabled_by) VALUES (?, datetime('now'), ?)`, name, by) + return err +} + +func (r *ConfigRegistry) RemoveDisabledPlugin(name string) error { + r.mu.Lock() + defer r.mu.Unlock() + _, err := r.db.Exec(`DELETE FROM disabled_plugins WHERE name = ?`, name) + return err +} + +type DisabledPluginInfo struct { + Name string `json:"name"` + DisabledAt string `json:"disabled_at"` + DisabledBy string `json:"disabled_by"` +} + +func (r *ConfigRegistry) ListDisabledPlugins() ([]DisabledPluginInfo, error) { + r.mu.RLock() + defer r.mu.RUnlock() + rows, err := r.db.Query(`SELECT name, disabled_at, disabled_by FROM disabled_plugins ORDER BY name`) + if err != nil { + return nil, err + } + defer rows.Close() + var list []DisabledPluginInfo + for rows.Next() { + var info DisabledPluginInfo + if err := rows.Scan(&info.Name, &info.DisabledAt, &info.DisabledBy); err != nil { + return nil, err + } + list = append(list, info) + } + return list, rows.Err() +} + +func (r *ConfigRegistry) IsPluginDisabled(name string) bool { + r.mu.RLock() + defer r.mu.RUnlock() + var count int + r.db.QueryRow(`SELECT COUNT(*) FROM disabled_plugins WHERE name = ?`, name).Scan(&count) + return count > 0 } func (r *ConfigRegistry) ensurePluginTable(name string) { diff --git a/internal/plugin/registry.go b/internal/plugin/registry.go index d127972..04ea414 100644 --- a/internal/plugin/registry.go +++ b/internal/plugin/registry.go @@ -22,7 +22,7 @@ import ( type NativeFactory func(name string, config map[string]interface{}) (sdk.Plugin, error) -// PluginMeta 插件显示名称元数据。 +// PluginMeta 插件显示名称元数据,来源于 plg.json / RegisterPluginMeta。 type PluginMeta struct { NameZh string `json:"name_zh"` NameEn string `json:"name_en"` @@ -189,6 +189,7 @@ func (r *Registry) buildSDK(name string) *sdk.PluginSDK { RegAPI: regAPI, RegOutput: regOutput, RegInput: regInput, + PluginMgr: r, }) } @@ -281,11 +282,7 @@ func (r *Registry) isDisabled(name string) bool { if r.cfgReg == nil { return false } - v, err := r.cfgReg.PluginConfig(name).Get("disabled") - if err != nil || v == nil { - return false - } - return fmt.Sprint(v) == "true" + return r.cfgReg.IsPluginDisabled(name) } func (r *Registry) loadOne(plgDir, name string) bool { @@ -437,7 +434,7 @@ func (r *Registry) IsDisabled(name string) bool { func (r *Registry) Enable(name string) error { if r.cfgReg != nil { - r.cfgReg.PluginConfig(name).Set("disabled", "false") + r.cfgReg.RemoveDisabledPlugin(name) } r.mu.Lock() delete(r.knownDisabled, name) @@ -473,12 +470,74 @@ func (r *Registry) Disable(name string) error { } if r.cfgReg != nil { - r.cfgReg.PluginConfig(name).Set("disabled", "true") + r.cfgReg.AddDisabledPlugin(name, "system") } log.Printf("[plugin] disabled: %s", name) return nil } +// ---- PluginManager interface ---- + +func (r *Registry) ListLoadedPlugins() []string { return r.List() } + +func (r *Registry) ListDisabledPlugins() []sdk.DisabledPluginInfo { + if r.cfgReg == nil { + return nil + } + list, err := r.cfgReg.ListDisabledPlugins() + if err != nil { + return nil + } + result := make([]sdk.DisabledPluginInfo, len(list)) + for i, v := range list { + result[i] = sdk.DisabledPluginInfo(v) + } + return result +} + +func (r *Registry) IsPluginDisabled(name string) bool { return r.isDisabled(name) } + +func (r *Registry) DisablePlugin(name, by string) error { + // Check not disabling self if running + if r.cfgReg != nil { + // If already disabled, no-op + if r.cfgReg.IsPluginDisabled(name) { + return fmt.Errorf("plugin %s already disabled", name) + } + } + + r.mu.Lock() + p, ok := r.plugins[name] + if ok { + if err := p.Stop(); err != nil { + log.Printf("[plugin] stop %s for disable: %v", name, err) + } + delete(r.plugins, name) + for i, inst := range r.instances { + if inst.Name() == name { + r.instances = append(r.instances[:i], r.instances[i+1:]...) + break + } + } + } + r.knownDisabled[name] = true + r.mu.Unlock() + + if r.toolCleaner != nil { + r.toolCleaner.UnregisterPluginTools(name) + } + + if r.cfgReg != nil { + r.cfgReg.AddDisabledPlugin(name, by) + } + log.Printf("[plugin] disabled: %s (by %s)", name, by) + return nil +} + +func (r *Registry) EnablePlugin(name string) error { return r.Enable(name) } + +func (r *Registry) ReloadPlugins() (string, error) { return r.Reload(r.plgDir) } + // ListKnown 返回所有已知插件(已加载 + 已禁用 + 已安装但未加载)。 func (r *Registry) ListKnown() []string { r.mu.RLock() diff --git a/internal/plugins/cli/plugin.go b/internal/plugins/cli/plugin.go index dc5c911..01fb925 100644 --- a/internal/plugins/cli/plugin.go +++ b/internal/plugins/cli/plugin.go @@ -221,7 +221,7 @@ func (p *Plugin) handleBuiltin(conn net.Conn, line string, s *sdk.PluginSDK) boo case "/settings": p.cmdSettings(conn, parts) case "/plugin": - p.cmdPlugin(conn, parts) + p.cmdPlugin(conn, parts, s) case "/memory": p.cmdMemory(conn, parts, s) case "/knowledge": @@ -244,9 +244,11 @@ func (p *Plugin) cmdHelp(conn net.Conn) { /settings 列出所有配置 /settings set 修改配置项 /settings core.llm 按前缀筛选 - /plugin list 列出已安装插件 + /plugin list 列出所有插件 /plugin install 安装插件(需回环网络) /plugin remove 卸载插件 + /plugin disable 禁用插件 + /plugin enable 启用插件 /plugin info 查看插件详情 /memory query <关键词> 查询图记忆 /knowledge 列出知识库 @@ -347,26 +349,49 @@ func (p *Plugin) cmdSettings(conn net.Conn, parts []string) { // ======== /plugin ======== -func (p *Plugin) cmdPlugin(conn net.Conn, parts []string) { +func (p *Plugin) cmdPlugin(conn net.Conn, parts []string, s *sdk.PluginSDK) { if len(parts) < 2 { - writeLine(conn, map[string]interface{}{"type": "response", "content": "用法: /plugin list|install |remove |info "}) + writeLine(conn, map[string]interface{}{"type": "response", "content": "用法: /plugin list|install |remove |info |disable |enable "}) return } + pmgr := s.PluginMgr() + switch parts[1] { case "list": - 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 } - names := pluginReg.List() - if len(names) == 0 { - writeLine(conn, map[string]interface{}{"type": "response", "content": "无已加载插件"}) + names := pmgr.ListLoadedPlugins() + disabled := pmgr.ListDisabledPlugins() + disabledNames := make(map[string]bool) + for _, d := range disabled { + disabledNames[d.Name] = true + } + all := make(map[string]string) + for _, n := range names { + all[n] = "\033[32m已加载\033[0m" + } + for _, d := range disabled { + if _, ok := all[d.Name]; !ok { + all[d.Name] = "\033[31m已禁用\033[0m" + } else { + all[d.Name] = "\033[32m已加载\033[0m (禁用将在重启后生效)" + } + } + if len(all) == 0 { + writeLine(conn, map[string]interface{}{"type": "response", "content": "无插件"}) return } + var lines []string + for name, status := range all { + lines = append(lines, fmt.Sprintf(" %s %s", name, status)) + } + sort.Strings(lines) writeLine(conn, map[string]interface{}{ "type": "response", - "content": fmt.Sprintf("已加载插件 (%d):\n %s", len(names), strings.Join(names, "\n ")), + "content": fmt.Sprintf("插件 (%d):\n%s", len(all), strings.Join(lines, "\n")), }) case "install": @@ -394,6 +419,10 @@ func (p *Plugin) cmdPlugin(conn net.Conn, parts []string) { writeLine(conn, map[string]interface{}{"type": "error", "error": err.Error()}) return } + // 同步清理禁用表 + if pmgr != nil { + pmgr.EnablePlugin(name) + } writeLine(conn, map[string]interface{}{"type": "response", "content": fmt.Sprintf("插件 %s 已删除,执行 /plugin reload 生效", name)}) case "info": @@ -407,24 +436,58 @@ func (p *Plugin) cmdPlugin(conn net.Conn, parts []string) { } plg := pluginReg.Get(parts[2]) if plg == nil { - writeLine(conn, map[string]interface{}{"type": "response", "content": fmt.Sprintf("插件 %q 未加载", parts[2])}) + if pmgr != nil && pmgr.IsPluginDisabled(parts[2]) { + writeLine(conn, map[string]interface{}{"type": "response", "content": fmt.Sprintf("插件 %q 已禁用", parts[2])}) + return + } + writeLine(conn, map[string]interface{}{"type": "response", "content": fmt.Sprintf("插件 %q 未安装", parts[2])}) return } writeLine(conn, map[string]interface{}{"type": "response", "content": fmt.Sprintf("名称: %s\n状态: 已加载", plg.Name())}) - case "reload": - if pluginReg == nil { - writeLine(conn, map[string]interface{}{"type": "error", "error": "plugin registry not available"}) + case "disable": + if len(parts) < 3 { + writeLine(conn, map[string]interface{}{"type": "response", "content": "用法: /plugin disable "}) return } - if _, err := pluginReg.Reload(pluginDir); err != nil { + if pmgr == nil { + writeLine(conn, map[string]interface{}{"type": "error", "error": "plugin manager not available"}) + return + } + if err := pmgr.DisablePlugin(parts[2], "cli"); err != nil { + writeLine(conn, map[string]interface{}{"type": "error", "error": err.Error()}) + return + } + writeLine(conn, map[string]interface{}{"type": "response", "content": fmt.Sprintf("插件 %s 已禁用", parts[2])}) + + case "enable": + if len(parts) < 3 { + writeLine(conn, map[string]interface{}{"type": "response", "content": "用法: /plugin enable "}) + return + } + if pmgr == nil { + writeLine(conn, map[string]interface{}{"type": "error", "error": "plugin manager not available"}) + return + } + if err := pmgr.EnablePlugin(parts[2]); err != nil { + writeLine(conn, map[string]interface{}{"type": "error", "error": err.Error()}) + return + } + writeLine(conn, map[string]interface{}{"type": "response", "content": fmt.Sprintf("插件 %s 已启用", parts[2])}) + + case "reload": + if pmgr == nil { + writeLine(conn, map[string]interface{}{"type": "error", "error": "plugin manager not available"}) + return + } + if _, err := pmgr.ReloadPlugins(); err != nil { writeLine(conn, map[string]interface{}{"type": "error", "error": err.Error()}) return } writeLine(conn, map[string]interface{}{"type": "response", "content": "插件已重载"}) default: - writeLine(conn, map[string]interface{}{"type": "response", "content": "未知: /plugin " + parts[1] + "。支持: list, install, remove, info, reload"}) + writeLine(conn, map[string]interface{}{"type": "response", "content": "未知: /plugin " + parts[1] + "。支持: list, install, remove, info, disable, enable, reload"}) } } diff --git a/internal/plugins/pluginmgr/plugin.go b/internal/plugins/pluginmgr/plugin.go index da4f0de..460b62c 100644 --- a/internal/plugins/pluginmgr/plugin.go +++ b/internal/plugins/pluginmgr/plugin.go @@ -455,6 +455,11 @@ func (p *Plugin) removePlugin(name string) (interface{}, error) { return map[string]interface{}{"error": err.Error()}, nil } + // 同步清理禁用表 + if Reg != nil { + Reg.EnablePlugin(name) + } + return map[string]interface{}{ "status": "removed", "name": name, diff --git a/internal/plugins/webui/dashboard.html b/internal/plugins/webui/dashboard.html index 445283a..7fdba08 100644 --- a/internal/plugins/webui/dashboard.html +++ b/internal/plugins/webui/dashboard.html @@ -107,6 +107,7 @@ tr:hover td { background:var(--bg-hover) } .badge-red { background:#7f1d1d; color:#fca5a5 } .badge-yellow { background:#713f12; color:#fde68a } .badge-blue { background:#1e3a5f; color:#93c5fd } +.badge-gray { background:#374151; color:#9ca3af } .btn { padding:6px 14px; border-radius:6px; border:none; font-size:12px; cursor:pointer; font-weight:500; transition:all .15s } .btn-primary { background:var(--accent); color:#fff } .btn-primary:hover { filter:brightness(1.1) } @@ -312,6 +313,7 @@ let state = { settings: {}, meta: {}, pluginMeta: {}, + disabledPlugins: [], settingsPlugins: ['core'], selectedSection: 'core', messages: [], @@ -439,6 +441,7 @@ async function renderAll() { state.meta = s.meta || {}; state.settingsPlugins = s.plugins || ['core']; state.pluginMeta = s.plugin_meta || {}; + state.disabledPlugins = s.disabled_plugins || []; } catch(e) {} try { state.installedPlugins = await api('/plugins') } catch(e) {} try { await loadTerminals() } catch(e) {} @@ -1223,17 +1226,38 @@ function renderPlugins() { + '' + '
' + '
'; + var disabledNames = {}; + (state.disabledPlugins || []).forEach(function(d) { disabledNames[d.name] = d }); var installedNames = (state.installedPlugins || []).map(function(p) { return p.name }); - html += '

' + __('已加载插件','Loaded Plugins') + ' (' + plugins.length + ')

'; - if (plugins.length === 0) { - html += '

' + __('暂无已加载插件','No loaded plugins') + '

'; + var allPluginNames = {}; + plugins.forEach(function(p) { allPluginNames[p.name] = true }); + state.disabledPlugins.forEach(function(d) { allPluginNames[d.name] = true }); + html += '

' + __('所有插件','All Plugins') + ' (' + Object.keys(allPluginNames).length + ')

'; + var names = Object.keys(allPluginNames).sort(); + if (names.length === 0) { + html += '

' + __('暂无插件','No plugins') + '

'; } else { html += ''; - plugins.forEach(function(p) { - var isExternal = installedNames.indexOf(p.name) >= 0; - html += '' - + '' - + ''; + names.forEach(function(name) { + var isExternal = installedNames.indexOf(name) >= 0; + var isDisabled = disabledNames[name]; + var loaded = plugins.some(function(p) { return p.name === name }); + var statusHtml = loaded && !isDisabled ? '' + __('已加载','Loaded') + '' + : loaded && isDisabled ? '' + __('运行中(禁用待生效)','Running (disable pending)') + '' + : isDisabled ? '' + __('已禁用','Disabled') + '' + : '' + __('未加载','Not Loaded') + ''; + var actionsHtml = ''; + if (loaded) { + actionsHtml += ''; + } else { + actionsHtml += ''; + } + if (isExternal) { + actionsHtml += ''; + } + html += '' + + '' + + ''; }); html += '
' + __('名称','Name') + '' + __('状态','Status') + '' + __('操作','Actions') + '
' + escHtml(p.name) + '' + __('已加载','Loaded') + '' + (isExternal ? '' : '' + __('内置','Built-in') + '') + '
' + escHtml(name) + '' + statusHtml + '' + actionsHtml + '
'; } @@ -1319,6 +1343,32 @@ async function removePlugin(name) { } catch(e) { toast(__('卸载失败: ','Unload failed: ') + e.message, true) } } +async function disablePlugin(name) { + if (name === 'webui') { + var r = confirm(__('禁用 WebUI 后将无法通过 URL:端口访问此管理面板,若要重新启用需要通过 CLI 命令 /plugin enable webui 恢复。\n\n确定要禁用吗?','Disabling WebUI will make this management panel inaccessible via URL:port. To re-enable, use CLI command /plugin enable webui.\n\nAre you sure?')); + if (!r) return; + } + try { + var r = await api('/plugins/' + encodeURIComponent(name) + '/disable', { method: 'POST' }); + toast(__('已禁用: ','Disabled: ') + name); + state.kernel = await api('/kernel'); + var s = await api('/settings'); + state.disabledPlugins = s.disabled_plugins || []; + renderPlugins(); + } catch(e) { toast(__('禁用失败: ','Disable failed: ') + e.message, true) } +} + +async function enablePlugin(name) { + try { + var r = await api('/plugins/' + encodeURIComponent(name) + '/enable', { method: 'POST' }); + toast(__('已启用: ','Enabled: ') + name); + state.kernel = await api('/kernel'); + var s = await api('/settings'); + state.disabledPlugins = s.disabled_plugins || []; + renderPlugins(); + } catch(e) { toast(__('启用失败: ','Enable failed: ') + e.message, true) } +} + async function reloadPlugins() { try { var r = await api('/plugins/reload', { method: 'POST' }); diff --git a/internal/plugins/webui/handler.go b/internal/plugins/webui/handler.go index 7f2c069..6c67449 100644 --- a/internal/plugins/webui/handler.go +++ b/internal/plugins/webui/handler.go @@ -27,6 +27,7 @@ import ( "gitcode.com/JianFeeeee/HomeAgent/internal/memory" "gitcode.com/JianFeeeee/HomeAgent/internal/memory/text" "gitcode.com/JianFeeeee/HomeAgent/internal/plugin" + 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" @@ -61,6 +62,7 @@ type Handler struct { tracker *tracker.Tracker cfgReg *internalConfig.ConfigRegistry pluginReg *plugin.Registry + pluginMgr sdk.PluginManager eventBus *events.Bus statusProvider agentCore.StatusProvider providerMgr *agentAPI.ProviderManager @@ -102,6 +104,8 @@ type termState struct { created time.Time } +func (h *Handler) SetPluginMgr(mgr sdk.PluginManager) { h.pluginMgr = mgr } + const maxChatHistory = 200 const maxCmdHistory = 100 const maxTerminals = 50 @@ -1106,12 +1110,17 @@ func (h *Handler) handleSettings(w http.ResponseWriter, r *http.Request) { plugins = append(plugins, "plugin."+p) } } + var disabledPlugins []sdk.DisabledPluginInfo + if h.pluginMgr != nil { + disabledPlugins = h.pluginMgr.ListDisabledPlugins() + } sort.Strings(plugins) writeJSON(w, http.StatusOK, map[string]interface{}{ - "settings": values, - "meta": meta, - "plugins": plugins, - "plugin_meta": pm, + "settings": values, + "meta": meta, + "plugins": plugins, + "plugin_meta": pm, + "disabled_plugins": disabledPlugins, }) case http.MethodPut: var body struct { @@ -1417,6 +1426,15 @@ func (h *Handler) handlePluginByID(w http.ResponseWriter, r *http.Request) { path := strings.TrimPrefix(r.URL.Path, "/api/v1/plugins/") path = strings.TrimSuffix(path, "/") + if path == "disabled" && r.Method == http.MethodGet { + if h.pluginMgr == nil { + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "plugin manager not available"}) + return + } + writeJSON(w, http.StatusOK, map[string]interface{}{"disabled": h.pluginMgr.ListDisabledPlugins()}) + return + } + if path == "reload" && r.Method == http.MethodPost { if h.pluginReg == nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "plugin registry not available"}) @@ -1436,6 +1454,38 @@ func (h *Handler) handlePluginByID(w http.ResponseWriter, r *http.Request) { return } + if idx := strings.LastIndex(path, "/"); idx > 0 { + name := path[:idx] + action := path[idx+1:] + if r.Method == http.MethodPost { + switch action { + case "disable": + if h.pluginMgr == nil { + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "plugin manager not available"}) + return + } + if err := h.pluginMgr.DisablePlugin(name, "webui"); err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + writeJSON(w, http.StatusOK, map[string]string{"status": "disabled"}) + return + + case "enable": + if h.pluginMgr == nil { + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "plugin manager not available"}) + return + } + if err := h.pluginMgr.EnablePlugin(name); err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + writeJSON(w, http.StatusOK, map[string]string{"status": "enabled"}) + return + } + } + } + switch r.Method { case http.MethodGet: h.proxyToPluginmgr(w, r, "/plugins/"+path) diff --git a/internal/plugins/webui/plugin.go b/internal/plugins/webui/plugin.go index bf82728..0ac6c81 100644 --- a/internal/plugins/webui/plugin.go +++ b/internal/plugins/webui/plugin.go @@ -195,6 +195,7 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { }) h := NewHandler(p.sup, p.mem, p.sk, p.lua, p.cfg, p.iom, p.tm, p.ks, p.tr, p.cr, p.pr, p.evBus, p.statusProvider, p.providerMgr, p.baseAPIKey) + h.SetPluginMgr(s.PluginMgr()) p.handler = h h.RegisterRoutes(p.mux) diff --git a/internal/sdk/plugin.go b/internal/sdk/plugin.go index 985034b..df6f02d 100644 --- a/internal/sdk/plugin.go +++ b/internal/sdk/plugin.go @@ -51,13 +51,31 @@ type OutputChannelRegistrar = pubsdk.OutputChannelRegistrar type InputChannelRegistrar = pubsdk.InputChannelRegistrar type ChannelDef = pubsdk.ChannelDef +type DisabledPluginInfo struct { + Name string `json:"name"` + DisabledAt string `json:"disabled_at"` + DisabledBy string `json:"disabled_by"` +} + +type PluginManager interface { + ListLoadedPlugins() []string + ListDisabledPlugins() []DisabledPluginInfo + IsPluginDisabled(name string) bool + DisablePlugin(name, by string) error + EnablePlugin(name string) error + ReloadPlugins() (string, error) +} + type PluginSDK struct { *pubsdk.PluginSDK - iom *agentIO.IOManager - eventBus *events.Bus - logger *log.Logger + iom *agentIO.IOManager + eventBus *events.Bus + logger *log.Logger + pluginMgr PluginManager } +func (s *PluginSDK) PluginMgr() PluginManager { return s.pluginMgr } + // ioAdapter 桥接 IOManager 到公共 SDK 的 IOInjector 接口, // 确保外部插件通过 s.InjectText() 等方法的调用能被路由到内核 IO 层。 type ioAdapter struct{ iom *agentIO.IOManager } @@ -95,6 +113,7 @@ type SDKConfig struct { RegAPI APIRegistrar RegOutput OutputChannelRegistrar RegInput InputChannelRegistrar + PluginMgr PluginManager } func New(name string, cfg SDKConfig) *PluginSDK { @@ -115,6 +134,7 @@ func New(name string, cfg SDKConfig) *PluginSDK { iom: cfg.IOManager, eventBus: cfg.EventBus, logger: log.Default(), + pluginMgr: cfg.PluginMgr, } }