@openclaw/qqbot 2026.6.9-beta.1 → 2026.6.9

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,8 +1,8 @@
1
- import { t as qqbotPlugin } from "./channel-91Y8ZcpJ.js";
1
+ import { t as qqbotPlugin } from "./channel-jbnSeYkd.js";
2
2
  import { a as resolveQQBotAccount, i as resolveDefaultQQBotAccountId, n as applyQQBotAccountConfig, r as listQQBotAccountIds, t as DEFAULT_ACCOUNT_ID } from "./config-ZEfgeoL4.js";
3
3
  import { t as qqbotSetupPlugin } from "./channel.setup-CQ_DFfPx.js";
4
- import { t as getFrameworkCommands } from "./slash-commands-impl-DVApdSop.js";
5
- import { n as registerRemindTool, r as registerChannelTool, t as registerQQBotTools } from "./tools-2d9t-N1b.js";
6
- import { t as registerQQBotFull } from "./channel-entry-D_eHXHMg.js";
7
- import { C as getMessageReplyConfig, D as OUTBOUND_ERROR_CODES, E as DEFAULT_MEDIA_SEND_ERROR, O as setOutboundAudioPort, S as checkMessageReplyLimit, T as recordMessageReply, _ as sendVideoMsg, a as sendText, b as resolveUserFacingMediaError, 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 getMessageReplyStats, x as MESSAGE_REPLY_LIMIT } from "./outbound-DN8kX1G3.js";
4
+ import { t as getFrameworkCommands } from "./slash-commands-impl-BUH24Ycv.js";
5
+ import { n as registerRemindTool, r as registerChannelTool, t as registerQQBotTools } from "./tools-CV5Y0isN.js";
6
+ import { t as registerQQBotFull } from "./channel-entry-5YxjzTrA.js";
7
+ import { C as getMessageReplyConfig, D as OUTBOUND_ERROR_CODES, E as DEFAULT_MEDIA_SEND_ERROR, O as setOutboundAudioPort, S as checkMessageReplyLimit, T as recordMessageReply, _ as sendVideoMsg, a as sendText, b as resolveUserFacingMediaError, 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 getMessageReplyStats, x as MESSAGE_REPLY_LIMIT } from "./outbound-BQcPCo-z.js";
8
8
  export { DEFAULT_ACCOUNT_ID, DEFAULT_MEDIA_SEND_ERROR, MESSAGE_REPLY_LIMIT, OUTBOUND_ERROR_CODES, applyQQBotAccountConfig, buildMediaTarget, checkMessageReplyLimit, getFrameworkCommands, getMessageReplyConfig, getMessageReplyStats, listQQBotAccountIds, parseTarget, qqbotPlugin, qqbotSetupPlugin, recordMessageReply, registerChannelTool, registerQQBotFull, registerQQBotTools, registerRemindTool, resolveDefaultQQBotAccountId, resolveOutboundMediaPath, resolveQQBotAccount, resolveUserFacingMediaError, sendCronMessage, sendDocument, sendMedia, sendPhoto, sendProactiveMessage, sendText, sendVideoMsg, sendVoice, setOutboundAudioPort };
@@ -1,7 +1,7 @@
1
1
  import { a as resolveQQBotAccount } from "./config-ZEfgeoL4.js";
2
- import { t as getFrameworkCommands } from "./slash-commands-impl-DVApdSop.js";
3
- import { t as registerQQBotTools } from "./tools-2d9t-N1b.js";
4
- import { h as sendDocument } from "./outbound-DN8kX1G3.js";
2
+ import { t as getFrameworkCommands } from "./slash-commands-impl-BUH24Ycv.js";
3
+ import { t as registerQQBotTools } from "./tools-CV5Y0isN.js";
4
+ import { h as sendDocument } from "./outbound-BQcPCo-z.js";
5
5
  //#region extensions/qqbot/src/bridge/commands/framework-context-adapter.ts
6
6
  /**
7
7
  * Default queue snapshot used for framework-registered commands.
@@ -1,2 +1,2 @@
1
- import { t as registerQQBotFull } from "./channel-entry-D_eHXHMg.js";
1
+ import { t as registerQQBotFull } from "./channel-entry-5YxjzTrA.js";
2
2
  export { registerQQBotFull };
@@ -1,7 +1,7 @@
1
1
  import { t as asOptionalObjectRecord } from "./string-normalize-R_0cKO7Q.js";
2
2
  import { a as qqbotSetupAdapterShared, i as qqbotMeta, n as qqbotSetupWizard, o as ensurePlatformAdapter, r as qqbotConfigAdapter, s as getBridgeLogger, t as qqbotChannelConfigSchema } from "./config-schema-BFLNZ8Nf.js";
3
3
  import { a as resolveQQBotAccount, n as applyQQBotAccountConfig, r as listQQBotAccountIds, t as DEFAULT_ACCOUNT_ID$1 } from "./config-ZEfgeoL4.js";
4
- import { t as getQQBotRuntime } from "./runtime-B9UoQ5NI.js";
4
+ import { t as getQQBotRuntime } from "./runtime-D1hn91-s.js";
5
5
  import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-BYTS8-ia.js";
6
6
  import { n as normalizeTarget, s as getQQBotDataPath, t as looksLikeQQBotTarget } from "./target-parser-BdCUmxK7.js";
7
7
  import { getExecApprovalReplyMetadata } from "openclaw/plugin-sdk/approval-runtime";
@@ -370,7 +370,7 @@ function createQQBotApprovalCapability() {
370
370
  },
371
371
  load: async () => {
372
372
  ensurePlatformAdapter();
373
- return (await import("./handler-runtime-BzeYyTXb.js")).qqbotApprovalNativeRuntime;
373
+ return (await import("./handler-runtime-D0l75JR8.js")).qqbotApprovalNativeRuntime;
374
374
  }
375
375
  })
376
376
  });
@@ -1066,12 +1066,12 @@ function resolveQQBotGroupToolPolicy(params) {
1066
1066
  //#region extensions/qqbot/src/channel.ts
1067
1067
  let gatewayModulePromise;
1068
1068
  function loadGatewayModule() {
1069
- gatewayModulePromise ??= import("./gateway-CwDoxaZF.js");
1069
+ gatewayModulePromise ??= import("./gateway-DilADGTY.js");
1070
1070
  return gatewayModulePromise;
1071
1071
  }
1072
1072
  let outboundMessagingModulePromise;
1073
1073
  function loadOutboundMessagingModule() {
1074
- outboundMessagingModulePromise ??= import("./outbound-DN8kX1G3.js").then((n) => n.t);
1074
+ outboundMessagingModulePromise ??= import("./outbound-BQcPCo-z.js").then((n) => n.t);
1075
1075
  return outboundMessagingModulePromise;
1076
1076
  }
1077
1077
  function createQQBotSendReceipt(params) {
@@ -1,2 +1,2 @@
1
- import { t as qqbotPlugin } from "./channel-91Y8ZcpJ.js";
1
+ import { t as qqbotPlugin } from "./channel-jbnSeYkd.js";
2
2
  export { qqbotPlugin };
@@ -1,21 +1,22 @@
1
- import { C as getNextMsgSeq, D as downloadFile, F as StreamInputMode, I as StreamInputState, M as getMaxUploadSize, P as StreamContentType, T as openLocalFile, a as createRawInputNotifyFn, b as stopBackgroundTokenRefresh, c as getMessageApi, d as initSender, f as onMessageSent, g as sendText, h as sendMedia, i as clearTokenCache, j as getImageMimeType, k as formatFileSize, l as getPluginUserAgent, m as sendInputNotify, n as acknowledgeInteraction, o as getAccessToken, p as registerAccount, r as buildDeliveryTarget, s as getGatewayUrl, t as accountToCreds, u as initApiConfig, x as withTokenRetry, y as startBackgroundTokenRefresh } from "./sender-DIMG7jHz.js";
1
+ import { C as getNextMsgSeq, D as downloadFile, F as StreamInputMode, I as StreamInputState, M as getMaxUploadSize, P as StreamContentType, T as openLocalFile, a as createRawInputNotifyFn, b as stopBackgroundTokenRefresh, c as getMessageApi, d as initSender, f as onMessageSent, g as sendText, h as sendMedia, i as clearTokenCache, j as getImageMimeType, k as formatFileSize, l as getPluginUserAgent, m as sendInputNotify, n as acknowledgeInteraction, o as getAccessToken, p as registerAccount, r as buildDeliveryTarget, s as getGatewayUrl, t as accountToCreds, u as initApiConfig, x as withTokenRetry, y as startBackgroundTokenRefresh } from "./sender-CRZ2af7P.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 { c as setBridgeLogger, o as ensurePlatformAdapter } from "./config-schema-BFLNZ8Nf.js";
4
- import { a as toGatewayAccount, i as openQQBotSyncKeyedStore, m as parseApprovalButtonData, n as createQQBotMarkdownChunker, o as authorizeQQBotApprovalAction, r as buildQQBotStateKey } from "./channel-91Y8ZcpJ.js";
4
+ import { a as toGatewayAccount, i as openQQBotSyncKeyedStore, m as parseApprovalButtonData, n as createQQBotMarkdownChunker, o as authorizeQQBotApprovalAction, r as buildQQBotStateKey } from "./channel-jbnSeYkd.js";
5
5
  import { d as resolveAccountBase } from "./config-ZEfgeoL4.js";
6
6
  import { a as formatErrorMessage, i as formatDuration, n as debugLog, r as debugWarn, t as debugError } from "./log-SDfMMBWe.js";
7
- import { n as getQQBotRuntimeForEngine, t as getQQBotRuntime } from "./runtime-B9UoQ5NI.js";
7
+ import { n as getQQBotRuntimeForEngine, t as getQQBotRuntime } from "./runtime-D1hn91-s.js";
8
8
  import { a as getHomeDir, c as getQQBotMediaDir, d as isLocalPath, f as isWindows, i as checkSilkWasmAvailable, o as getQQBotDataDir, p as normalizePath, s as getQQBotDataPath, u as getTempDir } from "./target-parser-BdCUmxK7.js";
9
- import { a as matchSlashCommand, i as initCommands, n as getFrameworkVersion, r as getPluginVersion } from "./slash-commands-impl-DVApdSop.js";
9
+ import { a as matchSlashCommand, i as initCommands, n as getFrameworkVersion, r as getPluginVersion } from "./slash-commands-impl-BUH24Ycv.js";
10
10
  import { n as runWithRequestContext } from "./request-context-Bm7PTBD1.js";
11
- import { E as DEFAULT_MEDIA_SEND_ERROR, O as setOutboundAudioPort, _ as sendVideoMsg, b as resolveUserFacingMediaError, c as isCronReminderPayload, d as normalizeMediaTags, g as sendPhoto, h as sendDocument, l as isMediaPayload, o as decodeMediaPath, r as sendMedia$1, s as encodePayloadForCron, u as parseQQBotPayload, v as sendVoice, y as resolveTrustedOutboundMediaPath } from "./outbound-DN8kX1G3.js";
11
+ import { E as DEFAULT_MEDIA_SEND_ERROR, O as setOutboundAudioPort, _ as sendVideoMsg, b as resolveUserFacingMediaError, c as isCronReminderPayload, d as normalizeMediaTags, g as sendPhoto, h as sendDocument, l as isMediaPayload, o as decodeMediaPath, r as sendMedia$1, s as encodePayloadForCron, u as parseQQBotPayload, v as sendVoice, y as resolveTrustedOutboundMediaPath } from "./outbound-BQcPCo-z.js";
12
12
  import { asBoolean, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
13
13
  import { isImplicitSameChatApprovalAuthorization } from "openclaw/plugin-sdk/approval-auth-runtime";
14
14
  import * as fs$1 from "node:fs";
15
15
  import fs from "node:fs";
16
16
  import * as os$1 from "node:os";
17
- import crypto from "node:crypto";
17
+ import { readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
18
18
  import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
19
+ import crypto from "node:crypto";
19
20
  import { privateFileStoreSync, readRegularFileSync } from "openclaw/plugin-sdk/security-runtime";
20
21
  import * as path$1 from "node:path";
21
22
  import path from "node:path";
@@ -1802,6 +1803,7 @@ var GatewayConnection = class {
1802
1803
  * Migrated from `src/stt.ts` — uses core/utils/string-normalize instead
1803
1804
  * of broad SDK text barrels.
1804
1805
  */
