@openclaw/qqbot 2026.6.11 → 2026.7.1-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.js CHANGED
@@ -1,8 +1,8 @@
1
- import { t as qqbotPlugin } from "./channel-DvWrqcZB.js";
1
+ import { t as qqbotPlugin } from "./channel-BQM6pDlD.js";
2
2
  import { a as resolveQQBotAccount, i as resolveDefaultQQBotAccountId, n as applyQQBotAccountConfig, r as listQQBotAccountIds, t as DEFAULT_ACCOUNT_ID } from "./config-C1qZbh0K.js";
3
- import { t as qqbotSetupPlugin } from "./channel.setup-CldhUlTO.js";
4
- import { o as getFrameworkCommands } from "./group-CvVXke_k.js";
5
- import { n as registerRemindTool, r as registerChannelTool, t as registerQQBotTools } from "./tools-CeUI9pG-.js";
6
- import { t as registerQQBotFull } from "./channel-entry-BGXFRmbg.js";
7
- import { C as getMessageReplyConfig, D as OUTBOUND_ERROR_CODES, E as DEFAULT_MEDIA_SEND_ERROR, O as setOutboundAudioPort, S as checkMessageReplyLimit, T as recordMessageReply, _ as sendVideoMsg, a as sendText, b as resolveUserFacingMediaError, f as buildMediaTarget, g as sendPhoto, h as sendDocument, i as sendProactiveMessage, m as resolveOutboundMediaPath, n as sendCronMessage, p as parseTarget, r as sendMedia, v as sendVoice, w as getMessageReplyStats, x as MESSAGE_REPLY_LIMIT } from "./outbound-BQcPCo-z.js";
3
+ import { t as qqbotSetupPlugin } from "./channel.setup-2ItDYKhz.js";
4
+ import { o as getFrameworkCommands } from "./group-BmS1Qwst.js";
5
+ import { A as OUTBOUND_ERROR_CODES, C as resolveUserFacingMediaError, D as getMessageReplyStats, E as getMessageReplyConfig, O as recordMessageReply, T as checkMessageReplyLimit, _ as sendVideoMsg, a as sendText, f as buildMediaTarget, g as sendPhoto, h as sendDocument, i as sendProactiveMessage, j as setOutboundAudioPort, k as DEFAULT_MEDIA_SEND_ERROR, m as resolveOutboundMediaPath, n as sendCronMessage, p as parseTarget, r as sendMedia, v as sendVoice, w as MESSAGE_REPLY_LIMIT } from "./outbound-BZHwMFUX.js";
6
+ import { n as registerRemindTool, r as registerChannelTool, t as registerQQBotTools } from "./tools-C8kTMXKc.js";
7
+ import { t as registerQQBotFull } from "./channel-entry-C18ypTaG.js";
8
8
  export { DEFAULT_ACCOUNT_ID, DEFAULT_MEDIA_SEND_ERROR, MESSAGE_REPLY_LIMIT, OUTBOUND_ERROR_CODES, applyQQBotAccountConfig, buildMediaTarget, checkMessageReplyLimit, getFrameworkCommands, getMessageReplyConfig, getMessageReplyStats, listQQBotAccountIds, parseTarget, qqbotPlugin, qqbotSetupPlugin, recordMessageReply, registerChannelTool, registerQQBotFull, registerQQBotTools, registerRemindTool, resolveDefaultQQBotAccountId, resolveOutboundMediaPath, resolveQQBotAccount, resolveUserFacingMediaError, sendCronMessage, sendDocument, sendMedia, sendPhoto, sendProactiveMessage, sendText, sendVideoMsg, sendVoice, setOutboundAudioPort };
@@ -1,11 +1,12 @@
1
1
  import { t as asOptionalObjectRecord } from "./string-normalize-R_0cKO7Q.js";
2
- import { a as qqbotSetupAdapterShared, i as qqbotMeta, n as qqbotSetupWizard, o as ensurePlatformAdapter, r as qqbotConfigAdapter, s as getBridgeLogger, t as qqbotChannelConfigSchema } from "./config-schema-gMW9zKvQ.js";
2
+ import { a as qqbotSetupAdapterShared, i as qqbotMeta, n as qqbotSetupWizard, o as ensurePlatformAdapter, r as qqbotConfigAdapter, s as getBridgeLogger, t as qqbotChannelConfigSchema } from "./config-schema-JZEf1dvB.js";
3
3
  import { a as resolveQQBotAccount, n as applyQQBotAccountConfig, r as listQQBotAccountIds, t as DEFAULT_ACCOUNT_ID$1 } from "./config-C1qZbh0K.js";
