This commit is contained in:
2025-12-31 17:47:39 +08:00
38 changed files with 4435 additions and 1343 deletions

View File

@@ -2,7 +2,6 @@ import importlib
import inspect
import os
import sys
import tomllib
import traceback
from typing import Dict, Type, List, Union
@@ -10,6 +9,8 @@ from loguru import logger
# from WechatAPI import WechatAPIClient # 注释掉WechatHookBot 不需要这个导入
from utils.singleton import Singleton
from utils.config_manager import get_bot_config
from utils.llm_tooling import register_plugin_tools, unregister_plugin_tools
from .event_manager import EventManager
from .plugin_base import PluginBase
@@ -22,10 +23,9 @@ class PluginManager(metaclass=Singleton):
self.bot = None
with open("main_config.toml", "rb") as f:
main_config = tomllib.load(f)
self.excluded_plugins = main_config.get("Bot", {}).get("disabled-plugins", [])
# 使用统一配置管理器
bot_config = get_bot_config()
self.excluded_plugins = bot_config.get("disabled-plugins", [])
def set_bot(self, bot):
"""设置 bot 客户端WechatHookClient"""
@@ -74,13 +74,34 @@ class PluginManager(metaclass=Singleton):
if is_disabled:
return False
# 创建插件实例
plugin = plugin_class()
EventManager.bind_instance(plugin)
await plugin.on_enable(self.bot)
# 生命周期: on_load可访问其他插件
await plugin.on_load(self)
# 生命周期: async_init加载配置、资源
await plugin.async_init()
# 绑定事件处理器
EventManager.bind_instance(plugin)
# 生命周期: on_enable注册定时任务
await plugin.on_enable(self.bot)
# 注册到插件管理器
self.plugins[plugin_name] = plugin
self.plugin_classes[plugin_name] = plugin_class
self.plugin_info[plugin_name]["enabled"] = True
# 注册插件的 LLM 工具到全局注册中心
try:
tools_config = self._get_tools_config()
timeout_config = self._get_timeout_config()
register_plugin_tools(plugin_name, plugin, tools_config, timeout_config)
except Exception as e:
logger.warning(f"注册插件 {plugin_name} 的工具时出错: {e}")
logger.success(f"加载插件 {plugin_name} 成功")
return True
except:
@@ -232,8 +253,22 @@ class PluginManager(metaclass=Singleton):
try:
plugin = self.plugins[plugin_name]
# 生命周期: on_disable清理定时任务
await plugin.on_disable()
# 解绑事件处理器
EventManager.unbind_instance(plugin)
# 从全局注册中心注销插件的工具
try:
unregister_plugin_tools(plugin_name)
except Exception as e:
logger.warning(f"注销插件 {plugin_name} 的工具时出错: {e}")
# 生命周期: on_unload释放资源
await plugin.on_unload()
del self.plugins[plugin_name]
del self.plugin_classes[plugin_name]
if plugin_name in self.plugin_info.keys():
@@ -256,7 +291,7 @@ class PluginManager(metaclass=Singleton):
return unloaded_plugins, failed_unloads
async def reload_plugin(self, plugin_name: str) -> bool:
"""重载单个插件"""
"""重载单个插件(支持状态保存和恢复)"""
if plugin_name not in self.plugin_classes:
return False
@@ -270,7 +305,15 @@ class PluginManager(metaclass=Singleton):
plugin_class = self.plugin_classes[plugin_name]
module_name = plugin_class.__module__
# 先卸载插件
# 生命周期: on_reload保存状态
saved_state = {}
if plugin_name in self.plugins:
try:
saved_state = await self.plugins[plugin_name].on_reload()
except Exception as e:
logger.warning(f"保存插件 {plugin_name} 状态失败: {e}")
# 卸载插件
if not await self.unload_plugin(plugin_name):
return False
@@ -284,8 +327,15 @@ class PluginManager(metaclass=Singleton):
issubclass(obj, PluginBase) and
obj != PluginBase and
obj.__name__ == plugin_name):
# 使用新的插件类而不是旧的
return await self.load_plugin(obj)
# 加载新插件
if await self.load_plugin(obj):
# 恢复状态
if saved_state and plugin_name in self.plugins:
try:
await self.plugins[plugin_name].restore_state(saved_state)
except Exception as e:
logger.warning(f"恢复插件 {plugin_name} 状态失败: {e}")
return True
return False
except Exception as e:
@@ -349,13 +399,42 @@ class PluginManager(metaclass=Singleton):
def get_plugin_info(self, plugin_name: str = None) -> Union[dict, List[dict]]:
"""获取插件信息
Args:
plugin_name: 插件名称如果为None则返回所有插件信息
Returns:
如果指定插件名,返回单个插件信息字典;否则返回所有插件信息列表
"""
if plugin_name:
return self.plugin_info.get(plugin_name)
return list(self.plugin_info.values())
def _get_tools_config(self) -> dict:
"""获取工具配置(用于工具注册)"""
try:
# 尝试从 AIChat 插件配置读取
import tomllib
from pathlib import Path
aichat_config_path = Path("plugins/AIChat/config.toml")
if aichat_config_path.exists():
with open(aichat_config_path, "rb") as f:
aichat_config = tomllib.load(f)
return aichat_config.get("tools", {})
except Exception:
pass
return {"mode": "all", "whitelist": [], "blacklist": []}
def _get_timeout_config(self) -> dict:
"""获取工具超时配置"""
try:
import tomllib
from pathlib import Path
aichat_config_path = Path("plugins/AIChat/config.toml")
if aichat_config_path.exists():
with open(aichat_config_path, "rb") as f:
aichat_config = tomllib.load(f)
return aichat_config.get("tools", {}).get("timeout", {})
except Exception:
pass
return {"default": 60}