@openclaw/qqbot 2026.5.12-beta.8 → 2026.5.14-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.js CHANGED
@@ -1,9 +1,9 @@
1
- import { C as recordMessageReply, E as setOutboundAudioPort, S as getMessageReplyStats, T as OUTBOUND_ERROR_CODES, _ as sendVoice, a as sendText, b as checkMessageReplyLimit, d as buildMediaTarget, f as parseTarget, g as sendVideoMsg, h as sendPhoto, i as sendProactiveMessage, m as sendDocument, n as sendCronMessage, p as resolveOutboundMediaPath, r as sendMedia, v as resolveUserFacingMediaError, w as DEFAULT_MEDIA_SEND_ERROR, x as getMessageReplyConfig, y as MESSAGE_REPLY_LIMIT } from "./outbound-BLWish89.js";
2
- import { a as resolveQQBotAccount, i as resolveDefaultQQBotAccountId, n as applyQQBotAccountConfig, r as listQQBotAccountIds, t as DEFAULT_ACCOUNT_ID } from "./config-BT8KP_H1.js";
3
- import { t as qqbotPlugin } from "./channel-CmK-n3Oz.js";
4
- import { L as formatErrorMessage, S as debugLog, o as getAccessToken, x as debugError } from "./sender-BrpzCcXA.js";
5
- import { t as qqbotSetupPlugin } from "./channel.setup-D_GS95Pk.js";
6
- import { r as getFrameworkCommands, t as getRequestContext } from "./request-context-BwSBFANU.js";
1
+ import { C as recordMessageReply, E as setOutboundAudioPort, S as getMessageReplyStats, T as OUTBOUND_ERROR_CODES, _ as sendVoice, a as sendText, b as checkMessageReplyLimit, d as buildMediaTarget, f as parseTarget, g as sendVideoMsg, h as sendPhoto, i as sendProactiveMessage, m as sendDocument, n as sendCronMessage, p as resolveOutboundMediaPath, r as sendMedia, v as resolveUserFacingMediaError, w as DEFAULT_MEDIA_SEND_ERROR, x as getMessageReplyConfig, y as MESSAGE_REPLY_LIMIT } from "./outbound-BdQEr78b.js";
2
+ import { a as resolveQQBotAccount, i as resolveDefaultQQBotAccountId, n as applyQQBotAccountConfig, r as listQQBotAccountIds, t as DEFAULT_ACCOUNT_ID } from "./config-DiBiPtf4.js";
3
+ import { t as qqbotPlugin } from "./channel-BYNY5Nwb.js";
4
+ import { L as formatErrorMessage, S as debugLog, o as getAccessToken, x as debugError } from "./sender-BeE_CBbN.js";
5
+ import { t as qqbotSetupPlugin } from "./channel.setup-DWWV_GAa.js";
6
+ import { r as getFrameworkCommands, t as getRequestContext } from "./request-context-Betw4nJr.js";
7
7
  import { callGatewayTool } from "openclaw/plugin-sdk/agent-harness-runtime";
8
8
  //#region extensions/qqbot/src/engine/tools/channel-api.ts
9
9
  /**
@@ -1,12 +1,10 @@
1
1
  import { t as asOptionalObjectRecord } from "./string-normalize-C46CS-F1.js";
2
- import { l as ensurePlatformAdapter, u as getBridgeLogger } from "./resolve-D3lCbUze.js";
3
- import { a as resolveApprovalTarget } from "./approval-cg0SVahb.js";
4
- import { a as resolveQQBotAccount, n as applyQQBotAccountConfig, t as DEFAULT_ACCOUNT_ID$1 } from "./config-BT8KP_H1.js";
5
- import { a as resolveQQBotExecApprovalConfig, i as matchesQQBotApprovalAccount, n as isQQBotExecApprovalAuthorizedSender, o as shouldHandleQQBotExecApprovalRequest, r as isQQBotExecApprovalClientEnabled, t as isQQBotExecApprovalApprover } from "./exec-approvals-W4oL9R6C.js";
6
- import { a as qqbotSetupAdapterShared, i as qqbotMeta, n as qqbotSetupWizard, r as qqbotConfigAdapter, t as qqbotChannelConfigSchema } from "./config-schema-BCxobX3l.js";
7
- import { n as writeOpenClawConfigThroughRuntime, t as toGatewayAccount } from "./narrowing-BoieBTIU.js";
8
- import { t as getQQBotRuntime } from "./runtime-CZyFkpnB.js";
9
- import { n as normalizeTarget, s as getQQBotDataPath, t as looksLikeQQBotTarget } from "./target-parser-C8HaD-ik.js";
2
+ import { a as resolveQQBotAccount, f as ensurePlatformAdapter, n as applyQQBotAccountConfig, p as getBridgeLogger, t as DEFAULT_ACCOUNT_ID$1 } from "./config-DiBiPtf4.js";
3
+ import { a as shouldHandleQQBotExecApprovalRequest, i as resolveQQBotExecApprovalConfig, n as isQQBotExecApprovalClientEnabled, r as matchesQQBotApprovalAccount, t as authorizeQQBotApprovalAction, u as resolveApprovalTarget } from "./exec-approvals-Deww_EGt.js";
4
+ import { a as qqbotSetupAdapterShared, i as qqbotMeta, n as qqbotSetupWizard, r as qqbotConfigAdapter, t as qqbotChannelConfigSchema } from "./config-schema-BeJQUheK.js";
5
+ import { n as writeOpenClawConfigThroughRuntime, t as toGatewayAccount } from "./narrowing-Bj4eMufj.js";
6
+ import { t as getQQBotRuntime } from "./runtime-B-cqLImu.js";
7
+ import { n as normalizeTarget, s as getQQBotDataPath, t as looksLikeQQBotTarget } from "./target-parser-ByU3srDK.js";
10
8
  import { getExecApprovalReplyMetadata } from "openclaw/plugin-sdk/approval-runtime";
11
9
  import { createMessageReceiptFromOutboundResults, defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-message";
12
10
  import { createChannelApprovalCapability } from "openclaw/plugin-sdk/approval-delivery-runtime";
@@ -21,10 +19,9 @@ import { loadJsonFile } from "openclaw/plugin-sdk/json-store";
21
19
  /**
22
20
  * QQ Bot Approval Capability — entry point.
23
21
  *
24
- * QQBot uses a simpler approval model than Telegram/Slack: any user who
25
- * can see the inline-keyboard buttons can approve. No explicit approver
26
- * list is required the bot simply sends the approval message to the
27
- * originating conversation and whoever clicks the button resolves it.
22
+ * QQBot uses a simpler approval model than Telegram/Slack: when no
23
+ * approver list is configured, the bot sends the approval message to the
24
+ * originating conversation and any participant can approve from there.
28
25
  *
29
26
  * When `execApprovals` IS configured, it gates which requests are
30
27
  * handled natively and who is authorized. When it is NOT configured,
@@ -65,24 +62,12 @@ function canResolveTarget(request) {
65
62
  }
66
63
  function createQQBotApprovalCapability() {
67
64
  return createChannelApprovalCapability({
68
- authorizeActorAction: ({ cfg, accountId, senderId, approvalKind }) => {
69
- if (hasExecApprovalConfig({
70
- cfg,
71
- accountId
72
- })) return (approvalKind === "plugin" ? isQQBotExecApprovalApprover({
73
- cfg,
74
- accountId,
75
- senderId
76
- }) : isQQBotExecApprovalAuthorizedSender({
77
- cfg,
78
- accountId,
79
- senderId
80
- })) ? { authorized: true } : {
81
- authorized: false,
82
- reason: "You are not authorized to approve this request."
83
- };
84
- return { authorized: true };
85
- },
65
+ authorizeActorAction: ({ cfg, accountId, senderId, approvalKind }) => authorizeQQBotApprovalAction({
66
+ cfg,
67
+ accountId,
68
+ senderId,
69
+ approvalKind
70
+ }),
86
71
  getActionAvailabilityState: ({ cfg, accountId }) => {
87
72
  return isNativeDeliveryEnabled({
88
73
  cfg,
@@ -156,7 +141,7 @@ function createQQBotApprovalCapability() {
156
141
  },
157
142
  load: async () => {
158
143
  ensurePlatformAdapter();
159
- return (await import("./handler-runtime-ZgvjnyoH.js")).qqbotApprovalNativeRuntime;
144
+ return (await import("./handler-runtime-COWL4RMq.js")).qqbotApprovalNativeRuntime;
160
145
  }
161
146
  })
162
147
  });
@@ -345,7 +330,7 @@ function clearAccountCredentials(cfg, accountId) {
345
330
  //#region extensions/qqbot/src/channel.ts
346
331
  let gatewayModulePromise;
347
332
  function loadGatewayModule() {
348
- gatewayModulePromise ??= import("./gateway-Bbsd8-ne.js");
333
+ gatewayModulePromise ??= import("./gateway-DEJhmN-p.js");
349
334
  return gatewayModulePromise;
350
335
  }
351
336
  function createQQBotSendReceipt(params) {
@@ -363,7 +348,7 @@ function createQQBotSendReceipt(params) {
363
348
  async function sendQQBotText(params) {
364
349
  await loadGatewayModule();
365
350
  const account = resolveQQBotAccount(params.cfg, params.accountId);
366
- const { sendText } = await import("./outbound-BLWish89.js").then((n) => n.t);
351
+ const { sendText } = await import("./outbound-BdQEr78b.js").then((n) => n.t);
367
352
  const result = await sendText({
368
353
  to: params.to,
369
354
  text: params.text,
@@ -385,7 +370,7 @@ async function sendQQBotText(params) {
385
370
  async function sendQQBotMedia(params) {
386
371
  await loadGatewayModule();
387
372
  const account = resolveQQBotAccount(params.cfg, params.accountId);
388
- const { sendMedia } = await import("./outbound-BLWish89.js").then((n) => n.t);
373
+ const { sendMedia } = await import("./outbound-BdQEr78b.js").then((n) => n.t);
389
374
  const result = await sendMedia({
390
375
  to: params.to,
391
376
  text: params.text ?? "",
@@ -1,2 +1,2 @@
1
- import { t as qqbotPlugin } from "./channel-CmK-n3Oz.js";
1
+ import { t as qqbotPlugin } from "./channel-BYNY5Nwb.js";
2
2
  export { qqbotPlugin };
@@ -1,5 +1,5 @@
1
- import "./resolve-D3lCbUze.js";
2
- import { a as qqbotSetupAdapterShared, i as qqbotMeta, n as qqbotSetupWizard, r as qqbotConfigAdapter, t as qqbotChannelConfigSchema } from "./config-schema-BCxobX3l.js";
1
+ import "./config-DiBiPtf4.js";
2
+ import { a as qqbotSetupAdapterShared, i as qqbotMeta, n as qqbotSetupWizard, r as qqbotConfigAdapter, t as qqbotChannelConfigSchema } from "./config-schema-BeJQUheK.js";
3
3
  //#region extensions/qqbot/src/channel.setup.ts
4
4
  /**
5
5
  * Setup-only QQBot plugin — lightweight subset used during `openclaw onboard`
@@ -1,6 +1,8 @@
1
1
  import { a as normalizeStringifiedOptionalString, c as getPlatformAdapter, d as registerPlatformAdapterFactory, l as hasPlatformAdapter, o as readStringField, r as normalizeOptionalLowercaseString, t as asOptionalObjectRecord, u as registerPlatformAdapter } from "./string-normalize-C46CS-F1.js";
2
- import { hasConfiguredSecretInput, normalizeResolvedSecretInputString, normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
2
+ import { coerceSecretRef, hasConfiguredSecretInput, normalizeResolvedSecretInputString, normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
3
3
  import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
4
+ import fs from "node:fs";
5
+ import { resolveDefaultSecretProviderAlias } from "openclaw/plugin-sdk/provider-auth";
4
6
  //#region extensions/qqbot/src/bridge/logger.ts
5
7
  let _logger = null;
6
8
  /** Register the framework logger. Called once in startGateway(). */
