feat(plugin_system): 引入组件局部状态管理并重构插件API
引入了基于 `stream_id` 的组件局部状态管理机制。这允许在不修改全局配置的情况下,为特定会话临时启用或禁用组件,提供了更高的灵活性。 全面重构了 `plugin_manage_api`,提供了更强大和稳定的插件管理功能: - 新增 `reload_all_plugins` 和 `get_system_report` API,方便进行批量重载和系统状态诊断。 - 增强了组件卸载逻辑,确保在插件移除时能更彻底地清理资源,特别是对 `EventHandler` 的订阅。 - 重写了内置的 `/system plugin` 命令,以利用新的API,并为相关操作添加了权限控制。 组件注册中心(ComponentRegistry)中的多个 `get_enabled_*` 方法现在可以接受 `stream_id`,以正确反映局部状态。 BREAKING CHANGE: `plugin_manage_api` 中的多个函数已被移除或替换。例如 `list_loaded_plugins` 和 `remove_plugin` 已被移除,加载插件的逻辑已整合到 `register_plugin_from_file` 中。内置的 `/system plugin` 命令的子命令也已更改。
This commit is contained in:
@@ -1,117 +1,257 @@
|
||||
def list_loaded_plugins() -> list[str]:
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from src.common.logger import get_logger
|
||||
from src.plugin_system.base.component_types import ComponentType
|
||||
from src.plugin_system.core.component_registry import ComponentInfo, component_registry
|
||||
from src.plugin_system.core.plugin_manager import plugin_manager
|
||||
|
||||
logger = get_logger("plugin_manage_api")
|
||||
|
||||
|
||||
async def reload_all_plugins() -> bool:
|
||||
"""
|
||||
列出所有当前加载的插件。
|
||||
重新加载所有当前已成功加载的插件。
|
||||
|
||||
此操作会先卸载所有插件,然后重新加载它们。
|
||||
|
||||
Returns:
|
||||
List[str]: 当前加载的插件名称列表。
|
||||
bool: 如果所有插件都成功重载,则为 True,否则为 False。
|
||||
"""
|
||||
from src.plugin_system.core.plugin_manager import plugin_manager
|
||||
logger.info("开始重新加载所有插件...")
|
||||
# 使用 list() 复制一份列表,防止在迭代时修改原始列表
|
||||
loaded_plugins = list(plugin_manager.list_loaded_plugins())
|
||||
all_success = True
|
||||
|
||||
return plugin_manager.list_loaded_plugins()
|
||||
for plugin_name in loaded_plugins:
|
||||
try:
|
||||
success = await reload_plugin(plugin_name)
|
||||
if not success:
|
||||
all_success = False
|
||||
logger.error(f"重载插件 {plugin_name} 失败。")
|
||||
except Exception as e:
|
||||
all_success = False
|
||||
logger.error(f"重载插件 {plugin_name} 时发生异常: {e}", exc_info=True)
|
||||
|
||||
logger.info("所有插件重载完毕。")
|
||||
return all_success
|
||||
|
||||
|
||||
def list_registered_plugins() -> list[str]:
|
||||
async def reload_plugin(name: str) -> bool:
|
||||
"""
|
||||
列出所有已注册的插件。
|
||||
|
||||
Returns:
|
||||
List[str]: 已注册的插件名称列表。
|
||||
"""
|
||||
from src.plugin_system.core.plugin_manager import plugin_manager
|
||||
|
||||
return plugin_manager.list_registered_plugins()
|
||||
|
||||
|
||||
def get_plugin_path(plugin_name: str) -> str:
|
||||
"""
|
||||
获取指定插件的路径。
|
||||
重新加载指定的单个插件。
|
||||
|
||||
Args:
|
||||
plugin_name (str): 插件名称。
|
||||
name (str): 要重载的插件的名称。
|
||||
|
||||
Returns:
|
||||
str: 插件目录的绝对路径。
|
||||
bool: 成功则为 True。
|
||||
|
||||
Raises:
|
||||
ValueError: 如果插件不存在。
|
||||
ValueError: 如果插件未找到。
|
||||
"""
|
||||
from src.plugin_system.core.plugin_manager import plugin_manager
|
||||
|
||||
if plugin_path := plugin_manager.get_plugin_path(plugin_name):
|
||||
return plugin_path
|
||||
else:
|
||||
raise ValueError(f"插件 '{plugin_name}' 不存在。")
|
||||
if name not in plugin_manager.list_registered_plugins():
|
||||
raise ValueError(f"插件 '{name}' 未注册。")
|
||||
return await plugin_manager.reload_registered_plugin(name)
|
||||
|
||||
|
||||
async def remove_plugin(plugin_name: str) -> bool:
|
||||
async def set_component_enabled(name: str, component_type: ComponentType, enabled: bool) -> bool:
|
||||
"""
|
||||
卸载指定的插件。
|
||||
全局范围内启用或禁用一个组件。
|
||||
|
||||
**此函数是异步的,确保在异步环境中调用。**
|
||||
此更改会更新组件注册表中的状态,但不会持久化到文件。
|
||||
|
||||
Args:
|
||||
plugin_name (str): 要卸载的插件名称。
|
||||
name (str): 组件名称。
|
||||
component_type (ComponentType): 组件类型。
|
||||
enabled (bool): True 为启用, False 为禁用。
|
||||
|
||||
Returns:
|
||||
bool: 卸载是否成功。
|
||||
bool: 操作成功则为 True。
|
||||
"""
|
||||
from src.plugin_system.core.plugin_manager import plugin_manager
|
||||
# Chatter 唯一性保护
|
||||
if component_type == ComponentType.CHATTER and not enabled:
|
||||
enabled_chatters = component_registry.get_enabled_components_by_type(ComponentType.CHATTER)
|
||||
if len(enabled_chatters) <= 1 and name in enabled_chatters:
|
||||
logger.warning(f"操作被阻止:不能禁用最后一个启用的 Chatter 组件 ('{name}')。")
|
||||
return False
|
||||
|
||||
return await plugin_manager.remove_registered_plugin(plugin_name)
|
||||
# 注意:这里我们直接修改 ComponentInfo 中的状态
|
||||
component_info = component_registry.get_component_info(name, component_type)
|
||||
if not component_info:
|
||||
logger.error(f"未找到组件 {name} ({component_type.value}),无法更改其状态。")
|
||||
return False
|
||||
component_info.enabled = enabled
|
||||
logger.info(f"组件 {name} ({component_type.value}) 的全局状态已设置为: {enabled}")
|
||||
return True
|
||||
|
||||
|
||||
async def reload_plugin(plugin_name: str) -> bool:
|
||||
def set_component_enabled_local(stream_id: str, name: str, component_type: ComponentType, enabled: bool) -> bool:
|
||||
"""
|
||||
重新加载指定的插件。
|
||||
在一个特定的 stream_id 上下文中临时启用或禁用组件。
|
||||
|
||||
**此函数是异步的,确保在异步环境中调用。**
|
||||
此状态仅存于内存,不影响全局状态。
|
||||
|
||||
Args:
|
||||
plugin_name (str): 要重新加载的插件名称。
|
||||
stream_id (str): 上下文标识符。
|
||||
name (str): 组件名称。
|
||||
component_type (ComponentType): 组件类型。
|
||||
enabled (bool): True 为启用, False 为禁用。
|
||||
|
||||
Returns:
|
||||
bool: 重新加载是否成功。
|
||||
bool: 操作成功则为 True。
|
||||
"""
|
||||
from src.plugin_system.core.plugin_manager import plugin_manager
|
||||
|
||||
return await plugin_manager.reload_registered_plugin(plugin_name)
|
||||
component_registry.set_local_component_state(stream_id, name, component_type, enabled)
|
||||
return True
|
||||
|
||||
|
||||
def load_plugin(plugin_name: str) -> tuple[bool, int]:
|
||||
def rescan_and_register_plugins(load_after_register: bool = True) -> tuple[int, int]:
|
||||
"""
|
||||
加载指定的插件。
|
||||
重新扫描所有插件目录,发现新插件并注册。
|
||||
|
||||
Args:
|
||||
plugin_name (str): 要加载的插件名称。
|
||||
load_after_register (bool): 如果为 True,新发现的插件将在注册后立即被加载。
|
||||
|
||||
Returns:
|
||||
Tuple[bool, int]: 加载是否成功,成功或失败个数。
|
||||
Tuple[int, int]: (成功数量, 失败数量)
|
||||
"""
|
||||
from src.plugin_system.core.plugin_manager import plugin_manager
|
||||
success_count, fail_count = plugin_manager.rescan_plugin_directory()
|
||||
if not load_after_register:
|
||||
return success_count, fail_count
|
||||
|
||||
return plugin_manager.load_registered_plugin_classes(plugin_name)
|
||||
newly_registered = [
|
||||
p for p in plugin_manager.list_registered_plugins() if p not in plugin_manager.list_loaded_plugins()
|
||||
]
|
||||
loaded_success = 0
|
||||
for plugin_name in newly_registered:
|
||||
status, _ = plugin_manager.load_registered_plugin_classes(plugin_name)
|
||||
if status:
|
||||
loaded_success += 1
|
||||
|
||||
return loaded_success, fail_count + (len(newly_registered) - loaded_success)
|
||||
|
||||
|
||||
def add_plugin_directory(plugin_directory: str) -> bool:
|
||||
def register_plugin_from_file(plugin_name: str, load_after_register: bool = True) -> bool:
|
||||
"""
|
||||
添加插件目录。
|
||||
从默认插件目录中查找、注册并加载一个插件。
|
||||
|
||||
Args:
|
||||
plugin_directory (str): 要添加的插件目录路径。
|
||||
plugin_name (str): 插件的名称(即其目录名)。
|
||||
load_after_register (bool): 注册后是否立即加载。
|
||||
|
||||
Returns:
|
||||
bool: 添加是否成功。
|
||||
bool: 成功则为 True。
|
||||
"""
|
||||
from src.plugin_system.core.plugin_manager import plugin_manager
|
||||
if plugin_name in plugin_manager.list_loaded_plugins():
|
||||
logger.warning(f"插件 '{plugin_name}' 已经加载。")
|
||||
return True
|
||||
|
||||
return plugin_manager.add_plugin_directory(plugin_directory)
|
||||
# 如果插件未注册,则遍历插件目录去查找
|
||||
if plugin_name not in plugin_manager.list_registered_plugins():
|
||||
logger.info(f"插件 '{plugin_name}' 未注册,开始在插件目录中搜索...")
|
||||
found_path = None
|
||||
for directory in plugin_manager.plugin_directories:
|
||||
potential_path = os.path.join(directory, plugin_name)
|
||||
if os.path.isdir(potential_path):
|
||||
found_path = potential_path
|
||||
break
|
||||
|
||||
if not found_path:
|
||||
logger.error(f"在所有插件目录中都未找到名为 '{plugin_name}' 的插件。")
|
||||
return False
|
||||
|
||||
plugin_file = os.path.join(found_path, "plugin.py")
|
||||
if not os.path.exists(plugin_file):
|
||||
logger.error(f"在 '{found_path}' 中未找到 plugin.py 文件。")
|
||||
return False
|
||||
|
||||
module = plugin_manager._load_plugin_module_file(plugin_file)
|
||||
if not module:
|
||||
logger.error(f"从 '{plugin_file}' 加载插件模块失败。")
|
||||
return False
|
||||
|
||||
if plugin_name not in plugin_manager.list_registered_plugins():
|
||||
logger.error(f"插件 '{plugin_name}' 在加载模块后依然未注册成功。")
|
||||
return False
|
||||
|
||||
logger.info(f"插件 '{plugin_name}' 已成功发现并注册。")
|
||||
|
||||
if load_after_register:
|
||||
status, _ = plugin_manager.load_registered_plugin_classes(plugin_name)
|
||||
return status
|
||||
return True
|
||||
|
||||
|
||||
def rescan_plugin_directory() -> tuple[int, int]:
|
||||
def get_component_count(component_type: ComponentType, stream_id: str | None = None) -> int:
|
||||
"""
|
||||
重新扫描插件目录,加载新插件。
|
||||
获取指定类型的已加载并启用的组件的总数。
|
||||
|
||||
可以根据 stream_id 考虑局部状态。
|
||||
|
||||
Args:
|
||||
component_type (ComponentType): 要查询的组件类型。
|
||||
stream_id (str | None): 可选的上下文ID。
|
||||
|
||||
Returns:
|
||||
Tuple[int, int]: 成功加载的插件数量和失败的插件数量。
|
||||
int: 该类型组件的数量。
|
||||
"""
|
||||
from src.plugin_system.core.plugin_manager import plugin_manager
|
||||
return len(component_registry.get_enabled_components_by_type(component_type, stream_id=stream_id))
|
||||
|
||||
return plugin_manager.rescan_plugin_directory()
|
||||
|
||||
def get_component_info(name: str, component_type: ComponentType) -> ComponentInfo | None:
|
||||
"""
|
||||
获取任何一个已注册组件的详细信息。
|
||||
|
||||
Args:
|
||||
name (str): 组件的唯一名称。
|
||||
component_type (ComponentType): 组件的类型。
|
||||
|
||||
Returns:
|
||||
ComponentInfo: 包含组件信息的对象,如果找不到则返回 None。
|
||||
"""
|
||||
return component_registry.get_component_info(name, component_type)
|
||||
|
||||
|
||||
def get_system_report() -> dict[str, Any]:
|
||||
"""
|
||||
生成一份详细的系统状态报告。
|
||||
|
||||
Returns:
|
||||
dict: 包含系统、插件和组件状态的详细报告。
|
||||
"""
|
||||
loaded_plugins_info = {}
|
||||
for name, instance in plugin_manager.loaded_plugins.items():
|
||||
plugin_info = component_registry.get_plugin_info(name)
|
||||
if not plugin_info:
|
||||
continue
|
||||
|
||||
components_details = []
|
||||
for comp_info in plugin_info.components:
|
||||
components_details.append(
|
||||
{
|
||||
"name": comp_info.name,
|
||||
"component_type": comp_info.component_type.value,
|
||||
"description": comp_info.description,
|
||||
"enabled": comp_info.enabled,
|
||||
}
|
||||
)
|
||||
|
||||
# 从 plugin_info (PluginInfo) 而不是 instance (PluginBase) 获取元数据
|
||||
loaded_plugins_info[name] = {
|
||||
"display_name": plugin_info.display_name or name,
|
||||
"version": plugin_info.version,
|
||||
"author": plugin_info.author,
|
||||
"enabled": instance.enable_plugin, # enable_plugin 状态还是需要从实例获取
|
||||
"components": components_details,
|
||||
}
|
||||
|
||||
report = {
|
||||
"system_info": {
|
||||
"loaded_plugins_count": len(plugin_manager.loaded_plugins),
|
||||
"total_components_count": component_registry.get_registry_stats().get("total_components", 0),
|
||||
},
|
||||
"plugins": loaded_plugins_info,
|
||||
"failed_plugins": plugin_manager.failed_plugins,
|
||||
}
|
||||
return report
|
||||
|
||||
Reference in New Issue
Block a user