@fengye_nobug/dsh-qq-bot 0.2.10 → 0.2.11

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,5 +1,9 @@
1
1
  # 更新日志
2
2
 
3
+ ## v0.2.11 — 2026-11-02
4
+
5
+ - **轮数上限会用到人设里的压缩**:`maxRecentTurns`(默认 40,填 0 不限制)在 QQ 叫醒的一轮结束并空闲后,压缩更早的内容。标准人设把压缩放在隔离域里,插件所在的宿主上下文看不见,所以 0.2.9 这次调用直接跳过,会话会一直变长,日志里也没有压缩记录。现在先向这条会话的人设要压缩服务,没有再看会话作用域和宿主。已经超限却找不到,或这次压缩没有改动会话时,记一条警告。仍然等这一轮空闲,并带上未取消的取消信号。不必在宿主配置里再打开压缩。哪一层都没有压缩时不影响收发。
6
+
3
7
  ## v0.2.10 — 2026-11-02
4
8
 
5
9
  - **用法说明和发消息工具不再用具体表情名当例子**:注入的用法说明,以及 `qq_bridge_send` 的工具说明和参数说明,不再把笑哭、挥手写成推荐写法。模型之前会抓住这两个例子反复使用。现在只说明半角方括号里写 `[表情名]` 或 `[表情数字]`。对得上的表情名仍会变成 QQ 系统表情,名字表还在 `faces.js`。认不出的方括号仍留在文字里。
package/DESIGN.md CHANGED
@@ -603,7 +603,7 @@ QQ 的“拍一拍”在 OneBot 里走的是 **notice 事件**,不是消息段
603
603
  |---|---|---|
604
604
  | `targetSessionId` | `''` | 接到哪个会话。`single` 时**空 = 不接到任何会话**。`perGroup` 时可以留空(新建会话用宿主当前默认模型);填了则新建会话沿用该会话的模型 |
605
605
  | `sessionScope` | `single` | 会话怎么分。`single` = 所有群共用一个会话;`perGroup` = 每个群、每个私聊各记各的,映射写在状态文件的 `sessionByDest`(键为 `group:<群号>` / `private:<QQ号>`)。新建会话带工作目录和「QQ群 / QQ私聊」标题,见 [README 的功能](./README.md#功能) |
606
- | `maxRecentTurns` | `40` | 每个会话最多留几轮。超出后,等这一轮回到空闲,再调用宿主 `compaction.compactNow(agent, signal)` 尽量压缩。`signal` 是未取消的 AbortSignal,不传命令 id。有投影时看投影轮数。压完没变短就停。`0` = 不限制。compaction 不在 inject 里,宿主没有这项服务时跳过 |
606
+ | `maxRecentTurns` | `40` | 每个会话最多留几轮。超出后,等这一轮回到空闲,再调用 `compaction.compactNow(agent, signal)` 尽量压缩。压缩服务先从这条会话的人设读取(`agentPresets.serviceFor(agent, 'compaction')`,标准人设把它放在隔离域里),没有再看会话作用域,最后看宿主。`signal` 是未取消的 AbortSignal,不传命令 id。有投影时看投影轮数。压完没变短就停。返回空也停,并记一条警告。`0` = 不限制。compaction 不在 inject 里。已经超限、三层都没有这项服务时记一条警告并跳过,收发不受影响 |
607
607
  | `nicknames` | `['244','小助手']` | 听到这些名字就回应 |
608
608
  | `wakeProbability` | `0.05` | 没点名时的回应概率 |
609
609
  | `whitelist` / `groupWhitelist` | `[]` | 哪些私聊可以叫醒 / 哪些群可以叫醒(**留空 = 谁都不能叫醒**)。群白名单在卡片里按群号填;连上且查到群名时,群号旁边显示当前群名和头像,查不到或没连上只显示群号,不影响保存 |
package/README.md CHANGED
@@ -62,7 +62,7 @@ DSH 0.2 的卡片把值写进 `$DSH_HOME/qq-bridge-settings.json`。这份文件
62
62
  |---|---|---|
63
63
  | `targetSessionId` | 空 | `single` 时接到哪个会话。留空 = 不接到任何会话 |
64
64
  | `sessionScope` | `single` | `perGroup` 时每个群、每个私聊各一条会话 |
65
- | `maxRecentTurns` | `40` | 每个会话最多留几轮。QQ 一轮结束后,更早的内容会尽量压缩掉。`0` = 不限制 |
65
+ | `maxRecentTurns` | `40` | 每个会话最多留几轮。QQ 一轮结束并空闲后,更早的内容会尽量压缩掉。用的是这条会话自己的压缩(标准人设里就有)。人设没有时再看会话作用域和宿主。已经超限却找不到压缩,或这次压缩没有改动会话时,记一条警告。`0` = 不限制 |
66
66
  | `nicknames` | `244`、`小助手` | 听到这些名字就叫醒 |
67
67
  | `wakeProbability` | `0.05` | 没点名时的叫醒概率 |
68
68
  | `whitelist` | `[]` | 哪些私聊可以叫醒。留空 = 都不叫醒 |
package/index.js CHANGED
@@ -1899,16 +1899,48 @@ export function apply(ctx, config) {
1899
1899
  }
1900
1900
  }
1901
1901
 
1902
+ /**
1903
+ * 读一份能调用的 compaction。
1904
+ *
1905
+ * 标准人设把 `dsh-compaction-basic` 放在 `isolate: { compaction: true }` 里。
1906
+ * 这份服务只在人设那一组的 realm 里,宿主 `ctx.get('compaction')` 和 `agent.ctx.get`
1907
+ * 都按自己的 realm 查找,读到的是空。`/compact` 能用,是因为 `dsh-command-compact`
1908
+ * 和压缩后端在同一组里,调用的是这一份 `compactNow`。
1909
+ * 宿主侧对应的读法是 `agentPresets.serviceFor(agent, 'compaction')`。
1910
+ *
1911
+ * 人设注册表没有这个方法,或这份人设没挂压缩时,再看会话作用域,最后看宿主。
1912
+ * 宿主自己提供 compaction 的布局因此仍然可用。某一层抛错就换下一层。
1913
+ * @returns {{ service: any, detail: string }}
1914
+ */
1915
+ function compactionFor(agent) {
1916
+ let detail = ''
1917
+ const take = (read) => {
1918
+ try {
1919
+ const value = read()
1920
+ if (value && typeof value.compactNow === 'function') return value
1921
+ } catch (error) {
1922
+ if (!detail) detail = error?.message ?? String(error)
1923
+ }
1924
+ return undefined
1925
+ }
1926
+ const service =
1927
+ take(() => ctx.get?.('agentPresets')?.serviceFor?.(agent, 'compaction')) ??
1928
+ take(() => agent?.ctx?.get?.('compaction')) ??
1929
+ take(() => (typeof ctx.get === 'function' ? ctx.get('compaction') : undefined))
1930
+ return { service, detail }
1931
+ }
1932
+
1902
1933
  /**
1903
1934
  * QQ 驱动的一轮结束后,把该会话压到 maxRecentTurns。
1904
- * 0 或宿主没有 compaction 时什么都不做。压缩失败只记日志,不挡出站。
1935
+ * 0 时什么都不做。压缩失败只记日志,不挡出站。
1905
1936
  *
1906
- * compaction 不放进 inject。它是可选服务:宿主没装压缩时,桥仍要收发 QQ。
1907
- * `ctx.get` 不要求 inject;没有这项服务时返回 undefined。
1937
+ * compaction 不放进 inject。它是可选服务:哪一层都没有时,桥仍要收发 QQ。
1938
+ * 会话已经超过上限、却找不到 compactNow 时记一条 warn,避免静默不压缩。
1908
1939
  *
1909
- * 宿主 `compactNow(agent, signal, sourceCommandId?)` 会立刻 `signal.throwIfAborted()`。
1910
- * 官方 `/compact` 传的是这次命令的 signal 和 commandId。这里没有命令,传一个未中止的
1911
- * AbortSignal,不传 sourceCommandId。少传 signal 会在写入 compaction 事件之前抛错。
1940
+ * `compactNow(agent, signal, sourceCommandId?)` 会立刻 `signal.throwIfAborted()`。
1941
+ * 这里没有命令,传一个未中止的 AbortSignal,不传 sourceCommandId。
1942
+ * 少传 signal 会在写入 compaction 事件之前抛错。
1943
+ * 返回 null 表示没有可压的区间:记 warn 并停,不再空转。
1912
1944
  *
1913
1945
  * `turn/end` 写入日志时,驱动还占着 running。这时调用会在 `runMaintenance` 上失败,
1914
1946
  * 同样写不出 compaction 事件。先等 `whenIdle`。旧宿主没有这个方法时让出一个宏任务
@@ -1917,8 +1949,6 @@ export function apply(ctx, config) {
1917
1949
  async function maybeTrimSession(agent) {
1918
1950
  const max = readSettings().maxRecentTurns
1919
1951
  if (!max || max <= 0 || !agent) return
1920
- const compaction = ctx.get('compaction')
1921
- if (!compaction || typeof compaction.compactNow !== 'function') return
1922
1952
  try {
1923
1953
  if (typeof agent.whenIdle === 'function') await agent.whenIdle()
1924
1954
  else await new Promise((resolve) => setTimeout(resolve, 0))
@@ -1946,19 +1976,40 @@ export function apply(ctx, config) {
1946
1976
  return 0
1947
1977
  }
1948
1978
  }
1979
+ const sessionId = () => agent.session?.id ?? agent.id
1980
+ let compaction
1981
+ let lookupDetail = ''
1982
+ let resolved = false
1983
+ const engine = () => {
1984
+ if (!resolved) {
1985
+ const found = compactionFor(agent)
1986
+ compaction = found.service
1987
+ lookupDetail = found.detail
1988
+ resolved = true
1989
+ }
1990
+ return compaction
1991
+ }
1949
1992
  let previous = Infinity
1950
1993
  for (let i = 0; i < 6; i++) {
1951
1994
  const turns = countTurns()
1952
1995
  if (!turns || turns <= max) return
1953
1996
  if (turns >= previous) {
1954
- logger.info(`session ${agent.session?.id ?? agent.id} compact 后轮次没有减少(${turns}),停止`)
1997
+ logger.info(`session ${sessionId()} compact 后轮次没有减少(${turns}),停止`)
1998
+ return
1999
+ }
2000
+ if (!engine()) {
2001
+ const why = lookupDetail ? `(${lookupDetail})` : ''
2002
+ logger.warn(`compact trim: 会话 ${sessionId()} 约 ${turns} 轮,超过上限 ${max},但没有可用的压缩服务${why}`)
1955
2003
  return
1956
2004
  }
1957
2005
  previous = turns
1958
2006
  try {
1959
2007
  const result = await compaction.compactNow(agent, new AbortController().signal)
1960
- if (!result) return
1961
- logger.info(`session ${agent.session?.id ?? agent.id} trimmed via compact (turns~${turns} > ${max})`)
2008
+ if (result == null) {
2009
+ logger.warn(`compact trim: 会话 ${sessionId()} 的 compactNow 返回空(当时约 ${turns} 轮,上限 ${max})`)
2010
+ return
2011
+ }
2012
+ logger.info(`session ${sessionId()} trimmed via compact (turns~${turns} > ${max})`)
1962
2013
  } catch (error) {
1963
2014
  logger.warn(`compact trim failed: ${error?.message ?? error}`)
1964
2015
  return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fengye_nobug/dsh-qq-bot",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "description": "把 DSH 会话接到 QQ(OneBot/NapCat)的插件。白名单默认全关;被 @、叫到昵称或被戳一定叫醒,其余消息按可调概率抽样。Fork 自 sj244/dsh-qq-bridge(MIT)。",