@openclaw/qqbot 2026.7.1-beta.1 → 2026.7.1-beta.4
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/dist/api.js +6 -6
- package/dist/{channel-Bh0MrYvU.js → channel-D1UztsnG.js} +84 -34
- package/dist/{channel-entry-LMtNY-J6.js → channel-entry-C5YdhX3Y.js} +3 -3
- package/dist/channel-entry-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.setup-CldhUlTO.js → channel.setup-2ItDYKhz.js} +1 -1
- package/dist/{config-schema-gMW9zKvQ.js → config-schema-JZEf1dvB.js} +2 -5
- package/dist/{gateway-BgfQAYxB.js → gateway-pJQppxe4.js} +344 -138
- package/dist/{group-DQQsZ6Y0.js → group-o0GmovSf.js} +14 -11
- package/dist/{handler-runtime-BQ72YPsH.js → handler-runtime-zQvT6SrI.js} +3 -3
- package/dist/{log-SDfMMBWe.js → log-DEtcoDWe.js} +3 -4
- package/dist/{outbound-Dfa2uOEp.js → outbound-BIrfvvFJ.js} +310 -68
- package/dist/{runtime-BL0xZzWe.js → runtime-DodcT_fQ.js} +1 -1
- package/dist/runtime-api.js +1 -1
- package/dist/{sender-BHWpE_yH.js → sender-CjDuU-uz.js} +31 -25
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{tools-BvXe7bTq.js → tools-UJJ-tLHP.js} +19 -36
- package/dist/tools-api.js +1 -1
- package/npm-shrinkwrap.json +3 -3
- package/package.json +4 -4
- package/skills/qqbot-media/SKILL.md +14 -11
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { C as getNextMsgSeq, D as downloadFile, F as StreamInputMode, I as StreamInputState, M as getMaxUploadSize, P as StreamContentType, T as openLocalFile, a as createRawInputNotifyFn, b as stopBackgroundTokenRefresh, c as getMessageApi, d as initSender, f as onMessageSent, g as sendText, h as sendMedia, i as clearTokenCache, j as getImageMimeType, k as formatFileSize, l as getPluginUserAgent, m as sendInputNotify, n as acknowledgeInteraction, o as getAccessToken, p as registerAccount, r as buildDeliveryTarget, s as getGatewayUrl, t as accountToCreds, u as initApiConfig, x as withTokenRetry, y as startBackgroundTokenRefresh } from "./sender-
|
|
1
|
+
import { C as getNextMsgSeq, D as downloadFile, F as StreamInputMode, I as StreamInputState, M as getMaxUploadSize, P as StreamContentType, T as openLocalFile, a as createRawInputNotifyFn, b as stopBackgroundTokenRefresh, c as getMessageApi, d as initSender, f as onMessageSent, g as sendText, h as sendMedia, i as clearTokenCache, j as getImageMimeType, k as formatFileSize, l as getPluginUserAgent, m as sendInputNotify, n as acknowledgeInteraction, o as getAccessToken, p as registerAccount, r as buildDeliveryTarget, s as getGatewayUrl, t as accountToCreds, u as initApiConfig, x as withTokenRetry, y as startBackgroundTokenRefresh } from "./sender-CjDuU-uz.js";
|
|
2
2
|
import { c as getPlatformAdapter, i as normalizeOptionalString$1, n as normalizeLowercaseStringOrEmpty$1, o as readStringField, s as sanitizeFileName, t as asOptionalObjectRecord } from "./string-normalize-R_0cKO7Q.js";
|
|
3
|
-
import { c as setBridgeLogger, o as ensurePlatformAdapter } from "./config-schema-
|
|
4
|
-
import { a as authorizeQQBotApprovalAction, i as toGatewayAccount, n as createQQBotMarkdownChunker, p as parseApprovalButtonData, r as openQQBotSyncKeyedStore } from "./channel-
|
|
3
|
+
import { c as setBridgeLogger, o as ensurePlatformAdapter } from "./config-schema-JZEf1dvB.js";
|
|
4
|
+
import { a as authorizeQQBotApprovalAction, i as toGatewayAccount, n as createQQBotMarkdownChunker, p as parseApprovalButtonData, r as openQQBotSyncKeyedStore } from "./channel-D1UztsnG.js";
|
|
5
5
|
import { d as resolveAccountBase } from "./config-C1qZbh0K.js";
|
|
6
|
-
import { a as formatErrorMessage, i as formatDuration, n as debugLog, r as debugWarn, t as debugError } from "./log-
|
|
7
|
-
import { n as getQQBotRuntimeForEngine, t as getQQBotRuntime } from "./runtime-
|
|
6
|
+
import { a as formatErrorMessage, i as formatDuration, n as debugLog, r as debugWarn, t as debugError } from "./log-DEtcoDWe.js";
|
|
7
|
+
import { n as getQQBotRuntimeForEngine, t as getQQBotRuntime } from "./runtime-DodcT_fQ.js";
|
|
8
8
|
import { t as buildQQBotStateKey } from "./state-keys-CQKlAFyo.js";
|
|
9
|
-
import { a as resolveMentionPatterns, c as getPluginVersion, d as PRIVATE_CHAT_ONLY_TEXT, f as classifyCoreCommandForGroup, i as resolveGroupSettings, l as initCommands, n as resolveGroupCommandLevelFromAccountConfig, r as resolveGroupConfig, s as getFrameworkVersion, u as matchSlashCommand } from "./group-
|
|
10
|
-
import {
|
|
9
|
+
import { a as resolveMentionPatterns, c as getPluginVersion, d as PRIVATE_CHAT_ONLY_TEXT, f as classifyCoreCommandForGroup, i as resolveGroupSettings, l as initCommands, n as resolveGroupCommandLevelFromAccountConfig, r as resolveGroupConfig, s as getFrameworkVersion, u as matchSlashCommand } from "./group-o0GmovSf.js";
|
|
10
|
+
import { B as normalizePath, C as resolveUserFacingMediaError, F as getQQBotMediaDir, L as getTempDir, M as checkSilkWasmAvailable, N as getHomeDir, P as getQQBotDataDir, R as isLocalPath, S as resolveWorkspaceScopedLocalRoots, _ as sendVideoMsg, b as resolveOutboundMediaLocalRoots, c as isCronReminderPayload, d as normalizeMediaTags, g as sendPhoto, h as sendDocument, j as setOutboundAudioPort, k as DEFAULT_MEDIA_SEND_ERROR, l as isMediaPayload, o as decodeMediaPath, r as sendMedia$1, s as encodePayloadForCron, u as parseQQBotPayload, v as sendVoice, x as resolveWorkspacePathCandidates, y as resolveTrustedOutboundMediaPath, z as isWindows } from "./outbound-BIrfvvFJ.js";
|
|
11
11
|
import { n as runWithRequestContext } from "./request-context-Bm7PTBD1.js";
|
|
12
12
|
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
13
|
+
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
|
13
14
|
import { isImplicitSameChatApprovalAuthorization } from "openclaw/plugin-sdk/approval-auth-runtime";
|
|
14
15
|
import * as fs$1 from "node:fs";
|
|
15
16
|
import fs from "node:fs";
|
|
@@ -17,7 +18,7 @@ import * as os$1 from "node:os";
|
|
|
17
18
|
import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
|
|
18
19
|
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
19
20
|
import crypto from "node:crypto";
|
|
20
|
-
import { readRegularFileSync } from "openclaw/plugin-sdk/security-runtime";
|
|
21
|
+
import { readRegularFileSync, resolveLocalPathFromRootsSync } from "openclaw/plugin-sdk/security-runtime";
|
|
21
22
|
import * as path$1 from "node:path";
|
|
22
23
|
import path from "node:path";
|
|
23
24
|
import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
|
|
@@ -26,6 +27,7 @@ import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
|
|
|
26
27
|
import { resolveRuntimeServiceVersion } from "openclaw/plugin-sdk/cli-runtime";
|
|
27
28
|
import WebSocket from "ws";
|
|
28
29
|
import { resolveAmbientNodeProxyAgent } from "openclaw/plugin-sdk/extension-shared";
|
|
30
|
+
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "openclaw/plugin-sdk/agent-runtime";
|
|
29
31
|
import { buildChannelInboundEventContext } from "openclaw/plugin-sdk/channel-inbound";
|
|
30
32
|
import { SILENT_REPLY_TOKEN, isSilentReplyPayloadText } from "openclaw/plugin-sdk/reply-chunking";
|
|
31
33
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
@@ -458,7 +460,7 @@ async function trySlashCommand(msg, ctx) {
|
|
|
458
460
|
const contentLower = content.toLowerCase();
|
|
459
461
|
if (URGENT_COMMANDS.some((cmd) => contentLower === cmd.toLowerCase() || contentLower.startsWith(cmd.toLowerCase() + " "))) {
|
|
460
462
|
if (isGroup && !commandAuthorized) return "enqueue";
|
|
461
|
-
log?.info(`Urgent command detected: ${content
|
|
463
|
+
log?.info(`Urgent command detected: ${truncateUtf16Safe(content, 20)}`);
|
|
462
464
|
return "urgent";
|
|
463
465
|
}
|
|
464
466
|
const receivedAt = Date.now();
|
|
@@ -1154,7 +1156,7 @@ function createMessageQueue(ctx) {
|
|
|
1154
1156
|
for (const m of batch) if (isSlashCommand(m)) commands.push(m);
|
|
1155
1157
|
else normal.push(m);
|
|
1156
1158
|
for (const cmd of commands) {
|
|
1157
|
-
log?.debug?.(`Processing command independently for ${peerId}: ${(cmd.content ?? "").trim()
|
|
1159
|
+
log?.debug?.(`Processing command independently for ${peerId}: ${truncateUtf16Safe((cmd.content ?? "").trim(), 50)}`);
|
|
1158
1160
|
await processOne(cmd, peerId, "Command processor");
|
|
1159
1161
|
}
|
|
1160
1162
|
if (normal.length > 0) {
|
|
@@ -1487,6 +1489,10 @@ var GatewayConnection = class {
|
|
|
1487
1489
|
const { account: _account, log } = this.ctx;
|
|
1488
1490
|
if (this.isAborted || this.reconnect.isExhausted()) {
|
|
1489
1491
|
log?.error(`Max reconnect attempts reached or aborted`);
|
|
1492
|
+
if (!this.isAborted) this.ctx.onDisconnected?.({
|
|
1493
|
+
reason: "reconnect attempts exhausted",
|
|
1494
|
+
fatal: true
|
|
1495
|
+
});
|
|
1490
1496
|
return;
|
|
1491
1497
|
}
|
|
1492
1498
|
if (this.reconnectTimer) {
|
|
@@ -1578,11 +1584,20 @@ var GatewayConnection = class {
|
|
|
1578
1584
|
}
|
|
1579
1585
|
case GatewayOp.HEARTBEAT_ACK: break;
|
|
1580
1586
|
case GatewayOp.RECONNECT:
|
|
1587
|
+
this.ctx.onDisconnected?.({
|
|
1588
|
+
reason: "server requested reconnect",
|
|
1589
|
+
fatal: false
|
|
1590
|
+
});
|
|
1581
1591
|
this.cleanup();
|
|
1582
1592
|
this.scheduleReconnect();
|
|
1583
1593
|
break;
|
|
1584
|
-
case GatewayOp.INVALID_SESSION:
|
|
1585
|
-
|
|
1594
|
+
case GatewayOp.INVALID_SESSION: {
|
|
1595
|
+
const canResume = d;
|
|
1596
|
+
this.ctx.onDisconnected?.({
|
|
1597
|
+
reason: canResume ? "session resume rejected" : "session invalidated",
|
|
1598
|
+
fatal: false
|
|
1599
|
+
});
|
|
1600
|
+
if (!canResume) {
|
|
1586
1601
|
this.sessionId = null;
|
|
1587
1602
|
this.lastSeq = null;
|
|
1588
1603
|
clearSession(account.accountId);
|
|
@@ -1591,6 +1606,7 @@ var GatewayConnection = class {
|
|
|
1591
1606
|
this.cleanup();
|
|
1592
1607
|
this.scheduleReconnect(3e3);
|
|
1593
1608
|
break;
|
|
1609
|
+
}
|
|
1594
1610
|
}
|
|
1595
1611
|
} catch (err) {
|
|
1596
1612
|
log?.error(`Message parse error: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1598,6 +1614,7 @@ var GatewayConnection = class {
|
|
|
1598
1614
|
});
|
|
1599
1615
|
ws.on("close", (code, reason) => {
|
|
1600
1616
|
log?.info(`WebSocket closed: ${code} ${reason.toString()}`);
|
|
1617
|
+
if (this.currentWs !== ws) return;
|
|
1601
1618
|
this.isConnecting = false;
|
|
1602
1619
|
this.handleClose(code);
|
|
1603
1620
|
});
|
|
@@ -1649,6 +1666,10 @@ var GatewayConnection = class {
|
|
|
1649
1666
|
}
|
|
1650
1667
|
if (action.refreshToken) this.shouldRefreshToken = true;
|
|
1651
1668
|
this.cleanup();
|
|
1669
|
+
if (!this.isAborted) this.ctx.onDisconnected?.({
|
|
1670
|
+
reason: action.reason,
|
|
1671
|
+
fatal: action.fatal
|
|
1672
|
+
});
|
|
1652
1673
|
if (action.fatal) return;
|
|
1653
1674
|
if (action.shouldReconnect) this.scheduleReconnect(action.reconnectDelay);
|
|
1654
1675
|
}
|
|
@@ -1716,7 +1737,7 @@ async function transcribeAudio(audioPath, cfg) {
|
|
|
1716
1737
|
try {
|
|
1717
1738
|
if (!resp.ok) {
|
|
1718
1739
|
const detail = await readResponseTextLimited(resp, STT_ERROR_BODY_LIMIT_BYTES).catch(() => "");
|
|
1719
|
-
throw new Error(`STT failed (HTTP ${resp.status}): ${detail
|
|
1740
|
+
throw new Error(`STT failed (HTTP ${resp.status}): ${truncateUtf16Safe(detail, 300)}`);
|
|
1720
1741
|
}
|
|
1721
1742
|
return normalizeOptionalString$1((await readProviderJsonResponse(resp, "qqbot.stt")).text) ?? null;
|
|
1722
1743
|
} finally {
|
|
@@ -1924,7 +1945,7 @@ async function processVoiceAttachment(localPath, audioPathInput, att, asrReferTe
|
|
|
1924
1945
|
try {
|
|
1925
1946
|
const transcript = await transcribeAudio(audioPath, cfg);
|
|
1926
1947
|
if (transcript) {
|
|
1927
|
-
log?.debug?.(`STT transcript: ${transcript
|
|
1948
|
+
log?.debug?.(`STT transcript: ${truncateUtf16Safe(transcript, 100)}...`);
|
|
1928
1949
|
return {
|
|
1929
1950
|
localPath,
|
|
1930
1951
|
type: "voice",
|
|
@@ -2670,7 +2691,7 @@ async function resolveQuote(event, deps) {
|
|
|
2670
2691
|
cfg: account.config,
|
|
2671
2692
|
log
|
|
2672
2693
|
}, attachmentProcessor);
|
|
2673
|
-
log?.debug?.(`Quote detected via msg_elements[0] (cache miss): id=${event.refMsgIdx}, content="${(refBody ?? ""
|
|
2694
|
+
log?.debug?.(`Quote detected via msg_elements[0] (cache miss): id=${event.refMsgIdx}, content="${truncateUtf16Safe(refBody ?? "", 80)}..."`);
|
|
2674
2695
|
return {
|
|
2675
2696
|
id: event.refMsgIdx,
|
|
2676
2697
|
body: refBody || void 0,
|
|
@@ -3296,17 +3317,39 @@ function hasQQBotImageSize(markdownImage) {
|
|
|
3296
3317
|
}
|
|
3297
3318
|
//#endregion
|
|
3298
3319
|
//#region extensions/qqbot/src/engine/messaging/outbound-deliver.ts
|
|
3320
|
+
/**
|
|
3321
|
+
* Outbound delivery helpers — core/ version.
|
|
3322
|
+
*
|
|
3323
|
+
* Uses the unified `sender.ts` business function layer for all text and
|
|
3324
|
+
* image sending. Media sends (photo/voice/video/file) are injected via
|
|
3325
|
+
* `DeliverDeps.mediaSender`.
|
|
3326
|
+
*/
|
|
3299
3327
|
/** Maximum text length for a single QQ Bot message. */
|
|
3300
3328
|
const TEXT_CHUNK_LIMIT = 5e3;
|
|
3301
|
-
function resolveMediaTargetContext(event,
|
|
3329
|
+
function resolveMediaTargetContext(event, actx) {
|
|
3330
|
+
const { account } = actx;
|
|
3302
3331
|
return {
|
|
3303
3332
|
targetType: event.type === "c2c" ? "c2c" : event.type === "group" ? "group" : event.type === "dm" ? "dm" : "channel",
|
|
3304
3333
|
targetId: event.type === "c2c" ? event.senderId : event.type === "group" ? event.groupOpenid : event.type === "dm" ? event.guildId : event.channelId,
|
|
3305
3334
|
account,
|
|
3306
|
-
replyToId: event.messageId
|
|
3335
|
+
replyToId: event.messageId,
|
|
3336
|
+
...actx.mediaAccess ? { mediaAccess: actx.mediaAccess } : {},
|
|
3337
|
+
...actx.mediaLocalRoots ? { mediaLocalRoots: actx.mediaLocalRoots } : {},
|
|
3338
|
+
...actx.mediaReadFile ? { mediaReadFile: actx.mediaReadFile } : {}
|
|
3307
3339
|
};
|
|
3308
3340
|
}
|
|
3341
|
+
function isHttpUrl(value) {
|
|
3342
|
+
return value.startsWith("http://") || value.startsWith("https://");
|
|
3343
|
+
}
|
|
3344
|
+
function isImageDataUrl(value) {
|
|
3345
|
+
return value.startsWith("data:image/");
|
|
3346
|
+
}
|
|
3347
|
+
function isBareRelativeMediaPath(value) {
|
|
3348
|
+
const trimmed = value.trim();
|
|
3349
|
+
return Boolean(trimmed) && !trimmed.startsWith("#") && !trimmed.startsWith("//") && !/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed);
|
|
3350
|
+
}
|
|
3309
3351
|
async function autoMediaBatch(params) {
|
|
3352
|
+
let sentCount = 0;
|
|
3310
3353
|
for (const mediaUrl of params.mediaUrls) try {
|
|
3311
3354
|
const result = await params.mediaSender.sendMedia({
|
|
3312
3355
|
to: params.qualifiedTarget,
|
|
@@ -3314,17 +3357,22 @@ async function autoMediaBatch(params) {
|
|
|
3314
3357
|
mediaUrl,
|
|
3315
3358
|
accountId: params.account.accountId,
|
|
3316
3359
|
replyToId: params.replyToId,
|
|
3317
|
-
account: params.account
|
|
3360
|
+
account: params.account,
|
|
3361
|
+
...params.mediaAccess ? { mediaAccess: params.mediaAccess } : {},
|
|
3362
|
+
...params.mediaLocalRoots ? { mediaLocalRoots: params.mediaLocalRoots } : {},
|
|
3363
|
+
...params.mediaReadFile ? { mediaReadFile: params.mediaReadFile } : {}
|
|
3318
3364
|
});
|
|
3319
3365
|
if (result.error) {
|
|
3320
3366
|
params.log?.error(params.onResultError(mediaUrl, result.error));
|
|
3321
3367
|
continue;
|
|
3322
3368
|
}
|
|
3369
|
+
sentCount++;
|
|
3323
3370
|
const successMessage = params.onSuccess?.(mediaUrl);
|
|
3324
3371
|
if (successMessage) params.log?.info(successMessage);
|
|
3325
3372
|
} catch (err) {
|
|
3326
3373
|
params.log?.error(params.onThrownError(mediaUrl, formatErrorMessage(err)));
|
|
3327
3374
|
}
|
|
3375
|
+
return sentCount;
|
|
3328
3376
|
}
|
|
3329
3377
|
async function sendTextChunkToTarget(params) {
|
|
3330
3378
|
const { account, event, text, consumeQuoteRef, allowDm, forcePlainText } = params;
|
|
@@ -3347,7 +3395,7 @@ async function sendTextChunks(text, event, actx, sendWithRetry, consumeQuoteRef,
|
|
|
3347
3395
|
consumeQuoteRef,
|
|
3348
3396
|
allowDm: true,
|
|
3349
3397
|
log,
|
|
3350
|
-
onSuccess: (chunk) => `Sent text chunk (${chunk.length}/${text.length} chars): ${chunk
|
|
3398
|
+
onSuccess: (chunk) => `Sent text chunk (${chunk.length}/${text.length} chars): ${truncateUtf16Safe(chunk, 50)}...`,
|
|
3351
3399
|
onError: (err) => `Failed to send text chunk: ${formatErrorMessage(err)}`
|
|
3352
3400
|
});
|
|
3353
3401
|
}
|
|
@@ -3364,7 +3412,7 @@ async function sendTextOnlyReply(text, event, actx, sendWithRetry, consumeQuoteR
|
|
|
3364
3412
|
allowDm: true,
|
|
3365
3413
|
forcePlainText: true,
|
|
3366
3414
|
log,
|
|
3367
|
-
onSuccess: (chunk) => `Sent text-only chunk (${chunk.length}/${safeText.length} chars): ${chunk
|
|
3415
|
+
onSuccess: (chunk) => `Sent text-only chunk (${chunk.length}/${safeText.length} chars): ${truncateUtf16Safe(chunk, 50)}...`,
|
|
3368
3416
|
onError: (err) => `Failed to send text-only chunk: ${formatErrorMessage(err)}`
|
|
3369
3417
|
});
|
|
3370
3418
|
}
|
|
@@ -3390,16 +3438,18 @@ async function sendWithResultLogging(params) {
|
|
|
3390
3438
|
const result = await params.run();
|
|
3391
3439
|
if (result.error) {
|
|
3392
3440
|
params.log?.error(params.onError(result.error));
|
|
3393
|
-
return;
|
|
3441
|
+
return false;
|
|
3394
3442
|
}
|
|
3395
3443
|
const successMessage = params.onSuccess?.();
|
|
3396
3444
|
if (successMessage) params.log?.info(successMessage);
|
|
3445
|
+
return true;
|
|
3397
3446
|
} catch (err) {
|
|
3398
3447
|
params.log?.error(params.onError(formatErrorMessage(err)));
|
|
3448
|
+
return false;
|
|
3399
3449
|
}
|
|
3400
3450
|
}
|
|
3401
3451
|
async function sendPhotoWithLogging(params) {
|
|
3402
|
-
await sendWithResultLogging({
|
|
3452
|
+
return await sendWithResultLogging({
|
|
3403
3453
|
run: async () => await params.mediaSender.sendPhoto(params.target, params.imageUrl),
|
|
3404
3454
|
log: params.log,
|
|
3405
3455
|
onSuccess: params.onSuccess ? () => params.onSuccess?.(params.imageUrl) : void 0,
|
|
@@ -3431,9 +3481,14 @@ async function sendVoiceWithTimeout(target, voicePath, account, mediaSender, log
|
|
|
3431
3481
|
});
|
|
3432
3482
|
}, voiceTimeout);
|
|
3433
3483
|
})]);
|
|
3434
|
-
if (result.error)
|
|
3484
|
+
if (result.error) {
|
|
3485
|
+
log?.error(`sendVoice error: ${result.error}`);
|
|
3486
|
+
return false;
|
|
3487
|
+
}
|
|
3488
|
+
return true;
|
|
3435
3489
|
} catch (err) {
|
|
3436
3490
|
log?.error(`sendVoice unexpected error: ${formatErrorMessage(err)}`);
|
|
3491
|
+
return false;
|
|
3437
3492
|
}
|
|
3438
3493
|
}
|
|
3439
3494
|
/**
|
|
@@ -3489,38 +3544,62 @@ async function parseAndSendMediaTags(replyText, event, actx, sendWithRetry, cons
|
|
|
3489
3544
|
content: filterInternalMarkers(textAfter)
|
|
3490
3545
|
});
|
|
3491
3546
|
log?.debug?.(`Send queue: ${sendQueue.map((item) => item.type).join(" -> ")}`);
|
|
3492
|
-
const mediaTarget = resolveMediaTargetContext(event,
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
})
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
})
|
|
3547
|
+
const mediaTarget = resolveMediaTargetContext(event, actx);
|
|
3548
|
+
let deliveredVisibleOutput = false;
|
|
3549
|
+
for (const item of sendQueue) if (item.type === "text") {
|
|
3550
|
+
await sendTextChunks(item.content, event, actx, sendWithRetry, consumeQuoteRef, deps);
|
|
3551
|
+
if (item.content.trim()) deliveredVisibleOutput = true;
|
|
3552
|
+
} else if (item.type === "image") {
|
|
3553
|
+
const sent = await sendPhotoWithLogging({
|
|
3554
|
+
target: mediaTarget,
|
|
3555
|
+
imageUrl: item.content,
|
|
3556
|
+
mediaSender: deps.mediaSender,
|
|
3557
|
+
log,
|
|
3558
|
+
onError: (error) => `sendPhoto error: ${error}`
|
|
3559
|
+
});
|
|
3560
|
+
deliveredVisibleOutput = deliveredVisibleOutput || sent;
|
|
3561
|
+
} else if (item.type === "voice") {
|
|
3562
|
+
const sent = await sendVoiceWithTimeout(mediaTarget, item.content, account, deps.mediaSender, log);
|
|
3563
|
+
deliveredVisibleOutput = deliveredVisibleOutput || sent;
|
|
3564
|
+
} else if (item.type === "video") {
|
|
3565
|
+
const sent = await sendWithResultLogging({
|
|
3566
|
+
run: async () => await deps.mediaSender.sendVideoMsg(mediaTarget, item.content),
|
|
3567
|
+
log,
|
|
3568
|
+
onError: (error) => `sendVideoMsg error: ${error}`
|
|
3569
|
+
});
|
|
3570
|
+
deliveredVisibleOutput = deliveredVisibleOutput || sent;
|
|
3571
|
+
} else if (item.type === "file") {
|
|
3572
|
+
const sent = await sendWithResultLogging({
|
|
3573
|
+
run: async () => await deps.mediaSender.sendDocument(mediaTarget, item.content),
|
|
3574
|
+
log,
|
|
3575
|
+
onError: (error) => `sendDocument error: ${error}`
|
|
3576
|
+
});
|
|
3577
|
+
deliveredVisibleOutput = deliveredVisibleOutput || sent;
|
|
3578
|
+
} else if (item.type === "media") {
|
|
3579
|
+
const sent = await sendWithResultLogging({
|
|
3580
|
+
run: async () => await deps.mediaSender.sendMedia({
|
|
3581
|
+
to: actx.qualifiedTarget,
|
|
3582
|
+
text: "",
|
|
3583
|
+
mediaUrl: item.content,
|
|
3584
|
+
accountId: account.accountId,
|
|
3585
|
+
replyToId: event.messageId,
|
|
3586
|
+
account,
|
|
3587
|
+
...actx.mediaAccess ? { mediaAccess: actx.mediaAccess } : {},
|
|
3588
|
+
...actx.mediaLocalRoots ? { mediaLocalRoots: actx.mediaLocalRoots } : {},
|
|
3589
|
+
...actx.mediaReadFile ? { mediaReadFile: actx.mediaReadFile } : {}
|
|
3590
|
+
}),
|
|
3591
|
+
log,
|
|
3592
|
+
onError: (error) => `sendMedia(auto) error: ${error}`
|
|
3593
|
+
});
|
|
3594
|
+
deliveredVisibleOutput = deliveredVisibleOutput || sent;
|
|
3595
|
+
}
|
|
3596
|
+
if (!deliveredVisibleOutput) {
|
|
3597
|
+
await sendTextChunks(DEFAULT_MEDIA_SEND_ERROR, event, actx, sendWithRetry, consumeQuoteRef, deps);
|
|
3598
|
+
return {
|
|
3599
|
+
handled: true,
|
|
3600
|
+
normalizedText: ""
|
|
3601
|
+
};
|
|
3602
|
+
}
|
|
3524
3603
|
return {
|
|
3525
3604
|
handled: true,
|
|
3526
3605
|
normalizedText: text
|
|
@@ -3534,18 +3613,18 @@ async function sendPlainReply(payload, replyText, event, actx, sendWithRetry, co
|
|
|
3534
3613
|
const { account, qualifiedTarget, log } = actx;
|
|
3535
3614
|
const collectedImageUrls = [];
|
|
3536
3615
|
const localMediaToSend = [];
|
|
3537
|
-
const collectImageUrl = (url) => {
|
|
3616
|
+
const collectImageUrl = (url, allowBareRelativeMedia = false) => {
|
|
3538
3617
|
if (!url) return false;
|
|
3539
|
-
const
|
|
3540
|
-
const isDataUrl = url
|
|
3541
|
-
if (
|
|
3618
|
+
const isRemoteHttpUrl = isHttpUrl(url);
|
|
3619
|
+
const isDataUrl = isImageDataUrl(url);
|
|
3620
|
+
if (isRemoteHttpUrl || isDataUrl) {
|
|
3542
3621
|
if (!collectedImageUrls.includes(url)) {
|
|
3543
3622
|
collectedImageUrls.push(url);
|
|
3544
|
-
log?.debug?.(`Collected ${isDataUrl ? "Base64" : "media URL"}: ${isDataUrl ? `(length: ${url.length})` : url
|
|
3623
|
+
log?.debug?.(`Collected ${isDataUrl ? "Base64" : "media URL"}: ${isDataUrl ? `(length: ${url.length})` : truncateUtf16Safe(url, 80) + "..."}`);
|
|
3545
3624
|
}
|
|
3546
3625
|
return true;
|
|
3547
3626
|
}
|
|
3548
|
-
if (isLocalPath(url)) {
|
|
3627
|
+
if (isLocalPath(url) || allowBareRelativeMedia && isBareRelativeMediaPath(url)) {
|
|
3549
3628
|
if (!localMediaToSend.includes(url)) {
|
|
3550
3629
|
localMediaToSend.push(url);
|
|
3551
3630
|
log?.debug?.(`Collected local media for auto-routing: ${url}`);
|
|
@@ -3554,15 +3633,15 @@ async function sendPlainReply(payload, replyText, event, actx, sendWithRetry, co
|
|
|
3554
3633
|
}
|
|
3555
3634
|
return false;
|
|
3556
3635
|
};
|
|
3557
|
-
if (payload.mediaUrls?.length) for (const url of payload.mediaUrls) collectImageUrl(url);
|
|
3558
|
-
if (payload.mediaUrl) collectImageUrl(payload.mediaUrl);
|
|
3636
|
+
if (payload.mediaUrls?.length) for (const url of payload.mediaUrls) collectImageUrl(url, true);
|
|
3637
|
+
if (payload.mediaUrl) collectImageUrl(payload.mediaUrl, true);
|
|
3559
3638
|
const mdMatches = [...replyText.matchAll(/!\[([^\]]*)\]\(([^)]+)\)/gi)];
|
|
3560
3639
|
for (const m of mdMatches) {
|
|
3561
3640
|
const url = m[2]?.trim();
|
|
3562
3641
|
if (url && !collectedImageUrls.includes(url)) {
|
|
3563
|
-
if (
|
|
3642
|
+
if (isHttpUrl(url)) {
|
|
3564
3643
|
collectedImageUrls.push(url);
|
|
3565
|
-
log?.debug?.(`Extracted HTTP image from markdown: ${url
|
|
3644
|
+
log?.debug?.(`Extracted HTTP image from markdown: ${truncateUtf16Safe(url, 80)}...`);
|
|
3566
3645
|
} else if (isLocalPath(url)) {
|
|
3567
3646
|
if (!localMediaToSend.includes(url)) {
|
|
3568
3647
|
localMediaToSend.push(url);
|
|
@@ -3576,7 +3655,7 @@ async function sendPlainReply(payload, replyText, event, actx, sendWithRetry, co
|
|
|
3576
3655
|
const url = m[1];
|
|
3577
3656
|
if (url && !collectedImageUrls.includes(url)) {
|
|
3578
3657
|
collectedImageUrls.push(url);
|
|
3579
|
-
log?.debug?.(`Extracted bare image URL: ${url
|
|
3658
|
+
log?.debug?.(`Extracted bare image URL: ${truncateUtf16Safe(url, 80)}...`);
|
|
3580
3659
|
}
|
|
3581
3660
|
}
|
|
3582
3661
|
const useMarkdown = account.markdownSupport;
|
|
@@ -3584,37 +3663,51 @@ async function sendPlainReply(payload, replyText, event, actx, sendWithRetry, co
|
|
|
3584
3663
|
let textWithoutImages = filterInternalMarkers(replyText);
|
|
3585
3664
|
for (const m of mdMatches) {
|
|
3586
3665
|
const url = m[2]?.trim();
|
|
3587
|
-
if (url && !
|
|
3666
|
+
if (url && !isHttpUrl(url) && !isLocalPath(url)) textWithoutImages = textWithoutImages.replace(m[0], "").trim();
|
|
3588
3667
|
}
|
|
3589
3668
|
if (useMarkdown) await sendMarkdownReply(textWithoutImages, collectedImageUrls, mdMatches, bareUrlMatches, event, actx, sendWithRetry, consumeQuoteRef, deps);
|
|
3590
3669
|
else await sendPlainTextReply(textWithoutImages, collectedImageUrls, mdMatches, bareUrlMatches, event, actx, sendWithRetry, consumeQuoteRef, deps);
|
|
3670
|
+
const hasVisibleTextOrInlineImage = Boolean(textWithoutImages.trim() || collectedImageUrls.length > 0);
|
|
3671
|
+
let sentMediaCount = 0;
|
|
3672
|
+
let sentFailureFallback = false;
|
|
3591
3673
|
if (localMediaToSend.length > 0) {
|
|
3592
3674
|
log?.debug?.(`Sending ${localMediaToSend.length} local media via sendMedia auto-routing`);
|
|
3593
|
-
await autoMediaBatch({
|
|
3675
|
+
sentMediaCount += await autoMediaBatch({
|
|
3594
3676
|
qualifiedTarget,
|
|
3595
3677
|
account,
|
|
3596
3678
|
replyToId: event.messageId,
|
|
3597
3679
|
mediaUrls: localMediaToSend,
|
|
3598
3680
|
mediaSender: deps.mediaSender,
|
|
3681
|
+
...actx.mediaAccess ? { mediaAccess: actx.mediaAccess } : {},
|
|
3682
|
+
...actx.mediaLocalRoots ? { mediaLocalRoots: actx.mediaLocalRoots } : {},
|
|
3683
|
+
...actx.mediaReadFile ? { mediaReadFile: actx.mediaReadFile } : {},
|
|
3599
3684
|
log,
|
|
3600
3685
|
onSuccess: (mediaPath) => `Sent local media: ${mediaPath}`,
|
|
3601
3686
|
onResultError: (mediaPath, error) => `sendMedia(auto) error for ${mediaPath}: ${error}`,
|
|
3602
3687
|
onThrownError: (mediaPath, error) => `sendMedia(auto) failed for ${mediaPath}: ${error}`
|
|
3603
3688
|
});
|
|
3689
|
+
if (!hasVisibleTextOrInlineImage && sentMediaCount === 0) {
|
|
3690
|
+
await sendTextChunks(DEFAULT_MEDIA_SEND_ERROR, event, actx, sendWithRetry, consumeQuoteRef, deps);
|
|
3691
|
+
sentFailureFallback = true;
|
|
3692
|
+
}
|
|
3604
3693
|
}
|
|
3605
3694
|
if (toolMediaUrls.length > 0) {
|
|
3606
3695
|
log?.debug?.(`Forwarding ${toolMediaUrls.length} tool-collected media URL(s) after block deliver`);
|
|
3607
|
-
await autoMediaBatch({
|
|
3696
|
+
sentMediaCount += await autoMediaBatch({
|
|
3608
3697
|
qualifiedTarget,
|
|
3609
3698
|
account,
|
|
3610
3699
|
replyToId: event.messageId,
|
|
3611
3700
|
mediaUrls: toolMediaUrls,
|
|
3612
3701
|
mediaSender: deps.mediaSender,
|
|
3702
|
+
...actx.mediaAccess ? { mediaAccess: actx.mediaAccess } : {},
|
|
3703
|
+
...actx.mediaLocalRoots ? { mediaLocalRoots: actx.mediaLocalRoots } : {},
|
|
3704
|
+
...actx.mediaReadFile ? { mediaReadFile: actx.mediaReadFile } : {},
|
|
3613
3705
|
log,
|
|
3614
|
-
onSuccess: (mediaUrl) => `Forwarded tool media: ${mediaUrl
|
|
3706
|
+
onSuccess: (mediaUrl) => `Forwarded tool media: ${truncateUtf16Safe(mediaUrl, 80)}...`,
|
|
3615
3707
|
onResultError: (_mediaUrl, error) => `Tool media forward error: ${error}`,
|
|
3616
3708
|
onThrownError: (_mediaUrl, error) => `Tool media forward failed: ${error}`
|
|
3617
3709
|
});
|
|
3710
|
+
if (!hasVisibleTextOrInlineImage && sentMediaCount === 0 && !sentFailureFallback) await sendTextChunks(DEFAULT_MEDIA_SEND_ERROR, event, actx, sendWithRetry, consumeQuoteRef, deps);
|
|
3618
3711
|
toolMediaUrls.length = 0;
|
|
3619
3712
|
}
|
|
3620
3713
|
}
|
|
@@ -3622,8 +3715,8 @@ async function sendMarkdownReply(textWithoutImages, imageUrls, mdMatches, bareUr
|
|
|
3622
3715
|
const { account, log } = actx;
|
|
3623
3716
|
const httpImageUrls = [];
|
|
3624
3717
|
const base64ImageUrls = [];
|
|
3625
|
-
for (const url of imageUrls) if (url
|
|
3626
|
-
else if (
|
|
3718
|
+
for (const url of imageUrls) if (isImageDataUrl(url)) base64ImageUrls.push(url);
|
|
3719
|
+
else if (isHttpUrl(url)) httpImageUrls.push(url);
|
|
3627
3720
|
log?.debug?.(`Image classification: httpUrls=${httpImageUrls.length}, base64=${base64ImageUrls.length}`);
|
|
3628
3721
|
if (base64ImageUrls.length > 0) {
|
|
3629
3722
|
log?.debug?.(`Sending ${base64ImageUrls.length} image(s) via Rich Media API...`);
|
|
@@ -3650,7 +3743,7 @@ async function sendMarkdownReply(textWithoutImages, imageUrls, mdMatches, bareUr
|
|
|
3650
3743
|
for (const url of httpImageUrls) if (!existingMdUrls.has(url)) try {
|
|
3651
3744
|
const size = await getImageSize(url);
|
|
3652
3745
|
imagesToAppend.push(formatQQBotMarkdownImage(url, size));
|
|
3653
|
-
log?.debug?.(`Formatted HTTP image: ${size ? `${size.width}x${size.height}` : "default size"} - ${url
|
|
3746
|
+
log?.debug?.(`Formatted HTTP image: ${size ? `${size.width}x${size.height}` : "default size"} - ${truncateUtf16Safe(url, 60)}...`);
|
|
3654
3747
|
} catch (err) {
|
|
3655
3748
|
log?.debug?.(`Failed to get image size, using default: ${formatErrorMessage(err)}`);
|
|
3656
3749
|
imagesToAppend.push(formatQQBotMarkdownImage(url, null));
|
|
@@ -3659,10 +3752,10 @@ async function sendMarkdownReply(textWithoutImages, imageUrls, mdMatches, bareUr
|
|
|
3659
3752
|
for (const m of mdMatches) {
|
|
3660
3753
|
const fullMatch = m[0];
|
|
3661
3754
|
const imgUrl = m[2];
|
|
3662
|
-
if ((imgUrl
|
|
3755
|
+
if (isHttpUrl(imgUrl) && !hasQQBotImageSize(fullMatch)) try {
|
|
3663
3756
|
const size = await getImageSize(imgUrl);
|
|
3664
3757
|
result = result.replace(fullMatch, formatQQBotMarkdownImage(imgUrl, size));
|
|
3665
|
-
log?.debug?.(`Updated image with size: ${size ? `${size.width}x${size.height}` : "default"} - ${imgUrl
|
|
3758
|
+
log?.debug?.(`Updated image with size: ${size ? `${size.width}x${size.height}` : "default"} - ${truncateUtf16Safe(imgUrl, 60)}...`);
|
|
3666
3759
|
} catch (err) {
|
|
3667
3760
|
log?.debug?.(`Failed to get image size for existing md, using default: ${formatErrorMessage(err)}`);
|
|
3668
3761
|
result = result.replace(fullMatch, formatQQBotMarkdownImage(imgUrl, null));
|
|
@@ -3687,7 +3780,7 @@ async function sendMarkdownReply(textWithoutImages, imageUrls, mdMatches, bareUr
|
|
|
3687
3780
|
}
|
|
3688
3781
|
async function sendPlainTextReply(textWithoutImages, imageUrls, mdMatches, bareUrlMatches, event, actx, sendWithRetry, consumeQuoteRef, deps) {
|
|
3689
3782
|
const { account, log } = actx;
|
|
3690
|
-
const imgMediaTarget = resolveMediaTargetContext(event,
|
|
3783
|
+
const imgMediaTarget = resolveMediaTargetContext(event, actx);
|
|
3691
3784
|
let result = textWithoutImages;
|
|
3692
3785
|
for (const m of mdMatches) result = result.replace(m[0], "").trim();
|
|
3693
3786
|
for (const m of bareUrlMatches) result = result.replace(m[0], "").trim();
|
|
@@ -3698,7 +3791,7 @@ async function sendPlainTextReply(textWithoutImages, imageUrls, mdMatches, bareU
|
|
|
3698
3791
|
imageUrl,
|
|
3699
3792
|
mediaSender: deps.mediaSender,
|
|
3700
3793
|
log,
|
|
3701
|
-
onSuccess: (nextImageUrl) => `Sent image via sendPhoto: ${nextImageUrl
|
|
3794
|
+
onSuccess: (nextImageUrl) => `Sent image via sendPhoto: ${truncateUtf16Safe(nextImageUrl, 80)}...`,
|
|
3702
3795
|
onError: (error) => `Failed to send image: ${error}`
|
|
3703
3796
|
});
|
|
3704
3797
|
if (result.trim()) await sendTextChunksWithRetry({
|
|
@@ -3798,13 +3891,27 @@ function formatMediaTypeLabel(mediaType) {
|
|
|
3798
3891
|
return mediaType[0].toUpperCase() + mediaType.slice(1);
|
|
3799
3892
|
}
|
|
3800
3893
|
function validateStructuredPayloadLocalPath(ctx, payloadPath, mediaType) {
|
|
3801
|
-
const
|
|
3802
|
-
|
|
3894
|
+
const candidatePaths = resolveWorkspacePathCandidates(normalizePath(payloadPath), ctx.mediaAccess?.workspaceDir);
|
|
3895
|
+
const localRoots = resolveWorkspaceScopedLocalRoots(resolveOutboundMediaLocalRoots(ctx), ctx.mediaAccess?.workspaceDir);
|
|
3896
|
+
const allowMissingHostRead = Boolean(resolveStructuredPayloadReadFile(ctx));
|
|
3897
|
+
for (const candidatePath of candidatePaths) {
|
|
3898
|
+
const allowedPath = resolveTrustedOutboundMediaPath(candidatePath, { allowMissing: allowMissingHostRead });
|
|
3899
|
+
if (allowedPath) return allowedPath;
|
|
3900
|
+
if (localRoots) {
|
|
3901
|
+
const scopedPath = resolveLocalPathFromRootsSync({
|
|
3902
|
+
filePath: candidatePath,
|
|
3903
|
+
roots: localRoots,
|
|
3904
|
+
label: "QQ Bot local roots",
|
|
3905
|
+
allowMissing: allowMissingHostRead
|
|
3906
|
+
})?.path;
|
|
3907
|
+
if (scopedPath) return scopedPath;
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3803
3910
|
ctx.log?.error(`Blocked ${mediaType} payload local path outside QQ Bot media storage`);
|
|
3804
3911
|
return null;
|
|
3805
3912
|
}
|
|
3806
3913
|
function isRemoteHttpUrl(p) {
|
|
3807
|
-
return
|
|
3914
|
+
return /^https?:\/\//i.test(p);
|
|
3808
3915
|
}
|
|
3809
3916
|
function isInlineImageDataUrl(p) {
|
|
3810
3917
|
return /^data:image\/[^;]+;base64,/i.test(p);
|
|
@@ -3825,7 +3932,7 @@ function resolveStructuredPayloadPath(ctx, payload, mediaType) {
|
|
|
3825
3932
|
};
|
|
3826
3933
|
}
|
|
3827
3934
|
function sanitizeForLog(value, maxLen = 200) {
|
|
3828
|
-
return value.replace(/[\r\n\t]/g, " ").replaceAll("\0", " ")
|
|
3935
|
+
return truncateUtf16Safe(value.replace(/[\r\n\t]/g, " ").replaceAll("\0", " "), maxLen);
|
|
3829
3936
|
}
|
|
3830
3937
|
function describeMediaTargetForLog(pathValue, isHttpUrl) {
|
|
3831
3938
|
if (!isHttpUrl) return "<local-file>";
|
|
@@ -3839,14 +3946,48 @@ function describeMediaTargetForLog(pathValue, isHttpUrl) {
|
|
|
3839
3946
|
return "<invalid-url>";
|
|
3840
3947
|
}
|
|
3841
3948
|
}
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3949
|
+
function resolveStructuredPayloadReadFile(ctx) {
|
|
3950
|
+
return ctx.mediaAccess?.readFile ?? ctx.mediaReadFile;
|
|
3951
|
+
}
|
|
3952
|
+
function assertBufferWithinTypeLimit(buffer, fileType) {
|
|
3953
|
+
const maxSize = getMaxUploadSize(fileType);
|
|
3954
|
+
if (buffer.length > maxSize) throw new Error(`File is too large (${formatFileSize(buffer.length)}); QQ Bot API limit is ${formatFileSize(maxSize)}`);
|
|
3955
|
+
}
|
|
3956
|
+
function imageBufferMatchesMime(buffer, mimeType) {
|
|
3957
|
+
if (mimeType === "image/png") return buffer.subarray(0, 8).equals(Buffer.from([
|
|
3958
|
+
137,
|
|
3959
|
+
80,
|
|
3960
|
+
78,
|
|
3961
|
+
71,
|
|
3962
|
+
13,
|
|
3963
|
+
10,
|
|
3964
|
+
26,
|
|
3965
|
+
10
|
|
3966
|
+
]));
|
|
3967
|
+
if (mimeType === "image/jpeg") return buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255;
|
|
3968
|
+
if (mimeType === "image/gif") {
|
|
3969
|
+
const header = buffer.subarray(0, 6).toString("ascii");
|
|
3970
|
+
return header === "GIF87a" || header === "GIF89a";
|
|
3971
|
+
}
|
|
3972
|
+
if (mimeType === "image/webp") return buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP";
|
|
3973
|
+
if (mimeType === "image/bmp") return buffer.subarray(0, 2).toString("ascii") === "BM";
|
|
3974
|
+
return false;
|
|
3975
|
+
}
|
|
3976
|
+
async function readLocalFileForInlineBase64(ctx, filePath, fileType) {
|
|
3977
|
+
const mediaReadFile = resolveStructuredPayloadReadFile(ctx);
|
|
3978
|
+
if (mediaReadFile) {
|
|
3979
|
+
let buffer = null;
|
|
3980
|
+
try {
|
|
3981
|
+
buffer = await mediaReadFile(filePath);
|
|
3982
|
+
} catch (err) {
|
|
3983
|
+
ctx.log?.debug?.(`Structured payload host read failed: ${formatErrorMessage(err)}`);
|
|
3984
|
+
}
|
|
3985
|
+
if (buffer !== null) {
|
|
3986
|
+
assertBufferWithinTypeLimit(buffer, fileType);
|
|
3987
|
+
if (buffer.length === 0) throw new Error(`File is empty: ${filePath}`);
|
|
3988
|
+
return buffer;
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3850
3991
|
const opened = await openLocalFile(filePath, { maxSize: getMaxUploadSize(fileType) });
|
|
3851
3992
|
try {
|
|
3852
3993
|
return await opened.handle.readFile();
|
|
@@ -3854,15 +3995,20 @@ async function readLocalFileForInlineBase64(filePath, fileType) {
|
|
|
3854
3995
|
await opened.close();
|
|
3855
3996
|
}
|
|
3856
3997
|
}
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3998
|
+
async function readPayloadFileBuffer(ctx, filePath, fileType) {
|
|
3999
|
+
const mediaReadFile = resolveStructuredPayloadReadFile(ctx);
|
|
4000
|
+
if (!mediaReadFile) return null;
|
|
4001
|
+
let buffer;
|
|
4002
|
+
try {
|
|
4003
|
+
buffer = await mediaReadFile(filePath);
|
|
4004
|
+
} catch (err) {
|
|
4005
|
+
ctx.log?.debug?.(`Structured payload host read failed: ${formatErrorMessage(err)}`);
|
|
4006
|
+
return null;
|
|
4007
|
+
}
|
|
4008
|
+
assertBufferWithinTypeLimit(buffer, fileType);
|
|
4009
|
+
if (buffer.length === 0) throw new Error(`File is empty: ${filePath}`);
|
|
4010
|
+
return buffer;
|
|
4011
|
+
}
|
|
3866
4012
|
async function assertLocalFileWithinTypeLimit(filePath, fileType) {
|
|
3867
4013
|
const opened = await openLocalFile(filePath, { maxSize: getMaxUploadSize(fileType) });
|
|
3868
4014
|
try {
|
|
@@ -3884,15 +4030,15 @@ async function handleImagePayload(ctx, payload) {
|
|
|
3884
4030
|
if (!imageUrl) return;
|
|
3885
4031
|
const originalImagePath = payload.source === "file" ? imageUrl : void 0;
|
|
3886
4032
|
if (payload.source === "file") try {
|
|
3887
|
-
const fileBuffer = await readLocalFileForInlineBase64(imageUrl, 1);
|
|
3888
|
-
const base64Data = fileBuffer.toString("base64");
|
|
4033
|
+
const fileBuffer = await readLocalFileForInlineBase64(ctx, imageUrl, 1);
|
|
3889
4034
|
const mimeType = getImageMimeType(imageUrl);
|
|
3890
4035
|
if (!mimeType) {
|
|
3891
4036
|
const ext = normalizeLowercaseStringOrEmpty$1(path.extname(imageUrl));
|
|
3892
4037
|
log?.error(`Unsupported image format: ${ext}`);
|
|
3893
4038
|
return;
|
|
3894
4039
|
}
|
|
3895
|
-
|
|
4040
|
+
if (!imageBufferMatchesMime(fileBuffer, mimeType)) throw new Error(`File is not an image: ${imageUrl}`);
|
|
4041
|
+
imageUrl = `data:${mimeType};base64,${fileBuffer.toString("base64")}`;
|
|
3896
4042
|
log?.debug?.(`Converted local image to Base64 (size: ${formatFileSize(fileBuffer.length)})`);
|
|
3897
4043
|
} catch (readErr) {
|
|
3898
4044
|
log?.error(`Failed to read local image: ${readErr instanceof Error ? readErr.message : JSON.stringify(readErr)}`);
|
|
@@ -3910,8 +4056,8 @@ async function handleImagePayload(ctx, payload) {
|
|
|
3910
4056
|
msgId: target.messageId,
|
|
3911
4057
|
localPathForMeta: originalImagePath
|
|
3912
4058
|
});
|
|
3913
|
-
else if (deliveryTarget.type === "dm") await sendText(deliveryTarget, ` await sendText(deliveryTarget, ``, creds, { msgId: target.messageId });
|
|
4060
|
+
else await sendText(deliveryTarget, ``, creds, { msgId: target.messageId });
|
|
3915
4061
|
}, log, account.accountId);
|
|
3916
4062
|
log?.debug?.(`Sent image via media payload`);
|
|
3917
4063
|
if (payload.caption) await sendTextToTarget(ctx, payload.caption);
|
|
@@ -3934,7 +4080,7 @@ async function sendTextAsVoiceReply(ctx, text, deps) {
|
|
|
3934
4080
|
log?.error(`Voice missing text`);
|
|
3935
4081
|
return false;
|
|
3936
4082
|
}
|
|
3937
|
-
log?.debug?.(`TTS: "${ttsText
|
|
4083
|
+
log?.debug?.(`TTS: "${truncateUtf16Safe(ttsText, 50)}..."`);
|
|
3938
4084
|
const ttsResult = await deps.tts.textToSpeech({
|
|
3939
4085
|
text: ttsText,
|
|
3940
4086
|
cfg,
|
|
@@ -4001,6 +4147,20 @@ async function handleVideoPayload(ctx, payload) {
|
|
|
4001
4147
|
msgId: target.messageId
|
|
4002
4148
|
});
|
|
4003
4149
|
else {
|
|
4150
|
+
const payloadBuffer = await readPayloadFileBuffer(ctx, videoPath, 2);
|
|
4151
|
+
if (payloadBuffer) {
|
|
4152
|
+
await sendMedia({
|
|
4153
|
+
target: deliveryTarget,
|
|
4154
|
+
creds,
|
|
4155
|
+
kind: "video",
|
|
4156
|
+
source: {
|
|
4157
|
+
buffer: payloadBuffer,
|
|
4158
|
+
fileName: sanitizeFileName(path.basename(videoPath))
|
|
4159
|
+
},
|
|
4160
|
+
msgId: target.messageId
|
|
4161
|
+
});
|
|
4162
|
+
return;
|
|
4163
|
+
}
|
|
4004
4164
|
const size = await assertLocalFileWithinTypeLimit(videoPath, 2);
|
|
4005
4165
|
log?.debug?.(`Video local (${formatFileSize(size)}): ${describeMediaTargetForLog(videoPath, false)}`);
|
|
4006
4166
|
await sendMedia({
|
|
@@ -4044,6 +4204,21 @@ async function handleFilePayload(ctx, payload) {
|
|
|
4044
4204
|
fileName
|
|
4045
4205
|
});
|
|
4046
4206
|
else {
|
|
4207
|
+
const payloadBuffer = await readPayloadFileBuffer(ctx, filePath, 4);
|
|
4208
|
+
if (payloadBuffer) {
|
|
4209
|
+
await sendMedia({
|
|
4210
|
+
target: deliveryTarget,
|
|
4211
|
+
creds,
|
|
4212
|
+
kind: "file",
|
|
4213
|
+
source: {
|
|
4214
|
+
buffer: payloadBuffer,
|
|
4215
|
+
fileName
|
|
4216
|
+
},
|
|
4217
|
+
msgId: target.messageId,
|
|
4218
|
+
fileName
|
|
4219
|
+
});
|
|
4220
|
+
return;
|
|
4221
|
+
}
|
|
4047
4222
|
const size = await assertLocalFileWithinTypeLimit(filePath, 4);
|
|
4048
4223
|
log?.debug?.(`File local (${formatFileSize(size)}): ${describeMediaTargetForLog(filePath, false)}`);
|
|
4049
4224
|
await sendMedia({
|
|
@@ -4064,6 +4239,12 @@ async function handleFilePayload(ctx, payload) {
|
|
|
4064
4239
|
}
|
|
4065
4240
|
//#endregion
|
|
4066
4241
|
//#region extensions/qqbot/src/engine/messaging/streaming-media-send.ts
|
|
4242
|
+
/**
|
|
4243
|
+
* 富媒体标签解析与发送队列
|
|
4244
|
+
*
|
|
4245
|
+
* 提供媒体标签(qqimg / qqvoice / qqvideo / qqfile / qqmedia)的检测、
|
|
4246
|
+
* 拆分、路径编码修复,以及统一的发送队列执行器。
|
|
4247
|
+
*/
|
|
4067
4248
|
function formatStreamSendErr(e) {
|
|
4068
4249
|
return e instanceof Error ? e.message : String(e);
|
|
4069
4250
|
}
|
|
@@ -4186,7 +4367,7 @@ function findFirstClosedMediaTag(text, log) {
|
|
|
4186
4367
|
* 媒体发送失败时,通过 onSendText 发送兜底文本通知用户。
|
|
4187
4368
|
*/
|
|
4188
4369
|
async function executeSendQueue(queue, ctx, options = {}) {
|
|
4189
|
-
const { mediaTarget, qualifiedTarget, account, replyToId, log } = ctx;
|
|
4370
|
+
const { mediaTarget, qualifiedTarget, account, replyToId, log, mediaAccess, mediaLocalRoots, mediaReadFile } = ctx;
|
|
4190
4371
|
const prefix = mediaTarget.logPrefix ?? `[qqbot:${account.accountId}]`;
|
|
4191
4372
|
/** 媒体发送失败时的兜底:通过 onSendText 发送错误文本给用户 */
|
|
4192
4373
|
const sendFallbackText = async (errorMsg) => {
|
|
@@ -4210,7 +4391,7 @@ async function executeSendQueue(queue, ctx, options = {}) {
|
|
|
4210
4391
|
else log?.info(`${prefix} executeSendQueue: no onSendText handler, skipping text`);
|
|
4211
4392
|
continue;
|
|
4212
4393
|
}
|
|
4213
|
-
log?.info(`${prefix} executeSendQueue: sending ${item.type}: ${item.content
|
|
4394
|
+
log?.info(`${prefix} executeSendQueue: sending ${item.type}: ${truncateUtf16Safe(item.content, 80)}...`);
|
|
4214
4395
|
if (item.type === "image") {
|
|
4215
4396
|
const result = await sendPhoto(mediaTarget, item.content);
|
|
4216
4397
|
if (result.error) {
|
|
@@ -4255,7 +4436,10 @@ async function executeSendQueue(queue, ctx, options = {}) {
|
|
|
4255
4436
|
mediaUrl: item.content,
|
|
4256
4437
|
accountId: account.accountId,
|
|
4257
4438
|
replyToId,
|
|
4258
|
-
account
|
|
4439
|
+
account,
|
|
4440
|
+
...mediaAccess ? { mediaAccess } : {},
|
|
4441
|
+
...mediaLocalRoots ? { mediaLocalRoots } : {},
|
|
4442
|
+
...mediaReadFile ? { mediaReadFile } : {}
|
|
4259
4443
|
});
|
|
4260
4444
|
if (result.error) {
|
|
4261
4445
|
log?.error(`${prefix} sendMedia(auto) error: ${result.error}`);
|
|
@@ -4414,11 +4598,11 @@ const THROTTLE_CONSTANTS = {
|
|
|
4414
4598
|
BATCH_AFTER_GAP_MS: 300
|
|
4415
4599
|
};
|
|
4416
4600
|
/** 终态集合 */
|
|
4417
|
-
const TERMINAL_PHASES = new Set(["completed", "aborted"]);
|
|
4601
|
+
const TERMINAL_PHASES = /* @__PURE__ */ new Set(["completed", "aborted"]);
|
|
4418
4602
|
/** 允许的状态转换 */
|
|
4419
4603
|
const PHASE_TRANSITIONS = {
|
|
4420
|
-
idle: new Set(["streaming", "aborted"]),
|
|
4421
|
-
streaming: new Set([
|
|
4604
|
+
idle: /* @__PURE__ */ new Set(["streaming", "aborted"]),
|
|
4605
|
+
streaming: /* @__PURE__ */ new Set([
|
|
4422
4606
|
"idle",
|
|
4423
4607
|
"completed",
|
|
4424
4608
|
"aborted"
|
|
@@ -4701,7 +4885,7 @@ var StreamingController = class {
|
|
|
4701
4885
|
*/
|
|
4702
4886
|
async onDeliver(payload) {
|
|
4703
4887
|
const rawLen = payload.text?.length ?? 0;
|
|
4704
|
-
const preview = (payload.text ?? ""
|
|
4888
|
+
const preview = truncateUtf16Safe(payload.text ?? "", 60).replace(/\n/g, "\\n");
|
|
4705
4889
|
this.logDebug(`onDeliver: rawLen=${rawLen}, phase=${this.phase}, streamMsgId=${this.streamMsgId}, sentIndex=${this.sentIndex}, sentChunks=${this.sentStreamChunkCount}, firstCB=${this.firstCallbackSource}, preview="${preview}"`);
|
|
4706
4890
|
if (this.isTerminalPhase) {
|
|
4707
4891
|
this.logDebug(`onDeliver: skipped (terminal phase)`);
|
|
@@ -4849,7 +5033,7 @@ var StreamingController = class {
|
|
|
4849
5033
|
if (this.isTerminalPhase) return;
|
|
4850
5034
|
const found = findFirstClosedMediaTag(normalizedFull.slice(this.sentIndex));
|
|
4851
5035
|
if (!found) break;
|
|
4852
|
-
this.logInfo(`processMediaTags: found <${found.tagName}> at offset ${this.sentIndex}, textBefore="${found.textBefore
|
|
5036
|
+
this.logInfo(`processMediaTags: found <${found.tagName}> at offset ${this.sentIndex}, textBefore="${truncateUtf16Safe(found.textBefore, 40)}"`);
|
|
4853
5037
|
const textBeforeEndInFull = this.sentIndex + found.textBefore.length;
|
|
4854
5038
|
await this.endCurrentStreamIfNeeded("processMediaTags:closedTag", textBeforeEndInFull);
|
|
4855
5039
|
if (this.isTerminalPhase) return;
|
|
@@ -4858,7 +5042,7 @@ var StreamingController = class {
|
|
|
4858
5042
|
type: found.itemType,
|
|
4859
5043
|
content: found.mediaPath
|
|
4860
5044
|
};
|
|
4861
|
-
this.logDebug(`processMediaTags: sending ${found.itemType}: ${found.mediaPath
|
|
5045
|
+
this.logDebug(`processMediaTags: sending ${found.itemType}: ${truncateUtf16Safe(found.mediaPath, 80)}`);
|
|
4862
5046
|
await sendMediaQueue([item], this.deps.mediaContext);
|
|
4863
5047
|
this.sentMediaCount++;
|
|
4864
5048
|
this.sentStreamChunkCount++;
|
|
@@ -5035,18 +5219,25 @@ var StreamingController = class {
|
|
|
5035
5219
|
*/
|
|
5036
5220
|
function toMediaSendContext(ctx) {
|
|
5037
5221
|
const { account, event, log } = ctx;
|
|
5222
|
+
const mediaAccessContext = {
|
|
5223
|
+
...ctx.mediaAccess ? { mediaAccess: ctx.mediaAccess } : {},
|
|
5224
|
+
...ctx.mediaLocalRoots ? { mediaLocalRoots: ctx.mediaLocalRoots } : {},
|
|
5225
|
+
...ctx.mediaReadFile ? { mediaReadFile: ctx.mediaReadFile } : {}
|
|
5226
|
+
};
|
|
5038
5227
|
return {
|
|
5039
5228
|
mediaTarget: {
|
|
5040
5229
|
targetType: event.type,
|
|
5041
5230
|
targetId: event.type === "c2c" ? event.senderId : event.type === "group" ? event.groupOpenid : event.channelId,
|
|
5042
5231
|
account,
|
|
5043
5232
|
replyToId: event.messageId,
|
|
5044
|
-
logPrefix: `[qqbot:${account.accountId}]
|
|
5233
|
+
logPrefix: `[qqbot:${account.accountId}]`,
|
|
5234
|
+
...mediaAccessContext
|
|
5045
5235
|
},
|
|
5046
5236
|
qualifiedTarget: event.type === "group" ? `qqbot:group:${event.groupOpenid}` : `qqbot:c2c:${event.senderId}`,
|
|
5047
5237
|
account,
|
|
5048
5238
|
replyToId: event.messageId,
|
|
5049
|
-
log
|
|
5239
|
+
log,
|
|
5240
|
+
...mediaAccessContext
|
|
5050
5241
|
};
|
|
5051
5242
|
}
|
|
5052
5243
|
/**
|
|
@@ -5176,7 +5367,7 @@ function isAudioFile(filePath, mimeType) {
|
|
|
5176
5367
|
".pcm"
|
|
5177
5368
|
].includes(ext);
|
|
5178
5369
|
}
|
|
5179
|
-
const QQ_NATIVE_VOICE_MIMES = new Set([
|
|
5370
|
+
const QQ_NATIVE_VOICE_MIMES = /* @__PURE__ */ new Set([
|
|
5180
5371
|
"audio/silk",
|
|
5181
5372
|
"audio/amr",
|
|
5182
5373
|
"audio/wav",
|
|
@@ -5185,7 +5376,7 @@ const QQ_NATIVE_VOICE_MIMES = new Set([
|
|
|
5185
5376
|
"audio/mpeg",
|
|
5186
5377
|
"audio/mp3"
|
|
5187
5378
|
]);
|
|
5188
|
-
const QQ_NATIVE_VOICE_EXTS = new Set([
|
|
5379
|
+
const QQ_NATIVE_VOICE_EXTS = /* @__PURE__ */ new Set([
|
|
5189
5380
|
".silk",
|
|
5190
5381
|
".slk",
|
|
5191
5382
|
".amr",
|
|
@@ -5551,6 +5742,9 @@ function blockReplyTextForDelivery(payload) {
|
|
|
5551
5742
|
function isSilentBlockReply(payload) {
|
|
5552
5743
|
return !hasReplyMedia(payload) && isSilentBlockReplyText((payload.text ?? "").trim());
|
|
5553
5744
|
}
|
|
5745
|
+
function isMediaOnlyBlockReply(payload) {
|
|
5746
|
+
return hasReplyMedia(payload) && isSilentBlockReplyText((payload.text ?? "").trim());
|
|
5747
|
+
}
|
|
5554
5748
|
/**
|
|
5555
5749
|
* Dispatch the AI reply for the given inbound context.
|
|
5556
5750
|
*
|
|
@@ -5560,6 +5754,13 @@ function isSilentBlockReply(payload) {
|
|
|
5560
5754
|
async function dispatchOutbound(inbound, deps) {
|
|
5561
5755
|
const { runtime, cfg, account, log } = deps;
|
|
5562
5756
|
const { event, qualifiedTarget } = inbound;
|
|
5757
|
+
const openClawCfg = cfg;
|
|
5758
|
+
const routeAgentId = inbound.route.agentId ?? resolveDefaultAgentId(openClawCfg);
|
|
5759
|
+
const workspaceDir = resolveAgentWorkspaceDir(openClawCfg, routeAgentId);
|
|
5760
|
+
const gatewayMediaContext = workspaceDir ? {
|
|
5761
|
+
mediaAccess: { workspaceDir },
|
|
5762
|
+
mediaLocalRoots: [workspaceDir]
|
|
5763
|
+
} : {};
|
|
5563
5764
|
const replyCtx = {
|
|
5564
5765
|
target: {
|
|
5565
5766
|
type: event.type,
|
|
@@ -5571,7 +5772,8 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5571
5772
|
},
|
|
5572
5773
|
account,
|
|
5573
5774
|
cfg,
|
|
5574
|
-
log
|
|
5775
|
+
log,
|
|
5776
|
+
...gatewayMediaContext
|
|
5575
5777
|
};
|
|
5576
5778
|
const sendWithRetry = (sendFn) => sendWithTokenRetry(account.appId, account.clientSecret, sendFn, log, account.accountId);
|
|
5577
5779
|
const sendErrorMessage = (errorText) => sendErrorToTarget(replyCtx, errorText);
|
|
@@ -5609,7 +5811,8 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5609
5811
|
mediaUrl,
|
|
5610
5812
|
accountId: account.accountId,
|
|
5611
5813
|
replyToId: event.messageId,
|
|
5612
|
-
account
|
|
5814
|
+
account,
|
|
5815
|
+
...gatewayMediaContext
|
|
5613
5816
|
}).then((r) => {
|
|
5614
5817
|
if (ac.signal.aborted) return {
|
|
5615
5818
|
channel: "qqbot",
|
|
@@ -5633,14 +5836,11 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5633
5836
|
}
|
|
5634
5837
|
};
|
|
5635
5838
|
const sendToolFallback = async () => {
|
|
5636
|
-
if (toolMediaUrls.length > 0) {
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
return;
|
|
5642
|
-
}
|
|
5643
|
-
if (toolTexts.length > 0) await sendErrorMessage(toolTexts.slice(-3).join("\n---\n").slice(0, 2e3));
|
|
5839
|
+
if (toolMediaUrls.length > 0) for (const mediaUrl of toolMediaUrls) await sendToolMediaWithTimeout(mediaUrl, {
|
|
5840
|
+
resultError: "Tool fallback error",
|
|
5841
|
+
thrownError: "Tool fallback failed"
|
|
5842
|
+
});
|
|
5843
|
+
if (toolTexts.length > 0) await sendErrorMessage(truncateUtf16Safe(toolTexts.slice(-3).join("\n---\n"), 2e3));
|
|
5644
5844
|
};
|
|
5645
5845
|
const hasPendingToolFallbackPayload = () => toolTexts.length > 0 || toolMediaUrls.length > 0;
|
|
5646
5846
|
const flushPendingToolDeliveriesOnce = async () => {
|
|
@@ -5731,7 +5931,8 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5731
5931
|
}, {
|
|
5732
5932
|
account,
|
|
5733
5933
|
qualifiedTarget,
|
|
5734
|
-
log
|
|
5934
|
+
log,
|
|
5935
|
+
...gatewayMediaContext
|
|
5735
5936
|
}, sendWithRetry, () => void 0, deliverDeps);
|
|
5736
5937
|
}
|
|
5737
5938
|
};
|
|
@@ -5740,7 +5941,7 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5740
5941
|
accountId: account.accountId,
|
|
5741
5942
|
direction: "outbound"
|
|
5742
5943
|
});
|
|
5743
|
-
const messagesConfig = runtime.channel.reply.resolveEffectiveMessagesConfig(cfg,
|
|
5944
|
+
const messagesConfig = runtime.channel.reply.resolveEffectiveMessagesConfig(cfg, routeAgentId);
|
|
5744
5945
|
const useOfficialC2cStream = shouldUseOfficialC2cStream(account, event.type === "c2c" ? "c2c" : event.type === "group" ? "group" : "channel");
|
|
5745
5946
|
const deliverToolProgressImmediately = shouldDeliverToolProgressImmediately(account, useOfficialC2cStream);
|
|
5746
5947
|
let streamingController = null;
|
|
@@ -5760,12 +5961,12 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5760
5961
|
groupOpenid: event.groupOpenid,
|
|
5761
5962
|
channelId: event.channelId
|
|
5762
5963
|
},
|
|
5763
|
-
log
|
|
5964
|
+
log,
|
|
5965
|
+
...gatewayMediaContext
|
|
5764
5966
|
}
|
|
5765
5967
|
});
|
|
5766
5968
|
const cfgWithSession = cfg;
|
|
5767
|
-
const
|
|
5768
|
-
const storePath = runtime.channel.session.resolveStorePath(cfgWithSession.session?.store, { agentId });
|
|
5969
|
+
const storePath = runtime.channel.session.resolveStorePath(cfgWithSession.session?.store, { agentId: routeAgentId });
|
|
5769
5970
|
const dispatchPromise = runtime.channel.inbound.run({
|
|
5770
5971
|
channel: "qqbot",
|
|
5771
5972
|
accountId: inbound.route.accountId,
|
|
@@ -5811,7 +6012,8 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5811
6012
|
}, {
|
|
5812
6013
|
account,
|
|
5813
6014
|
qualifiedTarget,
|
|
5814
|
-
log
|
|
6015
|
+
log,
|
|
6016
|
+
...gatewayMediaContext
|
|
5815
6017
|
}, sendWithRetry, () => void 0, deliverDeps);
|
|
5816
6018
|
recordOutbound();
|
|
5817
6019
|
return;
|
|
@@ -5829,7 +6031,8 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5829
6031
|
mediaUrl,
|
|
5830
6032
|
accountId: account.accountId,
|
|
5831
6033
|
replyToId: event.messageId,
|
|
5832
|
-
account
|
|
6034
|
+
account,
|
|
6035
|
+
...gatewayMediaContext
|
|
5833
6036
|
});
|
|
5834
6037
|
} catch {}
|
|
5835
6038
|
return;
|
|
@@ -5844,7 +6047,7 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5844
6047
|
return;
|
|
5845
6048
|
}
|
|
5846
6049
|
hasVisibleBlockResponse = true;
|
|
5847
|
-
if (streamingController && !streamingController.isTerminalPhase) {
|
|
6050
|
+
if (streamingController && !streamingController.isTerminalPhase && !isMediaOnlyBlockReply(payload)) {
|
|
5848
6051
|
try {
|
|
5849
6052
|
await streamingController.onDeliver(payload);
|
|
5850
6053
|
} catch (err) {
|
|
@@ -5881,7 +6084,8 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5881
6084
|
const deliverActx = {
|
|
5882
6085
|
account,
|
|
5883
6086
|
qualifiedTarget,
|
|
5884
|
-
log
|
|
6087
|
+
log,
|
|
6088
|
+
...gatewayMediaContext
|
|
5885
6089
|
};
|
|
5886
6090
|
const mediaResult = await parseAndSendMediaTags(replyText, deliverEvent, deliverActx, sendWithRetry, consumeQuoteRef, deliverDeps);
|
|
5887
6091
|
if (mediaResult.handled) {
|
|
@@ -6002,7 +6206,7 @@ async function buildCtxPayload(inbound, runtime, cfg) {
|
|
|
6002
6206
|
id: inbound.peerId
|
|
6003
6207
|
},
|
|
6004
6208
|
route: {
|
|
6005
|
-
agentId: inbound.route.agentId ??
|
|
6209
|
+
agentId: inbound.route.agentId ?? resolveDefaultAgentId(cfg),
|
|
6006
6210
|
routeSessionKey: inbound.route.sessionKey,
|
|
6007
6211
|
accountId: inbound.route.accountId
|
|
6008
6212
|
},
|
|
@@ -6137,7 +6341,7 @@ async function startGateway$1(ctx) {
|
|
|
6137
6341
|
initApiConfig(account.appId, { markdownSupport: account.markdownSupport });
|
|
6138
6342
|
log?.debug?.(`API config: markdownSupport=${account.markdownSupport}`);
|
|
6139
6343
|
onMessageSent(account.appId, (refIdx, meta) => {
|
|
6140
|
-
log?.info(`onMessageSent called: refIdx=${refIdx}, mediaType=${meta.mediaType}, ttsText=${meta.ttsText
|
|
6344
|
+
log?.info(`onMessageSent called: refIdx=${refIdx}, mediaType=${meta.mediaType}, ttsText=${meta.ttsText === void 0 ? void 0 : truncateUtf16Safe(meta.ttsText, 30)}`);
|
|
6141
6345
|
const attachments = [];
|
|
6142
6346
|
if (meta.mediaType) {
|
|
6143
6347
|
const localPath = meta.mediaLocalPath;
|
|
@@ -6284,6 +6488,7 @@ async function startGateway$1(ctx) {
|
|
|
6284
6488
|
onReady: ctx.onReady,
|
|
6285
6489
|
onResumed: ctx.onResumed,
|
|
6286
6490
|
onError: ctx.onError,
|
|
6491
|
+
onDisconnected: ctx.onDisconnected,
|
|
6287
6492
|
onInteraction: handleInteraction,
|
|
6288
6493
|
handleMessage
|
|
6289
6494
|
}).start();
|
|
@@ -6599,6 +6804,7 @@ async function startGateway(ctx) {
|
|
|
6599
6804
|
onReady: ctx.onReady,
|
|
6600
6805
|
onResumed: ctx.onResumed,
|
|
6601
6806
|
onError: ctx.onError,
|
|
6807
|
+
onDisconnected: ctx.onDisconnected,
|
|
6602
6808
|
log: accountLogger,
|
|
6603
6809
|
runtime,
|
|
6604
6810
|
adapters: createEngineAdapters()
|