@openclaw/qqbot 2026.7.1 → 2026.7.2-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.
Files changed (41) hide show
  1. package/dist/api.js +8 -7
  2. package/dist/{channel-entry-C5YdhX3Y.js → channel-entry-CA2T2sf8.js} +4 -4
  3. package/dist/channel-entry-api.js +1 -1
  4. package/dist/{channel-D1UztsnG.js → channel-nz3Mkify.js} +99 -54
  5. package/dist/channel-plugin-api.js +1 -1
  6. package/dist/{channel.setup-2ItDYKhz.js → channel.setup-CNoBtKXQ.js} +1 -1
  7. package/dist/{config-C1qZbh0K.js → config-CpOXnoEc.js} +2 -2
  8. package/dist/{config-schema-JZEf1dvB.js → config-schema-D7MaH5X5.js} +61 -76
  9. package/dist/doctor-contract-api.js +4 -10
  10. package/dist/{gateway-pJQppxe4.js → gateway-D8uYPtoy.js} +286 -174
  11. package/dist/{group-o0GmovSf.js → group-BVHG8qUZ.js} +40 -23
  12. package/dist/{handler-runtime-zQvT6SrI.js → handler-runtime-S1_XF8on.js} +8 -12
  13. package/dist/{log-DEtcoDWe.js → log-Da4jz75I.js} +4 -8
  14. package/dist/{outbound-BIrfvvFJ.js → outbound-FOG4zNLY.js} +8 -142
  15. package/dist/{runtime-DodcT_fQ.js → runtime-CyjBiGD2.js} +2 -2
  16. package/dist/runtime-api.js +1 -1
  17. package/dist/secret-contract-api.js +6 -22
  18. package/dist/{sender-CjDuU-uz.js → sender-BAUHZqDW.js} +225 -66
  19. package/dist/setup-plugin-api.js +1 -1
  20. package/dist/state-keys-jLJ2SmJA.js +225 -0
  21. package/dist/{tools-UJJ-tLHP.js → tools-CC5CKQig.js} +71 -26
  22. package/dist/tools-api.js +1 -1
  23. package/node_modules/p-map/index.d.ts +155 -0
  24. package/node_modules/p-map/index.js +284 -0
  25. package/node_modules/p-map/license +9 -0
  26. package/node_modules/p-map/package.json +57 -0
  27. package/node_modules/p-map/readme.md +190 -0
  28. package/node_modules/parse-ms/index.d.ts +30 -0
  29. package/node_modules/parse-ms/index.js +45 -0
  30. package/node_modules/parse-ms/license +9 -0
  31. package/node_modules/parse-ms/package.json +47 -0
  32. package/node_modules/parse-ms/readme.md +46 -0
  33. package/node_modules/pretty-ms/index.d.ts +157 -0
  34. package/node_modules/pretty-ms/index.js +149 -0
  35. package/node_modules/pretty-ms/license +9 -0
  36. package/node_modules/pretty-ms/package.json +55 -0
  37. package/node_modules/pretty-ms/readme.md +179 -0
  38. package/npm-shrinkwrap.json +44 -3
  39. package/openclaw.plugin.json +105 -133
  40. package/package.json +8 -4
  41. package/dist/state-keys-CQKlAFyo.js +0 -141
@@ -1,11 +1,12 @@
1
1
  import { t as asOptionalObjectRecord } from "./string-normalize-R_0cKO7Q.js";
2
- import { d as resolveAccountBase } from "./config-C1qZbh0K.js";
3
- import { n as debugLog } from "./log-DEtcoDWe.js";
4
- import { I as getQQBotMediaPath, N as getHomeDir, P as getQQBotDataDir, z as isWindows } from "./outbound-BIrfvvFJ.js";
2
+ import { d as resolveAccountBase } from "./config-CpOXnoEc.js";
3
+ import { n as debugLog } from "./log-Da4jz75I.js";
4
+ import { O as getHomeDir, P as isWindows, j as getQQBotMediaPath, k as getQQBotDataDir } from "./outbound-FOG4zNLY.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
8
  import { formatByteSize } from "openclaw/plugin-sdk/number-runtime";
9
+ import { resolveScopeRequireMention } from "openclaw/plugin-sdk/channel-policy";
9
10
  import { clearRuntimeConfigSnapshot } from "openclaw/plugin-sdk/runtime-config-snapshot";
10
11
  import { loadJsonFile } from "openclaw/plugin-sdk/json-store";
11
12
  //#region extensions/qqbot/src/engine/commands/builtin/state.ts
@@ -666,10 +667,15 @@ function tailFileLines(filePath, maxLines) {
666
667
  const readSize = Math.min(CHUNK_SIZE, position);
667
668
  position -= readSize;
668
669
  const buf = Buffer.alloc(readSize);
669
- fs.readSync(fd, buf, 0, readSize, position);
670
+ let actualRead = 0;
671
+ while (actualRead < readSize) {
672
+ const justRead = fs.readSync(fd, buf, actualRead, readSize - actualRead, position + actualRead);
673
+ if (justRead === 0) throw new Error(`Could not complete log read for ${filePath}`);
674
+ actualRead += justRead;
675
+ }
670
676
  chunks.unshift(buf);
671
- bytesRead += readSize;
672
- for (let i = 0; i < readSize; i++) if (buf[i] === 10) newlineCount++;
677
+ bytesRead += actualRead;
678
+ for (let i = 0; i < actualRead; i++) if (buf[i] === 10) newlineCount++;
673
679
  }
674
680
  const allLines = Buffer.concat(chunks).toString("utf8").split("\n");
675
681
  const tail = allLines.slice(-maxLines);
