@tencent-connect/openclaw-qqbot 1.7.2 → 2.0.0

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 (291) hide show
  1. package/README.md +25 -8
  2. package/README.zh.md +24 -8
  3. package/bin/qqbot-cli.js +5 -2
  4. package/dist/index.cjs +15549 -0
  5. package/dist/index.cjs.map +1 -0
  6. package/dist/index.d.cts +890 -0
  7. package/index.ts +56 -19
  8. package/openclaw.plugin.json +19 -3
  9. package/package.json +14 -23
  10. package/preload.cjs +17 -14
  11. package/scripts/link-sdk-core.cjs +133 -50
  12. package/scripts/upgrade-via-npm.sh +42 -11
  13. package/scripts/upgrade-via-source.sh +4 -4
  14. package/skills/qqbot-channel/SKILL.md +34 -12
  15. package/src/adapter/contract.ts +69 -0
  16. package/src/adapter/gateway.ts +61 -0
  17. package/src/adapter/index.ts +15 -0
  18. package/src/adapter/lint.ts +144 -0
  19. package/src/adapter/media.ts +112 -0
  20. package/src/adapter/pairing.ts +99 -0
  21. package/src/adapter/resolve.ts +333 -0
  22. package/src/adapter/setup.ts +55 -0
  23. package/src/adapter/webhook.ts +248 -0
  24. package/src/adapter/workspace.ts +29 -0
  25. package/src/bot-instance.ts +60 -0
  26. package/src/channel.ts +202 -413
  27. package/src/commands/bot-approve.ts +143 -0
  28. package/src/commands/bot-clear-storage.ts +114 -0
  29. package/src/commands/bot-group-always.ts +62 -0
  30. package/src/commands/bot-help.ts +40 -0
  31. package/src/commands/bot-logs.ts +248 -0
  32. package/src/commands/bot-me.ts +18 -0
  33. package/src/commands/bot-pairing.ts +57 -0
  34. package/src/commands/bot-ping.ts +33 -0
  35. package/src/commands/bot-streaming.ts +55 -0
  36. package/src/commands/bot-upgrade.ts +56 -0
  37. package/src/commands/bot-version.ts +41 -0
  38. package/src/commands/config-util.ts +96 -0
  39. package/src/commands/index.ts +49 -0
  40. package/src/config.ts +46 -19
  41. package/src/dispatch/body-assembler.ts +356 -0
  42. package/src/dispatch/ctx-builder.ts +110 -0
  43. package/src/dispatch/dispatch.ts +352 -0
  44. package/src/dispatch/envelope-builder.ts +112 -0
  45. package/src/dispatch/index.ts +2 -0
  46. package/src/{approval-handler.ts → features/approval-handler.ts} +40 -94
  47. package/src/features/approval-utils.ts +41 -0
  48. package/src/features/credential-backup.ts +82 -0
  49. package/src/features/history-store.ts +19 -0
  50. package/src/features/msgid-cache.ts +55 -0
  51. package/src/features/onboarding.ts +38 -0
  52. package/src/{proactive.ts → features/proactive.ts} +68 -49
  53. package/src/features/ref-index-store.ts +282 -0
  54. package/src/{update-checker.ts → features/update-checker.ts} +15 -35
  55. package/src/gateway/event-handlers.ts +241 -0
  56. package/src/gateway/index.ts +3 -0
  57. package/src/gateway/lifecycle.ts +221 -0
  58. package/src/gateway/middleware-setup.ts +139 -0
  59. package/src/gateway/qqbot-gateway.ts +342 -0
  60. package/src/middleware/access-control.ts +130 -0
  61. package/src/middleware/attachment.ts +314 -0
  62. package/src/middleware/policy-injector.ts +66 -0
  63. package/src/openclaw-plugin-sdk.d.ts +81 -10
  64. package/src/outbound/cron-scheduler.ts +130 -0
  65. package/src/outbound/debounce.ts +102 -0
  66. package/src/outbound/deliver-pipeline.ts +235 -0
  67. package/src/outbound/image-size.ts +123 -0
  68. package/src/outbound/index.ts +3 -0
  69. package/src/outbound/local-file-router.ts +145 -0
  70. package/src/outbound/media-send.ts +377 -0
  71. package/src/outbound/outbound-service.ts +222 -0
  72. package/src/outbound/reply-limiter.ts +128 -0
  73. package/src/outbound/sanitize.ts +32 -0
  74. package/src/outbound/streaming-controller.ts +228 -0
  75. package/src/outbound/target.ts +63 -0
  76. package/src/outbound/tts-provider.ts +84 -0
  77. package/src/request-context.ts +9 -8
  78. package/src/runtime.ts +31 -6
  79. package/src/setup/account-key.ts +41 -0
  80. package/src/setup/finalize.ts +110 -0
  81. package/src/setup/login.ts +197 -0
  82. package/src/setup/surface.ts +40 -0
  83. package/src/tools/platform.ts +149 -0
  84. package/src/tools/remind.ts +2 -0
  85. package/src/types-augment.d.ts +54 -0
  86. package/src/types.ts +51 -9
  87. package/src/utils/mention.ts +52 -0
  88. package/src/utils/pkg-version.ts +84 -47
  89. package/src/utils/platform.ts +31 -7
  90. package/src/utils/plugin-logger.ts +104 -0
  91. package/src/utils/ssrf-guard.ts +32 -2
  92. package/src/utils/stt.ts +137 -0
  93. package/src/utils/voice-text.ts +61 -0
  94. package/tsconfig.json +2 -1
  95. package/tsup.config.ts +57 -0
  96. package/dist/index.d.ts +0 -18
  97. package/dist/index.js +0 -27
  98. package/dist/src/admin-resolver.d.ts +0 -33
  99. package/dist/src/admin-resolver.js +0 -157
  100. package/dist/src/api.d.ts +0 -309
  101. package/dist/src/api.js +0 -923
  102. package/dist/src/approval-handler.d.ts +0 -47
  103. package/dist/src/approval-handler.js +0 -372
  104. package/dist/src/channel.d.ts +0 -29
  105. package/dist/src/channel.js +0 -528
  106. package/dist/src/config.d.ts +0 -60
  107. package/dist/src/config.js +0 -288
  108. package/dist/src/credential-backup.d.ts +0 -31
  109. package/dist/src/credential-backup.js +0 -66
  110. package/dist/src/deliver-debounce.d.ts +0 -74
  111. package/dist/src/deliver-debounce.js +0 -174
  112. package/dist/src/gateway.d.ts +0 -18
  113. package/dist/src/gateway.js +0 -2025
  114. package/dist/src/group-history.d.ts +0 -136
  115. package/dist/src/group-history.js +0 -226
  116. package/dist/src/image-server.d.ts +0 -87
  117. package/dist/src/image-server.js +0 -570
  118. package/dist/src/inbound-attachments.d.ts +0 -60
  119. package/dist/src/inbound-attachments.js +0 -248
  120. package/dist/src/known-users.d.ts +0 -100
  121. package/dist/src/known-users.js +0 -263
  122. package/dist/src/message-gating.d.ts +0 -53
  123. package/dist/src/message-gating.js +0 -107
  124. package/dist/src/message-queue.d.ts +0 -91
  125. package/dist/src/message-queue.js +0 -257
  126. package/dist/src/onboarding.d.ts +0 -10
  127. package/dist/src/onboarding.js +0 -203
  128. package/dist/src/outbound-deliver.d.ts +0 -48
  129. package/dist/src/outbound-deliver.js +0 -392
  130. package/dist/src/outbound.d.ts +0 -220
  131. package/dist/src/outbound.js +0 -975
  132. package/dist/src/proactive.d.ts +0 -170
  133. package/dist/src/proactive.js +0 -399
  134. package/dist/src/ref-index-store.d.ts +0 -101
  135. package/dist/src/ref-index-store.js +0 -298
  136. package/dist/src/reply-dispatcher.d.ts +0 -35
  137. package/dist/src/reply-dispatcher.js +0 -311
  138. package/dist/src/request-context.d.ts +0 -25
  139. package/dist/src/request-context.js +0 -37
  140. package/dist/src/runtime.d.ts +0 -3
  141. package/dist/src/runtime.js +0 -13
  142. package/dist/src/session-store.d.ts +0 -52
  143. package/dist/src/session-store.js +0 -254
  144. package/dist/src/slash-commands.d.ts +0 -82
  145. package/dist/src/slash-commands.js +0 -2233
  146. package/dist/src/startup-greeting.d.ts +0 -30
  147. package/dist/src/startup-greeting.js +0 -97
  148. package/dist/src/streaming.d.ts +0 -247
  149. package/dist/src/streaming.js +0 -899
  150. package/dist/src/stt.d.ts +0 -21
  151. package/dist/src/stt.js +0 -70
  152. package/dist/src/tools/channel.d.ts +0 -16
  153. package/dist/src/tools/channel.js +0 -231
  154. package/dist/src/tools/remind.d.ts +0 -2
  155. package/dist/src/tools/remind.js +0 -255
  156. package/dist/src/transport/index.d.ts +0 -10
  157. package/dist/src/transport/index.js +0 -9
  158. package/dist/src/transport/webhook-transport.d.ts +0 -67
  159. package/dist/src/transport/webhook-transport.js +0 -245
  160. package/dist/src/transport/webhook-verify.d.ts +0 -48
  161. package/dist/src/transport/webhook-verify.js +0 -98
  162. package/dist/src/types.d.ts +0 -462
  163. package/dist/src/types.js +0 -22
  164. package/dist/src/typing-keepalive.d.ts +0 -27
  165. package/dist/src/typing-keepalive.js +0 -64
  166. package/dist/src/update-checker.d.ts +0 -36
  167. package/dist/src/update-checker.js +0 -171
  168. package/dist/src/utils/audio-convert.d.ts +0 -98
  169. package/dist/src/utils/audio-convert.js +0 -783
  170. package/dist/src/utils/chunked-upload.d.ts +0 -68
  171. package/dist/src/utils/chunked-upload.js +0 -341
  172. package/dist/src/utils/file-utils.d.ts +0 -61
  173. package/dist/src/utils/file-utils.js +0 -172
  174. package/dist/src/utils/image-size.d.ts +0 -51
  175. package/dist/src/utils/image-size.js +0 -234
  176. package/dist/src/utils/media-send.d.ts +0 -158
  177. package/dist/src/utils/media-send.js +0 -502
  178. package/dist/src/utils/media-tags.d.ts +0 -14
  179. package/dist/src/utils/media-tags.js +0 -165
  180. package/dist/src/utils/payload.d.ts +0 -112
  181. package/dist/src/utils/payload.js +0 -186
  182. package/dist/src/utils/pkg-version.d.ts +0 -5
  183. package/dist/src/utils/pkg-version.js +0 -61
  184. package/dist/src/utils/platform.d.ts +0 -137
  185. package/dist/src/utils/platform.js +0 -390
  186. package/dist/src/utils/ssrf-guard.d.ts +0 -25
  187. package/dist/src/utils/ssrf-guard.js +0 -91
  188. package/dist/src/utils/text-parsing.d.ts +0 -31
  189. package/dist/src/utils/text-parsing.js +0 -74
  190. package/dist/src/utils/upload-cache.d.ts +0 -34
  191. package/dist/src/utils/upload-cache.js +0 -93
  192. package/node_modules/@eshaz/web-worker/LICENSE +0 -201
  193. package/node_modules/@eshaz/web-worker/README.md +0 -134
  194. package/node_modules/@eshaz/web-worker/browser.js +0 -17
  195. package/node_modules/@eshaz/web-worker/cjs/browser.js +0 -16
  196. package/node_modules/@eshaz/web-worker/cjs/node.js +0 -219
  197. package/node_modules/@eshaz/web-worker/index.d.ts +0 -4
  198. package/node_modules/@eshaz/web-worker/node.js +0 -223
  199. package/node_modules/@eshaz/web-worker/package.json +0 -54
  200. package/node_modules/@wasm-audio-decoders/common/index.js +0 -5
  201. package/node_modules/@wasm-audio-decoders/common/package.json +0 -36
  202. package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderCommon.js +0 -231
  203. package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderWorker.js +0 -129
  204. package/node_modules/@wasm-audio-decoders/common/src/puff/README +0 -67
  205. package/node_modules/@wasm-audio-decoders/common/src/puff/build_puff.js +0 -31
  206. package/node_modules/@wasm-audio-decoders/common/src/puff/puff.c +0 -863
  207. package/node_modules/@wasm-audio-decoders/common/src/puff/puff.h +0 -35
  208. package/node_modules/@wasm-audio-decoders/common/src/utilities.js +0 -3
  209. package/node_modules/@wasm-audio-decoders/common/types.d.ts +0 -7
  210. package/node_modules/mpg123-decoder/README.md +0 -265
  211. package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js +0 -185
  212. package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js.map +0 -1
  213. package/node_modules/mpg123-decoder/index.js +0 -8
  214. package/node_modules/mpg123-decoder/package.json +0 -58
  215. package/node_modules/mpg123-decoder/src/EmscriptenWasm.js +0 -464
  216. package/node_modules/mpg123-decoder/src/MPEGDecoder.js +0 -200
  217. package/node_modules/mpg123-decoder/src/MPEGDecoderWebWorker.js +0 -21
  218. package/node_modules/mpg123-decoder/types.d.ts +0 -30
  219. package/node_modules/silk-wasm/LICENSE +0 -21
  220. package/node_modules/silk-wasm/README.md +0 -85
  221. package/node_modules/silk-wasm/lib/index.cjs +0 -16
  222. package/node_modules/silk-wasm/lib/index.d.ts +0 -70
  223. package/node_modules/silk-wasm/lib/index.mjs +0 -16
  224. package/node_modules/silk-wasm/lib/silk.wasm +0 -0
  225. package/node_modules/silk-wasm/lib/utils.d.ts +0 -4
  226. package/node_modules/silk-wasm/package.json +0 -39
  227. package/node_modules/simple-yenc/.github/FUNDING.yml +0 -1
  228. package/node_modules/simple-yenc/.prettierignore +0 -1
  229. package/node_modules/simple-yenc/LICENSE +0 -7
  230. package/node_modules/simple-yenc/README.md +0 -163
  231. package/node_modules/simple-yenc/dist/esm.js +0 -1
  232. package/node_modules/simple-yenc/dist/index.js +0 -1
  233. package/node_modules/simple-yenc/package.json +0 -50
  234. package/node_modules/simple-yenc/rollup.config.js +0 -27
  235. package/node_modules/simple-yenc/src/simple-yenc.js +0 -302
  236. package/node_modules/ws/LICENSE +0 -20
  237. package/node_modules/ws/README.md +0 -548
  238. package/node_modules/ws/browser.js +0 -8
  239. package/node_modules/ws/index.js +0 -13
  240. package/node_modules/ws/lib/buffer-util.js +0 -131
  241. package/node_modules/ws/lib/constants.js +0 -19
  242. package/node_modules/ws/lib/event-target.js +0 -292
  243. package/node_modules/ws/lib/extension.js +0 -203
  244. package/node_modules/ws/lib/limiter.js +0 -55
  245. package/node_modules/ws/lib/permessage-deflate.js +0 -528
  246. package/node_modules/ws/lib/receiver.js +0 -706
  247. package/node_modules/ws/lib/sender.js +0 -602
  248. package/node_modules/ws/lib/stream.js +0 -161
  249. package/node_modules/ws/lib/subprotocol.js +0 -62
  250. package/node_modules/ws/lib/validation.js +0 -152
  251. package/node_modules/ws/lib/websocket-server.js +0 -554
  252. package/node_modules/ws/lib/websocket.js +0 -1393
  253. package/node_modules/ws/package.json +0 -69
  254. package/node_modules/ws/wrapper.mjs +0 -8
  255. package/scripts/postinstall-link-sdk.js +0 -170
  256. package/skills/qqbot-media/SKILL.md +0 -60
  257. package/src/admin-resolver.ts +0 -181
  258. package/src/api.ts +0 -1333
  259. package/src/credential-backup.ts +0 -72
  260. package/src/deliver-debounce.ts +0 -229
  261. package/src/gateway.ts +0 -2272
  262. package/src/group-history.ts +0 -328
  263. package/src/image-server.ts +0 -675
  264. package/src/inbound-attachments.ts +0 -321
  265. package/src/known-users.ts +0 -353
  266. package/src/message-gating.ts +0 -190
  267. package/src/message-queue.ts +0 -354
  268. package/src/onboarding.ts +0 -282
  269. package/src/outbound-deliver.ts +0 -486
  270. package/src/outbound.ts +0 -1179
  271. package/src/ref-index-store.ts +0 -412
  272. package/src/reply-dispatcher.ts +0 -334
  273. package/src/session-store.ts +0 -303
  274. package/src/slash-commands.ts +0 -2431
  275. package/src/startup-greeting.ts +0 -120
  276. package/src/streaming.ts +0 -1077
  277. package/src/stt.ts +0 -86
  278. package/src/tools/channel.ts +0 -275
  279. package/src/transport/index.ts +0 -11
  280. package/src/transport/webhook-transport.ts +0 -332
  281. package/src/transport/webhook-verify.ts +0 -119
  282. package/src/typing-keepalive.ts +0 -59
  283. package/src/utils/audio-convert.ts +0 -887
  284. package/src/utils/chunked-upload.ts +0 -483
  285. package/src/utils/file-utils.ts +0 -193
  286. package/src/utils/image-size.ts +0 -266
  287. package/src/utils/media-send.ts +0 -636
  288. package/src/utils/media-tags.ts +0 -183
  289. package/src/utils/payload.ts +0 -265
  290. package/src/utils/text-parsing.ts +0 -85
  291. package/src/utils/upload-cache.ts +0 -128
