Skip to content

fix(zulip): 修复 Zulip 通道附件上传与消息发送的 BUG,支持 Bot 自动订阅频道#480

Merged
pancacake merged 8 commits into
HKUDS:devfrom
wedone:fix/zulip-upload-and-send
May 14, 2026
Merged

fix(zulip): 修复 Zulip 通道附件上传与消息发送的 BUG,支持 Bot 自动订阅频道#480
pancacake merged 8 commits into
HKUDS:devfrom
wedone:fix/zulip-upload-and-send

Conversation

@wedone
Copy link
Copy Markdown
Contributor

@wedone wedone commented May 12, 2026

Description

修复 Zulip 通道在处理附件上传和消息发送时存在的四个 BUG,并新增 Bot 自动订阅频道功能。

BUG 1: 附件上传报错 str object has no attribute name

  • 根因: _upload_and_send 将文件路径字符串直接传入 client.call_endpoint(files=...),但 Zulip Python 客户端期望文件对象列表(内部执行 f.name),导致 AttributeError
  • 修复: 使用 open(media_path, "rb") 打开文件后传入文件对象

BUG 2: 附件重发时 Zulip 上传路径被当作本地文件路径

  • 根因: LLM 回复时使用消息内容中的 Zulip 服务器路径(如 /user_uploads/...)作为 media 参数,而非本地已下载的文件路径,导致 open() 报错 No such file or directory
  • 修复: 新增 _resolve_media_path 方法,将 Zulip 上传路径解析为本地文件路径(优先查找已下载副本,否则重新下载)

BUG 3: 附件发送报错 Message must have recipients

  • 根因: MessageTool 创建的 OutboundMessage 只包含 {"message_id": ...},缺少 msg_type/stream/topic/recipient_user_id 等 Zulip 路由字段,导致 _build_send_request 生成空收件人列表。_recipient_map 在收到消息时已存储完整路由元数据,但从未被读取使用
  • 修复: 在 send() 中,当 metadata 缺少 msg_type 时,从 _recipient_map 查找对应 chat_id 的路由信息并合并

BUG 4: 附件发送时产生重复的 message("...") 格式文本

  • 根因: LLM 调用 message 工具时,agent loop 先发送 _tool_hint 进度消息(如 message("内容")),然后 MessageTool 才发送真正的消息。配置中 send_tool_hints 默认为 False,但 _outbound_router 未检查该标志
  • 修复: 在 send() 入口处过滤 _tool_hint 消息,直接返回不发送

FEATURE: 支持 Bot 自动订阅频道以接收话题中的 @mention 消息

  • 问题: 在 Zulip 话题中 @机器人没有反应,deeptutor 容器无日志
  • 根因: Bot 未订阅频道,Zulip 服务器不会推送频道消息事件给未订阅的 Bot
  • 修复:
    • ZulipConfig 新增 subscribe_streams 配置项,支持指定频道名或通配符 '*'
    • start() 中新增 _subscribe_to_streams() 自动订阅频道
    • _subscribe_to_streams() 支持异常保护,订阅失败不阻断 Bot 启动
    • 通配符 '*' 与显式频道名混合使用时合并处理
    • _on_message() 添加调试日志便于排查
    • _bot_full_name 属性用于后续 @mention 内容匹配

Related Issues

  • Closes #...
  • Related to #...

Module(s) Affected

  • agents
  • api
  • config
  • core
  • knowledge
  • logging
  • services
  • tools
  • utils
  • web (Frontend)
  • docs (Documentation)
  • scripts
  • tests
  • Other: channels

Checklist

  • I have read and followed the contribution guidelines.
  • My code follows the project's coding standards.
  • I have run pre-commit run --all-files and fixed any issues.
  • I have added relevant tests for my changes.
  • I have updated the documentation (if necessary).
  • My changes do not introduce any new security vulnerabilities.

Additional Notes

涉及 deeptutor/tutorbot/channels/zulip.pytests/services/tutorbot/test_zulip_channel.py 两个文件,改动最小化,不影响其他通道(Matrix、飞书等)的行为。

@wedone wedone changed the title fix(zulip): 修复 Zulip 通道附件上传与消息发送的四个 BUG fix(zulip): 修复 Zulip 通道附件上传与消息发送的 BUG May 12, 2026
wedone added 6 commits May 14, 2026 10:29
Zulip Python 客户端的 call_endpoint 方法期望 files 参数接收文件对象列表(有 .name 属性),但代码错误地传入了文件路径字符串列表,导致附件上传时触发 AttributeError。