4
- import { t as getQQBotRuntime } from "./runtime-D1hn91-s.js";
5
- import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-BYTS8-ia.js";
6
- import { n as normalizeTarget, s as getQQBotDataPath, t as looksLikeQQBotTarget } from "./target-parser-BdCUmxK7.js";
4
+ import { t as getQQBotRuntime } from "./runtime-B4HWCOa-.js";
5
+ import { n as legacyConfigRules, r as normalizeCompatibilityConfig, t as buildQQBotStateKey } from "./state-keys-CQKlAFyo.js";
6
+ import { n as normalizeTarget, t as looksLikeQQBotTarget } from "./target-parser-DagYRQkP.js";
7
7
  import { getExecApprovalReplyMetadata } from "openclaw/plugin-sdk/approval-runtime";
8
8
  import { createMessageReceiptFromOutboundResults, defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-outbound";
9
+ import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
9
10
  import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking";
10
11
  import { createChannelApprovalCapability } from "openclaw/plugin-sdk/approval-delivery-runtime";
11
12
  import { createLazyChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
@@ -14,11 +15,6 @@ import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "opencl
14
15
  import { markImplicitSameChatApprovalAuthorization, resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
15
16
  import { createChannelExecApprovalProfile, isChannelExecApprovalClientEnabledFromConfig, matchesApprovalRequestFilters } from "openclaw/plugin-sdk/approval-client-runtime";
16
17
  import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
17
- import fs from "node:fs";
18
- import crypto from "node:crypto";
19
- import path from "node:path";
20
- import { loadJsonFile } from "openclaw/plugin-sdk/json-store";
21
- import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
22
18
  import { resolveChannelGroupToolsPolicy } from "openclaw/plugin-sdk/channel-policy";
23
19
  //#region extensions/qqbot/src/engine/approval/index.ts
24
20
  function buildExecApprovalText(request) {
@@ -370,7 +366,7 @@ function createQQBotApprovalCapability() {
370
366
  },
371
367
  load: async () => {
372
368
  ensurePlatformAdapter();
373
- return (await import("./handler-runtime--OTMkFWa.js")).qqbotApprovalNativeRuntime;
369
+ return (await import("./handler-runtime-rU03VA43.js")).qqbotApprovalNativeRuntime;
374
370
  }
375
371
  })
376
372
  });
@@ -412,41 +408,6 @@ const qqbotDoctor = {
412
408
  normalizeCompatibilityConfig
413
409
  };
414
410
  //#endregion
415
- //#region extensions/qqbot/src/engine/utils/data-paths.ts
416
- /**
417
- * Centralised filename helpers for persisted QQBot state.
418
- *
419
- * Every persistence module routes file paths through these helpers so the
420
- * naming convention stays in sync and legacy migrations are handled
421
- * consistently.
422
- *
423
- * Key design decisions:
424
- * - Credential backup is keyed only by `accountId` because recovery runs
425
- * exactly when the appId is missing from config.
426
- */
427
- /**
428
- * Normalise an identifier so it is safe to embed in a filename.
429
- * Keeps alphanumerics, dot, underscore, dash; everything else becomes `_`.
430
- */
431
- function safeName$1(id) {
432
- return id.replace(/[^a-zA-Z0-9._-]/g, "_");
433
- }
434
- function getCredentialBackupRoot() {
435
- return path.join(resolveStateDir(process.env), "qqbot", "data");
436
- }
437
- /**
438
- * Per-accountId credential backup file. Not keyed by appId because the
439
- * whole point of this file is to recover credentials when appId is
440
- * missing from the live config.
441
- */
442
- function getCredentialBackupFile(accountId) {
443
- return path.join(getCredentialBackupRoot(), `credential-backup-${safeName$1(accountId)}.json`);
444
- }
445
- /** Legacy single-file credential backup (pre-multi-account-isolation). */
446
- function getLegacyCredentialBackupFile() {
447
- return path.join(getCredentialBackupRoot(), "credential-backup.json");
448
- }
449
- //#endregion
450
411
  //#region extensions/qqbot/src/engine/utils/sqlite-state.ts
451
412
  function resolveStoreEnv(options) {
452
413
  if (!options.stateDir) return options.env;
@@ -463,9 +424,6 @@ function openQQBotSyncKeyedStore(options) {
463
424
  ...resolveStoreEnv(options) ? { env: resolveStoreEnv(options) } : {}
464
425
  });
