@openclaw-china/qqbot 2026.3.12 → 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 +84 -0
- package/dist/index.js +1325 -160
- 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
|
@@ -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);
|
|
@@ -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");
|
|
@@ -8015,21 +8101,22 @@ async function readMediaWithConfig(source, options) {
|
|
|
8015
8101
|
|
|
8016
8102
|
// src/outbound.ts
|
|
8017
8103
|
function stripPrefix(value, prefix) {
|
|
8018
|
-
return value.
|
|
8104
|
+
return value.slice(0, prefix.length).toLowerCase() === prefix ? value.slice(prefix.length) : value;
|
|
8019
8105
|
}
|
|
8020
8106
|
function parseTarget(to) {
|
|
8021
8107
|
let raw = to.trim();
|
|
8022
8108
|
raw = stripPrefix(raw, "qqbot:");
|
|
8023
|
-
|
|
8109
|
+
const normalizedRaw = raw.toLowerCase();
|
|
8110
|
+
if (normalizedRaw.startsWith("group:")) {
|
|
8024
8111
|
return { kind: "group", id: raw.slice("group:".length) };
|
|
8025
8112
|
}
|
|
8026
|
-
if (
|
|
8113
|
+
if (normalizedRaw.startsWith("channel:")) {
|
|
8027
8114
|
return { kind: "channel", id: raw.slice("channel:".length) };
|
|
8028
8115
|
}
|
|
8029
|
-
if (
|
|
8116
|
+
if (normalizedRaw.startsWith("user:")) {
|
|
8030
8117
|
return { kind: "c2c", id: raw.slice("user:".length) };
|
|
8031
8118
|
}
|
|
8032
|
-
if (
|
|
8119
|
+
if (normalizedRaw.startsWith("c2c:")) {
|
|
8033
8120
|
return { kind: "c2c", id: raw.slice("c2c:".length) };
|
|
8034
8121
|
}
|
|
8035
8122
|
return { kind: "c2c", id: raw };
|
|
@@ -9077,6 +9164,51 @@ function getQQBotRuntime() {
|
|
|
9077
9164
|
return runtime;
|
|
9078
9165
|
}
|
|
9079
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;
|
|
9080
9212
|
function resolveQQBotRouteSessionKey(route) {
|
|
9081
9213
|
const effectiveSessionKey = route.effectiveSessionKey?.trim();
|
|
9082
9214
|
if (effectiveSessionKey) {
|
|
@@ -9088,6 +9220,100 @@ function buildSessionDispatchQueueKey(route) {
|
|
|
9088
9220
|
const accountId = route.accountId?.trim() || DEFAULT_ACCOUNT_ID;
|
|
9089
9221
|
return `${accountId}:${resolveQQBotRouteSessionKey(route)}`;
|
|
9090
9222
|
}
|
|
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;
|
|
9272
|
+
try {
|
|
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
|
+
}
|
|
9289
|
+
} finally {
|
|
9290
|
+
state.processing = false;
|
|
9291
|
+
if (state.queue.length > 0) {
|
|
9292
|
+
void processSerializedSessionDispatchQueue(queueKey);
|
|
9293
|
+
return;
|
|
9294
|
+
}
|
|
9295
|
+
cleanupSessionDispatchState(queueKey, state);
|
|
9296
|
+
}
|
|
9297
|
+
}
|
|
9298
|
+
function dropQueuedSessionDispatches(queueKey) {
|
|
9299
|
+
const state = sessionDispatchQueue.get(queueKey);
|
|
9300
|
+
if (!state || state.queue.length === 0) {
|
|
9301
|
+
return 0;
|
|
9302
|
+
}
|
|
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;
|
|
9310
|
+
}
|
|
9311
|
+
function markSessionDispatchAbort(queueKey) {
|
|
9312
|
+
const state = getSessionDispatchState(queueKey);
|
|
9313
|
+
state.abortGeneration += 1;
|
|
9314
|
+
signalSessionDispatchState(state);
|
|
9315
|
+
return state.abortGeneration;
|
|
9316
|
+
}
|
|
9091
9317
|
function normalizeQQBotSessionKeyPart(value) {
|
|
9092
9318
|
const trimmed = value.trim();
|
|
9093
9319
|
return trimmed ? trimmed.toLowerCase() : "unknown";
|
|
@@ -9123,22 +9349,163 @@ function normalizeQQBotReplyTarget(value) {
|
|
|
9123
9349
|
return /^(user|group|channel):/i.test(trimmed) ? trimmed : void 0;
|
|
9124
9350
|
}
|
|
9125
9351
|
async function runSerializedSessionDispatch(queueKey, task) {
|
|
9126
|
-
const
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
|
|
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);
|
|
9130
9396
|
try {
|
|
9131
|
-
return await
|
|
9397
|
+
return await task();
|
|
9132
9398
|
} finally {
|
|
9133
|
-
|
|
9134
|
-
|
|
9399
|
+
state.immediateActiveCount = Math.max(0, state.immediateActiveCount - 1);
|
|
9400
|
+
signalSessionDispatchState(state);
|
|
9401
|
+
if (state.queue.length > 0) {
|
|
9402
|
+
void processSerializedSessionDispatchQueue(queueKey);
|
|
9135
9403
|
}
|
|
9404
|
+
cleanupSessionDispatchState(queueKey, state);
|
|
9136
9405
|
}
|
|
9137
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
|
+
}
|
|
9138
9427
|
function toString(value) {
|
|
9139
9428
|
if (typeof value === "string" && value.trim()) return value;
|
|
9140
9429
|
return void 0;
|
|
9141
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
|
+
}
|
|
9142
9509
|
function toNumber2(value) {
|
|
9143
9510
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
9144
9511
|
if (typeof value === "string") {
|
|
@@ -9522,14 +9889,13 @@ function parseC2CMessage(data, fallbackEventId) {
|
|
|
9522
9889
|
const id = toString(payload.id);
|
|
9523
9890
|
const eventId = resolveEventId(payload, fallbackEventId);
|
|
9524
9891
|
const timestamp = toNumber2(payload.timestamp) ?? Date.now();
|
|
9525
|
-
const author = payload.author ?? {};
|
|
9892
|
+
const author = asRecord(payload.author) ?? {};
|
|
9526
9893
|
const senderId = toString(author.user_openid);
|
|
9527
9894
|
if (!text && attachments.length === 0 || !id || !senderId) return null;
|
|
9528
9895
|
return {
|
|
9529
9896
|
type: "direct",
|
|
9530
9897
|
senderId,
|
|
9531
9898
|
c2cOpenid: senderId,
|
|
9532
|
-
senderName: toString(author.username),
|
|
9533
9899
|
content: text,
|
|
9534
9900
|
attachments: attachments.length > 0 ? attachments : void 0,
|
|
9535
9901
|
messageId: id,
|
|
@@ -9546,13 +9912,12 @@ function parseGroupMessage(data, fallbackEventId) {
|
|
|
9546
9912
|
const eventId = resolveEventId(payload, fallbackEventId);
|
|
9547
9913
|
const timestamp = toNumber2(payload.timestamp) ?? Date.now();
|
|
9548
9914
|
const groupOpenid = toString(payload.group_openid);
|
|
9549
|
-
const author = payload.author ?? {};
|
|
9915
|
+
const author = asRecord(payload.author) ?? {};
|
|
9550
9916
|
const senderId = toString(author.member_openid);
|
|
9551
9917
|
if (!text && attachments.length === 0 || !id || !senderId || !groupOpenid) return null;
|
|
9552
9918
|
return {
|
|
9553
9919
|
type: "group",
|
|
9554
9920
|
senderId,
|
|
9555
|
-
senderName: toString(author.nickname) ?? toString(author.username),
|
|
9556
9921
|
content: text,
|
|
9557
9922
|
attachments: attachments.length > 0 ? attachments : void 0,
|
|
9558
9923
|
messageId: id,
|
|
@@ -9570,13 +9935,12 @@ function parseChannelMessage(data, fallbackEventId) {
|
|
|
9570
9935
|
const timestamp = toNumber2(payload.timestamp) ?? Date.now();
|
|
9571
9936
|
const channelId = toString(payload.channel_id);
|
|
9572
9937
|
const guildId = toString(payload.guild_id);
|
|
9573
|
-
const author = payload.author ?? {};
|
|
9938
|
+
const author = asRecord(payload.author) ?? {};
|
|
9574
9939
|
const senderId = toString(author.id);
|
|
9575
9940
|
if (!text && attachments.length === 0 || !id || !senderId || !channelId) return null;
|
|
9576
9941
|
return {
|
|
9577
9942
|
type: "channel",
|
|
9578
9943
|
senderId,
|
|
9579
|
-
senderName: toString(author.username),
|
|
9580
9944
|
content: text,
|
|
9581
9945
|
attachments: attachments.length > 0 ? attachments : void 0,
|
|
9582
9946
|
messageId: id,
|
|
@@ -9594,13 +9958,12 @@ function parseDirectMessage(data, fallbackEventId) {
|
|
|
9594
9958
|
const eventId = resolveEventId(payload, fallbackEventId);
|
|
9595
9959
|
const timestamp = toNumber2(payload.timestamp) ?? Date.now();
|
|
9596
9960
|
const guildId = toString(payload.guild_id);
|
|
9597
|
-
const author = payload.author ?? {};
|
|
9961
|
+
const author = asRecord(payload.author) ?? {};
|
|
9598
9962
|
const senderId = toString(author.id);
|
|
9599
9963
|
if (!text && attachments.length === 0 || !id || !senderId) return null;
|
|
9600
9964
|
return {
|
|
9601
9965
|
type: "direct",
|
|
9602
9966
|
senderId,
|
|
9603
|
-
senderName: toString(author.username),
|
|
9604
9967
|
content: text,
|
|
9605
9968
|
attachments: attachments.length > 0 ? attachments : void 0,
|
|
9606
9969
|
messageId: id,
|
|
@@ -9675,7 +10038,7 @@ function resolveEnvelopeFrom(event) {
|
|
|
9675
10038
|
return event.senderName?.trim() || event.senderId;
|
|
9676
10039
|
}
|
|
9677
10040
|
function resolveKnownQQBotTargetFromInbound(params) {
|
|
9678
|
-
const { inbound, accountId } = params;
|
|
10041
|
+
const { inbound, accountId, persistentDisplayName } = params;
|
|
9679
10042
|
if (inbound.type === "direct") {
|
|
9680
10043
|
if (!inbound.c2cOpenid?.trim()) {
|
|
9681
10044
|
return void 0;
|
|
@@ -9684,7 +10047,7 @@ function resolveKnownQQBotTargetFromInbound(params) {
|
|
|
9684
10047
|
accountId,
|
|
9685
10048
|
kind: "user",
|
|
9686
10049
|
target: `user:${inbound.c2cOpenid}`,
|
|
9687
|
-
displayName:
|
|
10050
|
+
...persistentDisplayName ? { displayName: persistentDisplayName } : {},
|
|
9688
10051
|
sourceChatType: "direct",
|
|
9689
10052
|
firstSeenAt: inbound.timestamp,
|
|
9690
10053
|
lastSeenAt: inbound.timestamp
|
|
@@ -9695,7 +10058,7 @@ function resolveKnownQQBotTargetFromInbound(params) {
|
|
|
9695
10058
|
accountId,
|
|
9696
10059
|
kind: "group",
|
|
9697
10060
|
target: `group:${inbound.groupOpenid}`,
|
|
9698
|
-
displayName:
|
|
10061
|
+
...persistentDisplayName ? { displayName: persistentDisplayName } : {},
|
|
9699
10062
|
sourceChatType: "group",
|
|
9700
10063
|
firstSeenAt: inbound.timestamp,
|
|
9701
10064
|
lastSeenAt: inbound.timestamp
|
|
@@ -9706,7 +10069,7 @@ function resolveKnownQQBotTargetFromInbound(params) {
|
|
|
9706
10069
|
accountId,
|
|
9707
10070
|
kind: "channel",
|
|
9708
10071
|
target: `channel:${inbound.channelId}`,
|
|
9709
|
-
displayName:
|
|
10072
|
+
...persistentDisplayName ? { displayName: persistentDisplayName } : {},
|
|
9710
10073
|
sourceChatType: "channel",
|
|
9711
10074
|
firstSeenAt: inbound.timestamp,
|
|
9712
10075
|
lastSeenAt: inbound.timestamp
|
|
@@ -9821,6 +10184,14 @@ var DIRECTIVE_TAG_RE = /\[\[\s*(?:reply_to_current|reply_to\s*:[^\]]+|audio_as_v
|
|
|
9821
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;
|
|
9822
10185
|
var TTS_LIKE_RAW_TEXT_RE = /\[\[\s*(?:tts(?::text)?|\/tts(?::text)?|audio_as_voice|reply_to_current|reply_to\s*:)/i;
|
|
9823
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 = /[`*_~|]/;
|
|
9824
10195
|
var EXPLICIT_MARKDOWN_FENCE_RE = /(^|\n)(`{3,}|~{3,})\s*(?:markdown|md)\s*\n([\s\S]*?)\n\2(?=\n|$)/gi;
|
|
9825
10196
|
var GENERIC_MARKDOWN_FENCE_RE = /(^|\n)(`{3,}|~{3,})\s*\n([\s\S]*?)\n\2(?=\n|$)/g;
|
|
9826
10197
|
function extractFinalBlocks(text) {
|
|
@@ -9889,8 +10260,9 @@ function evaluateReplyFinalOnlyDelivery(params) {
|
|
|
9889
10260
|
}
|
|
9890
10261
|
function isQQBotC2CTarget(to) {
|
|
9891
10262
|
const trimmed = to.trim();
|
|
9892
|
-
const raw = trimmed.
|
|
9893
|
-
|
|
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:");
|
|
9894
10266
|
}
|
|
9895
10267
|
function splitQQBotMarkdownTransportMediaUrls(mediaUrls) {
|
|
9896
10268
|
const markdownImageUrls = [];
|
|
@@ -9992,10 +10364,592 @@ function normalizeQQBotRenderedMarkdown(text) {
|
|
|
9992
10364
|
);
|
|
9993
10365
|
return changed ? next.trim() : text.trim();
|
|
9994
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);
|
|
10844
|
+
}
|
|
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();
|
|
10867
|
+
}
|
|
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
|
+
}
|
|
10902
|
+
}
|
|
10903
|
+
appendPiece(headingText);
|
|
10904
|
+
continue;
|
|
10905
|
+
}
|
|
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);
|
|
10944
|
+
}
|
|
9995
10945
|
async function sendQQBotMediaWithFallback(params) {
|
|
9996
10946
|
const { qqCfg, to, mediaQueue, replyToId, replyEventId, accountId, logger, onDelivered, onError } = params;
|
|
9997
10947
|
const outbound = params.outbound ?? qqbotOutbound;
|
|
10948
|
+
const shouldContinue = params.shouldContinue ?? (() => true);
|
|
9998
10949
|
for (const mediaUrl of mediaQueue) {
|
|
10950
|
+
if (!shouldContinue()) {
|
|
10951
|
+
return;
|
|
10952
|
+
}
|
|
9999
10953
|
const result = await outbound.sendMedia({
|
|
10000
10954
|
cfg: { channels: { qqbot: qqCfg } },
|
|
10001
10955
|
to,
|
|
@@ -10011,6 +10965,9 @@ async function sendQQBotMediaWithFallback(params) {
|
|
|
10011
10965
|
if (!fallback) {
|
|
10012
10966
|
continue;
|
|
10013
10967
|
}
|
|
10968
|
+
if (!shouldContinue()) {
|
|
10969
|
+
return;
|
|
10970
|
+
}
|
|
10014
10971
|
const fallbackResult = await outbound.sendText({
|
|
10015
10972
|
cfg: { channels: { qqbot: qqCfg } },
|
|
10016
10973
|
to,
|
|
@@ -10063,10 +11020,17 @@ async function dispatchToAgent(params) {
|
|
|
10063
11020
|
const { inbound, cfg, qqCfg, accountId, logger, route } = params;
|
|
10064
11021
|
const runtime2 = getQQBotRuntime();
|
|
10065
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
|
+
};
|
|
10066
11030
|
const target = resolveChatTarget(inbound);
|
|
10067
11031
|
const outboundAccountId = route.accountId ?? accountId;
|
|
10068
11032
|
let typingRefIdx;
|
|
10069
|
-
if (inbound.c2cOpenid) {
|
|
11033
|
+
if (inbound.c2cOpenid && !isFastAbortCommand && !shouldSuppressVisibleReplies()) {
|
|
10070
11034
|
const typing = await qqbotOutbound.sendTyping({
|
|
10071
11035
|
cfg: { channels: { qqbot: qqCfg } },
|
|
10072
11036
|
to: `user:${inbound.c2cOpenid}`,
|
|
@@ -10103,7 +11067,7 @@ async function dispatchToAgent(params) {
|
|
|
10103
11067
|
delayMs: qqCfg.longTaskNoticeDelayMs ?? DEFAULT_LONG_TASK_NOTICE_DELAY_MS,
|
|
10104
11068
|
logger,
|
|
10105
11069
|
sendNotice: async () => {
|
|
10106
|
-
if (groupMessageInterfaceBlocked) return;
|
|
11070
|
+
if (groupMessageInterfaceBlocked || isFastAbortCommand || shouldSuppressVisibleReplies()) return;
|
|
10107
11071
|
const result = await qqbotOutbound.sendText({
|
|
10108
11072
|
cfg: { channels: { qqbot: qqCfg } },
|
|
10109
11073
|
to: target.to,
|
|
@@ -10137,6 +11101,9 @@ async function dispatchToAgent(params) {
|
|
|
10137
11101
|
logger
|
|
10138
11102
|
});
|
|
10139
11103
|
if (qqCfg.asr?.enabled && resolvedAttachmentResult.hasVoiceAttachment && !resolvedAttachmentResult.hasVoiceTranscript) {
|
|
11104
|
+
if (shouldSuppressVisibleReplies()) {
|
|
11105
|
+
return;
|
|
11106
|
+
}
|
|
10140
11107
|
const fallback = await qqbotOutbound.sendText({
|
|
10141
11108
|
cfg: { channels: { qqbot: qqCfg } },
|
|
10142
11109
|
to: target.to,
|
|
@@ -10246,29 +11213,43 @@ async function dispatchToAgent(params) {
|
|
|
10246
11213
|
}
|
|
10247
11214
|
finalCtx.BodyForAgent = appendCronHiddenPrompt(agentBody);
|
|
10248
11215
|
}
|
|
10249
|
-
if (storePath
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10262
|
-
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
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
|
+
}
|
|
10272
11253
|
}
|
|
10273
11254
|
}
|
|
10274
11255
|
const textApi = runtime2.channel?.text;
|
|
@@ -10296,10 +11277,13 @@ async function dispatchToAgent(params) {
|
|
|
10296
11277
|
const replyFinalOnly = qqCfg.replyFinalOnly ?? false;
|
|
10297
11278
|
const markdownSupport = qqCfg.markdownSupport ?? true;
|
|
10298
11279
|
const c2cMarkdownDeliveryMode = qqCfg.c2cMarkdownDeliveryMode ?? "proactive-table-only";
|
|
10299
|
-
const
|
|
11280
|
+
const c2cMarkdownChunkStrategy = qqCfg.c2cMarkdownChunkStrategy ?? "markdown-block";
|
|
11281
|
+
const isC2CTarget = isQQBotC2CTarget(target.to);
|
|
11282
|
+
const useC2CMarkdownTransport = markdownSupport && isC2CTarget;
|
|
10300
11283
|
let bufferedC2CMarkdownTexts = [];
|
|
10301
11284
|
let bufferedC2CMarkdownMediaUrls = [];
|
|
10302
11285
|
const bufferedC2CMarkdownMediaSeen = /* @__PURE__ */ new Set();
|
|
11286
|
+
const hasBufferedC2CMarkdownReply = () => bufferedC2CMarkdownTexts.length > 0 || bufferedC2CMarkdownMediaUrls.length > 0;
|
|
10303
11287
|
const bufferC2CMarkdownMedia = (url) => {
|
|
10304
11288
|
const next = url?.trim();
|
|
10305
11289
|
if (!next || bufferedC2CMarkdownMediaSeen.has(next)) return;
|
|
@@ -10307,12 +11291,18 @@ async function dispatchToAgent(params) {
|
|
|
10307
11291
|
bufferedC2CMarkdownMediaUrls.push(next);
|
|
10308
11292
|
};
|
|
10309
11293
|
const sendC2CMarkdownTransportPayload = async (params2) => {
|
|
11294
|
+
if (shouldSuppressVisibleReplies()) {
|
|
11295
|
+
return;
|
|
11296
|
+
}
|
|
10310
11297
|
const normalizedText = normalizeQQBotRenderedMarkdown(params2.text);
|
|
10311
11298
|
const { markdownImageUrls, mediaQueue } = splitQQBotMarkdownTransportMediaUrls(params2.mediaUrls);
|
|
10312
11299
|
const finalMarkdownText = await normalizeQQBotMarkdownImages({
|
|
10313
11300
|
text: normalizedText,
|
|
10314
11301
|
appendImageUrls: markdownImageUrls
|
|
10315
11302
|
});
|
|
11303
|
+
if (shouldSuppressVisibleReplies()) {
|
|
11304
|
+
return;
|
|
11305
|
+
}
|
|
10316
11306
|
const textReplyRefs = resolveQQBotTextReplyRefs({
|
|
10317
11307
|
to: target.to,
|
|
10318
11308
|
text: finalMarkdownText || normalizedText,
|
|
@@ -10321,52 +11311,59 @@ async function dispatchToAgent(params) {
|
|
|
10321
11311
|
replyToId: inbound.messageId,
|
|
10322
11312
|
replyEventId: inbound.eventId
|
|
10323
11313
|
});
|
|
10324
|
-
const
|
|
11314
|
+
const textChunks = finalMarkdownText ? chunkC2CMarkdownText({
|
|
11315
|
+
text: finalMarkdownText,
|
|
11316
|
+
limit,
|
|
11317
|
+
strategy: c2cMarkdownChunkStrategy,
|
|
11318
|
+
fallbackChunkText: chunkText
|
|
11319
|
+
}) : [];
|
|
10325
11320
|
const deliveryLabel = textReplyRefs.forceProactive ? "c2c-markdown-proactive" : "c2c-markdown-passive";
|
|
10326
11321
|
logger.info(
|
|
10327
|
-
`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}`
|
|
10328
11323
|
);
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
10338
|
-
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
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
|
+
}
|
|
10344
11342
|
if (!finalMarkdownText) {
|
|
10345
11343
|
return;
|
|
10346
11344
|
}
|
|
10347
|
-
for (let
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
|
|
10351
|
-
|
|
10352
|
-
|
|
10353
|
-
|
|
10354
|
-
|
|
10355
|
-
|
|
10356
|
-
|
|
10357
|
-
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
10368
|
-
|
|
10369
|
-
}
|
|
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();
|
|
10370
11367
|
}
|
|
10371
11368
|
}
|
|
10372
11369
|
};
|
|
@@ -10377,6 +11374,12 @@ async function dispatchToAgent(params) {
|
|
|
10377
11374
|
bufferedC2CMarkdownMediaSeen.clear();
|
|
10378
11375
|
return;
|
|
10379
11376
|
}
|
|
11377
|
+
if (shouldSuppressVisibleReplies()) {
|
|
11378
|
+
bufferedC2CMarkdownTexts = [];
|
|
11379
|
+
bufferedC2CMarkdownMediaUrls = [];
|
|
11380
|
+
bufferedC2CMarkdownMediaSeen.clear();
|
|
11381
|
+
return;
|
|
11382
|
+
}
|
|
10380
11383
|
const combinedText = bufferedC2CMarkdownTexts.join("\n\n").trim();
|
|
10381
11384
|
const combinedMediaUrls = [...bufferedC2CMarkdownMediaUrls];
|
|
10382
11385
|
bufferedC2CMarkdownTexts = [];
|
|
@@ -10389,6 +11392,9 @@ async function dispatchToAgent(params) {
|
|
|
10389
11392
|
});
|
|
10390
11393
|
};
|
|
10391
11394
|
const deliver = async (payload, info) => {
|
|
11395
|
+
if (shouldSuppressVisibleReplies()) {
|
|
11396
|
+
return;
|
|
11397
|
+
}
|
|
10392
11398
|
const typed = payload;
|
|
10393
11399
|
const extractedTextMedia = extractQQBotReplyMedia({
|
|
10394
11400
|
text: typed?.text ?? "",
|
|
@@ -10420,7 +11426,8 @@ async function dispatchToAgent(params) {
|
|
|
10420
11426
|
const textToSend = suppressText ? "" : cleanedText;
|
|
10421
11427
|
if (useC2CMarkdownTransport) {
|
|
10422
11428
|
const shouldBufferFinalOnlyPayload = replyFinalOnly && (!info?.kind || info.kind === "final");
|
|
10423
|
-
|
|
11429
|
+
const shouldBufferStructuredMarkdownPayload = !replyFinalOnly && c2cMarkdownChunkStrategy === "markdown-block" && info?.kind !== "tool" && looksLikeStructuredMarkdown(textToSend);
|
|
11430
|
+
if (shouldBufferFinalOnlyPayload || shouldBufferStructuredMarkdownPayload) {
|
|
10424
11431
|
if (textToSend) {
|
|
10425
11432
|
bufferedC2CMarkdownTexts = appendQQBotBufferedText(bufferedC2CMarkdownTexts, textToSend);
|
|
10426
11433
|
}
|
|
@@ -10429,6 +11436,12 @@ async function dispatchToAgent(params) {
|
|
|
10429
11436
|
}
|
|
10430
11437
|
return;
|
|
10431
11438
|
}
|
|
11439
|
+
if (hasBufferedC2CMarkdownReply()) {
|
|
11440
|
+
await flushBufferedC2CMarkdownReply();
|
|
11441
|
+
if (shouldSuppressVisibleReplies()) {
|
|
11442
|
+
return;
|
|
11443
|
+
}
|
|
11444
|
+
}
|
|
10432
11445
|
await sendC2CMarkdownTransportPayload({
|
|
10433
11446
|
text: textToSend,
|
|
10434
11447
|
mediaUrls: mediaQueue,
|
|
@@ -10448,6 +11461,9 @@ async function dispatchToAgent(params) {
|
|
|
10448
11461
|
});
|
|
10449
11462
|
const chunks = chunkText(converted);
|
|
10450
11463
|
for (const chunk of chunks) {
|
|
11464
|
+
if (shouldSuppressVisibleReplies()) {
|
|
11465
|
+
return;
|
|
11466
|
+
}
|
|
10451
11467
|
const result = await qqbotOutbound.sendText({
|
|
10452
11468
|
cfg: { channels: { qqbot: qqCfg } },
|
|
10453
11469
|
to: target.to,
|
|
@@ -10464,6 +11480,9 @@ async function dispatchToAgent(params) {
|
|
|
10464
11480
|
}
|
|
10465
11481
|
}
|
|
10466
11482
|
}
|
|
11483
|
+
if (shouldSuppressVisibleReplies()) {
|
|
11484
|
+
return;
|
|
11485
|
+
}
|
|
10467
11486
|
await sendQQBotMediaWithFallback({
|
|
10468
11487
|
qqCfg,
|
|
10469
11488
|
to: target.to,
|
|
@@ -10477,12 +11496,38 @@ async function dispatchToAgent(params) {
|
|
|
10477
11496
|
},
|
|
10478
11497
|
onError: (error) => {
|
|
10479
11498
|
markGroupMessageInterfaceBlocked(error);
|
|
10480
|
-
}
|
|
11499
|
+
},
|
|
11500
|
+
shouldContinue: () => !shouldSuppressVisibleReplies()
|
|
10481
11501
|
});
|
|
10482
11502
|
};
|
|
10483
11503
|
const humanDelay = replyApi.resolveHumanDelayConfig?.(cfg, route.agentId);
|
|
11504
|
+
const dispatchDirect = replyApi.dispatchReplyWithDispatcher;
|
|
10484
11505
|
const dispatchBuffered = replyApi.dispatchReplyWithBufferedBlockDispatcher;
|
|
10485
|
-
|
|
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}`);
|
|
10486
11531
|
await dispatchBuffered({
|
|
10487
11532
|
ctx: finalCtx,
|
|
10488
11533
|
cfg,
|
|
@@ -10497,10 +11542,12 @@ async function dispatchToAgent(params) {
|
|
|
10497
11542
|
logger.info(`reply skipped: ${info.reason}`);
|
|
10498
11543
|
}
|
|
10499
11544
|
}
|
|
10500
|
-
}
|
|
11545
|
+
},
|
|
11546
|
+
replyOptions: streamingReplyOptions
|
|
10501
11547
|
});
|
|
10502
11548
|
await flushBufferedC2CMarkdownReply();
|
|
10503
11549
|
} else {
|
|
11550
|
+
logger.debug(`[dispatch] mode=legacy session=${routeSessionKey} to=${target.to}`);
|
|
10504
11551
|
const dispatcherResult = replyApi.createReplyDispatcherWithTyping ? replyApi.createReplyDispatcherWithTyping({
|
|
10505
11552
|
deliver,
|
|
10506
11553
|
humanDelay,
|
|
@@ -10526,7 +11573,10 @@ async function dispatchToAgent(params) {
|
|
|
10526
11573
|
ctx: finalCtx,
|
|
10527
11574
|
cfg,
|
|
10528
11575
|
dispatcher: dispatcherResult.dispatcher,
|
|
10529
|
-
replyOptions:
|
|
11576
|
+
replyOptions: {
|
|
11577
|
+
...typeof dispatcherResult.replyOptions === "object" && dispatcherResult.replyOptions ? dispatcherResult.replyOptions : {},
|
|
11578
|
+
...streamingReplyOptions ?? {}
|
|
11579
|
+
}
|
|
10530
11580
|
});
|
|
10531
11581
|
dispatcherResult.markDispatchIdle?.();
|
|
10532
11582
|
await flushBufferedC2CMarkdownReply();
|
|
@@ -10535,7 +11585,7 @@ async function dispatchToAgent(params) {
|
|
|
10535
11585
|
inbound,
|
|
10536
11586
|
replyDelivered
|
|
10537
11587
|
});
|
|
10538
|
-
if (noReplyFallback && !groupMessageInterfaceBlocked) {
|
|
11588
|
+
if (noReplyFallback && !groupMessageInterfaceBlocked && !isFastAbortCommand && !shouldSuppressVisibleReplies()) {
|
|
10539
11589
|
logger.info("no visible reply generated for group mention; sending fallback text");
|
|
10540
11590
|
const fallbackResult = await qqbotOutbound.sendText({
|
|
10541
11591
|
cfg: { channels: { qqbot: qqCfg } },
|
|
@@ -10609,18 +11659,39 @@ async function handleQQBotDispatch(params) {
|
|
|
10609
11659
|
logger.info("qqbot disabled, ignoring inbound message");
|
|
10610
11660
|
return;
|
|
10611
11661
|
}
|
|
10612
|
-
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();
|
|
10613
11678
|
const inboundLogContent = sanitizeInboundLogText(
|
|
10614
11679
|
resolveInboundLogContent({
|
|
10615
11680
|
content,
|
|
10616
|
-
attachments:
|
|
11681
|
+
attachments: resolvedInbound.attachments
|
|
10617
11682
|
})
|
|
10618
11683
|
);
|
|
10619
|
-
logger.info(
|
|
10620
|
-
|
|
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)) {
|
|
10621
11688
|
return;
|
|
10622
11689
|
}
|
|
10623
|
-
const knownTarget = resolveKnownQQBotTargetFromInbound({
|
|
11690
|
+
const knownTarget = resolveKnownQQBotTargetFromInbound({
|
|
11691
|
+
inbound: resolvedInbound,
|
|
11692
|
+
accountId,
|
|
11693
|
+
persistentDisplayName: senderNameResolution.persistentDisplayName
|
|
11694
|
+
});
|
|
10624
11695
|
if (knownTarget) {
|
|
10625
11696
|
try {
|
|
10626
11697
|
upsertKnownQQBotTarget({ target: knownTarget });
|
|
@@ -10628,7 +11699,7 @@ async function handleQQBotDispatch(params) {
|
|
|
10628
11699
|
logger.warn(`failed to record known qqbot target: ${String(err)}`);
|
|
10629
11700
|
}
|
|
10630
11701
|
}
|
|
10631
|
-
const attachmentCount =
|
|
11702
|
+
const attachmentCount = resolvedInbound.attachments?.length ?? 0;
|
|
10632
11703
|
if (attachmentCount > 0) {
|
|
10633
11704
|
logger.info(`inbound message includes ${attachmentCount} attachment(s)`);
|
|
10634
11705
|
}
|
|
@@ -10641,7 +11712,7 @@ async function handleQQBotDispatch(params) {
|
|
|
10641
11712
|
logger.warn("routing API not available");
|
|
10642
11713
|
return;
|
|
10643
11714
|
}
|
|
10644
|
-
const target = resolveChatTarget(
|
|
11715
|
+
const target = resolveChatTarget(resolvedInbound);
|
|
10645
11716
|
const route = routing({
|
|
10646
11717
|
cfg: params.cfg,
|
|
10647
11718
|
channel: "qqbot",
|
|
@@ -10649,7 +11720,7 @@ async function handleQQBotDispatch(params) {
|
|
|
10649
11720
|
peer: { kind: target.peerKind, id: target.peerId }
|
|
10650
11721
|
});
|
|
10651
11722
|
const effectiveSessionKey = resolveQQBotEffectiveSessionKey({
|
|
10652
|
-
inbound,
|
|
11723
|
+
inbound: resolvedInbound,
|
|
10653
11724
|
route,
|
|
10654
11725
|
accountId
|
|
10655
11726
|
});
|
|
@@ -10659,13 +11730,36 @@ async function handleQQBotDispatch(params) {
|
|
|
10659
11730
|
effectiveSessionKey
|
|
10660
11731
|
};
|
|
10661
11732
|
const queueKey = buildSessionDispatchQueueKey(resolvedRoute);
|
|
10662
|
-
if (
|
|
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)) {
|
|
10663
11757
|
logger.info(`session busy; queueing inbound dispatch sessionKey=${resolveQQBotRouteSessionKey(resolvedRoute)}`);
|
|
10664
11758
|
}
|
|
10665
11759
|
await runSerializedSessionDispatch(
|
|
10666
11760
|
queueKey,
|
|
10667
11761
|
async () => dispatchToAgent({
|
|
10668
|
-
inbound: { ...
|
|
11762
|
+
inbound: { ...resolvedInbound, content },
|
|
10669
11763
|
cfg: params.cfg,
|
|
10670
11764
|
qqCfg,
|
|
10671
11765
|
accountId,
|
|
@@ -10693,6 +11787,10 @@ function formatGatewayConnectError(err) {
|
|
|
10693
11787
|
}
|
|
10694
11788
|
return String(err);
|
|
10695
11789
|
}
|
|
11790
|
+
function isConnectionIdle(conn) {
|
|
11791
|
+
if (!conn) return true;
|
|
11792
|
+
return !conn.socket && !conn.promise && !conn.connecting;
|
|
11793
|
+
}
|
|
10696
11794
|
var activeConnections = /* @__PURE__ */ new Map();
|
|
10697
11795
|
function getOrCreateConnection(accountId) {
|
|
10698
11796
|
let conn = activeConnections.get(accountId);
|
|
@@ -10722,7 +11820,10 @@ function clearTimers(conn) {
|
|
|
10722
11820
|
conn.reconnectTimer = null;
|
|
10723
11821
|
}
|
|
10724
11822
|
}
|
|
10725
|
-
function cleanupSocket(conn) {
|
|
11823
|
+
function cleanupSocket(conn, expectedSocket) {
|
|
11824
|
+
if (expectedSocket && conn.socket !== expectedSocket) {
|
|
11825
|
+
return false;
|
|
11826
|
+
}
|
|
10726
11827
|
clearTimers(conn);
|
|
10727
11828
|
if (conn.socket) {
|
|
10728
11829
|
try {
|
|
@@ -10733,6 +11834,7 @@ function cleanupSocket(conn) {
|
|
|
10733
11834
|
}
|
|
10734
11835
|
conn.socket = null;
|
|
10735
11836
|
}
|
|
11837
|
+
return true;
|
|
10736
11838
|
}
|
|
10737
11839
|
async function monitorQQBotProvider(opts = {}) {
|
|
10738
11840
|
const { config, runtime: runtime2, abortSignal, accountId = DEFAULT_ACCOUNT_ID, setStatus } = opts;
|
|
@@ -10740,11 +11842,16 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10740
11842
|
log: runtime2?.log,
|
|
10741
11843
|
error: runtime2?.error
|
|
10742
11844
|
});
|
|
10743
|
-
const
|
|
10744
|
-
if (
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
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) {
|
|
10748
11855
|
throw new Error(`QQBot monitor state invalid for account ${accountId}: active socket without promise`);
|
|
10749
11856
|
}
|
|
10750
11857
|
const qqCfg = config ? mergeQQBotAccountConfig(config, accountId) : void 0;
|
|
@@ -10754,18 +11861,20 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10754
11861
|
if (!qqCfg.appId || !qqCfg.clientSecret) {
|
|
10755
11862
|
throw new Error(`QQBot not configured for account ${accountId} (missing appId or clientSecret)`);
|
|
10756
11863
|
}
|
|
10757
|
-
|
|
11864
|
+
const nextConn = conn ?? getOrCreateConnection(accountId);
|
|
11865
|
+
nextConn.promise = new Promise((resolve3, reject) => {
|
|
10758
11866
|
let stopped = false;
|
|
10759
11867
|
const finish = (err) => {
|
|
10760
11868
|
if (stopped) return;
|
|
10761
11869
|
stopped = true;
|
|
10762
11870
|
abortSignal?.removeEventListener("abort", onAbort);
|
|
10763
|
-
cleanupSocket(
|
|
10764
|
-
|
|
10765
|
-
|
|
10766
|
-
|
|
10767
|
-
|
|
10768
|
-
|
|
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;
|
|
10769
11878
|
activeConnections.delete(accountId);
|
|
10770
11879
|
{
|
|
10771
11880
|
resolve3();
|
|
@@ -10775,33 +11884,33 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10775
11884
|
logger.info("abort signal received, stopping gateway");
|
|
10776
11885
|
finish();
|
|
10777
11886
|
};
|
|
10778
|
-
|
|
11887
|
+
nextConn.stop = () => {
|
|
10779
11888
|
logger.info("stop requested");
|
|
10780
11889
|
finish();
|
|
10781
11890
|
};
|
|
10782
11891
|
const scheduleReconnect = (reason) => {
|
|
10783
11892
|
if (stopped) return;
|
|
10784
|
-
if (
|
|
10785
|
-
const delay = RECONNECT_DELAYS_MS[Math.min(
|
|
10786
|
-
|
|
11893
|
+
if (nextConn.reconnectTimer) return;
|
|
11894
|
+
const delay = RECONNECT_DELAYS_MS[Math.min(nextConn.reconnectAttempt, RECONNECT_DELAYS_MS.length - 1)];
|
|
11895
|
+
nextConn.reconnectAttempt += 1;
|
|
10787
11896
|
logger.warn(`[reconnect] ${reason}; retry in ${delay}ms`);
|
|
10788
|
-
|
|
10789
|
-
|
|
11897
|
+
nextConn.reconnectTimer = setTimeout(() => {
|
|
11898
|
+
nextConn.reconnectTimer = null;
|
|
10790
11899
|
void connect();
|
|
10791
11900
|
}, delay);
|
|
10792
11901
|
};
|
|
10793
11902
|
const startHeartbeat = (intervalMs) => {
|
|
10794
|
-
if (
|
|
10795
|
-
clearInterval(
|
|
11903
|
+
if (nextConn.heartbeatTimer) {
|
|
11904
|
+
clearInterval(nextConn.heartbeatTimer);
|
|
10796
11905
|
}
|
|
10797
|
-
|
|
10798
|
-
if (!
|
|
10799
|
-
const payload = JSON.stringify({ op: 1, d:
|
|
10800
|
-
|
|
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);
|
|
10801
11910
|
}, intervalMs);
|
|
10802
11911
|
};
|
|
10803
11912
|
const sendIdentify = (token) => {
|
|
10804
|
-
if (!
|
|
11913
|
+
if (!nextConn.socket || nextConn.socket.readyState !== WebSocket.OPEN) return;
|
|
10805
11914
|
const payload = {
|
|
10806
11915
|
op: 2,
|
|
10807
11916
|
d: {
|
|
@@ -10810,10 +11919,10 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10810
11919
|
shard: [0, 1]
|
|
10811
11920
|
}
|
|
10812
11921
|
};
|
|
10813
|
-
|
|
11922
|
+
nextConn.socket.send(JSON.stringify(payload));
|
|
10814
11923
|
};
|
|
10815
11924
|
const sendResume = (token, session, seq) => {
|
|
10816
|
-
if (!
|
|
11925
|
+
if (!nextConn.socket || nextConn.socket.readyState !== WebSocket.OPEN) return;
|
|
10817
11926
|
const payload = {
|
|
10818
11927
|
op: 6,
|
|
10819
11928
|
d: {
|
|
@@ -10822,11 +11931,14 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10822
11931
|
seq
|
|
10823
11932
|
}
|
|
10824
11933
|
};
|
|
10825
|
-
|
|
11934
|
+
nextConn.socket.send(JSON.stringify(payload));
|
|
10826
11935
|
};
|
|
10827
|
-
const handleGatewayPayload = async (payload) => {
|
|
11936
|
+
const handleGatewayPayload = async (payload, activeSocket) => {
|
|
11937
|
+
if (stopped || nextConn.socket !== activeSocket) {
|
|
11938
|
+
return;
|
|
11939
|
+
}
|
|
10828
11940
|
if (typeof payload.s === "number") {
|
|
10829
|
-
|
|
11941
|
+
nextConn.lastSeq = payload.s;
|
|
10830
11942
|
}
|
|
10831
11943
|
switch (payload.op) {
|
|
10832
11944
|
case 10: {
|
|
@@ -10834,8 +11946,11 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10834
11946
|
const interval = hello?.heartbeat_interval ?? 3e4;
|
|
10835
11947
|
startHeartbeat(interval);
|
|
10836
11948
|
const token = await getAccessToken(qqCfg.appId, qqCfg.clientSecret);
|
|
10837
|
-
if (
|
|
10838
|
-
|
|
11949
|
+
if (stopped || nextConn.socket !== activeSocket) {
|
|
11950
|
+
return;
|
|
11951
|
+
}
|
|
11952
|
+
if (nextConn.sessionId && typeof nextConn.lastSeq === "number") {
|
|
11953
|
+
sendResume(token, nextConn.sessionId, nextConn.lastSeq);
|
|
10839
11954
|
} else {
|
|
10840
11955
|
sendIdentify(token);
|
|
10841
11956
|
}
|
|
@@ -10845,14 +11960,18 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10845
11960
|
setStatus?.({ lastEventAt: Date.now() });
|
|
10846
11961
|
return;
|
|
10847
11962
|
case 7:
|
|
10848
|
-
cleanupSocket(
|
|
11963
|
+
if (!cleanupSocket(nextConn, activeSocket)) {
|
|
11964
|
+
return;
|
|
11965
|
+
}
|
|
10849
11966
|
scheduleReconnect("server requested reconnect");
|
|
10850
11967
|
return;
|
|
10851
11968
|
case 9:
|
|
10852
|
-
|
|
10853
|
-
|
|
11969
|
+
nextConn.sessionId = null;
|
|
11970
|
+
nextConn.lastSeq = null;
|
|
10854
11971
|
clearTokenCache(qqCfg.appId);
|
|
10855
|
-
cleanupSocket(
|
|
11972
|
+
if (!cleanupSocket(nextConn, activeSocket)) {
|
|
11973
|
+
return;
|
|
11974
|
+
}
|
|
10856
11975
|
scheduleReconnect("invalid session");
|
|
10857
11976
|
return;
|
|
10858
11977
|
case 0: {
|
|
@@ -10860,14 +11979,14 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10860
11979
|
if (eventType === "READY") {
|
|
10861
11980
|
const ready = payload.d;
|
|
10862
11981
|
if (ready?.session_id) {
|
|
10863
|
-
|
|
11982
|
+
nextConn.sessionId = ready.session_id;
|
|
10864
11983
|
}
|
|
10865
|
-
|
|
11984
|
+
nextConn.reconnectAttempt = 0;
|
|
10866
11985
|
logger.info("gateway ready");
|
|
10867
11986
|
return;
|
|
10868
11987
|
}
|
|
10869
11988
|
if (eventType === "RESUMED") {
|
|
10870
|
-
|
|
11989
|
+
nextConn.reconnectAttempt = 0;
|
|
10871
11990
|
logger.info("gateway resumed");
|
|
10872
11991
|
return;
|
|
10873
11992
|
}
|
|
@@ -10888,15 +12007,21 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10888
12007
|
}
|
|
10889
12008
|
};
|
|
10890
12009
|
const connect = async () => {
|
|
10891
|
-
if (stopped ||
|
|
10892
|
-
|
|
12010
|
+
if (stopped || nextConn.connecting) return;
|
|
12011
|
+
nextConn.connecting = true;
|
|
10893
12012
|
try {
|
|
10894
|
-
cleanupSocket(
|
|
12013
|
+
cleanupSocket(nextConn);
|
|
10895
12014
|
const token = await getAccessToken(qqCfg.appId, qqCfg.clientSecret);
|
|
12015
|
+
if (stopped) return;
|
|
10896
12016
|
const gatewayUrl = await getGatewayUrl(token);
|
|
12017
|
+
if (stopped) return;
|
|
10897
12018
|
logger.info(`connecting gateway: ${gatewayUrl}`);
|
|
10898
12019
|
const ws = new WebSocket(gatewayUrl);
|
|
10899
|
-
|
|
12020
|
+
nextConn.socket = ws;
|
|
12021
|
+
if (stopped) {
|
|
12022
|
+
cleanupSocket(nextConn, ws);
|
|
12023
|
+
return;
|
|
12024
|
+
}
|
|
10900
12025
|
ws.on("open", () => {
|
|
10901
12026
|
logger.info("gateway socket opened");
|
|
10902
12027
|
});
|
|
@@ -10909,24 +12034,29 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10909
12034
|
logger.warn(`failed to parse gateway payload: ${String(err)}`);
|
|
10910
12035
|
return;
|
|
10911
12036
|
}
|
|
10912
|
-
void handleGatewayPayload(payload).catch((err) => {
|
|
12037
|
+
void handleGatewayPayload(payload, ws).catch((err) => {
|
|
10913
12038
|
logger.error(`gateway dispatch error: ${String(err)}`);
|
|
10914
12039
|
});
|
|
10915
12040
|
});
|
|
10916
12041
|
ws.on("close", (code, reason) => {
|
|
12042
|
+
if (!cleanupSocket(nextConn, ws)) {
|
|
12043
|
+
return;
|
|
12044
|
+
}
|
|
10917
12045
|
logger.warn(`gateway socket closed (${code}) ${String(reason)}`);
|
|
10918
|
-
cleanupSocket(conn);
|
|
10919
12046
|
scheduleReconnect("socket closed");
|
|
10920
12047
|
});
|
|
10921
12048
|
ws.on("error", (err) => {
|
|
12049
|
+
if (stopped || nextConn.socket !== ws) {
|
|
12050
|
+
return;
|
|
12051
|
+
}
|
|
10922
12052
|
logger.error(`gateway socket error: ${String(err)}`);
|
|
10923
12053
|
});
|
|
10924
12054
|
} catch (err) {
|
|
10925
12055
|
logger.error(`gateway connect failed: ${formatGatewayConnectError(err)}`);
|
|
10926
|
-
cleanupSocket(
|
|
12056
|
+
cleanupSocket(nextConn);
|
|
10927
12057
|
scheduleReconnect("connect failed");
|
|
10928
12058
|
} finally {
|
|
10929
|
-
|
|
12059
|
+
nextConn.connecting = false;
|
|
10930
12060
|
}
|
|
10931
12061
|
};
|
|
10932
12062
|
if (abortSignal?.aborted) {
|
|
@@ -10936,7 +12066,7 @@ async function monitorQQBotProvider(opts = {}) {
|
|
|
10936
12066
|
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
10937
12067
|
void connect();
|
|
10938
12068
|
});
|
|
10939
|
-
return
|
|
12069
|
+
return nextConn.promise;
|
|
10940
12070
|
}
|
|
10941
12071
|
function stopQQBotMonitorForAccount(accountId = DEFAULT_ACCOUNT_ID) {
|
|
10942
12072
|
const conn = activeConnections.get(accountId);
|
|
@@ -10973,7 +12103,8 @@ function resolveQQBotAccount(params) {
|
|
|
10973
12103
|
configured,
|
|
10974
12104
|
appId: credentials?.appId,
|
|
10975
12105
|
markdownSupport: merged.markdownSupport ?? true,
|
|
10976
|
-
c2cMarkdownDeliveryMode: merged.c2cMarkdownDeliveryMode ?? "proactive-table-only"
|
|
12106
|
+
c2cMarkdownDeliveryMode: merged.c2cMarkdownDeliveryMode ?? "proactive-table-only",
|
|
12107
|
+
c2cMarkdownChunkStrategy: merged.c2cMarkdownChunkStrategy ?? "markdown-block"
|
|
10977
12108
|
};
|
|
10978
12109
|
}
|
|
10979
12110
|
var qqbotPlugin = {
|
|
@@ -11051,6 +12182,10 @@ var qqbotPlugin = {
|
|
|
11051
12182
|
defaultAccount: { type: "string" },
|
|
11052
12183
|
appId: { type: ["string", "number"] },
|
|
11053
12184
|
clientSecret: { type: "string" },
|
|
12185
|
+
displayAliases: {
|
|
12186
|
+
type: "object",
|
|
12187
|
+
additionalProperties: { type: "string" }
|
|
12188
|
+
},
|
|
11054
12189
|
asr: {
|
|
11055
12190
|
type: "object",
|
|
11056
12191
|
additionalProperties: false,
|
|
@@ -11066,6 +12201,10 @@ var qqbotPlugin = {
|
|
|
11066
12201
|
type: "string",
|
|
11067
12202
|
enum: ["passive", "proactive-table-only", "proactive-all"]
|
|
11068
12203
|
},
|
|
12204
|
+
c2cMarkdownChunkStrategy: {
|
|
12205
|
+
type: "string",
|
|
12206
|
+
enum: ["markdown-block", "length"]
|
|
12207
|
+
},
|
|
11069
12208
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
11070
12209
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
11071
12210
|
requireMention: { type: "boolean" },
|
|
@@ -11096,6 +12235,10 @@ var qqbotPlugin = {
|
|
|
11096
12235
|
enabled: { type: "boolean" },
|
|
11097
12236
|
appId: { type: ["string", "number"] },
|
|
11098
12237
|
clientSecret: { type: "string" },
|
|
12238
|
+
displayAliases: {
|
|
12239
|
+
type: "object",
|
|
12240
|
+
additionalProperties: { type: "string" }
|
|
12241
|
+
},
|
|
11099
12242
|
asr: {
|
|
11100
12243
|
type: "object",
|
|
11101
12244
|
additionalProperties: false,
|
|
@@ -11111,6 +12254,10 @@ var qqbotPlugin = {
|
|
|
11111
12254
|
type: "string",
|
|
11112
12255
|
enum: ["passive", "proactive-table-only", "proactive-all"]
|
|
11113
12256
|
},
|
|
12257
|
+
c2cMarkdownChunkStrategy: {
|
|
12258
|
+
type: "string",
|
|
12259
|
+
enum: ["markdown-block", "length"]
|
|
12260
|
+
},
|
|
11114
12261
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
11115
12262
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
11116
12263
|
requireMention: { type: "boolean" },
|
|
@@ -11262,7 +12409,9 @@ var qqbotPlugin = {
|
|
|
11262
12409
|
ctx.log?.info(`[qqbot] starting gateway for account ${ctx.accountId}`);
|
|
11263
12410
|
if (ctx.runtime) {
|
|
11264
12411
|
const candidate = ctx.runtime;
|
|
11265
|
-
|
|
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) {
|
|
11266
12415
|
setQQBotRuntime(ctx.runtime);
|
|
11267
12416
|
}
|
|
11268
12417
|
}
|
|
@@ -11298,6 +12447,10 @@ var plugin = {
|
|
|
11298
12447
|
defaultAccount: { type: "string" },
|
|
11299
12448
|
appId: { type: ["string", "number"] },
|
|
11300
12449
|
clientSecret: { type: "string" },
|
|
12450
|
+
displayAliases: {
|
|
12451
|
+
type: "object",
|
|
12452
|
+
additionalProperties: { type: "string" }
|
|
12453
|
+
},
|
|
11301
12454
|
asr: {
|
|
11302
12455
|
type: "object",
|
|
11303
12456
|
additionalProperties: false,
|
|
@@ -11313,6 +12466,10 @@ var plugin = {
|
|
|
11313
12466
|
type: "string",
|
|
11314
12467
|
enum: ["passive", "proactive-table-only", "proactive-all"]
|
|
11315
12468
|
},
|
|
12469
|
+
c2cMarkdownChunkStrategy: {
|
|
12470
|
+
type: "string",
|
|
12471
|
+
enum: ["markdown-block", "length"]
|
|
12472
|
+
},
|
|
11316
12473
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
11317
12474
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
11318
12475
|
requireMention: { type: "boolean" },
|
|
@@ -11343,6 +12500,10 @@ var plugin = {
|
|
|
11343
12500
|
enabled: { type: "boolean" },
|
|
11344
12501
|
appId: { type: ["string", "number"] },
|
|
11345
12502
|
clientSecret: { type: "string" },
|
|
12503
|
+
displayAliases: {
|
|
12504
|
+
type: "object",
|
|
12505
|
+
additionalProperties: { type: "string" }
|
|
12506
|
+
},
|
|
11346
12507
|
asr: {
|
|
11347
12508
|
type: "object",
|
|
11348
12509
|
additionalProperties: false,
|
|
@@ -11358,6 +12519,10 @@ var plugin = {
|
|
|
11358
12519
|
type: "string",
|
|
11359
12520
|
enum: ["passive", "proactive-table-only", "proactive-all"]
|
|
11360
12521
|
},
|
|
12522
|
+
c2cMarkdownChunkStrategy: {
|
|
12523
|
+
type: "string",
|
|
12524
|
+
enum: ["markdown-block", "length"]
|
|
12525
|
+
},
|
|
11361
12526
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
11362
12527
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
11363
12528
|
requireMention: { type: "boolean" },
|