@openclaw/qqbot 2026.7.1-beta.1 → 2026.7.1-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +6 -6
- package/dist/{channel-Bh0MrYvU.js → channel-BQM6pDlD.js} +41 -30
- package/dist/{channel-entry-LMtNY-J6.js → channel-entry-C18ypTaG.js} +3 -3
- package/dist/channel-entry-api.js +1 -1
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.setup-CldhUlTO.js → channel.setup-2ItDYKhz.js} +1 -1
- package/dist/{config-schema-gMW9zKvQ.js → config-schema-JZEf1dvB.js} +2 -5
- package/dist/{gateway-BgfQAYxB.js → gateway-CjdB3B6n.js} +279 -108
- package/dist/{group-DQQsZ6Y0.js → group-BmS1Qwst.js} +1 -1
- package/dist/{handler-runtime-BQ72YPsH.js → handler-runtime-rU03VA43.js} +3 -3
- package/dist/{outbound-Dfa2uOEp.js → outbound-BZHwMFUX.js} +294 -54
- package/dist/{runtime-BL0xZzWe.js → runtime-B4HWCOa-.js} +1 -1
- package/dist/runtime-api.js +1 -1
- package/dist/{sender-BHWpE_yH.js → sender-BfVLJJYp.js} +14 -12
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{tools-BvXe7bTq.js → tools-C8kTMXKc.js} +1 -1
- package/dist/tools-api.js +1 -1
- package/npm-shrinkwrap.json +3 -3
- package/package.json +4 -4
- package/skills/qqbot-media/SKILL.md +14 -11
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as asOptionalObjectRecord } from "./string-normalize-R_0cKO7Q.js";
|
|
2
2
|
import { d as resolveAccountBase } from "./config-C1qZbh0K.js";
|
|
3
3
|
import { n as debugLog } from "./log-SDfMMBWe.js";
|
|
4
|
-
import {
|
|
4
|
+
import { I as getQQBotMediaPath, N as getHomeDir, P as getQQBotDataDir, z as isWindows } from "./outbound-BZHwMFUX.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";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { c as getMessageApi, t as accountToCreds } from "./sender-
|
|
2
|
-
import { o as ensurePlatformAdapter, s as getBridgeLogger } from "./config-schema-
|
|
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-
|
|
1
|
+
import { c as getMessageApi, t as accountToCreds } from "./sender-BfVLJJYp.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-BQM6pDlD.js";
|
|
4
4
|
import { a as resolveQQBotAccount } from "./config-C1qZbh0K.js";
|
|
5
5
|
import { resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
|
|
6
6
|
import { createChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
|
|
@@ -1,13 +1,16 @@
|
|
|
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-
|
|
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-BfVLJJYp.js";
|
|
2
2
|
import { c as getPlatformAdapter, i as normalizeOptionalString, n as normalizeLowercaseStringOrEmpty, s as sanitizeFileName } from "./string-normalize-R_0cKO7Q.js";
|
|
3
3
|
import { a as formatErrorMessage, n as debugLog, r as debugWarn, t as debugError } from "./log-SDfMMBWe.js";
|
|
4
4
|
import { r as parseTarget$1 } from "./target-parser-DagYRQkP.js";
|
|
5
|
-
import * as fs$
|
|
5
|
+
import * as fs$2 from "node:fs";
|
|
6
6
|
import * as os$1 from "node:os";
|
|
7
|
+
import { randomUUID } from "node:crypto";
|
|
7
8
|
import { pathExistsSync, resolveLocalPathFromRootsSync } from "openclaw/plugin-sdk/security-runtime";
|
|
8
9
|
import * as path$1 from "node:path";
|
|
9
10
|
import path from "node:path";
|
|
10
|
-
import { getFileExtension } from "openclaw/plugin-sdk/media-mime";
|
|
11
|
+
import { extensionForMime, getFileExtension } from "openclaw/plugin-sdk/media-mime";
|
|
12
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
13
|
+
import { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
|
|
11
14
|
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/sandbox";
|
|
12
15
|
//#region extensions/qqbot/src/engine/utils/platform.ts
|
|
13
16
|
/**
|
|
@@ -32,10 +35,10 @@ import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/sandbox";
|
|
|
32
35
|
function getHomeDir() {
|
|
33
36
|
try {
|
|
34
37
|
const home = os$1.homedir();
|
|
35
|
-
if (home && fs$
|
|
38
|
+
if (home && fs$2.existsSync(home)) return home;
|
|
36
39
|
} catch {}
|
|
37
40
|
const envHome = process.env.HOME || process.env.USERPROFILE;
|
|
38
|
-
if (envHome && fs$
|
|
41
|
+
if (envHome && fs$2.existsSync(envHome)) return envHome;
|
|
39
42
|
return getPlatformAdapter().getTempDir();
|
|
40
43
|
}
|
|
41
44
|
/**
|
|
@@ -76,7 +79,7 @@ function getQQBotDataPath(...subPaths) {
|
|
|
76
79
|
/** Return a path under `~/.openclaw/qqbot`, creating it on demand. */
|
|
77
80
|
function getQQBotDataDir(...subPaths) {
|
|
78
81
|
const dir = getQQBotDataPath(...subPaths);
|
|
79
|
-
if (!fs$
|
|
82
|
+
if (!fs$2.existsSync(dir)) fs$2.mkdirSync(dir, { recursive: true });
|
|
80
83
|
return dir;
|
|
81
84
|
}
|
|
82
85
|
/**
|
|
@@ -94,7 +97,7 @@ function getQQBotMediaPath(...subPaths) {
|
|
|
94
97
|
/** Return a path under `<openclaw-home>/.openclaw/media/qqbot`, creating it on demand. */
|
|
95
98
|
function getQQBotMediaDir(...subPaths) {
|
|
96
99
|
const dir = getQQBotMediaPath(...subPaths);
|
|
97
|
-
if (!fs$
|
|
100
|
+
if (!fs$2.existsSync(dir)) fs$2.mkdirSync(dir, { recursive: true });
|
|
98
101
|
return dir;
|
|
99
102
|
}
|
|
100
103
|
/**
|
|
@@ -163,14 +166,14 @@ function isLocalPath(p) {
|
|
|
163
166
|
if (p.startsWith(".\\") || p.startsWith("..\\")) return true;
|
|
164
167
|
return false;
|
|
165
168
|
}
|
|
166
|
-
function isPathWithinRoot(candidate, root) {
|
|
169
|
+
function isPathWithinRoot$1(candidate, root) {
|
|
167
170
|
const relative = path$1.relative(root, candidate);
|
|
168
171
|
return relative === "" || !relative.startsWith("..") && !path$1.isAbsolute(relative);
|
|
169
172
|
}
|
|
170
173
|
/** Remap legacy or hallucinated QQ Bot local media paths to real files when possible. */
|
|
171
174
|
function resolveQQBotLocalMediaPath(p) {
|
|
172
175
|
const normalized = normalizePath$1(p);
|
|
173
|
-
if (!isLocalPath(normalized) || fs$
|
|
176
|
+
if (!isLocalPath(normalized) || fs$2.existsSync(normalized)) return normalized;
|
|
174
177
|
const osHomeDir = getHomeDir();
|
|
175
178
|
const openclawHomeDir = resolveOpenClawHome();
|
|
176
179
|
const mediaRoot = getQQBotMediaPath();
|
|
@@ -190,10 +193,10 @@ function resolveQQBotLocalMediaPath(p) {
|
|
|
190
193
|
}
|
|
191
194
|
];
|
|
192
195
|
for (const { from, to } of candidateRoots) {
|
|
193
|
-
if (!isPathWithinRoot(normalized, from)) continue;
|
|
196
|
+
if (!isPathWithinRoot$1(normalized, from)) continue;
|
|
194
197
|
const relative = path$1.relative(from, normalized);
|
|
195
198
|
const candidate = path$1.join(to, relative);
|
|
196
|
-
if (fs$
|
|
199
|
+
if (fs$2.existsSync(candidate)) {
|
|
197
200
|
debugWarn(`[platform] Remapped missing QQBot media path ${normalized} -> ${candidate}`);
|
|
198
201
|
return candidate;
|
|
199
202
|
}
|
|
@@ -208,12 +211,12 @@ function resolveQQBotPayloadLocalFilePath(p) {
|
|
|
208
211
|
const candidate = resolveQQBotLocalMediaPath(p);
|
|
209
212
|
if (!candidate.trim()) return null;
|
|
210
213
|
const resolvedCandidate = path$1.resolve(candidate);
|
|
211
|
-
if (!fs$
|
|
212
|
-
const canonicalCandidate = fs$
|
|
214
|
+
if (!fs$2.existsSync(resolvedCandidate)) return null;
|
|
215
|
+
const canonicalCandidate = fs$2.realpathSync(resolvedCandidate);
|
|
213
216
|
const allowedRoots = [getOpenClawMediaDir(), getQQBotMediaPath()];
|
|
214
217
|
for (const root of allowedRoots) {
|
|
215
218
|
const resolvedRoot = path$1.resolve(root);
|
|
216
|
-
if (isPathWithinRoot(canonicalCandidate, fs$
|
|
219
|
+
if (isPathWithinRoot$1(canonicalCandidate, fs$2.existsSync(resolvedRoot) ? fs$2.realpathSync(resolvedRoot) : resolvedRoot)) return canonicalCandidate;
|
|
217
220
|
}
|
|
218
221
|
return null;
|
|
219
222
|
}
|
|
@@ -406,6 +409,46 @@ function buildFileTooLargeResult(fileType, fileSize) {
|
|
|
406
409
|
};
|
|
407
410
|
}
|
|
408
411
|
//#endregion
|
|
412
|
+
//#region extensions/qqbot/src/engine/messaging/outbound-media-path.ts
|
|
413
|
+
function mergeMediaLocalRoots(...groups) {
|
|
414
|
+
const roots = groups.flatMap((group) => group ?? []).map((root) => root.trim()).filter(Boolean);
|
|
415
|
+
return roots.length > 0 ? Array.from(new Set(roots)) : void 0;
|
|
416
|
+
}
|
|
417
|
+
function resolveOutboundMediaLocalRoots(ctx) {
|
|
418
|
+
return mergeMediaLocalRoots(ctx.mediaAccess?.localRoots, ctx.mediaLocalRoots);
|
|
419
|
+
}
|
|
420
|
+
function isPathWithinRoot(candidatePath, rootPath) {
|
|
421
|
+
const resolvedRoot = path.resolve(rootPath);
|
|
422
|
+
if (resolvedRoot === path.parse(resolvedRoot).root) return false;
|
|
423
|
+
const relative = path.relative(resolvedRoot, path.resolve(candidatePath));
|
|
424
|
+
return relative === "" || relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
425
|
+
}
|
|
426
|
+
function resolvePathInsideWorkspace(workspaceDir, pathWithinWorkspace) {
|
|
427
|
+
const mappedPath = path.resolve(workspaceDir, pathWithinWorkspace);
|
|
428
|
+
return isPathWithinRoot(mappedPath, workspaceDir) ? mappedPath : null;
|
|
429
|
+
}
|
|
430
|
+
function isVirtualWorkspacePath(normalizedPath) {
|
|
431
|
+
return normalizedPath === "/workspace" || normalizedPath.startsWith("/workspace/");
|
|
432
|
+
}
|
|
433
|
+
function resolveWorkspaceScopedLocalRoots(roots, workspaceDir) {
|
|
434
|
+
if (!roots?.length) return;
|
|
435
|
+
const scopedRoots = roots.map((root) => root.trim()).filter(Boolean).map((root) => workspaceDir && isVirtualWorkspacePath(root) ? resolveWorkspacePathCandidate(root, workspaceDir) : root).filter((root) => Boolean(root));
|
|
436
|
+
return scopedRoots.length > 0 ? Array.from(new Set(scopedRoots)) : void 0;
|
|
437
|
+
}
|
|
438
|
+
function resolveWorkspacePathCandidate(normalizedPath, workspaceDir) {
|
|
439
|
+
if (!workspaceDir) return isVirtualWorkspacePath(normalizedPath) ? null : normalizedPath;
|
|
440
|
+
if (normalizedPath === "/workspace") return workspaceDir;
|
|
441
|
+
if (normalizedPath.startsWith("/workspace/")) return resolvePathInsideWorkspace(workspaceDir, normalizedPath.slice(11));
|
|
442
|
+
if (path.isAbsolute(normalizedPath)) return normalizedPath;
|
|
443
|
+
return resolvePathInsideWorkspace(workspaceDir, normalizedPath);
|
|
444
|
+
}
|
|
445
|
+
function resolveWorkspacePathCandidates(normalizedPath, workspaceDir) {
|
|
446
|
+
const mappedPath = resolveWorkspacePathCandidate(normalizedPath, workspaceDir);
|
|
447
|
+
if (!mappedPath) return [];
|
|
448
|
+
if (mappedPath === normalizedPath) return [normalizedPath];
|
|
449
|
+
return path.isAbsolute(normalizedPath) && !isVirtualWorkspacePath(normalizedPath) ? [normalizedPath, mappedPath] : [mappedPath];
|
|
450
|
+
}
|
|
451
|
+
//#endregion
|
|
409
452
|
//#region extensions/qqbot/src/engine/messaging/trusted-media-path.ts
|
|
410
453
|
let cachedTrustedTmpRoot;
|
|
411
454
|
function trustedOpenClawTmpRoot() {
|
|
@@ -462,11 +505,15 @@ function parseTarget(to) {
|
|
|
462
505
|
/** Build a media target from a normal outbound context. */
|
|
463
506
|
function buildMediaTarget(ctx) {
|
|
464
507
|
const target = parseTarget(ctx.to);
|
|
508
|
+
const mediaLocalRoots = resolveOutboundMediaLocalRoots(ctx);
|
|
465
509
|
return {
|
|
466
510
|
targetType: target.type,
|
|
467
511
|
targetId: target.id,
|
|
468
512
|
account: ctx.account,
|
|
469
|
-
replyToId: ctx.replyToId ?? void 0
|
|
513
|
+
replyToId: ctx.replyToId ?? void 0,
|
|
514
|
+
...mediaLocalRoots ? { mediaLocalRoots } : {},
|
|
515
|
+
...ctx.mediaAccess ? { mediaAccess: ctx.mediaAccess } : {},
|
|
516
|
+
...ctx.mediaReadFile ? { mediaReadFile: ctx.mediaReadFile } : {}
|
|
470
517
|
};
|
|
471
518
|
}
|
|
472
519
|
/** Return true when public URLs should be passed through directly. */
|
|
@@ -480,19 +527,28 @@ const qqBotMediaKindLabel = {
|
|
|
480
527
|
file: "File",
|
|
481
528
|
media: "Media"
|
|
482
529
|
};
|
|
530
|
+
function isHttpUrl(pathValue) {
|
|
531
|
+
return pathValue.startsWith("http://") || pathValue.startsWith("https://");
|
|
532
|
+
}
|
|
533
|
+
function isDataUrl(pathValue) {
|
|
534
|
+
return pathValue.startsWith("data:");
|
|
535
|
+
}
|
|
483
536
|
function isHttpOrDataSource(pathValue) {
|
|
484
|
-
return pathValue
|
|
537
|
+
return isHttpUrl(pathValue) || isDataUrl(pathValue);
|
|
485
538
|
}
|
|
486
|
-
function
|
|
539
|
+
function resolveMissingPathWithinRoots(normalizedPath, allowedRoots) {
|
|
487
540
|
const resolvedCandidate = path.resolve(normalizedPath);
|
|
488
541
|
if (pathExistsSync(resolvedCandidate)) return null;
|
|
489
542
|
return resolveLocalPathFromRootsSync({
|
|
490
543
|
filePath: resolvedCandidate,
|
|
491
|
-
roots:
|
|
492
|
-
label: "QQ Bot
|
|
544
|
+
roots: allowedRoots,
|
|
545
|
+
label: "QQ Bot local roots",
|
|
493
546
|
allowMissing: true
|
|
494
547
|
})?.path ?? null;
|
|
495
548
|
}
|
|
549
|
+
function isPathWithinAnyRoot(candidatePath, allowedRoots) {
|
|
550
|
+
return allowedRoots?.some((root) => root.trim() && isPathWithinRoot(candidatePath, root)) ?? false;
|
|
551
|
+
}
|
|
496
552
|
function resolveExistingPathWithinRoots(normalizedPath, allowedRoots) {
|
|
497
553
|
return resolveLocalPathFromRootsSync({
|
|
498
554
|
filePath: normalizedPath,
|
|
@@ -500,30 +556,166 @@ function resolveExistingPathWithinRoots(normalizedPath, allowedRoots) {
|
|
|
500
556
|
label: "QQ Bot local roots"
|
|
501
557
|
})?.path ?? null;
|
|
502
558
|
}
|
|
559
|
+
function resolveOutboundMediaReadFile(ctx) {
|
|
560
|
+
return ctx.mediaAccess?.readFile ?? ctx.mediaReadFile;
|
|
561
|
+
}
|
|
562
|
+
function resolveHostReadMediaAccess(ctx) {
|
|
563
|
+
const mediaLocalRoots = resolveWorkspaceScopedLocalRoots(resolveOutboundMediaLocalRoots(ctx), ctx.mediaAccess?.workspaceDir);
|
|
564
|
+
if (!ctx.mediaAccess && !mediaLocalRoots) return;
|
|
565
|
+
const { localRoots: _localRoots, ...mediaAccessWithoutRoots } = ctx.mediaAccess ?? {};
|
|
566
|
+
return {
|
|
567
|
+
...mediaAccessWithoutRoots,
|
|
568
|
+
...mediaLocalRoots ? { localRoots: mediaLocalRoots } : {}
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
function mediaFileTypeForKind(mediaKind) {
|
|
572
|
+
switch (mediaKind) {
|
|
573
|
+
case "image": return 1;
|
|
574
|
+
case "voice": return 3;
|
|
575
|
+
case "video": return 2;
|
|
576
|
+
default: return 4;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function senderKindForLoadedMedia(mediaKind, loadedKind) {
|
|
580
|
+
if (mediaKind === "image") return loadedKind === "image" ? "image" : null;
|
|
581
|
+
if (mediaKind === "video") return loadedKind === "video" ? "video" : null;
|
|
582
|
+
if (mediaKind === "file") return "file";
|
|
583
|
+
if (loadedKind === "image") return "image";
|
|
584
|
+
if (loadedKind === "video") return "video";
|
|
585
|
+
return "file";
|
|
586
|
+
}
|
|
587
|
+
function resolveHostReadMediaPath(ctx, mediaPath) {
|
|
588
|
+
const normalizedPath = normalizePath$1(mediaPath);
|
|
589
|
+
if (path.isAbsolute(normalizedPath)) {
|
|
590
|
+
if (normalizedPath === "/workspace" || normalizedPath.startsWith("/workspace/")) return ctx.mediaAccess?.workspaceDir ? resolveWorkspacePathCandidate(normalizedPath, ctx.mediaAccess.workspaceDir) : null;
|
|
591
|
+
if (isPathWithinAnyRoot(normalizedPath, resolveOutboundMediaLocalRoots(ctx))) return normalizedPath;
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
if (!ctx.mediaAccess?.workspaceDir) return null;
|
|
595
|
+
return resolveWorkspacePathCandidate(normalizedPath, ctx.mediaAccess.workspaceDir);
|
|
596
|
+
}
|
|
597
|
+
async function stageLoadedHostReadVoice(mediaPath, loaded) {
|
|
598
|
+
const stagedDir = getQQBotMediaDir("host-read", "voice");
|
|
599
|
+
await mkdir(stagedDir, { recursive: true });
|
|
600
|
+
const rawFileName = sanitizeFileName(loaded.fileName || path.basename(mediaPath) || "voice");
|
|
601
|
+
const ext = path.extname(rawFileName);
|
|
602
|
+
const inferredExt = extensionForMime(loaded.contentType);
|
|
603
|
+
const baseName = sanitizeFileName(path.basename(rawFileName, ext)) || "voice";
|
|
604
|
+
const stagedPath = path.join(stagedDir, `${baseName}-${randomUUID()}${ext || inferredExt || ".bin"}`);
|
|
605
|
+
await writeFile(stagedPath, loaded.buffer);
|
|
606
|
+
return stagedPath;
|
|
607
|
+
}
|
|
608
|
+
async function stageHostReadVoice(ctx, mediaPath) {
|
|
609
|
+
const mediaReadFile = resolveOutboundMediaReadFile(ctx);
|
|
610
|
+
if (!mediaReadFile || isHttpOrDataSource(mediaPath)) return null;
|
|
611
|
+
const hostReadMediaPath = resolveHostReadMediaPath(ctx, mediaPath);
|
|
612
|
+
if (!hostReadMediaPath) return null;
|
|
613
|
+
const mediaAccess = resolveHostReadMediaAccess(ctx);
|
|
614
|
+
const loaded = await loadOutboundMediaFromUrl(hostReadMediaPath, {
|
|
615
|
+
maxBytes: getMaxUploadSize(3),
|
|
616
|
+
mediaAccess,
|
|
617
|
+
mediaReadFile,
|
|
618
|
+
workspaceDir: mediaAccess?.workspaceDir
|
|
619
|
+
});
|
|
620
|
+
if (loaded.kind !== "audio") throw new Error(`Unsupported voice media type: ${loaded.kind ?? "unknown"}`);
|
|
621
|
+
return await stageLoadedHostReadVoice(mediaPath, loaded);
|
|
622
|
+
}
|
|
623
|
+
async function trySendViaHostRead(ctx, mediaPath, mediaKind) {
|
|
624
|
+
const mediaReadFile = resolveOutboundMediaReadFile(ctx);
|
|
625
|
+
if (!mediaReadFile || isHttpOrDataSource(mediaPath)) return null;
|
|
626
|
+
const hostReadMediaPath = resolveHostReadMediaPath(ctx, mediaPath);
|
|
627
|
+
if (!hostReadMediaPath) return null;
|
|
628
|
+
const mediaAccess = resolveHostReadMediaAccess(ctx);
|
|
629
|
+
try {
|
|
630
|
+
const loaded = await loadOutboundMediaFromUrl(hostReadMediaPath, {
|
|
631
|
+
maxBytes: getMaxUploadSize(mediaFileTypeForKind(mediaKind)),
|
|
632
|
+
mediaAccess,
|
|
633
|
+
mediaReadFile,
|
|
634
|
+
workspaceDir: mediaAccess?.workspaceDir
|
|
635
|
+
});
|
|
636
|
+
const kind = senderKindForLoadedMedia(mediaKind, loaded.kind);
|
|
637
|
+
if (!kind) return {
|
|
638
|
+
channel: "qqbot",
|
|
639
|
+
error: `Unsupported ${mediaKind} media type: ${loaded.kind ?? "unknown"}`
|
|
640
|
+
};
|
|
641
|
+
if (loaded.buffer.length === 0) return {
|
|
642
|
+
channel: "qqbot",
|
|
643
|
+
error: `File is empty: ${hostReadMediaPath}`
|
|
644
|
+
};
|
|
645
|
+
if (mediaKind === "media" && loaded.kind === "audio") {
|
|
646
|
+
const directUploadFormats = ctx.account.config?.audioFormatPolicy?.uploadDirectFormats ?? ctx.account.config?.voiceDirectUploadFormats;
|
|
647
|
+
const transcodeEnabled = ctx.account.config?.audioFormatPolicy?.transcodeEnabled !== false;
|
|
648
|
+
return await sendVoiceFromLocal(ctx, await stageLoadedHostReadVoice(mediaPath, loaded), directUploadFormats, transcodeEnabled);
|
|
649
|
+
}
|
|
650
|
+
const creds = accountToCreds(ctx.account);
|
|
651
|
+
const target = {
|
|
652
|
+
type: ctx.targetType,
|
|
653
|
+
id: ctx.targetId
|
|
654
|
+
};
|
|
655
|
+
if (target.type !== "c2c" && target.type !== "group") return {
|
|
656
|
+
channel: "qqbot",
|
|
657
|
+
error: `${qqBotMediaKindLabel[mediaKind]} not supported in channel`
|
|
658
|
+
};
|
|
659
|
+
const r = await sendMedia$1({
|
|
660
|
+
target,
|
|
661
|
+
creds,
|
|
662
|
+
kind,
|
|
663
|
+
source: {
|
|
664
|
+
buffer: loaded.buffer,
|
|
665
|
+
...loaded.fileName ? { fileName: sanitizeFileName(loaded.fileName) } : {},
|
|
666
|
+
...loaded.contentType ? { mime: loaded.contentType } : {}
|
|
667
|
+
},
|
|
668
|
+
msgId: ctx.replyToId,
|
|
669
|
+
...kind === "file" && loaded.fileName ? { fileName: sanitizeFileName(loaded.fileName) } : {}
|
|
670
|
+
});
|
|
671
|
+
return {
|
|
672
|
+
channel: "qqbot",
|
|
673
|
+
messageId: r.id,
|
|
674
|
+
timestamp: r.timestamp
|
|
675
|
+
};
|
|
676
|
+
} catch (err) {
|
|
677
|
+
if (err instanceof UploadDailyLimitExceededError) return buildDailyLimitExceededResult(err.filePath === "<buffer>" ? new UploadDailyLimitExceededError(hostReadMediaPath, err.fileSize, err.message) : err);
|
|
678
|
+
return {
|
|
679
|
+
channel: "qqbot",
|
|
680
|
+
error: formatErrorMessage(err)
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
async function sendAutoDetectedMedia(ctx, mediaPath) {
|
|
685
|
+
const hostReadResult = await trySendViaHostRead(ctx, mediaPath, "media");
|
|
686
|
+
if (hostReadResult) return hostReadResult;
|
|
687
|
+
return await sendDocument(ctx, mediaPath);
|
|
688
|
+
}
|
|
503
689
|
function resolveOutboundMediaPath(rawPath, mediaKind, options = {}) {
|
|
504
690
|
const normalizedPath = normalizePath$1(rawPath);
|
|
505
691
|
if (isHttpOrDataSource(normalizedPath)) return {
|
|
506
692
|
ok: true,
|
|
507
693
|
mediaPath: normalizedPath
|
|
508
694
|
};
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
};
|
|
514
|
-
if (options.extraLocalRoots && options.extraLocalRoots.length > 0) {
|
|
515
|
-
const extraAllowedPath = resolveExistingPathWithinRoots(normalizedPath, options.extraLocalRoots);
|
|
516
|
-
if (extraAllowedPath) return {
|
|
695
|
+
const candidatePaths = resolveWorkspacePathCandidates(normalizedPath, options.workspaceDir);
|
|
696
|
+
for (const candidatePath of candidatePaths) {
|
|
697
|
+
const allowedPath = resolveTrustedOutboundMediaPath(candidatePath, { allowMissing: options.allowMissingLocalPath });
|
|
698
|
+
if (allowedPath) return {
|
|
517
699
|
ok: true,
|
|
518
|
-
mediaPath:
|
|
700
|
+
mediaPath: allowedPath
|
|
519
701
|
};
|
|
702
|
+
if (options.extraLocalRoots && options.extraLocalRoots.length > 0) {
|
|
703
|
+
const extraAllowedPath = resolveExistingPathWithinRoots(candidatePath, options.extraLocalRoots);
|
|
704
|
+
if (extraAllowedPath) return {
|
|
705
|
+
ok: true,
|
|
706
|
+
mediaPath: extraAllowedPath
|
|
707
|
+
};
|
|
708
|
+
}
|
|
520
709
|
}
|
|
521
710
|
if (options.allowMissingLocalPath) {
|
|
522
|
-
const
|
|
523
|
-
if (
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
711
|
+
const missingRoots = mergeMediaLocalRoots([getQQBotMediaDir()], options.extraLocalRoots);
|
|
712
|
+
if (missingRoots) for (const candidatePath of candidatePaths) {
|
|
713
|
+
const allowedMissingPath = resolveMissingPathWithinRoots(candidatePath, missingRoots);
|
|
714
|
+
if (allowedMissingPath) return {
|
|
715
|
+
ok: true,
|
|
716
|
+
mediaPath: allowedMissingPath
|
|
717
|
+
};
|
|
718
|
+
}
|
|
527
719
|
}
|
|
528
720
|
debugWarn(`blocked local ${mediaKind} path outside QQ Bot media storage`);
|
|
529
721
|
return {
|
|
@@ -535,15 +727,20 @@ function resolveOutboundMediaPath(rawPath, mediaKind, options = {}) {
|
|
|
535
727
|
* Send a photo from a local file, public URL, or Base64 data URL.
|
|
536
728
|
*/
|
|
537
729
|
async function sendPhoto(ctx, imagePath) {
|
|
538
|
-
const
|
|
730
|
+
const hostReadResult = await trySendViaHostRead(ctx, imagePath, "image");
|
|
731
|
+
if (hostReadResult) return hostReadResult;
|
|
732
|
+
const resolvedMediaPath = resolveOutboundMediaPath(imagePath, "image", {
|
|
733
|
+
extraLocalRoots: resolveOutboundMediaLocalRoots(ctx),
|
|
734
|
+
workspaceDir: ctx.mediaAccess?.workspaceDir
|
|
735
|
+
});
|
|
539
736
|
if (!resolvedMediaPath.ok) return {
|
|
540
737
|
channel: "qqbot",
|
|
541
738
|
error: resolvedMediaPath.error
|
|
542
739
|
};
|
|
543
740
|
const mediaPath = resolvedMediaPath.mediaPath;
|
|
544
741
|
const isLocal = isLocalPath(mediaPath);
|
|
545
|
-
const isHttp =
|
|
546
|
-
const isData = mediaPath
|
|
742
|
+
const isHttp = isHttpUrl(mediaPath);
|
|
743
|
+
const isData = isDataUrl(mediaPath);
|
|
547
744
|
if (isHttp && !shouldDirectUploadUrl(ctx.account)) {
|
|
548
745
|
debugLog(`sendPhoto: urlDirectUpload=false, downloading URL first...`);
|
|
549
746
|
const localFile = await downloadToFallbackDir(mediaPath, "sendPhoto");
|
|
@@ -663,13 +860,29 @@ async function sendPhotoFromLocal(ctx, mediaPath) {
|
|
|
663
860
|
* URL handling respects `urlDirectUpload`, and local files are transcoded when needed.
|
|
664
861
|
*/
|
|
665
862
|
async function sendVoice(ctx, voicePath, directUploadFormats, transcodeEnabled = true) {
|
|
666
|
-
|
|
863
|
+
let stagedHostReadVoice;
|
|
864
|
+
try {
|
|
865
|
+
stagedHostReadVoice = await stageHostReadVoice(ctx, voicePath);
|
|
866
|
+
} catch (err) {
|
|
867
|
+
return {
|
|
868
|
+
channel: "qqbot",
|
|
869
|
+
error: formatErrorMessage(err)
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
const resolvedMediaPath = stagedHostReadVoice ? {
|
|
873
|
+
ok: true,
|
|
874
|
+
mediaPath: stagedHostReadVoice
|
|
875
|
+
} : resolveOutboundMediaPath(voicePath, "voice", {
|
|
876
|
+
allowMissingLocalPath: true,
|
|
877
|
+
extraLocalRoots: resolveOutboundMediaLocalRoots(ctx),
|
|
878
|
+
workspaceDir: ctx.mediaAccess?.workspaceDir
|
|
879
|
+
});
|
|
667
880
|
if (!resolvedMediaPath.ok) return {
|
|
668
881
|
channel: "qqbot",
|
|
669
882
|
error: resolvedMediaPath.error
|
|
670
883
|
};
|
|
671
884
|
const mediaPath = resolvedMediaPath.mediaPath;
|
|
672
|
-
if (
|
|
885
|
+
if (isHttpUrl(mediaPath)) {
|
|
673
886
|
if (shouldDirectUploadUrl(ctx.account)) try {
|
|
674
887
|
const creds = accountToCreds(ctx.account);
|
|
675
888
|
const target = {
|
|
@@ -716,7 +929,8 @@ async function sendVoiceFromLocal(ctx, mediaPath, directUploadFormats, transcode
|
|
|
716
929
|
error: "Voice generate failed"
|
|
717
930
|
};
|
|
718
931
|
if (fileSize > getMaxUploadSize(3)) return buildFileTooLargeResult(3, fileSize);
|
|
719
|
-
const
|
|
932
|
+
const extraLocalRoots = resolveOutboundMediaLocalRoots(ctx);
|
|
933
|
+
const safeMediaPath = resolveTrustedOutboundMediaPath(mediaPath) ?? (extraLocalRoots ? resolveExistingPathWithinRoots(mediaPath, extraLocalRoots) : null);
|
|
720
934
|
if (!safeMediaPath) {
|
|
721
935
|
debugWarn(`sendVoice: blocked local voice path outside QQ Bot media storage`);
|
|
722
936
|
return {
|
|
@@ -779,13 +993,18 @@ async function sendVoiceFromLocal(ctx, mediaPath, directUploadFormats, transcode
|
|
|
779
993
|
}
|
|
780
994
|
/** Send video from either a public URL or a local file. */
|
|
781
995
|
async function sendVideoMsg(ctx, videoPath) {
|
|
782
|
-
const
|
|
996
|
+
const hostReadResult = await trySendViaHostRead(ctx, videoPath, "video");
|
|
997
|
+
if (hostReadResult) return hostReadResult;
|
|
998
|
+
const resolvedMediaPath = resolveOutboundMediaPath(videoPath, "video", {
|
|
999
|
+
extraLocalRoots: resolveOutboundMediaLocalRoots(ctx),
|
|
1000
|
+
workspaceDir: ctx.mediaAccess?.workspaceDir
|
|
1001
|
+
});
|
|
783
1002
|
if (!resolvedMediaPath.ok) return {
|
|
784
1003
|
channel: "qqbot",
|
|
785
1004
|
error: resolvedMediaPath.error
|
|
786
1005
|
};
|
|
787
1006
|
const mediaPath = resolvedMediaPath.mediaPath;
|
|
788
|
-
const isHttp =
|
|
1007
|
+
const isHttp = isHttpUrl(mediaPath);
|
|
789
1008
|
if (isHttp && !shouldDirectUploadUrl(ctx.account)) {
|
|
790
1009
|
debugLog(`sendVideoMsg: urlDirectUpload=false, downloading URL first...`);
|
|
791
1010
|
const localFile = await downloadToFallbackDir(mediaPath, "sendVideoMsg");
|
|
@@ -887,13 +1106,18 @@ async function sendVideoFromLocal(ctx, mediaPath) {
|
|
|
887
1106
|
}
|
|
888
1107
|
/** Send a file from a local path or public URL. */
|
|
889
1108
|
async function sendDocument(ctx, filePath, options = {}) {
|
|
890
|
-
const
|
|
1109
|
+
const hostReadResult = await trySendViaHostRead(ctx, filePath, "file");
|
|
1110
|
+
if (hostReadResult) return hostReadResult;
|
|
1111
|
+
const resolvedMediaPath = resolveOutboundMediaPath(filePath, "file", {
|
|
1112
|
+
extraLocalRoots: mergeMediaLocalRoots(options.allowQQBotDataDownloads ? [getQQBotDataDir("downloads")] : void 0, resolveOutboundMediaLocalRoots(ctx)),
|
|
1113
|
+
workspaceDir: ctx.mediaAccess?.workspaceDir
|
|
1114
|
+
});
|
|
891
1115
|
if (!resolvedMediaPath.ok) return {
|
|
892
1116
|
channel: "qqbot",
|
|
893
1117
|
error: resolvedMediaPath.error
|
|
894
1118
|
};
|
|
895
1119
|
const mediaPath = resolvedMediaPath.mediaPath;
|
|
896
|
-
const isHttp =
|
|
1120
|
+
const isHttp = isHttpUrl(mediaPath);
|
|
897
1121
|
const fileName = sanitizeFileName(path.basename(mediaPath));
|
|
898
1122
|
if (isHttp && !shouldDirectUploadUrl(ctx.account)) {
|
|
899
1123
|
debugLog(`sendDocument: urlDirectUpload=false, downloading URL first...`);
|
|
@@ -918,7 +1142,7 @@ async function sendDocument(ctx, filePath, options = {}) {
|
|
|
918
1142
|
kind: "file",
|
|
919
1143
|
source: { url: mediaPath },
|
|
920
1144
|
msgId: ctx.replyToId,
|
|
921
|
-
fileName
|
|
1145
|
+
...fileName ? { fileName } : {}
|
|
922
1146
|
});
|
|
923
1147
|
return {
|
|
924
1148
|
channel: "qqbot",
|
|
@@ -1435,7 +1659,10 @@ async function sendText(ctx) {
|
|
|
1435
1659
|
const mediaTarget = buildMediaTarget({
|
|
1436
1660
|
to,
|
|
1437
1661
|
account,
|
|
1438
|
-
replyToId
|
|
1662
|
+
replyToId,
|
|
1663
|
+
mediaAccess: ctx.mediaAccess,
|
|
1664
|
+
mediaLocalRoots: ctx.mediaLocalRoots,
|
|
1665
|
+
mediaReadFile: ctx.mediaReadFile
|
|
1439
1666
|
});
|
|
1440
1667
|
let lastResult = { channel: "qqbot" };
|
|
1441
1668
|
for (const item of sendQueue) try {
|
|
@@ -1464,7 +1691,10 @@ async function sendText(ctx) {
|
|
|
1464
1691
|
mediaUrl: item.content,
|
|
1465
1692
|
accountId: account.accountId,
|
|
1466
1693
|
replyToId,
|
|
1467
|
-
account
|
|
1694
|
+
account,
|
|
1695
|
+
mediaAccess: ctx.mediaAccess,
|
|
1696
|
+
mediaLocalRoots: ctx.mediaLocalRoots,
|
|
1697
|
+
mediaReadFile: ctx.mediaReadFile
|
|
1468
1698
|
});
|
|
1469
1699
|
} catch (err) {
|
|
1470
1700
|
const errMsg = formatErrorMessage(err);
|
|
@@ -1526,17 +1756,27 @@ async function sendMedia(ctx) {
|
|
|
1526
1756
|
channel: "qqbot",
|
|
1527
1757
|
error: "mediaUrl is required for sendMedia"
|
|
1528
1758
|
};
|
|
1529
|
-
const
|
|
1759
|
+
const target = buildMediaTarget({
|
|
1760
|
+
to,
|
|
1761
|
+
account,
|
|
1762
|
+
replyToId,
|
|
1763
|
+
mediaAccess: ctx.mediaAccess,
|
|
1764
|
+
mediaLocalRoots: ctx.mediaLocalRoots,
|
|
1765
|
+
mediaReadFile: ctx.mediaReadFile
|
|
1766
|
+
});
|
|
1767
|
+
const resolvedMediaPath = !ctx.mediaAccess?.readFile && !ctx.mediaReadFile ? resolveOutboundMediaPath(ctx.mediaUrl, "media", {
|
|
1768
|
+
allowMissingLocalPath: true,
|
|
1769
|
+
extraLocalRoots: target.mediaLocalRoots ? [...target.mediaLocalRoots] : void 0,
|
|
1770
|
+
workspaceDir: target.mediaAccess?.workspaceDir
|
|
1771
|
+
}) : {
|
|
1772
|
+
ok: true,
|
|
1773
|
+
mediaPath: ctx.mediaUrl
|
|
1774
|
+
};
|
|
1530
1775
|
if (!resolvedMediaPath.ok) return {
|
|
1531
1776
|
channel: "qqbot",
|
|
1532
1777
|
error: resolvedMediaPath.error
|
|
1533
1778
|
};
|
|
1534
1779
|
const mediaUrl = resolvedMediaPath.mediaPath;
|
|
1535
|
-
const target = buildMediaTarget({
|
|
1536
|
-
to,
|
|
1537
|
-
account,
|
|
1538
|
-
replyToId
|
|
1539
|
-
});
|
|
1540
1780
|
if (isAudioFile(mediaUrl, mimeType)) {
|
|
1541
1781
|
const result = await sendVoice(target, mediaUrl, account.config?.audioFormatPolicy?.uploadDirectFormats ?? account.config?.voiceDirectUploadFormats, account.config?.audioFormatPolicy?.transcodeEnabled !== false);
|
|
1542
1782
|
if (!result.error) {
|
|
@@ -1561,7 +1801,7 @@ async function sendMedia(ctx) {
|
|
|
1561
1801
|
return result;
|
|
1562
1802
|
}
|
|
1563
1803
|
if (!isImageFile(mediaUrl, mimeType) && !isAudioFile(mediaUrl, mimeType) && !isVideoFile(mediaUrl, mimeType)) {
|
|
1564
|
-
const result = await
|
|
1804
|
+
const result = await sendAutoDetectedMedia(target, mediaUrl);
|
|
1565
1805
|
if (!result.error && text?.trim()) await sendTextAfterMedia(target, text);
|
|
1566
1806
|
return result;
|
|
1567
1807
|
}
|
|
@@ -1622,4 +1862,4 @@ async function sendCronMessage(account, to, message) {
|
|
|
1622
1862
|
});
|
|
1623
1863
|
}
|
|
1624
1864
|
//#endregion
|
|
1625
|
-
export {
|
|
1865
|
+
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 };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { v as setOpenClawVersion } from "./sender-
|
|
1
|
+
import { v as setOpenClawVersion } from "./sender-BfVLJJYp.js";
|
|
2
2
|
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
3
3
|
//#region extensions/qqbot/src/bridge/runtime.ts
|
|
4
4
|
const { setRuntime: _setRuntime, clearRuntime: resetQQBotRuntimeForTest, getRuntime: getQQBotRuntime } = createPluginRuntimeStore({
|
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-B4HWCOa-.js";
|
|
2
2
|
export { getQQBotRuntime, setQQBotRuntime };
|
|
@@ -6,6 +6,7 @@ import { readProviderTextResponse, readResponseTextLimited } from "openclaw/plug
|
|
|
6
6
|
import { fetchWithSsrFGuard, isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
7
7
|
import * as crypto$1 from "node:crypto";
|
|
8
8
|
import crypto from "node:crypto";
|
|
9
|
+
import { sleep } from "openclaw/plugin-sdk/runtime-env";
|
|
9
10
|
import { FsSafeError, openLocalFileSafely, readRegularFile, statRegularFileSync } from "openclaw/plugin-sdk/security-runtime";
|
|
10
11
|
import * as path$1 from "node:path";
|
|
11
12
|
import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
|
|
@@ -462,6 +463,17 @@ async function normalizeSource(raw, opts = {}) {
|
|
|
462
463
|
//#endregion
|
|
463
464
|
//#region extensions/qqbot/src/engine/api/retry.ts
|
|
464
465
|
/**
|
|
466
|
+
* Generic retry engine for QQ Bot API requests.
|
|
467
|
+
*
|
|
468
|
+
* Replaces the three separate retry implementations in the old `api.ts`:
|
|
469
|
+
* - `apiRequestWithRetry` (upload retry with exponential backoff)
|
|
470
|
+
* - `partFinishWithRetry` (part-finish retry + persistent retry on specific biz codes)
|
|
471
|
+
* - `completeUploadWithRetry` (unconditional retry for complete-upload)
|
|
472
|
+
*
|
|
473
|
+
* All three patterns are expressed as a single `withRetry` function
|
|
474
|
+
* parameterized by `RetryPolicy` and optional `PersistentRetryPolicy`.
|
|
475
|
+
*/
|
|
476
|
+
/**
|
|
465
477
|
* Execute an async operation with configurable retry semantics.
|
|
466
478
|
*
|
|
467
479
|
* @param fn - The async operation to retry.
|
|
@@ -484,7 +496,7 @@ async function withRetry(fn, policy, persistentPolicy, logger) {
|
|
|
484
496
|
if (attempt < policy.maxRetries) {
|
|
485
497
|
const delay = policy.backoff === "exponential" ? policy.baseDelayMs * 2 ** attempt : policy.baseDelayMs;
|
|
486
498
|
logger?.debug?.(`[qqbot:retry] Attempt ${attempt + 1} failed, retrying in ${delay}ms: ${lastError.message.slice(0, 100)}`);
|
|
487
|
-
await sleep
|
|
499
|
+
await sleep(delay);
|
|
488
500
|
}
|
|
489
501
|
}
|
|
490
502
|
throw lastError;
|
|
@@ -514,16 +526,11 @@ async function persistentRetryLoop(fn, policy, logger) {
|
|
|
514
526
|
if (remaining <= 0) break;
|
|
515
527
|
const actualDelay = Math.min(policy.intervalMs, remaining);
|
|
516
528
|
(logger?.warn ?? logger?.error)?.(`[qqbot:retry] Persistent retry #${attempt}: retrying in ${actualDelay}ms (remaining=${Math.round(remaining / 1e3)}s)`);
|
|
517
|
-
await sleep
|
|
529
|
+
await sleep(actualDelay);
|
|
518
530
|
}
|
|
519
531
|
logger?.error?.(`[qqbot:retry] Persistent retry timed out after ${policy.timeoutMs / 1e3}s (${attempt} attempts)`);
|
|
520
532
|
throw lastError ?? /* @__PURE__ */ new Error(`Persistent retry timed out (${policy.timeoutMs / 1e3}s)`);
|
|
521
533
|
}
|
|
522
|
-
function sleep$1(ms) {
|
|
523
|
-
return new Promise((resolve) => {
|
|
524
|
-
setTimeout(resolve, ms);
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
534
|
/** Standard upload retry: exponential backoff, skip 400/401/timeout errors. */
|
|
528
535
|
const UPLOAD_RETRY_POLICY = {
|
|
529
536
|
maxRetries: 2,
|
|
@@ -967,11 +974,6 @@ async function runWithConcurrency(tasks, maxConcurrent) {
|
|
|
967
974
|
await Promise.all(batch.map((task) => task()));
|
|
968
975
|
}
|
|
969
976
|
}
|
|
970
|
-
function sleep(ms) {
|
|
971
|
-
return new Promise((resolve) => {
|
|
972
|
-
setTimeout(resolve, ms);
|
|
973
|
-
});
|
|
974
|
-
}
|
|
975
977
|
//#endregion
|
|
976
978
|
//#region extensions/qqbot/src/engine/api/token.ts
|
|
977
979
|
/**
|
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-2ItDYKhz.js";
|
|
2
2
|
export { qqbotSetupPlugin };
|