@openclaw-china/qqbot 2026.3.16 → 2026.3.19
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 +63 -0
- package/dist/index.js +575 -58
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +12 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -4228,6 +4228,11 @@ var displayAliasesSchema = external_exports.record(
|
|
|
4228
4228
|
).optional();
|
|
4229
4229
|
var QQBotC2CMarkdownDeliveryModeSchema = external_exports.enum(["passive", "proactive-table-only", "proactive-all"]).optional().default("proactive-table-only");
|
|
4230
4230
|
var QQBotC2CMarkdownChunkStrategySchema = external_exports.enum(["markdown-block", "length"]).optional().default("markdown-block");
|
|
4231
|
+
var QQBotTypingHeartbeatModeSchema = external_exports.enum(["none", "idle", "always"]).optional().default("idle");
|
|
4232
|
+
var DEFAULT_QQBOT_TYPING_HEARTBEAT_MODE = "idle";
|
|
4233
|
+
var DEFAULT_QQBOT_TYPING_HEARTBEAT_INTERVAL_MS = 5e3;
|
|
4234
|
+
var DEFAULT_QQBOT_TYPING_INPUT_SECONDS = 60;
|
|
4235
|
+
var DEFAULT_QQBOT_C2C_MARKDOWN_SAFE_CHUNK_BYTE_LIMIT = 1200;
|
|
4231
4236
|
var QQBotAccountSchema = external_exports.object({
|
|
4232
4237
|
name: external_exports.string().optional(),
|
|
4233
4238
|
enabled: external_exports.boolean().optional(),
|
|
@@ -4243,6 +4248,14 @@ var QQBotAccountSchema = external_exports.object({
|
|
|
4243
4248
|
markdownSupport: external_exports.boolean().optional().default(true),
|
|
4244
4249
|
c2cMarkdownDeliveryMode: QQBotC2CMarkdownDeliveryModeSchema,
|
|
4245
4250
|
c2cMarkdownChunkStrategy: QQBotC2CMarkdownChunkStrategySchema,
|
|
4251
|
+
c2cMarkdownSafeChunkByteLimit: external_exports.number().int().positive().optional(),
|
|
4252
|
+
typingHeartbeatMode: QQBotTypingHeartbeatModeSchema,
|
|
4253
|
+
typingHeartbeatIntervalMs: external_exports.number().int().positive().optional().default(
|
|
4254
|
+
DEFAULT_QQBOT_TYPING_HEARTBEAT_INTERVAL_MS
|
|
4255
|
+
),
|
|
4256
|
+
typingInputSeconds: external_exports.number().int().positive().optional().default(
|
|
4257
|
+
DEFAULT_QQBOT_TYPING_INPUT_SECONDS
|
|
4258
|
+
),
|
|
4246
4259
|
dmPolicy: external_exports.enum(["open", "pairing", "allowlist"]).optional().default("open"),
|
|
4247
4260
|
groupPolicy: external_exports.enum(["open", "allowlist", "disabled"]).optional().default("open"),
|
|
4248
4261
|
requireMention: external_exports.boolean().optional().default(true),
|
|
@@ -4292,6 +4305,19 @@ function resolveInboundMediaKeepDays(config) {
|
|
|
4292
4305
|
function resolveQQBotAutoSendLocalPathMedia(config) {
|
|
4293
4306
|
return config?.autoSendLocalPathMedia ?? true;
|
|
4294
4307
|
}
|
|
4308
|
+
function resolveQQBotC2CMarkdownSafeChunkByteLimit(config) {
|
|
4309
|
+
const value = config?.c2cMarkdownSafeChunkByteLimit;
|
|
4310
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
|
|
4311
|
+
}
|
|
4312
|
+
function resolveQQBotTypingHeartbeatMode(config) {
|
|
4313
|
+
return config?.typingHeartbeatMode ?? DEFAULT_QQBOT_TYPING_HEARTBEAT_MODE;
|
|
4314
|
+
}
|
|
4315
|
+
function resolveQQBotTypingHeartbeatIntervalMs(config) {
|
|
4316
|
+
return config?.typingHeartbeatIntervalMs ?? DEFAULT_QQBOT_TYPING_HEARTBEAT_INTERVAL_MS;
|
|
4317
|
+
}
|
|
4318
|
+
function resolveQQBotTypingInputSeconds(config) {
|
|
4319
|
+
return config?.typingInputSeconds ?? DEFAULT_QQBOT_TYPING_INPUT_SECONDS;
|
|
4320
|
+
}
|
|
4295
4321
|
function resolveInboundMediaTempDir() {
|
|
4296
4322
|
return DEFAULT_INBOUND_MEDIA_TEMP_DIR;
|
|
4297
4323
|
}
|
|
@@ -6548,6 +6574,7 @@ var CHANNEL_ORDER = [
|
|
|
6548
6574
|
"wecom",
|
|
6549
6575
|
"wecom-app",
|
|
6550
6576
|
"wecom-kf",
|
|
6577
|
+
"wechat-mp",
|
|
6551
6578
|
"feishu-china"
|
|
6552
6579
|
];
|
|
6553
6580
|
var CHANNEL_DISPLAY_LABELS = {
|
|
@@ -6556,6 +6583,7 @@ var CHANNEL_DISPLAY_LABELS = {
|
|
|
6556
6583
|
wecom: "WeCom\uFF08\u4F01\u4E1A\u5FAE\u4FE1-\u667A\u80FD\u673A\u5668\u4EBA\uFF09",
|
|
6557
6584
|
"wecom-app": "WeCom App\uFF08\u81EA\u5EFA\u5E94\u7528-\u53EF\u63A5\u5165\u5FAE\u4FE1\uFF09",
|
|
6558
6585
|
"wecom-kf": "WeCom KF\uFF08\u5FAE\u4FE1\u5BA2\u670D\uFF09",
|
|
6586
|
+
"wechat-mp": "WeChat MP\uFF08\u5FAE\u4FE1\u516C\u4F17\u53F7\uFF09",
|
|
6559
6587
|
qqbot: "QQBot\uFF08QQ \u673A\u5668\u4EBA\uFF09"
|
|
6560
6588
|
};
|
|
6561
6589
|
var CHANNEL_GUIDE_LINKS = {
|
|
@@ -6564,6 +6592,7 @@ var CHANNEL_GUIDE_LINKS = {
|
|
|
6564
6592
|
wecom: `${GUIDES_BASE}/wecom/configuration.md`,
|
|
6565
6593
|
"wecom-app": `${GUIDES_BASE}/wecom-app/configuration.md`,
|
|
6566
6594
|
"wecom-kf": "https://github.com/BytePioneer-AI/openclaw-china/blob/main/extensions/wecom-kf/README.md",
|
|
6595
|
+
"wechat-mp": `${GUIDES_BASE}/wechat-mp/configuration.md`,
|
|
6567
6596
|
qqbot: `${GUIDES_BASE}/qqbot/configuration.md`
|
|
6568
6597
|
};
|
|
6569
6598
|
var CHINA_CLI_STATE_KEY = /* @__PURE__ */ Symbol.for("@openclaw-china/china-cli-state");
|
|
@@ -6773,7 +6802,9 @@ function isChannelConfigured(cfg, channelId) {
|
|
|
6773
6802
|
case "wecom-app":
|
|
6774
6803
|
return hasTokenPair(channelCfg);
|
|
6775
6804
|
case "wecom-kf":
|
|
6776
|
-
return hasNonEmptyString(channelCfg.corpId) && hasNonEmptyString(channelCfg.
|
|
6805
|
+
return hasNonEmptyString(channelCfg.corpId) && hasNonEmptyString(channelCfg.token) && hasNonEmptyString(channelCfg.encodingAESKey);
|
|
6806
|
+
case "wechat-mp":
|
|
6807
|
+
return hasNonEmptyString(channelCfg.appId) && hasNonEmptyString(channelCfg.token);
|
|
6777
6808
|
default:
|
|
6778
6809
|
return false;
|
|
6779
6810
|
}
|
|
@@ -7034,6 +7065,15 @@ async function configureWecomKf(prompter, cfg) {
|
|
|
7034
7065
|
section("\u914D\u7F6E WeCom KF\uFF08\u5FAE\u4FE1\u5BA2\u670D\uFF09");
|
|
7035
7066
|
showGuideLink("wecom-kf");
|
|
7036
7067
|
const existing = getChannelConfig(cfg, "wecom-kf");
|
|
7068
|
+
Ve(
|
|
7069
|
+
[
|
|
7070
|
+
"\u5411\u5BFC\u987A\u5E8F\uFF1AwebhookPath / token / encodingAESKey / corpId / open_kfid / corpSecret",
|
|
7071
|
+
"\u57FA\u7840\u5FC5\u586B\uFF1AcorpId / token / encodingAESKey / open_kfid",
|
|
7072
|
+
"corpSecret \u4F1A\u4F5C\u4E3A\u6700\u540E\u4E00\u4E2A\u53C2\u6570\u8BE2\u95EE\uFF1B\u9996\u6B21\u63A5\u5165\u53EF\u5148\u7559\u7A7A\uFF0C\u5F85\u56DE\u8C03 URL \u6821\u9A8C\u901A\u8FC7\u5E76\u70B9\u51FB\u201C\u5F00\u59CB\u4F7F\u7528\u201D\u540E\u518D\u8865",
|
|
7073
|
+
"webhookPath \u9ED8\u8BA4\u503C\uFF1A/wecom-kf"
|
|
7074
|
+
].join("\n"),
|
|
7075
|
+
"\u53C2\u6570\u8BF4\u660E"
|
|
7076
|
+
);
|
|
7037
7077
|
const webhookPath = await prompter.askText({
|
|
7038
7078
|
label: "Webhook \u8DEF\u5F84\uFF08\u9ED8\u8BA4 /wecom-kf\uFF09",
|
|
7039
7079
|
defaultValue: toTrimmedString2(existing.webhookPath) ?? "/wecom-kf",
|
|
@@ -7054,19 +7094,14 @@ async function configureWecomKf(prompter, cfg) {
|
|
|
7054
7094
|
defaultValue: toTrimmedString2(existing.corpId),
|
|
7055
7095
|
required: true
|
|
7056
7096
|
});
|
|
7057
|
-
const corpSecret = await prompter.askSecret({
|
|
7058
|
-
label: "\u5FAE\u4FE1\u5BA2\u670D Secret",
|
|
7059
|
-
existingValue: toTrimmedString2(existing.corpSecret),
|
|
7060
|
-
required: true
|
|
7061
|
-
});
|
|
7062
7097
|
const openKfId = await prompter.askText({
|
|
7063
7098
|
label: "open_kfid",
|
|
7064
7099
|
defaultValue: toTrimmedString2(existing.openKfId),
|
|
7065
7100
|
required: true
|
|
7066
7101
|
});
|
|
7067
|
-
const
|
|
7068
|
-
label: "\
|
|
7069
|
-
|
|
7102
|
+
const corpSecret = await prompter.askSecret({
|
|
7103
|
+
label: "\u5FAE\u4FE1\u5BA2\u670D Secret\uFF08\u6700\u540E\u586B\u5199\uFF1B\u9996\u6B21\u63A5\u5165\u53EF\u5148\u7559\u7A7A\uFF09",
|
|
7104
|
+
existingValue: toTrimmedString2(existing.corpSecret),
|
|
7070
7105
|
required: false
|
|
7071
7106
|
});
|
|
7072
7107
|
return mergeChannelConfig(cfg, "wecom-kf", {
|
|
@@ -7074,8 +7109,72 @@ async function configureWecomKf(prompter, cfg) {
|
|
|
7074
7109
|
token,
|
|
7075
7110
|
encodingAESKey,
|
|
7076
7111
|
corpId,
|
|
7077
|
-
corpSecret,
|
|
7078
7112
|
openKfId,
|
|
7113
|
+
corpSecret: corpSecret || void 0
|
|
7114
|
+
});
|
|
7115
|
+
}
|
|
7116
|
+
async function configureWechatMp(prompter, cfg) {
|
|
7117
|
+
section("\u914D\u7F6E WeChat MP\uFF08\u5FAE\u4FE1\u516C\u4F17\u53F7\uFF09");
|
|
7118
|
+
showGuideLink("wechat-mp");
|
|
7119
|
+
const existing = getChannelConfig(cfg, "wechat-mp");
|
|
7120
|
+
const webhookPath = await prompter.askText({
|
|
7121
|
+
label: "Webhook \u8DEF\u5F84\uFF08\u9ED8\u8BA4 /wechat-mp\uFF09",
|
|
7122
|
+
defaultValue: toTrimmedString2(existing.webhookPath) ?? "/wechat-mp",
|
|
7123
|
+
required: true
|
|
7124
|
+
});
|
|
7125
|
+
const appId = await prompter.askText({
|
|
7126
|
+
label: "\u516C\u4F17\u53F7 appId",
|
|
7127
|
+
defaultValue: toTrimmedString2(existing.appId),
|
|
7128
|
+
required: true
|
|
7129
|
+
});
|
|
7130
|
+
const appSecret = await prompter.askSecret({
|
|
7131
|
+
label: "\u516C\u4F17\u53F7 appSecret\uFF08\u4E3B\u52A8\u53D1\u9001\u9700\u8981\uFF09",
|
|
7132
|
+
existingValue: toTrimmedString2(existing.appSecret),
|
|
7133
|
+
required: false
|
|
7134
|
+
});
|
|
7135
|
+
const token = await prompter.askSecret({
|
|
7136
|
+
label: "\u670D\u52A1\u5668\u914D\u7F6E token",
|
|
7137
|
+
existingValue: toTrimmedString2(existing.token),
|
|
7138
|
+
required: true
|
|
7139
|
+
});
|
|
7140
|
+
const messageMode = await prompter.askSelect(
|
|
7141
|
+
"\u6D88\u606F\u52A0\u89E3\u5BC6\u6A21\u5F0F",
|
|
7142
|
+
[
|
|
7143
|
+
{ value: "plain", label: "plain\uFF08\u660E\u6587\uFF09" },
|
|
7144
|
+
{ value: "safe", label: "safe\uFF08\u5B89\u5168\u6A21\u5F0F\uFF09" },
|
|
7145
|
+
{ value: "compat", label: "compat\uFF08\u517C\u5BB9\u6A21\u5F0F\uFF09" }
|
|
7146
|
+
],
|
|
7147
|
+
toTrimmedString2(existing.messageMode) ?? "safe"
|
|
7148
|
+
);
|
|
7149
|
+
let encodingAESKey = toTrimmedString2(existing.encodingAESKey);
|
|
7150
|
+
if (messageMode !== "plain") {
|
|
7151
|
+
encodingAESKey = await prompter.askSecret({
|
|
7152
|
+
label: "EncodingAESKey\uFF08safe/compat \u5FC5\u586B\uFF09",
|
|
7153
|
+
existingValue: encodingAESKey,
|
|
7154
|
+
required: true
|
|
7155
|
+
});
|
|
7156
|
+
}
|
|
7157
|
+
const replyMode = await prompter.askSelect(
|
|
7158
|
+
"\u56DE\u590D\u6A21\u5F0F",
|
|
7159
|
+
[
|
|
7160
|
+
{ value: "passive", label: "passive\uFF085 \u79D2\u5185\u88AB\u52A8\u56DE\u590D\uFF09" },
|
|
7161
|
+
{ value: "active", label: "active\uFF08\u5BA2\u670D\u6D88\u606F\u4E3B\u52A8\u53D1\u9001\uFF09" }
|
|
7162
|
+
],
|
|
7163
|
+
toTrimmedString2(existing.replyMode) ?? "passive"
|
|
7164
|
+
);
|
|
7165
|
+
const welcomeText = await prompter.askText({
|
|
7166
|
+
label: "\u6B22\u8FCE\u8BED\uFF08\u53EF\u9009\uFF09",
|
|
7167
|
+
defaultValue: toTrimmedString2(existing.welcomeText),
|
|
7168
|
+
required: false
|
|
7169
|
+
});
|
|
7170
|
+
return mergeChannelConfig(cfg, "wechat-mp", {
|
|
7171
|
+
webhookPath,
|
|
7172
|
+
appId,
|
|
7173
|
+
appSecret: appSecret || void 0,
|
|
7174
|
+
token,
|
|
7175
|
+
encodingAESKey: messageMode === "plain" ? void 0 : encodingAESKey,
|
|
7176
|
+
messageMode,
|
|
7177
|
+
replyMode,
|
|
7079
7178
|
welcomeText: welcomeText || void 0
|
|
7080
7179
|
});
|
|
7081
7180
|
}
|
|
@@ -7137,6 +7236,8 @@ async function configureSingleChannel(channel, prompter, cfg) {
|
|
|
7137
7236
|
return configureWecomApp(prompter, cfg);
|
|
7138
7237
|
case "wecom-kf":
|
|
7139
7238
|
return configureWecomKf(prompter, cfg);
|
|
7239
|
+
case "wechat-mp":
|
|
7240
|
+
return configureWechatMp(prompter, cfg);
|
|
7140
7241
|
case "qqbot":
|
|
7141
7242
|
return configureQQBot(prompter, cfg);
|
|
7142
7243
|
default:
|
|
@@ -7278,6 +7379,7 @@ var SUPPORTED_CHANNELS = [
|
|
|
7278
7379
|
"wecom",
|
|
7279
7380
|
"wecom-app",
|
|
7280
7381
|
"wecom-kf",
|
|
7382
|
+
"wechat-mp",
|
|
7281
7383
|
"qqbot"
|
|
7282
7384
|
];
|
|
7283
7385
|
var CHINA_INSTALL_HINT_SHOWN_KEY = /* @__PURE__ */ Symbol.for("@openclaw-china/china-install-hint-shown");
|
|
@@ -9623,6 +9725,37 @@ function startLongTaskNoticeTimer(params) {
|
|
|
9623
9725
|
}
|
|
9624
9726
|
};
|
|
9625
9727
|
}
|
|
9728
|
+
function startQQBotTypingHeartbeat(params) {
|
|
9729
|
+
const { intervalMs, renew, shouldRenew } = params;
|
|
9730
|
+
let stopped = false;
|
|
9731
|
+
let timer = null;
|
|
9732
|
+
let renewalInFlight = false;
|
|
9733
|
+
const clear = () => {
|
|
9734
|
+
if (!timer) return;
|
|
9735
|
+
clearInterval(timer);
|
|
9736
|
+
timer = null;
|
|
9737
|
+
};
|
|
9738
|
+
const stop = () => {
|
|
9739
|
+
if (stopped) return;
|
|
9740
|
+
stopped = true;
|
|
9741
|
+
clear();
|
|
9742
|
+
};
|
|
9743
|
+
if (intervalMs > 0) {
|
|
9744
|
+
timer = setInterval(() => {
|
|
9745
|
+
if (stopped || renewalInFlight) return;
|
|
9746
|
+
if (shouldRenew && !shouldRenew()) return;
|
|
9747
|
+
renewalInFlight = true;
|
|
9748
|
+
void renew().catch(() => void 0).finally(() => {
|
|
9749
|
+
renewalInFlight = false;
|
|
9750
|
+
});
|
|
9751
|
+
}, intervalMs);
|
|
9752
|
+
timer.unref?.();
|
|
9753
|
+
}
|
|
9754
|
+
return {
|
|
9755
|
+
stop,
|
|
9756
|
+
dispose: stop
|
|
9757
|
+
};
|
|
9758
|
+
}
|
|
9626
9759
|
function isHttpUrl3(value) {
|
|
9627
9760
|
return /^https?:\/\//i.test(value);
|
|
9628
9761
|
}
|
|
@@ -10085,7 +10218,7 @@ function extractLocalMediaFromText(params) {
|
|
|
10085
10218
|
const MARKDOWN_LINKED_IMAGE_RE2 = /\[!\[([^\]]*)\]\(([^)]+)\)\]\(([^)]+)\)/g;
|
|
10086
10219
|
const MARKDOWN_IMAGE_RE3 = /!\[([^\]]*)\]\(([^)]+)\)/g;
|
|
10087
10220
|
const MARKDOWN_LINK_RE2 = /\[([^\]]*)\]\(([^)]+)\)/g;
|
|
10088
|
-
const BARE_LOCAL_MEDIA_PATH_RE = /`?((?:\/(?:tmp|var|private|Users|home|root)\/[^\s`'",)]+|[A-Za-z]:[\\/][^\s`'",)]+)\.(?:png|jpg|jpeg|gif|bmp|webp|svg|ico|mp3|wav|ogg|m4a|amr|flac|aac|wma|mp4|mov|avi|mkv|webm|flv|wmv|m4v))`?/gi;
|
|
10221
|
+
const BARE_LOCAL_MEDIA_PATH_RE = /`?((?:\/(?:tmp|var|private|Users|home|root)\/[^\s`'",)]+|\/mnt\/[A-Za-z]\/[^\s`'",)]+|[A-Za-z]:[\\/][^\s`'",)]+)\.(?:png|jpg|jpeg|gif|bmp|webp|svg|ico|mp3|wav|ogg|m4a|amr|flac|aac|wma|mp4|mov|avi|mkv|webm|flv|wmv|m4v))`?/gi;
|
|
10089
10222
|
const collectLocalRichMedia = (rawValue, allowedTypes) => {
|
|
10090
10223
|
const candidate = stripTitleFromUrl(rawValue.trim());
|
|
10091
10224
|
if (!candidate || !isLocalReference(candidate)) {
|
|
@@ -10194,6 +10327,10 @@ var MARKDOWN_INLINE_STRUCTURE_RE = /(?:\*\*[^*\n]+\*\*|__[^_\n]+__|`[^`\n]+`|~~[
|
|
|
10194
10327
|
var MARKDOWN_BOUNDARY_GUARD_RE = /[`*_~|]/;
|
|
10195
10328
|
var EXPLICIT_MARKDOWN_FENCE_RE = /(^|\n)(`{3,}|~{3,})\s*(?:markdown|md)\s*\n([\s\S]*?)\n\2(?=\n|$)/gi;
|
|
10196
10329
|
var GENERIC_MARKDOWN_FENCE_RE = /(^|\n)(`{3,}|~{3,})\s*\n([\s\S]*?)\n\2(?=\n|$)/g;
|
|
10330
|
+
var QQBOT_MARKDOWN_SOFT_LIMIT_THRESHOLD = 128;
|
|
10331
|
+
var QQBOT_MARKDOWN_SOFT_LIMIT_HEADROOM_MIN = 16;
|
|
10332
|
+
var QQBOT_MARKDOWN_SOFT_LIMIT_HEADROOM_MAX = 320;
|
|
10333
|
+
var QQBOT_MARKDOWN_SOFT_LIMIT_HEADROOM_RATIO = 0.18;
|
|
10197
10334
|
function extractFinalBlocks(text) {
|
|
10198
10335
|
const matches = Array.from(text.matchAll(FINAL_BLOCK_RE));
|
|
10199
10336
|
if (matches.length === 0) return void 0;
|
|
@@ -10333,6 +10470,72 @@ function appendQQBotBufferedText(bufferedTexts, nextText) {
|
|
|
10333
10470
|
}
|
|
10334
10471
|
return [...bufferedTexts, normalized];
|
|
10335
10472
|
}
|
|
10473
|
+
function resolveQQBotLastNonEmptyLine(text) {
|
|
10474
|
+
return text.split("\n").map((line) => line.trimEnd()).reverse().find((line) => line.trim().length > 0);
|
|
10475
|
+
}
|
|
10476
|
+
function resolveQQBotFirstNonEmptyLine(text) {
|
|
10477
|
+
return text.split("\n").map((line) => line.trimStart()).find((line) => line.trim().length > 0);
|
|
10478
|
+
}
|
|
10479
|
+
function resolveQQBotTrailingMarkdownTableColumnCount(text) {
|
|
10480
|
+
const lines = text.split("\n");
|
|
10481
|
+
for (let index = Math.max(0, lines.length - 2); index >= 0; index -= 1) {
|
|
10482
|
+
if (!isQQBotMarkdownTableStart(lines, index)) {
|
|
10483
|
+
continue;
|
|
10484
|
+
}
|
|
10485
|
+
const trailingLines = lines.slice(index + 2).filter((line) => line.trim().length > 0);
|
|
10486
|
+
if (trailingLines.length === 0 || trailingLines.every((line) => line.includes("|"))) {
|
|
10487
|
+
return parseQQBotMarkdownTableRowCells(lines[index] ?? "").length;
|
|
10488
|
+
}
|
|
10489
|
+
}
|
|
10490
|
+
return void 0;
|
|
10491
|
+
}
|
|
10492
|
+
function mergeQQBotBufferedTextSegments(current, next) {
|
|
10493
|
+
const currentTrimmed = current.trimEnd();
|
|
10494
|
+
const nextTrimmed = next.trimStart();
|
|
10495
|
+
if (!currentTrimmed) return nextTrimmed;
|
|
10496
|
+
if (!nextTrimmed) return currentTrimmed;
|
|
10497
|
+
if (currentTrimmed === nextTrimmed || currentTrimmed.includes(nextTrimmed)) {
|
|
10498
|
+
return currentTrimmed;
|
|
10499
|
+
}
|
|
10500
|
+
if (nextTrimmed.includes(currentTrimmed)) {
|
|
10501
|
+
return nextTrimmed;
|
|
10502
|
+
}
|
|
10503
|
+
const lastLine = resolveQQBotLastNonEmptyLine(currentTrimmed) ?? "";
|
|
10504
|
+
const firstLine = resolveQQBotFirstNonEmptyLine(nextTrimmed) ?? "";
|
|
10505
|
+
const trailingTableColumnCount = resolveQQBotTrailingMarkdownTableColumnCount(currentTrimmed);
|
|
10506
|
+
const lastLineLooksLikeTable = lastLine.includes("|");
|
|
10507
|
+
const firstLineLooksLikeTable = firstLine.startsWith("|");
|
|
10508
|
+
const firstLineContainsTableCell = firstLine.includes("|");
|
|
10509
|
+
const expectedPipeCount = typeof trailingTableColumnCount === "number" && trailingTableColumnCount > 0 ? trailingTableColumnCount + 1 : void 0;
|
|
10510
|
+
const lastLinePipeCount = (lastLine.match(/\|/g) ?? []).length;
|
|
10511
|
+
const firstLinePipeCount = (firstLine.match(/\|/g) ?? []).length;
|
|
10512
|
+
const sameRowJoiner = typeof expectedPipeCount === "number" && lastLinePipeCount + firstLinePipeCount < expectedPipeCount ? " | " : " ";
|
|
10513
|
+
const lastLineEndsInsideTableRow = Boolean(
|
|
10514
|
+
trailingTableColumnCount && lastLineLooksLikeTable && (!lastLine.trimEnd().endsWith("|") || typeof expectedPipeCount === "number" && lastLinePipeCount < expectedPipeCount)
|
|
10515
|
+
);
|
|
10516
|
+
if (lastLineEndsInsideTableRow && nextTrimmed.includes("|")) {
|
|
10517
|
+
return `${currentTrimmed}${sameRowJoiner}${nextTrimmed}`;
|
|
10518
|
+
}
|
|
10519
|
+
if (firstLineLooksLikeTable) {
|
|
10520
|
+
if (lastLineLooksLikeTable || hasQQBotMarkdownTable(currentTrimmed)) {
|
|
10521
|
+
return `${currentTrimmed}
|
|
10522
|
+
${nextTrimmed}`;
|
|
10523
|
+
}
|
|
10524
|
+
}
|
|
10525
|
+
if (trailingTableColumnCount && firstLineContainsTableCell) {
|
|
10526
|
+
return `${currentTrimmed}${sameRowJoiner}${nextTrimmed}`;
|
|
10527
|
+
}
|
|
10528
|
+
return joinQQBotMarkdownPieces([currentTrimmed, nextTrimmed]);
|
|
10529
|
+
}
|
|
10530
|
+
function combineQQBotBufferedText(bufferedTexts) {
|
|
10531
|
+
return bufferedTexts.reduce((combined, segment) => {
|
|
10532
|
+
const normalized = segment.trim();
|
|
10533
|
+
if (!normalized) {
|
|
10534
|
+
return combined;
|
|
10535
|
+
}
|
|
10536
|
+
return mergeQQBotBufferedTextSegments(combined, normalized);
|
|
10537
|
+
}, "");
|
|
10538
|
+
}
|
|
10336
10539
|
function normalizeQQBotRenderedMarkdown(text) {
|
|
10337
10540
|
if (!text.trim()) return "";
|
|
10338
10541
|
let next = text.trim();
|
|
@@ -10552,6 +10755,25 @@ function parseQQBotMarkdownBlocks(text) {
|
|
|
10552
10755
|
function hasQQBotBoundaryGuard(text) {
|
|
10553
10756
|
return MARKDOWN_BOUNDARY_GUARD_RE.test(text);
|
|
10554
10757
|
}
|
|
10758
|
+
function measureQQBotUtf8Length(text) {
|
|
10759
|
+
return Buffer.byteLength(text, "utf8");
|
|
10760
|
+
}
|
|
10761
|
+
function findQQBotIndexWithinUtf8Limit(text, limit) {
|
|
10762
|
+
if (limit <= 0 || !text) {
|
|
10763
|
+
return 0;
|
|
10764
|
+
}
|
|
10765
|
+
let totalBytes = 0;
|
|
10766
|
+
let index = 0;
|
|
10767
|
+
for (const char of text) {
|
|
10768
|
+
const charBytes = measureQQBotUtf8Length(char);
|
|
10769
|
+
if (totalBytes + charBytes > limit) {
|
|
10770
|
+
break;
|
|
10771
|
+
}
|
|
10772
|
+
totalBytes += charBytes;
|
|
10773
|
+
index += char.length;
|
|
10774
|
+
}
|
|
10775
|
+
return index;
|
|
10776
|
+
}
|
|
10555
10777
|
function isQQBotSafeMarkdownBoundary(text, index) {
|
|
10556
10778
|
const left = text.slice(Math.max(0, index - 3), index).replace(/\s+/g, "");
|
|
10557
10779
|
const right = text.slice(index, Math.min(text.length, index + 3)).replace(/\s+/g, "");
|
|
@@ -10560,13 +10782,14 @@ function isQQBotSafeMarkdownBoundary(text, index) {
|
|
|
10560
10782
|
return !hasQQBotBoundaryGuard(leftEdge) && !hasQQBotBoundaryGuard(rightEdge);
|
|
10561
10783
|
}
|
|
10562
10784
|
function findQQBotRegexBoundary(text, limit, pattern) {
|
|
10563
|
-
const
|
|
10785
|
+
const scopedIndex = findQQBotIndexWithinUtf8Limit(text, limit);
|
|
10786
|
+
const scopedText = text.slice(0, scopedIndex);
|
|
10564
10787
|
const regex = new RegExp(pattern.source, pattern.flags);
|
|
10565
10788
|
let match = regex.exec(scopedText);
|
|
10566
10789
|
let lastBoundary;
|
|
10567
10790
|
while (match) {
|
|
10568
10791
|
const boundary = match.index + match[0].length;
|
|
10569
|
-
if (boundary > 0 && boundary <= limit && isQQBotSafeMarkdownBoundary(text, boundary)) {
|
|
10792
|
+
if (boundary > 0 && measureQQBotUtf8Length(text.slice(0, boundary)) <= limit && isQQBotSafeMarkdownBoundary(text, boundary)) {
|
|
10570
10793
|
lastBoundary = boundary;
|
|
10571
10794
|
}
|
|
10572
10795
|
match = regex.exec(scopedText);
|
|
@@ -10574,13 +10797,14 @@ function findQQBotRegexBoundary(text, limit, pattern) {
|
|
|
10574
10797
|
return lastBoundary;
|
|
10575
10798
|
}
|
|
10576
10799
|
function findQQBotFallbackBoundary(text, limit) {
|
|
10577
|
-
const
|
|
10578
|
-
|
|
10800
|
+
const maxIndex = findQQBotIndexWithinUtf8Limit(text, limit);
|
|
10801
|
+
const minIndex = Math.max(1, maxIndex - 120);
|
|
10802
|
+
for (let index = maxIndex; index >= minIndex; index -= 1) {
|
|
10579
10803
|
if (isQQBotSafeMarkdownBoundary(text, index)) {
|
|
10580
10804
|
return index;
|
|
10581
10805
|
}
|
|
10582
10806
|
}
|
|
10583
|
-
return
|
|
10807
|
+
return maxIndex;
|
|
10584
10808
|
}
|
|
10585
10809
|
function findQQBotSafeSplitIndex(text, limit) {
|
|
10586
10810
|
const boundaryPatterns = [
|
|
@@ -10599,14 +10823,15 @@ function findQQBotSafeSplitIndex(text, limit) {
|
|
|
10599
10823
|
return findQQBotFallbackBoundary(text, limit);
|
|
10600
10824
|
}
|
|
10601
10825
|
function splitQQBotHardText(text, limit) {
|
|
10602
|
-
if (limit <= 0 || text
|
|
10826
|
+
if (limit <= 0 || measureQQBotUtf8Length(text) <= limit) {
|
|
10603
10827
|
return [text];
|
|
10604
10828
|
}
|
|
10605
10829
|
const chunks = [];
|
|
10606
10830
|
let remaining = text;
|
|
10607
|
-
while (remaining
|
|
10608
|
-
|
|
10609
|
-
remaining
|
|
10831
|
+
while (measureQQBotUtf8Length(remaining) > limit) {
|
|
10832
|
+
const nextIndex = Math.max(1, findQQBotIndexWithinUtf8Limit(remaining, limit));
|
|
10833
|
+
chunks.push(remaining.slice(0, nextIndex));
|
|
10834
|
+
remaining = remaining.slice(nextIndex);
|
|
10610
10835
|
}
|
|
10611
10836
|
if (remaining) {
|
|
10612
10837
|
chunks.push(remaining);
|
|
@@ -10614,14 +10839,14 @@ function splitQQBotHardText(text, limit) {
|
|
|
10614
10839
|
return chunks;
|
|
10615
10840
|
}
|
|
10616
10841
|
function splitQQBotTextSafely(text, limit, options) {
|
|
10617
|
-
if (limit <= 0 || text
|
|
10842
|
+
if (limit <= 0 || measureQQBotUtf8Length(text) <= limit) {
|
|
10618
10843
|
return [text];
|
|
10619
10844
|
}
|
|
10620
10845
|
const trimLeading = options?.trimLeading ?? true;
|
|
10621
10846
|
const trimTrailing = options?.trimTrailing ?? true;
|
|
10622
10847
|
const chunks = [];
|
|
10623
10848
|
let remaining = text;
|
|
10624
|
-
while (remaining
|
|
10849
|
+
while (measureQQBotUtf8Length(remaining) > limit) {
|
|
10625
10850
|
const splitIndex = findQQBotSafeSplitIndex(remaining, limit);
|
|
10626
10851
|
let nextChunk = remaining.slice(0, splitIndex);
|
|
10627
10852
|
let nextRemaining = remaining.slice(splitIndex);
|
|
@@ -10632,7 +10857,8 @@ function splitQQBotTextSafely(text, limit, options) {
|
|
|
10632
10857
|
nextRemaining = nextRemaining.trimStart();
|
|
10633
10858
|
}
|
|
10634
10859
|
if (!nextChunk) {
|
|
10635
|
-
const
|
|
10860
|
+
const hardChunkIndex = Math.max(1, findQQBotIndexWithinUtf8Limit(remaining, limit));
|
|
10861
|
+
const hardChunk = remaining.slice(0, hardChunkIndex);
|
|
10636
10862
|
chunks.push(hardChunk);
|
|
10637
10863
|
remaining = remaining.slice(hardChunk.length);
|
|
10638
10864
|
continue;
|
|
@@ -10647,7 +10873,7 @@ function splitQQBotTextSafely(text, limit, options) {
|
|
|
10647
10873
|
return chunks;
|
|
10648
10874
|
}
|
|
10649
10875
|
function splitQQBotMarkdownLineBlock(text, limit) {
|
|
10650
|
-
if (limit <= 0 || text
|
|
10876
|
+
if (limit <= 0 || measureQQBotUtf8Length(text) <= limit) {
|
|
10651
10877
|
return [text];
|
|
10652
10878
|
}
|
|
10653
10879
|
const lines = text.split("\n");
|
|
@@ -10666,12 +10892,12 @@ function splitQQBotMarkdownLineBlock(text, limit) {
|
|
|
10666
10892
|
for (const line of lines) {
|
|
10667
10893
|
const candidate = currentLines.length > 0 ? `${currentLines.join("\n")}
|
|
10668
10894
|
${line}` : line;
|
|
10669
|
-
if (candidate
|
|
10895
|
+
if (measureQQBotUtf8Length(candidate) <= limit) {
|
|
10670
10896
|
currentLines.push(line);
|
|
10671
10897
|
continue;
|
|
10672
10898
|
}
|
|
10673
10899
|
flushCurrent();
|
|
10674
|
-
if (line
|
|
10900
|
+
if (measureQQBotUtf8Length(line) <= limit) {
|
|
10675
10901
|
currentLines.push(line);
|
|
10676
10902
|
continue;
|
|
10677
10903
|
}
|
|
@@ -10687,16 +10913,133 @@ ${line}` : line;
|
|
|
10687
10913
|
flushCurrent();
|
|
10688
10914
|
return chunks;
|
|
10689
10915
|
}
|
|
10916
|
+
function parseQQBotMarkdownTableRowCells(row, columnCount) {
|
|
10917
|
+
const trimmed = row.trim();
|
|
10918
|
+
const inner = trimmed.replace(/^\|\s*/, "").replace(/\s*\|$/, "");
|
|
10919
|
+
const cells = inner.split("|").map((cell) => cell.trim());
|
|
10920
|
+
if (typeof columnCount !== "number" || !Number.isFinite(columnCount) || columnCount <= 0) {
|
|
10921
|
+
return cells;
|
|
10922
|
+
}
|
|
10923
|
+
if (cells.length >= columnCount) {
|
|
10924
|
+
return cells.slice(0, columnCount);
|
|
10925
|
+
}
|
|
10926
|
+
return [...cells, ...Array.from({ length: columnCount - cells.length }, () => "")];
|
|
10927
|
+
}
|
|
10928
|
+
function renderQQBotMarkdownTableRow(cells) {
|
|
10929
|
+
return `| ${cells.join(" | ")} |`;
|
|
10930
|
+
}
|
|
10931
|
+
function splitQQBotMarkdownTableRowByCells(params) {
|
|
10932
|
+
const { row, columnCount, limit } = params;
|
|
10933
|
+
const normalizedCells = parseQQBotMarkdownTableRowCells(row, columnCount);
|
|
10934
|
+
const normalizedRow = renderQQBotMarkdownTableRow(normalizedCells);
|
|
10935
|
+
if (measureQQBotUtf8Length(normalizedRow) <= limit) {
|
|
10936
|
+
return [normalizedRow];
|
|
10937
|
+
}
|
|
10938
|
+
const anchorColumnCount = Math.min(
|
|
10939
|
+
Math.max(1, params.anchorColumnCount ?? 2),
|
|
10940
|
+
columnCount
|
|
10941
|
+
);
|
|
10942
|
+
const anchorCells = normalizedCells.map(
|
|
10943
|
+
(cell, index) => index < anchorColumnCount ? cell : ""
|
|
10944
|
+
);
|
|
10945
|
+
const chunks = [];
|
|
10946
|
+
let currentCells = [...anchorCells];
|
|
10947
|
+
let hasContent = false;
|
|
10948
|
+
const flushCurrent = () => {
|
|
10949
|
+
if (!hasContent) {
|
|
10950
|
+
return;
|
|
10951
|
+
}
|
|
10952
|
+
chunks.push(renderQQBotMarkdownTableRow(currentCells));
|
|
10953
|
+
currentCells = [...anchorCells];
|
|
10954
|
+
hasContent = false;
|
|
10955
|
+
};
|
|
10956
|
+
for (let index = anchorColumnCount; index < columnCount; index += 1) {
|
|
10957
|
+
const cell = normalizedCells[index] ?? "";
|
|
10958
|
+
if (!cell) {
|
|
10959
|
+
continue;
|
|
10960
|
+
}
|
|
10961
|
+
const candidateCells = [...currentCells];
|
|
10962
|
+
candidateCells[index] = cell;
|
|
10963
|
+
if (measureQQBotUtf8Length(renderQQBotMarkdownTableRow(candidateCells)) <= limit) {
|
|
10964
|
+
currentCells[index] = cell;
|
|
10965
|
+
hasContent = true;
|
|
10966
|
+
continue;
|
|
10967
|
+
}
|
|
10968
|
+
if (hasContent) {
|
|
10969
|
+
flushCurrent();
|
|
10970
|
+
const nextCandidateCells = [...currentCells];
|
|
10971
|
+
nextCandidateCells[index] = cell;
|
|
10972
|
+
if (measureQQBotUtf8Length(renderQQBotMarkdownTableRow(nextCandidateCells)) <= limit) {
|
|
10973
|
+
currentCells[index] = cell;
|
|
10974
|
+
hasContent = true;
|
|
10975
|
+
continue;
|
|
10976
|
+
}
|
|
10977
|
+
}
|
|
10978
|
+
const emptyRowBytes = measureQQBotUtf8Length(renderQQBotMarkdownTableRow(currentCells));
|
|
10979
|
+
const availableCellBytes = Math.max(1, limit - emptyRowBytes);
|
|
10980
|
+
for (const cellPiece of splitQQBotTextSafely(cell, availableCellBytes, {
|
|
10981
|
+
trimLeading: false,
|
|
10982
|
+
trimTrailing: false
|
|
10983
|
+
})) {
|
|
10984
|
+
const pieceCells = [...anchorCells];
|
|
10985
|
+
pieceCells[index] = cellPiece;
|
|
10986
|
+
chunks.push(renderQQBotMarkdownTableRow(pieceCells));
|
|
10987
|
+
}
|
|
10988
|
+
currentCells = [...anchorCells];
|
|
10989
|
+
hasContent = false;
|
|
10990
|
+
}
|
|
10991
|
+
flushCurrent();
|
|
10992
|
+
return chunks.length > 0 ? chunks : splitQQBotTextSafely(normalizedRow, limit);
|
|
10993
|
+
}
|
|
10994
|
+
function resolveQQBotMarkdownTableBlockLimit(params) {
|
|
10995
|
+
const { header, separator, rows, limit } = params;
|
|
10996
|
+
if (limit <= 512) {
|
|
10997
|
+
return limit;
|
|
10998
|
+
}
|
|
10999
|
+
const columnCount = parseQQBotMarkdownTableRowCells(header).length;
|
|
11000
|
+
if (columnCount < 8) {
|
|
11001
|
+
return limit;
|
|
11002
|
+
}
|
|
11003
|
+
const tablePrefixBytes = measureQQBotUtf8Length(`${header}
|
|
11004
|
+
${separator}`);
|
|
11005
|
+
const extraColumnCount = Math.max(0, columnCount - 7);
|
|
11006
|
+
const columnPenalty = Math.min(240, extraColumnCount * 55);
|
|
11007
|
+
const headerPenalty = Math.min(120, Math.floor(tablePrefixBytes * 0.25));
|
|
11008
|
+
const reducedLimit = limit - columnPenalty - headerPenalty;
|
|
11009
|
+
const minTableLimit = Math.max(tablePrefixBytes + 64, Math.floor(limit * 0.45));
|
|
11010
|
+
const maxSingleRowRequirement = Math.min(
|
|
11011
|
+
limit,
|
|
11012
|
+
rows.reduce((max, row) => {
|
|
11013
|
+
const normalizedRow = renderQQBotMarkdownTableRow(
|
|
11014
|
+
parseQQBotMarkdownTableRowCells(row, columnCount)
|
|
11015
|
+
);
|
|
11016
|
+
return Math.max(max, measureQQBotUtf8Length(`${header}
|
|
11017
|
+
${separator}
|
|
11018
|
+
${normalizedRow}`));
|
|
11019
|
+
}, tablePrefixBytes)
|
|
11020
|
+
);
|
|
11021
|
+
return Math.min(
|
|
11022
|
+
limit,
|
|
11023
|
+
Math.max(maxSingleRowRequirement, minTableLimit, Math.min(limit, reducedLimit))
|
|
11024
|
+
);
|
|
11025
|
+
}
|
|
10690
11026
|
function splitQQBotMarkdownTableBlock(text, limit) {
|
|
10691
|
-
if (limit <= 0 || text
|
|
11027
|
+
if (limit <= 0 || measureQQBotUtf8Length(text) <= limit) {
|
|
10692
11028
|
return [text];
|
|
10693
11029
|
}
|
|
10694
11030
|
const lines = text.split("\n");
|
|
10695
11031
|
const header = lines[0] ?? "";
|
|
10696
11032
|
const separator = lines[1] ?? "";
|
|
10697
11033
|
const rows = lines.slice(2);
|
|
11034
|
+
const packingLimit = resolveQQBotMarkdownTableBlockLimit({
|
|
11035
|
+
header,
|
|
11036
|
+
separator,
|
|
11037
|
+
rows,
|
|
11038
|
+
limit
|
|
11039
|
+
});
|
|
10698
11040
|
const tablePrefix = `${header}
|
|
10699
11041
|
${separator}`;
|
|
11042
|
+
const columnCount = parseQQBotMarkdownTableRowCells(header).length;
|
|
10700
11043
|
const chunks = [];
|
|
10701
11044
|
let currentRows = [];
|
|
10702
11045
|
const flushCurrent = () => {
|
|
@@ -10712,20 +11055,21 @@ ${currentRows.join("\n")}`);
|
|
|
10712
11055
|
${currentRows.join("\n")}
|
|
10713
11056
|
${row}` : `${tablePrefix}
|
|
10714
11057
|
${row}`;
|
|
10715
|
-
if (candidate
|
|
11058
|
+
if (measureQQBotUtf8Length(candidate) <= packingLimit) {
|
|
10716
11059
|
currentRows.push(row);
|
|
10717
11060
|
continue;
|
|
10718
11061
|
}
|
|
10719
11062
|
flushCurrent();
|
|
10720
|
-
if (`${tablePrefix}
|
|
10721
|
-
${row}
|
|
11063
|
+
if (measureQQBotUtf8Length(`${tablePrefix}
|
|
11064
|
+
${row}`) <= limit) {
|
|
10722
11065
|
currentRows.push(row);
|
|
10723
11066
|
continue;
|
|
10724
11067
|
}
|
|
10725
|
-
const maxRowLength = Math.max(16, limit - tablePrefix
|
|
10726
|
-
for (const rowPiece of
|
|
10727
|
-
|
|
10728
|
-
|
|
11068
|
+
const maxRowLength = Math.max(16, limit - measureQQBotUtf8Length(tablePrefix) - 1);
|
|
11069
|
+
for (const rowPiece of splitQQBotMarkdownTableRowByCells({
|
|
11070
|
+
row,
|
|
11071
|
+
columnCount,
|
|
11072
|
+
limit: maxRowLength
|
|
10729
11073
|
})) {
|
|
10730
11074
|
chunks.push(`${tablePrefix}
|
|
10731
11075
|
${rowPiece}`);
|
|
@@ -10735,7 +11079,7 @@ ${rowPiece}`);
|
|
|
10735
11079
|
return chunks.length > 0 ? chunks : [text];
|
|
10736
11080
|
}
|
|
10737
11081
|
function splitQQBotMarkdownCodeFence(text, limit) {
|
|
10738
|
-
if (limit <= 0 || text
|
|
11082
|
+
if (limit <= 0 || measureQQBotUtf8Length(text) <= limit) {
|
|
10739
11083
|
return [text];
|
|
10740
11084
|
}
|
|
10741
11085
|
const lines = text.split("\n");
|
|
@@ -10744,7 +11088,7 @@ function splitQQBotMarkdownCodeFence(text, limit) {
|
|
|
10744
11088
|
const hasClosingFence = lines.length > 1 && isQQBotFenceClosingLine(lines[lines.length - 1] ?? "", delimiter);
|
|
10745
11089
|
const closingLine = hasClosingFence ? lines[lines.length - 1] ?? delimiter : delimiter;
|
|
10746
11090
|
const codeLines = lines.slice(1, hasClosingFence ? -1 : lines.length);
|
|
10747
|
-
const fixedOverhead = openingLine
|
|
11091
|
+
const fixedOverhead = measureQQBotUtf8Length(openingLine) + measureQQBotUtf8Length(closingLine) + 2;
|
|
10748
11092
|
const availableLineLength = Math.max(1, limit - fixedOverhead);
|
|
10749
11093
|
const chunks = [];
|
|
10750
11094
|
let currentCodeLines = [];
|
|
@@ -10764,14 +11108,14 @@ ${codeLine}
|
|
|
10764
11108
|
${closingLine}` : `${openingLine}
|
|
10765
11109
|
${codeLine}
|
|
10766
11110
|
${closingLine}`;
|
|
10767
|
-
if (candidate
|
|
11111
|
+
if (measureQQBotUtf8Length(candidate) <= limit) {
|
|
10768
11112
|
currentCodeLines.push(codeLine);
|
|
10769
11113
|
continue;
|
|
10770
11114
|
}
|
|
10771
11115
|
flushCurrent();
|
|
10772
|
-
if (`${openingLine}
|
|
11116
|
+
if (measureQQBotUtf8Length(`${openingLine}
|
|
10773
11117
|
${codeLine}
|
|
10774
|
-
${closingLine}
|
|
11118
|
+
${closingLine}`) <= limit) {
|
|
10775
11119
|
currentCodeLines.push(codeLine);
|
|
10776
11120
|
continue;
|
|
10777
11121
|
}
|
|
@@ -10785,7 +11129,7 @@ ${closingLine}`);
|
|
|
10785
11129
|
return chunks.length > 0 ? chunks : [text];
|
|
10786
11130
|
}
|
|
10787
11131
|
function splitQQBotMarkdownBlock(block, limit) {
|
|
10788
|
-
if (limit <= 0 || block.text
|
|
11132
|
+
if (limit <= 0 || measureQQBotUtf8Length(block.text) <= limit) {
|
|
10789
11133
|
return [block.text];
|
|
10790
11134
|
}
|
|
10791
11135
|
switch (block.kind) {
|
|
@@ -10806,11 +11150,93 @@ function splitQQBotMarkdownBlock(block, limit) {
|
|
|
10806
11150
|
return [block.text];
|
|
10807
11151
|
}
|
|
10808
11152
|
}
|
|
10809
|
-
function
|
|
11153
|
+
function resolveQQBotStructuredMarkdownSoftLimit(limit, safeChunkByteLimit) {
|
|
11154
|
+
if (limit <= QQBOT_MARKDOWN_SOFT_LIMIT_THRESHOLD) {
|
|
11155
|
+
return limit;
|
|
11156
|
+
}
|
|
11157
|
+
const configuredSafeLimit = typeof safeChunkByteLimit === "number" && Number.isFinite(safeChunkByteLimit) && safeChunkByteLimit > 0 ? Math.floor(safeChunkByteLimit) : void 0;
|
|
11158
|
+
if (configuredSafeLimit) {
|
|
11159
|
+
return Math.min(limit, configuredSafeLimit);
|
|
11160
|
+
}
|
|
11161
|
+
const reservedLength = Math.min(
|
|
11162
|
+
QQBOT_MARKDOWN_SOFT_LIMIT_HEADROOM_MAX,
|
|
11163
|
+
Math.max(
|
|
11164
|
+
QQBOT_MARKDOWN_SOFT_LIMIT_HEADROOM_MIN,
|
|
11165
|
+
Math.floor(limit * QQBOT_MARKDOWN_SOFT_LIMIT_HEADROOM_RATIO)
|
|
11166
|
+
)
|
|
11167
|
+
);
|
|
11168
|
+
const softLimit = limit - reservedLength;
|
|
11169
|
+
const boundedSoftLimit = Math.min(
|
|
11170
|
+
softLimit > 0 ? softLimit : limit,
|
|
11171
|
+
DEFAULT_QQBOT_C2C_MARKDOWN_SAFE_CHUNK_BYTE_LIMIT
|
|
11172
|
+
);
|
|
11173
|
+
return boundedSoftLimit > 0 ? boundedSoftLimit : limit;
|
|
11174
|
+
}
|
|
11175
|
+
function maybePrefixQQBotContinuationPiece(params) {
|
|
11176
|
+
const prefix = params.prefix?.trim();
|
|
11177
|
+
if (!prefix) {
|
|
11178
|
+
return params.piece;
|
|
11179
|
+
}
|
|
11180
|
+
const prefixedPiece = joinQQBotMarkdownPieces([prefix, params.piece]);
|
|
11181
|
+
return measureQQBotUtf8Length(prefixedPiece) <= params.limit ? prefixedPiece : params.piece;
|
|
11182
|
+
}
|
|
11183
|
+
function resolveQQBotMarkdownLeadPiece(blocks, index, limit) {
|
|
11184
|
+
const block = blocks[index];
|
|
11185
|
+
if (!block) {
|
|
11186
|
+
return void 0;
|
|
11187
|
+
}
|
|
11188
|
+
if (block.kind === "heading") {
|
|
11189
|
+
const nextBlock = blocks[index + 1];
|
|
11190
|
+
if (nextBlock && nextBlock.kind !== "thematic-break") {
|
|
11191
|
+
const nextPieces = splitQQBotMarkdownBlock(nextBlock, limit);
|
|
11192
|
+
const firstBodyPiece = nextPieces[0];
|
|
11193
|
+
if (firstBodyPiece) {
|
|
11194
|
+
const pairedText = joinQQBotMarkdownPieces([block.text, firstBodyPiece]);
|
|
11195
|
+
if (measureQQBotUtf8Length(pairedText) <= limit) {
|
|
11196
|
+
return pairedText;
|
|
11197
|
+
}
|
|
11198
|
+
}
|
|
11199
|
+
}
|
|
11200
|
+
}
|
|
11201
|
+
return splitQQBotMarkdownBlock(block, limit).map((piece) => piece.trim()).find(Boolean);
|
|
11202
|
+
}
|
|
11203
|
+
function shouldQQBotCarryThematicBreakToNextBlock(params) {
|
|
11204
|
+
const block = params.blocks[params.index];
|
|
11205
|
+
if (!block || block.kind !== "thematic-break") {
|
|
11206
|
+
return false;
|
|
11207
|
+
}
|
|
11208
|
+
if (params.currentPieces.length === 0) {
|
|
11209
|
+
return true;
|
|
11210
|
+
}
|
|
11211
|
+
const withBreak = joinQQBotMarkdownPieces([...params.currentPieces, block.text]);
|
|
11212
|
+
if (measureQQBotUtf8Length(withBreak) > params.limit) {
|
|
11213
|
+
return true;
|
|
11214
|
+
}
|
|
11215
|
+
const nextLeadPiece = resolveQQBotMarkdownLeadPiece(
|
|
11216
|
+
params.blocks,
|
|
11217
|
+
params.index + 1,
|
|
11218
|
+
params.limit
|
|
11219
|
+
);
|
|
11220
|
+
if (!nextLeadPiece) {
|
|
11221
|
+
return false;
|
|
11222
|
+
}
|
|
11223
|
+
const prefixedLeadPiece = joinQQBotMarkdownPieces([block.text, nextLeadPiece]);
|
|
11224
|
+
if (measureQQBotUtf8Length(prefixedLeadPiece) > params.limit) {
|
|
11225
|
+
return false;
|
|
11226
|
+
}
|
|
11227
|
+
const sectionCandidate = joinQQBotMarkdownPieces([
|
|
11228
|
+
...params.currentPieces,
|
|
11229
|
+
block.text,
|
|
11230
|
+
nextLeadPiece
|
|
11231
|
+
]);
|
|
11232
|
+
return measureQQBotUtf8Length(sectionCandidate) > params.limit;
|
|
11233
|
+
}
|
|
11234
|
+
function chunkQQBotStructuredMarkdown(text, limit, safeChunkByteLimit) {
|
|
10810
11235
|
const blocks = parseQQBotMarkdownBlocks(text);
|
|
10811
11236
|
if (blocks.length === 0 || limit <= 0) {
|
|
10812
11237
|
return [text.trim()];
|
|
10813
11238
|
}
|
|
11239
|
+
const chunkLimit = resolveQQBotStructuredMarkdownSoftLimit(limit, safeChunkByteLimit);
|
|
10814
11240
|
const chunks = [];
|
|
10815
11241
|
let currentPieces = [];
|
|
10816
11242
|
let pendingPrefixPieces = [];
|
|
@@ -10828,14 +11254,14 @@ function chunkQQBotStructuredMarkdown(text, limit) {
|
|
|
10828
11254
|
if (!piece) {
|
|
10829
11255
|
return;
|
|
10830
11256
|
}
|
|
10831
|
-
const pieces = piece
|
|
11257
|
+
const pieces = measureQQBotUtf8Length(piece) > chunkLimit ? splitQQBotTextSafely(piece, chunkLimit) : [piece];
|
|
10832
11258
|
for (const nextPiece of pieces) {
|
|
10833
11259
|
const normalizedPiece = nextPiece.trim();
|
|
10834
11260
|
if (!normalizedPiece) {
|
|
10835
11261
|
continue;
|
|
10836
11262
|
}
|
|
10837
11263
|
const candidate = joinQQBotMarkdownPieces([...currentPieces, normalizedPiece]);
|
|
10838
|
-
if (currentPieces.length === 0 || candidate
|
|
11264
|
+
if (currentPieces.length === 0 || measureQQBotUtf8Length(candidate) <= chunkLimit) {
|
|
10839
11265
|
currentPieces.push(normalizedPiece);
|
|
10840
11266
|
continue;
|
|
10841
11267
|
}
|
|
@@ -10857,9 +11283,19 @@ function chunkQQBotStructuredMarkdown(text, limit) {
|
|
|
10857
11283
|
continue;
|
|
10858
11284
|
}
|
|
10859
11285
|
if (block.kind === "thematic-break") {
|
|
11286
|
+
if (shouldQQBotCarryThematicBreakToNextBlock({
|
|
11287
|
+
blocks,
|
|
11288
|
+
index,
|
|
11289
|
+
currentPieces,
|
|
11290
|
+
limit: chunkLimit
|
|
11291
|
+
})) {
|
|
11292
|
+
flushCurrent();
|
|
11293
|
+
pendingPrefixPieces.push(block.text);
|
|
11294
|
+
continue;
|
|
11295
|
+
}
|
|
10860
11296
|
if (currentPieces.length > 0) {
|
|
10861
11297
|
const candidate = joinQQBotMarkdownPieces([...currentPieces, block.text]);
|
|
10862
|
-
if (candidate
|
|
11298
|
+
if (measureQQBotUtf8Length(candidate) <= chunkLimit) {
|
|
10863
11299
|
currentPieces.push(block.text);
|
|
10864
11300
|
continue;
|
|
10865
11301
|
}
|
|
@@ -10872,7 +11308,7 @@ function chunkQQBotStructuredMarkdown(text, limit) {
|
|
|
10872
11308
|
const headingText = consumePendingPrefix(block.text);
|
|
10873
11309
|
const nextBlock = blocks[index + 1];
|
|
10874
11310
|
if (nextBlock && nextBlock.kind !== "thematic-break") {
|
|
10875
|
-
const nextPieces = splitQQBotMarkdownBlock(nextBlock,
|
|
11311
|
+
const nextPieces = splitQQBotMarkdownBlock(nextBlock, chunkLimit);
|
|
10876
11312
|
const firstBodyPiece = nextPieces[0];
|
|
10877
11313
|
if (firstBodyPiece) {
|
|
10878
11314
|
const pairedText = joinQQBotMarkdownPieces([headingText, firstBodyPiece]);
|
|
@@ -10881,19 +11317,33 @@ function chunkQQBotStructuredMarkdown(text, limit) {
|
|
|
10881
11317
|
headingText,
|
|
10882
11318
|
firstBodyPiece
|
|
10883
11319
|
]);
|
|
10884
|
-
if (pairedText
|
|
11320
|
+
if (measureQQBotUtf8Length(pairedText) <= chunkLimit && (currentPieces.length === 0 || measureQQBotUtf8Length(pairedCandidate) <= chunkLimit)) {
|
|
10885
11321
|
currentPieces.push(headingText, firstBodyPiece);
|
|
10886
11322
|
for (let pieceIndex = 1; pieceIndex < nextPieces.length; pieceIndex += 1) {
|
|
10887
|
-
|
|
11323
|
+
const nextPiece = nextPieces[pieceIndex] ?? "";
|
|
11324
|
+
appendPiece(
|
|
11325
|
+
nextBlock.kind === "table" ? maybePrefixQQBotContinuationPiece({
|
|
11326
|
+
prefix: headingText,
|
|
11327
|
+
piece: nextPiece,
|
|
11328
|
+
limit: chunkLimit
|
|
11329
|
+
}) : nextPiece
|
|
11330
|
+
);
|
|
10888
11331
|
}
|
|
10889
11332
|
index += 1;
|
|
10890
11333
|
continue;
|
|
10891
11334
|
}
|
|
10892
|
-
if (currentPieces.length > 0 && pairedText
|
|
11335
|
+
if (currentPieces.length > 0 && measureQQBotUtf8Length(pairedText) <= chunkLimit) {
|
|
10893
11336
|
flushCurrent();
|
|
10894
11337
|
currentPieces.push(headingText, firstBodyPiece);
|
|
10895
11338
|
for (let pieceIndex = 1; pieceIndex < nextPieces.length; pieceIndex += 1) {
|
|
10896
|
-
|
|
11339
|
+
const nextPiece = nextPieces[pieceIndex] ?? "";
|
|
11340
|
+
appendPiece(
|
|
11341
|
+
nextBlock.kind === "table" ? maybePrefixQQBotContinuationPiece({
|
|
11342
|
+
prefix: headingText,
|
|
11343
|
+
piece: nextPiece,
|
|
11344
|
+
limit: chunkLimit
|
|
11345
|
+
}) : nextPiece
|
|
11346
|
+
);
|
|
10897
11347
|
}
|
|
10898
11348
|
index += 1;
|
|
10899
11349
|
continue;
|
|
@@ -10904,16 +11354,29 @@ function chunkQQBotStructuredMarkdown(text, limit) {
|
|
|
10904
11354
|
continue;
|
|
10905
11355
|
}
|
|
10906
11356
|
const blockText = consumePendingPrefix(block.text);
|
|
10907
|
-
for (const piece of splitQQBotMarkdownBlock({ ...block, text: blockText },
|
|
11357
|
+
for (const piece of splitQQBotMarkdownBlock({ ...block, text: blockText }, chunkLimit)) {
|
|
10908
11358
|
appendPiece(piece);
|
|
10909
11359
|
}
|
|
10910
11360
|
}
|
|
10911
11361
|
if (pendingPrefixPieces.length > 0 && currentPieces.length > 0) {
|
|
10912
11362
|
const trailingCandidate = joinQQBotMarkdownPieces([...currentPieces, ...pendingPrefixPieces]);
|
|
10913
|
-
if (trailingCandidate
|
|
11363
|
+
if (measureQQBotUtf8Length(trailingCandidate) <= chunkLimit) {
|
|
10914
11364
|
currentPieces.push(...pendingPrefixPieces);
|
|
11365
|
+
pendingPrefixPieces = [];
|
|
10915
11366
|
}
|
|
10916
11367
|
}
|
|
11368
|
+
if (pendingPrefixPieces.length > 0 && chunks.length > 0) {
|
|
11369
|
+
const trailingPrefix = joinQQBotMarkdownPieces(pendingPrefixPieces);
|
|
11370
|
+
const lastChunk = chunks[chunks.length - 1] ?? "";
|
|
11371
|
+
const trailingCandidate = joinQQBotMarkdownPieces([lastChunk, trailingPrefix]);
|
|
11372
|
+
if (measureQQBotUtf8Length(trailingCandidate) <= chunkLimit) {
|
|
11373
|
+
chunks[chunks.length - 1] = trailingCandidate;
|
|
11374
|
+
pendingPrefixPieces = [];
|
|
11375
|
+
}
|
|
11376
|
+
}
|
|
11377
|
+
if (pendingPrefixPieces.length > 0) {
|
|
11378
|
+
currentPieces.push(joinQQBotMarkdownPieces(pendingPrefixPieces));
|
|
11379
|
+
}
|
|
10917
11380
|
flushCurrent();
|
|
10918
11381
|
return chunks.length > 0 ? chunks : [text.trim()];
|
|
10919
11382
|
}
|
|
@@ -10940,7 +11403,7 @@ function chunkC2CMarkdownText(params) {
|
|
|
10940
11403
|
if (params.limit <= 0 || !looksLikeStructuredMarkdown(normalized)) {
|
|
10941
11404
|
return params.fallbackChunkText ? params.fallbackChunkText(normalized) : [normalized];
|
|
10942
11405
|
}
|
|
10943
|
-
return chunkQQBotStructuredMarkdown(normalized, params.limit);
|
|
11406
|
+
return chunkQQBotStructuredMarkdown(normalized, params.limit, params.safeChunkByteLimit);
|
|
10944
11407
|
}
|
|
10945
11408
|
async function sendQQBotMediaWithFallback(params) {
|
|
10946
11409
|
const { qqCfg, to, mediaQueue, replyToId, replyEventId, accountId, logger, onDelivered, onError } = params;
|
|
@@ -11029,6 +11492,9 @@ async function dispatchToAgent(params) {
|
|
|
11029
11492
|
};
|
|
11030
11493
|
const target = resolveChatTarget(inbound);
|
|
11031
11494
|
const outboundAccountId = route.accountId ?? accountId;
|
|
11495
|
+
const typingHeartbeatMode = resolveQQBotTypingHeartbeatMode(qqCfg);
|
|
11496
|
+
const typingHeartbeatIntervalMs = resolveQQBotTypingHeartbeatIntervalMs(qqCfg);
|
|
11497
|
+
const typingInputSeconds = resolveQQBotTypingInputSeconds(qqCfg);
|
|
11032
11498
|
let typingRefIdx;
|
|
11033
11499
|
if (inbound.c2cOpenid && !isFastAbortCommand && !shouldSuppressVisibleReplies()) {
|
|
11034
11500
|
const typing = await qqbotOutbound.sendTyping({
|
|
@@ -11036,7 +11502,7 @@ async function dispatchToAgent(params) {
|
|
|
11036
11502
|
to: `user:${inbound.c2cOpenid}`,
|
|
11037
11503
|
replyToId: inbound.messageId,
|
|
11038
11504
|
replyEventId: inbound.eventId,
|
|
11039
|
-
inputSecond:
|
|
11505
|
+
inputSecond: typingInputSeconds,
|
|
11040
11506
|
accountId: outboundAccountId
|
|
11041
11507
|
});
|
|
11042
11508
|
if (typing.error) {
|
|
@@ -11052,10 +11518,15 @@ async function dispatchToAgent(params) {
|
|
|
11052
11518
|
}
|
|
11053
11519
|
let replyDelivered = false;
|
|
11054
11520
|
let groupMessageInterfaceBlocked = false;
|
|
11521
|
+
let lastVisibleOutboundAt = Date.now();
|
|
11522
|
+
let typingHeartbeat = null;
|
|
11055
11523
|
const markReplyDelivered = () => {
|
|
11056
11524
|
replyDelivered = true;
|
|
11057
11525
|
longTaskNotice.markReplyDelivered();
|
|
11058
11526
|
};
|
|
11527
|
+
const markVisibleOutboundStarted = () => {
|
|
11528
|
+
lastVisibleOutboundAt = Date.now();
|
|
11529
|
+
};
|
|
11059
11530
|
const markGroupMessageInterfaceBlocked = (error) => {
|
|
11060
11531
|
if (!isQQBotGroupMessageInterfaceBlocked(error)) return;
|
|
11061
11532
|
if (!groupMessageInterfaceBlocked) {
|
|
@@ -11063,11 +11534,40 @@ async function dispatchToAgent(params) {
|
|
|
11063
11534
|
}
|
|
11064
11535
|
groupMessageInterfaceBlocked = true;
|
|
11065
11536
|
};
|
|
11537
|
+
if (inbound.c2cOpenid && typingHeartbeatMode !== "none" && !isFastAbortCommand && !shouldSuppressVisibleReplies()) {
|
|
11538
|
+
typingHeartbeat = startQQBotTypingHeartbeat({
|
|
11539
|
+
intervalMs: typingHeartbeatIntervalMs,
|
|
11540
|
+
shouldRenew: () => {
|
|
11541
|
+
if (shouldSuppressVisibleReplies()) {
|
|
11542
|
+
return false;
|
|
11543
|
+
}
|
|
11544
|
+
if (typingHeartbeatMode === "always") {
|
|
11545
|
+
return true;
|
|
11546
|
+
}
|
|
11547
|
+
return Date.now() - lastVisibleOutboundAt >= typingHeartbeatIntervalMs;
|
|
11548
|
+
},
|
|
11549
|
+
renew: async () => {
|
|
11550
|
+
try {
|
|
11551
|
+
const typing = await qqbotOutbound.sendTyping({
|
|
11552
|
+
cfg: { channels: { qqbot: qqCfg } },
|
|
11553
|
+
to: `user:${inbound.c2cOpenid}`,
|
|
11554
|
+
replyToId: inbound.messageId,
|
|
11555
|
+
replyEventId: inbound.eventId,
|
|
11556
|
+
inputSecond: typingInputSeconds,
|
|
11557
|
+
accountId: outboundAccountId
|
|
11558
|
+
});
|
|
11559
|
+
void typing;
|
|
11560
|
+
} catch {
|
|
11561
|
+
}
|
|
11562
|
+
}
|
|
11563
|
+
});
|
|
11564
|
+
}
|
|
11066
11565
|
const longTaskNotice = startLongTaskNoticeTimer({
|
|
11067
11566
|
delayMs: qqCfg.longTaskNoticeDelayMs ?? DEFAULT_LONG_TASK_NOTICE_DELAY_MS,
|
|
11068
11567
|
logger,
|
|
11069
11568
|
sendNotice: async () => {
|
|
11070
11569
|
if (groupMessageInterfaceBlocked || isFastAbortCommand || shouldSuppressVisibleReplies()) return;
|
|
11570
|
+
markVisibleOutboundStarted();
|
|
11071
11571
|
const result = await qqbotOutbound.sendText({
|
|
11072
11572
|
cfg: { channels: { qqbot: qqCfg } },
|
|
11073
11573
|
to: target.to,
|
|
@@ -11080,7 +11580,7 @@ async function dispatchToAgent(params) {
|
|
|
11080
11580
|
logger.warn(`send long-task notice failed: ${result.error}`);
|
|
11081
11581
|
markGroupMessageInterfaceBlocked(result.error);
|
|
11082
11582
|
} else {
|
|
11083
|
-
|
|
11583
|
+
markReplyDelivered();
|
|
11084
11584
|
}
|
|
11085
11585
|
}
|
|
11086
11586
|
});
|
|
@@ -11104,6 +11604,7 @@ async function dispatchToAgent(params) {
|
|
|
11104
11604
|
if (shouldSuppressVisibleReplies()) {
|
|
11105
11605
|
return;
|
|
11106
11606
|
}
|
|
11607
|
+
markVisibleOutboundStarted();
|
|
11107
11608
|
const fallback = await qqbotOutbound.sendText({
|
|
11108
11609
|
cfg: { channels: { qqbot: qqCfg } },
|
|
11109
11610
|
to: target.to,
|
|
@@ -11116,7 +11617,7 @@ async function dispatchToAgent(params) {
|
|
|
11116
11617
|
logger.error(`sendText ASR fallback failed: ${fallback.error}`);
|
|
11117
11618
|
markGroupMessageInterfaceBlocked(fallback.error);
|
|
11118
11619
|
} else {
|
|
11119
|
-
|
|
11620
|
+
markReplyDelivered();
|
|
11120
11621
|
}
|
|
11121
11622
|
return;
|
|
11122
11623
|
}
|
|
@@ -11278,6 +11779,7 @@ async function dispatchToAgent(params) {
|
|
|
11278
11779
|
const markdownSupport = qqCfg.markdownSupport ?? true;
|
|
11279
11780
|
const c2cMarkdownDeliveryMode = qqCfg.c2cMarkdownDeliveryMode ?? "proactive-table-only";
|
|
11280
11781
|
const c2cMarkdownChunkStrategy = qqCfg.c2cMarkdownChunkStrategy ?? "markdown-block";
|
|
11782
|
+
const c2cMarkdownSafeChunkByteLimit = resolveQQBotC2CMarkdownSafeChunkByteLimit(qqCfg);
|
|
11281
11783
|
const isC2CTarget = isQQBotC2CTarget(target.to);
|
|
11282
11784
|
const useC2CMarkdownTransport = markdownSupport && isC2CTarget;
|
|
11283
11785
|
let bufferedC2CMarkdownTexts = [];
|
|
@@ -11315,13 +11817,17 @@ async function dispatchToAgent(params) {
|
|
|
11315
11817
|
text: finalMarkdownText,
|
|
11316
11818
|
limit,
|
|
11317
11819
|
strategy: c2cMarkdownChunkStrategy,
|
|
11820
|
+
safeChunkByteLimit: c2cMarkdownSafeChunkByteLimit,
|
|
11318
11821
|
fallbackChunkText: chunkText
|
|
11319
11822
|
}) : [];
|
|
11320
11823
|
const deliveryLabel = textReplyRefs.forceProactive ? "c2c-markdown-proactive" : "c2c-markdown-passive";
|
|
11321
11824
|
logger.info(
|
|
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}`
|
|
11825
|
+
`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} safeChunkByteLimit=${String(c2cMarkdownSafeChunkByteLimit ?? "auto")}`
|
|
11323
11826
|
);
|
|
11324
11827
|
if (!shouldSuppressVisibleReplies()) {
|
|
11828
|
+
if (mediaQueue.length > 0) {
|
|
11829
|
+
markVisibleOutboundStarted();
|
|
11830
|
+
}
|
|
11325
11831
|
await sendQQBotMediaWithFallback({
|
|
11326
11832
|
qqCfg,
|
|
11327
11833
|
to: target.to,
|
|
@@ -11350,6 +11856,7 @@ async function dispatchToAgent(params) {
|
|
|
11350
11856
|
logger.info(
|
|
11351
11857
|
`delivery=${deliveryLabel} segment=1/1 chunk=${chunkIndex + 1}/${textChunks.length} phase=${params2.phase} preview=${formatQQBotOutboundPreview(chunk)}`
|
|
11352
11858
|
);
|
|
11859
|
+
markVisibleOutboundStarted();
|
|
11353
11860
|
const result = await qqbotOutbound.sendText({
|
|
11354
11861
|
cfg: { channels: { qqbot: qqCfg } },
|
|
11355
11862
|
to: target.to,
|
|
@@ -11380,7 +11887,7 @@ async function dispatchToAgent(params) {
|
|
|
11380
11887
|
bufferedC2CMarkdownMediaSeen.clear();
|
|
11381
11888
|
return;
|
|
11382
11889
|
}
|
|
11383
|
-
const combinedText = bufferedC2CMarkdownTexts
|
|
11890
|
+
const combinedText = combineQQBotBufferedText(bufferedC2CMarkdownTexts);
|
|
11384
11891
|
const combinedMediaUrls = [...bufferedC2CMarkdownMediaUrls];
|
|
11385
11892
|
bufferedC2CMarkdownTexts = [];
|
|
11386
11893
|
bufferedC2CMarkdownMediaUrls = [];
|
|
@@ -11426,7 +11933,7 @@ async function dispatchToAgent(params) {
|
|
|
11426
11933
|
const textToSend = suppressText ? "" : cleanedText;
|
|
11427
11934
|
if (useC2CMarkdownTransport) {
|
|
11428
11935
|
const shouldBufferFinalOnlyPayload = replyFinalOnly && (!info?.kind || info.kind === "final");
|
|
11429
|
-
const shouldBufferStructuredMarkdownPayload = !replyFinalOnly && c2cMarkdownChunkStrategy === "markdown-block" && info?.kind !== "tool" && looksLikeStructuredMarkdown(textToSend);
|
|
11936
|
+
const shouldBufferStructuredMarkdownPayload = !replyFinalOnly && c2cMarkdownChunkStrategy === "markdown-block" && info?.kind !== "tool" && (hasBufferedC2CMarkdownReply() || looksLikeStructuredMarkdown(textToSend));
|
|
11430
11937
|
if (shouldBufferFinalOnlyPayload || shouldBufferStructuredMarkdownPayload) {
|
|
11431
11938
|
if (textToSend) {
|
|
11432
11939
|
bufferedC2CMarkdownTexts = appendQQBotBufferedText(bufferedC2CMarkdownTexts, textToSend);
|
|
@@ -11464,6 +11971,7 @@ async function dispatchToAgent(params) {
|
|
|
11464
11971
|
if (shouldSuppressVisibleReplies()) {
|
|
11465
11972
|
return;
|
|
11466
11973
|
}
|
|
11974
|
+
markVisibleOutboundStarted();
|
|
11467
11975
|
const result = await qqbotOutbound.sendText({
|
|
11468
11976
|
cfg: { channels: { qqbot: qqCfg } },
|
|
11469
11977
|
to: target.to,
|
|
@@ -11483,6 +11991,9 @@ async function dispatchToAgent(params) {
|
|
|
11483
11991
|
if (shouldSuppressVisibleReplies()) {
|
|
11484
11992
|
return;
|
|
11485
11993
|
}
|
|
11994
|
+
if (mediaQueue.length > 0) {
|
|
11995
|
+
markVisibleOutboundStarted();
|
|
11996
|
+
}
|
|
11486
11997
|
await sendQQBotMediaWithFallback({
|
|
11487
11998
|
qqCfg,
|
|
11488
11999
|
to: target.to,
|
|
@@ -11587,6 +12098,7 @@ async function dispatchToAgent(params) {
|
|
|
11587
12098
|
});
|
|
11588
12099
|
if (noReplyFallback && !groupMessageInterfaceBlocked && !isFastAbortCommand && !shouldSuppressVisibleReplies()) {
|
|
11589
12100
|
logger.info("no visible reply generated for group mention; sending fallback text");
|
|
12101
|
+
markVisibleOutboundStarted();
|
|
11590
12102
|
const fallbackResult = await qqbotOutbound.sendText({
|
|
11591
12103
|
cfg: { channels: { qqbot: qqCfg } },
|
|
11592
12104
|
to: target.to,
|
|
@@ -11603,6 +12115,7 @@ async function dispatchToAgent(params) {
|
|
|
11603
12115
|
}
|
|
11604
12116
|
}
|
|
11605
12117
|
} finally {
|
|
12118
|
+
typingHeartbeat?.dispose();
|
|
11606
12119
|
longTaskNotice.dispose();
|
|
11607
12120
|
try {
|
|
11608
12121
|
await pruneInboundMediaDir({
|
|
@@ -12205,6 +12718,7 @@ var qqbotPlugin = {
|
|
|
12205
12718
|
type: "string",
|
|
12206
12719
|
enum: ["markdown-block", "length"]
|
|
12207
12720
|
},
|
|
12721
|
+
c2cMarkdownSafeChunkByteLimit: { type: "integer", minimum: 1 },
|
|
12208
12722
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
12209
12723
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
12210
12724
|
requireMention: { type: "boolean" },
|
|
@@ -12258,6 +12772,7 @@ var qqbotPlugin = {
|
|
|
12258
12772
|
type: "string",
|
|
12259
12773
|
enum: ["markdown-block", "length"]
|
|
12260
12774
|
},
|
|
12775
|
+
c2cMarkdownSafeChunkByteLimit: { type: "integer", minimum: 1 },
|
|
12261
12776
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
12262
12777
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
12263
12778
|
requireMention: { type: "boolean" },
|
|
@@ -12470,6 +12985,7 @@ var plugin = {
|
|
|
12470
12985
|
type: "string",
|
|
12471
12986
|
enum: ["markdown-block", "length"]
|
|
12472
12987
|
},
|
|
12988
|
+
c2cMarkdownSafeChunkByteLimit: { type: "integer", minimum: 1 },
|
|
12473
12989
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
12474
12990
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
12475
12991
|
requireMention: { type: "boolean" },
|
|
@@ -12523,6 +13039,7 @@ var plugin = {
|
|
|
12523
13039
|
type: "string",
|
|
12524
13040
|
enum: ["markdown-block", "length"]
|
|
12525
13041
|
},
|
|
13042
|
+
c2cMarkdownSafeChunkByteLimit: { type: "integer", minimum: 1 },
|
|
12526
13043
|
dmPolicy: { type: "string", enum: ["open", "pairing", "allowlist"] },
|
|
12527
13044
|
groupPolicy: { type: "string", enum: ["open", "allowlist", "disabled"] },
|
|
12528
13045
|
requireMention: { type: "boolean" },
|