@openclaw/qqbot 2026.6.11 → 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-DvWrqcZB.js → channel-BQM6pDlD.js} +71 -126
- package/dist/{channel-entry-BGXFRmbg.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/doctor-contract-api.js +168 -2
- package/dist/{gateway-CCkrdgaK.js → gateway-CjdB3B6n.js} +284 -264
- package/dist/{group-CvVXke_k.js → group-BmS1Qwst.js} +2 -2
- package/dist/{handler-runtime--OTMkFWa.js → handler-runtime-rU03VA43.js} +3 -3
- package/dist/{outbound-BQcPCo-z.js → outbound-BZHwMFUX.js} +496 -44
- package/dist/{runtime-D1hn91-s.js → runtime-B4HWCOa-.js} +1 -1
- package/dist/runtime-api.js +1 -1
- package/dist/{sender-CRZ2af7P.js → sender-BfVLJJYp.js} +16 -14
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{doctor-contract-BYTS8-ia.js → state-keys-CQKlAFyo.js} +7 -1
- package/dist/target-parser-DagYRQkP.js +71 -0
- package/dist/{tools-CeUI9pG-.js → tools-C8kTMXKc.js} +58 -13
- package/dist/tools-api.js +1 -1
- package/npm-shrinkwrap.json +3 -3
- package/package.json +4 -4
- package/skills/qqbot-channel/SKILL.md +54 -41
- package/skills/qqbot-channel/references/api_references.md +16 -8
- package/skills/qqbot-media/SKILL.md +19 -13
- package/skills/qqbot-remind/SKILL.md +15 -14
- package/dist/target-parser-BdCUmxK7.js +0 -285
|
@@ -1,23 +1,23 @@
|
|
|
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
|
|
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-
|
|
8
|
-
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-
|
|
7
|
+
import { n as getQQBotRuntimeForEngine, t as getQQBotRuntime } from "./runtime-B4HWCOa-.js";
|
|
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-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";
|
|
10
11
|
import { n as runWithRequestContext } from "./request-context-Bm7PTBD1.js";
|
|
11
|
-
import { E as DEFAULT_MEDIA_SEND_ERROR, O as setOutboundAudioPort, _ as sendVideoMsg, b as resolveUserFacingMediaError, c as isCronReminderPayload, d as normalizeMediaTags, g as sendPhoto, h as sendDocument, l as isMediaPayload, o as decodeMediaPath, r as sendMedia$1, s as encodePayloadForCron, u as parseQQBotPayload, v as sendVoice, y as resolveTrustedOutboundMediaPath } from "./outbound-BQcPCo-z.js";
|
|
12
12
|
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
13
13
|
import { isImplicitSameChatApprovalAuthorization } from "openclaw/plugin-sdk/approval-auth-runtime";
|
|
14
14
|
import * as fs$1 from "node:fs";
|
|
15
15
|
import fs from "node:fs";
|
|
16
16
|
import * as os$1 from "node:os";
|
|
17
|
-
import { readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
|
|
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 {
|
|
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";
|
|
@@ -179,19 +180,10 @@ function formatRefEntryForAgent(entry) {
|
|
|
179
180
|
//#region extensions/qqbot/src/engine/ref/store.ts
|
|
180
181
|
/**
|
|
181
182
|
* Ref-index store — SQLite KV-backed store for message reference index.
|
|
182
|
-
*
|
|
183
|
-
* Legacy JSONL entries are imported once, then deleted after SQLite has the
|
|
184
|
-
* canonical ref-index rows.
|
|
185
183
|
*/
|
|
186
184
|
const MAX_ENTRIES = 5e4;
|
|
187
185
|
const TTL_MS = 10080 * 60 * 1e3;
|
|
188
186
|
const REF_INDEX_NAMESPACE = "ref-index";
|
|
189
|
-
const REF_INDEX_MIGRATIONS_NAMESPACE = "ref-index-migrations";
|
|
190
|
-
const LEGACY_REF_INDEX_MIGRATION_KEY = "ref-index-jsonl-v1";
|
|
191
|
-
let legacyImported$1 = false;
|
|
192
|
-
function getRefIndexFile() {
|
|
193
|
-
return path.join(getQQBotDataPath("data"), "ref-index.jsonl");
|
|
194
|
-
}
|
|
195
187
|
function createRefIndexStore() {
|
|
196
188
|
return openQQBotSyncKeyedStore({
|
|
197
189
|
namespace: REF_INDEX_NAMESPACE,
|
|
@@ -199,12 +191,6 @@ function createRefIndexStore() {
|
|
|
199
191
|
defaultTtlMs: TTL_MS
|
|
200
192
|
});
|
|
201
193
|
}
|
|
202
|
-
function createRefIndexMigrationStore() {
|
|
203
|
-
return openQQBotSyncKeyedStore({
|
|
204
|
-
namespace: REF_INDEX_MIGRATIONS_NAMESPACE,
|
|
205
|
-
maxEntries: 100
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
194
|
function refIndexStateKey(refIdx) {
|
|
209
195
|
return buildQQBotStateKey("ref-index", refIdx);
|
|
210
196
|
}
|
|
@@ -240,51 +226,9 @@ function toRefIndexEntry(entry) {
|
|
|
240
226
|
...entry.attachments ? { attachments: entry.attachments.map(toStoredAttachment) } : {}
|
|
241
227
|
};
|
|
242
228
|
}
|
|
243
|
-
function ensureLegacyRefIndexImported() {
|
|
244
|
-
if (legacyImported$1) return;
|
|
245
|
-
const migrationStore = createRefIndexMigrationStore();
|
|
246
|
-
if (migrationStore.lookup(LEGACY_REF_INDEX_MIGRATION_KEY)) {
|
|
247
|
-
legacyImported$1 = true;
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
try {
|
|
251
|
-
const refIndexFile = getRefIndexFile();
|
|
252
|
-
if (!fs.existsSync(refIndexFile)) {
|
|
253
|
-
migrationStore.register(LEGACY_REF_INDEX_MIGRATION_KEY, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
254
|
-
legacyImported$1 = true;
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
const lines = fs.readFileSync(refIndexFile, "utf-8").split("\n");
|
|
258
|
-
const now = Date.now();
|
|
259
|
-
let expired = 0;
|
|
260
|
-
let imported = 0;
|
|
261
|
-
const store = createRefIndexStore();
|
|
262
|
-
for (const line of lines) {
|
|
263
|
-
const trimmed = line.trim();
|
|
264
|
-
if (!trimmed) continue;
|
|
265
|
-
try {
|
|
266
|
-
const entry = JSON.parse(trimmed);
|
|
267
|
-
if (!entry.k || !entry.v || !entry.t) continue;
|
|
268
|
-
if (now - entry.t > TTL_MS) {
|
|
269
|
-
expired++;
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
store.register(refIndexStateKey(entry.k), toStoredRefIndexEntry(entry.v, entry.t), { ttlMs: Math.max(1, TTL_MS - (now - entry.t)) });
|
|
273
|
-
imported++;
|
|
274
|
-
} catch {}
|
|
275
|
-
}
|
|
276
|
-
migrationStore.register(LEGACY_REF_INDEX_MIGRATION_KEY, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
277
|
-
legacyImported$1 = true;
|
|
278
|
-
fs.rmSync(refIndexFile, { force: true });
|
|
279
|
-
debugLog(`[ref-index-store] Migrated ${imported} entries to SQLite (${expired} expired)`);
|
|
280
|
-
} catch (err) {
|
|
281
|
-
debugError(`[ref-index-store] Failed to import legacy JSONL: ${formatErrorMessage(err)}`);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
229
|
/** Persist a refIdx mapping for one message. */
|
|
285
230
|
function setRefIndex(refIdx, entry) {
|
|
286
231
|
try {
|
|
287
|
-
ensureLegacyRefIndexImported();
|
|
288
232
|
const now = Date.now();
|
|
289
233
|
createRefIndexStore().register(refIndexStateKey(refIdx), toStoredRefIndexEntry(entry, now), { ttlMs: TTL_MS });
|
|
290
234
|
} catch (err) {
|
|
@@ -294,7 +238,6 @@ function setRefIndex(refIdx, entry) {
|
|
|
294
238
|
/** Look up one quoted message by refIdx. */
|
|
295
239
|
function getRefIndex(refIdx) {
|
|
296
240
|
try {
|
|
297
|
-
ensureLegacyRefIndexImported();
|
|
298
241
|
const store = createRefIndexStore();
|
|
299
242
|
const key = refIndexStateKey(refIdx);
|
|
300
243
|
const entry = store.lookup(key);
|
|
@@ -567,34 +510,19 @@ async function trySlashCommand(msg, ctx) {
|
|
|
567
510
|
//#region extensions/qqbot/src/engine/session/known-users.ts
|
|
568
511
|
/**
|
|
569
512
|
* Known user tracking — SQLite KV-backed store.
|
|
570
|
-
*
|
|
571
|
-
* Legacy `known-users.json` data is imported once, then deleted after SQLite
|
|
572
|
-
* has the canonical copy.
|
|
573
513
|
*/
|
|
574
|
-
function getKnownUsersFile() {
|
|
575
|
-
return path.join(getQQBotDataPath("data"), "known-users.json");
|
|
576
|
-
}
|
|
577
514
|
function makeUserKey(user) {
|
|
578
515
|
const base = `${user.accountId}:${user.type}:${user.openid}`;
|
|
579
516
|
return user.type === "group" && user.groupOpenid ? `${base}:${user.groupOpenid}` : base;
|
|
580
517
|
}
|
|
581
518
|
const KNOWN_USERS_NAMESPACE = "known-users";
|
|
582
|
-
const KNOWN_USERS_MIGRATIONS_NAMESPACE = "known-users-migrations";
|
|
583
|
-
const LEGACY_KNOWN_USERS_MIGRATION_KEY = "known-users-json-v1";
|
|
584
519
|
const MAX_KNOWN_USERS = 1e5;
|
|
585
|
-
let legacyImported = false;
|
|
586
520
|
function createKnownUsersStore() {
|
|
587
521
|
return openQQBotSyncKeyedStore({
|
|
588
522
|
namespace: KNOWN_USERS_NAMESPACE,
|
|
589
523
|
maxEntries: MAX_KNOWN_USERS
|
|
590
524
|
});
|
|
591
525
|
}
|
|
592
|
-
function createKnownUsersMigrationStore() {
|
|
593
|
-
return openQQBotSyncKeyedStore({
|
|
594
|
-
namespace: KNOWN_USERS_MIGRATIONS_NAMESPACE,
|
|
595
|
-
maxEntries: 100
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
526
|
function knownUserStateKey(key) {
|
|
599
527
|
return crypto.createHash("sha256").update(key).digest("hex");
|
|
600
528
|
}
|
|
@@ -610,32 +538,9 @@ function toStoredKnownUser(user) {
|
|
|
610
538
|
interactionCount: user.interactionCount
|
|
611
539
|
};
|
|
612
540
|
}
|
|
613
|
-
function ensureLegacyKnownUsersImported() {
|
|
614
|
-
if (legacyImported) return;
|
|
615
|
-
const migrationStore = createKnownUsersMigrationStore();
|
|
616
|
-
if (migrationStore.lookup(LEGACY_KNOWN_USERS_MIGRATION_KEY)) {
|
|
617
|
-
legacyImported = true;
|
|
618
|
-
return;
|
|
619
|
-
}
|
|
620
|
-
try {
|
|
621
|
-
const knownUsersFile = getKnownUsersFile();
|
|
622
|
-
const users = privateFileStoreSync(path.dirname(knownUsersFile)).readJsonIfExists(path.basename(knownUsersFile));
|
|
623
|
-
if (Array.isArray(users)) {
|
|
624
|
-
const store = createKnownUsersStore();
|
|
625
|
-
for (const user of users) store.registerIfAbsent(knownUserStateKey(makeUserKey(user)), toStoredKnownUser(user));
|
|
626
|
-
debugLog(`[known-users] Migrated ${users.length} users to SQLite`);
|
|
627
|
-
fs.rmSync(knownUsersFile, { force: true });
|
|
628
|
-
}
|
|
629
|
-
migrationStore.register(LEGACY_KNOWN_USERS_MIGRATION_KEY, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
630
|
-
legacyImported = true;
|
|
631
|
-
} catch (err) {
|
|
632
|
-
debugError(`[known-users] Failed to import legacy users: ${formatErrorMessage(err)}`);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
541
|
/** Record a known user whenever a message is received. */
|
|
636
542
|
function recordKnownUser(user) {
|
|
637
543
|
try {
|
|
638
|
-
ensureLegacyKnownUsersImported();
|
|
639
544
|
const store = createKnownUsersStore();
|
|
640
545
|
const stateKey = knownUserStateKey(makeUserKey(user));
|
|
641
546
|
const now = Date.now();
|
|
@@ -669,34 +574,12 @@ function recordKnownUser(user) {
|
|
|
669
574
|
//#region extensions/qqbot/src/engine/session/session-store.ts
|
|
670
575
|
/**
|
|
671
576
|
* Gateway session persistence — SQLite KV-backed store.
|
|
672
|
-
*
|
|
673
|
-
* Legacy JSON session files are imported on first account access, then
|
|
674
|
-
* removed after SQLite has the canonical short-lived session entry.
|
|
675
577
|
*/
|
|
676
578
|
const SESSION_EXPIRE_TIME = 300 * 1e3;
|
|
677
579
|
const SAVE_THROTTLE_MS = 1e3;
|
|
678
580
|
const SESSION_NAMESPACE = "gateway-sessions";
|
|
679
581
|
const MAX_SESSIONS = 1e3;
|
|
680
582
|
const throttleState = /* @__PURE__ */ new Map();
|
|
681
|
-
function getSessionDir() {
|
|
682
|
-
return getQQBotDataPath("sessions");
|
|
683
|
-
}
|
|
684
|
-
function encodeAccountIdForFileName(accountId) {
|
|
685
|
-
return Buffer.from(accountId, "utf8").toString("base64url");
|
|
686
|
-
}
|
|
687
|
-
function getLegacySessionPath(accountId) {
|
|
688
|
-
const safeId = accountId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
689
|
-
return path.join(getSessionDir(), `session-${safeId}.json`);
|
|
690
|
-
}
|
|
691
|
-
function getSessionPath(accountId) {
|
|
692
|
-
const encodedId = encodeAccountIdForFileName(accountId);
|
|
693
|
-
return path.join(getSessionDir(), `session-${encodedId}.json`);
|
|
694
|
-
}
|
|
695
|
-
function getCandidateSessionPaths(accountId) {
|
|
696
|
-
const primaryPath = getSessionPath(accountId);
|
|
697
|
-
const legacyPath = getLegacySessionPath(accountId);
|
|
698
|
-
return primaryPath === legacyPath ? [primaryPath] : [primaryPath, legacyPath];
|
|
699
|
-
}
|
|
700
583
|
function createSessionStore() {
|
|
701
584
|
return openQQBotSyncKeyedStore({
|
|
702
585
|
namespace: SESSION_NAMESPACE,
|
|
@@ -707,9 +590,6 @@ function createSessionStore() {
|
|
|
707
590
|
function sessionKey(accountId) {
|
|
708
591
|
return buildQQBotStateKey("gateway-session", accountId);
|
|
709
592
|
}
|
|
710
|
-
function remainingSessionTtlMs(state, now = Date.now()) {
|
|
711
|
-
return Math.max(1, SESSION_EXPIRE_TIME - (now - state.savedAt));
|
|
712
|
-
}
|
|
713
593
|
function toStoredSessionState(state) {
|
|
714
594
|
return {
|
|
715
595
|
sessionId: state.sessionId,
|
|
@@ -721,35 +601,11 @@ function toStoredSessionState(state) {
|
|
|
721
601
|
...state.appId ? { appId: state.appId } : {}
|
|
722
602
|
};
|
|
723
603
|
}
|
|
724
|
-
function removeFileQuietly(filePath) {
|
|
725
|
-
try {
|
|
726
|
-
fs.unlinkSync(filePath);
|
|
727
|
-
} catch {}
|
|
728
|
-
}
|
|
729
|
-
function loadLegacySession(accountId) {
|
|
730
|
-
for (const candidatePath of getCandidateSessionPaths(accountId)) {
|
|
731
|
-
const state = privateFileStoreSync(path.dirname(candidatePath)).readJsonIfExists(path.basename(candidatePath));
|
|
732
|
-
if (state) return {
|
|
733
|
-
state,
|
|
734
|
-
filePath: candidatePath
|
|
735
|
-
};
|
|
736
|
-
}
|
|
737
|
-
return null;
|
|
738
|
-
}
|
|
739
|
-
function migrateLegacySession(accountId) {
|
|
740
|
-
const legacy = loadLegacySession(accountId);
|
|
741
|
-
if (!legacy) return null;
|
|
742
|
-
const now = Date.now();
|
|
743
|
-
if (now - legacy.state.savedAt <= SESSION_EXPIRE_TIME) createSessionStore().register(sessionKey(accountId), toStoredSessionState(legacy.state), { ttlMs: remainingSessionTtlMs(legacy.state, now) });
|
|
744
|
-
for (const filePath of getCandidateSessionPaths(accountId)) removeFileQuietly(filePath);
|
|
745
|
-
return legacy.state;
|
|
746
|
-
}
|
|
747
604
|
/** Load a saved session, rejecting expired or mismatched appId entries. */
|
|
748
605
|
function loadSession(accountId, expectedAppId) {
|
|
749
606
|
try {
|
|
750
607
|
const store = createSessionStore();
|
|
751
|
-
|
|
752
|
-
if (!state) state = migrateLegacySession(accountId) ?? void 0;
|
|
608
|
+
const state = store.lookup(sessionKey(accountId));
|
|
753
609
|
if (!state) return null;
|
|
754
610
|
const now = Date.now();
|
|
755
611
|
if (now - state.savedAt > SESSION_EXPIRE_TIME) {
|
|
@@ -813,16 +669,12 @@ function saveSession(state) {
|
|
|
813
669
|
}
|
|
814
670
|
}
|
|
815
671
|
function doSaveSession(state) {
|
|
816
|
-
const filePath = getSessionPath(state.accountId);
|
|
817
|
-
const legacyPath = getLegacySessionPath(state.accountId);
|
|
818
672
|
try {
|
|
819
673
|
const stateToSave = {
|
|
820
674
|
...state,
|
|
821
675
|
savedAt: Date.now()
|
|
822
676
|
};
|
|
823
677
|
createSessionStore().register(sessionKey(state.accountId), toStoredSessionState(stateToSave), { ttlMs: SESSION_EXPIRE_TIME });
|
|
824
|
-
removeFileQuietly(filePath);
|
|
825
|
-
removeFileQuietly(legacyPath);
|
|
826
678
|
debugLog(`[session-store] Saved session for ${state.accountId}: sessionId=${state.sessionId}, lastSeq=${state.lastSeq}`);
|
|
827
679
|
} catch (err) {
|
|
828
680
|
debugError(`[session-store] Failed to save session for ${state.accountId}: ${formatErrorMessage(err)}`);
|
|
@@ -836,9 +688,7 @@ function clearSession(accountId) {
|
|
|
836
688
|
throttleState.delete(accountId);
|
|
837
689
|
}
|
|
838
690
|
try {
|
|
839
|
-
|
|
840
|
-
for (const filePath of getCandidateSessionPaths(accountId)) removeFileQuietly(filePath);
|
|
841
|
-
if (cleared) debugLog(`[session-store] Cleared session for ${accountId}`);
|
|
691
|
+
if (createSessionStore().delete(sessionKey(accountId))) debugLog(`[session-store] Cleared session for ${accountId}`);
|
|
842
692
|
} catch (err) {
|
|
843
693
|
debugError(`[session-store] Failed to clear session for ${accountId}: ${formatErrorMessage(err)}`);
|
|
844
694
|
}
|
|
@@ -1869,7 +1719,7 @@ async function transcribeAudio(audioPath, cfg) {
|
|
|
1869
1719
|
const detail = await readResponseTextLimited(resp, STT_ERROR_BODY_LIMIT_BYTES).catch(() => "");
|
|
1870
1720
|
throw new Error(`STT failed (HTTP ${resp.status}): ${detail.slice(0, 300)}`);
|
|
1871
1721
|
}
|
|
1872
|
-
return normalizeOptionalString$1((await resp.
|
|
1722
|
+
return normalizeOptionalString$1((await readProviderJsonResponse(resp, "qqbot.stt")).text) ?? null;
|
|
1873
1723
|
} finally {
|
|
1874
1724
|
await release();
|
|
1875
1725
|
}
|
|
@@ -3449,15 +3299,30 @@ function hasQQBotImageSize(markdownImage) {
|
|
|
3449
3299
|
//#region extensions/qqbot/src/engine/messaging/outbound-deliver.ts
|
|
3450
3300
|
/** Maximum text length for a single QQ Bot message. */
|
|
3451
3301
|
const TEXT_CHUNK_LIMIT = 5e3;
|
|
3452
|
-
function resolveMediaTargetContext(event,
|
|
3302
|
+
function resolveMediaTargetContext(event, actx) {
|
|
3303
|
+
const { account } = actx;
|
|
3453
3304
|
return {
|
|
3454
3305
|
targetType: event.type === "c2c" ? "c2c" : event.type === "group" ? "group" : event.type === "dm" ? "dm" : "channel",
|
|
3455
3306
|
targetId: event.type === "c2c" ? event.senderId : event.type === "group" ? event.groupOpenid : event.type === "dm" ? event.guildId : event.channelId,
|
|
3456
3307
|
account,
|
|
3457
|
-
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 } : {}
|
|
3458
3312
|
};
|
|
3459
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
|
+
}
|
|
3460
3324
|
async function autoMediaBatch(params) {
|
|
3325
|
+
let sentCount = 0;
|
|
3461
3326
|
for (const mediaUrl of params.mediaUrls) try {
|
|
3462
3327
|
const result = await params.mediaSender.sendMedia({
|
|
3463
3328
|
to: params.qualifiedTarget,
|
|
@@ -3465,17 +3330,22 @@ async function autoMediaBatch(params) {
|
|
|
3465
3330
|
mediaUrl,
|
|
3466
3331
|
accountId: params.account.accountId,
|
|
3467
3332
|
replyToId: params.replyToId,
|
|
3468
|
-
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 } : {}
|
|
3469
3337
|
});
|
|
3470
3338
|
if (result.error) {
|
|
3471
3339
|
params.log?.error(params.onResultError(mediaUrl, result.error));
|
|
3472
3340
|
continue;
|
|
3473
3341
|
}
|
|
3342
|
+
sentCount++;
|
|
3474
3343
|
const successMessage = params.onSuccess?.(mediaUrl);
|
|
3475
3344
|
if (successMessage) params.log?.info(successMessage);
|
|
3476
3345
|
} catch (err) {
|
|
3477
3346
|
params.log?.error(params.onThrownError(mediaUrl, formatErrorMessage(err)));
|
|
3478
3347
|
}
|
|
3348
|
+
return sentCount;
|
|
3479
3349
|
}
|
|
3480
3350
|
async function sendTextChunkToTarget(params) {
|
|
3481
3351
|
const { account, event, text, consumeQuoteRef, allowDm, forcePlainText } = params;
|
|
@@ -3541,16 +3411,18 @@ async function sendWithResultLogging(params) {
|
|
|
3541
3411
|
const result = await params.run();
|
|
3542
3412
|
if (result.error) {
|
|
3543
3413
|
params.log?.error(params.onError(result.error));
|
|
3544
|
-
return;
|
|
3414
|
+
return false;
|
|
3545
3415
|
}
|
|
3546
3416
|
const successMessage = params.onSuccess?.();
|
|
3547
3417
|
if (successMessage) params.log?.info(successMessage);
|
|
3418
|
+
return true;
|
|
3548
3419
|
} catch (err) {
|
|
3549
3420
|
params.log?.error(params.onError(formatErrorMessage(err)));
|
|
3421
|
+
return false;
|
|
3550
3422
|
}
|
|
3551
3423
|
}
|
|
3552
3424
|
async function sendPhotoWithLogging(params) {
|
|
3553
|
-
await sendWithResultLogging({
|
|
3425
|
+
return await sendWithResultLogging({
|
|
3554
3426
|
run: async () => await params.mediaSender.sendPhoto(params.target, params.imageUrl),
|
|
3555
3427
|
log: params.log,
|
|
3556
3428
|
onSuccess: params.onSuccess ? () => params.onSuccess?.(params.imageUrl) : void 0,
|
|
@@ -3582,9 +3454,14 @@ async function sendVoiceWithTimeout(target, voicePath, account, mediaSender, log
|
|
|
3582
3454
|
});
|
|
3583
3455
|
}, voiceTimeout);
|
|
3584
3456
|
})]);
|
|
3585
|
-
if (result.error)
|
|
3457
|
+
if (result.error) {
|
|
3458
|
+
log?.error(`sendVoice error: ${result.error}`);
|
|
3459
|
+
return false;
|
|
3460
|
+
}
|
|
3461
|
+
return true;
|
|
3586
3462
|
} catch (err) {
|
|
3587
3463
|
log?.error(`sendVoice unexpected error: ${formatErrorMessage(err)}`);
|
|
3464
|
+
return false;
|
|
3588
3465
|
}
|
|
3589
3466
|
}
|
|
3590
3467
|
/**
|
|
@@ -3640,38 +3517,62 @@ async function parseAndSendMediaTags(replyText, event, actx, sendWithRetry, cons
|
|
|
3640
3517
|
content: filterInternalMarkers(textAfter)
|
|
3641
3518
|
});
|
|
3642
3519
|
log?.debug?.(`Send queue: ${sendQueue.map((item) => item.type).join(" -> ")}`);
|
|
3643
|
-
const mediaTarget = resolveMediaTargetContext(event,
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
})
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
})
|
|
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
|
+
}
|
|
3675
3576
|
return {
|
|
3676
3577
|
handled: true,
|
|
3677
3578
|
normalizedText: text
|
|
@@ -3685,18 +3586,18 @@ async function sendPlainReply(payload, replyText, event, actx, sendWithRetry, co
|
|
|
3685
3586
|
const { account, qualifiedTarget, log } = actx;
|
|
3686
3587
|
const collectedImageUrls = [];
|
|
3687
3588
|
const localMediaToSend = [];
|
|
3688
|
-
const collectImageUrl = (url) => {
|
|
3589
|
+
const collectImageUrl = (url, allowBareRelativeMedia = false) => {
|
|
3689
3590
|
if (!url) return false;
|
|
3690
|
-
const
|
|
3691
|
-
const isDataUrl = url
|
|
3692
|
-
if (
|
|
3591
|
+
const isRemoteHttpUrl = isHttpUrl(url);
|
|
3592
|
+
const isDataUrl = isImageDataUrl(url);
|
|
3593
|
+
if (isRemoteHttpUrl || isDataUrl) {
|
|
3693
3594
|
if (!collectedImageUrls.includes(url)) {
|
|
3694
3595
|
collectedImageUrls.push(url);
|
|
3695
3596
|
log?.debug?.(`Collected ${isDataUrl ? "Base64" : "media URL"}: ${isDataUrl ? `(length: ${url.length})` : url.slice(0, 80) + "..."}`);
|
|
3696
3597
|
}
|
|
3697
3598
|
return true;
|
|
3698
3599
|
}
|
|
3699
|
-
if (isLocalPath(url)) {
|
|
3600
|
+
if (isLocalPath(url) || allowBareRelativeMedia && isBareRelativeMediaPath(url)) {
|
|
3700
3601
|
if (!localMediaToSend.includes(url)) {
|
|
3701
3602
|
localMediaToSend.push(url);
|
|
3702
3603
|
log?.debug?.(`Collected local media for auto-routing: ${url}`);
|
|
@@ -3705,13 +3606,13 @@ async function sendPlainReply(payload, replyText, event, actx, sendWithRetry, co
|
|
|
3705
3606
|
}
|
|
3706
3607
|
return false;
|
|
3707
3608
|
};
|
|
3708
|
-
if (payload.mediaUrls?.length) for (const url of payload.mediaUrls) collectImageUrl(url);
|
|
3709
|
-
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);
|
|
3710
3611
|
const mdMatches = [...replyText.matchAll(/!\[([^\]]*)\]\(([^)]+)\)/gi)];
|
|
3711
3612
|
for (const m of mdMatches) {
|
|
3712
3613
|
const url = m[2]?.trim();
|
|
3713
3614
|
if (url && !collectedImageUrls.includes(url)) {
|
|
3714
|
-
if (
|
|
3615
|
+
if (isHttpUrl(url)) {
|
|
3715
3616
|
collectedImageUrls.push(url);
|
|
3716
3617
|
log?.debug?.(`Extracted HTTP image from markdown: ${url.slice(0, 80)}...`);
|
|
3717
3618
|
} else if (isLocalPath(url)) {
|
|
@@ -3735,37 +3636,51 @@ async function sendPlainReply(payload, replyText, event, actx, sendWithRetry, co
|
|
|
3735
3636
|
let textWithoutImages = filterInternalMarkers(replyText);
|
|
3736
3637
|
for (const m of mdMatches) {
|
|
3737
3638
|
const url = m[2]?.trim();
|
|
3738
|
-
if (url && !
|
|
3639
|
+
if (url && !isHttpUrl(url) && !isLocalPath(url)) textWithoutImages = textWithoutImages.replace(m[0], "").trim();
|
|
3739
3640
|
}
|
|
3740
3641
|
if (useMarkdown) await sendMarkdownReply(textWithoutImages, collectedImageUrls, mdMatches, bareUrlMatches, event, actx, sendWithRetry, consumeQuoteRef, deps);
|
|
3741
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;
|
|
3742
3646
|
if (localMediaToSend.length > 0) {
|
|
3743
3647
|
log?.debug?.(`Sending ${localMediaToSend.length} local media via sendMedia auto-routing`);
|
|
3744
|
-
await autoMediaBatch({
|
|
3648
|
+
sentMediaCount += await autoMediaBatch({
|
|
3745
3649
|
qualifiedTarget,
|
|
3746
3650
|
account,
|
|
3747
3651
|
replyToId: event.messageId,
|
|
3748
3652
|
mediaUrls: localMediaToSend,
|
|
3749
3653
|
mediaSender: deps.mediaSender,
|
|
3654
|
+
...actx.mediaAccess ? { mediaAccess: actx.mediaAccess } : {},
|
|
3655
|
+
...actx.mediaLocalRoots ? { mediaLocalRoots: actx.mediaLocalRoots } : {},
|
|
3656
|
+
...actx.mediaReadFile ? { mediaReadFile: actx.mediaReadFile } : {},
|
|
3750
3657
|
log,
|
|
3751
3658
|
onSuccess: (mediaPath) => `Sent local media: ${mediaPath}`,
|
|
3752
3659
|
onResultError: (mediaPath, error) => `sendMedia(auto) error for ${mediaPath}: ${error}`,
|
|
3753
3660
|
onThrownError: (mediaPath, error) => `sendMedia(auto) failed for ${mediaPath}: ${error}`
|
|
3754
3661
|
});
|
|
3662
|
+
if (!hasVisibleTextOrInlineImage && sentMediaCount === 0) {
|
|
3663
|
+
await sendTextChunks(DEFAULT_MEDIA_SEND_ERROR, event, actx, sendWithRetry, consumeQuoteRef, deps);
|
|
3664
|
+
sentFailureFallback = true;
|
|
3665
|
+
}
|
|
3755
3666
|
}
|
|
3756
3667
|
if (toolMediaUrls.length > 0) {
|
|
3757
3668
|
log?.debug?.(`Forwarding ${toolMediaUrls.length} tool-collected media URL(s) after block deliver`);
|
|
3758
|
-
await autoMediaBatch({
|
|
3669
|
+
sentMediaCount += await autoMediaBatch({
|
|
3759
3670
|
qualifiedTarget,
|
|
3760
3671
|
account,
|
|
3761
3672
|
replyToId: event.messageId,
|
|
3762
3673
|
mediaUrls: toolMediaUrls,
|
|
3763
3674
|
mediaSender: deps.mediaSender,
|
|
3675
|
+
...actx.mediaAccess ? { mediaAccess: actx.mediaAccess } : {},
|
|
3676
|
+
...actx.mediaLocalRoots ? { mediaLocalRoots: actx.mediaLocalRoots } : {},
|
|
3677
|
+
...actx.mediaReadFile ? { mediaReadFile: actx.mediaReadFile } : {},
|
|
3764
3678
|
log,
|
|
3765
3679
|
onSuccess: (mediaUrl) => `Forwarded tool media: ${mediaUrl.slice(0, 80)}...`,
|
|
3766
3680
|
onResultError: (_mediaUrl, error) => `Tool media forward error: ${error}`,
|
|
3767
3681
|
onThrownError: (_mediaUrl, error) => `Tool media forward failed: ${error}`
|
|
3768
3682
|
});
|
|
3683
|
+
if (!hasVisibleTextOrInlineImage && sentMediaCount === 0 && !sentFailureFallback) await sendTextChunks(DEFAULT_MEDIA_SEND_ERROR, event, actx, sendWithRetry, consumeQuoteRef, deps);
|
|
3769
3684
|
toolMediaUrls.length = 0;
|
|
3770
3685
|
}
|
|
3771
3686
|
}
|
|
@@ -3773,8 +3688,8 @@ async function sendMarkdownReply(textWithoutImages, imageUrls, mdMatches, bareUr
|
|
|
3773
3688
|
const { account, log } = actx;
|
|
3774
3689
|
const httpImageUrls = [];
|
|
3775
3690
|
const base64ImageUrls = [];
|
|
3776
|
-
for (const url of imageUrls) if (url
|
|
3777
|
-
else if (
|
|
3691
|
+
for (const url of imageUrls) if (isImageDataUrl(url)) base64ImageUrls.push(url);
|
|
3692
|
+
else if (isHttpUrl(url)) httpImageUrls.push(url);
|
|
3778
3693
|
log?.debug?.(`Image classification: httpUrls=${httpImageUrls.length}, base64=${base64ImageUrls.length}`);
|
|
3779
3694
|
if (base64ImageUrls.length > 0) {
|
|
3780
3695
|
log?.debug?.(`Sending ${base64ImageUrls.length} image(s) via Rich Media API...`);
|
|
@@ -3810,7 +3725,7 @@ async function sendMarkdownReply(textWithoutImages, imageUrls, mdMatches, bareUr
|
|
|
3810
3725
|
for (const m of mdMatches) {
|
|
3811
3726
|
const fullMatch = m[0];
|
|
3812
3727
|
const imgUrl = m[2];
|
|
3813
|
-
if ((imgUrl
|
|
3728
|
+
if (isHttpUrl(imgUrl) && !hasQQBotImageSize(fullMatch)) try {
|
|
3814
3729
|
const size = await getImageSize(imgUrl);
|
|
3815
3730
|
result = result.replace(fullMatch, formatQQBotMarkdownImage(imgUrl, size));
|
|
3816
3731
|
log?.debug?.(`Updated image with size: ${size ? `${size.width}x${size.height}` : "default"} - ${imgUrl.slice(0, 60)}...`);
|
|
@@ -3838,7 +3753,7 @@ async function sendMarkdownReply(textWithoutImages, imageUrls, mdMatches, bareUr
|
|
|
3838
3753
|
}
|
|
3839
3754
|
async function sendPlainTextReply(textWithoutImages, imageUrls, mdMatches, bareUrlMatches, event, actx, sendWithRetry, consumeQuoteRef, deps) {
|
|
3840
3755
|
const { account, log } = actx;
|
|
3841
|
-
const imgMediaTarget = resolveMediaTargetContext(event,
|
|
3756
|
+
const imgMediaTarget = resolveMediaTargetContext(event, actx);
|
|
3842
3757
|
let result = textWithoutImages;
|
|
3843
3758
|
for (const m of mdMatches) result = result.replace(m[0], "").trim();
|
|
3844
3759
|
for (const m of bareUrlMatches) result = result.replace(m[0], "").trim();
|
|
@@ -3949,13 +3864,27 @@ function formatMediaTypeLabel(mediaType) {
|
|
|
3949
3864
|
return mediaType[0].toUpperCase() + mediaType.slice(1);
|
|
3950
3865
|
}
|
|
3951
3866
|
function validateStructuredPayloadLocalPath(ctx, payloadPath, mediaType) {
|
|
3952
|
-
const
|
|
3953
|
-
|
|
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
|
+
}
|
|
3954
3883
|
ctx.log?.error(`Blocked ${mediaType} payload local path outside QQ Bot media storage`);
|
|
3955
3884
|
return null;
|
|
3956
3885
|
}
|
|
3957
3886
|
function isRemoteHttpUrl(p) {
|
|
3958
|
-
return
|
|
3887
|
+
return /^https?:\/\//i.test(p);
|
|
3959
3888
|
}
|
|
3960
3889
|
function isInlineImageDataUrl(p) {
|
|
3961
3890
|
return /^data:image\/[^;]+;base64,/i.test(p);
|
|
@@ -3990,14 +3919,48 @@ function describeMediaTargetForLog(pathValue, isHttpUrl) {
|
|
|
3990
3919
|
return "<invalid-url>";
|
|
3991
3920
|
}
|
|
3992
3921
|
}
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
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
|
+
}
|
|
4001
3964
|
const opened = await openLocalFile(filePath, { maxSize: getMaxUploadSize(fileType) });
|
|
4002
3965
|
try {
|
|
4003
3966
|
return await opened.handle.readFile();
|
|
@@ -4005,15 +3968,20 @@ async function readLocalFileForInlineBase64(filePath, fileType) {
|
|
|
4005
3968
|
await opened.close();
|
|
4006
3969
|
}
|
|
4007
3970
|
}
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
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
|
+
}
|
|
4017
3985
|
async function assertLocalFileWithinTypeLimit(filePath, fileType) {
|
|
4018
3986
|
const opened = await openLocalFile(filePath, { maxSize: getMaxUploadSize(fileType) });
|
|
4019
3987
|
try {
|
|
@@ -4035,15 +4003,15 @@ async function handleImagePayload(ctx, payload) {
|
|
|
4035
4003
|
if (!imageUrl) return;
|
|
4036
4004
|
const originalImagePath = payload.source === "file" ? imageUrl : void 0;
|
|
4037
4005
|
if (payload.source === "file") try {
|
|
4038
|
-
const fileBuffer = await readLocalFileForInlineBase64(imageUrl, 1);
|
|
4039
|
-
const base64Data = fileBuffer.toString("base64");
|
|
4006
|
+
const fileBuffer = await readLocalFileForInlineBase64(ctx, imageUrl, 1);
|
|
4040
4007
|
const mimeType = getImageMimeType(imageUrl);
|
|
4041
4008
|
if (!mimeType) {
|
|
4042
4009
|
const ext = normalizeLowercaseStringOrEmpty$1(path.extname(imageUrl));
|
|
4043
4010
|
log?.error(`Unsupported image format: ${ext}`);
|
|
4044
4011
|
return;
|
|
4045
4012
|
}
|
|
4046
|
-
|
|
4013
|
+
if (!imageBufferMatchesMime(fileBuffer, mimeType)) throw new Error(`File is not an image: ${imageUrl}`);
|
|
4014
|
+
imageUrl = `data:${mimeType};base64,${fileBuffer.toString("base64")}`;
|
|
4047
4015
|
log?.debug?.(`Converted local image to Base64 (size: ${formatFileSize(fileBuffer.length)})`);
|
|
4048
4016
|
} catch (readErr) {
|
|
4049
4017
|
log?.error(`Failed to read local image: ${readErr instanceof Error ? readErr.message : JSON.stringify(readErr)}`);
|
|
@@ -4061,8 +4029,8 @@ async function handleImagePayload(ctx, payload) {
|
|
|
4061
4029
|
msgId: target.messageId,
|
|
4062
4030
|
localPathForMeta: originalImagePath
|
|
4063
4031
|
});
|
|
4064
|
-
else if (deliveryTarget.type === "dm") await sendText(deliveryTarget, ` await sendText(deliveryTarget, ``, creds, { msgId: target.messageId });
|
|
4033
|
+
else await sendText(deliveryTarget, ``, creds, { msgId: target.messageId });
|
|
4066
4034
|
}, log, account.accountId);
|
|
4067
4035
|
log?.debug?.(`Sent image via media payload`);
|
|
4068
4036
|
if (payload.caption) await sendTextToTarget(ctx, payload.caption);
|
|
@@ -4152,6 +4120,20 @@ async function handleVideoPayload(ctx, payload) {
|
|
|
4152
4120
|
msgId: target.messageId
|
|
4153
4121
|
});
|
|
4154
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
|
+
}
|
|
4155
4137
|
const size = await assertLocalFileWithinTypeLimit(videoPath, 2);
|
|
4156
4138
|
log?.debug?.(`Video local (${formatFileSize(size)}): ${describeMediaTargetForLog(videoPath, false)}`);
|
|
4157
4139
|
await sendMedia({
|
|
@@ -4195,6 +4177,21 @@ async function handleFilePayload(ctx, payload) {
|
|
|
4195
4177
|
fileName
|
|
4196
4178
|
});
|
|
4197
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
|
+
}
|
|
4198
4195
|
const size = await assertLocalFileWithinTypeLimit(filePath, 4);
|
|
4199
4196
|
log?.debug?.(`File local (${formatFileSize(size)}): ${describeMediaTargetForLog(filePath, false)}`);
|
|
4200
4197
|
await sendMedia({
|
|
@@ -4337,7 +4334,7 @@ function findFirstClosedMediaTag(text, log) {
|
|
|
4337
4334
|
* 媒体发送失败时,通过 onSendText 发送兜底文本通知用户。
|
|
4338
4335
|
*/
|
|
4339
4336
|
async function executeSendQueue(queue, ctx, options = {}) {
|
|
4340
|
-
const { mediaTarget, qualifiedTarget, account, replyToId, log } = ctx;
|
|
4337
|
+
const { mediaTarget, qualifiedTarget, account, replyToId, log, mediaAccess, mediaLocalRoots, mediaReadFile } = ctx;
|
|
4341
4338
|
const prefix = mediaTarget.logPrefix ?? `[qqbot:${account.accountId}]`;
|
|
4342
4339
|
/** 媒体发送失败时的兜底:通过 onSendText 发送错误文本给用户 */
|
|
4343
4340
|
const sendFallbackText = async (errorMsg) => {
|
|
@@ -4406,7 +4403,10 @@ async function executeSendQueue(queue, ctx, options = {}) {
|
|
|
4406
4403
|
mediaUrl: item.content,
|
|
4407
4404
|
accountId: account.accountId,
|
|
4408
4405
|
replyToId,
|
|
4409
|
-
account
|
|
4406
|
+
account,
|
|
4407
|
+
...mediaAccess ? { mediaAccess } : {},
|
|
4408
|
+
...mediaLocalRoots ? { mediaLocalRoots } : {},
|
|
4409
|
+
...mediaReadFile ? { mediaReadFile } : {}
|
|
4410
4410
|
});
|
|
4411
4411
|
if (result.error) {
|
|
4412
4412
|
log?.error(`${prefix} sendMedia(auto) error: ${result.error}`);
|
|
@@ -5186,18 +5186,25 @@ var StreamingController = class {
|
|
|
5186
5186
|
*/
|
|
5187
5187
|
function toMediaSendContext(ctx) {
|
|
5188
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
|
+
};
|
|
5189
5194
|
return {
|
|
5190
5195
|
mediaTarget: {
|
|
5191
5196
|
targetType: event.type,
|
|
5192
5197
|
targetId: event.type === "c2c" ? event.senderId : event.type === "group" ? event.groupOpenid : event.channelId,
|
|
5193
5198
|
account,
|
|
5194
5199
|
replyToId: event.messageId,
|
|
5195
|
-
logPrefix: `[qqbot:${account.accountId}]
|
|
5200
|
+
logPrefix: `[qqbot:${account.accountId}]`,
|
|
5201
|
+
...mediaAccessContext
|
|
5196
5202
|
},
|
|
5197
5203
|
qualifiedTarget: event.type === "group" ? `qqbot:group:${event.groupOpenid}` : `qqbot:c2c:${event.senderId}`,
|
|
5198
5204
|
account,
|
|
5199
5205
|
replyToId: event.messageId,
|
|
5200
|
-
log
|
|
5206
|
+
log,
|
|
5207
|
+
...mediaAccessContext
|
|
5201
5208
|
};
|
|
5202
5209
|
}
|
|
5203
5210
|
/**
|
|
@@ -5702,6 +5709,9 @@ function blockReplyTextForDelivery(payload) {
|
|
|
5702
5709
|
function isSilentBlockReply(payload) {
|
|
5703
5710
|
return !hasReplyMedia(payload) && isSilentBlockReplyText((payload.text ?? "").trim());
|
|
5704
5711
|
}
|
|
5712
|
+
function isMediaOnlyBlockReply(payload) {
|
|
5713
|
+
return hasReplyMedia(payload) && isSilentBlockReplyText((payload.text ?? "").trim());
|
|
5714
|
+
}
|
|
5705
5715
|
/**
|
|
5706
5716
|
* Dispatch the AI reply for the given inbound context.
|
|
5707
5717
|
*
|
|
@@ -5711,6 +5721,13 @@ function isSilentBlockReply(payload) {
|
|
|
5711
5721
|
async function dispatchOutbound(inbound, deps) {
|
|
5712
5722
|
const { runtime, cfg, account, log } = deps;
|
|
5713
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
|
+
} : {};
|
|
5714
5731
|
const replyCtx = {
|
|
5715
5732
|
target: {
|
|
5716
5733
|
type: event.type,
|
|
@@ -5722,7 +5739,8 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5722
5739
|
},
|
|
5723
5740
|
account,
|
|
5724
5741
|
cfg,
|
|
5725
|
-
log
|
|
5742
|
+
log,
|
|
5743
|
+
...gatewayMediaContext
|
|
5726
5744
|
};
|
|
5727
5745
|
const sendWithRetry = (sendFn) => sendWithTokenRetry(account.appId, account.clientSecret, sendFn, log, account.accountId);
|
|
5728
5746
|
const sendErrorMessage = (errorText) => sendErrorToTarget(replyCtx, errorText);
|
|
@@ -5760,7 +5778,8 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5760
5778
|
mediaUrl,
|
|
5761
5779
|
accountId: account.accountId,
|
|
5762
5780
|
replyToId: event.messageId,
|
|
5763
|
-
account
|
|
5781
|
+
account,
|
|
5782
|
+
...gatewayMediaContext
|
|
5764
5783
|
}).then((r) => {
|
|
5765
5784
|
if (ac.signal.aborted) return {
|
|
5766
5785
|
channel: "qqbot",
|
|
@@ -5784,13 +5803,10 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5784
5803
|
}
|
|
5785
5804
|
};
|
|
5786
5805
|
const sendToolFallback = async () => {
|
|
5787
|
-
if (toolMediaUrls.length > 0) {
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
});
|
|
5792
|
-
return;
|
|
5793
|
-
}
|
|
5806
|
+
if (toolMediaUrls.length > 0) for (const mediaUrl of toolMediaUrls) await sendToolMediaWithTimeout(mediaUrl, {
|
|
5807
|
+
resultError: "Tool fallback error",
|
|
5808
|
+
thrownError: "Tool fallback failed"
|
|
5809
|
+
});
|
|
5794
5810
|
if (toolTexts.length > 0) await sendErrorMessage(toolTexts.slice(-3).join("\n---\n").slice(0, 2e3));
|
|
5795
5811
|
};
|
|
5796
5812
|
const hasPendingToolFallbackPayload = () => toolTexts.length > 0 || toolMediaUrls.length > 0;
|
|
@@ -5882,7 +5898,8 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5882
5898
|
}, {
|
|
5883
5899
|
account,
|
|
5884
5900
|
qualifiedTarget,
|
|
5885
|
-
log
|
|
5901
|
+
log,
|
|
5902
|
+
...gatewayMediaContext
|
|
5886
5903
|
}, sendWithRetry, () => void 0, deliverDeps);
|
|
5887
5904
|
}
|
|
5888
5905
|
};
|
|
@@ -5891,7 +5908,7 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5891
5908
|
accountId: account.accountId,
|
|
5892
5909
|
direction: "outbound"
|
|
5893
5910
|
});
|
|
5894
|
-
const messagesConfig = runtime.channel.reply.resolveEffectiveMessagesConfig(cfg,
|
|
5911
|
+
const messagesConfig = runtime.channel.reply.resolveEffectiveMessagesConfig(cfg, routeAgentId);
|
|
5895
5912
|
const useOfficialC2cStream = shouldUseOfficialC2cStream(account, event.type === "c2c" ? "c2c" : event.type === "group" ? "group" : "channel");
|
|
5896
5913
|
const deliverToolProgressImmediately = shouldDeliverToolProgressImmediately(account, useOfficialC2cStream);
|
|
5897
5914
|
let streamingController = null;
|
|
@@ -5911,12 +5928,12 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5911
5928
|
groupOpenid: event.groupOpenid,
|
|
5912
5929
|
channelId: event.channelId
|
|
5913
5930
|
},
|
|
5914
|
-
log
|
|
5931
|
+
log,
|
|
5932
|
+
...gatewayMediaContext
|
|
5915
5933
|
}
|
|
5916
5934
|
});
|
|
5917
5935
|
const cfgWithSession = cfg;
|
|
5918
|
-
const
|
|
5919
|
-
const storePath = runtime.channel.session.resolveStorePath(cfgWithSession.session?.store, { agentId });
|
|
5936
|
+
const storePath = runtime.channel.session.resolveStorePath(cfgWithSession.session?.store, { agentId: routeAgentId });
|
|
5920
5937
|
const dispatchPromise = runtime.channel.inbound.run({
|
|
5921
5938
|
channel: "qqbot",
|
|
5922
5939
|
accountId: inbound.route.accountId,
|
|
@@ -5962,7 +5979,8 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5962
5979
|
}, {
|
|
5963
5980
|
account,
|
|
5964
5981
|
qualifiedTarget,
|
|
5965
|
-
log
|
|
5982
|
+
log,
|
|
5983
|
+
...gatewayMediaContext
|
|
5966
5984
|
}, sendWithRetry, () => void 0, deliverDeps);
|
|
5967
5985
|
recordOutbound();
|
|
5968
5986
|
return;
|
|
@@ -5980,7 +5998,8 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5980
5998
|
mediaUrl,
|
|
5981
5999
|
accountId: account.accountId,
|
|
5982
6000
|
replyToId: event.messageId,
|
|
5983
|
-
account
|
|
6001
|
+
account,
|
|
6002
|
+
...gatewayMediaContext
|
|
5984
6003
|
});
|
|
5985
6004
|
} catch {}
|
|
5986
6005
|
return;
|
|
@@ -5995,7 +6014,7 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5995
6014
|
return;
|
|
5996
6015
|
}
|
|
5997
6016
|
hasVisibleBlockResponse = true;
|
|
5998
|
-
if (streamingController && !streamingController.isTerminalPhase) {
|
|
6017
|
+
if (streamingController && !streamingController.isTerminalPhase && !isMediaOnlyBlockReply(payload)) {
|
|
5999
6018
|
try {
|
|
6000
6019
|
await streamingController.onDeliver(payload);
|
|
6001
6020
|
} catch (err) {
|
|
@@ -6032,7 +6051,8 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
6032
6051
|
const deliverActx = {
|
|
6033
6052
|
account,
|
|
6034
6053
|
qualifiedTarget,
|
|
6035
|
-
log
|
|
6054
|
+
log,
|
|
6055
|
+
...gatewayMediaContext
|
|
6036
6056
|
};
|
|
6037
6057
|
const mediaResult = await parseAndSendMediaTags(replyText, deliverEvent, deliverActx, sendWithRetry, consumeQuoteRef, deliverDeps);
|
|
6038
6058
|
if (mediaResult.handled) {
|
|
@@ -6153,7 +6173,7 @@ async function buildCtxPayload(inbound, runtime, cfg) {
|
|
|
6153
6173
|
id: inbound.peerId
|
|
6154
6174
|
},
|
|
6155
6175
|
route: {
|
|
6156
|
-
agentId: inbound.route.agentId ??
|
|
6176
|
+
agentId: inbound.route.agentId ?? resolveDefaultAgentId(cfg),
|
|
6157
6177
|
routeSessionKey: inbound.route.sessionKey,
|
|
6158
6178
|
accountId: inbound.route.accountId
|
|
6159
6179
|
},
|