@zaofan/dsh-qqbot 0.9.6 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +400 -344
  2. package/README_EN.md +313 -281
  3. package/client/qqbot-settings.js +431 -10
  4. package/dist/channel-tools.d.ts +1 -1
  5. package/dist/channel-tools.d.ts.map +1 -1
  6. package/dist/channel-tools.js +166 -15
  7. package/dist/channel-tools.js.map +1 -1
  8. package/dist/commands/approve.d.ts +17 -0
  9. package/dist/commands/approve.d.ts.map +1 -0
  10. package/dist/commands/approve.js +24 -0
  11. package/dist/commands/approve.js.map +1 -0
  12. package/dist/commands/botplay.d.ts +15 -0
  13. package/dist/commands/botplay.d.ts.map +1 -0
  14. package/dist/commands/botplay.js +34 -0
  15. package/dist/commands/botplay.js.map +1 -0
  16. package/dist/commands/exit.d.ts +7 -0
  17. package/dist/commands/exit.d.ts.map +1 -0
  18. package/dist/commands/exit.js +85 -0
  19. package/dist/commands/exit.js.map +1 -0
  20. package/dist/commands/index.d.ts.map +1 -1
  21. package/dist/commands/index.js +14 -1
  22. package/dist/commands/index.js.map +1 -1
  23. package/dist/commands/outmode.d.ts +7 -3
  24. package/dist/commands/outmode.d.ts.map +1 -1
  25. package/dist/commands/outmode.js +19 -9
  26. package/dist/commands/outmode.js.map +1 -1
  27. package/dist/commands/permission.d.ts +18 -0
  28. package/dist/commands/permission.d.ts.map +1 -0
  29. package/dist/commands/permission.js +43 -0
  30. package/dist/commands/permission.js.map +1 -0
  31. package/dist/commands/session.d.ts +5 -1
  32. package/dist/commands/session.d.ts.map +1 -1
  33. package/dist/commands/session.js +51 -5
  34. package/dist/commands/session.js.map +1 -1
  35. package/dist/config.d.ts +73 -0
  36. package/dist/config.d.ts.map +1 -1
  37. package/dist/config.js +89 -0
  38. package/dist/config.js.map +1 -1
  39. package/dist/features/approval-switch.d.ts +22 -0
  40. package/dist/features/approval-switch.d.ts.map +1 -0
  41. package/dist/features/approval-switch.js +10 -0
  42. package/dist/features/approval-switch.js.map +1 -0
  43. package/dist/features/botplay.d.ts +141 -0
  44. package/dist/features/botplay.d.ts.map +1 -0
  45. package/dist/features/botplay.js +558 -0
  46. package/dist/features/botplay.js.map +1 -0
  47. package/dist/features/extension-store.d.ts +27 -0
  48. package/dist/features/extension-store.d.ts.map +1 -0
  49. package/dist/features/extension-store.js +152 -0
  50. package/dist/features/extension-store.js.map +1 -0
  51. package/dist/features/group-hub.d.ts +40 -0
  52. package/dist/features/group-hub.d.ts.map +1 -0
  53. package/dist/features/group-hub.js +174 -0
  54. package/dist/features/group-hub.js.map +1 -0
  55. package/dist/features/group-join-request.d.ts.map +1 -1
  56. package/dist/features/group-join-request.js +35 -33
  57. package/dist/features/group-join-request.js.map +1 -1
  58. package/dist/features/qq-approval.d.ts.map +1 -1
  59. package/dist/features/qq-approval.js +3 -1
  60. package/dist/features/qq-approval.js.map +1 -1
  61. package/dist/features/scheduler.d.ts.map +1 -1
  62. package/dist/features/scheduler.js +2 -1
  63. package/dist/features/scheduler.js.map +1 -1
  64. package/dist/gateway/bootstrap.d.ts.map +1 -1
  65. package/dist/gateway/bootstrap.js +91 -11
  66. package/dist/gateway/bootstrap.js.map +1 -1
  67. package/dist/gateway/data-root.d.ts +15 -0
  68. package/dist/gateway/data-root.d.ts.map +1 -0
  69. package/dist/gateway/data-root.js +59 -0
  70. package/dist/gateway/data-root.js.map +1 -0
  71. package/dist/gateway/debounce.d.ts.map +1 -1
  72. package/dist/gateway/debounce.js +3 -2
  73. package/dist/gateway/debounce.js.map +1 -1
  74. package/dist/gateway/middleware-setup.d.ts +1 -1
  75. package/dist/gateway/middleware-setup.d.ts.map +1 -1
  76. package/dist/gateway/middleware-setup.js +42 -14
  77. package/dist/gateway/middleware-setup.js.map +1 -1
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +22 -2
  80. package/dist/index.js.map +1 -1
  81. package/dist/middleware/attachment.d.ts.map +1 -1
  82. package/dist/middleware/attachment.js +2 -1
  83. package/dist/middleware/attachment.js.map +1 -1
  84. package/dist/middleware/media-history.d.ts +2 -0
  85. package/dist/middleware/media-history.d.ts.map +1 -1
  86. package/dist/middleware/media-history.js +6 -1
  87. package/dist/middleware/media-history.js.map +1 -1
  88. package/dist/middleware/sticker-capture.d.ts.map +1 -1
  89. package/dist/middleware/sticker-capture.js +2 -2
  90. package/dist/middleware/sticker-capture.js.map +1 -1
  91. package/dist/model/model-resolver.d.ts +12 -0
  92. package/dist/model/model-resolver.d.ts.map +1 -1
  93. package/dist/model/model-resolver.js +24 -0
  94. package/dist/model/model-resolver.js.map +1 -1
  95. package/dist/model/prefs-store.d.ts +16 -0
  96. package/dist/model/prefs-store.d.ts.map +1 -1
  97. package/dist/model/prefs-store.js +48 -0
  98. package/dist/model/prefs-store.js.map +1 -1
  99. package/dist/session/session-manager.d.ts +41 -2
  100. package/dist/session/session-manager.d.ts.map +1 -1
  101. package/dist/session/session-manager.js +185 -21
  102. package/dist/session/session-manager.js.map +1 -1
  103. package/dist/transport/attachment.d.ts.map +1 -1
  104. package/dist/transport/attachment.js +2 -1
  105. package/dist/transport/attachment.js.map +1 -1
  106. package/dist/transport/inbound.d.ts.map +1 -1
  107. package/dist/transport/inbound.js +9 -1
  108. package/dist/transport/inbound.js.map +1 -1
  109. package/package.json +1 -1
  110. package/settings-host.js +177 -52