465
426
  }
466
- function buildQQBotStateKey(...parts) {
467
- return crypto.createHash("sha256").update(JSON.stringify(parts)).digest("hex");
468
- }
469
427
  //#endregion
470
428
  //#region extensions/qqbot/src/engine/config/credential-backup.ts
471
429
  /**
@@ -481,8 +439,8 @@ function buildQQBotStateKey(...parts) {
481
439
  * - During plugin startup, if the live config has an empty appId or
482
440
  * secret, the gateway consults the backup and restores the values
483
441
  * via the config mutation API.
484
- * - Legacy JSON backups are imported on first read, then removed after
485
- * SQLite has the canonical copy.
442
+ * - Legacy JSON backups are imported by `openclaw doctor --fix`, not by
443
+ * runtime startup.
486
444
  *
487
445
  * Safety notes:
488
446
  * - Only restore when credentials are **actually empty** — never
@@ -498,53 +456,16 @@ function createCredentialBackupStore() {
498
456
  maxEntries: MAX_CREDENTIAL_BACKUPS
499
457
  });
500
458
  }
501
- function safeName(id) {
502
- return id.replace(/[^a-zA-Z0-9._-]/g, "_");
503
- }
504
- function getLegacyOsHomeCredentialBackupFile(accountId) {
505
- return path.join(getQQBotDataPath("data"), `credential-backup-${safeName(accountId)}.json`);
506
- }
507
- function getLegacyOsHomeCredentialBackupFileWithoutAccount() {
508
- return path.join(getQQBotDataPath("data"), "credential-backup.json");
509
- }
510
459
  function credentialBackupKey(accountId) {
511
460
  return buildQQBotStateKey("credential-backup", accountId);
512
461
  }
513
462
  function isUsableBackup(data) {
514
463
  return Boolean(data?.accountId && data.appId && data.clientSecret);
515
464
  }
516
- function loadUsableBackupFromFile(filePath) {
517
- const data = loadJsonFile(filePath);
518
- return isUsableBackup(data) ? data : null;
519
- }
520
- function removeFileQuietly(filePath) {
521
- try {
522
- fs.unlinkSync(filePath);
523
- } catch {}
524
- }
525
- function findLegacyBackup(accountId) {
526
- const candidates = accountId ? [
527
- getCredentialBackupFile(accountId),
528
- getLegacyCredentialBackupFile(),
529
- getLegacyOsHomeCredentialBackupFile(accountId),
530
- getLegacyOsHomeCredentialBackupFileWithoutAccount()
531
- ] : [getLegacyCredentialBackupFile(), getLegacyOsHomeCredentialBackupFileWithoutAccount()];
532
- for (const filePath of candidates) {
533
- const data = loadUsableBackupFromFile(filePath);
534
- if (!data) continue;
535
- if (accountId && data.accountId !== accountId) continue;
536
- return {
537
- data,
538
- filePath
539
- };
540
- }
541
- return null;
542
- }
543
465
  /** Persist a credential snapshot (called once gateway reaches READY). */
