Skip to content

[Bug] turn_off_plugin / turn_on_plugin 的 startswith 方向写反 #8583

@irmia2026

Description

@irmia2026

What happened / 发生了什么

star_manager.pyturn_off_plugin(停用插件时自动停用其工具)和 turn_on_plugin(启用插件时自动恢复其工具)各有一处 startswith 方向写反了,导致这两个方法的核心逻辑——判断"某工具是否属于某插件"——对大部分工具恒为 False,deactivate/activate 形同虚设。

同一文件 _unbind_plugin 使用了正确的方向,三者本应共享完全相同的判断逻辑。


根因:三处同一逻辑,两个方向反了

下面是 star_manager.py 中三处判断"工具 mp 是否属于插件 plugin"的位置:

行号 方法 判断条件 方向
1633 _unbind_plugin mp.startswith(plugin_module_path) ✅ 正确
1708 turn_off_plugin plugin.module_path.startswith(mp) ❌ 反
1783 turn_on_plugin plugin.module_path.startswith(mp) ❌ 反

L1708turn_off_plugin(停用插件时遍历 func_list 设 active=False):

mp = func_tool.handler_module_path
# 子模块中注册的工具,mp 可能是:
#   - None(@llm_tool 装饰器路径,load() 中 handler.__module__ 与
#           metadata.module_path 不匹配,未覆写 handler_module_path)
#   - "astrbot.core.agent.tool"(直接 FunctionTool() 实例,add_llm_tools
#           读的是 FunctionTool 类自身的 __module__)
#   - "data.plugins.xxx.tools.safe_edit"(插件子类的实例,add_llm_tools
#           从 tool.__module__ 取到了子模块路径)
# 这三种值与 plugin.module_path(如 "data.plugins.xxx.main")均不等同
# 插件的 module_path = "data.plugins.xxx.main"

if (
    plugin.module_path
    and mp
    and plugin.module_path.startswith(mp)  # ← 方向反了
    # 前两种场景 mp 为 None 或 "astrbot.core.agent.tool"
    #   → mp 为 None 时短路于 and mp → 永假
    #   → "astrbot.core.agent.tool" 不含插件目录结构 → 永假
    # 第三种场景(mp = "data.plugins.xxx.tools.safe_edit"):
    #   "data.plugins.xxx.main"
    #       .startswith("data.plugins.xxx.tools.safe_edit")
    #   → False  ← 方向反了,永远匹配不上
    and not mp.endswith(("astrbot.builtin_stars", "data.plugins"))
):
    func_tool.active = False
    # 原本应该在这里写入 inactivated_llm_tools

L1783turn_on_plugin(启用插件时遍历 func_list 恢复 active=True 并清理黑名单):条件完全相同,方向同样反了。

L1633_unbind_plugin(重载时卸载工具,仅作参照):

mp.startswith(plugin_module_path)
# 无论 mp 取什么值(None / 错误模块 / 子模块路径),
# 对比"正确方向"与 L1708/L1783 可知:_unbind_plugin 的方向是对的,
# L1708/L1783 的方向是反的。至于 mp 值本身可能也不正确的问题,
# 是另一个 bug(handler_module_path 前缀缺失,见 #8578),
# 与 startswith 方向属于独立问题。

语义上,"工具 X 是否属于插件 Y"的正确判断是:工具的 handler_module_path 应始于插件的 module_path。即 mp.startswith(plugin.module_path)

L1708 和 L1783 写成了 plugin.module_path.startswith(mp),等于在问"插件的 module_path 是否始于工具的 handler_module_path"——这在任何正常场景下都不成立。


为什么通常没有被发现

  • 当工具的 handler 定义在插件主模块(main.py / __init__.py)中时,load():1084 会覆写 handler_module_path = metadata.module_path,两根串完全相同,"A".startswith("A") 两个方向都返回 True,bug 不暴露。
  • 当工具在子模块(tools/ 目录)中定义时,handler_module_path 不受 L1084 修正(handler.__module__ != metadata.module_path),保持默认 None 或被 add_llm_tools 设为错误值。此时反向的 startswith 立即暴露。

根因:三处同一逻辑,两个方向反了

下面是 star_manager.py 中三处判断"工具 mp 是否属于插件 plugin"的位置:

行号 方法 判断条件 方向
1633 _unbind_plugin mp.startswith(plugin_module_path) ✅ 正确
1708 turn_off_plugin plugin.module_path.startswith(mp) ❌ 反
1783 turn_on_plugin plugin.module_path.startswith(mp) ❌ 反

L1708turn_off_plugin(停用插件时遍历 func_list 设 active=False):

