feat: add plugin load&unload hook#5331
Conversation
Summary of ChangesHello @PyuraMazo, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 此拉取请求旨在通过引入插件加载和卸载的生命周期钩子,增强 AstrBot 的插件管理能力。这些钩子为其他插件提供了监听和响应插件列表变化的能力,从而可以更方便地实现对插件状态的监控、通知用户插件变更,以及对特定属性插件进行特殊化管理。这一改动提升了系统的可扩展性和灵活性,而不会引入破坏性变更。 Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Hey - 我在这里给出了一些整体性的反馈:
star_manager.py中用于插件加载/卸载钩子的处理器调用逻辑几乎完全相同;可以考虑抽取一个小的辅助函数(例如_dispatch_plugin_lifecycle_event(event_type, metadata))来避免重复,并让未来新增的生命周期钩子保持一致。- PR 描述里的注释提到在插件卸载“之前”触发卸载钩子,但在
_terminate_plugin中,钩子是在调用terminate之后才被触发;最好在代码或 docstring 中把这种调用顺序明确记录下来,以便插件作者知道他们看到的是终止前还是终止后的插件状态。
面向 AI Agent 的提示词
Please address the comments from this code review:
## Overall Comments
- The handler invocation logic for plugin load/unload hooks in `star_manager.py` is almost identical; consider extracting a small helper (e.g. `_dispatch_plugin_lifecycle_event(event_type, metadata)`) to avoid duplication and keep future lifecycle hooks consistent.
- The comment in the PR description mentions firing the unload hook "before" plugin unload, but in `_terminate_plugin` the hook is dispatched after `terminate` is called; it would be good to explicitly document this ordering in the code/docstring so plugin authors know whether they see the plugin pre- or post-termination.帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据这些反馈来改进今后的代码审查。
Original comment in English
Hey - I've left some high level feedback:
- The handler invocation logic for plugin load/unload hooks in
star_manager.pyis almost identical; consider extracting a small helper (e.g._dispatch_plugin_lifecycle_event(event_type, metadata)) to avoid duplication and keep future lifecycle hooks consistent. - The comment in the PR description mentions firing the unload hook "before" plugin unload, but in
_terminate_pluginthe hook is dispatched afterterminateis called; it would be good to explicitly document this ordering in the code/docstring so plugin authors know whether they see the plugin pre- or post-termination.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The handler invocation logic for plugin load/unload hooks in `star_manager.py` is almost identical; consider extracting a small helper (e.g. `_dispatch_plugin_lifecycle_event(event_type, metadata)`) to avoid duplication and keep future lifecycle hooks consistent.
- The comment in the PR description mentions firing the unload hook "before" plugin unload, but in `_terminate_plugin` the hook is dispatched after `terminate` is called; it would be good to explicitly document this ordering in the code/docstring so plugin authors know whether they see the plugin pre- or post-termination.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Code Review
This pull request introduces two key event hooks, OnPluginLoadedEvent and OnPluginUnloadedEvent, for plugin lifecycle management, enabling developers to monitor plugin list changes. However, the current implementation of triggering these hooks in star_manager.py is vulnerable to Denial of Service (DoS) attacks, as malicious plugins could block the entire plugin management system or crash the bot process. It is recommended to execute these hooks concurrently with a timeout and ensure all exceptions are caught to maintain system stability. Additionally, consider adding type overloading in StarHandlerRegistry and optimizing error logging for better observability.
| for handler in handlers: | ||
| try: | ||
| logger.info( | ||
| f"hook(on_plugin_loaded) -> {star_map[handler.handler_module_path].name} - {handler.handler_name}", | ||
| ) | ||
| await handler.handler(metadata) | ||
| except Exception: | ||
| logger.error(traceback.format_exc()) |
There was a problem hiding this comment.
The newly introduced plugin lifecycle hooks are executed sequentially and awaited while holding the global plugin management lock (_pm_lock). This creates a Denial of Service (DoS) vulnerability, as a malicious or poorly implemented plugin could indefinitely block the entire plugin system by providing a hook that never returns. Additionally, when triggering plugin load hooks, it's recommended to include the plugin's name (metadata.name) in the logs and explicitly state which plugin's hook failed during exception handling to improve observability and troubleshooting.
logger.info(
f"hook(on_plugin_loaded): plugin {metadata.name} loaded -> handled by {star_map[handler.handler_module_path].name}.{handler.handler_name}",
)
await handler.handler(metadata)
except Exception as e:
logger.error(f"Error in on_plugin_loaded hook of plugin {star_map[handler.handler_module_path].name}: {e}")
logger.error(traceback.format_exc())| for handler in handlers: | ||
| try: | ||
| logger.info( | ||
| f"hook(on_plugin_unloaded) -> {star_map[handler.handler_module_path].name} - {handler.handler_name}", | ||
| ) | ||
| await handler.handler(star_metadata) | ||
| except Exception: | ||
| logger.error(traceback.format_exc()) |
There was a problem hiding this comment.
In the _terminate_plugin method, the hook execution is wrapped in a try...except Exception block, which does not catch BaseException types like SystemExit. A malicious plugin could register an on_plugin_unloaded hook that raises SystemExit, causing the entire bot process to terminate during a plugin reload or uninstallation. It is recommended to catch BaseException here or execute hooks in a way that they cannot crash the main process.
|
Generated docs update PR (pending manual review): AI change summary:
Experimental bot notice:
|
添加了两个事件钩子:插件加载和插件卸载。这两个钩子达到了监测插件生命周期的效果,为其它插件对插件列表变化的处理提供了更方便简洁的获取方式。
目前可以想到的作用包括:
本来还打算添加一个插件配置更新的钩子,但是这个钩子可以被上述两个钩子结合实现相同甚至更广泛的效果,因为通过增删的插件元数据很容易获取到变动插件的配置内容,甚至更多其它有用的效果。
Modifications / 改动点
新增功能:
修改的核心文件:
- 在 EventType 枚举中添加了两个新事件类型
- 在 get_handlers_by_event_type 方法的过滤逻辑中添加了新事件类型的白名单
- 新增 register_on_plugin_loaded() 装饰器
- 新增 register_on_plugin_unloaded() 装饰器
- 两个装饰器都接收 metadata(StarMetadata)参数
- 在插件加载完成后触发 OnPluginLoadedEvent 事件
- 在插件卸载完成前触发 OnPluginUnloadedEvent 事件
- 导入并导出 on_plugin_loaded 和 on_plugin_unloaded 装饰器
- 导出 register_on_plugin_loaded 和 register_on_plugin_unloaded
Screenshots or Test Results / 运行截图或测试结果
AstrBot启动时:

重载插件时:

Checklist / 检查清单
requirements.txt和pyproject.toml文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations inrequirements.txtandpyproject.toml.Summary by Sourcery
为插件加载和卸载添加插件生命周期钩子事件,并提供装饰器,便于其他插件对这些事件作出响应。
新特性:
OnPluginLoadedEvent和OnPluginUnloadedEvent事件类型,用于表示插件加载和卸载的生命周期阶段。register_on_plugin_loaded和register_on_plugin_unloaded装饰器,并通过公共事件过滤器 API 重新导出它们,以便插件可以为这些生命周期事件注册处理程序。Original summary in English
Summary by Sourcery
Add plugin lifecycle hook events for plugin load and unload and expose decorators for other plugins to react to these events.
New Features: