dsh-qq-link 1.4.7 → 1.4.8

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,28 @@
1
+ ## 1.4.8 — 2026-11-01
2
+
3
+ **QQ 转进来的消息被判成「你在 DSH 窗口里自己打的」—— 「只回 QQ 唤醒」开着时,QQ 那边永远收不到回复**
4
+
5
+ - 🐞 现象:开着「只回 QQ 唤醒」,从 QQ 发一句,DSH 里正常回了(页面上看得见),**QQ 那边什么都没有**;
6
+ 日志写的是「这一轮是你在 DSH 窗口里自己打的」。1.4.7 想修的正是这个,但那版**没生效**。
7
+ - 根因:读错了字段。`user/message` 这种事件的 `data` **就是消息本体**(`{ id, role, content, source }`),
8
+ `source` 在 `event.data.source`;只有 `assistant/message` 才包一层 `data.message`。
9
+ 代码读的是 `event.data.message.source` —— 永远是 `undefined`,于是 `kind` 永远空、每一轮都当成"你手打的"。
10
+ - ✅ 两种形状都试(`data.source || data.message.source`),哪天真换了也不会立刻断。
11
+ - ⚠ 测试也跟着错了:用例里把假事件造成 `data.message.source`,所以一直"通过"。
12
+ 现在用例改成真实形状,这条才算真的被盯住。
13
+
14
+ **群门槛判过「不接」之后,下一轮正常的话会被整轮吞掉**
15
+
16
+ - 🐞 现象:开着群里那个「回复门槛」,某轮判定「这条不是在跟我说话」之后,
17
+ **紧接着下一轮它好好回了一句,QQ 那边却什么都没出现**,日志里只有一句「这一轮按判定跳过发送」。
18
+ - 根因:门槛判「不接」时会挂一个「这轮别往 QQ 发」的静默标记,靠 `turn/end` 清掉。
19
+ 但清理那段写在「这一轮真的说完话」的分支里,下面两种轮次**根本走不到**:
20
+ · 开了「真停轮」→ 这一轮是被 cancel 停的,`turn/end` 是 **aborted**;
21
+ · 这一轮一个字都没说出来(没有内容)。
22
+ 标记于是漏到下一轮,下一轮正常结束时被当成「判定过的不接」整轮跳过。
23
+ - ✅ 现在静默标记**只对触发它的那一轮有效**:`turn/end` 一到就作废(不再看这一轮是怎么结束的);
24
+ 另外 `turn/start` 也会清一次,兜住那些**没有 turn/end**的轮次(宿主被杀、会话被换掉、轮次被丢掉)。
25
+
1
26
  ## 1.4.7 — 2026-11-01
2
27
 
3
28
  **「只回 QQ 唤醒」开着时,QQ 叫醒的那一轮也发不出去(1.4.4 带进来的)**
