@openclaw-china/qqbot 2026.3.11 → 2026.3.16
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/index.d.ts +86 -1
- package/dist/index.js +2065 -299
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +67 -0
- package/package.json +4 -3
- package/skills/qqbot-contact-send/SKILL.md +112 -0
- package/skills/qqbot-contact-send/scripts/prepare_send.py +141 -0
- package/skills/qqbot-contact-send/scripts/resolve_known_target.py +74 -0
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { homedir, tmpdir } from 'os';
|
|
|
3
3
|
import * as path2 from 'path';
|
|
4
4
|
import { join, dirname } from 'path';
|
|
5
5
|
import * as fs3 from 'fs';
|
|
6
|
-
import { existsSync, readFileSync, rmSync, writeFileSync, mkdirSync } from 'fs';
|
|
6
|
+
import { existsSync, readFileSync, rmSync, writeFileSync, renameSync, copyFileSync, mkdirSync, appendFileSync } from 'fs';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import * as fsPromises from 'fs/promises';
|
|
9
9
|
import { createHmac } from 'crypto';
|
|
@@ -651,8 +651,8 @@ function getErrorMap() {
|
|
|
651
651
|
|
|
652
652
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
653
653
|
var makeIssue = (params) => {
|
|
654
|
-
const { data, path:
|
|
655
|
-
const fullPath = [...
|
|
654
|
+
const { data, path: path5, errorMaps, issueData } = params;
|
|
655
|
+
const fullPath = [...path5, ...issueData.path || []];
|
|
656
656
|
const fullIssue = {
|
|
657
657
|
...issueData,
|
|
658
658
|
path: fullPath
|
|
@@ -768,11 +768,11 @@ var errorUtil;
|
|
|
768
768
|
|
|
769
769
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
|
|
770
770
|
var ParseInputLazyPath = class {
|
|
771
|
-
constructor(parent, value,
|
|
771
|
+
constructor(parent, value, path5, key) {
|
|
772
772
|
this._cachedPath = [];
|
|
773
773
|
this.parent = parent;
|
|
774
774
|
this.data = value;
|
|
775
|
-
this._path =
|
|
775
|
+
this._path = path5;
|
|
776
776
|
this._key = key;
|
|
777
777
|
}
|
|
778
778
|
get path() {
|
|
@@ -4223,12 +4223,17 @@ var optionalCoercedString = external_exports.preprocess(
|
|
|
4223
4223
|
(value) => toTrimmedString(value),
|
|
4224
4224
|
external_exports.string().min(1).optional()
|
|
4225
4225
|
);
|
|
4226
|
+
var displayAliasesSchema = external_exports.record(
|
|
4227
|
+
external_exports.preprocess((value) => toTrimmedString(value), external_exports.string().min(1))
|
|
4228
|
+
).optional();
|
|
4226
4229
|
var QQBotC2CMarkdownDeliveryModeSchema = external_exports.enum(["passive", "proactive-table-only", "proactive-all"]).optional().default("proactive-table-only");
|
|
4230
|
+
var QQBotC2CMarkdownChunkStrategySchema = external_exports.enum(["markdown-block", "length"]).optional().default("markdown-block");
|
|
4227
4231
|
var QQBotAccountSchema = external_exports.object({
|
|
4228
4232
|
name: external_exports.string().optional(),
|
|
4229
4233
|
enabled: external_exports.boolean().optional(),
|
|
4230
4234
|
appId: optionalCoercedString,
|
|
4231
4235
|
clientSecret: optionalCoercedString,
|
|
4236
|
+
displayAliases: displayAliasesSchema,
|
|
4232
4237
|
asr: external_exports.object({
|
|
4233
4238
|
enabled: external_exports.boolean().optional().default(false),
|
|
4234
4239
|
appId: optionalCoercedString,
|
|
@@ -4237,6 +4242,7 @@ var QQBotAccountSchema = external_exports.object({
|
|
|
4237
4242
|
}).optional(),
|
|
4238
4243
|
markdownSupport: external_exports.boolean().optional().default(true),
|
|
4239
4244
|
c2cMarkdownDeliveryMode: QQBotC2CMarkdownDeliveryModeSchema,
|
|
4245
|
+
c2cMarkdownChunkStrategy: QQBotC2CMarkdownChunkStrategySchema,
|
|
4240
4246
|
dmPolicy: external_exports.enum(["open", "pairing", "allowlist"]).optional().default("open"),
|
|
4241
4247
|
groupPolicy: external_exports.enum(["open", "allowlist", "disabled"]).optional().default("open"),
|
|
4242
4248
|
requireMention: external_exports.boolean().optional().default(true),
|
|
@@ -4261,6 +4267,21 @@ QQBotAccountSchema.extend({
|
|
|
4261
4267
|
var DEFAULT_INBOUND_MEDIA_DIR = join(homedir(), ".openclaw", "media", "qqbot", "inbound");
|
|
4262
4268
|
var DEFAULT_INBOUND_MEDIA_KEEP_DAYS = 7;
|
|
4263
4269
|
var DEFAULT_INBOUND_MEDIA_TEMP_DIR = join(tmpdir(), "qqbot-media");
|
|
4270
|
+
function normalizeDisplayAliasesMap(raw) {
|
|
4271
|
+
if (!raw || typeof raw !== "object") {
|
|
4272
|
+
return {};
|
|
4273
|
+
}
|
|
4274
|
+
const aliases = {};
|
|
4275
|
+
for (const [rawKey, rawValue] of Object.entries(raw)) {
|
|
4276
|
+
const key = rawKey.trim();
|
|
4277
|
+
const value = toTrimmedString(rawValue);
|
|
4278
|
+
if (!key || !value) {
|
|
4279
|
+
continue;
|
|
4280
|
+
}
|
|
4281
|
+
aliases[key] = value;
|
|
4282
|
+
}
|
|
4283
|
+
return aliases;
|
|
4284
|
+
}
|
|
4264
4285
|
function resolveInboundMediaDir(config) {
|
|
4265
4286
|
return String(config?.inboundMedia?.dir ?? "").trim() || DEFAULT_INBOUND_MEDIA_DIR;
|
|
4266
4287
|
}
|
|
@@ -4301,7 +4322,15 @@ function mergeQQBotAccountConfig(cfg, accountId) {
|
|
|
4301
4322
|
const base = cfg.channels?.qqbot ?? {};
|
|
4302
4323
|
const { accounts: _ignored, defaultAccount: _ignored2, ...baseConfig } = base;
|
|
4303
4324
|
const account = resolveAccountConfig(cfg, accountId) ?? {};
|
|
4304
|
-
|
|
4325
|
+
const mergedDisplayAliases = {
|
|
4326
|
+
...normalizeDisplayAliasesMap(baseConfig.displayAliases),
|
|
4327
|
+
...normalizeDisplayAliasesMap(account.displayAliases)
|
|
4328
|
+
};
|
|
4329
|
+
return {
|
|
4330
|
+
...baseConfig,
|
|
4331
|
+
...account,
|
|
4332
|
+
...Object.keys(mergedDisplayAliases).length > 0 ? { displayAliases: mergedDisplayAliases } : {}
|
|
4333
|
+
};
|
|
4305
4334
|
}
|
|
4306
4335
|
function resolveQQBotCredentials(config) {
|
|
4307
4336
|
const appId = toTrimmedString(config?.appId);
|
|
@@ -4876,7 +4905,7 @@ function extractMediaFromText(text, options = {}) {
|
|
|
4876
4905
|
const {
|
|
4877
4906
|
removeFromText = true,
|
|
4878
4907
|
checkExists = false,
|
|
4879
|
-
existsSync:
|
|
4908
|
+
existsSync: existsSync7,
|
|
4880
4909
|
parseMediaLines = false,
|
|
4881
4910
|
parseMarkdownImages = true,
|
|
4882
4911
|
parseHtmlImages = true,
|
|
@@ -4891,7 +4920,7 @@ function extractMediaFromText(text, options = {}) {
|
|
|
4891
4920
|
const key = media.localPath || media.source;
|
|
4892
4921
|
if (seenSources.has(key)) return false;
|
|
4893
4922
|
if (checkExists && media.isLocal && media.localPath) {
|
|
4894
|
-
const exists =
|
|
4923
|
+
const exists = existsSync7 ? existsSync7(media.localPath) : fs3.existsSync(media.localPath);
|
|
4895
4924
|
if (!exists) return false;
|
|
4896
4925
|
}
|
|
4897
4926
|
seenSources.add(key);
|
|
@@ -6518,6 +6547,7 @@ var CHANNEL_ORDER = [
|
|
|
6518
6547
|
"qqbot",
|
|
6519
6548
|
"wecom",
|
|
6520
6549
|
"wecom-app",
|
|
6550
|
+
"wecom-kf",
|
|
6521
6551
|
"feishu-china"
|
|
6522
6552
|
];
|
|
6523
6553
|
var CHANNEL_DISPLAY_LABELS = {
|
|
@@ -6525,6 +6555,7 @@ var CHANNEL_DISPLAY_LABELS = {
|
|
|
6525
6555
|
"feishu-china": "Feishu\uFF08\u98DE\u4E66\uFF09",
|
|
6526
6556
|
wecom: "WeCom\uFF08\u4F01\u4E1A\u5FAE\u4FE1-\u667A\u80FD\u673A\u5668\u4EBA\uFF09",
|
|
6527
6557
|
"wecom-app": "WeCom App\uFF08\u81EA\u5EFA\u5E94\u7528-\u53EF\u63A5\u5165\u5FAE\u4FE1\uFF09",
|
|
6558
|
+
"wecom-kf": "WeCom KF\uFF08\u5FAE\u4FE1\u5BA2\u670D\uFF09",
|
|
6528
6559
|
qqbot: "QQBot\uFF08QQ \u673A\u5668\u4EBA\uFF09"
|
|
6529
6560
|
};
|
|
6530
6561
|
var CHANNEL_GUIDE_LINKS = {
|
|
@@ -6532,6 +6563,7 @@ var CHANNEL_GUIDE_LINKS = {
|
|
|
6532
6563
|
"feishu-china": "https://github.com/BytePioneer-AI/openclaw-china/blob/main/README.md",
|
|
6533
6564
|
wecom: `${GUIDES_BASE}/wecom/configuration.md`,
|
|
6534
6565
|
"wecom-app": `${GUIDES_BASE}/wecom-app/configuration.md`,
|
|
6566
|
+
"wecom-kf": "https://github.com/BytePioneer-AI/openclaw-china/blob/main/extensions/wecom-kf/README.md",
|
|
6535
6567
|
qqbot: `${GUIDES_BASE}/qqbot/configuration.md`
|
|
6536
6568
|
};
|
|
6537
6569
|
var CHINA_CLI_STATE_KEY = /* @__PURE__ */ Symbol.for("@openclaw-china/china-cli-state");
|
|
@@ -6740,6 +6772,8 @@ function isChannelConfigured(cfg, channelId) {
|
|
|
6740
6772
|
return hasWecomWsCredentialPair(channelCfg);
|
|
6741
6773
|
case "wecom-app":
|
|
6742
6774
|
return hasTokenPair(channelCfg);
|
|
6775
|
+
case "wecom-kf":
|
|
6776
|
+
return hasNonEmptyString(channelCfg.corpId) && hasNonEmptyString(channelCfg.corpSecret) && hasNonEmptyString(channelCfg.token) && hasNonEmptyString(channelCfg.encodingAESKey);
|
|
6743
6777
|
default:
|
|
6744
6778
|
return false;
|
|
6745
6779
|
}
|
|
@@ -6996,6 +7030,55 @@ async function configureWecomApp(prompter, cfg) {
|
|
|
6996
7030
|
patch.asr = asr;
|
|
6997
7031
|
return mergeChannelConfig(cfg, "wecom-app", patch);
|
|
6998
7032
|
}
|
|
7033
|
+
async function configureWecomKf(prompter, cfg) {
|
|
7034
|
+
section("\u914D\u7F6E WeCom KF\uFF08\u5FAE\u4FE1\u5BA2\u670D\uFF09");
|
|
7035
|
+
showGuideLink("wecom-kf");
|
|
7036
|
+
const existing = getChannelConfig(cfg, "wecom-kf");
|
|
7037
|
+
const webhookPath = await prompter.askText({
|
|
7038
|
+
label: "Webhook \u8DEF\u5F84\uFF08\u9ED8\u8BA4 /wecom-kf\uFF09",
|
|
7039
|
+
defaultValue: toTrimmedString2(existing.webhookPath) ?? "/wecom-kf",
|
|
7040
|
+
required: true
|
|
7041
|
+
});
|
|
7042
|
+
const token = await prompter.askSecret({
|
|
7043
|
+
label: "\u5FAE\u4FE1\u5BA2\u670D\u56DE\u8C03 Token",
|
|
7044
|
+
existingValue: toTrimmedString2(existing.token),
|
|
7045
|
+
required: true
|
|
7046
|
+
});
|
|
7047
|
+
const encodingAESKey = await prompter.askSecret({
|
|
7048
|
+
label: "\u5FAE\u4FE1\u5BA2\u670D\u56DE\u8C03 EncodingAESKey",
|
|
7049
|
+
existingValue: toTrimmedString2(existing.encodingAESKey),
|
|
7050
|
+
required: true
|
|
7051
|
+
});
|
|
7052
|
+
const corpId = await prompter.askText({
|
|
7053
|
+
label: "corpId",
|
|
7054
|
+
defaultValue: toTrimmedString2(existing.corpId),
|
|
7055
|
+
required: true
|
|
7056
|
+
});
|
|
7057
|
+
const corpSecret = await prompter.askSecret({
|
|
7058
|
+
label: "\u5FAE\u4FE1\u5BA2\u670D Secret",
|
|
7059
|
+
existingValue: toTrimmedString2(existing.corpSecret),
|
|
7060
|
+
required: true
|
|
7061
|
+
});
|
|
7062
|
+
const openKfId = await prompter.askText({
|
|
7063
|
+
label: "open_kfid",
|
|
7064
|
+
defaultValue: toTrimmedString2(existing.openKfId),
|
|
7065
|
+
required: true
|
|
7066
|
+
});
|
|
7067
|
+
const welcomeText = await prompter.askText({
|
|
7068
|
+
label: "\u6B22\u8FCE\u8BED\uFF08\u53EF\u9009\uFF09",
|
|
7069
|
+
defaultValue: toTrimmedString2(existing.welcomeText),
|
|
7070
|
+
required: false
|
|
7071
|
+
});
|
|
7072
|
+
return mergeChannelConfig(cfg, "wecom-kf", {
|
|
7073
|
+
webhookPath,
|
|
7074
|
+
token,
|
|
7075
|
+
encodingAESKey,
|
|
7076
|
+
corpId,
|
|
7077
|
+
corpSecret,
|
|
7078
|
+
openKfId,
|
|
7079
|
+
welcomeText: welcomeText || void 0
|
|
7080
|
+
});
|
|
7081
|
+
}
|
|
6999
7082
|
async function configureQQBot(prompter, cfg) {
|
|
7000
7083
|
section("\u914D\u7F6E QQBot\uFF08QQ \u673A\u5668\u4EBA\uFF09");
|
|
7001
7084
|
showGuideLink("qqbot");
|
|
@@ -7052,6 +7135,8 @@ async function configureSingleChannel(channel, prompter, cfg) {
|
|
|
7052
7135
|
return configureWecom(prompter, cfg);
|
|
7053
7136
|
case "wecom-app":
|
|
7054
7137
|
return configureWecomApp(prompter, cfg);
|
|
7138
|
+
case "wecom-kf":
|
|
7139
|
+
return configureWecomKf(prompter, cfg);
|
|
7055
7140
|
case "qqbot":
|
|
7056
7141
|
return configureQQBot(prompter, cfg);
|
|
7057
7142
|
default:
|
|
@@ -7192,6 +7277,7 @@ var SUPPORTED_CHANNELS = [
|
|
|
7192
7277
|
"feishu-china",
|
|
7193
7278
|
"wecom",
|
|
7194
7279
|
"wecom-app",
|
|
7280
|
+
"wecom-kf",
|
|
7195
7281
|
"qqbot"
|
|
7196
7282
|
];
|
|
7197
7283
|
var CHINA_INSTALL_HINT_SHOWN_KEY = /* @__PURE__ */ Symbol.for("@openclaw-china/china-install-hint-shown");
|
|
@@ -7380,8 +7466,8 @@ async function getAccessToken(appId, clientSecret, options) {
|
|
|
7380
7466
|
tokenPromiseMap.set(normalizedAppId, promise);
|
|
7381
7467
|
return promise;
|
|
7382
7468
|
}
|
|
7383
|
-
async function apiGet(accessToken,
|
|
7384
|
-
const url = `${API_BASE}${
|
|
7469
|
+
async function apiGet(accessToken, path5, options) {
|
|
7470
|
+
const url = `${API_BASE}${path5}`;
|
|
7385
7471
|
return httpGet(url, {
|
|
7386
7472
|
...options,
|
|
7387
7473
|
headers: {
|
|
@@ -7390,8 +7476,8 @@ async function apiGet(accessToken, path4, options) {
|
|
|
7390
7476
|
}
|
|
7391
7477
|
});
|
|
7392
7478
|
}
|
|
7393
|
-
async function apiPost(accessToken,
|
|
7394
|
-
const url = `${API_BASE}${
|
|
7479
|
+
async function apiPost(accessToken, path5, body, options) {
|
|
7480
|
+
const url = `${API_BASE}${path5}`;
|
|
7395
7481
|
return httpPost(url, body, {
|
|
7396
7482
|
...options,
|
|
7397
7483
|
headers: {
|
|
@@ -7490,7 +7576,7 @@ async function sendChannelMessage(params) {
|
|
|
7490
7576
|
});
|
|
7491
7577
|
}
|
|
7492
7578
|
async function sendC2CInputNotify(params) {
|
|
7493
|
-
await postPassiveMessage({
|
|
7579
|
+
const response = await postPassiveMessage({
|
|
7494
7580
|
accessToken: params.accessToken,
|
|
7495
7581
|
path: `/v2/users/${params.openid}/messages`,
|
|
7496
7582
|
sequenceKey: resolveMsgSeqKey(params.messageId, params.eventId),
|
|
@@ -7505,6 +7591,8 @@ async function sendC2CInputNotify(params) {
|
|
|
7505
7591
|
...params.messageId ? { msg_id: params.messageId } : params.eventId ? { event_id: params.eventId } : {}
|
|
7506
7592
|
})
|
|
7507
7593
|
});
|
|
7594
|
+
const refIdx = response.ext_info?.ref_idx?.trim();
|
|
7595
|
+
return refIdx ? { refIdx } : {};
|
|
7508
7596
|
}
|
|
7509
7597
|
async function uploadC2CMedia(params) {
|
|
7510
7598
|
const body = {
|
|
@@ -7574,6 +7662,233 @@ async function sendGroupMediaMessage(params) {
|
|
|
7574
7662
|
})
|
|
7575
7663
|
});
|
|
7576
7664
|
}
|
|
7665
|
+
var REF_INDEX_FILE = join(homedir(), ".openclaw", "qqbot", "data", "ref-index.jsonl");
|
|
7666
|
+
var MAX_CONTENT_LENGTH = 500;
|
|
7667
|
+
var MAX_ENTRIES = 5e4;
|
|
7668
|
+
var TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
7669
|
+
var COMPACT_THRESHOLD_RATIO = 2;
|
|
7670
|
+
var cache = null;
|
|
7671
|
+
var totalLinesOnDisk = 0;
|
|
7672
|
+
function normalizeRefIdx(refIdx) {
|
|
7673
|
+
const next = refIdx.trim();
|
|
7674
|
+
return next ? next : void 0;
|
|
7675
|
+
}
|
|
7676
|
+
function ensureStorageDir() {
|
|
7677
|
+
mkdirSync(dirname(REF_INDEX_FILE), { recursive: true });
|
|
7678
|
+
}
|
|
7679
|
+
function truncateContent(content) {
|
|
7680
|
+
return content.trim().slice(0, MAX_CONTENT_LENGTH);
|
|
7681
|
+
}
|
|
7682
|
+
function sanitizeAttachmentSummary(attachment) {
|
|
7683
|
+
const type = attachment.type;
|
|
7684
|
+
const filename = attachment.filename?.trim();
|
|
7685
|
+
const contentType = attachment.contentType?.trim();
|
|
7686
|
+
const localPath = attachment.localPath?.trim();
|
|
7687
|
+
const url = attachment.url?.trim();
|
|
7688
|
+
const transcript = attachment.transcript?.trim();
|
|
7689
|
+
if (!filename && !contentType && !localPath && !url && !transcript && type === "unknown") {
|
|
7690
|
+
return void 0;
|
|
7691
|
+
}
|
|
7692
|
+
return {
|
|
7693
|
+
type,
|
|
7694
|
+
...filename ? { filename } : {},
|
|
7695
|
+
...contentType ? { contentType } : {},
|
|
7696
|
+
...localPath ? { localPath } : {},
|
|
7697
|
+
...url ? { url } : {},
|
|
7698
|
+
...transcript ? { transcript } : {},
|
|
7699
|
+
...transcript && attachment.transcriptSource ? { transcriptSource: attachment.transcriptSource } : {}
|
|
7700
|
+
};
|
|
7701
|
+
}
|
|
7702
|
+
function sanitizeEntry(entry) {
|
|
7703
|
+
const senderId = entry.senderId.trim() || "unknown";
|
|
7704
|
+
const senderName = entry.senderName?.trim();
|
|
7705
|
+
const timestamp = Number.isFinite(entry.timestamp) ? Math.trunc(entry.timestamp) : Date.now();
|
|
7706
|
+
const attachments = entry.attachments?.map((attachment) => sanitizeAttachmentSummary(attachment)).filter((attachment) => Boolean(attachment));
|
|
7707
|
+
return {
|
|
7708
|
+
content: truncateContent(entry.content),
|
|
7709
|
+
senderId,
|
|
7710
|
+
...senderName ? { senderName } : {},
|
|
7711
|
+
timestamp,
|
|
7712
|
+
...entry.isBot ? { isBot: true } : {},
|
|
7713
|
+
...attachments && attachments.length > 0 ? { attachments } : {}
|
|
7714
|
+
};
|
|
7715
|
+
}
|
|
7716
|
+
function shouldCompact() {
|
|
7717
|
+
if (!cache) return false;
|
|
7718
|
+
return totalLinesOnDisk > cache.size * COMPACT_THRESHOLD_RATIO && totalLinesOnDisk > 1e3;
|
|
7719
|
+
}
|
|
7720
|
+
function compactFile() {
|
|
7721
|
+
if (!cache) return;
|
|
7722
|
+
try {
|
|
7723
|
+
ensureStorageDir();
|
|
7724
|
+
const tempPath = `${REF_INDEX_FILE}.tmp`;
|
|
7725
|
+
const lines = [];
|
|
7726
|
+
for (const [key, entry] of cache.entries()) {
|
|
7727
|
+
lines.push(
|
|
7728
|
+
JSON.stringify({
|
|
7729
|
+
k: key,
|
|
7730
|
+
v: sanitizeEntry(entry),
|
|
7731
|
+
t: entry._createdAt
|
|
7732
|
+
})
|
|
7733
|
+
);
|
|
7734
|
+
}
|
|
7735
|
+
writeFileSync(tempPath, lines.length > 0 ? `${lines.join("\n")}
|
|
7736
|
+
` : "", "utf8");
|
|
7737
|
+
renameSync(tempPath, REF_INDEX_FILE);
|
|
7738
|
+
totalLinesOnDisk = cache.size;
|
|
7739
|
+
} catch {
|
|
7740
|
+
}
|
|
7741
|
+
}
|
|
7742
|
+
function evictIfNeeded() {
|
|
7743
|
+
if (!cache || cache.size < MAX_ENTRIES) return;
|
|
7744
|
+
const now = Date.now();
|
|
7745
|
+
for (const [key, entry] of cache.entries()) {
|
|
7746
|
+
if (now - entry._createdAt > TTL_MS) {
|
|
7747
|
+
cache.delete(key);
|
|
7748
|
+
}
|
|
7749
|
+
}
|
|
7750
|
+
if (cache.size < MAX_ENTRIES) {
|
|
7751
|
+
return;
|
|
7752
|
+
}
|
|
7753
|
+
const sorted = [...cache.entries()].sort((left, right) => left[1]._createdAt - right[1]._createdAt);
|
|
7754
|
+
const removeCount = cache.size - MAX_ENTRIES + 1;
|
|
7755
|
+
for (let index = 0; index < removeCount; index += 1) {
|
|
7756
|
+
const key = sorted[index]?.[0];
|
|
7757
|
+
if (key) {
|
|
7758
|
+
cache.delete(key);
|
|
7759
|
+
}
|
|
7760
|
+
}
|
|
7761
|
+
}
|
|
7762
|
+
function loadCache() {
|
|
7763
|
+
if (cache) {
|
|
7764
|
+
return cache;
|
|
7765
|
+
}
|
|
7766
|
+
cache = /* @__PURE__ */ new Map();
|
|
7767
|
+
totalLinesOnDisk = 0;
|
|
7768
|
+
try {
|
|
7769
|
+
if (!existsSync(REF_INDEX_FILE)) {
|
|
7770
|
+
return cache;
|
|
7771
|
+
}
|
|
7772
|
+
const now = Date.now();
|
|
7773
|
+
const raw = readFileSync(REF_INDEX_FILE, "utf8");
|
|
7774
|
+
const lines = raw.split(/\r?\n/);
|
|
7775
|
+
for (const line of lines) {
|
|
7776
|
+
const trimmed = line.trim();
|
|
7777
|
+
if (!trimmed) continue;
|
|
7778
|
+
totalLinesOnDisk += 1;
|
|
7779
|
+
try {
|
|
7780
|
+
const parsed = JSON.parse(trimmed);
|
|
7781
|
+
const key = typeof parsed.k === "string" ? normalizeRefIdx(parsed.k) : void 0;
|
|
7782
|
+
const createdAt = typeof parsed.t === "number" && Number.isFinite(parsed.t) ? parsed.t : void 0;
|
|
7783
|
+
if (!key || createdAt === void 0 || !parsed.v || typeof parsed.v !== "object") {
|
|
7784
|
+
continue;
|
|
7785
|
+
}
|
|
7786
|
+
if (now - createdAt > TTL_MS) {
|
|
7787
|
+
continue;
|
|
7788
|
+
}
|
|
7789
|
+
const entry = sanitizeEntry(parsed.v);
|
|
7790
|
+
cache.set(key, {
|
|
7791
|
+
...entry,
|
|
7792
|
+
_createdAt: createdAt
|
|
7793
|
+
});
|
|
7794
|
+
} catch {
|
|
7795
|
+
}
|
|
7796
|
+
}
|
|
7797
|
+
if (shouldCompact()) {
|
|
7798
|
+
compactFile();
|
|
7799
|
+
}
|
|
7800
|
+
} catch {
|
|
7801
|
+
cache = /* @__PURE__ */ new Map();
|
|
7802
|
+
totalLinesOnDisk = 0;
|
|
7803
|
+
}
|
|
7804
|
+
return cache;
|
|
7805
|
+
}
|
|
7806
|
+
function appendLine(line) {
|
|
7807
|
+
try {
|
|
7808
|
+
ensureStorageDir();
|
|
7809
|
+
appendFileSync(REF_INDEX_FILE, `${JSON.stringify(line)}
|
|
7810
|
+
`, "utf8");
|
|
7811
|
+
totalLinesOnDisk += 1;
|
|
7812
|
+
} catch {
|
|
7813
|
+
}
|
|
7814
|
+
}
|
|
7815
|
+
function restoreEntry(entry) {
|
|
7816
|
+
const sanitized = sanitizeEntry(entry);
|
|
7817
|
+
return {
|
|
7818
|
+
content: sanitized.content,
|
|
7819
|
+
senderId: sanitized.senderId,
|
|
7820
|
+
...sanitized.senderName ? { senderName: sanitized.senderName } : {},
|
|
7821
|
+
timestamp: sanitized.timestamp,
|
|
7822
|
+
...sanitized.isBot ? { isBot: true } : {},
|
|
7823
|
+
...sanitized.attachments ? { attachments: sanitized.attachments } : {}
|
|
7824
|
+
};
|
|
7825
|
+
}
|
|
7826
|
+
function formatAttachmentSummary(attachment) {
|
|
7827
|
+
const sourceParts = [
|
|
7828
|
+
attachment.localPath?.trim() ? `\u672C\u5730: ${attachment.localPath.trim()}` : void 0,
|
|
7829
|
+
attachment.url?.trim() ? `\u94FE\u63A5: ${attachment.url.trim()}` : void 0
|
|
7830
|
+
].filter((value) => Boolean(value));
|
|
7831
|
+
const sourceSuffix = sourceParts.length > 0 ? ` (${sourceParts.join(" | ")})` : "";
|
|
7832
|
+
if (attachment.type === "image") {
|
|
7833
|
+
return `[\u56FE\u7247${attachment.filename?.trim() ? `: ${attachment.filename.trim()}` : ""}]${sourceSuffix}`;
|
|
7834
|
+
}
|
|
7835
|
+
if (attachment.type === "voice") {
|
|
7836
|
+
if (attachment.transcript?.trim()) {
|
|
7837
|
+
const sourceLabel = attachment.transcriptSource === "asr" ? "\u5B98\u65B9\u8BC6\u522B" : attachment.transcriptSource === "stt" ? "\u672C\u5730\u8BC6\u522B" : attachment.transcriptSource === "tts" ? "TTS \u539F\u6587" : attachment.transcriptSource === "fallback" ? "\u515C\u5E95\u6587\u672C" : void 0;
|
|
7838
|
+
return `[\u8BED\u97F3\u6D88\u606F: ${attachment.transcript.trim()}${sourceLabel ? ` (${sourceLabel})` : ""}]${sourceSuffix}`;
|
|
7839
|
+
}
|
|
7840
|
+
return `[\u8BED\u97F3\u6D88\u606F]${sourceSuffix}`;
|
|
7841
|
+
}
|
|
7842
|
+
if (attachment.type === "video") {
|
|
7843
|
+
return `[\u89C6\u9891${attachment.filename?.trim() ? `: ${attachment.filename.trim()}` : ""}]${sourceSuffix}`;
|
|
7844
|
+
}
|
|
7845
|
+
if (attachment.type === "file") {
|
|
7846
|
+
return `[\u6587\u4EF6${attachment.filename?.trim() ? `: ${attachment.filename.trim()}` : ""}]${sourceSuffix}`;
|
|
7847
|
+
}
|
|
7848
|
+
return `[\u9644\u4EF6${attachment.filename?.trim() ? `: ${attachment.filename.trim()}` : ""}]${sourceSuffix}`;
|
|
7849
|
+
}
|
|
7850
|
+
function setRefIndex(refIdx, entry) {
|
|
7851
|
+
const key = normalizeRefIdx(refIdx);
|
|
7852
|
+
if (!key) return;
|
|
7853
|
+
const store = loadCache();
|
|
7854
|
+
evictIfNeeded();
|
|
7855
|
+
const nextEntry = sanitizeEntry(entry);
|
|
7856
|
+
const createdAt = Date.now();
|
|
7857
|
+
store.set(key, {
|
|
7858
|
+
...nextEntry,
|
|
7859
|
+
_createdAt: createdAt
|
|
7860
|
+
});
|
|
7861
|
+
appendLine({
|
|
7862
|
+
k: key,
|
|
7863
|
+
v: nextEntry,
|
|
7864
|
+
t: createdAt
|
|
7865
|
+
});
|
|
7866
|
+
if (shouldCompact()) {
|
|
7867
|
+
compactFile();
|
|
7868
|
+
}
|
|
7869
|
+
}
|
|
7870
|
+
function getRefIndex(refIdx) {
|
|
7871
|
+
const key = normalizeRefIdx(refIdx);
|
|
7872
|
+
if (!key) return null;
|
|
7873
|
+
const store = loadCache();
|
|
7874
|
+
const entry = store.get(key);
|
|
7875
|
+
if (!entry) {
|
|
7876
|
+
return null;
|
|
7877
|
+
}
|
|
7878
|
+
if (Date.now() - entry._createdAt > TTL_MS) {
|
|
7879
|
+
store.delete(key);
|
|
7880
|
+
return null;
|
|
7881
|
+
}
|
|
7882
|
+
return restoreEntry(entry);
|
|
7883
|
+
}
|
|
7884
|
+
function formatRefEntryForAgent(entry) {
|
|
7885
|
+
const content = entry.content.trim();
|
|
7886
|
+
const parts = content ? [content] : [];
|
|
7887
|
+
for (const attachment of entry.attachments ?? []) {
|
|
7888
|
+
parts.push(formatAttachmentSummary(attachment));
|
|
7889
|
+
}
|
|
7890
|
+
return parts.join("\n") || "[\u7A7A\u6D88\u606F]";
|
|
7891
|
+
}
|
|
7577
7892
|
var require2 = createRequire(import.meta.url);
|
|
7578
7893
|
function resolveQQBotMediaFileType(fileName) {
|
|
7579
7894
|
const mediaType = detectMediaType(fileName);
|
|
@@ -7729,7 +8044,7 @@ async function sendFileQQBot(params) {
|
|
|
7729
8044
|
}
|
|
7730
8045
|
}
|
|
7731
8046
|
try {
|
|
7732
|
-
|
|
8047
|
+
const result = await sendC2CMediaMessage({
|
|
7733
8048
|
accessToken,
|
|
7734
8049
|
openid: target.id,
|
|
7735
8050
|
fileInfo,
|
|
@@ -7737,6 +8052,12 @@ async function sendFileQQBot(params) {
|
|
|
7737
8052
|
...messageId ? { messageId } : {},
|
|
7738
8053
|
...eventId ? { eventId } : {}
|
|
7739
8054
|
});
|
|
8055
|
+
const refIdx = result.ext_info?.ref_idx?.trim();
|
|
8056
|
+
return {
|
|
8057
|
+
id: result.id,
|
|
8058
|
+
timestamp: result.timestamp,
|
|
8059
|
+
...refIdx ? { refIdx } : {}
|
|
8060
|
+
};
|
|
7740
8061
|
} catch (err) {
|
|
7741
8062
|
const message = formatQQBotError(err);
|
|
7742
8063
|
throw new Error(`QQBot C2C media send failed: ${message}`);
|
|
@@ -7780,21 +8101,22 @@ async function readMediaWithConfig(source, options) {
|
|
|
7780
8101
|
|
|
7781
8102
|
// src/outbound.ts
|
|
7782
8103
|
function stripPrefix(value, prefix) {
|
|
7783
|
-
return value.
|
|
8104
|
+
return value.slice(0, prefix.length).toLowerCase() === prefix ? value.slice(prefix.length) : value;
|
|
7784
8105
|
}
|
|
7785
8106
|
function parseTarget(to) {
|
|
7786
8107
|
let raw = to.trim();
|
|
7787
8108
|
raw = stripPrefix(raw, "qqbot:");
|
|
7788
|
-
|
|
8109
|
+
const normalizedRaw = raw.toLowerCase();
|
|
8110
|
+
if (normalizedRaw.startsWith("group:")) {
|
|
7789
8111
|
return { kind: "group", id: raw.slice("group:".length) };
|
|
7790
8112
|
}
|
|
7791
|
-
if (
|
|
8113
|
+
if (normalizedRaw.startsWith("channel:")) {
|
|
7792
8114
|
return { kind: "channel", id: raw.slice("channel:".length) };
|
|
7793
8115
|
}
|
|
7794
|
-
if (
|
|
8116
|
+
if (normalizedRaw.startsWith("user:")) {
|
|
7795
8117
|
return { kind: "c2c", id: raw.slice("user:".length) };
|
|
7796
8118
|
}
|
|
7797
|
-
if (
|
|
8119
|
+
if (normalizedRaw.startsWith("c2c:")) {
|
|
7798
8120
|
return { kind: "c2c", id: raw.slice("c2c:".length) };
|
|
7799
8121
|
}
|
|
7800
8122
|
return { kind: "c2c", id: raw };
|
|
@@ -7853,6 +8175,85 @@ function shouldRetryWithEventId(err) {
|
|
|
7853
8175
|
function shouldSendTextAsFollowupForMedia(mediaUrl) {
|
|
7854
8176
|
return detectMediaType(stripTitleFromUrl(mediaUrl)) === "file";
|
|
7855
8177
|
}
|
|
8178
|
+
function isHttpUrl2(value) {
|
|
8179
|
+
return /^https?:\/\//i.test(value);
|
|
8180
|
+
}
|
|
8181
|
+
function resolveResponseRefIdx(response) {
|
|
8182
|
+
if (!response || typeof response !== "object") {
|
|
8183
|
+
return void 0;
|
|
8184
|
+
}
|
|
8185
|
+
const direct = response.refIdx;
|
|
8186
|
+
if (typeof direct === "string" && direct.trim()) {
|
|
8187
|
+
return direct.trim();
|
|
8188
|
+
}
|
|
8189
|
+
const extInfo = response.ext_info;
|
|
8190
|
+
if (typeof extInfo?.ref_idx === "string" && extInfo.ref_idx.trim()) {
|
|
8191
|
+
return extInfo.ref_idx.trim();
|
|
8192
|
+
}
|
|
8193
|
+
return void 0;
|
|
8194
|
+
}
|
|
8195
|
+
function resolveOutboundAttachmentType(mediaUrl) {
|
|
8196
|
+
const detected = detectMediaType(stripTitleFromUrl(mediaUrl));
|
|
8197
|
+
if (detected === "image") return "image";
|
|
8198
|
+
if (detected === "video") return "video";
|
|
8199
|
+
if (detected === "audio") return "voice";
|
|
8200
|
+
if (detected === "file") return "file";
|
|
8201
|
+
return "unknown";
|
|
8202
|
+
}
|
|
8203
|
+
function resolveOutboundAttachmentFileName(mediaUrl) {
|
|
8204
|
+
const source = stripTitleFromUrl(mediaUrl).trim();
|
|
8205
|
+
if (!source) return void 0;
|
|
8206
|
+
if (isHttpUrl2(source)) {
|
|
8207
|
+
try {
|
|
8208
|
+
const base2 = path2.posix.basename(new URL(source).pathname);
|
|
8209
|
+
return base2 && base2 !== "/" ? base2 : void 0;
|
|
8210
|
+
} catch {
|
|
8211
|
+
return void 0;
|
|
8212
|
+
}
|
|
8213
|
+
}
|
|
8214
|
+
const base = path2.basename(source);
|
|
8215
|
+
return base || void 0;
|
|
8216
|
+
}
|
|
8217
|
+
function buildOutboundAttachmentSummary(params) {
|
|
8218
|
+
const source = stripTitleFromUrl(params.mediaUrl).trim();
|
|
8219
|
+
const type = resolveOutboundAttachmentType(source);
|
|
8220
|
+
const filename = resolveOutboundAttachmentFileName(source);
|
|
8221
|
+
const text = params.text?.trim();
|
|
8222
|
+
return {
|
|
8223
|
+
type,
|
|
8224
|
+
...filename ? { filename } : {},
|
|
8225
|
+
...isHttpUrl2(source) ? { url: source } : { localPath: source },
|
|
8226
|
+
...type === "voice" && text ? {
|
|
8227
|
+
transcript: text,
|
|
8228
|
+
transcriptSource: "tts"
|
|
8229
|
+
} : {}
|
|
8230
|
+
};
|
|
8231
|
+
}
|
|
8232
|
+
function recordOutboundC2CRefIndex(params) {
|
|
8233
|
+
const refIdx = params.refIdx?.trim();
|
|
8234
|
+
if (!refIdx) return;
|
|
8235
|
+
const text = params.text?.trim() ?? "";
|
|
8236
|
+
const attachments = params.mediaUrl?.trim() ? [buildOutboundAttachmentSummary({ mediaUrl: params.mediaUrl, text })] : void 0;
|
|
8237
|
+
if (!text && !attachments) {
|
|
8238
|
+
return;
|
|
8239
|
+
}
|
|
8240
|
+
try {
|
|
8241
|
+
const accountLabel = params.accountId?.trim() || DEFAULT_ACCOUNT_ID;
|
|
8242
|
+
setRefIndex(refIdx, {
|
|
8243
|
+
content: text,
|
|
8244
|
+
senderId: accountLabel,
|
|
8245
|
+
senderName: accountLabel,
|
|
8246
|
+
timestamp: Date.now(),
|
|
8247
|
+
isBot: true,
|
|
8248
|
+
...attachments ? { attachments } : {}
|
|
8249
|
+
});
|
|
8250
|
+
console.info(
|
|
8251
|
+
`[qqbot] cached outbound ref_idx=${refIdx} accountId=${accountLabel} textLen=${text.length} media=${params.mediaUrl?.trim() ? "yes" : "no"}`
|
|
8252
|
+
);
|
|
8253
|
+
} catch (err) {
|
|
8254
|
+
console.warn(`[qqbot] failed to cache outbound ref_idx=${refIdx}: ${String(err)}`);
|
|
8255
|
+
}
|
|
8256
|
+
}
|
|
7856
8257
|
function buildPassiveReplyRefs(params) {
|
|
7857
8258
|
if (params.replyToId) {
|
|
7858
8259
|
return { messageId: params.replyToId };
|
|
@@ -7998,7 +8399,14 @@ var qqbotOutbound = {
|
|
|
7998
8399
|
content: text,
|
|
7999
8400
|
markdown
|
|
8000
8401
|
});
|
|
8001
|
-
|
|
8402
|
+
const refIdx2 = resolveResponseRefIdx(result2);
|
|
8403
|
+
recordOutboundC2CRefIndex({ refIdx: refIdx2, accountId, text });
|
|
8404
|
+
return {
|
|
8405
|
+
channel: "qqbot",
|
|
8406
|
+
messageId: result2.id,
|
|
8407
|
+
timestamp: result2.timestamp,
|
|
8408
|
+
...refIdx2 ? { refIdx: refIdx2 } : {}
|
|
8409
|
+
};
|
|
8002
8410
|
}
|
|
8003
8411
|
let result;
|
|
8004
8412
|
try {
|
|
@@ -8065,7 +8473,14 @@ var qqbotOutbound = {
|
|
|
8065
8473
|
throw retryErr;
|
|
8066
8474
|
}
|
|
8067
8475
|
}
|
|
8068
|
-
|
|
8476
|
+
const refIdx = resolveResponseRefIdx(result);
|
|
8477
|
+
recordOutboundC2CRefIndex({ refIdx, accountId, text });
|
|
8478
|
+
return {
|
|
8479
|
+
channel: "qqbot",
|
|
8480
|
+
messageId: result.id,
|
|
8481
|
+
timestamp: result.timestamp,
|
|
8482
|
+
...refIdx ? { refIdx } : {}
|
|
8483
|
+
};
|
|
8069
8484
|
} catch (err) {
|
|
8070
8485
|
const message = summarizeError(err);
|
|
8071
8486
|
return { channel: "qqbot", error: message };
|
|
@@ -8174,7 +8589,21 @@ ${mediaUrl}` : mediaUrl;
|
|
|
8174
8589
|
};
|
|
8175
8590
|
}
|
|
8176
8591
|
}
|
|
8177
|
-
|
|
8592
|
+
const refIdx = target.kind === "c2c" ? resolveResponseRefIdx(result) : void 0;
|
|
8593
|
+
if (target.kind === "c2c") {
|
|
8594
|
+
recordOutboundC2CRefIndex({
|
|
8595
|
+
refIdx,
|
|
8596
|
+
accountId,
|
|
8597
|
+
text: sendTextAsFollowup ? void 0 : trimmedText,
|
|
8598
|
+
mediaUrl
|
|
8599
|
+
});
|
|
8600
|
+
}
|
|
8601
|
+
return {
|
|
8602
|
+
channel: "qqbot",
|
|
8603
|
+
messageId: result.id,
|
|
8604
|
+
timestamp: result.timestamp,
|
|
8605
|
+
...refIdx ? { refIdx } : {}
|
|
8606
|
+
};
|
|
8178
8607
|
} catch (err) {
|
|
8179
8608
|
const message = summarizeError(err);
|
|
8180
8609
|
return { channel: "qqbot", error: message };
|
|
@@ -8193,8 +8622,9 @@ ${mediaUrl}` : mediaUrl;
|
|
|
8193
8622
|
}
|
|
8194
8623
|
try {
|
|
8195
8624
|
const accessToken = await getAccessToken(credentials.appId, credentials.clientSecret);
|
|
8625
|
+
let typingResult;
|
|
8196
8626
|
try {
|
|
8197
|
-
await sendC2CInputNotify({
|
|
8627
|
+
typingResult = await sendC2CInputNotify({
|
|
8198
8628
|
accessToken,
|
|
8199
8629
|
openid: target.id,
|
|
8200
8630
|
messageId: replyToId,
|
|
@@ -8216,7 +8646,7 @@ ${mediaUrl}` : mediaUrl;
|
|
|
8216
8646
|
reason: summarizeError(err)
|
|
8217
8647
|
});
|
|
8218
8648
|
try {
|
|
8219
|
-
await sendC2CInputNotify({
|
|
8649
|
+
typingResult = await sendC2CInputNotify({
|
|
8220
8650
|
accessToken,
|
|
8221
8651
|
openid: target.id,
|
|
8222
8652
|
eventId: replyEventId,
|
|
@@ -8245,7 +8675,10 @@ ${mediaUrl}` : mediaUrl;
|
|
|
8245
8675
|
throw retryErr;
|
|
8246
8676
|
}
|
|
8247
8677
|
}
|
|
8248
|
-
return {
|
|
8678
|
+
return {
|
|
8679
|
+
channel: "qqbot",
|
|
8680
|
+
...typingResult?.refIdx ? { refIdx: typingResult.refIdx } : {}
|
|
8681
|
+
};
|
|
8249
8682
|
} catch (err) {
|
|
8250
8683
|
const message = summarizeError(err);
|
|
8251
8684
|
return { channel: "qqbot", error: message };
|
|
@@ -8394,13 +8827,13 @@ async function replaceAsync(input2, pattern, replacer) {
|
|
|
8394
8827
|
return result;
|
|
8395
8828
|
}
|
|
8396
8829
|
async function getResolvedImageSize(params) {
|
|
8397
|
-
const { url, cache, resolveImageSize } = params;
|
|
8398
|
-
const existing =
|
|
8830
|
+
const { url, cache: cache2, resolveImageSize } = params;
|
|
8831
|
+
const existing = cache2.get(url);
|
|
8399
8832
|
if (existing) {
|
|
8400
8833
|
return existing;
|
|
8401
8834
|
}
|
|
8402
8835
|
const pending = resolveImageSize(url).then((size) => normalizeImageSize(size) ?? DEFAULT_QQBOT_MARKDOWN_IMAGE_SIZE).catch(() => DEFAULT_QQBOT_MARKDOWN_IMAGE_SIZE);
|
|
8403
|
-
|
|
8836
|
+
cache2.set(url, pending);
|
|
8404
8837
|
return pending;
|
|
8405
8838
|
}
|
|
8406
8839
|
async function normalizeTextSegment(params) {
|
|
@@ -8527,13 +8960,29 @@ async function normalizeQQBotMarkdownImages(params) {
|
|
|
8527
8960
|
|
|
8528
8961
|
${appendedImages.join("\n")}`;
|
|
8529
8962
|
}
|
|
8530
|
-
var DEFAULT_KNOWN_TARGETS_PATH = join(homedir(), ".openclaw", "
|
|
8963
|
+
var DEFAULT_KNOWN_TARGETS_PATH = join(homedir(), ".openclaw", "qqbot", "data", "known-targets.json");
|
|
8964
|
+
var LEGACY_KNOWN_TARGETS_PATH = join(homedir(), ".openclaw", "data", "qqbot", "known-targets.json");
|
|
8531
8965
|
function resolveKnownTargetsFilePath(options) {
|
|
8532
8966
|
return options?.filePath?.trim() || DEFAULT_KNOWN_TARGETS_PATH;
|
|
8533
8967
|
}
|
|
8534
8968
|
function ensureKnownTargetsDir(filePath) {
|
|
8535
8969
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
8536
8970
|
}
|
|
8971
|
+
function migrateLegacyKnownTargets(filePath) {
|
|
8972
|
+
if (filePath !== DEFAULT_KNOWN_TARGETS_PATH) {
|
|
8973
|
+
return;
|
|
8974
|
+
}
|
|
8975
|
+
if (existsSync(filePath) || !existsSync(LEGACY_KNOWN_TARGETS_PATH)) {
|
|
8976
|
+
return;
|
|
8977
|
+
}
|
|
8978
|
+
ensureKnownTargetsDir(filePath);
|
|
8979
|
+
try {
|
|
8980
|
+
renameSync(LEGACY_KNOWN_TARGETS_PATH, filePath);
|
|
8981
|
+
} catch {
|
|
8982
|
+
copyFileSync(LEGACY_KNOWN_TARGETS_PATH, filePath);
|
|
8983
|
+
rmSync(LEGACY_KNOWN_TARGETS_PATH, { force: true });
|
|
8984
|
+
}
|
|
8985
|
+
}
|
|
8537
8986
|
function compareTargetsByLastSeenDesc(a, b) {
|
|
8538
8987
|
if (b.lastSeenAt !== a.lastSeenAt) {
|
|
8539
8988
|
return b.lastSeenAt - a.lastSeenAt;
|
|
@@ -8569,6 +9018,7 @@ function parseKnownTargets(raw, filePath) {
|
|
|
8569
9018
|
}
|
|
8570
9019
|
function readKnownTargets(options) {
|
|
8571
9020
|
const filePath = resolveKnownTargetsFilePath(options);
|
|
9021
|
+
migrateLegacyKnownTargets(filePath);
|
|
8572
9022
|
if (!existsSync(filePath)) {
|
|
8573
9023
|
return [];
|
|
8574
9024
|
}
|
|
@@ -8580,6 +9030,7 @@ function readKnownTargets(options) {
|
|
|
8580
9030
|
}
|
|
8581
9031
|
function writeKnownTargets(targets, options) {
|
|
8582
9032
|
const filePath = resolveKnownTargetsFilePath(options);
|
|
9033
|
+
migrateLegacyKnownTargets(filePath);
|
|
8583
9034
|
if (targets.length === 0) {
|
|
8584
9035
|
if (existsSync(filePath)) {
|
|
8585
9036
|
rmSync(filePath, { force: true });
|
|
@@ -8713,84 +9164,433 @@ function getQQBotRuntime() {
|
|
|
8713
9164
|
return runtime;
|
|
8714
9165
|
}
|
|
8715
9166
|
var sessionDispatchQueue = /* @__PURE__ */ new Map();
|
|
9167
|
+
var QQBOT_ABORT_TRIGGERS = /* @__PURE__ */ new Set([
|
|
9168
|
+
"stop",
|
|
9169
|
+
"esc",
|
|
9170
|
+
"abort",
|
|
9171
|
+
"wait",
|
|
9172
|
+
"exit",
|
|
9173
|
+
"interrupt",
|
|
9174
|
+
"detente",
|
|
9175
|
+
"deten",
|
|
9176
|
+
"det\xE9n",
|
|
9177
|
+
"arrete",
|
|
9178
|
+
"arr\xEAte",
|
|
9179
|
+
"\u505C\u6B62",
|
|
9180
|
+
"\u3084\u3081\u3066",
|
|
9181
|
+
"\u6B62\u3081\u3066",
|
|
9182
|
+
"\u0930\u0941\u0915\u094B",
|
|
9183
|
+
"\u062A\u0648\u0642\u0641",
|
|
9184
|
+
"\u0441\u0442\u043E\u043F",
|
|
9185
|
+
"\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0441\u044C",
|
|
9186
|
+
"\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438",
|
|
9187
|
+
"\u043E\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C",
|
|
9188
|
+
"\u043F\u0440\u0435\u043A\u0440\u0430\u0442\u0438",
|
|
9189
|
+
"halt",
|
|
9190
|
+
"anhalten",
|
|
9191
|
+
"aufh\xF6ren",
|
|
9192
|
+
"hoer auf",
|
|
9193
|
+
"stopp",
|
|
9194
|
+
"pare",
|
|
9195
|
+
"stop openclaw",
|
|
9196
|
+
"openclaw stop",
|
|
9197
|
+
"stop action",
|
|
9198
|
+
"stop current action",
|
|
9199
|
+
"stop run",
|
|
9200
|
+
"stop current run",
|
|
9201
|
+
"stop agent",
|
|
9202
|
+
"stop the agent",
|
|
9203
|
+
"stop don't do anything",
|
|
9204
|
+
"stop dont do anything",
|
|
9205
|
+
"stop do not do anything",
|
|
9206
|
+
"stop doing anything",
|
|
9207
|
+
"do not do that",
|
|
9208
|
+
"please stop",
|
|
9209
|
+
"stop please"
|
|
9210
|
+
]);
|
|
9211
|
+
var QQBOT_ABORT_TRAILING_PUNCTUATION_RE = /[.!?…,,。;;::'"’”)\]}]+$/u;
|
|
9212
|
+
function resolveQQBotRouteSessionKey(route) {
|
|
9213
|
+
const effectiveSessionKey = route.effectiveSessionKey?.trim();
|
|
9214
|
+
if (effectiveSessionKey) {
|
|
9215
|
+
return effectiveSessionKey;
|
|
9216
|
+
}
|
|
9217
|
+
return route.sessionKey;
|
|
9218
|
+
}
|
|
8716
9219
|
function buildSessionDispatchQueueKey(route) {
|
|
8717
9220
|
const accountId = route.accountId?.trim() || DEFAULT_ACCOUNT_ID;
|
|
8718
|
-
return `${accountId}:${route
|
|
9221
|
+
return `${accountId}:${resolveQQBotRouteSessionKey(route)}`;
|
|
8719
9222
|
}
|
|
8720
|
-
|
|
8721
|
-
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
9223
|
+
function createSessionDispatchState() {
|
|
9224
|
+
return {
|
|
9225
|
+
queue: [],
|
|
9226
|
+
processing: false,
|
|
9227
|
+
immediateActiveCount: 0,
|
|
9228
|
+
waiters: [],
|
|
9229
|
+
abortGeneration: 0
|
|
9230
|
+
};
|
|
9231
|
+
}
|
|
9232
|
+
function getSessionDispatchState(queueKey) {
|
|
9233
|
+
const existing = sessionDispatchQueue.get(queueKey);
|
|
9234
|
+
if (existing) {
|
|
9235
|
+
return existing;
|
|
9236
|
+
}
|
|
9237
|
+
const created = createSessionDispatchState();
|
|
9238
|
+
sessionDispatchQueue.set(queueKey, created);
|
|
9239
|
+
return created;
|
|
9240
|
+
}
|
|
9241
|
+
function signalSessionDispatchState(state) {
|
|
9242
|
+
const waiters = state.waiters.splice(0, state.waiters.length);
|
|
9243
|
+
for (const resolve3 of waiters) {
|
|
9244
|
+
resolve3();
|
|
9245
|
+
}
|
|
9246
|
+
}
|
|
9247
|
+
function waitForSessionDispatchState(state) {
|
|
9248
|
+
return new Promise((resolve3) => {
|
|
9249
|
+
state.waiters.push(resolve3);
|
|
9250
|
+
});
|
|
9251
|
+
}
|
|
9252
|
+
function cleanupSessionDispatchState(queueKey, state) {
|
|
9253
|
+
if (state.processing || state.immediateActiveCount > 0 || state.queue.length > 0 || state.waiters.length > 0) {
|
|
9254
|
+
return;
|
|
9255
|
+
}
|
|
9256
|
+
if (sessionDispatchQueue.get(queueKey) === state) {
|
|
9257
|
+
sessionDispatchQueue.delete(queueKey);
|
|
9258
|
+
}
|
|
9259
|
+
}
|
|
9260
|
+
function hasSessionDispatchBacklog(queueKey) {
|
|
9261
|
+
const state = sessionDispatchQueue.get(queueKey);
|
|
9262
|
+
return Boolean(
|
|
9263
|
+
state && (state.processing || state.immediateActiveCount > 0 || state.queue.length > 0)
|
|
9264
|
+
);
|
|
9265
|
+
}
|
|
9266
|
+
async function processSerializedSessionDispatchQueue(queueKey) {
|
|
9267
|
+
const state = sessionDispatchQueue.get(queueKey);
|
|
9268
|
+
if (!state || state.processing) {
|
|
9269
|
+
return;
|
|
9270
|
+
}
|
|
9271
|
+
state.processing = true;
|
|
8725
9272
|
try {
|
|
8726
|
-
|
|
9273
|
+
for (; ; ) {
|
|
9274
|
+
if (state.immediateActiveCount > 0) {
|
|
9275
|
+
await waitForSessionDispatchState(state);
|
|
9276
|
+
continue;
|
|
9277
|
+
}
|
|
9278
|
+
const next = state.queue.shift();
|
|
9279
|
+
if (!next) {
|
|
9280
|
+
break;
|
|
9281
|
+
}
|
|
9282
|
+
try {
|
|
9283
|
+
await next.task();
|
|
9284
|
+
next.resolve();
|
|
9285
|
+
} catch (err) {
|
|
9286
|
+
next.reject(err);
|
|
9287
|
+
}
|
|
9288
|
+
}
|
|
8727
9289
|
} finally {
|
|
8728
|
-
|
|
8729
|
-
|
|
9290
|
+
state.processing = false;
|
|
9291
|
+
if (state.queue.length > 0) {
|
|
9292
|
+
void processSerializedSessionDispatchQueue(queueKey);
|
|
9293
|
+
return;
|
|
8730
9294
|
}
|
|
9295
|
+
cleanupSessionDispatchState(queueKey, state);
|
|
8731
9296
|
}
|
|
8732
9297
|
}
|
|
8733
|
-
function
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
function toNumber2(value) {
|
|
8738
|
-
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
8739
|
-
if (typeof value === "string") {
|
|
8740
|
-
const parsed = Date.parse(value);
|
|
8741
|
-
if (!Number.isNaN(parsed)) return parsed;
|
|
9298
|
+
function dropQueuedSessionDispatches(queueKey) {
|
|
9299
|
+
const state = sessionDispatchQueue.get(queueKey);
|
|
9300
|
+
if (!state || state.queue.length === 0) {
|
|
9301
|
+
return 0;
|
|
8742
9302
|
}
|
|
8743
|
-
|
|
9303
|
+
const dropped = state.queue.splice(0, state.queue.length);
|
|
9304
|
+
for (const item of dropped) {
|
|
9305
|
+
item.resolve();
|
|
9306
|
+
}
|
|
9307
|
+
signalSessionDispatchState(state);
|
|
9308
|
+
cleanupSessionDispatchState(queueKey, state);
|
|
9309
|
+
return dropped.length;
|
|
8744
9310
|
}
|
|
8745
|
-
function
|
|
8746
|
-
|
|
8747
|
-
|
|
8748
|
-
|
|
9311
|
+
function markSessionDispatchAbort(queueKey) {
|
|
9312
|
+
const state = getSessionDispatchState(queueKey);
|
|
9313
|
+
state.abortGeneration += 1;
|
|
9314
|
+
signalSessionDispatchState(state);
|
|
9315
|
+
return state.abortGeneration;
|
|
8749
9316
|
}
|
|
8750
|
-
function
|
|
8751
|
-
if (typeof value !== "string") return void 0;
|
|
9317
|
+
function normalizeQQBotSessionKeyPart(value) {
|
|
8752
9318
|
const trimmed = value.trim();
|
|
8753
|
-
|
|
8754
|
-
if (trimmed.startsWith("//")) return `https:${trimmed}`;
|
|
8755
|
-
return trimmed;
|
|
9319
|
+
return trimmed ? trimmed.toLowerCase() : "unknown";
|
|
8756
9320
|
}
|
|
8757
|
-
function
|
|
8758
|
-
const
|
|
8759
|
-
|
|
8760
|
-
const
|
|
8761
|
-
|
|
8762
|
-
|
|
8763
|
-
const data = entry;
|
|
8764
|
-
const url = normalizeAttachmentUrl(data.url);
|
|
8765
|
-
if (!url) continue;
|
|
8766
|
-
items.push({
|
|
8767
|
-
url,
|
|
8768
|
-
filename: toString(data.filename),
|
|
8769
|
-
contentType: toString(data.content_type),
|
|
8770
|
-
size: toNonNegativeNumber(data.size)
|
|
8771
|
-
});
|
|
9321
|
+
function buildQQBotDirectSessionKey(params) {
|
|
9322
|
+
const normalizedAccountId = normalizeQQBotSessionKeyPart(params.accountId);
|
|
9323
|
+
const normalizedSenderId = normalizeQQBotSessionKeyPart(params.senderStableId);
|
|
9324
|
+
const trimmedRouteSessionKey = params.routeSessionKey.trim();
|
|
9325
|
+
if (!trimmedRouteSessionKey) {
|
|
9326
|
+
return `agent:main:qqbot:dm:${normalizedAccountId}:${normalizedSenderId}`;
|
|
8772
9327
|
}
|
|
8773
|
-
|
|
8774
|
-
|
|
8775
|
-
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
return {
|
|
8779
|
-
text: rawContent.trim(),
|
|
8780
|
-
attachments
|
|
8781
|
-
};
|
|
9328
|
+
const qqAgentRouteMatch = trimmedRouteSessionKey.match(/^(agent:[^:]+:qqbot:)(?:direct|dm):.+$/i);
|
|
9329
|
+
if (qqAgentRouteMatch?.[1]) {
|
|
9330
|
+
return `${qqAgentRouteMatch[1]}dm:${normalizedAccountId}:${normalizedSenderId}`;
|
|
9331
|
+
}
|
|
9332
|
+
return `${trimmedRouteSessionKey}:dm:${normalizedAccountId}:${normalizedSenderId}`;
|
|
8782
9333
|
}
|
|
8783
|
-
function
|
|
8784
|
-
|
|
9334
|
+
function normalizeQQBotReplyTarget(value) {
|
|
9335
|
+
if (typeof value !== "string") {
|
|
9336
|
+
return void 0;
|
|
9337
|
+
}
|
|
9338
|
+
let trimmed = value.trim();
|
|
9339
|
+
if (!trimmed) {
|
|
9340
|
+
return void 0;
|
|
9341
|
+
}
|
|
9342
|
+
if (/^qqbot:/i.test(trimmed)) {
|
|
9343
|
+
trimmed = trimmed.slice("qqbot:".length).trim();
|
|
9344
|
+
}
|
|
9345
|
+
if (/^c2c:/i.test(trimmed)) {
|
|
9346
|
+
const openid = trimmed.slice("c2c:".length).trim();
|
|
9347
|
+
return openid ? `user:${openid}` : void 0;
|
|
9348
|
+
}
|
|
9349
|
+
return /^(user|group|channel):/i.test(trimmed) ? trimmed : void 0;
|
|
8785
9350
|
}
|
|
8786
|
-
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
9351
|
+
async function runSerializedSessionDispatch(queueKey, task) {
|
|
9352
|
+
const state = getSessionDispatchState(queueKey);
|
|
9353
|
+
return new Promise((resolve3, reject) => {
|
|
9354
|
+
let settled = false;
|
|
9355
|
+
state.queue.push({
|
|
9356
|
+
task: async () => {
|
|
9357
|
+
if (settled) {
|
|
9358
|
+
return;
|
|
9359
|
+
}
|
|
9360
|
+
try {
|
|
9361
|
+
const result = await task();
|
|
9362
|
+
if (!settled) {
|
|
9363
|
+
settled = true;
|
|
9364
|
+
resolve3(result);
|
|
9365
|
+
}
|
|
9366
|
+
} catch (err) {
|
|
9367
|
+
if (!settled) {
|
|
9368
|
+
settled = true;
|
|
9369
|
+
reject(err);
|
|
9370
|
+
}
|
|
9371
|
+
}
|
|
9372
|
+
},
|
|
9373
|
+
resolve: () => {
|
|
9374
|
+
if (settled) {
|
|
9375
|
+
return;
|
|
9376
|
+
}
|
|
9377
|
+
settled = true;
|
|
9378
|
+
resolve3(void 0);
|
|
9379
|
+
},
|
|
9380
|
+
reject: (err) => {
|
|
9381
|
+
if (settled) {
|
|
9382
|
+
return;
|
|
9383
|
+
}
|
|
9384
|
+
settled = true;
|
|
9385
|
+
reject(err);
|
|
9386
|
+
}
|
|
9387
|
+
});
|
|
9388
|
+
signalSessionDispatchState(state);
|
|
9389
|
+
void processSerializedSessionDispatchQueue(queueKey);
|
|
9390
|
+
});
|
|
9391
|
+
}
|
|
9392
|
+
async function runImmediateSessionDispatch(queueKey, task) {
|
|
9393
|
+
const state = getSessionDispatchState(queueKey);
|
|
9394
|
+
state.immediateActiveCount += 1;
|
|
9395
|
+
signalSessionDispatchState(state);
|
|
9396
|
+
try {
|
|
9397
|
+
return await task();
|
|
9398
|
+
} finally {
|
|
9399
|
+
state.immediateActiveCount = Math.max(0, state.immediateActiveCount - 1);
|
|
9400
|
+
signalSessionDispatchState(state);
|
|
9401
|
+
if (state.queue.length > 0) {
|
|
9402
|
+
void processSerializedSessionDispatchQueue(queueKey);
|
|
9403
|
+
}
|
|
9404
|
+
cleanupSessionDispatchState(queueKey, state);
|
|
9405
|
+
}
|
|
9406
|
+
}
|
|
9407
|
+
function normalizeQQBotAbortTriggerText(text) {
|
|
9408
|
+
return text.trim().toLowerCase().replace(/[’`]/g, "'").replace(/\s+/g, " ").replace(QQBOT_ABORT_TRAILING_PUNCTUATION_RE, "").trim();
|
|
9409
|
+
}
|
|
9410
|
+
function isQQBotAbortTrigger(text) {
|
|
9411
|
+
if (!text) {
|
|
9412
|
+
return false;
|
|
9413
|
+
}
|
|
9414
|
+
return QQBOT_ABORT_TRIGGERS.has(normalizeQQBotAbortTriggerText(text));
|
|
9415
|
+
}
|
|
9416
|
+
function isQQBotFastAbortCommandText(text) {
|
|
9417
|
+
if (!text) {
|
|
9418
|
+
return false;
|
|
9419
|
+
}
|
|
9420
|
+
const normalized = text.trim();
|
|
9421
|
+
if (!normalized) {
|
|
9422
|
+
return false;
|
|
9423
|
+
}
|
|
9424
|
+
const lower = normalized.toLowerCase();
|
|
9425
|
+
return lower === "/stop" || normalizeQQBotAbortTriggerText(lower) === "/stop" || isQQBotAbortTrigger(lower);
|
|
9426
|
+
}
|
|
9427
|
+
function toString(value) {
|
|
9428
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
9429
|
+
return void 0;
|
|
9430
|
+
}
|
|
9431
|
+
function asRecord(value) {
|
|
9432
|
+
if (!value || typeof value !== "object") {
|
|
9433
|
+
return void 0;
|
|
9434
|
+
}
|
|
9435
|
+
return value;
|
|
9436
|
+
}
|
|
9437
|
+
function normalizeQQBotDisplayAliasesMap(raw) {
|
|
9438
|
+
if (!raw || typeof raw !== "object") {
|
|
9439
|
+
return {};
|
|
9440
|
+
}
|
|
9441
|
+
const aliases = {};
|
|
9442
|
+
for (const [rawKey, rawValue] of Object.entries(raw)) {
|
|
9443
|
+
const key = rawKey.trim();
|
|
9444
|
+
const value = toString(rawValue);
|
|
9445
|
+
if (!key || !value) {
|
|
9446
|
+
continue;
|
|
9447
|
+
}
|
|
9448
|
+
aliases[key] = value;
|
|
9449
|
+
}
|
|
9450
|
+
return aliases;
|
|
9451
|
+
}
|
|
9452
|
+
function resolveQQBotDisplayAliasMaps(cfg, accountId) {
|
|
9453
|
+
const qqbot = cfg?.channels?.qqbot;
|
|
9454
|
+
return {
|
|
9455
|
+
globalAliases: normalizeQQBotDisplayAliasesMap(qqbot?.displayAliases),
|
|
9456
|
+
accountAliases: normalizeQQBotDisplayAliasesMap(qqbot?.accounts?.[accountId]?.displayAliases)
|
|
9457
|
+
};
|
|
9458
|
+
}
|
|
9459
|
+
function resolveQQBotSenderName(params) {
|
|
9460
|
+
const { inbound, cfg, accountId } = params;
|
|
9461
|
+
const stableId = inbound.c2cOpenid?.trim() || inbound.senderId.trim();
|
|
9462
|
+
const { globalAliases, accountAliases } = resolveQQBotDisplayAliasMaps(cfg, accountId);
|
|
9463
|
+
if (inbound.type === "direct") {
|
|
9464
|
+
const knownTarget = stableId ? getKnownQQBotTarget({ accountId, target: `user:${stableId}` }) : void 0;
|
|
9465
|
+
const knownTargetDisplayName = knownTarget?.displayName?.trim();
|
|
9466
|
+
if (knownTargetDisplayName) {
|
|
9467
|
+
return {
|
|
9468
|
+
displayName: knownTargetDisplayName,
|
|
9469
|
+
persistentDisplayName: knownTargetDisplayName,
|
|
9470
|
+
source: "known-target",
|
|
9471
|
+
knownTargetDisplayName
|
|
9472
|
+
};
|
|
9473
|
+
}
|
|
9474
|
+
const aliasKeys = [...new Set([`user:${stableId}`, stableId, inbound.senderId.trim()].filter(Boolean))];
|
|
9475
|
+
for (const aliasKey of aliasKeys) {
|
|
9476
|
+
const alias = accountAliases[aliasKey];
|
|
9477
|
+
if (alias) {
|
|
9478
|
+
return {
|
|
9479
|
+
displayName: alias,
|
|
9480
|
+
persistentDisplayName: alias,
|
|
9481
|
+
source: "account-alias",
|
|
9482
|
+
matchedAliasKey: aliasKey
|
|
9483
|
+
};
|
|
9484
|
+
}
|
|
9485
|
+
}
|
|
9486
|
+
for (const aliasKey of aliasKeys) {
|
|
9487
|
+
const alias = globalAliases[aliasKey];
|
|
9488
|
+
if (alias) {
|
|
9489
|
+
return {
|
|
9490
|
+
displayName: alias,
|
|
9491
|
+
persistentDisplayName: alias,
|
|
9492
|
+
source: "global-alias",
|
|
9493
|
+
matchedAliasKey: aliasKey
|
|
9494
|
+
};
|
|
9495
|
+
}
|
|
9496
|
+
}
|
|
9497
|
+
}
|
|
9498
|
+
return {
|
|
9499
|
+
displayName: stableId,
|
|
9500
|
+
source: "stable-id"
|
|
9501
|
+
};
|
|
9502
|
+
}
|
|
9503
|
+
function logQQBotSenderNameResolution(params) {
|
|
9504
|
+
const { logger, inbound, accountId, resolution } = params;
|
|
9505
|
+
logger.debug?.(
|
|
9506
|
+
`[display-name] accountId=${accountId} type=${inbound.type} senderId=${inbound.senderId} knownTarget=${resolution.knownTargetDisplayName ?? "-"} alias=${resolution.matchedAliasKey ?? "-"} final=${JSON.stringify(resolution.displayName)} source=${resolution.source}`
|
|
9507
|
+
);
|
|
9508
|
+
}
|
|
9509
|
+
function toNumber2(value) {
|
|
9510
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
9511
|
+
if (typeof value === "string") {
|
|
9512
|
+
const parsed = Date.parse(value);
|
|
9513
|
+
if (!Number.isNaN(parsed)) return parsed;
|
|
9514
|
+
}
|
|
9515
|
+
return void 0;
|
|
9516
|
+
}
|
|
9517
|
+
function toNonNegativeNumber(value) {
|
|
9518
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
|
|
9519
|
+
if (value < 0) return void 0;
|
|
9520
|
+
return value;
|
|
9521
|
+
}
|
|
9522
|
+
function normalizeAttachmentUrl(value) {
|
|
9523
|
+
if (typeof value !== "string") return void 0;
|
|
9524
|
+
const trimmed = value.trim();
|
|
9525
|
+
if (!trimmed) return void 0;
|
|
9526
|
+
if (trimmed.startsWith("//")) return `https:${trimmed}`;
|
|
9527
|
+
return trimmed;
|
|
9528
|
+
}
|
|
9529
|
+
function parseAttachments(payload) {
|
|
9530
|
+
const raw = payload.attachments;
|
|
9531
|
+
if (!Array.isArray(raw)) return [];
|
|
9532
|
+
const items = [];
|
|
9533
|
+
for (const entry of raw) {
|
|
9534
|
+
if (!entry || typeof entry !== "object") continue;
|
|
9535
|
+
const data = entry;
|
|
9536
|
+
const url = normalizeAttachmentUrl(data.url);
|
|
9537
|
+
if (!url) continue;
|
|
9538
|
+
items.push({
|
|
9539
|
+
url,
|
|
9540
|
+
filename: toString(data.filename),
|
|
9541
|
+
contentType: toString(data.content_type),
|
|
9542
|
+
size: toNonNegativeNumber(data.size)
|
|
9543
|
+
});
|
|
9544
|
+
}
|
|
9545
|
+
return items;
|
|
9546
|
+
}
|
|
9547
|
+
function parseTextWithAttachments(payload) {
|
|
9548
|
+
const rawContent = typeof payload.content === "string" ? payload.content : "";
|
|
9549
|
+
const attachments = parseAttachments(payload);
|
|
9550
|
+
return {
|
|
9551
|
+
text: rawContent.trim(),
|
|
9552
|
+
attachments
|
|
9553
|
+
};
|
|
9554
|
+
}
|
|
9555
|
+
function parseQQBotRefIndices(payload) {
|
|
9556
|
+
const scene = payload.message_scene;
|
|
9557
|
+
if (!scene || typeof scene !== "object") {
|
|
9558
|
+
return {};
|
|
9559
|
+
}
|
|
9560
|
+
const ext = scene.ext;
|
|
9561
|
+
if (!Array.isArray(ext)) {
|
|
9562
|
+
return {};
|
|
9563
|
+
}
|
|
9564
|
+
let refMsgIdx;
|
|
9565
|
+
let msgIdx;
|
|
9566
|
+
for (const value of ext) {
|
|
9567
|
+
const item = toString(value);
|
|
9568
|
+
if (!item) continue;
|
|
9569
|
+
if (item.startsWith("ref_msg_idx=")) {
|
|
9570
|
+
refMsgIdx = toString(item.slice("ref_msg_idx=".length));
|
|
9571
|
+
continue;
|
|
9572
|
+
}
|
|
9573
|
+
if (item.startsWith("msg_idx=")) {
|
|
9574
|
+
msgIdx = toString(item.slice("msg_idx=".length));
|
|
9575
|
+
}
|
|
9576
|
+
}
|
|
9577
|
+
return {
|
|
9578
|
+
...refMsgIdx ? { refMsgIdx } : {},
|
|
9579
|
+
...msgIdx ? { msgIdx } : {}
|
|
9580
|
+
};
|
|
9581
|
+
}
|
|
9582
|
+
function resolveEventId(payload, fallbackEventId) {
|
|
9583
|
+
return toString(payload.event_id) ?? toString(payload.eventId) ?? toString(fallbackEventId);
|
|
9584
|
+
}
|
|
9585
|
+
var VOICE_ASR_FALLBACK_TEXT = "\u5F53\u524D\u8BED\u97F3\u529F\u80FD\u672A\u542F\u52A8\u6216\u8BC6\u522B\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002";
|
|
9586
|
+
var VOICE_EXTENSIONS = [".silk", ".amr", ".mp3", ".wav", ".ogg", ".m4a", ".aac", ".speex"];
|
|
9587
|
+
var VOICE_ASR_ERROR_MAX_LENGTH = 500;
|
|
9588
|
+
var LONG_TASK_NOTICE_TEXT = "\u4EFB\u52A1\u5904\u7406\u65F6\u95F4\u8F83\u957F\uFF0C\u8BF7\u7A0D\u7B49\uFF0C\u6211\u8FD8\u5728\u7EE7\u7EED\u5904\u7406\u3002";
|
|
9589
|
+
var DEFAULT_LONG_TASK_NOTICE_DELAY_MS = 3e4;
|
|
9590
|
+
var QQ_GROUP_NO_REPLY_FALLBACK_TEXT = "\u6211\u5728\u3002\u4F60\u53EF\u4EE5\u76F4\u63A5\u8BF4\u5177\u4F53\u4E00\u70B9\u3002";
|
|
9591
|
+
var QQ_QUOTE_BODY_UNAVAILABLE_TEXT = "\u539F\u59CB\u5185\u5BB9\u4E0D\u53EF\u7528";
|
|
9592
|
+
function startLongTaskNoticeTimer(params) {
|
|
9593
|
+
const { delayMs, logger, sendNotice } = params;
|
|
8794
9594
|
let completed = false;
|
|
8795
9595
|
let timer = null;
|
|
8796
9596
|
const clear = () => {
|
|
@@ -8823,7 +9623,7 @@ function startLongTaskNoticeTimer(params) {
|
|
|
8823
9623
|
}
|
|
8824
9624
|
};
|
|
8825
9625
|
}
|
|
8826
|
-
function
|
|
9626
|
+
function isHttpUrl3(value) {
|
|
8827
9627
|
return /^https?:\/\//i.test(value);
|
|
8828
9628
|
}
|
|
8829
9629
|
function isImageAttachment(att) {
|
|
@@ -8896,7 +9696,7 @@ async function resolveInboundAttachmentsForAgent(params) {
|
|
|
8896
9696
|
let asrErrorMessage;
|
|
8897
9697
|
for (const att of list) {
|
|
8898
9698
|
const next = { attachment: att };
|
|
8899
|
-
if (isImageAttachment(att) &&
|
|
9699
|
+
if (isImageAttachment(att) && isHttpUrl3(att.url)) {
|
|
8900
9700
|
try {
|
|
8901
9701
|
const downloaded = await downloadToTempFile(att.url, {
|
|
8902
9702
|
timeout,
|
|
@@ -8925,7 +9725,7 @@ async function resolveInboundAttachmentsForAgent(params) {
|
|
|
8925
9725
|
logger.info("voice attachment received but ASR is disabled");
|
|
8926
9726
|
} else if (!asrCredentials) {
|
|
8927
9727
|
logger.warn("voice ASR enabled but credentials are missing or invalid");
|
|
8928
|
-
} else if (!
|
|
9728
|
+
} else if (!isHttpUrl3(att.url)) {
|
|
8929
9729
|
logger.warn("voice ASR skipped: attachment URL is not an HTTP URL");
|
|
8930
9730
|
} else {
|
|
8931
9731
|
try {
|
|
@@ -8996,6 +9796,74 @@ function buildInboundContentWithAttachments(params) {
|
|
|
8996
9796
|
parts.push(block);
|
|
8997
9797
|
return parts.join("\n\n");
|
|
8998
9798
|
}
|
|
9799
|
+
function resolveRefAttachmentType(attachment) {
|
|
9800
|
+
const contentType = attachment.contentType?.trim().toLowerCase() ?? "";
|
|
9801
|
+
if (contentType.startsWith("image/") || isImageAttachment(attachment)) {
|
|
9802
|
+
return "image";
|
|
9803
|
+
}
|
|
9804
|
+
if (contentType === "voice" || contentType.startsWith("audio/") || isVoiceAttachment(attachment)) {
|
|
9805
|
+
return "voice";
|
|
9806
|
+
}
|
|
9807
|
+
if (contentType.startsWith("video/")) {
|
|
9808
|
+
return "video";
|
|
9809
|
+
}
|
|
9810
|
+
const mediaType = detectMediaType(attachment.filename?.trim() || attachment.url);
|
|
9811
|
+
if (mediaType === "image") return "image";
|
|
9812
|
+
if (mediaType === "audio") return "voice";
|
|
9813
|
+
if (mediaType === "video") return "video";
|
|
9814
|
+
if (mediaType === "file") return "file";
|
|
9815
|
+
return "unknown";
|
|
9816
|
+
}
|
|
9817
|
+
function buildInboundRefAttachmentSummaries(attachments) {
|
|
9818
|
+
if (attachments.length === 0) {
|
|
9819
|
+
return void 0;
|
|
9820
|
+
}
|
|
9821
|
+
return attachments.map((item) => ({
|
|
9822
|
+
type: resolveRefAttachmentType(item.attachment),
|
|
9823
|
+
...item.attachment.filename?.trim() ? { filename: item.attachment.filename.trim() } : {},
|
|
9824
|
+
...item.attachment.contentType?.trim() ? { contentType: item.attachment.contentType.trim() } : {},
|
|
9825
|
+
...item.localImagePath?.trim() ? { localPath: item.localImagePath.trim() } : {},
|
|
9826
|
+
...item.attachment.url?.trim() ? { url: item.attachment.url.trim() } : {},
|
|
9827
|
+
...item.voiceTranscript?.trim() ? {
|
|
9828
|
+
transcript: item.voiceTranscript.trim(),
|
|
9829
|
+
transcriptSource: "asr"
|
|
9830
|
+
} : {}
|
|
9831
|
+
}));
|
|
9832
|
+
}
|
|
9833
|
+
function buildQuotedAgentBody(params) {
|
|
9834
|
+
const quoteBlock = `[\u5F15\u7528\u6D88\u606F\u5F00\u59CB]
|
|
9835
|
+
${params.replyToBody}
|
|
9836
|
+
[\u5F15\u7528\u6D88\u606F\u7ED3\u675F]`;
|
|
9837
|
+
return params.baseBody ? `${quoteBlock}
|
|
9838
|
+
|
|
9839
|
+
${params.baseBody}` : quoteBlock;
|
|
9840
|
+
}
|
|
9841
|
+
function resolveAgentBodyBase(ctx) {
|
|
9842
|
+
if (typeof ctx.BodyForAgent === "string" && ctx.BodyForAgent.trim()) {
|
|
9843
|
+
return ctx.BodyForAgent;
|
|
9844
|
+
}
|
|
9845
|
+
if (typeof ctx.RawBody === "string" && ctx.RawBody.trim()) {
|
|
9846
|
+
return ctx.RawBody;
|
|
9847
|
+
}
|
|
9848
|
+
if (typeof ctx.Body === "string" && ctx.Body.trim()) {
|
|
9849
|
+
return ctx.Body;
|
|
9850
|
+
}
|
|
9851
|
+
if (typeof ctx.CommandBody === "string" && ctx.CommandBody.trim()) {
|
|
9852
|
+
return ctx.CommandBody;
|
|
9853
|
+
}
|
|
9854
|
+
return "";
|
|
9855
|
+
}
|
|
9856
|
+
function uniqueRefIndexKeys(...values) {
|
|
9857
|
+
const keys = [];
|
|
9858
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9859
|
+
for (const value of values) {
|
|
9860
|
+
const next = value?.trim();
|
|
9861
|
+
if (!next || seen.has(next)) continue;
|
|
9862
|
+
seen.add(next);
|
|
9863
|
+
keys.push(next);
|
|
9864
|
+
}
|
|
9865
|
+
return keys;
|
|
9866
|
+
}
|
|
8999
9867
|
function resolveInboundLogContent(params) {
|
|
9000
9868
|
const text = params.content.trim();
|
|
9001
9869
|
if (text) return text;
|
|
@@ -9017,22 +9885,23 @@ function sanitizeInboundLogText(text) {
|
|
|
9017
9885
|
function parseC2CMessage(data, fallbackEventId) {
|
|
9018
9886
|
const payload = data;
|
|
9019
9887
|
const { text, attachments } = parseTextWithAttachments(payload);
|
|
9888
|
+
const refIndices = parseQQBotRefIndices(payload);
|
|
9020
9889
|
const id = toString(payload.id);
|
|
9021
9890
|
const eventId = resolveEventId(payload, fallbackEventId);
|
|
9022
9891
|
const timestamp = toNumber2(payload.timestamp) ?? Date.now();
|
|
9023
|
-
const author = payload.author ?? {};
|
|
9892
|
+
const author = asRecord(payload.author) ?? {};
|
|
9024
9893
|
const senderId = toString(author.user_openid);
|
|
9025
9894
|
if (!text && attachments.length === 0 || !id || !senderId) return null;
|
|
9026
9895
|
return {
|
|
9027
9896
|
type: "direct",
|
|
9028
9897
|
senderId,
|
|
9029
9898
|
c2cOpenid: senderId,
|
|
9030
|
-
senderName: toString(author.username),
|
|
9031
9899
|
content: text,
|
|
9032
9900
|
attachments: attachments.length > 0 ? attachments : void 0,
|
|
9033
9901
|
messageId: id,
|
|
9034
9902
|
eventId,
|
|
9035
9903
|
timestamp,
|
|
9904
|
+
...refIndices,
|
|
9036
9905
|
mentionedBot: false
|
|
9037
9906
|
};
|
|
9038
9907
|
}
|
|
@@ -9043,13 +9912,12 @@ function parseGroupMessage(data, fallbackEventId) {
|
|
|
9043
9912
|
const eventId = resolveEventId(payload, fallbackEventId);
|
|
9044
9913
|
const timestamp = toNumber2(payload.timestamp) ?? Date.now();
|
|
9045
9914
|
const groupOpenid = toString(payload.group_openid);
|
|
9046
|
-
const author = payload.author ?? {};
|
|
9915
|
+
const author = asRecord(payload.author) ?? {};
|
|
9047
9916
|
const senderId = toString(author.member_openid);
|
|
9048
9917
|
if (!text && attachments.length === 0 || !id || !senderId || !groupOpenid) return null;
|
|
9049
9918
|
return {
|
|
9050
9919
|
type: "group",
|
|
9051
9920
|
senderId,
|
|
9052
|
-
senderName: toString(author.nickname) ?? toString(author.username),
|
|
9053
9921
|
content: text,
|
|
9054
9922
|
attachments: attachments.length > 0 ? attachments : void 0,
|
|
9055
9923
|
messageId: id,
|
|
@@ -9067,13 +9935,12 @@ function parseChannelMessage(data, fallbackEventId) {
|
|
|
9067
9935
|
const timestamp = toNumber2(payload.timestamp) ?? Date.now();
|
|
9068
9936
|
const channelId = toString(payload.channel_id);
|
|
9069
9937
|
const guildId = toString(payload.guild_id);
|
|
9070
|
-
const author = payload.author ?? {};
|
|
9938
|
+
const author = asRecord(payload.author) ?? {};
|
|
9071
9939
|
const senderId = toString(author.id);
|
|
9072
9940
|
if (!text && attachments.length === 0 || !id || !senderId || !channelId) return null;
|
|
9073
9941
|
return {
|
|
9074
9942
|
type: "channel",
|
|
9075
9943
|
senderId,
|
|
9076
|
-
senderName: toString(author.username),
|
|
9077
9944
|
content: text,
|
|
9078
9945
|
attachments: attachments.length > 0 ? attachments : void 0,
|
|
9079
9946
|
messageId: id,
|
|
@@ -9091,13 +9958,12 @@ function parseDirectMessage(data, fallbackEventId) {
|
|
|
9091
9958
|
const eventId = resolveEventId(payload, fallbackEventId);
|
|
9092
9959
|
const timestamp = toNumber2(payload.timestamp) ?? Date.now();
|
|
9093
9960
|
const guildId = toString(payload.guild_id);
|
|
9094
|
-
const author = payload.author ?? {};
|
|
9961
|
+
const author = asRecord(payload.author) ?? {};
|
|
9095
9962
|
const senderId = toString(author.id);
|
|
9096
9963
|
if (!text && attachments.length === 0 || !id || !senderId) return null;
|
|
9097
9964
|
return {
|
|
9098
9965
|
type: "direct",
|
|
9099
9966
|
senderId,
|
|
9100
|
-
senderName: toString(author.username),
|
|
9101
9967
|
content: text,
|
|
9102
9968
|
attachments: attachments.length > 0 ? attachments : void 0,
|
|
9103
9969
|
messageId: id,
|
|
@@ -9146,6 +10012,22 @@ function resolveChatTarget(event) {
|
|
|
9146
10012
|
peerKind: "dm"
|
|
9147
10013
|
};
|
|
9148
10014
|
}
|
|
10015
|
+
function resolveQQBotEffectiveSessionKey(params) {
|
|
10016
|
+
const { inbound, route, accountId } = params;
|
|
10017
|
+
if (inbound.type !== "direct") {
|
|
10018
|
+
return route.sessionKey;
|
|
10019
|
+
}
|
|
10020
|
+
const senderStableId = inbound.c2cOpenid?.trim() || inbound.senderId?.trim();
|
|
10021
|
+
if (!senderStableId) {
|
|
10022
|
+
return route.sessionKey;
|
|
10023
|
+
}
|
|
10024
|
+
const resolvedAccountId = route.accountId?.trim() || accountId.trim() || DEFAULT_ACCOUNT_ID;
|
|
10025
|
+
return buildQQBotDirectSessionKey({
|
|
10026
|
+
routeSessionKey: route.sessionKey,
|
|
10027
|
+
accountId: resolvedAccountId,
|
|
10028
|
+
senderStableId
|
|
10029
|
+
});
|
|
10030
|
+
}
|
|
9149
10031
|
function resolveEnvelopeFrom(event) {
|
|
9150
10032
|
if (event.type === "group") {
|
|
9151
10033
|
return `group:${(event.groupOpenid ?? "unknown").toLowerCase()}`;
|
|
@@ -9156,7 +10038,7 @@ function resolveEnvelopeFrom(event) {
|
|
|
9156
10038
|
return event.senderName?.trim() || event.senderId;
|
|
9157
10039
|
}
|
|
9158
10040
|
function resolveKnownQQBotTargetFromInbound(params) {
|
|
9159
|
-
const { inbound, accountId } = params;
|
|
10041
|
+
const { inbound, accountId, persistentDisplayName } = params;
|
|
9160
10042
|
if (inbound.type === "direct") {
|
|
9161
10043
|
if (!inbound.c2cOpenid?.trim()) {
|
|
9162
10044
|
return void 0;
|
|
@@ -9165,7 +10047,7 @@ function resolveKnownQQBotTargetFromInbound(params) {
|
|
|
9165
10047
|
accountId,
|
|
9166
10048
|
kind: "user",
|
|
9167
10049
|
target: `user:${inbound.c2cOpenid}`,
|
|
9168
|
-
displayName:
|
|
10050
|
+
...persistentDisplayName ? { displayName: persistentDisplayName } : {},
|
|
9169
10051
|
sourceChatType: "direct",
|
|
9170
10052
|
firstSeenAt: inbound.timestamp,
|
|
9171
10053
|
lastSeenAt: inbound.timestamp
|
|
@@ -9176,7 +10058,7 @@ function resolveKnownQQBotTargetFromInbound(params) {
|
|
|
9176
10058
|
accountId,
|
|
9177
10059
|
kind: "group",
|
|
9178
10060
|
target: `group:${inbound.groupOpenid}`,
|
|
9179
|
-
displayName:
|
|
10061
|
+
...persistentDisplayName ? { displayName: persistentDisplayName } : {},
|
|
9180
10062
|
sourceChatType: "group",
|
|
9181
10063
|
firstSeenAt: inbound.timestamp,
|
|
9182
10064
|
lastSeenAt: inbound.timestamp
|
|
@@ -9187,7 +10069,7 @@ function resolveKnownQQBotTargetFromInbound(params) {
|
|
|
9187
10069
|
accountId,
|
|
9188
10070
|
kind: "channel",
|
|
9189
10071
|
target: `channel:${inbound.channelId}`,
|
|
9190
|
-
displayName:
|
|
10072
|
+
...persistentDisplayName ? { displayName: persistentDisplayName } : {},
|
|
9191
10073
|
sourceChatType: "channel",
|
|
9192
10074
|
firstSeenAt: inbound.timestamp,
|
|
9193
10075
|
lastSeenAt: inbound.timestamp
|
|
@@ -9302,6 +10184,14 @@ var DIRECTIVE_TAG_RE = /\[\[\s*(?:reply_to_current|reply_to\s*:[^\]]+|audio_as_v
|
|
|
9302
10184
|
var VOICE_EMOTION_TAG_RE = /\[(?:happy|excited|calm|sad|angry|frustrated|softly|whispers|loudly|cheerfully|deadpan|sarcastically|laughs|sighs|chuckles|gasps|pause|slowly|rushed|hesitates|playfully|warmly|gently)\]/gi;
|
|
9303
10185
|
var TTS_LIKE_RAW_TEXT_RE = /\[\[\s*(?:tts(?::text)?|\/tts(?::text)?|audio_as_voice|reply_to_current|reply_to\s*:)/i;
|
|
9304
10186
|
var MARKDOWN_TABLE_SEPARATOR_RE = /^\|?(?:\s*:?-{3,}:?\s*\|)+(?:\s*:?-{3,}:?)?\|?$/;
|
|
10187
|
+
var MARKDOWN_THEMATIC_BREAK_RE = /^\s{0,3}(?:(?:-\s*){3,}|(?:_\s*){3,}|(?:\*\s*){3,})$/;
|
|
10188
|
+
var MARKDOWN_ATX_HEADING_RE = /^\s{0,3}#{1,6}\s+\S/;
|
|
10189
|
+
var MARKDOWN_BLOCKQUOTE_RE = /^\s{0,3}>\s?/;
|
|
10190
|
+
var MARKDOWN_FENCE_RE = /^\s*(`{3,}|~{3,})(.*)$/;
|
|
10191
|
+
var MARKDOWN_LIST_ITEM_RE = /^\s*(?:[-+*]|\d+\.)\s+/;
|
|
10192
|
+
var MARKDOWN_LIST_CONTINUATION_RE = /^\s{2,}\S/;
|
|
10193
|
+
var MARKDOWN_INLINE_STRUCTURE_RE = /(?:\*\*[^*\n]+\*\*|__[^_\n]+__|`[^`\n]+`|~~[^~\n]+~~|\*[^*\n]+\*)/;
|
|
10194
|
+
var MARKDOWN_BOUNDARY_GUARD_RE = /[`*_~|]/;
|
|
9305
10195
|
var EXPLICIT_MARKDOWN_FENCE_RE = /(^|\n)(`{3,}|~{3,})\s*(?:markdown|md)\s*\n([\s\S]*?)\n\2(?=\n|$)/gi;
|
|
9306
10196
|
var GENERIC_MARKDOWN_FENCE_RE = /(^|\n)(`{3,}|~{3,})\s*\n([\s\S]*?)\n\2(?=\n|$)/g;
|
|
9307
10197
|
function extractFinalBlocks(text) {
|
|
@@ -9370,8 +10260,9 @@ function evaluateReplyFinalOnlyDelivery(params) {
|
|
|
9370
10260
|
}
|
|
9371
10261
|
function isQQBotC2CTarget(to) {
|
|
9372
10262
|
const trimmed = to.trim();
|
|
9373
|
-
const raw = trimmed.
|
|
9374
|
-
|
|
10263
|
+
const raw = trimmed.slice(0, "qqbot:".length).toLowerCase() === "qqbot:" ? trimmed.slice("qqbot:".length) : trimmed;
|
|
10264
|
+
const normalizedRaw = raw.toLowerCase();
|
|
10265
|
+
return !normalizedRaw.startsWith("group:") && !normalizedRaw.startsWith("channel:");
|
|
9375
10266
|
}
|
|
9376
10267
|
function splitQQBotMarkdownTransportMediaUrls(mediaUrls) {
|
|
9377
10268
|
const markdownImageUrls = [];
|
|
@@ -9453,36 +10344,619 @@ function normalizeQQBotRenderedMarkdown(text) {
|
|
|
9453
10344
|
if (!normalizedInner) {
|
|
9454
10345
|
return block;
|
|
9455
10346
|
}
|
|
9456
|
-
changed = true;
|
|
9457
|
-
return `${leadingLineBreak}${normalizedInner}`;
|
|
10347
|
+
changed = true;
|
|
10348
|
+
return `${leadingLineBreak}${normalizedInner}`;
|
|
10349
|
+
}
|
|
10350
|
+
);
|
|
10351
|
+
next = next.replace(
|
|
10352
|
+
GENERIC_MARKDOWN_FENCE_RE,
|
|
10353
|
+
(block, leadingLineBreak, _fence, inner) => {
|
|
10354
|
+
const normalizedInner = inner.trim();
|
|
10355
|
+
if (!normalizedInner) {
|
|
10356
|
+
return block;
|
|
10357
|
+
}
|
|
10358
|
+
if (!hasQQBotMarkdownTable(normalizedInner)) {
|
|
10359
|
+
return block;
|
|
10360
|
+
}
|
|
10361
|
+
changed = true;
|
|
10362
|
+
return `${leadingLineBreak}${normalizedInner}`;
|
|
10363
|
+
}
|
|
10364
|
+
);
|
|
10365
|
+
return changed ? next.trim() : text.trim();
|
|
10366
|
+
}
|
|
10367
|
+
function normalizeQQBotMarkdownSegment(text) {
|
|
10368
|
+
return text.replace(/\r\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
10369
|
+
}
|
|
10370
|
+
function isBlankQQBotMarkdownLine(line) {
|
|
10371
|
+
return line.trim().length === 0;
|
|
10372
|
+
}
|
|
10373
|
+
function resolveQQBotFenceDelimiter(line) {
|
|
10374
|
+
const match = line.match(MARKDOWN_FENCE_RE);
|
|
10375
|
+
return match?.[1];
|
|
10376
|
+
}
|
|
10377
|
+
function isQQBotFenceClosingLine(line, delimiter) {
|
|
10378
|
+
const escapedDelimiter = delimiter.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
10379
|
+
const closingRe = new RegExp(`^\\s*${escapedDelimiter}${delimiter[0]}*\\s*$`);
|
|
10380
|
+
return closingRe.test(line);
|
|
10381
|
+
}
|
|
10382
|
+
function joinQQBotMarkdownPieces(parts) {
|
|
10383
|
+
return parts.filter(Boolean).join("\n\n").trim();
|
|
10384
|
+
}
|
|
10385
|
+
function isQQBotMarkdownTableStart(lines, index) {
|
|
10386
|
+
const header = lines[index]?.trim() ?? "";
|
|
10387
|
+
const separator = lines[index + 1]?.trim() ?? "";
|
|
10388
|
+
return Boolean(header.includes("|") && MARKDOWN_TABLE_SEPARATOR_RE.test(separator));
|
|
10389
|
+
}
|
|
10390
|
+
function collectQQBotFencedCodeBlock(lines, startIndex) {
|
|
10391
|
+
const openingLine = lines[startIndex] ?? "";
|
|
10392
|
+
const delimiter = resolveQQBotFenceDelimiter(openingLine) ?? "```";
|
|
10393
|
+
let index = startIndex + 1;
|
|
10394
|
+
while (index < lines.length) {
|
|
10395
|
+
if (isQQBotFenceClosingLine(lines[index] ?? "", delimiter)) {
|
|
10396
|
+
index += 1;
|
|
10397
|
+
break;
|
|
10398
|
+
}
|
|
10399
|
+
index += 1;
|
|
10400
|
+
}
|
|
10401
|
+
return {
|
|
10402
|
+
block: {
|
|
10403
|
+
kind: "code",
|
|
10404
|
+
text: lines.slice(startIndex, index).join("\n").trimEnd()
|
|
10405
|
+
},
|
|
10406
|
+
nextIndex: index
|
|
10407
|
+
};
|
|
10408
|
+
}
|
|
10409
|
+
function collectQQBotMarkdownTableBlock(lines, startIndex) {
|
|
10410
|
+
let index = startIndex + 2;
|
|
10411
|
+
while (index < lines.length) {
|
|
10412
|
+
const line = lines[index] ?? "";
|
|
10413
|
+
if (isBlankQQBotMarkdownLine(line) || !line.includes("|")) {
|
|
10414
|
+
break;
|
|
10415
|
+
}
|
|
10416
|
+
index += 1;
|
|
10417
|
+
}
|
|
10418
|
+
return {
|
|
10419
|
+
block: {
|
|
10420
|
+
kind: "table",
|
|
10421
|
+
text: lines.slice(startIndex, index).join("\n").trimEnd()
|
|
10422
|
+
},
|
|
10423
|
+
nextIndex: index
|
|
10424
|
+
};
|
|
10425
|
+
}
|
|
10426
|
+
function collectQQBotBlockquoteBlock(lines, startIndex) {
|
|
10427
|
+
const collected = [];
|
|
10428
|
+
let index = startIndex;
|
|
10429
|
+
while (index < lines.length) {
|
|
10430
|
+
const line = lines[index] ?? "";
|
|
10431
|
+
if (MARKDOWN_BLOCKQUOTE_RE.test(line)) {
|
|
10432
|
+
collected.push(line);
|
|
10433
|
+
index += 1;
|
|
10434
|
+
continue;
|
|
10435
|
+
}
|
|
10436
|
+
if (isBlankQQBotMarkdownLine(line) && index + 1 < lines.length && MARKDOWN_BLOCKQUOTE_RE.test(lines[index + 1] ?? "")) {
|
|
10437
|
+
collected.push(line);
|
|
10438
|
+
index += 1;
|
|
10439
|
+
continue;
|
|
10440
|
+
}
|
|
10441
|
+
break;
|
|
10442
|
+
}
|
|
10443
|
+
return {
|
|
10444
|
+
block: {
|
|
10445
|
+
kind: "blockquote",
|
|
10446
|
+
text: collected.join("\n").trimEnd()
|
|
10447
|
+
},
|
|
10448
|
+
nextIndex: index
|
|
10449
|
+
};
|
|
10450
|
+
}
|
|
10451
|
+
function collectQQBotListBlock(lines, startIndex) {
|
|
10452
|
+
const collected = [];
|
|
10453
|
+
let index = startIndex;
|
|
10454
|
+
while (index < lines.length) {
|
|
10455
|
+
const line = lines[index] ?? "";
|
|
10456
|
+
if (isBlankQQBotMarkdownLine(line)) {
|
|
10457
|
+
break;
|
|
10458
|
+
}
|
|
10459
|
+
if (MARKDOWN_FENCE_RE.test(line) || MARKDOWN_BLOCKQUOTE_RE.test(line) || MARKDOWN_ATX_HEADING_RE.test(line) || MARKDOWN_THEMATIC_BREAK_RE.test(line) || isQQBotMarkdownTableStart(lines, index)) {
|
|
10460
|
+
break;
|
|
10461
|
+
}
|
|
10462
|
+
if (collected.length > 0 && !MARKDOWN_LIST_ITEM_RE.test(line) && !MARKDOWN_LIST_CONTINUATION_RE.test(line)) {
|
|
10463
|
+
collected.push(line);
|
|
10464
|
+
index += 1;
|
|
10465
|
+
continue;
|
|
10466
|
+
}
|
|
10467
|
+
collected.push(line);
|
|
10468
|
+
index += 1;
|
|
10469
|
+
}
|
|
10470
|
+
return {
|
|
10471
|
+
block: {
|
|
10472
|
+
kind: "list",
|
|
10473
|
+
text: collected.join("\n").trimEnd()
|
|
10474
|
+
},
|
|
10475
|
+
nextIndex: index
|
|
10476
|
+
};
|
|
10477
|
+
}
|
|
10478
|
+
function collectQQBotParagraphBlock(lines, startIndex) {
|
|
10479
|
+
const collected = [];
|
|
10480
|
+
let index = startIndex;
|
|
10481
|
+
while (index < lines.length) {
|
|
10482
|
+
const line = lines[index] ?? "";
|
|
10483
|
+
if (isBlankQQBotMarkdownLine(line)) {
|
|
10484
|
+
break;
|
|
10485
|
+
}
|
|
10486
|
+
if (collected.length > 0 && (MARKDOWN_FENCE_RE.test(line) || MARKDOWN_BLOCKQUOTE_RE.test(line) || MARKDOWN_ATX_HEADING_RE.test(line) || MARKDOWN_THEMATIC_BREAK_RE.test(line) || MARKDOWN_LIST_ITEM_RE.test(line) || isQQBotMarkdownTableStart(lines, index))) {
|
|
10487
|
+
break;
|
|
10488
|
+
}
|
|
10489
|
+
collected.push(line);
|
|
10490
|
+
index += 1;
|
|
10491
|
+
}
|
|
10492
|
+
return {
|
|
10493
|
+
block: {
|
|
10494
|
+
kind: "paragraph",
|
|
10495
|
+
text: collected.join("\n").trimEnd()
|
|
10496
|
+
},
|
|
10497
|
+
nextIndex: index
|
|
10498
|
+
};
|
|
10499
|
+
}
|
|
10500
|
+
function parseQQBotMarkdownBlocks(text) {
|
|
10501
|
+
const lines = text.replace(/\r\n/g, "\n").split("\n");
|
|
10502
|
+
const blocks = [];
|
|
10503
|
+
let index = 0;
|
|
10504
|
+
while (index < lines.length) {
|
|
10505
|
+
while (index < lines.length && isBlankQQBotMarkdownLine(lines[index] ?? "")) {
|
|
10506
|
+
index += 1;
|
|
10507
|
+
}
|
|
10508
|
+
if (index >= lines.length) {
|
|
10509
|
+
break;
|
|
10510
|
+
}
|
|
10511
|
+
const line = lines[index] ?? "";
|
|
10512
|
+
if (MARKDOWN_FENCE_RE.test(line)) {
|
|
10513
|
+
const result2 = collectQQBotFencedCodeBlock(lines, index);
|
|
10514
|
+
blocks.push(result2.block);
|
|
10515
|
+
index = result2.nextIndex;
|
|
10516
|
+
continue;
|
|
10517
|
+
}
|
|
10518
|
+
if (isQQBotMarkdownTableStart(lines, index)) {
|
|
10519
|
+
const result2 = collectQQBotMarkdownTableBlock(lines, index);
|
|
10520
|
+
blocks.push(result2.block);
|
|
10521
|
+
index = result2.nextIndex;
|
|
10522
|
+
continue;
|
|
10523
|
+
}
|
|
10524
|
+
if (MARKDOWN_THEMATIC_BREAK_RE.test(line)) {
|
|
10525
|
+
blocks.push({ kind: "thematic-break", text: line.trim() });
|
|
10526
|
+
index += 1;
|
|
10527
|
+
continue;
|
|
10528
|
+
}
|
|
10529
|
+
if (MARKDOWN_BLOCKQUOTE_RE.test(line)) {
|
|
10530
|
+
const result2 = collectQQBotBlockquoteBlock(lines, index);
|
|
10531
|
+
blocks.push(result2.block);
|
|
10532
|
+
index = result2.nextIndex;
|
|
10533
|
+
continue;
|
|
10534
|
+
}
|
|
10535
|
+
if (MARKDOWN_ATX_HEADING_RE.test(line)) {
|
|
10536
|
+
blocks.push({ kind: "heading", text: line.trimEnd() });
|
|
10537
|
+
index += 1;
|
|
10538
|
+
continue;
|
|
10539
|
+
}
|
|
10540
|
+
if (MARKDOWN_LIST_ITEM_RE.test(line)) {
|
|
10541
|
+
const result2 = collectQQBotListBlock(lines, index);
|
|
10542
|
+
blocks.push(result2.block);
|
|
10543
|
+
index = result2.nextIndex;
|
|
10544
|
+
continue;
|
|
10545
|
+
}
|
|
10546
|
+
const result = collectQQBotParagraphBlock(lines, index);
|
|
10547
|
+
blocks.push(result.block);
|
|
10548
|
+
index = result.nextIndex;
|
|
10549
|
+
}
|
|
10550
|
+
return blocks;
|
|
10551
|
+
}
|
|
10552
|
+
function hasQQBotBoundaryGuard(text) {
|
|
10553
|
+
return MARKDOWN_BOUNDARY_GUARD_RE.test(text);
|
|
10554
|
+
}
|
|
10555
|
+
function isQQBotSafeMarkdownBoundary(text, index) {
|
|
10556
|
+
const left = text.slice(Math.max(0, index - 3), index).replace(/\s+/g, "");
|
|
10557
|
+
const right = text.slice(index, Math.min(text.length, index + 3)).replace(/\s+/g, "");
|
|
10558
|
+
const leftEdge = left.slice(-1);
|
|
10559
|
+
const rightEdge = right.slice(0, 1);
|
|
10560
|
+
return !hasQQBotBoundaryGuard(leftEdge) && !hasQQBotBoundaryGuard(rightEdge);
|
|
10561
|
+
}
|
|
10562
|
+
function findQQBotRegexBoundary(text, limit, pattern) {
|
|
10563
|
+
const scopedText = text.slice(0, Math.min(limit + 1, text.length));
|
|
10564
|
+
const regex = new RegExp(pattern.source, pattern.flags);
|
|
10565
|
+
let match = regex.exec(scopedText);
|
|
10566
|
+
let lastBoundary;
|
|
10567
|
+
while (match) {
|
|
10568
|
+
const boundary = match.index + match[0].length;
|
|
10569
|
+
if (boundary > 0 && boundary <= limit && isQQBotSafeMarkdownBoundary(text, boundary)) {
|
|
10570
|
+
lastBoundary = boundary;
|
|
10571
|
+
}
|
|
10572
|
+
match = regex.exec(scopedText);
|
|
10573
|
+
}
|
|
10574
|
+
return lastBoundary;
|
|
10575
|
+
}
|
|
10576
|
+
function findQQBotFallbackBoundary(text, limit) {
|
|
10577
|
+
const minIndex = Math.max(1, limit - 120);
|
|
10578
|
+
for (let index = limit; index >= minIndex; index -= 1) {
|
|
10579
|
+
if (isQQBotSafeMarkdownBoundary(text, index)) {
|
|
10580
|
+
return index;
|
|
10581
|
+
}
|
|
10582
|
+
}
|
|
10583
|
+
return limit;
|
|
10584
|
+
}
|
|
10585
|
+
function findQQBotSafeSplitIndex(text, limit) {
|
|
10586
|
+
const boundaryPatterns = [
|
|
10587
|
+
/\n\n+/g,
|
|
10588
|
+
/\n/g,
|
|
10589
|
+
/[。!?.!?;;::](?:\s+|$)/g,
|
|
10590
|
+
/[,,](?:\s+|$)/g,
|
|
10591
|
+
/\s+/g
|
|
10592
|
+
];
|
|
10593
|
+
for (const pattern of boundaryPatterns) {
|
|
10594
|
+
const boundary = findQQBotRegexBoundary(text, limit, pattern);
|
|
10595
|
+
if (boundary && boundary > 0) {
|
|
10596
|
+
return boundary;
|
|
10597
|
+
}
|
|
10598
|
+
}
|
|
10599
|
+
return findQQBotFallbackBoundary(text, limit);
|
|
10600
|
+
}
|
|
10601
|
+
function splitQQBotHardText(text, limit) {
|
|
10602
|
+
if (limit <= 0 || text.length <= limit) {
|
|
10603
|
+
return [text];
|
|
10604
|
+
}
|
|
10605
|
+
const chunks = [];
|
|
10606
|
+
let remaining = text;
|
|
10607
|
+
while (remaining.length > limit) {
|
|
10608
|
+
chunks.push(remaining.slice(0, limit));
|
|
10609
|
+
remaining = remaining.slice(limit);
|
|
10610
|
+
}
|
|
10611
|
+
if (remaining) {
|
|
10612
|
+
chunks.push(remaining);
|
|
10613
|
+
}
|
|
10614
|
+
return chunks;
|
|
10615
|
+
}
|
|
10616
|
+
function splitQQBotTextSafely(text, limit, options) {
|
|
10617
|
+
if (limit <= 0 || text.length <= limit) {
|
|
10618
|
+
return [text];
|
|
10619
|
+
}
|
|
10620
|
+
const trimLeading = options?.trimLeading ?? true;
|
|
10621
|
+
const trimTrailing = options?.trimTrailing ?? true;
|
|
10622
|
+
const chunks = [];
|
|
10623
|
+
let remaining = text;
|
|
10624
|
+
while (remaining.length > limit) {
|
|
10625
|
+
const splitIndex = findQQBotSafeSplitIndex(remaining, limit);
|
|
10626
|
+
let nextChunk = remaining.slice(0, splitIndex);
|
|
10627
|
+
let nextRemaining = remaining.slice(splitIndex);
|
|
10628
|
+
if (trimTrailing) {
|
|
10629
|
+
nextChunk = nextChunk.trimEnd();
|
|
10630
|
+
}
|
|
10631
|
+
if (trimLeading) {
|
|
10632
|
+
nextRemaining = nextRemaining.trimStart();
|
|
10633
|
+
}
|
|
10634
|
+
if (!nextChunk) {
|
|
10635
|
+
const hardChunk = remaining.slice(0, limit);
|
|
10636
|
+
chunks.push(hardChunk);
|
|
10637
|
+
remaining = remaining.slice(hardChunk.length);
|
|
10638
|
+
continue;
|
|
10639
|
+
}
|
|
10640
|
+
chunks.push(nextChunk);
|
|
10641
|
+
remaining = nextRemaining;
|
|
10642
|
+
}
|
|
10643
|
+
const finalChunk = trimTrailing ? remaining.trimEnd() : remaining;
|
|
10644
|
+
if (finalChunk) {
|
|
10645
|
+
chunks.push(finalChunk);
|
|
10646
|
+
}
|
|
10647
|
+
return chunks;
|
|
10648
|
+
}
|
|
10649
|
+
function splitQQBotMarkdownLineBlock(text, limit) {
|
|
10650
|
+
if (limit <= 0 || text.length <= limit) {
|
|
10651
|
+
return [text];
|
|
10652
|
+
}
|
|
10653
|
+
const lines = text.split("\n");
|
|
10654
|
+
const chunks = [];
|
|
10655
|
+
let currentLines = [];
|
|
10656
|
+
const flushCurrent = () => {
|
|
10657
|
+
if (currentLines.length === 0) {
|
|
10658
|
+
return;
|
|
10659
|
+
}
|
|
10660
|
+
const chunk = currentLines.join("\n").trimEnd();
|
|
10661
|
+
if (chunk) {
|
|
10662
|
+
chunks.push(chunk);
|
|
10663
|
+
}
|
|
10664
|
+
currentLines = [];
|
|
10665
|
+
};
|
|
10666
|
+
for (const line of lines) {
|
|
10667
|
+
const candidate = currentLines.length > 0 ? `${currentLines.join("\n")}
|
|
10668
|
+
${line}` : line;
|
|
10669
|
+
if (candidate.length <= limit) {
|
|
10670
|
+
currentLines.push(line);
|
|
10671
|
+
continue;
|
|
10672
|
+
}
|
|
10673
|
+
flushCurrent();
|
|
10674
|
+
if (line.length <= limit) {
|
|
10675
|
+
currentLines.push(line);
|
|
10676
|
+
continue;
|
|
10677
|
+
}
|
|
10678
|
+
for (const piece of splitQQBotTextSafely(line, limit, {
|
|
10679
|
+
trimLeading: false,
|
|
10680
|
+
trimTrailing: false
|
|
10681
|
+
})) {
|
|
10682
|
+
if (piece) {
|
|
10683
|
+
chunks.push(piece);
|
|
10684
|
+
}
|
|
10685
|
+
}
|
|
10686
|
+
}
|
|
10687
|
+
flushCurrent();
|
|
10688
|
+
return chunks;
|
|
10689
|
+
}
|
|
10690
|
+
function splitQQBotMarkdownTableBlock(text, limit) {
|
|
10691
|
+
if (limit <= 0 || text.length <= limit) {
|
|
10692
|
+
return [text];
|
|
10693
|
+
}
|
|
10694
|
+
const lines = text.split("\n");
|
|
10695
|
+
const header = lines[0] ?? "";
|
|
10696
|
+
const separator = lines[1] ?? "";
|
|
10697
|
+
const rows = lines.slice(2);
|
|
10698
|
+
const tablePrefix = `${header}
|
|
10699
|
+
${separator}`;
|
|
10700
|
+
const chunks = [];
|
|
10701
|
+
let currentRows = [];
|
|
10702
|
+
const flushCurrent = () => {
|
|
10703
|
+
if (currentRows.length === 0) {
|
|
10704
|
+
return;
|
|
10705
|
+
}
|
|
10706
|
+
chunks.push(`${tablePrefix}
|
|
10707
|
+
${currentRows.join("\n")}`);
|
|
10708
|
+
currentRows = [];
|
|
10709
|
+
};
|
|
10710
|
+
for (const row of rows) {
|
|
10711
|
+
const candidate = currentRows.length > 0 ? `${tablePrefix}
|
|
10712
|
+
${currentRows.join("\n")}
|
|
10713
|
+
${row}` : `${tablePrefix}
|
|
10714
|
+
${row}`;
|
|
10715
|
+
if (candidate.length <= limit) {
|
|
10716
|
+
currentRows.push(row);
|
|
10717
|
+
continue;
|
|
10718
|
+
}
|
|
10719
|
+
flushCurrent();
|
|
10720
|
+
if (`${tablePrefix}
|
|
10721
|
+
${row}`.length <= limit) {
|
|
10722
|
+
currentRows.push(row);
|
|
10723
|
+
continue;
|
|
10724
|
+
}
|
|
10725
|
+
const maxRowLength = Math.max(16, limit - tablePrefix.length - 1);
|
|
10726
|
+
for (const rowPiece of splitQQBotTextSafely(row, maxRowLength, {
|
|
10727
|
+
trimLeading: false,
|
|
10728
|
+
trimTrailing: false
|
|
10729
|
+
})) {
|
|
10730
|
+
chunks.push(`${tablePrefix}
|
|
10731
|
+
${rowPiece}`);
|
|
10732
|
+
}
|
|
10733
|
+
}
|
|
10734
|
+
flushCurrent();
|
|
10735
|
+
return chunks.length > 0 ? chunks : [text];
|
|
10736
|
+
}
|
|
10737
|
+
function splitQQBotMarkdownCodeFence(text, limit) {
|
|
10738
|
+
if (limit <= 0 || text.length <= limit) {
|
|
10739
|
+
return [text];
|
|
10740
|
+
}
|
|
10741
|
+
const lines = text.split("\n");
|
|
10742
|
+
const openingLine = lines[0] ?? "```";
|
|
10743
|
+
const delimiter = resolveQQBotFenceDelimiter(openingLine) ?? "```";
|
|
10744
|
+
const hasClosingFence = lines.length > 1 && isQQBotFenceClosingLine(lines[lines.length - 1] ?? "", delimiter);
|
|
10745
|
+
const closingLine = hasClosingFence ? lines[lines.length - 1] ?? delimiter : delimiter;
|
|
10746
|
+
const codeLines = lines.slice(1, hasClosingFence ? -1 : lines.length);
|
|
10747
|
+
const fixedOverhead = openingLine.length + closingLine.length + 2;
|
|
10748
|
+
const availableLineLength = Math.max(1, limit - fixedOverhead);
|
|
10749
|
+
const chunks = [];
|
|
10750
|
+
let currentCodeLines = [];
|
|
10751
|
+
const flushCurrent = () => {
|
|
10752
|
+
if (currentCodeLines.length === 0) {
|
|
10753
|
+
return;
|
|
10754
|
+
}
|
|
10755
|
+
chunks.push(`${openingLine}
|
|
10756
|
+
${currentCodeLines.join("\n")}
|
|
10757
|
+
${closingLine}`);
|
|
10758
|
+
currentCodeLines = [];
|
|
10759
|
+
};
|
|
10760
|
+
for (const codeLine of codeLines) {
|
|
10761
|
+
const candidate = currentCodeLines.length > 0 ? `${openingLine}
|
|
10762
|
+
${currentCodeLines.join("\n")}
|
|
10763
|
+
${codeLine}
|
|
10764
|
+
${closingLine}` : `${openingLine}
|
|
10765
|
+
${codeLine}
|
|
10766
|
+
${closingLine}`;
|
|
10767
|
+
if (candidate.length <= limit) {
|
|
10768
|
+
currentCodeLines.push(codeLine);
|
|
10769
|
+
continue;
|
|
10770
|
+
}
|
|
10771
|
+
flushCurrent();
|
|
10772
|
+
if (`${openingLine}
|
|
10773
|
+
${codeLine}
|
|
10774
|
+
${closingLine}`.length <= limit) {
|
|
10775
|
+
currentCodeLines.push(codeLine);
|
|
10776
|
+
continue;
|
|
10777
|
+
}
|
|
10778
|
+
for (const linePiece of splitQQBotHardText(codeLine, availableLineLength)) {
|
|
10779
|
+
chunks.push(`${openingLine}
|
|
10780
|
+
${linePiece}
|
|
10781
|
+
${closingLine}`);
|
|
10782
|
+
}
|
|
10783
|
+
}
|
|
10784
|
+
flushCurrent();
|
|
10785
|
+
return chunks.length > 0 ? chunks : [text];
|
|
10786
|
+
}
|
|
10787
|
+
function splitQQBotMarkdownBlock(block, limit) {
|
|
10788
|
+
if (limit <= 0 || block.text.length <= limit) {
|
|
10789
|
+
return [block.text];
|
|
10790
|
+
}
|
|
10791
|
+
switch (block.kind) {
|
|
10792
|
+
case "table":
|
|
10793
|
+
return splitQQBotMarkdownTableBlock(block.text, limit);
|
|
10794
|
+
case "code":
|
|
10795
|
+
return splitQQBotMarkdownCodeFence(block.text, limit);
|
|
10796
|
+
case "blockquote":
|
|
10797
|
+
return splitQQBotMarkdownLineBlock(block.text, limit);
|
|
10798
|
+
case "list":
|
|
10799
|
+
return splitQQBotMarkdownLineBlock(block.text, limit);
|
|
10800
|
+
case "paragraph":
|
|
10801
|
+
case "heading":
|
|
10802
|
+
return splitQQBotTextSafely(block.text, limit);
|
|
10803
|
+
case "thematic-break":
|
|
10804
|
+
return [block.text];
|
|
10805
|
+
default:
|
|
10806
|
+
return [block.text];
|
|
10807
|
+
}
|
|
10808
|
+
}
|
|
10809
|
+
function chunkQQBotStructuredMarkdown(text, limit) {
|
|
10810
|
+
const blocks = parseQQBotMarkdownBlocks(text);
|
|
10811
|
+
if (blocks.length === 0 || limit <= 0) {
|
|
10812
|
+
return [text.trim()];
|
|
10813
|
+
}
|
|
10814
|
+
const chunks = [];
|
|
10815
|
+
let currentPieces = [];
|
|
10816
|
+
let pendingPrefixPieces = [];
|
|
10817
|
+
const flushCurrent = () => {
|
|
10818
|
+
if (currentPieces.length === 0) {
|
|
10819
|
+
return;
|
|
10820
|
+
}
|
|
10821
|
+
const chunk = joinQQBotMarkdownPieces(currentPieces);
|
|
10822
|
+
if (chunk) {
|
|
10823
|
+
chunks.push(chunk);
|
|
10824
|
+
}
|
|
10825
|
+
currentPieces = [];
|
|
10826
|
+
};
|
|
10827
|
+
const appendPiece = (piece) => {
|
|
10828
|
+
if (!piece) {
|
|
10829
|
+
return;
|
|
10830
|
+
}
|
|
10831
|
+
const pieces = piece.length > limit ? splitQQBotTextSafely(piece, limit) : [piece];
|
|
10832
|
+
for (const nextPiece of pieces) {
|
|
10833
|
+
const normalizedPiece = nextPiece.trim();
|
|
10834
|
+
if (!normalizedPiece) {
|
|
10835
|
+
continue;
|
|
10836
|
+
}
|
|
10837
|
+
const candidate = joinQQBotMarkdownPieces([...currentPieces, normalizedPiece]);
|
|
10838
|
+
if (currentPieces.length === 0 || candidate.length <= limit) {
|
|
10839
|
+
currentPieces.push(normalizedPiece);
|
|
10840
|
+
continue;
|
|
10841
|
+
}
|
|
10842
|
+
flushCurrent();
|
|
10843
|
+
currentPieces.push(normalizedPiece);
|
|
9458
10844
|
}
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
|
|
10845
|
+
};
|
|
10846
|
+
const consumePendingPrefix = (piece) => {
|
|
10847
|
+
if (pendingPrefixPieces.length === 0) {
|
|
10848
|
+
return piece;
|
|
10849
|
+
}
|
|
10850
|
+
const prefixed = joinQQBotMarkdownPieces([...pendingPrefixPieces, piece]);
|
|
10851
|
+
pendingPrefixPieces = [];
|
|
10852
|
+
return prefixed;
|
|
10853
|
+
};
|
|
10854
|
+
for (let index = 0; index < blocks.length; index += 1) {
|
|
10855
|
+
const block = blocks[index];
|
|
10856
|
+
if (!block) {
|
|
10857
|
+
continue;
|
|
10858
|
+
}
|
|
10859
|
+
if (block.kind === "thematic-break") {
|
|
10860
|
+
if (currentPieces.length > 0) {
|
|
10861
|
+
const candidate = joinQQBotMarkdownPieces([...currentPieces, block.text]);
|
|
10862
|
+
if (candidate.length <= limit) {
|
|
10863
|
+
currentPieces.push(block.text);
|
|
10864
|
+
continue;
|
|
10865
|
+
}
|
|
10866
|
+
flushCurrent();
|
|
9466
10867
|
}
|
|
9467
|
-
|
|
9468
|
-
|
|
10868
|
+
pendingPrefixPieces.push(block.text);
|
|
10869
|
+
continue;
|
|
10870
|
+
}
|
|
10871
|
+
if (block.kind === "heading") {
|
|
10872
|
+
const headingText = consumePendingPrefix(block.text);
|
|
10873
|
+
const nextBlock = blocks[index + 1];
|
|
10874
|
+
if (nextBlock && nextBlock.kind !== "thematic-break") {
|
|
10875
|
+
const nextPieces = splitQQBotMarkdownBlock(nextBlock, limit);
|
|
10876
|
+
const firstBodyPiece = nextPieces[0];
|
|
10877
|
+
if (firstBodyPiece) {
|
|
10878
|
+
const pairedText = joinQQBotMarkdownPieces([headingText, firstBodyPiece]);
|
|
10879
|
+
const pairedCandidate = joinQQBotMarkdownPieces([
|
|
10880
|
+
...currentPieces,
|
|
10881
|
+
headingText,
|
|
10882
|
+
firstBodyPiece
|
|
10883
|
+
]);
|
|
10884
|
+
if (pairedText.length <= limit && (currentPieces.length === 0 || pairedCandidate.length <= limit)) {
|
|
10885
|
+
currentPieces.push(headingText, firstBodyPiece);
|
|
10886
|
+
for (let pieceIndex = 1; pieceIndex < nextPieces.length; pieceIndex += 1) {
|
|
10887
|
+
appendPiece(nextPieces[pieceIndex] ?? "");
|
|
10888
|
+
}
|
|
10889
|
+
index += 1;
|
|
10890
|
+
continue;
|
|
10891
|
+
}
|
|
10892
|
+
if (currentPieces.length > 0 && pairedText.length <= limit) {
|
|
10893
|
+
flushCurrent();
|
|
10894
|
+
currentPieces.push(headingText, firstBodyPiece);
|
|
10895
|
+
for (let pieceIndex = 1; pieceIndex < nextPieces.length; pieceIndex += 1) {
|
|
10896
|
+
appendPiece(nextPieces[pieceIndex] ?? "");
|
|
10897
|
+
}
|
|
10898
|
+
index += 1;
|
|
10899
|
+
continue;
|
|
10900
|
+
}
|
|
10901
|
+
}
|
|
9469
10902
|
}
|
|
9470
|
-
|
|
9471
|
-
|
|
10903
|
+
appendPiece(headingText);
|
|
10904
|
+
continue;
|
|
9472
10905
|
}
|
|
9473
|
-
|
|
9474
|
-
|
|
10906
|
+
const blockText = consumePendingPrefix(block.text);
|
|
10907
|
+
for (const piece of splitQQBotMarkdownBlock({ ...block, text: blockText }, limit)) {
|
|
10908
|
+
appendPiece(piece);
|
|
10909
|
+
}
|
|
10910
|
+
}
|
|
10911
|
+
if (pendingPrefixPieces.length > 0 && currentPieces.length > 0) {
|
|
10912
|
+
const trailingCandidate = joinQQBotMarkdownPieces([...currentPieces, ...pendingPrefixPieces]);
|
|
10913
|
+
if (trailingCandidate.length <= limit) {
|
|
10914
|
+
currentPieces.push(...pendingPrefixPieces);
|
|
10915
|
+
}
|
|
10916
|
+
}
|
|
10917
|
+
flushCurrent();
|
|
10918
|
+
return chunks.length > 0 ? chunks : [text.trim()];
|
|
10919
|
+
}
|
|
10920
|
+
function looksLikeStructuredMarkdown(text) {
|
|
10921
|
+
const normalized = normalizeQQBotMarkdownSegment(text);
|
|
10922
|
+
if (!normalized) {
|
|
10923
|
+
return false;
|
|
10924
|
+
}
|
|
10925
|
+
const lines = normalized.split("\n");
|
|
10926
|
+
if (hasQQBotMarkdownTable(normalized)) {
|
|
10927
|
+
return true;
|
|
10928
|
+
}
|
|
10929
|
+
return normalized.includes("\n\n") || lines.some((line) => MARKDOWN_ATX_HEADING_RE.test(line)) || lines.some((line) => MARKDOWN_BLOCKQUOTE_RE.test(line)) || lines.some((line) => MARKDOWN_FENCE_RE.test(line)) || lines.some((line) => MARKDOWN_THEMATIC_BREAK_RE.test(line)) || lines.some((line) => MARKDOWN_LIST_ITEM_RE.test(line)) || MARKDOWN_INLINE_STRUCTURE_RE.test(normalized);
|
|
10930
|
+
}
|
|
10931
|
+
function chunkC2CMarkdownText(params) {
|
|
10932
|
+
const normalized = params.text.trim();
|
|
10933
|
+
if (!normalized) {
|
|
10934
|
+
return [];
|
|
10935
|
+
}
|
|
10936
|
+
const strategy = params.strategy ?? "markdown-block";
|
|
10937
|
+
if (strategy === "length") {
|
|
10938
|
+
return params.fallbackChunkText ? params.fallbackChunkText(normalized) : [normalized];
|
|
10939
|
+
}
|
|
10940
|
+
if (params.limit <= 0 || !looksLikeStructuredMarkdown(normalized)) {
|
|
10941
|
+
return params.fallbackChunkText ? params.fallbackChunkText(normalized) : [normalized];
|
|
10942
|
+
}
|
|
10943
|
+
return chunkQQBotStructuredMarkdown(normalized, params.limit);
|
|
9475
10944
|
}
|
|
9476
10945
|
async function sendQQBotMediaWithFallback(params) {
|
|
9477
|
-
const { qqCfg, to, mediaQueue, replyToId, replyEventId, logger, onDelivered, onError } = params;
|
|
10946
|
+
const { qqCfg, to, mediaQueue, replyToId, replyEventId, accountId, logger, onDelivered, onError } = params;
|
|
9478
10947
|
const outbound = params.outbound ?? qqbotOutbound;
|
|
10948
|
+
const shouldContinue = params.shouldContinue ?? (() => true);
|
|
9479
10949
|
for (const mediaUrl of mediaQueue) {
|
|
10950
|
+
if (!shouldContinue()) {
|
|
10951
|
+
return;
|
|
10952
|
+
}
|
|
9480
10953
|
const result = await outbound.sendMedia({
|
|
9481
10954
|
cfg: { channels: { qqbot: qqCfg } },
|
|
9482
10955
|
to,
|
|
9483
10956
|
mediaUrl,
|
|
9484
10957
|
replyToId,
|
|
9485
|
-
replyEventId
|
|
10958
|
+
replyEventId,
|
|
10959
|
+
accountId
|
|
9486
10960
|
});
|
|
9487
10961
|
if (result.error) {
|
|
9488
10962
|
logger.error(`sendMedia failed: ${result.error}`);
|
|
@@ -9491,12 +10965,16 @@ async function sendQQBotMediaWithFallback(params) {
|
|
|
9491
10965
|
if (!fallback) {
|
|
9492
10966
|
continue;
|
|
9493
10967
|
}
|
|
10968
|
+
if (!shouldContinue()) {
|
|
10969
|
+
return;
|
|
10970
|
+
}
|
|
9494
10971
|
const fallbackResult = await outbound.sendText({
|
|
9495
10972
|
cfg: { channels: { qqbot: qqCfg } },
|
|
9496
10973
|
to,
|
|
9497
10974
|
text: fallback,
|
|
9498
10975
|
replyToId,
|
|
9499
|
-
replyEventId
|
|
10976
|
+
replyEventId,
|
|
10977
|
+
accountId
|
|
9500
10978
|
});
|
|
9501
10979
|
if (fallbackResult.error) {
|
|
9502
10980
|
logger.error(`sendText fallback failed: ${fallbackResult.error}`);
|
|
@@ -9541,17 +11019,30 @@ function buildInboundContext(params) {
|
|
|
9541
11019
|
async function dispatchToAgent(params) {
|
|
9542
11020
|
const { inbound, cfg, qqCfg, accountId, logger, route } = params;
|
|
9543
11021
|
const runtime2 = getQQBotRuntime();
|
|
11022
|
+
const routeSessionKey = resolveQQBotRouteSessionKey(route);
|
|
11023
|
+
const queueKey = buildSessionDispatchQueueKey(route);
|
|
11024
|
+
const isFastAbortCommand = isQQBotFastAbortCommandText(inbound.content);
|
|
11025
|
+
const dispatchAbortGeneration = getSessionDispatchState(queueKey).abortGeneration;
|
|
11026
|
+
const shouldSuppressVisibleReplies = () => {
|
|
11027
|
+
const currentAbortGeneration = sessionDispatchQueue.get(queueKey)?.abortGeneration ?? dispatchAbortGeneration;
|
|
11028
|
+
return currentAbortGeneration !== dispatchAbortGeneration;
|
|
11029
|
+
};
|
|
9544
11030
|
const target = resolveChatTarget(inbound);
|
|
9545
|
-
|
|
11031
|
+
const outboundAccountId = route.accountId ?? accountId;
|
|
11032
|
+
let typingRefIdx;
|
|
11033
|
+
if (inbound.c2cOpenid && !isFastAbortCommand && !shouldSuppressVisibleReplies()) {
|
|
9546
11034
|
const typing = await qqbotOutbound.sendTyping({
|
|
9547
11035
|
cfg: { channels: { qqbot: qqCfg } },
|
|
9548
11036
|
to: `user:${inbound.c2cOpenid}`,
|
|
9549
11037
|
replyToId: inbound.messageId,
|
|
9550
11038
|
replyEventId: inbound.eventId,
|
|
9551
|
-
inputSecond: 60
|
|
11039
|
+
inputSecond: 60,
|
|
11040
|
+
accountId: outboundAccountId
|
|
9552
11041
|
});
|
|
9553
11042
|
if (typing.error) {
|
|
9554
11043
|
logger.warn(`sendTyping failed: ${typing.error}`);
|
|
11044
|
+
} else {
|
|
11045
|
+
typingRefIdx = typing.refIdx;
|
|
9555
11046
|
}
|
|
9556
11047
|
}
|
|
9557
11048
|
const replyApi = runtime2.channel?.reply;
|
|
@@ -9576,13 +11067,14 @@ async function dispatchToAgent(params) {
|
|
|
9576
11067
|
delayMs: qqCfg.longTaskNoticeDelayMs ?? DEFAULT_LONG_TASK_NOTICE_DELAY_MS,
|
|
9577
11068
|
logger,
|
|
9578
11069
|
sendNotice: async () => {
|
|
9579
|
-
if (groupMessageInterfaceBlocked) return;
|
|
11070
|
+
if (groupMessageInterfaceBlocked || isFastAbortCommand || shouldSuppressVisibleReplies()) return;
|
|
9580
11071
|
const result = await qqbotOutbound.sendText({
|
|
9581
11072
|
cfg: { channels: { qqbot: qqCfg } },
|
|
9582
11073
|
to: target.to,
|
|
9583
11074
|
text: LONG_TASK_NOTICE_TEXT,
|
|
9584
11075
|
replyToId: inbound.messageId,
|
|
9585
|
-
replyEventId: inbound.eventId
|
|
11076
|
+
replyEventId: inbound.eventId,
|
|
11077
|
+
accountId: outboundAccountId
|
|
9586
11078
|
});
|
|
9587
11079
|
if (result.error) {
|
|
9588
11080
|
logger.warn(`send long-task notice failed: ${result.error}`);
|
|
@@ -9602,19 +11094,23 @@ async function dispatchToAgent(params) {
|
|
|
9602
11094
|
{ agentId: route.agentId }
|
|
9603
11095
|
);
|
|
9604
11096
|
const envelopeOptions = replyApi.resolveEnvelopeFormatOptions?.(cfg);
|
|
9605
|
-
const previousTimestamp = storePath && sessionApi?.readSessionUpdatedAt ? sessionApi.readSessionUpdatedAt({ storePath, sessionKey:
|
|
11097
|
+
const previousTimestamp = storePath && sessionApi?.readSessionUpdatedAt ? sessionApi.readSessionUpdatedAt({ storePath, sessionKey: routeSessionKey }) : null;
|
|
9606
11098
|
const resolvedAttachmentResult = await resolveInboundAttachmentsForAgent({
|
|
9607
11099
|
attachments: inbound.attachments,
|
|
9608
11100
|
qqCfg,
|
|
9609
11101
|
logger
|
|
9610
11102
|
});
|
|
9611
11103
|
if (qqCfg.asr?.enabled && resolvedAttachmentResult.hasVoiceAttachment && !resolvedAttachmentResult.hasVoiceTranscript) {
|
|
11104
|
+
if (shouldSuppressVisibleReplies()) {
|
|
11105
|
+
return;
|
|
11106
|
+
}
|
|
9612
11107
|
const fallback = await qqbotOutbound.sendText({
|
|
9613
11108
|
cfg: { channels: { qqbot: qqCfg } },
|
|
9614
11109
|
to: target.to,
|
|
9615
11110
|
text: buildVoiceASRFallbackReply(resolvedAttachmentResult.asrErrorMessage),
|
|
9616
11111
|
replyToId: inbound.messageId,
|
|
9617
|
-
replyEventId: inbound.eventId
|
|
11112
|
+
replyEventId: inbound.eventId,
|
|
11113
|
+
accountId: outboundAccountId
|
|
9618
11114
|
});
|
|
9619
11115
|
if (fallback.error) {
|
|
9620
11116
|
logger.error(`sendText ASR fallback failed: ${fallback.error}`);
|
|
@@ -9629,6 +11125,39 @@ async function dispatchToAgent(params) {
|
|
|
9629
11125
|
if (localImageCount > 0) {
|
|
9630
11126
|
logger.info(`prepared ${localImageCount} local image attachment(s) for agent`);
|
|
9631
11127
|
}
|
|
11128
|
+
let replyToId;
|
|
11129
|
+
let replyToBody;
|
|
11130
|
+
let replyToSender;
|
|
11131
|
+
let replyToIsQuote = false;
|
|
11132
|
+
if (inbound.c2cOpenid && inbound.refMsgIdx) {
|
|
11133
|
+
replyToId = inbound.refMsgIdx;
|
|
11134
|
+
replyToIsQuote = true;
|
|
11135
|
+
const refEntry = getRefIndex(inbound.refMsgIdx);
|
|
11136
|
+
if (refEntry) {
|
|
11137
|
+
replyToBody = formatRefEntryForAgent(refEntry);
|
|
11138
|
+
replyToSender = refEntry.senderName ?? refEntry.senderId;
|
|
11139
|
+
logger.info(`quote context resolved refMsgIdx=${inbound.refMsgIdx}`);
|
|
11140
|
+
} else {
|
|
11141
|
+
replyToBody = QQ_QUOTE_BODY_UNAVAILABLE_TEXT;
|
|
11142
|
+
logger.warn(`quote context missing refMsgIdx=${inbound.refMsgIdx}`);
|
|
11143
|
+
}
|
|
11144
|
+
}
|
|
11145
|
+
const refAttachmentSummaries = buildInboundRefAttachmentSummaries(resolvedAttachments);
|
|
11146
|
+
const currentRefIndexKeys = inbound.c2cOpenid ? uniqueRefIndexKeys(inbound.msgIdx, typingRefIdx) : [];
|
|
11147
|
+
if (currentRefIndexKeys.length > 0) {
|
|
11148
|
+
for (const currentRefIndexKey of currentRefIndexKeys) {
|
|
11149
|
+
setRefIndex(currentRefIndexKey, {
|
|
11150
|
+
content: inbound.content,
|
|
11151
|
+
senderId: inbound.senderId,
|
|
11152
|
+
...inbound.senderName ? { senderName: inbound.senderName } : {},
|
|
11153
|
+
timestamp: inbound.timestamp,
|
|
11154
|
+
...refAttachmentSummaries ? { attachments: refAttachmentSummaries } : {}
|
|
11155
|
+
});
|
|
11156
|
+
}
|
|
11157
|
+
logger.info(
|
|
11158
|
+
`cached inbound ref_idx keys=${currentRefIndexKeys.join(",")} msgIdx=${inbound.msgIdx ?? "-"} typingRefIdx=${typingRefIdx ?? "-"}`
|
|
11159
|
+
);
|
|
11160
|
+
}
|
|
9632
11161
|
const rawBody = buildInboundContentWithAttachments({
|
|
9633
11162
|
content: inbound.content,
|
|
9634
11163
|
attachments: resolvedAttachments
|
|
@@ -9654,51 +11183,73 @@ async function dispatchToAgent(params) {
|
|
|
9654
11183
|
}) : rawBody;
|
|
9655
11184
|
const inboundCtx = buildInboundContext({
|
|
9656
11185
|
event: inbound,
|
|
9657
|
-
sessionKey:
|
|
9658
|
-
accountId:
|
|
11186
|
+
sessionKey: routeSessionKey,
|
|
11187
|
+
accountId: outboundAccountId,
|
|
9659
11188
|
body: inboundBody,
|
|
9660
11189
|
rawBody,
|
|
9661
11190
|
commandBody: rawBody
|
|
9662
11191
|
});
|
|
9663
11192
|
const finalizeInboundContext = replyApi?.finalizeInboundContext;
|
|
9664
11193
|
const finalCtx = finalizeInboundContext ? finalizeInboundContext(inboundCtx) : inboundCtx;
|
|
9665
|
-
|
|
9666
|
-
|
|
9667
|
-
|
|
9668
|
-
|
|
9669
|
-
|
|
9670
|
-
|
|
9671
|
-
|
|
9672
|
-
|
|
9673
|
-
|
|
9674
|
-
|
|
9675
|
-
|
|
9676
|
-
|
|
9677
|
-
|
|
9678
|
-
|
|
9679
|
-
|
|
9680
|
-
|
|
9681
|
-
|
|
9682
|
-
|
|
9683
|
-
const isGroup = inbound.type === "group" || inbound.type === "channel";
|
|
9684
|
-
const updateLastRoute = !isGroup ? {
|
|
9685
|
-
sessionKey: mainSessionKey ?? route.sessionKey,
|
|
9686
|
-
channel: "qqbot",
|
|
9687
|
-
to: finalCtx.OriginatingTo ?? finalCtx.To ?? `user:${inbound.senderId}`,
|
|
9688
|
-
accountId: route.accountId ?? accountId
|
|
9689
|
-
} : void 0;
|
|
9690
|
-
const recordSessionKey = typeof finalCtx.SessionKey === "string" && finalCtx.SessionKey.trim() ? finalCtx.SessionKey : route.sessionKey;
|
|
9691
|
-
await sessionApi.recordInboundSession({
|
|
9692
|
-
storePath,
|
|
9693
|
-
sessionKey: recordSessionKey,
|
|
9694
|
-
ctx: finalCtx,
|
|
9695
|
-
updateLastRoute,
|
|
9696
|
-
onRecordError: (err) => {
|
|
9697
|
-
logger.warn(`failed to record inbound session: ${String(err)}`);
|
|
9698
|
-
}
|
|
11194
|
+
const ctxTo = normalizeQQBotReplyTarget(finalCtx.To);
|
|
11195
|
+
const ctxOriginatingTo = normalizeQQBotReplyTarget(finalCtx.OriginatingTo);
|
|
11196
|
+
const stableTo = ctxOriginatingTo ?? ctxTo ?? target.to;
|
|
11197
|
+
finalCtx.To = stableTo;
|
|
11198
|
+
finalCtx.OriginatingTo = stableTo;
|
|
11199
|
+
if (replyToId) {
|
|
11200
|
+
finalCtx.ReplyToId = replyToId;
|
|
11201
|
+
finalCtx.ReplyToBody = replyToBody;
|
|
11202
|
+
finalCtx.ReplyToSender = replyToSender;
|
|
11203
|
+
finalCtx.ReplyToIsQuote = replyToIsQuote;
|
|
11204
|
+
}
|
|
11205
|
+
const isSlashCommand = typeof finalCtx.CommandBody === "string" ? finalCtx.CommandBody.trim().startsWith("/") : typeof finalCtx.RawBody === "string" ? finalCtx.RawBody.trim().startsWith("/") : false;
|
|
11206
|
+
if (!isSlashCommand) {
|
|
11207
|
+
let agentBody = resolveAgentBodyBase(finalCtx);
|
|
11208
|
+
if (replyToIsQuote && replyToBody && replyToBody !== QQ_QUOTE_BODY_UNAVAILABLE_TEXT) {
|
|
11209
|
+
agentBody = buildQuotedAgentBody({
|
|
11210
|
+
baseBody: agentBody,
|
|
11211
|
+
replyToBody
|
|
9699
11212
|
});
|
|
9700
|
-
}
|
|
9701
|
-
|
|
11213
|
+
}
|
|
11214
|
+
finalCtx.BodyForAgent = appendCronHiddenPrompt(agentBody);
|
|
11215
|
+
}
|
|
11216
|
+
if (storePath) {
|
|
11217
|
+
const mainSessionKeyRaw = route.mainSessionKey;
|
|
11218
|
+
const mainSessionKey = typeof mainSessionKeyRaw === "string" && mainSessionKeyRaw.trim() ? mainSessionKeyRaw.trim() : void 0;
|
|
11219
|
+
const isGroup = inbound.type === "group" || inbound.type === "channel";
|
|
11220
|
+
const updateLastRoute = !isGroup ? {
|
|
11221
|
+
sessionKey: mainSessionKey ?? route.sessionKey,
|
|
11222
|
+
channel: "qqbot",
|
|
11223
|
+
to: stableTo,
|
|
11224
|
+
accountId: outboundAccountId
|
|
11225
|
+
} : void 0;
|
|
11226
|
+
const recordSessionKey = typeof finalCtx.SessionKey === "string" && finalCtx.SessionKey.trim() ? finalCtx.SessionKey : routeSessionKey;
|
|
11227
|
+
if (sessionApi?.recordInboundSession) {
|
|
11228
|
+
try {
|
|
11229
|
+
await sessionApi.recordInboundSession({
|
|
11230
|
+
storePath,
|
|
11231
|
+
sessionKey: recordSessionKey,
|
|
11232
|
+
ctx: finalCtx,
|
|
11233
|
+
updateLastRoute,
|
|
11234
|
+
onRecordError: (err) => {
|
|
11235
|
+
logger.warn(`failed to record inbound session: ${String(err)}`);
|
|
11236
|
+
}
|
|
11237
|
+
});
|
|
11238
|
+
} catch (err) {
|
|
11239
|
+
logger.warn(`failed to record inbound session: ${String(err)}`);
|
|
11240
|
+
}
|
|
11241
|
+
}
|
|
11242
|
+
if (sessionApi?.recordSessionMetaFromInbound) {
|
|
11243
|
+
try {
|
|
11244
|
+
await sessionApi.recordSessionMetaFromInbound({
|
|
11245
|
+
storePath,
|
|
11246
|
+
sessionKey: recordSessionKey,
|
|
11247
|
+
ctx: finalCtx,
|
|
11248
|
+
createIfMissing: true
|
|
11249
|
+
});
|
|
11250
|
+
} catch (err) {
|
|
11251
|
+
logger.warn(`failed to record inbound session meta: ${String(err)}`);
|
|
11252
|
+
}
|
|
9702
11253
|
}
|
|
9703
11254
|
}
|
|
9704
11255
|
const textApi = runtime2.channel?.text;
|
|
@@ -9711,7 +11262,7 @@ async function dispatchToAgent(params) {
|
|
|
9711
11262
|
const tableMode = textApi?.resolveMarkdownTableMode?.({
|
|
9712
11263
|
cfg,
|
|
9713
11264
|
channel: "qqbot",
|
|
9714
|
-
accountId:
|
|
11265
|
+
accountId: outboundAccountId
|
|
9715
11266
|
});
|
|
9716
11267
|
const resolvedTableMode = tableMode ?? "bullets";
|
|
9717
11268
|
const chunkText = (text) => {
|
|
@@ -9726,90 +11277,124 @@ async function dispatchToAgent(params) {
|
|
|
9726
11277
|
const replyFinalOnly = qqCfg.replyFinalOnly ?? false;
|
|
9727
11278
|
const markdownSupport = qqCfg.markdownSupport ?? true;
|
|
9728
11279
|
const c2cMarkdownDeliveryMode = qqCfg.c2cMarkdownDeliveryMode ?? "proactive-table-only";
|
|
9729
|
-
const
|
|
11280
|
+
const c2cMarkdownChunkStrategy = qqCfg.c2cMarkdownChunkStrategy ?? "markdown-block";
|
|
11281
|
+
const isC2CTarget = isQQBotC2CTarget(target.to);
|
|
11282
|
+
const useC2CMarkdownTransport = markdownSupport && isC2CTarget;
|
|
9730
11283
|
let bufferedC2CMarkdownTexts = [];
|
|
9731
11284
|
let bufferedC2CMarkdownMediaUrls = [];
|
|
9732
11285
|
const bufferedC2CMarkdownMediaSeen = /* @__PURE__ */ new Set();
|
|
11286
|
+
const hasBufferedC2CMarkdownReply = () => bufferedC2CMarkdownTexts.length > 0 || bufferedC2CMarkdownMediaUrls.length > 0;
|
|
9733
11287
|
const bufferC2CMarkdownMedia = (url) => {
|
|
9734
11288
|
const next = url?.trim();
|
|
9735
11289
|
if (!next || bufferedC2CMarkdownMediaSeen.has(next)) return;
|
|
9736
11290
|
bufferedC2CMarkdownMediaSeen.add(next);
|
|
9737
11291
|
bufferedC2CMarkdownMediaUrls.push(next);
|
|
9738
11292
|
};
|
|
9739
|
-
const
|
|
9740
|
-
if (
|
|
9741
|
-
bufferedC2CMarkdownTexts = [];
|
|
9742
|
-
bufferedC2CMarkdownMediaUrls = [];
|
|
9743
|
-
bufferedC2CMarkdownMediaSeen.clear();
|
|
11293
|
+
const sendC2CMarkdownTransportPayload = async (params2) => {
|
|
11294
|
+
if (shouldSuppressVisibleReplies()) {
|
|
9744
11295
|
return;
|
|
9745
11296
|
}
|
|
9746
|
-
const
|
|
9747
|
-
const
|
|
9748
|
-
bufferedC2CMarkdownTexts = [];
|
|
9749
|
-
bufferedC2CMarkdownMediaUrls = [];
|
|
9750
|
-
bufferedC2CMarkdownMediaSeen.clear();
|
|
9751
|
-
const normalizedCombinedText = normalizeQQBotRenderedMarkdown(combinedText);
|
|
9752
|
-
const { markdownImageUrls, mediaQueue } = splitQQBotMarkdownTransportMediaUrls(combinedMediaUrls);
|
|
11297
|
+
const normalizedText = normalizeQQBotRenderedMarkdown(params2.text);
|
|
11298
|
+
const { markdownImageUrls, mediaQueue } = splitQQBotMarkdownTransportMediaUrls(params2.mediaUrls);
|
|
9753
11299
|
const finalMarkdownText = await normalizeQQBotMarkdownImages({
|
|
9754
|
-
text:
|
|
11300
|
+
text: normalizedText,
|
|
9755
11301
|
appendImageUrls: markdownImageUrls
|
|
9756
11302
|
});
|
|
11303
|
+
if (shouldSuppressVisibleReplies()) {
|
|
11304
|
+
return;
|
|
11305
|
+
}
|
|
9757
11306
|
const textReplyRefs = resolveQQBotTextReplyRefs({
|
|
9758
11307
|
to: target.to,
|
|
9759
|
-
text: finalMarkdownText ||
|
|
11308
|
+
text: finalMarkdownText || normalizedText,
|
|
9760
11309
|
markdownSupport,
|
|
9761
11310
|
c2cMarkdownDeliveryMode,
|
|
9762
11311
|
replyToId: inbound.messageId,
|
|
9763
11312
|
replyEventId: inbound.eventId
|
|
9764
11313
|
});
|
|
9765
|
-
const
|
|
11314
|
+
const textChunks = finalMarkdownText ? chunkC2CMarkdownText({
|
|
11315
|
+
text: finalMarkdownText,
|
|
11316
|
+
limit,
|
|
11317
|
+
strategy: c2cMarkdownChunkStrategy,
|
|
11318
|
+
fallbackChunkText: chunkText
|
|
11319
|
+
}) : [];
|
|
9766
11320
|
const deliveryLabel = textReplyRefs.forceProactive ? "c2c-markdown-proactive" : "c2c-markdown-passive";
|
|
9767
11321
|
logger.info(
|
|
9768
|
-
`delivery=${deliveryLabel} to=${target.to}
|
|
11322
|
+
`delivery=${deliveryLabel} to=${target.to} chunks=${textChunks.length} media=${mediaQueue.length} replyToId=${textReplyRefs.replyToId ? "yes" : "no"} replyEventId=${textReplyRefs.replyEventId ? "yes" : "no"} phase=${params2.phase} tableMode=${String(resolvedTableMode)} chunkMode=${String(chunkMode ?? "default")} chunkStrategy=${c2cMarkdownChunkStrategy}`
|
|
9769
11323
|
);
|
|
9770
|
-
|
|
9771
|
-
|
|
9772
|
-
|
|
9773
|
-
|
|
9774
|
-
|
|
9775
|
-
|
|
9776
|
-
|
|
9777
|
-
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
11324
|
+
if (!shouldSuppressVisibleReplies()) {
|
|
11325
|
+
await sendQQBotMediaWithFallback({
|
|
11326
|
+
qqCfg,
|
|
11327
|
+
to: target.to,
|
|
11328
|
+
mediaQueue,
|
|
11329
|
+
replyToId: textReplyRefs.replyToId,
|
|
11330
|
+
replyEventId: textReplyRefs.replyEventId,
|
|
11331
|
+
accountId: outboundAccountId,
|
|
11332
|
+
logger,
|
|
11333
|
+
onDelivered: () => {
|
|
11334
|
+
markReplyDelivered();
|
|
11335
|
+
},
|
|
11336
|
+
onError: (error) => {
|
|
11337
|
+
markGroupMessageInterfaceBlocked(error);
|
|
11338
|
+
},
|
|
11339
|
+
shouldContinue: () => !shouldSuppressVisibleReplies()
|
|
11340
|
+
});
|
|
11341
|
+
}
|
|
9784
11342
|
if (!finalMarkdownText) {
|
|
9785
11343
|
return;
|
|
9786
11344
|
}
|
|
9787
|
-
for (let
|
|
9788
|
-
|
|
9789
|
-
|
|
9790
|
-
|
|
9791
|
-
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
|
|
9797
|
-
|
|
9798
|
-
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
11345
|
+
for (let chunkIndex = 0; chunkIndex < textChunks.length; chunkIndex += 1) {
|
|
11346
|
+
if (shouldSuppressVisibleReplies()) {
|
|
11347
|
+
return;
|
|
11348
|
+
}
|
|
11349
|
+
const chunk = textChunks[chunkIndex] ?? "";
|
|
11350
|
+
logger.info(
|
|
11351
|
+
`delivery=${deliveryLabel} segment=1/1 chunk=${chunkIndex + 1}/${textChunks.length} phase=${params2.phase} preview=${formatQQBotOutboundPreview(chunk)}`
|
|
11352
|
+
);
|
|
11353
|
+
const result = await qqbotOutbound.sendText({
|
|
11354
|
+
cfg: { channels: { qqbot: qqCfg } },
|
|
11355
|
+
to: target.to,
|
|
11356
|
+
text: chunk,
|
|
11357
|
+
replyToId: textReplyRefs.replyToId,
|
|
11358
|
+
replyEventId: textReplyRefs.replyEventId,
|
|
11359
|
+
accountId: outboundAccountId
|
|
11360
|
+
});
|
|
11361
|
+
if (result.error) {
|
|
11362
|
+
logger.error(`send QQ markdown reply failed: ${result.error}`);
|
|
11363
|
+
markGroupMessageInterfaceBlocked(result.error);
|
|
11364
|
+
} else {
|
|
11365
|
+
logger.info(`sent QQ markdown reply (phase=${params2.phase}, len=${chunk.length})`);
|
|
11366
|
+
markReplyDelivered();
|
|
9809
11367
|
}
|
|
9810
11368
|
}
|
|
9811
11369
|
};
|
|
11370
|
+
const flushBufferedC2CMarkdownReply = async () => {
|
|
11371
|
+
if (!useC2CMarkdownTransport || bufferedC2CMarkdownTexts.length === 0 && bufferedC2CMarkdownMediaUrls.length === 0) {
|
|
11372
|
+
bufferedC2CMarkdownTexts = [];
|
|
11373
|
+
bufferedC2CMarkdownMediaUrls = [];
|
|
11374
|
+
bufferedC2CMarkdownMediaSeen.clear();
|
|
11375
|
+
return;
|
|
11376
|
+
}
|
|
11377
|
+
if (shouldSuppressVisibleReplies()) {
|
|
11378
|
+
bufferedC2CMarkdownTexts = [];
|
|
11379
|
+
bufferedC2CMarkdownMediaUrls = [];
|
|
11380
|
+
bufferedC2CMarkdownMediaSeen.clear();
|
|
11381
|
+
return;
|
|
11382
|
+
}
|
|
11383
|
+
const combinedText = bufferedC2CMarkdownTexts.join("\n\n").trim();
|
|
11384
|
+
const combinedMediaUrls = [...bufferedC2CMarkdownMediaUrls];
|
|
11385
|
+
bufferedC2CMarkdownTexts = [];
|
|
11386
|
+
bufferedC2CMarkdownMediaUrls = [];
|
|
11387
|
+
bufferedC2CMarkdownMediaSeen.clear();
|
|
11388
|
+
await sendC2CMarkdownTransportPayload({
|
|
11389
|
+
text: combinedText,
|
|
11390
|
+
mediaUrls: combinedMediaUrls,
|
|
11391
|
+
phase: "buffered"
|
|
11392
|
+
});
|
|
11393
|
+
};
|
|
9812
11394
|
const deliver = async (payload, info) => {
|
|
11395
|
+
if (shouldSuppressVisibleReplies()) {
|
|
11396
|
+
return;
|
|
11397
|
+
}
|
|
9813
11398
|
const typed = payload;
|
|
9814
11399
|
const extractedTextMedia = extractQQBotReplyMedia({
|
|
9815
11400
|
text: typed?.text ?? "",
|
|
@@ -9840,12 +11425,28 @@ async function dispatchToAgent(params) {
|
|
|
9840
11425
|
const suppressText = deliveryDecision.suppressText || suppressEchoText;
|
|
9841
11426
|
const textToSend = suppressText ? "" : cleanedText;
|
|
9842
11427
|
if (useC2CMarkdownTransport) {
|
|
9843
|
-
|
|
9844
|
-
|
|
11428
|
+
const shouldBufferFinalOnlyPayload = replyFinalOnly && (!info?.kind || info.kind === "final");
|
|
11429
|
+
const shouldBufferStructuredMarkdownPayload = !replyFinalOnly && c2cMarkdownChunkStrategy === "markdown-block" && info?.kind !== "tool" && looksLikeStructuredMarkdown(textToSend);
|
|
11430
|
+
if (shouldBufferFinalOnlyPayload || shouldBufferStructuredMarkdownPayload) {
|
|
11431
|
+
if (textToSend) {
|
|
11432
|
+
bufferedC2CMarkdownTexts = appendQQBotBufferedText(bufferedC2CMarkdownTexts, textToSend);
|
|
11433
|
+
}
|
|
11434
|
+
for (const url of mediaQueue) {
|
|
11435
|
+
bufferC2CMarkdownMedia(url);
|
|
11436
|
+
}
|
|
11437
|
+
return;
|
|
9845
11438
|
}
|
|
9846
|
-
|
|
9847
|
-
|
|
11439
|
+
if (hasBufferedC2CMarkdownReply()) {
|
|
11440
|
+
await flushBufferedC2CMarkdownReply();
|
|
11441
|
+
if (shouldSuppressVisibleReplies()) {
|
|
11442
|
+
return;
|
|
11443
|
+
}
|
|
9848
11444
|
}
|
|
11445
|
+
await sendC2CMarkdownTransportPayload({
|
|
11446
|
+
text: textToSend,
|
|
11447
|
+
mediaUrls: mediaQueue,
|
|
11448
|
+
phase: "immediate"
|
|
11449
|
+
});
|
|
9849
11450
|
return;
|
|
9850
11451
|
}
|
|
9851
11452
|
if (textToSend) {
|
|
@@ -9860,12 +11461,16 @@ async function dispatchToAgent(params) {
|
|
|
9860
11461
|
});
|
|
9861
11462
|
const chunks = chunkText(converted);
|
|
9862
11463
|
for (const chunk of chunks) {
|
|
11464
|
+
if (shouldSuppressVisibleReplies()) {
|
|
11465
|
+
return;
|
|
11466
|
+
}
|
|
9863
11467
|
const result = await qqbotOutbound.sendText({
|
|
9864
11468
|
cfg: { channels: { qqbot: qqCfg } },
|
|
9865
11469
|
to: target.to,
|
|
9866
11470
|
text: chunk,
|
|
9867
11471
|
replyToId: textReplyRefs.replyToId,
|
|
9868
|
-
replyEventId: textReplyRefs.replyEventId
|
|
11472
|
+
replyEventId: textReplyRefs.replyEventId,
|
|
11473
|
+
accountId: outboundAccountId
|
|
9869
11474
|
});
|
|
9870
11475
|
if (result.error) {
|
|
9871
11476
|
logger.error(`sendText failed: ${result.error}`);
|
|
@@ -9875,24 +11480,54 @@ async function dispatchToAgent(params) {
|
|
|
9875
11480
|
}
|
|
9876
11481
|
}
|
|
9877
11482
|
}
|
|
11483
|
+
if (shouldSuppressVisibleReplies()) {
|
|
11484
|
+
return;
|
|
11485
|
+
}
|
|
9878
11486
|
await sendQQBotMediaWithFallback({
|
|
9879
11487
|
qqCfg,
|
|
9880
11488
|
to: target.to,
|
|
9881
11489
|
mediaQueue,
|
|
9882
11490
|
replyToId: inbound.messageId,
|
|
9883
11491
|
replyEventId: inbound.eventId,
|
|
11492
|
+
accountId: outboundAccountId,
|
|
9884
11493
|
logger,
|
|
9885
11494
|
onDelivered: () => {
|
|
9886
11495
|
markReplyDelivered();
|
|
9887
11496
|
},
|
|
9888
11497
|
onError: (error) => {
|
|
9889
11498
|
markGroupMessageInterfaceBlocked(error);
|
|
9890
|
-
}
|
|
11499
|
+
},
|
|
11500
|
+
shouldContinue: () => !shouldSuppressVisibleReplies()
|
|
9891
11501
|
});
|
|
9892
11502
|
};
|
|
9893
11503
|
const humanDelay = replyApi.resolveHumanDelayConfig?.(cfg, route.agentId);
|
|
11504
|
+
const dispatchDirect = replyApi.dispatchReplyWithDispatcher;
|
|
9894
11505
|
const dispatchBuffered = replyApi.dispatchReplyWithBufferedBlockDispatcher;
|
|
9895
|
-
|
|
11506
|
+
const streamingReplyOptions = isC2CTarget && !replyFinalOnly ? {
|
|
11507
|
+
disableBlockStreaming: false
|
|
11508
|
+
} : void 0;
|
|
11509
|
+
if (isC2CTarget && !replyFinalOnly && dispatchDirect) {
|
|
11510
|
+
logger.debug(`[dispatch] mode=direct session=${routeSessionKey} to=${target.to}`);
|
|
11511
|
+
await dispatchDirect({
|
|
11512
|
+
ctx: finalCtx,
|
|
11513
|
+
cfg,
|
|
11514
|
+
dispatcherOptions: {
|
|
11515
|
+
deliver,
|
|
11516
|
+
humanDelay,
|
|
11517
|
+
onError: (err, info) => {
|
|
11518
|
+
logger.error(`${info.kind} reply failed: ${String(err)}`);
|
|
11519
|
+
},
|
|
11520
|
+
onSkip: (_payload, info) => {
|
|
11521
|
+
if (info.reason !== "silent") {
|
|
11522
|
+
logger.info(`reply skipped: ${info.reason}`);
|
|
11523
|
+
}
|
|
11524
|
+
}
|
|
11525
|
+
},
|
|
11526
|
+
replyOptions: streamingReplyOptions
|
|
11527
|
+
});
|
|
11528
|
+
await flushBufferedC2CMarkdownReply();
|
|
11529
|
+
} else if (dispatchBuffered) {
|
|
11530
|
+
logger.debug(`[dispatch] mode=buffered session=${routeSessionKey} to=${target.to}`);
|
|
9896
11531
|
await dispatchBuffered({
|
|
9897
11532
|
ctx: finalCtx,
|
|
9898
11533
|
cfg,
|
|
@@ -9907,10 +11542,12 @@ async function dispatchToAgent(params) {
|
|
|
9907
11542
|
logger.info(`reply skipped: ${info.reason}`);
|
|
9908
11543
|
}
|
|
9909
11544
|
}
|
|
9910
|
-
}
|
|
11545
|
+
},
|
|
11546
|
+
replyOptions: streamingReplyOptions
|
|
9911
11547
|
});
|
|
9912
11548
|
await flushBufferedC2CMarkdownReply();
|
|
9913
11549
|
} else {
|
|
11550
|
+
logger.debug(`[dispatch] mode=legacy session=${routeSessionKey} to=${target.to}`);
|
|
9914
11551
|
const dispatcherResult = replyApi.createReplyDispatcherWithTyping ? replyApi.createReplyDispatcherWithTyping({
|
|
9915
11552
|
deliver,
|
|
9916
11553
|
humanDelay,
|
|
@@ -9936,7 +11573,10 @@ async function dispatchToAgent(params) {
|
|
|
9936
11573
|
ctx: finalCtx,
|
|
9937
11574
|
cfg,
|
|
9938
11575
|
dispatcher: dispatcherResult.dispatcher,
|
|
9939
|
-
replyOptions:
|
|
11576
|
+
replyOptions: {
|
|
11577
|
+
...typeof dispatcherResult.replyOptions === "object" && dispatcherResult.replyOptions ? dispatcherResult.replyOptions : {},
|
|
11578
|
+
...streamingReplyOptions ?? {}
|
|
11579
|
+
}
|
|
9940
11580
|
});
|
|
9941
11581
|
dispatcherResult.markDispatchIdle?.();
|
|
9942
11582
|
await flushBufferedC2CMarkdownReply();
|
|
@@ -9945,14 +11585,15 @@ async function dispatchToAgent(params) {
|
|
|
9945
11585
|
inbound,
|
|
9946
11586
|
replyDelivered
|
|
9947
11587
|
});
|
|
9948
|
-
if (noReplyFallback && !groupMessageInterfaceBlocked) {
|
|
11588
|
+
if (noReplyFallback && !groupMessageInterfaceBlocked && !isFastAbortCommand && !shouldSuppressVisibleReplies()) {
|
|
9949
11589
|
logger.info("no visible reply generated for group mention; sending fallback text");
|
|
9950
11590
|
const fallbackResult = await qqbotOutbound.sendText({
|
|
9951
11591
|
cfg: { channels: { qqbot: qqCfg } },
|
|
9952
11592
|
to: target.to,
|
|
9953
11593
|
text: noReplyFallback,
|
|
9954
11594
|
replyToId: inbound.messageId,
|
|
9955
|
-
replyEventId: inbound.eventId
|
|
11595
|
+
replyEventId: inbound.eventId,
|
|
11596
|
+
accountId: outboundAccountId
|
|
9956
11597
|
});
|
|
9957
11598
|
if (fallbackResult.error) {
|
|
9958
11599
|
logger.error(`sendText no-reply fallback failed: ${fallbackResult.error}`);
|
|
@@ -10018,18 +11659,39 @@ async function handleQQBotDispatch(params) {
|
|
|
10018
11659
|
logger.info("qqbot disabled, ignoring inbound message");
|
|
10019
11660
|
return;
|
|
10020
11661
|
}
|
|
10021
|
-
const
|
|
11662
|
+
const senderNameResolution = resolveQQBotSenderName({
|
|
11663
|
+
inbound,
|
|
11664
|
+
cfg: params.cfg,
|
|
11665
|
+
accountId
|
|
11666
|
+
});
|
|
11667
|
+
const resolvedInbound = {
|
|
11668
|
+
...inbound,
|
|
11669
|
+
senderName: senderNameResolution.displayName
|
|
11670
|
+
};
|
|
11671
|
+
logQQBotSenderNameResolution({
|
|
11672
|
+
logger,
|
|
11673
|
+
inbound,
|
|
11674
|
+
accountId,
|
|
11675
|
+
resolution: senderNameResolution
|
|
11676
|
+
});
|
|
11677
|
+
const content = resolvedInbound.content.trim();
|
|
10022
11678
|
const inboundLogContent = sanitizeInboundLogText(
|
|
10023
11679
|
resolveInboundLogContent({
|
|
10024
11680
|
content,
|
|
10025
|
-
attachments:
|
|
11681
|
+
attachments: resolvedInbound.attachments
|
|
10026
11682
|
})
|
|
10027
11683
|
);
|
|
10028
|
-
logger.info(
|
|
10029
|
-
|
|
11684
|
+
logger.info(
|
|
11685
|
+
`[inbound-user] accountId=${accountId} senderId=${resolvedInbound.senderId} senderName=${JSON.stringify(resolvedInbound.senderName ?? resolvedInbound.senderId)} content=${inboundLogContent}`
|
|
11686
|
+
);
|
|
11687
|
+
if (!shouldHandleMessage(resolvedInbound, qqCfg, logger)) {
|
|
10030
11688
|
return;
|
|
10031
11689
|
}
|
|
10032
|
-
const knownTarget = resolveKnownQQBotTargetFromInbound({
|
|
11690
|
+
const knownTarget = resolveKnownQQBotTargetFromInbound({
|
|
11691
|
+
inbound: resolvedInbound,
|
|
11692
|
+
accountId,
|
|
11693
|
+
persistentDisplayName: senderNameResolution.persistentDisplayName
|
|
11694
|
+
});
|
|
10033
11695
|
if (knownTarget) {
|
|
10034
11696
|
try {
|
|
10035
11697
|
upsertKnownQQBotTarget({ target: knownTarget });
|
|
@@ -10037,7 +11699,7 @@ async function handleQQBotDispatch(params) {
|
|
|
10037
11699
|
logger.warn(`failed to record known qqbot target: ${String(err)}`);
|
|
10038
11700
|
}
|
|
10039
11701
|
}
|
|
10040
|
-
const attachmentCount =
|
|
11702
|
+
const attachmentCount = resolvedInbound.attachments?.length ?? 0;
|
|
10041
11703
|
if (attachmentCount > 0) {
|
|
10042
11704
|
logger.info(`inbound message includes ${attachmentCount} attachment(s)`);
|
|
10043
11705
|
}
|
|
@@ -10050,26 +11712,59 @@ async function handleQQBotDispatch(params) {
|
|
|
10050
11712
|
logger.warn("routing API not available");
|
|
10051
11713
|
return;
|
|
10052
11714
|
}
|
|
10053
|
-
const target = resolveChatTarget(
|
|
11715
|
+
const target = resolveChatTarget(resolvedInbound);
|
|
10054
11716
|
const route = routing({
|
|
10055
11717
|
cfg: params.cfg,
|
|
10056
11718
|
channel: "qqbot",
|
|
10057
11719
|
accountId,
|
|
10058
11720
|
peer: { kind: target.peerKind, id: target.peerId }
|
|
10059
11721
|
});
|
|
10060
|
-
const
|
|
10061
|
-
|
|
10062
|
-
|
|
11722
|
+
const effectiveSessionKey = resolveQQBotEffectiveSessionKey({
|
|
11723
|
+
inbound: resolvedInbound,
|
|
11724
|
+
route,
|
|
11725
|
+
accountId
|
|
11726
|
+
});
|
|
11727
|
+
const resolvedRoute = effectiveSessionKey === route.sessionKey ? route : {
|
|
11728
|
+
...route,
|
|
11729
|
+
mainSessionKey: route.mainSessionKey?.trim() || route.sessionKey,
|
|
11730
|
+
effectiveSessionKey
|
|
11731
|
+
};
|
|
11732
|
+
const queueKey = buildSessionDispatchQueueKey(resolvedRoute);
|
|
11733
|
+
if (isQQBotFastAbortCommandText(content)) {
|
|
11734
|
+
const routeSessionKey = resolveQQBotRouteSessionKey(resolvedRoute);
|
|
11735
|
+
markSessionDispatchAbort(queueKey);
|
|
11736
|
+
const droppedCount = dropQueuedSessionDispatches(queueKey);
|
|
11737
|
+
logger.info(
|
|
11738
|
+
`session fast-abort command detected; executing immediately sessionKey=${routeSessionKey}`
|
|
11739
|
+
);
|
|
11740
|
+
logger.info(
|
|
11741
|
+
`session fast-abort command dropped ${droppedCount} queued messages sessionKey=${routeSessionKey}`
|
|
11742
|
+
);
|
|
11743
|
+
await runImmediateSessionDispatch(
|
|
11744
|
+
queueKey,
|
|
11745
|
+
async () => dispatchToAgent({
|
|
11746
|
+
inbound: { ...resolvedInbound, content },
|
|
11747
|
+
cfg: params.cfg,
|
|
11748
|
+
qqCfg,
|
|
11749
|
+
accountId,
|
|
11750
|
+
logger,
|
|
11751
|
+
route: resolvedRoute
|
|
11752
|
+
})
|
|
11753
|
+
);
|
|
11754
|
+
return;
|
|
11755
|
+
}
|
|
11756
|
+
if (hasSessionDispatchBacklog(queueKey)) {
|
|
11757
|
+
logger.info(`session busy; queueing inbound dispatch sessionKey=${resolveQQBotRouteSessionKey(resolvedRoute)}`);
|
|
10063
11758
|
}
|
|
10064
11759
|
await runSerializedSessionDispatch(
|
|
10065
11760
|
queueKey,
|
|
10066
11761
|
async () => dispatchToAgent({
|
|
10067
|
-
inbound: { ...
|
|
11762
|
+
inbound: { ...resolvedInbound, content },
|
|
10068
11763
|
cfg: params.cfg,
|
|
10069
11764
|
qqCfg,
|
|
10070
11765
|
accountId,
|
|
10071
11766
|
logger,
|
|
10072
|
-
route
|
|
11767
|
+
route: resolvedRoute
|
|
10073
11768
|
})
|
|
10074
11769
|
);
|
|
10075
11770
|
}
|
|
@@ -10092,6 +11787,10 @@ function formatGatewayConnectError(err) {
|
|
|
10092
11787
|
}
|
|
10093
11788
|
return String(err);
|
|
10094
11789
|
}
|
|
11790
|
+
function isConnectionIdle(conn) {
|
|
11791
|
+
if (!conn) return true;
|
|
11792
|
+
return !conn.socket && !conn.promise && !conn.connecting;
|
|
11793
|
+
}
|
|
10095
11794
|
var activeConnections = /* @__PURE__ */ new Map();
|
|
10096
11795
|
function getOrCreateConnection(accountId) {
|
|
10097
11796
|
let conn = activeConnections.get(accountId);
|
|
@@ -10121,7 +11820,10 @@ function clearTimers(conn) {
|
|
|
10121
11820
|
conn.reconnectTimer = null;
|
|
10122
11821
|
}
|
|
10123
11822
|
}
|
|
10124
|
-
function cleanupSocket(conn) {
|
|
11823
|
+
function cleanupSocket(conn, expectedSocket) {
|
|
11824
|
+
if (expectedSocket && conn.socket !== expectedSocket) {
|
|
11825
|
+
return false;
|
|
11826
|
+
}
|
|
10125
11827
|
clearTimers(conn);
|
|
10126
11828
|
if (conn.socket) {
|
|
10127
11829
|
try {
|
|
@@ -10132,6 +11834,7 @@ function cleanupSocket(conn) {
|
|
|
10132
11834
|
}
|
|
10133
11835
|
conn.socket = null;
|
|
10134
11836
|
}
|
|
11837
|
+
return true;
|
|
10135
11838
|
}
|
|
10136
11839
|
async function monitorQQBotProvider(opts = {}) {
|
|
10137
11840
|
const { config, runtime: runtime2, abortSignal, accountId = DEFAULT_ACCOUNT_ID, setStatus } = opts;
|
|
@@ -10139,11 +11842,16 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10139
11842
|
log: runtime2?.log,
|
|
10140
11843
|
error: runtime2?.error
|
|
10141
11844
|
});
|
|
10142
|
-
const
|
|
10143
|
-
if (
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
|
|
11845
|
+
const existingConn = activeConnections.get(accountId);
|
|
11846
|
+
if (!existingConn) ; else if (isConnectionIdle(existingConn)) {
|
|
11847
|
+
activeConnections.delete(accountId);
|
|
11848
|
+
}
|
|
11849
|
+
const conn = activeConnections.get(accountId);
|
|
11850
|
+
const existingPromise = conn?.promise;
|
|
11851
|
+
if (existingPromise) {
|
|
11852
|
+
return existingPromise;
|
|
11853
|
+
}
|
|
11854
|
+
if (conn?.socket) {
|
|
10147
11855
|
throw new Error(`QQBot monitor state invalid for account ${accountId}: active socket without promise`);
|
|
10148
11856
|
}
|
|
10149
11857
|
const qqCfg = config ? mergeQQBotAccountConfig(config, accountId) : void 0;
|
|
@@ -10153,18 +11861,20 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10153
11861
|
if (!qqCfg.appId || !qqCfg.clientSecret) {
|
|
10154
11862
|
throw new Error(`QQBot not configured for account ${accountId} (missing appId or clientSecret)`);
|
|
10155
11863
|
}
|
|
10156
|
-
|
|
11864
|
+
const nextConn = conn ?? getOrCreateConnection(accountId);
|
|
11865
|
+
nextConn.promise = new Promise((resolve3, reject) => {
|
|
10157
11866
|
let stopped = false;
|
|
10158
11867
|
const finish = (err) => {
|
|
10159
11868
|
if (stopped) return;
|
|
10160
11869
|
stopped = true;
|
|
10161
11870
|
abortSignal?.removeEventListener("abort", onAbort);
|
|
10162
|
-
cleanupSocket(
|
|
10163
|
-
|
|
10164
|
-
|
|
10165
|
-
|
|
10166
|
-
|
|
10167
|
-
|
|
11871
|
+
cleanupSocket(nextConn);
|
|
11872
|
+
nextConn.connecting = false;
|
|
11873
|
+
nextConn.sessionId = null;
|
|
11874
|
+
nextConn.lastSeq = null;
|
|
11875
|
+
nextConn.promise = null;
|
|
11876
|
+
nextConn.stop = null;
|
|
11877
|
+
nextConn.reconnectAttempt = 0;
|
|
10168
11878
|
activeConnections.delete(accountId);
|
|
10169
11879
|
{
|
|
10170
11880
|
resolve3();
|
|
@@ -10174,33 +11884,33 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10174
11884
|
logger.info("abort signal received, stopping gateway");
|
|
10175
11885
|
finish();
|
|
10176
11886
|
};
|
|
10177
|
-
|
|
11887
|
+
nextConn.stop = () => {
|
|
10178
11888
|
logger.info("stop requested");
|
|
10179
11889
|
finish();
|
|
10180
11890
|
};
|
|
10181
11891
|
const scheduleReconnect = (reason) => {
|
|
10182
11892
|
if (stopped) return;
|
|
10183
|
-
if (
|
|
10184
|
-
const delay = RECONNECT_DELAYS_MS[Math.min(
|
|
10185
|
-
|
|
11893
|
+
if (nextConn.reconnectTimer) return;
|
|
11894
|
+
const delay = RECONNECT_DELAYS_MS[Math.min(nextConn.reconnectAttempt, RECONNECT_DELAYS_MS.length - 1)];
|
|
11895
|
+
nextConn.reconnectAttempt += 1;
|
|
10186
11896
|
logger.warn(`[reconnect] ${reason}; retry in ${delay}ms`);
|
|
10187
|
-
|
|
10188
|
-
|
|
11897
|
+
nextConn.reconnectTimer = setTimeout(() => {
|
|
11898
|
+
nextConn.reconnectTimer = null;
|
|
10189
11899
|
void connect();
|
|
10190
11900
|
}, delay);
|
|
10191
11901
|
};
|
|
10192
11902
|
const startHeartbeat = (intervalMs) => {
|
|
10193
|
-
if (
|
|
10194
|
-
clearInterval(
|
|
11903
|
+
if (nextConn.heartbeatTimer) {
|
|
11904
|
+
clearInterval(nextConn.heartbeatTimer);
|
|
10195
11905
|
}
|
|
10196
|
-
|
|
10197
|
-
if (!
|
|
10198
|
-
const payload = JSON.stringify({ op: 1, d:
|
|
10199
|
-
|
|
11906
|
+
nextConn.heartbeatTimer = setInterval(() => {
|
|
11907
|
+
if (!nextConn.socket || nextConn.socket.readyState !== WebSocket.OPEN) return;
|
|
11908
|
+
const payload = JSON.stringify({ op: 1, d: nextConn.lastSeq });
|
|
11909
|
+
nextConn.socket.send(payload);
|
|
10200
11910
|
}, intervalMs);
|
|
10201
11911
|
};
|
|
10202
11912
|
const sendIdentify = (token) => {
|
|
10203
|
-
if (!
|
|
11913
|
+
if (!nextConn.socket || nextConn.socket.readyState !== WebSocket.OPEN) return;
|
|
10204
11914
|
const payload = {
|
|
10205
11915
|
op: 2,
|
|
10206
11916
|
d: {
|
|
@@ -10209,10 +11919,10 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10209
11919
|
shard: [0, 1]
|
|
10210
11920
|
}
|
|
10211
11921
|
};
|
|
10212
|
-
|
|
11922
|
+
nextConn.socket.send(JSON.stringify(payload));
|
|
10213
11923
|
};
|
|
10214
11924
|
const sendResume = (token, session, seq) => {
|
|
10215
|
-
if (!
|
|
11925
|
+
if (!nextConn.socket || nextConn.socket.readyState !== WebSocket.OPEN) return;
|
|
10216
11926
|
const payload = {
|
|
10217
11927
|
op: 6,
|
|
10218
11928
|
d: {
|
|
@@ -10221,11 +11931,14 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10221
11931
|
seq
|
|
10222
11932
|
}
|
|
10223
11933
|
};
|
|
10224
|
-
|
|
11934
|
+
nextConn.socket.send(JSON.stringify(payload));
|
|
10225
11935
|
};
|
|
10226
|
-
const handleGatewayPayload = async (payload) => {
|
|
11936
|
+
const handleGatewayPayload = async (payload, activeSocket) => {
|
|
11937
|
+
if (stopped || nextConn.socket !== activeSocket) {
|
|
11938
|
+
return;
|
|
11939
|
+
}
|
|
10227
11940
|
if (typeof payload.s === "number") {
|
|
10228
|
-
|
|
11941
|
+
nextConn.lastSeq = payload.s;
|
|
10229
11942
|
}
|
|
10230
11943
|
switch (payload.op) {
|
|
10231
11944
|
case 10: {
|
|
@@ -10233,8 +11946,11 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10233
11946
|
const interval = hello?.heartbeat_interval ?? 3e4;
|
|
10234
11947
|
startHeartbeat(interval);
|
|
10235
11948
|
const token = await getAccessToken(qqCfg.appId, qqCfg.clientSecret);
|
|
10236
|
-
if (
|
|
10237
|
-
|
|
11949
|
+
if (stopped || nextConn.socket !== activeSocket) {
|
|
11950
|
+
return;
|
|
11951
|
+
}
|
|
11952
|
+
if (nextConn.sessionId && typeof nextConn.lastSeq === "number") {
|
|
11953
|
+
sendResume(token, nextConn.sessionId, nextConn.lastSeq);
|
|
10238
11954
|
} else {
|
|
10239
11955
|
sendIdentify(token);
|
|
10240
11956
|
}
|
|
@@ -10244,14 +11960,18 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10244
11960
|
setStatus?.({ lastEventAt: Date.now() });
|
|
10245
11961
|
return;
|
|
10246
11962
|
case 7:
|
|
10247
|
-
cleanupSocket(
|
|
11963
|
+
if (!cleanupSocket(nextConn, activeSocket)) {
|
|
11964
|
+
return;
|
|
11965
|
+
}
|
|
10248
11966
|
scheduleReconnect("server requested reconnect");
|
|
10249
11967
|
return;
|
|
10250
11968
|
case 9:
|
|
10251
|
-
|
|
10252
|
-
|
|
11969
|
+
nextConn.sessionId = null;
|
|
11970
|
+
nextConn.lastSeq = null;
|
|
10253
11971
|
clearTokenCache(qqCfg.appId);
|
|
10254
|
-
cleanupSocket(
|
|
11972
|
+
if (!cleanupSocket(nextConn, activeSocket)) {
|
|
11973
|
+
return;
|
|
11974
|
+
}
|
|
10255
11975
|
scheduleReconnect("invalid session");
|
|
10256
11976
|
return;
|
|
10257
11977
|
case 0: {
|
|
@@ -10259,14 +11979,14 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10259
11979
|
if (eventType === "READY") {
|
|
10260
11980
|
const ready = payload.d;
|
|
10261
11981
|
if (ready?.session_id) {
|
|
10262
|
-
|
|
11982
|
+
nextConn.sessionId = ready.session_id;
|
|
10263
11983
|
}
|
|
10264
|
-
|
|
11984
|
+
nextConn.reconnectAttempt = 0;
|
|
10265
11985
|
logger.info("gateway ready");
|
|
10266
11986
|
return;
|
|
10267
11987
|
}
|
|
10268
11988
|
if (eventType === "RESUMED") {
|
|
10269
|
-
|
|
11989
|
+
nextConn.reconnectAttempt = 0;
|
|
10270
11990
|
logger.info("gateway resumed");
|
|
10271
11991
|
return;
|
|
10272
11992
|
}
|
|
@@ -10287,15 +12007,21 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10287
12007
|
}
|
|
10288
12008
|
};
|
|
10289
12009
|
const connect = async () => {
|
|
10290
|
-
if (stopped ||
|
|
10291
|
-
|
|
12010
|
+
if (stopped || nextConn.connecting) return;
|
|
12011
|
+
nextConn.connecting = true;
|
|
10292
12012
|
try {
|
|
10293
|
-
cleanupSocket(
|
|
12013
|
+
cleanupSocket(nextConn);
|
|
10294
12014
|
const token = await getAccessToken(qqCfg.appId, qqCfg.clientSecret);
|
|
12015
|
+
if (stopped) return;
|
|
10295
12016
|
const gatewayUrl = await getGatewayUrl(token);
|
|
12017
|
+
if (stopped) return;
|
|
10296
12018
|
logger.info(`connecting gateway: ${gatewayUrl}`);
|
|
10297
12019
|
const ws = new WebSocket(gatewayUrl);
|
|
10298
|
-
|
|
12020
|
+
nextConn.socket = ws;
|
|
12021
|
+
if (stopped) {
|
|
12022
|
+
cleanupSocket(nextConn, ws);
|
|
12023
|
+
return;
|
|
12024
|
+
}
|
|
10299
12025
|
ws.on("open", () => {
|
|
10300
12026
|
logger.info("gateway socket opened");
|
|
10301
12027
|
});
|
|
@@ -10308,24 +12034,29 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10308
12034
|
logger.warn(`failed to parse gateway payload: ${String(err)}`);
|
|
10309
12035
|
return;
|
|
10310
12036
|
}
|
|
10311
|
-
void handleGatewayPayload(payload).catch((err) => {
|
|
12037
|
+
void handleGatewayPayload(payload, ws).catch((err) => {
|
|
10312
12038
|
logger.error(`gateway dispatch error: ${String(err)}`);
|
|
10313
12039
|
});
|
|
10314
12040
|
});
|
|
10315
12041
|
ws.on("close", (code, reason) => {
|
|
12042
|
+
if (!cleanupSocket(nextConn, ws)) {
|
|
12043
|
+
return;
|
|
12044
|
+
}
|
|
10316
12045
|
logger.warn(`gateway socket closed (${code}) ${String(reason)}`);
|
|
10317
|
-
cleanupSocket(conn);
|
|
10318
12046
|
scheduleReconnect("socket closed");
|
|
10319
12047
|
});
|
|
10320
12048
|
ws.on("error", (err) => {
|
|
12049
|
+
if (stopped || nextConn.socket !== ws) {
|
|
12050
|
+
return;
|
|
12051
|
+
}
|
|
10321
12052
|
logger.error(`gateway socket error: ${String(err)}`);
|
|
10322
12053
|
});
|
|
10323
12054
|
} catch (err) {
|
|
10324
12055
|
logger.error(`gateway connect failed: ${formatGatewayConnectError(err)}`);
|
|
10325
|
-
cleanupSocket(
|
|
12056
|
+
cleanupSocket(nextConn);
|
|
10326
12057
|
scheduleReconnect("connect failed");
|
|
10327
12058
|
} finally {
|
|
10328
|
-
|
|
12059
|
+
nextConn.connecting = false;
|
|
10329
12060
|
}
|
|
10330
12061
|
};
|
|
10331
12062
|
if (abortSignal?.aborted) {
|
|
@@ -10335,7 +12066,7 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10335
12066
|
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
10336
12067
|
void connect();
|
|
10337
12068
|
});
|
|
10338
|
-
return
|
|
12069
|
+
return nextConn.promise;
|
|
10339
12070
|
}
|
|
10340
12071
|
function stopQQBotMonitorForAccount(accountId = DEFAULT_ACCOUNT_ID) {
|
|
10341
12072
|
const conn = activeConnections.get(accountId);
|
|
@@ -10372,7 +12103,8 @@ function resolveQQBotAccount(params) {
|
|
|
10372
12103
|
configured,
|
|
10373
12104
|
appId: credentials?.appId,
|
|
10374
12105
|
markdownSupport: merged.markdownSupport ?? true,
|
|
10375
|
-
c2cMarkdownDeliveryMode: merged.c2cMarkdownDeliveryMode ?? "proactive-table-only"
|
|
12106
|
+
c2cMarkdownDeliveryMode: merged.c2cMarkdownDeliveryMode ?? "proactive-table-only",
|
|
12107
|
+
c2cMarkdownChunkStrategy: merged.c2cMarkdownChunkStrategy ?? "markdown-block"
|
|
10376
12108
|
};
|
|
10377
12109
|
}
|
|
10378
12110
|
var qqbotPlugin = {
|
|
@@ -10381,7 +12113,7 @@ var qqbotPlugin = {
|
|
|
10381
12113
|
...meta
|
|
10382
12114
|
},
|
|
10383
12115
|
capabilities: {
|
|
10384
|
-
chatTypes: ["direct", "channel"],
|
|
12116
|
+
chatTypes: ["direct", "group", "channel"],
|
|
10385
12117
|
media: true,
|
|
10386
12118
|
reactions: false,
|
|
10387
12119
|
threads: false,
|
|
@@ -10450,6 +12182,10 @@ var qqbotPlugin = {
|
|
|
10450
12182
|
defaultAccount: { type: "string" },
|
|
10451
12183
|
appId: { type: ["string", "number"] },
|
|
10452
12184
|
clientSecret: { type: "string" },
|
|
12185
|
+
displayAliases: {
|
|
12186
|
+
type: "object",
|
|
12187
|
+
additionalProperties: { type: "string" }
|
|
12188
|
+
},
|
|
10453
12189
|
asr: {
|
|
10454
12190
|
type: "object",
|
|
10455
12191
|
additionalProperties: false,
|
|
@@ -10465,6 +12201,10 @@ var qqbotPlugin = {
|
|
|
10465
12201
|
type: "string",
|
|
10466
12202
|
enum: ["passive", "proactive-table-only", "proactive-all"]
|
|
10467
12203
|
},
|
|
12204
|
+
c2cMarkdownChunkStrategy: {
|
|
12205
|
+
type: "string",
|
|
12206
|
+
enum: ["markdown-block", "length"]
|
|
12207
|
+
},
|
|
10468
12208
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
10469
12209
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
10470
12210
|
requireMention: { type: "boolean" },
|
|
@@ -10495,6 +12235,10 @@ var qqbotPlugin = {
|
|
|
10495
12235
|
enabled: { type: "boolean" },
|
|
10496
12236
|
appId: { type: ["string", "number"] },
|
|
10497
12237
|
clientSecret: { type: "string" },
|
|
12238
|
+
displayAliases: {
|
|
12239
|
+
type: "object",
|
|
12240
|
+
additionalProperties: { type: "string" }
|
|
12241
|
+
},
|
|
10498
12242
|
asr: {
|
|
10499
12243
|
type: "object",
|
|
10500
12244
|
additionalProperties: false,
|
|
@@ -10510,6 +12254,10 @@ var qqbotPlugin = {
|
|
|
10510
12254
|
type: "string",
|
|
10511
12255
|
enum: ["passive", "proactive-table-only", "proactive-all"]
|
|
10512
12256
|
},
|
|
12257
|
+
c2cMarkdownChunkStrategy: {
|
|
12258
|
+
type: "string",
|
|
12259
|
+
enum: ["markdown-block", "length"]
|
|
12260
|
+
},
|
|
10513
12261
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
10514
12262
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
10515
12263
|
requireMention: { type: "boolean" },
|
|
@@ -10661,7 +12409,9 @@ var qqbotPlugin = {
|
|
|
10661
12409
|
ctx.log?.info(`[qqbot] starting gateway for account ${ctx.accountId}`);
|
|
10662
12410
|
if (ctx.runtime) {
|
|
10663
12411
|
const candidate = ctx.runtime;
|
|
10664
|
-
|
|
12412
|
+
const hasRouting = Boolean(candidate.channel?.routing?.resolveAgentRoute);
|
|
12413
|
+
const hasReply = Boolean(candidate.channel?.reply?.dispatchReplyWithDispatcher) || Boolean(candidate.channel?.reply?.dispatchReplyWithBufferedBlockDispatcher) || Boolean(candidate.channel?.reply?.dispatchReplyFromConfig);
|
|
12414
|
+
if (hasRouting && hasReply) {
|
|
10665
12415
|
setQQBotRuntime(ctx.runtime);
|
|
10666
12416
|
}
|
|
10667
12417
|
}
|
|
@@ -10697,6 +12447,10 @@ var plugin = {
|
|
|
10697
12447
|
defaultAccount: { type: "string" },
|
|
10698
12448
|
appId: { type: ["string", "number"] },
|
|
10699
12449
|
clientSecret: { type: "string" },
|
|
12450
|
+
displayAliases: {
|
|
12451
|
+
type: "object",
|
|
12452
|
+
additionalProperties: { type: "string" }
|
|
12453
|
+
},
|
|
10700
12454
|
asr: {
|
|
10701
12455
|
type: "object",
|
|
10702
12456
|
additionalProperties: false,
|
|
@@ -10712,6 +12466,10 @@ var plugin = {
|
|
|
10712
12466
|
type: "string",
|
|
10713
12467
|
enum: ["passive", "proactive-table-only", "proactive-all"]
|
|
10714
12468
|
},
|
|
12469
|
+
c2cMarkdownChunkStrategy: {
|
|
12470
|
+
type: "string",
|
|
12471
|
+
enum: ["markdown-block", "length"]
|
|
12472
|
+
},
|
|
10715
12473
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
10716
12474
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
10717
12475
|
requireMention: { type: "boolean" },
|
|
@@ -10742,6 +12500,10 @@ var plugin = {
|
|
|
10742
12500
|
enabled: { type: "boolean" },
|
|
10743
12501
|
appId: { type: ["string", "number"] },
|
|
10744
12502
|
clientSecret: { type: "string" },
|
|
12503
|
+
displayAliases: {
|
|
12504
|
+
type: "object",
|
|
12505
|
+
additionalProperties: { type: "string" }
|
|
12506
|
+
},
|
|
10745
12507
|
asr: {
|
|
10746
12508
|
type: "object",
|
|
10747
12509
|
additionalProperties: false,
|
|
@@ -10757,6 +12519,10 @@ var plugin = {
|
|
|
10757
12519
|
type: "string",
|
|
10758
12520
|
enum: ["passive", "proactive-table-only", "proactive-all"]
|
|
10759
12521
|
},
|
|
12522
|
+
c2cMarkdownChunkStrategy: {
|
|
12523
|
+
type: "string",
|
|
12524
|
+
enum: ["markdown-block", "length"]
|
|
12525
|
+
},
|
|
10760
12526
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
10761
12527
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
10762
12528
|
requireMention: { type: "boolean" },
|