From d6e28d7b1f8cf4d0776b48f0723ba475e10407f4 Mon Sep 17 00:00:00 2001 From: mirivlad Date: Mon, 8 Jun 2026 11:45:35 +0800 Subject: [PATCH] fix(plugins): restore plugin runtimes on startup + graceful shutdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: initVault() called Discover() but never called SyncConfig(), InitRuntimes(), CallInitHooks(), or StartSchedulers(). On restart, plugins were discovered from disk but their config state (Installed/Enabled) was not restored and no Lua VMs were created. This caused: - Settings → Plugins showing plugins as 'not installed' after restart - Sidebar showing calendar item (from enabledSet config) but GetPluginPanelHTML failing (only checked p.Active) - Toggle working in-session but state lost on restart - closeVault() not stopping plugin schedulers/shutdown/VM (leak) Fixes: - bindings_config.go: add SyncConfig(appCfg) after Discover() - bindings_config.go: add InitRuntimes() + CallInitHooks() + StartSchedulers() after watcher start - bindings_config.go: add StopSchedulers + CallShutdownHooks + CloseRuntimes to closeVault() (before db.Close since plugins use DB) - bindings_plugins.go: GetPluginPanelHTML now checks enabledSet || p.Active, consistent with ListSystemViewsWithPlugins and ListPlugins --- cmd/verstak-gui/bindings_config.go | 14 ++++++++++++++ cmd/verstak-gui/bindings_plugins.go | 10 +++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/cmd/verstak-gui/bindings_config.go b/cmd/verstak-gui/bindings_config.go index 0668373..51cb5ae 100644 --- a/cmd/verstak-gui/bindings_config.go +++ b/cmd/verstak-gui/bindings_config.go @@ -228,6 +228,9 @@ func (a *App) initVault(vaultPath string) error { } } + // Apply installed/enabled state from config to discovered plugins + pm.SyncConfig(appCfg) + // Sync service deviceID := "" _ = appCfg // will store sync settings @@ -292,6 +295,11 @@ func (a *App) initVault(vaultPath string) error { // Start auto-sync loop go a.autoSyncLoop() + // Start plugin runtimes for enabled plugins (creates VM, loads scripts, calls on_init) + pm.InitRuntimes() + pm.CallInitHooks() + pm.StartSchedulers() + // Start bridge server for browser extension integration (if enabled). if appCfg == nil || appCfg.Vault.Bridge.Enabled { a.startBridge(appCfg) @@ -313,6 +321,12 @@ func (a *App) closeVault() { if a.fileWatcher != nil { a.fileWatcher.Stop() } + // Stop plugin runtimes (schedulers → on_shutdown → close VMs) + if a.plugins != nil { + a.plugins.StopSchedulers() + a.plugins.CallShutdownHooks() + a.plugins.CloseRuntimes() + } // Stop bridge server. if a.bridge != nil { a.bridge.Stop() diff --git a/cmd/verstak-gui/bindings_plugins.go b/cmd/verstak-gui/bindings_plugins.go index 56c28bf..dfa2495 100644 --- a/cmd/verstak-gui/bindings_plugins.go +++ b/cmd/verstak-gui/bindings_plugins.go @@ -180,8 +180,16 @@ func (a *App) GetPluginPanelHTML(pluginName string) (string, error) { if a.plugins == nil { return "", fmt.Errorf("plugin manager not ready") } + appCfg, _ := config.LoadAppConfig() + enabledSet := make(map[string]bool) + if appCfg != nil { + for _, name := range appCfg.EnabledPlugins { + enabledSet[name] = true + } + } for _, p := range a.plugins.Plugins() { - if p.Meta.Name != pluginName || !p.Active { + active := enabledSet[p.Meta.Name] || p.Active + if p.Meta.Name != pluginName || !active { continue } if p.Meta.Panel == "" {