Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions astrbot/core/star/star_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -996,7 +996,7 @@ async def reload(self, specified_plugin_name=None):
logger.warning(
f"插件 {smd.name} 未被正常终止: {e!s}, 可能会导致该插件运行不正常。",
)
if smd.name and smd.module_path:
if smd.name and smd.module_path and smd.activated:
await self._unbind_plugin(smd.name, smd.module_path)
Comment on lines +999 to 1000

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

在重载所有插件(specified_plugin_nameNone)的情况下,star_mapstar_registry 会被完全清空(star_map.clear()star_registry.clear())。

如果在此处对已停用的插件跳过 _unbind_plugin,其对应的模块将不会从 sys.modules 中被清除。当后续调用 load() 时,由于 star_map 已被清空,且模块由于缓存在 sys.modules 中而不会重新执行(导致类装饰器/子类钩子无法再次运行来注册插件),load() 将无法在 star_map 中找到该插件。这会迫使 load() 回退到旧版的加载机制,如果插件类名不符合旧版命名规范(如以 Plugin 结尾或命名为 Main),将会抛出异常导致加载失败。

因此,在全量重载时,不应该跳过 _unbind_plugin

Suggested change
if smd.name and smd.module_path and smd.activated:
await self._unbind_plugin(smd.name, smd.module_path)
if smd.name and smd.module_path:
await self._unbind_plugin(smd.name, smd.module_path)

@irmia2026 irmia2026 Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review! However, this concern is not applicable here — I've verified it through source analysis.

_unbind_plugin (L1769-1820) never touches sys.modules. It only:

  • Deletes from star_map / star_registry
  • Removes handlers from star_handlers_registry
  • Removes tools from llm_tools.func_list
  • Unregisters platform adapters

There is no import importlib, no sys.modules, no reload() call anywhere in the function body.

Additionally, in the full reload path, star_map.clear() and star_registry.clear() are called right after the unbind loop, so any skipped entries are still cleared.

And regarding consistency: _terminate_plugin already has the same guard (if not star_metadata.activated: return) at L1882. Keeping _unbind_plugin aligned with that pattern is the safer design.


star_handlers_registry.clear()
Expand All @@ -1013,7 +1013,7 @@ async def reload(self, specified_plugin_name=None):
logger.warning(
f"插件 {smd.name} 未被正常终止: {e!s}, 可能会导致该插件运行不正常。",
)
if smd.name:
if smd.name and smd.activated:
await self._unbind_plugin(smd.name, specified_module_path)
Comment on lines +1016 to 1017

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

在单插件重载的情况下,如果该插件之前已被停用,跳过 _unbind_plugin 会导致现有的 StarMetadata 对象被保留并复用。

然而,在 load() 方法中,只有当插件在 inactivated_plugins 中时才会将其 metadata.activated 设为 False,但从未在启用时将其设为 True。这意味着,当用户重新启用该插件(turn_on_plugin)并触发重载时,虽然插件类会被实例化,但其 activated 状态仍将保持为 False。这会导致该插件的所有事件处理器在 get_handlers_by_event_type 中被过滤掉,从而使插件完全无法工作。

建议在跳过 _unbind_plugin 的同时,检查该插件是否已被移出 inactivated_plugins,如果是,则将其 activated 状态恢复为 True

Suggested change
if smd.name and smd.activated:
await self._unbind_plugin(smd.name, specified_module_path)
if smd.name and smd.activated:
await self._unbind_plugin(smd.name, specified_module_path)
elif smd.name:
inactivated_plugins = await sp.global_get("inactivated_plugins", [])
if specified_module_path not in inactivated_plugins:
smd.activated = True

@irmia2026 irmia2026 Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch on the surface, but the underlying mechanism already handles this correctly. Let me explain:

load() always creates a fresh StarMetadata and overwrites star_map.

At L1294: star_map[path] = metadata — this unconditionally overwrites whatever was in star_map with the newly created metadata object.

At L1299: if metadata.module_path in inactivated_plugins: metadata.activated = False — it only flips to False for inactivated plugins.

The key insight: StarMetadata.activated defaults to True (defined at star.py:51: activated: bool = True). So when load() creates a fresh metadata and the plugin is NOT in inactivated_plugins (i.e., after turn_on_plugin), activated stays True automatically.

Scenario trace:

  1. Plugin is inactivated → reload() with our fix skips _unbind_pluginload() creates fresh metadata → activated = False
  2. User calls turn_on_plugin → plugin removed from inactivated_pluginsreload() skips _unbind_pluginload() creates fresh new metadata → activated = True ✅ (not in inactivated_plugins)

The old metadata's activated=False state is irrelevant because star_map[path] = metadata overwrites it every time.


result = await self.load(specified_module_path)
Expand Down
Loading