@openclaw-china/qqbot 2026.3.22 → 2026.4.23
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 +44 -10
- package/dist/index.js +604 -240
- package/dist/index.js.map +1 -1
- package/openclaw.plugin.json +4 -2
- package/package.json +2 -2
- package/skills/qqbot-contact-send/SKILL.md +1 -1
- package/skills/qqbot-contact-send/scripts/prepare_send.py +1 -1
package/dist/index.js
CHANGED
|
@@ -4214,6 +4214,10 @@ var coerce = {
|
|
|
4214
4214
|
var NEVER = INVALID;
|
|
4215
4215
|
|
|
4216
4216
|
// src/config.ts
|
|
4217
|
+
var QQBOT_CHANNEL_ID = "qqbot";
|
|
4218
|
+
var QQBOT_CONFIG_CHANNEL_ID = "qqbot-china";
|
|
4219
|
+
var QQBOT_CONFIG_PREFIX = `channels.${QQBOT_CONFIG_CHANNEL_ID}`;
|
|
4220
|
+
var QQBOT_CONFIG_PREFIXES = [QQBOT_CONFIG_PREFIX];
|
|
4217
4221
|
function toTrimmedString(value) {
|
|
4218
4222
|
if (value === void 0 || value === null) return void 0;
|
|
4219
4223
|
const next = String(value).trim();
|
|
@@ -4238,6 +4242,7 @@ var QQBotAccountSchema = external_exports.object({
|
|
|
4238
4242
|
enabled: external_exports.boolean().optional(),
|
|
4239
4243
|
appId: optionalCoercedString,
|
|
4240
4244
|
clientSecret: optionalCoercedString,
|
|
4245
|
+
streaming: external_exports.boolean().optional().default(false),
|
|
4241
4246
|
displayAliases: displayAliasesSchema,
|
|
4242
4247
|
asr: external_exports.object({
|
|
4243
4248
|
enabled: external_exports.boolean().optional().default(false),
|
|
@@ -4318,12 +4323,48 @@ function resolveQQBotTypingHeartbeatIntervalMs(config) {
|
|
|
4318
4323
|
function resolveQQBotTypingInputSeconds(config) {
|
|
4319
4324
|
return config?.typingInputSeconds ?? DEFAULT_QQBOT_TYPING_INPUT_SECONDS;
|
|
4320
4325
|
}
|
|
4326
|
+
function resolveQQBotStreaming(config) {
|
|
4327
|
+
return config?.streaming === true;
|
|
4328
|
+
}
|
|
4321
4329
|
function resolveInboundMediaTempDir() {
|
|
4322
4330
|
return DEFAULT_INBOUND_MEDIA_TEMP_DIR;
|
|
4323
4331
|
}
|
|
4332
|
+
function asQQBotConfig(value) {
|
|
4333
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
4334
|
+
return void 0;
|
|
4335
|
+
}
|
|
4336
|
+
return value;
|
|
4337
|
+
}
|
|
4338
|
+
function resolveQQBotChannelConfig(cfg) {
|
|
4339
|
+
return asQQBotConfig(cfg?.channels?.[QQBOT_CONFIG_CHANNEL_ID]);
|
|
4340
|
+
}
|
|
4341
|
+
function withQQBotChannelConfig(cfg, qqbotConfig) {
|
|
4342
|
+
const nextChannels = { ...cfg.channels ?? {} };
|
|
4343
|
+
if (qqbotConfig) {
|
|
4344
|
+
nextChannels[QQBOT_CONFIG_CHANNEL_ID] = qqbotConfig;
|
|
4345
|
+
} else {
|
|
4346
|
+
delete nextChannels[QQBOT_CONFIG_CHANNEL_ID];
|
|
4347
|
+
}
|
|
4348
|
+
if (Object.keys(nextChannels).length === 0) {
|
|
4349
|
+
const next = { ...cfg };
|
|
4350
|
+
delete next.channels;
|
|
4351
|
+
return next;
|
|
4352
|
+
}
|
|
4353
|
+
return {
|
|
4354
|
+
...cfg,
|
|
4355
|
+
channels: nextChannels
|
|
4356
|
+
};
|
|
4357
|
+
}
|
|
4358
|
+
function stripQQBotChannelPrefix(value) {
|
|
4359
|
+
let next = value.trim();
|
|
4360
|
+
if (next.slice(0, QQBOT_CHANNEL_ID.length + 1).toLowerCase() === `${QQBOT_CHANNEL_ID}:`) {
|
|
4361
|
+
next = next.slice(QQBOT_CHANNEL_ID.length + 1).trim();
|
|
4362
|
+
}
|
|
4363
|
+
return next;
|
|
4364
|
+
}
|
|
4324
4365
|
var DEFAULT_ACCOUNT_ID = "default";
|
|
4325
4366
|
function listConfiguredAccountIds(cfg) {
|
|
4326
|
-
const accounts = cfg
|
|
4367
|
+
const accounts = resolveQQBotChannelConfig(cfg)?.accounts;
|
|
4327
4368
|
if (!accounts || typeof accounts !== "object") return [];
|
|
4328
4369
|
return Object.keys(accounts).filter(Boolean);
|
|
4329
4370
|
}
|
|
@@ -4333,19 +4374,19 @@ function listQQBotAccountIds(cfg) {
|
|
|
4333
4374
|
return ids.sort((a, b) => a.localeCompare(b));
|
|
4334
4375
|
}
|
|
4335
4376
|
function resolveDefaultQQBotAccountId(cfg) {
|
|
4336
|
-
const qqbotConfig = cfg
|
|
4377
|
+
const qqbotConfig = resolveQQBotChannelConfig(cfg);
|
|
4337
4378
|
if (qqbotConfig?.defaultAccount?.trim()) return qqbotConfig.defaultAccount.trim();
|
|
4338
4379
|
const ids = listQQBotAccountIds(cfg);
|
|
4339
4380
|
if (ids.includes(DEFAULT_ACCOUNT_ID)) return DEFAULT_ACCOUNT_ID;
|
|
4340
4381
|
return ids[0] ?? DEFAULT_ACCOUNT_ID;
|
|
4341
4382
|
}
|
|
4342
4383
|
function resolveAccountConfig(cfg, accountId) {
|
|
4343
|
-
const accounts = cfg
|
|
4384
|
+
const accounts = resolveQQBotChannelConfig(cfg)?.accounts;
|
|
4344
4385
|
if (!accounts || typeof accounts !== "object") return void 0;
|
|
4345
4386
|
return accounts[accountId];
|
|
4346
4387
|
}
|
|
4347
4388
|
function mergeQQBotAccountConfig(cfg, accountId) {
|
|
4348
|
-
const base = cfg
|
|
4389
|
+
const base = resolveQQBotChannelConfig(cfg) ?? {};
|
|
4349
4390
|
const { accounts: _ignored, defaultAccount: _ignored2, ...baseConfig } = base;
|
|
4350
4391
|
const account = resolveAccountConfig(cfg, accountId) ?? {};
|
|
4351
4392
|
const mergedDisplayAliases = {
|
|
@@ -4383,41 +4424,35 @@ function isPromptCancelled(value) {
|
|
|
4383
4424
|
return typeof value === "symbol";
|
|
4384
4425
|
}
|
|
4385
4426
|
function setQQBotCredentials(params) {
|
|
4386
|
-
const existing = params.cfg
|
|
4427
|
+
const existing = resolveQQBotChannelConfig(params.cfg) ?? {};
|
|
4387
4428
|
if (params.accountId === DEFAULT_ACCOUNT_ID) {
|
|
4388
|
-
return
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
...
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
appId: params.appId,
|
|
4396
|
-
clientSecret: params.clientSecret
|
|
4397
|
-
}
|
|
4429
|
+
return withQQBotChannelConfig(
|
|
4430
|
+
params.cfg,
|
|
4431
|
+
{
|
|
4432
|
+
...existing,
|
|
4433
|
+
enabled: true,
|
|
4434
|
+
appId: params.appId,
|
|
4435
|
+
clientSecret: params.clientSecret
|
|
4398
4436
|
}
|
|
4399
|
-
|
|
4437
|
+
);
|
|
4400
4438
|
}
|
|
4401
4439
|
const accounts = existing.accounts ?? {};
|
|
4402
|
-
return
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
...
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
...accounts,
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
appId: params.appId,
|
|
4415
|
-
clientSecret: params.clientSecret
|
|
4416
|
-
}
|
|
4440
|
+
return withQQBotChannelConfig(
|
|
4441
|
+
params.cfg,
|
|
4442
|
+
{
|
|
4443
|
+
...existing,
|
|
4444
|
+
enabled: true,
|
|
4445
|
+
accounts: {
|
|
4446
|
+
...accounts,
|
|
4447
|
+
[params.accountId]: {
|
|
4448
|
+
...accounts[params.accountId],
|
|
4449
|
+
enabled: true,
|
|
4450
|
+
appId: params.appId,
|
|
4451
|
+
clientSecret: params.clientSecret
|
|
4417
4452
|
}
|
|
4418
4453
|
}
|
|
4419
4454
|
}
|
|
4420
|
-
|
|
4455
|
+
);
|
|
4421
4456
|
}
|
|
4422
4457
|
async function noteQQBotCredentialHelp(prompter) {
|
|
4423
4458
|
await prompter.note(
|
|
@@ -4427,13 +4462,13 @@ async function noteQQBotCredentialHelp(prompter) {
|
|
|
4427
4462
|
"3) \u5728\u5F00\u53D1\u8BBE\u7F6E\u4E2D\u914D\u7F6E\u6C99\u7BB1\u6210\u5458\u6216\u6D4B\u8BD5\u7FA4",
|
|
4428
4463
|
"4) \u914D\u7F6E\u5B8C\u6210\u540E\u53EF\u4F7F\u7528 openclaw gateway \u542F\u52A8\u8FDE\u63A5",
|
|
4429
4464
|
"",
|
|
4430
|
-
|
|
4465
|
+
`\u547D\u4EE4\u884C\u4E5F\u652F\u6301\uFF1Aopenclaw channels add --channel ${QQBOT_CHANNEL_ID} --token "AppID:ClientSecret"`
|
|
4431
4466
|
].join("\n"),
|
|
4432
4467
|
"QQ Bot \u914D\u7F6E"
|
|
4433
4468
|
);
|
|
4434
4469
|
}
|
|
4435
4470
|
function resolveOnboardingAccountId(params) {
|
|
4436
|
-
const override = params.accountOverrides?.
|
|
4471
|
+
const override = params.accountOverrides?.[QQBOT_CONFIG_CHANNEL_ID]?.trim();
|
|
4437
4472
|
if (override) return override;
|
|
4438
4473
|
const defaultAccountId = resolveDefaultQQBotAccountId(params.cfg);
|
|
4439
4474
|
const accountIds = listQQBotAccountIds(params.cfg);
|
|
@@ -4450,7 +4485,7 @@ function resolveOnboardingAccountId(params) {
|
|
|
4450
4485
|
}).then((selected) => isPromptCancelled(selected) ? defaultAccountId : selected);
|
|
4451
4486
|
}
|
|
4452
4487
|
var qqbotOnboardingAdapter = {
|
|
4453
|
-
channel:
|
|
4488
|
+
channel: QQBOT_CHANNEL_ID,
|
|
4454
4489
|
getStatus: async (params) => {
|
|
4455
4490
|
const accountIds = listQQBotAccountIds(params.cfg);
|
|
4456
4491
|
const configuredAccountId = accountIds.find(
|
|
@@ -4462,7 +4497,7 @@ var qqbotOnboardingAdapter = {
|
|
|
4462
4497
|
configuredAccountId && configuredAccountId !== DEFAULT_ACCOUNT_ID ? `QQ Bot: \u5DF2\u914D\u7F6E (${configuredAccountId})` : `QQ Bot: \u5DF2\u914D\u7F6E${defaultAccountId !== DEFAULT_ACCOUNT_ID ? ` (default=${defaultAccountId})` : ""}`
|
|
4463
4498
|
] : ["QQ Bot: \u9700\u8981 AppID \u548C ClientSecret"];
|
|
4464
4499
|
return {
|
|
4465
|
-
channel:
|
|
4500
|
+
channel: QQBOT_CHANNEL_ID,
|
|
4466
4501
|
configured,
|
|
4467
4502
|
statusLines,
|
|
4468
4503
|
selectionHint: configured ? "\u5DF2\u914D\u7F6E" : "\u9700\u8981 AppID \u548C ClientSecret",
|
|
@@ -4516,15 +4551,9 @@ var qqbotOnboardingAdapter = {
|
|
|
4516
4551
|
}
|
|
4517
4552
|
return { cfg: next, accountId };
|
|
4518
4553
|
},
|
|
4519
|
-
disable: (cfg) => ({
|
|
4520
|
-
...cfg,
|
|
4521
|
-
|
|
4522
|
-
...cfg.channels,
|
|
4523
|
-
qqbot: {
|
|
4524
|
-
...cfg.channels?.qqbot ?? {},
|
|
4525
|
-
enabled: false
|
|
4526
|
-
}
|
|
4527
|
-
}
|
|
4554
|
+
disable: (cfg) => withQQBotChannelConfig(cfg, {
|
|
4555
|
+
...resolveQQBotChannelConfig(cfg) ?? {},
|
|
4556
|
+
enabled: false
|
|
4528
4557
|
})
|
|
4529
4558
|
};
|
|
4530
4559
|
|
|
@@ -6568,9 +6597,10 @@ var CONFIG_FILE_PATH = join(OPENCLAW_HOME, "openclaw.json");
|
|
|
6568
6597
|
var ANSI_RESET = "\x1B[0m";
|
|
6569
6598
|
var ANSI_LINK = "\x1B[1;4;96m";
|
|
6570
6599
|
var ANSI_BORDER = "\x1B[92m";
|
|
6600
|
+
var QQBOT_CHANNEL_ID2 = "qqbot-china";
|
|
6571
6601
|
var CHANNEL_ORDER = [
|
|
6572
6602
|
"dingtalk",
|
|
6573
|
-
|
|
6603
|
+
QQBOT_CHANNEL_ID2,
|
|
6574
6604
|
"wecom",
|
|
6575
6605
|
"wecom-app",
|
|
6576
6606
|
"wecom-kf",
|
|
@@ -6584,7 +6614,7 @@ var CHANNEL_DISPLAY_LABELS = {
|
|
|
6584
6614
|
"wecom-app": "WeCom App\uFF08\u81EA\u5EFA\u5E94\u7528-\u53EF\u63A5\u5165\u5FAE\u4FE1\uFF09",
|
|
6585
6615
|
"wecom-kf": "WeCom KF\uFF08\u5FAE\u4FE1\u5BA2\u670D\uFF09",
|
|
6586
6616
|
"wechat-mp": "WeChat MP\uFF08\u5FAE\u4FE1\u516C\u4F17\u53F7\uFF09",
|
|
6587
|
-
qqbot: "QQBot\uFF08QQ \u673A\u5668\u4EBA\uFF09"
|
|
6617
|
+
"qqbot-china": "QQBot\uFF08QQ \u673A\u5668\u4EBA\uFF09"
|
|
6588
6618
|
};
|
|
6589
6619
|
var CHANNEL_GUIDE_LINKS = {
|
|
6590
6620
|
dingtalk: `${GUIDES_BASE}/dingtalk/configuration.md`,
|
|
@@ -6593,7 +6623,7 @@ var CHANNEL_GUIDE_LINKS = {
|
|
|
6593
6623
|
"wecom-app": `${GUIDES_BASE}/wecom-app/configuration.md`,
|
|
6594
6624
|
"wecom-kf": "https://github.com/BytePioneer-AI/openclaw-china/blob/main/extensions/wecom-kf/README.md",
|
|
6595
6625
|
"wechat-mp": `${GUIDES_BASE}/wechat-mp/configuration.md`,
|
|
6596
|
-
qqbot: `${GUIDES_BASE}/qqbot/configuration.md`
|
|
6626
|
+
"qqbot-china": `${GUIDES_BASE}/qqbot/configuration.md`
|
|
6597
6627
|
};
|
|
6598
6628
|
var CHINA_CLI_STATE_KEY = /* @__PURE__ */ Symbol.for("@openclaw-china/china-cli-state");
|
|
6599
6629
|
var PromptCancelledError = class extends Error {
|
|
@@ -6795,7 +6825,7 @@ function isChannelConfigured(cfg, channelId) {
|
|
|
6795
6825
|
return hasNonEmptyString(channelCfg.clientId) && hasNonEmptyString(channelCfg.clientSecret);
|
|
6796
6826
|
case "feishu-china":
|
|
6797
6827
|
return hasNonEmptyString(channelCfg.appId) && hasNonEmptyString(channelCfg.appSecret);
|
|
6798
|
-
case "qqbot":
|
|
6828
|
+
case "qqbot-china":
|
|
6799
6829
|
return hasNonEmptyString(channelCfg.appId) && hasNonEmptyString(channelCfg.clientSecret);
|
|
6800
6830
|
case "wecom":
|
|
6801
6831
|
return hasWecomWsCredentialPair(channelCfg);
|
|
@@ -6816,11 +6846,12 @@ function withConfiguredSuffix(cfg, channelId) {
|
|
|
6816
6846
|
function mergeChannelConfig(cfg, channelId, patch) {
|
|
6817
6847
|
const channels = isRecord(cfg.channels) ? { ...cfg.channels } : {};
|
|
6818
6848
|
const existing = getChannelConfig(cfg, channelId);
|
|
6819
|
-
|
|
6849
|
+
const nextChannelConfig = {
|
|
6820
6850
|
...existing,
|
|
6821
6851
|
...patch,
|
|
6822
6852
|
enabled: true
|
|
6823
6853
|
};
|
|
6854
|
+
channels[channelId] = nextChannelConfig;
|
|
6824
6855
|
return {
|
|
6825
6856
|
...cfg,
|
|
6826
6857
|
channels
|
|
@@ -7197,8 +7228,8 @@ async function configureWechatMp(prompter, cfg) {
|
|
|
7197
7228
|
}
|
|
7198
7229
|
async function configureQQBot(prompter, cfg) {
|
|
7199
7230
|
section("\u914D\u7F6E QQBot\uFF08QQ \u673A\u5668\u4EBA\uFF09");
|
|
7200
|
-
showGuideLink(
|
|
7201
|
-
const existing = getChannelConfig(cfg,
|
|
7231
|
+
showGuideLink(QQBOT_CHANNEL_ID2);
|
|
7232
|
+
const existing = getChannelConfig(cfg, QQBOT_CHANNEL_ID2);
|
|
7202
7233
|
const existingAsr = isRecord(existing.asr) ? existing.asr : {};
|
|
7203
7234
|
const appId = await prompter.askText({
|
|
7204
7235
|
label: "QQBot appId",
|
|
@@ -7235,7 +7266,7 @@ async function configureQQBot(prompter, cfg) {
|
|
|
7235
7266
|
required: true
|
|
7236
7267
|
});
|
|
7237
7268
|
}
|
|
7238
|
-
return mergeChannelConfig(cfg,
|
|
7269
|
+
return mergeChannelConfig(cfg, QQBOT_CHANNEL_ID2, {
|
|
7239
7270
|
appId,
|
|
7240
7271
|
clientSecret,
|
|
7241
7272
|
asr
|
|
@@ -7255,7 +7286,7 @@ async function configureSingleChannel(channel, prompter, cfg) {
|
|
|
7255
7286
|
return configureWecomKf(prompter, cfg);
|
|
7256
7287
|
case "wechat-mp":
|
|
7257
7288
|
return configureWechatMp(prompter, cfg);
|
|
7258
|
-
case "qqbot":
|
|
7289
|
+
case "qqbot-china":
|
|
7259
7290
|
return configureQQBot(prompter, cfg);
|
|
7260
7291
|
default:
|
|
7261
7292
|
return cfg;
|
|
@@ -7397,7 +7428,7 @@ var SUPPORTED_CHANNELS = [
|
|
|
7397
7428
|
"wecom-app",
|
|
7398
7429
|
"wecom-kf",
|
|
7399
7430
|
"wechat-mp",
|
|
7400
|
-
"qqbot"
|
|
7431
|
+
"qqbot-china"
|
|
7401
7432
|
];
|
|
7402
7433
|
var CHINA_INSTALL_HINT_SHOWN_KEY = /* @__PURE__ */ Symbol.for("@openclaw-china/china-install-hint-shown");
|
|
7403
7434
|
function isRecord2(value) {
|
|
@@ -7502,6 +7533,9 @@ function nextMsgSeq(sequenceKey) {
|
|
|
7502
7533
|
}
|
|
7503
7534
|
return MSG_SEQ_BASE + next;
|
|
7504
7535
|
}
|
|
7536
|
+
function allocateMsgSeq(sequenceKey) {
|
|
7537
|
+
return nextMsgSeq(sequenceKey);
|
|
7538
|
+
}
|
|
7505
7539
|
function resolveMsgSeqKey(messageId, eventId) {
|
|
7506
7540
|
if (messageId) return `msg:${messageId}`;
|
|
7507
7541
|
if (eventId) return `event:${eventId}`;
|
|
@@ -7609,6 +7643,16 @@ async function getGatewayUrl(accessToken) {
|
|
|
7609
7643
|
const data = await apiGet(accessToken, "/gateway", { timeout: 15e3 });
|
|
7610
7644
|
return data.url;
|
|
7611
7645
|
}
|
|
7646
|
+
var QQBotStreamInputMode = {
|
|
7647
|
+
REPLACE: "replace"
|
|
7648
|
+
};
|
|
7649
|
+
var QQBotStreamInputState = {
|
|
7650
|
+
GENERATING: 1,
|
|
7651
|
+
DONE: 10
|
|
7652
|
+
};
|
|
7653
|
+
var QQBotStreamContentType = {
|
|
7654
|
+
MARKDOWN: "markdown"
|
|
7655
|
+
};
|
|
7612
7656
|
function buildMessageBody(params) {
|
|
7613
7657
|
const body = buildTextMessageBody({
|
|
7614
7658
|
content: params.content,
|
|
@@ -7766,6 +7810,24 @@ async function sendC2CMediaMessage(params) {
|
|
|
7766
7810
|
})
|
|
7767
7811
|
});
|
|
7768
7812
|
}
|
|
7813
|
+
async function sendC2CStreamMessage(params) {
|
|
7814
|
+
const body = {
|
|
7815
|
+
input_mode: params.request.input_mode,
|
|
7816
|
+
input_state: params.request.input_state,
|
|
7817
|
+
content_type: params.request.content_type,
|
|
7818
|
+
content_raw: params.request.content_raw,
|
|
7819
|
+
event_id: params.request.event_id,
|
|
7820
|
+
msg_id: params.request.msg_id,
|
|
7821
|
+
msg_seq: params.request.msg_seq,
|
|
7822
|
+
index: params.request.index
|
|
7823
|
+
};
|
|
7824
|
+
if (params.request.stream_msg_id) {
|
|
7825
|
+
body.stream_msg_id = params.request.stream_msg_id;
|
|
7826
|
+
}
|
|
7827
|
+
return apiPost(params.accessToken, `/v2/users/${params.openid}/stream_messages`, body, {
|
|
7828
|
+
timeout: 15e3
|
|
7829
|
+
});
|
|
7830
|
+
}
|
|
7769
7831
|
async function sendGroupMediaMessage(params) {
|
|
7770
7832
|
return postPassiveMessage({
|
|
7771
7833
|
accessToken: params.accessToken,
|
|
@@ -8219,12 +8281,8 @@ async function readMediaWithConfig(source, options) {
|
|
|
8219
8281
|
}
|
|
8220
8282
|
|
|
8221
8283
|
// src/outbound.ts
|
|
8222
|
-
function stripPrefix(value, prefix) {
|
|
8223
|
-
return value.slice(0, prefix.length).toLowerCase() === prefix ? value.slice(prefix.length) : value;
|
|
8224
|
-
}
|
|
8225
8284
|
function parseTarget(to) {
|
|
8226
|
-
let raw = to
|
|
8227
|
-
raw = stripPrefix(raw, "qqbot:");
|
|
8285
|
+
let raw = stripQQBotChannelPrefix(to);
|
|
8228
8286
|
const normalizedRaw = raw.toLowerCase();
|
|
8229
8287
|
if (normalizedRaw.startsWith("group:")) {
|
|
8230
8288
|
return { kind: "group", id: raw.slice("group:".length) };
|
|
@@ -8391,7 +8449,7 @@ var qqbotOutbound = {
|
|
|
8391
8449
|
const qqCfg = mergeQQBotAccountConfig(cfg, accountId ?? DEFAULT_ACCOUNT_ID);
|
|
8392
8450
|
const credentials = resolveQQBotCredentials(qqCfg);
|
|
8393
8451
|
if (!credentials) {
|
|
8394
|
-
return { channel:
|
|
8452
|
+
return { channel: QQBOT_CHANNEL_ID, error: "QQBot not configured (missing appId/clientSecret)" };
|
|
8395
8453
|
}
|
|
8396
8454
|
const target = parseTarget(to);
|
|
8397
8455
|
const accessToken = await getAccessToken(credentials.appId, credentials.clientSecret);
|
|
@@ -8415,7 +8473,7 @@ var qqbotOutbound = {
|
|
|
8415
8473
|
content: text,
|
|
8416
8474
|
markdown
|
|
8417
8475
|
});
|
|
8418
|
-
return { channel:
|
|
8476
|
+
return { channel: QQBOT_CHANNEL_ID, messageId: result3.id, timestamp: result3.timestamp };
|
|
8419
8477
|
}
|
|
8420
8478
|
let result2;
|
|
8421
8479
|
try {
|
|
@@ -8482,7 +8540,7 @@ var qqbotOutbound = {
|
|
|
8482
8540
|
throw retryErr;
|
|
8483
8541
|
}
|
|
8484
8542
|
}
|
|
8485
|
-
return { channel:
|
|
8543
|
+
return { channel: QQBOT_CHANNEL_ID, messageId: result2.id, timestamp: result2.timestamp };
|
|
8486
8544
|
}
|
|
8487
8545
|
if (target.kind === "channel") {
|
|
8488
8546
|
logQQBotOutboundDispatch({
|
|
@@ -8500,7 +8558,7 @@ var qqbotOutbound = {
|
|
|
8500
8558
|
content: text,
|
|
8501
8559
|
messageId: replyToId
|
|
8502
8560
|
});
|
|
8503
|
-
return { channel:
|
|
8561
|
+
return { channel: QQBOT_CHANNEL_ID, messageId: result2.id, timestamp: result2.timestamp };
|
|
8504
8562
|
}
|
|
8505
8563
|
if (!replyToId && !replyEventId) {
|
|
8506
8564
|
logQQBotOutboundDispatch({
|
|
@@ -8521,7 +8579,7 @@ var qqbotOutbound = {
|
|
|
8521
8579
|
const refIdx2 = resolveResponseRefIdx(result2);
|
|
8522
8580
|
recordOutboundC2CRefIndex({ refIdx: refIdx2, accountId, text });
|
|
8523
8581
|
return {
|
|
8524
|
-
channel:
|
|
8582
|
+
channel: QQBOT_CHANNEL_ID,
|
|
8525
8583
|
messageId: result2.id,
|
|
8526
8584
|
timestamp: result2.timestamp,
|
|
8527
8585
|
...refIdx2 ? { refIdx: refIdx2 } : {}
|
|
@@ -8595,14 +8653,14 @@ var qqbotOutbound = {
|
|
|
8595
8653
|
const refIdx = resolveResponseRefIdx(result);
|
|
8596
8654
|
recordOutboundC2CRefIndex({ refIdx, accountId, text });
|
|
8597
8655
|
return {
|
|
8598
|
-
channel:
|
|
8656
|
+
channel: QQBOT_CHANNEL_ID,
|
|
8599
8657
|
messageId: result.id,
|
|
8600
8658
|
timestamp: result.timestamp,
|
|
8601
8659
|
...refIdx ? { refIdx } : {}
|
|
8602
8660
|
};
|
|
8603
8661
|
} catch (err) {
|
|
8604
8662
|
const message = summarizeError(err);
|
|
8605
|
-
return { channel:
|
|
8663
|
+
return { channel: QQBOT_CHANNEL_ID, error: message };
|
|
8606
8664
|
}
|
|
8607
8665
|
},
|
|
8608
8666
|
sendMedia: async (params) => {
|
|
@@ -8610,13 +8668,13 @@ var qqbotOutbound = {
|
|
|
8610
8668
|
if (!mediaUrl) {
|
|
8611
8669
|
const fallbackText = text?.trim() ?? "";
|
|
8612
8670
|
if (!fallbackText) {
|
|
8613
|
-
return { channel:
|
|
8671
|
+
return { channel: QQBOT_CHANNEL_ID, error: "mediaUrl is required for sendMedia" };
|
|
8614
8672
|
}
|
|
8615
8673
|
return qqbotOutbound.sendText({ cfg, to, text: fallbackText, replyToId, replyEventId, accountId });
|
|
8616
8674
|
}
|
|
8617
8675
|
const qqCfg = mergeQQBotAccountConfig(cfg, accountId ?? DEFAULT_ACCOUNT_ID);
|
|
8618
8676
|
if (!resolveQQBotCredentials(qqCfg)) {
|
|
8619
|
-
return { channel:
|
|
8677
|
+
return { channel: QQBOT_CHANNEL_ID, error: "QQBot not configured (missing appId/clientSecret)" };
|
|
8620
8678
|
}
|
|
8621
8679
|
const target = parseTarget(to);
|
|
8622
8680
|
const trimmedText = text?.trim() ? text.trim() : void 0;
|
|
@@ -8703,7 +8761,7 @@ ${mediaUrl}` : mediaUrl;
|
|
|
8703
8761
|
});
|
|
8704
8762
|
if (textResult.error) {
|
|
8705
8763
|
return {
|
|
8706
|
-
channel:
|
|
8764
|
+
channel: QQBOT_CHANNEL_ID,
|
|
8707
8765
|
error: `QQBot follow-up text send failed after media delivery: ${textResult.error}`
|
|
8708
8766
|
};
|
|
8709
8767
|
}
|
|
@@ -8718,14 +8776,14 @@ ${mediaUrl}` : mediaUrl;
|
|
|
8718
8776
|
});
|
|
8719
8777
|
}
|
|
8720
8778
|
return {
|
|
8721
|
-
channel:
|
|
8779
|
+
channel: QQBOT_CHANNEL_ID,
|
|
8722
8780
|
messageId: result.id,
|
|
8723
8781
|
timestamp: result.timestamp,
|
|
8724
8782
|
...refIdx ? { refIdx } : {}
|
|
8725
8783
|
};
|
|
8726
8784
|
} catch (err) {
|
|
8727
8785
|
const message = summarizeError(err);
|
|
8728
|
-
return { channel:
|
|
8786
|
+
return { channel: QQBOT_CHANNEL_ID, error: message };
|
|
8729
8787
|
}
|
|
8730
8788
|
},
|
|
8731
8789
|
sendTyping: async (params) => {
|
|
@@ -8733,11 +8791,11 @@ ${mediaUrl}` : mediaUrl;
|
|
|
8733
8791
|
const qqCfg = mergeQQBotAccountConfig(cfg, accountId ?? DEFAULT_ACCOUNT_ID);
|
|
8734
8792
|
const credentials = resolveQQBotCredentials(qqCfg);
|
|
8735
8793
|
if (!credentials) {
|
|
8736
|
-
return { channel:
|
|
8794
|
+
return { channel: QQBOT_CHANNEL_ID, error: "QQBot not configured (missing appId/clientSecret)" };
|
|
8737
8795
|
}
|
|
8738
8796
|
const target = parseTarget(to);
|
|
8739
8797
|
if (target.kind !== "c2c") {
|
|
8740
|
-
return { channel:
|
|
8798
|
+
return { channel: QQBOT_CHANNEL_ID };
|
|
8741
8799
|
}
|
|
8742
8800
|
try {
|
|
8743
8801
|
const accessToken = await getAccessToken(credentials.appId, credentials.clientSecret);
|
|
@@ -8795,12 +8853,12 @@ ${mediaUrl}` : mediaUrl;
|
|
|
8795
8853
|
}
|
|
8796
8854
|
}
|
|
8797
8855
|
return {
|
|
8798
|
-
channel:
|
|
8856
|
+
channel: QQBOT_CHANNEL_ID,
|
|
8799
8857
|
...typingResult?.refIdx ? { refIdx: typingResult.refIdx } : {}
|
|
8800
8858
|
};
|
|
8801
8859
|
} catch (err) {
|
|
8802
8860
|
const message = summarizeError(err);
|
|
8803
|
-
return { channel:
|
|
8861
|
+
return { channel: QQBOT_CHANNEL_ID, error: message };
|
|
8804
8862
|
}
|
|
8805
8863
|
}
|
|
8806
8864
|
};
|
|
@@ -9248,7 +9306,7 @@ function clearKnownQQBotTargets(params = {}) {
|
|
|
9248
9306
|
async function sendProactiveQQBotMessage(params) {
|
|
9249
9307
|
const to = params.to.trim();
|
|
9250
9308
|
if (!to) {
|
|
9251
|
-
return { channel:
|
|
9309
|
+
return { channel: QQBOT_CHANNEL_ID, error: "to is required for proactive send" };
|
|
9252
9310
|
}
|
|
9253
9311
|
if (params.mediaUrl?.trim()) {
|
|
9254
9312
|
return qqbotOutbound.sendMedia({
|
|
@@ -9261,7 +9319,7 @@ async function sendProactiveQQBotMessage(params) {
|
|
|
9261
9319
|
}
|
|
9262
9320
|
const text = params.text?.trim();
|
|
9263
9321
|
if (!text) {
|
|
9264
|
-
return { channel:
|
|
9322
|
+
return { channel: QQBOT_CHANNEL_ID, error: "text or mediaUrl is required for proactive send" };
|
|
9265
9323
|
}
|
|
9266
9324
|
return qqbotOutbound.sendText({
|
|
9267
9325
|
cfg: params.cfg,
|
|
@@ -9282,6 +9340,263 @@ function getQQBotRuntime() {
|
|
|
9282
9340
|
}
|
|
9283
9341
|
return runtime;
|
|
9284
9342
|
}
|
|
9343
|
+
|
|
9344
|
+
// src/streaming.ts
|
|
9345
|
+
var DEFAULT_THROTTLE_MS = 500;
|
|
9346
|
+
var DEFAULT_MIN_THROTTLE_MS = 300;
|
|
9347
|
+
var QQBotStreamingController = class {
|
|
9348
|
+
params;
|
|
9349
|
+
throttleMs;
|
|
9350
|
+
chain = Promise.resolve();
|
|
9351
|
+
flushTimer = null;
|
|
9352
|
+
startPromise = null;
|
|
9353
|
+
latestText = "";
|
|
9354
|
+
lastSentText = "";
|
|
9355
|
+
streamMsgId;
|
|
9356
|
+
msgSeq;
|
|
9357
|
+
index = 0;
|
|
9358
|
+
lastPartialLength = 0;
|
|
9359
|
+
lastSendAt = 0;
|
|
9360
|
+
sessionSentChunkCount = 0;
|
|
9361
|
+
sessionShouldFallbackToStatic = false;
|
|
9362
|
+
firstChunkNotified = false;
|
|
9363
|
+
replyOrdinal = 0;
|
|
9364
|
+
disposed = false;
|
|
9365
|
+
constructor(params) {
|
|
9366
|
+
this.params = params;
|
|
9367
|
+
const throttle = params.throttleMs ?? DEFAULT_THROTTLE_MS;
|
|
9368
|
+
const minThrottle = params.minThrottleMs ?? DEFAULT_MIN_THROTTLE_MS;
|
|
9369
|
+
this.throttleMs = Math.max(throttle, minThrottle);
|
|
9370
|
+
}
|
|
9371
|
+
get hasSuccessfulChunk() {
|
|
9372
|
+
return this.sessionSentChunkCount > 0;
|
|
9373
|
+
}
|
|
9374
|
+
get shouldFallbackToStatic() {
|
|
9375
|
+
return this.sessionShouldFallbackToStatic && this.sessionSentChunkCount === 0;
|
|
9376
|
+
}
|
|
9377
|
+
get hasObservedPartial() {
|
|
9378
|
+
return this.lastPartialLength > 0;
|
|
9379
|
+
}
|
|
9380
|
+
async onPartialReply(text) {
|
|
9381
|
+
await this.enqueue(async () => {
|
|
9382
|
+
if (this.disposed) return;
|
|
9383
|
+
if (this.lastPartialLength > 0 && text.length < this.lastPartialLength) {
|
|
9384
|
+
this.logInfo(
|
|
9385
|
+
`reply boundary detected (${text.length} < ${this.lastPartialLength}), starting new stream session`
|
|
9386
|
+
);
|
|
9387
|
+
await this.finalizeCurrentReply();
|
|
9388
|
+
this.resetReplyState();
|
|
9389
|
+
}
|
|
9390
|
+
this.lastPartialLength = text.length;
|
|
9391
|
+
this.latestText = text;
|
|
9392
|
+
if (!text.trim() || this.sessionShouldFallbackToStatic) {
|
|
9393
|
+
return;
|
|
9394
|
+
}
|
|
9395
|
+
if (!this.streamMsgId) {
|
|
9396
|
+
await this.ensureStreamingStarted();
|
|
9397
|
+
return;
|
|
9398
|
+
}
|
|
9399
|
+
this.scheduleFlush();
|
|
9400
|
+
});
|
|
9401
|
+
}
|
|
9402
|
+
async finalize() {
|
|
9403
|
+
await this.enqueue(async () => {
|
|
9404
|
+
await this.finalizeCurrentReply();
|
|
9405
|
+
});
|
|
9406
|
+
}
|
|
9407
|
+
dispose() {
|
|
9408
|
+
this.disposed = true;
|
|
9409
|
+
this.clearFlushTimer();
|
|
9410
|
+
}
|
|
9411
|
+
enqueue(task) {
|
|
9412
|
+
this.chain = this.chain.then(task, async (err) => {
|
|
9413
|
+
this.logError(`stream queue recovered after error: ${String(err)}`);
|
|
9414
|
+
await task();
|
|
9415
|
+
});
|
|
9416
|
+
return this.chain;
|
|
9417
|
+
}
|
|
9418
|
+
async ensureStreamingStarted() {
|
|
9419
|
+
if (this.disposed || this.streamMsgId || this.sessionShouldFallbackToStatic) {
|
|
9420
|
+
return;
|
|
9421
|
+
}
|
|
9422
|
+
if (this.startPromise) {
|
|
9423
|
+
await this.startPromise;
|
|
9424
|
+
return;
|
|
9425
|
+
}
|
|
9426
|
+
this.startPromise = this.startStreaming();
|
|
9427
|
+
try {
|
|
9428
|
+
await this.startPromise;
|
|
9429
|
+
} finally {
|
|
9430
|
+
this.startPromise = null;
|
|
9431
|
+
}
|
|
9432
|
+
}
|
|
9433
|
+
async startStreaming() {
|
|
9434
|
+
if (!this.latestText.trim()) {
|
|
9435
|
+
return;
|
|
9436
|
+
}
|
|
9437
|
+
try {
|
|
9438
|
+
this.msgSeq ??= allocateMsgSeq(`stream:${this.params.messageId}:${this.replyOrdinal}`);
|
|
9439
|
+
const response = await this.sendChunk({
|
|
9440
|
+
content: this.latestText,
|
|
9441
|
+
inputState: QQBotStreamInputState.GENERATING
|
|
9442
|
+
});
|
|
9443
|
+
if (!response.id) {
|
|
9444
|
+
throw new Error("QQ stream response missing stream message id");
|
|
9445
|
+
}
|
|
9446
|
+
this.streamMsgId = response.id;
|
|
9447
|
+
this.lastSentText = this.latestText;
|
|
9448
|
+
this.lastSendAt = Date.now();
|
|
9449
|
+
this.sessionSentChunkCount += 1;
|
|
9450
|
+
this.index += 1;
|
|
9451
|
+
await this.notifyFirstChunk();
|
|
9452
|
+
if (this.latestText !== this.lastSentText) {
|
|
9453
|
+
this.scheduleFlush();
|
|
9454
|
+
}
|
|
9455
|
+
} catch (err) {
|
|
9456
|
+
this.sessionShouldFallbackToStatic = true;
|
|
9457
|
+
this.logWarn(`failed to start stream session, falling back to static: ${String(err)}`);
|
|
9458
|
+
}
|
|
9459
|
+
}
|
|
9460
|
+
async flushNow() {
|
|
9461
|
+
if (this.disposed || !this.streamMsgId || this.sessionShouldFallbackToStatic || !this.latestText.trim() || this.latestText === this.lastSentText) {
|
|
9462
|
+
return;
|
|
9463
|
+
}
|
|
9464
|
+
try {
|
|
9465
|
+
await this.sendChunk({
|
|
9466
|
+
content: this.latestText,
|
|
9467
|
+
inputState: QQBotStreamInputState.GENERATING
|
|
9468
|
+
});
|
|
9469
|
+
this.lastSentText = this.latestText;
|
|
9470
|
+
this.lastSendAt = Date.now();
|
|
9471
|
+
this.sessionSentChunkCount += 1;
|
|
9472
|
+
this.index += 1;
|
|
9473
|
+
await this.notifyFirstChunk();
|
|
9474
|
+
} catch (err) {
|
|
9475
|
+
this.logWarn(`failed to flush stream chunk: ${String(err)}`);
|
|
9476
|
+
}
|
|
9477
|
+
}
|
|
9478
|
+
scheduleFlush() {
|
|
9479
|
+
if (this.disposed || this.flushTimer || !this.streamMsgId || this.sessionShouldFallbackToStatic) {
|
|
9480
|
+
return;
|
|
9481
|
+
}
|
|
9482
|
+
const elapsed = Date.now() - this.lastSendAt;
|
|
9483
|
+
if (elapsed >= this.throttleMs) {
|
|
9484
|
+
void this.enqueue(async () => {
|
|
9485
|
+
await this.flushNow();
|
|
9486
|
+
});
|
|
9487
|
+
return;
|
|
9488
|
+
}
|
|
9489
|
+
const waitMs = this.throttleMs - elapsed;
|
|
9490
|
+
this.flushTimer = setTimeout(() => {
|
|
9491
|
+
this.flushTimer = null;
|
|
9492
|
+
void this.enqueue(async () => {
|
|
9493
|
+
await this.flushNow();
|
|
9494
|
+
});
|
|
9495
|
+
}, waitMs);
|
|
9496
|
+
this.flushTimer.unref?.();
|
|
9497
|
+
}
|
|
9498
|
+
async finalizeCurrentReply() {
|
|
9499
|
+
this.clearFlushTimer();
|
|
9500
|
+
if (this.startPromise) {
|
|
9501
|
+
await this.startPromise;
|
|
9502
|
+
}
|
|
9503
|
+
if (!this.streamMsgId) {
|
|
9504
|
+
return;
|
|
9505
|
+
}
|
|
9506
|
+
const finalText = (this.latestText || this.lastSentText).trim() ? this.latestText || this.lastSentText : this.lastSentText;
|
|
9507
|
+
if (!finalText) {
|
|
9508
|
+
this.resetStreamSession();
|
|
9509
|
+
return;
|
|
9510
|
+
}
|
|
9511
|
+
try {
|
|
9512
|
+
await this.sendChunk({
|
|
9513
|
+
content: finalText,
|
|
9514
|
+
inputState: QQBotStreamInputState.DONE
|
|
9515
|
+
});
|
|
9516
|
+
this.lastSentText = finalText;
|
|
9517
|
+
this.lastSendAt = Date.now();
|
|
9518
|
+
this.sessionSentChunkCount += 1;
|
|
9519
|
+
this.index += 1;
|
|
9520
|
+
} catch (err) {
|
|
9521
|
+
this.logWarn(`failed to finalize stream session: ${String(err)}`);
|
|
9522
|
+
} finally {
|
|
9523
|
+
this.resetStreamSession();
|
|
9524
|
+
}
|
|
9525
|
+
}
|
|
9526
|
+
async sendChunk(params) {
|
|
9527
|
+
const msgSeq = this.msgSeq ?? allocateMsgSeq(`stream:${this.params.messageId}:${this.replyOrdinal}`);
|
|
9528
|
+
this.msgSeq = msgSeq;
|
|
9529
|
+
const accessToken = await getAccessToken(this.params.appId, this.params.clientSecret);
|
|
9530
|
+
const response = await sendC2CStreamMessage({
|
|
9531
|
+
accessToken,
|
|
9532
|
+
openid: this.params.openid,
|
|
9533
|
+
request: {
|
|
9534
|
+
input_mode: QQBotStreamInputMode.REPLACE,
|
|
9535
|
+
input_state: params.inputState,
|
|
9536
|
+
content_type: QQBotStreamContentType.MARKDOWN,
|
|
9537
|
+
content_raw: params.content,
|
|
9538
|
+
event_id: this.params.eventId,
|
|
9539
|
+
msg_id: this.params.messageId,
|
|
9540
|
+
msg_seq: msgSeq,
|
|
9541
|
+
index: this.index,
|
|
9542
|
+
...this.streamMsgId ? { stream_msg_id: this.streamMsgId } : {}
|
|
9543
|
+
}
|
|
9544
|
+
});
|
|
9545
|
+
if (response.code && response.code > 0) {
|
|
9546
|
+
throw new Error(`QQ stream API error ${response.code}: ${response.message ?? "unknown error"}`);
|
|
9547
|
+
}
|
|
9548
|
+
return {
|
|
9549
|
+
id: typeof response.id === "string" ? response.id : void 0
|
|
9550
|
+
};
|
|
9551
|
+
}
|
|
9552
|
+
async notifyFirstChunk() {
|
|
9553
|
+
if (this.firstChunkNotified) {
|
|
9554
|
+
return;
|
|
9555
|
+
}
|
|
9556
|
+
this.firstChunkNotified = true;
|
|
9557
|
+
try {
|
|
9558
|
+
await this.params.onFirstChunk?.();
|
|
9559
|
+
} catch (err) {
|
|
9560
|
+
this.logWarn(`onFirstChunk hook failed: ${String(err)}`);
|
|
9561
|
+
}
|
|
9562
|
+
}
|
|
9563
|
+
clearFlushTimer() {
|
|
9564
|
+
if (!this.flushTimer) {
|
|
9565
|
+
return;
|
|
9566
|
+
}
|
|
9567
|
+
clearTimeout(this.flushTimer);
|
|
9568
|
+
this.flushTimer = null;
|
|
9569
|
+
}
|
|
9570
|
+
resetReplyState() {
|
|
9571
|
+
this.replyOrdinal += 1;
|
|
9572
|
+
this.lastPartialLength = 0;
|
|
9573
|
+
this.latestText = "";
|
|
9574
|
+
this.lastSentText = "";
|
|
9575
|
+
this.sessionSentChunkCount = 0;
|
|
9576
|
+
this.sessionShouldFallbackToStatic = false;
|
|
9577
|
+
this.firstChunkNotified = false;
|
|
9578
|
+
this.resetStreamSession();
|
|
9579
|
+
}
|
|
9580
|
+
resetStreamSession() {
|
|
9581
|
+
this.streamMsgId = void 0;
|
|
9582
|
+
this.msgSeq = void 0;
|
|
9583
|
+
this.index = 0;
|
|
9584
|
+
this.lastSendAt = 0;
|
|
9585
|
+
this.clearFlushTimer();
|
|
9586
|
+
}
|
|
9587
|
+
logInfo(message) {
|
|
9588
|
+
const next = `${this.params.logPrefix ?? "[qqbot:streaming]"} ${message}`;
|
|
9589
|
+
this.params.logger?.info?.(next);
|
|
9590
|
+
}
|
|
9591
|
+
logWarn(message) {
|
|
9592
|
+
const next = `${this.params.logPrefix ?? "[qqbot:streaming]"} ${message}`;
|
|
9593
|
+
(this.params.logger?.warn ?? this.params.logger?.info)?.(next);
|
|
9594
|
+
}
|
|
9595
|
+
logError(message) {
|
|
9596
|
+
const next = `${this.params.logPrefix ?? "[qqbot:streaming]"} ${message}`;
|
|
9597
|
+
this.params.logger?.error?.(next);
|
|
9598
|
+
}
|
|
9599
|
+
};
|
|
9285
9600
|
var sessionDispatchQueue = /* @__PURE__ */ new Map();
|
|
9286
9601
|
var QQBOT_ABORT_TRIGGERS = /* @__PURE__ */ new Set([
|
|
9287
9602
|
"stop",
|
|
@@ -9328,6 +9643,17 @@ var QQBOT_ABORT_TRIGGERS = /* @__PURE__ */ new Set([
|
|
|
9328
9643
|
"stop please"
|
|
9329
9644
|
]);
|
|
9330
9645
|
var QQBOT_ABORT_TRAILING_PUNCTUATION_RE = /[.!?…,,。;;::'"’”)\]}]+$/u;
|
|
9646
|
+
var QQBOT_ROUTE_PREFIX_RE = new RegExp(
|
|
9647
|
+
`^(agent:[^:]+:${QQBOT_CHANNEL_ID}:)(?:direct|dm):.+$`,
|
|
9648
|
+
"i"
|
|
9649
|
+
);
|
|
9650
|
+
function buildQQBotScopedConfig(qqCfg) {
|
|
9651
|
+
return {
|
|
9652
|
+
channels: {
|
|
9653
|
+
[QQBOT_CONFIG_CHANNEL_ID]: qqCfg
|
|
9654
|
+
}
|
|
9655
|
+
};
|
|
9656
|
+
}
|
|
9331
9657
|
function resolveQQBotRouteSessionKey(route) {
|
|
9332
9658
|
const effectiveSessionKey = route.effectiveSessionKey?.trim();
|
|
9333
9659
|
if (effectiveSessionKey) {
|
|
@@ -9442,9 +9768,9 @@ function buildQQBotDirectSessionKey(params) {
|
|
|
9442
9768
|
const normalizedSenderId = normalizeQQBotSessionKeyPart(params.senderStableId);
|
|
9443
9769
|
const trimmedRouteSessionKey = params.routeSessionKey.trim();
|
|
9444
9770
|
if (!trimmedRouteSessionKey) {
|
|
9445
|
-
return `agent:main:
|
|
9771
|
+
return `agent:main:${QQBOT_CHANNEL_ID}:dm:${normalizedAccountId}:${normalizedSenderId}`;
|
|
9446
9772
|
}
|
|
9447
|
-
const qqAgentRouteMatch = trimmedRouteSessionKey.match(
|
|
9773
|
+
const qqAgentRouteMatch = trimmedRouteSessionKey.match(QQBOT_ROUTE_PREFIX_RE);
|
|
9448
9774
|
if (qqAgentRouteMatch?.[1]) {
|
|
9449
9775
|
return `${qqAgentRouteMatch[1]}dm:${normalizedAccountId}:${normalizedSenderId}`;
|
|
9450
9776
|
}
|
|
@@ -9458,9 +9784,7 @@ function normalizeQQBotReplyTarget(value) {
|
|
|
9458
9784
|
if (!trimmed) {
|
|
9459
9785
|
return void 0;
|
|
9460
9786
|
}
|
|
9461
|
-
|
|
9462
|
-
trimmed = trimmed.slice("qqbot:".length).trim();
|
|
9463
|
-
}
|
|
9787
|
+
trimmed = stripQQBotChannelPrefix(trimmed);
|
|
9464
9788
|
if (/^c2c:/i.test(trimmed)) {
|
|
9465
9789
|
const openid = trimmed.slice("c2c:".length).trim();
|
|
9466
9790
|
return openid ? `user:${openid}` : void 0;
|
|
@@ -9569,7 +9893,7 @@ function normalizeQQBotDisplayAliasesMap(raw) {
|
|
|
9569
9893
|
return aliases;
|
|
9570
9894
|
}
|
|
9571
9895
|
function resolveQQBotDisplayAliasMaps(cfg, accountId) {
|
|
9572
|
-
const qqbot = cfg
|
|
9896
|
+
const qqbot = resolveQQBotChannelConfig(cfg);
|
|
9573
9897
|
return {
|
|
9574
9898
|
globalAliases: normalizeQQBotDisplayAliasesMap(qqbot?.displayAliases),
|
|
9575
9899
|
accountAliases: normalizeQQBotDisplayAliasesMap(qqbot?.accounts?.[accountId]?.displayAliases)
|
|
@@ -10413,8 +10737,7 @@ function evaluateReplyFinalOnlyDelivery(params) {
|
|
|
10413
10737
|
return { skipDelivery: true, suppressText: false };
|
|
10414
10738
|
}
|
|
10415
10739
|
function isQQBotC2CTarget(to) {
|
|
10416
|
-
const
|
|
10417
|
-
const raw = trimmed.slice(0, "qqbot:".length).toLowerCase() === "qqbot:" ? trimmed.slice("qqbot:".length) : trimmed;
|
|
10740
|
+
const raw = stripQQBotChannelPrefix(to);
|
|
10418
10741
|
const normalizedRaw = raw.toLowerCase();
|
|
10419
10742
|
return !normalizedRaw.startsWith("group:") && !normalizedRaw.startsWith("channel:");
|
|
10420
10743
|
}
|
|
@@ -11408,6 +11731,14 @@ function looksLikeStructuredMarkdown(text) {
|
|
|
11408
11731
|
}
|
|
11409
11732
|
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);
|
|
11410
11733
|
}
|
|
11734
|
+
function looksLikeQQBotStreamingIneligibleMarkdown(text) {
|
|
11735
|
+
const normalized = normalizeQQBotMarkdownSegment(text);
|
|
11736
|
+
if (!normalized) {
|
|
11737
|
+
return false;
|
|
11738
|
+
}
|
|
11739
|
+
const lines = normalized.split("\n");
|
|
11740
|
+
return hasQQBotMarkdownTable(normalized) || 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));
|
|
11741
|
+
}
|
|
11411
11742
|
function chunkC2CMarkdownText(params) {
|
|
11412
11743
|
const normalized = params.text.trim();
|
|
11413
11744
|
if (!normalized) {
|
|
@@ -11431,7 +11762,7 @@ async function sendQQBotMediaWithFallback(params) {
|
|
|
11431
11762
|
return;
|
|
11432
11763
|
}
|
|
11433
11764
|
const result = await outbound.sendMedia({
|
|
11434
|
-
cfg:
|
|
11765
|
+
cfg: buildQQBotScopedConfig(qqCfg),
|
|
11435
11766
|
to,
|
|
11436
11767
|
mediaUrl,
|
|
11437
11768
|
replyToId,
|
|
@@ -11449,7 +11780,7 @@ async function sendQQBotMediaWithFallback(params) {
|
|
|
11449
11780
|
return;
|
|
11450
11781
|
}
|
|
11451
11782
|
const fallbackResult = await outbound.sendText({
|
|
11452
|
-
cfg:
|
|
11783
|
+
cfg: buildQQBotScopedConfig(qqCfg),
|
|
11453
11784
|
to,
|
|
11454
11785
|
text: fallback,
|
|
11455
11786
|
replyToId,
|
|
@@ -11474,7 +11805,7 @@ function buildInboundContext(params) {
|
|
|
11474
11805
|
const commandBody = params.commandBody ?? event.content;
|
|
11475
11806
|
const chatType = event.type === "group" || event.type === "channel" ? "group" : "direct";
|
|
11476
11807
|
const { to } = resolveChatTarget(event);
|
|
11477
|
-
const from = event.type === "group" ?
|
|
11808
|
+
const from = event.type === "group" ? `${QQBOT_CHANNEL_ID}:group:${event.groupOpenid ?? ""}` : event.type === "channel" ? `${QQBOT_CHANNEL_ID}:channel:${event.channelId ?? ""}` : `${QQBOT_CHANNEL_ID}:${event.senderId}`;
|
|
11478
11809
|
return {
|
|
11479
11810
|
Body: body,
|
|
11480
11811
|
RawBody: rawBody,
|
|
@@ -11487,12 +11818,12 @@ function buildInboundContext(params) {
|
|
|
11487
11818
|
GroupSubject: event.type === "group" ? event.groupOpenid : event.channelId,
|
|
11488
11819
|
SenderName: event.senderName,
|
|
11489
11820
|
SenderId: event.senderId,
|
|
11490
|
-
Provider:
|
|
11821
|
+
Provider: QQBOT_CHANNEL_ID,
|
|
11491
11822
|
MessageSid: event.messageId,
|
|
11492
11823
|
Timestamp: event.timestamp,
|
|
11493
11824
|
WasMentioned: event.mentionedBot,
|
|
11494
11825
|
CommandAuthorized: true,
|
|
11495
|
-
OriginatingChannel:
|
|
11826
|
+
OriginatingChannel: QQBOT_CHANNEL_ID,
|
|
11496
11827
|
OriginatingTo: to
|
|
11497
11828
|
};
|
|
11498
11829
|
}
|
|
@@ -11515,7 +11846,7 @@ async function dispatchToAgent(params) {
|
|
|
11515
11846
|
let typingRefIdx;
|
|
11516
11847
|
if (inbound.c2cOpenid && !isFastAbortCommand && !shouldSuppressVisibleReplies()) {
|
|
11517
11848
|
const typing = await qqbotOutbound.sendTyping({
|
|
11518
|
-
cfg:
|
|
11849
|
+
cfg: buildQQBotScopedConfig(qqCfg),
|
|
11519
11850
|
to: `user:${inbound.c2cOpenid}`,
|
|
11520
11851
|
replyToId: inbound.messageId,
|
|
11521
11852
|
replyEventId: inbound.eventId,
|
|
@@ -11566,7 +11897,7 @@ async function dispatchToAgent(params) {
|
|
|
11566
11897
|
renew: async () => {
|
|
11567
11898
|
try {
|
|
11568
11899
|
const typing = await qqbotOutbound.sendTyping({
|
|
11569
|
-
cfg:
|
|
11900
|
+
cfg: buildQQBotScopedConfig(qqCfg),
|
|
11570
11901
|
to: `user:${inbound.c2cOpenid}`,
|
|
11571
11902
|
replyToId: inbound.messageId,
|
|
11572
11903
|
replyEventId: inbound.eventId,
|
|
@@ -11586,7 +11917,7 @@ async function dispatchToAgent(params) {
|
|
|
11586
11917
|
if (groupMessageInterfaceBlocked || isFastAbortCommand || shouldSuppressVisibleReplies()) return;
|
|
11587
11918
|
markVisibleOutboundStarted();
|
|
11588
11919
|
const result = await qqbotOutbound.sendText({
|
|
11589
|
-
cfg:
|
|
11920
|
+
cfg: buildQQBotScopedConfig(qqCfg),
|
|
11590
11921
|
to: target.to,
|
|
11591
11922
|
text: LONG_TASK_NOTICE_TEXT,
|
|
11592
11923
|
replyToId: inbound.messageId,
|
|
@@ -11623,7 +11954,7 @@ async function dispatchToAgent(params) {
|
|
|
11623
11954
|
}
|
|
11624
11955
|
markVisibleOutboundStarted();
|
|
11625
11956
|
const fallback = await qqbotOutbound.sendText({
|
|
11626
|
-
cfg:
|
|
11957
|
+
cfg: buildQQBotScopedConfig(qqCfg),
|
|
11627
11958
|
to: target.to,
|
|
11628
11959
|
text: buildVoiceASRFallbackReply(resolvedAttachmentResult.asrErrorMessage),
|
|
11629
11960
|
replyToId: inbound.messageId,
|
|
@@ -11737,7 +12068,7 @@ async function dispatchToAgent(params) {
|
|
|
11737
12068
|
const isGroup = inbound.type === "group" || inbound.type === "channel";
|
|
11738
12069
|
const updateLastRoute = !isGroup ? {
|
|
11739
12070
|
sessionKey: mainSessionKey ?? route.sessionKey,
|
|
11740
|
-
channel:
|
|
12071
|
+
channel: QQBOT_CHANNEL_ID,
|
|
11741
12072
|
to: stableTo,
|
|
11742
12073
|
accountId: outboundAccountId
|
|
11743
12074
|
} : void 0;
|
|
@@ -11773,13 +12104,13 @@ async function dispatchToAgent(params) {
|
|
|
11773
12104
|
const textApi = runtime2.channel?.text;
|
|
11774
12105
|
const limit = textApi?.resolveTextChunkLimit?.({
|
|
11775
12106
|
cfg,
|
|
11776
|
-
channel:
|
|
12107
|
+
channel: QQBOT_CHANNEL_ID,
|
|
11777
12108
|
defaultLimit: qqCfg.textChunkLimit ?? 1500
|
|
11778
12109
|
}) ?? (qqCfg.textChunkLimit ?? 1500);
|
|
11779
|
-
const chunkMode = textApi?.resolveChunkMode?.(cfg,
|
|
12110
|
+
const chunkMode = textApi?.resolveChunkMode?.(cfg, QQBOT_CHANNEL_ID);
|
|
11780
12111
|
const tableMode = textApi?.resolveMarkdownTableMode?.({
|
|
11781
12112
|
cfg,
|
|
11782
|
-
channel:
|
|
12113
|
+
channel: QQBOT_CHANNEL_ID,
|
|
11783
12114
|
accountId: outboundAccountId
|
|
11784
12115
|
});
|
|
11785
12116
|
const resolvedTableMode = tableMode ?? "bullets";
|
|
@@ -11798,10 +12129,26 @@ async function dispatchToAgent(params) {
|
|
|
11798
12129
|
const c2cMarkdownChunkStrategy = qqCfg.c2cMarkdownChunkStrategy ?? "markdown-block";
|
|
11799
12130
|
const c2cMarkdownSafeChunkByteLimit = resolveQQBotC2CMarkdownSafeChunkByteLimit(qqCfg);
|
|
11800
12131
|
const isC2CTarget = isQQBotC2CTarget(target.to);
|
|
12132
|
+
const streamingEnabled = isC2CTarget && !replyFinalOnly && resolveQQBotStreaming(qqCfg);
|
|
11801
12133
|
const useC2CMarkdownTransport = markdownSupport && isC2CTarget;
|
|
11802
12134
|
let bufferedC2CMarkdownTexts = [];
|
|
11803
12135
|
let bufferedC2CMarkdownMediaUrls = [];
|
|
11804
12136
|
const bufferedC2CMarkdownMediaSeen = /* @__PURE__ */ new Set();
|
|
12137
|
+
const streamingCredentials = streamingEnabled ? resolveQQBotCredentials(qqCfg) : void 0;
|
|
12138
|
+
const streamingController = streamingEnabled && inbound.c2cOpenid && streamingCredentials ? new QQBotStreamingController({
|
|
12139
|
+
appId: streamingCredentials.appId,
|
|
12140
|
+
clientSecret: streamingCredentials.clientSecret,
|
|
12141
|
+
openid: inbound.c2cOpenid,
|
|
12142
|
+
messageId: inbound.messageId,
|
|
12143
|
+
eventId: inbound.eventId ?? inbound.messageId,
|
|
12144
|
+
logger,
|
|
12145
|
+
logPrefix: `[qqbot:${outboundAccountId}:streaming]`,
|
|
12146
|
+
onFirstChunk: async () => {
|
|
12147
|
+
markVisibleOutboundStarted();
|
|
12148
|
+
markReplyDelivered();
|
|
12149
|
+
typingHeartbeat?.stop();
|
|
12150
|
+
}
|
|
12151
|
+
}) : null;
|
|
11805
12152
|
const hasBufferedC2CMarkdownReply = () => bufferedC2CMarkdownTexts.length > 0 || bufferedC2CMarkdownMediaUrls.length > 0;
|
|
11806
12153
|
const bufferC2CMarkdownMedia = (url) => {
|
|
11807
12154
|
const next = url?.trim();
|
|
@@ -11809,6 +12156,28 @@ async function dispatchToAgent(params) {
|
|
|
11809
12156
|
bufferedC2CMarkdownMediaSeen.add(next);
|
|
11810
12157
|
bufferedC2CMarkdownMediaUrls.push(next);
|
|
11811
12158
|
};
|
|
12159
|
+
const handleStreamingPartialReply = async (payload) => {
|
|
12160
|
+
if (!streamingController || shouldSuppressVisibleReplies()) {
|
|
12161
|
+
return;
|
|
12162
|
+
}
|
|
12163
|
+
const rawText = payload.text ?? "";
|
|
12164
|
+
if (!rawText.trim()) {
|
|
12165
|
+
return;
|
|
12166
|
+
}
|
|
12167
|
+
const extractedTextMedia = extractQQBotReplyMedia({
|
|
12168
|
+
text: rawText,
|
|
12169
|
+
logger,
|
|
12170
|
+
autoSendLocalPathMedia: resolveQQBotAutoSendLocalPathMedia(qqCfg)
|
|
12171
|
+
});
|
|
12172
|
+
const cleanedText = sanitizeQQBotOutboundText(extractedTextMedia.text);
|
|
12173
|
+
if (!cleanedText) {
|
|
12174
|
+
return;
|
|
12175
|
+
}
|
|
12176
|
+
if (!streamingController.hasSuccessfulChunk && (extractedTextMedia.mediaUrls.length > 0 || looksLikeQQBotStreamingIneligibleMarkdown(cleanedText))) {
|
|
12177
|
+
return;
|
|
12178
|
+
}
|
|
12179
|
+
await streamingController.onPartialReply(cleanedText);
|
|
12180
|
+
};
|
|
11812
12181
|
const sendC2CMarkdownTransportPayload = async (params2) => {
|
|
11813
12182
|
if (shouldSuppressVisibleReplies()) {
|
|
11814
12183
|
return;
|
|
@@ -11875,7 +12244,7 @@ async function dispatchToAgent(params) {
|
|
|
11875
12244
|
);
|
|
11876
12245
|
markVisibleOutboundStarted();
|
|
11877
12246
|
const result = await qqbotOutbound.sendText({
|
|
11878
|
-
cfg:
|
|
12247
|
+
cfg: buildQQBotScopedConfig(qqCfg),
|
|
11879
12248
|
to: target.to,
|
|
11880
12249
|
text: chunk,
|
|
11881
12250
|
replyToId: textReplyRefs.replyToId,
|
|
@@ -11946,8 +12315,14 @@ async function dispatchToAgent(params) {
|
|
|
11946
12315
|
});
|
|
11947
12316
|
if (deliveryDecision.skipDelivery) return;
|
|
11948
12317
|
const suppressEchoText = mediaQueue.length > 0 && shouldSuppressQQBotTextWhenMediaPresent(extractedTextMedia.text, cleanedText);
|
|
11949
|
-
const
|
|
12318
|
+
const streamingOwnsAssistantText = info?.kind !== "tool" && Boolean(
|
|
12319
|
+
streamingController && streamingController.hasSuccessfulChunk && !streamingController.shouldFallbackToStatic
|
|
12320
|
+
);
|
|
12321
|
+
const suppressText = deliveryDecision.suppressText || suppressEchoText || streamingOwnsAssistantText;
|
|
11950
12322
|
const textToSend = suppressText ? "" : cleanedText;
|
|
12323
|
+
if (streamingOwnsAssistantText && mediaQueue.length === 0 && !textToSend) {
|
|
12324
|
+
return;
|
|
12325
|
+
}
|
|
11951
12326
|
if (useC2CMarkdownTransport) {
|
|
11952
12327
|
const shouldBufferFinalOnlyPayload = replyFinalOnly && (!info?.kind || info.kind === "final");
|
|
11953
12328
|
const shouldBufferStructuredMarkdownPayload = !replyFinalOnly && c2cMarkdownChunkStrategy === "markdown-block" && info?.kind !== "tool" && (hasBufferedC2CMarkdownReply() || looksLikeStructuredMarkdown(textToSend));
|
|
@@ -11990,7 +12365,7 @@ async function dispatchToAgent(params) {
|
|
|
11990
12365
|
}
|
|
11991
12366
|
markVisibleOutboundStarted();
|
|
11992
12367
|
const result = await qqbotOutbound.sendText({
|
|
11993
|
-
cfg:
|
|
12368
|
+
cfg: buildQQBotScopedConfig(qqCfg),
|
|
11994
12369
|
to: target.to,
|
|
11995
12370
|
text: chunk,
|
|
11996
12371
|
replyToId: textReplyRefs.replyToId,
|
|
@@ -12032,82 +12407,92 @@ async function dispatchToAgent(params) {
|
|
|
12032
12407
|
const dispatchDirect = replyApi.dispatchReplyWithDispatcher;
|
|
12033
12408
|
const dispatchBuffered = replyApi.dispatchReplyWithBufferedBlockDispatcher;
|
|
12034
12409
|
const streamingReplyOptions = isC2CTarget && !replyFinalOnly ? {
|
|
12035
|
-
disableBlockStreaming: false
|
|
12410
|
+
disableBlockStreaming: false,
|
|
12411
|
+
...streamingController ? {
|
|
12412
|
+
onPartialReply: handleStreamingPartialReply
|
|
12413
|
+
} : {}
|
|
12036
12414
|
} : void 0;
|
|
12037
|
-
|
|
12038
|
-
|
|
12039
|
-
|
|
12040
|
-
|
|
12041
|
-
|
|
12042
|
-
|
|
12043
|
-
|
|
12044
|
-
|
|
12045
|
-
|
|
12046
|
-
|
|
12415
|
+
try {
|
|
12416
|
+
if (isC2CTarget && !replyFinalOnly && dispatchDirect) {
|
|
12417
|
+
logger.debug(`[dispatch] mode=direct session=${routeSessionKey} to=${target.to}`);
|
|
12418
|
+
await dispatchDirect({
|
|
12419
|
+
ctx: finalCtx,
|
|
12420
|
+
cfg,
|
|
12421
|
+
dispatcherOptions: {
|
|
12422
|
+
deliver,
|
|
12423
|
+
humanDelay,
|
|
12424
|
+
onError: (err, info) => {
|
|
12425
|
+
logger.error(`${info.kind} reply failed: ${String(err)}`);
|
|
12426
|
+
},
|
|
12427
|
+
onSkip: (_payload, info) => {
|
|
12428
|
+
if (info.reason !== "silent") {
|
|
12429
|
+
logger.info(`reply skipped: ${info.reason}`);
|
|
12430
|
+
}
|
|
12431
|
+
}
|
|
12047
12432
|
},
|
|
12048
|
-
|
|
12049
|
-
|
|
12050
|
-
|
|
12433
|
+
replyOptions: streamingReplyOptions
|
|
12434
|
+
});
|
|
12435
|
+
await flushBufferedC2CMarkdownReply();
|
|
12436
|
+
} else if (dispatchBuffered) {
|
|
12437
|
+
logger.debug(`[dispatch] mode=buffered session=${routeSessionKey} to=${target.to}`);
|
|
12438
|
+
await dispatchBuffered({
|
|
12439
|
+
ctx: finalCtx,
|
|
12440
|
+
cfg,
|
|
12441
|
+
dispatcherOptions: {
|
|
12442
|
+
deliver,
|
|
12443
|
+
humanDelay,
|
|
12444
|
+
onError: (err, info) => {
|
|
12445
|
+
logger.error(`${info.kind} reply failed: ${String(err)}`);
|
|
12446
|
+
},
|
|
12447
|
+
onSkip: (_payload, info) => {
|
|
12448
|
+
if (info.reason !== "silent") {
|
|
12449
|
+
logger.info(`reply skipped: ${info.reason}`);
|
|
12450
|
+
}
|
|
12051
12451
|
}
|
|
12052
|
-
}
|
|
12053
|
-
|
|
12054
|
-
|
|
12055
|
-
|
|
12056
|
-
|
|
12057
|
-
|
|
12058
|
-
|
|
12059
|
-
await dispatchBuffered({
|
|
12060
|
-
ctx: finalCtx,
|
|
12061
|
-
cfg,
|
|
12062
|
-
dispatcherOptions: {
|
|
12452
|
+
},
|
|
12453
|
+
replyOptions: streamingReplyOptions
|
|
12454
|
+
});
|
|
12455
|
+
await flushBufferedC2CMarkdownReply();
|
|
12456
|
+
} else {
|
|
12457
|
+
logger.debug(`[dispatch] mode=legacy session=${routeSessionKey} to=${target.to}`);
|
|
12458
|
+
const dispatcherResult = replyApi.createReplyDispatcherWithTyping ? replyApi.createReplyDispatcherWithTyping({
|
|
12063
12459
|
deliver,
|
|
12064
12460
|
humanDelay,
|
|
12065
12461
|
onError: (err, info) => {
|
|
12066
12462
|
logger.error(`${info.kind} reply failed: ${String(err)}`);
|
|
12067
|
-
},
|
|
12068
|
-
onSkip: (_payload, info) => {
|
|
12069
|
-
if (info.reason !== "silent") {
|
|
12070
|
-
logger.info(`reply skipped: ${info.reason}`);
|
|
12071
|
-
}
|
|
12072
12463
|
}
|
|
12073
|
-
}
|
|
12074
|
-
|
|
12075
|
-
|
|
12076
|
-
|
|
12077
|
-
|
|
12078
|
-
|
|
12079
|
-
|
|
12080
|
-
|
|
12081
|
-
|
|
12082
|
-
|
|
12083
|
-
|
|
12464
|
+
}) : {
|
|
12465
|
+
dispatcher: replyApi.createReplyDispatcher?.({
|
|
12466
|
+
deliver,
|
|
12467
|
+
humanDelay,
|
|
12468
|
+
onError: (err, info) => {
|
|
12469
|
+
logger.error(`${info.kind} reply failed: ${String(err)}`);
|
|
12470
|
+
}
|
|
12471
|
+
}),
|
|
12472
|
+
replyOptions: {},
|
|
12473
|
+
markDispatchIdle: () => void 0
|
|
12474
|
+
};
|
|
12475
|
+
if (!dispatcherResult.dispatcher || !replyApi.dispatchReplyFromConfig) {
|
|
12476
|
+
logger.warn("dispatcher not available, skipping reply");
|
|
12477
|
+
return;
|
|
12084
12478
|
}
|
|
12085
|
-
|
|
12086
|
-
|
|
12087
|
-
|
|
12088
|
-
|
|
12089
|
-
|
|
12090
|
-
|
|
12479
|
+
await replyApi.dispatchReplyFromConfig({
|
|
12480
|
+
ctx: finalCtx,
|
|
12481
|
+
cfg,
|
|
12482
|
+
dispatcher: dispatcherResult.dispatcher,
|
|
12483
|
+
replyOptions: {
|
|
12484
|
+
...typeof dispatcherResult.replyOptions === "object" && dispatcherResult.replyOptions ? dispatcherResult.replyOptions : {},
|
|
12485
|
+
...streamingReplyOptions ?? {}
|
|
12091
12486
|
}
|
|
12092
|
-
})
|
|
12093
|
-
|
|
12094
|
-
|
|
12095
|
-
}
|
|
12096
|
-
|
|
12097
|
-
|
|
12098
|
-
|
|
12487
|
+
});
|
|
12488
|
+
dispatcherResult.markDispatchIdle?.();
|
|
12489
|
+
await flushBufferedC2CMarkdownReply();
|
|
12490
|
+
}
|
|
12491
|
+
} finally {
|
|
12492
|
+
if (streamingController) {
|
|
12493
|
+
await streamingController.finalize();
|
|
12494
|
+
streamingController.dispose();
|
|
12099
12495
|
}
|
|
12100
|
-
await replyApi.dispatchReplyFromConfig({
|
|
12101
|
-
ctx: finalCtx,
|
|
12102
|
-
cfg,
|
|
12103
|
-
dispatcher: dispatcherResult.dispatcher,
|
|
12104
|
-
replyOptions: {
|
|
12105
|
-
...typeof dispatcherResult.replyOptions === "object" && dispatcherResult.replyOptions ? dispatcherResult.replyOptions : {},
|
|
12106
|
-
...streamingReplyOptions ?? {}
|
|
12107
|
-
}
|
|
12108
|
-
});
|
|
12109
|
-
dispatcherResult.markDispatchIdle?.();
|
|
12110
|
-
await flushBufferedC2CMarkdownReply();
|
|
12111
12496
|
}
|
|
12112
12497
|
const noReplyFallback = resolveQQBotNoReplyFallback({
|
|
12113
12498
|
inbound,
|
|
@@ -12117,7 +12502,7 @@ async function dispatchToAgent(params) {
|
|
|
12117
12502
|
logger.info("no visible reply generated for group mention; sending fallback text");
|
|
12118
12503
|
markVisibleOutboundStarted();
|
|
12119
12504
|
const fallbackResult = await qqbotOutbound.sendText({
|
|
12120
|
-
cfg:
|
|
12505
|
+
cfg: buildQQBotScopedConfig(qqCfg),
|
|
12121
12506
|
to: target.to,
|
|
12122
12507
|
text: noReplyFallback,
|
|
12123
12508
|
replyToId: inbound.messageId,
|
|
@@ -12245,7 +12630,7 @@ async function handleQQBotDispatch(params) {
|
|
|
12245
12630
|
const target = resolveChatTarget(resolvedInbound);
|
|
12246
12631
|
const route = routing({
|
|
12247
12632
|
cfg: params.cfg,
|
|
12248
|
-
channel:
|
|
12633
|
+
channel: QQBOT_CHANNEL_ID,
|
|
12249
12634
|
accountId,
|
|
12250
12635
|
peer: { kind: target.peerKind, id: target.peerId }
|
|
12251
12636
|
});
|
|
@@ -12611,7 +12996,7 @@ function stopQQBotMonitorForAccount(accountId = DEFAULT_ACCOUNT_ID) {
|
|
|
12611
12996
|
|
|
12612
12997
|
// src/channel.ts
|
|
12613
12998
|
var meta = {
|
|
12614
|
-
id:
|
|
12999
|
+
id: QQBOT_CHANNEL_ID,
|
|
12615
13000
|
label: "QQ Bot",
|
|
12616
13001
|
selectionLabel: "QQ Bot",
|
|
12617
13002
|
docsPath: "/channels/qqbot",
|
|
@@ -12623,7 +13008,7 @@ var meta = {
|
|
|
12623
13008
|
function resolveQQBotAccount(params) {
|
|
12624
13009
|
const { cfg, accountId = DEFAULT_ACCOUNT_ID } = params;
|
|
12625
13010
|
const merged = mergeQQBotAccountConfig(cfg, accountId);
|
|
12626
|
-
const baseEnabled = cfg
|
|
13011
|
+
const baseEnabled = resolveQQBotChannelConfig(cfg)?.enabled !== false;
|
|
12627
13012
|
const enabled = baseEnabled && merged.enabled !== false;
|
|
12628
13013
|
const credentials = resolveQQBotCredentials(merged);
|
|
12629
13014
|
const configured = Boolean(credentials);
|
|
@@ -12632,13 +13017,14 @@ function resolveQQBotAccount(params) {
|
|
|
12632
13017
|
enabled,
|
|
12633
13018
|
configured,
|
|
12634
13019
|
appId: credentials?.appId,
|
|
13020
|
+
streaming: merged.streaming === true,
|
|
12635
13021
|
markdownSupport: merged.markdownSupport ?? true,
|
|
12636
13022
|
c2cMarkdownDeliveryMode: merged.c2cMarkdownDeliveryMode ?? "proactive-table-only",
|
|
12637
13023
|
c2cMarkdownChunkStrategy: merged.c2cMarkdownChunkStrategy ?? "markdown-block"
|
|
12638
13024
|
};
|
|
12639
13025
|
}
|
|
12640
13026
|
var qqbotPlugin = {
|
|
12641
|
-
id:
|
|
13027
|
+
id: QQBOT_CHANNEL_ID,
|
|
12642
13028
|
meta: {
|
|
12643
13029
|
...meta
|
|
12644
13030
|
},
|
|
@@ -12657,10 +13043,7 @@ var qqbotPlugin = {
|
|
|
12657
13043
|
normalizeTarget: (raw) => {
|
|
12658
13044
|
const trimmed = raw.trim();
|
|
12659
13045
|
if (!trimmed) return void 0;
|
|
12660
|
-
let value = trimmed;
|
|
12661
|
-
if (/^qqbot:/i.test(value)) {
|
|
12662
|
-
value = value.slice("qqbot:".length);
|
|
12663
|
-
}
|
|
13046
|
+
let value = stripQQBotChannelPrefix(trimmed);
|
|
12664
13047
|
if (/^(user|group|channel):/i.test(value)) {
|
|
12665
13048
|
return value;
|
|
12666
13049
|
}
|
|
@@ -12712,6 +13095,7 @@ var qqbotPlugin = {
|
|
|
12712
13095
|
defaultAccount: { type: "string" },
|
|
12713
13096
|
appId: { type: ["string", "number"] },
|
|
12714
13097
|
clientSecret: { type: "string" },
|
|
13098
|
+
streaming: { type: "boolean" },
|
|
12715
13099
|
displayAliases: {
|
|
12716
13100
|
type: "object",
|
|
12717
13101
|
additionalProperties: { type: "string" }
|
|
@@ -12766,6 +13150,7 @@ var qqbotPlugin = {
|
|
|
12766
13150
|
enabled: { type: "boolean" },
|
|
12767
13151
|
appId: { type: ["string", "number"] },
|
|
12768
13152
|
clientSecret: { type: "string" },
|
|
13153
|
+
streaming: { type: "boolean" },
|
|
12769
13154
|
displayAliases: {
|
|
12770
13155
|
type: "object",
|
|
12771
13156
|
additionalProperties: { type: "string" }
|
|
@@ -12816,7 +13201,7 @@ var qqbotPlugin = {
|
|
|
12816
13201
|
}
|
|
12817
13202
|
}
|
|
12818
13203
|
},
|
|
12819
|
-
reload: { configPrefixes: [
|
|
13204
|
+
reload: { configPrefixes: [...QQBOT_CONFIG_PREFIXES] },
|
|
12820
13205
|
onboarding: qqbotOnboardingAdapter,
|
|
12821
13206
|
config: {
|
|
12822
13207
|
listAccountIds: (cfg) => listQQBotAccountIds(cfg),
|
|
@@ -12824,58 +13209,41 @@ var qqbotPlugin = {
|
|
|
12824
13209
|
defaultAccountId: () => DEFAULT_ACCOUNT_ID,
|
|
12825
13210
|
setAccountEnabled: (params) => {
|
|
12826
13211
|
const accountId = params.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
12827
|
-
const existing = params.cfg
|
|
13212
|
+
const existing = resolveQQBotChannelConfig(params.cfg) ?? {};
|
|
12828
13213
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
12829
|
-
return
|
|
12830
|
-
|
|
12831
|
-
|
|
12832
|
-
|
|
12833
|
-
qqbot: { ...existing, enabled: params.enabled }
|
|
12834
|
-
}
|
|
12835
|
-
};
|
|
13214
|
+
return withQQBotChannelConfig(
|
|
13215
|
+
params.cfg,
|
|
13216
|
+
{ ...existing, enabled: params.enabled }
|
|
13217
|
+
);
|
|
12836
13218
|
}
|
|
12837
13219
|
const accounts = existing.accounts ?? {};
|
|
12838
13220
|
const account = accounts[accountId] ?? {};
|
|
12839
|
-
return
|
|
12840
|
-
|
|
12841
|
-
|
|
12842
|
-
...
|
|
12843
|
-
|
|
12844
|
-
...
|
|
12845
|
-
|
|
12846
|
-
...accounts,
|
|
12847
|
-
[accountId]: { ...account, enabled: params.enabled }
|
|
12848
|
-
}
|
|
13221
|
+
return withQQBotChannelConfig(
|
|
13222
|
+
params.cfg,
|
|
13223
|
+
{
|
|
13224
|
+
...existing,
|
|
13225
|
+
accounts: {
|
|
13226
|
+
...accounts,
|
|
13227
|
+
[accountId]: { ...account, enabled: params.enabled }
|
|
12849
13228
|
}
|
|
12850
13229
|
}
|
|
12851
|
-
|
|
13230
|
+
);
|
|
12852
13231
|
},
|
|
12853
13232
|
deleteAccount: (params) => {
|
|
12854
13233
|
const accountId = params.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
12855
13234
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
12856
|
-
|
|
12857
|
-
const nextChannels = { ...params.cfg.channels };
|
|
12858
|
-
delete nextChannels.qqbot;
|
|
12859
|
-
if (Object.keys(nextChannels).length > 0) {
|
|
12860
|
-
next.channels = nextChannels;
|
|
12861
|
-
} else {
|
|
12862
|
-
delete next.channels;
|
|
12863
|
-
}
|
|
12864
|
-
return next;
|
|
13235
|
+
return withQQBotChannelConfig(params.cfg);
|
|
12865
13236
|
}
|
|
12866
|
-
const existing = params.cfg
|
|
13237
|
+
const existing = resolveQQBotChannelConfig(params.cfg);
|
|
12867
13238
|
if (!existing?.accounts?.[accountId]) return params.cfg;
|
|
12868
13239
|
const { [accountId]: _removed, ...remainingAccounts } = existing.accounts;
|
|
12869
|
-
return
|
|
12870
|
-
|
|
12871
|
-
|
|
12872
|
-
...
|
|
12873
|
-
|
|
12874
|
-
...existing,
|
|
12875
|
-
accounts: Object.keys(remainingAccounts).length > 0 ? remainingAccounts : void 0
|
|
12876
|
-
}
|
|
13240
|
+
return withQQBotChannelConfig(
|
|
13241
|
+
params.cfg,
|
|
13242
|
+
{
|
|
13243
|
+
...existing,
|
|
13244
|
+
accounts: Object.keys(remainingAccounts).length > 0 ? remainingAccounts : void 0
|
|
12877
13245
|
}
|
|
12878
|
-
|
|
13246
|
+
);
|
|
12879
13247
|
},
|
|
12880
13248
|
isConfigured: (_account, cfg, accountId) => {
|
|
12881
13249
|
const id = accountId ?? _account.accountId;
|
|
@@ -12896,11 +13264,11 @@ var qqbotPlugin = {
|
|
|
12896
13264
|
},
|
|
12897
13265
|
security: {
|
|
12898
13266
|
collectWarnings: (params) => {
|
|
12899
|
-
const qqCfg = params.cfg
|
|
13267
|
+
const qqCfg = resolveQQBotChannelConfig(params.cfg);
|
|
12900
13268
|
const groupPolicy = qqCfg?.groupPolicy ?? "allowlist";
|
|
12901
13269
|
if (groupPolicy !== "open") return [];
|
|
12902
13270
|
return [
|
|
12903
|
-
`- QQ groups: groupPolicy="open" allows any member to trigger (mention-gated). Set channels.
|
|
13271
|
+
`- QQ groups: groupPolicy="open" allows any member to trigger (mention-gated). Set channels.${QQBOT_CONFIG_CHANNEL_ID}.groupPolicy="allowlist" + channels.${QQBOT_CONFIG_CHANNEL_ID}.groupAllowFrom to restrict senders.`
|
|
12904
13272
|
];
|
|
12905
13273
|
}
|
|
12906
13274
|
},
|
|
@@ -12908,37 +13276,31 @@ var qqbotPlugin = {
|
|
|
12908
13276
|
resolveAccountId: (params) => params.accountId ?? resolveDefaultQQBotAccountId(params.cfg),
|
|
12909
13277
|
applyAccountConfig: (params) => {
|
|
12910
13278
|
const accountId = params.accountId ?? DEFAULT_ACCOUNT_ID;
|
|
12911
|
-
const existing = params.cfg
|
|
13279
|
+
const existing = resolveQQBotChannelConfig(params.cfg) ?? {};
|
|
12912
13280
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
12913
|
-
return
|
|
12914
|
-
|
|
12915
|
-
|
|
12916
|
-
|
|
12917
|
-
qqbot: { ...existing, ...params.config, enabled: true }
|
|
12918
|
-
}
|
|
12919
|
-
};
|
|
13281
|
+
return withQQBotChannelConfig(
|
|
13282
|
+
params.cfg,
|
|
13283
|
+
{ ...existing, ...params.config, enabled: true }
|
|
13284
|
+
);
|
|
12920
13285
|
}
|
|
12921
13286
|
const accounts = existing.accounts ?? {};
|
|
12922
|
-
return
|
|
12923
|
-
|
|
12924
|
-
|
|
12925
|
-
...
|
|
12926
|
-
|
|
12927
|
-
...
|
|
12928
|
-
accounts:
|
|
12929
|
-
...accounts,
|
|
12930
|
-
[accountId]: { ...accounts[accountId], ...params.config, enabled: true }
|
|
12931
|
-
}
|
|
13287
|
+
return withQQBotChannelConfig(
|
|
13288
|
+
params.cfg,
|
|
13289
|
+
{
|
|
13290
|
+
...existing,
|
|
13291
|
+
accounts: {
|
|
13292
|
+
...accounts,
|
|
13293
|
+
[accountId]: { ...accounts[accountId], ...params.config, enabled: true }
|
|
12932
13294
|
}
|
|
12933
13295
|
}
|
|
12934
|
-
|
|
13296
|
+
);
|
|
12935
13297
|
}
|
|
12936
13298
|
},
|
|
12937
13299
|
outbound: qqbotOutbound,
|
|
12938
13300
|
gateway: {
|
|
12939
13301
|
startAccount: async (ctx) => {
|
|
12940
13302
|
ctx.setStatus?.({ accountId: ctx.accountId });
|
|
12941
|
-
ctx.log?.info(`[
|
|
13303
|
+
ctx.log?.info(`[${QQBOT_CHANNEL_ID}] starting gateway for account ${ctx.accountId}`);
|
|
12942
13304
|
if (ctx.runtime) {
|
|
12943
13305
|
const candidate = ctx.runtime;
|
|
12944
13306
|
const hasRouting = Boolean(candidate.channel?.routing?.resolveAgentRoute);
|
|
@@ -12967,7 +13329,7 @@ var qqbotPlugin = {
|
|
|
12967
13329
|
|
|
12968
13330
|
// index.ts
|
|
12969
13331
|
var plugin = {
|
|
12970
|
-
id:
|
|
13332
|
+
id: QQBOT_CHANNEL_ID,
|
|
12971
13333
|
name: "QQ Bot",
|
|
12972
13334
|
description: "QQ \u5F00\u653E\u5E73\u53F0\u673A\u5668\u4EBA\u6D88\u606F\u6E20\u9053\u63D2\u4EF6",
|
|
12973
13335
|
configSchema: {
|
|
@@ -12979,6 +13341,7 @@ var plugin = {
|
|
|
12979
13341
|
defaultAccount: { type: "string" },
|
|
12980
13342
|
appId: { type: ["string", "number"] },
|
|
12981
13343
|
clientSecret: { type: "string" },
|
|
13344
|
+
streaming: { type: "boolean" },
|
|
12982
13345
|
displayAliases: {
|
|
12983
13346
|
type: "object",
|
|
12984
13347
|
additionalProperties: { type: "string" }
|
|
@@ -13033,6 +13396,7 @@ var plugin = {
|
|
|
13033
13396
|
enabled: { type: "boolean" },
|
|
13034
13397
|
appId: { type: ["string", "number"] },
|
|
13035
13398
|
clientSecret: { type: "string" },
|
|
13399
|
+
streaming: { type: "boolean" },
|
|
13036
13400
|
displayAliases: {
|
|
13037
13401
|
type: "object",
|
|
13038
13402
|
additionalProperties: { type: "string" }
|
|
@@ -13083,7 +13447,7 @@ var plugin = {
|
|
|
13083
13447
|
}
|
|
13084
13448
|
},
|
|
13085
13449
|
register(api) {
|
|
13086
|
-
registerChinaSetupCli(api, { channels: [
|
|
13450
|
+
registerChinaSetupCli(api, { channels: [QQBOT_CONFIG_CHANNEL_ID] });
|
|
13087
13451
|
showChinaInstallHint(api);
|
|
13088
13452
|
if (api.runtime) {
|
|
13089
13453
|
setQQBotRuntime(api.runtime);
|
|
@@ -13093,6 +13457,6 @@ var plugin = {
|
|
|
13093
13457
|
};
|
|
13094
13458
|
var index_default = plugin;
|
|
13095
13459
|
|
|
13096
|
-
export { DEFAULT_ACCOUNT_ID, clearKnownQQBotTargets, index_default as default, getKnownQQBotTarget, getQQBotRuntime, listKnownQQBotTargets, qqbotPlugin, removeKnownQQBotTarget, sendProactiveQQBotMessage, setQQBotRuntime };
|
|
13460
|
+
export { DEFAULT_ACCOUNT_ID, QQBOT_CHANNEL_ID, QQBOT_CONFIG_CHANNEL_ID, clearKnownQQBotTargets, index_default as default, getKnownQQBotTarget, getQQBotRuntime, listKnownQQBotTargets, qqbotPlugin, removeKnownQQBotTarget, sendProactiveQQBotMessage, setQQBotRuntime };
|
|
13097
13461
|
//# sourceMappingURL=index.js.map
|
|
13098
13462
|
//# sourceMappingURL=index.js.map
|