@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.
- package/README.md +25 -8
- package/README.zh.md +24 -8
- package/bin/qqbot-cli.js +5 -2
- package/dist/index.cjs +15549 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +890 -0
- package/index.ts +56 -19
- package/openclaw.plugin.json +19 -3
- package/package.json +14 -23
- package/preload.cjs +17 -14
- package/scripts/link-sdk-core.cjs +133 -50
- package/scripts/upgrade-via-npm.sh +42 -11
- package/scripts/upgrade-via-source.sh +4 -4
- package/skills/qqbot-channel/SKILL.md +34 -12
- package/src/adapter/contract.ts +69 -0
- package/src/adapter/gateway.ts +61 -0
- package/src/adapter/index.ts +15 -0
- package/src/adapter/lint.ts +144 -0
- package/src/adapter/media.ts +112 -0
- package/src/adapter/pairing.ts +99 -0
- package/src/adapter/resolve.ts +333 -0
- package/src/adapter/setup.ts +55 -0
- package/src/adapter/webhook.ts +248 -0
- package/src/adapter/workspace.ts +29 -0
- package/src/bot-instance.ts +60 -0
- package/src/channel.ts +202 -413
- package/src/commands/bot-approve.ts +143 -0
- package/src/commands/bot-clear-storage.ts +114 -0
- package/src/commands/bot-group-always.ts +62 -0
- package/src/commands/bot-help.ts +40 -0
- package/src/commands/bot-logs.ts +248 -0
- package/src/commands/bot-me.ts +18 -0
- package/src/commands/bot-pairing.ts +57 -0
- package/src/commands/bot-ping.ts +33 -0
- package/src/commands/bot-streaming.ts +55 -0
- package/src/commands/bot-upgrade.ts +56 -0
- package/src/commands/bot-version.ts +41 -0
- package/src/commands/config-util.ts +96 -0
- package/src/commands/index.ts +49 -0
- package/src/config.ts +46 -19
- package/src/dispatch/body-assembler.ts +356 -0
- package/src/dispatch/ctx-builder.ts +110 -0
- package/src/dispatch/dispatch.ts +352 -0
- package/src/dispatch/envelope-builder.ts +112 -0
- package/src/dispatch/index.ts +2 -0
- package/src/{approval-handler.ts → features/approval-handler.ts} +40 -94
- package/src/features/approval-utils.ts +41 -0
- package/src/features/credential-backup.ts +82 -0
- package/src/features/history-store.ts +19 -0
- package/src/features/msgid-cache.ts +55 -0
- package/src/features/onboarding.ts +38 -0
- package/src/{proactive.ts → features/proactive.ts} +68 -49
- package/src/features/ref-index-store.ts +282 -0
- package/src/{update-checker.ts → features/update-checker.ts} +15 -35
- package/src/gateway/event-handlers.ts +241 -0
- package/src/gateway/index.ts +3 -0
- package/src/gateway/lifecycle.ts +221 -0
- package/src/gateway/middleware-setup.ts +139 -0
- package/src/gateway/qqbot-gateway.ts +342 -0
- package/src/middleware/access-control.ts +130 -0
- package/src/middleware/attachment.ts +314 -0
- package/src/middleware/policy-injector.ts +66 -0
- package/src/openclaw-plugin-sdk.d.ts +81 -10
- package/src/outbound/cron-scheduler.ts +130 -0
- package/src/outbound/debounce.ts +102 -0
- package/src/outbound/deliver-pipeline.ts +235 -0
- package/src/outbound/image-size.ts +123 -0
- package/src/outbound/index.ts +3 -0
- package/src/outbound/local-file-router.ts +145 -0
- package/src/outbound/media-send.ts +377 -0
- package/src/outbound/outbound-service.ts +222 -0
- package/src/outbound/reply-limiter.ts +128 -0
- package/src/outbound/sanitize.ts +32 -0
- package/src/outbound/streaming-controller.ts +228 -0
- package/src/outbound/target.ts +63 -0
- package/src/outbound/tts-provider.ts +84 -0
- package/src/request-context.ts +9 -8
- package/src/runtime.ts +31 -6
- package/src/setup/account-key.ts +41 -0
- package/src/setup/finalize.ts +110 -0
- package/src/setup/login.ts +197 -0
- package/src/setup/surface.ts +40 -0
- package/src/tools/platform.ts +149 -0
- package/src/tools/remind.ts +2 -0
- package/src/types-augment.d.ts +54 -0
- package/src/types.ts +51 -9
- package/src/utils/mention.ts +52 -0
- package/src/utils/pkg-version.ts +84 -47
- package/src/utils/platform.ts +31 -7
- package/src/utils/plugin-logger.ts +104 -0
- package/src/utils/ssrf-guard.ts +32 -2
- package/src/utils/stt.ts +137 -0
- package/src/utils/voice-text.ts +61 -0
- package/tsconfig.json +2 -1
- package/tsup.config.ts +57 -0
- package/dist/index.d.ts +0 -18
- package/dist/index.js +0 -27
- package/dist/src/admin-resolver.d.ts +0 -33
- package/dist/src/admin-resolver.js +0 -157
- package/dist/src/api.d.ts +0 -309
- package/dist/src/api.js +0 -923
- package/dist/src/approval-handler.d.ts +0 -47
- package/dist/src/approval-handler.js +0 -372
- package/dist/src/channel.d.ts +0 -29
- package/dist/src/channel.js +0 -528
- package/dist/src/config.d.ts +0 -60
- package/dist/src/config.js +0 -288
- package/dist/src/credential-backup.d.ts +0 -31
- package/dist/src/credential-backup.js +0 -66
- package/dist/src/deliver-debounce.d.ts +0 -74
- package/dist/src/deliver-debounce.js +0 -174
- package/dist/src/gateway.d.ts +0 -18
- package/dist/src/gateway.js +0 -2025
- package/dist/src/group-history.d.ts +0 -136
- package/dist/src/group-history.js +0 -226
- package/dist/src/image-server.d.ts +0 -87
- package/dist/src/image-server.js +0 -570
- package/dist/src/inbound-attachments.d.ts +0 -60
- package/dist/src/inbound-attachments.js +0 -248
- package/dist/src/known-users.d.ts +0 -100
- package/dist/src/known-users.js +0 -263
- package/dist/src/message-gating.d.ts +0 -53
- package/dist/src/message-gating.js +0 -107
- package/dist/src/message-queue.d.ts +0 -91
- package/dist/src/message-queue.js +0 -257
- package/dist/src/onboarding.d.ts +0 -10
- package/dist/src/onboarding.js +0 -203
- package/dist/src/outbound-deliver.d.ts +0 -48
- package/dist/src/outbound-deliver.js +0 -392
- package/dist/src/outbound.d.ts +0 -220
- package/dist/src/outbound.js +0 -975
- package/dist/src/proactive.d.ts +0 -170
- package/dist/src/proactive.js +0 -399
- package/dist/src/ref-index-store.d.ts +0 -101
- package/dist/src/ref-index-store.js +0 -298
- package/dist/src/reply-dispatcher.d.ts +0 -35
- package/dist/src/reply-dispatcher.js +0 -311
- package/dist/src/request-context.d.ts +0 -25
- package/dist/src/request-context.js +0 -37
- package/dist/src/runtime.d.ts +0 -3
- package/dist/src/runtime.js +0 -13
- package/dist/src/session-store.d.ts +0 -52
- package/dist/src/session-store.js +0 -254
- package/dist/src/slash-commands.d.ts +0 -82
- package/dist/src/slash-commands.js +0 -2233
- package/dist/src/startup-greeting.d.ts +0 -30
- package/dist/src/startup-greeting.js +0 -97
- package/dist/src/streaming.d.ts +0 -247
- package/dist/src/streaming.js +0 -899
- package/dist/src/stt.d.ts +0 -21
- package/dist/src/stt.js +0 -70
- package/dist/src/tools/channel.d.ts +0 -16
- package/dist/src/tools/channel.js +0 -231
- package/dist/src/tools/remind.d.ts +0 -2
- package/dist/src/tools/remind.js +0 -255
- package/dist/src/transport/index.d.ts +0 -10
- package/dist/src/transport/index.js +0 -9
- package/dist/src/transport/webhook-transport.d.ts +0 -67
- package/dist/src/transport/webhook-transport.js +0 -245
- package/dist/src/transport/webhook-verify.d.ts +0 -48
- package/dist/src/transport/webhook-verify.js +0 -98
- package/dist/src/types.d.ts +0 -462
- package/dist/src/types.js +0 -22
- package/dist/src/typing-keepalive.d.ts +0 -27
- package/dist/src/typing-keepalive.js +0 -64
- package/dist/src/update-checker.d.ts +0 -36
- package/dist/src/update-checker.js +0 -171
- package/dist/src/utils/audio-convert.d.ts +0 -98
- package/dist/src/utils/audio-convert.js +0 -783
- package/dist/src/utils/chunked-upload.d.ts +0 -68
- package/dist/src/utils/chunked-upload.js +0 -341
- package/dist/src/utils/file-utils.d.ts +0 -61
- package/dist/src/utils/file-utils.js +0 -172
- package/dist/src/utils/image-size.d.ts +0 -51
- package/dist/src/utils/image-size.js +0 -234
- package/dist/src/utils/media-send.d.ts +0 -158
- package/dist/src/utils/media-send.js +0 -502
- package/dist/src/utils/media-tags.d.ts +0 -14
- package/dist/src/utils/media-tags.js +0 -165
- package/dist/src/utils/payload.d.ts +0 -112
- package/dist/src/utils/payload.js +0 -186
- package/dist/src/utils/pkg-version.d.ts +0 -5
- package/dist/src/utils/pkg-version.js +0 -61
- package/dist/src/utils/platform.d.ts +0 -137
- package/dist/src/utils/platform.js +0 -390
- package/dist/src/utils/ssrf-guard.d.ts +0 -25
- package/dist/src/utils/ssrf-guard.js +0 -91
- package/dist/src/utils/text-parsing.d.ts +0 -31
- package/dist/src/utils/text-parsing.js +0 -74
- package/dist/src/utils/upload-cache.d.ts +0 -34
- package/dist/src/utils/upload-cache.js +0 -93
- package/node_modules/@eshaz/web-worker/LICENSE +0 -201
- package/node_modules/@eshaz/web-worker/README.md +0 -134
- package/node_modules/@eshaz/web-worker/browser.js +0 -17
- package/node_modules/@eshaz/web-worker/cjs/browser.js +0 -16
- package/node_modules/@eshaz/web-worker/cjs/node.js +0 -219
- package/node_modules/@eshaz/web-worker/index.d.ts +0 -4
- package/node_modules/@eshaz/web-worker/node.js +0 -223
- package/node_modules/@eshaz/web-worker/package.json +0 -54
- package/node_modules/@wasm-audio-decoders/common/index.js +0 -5
- package/node_modules/@wasm-audio-decoders/common/package.json +0 -36
- package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderCommon.js +0 -231
- package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderWorker.js +0 -129
- package/node_modules/@wasm-audio-decoders/common/src/puff/README +0 -67
- package/node_modules/@wasm-audio-decoders/common/src/puff/build_puff.js +0 -31
- package/node_modules/@wasm-audio-decoders/common/src/puff/puff.c +0 -863
- package/node_modules/@wasm-audio-decoders/common/src/puff/puff.h +0 -35
- package/node_modules/@wasm-audio-decoders/common/src/utilities.js +0 -3
- package/node_modules/@wasm-audio-decoders/common/types.d.ts +0 -7
- package/node_modules/mpg123-decoder/README.md +0 -265
- package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js +0 -185
- package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js.map +0 -1
- package/node_modules/mpg123-decoder/index.js +0 -8
- package/node_modules/mpg123-decoder/package.json +0 -58
- package/node_modules/mpg123-decoder/src/EmscriptenWasm.js +0 -464
- package/node_modules/mpg123-decoder/src/MPEGDecoder.js +0 -200
- package/node_modules/mpg123-decoder/src/MPEGDecoderWebWorker.js +0 -21
- package/node_modules/mpg123-decoder/types.d.ts +0 -30
- package/node_modules/silk-wasm/LICENSE +0 -21
- package/node_modules/silk-wasm/README.md +0 -85
- package/node_modules/silk-wasm/lib/index.cjs +0 -16
- package/node_modules/silk-wasm/lib/index.d.ts +0 -70
- package/node_modules/silk-wasm/lib/index.mjs +0 -16
- package/node_modules/silk-wasm/lib/silk.wasm +0 -0
- package/node_modules/silk-wasm/lib/utils.d.ts +0 -4
- package/node_modules/silk-wasm/package.json +0 -39
- package/node_modules/simple-yenc/.github/FUNDING.yml +0 -1
- package/node_modules/simple-yenc/.prettierignore +0 -1
- package/node_modules/simple-yenc/LICENSE +0 -7
- package/node_modules/simple-yenc/README.md +0 -163
- package/node_modules/simple-yenc/dist/esm.js +0 -1
- package/node_modules/simple-yenc/dist/index.js +0 -1
- package/node_modules/simple-yenc/package.json +0 -50
- package/node_modules/simple-yenc/rollup.config.js +0 -27
- package/node_modules/simple-yenc/src/simple-yenc.js +0 -302
- package/node_modules/ws/LICENSE +0 -20
- package/node_modules/ws/README.md +0 -548
- package/node_modules/ws/browser.js +0 -8
- package/node_modules/ws/index.js +0 -13
- package/node_modules/ws/lib/buffer-util.js +0 -131
- package/node_modules/ws/lib/constants.js +0 -19
- package/node_modules/ws/lib/event-target.js +0 -292
- package/node_modules/ws/lib/extension.js +0 -203
- package/node_modules/ws/lib/limiter.js +0 -55
- package/node_modules/ws/lib/permessage-deflate.js +0 -528
- package/node_modules/ws/lib/receiver.js +0 -706
- package/node_modules/ws/lib/sender.js +0 -602
- package/node_modules/ws/lib/stream.js +0 -161
- package/node_modules/ws/lib/subprotocol.js +0 -62
- package/node_modules/ws/lib/validation.js +0 -152
- package/node_modules/ws/lib/websocket-server.js +0 -554
- package/node_modules/ws/lib/websocket.js +0 -1393
- package/node_modules/ws/package.json +0 -69
- package/node_modules/ws/wrapper.mjs +0 -8
- package/scripts/postinstall-link-sdk.js +0 -170
- package/skills/qqbot-media/SKILL.md +0 -60
- package/src/admin-resolver.ts +0 -181
- package/src/api.ts +0 -1333
- package/src/credential-backup.ts +0 -72
- package/src/deliver-debounce.ts +0 -229
- package/src/gateway.ts +0 -2272
- package/src/group-history.ts +0 -328
- package/src/image-server.ts +0 -675
- package/src/inbound-attachments.ts +0 -321
- package/src/known-users.ts +0 -353
- package/src/message-gating.ts +0 -190
- package/src/message-queue.ts +0 -354
- package/src/onboarding.ts +0 -282
- package/src/outbound-deliver.ts +0 -486
- package/src/outbound.ts +0 -1179
- package/src/ref-index-store.ts +0 -412
- package/src/reply-dispatcher.ts +0 -334
- package/src/session-store.ts +0 -303
- package/src/slash-commands.ts +0 -2431
- package/src/startup-greeting.ts +0 -120
- package/src/streaming.ts +0 -1077
- package/src/stt.ts +0 -86
- package/src/tools/channel.ts +0 -275
- package/src/transport/index.ts +0 -11
- package/src/transport/webhook-transport.ts +0 -332
- package/src/transport/webhook-verify.ts +0 -119
- package/src/typing-keepalive.ts +0 -59
- package/src/utils/audio-convert.ts +0 -887
- package/src/utils/chunked-upload.ts +0 -483
- package/src/utils/file-utils.ts +0 -193
- package/src/utils/image-size.ts +0 -266
- package/src/utils/media-send.ts +0 -636
- package/src/utils/media-tags.ts +0 -183
- package/src/utils/payload.ts +0 -265
- package/src/utils/text-parsing.ts +0 -85
- package/src/utils/upload-cache.ts +0 -128
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Body 组装器(消息入站 body 组装)
|
|
3
|
+
*
|
|
4
|
+
* SDK 中间件链已经完成了所有预处理:
|
|
5
|
+
* - ctx.message.content ← contentSanitizer(face 解析 + mention 清洗)
|
|
6
|
+
* - ctx.state.quote ← quoteRef
|
|
7
|
+
* - ctx.state.history ← historyBuffer
|
|
8
|
+
* - ctx.state.mention ← mentionGate
|
|
9
|
+
* - ctx.state.processedAttachments ← attachmentProcessor(语音 STT、图片 URL)
|
|
10
|
+
*
|
|
11
|
+
* 本模块仅负责"按框架协议把上述上下文拼成最终字符串",是纯函数。
|
|
12
|
+
*
|
|
13
|
+
* 输出字段语义(与框架 buildCtxPayload 语义一致):
|
|
14
|
+
* - webBody → ctxPayload.Body (Web UI 展示)
|
|
15
|
+
* - agentBody → ctxPayload.BodyForAgent (AI 看到的)
|
|
16
|
+
* - rawBody → ctxPayload.RawBody / CommandBody(命令解析、审计)
|
|
17
|
+
* - systemPrompt → ctxPayload.GroupSystemPrompt
|
|
18
|
+
*/
|
|
19
|
+
import type {
|
|
20
|
+
MiddlewareContext,
|
|
21
|
+
QQBotInboundMessage,
|
|
22
|
+
ResolvedQuote,
|
|
23
|
+
HistoryEntry,
|
|
24
|
+
} from '@tencent-connect/qqbot-nodejs';
|
|
25
|
+
import type { ResolvedQQBotAccount } from '../types.js';
|
|
26
|
+
import type { ProcessedAttachments } from '../middleware/attachment.js';
|
|
27
|
+
import { getAdapters } from '../adapter/resolve.js';
|
|
28
|
+
|
|
29
|
+
// ── 协议常量 ─────────────────────────────
|
|
30
|
+
const QUOTE_BEGIN = '[Quoted message begins]';
|
|
31
|
+
const QUOTE_END = '[Quoted message ends]';
|
|
32
|
+
const REF_BEGIN = '[Reference message begins]';
|
|
33
|
+
const REF_END = '[Reference message ends]';
|
|
34
|
+
const HISTORY_BEGIN = '[Chat history begins]';
|
|
35
|
+
const HISTORY_END = '[Chat history ends]';
|
|
36
|
+
const MERGE_CTX_BEGIN = '[Merged messages begins]';
|
|
37
|
+
const MERGE_CTX_END = '[Merged messages ends]';
|
|
38
|
+
const CURRENT_MSG = '[Current message]';
|
|
39
|
+
|
|
40
|
+
export interface AssembledBody {
|
|
41
|
+
/** Web UI 展示用 body */
|
|
42
|
+
webBody: string;
|
|
43
|
+
/** AI 实际接收的 body(dynamicCtx + userMessage [+ history 前缀]) */
|
|
44
|
+
agentBody: string;
|
|
45
|
+
/** 原始 content(命令解析 / 审计用) */
|
|
46
|
+
rawBody: string;
|
|
47
|
+
/** 群系统提示(account.systemPrompt + 群级提示拼接) */
|
|
48
|
+
systemPrompt?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 把 SDK 中间件链产出的所有上下文组装为框架规约的 body。
|
|
53
|
+
*/
|
|
54
|
+
export function assembleBody(
|
|
55
|
+
ctx: MiddlewareContext,
|
|
56
|
+
msg: QQBotInboundMessage,
|
|
57
|
+
account: ResolvedQQBotAccount,
|
|
58
|
+
getRuntime?: () => any,
|
|
59
|
+
): AssembledBody {
|
|
60
|
+
const rawBody = msg.content ?? '';
|
|
61
|
+
const isGroup = msg.kind === 'group';
|
|
62
|
+
|
|
63
|
+
const mentionState = ctx.state.mention as { wasMentioned?: boolean } | undefined;
|
|
64
|
+
const wasMentioned = mentionState?.wasMentioned ?? false;
|
|
65
|
+
const processed = ctx.state.processedAttachments as ProcessedAttachments | undefined;
|
|
66
|
+
const quote = ctx.state.quote as ResolvedQuote | undefined;
|
|
67
|
+
const history = ctx.state.history as HistoryEntry[] | undefined;
|
|
68
|
+
const mergedMessages = ctx.state.mergedMessages as MiddlewareContext[] | undefined;
|
|
69
|
+
|
|
70
|
+
// ── Layer 1: userContent(清洗后的文本 + 语音转录 + 附件描述) ──
|
|
71
|
+
const userContent = buildUserContent(ctx.message.content ?? '', processed);
|
|
72
|
+
|
|
73
|
+
// ── Layer 2: quotePart ──
|
|
74
|
+
const quotePart = buildQuotePart(quote);
|
|
75
|
+
|
|
76
|
+
// ── Layer 3: userMessage(群带 [sender] 前缀 + (@you),合并消息特殊处理) ──
|
|
77
|
+
const userMessage = mergedMessages && mergedMessages.length > 0
|
|
78
|
+
? buildMergedUserMessage({ messages: mergedMessages, quotePart, isGroup, wasMentioned, getRuntime })
|
|
79
|
+
: buildUserMessage({ msg, userContent, quotePart, isGroup, wasMentioned });
|
|
80
|
+
|
|
81
|
+
// ── Layer 4: dynamicCtx(媒体元数据块 + msg_elements 上下文) ──
|
|
82
|
+
const dynamicCtx = buildDynamicCtx(processed, msg, quote);
|
|
83
|
+
|
|
84
|
+
// ── Layer 5: agentBody(命令直通 / 群被@时前置历史) ──
|
|
85
|
+
const agentBody = buildAgentBody({
|
|
86
|
+
userContent,
|
|
87
|
+
base: dynamicCtx + userMessage,
|
|
88
|
+
isGroup,
|
|
89
|
+
wasMentioned,
|
|
90
|
+
history,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// ── webBody(合并消息包含全部前置 + 最后一条 + quotePart,对齐 agentBody) ──
|
|
94
|
+
const bodyContent = mergedMessages && mergedMessages.length > 0
|
|
95
|
+
? userMessage
|
|
96
|
+
: `${quotePart}${userContent}`;
|
|
97
|
+
|
|
98
|
+
// ── webBody 外层 envelope 渲染(用 formatInboundEnvelope 包装) ──
|
|
99
|
+
const webBody = getRuntime
|
|
100
|
+
? renderWebBody(getRuntime, bodyContent, msg, isGroup)
|
|
101
|
+
: bodyContent;
|
|
102
|
+
|
|
103
|
+
const systemPrompt = account.systemPrompt?.trim() || undefined;
|
|
104
|
+
|
|
105
|
+
return { webBody, agentBody, rawBody, systemPrompt };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── 局部组装函数 ─────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
/** Layer 1:sanitized + 语音转录 + 附件描述 */
|
|
111
|
+
function buildUserContent(sanitizedRaw: string, processed: ProcessedAttachments | undefined): string {
|
|
112
|
+
const sanitized = sanitizedRaw.trim();
|
|
113
|
+
const voiceText = processed?.voiceText ?? '';
|
|
114
|
+
const attachmentInfo = processed?.otherInfo ? `\n${processed.otherInfo}` : '';
|
|
115
|
+
|
|
116
|
+
if (voiceText) {
|
|
117
|
+
return (sanitized ? `${sanitized}\n${voiceText}` : voiceText) + attachmentInfo;
|
|
118
|
+
}
|
|
119
|
+
return sanitized + attachmentInfo;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Layer 2:[Quoted message begins]…[Quoted message ends] */
|
|
123
|
+
function buildQuotePart(quote: ResolvedQuote | undefined): string {
|
|
124
|
+
if (!quote) return '';
|
|
125
|
+
const text = quote.text || 'Original content unavailable';
|
|
126
|
+
return `${QUOTE_BEGIN}\n${text}\n${QUOTE_END}\n${CURRENT_MSG}\n`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Layer 3:quote + [Sender] {content}{(@you)?} */
|
|
130
|
+
function buildUserMessage(input: {
|
|
131
|
+
msg: QQBotInboundMessage;
|
|
132
|
+
userContent: string;
|
|
133
|
+
quotePart: string;
|
|
134
|
+
isGroup: boolean;
|
|
135
|
+
wasMentioned: boolean;
|
|
136
|
+
}): string {
|
|
137
|
+
const { msg, userContent, quotePart, isGroup, wasMentioned } = input;
|
|
138
|
+
const atYouTag = isGroup && wasMentioned ? ' (@you)' : '';
|
|
139
|
+
|
|
140
|
+
if (isGroup) {
|
|
141
|
+
const senderLabel = formatSenderLabel(msg.senderName, msg.senderId);
|
|
142
|
+
return `${quotePart}[${senderLabel}] ${userContent}${atYouTag}`;
|
|
143
|
+
}
|
|
144
|
+
return `${quotePart}${userContent}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Layer 3(合并版):concurrencyGuard merge 透传的多条消息 */
|
|
148
|
+
function buildMergedUserMessage(input: {
|
|
149
|
+
messages: MiddlewareContext[];
|
|
150
|
+
quotePart: string;
|
|
151
|
+
isGroup: boolean;
|
|
152
|
+
wasMentioned: boolean;
|
|
153
|
+
getRuntime?: () => any;
|
|
154
|
+
}): string {
|
|
155
|
+
const { messages, quotePart, isGroup, wasMentioned, getRuntime } = input;
|
|
156
|
+
if (messages.length <= 1) {
|
|
157
|
+
const single = messages[0]!;
|
|
158
|
+
return buildUserMessage({
|
|
159
|
+
msg: single.message,
|
|
160
|
+
userContent: single.message.content ?? '',
|
|
161
|
+
quotePart,
|
|
162
|
+
isGroup,
|
|
163
|
+
wasMentioned,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const formatEnvelope = getRuntime
|
|
168
|
+
? getAdapters(getRuntime()).formatEnvelope
|
|
169
|
+
: null;
|
|
170
|
+
|
|
171
|
+
const lines = messages.map((ctx, i) => {
|
|
172
|
+
const isLast = i === messages.length - 1;
|
|
173
|
+
const line = formatMergedLine(ctx, { isGroup, isLast, wasMentioned, formatEnvelope });
|
|
174
|
+
return line && isLast ? `${quotePart}${line}` : line;
|
|
175
|
+
}).filter(Boolean);
|
|
176
|
+
|
|
177
|
+
if (!isGroup || allSameSender(messages)) {
|
|
178
|
+
return lines.join('\n');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const last = lines.pop()!;
|
|
182
|
+
return [MERGE_CTX_BEGIN, ...lines, MERGE_CTX_END, CURRENT_MSG, last].join('\n');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function formatMergedLine(
|
|
186
|
+
ctx: MiddlewareContext,
|
|
187
|
+
opts: { isGroup: boolean; isLast: boolean; wasMentioned: boolean; formatEnvelope: ((p: Record<string, unknown>) => string) | null },
|
|
188
|
+
): string {
|
|
189
|
+
const m = ctx.message;
|
|
190
|
+
const content = (m.content ?? '').trim();
|
|
191
|
+
|
|
192
|
+
if (opts.formatEnvelope && opts.isGroup) {
|
|
193
|
+
const atYouTag = opts.isLast && opts.wasMentioned ? ' (@you)' : '';
|
|
194
|
+
return opts.formatEnvelope({
|
|
195
|
+
channel: 'qqbot',
|
|
196
|
+
from: formatSenderLabel(m.senderName, m.senderId),
|
|
197
|
+
timestamp: normalizeTimestamp(m.timestamp),
|
|
198
|
+
body: content + atYouTag,
|
|
199
|
+
chatType: 'group',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return opts.isGroup
|
|
203
|
+
? `${formatSenderLabel(m.senderName, m.senderId)}: ${content}`
|
|
204
|
+
: content;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function allSameSender(messages: MiddlewareContext[]): boolean {
|
|
208
|
+
const first = messages[0]?.message.senderId;
|
|
209
|
+
return messages.every((c) => c.message.senderId === first);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Layer 4:- Images / - Voice / - ASR 元数据块 + msg_elements 引用上下文 */
|
|
213
|
+
function buildDynamicCtx(
|
|
214
|
+
processed: ProcessedAttachments | undefined,
|
|
215
|
+
msg: QQBotInboundMessage,
|
|
216
|
+
quote: ResolvedQuote | undefined,
|
|
217
|
+
): string {
|
|
218
|
+
const lines: string[] = [];
|
|
219
|
+
|
|
220
|
+
// Images
|
|
221
|
+
if (processed?.imageUrls.length) {
|
|
222
|
+
lines.push(`- Images: ${processed.imageUrls.join(', ')}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Voice:从 transcripts 行存投影出 paths + urls,去重后拼接
|
|
226
|
+
const transcripts = processed?.transcripts ?? [];
|
|
227
|
+
const voiceRefs = unique([
|
|
228
|
+
...transcripts.map((t) => t.localPath).filter(isNonEmpty),
|
|
229
|
+
...transcripts.map((t) => t.remoteUrl).filter(isNonEmpty),
|
|
230
|
+
]);
|
|
231
|
+
if (voiceRefs.length > 0) {
|
|
232
|
+
lines.push(`- Voice: ${voiceRefs.join(', ')}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ASR:source==='asr' 的 text,或任意 transcript 上的 asrReferText
|
|
236
|
+
const asrTexts = unique(
|
|
237
|
+
transcripts
|
|
238
|
+
.map((t) => (t.source === 'asr' ? t.text : t.asrReferText))
|
|
239
|
+
.filter(isNonEmpty),
|
|
240
|
+
);
|
|
241
|
+
if (asrTexts.length > 0) {
|
|
242
|
+
lines.push(`- ASR: ${asrTexts.join(' | ')}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// msg_elements 上下文(仅当非引用消息时解析,避免与 quotePart 重复)
|
|
246
|
+
if (!quote) {
|
|
247
|
+
const elementsCtx = buildMsgElementsContext(msg);
|
|
248
|
+
if (elementsCtx.length > 0) {
|
|
249
|
+
lines.push(REF_BEGIN, ...elementsCtx, REF_END);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return lines.length > 0 ? `${lines.join('\n')}\n\n` : '';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** 从 msg_elements 提取上下文(非引用消息也解析,如被回复的 bot 消息等) */
|
|
257
|
+
function buildMsgElementsContext(msg: QQBotInboundMessage): string[] {
|
|
258
|
+
const elements = msg.msgElements;
|
|
259
|
+
if (!elements || elements.length === 0) return [];
|
|
260
|
+
|
|
261
|
+
const lines: string[] = [];
|
|
262
|
+
let index = 0;
|
|
263
|
+
for (const el of elements) {
|
|
264
|
+
const content = el.content?.trim();
|
|
265
|
+
if (!content) continue;
|
|
266
|
+
index += 1;
|
|
267
|
+
|
|
268
|
+
const author = (el as Record<string, unknown>).author as
|
|
269
|
+
| { username?: string }
|
|
270
|
+
| undefined;
|
|
271
|
+
const sender = author?.username ?? '未知';
|
|
272
|
+
|
|
273
|
+
lines.push(
|
|
274
|
+
`=== 消息 ${index} ===`,
|
|
275
|
+
`[消息内容] ${content}`,
|
|
276
|
+
`[发送者] ${sender}`,
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return lines;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function unique<T>(arr: T[]): T[] {
|
|
284
|
+
return [...new Set(arr)];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function isNonEmpty(x: string | undefined): x is string {
|
|
288
|
+
return typeof x === 'string' && x.length > 0;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** Layer 5:base = dynamicCtx+userMessage;命令直通;群被@叠历史前缀 */
|
|
292
|
+
function buildAgentBody(input: {
|
|
293
|
+
userContent: string;
|
|
294
|
+
base: string;
|
|
295
|
+
isGroup: boolean;
|
|
296
|
+
wasMentioned: boolean;
|
|
297
|
+
history: HistoryEntry[] | undefined;
|
|
298
|
+
}): string {
|
|
299
|
+
const { userContent, base, isGroup, wasMentioned, history } = input;
|
|
300
|
+
|
|
301
|
+
// 斜杠命令直通:去除一切装饰及首尾空白
|
|
302
|
+
if (userContent.trim().startsWith('/')) {
|
|
303
|
+
return userContent;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// 群被@ + 有历史 → 叠历史前缀
|
|
307
|
+
if (isGroup && wasMentioned && history && history.length > 0) {
|
|
308
|
+
const historyText = history
|
|
309
|
+
.map((h) => {
|
|
310
|
+
const label = formatSenderLabel(h.senderName, h.senderId);
|
|
311
|
+
return `[${label}] ${h.content}`;
|
|
312
|
+
})
|
|
313
|
+
.join('\n');
|
|
314
|
+
return [HISTORY_BEGIN, historyText, '', HISTORY_END, CURRENT_MSG, base].join('\n');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return base;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** "Nick (openid)" 标签;当 name 已含 id 时避免双重包裹 */
|
|
321
|
+
function formatSenderLabel(name: string | undefined, id: string): string {
|
|
322
|
+
if (!name) return id;
|
|
323
|
+
return name.includes(id) ? name : `${name} (${id})`;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** webBody 外层 envelope 渲染 */
|
|
327
|
+
function renderWebBody(
|
|
328
|
+
getRuntime: () => any,
|
|
329
|
+
bodyContent: string,
|
|
330
|
+
msg: QQBotInboundMessage,
|
|
331
|
+
isGroup: boolean,
|
|
332
|
+
): string {
|
|
333
|
+
try {
|
|
334
|
+
const { formatEnvelope } = getAdapters(getRuntime());
|
|
335
|
+
if (!formatEnvelope) return bodyContent;
|
|
336
|
+
return formatEnvelope({
|
|
337
|
+
channel: 'qqbot',
|
|
338
|
+
from: msg.senderName ?? msg.senderId,
|
|
339
|
+
timestamp: normalizeTimestamp(msg.timestamp),
|
|
340
|
+
body: bodyContent,
|
|
341
|
+
chatType: isGroup ? 'group' : 'direct',
|
|
342
|
+
});
|
|
343
|
+
} catch {
|
|
344
|
+
return bodyContent;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** timestamp 归一化到 epoch ms */
|
|
349
|
+
function normalizeTimestamp(ts: string | number | undefined): number {
|
|
350
|
+
if (typeof ts === 'number') return ts;
|
|
351
|
+
if (typeof ts === 'string') {
|
|
352
|
+
const d = new Date(ts).getTime();
|
|
353
|
+
if (!Number.isNaN(d)) return d;
|
|
354
|
+
}
|
|
355
|
+
return Date.now();
|
|
356
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ctxPayload 构建器
|
|
3
|
+
*
|
|
4
|
+
* 与内置版 outbound-dispatch.ts:buildCtxPayload 对齐,
|
|
5
|
+
* 将 SDK 消息数据转换为 OpenClaw 框架标准的入站上下文。
|
|
6
|
+
*/
|
|
7
|
+
import type { MiddlewareContext, QQBotInboundMessage } from '@tencent-connect/qqbot-nodejs';
|
|
8
|
+
import type { ResolvedQQBotAccount } from '../types.js';
|
|
9
|
+
import type { AssembledBody } from './body-assembler.js';
|
|
10
|
+
import type { OpenClawInboundMessage } from './envelope-builder.js';
|
|
11
|
+
import type { RuntimeAdapters } from '../adapter/resolve.js';
|
|
12
|
+
|
|
13
|
+
export interface CtxPayloadParams {
|
|
14
|
+
assembled: AssembledBody;
|
|
15
|
+
envelope: OpenClawInboundMessage;
|
|
16
|
+
route: { sessionKey: string; accountId: string; agentId?: string };
|
|
17
|
+
msg: QQBotInboundMessage;
|
|
18
|
+
ctx: MiddlewareContext;
|
|
19
|
+
adapters: RuntimeAdapters;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function buildCtxPayload(params: CtxPayloadParams): any {
|
|
23
|
+
const { assembled, envelope, route, msg, ctx, adapters } = params;
|
|
24
|
+
|
|
25
|
+
const isSlashCommand = /^\//.test(assembled.rawBody ?? '');
|
|
26
|
+
const convKind = envelope.chatScope === 'group' ? 'group' : 'direct';
|
|
27
|
+
const peerId = convKind === 'group'
|
|
28
|
+
? (envelope.groupId ?? envelope.senderId)
|
|
29
|
+
: envelope.senderId;
|
|
30
|
+
const groupId = convKind === 'group' ? envelope.groupId : undefined;
|
|
31
|
+
|
|
32
|
+
const processed = ctx.state.processedAttachments as any;
|
|
33
|
+
const voicePaths = processed?.localMediaPaths?.filter((_: string, i: number) =>
|
|
34
|
+
processed.localMediaTypes?.[i]?.startsWith('audio/')) ?? [];
|
|
35
|
+
const voiceUrls = processed?.remoteMediaUrls?.filter((_: string, i: number) =>
|
|
36
|
+
processed.remoteMediaUrls?.[i]?.startsWith?.('audio/')) ?? [];
|
|
37
|
+
|
|
38
|
+
const msgTimestamp = (msg as any).timestamp ?? (msg as any).Timestamp;
|
|
39
|
+
|
|
40
|
+
return adapters.buildInboundContext?.({
|
|
41
|
+
channel: 'qqbot',
|
|
42
|
+
accountId: route.accountId,
|
|
43
|
+
provider: 'qqbot',
|
|
44
|
+
surface: 'qqbot',
|
|
45
|
+
messageId: envelope.messageId,
|
|
46
|
+
timestamp: msgTimestamp ? new Date(msgTimestamp).getTime() : Date.now(),
|
|
47
|
+
from: envelope.targetId,
|
|
48
|
+
sender: { id: envelope.senderId, name: envelope.senderName },
|
|
49
|
+
conversation: {
|
|
50
|
+
kind: convKind,
|
|
51
|
+
id: peerId,
|
|
52
|
+
label: assembled.systemPrompt,
|
|
53
|
+
},
|
|
54
|
+
message: {
|
|
55
|
+
body: assembled.webBody,
|
|
56
|
+
bodyForAgent: assembled.agentBody,
|
|
57
|
+
rawBody: assembled.rawBody,
|
|
58
|
+
commandBody: assembled.rawBody,
|
|
59
|
+
},
|
|
60
|
+
route: {
|
|
61
|
+
agentId: route.agentId ?? 'default',
|
|
62
|
+
routeSessionKey: route.sessionKey,
|
|
63
|
+
accountId: route.accountId,
|
|
64
|
+
},
|
|
65
|
+
reply: {
|
|
66
|
+
to: envelope.targetId,
|
|
67
|
+
replyToId: envelope.messageId,
|
|
68
|
+
originatingTo: envelope.targetId,
|
|
69
|
+
},
|
|
70
|
+
access: {
|
|
71
|
+
commands: { authorized: isSlashCommand },
|
|
72
|
+
},
|
|
73
|
+
command: isSlashCommand
|
|
74
|
+
? { kind: 'text-slash' as const, body: assembled.rawBody!, authorized: true }
|
|
75
|
+
: undefined,
|
|
76
|
+
media: voicePaths.length > 0
|
|
77
|
+
? voicePaths.map((p: string, i: number) => ({
|
|
78
|
+
contentType: processed?.localMediaTypes?.[i] ?? 'audio/silk',
|
|
79
|
+
localPath: p,
|
|
80
|
+
url: voiceUrls[i],
|
|
81
|
+
}))
|
|
82
|
+
: voiceUrls.length > 0
|
|
83
|
+
? voiceUrls.map((u: string) => ({ contentType: 'audio/wav', url: u }))
|
|
84
|
+
: undefined,
|
|
85
|
+
supplemental: {
|
|
86
|
+
quote: envelope.quote
|
|
87
|
+
? { id: envelope.messageId, body: envelope.quote.content, sender: envelope.quote.senderId }
|
|
88
|
+
: undefined,
|
|
89
|
+
groupSystemPrompt: envelope.systemPrompt,
|
|
90
|
+
},
|
|
91
|
+
extra: {
|
|
92
|
+
...(isSlashCommand ? { CommandSource: 'text' } : {}),
|
|
93
|
+
...(groupId ? { QQGroupOpenid: groupId } : {}),
|
|
94
|
+
...(processed?.localMediaPaths?.length
|
|
95
|
+
? {
|
|
96
|
+
MediaPaths: processed.localMediaPaths,
|
|
97
|
+
MediaPath: processed.localMediaPaths[0],
|
|
98
|
+
MediaTypes: processed.localMediaTypes,
|
|
99
|
+
MediaType: processed.localMediaTypes?.[0],
|
|
100
|
+
}
|
|
101
|
+
: {}),
|
|
102
|
+
...(processed?.remoteMediaUrls?.length
|
|
103
|
+
? {
|
|
104
|
+
MediaUrls: processed.remoteMediaUrls,
|
|
105
|
+
MediaUrl: processed.remoteMediaUrls[0],
|
|
106
|
+
}
|
|
107
|
+
: {}),
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|