@@ -766,15 +772,10 @@ function registerLogCommands(registry) {
766
772
  //#endregion
767
773
  //#region extensions/qqbot/src/engine/commands/builtin/register-streaming.ts
768
774
  function isStreamingConfigEnabled(streaming) {
769
- if (streaming === true) return true;
770
- if (streaming === false || streaming === void 0 || streaming === null) return false;
771
- if (typeof streaming === "object") {
772
- const o = streaming;
773
- if (o.c2cStreamApi === true) return true;
774
- if (o.mode === "off") return false;
775
- return true;
776
- }
777
- return false;
775
+ if (!streaming || typeof streaming !== "object") return false;
776
+ const o = streaming;
777
+ if (o.nativeTransport === true) return true;
778
+ return o.mode !== "off";
778
779
  }
779
780
  function registerStreamingCommands(registry) {
780
781
  registry.register({
@@ -820,7 +821,7 @@ function registerStreamingCommands(registry) {
820
821
  ``,
821
822
  `\`\`\`shell`,
822
823
  `# 1. 开启流式消息`,
823
- `openclaw config set channels.qqbot.streaming true`,
824
+ `openclaw config set channels.qqbot.streaming.nativeTransport true`,
824
825
  ``,
825
826
  `# 2. 重启网关使配置生效`,
826
827
  `openclaw gateway restart`,
@@ -833,7 +834,10 @@ function registerStreamingCommands(registry) {
833
834
  const qqbot = (currentCfg.channels ?? {}).qqbot;
834
835
  if (!qqbot) return `❌ 配置文件中未找到 qqbot 通道配置`;
835
836
  const accountId = ctx.accountId;
836
- const newVal = wantOn;
837
+ const newVal = wantOn ? {
838
+ mode: "partial",
839
+ nativeTransport: true
840
+ } : { mode: "off" };
837
841
  if (accountId !== "default") {
838
842
  const nextAccounts = { ...qqbot.accounts ?? {} };
839
843
  const acct = { ...nextAccounts[accountId] };
@@ -912,7 +916,7 @@ const PRIVATE_ONLY_CORE_COMMANDS = /* @__PURE__ */ new Set([
912
916
  "tools",
913
917
  "skill",
914
918
  "diagnostics",
915
- "crestodian",
919
+ "openclaw",
916
920
  "tasks",
917
921
  "allowlist",
918
922
  "approve",
@@ -1097,13 +1101,17 @@ function getPluginVersion() {
1097
1101
  function getFrameworkVersion() {
1098
1102
  return getFrameworkVersionString();
1099
1103
  }
1104
+ //#endregion
1105
+ //#region extensions/qqbot/src/engine/config/group.ts
1106
+ const DEFAULT_GROUP_HISTORY_LIMIT = 50;
1107
+ const DEFAULT_GROUP_COMMAND_LEVEL = "all";
1100
1108
  const DEFAULT_GROUP_PROMPT = "If the sender is a bot, respond only when they explicitly @mention you to ask a question or request assistance with a specific task; keep your replies concise and clear, avoiding the urge to race other bots to answer or engage in lengthy, unproductive exchanges. In group chats, prioritize responding to messages from human users; bots should maintain a collaborative rather than competitive dynamic to ensure the conversation remains orderly and does not result in message flooding.";
1101
1109
  const DEFAULT_GROUP_CONFIG = {
1102
1110
  requireMention: true,
1103
1111
  ignoreOtherMentions: false,
1104
- commandLevel: "all",
1112
+ commandLevel: DEFAULT_GROUP_COMMAND_LEVEL,
1105
1113
  name: "",
1106
- historyLimit: 50
1114
+ historyLimit: DEFAULT_GROUP_HISTORY_LIMIT
1107
1115
  };
1108
1116
  function readGroupsMap(cfg, accountId) {
1109
1117
  const groups = asOptionalObjectRecord(resolveAccountBase(cfg, accountId).config.groups);
@@ -1134,11 +1142,20 @@ function readHistoryLimit(obj, key) {
1134
1142
  function resolveGroupConfig(cfg, groupOpenid, accountId) {
1135
1143
  const account = resolveAccountBase(cfg, accountId);
1136
1144
  const groups = readGroupsMap(cfg, accountId);
1137
- const wildcard = groups["*"] ?? {};
1145
+ const { "*": wildcard = {}, ...scopes } = groups;
1138
1146
  const specific = groupOpenid ? groups[groupOpenid] ?? {} : {};
1139
- const accountDefaultRequireMention = asBoolean(account.config.defaultRequireMention) ?? DEFAULT_GROUP_CONFIG.requireMention;
1147
+ const accountDefaultRequireMention = asBoolean(account.config.defaultRequireMention);
1148
+ const mentionTree = {
1149
+ defaults: { requireMention: readBoolean(wildcard, "requireMention") },
1150
+ scopes: Object.fromEntries(Object.entries(scopes).map(([key, entry]) => [key, { requireMention: readBoolean(entry, "requireMention") }]))
1151
+ };
1140
1152
  return {
1141
- requireMention: readBoolean(specific, "requireMention") ?? readBoolean(wildcard, "requireMention") ?? accountDefaultRequireMention,
1153
+ requireMention: resolveScopeRequireMention({
1154
+ tree: mentionTree,
1155
+ path: groupOpenid && Object.hasOwn(mentionTree.scopes, groupOpenid) ? [groupOpenid] : [],
1156
+ requireMentionOverride: accountDefaultRequireMention,
1157
+ overrideOrder: "after-config"
1158
+ }),
1142
1159
  ignoreOtherMentions: readBoolean(specific, "ignoreOtherMentions") ?? readBoolean(wildcard, "ignoreOtherMentions") ?? DEFAULT_GROUP_CONFIG.ignoreOtherMentions,
1143
1160
  commandLevel: readCommandLevel(specific, "commandLevel") ?? readCommandLevel(wildcard, "commandLevel") ?? DEFAULT_GROUP_CONFIG.commandLevel,
1144
1161
  name: readString(specific, "name") ?? readString(wildcard, "name") ?? DEFAULT_GROUP_CONFIG.name,
@@ -1,13 +1,10 @@
1
- import { c as getMessageApi, t as accountToCreds } from "./sender-CjDuU-uz.js";
2
- import { o as ensurePlatformAdapter, s as getBridgeLogger } from "./config-schema-JZEf1dvB.js";
3
- import { c as resolveQQBotExecApprovalConfig, d as buildExecApprovalText, f as buildPluginApprovalText, l as shouldHandleQQBotExecApprovalRequest, m as resolveApprovalTarget, o as isQQBotExecApprovalClientEnabled, s as matchesQQBotApprovalAccount, u as buildApprovalKeyboard } from "./channel-D1UztsnG.js";
4
- import { a as resolveQQBotAccount } from "./config-C1qZbh0K.js";
1
+ import { c as getMessageApi, t as accountToCreds } from "./sender-BAUHZqDW.js";
2
+ import { o as ensurePlatformAdapter, s as getBridgeLogger } from "./config-schema-D7MaH5X5.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-nz3Mkify.js";
4
+ import { a as resolveQQBotAccount } from "./config-CpOXnoEc.js";
5
5
  import { resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
6
6
  import { createChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
7
7
  //#region extensions/qqbot/src/bridge/approval/handler-runtime.ts
8
- function isExecRequest(request) {
9
- return "expiresAtMs" in request;
10
- }
11
8
  function resolveQQTarget(request) {
12
9
  const sessionConversation = resolveApprovalRequestSessionConversation({
13
10
  request,
@@ -70,11 +67,10 @@ const qqbotApprovalNativeRuntime = createChannelApprovalNativeRuntimeAdapter({
70
67
  }
71
68
  },
72
69
  presentation: {
73
- buildPendingPayload: ({ request, view }) => {
74
- const req = request;
75
- const text = isExecRequest(req) ? buildExecApprovalText(req) : buildPluginApprovalText(req);
76
- const keyboard = buildApprovalKeyboard(req.id, view.actions.map((action) => action.decision));
77
- getBridgeLogger().debug?.(`[qqbot:approval-runtime] buildPendingPayload requestId=${req.id} kind=${isExecRequest(req) ? "exec" : "plugin"}`);
70
+ buildPendingPayload: ({ view, nowMs }) => {
71
+ const text = view.approvalKind === "exec" ? buildExecApprovalText(view, nowMs) : buildPluginApprovalText(view, nowMs);
72
+ const keyboard = buildApprovalKeyboard(view.approvalId, view.approvalKind, view.actions.map((action) => action.decision));
73
+ getBridgeLogger().debug?.(`[qqbot:approval-runtime] buildPendingPayload requestId=${view.approvalId} kind=${view.approvalKind}`);
78
74
  return {
79
75
  text,
80
76
  keyboard
@@ -1,12 +1,11 @@
1
1
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
2
+ import prettyMilliseconds from "pretty-ms";
2
3
  //#region extensions/qqbot/src/engine/utils/format.ts
3
4
  /**
4
5
  * General formatting and string utilities.
5
6
  * 通用格式化与字符串工具。
6
7
  *
7
- * Pure utility functions with zero external dependencies.
8
- * Replaces `openclaw/plugin-sdk/error-runtime` and `text-runtime`
9
- * helpers for use inside engine/.
8
+ * Pure utility functions, with duration presentation from the plugin-local dependency.
10
9
  *
11
10
  * NOTE: The framework `formatErrorMessage` also applies `redactSensitiveText()`
12
11
  * for token masking. We intentionally omit that here — the framework's log
@@ -46,11 +45,8 @@ function formatErrorMessage(err) {
46
45
  }
47
46
  /** Format a millisecond duration into a human-readable string (e.g. "5m 30s"). */
48
47
  function formatDuration(durationMs) {
49
- const seconds = Math.round(durationMs / 1e3);
50
- if (seconds < 60) return `${seconds}s`;
51
- const minutes = Math.floor(seconds / 60);
52
- const remainSeconds = seconds % 60;
53
- return remainSeconds > 0 ? `${minutes}m ${remainSeconds}s` : `${minutes}m`;
48
+ if (durationMs <= 0) return "0s";
49
+ return prettyMilliseconds(durationMs < 1e3 ? Math.round(durationMs) : Math.round(durationMs / 1e3) * 1e3, { unitCount: 2 });
54
50
  }
55
51
  //#endregion
56
52
  //#region extensions/qqbot/src/engine/utils/log.ts
@@ -1,6 +1,6 @@
1
- import { A as getFileTypeName, D as downloadFile, E as checkFileSize, L as __exportAll, M as getMaxUploadSize, N as readFileAsync, O as fileExistsAsync, S as UploadDailyLimitExceededError, g as sendText$1, h as sendMedia$1, j as getImageMimeType, k as formatFileSize, t as accountToCreds, u as initApiConfig, w as UPLOAD_PREPARE_FALLBACK_CODE } from "./sender-CjDuU-uz.js";
1
+ import { A as UPLOAD_PREPARE_FALLBACK_CODE, C as checkMessageReplyLimit, D as recordMessageReply, E as getMessageReplyStats, F as formatFileSize, I as getFileTypeName, L as getImageMimeType, M as checkFileSize, N as downloadFile, O as UploadDailyLimitExceededError, P as fileExistsAsync, R as getMaxUploadSize, T as getMessageReplyConfig, U as __exportAll, g as sendText$1, h as sendMedia$1, t as accountToCreds, u as initApiConfig, z as readFileAsync } from "./sender-BAUHZqDW.js";
2
2
  import { c as getPlatformAdapter, i as normalizeOptionalString, n as normalizeLowercaseStringOrEmpty, s as sanitizeFileName } from "./string-normalize-R_0cKO7Q.js";
3
- import { a as formatErrorMessage, n as debugLog, r as debugWarn, t as debugError } from "./log-DEtcoDWe.js";
3
+ import { a as formatErrorMessage, n as debugLog, r as debugWarn, t as debugError } from "./log-Da4jz75I.js";
4
4
  import { r as parseTarget$1 } from "./target-parser-DagYRQkP.js";
5
5
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
6
6
  import * as fs$2 from "node:fs";
@@ -262,122 +262,6 @@ const OUTBOUND_ERROR_CODES = {
262
262
  };
263
263
  const DEFAULT_MEDIA_SEND_ERROR = "发送失败,请稍后重试。";
264
264
  //#endregion
265
- //#region extensions/qqbot/src/engine/messaging/reply-limiter.ts
266
- const DEFAULT_LIMIT = 4;
267
- const DEFAULT_TTL_MS = 3600 * 1e3;
268
- const DEFAULT_MAX_TRACKED = 1e4;
269
- /**
270
- * Per-account reply limiter with automatic eviction.
271
- *
272
- * Usage:
273
- * ```ts
274
- * const limiter = new ReplyLimiter({ limit: 4, ttlMs: 3600000 });
275
- * const check = limiter.checkLimit(messageId);
276
- * if (check.allowed) {
277
- * await sendPassiveReply(...);
278
- * limiter.record(messageId);
279
- * } else if (check.shouldFallbackToProactive) {
280
- * await sendProactiveMessage(...);
281
- * }
282
- * ```
283
- */
284
- var ReplyLimiter = class {
285
- constructor(config) {
286
- this.tracker = /* @__PURE__ */ new Map();
287
- this.limit = config?.limit ?? DEFAULT_LIMIT;
288
- this.ttlMs = config?.ttlMs ?? DEFAULT_TTL_MS;
289
- this.maxTracked = config?.maxTrackedMessages ?? DEFAULT_MAX_TRACKED;
290
- }
291
- /** Check whether a passive reply is allowed for the given message. */
292
- checkLimit(messageId) {
293
- const now = Date.now();
294
- this.evictIfNeeded(now);
295
- const record = this.tracker.get(messageId);
296
- if (!record) return {
297
- allowed: true,
298
- remaining: this.limit,
299
- shouldFallbackToProactive: false
300
- };
301
- if (now - record.firstReplyAt > this.ttlMs) return {
302
- allowed: false,
303
- remaining: 0,
304
- shouldFallbackToProactive: true,
305
- fallbackReason: "expired",
306
- message: `Message is older than ${this.ttlMs / (3600 * 1e3)}h; sending as a proactive message instead`
307
- };
308
- const remaining = this.limit - record.count;
309
- if (remaining <= 0) return {
310
- allowed: false,
311
- remaining: 0,
312
- shouldFallbackToProactive: true,
313
- fallbackReason: "limit_exceeded",
314
- message: `Passive reply limit reached (${this.limit} per hour); sending proactively instead`
315
- };
316
- return {
317
- allowed: true,
318
- remaining,
319
- shouldFallbackToProactive: false
320
- };
321
- }
322
- /** Record one passive reply against a message. */
323
- record(messageId) {
324
- const now = Date.now();
325
- const existing = this.tracker.get(messageId);
326
- if (!existing) this.tracker.set(messageId, {
327
- count: 1,
328
- firstReplyAt: now
329
- });
330
- else if (now - existing.firstReplyAt > this.ttlMs) this.tracker.set(messageId, {
331
- count: 1,
332
- firstReplyAt: now
333
- });
334
- else existing.count++;
335
- }
336
- /** Return diagnostic stats. */
337
- getStats() {
338
- let totalReplies = 0;
339
- for (const record of this.tracker.values()) totalReplies += record.count;
340
- return {
341
- trackedMessages: this.tracker.size,
342
- totalReplies
343
- };
344
- }
345
- /** Return limiter configuration. */
346
- getConfig() {
347
- return {
348
- limit: this.limit,
349
- ttlMs: this.ttlMs,
350
- ttlHours: this.ttlMs / (3600 * 1e3)
351
- };
352
- }
353
- /** Clear all tracked records. */
354
- clear() {
355
- this.tracker.clear();
356
- }
357
- /** Opportunistically evict expired records to keep the tracker bounded. */
358
- evictIfNeeded(now) {
359
- if (this.tracker.size <= this.maxTracked) return;
360
- for (const [id, rec] of this.tracker) if (now - rec.firstReplyAt > this.ttlMs) this.tracker.delete(id);
361
- }
362
- };
363
- //#endregion
364
- //#region extensions/qqbot/src/engine/messaging/outbound-reply.ts
365
- const replyLimiter = new ReplyLimiter();
366
- const MESSAGE_REPLY_LIMIT = 4;
367
- function checkMessageReplyLimit(messageId) {
368
- return replyLimiter.checkLimit(messageId);
369
- }
370
- function recordMessageReply(messageId) {
371
- replyLimiter.record(messageId);
372
- debugLog(`[qqbot] recordMessageReply: ${messageId}, count=${replyLimiter.getStats().totalReplies}`);
373
- }
374
- function getMessageReplyStats() {
375
- return replyLimiter.getStats();
376
- }
377
- function getMessageReplyConfig() {
378
- return replyLimiter.getConfig();
379
- }
380
- //#endregion
381
265
  //#region extensions/qqbot/src/engine/messaging/outbound-result-helpers.ts
382
266
  /**
383
267
  * Convert a media send result into a user-facing message.
@@ -1491,7 +1375,7 @@ function decodeMediaPath(raw, log) {
1491
1375
  const code = decoded.charCodeAt(i);
1492
1376
  if (code <= 255) bytes.push(code);
1493
1377
  else {
1494
- const charBytes = Buffer.from(decoded[i], "utf8");
1378
+ const charBytes = Buffer.from(decoded.charAt(i), "utf8");
1495
1379
  bytes.push(...charBytes);
1496
1380
  }
1497
1381
  }
@@ -1545,7 +1429,7 @@ function isVideoFile$1(filePath, mimeType) {
1545
1429
  //#region extensions/qqbot/src/engine/messaging/outbound.ts
1546
1430
  var outbound_exports = /* @__PURE__ */ __exportAll({
1547
1431
  DEFAULT_MEDIA_SEND_ERROR: () => DEFAULT_MEDIA_SEND_ERROR,
1548
- MESSAGE_REPLY_LIMIT: () => 4,
1432
+ MESSAGE_REPLY_LIMIT: () => 5,
1549
1433
  OUTBOUND_ERROR_CODES: () => OUTBOUND_ERROR_CODES,
1550
1434
  buildMediaTarget: () => buildMediaTarget,
1551
1435
  checkMessageReplyLimit: () => checkMessageReplyLimit,
@@ -1579,8 +1463,8 @@ const mediaPathDecodeLog = {
1579
1463
  */
1580
1464
  async function sendText(ctx) {
1581
1465
  const { to, account } = ctx;
1582
- let { text, replyToId } = ctx;
1583
- let fallbackToProactive = false;
1466
+ const { replyToId } = ctx;
1467
+ let { text } = ctx;
1584
1468
  initApiConfig(account.appId, { markdownSupport: account.markdownSupport });
1585
1469
  debugLog("[qqbot] sendText ctx:", JSON.stringify({
1586
1470
  to,
@@ -1588,21 +1472,6 @@ async function sendText(ctx) {
1588
1472
  replyToId,
1589
1473
  accountId: account.accountId
1590
1474
  }, null, 2));
1591
- if (replyToId) {
1592
- const limitCheck = checkMessageReplyLimit(replyToId);
1593
- if (!limitCheck.allowed) if (limitCheck.shouldFallbackToProactive) {
1594
- debugWarn(`[qqbot] sendText: passive reply unavailable, falling back to proactive send - ${limitCheck.message}`);
1595
- fallbackToProactive = true;
1596
- replyToId = null;
1597
- } else {
1598
- debugError(`[qqbot] sendText: passive reply was blocked without a fallback path - ${limitCheck.message}`);
1599
- return {
1600
- channel: "qqbot",
1601
- error: limitCheck.message
1602
- };
1603
- }
1604
- else debugLog(`[qqbot] sendText: remaining passive replies for ${replyToId}: ${limitCheck.remaining}/4`);
1605
- }
1606
1475
  text = normalizeMediaTags(text);
1607
1476
  const mediaTagMatches = text.match(/<(qqimg|qqvoice|qqvideo|qqfile|qqmedia)>([^<>]+)<\/(?:qqimg|qqvoice|qqvideo|qqfile|qqmedia|img)>/gi);
1608
1477
  if (mediaTagMatches && mediaTagMatches.length > 0) {
@@ -1675,7 +1544,6 @@ async function sendText(ctx) {
1675
1544
  type: target.type === "channel" ? "channel" : target.type,
1676
1545
  id: target.id
1677
1546
  }, item.content, creds, { msgId: replyToId ?? void 0 });
1678
- if (replyToId) recordMessageReply(replyToId);
1679
1547
  lastResult = {
1680
1548
  channel: "qqbot",
1681
1549
  messageId: result.id,
@@ -1716,8 +1584,7 @@ async function sendText(ctx) {
1716
1584
  error: "Proactive messages require non-empty content (--message cannot be empty)"
1717
1585
  };
1718
1586
  }
1719
- if (fallbackToProactive) debugLog(`[qqbot] sendText: [fallback] sending proactive message to ${to}, length=${text.length}`);
1720
- else debugLog(`[qqbot] sendText: sending proactive message to ${to}, length=${text.length}`);
1587
+ debugLog(`[qqbot] sendText: sending proactive message to ${to}, length=${text.length}`);
1721
1588
  }
1722
1589
  if (!account.appId || !account.clientSecret) return {
1723
1590
  channel: "qqbot",
@@ -1732,7 +1599,6 @@ async function sendText(ctx) {
1732
1599
  };
1733
1600
  debugLog("[qqbot] sendText target:", JSON.stringify(target));
1734
1601
  const result = await sendText$1(deliveryTarget, text, creds, { msgId: replyToId ?? void 0 });
1735
- if (replyToId) recordMessageReply(replyToId);
1736
1602
  return {
1737
1603
  channel: "qqbot",
1738
1604
  messageId: result.id,
@@ -1864,4 +1730,4 @@ async function sendCronMessage(account, to, message) {
1864
1730
  });
1865
1731
  }
1866
1732
  //#endregion
1867
- export { OUTBOUND_ERROR_CODES as A, normalizePath$1 as B, resolveUserFacingMediaError as C, getMessageReplyStats as D, getMessageReplyConfig as E, getQQBotMediaDir as F, getQQBotMediaPath as I, getTempDir as L, checkSilkWasmAvailable as M, getHomeDir as N, recordMessageReply as O, getQQBotDataDir as P, isLocalPath as R, resolveWorkspaceScopedLocalRoots as S, checkMessageReplyLimit as T, sendVideoMsg as _, sendText as a, resolveOutboundMediaLocalRoots as b, isCronReminderPayload as c, normalizeMediaTags as d, buildMediaTarget as f, sendPhoto as g, sendDocument as h, sendProactiveMessage as i, setOutboundAudioPort as j, DEFAULT_MEDIA_SEND_ERROR 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, MESSAGE_REPLY_LIMIT as w, resolveWorkspacePathCandidates as x, resolveTrustedOutboundMediaPath as y, isWindows as z };
1733
+ export { getQQBotMediaDir as A, resolveUserFacingMediaError as C, checkSilkWasmAvailable as D, setOutboundAudioPort as E, normalizePath$1 as F, getTempDir as M, isLocalPath as N, getHomeDir as O, isWindows as P, resolveWorkspaceScopedLocalRoots as S, OUTBOUND_ERROR_CODES as T, sendVideoMsg as _, sendText as a, resolveOutboundMediaLocalRoots as b, isCronReminderPayload as c, normalizeMediaTags as d, buildMediaTarget as f, sendPhoto as g, sendDocument as h, sendProactiveMessage as i, getQQBotMediaPath as j, getQQBotDataDir 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, DEFAULT_MEDIA_SEND_ERROR as w, resolveWorkspacePathCandidates as x, resolveTrustedOutboundMediaPath as y };
@@ -1,7 +1,7 @@
1
- import { v as setOpenClawVersion } from "./sender-CjDuU-uz.js";
1
+ import { v as setOpenClawVersion } from "./sender-BAUHZqDW.js";
2
2
  import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
3
3
  //#region extensions/qqbot/src/bridge/runtime.ts
4
- const { setRuntime: _setRuntime, clearRuntime: resetQQBotRuntimeForTest, getRuntime: getQQBotRuntime } = createPluginRuntimeStore({
4
+ const { setRuntime: _setRuntime, getRuntime: getQQBotRuntime } = createPluginRuntimeStore({
5
5
  pluginId: "qqbot",
6
6
  errorMessage: "QQBot runtime not initialized"
7
7
  });
@@ -1,2 +1,2 @@
1
- import { r as setQQBotRuntime, t as getQQBotRuntime } from "./runtime-DodcT_fQ.js";
1
+ import { r as setQQBotRuntime, t as getQQBotRuntime } from "./runtime-CyjBiGD2.js";
2
2
  export { getQQBotRuntime, setQQBotRuntime };
@@ -1,27 +1,11 @@
1
- import { collectConditionalChannelFieldAssignments, getChannelSurface, hasConfiguredSecretInputValue } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
1
+ import { collectConditionalChannelFieldAssignments, createChannelSecretTargetRegistryEntries, getChannelSurface, hasConfiguredSecretInputValue } from "openclaw/plugin-sdk/channel-secret-basic-runtime";
2
2
  //#region extensions/qqbot/src/secret-contract.ts
3
3
  const DEFAULT_ACCOUNT_ID = "default";
4
- const secretTargetRegistryEntries = [{
5
- id: "channels.qqbot.accounts.*.clientSecret",
6
- targetType: "channels.qqbot.accounts.*.clientSecret",
7
- configFile: "openclaw.json",
8
- pathPattern: "channels.qqbot.accounts.*.clientSecret",
9
- secretShape: "secret_input",
10
- expectedResolvedValue: "string",
11
- includeInPlan: true,
12
- includeInConfigure: true,
13
- includeInAudit: true
14
- }, {
15
- id: "channels.qqbot.clientSecret",
16
- targetType: "channels.qqbot.clientSecret",
17
- configFile: "openclaw.json",
18
- pathPattern: "channels.qqbot.clientSecret",
19
- secretShape: "secret_input",
20
- expectedResolvedValue: "string",
21
- includeInPlan: true,
22
- includeInConfigure: true,
23
- includeInAudit: true
24
- }];
4
+ const secretTargetRegistryEntries = createChannelSecretTargetRegistryEntries({
5
+ channelKey: "qqbot",
6
+ account: ["clientSecret"],
7
+ channel: ["clientSecret"]
8
+ });
25
9
  function hasTopLevelAppId(qqbot) {
26
10
  if (typeof qqbot.appId === "string") return qqbot.appId.trim().length > 0;
27
11
  return typeof qqbot.appId === "number";