@zaofan/dsh-qqbot 1.5.12 → 1.5.15

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 (32) hide show
  1. package/README.md +31 -117
  2. package/{README.md.bak-1511-1790525690506 → README.md.bak-final-1790552188493} +25 -61
  3. package/{README.md.bak-1512-1790529705004 → README.md.bak-ghnote-1790548468037} +19 -99
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +100 -10
  6. package/dist/index.js.map +1 -1
  7. package/docs/USER-GUIDE.md +113 -0
  8. package/entry.js +57 -0
  9. package/package.json +136 -125
  10. package/settings-host.js +72 -25
  11. package/client/qqbot-settings.js.bak-apifix-1790512748500 +0 -4846
  12. package/client/qqbot-settings.js.bak-bind3-1790517775201 +0 -4882
  13. package/client/qqbot-settings.js.bak-clamp-195559 +0 -4780
  14. package/client/qqbot-settings.js.bak-ctxev-1790511964477 +0 -4830
  15. package/client/qqbot-settings.js.bak-ctxless-202526 +0 -4785
  16. package/client/qqbot-settings.js.bak-ctxui-1790511940536 +0 -4824
  17. package/client/qqbot-settings.js.bak-delegate-1790515486404 +0 -4844
  18. package/client/qqbot-settings.js.bak-fixctx2-1790512183370 +0 -4844
  19. package/client/qqbot-settings.js.bak-ga-041943 +0 -4775
  20. package/client/qqbot-settings.js.bak-lmreset-1790522156560 +0 -4873
  21. package/client/qqbot-settings.js.bak-lmreset2-1790522188051 +0 -4873
  22. package/client/qqbot-settings.js.bak-movectx-1790512156480 +0 -4838
  23. package/client/qqbot-settings.js.bak-privacy-1790525734851 +0 -4876
  24. package/client/qqbot-settings.js.bak-save2-1790517095028 +0 -4859
  25. package/dist/commands/approve.d.ts +0 -17
  26. package/dist/commands/approve.d.ts.map +0 -1
  27. package/dist/commands/approve.js +0 -24
  28. package/dist/commands/approve.js.map +0 -1
  29. package/dist/features/approval-switch.d.ts +0 -22
  30. package/dist/features/approval-switch.d.ts.map +0 -1
  31. package/dist/features/approval-switch.js +0 -10
  32. package/dist/features/approval-switch.js.map +0 -1
package/README.md CHANGED
@@ -67,7 +67,14 @@
67
67
 
68
68
  ## 安装
69
69
 
70
- > ⚠️ **必须装到 `web` profile**(`dsh web` 设置面板的宿主);装到别的 profile 只会得到没有设置面板的裸环境。
70
+ > ## ⚠️ 必须装到 `web` profile
71
+ >
72
+ > 本插件的**前端设置面板**(dock 球 + 「设置 → QQ 机器人」整页)只在 **`web` profile** 里生效。
73
+ > 装到别的 profile 会得到一个 **没有设置面板的裸环境** —— dock 球和设置页都不会出现,
74
+ > 表现为"插件装了但看不到任何界面"。
75
+ >
76
+ > **正常使用 `dsh web` 的用户不用关心**(`dsh web` 默认就是 `web` profile,插件页/插件市场装机也落在它上面)。
77
+ > 只有手动指定过 `--profile <其它名字>` 的场景才需要注意。
71
78
  > 也不要 `add @tencent-connect/dsh-qqbot`——那会装上游官方版(无本 fork 增强功能)。
72
79
  >
73
80
  > ✅ **单包自含,装一个就全有**:QQ 机器人 + Web 可视化设置面板(host 桥 + 设置页 UI)都打包在
@@ -82,6 +89,11 @@ npx @deepseek-ai/dsh plugin --profile web add @zaofan/dsh-qqbot
82
89
 
83
90
  > 尚未发布到 npm 前,请用下面的方式二。
84
91
 
92
+ > ⚠️ **从 GitHub 直装(`github:gcry13067381632-jpg/dsh-qqbot`)需要编译产物 `dist/`,而仓库不提交 `dist`。**
93
+ > 包已声明 `prepare` 脚本,pnpm 安装时会自动编译;
94
+ > 若你的包管理器没自动跑(或报 `failed to import` / 找不到 `dist/index.js`),
95
+ > 手动在插件目录执行一次 `npm run build` 即可。**最省事的做法是用 npm 上的发布版**(`@zaofan/dsh-qqbot`)。
96
+
85
97
  ### 方式二:源码分发(当前推荐)
86
98
 
87
99
  **Windows(一键脚本)**:
@@ -234,134 +246,36 @@ dsh: disabling profile plugin row "mcp-chrome": Plugin ... is incompatible with
234
246
 
235
247
  想强行运行,可用官方提供的**确切版本例外**(`dsh plugin allow-version`,或插件管理页里授予),但官方警告"可能崩溃或数据丢失",自行权衡。
236
248
 
237
- ## 支持这个项目
238
-
239
- 如果这个插件帮你省了 token、或者让你家的鲸鱼更活蹦乱跳 —— **给个 ⭐ Star** 就是最实在的支持;有 bug / 想要的功能,欢迎开 [Issue](https://github.com/gcry13067381632-jpg/dsh-qqbot/issues)。
240
-
241
- ## License
242
-
243
- [MIT](./LICENSE)
244
-
245
- ### Q: 升级到 dsh 0.1.7 后,我以前设置的群守则(groupPrompt)不见了?
246
-
247
- **A:这是 0.1.7 一次性导入设置时的已知坑,内容没丢,可以找回。**
248
-
249
- 0.1.7 会把旧的 `~/.dsh/settings.yaml` 整体导入 profile,但导入**不一定落到 entry config 里**;
250
- 插件的设置页读不到自定义值 → 拿到默认值 → 保存时把默认值写回 → 你的自定义内容就被顶掉了。
251
-
252
- **找回步骤**:
253
- 1. 打开 `~/.dsh/settings.yaml.imported`(宿主留档的原件,同目录通常还有 `.bak`)
254
- 2. 找到你实例对应的 section(例如 `im-qqbot-2:`),再往下找 `groupPrompt:` 那一行
255
- 3. 复制它的内容(`|-` 或 `>-` 块标量,注意去掉块缩进)
256
- 4. 回到 Web 设置页的「群聊常驻守则」,粘贴回去并**保存**
257
- — 1.5.8 起设置保存在插件自有存储(`~/.dsh/qqbot-settings/<实例>.json`),不会再丢
258
-
259
- > ⚠️ **群守则里常含私人信息**(主人 openid、进群暗号等)。**发布、分享配置或截图时请务必剔除 `groupPrompt`。**
260
-
261
- ### Q: 设置页保存后,为什么 patch 里看不到我的改动?
262
-
263
- **A:1.5.8 起,设置页保存写在插件自有存储,**故意不写** `cordis.patch.yml`。**
264
-
265
- - 位置:`~/.dsh/qqbot-settings/<实例名>.json`(例如 `im-qqbot-2.json`)
266
- - 为什么不写 patch:写 patch 会触发宿主热更新 → 插件重新 apply → 若此时凭据无效又会写配置,
267
- **形成死循环**(实测把 dsh 启动刷死);而且手拼 YAML 保不住字段类型、也不支持新增键。
268
- - 生效优先级:**自有存储覆盖 patch**。所以两份不一致时,以自有存储为准。
269
-
270
- ### Q: 面板上「实时入群事件」开关是干什么的?打开后机器人连不上了?
271
-
272
- **A:它订阅 `GROUP_JOIN_REQUEST`(群成员事件,intent `1<<24`),需要 QQ 开放平台**先开通**该事件。**
273
-
274
- 未开通时打开会导致**机器人连不上**(网关拒绝 Identify,close `4914`/`4915`)。
275
- 正确顺序:**① 平台开通事件 → ② 打开开关 → ③ 重启**。
276
-
277
- **只想收通知、不想折腾平台?** 用「轮询审批」即可(不需要任何事件授权):
278
- - 勾「轮询审批」= 定时拉取各群审批列表
279
- - 再按需勾「唤醒AI」/「注入群管会话」/「通知普通群」决定拿到申请后做什么
280
- - ⚠️ 注意:`唤醒AI` 与 `注入群管会话` **互斥且唤醒优先** —— 两个都勾时只会唤醒 AI,不会注入会话。
281
- 想要「静默注入群管会话」就**只勾「注入群管会话」**。
282
-
283
- ### Q: 为什么自有存储里不要出现空对象(`{}`)?
284
-
285
- **A:空对象会把 patch 里的整块配置顶掉,导致依赖它的功能静默失效。**
286
-
287
- 插件读取配置时会把「自有存储」(`~/.dsh/qqbot-settings/<实例>.json`) 与 profile 的 patch 合并。
288
- 如果自有存储里某个字段是**空对象**,浅合并会把 patch 里该字段的**整块配置顶掉**。
289
-
290
- **真实案例**:`sticker: {}` 让 `config.sticker.collectEnabled` 变成 `undefined`
291
- → **图片自动下载停摆**(现象:最后一张自动下载的图停在某个时刻,之后入站图片只剩 QQ 长 URL)。
292
-
293
- 1.5.9 起已改为**递归合并**(空对象不再覆盖),但如果你手动编辑过那份 JSON:
294
- - **想恢复默认**:直接**删掉该字段**,不要写 `{}`
295
- - 只写你**真正改过**的键
296
-
297
- ### Q: 升级后我自定义的配置(群守则 / 注入规则 / 互动事件)被默认值顶掉了,能找回吗?
298
-
299
- **A:能。宿主把旧的 `settings.yaml` 留档了。**
300
-
301
- 0.1.7 会把 `~/.dsh/settings.yaml` 一次性导入 profile,但**导入不一定落到 entry config 里**;
302
- 插件读不到自定义值时就会用默认值回写,把你的配置顶掉。
303
-
304
- **找回步骤**:
305
- 1. 打开 `~/.dsh/settings.yaml.imported`(同目录通常还有 `.bak`)
306
- 2. 找到你实例的 section(如 `im-qqbot-2:`),再找对应字段(`groupPrompt:` / `injectRules:` / `botplayEvents:` …)
307
- 3. 复制内容,回到 Web 设置页粘回去并保存
308
- (1.5.9 起配置存在插件自有存储,不会再被默认值覆盖)
309
-
310
- > ⚠️ 群守则里常含私人信息(主人 openid、进群暗号等),**发布/分享/截图时记得剔除**。
311
-
312
- ---
313
-
314
249
  ## 🚫 无上下文模式(按会话省 token)
315
250
 
316
- **用途**:让某个 QQ 会话"每轮只记得最近几条对话",更早的历史自动折叠 —— 群聊场景能省下大量 token。
317
-
318
- ### 怎么开
319
-
320
- 1. 打开 dock 面板(右下角 🛡)→「⚙ **单会话设置**」→ 子标签「🚫 **无上下文**」
321
- 2. 面板会显示**当前会话命中的群**("作用对象"),确认是你要设的那个
322
- 3. 勾选「无上下文模式」,填「带 @ 前 N 条」,点「💾 保存(本会话)」
323
- - 保存成功会显示「✅ 已保存并回读确认: 开启 / 带 N 条」
251
+ 让某个 QQ 会话**每轮只记得最近几条对话**,更早的历史自动折叠 —— 群聊省 token 利器。
324
252
 
325
- ### 行为说明(几个容易误解的地方)
253
+ **怎么开**:dock 面板(右下角 🛡)→「⚙ 单会话设置」→ 子标签「🚫 无上下文」→ 勾选 + 填「带 @ 前 N 条」→ 保存。
326
254
 
327
- | 现象 | 说明 |
328
- |---|---|
329
- | **web 上还能翻到旧对话** | ✅ 正常。被折叠的只是「模型视野」,**会话记录本身不删**;web 里会多一行「上下文已压缩 · 已压缩 N 条历史记录」的折叠标记 |
330
- | **N 条 ≠ N 条 QQ 消息** | ⚠️ N 计的是 **dsh 侧的消息条数**。插件入站时会把"群历史 + 当前消息"**聚合成一条**,所以 1 条可能含多条 QQ 消息(群历史缓冲上限由 `historyLimit` 控制,默认 10) |
331
- | **群守则每轮都重新注入** | ✅ 正常且必要。压缩会把旧的守则一起折叠,所以每轮补一份;**改了群守则会立刻生效**(去重是按内容比较) |
332
- | **什么时候生效** | 压缩发生在**回合开始**,所以**从下一轮起**才看不到更早的对话(当轮她已经装好上下文了) |
333
- | **会调额外的模型吗** | ❌ 不会。替身文本是写死的,**零 LLM 调用**(这点比 dsh 自带 compaction 的"生成摘要"更省) |
255
+ **几点先说清**(细节见手册):
256
+ - **web 记录不删** —— 只多一行「上下文已压缩」折叠标记
257
+ - **N 计的是 dsh 侧的消息条数**,1 条可能含多条 QQ 消息(入站时会聚合)
258
+ - **零额外模型调用**(替身文本写死)
259
+ - **从下一轮起生效**(压缩发生在回合开始)
334
260
 
335
- ### 全局开启(可选)
261
+ 📖 完整说明(含易误解点、全局开关、存储位置)→ **[用户手册](docs/USER-GUIDE.md#无上下文模式按会话省-token)**
336
262
 
337
- 不想逐会话设置,也可以在插件配置里开全局:
338
-
339
- ```yaml
340
- contextlessMode: true # 所有会话都启用
341
- contextlessWindow: 5 # 保留最近 5 条
342
- ```
343
-
344
- ### 存储位置
263
+ ## ✅ QQ 审批卡片:谁可以点
345
264
 
346
- `{dataRoot}/.qqbot/contextless.json` —— 键是会话(`qqbot:<appId>:group:<群openid>`),值是 `{ enabled, window }`。
265
+ 发起审批的卡片**发到发起者所在的会话**(群里就发到群里),但**只有下列人能点按钮**:
266
+ **① 发起者本人 ② 主人白名单**(`groupAdmin.owners`)。
347
267
 
348
- > ⚠️ 用 dedupe 脚本清历史时注意:不要手动编辑这个文件(面板保存会回读校验);如果想全部关掉,把每个 key 的 `enabled` 改成 `false` 即可。
268
+ > ⚠️ **群聊场景一定要填白名单**:设置 → QQ 机器人 →「允许操作的主人 openid(逗号分隔,可留空=不校验)」。
269
+ > 否则别人发起的审批,主人点了没反应。
349
270
 
271
+ 📖 细节 → **[用户手册](docs/USER-GUIDE.md#qq-审批卡片谁可以点)**
350
272
 
351
273
  ---
352
274
 
353
- ## ✅ QQ 审批卡片:谁可以点
354
-
355
- 当某个工具调用需要主人批准时,机器人会把「审批卡片」发到**发起者所在的会话**(群里就发到群里)。
356
- 卡片的按钮**只有下列人可以点**:
357
-
358
- 1. **发起者本人**
359
- 2. **主人白名单** —— `groupAdmin.owners`
275
+ ## 支持这个项目
360
276
 
361
- > 白名单在哪填:**设置 → QQ 机器人 → 「允许操作的主人 openid(逗号分隔,可留空=不校验)」**
362
- >
363
- > ⚠️ **群聊场景一定要填**:否则别人发起的审批,主人点了没反应(卡片只认发起者)。
364
- > 私聊场景不受影响(本就只有双方)。
277
+ 如果这个插件帮你省了 token、或者让你家的鲸鱼更活蹦乱跳 —— **给个 ⭐ Star** 就是最实在的支持;有 bug / 想要的功能,欢迎开 [Issue](https://github.com/gcry13067381632-jpg/dsh-qqbot/issues)。
365
278
 
366
- **这个白名单同时用于**:群管理工具的权限校验 + 审批卡片的可点名单(同一个"主人"概念)。
279
+ ## License
367
280
 
281
+ [MIT](./LICENSE)
@@ -82,6 +82,11 @@ npx @deepseek-ai/dsh plugin --profile web add @zaofan/dsh-qqbot
82
82
 
83
83
  > 尚未发布到 npm 前,请用下面的方式二。
84
84
 
85
+ > ⚠️ **从 GitHub 直装(`github:gcry13067381632-jpg/dsh-qqbot`)需要编译产物 `dist/`,而仓库不提交 `dist`。**
86
+ > 包已声明 `prepare` 脚本,pnpm 安装时会自动编译;
87
+ > 若你的包管理器没自动跑(或报 `failed to import` / 找不到 `dist/index.js`),
88
+ > 手动在插件目录执行一次 `npm run build` 即可。**最省事的做法是用 npm 上的发布版**(`@zaofan/dsh-qqbot`)。
89
+
85
90
  ### 方式二:源码分发(当前推荐)
86
91
 
87
92
  **Windows(一键脚本)**:
@@ -234,77 +239,36 @@ dsh: disabling profile plugin row "mcp-chrome": Plugin ... is incompatible with
234
239
 
235
240
  想强行运行,可用官方提供的**确切版本例外**(`dsh plugin allow-version`,或插件管理页里授予),但官方警告"可能崩溃或数据丢失",自行权衡。
236
241
 
237
- ## 支持这个项目
238
-
239
- 如果这个插件帮你省了 token、或者让你家的鲸鱼更活蹦乱跳 —— **给个 ⭐ Star** 就是最实在的支持;有 bug / 想要的功能,欢迎开 [Issue](https://github.com/gcry13067381632-jpg/dsh-qqbot/issues)。
240
-
241
- ## License
242
-
243
- [MIT](./LICENSE)
244
-
245
- ### Q: 升级到 dsh 0.1.7 后,我以前设置的群守则(groupPrompt)不见了?
246
-
247
- **A:这是 0.1.7 一次性导入设置时的已知坑,内容没丢,可以找回。**
248
-
249
- 0.1.7 会把旧的 `~/.dsh/settings.yaml` 整体导入 profile,但导入**不一定落到 entry config 里**;
250
- 插件的设置页读不到自定义值 → 拿到默认值 → 保存时把默认值写回 → 你的自定义内容就被顶掉了。
242
+ ## 🚫 无上下文模式(按会话省 token)
251
243
 
252
- **找回步骤**:
253
- 1. 打开 `~/.dsh/settings.yaml.imported`(宿主留档的原件,同目录通常还有 `.bak`)
254
- 2. 找到你实例对应的 section(例如 `im-qqbot-2:`),再往下找 `groupPrompt:` 那一行
255
- 3. 复制它的内容(`|-` 或 `>-` 块标量,注意去掉块缩进)
256
- 4. 回到 Web 设置页的「群聊常驻守则」,粘贴回去并**保存**
257
- — 1.5.8 起设置保存在插件自有存储(`~/.dsh/qqbot-settings/<实例>.json`),不会再丢
244
+ 让某个 QQ 会话**每轮只记得最近几条对话**,更早的历史自动折叠 —— 群聊省 token 利器。
258
245
 
259
- > ⚠️ **群守则里常含私人信息**(主人 openid、进群暗号等)。**发布、分享配置或截图时请务必剔除 `groupPrompt`。**
246
+ **怎么开**:dock 面板(右下角 🛡)→「⚙ 单会话设置」→ 子标签「🚫 无上下文」→ 勾选 + 填「带 @ 前 N 条」→ 保存。
260
247
 
261
- ### Q: 设置页保存后,为什么 patch 里看不到我的改动?
248
+ **几点先说清**(细节见手册):
249
+ - **web 记录不删** —— 只多一行「上下文已压缩」折叠标记
250
+ - **N 计的是 dsh 侧的消息条数**,1 条可能含多条 QQ 消息(入站时会聚合)
251
+ - **零额外模型调用**(替身文本写死)
252
+ - **从下一轮起生效**(压缩发生在回合开始)
262
253
 
263
- **A:1.5.8 起,设置页保存写在插件自有存储,**故意不写** `cordis.patch.yml`。**
254
+ 📖 完整说明(含易误解点、全局开关、存储位置)→ **[用户手册](docs/USER-GUIDE.md#无上下文模式按会话省-token)**
264
255
 
265
- - 位置:`~/.dsh/qqbot-settings/<实例名>.json`(例如 `im-qqbot-2.json`)
266
- - 为什么不写 patch:写 patch 会触发宿主热更新 → 插件重新 apply → 若此时凭据无效又会写配置,
267
- **形成死循环**(实测把 dsh 启动刷死);而且手拼 YAML 保不住字段类型、也不支持新增键。
268
- - 生效优先级:**自有存储覆盖 patch**。所以两份不一致时,以自有存储为准。
256
+ ## ✅ QQ 审批卡片:谁可以点
269
257
 
270
- ### Q: 面板上「实时入群事件」开关是干什么的?打开后机器人连不上了?
258
+ 发起审批的卡片**发到发起者所在的会话**(群里就发到群里),但**只有下列人能点按钮**:
259
+ **① 发起者本人 ② 主人白名单**(`groupAdmin.owners`)。
271
260
 
272
- **A:它订阅 `GROUP_JOIN_REQUEST`(群成员事件,intent `1<<24`),需要 QQ 开放平台**先开通**该事件。**
261
+ > ⚠️ **群聊场景一定要填白名单**:设置 → QQ 机器人 →「允许操作的主人 openid(逗号分隔,可留空=不校验)」。
262
+ > 否则别人发起的审批,主人点了没反应。
273
263
 
274
- 未开通时打开会导致**机器人连不上**(网关拒绝 Identify,close `4914`/`4915`)。
275
- 正确顺序:**① 平台开通事件 → ② 打开开关 → ③ 重启**。
264
+ 📖 细节 → **[用户手册](docs/USER-GUIDE.md#qq-审批卡片谁可以点)**
276
265
 
277
- **只想收通知、不想折腾平台?** 用「轮询审批」即可(不需要任何事件授权):
278
- - 勾「轮询审批」= 定时拉取各群审批列表
279
- - 再按需勾「唤醒AI」/「注入群管会话」/「通知普通群」决定拿到申请后做什么
280
- - ⚠️ 注意:`唤醒AI` 与 `注入群管会话` **互斥且唤醒优先** —— 两个都勾时只会唤醒 AI,不会注入会话。
281
- 想要「静默注入群管会话」就**只勾「注入群管会话」**。
282
-
283
- ### Q: 为什么自有存储里不要出现空对象(`{}`)?
284
-
285
- **A:空对象会把 patch 里的整块配置顶掉,导致依赖它的功能静默失效。**
286
-
287
- 插件读取配置时会把「自有存储」(`~/.dsh/qqbot-settings/<实例>.json`) 与 profile 的 patch 合并。
288
- 如果自有存储里某个字段是**空对象**,浅合并会把 patch 里该字段的**整块配置顶掉**。
289
-
290
- **真实案例**:`sticker: {}` 让 `config.sticker.collectEnabled` 变成 `undefined`
291
- → **图片自动下载停摆**(现象:最后一张自动下载的图停在某个时刻,之后入站图片只剩 QQ 长 URL)。
292
-
293
- 1.5.9 起已改为**递归合并**(空对象不再覆盖),但如果你手动编辑过那份 JSON:
294
- - **想恢复默认**:直接**删掉该字段**,不要写 `{}`
295
- - 只写你**真正改过**的键
296
-
297
- ### Q: 升级后我自定义的配置(群守则 / 注入规则 / 互动事件)被默认值顶掉了,能找回吗?
266
+ ---
298
267
 
299
- **A:能。宿主把旧的 `settings.yaml` 留档了。**
268
+ ## 支持这个项目
300
269
 
301
- 0.1.7 会把 `~/.dsh/settings.yaml` 一次性导入 profile,但**导入不一定落到 entry config 里**;
302
- 插件读不到自定义值时就会用默认值回写,把你的配置顶掉。
270
+ 如果这个插件帮你省了 token、或者让你家的鲸鱼更活蹦乱跳 —— **给个 ⭐ Star** 就是最实在的支持;有 bug / 想要的功能,欢迎开 [Issue](https://github.com/gcry13067381632-jpg/dsh-qqbot/issues)。
303
271
 
304
- **找回步骤**:
305
- 1. 打开 `~/.dsh/settings.yaml.imported`(同目录通常还有 `.bak`)
306
- 2. 找到你实例的 section(如 `im-qqbot-2:`),再找对应字段(`groupPrompt:` / `injectRules:` / `botplayEvents:` …)
307
- 3. 复制内容,回到 Web 设置页粘回去并保存
308
- (1.5.9 起配置存在插件自有存储,不会再被默认值覆盖)
272
+ ## License
309
273
 
310
- > ⚠️ 群守则里常含私人信息(主人 openid、进群暗号等),**发布/分享/截图时记得剔除**。
274
+ [MIT](./LICENSE)
@@ -234,116 +234,36 @@ dsh: disabling profile plugin row "mcp-chrome": Plugin ... is incompatible with
234
234
 
235
235
  想强行运行,可用官方提供的**确切版本例外**(`dsh plugin allow-version`,或插件管理页里授予),但官方警告"可能崩溃或数据丢失",自行权衡。
236
236
 
237
- ## 支持这个项目
238
-
239
- 如果这个插件帮你省了 token、或者让你家的鲸鱼更活蹦乱跳 —— **给个 ⭐ Star** 就是最实在的支持;有 bug / 想要的功能,欢迎开 [Issue](https://github.com/gcry13067381632-jpg/dsh-qqbot/issues)。
240
-
241
- ## License
242
-
243
- [MIT](./LICENSE)
244
-
245
- ### Q: 升级到 dsh 0.1.7 后,我以前设置的群守则(groupPrompt)不见了?
246
-
247
- **A:这是 0.1.7 一次性导入设置时的已知坑,内容没丢,可以找回。**
248
-
249
- 0.1.7 会把旧的 `~/.dsh/settings.yaml` 整体导入 profile,但导入**不一定落到 entry config 里**;
250
- 插件的设置页读不到自定义值 → 拿到默认值 → 保存时把默认值写回 → 你的自定义内容就被顶掉了。
251
-
252
- **找回步骤**:
253
- 1. 打开 `~/.dsh/settings.yaml.imported`(宿主留档的原件,同目录通常还有 `.bak`)
254
- 2. 找到你实例对应的 section(例如 `im-qqbot-2:`),再往下找 `groupPrompt:` 那一行
255
- 3. 复制它的内容(`|-` 或 `>-` 块标量,注意去掉块缩进)
256
- 4. 回到 Web 设置页的「群聊常驻守则」,粘贴回去并**保存**
257
- — 1.5.8 起设置保存在插件自有存储(`~/.dsh/qqbot-settings/<实例>.json`),不会再丢
258
-
259
- > ⚠️ **群守则里常含私人信息**(主人 openid、进群暗号等)。**发布、分享配置或截图时请务必剔除 `groupPrompt`。**
260
-
261
- ### Q: 设置页保存后,为什么 patch 里看不到我的改动?
262
-
263
- **A:1.5.8 起,设置页保存写在插件自有存储,**故意不写** `cordis.patch.yml`。**
264
-
265
- - 位置:`~/.dsh/qqbot-settings/<实例名>.json`(例如 `im-qqbot-2.json`)
266
- - 为什么不写 patch:写 patch 会触发宿主热更新 → 插件重新 apply → 若此时凭据无效又会写配置,
267
- **形成死循环**(实测把 dsh 启动刷死);而且手拼 YAML 保不住字段类型、也不支持新增键。
268
- - 生效优先级:**自有存储覆盖 patch**。所以两份不一致时,以自有存储为准。
269
-
270
- ### Q: 面板上「实时入群事件」开关是干什么的?打开后机器人连不上了?
271
-
272
- **A:它订阅 `GROUP_JOIN_REQUEST`(群成员事件,intent `1<<24`),需要 QQ 开放平台**先开通**该事件。**
273
-
274
- 未开通时打开会导致**机器人连不上**(网关拒绝 Identify,close `4914`/`4915`)。
275
- 正确顺序:**① 平台开通事件 → ② 打开开关 → ③ 重启**。
276
-
277
- **只想收通知、不想折腾平台?** 用「轮询审批」即可(不需要任何事件授权):
278
- - 勾「轮询审批」= 定时拉取各群审批列表
279
- - 再按需勾「唤醒AI」/「注入群管会话」/「通知普通群」决定拿到申请后做什么
280
- - ⚠️ 注意:`唤醒AI` 与 `注入群管会话` **互斥且唤醒优先** —— 两个都勾时只会唤醒 AI,不会注入会话。
281
- 想要「静默注入群管会话」就**只勾「注入群管会话」**。
282
-
283
- ### Q: 为什么自有存储里不要出现空对象(`{}`)?
284
-
285
- **A:空对象会把 patch 里的整块配置顶掉,导致依赖它的功能静默失效。**
237
+ ## 🚫 无上下文模式(按会话省 token)
286
238
 
287
- 插件读取配置时会把「自有存储」(`~/.dsh/qqbot-settings/<实例>.json`) 与 profile 的 patch 合并。
288
- 如果自有存储里某个字段是**空对象**,浅合并会把 patch 里该字段的**整块配置顶掉**。
239
+ 让某个 QQ 会话**每轮只记得最近几条对话**,更早的历史自动折叠 —— 群聊省 token 利器。
289
240
 
290
- **真实案例**:`sticker: {}` 让 `config.sticker.collectEnabled` 变成 `undefined`
291
- → **图片自动下载停摆**(现象:最后一张自动下载的图停在某个时刻,之后入站图片只剩 QQ 长 URL)。
241
+ **怎么开**:dock 面板(右下角 🛡)→「⚙ 单会话设置」→ 子标签「🚫 无上下文」→ 勾选 + 填「带 @ 前 N 条」→ 保存。
292
242
 
293
- 1.5.9 起已改为**递归合并**(空对象不再覆盖),但如果你手动编辑过那份 JSON:
294
- - **想恢复默认**:直接**删掉该字段**,不要写 `{}`
295
- - 只写你**真正改过**的键
243
+ **几点先说清**(细节见手册):
244
+ - **web 记录不删** —— 只多一行「上下文已压缩」折叠标记
245
+ - **N 计的是 dsh 侧的消息条数**,1 条可能含多条 QQ 消息(入站时会聚合)
246
+ - **零额外模型调用**(替身文本写死)
247
+ - **从下一轮起生效**(压缩发生在回合开始)
296
248
 
297
- ### Q: 升级后我自定义的配置(群守则 / 注入规则 / 互动事件)被默认值顶掉了,能找回吗?
249
+ 📖 完整说明(含易误解点、全局开关、存储位置)→ **[用户手册](docs/USER-GUIDE.md#无上下文模式按会话省-token)**
298
250
 
299
- **A:能。宿主把旧的 `settings.yaml` 留档了。**
251
+ ## ✅ QQ 审批卡片:谁可以点
300
252
 
301
- 0.1.7 会把 `~/.dsh/settings.yaml` 一次性导入 profile,但**导入不一定落到 entry config 里**;
302
- 插件读不到自定义值时就会用默认值回写,把你的配置顶掉。
253
+ 发起审批的卡片**发到发起者所在的会话**(群里就发到群里),但**只有下列人能点按钮**:
254
+ **① 发起者本人 ② 主人白名单**(`groupAdmin.owners`)。
303
255
 
304
- **找回步骤**:
305
- 1. 打开 `~/.dsh/settings.yaml.imported`(同目录通常还有 `.bak`)
306
- 2. 找到你实例的 section(如 `im-qqbot-2:`),再找对应字段(`groupPrompt:` / `injectRules:` / `botplayEvents:` …)
307
- 3. 复制内容,回到 Web 设置页粘回去并保存
308
- (1.5.9 起配置存在插件自有存储,不会再被默认值覆盖)
256
+ > ⚠️ **群聊场景一定要填白名单**:设置 → QQ 机器人 →「允许操作的主人 openid(逗号分隔,可留空=不校验)」。
257
+ > 否则别人发起的审批,主人点了没反应。
309
258
 
310
- > ⚠️ 群守则里常含私人信息(主人 openid、进群暗号等),**发布/分享/截图时记得剔除**。
259
+ 📖 细节 → **[用户手册](docs/USER-GUIDE.md#qq-审批卡片谁可以点)**
311
260
 
312
261
  ---
313
262
 
314
- ## 🚫 无上下文模式(按会话省 token)
315
-
316
- **用途**:让某个 QQ 会话"每轮只记得最近几条对话",更早的历史自动折叠 —— 群聊场景能省下大量 token。
317
-
318
- ### 怎么开
319
-
320
- 1. 打开 dock 面板(右下角 🛡)→「⚙ **单会话设置**」→ 子标签「🚫 **无上下文**」
321
- 2. 面板会显示**当前会话命中的群**("作用对象"),确认是你要设的那个
322
- 3. 勾选「无上下文模式」,填「带 @ 前 N 条」,点「💾 保存(本会话)」
323
- - 保存成功会显示「✅ 已保存并回读确认: 开启 / 带 N 条」
324
-
325
- ### 行为说明(几个容易误解的地方)
326
-
327
- | 现象 | 说明 |
328
- |---|---|
329
- | **web 上还能翻到旧对话** | ✅ 正常。被折叠的只是「模型视野」,**会话记录本身不删**;web 里会多一行「上下文已压缩 · 已压缩 N 条历史记录」的折叠标记 |
330
- | **N 条 ≠ N 条 QQ 消息** | ⚠️ N 计的是 **dsh 侧的消息条数**。插件入站时会把"群历史 + 当前消息"**聚合成一条**,所以 1 条可能含多条 QQ 消息(群历史缓冲上限由 `historyLimit` 控制,默认 10) |
331
- | **群守则每轮都重新注入** | ✅ 正常且必要。压缩会把旧的守则一起折叠,所以每轮补一份;**改了群守则会立刻生效**(去重是按内容比较) |
332
- | **什么时候生效** | 压缩发生在**回合开始**,所以**从下一轮起**才看不到更早的对话(当轮她已经装好上下文了) |
333
- | **会调额外的模型吗** | ❌ 不会。替身文本是写死的,**零 LLM 调用**(这点比 dsh 自带 compaction 的"生成摘要"更省) |
334
-
335
- ### 全局开启(可选)
336
-
337
- 不想逐会话设置,也可以在插件配置里开全局:
338
-
339
- ```yaml
340
- contextlessMode: true # 所有会话都启用
341
- contextlessWindow: 5 # 保留最近 5 条
342
- ```
343
-
344
- ### 存储位置
263
+ ## 支持这个项目
345
264
 
346
- `{dataRoot}/.qqbot/contextless.json` —— 键是会话(`qqbot:<appId>:group:<群openid>`),值是 `{ enabled, window }`。
265
+ 如果这个插件帮你省了 token、或者让你家的鲸鱼更活蹦乱跳 —— **给个 ⭐ Star** 就是最实在的支持;有 bug / 想要的功能,欢迎开 [Issue](https://github.com/gcry13067381632-jpg/dsh-qqbot/issues)。
347
266
 
348
- > ⚠️ 用 dedupe 脚本清历史时注意:不要手动编辑这个文件(面板保存会回读校验);如果想全部关掉,把每个 key 的 `enabled` 改成 `false` 即可。
267
+ ## License
349
268
 
269
+ [MIT](./LICENSE)
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAA2D,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAqB1G,eAAO,MAAM,IAAI,aAAa,CAAC;AAG/B,eAAO,MAAM,MAAM,UAA6B,CAAC;AACjD,eAAO,MAAM,MAAM,2DAAe,CAAC;AAEnC,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAsJ9E;AAuFD,wBAAgB,kBAAkB,IAAI,OAAO,CAAyB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAA2D,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAqB1G,eAAO,MAAM,IAAI,aAAa,CAAC;AAG/B,eAAO,MAAM,MAAM,UAA6B,CAAC;AACjD,eAAO,MAAM,MAAM,2DAAe,CAAC;AAEnC,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAyO9E;AAuFD,wBAAgB,kBAAkB,IAAI,OAAO,CAAyB"}
package/dist/index.js CHANGED
@@ -33,6 +33,25 @@ export const Config = ConfigSchema;
33
33
  export async function apply(ctx, config) {
34
34
  const agents = ctx.agents;
35
35
  const logger = ctx.logger ?? console;
36
+ // __INSTALL_SELFCHECK__ 安装完整性自检(2026-09-28)
37
+ // entry.js(包入口)已拦住"缺 dist";这里补查"import 成功但功能不全"
38
+ // (缺前端 / 缺配置桥 / 缺 patch —— 常见于"从 GitHub 直装但没跑构建")。
39
+ try {
40
+ const here = new URL('.', import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, '$1'); // → .../dist/
41
+ const pkgRoot = here.replace(/[\\/]dist[\\/]?$/, '');
42
+ const expect = ['client/qqbot-settings.js', 'settings-host.js', 'cordis.patch.yml'];
43
+ const miss = expect.filter((r) => !existsSync(pkgRoot + '/' + r));
44
+ if (miss.length > 0) {
45
+ logger.warn('');
46
+ logger.warn(' ⚠️ dsh-qqbot 安装不完整:缺少 ' + miss.join(', '));
47
+ logger.warn(' 插件主功能可用,但设置面板 / 配置桥可能不工作。');
48
+ logger.warn(' 建议改用 npm 发布版安装(自带全部文件,无需构建):');
49
+ logger.warn(' pnpm add @zaofan/dsh-qqbot');
50
+ logger.warn(' (安装目录: ' + pkgRoot + ')');
51
+ logger.warn('');
52
+ }
53
+ }
54
+ catch { /* 自检失败不影响启动 */ }
36
55
  // 多账号实例身份: settingsNs 承担"实例 id"(账号页保存时写入, 默认 im-qqbot=主账号)。
37
56
  const ns = (config.settingsNs ?? '').trim() || 'im-qqbot';
38
57
  const isMain = ns === 'im-qqbot';
@@ -46,14 +65,24 @@ export async function apply(ctx, config) {
46
65
  let appSecret = resolveEnv(config.appSecret, 'QQBOT_SECRET');
47
66
  const cwdOverride = resolveEnv(config.cwd ?? '', 'QQBOT_CWD') || undefined;
48
67
  const presetOverride = resolveEnv(config.preset ?? '', 'QQBOT_PRESET') || undefined;
49
- // ── 凭据缺失 ──
50
- if (!appId || !appSecret) {
68
+ // ── 凭据检查(⚠️ 2026-09-28 重要改动:不再"缺凭据就整个 return")──
69
+ // 原来缺凭据直接 return,会连带跳过【设置面板 host 桥】的装载,后果是新用户装完插件打开 dsh:
70
+ // · /api/qqbot-settings/* 全部 404
71
+ // · 前端 entry 激活失败 → 页面报 "1 entry did not activate"
72
+ // · 根本走不到「扫码绑定」—— 连填凭据的界面都打不开。
73
+ // 现在改为:**设置面板照常装载**(settings + host 桥),只把【网关启动】留到凭据检查之后。
74
+ // 新用户路径:装插件 → 打开 dsh → 设置页「QQ 机器人」→ 填 AppID/Secret 或点「扫码绑定」
75
+ // → 凭据写入 → 热更新 → 本次 apply 有凭据 → 自动连上。
76
+ // 仍然**不自动弹二维码**(保持 2026-09-24 的死循环修复:扫码只在用户主动点击时发生)。
77
+ const hasCred = !!appId && !!appSecret;
78
+ if (!hasCred) {
51
79
  // ⚠️ 2026-09-24 主人要求:**启动时不再自动弹二维码**。
52
80
  // 原因:自动扫码会 persistCredentialsToProfile() 写 profile patch → 触发 dsh 热更新
53
81
  // → 插件重新 apply → 凭据仍缺失 → 再扫码,形成死循环(实测把宿主启动刷死)。
54
82
  // 现在一律只提示;需要扫码时去 Web「账号与预设」点「扫码绑定」(那条路径是交互式的,不会自转)。
55
- logger.error(`实例 ${ns}: 未配置 appId/appSecret——请在 Web「账号与预设」填写凭据(或在那里点「扫码绑定」)后保存,本实例本次不启动。`);
56
- return;
83
+ logger.warn(`实例 ${ns}: 尚未配置 appId/appSecret —— 机器人暂不连接 QQ,但设置面板已就绪。`);
84
+ logger.warn(' · 请在 dsh 设置页「QQ 机器人」里填写 AppID/AppSecret,或点「扫码绑定」;');
85
+ logger.warn(' · 凭据保存后会热生效,本实例自动连接(无需重启)。');
57
86
  }
58
87
  const resolvedConfig = {
59
88
  ...config,
@@ -110,6 +139,31 @@ export async function apply(ctx, config) {
110
139
  Object.assign(resolvedConfig, mergeDeep(resolvedConfig, ownSettings));
111
140
  logger.info(`已加载插件自有设置(${Object.keys(ownSettings).length} 项): ${ownSettingsFile}`);
112
141
  }
142
+ // ⚠️ 2026-09-28 修:监听里除了热更新配置,还要处理"从无凭据 → 有凭据"的自动启动网关。
143
+ // 背景:bootstrapGateway 只在 apply() 里跑一次。用户首次安装时没有凭据,
144
+ // 插件只是"装好设置面板"就结束了(见下方 hasCred 分支)→ 此后在设置页填好凭据,
145
+ // 配置对象虽然更新了,但**网关从未启动** → 表现就是"填了密码却不会自动重连"(必须重启)。
146
+ // 现在:一旦检测到凭据从无到有,就自动启动一次网关。
147
+ let gatewayStarted = false;
148
+ const tryStartGateway = async () => {
149
+ if (gatewayStarted)
150
+ return;
151
+ const cur = resolvedConfig;
152
+ const id = String(cur.appId ?? '');
153
+ const sec = String(cur.appSecret ?? '');
154
+ if (!id || !sec)
155
+ return;
156
+ gatewayStarted = true;
157
+ try {
158
+ logger.info(`实例 ${ns}: 检测到凭据已就绪,开始启动网关…`);
159
+ await bootstrapGateway(ctx, agents, resolvedConfig, logger);
160
+ logger.info(`实例 ${ns}: 网关已启动(凭据热更新生效,无需重启)`);
161
+ }
162
+ catch (err) {
163
+ gatewayStarted = false; // 失败则允许下次重试
164
+ logger.warn?.(`实例 ${ns}: 凭据热更新后启动网关失败: ${err instanceof Error ? err.message : String(err)}`);
165
+ }
166
+ };
113
167
  try {
114
168
  ctx.on('qqbot/settings-changed', (changedNs) => {
115
169
  if (changedNs && changedNs !== ns)
@@ -117,6 +171,7 @@ export async function apply(ctx, config) {
117
171
  const next = readOwnSettings();
118
172
  Object.assign(resolvedConfig, mergeDeep(resolvedConfig, next));
119
173
  logger.info(`设置已热更新(${ns}): ${Object.keys(next).length} 项`);
174
+ void tryStartGateway(); // ← 若刚从"无凭据"变成"有凭据",自动把网关拉起来
120
175
  });
121
176
  }
122
177
  catch { /* 事件系统不可用 → 退化为重启生效 */ }
@@ -144,16 +199,45 @@ export async function apply(ctx, config) {
144
199
  const declared = Array.isArray(bridgeMod.inject) ? bridgeMod.inject : [];
145
200
  const required = declared.filter((s) => s !== 'agentPresets');
146
201
  const deps = required.length > 0 ? required : ['settings', 'webServer'];
147
- ctx.inject(deps, (serverCtx) => {
202
+ // ⚠️⚠️ 2026-09-28 关键修复:**不要用 ctx.inject 等依赖**。
203
+ // 原因:cordis 的 ctx.inject(deps, cb) 会把 cb 挂成一个【子 fiber】并让【当前插件】
204
+ // 一起等待这些服务 —— 一旦宿主没有提供(或尚未注册)settings / webServer,
205
+ // 插件自身就永久 pending,宿主判定 "1 required plugin did not activate",
206
+ // 连带【整个 Web UI 都不渲染】(只剩一句错误 + 一个 dock 球)。
207
+ // 实测:全新 profile 装本插件必然复现;老环境因为服务早就绪所以看不出来。
208
+ // 现在改为:服务已就绪就立即装桥;没就绪则监听服务注册事件,**绝不阻塞插件自身激活**。
209
+ const mountBridge = (c) => {
148
210
  try {
149
- bridgeMod.apply(serverCtx);
150
- logger.info(`[im-qqbot] settings host 桥已 apply(ctx.inject ${deps.join('+')})`);
211
+ bridgeMod.apply(c);
212
+ logger.info(`[im-qqbot] settings host 桥已 apply(deps=${deps.join('+')})`);
151
213
  }
152
214
  catch (err) {
153
215
  logger.warn?.(`im-qqbot: settings host 桥 apply 异常: ${err instanceof Error ? err.message : String(err)}`);
154
216
  }
155
- });
156
- logger.info(`[im-qqbot] settings host 桥已装载(ctx.inject 姿势, deps=${deps.join('+')})`);
217
+ };
218
+ // ⚠️⚠️ 2026-09-28 最终方案(两次踩坑后的结论):
219
+ // · 直接 mountBridge(ctx) 不行 —— 桥的 apply 内部要 `ctx.webServer`,
220
+ // 而没经过 inject 的 ctx 访问它会抛 "cannot get property \"webServer\" without inject"。
221
+ // · 用 ctx.inject(deps, cb) 又不行 —— 它会让**插件自身 fiber 一起等依赖**,
222
+ // 宿主在服务就绪前就判 "1 required plugin did not activate",
223
+ // 【整个 Web UI 都不渲染】(只剩一句错误 + 一个 dock 球)。
224
+ // → 解法:**apply 先正常返回(插件立即激活成功)**,
225
+ // 再用 setTimeout 延后到宿主服务注册完成,此时才做 ctx.inject(不再影响已完成的激活)。
226
+ const doInjectLater = () => {
227
+ try {
228
+ ctx.inject(deps, (serverCtx) => mountBridge(serverCtx));
229
+ }
230
+ catch (err) {
231
+ }
232
+ };
233
+ // 用较短的定时器即可:宿主通常同一轮就把服务注册完
234
+ try {
235
+ setTimeout(doInjectLater, 1500);
236
+ }
237
+ catch {
238
+ doInjectLater();
239
+ }
240
+ logger.info(`[im-qqbot] settings host 桥已排程(延后 inject, deps=${deps.join('+')})`);
157
241
  }
158
242
  else {
159
243
  logger.warn('[im-qqbot] settings-host.js 缺少 apply, 桥跳过');
@@ -178,7 +262,13 @@ export async function apply(ctx, config) {
178
262
  catch (err) {
179
263
  logger.warn(`[im-qqbot] 小传上下文注册失败: ${err instanceof Error ? err.message : String(err)}`);
180
264
  }
181
- await bootstrapGateway(ctx, agents, resolvedConfig, logger);
265
+ // 没有凭据 → 保留设置面板即可,不启动网关(不连 QQ、不注册入站)
266
+ // 此时已注册 settings-changed 监听:用户填好凭据保存后会自动启动网关(无需重启)。
267
+ if (!hasCred) {
268
+ logger.info(`实例 ${ns}: 暂无凭据 —— 设置面板已就绪,填好凭据保存后会自动连接(无需重启)。`);
269
+ return;
270
+ }
271
+ await tryStartGateway();
182
272
  }
183
273
  /** 把 settings 用户层合并进 live 运行时配置(原地字段替换; schema 解析值含默认, 可整体覆盖) */
184
274
  async function installLiveSettings(ctx, live, logger, nsOverride) {