@openclaw/qqbot 2026.5.27 → 2026.5.28-beta.2

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-DHc9KJCp.js";
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";
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-C_7BS_5A.js";
4
- import { C as debugError, l as getAccessToken, w as debugLog, z as formatErrorMessage } from "./runtime-TzkQ0YbR.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";
5
5
  import { t as qqbotSetupPlugin } from "./channel.setup-BxGpVkdx.js";
6
- import { r as getFrameworkCommands, t as getRequestContext } from "./request-context-Dge7ttkg.js";
6
+ import { r as getFrameworkCommands, t as getRequestContext } from "./request-context-D4dkxrmN.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
  /**
@@ -269,14 +269,17 @@ const RemindSchema = {
269
269
  * @returns Milliseconds or null if unparseable.
270
270
  */
271
271
  function parseRelativeTime(timeStr) {
272
- const s = timeStr.toLowerCase();
272
+ const s = timeStr.trim().toLowerCase();
273
273
  if (/^\d+$/.test(s)) return Number.parseInt(s, 10) * 6e4;
274
274
  let totalMs = 0;
275
275
  let matched = false;
276
- const regex = /(\d+(?:\.\d+)?)\s*(d|h|m|s)/g;
276
+ let consumed = 0;
277
+ const regex = /(\d+(?:\.\d+)?)\s*(d|h|m|s)\s*/g;
277
278
  let match;
278
279
  while ((match = regex.exec(s)) !== null) {
280
+ if (match.index !== consumed) return null;
279
281
  matched = true;
282
+ consumed = regex.lastIndex;
280
283
  const value = Number.parseFloat(match[1]);
281
284
  switch (match[2]) {
282
285
  case "d":
@@ -293,7 +296,7 @@ function parseRelativeTime(timeStr) {
293
296
  break;
294
297
  }
295
298
  }
296
- return matched ? Math.round(totalMs) : null;
299
+ return matched && consumed === s.length ? Math.round(totalMs) : null;
297
300
  }
298
301
  /**
299
302
  * Check whether a time string is a cron expression (3–6 space-separated fields).
@@ -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-TzkQ0YbR.js";
3
+ import { T as debugWarn, t as getQQBotRuntime, w as debugLog, z as formatErrorMessage } from "./runtime-PZU9ND1Q.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";
@@ -28,13 +28,7 @@ function buildExecApprovalText(request) {
28
28
  lines.push("", `\u23f1\ufe0f \u8d85\u65f6: ${expiresIn} \u79d2`);
29
29
  return lines.join("\n");
30
30
  }
31
- function appendCommandActionLines(lines, actions) {
32
- const commandActions = actions.filter((action) => (action.kind === "command" || action.decision == null) && typeof action.command === "string" && action.command.trim().length > 0);
33
- if (commandActions.length === 0) return;
34
- lines.push("", "🗨️ 命令:");
35
- for (const action of commandActions) lines.push(`- ${action.label}: ${action.command.trim()}`);
36
- }
37
- function buildPluginApprovalText(request, actions = []) {
31
+ function buildPluginApprovalText(request) {
38
32
  const timeoutSec = Math.round((request.request.timeoutMs ?? 12e4) / 1e3);
39
33
  const lines = [`${request.request.severity === "critical" ? "🔴" : request.request.severity === "info" ? "🔵" : "🟡"} \u5ba1\u6279\u8bf7\u6c42`, ""];
40
34
  lines.push(`\u{1f4cb} ${request.request.title}`);
@@ -42,7 +36,6 @@ function buildPluginApprovalText(request, actions = []) {
42
36
  if (request.request.toolName) lines.push(`\u{1f527} \u5de5\u5177: ${request.request.toolName}`);
43
37
  if (request.request.pluginId) lines.push(`\u{1f50c} \u63d2\u4ef6: ${request.request.pluginId}`);
44
38
  if (request.request.agentId) lines.push(`\u{1f916} Agent: ${request.request.agentId}`);
45
- appendCommandActionLines(lines, actions);
46
39
  lines.push("", `\u23f1\ufe0f \u8d85\u65f6: ${timeoutSec} \u79d2`);
47
40
  return lines.join("\n");
48
41
  }
@@ -381,7 +374,7 @@ function createQQBotApprovalCapability() {
381
374
  },
382
375
  load: async () => {
383
376
  ensurePlatformAdapter();
384
- return (await import("./handler-runtime-CSVOet7I.js")).qqbotApprovalNativeRuntime;
377
+ return (await import("./handler-runtime-yjslhmCX.js")).qqbotApprovalNativeRuntime;
385
378
  }
386
379
  })
387
380
  });
@@ -875,7 +868,7 @@ function looksLikeQQBotTarget(id) {
875
868
  //#region extensions/qqbot/src/channel.ts
876
869
  let gatewayModulePromise;
877
870
  function loadGatewayModule() {
878
- gatewayModulePromise ??= import("./gateway-BdWwKDtj.js");
871
+ gatewayModulePromise ??= import("./gateway-D1g0WRCr.js");
879
872
  return gatewayModulePromise;
880
873
  }
881
874
  function createQQBotSendReceipt(params) {
@@ -893,7 +886,7 @@ function createQQBotSendReceipt(params) {
893
886
  async function sendQQBotText(params) {
894
887
  await loadGatewayModule();
895
888
  const account = resolveQQBotAccount(params.cfg, params.accountId);
896
- const { sendText } = await import("./outbound-DHc9KJCp.js").then((n) => n.t);
889
+ const { sendText } = await import("./outbound-DsXljRHh.js").then((n) => n.t);
897
890
  const result = await sendText({
898
891
  to: params.to,
899
892
  text: params.text,
@@ -915,7 +908,7 @@ async function sendQQBotText(params) {
915
908
  async function sendQQBotMedia(params) {
916
909
  await loadGatewayModule();
917
910
  const account = resolveQQBotAccount(params.cfg, params.accountId);
918
- const { sendMedia } = await import("./outbound-DHc9KJCp.js").then((n) => n.t);
911
+ const { sendMedia } = await import("./outbound-DsXljRHh.js").then((n) => n.t);
919
912
  const result = await sendMedia({
920
913
  to: params.to,
921
914
  text: params.text ?? "",
@@ -1,2 +1,2 @@
1
- import { t as qqbotPlugin } from "./channel-C_7BS_5A.js";
1
+ import { t as qqbotPlugin } from "./channel-Cx3C1vCZ.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-DHc9KJCp.js";
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";
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-C_7BS_5A.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-TzkQ0YbR.js";
6
- import { a as getPluginVersion, i as getFrameworkVersion, n as runWithRequestContext, o as initCommands, s as matchSlashCommand } from "./request-context-Dge7ttkg.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";
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";
@@ -5665,7 +5665,7 @@ function parseWavFallback(buf) {
5665
5665
  * QQBot reply path abort at ~5 minutes with "LLM request timed out",
5666
5666
  * despite the direct ollama call to the same model working. The
5667
5667
  * embedded-runner / idle-timeout layer already honors longer
5668
- * provider timeouts (see `src/agents/pi-embedded-runner/run/llm-idle-timeout.ts`),
5668
+ * provider timeouts (see `src/agents/embedded-agent-runner/run/llm-idle-timeout.ts`),
5669
5669
  * but the QQBot outbound dispatcher held an independent hardcoded
5670
5670
  * `RESPONSE_TIMEOUT = 300_000` watchdog that quietly undercut the
5671
5671
  * configured ceiling.
@@ -5690,7 +5690,7 @@ const DEFAULT_RESPONSE_TIMEOUT_MS = 3e5;
5690
5690
  /**
5691
5691
  * Upper bound to keep the watchdog inside the safe `setTimeout` range
5692
5692
  * (approximately 24.8 days). Mirrors `MAX_SAFE_TIMEOUT_MS` in
5693
- * `src/agents/pi-embedded-runner/run/llm-idle-timeout.ts`.
5693
+ * `src/agents/embedded-agent-runner/run/llm-idle-timeout.ts`.
5694
5694
  */
5695
5695
  const MAX_SAFE_TIMEOUT_MS = 2147e6;
5696
5696
  function positiveSecondsToMs(value) {
@@ -1,14 +1,11 @@
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-C_7BS_5A.js";
3
- import { d as getMessageApi, i as accountToCreds } from "./runtime-TzkQ0YbR.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";
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
7
7
  function isExecRequest(request) {
8
- return "command" in request.request;
9
- }
10
- function listDecisionActions(actions) {
11
- return actions.flatMap((action) => action.kind === "decision" && action.decision ? [action.decision] : []);
8
+ return "expiresAtMs" in request;
12
9
  }
13
10
  function resolveQQTarget(request) {
14
11
  const sessionConversation = resolveApprovalRequestSessionConversation({
@@ -74,9 +71,8 @@ const qqbotApprovalNativeRuntime = createChannelApprovalNativeRuntimeAdapter({
74
71
  presentation: {
75
72
  buildPendingPayload: ({ request, view }) => {
76
73
  const req = request;
77
- const text = isExecRequest(req) ? buildExecApprovalText(req) : buildPluginApprovalText(req, view.actions);
78
- const decisionActions = listDecisionActions(view.actions);
79
- const keyboard = decisionActions.length > 0 ? buildApprovalKeyboard(req.id, decisionActions) : void 0;
74
+ const text = isExecRequest(req) ? buildExecApprovalText(req) : buildPluginApprovalText(req);
75
+ const keyboard = buildApprovalKeyboard(req.id, view.actions.map((action) => action.decision));
80
76
  getBridgeLogger().debug?.(`[qqbot:approval-runtime] buildPendingPayload requestId=${req.id} kind=${isExecRequest(req) ? "exec" : "plugin"}`);
81
77
  return {
82
78
  text,
@@ -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-C_7BS_5A.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-TzkQ0YbR.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";
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";
@@ -1,5 +1,5 @@
1
- import { a as getQQBotDataDir, c as getQQBotMediaPath, d as isWindows, i as getHomeDir } from "./channel-C_7BS_5A.js";
2
- import { w as debugLog } from "./runtime-TzkQ0YbR.js";
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";
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";
@@ -4,10 +4,11 @@ import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
4
4
  import os from "node:os";
5
5
  import * as crypto$1 from "node:crypto";
6
6
  import crypto from "node:crypto";
7
- import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
7
+ import { fetchWithSsrFGuard, isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
8
8
  import { FsSafeError, openLocalFileSafely, readRegularFile, statRegularFileSync } from "openclaw/plugin-sdk/security-runtime";
9
9
  import * as path$1 from "node:path";
10
10
  import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
11
+ import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
11
12
  //#region extensions/qqbot/src/engine/types.ts
12
13
  /**
13
14
  * Core API layer public types.
@@ -755,7 +756,8 @@ var ChunkedMediaApi = class {
755
756
  this.logger?.info?.(`${prefix} Start: file=${displayName} size=${formatFileSize(fileSize)} type=${opts.fileType}`);
756
757
  const hashes = await computeHashes(input);
757
758
  this.logger?.debug?.(`${prefix} hashes: md5=${hashes.md5} sha1=${hashes.sha1} md5_10m=${hashes.md5_10m}`);
758
- if (this.cache) {
759
+ const canUseUploadCache = opts.fileType !== 4;
760
+ if (this.cache && canUseUploadCache) {
759
761
  const cached = this.cache.get(hashes.md5, opts.scope, opts.targetId, opts.fileType);
760
762
  if (cached) {
761
763
  this.logger?.info?.(`${prefix} cache HIT (md5=${hashes.md5.slice(0, 8)}) — skipping chunked upload`);
@@ -798,7 +800,7 @@ var ChunkedMediaApi = class {
798
800
  this.logger?.info?.(`${prefix} all parts uploaded, completing...`);
799
801
  const result = await this.callCompleteUpload(opts, upload_id);
800
802
  this.logger?.info?.(`${prefix} completed: file_uuid=${result.file_uuid} ttl=${result.ttl}s`);
801
- if (this.cache && result.file_info && result.ttl > 0) this.cache.set(hashes.md5, opts.scope, opts.targetId, opts.fileType, result.file_info, result.file_uuid, result.ttl);
803
+ if (this.cache && canUseUploadCache && result.file_info && result.ttl > 0) this.cache.set(hashes.md5, opts.scope, opts.targetId, opts.fileType, result.file_info, result.file_uuid, result.ttl);
802
804
  return result;
803
805
  } finally {
804
806
  if (input.kind === "localPath" && input.closeWhenDone) await input.opened.close().catch(() => void 0);
@@ -1186,6 +1188,82 @@ var TokenManager = class {
1186
1188
  * {@link ./media-chunked.ts}; this module currently handles only the
1187
1189
  * one-shot path.
1188
1190
  */
1191
+ const DIRECT_UPLOAD_DOWNLOAD_TIMEOUT_MS = 3e4;
1192
+ const DIRECT_UPLOAD_READ_IDLE_TIMEOUT_MS = 1e4;
1193
+ const DIRECT_UPLOAD_BODY_GRACE_TIMEOUT_MS = 3e4;
1194
+ const DIRECT_UPLOAD_MIN_DOWNLOAD_BYTES_PER_SECOND = 256 * 1024;
1195
+ const DIRECT_UPLOAD_MAX_BODY_TIMEOUT_MS = 8 * 6e4;
1196
+ function assertDirectUploadDownloadHostAllowed(hostname) {
1197
+ if (isBlockedHostnameOrIp(hostname)) throw new Error("Blocked hostname or private/internal/special-use IP address");
1198
+ }
1199
+ async function fetchDirectUploadDownload(url) {
1200
+ const controller = new AbortController();
1201
+ const timeoutError = /* @__PURE__ */ new Error("Direct-upload media URL fetch timed out");
1202
+ let timedOut = false;
1203
+ let timeout;
1204
+ const timeoutPromise = new Promise((_, reject) => {
1205
+ timeout = setTimeout(() => {
1206
+ timedOut = true;
1207
+ controller.abort(timeoutError);
1208
+ reject(timeoutError);
1209
+ }, DIRECT_UPLOAD_DOWNLOAD_TIMEOUT_MS);
1210
+ unrefTimer(timeout);
1211
+ });
1212
+ const guardedFetch = fetchWithSsrFGuard({
1213
+ url,
1214
+ maxRedirects: 0,
1215
+ signal: controller.signal
1216
+ });
1217
+ guardedFetch.then((result) => {
1218
+ if (timedOut) result.release().catch(() => void 0);
1219
+ }, () => void 0);
1220
+ try {
1221
+ return await Promise.race([guardedFetch, timeoutPromise]);
1222
+ } finally {
1223
+ if (timeout) clearTimeout(timeout);
1224
+ }
1225
+ }
1226
+ function unrefTimer(timeout) {
1227
+ if (typeof timeout === "object" && "unref" in timeout) timeout.unref();
1228
+ }
1229
+ function resolveDirectUploadBodyTimeoutMs(maxBytes) {
1230
+ const transferTimeoutMs = Math.ceil(maxBytes / DIRECT_UPLOAD_MIN_DOWNLOAD_BYTES_PER_SECOND * 1e3);
1231
+ return Math.min(DIRECT_UPLOAD_BODY_GRACE_TIMEOUT_MS + transferTimeoutMs, DIRECT_UPLOAD_MAX_BODY_TIMEOUT_MS);
1232
+ }
1233
+ async function readDirectUploadResponse(response, maxBytes) {
1234
+ const timeoutMs = resolveDirectUploadBodyTimeoutMs(maxBytes);
1235
+ const timeoutError = /* @__PURE__ */ new Error(`Direct-upload media URL body timed out after ${timeoutMs}ms`);
1236
+ let timeout;
1237
+ const timeoutPromise = new Promise((_, reject) => {
1238
+ timeout = setTimeout(() => {
1239
+ response.body?.cancel(timeoutError).catch(() => void 0);
1240
+ reject(timeoutError);
1241
+ }, timeoutMs);
1242
+ unrefTimer(timeout);
1243
+ });
1244
+ try {
1245
+ return await Promise.race([readResponseWithLimit(response, maxBytes, { chunkTimeoutMs: DIRECT_UPLOAD_READ_IDLE_TIMEOUT_MS }), timeoutPromise]);
1246
+ } finally {
1247
+ if (timeout) clearTimeout(timeout);
1248
+ }
1249
+ }
1250
+ async function downloadDirectUploadUrl(url, opts = {}) {
1251
+ let parsed;
1252
+ try {
1253
+ parsed = new URL(url);
1254
+ } catch {
1255
+ throw new Error("Direct-upload media URL must be a valid URL");
1256
+ }
1257
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error("Direct-upload media URL must use HTTP or HTTPS");
1258
+ assertDirectUploadDownloadHostAllowed(parsed.hostname);
1259
+ const { response, release } = await fetchDirectUploadDownload(parsed.toString());
1260
+ try {
1261
+ if (!response.ok) throw new Error(`Direct-upload media URL returned HTTP ${response.status}`);
1262
+ return await readDirectUploadResponse(response, opts.maxBytes ?? 20971520);
1263
+ } finally {
1264
+ await release?.();
1265
+ }
1266
+ }
1189
1267
  /**
1190
1268
  * Small-file media upload module.
1191
1269
  *
@@ -1227,9 +1305,11 @@ var MediaApi = class {
1227
1305
  let fileData = opts.fileData;
1228
1306
  if (opts.buffer) fileData = opts.buffer.toString("base64");
1229
1307
  else if (opts.localPath) fileData = (await fs$1.promises.readFile(opts.localPath)).toString("base64");
1230
- if (fileData && this.cache) {
1231
- const hash = this.cache.computeHash(fileData);
1232
- const cached = this.cache.get(hash, scope, targetId, fileType);
1308
+ else if (opts.url !== void 0) fileData = (await downloadDirectUploadUrl(opts.url)).toString("base64");
1309
+ const uploadCache = fileData !== void 0 && !(fileType === 4 && opts.fileName) ? this.cache : void 0;
1310
+ if (fileData !== void 0 && uploadCache) {
1311
+ const hash = uploadCache.computeHash(fileData);
1312
+ const cached = uploadCache.get(hash, scope, targetId, fileType);
1233
1313
  if (cached) return {
1234
1314
  file_uuid: "",
1235
1315
  file_info: cached,
@@ -1240,8 +1320,7 @@ var MediaApi = class {
1240
1320
  file_type: fileType,
1241
1321
  srv_send_msg: opts.srvSendMsg ?? false
1242
1322
  };
1243
- if (opts.url) body.url = opts.url;
1244
- else if (fileData) body.file_data = fileData;
1323
+ if (fileData !== void 0) body.file_data = fileData;
1245
1324
  if (fileType === 4 && opts.fileName) body.file_name = this.sanitize(opts.fileName);
1246
1325
  const token = await this.tokenManager.getAccessToken(creds.appId, creds.clientSecret);
1247
1326
  const path = mediaUploadPath(scope, targetId);
@@ -1249,9 +1328,9 @@ var MediaApi = class {
1249
1328
  redactBodyKeys: ["file_data"],
1250
1329
  uploadRequest: true
1251
1330
  }), UPLOAD_RETRY_POLICY, void 0, this.logger);
1252
- if (fileData && result.file_info && result.ttl > 0 && this.cache) {
1253
- const hash = this.cache.computeHash(fileData);
1254
- this.cache.set(hash, scope, targetId, fileType, result.file_info, result.file_uuid, result.ttl);
1331
+ if (fileData !== void 0 && uploadCache && result.file_info && result.ttl > 0) {
1332
+ const hash = uploadCache.computeHash(fileData);
1333
+ uploadCache.set(hash, scope, targetId, fileType, result.file_info, result.file_uuid, result.ttl);
1255
1334
  }
1256
1335
  return result;
1257
1336
  }
@@ -1900,10 +1979,25 @@ async function sendMediaInternal(ctx, opts) {
1900
1979
  */
1901
1980
  async function dispatchUpload(ctx, scope, targetId, fileType, source, creds, fileName) {
1902
1981
  switch (source.kind) {
1903
- case "url": return ctx.mediaApi.uploadMedia(scope, targetId, fileType, creds, {
1904
- url: source.url,
1905
- fileName
1906
- });
1982
+ case "url": {
1983
+ const buffer = await downloadDirectUploadUrl(source.url, { maxBytes: getMaxUploadSize(fileType) });
1984
+ if (buffer.length >= 5242880) return ctx.chunkedMediaApi.uploadChunked({
1985
+ scope,
1986
+ targetId,
1987
+ fileType,
1988
+ source: {
1989
+ kind: "buffer",
1990
+ buffer,
1991
+ fileName
1992
+ },
1993
+ creds,
1994
+ fileName
1995
+ });
1996
+ return ctx.mediaApi.uploadMedia(scope, targetId, fileType, creds, {
1997
+ buffer,
1998
+ fileName
1999
+ });
2000
+ }
1907
2001
  case "base64": return ctx.mediaApi.uploadMedia(scope, targetId, fileType, creds, {
1908
2002
  fileData: source.data,
1909
2003
  fileName
@@ -1,2 +1,2 @@
1
- import { r as setQQBotRuntime, t as getQQBotRuntime } from "./runtime-TzkQ0YbR.js";
1
+ import { r as setQQBotRuntime, t as getQQBotRuntime } from "./runtime-PZU9ND1Q.js";
2
2
  export { getQQBotRuntime, setQQBotRuntime };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/qqbot",
3
- "version": "2026.5.27",
3
+ "version": "2026.5.28-beta.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/qqbot",
9
- "version": "2026.5.27",
9
+ "version": "2026.5.28-beta.2",
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.27"
18
+ "openclaw": ">=2026.5.28-beta.2"
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.27",
3
+ "version": "2026.5.28-beta.2",
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.27"
19
+ "openclaw": ">=2026.5.28-beta.2"
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.27"
48
+ "pluginApi": ">=2026.5.28-beta.2"
49
49
  },
50
50
  "build": {
51
- "openclawVersion": "2026.5.27"
51
+ "openclawVersion": "2026.5.28-beta.2"
52
52
  },
53
53
  "release": {
54
54
  "publishToClawHub": true,