@openclaw/qqbot 2026.5.24-beta.2 → 2026.5.26-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +6 -6
- package/dist/{channel-M_xgk02s.js → channel-DtN9gbLm.js} +63 -21
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.setup-CNTkQaYC.js → channel.setup-BxGpVkdx.js} +1 -1
- package/dist/{config-schema-CfbaUZcI.js → config-schema-iX2iJzKm.js} +2 -2
- package/dist/{gateway-CkzNqy_N.js → gateway-DuIz6XAh.js} +103 -28
- package/dist/{handler-runtime-BcPUK_xf.js → handler-runtime-I6vCvj2m.js} +3 -3
- package/dist/{outbound-CVDwCPRR.js → outbound-Ce2aX078.js} +3 -3
- package/dist/{request-context-ykvLAJFX.js → request-context-D-rKRREP.js} +4 -3
- package/dist/{runtime-CMJ5DY9K.js → runtime-TzkQ0YbR.js} +189 -189
- package/dist/runtime-api.js +1 -1
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{string-normalize-du8a5Uk3.js → string-normalize-R_0cKO7Q.js} +4 -1
- package/node_modules/ws/lib/receiver.js +54 -0
- package/node_modules/ws/lib/websocket-server.js +8 -0
- package/node_modules/ws/lib/websocket.js +14 -0
- package/node_modules/ws/package.json +1 -1
- package/npm-shrinkwrap.json +7 -7
- package/package.json +5 -5
package/dist/api.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { C as recordMessageReply, E as setOutboundAudioPort, S as getMessageReplyStats, T as OUTBOUND_ERROR_CODES, _ as sendVoice, a as sendText, b as checkMessageReplyLimit, d as buildMediaTarget, f as parseTarget, g as sendVideoMsg, h as sendPhoto, i as sendProactiveMessage, m as sendDocument, n as sendCronMessage, p as resolveOutboundMediaPath, r as sendMedia, v as resolveUserFacingMediaError, w as DEFAULT_MEDIA_SEND_ERROR, x as getMessageReplyConfig, y as MESSAGE_REPLY_LIMIT } from "./outbound-
|
|
2
|
-
import { c as listQQBotAccountIds, l as resolveDefaultQQBotAccountId, o as DEFAULT_ACCOUNT_ID, s as applyQQBotAccountConfig, u as resolveQQBotAccount } from "./config-schema-
|
|
3
|
-
import { t as qqbotPlugin } from "./channel-
|
|
4
|
-
import { C as debugError, l as getAccessToken, w as debugLog, z as formatErrorMessage } from "./runtime-
|
|
5
|
-
import { t as qqbotSetupPlugin } from "./channel.setup-
|
|
6
|
-
import { r as getFrameworkCommands, t as getRequestContext } from "./request-context-
|
|
1
|
+
import { C as recordMessageReply, E as setOutboundAudioPort, S as getMessageReplyStats, T as OUTBOUND_ERROR_CODES, _ as sendVoice, a as sendText, b as checkMessageReplyLimit, d as buildMediaTarget, f as parseTarget, g as sendVideoMsg, h as sendPhoto, i as sendProactiveMessage, m as sendDocument, n as sendCronMessage, p as resolveOutboundMediaPath, r as sendMedia, v as resolveUserFacingMediaError, w as DEFAULT_MEDIA_SEND_ERROR, x as getMessageReplyConfig, y as MESSAGE_REPLY_LIMIT } from "./outbound-Ce2aX078.js";
|
|
2
|
+
import { c as listQQBotAccountIds, l as resolveDefaultQQBotAccountId, o as DEFAULT_ACCOUNT_ID, s as applyQQBotAccountConfig, u as resolveQQBotAccount } from "./config-schema-iX2iJzKm.js";
|
|
3
|
+
import { t as qqbotPlugin } from "./channel-DtN9gbLm.js";
|
|
4
|
+
import { C as debugError, l as getAccessToken, w as debugLog, z as formatErrorMessage } from "./runtime-TzkQ0YbR.js";
|
|
5
|
+
import { t as qqbotSetupPlugin } from "./channel.setup-BxGpVkdx.js";
|
|
6
|
+
import { r as getFrameworkCommands, t as getRequestContext } from "./request-context-D-rKRREP.js";
|
|
7
7
|
import { callGatewayTool } from "openclaw/plugin-sdk/agent-harness-runtime";
|
|
8
8
|
//#region extensions/qqbot/src/engine/tools/channel-api.ts
|
|
9
9
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as getPlatformAdapter, t as asOptionalObjectRecord } from "./string-normalize-
|
|
2
|
-
import { a as qqbotSetupAdapterShared, c as listQQBotAccountIds, i as qqbotMeta, m as getBridgeLogger, n as qqbotSetupWizard, o as DEFAULT_ACCOUNT_ID$1, p as ensurePlatformAdapter, r as qqbotConfigAdapter, s as applyQQBotAccountConfig, t as qqbotChannelConfigSchema, u as resolveQQBotAccount } from "./config-schema-
|
|
3
|
-
import { T as debugWarn, t as getQQBotRuntime, w as debugLog, z as formatErrorMessage } from "./runtime-
|
|
1
|
+
import { c as getPlatformAdapter, t as asOptionalObjectRecord } from "./string-normalize-R_0cKO7Q.js";
|
|
2
|
+
import { a as qqbotSetupAdapterShared, c as listQQBotAccountIds, i as qqbotMeta, m as getBridgeLogger, n as qqbotSetupWizard, o as DEFAULT_ACCOUNT_ID$1, p as ensurePlatformAdapter, r as qqbotConfigAdapter, s as applyQQBotAccountConfig, t as qqbotChannelConfigSchema, u as resolveQQBotAccount } from "./config-schema-iX2iJzKm.js";
|
|
3
|
+
import { T as debugWarn, t as getQQBotRuntime, w as debugLog, z as formatErrorMessage } from "./runtime-TzkQ0YbR.js";
|
|
4
4
|
import { getExecApprovalReplyMetadata } from "openclaw/plugin-sdk/approval-runtime";
|
|
5
5
|
import { createMessageReceiptFromOutboundResults, defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-message";
|
|
6
6
|
import { createChannelApprovalCapability } from "openclaw/plugin-sdk/approval-delivery-runtime";
|
|
@@ -374,7 +374,7 @@ function createQQBotApprovalCapability() {
|
|
|
374
374
|
},
|
|
375
375
|
load: async () => {
|
|
376
376
|
ensurePlatformAdapter();
|
|
377
|
-
return (await import("./handler-runtime-
|
|
377
|
+
return (await import("./handler-runtime-I6vCvj2m.js")).qqbotApprovalNativeRuntime;
|
|
378
378
|
}
|
|
379
379
|
})
|
|
380
380
|
});
|
|
@@ -419,12 +419,17 @@ async function writeOpenClawConfigThroughRuntime(runtime, cfg) {
|
|
|
419
419
|
* The temp-directory fallback is delegated to the PlatformAdapter.
|
|
420
420
|
*/
|
|
421
421
|
/**
|
|
422
|
-
* Resolve the current user's home directory safely across platforms.
|
|
422
|
+
* Resolve the current user's OS home directory safely across platforms.
|
|
423
423
|
*
|
|
424
424
|
* Priority:
|
|
425
425
|
* 1. `os.homedir()`
|
|
426
426
|
* 2. `$HOME` or `%USERPROFILE%`
|
|
427
427
|
* 3. PlatformAdapter.getTempDir() as a last resort
|
|
428
|
+
*
|
|
429
|
+
* This is the *operating-system* home and intentionally ignores
|
|
430
|
+
* `OPENCLAW_HOME`. Persistent QQ Bot data (sessions, known users, refs) is
|
|
431
|
+
* keyed on this value to keep upgrades from hiding existing state when an
|
|
432
|
+
* operator later sets `OPENCLAW_HOME`.
|
|
428
433
|
*/
|
|
429
434
|
function getHomeDir() {
|
|
430
435
|
try {
|
|
@@ -435,7 +440,39 @@ function getHomeDir() {
|
|
|
435
440
|
if (envHome && fs$1.existsSync(envHome)) return envHome;
|
|
436
441
|
return getPlatformAdapter().getTempDir();
|
|
437
442
|
}
|
|
438
|
-
/**
|
|
443
|
+
/**
|
|
444
|
+
* Resolve the effective OpenClaw home directory.
|
|
445
|
+
*
|
|
446
|
+
* Mirrors the contract from core (`src/infra/home-dir.ts::resolveEffectiveHomeDir`)
|
|
447
|
+
* so QQ Bot media roots live under the same tree the rest of OpenClaw treats as
|
|
448
|
+
* `~`. The extension cannot import the core helper directly (it is a separate
|
|
449
|
+
* package with `openclaw` as a peer dependency), so this re-implements the
|
|
450
|
+
* minimal contract:
|
|
451
|
+
*
|
|
452
|
+
* 1. `OPENCLAW_HOME` when set (with `~` / `~/...` expanded against the OS home).
|
|
453
|
+
* 2. Otherwise fall back to {@link getHomeDir} so existing single-home
|
|
454
|
+
* deployments are unaffected.
|
|
455
|
+
*
|
|
456
|
+
* Empty / `"undefined"` / `"null"` strings are treated as unset to match how
|
|
457
|
+
* core normalizes the variable.
|
|
458
|
+
*/
|
|
459
|
+
function resolveOpenClawHome() {
|
|
460
|
+
const raw = process.env.OPENCLAW_HOME?.trim();
|
|
461
|
+
if (!raw || raw === "undefined" || raw === "null") return getHomeDir();
|
|
462
|
+
if (raw === "~" || raw.startsWith("~/") || raw.startsWith("~\\")) {
|
|
463
|
+
const osHome = getHomeDir();
|
|
464
|
+
if (raw === "~") return osHome;
|
|
465
|
+
return path$1.join(osHome, raw.slice(2));
|
|
466
|
+
}
|
|
467
|
+
return raw;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Return a path under `~/.openclaw/qqbot` without creating it.
|
|
471
|
+
*
|
|
472
|
+
* Anchored on the OS home (not `OPENCLAW_HOME`) so persisted QQ Bot data
|
|
473
|
+
* (sessions, known users, ref index, credential backups) does not silently
|
|
474
|
+
* disappear when an operator adds `OPENCLAW_HOME` after the fact.
|
|
475
|
+
*/
|
|
439
476
|
function getQQBotDataPath(...subPaths) {
|
|
440
477
|
return path$1.join(getHomeDir(), ".openclaw", "qqbot", ...subPaths);
|
|
441
478
|
}
|
|
@@ -446,32 +483,36 @@ function getQQBotDataDir(...subPaths) {
|
|
|
446
483
|
return dir;
|
|
447
484
|
}
|
|
448
485
|
/**
|
|
449
|
-
* Return a path under
|
|
486
|
+
* Return a path under `<openclaw-home>/.openclaw/media/qqbot` without creating it.
|
|
450
487
|
*
|
|
451
|
-
* Unlike `getQQBotDataPath`, this lives under OpenClaw's core media allowlist
|
|
452
|
-
* downloaded images and audio can be accessed by framework media tooling.
|
|
488
|
+
* Unlike `getQQBotDataPath`, this lives under OpenClaw's core media allowlist
|
|
489
|
+
* so downloaded images and audio can be accessed by framework media tooling.
|
|
490
|
+
* The base honors `OPENCLAW_HOME` (when set) so files written by agents into
|
|
491
|
+
* the OpenClaw-managed media tree are reachable by this plugin even when
|
|
492
|
+
* `HOME` and `OPENCLAW_HOME` differ (Docker, multi-user hosts). Fixes #83562.
|
|
453
493
|
*/
|
|
454
494
|
function getQQBotMediaPath(...subPaths) {
|
|
455
|
-
return path$1.join(
|
|
495
|
+
return path$1.join(resolveOpenClawHome(), ".openclaw", "media", "qqbot", ...subPaths);
|
|
456
496
|
}
|
|
457
|
-
/** Return a path under
|
|
497
|
+
/** Return a path under `<openclaw-home>/.openclaw/media/qqbot`, creating it on demand. */
|
|
458
498
|
function getQQBotMediaDir(...subPaths) {
|
|
459
499
|
const dir = getQQBotMediaPath(...subPaths);
|
|
460
500
|
if (!fs$1.existsSync(dir)) fs$1.mkdirSync(dir, { recursive: true });
|
|
461
501
|
return dir;
|
|
462
502
|
}
|
|
463
503
|
/**
|
|
464
|
-
* Return
|
|
504
|
+
* Return `<openclaw-home>/.openclaw/media`, OpenClaw's shared media root.
|
|
465
505
|
*
|
|
466
506
|
* This mirrors the directory that core's `buildMediaLocalRoots` exposes as an
|
|
467
507
|
* allowlisted location (see `openclaw/src/media/local-roots.ts`). Using it as a
|
|
468
508
|
* QQ Bot payload root lets the plugin trust framework-produced files that live
|
|
469
509
|
* in sibling subdirectories such as `outbound/` (written by
|
|
470
510
|
* `saveMediaBuffer(..., "outbound", ...)`) or `inbound/`, while still keeping
|
|
471
|
-
* the check anchored to a single, well-known directory.
|
|
511
|
+
* the check anchored to a single, well-known directory. Like
|
|
512
|
+
* {@link getQQBotMediaPath}, the base honors `OPENCLAW_HOME`.
|
|
472
513
|
*/
|
|
473
514
|
function getOpenClawMediaDir() {
|
|
474
|
-
return path$1.join(
|
|
515
|
+
return path$1.join(resolveOpenClawHome(), ".openclaw", "media");
|
|
475
516
|
}
|
|
476
517
|
function isWindows() {
|
|
477
518
|
return process.platform === "win32";
|
|
@@ -533,14 +574,15 @@ function isPathWithinRoot(candidate, root) {
|
|
|
533
574
|
function resolveQQBotLocalMediaPath(p) {
|
|
534
575
|
const normalized = normalizePath(p);
|
|
535
576
|
if (!isLocalPath(normalized) || fs$1.existsSync(normalized)) return normalized;
|
|
536
|
-
const
|
|
577
|
+
const osHomeDir = getHomeDir();
|
|
578
|
+
const openclawHomeDir = resolveOpenClawHome();
|
|
537
579
|
const mediaRoot = getQQBotMediaPath();
|
|
538
580
|
const dataRoot = getQQBotDataPath();
|
|
539
581
|
const candidateRoots = [
|
|
540
|
-
{
|
|
541
|
-
from
|
|
582
|
+
...Array.from(new Set([path$1.join(osHomeDir, ".openclaw", "workspace", "qqbot"), path$1.join(openclawHomeDir, ".openclaw", "workspace", "qqbot")])).map((from) => ({
|
|
583
|
+
from,
|
|
542
584
|
to: mediaRoot
|
|
543
|
-
},
|
|
585
|
+
})),
|
|
544
586
|
{
|
|
545
587
|
from: dataRoot,
|
|
546
588
|
to: mediaRoot
|
|
@@ -826,7 +868,7 @@ function looksLikeQQBotTarget(id) {
|
|
|
826
868
|
//#region extensions/qqbot/src/channel.ts
|
|
827
869
|
let gatewayModulePromise;
|
|
828
870
|
function loadGatewayModule() {
|
|
829
|
-
gatewayModulePromise ??= import("./gateway-
|
|
871
|
+
gatewayModulePromise ??= import("./gateway-DuIz6XAh.js");
|
|
830
872
|
return gatewayModulePromise;
|
|
831
873
|
}
|
|
832
874
|
function createQQBotSendReceipt(params) {
|
|
@@ -844,7 +886,7 @@ function createQQBotSendReceipt(params) {
|
|
|
844
886
|
async function sendQQBotText(params) {
|
|
845
887
|
await loadGatewayModule();
|
|
846
888
|
const account = resolveQQBotAccount(params.cfg, params.accountId);
|
|
847
|
-
const { sendText } = await import("./outbound-
|
|
889
|
+
const { sendText } = await import("./outbound-Ce2aX078.js").then((n) => n.t);
|
|
848
890
|
const result = await sendText({
|
|
849
891
|
to: params.to,
|
|
850
892
|
text: params.text,
|
|
@@ -866,7 +908,7 @@ async function sendQQBotText(params) {
|
|
|
866
908
|
async function sendQQBotMedia(params) {
|
|
867
909
|
await loadGatewayModule();
|
|
868
910
|
const account = resolveQQBotAccount(params.cfg, params.accountId);
|
|
869
|
-
const { sendMedia } = await import("./outbound-
|
|
911
|
+
const { sendMedia } = await import("./outbound-Ce2aX078.js").then((n) => n.t);
|
|
870
912
|
const result = await sendMedia({
|
|
871
913
|
to: params.to,
|
|
872
914
|
text: params.text ?? "",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as qqbotPlugin } from "./channel-
|
|
1
|
+
import { t as qqbotPlugin } from "./channel-DtN9gbLm.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-
|
|
1
|
+
import { a as qqbotSetupAdapterShared, i as qqbotMeta, n as qqbotSetupWizard, r as qqbotConfigAdapter, t as qqbotChannelConfigSchema } from "./config-schema-iX2iJzKm.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,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as normalizeStringifiedEntries, c as getPlatformAdapter, d as registerPlatformAdapterFactory, l as hasPlatformAdapter, n as normalizeLowercaseStringOrEmpty, o as readStringField, r as normalizeOptionalLowercaseString, t as asOptionalObjectRecord, u as registerPlatformAdapter } from "./string-normalize-R_0cKO7Q.js";
|
|
2
2
|
import { buildSecretInputSchema, coerceSecretRef, hasConfiguredSecretInput, normalizeResolvedSecretInputString, normalizeSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
3
3
|
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
|
|
4
4
|
import fs from "node:fs";
|
|
@@ -285,7 +285,7 @@ function describeAccount(account) {
|
|
|
285
285
|
}
|
|
286
286
|
/** Normalize allowFrom entries into uppercase strings without the qqbot: prefix. */
|
|
287
287
|
function formatAllowFrom(allowFrom) {
|
|
288
|
-
return (allowFrom ?? []).map((entry) =>
|
|
288
|
+
return normalizeStringifiedEntries(allowFrom ?? []).map((entry) => entry.replace(/^qqbot:/i, "")).map((entry) => entry.toUpperCase());
|
|
289
289
|
}
|
|
290
290
|
//#endregion
|
|
291
291
|
//#region extensions/qqbot/src/bridge/config.ts
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { E as setOutboundAudioPort, _ as sendVoice, c as isMediaPayload, g as sendVideoMsg, h as sendPhoto, l as parseQQBotPayload, m as sendDocument, o as encodePayloadForCron, r as sendMedia, s as isCronReminderPayload, u as normalizeMediaTags, v as resolveUserFacingMediaError, w as DEFAULT_MEDIA_SEND_ERROR } from "./outbound-
|
|
2
|
-
import { c as getPlatformAdapter, i as normalizeOptionalString, n as normalizeLowercaseStringOrEmpty, o as readStringField, s as sanitizeFileName, t as asOptionalObjectRecord } from "./string-normalize-
|
|
3
|
-
import { f as resolveAccountBase, h as setBridgeLogger, p as ensurePlatformAdapter } from "./config-schema-
|
|
4
|
-
import { C as parseApprovalButtonData, a as getQQBotDataDir, d as isWindows, f as normalizePath$1, h as authorizeQQBotApprovalAction, i as getHomeDir, l as getTempDir, m as toGatewayAccount, o as getQQBotDataPath, p as resolveQQBotPayloadLocalFilePath, r as checkSilkWasmAvailable, s as getQQBotMediaDir, u as isLocalPath } from "./channel-
|
|
5
|
-
import { B as StreamContentType, C as debugError, D as getNextMsgSeq, F as getImageMimeType, H as StreamInputState, I as getMaxUploadSize, N as formatFileSize, R as formatDuration, S as withTokenRetry, T as debugWarn, V as StreamInputMode, _ as sendInputNotify, a as acknowledgeInteraction, b as startBackgroundTokenRefresh, c as createRawInputNotifyFn, d as getMessageApi, f as getPluginUserAgent, g as registerAccount, h as onMessageSent, i as accountToCreds, j as downloadFile, k as openLocalFile, l as getAccessToken, m as initSender, n as getQQBotRuntimeForEngine, o as buildDeliveryTarget, p as initApiConfig, s as clearTokenCache, t as getQQBotRuntime, u as getGatewayUrl, v as sendMedia$1, w as debugLog, x as stopBackgroundTokenRefresh, y as sendText, z as formatErrorMessage } from "./runtime-
|
|
6
|
-
import { a as getPluginVersion, i as getFrameworkVersion, n as runWithRequestContext, o as initCommands, s as matchSlashCommand } from "./request-context-
|
|
1
|
+
import { E as setOutboundAudioPort, _ as sendVoice, c as isMediaPayload, g as sendVideoMsg, h as sendPhoto, l as parseQQBotPayload, m as sendDocument, o as encodePayloadForCron, r as sendMedia, s as isCronReminderPayload, u as normalizeMediaTags, v as resolveUserFacingMediaError, w as DEFAULT_MEDIA_SEND_ERROR } from "./outbound-Ce2aX078.js";
|
|
2
|
+
import { c as getPlatformAdapter, i as normalizeOptionalString$1, n as normalizeLowercaseStringOrEmpty$1, o as readStringField, s as sanitizeFileName, t as asOptionalObjectRecord } from "./string-normalize-R_0cKO7Q.js";
|
|
3
|
+
import { f as resolveAccountBase, h as setBridgeLogger, p as ensurePlatformAdapter } from "./config-schema-iX2iJzKm.js";
|
|
4
|
+
import { C as parseApprovalButtonData, a as getQQBotDataDir, d as isWindows, f as normalizePath$1, h as authorizeQQBotApprovalAction, i as getHomeDir, l as getTempDir, m as toGatewayAccount, o as getQQBotDataPath, p as resolveQQBotPayloadLocalFilePath, r as checkSilkWasmAvailable, s as getQQBotMediaDir, u as isLocalPath } from "./channel-DtN9gbLm.js";
|
|
5
|
+
import { B as StreamContentType, C as debugError, D as getNextMsgSeq, F as getImageMimeType, H as StreamInputState, I as getMaxUploadSize, N as formatFileSize, R as formatDuration, S as withTokenRetry, T as debugWarn, V as StreamInputMode, _ as sendInputNotify, a as acknowledgeInteraction, b as startBackgroundTokenRefresh, c as createRawInputNotifyFn, d as getMessageApi, f as getPluginUserAgent, g as registerAccount, h as onMessageSent, i as accountToCreds, j as downloadFile, k as openLocalFile, l as getAccessToken, m as initSender, n as getQQBotRuntimeForEngine, o as buildDeliveryTarget, p as initApiConfig, s as clearTokenCache, t as getQQBotRuntime, u as getGatewayUrl, v as sendMedia$1, w as debugLog, x as stopBackgroundTokenRefresh, y as sendText, z as formatErrorMessage } from "./runtime-TzkQ0YbR.js";
|
|
6
|
+
import { a as getPluginVersion, i as getFrameworkVersion, n as runWithRequestContext, o as initCommands, s as matchSlashCommand } from "./request-context-D-rKRREP.js";
|
|
7
|
+
import { asBoolean, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
7
8
|
import * as fs$1 from "node:fs";
|
|
8
9
|
import fs from "node:fs";
|
|
9
10
|
import * as os$1 from "node:os";
|
|
@@ -1822,7 +1823,7 @@ async function transcribeAudio(audioPath, cfg) {
|
|
|
1822
1823
|
const detail = await resp.text().catch(() => "");
|
|
1823
1824
|
throw new Error(`STT failed (HTTP ${resp.status}): ${detail.slice(0, 300)}`);
|
|
1824
1825
|
}
|
|
1825
|
-
return normalizeOptionalString((await resp.json()).text) ?? null;
|
|
1826
|
+
return normalizeOptionalString$1((await resp.json()).text) ?? null;
|
|
1826
1827
|
} finally {
|
|
1827
1828
|
await release();
|
|
1828
1829
|
}
|
|
@@ -1878,7 +1879,7 @@ async function processAttachments(attachments, ctx) {
|
|
|
1878
1879
|
};
|
|
1879
1880
|
});
|
|
1880
1881
|
const processTasks = (await Promise.all(downloadTasks)).map(async ({ att, attUrl, isVoice, localPath, audioPath }) => {
|
|
1881
|
-
const asrReferText = normalizeOptionalString(att.asr_refer_text) ?? "";
|
|
1882
|
+
const asrReferText = normalizeOptionalString$1(att.asr_refer_text) ?? "";
|
|
1882
1883
|
const voiceSourceUrl = (isVoice && att.voice_wav_url ? att.voice_wav_url.startsWith("//") ? `https:${att.voice_wav_url}` : att.voice_wav_url : "") || attUrl;
|
|
1883
1884
|
const meta = {
|
|
1884
1885
|
voiceUrl: isVoice && voiceSourceUrl ? voiceSourceUrl : void 0,
|
|
@@ -2450,6 +2451,14 @@ function escapeRegex(str) {
|
|
|
2450
2451
|
}
|
|
2451
2452
|
//#endregion
|
|
2452
2453
|
//#region extensions/qqbot/src/engine/gateway/stages/envelope-stage.ts
|
|
2454
|
+
/**
|
|
2455
|
+
* Envelope stage — render the Web UI body, the dynamic-context block,
|
|
2456
|
+
* the final group system prompt, and the media classification arrays.
|
|
2457
|
+
*
|
|
2458
|
+
* All logic here is presentation-layer glue: it combines fields built by
|
|
2459
|
+
* earlier stages into the display-friendly strings the outbound
|
|
2460
|
+
* dispatcher needs. No decisions / gating.
|
|
2461
|
+
*/
|
|
2453
2462
|
/** Format the inbound envelope (Web UI body). */
|
|
2454
2463
|
function buildBody(input) {
|
|
2455
2464
|
const { event, deps, userContent, isGroupChat, imageUrls } = input;
|
|
@@ -2506,8 +2515,8 @@ function classifyMedia(processed) {
|
|
|
2506
2515
|
localMediaTypes.push(t);
|
|
2507
2516
|
}
|
|
2508
2517
|
}
|
|
2509
|
-
const uniqueVoicePaths =
|
|
2510
|
-
const uniqueVoiceUrls =
|
|
2518
|
+
const uniqueVoicePaths = uniqueStrings(processed.voiceAttachmentPaths);
|
|
2519
|
+
const uniqueVoiceUrls = uniqueStrings(processed.voiceAttachmentUrls);
|
|
2511
2520
|
const voiceMediaTypes = [...uniqueVoicePaths, ...uniqueVoiceUrls].map(() => "audio/wav");
|
|
2512
2521
|
return {
|
|
2513
2522
|
localMediaPaths,
|
|
@@ -2516,7 +2525,7 @@ function classifyMedia(processed) {
|
|
|
2516
2525
|
remoteMediaTypes,
|
|
2517
2526
|
uniqueVoicePaths,
|
|
2518
2527
|
uniqueVoiceUrls,
|
|
2519
|
-
uniqueVoiceAsrReferTexts:
|
|
2528
|
+
uniqueVoiceAsrReferTexts: uniqueStrings(processed.voiceAsrReferTexts).filter(Boolean),
|
|
2520
2529
|
voiceMediaTypes,
|
|
2521
2530
|
hasAsrReferFallback: processed.voiceTranscriptSources.includes("asr"),
|
|
2522
2531
|
voiceTranscriptSources: processed.voiceTranscriptSources
|
|
@@ -2540,8 +2549,7 @@ function readGroupsMap(cfg, accountId) {
|
|
|
2540
2549
|
return normalized;
|
|
2541
2550
|
}
|
|
2542
2551
|
function readBoolean(obj, key) {
|
|
2543
|
-
|
|
2544
|
-
return typeof v === "boolean" ? v : void 0;
|
|
2552
|
+
return asBoolean(obj[key]);
|
|
2545
2553
|
}
|
|
2546
2554
|
function readString(obj, key) {
|
|
2547
2555
|
const v = obj[key];
|
|
@@ -3209,11 +3217,10 @@ function authorizeApprovalButtonActor(params) {
|
|
|
3209
3217
|
};
|
|
3210
3218
|
}
|
|
3211
3219
|
function resolveApprovalActorSenderIds(event) {
|
|
3212
|
-
|
|
3220
|
+
return uniqueStrings([event.group_member_openid, event.user_openid].flatMap((value) => {
|
|
3213
3221
|
const normalized = typeof value === "string" ? value.trim() : "";
|
|
3214
3222
|
return normalized ? [normalized] : [];
|
|
3215
|
-
});
|
|
3216
|
-
return Array.from(new Set(ids));
|
|
3223
|
+
}));
|
|
3217
3224
|
}
|
|
3218
3225
|
function resolveApprovalKind(approvalId) {
|
|
3219
3226
|
return approvalId.toLowerCase().startsWith("plugin:") ? "plugin" : "exec";
|
|
@@ -3589,7 +3596,7 @@ async function parseAndSendMediaTags(replyText, event, actx, sendWithRetry, cons
|
|
|
3589
3596
|
normalizedText: text
|
|
3590
3597
|
};
|
|
3591
3598
|
const tagCounts = mediaTagMatches.reduce((acc, m) => {
|
|
3592
|
-
const t = normalizeLowercaseStringOrEmpty(m[1]);
|
|
3599
|
+
const t = normalizeLowercaseStringOrEmpty$1(m[1]);
|
|
3593
3600
|
acc[t] = (acc[t] ?? 0) + 1;
|
|
3594
3601
|
return acc;
|
|
3595
3602
|
}, {});
|
|
@@ -3604,8 +3611,8 @@ async function parseAndSendMediaTags(replyText, event, actx, sendWithRetry, cons
|
|
|
3604
3611
|
type: "text",
|
|
3605
3612
|
content: filterInternalMarkers(textBefore)
|
|
3606
3613
|
});
|
|
3607
|
-
const tagName = normalizeLowercaseStringOrEmpty(match[1]);
|
|
3608
|
-
const mediaPath = decodeMediaPath(normalizeOptionalString(match[2]) ?? "", log);
|
|
3614
|
+
const tagName = normalizeLowercaseStringOrEmpty$1(match[1]);
|
|
3615
|
+
const mediaPath = decodeMediaPath(normalizeOptionalString$1(match[2]) ?? "", log);
|
|
3609
3616
|
if (mediaPath) {
|
|
3610
3617
|
const itemType = {
|
|
3611
3618
|
qqmedia: "media",
|
|
@@ -4026,7 +4033,7 @@ async function handleImagePayload(ctx, payload) {
|
|
|
4026
4033
|
const base64Data = fileBuffer.toString("base64");
|
|
4027
4034
|
const mimeType = getImageMimeType(imageUrl);
|
|
4028
4035
|
if (!mimeType) {
|
|
4029
|
-
const ext = normalizeLowercaseStringOrEmpty(path.extname(imageUrl));
|
|
4036
|
+
const ext = normalizeLowercaseStringOrEmpty$1(path.extname(imageUrl));
|
|
4030
4037
|
log?.error(`Unsupported image format: ${ext}`);
|
|
4031
4038
|
return;
|
|
4032
4039
|
}
|
|
@@ -5282,7 +5289,7 @@ async function convertSilkToWav(inputPath, outputDir) {
|
|
|
5282
5289
|
/** Check whether an attachment is a voice file (by MIME type or extension). */
|
|
5283
5290
|
function isVoiceAttachment(att) {
|
|
5284
5291
|
if (att.content_type === "voice" || att.content_type?.startsWith("audio/")) return true;
|
|
5285
|
-
const ext = att.filename ? normalizeLowercaseStringOrEmpty(path$1.extname(att.filename)) : "";
|
|
5292
|
+
const ext = att.filename ? normalizeLowercaseStringOrEmpty$1(path$1.extname(att.filename)) : "";
|
|
5286
5293
|
return [
|
|
5287
5294
|
".amr",
|
|
5288
5295
|
".silk",
|
|
@@ -5295,7 +5302,7 @@ function isAudioFile(filePath, mimeType) {
|
|
|
5295
5302
|
if (mimeType) {
|
|
5296
5303
|
if (mimeType === "voice" || mimeType.startsWith("audio/")) return true;
|
|
5297
5304
|
}
|
|
5298
|
-
const ext = normalizeLowercaseStringOrEmpty(path$1.extname(filePath));
|
|
5305
|
+
const ext = normalizeLowercaseStringOrEmpty$1(path$1.extname(filePath));
|
|
5299
5306
|
return [
|
|
5300
5307
|
".silk",
|
|
5301
5308
|
".slk",
|
|
@@ -5329,8 +5336,8 @@ const QQ_NATIVE_VOICE_EXTS = new Set([
|
|
|
5329
5336
|
]);
|
|
5330
5337
|
/** Check whether a voice file needs transcoding for upload (QQ-native formats skip it). */
|
|
5331
5338
|
function shouldTranscodeVoice(filePath, mimeType) {
|
|
5332
|
-
if (mimeType && QQ_NATIVE_VOICE_MIMES.has(normalizeLowercaseStringOrEmpty(mimeType))) return false;
|
|
5333
|
-
const ext = normalizeLowercaseStringOrEmpty(path$1.extname(filePath));
|
|
5339
|
+
if (mimeType && QQ_NATIVE_VOICE_MIMES.has(normalizeLowercaseStringOrEmpty$1(mimeType))) return false;
|
|
5340
|
+
const ext = normalizeLowercaseStringOrEmpty$1(path$1.extname(filePath));
|
|
5334
5341
|
if (QQ_NATIVE_VOICE_EXTS.has(ext)) return false;
|
|
5335
5342
|
return isAudioFile(filePath, mimeType);
|
|
5336
5343
|
}
|
|
@@ -5341,7 +5348,7 @@ const QQ_NATIVE_UPLOAD_FORMATS = [
|
|
|
5341
5348
|
];
|
|
5342
5349
|
function normalizeFormats(formats) {
|
|
5343
5350
|
return formats.map((f) => {
|
|
5344
|
-
const lower = normalizeLowercaseStringOrEmpty(f);
|
|
5351
|
+
const lower = normalizeLowercaseStringOrEmpty$1(f);
|
|
5345
5352
|
return lower.startsWith(".") ? lower : `.${lower}`;
|
|
5346
5353
|
});
|
|
5347
5354
|
}
|
|
@@ -5361,7 +5368,7 @@ async function audioFileToSilkBase64(filePath, directUploadFormats) {
|
|
|
5361
5368
|
debugError(`[audio-convert] file is empty: ${filePath}`);
|
|
5362
5369
|
return null;
|
|
5363
5370
|
}
|
|
5364
|
-
const ext = normalizeLowercaseStringOrEmpty(path$1.extname(filePath));
|
|
5371
|
+
const ext = normalizeLowercaseStringOrEmpty$1(path$1.extname(filePath));
|
|
5365
5372
|
if ((directUploadFormats ? normalizeFormats(directUploadFormats) : QQ_NATIVE_UPLOAD_FORMATS).includes(ext)) {
|
|
5366
5373
|
debugLog(`[audio-convert] direct upload (QQ native format): ${ext} (${buf.length} bytes)`);
|
|
5367
5374
|
return buf.toString("base64");
|
|
@@ -5586,8 +5593,75 @@ function parseWavFallback(buf) {
|
|
|
5586
5593
|
return null;
|
|
5587
5594
|
}
|
|
5588
5595
|
//#endregion
|
|
5596
|
+
//#region extensions/qqbot/src/engine/gateway/response-timeout.ts
|
|
5597
|
+
/**
|
|
5598
|
+
* QQBot outbound response watchdog timeout resolver.
|
|
5599
|
+
*
|
|
5600
|
+
* Background — issue #85267:
|
|
5601
|
+
* The reporter ran openclaw + ollama + `qwen3.5:27b` (a slow local model)
|
|
5602
|
+
* with `models.providers.ollama.timeoutSeconds: 1800` and saw the
|
|
5603
|
+
* QQBot reply path abort at ~5 minutes with "LLM request timed out",
|
|
5604
|
+
* despite the direct ollama call to the same model working. The
|
|
5605
|
+
* embedded-runner / idle-timeout layer already honors longer
|
|
5606
|
+
* provider timeouts (see `src/agents/pi-embedded-runner/run/llm-idle-timeout.ts`),
|
|
5607
|
+
* but the QQBot outbound dispatcher held an independent hardcoded
|
|
5608
|
+
* `RESPONSE_TIMEOUT = 300_000` watchdog that quietly undercut the
|
|
5609
|
+
* configured ceiling.
|
|
5610
|
+
*
|
|
5611
|
+
* Fix shape (clawsweeper `clawsweeper:fix-shape-clear`):
|
|
5612
|
+
* Don't add a new QQBot-only knob. Instead derive the QQBot wait
|
|
5613
|
+
* budget from the existing agent/provider timeout settings the user
|
|
5614
|
+
* already configured:
|
|
5615
|
+
* - `agents.defaults.timeoutSeconds`
|
|
5616
|
+
* - `models.providers.<id>.timeoutSeconds` (max across configured providers)
|
|
5617
|
+
* Take the maximum and clamp to `[DEFAULT_RESPONSE_TIMEOUT_MS, MAX_SAFE_TIMEOUT_MS]`.
|
|
5618
|
+
* The default floor preserves the existing 5-minute guard for users
|
|
5619
|
+
* that have not configured any longer ceiling — i.e. a no-op for
|
|
5620
|
+
* typical cloud-model deployments.
|
|
5621
|
+
*/
|
|
5622
|
+
/**
|
|
5623
|
+
* Default QQBot outbound response watchdog when no config override is
|
|
5624
|
+
* present. Preserves the historical 5-minute guard for unconfigured
|
|
5625
|
+
* deployments.
|
|
5626
|
+
*/
|
|
5627
|
+
const DEFAULT_RESPONSE_TIMEOUT_MS = 3e5;
|
|
5628
|
+
/**
|
|
5629
|
+
* Upper bound to keep the watchdog inside the safe `setTimeout` range
|
|
5630
|
+
* (approximately 24.8 days). Mirrors `MAX_SAFE_TIMEOUT_MS` in
|
|
5631
|
+
* `src/agents/pi-embedded-runner/run/llm-idle-timeout.ts`.
|
|
5632
|
+
*/
|
|
5633
|
+
const MAX_SAFE_TIMEOUT_MS = 2147e6;
|
|
5634
|
+
function positiveSecondsToMs(value) {
|
|
5635
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return;
|
|
5636
|
+
return Math.floor(value * 1e3);
|
|
5637
|
+
}
|
|
5638
|
+
/**
|
|
5639
|
+
* Resolve the QQBot outbound response watchdog (ms).
|
|
5640
|
+
*
|
|
5641
|
+
* The watchdog is the longest of:
|
|
5642
|
+
* - `DEFAULT_RESPONSE_TIMEOUT_MS` (5 min, historical floor)
|
|
5643
|
+
* - `cfg.agents.defaults.timeoutSeconds` converted to ms
|
|
5644
|
+
* - the maximum `cfg.models.providers.<id>.timeoutSeconds` across
|
|
5645
|
+
* configured providers, converted to ms
|
|
5646
|
+
*
|
|
5647
|
+
* Returns at most `MAX_SAFE_TIMEOUT_MS` so the chosen value is always
|
|
5648
|
+
* a safe `setTimeout` argument.
|
|
5649
|
+
*/
|
|
5650
|
+
function resolveResponseTimeoutMs(cfg) {
|
|
5651
|
+
const candidates = [DEFAULT_RESPONSE_TIMEOUT_MS];
|
|
5652
|
+
const typed = cfg ?? {};
|
|
5653
|
+
const agentDefaultMs = positiveSecondsToMs(typed.agents?.defaults?.timeoutSeconds);
|
|
5654
|
+
if (agentDefaultMs !== void 0) candidates.push(agentDefaultMs);
|
|
5655
|
+
const providers = typed.models?.providers;
|
|
5656
|
+
if (providers && typeof providers === "object") for (const entry of Object.values(providers)) {
|
|
5657
|
+
const providerMs = positiveSecondsToMs(entry?.timeoutSeconds);
|
|
5658
|
+
if (providerMs !== void 0) candidates.push(providerMs);
|
|
5659
|
+
}
|
|
5660
|
+
const chosen = Math.max(...candidates);
|
|
5661
|
+
return Math.min(chosen, MAX_SAFE_TIMEOUT_MS);
|
|
5662
|
+
}
|
|
5663
|
+
//#endregion
|
|
5589
5664
|
//#region extensions/qqbot/src/engine/gateway/outbound-dispatch.ts
|
|
5590
|
-
const RESPONSE_TIMEOUT = 3e5;
|
|
5591
5665
|
const TOOL_ONLY_TIMEOUT = 6e4;
|
|
5592
5666
|
const MAX_TOOL_RENEWALS = 3;
|
|
5593
5667
|
const TOOL_MEDIA_SEND_TIMEOUT = 45e3;
|
|
@@ -5659,10 +5733,11 @@ async function dispatchOutbound(inbound, deps) {
|
|
|
5659
5733
|
}
|
|
5660
5734
|
if (toolTexts.length > 0) await sendErrorMessage(toolTexts.slice(-3).join("\n---\n").slice(0, 2e3));
|
|
5661
5735
|
};
|
|
5736
|
+
const responseTimeoutMs = resolveResponseTimeoutMs(cfg);
|
|
5662
5737
|
const timeoutPromise = new Promise((_, reject) => {
|
|
5663
5738
|
timeoutId = setTimeout(() => {
|
|
5664
5739
|
if (!hasResponse) reject(/* @__PURE__ */ new Error("Response timeout"));
|
|
5665
|
-
},
|
|
5740
|
+
}, responseTimeoutMs);
|
|
5666
5741
|
});
|
|
5667
5742
|
const deliverDeps = {
|
|
5668
5743
|
mediaSender: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { m as getBridgeLogger, p as ensurePlatformAdapter, u as resolveQQBotAccount } from "./config-schema-
|
|
2
|
-
import { S as buildPluginApprovalText, _ as matchesQQBotApprovalAccount, b as buildApprovalKeyboard, g as isQQBotExecApprovalClientEnabled, v as resolveQQBotExecApprovalConfig, w as resolveApprovalTarget, x as buildExecApprovalText, y as shouldHandleQQBotExecApprovalRequest } from "./channel-
|
|
3
|
-
import { d as getMessageApi, i as accountToCreds } from "./runtime-
|
|
1
|
+
import { m as getBridgeLogger, p as ensurePlatformAdapter, u as resolveQQBotAccount } from "./config-schema-iX2iJzKm.js";
|
|
2
|
+
import { S as buildPluginApprovalText, _ as matchesQQBotApprovalAccount, b as buildApprovalKeyboard, g as isQQBotExecApprovalClientEnabled, v as resolveQQBotExecApprovalConfig, w as resolveApprovalTarget, x as buildExecApprovalText, y as shouldHandleQQBotExecApprovalRequest } from "./channel-DtN9gbLm.js";
|
|
3
|
+
import { d as getMessageApi, i as accountToCreds } from "./runtime-TzkQ0YbR.js";
|
|
4
4
|
import { resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
|
|
5
5
|
import { createChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
|
|
6
6
|
//#region extensions/qqbot/src/bridge/approval/handler-runtime.ts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { i as normalizeOptionalString, n as normalizeLowercaseStringOrEmpty, s as sanitizeFileName } from "./string-normalize-
|
|
2
|
-
import { a as getQQBotDataDir, f as normalizePath, n as parseTarget$1, p as resolveQQBotPayloadLocalFilePath, s as getQQBotMediaDir, u as isLocalPath } from "./channel-
|
|
3
|
-
import { A as checkFileSize, C as debugError, E as UploadDailyLimitExceededError, F as getImageMimeType, I as getMaxUploadSize, L as readFileAsync, M as fileExistsAsync, N as formatFileSize, O as UPLOAD_PREPARE_FALLBACK_CODE, P as getFileTypeName, T as debugWarn, i as accountToCreds, j as downloadFile, p as initApiConfig, v as sendMedia$1, w as debugLog, y as sendText$1, z as formatErrorMessage } from "./runtime-
|
|
1
|
+
import { i as normalizeOptionalString, n as normalizeLowercaseStringOrEmpty, s as sanitizeFileName } from "./string-normalize-R_0cKO7Q.js";
|
|
2
|
+
import { a as getQQBotDataDir, f as normalizePath, n as parseTarget$1, p as resolveQQBotPayloadLocalFilePath, s as getQQBotMediaDir, u as isLocalPath } from "./channel-DtN9gbLm.js";
|
|
3
|
+
import { A as checkFileSize, C as debugError, E as UploadDailyLimitExceededError, F as getImageMimeType, I as getMaxUploadSize, L as readFileAsync, M as fileExistsAsync, N as formatFileSize, O as UPLOAD_PREPARE_FALLBACK_CODE, P as getFileTypeName, T as debugWarn, i as accountToCreds, j as downloadFile, p as initApiConfig, v as sendMedia$1, w as debugLog, y as sendText$1, z as formatErrorMessage } from "./runtime-TzkQ0YbR.js";
|
|
4
4
|
import { pathExistsSync, resolveLocalPathFromRootsSync } from "openclaw/plugin-sdk/security-runtime";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { getFileExtension } from "openclaw/plugin-sdk/media-mime";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { a as getQQBotDataDir, c as getQQBotMediaPath, d as isWindows, i as getHomeDir } from "./channel-
|
|
2
|
-
import { w as debugLog } from "./runtime-
|
|
1
|
+
import { a as getQQBotDataDir, c as getQQBotMediaPath, d as isWindows, i as getHomeDir } from "./channel-DtN9gbLm.js";
|
|
2
|
+
import { w as debugLog } from "./runtime-TzkQ0YbR.js";
|
|
3
|
+
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { loadJsonFile } from "openclaw/plugin-sdk/json-store";
|
|
@@ -648,7 +649,7 @@ function buildBotLogsResult() {
|
|
|
648
649
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
649
650
|
const tmpFile = writeNewTextFileSync(path.join(tmpDir, `bot-logs-${timestamp}.txt`), lines.join("\n"));
|
|
650
651
|
const fileCount = recentFiles.length;
|
|
651
|
-
const topSources =
|
|
652
|
+
const topSources = uniqueStrings(recentFiles.map((item) => item.sourceDir)).slice(0, 3);
|
|
652
653
|
let summaryText = `共 ${fileCount} 个日志文件,包含 ${totalIncluded} 行内容`;
|
|
653
654
|
if (truncatedCount > 0) summaryText += `(其中 ${truncatedCount} 个文件已截断为最后 ${MAX_LINES_PER_FILE} 行,总计原始 ${totalOriginal} 行)`;
|
|
654
655
|
return {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { c as getPlatformAdapter, i as normalizeOptionalString, s as sanitizeFileName } from "./string-normalize-
|
|
1
|
+
import { c as getPlatformAdapter, i as normalizeOptionalString, s as sanitizeFileName } from "./string-normalize-R_0cKO7Q.js";
|
|
2
2
|
import * as fs$1 from "node:fs";
|
|
3
3
|
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
4
4
|
import os from "node:os";
|
|
@@ -985,6 +985,194 @@ function sleep(ms) {
|
|
|
985
985
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
986
986
|
}
|
|
987
987
|
//#endregion
|
|
988
|
+
//#region extensions/qqbot/src/engine/api/token.ts
|
|
989
|
+
const TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken";
|
|
990
|
+
/**
|
|
991
|
+
* Per-appId token manager with caching, singleflight, and background refresh.
|
|
992
|
+
*
|
|
993
|
+
* Usage:
|
|
994
|
+
* ```ts
|
|
995
|
+
* const tm = new TokenManager({ logger, userAgent: 'QQBotPlugin/1.0' });
|
|
996
|
+
* const token = await tm.getAccessToken('appId', 'secret');
|
|
997
|
+
* ```
|
|
998
|
+
*/
|
|
999
|
+
var TokenManager = class {
|
|
1000
|
+
constructor(config) {
|
|
1001
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
1002
|
+
this.fetchPromises = /* @__PURE__ */ new Map();
|
|
1003
|
+
this.refreshControllers = /* @__PURE__ */ new Map();
|
|
1004
|
+
this.logger = config?.logger;
|
|
1005
|
+
const ua = config?.userAgent ?? "QQBotPlugin/unknown";
|
|
1006
|
+
this.resolveUserAgent = typeof ua === "function" ? ua : () => ua;
|
|
1007
|
+
}
|
|
1008
|
+
/**
|
|
1009
|
+
* Obtain an access token with caching and singleflight semantics.
|
|
1010
|
+
*
|
|
1011
|
+
* When multiple callers request a token for the same appId concurrently,
|
|
1012
|
+
* only one actual HTTP request is made — the others await the same promise.
|
|
1013
|
+
*/
|
|
1014
|
+
async getAccessToken(appId, clientSecret) {
|
|
1015
|
+
const normalizedId = appId.trim();
|
|
1016
|
+
const cached = this.cache.get(normalizedId);
|
|
1017
|
+
const refreshAheadMs = cached ? Math.min(300 * 1e3, (cached.expiresAt - Date.now()) / 3) : 0;
|
|
1018
|
+
if (cached && Date.now() < cached.expiresAt - refreshAheadMs) return cached.token;
|
|
1019
|
+
let pending = this.fetchPromises.get(normalizedId);
|
|
1020
|
+
if (pending) {
|
|
1021
|
+
this.logger?.debug?.(`[qqbot:token:${normalizedId}] Fetch in progress, reusing promise`);
|
|
1022
|
+
return pending;
|
|
1023
|
+
}
|
|
1024
|
+
pending = (async () => {
|
|
1025
|
+
try {
|
|
1026
|
+
return await this.doFetchToken(normalizedId, clientSecret);
|
|
1027
|
+
} finally {
|
|
1028
|
+
this.fetchPromises.delete(normalizedId);
|
|
1029
|
+
}
|
|
1030
|
+
})();
|
|
1031
|
+
this.fetchPromises.set(normalizedId, pending);
|
|
1032
|
+
return pending;
|
|
1033
|
+
}
|
|
1034
|
+
/** Clear the cached token for one appId, or all. */
|
|
1035
|
+
clearCache(appId) {
|
|
1036
|
+
if (appId) {
|
|
1037
|
+
this.cache.delete(appId.trim());
|
|
1038
|
+
this.logger?.debug?.(`[qqbot:token:${appId}] Cache cleared`);
|
|
1039
|
+
} else {
|
|
1040
|
+
this.cache.clear();
|
|
1041
|
+
this.logger?.debug?.(`[token] All caches cleared`);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
/** Return token status for diagnostics. */
|
|
1045
|
+
getStatus(appId) {
|
|
1046
|
+
if (this.fetchPromises.has(appId)) return {
|
|
1047
|
+
status: "refreshing",
|
|
1048
|
+
expiresAt: this.cache.get(appId)?.expiresAt ?? null
|
|
1049
|
+
};
|
|
1050
|
+
const cached = this.cache.get(appId);
|
|
1051
|
+
if (!cached) return {
|
|
1052
|
+
status: "none",
|
|
1053
|
+
expiresAt: null
|
|
1054
|
+
};
|
|
1055
|
+
const remaining = cached.expiresAt - Date.now();
|
|
1056
|
+
return {
|
|
1057
|
+
status: remaining > Math.min(300 * 1e3, remaining / 3) ? "valid" : "expired",
|
|
1058
|
+
expiresAt: cached.expiresAt
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
/** Start a background token refresh loop for one appId. */
|
|
1062
|
+
startBackgroundRefresh(appId, clientSecret, options) {
|
|
1063
|
+
if (this.refreshControllers.has(appId)) {
|
|
1064
|
+
this.logger?.info?.(`[qqbot:token:${appId}] Background refresh already running`);
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
const { refreshAheadMs = 300 * 1e3, randomOffsetMs = 30 * 1e3, minRefreshIntervalMs = 60 * 1e3, retryDelayMs = 5 * 1e3 } = options ?? {};
|
|
1068
|
+
const controller = new AbortController();
|
|
1069
|
+
this.refreshControllers.set(appId, controller);
|
|
1070
|
+
const { signal } = controller;
|
|
1071
|
+
const loop = async () => {
|
|
1072
|
+
this.logger?.info?.(`[qqbot:token:${appId}] Background refresh started`);
|
|
1073
|
+
while (!signal.aborted) try {
|
|
1074
|
+
await this.getAccessToken(appId, clientSecret);
|
|
1075
|
+
const cached = this.cache.get(appId);
|
|
1076
|
+
if (cached) {
|
|
1077
|
+
const expiresIn = cached.expiresAt - Date.now();
|
|
1078
|
+
const randomOffset = Math.random() * randomOffsetMs;
|
|
1079
|
+
const refreshIn = Math.max(expiresIn - refreshAheadMs - randomOffset, minRefreshIntervalMs);
|
|
1080
|
+
this.logger?.debug?.(`[qqbot:token:${appId}] Next refresh in ${Math.round(refreshIn / 1e3)}s`);
|
|
1081
|
+
await this.abortableSleep(refreshIn, signal);
|
|
1082
|
+
} else await this.abortableSleep(minRefreshIntervalMs, signal);
|
|
1083
|
+
} catch (err) {
|
|
1084
|
+
if (signal.aborted) break;
|
|
1085
|
+
this.logger?.error?.(`[qqbot:token:${appId}] Background refresh failed: ${formatErrorMessage(err)}`);
|
|
1086
|
+
await this.abortableSleep(retryDelayMs, signal);
|
|
1087
|
+
}
|
|
1088
|
+
this.refreshControllers.delete(appId);
|
|
1089
|
+
this.logger?.info?.(`[qqbot:token:${appId}] Background refresh stopped`);
|
|
1090
|
+
};
|
|
1091
|
+
loop().catch((err) => {
|
|
1092
|
+
this.refreshControllers.delete(appId);
|
|
1093
|
+
this.logger?.error?.(`[qqbot:token:${appId}] Background refresh crashed: ${err}`);
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
/** Stop background refresh for one appId, or all. */
|
|
1097
|
+
stopBackgroundRefresh(appId) {
|
|
1098
|
+
if (appId) {
|
|
1099
|
+
const ctrl = this.refreshControllers.get(appId);
|
|
1100
|
+
if (ctrl) {
|
|
1101
|
+
ctrl.abort();
|
|
1102
|
+
this.refreshControllers.delete(appId);
|
|
1103
|
+
}
|
|
1104
|
+
} else {
|
|
1105
|
+
for (const ctrl of this.refreshControllers.values()) ctrl.abort();
|
|
1106
|
+
this.refreshControllers.clear();
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
/** Check whether background refresh is running. */
|
|
1110
|
+
isBackgroundRefreshRunning(appId) {
|
|
1111
|
+
if (appId) return this.refreshControllers.has(appId);
|
|
1112
|
+
return this.refreshControllers.size > 0;
|
|
1113
|
+
}
|
|
1114
|
+
async doFetchToken(appId, clientSecret) {
|
|
1115
|
+
this.logger?.debug?.(`[qqbot:token:${appId}] >>> POST ${TOKEN_URL}`);
|
|
1116
|
+
let response;
|
|
1117
|
+
try {
|
|
1118
|
+
response = await fetch(TOKEN_URL, {
|
|
1119
|
+
method: "POST",
|
|
1120
|
+
headers: {
|
|
1121
|
+
"Content-Type": "application/json",
|
|
1122
|
+
"User-Agent": this.resolveUserAgent()
|
|
1123
|
+
},
|
|
1124
|
+
body: JSON.stringify({
|
|
1125
|
+
appId,
|
|
1126
|
+
clientSecret
|
|
1127
|
+
})
|
|
1128
|
+
});
|
|
1129
|
+
} catch (err) {
|
|
1130
|
+
this.logger?.error?.(`[qqbot:token:${appId}] Network error: ${formatErrorMessage(err)}`);
|
|
1131
|
+
throw new Error(`Network error getting access_token: ${formatErrorMessage(err)}`, { cause: err });
|
|
1132
|
+
}
|
|
1133
|
+
const traceId = response.headers.get("x-tps-trace-id") ?? "";
|
|
1134
|
+
this.logger?.debug?.(`[qqbot:token:${appId}] <<< ${response.status}${traceId ? ` | TraceId: ${traceId}` : ""}`);
|
|
1135
|
+
let rawBody;
|
|
1136
|
+
try {
|
|
1137
|
+
rawBody = await response.text();
|
|
1138
|
+
} catch (err) {
|
|
1139
|
+
throw new Error(`Failed to read access_token response: ${formatErrorMessage(err)}`, { cause: err });
|
|
1140
|
+
}
|
|
1141
|
+
const logBody = rawBody.replace(/"access_token"\s*:\s*"[^"]+"/g, "\"access_token\": \"***\"");
|
|
1142
|
+
this.logger?.debug?.(`[qqbot:token:${appId}] <<< Body: ${logBody}`);
|
|
1143
|
+
let data;
|
|
1144
|
+
try {
|
|
1145
|
+
data = JSON.parse(rawBody);
|
|
1146
|
+
} catch {
|
|
1147
|
+
throw new Error("QQBot access_token response was malformed JSON");
|
|
1148
|
+
}
|
|
1149
|
+
if (!data.access_token) throw new Error(`Failed to get access_token: ${JSON.stringify(data)}`);
|
|
1150
|
+
const expiresAt = Date.now() + (data.expires_in ?? 7200) * 1e3;
|
|
1151
|
+
this.cache.set(appId, {
|
|
1152
|
+
token: data.access_token,
|
|
1153
|
+
expiresAt,
|
|
1154
|
+
appId
|
|
1155
|
+
});
|
|
1156
|
+
this.logger?.debug?.(`[qqbot:token:${appId}] Cached, expires at: ${new Date(expiresAt).toISOString()}`);
|
|
1157
|
+
return data.access_token;
|
|
1158
|
+
}
|
|
1159
|
+
abortableSleep(ms, signal) {
|
|
1160
|
+
return new Promise((resolve, reject) => {
|
|
1161
|
+
const timer = setTimeout(resolve, ms);
|
|
1162
|
+
if (signal.aborted) {
|
|
1163
|
+
clearTimeout(timer);
|
|
1164
|
+
reject(/* @__PURE__ */ new Error("Aborted"));
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
const onAbort = () => {
|
|
1168
|
+
clearTimeout(timer);
|
|
1169
|
+
reject(/* @__PURE__ */ new Error("Aborted"));
|
|
1170
|
+
};
|
|
1171
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
//#endregion
|
|
988
1176
|
//#region extensions/qqbot/src/engine/api/media.ts
|
|
989
1177
|
/**
|
|
990
1178
|
* Media upload API for the QQ Open Platform (small-file direct upload).
|
|
@@ -1240,194 +1428,6 @@ var MessageApi = class {
|
|
|
1240
1428
|
}
|
|
1241
1429
|
};
|
|
1242
1430
|
//#endregion
|
|
1243
|
-
//#region extensions/qqbot/src/engine/api/token.ts
|
|
1244
|
-
const TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken";
|
|
1245
|
-
/**
|
|
1246
|
-
* Per-appId token manager with caching, singleflight, and background refresh.
|
|
1247
|
-
*
|
|
1248
|
-
* Usage:
|
|
1249
|
-
* ```ts
|
|
1250
|
-
* const tm = new TokenManager({ logger, userAgent: 'QQBotPlugin/1.0' });
|
|
1251
|
-
* const token = await tm.getAccessToken('appId', 'secret');
|
|
1252
|
-
* ```
|
|
1253
|
-
*/
|
|
1254
|
-
var TokenManager = class {
|
|
1255
|
-
constructor(config) {
|
|
1256
|
-
this.cache = /* @__PURE__ */ new Map();
|
|
1257
|
-
this.fetchPromises = /* @__PURE__ */ new Map();
|
|
1258
|
-
this.refreshControllers = /* @__PURE__ */ new Map();
|
|
1259
|
-
this.logger = config?.logger;
|
|
1260
|
-
const ua = config?.userAgent ?? "QQBotPlugin/unknown";
|
|
1261
|
-
this.resolveUserAgent = typeof ua === "function" ? ua : () => ua;
|
|
1262
|
-
}
|
|
1263
|
-
/**
|
|
1264
|
-
* Obtain an access token with caching and singleflight semantics.
|
|
1265
|
-
*
|
|
1266
|
-
* When multiple callers request a token for the same appId concurrently,
|
|
1267
|
-
* only one actual HTTP request is made — the others await the same promise.
|
|
1268
|
-
*/
|
|
1269
|
-
async getAccessToken(appId, clientSecret) {
|
|
1270
|
-
const normalizedId = appId.trim();
|
|
1271
|
-
const cached = this.cache.get(normalizedId);
|
|
1272
|
-
const refreshAheadMs = cached ? Math.min(300 * 1e3, (cached.expiresAt - Date.now()) / 3) : 0;
|
|
1273
|
-
if (cached && Date.now() < cached.expiresAt - refreshAheadMs) return cached.token;
|
|
1274
|
-
let pending = this.fetchPromises.get(normalizedId);
|
|
1275
|
-
if (pending) {
|
|
1276
|
-
this.logger?.debug?.(`[qqbot:token:${normalizedId}] Fetch in progress, reusing promise`);
|
|
1277
|
-
return pending;
|
|
1278
|
-
}
|
|
1279
|
-
pending = (async () => {
|
|
1280
|
-
try {
|
|
1281
|
-
return await this.doFetchToken(normalizedId, clientSecret);
|
|
1282
|
-
} finally {
|
|
1283
|
-
this.fetchPromises.delete(normalizedId);
|
|
1284
|
-
}
|
|
1285
|
-
})();
|
|
1286
|
-
this.fetchPromises.set(normalizedId, pending);
|
|
1287
|
-
return pending;
|
|
1288
|
-
}
|
|
1289
|
-
/** Clear the cached token for one appId, or all. */
|
|
1290
|
-
clearCache(appId) {
|
|
1291
|
-
if (appId) {
|
|
1292
|
-
this.cache.delete(appId.trim());
|
|
1293
|
-
this.logger?.debug?.(`[qqbot:token:${appId}] Cache cleared`);
|
|
1294
|
-
} else {
|
|
1295
|
-
this.cache.clear();
|
|
1296
|
-
this.logger?.debug?.(`[token] All caches cleared`);
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
/** Return token status for diagnostics. */
|
|
1300
|
-
getStatus(appId) {
|
|
1301
|
-
if (this.fetchPromises.has(appId)) return {
|
|
1302
|
-
status: "refreshing",
|
|
1303
|
-
expiresAt: this.cache.get(appId)?.expiresAt ?? null
|
|
1304
|
-
};
|
|
1305
|
-
const cached = this.cache.get(appId);
|
|
1306
|
-
if (!cached) return {
|
|
1307
|
-
status: "none",
|
|
1308
|
-
expiresAt: null
|
|
1309
|
-
};
|
|
1310
|
-
const remaining = cached.expiresAt - Date.now();
|
|
1311
|
-
return {
|
|
1312
|
-
status: remaining > Math.min(300 * 1e3, remaining / 3) ? "valid" : "expired",
|
|
1313
|
-
expiresAt: cached.expiresAt
|
|
1314
|
-
};
|
|
1315
|
-
}
|
|
1316
|
-
/** Start a background token refresh loop for one appId. */
|
|
1317
|
-
startBackgroundRefresh(appId, clientSecret, options) {
|
|
1318
|
-
if (this.refreshControllers.has(appId)) {
|
|
1319
|
-
this.logger?.info?.(`[qqbot:token:${appId}] Background refresh already running`);
|
|
1320
|
-
return;
|
|
1321
|
-
}
|
|
1322
|
-
const { refreshAheadMs = 300 * 1e3, randomOffsetMs = 30 * 1e3, minRefreshIntervalMs = 60 * 1e3, retryDelayMs = 5 * 1e3 } = options ?? {};
|
|
1323
|
-
const controller = new AbortController();
|
|
1324
|
-
this.refreshControllers.set(appId, controller);
|
|
1325
|
-
const { signal } = controller;
|
|
1326
|
-
const loop = async () => {
|
|
1327
|
-
this.logger?.info?.(`[qqbot:token:${appId}] Background refresh started`);
|
|
1328
|
-
while (!signal.aborted) try {
|
|
1329
|
-
await this.getAccessToken(appId, clientSecret);
|
|
1330
|
-
const cached = this.cache.get(appId);
|
|
1331
|
-
if (cached) {
|
|
1332
|
-
const expiresIn = cached.expiresAt - Date.now();
|
|
1333
|
-
const randomOffset = Math.random() * randomOffsetMs;
|
|
1334
|
-
const refreshIn = Math.max(expiresIn - refreshAheadMs - randomOffset, minRefreshIntervalMs);
|
|
1335
|
-
this.logger?.debug?.(`[qqbot:token:${appId}] Next refresh in ${Math.round(refreshIn / 1e3)}s`);
|
|
1336
|
-
await this.abortableSleep(refreshIn, signal);
|
|
1337
|
-
} else await this.abortableSleep(minRefreshIntervalMs, signal);
|
|
1338
|
-
} catch (err) {
|
|
1339
|
-
if (signal.aborted) break;
|
|
1340
|
-
this.logger?.error?.(`[qqbot:token:${appId}] Background refresh failed: ${formatErrorMessage(err)}`);
|
|
1341
|
-
await this.abortableSleep(retryDelayMs, signal);
|
|
1342
|
-
}
|
|
1343
|
-
this.refreshControllers.delete(appId);
|
|
1344
|
-
this.logger?.info?.(`[qqbot:token:${appId}] Background refresh stopped`);
|
|
1345
|
-
};
|
|
1346
|
-
loop().catch((err) => {
|
|
1347
|
-
this.refreshControllers.delete(appId);
|
|
1348
|
-
this.logger?.error?.(`[qqbot:token:${appId}] Background refresh crashed: ${err}`);
|
|
1349
|
-
});
|
|
1350
|
-
}
|
|
1351
|
-
/** Stop background refresh for one appId, or all. */
|
|
1352
|
-
stopBackgroundRefresh(appId) {
|
|
1353
|
-
if (appId) {
|
|
1354
|
-
const ctrl = this.refreshControllers.get(appId);
|
|
1355
|
-
if (ctrl) {
|
|
1356
|
-
ctrl.abort();
|
|
1357
|
-
this.refreshControllers.delete(appId);
|
|
1358
|
-
}
|
|
1359
|
-
} else {
|
|
1360
|
-
for (const ctrl of this.refreshControllers.values()) ctrl.abort();
|
|
1361
|
-
this.refreshControllers.clear();
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
/** Check whether background refresh is running. */
|
|
1365
|
-
isBackgroundRefreshRunning(appId) {
|
|
1366
|
-
if (appId) return this.refreshControllers.has(appId);
|
|
1367
|
-
return this.refreshControllers.size > 0;
|
|
1368
|
-
}
|
|
1369
|
-
async doFetchToken(appId, clientSecret) {
|
|
1370
|
-
this.logger?.debug?.(`[qqbot:token:${appId}] >>> POST ${TOKEN_URL}`);
|
|
1371
|
-
let response;
|
|
1372
|
-
try {
|
|
1373
|
-
response = await fetch(TOKEN_URL, {
|
|
1374
|
-
method: "POST",
|
|
1375
|
-
headers: {
|
|
1376
|
-
"Content-Type": "application/json",
|
|
1377
|
-
"User-Agent": this.resolveUserAgent()
|
|
1378
|
-
},
|
|
1379
|
-
body: JSON.stringify({
|
|
1380
|
-
appId,
|
|
1381
|
-
clientSecret
|
|
1382
|
-
})
|
|
1383
|
-
});
|
|
1384
|
-
} catch (err) {
|
|
1385
|
-
this.logger?.error?.(`[qqbot:token:${appId}] Network error: ${formatErrorMessage(err)}`);
|
|
1386
|
-
throw new Error(`Network error getting access_token: ${formatErrorMessage(err)}`, { cause: err });
|
|
1387
|
-
}
|
|
1388
|
-
const traceId = response.headers.get("x-tps-trace-id") ?? "";
|
|
1389
|
-
this.logger?.debug?.(`[qqbot:token:${appId}] <<< ${response.status}${traceId ? ` | TraceId: ${traceId}` : ""}`);
|
|
1390
|
-
let rawBody;
|
|
1391
|
-
try {
|
|
1392
|
-
rawBody = await response.text();
|
|
1393
|
-
} catch (err) {
|
|
1394
|
-
throw new Error(`Failed to read access_token response: ${formatErrorMessage(err)}`, { cause: err });
|
|
1395
|
-
}
|
|
1396
|
-
const logBody = rawBody.replace(/"access_token"\s*:\s*"[^"]+"/g, "\"access_token\": \"***\"");
|
|
1397
|
-
this.logger?.debug?.(`[qqbot:token:${appId}] <<< Body: ${logBody}`);
|
|
1398
|
-
let data;
|
|
1399
|
-
try {
|
|
1400
|
-
data = JSON.parse(rawBody);
|
|
1401
|
-
} catch {
|
|
1402
|
-
throw new Error("QQBot access_token response was malformed JSON");
|
|
1403
|
-
}
|
|
1404
|
-
if (!data.access_token) throw new Error(`Failed to get access_token: ${JSON.stringify(data)}`);
|
|
1405
|
-
const expiresAt = Date.now() + (data.expires_in ?? 7200) * 1e3;
|
|
1406
|
-
this.cache.set(appId, {
|
|
1407
|
-
token: data.access_token,
|
|
1408
|
-
expiresAt,
|
|
1409
|
-
appId
|
|
1410
|
-
});
|
|
1411
|
-
this.logger?.debug?.(`[qqbot:token:${appId}] Cached, expires at: ${new Date(expiresAt).toISOString()}`);
|
|
1412
|
-
return data.access_token;
|
|
1413
|
-
}
|
|
1414
|
-
abortableSleep(ms, signal) {
|
|
1415
|
-
return new Promise((resolve, reject) => {
|
|
1416
|
-
const timer = setTimeout(resolve, ms);
|
|
1417
|
-
if (signal.aborted) {
|
|
1418
|
-
clearTimeout(timer);
|
|
1419
|
-
reject(/* @__PURE__ */ new Error("Aborted"));
|
|
1420
|
-
return;
|
|
1421
|
-
}
|
|
1422
|
-
const onAbort = () => {
|
|
1423
|
-
clearTimeout(timer);
|
|
1424
|
-
reject(/* @__PURE__ */ new Error("Aborted"));
|
|
1425
|
-
};
|
|
1426
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1427
|
-
});
|
|
1428
|
-
}
|
|
1429
|
-
};
|
|
1430
|
-
//#endregion
|
|
1431
1431
|
//#region extensions/qqbot/src/engine/utils/log.ts
|
|
1432
1432
|
/**
|
|
1433
1433
|
* QQBot debug logging utilities.
|
package/dist/runtime-api.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { r as setQQBotRuntime, t as getQQBotRuntime } from "./runtime-
|
|
1
|
+
import { r as setQQBotRuntime, t as getQQBotRuntime } from "./runtime-TzkQ0YbR.js";
|
|
2
2
|
export { getQQBotRuntime, setQQBotRuntime };
|
package/dist/setup-plugin-api.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as qqbotSetupPlugin } from "./channel.setup-
|
|
1
|
+
import { t as qqbotSetupPlugin } from "./channel.setup-BxGpVkdx.js";
|
|
2
2
|
export { qqbotSetupPlugin };
|
|
@@ -45,6 +45,9 @@ function normalizeStringifiedOptionalString(value) {
|
|
|
45
45
|
if (typeof value === "string") return normalizeOptionalString(value);
|
|
46
46
|
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return normalizeOptionalString(String(value));
|
|
47
47
|
}
|
|
48
|
+
function normalizeStringifiedEntries(values) {
|
|
49
|
+
return (values ?? []).map((entry) => normalizeStringifiedOptionalString(entry)).filter((entry) => Boolean(entry));
|
|
50
|
+
}
|
|
48
51
|
/** Return the trimmed lowercase string or `undefined`. */
|
|
49
52
|
function normalizeOptionalLowercaseString(value) {
|
|
50
53
|
return normalizeOptionalString(value)?.toLowerCase();
|
|
@@ -79,4 +82,4 @@ function sanitizeFileName(name) {
|
|
|
79
82
|
return result;
|
|
80
83
|
}
|
|
81
84
|
//#endregion
|
|
82
|
-
export {
|
|
85
|
+
export { normalizeStringifiedEntries as a, getPlatformAdapter as c, registerPlatformAdapterFactory as d, normalizeOptionalString as i, hasPlatformAdapter as l, normalizeLowercaseStringOrEmpty as n, readStringField as o, normalizeOptionalLowercaseString as r, sanitizeFileName as s, asOptionalObjectRecord as t, registerPlatformAdapter as u };
|
|
@@ -40,6 +40,10 @@ class Receiver extends Writable {
|
|
|
40
40
|
* extensions
|
|
41
41
|
* @param {Boolean} [options.isServer=false] Specifies whether to operate in
|
|
42
42
|
* client or server mode
|
|
43
|
+
* @param {Number} [options.maxBufferedChunks=0] The maximum number of
|
|
44
|
+
* buffered data chunks
|
|
45
|
+
* @param {Number} [options.maxFragments=0] The maximum number of message
|
|
46
|
+
* fragments
|
|
43
47
|
* @param {Number} [options.maxPayload=0] The maximum allowed message length
|
|
44
48
|
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
|
|
45
49
|
* not to skip UTF-8 validation for text and close messages
|
|
@@ -54,6 +58,8 @@ class Receiver extends Writable {
|
|
|
54
58
|
this._binaryType = options.binaryType || BINARY_TYPES[0];
|
|
55
59
|
this._extensions = options.extensions || {};
|
|
56
60
|
this._isServer = !!options.isServer;
|
|
61
|
+
this._maxBufferedChunks = options.maxBufferedChunks | 0;
|
|
62
|
+
this._maxFragments = options.maxFragments | 0;
|
|
57
63
|
this._maxPayload = options.maxPayload | 0;
|
|
58
64
|
this._skipUTF8Validation = !!options.skipUTF8Validation;
|
|
59
65
|
this[kWebSocket] = undefined;
|
|
@@ -89,6 +95,22 @@ class Receiver extends Writable {
|
|
|
89
95
|
_write(chunk, encoding, cb) {
|
|
90
96
|
if (this._opcode === 0x08 && this._state == GET_INFO) return cb();
|
|
91
97
|
|
|
98
|
+
if (
|
|
99
|
+
this._maxBufferedChunks > 0 &&
|
|
100
|
+
this._buffers.length >= this._maxBufferedChunks
|
|
101
|
+
) {
|
|
102
|
+
cb(
|
|
103
|
+
this.createError(
|
|
104
|
+
RangeError,
|
|
105
|
+
'Too many buffered chunks',
|
|
106
|
+
false,
|
|
107
|
+
1008,
|
|
108
|
+
'WS_ERR_TOO_MANY_BUFFERED_PARTS'
|
|
109
|
+
)
|
|
110
|
+
);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
92
114
|
this._bufferedBytes += chunk.length;
|
|
93
115
|
this._buffers.push(chunk);
|
|
94
116
|
this.startLoop(cb);
|
|
@@ -485,6 +507,22 @@ class Receiver extends Writable {
|
|
|
485
507
|
}
|
|
486
508
|
|
|
487
509
|
if (data.length) {
|
|
510
|
+
if (
|
|
511
|
+
this._maxFragments > 0 &&
|
|
512
|
+
this._fragments.length >= this._maxFragments
|
|
513
|
+
) {
|
|
514
|
+
const error = this.createError(
|
|
515
|
+
RangeError,
|
|
516
|
+
'Too many message fragments',
|
|
517
|
+
false,
|
|
518
|
+
1008,
|
|
519
|
+
'WS_ERR_TOO_MANY_BUFFERED_PARTS'
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
cb(error);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
|
|
488
526
|
//
|
|
489
527
|
// This message is not compressed so its length is the sum of the payload
|
|
490
528
|
// length of all fragments.
|
|
@@ -524,6 +562,22 @@ class Receiver extends Writable {
|
|
|
524
562
|
return;
|
|
525
563
|
}
|
|
526
564
|
|
|
565
|
+
if (
|
|
566
|
+
this._maxFragments > 0 &&
|
|
567
|
+
this._fragments.length >= this._maxFragments
|
|
568
|
+
) {
|
|
569
|
+
const error = this.createError(
|
|
570
|
+
RangeError,
|
|
571
|
+
'Too many message fragments',
|
|
572
|
+
false,
|
|
573
|
+
1008,
|
|
574
|
+
'WS_ERR_TOO_MANY_BUFFERED_PARTS'
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
cb(error);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
527
581
|
this._fragments.push(buf);
|
|
528
582
|
}
|
|
529
583
|
|
|
@@ -43,6 +43,10 @@ class WebSocketServer extends EventEmitter {
|
|
|
43
43
|
* called
|
|
44
44
|
* @param {Function} [options.handleProtocols] A hook to handle protocols
|
|
45
45
|
* @param {String} [options.host] The hostname where to bind the server
|
|
46
|
+
* @param {Number} [options.maxBufferedChunks=1048576] The maximum number of
|
|
47
|
+
* buffered data chunks
|
|
48
|
+
* @param {Number} [options.maxFragments=131072] The maximum number of message
|
|
49
|
+
* fragments
|
|
46
50
|
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
|
47
51
|
* size
|
|
48
52
|
* @param {Boolean} [options.noServer=false] Enable no server mode
|
|
@@ -65,6 +69,8 @@ class WebSocketServer extends EventEmitter {
|
|
|
65
69
|
options = {
|
|
66
70
|
allowSynchronousEvents: true,
|
|
67
71
|
autoPong: true,
|
|
72
|
+
maxBufferedChunks: 1024 * 1024,
|
|
73
|
+
maxFragments: 128 * 1024,
|
|
68
74
|
maxPayload: 100 * 1024 * 1024,
|
|
69
75
|
skipUTF8Validation: false,
|
|
70
76
|
perMessageDeflate: false,
|
|
@@ -424,6 +430,8 @@ class WebSocketServer extends EventEmitter {
|
|
|
424
430
|
|
|
425
431
|
ws.setSocket(socket, head, {
|
|
426
432
|
allowSynchronousEvents: this.options.allowSynchronousEvents,
|
|
433
|
+
maxBufferedChunks: this.options.maxBufferedChunks,
|
|
434
|
+
maxFragments: this.options.maxFragments,
|
|
427
435
|
maxPayload: this.options.maxPayload,
|
|
428
436
|
skipUTF8Validation: this.options.skipUTF8Validation
|
|
429
437
|
});
|
|
@@ -201,6 +201,10 @@ class WebSocket extends EventEmitter {
|
|
|
201
201
|
* multiple times in the same tick
|
|
202
202
|
* @param {Function} [options.generateMask] The function used to generate the
|
|
203
203
|
* masking key
|
|
204
|
+
* @param {Number} [options.maxBufferedChunks=0] The maximum number of
|
|
205
|
+
* buffered data chunks
|
|
206
|
+
* @param {Number} [options.maxFragments=0] The maximum number of message
|
|
207
|
+
* fragments
|
|
204
208
|
* @param {Number} [options.maxPayload=0] The maximum allowed message size
|
|
205
209
|
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
|
|
206
210
|
* not to skip UTF-8 validation for text and close messages
|
|
@@ -212,6 +216,8 @@ class WebSocket extends EventEmitter {
|
|
|
212
216
|
binaryType: this.binaryType,
|
|
213
217
|
extensions: this._extensions,
|
|
214
218
|
isServer: this._isServer,
|
|
219
|
+
maxBufferedChunks: options.maxBufferedChunks,
|
|
220
|
+
maxFragments: options.maxFragments,
|
|
215
221
|
maxPayload: options.maxPayload,
|
|
216
222
|
skipUTF8Validation: options.skipUTF8Validation
|
|
217
223
|
});
|
|
@@ -640,6 +646,10 @@ module.exports = WebSocket;
|
|
|
640
646
|
* masking key
|
|
641
647
|
* @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the
|
|
642
648
|
* handshake request
|
|
649
|
+
* @param {Number} [options.maxBufferedChunks=1048576] The maximum number of
|
|
650
|
+
* buffered data chunks
|
|
651
|
+
* @param {Number} [options.maxFragments=131072] The maximum number of message
|
|
652
|
+
* fragments
|
|
643
653
|
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
|
644
654
|
* size
|
|
645
655
|
* @param {Number} [options.maxRedirects=10] The maximum number of redirects
|
|
@@ -660,6 +670,8 @@ function initAsClient(websocket, address, protocols, options) {
|
|
|
660
670
|
autoPong: true,
|
|
661
671
|
closeTimeout: CLOSE_TIMEOUT,
|
|
662
672
|
protocolVersion: protocolVersions[1],
|
|
673
|
+
maxBufferedChunks: 1024 * 1024,
|
|
674
|
+
maxFragments: 128 * 1024,
|
|
663
675
|
maxPayload: 100 * 1024 * 1024,
|
|
664
676
|
skipUTF8Validation: false,
|
|
665
677
|
perMessageDeflate: true,
|
|
@@ -1017,6 +1029,8 @@ function initAsClient(websocket, address, protocols, options) {
|
|
|
1017
1029
|
websocket.setSocket(socket, head, {
|
|
1018
1030
|
allowSynchronousEvents: opts.allowSynchronousEvents,
|
|
1019
1031
|
generateMask: opts.generateMask,
|
|
1032
|
+
maxBufferedChunks: opts.maxBufferedChunks,
|
|
1033
|
+
maxFragments: opts.maxFragments,
|
|
1020
1034
|
maxPayload: opts.maxPayload,
|
|
1021
1035
|
skipUTF8Validation: opts.skipUTF8Validation
|
|
1022
1036
|
});
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/qqbot",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.26-beta.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@openclaw/qqbot",
|
|
9
|
-
"version": "2026.5.
|
|
9
|
+
"version": "2026.5.26-beta.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@tencent-connect/qqbot-connector": "1.1.0",
|
|
12
12
|
"mpg123-decoder": "1.0.3",
|
|
13
13
|
"silk-wasm": "3.7.1",
|
|
14
|
-
"ws": "8.
|
|
14
|
+
"ws": "8.21.0",
|
|
15
15
|
"zod": "4.4.3"
|
|
16
16
|
},
|
|
17
17
|
"peerDependencies": {
|
|
18
|
-
"openclaw": ">=2026.5.
|
|
18
|
+
"openclaw": ">=2026.5.26-beta.1"
|
|
19
19
|
},
|
|
20
20
|
"peerDependenciesMeta": {
|
|
21
21
|
"openclaw": {
|
|
@@ -92,9 +92,9 @@
|
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
94
|
"node_modules/ws": {
|
|
95
|
-
"version": "8.
|
|
96
|
-
"resolved": "https://registry.npmjs.org/ws/-/ws-8.
|
|
97
|
-
"integrity": "sha512-
|
|
95
|
+
"version": "8.21.0",
|
|
96
|
+
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
|
|
97
|
+
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
|
|
98
98
|
"license": "MIT",
|
|
99
99
|
"engines": {
|
|
100
100
|
"node": ">=10.0.0"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/qqbot",
|
|
3
|
-
"version": "2026.5.
|
|
3
|
+
"version": "2026.5.26-beta.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "OpenClaw QQ Bot channel plugin",
|
|
6
6
|
"repository": {
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
"@tencent-connect/qqbot-connector": "1.1.0",
|
|
13
13
|
"mpg123-decoder": "1.0.3",
|
|
14
14
|
"silk-wasm": "3.7.1",
|
|
15
|
-
"ws": "8.
|
|
15
|
+
"ws": "8.21.0",
|
|
16
16
|
"zod": "4.4.3"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
|
-
"openclaw": ">=2026.5.
|
|
19
|
+
"openclaw": ">=2026.5.26-beta.1"
|
|
20
20
|
},
|
|
21
21
|
"peerDependenciesMeta": {
|
|
22
22
|
"openclaw": {
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"minHostVersion": ">=2026.4.10"
|
|
46
46
|
},
|
|
47
47
|
"compat": {
|
|
48
|
-
"pluginApi": ">=2026.5.
|
|
48
|
+
"pluginApi": ">=2026.5.26-beta.1"
|
|
49
49
|
},
|
|
50
50
|
"build": {
|
|
51
|
-
"openclawVersion": "2026.5.
|
|
51
|
+
"openclawVersion": "2026.5.26-beta.1"
|
|
52
52
|
},
|
|
53
53
|
"release": {
|
|
54
54
|
"publishToClawHub": true,
|