@tencent-connect/dsh-qqbot 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.orange-ci.yml +2 -68
- package/CHANGELOG.md +56 -0
- package/CHANGELOG_EN.md +95 -0
- package/README.md +119 -18
- package/README_EN.md +107 -17
- package/cordis.patch.yml +1 -1
- package/cordis.yml +46 -3
- package/dist/commands/help.d.ts +6 -7
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +23 -7
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/index.d.ts +7 -5
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +12 -12
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/misc.d.ts +10 -8
- package/dist/commands/misc.d.ts.map +1 -1
- package/dist/commands/misc.js +45 -8
- package/dist/commands/misc.js.map +1 -1
- package/dist/commands/model.d.ts +3 -4
- package/dist/commands/model.d.ts.map +1 -1
- package/dist/commands/model.js +8 -7
- package/dist/commands/model.js.map +1 -1
- package/dist/commands/preset.d.ts +8 -0
- package/dist/commands/preset.d.ts.map +1 -0
- package/dist/commands/preset.js +52 -0
- package/dist/commands/preset.js.map +1 -0
- package/dist/commands/session.d.ts +11 -7
- package/dist/commands/session.d.ts.map +1 -1
- package/dist/commands/session.js +29 -13
- package/dist/commands/session.js.map +1 -1
- package/dist/commands/status.d.ts +3 -4
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +2 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/types.d.ts +9 -2
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/config.d.ts +40 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +45 -9
- package/dist/config.js.map +1 -1
- package/dist/features/answer-parser.d.ts +9 -0
- package/dist/features/answer-parser.d.ts.map +1 -0
- package/dist/features/answer-parser.js +23 -0
- package/dist/features/answer-parser.js.map +1 -0
- package/dist/features/approval-channel.d.ts +76 -0
- package/dist/features/approval-channel.d.ts.map +1 -0
- package/dist/features/approval-channel.js +142 -0
- package/dist/features/approval-channel.js.map +1 -0
- package/dist/features/approval-renderer.d.ts +18 -0
- package/dist/features/approval-renderer.d.ts.map +1 -0
- package/dist/features/approval-renderer.js +52 -0
- package/dist/features/approval-renderer.js.map +1 -0
- package/dist/features/button-utils.d.ts +33 -0
- package/dist/features/button-utils.d.ts.map +1 -0
- package/dist/features/button-utils.js +27 -0
- package/dist/features/button-utils.js.map +1 -0
- package/dist/features/history-store.d.ts.map +1 -1
- package/dist/features/history-store.js +0 -1
- package/dist/features/history-store.js.map +1 -1
- package/dist/features/question-channel.d.ts +96 -0
- package/dist/features/question-channel.d.ts.map +1 -0
- package/dist/features/question-channel.js +234 -0
- package/dist/features/question-channel.js.map +1 -0
- package/dist/features/question-renderer.d.ts +20 -0
- package/dist/features/question-renderer.d.ts.map +1 -0
- package/dist/features/question-renderer.js +70 -0
- package/dist/features/question-renderer.js.map +1 -0
- package/dist/gateway/bootstrap.d.ts +3 -3
- package/dist/gateway/bootstrap.d.ts.map +1 -1
- package/dist/gateway/bootstrap.js +84 -8
- package/dist/gateway/bootstrap.js.map +1 -1
- package/dist/gateway/index.d.ts +2 -2
- package/dist/gateway/index.js +2 -2
- package/dist/gateway/middleware-setup.d.ts +3 -3
- package/dist/gateway/middleware-setup.d.ts.map +1 -1
- package/dist/gateway/middleware-setup.js +9 -7
- package/dist/gateway/middleware-setup.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -25
- package/dist/index.js.map +1 -1
- package/dist/media/media-cleaner.d.ts +19 -0
- package/dist/media/media-cleaner.d.ts.map +1 -0
- package/dist/media/media-cleaner.js +81 -0
- package/dist/media/media-cleaner.js.map +1 -0
- package/dist/media/send-file-tool.d.ts +35 -0
- package/dist/media/send-file-tool.d.ts.map +1 -0
- package/dist/media/send-file-tool.js +150 -0
- package/dist/media/send-file-tool.js.map +1 -0
- package/dist/media/vision-tool.d.ts +16 -0
- package/dist/media/vision-tool.d.ts.map +1 -0
- package/dist/media/vision-tool.js +241 -0
- package/dist/media/vision-tool.js.map +1 -0
- package/dist/middleware/attachment.d.ts +3 -3
- package/dist/middleware/attachment.d.ts.map +1 -1
- package/dist/middleware/attachment.js +17 -4
- package/dist/middleware/attachment.js.map +1 -1
- package/dist/middleware/question-answer.d.ts +10 -0
- package/dist/middleware/question-answer.d.ts.map +1 -0
- package/dist/middleware/question-answer.js +19 -0
- package/dist/middleware/question-answer.js.map +1 -0
- package/dist/model/index.d.ts +4 -4
- package/dist/model/index.js +3 -3
- package/dist/model/model-resolver.d.ts +27 -13
- package/dist/model/model-resolver.d.ts.map +1 -1
- package/dist/model/model-resolver.js +82 -27
- package/dist/model/model-resolver.js.map +1 -1
- package/dist/model/prefs-store.d.ts +6 -1
- package/dist/model/prefs-store.d.ts.map +1 -1
- package/dist/model/prefs-store.js +25 -1
- package/dist/model/prefs-store.js.map +1 -1
- package/dist/model/settings-reader.d.ts +1 -1
- package/dist/session/idle-evictor.d.ts +2 -2
- package/dist/session/idle-evictor.d.ts.map +1 -1
- package/dist/session/idle-evictor.js +1 -1
- package/dist/session/idle-evictor.js.map +1 -1
- package/dist/session/index.d.ts +3 -3
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +2 -2
- package/dist/session/session-manager.d.ts +67 -9
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +216 -20
- package/dist/session/session-manager.js.map +1 -1
- package/dist/session/types.d.ts +47 -1
- package/dist/session/types.d.ts.map +1 -1
- package/dist/setup/bind-routes.d.ts +78 -0
- package/dist/setup/bind-routes.d.ts.map +1 -0
- package/dist/setup/bind-routes.js +311 -0
- package/dist/setup/bind-routes.js.map +1 -0
- package/dist/setup/binding-service.d.ts +86 -0
- package/dist/setup/binding-service.d.ts.map +1 -0
- package/dist/setup/binding-service.js +175 -0
- package/dist/setup/binding-service.js.map +1 -0
- package/dist/setup/index.d.ts +50 -0
- package/dist/setup/index.d.ts.map +1 -0
- package/dist/setup/index.js +98 -0
- package/dist/setup/index.js.map +1 -0
- package/dist/setup/persist.d.ts +41 -0
- package/dist/setup/persist.d.ts.map +1 -0
- package/dist/setup/persist.js +201 -0
- package/dist/setup/persist.js.map +1 -0
- package/dist/setup/presenters.d.ts +40 -0
- package/dist/setup/presenters.d.ts.map +1 -0
- package/dist/setup/presenters.js +84 -0
- package/dist/setup/presenters.js.map +1 -0
- package/dist/setup/qr-svg.d.ts +8 -0
- package/dist/setup/qr-svg.d.ts.map +1 -0
- package/dist/setup/qr-svg.js +20 -0
- package/dist/setup/qr-svg.js.map +1 -0
- package/dist/shared/bind-protocol.d.ts +42 -0
- package/dist/shared/bind-protocol.d.ts.map +1 -0
- package/dist/shared/bind-protocol.js +19 -0
- package/dist/shared/bind-protocol.js.map +1 -0
- package/dist/shared/index.d.ts +3 -3
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +3 -3
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/send-helper.js +1 -1
- package/dist/shared/utils.d.ts +10 -3
- package/dist/shared/utils.d.ts.map +1 -1
- package/dist/shared/utils.js +47 -6
- package/dist/shared/utils.js.map +1 -1
- package/dist/transport/attachment.d.ts +18 -7
- package/dist/transport/attachment.d.ts.map +1 -1
- package/dist/transport/attachment.js +77 -30
- package/dist/transport/attachment.js.map +1 -1
- package/dist/transport/inbound.d.ts +5 -4
- package/dist/transport/inbound.d.ts.map +1 -1
- package/dist/transport/inbound.js +120 -80
- package/dist/transport/inbound.js.map +1 -1
- package/dist/transport/index.d.ts +4 -4
- package/dist/transport/index.js +4 -4
- package/dist/transport/msgid-cache.d.ts +23 -0
- package/dist/transport/msgid-cache.d.ts.map +1 -0
- package/dist/transport/msgid-cache.js +57 -0
- package/dist/transport/msgid-cache.js.map +1 -0
- package/dist/transport/outbound-buffer.d.ts +9 -6
- package/dist/transport/outbound-buffer.d.ts.map +1 -1
- package/dist/transport/outbound-buffer.js +5 -5
- package/dist/transport/outbound-buffer.js.map +1 -1
- package/dist/transport/outbound.d.ts +9 -9
- package/dist/transport/outbound.d.ts.map +1 -1
- package/dist/transport/outbound.js +7 -7
- package/dist/transport/outbound.js.map +1 -1
- package/dist/transport/reply-limiter.d.ts +31 -0
- package/dist/transport/reply-limiter.d.ts.map +1 -0
- package/dist/transport/reply-limiter.js +50 -0
- package/dist/transport/reply-limiter.js.map +1 -0
- package/dist/transport/reply-target.d.ts +29 -0
- package/dist/transport/reply-target.d.ts.map +1 -0
- package/dist/transport/reply-target.js +55 -0
- package/dist/transport/reply-target.js.map +1 -0
- package/dist/transport/streaming-writer.d.ts +3 -3
- package/dist/transport/streaming-writer.d.ts.map +1 -1
- package/dist/transport/streaming-writer.js +1 -1
- package/dist/transport/streaming-writer.js.map +1 -1
- package/dist/transport/tool-presenter.d.ts +0 -1
- package/dist/transport/tool-presenter.d.ts.map +1 -1
- package/dist/transport/tool-presenter.js +0 -1
- package/dist/transport/tool-presenter.js.map +1 -1
- package/dist/types.d.ts +9 -15
- package/dist/types.d.ts.map +1 -1
- package/docs/assets/qqchannel.jpg +0 -0
- package/docs/assets/qqgroup.jpg +0 -0
- package/docs/assets/qrcode.png +0 -0
- package/lib/client.js +996 -0
- package/lib/client.js.map +1 -0
- package/package.json +44 -7
- package/pnpm-workspace.yaml +1 -0
- package/tsconfig.client.json +19 -0
- package/tsdown.client.config.ts +58 -0
- package/dist/setup.d.ts +0 -24
- package/dist/setup.d.ts.map +0 -1
- package/dist/setup.js +0 -170
- package/dist/setup.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGlE,6BAA6B;AAC7B,wBAAgB,aAAa,CAAC,EAAE,OAAO,EAAE,EAAE,WAAW,GAAG,kBAAkB,CAoB1E"}
|
package/dist/commands/status.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { getScopePeer, formatRelativeTime } from
|
|
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":"
|
|
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"}
|
package/dist/commands/types.d.ts
CHANGED
|
@@ -3,10 +3,17 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 每个命令工厂函数接收 CommandDeps,由 commands/index.ts 统一注入。
|
|
5
5
|
*/
|
|
6
|
-
import type {
|
|
7
|
-
import type {
|
|
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,14 @@ 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
|
}
|
|
91
|
+
/** 访问控制子 Schema(独立导出:config 路由的服务端校验复用) */
|
|
92
|
+
export declare const AccessControlSchema: Schema<AccessControlConfig>;
|
|
53
93
|
export declare const ConfigSchema: Schema<ImQQBotConfig>;
|
|
54
94
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -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;
|
|
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,2CAA2C;AAC3C,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,mBAAmB,CAK1D,CAAC;AAEH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,aAAa,CA0D7C,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -2,9 +2,16 @@
|
|
|
2
2
|
* dsh-im-qqbot 插件配置 Schema
|
|
3
3
|
*/
|
|
4
4
|
import Schema from '@deepseek-ai/schemastery';
|
|
5
|
+
/** 访问控制子 Schema(独立导出:config 路由的服务端校验复用) */
|
|
6
|
+
export const AccessControlSchema = Schema.object({
|
|
7
|
+
c2cMode: Schema.union(['open', 'allowlist', 'disabled']).default('open').description('C2C访问模式'),
|
|
8
|
+
c2cAllow: Schema.array(Schema.string()).default([]).description('C2C白名单'),
|
|
9
|
+
groupMode: Schema.union(['open', 'allowlist', 'disabled']).default('open').description('群聊访问模式'),
|
|
10
|
+
groupAllow: Schema.array(Schema.string()).default([]).description('群聊白名单'),
|
|
11
|
+
});
|
|
5
12
|
export const ConfigSchema = Schema.object({
|
|
6
|
-
appId: Schema.string().default('').description('QQ
|
|
7
|
-
appSecret: Schema.string().default('').description('QQ
|
|
13
|
+
appId: Schema.string().default('').description('QQ 机器人 AppID'),
|
|
14
|
+
appSecret: Schema.string().default('').description('QQ 机器人 AppSecret'),
|
|
8
15
|
provider: Schema.string().description('LLM provider name'),
|
|
9
16
|
model: Schema.string().description('Model name'),
|
|
10
17
|
preset: Schema.string().description('Agent preset id'),
|
|
@@ -16,14 +23,10 @@ export const ConfigSchema = Schema.object({
|
|
|
16
23
|
streaming: Schema.boolean().default(true).description('是否启用流式输出(群聊始终不启用)'),
|
|
17
24
|
sessionIdleTimeout: Schema.number().default(30 * 60 * 1000).description('会话闲置超时(ms)'),
|
|
18
25
|
maxQueue: Schema.number().default(20).description('并发队列最大长度'),
|
|
19
|
-
processingTimeoutMs: Schema.number().default(
|
|
26
|
+
processingTimeoutMs: Schema.number().default(30 * 60 * 1000).description('处理超时(ms),超时中断当前 LLM 调用'),
|
|
27
|
+
askTimeoutMs: Schema.number().default(5 * 60 * 1000).description('待答问题超时(ms),超时自动拒绝并提示'),
|
|
20
28
|
historyLimit: Schema.number().default(10).description('群历史缓冲条数'),
|
|
21
|
-
access:
|
|
22
|
-
c2cMode: Schema.union(['open', 'allowlist', 'disabled']).default('open').description('C2C访问模式'),
|
|
23
|
-
c2cAllow: Schema.array(Schema.string()).default([]).description('C2C白名单'),
|
|
24
|
-
groupMode: Schema.union(['open', 'allowlist', 'disabled']).default('open').description('群聊访问模式'),
|
|
25
|
-
groupAllow: Schema.array(Schema.string()).default([]).description('群聊白名单'),
|
|
26
|
-
}).default({
|
|
29
|
+
access: AccessControlSchema.default({
|
|
27
30
|
c2cMode: 'open',
|
|
28
31
|
c2cAllow: [],
|
|
29
32
|
groupMode: 'open',
|
|
@@ -31,5 +34,38 @@ export const ConfigSchema = Schema.object({
|
|
|
31
34
|
}).description('访问控制'),
|
|
32
35
|
showToolResults: Schema.boolean().default(false).description('是否展示工具调用成功结果(错误始终展示)'),
|
|
33
36
|
debug: Schema.boolean().default(false),
|
|
37
|
+
media: Schema.object({
|
|
38
|
+
enabled: Schema.boolean().default(true).description('是否启用富媒体理解(图片/视频下载 + 工具分析)'),
|
|
39
|
+
maxMB: Schema.number().default(200).description('富媒体下载大小上限(MB)'),
|
|
40
|
+
ttlHours: Schema.number().default(24).description('富媒体存活时长(小时),0=永不过期'),
|
|
41
|
+
}).default({
|
|
42
|
+
enabled: true,
|
|
43
|
+
maxMB: 200,
|
|
44
|
+
ttlHours: 24,
|
|
45
|
+
}).description('富媒体理解配置'),
|
|
46
|
+
vision: Schema.object({
|
|
47
|
+
enabled: Schema.boolean().default(false).description('是否启用视觉理解(qqbot_describe_image 工具)'),
|
|
48
|
+
provider: Schema.string().default('').description('视觉模型 provider(dsh 注册的 llm adapter,如 pi-ai)'),
|
|
49
|
+
model: Schema.string().default('').description('视觉模型 id(如 qwen-vl-max)'),
|
|
50
|
+
defaultPrompt: Schema.string().default('Describe this image in detail.').description('默认描述 prompt'),
|
|
51
|
+
maxBytes: Schema.number().default(10 * 1024 * 1024).description('图片字节上限'),
|
|
52
|
+
maxTokens: Schema.number().default(1024).description('输出 token 上限'),
|
|
53
|
+
timeoutMs: Schema.number().default(120000).description('视觉调用超时(ms)'),
|
|
54
|
+
}).default({
|
|
55
|
+
enabled: false,
|
|
56
|
+
provider: '',
|
|
57
|
+
model: '',
|
|
58
|
+
defaultPrompt: 'Describe this image in detail.',
|
|
59
|
+
maxBytes: 10 * 1024 * 1024,
|
|
60
|
+
maxTokens: 1024,
|
|
61
|
+
timeoutMs: 120000,
|
|
62
|
+
}).description('视觉理解配置'),
|
|
63
|
+
sendFile: Schema.object({
|
|
64
|
+
restrictPaths: Schema.boolean().default(true).description('是否启用路径白名单(默认 true,仅允许 media + cwd + extraRoots)'),
|
|
65
|
+
extraRoots: Schema.array(Schema.string()).default([]).description('额外允许访问的根目录'),
|
|
66
|
+
}).default({
|
|
67
|
+
restrictPaths: true,
|
|
68
|
+
extraRoots: [],
|
|
69
|
+
}).description('附件发送工具配置'),
|
|
34
70
|
});
|
|
35
71
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,MAAM,MAAM,0BAA0B,CAAC;
|
|
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"}
|