1806
+ const STT_ERROR_BODY_LIMIT_BYTES = 8 * 1024;
1805
1807
  /** Resolve the STT configuration from the nested config object. */
1806
1808
  function resolveSTTConfig(cfg) {
1807
1809
  const channelStt = asOptionalObjectRecord(asOptionalObjectRecord(asOptionalObjectRecord(cfg.channels)?.qqbot)?.stt);
@@ -1855,7 +1857,7 @@ async function transcribeAudio(audioPath, cfg) {
1855
1857
  });
1856
1858
  try {
1857
1859
  if (!resp.ok) {
1858
- const detail = await resp.text().catch(() => "");
1860
+ const detail = await readResponseTextLimited(resp, STT_ERROR_BODY_LIMIT_BYTES).catch(() => "");
1859
1861
  throw new Error(`STT failed (HTTP ${resp.status}): ${detail.slice(0, 300)}`);
1860
1862
  }
1861
1863
  return normalizeOptionalString$1((await resp.json()).text) ?? null;
@@ -1,6 +1,6 @@
1
- import { c as getMessageApi, t as accountToCreds } from "./sender-DIMG7jHz.js";
1
+ import { c as getMessageApi, t as accountToCreds } from "./sender-CRZ2af7P.js";
2
2
  import { o as ensurePlatformAdapter, s as getBridgeLogger } from "./config-schema-BFLNZ8Nf.js";
3
- import { c as matchesQQBotApprovalAccount, d as buildApprovalKeyboard, f as buildExecApprovalText, h as resolveApprovalTarget, l as resolveQQBotExecApprovalConfig, p as buildPluginApprovalText, s as isQQBotExecApprovalClientEnabled, u as shouldHandleQQBotExecApprovalRequest } from "./channel-91Y8ZcpJ.js";
3
+ import { c as matchesQQBotApprovalAccount, d as buildApprovalKeyboard, f as buildExecApprovalText, h as resolveApprovalTarget, l as resolveQQBotExecApprovalConfig, p as buildPluginApprovalText, s as isQQBotExecApprovalClientEnabled, u as shouldHandleQQBotExecApprovalRequest } from "./channel-jbnSeYkd.js";
4
4
  import { a as resolveQQBotAccount } from "./config-ZEfgeoL4.js";
5
5
  import { resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
6
6
  import { createChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
@@ -1,4 +1,4 @@
1
- import { A as getFileTypeName, D as downloadFile, E as checkFileSize, L as __exportAll, M as getMaxUploadSize, N as readFileAsync, O as fileExistsAsync, S as UploadDailyLimitExceededError, g as sendText$1, h as sendMedia$1, j as getImageMimeType, k as formatFileSize, t as accountToCreds, u as initApiConfig, w as UPLOAD_PREPARE_FALLBACK_CODE } from "./sender-DIMG7jHz.js";
1
+ import { A as getFileTypeName, D as downloadFile, E as checkFileSize, L as __exportAll, M as getMaxUploadSize, N as readFileAsync, O as fileExistsAsync, S as UploadDailyLimitExceededError, g as sendText$1, h as sendMedia$1, j as getImageMimeType, k as formatFileSize, t as accountToCreds, u as initApiConfig, w as UPLOAD_PREPARE_FALLBACK_CODE } from "./sender-CRZ2af7P.js";
2
2
  import { i as normalizeOptionalString, n as normalizeLowercaseStringOrEmpty, s as sanitizeFileName } from "./string-normalize-R_0cKO7Q.js";
3
3
  import { a as formatErrorMessage, n as debugLog, r as debugWarn, t as debugError } from "./log-SDfMMBWe.js";
4
4
  import { c as getQQBotMediaDir, d as isLocalPath, m as resolveQQBotPayloadLocalFilePath, o as getQQBotDataDir, p as normalizePath$1, r as parseTarget$1 } from "./target-parser-BdCUmxK7.js";
@@ -1,4 +1,4 @@
1
- import { v as setOpenClawVersion } from "./sender-DIMG7jHz.js";
1
+ import { v as setOpenClawVersion } from "./sender-CRZ2af7P.js";
2
2
  import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
3
3
  //#region extensions/qqbot/src/bridge/runtime.ts
4
4
  const { setRuntime: _setRuntime, clearRuntime: resetQQBotRuntimeForTest, getRuntime: getQQBotRuntime } = createPluginRuntimeStore({
@@ -1,2 +1,2 @@
1
- import { r as setQQBotRuntime, t as getQQBotRuntime } from "./runtime-B9UoQ5NI.js";
1
+ import { r as setQQBotRuntime, t as getQQBotRuntime } from "./runtime-D1hn91-s.js";
2
2
  export { getQQBotRuntime, setQQBotRuntime };
@@ -2,9 +2,10 @@ import { c as getPlatformAdapter, i as normalizeOptionalString, s as sanitizeFil
2
2
  import { a as formatErrorMessage, n as debugLog, r as debugWarn, t as debugError } from "./log-SDfMMBWe.js";
3
3
  import * as fs$1 from "node:fs";
4
4
  import os from "node:os";
5
+ import { readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
6
+ import { fetchWithSsrFGuard, isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
5
7
  import * as crypto$1 from "node:crypto";
6
8
  import crypto from "node:crypto";
7
- import { fetchWithSsrFGuard, isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
8
9
  import { FsSafeError, openLocalFileSafely, readRegularFile, statRegularFileSync } from "openclaw/plugin-sdk/security-runtime";
9
10
  import * as path$1 from "node:path";
10
11
  import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
@@ -72,6 +73,13 @@ const StreamContentType = { MARKDOWN: "markdown" };
72
73
  const DEFAULT_BASE_URL = "https://api.sgroup.qq.com";
73
74
  const DEFAULT_TIMEOUT_MS = 3e4;
74
75
  const FILE_UPLOAD_TIMEOUT_MS = 12e4;
76
+ const QQBOT_API_ERROR_BODY_LIMIT_BYTES = 8 * 1024;
77
+ function resolveQqbotApiSsrfPolicy(url) {
78
+ return {
79
+ hostnameAllowlist: [new URL(url).hostname],
80
+ allowRfc2544BenchmarkRange: true
81
+ };
82
+ }
75
83
  /**
76
84
  * Stateful HTTP client for the QQ Open Platform.
77
85
  *
@@ -125,8 +133,16 @@ var ApiClient = class {
125
133
  this.logger.debug(`[qqbot:api] >>> Body: ${JSON.stringify(logBody)}`);
126
134
  }
127
135
  let res;
136
+ let release;
128
137
  try {
129
- res = await fetch(url, fetchInit);
138
+ const guarded = await fetchWithSsrFGuard({
139
+ url,
140
+ init: fetchInit,
141
+ auditContext: "qqbot-api",
142
+ policy: resolveQqbotApiSsrfPolicy(url)
143
+ });
144
+ res = guarded.response;
145
+ release = guarded.release;
130
146
  } catch (err) {
131
147
  clearTimeout(timeoutId);
132
148
  if (err instanceof Error && err.name === "AbortError") {
@@ -138,32 +154,38 @@ var ApiClient = class {
138
154
  } finally {
139
155
  clearTimeout(timeoutId);
140
156
  }
141
- const traceId = res.headers.get("x-tps-trace-id") ?? "";
142
- this.logger?.info?.(`[qqbot:api] <<< Status: ${res.status} ${res.statusText}${traceId ? ` | TraceId: ${traceId}` : ""}`);
143
- let rawBody;
144
157
  try {
145
- rawBody = await res.text();
146
- } catch (err) {
147
- throw new ApiError(`Failed to read response [${path}]: ${formatErrorMessage(err)}`, res.status, path);
148
- }
149
- this.logger?.debug?.(`[qqbot:api] <<< Body: ${rawBody}`);
150
- const isHtmlResponse = (res.headers.get("content-type") ?? "").includes("text/html") || rawBody.trimStart().startsWith("<");
151
- if (!res.ok) {
152
- if (isHtmlResponse) throw new ApiError(`${res.status === 502 || res.status === 503 || res.status === 504 ? "调用发生异常,请稍候重试" : res.status === 429 ? "请求过于频繁,已被限流" : `开放平台返回 HTTP ${res.status}`}(${path}),请稍后重试`, res.status, path);
158
+ const traceId = res.headers.get("x-tps-trace-id") ?? "";
159
+ this.logger?.info?.(`[qqbot:api] <<< Status: ${res.status} ${res.statusText}${traceId ? ` | TraceId: ${traceId}` : ""}`);
160
+ const readBody = async (limitBytes) => {
161
+ try {
162
+ return limitBytes === void 0 ? await res.text() : await readResponseTextLimited(res, limitBytes);
163
+ } catch (err) {
164
+ throw new ApiError(`Failed to read response [${path}]: ${formatErrorMessage(err)}`, res.status, path);
165
+ }
166
+ };
167
+ const rawBody = res.ok ? await readBody() : await readBody(QQBOT_API_ERROR_BODY_LIMIT_BYTES);
168
+ this.logger?.debug?.(`[qqbot:api] <<< Body: ${rawBody}`);
169
+ const isHtmlResponse = (res.headers.get("content-type") ?? "").includes("text/html") || rawBody.trimStart().startsWith("<");
170
+ if (!res.ok) {
171
+ if (isHtmlResponse) throw new ApiError(`${res.status === 502 || res.status === 503 || res.status === 504 ? "调用发生异常,请稍候重试" : res.status === 429 ? "请求过于频繁,已被限流" : `开放平台返回 HTTP ${res.status}`}(${path}),请稍后重试`, res.status, path);
172
+ try {
173
+ const error = JSON.parse(rawBody);
174
+ const bizCode = error.code ?? error.err_code;
175
+ throw new ApiError(`API Error [${path}]: ${error.message ?? rawBody}`, res.status, path, bizCode, error.message);
176
+ } catch (parseErr) {
177
+ if (parseErr instanceof ApiError) throw parseErr;
178
+ throw new ApiError(`API Error [${path}] HTTP ${res.status}: ${rawBody.slice(0, 200)}`, res.status, path);
179
+ }
180
+ }
181
+ if (isHtmlResponse) throw new ApiError(`QQ 服务端返回了非 JSON 响应(${path}),可能是临时故障,请稍后重试`, res.status, path);
153
182
  try {
154
- const error = JSON.parse(rawBody);
155
- const bizCode = error.code ?? error.err_code;
156
- throw new ApiError(`API Error [${path}]: ${error.message ?? rawBody}`, res.status, path, bizCode, error.message);
157
- } catch (parseErr) {
158
- if (parseErr instanceof ApiError) throw parseErr;
159
- throw new ApiError(`API Error [${path}] HTTP ${res.status}: ${rawBody.slice(0, 200)}`, res.status, path);
183
+ return JSON.parse(rawBody);
184
+ } catch {
185
+ throw new ApiError(`开放平台响应格式异常(${path}),请稍后重试`, res.status, path);
160
186
  }
161
- }
162
- if (isHtmlResponse) throw new ApiError(`QQ 服务端返回了非 JSON 响应(${path}),可能是临时故障,请稍后重试`, res.status, path);
163
- try {
164
- return JSON.parse(rawBody);
165
- } catch {
166
- throw new ApiError(`开放平台响应格式异常(${path}),请稍后重试`, res.status, path);
187
+ } finally {
188
+ await release?.();
167
189
  }
168
190
  }
169
191
  };
@@ -680,6 +702,7 @@ const MAX_CONCURRENT_PARTS = 10;
680
702
  const MAX_PART_FINISH_RETRY_TIMEOUT_MS = 600 * 1e3;
681
703
  /** Per-part PUT timeout (5 minutes). Matches the low-bandwidth tolerance. */
682
704
  const PART_UPLOAD_TIMEOUT_MS = 3e5;
705
+ const PART_UPLOAD_ERROR_BODY_LIMIT_BYTES = 8 * 1024;
683
706
  /**
684
707
  * Boundary used by `md5_10m` — first 10,002,432 bytes.
685
708
  *
@@ -907,7 +930,7 @@ async function putToPresignedUrl(presignedUrl, data, partIndex, totalParts, logg
907
930
  const requestId = response.headers.get("x-cos-request-id") ?? "-";
908
931
  const etag = response.headers.get("ETag") ?? "-";
909
932
  if (!response.ok) {
910
- const body = await response.text().catch(() => "");
933
+ const body = await readResponseTextLimited(response, PART_UPLOAD_ERROR_BODY_LIMIT_BYTES).catch(() => "");
911
934
  logger?.error?.(`${prefix} PUT part ${partIndex}/${totalParts}: HTTP ${response.status} ${response.statusText} (${elapsed}ms, requestId=${requestId}) body=${body.slice(0, 160)}`);
912
935
  throw new Error(`COS PUT failed: ${response.status} ${response.statusText} - ${body.slice(0, 120)}`);
913
936
  }
@@ -960,6 +983,7 @@ function sleep(ms) {
960
983
  */
961
984
  const TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken";
962
985
  const DEFAULT_TOKEN_EXPIRES_IN_SECONDS = 7200;
986
+ const QQBOT_TOKEN_RESPONSE_LIMIT_BYTES = 8 * 1024;
963
987
  /**
964
988
  * Host-scoped SSRF policy for the QQ Bot token endpoint.
965
989
  *
@@ -1102,11 +1126,6 @@ var TokenManager = class {
1102
1126
  this.refreshControllers.clear();
1103
1127
  }
1104
1128
  }
1105
- /** Check whether background refresh is running. */
1106
- isBackgroundRefreshRunning(appId) {
1107
- if (appId) return this.refreshControllers.has(appId);
1108
- return this.refreshControllers.size > 0;
1109
- }
1110
1129
  async doFetchToken(appId, clientSecret) {
1111
1130
  this.logger?.debug?.(`[qqbot:token:${appId}] >>> POST ${TOKEN_URL}`);
1112
1131
  let response;
@@ -1140,7 +1159,7 @@ var TokenManager = class {
1140
1159
  this.logger?.debug?.(`[qqbot:token:${appId}] <<< ${response.status}${traceId ? ` | TraceId: ${traceId}` : ""}`);
1141
1160
  let rawBody;
1142
1161
  try {
1143
- rawBody = await response.text();
1162
+ rawBody = await readResponseTextLimited(response, QQBOT_TOKEN_RESPONSE_LIMIT_BYTES);
1144
1163
  } catch (err) {
1145
1164
  throw new Error(`Failed to read access_token response: ${formatErrorMessage(err)}`, { cause: err });
1146
1165
  }
@@ -909,10 +909,6 @@ var SlashCommandRegistry = class {
909
909
  handler: cmd.handler
910
910
  }));
911
911
  }
912
- /** Return all pre-dispatch commands. */
913
- getPreDispatchCommands() {
914
- return this.commands;
915
- }
916
912
  /** Return all registered commands (both maps) for help listing. */
917
913
  getAllCommands() {
918
914
  const all = /* @__PURE__ */ new Map();
@@ -1,6 +1,8 @@
1
1
  import { a as resolveQQBotAccount, r as listQQBotAccountIds } from "./config-ZEfgeoL4.js";
2
2
  import { a as formatErrorMessage, n as debugLog, t as debugError } from "./log-SDfMMBWe.js";
3
3
  import { t as getRequestContext } from "./request-context-Bm7PTBD1.js";
4
+ import { readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
5
+ import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
4
6
  import { resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
5
7
  import { callGatewayTool } from "openclaw/plugin-sdk/agent-harness-runtime";
6
8
  //#region extensions/qqbot/src/engine/tools/channel-api.ts
@@ -15,6 +17,13 @@ import { callGatewayTool } from "openclaw/plugin-sdk/agent-harness-runtime";
15
17
  */
16
18
  const API_BASE = "https://api.sgroup.qq.com";
17
19
  const DEFAULT_TIMEOUT_MS = 3e4;
20
+ const CHANNEL_API_ERROR_BODY_LIMIT_BYTES = 8 * 1024;
21
+ function resolveChannelApiSsrfPolicy(url) {
22
+ return {
23
+ hostnameAllowlist: [new URL(url).hostname],
24
+ allowRfc2544BenchmarkRange: true
25
+ };
26
+ }
18
27
  /**
19
28
  * JSON Schema for AI tool parameters (used by framework registration).
20
29
  * AI Tool 参数的 JSON Schema 定义(供框架注册使用)。
@@ -124,8 +133,16 @@ async function executeChannelApi(params, options) {
124
133
  ].includes(method)) fetchOptions.body = JSON.stringify(params.body);
125
134
  debugLog(`[qqbot-channel-api] >>> ${method} ${url} (timeout: ${DEFAULT_TIMEOUT_MS}ms)`);
126
135
  let res;
136
+ let release;
127
137
  try {
128
- res = await fetch(url, fetchOptions);
138
+ const guarded = await fetchWithSsrFGuard({
139
+ url,
140
+ init: fetchOptions,
141
+ auditContext: "qqbot-channel-api",
142
+ policy: resolveChannelApiSsrfPolicy(url)
143
+ });
144
+ res = guarded.response;
145
+ release = guarded.release;
129
146
  } catch (err) {
130
147
  clearTimeout(timeoutId);
131
148
  if (err instanceof Error && err.name === "AbortError") {
@@ -143,42 +160,46 @@ async function executeChannelApi(params, options) {
143
160
  } finally {
144
161
  clearTimeout(timeoutId);
145
162
  }
146
- debugLog(`[qqbot-channel-api] <<< Status: ${res.status} ${res.statusText}`);
147
- const rawBody = await res.text();
148
- if (!rawBody || rawBody.trim() === "") {
149
- if (res.ok) return json$1({
150
- success: true,
151
- status: res.status,
152
- path: params.path
153
- });
154
- return json$1({
155
- error: `API returned ${res.status} ${res.statusText}`,
156
- status: res.status,
157
- path: params.path
158
- });
159
- }
160
- let parsed;
161
163
  try {
162
- parsed = JSON.parse(rawBody);
163
- } catch {
164
- parsed = rawBody;
165
- }
166
- if (!res.ok) {
167
- const errMsg = typeof parsed === "object" && parsed && "message" in parsed ? String(parsed.message) : `${res.status} ${res.statusText}`;
168
- debugError(`[qqbot-channel-api] Error [${method} ${params.path}]: ${errMsg}`);
164
+ debugLog(`[qqbot-channel-api] <<< Status: ${res.status} ${res.statusText}`);
165
+ const rawBody = res.ok ? await res.text() : await readResponseTextLimited(res, CHANNEL_API_ERROR_BODY_LIMIT_BYTES);
166
+ if (!rawBody || rawBody.trim() === "") {
167
+ if (res.ok) return json$1({
168
+ success: true,
169
+ status: res.status,
170
+ path: params.path
171
+ });
172
+ return json$1({
173
+ error: `API returned ${res.status} ${res.statusText}`,
174
+ status: res.status,
175
+ path: params.path
176
+ });
177
+ }
178
+ let parsed;
179
+ try {
180
+ parsed = JSON.parse(rawBody);
181
+ } catch {
182
+ parsed = rawBody;
183
+ }
184
+ if (!res.ok) {
185
+ const errMsg = typeof parsed === "object" && parsed && "message" in parsed ? String(parsed.message) : `${res.status} ${res.statusText}`;
186
+ debugError(`[qqbot-channel-api] Error [${method} ${params.path}]: ${errMsg}`);
187
+ return json$1({
188
+ error: errMsg,
189
+ status: res.status,
190
+ path: params.path,
191
+ details: parsed
192
+ });
193
+ }
169
194
  return json$1({
170
- error: errMsg,
195
+ success: true,
171
196
  status: res.status,
172
197
  path: params.path,
173
- details: parsed
198
+ data: parsed
174
199
  });
200
+ } finally {
201
+ await release?.();
175
202
  }
176
- return json$1({
177
- success: true,
178
- status: res.status,
179
- path: params.path,
180
- data: parsed
181
- });
182
203
  } catch (err) {
183
204
  return json$1({
184
205
  error: formatErrorMessage(err),
@@ -209,7 +230,7 @@ function registerChannelTool(api) {
209
230
  description: "Authenticated HTTP proxy for QQ Open Platform channel APIs. Common endpoints: list guilds GET /users/@me/guilds | list channels GET /guilds/{guild_id}/channels | get channel GET /channels/{channel_id} | create channel POST /guilds/{guild_id}/channels | list members GET /guilds/{guild_id}/members?after=0&limit=100 | get member GET /guilds/{guild_id}/members/{user_id} | list threads GET /channels/{channel_id}/threads | create thread PUT /channels/{channel_id}/threads | create announce POST /guilds/{guild_id}/announces | create schedule POST /channels/{channel_id}/schedules. See the qqbot-channel skill for full endpoint details.",
210
231
  parameters: ChannelApiSchema,
211
232
  async execute(_toolCallId, params) {
212
- const { getAccessToken } = await import("./sender-DIMG7jHz.js").then((n) => n._);
233
+ const { getAccessToken } = await import("./sender-CRZ2af7P.js").then((n) => n._);
213
234
  return executeChannelApi(params, { accessToken: await getAccessToken(account.appId, account.clientSecret) });
214
235
  }
215
236
  }, { name: "qqbot_channel_api" });
package/dist/tools-api.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as registerQQBotTools } from "./tools-2d9t-N1b.js";
1
+ import { t as registerQQBotTools } from "./tools-CV5Y0isN.js";
2
2
  export { registerQQBotTools };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/qqbot",
3
- "version": "2026.6.9-beta.1",
3
+ "version": "2026.6.9",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/qqbot",
9
- "version": "2026.6.9-beta.1",
9
+ "version": "2026.6.9",
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.6.9-beta.1"
18
+ "openclaw": ">=2026.6.9"
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.6.9-beta.1",
3
+ "version": "2026.6.9",
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.6.9-beta.1"
19
+ "openclaw": ">=2026.6.9"
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.6.9-beta.1"
48
+ "pluginApi": ">=2026.6.9"
49
49
  },
50
50
  "build": {
51
- "openclawVersion": "2026.6.9-beta.1"
51
+ "openclawVersion": "2026.6.9"
52
52
  },
53
53
  "release": {
54
54
  "publishToClawHub": true,