修复方案:在调用 call_endpoint 前使用 open() 打开文件,传递文件对象给 Zulip 客户端,确保 with 语句在使用完毕后正确关闭文件。

Closes #附件上传失败问题
LLM 回复时可能使用消息内容中的 Zulip 上传路径(如 /user_uploads/...)
作为 media 参数,而非本地已下载的文件路径,导致 open() 报错 No such file or directory。

添加 _resolve_media_path 方法,在上传前将 Zulip 路径解析为本地文件:
- 本地路径直接存在则原样返回
- Zulip 上传路径(/user_uploads/...)或完整 URL 先查找已下载的本地副本
- 若本地不存在则从 Zulip 服务器重新下载
- 无法解析时记录错误并跳过
…cipients

MessageTool 创建的 OutboundMessage 只包含 message_id,缺少 msg_type/stream/topic/recipient_user_id 等 Zulip 路由字段,导致 _build_send_request 生成空收件人列表。

_recipient_map 在收到消息时已存储了完整的路由元数据,但从未被用于补全发送消息的元数据。

修复:在 send() 中,当 metadata 缺少 msg_type 时,从 _recipient_map 查找对应 chat_id 的路由信息并合并,确保发送消息时始终有正确的收件人。
LLM 调用 message 工具时,agent loop 会先发送一条 _tool_hint 进度消息,然后 MessageTool 才发送真正的消息。配置中 send_tool_hints 默认为 False,但 _outbound_router 未检查该标志,导致 Zulip 通道将工具提示当作普通消息发送,产生重复且格式错误的文本。修复:在 send() 入口处过滤 _tool_hint 消息,直接返回不发送。
修复现有 test_upload_uses_call_endpoint 测试(使用临时文件替代不存在的路径),新增测试覆盖:- _resolve_media_path: 本地路径直返、Zulip 路径缓存查找、Zulip 路径下载、完整 URL 解析、无效路径返回 None- _upload_and_send: 传递文件对象而非字符串、无法解析路径时跳过- send 元数据补全: 从 _recipient_map 补全缺失路由信息、已有元数据不被覆盖- send _tool_hint 过滤: tool_hint 消息被跳过、普通 progress 消息正常发送
问题:在Zulip话题中@机器人没有反应,deeptutor容器无日志。
根因:Bot未订阅频道,Zulip服务器不会推送频道消息事件给未订阅的Bot。

主要修改:
- ZulipConfig新增subscribe_streams配置项,支持指定频道名或通配符'*'
- start()中新增_subscribe_to_streams()自动订阅频道
- _subscribe_to_streams()支持异常保护,订阅失败不阻断Bot启动
- 通配符'*'与显式频道名混合使用时合并处理
- _on_message()添加调试日志便于排查
- _bot_full_name属性用于后续@mention内容匹配

测试修复:
- TestDownloadAttachments添加get_media_dir mock解决yaml导入错误
- TestUploadAndSend改用call_args_list筛选上传调用修复KeyError
- 新增TestSubscribeStreams测试类(7个用例)
- 将test_stop_cancels_typing_tasks移至TestStop类
@wedone wedone force-pushed the fix/zulip-upload-and-send branch from 7caffa9 to 9f6ebe9 Compare May 14, 2026 02:30
@wedone wedone changed the title fix(zulip): 修复 Zulip 通道附件上传与消息发送的 BUG fix(zulip): 修复 Zulip 通道附件上传与消息发送的 BUG,支持 Bot 自动订阅频道 May 14, 2026
wedone added 2 commits May 14, 2026 11:37
问题:在Zulip频道中@机器人没有反应,私聊正常。
根因:_is_mentioned方法只检查mentioned flag,遗漏了
      wildcard_mentioned、stream_wildcard_mentioned、
      topic_wildcard_mentioned等通配符mention标志。

修改:
- _is_mentioned支持所有四种mention flags
- 增强日志输出便于排查问题
- 补充边界测试用例(flags缺失、非字符串元素等)
问题:Bot已订阅所有频道时,add_subscriptions仍返回
      non-success结果,原代码记录WARNING级别日志造成误导。

修改:
- 根据already_subscribed计算实际订阅情况
- 有新增订阅时记录INFO,无新增时记录INFO提示已订阅
- 非success但全部已订阅时降级为DEBUG日志
- 真正失败时才保持WARNING级别
@pancacake pancacake merged commit cade789 into HKUDS:dev May 14, 2026
4 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants