@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.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.channels?.qqbot?.accounts;
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.channels?.qqbot;
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.channels?.qqbot?.accounts;
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.channels?.qqbot ?? {};
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.channels?.qqbot ?? {};
4427
+ const existing = resolveQQBotChannelConfig(params.cfg) ?? {};
4387
4428
  if (params.accountId === DEFAULT_ACCOUNT_ID) {
4388
- return {
4389
- ...params.cfg,
4390
- channels: {
4391
- ...params.cfg.channels,
4392
- qqbot: {
4393
- ...existing,
4394
- enabled: true,
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
- ...params.cfg,
4404
- channels: {
4405
- ...params.cfg.channels,
4406
- qqbot: {
4407
- ...existing,
4408
- enabled: true,
4409
- accounts: {
4410
- ...accounts,
4411
- [params.accountId]: {
4412
- ...accounts[params.accountId],
4413
- enabled: true,
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
- '\u547D\u4EE4\u884C\u4E5F\u652F\u6301\uFF1Aopenclaw channels add --channel qqbot --token "AppID:ClientSecret"'
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?.qqbot?.trim();
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: "qqbot",
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: "qqbot",
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
- channels: {
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
- "qqbot",
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
- channels[channelId] = {
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("qqbot");
7201
- const existing = getChannelConfig(cfg, "qqbot");
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, "qqbot", {
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.trim();
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: "qqbot", error: "QQBot not configured (missing appId/clientSecret)" };
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: "qqbot", messageId: result3.id, timestamp: result3.timestamp };
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: "qqbot", messageId: result2.id, timestamp: result2.timestamp };
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: "qqbot", messageId: result2.id, timestamp: result2.timestamp };
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: "qqbot",
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: "qqbot",
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: "qqbot", error: message };
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: "qqbot", error: "mediaUrl is required for sendMedia" };
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: "qqbot", error: "QQBot not configured (missing appId/clientSecret)" };
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: "qqbot",
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: "qqbot",
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: "qqbot", error: message };
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: "qqbot", error: "QQBot not configured (missing appId/clientSecret)" };
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: "qqbot" };
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: "qqbot",
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: "qqbot", error: message };
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: "qqbot", error: "to is required for proactive send" };
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: "qqbot", error: "text or mediaUrl is required for proactive send" };
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:qqbot:dm:${normalizedAccountId}:${normalizedSenderId}`;
9771
+ return `agent:main:${QQBOT_CHANNEL_ID}:dm:${normalizedAccountId}:${normalizedSenderId}`;
9446
9772
  }
9447
- const qqAgentRouteMatch = trimmedRouteSessionKey.match(/^(agent:[^:]+:qqbot:)(?:direct|dm):.+$/i);
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
- if (/^qqbot:/i.test(trimmed)) {
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?.channels?.qqbot;
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 trimmed = to.trim();
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: { channels: { qqbot: qqCfg } },
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: { channels: { qqbot: qqCfg } },
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" ? `qqbot:group:${event.groupOpenid ?? ""}` : event.type === "channel" ? `qqbot:channel:${event.channelId ?? ""}` : `qqbot:${event.senderId}`;
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: "qqbot",
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: "qqbot",
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: { channels: { qqbot: qqCfg } },
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: { channels: { qqbot: qqCfg } },
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: { channels: { qqbot: qqCfg } },
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: { channels: { qqbot: qqCfg } },
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: "qqbot",
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: "qqbot",
12107
+ channel: QQBOT_CHANNEL_ID,
11777
12108
  defaultLimit: qqCfg.textChunkLimit ?? 1500
11778
12109
  }) ?? (qqCfg.textChunkLimit ?? 1500);
11779
- const chunkMode = textApi?.resolveChunkMode?.(cfg, "qqbot");
12110
+ const chunkMode = textApi?.resolveChunkMode?.(cfg, QQBOT_CHANNEL_ID);
11780
12111
  const tableMode = textApi?.resolveMarkdownTableMode?.({
11781
12112
  cfg,
11782
- channel: "qqbot",
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: { channels: { qqbot: qqCfg } },
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 suppressText = deliveryDecision.suppressText || suppressEchoText;
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: { channels: { qqbot: qqCfg } },
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
- if (isC2CTarget && !replyFinalOnly && dispatchDirect) {
12038
- logger.debug(`[dispatch] mode=direct session=${routeSessionKey} to=${target.to}`);
12039
- await dispatchDirect({
12040
- ctx: finalCtx,
12041
- cfg,
12042
- dispatcherOptions: {
12043
- deliver,
12044
- humanDelay,
12045
- onError: (err, info) => {
12046
- logger.error(`${info.kind} reply failed: ${String(err)}`);
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
- onSkip: (_payload, info) => {
12049
- if (info.reason !== "silent") {
12050
- logger.info(`reply skipped: ${info.reason}`);
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
- replyOptions: streamingReplyOptions
12055
- });
12056
- await flushBufferedC2CMarkdownReply();
12057
- } else if (dispatchBuffered) {
12058
- logger.debug(`[dispatch] mode=buffered session=${routeSessionKey} to=${target.to}`);
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
- replyOptions: streamingReplyOptions
12075
- });
12076
- await flushBufferedC2CMarkdownReply();
12077
- } else {
12078
- logger.debug(`[dispatch] mode=legacy session=${routeSessionKey} to=${target.to}`);
12079
- const dispatcherResult = replyApi.createReplyDispatcherWithTyping ? replyApi.createReplyDispatcherWithTyping({
12080
- deliver,
12081
- humanDelay,
12082
- onError: (err, info) => {
12083
- logger.error(`${info.kind} reply failed: ${String(err)}`);
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
- dispatcher: replyApi.createReplyDispatcher?.({
12087
- deliver,
12088
- humanDelay,
12089
- onError: (err, info) => {
12090
- logger.error(`${info.kind} reply failed: ${String(err)}`);
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
- replyOptions: {},
12094
- markDispatchIdle: () => void 0
12095
- };
12096
- if (!dispatcherResult.dispatcher || !replyApi.dispatchReplyFromConfig) {
12097
- logger.warn("dispatcher not available, skipping reply");
12098
- return;
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: { channels: { qqbot: qqCfg } },
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: "qqbot",
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: "qqbot",
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.channels?.qqbot?.enabled !== false;
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: "qqbot",
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: ["channels.qqbot"] },
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.channels?.qqbot ?? {};
13212
+ const existing = resolveQQBotChannelConfig(params.cfg) ?? {};
12828
13213
  if (accountId === DEFAULT_ACCOUNT_ID) {
12829
- return {
12830
- ...params.cfg,
12831
- channels: {
12832
- ...params.cfg.channels,
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
- ...params.cfg,
12841
- channels: {
12842
- ...params.cfg.channels,
12843
- qqbot: {
12844
- ...existing,
12845
- accounts: {
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
- const next = { ...params.cfg };
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.channels?.qqbot;
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
- ...params.cfg,
12871
- channels: {
12872
- ...params.cfg.channels,
12873
- qqbot: {
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.channels?.qqbot;
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.qqbot.groupPolicy="allowlist" + channels.qqbot.groupAllowFrom to restrict senders.`
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.channels?.qqbot ?? {};
13279
+ const existing = resolveQQBotChannelConfig(params.cfg) ?? {};
12912
13280
  if (accountId === DEFAULT_ACCOUNT_ID) {
12913
- return {
12914
- ...params.cfg,
12915
- channels: {
12916
- ...params.cfg.channels,
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
- ...params.cfg,
12924
- channels: {
12925
- ...params.cfg.channels,
12926
- qqbot: {
12927
- ...existing,
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(`[qqbot] starting gateway for account ${ctx.accountId}`);
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: "qqbot",
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: ["qqbot"] });
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