diff --git a/astrbot/core/pipeline/waking_check/stage.py b/astrbot/core/pipeline/waking_check/stage.py
index 6fb3378ca..7c91a2fcf 100644
--- a/astrbot/core/pipeline/waking_check/stage.py
+++ b/astrbot/core/pipeline/waking_check/stage.py
@@ -8,6 +8,7 @@ from astrbot.core.message.components import At, AtAll
from astrbot.core.star.star_handler import star_handlers_registry, EventType
from astrbot.core.star.star import star_map
from astrbot.core.star.filter.permission import PermissionTypeFilter
+from astrbot.core.star.session_plugin_manager import SessionPluginManager
@register_stage
@@ -160,6 +161,9 @@ class WakingCheckStage(Stage):
event.clear_extra()
+ # 根据会话配置过滤插件处理器
+ activated_handlers = SessionPluginManager.filter_handlers_by_session(event, activated_handlers)
+
event.set_extra("activated_handlers", activated_handlers)
event.set_extra("handlers_parsed_params", handlers_parsed_params)
diff --git a/astrbot/core/star/session_plugin_manager.py b/astrbot/core/star/session_plugin_manager.py
new file mode 100644
index 000000000..8f68247b3
--- /dev/null
+++ b/astrbot/core/star/session_plugin_manager.py
@@ -0,0 +1,135 @@
+"""
+会话插件管理器 - 负责管理每个会话的插件启停状态
+"""
+
+from astrbot.core import sp, logger
+from typing import Dict, List, Optional
+from astrbot.core.platform.astr_message_event import AstrMessageEvent
+
+
+class SessionPluginManager:
+ """管理会话级别的插件启停状态"""
+
+ @staticmethod
+ def is_plugin_enabled_for_session(session_id: str, plugin_name: str) -> bool:
+ """检查插件是否在指定会话中启用
+
+ Args:
+ session_id: 会话ID (unified_msg_origin)
+ plugin_name: 插件名称
+
+ Returns:
+ bool: True表示启用,False表示禁用
+ """
+ # 获取会话插件配置
+ session_plugin_config = sp.get("session_plugin_config", {})
+ session_config = session_plugin_config.get(session_id, {})
+
+ enabled_plugins = session_config.get("enabled_plugins", [])
+ disabled_plugins = session_config.get("disabled_plugins", [])
+
+ # 如果插件在禁用列表中,返回False
+ if plugin_name in disabled_plugins:
+ return False
+
+ # 如果插件在启用列表中,返回True
+ if plugin_name in enabled_plugins:
+ return True
+
+ # 如果都没有配置,默认为启用(兼容性考虑)
+ return True
+
+ @staticmethod
+ def set_plugin_status_for_session(session_id: str, plugin_name: str, enabled: bool) -> None:
+ """设置插件在指定会话中的启停状态
+
+ Args:
+ session_id: 会话ID (unified_msg_origin)
+ plugin_name: 插件名称
+ enabled: True表示启用,False表示禁用
+ """
+ # 获取当前配置
+ session_plugin_config = sp.get("session_plugin_config", {})
+ if session_id not in session_plugin_config:
+ session_plugin_config[session_id] = {
+ "enabled_plugins": [],
+ "disabled_plugins": []
+ }
+
+ session_config = session_plugin_config[session_id]
+ enabled_plugins = session_config.get("enabled_plugins", [])
+ disabled_plugins = session_config.get("disabled_plugins", [])
+
+ if enabled:
+ # 启用插件
+ if plugin_name in disabled_plugins:
+ disabled_plugins.remove(plugin_name)
+ if plugin_name not in enabled_plugins:
+ enabled_plugins.append(plugin_name)
+ else:
+ # 禁用插件
+ if plugin_name in enabled_plugins:
+ enabled_plugins.remove(plugin_name)
+ if plugin_name not in disabled_plugins:
+ disabled_plugins.append(plugin_name)
+
+ # 保存配置
+ session_config["enabled_plugins"] = enabled_plugins
+ session_config["disabled_plugins"] = disabled_plugins
+ session_plugin_config[session_id] = session_config
+ sp.put("session_plugin_config", session_plugin_config)
+
+ logger.info(f"会话 {session_id} 的插件 {plugin_name} 状态已更新为: {'启用' if enabled else '禁用'}")
+
+ @staticmethod
+ def get_session_plugin_config(session_id: str) -> Dict[str, List[str]]:
+ """获取指定会话的插件配置
+
+ Args:
+ session_id: 会话ID (unified_msg_origin)
+
+ Returns:
+ Dict[str, List[str]]: 包含enabled_plugins和disabled_plugins的字典
+ """
+ session_plugin_config = sp.get("session_plugin_config", {})
+ return session_plugin_config.get(session_id, {
+ "enabled_plugins": [],
+ "disabled_plugins": []
+ })
+
+ @staticmethod
+ def filter_handlers_by_session(event: AstrMessageEvent, handlers: List) -> List:
+ """根据会话配置过滤处理器列表
+
+ Args:
+ event: 消息事件
+ handlers: 原始处理器列表
+
+ Returns:
+ List: 过滤后的处理器列表
+ """
+ from astrbot.core.star.star import star_map
+
+ session_id = event.unified_msg_origin
+ filtered_handlers = []
+
+ for handler in handlers:
+ # 获取处理器对应的插件
+ plugin = star_map.get(handler.handler_module_path)
+ if not plugin:
+ # 如果找不到插件元数据,允许执行(可能是系统插件)
+ filtered_handlers.append(handler)
+ continue
+
+ # 跳过保留插件(系统插件)
+ if plugin.reserved:
+ filtered_handlers.append(handler)
+ continue
+
+ # 检查插件是否在当前会话中启用
+ if SessionPluginManager.is_plugin_enabled_for_session(session_id, plugin.name):
+ filtered_handlers.append(handler)
+ else:
+ logger.debug(f"插件 {plugin.name} 在会话 {session_id} 中被禁用,跳过处理器 {handler.handler_name}")
+
+ return filtered_handlers
diff --git a/astrbot/dashboard/routes/session_management.py b/astrbot/dashboard/routes/session_management.py
index dd7da8add..ed3af645f 100644
--- a/astrbot/dashboard/routes/session_management.py
+++ b/astrbot/dashboard/routes/session_management.py
@@ -5,6 +5,7 @@ from quart import request
from astrbot.core.db import BaseDatabase
from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
from astrbot.core.provider.entities import ProviderType
+from astrbot.core.star.session_plugin_manager import SessionPluginManager
class SessionManagementRoute(Route):
@@ -20,6 +21,8 @@ class SessionManagementRoute(Route):
"/session/update_persona": ("POST", self.update_session_persona),
"/session/update_provider": ("POST", self.update_session_provider),
"/session/get_session_info": ("POST", self.get_session_info),
+ "/session/plugins": ("GET", self.get_session_plugins),
+ "/session/update_plugin": ("POST", self.update_session_plugin),
}
self.db_helper = db_helper
self.core_lifecycle = core_lifecycle
@@ -359,3 +362,82 @@ class SessionManagementRoute(Route):
error_msg = f"获取会话信息失败: {str(e)}\n{traceback.format_exc()}"
logger.error(error_msg)
return Response().error(f"获取会话信息失败: {str(e)}").__dict__
+
+ async def get_session_plugins(self):
+ """获取指定会话的插件配置信息"""
+ try:
+ session_id = request.args.get("session_id")
+
+ if not session_id:
+ return Response().error("缺少必要参数: session_id").__dict__
+
+ # 获取所有已激活的插件
+ all_plugins = []
+ plugin_manager = self.core_lifecycle.star_context._star_manager
+
+ for plugin in plugin_manager.context.get_all_stars():
+ # 只显示已激活的插件,不包括保留插件
+ if plugin.activated and not plugin.reserved:
+ plugin_enabled = SessionPluginManager.is_plugin_enabled_for_session(session_id, plugin.name)
+
+ all_plugins.append({
+ "name": plugin.name,
+ "author": plugin.author,
+ "desc": plugin.desc,
+ "enabled": plugin_enabled,
+ })
+
+ return Response().ok({
+ "session_id": session_id,
+ "plugins": all_plugins,
+ }).__dict__
+
+ except Exception as e:
+ error_msg = f"获取会话插件配置失败: {str(e)}\n{traceback.format_exc()}"
+ logger.error(error_msg)
+ return Response().error(f"获取会话插件配置失败: {str(e)}").__dict__
+
+ async def update_session_plugin(self):
+ """更新指定会话的插件启停状态"""
+ try:
+ data = await request.get_json()
+ session_id = data.get("session_id")
+ plugin_name = data.get("plugin_name")
+ enabled = data.get("enabled")
+
+ if not session_id:
+ return Response().error("缺少必要参数: session_id").__dict__
+
+ if not plugin_name:
+ return Response().error("缺少必要参数: plugin_name").__dict__
+
+ if enabled is None:
+ return Response().error("缺少必要参数: enabled").__dict__
+
+ # 验证插件是否存在且已激活
+ plugin_manager = self.core_lifecycle.star_context._star_manager
+ plugin = plugin_manager.context.get_registered_star(plugin_name)
+
+ if not plugin:
+ return Response().error(f"插件 {plugin_name} 不存在").__dict__
+
+ if not plugin.activated:
+ return Response().error(f"插件 {plugin_name} 未激活").__dict__
+
+ if plugin.reserved:
+ return Response().error(f"插件 {plugin_name} 是系统保留插件,无法管理").__dict__
+
+ # 使用 SessionPluginManager 更新插件状态
+ SessionPluginManager.set_plugin_status_for_session(session_id, plugin_name, enabled)
+
+ return Response().ok({
+ "message": f"插件 {plugin_name} 已{'启用' if enabled else '禁用'}",
+ "session_id": session_id,
+ "plugin_name": plugin_name,
+ "enabled": enabled,
+ }).__dict__
+
+ except Exception as e:
+ error_msg = f"更新会话插件状态失败: {str(e)}\n{traceback.format_exc()}"
+ logger.error(error_msg)
+ return Response().error(f"更新会话插件状态失败: {str(e)}").__dict__
diff --git a/dashboard/src/views/SessionManagementPage.vue b/dashboard/src/views/SessionManagementPage.vue
index b3f8d95ad..7b54dc14c 100644
--- a/dashboard/src/views/SessionManagementPage.vue
+++ b/dashboard/src/views/SessionManagementPage.vue
@@ -157,6 +157,19 @@
+
+
+
+ 编辑
+
+
+
+
+
+
+
+ mdi-pencil
+ 插件管理 - {{ selectedSessionForPlugin.session_name }}
+
+
+ mdi-close
+
+
+
+
+
+
mdi-puzzle-outline
+
暂无可用插件
+
目前没有激活的插件
+
+
+
+
+
+
+ {{ plugin.enabled ? 'mdi-check-circle' : 'mdi-circle-outline' }}
+
+
+
+
+ {{ plugin.name }}
+
+
+
+ 作者: {{ plugin.author }}
+
+
+
+ togglePlugin(plugin, value)"
+ :loading="plugin.updating"
+ >
+
+
+
+
+
+
+
+ 加载插件列表中...
+
+
+
+
{{ snackbarText }}
@@ -342,6 +414,12 @@ export default {
detailDialog: false,
selectedSession: null,
+ // 插件管理
+ pluginDialog: false,
+ selectedSessionForPlugin: null,
+ sessionPlugins: [],
+ loadingPlugins: false,
+
// 提示信息
snackbar: false,
snackbarText: '',
@@ -354,6 +432,7 @@ export default {
{ title: 'Chat Provider', key: 'chat_provider', sortable: false, width: '180px' },
{ title: 'STT Provider', key: 'stt_provider', sortable: false, width: '150px' },
{ title: 'TTS Provider', key: 'tts_provider', sortable: false, width: '150px' },
+ { title: '插件管理', key: 'plugins', sortable: false, width: '120px' },
{ title: '操作', key: 'actions', sortable: false, width: '80px' },
],
}
@@ -433,7 +512,8 @@ export default {
const data = response.data.data;
this.sessions = data.sessions.map(session => ({
...session,
- updating: false // 添加更新状态标志
+ updating: false, // 添加更新状态标志
+ loadingPlugins: false // 添加插件加载状态标志
}));
this.availablePersonas = data.available_personas;
this.availableChatProviders = data.available_chat_providers;
@@ -549,6 +629,55 @@ export default {
this.batchChatProvider = null;
},
+ async openPluginManager(session) {
+ this.selectedSessionForPlugin = session;
+ this.pluginDialog = true;
+ this.loadingPlugins = true;
+ this.sessionPlugins = [];
+
+ try {
+ const response = await axios.get('/api/session/plugins', {
+ params: { session_id: session.session_id }
+ });
+
+ if (response.data.status === 'ok') {
+ this.sessionPlugins = response.data.data.plugins.map(plugin => ({
+ ...plugin,
+ updating: false
+ }));
+ } else {
+ this.showError(response.data.message || '加载插件列表失败');
+ }
+ } catch (error) {
+ this.showError(error.response?.data?.message || '加载插件列表失败');
+ }
+
+ this.loadingPlugins = false;
+ },
+
+ async togglePlugin(plugin, enabled) {
+ plugin.updating = true;
+
+ try {
+ const response = await axios.post('/api/session/update_plugin', {
+ session_id: this.selectedSessionForPlugin.session_id,
+ plugin_name: plugin.name,
+ enabled: enabled
+ });
+
+ if (response.data.status === 'ok') {
+ plugin.enabled = enabled;
+ this.showSuccess(`插件 ${plugin.name} ${enabled ? '已启用' : '已禁用'}`);
+ } else {
+ this.showError(response.data.message || '插件状态更新失败');
+ }
+ } catch (error) {
+ this.showError(error.response?.data?.message || '插件状态更新失败');
+ }
+
+ plugin.updating = false;
+ },
+
showSessionDetail(session) {
this.selectedSession = session;
this.detailDialog = true;