Hermes 说"文件已附上",而你的平台什么也没收到


最难受的一类 bug,是 agent 很笃定、用户什么也没看见。你让 Hermes 把一份报告发到自己的聊天平台——一个手机 App、一个内部工具、你自己写的桥接服务——它回答“已发送,文件已附上”。什么都没到。你再问一次,agent 道歉、重发,还是什么都没到,因为它根本无从知道附件在它下面某一层被丢掉了。

症状与报错原文

出问题的平台并没有坏。它实现了 send_document、send_image_file 和 send_video,它的 sender 也处理 media_files。它只是不在 send_message 工具认定的“支持媒体”的平台集合里。只让它发媒体,工具会直接拒绝:

send_message MEDIA delivery is currently only supported for telegram, discord, matrix, weixin, signal, yuanbao, feishu, whatsapp, slack and qqbot; target mobile had only media attachments

如果同时发文字和文件,失败会更安静:文字发出去了,附件被丢掉,只留一条警告——而这条警告只出现在工具结果里。从用户那一侧看,这条消息像是发出去了。所以报告这个问题的维护者才这样描述结果:他们的 agent 反复告诉用户“文件已附上”,而实际上什么也没到。

这个决定是在哪里做出的

tools/send_message_tool.py 在两个地方回答了“这个平台能不能收文件”,而其中只有一个是关于你的插件的:

  • 一条人类可读的提示,用在上面那句报错里:telegram, discord, matrix, weixin, signal, yuanbao, feishu, whatsapp, slack and qqbot。
  • 一个真正启用插件路径的字典,短得多:
_PLUGIN_STANDALONE_MEDIA = {
    "discord": ("Discord", False, True, [], False),
    "feishu": ("Feishu", True, False, None, False),
    "slack": ("Slack", True, True, [], False),
    "whatsapp": ("WhatsApp", True, True, None, True),
}

按它上面的注释,每个值是一个元组:(label, discover_plugins_first, caption_capable, media_files_sentinel, pass_force_document)。这个列表存在的原因,是把文件塞进这四个平台的载荷需要通用 sender 不具备的知识——强制先发现插件注册表、图注处理方式,或者非最终媒体分片要用某种特定形状。

任何不在这份字典里的插件平台,都会落到通用的纯文本路径上:文字保留、附件丢弃,只发媒体则直接失败(就是上面那条报错)。插件自己的代码根本不会被询问——这一点最扎人:平台可以完全具备能力,却在这道判断里完全不可见。

为什么“加上这个平台就行”不是完整答案

报告者做了最直接的试验:在本地给自己的平台加一个元组(("Mobile App", True, True, [], False)),工具就会把附件走插件的 standalone_sender_fn 送出去。文件以媒体帧形式到达——kind=document、mime=text/markdown,和原文件逐字节一致。也就是说管道是通的,卡住的是那道门。

但一份硬编码字典意味着:以后每出现一个新的插件平台,都得先改核心代码才能发文件,而了解“这个平台支持媒体”的人永远不是改核心的那个人。issue 里提的解法很自然:让插件在自己的注册表条目里声明——类似 media_files_supported 加一个 captionable 标志,在 _plugin_standalone_sender 解析 sender 的地方读它——让能力跟着插件走,而不是住在核心白名单里。

那次排查还有第二层,对写插件适配器的人是个有用的提醒:报告者同时也发现适配器侧有两个错误,已在他们的适配器 1.1.1 里修掉。媒体描述符是以 (path, is_voice) 元组形式到达的,而他们的 sender 之前把它当成普通字符串处理,结果谁也没被取到。换句话说,“我的平台支持媒体”这件事得端到端成立——工具要把附件路由出去,适配器也要看懂自己收到的东西。

今天碰到这个问题怎么办

  • 看工具结果,不要看聊天窗口。 当 agent 说文件已发送时,附件消失的原因写在 send_message 的工具结果里,而不在助手的回复里。如果你在调试自己写的桥接,那串文字是第一个该读的东西。
  • 调试期间把文字和媒体分开。 纯文本消息在插件平台上始终可用;只发媒体会大声失败,这反而更利于定位。
  • 插件作者: 把能力问题记进你的注册表而不是靠假设,并确认你的 standalone_sender_fn 能处理它实际收到的元组形状。如果今天就需要附件,能选的只有本地给自己那份 Hermes 打个 patch,或者等核心改动——这两件事都不该让用户悄悄承担。
  • 其他用户: 如果你在用插件平台且文件投递对你重要,去给“由插件声明能力”这个方向投票或补充场景。核心字典里按平台加条目是一种维护负担,而声明式能力能把它消掉。

当前状态

该问题登记为 issue #139354,2026 年 10 月 15 日提交,附了一个基于手机插件的可复现案例和上面那段本地验证的修法。截至本文写作,核心行为没有变化:决定权仍然在那个四平台字典手里,issue 里提的设计——由插件声明能力——仍是提案,不是已落地的改动。

值得放在整体里看:send_message 只是一个工具,这只是它的其中一道门。如果你正在更大范围地做插件平台,hermes-mobile 生态条目就是一个从头到尾从平台侧修这类问题的实例,hermes send 命令参考则覆盖了这个工具包装的 CLI 表层。


来源:Hermes Agent 仓库 issue #139354(含报告者的本地验证与适配器 1.1.1 的修复);main 分支上 tools/send_message_tool.py 的 _PLUGIN_STANDALONE_MEDIA 与报错里使用的媒体平台提示。