@openclaw/qqbot 2026.7.1-beta.2 → 2026.7.1-beta.5

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-BQM6pDlD.js";
1
+ import { t as qqbotPlugin } from "./channel-D1UztsnG.js";
2
2
  import { a as resolveQQBotAccount, i as resolveDefaultQQBotAccountId, n as applyQQBotAccountConfig, r as listQQBotAccountIds, t as DEFAULT_ACCOUNT_ID } from "./config-C1qZbh0K.js";
3
3
  import { t as qqbotSetupPlugin } from "./channel.setup-2ItDYKhz.js";
4
- import { o as getFrameworkCommands } from "./group-BmS1Qwst.js";
5
- import { A as OUTBOUND_ERROR_CODES, C as resolveUserFacingMediaError, D as getMessageReplyStats, E as getMessageReplyConfig, O as recordMessageReply, T as checkMessageReplyLimit, _ as sendVideoMsg, a as sendText, f as buildMediaTarget, g as sendPhoto, h as sendDocument, i as sendProactiveMessage, j as setOutboundAudioPort, k as DEFAULT_MEDIA_SEND_ERROR, m as resolveOutboundMediaPath, n as sendCronMessage, p as parseTarget, r as sendMedia, v as sendVoice, w as MESSAGE_REPLY_LIMIT } from "./outbound-BZHwMFUX.js";
6
- import { n as registerRemindTool, r as registerChannelTool, t as registerQQBotTools } from "./tools-C8kTMXKc.js";
7
- import { t as registerQQBotFull } from "./channel-entry-C18ypTaG.js";
4
+ import { o as getFrameworkCommands } from "./group-o0GmovSf.js";
5
+ import { A as OUTBOUND_ERROR_CODES, C as resolveUserFacingMediaError, D as getMessageReplyStats, E as getMessageReplyConfig, O as recordMessageReply, T as checkMessageReplyLimit, _ as sendVideoMsg, a as sendText, f as buildMediaTarget, g as sendPhoto, h as sendDocument, i as sendProactiveMessage, j as setOutboundAudioPort, k as DEFAULT_MEDIA_SEND_ERROR, m as resolveOutboundMediaPath, n as sendCronMessage, p as parseTarget, r as sendMedia, v as sendVoice, w as MESSAGE_REPLY_LIMIT } from "./outbound-BIrfvvFJ.js";
6
+ import { n as registerRemindTool, r as registerChannelTool, t as registerQQBotTools } from "./tools-UJJ-tLHP.js";
7
+ import { t as registerQQBotFull } from "./channel-entry-C5YdhX3Y.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,10 +1,11 @@
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-JZEf1dvB.js";
3
3
  import { a as resolveQQBotAccount, n as applyQQBotAccountConfig, r as listQQBotAccountIds, t as DEFAULT_ACCOUNT_ID$1 } from "./config-C1qZbh0K.js";
4
- import { t as getQQBotRuntime } from "./runtime-B4HWCOa-.js";
4
+ import { t as getQQBotRuntime } from "./runtime-DodcT_fQ.js";
5
5
  import { n as legacyConfigRules, r as normalizeCompatibilityConfig, t as buildQQBotStateKey } from "./state-keys-CQKlAFyo.js";
6
- import { n as normalizeTarget, t as looksLikeQQBotTarget } from "./target-parser-DagYRQkP.js";
6
+ import { n as normalizeTarget, r as parseTarget, t as looksLikeQQBotTarget } from "./target-parser-DagYRQkP.js";
7
7
  import { getExecApprovalReplyMetadata } from "openclaw/plugin-sdk/approval-runtime";
8
+ import { buildChannelOutboundSessionRoute } from "openclaw/plugin-sdk/channel-core";
8
9
  import { createMessageReceiptFromOutboundResults, defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-outbound";
9
10
  import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
10
11
  import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
@@ -12,16 +13,24 @@ import { createChannelApprovalCapability } from "openclaw/plugin-sdk/approval-de
12
13
  import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
13
14
  import { resolveApprovalRequestChannelAccountId, resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
14
15
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
16
+ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
15
17
  import { markImplicitSameChatApprovalAuthorization, resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
16
18
  import { createChannelExecApprovalProfile, isChannelExecApprovalClientEnabledFromConfig, matchesApprovalRequestFilters } from "openclaw/plugin-sdk/approval-client-runtime";
17
19
  import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
18
20
  import { resolveChannelGroupToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
19
21
  //#region extensions/qqbot/src/engine/approval/index.ts
22
+ /**
23
+ * Approval helpers — pure functions, zero framework dependencies.
24
+ *
25
+ * - Build approval message text + inline keyboard
26
+ * - Resolve delivery target from session metadata
27
+ * - Parse INTERACTION_CREATE button data
28
+ */
20
29
  function buildExecApprovalText(request) {
21
30
  const expiresIn = Math.max(0, Math.round((request.expiresAtMs - Date.now()) / 1e3));
22
31
  const lines = ["🔐 命令执行审批", ""];
23
32
  const cmd = request.request.commandPreview ?? request.request.command ?? "";
24
- if (cmd) lines.push(`\`\`\`\n${cmd.slice(0, 300)}\n\`\`\``);
33
+ if (cmd) lines.push(`\`\`\`\n${truncateUtf16Safe(cmd, 300)}\n\`\`\``);
25
34
  if (request.request.cwd) lines.push(`\u{1f4c1} \u76ee\u5f55: ${request.request.cwd}`);
26
35
  if (request.request.agentId) lines.push(`\u{1f916} Agent: ${request.request.agentId}`);
27
36
  lines.push("", `\u23f1\ufe0f \u8d85\u65f6: ${expiresIn} \u79d2`);
@@ -366,7 +375,7 @@ function createQQBotApprovalCapability() {
366
375
  },
367
376
  load: async () => {
368
377
  ensurePlatformAdapter();
369
- return (await import("./handler-runtime-rU03VA43.js")).qqbotApprovalNativeRuntime;
378
+ return (await import("./handler-runtime-zQvT6SrI.js")).qqbotApprovalNativeRuntime;
370
379
  }
371
380
  })
372
381
  });
@@ -999,8 +1008,8 @@ function resolveQQBotGroupToolPolicy(params) {
999
1008
  }
1000
1009
  //#endregion
1001
1010
  //#region extensions/qqbot/src/channel.ts
1002
- const loadGatewayModule = createLazyRuntimeModule(() => import("./gateway-CjdB3B6n.js"));
1003
- const loadOutboundMessagingModule = createLazyRuntimeModule(() => import("./outbound-BZHwMFUX.js").then((n) => n.t));
1011
+ const loadGatewayModule = createLazyRuntimeModule(() => import("./gateway-pJQppxe4.js"));
1012
+ const loadOutboundMessagingModule = createLazyRuntimeModule(() => import("./outbound-BIrfvvFJ.js").then((n) => n.t));
1004
1013
  function createQQBotSendReceipt(params) {
1005
1014
  const messageId = params.messageId?.trim();
1006
1015
  return createMessageReceiptFromOutboundResults({
@@ -1013,6 +1022,25 @@ function createQQBotSendReceipt(params) {
1013
1022
  kind: params.kind
1014
1023
  });
1015
1024
  }
1025
+ function resolveQQBotOutboundSessionRoute(params) {
1026
+ const target = parseTarget(params.target);
1027
+ const chatType = target.type === "c2c" ? "direct" : "group";
1028
+ const qualifiedTarget = `qqbot:${target.type}:${target.id}`;
1029
+ return buildChannelOutboundSessionRoute({
1030
+ cfg: params.cfg,
1031
+ agentId: params.agentId,
1032
+ channel: "qqbot",
1033
+ accountId: params.accountId,
1034
+ recipientSessionExact: true,
1035
+ peer: {
1036
+ kind: chatType,
1037
+ id: target.id
1038
+ },
1039
+ chatType,
1040
+ from: qualifiedTarget,
1041
+ to: qualifiedTarget
1042
+ });
1043
+ }
1016
1044
  async function sendQQBotText(params) {
1017
1045
  await loadGatewayModule();
1018
1046
  const account = resolveQQBotAccount(params.cfg, params.accountId);
@@ -1156,6 +1184,7 @@ const qqbotPlugin = {
1156
1184
  targetPrefixes: ["qqbot"],
1157
1185
  /** Normalize common QQ Bot target formats into the canonical qqbot:... form. */
1158
1186
  normalizeTarget,
1187
+ resolveOutboundSessionRoute: (params) => resolveQQBotOutboundSessionRoute(params),
1159
1188
  targetResolver: {
1160
1189
  /** Return true when the id looks like a QQ Bot target. */
1161
1190
  looksLikeId: looksLikeQQBotTarget,
@@ -1225,7 +1254,8 @@ const qqbotPlugin = {
1225
1254
  ...ctx.getStatus(),
1226
1255
  running: true,
1227
1256
  connected: true,
1228
- lastConnectedAt: Date.now()
1257
+ lastConnectedAt: Date.now(),
1258
+ lastError: null
1229
1259
  });
1230
1260
  persistAccountCredentialSnapshot(account);
1231
1261
  },
@@ -1235,7 +1265,8 @@ const qqbotPlugin = {
1235
1265
  ...ctx.getStatus(),
1236
1266
  running: true,
1237
1267
  connected: true,
1238
- lastConnectedAt: Date.now()
1268
+ lastConnectedAt: Date.now(),
1269
+ lastError: null
1239
1270
  });
1240
1271
  persistAccountCredentialSnapshot(account);
1241
1272
  },
@@ -1245,6 +1276,14 @@ const qqbotPlugin = {
1245
1276
  ...ctx.getStatus(),
1246
1277
  lastError: error.message
1247
1278
  });