544
466
  function saveCredentialBackup(accountId, appId, clientSecret) {
545
467
  if (!appId || !clientSecret) return;
546
468
  try {
547
- const backupPath = getCredentialBackupFile(accountId);
548
469
  const data = {
549
470
  accountId,
550
471
  appId,
@@ -552,14 +473,13 @@ function saveCredentialBackup(accountId, appId, clientSecret) {
552
473
  savedAt: (/* @__PURE__ */ new Date()).toISOString()
553
474
  };
554
475
  createCredentialBackupStore().register(credentialBackupKey(accountId), data);
555
- removeFileQuietly(backupPath);
556
476
  } catch {}
557
477
  }
558
478
  /**
559
479
  * Load a credential snapshot for `accountId`.
560
480
  *
561
- * Consults SQLite first; falls back to shipped JSON backups and imports
562
- * them when the embedded `accountId` matches the request.
481
+ * Reads SQLite only. Legacy JSON backup import is owned by doctor/setup
482
+ * migration so runtime startup stays canonical-state-only.
563
483
  */
564
484
  function loadCredentialBackup(accountId) {
565
485
  try {
@@ -567,12 +487,6 @@ function loadCredentialBackup(accountId) {
567
487
  const data = createCredentialBackupStore().lookup(credentialBackupKey(accountId));
568
488
  if (isUsableBackup(data)) return data;
569
489
  }
570
- const legacy = findLegacyBackup(accountId);
571
- if (legacy) {
572
- createCredentialBackupStore().register(credentialBackupKey(legacy.data.accountId), legacy.data);
573
- removeFileQuietly(legacy.filePath);
574
- return legacy.data;
575
- }
576
490
  } catch {}
577
491
  return null;
578
492
  }
@@ -934,11 +848,32 @@ function isTableSeparatorLine(line) {
934
848
  const cells = splitTableCells(line);
935
849
  return cells.length > 0 && cells.every((cell) => /^:?-+:?$/.test(cell.trim()));
936
850
  }
851
+ function splitTableRowCells(inner) {
852
+ const cells = [];
853
+ let current = "";
854
+ for (let i = 0; i < inner.length; i++) {
855
+ const char = inner[i];
856
+ if (char === "\\" && i + 1 < inner.length) {
857
+ const next = inner[i + 1];
858
+ current += next === "|" || next === "\\" ? next : `\\${next}`;
859
+ i++;
860
+ continue;
861
+ }
862
+ if (char === "|") {
863
+ cells.push(current);
864
+ current = "";
865
+ continue;
866
+ }
867
+ current += char;
868
+ }
869
+ cells.push(current);
870
+ return cells;
871
+ }
937
872
  function splitTableCells(line) {
938
- return line.trim().slice(1, -1).split("|").map((cell) => cell.trim());
873
+ return splitTableRowCells(line.trim().slice(1, -1)).map((cell) => cell.trim());
939
874
  }
940
875
  function splitPartialTableCells(line) {
941
- return line.trim().replace(/^\|/, "").split("|").map((cell) => cell.trim()).filter((cell) => cell.length > 0);
876
+ return splitTableRowCells(line.trim().replace(/^\|/, "")).map((cell) => cell.trim()).filter((cell) => cell.length > 0);
942
877
  }
943
878
  function mergeRowFragments(pending, next) {
944
879
  return pending ? `${pending}${next}` : next;
@@ -1064,16 +999,8 @@ function resolveQQBotGroupToolPolicy(params) {
1064
999
  }
1065
1000
  //#endregion
1066
1001
  //#region extensions/qqbot/src/channel.ts
1067
- let gatewayModulePromise;
1068
- function loadGatewayModule() {
1069
- gatewayModulePromise ??= import("./gateway-CCkrdgaK.js");
1070
- return gatewayModulePromise;
1071
- }
1072
- let outboundMessagingModulePromise;
1073
- function loadOutboundMessagingModule() {
1074
- outboundMessagingModulePromise ??= import("./outbound-BQcPCo-z.js").then((n) => n.t);
1075
- return outboundMessagingModulePromise;
1076
- }
1002
+ const loadGatewayModule = createLazyRuntimeModule(() => import("./gateway-CjdB3B6n.js"));
1003
+ const loadOutboundMessagingModule = createLazyRuntimeModule(() => import("./outbound-BZHwMFUX.js").then((n) => n.t));
1077
1004
  function createQQBotSendReceipt(params) {
1078
1005
  const messageId = params.messageId?.trim();
1079
1006
  return createMessageReceiptFromOutboundResults({
@@ -1095,7 +1022,10 @@ async function sendQQBotText(params) {
1095
1022
  text: params.text,
1096
1023
  accountId: params.accountId,
1097
1024
  replyToId: params.replyToId,
1098
- account: toGatewayAccount(account)
1025
+ account: toGatewayAccount(account),
1026
+ ...params.mediaAccess ? { mediaAccess: params.mediaAccess } : {},
1027
+ ...params.mediaLocalRoots ? { mediaLocalRoots: params.mediaLocalRoots } : {},
1028
+ ...params.mediaReadFile ? { mediaReadFile: params.mediaReadFile } : {}
1099
1029
  });
1100
1030
  return {
1101
1031
  channel: "qqbot",
@@ -1118,7 +1048,10 @@ async function sendQQBotMedia(params) {
1118
1048
  mediaUrl: params.mediaUrl ?? "",
1119
1049
  accountId: params.accountId,
1120
1050
  replyToId: params.replyToId,
1121
- account: toGatewayAccount(account)
1051
+ account: toGatewayAccount(account),
1052
+ ...params.mediaAccess ? { mediaAccess: params.mediaAccess } : {},
1053
+ ...params.mediaLocalRoots ? { mediaLocalRoots: params.mediaLocalRoots } : {},
1054
+ ...params.mediaReadFile ? { mediaReadFile: params.mediaReadFile } : {}
1122
1055
  });
1123
1056
  return {
1124
1057
  channel: "qqbot",
@@ -1131,6 +1064,14 @@ async function sendQQBotMedia(params) {
1131
1064
  meta: result.error ? { error: result.error } : void 0
1132
1065
  };
1133
1066
  }
1067
+ function resolveQQBotOutboundMediaAccessContext(ctx) {
1068
+ const record = ctx && typeof ctx === "object" ? ctx : void 0;
1069
+ return {
1070
+ ...record?.mediaAccess ? { mediaAccess: record.mediaAccess } : {},
1071
+ ...record?.mediaLocalRoots ? { mediaLocalRoots: record.mediaLocalRoots } : {},
1072
+ ...record?.mediaReadFile ? { mediaReadFile: record.mediaReadFile } : {}
1073
+ };
1074
+ }
1134
1075
  function toQQBotMessageSendResult(result) {
1135
1076
  if (result.meta?.error) throw new Error(result.meta.error);
1136
1077
  if (result.receipt.platformMessageIds.length === 0) throw new Error("QQBot message adapter send did not return a platform message id");
@@ -1152,7 +1093,8 @@ const qqbotMessageAdapter = defineChannelMessageAdapter({
1152
1093
  to: ctx.to,
1153
1094
  text: ctx.text,
1154
1095
  accountId: ctx.accountId,
1155
- replyToId: ctx.replyToId
1096
+ replyToId: ctx.replyToId,
1097
+ ...resolveQQBotOutboundMediaAccessContext(ctx)
1156
1098
  })),
1157
1099
  media: async (ctx) => toQQBotMessageSendResult(await sendQQBotMedia({
1158
1100
  cfg: ctx.cfg,
@@ -1160,7 +1102,8 @@ const qqbotMessageAdapter = defineChannelMessageAdapter({
1160
1102
  text: ctx.text,
1161
1103
  mediaUrl: ctx.mediaUrl,
1162
1104
  accountId: ctx.accountId,
1163
- replyToId: ctx.replyToId
1105
+ replyToId: ctx.replyToId,
1106
+ ...resolveQQBotOutboundMediaAccessContext(ctx)
1164
1107
  }))
1165
1108
  }
1166
1109
  });
@@ -1231,20 +1174,22 @@ const qqbotPlugin = {
1231
1174
  payload,
1232
1175
  hint
1233
1176
  }),
1234
- sendText: async ({ to, text, accountId, replyToId, cfg }) => await sendQQBotText({
1235
- cfg,
1236
- to,
1237
- text,
1238
- accountId,
1239
- replyToId
1177
+ sendText: async (ctx) => await sendQQBotText({
1178
+ cfg: ctx.cfg,
1179
+ to: ctx.to,
1180
+ text: ctx.text,
1181
+ accountId: ctx.accountId,
1182
+ replyToId: ctx.replyToId,
1183
+ ...resolveQQBotOutboundMediaAccessContext(ctx)
1240
1184
  }),
1241
- sendMedia: async ({ to, text, mediaUrl, accountId, replyToId, cfg }) => await sendQQBotMedia({
1242
- cfg,
1243
- to,
1244
- text,
1245
- mediaUrl,
1246
- accountId,
1247
- replyToId
1185
+ sendMedia: async (ctx) => await sendQQBotMedia({
1186
+ cfg: ctx.cfg,
1187
+ to: ctx.to,
1188
+ text: ctx.text,
1189
+ mediaUrl: ctx.mediaUrl,
1190
+ accountId: ctx.accountId,
1191
+ replyToId: ctx.replyToId,
1192
+ ...resolveQQBotOutboundMediaAccessContext(ctx)
1248
1193
  })
1249
1194
  },
1250
1195
  gateway: {
@@ -1349,4 +1294,4 @@ const qqbotPlugin = {
1349
1294
  }
1350
1295
  };
1351
1296
  //#endregion
1352
- export { toGatewayAccount as a, matchesQQBotApprovalAccount as c, buildApprovalKeyboard as d, buildExecApprovalText as f, resolveApprovalTarget as h, openQQBotSyncKeyedStore as i, resolveQQBotExecApprovalConfig as l, parseApprovalButtonData as m, createQQBotMarkdownChunker as n, authorizeQQBotApprovalAction as o, buildPluginApprovalText as p, buildQQBotStateKey as r, isQQBotExecApprovalClientEnabled as s, qqbotPlugin as t, shouldHandleQQBotExecApprovalRequest as u };
1297
+ export { authorizeQQBotApprovalAction as a, resolveQQBotExecApprovalConfig as c, buildExecApprovalText as d, buildPluginApprovalText as f, toGatewayAccount as i, shouldHandleQQBotExecApprovalRequest as l, resolveApprovalTarget as m, createQQBotMarkdownChunker as n, isQQBotExecApprovalClientEnabled as o, parseApprovalButtonData as p, openQQBotSyncKeyedStore as r, matchesQQBotApprovalAccount as s, qqbotPlugin as t, buildApprovalKeyboard as u };
@@ -1,7 +1,7 @@
1
1
  import { a as resolveQQBotAccount } from "./config-C1qZbh0K.js";
2
- import { d as PRIVATE_CHAT_ONLY_TEXT, n as resolveGroupCommandLevelFromAccountConfig, o as getFrameworkCommands } from "./group-CvVXke_k.js";
3
- import { t as registerQQBotTools } from "./tools-CeUI9pG-.js";
4
- import { h as sendDocument } from "./outbound-BQcPCo-z.js";
2
+ import { d as PRIVATE_CHAT_ONLY_TEXT, n as resolveGroupCommandLevelFromAccountConfig, o as getFrameworkCommands } from "./group-BmS1Qwst.js";
3
+ import { h as sendDocument } from "./outbound-BZHwMFUX.js";
4
+ import { t as registerQQBotTools } from "./tools-C8kTMXKc.js";
5
5
  //#region extensions/qqbot/src/bridge/commands/framework-context-adapter.ts
6
6
  /**
7
7
  * Default queue snapshot used for framework-registered commands.
@@ -1,2 +1,2 @@
1
- import { t as registerQQBotFull } from "./channel-entry-BGXFRmbg.js";
1
+ import { t as registerQQBotFull } from "./channel-entry-C18ypTaG.js";
2
2
  export { registerQQBotFull };
@@ -1,2 +1,2 @@
1
- import { t as qqbotPlugin } from "./channel-DvWrqcZB.js";
1
+ import { t as qqbotPlugin } from "./channel-BQM6pDlD.js";
2
2
  export { qqbotPlugin };
@@ -1,4 +1,4 @@
1
- import { a as qqbotSetupAdapterShared, i as qqbotMeta, n as qqbotSetupWizard, r as qqbotConfigAdapter, t as qqbotChannelConfigSchema } from "./config-schema-gMW9zKvQ.js";
1
+ import { a as qqbotSetupAdapterShared, i as qqbotMeta, n as qqbotSetupWizard, r as qqbotConfigAdapter, t as qqbotChannelConfigSchema } from "./config-schema-JZEf1dvB.js";
2
2
  //#region extensions/qqbot/src/channel.setup.ts
3
3
  /**
4
4
  * Setup-only QQBot plugin — lightweight subset used during `openclaw onboard`
@@ -1,5 +1,6 @@
1
1
  import { d as registerPlatformAdapterFactory, l as hasPlatformAdapter, n as normalizeLowercaseStringOrEmpty, u as registerPlatformAdapter } from "./string-normalize-R_0cKO7Q.js";
2
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-C1qZbh0K.js";
3
+ import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
3
4
  import { buildSecretInputSchema, hasConfiguredSecretInput, normalizeResolvedSecretInputString, normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
4
5
  import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
5
6
  import { applyAccountNameToChannelSection, deleteAccountFromConfigSection, setAccountEnabledInConfigSection } from "openclaw/plugin-sdk/core";
@@ -47,11 +48,7 @@ function getBridgeLogger() {
47
48
  * statically at the top level so they work reliably in both production and
48
49
  * vitest (which resolves bare specifiers via `resolve.alias`, not Node CJS).
49
50
  */
50
- let mediaRuntimeModulePromise = null;
51
- const loadMediaRuntimeModule = async () => {
52
- mediaRuntimeModulePromise ??= import("openclaw/plugin-sdk/media-runtime");
53
- return await mediaRuntimeModulePromise;
54
- };
51
+ const loadMediaRuntimeModule = createLazyRuntimeModule(() => import("openclaw/plugin-sdk/media-runtime"));
55
52
  function createBuiltinAdapter() {
56
53
  return {
57
54
  async validateRemoteUrl(_url, _options) {},
@@ -1,2 +1,168 @@
1
- import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-BYTS8-ia.js";
2
- export { legacyConfigRules, normalizeCompatibilityConfig };
1
+ import { n as legacyConfigRules, r as normalizeCompatibilityConfig, t as buildQQBotStateKey } from "./state-keys-CQKlAFyo.js";
2
+ import path from "node:path";
3
+ import fs from "node:fs/promises";
4
+ //#region extensions/qqbot/src/state-migrations.ts
5
+ const CREDENTIAL_BACKUPS_NAMESPACE = "credential-backups";
6
+ const MAX_CREDENTIAL_BACKUPS = 1e3;
7
+ function safeName(id) {
8
+ return id.replace(/[^a-zA-Z0-9._-]/g, "_");
9
+ }
10
+ async function fileExists(filePath) {
11
+ try {
12
+ return (await fs.lstat(filePath)).isFile();
13
+ } catch {
14
+ return false;
15
+ }
16
+ }
17
+ async function readCredentialBackup(filePath) {
18
+ try {
19
+ const parsed = JSON.parse(await fs.readFile(filePath, "utf8"));
20
+ if (typeof parsed.accountId !== "string" || typeof parsed.appId !== "string" || typeof parsed.clientSecret !== "string" || !parsed.accountId || !parsed.appId || !parsed.clientSecret) return null;
21
+ return {
22
+ accountId: parsed.accountId,
23
+ appId: parsed.appId,
24
+ clientSecret: parsed.clientSecret,
25
+ savedAt: typeof parsed.savedAt === "string" && parsed.savedAt ? parsed.savedAt : (/* @__PURE__ */ new Date(0)).toISOString()
26
+ };
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+ function credentialBackupKey(accountId) {
32
+ return buildQQBotStateKey("credential-backup", accountId);
33
+ }
34
+ async function credentialBackupCandidates(stateDir) {
35
+ const dataDir = path.join(stateDir, "qqbot", "data");
36
+ const accountFiles = [];
37
+ try {
38
+ for (const entry of await fs.readdir(dataDir, { withFileTypes: true })) if (entry.isFile() && entry.name.startsWith("credential-backup-") && entry.name.endsWith(".json")) accountFiles.push({
39
+ sourcePath: path.join(dataDir, entry.name),
40
+ expectedSafeAccountId: entry.name.slice(18, -5)
41
+ });
42
+ } catch {}
43
+ accountFiles.sort((left, right) => left.sourcePath.localeCompare(right.sourcePath));
44
+ const singlePath = path.join(dataDir, "credential-backup.json");
45
+ return await fileExists(singlePath) ? [...accountFiles, { sourcePath: singlePath }] : accountFiles;
46
+ }
47
+ async function readLegacyCredentialBackups(stateDir) {
48
+ const backups = [];
49
+ for (const candidate of await credentialBackupCandidates(stateDir)) {
50
+ const value = await readCredentialBackup(candidate.sourcePath);
51
+ if (!value || candidate.expectedSafeAccountId !== void 0 && safeName(value.accountId) !== candidate.expectedSafeAccountId) continue;
52
+ backups.push({
53
+ sourcePath: candidate.sourcePath,
54
+ key: credentialBackupKey(value.accountId),
55
+ value
56
+ });
57
+ }
58
+ return backups;
59
+ }
60
+ async function archiveLegacySource(params) {
61
+ const archivedPath = `${params.sourcePath}.migrated`;
62
+ if (await fileExists(archivedPath)) {
63
+ params.warnings.push(`Left QQBot credential backup in place because ${archivedPath} already exists`);
64
+ return;
65
+ }
66
+ try {
67
+ await fs.chmod(params.sourcePath, 384);
68
+ } catch (err) {
69
+ params.warnings.push(`Failed securing QQBot credential backup legacy source: ${String(err)}`);
70
+ return;
71
+ }
72
+ try {
73
+ await fs.rename(params.sourcePath, archivedPath);
74
+ try {
75
+ await fs.chmod(archivedPath, 384);
76
+ } catch (err) {
77
+ params.warnings.push(`Failed securing archived QQBot credential backup legacy source: ${String(err)}`);
78
+ }
79
+ params.changes.push(`Archived QQBot credential backup legacy source -> ${archivedPath}`);
80
+ } catch (err) {
81
+ params.warnings.push(`Failed archiving QQBot credential backup: ${String(err)}`);
82
+ }
83
+ }
84
+ function sameCredentialBackup(left, right) {
85
+ return left?.accountId === right.accountId && left.appId === right.appId && left.clientSecret === right.clientSecret && left.savedAt === right.savedAt;
86
+ }
87
+ async function rollbackCredentialImports(store, inserted, existing) {
88
+ for (const [key, value] of [...inserted].toReversed()) if (sameCredentialBackup(await store.lookup(key), value)) await store.delete(key);
89
+ for (const [key, value] of existing) if (await store.lookup(key) === void 0) await store.registerIfAbsent(key, value);
90
+ }
91
+ function findMissingKey(expected, actual) {
92
+ for (const key of expected) if (!actual.has(key)) return key;
93
+ return null;
94
+ }
95
+ const stateMigrations = [{
96
+ id: "qqbot-credential-backups-json-to-plugin-state",
97
+ label: "QQBot credential backups",
98
+ async detectLegacyState(params) {
99
+ const backups = await readLegacyCredentialBackups(params.stateDir);
100
+ if (backups.length === 0) return null;
101
+ return { preview: [`- QQBot credential backups: ${backups.length} ${backups.length === 1 ? "file" : "files"} -> plugin state (${CREDENTIAL_BACKUPS_NAMESPACE})`] };
102
+ },
103
+ async migrateLegacyState(params) {
104
+ const changes = [];
105
+ const warnings = [];
106
+ const backups = await readLegacyCredentialBackups(params.stateDir);
107
+ if (backups.length === 0) return {
108
+ changes,
109
+ warnings
110
+ };
111
+ const selectedByKey = /* @__PURE__ */ new Map();
112
+ for (const backup of backups) if (!selectedByKey.has(backup.key)) selectedByKey.set(backup.key, backup);
113
+ const store = params.context.openPluginStateKeyedStore({
114
+ namespace: CREDENTIAL_BACKUPS_NAMESPACE,
115
+ maxEntries: MAX_CREDENTIAL_BACKUPS
116
+ });
117
+ const existingEntries = await store.entries();
118
+ const existingValues = new Map(existingEntries.map((entry) => [entry.key, entry.value]));
119
+ const existingKeys = new Set(existingValues.keys());
120
+ const missing = [...selectedByKey.values()].filter((backup) => !existingKeys.has(backup.key));
121
+ const available = MAX_CREDENTIAL_BACKUPS - existingKeys.size;
122
+ if (missing.length > available) {
123
+ warnings.push(`Skipped QQBot credential backup migration because plugin state has room for ${available} of ${missing.length} missing entries; left legacy sources in place`);
124
+ return {
125
+ changes,
126
+ warnings
127
+ };
128
+ }
129
+ const expectedKeys = new Set(existingKeys);
130
+ const inserted = /* @__PURE__ */ new Map();
131
+ for (const backup of missing) try {
132
+ if (await store.registerIfAbsent(backup.key, backup.value)) inserted.set(backup.key, backup.value);
133
+ const missingKey = findMissingKey(new Set(expectedKeys).add(backup.key), new Set((await store.entries()).map((entry) => entry.key)));
134
+ if (missingKey) {
135
+ await rollbackCredentialImports(store, inserted, existingValues);
136
+ warnings.push(`Stopped QQBot credential backup migration because plugin state capacity evicted ${missingKey}; restored credential state and left legacy sources in place`);
137
+ return {
138
+ changes,
139
+ warnings
140
+ };
141
+ }
142
+ expectedKeys.add(backup.key);
143
+ } catch (err) {
144
+ try {
145
+ await rollbackCredentialImports(store, inserted, existingValues);
146
+ } catch (rollbackErr) {
147
+ warnings.push(`Failed restoring QQBot credential state after migration error: ${String(rollbackErr)}`);
148
+ }
149
+ warnings.push(`Failed migrating QQBot credential backup: ${String(err)}; left legacy sources in place`);
150
+ return {
151
+ changes,
152
+ warnings
153
+ };
154
+ }
155
+ if (inserted.size > 0) changes.push(`Migrated ${inserted.size} QQBot credential ${inserted.size === 1 ? "backup" : "backups"} -> plugin state`);
156
+ for (const backup of backups) await archiveLegacySource({
157
+ sourcePath: backup.sourcePath,
158
+ changes,
159
+ warnings
160
+ });
161
+ return {
162
+ changes,
163
+ warnings
164
+ };
165
+ }
166
+ }];
167
+ //#endregion
168
+ export { legacyConfigRules, normalizeCompatibilityConfig, stateMigrations };