优化插件热重载管理,支持多个监听目录
This commit is contained in:
@@ -16,6 +16,7 @@ from src.chat.utils.chat_message_builder import (
|
||||
build_readable_messages_with_id,
|
||||
get_raw_msg_before_timestamp_with_chat,
|
||||
)
|
||||
from src.plugin_system.apis.message_api import filter_mai_messages
|
||||
from src.chat.utils.utils import get_chat_type_and_target_info
|
||||
from src.chat.planner_actions.action_manager import ActionManager
|
||||
from src.chat.message_receive.chat_stream import get_chat_manager
|
||||
@@ -269,6 +270,9 @@ class ActionPlanner:
|
||||
timestamp=time.time(),
|
||||
limit=int(global_config.chat.max_context_size * 0.6),
|
||||
)
|
||||
|
||||
# 过滤掉bot自己的消息,避免planner把bot消息当作新消息处理
|
||||
message_list_before_now = filter_mai_messages(message_list_before_now)
|
||||
|
||||
chat_content_block, message_id_list = build_readable_messages_with_id(
|
||||
messages=message_list_before_now,
|
||||
|
||||
@@ -8,7 +8,7 @@ import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
from threading import Thread
|
||||
from typing import Dict, Set
|
||||
from typing import Dict, Set, List, Optional, Tuple
|
||||
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
@@ -31,27 +31,34 @@ class PluginFileHandler(FileSystemEventHandler):
|
||||
|
||||
def on_modified(self, event):
|
||||
"""文件修改事件"""
|
||||
if not event.is_directory and (event.src_path.endswith('.py') or event.src_path.endswith('.toml')):
|
||||
self._handle_file_change(event.src_path, "modified")
|
||||
if not event.is_directory:
|
||||
file_path = str(event.src_path)
|
||||
if file_path.endswith(('.py', '.toml')):
|
||||
self._handle_file_change(file_path, "modified")
|
||||
|
||||
def on_created(self, event):
|
||||
"""文件创建事件"""
|
||||
if not event.is_directory and (event.src_path.endswith('.py') or event.src_path.endswith('.toml')):
|
||||
self._handle_file_change(event.src_path, "created")
|
||||
if not event.is_directory:
|
||||
file_path = str(event.src_path)
|
||||
if file_path.endswith(('.py', '.toml')):
|
||||
self._handle_file_change(file_path, "created")
|
||||
|
||||
def on_deleted(self, event):
|
||||
"""文件删除事件"""
|
||||
if not event.is_directory and (event.src_path.endswith('.py') or event.src_path.endswith('.toml')):
|
||||
self._handle_file_change(event.src_path, "deleted")
|
||||
if not event.is_directory:
|
||||
file_path = str(event.src_path)
|
||||
if file_path.endswith(('.py', '.toml')):
|
||||
self._handle_file_change(file_path, "deleted")
|
||||
|
||||
def _handle_file_change(self, file_path: str, change_type: str):
|
||||
"""处理文件变化"""
|
||||
try:
|
||||
# 获取插件名称
|
||||
plugin_name = self._get_plugin_name_from_path(file_path)
|
||||
if not plugin_name:
|
||||
plugin_info = self._get_plugin_info_from_path(file_path)
|
||||
if not plugin_info:
|
||||
return
|
||||
|
||||
plugin_name, source_type = plugin_info
|
||||
current_time = time.time()
|
||||
last_time = self.last_reload_time.get(plugin_name, 0)
|
||||
|
||||
@@ -60,18 +67,18 @@ class PluginFileHandler(FileSystemEventHandler):
|
||||
return
|
||||
|
||||
file_name = Path(file_path).name
|
||||
logger.info(f"📁 检测到插件文件变化: {file_name} ({change_type})")
|
||||
logger.info(f"📁 检测到插件文件变化: {file_name} ({change_type}) [{source_type}]")
|
||||
|
||||
# 如果是删除事件,处理关键文件删除
|
||||
if change_type == "deleted":
|
||||
if file_name == "plugin.py":
|
||||
if plugin_name in plugin_manager.loaded_plugins:
|
||||
logger.info(f"🗑️ 插件主文件被删除,卸载插件: {plugin_name}")
|
||||
logger.info(f"🗑️ 插件主文件被删除,卸载插件: {plugin_name} [{source_type}]")
|
||||
self.hot_reload_manager._unload_plugin(plugin_name)
|
||||
return
|
||||
elif file_name == "manifest.toml":
|
||||
elif file_name in ("manifest.toml", "_manifest.json"):
|
||||
if plugin_name in plugin_manager.loaded_plugins:
|
||||
logger.info(f"🗑️ 插件配置文件被删除,卸载插件: {plugin_name}")
|
||||
logger.info(f"🗑️ 插件配置文件被删除,卸载插件: {plugin_name} [{source_type}]")
|
||||
self.hot_reload_manager._unload_plugin(plugin_name)
|
||||
return
|
||||
|
||||
@@ -83,7 +90,7 @@ class PluginFileHandler(FileSystemEventHandler):
|
||||
# 延迟重载,避免文件正在写入时重载
|
||||
reload_thread = Thread(
|
||||
target=self._delayed_reload,
|
||||
args=(plugin_name,),
|
||||
args=(plugin_name, source_type),
|
||||
daemon=True
|
||||
)
|
||||
reload_thread.start()
|
||||
@@ -91,58 +98,84 @@ class PluginFileHandler(FileSystemEventHandler):
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 处理文件变化时发生错误: {e}")
|
||||
|
||||
def _delayed_reload(self, plugin_name: str):
|
||||
def _delayed_reload(self, plugin_name: str, source_type: str):
|
||||
"""延迟重载插件"""
|
||||
try:
|
||||
time.sleep(self.debounce_delay)
|
||||
|
||||
if plugin_name in self.pending_reloads:
|
||||
self.pending_reloads.remove(plugin_name)
|
||||
logger.info(f"🔄 延迟重载插件: {plugin_name} [{source_type}]")
|
||||
self.hot_reload_manager._reload_plugin(plugin_name)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 延迟重载插件 {plugin_name} 时发生错误: {e}")
|
||||
|
||||
def _get_plugin_name_from_path(self, file_path: str) -> str:
|
||||
"""从文件路径获取插件名称"""
|
||||
def _get_plugin_info_from_path(self, file_path: str) -> Optional[Tuple[str, str]]:
|
||||
"""从文件路径获取插件信息
|
||||
|
||||
Returns:
|
||||
tuple[插件名称, 源类型] 或 None
|
||||
"""
|
||||
try:
|
||||
path = Path(file_path)
|
||||
|
||||
# 检查是否在监听的插件目录中
|
||||
plugin_root = Path(self.hot_reload_manager.watch_directory)
|
||||
if not path.is_relative_to(plugin_root):
|
||||
return ""
|
||||
# 检查是否在任何一个监听的插件目录中
|
||||
for watch_dir in self.hot_reload_manager.watch_directories:
|
||||
plugin_root = Path(watch_dir)
|
||||
if path.is_relative_to(plugin_root):
|
||||
# 确定源类型
|
||||
if "src" in str(plugin_root):
|
||||
source_type = "built-in"
|
||||
else:
|
||||
source_type = "external"
|
||||
|
||||
# 获取插件目录名(插件名)
|
||||
relative_path = path.relative_to(plugin_root)
|
||||
if len(relative_path.parts) == 0:
|
||||
continue
|
||||
|
||||
plugin_name = relative_path.parts[0]
|
||||
|
||||
# 获取插件目录名(插件名)
|
||||
relative_path = path.relative_to(plugin_root)
|
||||
plugin_name = relative_path.parts[0]
|
||||
# 确认这是一个有效的插件目录
|
||||
plugin_dir = plugin_root / plugin_name
|
||||
if plugin_dir.is_dir():
|
||||
# 检查是否有插件主文件或配置文件
|
||||
has_plugin_py = (plugin_dir / "plugin.py").exists()
|
||||
has_manifest = ((plugin_dir / "manifest.toml").exists() or
|
||||
(plugin_dir / "_manifest.json").exists())
|
||||
|
||||
if has_plugin_py or has_manifest:
|
||||
return plugin_name, source_type
|
||||
|
||||
# 确认这是一个有效的插件目录(检查是否有 plugin.py 或 manifest.toml)
|
||||
plugin_dir = plugin_root / plugin_name
|
||||
if plugin_dir.is_dir() and ((plugin_dir / "plugin.py").exists() or (plugin_dir / "manifest.toml").exists()):
|
||||
return plugin_name
|
||||
|
||||
return ""
|
||||
return None
|
||||
|
||||
except Exception:
|
||||
return ""
|
||||
return None
|
||||
|
||||
|
||||
class PluginHotReloadManager:
|
||||
"""插件热重载管理器"""
|
||||
|
||||
def __init__(self, watch_directory: str = None):
|
||||
print("fuck")
|
||||
print(os.getcwd())
|
||||
self.watch_directory = os.path.join(os.getcwd(), "plugins")
|
||||
self.observer = None
|
||||
self.file_handler = None
|
||||
def __init__(self, watch_directories: Optional[List[str]] = None):
|
||||
if watch_directories is None:
|
||||
# 默认监听两个目录:根目录下的 plugins 和 src 下的插件目录
|
||||
self.watch_directories = [
|
||||
os.path.join(os.getcwd(), "plugins"), # 外部插件目录
|
||||
os.path.join(os.getcwd(), "src", "plugins", "built_in") # 内置插件目录
|
||||
]
|
||||
else:
|
||||
self.watch_directories = watch_directories
|
||||
|
||||
self.observers = []
|
||||
self.file_handlers = []
|
||||
self.is_running = False
|
||||
|
||||
# 确保监听目录存在
|
||||
if not os.path.exists(self.watch_directory):
|
||||
os.makedirs(self.watch_directory, exist_ok=True)
|
||||
logger.info(f"创建插件监听目录: {self.watch_directory}")
|
||||
for watch_dir in self.watch_directories:
|
||||
if not os.path.exists(watch_dir):
|
||||
os.makedirs(watch_dir, exist_ok=True)
|
||||
logger.info(f"📁 创建插件监听目录: {watch_dir}")
|
||||
|
||||
def start(self):
|
||||
"""启动热重载监听"""
|
||||
@@ -151,34 +184,57 @@ class PluginHotReloadManager:
|
||||
return
|
||||
|
||||
try:
|
||||
self.observer = Observer()
|
||||
self.file_handler = PluginFileHandler(self)
|
||||
# 为每个监听目录创建独立的观察者
|
||||
for watch_dir in self.watch_directories:
|
||||
observer = Observer()
|
||||
file_handler = PluginFileHandler(self)
|
||||
|
||||
observer.schedule(
|
||||
file_handler,
|
||||
watch_dir,
|
||||
recursive=True
|
||||
)
|
||||
|
||||
observer.start()
|
||||
self.observers.append(observer)
|
||||
self.file_handlers.append(file_handler)
|
||||
|
||||
self.observer.schedule(
|
||||
self.file_handler,
|
||||
self.watch_directory,
|
||||
recursive=True
|
||||
)
|
||||
|
||||
self.observer.start()
|
||||
self.is_running = True
|
||||
|
||||
logger.info("🚀 插件热重载已启动,监听目录: plugins")
|
||||
# 打印监听的目录信息
|
||||
dir_info = []
|
||||
for watch_dir in self.watch_directories:
|
||||
if "src" in watch_dir:
|
||||
dir_info.append(f"{watch_dir} (内置插件)")
|
||||
else:
|
||||
dir_info.append(f"{watch_dir} (外部插件)")
|
||||
|
||||
logger.info(f"🚀 插件热重载已启动,监听目录:")
|
||||
for info in dir_info:
|
||||
logger.info(f" 📂 {info}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 启动插件热重载失败: {e}")
|
||||
self.stop() # 清理已创建的观察者
|
||||
self.is_running = False
|
||||
|
||||
def stop(self):
|
||||
"""停止热重载监听"""
|
||||
if not self.is_running:
|
||||
if not self.is_running and not self.observers:
|
||||
return
|
||||
|
||||
if self.observer:
|
||||
self.observer.stop()
|
||||
self.observer.join()
|
||||
# 停止所有观察者
|
||||
for observer in self.observers:
|
||||
try:
|
||||
observer.stop()
|
||||
observer.join()
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 停止观察者时发生错误: {e}")
|
||||
|
||||
self.observers.clear()
|
||||
self.file_handlers.clear()
|
||||
self.is_running = False
|
||||
logger.info("🛑 插件热重载已停止")
|
||||
|
||||
def _reload_plugin(self, plugin_name: str):
|
||||
"""重载指定插件"""
|
||||
@@ -228,11 +284,47 @@ class PluginHotReloadManager:
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 重载所有插件时发生错误: {e}")
|
||||
|
||||
def add_watch_directory(self, directory: str):
|
||||
"""添加新的监听目录"""
|
||||
if directory in self.watch_directories:
|
||||
logger.info(f"目录 {directory} 已在监听列表中")
|
||||
return
|
||||
|
||||
# 确保目录存在
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory, exist_ok=True)
|
||||
logger.info(f"📁 创建插件监听目录: {directory}")
|
||||
|
||||
self.watch_directories.append(directory)
|
||||
|
||||
# 如果热重载正在运行,为新目录创建观察者
|
||||
if self.is_running:
|
||||
try:
|
||||
observer = Observer()
|
||||
file_handler = PluginFileHandler(self)
|
||||
|
||||
observer.schedule(
|
||||
file_handler,
|
||||
directory,
|
||||
recursive=True
|
||||
)
|
||||
|
||||
observer.start()
|
||||
self.observers.append(observer)
|
||||
self.file_handlers.append(file_handler)
|
||||
|
||||
logger.info(f"📂 已添加新的监听目录: {directory}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 添加监听目录 {directory} 失败: {e}")
|
||||
self.watch_directories.remove(directory)
|
||||
|
||||
def get_status(self) -> dict:
|
||||
"""获取热重载状态"""
|
||||
return {
|
||||
"is_running": self.is_running,
|
||||
"watch_directory": self.watch_directory,
|
||||
"watch_directories": self.watch_directories,
|
||||
"active_observers": len(self.observers),
|
||||
"loaded_plugins": len(plugin_manager.loaded_plugins),
|
||||
"failed_plugins": len(plugin_manager.failed_plugins),
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ class ScheduleManager:
|
||||
logger.debug("当前时间没有日程活动")
|
||||
|
||||
# 每5分钟检查一次,避免频繁检查
|
||||
await asyncio.sleep(60)
|
||||
await asyncio.sleep(300)
|
||||
|
||||
except asyncio.CancelledError:
|
||||
logger.info("定时任务循环被取消")
|
||||
|
||||
Reference in New Issue
Block a user