@tencent-connect/dsh-qqbot 0.3.0 → 0.5.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 (183) hide show
  1. package/.orange-ci.yml +2 -68
  2. package/CHANGELOG.md +45 -0
  3. package/CHANGELOG_EN.md +84 -0
  4. package/README.md +107 -17
  5. package/README_EN.md +107 -17
  6. package/cordis.patch.yml +1 -1
  7. package/cordis.yml +46 -3
  8. package/dist/commands/help.d.ts +6 -7
  9. package/dist/commands/help.d.ts.map +1 -1
  10. package/dist/commands/help.js +23 -7
  11. package/dist/commands/help.js.map +1 -1
  12. package/dist/commands/index.d.ts +7 -5
  13. package/dist/commands/index.d.ts.map +1 -1
  14. package/dist/commands/index.js +12 -12
  15. package/dist/commands/index.js.map +1 -1
  16. package/dist/commands/misc.d.ts +10 -8
  17. package/dist/commands/misc.d.ts.map +1 -1
  18. package/dist/commands/misc.js +45 -8
  19. package/dist/commands/misc.js.map +1 -1
  20. package/dist/commands/model.d.ts +3 -4
  21. package/dist/commands/model.d.ts.map +1 -1
  22. package/dist/commands/model.js +8 -7
  23. package/dist/commands/model.js.map +1 -1
  24. package/dist/commands/preset.d.ts +8 -0
  25. package/dist/commands/preset.d.ts.map +1 -0
  26. package/dist/commands/preset.js +52 -0
  27. package/dist/commands/preset.js.map +1 -0
  28. package/dist/commands/session.d.ts +11 -7
  29. package/dist/commands/session.d.ts.map +1 -1
  30. package/dist/commands/session.js +29 -13
  31. package/dist/commands/session.js.map +1 -1
  32. package/dist/commands/status.d.ts +3 -4
  33. package/dist/commands/status.d.ts.map +1 -1
  34. package/dist/commands/status.js +2 -1
  35. package/dist/commands/status.js.map +1 -1
  36. package/dist/commands/types.d.ts +9 -2
  37. package/dist/commands/types.d.ts.map +1 -1
  38. package/dist/config.d.ts +38 -0
  39. package/dist/config.d.ts.map +1 -1
  40. package/dist/config.js +35 -1
  41. package/dist/config.js.map +1 -1
  42. package/dist/features/answer-parser.d.ts +9 -0
  43. package/dist/features/answer-parser.d.ts.map +1 -0
  44. package/dist/features/answer-parser.js +23 -0
  45. package/dist/features/answer-parser.js.map +1 -0
  46. package/dist/features/approval-channel.d.ts +76 -0
  47. package/dist/features/approval-channel.d.ts.map +1 -0
  48. package/dist/features/approval-channel.js +142 -0
  49. package/dist/features/approval-channel.js.map +1 -0
  50. package/dist/features/approval-renderer.d.ts +18 -0
  51. package/dist/features/approval-renderer.d.ts.map +1 -0
  52. package/dist/features/approval-renderer.js +52 -0
  53. package/dist/features/approval-renderer.js.map +1 -0
  54. package/dist/features/button-utils.d.ts +33 -0
  55. package/dist/features/button-utils.d.ts.map +1 -0
  56. package/dist/features/button-utils.js +27 -0
  57. package/dist/features/button-utils.js.map +1 -0
  58. package/dist/features/history-store.d.ts.map +1 -1
  59. package/dist/features/history-store.js +0 -1
  60. package/dist/features/history-store.js.map +1 -1
  61. package/dist/features/question-channel.d.ts +96 -0
  62. package/dist/features/question-channel.d.ts.map +1 -0
  63. package/dist/features/question-channel.js +234 -0
  64. package/dist/features/question-channel.js.map +1 -0
  65. package/dist/features/question-renderer.d.ts +20 -0
  66. package/dist/features/question-renderer.d.ts.map +1 -0
  67. package/dist/features/question-renderer.js +70 -0
  68. package/dist/features/question-renderer.js.map +1 -0
  69. package/dist/gateway/bootstrap.d.ts +3 -3
  70. package/dist/gateway/bootstrap.d.ts.map +1 -1
  71. package/dist/gateway/bootstrap.js +76 -8
  72. package/dist/gateway/bootstrap.js.map +1 -1
  73. package/dist/gateway/index.d.ts +2 -2
  74. package/dist/gateway/index.js +2 -2
  75. package/dist/gateway/middleware-setup.d.ts +3 -3
  76. package/dist/gateway/middleware-setup.d.ts.map +1 -1
  77. package/dist/gateway/middleware-setup.js +9 -7
  78. package/dist/gateway/middleware-setup.js.map +1 -1
  79. package/dist/index.d.ts +2 -2
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +16 -11
  82. package/dist/index.js.map +1 -1
  83. package/dist/media/media-cleaner.d.ts +19 -0
  84. package/dist/media/media-cleaner.d.ts.map +1 -0
  85. package/dist/media/media-cleaner.js +81 -0
  86. package/dist/media/media-cleaner.js.map +1 -0
  87. package/dist/media/send-file-tool.d.ts +35 -0
  88. package/dist/media/send-file-tool.d.ts.map +1 -0
  89. package/dist/media/send-file-tool.js +150 -0
  90. package/dist/media/send-file-tool.js.map +1 -0
  91. package/dist/media/vision-tool.d.ts +16 -0
  92. package/dist/media/vision-tool.d.ts.map +1 -0
  93. package/dist/media/vision-tool.js +241 -0
  94. package/dist/media/vision-tool.js.map +1 -0
  95. package/dist/middleware/attachment.d.ts +3 -3
  96. package/dist/middleware/attachment.d.ts.map +1 -1
  97. package/dist/middleware/attachment.js +17 -4
  98. package/dist/middleware/attachment.js.map +1 -1
  99. package/dist/middleware/question-answer.d.ts +10 -0
  100. package/dist/middleware/question-answer.d.ts.map +1 -0
  101. package/dist/middleware/question-answer.js +19 -0
  102. package/dist/middleware/question-answer.js.map +1 -0
  103. package/dist/model/index.d.ts +4 -4
  104. package/dist/model/index.js +3 -3
  105. package/dist/model/model-resolver.d.ts +27 -13
  106. package/dist/model/model-resolver.d.ts.map +1 -1
  107. package/dist/model/model-resolver.js +82 -27
  108. package/dist/model/model-resolver.js.map +1 -1
  109. package/dist/model/prefs-store.d.ts +6 -1
  110. package/dist/model/prefs-store.d.ts.map +1 -1
  111. package/dist/model/prefs-store.js +25 -1
  112. package/dist/model/prefs-store.js.map +1 -1
  113. package/dist/model/settings-reader.d.ts +1 -1
  114. package/dist/session/idle-evictor.d.ts +2 -2
  115. package/dist/session/idle-evictor.d.ts.map +1 -1
  116. package/dist/session/idle-evictor.js +1 -1
  117. package/dist/session/idle-evictor.js.map +1 -1
  118. package/dist/session/index.d.ts +3 -3
  119. package/dist/session/index.d.ts.map +1 -1
  120. package/dist/session/index.js +2 -2
  121. package/dist/session/session-manager.d.ts +67 -9
  122. package/dist/session/session-manager.d.ts.map +1 -1
  123. package/dist/session/session-manager.js +216 -20
  124. package/dist/session/session-manager.js.map +1 -1
  125. package/dist/session/types.d.ts +47 -1
  126. package/dist/session/types.d.ts.map +1 -1
  127. package/dist/setup.d.ts +4 -4
  128. package/dist/setup.d.ts.map +1 -1
  129. package/dist/setup.js +63 -28
  130. package/dist/setup.js.map +1 -1
  131. package/dist/shared/index.d.ts +3 -3
  132. package/dist/shared/index.d.ts.map +1 -1
  133. package/dist/shared/index.js +3 -3
  134. package/dist/shared/index.js.map +1 -1
  135. package/dist/shared/send-helper.js +1 -1
  136. package/dist/shared/utils.d.ts +8 -1
  137. package/dist/shared/utils.d.ts.map +1 -1
  138. package/dist/shared/utils.js +18 -7
  139. package/dist/shared/utils.js.map +1 -1
  140. package/dist/transport/attachment.d.ts +18 -7
  141. package/dist/transport/attachment.d.ts.map +1 -1
  142. package/dist/transport/attachment.js +77 -30
  143. package/dist/transport/attachment.js.map +1 -1
  144. package/dist/transport/inbound.d.ts +5 -4
  145. package/dist/transport/inbound.d.ts.map +1 -1
  146. package/dist/transport/inbound.js +120 -80
  147. package/dist/transport/inbound.js.map +1 -1
  148. package/dist/transport/index.d.ts +4 -4
  149. package/dist/transport/index.js +4 -4
  150. package/dist/transport/msgid-cache.d.ts +23 -0
  151. package/dist/transport/msgid-cache.d.ts.map +1 -0
  152. package/dist/transport/msgid-cache.js +57 -0
  153. package/dist/transport/msgid-cache.js.map +1 -0
  154. package/dist/transport/outbound-buffer.d.ts +9 -6
  155. package/dist/transport/outbound-buffer.d.ts.map +1 -1
  156. package/dist/transport/outbound-buffer.js +5 -5
  157. package/dist/transport/outbound-buffer.js.map +1 -1
  158. package/dist/transport/outbound.d.ts +9 -9
  159. package/dist/transport/outbound.d.ts.map +1 -1
  160. package/dist/transport/outbound.js +7 -7
  161. package/dist/transport/outbound.js.map +1 -1
  162. package/dist/transport/reply-limiter.d.ts +31 -0
  163. package/dist/transport/reply-limiter.d.ts.map +1 -0
  164. package/dist/transport/reply-limiter.js +50 -0
  165. package/dist/transport/reply-limiter.js.map +1 -0
  166. package/dist/transport/reply-target.d.ts +29 -0
  167. package/dist/transport/reply-target.d.ts.map +1 -0
  168. package/dist/transport/reply-target.js +55 -0
  169. package/dist/transport/reply-target.js.map +1 -0
  170. package/dist/transport/streaming-writer.d.ts +3 -3
  171. package/dist/transport/streaming-writer.d.ts.map +1 -1
  172. package/dist/transport/streaming-writer.js +1 -1
  173. package/dist/transport/streaming-writer.js.map +1 -1
  174. package/dist/transport/tool-presenter.d.ts +0 -1
  175. package/dist/transport/tool-presenter.d.ts.map +1 -1
  176. package/dist/transport/tool-presenter.js +0 -1
  177. package/dist/transport/tool-presenter.js.map +1 -1
  178. package/dist/types.d.ts +9 -15
  179. package/dist/types.d.ts.map +1 -1
  180. package/docs/assets/qqchannel.jpg +0 -0
  181. package/docs/assets/qqgroup.jpg +0 -0
  182. package/docs/assets/qrcode.png +0 -0
  183. package/package.json +21 -4
