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