From 1efc1a9247336ab6fe8115e73fd2838520e47372 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 9 Aug 2026 16:32:05 +0200 Subject: [PATCH] Fixed left-over plugin activation state after deleting a plugin --- .../Services/PluginInstallService.Delete.cs | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/app/MindWork AI Studio/Tools/Services/PluginInstallService.Delete.cs b/app/MindWork AI Studio/Tools/Services/PluginInstallService.Delete.cs index e2227a83..f61451da 100644 --- a/app/MindWork AI Studio/Tools/Services/PluginInstallService.Delete.cs +++ b/app/MindWork AI Studio/Tools/Services/PluginInstallService.Delete.cs @@ -158,7 +158,10 @@ public sealed partial class PluginInstallService /// For a configuration plugin, we do not remove its providers, data sources, chat templates, /// profiles, or locked settings ourselves. The reload does that: it recognizes them as left over /// once their configuration plugin is gone, and it also deletes the related secrets from the OS - /// keyring. + /// keyring.

+ /// What the reload cannot recognize as left over is everything the user decided about the plugin + /// itself: its activation state and, for a language plugin, the language choice. Those are + /// removed here. /// /// Plugin metadata of the configuration or language plugin. /// Cancellation token for settings storage and plugin reload. @@ -175,6 +178,7 @@ public sealed partial class PluginInstallService await this.installSemaphore.WaitAsync(token); var pluginDirectory = plugin.LocalPath; var backupDirectory = string.Empty; + var wasEnabled = false; var wasChosenLanguage = false; try @@ -189,6 +193,14 @@ public sealed partial class PluginInstallService Directory.CreateDirectory(Path.GetDirectoryName(backupDirectory)!); Directory.Move(pluginDirectory, backupDirectory); + // + // Nothing removes the activation state of a plugin which is gone. Should the user install + // a plugin with the same ID again later, it would start enabled without ever having been + // switched on. We ask for removal regardless of the plugin type: a configuration plugin + // is never listed there, so this simply does nothing for it: + // + wasEnabled = this.settingsManager.ConfigurationData.EnabledPlugins.Remove(plugin.Id); + // // When the user had chosen this language plugin, the app would silently fall back to // English while the settings still point to the deleted plugin. We return the language @@ -199,9 +211,11 @@ public sealed partial class PluginInstallService { this.settingsManager.ConfigurationData.App.LanguageBehavior = LangBehavior.AUTO; this.settingsManager.ConfigurationData.App.LanguagePluginId = Guid.Empty; - await this.settingsManager.StoreSettings(); } + if (wasEnabled || wasChosenLanguage) + await this.settingsManager.StoreSettings(); + await PluginFactory.LoadAll(token); TryDeleteDirectory(backupDirectory, "local plugin delete backup", this.logger); @@ -212,7 +226,7 @@ public sealed partial class PluginInstallService { this.logger.LogError(e, $"Failed to delete {plugin.Type} plugin '{plugin.Name}' ({plugin.Id}) from '{pluginDirectory}'."); - await this.TryRestoreDeletedLocalPluginAsync(plugin, pluginDirectory, backupDirectory, wasChosenLanguage, token); + await this.TryRestoreDeletedLocalPluginAsync(plugin, pluginDirectory, backupDirectory, wasEnabled, wasChosenLanguage, token); return DeleteError(plugin, pluginDirectory, string.Format(TB("Unexpected error: {0}"), e.Message)); } finally @@ -310,20 +324,25 @@ public sealed partial class PluginInstallService } } - private async Task TryRestoreDeletedLocalPluginAsync(IAvailablePlugin plugin, string pluginDirectory, string backupDirectory, bool wasChosenLanguage, CancellationToken token) + private async Task TryRestoreDeletedLocalPluginAsync(IAvailablePlugin plugin, string pluginDirectory, string backupDirectory, bool wasEnabled, bool wasChosenLanguage, CancellationToken token) { try { if (!Directory.Exists(pluginDirectory) && Directory.Exists(backupDirectory)) Directory.Move(backupDirectory, pluginDirectory); + if (wasEnabled && !this.settingsManager.ConfigurationData.EnabledPlugins.Contains(plugin.Id)) + this.settingsManager.ConfigurationData.EnabledPlugins.Add(plugin.Id); + if (wasChosenLanguage) { this.settingsManager.ConfigurationData.App.LanguageBehavior = LangBehavior.MANUAL; this.settingsManager.ConfigurationData.App.LanguagePluginId = plugin.Id; - await this.settingsManager.StoreSettings(); } + if (wasEnabled || wasChosenLanguage) + await this.settingsManager.StoreSettings(); + // The reload restores everything the plugin configured, because it is back in place: await PluginFactory.LoadAll(token); }