@openclaw/qqbot 2026.7.1-beta.1 → 2026.7.1-beta.2
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-BQM6pDlD.js} +41 -30
- package/dist/{channel-entry-LMtNY-J6.js → channel-entry-C18ypTaG.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-CjdB3B6n.js} +279 -108
- package/dist/{group-DQQsZ6Y0.js → group-BmS1Qwst.js} +1 -1
- package/dist/{handler-runtime-BQ72YPsH.js → handler-runtime-rU03VA43.js} +3 -3
- package/dist/{outbound-Dfa2uOEp.js → outbound-BZHwMFUX.js} +294 -54
- package/dist/{runtime-BL0xZzWe.js → runtime-B4HWCOa-.js} +1 -1
- package/dist/runtime-api.js +1 -1
- package/dist/{sender-BHWpE_yH.js → sender-BfVLJJYp.js} +14 -12
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{tools-BvXe7bTq.js → tools-C8kTMXKc.js} +1 -1
- 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,13 +1,13 @@
|
|
|
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-BfVLJJYp.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-BQM6pDlD.js";
|
|
5
5
|
import { d as resolveAccountBase } from "./config-C1qZbh0K.js";
|
|
6
6
|
import { a as formatErrorMessage, i as formatDuration, n as debugLog, r as debugWarn, t as debugError } from "./log-SDfMMBWe.js";
|
|
7
|
-
import { n as getQQBotRuntimeForEngine, t as getQQBotRuntime } from "./runtime-
|
|
7
|
+
import { n as getQQBotRuntimeForEngine, t as getQQBotRuntime } from "./runtime-B4HWCOa-.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-BmS1Qwst.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-BZHwMFUX.js";
|
|
11
11
|
import { n as runWithRequestContext } from "./request-context-Bm7PTBD1.js";
|
|
12
12
|
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
13
13
|
import { isImplicitSameChatApprovalAuthorization } from "openclaw/plugin-sdk/approval-auth-runtime";
|
|
@@ -17,7 +17,7 @@ import * as os$1 from "node:os";
|
|
|
17
17
|
import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
|
|
18
18
|
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
19
19
|
import crypto from "node:crypto";
|
|
20
|
-
import { readRegularFileSync } from "openclaw/plugin-sdk/security-runtime";
|
|
20
|
+
import { readRegularFileSync, resolveLocalPathFromRootsSync } from "openclaw/plugin-sdk/security-runtime";
|
|
21
21
|
import * as path$1 from "node:path";
|
|
22
22
|
import path from "node:path";
|
|
23
23
|
import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
|
|
@@ -26,6 +26,7 @@ import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
|
|
|
26
26
|
import { resolveRuntimeServiceVersion } from "openclaw/plugin-sdk/cli-runtime";
|
|
27
27
|
import WebSocket from "ws";
|
|
28
28
|
import { resolveAmbientNodeProxyAgent } from "openclaw/plugin-sdk/extension-shared";
|
|
29
|
+
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "openclaw/plugin-sdk/agent-runtime";
|
|
29
30
|
import { buildChannelInboundEventContext } from "openclaw/plugin-sdk/channel-inbound";
|
|
30
31
|
import { SILENT_REPLY_TOKEN, isSilentReplyPayloadText } from "openclaw/plugin-sdk/reply-chunking";
|
|
31
32
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
@@ -3298,15 +3299,30 @@ function hasQQBotImageSize(markdownImage) {
|
|
|
3298
3299
|
//#region extensions/qqbot/src/engine/messaging/outbound-deliver.ts
|
|
3299
3300
|
/** Maximum text length for a single QQ Bot message. */
|
|
3300
3301
|
const TEXT_CHUNK_LIMIT = 5e3;
|
|
3301
|
-
function resolveMediaTargetContext(event,
|
|
3302
|
+
function resolveMediaTargetContext(event, actx) {
|
|
3303
|
+
const { account } = actx;
|
|
3302
3304
|
return {
|
|
3303
3305
|
targetType: event.type === "c2c" ? "c2c" : event.type === "group" ? "group" : event.type === "dm" ? "dm" : "channel",
|
|
3304
3306
|
targetId: event.type === "c2c" ? event.senderId : event.type === "group" ? event.groupOpenid : event.type === "dm" ? event.guildId : event.channelId,
|
|
3305
3307
|
account,
|
|
3306
|
-
replyToId: event.messageId
|
|
3308
|
+
replyToId: event.messageId,
|
|
3309
|
+
...actx.mediaAccess ? { mediaAccess: actx.mediaAccess } : {},
|
|
3310
|
+
...actx.mediaLocalRoots ? { mediaLocalRoots: actx.mediaLocalRoots } : {},
|
|
3311
|
+
...actx.mediaReadFile ? { mediaReadFile: actx.mediaReadFile } : {}
|
|
3307
3312
|
};
|
|
3308
3313
|
}
|
|
3314
|
+
function isHttpUrl(value) {
|
|
3315
|
+
return value.startsWith("http://") || value.startsWith("https://");
|
|
3316
|
+
}
|
|
3317
|
+
function isImageDataUrl(value) {
|
|
3318
|
+
return value.startsWith("data:image/");
|
|
3319
|
+
}
|
|
3320
|
+
function isBareRelativeMediaPath(value) {
|
|
3321
|
+
const trimmed = value.trim();
|
|
3322
|
+
return Boolean(trimmed) && !trimmed.startsWith("#") && !trimmed.startsWith("//") && !/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed);
|
|
3323
|
+
}
|
|
3309
3324
|
async function autoMediaBatch(params) {
|
|
3325
|
+
let sentCount = 0;
|
|
3310
3326
|
for (const mediaUrl of params.mediaUrls) try {
|
|
3311
3327
|
const result = await params.mediaSender.sendMedia({
|
|
3312
3328
|
to: params.qualifiedTarget,
|
|
@@ -3314,17 +3330,22 @@ async function autoMediaBatch(params) {
|
|
|
3314
3330
|
mediaUrl,
|
|
3315
3331
|
accountId: params.account.accountId,
|
|
3316
3332
|
replyToId: params.replyToId,
|
|
3317
|
-
account: params.account
|
|
3333
|
+
account: params.account,
|
|
3334
|
+
...params.mediaAccess ? { mediaAccess: params.mediaAccess } : {},
|
|
3335
|
+
...params.mediaLocalRoots ? { mediaLocalRoots: params.mediaLocalRoots } : {},
|
|
3336
|
+
...params.mediaReadFile ? { mediaReadFile: params.mediaReadFile } : {}
|
|
3318
3337
|
});
|
|
3319
3338
|
if (result.error) {
|
|
3320
3339
|
params.log?.error(params.onResultError(mediaUrl, result.error));
|
|
3321
3340
|
continue;
|
|
3322
3341
|
}
|
|
3342
|
+
sentCount++;
|
|
3323
3343
|
const successMessage = params.onSuccess?.(mediaUrl);
|
|
3324
3344
|
if (successMessage) params.log?.info(successMessage);
|
|
3325
3345
|
} catch (err) {
|
|
3326
3346
|
params.log?.error(params.onThrownError(mediaUrl, formatErrorMessage(err)));
|
|
3327
3347
|
}
|
|
3348
|
+
return sentCount;
|
|
3328
3349
|
}
|
|
3329
3350
|
async function sendTextChunkToTarget(params) {
|
|
3330
3351
|
const { account, event, text, consumeQuoteRef, allowDm, forcePlainText } = params;
|
|
@@ -3390,16 +3411,18 @@ async function sendWithResultLogging(params) {
|
|
|
3390
3411
|
const result = await params.run();
|
|
3391
3412
|
if (result.error) {
|
|
3392
3413
|
params.log?.error(params.onError(result.error));
|
|
3393
|
-
return;
|
|
3414
|
+
return false;
|
|
3394
3415
|
}
|
|
3395
3416
|
const successMessage = params.onSuccess?.();
|
|
3396
3417
|
if (successMessage) params.log?.info(successMessage);
|
|
3418
|
+
return true;
|
|
3397
3419
|
} catch (err) {
|
|
3398
3420
|
params.log?.error(params.onError(formatErrorMessage(err)));
|
|
3421
|
+
return false;
|
|
3399
3422
|
}
|
|
3400
3423
|
}
|
|
3401
3424
|
async function sendPhotoWithLogging(params) {
|
|
3402
|
-
await sendWithResultLogging({
|
|
3425
|
+
return await sendWithResultLogging({
|
|
3403
3426
|
run: async () => await params.mediaSender.sendPhoto(params.target, params.imageUrl),
|
|
3404
3427
|
log: params.log,
|
|
3405
3428
|
onSuccess: params.onSuccess ? () => params.onSuccess?.(params.imageUrl) : void 0,
|
|
@@ -3431,9 +3454,14 @@ async function sendVoiceWithTimeout(target, voicePath, account, mediaSender, log
|
|
|
3431
3454
|
});
|
|
3432
3455
|
}, voiceTimeout);
|
|
3433
3456
|
})]);
|
|
3434
|
-
if (result.error)
|
|
3457
|
+
if (result.error) {
|
|
3458
|
+
log?.error(`sendVoice error: ${result.error}`);
|
|
3459
|
+
return false;
|
|
3460
|
+
}
|
|
3461
|
+
return true;
|
|
3435
3462
|
} catch (err) {
|
|
3436
3463
|
log?.error(`sendVoice unexpected error: ${formatErrorMessage(err)}`);
|
|
3464
|
+
return false;
|
|
3437
3465
|
}
|
|
3438
3466
|
}
|
|
3439
3467
|
/**
|
|
@@ -3489,38 +3517,62 @@ async function parseAndSendMediaTags(replyText, event, actx, sendWithRetry, cons
|
|
|
3489
3517
|
content: filterInternalMarkers(textAfter)
|
|
3490
3518
|
});
|
|
3491
3519
|
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
|
-
})
|
|
3520
|
+
const mediaTarget = resolveMediaTargetContext(event, actx);
|
|
3521
|
+
let deliveredVisibleOutput = false;
|
|
3522
|
+
for (const item of sendQueue) if (item.type === "text") {
|
|
3523
|
+
await sendTextChunks(item.content, event, actx, sendWithRetry, consumeQuoteRef, deps);
|
|
3524
|
+
if (item.content.trim()) deliveredVisibleOutput = true;
|
|
3525
|
+
} else if (item.type === "image") {
|
|
3526
|
+
const sent = await sendPhotoWithLogging({
|
|
3527
|
+
target: mediaTarget,
|
|
3528
|
+
imageUrl: item.content,
|
|
3529
|
+
mediaSender: deps.mediaSender,
|
|
3530
|
+
log,
|
|
3531
|
+
onError: (error) => `sendPhoto error: ${error}`
|
|
3532
|
+
});
|
|
3533
|
+
deliveredVisibleOutput = deliveredVisibleOutput || sent;
|
|
3534
|
+
} else if (item.type === "voice") {
|
|
3535
|
+
const sent = await sendVoiceWithTimeout(mediaTarget, item.content, account, deps.mediaSender, log);
|
|
3536
|
+
deliveredVisibleOutput = deliveredVisibleOutput || sent;
|
|
3537
|
+
} else if (item.type === "video") {
|
|
3538
|
+
const sent = await sendWithResultLogging({
|
|
3539
|
+
run: async () => await deps.mediaSender.sendVideoMsg(mediaTarget, item.content),
|
|
3540
|
+
log,
|
|
3541
|
+
onError: (error) => `sendVideoMsg error: ${error}`
|
|
3542
|
+
});
|
|
3543
|
+
deliveredVisibleOutput = deliveredVisibleOutput || sent;
|
|
3544
|
+
} else if (item.type === "file") {
|
|
3545
|
+
const sent = await sendWithResultLogging({
|
|
3546
|
+
run: async () => await deps.mediaSender.sendDocument(mediaTarget, item.content),
|
|
3547
|
+
log,
|
|
3548
|
+
onError: (error) => `sendDocument error: ${error}`
|
|
3549
|
+
});
|
|
3550
|
+
deliveredVisibleOutput = deliveredVisibleOutput || sent;
|
|
3551
|
+
} else if (item.type === "media") {
|
|
3552
|
+
const sent = await sendWithResultLogging({
|
|
3553
|
+
run: async () => await deps.mediaSender.sendMedia({
|
|
3554
|
+
to: actx.qualifiedTarget,
|
|
3555
|
+
text: "",
|
|
3556
|
+
mediaUrl: item.content,
|
|
3557
|
+
accountId: account.accountId,
|
|
3558
|
+
replyToId: event.messageId,
|
|
3559
|
+
account,
|
|
3560
|
+
...actx.mediaAccess ? { mediaAccess: actx.mediaAccess } : {},
|
|
3561
|
+
...actx.mediaLocalRoots ? { mediaLocalRoots: actx.mediaLocalRoots } : {},
|
|
3562
|
+
...actx.mediaReadFile ? { mediaReadFile: actx.mediaReadFile } : {}
|
|
3563
|
+
}),
|
|
3564
|
+
log,
|
|
3565
|
+
onError: (error) => `sendMedia(auto) error: ${error}`
|
|
3566
|
+
});
|
|
3567
|
+
deliveredVisibleOutput = deliveredVisibleOutput || sent;
|
|
3568
|
+
}
|
|
3569
|
+
if (!deliveredVisibleOutput) {
|
|
3570
|
+
await sendTextChunks(DEFAULT_MEDIA_SEND_ERROR, event, actx, sendWithRetry, consumeQuoteRef, deps);
|
|
3571
|
+
return {
|
|
3572
|
+
handled: true,
|
|
3573
|
+
normalizedText: ""
|
|
3574
|
+
};
|
|
3575
|
+
}
|
|
3524
3576
|
return {
|
|
3525
3577
|
handled: true,
|
|
3526
3578
|
normalizedText: text
|
|
@@ -3534,18 +3586,18 @@ async function sendPlainReply(payload, replyText, event, actx, sendWithRetry, co
|
|
|
3534
3586
|
const { account, qualifiedTarget, log } = actx;
|
|
3535
3587
|
const collectedImageUrls = [];
|
|
3536
3588
|
const localMediaToSend = [];
|
|
3537
|
-
const collectImageUrl = (url) => {
|
|
3589
|
+
const collectImageUrl = (url, allowBareRelativeMedia = false) => {
|
|
3538
3590
|
if (!url) return false;
|
|
3539
|
-
const
|
|
3540
|
-
const isDataUrl = url
|
|
3541
|
-
if (
|
|
3591
|
+
const isRemoteHttpUrl = isHttpUrl(url);
|
|
3592
|
+
const isDataUrl = isImageDataUrl(url);
|
|
3593
|
+
if (isRemoteHttpUrl || isDataUrl) {
|
|
3542
3594
|
if (!collectedImageUrls.includes(url)) {
|
|
3543
3595
|
collectedImageUrls.push(url);
|
|
3544
3596
|
log?.debug?.(`Collected ${isDataUrl ? "Base64" : "media URL"}: ${isDataUrl ? `(length: ${url.length})` : url.slice(0, 80) + "..."}`);
|
|
3545
3597
|
}
|
|
3546
3598
|
return true;
|
|
3547
3599
|
}
|
|
3548
|
-
if (isLocalPath(url)) {
|
|
3600
|
+
if (isLocalPath(url) || allowBareRelativeMedia && isBareRelativeMediaPath(url)) {
|
|
3549
3601
|
if (!localMediaToSend.includes(url)) {
|
|
3550
3602
|
localMediaToSend.push(url);
|
|
3551
3603
|
log?.debug?.(`Collected local media for auto-routing: ${url}`);
|
|
@@ -3554,13 +3606,13 @@ async function sendPlainReply(payload, replyText, event, actx, sendWithRetry, co
|
|
|
3554
3606
|
}
|
|
3555
3607
|
return false;
|
|
3556
3608
|
};
|
|
3557
|
-
if (payload.mediaUrls?.length) for (const url of payload.mediaUrls) collectImageUrl(url);
|
|
3558
|
-
if (payload.mediaUrl) collectImageUrl(payload.mediaUrl);
|
|
3609
|
+
if (payload.mediaUrls?.length) for (const url of payload.mediaUrls) collectImageUrl(url, true);
|
|
3610
|
+
if (payload.mediaUrl) collectImageUrl(payload.mediaUrl, true);
|
|
3559
3611
|
const mdMatches = [...replyText.matchAll(/!\[([^\]]*)\]\(([^)]+)\)/gi)];
|
|
3560
3612
|
for (const m of mdMatches) {
|
|
3561
3613
|
const url = m[2]?.trim();
|
|
3562
3614
|
if (url && !collectedImageUrls.includes(url)) {
|
|
3563
|
-
if (
|
|
3615
|
+
if (isHttpUrl(url)) {
|
|
3564
3616
|
collectedImageUrls.push(url);
|
|
3565
3617
|
log?.debug?.(`Extracted HTTP image from markdown: ${url.slice(0, 80)}...`);
|
|
3566
3618
|
} else if (isLocalPath(url)) {
|
|
@@ -3584,37 +3636,51 @@ async function sendPlainReply(payload, replyText, event, actx, sendWithRetry, co
|
|
|
3584
3636
|
let textWithoutImages = filterInternalMarkers(replyText);
|
|
3585
3637
|
for (const m of mdMatches) {
|
|
3586
3638
|
const url = m[2]?.trim();
|
|
3587
|
-
if (url && !
|
|
3639
|
+
if (url && !isHttpUrl(url) && !isLocalPath(url)) textWithoutImages = textWithoutImages.replace(m[0], "").trim();
|
|
3588
3640
|
}
|
|
3589
3641
|
if (useMarkdown) await sendMarkdownReply(textWithoutImages, collectedImageUrls, mdMatches, bareUrlMatches, event, actx, sendWithRetry, consumeQuoteRef, deps);
|
|
3590
3642
|
else await sendPlainTextReply(textWithoutImages, collectedImageUrls, mdMatches, bareUrlMatches, event, actx, sendWithRetry, consumeQuoteRef, deps);
|
|
3643
|
+
const hasVisibleTextOrInlineImage = Boolean(textWithoutImages.trim() || collectedImageUrls.length > 0);
|
|
3644
|
+
let sentMediaCount = 0;
|
|
3645
|
+
let sentFailureFallback = false;
|
|
3591
3646
|
if (localMediaToSend.length > 0) {
|
|
3592
3647
|
log?.debug?.(`Sending ${localMediaToSend.length} local media via sendMedia auto-routing`);
|
|
3593
|
-
await autoMediaBatch({
|
|
3648
|
+
sentMediaCount += await autoMediaBatch({
|
|
3594
3649
|
qualifiedTarget,
|
|
3595
3650
|
account,
|
|
3596
3651
|
replyToId: event.messageId,
|
|
3597
3652
|
mediaUrls: localMediaToSend,
|
|
3598
3653
|
mediaSender: deps.mediaSender,
|
|
3654
|
+
...actx.mediaAccess ? { mediaAccess: actx.mediaAccess } : {},
|
|
3655
|
+
...actx.mediaLocalRoots ? { mediaLocalRoots: actx.mediaLocalRoots } : {},
|
|
3656
|
+
...actx.mediaReadFile ? { mediaReadFile: actx.mediaReadFile } : {},
|
|
3599
3657
|
log,
|
|
3600
3658
|
onSuccess: (mediaPath) => `Sent local media: ${mediaPath}`,
|
|
3601
3659
|
onResultError: (mediaPath, error) => `sendMedia(auto) error for ${mediaPath}: ${error}`,
|
|
3602
3660
|
onThrownError: (mediaPath, error) => `sendMedia(auto) failed for ${mediaPath}: ${error}`
|
|
3603
3661
|
});
|
|
3662
|
+
if (!hasVisibleTextOrInlineImage && sentMediaCount === 0) {
|
|
3663
|
+
await sendTextChunks(DEFAULT_MEDIA_SEND_ERROR, event, actx, sendWithRetry, consumeQuoteRef, deps);
|
|
3664
|
+
sentFailureFallback = true;
|
|
3665
|
+
}
|
|
3604
3666
|
}
|
|
3605
3667
|
if (toolMediaUrls.length > 0) {
|
|
3606
3668
|
log?.debug?.(`Forwarding ${toolMediaUrls.length} tool-collected media URL(s) after block deliver`);
|
|
3607
|
-
await autoMediaBatch({
|
|
3669
|
+
sentMediaCount += await autoMediaBatch({
|
|
3608
3670
|
qualifiedTarget,
|
|
3609
3671
|
account,
|
|
3610
3672
|
replyToId: event.messageId,
|
|
3611
3673
|
mediaUrls: toolMediaUrls,
|
|
3612
3674
|
mediaSender: deps.mediaSender,
|
|
3675
|
+
...actx.mediaAccess ? { mediaAccess: actx.mediaAccess } : {},
|
|
3676
|
+
...actx.mediaLocalRoots ? { mediaLocalRoots: actx.mediaLocalRoots } : {},
|
|
3677
|
+
...actx.mediaReadFile ? { mediaReadFile: actx.mediaReadFile } : {},
|
|
3613
3678
|
log,
|
|
3614
3679
|
onSuccess: (mediaUrl) => `Forwarded tool media: ${mediaUrl.slice(0, 80)}...`,
|
|
3615
3680
|
onResultError: (_mediaUrl, error) => `Tool media forward error: ${error}`,
|
|
3616
3681
|
onThrownError: (_mediaUrl, error) => `Tool media forward failed: ${error}`
|
|
3617
3682
|
});
|
|
3683
|
+
if (!hasVisibleTextOrInlineImage && sentMediaCount === 0 && !sentFailureFallback) await sendTextChunks(DEFAULT_MEDIA_SEND_ERROR, event, actx, sendWithRetry, consumeQuoteRef, deps);
|
|
3618
3684
|
toolMediaUrls.length = 0;
|
|
3619
3685
|
}
|
|
3620
3686
|
}
|
|
@@ -3622,8 +3688,8 @@ async function sendMarkdownReply(textWithoutImages, imageUrls, mdMatches, bareUr
|
|
|
3622
3688
|
const { account, log } = actx;
|
|
3623
3689
|
const httpImageUrls = [];
|
|
3624
3690
|
const base64ImageUrls = [];
|
|
3625
|
-
for (const url of imageUrls) if (url
|
|
3626
|
-
else if (
|
|
3691
|
+
for (const url of imageUrls) if (isImageDataUrl(url)) base64ImageUrls.push(url);
|
|
3692
|
+
else if (isHttpUrl(url)) httpImageUrls.push(url);
|
|
3627
3693
|
log?.debug?.(`Image classification: httpUrls=${httpImageUrls.length}, base64=${base64ImageUrls.length}`);
|
|
3628
3694
|
if (base64ImageUrls.length > 0) {
|
|
3629
3695
|
log?.debug?.(`Sending ${base64ImageUrls.length} image(s) via Rich Media API...`);
|
|
@@ -3659,7 +3725,7 @@ async function sendMarkdownReply(textWithoutImages, imageUrls, mdMatches, bareUr
|
|
|
3659
3725
|
for (const m of mdMatches) {
|
|
3660
3726
|
const fullMatch = m[0];
|
|
3661
3727
|
const imgUrl = m[2];
|
|
3662
|
-
if ((imgUrl
|
|
3728
|
+
if (isHttpUrl(imgUrl) && !hasQQBotImageSize(fullMatch)) try {
|
|
3663
3729
|
const size = await getImageSize(imgUrl);
|
|
3664
3730
|
result = result.replace(fullMatch, formatQQBotMarkdownImage(imgUrl, size));
|
|
3665
3731
|
log?.debug?.(`Updated image with size: ${size ? `${size.width}x${size.height}` : "default"} - ${imgUrl.slice(0, 60)}...`);
|
|
@@ -3687,7 +3753,7 @@ async function sendMarkdownReply(textWithoutImages, imageUrls, mdMatches, bareUr
|
|
|
3687
3753
|
}
|
|
3688
3754
|
async function sendPlainTextReply(textWithoutImages, imageUrls, mdMatches, bareUrlMatches, event, actx, sendWithRetry, consumeQuoteRef, deps) {
|
|
3689
3755
|
const { account, log } = actx;
|
|
3690
|
-
const imgMediaTarget = resolveMediaTargetContext(event,
|
|
3756
|
+
const imgMediaTarget = resolveMediaTargetContext(event, actx);
|
|
3691
3757
|
let result = textWithoutImages;
|
|
3692
3758
|
for (const m of mdMatches) result = result.replace(m[0], "").trim();
|
|
3693
3759
|
for (const m of bareUrlMatches) result = result.replace(m[0], "").trim();
|
|
@@ -3798,13 +3864,27 @@ function formatMediaTypeLabel(mediaType) {
|
|
|
3798
3864
|
return mediaType[0].toUpperCase() + mediaType.slice(1);
|
|
3799
3865
|
}
|
|
3800
3866
|
function validateStructuredPayloadLocalPath(ctx, payloadPath, mediaType) {
|
|
3801
|
-
const
|
|
3802
|
-
|
|
3867
|
+
const candidatePaths = resolveWorkspacePathCandidates(normalizePath(payloadPath), ctx.mediaAccess?.workspaceDir);
|
|
3868
|
+
const localRoots = resolveWorkspaceScopedLocalRoots(resolveOutboundMediaLocalRoots(ctx), ctx.mediaAccess?.workspaceDir);
|
|
3869
|
+
const allowMissingHostRead = Boolean(resolveStructuredPayloadReadFile(ctx));
|
|
3870
|
+
for (const candidatePath of candidatePaths) {
|
|
3871
|
+
const allowedPath = resolveTrustedOutboundMediaPath(candidatePath, { allowMissing: allowMissingHostRead });
|
|
3872
|
+
if (allowedPath) return allowedPath;
|
|
3873
|
+
if (localRoots) {
|
|
3874
|
+
const scopedPath = resolveLocalPathFromRootsSync({
|
|
3875
|
+
filePath: candidatePath,
|
|
3876
|
+
roots: localRoots,
|
|
3877
|
+
label: "QQ Bot local roots",
|
|
3878
|
+
allowMissing: allowMissingHostRead
|
|
3879
|
+
})?.path;
|
|
3880
|
+
if (scopedPath) return scopedPath;
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3803
3883
|
ctx.log?.error(`Blocked ${mediaType} payload local path outside QQ Bot media storage`);
|
|
3804
3884
|
return null;
|
|
3805
3885
|
}
|
|
3806
3886
|
function isRemoteHttpUrl(p) {
|
|
3807
|
-
return
|
|
3887
|
+
return /^https?:\/\//i.test(p);
|
|
3808
3888
|
}
|
|
3809
3889
|
function isInlineImageDataUrl(p) {
|
|
3810
3890
|
return /^data:image\/[^;]+;base64,/i.test(p);
|
|
@@ -3839,14 +3919,48 @@ function describeMediaTargetForLog(pathValue, isHttpUrl) {
|
|
|
3839
3919
|
return "<invalid-url>";
|
|
3840
3920
|
}
|
|
3841
3921
|
}
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3922
|
+
function resolveStructuredPayloadReadFile(ctx) {
|
|
3923
|
+
return ctx.mediaAccess?.readFile ?? ctx.mediaReadFile;
|
|
3924
|
+
}
|
|
3925
|
+
function assertBufferWithinTypeLimit(buffer, fileType) {
|
|
3926
|
+
const maxSize = getMaxUploadSize(fileType);
|
|
3927
|
+
if (buffer.length > maxSize) throw new Error(`File is too large (${formatFileSize(buffer.length)}); QQ Bot API limit is ${formatFileSize(maxSize)}`);
|
|
3928
|
+
}
|
|
3929
|
+
function imageBufferMatchesMime(buffer, mimeType) {
|
|
3930
|
+
if (mimeType === "image/png") return buffer.subarray(0, 8).equals(Buffer.from([
|
|
3931
|
+
137,
|
|
3932
|
+
80,
|
|
3933
|
+
78,
|
|
3934
|
+
71,
|
|
3935
|
+
13,
|
|
3936
|
+
10,
|
|
3937
|
+
26,
|
|
3938
|
+
10
|
|
3939
|
+
]));
|
|
3940
|
+
if (mimeType === "image/jpeg") return buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255;
|
|
3941
|
+
if (mimeType === "image/gif") {
|
|
3942
|
+
const header = buffer.subarray(0, 6).toString("ascii");
|
|
3943
|
+
return header === "GIF87a" || header === "GIF89a";
|
|
3944
|
+
}
|
|
3945
|
+
if (mimeType === "image/webp") return buffer.subarray(0, 4).toString("ascii") === "RIFF" && buffer.subarray(8, 12).toString("ascii") === "WEBP";
|
|
3946
|
+
if (mimeType === "image/bmp") return buffer.subarray(0, 2).toString("ascii") === "BM";
|
|
3947
|
+
return false;
|
|
3948
|
+
}
|
|
3949
|
+
async function readLocalFileForInlineBase64(ctx, filePath, fileType) {
|
|
3950
|
+
const mediaReadFile = resolveStructuredPayloadReadFile(ctx);
|
|
3951
|
+
if (mediaReadFile) {
|
|
3952
|
+
let buffer = null;
|
|
3953
|
+
try {
|
|
3954
|
+
buffer = await mediaReadFile(filePath);
|
|
3955
|
+
} catch (err) {
|
|
3956
|
+
ctx.log?.debug?.(`Structured payload host read failed: ${formatErrorMessage(err)}`);
|
|
3957
|
+
}
|
|
3958
|
+
if (buffer !== null) {
|
|
3959
|
+
assertBufferWithinTypeLimit(buffer, fileType);
|
|
3960
|
+
if (buffer.length === 0) throw new Error(`File is empty: ${filePath}`);
|
|
3961
|
+
return buffer;
|
|
3962
|
+
}
|
|
3963
|
+
}
|
|
3850
3964
|
const opened = await openLocalFile(filePath, { maxSize: getMaxUploadSize(fileType) });
|
|
3851
3965
|
try {
|
|
3852
3966
|
return await opened.handle.readFile();
|
|
@@ -3854,15 +3968,20 @@ async function readLocalFileForInlineBase64(filePath, fileType) {
|
|
|
3854
3968
|
await opened.close();
|
|
3855
3969
|
}
|
|
3856
3970
|
}
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3971
|
+
async function readPayloadFileBuffer(ctx, filePath, fileType) {
|
|
3972
|
+
const mediaReadFile = resolveStructuredPayloadReadFile(ctx);
|
|
3973
|
+
if (!mediaReadFile) return null;
|
|
3974
|
+
let buffer;
|
|
3975
|
+
try {
|
|
3976
|
+
buffer = await mediaReadFile(filePath);
|
|
3977
|
+
} catch (err) {
|
|
3978
|
+
ctx.log?.debug?.(`Structured payload host read failed: ${formatErrorMessage(err)}`);
|
|
3979
|
+
return null;
|
|
3980
|
+
}
|
|
3981
|
+
assertBufferWithinTypeLimit(buffer, fileType);
|
|
3982
|
+
if (buffer.length === 0) throw new Error(`File is empty: ${filePath}`);
|
|
3983
|
+
return buffer;
|
|
3984
|
+
}
|
|
3866
3985
|
async function assertLocalFileWithinTypeLimit(filePath, fileType) {
|
|
3867
3986
|
const opened = await openLocalFile(filePath, { maxSize: getMaxUploadSize(fileType) });
|
|
3868
3987
|
try {
|
|
@@ -3884,15 +4003,15 @@ async function handleImagePayload(ctx, payload) {
|
|
|
3884
4003
|
if (!imageUrl) return;
|
|
3885
4004
|
const originalImagePath = payload.source === "file" ? imageUrl : void 0;
|
|
3886
4005
|
if (payload.source === "file") try {
|
|
3887
|
-
const fileBuffer = await readLocalFileForInlineBase64(imageUrl, 1);
|
|
3888
|
-
const base64Data = fileBuffer.toString("base64");
|
|
4006
|
+
const fileBuffer = await readLocalFileForInlineBase64(ctx, imageUrl, 1);
|
|
3889
4007
|
const mimeType = getImageMimeType(imageUrl);
|
|
3890
4008
|
if (!mimeType) {
|
|
3891
4009
|
const ext = normalizeLowercaseStringOrEmpty$1(path.extname(imageUrl));
|
|
3892
4010
|
log?.error(`Unsupported image format: ${ext}`);
|
|
3893
4011
|
return;
|
|
3894
4012
|
}
|
|
3895
|
-
|
|
4013
|
+
if (!imageBufferMatchesMime(fileBuffer, mimeType)) throw new Error(`File is not an image: ${imageUrl}`);
|
|
4014
|
+
imageUrl = `data:${mimeType};base64,${fileBuffer.toString("base64")}`;
|
|
3896
4015
|
log?.debug?.(`Converted local image to Base64 (size: ${formatFileSize(fileBuffer.length)})`);
|
|
3897
4016
|
} catch (readErr) {
|
|
3898
4017
|
log?.error(`Failed to read local image: ${readErr instanceof Error ? readErr.message : JSON.stringify(readErr)}`);
|
|
@@ -3910,8 +4029,8 @@ async function handleImagePayload(ctx, payload) {
|
|
|
3910
4029
|
msgId: target.messageId,
|
|
3911
4030
|
localPathForMeta: originalImagePath
|
|
3912
4031
|
});
|
|
3913
|
-
else if (deliveryTarget.type === "dm") await sendText(deliveryTarget, ` await sendText(deliveryTarget, ``, creds, { msgId: target.messageId });
|
|
4033
|
+
else await sendText(deliveryTarget, ``, creds, { msgId: target.messageId });
|
|
3915
4034
|
}, log, account.accountId);
|
|
3916
4035
|
log?.debug?.(`Sent image via media payload`);
|
|
3917
4036
|
if (payload.caption) await sendTextToTarget(ctx, payload.caption);
|
|
@@ -4001,6 +4120,20 @@ async function handleVideoPayload(ctx, payload) {
|
|
|
4001
4120
|
msgId: target.messageId
|
|
4002
4121
|
});
|
|
4003
4122
|
else {
|
|
4123
|
+
const payloadBuffer = await readPayloadFileBuffer(ctx, videoPath, 2);
|
|
4124
|
+
if (payloadBuffer) {
|
|
4125
|
+
await sendMedia({
|
|
4126
|
+
target: deliveryTarget,
|
|
4127
|
+
creds,
|
|
4128
|
+
kind: "video",
|
|
4129
|
+
source: {
|
|
4130
|
+
buffer: payloadBuffer,
|
|
4131
|
+
fileName: sanitizeFileName(path.basename(videoPath))
|
|
4132
|
+
},
|
|
4133
|
+
msgId: target.messageId
|
|
4134
|
+
});
|
|
4135
|
+
return;
|
|
4136
|
+
}
|
|
4004
4137
|
const size = await assertLocalFileWithinTypeLimit(videoPath, 2);
|
|
4005
4138
|
log?.debug?.(`Video local (${formatFileSize(size)}): ${describeMediaTargetForLog(videoPath, false)}`);
|
|
4006
4139
|
await sendMedia({
|
|
@@ -4044,6 +4177,21 @@ async function handleFilePayload(ctx, payload) {
|
|
|
4044
4177
|
fileName
|
|
4045
4178
|
});
|
|
4046
4179
|
else {
|
|
4180
|
+
const payloadBuffer = await readPayloadFileBuffer(ctx, filePath, 4);
|
|
4181
|
+
if (payloadBuffer) {
|
|
4182
|
+
await sendMedia({
|
|
4183
|
+
target: deliveryTarget,
|
|
4184
|
+
creds,
|
|
4185
|
+
kind: "file",
|
|
4186
|
+
source: {
|
|
4187
|
+
buffer: payloadBuffer,
|
|
4188
|
+
fileName
|
|
4189
|
+
},
|
|
4190
|
+
msgId: target.messageId,
|
|
4191
|
+
fileName
|
|
4192
|
+
});
|
|
4193
|
+
return;
|
|
4194
|
+
}
|
|
4047
4195
|
const size = await assertLocalFileWithinTypeLimit(filePath, 4);
|
|
4048
4196
|
log?.debug?.(`File local (${formatFileSize(size)}): ${describeMediaTargetForLog(filePath, false)}`);
|
|
4049
4197
|
await sendMedia({
|
|
@@ -4186,7 +4334,7 @@ function findFirstClosedMediaTag(text, log) {
|
|
|
4186
4334
|
* 媒体发送失败时,通过 onSendText 发送兜底文本通知用户。
|
|
4187
4335
|
*/
|
|
4188
4336
|
async function executeSendQueue(queue, ctx, options = {}) {
|
|
4189
|
-
const { mediaTarget, qualifiedTarget, account, replyToId, log } = ctx;
|
|
4337
|
+
const { mediaTarget, qualifiedTarget, account, replyToId, log, mediaAccess, mediaLocalRoots, mediaReadFile } = ctx;
|
|
4190
4338
|
const prefix = mediaTarget.logPrefix ?? `[qqbot:${account.accountId}]`;
|
|
4191
4339
|
/** 媒体发送失败时的兜底:通过 onSendText 发送错误文本给用户 */
|
|
4192
4340
|
const sendFallbackText = async (errorMsg) => {
|
|
@@ -4255,7 +4403,10 @@ async function executeSendQueue(queue, ctx, options = {}) {
|
|
|
4255
4403
|
mediaUrl: item.content,
|
|
4256
4404
|
accountId: account.accountId,
|
|
4257
4405
|
replyToId,
|
|
4258
|
-
account
|
|
4406
|
+
account,
|
|
4407
|
+
...mediaAccess ? { mediaAccess } : {},
|
|
4408
|
+
...mediaLocalRoots ? { mediaLocalRoots } : {},
|
|
4409
|
+
...mediaReadFile ? { mediaReadFile } : {}
|
|
4259
4410
|
});
|
|
4260
4411
|
if (result.error) {
|
|
4261
4412
|
log?.error(`${prefix} sendMedia(auto) error: ${result.error}`);
|
|
@@ -5035,18 +5186,25 @@ var StreamingController = class {
|
|
|
5035
5186
|
*/
|
|
5036
5187
|
function toMediaSendContext(ctx) {
|
|
5037
5188
|
const { account, event, log } = ctx;
|
|
5189
|
+
const mediaAccessContext = {
|
|
5190
|
+
...ctx.mediaAccess ? { mediaAccess: ctx.mediaAccess } : {},
|
|
5191
|
+
...ctx.mediaLocalRoots ? { mediaLocalRoots: ctx.mediaLocalRoots } : {},
|
|
5192
|
+
...ctx.mediaReadFile ? { mediaReadFile: ctx.mediaReadFile } : {}
|
|
5193
|
+
};
|
|
5038
5194
|
return {
|
|
5039
5195
|
mediaTarget: {
|
|
5040
5196
|
targetType: event.type,
|
|
5041
5197
|
targetId: event.type === "c2c" ? event.senderId : event.type === "group" ? event.groupOpenid : event.channelId,
|
|
5042
5198
|
account,
|
|
5043
5199
|
replyToId: event.messageId,
|
|
5044
|
-
logPrefix: `[qqbot:${account.accountId}]
|
|
5200
|
+
logPrefix: `[qqbot:${account.accountId}]`,
|
|
5201
|
+
...mediaAccessContext
|
|
5045
5202
|
},
|
|
5046
5203
|
qualifiedTarget: event.type === "group" ? `qqbot:group:${event.groupOpenid}` : `qqbot:c2c:${event.senderId}`,
|
|
5047
5204
|
account,
|
|
5048
5205
|
replyToId: event.messageId,
|
|
5049
|
-
log
|
|
5206
|
+
log,
|
|
5207
|
+
...mediaAccessContext
|
|
5050
5208
|
};
|
|
5051
5209
|
}
|
|
5052
5210
|
/**
|
|
@@ -5551,6 +5709,9 @@ function blockReplyTextForDelivery(payload) {
|
|
|
5551
5709
|
function isSilentBlockReply(payload) {
|
|
5552
5710
|
return !hasReplyMedia(payload) && isSilentBlockReplyText((payload.text ?? "").trim());
|
|
5553
5711
|
}
|
|
5712
|
+
function isMediaOnlyBlockReply(payload) {
|
|
5713
|
+
return hasReplyMedia(payload) && isSilentBlockReplyText((payload.text ?? "").trim());
|
|
5714
|
+
}
|
|
5554
5715
|
/**
|
|
5555
5716
|
* Dispatch the AI reply for the given inbound context.
|
|
5556
5717
|
*
|
|
@@ -5560,6 +5721,13 @@ function isSilentBlockReply(payload) {
|
|
|
5560
5721
|
async function dispatchOutbound(inbound, deps) {
|
|
5561
5722
|
const { runtime, cfg, account, log } = deps;
|
|
5562
5723
|
const { event, qualifiedTarget } = inbound;
|
|
5724
|
+
const openClawCfg = cfg;
|
|
5725
|
+
const routeAgentId = inbound.route.agentId ?? resolveDefaultAgentId(openClawCfg);
|
|
5726
|
+
const workspaceDir = resolveAgentWorkspaceDir(openClawCfg, routeAgentId);
|
|
5727
|
+
const gatewayMediaContext = workspaceDir ? {
|
|
5728
|
+
mediaAccess: { workspaceDir },
|
|
5729
|
+
mediaLocalRoots: [workspaceDir]
|
|
5730
|
+
} : {};
|
|
5563
5731
|
const replyCtx = {
|
|
5564
5732
|
target: {
|
|
5565
5733
|
type: event.type,
|
|
@@ -5571,7 +5739,8 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5571
5739
|
},
|
|
5572
5740
|
account,
|
|
5573
5741
|
cfg,
|
|
5574
|
-
log
|
|
5742
|
+
log,
|
|
5743
|
+
...gatewayMediaContext
|
|
5575
5744
|
};
|
|
5576
5745
|
const sendWithRetry = (sendFn) => sendWithTokenRetry(account.appId, account.clientSecret, sendFn, log, account.accountId);
|
|
5577
5746
|
const sendErrorMessage = (errorText) => sendErrorToTarget(replyCtx, errorText);
|
|
@@ -5609,7 +5778,8 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5609
5778
|
mediaUrl,
|
|
5610
5779
|
accountId: account.accountId,
|
|
5611
5780
|
replyToId: event.messageId,
|
|
5612
|
-
account
|
|
5781
|
+
account,
|
|
5782
|
+
...gatewayMediaContext
|
|
5613
5783
|
}).then((r) => {
|
|
5614
5784
|
if (ac.signal.aborted) return {
|
|
5615
5785
|
channel: "qqbot",
|
|
@@ -5633,13 +5803,10 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5633
5803
|
}
|
|
5634
5804
|
};
|
|
5635
5805
|
const sendToolFallback = async () => {
|
|
5636
|
-
if (toolMediaUrls.length > 0) {
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
});
|
|
5641
|
-
return;
|
|
5642
|
-
}
|
|
5806
|
+
if (toolMediaUrls.length > 0) for (const mediaUrl of toolMediaUrls) await sendToolMediaWithTimeout(mediaUrl, {
|
|
5807
|
+
resultError: "Tool fallback error",
|
|
5808
|
+
thrownError: "Tool fallback failed"
|
|
5809
|
+
});
|
|
5643
5810
|
if (toolTexts.length > 0) await sendErrorMessage(toolTexts.slice(-3).join("\n---\n").slice(0, 2e3));
|
|
5644
5811
|
};
|
|
5645
5812
|
const hasPendingToolFallbackPayload = () => toolTexts.length > 0 || toolMediaUrls.length > 0;
|
|
@@ -5731,7 +5898,8 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5731
5898
|
}, {
|
|
5732
5899
|
account,
|
|
5733
5900
|
qualifiedTarget,
|
|
5734
|
-
log
|
|
5901
|
+
log,
|
|
5902
|
+
...gatewayMediaContext
|
|
5735
5903
|
}, sendWithRetry, () => void 0, deliverDeps);
|
|
5736
5904
|
}
|
|
5737
5905
|
};
|
|
@@ -5740,7 +5908,7 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5740
5908
|
accountId: account.accountId,
|
|
5741
5909
|
direction: "outbound"
|
|
5742
5910
|
});
|
|
5743
|
-
const messagesConfig = runtime.channel.reply.resolveEffectiveMessagesConfig(cfg,
|
|
5911
|
+
const messagesConfig = runtime.channel.reply.resolveEffectiveMessagesConfig(cfg, routeAgentId);
|
|
5744
5912
|
const useOfficialC2cStream = shouldUseOfficialC2cStream(account, event.type === "c2c" ? "c2c" : event.type === "group" ? "group" : "channel");
|
|
5745
5913
|
const deliverToolProgressImmediately = shouldDeliverToolProgressImmediately(account, useOfficialC2cStream);
|
|
5746
5914
|
let streamingController = null;
|
|
@@ -5760,12 +5928,12 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5760
5928
|
groupOpenid: event.groupOpenid,
|
|
5761
5929
|
channelId: event.channelId
|
|
5762
5930
|
},
|
|
5763
|
-
log
|
|
5931
|
+
log,
|
|
5932
|
+
...gatewayMediaContext
|
|
5764
5933
|
}
|
|
5765
5934
|
});
|
|
5766
5935
|
const cfgWithSession = cfg;
|
|
5767
|
-
const
|
|
5768
|
-
const storePath = runtime.channel.session.resolveStorePath(cfgWithSession.session?.store, { agentId });
|
|
5936
|
+
const storePath = runtime.channel.session.resolveStorePath(cfgWithSession.session?.store, { agentId: routeAgentId });
|
|
5769
5937
|
const dispatchPromise = runtime.channel.inbound.run({
|
|
5770
5938
|
channel: "qqbot",
|
|
5771
5939
|
accountId: inbound.route.accountId,
|
|
@@ -5811,7 +5979,8 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5811
5979
|
}, {
|
|
5812
5980
|
account,
|
|
5813
5981
|
qualifiedTarget,
|
|
5814
|
-
log
|
|
5982
|
+
log,
|
|
5983
|
+
...gatewayMediaContext
|
|
5815
5984
|
}, sendWithRetry, () => void 0, deliverDeps);
|
|
5816
5985
|
recordOutbound();
|
|
5817
5986
|
return;
|
|
@@ -5829,7 +5998,8 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5829
5998
|
mediaUrl,
|
|
5830
5999
|
accountId: account.accountId,
|
|
5831
6000
|
replyToId: event.messageId,
|
|
5832
|
-
account
|
|
6001
|
+
account,
|
|
6002
|
+
...gatewayMediaContext
|
|
5833
6003
|
});
|
|
5834
6004
|
} catch {}
|
|
5835
6005
|
return;
|
|
@@ -5844,7 +6014,7 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5844
6014
|
return;
|
|
5845
6015
|
}
|
|
5846
6016
|
hasVisibleBlockResponse = true;
|
|
5847
|
-
if (streamingController && !streamingController.isTerminalPhase) {
|
|
6017
|
+
if (streamingController && !streamingController.isTerminalPhase && !isMediaOnlyBlockReply(payload)) {
|
|
5848
6018
|
try {
|
|
5849
6019
|
await streamingController.onDeliver(payload);
|
|
5850
6020
|
} catch (err) {
|
|
@@ -5881,7 +6051,8 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5881
6051
|
const deliverActx = {
|
|
5882
6052
|
account,
|
|
5883
6053
|
qualifiedTarget,
|
|
5884
|
-
log
|
|
6054
|
+
log,
|
|
6055
|
+
...gatewayMediaContext
|
|
5885
6056
|
};
|
|
5886
6057
|
const mediaResult = await parseAndSendMediaTags(replyText, deliverEvent, deliverActx, sendWithRetry, consumeQuoteRef, deliverDeps);
|
|
5887
6058
|
if (mediaResult.handled) {
|
|
@@ -6002,7 +6173,7 @@ async function buildCtxPayload(inbound, runtime, cfg) {
|
|
|
6002
6173
|
id: inbound.peerId
|
|
6003
6174
|
},
|
|
6004
6175
|
route: {
|
|
6005
|
-
agentId: inbound.route.agentId ??
|
|
6176
|
+
agentId: inbound.route.agentId ?? resolveDefaultAgentId(cfg),
|
|
6006
6177
|
routeSessionKey: inbound.route.sessionKey,
|
|
6007
6178
|
accountId: inbound.route.accountId
|
|
6008
6179
|
},
|