@openclaw/qqbot 2026.6.11-beta.2 → 2026.7.1-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.
@@ -1,23 +1,23 @@
1
- import { C as getNextMsgSeq, D as downloadFile, F as StreamInputMode, I as StreamInputState, M as getMaxUploadSize, P as StreamContentType, T as openLocalFile, a as createRawInputNotifyFn, b as stopBackgroundTokenRefresh, c as getMessageApi, d as initSender, f as onMessageSent, g as sendText, h as sendMedia, i as clearTokenCache, j as getImageMimeType, k as formatFileSize, l as getPluginUserAgent, m as sendInputNotify, n as acknowledgeInteraction, o as getAccessToken, p as registerAccount, r as buildDeliveryTarget, s as getGatewayUrl, t as accountToCreds, u as initApiConfig, x as withTokenRetry, y as startBackgroundTokenRefresh } from "./sender-CRZ2af7P.js";
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-BHWpE_yH.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-gMW9zKvQ.js";
4
- import { a as toGatewayAccount, i as openQQBotSyncKeyedStore, m as parseApprovalButtonData, n as createQQBotMarkdownChunker, o as authorizeQQBotApprovalAction, r as buildQQBotStateKey } from "./channel-DvWrqcZB.js";
4
+ import { a as authorizeQQBotApprovalAction, i as toGatewayAccount, n as createQQBotMarkdownChunker, p as parseApprovalButtonData, r as openQQBotSyncKeyedStore } from "./channel-Bh0MrYvU.js";
5
5
  import { d as resolveAccountBase } from "./config-C1qZbh0K.js";
6
6
  import { a as formatErrorMessage, i as formatDuration, n as debugLog, r as debugWarn, t as debugError } from "./log-SDfMMBWe.js";
7
- import { n as getQQBotRuntimeForEngine, t as getQQBotRuntime } from "./runtime-D1hn91-s.js";
8
- import { a as getHomeDir, c as getQQBotMediaDir, d as isLocalPath, f as isWindows, i as checkSilkWasmAvailable, o as getQQBotDataDir, p as normalizePath, s as getQQBotDataPath, u as getTempDir } from "./target-parser-BdCUmxK7.js";
9
- import { a as 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-CvVXke_k.js";
7
+ import { n as getQQBotRuntimeForEngine, t as getQQBotRuntime } from "./runtime-BL0xZzWe.js";
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-DQQsZ6Y0.js";
10
+ import { A as getHomeDir, E as DEFAULT_MEDIA_SEND_ERROR, F as isLocalPath, I as isWindows, L as normalizePath, M as getQQBotMediaDir, O as setOutboundAudioPort, P as getTempDir, _ as sendVideoMsg, b as resolveUserFacingMediaError, c as isCronReminderPayload, d as normalizeMediaTags, g as sendPhoto, h as sendDocument, j as getQQBotDataDir, k as checkSilkWasmAvailable, l as isMediaPayload, o as decodeMediaPath, r as sendMedia$1, s as encodePayloadForCron, u as parseQQBotPayload, v as sendVoice, y as resolveTrustedOutboundMediaPath } from "./outbound-Dfa2uOEp.js";
10
11
  import { n as runWithRequestContext } from "./request-context-Bm7PTBD1.js";
11
- import { E as DEFAULT_MEDIA_SEND_ERROR, O as setOutboundAudioPort, _ as sendVideoMsg, b as resolveUserFacingMediaError, c as isCronReminderPayload, d as normalizeMediaTags, g as sendPhoto, h as sendDocument, l as isMediaPayload, o as decodeMediaPath, r as sendMedia$1, s as encodePayloadForCron, u as parseQQBotPayload, v as sendVoice, y as resolveTrustedOutboundMediaPath } from "./outbound-BQcPCo-z.js";
12
12
  import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
13
13
  import { isImplicitSameChatApprovalAuthorization } from "openclaw/plugin-sdk/approval-auth-runtime";
14
14
  import * as fs$1 from "node:fs";
15
15
  import fs from "node:fs";
16
16
  import * as os$1 from "node:os";
17
- import { readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
17
+ import { readProviderJsonResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
18
18
  import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
19
19
  import crypto from "node:crypto";
20
- import { privateFileStoreSync, readRegularFileSync } from "openclaw/plugin-sdk/security-runtime";
20
+ import { readRegularFileSync } from "openclaw/plugin-sdk/security-runtime";
21
21
  import * as path$1 from "node:path";
22
22
  import path from "node:path";
23
23
  import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
@@ -179,19 +179,10 @@ function formatRefEntryForAgent(entry) {
179
179
  //#region extensions/qqbot/src/engine/ref/store.ts
180
180
  /**
181
181
  * Ref-index store — SQLite KV-backed store for message reference index.
182
- *
183
- * Legacy JSONL entries are imported once, then deleted after SQLite has the
184
- * canonical ref-index rows.
185
182
  */
186
183
  const MAX_ENTRIES = 5e4;
187
184
  const TTL_MS = 10080 * 60 * 1e3;
188
185
  const REF_INDEX_NAMESPACE = "ref-index";
189
- const REF_INDEX_MIGRATIONS_NAMESPACE = "ref-index-migrations";
190
- const LEGACY_REF_INDEX_MIGRATION_KEY = "ref-index-jsonl-v1";
191
- let legacyImported$1 = false;
192
- function getRefIndexFile() {
193
- return path.join(getQQBotDataPath("data"), "ref-index.jsonl");
194
- }
195
186
  function createRefIndexStore() {
196
187
  return openQQBotSyncKeyedStore({
197
188
  namespace: REF_INDEX_NAMESPACE,
@@ -199,12 +190,6 @@ function createRefIndexStore() {
199
190
  defaultTtlMs: TTL_MS
200
191
  });
201
192
  }
202
- function createRefIndexMigrationStore() {
203
- return openQQBotSyncKeyedStore({
204
- namespace: REF_INDEX_MIGRATIONS_NAMESPACE,
205
- maxEntries: 100
206
- });
207
- }
208
193
  function refIndexStateKey(refIdx) {
209
194
  return buildQQBotStateKey("ref-index", refIdx);
210
195
  }
@@ -240,51 +225,9 @@ function toRefIndexEntry(entry) {
240
225
  ...entry.attachments ? { attachments: entry.attachments.map(toStoredAttachment) } : {}
241
226
  };
242
227
  }
243
- function ensureLegacyRefIndexImported() {
244
- if (legacyImported$1) return;
245
- const migrationStore = createRefIndexMigrationStore();
246
- if (migrationStore.lookup(LEGACY_REF_INDEX_MIGRATION_KEY)) {
247
- legacyImported$1 = true;
248
- return;
249
- }
250
- try {
251
- const refIndexFile = getRefIndexFile();
252
- if (!fs.existsSync(refIndexFile)) {
253
- migrationStore.register(LEGACY_REF_INDEX_MIGRATION_KEY, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
254
- legacyImported$1 = true;
255
- return;
256
- }
257
- const lines = fs.readFileSync(refIndexFile, "utf-8").split("\n");
258
- const now = Date.now();
259
- let expired = 0;
260
- let imported = 0;
261
- const store = createRefIndexStore();
262
- for (const line of lines) {
263
- const trimmed = line.trim();
264
- if (!trimmed) continue;
265
- try {
266
- const entry = JSON.parse(trimmed);
267
- if (!entry.k || !entry.v || !entry.t) continue;
268
- if (now - entry.t > TTL_MS) {
269
- expired++;
270
- continue;
271
- }
272
- store.register(refIndexStateKey(entry.k), toStoredRefIndexEntry(entry.v, entry.t), { ttlMs: Math.max(1, TTL_MS - (now - entry.t)) });
273
- imported++;
274
- } catch {}
275
- }
276
- migrationStore.register(LEGACY_REF_INDEX_MIGRATION_KEY, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
277
- legacyImported$1 = true;
278
- fs.rmSync(refIndexFile, { force: true });
279
- debugLog(`[ref-index-store] Migrated ${imported} entries to SQLite (${expired} expired)`);
280
- } catch (err) {
281
- debugError(`[ref-index-store] Failed to import legacy JSONL: ${formatErrorMessage(err)}`);
282
- }
283
- }
284
228
  /** Persist a refIdx mapping for one message. */
285
229
  function setRefIndex(refIdx, entry) {
286
230
  try {
287
- ensureLegacyRefIndexImported();
288
231
  const now = Date.now();
289
232
  createRefIndexStore().register(refIndexStateKey(refIdx), toStoredRefIndexEntry(entry, now), { ttlMs: TTL_MS });
290
233
  } catch (err) {
@@ -294,7 +237,6 @@ function setRefIndex(refIdx, entry) {
294
237
  /** Look up one quoted message by refIdx. */
295
238
  function getRefIndex(refIdx) {
296
239
  try {
297
- ensureLegacyRefIndexImported();
298
240
  const store = createRefIndexStore();
299
241
  const key = refIndexStateKey(refIdx);
300
242
  const entry = store.lookup(key);
@@ -567,34 +509,19 @@ async function trySlashCommand(msg, ctx) {
567
509
  //#region extensions/qqbot/src/engine/session/known-users.ts
568
510
  /**
569
511
  * Known user tracking — SQLite KV-backed store.
570
- *
571
- * Legacy `known-users.json` data is imported once, then deleted after SQLite
572
- * has the canonical copy.
573
512
  */
574
- function getKnownUsersFile() {
575
- return path.join(getQQBotDataPath("data"), "known-users.json");
576
- }
577
513
  function makeUserKey(user) {
578
514
  const base = `${user.accountId}:${user.type}:${user.openid}`;
579
515
  return user.type === "group" && user.groupOpenid ? `${base}:${user.groupOpenid}` : base;
580
516
  }
581
517
  const KNOWN_USERS_NAMESPACE = "known-users";
582
- const KNOWN_USERS_MIGRATIONS_NAMESPACE = "known-users-migrations";
583
- const LEGACY_KNOWN_USERS_MIGRATION_KEY = "known-users-json-v1";
584
518
  const MAX_KNOWN_USERS = 1e5;
585
- let legacyImported = false;
586
519
  function createKnownUsersStore() {
587
520
  return openQQBotSyncKeyedStore({
588
521
  namespace: KNOWN_USERS_NAMESPACE,
589
522
  maxEntries: MAX_KNOWN_USERS
590
523
  });
591
524
  }
592
- function createKnownUsersMigrationStore() {
593
- return openQQBotSyncKeyedStore({
594
- namespace: KNOWN_USERS_MIGRATIONS_NAMESPACE,
595
- maxEntries: 100
596
- });
597
- }
598
525
  function knownUserStateKey(key) {
599
526
  return crypto.createHash("sha256").update(key).digest("hex");
600
527
  }
@@ -610,32 +537,9 @@ function toStoredKnownUser(user) {
610
537
  interactionCount: user.interactionCount
611
538
  };
612
539
  }
613
- function ensureLegacyKnownUsersImported() {
614
- if (legacyImported) return;
615
- const migrationStore = createKnownUsersMigrationStore();
616
- if (migrationStore.lookup(LEGACY_KNOWN_USERS_MIGRATION_KEY)) {
617
- legacyImported = true;
618
- return;
619
- }
620
- try {
621
- const knownUsersFile = getKnownUsersFile();
622
- const users = privateFileStoreSync(path.dirname(knownUsersFile)).readJsonIfExists(path.basename(knownUsersFile));
623
- if (Array.isArray(users)) {
624
- const store = createKnownUsersStore();
625
- for (const user of users) store.registerIfAbsent(knownUserStateKey(makeUserKey(user)), toStoredKnownUser(user));
626
- debugLog(`[known-users] Migrated ${users.length} users to SQLite`);
627
- fs.rmSync(knownUsersFile, { force: true });
628
- }
629
- migrationStore.register(LEGACY_KNOWN_USERS_MIGRATION_KEY, { importedAt: (/* @__PURE__ */ new Date()).toISOString() });
630
- legacyImported = true;
631
- } catch (err) {
632
- debugError(`[known-users] Failed to import legacy users: ${formatErrorMessage(err)}`);
633
- }
634
- }
635
540
  /** Record a known user whenever a message is received. */
636
541
  function recordKnownUser(user) {
637
542
  try {
638
- ensureLegacyKnownUsersImported();
639
543
  const store = createKnownUsersStore();
640
544
  const stateKey = knownUserStateKey(makeUserKey(user));
641
545
  const now = Date.now();
@@ -669,34 +573,12 @@ function recordKnownUser(user) {
669
573
  //#region extensions/qqbot/src/engine/session/session-store.ts
670
574
  /**
671
575
  * Gateway session persistence — SQLite KV-backed store.
672
- *
673
- * Legacy JSON session files are imported on first account access, then
674
- * removed after SQLite has the canonical short-lived session entry.
675
576
  */
676
577
  const SESSION_EXPIRE_TIME = 300 * 1e3;
677
578
  const SAVE_THROTTLE_MS = 1e3;
678
579
  const SESSION_NAMESPACE = "gateway-sessions";
679
580
  const MAX_SESSIONS = 1e3;
680
581
  const throttleState = /* @__PURE__ */ new Map();
681
- function getSessionDir() {
682
- return getQQBotDataPath("sessions");
683
- }
684
- function encodeAccountIdForFileName(accountId) {
685
- return Buffer.from(accountId, "utf8").toString("base64url");
686
- }
687
- function getLegacySessionPath(accountId) {
688
- const safeId = accountId.replace(/[^a-zA-Z0-9_-]/g, "_");
689
- return path.join(getSessionDir(), `session-${safeId}.json`);
690
- }
691
- function getSessionPath(accountId) {
692
- const encodedId = encodeAccountIdForFileName(accountId);
693
- return path.join(getSessionDir(), `session-${encodedId}.json`);
694
- }
695
- function getCandidateSessionPaths(accountId) {
696
- const primaryPath = getSessionPath(accountId);
697
- const legacyPath = getLegacySessionPath(accountId);
698
- return primaryPath === legacyPath ? [primaryPath] : [primaryPath, legacyPath];
699
- }
700
582
  function createSessionStore() {
701
583
  return openQQBotSyncKeyedStore({
702
584
  namespace: SESSION_NAMESPACE,
@@ -707,9 +589,6 @@ function createSessionStore() {
707
589
  function sessionKey(accountId) {
708
590
  return buildQQBotStateKey("gateway-session", accountId);
709
591
  }
710
- function remainingSessionTtlMs(state, now = Date.now()) {
711
- return Math.max(1, SESSION_EXPIRE_TIME - (now - state.savedAt));
712
- }
713
592
  function toStoredSessionState(state) {
714
593
  return {
715
594
  sessionId: state.sessionId,
@@ -721,35 +600,11 @@ function toStoredSessionState(state) {
721
600
  ...state.appId ? { appId: state.appId } : {}
722
601
  };
723
602
  }
724
- function removeFileQuietly(filePath) {
725
- try {
726
- fs.unlinkSync(filePath);
727
- } catch {}
728
- }
729
- function loadLegacySession(accountId) {
730
- for (const candidatePath of getCandidateSessionPaths(accountId)) {
731
- const state = privateFileStoreSync(path.dirname(candidatePath)).readJsonIfExists(path.basename(candidatePath));
732
- if (state) return {
733
- state,
734
- filePath: candidatePath
735
- };
736
- }
737
- return null;
738
- }
739
- function migrateLegacySession(accountId) {
740
- const legacy = loadLegacySession(accountId);
741
- if (!legacy) return null;
742
- const now = Date.now();
743
- if (now - legacy.state.savedAt <= SESSION_EXPIRE_TIME) createSessionStore().register(sessionKey(accountId), toStoredSessionState(legacy.state), { ttlMs: remainingSessionTtlMs(legacy.state, now) });
744
- for (const filePath of getCandidateSessionPaths(accountId)) removeFileQuietly(filePath);
745
- return legacy.state;
746
- }
747
603
  /** Load a saved session, rejecting expired or mismatched appId entries. */
748
604
  function loadSession(accountId, expectedAppId) {
749
605
  try {
750
606
  const store = createSessionStore();
751
- let state = store.lookup(sessionKey(accountId));
752
- if (!state) state = migrateLegacySession(accountId) ?? void 0;
607
+ const state = store.lookup(sessionKey(accountId));
753
608
  if (!state) return null;
754
609
  const now = Date.now();
755
610
  if (now - state.savedAt > SESSION_EXPIRE_TIME) {
@@ -813,16 +668,12 @@ function saveSession(state) {
813
668
  }
814
669
  }
815
670
  function doSaveSession(state) {
816
- const filePath = getSessionPath(state.accountId);
817
- const legacyPath = getLegacySessionPath(state.accountId);
818
671
  try {
819
672
  const stateToSave = {
820
673
  ...state,
821
674
  savedAt: Date.now()
822
675
  };
823
676
  createSessionStore().register(sessionKey(state.accountId), toStoredSessionState(stateToSave), { ttlMs: SESSION_EXPIRE_TIME });
824
- removeFileQuietly(filePath);
825
- removeFileQuietly(legacyPath);
826
677
  debugLog(`[session-store] Saved session for ${state.accountId}: sessionId=${state.sessionId}, lastSeq=${state.lastSeq}`);
827
678
  } catch (err) {
828
679
  debugError(`[session-store] Failed to save session for ${state.accountId}: ${formatErrorMessage(err)}`);
@@ -836,9 +687,7 @@ function clearSession(accountId) {
836
687
  throttleState.delete(accountId);
837
688
  }
838
689
  try {
839
- const cleared = createSessionStore().delete(sessionKey(accountId));
840
- for (const filePath of getCandidateSessionPaths(accountId)) removeFileQuietly(filePath);
841
- if (cleared) debugLog(`[session-store] Cleared session for ${accountId}`);
690
+ if (createSessionStore().delete(sessionKey(accountId))) debugLog(`[session-store] Cleared session for ${accountId}`);
842
691
  } catch (err) {
843
692
  debugError(`[session-store] Failed to clear session for ${accountId}: ${formatErrorMessage(err)}`);
844
693
  }
@@ -1869,7 +1718,7 @@ async function transcribeAudio(audioPath, cfg) {
1869
1718
  const detail = await readResponseTextLimited(resp, STT_ERROR_BODY_LIMIT_BYTES).catch(() => "");
1870
1719
  throw new Error(`STT failed (HTTP ${resp.status}): ${detail.slice(0, 300)}`);
1871
1720
  }
1872
- return normalizeOptionalString$1((await resp.json()).text) ?? null;
1721
+ return normalizeOptionalString$1((await readProviderJsonResponse(resp, "qqbot.stt")).text) ?? null;
1873
1722
  } finally {
1874
1723
  await release();
1875
1724
  }
@@ -1,12 +1,12 @@
1
1
  import { t as asOptionalObjectRecord } from "./string-normalize-R_0cKO7Q.js";
2
2
  import { d as resolveAccountBase } from "./config-C1qZbh0K.js";
3
3
  import { n as debugLog } from "./log-SDfMMBWe.js";
4
- import { a as getHomeDir, f as isWindows, l as getQQBotMediaPath, o as getQQBotDataDir } from "./target-parser-BdCUmxK7.js";
4
+ import { A as getHomeDir, I as isWindows, N as getQQBotMediaPath, j as getQQBotDataDir } from "./outbound-Dfa2uOEp.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 { loadJsonFile } from "openclaw/plugin-sdk/json-store";
9
8
  import { clearRuntimeConfigSnapshot } from "openclaw/plugin-sdk/runtime-config-snapshot";
9
+ import { loadJsonFile } from "openclaw/plugin-sdk/json-store";
10
10
  //#region extensions/qqbot/src/engine/commands/builtin/state.ts
11
11
  let resolveVersionGetter = () => "unknown";
12
12
  let approveRuntimeGetter = null;
@@ -1,6 +1,6 @@
1
- import { c as getMessageApi, t as accountToCreds } from "./sender-CRZ2af7P.js";
1
+ import { c as getMessageApi, t as accountToCreds } from "./sender-BHWpE_yH.js";
2
2
  import { o as ensurePlatformAdapter, s as getBridgeLogger } from "./config-schema-gMW9zKvQ.js";
3
- import { c as matchesQQBotApprovalAccount, d as buildApprovalKeyboard, f as buildExecApprovalText, h as resolveApprovalTarget, l as resolveQQBotExecApprovalConfig, p as buildPluginApprovalText, s as isQQBotExecApprovalClientEnabled, u as shouldHandleQQBotExecApprovalRequest } from "./channel-DvWrqcZB.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-Bh0MrYvU.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,11 +1,223 @@
1
- import { A as getFileTypeName, D as downloadFile, E as checkFileSize, L as __exportAll, M as getMaxUploadSize, N as readFileAsync, O as fileExistsAsync, S as UploadDailyLimitExceededError, g as sendText$1, h as sendMedia$1, j as getImageMimeType, k as formatFileSize, t as accountToCreds, u as initApiConfig, w as UPLOAD_PREPARE_FALLBACK_CODE } from "./sender-CRZ2af7P.js";
2
- import { i as normalizeOptionalString, n as normalizeLowercaseStringOrEmpty, s as sanitizeFileName } from "./string-normalize-R_0cKO7Q.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-BHWpE_yH.js";
2
+ import { c as getPlatformAdapter, i as normalizeOptionalString, n as normalizeLowercaseStringOrEmpty, s as sanitizeFileName } from "./string-normalize-R_0cKO7Q.js";
3
3
  import { a as formatErrorMessage, n as debugLog, r as debugWarn, t as debugError } from "./log-SDfMMBWe.js";
4
- 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
+ import { r as parseTarget$1 } from "./target-parser-DagYRQkP.js";
5
+ import * as fs$1 from "node:fs";
6
+ import * as os$1 from "node:os";
5
7
  import { pathExistsSync, resolveLocalPathFromRootsSync } from "openclaw/plugin-sdk/security-runtime";
8
+ import * as path$1 from "node:path";
6
9
  import path from "node:path";
7
10
  import { getFileExtension } from "openclaw/plugin-sdk/media-mime";
8
11
  import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/sandbox";
12
+ //#region extensions/qqbot/src/engine/utils/platform.ts
13
+ /**
14
+ * Cross-platform path and detection helpers for core/ modules.
15
+ *
16
+ * Provides home/data/media directory helpers, platform detection,
17
+ * silk-wasm availability checks — all without importing `openclaw/plugin-sdk`.
18
+ * The temp-directory fallback is delegated to the PlatformAdapter.
19
+ */
20
+ /**
21
+ * Resolve the current user's OS home directory safely across platforms.
22
+ *
23
+ * Priority:
24
+ * 1. `os.homedir()`
25
+ * 2. `$HOME` or `%USERPROFILE%`
26
+ * 3. PlatformAdapter.getTempDir() as a last resort
27
+ *
28
+ * This is the *operating-system* home and intentionally ignores
29
+ * `OPENCLAW_HOME`. QQ Bot still checks this tree for legacy state imports and
30
+ * media-path remaps from older releases.
31
+ */
32
+ function getHomeDir() {
33
+ try {
34
+ const home = os$1.homedir();
35
+ if (home && fs$1.existsSync(home)) return home;
36
+ } catch {}
37
+ const envHome = process.env.HOME || process.env.USERPROFILE;
38
+ if (envHome && fs$1.existsSync(envHome)) return envHome;
39
+ return getPlatformAdapter().getTempDir();
40
+ }
41
+ /**
42
+ * Resolve the effective OpenClaw home directory.
43
+ *
44
+ * Mirrors the contract from core (`src/infra/home-dir.ts::resolveEffectiveHomeDir`)
45
+ * so QQ Bot media roots live under the same tree the rest of OpenClaw treats as
46
+ * `~`. The extension cannot import the core helper directly (it is a separate
47
+ * package with `openclaw` as a peer dependency), so this re-implements the
48
+ * minimal contract:
49
+ *
50
+ * 1. `OPENCLAW_HOME` when set (with `~` / `~/...` expanded against the OS home).
51
+ * 2. Otherwise fall back to {@link getHomeDir} so existing single-home
52
+ * deployments are unaffected.
53
+ *
54
+ * Empty / `"undefined"` / `"null"` strings are treated as unset to match how
55
+ * core normalizes the variable.
56
+ */
57
+ function resolveOpenClawHome() {
58
+ const raw = process.env.OPENCLAW_HOME?.trim();
59
+ if (!raw || raw === "undefined" || raw === "null") return getHomeDir();
60
+ if (raw === "~" || raw.startsWith("~/") || raw.startsWith("~\\")) {
61
+ const osHome = getHomeDir();
62
+ if (raw === "~") return osHome;
63
+ return path$1.join(osHome, raw.slice(2));
64
+ }
65
+ return raw;
66
+ }
67
+ /**
68
+ * Return a legacy path under `~/.openclaw/qqbot` without creating it.
69
+ *
70
+ * Current QQ Bot runtime state lives in plugin SQLite KV. This path remains for
71
+ * legacy imports and media-path remaps from older releases.
72
+ */
73
+ function getQQBotDataPath(...subPaths) {
74
+ return path$1.join(getHomeDir(), ".openclaw", "qqbot", ...subPaths);
75
+ }
76
+ /** Return a path under `~/.openclaw/qqbot`, creating it on demand. */
77
+ function getQQBotDataDir(...subPaths) {
78
+ const dir = getQQBotDataPath(...subPaths);
79
+ if (!fs$1.existsSync(dir)) fs$1.mkdirSync(dir, { recursive: true });
80
+ return dir;
81
+ }
82
+ /**
83
+ * Return a path under `<openclaw-home>/.openclaw/media/qqbot` without creating it.
84
+ *
85
+ * Unlike `getQQBotDataPath`, this lives under OpenClaw's core media allowlist
86
+ * so downloaded images and audio can be accessed by framework media tooling.
87
+ * The base honors `OPENCLAW_HOME` (when set) so files written by agents into
88
+ * the OpenClaw-managed media tree are reachable by this plugin even when
89
+ * `HOME` and `OPENCLAW_HOME` differ (Docker, multi-user hosts). Fixes #83562.
90
+ */
91
+ function getQQBotMediaPath(...subPaths) {
92
+ return path$1.join(resolveOpenClawHome(), ".openclaw", "media", "qqbot", ...subPaths);
93
+ }
94
+ /** Return a path under `<openclaw-home>/.openclaw/media/qqbot`, creating it on demand. */
95
+ function getQQBotMediaDir(...subPaths) {
96
+ const dir = getQQBotMediaPath(...subPaths);
97
+ if (!fs$1.existsSync(dir)) fs$1.mkdirSync(dir, { recursive: true });
98
+ return dir;
99
+ }
100
+ /**
101
+ * Return `<openclaw-home>/.openclaw/media`, OpenClaw's shared media root.
102
+ *
103
+ * This mirrors the directory that core's `buildMediaLocalRoots` exposes as an
104
+ * allowlisted location (see `openclaw/src/media/local-roots.ts`). Using it as a
105
+ * QQ Bot payload root lets the plugin trust framework-produced files that live
106
+ * in sibling subdirectories such as `outbound/` (written by
107
+ * `saveMediaBuffer(..., "outbound", ...)`) or `inbound/`, while still keeping
108
+ * the check anchored to a single, well-known directory. Like
109
+ * {@link getQQBotMediaPath}, the base honors `OPENCLAW_HOME`.
110
+ */
111
+ function getOpenClawMediaDir() {
112
+ return path$1.join(resolveOpenClawHome(), ".openclaw", "media");
113
+ }
114
+ function isWindows() {
115
+ return process.platform === "win32";
116
+ }
117
+ /** Return the preferred temporary directory. */
118
+ function getTempDir() {
119
+ return getPlatformAdapter().getTempDir();
120
+ }
121
+ let silkWasmAvailable = null;
122
+ /** Check whether silk-wasm can run in the current environment. */
123
+ async function checkSilkWasmAvailable() {
124
+ if (silkWasmAvailable !== null) return silkWasmAvailable;
125
+ try {
126
+ const { isSilk } = await import("silk-wasm");
127
+ isSilk(new Uint8Array(0));
128
+ silkWasmAvailable = true;
129
+ debugLog("[platform] silk-wasm: available");
130
+ } catch (err) {
131
+ silkWasmAvailable = false;
132
+ debugWarn(`[platform] silk-wasm: NOT available (${formatErrorMessage(err)})`);
133
+ }
134
+ return silkWasmAvailable;
135
+ }
136
+ /** Expand `~` to the current user's home directory. */
137
+ function expandTilde$1(p) {
138
+ if (!p) return p;
139
+ if (p === "~") return getHomeDir();
140
+ if (p.startsWith("~/") || p.startsWith("~\\")) return path$1.join(getHomeDir(), p.slice(2));
141
+ return p;
142
+ }
143
+ /** Normalize a user-provided path by trimming, stripping `file://`, and expanding `~`. */
144
+ function normalizePath$1(p) {
145
+ let result = p.trim();
146
+ if (result.startsWith("file://")) {
147
+ result = result.slice(7);
148
+ try {
149
+ result = decodeURIComponent(result);
150
+ } catch {}
151
+ }
152
+ return expandTilde$1(result);
153
+ }
154
+ /** Return true when the string looks like a local filesystem path rather than a URL. */
155
+ function isLocalPath(p) {
156
+ if (!p) return false;
157
+ if (p.startsWith("file://")) return true;
158
+ if (p === "~" || p.startsWith("~/") || p.startsWith("~\\")) return true;
159
+ if (p.startsWith("/")) return true;
160
+ if (/^[a-zA-Z]:[\\/]/.test(p)) return true;
161
+ if (p.startsWith("\\\\")) return true;
162
+ if (p.startsWith("./") || p.startsWith("../")) return true;
163
+ if (p.startsWith(".\\") || p.startsWith("..\\")) return true;
164
+ return false;
165
+ }
166
+ function isPathWithinRoot(candidate, root) {
167
+ const relative = path$1.relative(root, candidate);
168
+ return relative === "" || !relative.startsWith("..") && !path$1.isAbsolute(relative);
169
+ }
170
+ /** Remap legacy or hallucinated QQ Bot local media paths to real files when possible. */
171
+ function resolveQQBotLocalMediaPath(p) {
172
+ const normalized = normalizePath$1(p);
173
+ if (!isLocalPath(normalized) || fs$1.existsSync(normalized)) return normalized;
174
+ const osHomeDir = getHomeDir();
175
+ const openclawHomeDir = resolveOpenClawHome();
176
+ const mediaRoot = getQQBotMediaPath();
177
+ const dataRoot = getQQBotDataPath();
178
+ const candidateRoots = [
179
+ ...Array.from(new Set([path$1.join(osHomeDir, ".openclaw", "workspace", "qqbot"), path$1.join(openclawHomeDir, ".openclaw", "workspace", "qqbot")])).map((from) => ({
180
+ from,
181
+ to: mediaRoot
182
+ })),
183
+ {
184
+ from: dataRoot,
185
+ to: mediaRoot
186
+ },
187
+ {
188
+ from: mediaRoot,
189
+ to: dataRoot
190
+ }
191
+ ];
192
+ for (const { from, to } of candidateRoots) {
193
+ if (!isPathWithinRoot(normalized, from)) continue;
194
+ const relative = path$1.relative(from, normalized);
195
+ const candidate = path$1.join(to, relative);
196
+ if (fs$1.existsSync(candidate)) {
197
+ debugWarn(`[platform] Remapped missing QQBot media path ${normalized} -> ${candidate}`);
198
+ return candidate;
199
+ }
200
+ }
201
+ return normalized;
202
+ }
203
+ /**
204
+ * Resolve a structured-payload local file path and enforce that it stays within
205
+ * QQ Bot-owned storage roots.
206
+ */
207
+ function resolveQQBotPayloadLocalFilePath(p) {
208
+ const candidate = resolveQQBotLocalMediaPath(p);
209
+ if (!candidate.trim()) return null;
210
+ const resolvedCandidate = path$1.resolve(candidate);
211
+ if (!fs$1.existsSync(resolvedCandidate)) return null;
212
+ const canonicalCandidate = fs$1.realpathSync(resolvedCandidate);
213
+ const allowedRoots = [getOpenClawMediaDir(), getQQBotMediaPath()];
214
+ for (const root of allowedRoots) {
215
+ const resolvedRoot = path$1.resolve(root);
216
+ if (isPathWithinRoot(canonicalCandidate, fs$1.existsSync(resolvedRoot) ? fs$1.realpathSync(resolvedRoot) : resolvedRoot)) return canonicalCandidate;
217
+ }
218
+ return null;
219
+ }
220
+ //#endregion
9
221
  //#region extensions/qqbot/src/engine/messaging/outbound-audio-port.ts
10
222
  let outboundAudioPort = null;
11
223
  /**
@@ -1410,4 +1622,4 @@ async function sendCronMessage(account, to, message) {
1410
1622
  });
1411
1623
  }
1412
1624
  //#endregion
1413
- export { getMessageReplyConfig as C, OUTBOUND_ERROR_CODES as D, DEFAULT_MEDIA_SEND_ERROR as E, setOutboundAudioPort as O, checkMessageReplyLimit as S, recordMessageReply as T, sendVideoMsg as _, sendText as a, resolveUserFacingMediaError as b, isCronReminderPayload as c, normalizeMediaTags as d, buildMediaTarget as f, sendPhoto as g, sendDocument as h, sendProactiveMessage as i, isMediaPayload as l, resolveOutboundMediaPath as m, sendCronMessage as n, decodeMediaPath as o, parseTarget as p, sendMedia as r, encodePayloadForCron as s, outbound_exports as t, parseQQBotPayload as u, sendVoice as v, getMessageReplyStats as w, MESSAGE_REPLY_LIMIT as x, resolveTrustedOutboundMediaPath as y };
1625
+ export { getHomeDir as A, getMessageReplyConfig as C, OUTBOUND_ERROR_CODES as D, DEFAULT_MEDIA_SEND_ERROR as E, isLocalPath as F, isWindows as I, normalizePath$1 as L, getQQBotMediaDir as M, getQQBotMediaPath as N, setOutboundAudioPort as O, getTempDir as P, checkMessageReplyLimit as S, recordMessageReply as T, sendVideoMsg as _, sendText as a, resolveUserFacingMediaError as b, isCronReminderPayload as c, normalizeMediaTags as d, buildMediaTarget as f, sendPhoto as g, sendDocument as h, sendProactiveMessage as i, getQQBotDataDir as j, checkSilkWasmAvailable as k, isMediaPayload as l, resolveOutboundMediaPath as m, sendCronMessage as n, decodeMediaPath as o, parseTarget as p, sendMedia as r, encodePayloadForCron as s, outbound_exports as t, parseQQBotPayload as u, sendVoice as v, getMessageReplyStats as w, MESSAGE_REPLY_LIMIT as x, resolveTrustedOutboundMediaPath as y };
@@ -1,4 +1,4 @@
1
- import { v as setOpenClawVersion } from "./sender-CRZ2af7P.js";
1
+ import { v as setOpenClawVersion } from "./sender-BHWpE_yH.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-D1hn91-s.js";
1
+ import { r as setQQBotRuntime, t as getQQBotRuntime } from "./runtime-BL0xZzWe.js";
2
2
  export { getQQBotRuntime, setQQBotRuntime };
@@ -2,7 +2,7 @@ import { c as getPlatformAdapter, i as normalizeOptionalString, s as sanitizeFil
2
2
  import { a as formatErrorMessage, n as debugLog, r as debugWarn, t as debugError } from "./log-SDfMMBWe.js";
3
3
  import * as fs$1 from "node:fs";
4
4
  import os from "node:os";
5
- import { readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
5
+ import { readProviderTextResponse, readResponseTextLimited } from "openclaw/plugin-sdk/provider-http";
6
6
  import { fetchWithSsrFGuard, isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
7
7
  import * as crypto$1 from "node:crypto";
8
8
  import crypto from "node:crypto";
@@ -159,7 +159,7 @@ var ApiClient = class {
159
159
  this.logger?.info?.(`[qqbot:api] <<< Status: ${res.status} ${res.statusText}${traceId ? ` | TraceId: ${traceId}` : ""}`);
160
160
  const readBody = async (limitBytes) => {
161
161
  try {
162
- return limitBytes === void 0 ? await res.text() : await readResponseTextLimited(res, limitBytes);
162
+ return limitBytes === void 0 ? await readProviderTextResponse(res, "QQBot API response") : await readResponseTextLimited(res, limitBytes);
163
163
  } catch (err) {
164
164
  throw new ApiError(`Failed to read response [${path}]: ${formatErrorMessage(err)}`, res.status, path);
165
165
  }
@@ -1,3 +1,4 @@
1
+ import crypto from "node:crypto";
1
2
  import { asObjectRecord } from "openclaw/plugin-sdk/runtime-doctor";
2
3
  //#region extensions/qqbot/src/doctor-contract.ts
3
4
  const RESTRICTED_GROUP_TOOLS = { deny: [
@@ -132,4 +133,9 @@ function normalizeCompatibilityConfig({ cfg }) {
132
133
  };
133
134
  }
134
135
  //#endregion
135
- export { normalizeCompatibilityConfig as n, legacyConfigRules as t };
136
+ //#region extensions/qqbot/src/engine/utils/state-keys.ts
137
+ function buildQQBotStateKey(...parts) {
138
+ return crypto.createHash("sha256").update(JSON.stringify(parts)).digest("hex");
139
+ }
140
+ //#endregion
141
+ export { legacyConfigRules as n, normalizeCompatibilityConfig as r, buildQQBotStateKey as t };