@@ -1,8 +1,9 @@
1
- import { getScopePeer, formatRelativeTime } from '../shared/index.js';
1
+ import { getScopePeer, formatRelativeTime } from "../shared/index.js";
2
2
  /** /bot-status — 查看当前会话状态 */
3
3
  export function statusCommand({ manager }) {
4
4
  return {
5
5
  name: 'bot-status',
6
+ category: 'qqbot',
6
7
  description: '查看当前会话状态',
7
8
  handler: (cmdCtx) => {
8
9
  const { scope, peerId } = getScopePeer(cmdCtx);
@@ -1 +1 @@
1
- {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAEtE,6BAA6B;AAC7B,MAAM,UAAU,aAAa,CAAC,EAAE,OAAO,EAAe;IACpD,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,WAAW,EAAE,UAAU;QACvB,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE;YAClB,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;YAC/C,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAChD,IAAI,CAAC,MAAM,CAAC,MAAM;gBAAE,OAAO,SAAS,CAAC;YACrC,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;YAC/E,OAAO;gBACL,SAAS;gBACT,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE;gBAC9D,OAAO,SAAS,EAAE;gBAClB,WAAW,MAAM,CAAC,MAAM,IAAI,GAAG,EAAE;gBACjC,QAAQ,MAAM,CAAC,YAAY,IAAI,CAAC,EAAE;gBAClC,SAAS,kBAAkB,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE;aACnD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAEtE,6BAA6B;AAC7B,MAAM,UAAU,aAAa,CAAC,EAAE,OAAO,EAAe;IACpD,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,QAAQ,EAAE,OAAO;QACjB,WAAW,EAAE,UAAU;QACvB,OAAO,EAAE,CAAC,MAAM,EAAE,EAAE;YAClB,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;YAC/C,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAChD,IAAI,CAAC,MAAM,CAAC,MAAM;gBAAE,OAAO,SAAS,CAAC;YACrC,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;YAC/E,OAAO;gBACL,SAAS;gBACT,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE;gBAC9D,OAAO,SAAS,EAAE;gBAClB,WAAW,MAAM,CAAC,MAAM,IAAI,GAAG,EAAE;gBACjC,QAAQ,MAAM,CAAC,YAAY,IAAI,CAAC,EAAE;gBAClC,SAAS,kBAAkB,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE;aACnD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACf,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -3,10 +3,17 @@
3
3
  *
4
4
  * 每个命令工厂函数接收 CommandDeps,由 commands/index.ts 统一注入。
5
5
  */
6
- import type { SessionManager } from '../session/index.js';
7
- import type { ImQQBotConfig } from '../config.js';
6
+ import type { SlashCommand } from '@tencent-connect/qqbot-nodejs';
7
+ import type { SessionManager } from '../session/index.ts';
8
+ import type { ImQQBotConfig } from '../config.ts';
8
9
  export interface CommandDeps {
9
10
  manager: SessionManager;
10
11
  config: ImQQBotConfig;
11
12
  }
13
+ /** 命令分类:agent = 底层 agent 通用能力,qqbot = 插件特有 */
14
+ export type CommandCategory = 'agent' | 'qqbot';
15
+ /** 带分类的斜杠命令(category 供 help 分组展示,SDK 忽略未知字段) */
16
+ export interface CategorizedCommand extends SlashCommand {
17
+ category: CommandCategory;
18
+ }
12
19
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/commands/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC;CACvB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/commands/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,8CAA8C;AAC9C,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,OAAO,CAAC;AAEhD,gDAAgD;AAChD,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,QAAQ,EAAE,eAAe,CAAC;CAC3B"}
package/dist/config.d.ts CHANGED
@@ -12,6 +12,36 @@ export interface AccessControlConfig {
12
12
  /** 群聊白名单(group openid) */
13
13
  groupAllow: string[];
14
14
  }
15
+ export interface MediaConfig {
16
+ /** 是否启用富媒体理解(图片/视频下载 + 工具分析) */
17
+ enabled: boolean;
18
+ /** 富媒体下载大小上限(MB),默认 200 */
19
+ maxMB: number;
20
+ /** 富媒体存活时长(小时,TTL),默认 24,0 = 永不过期 */
21
+ ttlHours: number;
22
+ }
23
+ export interface VisionConfig {
24
+ /** 是否启用视觉理解(qqbot_describe_image 工具) */
25
+ enabled: boolean;
26
+ /** 视觉模型 provider(dsh 注册的 llm adapter,如 pi-ai) */
27
+ provider: string;
28
+ /** 视觉模型 id(如 qwen-vl-max) */
29
+ model: string;
30
+ /** 默认描述 prompt(调用未显式指定时使用) */
31
+ defaultPrompt: string;
32
+ /** 图片字节上限,默认 10MB */
33
+ maxBytes: number;
34
+ /** 输出 token 上限 */
35
+ maxTokens: number;
36
+ /** 视觉调用超时(ms) */
37
+ timeoutMs: number;
38
+ }
39
+ export interface SendFileConfig {
40
+ /** 是否启用路径白名单(默认 true,仅允许 media + cwd + extraRoots;关闭则任意路径) */
41
+ restrictPaths: boolean;
42
+ /** 额外允许访问的根目录(media 和 agent cwd 始终默认允许) */
43
+ extraRoots: string[];
44
+ }
15
45
  export interface ImQQBotConfig {
16
46
  /** QQ Bot AppID */
17
47
  appId: string;
@@ -41,6 +71,8 @@ export interface ImQQBotConfig {
41
71
  maxQueue: number;
42
72
  /** 处理超时(ms),超时中断当前 LLM 调用 */
43
73
  processingTimeoutMs: number;
74
+ /** 待答问题超时(ms),超时自动拒绝并提示(ask_user_question) */
75
+ askTimeoutMs: number;
44
76
  /** 群历史缓冲条数 */
45
77
  historyLimit: number;
46
78
  /** 访问控制 */
@@ -49,6 +81,12 @@ export interface ImQQBotConfig {
49
81
  showToolResults: boolean;
50
82
  /** 调试模式 */
51
83
  debug: boolean;
84
+ /** 富媒体理解(图片/视频) */
85
+ media: MediaConfig;
86
+ /** 视觉理解(qqbot_describe_image 工具,走 dsh llm + attachments) */
87
+ vision: VisionConfig;
88
+ /** 附件发送(qqbot_send_file 工具) */
89
+ sendFile: SendFileConfig;
52
90
  }
53
91
  export declare const ConfigSchema: Schema<ImQQBotConfig>;
54
92
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,MAAM,MAAM,0BAA0B,CAAC;AAE9C,MAAM,WAAW,mBAAmB;IAClC,eAAe;IACf,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;IAC3C,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa;IACb,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;IAC7C,0BAA0B;IAC1B,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,mBAAmB;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,uBAAuB;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,cAAc,EAAE,OAAO,CAAC;IACxB,yBAAyB;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yBAAyB;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,wBAAwB;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,2BAA2B;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,cAAc;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW;IACX,MAAM,EAAE,mBAAmB,CAAC;IAC5B,6BAA6B;IAC7B,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW;IACX,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,aAAa,CA6B7C,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,MAAM,MAAM,0BAA0B,CAAC;AAE9C,MAAM,WAAW,mBAAmB;IAClC,eAAe;IACf,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;IAC3C,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,aAAa;IACb,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;IAC7C,0BAA0B;IAC1B,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,gCAAgC;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,wCAAwC;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,8DAA8D;IAC9D,aAAa,EAAE,OAAO,CAAC;IACvB,2CAA2C;IAC3C,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,mBAAmB;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,uBAAuB;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,cAAc,EAAE,OAAO,CAAC;IACxB,yBAAyB;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yBAAyB;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,wBAAwB;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,2BAA2B;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,8CAA8C;IAC9C,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW;IACX,MAAM,EAAE,mBAAmB,CAAC;IAC5B,6BAA6B;IAC7B,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW;IACX,KAAK,EAAE,OAAO,CAAC;IACf,mBAAmB;IACnB,KAAK,EAAE,WAAW,CAAC;IACnB,4DAA4D;IAC5D,MAAM,EAAE,YAAY,CAAC;IACrB,+BAA+B;IAC/B,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,aAAa,CA+D7C,CAAC"}
package/dist/config.js CHANGED
@@ -16,7 +16,8 @@ export const ConfigSchema = Schema.object({
16
16
  streaming: Schema.boolean().default(true).description('是否启用流式输出(群聊始终不启用)'),
17
17
  sessionIdleTimeout: Schema.number().default(30 * 60 * 1000).description('会话闲置超时(ms)'),
18
18
  maxQueue: Schema.number().default(20).description('并发队列最大长度'),
19
- processingTimeoutMs: Schema.number().default(120000).description('处理超时(ms)'),
19
+ processingTimeoutMs: Schema.number().default(30 * 60 * 1000).description('处理超时(ms),超时中断当前 LLM 调用'),
20
+ askTimeoutMs: Schema.number().default(5 * 60 * 1000).description('待答问题超时(ms),超时自动拒绝并提示'),
20
21
  historyLimit: Schema.number().default(10).description('群历史缓冲条数'),
21
22
  access: Schema.object({
22
23
  c2cMode: Schema.union(['open', 'allowlist', 'disabled']).default('open').description('C2C访问模式'),
@@ -31,5 +32,38 @@ export const ConfigSchema = Schema.object({
31
32
  }).description('访问控制'),
32
33
  showToolResults: Schema.boolean().default(false).description('是否展示工具调用成功结果(错误始终展示)'),
33
34
  debug: Schema.boolean().default(false),
35
+ media: Schema.object({
36
+ enabled: Schema.boolean().default(true).description('是否启用富媒体理解(图片/视频下载 + 工具分析)'),
37
+ maxMB: Schema.number().default(200).description('富媒体下载大小上限(MB)'),
38
+ ttlHours: Schema.number().default(24).description('富媒体存活时长(小时),0=永不过期'),
39
+ }).default({
40
+ enabled: true,
41
+ maxMB: 200,
42
+ ttlHours: 24,
43
+ }).description('富媒体理解配置'),
44
+ vision: Schema.object({
45
+ enabled: Schema.boolean().default(false).description('是否启用视觉理解(qqbot_describe_image 工具)'),
46
+ provider: Schema.string().default('').description('视觉模型 provider(dsh 注册的 llm adapter,如 pi-ai)'),
47
+ model: Schema.string().default('').description('视觉模型 id(如 qwen-vl-max)'),
48
+ defaultPrompt: Schema.string().default('Describe this image in detail.').description('默认描述 prompt'),
49
+ maxBytes: Schema.number().default(10 * 1024 * 1024).description('图片字节上限'),
50
+ maxTokens: Schema.number().default(1024).description('输出 token 上限'),
51
+ timeoutMs: Schema.number().default(120000).description('视觉调用超时(ms)'),
52
+ }).default({
53
+ enabled: false,
54
+ provider: '',
55
+ model: '',
56
+ defaultPrompt: 'Describe this image in detail.',
57
+ maxBytes: 10 * 1024 * 1024,
58
+ maxTokens: 1024,
59
+ timeoutMs: 120000,
60
+ }).description('视觉理解配置'),
61
+ sendFile: Schema.object({
62
+ restrictPaths: Schema.boolean().default(true).description('是否启用路径白名单(默认 true,仅允许 media + cwd + extraRoots)'),
63
+ extraRoots: Schema.array(Schema.string()).default([]).description('额外允许访问的根目录'),
64
+ }).default({
65
+ restrictPaths: true,
66
+ extraRoots: [],
67
+ }).description('附件发送工具配置'),
34
68
  });
35
69
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * QQ 文本回复 → 结构化答案(纯函数,便于单测)。
3
+ *
4
+ * 逐题解析:一次解析一个问题的答案。
5
+ */
6
+ import type { UserQuestion, UserQuestionAnswer } from './question-channel.ts';
7
+ /** 把用户对单个问题的文本回复解析为答案 */
8
+ export declare function parseAnswer(question: UserQuestion, text: string): UserQuestionAnswer;
9
+ //# sourceMappingURL=answer-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"answer-parser.d.ts","sourceRoot":"","sources":["../../src/features/answer-parser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE9E,yBAAyB;AACzB,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,CAmBpF"}
@@ -0,0 +1,23 @@
1
+ /** 把用户对单个问题的文本回复解析为答案 */
2
+ export function parseAnswer(question, text) {
3
+ const opts = question.options ?? [];
4
+ if (opts.length > 0) {
5
+ // 纯数字/分隔符 → 按编号选择
6
+ if (/^[\d\s,,、;;./]+$/.test(text)) {
7
+ const nums = [...text.matchAll(/\d+/g)]
8
+ .map((m) => parseInt(m[0], 10))
9
+ .filter((n) => n >= 1 && n <= opts.length);
10
+ if (nums.length > 0) {
11
+ const picked = question.multiSelect ? nums : nums.slice(0, 1);
12
+ const selected = [...new Set(picked.map((n) => opts[n - 1]?.label).filter((l) => typeof l === 'string'))];
13
+ return { id: question.id, selected };
14
+ }
15
+ }
16
+ // 文本与某个选项 label 完全一致(忽略大小写)
17
+ const exact = opts.find((o) => o.label.toLowerCase() === text.toLowerCase());
18
+ if (exact)
19
+ return { id: question.id, selected: [exact.label] };
20
+ }
21
+ return { id: question.id, selected: [], custom: text };
22
+ }
23
+ //# sourceMappingURL=answer-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"answer-parser.js","sourceRoot":"","sources":["../../src/features/answer-parser.ts"],"names":[],"mappings":"AAOA,yBAAyB;AACzB,MAAM,UAAU,WAAW,CAAC,QAAsB,EAAE,IAAY;IAC9D,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC;IACpC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,kBAAkB;QAClB,IAAI,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;iBACpC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;iBAC9B,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC;YAC7C,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpB,MAAM,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBAC9D,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC;gBACvH,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC;YACvC,CAAC;QACH,CAAC;QACD,4BAA4B;QAC5B,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QAC7E,IAAI,KAAK;YAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;IACjE,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AACzD,CAAC"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * ApprovalChannel — 把 dsh 的审批 seam(`approval/request` waterfall)桥接到 QQ。
3
+ *
4
+ * 接入方式:监听 `approval/request` waterfall,按 `request.agent.id` 路由到 QQ
5
+ * 会话;非 QQ 会话委托链上其他 answerer(`next()`),多通道(Web/TUI)共存。
6
+ *
7
+ * 每个会话同一时间最多一个 pending 审批(审批是阻塞式的);outcome 是 dsh
8
+ * 协议的闭合集合,无 allow-always,只提供「允许一次 / 拒绝」两个按钮。
9
+ */
10
+ import type { InteractionEvent } from '@tencent-connect/qqbot-nodejs';
11
+ import type { ChatScope, Logger, ReplyTarget } from '../types.ts';
12
+ export type ApprovalOutcome = 'allowed-once' | 'rejected' | 'cancelled' | 'unavailable';
13
+ /** session log 中 tool/call 事件的最小结构(commandOf 回显命令用) */
14
+ interface ApprovalSessionEvent {
15
+ type: string;
16
+ data?: {
17
+ callId?: unknown;
18
+ arguments?: unknown;
19
+ [key: string]: unknown;
20
+ };
21
+ }
22
+ export interface ApprovalRequest {
23
+ agent: {
24
+ id: string;
25
+ session: {
26
+ events: ApprovalSessionEvent[];
27
+ };
28
+ };
29
+ toolName: string;
30
+ /** 关联的 tool/call 事件 id,用于从 session log 找回被 gate 的命令 */
31
+ callId?: string;
32
+ /** asker 的人类可读解释 */
33
+ reason?: string;
34
+ signal?: AbortSignal;
35
+ }
36
+ export interface ApprovalSessionRecordLike {
37
+ sessionKey: string;
38
+ scope: ChatScope;
39
+ replyTarget: ReplyTarget;
40
+ }
41
+ export interface ApprovalChannelManagerLike {
42
+ findBySessionId(sessionId: string): ApprovalSessionRecordLike | undefined;
43
+ getSessionRecord(scope: ChatScope, peerId: string): ApprovalSessionRecordLike | undefined;
44
+ }
45
+ export interface ApprovalChannelSenderLike {
46
+ sendMarkdown(target: ReplyTarget, content: string, opts?: {
47
+ keyboard?: unknown;
48
+ }): Promise<unknown>;
49
+ }
50
+ /** approval/request waterfall 的 answerer handler */
51
+ export interface ApprovalChannelContext {
52
+ get(name: string): unknown;
53
+ on(event: string, handler: (...args: unknown[]) => unknown, options?: {
54
+ prepend?: boolean;
55
+ }): void;
56
+ }
57
+ export declare class ApprovalChannel {
58
+ private readonly pending;
59
+ private readonly manager;
60
+ private readonly sender;
61
+ private readonly logger;
62
+ constructor(manager: ApprovalChannelManagerLike, sender: ApprovalChannelSenderLike, logger: Logger);
63
+ /**
64
+ * 注册 approval/request waterfall answerer。approval 服务未挂载时优雅禁用。
65
+ */
66
+ install(ctx: ApprovalChannelContext): void;
67
+ /** 会话回收时清理其 pending 审批,避免 Promise 悬挂 */
68
+ cancelPending(sessionKey: string): void;
69
+ /** 按钮点击决策:解码 button_data,settle pending 审批 */
70
+ handleInteraction(event: InteractionEvent): boolean;
71
+ /** 走 QQ 通道:发审批消息 + 挂起 Promise,等待用户决策 */
72
+ private park;
73
+ private removeAbort;
74
+ }
75
+ export {};
76
+ //# sourceMappingURL=approval-channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-channel.d.ts","sourceRoot":"","sources":["../../src/features/approval-channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAMlE,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,UAAU,GAAG,WAAW,GAAG,aAAa,CAAC;AAExF,uDAAuD;AACvD,UAAU,oBAAoB;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;CAC1E;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE;QACL,EAAE,EAAE,MAAM,CAAC;QACX,OAAO,EAAE;YAAE,MAAM,EAAE,oBAAoB,EAAE,CAAA;SAAE,CAAC;KAC7C,CAAC;IACF,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oBAAoB;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAID,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,SAAS,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,MAAM,WAAW,0BAA0B;IACzC,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,yBAAyB,GAAG,SAAS,CAAC;IAC1E,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,yBAAyB,GAAG,SAAS,CAAC;CAC3F;AAED,MAAM,WAAW,yBAAyB;IACxC,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACrG;AAED,oDAAoD;AACpD,MAAM,WAAW,sBAAsB;IACrC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,EAAE,CACA,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EACxC,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAC9B,IAAI,CAAC;CACT;AAyCD,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA6B;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAG9B,OAAO,EAAE,0BAA0B,EACnC,MAAM,EAAE,yBAAyB,EACjC,MAAM,EAAE,MAAM;IAOhB;;OAEG;IACI,OAAO,CAAC,GAAG,EAAE,sBAAsB,GAAG,IAAI;IAuBjD,wCAAwC;IACjC,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAQ9C,8CAA8C;IACvC,iBAAiB,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO;IAoB1D,wCAAwC;YAC1B,IAAI;IAkClB,OAAO,CAAC,WAAW;CAKpB"}
@@ -0,0 +1,142 @@
1
+ import { decodeButtonData } from "./button-utils.js";
2
+ import { buildApprovalKeyboard, buildApprovalText } from "./approval-renderer.js";
3
+ const COMMAND_CLIP = 500;
4
+ /**
5
+ * 从 session log 按 callId 找回被 gate 的命令(审批请求本身不重复携带 arguments)。
6
+ * 对齐 TUI/web 的 `commandOf` 语义。
7
+ */
8
+ function commandOf(req) {
9
+ if (req.callId === undefined)
10
+ return undefined;
11
+ const events = req.agent.session.events;
12
+ for (let i = events.length - 1; i >= 0; i -= 1) {
13
+ const event = events[i];
14
+ if (!event || event.type !== 'tool/call')
15
+ continue;
16
+ const data = event.data;
17
+ if (String(data?.callId) !== String(req.callId))
18
+ continue;
19
+ const raw = data?.arguments;
20
+ if (typeof raw !== 'string')
21
+ return undefined;
22
+ try {
23
+ const parsed = JSON.parse(raw);
24
+ if (parsed !== null && typeof parsed === 'object' && 'command' in parsed) {
25
+ const command = parsed.command;
26
+ if (typeof command === 'string')
27
+ return command;
28
+ }
29
+ }
30
+ catch {
31
+ // 非 JSON,回退到原始字符串
32
+ }
33
+ return raw.length <= COMMAND_CLIP ? raw : `${raw.slice(0, COMMAND_CLIP)}…`;
34
+ }
35
+ return undefined;
36
+ }
37
+ export class ApprovalChannel {
38
+ pending = new Map();
39
+ manager;
40
+ sender;
41
+ logger;
42
+ constructor(manager, sender, logger) {
43
+ this.manager = manager;
44
+ this.sender = sender;
45
+ this.logger = logger;
46
+ }
47
+ /**
48
+ * 注册 approval/request waterfall answerer。approval 服务未挂载时优雅禁用。
49
+ */
50
+ install(ctx) {
51
+ let hasApproval = false;
52
+ try {
53
+ hasApproval = ctx.get('approval') !== undefined;
54
+ }
55
+ catch {
56
+ hasApproval = false;
57
+ }
58
+ if (!hasApproval) {
59
+ this.logger.debug('im-qqbot: approval service unavailable — approval channel disabled');
60
+ return;
61
+ }
62
+ const self = this;
63
+ ctx.on('approval/request', (req, next) => {
64
+ const request = req;
65
+ const nextFn = next;
66
+ const record = self.manager.findBySessionId(request.agent.id);
67
+ if (record)
68
+ return self.park(record, request);
69
+ return nextFn();
70
+ }, { prepend: true });
71
+ this.logger.info('im-qqbot: QQ approval channel installed');
72
+ }
73
+ /** 会话回收时清理其 pending 审批,避免 Promise 悬挂 */
74
+ cancelPending(sessionKey) {
75
+ const entry = this.pending.get(sessionKey);
76
+ if (!entry)
77
+ return;
78
+ this.pending.delete(sessionKey);
79
+ this.removeAbort(entry);
80
+ entry.resolve('cancelled');
81
+ }
82
+ /** 按钮点击决策:解码 button_data,settle pending 审批 */
83
+ handleInteraction(event) {
84
+ const peerId = event.group_openid ?? event.user_openid;
85
+ if (!peerId)
86
+ return false;
87
+ const scope = event.group_openid ? 'group' : 'c2c';
88
+ const record = this.manager.getSessionRecord(scope, peerId);
89
+ if (!record)
90
+ return false;
91
+ const entry = this.pending.get(record.sessionKey);
92
+ if (!entry)
93
+ return false;
94
+ const data = event.data?.resolved?.button_data;
95
+ if (!data)
96
+ return false;
97
+ const button = decodeButtonData(data);
98
+ if (!button || button.t !== 'approval')
99
+ return false;
100
+ this.pending.delete(record.sessionKey);
101
+ this.removeAbort(entry);
102
+ entry.resolve(button.d === 'allow' ? 'allowed-once' : 'rejected');
103
+ return true;
104
+ }
105
+ /** 走 QQ 通道:发审批消息 + 挂起 Promise,等待用户决策 */
106
+ async park(record, request) {
107
+ const key = record.sessionKey;
108
+ // 会话回收/取消可能在发送前发生:signal 已 abort 则直接失败
109
+ if (request.signal?.aborted)
110
+ return 'cancelled';
111
+ const command = commandOf(request);
112
+ try {
113
+ await this.sender.sendMarkdown(record.replyTarget, buildApprovalText(request, command), { keyboard: buildApprovalKeyboard() });
114
+ }
115
+ catch (err) {
116
+ const msg = err instanceof Error ? err.message : String(err);
117
+ this.logger.error(`im-qqbot: approval send failed key=${key}: ${msg}`);
118
+ return 'unavailable'; // 无法呈现审批,fail-closed
119
+ }
120
+ return new Promise((resolve) => {
121
+ const entry = { record, request, resolve };
122
+ if (request.signal) {
123
+ const onAbort = () => {
124
+ if (this.pending.get(key) !== entry)
125
+ return;
126
+ this.pending.delete(key);
127
+ resolve('cancelled');
128
+ };
129
+ entry.onAbort = onAbort;
130
+ request.signal.addEventListener('abort', onAbort, { once: true });
131
+ }
132
+ this.pending.set(key, entry);
133
+ this.logger.info(`im-qqbot: approval sent to QQ, waiting for decision key=${key}`);
134
+ });
135
+ }
136
+ removeAbort(entry) {
137
+ if (entry.onAbort && entry.request.signal) {
138
+ entry.request.signal.removeEventListener('abort', entry.onAbort);
139
+ }
140
+ }
141
+ }
142
+ //# sourceMappingURL=approval-channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-channel.js","sourceRoot":"","sources":["../../src/features/approval-channel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * QQ 审批的文本渲染与按钮键盘构建(纯函数,便于单测)。
3
+ *
4
+ * 单次审批:一次只渲染一个审批请求(每个会话同时只有一个 pending 审批)。
5
+ */
6
+ import type { InlineKeyboard } from '@tencent-connect/qqbot-nodejs';
7
+ import type { ApprovalRequest } from './approval-channel.ts';
8
+ /** 把审批请求渲染成 QQ 文本(含被 gate 的命令回显) */
9
+ export declare function buildApprovalText(request: ApprovalRequest, command?: string): string;
10
+ /**
11
+ * 构建审批键盘:两按钮(允许一次 / 拒绝)。
12
+ *
13
+ * dsh 审批协议 outcome 是闭合集合(allowed-once/rejected/cancelled/unavailable),
14
+ * 无 allow-always,因此只提供两个按钮;`group_id` 相同实现单选互斥变灰。
15
+ * button_data 编码 `{"t":"approval","d":"allow"|"deny"}`,由分发器解码。
16
+ */
17
+ export declare function buildApprovalKeyboard(): InlineKeyboard;
18
+ //# sourceMappingURL=approval-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-renderer.d.ts","sourceRoot":"","sources":["../../src/features/approval-renderer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D,oCAAoC;AACpC,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,eAAe,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAgBpF;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,IAAI,cAAc,CAwBtD"}
@@ -0,0 +1,52 @@
1
+ import { encodeButtonData } from "./button-utils.js";
2
+ /** 把审批请求渲染成 QQ 文本(含被 gate 的命令回显) */
3
+ export function buildApprovalText(request, command) {
4
+ const lines = ['🔐 **执行审批**', ''];
5
+ lines.push(`🔧 工具: ${request.toolName}`);
6
+ if (command) {
7
+ lines.push('');
8
+ lines.push('```');
9
+ lines.push(command);
10
+ lines.push('```');
11
+ }
12
+ if (request.reason) {
13
+ lines.push('');
14
+ lines.push(`📝 ${request.reason}`);
15
+ }
16
+ lines.push('');
17
+ lines.push('> 👇 点击下方按钮确认是否允许执行');
18
+ return lines.join('\n');
19
+ }
20
+ /**
21
+ * 构建审批键盘:两按钮(允许一次 / 拒绝)。
22
+ *
23
+ * dsh 审批协议 outcome 是闭合集合(allowed-once/rejected/cancelled/unavailable),
24
+ * 无 allow-always,因此只提供两个按钮;`group_id` 相同实现单选互斥变灰。
25
+ * button_data 编码 `{"t":"approval","d":"allow"|"deny"}`,由分发器解码。
26
+ */
27
+ export function buildApprovalKeyboard() {
28
+ const allow = {
29
+ id: 'approval-allow',
30
+ render_data: { label: '✅ 允许一次', visited_label: '✓ 已允许', style: 1 },
31
+ action: {
32
+ type: 1,
33
+ permission: { type: 2 },
34
+ click_limit: 1,
35
+ data: encodeButtonData({ t: 'approval', d: 'allow' }),
36
+ },
37
+ group_id: 'approval',
38
+ };
39
+ const deny = {
40
+ id: 'approval-deny',
41
+ render_data: { label: '❌ 拒绝', visited_label: '✓ 已拒绝', style: 0 },
42
+ action: {
43
+ type: 1,
44
+ permission: { type: 2 },
45
+ click_limit: 1,
46
+ data: encodeButtonData({ t: 'approval', d: 'deny' }),
47
+ },
48
+ group_id: 'approval',
49
+ };
50
+ return { content: { rows: [{ buttons: [allow, deny] }] } };
51
+ }
52
+ //# sourceMappingURL=approval-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-renderer.js","sourceRoot":"","sources":["../../src/features/approval-renderer.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,oCAAoC;AACpC,MAAM,UAAU,iBAAiB,CAAC,OAAwB,EAAE,OAAgB;IAC1E,MAAM,KAAK,GAAa,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IAC5C,KAAK,CAAC,IAAI,CAAC,UAAU,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IACzC,IAAI,OAAO,EAAE,CAAC;QACZ,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAClB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpB,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACrC,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IAClC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB;IACnC,MAAM,KAAK,GAAG;QACZ,EAAE,EAAE,gBAAgB;QACpB,WAAW,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE;QAClE,MAAM,EAAE;YACN,IAAI,EAAE,CAAC;YACP,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE;YACvB,WAAW,EAAE,CAAC;YACd,IAAI,EAAE,gBAAgB,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC;SACtD;QACD,QAAQ,EAAE,UAAU;KACrB,CAAC;IACF,MAAM,IAAI,GAAG;QACX,EAAE,EAAE,eAAe;QACnB,WAAW,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE;QAChE,MAAM,EAAE;YACN,IAAI,EAAE,CAAC;YACP,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE;YACvB,WAAW,EAAE,CAAC;YACd,IAAI,EAAE,gBAAgB,CAAC,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;SACrD;QACD,QAAQ,EAAE,UAAU;KACrB,CAAC;IACF,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 按钮渲染与 button_data 编码的共享工具(QuestionChannel / ApprovalChannel 共用)。
3
+ *
4
+ * 统一 button_data 编码:所有回调按钮的 data 都带顶层 `t` 判别字段,
5
+ * 供 `dispatchInteraction` 分发器统一路由到对应 channel,避免多通道
6
+ * 各自 try 导致误判。
7
+ */
8
+ import type { InlineKeyboard } from '@tencent-connect/qqbot-nodejs';
9
+ /** QQ 按钮 label 有长度限制,超长截断(正文保留完整文本;多列布局下阈值更短) */
10
+ export declare const BUTTON_LABEL_MAX = 10;
11
+ /** 单选按钮每行个数(多列布局,减少选项多时的刷屏) */
12
+ export declare const BUTTONS_PER_ROW = 2;
13
+ /** 截断按钮 label,超长补省略号 */
14
+ export declare function buttonLabel(text: string | undefined): string;
15
+ /** 问答按钮:q=问题 id,i=选项下标 */
16
+ export interface QuestionButtonData {
17
+ t: 'question';
18
+ q: string;
19
+ i: number;
20
+ }
21
+ /** 审批按钮:d=决策 */
22
+ export interface ApprovalButtonData {
23
+ t: 'approval';
24
+ d: 'allow' | 'deny';
25
+ }
26
+ export type ButtonData = QuestionButtonData | ApprovalButtonData;
27
+ /** 编码 button_data(JSON 字符串) */
28
+ export declare function encodeButtonData(data: ButtonData): string;
29
+ /** 解码 button_data;非法 JSON 或缺少合法 `t` 时返回 undefined */
30
+ export declare function decodeButtonData(raw: string): ButtonData | undefined;
31
+ /** 构建内联键盘的行数组(供 buildKeyboard/buildApprovalKeyboard 复用类型) */
32
+ export type KeyboardRows = InlineKeyboard['content']['rows'];
33
+ //# sourceMappingURL=button-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button-utils.d.ts","sourceRoot":"","sources":["../../src/features/button-utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAEpE,iDAAiD;AACjD,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,+BAA+B;AAC/B,eAAO,MAAM,eAAe,IAAI,CAAC;AAEjC,wBAAwB;AACxB,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAG5D;AAID,0BAA0B;AAC1B,MAAM,WAAW,kBAAkB;IACjC,CAAC,EAAE,UAAU,CAAC;IACd,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,gBAAgB;AAChB,MAAM,WAAW,kBAAkB;IACjC,CAAC,EAAE,UAAU,CAAC;IACd,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,UAAU,GAAG,kBAAkB,GAAG,kBAAkB,CAAC;AAEjE,+BAA+B;AAC/B,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAEzD;AAED,qDAAqD;AACrD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAUpE;AAED,6DAA6D;AAC7D,MAAM,MAAM,YAAY,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC"}
@@ -0,0 +1,27 @@
1
+ /** QQ 按钮 label 有长度限制,超长截断(正文保留完整文本;多列布局下阈值更短) */
2
+ export const BUTTON_LABEL_MAX = 10;
3
+ /** 单选按钮每行个数(多列布局,减少选项多时的刷屏) */
4
+ export const BUTTONS_PER_ROW = 2;
5
+ /** 截断按钮 label,超长补省略号 */
6
+ export function buttonLabel(text) {
7
+ const s = String(text ?? '').trim();
8
+ return s.length > BUTTON_LABEL_MAX ? s.slice(0, BUTTON_LABEL_MAX - 1) + '…' : s;
9
+ }
10
+ /** 编码 button_data(JSON 字符串) */
11
+ export function encodeButtonData(data) {
12
+ return JSON.stringify(data);
13
+ }
14
+ /** 解码 button_data;非法 JSON 或缺少合法 `t` 时返回 undefined */
15
+ export function decodeButtonData(raw) {
16
+ try {
17
+ const parsed = JSON.parse(raw);
18
+ if (parsed !== null && typeof parsed === 'object' && (parsed.t === 'question' || parsed.t === 'approval')) {
19
+ return parsed;
20
+ }
21
+ }
22
+ catch {
23
+ // fall through
24
+ }
25
+ return undefined;
26
+ }
27
+ //# sourceMappingURL=button-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button-utils.js","sourceRoot":"","sources":["../../src/features/button-utils.ts"],"names":[],"mappings":"AASA,iDAAiD;AACjD,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAEnC,+BAA+B;AAC/B,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC;AAEjC,wBAAwB;AACxB,MAAM,UAAU,WAAW,CAAC,IAAwB;IAClD,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACpC,OAAO,CAAC,CAAC,MAAM,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAClF,CAAC;AAmBD,+BAA+B;AAC/B,MAAM,UAAU,gBAAgB,CAAC,IAAgB;IAC/C,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAwB,CAAC;QACtD,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,UAAU,IAAI,MAAM,CAAC,CAAC,KAAK,UAAU,CAAC,EAAE,CAAC;YAC1G,OAAO,MAAoB,CAAC;QAC9B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,eAAe;IACjB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"history-store.d.ts","sourceRoot":"","sources":["../../src/features/history-store.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAIlE,mBAAmB;AACnB,wBAAgB,eAAe,IAAI,YAAY,CAG9C;AAED,mDAAmD;AACnD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED,gCAAgC;AAChC,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAEtE"}
1
+ {"version":3,"file":"history-store.d.ts","sourceRoot":"","sources":["../../src/features/history-store.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAIlE,mBAAmB;AACnB,wBAAgB,eAAe,IAAI,YAAY,CAG9C;AAED,mDAAmD;AACnD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED,gCAAgC;AAChC,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAEtE"}
@@ -3,7 +3,6 @@
3
3
  *
4
4
  * historyBuffer 中间件需要一个跨调用可访问的 HistoryStore 实例,
5
5
  * 以便在回复完成后清空某群的历史缓存(避免下次 @ 时重复组包)。
6
- * 对齐 openclaw-qqbot 的 features/history-store。
7
6
  */
8
7
  import { MemoryHistoryStore } from '@tencent-connect/qqbot-nodejs';
9
8
  let _store = null;