What happened / 发生了什么
star_manager.py 中 turn_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) |
❌ 反 |
L1708 — turn_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
L1783 — turn_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) |
❌ 反 |
L1708 — turn_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
L1783 — turn_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_path 与 module_path 共享前缀(如 data.plugins.),否则 startswith 匹配仍然失败。该前缀问题见 #8578。
Reproduce / 如何复现?
- 部署一个注册了函数工具的插件(工具类放在
tools/ 子目录中)
- 在 WebUI 中停用该插件
- 检查「函数工具管理」→ 工具仍显示为启用状态(
active=True)
- 在 WebUI 中重新启用该插件
- 如果此前手动关闭过工具,检查「函数工具管理」→ 工具仍显示为关闭状态(
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 吗?
Code of Conduct
What happened / 发生了什么
star_manager.py中turn_off_plugin(停用插件时自动停用其工具)和turn_on_plugin(启用插件时自动恢复其工具)各有一处startswith方向写反了,导致这两个方法的核心逻辑——判断"某工具是否属于某插件"——对大部分工具恒为 False,deactivate/activate 形同虚设。同一文件
_unbind_plugin使用了正确的方向,三者本应共享完全相同的判断逻辑。根因:三处同一逻辑,两个方向反了
下面是
star_manager.py中三处判断"工具 mp 是否属于插件 plugin"的位置:_unbind_pluginmp.startswith(plugin_module_path)turn_off_pluginplugin.module_path.startswith(mp)turn_on_pluginplugin.module_path.startswith(mp)L1708 —
turn_off_plugin(停用插件时遍历 func_list 设active=False):L1783 —
turn_on_plugin(启用插件时遍历 func_list 恢复active=True并清理黑名单):条件完全相同,方向同样反了。L1633 —
_unbind_plugin(重载时卸载工具,仅作参照):语义上,"工具 X 是否属于插件 Y"的正确判断是:工具的 handler_module_path 应始于插件的 module_path。即
mp.startswith(plugin.module_path)。L1708 和 L1783 写成了
plugin.module_path.startswith(mp),等于在问"插件的 module_path 是否始于工具的 handler_module_path"——这在任何正常场景下都不成立。为什么通常没有被发现
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"的位置:_unbind_pluginmp.startswith(plugin_module_path)turn_off_pluginplugin.module_path.startswith(mp)turn_on_pluginplugin.module_path.startswith(mp)L1708 —
turn_off_plugin(停用插件时遍历 func_list 设active=False):L1783 —
turn_on_plugin(启用插件时遍历 func_list 恢复active=True并清理黑名单):条件完全相同,方向同样反了。L1633 —
_unbind_plugin(重载时卸载工具,仅作参照):语义上,"工具 X 是否属于插件 Y"的正确判断是:工具的 handler_module_path 应始于插件的 module_path。即
mp.startswith(plugin.module_path)。L1708 和 L1783 写成了
plugin.module_path.startswith(mp),等于在问"插件的 module_path 是否始于工具的 handler_module_path"——这在任何正常场景下都不成立。为什么通常没有被发现
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.pyturn_off_plugin— 反方向plugin.module_path.startswith(mp)astrbot/core/star/star_manager.pyturn_on_plugin— 反方向plugin.module_path.startswith(mp)astrbot/core/star/star_manager.py_unbind_plugin— 正确方向(供参照)Expected behavior / 期望行为
两处改为
mp.startswith(plugin.module_path):注意:仅修正方向不够——还需要
handler_module_path与module_path共享前缀(如data.plugins.),否则startswith匹配仍然失败。该前缀问题见 #8578。Reproduce / 如何复现?
tools/子目录中)active=True)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 部署)、使用的提供商、使用的消息平台适配器
OS
Windows
Logs / 报错日志
无显式报错。
Are you willing to submit a PR? / 你愿意提交 PR 吗?
Code of Conduct