package/lib/host.js CHANGED
@@ -1406,6 +1406,15 @@ export function apply(ctx) {
1406
1406
  const 上一轮 = 上一轮们.get(键) || 空轮()
1407
1407
  const 记上一轮 = (v) => 上一轮们.set(键, v)
1408
1408
 
1409
+ // ⭐ 1.4.8(issue #27):新一轮一开,上一轮的「静默标记」就必须作废。
1410
+ // 正常路径下面 turn/end 会清;这里兜住那些**没发 turn/end 就没了**的轮次
1411
+ // (宿主被杀、会话被换掉、插件在这一轮里被卸载重载),
1412
+ // 否则标记会一直挂着,把这个会话接下来的回复全吞掉(表现是「它明明回了,群里却没有」)。
1413
+ if (event && event.type === 'turn/start') {
1414
+ 静默轮.delete(键)
1415
+ return
1416
+ }
1417
+
1409
1418
  if (event && event.type === 'assistant/message') {
1410
1419
  const d = event.data || {}
1411
1420
  if (d.interrupted) {
@@ -1423,7 +1432,14 @@ export function apply(ctx) {
1423
1432
  // ⭐ 1.0.1:她自己在这间会话里打了字(source 不是 qq)→ 尾巴立刻清掉,
1424
1433
  // 不然「QQ 叫醒一次」会把后面她自己聊的几轮也推去手机(老坑)。
1425
1434
  if (event && event.type === 'user/message') {
1426
- const 源 = event.data && event.data.message && event.data.message.source
1435
+ // ⚠⚠⚠ 1.4.8:`source` 在**事件自己的 data 上** —— user/message 的 data 就是消息本体
1436
+ // (`{ id, role, content, source }`,实测她本机会话日志的 7388 条 user/message 全是这个形状)。
1437
+ // 只有 assistant/message 那种才包一层 `data.message`。
1438
+ // 1.4.4~1.4.7 读的是 `event.data.message.source` —— 永远是 undefined ⇒ `kind` 永远空
1439
+ // ⇒ 每一轮都被判成「你在 DSH 窗口里自己打的」⇒「只回 QQ 唤醒」开着时,
1440
+ // **QQ 转进来的消息回不出去**(日志写「这一轮是你在 DSH 窗口里自己打的」,明明是从 QQ 来的)。
1441
+ // 两种形状都试,哪天真换了也不会立刻断。
1442
+ const 源 = (event.data && (event.data.source || (event.data.message && event.data.message.source))) || null
1427
1443
  const 类 = String((源 && 源.kind) || '')
1428
1444
  if (!类 || 类 === 'user') 尾巴.delete(键)
1429
1445
  // ⭐ 1.4.4:把「这一轮是谁起的头」记下来 —— turn/end 判定要不要推到手机时用
@@ -1443,6 +1459,13 @@ export function apply(ctx) {
1443
1459
  if (event && event.type === 'turn/end') {
1444
1460
  const d = event.data || {}
1445
1461
  const 完成 = !!(d.reason && d.reason.kind === 'completed')
1462
+ // ⭐ 1.4.8(issue #27):静默标记只对**触发它的那一轮**有效,这一轮一结束就作废。
1463
+ // 以前只在「这一轮真的说完了话」那条路上清,下面两种情况都清不掉:
1464
+ // · 门槛判了不接 → 这一轮是被 cancel 停的,turn/end 是 aborted(完成 = false)
1465
+ // · 这一轮一个字都没说出来(没有内容)
1466
+ // 标记于是漏到下一轮,下一轮 completed 时被当成「判定过的不接」整轮跳过。
1467
+ const 这轮静默 = 静默轮.has(键)
1468
+ if (这轮静默) 静默轮.delete(键)
1446
1469
  const 有东西 = 上一轮.文本 || (上一轮.图们 && 上一轮.图们.length)
1447
1470
  // ⚠ 去重的键必须是「会话 + 轮号」:只按轮号的话,换过目标会话就会静默漏发
1448
1471
  const 轮标 = 键 + ':' + String(d.turn)
@@ -1472,8 +1495,7 @@ export function apply(ctx) {
1472
1495
  本轮有QQ.delete(键)
1473
1496
  const 是本地手打 = !这轮来过QQ && 有来源 && (来源 === 'user' || 来源 === '')
1474
1497
  // 门槛判了「不接」→ 这一轮一个字都不往外发(但会话里照常留着)
1475
- if (静默轮.has(键)) {
1476
- 静默轮.delete(键)
1498
+ if (这轮静默) {
1477
1499
  记账('回复门槛:这一轮按判定跳过发送(' + 短id(键) + ')')
1478
1500
  } else {
1479
1501
  void 落地一发(配置, 上一轮.文本, 上一轮.图们, 方向, 是QQ唤醒, 是本地手打)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-qq-link",
3
- "version": "1.4.7",
3
+ "version": "1.4.8",
4
4
  "description": "通到 QQ:DSH 里的 AI 伙伴和 QQ 双向互通 —— 它说完话自动发到 QQ,你在 QQ 里说的话自动转进 DSH。走 OneBot 11 HTTP(SnowLuma / NapCat 都行)。",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "engines": {