Files
homeagent-sdk/example/qq/skill/qq-tools/SKILL.md
JianFeeeee bea410f891 fix(qq): 私聊历史拉不到 —— group_id=0 被当成"查群 0"
现场(2026-10-19 内核日志,13 次调用全灭):
用户反复说"gethistory 拉不到私聊消息",纠正多次仍不改。
查日志:qq_get_history 失败 13 次,错误全部是
  napcat get_group_msg_history: retcode=200: 消息undefined不存在
而 get_friend_msg_history 一次都没被调用过。

## 根因

handleGetHistory 用 `convInt64(args["group_id"])` 的 **err==nil** 判"调用方给了群号",
而 convInt64 对 0 也成功返回。于是:

  模型 qq_get_message 取私聊正文
    → 返回体无条件带 "group_id": 0(QQ 协议里私聊群号就是 0)
    → 模型把字段原样抄进 qq_get_history
    → 代码认为"要查群 0" → get_group_msg_history(group_id=0)
    → NapCat 回「消息undefined不存在」,看起来像历史拉不到

实机验证:直接 POST NapCat
  get_group_msg_history {"group_id":0}  → retcode=200 消息undefined不存在
  get_friend_msg_history {"user_id":…} → retcode=0 正常

## 改动

1. 新增 argPeerID:把 0 判为"未提供"(群号/QQ号都是正整数,0 从不是合法会话)。
   权限门用的 argInt64 早有 parsed!=0 检查 —— 本仓此前是「权限门严、取数松」。
2. handleGetHistory 改用它,并区分三种情况:只给 user_id 走私聊、只给 group_id
   走群聊、两个都给直接报错(旧实现静默选群分支,把私聊打成群查询)。
3. 同一零值缺陷一并修:handleSendFile(group_id=0 会发往不存在的群)、
   handleMarkRead(markChatRead(0) 静默清空不存在的会话)、
   handleResolveNickname(group_id=0 打群成员端点)。
4. **msgToGetMsgResult 私聊不再输出 group_id** —— 这是根因的上游。只改下游判据
   只能止血,模型仍会一次次把 0 抄回来。
5. schema 写死参数语义:「私聊必须省略 group_id、只传 user_id;不要传 0」。
   模型需要知道自己该传什么,否则会反复试错。
6. skill 补「拉私聊历史」整节 + 常见报错对照表;顺带修正一句错述
   (原文称"本插件声明 auto_start: false",实际三方都未声明,按契约默认自启)。
7. 新增 history_endpoint_test.go:5 条判据钉住端点选择与返回体契约,
   用 httptest 打桩 NapCat,断言真实命中的 action。

## 版本

1.4.3 → 1.4.4。已构建 hmap 并经 plugin_install 装到本机生产实例,
增量重载后 qq 子进程换新、工具全部重注册、skill 移交 skillmgr。
2026-10-19 20:27:23 +08:00

3.8 KiB
Raw Blame History

name, description
name description
qq-tools QQ 消息收发(NapCat 协议桥接)。本插件**常驻自启**:它在监听外部消息,不能按需加载。

QQ 消息

本插件为什么常驻

QQ 插件在 HTTP 端口上监听 NapCat 推送。用户发来的 QQ 消息随时可能到达, 到达后它立刻注入给 agent。

如果把本插件改成按需加载:

  • 内核启动时它不会被拉起
  • 没有人会去拉起它 —— 因为没有人知道有消息在等着
  • 结果:QQ 消息静默丢失,用户以为 agent 不理他

所以 auto_start 必须保持默认(true)。这不是性能取舍,是功能正确性。

工具

工具 用途
qq_get_message 按 message_id 取单条正文(消息刚到时先用它)
qq_get_history 按会话拉最近历史(群聊传 group_id,私聊传 user_id)
qq_list_chats 会话列表,按最新消息排序,带未读数与摘要
qq_mark_read 清零某会话未读数
output_send__qq 主动发消息(群/私聊由 meta 决定)

★★ 拉私聊历史:只传 user_id,不要传 group_id

这是本插件最容易踩的坑,已经真实咬过人(2026-10-19 连续 13 次调用全失败):

qq_get_history(user_id=2198972886)            ✅ 拉私聊
qq_get_history(group_id=123456)               ✅ 拉群聊
qq_get_history(group_id=0, user_id=2198972886) ❌ 报「消息undefined不存在」

为什么容易错:qq_get_message 取回一条私聊消息时,QQ 协议里群号 就是 0。如果返回体带了这个字段,把它原样抄进 qq_get_history 就会去查 "群 0" —— 而群 0 不存在,NapCat 只会回一句含糊的 retcode=200 消息undefined不存在,看起来像"历史拉不到",其实是参数用错。

现在的行为(已修):

  • 私聊消息的返回体不再包含 group_id —— 没这个字段就别去抄它
  • 后端把 group_id=0 判为"未提供",走私聊端点
  • 两个都传会直接报错并说明"只能二选一",而不是静默选错分支

判断该传哪个:看消息来源。私聊注入会带 user_id=...;群聊注入带 group_id=...。拿不准就先 qq_list_chats,它会标明每个会话是群还是私聊。

常见报错怎么读

现象 含义 怎么办
消息undefined不存在 通常是把私聊当群聊查了(group_id=0 或群号错) 私聊改传 user_id,别传 group_id
message_id 可能已过期 NapCat 侧短号有时效(约 7 天) 改用 qq_get_history 按会话拉
只能访问当前 QQ 会话 权限门:非 owner 只能读本轮会话 不是 bug;需要跨会话请让 owner 操作

注意

  • 依赖 NapCat 服务在运行且已登录。
  • 消息可能过期(NapCat 侧有时效),发送失败时会明确报错。
  • 不要在群里 @全体成员、不要连续刷屏。

★ 用 plugin_stop / plugin_start,不要用 plugin_disable / plugin_enable

四个工具看着像同义词,语义差别很大:

工具 作用 跨重启
plugin_stop 运行态关闭,现在停掉 不影响,重启照常加载
plugin_start 运行态拉起,现在开回来 不影响
plugin_disable 持久禁用 会记下来,开机也不自启
plugin_enable 解除持久禁用 会记下来

释放上下文用 plugin_stop。 用 plugin_disable 会顺手把"开机自启" 永久关掉 —— 用户下次开机发现插件没了,得再手动开回来。

(本插件没有声明 auto_start: false,即按契约默认自启 —— 这是对的, 它必须常驻。所以这里更要小心:plugin_disable 会写进持久禁用清单, 让重启后的自启也失效,只能再用 plugin_enable 解。)