1279
+ },
1280
+ onDisconnected: ({ reason, fatal }) => {
1281
+ log?.info(`[qqbot:${account.accountId}] Gateway disconnected${reason ? `: ${reason}` : ""}`);
1282
+ ctx.setStatus({
1283
+ ...ctx.getStatus(),
1284
+ connected: false,
1285
+ ...fatal && reason ? { lastError: reason } : {}
1286
+ });
1248
1287
  }
1249
1288
  });
1250
1289
  },
@@ -1,7 +1,7 @@
1
1
  import { a as resolveQQBotAccount } from "./config-C1qZbh0K.js";
2
- import { d as PRIVATE_CHAT_ONLY_TEXT, n as resolveGroupCommandLevelFromAccountConfig, o as getFrameworkCommands } from "./group-BmS1Qwst.js";
3
- import { h as sendDocument } from "./outbound-BZHwMFUX.js";
4
- import { t as registerQQBotTools } from "./tools-C8kTMXKc.js";
2
+ import { d as PRIVATE_CHAT_ONLY_TEXT, n as resolveGroupCommandLevelFromAccountConfig, o as getFrameworkCommands } from "./group-o0GmovSf.js";
3
+ import { h as sendDocument } from "./outbound-BIrfvvFJ.js";
4
+ import { t as registerQQBotTools } from "./tools-UJJ-tLHP.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-C18ypTaG.js";
1
+ import { t as registerQQBotFull } from "./channel-entry-C5YdhX3Y.js";
2
2
  export { registerQQBotFull };
@@ -1,2 +1,2 @@
1
- import { t as qqbotPlugin } from "./channel-BQM6pDlD.js";
1
+ import { t as qqbotPlugin } from "./channel-D1UztsnG.js";
2
2
  export { qqbotPlugin };
@@ -1,15 +1,16 @@
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-BfVLJJYp.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-CjDuU-uz.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-JZEf1dvB.js";
4
- import { a as authorizeQQBotApprovalAction, i as toGatewayAccount, n as createQQBotMarkdownChunker, p as parseApprovalButtonData, r as openQQBotSyncKeyedStore } from "./channel-BQM6pDlD.js";
4
+ import { a as authorizeQQBotApprovalAction, i as toGatewayAccount, n as createQQBotMarkdownChunker, p as parseApprovalButtonData, r as openQQBotSyncKeyedStore } from "./channel-D1UztsnG.js";
5
5
  import { d as resolveAccountBase } from "./config-C1qZbh0K.js";
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-B4HWCOa-.js";
6
+ import { a as formatErrorMessage, i as formatDuration, n as debugLog, r as debugWarn, t as debugError } from "./log-DEtcoDWe.js";
7
+ import { n as getQQBotRuntimeForEngine, t as getQQBotRuntime } from "./runtime-DodcT_fQ.js";
8
8
  import { t as buildQQBotStateKey } from "./state-keys-CQKlAFyo.js";
9
- import { a as resolveMentionPatterns, c as getPluginVersion, d as PRIVATE_CHAT_ONLY_TEXT, f as classifyCoreCommandForGroup, i as resolveGroupSettings, l as initCommands, n as resolveGroupCommandLevelFromAccountConfig, r as resolveGroupConfig, s as getFrameworkVersion, u as matchSlashCommand } from "./group-BmS1Qwst.js";
10
- import { B as normalizePath, C as resolveUserFacingMediaError, F as getQQBotMediaDir, L as getTempDir, M as checkSilkWasmAvailable, N as getHomeDir, P as getQQBotDataDir, R as isLocalPath, S as resolveWorkspaceScopedLocalRoots, _ as sendVideoMsg, b as resolveOutboundMediaLocalRoots, c as isCronReminderPayload, d as normalizeMediaTags, g as sendPhoto, h as sendDocument, j as setOutboundAudioPort, k as DEFAULT_MEDIA_SEND_ERROR, l as isMediaPayload, o as decodeMediaPath, r as sendMedia$1, s as encodePayloadForCron, u as parseQQBotPayload, v as sendVoice, x as resolveWorkspacePathCandidates, y as resolveTrustedOutboundMediaPath, z as isWindows } from "./outbound-BZHwMFUX.js";
9
+ import { a as resolveMentionPatterns, c as getPluginVersion, d as PRIVATE_CHAT_ONLY_TEXT, f as classifyCoreCommandForGroup, i as resolveGroupSettings, l as initCommands, n as resolveGroupCommandLevelFromAccountConfig, r as resolveGroupConfig, s as getFrameworkVersion, u as matchSlashCommand } from "./group-o0GmovSf.js";
10
+ import { B as normalizePath, C as resolveUserFacingMediaError, F as getQQBotMediaDir, L as getTempDir, M as checkSilkWasmAvailable, N as getHomeDir, P as getQQBotDataDir, R as isLocalPath, S as resolveWorkspaceScopedLocalRoots, _ as sendVideoMsg, b as resolveOutboundMediaLocalRoots, c as isCronReminderPayload, d as normalizeMediaTags, g as sendPhoto, h as sendDocument, j as setOutboundAudioPort, k as DEFAULT_MEDIA_SEND_ERROR, l as isMediaPayload, o as decodeMediaPath, r as sendMedia$1, s as encodePayloadForCron, u as parseQQBotPayload, v as sendVoice, x as resolveWorkspacePathCandidates, y as resolveTrustedOutboundMediaPath, z as isWindows } from "./outbound-BIrfvvFJ.js";
11
11
  import { n as runWithRequestContext } from "./request-context-Bm7PTBD1.js";
12
12
  import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
13
+ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
13
14
  import { isImplicitSameChatApprovalAuthorization } from "openclaw/plugin-sdk/approval-auth-runtime";
14
15
  import * as fs$1 from "node:fs";
15
16
  import fs from "node:fs";
@@ -459,7 +460,7 @@ async function trySlashCommand(msg, ctx) {
459
460
  const contentLower = content.toLowerCase();
460
461
  if (URGENT_COMMANDS.some((cmd) => contentLower === cmd.toLowerCase() || contentLower.startsWith(cmd.toLowerCase() + " "))) {
461
462
  if (isGroup && !commandAuthorized) return "enqueue";
462
- log?.info(`Urgent command detected: ${content.slice(0, 20)}`);
463
+ log?.info(`Urgent command detected: ${truncateUtf16Safe(content, 20)}`);
463
464
  return "urgent";
464
465
  }
465
466
  const receivedAt = Date.now();
@@ -1155,7 +1156,7 @@ function createMessageQueue(ctx) {
1155
1156
  for (const m of batch) if (isSlashCommand(m)) commands.push(m);
1156
1157
  else normal.push(m);
1157
1158
  for (const cmd of commands) {
1158
- log?.debug?.(`Processing command independently for ${peerId}: ${(cmd.content ?? "").trim().slice(0, 50)}`);
1159
+ log?.debug?.(`Processing command independently for ${peerId}: ${truncateUtf16Safe((cmd.content ?? "").trim(), 50)}`);
1159
1160
  await processOne(cmd, peerId, "Command processor");
1160
1161
  }
1161
1162
  if (normal.length > 0) {
@@ -1488,6 +1489,10 @@ var GatewayConnection = class {
1488
1489
  const { account: _account, log } = this.ctx;
1489
1490
  if (this.isAborted || this.reconnect.isExhausted()) {
1490
1491
  log?.error(`Max reconnect attempts reached or aborted`);
1492
+ if (!this.isAborted) this.ctx.onDisconnected?.({
1493
+ reason: "reconnect attempts exhausted",
1494
+ fatal: true
1495
+ });
1491
1496
  return;
1492
1497
  }
1493
1498
  if (this.reconnectTimer) {
@@ -1579,11 +1584,20 @@ var GatewayConnection = class {
1579
1584
  }
1580
1585
  case GatewayOp.HEARTBEAT_ACK: break;
1581
1586
  case GatewayOp.RECONNECT:
1587
+ this.ctx.onDisconnected?.({
1588
+ reason: "server requested reconnect",
1589
+ fatal: false
1590
+ });
1582
1591
  this.cleanup();
1583
1592
  this.scheduleReconnect();
1584
1593
  break;
1585
- case GatewayOp.INVALID_SESSION:
1586
- if (!d) {
1594
+ case GatewayOp.INVALID_SESSION: {
1595
+ const canResume = d;
1596
+ this.ctx.onDisconnected?.({
1597
+ reason: canResume ? "session resume rejected" : "session invalidated",
1598
+ fatal: false
1599
+ });
1600
+ if (!canResume) {
1587
1601
  this.sessionId = null;
1588
1602
  this.lastSeq = null;
1589
1603
  clearSession(account.accountId);
@@ -1592,6 +1606,7 @@ var GatewayConnection = class {
1592
1606
  this.cleanup();
1593
1607
  this.scheduleReconnect(3e3);
1594
1608
  break;
1609
+ }
1595
1610
  }
1596
1611
  } catch (err) {
1597
1612
  log?.error(`Message parse error: ${err instanceof Error ? err.message : String(err)}`);
@@ -1599,6 +1614,7 @@ var GatewayConnection = class {
1599
1614
  });
1600
1615
  ws.on("close", (code, reason) => {
1601
1616
  log?.info(`WebSocket closed: ${code} ${reason.toString()}`);
1617
+ if (this.currentWs !== ws) return;
1602
1618
  this.isConnecting = false;
1603
1619
  this.handleClose(code);
1604
1620
  });
@@ -1650,6 +1666,10 @@ var GatewayConnection = class {
1650
1666
  }
1651
1667
  if (action.refreshToken) this.shouldRefreshToken = true;
1652
1668
  this.cleanup();
1669
+ if (!this.isAborted) this.ctx.onDisconnected?.({
1670
+ reason: action.reason,
1671
+ fatal: action.fatal
1672
+ });
1653
1673
  if (action.fatal) return;
1654
1674
  if (action.shouldReconnect) this.scheduleReconnect(action.reconnectDelay);
1655
1675
  }
@@ -1717,7 +1737,7 @@ async function transcribeAudio(audioPath, cfg) {
1717
1737
  try {
1718
1738
  if (!resp.ok) {
1719
1739
  const detail = await readResponseTextLimited(resp, STT_ERROR_BODY_LIMIT_BYTES).catch(() => "");
1720
- throw new Error(`STT failed (HTTP ${resp.status}): ${detail.slice(0, 300)}`);
1740
+ throw new Error(`STT failed (HTTP ${resp.status}): ${truncateUtf16Safe(detail, 300)}`);
1721
1741
  }
1722
1742
  return normalizeOptionalString$1((await readProviderJsonResponse(resp, "qqbot.stt")).text) ?? null;
1723
1743
  } finally {
@@ -1925,7 +1945,7 @@ async function processVoiceAttachment(localPath, audioPathInput, att, asrReferTe
1925
1945
  try {
1926
1946
  const transcript = await transcribeAudio(audioPath, cfg);
1927
1947
  if (transcript) {
1928
- log?.debug?.(`STT transcript: ${transcript.slice(0, 100)}...`);
1948
+ log?.debug?.(`STT transcript: ${truncateUtf16Safe(transcript, 100)}...`);
1929
1949
  return {
1930
1950
  localPath,
1931
1951
  type: "voice",
@@ -2671,7 +2691,7 @@ async function resolveQuote(event, deps) {
2671
2691
  cfg: account.config,
2672
2692
  log
2673
2693
  }, attachmentProcessor);
2674
- log?.debug?.(`Quote detected via msg_elements[0] (cache miss): id=${event.refMsgIdx}, content="${(refBody ?? "").slice(0, 80)}..."`);
2694
+ log?.debug?.(`Quote detected via msg_elements[0] (cache miss): id=${event.refMsgIdx}, content="${truncateUtf16Safe(refBody ?? "", 80)}..."`);
2675
2695
  return {
2676
2696
  id: event.refMsgIdx,
2677
2697
  body: refBody || void 0,
@@ -3297,6 +3317,13 @@ function hasQQBotImageSize(markdownImage) {
3297
3317
  }
3298
3318
  //#endregion
3299
3319
  //#region extensions/qqbot/src/engine/messaging/outbound-deliver.ts
3320
+ /**
3321
+ * Outbound delivery helpers — core/ version.
3322
+ *
3323
+ * Uses the unified `sender.ts` business function layer for all text and
3324
+ * image sending. Media sends (photo/voice/video/file) are injected via
3325
+ * `DeliverDeps.mediaSender`.
3326
+ */
3300
3327
  /** Maximum text length for a single QQ Bot message. */
3301
3328
  const TEXT_CHUNK_LIMIT = 5e3;
3302
3329
  function resolveMediaTargetContext(event, actx) {
@@ -3368,7 +3395,7 @@ async function sendTextChunks(text, event, actx, sendWithRetry, consumeQuoteRef,
3368
3395
  consumeQuoteRef,
3369
3396
  allowDm: true,
3370
3397
  log,
3371
- onSuccess: (chunk) => `Sent text chunk (${chunk.length}/${text.length} chars): ${chunk.slice(0, 50)}...`,
3398
+ onSuccess: (chunk) => `Sent text chunk (${chunk.length}/${text.length} chars): ${truncateUtf16Safe(chunk, 50)}...`,
3372
3399
  onError: (err) => `Failed to send text chunk: ${formatErrorMessage(err)}`
3373
3400
  });
3374
3401
  }
@@ -3385,7 +3412,7 @@ async function sendTextOnlyReply(text, event, actx, sendWithRetry, consumeQuoteR
3385
3412
  allowDm: true,
3386
3413
  forcePlainText: true,
3387
3414
  log,
3388
- onSuccess: (chunk) => `Sent text-only chunk (${chunk.length}/${safeText.length} chars): ${chunk.slice(0, 50)}...`,
3415
+ onSuccess: (chunk) => `Sent text-only chunk (${chunk.length}/${safeText.length} chars): ${truncateUtf16Safe(chunk, 50)}...`,
3389
3416
  onError: (err) => `Failed to send text-only chunk: ${formatErrorMessage(err)}`
3390
3417
  });
3391
3418
  }
@@ -3593,7 +3620,7 @@ async function sendPlainReply(payload, replyText, event, actx, sendWithRetry, co
3593
3620
  if (isRemoteHttpUrl || isDataUrl) {
3594
3621
  if (!collectedImageUrls.includes(url)) {
3595
3622
  collectedImageUrls.push(url);
3596
- log?.debug?.(`Collected ${isDataUrl ? "Base64" : "media URL"}: ${isDataUrl ? `(length: ${url.length})` : url.slice(0, 80) + "..."}`);
3623
+ log?.debug?.(`Collected ${isDataUrl ? "Base64" : "media URL"}: ${isDataUrl ? `(length: ${url.length})` : truncateUtf16Safe(url, 80) + "..."}`);
3597
3624
  }
3598
3625
  return true;
3599
3626
  }
@@ -3614,7 +3641,7 @@ async function sendPlainReply(payload, replyText, event, actx, sendWithRetry, co
3614
3641
  if (url && !collectedImageUrls.includes(url)) {
3615
3642
  if (isHttpUrl(url)) {
3616
3643
  collectedImageUrls.push(url);
3617
- log?.debug?.(`Extracted HTTP image from markdown: ${url.slice(0, 80)}...`);
3644
+ log?.debug?.(`Extracted HTTP image from markdown: ${truncateUtf16Safe(url, 80)}...`);
3618
3645
  } else if (isLocalPath(url)) {
3619
3646
  if (!localMediaToSend.includes(url)) {
3620
3647
  localMediaToSend.push(url);
@@ -3628,7 +3655,7 @@ async function sendPlainReply(payload, replyText, event, actx, sendWithRetry, co
3628
3655
  const url = m[1];
3629
3656
  if (url && !collectedImageUrls.includes(url)) {
3630
3657
  collectedImageUrls.push(url);
3631
- log?.debug?.(`Extracted bare image URL: ${url.slice(0, 80)}...`);
3658
+ log?.debug?.(`Extracted bare image URL: ${truncateUtf16Safe(url, 80)}...`);
3632
3659
  }
3633
3660
  }
3634
3661
  const useMarkdown = account.markdownSupport;
@@ -3676,7 +3703,7 @@ async function sendPlainReply(payload, replyText, event, actx, sendWithRetry, co
3676
3703
  ...actx.mediaLocalRoots ? { mediaLocalRoots: actx.mediaLocalRoots } : {},
3677
3704
  ...actx.mediaReadFile ? { mediaReadFile: actx.mediaReadFile } : {},
3678
3705
  log,
3679
- onSuccess: (mediaUrl) => `Forwarded tool media: ${mediaUrl.slice(0, 80)}...`,
3706
+ onSuccess: (mediaUrl) => `Forwarded tool media: ${truncateUtf16Safe(mediaUrl, 80)}...`,
3680
3707
  onResultError: (_mediaUrl, error) => `Tool media forward error: ${error}`,
3681
3708
  onThrownError: (_mediaUrl, error) => `Tool media forward failed: ${error}`
3682
3709
  });
@@ -3716,7 +3743,7 @@ async function sendMarkdownReply(textWithoutImages, imageUrls, mdMatches, bareUr
3716
3743
  for (const url of httpImageUrls) if (!existingMdUrls.has(url)) try {
3717
3744
  const size = await getImageSize(url);
3718
3745
  imagesToAppend.push(formatQQBotMarkdownImage(url, size));
3719
- log?.debug?.(`Formatted HTTP image: ${size ? `${size.width}x${size.height}` : "default size"} - ${url.slice(0, 60)}...`);
3746
+ log?.debug?.(`Formatted HTTP image: ${size ? `${size.width}x${size.height}` : "default size"} - ${truncateUtf16Safe(url, 60)}...`);
3720
3747
  } catch (err) {
3721
3748
  log?.debug?.(`Failed to get image size, using default: ${formatErrorMessage(err)}`);
3722
3749
  imagesToAppend.push(formatQQBotMarkdownImage(url, null));
@@ -3728,7 +3755,7 @@ async function sendMarkdownReply(textWithoutImages, imageUrls, mdMatches, bareUr
3728
3755
  if (isHttpUrl(imgUrl) && !hasQQBotImageSize(fullMatch)) try {
3729
3756
  const size = await getImageSize(imgUrl);
3730
3757
  result = result.replace(fullMatch, formatQQBotMarkdownImage(imgUrl, size));
3731
- log?.debug?.(`Updated image with size: ${size ? `${size.width}x${size.height}` : "default"} - ${imgUrl.slice(0, 60)}...`);
3758
+ log?.debug?.(`Updated image with size: ${size ? `${size.width}x${size.height}` : "default"} - ${truncateUtf16Safe(imgUrl, 60)}...`);
3732
3759
  } catch (err) {
3733
3760
  log?.debug?.(`Failed to get image size for existing md, using default: ${formatErrorMessage(err)}`);
3734
3761
  result = result.replace(fullMatch, formatQQBotMarkdownImage(imgUrl, null));
@@ -3764,7 +3791,7 @@ async function sendPlainTextReply(textWithoutImages, imageUrls, mdMatches, bareU
3764
3791
  imageUrl,
3765
3792
  mediaSender: deps.mediaSender,
3766
3793
  log,
3767
- onSuccess: (nextImageUrl) => `Sent image via sendPhoto: ${nextImageUrl.slice(0, 80)}...`,
3794
+ onSuccess: (nextImageUrl) => `Sent image via sendPhoto: ${truncateUtf16Safe(nextImageUrl, 80)}...`,
3768
3795
  onError: (error) => `Failed to send image: ${error}`
3769
3796
  });
3770
3797
  if (result.trim()) await sendTextChunksWithRetry({
@@ -3905,7 +3932,7 @@ function resolveStructuredPayloadPath(ctx, payload, mediaType) {
3905
3932
  };
3906
3933
  }
3907
3934
  function sanitizeForLog(value, maxLen = 200) {
3908
- return value.replace(/[\r\n\t]/g, " ").replaceAll("\0", " ").slice(0, maxLen);
3935
+ return truncateUtf16Safe(value.replace(/[\r\n\t]/g, " ").replaceAll("\0", " "), maxLen);
3909
3936
  }
3910
3937
  function describeMediaTargetForLog(pathValue, isHttpUrl) {
3911
3938
  if (!isHttpUrl) return "<local-file>";
@@ -4053,7 +4080,7 @@ async function sendTextAsVoiceReply(ctx, text, deps) {
4053
4080
  log?.error(`Voice missing text`);
4054
4081
  return false;
4055
4082
  }
4056
- log?.debug?.(`TTS: "${ttsText.slice(0, 50)}..."`);
4083
+ log?.debug?.(`TTS: "${truncateUtf16Safe(ttsText, 50)}..."`);
4057
4084
  const ttsResult = await deps.tts.textToSpeech({
4058
4085
  text: ttsText,
4059
4086
  cfg,
@@ -4212,6 +4239,12 @@ async function handleFilePayload(ctx, payload) {
4212
4239
  }
4213
4240
  //#endregion
4214
4241
  //#region extensions/qqbot/src/engine/messaging/streaming-media-send.ts
4242
+ /**
4243
+ * 富媒体标签解析与发送队列
4244
+ *
4245
+ * 提供媒体标签(qqimg / qqvoice / qqvideo / qqfile / qqmedia)的检测、
4246
+ * 拆分、路径编码修复,以及统一的发送队列执行器。
4247
+ */
4215
4248
  function formatStreamSendErr(e) {
4216
4249
  return e instanceof Error ? e.message : String(e);
4217
4250
  }
@@ -4358,7 +4391,7 @@ async function executeSendQueue(queue, ctx, options = {}) {
4358
4391
  else log?.info(`${prefix} executeSendQueue: no onSendText handler, skipping text`);
4359
4392
  continue;
4360
4393
  }
4361
- log?.info(`${prefix} executeSendQueue: sending ${item.type}: ${item.content.slice(0, 80)}...`);
4394
+ log?.info(`${prefix} executeSendQueue: sending ${item.type}: ${truncateUtf16Safe(item.content, 80)}...`);
4362
4395
  if (item.type === "image") {
4363
4396
  const result = await sendPhoto(mediaTarget, item.content);
4364
4397
  if (result.error) {
@@ -4565,11 +4598,11 @@ const THROTTLE_CONSTANTS = {
4565
4598
  BATCH_AFTER_GAP_MS: 300
4566
4599
  };
4567
4600
  /** 终态集合 */
4568
- const TERMINAL_PHASES = new Set(["completed", "aborted"]);
4601
+ const TERMINAL_PHASES = /* @__PURE__ */ new Set(["completed", "aborted"]);
4569
4602
  /** 允许的状态转换 */
4570
4603
  const PHASE_TRANSITIONS = {
4571
- idle: new Set(["streaming", "aborted"]),
4572
- streaming: new Set([
4604
+ idle: /* @__PURE__ */ new Set(["streaming", "aborted"]),
4605
+ streaming: /* @__PURE__ */ new Set([
4573
4606
  "idle",
4574
4607
  "completed",
4575
4608
  "aborted"
@@ -4852,7 +4885,7 @@ var StreamingController = class {
4852
4885
  */
4853
4886
  async onDeliver(payload) {
4854
4887
  const rawLen = payload.text?.length ?? 0;
4855
- const preview = (payload.text ?? "").slice(0, 60).replace(/\n/g, "\\n");
4888
+ const preview = truncateUtf16Safe(payload.text ?? "", 60).replace(/\n/g, "\\n");
4856
4889
  this.logDebug(`onDeliver: rawLen=${rawLen}, phase=${this.phase}, streamMsgId=${this.streamMsgId}, sentIndex=${this.sentIndex}, sentChunks=${this.sentStreamChunkCount}, firstCB=${this.firstCallbackSource}, preview="${preview}"`);
4857
4890
  if (this.isTerminalPhase) {
4858
4891
  this.logDebug(`onDeliver: skipped (terminal phase)`);
@@ -5000,7 +5033,7 @@ var StreamingController = class {
5000
5033
  if (this.isTerminalPhase) return;
5001
5034
  const found = findFirstClosedMediaTag(normalizedFull.slice(this.sentIndex));
5002
5035
  if (!found) break;
5003
- this.logInfo(`processMediaTags: found <${found.tagName}> at offset ${this.sentIndex}, textBefore="${found.textBefore.slice(0, 40)}"`);
5036
+ this.logInfo(`processMediaTags: found <${found.tagName}> at offset ${this.sentIndex}, textBefore="${truncateUtf16Safe(found.textBefore, 40)}"`);
5004
5037
  const textBeforeEndInFull = this.sentIndex + found.textBefore.length;
5005
5038
  await this.endCurrentStreamIfNeeded("processMediaTags:closedTag", textBeforeEndInFull);
5006
5039
  if (this.isTerminalPhase) return;
@@ -5009,7 +5042,7 @@ var StreamingController = class {
5009
5042
  type: found.itemType,
5010
5043
  content: found.mediaPath
5011
5044
  };
5012
- this.logDebug(`processMediaTags: sending ${found.itemType}: ${found.mediaPath.slice(0, 80)}`);
5045
+ this.logDebug(`processMediaTags: sending ${found.itemType}: ${truncateUtf16Safe(found.mediaPath, 80)}`);
5013
5046
  await sendMediaQueue([item], this.deps.mediaContext);
5014
5047
  this.sentMediaCount++;
5015
5048
  this.sentStreamChunkCount++;
@@ -5334,7 +5367,7 @@ function isAudioFile(filePath, mimeType) {
5334
5367
  ".pcm"
5335
5368
  ].includes(ext);
5336
5369
  }
5337
- const QQ_NATIVE_VOICE_MIMES = new Set([
5370
+ const QQ_NATIVE_VOICE_MIMES = /* @__PURE__ */ new Set([
5338
5371
  "audio/silk",
5339
5372
  "audio/amr",
5340
5373
  "audio/wav",
@@ -5343,7 +5376,7 @@ const QQ_NATIVE_VOICE_MIMES = new Set([
5343
5376
  "audio/mpeg",
5344
5377
  "audio/mp3"
5345
5378
  ]);
5346
- const QQ_NATIVE_VOICE_EXTS = new Set([
5379
+ const QQ_NATIVE_VOICE_EXTS = /* @__PURE__ */ new Set([
5347
5380
  ".silk",
5348
5381
  ".slk",
5349
5382
  ".amr",
@@ -5807,7 +5840,7 @@ async function dispatchOutbound(inbound, deps) {
5807
5840
  resultError: "Tool fallback error",
5808
5841
  thrownError: "Tool fallback failed"
5809
5842
  });
5810
- if (toolTexts.length > 0) await sendErrorMessage(toolTexts.slice(-3).join("\n---\n").slice(0, 2e3));
5843
+ if (toolTexts.length > 0) await sendErrorMessage(truncateUtf16Safe(toolTexts.slice(-3).join("\n---\n"), 2e3));
5811
5844
  };
5812
5845
  const hasPendingToolFallbackPayload = () => toolTexts.length > 0 || toolMediaUrls.length > 0;
5813
5846
  const flushPendingToolDeliveriesOnce = async () => {
@@ -6308,7 +6341,7 @@ async function startGateway$1(ctx) {
6308
6341
  initApiConfig(account.appId, { markdownSupport: account.markdownSupport });
6309
6342
  log?.debug?.(`API config: markdownSupport=${account.markdownSupport}`);
6310
6343
  onMessageSent(account.appId, (refIdx, meta) => {
6311
- log?.info(`onMessageSent called: refIdx=${refIdx}, mediaType=${meta.mediaType}, ttsText=${meta.ttsText?.slice(0, 30)}`);
6344
+ log?.info(`onMessageSent called: refIdx=${refIdx}, mediaType=${meta.mediaType}, ttsText=${meta.ttsText === void 0 ? void 0 : truncateUtf16Safe(meta.ttsText, 30)}`);
6312
6345
  const attachments = [];
6313
6346
  if (meta.mediaType) {
6314
6347
  const localPath = meta.mediaLocalPath;
@@ -6455,6 +6488,7 @@ async function startGateway$1(ctx) {
6455
6488
  onReady: ctx.onReady,
6456
6489
  onResumed: ctx.onResumed,
6457
6490
  onError: ctx.onError,
6491
+ onDisconnected: ctx.onDisconnected,
6458
6492
  onInteraction: handleInteraction,
6459
6493
  handleMessage
6460
6494
  }).start();
@@ -6770,6 +6804,7 @@ async function startGateway(ctx) {
6770
6804
  onReady: ctx.onReady,
6771
6805
  onResumed: ctx.onResumed,
6772
6806
  onError: ctx.onError,
6807
+ onDisconnected: ctx.onDisconnected,
6773
6808
  log: accountLogger,
6774
6809
  runtime,
6775
6810
  adapters: createEngineAdapters()
@@ -1,10 +1,11 @@
1
1
  import { t as asOptionalObjectRecord } from "./string-normalize-R_0cKO7Q.js";
2
2
  import { d as resolveAccountBase } from "./config-C1qZbh0K.js";
3
- import { n as debugLog } from "./log-SDfMMBWe.js";
4
- import { I as getQQBotMediaPath, N as getHomeDir, P as getQQBotDataDir, z as isWindows } from "./outbound-BZHwMFUX.js";
3
+ import { n as debugLog } from "./log-DEtcoDWe.js";
4
+ import { I as getQQBotMediaPath, N as getHomeDir, P as getQQBotDataDir, z as isWindows } from "./outbound-BIrfvvFJ.js";
5
5
  import { asBoolean, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
6
6
  import fs from "node:fs";
7
7
  import path from "node:path";
8
+ import { formatByteSize } from "openclaw/plugin-sdk/number-runtime";
8
9
  import { clearRuntimeConfigSnapshot } from "openclaw/plugin-sdk/runtime-config-snapshot";
9
10
  import { loadJsonFile } from "openclaw/plugin-sdk/json-store";
10
11
  //#region extensions/qqbot/src/engine/commands/builtin/state.ts
@@ -321,10 +322,12 @@ function scanDirectoryFiles(dirPath) {
321
322
  return files;
322
323
  }
323
324
  function formatBytes(bytes) {
324
- if (bytes < 1024) return `${bytes} B`;
325
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
326
- if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
327
- return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
325
+ return formatByteSize(bytes, {
326
+ style: "legacy-binary",
327
+ maxUnit: "giga",
328
+ separator: " ",
329
+ fractionDigits: (_value, unit) => unit === "byte" ? null : 1
330
+ });
328
331
  }
329
332
  function removeEmptyDirs(dirPath) {
330
333
  if (!fs.existsSync(dirPath)) return;
@@ -549,7 +552,7 @@ function collectCandidateLogDirs() {
549
552
  pushDir(path.join(homeDir, name));
550
553
  pushDir(path.join(homeDir, name, "logs"));
551
554
  }
552
- const searchRoots = new Set([
555
+ const searchRoots = /* @__PURE__ */ new Set([
553
556
  homeDir,
554
557
  process.cwd(),
555
558
  path.dirname(process.cwd())
@@ -880,14 +883,14 @@ function registerBuiltinSlashCommands(registry) {
880
883
  //#endregion
881
884
  //#region extensions/qqbot/src/engine/commands/command-visibility.ts
882
885
  const PRIVATE_CHAT_ONLY_TEXT = "该命令仅限私聊使用,请在私聊中发送。";
883
- const GROUP_VISIBLE_CORE_COMMANDS = new Set([
886
+ const GROUP_VISIBLE_CORE_COMMANDS = /* @__PURE__ */ new Set([
884
887
  "help",
885
888
  "btw",
886
889
  "side",
887
890
  "stop"
888
891
  ]);
889
- const STRICT_CORE_COMMANDS = new Set(["new", "reset"]);
890
- const GROUP_HIDDEN_CORE_COMMANDS = new Set([
892
+ const STRICT_CORE_COMMANDS = /* @__PURE__ */ new Set(["new", "reset"]);
893
+ const GROUP_HIDDEN_CORE_COMMANDS = /* @__PURE__ */ new Set([
891
894
  "goal",
892
895
  "usage",
893
896
  "activation",
@@ -904,7 +907,7 @@ const GROUP_HIDDEN_CORE_COMMANDS = new Set([
904
907
  "reason",
905
908
  "queue"
906
909
  ]);
907
- const PRIVATE_ONLY_CORE_COMMANDS = new Set([
910
+ const PRIVATE_ONLY_CORE_COMMANDS = /* @__PURE__ */ new Set([
908
911
  "commands",
909
912
  "tools",
910
913
  "skill",
@@ -1,6 +1,6 @@
1
- import { c as getMessageApi, t as accountToCreds } from "./sender-BfVLJJYp.js";
1
+ import { c as getMessageApi, t as accountToCreds } from "./sender-CjDuU-uz.js";
2
2
  import { o as ensurePlatformAdapter, s as getBridgeLogger } from "./config-schema-JZEf1dvB.js";
3
- import { c as resolveQQBotExecApprovalConfig, d as buildExecApprovalText, f as buildPluginApprovalText, l as shouldHandleQQBotExecApprovalRequest, m as resolveApprovalTarget, o as isQQBotExecApprovalClientEnabled, s as matchesQQBotApprovalAccount, u as buildApprovalKeyboard } from "./channel-BQM6pDlD.js";
3
+ import { c as resolveQQBotExecApprovalConfig, d as buildExecApprovalText, f as buildPluginApprovalText, l as shouldHandleQQBotExecApprovalRequest, m as resolveApprovalTarget, o as isQQBotExecApprovalClientEnabled, s as matchesQQBotApprovalAccount, u as buildApprovalKeyboard } from "./channel-D1UztsnG.js";
4
4
  import { a as resolveQQBotAccount } from "./config-C1qZbh0K.js";
5
5
  import { resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
6
6
  import { createChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
@@ -1,3 +1,4 @@
1
+ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
1
2
  //#region extensions/qqbot/src/engine/utils/format.ts
2
3
  /**
3
4
  * General formatting and string utilities.
@@ -22,7 +23,7 @@ function formatErrorMessage(err) {
22
23
  if (err instanceof Error) {
23
24
  let formatted = err.message || err.name || "Error";
24
25
  let cause = err.cause;
25
- const seen = new Set([err]);
26
+ const seen = /* @__PURE__ */ new Set([err]);
26
27
  while (cause && !seen.has(cause)) {
27
28
  seen.add(cause);
28
29
  if (cause instanceof Error) {
@@ -59,8 +60,6 @@ function formatDuration(durationMs) {
59
60
  *
60
61
  * Only outputs when the QQBOT_DEBUG environment variable is set,
61
62
  * preventing user message content from leaking in production logs.
62
- *
63
- * Self-contained within engine/ — no framework SDK dependency.
64
63
  */
65
64
  function isQqbotDebugEnabled() {
66
65
  const value = process.env.QQBOT_DEBUG;
@@ -86,7 +85,7 @@ function sanitizeDebugLogValue(value) {
86
85
  }
87
86
  const sanitized = text.replace(/\p{Cc}/gu, " ").replace(/\s+/g, " ").trim();
88
87
  if (sanitized.length <= MAX_LOG_VALUE_CHARS) return sanitized;
89
- return `${sanitized.slice(0, MAX_LOG_VALUE_CHARS)}...`;
88
+ return `${truncateUtf16Safe(sanitized, MAX_LOG_VALUE_CHARS)}...`;
90
89
  }
91
90
  function formatDebugLogArgs(args) {
92
91
  return args.map(sanitizeDebugLogValue).join(" ");
@@ -1,7 +1,8 @@
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-BfVLJJYp.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-CjDuU-uz.js";
2
2
  import { c as getPlatformAdapter, i as normalizeOptionalString, n as normalizeLowercaseStringOrEmpty, s as sanitizeFileName } from "./string-normalize-R_0cKO7Q.js";
3
- import { a as formatErrorMessage, n as debugLog, r as debugWarn, t as debugError } from "./log-SDfMMBWe.js";
3
+ import { a as formatErrorMessage, n as debugLog, r as debugWarn, t as debugError } from "./log-DEtcoDWe.js";
4
4
  import { r as parseTarget$1 } from "./target-parser-DagYRQkP.js";
5
+ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
5
6
  import * as fs$2 from "node:fs";
6
7
  import * as os$1 from "node:os";
7
8
  import { randomUUID } from "node:crypto";
@@ -127,7 +128,7 @@ async function checkSilkWasmAvailable() {
127
128
  if (silkWasmAvailable !== null) return silkWasmAvailable;
128
129
  try {
129
130
  const { isSilk } = await import("silk-wasm");
130
- isSilk(new Uint8Array(0));
131
+ isSilk(/* @__PURE__ */ new Uint8Array(0));
131
132
  silkWasmAvailable = true;
132
133
  debugLog("[platform] silk-wasm: available");
133
134
  } catch (err) {
@@ -179,7 +180,7 @@ function resolveQQBotLocalMediaPath(p) {
179
180
  const mediaRoot = getQQBotMediaPath();
180
181
  const dataRoot = getQQBotDataPath();
181
182
  const candidateRoots = [
182
- ...Array.from(new Set([path$1.join(osHomeDir, ".openclaw", "workspace", "qqbot"), path$1.join(openclawHomeDir, ".openclaw", "workspace", "qqbot")])).map((from) => ({
183
+ ...Array.from(/* @__PURE__ */ new Set([path$1.join(osHomeDir, ".openclaw", "workspace", "qqbot"), path$1.join(openclawHomeDir, ".openclaw", "workspace", "qqbot")])).map((from) => ({
183
184
  from,
184
185
  to: mediaRoot
185
186
  })),
@@ -747,13 +748,13 @@ async function sendPhoto(ctx, imagePath) {
747
748
  if (localFile) return await sendPhotoFromLocal(ctx, localFile);
748
749
  return {
749
750
  channel: "qqbot",
750
- error: `Failed to download image: ${mediaPath.slice(0, 80)}`
751
+ error: `Failed to download image: ${truncateUtf16Safe(mediaPath, 80)}`
751
752
  };
752
753
  }
753
754
  if (isLocal) return await sendPhotoFromLocal(ctx, mediaPath);
754
755
  if (!isHttp && !isData) return {
755
756
  channel: "qqbot",
756
- error: `Unsupported image source: ${mediaPath.slice(0, 50)}`
757
+ error: `Unsupported image source: ${truncateUtf16Safe(mediaPath, 50)}`
757
758
  };
758
759
  try {
759
760
  const creds = accountToCreds(ctx.account);
@@ -916,7 +917,7 @@ async function sendVoice(ctx, voicePath, directUploadFormats, transcodeEnabled =
916
917
  if (localFile) return await sendVoiceFromLocal(ctx, localFile, directUploadFormats, transcodeEnabled);
917
918
  return {
918
919
  channel: "qqbot",
919
- error: `Failed to download audio: ${mediaPath.slice(0, 80)}`
920
+ error: `Failed to download audio: ${truncateUtf16Safe(mediaPath, 80)}`
920
921
  };
921
922
  }
922
923
  return await sendVoiceFromLocal(ctx, mediaPath, directUploadFormats, transcodeEnabled);
@@ -1011,7 +1012,7 @@ async function sendVideoMsg(ctx, videoPath) {
1011
1012
  if (localFile) return await sendVideoFromLocal(ctx, localFile);
1012
1013
  return {
1013
1014
  channel: "qqbot",
1014
- error: `Failed to download video: ${mediaPath.slice(0, 80)}`
1015
+ error: `Failed to download video: ${truncateUtf16Safe(mediaPath, 80)}`
1015
1016
  };
1016
1017
  }
1017
1018
  try {
@@ -1125,7 +1126,7 @@ async function sendDocument(ctx, filePath, options = {}) {
1125
1126
  if (localFile) return await sendDocumentFromLocal(ctx, localFile);
1126
1127
  return {
1127
1128
  channel: "qqbot",
1128
- error: `Failed to download file: ${mediaPath.slice(0, 80)}`
1129
+ error: `Failed to download file: ${truncateUtf16Safe(mediaPath, 80)}`
1129
1130
  };
1130
1131
  }
1131
1132
  try {
@@ -1230,7 +1231,7 @@ async function downloadToFallbackDir(httpUrl, caller) {
1230
1231
  try {
1231
1232
  const localFile = await downloadFile(httpUrl, getQQBotMediaDir("downloads", "url-fallback"));
1232
1233
  if (!localFile) {
1233
- debugError(`${caller} fallback: download also failed for ${httpUrl.slice(0, 80)}`);
1234
+ debugError(`${caller} fallback: download also failed for ${truncateUtf16Safe(httpUrl, 80)}`);
1234
1235
  return null;
1235
1236
  }
1236
1237
  debugLog(`${caller} fallback: downloaded → ${localFile}`);
@@ -1395,7 +1396,8 @@ function parseQQBotPayload(text) {
1395
1396
  /** Encode a cron reminder payload into the stored cron-message format. */
1396
1397
  function encodePayloadForCron(payload) {
1397
1398
  const jsonString = JSON.stringify(payload);
1398
- return `${CRON_PREFIX}${Buffer.from(jsonString, "utf-8").toString("base64")}`;
1399
+ const base64 = Buffer.from(jsonString, "utf-8").toString("base64");
1400
+ return `${CRON_PREFIX}${base64}`;
1399
1401
  }
1400
1402
  /** Decode a stored cron payload. */
1401
1403
  function decodeCronPayload(message) {
@@ -1512,7 +1514,7 @@ function decodeMediaPath(raw, log) {
1512
1514
  * These replace the inline `isImageFile` and `isVideoFile` helpers scattered
1513
1515
  * across `outbound.ts`. Centralizing them here keeps detection consistent.
1514
1516
  */
1515
- const IMAGE_EXTENSIONS = new Set([
1517
+ const IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
1516
1518
  ".jpg",
1517
1519
  ".jpeg",
1518
1520
  ".png",
@@ -1520,7 +1522,7 @@ const IMAGE_EXTENSIONS = new Set([
1520
1522
  ".webp",
1521
1523
  ".bmp"
1522
1524
  ]);
1523
- const VIDEO_EXTENSIONS = new Set([
1525
+ const VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
1524
1526
  ".mp4",
1525
1527
  ".mov",
1526
1528
  ".avi",
@@ -1582,7 +1584,7 @@ async function sendText(ctx) {
1582
1584
  initApiConfig(account.appId, { markdownSupport: account.markdownSupport });
1583
1585
  debugLog("[qqbot] sendText ctx:", JSON.stringify({
1584
1586
  to,
1585
- text: text?.slice(0, 50),
1587
+ text: truncateUtf16Safe(text, 50),
1586
1588
  replyToId,
1587
1589
  accountId: account.accountId
1588
1590
  }, null, 2));
@@ -1680,7 +1682,7 @@ async function sendText(ctx) {
1680
1682
  timestamp: result.timestamp,
1681
1683
  refIdx: result.ext_info?.ref_idx
1682
1684
  };
1683
- debugLog(`[qqbot] sendText: Sent text part: ${item.content.slice(0, 30)}...`);
1685
+ debugLog(`[qqbot] sendText: Sent text part: ${truncateUtf16Safe(item.content, 30)}...`);
1684
1686
  } else if (item.type === "image") lastResult = await sendPhoto(mediaTarget, item.content);
1685
1687
  else if (item.type === "voice") lastResult = await sendVoice(mediaTarget, item.content, void 0, account.config?.audioFormatPolicy?.transcodeEnabled !== false);
1686
1688
  else if (item.type === "video") lastResult = await sendVideoMsg(mediaTarget, item.content);
@@ -1,4 +1,4 @@
1
- import { v as setOpenClawVersion } from "./sender-BfVLJJYp.js";
1
+ import { v as setOpenClawVersion } from "./sender-CjDuU-uz.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-B4HWCOa-.js";
1
+ import { r as setQQBotRuntime, t as getQQBotRuntime } from "./runtime-DodcT_fQ.js";
2
2
  export { getQQBotRuntime, setQQBotRuntime };
@@ -1,5 +1,6 @@
1
1
  import { c as getPlatformAdapter, i as normalizeOptionalString, s as sanitizeFileName } from "./string-normalize-R_0cKO7Q.js";
2
- import { a as formatErrorMessage, n as debugLog, r as debugWarn, t as debugError } from "./log-SDfMMBWe.js";
2
+ import { a as formatErrorMessage, n as debugLog, r as debugWarn, t as debugError } from "./log-DEtcoDWe.js";
3
+ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
3
4
  import * as fs$1 from "node:fs";
4
5
  import os from "node:os";
5
6
  import { readProviderTextResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
@@ -10,8 +11,8 @@ import { sleep } from "openclaw/plugin-sdk/runtime-env";
10
11
  import { FsSafeError, openLocalFileSafely, readRegularFile, statRegularFileSync } from "openclaw/plugin-sdk/security-runtime";
11
12
  import * as path$1 from "node:path";
12
13
  import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
14
+ import { asDateTimestampMs, formatByteSize, isFutureDateTimestampMs, parseStrictPositiveInteger, resolveExpiresAtMsFromDurationSeconds, resolveTimestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
13
15
  import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
14
- import { asDateTimestampMs, isFutureDateTimestampMs, parseStrictPositiveInteger, resolveExpiresAtMsFromDurationSeconds, resolveTimestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
15
16
  //#region \0rolldown/runtime.js
16
17
  var __defProp = Object.defineProperty;
17
18
  var __exportAll = (all, no_symbols) => {
@@ -176,7 +177,7 @@ var ApiClient = class {
176
177
  throw new ApiError(`API Error [${path}]: ${error.message ?? rawBody}`, res.status, path, bizCode, error.message);
177
178
  } catch (parseErr) {
178
179
  if (parseErr instanceof ApiError) throw parseErr;
179
- throw new ApiError(`API Error [${path}] HTTP ${res.status}: ${rawBody.slice(0, 200)}`, res.status, path);
180
+ throw new ApiError(`API Error [${path}] HTTP ${res.status}: ${truncateUtf16Safe(rawBody, 200)}`, res.status, path);
180
181
  }
181
182
  }
182
183
  if (isHtmlResponse) throw new ApiError(`QQ 服务端返回了非 JSON 响应(${path}),可能是临时故障,请稍后重试`, res.status, path);
@@ -287,16 +288,19 @@ async function fileExistsAsync(filePath) {
287
288
  }
288
289
  /** Format a byte count into a human-readable size string. */
289
290
  function formatFileSize(bytes) {
290
- if (bytes < 1024) return `${bytes}B`;
291
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
292
- return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
291
+ return formatByteSize(bytes, {
292
+ style: "legacy-binary",
293
+ maxUnit: "mega",
294
+ separator: "",
295
+ fractionDigits: (_value, unit) => unit === "byte" ? null : 1
296
+ });
293
297
  }
294
298
  /** Infer a MIME type from the file extension. */
295
299
  function getMimeType(filePath) {
296
300
  return mimeTypeFromFilePath(filePath) ?? "application/octet-stream";
297
301
  }
298
302
  /** Extensions accepted as image uploads by the QQ Bot media pipeline. */
299
- const IMAGE_EXTENSIONS = new Set([
303
+ const IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
300
304
  ".jpg",
301
305
  ".jpeg",
302
306
  ".png",
@@ -495,7 +499,7 @@ async function withRetry(fn, policy, persistentPolicy, logger) {
495
499
  if (policy.shouldRetry?.(lastError, attempt) === false) throw lastError;
496
500
  if (attempt < policy.maxRetries) {
497
501
  const delay = policy.backoff === "exponential" ? policy.baseDelayMs * 2 ** attempt : policy.baseDelayMs;
498
- logger?.debug?.(`[qqbot:retry] Attempt ${attempt + 1} failed, retrying in ${delay}ms: ${lastError.message.slice(0, 100)}`);
502
+ logger?.debug?.(`[qqbot:retry] Attempt ${attempt + 1} failed, retrying in ${delay}ms: ${truncateUtf16Safe(lastError.message, 100)}`);
499
503
  await sleep(delay);
500
504
  }
501
505
  }
@@ -571,7 +575,7 @@ function buildPartFinishPersistentPolicy(retryTimeoutMs, retryableCodes = PART_F
571
575
  };
572
576
  }
573
577
  /** Business error codes that trigger persistent part-finish retry. */
574
- const PART_FINISH_RETRYABLE_CODES = new Set([40093001]);
578
+ const PART_FINISH_RETRYABLE_CODES = /* @__PURE__ */ new Set([40093001]);
575
579
  /** upload_prepare error code indicating daily limit exceeded. */
576
580
  const UPLOAD_PREPARE_FALLBACK_CODE = 40093002;
577
581
  //#endregion
@@ -938,8 +942,8 @@ async function putToPresignedUrl(presignedUrl, data, partIndex, totalParts, logg
938
942
  const etag = response.headers.get("ETag") ?? "-";
939
943
  if (!response.ok) {
940
944
  const body = await readResponseTextLimited(response, PART_UPLOAD_ERROR_BODY_LIMIT_BYTES).catch(() => "");
941
- logger?.error?.(`${prefix} PUT part ${partIndex}/${totalParts}: HTTP ${response.status} ${response.statusText} (${elapsed}ms, requestId=${requestId}) body=${body.slice(0, 160)}`);
942
- throw new Error(`COS PUT failed: ${response.status} ${response.statusText} - ${body.slice(0, 120)}`);
945
+ logger?.error?.(`${prefix} PUT part ${partIndex}/${totalParts}: HTTP ${response.status} ${response.statusText} (${elapsed}ms, requestId=${requestId}) body=${truncateUtf16Safe(body, 160)}`);
946
+ throw new Error(`COS PUT failed: ${response.status} ${response.statusText} - ${truncateUtf16Safe(body, 120)}`);
943
947
  }
944
948
  logger?.debug?.(`${prefix} PUT part ${partIndex}/${totalParts} OK (${elapsed}ms ETag=${etag} requestId=${requestId})`);
945
949
  return;
@@ -951,7 +955,7 @@ async function putToPresignedUrl(presignedUrl, data, partIndex, totalParts, logg
951
955
  if (lastError.name === "AbortError") lastError = /* @__PURE__ */ new Error(`Part ${partIndex}/${totalParts} upload timeout after ${PART_UPLOAD_TIMEOUT_MS}ms`);
952
956
  if (attempt < PART_UPLOAD_MAX_RETRIES) {
953
957
  const delay = 1e3 * 2 ** attempt;
954
- (logger?.warn ?? logger?.error)?.(`${prefix} PUT part ${partIndex}/${totalParts} attempt ${attempt + 1} failed (${lastError.message.slice(0, 120)}), retrying in ${delay}ms`);
958
+ (logger?.warn ?? logger?.error)?.(`${prefix} PUT part ${partIndex}/${totalParts} attempt ${attempt + 1} failed (${truncateUtf16Safe(lastError.message, 120)}), retrying in ${delay}ms`);
955
959
  await sleep(delay);
956
960
  }
957
961
  } finally {
@@ -1812,7 +1816,7 @@ async function withTokenRetry(creds, sendFn, log, _accountId) {
1812
1816
  }
1813
1817
  const errMsg = formatErrorMessage(err);
1814
1818
  if (errMsg.includes("401") || errMsg.includes("token") || errMsg.includes("access_token")) {
1815
- log?.warn?.(`Token retry triggered by string heuristic (err is not ApiError). Consider propagating ApiError end-to-end. msg=${errMsg.slice(0, 120)}`);
1819
+ log?.warn?.(`Token retry triggered by string heuristic (err is not ApiError). Consider propagating ApiError end-to-end. msg=${truncateUtf16Safe(errMsg, 120)}`);
1816
1820
  clearTokenCache(creds.appId);
1817
1821
  return await sendFn(await getAccessToken(creds.appId, creds.clientSecret));
1818
1822
  }
@@ -1,11 +1,12 @@
1
1
  import { a as resolveQQBotAccount, r as listQQBotAccountIds } from "./config-C1qZbh0K.js";
2
- import { a as formatErrorMessage, n as debugLog, t as debugError } from "./log-SDfMMBWe.js";
2
+ import { a as formatErrorMessage, n as debugLog, t as debugError } from "./log-DEtcoDWe.js";
3
3
  import { t as getRequestContext } from "./request-context-Bm7PTBD1.js";
4
+ import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
4
5
  import { readProviderTextResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
5
6
  import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
6
7
  import { resolveExpiresAtMsFromDurationMs } from "openclaw/plugin-sdk/number-runtime";
8
+ import { jsonResult } from "openclaw/plugin-sdk/tool-results";
7
9
  import { callGatewayTool } from "openclaw/plugin-sdk/agent-harness-runtime";
8
- import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
9
10
  //#region extensions/qqbot/src/engine/tools/channel-api.ts
10
11
  /**
11
12
  * QQ Channel API proxy tool core logic.
@@ -118,15 +119,6 @@ function validateDeleteConfirmation(params) {
118
119
  if (isBulkAnnouncementDeletePath(params.path) && !params.bulkConfirmed) return "Deleting all announcements requires bulkConfirmed=true after a separate bulk-delete confirmation.";
119
120
  return null;
120
121
  }
121
- function json$1(data) {
122
- return {
123
- content: [{
124
- type: "text",
125
- text: JSON.stringify(data, null, 2)
126
- }],
127
- details: data
128
- };
129
- }
130
122
  /**
131
123
  * Execute a channel API proxy request.
132
124
  * 执行频道 API 代理请求。
@@ -136,8 +128,8 @@ function json$1(data) {
136
128
  * response formatting suitable for AI tool output.
137
129
  */
138
130
  async function executeChannelApi(params, options) {
139
- if (!params.method) return json$1({ error: "method is required" });
140
- if (!params.path) return json$1({ error: "path is required" });
131
+ if (!params.method) return jsonResult({ error: "method is required" });
132
+ if (!params.path) return jsonResult({ error: "path is required" });
141
133
  const method = params.method.toUpperCase();
142
134
  if (![
143
135
  "GET",
@@ -145,14 +137,14 @@ async function executeChannelApi(params, options) {
145
137
  "PUT",
146
138
  "PATCH",
147
139
  "DELETE"
148
- ].includes(method)) return json$1({ error: `Unsupported HTTP method: ${method}. Allowed values: GET, POST, PUT, PATCH, DELETE` });
140
+ ].includes(method)) return jsonResult({ error: `Unsupported HTTP method: ${method}. Allowed values: GET, POST, PUT, PATCH, DELETE` });
149
141
  const pathError = validatePath(params.path);
150
- if (pathError) return json$1({ error: pathError });
142
+ if (pathError) return jsonResult({ error: pathError });
151
143
  const confirmationError = validateDeleteConfirmation({
152
144
  ...params,
153
145
  method
154
146
  });
155
- if (confirmationError) return json$1({
147
+ if (confirmationError) return jsonResult({
156
148
  error: confirmationError,
157
149
  path: params.path
158
150
  });
@@ -191,13 +183,13 @@ async function executeChannelApi(params, options) {
191
183
  clearTimeout(timeoutId);
192
184
  if (err instanceof Error && err.name === "AbortError") {
193
185
  debugError(`[qqbot-channel-api] <<< Request timeout after ${DEFAULT_TIMEOUT_MS}ms`);
194
- return json$1({
186
+ return jsonResult({
195
187
  error: `Request timed out after ${DEFAULT_TIMEOUT_MS}ms`,
196
188
  path: params.path
197
189
  });
198
190
  }
199
191
  debugError("[qqbot-channel-api] <<< Network error:", err);
200
- return json$1({
192
+ return jsonResult({
201
193
  error: `Network error: ${formatErrorMessage(err)}`,
202
194
  path: params.path
203
195
  });
@@ -208,12 +200,12 @@ async function executeChannelApi(params, options) {
208
200
  debugLog(`[qqbot-channel-api] <<< Status: ${res.status} ${res.statusText}`);
209
201
  const rawBody = res.ok ? await readProviderTextResponse(res, "QQ channel API response") : await readResponseTextLimited(res, CHANNEL_API_ERROR_BODY_LIMIT_BYTES);
210
202
  if (!rawBody || rawBody.trim() === "") {
211
- if (res.ok) return json$1({
203
+ if (res.ok) return jsonResult({
212
204
  success: true,
213
205
  status: res.status,
214
206
  path: params.path
215
207
  });
216
- return json$1({
208
+ return jsonResult({
217
209
  error: `API returned ${res.status} ${res.statusText}`,
218
210
  status: res.status,
219
211
  path: params.path
@@ -228,14 +220,14 @@ async function executeChannelApi(params, options) {
228
220
  if (!res.ok) {
229
221
  const errMsg = typeof parsed === "object" && parsed && "message" in parsed ? String(parsed.message) : `${res.status} ${res.statusText}`;
230
222
  debugError(`[qqbot-channel-api] Error [${method} ${params.path}]: ${errMsg}`);
231
- return json$1({
223
+ return jsonResult({
232
224
  error: errMsg,
233
225
  status: res.status,
234
226
  path: params.path,
235
227
  details: parsed
236
228
  });
237
229
  }
238
- return json$1({
230
+ return jsonResult({
239
231
  success: true,
240
232
  status: res.status,
241
233
  path: params.path,
@@ -245,7 +237,7 @@ async function executeChannelApi(params, options) {
245
237
  await release?.();
246
238
  }
247
239
  } catch (err) {
248
- return json$1({
240
+ return jsonResult({
249
241
  error: formatErrorMessage(err),
250
242
  path: params.path
251
243
  });
@@ -274,7 +266,7 @@ function registerChannelTool(api) {
274
266
  description: "Authenticated HTTP proxy for QQ Open Platform channel APIs. Use write and delete endpoints only after explicit user intent; DELETE requires confirmed=true, and bulk deletes require bulkConfirmed=true after confirming the exact target. 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.",
275
267
  parameters: ChannelApiSchema,
276
268
  async execute(_toolCallId, params) {
277
- const { getAccessToken } = await import("./sender-BfVLJJYp.js").then((n) => n._);
269
+ const { getAccessToken } = await import("./sender-CjDuU-uz.js").then((n) => n._);
278
270
  return executeChannelApi(params, { accessToken: await getAccessToken(account.appId, account.clientSecret) });
279
271
  }
280
272
  }, { name: "qqbot_channel_api" });
@@ -451,15 +443,6 @@ function formatDelay(ms) {
451
443
  if (minutes === 0) return `${hours}h`;
452
444
  return `${hours}h${minutes}m`;
453
445
  }
454
- function json(data) {
455
- return {
456
- content: [{
457
- type: "text",
458
- text: JSON.stringify(data, null, 2)
459
- }],
460
- details: data
461
- };
462
- }
463
446
  function formatSchedulerError(error) {
464
447
  return error instanceof Error ? error.message : String(error);
465
448
  }
@@ -526,17 +509,17 @@ function prepareRemindCronAction(params, ctx = {}) {
526
509
  }
527
510
  async function executeScheduledRemind(params, ctx, scheduler) {
528
511
  const plan = prepareRemindCronAction(params, ctx);
529
- if (!plan.ok) return json({ error: plan.error });
512
+ if (!plan.ok) return jsonResult({ error: plan.error });
530
513
  try {
531
514
  const cronResult = await scheduler(plan.cronAction);
532
- return json({
515
+ return jsonResult({
533
516
  ok: true,
534
517
  action: plan.action,
535
518
  summary: plan.summary,
536
519
  cronResult
537
520
  });
538
521
  } catch (error) {
539
- return json({
522
+ return jsonResult({
540
523
  error: `Failed to run Gateway cron action: ${formatSchedulerError(error)}`,
541
524
  action: plan.action
542
525
  });
package/dist/tools-api.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as registerQQBotTools } from "./tools-C8kTMXKc.js";
1
+ import { t as registerQQBotTools } from "./tools-UJJ-tLHP.js";
2
2
  export { registerQQBotTools };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/qqbot",
3
- "version": "2026.7.1-beta.2",
3
+ "version": "2026.7.1-beta.5",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/qqbot",
9
- "version": "2026.7.1-beta.2",
9
+ "version": "2026.7.1-beta.5",
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.7.1-beta.2"
18
+ "openclaw": ">=2026.7.1-beta.5"
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.7.1-beta.2",
3
+ "version": "2026.7.1-beta.5",
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.7.1-beta.2"
19
+ "openclaw": ">=2026.7.1-beta.5"
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.7.1-beta.2"
48
+ "pluginApi": ">=2026.7.1-beta.5"
49
49
  },
50
50
  "build": {
51
- "openclawVersion": "2026.7.1-beta.2"
51
+ "openclawVersion": "2026.7.1-beta.5"
52
52
  },
53
53
  "release": {
54
54
  "publishToClawHub": true,