fix(webui): sync plugin activation status on enable (#9156)

* fix(webui): fix plugin activation status sync issue in turn_on_plugin #9149

* fix(webui): sync plugin activation status after enabling

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Glmg
2026-07-06 21:16:14 +08:00
committed by GitHub
parent ab2502c174
commit ba7f8ebfb1
3 changed files with 100 additions and 7 deletions

View File

@@ -1295,8 +1295,7 @@ class PluginManager:
star_registry.append(metadata)
# 禁用/启用插件
if metadata.module_path in inactivated_plugins:
metadata.activated = False
metadata.activated = metadata.module_path not in inactivated_plugins
# Plugin logo path
if os.path.exists(logo_path):
@@ -1934,7 +1933,12 @@ class PluginManager:
for func_tool in self._iter_plugin_llm_tools(plugin.module_path):
func_tool.active = func_tool.name not in inactivated_llm_tools
await self.reload(plugin_name)
success, error = await self.reload(plugin_name)
if not success:
raise Exception(error or f"插件 {plugin_name} 启用失败。")
current_plugin = self.context.get_registered_star(plugin_name)
if current_plugin:
current_plugin.activated = True
async def install_plugin_from_file(
self, zip_file_path: str, ignore_version_check: bool = False

View File

@@ -1043,9 +1043,12 @@ export const useExtensionPage = () => {
};
const pluginOn = async (extension) => {
const previousActivated = extension.activated;
extension.activated = true;
try {
const res = await pluginApi.setEnabled(extension.name, true);
if (res.data.status === "error") {
extension.activated = previousActivated;
toast(res.data.message, "error");
return;
}
@@ -1054,20 +1057,25 @@ export const useExtensionPage = () => {
await checkAndPromptConflicts();
} catch (err) {
extension.activated = previousActivated;
toast(err, "error");
}
};
const pluginOff = async (extension) => {
const previousActivated = extension.activated;
extension.activated = false;
try {
const res = await pluginApi.setEnabled(extension.name, false);
if (res.data.status === "error") {
extension.activated = previousActivated;
toast(res.data.message, "error");
return;
}
toast(res.data.message, "success");
getExtensions();
await getExtensions();
} catch (err) {
extension.activated = previousActivated;
toast(err, "error");
}
};

View File

@@ -2,6 +2,7 @@ import asyncio
import json
import os
from pathlib import Path
from types import ModuleType
from typing import Any, cast
import pytest
@@ -1981,6 +1982,89 @@ async def test_uninstall_failed_plugin_without_plugin_id_in_record(
# --- reload + deactivated plugin regression tests ---
@pytest.mark.asyncio
@pytest.mark.parametrize(
("inactivated_plugins", "expected_activated"),
[
([], True),
(["data.plugins.demo_plugin.main"], False),
],
)
async def test_load_syncs_existing_metadata_activation_from_preferences(
plugin_manager_pm: PluginManager,
monkeypatch,
inactivated_plugins: list[str],
expected_activated: bool,
):
"""Existing plugin metadata activation follows persisted preferences."""
_clear_star_runtime_state()
plugin_name = "demo_plugin"
module_path = f"data.plugins.{plugin_name}.main"
metadata = star_manager_module.StarMetadata(
name=plugin_name,
author="AstrBot Team",
desc="Demo plugin",
version="1.0.0",
root_dir_name=plugin_name,
module_path=module_path,
activated=False,
)
star_manager_module.star_map[module_path] = metadata
star_manager_module.star_registry.append(metadata)
preferences = {
"inactivated_plugins": inactivated_plugins,
"inactivated_llm_tools": [],
"alter_cmd": {},
}
async def mock_global_get(key, default=None):
return preferences.get(key, default)
async def mock_import_plugin_with_dependency_recovery(
path,
module_str,
root_dir_name,
requirements_path,
*,
reserved=False,
):
del module_str, root_dir_name, requirements_path, reserved
assert path == module_path
return ModuleType(module_path)
async def mock_sync_command_configs():
return None
monkeypatch.setattr(star_manager_module.sp, "global_get", mock_global_get)
monkeypatch.setattr(
plugin_manager_pm,
"_get_plugin_modules",
lambda: [{"pname": plugin_name, "module": "main"}],
)
monkeypatch.setattr(
plugin_manager_pm,
"_import_plugin_with_dependency_recovery",
mock_import_plugin_with_dependency_recovery,
)
monkeypatch.setattr(plugin_manager_pm, "_load_plugin_metadata", lambda **_: None)
monkeypatch.setattr(
star_manager_module,
"sync_command_configs",
mock_sync_command_configs,
)
try:
success, error = await plugin_manager_pm.load(
specified_module_path=module_path,
)
assert success is True
assert error is None
assert metadata.activated is expected_activated
finally:
_clear_star_runtime_state()
@pytest.mark.asyncio
async def test_reload_deactivated_plugin_preserves_tools(
plugin_manager_pm: PluginManager, monkeypatch
@@ -2156,9 +2240,6 @@ async def test_turn_on_plugin_after_deactivated_reload_reactivates_tools(
async def mock_reload(plugin_name_arg):
assert plugin_name_arg == plugin_name
# Simulate what load() does: re-register with activated=True
# since it's no longer in inactivated_plugins
plugin.activated = True
return True, None
monkeypatch.setattr(star_manager_module.sp, "global_get", mock_global_get)