@openclaw/qqbot 2026.5.28-beta.2 → 2026.5.28-beta.4

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/api.js CHANGED
@@ -1,9 +1,9 @@
1
- import { C as recordMessageReply, E as setOutboundAudioPort, S as getMessageReplyStats, T as OUTBOUND_ERROR_CODES, _ as sendVoice, a as sendText, b as checkMessageReplyLimit, d as buildMediaTarget, f as parseTarget, g as sendVideoMsg, h as sendPhoto, i as sendProactiveMessage, m as sendDocument, n as sendCronMessage, p as resolveOutboundMediaPath, r as sendMedia, v as resolveUserFacingMediaError, w as DEFAULT_MEDIA_SEND_ERROR, x as getMessageReplyConfig, y as MESSAGE_REPLY_LIMIT } from "./outbound-DsXljRHh.js";
1
+ import { C as getMessageReplyStats, D as setOutboundAudioPort, E as OUTBOUND_ERROR_CODES, S as getMessageReplyConfig, T as DEFAULT_MEDIA_SEND_ERROR, _ as sendVideoMsg, a as sendText, b as MESSAGE_REPLY_LIMIT, f as buildMediaTarget, g as sendPhoto, h as sendDocument, i as sendProactiveMessage, m as resolveOutboundMediaPath, n as sendCronMessage, p as parseTarget, r as sendMedia, v as sendVoice, w as recordMessageReply, x as checkMessageReplyLimit, y as resolveUserFacingMediaError } from "./outbound-Ba5ryGDm.js";
2
2
  import { c as listQQBotAccountIds, l as resolveDefaultQQBotAccountId, o as DEFAULT_ACCOUNT_ID, s as applyQQBotAccountConfig, u as resolveQQBotAccount } from "./config-schema-iX2iJzKm.js";
3
- import { t as qqbotPlugin } from "./channel-Cx3C1vCZ.js";
4
- import { C as debugError, l as getAccessToken, w as debugLog, z as formatErrorMessage } from "./runtime-PZU9ND1Q.js";
3
+ import { t as qqbotPlugin } from "./channel-8Efx0wKu.js";
4
+ import { C as debugError, l as getAccessToken, w as debugLog, z as formatErrorMessage } from "./runtime-DWfbz21q.js";
5
5
  import { t as qqbotSetupPlugin } from "./channel.setup-BxGpVkdx.js";
6
- import { r as getFrameworkCommands, t as getRequestContext } from "./request-context-D4dkxrmN.js";
6
+ import { r as getFrameworkCommands, t as getRequestContext } from "./request-context-BCsRtyph.js";
7
7
  import { callGatewayTool } from "openclaw/plugin-sdk/agent-harness-runtime";
8
8
  //#region extensions/qqbot/src/engine/tools/channel-api.ts