@@ -48,8 +50,8 @@ function createBuiltinAdapter() {
48
50
  if (typeof value === "string") return value || void 0;
49
51
  },
50
52
  async downloadFile(url, destDir, filename) {
51
- const { fetchRemoteMedia } = await import("openclaw/plugin-sdk/media-runtime");
52
- const result = await fetchRemoteMedia({
53
+ const { readRemoteMediaBuffer } = await import("openclaw/plugin-sdk/media-runtime");
54
+ const result = await readRemoteMediaBuffer({
53
55
  url,
54
56
  filePathHint: filename
55
57
  });
@@ -61,8 +63,8 @@ function createBuiltinAdapter() {
61
63
  return destPath;
62
64
  },
63
65
  async fetchMedia(options) {
64
- const { fetchRemoteMedia } = await import("openclaw/plugin-sdk/media-runtime");
65
- const result = await fetchRemoteMedia({
66
+ const { readRemoteMediaBuffer } = await import("openclaw/plugin-sdk/media-runtime");
67
+ const result = await readRemoteMediaBuffer({
66
68
  url: options.url,
67
69
  filePathHint: options.filePathHint,
68
70
  maxBytes: options.maxBytes,
@@ -131,7 +133,7 @@ ensurePlatformAdapter();
131
133
  * Default account ID, used for the unnamed top-level account.
132
134
  * 默认账号 ID,用于顶层配置中未命名的账号。
133
135
  */
134
- const DEFAULT_ACCOUNT_ID = "default";
136
+ const DEFAULT_ACCOUNT_ID$1 = "default";
135
137
  function normalizeAppId(raw) {
136
138
  if (typeof raw === "string") return raw.trim();
137
139
  if (typeof raw === "number") return String(raw);
@@ -155,7 +157,7 @@ function readQQBotSection(cfg) {
155
157
  function listAccountIds(cfg) {
156
158
  const ids = /* @__PURE__ */ new Set();
157
159
  const qqbot = readQQBotSection(cfg);
158
- if (qqbot?.appId || process.env.QQBOT_APP_ID) ids.add(DEFAULT_ACCOUNT_ID);
160
+ if (qqbot?.appId || process.env.QQBOT_APP_ID) ids.add(DEFAULT_ACCOUNT_ID$1);
159
161
  if (qqbot?.accounts) {
160
162
  for (const accountId of Object.keys(qqbot.accounts)) if (qqbot.accounts[accountId]?.appId) ids.add(accountId);
161
163
  }
@@ -169,12 +171,12 @@ function resolveDefaultAccountId(cfg) {
169
171
  const qqbot = readQQBotSection(cfg);
170
172
  const configuredDefaultAccountId = normalizeOptionalLowercaseString(qqbot?.defaultAccount);
171
173
  if (configuredDefaultAccountId && (configuredDefaultAccountId === "default" || Boolean(qqbot?.accounts?.[configuredDefaultAccountId]?.appId))) return configuredDefaultAccountId;
172
- if (qqbot?.appId || process.env.QQBOT_APP_ID) return DEFAULT_ACCOUNT_ID;
174
+ if (qqbot?.appId || process.env.QQBOT_APP_ID) return DEFAULT_ACCOUNT_ID$1;
173
175
  if (qqbot?.accounts) {
174
176
  const ids = Object.keys(qqbot.accounts);
175
177
  if (ids.length > 0) return ids[0];
176
178
  }
177
- return DEFAULT_ACCOUNT_ID;
179
+ return DEFAULT_ACCOUNT_ID$1;
178
180
  }
179
181
  /**
180
182
  * Resolve base account info (without credentials).
@@ -281,4 +283,92 @@ function formatAllowFrom(allowFrom) {
281
283
  return (allowFrom ?? []).map((entry) => normalizeStringifiedOptionalString(entry)).filter((entry) => Boolean(entry)).map((entry) => entry.replace(/^qqbot:/i, "")).map((entry) => entry.toUpperCase());
282
284
  }
283
285
  //#endregion
284
- export { isAccountConfigured as a, resolveDefaultAccountId as c, setBridgeLogger as d, formatAllowFrom as i, ensurePlatformAdapter as l, applyAccountConfig as n, listAccountIds as o, describeAccount as r, resolveAccountBase as s, DEFAULT_ACCOUNT_ID as t, getBridgeLogger as u };
286
+ //#region extensions/qqbot/src/bridge/config.ts
287
+ const DEFAULT_ACCOUNT_ID = DEFAULT_ACCOUNT_ID$1;
288
+ function assertNotLegacySecretRefMarker(value, path) {
289
+ const normalized = normalizeSecretInputString(value);
290
+ if (!normalized || !/^secretref(?:-env)?:/i.test(normalized)) return;
291
+ throw new Error(`${path}: legacy SecretRef marker strings are not valid QQ Bot clientSecret values; use a structured SecretRef object instead.`);
292
+ }
293
+ function resolveEnvSecretRefValue(params) {
294
+ const ref = coerceSecretRef(params.value, params.cfg.secrets?.defaults);
295
+ if (!ref || ref.source !== "env") return;
296
+ const providerConfig = params.cfg.secrets?.providers?.[ref.provider];
297
+ if (providerConfig) {
298
+ if (providerConfig.source !== "env") throw new Error(`Secret provider "${ref.provider}" has source "${providerConfig.source}" but ref requests "env".`);
299
+ if (providerConfig.allowlist && !providerConfig.allowlist.includes(ref.id)) throw new Error(`Environment variable "${ref.id}" is not allowlisted in secrets.providers.${ref.provider}.allowlist.`);
300
+ } else if (ref.provider !== resolveDefaultSecretProviderAlias(params.cfg, "env")) throw new Error(`Secret provider "${ref.provider}" is not configured (ref: env:${ref.provider}:${ref.id}).`);
301
+ return normalizeSecretInputString((params.env ?? process.env)[ref.id]);
302
+ }
303
+ function resolveQQBotClientSecretInput(params) {
304
+ assertNotLegacySecretRefMarker(params.value, params.path);
305
+ const envSecret = resolveEnvSecretRefValue({
306
+ cfg: params.cfg,
307
+ value: params.value
308
+ });
309
+ if (envSecret) return envSecret;
310
+ return getPlatformAdapter().resolveSecretInputString({
311
+ value: params.value,
312
+ path: params.path
313
+ });
314
+ }
315
+ /** List all configured QQBot account IDs. */
316
+ function listQQBotAccountIds(cfg) {
317
+ return listAccountIds(cfg);
318
+ }
319
+ /** Resolve the default QQBot account ID. */
320
+ function resolveDefaultQQBotAccountId(cfg) {
321
+ return resolveDefaultAccountId(cfg);
322
+ }
323
+ /** Resolve QQBot account config for runtime or setup flows. */
324
+ function resolveQQBotAccount(cfg, accountId, opts) {
325
+ const base = resolveAccountBase(cfg, accountId);
326
+ const qqbot = cfg.channels?.qqbot;
327
+ /**
328
+ * Legacy top-level account uses `channels.qqbot` as the base, but per-account
329
+ * fields (allowFrom, streaming, …) often live under `accounts.default`.
330
+ * Merge that slice so runtime sees `config.streaming` etc.
331
+ */
332
+ const accountConfig = base.accountId === DEFAULT_ACCOUNT_ID ? {
333
+ ...qqbot,
334
+ ...qqbot?.accounts?.[DEFAULT_ACCOUNT_ID]
335
+ } : qqbot?.accounts?.[base.accountId] ?? {};
336
+ let clientSecret = "";
337
+ let secretSource = "none";
338
+ const clientSecretPath = base.accountId === DEFAULT_ACCOUNT_ID ? "channels.qqbot.clientSecret" : `channels.qqbot.accounts.${base.accountId}.clientSecret`;
339
+ const adapter = getPlatformAdapter();
340
+ if (adapter.hasConfiguredSecret(accountConfig.clientSecret)) {
341
+ clientSecret = opts?.allowUnresolvedSecretRef ? adapter.normalizeSecretInputString(accountConfig.clientSecret) ?? "" : resolveQQBotClientSecretInput({
342
+ cfg,
343
+ value: accountConfig.clientSecret,
344
+ path: clientSecretPath
345
+ }) ?? "";
346
+ secretSource = "config";
347
+ } else if (accountConfig.clientSecretFile) try {
348
+ clientSecret = fs.readFileSync(accountConfig.clientSecretFile, "utf8").trim();
349
+ secretSource = "file";
350
+ } catch {
351
+ secretSource = "none";
352
+ }
353
+ else if (process.env.QQBOT_CLIENT_SECRET && base.accountId === DEFAULT_ACCOUNT_ID) {
354
+ clientSecret = process.env.QQBOT_CLIENT_SECRET;
355
+ secretSource = "env";
356
+ }
357
+ return {
358
+ accountId: base.accountId,
359
+ name: accountConfig.name,
360
+ enabled: base.enabled,
361
+ appId: base.appId,
362
+ clientSecret,
363
+ secretSource,
364
+ systemPrompt: base.systemPrompt,
365
+ markdownSupport: base.markdownSupport,
366
+ config: accountConfig
367
+ };
368
+ }
369
+ /** Apply account config updates back into the OpenClaw config object. */
370
+ function applyQQBotAccountConfig(cfg, accountId, input) {
371
+ return applyAccountConfig(cfg, accountId, input);
372
+ }
373
+ //#endregion
374
+ export { resolveQQBotAccount as a, describeAccount as c, resolveAccountBase as d, ensurePlatformAdapter as f, resolveDefaultQQBotAccountId as i, formatAllowFrom as l, setBridgeLogger as m, applyQQBotAccountConfig as n, DEFAULT_ACCOUNT_ID$1 as o, getBridgeLogger as p, listQQBotAccountIds as r, applyAccountConfig as s, DEFAULT_ACCOUNT_ID as t, isAccountConfigured as u };
@@ -1,6 +1,5 @@
1
1
  import { n as normalizeLowercaseStringOrEmpty } from "./string-normalize-C46CS-F1.js";
2
- import { a as isAccountConfigured, i as formatAllowFrom, n as applyAccountConfig, r as describeAccount } from "./resolve-D3lCbUze.js";
3
- import { a as resolveQQBotAccount, i as resolveDefaultQQBotAccountId, n as applyQQBotAccountConfig, r as listQQBotAccountIds } from "./config-BT8KP_H1.js";
2
+ import { a as resolveQQBotAccount, c as describeAccount, i as resolveDefaultQQBotAccountId, l as formatAllowFrom, n as applyQQBotAccountConfig, r as listQQBotAccountIds, s as applyAccountConfig, u as isAccountConfigured } from "./config-DiBiPtf4.js";
4
3
  import { buildSecretInputSchema } from "openclaw/plugin-sdk/secret-input";
5
4
  import { applyAccountNameToChannelSection, deleteAccountFromConfigSection, setAccountEnabledInConfigSection } from "openclaw/plugin-sdk/core";
6
5
  import { DEFAULT_ACCOUNT_ID, createStandardChannelSetupStatus, setSetupChannelEnabled } from "openclaw/plugin-sdk/setup";
@@ -1,9 +1,102 @@
1
- import { a as resolveQQBotAccount, r as listQQBotAccountIds } from "./config-BT8KP_H1.js";
1
+ import { a as resolveQQBotAccount, r as listQQBotAccountIds } from "./config-DiBiPtf4.js";
2
2
  import { resolveApprovalRequestChannelAccountId } from "openclaw/plugin-sdk/approval-native-runtime";
3
3
  import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
4
4
  import { resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
5
5
  import { createChannelExecApprovalProfile, isChannelExecApprovalClientEnabledFromConfig, matchesApprovalRequestFilters } from "openclaw/plugin-sdk/approval-client-runtime";
6
6
  import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
7
+ //#region extensions/qqbot/src/engine/approval/index.ts
8
+ function buildExecApprovalText(request) {
9
+ const expiresIn = Math.max(0, Math.round((request.expiresAtMs - Date.now()) / 1e3));
10
+ const lines = ["🔐 命令执行审批", ""];
11
+ const cmd = request.request.commandPreview ?? request.request.command ?? "";
12
+ if (cmd) lines.push(`\`\`\`\n${cmd.slice(0, 300)}\n\`\`\``);
13
+ if (request.request.cwd) lines.push(`\u{1f4c1} \u76ee\u5f55: ${request.request.cwd}`);
14
+ if (request.request.agentId) lines.push(`\u{1f916} Agent: ${request.request.agentId}`);
15
+ lines.push("", `\u23f1\ufe0f \u8d85\u65f6: ${expiresIn} \u79d2`);
16
+ return lines.join("\n");
17
+ }
18
+ function buildPluginApprovalText(request) {
19
+ const timeoutSec = Math.round((request.request.timeoutMs ?? 12e4) / 1e3);
20
+ const lines = [`${request.request.severity === "critical" ? "🔴" : request.request.severity === "info" ? "🔵" : "🟡"} \u5ba1\u6279\u8bf7\u6c42`, ""];
21
+ lines.push(`\u{1f4cb} ${request.request.title}`);
22
+ if (request.request.description) lines.push(`\u{1f4dd} ${request.request.description}`);
23
+ if (request.request.toolName) lines.push(`\u{1f527} \u5de5\u5177: ${request.request.toolName}`);
24
+ if (request.request.pluginId) lines.push(`\u{1f50c} \u63d2\u4ef6: ${request.request.pluginId}`);
25
+ if (request.request.agentId) lines.push(`\u{1f916} Agent: ${request.request.agentId}`);
26
+ lines.push("", `\u23f1\ufe0f \u8d85\u65f6: ${timeoutSec} \u79d2`);
27
+ return lines.join("\n");
28
+ }
29
+ /**
30
+ * Build the three-button inline keyboard for approval messages.
31
+ *
32
+ * type=1 (Callback): click triggers INTERACTION_CREATE, button_data = data field.
33
+ * group_id "approval": clicking one button grays out the others (mutual exclusion).
34
+ * click_limit=1: each user can only click once.
35
+ * permission.type=2: all users can interact.
36
+ */
37
+ function buildApprovalKeyboard(approvalId, allowedDecisions = [
38
+ "allow-once",
39
+ "allow-always",
40
+ "deny"
41
+ ]) {
42
+ const makeBtn = (id, label, visitedLabel, data, style) => ({
43
+ id,
44
+ render_data: {
45
+ label,
46
+ visited_label: visitedLabel,
47
+ style
48
+ },
49
+ action: {
50
+ type: 1,
51
+ data,
52
+ permission: { type: 2 },
53
+ click_limit: 1
54
+ },
55
+ group_id: "approval"
56
+ });
57
+ const buttons = [];
58
+ if (allowedDecisions.includes("allow-once")) buttons.push(makeBtn("allow", "✅ 允许一次", "已允许", `approve:${approvalId}:allow-once`, 1));
59
+ if (allowedDecisions.includes("allow-always")) buttons.push(makeBtn("always", "⭐ 始终允许", "已始终允许", `approve:${approvalId}:allow-always`, 1));
60
+ if (allowedDecisions.includes("deny")) buttons.push(makeBtn("deny", "❌ 拒绝", "已拒绝", `approve:${approvalId}:deny`, 0));
61
+ return { content: { rows: [{ buttons }] } };
62
+ }
63
+ /**
64
+ * Extract the delivery target from a sessionKey or turnSourceTo string.
65
+ *
66
+ * Expected formats:
67
+ * agent:main:qqbot:direct:OPENID -> { type: "c2c", id: "OPENID" }
68
+ * agent:main:qqbot:c2c:OPENID -> { type: "c2c", id: "OPENID" }
69
+ * agent:main:qqbot:group:GROUPID -> { type: "group", id: "GROUPID" }
70
+ *
71
+ * Returns null if neither field matches the expected pattern.
72
+ */
73
+ function resolveApprovalTarget(sessionKey, turnSourceTo) {
74
+ const sk = sessionKey ?? turnSourceTo;
75
+ if (!sk) return null;
76
+ const m = sk.match(/qqbot:(c2c|direct|group):([A-F0-9]+)/i);
77
+ if (!m) return null;
78
+ return {
79
+ type: m[1].toLowerCase() === "group" ? "group" : "c2c",
80
+ id: m[2]
81
+ };
82
+ }
83
+ /**
84
+ * Parse the button_data string from an INTERACTION_CREATE event.
85
+ *
86
+ * Expected format: `approve:<approvalId>:<decision>`
87
+ * where approvalId may be prefixed with "exec:" or "plugin:".
88
+ *
89
+ * Returns null if the data does not match the approval button format.
90
+ */
91
+ function parseApprovalButtonData(buttonData) {
92
+ const m = buttonData.match(/^approve:((?:(?:exec|plugin):)?[0-9a-f-]+):(allow-once|allow-always|deny)$/i);
93
+ if (!m) return null;
94
+ return {
95
+ approvalId: m[1],
96
+ decision: m[2]
97
+ };
98
+ }
99
+ //#endregion
7
100
  //#region extensions/qqbot/src/exec-approvals.ts
8
101
  function normalizeApproverId(value) {
9
102
  return normalizeOptionalString(String(value)) || void 0;
@@ -134,5 +227,12 @@ const isQQBotExecApprovalClientEnabled = qqbotExecApprovalProfile.isClientEnable
134
227
  const isQQBotExecApprovalApprover = qqbotExecApprovalProfile.isApprover;
135
228
  const isQQBotExecApprovalAuthorizedSender = qqbotExecApprovalProfile.isAuthorizedSender;
136
229
  const shouldHandleQQBotExecApprovalRequest = qqbotExecApprovalProfile.shouldHandleRequest;
230
+ function authorizeQQBotApprovalAction(params) {
231
+ if (resolveQQBotExecApprovalConfig(params) === void 0) return { authorized: true };
232
+ return (params.approvalKind === "plugin" ? isQQBotExecApprovalApprover(params) : isQQBotExecApprovalAuthorizedSender(params)) ? { authorized: true } : {
233
+ authorized: false,
234
+ reason: "You are not authorized to approve this request."
235
+ };
236
+ }
137
237
  //#endregion
138
- export { resolveQQBotExecApprovalConfig as a, matchesQQBotApprovalAccount as i, isQQBotExecApprovalAuthorizedSender as n, shouldHandleQQBotExecApprovalRequest as o, isQQBotExecApprovalClientEnabled as r, isQQBotExecApprovalApprover as t };
238
+ export { shouldHandleQQBotExecApprovalRequest as a, buildPluginApprovalText as c, resolveQQBotExecApprovalConfig as i, parseApprovalButtonData as l, isQQBotExecApprovalClientEnabled as n, buildApprovalKeyboard as o, matchesQQBotApprovalAccount as r, buildExecApprovalText as s, authorizeQQBotApprovalAction as t, resolveApprovalTarget as u };
@@ -1,12 +1,12 @@
1
- import { E as setOutboundAudioPort, _ as sendVoice, c as isMediaPayload, g as sendVideoMsg, h as sendPhoto, l as parseQQBotPayload, m as sendDocument, o as encodePayloadForCron, r as sendMedia, s as isCronReminderPayload, u as normalizeMediaTags, v as resolveUserFacingMediaError, w as DEFAULT_MEDIA_SEND_ERROR } from "./outbound-BLWish89.js";
1
+ import { E as setOutboundAudioPort, _ as sendVoice, c as isMediaPayload, g as sendVideoMsg, h as sendPhoto, l as parseQQBotPayload, m as sendDocument, o as encodePayloadForCron, r as sendMedia, s as isCronReminderPayload, u as normalizeMediaTags, v as resolveUserFacingMediaError, w as DEFAULT_MEDIA_SEND_ERROR } from "./outbound-BdQEr78b.js";
2
2
  import { c as getPlatformAdapter, i as normalizeOptionalString, n as normalizeLowercaseStringOrEmpty, o as readStringField, s as sanitizeFileName, t as asOptionalObjectRecord } from "./string-normalize-C46CS-F1.js";
3
- import { d as setBridgeLogger, l as ensurePlatformAdapter, s as resolveAccountBase } from "./resolve-D3lCbUze.js";
4
- import { i as parseApprovalButtonData } from "./approval-cg0SVahb.js";
5
- import { t as toGatewayAccount } from "./narrowing-BoieBTIU.js";
6
- import { B as StreamInputState, C as debugWarn, D as openLocalFile, I as formatDuration, L as formatErrorMessage, N as getImageMimeType, P as getMaxUploadSize, R as StreamContentType, S as debugLog, T as getNextMsgSeq, a as createRawInputNotifyFn, b as withTokenRetry, c as getMessageApi, d as initSender, f as onMessageSent, g as sendText, h as sendMedia$1, i as clearTokenCache, j as formatFileSize, k as downloadFile, 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, v as startBackgroundTokenRefresh, x as debugError, y as stopBackgroundTokenRefresh, z as StreamInputMode } from "./sender-BrpzCcXA.js";
7
- import { n as getQQBotRuntimeForEngine, t as getQQBotRuntime } from "./runtime-CZyFkpnB.js";
8
- import { a as getHomeDir, c as getQQBotMediaDir, d as isLocalPath, f as isWindows, i as checkSilkWasmAvailable, m as resolveQQBotPayloadLocalFilePath, o as getQQBotDataDir, p as normalizePath$1, s as getQQBotDataPath, u as getTempDir } from "./target-parser-C8HaD-ik.js";
9
- import { a as getPluginVersion, i as getFrameworkVersion, n as runWithRequestContext, o as initCommands, s as matchSlashCommand } from "./request-context-BwSBFANU.js";
3
+ import { d as resolveAccountBase, f as ensurePlatformAdapter, m as setBridgeLogger } from "./config-DiBiPtf4.js";
4
+ import { l as parseApprovalButtonData, t as authorizeQQBotApprovalAction } from "./exec-approvals-Deww_EGt.js";
5
+ import { t as toGatewayAccount } from "./narrowing-Bj4eMufj.js";
6
+ import { B as StreamInputState, C as debugWarn, D as openLocalFile, I as formatDuration, L as formatErrorMessage, N as getImageMimeType, P as getMaxUploadSize, R as StreamContentType, S as debugLog, T as getNextMsgSeq, a as createRawInputNotifyFn, b as withTokenRetry, c as getMessageApi, d as initSender, f as onMessageSent, g as sendText, h as sendMedia$1, i as clearTokenCache, j as formatFileSize, k as downloadFile, 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, v as startBackgroundTokenRefresh, x as debugError, y as stopBackgroundTokenRefresh, z as StreamInputMode } from "./sender-BeE_CBbN.js";
7
+ import { n as getQQBotRuntimeForEngine, t as getQQBotRuntime } from "./runtime-B-cqLImu.js";
8
+ import { a as getHomeDir, c as getQQBotMediaDir, d as isLocalPath, f as isWindows, i as checkSilkWasmAvailable, m as resolveQQBotPayloadLocalFilePath, o as getQQBotDataDir, p as normalizePath$1, s as getQQBotDataPath, u as getTempDir } from "./target-parser-ByU3srDK.js";
9
+ import { a as getPluginVersion, i as getFrameworkVersion, n as runWithRequestContext, o as initCommands, s as matchSlashCommand } from "./request-context-Betw4nJr.js";
10
10
  import * as fs$1 from "node:fs";
11
11
  import fs from "node:fs";
12
12
  import * as os$1 from "node:os";
@@ -3026,18 +3026,6 @@ function resolveQQBotEffectivePolicies(input) {
3026
3026
  //#endregion
3027
3027
  //#region extensions/qqbot/src/engine/gateway/interaction-handler.ts
3028
3028
  /**
3029
- * INTERACTION_CREATE event handler.
3030
- *
3031
- * Handles three interaction branches:
3032
- *
3033
- * 1. **Config query** (type=2001) — reads config, ACKs with `claw_cfg`.
3034
- * 2. **Config update** (type=2002) — writes config, ACKs with updated snapshot.
3035
- * 3. **Approval button** (other) — ACKs, resolves approval via PlatformAdapter.
3036
- *
3037
- * Config query/update require `runtime.config`. When unavailable, those
3038
- * branches fall through to a bare ACK (backward-compatible).
3039
- */
3040
- /**
3041
3029
  * Build the canonical `claw_cfg` snapshot returned in interaction ACKs.
3042
3030
  *
3043
3031
  * Pure function — all resolution helpers live in engine/config/.
@@ -3123,7 +3111,7 @@ function applyRequireMentionUpdate(cfg, accountId, groupOpenid, update) {
3123
3111
  * Returns a fire-and-forget callback that `GatewayConnection` calls
3124
3112
  * on every `action: "interaction"` dispatch result.
3125
3113
  */
3126
- function createInteractionHandler(account, runtime, log) {
3114
+ function createInteractionHandler(account, runtime, log, options) {
3127
3115
  return (event) => {
3128
3116
  const creds = accountToCreds(account);
3129
3117
  const type = event.data?.type;
@@ -3137,22 +3125,102 @@ function createInteractionHandler(account, runtime, log) {
3137
3125
  handleWithAck(creds, event, log, "CONFIG_UPDATE", () => applyConfigUpdate(event, account.accountId, runtime, log));
3138
3126
  return;
3139
3127
  }
3140
- acknowledgeInteraction(creds, event.id).catch((err) => {
3141
- log?.error(`Interaction ACK failed: ${err instanceof Error ? err.message : String(err)}`);
3142
- });
3143
3128
  const parsed = parseApprovalButtonData(event.data?.resolved?.button_data ?? "");
3144
- if (!parsed) return;
3145
- const adapter = getPlatformAdapter();
3146
- if (!adapter.resolveApproval) {
3147
- log?.error("resolveApproval not available on PlatformAdapter");
3129
+ if (!parsed) {
3130
+ acknowledgeInteraction(creds, event.id).catch((err) => {
3131
+ log?.error(`Interaction ACK failed: ${err instanceof Error ? err.message : String(err)}`);
3132
+ });
3148
3133
  return;
3149
3134
  }
3150
- adapter.resolveApproval(parsed.approvalId, parsed.decision).then((ok) => {
3151
- if (ok) log?.info(`Approval resolved: id=${parsed.approvalId}, decision=${parsed.decision}`);
3152
- else log?.error(`Approval resolve failed: id=${parsed.approvalId}`);
3135
+ handleApprovalButtonInteraction({
3136
+ accountId: account.accountId,
3137
+ creds,
3138
+ event,
3139
+ getActiveCfg: options?.getActiveCfg ?? runtime.config?.current,
3140
+ log,
3141
+ parsed
3153
3142
  });
3154
3143
  };
3155
3144
  }
3145
+ async function handleApprovalButtonInteraction(params) {
3146
+ if (!params.getActiveCfg) {
3147
+ await acknowledgeApprovalInteraction(params.creds, params.event, params.log, { content: "Approval is unavailable." });
3148
+ params.log?.error("Approval button rejected: active config is unavailable");
3149
+ return;
3150
+ }
3151
+ let cfg;
3152
+ try {
3153
+ cfg = params.getActiveCfg();
3154
+ } catch (err) {
3155
+ await acknowledgeApprovalInteraction(params.creds, params.event, params.log, { content: "Approval is unavailable." });
3156
+ params.log?.error(`Approval button rejected: active config failed to load: ${err instanceof Error ? err.message : String(err)}`);
3157
+ return;
3158
+ }
3159
+ const authorization = authorizeApprovalButtonActor({
3160
+ cfg,
3161
+ accountId: params.accountId,
3162
+ event: params.event,
3163
+ approvalKind: resolveApprovalKind(params.parsed.approvalId)
3164
+ });
3165
+ if (!authorization.authorized) {
3166
+ await acknowledgeApprovalInteraction(params.creds, params.event, params.log, { content: authorization.reason ?? "You are not authorized to approve this request." });
3167
+ params.log?.info(`Approval button rejected: id=${params.parsed.approvalId}`);
3168
+ return;
3169
+ }
3170
+ await acknowledgeApprovalInteraction(params.creds, params.event, params.log);
3171
+ const adapter = getPlatformAdapter();
3172
+ if (!adapter.resolveApproval) {
3173
+ params.log?.error("resolveApproval not available on PlatformAdapter");
3174
+ return;
3175
+ }
3176
+ try {
3177
+ if (await adapter.resolveApproval(params.parsed.approvalId, params.parsed.decision)) params.log?.info(`Approval resolved: id=${params.parsed.approvalId}, decision=${params.parsed.decision}`);
3178
+ else params.log?.error(`Approval resolve failed: id=${params.parsed.approvalId}`);
3179
+ } catch (err) {
3180
+ params.log?.error(`Approval resolve failed: id=${params.parsed.approvalId}: ${err instanceof Error ? err.message : String(err)}`);
3181
+ }
3182
+ }
3183
+ async function acknowledgeApprovalInteraction(creds, event, log, data) {
3184
+ try {
3185
+ await acknowledgeInteraction(creds, event.id, 0, data);
3186
+ } catch (err) {
3187
+ log?.error(`Interaction ACK failed: ${err instanceof Error ? err.message : String(err)}`);
3188
+ }
3189
+ }
3190
+ function authorizeApprovalButtonActor(params) {
3191
+ const senderIds = resolveApprovalActorSenderIds(params.event);
3192
+ if (senderIds.length === 0) return authorizeQQBotApprovalAction({
3193
+ cfg: params.cfg,
3194
+ accountId: params.accountId,
3195
+ senderId: null,
3196
+ approvalKind: params.approvalKind
3197
+ });
3198
+ let denial;
3199
+ for (const senderId of senderIds) {
3200
+ const result = authorizeQQBotApprovalAction({
3201
+ cfg: params.cfg,
3202
+ accountId: params.accountId,
3203
+ senderId,
3204
+ approvalKind: params.approvalKind
3205
+ });
3206
+ if (result.authorized) return result;
3207
+ denial ??= result;
3208
+ }
3209
+ return denial ?? {
3210
+ authorized: false,
3211
+ reason: "You are not authorized to approve this request."
3212
+ };
3213
+ }
3214
+ function resolveApprovalActorSenderIds(event) {
3215
+ const ids = [event.group_member_openid, event.user_openid].flatMap((value) => {
3216
+ const normalized = typeof value === "string" ? value.trim() : "";
3217
+ return normalized ? [normalized] : [];
3218
+ });
3219
+ return Array.from(new Set(ids));
3220
+ }
3221
+ function resolveApprovalKind(approvalId) {
3222
+ return approvalId.toLowerCase().startsWith("plugin:") ? "plugin" : "exec";
3223
+ }
3156
3224
  /** Execute an async handler, ACK with the result, and handle errors. */
3157
3225
  async function handleWithAck(creds, event, log, label, handler) {
3158
3226
  try {
@@ -3267,7 +3335,7 @@ const IMAGE_PROBE_SSRF_POLICY = {};
3267
3335
  /**
3268
3336
  * Fetch image dimensions from a public URL using only the first 64 KB.
3269
3337
  *
3270
- * Uses {@link fetchRemoteMedia} with SSRF guard to block probes against
3338
+ * Uses {@link readRemoteMediaBuffer} with SSRF guard to block probes against
3271
3339
  * private/reserved/loopback/link-local/metadata destinations.
3272
3340
  */
3273
3341
  async function getImageSizeFromUrl(url, timeoutMs = 5e3) {
@@ -6069,7 +6137,7 @@ async function startGateway$1(ctx) {
6069
6137
  });
6070
6138
  }
6071
6139
  };
6072
- const handleInteraction = createInteractionHandler(account, ctx.runtime, log);
6140
+ const handleInteraction = createInteractionHandler(account, ctx.runtime, log, { getActiveCfg: () => activeCfgProvider.getActiveCfg() });
6073
6141
  await new GatewayConnection({
6074
6142
  account,
6075
6143
  abortSignal: ctx.abortSignal,
@@ -1,8 +1,6 @@
1
- import { l as ensurePlatformAdapter, u as getBridgeLogger } from "./resolve-D3lCbUze.js";
2
- import { a as resolveApprovalTarget, n as buildExecApprovalText, r as buildPluginApprovalText, t as buildApprovalKeyboard } from "./approval-cg0SVahb.js";
3
- import { a as resolveQQBotAccount } from "./config-BT8KP_H1.js";
4
- import { a as resolveQQBotExecApprovalConfig, i as matchesQQBotApprovalAccount, o as shouldHandleQQBotExecApprovalRequest, r as isQQBotExecApprovalClientEnabled } from "./exec-approvals-W4oL9R6C.js";
5
- import { c as getMessageApi, t as accountToCreds } from "./sender-BrpzCcXA.js";
1
+ import { a as resolveQQBotAccount, f as ensurePlatformAdapter, p as getBridgeLogger } from "./config-DiBiPtf4.js";
2
+ import { a as shouldHandleQQBotExecApprovalRequest, c as buildPluginApprovalText, i as resolveQQBotExecApprovalConfig, n as isQQBotExecApprovalClientEnabled, o as buildApprovalKeyboard, r as matchesQQBotApprovalAccount, s as buildExecApprovalText, u as resolveApprovalTarget } from "./exec-approvals-Deww_EGt.js";
3
+ import { c as getMessageApi, t as accountToCreds } from "./sender-BeE_CBbN.js";
6
4
  import { resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
7
5
  import { createChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
8
6
  //#region extensions/qqbot/src/bridge/approval/handler-runtime.ts
@@ -1,6 +1,6 @@
1
1
  import { i as normalizeOptionalString, n as normalizeLowercaseStringOrEmpty, s as sanitizeFileName } from "./string-normalize-C46CS-F1.js";
2
- import { A as fileExistsAsync, C as debugWarn, E as UPLOAD_PREPARE_FALLBACK_CODE, F as readFileAsync, L as formatErrorMessage, M as getFileTypeName, N as getImageMimeType, O as checkFileSize, P as getMaxUploadSize, S as debugLog, g as sendText$1, h as sendMedia$1, j as formatFileSize, k as downloadFile, t as accountToCreds, u as initApiConfig, w as UploadDailyLimitExceededError, x as debugError } from "./sender-BrpzCcXA.js";
3
- import { c as getQQBotMediaDir, d as isLocalPath, m as resolveQQBotPayloadLocalFilePath, o as getQQBotDataDir, p as normalizePath, r as parseTarget$1 } from "./target-parser-C8HaD-ik.js";
2
+ import { A as fileExistsAsync, C as debugWarn, E as UPLOAD_PREPARE_FALLBACK_CODE, F as readFileAsync, L as formatErrorMessage, M as getFileTypeName, N as getImageMimeType, O as checkFileSize, P as getMaxUploadSize, S as debugLog, g as sendText$1, h as sendMedia$1, j as formatFileSize, k as downloadFile, t as accountToCreds, u as initApiConfig, w as UploadDailyLimitExceededError, x as debugError } from "./sender-BeE_CBbN.js";
3
+ import { c as getQQBotMediaDir, d as isLocalPath, m as resolveQQBotPayloadLocalFilePath, o as getQQBotDataDir, p as normalizePath, r as parseTarget$1 } from "./target-parser-ByU3srDK.js";
4
4
  import { pathExistsSync, resolveLocalPathFromRootsSync } from "openclaw/plugin-sdk/security-runtime";
5
5
  import path from "node:path";
6
6
  import { getFileExtension } from "openclaw/plugin-sdk/media-mime";
@@ -878,6 +878,14 @@ const CRON_PREFIX = "QQBOT_CRON:";
878
878
  function formatErr(e) {
879
879
  return e instanceof Error ? e.message : String(e);
880
880
  }
881
+ function normalizeBase64ForCompare(value) {
882
+ return value.replace(/=+$/u, "").replace(/-/gu, "+").replace(/_/gu, "/");
883
+ }
884
+ function decodeStrictBase64Utf8(value) {
885
+ const buffer = Buffer.from(value, "base64");
886
+ if (normalizeBase64ForCompare(buffer.toString("base64")) !== normalizeBase64ForCompare(value)) throw new Error("Cron payload body is not valid base64");
887
+ return buffer.toString("utf-8");
888
+ }
881
889
  /** Parse model output that may start with the QQ Bot structured payload prefix. */
882
890
  function parseQQBotPayload(text) {
883
891
  const trimmedText = text.trim();
@@ -933,7 +941,7 @@ function decodeCronPayload(message) {
933
941
  error: "Cron payload body is empty"
934
942
  };
935
943
  try {
936
- const jsonString = Buffer.from(base64Content, "base64").toString("utf-8");
944
+ const jsonString = decodeStrictBase64Utf8(base64Content);
937
945
  const payload = JSON.parse(jsonString);
938
946
  if (payload.type !== "cron_reminder") return {
939
947
  isCronPayload: true,
@@ -1,5 +1,5 @@
1
- import { S as debugLog } from "./sender-BrpzCcXA.js";
2
- import { a as getHomeDir, f as isWindows, l as getQQBotMediaPath, o as getQQBotDataDir } from "./target-parser-C8HaD-ik.js";
1
+ import { S as debugLog } from "./sender-BeE_CBbN.js";
2
+ import { a as getHomeDir, f as isWindows, l as getQQBotMediaPath, o as getQQBotDataDir } from "./target-parser-ByU3srDK.js";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { loadJsonFile } from "openclaw/plugin-sdk/json-store";
@@ -1,4 +1,4 @@
1
- import { _ as setOpenClawVersion } from "./sender-BrpzCcXA.js";
1
+ import { _ as setOpenClawVersion } from "./sender-BeE_CBbN.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, getRuntime: getQQBotRuntime } = createPluginRuntimeStore({
@@ -1,2 +1,2 @@
1
- import { r as setQQBotRuntime, t as getQQBotRuntime } from "./runtime-CZyFkpnB.js";
1
+ import { r as setQQBotRuntime, t as getQQBotRuntime } from "./runtime-B-cqLImu.js";
2
2
  export { getQQBotRuntime, setQQBotRuntime };
@@ -1386,14 +1386,19 @@ var TokenManager = class {
1386
1386
  }
1387
1387
  const traceId = response.headers.get("x-tps-trace-id") ?? "";
1388
1388
  this.logger?.debug?.(`[qqbot:token:${appId}] <<< ${response.status}${traceId ? ` | TraceId: ${traceId}` : ""}`);
1389
+ let rawBody;
1390
+ try {
1391
+ rawBody = await response.text();
1392
+ } catch (err) {
1393
+ throw new Error(`Failed to read access_token response: ${formatErrorMessage(err)}`, { cause: err });
1394
+ }
1395
+ const logBody = rawBody.replace(/"access_token"\s*:\s*"[^"]+"/g, "\"access_token\": \"***\"");
1396
+ this.logger?.debug?.(`[qqbot:token:${appId}] <<< Body: ${logBody}`);
1389
1397
  let data;
1390
1398
  try {
1391
- const rawBody = await response.text();
1392
- const logBody = rawBody.replace(/"access_token"\s*:\s*"[^"]+"/g, "\"access_token\": \"***\"");
1393
- this.logger?.debug?.(`[qqbot:token:${appId}] <<< Body: ${logBody}`);
1394
1399
  data = JSON.parse(rawBody);
1395
- } catch (err) {
1396
- throw new Error(`Failed to parse access_token response: ${formatErrorMessage(err)}`, { cause: err });
1400
+ } catch {
1401
+ throw new Error("QQBot access_token response was malformed JSON");
1397
1402
  }
1398
1403
  if (!data.access_token) throw new Error(`Failed to get access_token: ${JSON.stringify(data)}`);
1399
1404
  const expiresAt = Date.now() + (data.expires_in ?? 7200) * 1e3;
@@ -1,2 +1,2 @@
1
- import { t as qqbotSetupPlugin } from "./channel.setup-D_GS95Pk.js";
1
+ import { t as qqbotSetupPlugin } from "./channel.setup-DWWV_GAa.js";
2
2
  export { qqbotSetupPlugin };
@@ -1,5 +1,5 @@
1
1
  import { c as getPlatformAdapter } from "./string-normalize-C46CS-F1.js";
2
- import { C as debugWarn, L as formatErrorMessage, S as debugLog } from "./sender-BrpzCcXA.js";
2
+ import { C as debugWarn, L as formatErrorMessage, S as debugLog } from "./sender-BeE_CBbN.js";
3
3
  import * as fs$1 from "node:fs";
4
4
  import * as os$1 from "node:os";
5
5
  import * as path$1 from "node:path";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/qqbot",
3
- "version": "2026.5.12-beta.8",
3
+ "version": "2026.5.14-beta.1",
4
4
  "private": false,
5
5
  "description": "OpenClaw QQ Bot channel plugin",
6
6
  "repository": {
@@ -21,7 +21,7 @@
21
21
  "openclaw": "workspace:*"
22
22
  },
23
23
  "peerDependencies": {
24
- "openclaw": ">=2026.5.12-beta.8"
24
+ "openclaw": ">=2026.5.14-beta.1"
25
25
  },
26
26
  "peerDependenciesMeta": {
27
27
  "openclaw": {
@@ -50,10 +50,10 @@
50
50
  "minHostVersion": ">=2026.4.10"
51
51
  },
52
52
  "compat": {
53
- "pluginApi": ">=2026.5.12-beta.8"
53
+ "pluginApi": ">=2026.5.14-beta.1"
54
54
  },
55
55
  "build": {
56
- "openclawVersion": "2026.5.12-beta.8"
56
+ "openclawVersion": "2026.5.14-beta.1"
57
57
  },
58
58
  "release": {
59
59
  "publishToClawHub": true,
@@ -1,94 +0,0 @@
1
- //#region extensions/qqbot/src/engine/approval/index.ts
2
- function buildExecApprovalText(request) {
3
- const expiresIn = Math.max(0, Math.round((request.expiresAtMs - Date.now()) / 1e3));
4
- const lines = ["🔐 命令执行审批", ""];
5
- const cmd = request.request.commandPreview ?? request.request.command ?? "";
6
- if (cmd) lines.push(`\`\`\`\n${cmd.slice(0, 300)}\n\`\`\``);
7
- if (request.request.cwd) lines.push(`\u{1f4c1} \u76ee\u5f55: ${request.request.cwd}`);
8
- if (request.request.agentId) lines.push(`\u{1f916} Agent: ${request.request.agentId}`);
9
- lines.push("", `\u23f1\ufe0f \u8d85\u65f6: ${expiresIn} \u79d2`);
10
- return lines.join("\n");
11
- }
12
- function buildPluginApprovalText(request) {
13
- const timeoutSec = Math.round((request.request.timeoutMs ?? 12e4) / 1e3);
14
- const lines = [`${request.request.severity === "critical" ? "🔴" : request.request.severity === "info" ? "🔵" : "🟡"} \u5ba1\u6279\u8bf7\u6c42`, ""];
15
- lines.push(`\u{1f4cb} ${request.request.title}`);
16
- if (request.request.description) lines.push(`\u{1f4dd} ${request.request.description}`);
17
- if (request.request.toolName) lines.push(`\u{1f527} \u5de5\u5177: ${request.request.toolName}`);
18
- if (request.request.pluginId) lines.push(`\u{1f50c} \u63d2\u4ef6: ${request.request.pluginId}`);
19
- if (request.request.agentId) lines.push(`\u{1f916} Agent: ${request.request.agentId}`);
20
- lines.push("", `\u23f1\ufe0f \u8d85\u65f6: ${timeoutSec} \u79d2`);
21
- return lines.join("\n");
22
- }
23
- /**
24
- * Build the three-button inline keyboard for approval messages.
25
- *
26
- * type=1 (Callback): click triggers INTERACTION_CREATE, button_data = data field.
27
- * group_id "approval": clicking one button grays out the others (mutual exclusion).
28
- * click_limit=1: each user can only click once.
29
- * permission.type=2: all users can interact.
30
- */
31
- function buildApprovalKeyboard(approvalId, allowedDecisions = [
32
- "allow-once",
33
- "allow-always",
34
- "deny"
35
- ]) {
36
- const makeBtn = (id, label, visitedLabel, data, style) => ({
37
- id,
38
- render_data: {
39
- label,
40
- visited_label: visitedLabel,
41
- style
42
- },
43
- action: {
44
- type: 1,
45
- data,
46
- permission: { type: 2 },
47
- click_limit: 1
48
- },
49
- group_id: "approval"
50
- });
51
- const buttons = [];
52
- if (allowedDecisions.includes("allow-once")) buttons.push(makeBtn("allow", "✅ 允许一次", "已允许", `approve:${approvalId}:allow-once`, 1));
53
- if (allowedDecisions.includes("allow-always")) buttons.push(makeBtn("always", "⭐ 始终允许", "已始终允许", `approve:${approvalId}:allow-always`, 1));
54
- if (allowedDecisions.includes("deny")) buttons.push(makeBtn("deny", "❌ 拒绝", "已拒绝", `approve:${approvalId}:deny`, 0));
55
- return { content: { rows: [{ buttons }] } };
56
- }
57
- /**
58
- * Extract the delivery target from a sessionKey or turnSourceTo string.
59
- *
60
- * Expected formats:
61
- * agent:main:qqbot:direct:OPENID -> { type: "c2c", id: "OPENID" }
62
- * agent:main:qqbot:c2c:OPENID -> { type: "c2c", id: "OPENID" }
63
- * agent:main:qqbot:group:GROUPID -> { type: "group", id: "GROUPID" }
64
- *
65
- * Returns null if neither field matches the expected pattern.
66
- */
67
- function resolveApprovalTarget(sessionKey, turnSourceTo) {
68
- const sk = sessionKey ?? turnSourceTo;
69
- if (!sk) return null;
70
- const m = sk.match(/qqbot:(c2c|direct|group):([A-F0-9]+)/i);
71
- if (!m) return null;
72
- return {
73
- type: m[1].toLowerCase() === "group" ? "group" : "c2c",
74
- id: m[2]
75
- };
76
- }
77
- /**
78
- * Parse the button_data string from an INTERACTION_CREATE event.
79
- *
80
- * Expected format: `approve:<approvalId>:<decision>`
81
- * where approvalId may be prefixed with "exec:" or "plugin:".
82
- *
83
- * Returns null if the data does not match the approval button format.
84
- */
85
- function parseApprovalButtonData(buttonData) {
86
- const m = buttonData.match(/^approve:((?:(?:exec|plugin):)?[0-9a-f-]+):(allow-once|allow-always|deny)$/i);
87
- if (!m) return null;
88
- return {
89
- approvalId: m[1],
90
- decision: m[2]
91
- };
92
- }
93
- //#endregion
94
- export { resolveApprovalTarget as a, parseApprovalButtonData as i, buildExecApprovalText as n, buildPluginApprovalText as r, buildApprovalKeyboard as t };
@@ -1,94 +0,0 @@
1
- import { c as getPlatformAdapter } from "./string-normalize-C46CS-F1.js";
2
- import { c as resolveDefaultAccountId, n as applyAccountConfig, o as listAccountIds, s as resolveAccountBase, t as DEFAULT_ACCOUNT_ID$1 } from "./resolve-D3lCbUze.js";
3
- import { coerceSecretRef, normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
4
- import fs from "node:fs";
5
- import { resolveDefaultSecretProviderAlias } from "openclaw/plugin-sdk/provider-auth";
6
- //#region extensions/qqbot/src/bridge/config.ts
7
- const DEFAULT_ACCOUNT_ID = DEFAULT_ACCOUNT_ID$1;
8
- function assertNotLegacySecretRefMarker(value, path) {
9
- const normalized = normalizeSecretInputString(value);
10
- if (!normalized || !/^secretref(?:-env)?:/i.test(normalized)) return;
11
- throw new Error(`${path}: legacy SecretRef marker strings are not valid QQ Bot clientSecret values; use a structured SecretRef object instead.`);
12
- }
13
- function resolveEnvSecretRefValue(params) {
14
- const ref = coerceSecretRef(params.value, params.cfg.secrets?.defaults);
15
- if (!ref || ref.source !== "env") return;
16
- const providerConfig = params.cfg.secrets?.providers?.[ref.provider];
17
- if (providerConfig) {
18
- if (providerConfig.source !== "env") throw new Error(`Secret provider "${ref.provider}" has source "${providerConfig.source}" but ref requests "env".`);
19
- if (providerConfig.allowlist && !providerConfig.allowlist.includes(ref.id)) throw new Error(`Environment variable "${ref.id}" is not allowlisted in secrets.providers.${ref.provider}.allowlist.`);
20
- } else if (ref.provider !== resolveDefaultSecretProviderAlias(params.cfg, "env")) throw new Error(`Secret provider "${ref.provider}" is not configured (ref: env:${ref.provider}:${ref.id}).`);
21
- return normalizeSecretInputString((params.env ?? process.env)[ref.id]);
22
- }
23
- function resolveQQBotClientSecretInput(params) {
24
- assertNotLegacySecretRefMarker(params.value, params.path);
25
- const envSecret = resolveEnvSecretRefValue({
26
- cfg: params.cfg,
27
- value: params.value
28
- });
29
- if (envSecret) return envSecret;
30
- return getPlatformAdapter().resolveSecretInputString({
31
- value: params.value,
32
- path: params.path
33
- });
34
- }
35
- /** List all configured QQBot account IDs. */
36
- function listQQBotAccountIds(cfg) {
37
- return listAccountIds(cfg);
38
- }
39
- /** Resolve the default QQBot account ID. */
40
- function resolveDefaultQQBotAccountId(cfg) {
41
- return resolveDefaultAccountId(cfg);
42
- }
43
- /** Resolve QQBot account config for runtime or setup flows. */
44
- function resolveQQBotAccount(cfg, accountId, opts) {
45
- const base = resolveAccountBase(cfg, accountId);
46
- const qqbot = cfg.channels?.qqbot;
47
- /**
48
- * Legacy top-level account uses `channels.qqbot` as the base, but per-account
49
- * fields (allowFrom, streaming, …) often live under `accounts.default`.
50
- * Merge that slice so runtime sees `config.streaming` etc.
51
- */
52
- const accountConfig = base.accountId === DEFAULT_ACCOUNT_ID ? {
53
- ...qqbot,
54
- ...qqbot?.accounts?.[DEFAULT_ACCOUNT_ID]
55
- } : qqbot?.accounts?.[base.accountId] ?? {};
56
- let clientSecret = "";
57
- let secretSource = "none";
58
- const clientSecretPath = base.accountId === DEFAULT_ACCOUNT_ID ? "channels.qqbot.clientSecret" : `channels.qqbot.accounts.${base.accountId}.clientSecret`;
59
- const adapter = getPlatformAdapter();
60
- if (adapter.hasConfiguredSecret(accountConfig.clientSecret)) {
61
- clientSecret = opts?.allowUnresolvedSecretRef ? adapter.normalizeSecretInputString(accountConfig.clientSecret) ?? "" : resolveQQBotClientSecretInput({
62
- cfg,
63
- value: accountConfig.clientSecret,
64
- path: clientSecretPath
65
- }) ?? "";
66
- secretSource = "config";
67
- } else if (accountConfig.clientSecretFile) try {
68
- clientSecret = fs.readFileSync(accountConfig.clientSecretFile, "utf8").trim();
69
- secretSource = "file";
70
- } catch {
71
- secretSource = "none";
72
- }
73
- else if (process.env.QQBOT_CLIENT_SECRET && base.accountId === DEFAULT_ACCOUNT_ID) {
74
- clientSecret = process.env.QQBOT_CLIENT_SECRET;
75
- secretSource = "env";
76
- }
77
- return {
78
- accountId: base.accountId,
79
- name: accountConfig.name,
80
- enabled: base.enabled,
81
- appId: base.appId,
82
- clientSecret,
83
- secretSource,
84
- systemPrompt: base.systemPrompt,
85
- markdownSupport: base.markdownSupport,
86
- config: accountConfig
87
- };
88
- }
89
- /** Apply account config updates back into the OpenClaw config object. */
90
- function applyQQBotAccountConfig(cfg, accountId, input) {
91
- return applyAccountConfig(cfg, accountId, input);
92
- }
93
- //#endregion
94
- export { resolveQQBotAccount as a, resolveDefaultQQBotAccountId as i, applyQQBotAccountConfig as n, listQQBotAccountIds as r, DEFAULT_ACCOUNT_ID as t };