From 80eb9395c754d73c53813ee1ba691e879e21c9ef Mon Sep 17 00:00:00 2001 From: Flartiny <1335348298@qq.com> Date: Tue, 24 Jun 2025 22:36:21 +0800 Subject: [PATCH 1/5] feat: add hooks -- on_star_activated, on_star_deactivated --- astrbot/api/event/filter/__init__.py | 4 ++ astrbot/core/star/register/__init__.py | 4 ++ astrbot/core/star/register/star_handler.py | 28 +++++++++++++ astrbot/core/star/star_handler.py | 2 + astrbot/core/star/star_manager.py | 46 ++++++++++++++++++++++ 5 files changed, 84 insertions(+) diff --git a/astrbot/api/event/filter/__init__.py b/astrbot/api/event/filter/__init__.py index dd737e3ff..830de7892 100644 --- a/astrbot/api/event/filter/__init__.py +++ b/astrbot/api/event/filter/__init__.py @@ -12,6 +12,8 @@ register_llm_tool as llm_tool, register_on_decorating_result as on_decorating_result, register_after_message_sent as after_message_sent, + register_on_star_activated as on_star_activated, + register_on_star_deactivated as on_star_deactivated, ) from astrbot.core.star.filter.event_message_type import ( @@ -46,4 +48,6 @@ "on_decorating_result", "after_message_sent", "on_llm_response", + "on_star_activated", + "on_star_deactivated", ] diff --git a/astrbot/core/star/register/__init__.py b/astrbot/core/star/register/__init__.py index fa6a730ba..7697f3b77 100644 --- a/astrbot/core/star/register/__init__.py +++ b/astrbot/core/star/register/__init__.py @@ -13,6 +13,8 @@ register_llm_tool, register_on_decorating_result, register_after_message_sent, + register_on_star_activated, + register_on_star_deactivated, ) __all__ = [ @@ -30,4 +32,6 @@ "register_llm_tool", "register_on_decorating_result", "register_after_message_sent", + "register_on_star_activated", + "register_on_star_deactivated", ] diff --git a/astrbot/core/star/register/star_handler.py b/astrbot/core/star/register/star_handler.py index 0b9f7ad09..de38eecc2 100644 --- a/astrbot/core/star/register/star_handler.py +++ b/astrbot/core/star/register/star_handler.py @@ -388,3 +388,31 @@ def decorator(awaitable): return awaitable return decorator + + +def register_on_star_activated(star_name: str = None, **kwargs): + """当指定插件被激活时""" + + def decorator(awaitable): + handler_md = get_handler_or_create( + awaitable, EventType.OnStarActivatedEvent, **kwargs + ) + if star_name: + handler_md.extras_configs["target_star_name"] = star_name + return awaitable + + return decorator + + +def register_on_star_deactivated(star_name: str = None, **kwargs): + """当指定插件被停用时""" + + def decorator(awaitable): + handler_md = get_handler_or_create( + awaitable, EventType.OnStarDeactivatedEvent, **kwargs + ) + if star_name: + handler_md.extras_configs["target_star_name"] = star_name + return awaitable + + return decorator diff --git a/astrbot/core/star/star_handler.py b/astrbot/core/star/star_handler.py index d375091e5..2cbe5fbf0 100644 --- a/astrbot/core/star/star_handler.py +++ b/astrbot/core/star/star_handler.py @@ -85,6 +85,8 @@ class EventType(enum.Enum): OnCallingFuncToolEvent = enum.auto() # 调用函数工具 OnAfterMessageSentEvent = enum.auto() # 发送消息后 + OnStarActivatedEvent = enum.auto() # 插件启用 + OnStarDeactivatedEvent = enum.auto() # 插件禁用 @dataclass class StarHandlerMetadata: diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index 3dd4cd1cf..b4774a15e 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -30,6 +30,9 @@ from .star import star_map, star_registry from .star_handler import star_handlers_registry from .updator import PluginUpdator +from .star_handler import EventType, StarHandlerMetadata +from .star_handler import star_map + try: from watchfiles import PythonFilter, awatch @@ -468,6 +471,9 @@ async def load(self, specified_module_path=None, specified_dir_name=None): metadata.star_cls = metadata.star_cls_type( context=self.context ) + await self._trigger_star_lifecycle_event( + EventType.OnStarActivatedEvent, metadata + ) else: logger.info(f"插件 {metadata.name} 已被禁用。") @@ -784,6 +790,10 @@ async def _terminate_plugin(self, star_metadata: StarMetadata): logger.debug(f"插件 {star_metadata.name} 未被激活,不需要终止,跳过。") return + await self._trigger_star_lifecycle_event( + EventType.OnStarDeactivatedEvent, star_metadata + ) + if hasattr(star_metadata.star_cls, "__del__"): asyncio.get_event_loop().run_in_executor( None, star_metadata.star_cls.__del__ @@ -858,3 +868,39 @@ async def install_plugin_from_file(self, zip_file_path: str): } return plugin_info + + async def _trigger_star_lifecycle_event( + self, event_type: EventType, star_metadata: StarMetadata + ): + """ + 内部辅助函数,用于触发插件(Star)相关的生命周期事件。 + Args: + event_type: 要触发的事件类型 (EventType.OnStarActivatedEvent 或 EventType.OnStarDeactivatedEvent)。 + star_metadata: 触发事件的插件的 StarMetadata 对象。 + """ + handlers_to_run: List[StarHandlerMetadata] = [] + # 获取所有监听该事件类型的 handlers + handlers = star_handlers_registry.get_handlers_by_event_type(event_type) + + for handler in handlers: + # 检查这个 handler 是否监听了特定的插件名 + target_star_name = handler.extras_configs.get("target_star_name") + if target_star_name: + # 如果指定了目标插件名,则只在匹配时添加 + if target_star_name == star_metadata.name: + handlers_to_run.append(handler) + else: + # 如果没有指定目标插件名,则添加所有 handler + handlers_to_run.append(handler) + + for handler in handlers_to_run: + try: + # 调用插件的钩子函数,并传入 StarMetadata 对象 + logger.info( + f"hook({event_type.name}) -> {star_map[handler.handler_module_path].name} - {handler.handler_name} (目标插件: {star_metadata.name})" + ) + await handler.handler(star_metadata) # 传递参数 + except Exception: + logger.error( + f"执行插件 {handler.handler_name} 的 {event_type.name} 钩子时出错: {traceback.format_exc()}" + ) From 2340c5c5f815cfbe79338b06033acc7e9c438628 Mon Sep 17 00:00:00 2001 From: Flartiny <1335348298@qq.com> Date: Wed, 25 Jun 2025 21:16:21 +0800 Subject: [PATCH 2/5] feat: Allow dependencies field in metadata.yaml --- astrbot/core/star/star.py | 1 + astrbot/core/star/star_manager.py | 77 +++++++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/astrbot/core/star/star.py b/astrbot/core/star/star.py index 10cf90c8b..d4c70b7a4 100644 --- a/astrbot/core/star/star.py +++ b/astrbot/core/star/star.py @@ -23,6 +23,7 @@ class StarMetadata: desc: str # 插件简介 version: str # 插件版本 repo: str = None # 插件仓库地址 + dependencies: List[str] = field(default_factory=list) star_cls_type: type = None """插件的类对象的类型""" diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index b4774a15e..52a4f5aef 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -11,7 +11,7 @@ import sys import traceback from types import ModuleType -from typing import List +from typing import List, Dict import yaml @@ -31,7 +31,7 @@ from .star_handler import star_handlers_registry from .updator import PluginUpdator from .star_handler import EventType, StarHandlerMetadata -from .star_handler import star_map +from graphlib import TopologicalSorter, CycleError try: @@ -227,6 +227,7 @@ def _load_plugin_metadata(self, plugin_path: str, plugin_obj=None) -> StarMetada desc=metadata["desc"], version=metadata["version"], repo=metadata["repo"] if "repo" in metadata else None, + dependencies=metadata.get("dependencies", []), ) return metadata @@ -378,9 +379,9 @@ async def load(self, specified_module_path=None, specified_dir_name=None): alter_cmd = sp.get("alter_cmd", {}) - plugin_modules = self._get_plugin_modules() + plugin_modules, msg = await self._get_load_order(specified_dir_name, specified_module_path) if plugin_modules is None: - return False, "未找到任何插件模块" + return False, msg fail_rec = "" @@ -904,3 +905,71 @@ async def _trigger_star_lifecycle_event( logger.error( f"执行插件 {handler.handler_name} 的 {event_type.name} 钩子时出错: {traceback.format_exc()}" ) + + def _get_plugin_dir_path(self, root_dir_name: str, is_reserved: bool) -> str: + """根据插件的根目录名和是否为保留插件,返回插件的完整文件路径。""" + return ( + os.path.join(self.plugin_store_path, root_dir_name) + if not is_reserved + else os.path.join(self.reserved_plugin_path, root_dir_name) + ) + + def _build_module_path(self, plugin_module_info: dict) -> str: + """根据插件模块信息构建完整的模块路径。""" + reserved = plugin_module_info.get("reserved", False) + path_prefix = "packages." if reserved else "data.plugins." + return f"{path_prefix}{plugin_module_info['pname']}.{plugin_module_info['module']}" + + async def _get_load_order(self, specified_dir_name: str = None, specified_module_path: str = None): + plugin_modules = self._get_plugin_modules() + if plugin_modules is None: + return None, "未找到任何插件模块" + all_plugins_metadata_map: Dict[str, StarMetadata] = {} + dependency_graph_for_sorter: Dict[str, set[str]] = {} + name2module = {} + reserved_plugins = [] + for plugin_module in plugin_modules: + module_path = self._build_module_path(plugin_module) + root_dir_name = plugin_module["pname"] + is_reserved = plugin_module.get("reserved", False) + plugin_dir_path = self._get_plugin_dir_path(root_dir_name, is_reserved) + if (specified_module_path and module_path != specified_module_path): + continue + if (specified_dir_name and root_dir_name != specified_dir_name): + continue + try: + metadata = self._load_plugin_metadata(plugin_dir_path) + if metadata: + all_plugins_metadata_map[root_dir_name] = metadata + name2module[root_dir_name] = plugin_module + dependency_graph_for_sorter[root_dir_name] = set() + elif is_reserved: + reserved_plugins.append(plugin_module) + else: + logger.warning(f"插件 {root_dir_name} 未找到有效的 metadata.yaml, 跳过加载元数据。") + except Exception as e: + pass + + for root_dir_name, metadata in all_plugins_metadata_map.items(): + for dep_name in metadata.dependencies: + if dep_name not in dependency_graph_for_sorter: + logger.warning(f"插件 {root_dir_name} 声明依赖 {dep_name},但该插件未被发现。") + continue + dependency_graph_for_sorter[dep_name].add(root_dir_name) + + try: + sorter = TopologicalSorter(dependency_graph_for_sorter) + load_order_names = list(sorter.static_order()) + logger.info(f"非系统插件加载顺序:{load_order_names}") + + except CycleError as e: + logger.error(f"插件存在循环依赖,无法确定加载顺序: {e.args[0]}") + return None, f"插件存在循环依赖,无法加载: {e.args[0]}" + except Exception as e: + logger.error(f"拓扑排序失败: {traceback.format_exc()}") + return None, f"拓扑排序失败: {str(e)}" + + load_order_modules = [name2module[name] for name in load_order_names] + load_order_modules = reserved_plugins + load_order_modules + + return load_order_modules, None \ No newline at end of file From 186640603d39dda05e04a74782abc3c93d8395cb Mon Sep 17 00:00:00 2001 From: Flartiny <1335348298@qq.com> Date: Sat, 28 Jun 2025 21:48:07 +0800 Subject: [PATCH 3/5] refactor: Introducing networkx to manage the process of plugin loading --- astrbot/core/star/star_manager.py | 145 +++++++++++++++--------------- pyproject.toml | 1 + requirements.txt | 3 +- 3 files changed, 74 insertions(+), 75 deletions(-) diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index 52a4f5aef..14a0d532f 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -31,8 +31,7 @@ from .star_handler import star_handlers_registry from .updator import PluginUpdator from .star_handler import EventType, StarHandlerMetadata -from graphlib import TopologicalSorter, CycleError - +import networkx as nx try: from watchfiles import PythonFilter, awatch @@ -321,21 +320,11 @@ async def reload(self, specified_plugin_name=None): star_handlers_registry.clear() star_map.clear() star_registry.clear() + plugin_modules = await self._get_load_order() + result = await self.load(plugin_modules=plugin_modules) else: # 只重载指定插件 - smd = star_map.get(specified_module_path) - if smd: - try: - await self._terminate_plugin(smd) - except Exception as e: - logger.warning(traceback.format_exc()) - logger.warning( - f"插件 {smd.name} 未被正常终止: {str(e)}, 可能会导致该插件运行不正常。" - ) - - await self._unbind_plugin(smd.name, specified_module_path) - - result = await self.load(specified_module_path) + result = await self.batch_reload(specified_module_path=specified_module_path) # 更新所有插件的平台兼容性 await self.update_all_platform_compatibility() @@ -361,7 +350,7 @@ async def update_all_platform_compatibility(self): return True - async def load(self, specified_module_path=None, specified_dir_name=None): + async def load(self, plugin_modules=None): """载入插件。 当 specified_module_path 或者 specified_dir_name 不为 None 时,只载入指定的插件。 @@ -379,10 +368,9 @@ async def load(self, specified_module_path=None, specified_dir_name=None): alter_cmd = sp.get("alter_cmd", {}) - plugin_modules, msg = await self._get_load_order(specified_dir_name, specified_module_path) - if plugin_modules is None: - return False, msg - + if not plugin_modules: + return False, "未找到任何插件模块" + logger.info(f"正在按顺序加载插件: {[plugin_module['pname'] for plugin_module in plugin_modules]}") fail_rec = "" # 导入插件模块,并尝试实例化插件类 @@ -398,12 +386,6 @@ async def load(self, specified_module_path=None, specified_dir_name=None): path = "data.plugins." if not reserved else "packages." path += root_dir_name + "." + module_str - # 检查是否需要载入指定的插件 - if specified_module_path and path != specified_module_path: - continue - if specified_dir_name and root_dir_name != specified_dir_name: - continue - logger.info(f"正在载入插件 {root_dir_name} ...") # 尝试导入模块 @@ -632,7 +614,8 @@ async def install_plugin(self, repo_url: str, proxy=""): plugin_path = await self.updator.install(repo_url, proxy) # reload the plugin dir_name = os.path.basename(plugin_path) - await self.load(specified_dir_name=dir_name) + plugin_modules = await self._get_load_order(specified_dir_name=dir_name) + await self.batch_reload(plugin_modules=plugin_modules) # Get the plugin metadata to return repo info plugin = self.context.get_registered_star(dir_name) @@ -836,7 +819,8 @@ async def install_plugin_from_file(self, zip_file_path: str): except BaseException as e: logger.warning(f"删除插件压缩包失败: {str(e)}") # await self.reload() - await self.load(specified_dir_name=dir_name) + plugin_modules = await self._get_load_order(specified_dir_name=dir_name) + await self.batch_reload(plugin_modules=plugin_modules) # Get the plugin metadata to return repo info plugin = self.context.get_registered_star(dir_name) @@ -886,12 +870,8 @@ async def _trigger_star_lifecycle_event( for handler in handlers: # 检查这个 handler 是否监听了特定的插件名 target_star_name = handler.extras_configs.get("target_star_name") - if target_star_name: + if target_star_name and target_star_name == star_metadata.name: # 如果指定了目标插件名,则只在匹配时添加 - if target_star_name == star_metadata.name: - handlers_to_run.append(handler) - else: - # 如果没有指定目标插件名,则添加所有 handler handlers_to_run.append(handler) for handler in handlers_to_run: @@ -921,55 +901,72 @@ def _build_module_path(self, plugin_module_info: dict) -> str: return f"{path_prefix}{plugin_module_info['pname']}.{plugin_module_info['module']}" async def _get_load_order(self, specified_dir_name: str = None, specified_module_path: str = None): + star_graph = self._build_star_graph() + if star_graph is None: + return None + try: + if specified_dir_name: + for node in star_graph: + if star_graph.nodes[node]["data"].get("pname") == specified_dir_name: + dependent_nodes = nx.descendants(star_graph, node) + sub_graph = star_graph.subgraph(dependent_nodes.union({node})) + load_order = list(nx.topological_sort(sub_graph)) + return [star_graph.nodes[node]["data"] for node in load_order] + elif specified_module_path: + for node in star_graph: + if specified_module_path == self._build_module_path(star_graph.nodes[node].get("data")): + dependent_nodes = nx.descendants(star_graph, node) + sub_graph = star_graph.subgraph(dependent_nodes.union({node})) + load_order = list(nx.topological_sort(sub_graph)) + return [star_graph.nodes[node]["data"] for node in load_order] + else: + return [star_graph.nodes[node]["data"] for node in list(nx.topological_sort(star_graph))] + except nx.NetworkXUnfeasible: + logger.error("出现循环依赖,无法确定加载顺序,按自然顺序加载") + return [star_graph.nodes[node]["data"] for node in star_graph] + + def _build_star_graph(self): plugin_modules = self._get_plugin_modules() if plugin_modules is None: - return None, "未找到任何插件模块" - all_plugins_metadata_map: Dict[str, StarMetadata] = {} - dependency_graph_for_sorter: Dict[str, set[str]] = {} - name2module = {} - reserved_plugins = [] + return None + G = nx.DiGraph() for plugin_module in plugin_modules: - module_path = self._build_module_path(plugin_module) root_dir_name = plugin_module["pname"] is_reserved = plugin_module.get("reserved", False) plugin_dir_path = self._get_plugin_dir_path(root_dir_name, is_reserved) - if (specified_module_path and module_path != specified_module_path): - continue - if (specified_dir_name and root_dir_name != specified_dir_name): - continue + G.add_node(root_dir_name, data=plugin_module) try: metadata = self._load_plugin_metadata(plugin_dir_path) if metadata: - all_plugins_metadata_map[root_dir_name] = metadata - name2module[root_dir_name] = plugin_module - dependency_graph_for_sorter[root_dir_name] = set() - elif is_reserved: - reserved_plugins.append(plugin_module) - else: - logger.warning(f"插件 {root_dir_name} 未找到有效的 metadata.yaml, 跳过加载元数据。") - except Exception as e: + for dep_name in metadata.dependencies: + G.add_edge(root_dir_name, dep_name) + except Exception: pass + # 过滤不存在的依赖(出边没有data, 就删除指向的节点) + nodes_to_remove = [] + for node_name in list(G.nodes()): + for neighbor in list(G.neighbors(node_name)): + if G.nodes[neighbor].get("data") is None: + nodes_to_remove.append(neighbor) + logger.warning(f"插件 {node_name} 声明依赖 {neighbor}, 但该插件未被发现,跳过加载。") + for node in nodes_to_remove: + G.remove_node(node) + return G + + async def batch_reload(self, specified_module_path = None, plugin_modules = None): + if not plugin_modules: + plugin_modules = await self._get_load_order(specified_module_path = specified_module_path) + for plugin_module in plugin_modules: + specified_module_path = self._build_module_path(plugin_module) + smd = star_map.get(specified_module_path) + if smd: + try: + await self._terminate_plugin(smd) + except Exception as e: + logger.warning(traceback.format_exc()) + logger.warning( + f"插件 {smd.name} 未被正常终止: {str(e)}, 可能会导致该插件运行不正常。" + ) + await self._unbind_plugin(smd.name, specified_module_path) - for root_dir_name, metadata in all_plugins_metadata_map.items(): - for dep_name in metadata.dependencies: - if dep_name not in dependency_graph_for_sorter: - logger.warning(f"插件 {root_dir_name} 声明依赖 {dep_name},但该插件未被发现。") - continue - dependency_graph_for_sorter[dep_name].add(root_dir_name) - - try: - sorter = TopologicalSorter(dependency_graph_for_sorter) - load_order_names = list(sorter.static_order()) - logger.info(f"非系统插件加载顺序:{load_order_names}") - - except CycleError as e: - logger.error(f"插件存在循环依赖,无法确定加载顺序: {e.args[0]}") - return None, f"插件存在循环依赖,无法加载: {e.args[0]}" - except Exception as e: - logger.error(f"拓扑排序失败: {traceback.format_exc()}") - return None, f"拓扑排序失败: {str(e)}" - - load_order_modules = [name2module[name] for name in load_order_names] - load_order_modules = reserved_plugins + load_order_modules - - return load_order_modules, None \ No newline at end of file + return await self.load(plugin_modules=plugin_modules) diff --git a/pyproject.toml b/pyproject.toml index e12abbcdd..01755b6f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ dependencies = [ "watchfiles>=1.0.5", "websockets>=15.0.1", "wechatpy>=1.8.18", + "networkx>=3.4.2", ] [project.scripts] diff --git a/requirements.txt b/requirements.txt index 5349338d6..fd94bcfdb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,4 +39,5 @@ faiss-cpu aiosqlite nh3 py-cord>=2.6.1 -slack-sdk \ No newline at end of file +slack-sdk +networkx \ No newline at end of file From 2783cc00a725d742e6f36c05d37eed6ab2bb31a8 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 20 Aug 2025 15:41:30 +0800 Subject: [PATCH 4/5] chore: clean code --- astrbot/core/star/star_manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index 714205dab..91e7d8f97 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -371,7 +371,6 @@ async def load(self, plugin_modules=None): inactivated_llm_tools = await sp.global_get("inactivated_llm_tools", []) alter_cmd = await sp.global_get("alter_cmd", {}) - plugin_modules = self._get_plugin_modules() if plugin_modules is None: return False, "未找到任何插件模块" logger.info( From 7f8d0e8e49fc5fbbc54ab1aa53dde9c153a8e5c3 Mon Sep 17 00:00:00 2001 From: Raven95676 Date: Wed, 20 Aug 2025 17:16:34 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E9=A1=BA=E5=BA=8F=EF=BC=8C=E7=A1=AE=E4=BF=9D?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E6=8F=92=E4=BB=B6=E4=BC=98=E5=85=88=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/star/star_manager.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index 2dce933f0..2a952cf80 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -331,7 +331,7 @@ async def reload(self, specified_plugin_name=None): ) return result - + async def load(self, plugin_modules=None): """载入插件。 当 specified_module_path 或者 specified_dir_name 不为 None 时,只载入指定的插件。 @@ -937,10 +937,21 @@ async def _get_load_order( load_order = list(nx.topological_sort(sub_graph)) return [star_graph.nodes[node]["data"] for node in load_order] else: - return [ + sorted_nodes = list(nx.topological_sort(star_graph)) + + reserved_plugins = [ + star_graph.nodes[node]["data"] + for node in sorted_nodes + if star_graph.nodes[node]["data"].get("reserved", False) + ] + non_reserved_plugins = [ star_graph.nodes[node]["data"] - for node in list(nx.topological_sort(star_graph)) + for node in sorted_nodes + if not star_graph.nodes[node]["data"].get("reserved", False) ] + + return reserved_plugins + non_reserved_plugins + except nx.NetworkXUnfeasible: logger.error("出现循环依赖,无法确定加载顺序,按自然顺序加载") return [star_graph.nodes[node]["data"] for node in star_graph]