@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,47 +0,0 @@
1
- /**
2
- * QQBot Approval Handler
3
- *
4
- * 监听 Gateway 的 exec/plugin approval 事件,
5
- * 直接调用 QQ API 发送带 Inline Keyboard 的审批消息。
6
- * 参考 DiscordExecApprovalHandler 的实现模式。
7
- *
8
- * 兼容性:gateway-runtime / approval-runtime 模块在 openclaw < 3.22 上不存在,
9
- * 使用动态 import 避免插件整体加载失败,旧版框架上审批功能自动降级(不可用)。
10
- */
11
- import type { OpenClawConfig } from "openclaw/plugin-sdk";
12
- export interface QQBotApprovalHandlerOpts {
13
- accountId: string;
14
- appId: string;
15
- clientSecret: string;
16
- cfg: OpenClawConfig;
17
- gatewayUrl?: string;
18
- log?: {
19
- info: (msg: string) => void;
20
- error: (msg: string) => void;
21
- debug?: (msg: string) => void;
22
- };
23
- }
24
- export declare class QQBotApprovalHandler {
25
- private gatewayClient;
26
- private pending;
27
- private requestCache;
28
- private opts;
29
- private started;
30
- constructor(opts: QQBotApprovalHandlerOpts);
31
- start(): Promise<void>;
32
- stop(): Promise<void>;
33
- /** 检查是否有指定 shortId 对应的 pending 审批 */
34
- hasShortId(shortId: string): boolean;
35
- /** 解析审批请求(供 Interaction 回调或 /approve 命令调用) */
36
- resolveApproval(approvalId: string, decision: "allow-once" | "allow-always" | "deny"): Promise<boolean>;
37
- private handleGatewayEvent;
38
- private handleRequested;
39
- private handleResolved;
40
- private handleTimeout;
41
- }
42
- export declare function isApprovalFeatureAvailable(): boolean;
43
- export declare function setApprovalFeatureAvailable(available: boolean): void;
44
- export declare function registerApprovalHandler(accountId: string, handler: QQBotApprovalHandler): void;
45
- export declare function unregisterApprovalHandler(accountId: string): void;
46
- export declare function getApprovalHandler(accountId: string): QQBotApprovalHandler | undefined;
47
- export declare function findApprovalHandlerForShortId(shortId: string): QQBotApprovalHandler | undefined;
@@ -1,372 +0,0 @@
1
- /**
2
- * QQBot Approval Handler
3
- *
4
- * 监听 Gateway 的 exec/plugin approval 事件,
5
- * 直接调用 QQ API 发送带 Inline Keyboard 的审批消息。
6
- * 参考 DiscordExecApprovalHandler 的实现模式。
7
- *
8
- * 兼容性:gateway-runtime / approval-runtime 模块在 openclaw < 3.22 上不存在,
9
- * 使用动态 import 避免插件整体加载失败,旧版框架上审批功能自动降级(不可用)。
10
- */
11
- import { createRequire } from "node:module";
12
- import path from "node:path";
13
- import { fileURLToPath } from "node:url";
14
- import { getAccessToken, sendC2CMessageWithInlineKeyboard, sendGroupMessageWithInlineKeyboard, } from "./api.js";
15
- // ─── 动态加载 gateway-runtime(兼容不同安装环境) ────────
16
- function loadGatewayRuntime() {
17
- const req = createRequire(import.meta.url);
18
- const currentFile = fileURLToPath(import.meta.url);
19
- const pluginRoot = path.resolve(path.dirname(currentFile), "..", "..");
20
- const fs = req("node:fs");
21
- // 尝试从找到的 openclaw 根目录加载 gateway-runtime.js
22
- const tryLoadFromRoot = (root) => {
23
- for (const rel of ["dist/plugin-sdk/gateway-runtime.js", "plugin-sdk/gateway-runtime.js"]) {
24
- const p = path.join(root, rel);
25
- try {
26
- if (fs.existsSync(p))
27
- return req(p);
28
- }
29
- catch { /* try next */ }
30
- }
31
- return null;
32
- };
33
- // 策略 1: link-sdk-core.cjs findOpenclawRoot
34
- try {
35
- const { findOpenclawRoot } = req(path.join(pluginRoot, "scripts", "link-sdk-core.cjs"));
36
- const root = findOpenclawRoot(pluginRoot);
37
- if (root) {
38
- const mod = tryLoadFromRoot(root);
39
- if (mod)
40
- return mod;
41
- }
42
- }
43
- catch { /* fallback */ }
44
- // 策略 2: process.argv[1] 反推(当前进程就是 openclaw)
45
- try {
46
- const entry = process.argv[1];
47
- if (entry) {
48
- const realEntry = fs.realpathSync(entry);
49
- let dir = path.dirname(realEntry);
50
- for (let i = 0; i < 6; i++) {
51
- const mod = tryLoadFromRoot(dir);
52
- if (mod)
53
- return mod;
54
- const parent = path.dirname(dir);
55
- if (parent === dir)
56
- break;
57
- dir = parent;
58
- }
59
- }
60
- }
61
- catch { /* fallback */ }
62
- throw new Error("Cannot find openclaw/plugin-sdk/gateway-runtime (all strategies failed)");
63
- }
64
- // ─── 辅助函数 ───────────────────────────────────────────────
65
- function toShortId(approvalId) {
66
- return approvalId.replace(/^(exec|plugin):/, "").slice(0, 8);
67
- }
68
- function resolveApprovalKind(approvalId) {
69
- return approvalId.startsWith("plugin:") ? "plugin" : "exec";
70
- }
71
- function buildExecApprovalText(request) {
72
- const expiresIn = Math.max(0, Math.round((request.expiresAtMs - Date.now()) / 1000));
73
- const lines = ["🔐 命令执行审批", ""];
74
- const cmd = request.request.commandPreview ?? request.request.command ?? "";
75
- if (cmd)
76
- lines.push(`\`\`\`\n${cmd.slice(0, 300)}\n\`\`\``);
77
- if (request.request.cwd)
78
- lines.push(`📁 目录: ${request.request.cwd}`);
79
- if (request.request.agentId)
80
- lines.push(`🤖 Agent: ${request.request.agentId}`);
81
- lines.push("", `⏱️ 超时: ${expiresIn} 秒`);
82
- return lines.join("\n");
83
- }
84
- function buildPluginApprovalText(request) {
85
- const timeoutSec = Math.round((request.request.timeoutMs ?? 120_000) / 1000);
86
- const severityIcon = request.request.severity === "critical" ? "🔴"
87
- : request.request.severity === "info" ? "🔵"
88
- : "🟡";
89
- const lines = [`${severityIcon} 审批请求`, ""];
90
- lines.push(`📋 ${request.request.title}`);
91
- if (request.request.description)
92
- lines.push(`📝 ${request.request.description}`);
93
- if (request.request.toolName)
94
- lines.push(`🔧 工具: ${request.request.toolName}`);
95
- if (request.request.pluginId)
96
- lines.push(`🔌 插件: ${request.request.pluginId}`);
97
- if (request.request.agentId)
98
- lines.push(`🤖 Agent: ${request.request.agentId}`);
99
- lines.push("", `⏱️ 超时: ${timeoutSec} 秒`);
100
- return lines.join("\n");
101
- }
102
- /**
103
- * Inline Keyboard(内嵌回调型按钮)
104
- * type=1(Callback):点击触发 INTERACTION_CREATE,button_data = data 字段
105
- * group_id 相同 → 点一个后其余变灰(三选一语义)
106
- * click_limit=1 → 每人只能点一次
107
- * permission.type=2 → 所有人可操作
108
- */
109
- function buildApprovalKeyboard(approvalId) {
110
- const makeBtn = (id, label, visitedLabel, data, style) => ({
111
- id,
112
- render_data: { label, visited_label: visitedLabel, style },
113
- action: {
114
- type: 1,
115
- data,
116
- permission: { type: 2 },
117
- click_limit: 1,
118
- },
119
- group_id: "approval",
120
- });
121
- return {
122
- content: {
123
- rows: [
124
- {
125
- buttons: [
126
- makeBtn("allow", "✅ 允许一次", "已允许", `approve:${approvalId}:allow-once`, 1),
127
- makeBtn("always", "⭐ 始终允许", "已始终允许", `approve:${approvalId}:allow-always`, 1),
128
- makeBtn("deny", "❌ 拒绝", "已拒绝", `approve:${approvalId}:deny`, 0),
129
- ],
130
- },
131
- ],
132
- },
133
- };
134
- }
135
- /** 从 sessionKey 或 turnSourceTo 提取投递目标 */
136
- function resolveTarget(sessionKey, turnSourceTo) {
137
- // 优先从 sessionKey 解析(如 agent:main:qqbot:direct:OPENID)
138
- const sk = sessionKey ?? turnSourceTo;
139
- if (!sk)
140
- return null;
141
- const m = sk.match(/qqbot:(c2c|direct|group):([A-F0-9]+)/i);
142
- if (!m)
143
- return null;
144
- const type = m[1].toLowerCase() === "group" ? "group" : "c2c";
145
- return { type, id: m[2] };
146
- }
147
- // ─── Handler 类 ──────────────────────────────────────────────
148
- export class QQBotApprovalHandler {
149
- gatewayClient = null;
150
- pending = new Map();
151
- requestCache = new Map();
152
- opts;
153
- started = false;
154
- constructor(opts) {
155
- this.opts = opts;
156
- }
157
- async start() {
158
- if (this.started)
159
- return;
160
- this.started = true;
161
- const { log } = this.opts;
162
- log?.info(`[qqbot:${this.opts.accountId}] approval-handler: starting`);
163
- // 动态加载 gateway-runtime(兼容旧版框架 / pnpm 环境)
164
- let gatewayRuntime;
165
- try {
166
- gatewayRuntime = loadGatewayRuntime();
167
- }
168
- catch (err) {
169
- log?.error(`[qqbot:${this.opts.accountId}] approval-handler: gateway-runtime module not available, approval feature disabled. Error: ${err}`);
170
- this.started = false;
171
- return;
172
- }
173
- try {
174
- this.gatewayClient = await gatewayRuntime.createOperatorApprovalsGatewayClient({
175
- config: this.opts.cfg,
176
- gatewayUrl: this.opts.gatewayUrl,
177
- clientDisplayName: "QQBot Approval Handler",
178
- onEvent: (evt) => this.handleGatewayEvent(evt),
179
- onHelloOk: () => log?.info(`[qqbot:${this.opts.accountId}] approval-handler: connected to gateway`),
180
- onConnectError: (err) => log?.error(`[qqbot:${this.opts.accountId}] approval-handler: connect error: ${err.message}`),
181
- onClose: (code, reason) => log?.debug?.(`[qqbot:${this.opts.accountId}] approval-handler: gateway closed: ${code} ${reason}`),
182
- });
183
- this.gatewayClient.start();
184
- setApprovalFeatureAvailable(true);
185
- }
186
- catch (err) {
187
- log?.error(`[qqbot:${this.opts.accountId}] approval-handler: failed to create gateway client: ${err}`);
188
- this.started = false;
189
- }
190
- }
191
- async stop() {
192
- if (!this.started)
193
- return;
194
- this.started = false;
195
- for (const entry of this.pending.values())
196
- clearTimeout(entry.timeoutId);
197
- this.pending.clear();
198
- this.requestCache.clear();
199
- this.gatewayClient?.stop();
200
- this.gatewayClient = null;
201
- this.opts.log?.info(`[qqbot:${this.opts.accountId}] approval-handler: stopped`);
202
- }
203
- /** 检查是否有指定 shortId 对应的 pending 审批 */
204
- hasShortId(shortId) {
205
- for (const id of this.pending.keys()) {
206
- if (toShortId(id) === shortId)
207
- return true;
208
- }
209
- return false;
210
- }
211
- /** 解析审批请求(供 Interaction 回调或 /approve 命令调用) */
212
- async resolveApproval(approvalId, decision) {
213
- if (!this.gatewayClient)
214
- return false;
215
- // 查找完整 ID:支持完整 ID(exec:uuid / plugin:uuid)、纯 UUID、或 shortId(8位)
216
- let fullId = approvalId;
217
- if (this.pending.has(approvalId)) {
218
- fullId = approvalId;
219
- }
220
- else {
221
- // 尝试在 pending keys 中匹配:纯 UUID 可能对应 exec:uuid 或 plugin:uuid
222
- for (const id of this.pending.keys()) {
223
- if (id === approvalId) {
224
- fullId = id;
225
- break;
226
- }
227
- // 纯 UUID 匹配:pending key 的 uuid 部分等于传入值
228
- if (id.replace(/^(exec|plugin):/, "") === approvalId) {
229
- fullId = id;
230
- break;
231
- }
232
- // shortId 匹配
233
- if (toShortId(id) === approvalId) {
234
- fullId = id;
235
- break;
236
- }
237
- }
238
- // 也在 requestCache 中查找(handleResolved 可能已清除 pending)
239
- if (fullId === approvalId && !this.requestCache.has(approvalId)) {
240
- for (const id of this.requestCache.keys()) {
241
- if (id.replace(/^(exec|plugin):/, "") === approvalId) {
242
- fullId = id;
243
- break;
244
- }
245
- }
246
- }
247
- }
248
- const kind = resolveApprovalKind(fullId);
249
- const method = kind === "plugin" ? "plugin.approval.resolve" : "exec.approval.resolve";
250
- const isPending = this.pending.has(fullId);
251
- const isCached = this.requestCache.has(fullId);
252
- this.opts.log?.info(`[qqbot:${this.opts.accountId}] approval-handler: resolving ${fullId} (input=${approvalId}) kind=${kind} → ${decision}, pending=${isPending}, cached=${isCached}`);
253
- try {
254
- await this.gatewayClient.request(method, { id: fullId, decision });
255
- this.opts.log?.info(`[qqbot:${this.opts.accountId}] approval-handler: RPC success ${toShortId(fullId)} → ${decision} (method=${method})`);
256
- return true;
257
- }
258
- catch (err) {
259
- this.opts.log?.error(`[qqbot:${this.opts.accountId}] approval-handler: resolve failed: ${err}`);
260
- return false;
261
- }
262
- }
263
- handleGatewayEvent(evt) {
264
- if (evt.event === "exec.approval.requested") {
265
- void this.handleRequested(evt.payload, "exec");
266
- }
267
- else if (evt.event === "plugin.approval.requested") {
268
- void this.handleRequested(evt.payload, "plugin");
269
- }
270
- else if (evt.event === "exec.approval.resolved") {
271
- void this.handleResolved(evt.payload);
272
- }
273
- else if (evt.event === "plugin.approval.resolved") {
274
- void this.handleResolved(evt.payload);
275
- }
276
- }
277
- async handleRequested(request, kind) {
278
- const { log, appId, clientSecret, accountId } = this.opts;
279
- const shortId = toShortId(request.id);
280
- // 只处理本账号的请求
281
- const reqAccountId = request.request.turnSourceAccountId?.trim();
282
- if (reqAccountId && reqAccountId !== accountId)
283
- return;
284
- // 解析投递目标
285
- const sessionKey = request.request.sessionKey;
286
- const turnSourceTo = request.request.turnSourceTo;
287
- const target = resolveTarget(sessionKey, turnSourceTo);
288
- if (!target) {
289
- log?.info(`[qqbot:${accountId}] approval-handler: no QQ target for ${shortId} (session=${sessionKey})`);
290
- return;
291
- }
292
- // 缓存请求
293
- this.requestCache.set(request.id, kind === "plugin"
294
- ? { kind: "plugin", request: request }
295
- : { kind: "exec", request: request });
296
- log?.info(`[qqbot:${accountId}] approval-handler: sending ${kind} approval ${shortId} to ${target.type}:${target.id}`);
297
- const text = kind === "plugin"
298
- ? buildPluginApprovalText(request)
299
- : buildExecApprovalText(request);
300
- const keyboard = buildApprovalKeyboard(request.id);
301
- const timeoutMs = kind === "plugin"
302
- ? (request.request.timeoutMs ?? 120_000)
303
- : Math.max(0, request.expiresAtMs - Date.now());
304
- // 短暂延迟,确保框架侧 waitDecision 已就绪,避免时序竞争
305
- await new Promise((r) => setTimeout(r, 2000));
306
- try {
307
- const token = await getAccessToken(appId, clientSecret);
308
- if (target.type === "c2c") {
309
- await sendC2CMessageWithInlineKeyboard(token, target.id, text, keyboard);
310
- }
311
- else {
312
- await sendGroupMessageWithInlineKeyboard(token, target.id, text, keyboard);
313
- }
314
- log?.info(`[qqbot:${accountId}] approval-handler: sent ${kind} approval ${shortId}`);
315
- const timeoutId = setTimeout(() => {
316
- this.handleTimeout(request.id, target);
317
- }, timeoutMs + 2_000);
318
- this.pending.set(request.id, { targets: [target], timeoutId });
319
- }
320
- catch (err) {
321
- this.requestCache.delete(request.id);
322
- log?.error(`[qqbot:${accountId}] approval-handler: failed to send approval ${shortId}: ${err}`);
323
- }
324
- }
325
- async handleResolved(resolved) {
326
- const entry = this.pending.get(resolved.id);
327
- const resolvedBy = resolved.resolvedBy ?? "unknown";
328
- const kind = resolveApprovalKind(resolved.id);
329
- this.opts.log?.info(`[qqbot:${this.opts.accountId}] approval-handler: gateway confirmed ${toShortId(resolved.id)} → ${resolved.decision} (kind=${kind}, resolvedBy=${resolvedBy}, wasPending=${!!entry})`);
330
- if (!entry)
331
- return;
332
- clearTimeout(entry.timeoutId);
333
- this.pending.delete(resolved.id);
334
- this.requestCache.delete(resolved.id);
335
- // 框架 Forwarder 负责发送 resolved 通知(已通过 buildResolvedPayload=null 抑制),此处不重复发送
336
- }
337
- async handleTimeout(approvalId, target) {
338
- const { log, accountId } = this.opts;
339
- if (!this.pending.has(approvalId))
340
- return;
341
- this.pending.delete(approvalId);
342
- this.requestCache.delete(approvalId);
343
- log?.info(`[qqbot:${accountId}] approval-handler: timeout ${toShortId(approvalId)}`);
344
- // 超时由框架处理,此处仅清理状态,不重复发消息
345
- }
346
- }
347
- // ─── 模块级 handler 注册 ────────────────────────────────────
348
- const _handlers = new Map();
349
- /** 审批功能是否可用(gateway-runtime 模块加载成功则为 true) */
350
- let _approvalFeatureAvailable = false;
351
- export function isApprovalFeatureAvailable() {
352
- return _approvalFeatureAvailable;
353
- }
354
- export function setApprovalFeatureAvailable(available) {
355
- _approvalFeatureAvailable = available;
356
- }
357
- export function registerApprovalHandler(accountId, handler) {
358
- _handlers.set(accountId, handler);
359
- }
360
- export function unregisterApprovalHandler(accountId) {
361
- _handlers.delete(accountId);
362
- }
363
- export function getApprovalHandler(accountId) {
364
- return _handlers.get(accountId);
365
- }
366
- export function findApprovalHandlerForShortId(shortId) {
367
- for (const handler of _handlers.values()) {
368
- if (handler.hasShortId(shortId))
369
- return handler;
370
- }
371
- return undefined;
372
- }
@@ -1,29 +0,0 @@
1
- import { type ChannelPlugin } from "openclaw/plugin-sdk/core";
2
- import type { ResolvedQQBotAccount } from "./types.js";
3
- /** QQ Bot 单条消息文本长度上限 */
4
- export declare const TEXT_CHUNK_LIMIT = 5000;
5
- /**
6
- * Markdown 感知的文本分块函数
7
- * 委托给 SDK 内置的 channel.text.chunkMarkdownText
8
- * 支持代码块自动关闭/重开、括号感知等
9
- */
10
- export declare function chunkText(text: string, limit: number): string[];
11
- export declare const qqbotPlugin: ChannelPlugin<ResolvedQQBotAccount>;
12
- /** 清理 @mention:替换 <@openid> 为 @用户名,去除 @机器人自身 */
13
- export declare function stripMentionText(text: string, mentions?: Array<{
14
- member_openid?: string;
15
- id?: string;
16
- user_openid?: string;
17
- is_you?: boolean;
18
- nickname?: string;
19
- username?: string;
20
- }>): string;
21
- /** 检测消息是否 @了机器人(mentions > eventType > mentionPatterns) */
22
- export declare function detectWasMentioned({ eventType, mentions, content, mentionPatterns }: {
23
- eventType?: string;
24
- mentions?: Array<{
25
- is_you?: boolean;
26
- }>;
27
- content?: string;
28
- mentionPatterns?: string[];
29
- }): boolean;