package/README.md CHANGED
@@ -1,344 +1,400 @@
1
- # @zaofan/dsh-qqbot
2
-
3
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE) ![Platform](https://img.shields.io/badge/platform-QQ%20Bot%20(dsh)-blue)
4
-
5
- 基于 [deepseek-harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) 的 QQ Bot IM 插件**增强 fork**:将 QQ 消息平台作为 dsh agent 的前端协议驱动,并加入表情包图库、富媒体收发、定时任务、多实例人格、可视化设置面板等能力。
6
-
7
- 📦 仓库: [gcry13067381632-jpg/dsh-qqbot](https://github.com/gcry13067381632-jpg/dsh-qqbot)(fork 自 [tencent-connect/dsh-qqbot](https://github.com/tencent-connect/dsh-qqbot))
8
-
9
- 中文 | [English](./README_EN.md)
10
-
11
- ## 🐋 本 fork 增强版
12
-
13
- **一句话**:人家是把 dsh 的 QQ 机器人养成"活鱼"的增强版——会存表情包、会挑图回你、到点自己开口,一台电脑还能同时养好几条性格不同的鲸鱼。
14
-
15
- > 它是上游 [@tencent-connect/dsh-qqbot](https://github.com/tencent-connect/dsh-qqbot) 的增强 fork(改动都在这边,升级/重装上游会被冲掉哦)。
16
-
17
- ### 她能帮你……
18
-
19
- **🤳 群里发的图,她偷偷全存进小图库**
20
- 自动去重、分「待整理/收藏/回收站」;你说一句"发个开心点的图",她自己搜库、自己挑、自己发,还会挑场合出手(冷场不发、刷屏限量、同图不连发)。
21
-
22
- **⏰ 到点她自己会开口**
23
- "每天早 9 点去群里说早安""30 秒后提醒我喝水"——她说到做到,准点冒泡。
24
-
25
- **🧑‍🤝‍🧑 一个电脑,N 条鲸鱼同时在线**
26
- 每条号独立 AppID、独立人格、独立图库/定时/闸门,互不串号;Web 页「扫码绑定」手机一扫就上岗。
27
-
28
- **💬 悬浮球 dock:她的随身控制台**
29
- 设置面板右下角的小球,点开就是一整个操作台:**💬 聊天**(像 QQ 一样回放群/私聊记录,气泡+头像,图能放大、本地视频能播、SILK 语音转 mp3 直接听、文件出下载卡,还能在聊天框里直接发文字/插图/发文件——长文本自动拆条连发不被吞);**📥 入群审批**、**🔇 禁言**(机器人为群管理员时,有人申请进群她会提醒你,回句"通过/拒绝"就批);**⚙️ 出站**(适配主动:刚收到真人消息时前几条带引用回你、连发自动转独立消息,定时/后台推送不打扰)。
30
-
31
- **🛡️ 群主/群管好帮手**
32
- 入群审批 + 禁言 + 查成员,全走官方接口,出错给"人话"(不是管理员/不能禁群主……都告诉你为什么)。
33
-
34
- **🖥️ 不碰配置文件,设置面板点点点**
35
- 怎么回、能发什么图、什么时候开口、什么人格——面板上改完保存即生效(只有增删账号才要重启)。还有 ✏️ 预设人格编辑器,直接在网页里改她的"性格文件"。
36
-
37
- **📦 干净又利落**
38
- 发图/撤消息用纯文本就能驱动(回复里写 `[MEDIA:image|路径]` / `[RECALL]`);仓库不含任何机器人凭据与隐私。
39
-
40
- ### 📸 效果展示
41
-
42
- 图①:dsh 运行后台——思考过程、工具调用、Token 用量一目了然(配合「回复闸门 reply_gate」可让机器人自主判断该开口还是静默吃瓜);
43
- 图②:QQ 群里的抓鬼游戏互动——该回就回、该藏就藏,角色扮演全自动;
44
- 图③:斗图实战——机器人用自己收藏的表情包接招回击,图、文分开两条连发。
45
-
46
- ![后台运行日志(思考过程与工具调用可见)](docs/showcase-1-log.png)
47
-
48
- ![QQ 群聊互动效果(角色扮演/自主静默)](docs/showcase-2-chat.png)
49
-
50
- ![斗图实战(发表情包接招回击)](docs/showcase-3-doutu.png)
51
-
52
- ### 给开发者的话
53
- - QQ 会话内可直接调用的标准工具:发图/撤图/查库/打标/查未整理/定时(`send_media`/`recall_message`/`list_stickers`/`sticker_tag`/`sticker_untagged`/`schedule_timer`/`schedule_cancel`…),会话按账号精确路由
54
- - **群管理工具**(`group_join_requests`/`group_approve_join`/`group_mute_state`/`group_mute_member`…):入群审批与禁言,需机器人为该群管理员;对话内管当前群,web/非群会话用配置的 `manageGroup`
55
- - **纯文本也能发图撤消息**:让 AI 在回复里写 `[MEDIA:image|图片路径或网址]` 就自动变成真图发出去(`voice`/`video`/`file` 同理);写 `[RECALL]` 撤回自己刚发的那条
56
- - 会话归属、工作区挂载等宿主问题已按官方机制修好(移植上游 PR #21,幂等、全 fail-soft)
57
-
58
- > 🛡️ 仓库**不含**任何机器人凭据、图库数据、日志与个人路径(发布前已清理)。AppID/AppSecret 请走环境变量或 Web 面板注入,**不要提交进 git**。
59
-
60
- ### 构建与部署
61
-
62
- ```bash
63
- npm install # 安装依赖(peer 依赖由 dsh 宿主解析)
64
- npm run build # 或: node node_modules/typescript/lib/tsc.js -p tsconfig.json
65
- npm run check:package # 发布前自检(单包四项家当齐全)
66
- pnpm pack # 打 tarball(供 dsh plugin add 安装)
67
- ```
68
-
69
- 安装到 dsh profile 见下方「安装」小节(同样支持 `dsh plugin add` 与扫码引导)。
70
- 仓库根的 `install.ps1` 提供 Windows 一键安装(自动 pack 到无空格目录 → add → 重启提示)。
71
-
72
- ---
73
-
74
- ## 架构
75
-
76
- ```
77
- QQ 用户 → QQ WebSocket → dsh-im-qqbot → ctx.agents → dsh agent loop → LLM
78
- ↑ │
79
- └── session/event ──────────┘
80
- (assistant reply → QQ sendMarkdown)
81
- ```
82
-
83
- ## 安装
84
-
85
- > ⚠️ **必须装到 `web` profile**(`dsh web` 设置面板的宿主);装到别的 profile 只会得到没有设置面板的裸环境。
86
- > 也不要 `add @tencent-connect/dsh-qqbot`——那会装上游官方版(无本 fork 增强功能)。
87
- >
88
- > ✅ **单包自含,装一个就全有**:QQ 机器人 + Web 可视化设置面板(host 桥 + 设置页 UI)都打包在
89
- > 这一个包内——装完它,dsh Web「设置」里就会出现「QQ 机器人 (im-qqbot)」面板(多账号时每个实例各一页),
90
- > **无需再单独安装 dsh-qqbot-settings**。
91
-
92
- ### 方式一(发布到 npm 后):一条命令
93
-
94
- ```powershell
95
- npx @deepseek-ai/dsh plugin --profile web add @zaofan/dsh-qqbot
96
- ```
97
-
98
- > 尚未发布到 npm 前,请用下面的方式二。
99
-
100
- ### 方式二:源码分发(当前推荐)
101
-
102
- **Windows(一键脚本)**:
103
-
104
- ```powershell
105
- git clone https://github.com/gcry13067381632-jpg/dsh-qqbot.git
106
- cd dsh-qqbot
107
- .\install.ps1 # 自动 install/build → pack → add tarball → 输出重启指引
108
- ```
109
-
110
- > 若系统禁止运行脚本,改用:`powershell -ExecutionPolicy Bypass -File .\install.ps1`
111
-
112
- **macOS / Linux(手动)**:
113
-
114
- ```bash
115
- git clone https://github.com/gcry13067381632-jpg/dsh-qqbot.git
116
- cd dsh-qqbot
117
- npm install && npm run build
118
- pnpm pack --pack-destination /tmp
119
- npx @deepseek-ai/dsh plugin --profile web add /tmp/zaofan-dsh-qqbot-0.4.0.tgz
120
- ```
121
-
122
- > 💡 为什么打 tarball、而不是 `add` 源码目录?实测教训:
123
- > ① 目录路径含空格时 Windows 会把参数在空格处拆碎(pnpm 报 `- isn't supported`);
124
- > ② `add` 目录 = pnpm link(junction),插件无法按"代码位置"反推 profile → 扫码凭据落不了盘,只能走环境变量。
125
-
126
- ### 排障: npm 安装报 ERESOLVE(2026-09-06 移植上游 PR #42)
127
-
128
- 首次 `npm install` 可能报 `ERESOLVE could not resolve`——原因: `@deepseek-ai/dsh-tools`/`dsh-agent` 等 peer 依赖仍在 prerelease(-rc) 版本线,npm 7+ 严格解析拒绝不相交组合。**这是上游版本线问题,不是插件 bug**,两条绕过路:
129
-
130
- ```bash
131
- npm install --legacy-peer-deps # 仅安装期解析策略, 不改运行行为
132
- # 或: 装完依赖后手动 build + pack(peer 由 dsh 宿主解析, 不受影响)
133
- ```
134
-
135
- > 跟踪中: 上游 #37 根治后此段可删(版本线收敛后 npm 不再报错)。
136
-
137
- ### 首次启动与绑定
138
-
139
- 启动 `dsh web` 后,若未配置凭据会自动进入**扫码引导**:终端输出二维码 → 手机 QQ 扫码绑定 → 凭据自动保存,重启不丢(设置面板里也可随时「扫码绑定」/改账号)。
140
-
141
- ![二维码扫码示意图](./docs/assets/qrcode.png)
142
-
143
- > **提示**:建议使用 `0.4.0` 以上版本扫码,支持点击链接在浏览器打开,避免部分终端二维码渲染错位的问题。
144
-
145
- ### 还没有 QQ 机器人?先注册一个(拿 AppID / AppSecret)
146
-
147
- 1. 打开 [QQ 开放平台](https://q.qq.com),用 QQ 号登录;
148
- 2. 进入「机器人」→「创建机器人」,填好名称、头像、简介;
149
- 3. 创建完成后在机器人详情页拿到 **AppID** 与 **AppSecret**;
150
- 4. 在 dsh Web → 设置 →「QQ 机器人」→「账号与预设」里填入并保存
151
- (或设为环境变量 `QQBOT_APPID` / `QQBOT_SECRET`);
152
- 5. 按需在平台开通**单聊/群聊**消息权限(群聊一般需要提交用途审核)。
153
-
154
- > 💡 更省事:机器人建好即可,首次启动直接**扫码绑定**,不用手抄凭据。
155
-
156
- ### 开发者:--patch 开发模式
157
-
158
- ```bash
159
- export QQBOT_APPID="你的AppID" QQBOT_SECRET="你的AppSecret"
160
- npx @deepseek-ai/dsh web --patch /path/to/dsh-qqbot/cordis.dev.yml
161
- ```
162
-
163
- ## QQ 远程审批(可选)
164
-
165
- 当 Agent 的工具要访问**工作区之外**的位置时,dsh 会触发权限审批。开启后,机器人把审批请求**发到 QQ**(任务发起者的会话),你直接在 QQ 里放行/拒绝:
166
-
167
- > ⚠️ **DSH 权限申请**
168
- > 工具:pwsh
169
- > 原因:需要访问工作区外路径
170
- >
171
- > 允许本次操作:`/approve A1B2C3`
172
- > 拒绝本次操作:`/deny A1B2C3`
173
- > 仅本次有效,120 秒后自动拒绝。
174
-
175
- **启用**(二选一;保存即对新审批请求生效,无需重启):
176
- - **Web 设置面板**:设置 →「QQ 机器人」→ ⑤ QQ 远程审批 → 勾选开启;
177
- - 或 `cordis.patch.yml` 的实例 config 加两行后重启:
178
-
179
- ```yaml
180
- - id: im-qqbot
181
- config:
182
- enableApprovals: true # 默认 false
183
- approvalTimeoutMs: 120000 # 等待时长, 超时自动拒绝
184
- ```
185
-
186
- **安全边界**:验证码一次性;仅"任务发起者本人 + 同一会话"可批(群聊里其他人看到验证码也无效);只授权当前这一次操作;Agent 取消或 dsh 退出自动取消。
187
-
188
- > 思路来源: wang-22-code/dsh-qqbot-bridge 的 QQ 审批设计(宿主 dsh `approval/request` 标准事件,官方 dsh-acp / Web 审批弹窗同款机制)。
189
-
190
- ## QQ 群管理(可选)
191
-
192
- 机器人**为群管理员**时,可开启群管理能力:实时接收「入群申请」并自动提醒主人、按申请审批入群、查询/设置群成员禁言。所有操作走腾讯官方 GroupOpenMsg 接口,错误信息已做"人话"映射(如 11703=机器人不是该群管理员、40103004=不能禁言群主/管理员、11255=群已注销)。
193
-
194
- **能力总开关**:
195
-
196
- - **Web 设置面板**: 设置 →「QQ 机器人」→ ⑥ QQ 群管理 → 勾选开启,并填/选「默认管理群」;
197
- - 或 `cordis.patch.yml` 的实例 config 加配置后重启:
198
-
199
- ```yaml
200
- - id: im-qqbot
201
- config:
202
- groupAdmin:
203
- enabled: true
204
- owners: [] # 主人 openid 白名单(空=不校验)
205
- manageGroup: "群openid" # 对话内默认管理群(web/非群会话用; 群会话自动取当前群)
206
- watchJoinRequests: true # 订阅入群申请事件(改后需重启: 涉及连接期 intents)
207
- notifyInGroup: true # 收到申请时在群内发提醒
208
- ```
209
-
210
- > ⚠️ `watchJoinRequests` 需要连接期注册 intents(GROUP_MEMBER_EVENT, 1<<24)——**改它必须重启**,不是 live 热改;且需官方对该机器人开放对应能力,否则连接可能被拒(4914/4915)。
211
-
212
- **入群审批怎么用**: 事件到达 → bot 在群里发一条提醒(含申请人昵称/验证语)→ 你在对话里说"通过/拒绝"(AI 调 `group_approve_join`)→ 官方落库审批。也可以在设置面板「⑥ QQ 群管理 → 入群审批」页看待审批清单手动批。
213
-
214
- **配置项**(Web 面板 ⑥ 可改, 见下表 `groupAdmin.*`)
215
-
216
- ## 配置项
217
-
218
- | 配置 | 类型 | 默认值 | 说明 |
219
- |------|------|--------|------|
220
- | `appId` | string | **必填** | QQ Bot AppID(或通过 `QQBOT_APPID` 环境变量) |
221
- | `appSecret` | string | **必填** | QQ Bot AppSecret(或通过 `QQBOT_SECRET` 环境变量) |
222
- | `provider` | string | `deepseek-official` | LLM 提供商名称 |
223
- | `model` | string | `deepseek-chat` | 模型名称 |
224
- | `preset` | string | - | Agent preset id |
225
- | `cwd` | string | `process.cwd()` | Agent 工作目录 |
226
- | `requireMention` | boolean | `true` | 群聊是否需要 @bot 才触发 |
227
- | `groupPrompt` | string | - | 群聊额外 system prompt |
228
- | `directPrompt` | string | - | 私聊额外 system prompt |
229
- | `textChunkLimit` | number | `4500` | 单条消息最大字符数 |
230
- | `sessionIdleTimeout` | number | `1800000` | 会话闲置超时(ms),默认 30 分钟 |
231
- | `debug` | boolean | `false` | 调试模式 |
232
- | `groupAdmin.enabled` | boolean | `false` | 群管理总开关(需机器人为群管理员) |
233
- | `groupAdmin.owners` | string[] | `[]` | 可操作群管理的主人 openid 白名单(空=不校验) |
234
- | `groupAdmin.manageGroup` | string | `''` | 对话内默认管理群 openid(web/非群会话时用) |
235
- | `groupAdmin.watchJoinRequests` | boolean | `false` | 订阅入群申请事件(改后需重启) |
236
- | `groupAdmin.notifyInGroup` | boolean | `true` | 收到申请时在群内发提醒 |
237
-
238
- > 🔧 新版 Web 面板把群管理单开成「⑥ QQ 群管理」卡片(入群审批/禁言/成员信息/黑名单), 与上方 `groupAdmin.*` 配置同一份数据。
239
-
240
- ## 内置命令
241
-
242
- 在 QQ 群里直接发(无需 @ 机器人;走 SDK 直通,不占用 AI 回合):
243
-
244
- | 命令 | 说明 |
245
- |------|------|
246
- | `/outmode` | 查看当前出站模式与四档说明 |
247
- | `/outmode adaptive` | 切到 **适配主动**(默认): 收到真人消息前5条带引用回你, 之后自动转独立消息, 连发不被吞 |
248
- | `/outmode passive` | 切到 **被动**: 始终回复你那条(连发约4~5条后被QQ吞) |
249
- | `/outmode silent` | 切到 **完全不出站**: 她照常思考但不向QQ发任何回复(web可对话) |
250
- | `/outmode nothink` | 切到 **完全不思考**: QQ入站不唤醒AI, 消息只记录(逃生通道, 可随时切回) |
251
- | `/bot-reset` | 重置当前会话(清除上下文) |
252
- | `/bot-new` | 开启新会话(保留旧会话历史) |
253
- | `/bot-model` / `/model` | 查看或切换模型(如 `/bot-model deepseek-official/deepseek-v4-flash`) |
254
- | `/bot-status` | 查看当前会话状态 |
255
- | `/bot-ping` | 连通性测试 |
256
- | `/bot-version` | 查看版本与当前模型 |
257
- | `/bot-stop` | 中止当前正在生成的内容 |
258
- | `/bot-help` | 查看所有指令 |
259
- | `/tools-reload` | 热刷新 QQ 通道工具(开发用, 新工具无需重启即可用) |
260
-
261
- > 💡 `/outmode` 是她的"逃生开关":即使处于 nothink(完全不思考)状态,SDK 直通命令也能把她唤醒——在 QQ 里发 `/outmode adaptive` 即可。
262
-
263
- ## 富媒体指令(AI 回复里写标记,自动变成真消息)
264
-
265
- 让 AI(或你替她)在回复正文里写以下标记,插件会自动拆出来发成真实的 QQ 消息,**标记本身不会显示**:
266
-
267
- | 标记 | 效果 | 示例 |
268
- |------|------|------|
269
- | `[MEDIA:image\|来源]` | 发图片(本地路径或 http(s) 链接) | `[MEDIA:image\|D:\pics\kiss.jpg]` / `[MEDIA:image\|https://…/a.png]` |
270
- | `[MEDIA:voice\|来源]` | 发语音(仅支持本地路径或 QQ 可拉取的链接) | `[MEDIA:voice\|D:\audio\hi.silk]` |
271
- | `[MEDIA:video\|来源]` | 发视频 | `[MEDIA:video\|D:\videos\clip.mp4]` |
272
- | `[MEDIA:file\|来源]` | 发文件 | `[MEDIA:file\|D:\docs\计划.pdf]` |
273
- | `[RECALL]` | 撤回自己刚发的那条消息 | 单独一行写 `[RECALL]` |
274
- | `[RECALL:N]` | 撤回自己发的倒数第 N 条 | 如 `[RECALL:2]` 撤倒数第二条 |
275
-
276
- 要点:
277
- - 图片/文件可用**本机绝对路径**或**网络 URL**;语音本地路径若为 QQ SILK 格式也能转码发送。
278
- - ≥5MB 的本地大文件(视频/压缩包…)自动转后台分片上传,不阻塞对话。
279
- - 一次回复可混用多条 `[MEDIA:]`,配合长文本拆条连发使用。
280
- - 这些是"AI 会自己写"的暗号——正常聊天时她收到"发个开心点的图"这类指令,会自己调工具完成,不需要你手动写标记。
281
-
282
- ## 核心模块
283
-
284
- ```
285
- src/
286
- ├── index.ts # Cordis 插件入口(async apply)
287
- ├── config.ts # 配置 Schema
288
- ├── types.ts # 全局类型定义
289
- ├── setup.ts # 凭据绑定(扫码)
290
- ├── transport/ # 传输层
291
- │ ├── inbound.ts # QQ 入站消息 → agent.followup()
292
- │ ├── outbound.ts # session/event → QQ sendMarkdown
293
- │ ├── outbound-buffer.ts # 流式缓冲
294
- │ └── chunker.ts # Markdown 文本切分
295
- ├── session/ # 会话管理层
296
- │ ├── session-manager.ts # QQ peer → Agent 映射
297
- │ └── idle-evictor.ts # 闲置回收
298
- ├── model/ # 模型路由层
299
- │ ├── model-resolver.ts # 路由解析
300
- │ ├── prefs-store.ts # per-peer 偏好持久化
301
- │ └── settings-reader.ts # settings.yaml 只读
302
- ├── shared/ # 共享工具
303
- │ ├── utils.ts # 通用函数
304
- │ ├── scope.ts # scope/peer 提取
305
- │ └── send-helper.ts # 分块发送
306
- ├── commands/ # 斜杠命令
307
- └── typings/ # 外部模块声明
308
- ```
309
-
310
- ## 会话路由
311
-
312
- sessionKey: `qqbot:${appId}:${scope}:${peerId}`,由 SHA-256 确定性派生 SessionId,重启后可恢复。
313
-
314
- 解析策略:进程内复用 → 持久化恢复 → 全新创建。
315
-
316
- ## 设计原则
317
-
318
- - **纯 Cordis 插件** — 遵循 dsh "Plugins, not loop changes" 原则
319
- - **声明式依赖** — `inject = ['agents']`,不直接耦合其他插件
320
- - **会话隔离** — 每个 QQ 私聊用户/群聊各一个独立 Agent
321
- - **Preset 支持** — 可通过 `agent-presets` 服务挂载预设(工具集、prompt 等)
322
- - **闲置回收** — 超时自动 dispose Agent,防止内存泄漏
323
- - **Markdown 输出** — 回复以 Markdown 格式发送,支持代码块/表格感知切分
324
-
325
- ## 本地开发
326
-
327
- ```bash
328
- # 安装依赖
329
- pnpm install
330
-
331
- # 构建
332
- pnpm build
333
-
334
- # 开发模式(watch)
335
- pnpm dev
336
-
337
- # 用 --patch 方式调试
338
- export QQBOT_APPID="xxx" QQBOT_SECRET="xxx"
339
- npx @deepseek-ai/dsh web --patch /path/to/dsh-qqbot/cordis.dev.yml
340
- ```
341
-
342
- ## License
343
-
344
- [MIT](./LICENSE)
1
+ # @zaofan/dsh-qqbot
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](./LICENSE) ![Platform](https://img.shields.io/badge/platform-QQ%20Bot%20(dsh)-blue)
4
+
5
+ 基于 [deepseek-harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) 的 QQ Bot IM 插件**增强 fork**:将 QQ 消息平台作为 dsh agent 的前端协议驱动,并加入表情包图库、富媒体收发、定时任务、多实例人格、可视化设置面板等能力。
6
+
7
+ 📦 仓库: [gcry13067381632-jpg/dsh-qqbot](https://github.com/gcry13067381632-jpg/dsh-qqbot)(fork 自 [tencent-connect/dsh-qqbot](https://github.com/tencent-connect/dsh-qqbot))
8
+
9
+ 中文 | [English](./README_EN.md)
10
+
11
+ ## 🐋 本 fork 增强版
12
+
13
+ **一句话**:人家是把 dsh 的 QQ 机器人养成"活鱼"的增强版——会存表情包、会挑图回你、到点自己开口,一台电脑还能同时养好几条性格不同的鲸鱼。
14
+
15
+ > 它是上游 [@tencent-connect/dsh-qqbot](https://github.com/tencent-connect/dsh-qqbot) 的增强 fork(改动都在这边,升级/重装上游会被冲掉哦)。
16
+
17
+ ### 她能帮你……
18
+
19
+ **🤳 群里发的图,她偷偷全存进小图库**
20
+ 自动去重、分「待整理/收藏/回收站」;你说一句"发个开心点的图",她自己搜库、自己挑、自己发,还会挑场合出手(冷场不发、刷屏限量、同图不连发)。
21
+
22
+ **⏰ 到点她自己会开口**
23
+ "每天早 9 点去群里说早安""30 秒后提醒我喝水"——她说到做到,准点冒泡。
24
+
25
+ **🧑‍🤝‍🧑 一个电脑,N 条鲸鱼同时在线**
26
+ 每条号独立 AppID、独立人格、独立图库/定时/闸门,互不串号;Web 页「扫码绑定」手机一扫就上岗。
27
+
28
+ **💬 悬浮球 dock:她的随身控制台**
29
+ 设置面板右下角的小球,点开就是一整个操作台:**💬 聊天**(像 QQ 一样回放群/私聊记录,气泡+头像,图能放大、本地视频能播、SILK 语音转 mp3 直接听、文件出下载卡,还能在聊天框里直接发文字/插图/发文件——长文本自动拆条连发不被吞);**📥 入群审批**、**🔇 禁言**(机器人为群管理员时,有人申请进群她会提醒你,回句"通过/拒绝"就批);**⚙️ 出站**(适配主动:刚收到真人消息时前几条带引用回你、连发自动转独立消息,定时/后台推送不打扰)。
30
+
31
+ **🛡️ 群主/群管好帮手**
32
+ 入群审批 + 禁言 + 查成员,全走官方接口,出错给"人话"(不是管理员/不能禁群主……都告诉你为什么)。
33
+
34
+ **🖥️ 不碰配置文件,设置面板点点点**
35
+ 怎么回、能发什么图、什么时候开口、什么人格——面板上改完保存即生效(只有增删账号才要重启)。还有 ✏️ 预设人格编辑器,直接在网页里改她的"性格文件"。
36
+
37
+ **📦 干净又利落**
38
+ 发图/撤消息用纯文本就能驱动(回复里写 `[MEDIA:image|路径]` / `[RECALL]`);仓库不含任何机器人凭据与隐私。
39
+
40
+ ### 📸 效果展示
41
+
42
+ 图①:dsh 运行后台——思考过程、工具调用、Token 用量一目了然(配合「回复闸门 reply_gate」可让机器人自主判断该开口还是静默吃瓜);
43
+ 图②:QQ 群里的抓鬼游戏互动——该回就回、该藏就藏,角色扮演全自动;
44
+ 图③:斗图实战——机器人用自己收藏的表情包接招回击,图、文分开两条连发。
45
+
46
+ ![后台运行日志(思考过程与工具调用可见)](docs/showcase-1-log.png)
47
+
48
+ ![QQ 群聊互动效果(角色扮演/自主静默)](docs/showcase-2-chat.png)
49
+
50
+ ![斗图实战(发表情包接招回击)](docs/showcase-3-doutu.png)
51
+
52
+ ### 给AI的话
53
+ - QQ 会话内可直接调用的标准工具:发图/撤图/查库/打标/查未整理/定时(`send_media`/`recall_message`/`list_stickers`/`sticker_tag`/`sticker_untagged`/`schedule_timer`/`schedule_cancel`…),会话按账号精确路由
54
+ - **群管理工具**(`group_join_requests`/`group_approve_join`/`group_mute_state`/`group_mute_member`…):入群审批与禁言,需机器人为该群管理员;对话内管当前群,web/非群会话用配置的 `manageGroup`
55
+ - **纯文本也能发图撤消息**:让 AI 在回复里写 `[MEDIA:image|图片路径或网址]` 就自动变成真图发出去(`voice`/`video`/`file` 同理);写 `[RECALL]` 撤回自己刚发的那条
56
+ - 会话归属、工作区挂载等宿主问题已按官方机制修好(移植上游 PR #21,幂等、全 fail-soft)
57
+
58
+ > 🛡️ 仓库**不含**任何机器人凭据、图库数据、日志与个人路径(发布前已清理)。AppID/AppSecret 请走环境变量或 Web 面板注入,**不要提交进 git**。
59
+
60
+ ### 构建与部署
61
+
62
+ ```bash
63
+ npm install # 安装依赖(peer 依赖由 dsh 宿主解析)
64
+ npm run build # 或: node node_modules/typescript/lib/tsc.js -p tsconfig.json
65
+ npm run check:package # 发布前自检(单包四项家当齐全)
66
+ pnpm pack # 打 tarball(供 dsh plugin add 安装)
67
+ ```
68
+
69
+ 安装到 dsh profile 见下方「安装」小节(同样支持 `dsh plugin add` 与扫码引导)。
70
+ 仓库根的 `install.ps1` 提供 Windows 一键安装(自动 pack 到无空格目录 → add → 重启提示)。
71
+
72
+ ---
73
+
74
+ ## 架构
75
+
76
+ ```
77
+ QQ 用户 → QQ WebSocket → dsh-im-qqbot → ctx.agents → dsh agent loop → LLM
78
+ ↑ │
79
+ └── session/event ──────────┘
80
+ (assistant reply → QQ sendMarkdown)
81
+ ```
82
+
83
+ ## 安装
84
+
85
+ > ⚠️ **必须装到 `web` profile**(`dsh web` 设置面板的宿主);装到别的 profile 只会得到没有设置面板的裸环境。
86
+ > 也不要 `add @tencent-connect/dsh-qqbot`——那会装上游官方版(无本 fork 增强功能)。
87
+ >
88
+ > ✅ **单包自含,装一个就全有**:QQ 机器人 + Web 可视化设置面板(host 桥 + 设置页 UI)都打包在
89
+ > 这一个包内——装完它,dsh Web「设置」里就会出现「QQ 机器人 (im-qqbot)」面板(多账号时每个实例各一页),
90
+ > **无需再单独安装 dsh-qqbot-settings**。
91
+
92
+ ### 方式一(发布到 npm 后):一条命令
93
+
94
+ ```powershell
95
+ npx @deepseek-ai/dsh plugin --profile web add @zaofan/dsh-qqbot
96
+ ```
97
+
98
+ > 尚未发布到 npm 前,请用下面的方式二。
99
+
100
+ ### 方式二:源码分发(当前推荐)
101
+
102
+ **Windows(一键脚本)**:
103
+
104
+ ```powershell
105
+ git clone https://github.com/gcry13067381632-jpg/dsh-qqbot.git
106
+ cd dsh-qqbot
107
+ .\install.ps1 # 自动 install/build → pack → add tarball → 输出重启指引
108
+ ```
109
+
110
+ > 若系统禁止运行脚本,改用:`powershell -ExecutionPolicy Bypass -File .\install.ps1`
111
+
112
+ **macOS / Linux(手动)**:
113
+
114
+ ```bash
115
+ git clone https://github.com/gcry13067381632-jpg/dsh-qqbot.git
116
+ cd dsh-qqbot
117
+ npm install && npm run build
118
+ pnpm pack --pack-destination /tmp
119
+ npx @deepseek-ai/dsh plugin --profile web add /tmp/zaofan-dsh-qqbot-0.4.0.tgz
120
+ ```
121
+
122
+ > 💡 为什么打 tarball、而不是 `add` 源码目录?实测教训:
123
+ > ① 目录路径含空格时 Windows 会把参数在空格处拆碎(pnpm 报 `- isn't supported`);
124
+ > ② `add` 目录 = pnpm link(junction),插件无法按"代码位置"反推 profile → 扫码凭据落不了盘,只能走环境变量。
125
+
126
+ ### 排障: npm 安装报 ERESOLVE(2026-09-06 移植上游 PR #42)
127
+
128
+ 首次 `npm install` 可能报 `ERESOLVE could not resolve`——原因: `@deepseek-ai/dsh-tools`/`dsh-agent` 等 peer 依赖仍在 prerelease(-rc) 版本线,npm 7+ 严格解析拒绝不相交组合。**这是上游版本线问题,不是插件 bug**,两条绕过路:
129
+
130
+ ```bash
131
+ npm install --legacy-peer-deps # 仅安装期解析策略, 不改运行行为
132
+ # 或: 装完依赖后手动 build + pack(peer 由 dsh 宿主解析, 不受影响)
133
+ ```
134
+
135
+ > 跟踪中: 上游 #37 根治后此段可删(版本线收敛后 npm 不再报错)。
136
+
137
+ ### 首次启动与绑定
138
+
139
+ 启动 `dsh web` 后,若未配置凭据会自动进入**扫码引导**:终端输出二维码 → 手机 QQ 扫码绑定 → 凭据自动保存,重启不丢(设置面板里也可随时「扫码绑定」/改账号)。
140
+
141
+ ![二维码扫码示意图](./docs/assets/qrcode.png)
142
+
143
+ > **提示**:建议使用 `0.4.0` 以上版本扫码,支持点击链接在浏览器打开,避免部分终端二维码渲染错位的问题。
144
+
145
+ ### 还没有 QQ 机器人?先注册一个(拿 AppID / AppSecret)
146
+
147
+ 1. 打开 [QQ 开放平台](https://q.qq.com),用 QQ 号登录;
148
+ 2. 进入「机器人」→「创建机器人」,填好名称、头像、简介;
149
+ 3. 创建完成后在机器人详情页拿到 **AppID** 与 **AppSecret**;
150
+ 4. 在 dsh Web → 设置 →「QQ 机器人」→「账号与预设」里填入并保存
151
+ (或设为环境变量 `QQBOT_APPID` / `QQBOT_SECRET`);
152
+ 5. 按需在平台开通**单聊/群聊**消息权限(群聊一般需要提交用途审核)。
153
+
154
+ > 💡 更省事:机器人建好即可,首次启动直接**扫码绑定**,不用手抄凭据。
155
+
156
+ ### 开发者:--patch 开发模式
157
+
158
+ ```bash
159
+ export QQBOT_APPID="你的AppID" QQBOT_SECRET="你的AppSecret"
160
+ npx @deepseek-ai/dsh web --patch /path/to/dsh-qqbot/cordis.dev.yml
161
+ ```
162
+
163
+ ## QQ 远程审批(可选)
164
+
165
+ 当 Agent 的工具要访问**工作区之外**的位置时,dsh 会触发权限审批。开启后,机器人把审批请求**发到 QQ**(任务发起者的会话),你直接在 QQ 里放行/拒绝:
166
+
167
+ > ⚠️ **DSH 权限申请**
168
+ > 工具:pwsh
169
+ > 原因:需要访问工作区外路径
170
+ >
171
+ > 允许本次操作:`/approve A1B2C3`
172
+ > 拒绝本次操作:`/deny A1B2C3`
173
+ > 仅本次有效,120 秒后自动拒绝。
174
+
175
+ **启用**(二选一;保存即对新审批请求生效,无需重启):
176
+ - **Web 设置面板**:设置 →「QQ 机器人」→ ⑤ QQ 远程审批 → 勾选开启;
177
+ - 或 `cordis.patch.yml` 的实例 config 加两行后重启:
178
+
179
+ ```yaml
180
+ - id: im-qqbot
181
+ config:
182
+ enableApprovals: true # 默认 false
183
+ approvalTimeoutMs: 120000 # 等待时长, 超时自动拒绝
184
+ ```
185
+
186
+ **安全边界**:验证码一次性;仅"任务发起者本人 + 同一会话"可批(群聊里其他人看到验证码也无效);只授权当前这一次操作;Agent 取消或 dsh 退出自动取消。
187
+
188
+ > 思路来源: wang-22-code/dsh-qqbot-bridge 的 QQ 审批设计(宿主 dsh `approval/request` 标准事件,官方 dsh-acp / Web 审批弹窗同款机制)。
189
+
190
+ ## QQ 群管理(可选)
191
+
192
+ 机器人**为群管理员**时,可开启群管理能力:实时接收「入群申请」并自动提醒主人、按申请审批入群、查询/设置群成员禁言。所有操作走腾讯官方 GroupOpenMsg 接口,错误信息已做"人话"映射(如 11703=机器人不是该群管理员、40103004=不能禁言群主/管理员、11255=群已注销)。
193
+
194
+ **能力总开关**:
195
+
196
+ - **Web 设置面板**: 设置 →「QQ 机器人」→ ⑥ QQ 群管理 → 勾选开启,并填/选「默认管理群」;
197
+ - 或 `cordis.patch.yml` 的实例 config 加配置后重启:
198
+
199
+ ```yaml
200
+ - id: im-qqbot
201
+ config:
202
+ groupAdmin:
203
+ enabled: true
204
+ owners: [] # 主人 openid 白名单(空=不校验)
205
+ manageGroup: "群openid" # 对话内默认管理群(web/非群会话用; 群会话自动取当前群)
206
+ watchJoinRequests: true # 订阅入群申请事件(改后需重启: 涉及连接期 intents)
207
+ notifyInGroup: true # 收到申请时在群内发提醒
208
+ ```
209
+
210
+ > ⚠️ `watchJoinRequests` 需要连接期注册 intents(GROUP_MEMBER_EVENT, 1<<24)——**改它必须重启**,不是 live 热改;且需官方对该机器人开放对应能力,否则连接可能被拒(4914/4915)。
211
+
212
+ **入群审批怎么用**: 事件到达 → bot 在群里发一条提醒(含申请人昵称/验证语)→ 你在对话里说"通过/拒绝"(AI 调 `group_approve_join`)→ 官方落库审批。也可以在设置面板「⑥ QQ 群管理 → 入群审批」页看待审批清单手动批。
213
+
214
+ **配置项**(Web 面板 ⑥ 可改, 见下表 `groupAdmin.*`)
215
+
216
+ ## 配置项
217
+
218
+ | 配置 | 类型 | 默认值 | 说明 |
219
+ |------|------|--------|------|
220
+ | `appId` | string | **必填** | QQ Bot AppID(或通过 `QQBOT_APPID` 环境变量) |
221
+ | `appSecret` | string | **必填** | QQ Bot AppSecret(或通过 `QQBOT_SECRET` 环境变量) |
222
+ | `provider` | string | `deepseek-official` | LLM 提供商名称 |
223
+ | `model` | string | `deepseek-chat` | 模型名称 |
224
+ | `preset` | string | - | Agent preset id |
225
+ | `cwd` | string | `process.cwd()` | Agent 工作目录 |
226
+ | `requireMention` | boolean | `true` | 群聊是否需要 @bot 才触发 |
227
+ | `groupPrompt` | string | - | 群聊额外 system prompt |
228
+ | `directPrompt` | string | - | 私聊额外 system prompt |
229
+ | `textChunkLimit` | number | `4500` | 单条消息最大字符数 |
230
+ | `sessionIdleTimeout` | number | `1800000` | 会话闲置超时(ms),默认 30 分钟 |
231
+ | `debug` | boolean | `false` | 调试模式 |
232
+ | `groupAdmin.enabled` | boolean | `false` | 群管理总开关(需机器人为群管理员) |
233
+ | `groupAdmin.owners` | string[] | `[]` | 可操作群管理的主人 openid 白名单(空=不校验) |
234
+ | `groupAdmin.manageGroup` | string | `''` | 对话内默认管理群 openid(web/非群会话时用) |
235
+ | `groupAdmin.watchJoinRequests` | boolean | `false` | 订阅入群申请事件(改后需重启) |
236
+ | `groupAdmin.notifyInGroup` | boolean | `true` | 收到申请时在群内发提醒 |
237
+
238
+ > 🔧 新版 Web 面板把群管理单开成「⑥ QQ 群管理」卡片(入群审批/禁言/成员信息/黑名单), 与上方 `groupAdmin.*` 配置同一份数据。
239
+
240
+ ## 内置命令
241
+
242
+ 在 QQ 群里直接发(无需 @ 机器人;走 SDK 直通,不占用 AI 回合):
243
+
244
+ | 命令 | 说明 |
245
+ |------|------|
246
+ | `/outmode` | 查看当前出站模式与四档说明 |
247
+ | `/outmode adaptive` | 切到 **适配主动**(默认): 收到真人消息前5条带引用回你, 之后自动转独立消息, 连发不被吞 |
248
+ | `/outmode passive` | 切到 **被动**: 始终回复你那条(连发约4~5条后被QQ吞) |
249
+ | `/outmode silent` | 切到 **完全不出站**: 她照常思考但不向QQ发任何回复(web可对话) |
250
+ | `/outmode nothink` | 切到 **完全不思考**: QQ入站不唤醒AI, 消息只记录(逃生通道, 可随时切回) |
251
+ | `/bot-reset` | 重置当前会话(清除上下文) |
252
+ | `/bot-new` | 开启新会话(保留旧会话历史) |
253
+ | `/bot-model` / `/model` | 查看或切换模型(如 `/bot-model deepseek-official/deepseek-v4-flash`) |
254
+ | `/bot-status` | 查看当前会话状态 |
255
+ | `/bot-ping` | 连通性测试 |
256
+ | `/bot-version` | 查看版本与当前模型 |
257
+ | `/bot-stop` | 中止当前正在生成的内容 |
258
+ | `/bot-restart` | 自重启 dsh 宿主(约4秒, 期间短暂离线, 自动拉起) |
259
+ | `/botplay` | 出互动事件目录卡(点事件直接触发, 自动翻页); `/botplay 事件名` 直接触发(如 `/botplay 签到`) |
260
+ | `/perm` | 切换权限档: `/perm` 查看; `/perm 只读\|工作区\|全权` 切换(即时生效) |
261
+ | `/new [preset]` | 以指定人格开新会话(旧会话存档可回看); `/presets` 看可用人格 |
262
+ | `/bot-help` | 查看所有指令 |
263
+ | `/tools-reload` | 热刷新 QQ 通道工具(开发用, 新工具无需重启即可用) |
264
+
265
+ > 💡 `/outmode` 是她的"逃生开关":即使处于 nothink(完全不思考)状态,SDK 直通命令也能把她唤醒——在 QQ 里发 `/outmode adaptive` 即可。
266
+
267
+ ## 用户扩展(自定义斜杠命令 / QQ 工具)(v0.9.8+)
268
+
269
+ > 给"用户自己 + AI 自己"写扩展用的。写在**账号数据目录的扩展区**(默认=账号工作目录 cwd;
270
+ > 若账号配置了 `dataRoot`, 则在 `{dataRoot}/.qqbot-extensions`), 不碰插件本体——
271
+ > 以后升级插件(换 node_modules)不会覆盖你的扩展。扩展=可执行 JS, 只在你自己的机器上跑。
272
+ > 查看当前目录: dock 账号列表会显示该账号的"数据目录"。
273
+
274
+ ### 目录结构(每账号独立)
275
+ ```
276
+ <数据目录>/
277
+ ├── 表情包/ # 图库(若配置了 dataRoot, 如 cwd/dshqqbot/表情包)
278
+ ├── .qqbot/ # 台账/定时/审批(如 cwd/dshqqbot/.qqbot)
279
+ └── .qqbot-extensions/
280
+ ├── commands/ # 自定义斜杠命令(重启后生效)
281
+ └── tools/ # 自定义 QQ 通道工具(AI 可调; 写完用 /tools-reload 或让 AI 调 tools_reload 热刷)
282
+ ```
283
+ 数据目录 = `dataRoot`(已配置, 例 `D:\...\鲸鱼娘\dshqqbot`)或账号 cwd(未配置时, 向后兼容)。
284
+
285
+ ### 自定义斜杠命令: .qqbot-extensions/commands/xxx.mjs
286
+ ```js
287
+ export default {
288
+ name: ['hello', '你好'], // 命令名(可别名数组); QQ 群发 /hello 或 /你好 触发
289
+ description: '打招呼(示例)',
290
+ usage: '/hello [名字]',
291
+ handler: (ctx) => `👋 你好 ${ctx.command.raw || ''}`.trim(), // 返回文本即回复
292
+ };
293
+ ```
294
+ 改完**重启宿主**(`/bot-restart`)生效, 或直接问 AI(它知道规则)。
295
+
296
+ ### 自定义 QQ 工具: .qqbot-extensions/tools/xxx.mjs
297
+ ```js
298
+ export default {
299
+ name: 'roll_dice',
300
+ description: '掷一颗 N 面骰子, 返回点数',
301
+ inputSchema: { // ⚠️ 可选参数不要写 required; 必填才写 required: true
302
+ sides: { type: 'integer', description: '骰子面数, 默认 6' },
303
+ },
304
+ // env: { cwd, manager, sender, replyTarget, exec } —— sender/replyTarget 可发 QQ 消息
305
+ run: async (args, env) => {
306
+ const sides = Math.max(2, Math.min(1000, Math.round(Number(args.sides) || 6)));
307
+ return { ok: true, msg: `🎲 ${1 + Math.floor(Math.random() * sides)}` };
308
+ },
309
+ };
310
+ ```
311
+ 写完在 QQ 里发 `/tools-reload`(或直接让 AI 调 `tools_reload` 工具)即可用, 无需重启。
312
+
313
+ ### 给 AI 的要点(让 AI 帮用户写扩展时照此办)
314
+ 1. 命令/工具文件都放**账号数据目录**的 `.qqbot-extensions/` 下(dataRoot 优先, 无则 cwd), 别放插件包内。
315
+ 2. 工具入参 schema 用 JSON Schema 风格; **可选参数不带 required 字段**。
316
+ 3. 写完后告知用户: 命令需重启, 工具发 `/tools-reload` 或调 tools_reload。
317
+ 4. 返回统一 `{ ok, msg }`(工具)或纯文本(命令)。
318
+
319
+ ## 富媒体指令(AI 回复里写标记,自动变成真消息)
320
+
321
+ 让 AI(或你替她)在回复正文里写以下标记,插件会自动拆出来发成真实的 QQ 消息,**标记本身不会显示**:
322
+
323
+ | 标记 | 效果 | 示例 |
324
+ |------|------|------|
325
+ | `[MEDIA:image\|来源]` | 发图片(本地路径或 http(s) 链接) | `[MEDIA:image\|D:\pics\kiss.jpg]` / `[MEDIA:image\|https://…/a.png]` |
326
+ | `[MEDIA:voice\|来源]` | 发语音(仅支持本地路径或 QQ 可拉取的链接) | `[MEDIA:voice\|D:\audio\hi.silk]` |
327
+ | `[MEDIA:video\|来源]` | 发视频 | `[MEDIA:video\|D:\videos\clip.mp4]` |
328
+ | `[MEDIA:file\|来源]` | 发文件 | `[MEDIA:file\|D:\docs\计划.pdf]` |
329
+ | `[RECALL]` | 撤回自己刚发的那条消息 | 单独一行写 `[RECALL]` |
330
+ | `[RECALL:N]` | 撤回自己发的倒数第 N 条 | 如 `[RECALL:2]` 撤倒数第二条 |
331
+
332
+ 要点:
333
+ - 图片/文件可用**本机绝对路径**或**网络 URL**;语音本地路径若为 QQ SILK 格式也能转码发送。
334
+ - ≥5MB 的本地大文件(视频/压缩包…)自动转后台分片上传,不阻塞对话。
335
+ - 一次回复可混用多条 `[MEDIA:]`,配合长文本拆条连发使用。
336
+ - 这些是"AI 会自己写"的暗号——正常聊天时她收到"发个开心点的图"这类指令,会自己调工具完成,不需要你手动写标记。
337
+
338
+ ## 核心模块
339
+
340
+ ```
341
+ src/
342
+ ├── index.ts # Cordis 插件入口(async apply)
343
+ ├── config.ts # 配置 Schema
344
+ ├── types.ts # 全局类型定义
345
+ ├── setup.ts # 凭据绑定(扫码)
346
+ ├── transport/ # 传输层
347
+ │ ├── inbound.ts # QQ 入站消息 → agent.followup()
348
+ │ ├── outbound.ts # session/event → QQ sendMarkdown
349
+ │ ├── outbound-buffer.ts # 流式缓冲
350
+ │ └── chunker.ts # Markdown 文本切分
351
+ ├── session/ # 会话管理层
352
+ │ ├── session-manager.ts # QQ peer → Agent 映射
353
+ │ └── idle-evictor.ts # 闲置回收
354
+ ├── model/ # 模型路由层
355
+ │ ├── model-resolver.ts # 路由解析
356
+ │ ├── prefs-store.ts # per-peer 偏好持久化
357
+ │ └── settings-reader.ts # settings.yaml 只读
358
+ ├── shared/ # 共享工具
359
+ │ ├── utils.ts # 通用函数
360
+ │ ├── scope.ts # scope/peer 提取
361
+ │ └── send-helper.ts # 分块发送
362
+ ├── commands/ # 斜杠命令
363
+ └── typings/ # 外部模块声明
364
+ ```
365
+
366
+ ## 会话路由
367
+
368
+ sessionKey: `qqbot:${appId}:${scope}:${peerId}`,由 SHA-256 确定性派生 SessionId,重启后可恢复。
369
+
370
+ 解析策略:进程内复用 → 持久化恢复 → 全新创建。
371
+
372
+ ## 设计原则
373
+
374
+ - **纯 Cordis 插件** — 遵循 dsh "Plugins, not loop changes" 原则
375
+ - **声明式依赖** — `inject = ['agents']`,不直接耦合其他插件
376
+ - **会话隔离** — 每个 QQ 私聊用户/群聊各一个独立 Agent
377
+ - **Preset 支持** — 可通过 `agent-presets` 服务挂载预设(工具集、prompt 等)
378
+ - **闲置回收** — 超时自动 dispose Agent,防止内存泄漏
379
+ - **Markdown 输出** — 回复以 Markdown 格式发送,支持代码块/表格感知切分
380
+
381
+ ## 本地开发
382
+
383
+ ```bash
384
+ # 安装依赖
385
+ pnpm install
386
+
387
+ # 构建
388
+ pnpm build
389
+
390
+ # 开发模式(watch)
391
+ pnpm dev
392
+
393
+ # 用 --patch 方式调试
394
+ export QQBOT_APPID="xxx" QQBOT_SECRET="xxx"
395
+ npx @deepseek-ai/dsh web --patch /path/to/dsh-qqbot/cordis.dev.yml
396
+ ```
397
+
398
+ ## License
399
+
400
+ [MIT](./LICENSE)