fix(plugins): restore plugin runtimes on startup + graceful shutdown

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
This commit is contained in:
mirivlad 2026-06-08 11:45:35 +08:00
parent f769daa617
commit d6e28d7b1f
2 changed files with 23 additions and 1 deletions

View File

@ -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 // Sync service
deviceID := "" deviceID := ""
_ = appCfg // will store sync settings _ = appCfg // will store sync settings
@ -292,6 +295,11 @@ func (a *App) initVault(vaultPath string) error {
// Start auto-sync loop // Start auto-sync loop
go a.autoSyncLoop() 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). // Start bridge server for browser extension integration (if enabled).
if appCfg == nil || appCfg.Vault.Bridge.Enabled { if appCfg == nil || appCfg.Vault.Bridge.Enabled {
a.startBridge(appCfg) a.startBridge(appCfg)
@ -313,6 +321,12 @@ func (a *App) closeVault() {
if a.fileWatcher != nil { if a.fileWatcher != nil {
a.fileWatcher.Stop() 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. // Stop bridge server.
if a.bridge != nil { if a.bridge != nil {
a.bridge.Stop() a.bridge.Stop()

View File

@ -180,8 +180,16 @@ func (a *App) GetPluginPanelHTML(pluginName string) (string, error) {
if a.plugins == nil { if a.plugins == nil {
return "", fmt.Errorf("plugin manager not ready") 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() { 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 continue
} }
if p.Meta.Panel == "" { if p.Meta.Panel == "" {