9
9
  /**
@@ -1,6 +1,6 @@
1
1
  import { c as getPlatformAdapter, t as asOptionalObjectRecord } from "./string-normalize-R_0cKO7Q.js";
2
2
  import { a as qqbotSetupAdapterShared, c as listQQBotAccountIds, i as qqbotMeta, m as getBridgeLogger, n as qqbotSetupWizard, o as DEFAULT_ACCOUNT_ID$1, p as ensurePlatformAdapter, r as qqbotConfigAdapter, s as applyQQBotAccountConfig, t as qqbotChannelConfigSchema, u as resolveQQBotAccount } from "./config-schema-iX2iJzKm.js";
3
- import { T as debugWarn, t as getQQBotRuntime, w as debugLog, z as formatErrorMessage } from "./runtime-PZU9ND1Q.js";
3
+ import { T as debugWarn, t as getQQBotRuntime, w as debugLog, z as formatErrorMessage } from "./runtime-DWfbz21q.js";
4
4
  import { getExecApprovalReplyMetadata } from "openclaw/plugin-sdk/approval-runtime";
5
5
  import { createMessageReceiptFromOutboundResults, defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-outbound";
6
6
  import { createChannelApprovalCapability } from "openclaw/plugin-sdk/approval-delivery-runtime";
@@ -374,7 +374,7 @@ function createQQBotApprovalCapability() {
374
374
  },
375
375
  load: async () => {
376
376
  ensurePlatformAdapter();
377
- return (await import("./handler-runtime-yjslhmCX.js")).qqbotApprovalNativeRuntime;
377
+ return (await import("./handler-runtime-C-5nL3Cl.js")).qqbotApprovalNativeRuntime;
378
378
  }
379
379
  })
380
380
  });
@@ -868,7 +868,7 @@ function looksLikeQQBotTarget(id) {
868
868
  //#region extensions/qqbot/src/channel.ts
869
869
  let gatewayModulePromise;
870
870
  function loadGatewayModule() {
871
- gatewayModulePromise ??= import("./gateway-D1g0WRCr.js");
871
+ gatewayModulePromise ??= import("./gateway-CuTCxSqs.js");
872
872
  return gatewayModulePromise;
873
873
  }
874
874
  function createQQBotSendReceipt(params) {
@@ -886,7 +886,7 @@ function createQQBotSendReceipt(params) {
886
886
  async function sendQQBotText(params) {
887
887
  await loadGatewayModule();
888
888
  const account = resolveQQBotAccount(params.cfg, params.accountId);
889
- const { sendText } = await import("./outbound-DsXljRHh.js").then((n) => n.t);
889
+ const { sendText } = await import("./outbound-Ba5ryGDm.js").then((n) => n.t);
890
890
  const result = await sendText({
891
891
  to: params.to,
892
892
  text: params.text,
@@ -908,7 +908,7 @@ async function sendQQBotText(params) {
908
908
  async function sendQQBotMedia(params) {
909
909
  await loadGatewayModule();
910
910
  const account = resolveQQBotAccount(params.cfg, params.accountId);
911
- const { sendMedia } = await import("./outbound-DsXljRHh.js").then((n) => n.t);
911
+ const { sendMedia } = await import("./outbound-Ba5ryGDm.js").then((n) => n.t);
912
912
  const result = await sendMedia({
913
913
  to: params.to,
914
914
  text: params.text ?? "",
@@ -1,2 +1,2 @@
1
- import { t as qqbotPlugin } from "./channel-Cx3C1vCZ.js";
1
+ import { t as qqbotPlugin } from "./channel-8Efx0wKu.js";
2
2
  export { qqbotPlugin };
@@ -1,9 +1,9 @@
1
- import { E as setOutboundAudioPort, _ as sendVoice, c as isMediaPayload, g as sendVideoMsg, h as sendPhoto, l as parseQQBotPayload, m as sendDocument, o as encodePayloadForCron, r as sendMedia, s as isCronReminderPayload, u as normalizeMediaTags, v as resolveUserFacingMediaError, w as DEFAULT_MEDIA_SEND_ERROR } from "./outbound-DsXljRHh.js";
1
+ import { D as setOutboundAudioPort, T as DEFAULT_MEDIA_SEND_ERROR, _ as sendVideoMsg, c as isCronReminderPayload, d as normalizeMediaTags, g as sendPhoto, h as sendDocument, l as isMediaPayload, o as decodeMediaPath, r as sendMedia, s as encodePayloadForCron, u as parseQQBotPayload, v as sendVoice, y as resolveUserFacingMediaError } from "./outbound-Ba5ryGDm.js";
2
2
  import { c as getPlatformAdapter, i as normalizeOptionalString$1, n as normalizeLowercaseStringOrEmpty$1, o as readStringField, s as sanitizeFileName, t as asOptionalObjectRecord } from "./string-normalize-R_0cKO7Q.js";
3
3
  import { f as resolveAccountBase, h as setBridgeLogger, p as ensurePlatformAdapter } from "./config-schema-iX2iJzKm.js";
4
- import { C as parseApprovalButtonData, a as getQQBotDataDir, d as isWindows, f as normalizePath$1, h as authorizeQQBotApprovalAction, i as getHomeDir, l as getTempDir, m as toGatewayAccount, o as getQQBotDataPath, p as resolveQQBotPayloadLocalFilePath, r as checkSilkWasmAvailable, s as getQQBotMediaDir, u as isLocalPath } from "./channel-Cx3C1vCZ.js";
5
- import { B as StreamContentType, C as debugError, D as getNextMsgSeq, F as getImageMimeType, H as StreamInputState, I as getMaxUploadSize, N as formatFileSize, R as formatDuration, S as withTokenRetry, T as debugWarn, V as StreamInputMode, _ as sendInputNotify, a as acknowledgeInteraction, b as startBackgroundTokenRefresh, c as createRawInputNotifyFn, d as getMessageApi, f as getPluginUserAgent, g as registerAccount, h as onMessageSent, i as accountToCreds, j as downloadFile, k as openLocalFile, l as getAccessToken, m as initSender, n as getQQBotRuntimeForEngine, o as buildDeliveryTarget, p as initApiConfig, s as clearTokenCache, t as getQQBotRuntime, u as getGatewayUrl, v as sendMedia$1, w as debugLog, x as stopBackgroundTokenRefresh, y as sendText, z as formatErrorMessage } from "./runtime-PZU9ND1Q.js";
6
- import { a as getPluginVersion, i as getFrameworkVersion, n as runWithRequestContext, o as initCommands, s as matchSlashCommand } from "./request-context-D4dkxrmN.js";
4
+ import { C as parseApprovalButtonData, a as getQQBotDataDir, d as isWindows, f as normalizePath, h as authorizeQQBotApprovalAction, i as getHomeDir, l as getTempDir, m as toGatewayAccount, o as getQQBotDataPath, p as resolveQQBotPayloadLocalFilePath, r as checkSilkWasmAvailable, s as getQQBotMediaDir, u as isLocalPath } from "./channel-8Efx0wKu.js";
5
+ import { B as StreamContentType, C as debugError, D as getNextMsgSeq, F as getImageMimeType, H as StreamInputState, I as getMaxUploadSize, N as formatFileSize, R as formatDuration, S as withTokenRetry, T as debugWarn, V as StreamInputMode, _ as sendInputNotify, a as acknowledgeInteraction, b as startBackgroundTokenRefresh, c as createRawInputNotifyFn, d as getMessageApi, f as getPluginUserAgent, g as registerAccount, h as onMessageSent, i as accountToCreds, j as downloadFile, k as openLocalFile, l as getAccessToken, m as initSender, n as getQQBotRuntimeForEngine, o as buildDeliveryTarget, p as initApiConfig, s as clearTokenCache, t as getQQBotRuntime, u as getGatewayUrl, v as sendMedia$1, w as debugLog, x as stopBackgroundTokenRefresh, y as sendText, z as formatErrorMessage } from "./runtime-DWfbz21q.js";
6
+ import { a as getPluginVersion, i as getFrameworkVersion, n as runWithRequestContext, o as initCommands, s as matchSlashCommand } from "./request-context-BCsRtyph.js";
7
7
  import { asBoolean, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
8
8
  import { isImplicitSameChatApprovalAuthorization } from "openclaw/plugin-sdk/approval-auth-runtime";
9
9
  import * as fs$1 from "node:fs";
@@ -15,6 +15,7 @@ import { appendRegularFileSync, privateFileStoreSync, readRegularFileSync, repla
15
15
  import * as path$1 from "node:path";
16
16
  import path from "node:path";
17
17
  import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
18
+ import { MAX_TIMER_TIMEOUT_MS, finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
18
19
  import { resolveRuntimeServiceVersion } from "openclaw/plugin-sdk/cli-runtime";
19
20
  import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
20
21
  import WebSocket from "ws";
@@ -3465,64 +3466,6 @@ function hasQQBotImageSize(markdownImage) {
3465
3466
  return /!\[#\d+px\s+#\d+px\]/.test(markdownImage);
3466
3467
  }
3467
3468
  //#endregion
3468
- //#region extensions/qqbot/src/engine/messaging/decode-media-path.ts
3469
- /**
3470
- * Normalize a file path by expanding `~` to the home directory and trimming.
3471
- *
3472
- * This is a minimal re-implementation of `utils/platform.ts#normalizePath`
3473
- * so that `core/` remains self-contained.
3474
- */
3475
- function normalizePath(p) {
3476
- let result = p.trim();
3477
- if (result.startsWith("~/") || result === "~") {
3478
- const home = typeof process !== "undefined" ? process.env.HOME ?? process.env.USERPROFILE : void 0;
3479
- if (home) result = result === "~" ? home : `${home}${result.slice(1)}`;
3480
- }
3481
- return result;
3482
- }
3483
- /**
3484
- * Decode a media path by stripping `MEDIA:`, expanding `~`, and unescaping
3485
- * octal/UTF-8 byte sequences.
3486
- *
3487
- * @param raw - Raw path string from a media tag.
3488
- * @param log - Optional logger for decode diagnostics.
3489
- * @returns The decoded, normalized media path.
3490
- */
3491
- function decodeMediaPath(raw, log) {
3492
- let mediaPath = raw;
3493
- if (mediaPath.startsWith("MEDIA:")) mediaPath = mediaPath.slice(6);
3494
- mediaPath = normalizePath(mediaPath);
3495
- mediaPath = mediaPath.replace(/\\\\/g, "\\");
3496
- const isWinLocal = /^[a-zA-Z]:[\\/]/.test(mediaPath) || mediaPath.startsWith("\\\\");
3497
- try {
3498
- const hasOctal = /\\[0-7]{1,3}/.test(mediaPath);
3499
- const hasNonASCII = /[\u0080-\u00FF]/.test(mediaPath);
3500
- if (!isWinLocal && (hasOctal || hasNonASCII)) {
3501
- log?.debug?.(`Decoding path with mixed encoding: ${mediaPath}`);
3502
- const decoded = mediaPath.replace(/\\([0-7]{1,3})/g, (_, octal) => {
3503
- return String.fromCharCode(Number.parseInt(octal, 8));
3504
- });
3505
- const bytes = [];
3506
- for (let i = 0; i < decoded.length; i++) {
3507
- const code = decoded.charCodeAt(i);
3508
- if (code <= 255) bytes.push(code);
3509
- else {
3510
- const charBytes = Buffer.from(decoded[i], "utf8");
3511
- bytes.push(...charBytes);
3512
- }
3513
- }
3514
- const utf8Decoded = Buffer.from(bytes).toString("utf8");
3515
- if (!utf8Decoded.includes("�") || utf8Decoded.length < decoded.length) {
3516
- mediaPath = utf8Decoded;
3517
- log?.debug?.(`Successfully decoded path: ${mediaPath}`);
3518
- }
3519
- }
3520
- } catch (decodeErr) {
3521
- log?.error(`Path decode error: ${String(decodeErr)}`);
3522
- }
3523
- return mediaPath;
3524
- }
3525
- //#endregion
3526
3469
  //#region extensions/qqbot/src/engine/messaging/outbound-deliver.ts
3527
3470
  /** Maximum text length for a single QQ Bot message. */
3528
3471
  const TEXT_CHUNK_LIMIT = 5e3;
@@ -4018,7 +3961,7 @@ function isInlineImageDataUrl(p) {
4018
3961
  }
4019
3962
  function resolveStructuredPayloadPath(ctx, payload, mediaType) {
4020
3963
  const originalPath = payload.path ?? "";
4021
- const normalizedPath = normalizePath$1(originalPath);
3964
+ const normalizedPath = normalizePath(originalPath);
4022
3965
  const isHttpUrl = isRemoteHttpUrl(normalizedPath);
4023
3966
  const resolvedPath = isHttpUrl ? normalizedPath : validateStructuredPayloadLocalPath(ctx, originalPath, mediaType);
4024
3967
  if (!resolvedPath) return null;
@@ -4080,7 +4023,7 @@ async function assertLocalFileWithinTypeLimit(filePath, fileType) {
4080
4023
  }
4081
4024
  async function handleImagePayload(ctx, payload) {
4082
4025
  const { target, account, log } = ctx;
4083
- const normalizedPath = normalizePath$1(payload.path);
4026
+ const normalizedPath = normalizePath(payload.path);
4084
4027
  let imageUrl;
4085
4028
  if (payload.source === "file") imageUrl = validateStructuredPayloadLocalPath(ctx, normalizedPath, "image");
4086
4029
  else if (isRemoteHttpUrl(normalizedPath) || isInlineImageDataUrl(normalizedPath)) imageUrl = normalizedPath;
@@ -4368,7 +4311,7 @@ function findFirstClosedMediaTag(text, log) {
4368
4311
  const tagName = match[1].toLowerCase();
4369
4312
  let mediaPath = match[2]?.trim() ?? "";
4370
4313
  if (mediaPath.startsWith("MEDIA:")) mediaPath = mediaPath.slice(6);
4371
- mediaPath = normalizePath$1(mediaPath);
4314
+ mediaPath = normalizePath(mediaPath);
4372
4315
  mediaPath = fixPathEncoding(mediaPath, log);
4373
4316
  return {
4374
4317
  textBefore,
@@ -5676,7 +5619,7 @@ function parseWavFallback(buf) {
5676
5619
  * already configured:
5677
5620
  * - `agents.defaults.timeoutSeconds`
5678
5621
  * - `models.providers.<id>.timeoutSeconds` (max across configured providers)
5679
- * Take the maximum and clamp to `[DEFAULT_RESPONSE_TIMEOUT_MS, MAX_SAFE_TIMEOUT_MS]`.
5622
+ * Take the maximum and clamp to `[DEFAULT_RESPONSE_TIMEOUT_MS, MAX_TIMER_TIMEOUT_MS]`.
5680
5623
  * The default floor preserves the existing 5-minute guard for users
5681
5624
  * that have not configured any longer ceiling — i.e. a no-op for
5682
5625
  * typical cloud-model deployments.
@@ -5688,16 +5631,6 @@ function parseWavFallback(buf) {
5688
5631
  */
5689
5632
  const DEFAULT_RESPONSE_TIMEOUT_MS = 3e5;
5690
5633
  /**
5691
- * Upper bound to keep the watchdog inside the safe `setTimeout` range
5692
- * (approximately 24.8 days). Mirrors `MAX_SAFE_TIMEOUT_MS` in
5693
- * `src/agents/embedded-agent-runner/run/llm-idle-timeout.ts`.
5694
- */
5695
- const MAX_SAFE_TIMEOUT_MS = 2147e6;
5696
- function positiveSecondsToMs(value) {
5697
- if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return;
5698
- return Math.floor(value * 1e3);
5699
- }
5700
- /**
5701
5634
  * Resolve the QQBot outbound response watchdog (ms).
5702
5635
  *
5703
5636
  * The watchdog is the longest of:
@@ -5706,21 +5639,21 @@ function positiveSecondsToMs(value) {
5706
5639
  * - the maximum `cfg.models.providers.<id>.timeoutSeconds` across
5707
5640
  * configured providers, converted to ms
5708
5641
  *
5709
- * Returns at most `MAX_SAFE_TIMEOUT_MS` so the chosen value is always
5642
+ * Returns at most `MAX_TIMER_TIMEOUT_MS` so the chosen value is always
5710
5643
  * a safe `setTimeout` argument.
5711
5644
  */
5712
5645
  function resolveResponseTimeoutMs(cfg) {
5713
5646
  const candidates = [DEFAULT_RESPONSE_TIMEOUT_MS];
5714
5647
  const typed = cfg ?? {};
5715
- const agentDefaultMs = positiveSecondsToMs(typed.agents?.defaults?.timeoutSeconds);
5648
+ const agentDefaultMs = finiteSecondsToTimerSafeMilliseconds(typed.agents?.defaults?.timeoutSeconds);
5716
5649
  if (agentDefaultMs !== void 0) candidates.push(agentDefaultMs);
5717
5650
  const providers = typed.models?.providers;
5718
5651
  if (providers && typeof providers === "object") for (const entry of Object.values(providers)) {
5719
- const providerMs = positiveSecondsToMs(entry?.timeoutSeconds);
5652
+ const providerMs = finiteSecondsToTimerSafeMilliseconds(entry?.timeoutSeconds);
5720
5653
  if (providerMs !== void 0) candidates.push(providerMs);
5721
5654
  }
5722
5655
  const chosen = Math.max(...candidates);
5723
- return Math.min(chosen, MAX_SAFE_TIMEOUT_MS);
5656
+ return Math.min(chosen, MAX_TIMER_TIMEOUT_MS);
5724
5657
  }
5725
5658
  //#endregion
5726
5659
  //#region extensions/qqbot/src/engine/gateway/outbound-dispatch.ts
@@ -6341,7 +6274,7 @@ async function startTypingForEvent(event, account, log) {
6341
6274
  try {
6342
6275
  const creds = accountToCreds(account);
6343
6276
  const rawNotifyFn = createRawInputNotifyFn(account.appId);
6344
- try {
6277
+ const sendNotifyAndStartKeepAlive = async () => {
6345
6278
  const resp = await sendInputNotify({
6346
6279
  openid: event.senderId,
6347
6280
  creds,
@@ -6354,22 +6287,14 @@ async function startTypingForEvent(event, account, log) {
6354
6287
  refIdx: resp.refIdx,
6355
6288
  keepAlive
6356
6289
  };
6290
+ };
6291
+ try {
6292
+ return await sendNotifyAndStartKeepAlive();
6357
6293
  } catch (notifyErr) {
6358
6294
  const errMsg = String(notifyErr);
6359
6295
  if (errMsg.includes("token") || errMsg.includes("401") || errMsg.includes("11244")) {
6360
6296
  clearTokenCache(account.appId);
6361
- const resp = await sendInputNotify({
6362
- openid: event.senderId,
6363
- creds,
6364
- msgId: event.messageId,
6365
- inputSecond: 10
6366
- });
6367
- const keepAlive = new TypingKeepAlive(() => getAccessToken(account.appId, account.clientSecret), () => clearTokenCache(account.appId), rawNotifyFn, event.senderId, event.messageId, log);
6368
- keepAlive.start();
6369
- return {
6370
- refIdx: resp.refIdx,
6371
- keepAlive
6372
- };
6297
+ return await sendNotifyAndStartKeepAlive();
6373
6298
  }
6374
6299
  throw notifyErr;
6375
6300
  }
@@ -1,6 +1,6 @@
1
1
  import { m as getBridgeLogger, p as ensurePlatformAdapter, u as resolveQQBotAccount } from "./config-schema-iX2iJzKm.js";
2
- import { S as buildPluginApprovalText, _ as matchesQQBotApprovalAccount, b as buildApprovalKeyboard, g as isQQBotExecApprovalClientEnabled, v as resolveQQBotExecApprovalConfig, w as resolveApprovalTarget, x as buildExecApprovalText, y as shouldHandleQQBotExecApprovalRequest } from "./channel-Cx3C1vCZ.js";
3
- import { d as getMessageApi, i as accountToCreds } from "./runtime-PZU9ND1Q.js";
2
+ import { S as buildPluginApprovalText, _ as matchesQQBotApprovalAccount, b as buildApprovalKeyboard, g as isQQBotExecApprovalClientEnabled, v as resolveQQBotExecApprovalConfig, w as resolveApprovalTarget, x as buildExecApprovalText, y as shouldHandleQQBotExecApprovalRequest } from "./channel-8Efx0wKu.js";
3
+ import { d as getMessageApi, i as accountToCreds } from "./runtime-DWfbz21q.js";
4
4
  import { resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
5
5
  import { createChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
6
6
  //#region extensions/qqbot/src/bridge/approval/handler-runtime.ts
@@ -1,6 +1,6 @@
1
1
  import { i as normalizeOptionalString, n as normalizeLowercaseStringOrEmpty, s as sanitizeFileName } from "./string-normalize-R_0cKO7Q.js";
2
- import { a as getQQBotDataDir, f as normalizePath, n as parseTarget$1, p as resolveQQBotPayloadLocalFilePath, s as getQQBotMediaDir, u as isLocalPath } from "./channel-Cx3C1vCZ.js";
3
- import { A as checkFileSize, C as debugError, E as UploadDailyLimitExceededError, F as getImageMimeType, I as getMaxUploadSize, L as readFileAsync, M as fileExistsAsync, N as formatFileSize, O as UPLOAD_PREPARE_FALLBACK_CODE, P as getFileTypeName, T as debugWarn, i as accountToCreds, j as downloadFile, p as initApiConfig, v as sendMedia$1, w as debugLog, y as sendText$1, z as formatErrorMessage } from "./runtime-PZU9ND1Q.js";
2
+ import { a as getQQBotDataDir, f as normalizePath$1, n as parseTarget$1, p as resolveQQBotPayloadLocalFilePath, s as getQQBotMediaDir, u as isLocalPath } from "./channel-8Efx0wKu.js";
3
+ import { A as checkFileSize, C as debugError, E as UploadDailyLimitExceededError, F as getImageMimeType, I as getMaxUploadSize, L as readFileAsync, M as fileExistsAsync, N as formatFileSize, O as UPLOAD_PREPARE_FALLBACK_CODE, P as getFileTypeName, T as debugWarn, i as accountToCreds, j as downloadFile, p as initApiConfig, v as sendMedia$1, w as debugLog, y as sendText$1, z as formatErrorMessage } from "./runtime-DWfbz21q.js";
4
4
  import { pathExistsSync, resolveLocalPathFromRootsSync } from "openclaw/plugin-sdk/security-runtime";
5
5
  import path from "node:path";
6
6
  import { getFileExtension } from "openclaw/plugin-sdk/media-mime";
@@ -258,7 +258,7 @@ function resolveExistingPathWithinRoots(normalizedPath, allowedRoots) {
258
258
  })?.path ?? null;
259
259
  }
260
260
  function resolveOutboundMediaPath(rawPath, mediaKind, options = {}) {
261
- const normalizedPath = normalizePath(rawPath);
261
+ const normalizedPath = normalizePath$1(rawPath);
262
262
  if (isHttpOrDataSource(normalizedPath)) return {
263
263
  ok: true,
264
264
  mediaPath: normalizedPath
@@ -970,6 +970,75 @@ function isMediaPayload(payload) {
970
970
  return payload.type === "media";
971
971
  }
972
972
  //#endregion
973
+ //#region extensions/qqbot/src/engine/messaging/decode-media-path.ts
974
+ function getHomeForTildePath(windowsStyle) {
975
+ if (windowsStyle && process.env.USERPROFILE) return process.env.USERPROFILE;
976
+ return process.env.HOME ?? process.env.USERPROFILE;
977
+ }
978
+ /**
979
+ * Normalize a file path by expanding `~` to the home directory and trimming.
980
+ *
981
+ * This is a minimal re-implementation of `utils/platform.ts#normalizePath`
982
+ * so that `core/` remains self-contained.
983
+ */
984
+ function normalizePath(p) {
985
+ let result = p.trim();
986
+ if (result.startsWith("file://")) {
987
+ result = result.slice(7);
988
+ try {
989
+ result = decodeURIComponent(result);
990
+ } catch {}
991
+ }
992
+ const windowsStyleHomePath = result.startsWith("~\\");
993
+ if (result === "~" || result.startsWith("~/") || windowsStyleHomePath) {
994
+ const home = typeof process !== "undefined" ? getHomeForTildePath(windowsStyleHomePath) : void 0;
995
+ if (home) result = result === "~" ? home : `${home}${result.slice(1)}`;
996
+ }
997
+ return result;
998
+ }
999
+ /**
1000
+ * Decode a media path by stripping `MEDIA:`, expanding `~`, and unescaping
1001
+ * octal/UTF-8 byte sequences.
1002
+ *
1003
+ * @param raw - Raw path string from a media tag.
1004
+ * @param log - Optional logger for decode diagnostics.
1005
+ * @returns The decoded, normalized media path.
1006
+ */
1007
+ function decodeMediaPath(raw, log) {
1008
+ let mediaPath = raw;
1009
+ if (mediaPath.startsWith("MEDIA:")) mediaPath = mediaPath.slice(6);
1010
+ mediaPath = normalizePath(mediaPath);
1011
+ mediaPath = mediaPath.replace(/\\\\/g, "\\");
1012
+ const isWinLocal = /^[a-zA-Z]:[\\/]/.test(mediaPath) || mediaPath.startsWith("\\\\");
1013
+ try {
1014
+ const hasOctal = /\\[0-7]{1,3}/.test(mediaPath);
1015
+ const hasNonASCII = /[\u0080-\u00FF]/.test(mediaPath);
1016
+ if (!isWinLocal && (hasOctal || hasNonASCII)) {
1017
+ log?.debug?.(`Decoding path with mixed encoding: ${mediaPath}`);
1018
+ const decoded = mediaPath.replace(/\\([0-7]{1,3})/g, (_, octal) => {
1019
+ return String.fromCharCode(Number.parseInt(octal, 8));
1020
+ });
1021
+ const bytes = [];
1022
+ for (let i = 0; i < decoded.length; i++) {
1023
+ const code = decoded.charCodeAt(i);
1024
+ if (code <= 255) bytes.push(code);
1025
+ else {
1026
+ const charBytes = Buffer.from(decoded[i], "utf8");
1027
+ bytes.push(...charBytes);
1028
+ }
1029
+ }
1030
+ const utf8Decoded = Buffer.from(bytes).toString("utf8");
1031
+ if (!utf8Decoded.includes("�") || utf8Decoded.length < decoded.length) {
1032
+ mediaPath = utf8Decoded;
1033
+ log?.debug?.(`Successfully decoded path: ${mediaPath}`);
1034
+ }
1035
+ }
1036
+ } catch (decodeErr) {
1037
+ log?.error(`Path decode error: ${String(decodeErr)}`);
1038
+ }
1039
+ return mediaPath;
1040
+ }
1041
+ //#endregion
973
1042
  //#region extensions/qqbot/src/engine/messaging/media-type-detect.ts
974
1043
  /**
975
1044
  * Media type detection — pure functions for classifying files by MIME or extension.
@@ -1016,6 +1085,11 @@ var outbound_exports = /* @__PURE__ */ __exportAll({
1016
1085
  });
1017
1086
  const isImageFile = isImageFile$1;
1018
1087
  const isVideoFile = isVideoFile$1;
1088
+ const mediaPathDecodeLog = {
1089
+ info: (message) => debugLog(`[qqbot] sendText: ${message}`),
1090
+ error: (message) => debugError(`[qqbot] sendText: ${message}`),
1091
+ debug: (message) => debugLog(`[qqbot] sendText: ${message}`)
1092
+ };
1019
1093
  /**
1020
1094
  * Send text, optionally falling back from passive reply mode to proactive mode.
1021
1095
  *
@@ -1062,37 +1136,7 @@ async function sendText(ctx) {
1062
1136
  content: textBefore
1063
1137
  });
1064
1138
  const tagName = normalizeLowercaseStringOrEmpty(match[1]);
1065
- let mediaPath = normalizeOptionalString(match[2]) ?? "";
1066
- if (mediaPath.startsWith("MEDIA:")) mediaPath = mediaPath.slice(6);
1067
- mediaPath = normalizePath(mediaPath);
1068
- mediaPath = mediaPath.replace(/\\\\/g, "\\");
1069
- const isWinLocal = /^[a-zA-Z]:[\\/]/.test(mediaPath) || mediaPath.startsWith("\\\\");
1070
- try {
1071
- const hasOctal = /\\[0-7]{1,3}/.test(mediaPath);
1072
- const hasNonASCII = /[\u0080-\u00FF]/.test(mediaPath);
1073
- if (!isWinLocal && (hasOctal || hasNonASCII)) {
1074
- debugLog(`[qqbot] sendText: Decoding path with mixed encoding: ${mediaPath}`);
1075
- let decoded = mediaPath.replace(/\\([0-7]{1,3})/g, (_, octal) => {
1076
- return String.fromCharCode(Number.parseInt(octal, 8));
1077
- });
1078
- const bytes = [];
1079
- for (let i = 0; i < decoded.length; i++) {
1080
- const code = decoded.charCodeAt(i);
1081
- if (code <= 255) bytes.push(code);
1082
- else {
1083
- const charBytes = Buffer.from(decoded[i], "utf8");
1084
- bytes.push(...charBytes);
1085
- }
1086
- }
1087
- const utf8Decoded = Buffer.from(bytes).toString("utf8");
1088
- if (!utf8Decoded.includes("�") || utf8Decoded.length < decoded.length) {
1089
- mediaPath = utf8Decoded;
1090
- debugLog(`[qqbot] sendText: Successfully decoded path: ${mediaPath}`);
1091
- }
1092
- }
1093
- } catch (decodeErr) {
1094
- debugError(`[qqbot] sendText: Path decode error: ${decodeErr instanceof Error ? decodeErr.message : JSON.stringify(decodeErr)}`);
1095
- }
1139
+ const mediaPath = decodeMediaPath(normalizeOptionalString(match[2]) ?? "", mediaPathDecodeLog);
1096
1140
  if (mediaPath) if (tagName === "qqmedia") {
1097
1141
  sendQueue.push({
1098
1142
  type: "media",
@@ -1322,4 +1366,4 @@ async function sendCronMessage(account, to, message) {
1322
1366
  });
1323
1367
  }
1324
1368
  //#endregion
1325
- export { recordMessageReply as C, setOutboundAudioPort as E, getMessageReplyStats as S, OUTBOUND_ERROR_CODES as T, sendVoice as _, sendText as a, checkMessageReplyLimit as b, isMediaPayload as c, buildMediaTarget as d, parseTarget as f, sendVideoMsg as g, sendPhoto as h, sendProactiveMessage as i, parseQQBotPayload as l, sendDocument as m, sendCronMessage as n, encodePayloadForCron as o, resolveOutboundMediaPath as p, sendMedia as r, isCronReminderPayload as s, outbound_exports as t, normalizeMediaTags as u, resolveUserFacingMediaError as v, DEFAULT_MEDIA_SEND_ERROR as w, getMessageReplyConfig as x, MESSAGE_REPLY_LIMIT as y };
1369
+ export { getMessageReplyStats as C, setOutboundAudioPort as D, OUTBOUND_ERROR_CODES as E, getMessageReplyConfig as S, DEFAULT_MEDIA_SEND_ERROR as T, sendVideoMsg as _, sendText as a, MESSAGE_REPLY_LIMIT as b, isCronReminderPayload as c, normalizeMediaTags as d, buildMediaTarget as f, sendPhoto as g, sendDocument as h, sendProactiveMessage as i, isMediaPayload as l, resolveOutboundMediaPath as m, sendCronMessage as n, decodeMediaPath as o, parseTarget as p, sendMedia as r, encodePayloadForCron as s, outbound_exports as t, parseQQBotPayload as u, sendVoice as v, recordMessageReply as w, checkMessageReplyLimit as x, resolveUserFacingMediaError as y };
@@ -1,5 +1,5 @@
1
- import { a as getQQBotDataDir, c as getQQBotMediaPath, d as isWindows, i as getHomeDir } from "./channel-Cx3C1vCZ.js";
2
- import { w as debugLog } from "./runtime-PZU9ND1Q.js";
1
+ import { a as getQQBotDataDir, c as getQQBotMediaPath, d as isWindows, i as getHomeDir } from "./channel-8Efx0wKu.js";
2
+ import { w as debugLog } from "./runtime-DWfbz21q.js";
3
3
  import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
@@ -9,6 +9,7 @@ import { FsSafeError, openLocalFileSafely, readRegularFile, statRegularFileSync
9
9
  import * as path$1 from "node:path";
10
10
  import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
11
11
  import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
12
+ import { parseStrictPositiveInteger } from "openclaw/plugin-sdk/number-runtime";
12
13
  //#region extensions/qqbot/src/engine/types.ts
13
14
  /**
14
15
  * Core API layer public types.
@@ -988,7 +989,21 @@ function sleep(ms) {
988
989
  }
989
990
  //#endregion
990
991
  //#region extensions/qqbot/src/engine/api/token.ts
992
+ /**
993
+ * Token management for the QQ Open Platform.
994
+ *
995
+ * All state (cache, singleflight promises, background refresh controllers)
996
+ * is encapsulated in the `TokenManager` class instance — no module-level
997
+ * globals, fully supporting multi-account concurrent operation.
998
+ */
991
999
  const TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken";
1000
+ const DEFAULT_TOKEN_EXPIRES_IN_SECONDS = 7200;
1001
+ function resolveTokenExpiresInSeconds(value) {
1002
+ const parsed = parseStrictPositiveInteger(value);
1003
+ if (parsed !== void 0) return parsed;
1004
+ if (value == null || typeof value === "number" && !Number.isFinite(value)) return DEFAULT_TOKEN_EXPIRES_IN_SECONDS;
1005
+ return 0;
1006
+ }
992
1007
  /**
993
1008
  * Per-appId token manager with caching, singleflight, and background refresh.
994
1009
  *
@@ -1116,47 +1131,59 @@ var TokenManager = class {
1116
1131
  async doFetchToken(appId, clientSecret) {
1117
1132
  this.logger?.debug?.(`[qqbot:token:${appId}] >>> POST ${TOKEN_URL}`);
1118
1133
  let response;
1134
+ let release;
1119
1135
  try {
1120
- response = await fetch(TOKEN_URL, {
1121
- method: "POST",
1122
- headers: {
1123
- "Content-Type": "application/json",
1124
- "User-Agent": this.resolveUserAgent()
1125
- },
1126
- body: JSON.stringify({
1127
- appId,
1128
- clientSecret
1129
- })
1136
+ const guarded = await fetchWithSsrFGuard({
1137
+ url: TOKEN_URL,
1138
+ auditContext: "qqbot-token",
1139
+ capture: false,
1140
+ init: {
1141
+ method: "POST",
1142
+ headers: {
1143
+ "Content-Type": "application/json",
1144
+ "User-Agent": this.resolveUserAgent()
1145
+ },
1146
+ body: JSON.stringify({
1147
+ appId,
1148
+ clientSecret
1149
+ })
1150
+ }
1130
1151
  });
1152
+ response = guarded.response;
1153
+ release = guarded.release;
1131
1154
  } catch (err) {
1132
1155
  this.logger?.error?.(`[qqbot:token:${appId}] Network error: ${formatErrorMessage(err)}`);
1133
1156
  throw new Error(`Network error getting access_token: ${formatErrorMessage(err)}`, { cause: err });
1134
1157
  }
1135
- const traceId = response.headers.get("x-tps-trace-id") ?? "";
1136
- this.logger?.debug?.(`[qqbot:token:${appId}] <<< ${response.status}${traceId ? ` | TraceId: ${traceId}` : ""}`);
1137
- let rawBody;
1138
- try {
1139
- rawBody = await response.text();
1140
- } catch (err) {
1141
- throw new Error(`Failed to read access_token response: ${formatErrorMessage(err)}`, { cause: err });
1142
- }
1143
- const logBody = rawBody.replace(/"access_token"\s*:\s*"[^"]+"/g, "\"access_token\": \"***\"");
1144
- this.logger?.debug?.(`[qqbot:token:${appId}] <<< Body: ${logBody}`);
1145
- let data;
1146
1158
  try {
1147
- data = JSON.parse(rawBody);
1148
- } catch {
1149
- throw new Error("QQBot access_token response was malformed JSON");
1159
+ const traceId = response.headers.get("x-tps-trace-id") ?? "";
1160
+ this.logger?.debug?.(`[qqbot:token:${appId}] <<< ${response.status}${traceId ? ` | TraceId: ${traceId}` : ""}`);
1161
+ let rawBody;
1162
+ try {
1163
+ rawBody = await response.text();
1164
+ } catch (err) {
1165
+ throw new Error(`Failed to read access_token response: ${formatErrorMessage(err)}`, { cause: err });
1166
+ }
1167
+ const logBody = rawBody.replace(/"access_token"\s*:\s*"[^"]+"/g, "\"access_token\": \"***\"");
1168
+ this.logger?.debug?.(`[qqbot:token:${appId}] <<< Body: ${logBody}`);
1169
+ let data;
1170
+ try {
1171
+ data = JSON.parse(rawBody);
1172
+ } catch {
1173
+ throw new Error("QQBot access_token response was malformed JSON");
1174
+ }
1175
+ if (!data.access_token) throw new Error(`Failed to get access_token: ${JSON.stringify(data)}`);
1176
+ const expiresAt = Date.now() + resolveTokenExpiresInSeconds(data.expires_in) * 1e3;
1177
+ this.cache.set(appId, {
1178
+ token: data.access_token,
1179
+ expiresAt,
1180
+ appId
1181
+ });
1182
+ this.logger?.debug?.(`[qqbot:token:${appId}] Cached, expires at: ${new Date(expiresAt).toISOString()}`);
1183
+ return data.access_token;
1184
+ } finally {
1185
+ await release?.();
1150
1186
  }
1151
- if (!data.access_token) throw new Error(`Failed to get access_token: ${JSON.stringify(data)}`);
1152
- const expiresAt = Date.now() + (data.expires_in ?? 7200) * 1e3;
1153
- this.cache.set(appId, {
1154
- token: data.access_token,
1155
- expiresAt,
1156
- appId
1157
- });
1158
- this.logger?.debug?.(`[qqbot:token:${appId}] Cached, expires at: ${new Date(expiresAt).toISOString()}`);
1159
- return data.access_token;
1160
1187
  }
1161
1188
  abortableSleep(ms, signal) {
1162
1189
  return new Promise((resolve, reject) => {
@@ -1,2 +1,2 @@
1
- import { r as setQQBotRuntime, t as getQQBotRuntime } from "./runtime-PZU9ND1Q.js";
1
+ import { r as setQQBotRuntime, t as getQQBotRuntime } from "./runtime-DWfbz21q.js";
2
2
  export { getQQBotRuntime, setQQBotRuntime };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/qqbot",
3
- "version": "2026.5.28-beta.2",
3
+ "version": "2026.5.28-beta.4",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/qqbot",
9
- "version": "2026.5.28-beta.2",
9
+ "version": "2026.5.28-beta.4",
10
10
  "dependencies": {
11
11
  "@tencent-connect/qqbot-connector": "1.1.0",
12
12
  "mpg123-decoder": "1.0.3",
@@ -15,7 +15,7 @@
15
15
  "zod": "4.4.3"
16
16
  },
17
17
  "peerDependencies": {
18
- "openclaw": ">=2026.5.28-beta.2"
18
+ "openclaw": ">=2026.5.28-beta.4"
19
19
  },
20
20
  "peerDependenciesMeta": {
21
21
  "openclaw": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/qqbot",
3
- "version": "2026.5.28-beta.2",
3
+ "version": "2026.5.28-beta.4",
4
4
  "private": false,
5
5
  "description": "OpenClaw QQ Bot channel plugin for group and direct-message workflows.",
6
6
  "repository": {
@@ -16,7 +16,7 @@
16
16
  "zod": "4.4.3"
17
17
  },
18
18
  "peerDependencies": {
19
- "openclaw": ">=2026.5.28-beta.2"
19
+ "openclaw": ">=2026.5.28-beta.4"
20
20
  },
21
21
  "peerDependenciesMeta": {
22
22
  "openclaw": {
@@ -45,10 +45,10 @@
45
45
  "minHostVersion": ">=2026.4.10"
46
46
  },
47
47
  "compat": {
48
- "pluginApi": ">=2026.5.28-beta.2"
48
+ "pluginApi": ">=2026.5.28-beta.4"
49
49
  },
50
50
  "build": {
51
- "openclawVersion": "2026.5.28-beta.2"
51
+ "openclawVersion": "2026.5.28-beta.4"
52
52
  },
53
53
  "release": {
54
54
  "publishToClawHub": true,