@@ -1,72 +0,0 @@
1
- /**
2
- * 凭证暂存与恢复
3
- *
4
- * 解决热更新被打断时 openclaw.json 中 appId/secret 丢失的问题。
5
- *
6
- * 原理:
7
- * - 每次 gateway 成功启动后,把当前账户的 appId/secret 写入暂存文件
8
- * - 插件启动时如果检测到配置中 appId/secret 为空,尝试从暂存文件恢复
9
- * - 暂存文件存储在 ~/.openclaw/qqbot/data/ 下,不受插件目录替换影响
10
- *
11
- * 安全保障:
12
- * - 只在 appId/secret **确实为空** 时才尝试恢复(不干扰正常配置变更)
13
- * - 恢复后通过 openclaw 的 config API 写回配置文件,确保框架感知到变更
14
- * - 暂存文件使用原子写入(先写 .tmp 再 rename)防止损坏
15
- */
16
-
17
- import fs from "node:fs";
18
- import path from "node:path";
19
- import { getQQBotDataDir } from "./utils/platform.js";
20
-
21
- const BACKUP_FILENAME = "credential-backup.json";
22
-
23
- interface CredentialBackup {
24
- accountId: string;
25
- appId: string;
26
- clientSecret: string;
27
- savedAt: string;
28
- }
29
-
30
- function getBackupPath(): string {
31
- return path.join(getQQBotDataDir("data"), BACKUP_FILENAME);
32
- }
33
-
34
- /**
35
- * 保存凭证快照到暂存文件(gateway 成功启动后调用)
36
- */
37
- export function saveCredentialBackup(accountId: string, appId: string, clientSecret: string): void {
38
- if (!appId || !clientSecret) return; // 不保存空凭证
39
- try {
40
- const backupPath = getBackupPath();
41
- const data: CredentialBackup = {
42
- accountId,
43
- appId,
44
- clientSecret,
45
- savedAt: new Date().toISOString(),
46
- };
47
- const tmpPath = backupPath + ".tmp";
48
- fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2) + "\n", "utf8");
49
- fs.renameSync(tmpPath, backupPath);
50
- } catch {
51
- // 非关键操作,静默忽略
52
- }
53
- }
54
-
55
- /**
56
- * 从暂存文件读取凭证(仅在配置为空时调用)
57
- * 返回 null 表示无可用备份
58
- */
59
- export function loadCredentialBackup(accountId?: string): CredentialBackup | null {
60
- try {
61
- const backupPath = getBackupPath();
62
- if (!fs.existsSync(backupPath)) return null;
63
- const raw = fs.readFileSync(backupPath, "utf8");
64
- const data: CredentialBackup = JSON.parse(raw);
65
- if (!data.appId || !data.clientSecret) return null;
66
- // 如果指定了 accountId,校验是否匹配
67
- if (accountId && data.accountId !== accountId) return null;
68
- return data;
69
- } catch {
70
- return null;
71
- }
72
- }
@@ -1,229 +0,0 @@
1
- /**
2
- * 出站消息合并回复(Deliver Debounce)模块
3
- *
4
- * 解决的问题:
5
- * 当 openclaw 框架层的 embedded agent 超时或快速连续产生多次 deliver 时,
6
- * 用户会在短时间内收到大量碎片消息(消息轰炸)。
7
- *
8
- * 解决方案:
9
- * 在 deliver 回调和实际发送之间加入 debounce 层。
10
- * 短时间内(windowMs)连续到达的多条纯文本 deliver 会被合并为一条消息发送。
11
- * 含媒体的 deliver 会立即 flush 已缓冲的文本并正常处理媒体。
12
- */
13
-
14
- import type { DeliverDebounceConfig } from "./types.js";
15
-
16
- // ============ 默认值 ============
17
-
18
- const DEFAULT_WINDOW_MS = 1500;
19
- const DEFAULT_MAX_WAIT_MS = 8000;
20
- const DEFAULT_SEPARATOR = "\n\n---\n\n";
21
-
22
- // ============ 类型定义 ============
23
-
24
- export interface DeliverPayload {
25
- text?: string;
26
- mediaUrls?: string[];
27
- mediaUrl?: string;
28
- }
29
-
30
- export interface DeliverInfo {
31
- kind: string;
32
- }
33
-
34
- /** 实际执行发送的回调 */
35
- export type DeliverExecutor = (payload: DeliverPayload, info: DeliverInfo) => Promise<void>;
36
-
37
- // ============ DeliverDebouncer 类 ============
38
-
39
- export class DeliverDebouncer {
40
- private readonly windowMs: number;
41
- private readonly maxWaitMs: number;
42
- private readonly separator: string;
43
- private readonly executor: DeliverExecutor;
44
- private readonly log?: {
45
- info: (msg: string) => void;
46
- error: (msg: string) => void;
47
- };
48
- private readonly prefix: string;
49
-
50
- /** 缓冲中的文本片段 */
51
- private bufferedTexts: string[] = [];
52
- /** 缓冲中最后一次 deliver 的 info(用于 flush 时传递 kind) */
53
- private lastInfo: DeliverInfo | null = null;
54
- /** 缓冲中最后一次 deliver 的 payload(非文本字段,如 mediaUrls) */
55
- private lastPayload: DeliverPayload | null = null;
56
- /** debounce 定时器 */
57
- private debounceTimer: ReturnType<typeof setTimeout> | null = null;
58
- /** 最大等待定时器(从第一条 deliver 开始计算) */
59
- private maxWaitTimer: ReturnType<typeof setTimeout> | null = null;
60
- /** 是否正在 flush */
61
- private flushing = false;
62
- /** 已销毁标记 */
63
- private disposed = false;
64
-
65
- constructor(
66
- config: DeliverDebounceConfig | undefined,
67
- executor: DeliverExecutor,
68
- log?: { info: (msg: string) => void; error: (msg: string) => void },
69
- prefix = "[debounce]",
70
- ) {
71
- this.windowMs = config?.windowMs ?? DEFAULT_WINDOW_MS;
72
- this.maxWaitMs = config?.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
73
- this.separator = config?.separator ?? DEFAULT_SEPARATOR;
74
- this.executor = executor;
75
- this.log = log;
76
- this.prefix = prefix;
77
- }
78
-
79
- /**
80
- * 接收一次 deliver 调用。
81
- * - 纯文本 deliver → 缓冲并设置 debounce 定时器
82
- * - 含媒体 deliver → 先 flush 已缓冲文本,再直接执行当前 deliver
83
- */
84
- async deliver(payload: DeliverPayload, info: DeliverInfo): Promise<void> {
85
- if (this.disposed) return;
86
-
87
- const hasMedia = Boolean(
88
- (payload.mediaUrls && payload.mediaUrls.length > 0) || payload.mediaUrl,
89
- );
90
- const text = (payload.text ?? "").trim();
91
-
92
- // 含媒体的 deliver:立即 flush 缓冲 + 直接执行
93
- if (hasMedia) {
94
- this.log?.info(`${this.prefix} Media deliver detected, flushing ${this.bufferedTexts.length} buffered text(s) first`);
95
- await this.flush();
96
- await this.executor(payload, info);
97
- return;
98
- }
99
-
100
- // 空文本 deliver:直接透传(不缓冲)
101
- if (!text) {
102
- await this.executor(payload, info);
103
- return;
104
- }
105
-
106
- // 纯文本 deliver:缓冲
107
- this.bufferedTexts.push(text);
108
- this.lastInfo = info;
109
- this.lastPayload = payload;
110
-
111
- this.log?.info(
112
- `${this.prefix} Buffered text #${this.bufferedTexts.length} (${text.length} chars), window=${this.windowMs}ms`,
113
- );
114
-
115
- // 重置 debounce 定时器
116
- if (this.debounceTimer) {
117
- clearTimeout(this.debounceTimer);
118
- }
119
- this.debounceTimer = setTimeout(() => {
120
- this.flush().catch((err) => {
121
- this.log?.error(`${this.prefix} Flush error (debounce timer): ${err}`);
122
- });
123
- }, this.windowMs);
124
-
125
- // 首次缓冲时启动最大等待定时器
126
- if (this.bufferedTexts.length === 1) {
127
- if (this.maxWaitTimer) {
128
- clearTimeout(this.maxWaitTimer);
129
- }
130
- this.maxWaitTimer = setTimeout(() => {
131
- this.log?.info(`${this.prefix} Max wait (${this.maxWaitMs}ms) reached, force flushing`);
132
- this.flush().catch((err) => {
133
- this.log?.error(`${this.prefix} Flush error (max wait timer): ${err}`);
134
- });
135
- }, this.maxWaitMs);
136
- }
137
- }
138
-
139
- /**
140
- * 将缓冲中的文本合并为一条消息发送
141
- */
142
- async flush(): Promise<void> {
143
- if (this.flushing || this.bufferedTexts.length === 0) return;
144
- this.flushing = true;
145
-
146
- // 清除定时器
147
- if (this.debounceTimer) {
148
- clearTimeout(this.debounceTimer);
149
- this.debounceTimer = null;
150
- }
151
- if (this.maxWaitTimer) {
152
- clearTimeout(this.maxWaitTimer);
153
- this.maxWaitTimer = null;
154
- }
155
-
156
- // 取出缓冲
157
- const texts = this.bufferedTexts;
158
- const info = this.lastInfo!;
159
- const lastPayload = this.lastPayload!;
160
- this.bufferedTexts = [];
161
- this.lastInfo = null;
162
- this.lastPayload = null;
163
-
164
- try {
165
- if (texts.length === 1) {
166
- // 只有一条,直接透传原始 payload
167
- this.log?.info(`${this.prefix} Flushing single buffered text (${texts[0].length} chars)`);
168
- await this.executor({ ...lastPayload, text: texts[0] }, info);
169
- } else {
170
- // 多条合并
171
- const merged = texts.join(this.separator);
172
- this.log?.info(
173
- `${this.prefix} Merged ${texts.length} buffered texts into one (${merged.length} chars)`,
174
- );
175
- await this.executor({ ...lastPayload, text: merged }, info);
176
- }
177
- } finally {
178
- this.flushing = false;
179
- }
180
- }
181
-
182
- /**
183
- * 销毁:flush 剩余缓冲并清除定时器
184
- */
185
- async dispose(): Promise<void> {
186
- this.disposed = true;
187
- if (this.debounceTimer) {
188
- clearTimeout(this.debounceTimer);
189
- this.debounceTimer = null;
190
- }
191
- if (this.maxWaitTimer) {
192
- clearTimeout(this.maxWaitTimer);
193
- this.maxWaitTimer = null;
194
- }
195
- // flush 剩余
196
- if (this.bufferedTexts.length > 0) {
197
- this.flushing = false; // 确保 flush 能执行
198
- await this.flush();
199
- }
200
- }
201
-
202
- /** 当前是否有缓冲中的文本 */
203
- get hasPending(): boolean {
204
- return this.bufferedTexts.length > 0;
205
- }
206
-
207
- /** 缓冲中的文本数量 */
208
- get pendingCount(): number {
209
- return this.bufferedTexts.length;
210
- }
211
- }
212
-
213
- // ============ 工厂函数 ============
214
-
215
- /**
216
- * 根据配置创建 debouncer 或返回 null(禁用时)
217
- */
218
- export function createDeliverDebouncer(
219
- config: DeliverDebounceConfig | undefined,
220
- executor: DeliverExecutor,
221
- log?: { info: (msg: string) => void; error: (msg: string) => void },
222
- prefix?: string,
223
- ): DeliverDebouncer | null {
224
- // 未配置时默认启用
225
- if (config?.enabled === false) {
226
- return null;
227
- }
228
- return new DeliverDebouncer(config, executor, log, prefix);
229
- }