@openclaw/qqbot 2026.6.5-beta.1 → 2026.6.5-beta.3

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.
@@ -0,0 +1,135 @@
1
+ import { asObjectRecord } from "openclaw/plugin-sdk/runtime-doctor";
2
+ //#region extensions/qqbot/src/doctor-contract.ts
3
+ const RESTRICTED_GROUP_TOOLS = { deny: [
4
+ "exec",
5
+ "read",
6
+ "write"
7
+ ] };
8
+ function hasLegacyGroupToolPolicy(value) {
9
+ const groups = asObjectRecord(value);
10
+ if (!groups) return false;
11
+ return Object.values(groups).some((group) => asObjectRecord(group)?.toolPolicy !== void 0);
12
+ }
13
+ function hasLegacyAccountGroupToolPolicy(value) {
14
+ const accounts = asObjectRecord(value);
15
+ if (!accounts) return false;
16
+ return Object.values(accounts).some((account) => hasLegacyGroupToolPolicy(asObjectRecord(account)?.groups));
17
+ }
18
+ function migrateToolPolicy(value) {
19
+ if (value === "none") return { deny: ["*"] };
20
+ if (value === "full") return { allow: [] };
21
+ if (value === "restricted") return { ...RESTRICTED_GROUP_TOOLS };
22
+ }
23
+ function describeToolPolicy(value) {
24
+ return typeof value === "string" ? value : String(value);
25
+ }
26
+ function migrateGroups(params) {
27
+ let changed = false;
28
+ const nextGroups = { ...params.groups };
29
+ for (const [groupId, rawGroup] of Object.entries(params.groups)) {
30
+ const group = asObjectRecord(rawGroup);
31
+ if (!group || group.toolPolicy === void 0) continue;
32
+ const { toolPolicy, ...rest } = group;
33
+ const nextGroup = { ...rest };
34
+ const policy = migrateToolPolicy(toolPolicy);
35
+ const path = `${params.pathPrefix}.${groupId}`;
36
+ if (nextGroup.tools !== void 0) params.changes.push(`Removed ${path}.toolPolicy (${path}.tools already exists).`);
37
+ else if (policy) {
38
+ nextGroup.tools = policy;
39
+ params.changes.push(`Moved ${path}.toolPolicy=${describeToolPolicy(toolPolicy)} to ${path}.tools.`);
40
+ } else params.changes.push(`Removed unsupported ${path}.toolPolicy=${describeToolPolicy(toolPolicy)}.`);
41
+ nextGroups[groupId] = nextGroup;
42
+ changed = true;
43
+ }
44
+ return {
45
+ groups: nextGroups,
46
+ changed
47
+ };
48
+ }
49
+ const legacyConfigRules = [{
50
+ path: [
51
+ "channels",
52
+ "qqbot",
53
+ "groups"
54
+ ],
55
+ message: "channels.qqbot.groups.<id>.toolPolicy is legacy and was ignored by QQBot group tool enforcement; use channels.qqbot.groups.<id>.tools instead. Run \"openclaw doctor --fix\".",
56
+ match: hasLegacyGroupToolPolicy
57
+ }, {
58
+ path: [
59
+ "channels",
60
+ "qqbot",
61
+ "accounts"
62
+ ],
63
+ message: "channels.qqbot.accounts.<id>.groups.<groupId>.toolPolicy is legacy and was ignored by QQBot group tool enforcement; use channels.qqbot.accounts.<id>.groups.<groupId>.tools instead. Run \"openclaw doctor --fix\".",
64
+ match: hasLegacyAccountGroupToolPolicy
65
+ }];
66
+ function normalizeCompatibilityConfig({ cfg }) {
67
+ const rawEntry = asObjectRecord(cfg.channels?.qqbot);
68
+ if (!rawEntry) return {
69
+ config: cfg,
70
+ changes: []
71
+ };
72
+ const changes = [];
73
+ let updated = rawEntry;
74
+ let changed = false;
75
+ const groups = asObjectRecord(updated.groups);
76
+ if (groups) {
77
+ const migrated = migrateGroups({
78
+ groups,
79
+ pathPrefix: "channels.qqbot.groups",
80
+ changes
81
+ });
82
+ if (migrated.changed) {
83
+ updated = {
84
+ ...updated,
85
+ groups: migrated.groups
86
+ };
87
+ changed = true;
88
+ }
89
+ }
90
+ const accounts = asObjectRecord(updated.accounts);
91
+ if (accounts) {
92
+ let accountsChanged = false;
93
+ const nextAccounts = { ...accounts };
94
+ for (const [accountId, rawAccount] of Object.entries(accounts)) {
95
+ const account = asObjectRecord(rawAccount);
96
+ const accountGroups = asObjectRecord(account?.groups);
97
+ if (!account || !accountGroups) continue;
98
+ const migrated = migrateGroups({
99
+ groups: accountGroups,
100
+ pathPrefix: `channels.qqbot.accounts.${accountId}.groups`,
101
+ changes
102
+ });
103
+ if (migrated.changed) {
104
+ nextAccounts[accountId] = {
105
+ ...account,
106
+ groups: migrated.groups
107
+ };
108
+ accountsChanged = true;
109
+ }
110
+ }
111
+ if (accountsChanged) {
112
+ updated = {
113
+ ...updated,
114
+ accounts: nextAccounts
115
+ };
116
+ changed = true;
117
+ }
118
+ }
119
+ if (!changed) return {
120
+ config: cfg,
121
+ changes: []
122
+ };
123
+ return {
124
+ config: {
125
+ ...cfg,
126
+ channels: {
127
+ ...cfg.channels,
128
+ qqbot: updated
129
+ }
130
+ },
131
+ changes
132
+ };
133
+ }
134
+ //#endregion
135
+ export { normalizeCompatibilityConfig as n, legacyConfigRules as t };
@@ -0,0 +1,2 @@
1
+ import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-BYTS8-ia.js";
2
+ export { legacyConfigRules, normalizeCompatibilityConfig };
@@ -1,9 +1,14 @@
1
- import { D as setOutboundAudioPort, T as DEFAULT_MEDIA_SEND_ERROR, _ as sendVideoMsg, c as isCronReminderPayload, d as normalizeMediaTags, g as sendPhoto, h as sendDocument, l as isMediaPayload, o as decodeMediaPath, r as sendMedia, s as encodePayloadForCron, u as parseQQBotPayload, v as sendVoice, y as resolveUserFacingMediaError } from "./outbound-CEqyriPT.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-DIMG7jHz.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
- import { f as resolveAccountBase, h as setBridgeLogger, p as ensurePlatformAdapter } from "./config-schema-BI7AYP6Q.js";
4
- import { T as parseApprovalButtonData, _ as authorizeQQBotApprovalAction, a as checkSilkWasmAvailable, c as getQQBotDataPath, d as getTempDir, f as isLocalPath, g as toGatewayAccount, h as resolveQQBotPayloadLocalFilePath, i as openQQBotSyncKeyedStore, l as getQQBotMediaDir, m as normalizePath, o as getHomeDir, p as isWindows, r as buildQQBotStateKey, s as getQQBotDataDir } from "./channel-BLMrGYfg.js";
5
- import { B as StreamContentType, C as debugError, D as getNextMsgSeq, F as getImageMimeType, H as StreamInputState, I as getMaxUploadSize, N as formatFileSize, R as formatDuration, S as withTokenRetry, T as debugWarn, V as StreamInputMode, _ as sendInputNotify, a as acknowledgeInteraction, b as startBackgroundTokenRefresh, c as createRawInputNotifyFn, d as getMessageApi, f as getPluginUserAgent, g as registerAccount, h as onMessageSent, i as accountToCreds, j as downloadFile, k as openLocalFile, l as getAccessToken, m as initSender, n as getQQBotRuntimeForEngine, o as buildDeliveryTarget, p as initApiConfig, s as clearTokenCache, t as getQQBotRuntime, u as getGatewayUrl, v as sendMedia$1, w as debugLog, x as stopBackgroundTokenRefresh, y as sendText, z as formatErrorMessage } from "./runtime-BSgtMZ6G.js";
6
- import { a as getPluginVersion, i as getFrameworkVersion, n as runWithRequestContext, o as initCommands, s as matchSlashCommand } from "./request-context-DsuA124s.js";
3
+ import { c as setBridgeLogger, o as ensurePlatformAdapter } from "./config-schema-BFLNZ8Nf.js";
4
+ import { a as authorizeQQBotApprovalAction, i as toGatewayAccount, n as buildQQBotStateKey, p as parseApprovalButtonData, r as openQQBotSyncKeyedStore } from "./channel-CcN43bPL.js";
5
+ import { d as resolveAccountBase } from "./config-ZEfgeoL4.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-B9UoQ5NI.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, s as getQQBotDataPath, u as getTempDir } from "./target-parser-BdCUmxK7.js";
9
+ import { a as matchSlashCommand, i as initCommands, n as getFrameworkVersion, r as getPluginVersion } from "./slash-commands-impl-FRw-Dl44.js";
10
+ import { n as runWithRequestContext } from "./request-context-Bm7PTBD1.js";
11
+ import { D as setOutboundAudioPort, T as DEFAULT_MEDIA_SEND_ERROR, _ as sendVideoMsg, c as isCronReminderPayload, d as normalizeMediaTags, g as sendPhoto, h as sendDocument, l as isMediaPayload, o as decodeMediaPath, r as sendMedia$1, s as encodePayloadForCron, u as parseQQBotPayload, v as sendVoice, y as resolveUserFacingMediaError } from "./outbound-C9wV892v.js";
7
12
  import { asBoolean, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
8
13
  import { isImplicitSameChatApprovalAuthorization } from "openclaw/plugin-sdk/approval-auth-runtime";
9
14
  import * as fs$1 from "node:fs";
@@ -17,6 +22,7 @@ import path from "node:path";
17
22
  import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
18
23
  import { MAX_TIMER_TIMEOUT_MS, finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
19
24
  import { resolveRuntimeServiceVersion } from "openclaw/plugin-sdk/cli-runtime";
25
+ import { loadSessionStore } from "openclaw/plugin-sdk/session-store-runtime";
20
26
  import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";
21
27
  import WebSocket from "ws";
22
28
  import { resolveAmbientNodeProxyAgent } from "openclaw/plugin-sdk/extension-shared";
@@ -60,10 +66,7 @@ function resolveSessionStorePath(cfg, agentId) {
60
66
  function createNodeSessionStoreReader() {
61
67
  return { read: ({ cfg, agentId }) => {
62
68
  try {
63
- const storePath = resolveSessionStorePath(cfg, agentId);
64
- if (!fs.existsSync(storePath)) return null;
65
- const raw = fs.readFileSync(storePath, "utf-8");
66
- return JSON.parse(raw);
69
+ return loadSessionStore(resolveSessionStorePath(cfg, agentId), { skipCache: true });
67
70
  } catch {
68
71
  return null;
69
72
  }
@@ -2564,7 +2567,6 @@ function classifyMedia(processed) {
2564
2567
  const DEFAULT_GROUP_CONFIG = {
2565
2568
  requireMention: true,
2566
2569
  ignoreOtherMentions: false,
2567
- toolPolicy: "restricted",
2568
2570
  name: "",
2569
2571
  historyLimit: 50
2570
2572
  };
@@ -2585,10 +2587,6 @@ function readString(obj, key) {
2585
2587
  const v = obj[key];
2586
2588
  return typeof v === "string" && v.length > 0 ? v : void 0;
2587
2589
  }
2588
- function readToolPolicy(obj, key) {
2589
- const v = obj[key];
2590
- return v === "full" || v === "restricted" || v === "none" ? v : void 0;
2591
- }
2592
2590
  function readHistoryLimit(obj, key) {
2593
2591
  const v = obj[key];
2594
2592
  if (typeof v !== "number" || !Number.isFinite(v)) return;
@@ -2601,7 +2599,6 @@ function resolveGroupConfig(cfg, groupOpenid, accountId) {
2601
2599
  return {
2602
2600
  requireMention: readBoolean(specific, "requireMention") ?? readBoolean(wildcard, "requireMention") ?? DEFAULT_GROUP_CONFIG.requireMention,
2603
2601
  ignoreOtherMentions: readBoolean(specific, "ignoreOtherMentions") ?? readBoolean(wildcard, "ignoreOtherMentions") ?? DEFAULT_GROUP_CONFIG.ignoreOtherMentions,
2604
- toolPolicy: readToolPolicy(specific, "toolPolicy") ?? readToolPolicy(wildcard, "toolPolicy") ?? DEFAULT_GROUP_CONFIG.toolPolicy,
2605
2602
  name: readString(specific, "name") ?? readString(wildcard, "name") ?? DEFAULT_GROUP_CONFIG.name,
2606
2603
  prompt: readString(specific, "prompt") ?? readString(wildcard, "prompt"),
2607
2604
  historyLimit: readHistoryLimit(specific, "historyLimit") ?? readHistoryLimit(wildcard, "historyLimit") ?? DEFAULT_GROUP_CONFIG.historyLimit
@@ -3828,7 +3825,7 @@ async function sendMarkdownReply(textWithoutImages, imageUrls, mdMatches, bareUr
3828
3825
  const target = buildDeliveryTarget(event);
3829
3826
  const creds = accountToCreds(account);
3830
3827
  if (target.type === "c2c" || target.type === "group") await withTokenRetry(creds, async () => {
3831
- await sendMedia$1({
3828
+ await sendMedia({
3832
3829
  target,
3833
3830
  creds,
3834
3831
  kind: "image",
@@ -4099,7 +4096,7 @@ async function handleImagePayload(ctx, payload) {
4099
4096
  const deliveryTarget = buildDeliveryTarget(target);
4100
4097
  const creds = accountToCreds(account);
4101
4098
  await withTokenRetry(creds, async () => {
4102
- if (deliveryTarget.type === "c2c" || deliveryTarget.type === "group") await sendMedia$1({
4099
+ if (deliveryTarget.type === "c2c" || deliveryTarget.type === "group") await sendMedia({
4103
4100
  target: deliveryTarget,
4104
4101
  creds,
4105
4102
  kind: "image",
@@ -4154,7 +4151,7 @@ async function sendTextAsVoiceReply(ctx, text, deps) {
4154
4151
  const deliveryTarget = buildDeliveryTarget(target);
4155
4152
  const creds = accountToCreds(account);
4156
4153
  await withTokenRetry(creds, async () => {
4157
- if (deliveryTarget.type === "c2c" || deliveryTarget.type === "group") await sendMedia$1({
4154
+ if (deliveryTarget.type === "c2c" || deliveryTarget.type === "group") await sendMedia({
4158
4155
  target: deliveryTarget,
4159
4156
  creds,
4160
4157
  kind: "voice",
@@ -4190,7 +4187,7 @@ async function handleVideoPayload(ctx, payload) {
4190
4187
  return;
4191
4188
  }
4192
4189
  await withTokenRetry(creds, async () => {
4193
- if (isHttpUrl) await sendMedia$1({
4190
+ if (isHttpUrl) await sendMedia({
4194
4191
  target: deliveryTarget,
4195
4192
  creds,
4196
4193
  kind: "video",
@@ -4200,7 +4197,7 @@ async function handleVideoPayload(ctx, payload) {
4200
4197
  else {
4201
4198
  const size = await assertLocalFileWithinTypeLimit(videoPath, 2);
4202
4199
  log?.debug?.(`Video local (${formatFileSize(size)}): ${describeMediaTargetForLog(videoPath, false)}`);
4203
- await sendMedia$1({
4200
+ await sendMedia({
4204
4201
  target: deliveryTarget,
4205
4202
  creds,
4206
4203
  kind: "video",
@@ -4232,7 +4229,7 @@ async function handleFilePayload(ctx, payload) {
4232
4229
  return;
4233
4230
  }
4234
4231
  await withTokenRetry(creds, async () => {
4235
- if (isHttpUrl) await sendMedia$1({
4232
+ if (isHttpUrl) await sendMedia({
4236
4233
  target: deliveryTarget,
4237
4234
  creds,
4238
4235
  kind: "file",
@@ -4243,7 +4240,7 @@ async function handleFilePayload(ctx, payload) {
4243
4240
  else {
4244
4241
  const size = await assertLocalFileWithinTypeLimit(filePath, 4);
4245
4242
  log?.debug?.(`File local (${formatFileSize(size)}): ${describeMediaTargetForLog(filePath, false)}`);
4246
- await sendMedia$1({
4243
+ await sendMedia({
4247
4244
  target: deliveryTarget,
4248
4245
  creds,
4249
4246
  kind: "file",
@@ -4446,7 +4443,7 @@ async function executeSendQueue(queue, ctx, options = {}) {
4446
4443
  await sendFallbackText(resolveUserFacingMediaError(result));
4447
4444
  }
4448
4445
  } else if (item.type === "media") {
4449
- const result = await sendMedia({
4446
+ const result = await sendMedia$1({
4450
4447
  to: qualifiedTarget,
4451
4448
  text: "",
4452
4449
  mediaUrl: item.content,
@@ -5774,7 +5771,7 @@ async function dispatchOutbound(inbound, deps) {
5774
5771
  for (const mediaUrl of toolMediaUrls) {
5775
5772
  const ac = new AbortController();
5776
5773
  try {
5777
- const result = await Promise.race([sendMedia({
5774
+ const result = await Promise.race([sendMedia$1({
5778
5775
  to: qualifiedTarget,
5779
5776
  text: "",
5780
5777
  mediaUrl,
@@ -5833,7 +5830,7 @@ async function dispatchOutbound(inbound, deps) {
5833
5830
  sendVoice: (target, voicePath, uploadFormats, transcodeEnabled) => sendVoice(target, voicePath, uploadFormats, transcodeEnabled),
5834
5831
  sendVideoMsg: (target, videoPath) => sendVideoMsg(target, videoPath),
5835
5832
  sendDocument: (target, filePath) => sendDocument(target, filePath),
5836
- sendMedia: (opts) => sendMedia(opts)
5833
+ sendMedia: (opts) => sendMedia$1(opts)
5837
5834
  },
5838
5835
  chunkText: (text, limit) => runtime.channel.text.chunkMarkdownText(text, limit)
5839
5836
  };
@@ -5929,7 +5926,7 @@ async function dispatchOutbound(inbound, deps) {
5929
5926
  const urlsToSend = [...toolMediaUrls];
5930
5927
  toolMediaUrls.length = 0;
5931
5928
  for (const mediaUrl of urlsToSend) try {
5932
- await sendMedia({
5929
+ await sendMedia$1({
5933
5930
  to: qualifiedTarget,
5934
5931
  text: "",
5935
5932
  mediaUrl,
@@ -1,6 +1,7 @@
1
- import { m as getBridgeLogger, p as ensurePlatformAdapter, u as resolveQQBotAccount } from "./config-schema-BI7AYP6Q.js";
2
- import { C as buildExecApprovalText, E as resolveApprovalTarget, S as buildApprovalKeyboard, b as resolveQQBotExecApprovalConfig, v as isQQBotExecApprovalClientEnabled, w as buildPluginApprovalText, x as shouldHandleQQBotExecApprovalRequest, y as matchesQQBotApprovalAccount } from "./channel-BLMrGYfg.js";
3
- import { d as getMessageApi, i as accountToCreds } from "./runtime-BSgtMZ6G.js";
1
+ import { c as getMessageApi, t as accountToCreds } from "./sender-DIMG7jHz.js";
2
+ import { o as ensurePlatformAdapter, s as getBridgeLogger } from "./config-schema-BFLNZ8Nf.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-CcN43bPL.js";
4
+ import { a as resolveQQBotAccount } from "./config-ZEfgeoL4.js";
4
5
  import { resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
5
6
  import { createChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
6
7
  //#region extensions/qqbot/src/bridge/approval/handler-runtime.ts
package/dist/index.js CHANGED
@@ -1,8 +1,15 @@
1
1
  import { defineBundledChannelEntry, loadBundledEntryExportSync } from "openclaw/plugin-sdk/channel-entry-contract";
2
2
  //#region extensions/qqbot/index.ts
3
3
  function registerQQBotFull(api) {
4
+ if (api.registrationMode === "tool-discovery") {
5
+ loadBundledEntryExportSync(import.meta.url, {
6
+ specifier: "./tools-api.js",
7
+ exportName: "registerQQBotTools"
8
+ })(api);
9
+ return;
10
+ }
4
11
  loadBundledEntryExportSync(import.meta.url, {
5
- specifier: "./api.js",
12
+ specifier: "./channel-entry-api.js",
6
13
  exportName: "registerQQBotFull"
7
14
  })(api);
8
15
  }
@@ -0,0 +1,107 @@
1
+ //#region extensions/qqbot/src/engine/utils/format.ts
2
+ /**
3
+ * General formatting and string utilities.
4
+ * 通用格式化与字符串工具。
5
+ *
6
+ * Pure utility functions with zero external dependencies.
7
+ * Replaces `openclaw/plugin-sdk/error-runtime` and `text-runtime`
8
+ * helpers for use inside engine/.
9
+ *
10
+ * NOTE: The framework `formatErrorMessage` also applies `redactSensitiveText()`
11
+ * for token masking. We intentionally omit that here — the framework's log
12
+ * pipeline handles redaction at a higher level.
13
+ */
14
+ /**
15
+ * Format any error object into a readable string.
16
+ * 将任意错误对象格式化为可读字符串。
17
+ *
18
+ * Traverses the `.cause` chain for nested Error objects to include
19
+ * the full error context (e.g. network errors wrapped inside HTTP errors).
20
+ */
21
+ function formatErrorMessage(err) {
22
+ if (err instanceof Error) {
23
+ let formatted = err.message || err.name || "Error";
24
+ let cause = err.cause;
25
+ const seen = new Set([err]);
26
+ while (cause && !seen.has(cause)) {
27
+ seen.add(cause);
28
+ if (cause instanceof Error) {
29
+ if (cause.message) formatted += ` | ${cause.message}`;
30
+ cause = cause.cause;
31
+ } else if (typeof cause === "string") {
32
+ formatted += ` | ${cause}`;
33
+ break;
34
+ } else break;
35
+ }
36
+ return formatted;
37
+ }
38
+ if (typeof err === "string") return err;
39
+ if (err === null || err === void 0 || typeof err === "number" || typeof err === "boolean" || typeof err === "bigint") return String(err);
40
+ try {
41
+ return JSON.stringify(err);
42
+ } catch {
43
+ return Object.prototype.toString.call(err);
44
+ }
45
+ }
46
+ /** Format a millisecond duration into a human-readable string (e.g. "5m 30s"). */
47
+ function formatDuration(durationMs) {
48
+ const seconds = Math.round(durationMs / 1e3);
49
+ if (seconds < 60) return `${seconds}s`;
50
+ const minutes = Math.floor(seconds / 60);
51
+ const remainSeconds = seconds % 60;
52
+ return remainSeconds > 0 ? `${minutes}m ${remainSeconds}s` : `${minutes}m`;
53
+ }
54
+ //#endregion
55
+ //#region extensions/qqbot/src/engine/utils/log.ts
56
+ /**
57
+ * QQBot debug logging utilities.
58
+ * QQBot 调试日志工具。
59
+ *
60
+ * Only outputs when the QQBOT_DEBUG environment variable is set,
61
+ * preventing user message content from leaking in production logs.
62
+ *
63
+ * Self-contained within engine/ — no framework SDK dependency.
64
+ */
65
+ function isQqbotDebugEnabled() {
66
+ const value = process.env.QQBOT_DEBUG;
67
+ if (typeof value !== "string") return false;
68
+ switch (value.trim().toLowerCase()) {
69
+ case "1":
70
+ case "on":
71
+ case "true":
72
+ case "yes": return true;
73
+ default: return false;
74
+ }
75
+ }
76
+ const isDebug = () => isQqbotDebugEnabled();
77
+ const MAX_LOG_VALUE_CHARS = 4096;
78
+ function sanitizeDebugLogValue(value) {
79
+ let text;
80
+ if (typeof value === "string") text = value;
81
+ else if (value instanceof Error) text = value.stack || value.message;
82
+ else try {
83
+ text = JSON.stringify(value) ?? String(value);
84
+ } catch {
85
+ text = String(value);
86
+ }
87
+ const sanitized = text.replace(/\p{Cc}/gu, " ").replace(/\s+/g, " ").trim();
88
+ if (sanitized.length <= MAX_LOG_VALUE_CHARS) return sanitized;
89
+ return `${sanitized.slice(0, MAX_LOG_VALUE_CHARS)}...`;
90
+ }
91
+ function formatDebugLogArgs(args) {
92
+ return args.map(sanitizeDebugLogValue).join(" ");
93
+ }
94
+ /** Debug-level log; only outputs when QQBOT_DEBUG is enabled. */
95
+ function debugLog(...args) {
96
+ if (isDebug()) console.log(formatDebugLogArgs(args).replace(/\n|\r/g, ""));
97
+ }
98
+ /** Debug-level warning; only outputs when QQBOT_DEBUG is enabled. */
99
+ function debugWarn(...args) {
100
+ if (isDebug()) console.warn(formatDebugLogArgs(args).replace(/\n|\r/g, ""));
101
+ }
102
+ /** Debug-level error; only outputs when QQBOT_DEBUG is enabled. */
103
+ function debugError(...args) {
104
+ if (isDebug()) console.error(formatDebugLogArgs(args).replace(/\n|\r/g, ""));
105
+ }
106
+ //#endregion
107
+ export { formatErrorMessage as a, formatDuration as i, debugLog as n, debugWarn as r, debugError as t };
@@ -1,21 +1,10 @@
1
+ import { A as getFileTypeName, D as downloadFile, E as checkFileSize, L as __exportAll, M as getMaxUploadSize, N as readFileAsync, O as fileExistsAsync, S as UploadDailyLimitExceededError, g as sendText$1, h as sendMedia$1, j as getImageMimeType, k as formatFileSize, t as accountToCreds, u as initApiConfig, w as UPLOAD_PREPARE_FALLBACK_CODE } from "./sender-DIMG7jHz.js";
1
2
  import { i as normalizeOptionalString, n as normalizeLowercaseStringOrEmpty, s as sanitizeFileName } from "./string-normalize-R_0cKO7Q.js";
2
- import { f as isLocalPath, h as resolveQQBotPayloadLocalFilePath, l as getQQBotMediaDir, m as normalizePath$1, n as parseTarget$1, s as getQQBotDataDir } from "./channel-BLMrGYfg.js";
3
- import { A as checkFileSize, C as debugError, E as UploadDailyLimitExceededError, F as getImageMimeType, I as getMaxUploadSize, L as readFileAsync, M as fileExistsAsync, N as formatFileSize, O as UPLOAD_PREPARE_FALLBACK_CODE, P as getFileTypeName, T as debugWarn, i as accountToCreds, j as downloadFile, p as initApiConfig, v as sendMedia$1, w as debugLog, y as sendText$1, z as formatErrorMessage } from "./runtime-BSgtMZ6G.js";
3
+ import { a as formatErrorMessage, n as debugLog, r as debugWarn, t as debugError } from "./log-SDfMMBWe.js";
4
+ import { c as getQQBotMediaDir, d as isLocalPath, m as resolveQQBotPayloadLocalFilePath, o as getQQBotDataDir, p as normalizePath$1, r as parseTarget$1 } from "./target-parser-BdCUmxK7.js";
4
5
  import { pathExistsSync, resolveLocalPathFromRootsSync } from "openclaw/plugin-sdk/security-runtime";
5
6
  import path from "node:path";
6
7
  import { getFileExtension } from "openclaw/plugin-sdk/media-mime";
7
- //#region \0rolldown/runtime.js
8
- var __defProp = Object.defineProperty;
9
- var __exportAll = (all, no_symbols) => {
10
- let target = {};
11
- for (var name in all) __defProp(target, name, {
12
- get: all[name],
13
- enumerable: true
14
- });
15
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
16
- return target;
17
- };
18
- //#endregion
19
8
  //#region extensions/qqbot/src/engine/messaging/outbound-audio-port.ts
20
9
  let outboundAudioPort = null;
21
10
  /**
@@ -0,0 +1,43 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ //#region extensions/qqbot/src/engine/utils/request-context.ts
3
+ /**
4
+ * Request-level context using AsyncLocalStorage.
5
+ *
6
+ * Provides ambient context (accountId, target openid, chat type, etc.)
7
+ * throughout the request lifecycle without explicit parameter threading.
8
+ *
9
+ * Gateway establishes the scope around each inbound message via
10
+ * `runWithRequestContext()`; any async code within that scope (including
11
+ * AI agent calls and tool `execute` callbacks) can retrieve the current
12
+ * request via `getRequestContext()` without racing with concurrent
13
+ * inbound messages.
14
+ *
15
+ * This is a pure Node.js module with zero framework dependencies,
16
+ * making it trivially portable between the built-in and standalone
17
+ * versions of QQBot.
18
+ */
19
+ const store = new AsyncLocalStorage();
20
+ /**
21
+ * Execute an async function with request-scoped context.
22
+ *
23
+ * All code running within `fn` (including nested async calls) can
24
+ * retrieve the context via `getRequestContext()`.
25
+ *
26
+ * @param ctx - The context to attach to this request.
27
+ * @param fn - The async function to run within the context.
28
+ * @returns The return value of `fn`.
29
+ */
30
+ function runWithRequestContext(ctx, fn) {
31
+ return store.run(ctx, fn);
32
+ }
33
+ /**
34
+ * Retrieve the current request context.
35
+ *
36
+ * Returns `undefined` when called outside of a `runWithRequestContext`
37
+ * scope.
38
+ */
39
+ function getRequestContext() {
40
+ return store.getStore();
41
+ }
42
+ //#endregion
43
+ export { runWithRequestContext as n, getRequestContext as t };
@@ -0,0 +1,18 @@
1
+ import { v as setOpenClawVersion } from "./sender-DIMG7jHz.js";
2
+ import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
3
+ //#region extensions/qqbot/src/bridge/runtime.ts
4
+ const { setRuntime: _setRuntime, clearRuntime: resetQQBotRuntimeForTest, getRuntime: getQQBotRuntime } = createPluginRuntimeStore({
5
+ pluginId: "qqbot",
6
+ errorMessage: "QQBot runtime not initialized"
7
+ });
8
+ /** Set the QQBot runtime and inject the framework version into the User-Agent. */
9
+ function setQQBotRuntime(runtime) {
10
+ _setRuntime(runtime);
11
+ setOpenClawVersion(runtime.version);
12
+ }
13
+ /** Type-narrowed getter for engine/ modules that need GatewayPluginRuntime. */
14
+ function getQQBotRuntimeForEngine() {
15
+ return getQQBotRuntime();
16
+ }
17
+ //#endregion
18
+ export { getQQBotRuntimeForEngine as n, setQQBotRuntime as r, getQQBotRuntime as t };
@@ -1,2 +1,2 @@
1
- import { r as setQQBotRuntime, t as getQQBotRuntime } from "./runtime-BSgtMZ6G.js";
1
+ import { r as setQQBotRuntime, t as getQQBotRuntime } from "./runtime-B9UoQ5NI.js";
2
2
  export { getQQBotRuntime, setQQBotRuntime };