mp = func_tool.handler_module_path
# 子模块中注册的工具,mp 可能是:
#   - None(@llm_tool 装饰器路径,load() 中 handler.__module__ 与
#           metadata.module_path 不匹配,未覆写 handler_module_path)
#   - "astrbot.core.agent.tool"(直接 FunctionTool() 实例,add_llm_tools
#           读的是 FunctionTool 类自身的 __module__)
#   - "data.plugins.xxx.tools.safe_edit"(插件子类的实例,add_llm_tools
#           从 tool.__module__ 取到了子模块路径)
# 这三种值与 plugin.module_path(如 "data.plugins.xxx.main")均不等同
# 插件的 module_path = "data.plugins.xxx.main"

if (
    plugin.module_path
    and mp
    and plugin.module_path.startswith(mp)  # ← 方向反了
    # 前两种场景 mp 为 None 或 "astrbot.core.agent.tool"
    #   → mp 为 None 时短路于 and mp → 永假
    #   → "astrbot.core.agent.tool" 不含插件目录结构 → 永假
    # 第三种场景(mp = "data.plugins.xxx.tools.safe_edit"):
    #   "data.plugins.xxx.main"
    #       .startswith("data.plugins.xxx.tools.safe_edit")
    #   → False  ← 方向反了,永远匹配不上
    and not mp.endswith(("astrbot.builtin_stars", "data.plugins"))
):
    func_tool.active = False
    # 原本应该在这里写入 inactivated_llm_tools

L1783turn_on_plugin(启用插件时遍历 func_list 恢复 active=True 并清理黑名单):条件完全相同,方向同样反了。

L1633_unbind_plugin(重载时卸载工具,仅作参照):

mp.startswith(plugin_module_path)
# 无论 mp 取什么值(None / 错误模块 / 子模块路径),
# 对比"正确方向"与 L1708/L1783 可知:_unbind_plugin 的方向是对的,
# L1708/L1783 的方向是反的。至于 mp 值本身可能也不正确的问题,
# 是另一个 bug(handler_module_path 前缀缺失,见 #8578),
# 与 startswith 方向属于独立问题。

语义上,"工具 X 是否属于插件 Y"的正确判断是:工具的 handler_module_path 应始于插件的 module_path。即 mp.startswith(plugin.module_path)

L1708 和 L1783 写成了 plugin.module_path.startswith(mp),等于在问"插件的 module_path 是否始于工具的 handler_module_path"——这在任何正常场景下都不成立。


为什么通常没有被发现

  • 当工具的 handler 定义在插件主模块(main.py / __init__.py)中时,load():1084 会覆写 handler_module_path = metadata.module_path,两根串完全相同,"A".startswith("A") 两个方向都返回 True,bug 不暴露。
  • 当工具在子模块(tools/ 目录)中定义时,handler_module_path 不受 L1084 修正(handler.__module__ != metadata.module_path),保持默认 None 或被 add_llm_tools 设为错误值。此时反向的 startswith 立即暴露。

涉及文件

文件 行号 说明
astrbot/core/star/star_manager.py 1708 turn_off_plugin — 反方向 plugin.module_path.startswith(mp)
astrbot/core/star/star_manager.py 1783 turn_on_plugin — 反方向 plugin.module_path.startswith(mp)
astrbot/core/star/star_manager.py 1633 _unbind_plugin — 正确方向(供参照)

Expected behavior / 期望行为

两处改为 mp.startswith(plugin.module_path)

# turn_off_plugin (L1708) & turn_on_plugin (L1783)
if (
    plugin.module_path
    and mp
    and mp.startswith(plugin.module_path)  # ← 修正方向
    and not mp.endswith(("astrbot.builtin_stars", "data.plugins"))
):

注意:仅修正方向不够——还需要 handler_module_pathmodule_path 共享前缀(如 data.plugins.),否则 startswith 匹配仍然失败。该前缀问题见 #8578


Reproduce / 如何复现?

  1. 部署一个注册了函数工具的插件(工具类放在 tools/ 子目录中)
  2. 在 WebUI 中停用该插件
  3. 检查「函数工具管理」→ 工具仍显示为启用状态active=True
  4. 在 WebUI 中重新启用该插件
  5. 如果此前手动关闭过工具,检查「函数工具管理」→ 工具仍显示为关闭状态active=False),未被恢复

WebUI 中「函数工具管理」页面的单个工具开关不受此 bug 影响——它走 activate_llm_tool / deactivate_llm_tool 独立路径。


AstrBot version, deployment method (e.g., Windows Docker Desktop deployment), provider used, and messaging platform used. / AstrBot 版本、部署方式(如 Windows Docker Desktop 部署)、使用的提供商、使用的消息平台适配器

AstrBot 版本 v4.24.2
Python 版本 3.12.12
操作系统 Windows 11

OS

Windows

Logs / 报错日志

无显式报错。

Are you willing to submit a PR? / 你愿意提交 PR 吗?

  • Yes!

Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:coreThe bug / feature is about astrbot's core, backendbugSomething isn't workingfeature:pluginThe bug / feature is about AstrBot plugin system.priority: p1

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions