@openclaw/qqbot 2026.6.5-beta.2 → 2026.6.5-beta.3
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 +7 -657
- package/dist/{channel-BLMrGYfg.js → channel-CcN43bPL.js} +32 -287
- package/dist/channel-entry-api.js +2 -0
- package/dist/channel-entry-fUBLXKPr.js +143 -0
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.setup-xBlmSU4X.js → channel.setup-CQ_DFfPx.js} +1 -1
- package/dist/config-ZEfgeoL4.js +257 -0
- package/dist/{config-schema-BI7AYP6Q.js → config-schema-BFLNZ8Nf.js} +17 -259
- package/dist/doctor-contract-BYTS8-ia.js +135 -0
- package/dist/doctor-contract-api.js +2 -0
- package/dist/{gateway-iL9SWAED.js → gateway-DdM2k3ss.js} +23 -26
- package/dist/{handler-runtime-CjXwuNlq.js → handler-runtime-JKYDlRdq.js} +4 -3
- package/dist/index.js +8 -1
- package/dist/log-SDfMMBWe.js +107 -0
- package/dist/{outbound-CEqyriPT.js → outbound-C9wV892v.js} +3 -14
- package/dist/request-context-Bm7PTBD1.js +43 -0
- package/dist/runtime-B9UoQ5NI.js +18 -0
- package/dist/runtime-api.js +1 -1
- package/dist/{runtime-BSgtMZ6G.js → sender-DIMG7jHz.js} +36 -123
- package/dist/setup-plugin-api.js +1 -1
- package/dist/{request-context-DsuA124s.js → slash-commands-impl-FRw-Dl44.js} +3 -45
- package/dist/target-parser-BdCUmxK7.js +285 -0
- package/dist/tools-2d9t-N1b.js +518 -0
- package/dist/tools-api.js +2 -0
- package/npm-shrinkwrap.json +3 -3
- package/openclaw.plugin.json +162 -0
- package/package.json +4 -4
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { c as getPlatformAdapter, i as normalizeOptionalString, s as sanitizeFileName } from "./string-normalize-R_0cKO7Q.js";
|
|
2
|
+
import { a as formatErrorMessage, n as debugLog, r as debugWarn, t as debugError } from "./log-SDfMMBWe.js";
|
|
2
3
|
import * as fs$1 from "node:fs";
|
|
3
|
-
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import * as crypto$1 from "node:crypto";
|
|
6
6
|
import crypto from "node:crypto";
|
|
@@ -10,6 +10,18 @@ import * as path$1 from "node:path";
|
|
|
10
10
|
import { mimeTypeFromFilePath } from "openclaw/plugin-sdk/media-mime";
|
|
11
11
|
import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
|
|
12
12
|
import { asDateTimestampMs, isFutureDateTimestampMs, parseStrictPositiveInteger, resolveExpiresAtMsFromDurationSeconds, resolveTimestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
|
|
13
|
+
//#region \0rolldown/runtime.js
|
|
14
|
+
var __defProp = Object.defineProperty;
|
|
15
|
+
var __exportAll = (all, no_symbols) => {
|
|
16
|
+
let target = {};
|
|
17
|
+
for (var name in all) __defProp(target, name, {
|
|
18
|
+
get: all[name],
|
|
19
|
+
enumerable: true
|
|
20
|
+
});
|
|
21
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
22
|
+
return target;
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
13
25
|
//#region extensions/qqbot/src/engine/types.ts
|
|
14
26
|
/**
|
|
15
27
|
* Core API layer public types.
|
|
@@ -46,60 +58,6 @@ const StreamInputState = {
|
|
|
46
58
|
/** Stream message content type. */
|
|
47
59
|
const StreamContentType = { MARKDOWN: "markdown" };
|
|
48
60
|
//#endregion
|
|
49
|
-
//#region extensions/qqbot/src/engine/utils/format.ts
|
|
50
|
-
/**
|
|
51
|
-
* General formatting and string utilities.
|
|
52
|
-
* 通用格式化与字符串工具。
|
|
53
|
-
*
|
|
54
|
-
* Pure utility functions with zero external dependencies.
|
|
55
|
-
* Replaces `openclaw/plugin-sdk/error-runtime` and `text-runtime`
|
|
56
|
-
* helpers for use inside engine/.
|
|
57
|
-
*
|
|
58
|
-
* NOTE: The framework `formatErrorMessage` also applies `redactSensitiveText()`
|
|
59
|
-
* for token masking. We intentionally omit that here — the framework's log
|
|
60
|
-
* pipeline handles redaction at a higher level.
|
|
61
|
-
*/
|
|
62
|
-
/**
|
|
63
|
-
* Format any error object into a readable string.
|
|
64
|
-
* 将任意错误对象格式化为可读字符串。
|
|
65
|
-
*
|
|
66
|
-
* Traverses the `.cause` chain for nested Error objects to include
|
|
67
|
-
* the full error context (e.g. network errors wrapped inside HTTP errors).
|
|
68
|
-
*/
|
|
69
|
-
function formatErrorMessage(err) {
|
|
70
|
-
if (err instanceof Error) {
|
|
71
|
-
let formatted = err.message || err.name || "Error";
|
|
72
|
-
let cause = err.cause;
|
|
73
|
-
const seen = new Set([err]);
|
|
74
|
-
while (cause && !seen.has(cause)) {
|
|
75
|
-
seen.add(cause);
|
|
76
|
-
if (cause instanceof Error) {
|
|
77
|
-
if (cause.message) formatted += ` | ${cause.message}`;
|
|
78
|
-
cause = cause.cause;
|
|
79
|
-
} else if (typeof cause === "string") {
|
|
80
|
-
formatted += ` | ${cause}`;
|
|
81
|
-
break;
|
|
82
|
-
} else break;
|
|
83
|
-
}
|
|
84
|
-
return formatted;
|
|
85
|
-
}
|
|
86
|
-
if (typeof err === "string") return err;
|
|
87
|
-
if (err === null || err === void 0 || typeof err === "number" || typeof err === "boolean" || typeof err === "bigint") return String(err);
|
|
88
|
-
try {
|
|
89
|
-
return JSON.stringify(err);
|
|
90
|
-
} catch {
|
|
91
|
-
return Object.prototype.toString.call(err);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
/** Format a millisecond duration into a human-readable string (e.g. "5m 30s"). */
|
|
95
|
-
function formatDuration(durationMs) {
|
|
96
|
-
const seconds = Math.round(durationMs / 1e3);
|
|
97
|
-
if (seconds < 60) return `${seconds}s`;
|
|
98
|
-
const minutes = Math.floor(seconds / 60);
|
|
99
|
-
const remainSeconds = seconds % 60;
|
|
100
|
-
return remainSeconds > 0 ? `${minutes}m ${remainSeconds}s` : `${minutes}m`;
|
|
101
|
-
}
|
|
102
|
-
//#endregion
|
|
103
61
|
//#region extensions/qqbot/src/engine/api/api-client.ts
|
|
104
62
|
/**
|
|
105
63
|
* Core HTTP client for the QQ Open Platform REST API.
|
|
@@ -1562,58 +1520,6 @@ var MessageApi = class {
|
|
|
1562
1520
|
}
|
|
1563
1521
|
};
|
|
1564
1522
|
//#endregion
|
|
1565
|
-
//#region extensions/qqbot/src/engine/utils/log.ts
|
|
1566
|
-
/**
|
|
1567
|
-
* QQBot debug logging utilities.
|
|
1568
|
-
* QQBot 调试日志工具。
|
|
1569
|
-
*
|
|
1570
|
-
* Only outputs when the QQBOT_DEBUG environment variable is set,
|
|
1571
|
-
* preventing user message content from leaking in production logs.
|
|
1572
|
-
*
|
|
1573
|
-
* Self-contained within engine/ — no framework SDK dependency.
|
|
1574
|
-
*/
|
|
1575
|
-
function isQqbotDebugEnabled() {
|
|
1576
|
-
const value = process.env.QQBOT_DEBUG;
|
|
1577
|
-
if (typeof value !== "string") return false;
|
|
1578
|
-
switch (value.trim().toLowerCase()) {
|
|
1579
|
-
case "1":
|
|
1580
|
-
case "on":
|
|
1581
|
-
case "true":
|
|
1582
|
-
case "yes": return true;
|
|
1583
|
-
default: return false;
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
const isDebug = () => isQqbotDebugEnabled();
|
|
1587
|
-
const MAX_LOG_VALUE_CHARS = 4096;
|
|
1588
|
-
function sanitizeDebugLogValue(value) {
|
|
1589
|
-
let text;
|
|
1590
|
-
if (typeof value === "string") text = value;
|
|
1591
|
-
else if (value instanceof Error) text = value.stack || value.message;
|
|
1592
|
-
else try {
|
|
1593
|
-
text = JSON.stringify(value) ?? String(value);
|
|
1594
|
-
} catch {
|
|
1595
|
-
text = String(value);
|
|
1596
|
-
}
|
|
1597
|
-
const sanitized = text.replace(/\p{Cc}/gu, " ").replace(/\s+/g, " ").trim();
|
|
1598
|
-
if (sanitized.length <= MAX_LOG_VALUE_CHARS) return sanitized;
|
|
1599
|
-
return `${sanitized.slice(0, MAX_LOG_VALUE_CHARS)}...`;
|
|
1600
|
-
}
|
|
1601
|
-
function formatDebugLogArgs(args) {
|
|
1602
|
-
return args.map(sanitizeDebugLogValue).join(" ");
|
|
1603
|
-
}
|
|
1604
|
-
/** Debug-level log; only outputs when QQBOT_DEBUG is enabled. */
|
|
1605
|
-
function debugLog(...args) {
|
|
1606
|
-
if (isDebug()) console.log(formatDebugLogArgs(args).replace(/\n|\r/g, ""));
|
|
1607
|
-
}
|
|
1608
|
-
/** Debug-level warning; only outputs when QQBOT_DEBUG is enabled. */
|
|
1609
|
-
function debugWarn(...args) {
|
|
1610
|
-
if (isDebug()) console.warn(formatDebugLogArgs(args).replace(/\n|\r/g, ""));
|
|
1611
|
-
}
|
|
1612
|
-
/** Debug-level error; only outputs when QQBOT_DEBUG is enabled. */
|
|
1613
|
-
function debugError(...args) {
|
|
1614
|
-
if (isDebug()) console.error(formatDebugLogArgs(args).replace(/\n|\r/g, ""));
|
|
1615
|
-
}
|
|
1616
|
-
//#endregion
|
|
1617
1523
|
//#region extensions/qqbot/src/engine/utils/upload-cache.ts
|
|
1618
1524
|
/**
|
|
1619
1525
|
* Cache `file_info` values returned by the QQ Bot API so identical uploads can be reused
|
|
@@ -1693,6 +1599,28 @@ function setCachedFileInfo(contentHash, scope, targetId, fileType, fileInfo, fil
|
|
|
1693
1599
|
* always go through exported functions that resolve the correct
|
|
1694
1600
|
* `AccountContext` by appId.
|
|
1695
1601
|
*/
|
|
1602
|
+
var sender_exports = /* @__PURE__ */ __exportAll({
|
|
1603
|
+
accountToCreds: () => accountToCreds,
|
|
1604
|
+
acknowledgeInteraction: () => acknowledgeInteraction,
|
|
1605
|
+
buildDeliveryTarget: () => buildDeliveryTarget,
|
|
1606
|
+
clearTokenCache: () => clearTokenCache,
|
|
1607
|
+
createRawInputNotifyFn: () => createRawInputNotifyFn,
|
|
1608
|
+
getAccessToken: () => getAccessToken,
|
|
1609
|
+
getGatewayUrl: () => getGatewayUrl,
|
|
1610
|
+
getMessageApi: () => getMessageApi,
|
|
1611
|
+
getPluginUserAgent: () => getPluginUserAgent,
|
|
1612
|
+
initApiConfig: () => initApiConfig,
|
|
1613
|
+
initSender: () => initSender,
|
|
1614
|
+
onMessageSent: () => onMessageSent,
|
|
1615
|
+
registerAccount: () => registerAccount,
|
|
1616
|
+
sendInputNotify: () => sendInputNotify,
|
|
1617
|
+
sendMedia: () => sendMedia,
|
|
1618
|
+
sendText: () => sendText,
|
|
1619
|
+
setOpenClawVersion: () => setOpenClawVersion,
|
|
1620
|
+
startBackgroundTokenRefresh: () => startBackgroundTokenRefresh,
|
|
1621
|
+
stopBackgroundTokenRefresh: () => stopBackgroundTokenRefresh,
|
|
1622
|
+
withTokenRetry: () => withTokenRetry
|
|
1623
|
+
});
|
|
1696
1624
|
let pluginVersion = "unknown";
|
|
1697
1625
|
let openclawVersion = "unknown";
|
|
1698
1626
|
/** Build the User-Agent string from the current plugin and framework versions. */
|
|
@@ -2129,19 +2057,4 @@ function supportsRichMedia(targetType) {
|
|
|
2129
2057
|
return targetType === "c2c" || targetType === "group";
|
|
2130
2058
|
}
|
|
2131
2059
|
//#endregion
|
|
2132
|
-
|
|
2133
|
-
const { setRuntime: _setRuntime, clearRuntime: resetQQBotRuntimeForTest, getRuntime: getQQBotRuntime } = createPluginRuntimeStore({
|
|
2134
|
-
pluginId: "qqbot",
|
|
2135
|
-
errorMessage: "QQBot runtime not initialized"
|
|
2136
|
-
});
|
|
2137
|
-
/** Set the QQBot runtime and inject the framework version into the User-Agent. */
|
|
2138
|
-
function setQQBotRuntime(runtime) {
|
|
2139
|
-
_setRuntime(runtime);
|
|
2140
|
-
setOpenClawVersion(runtime.version);
|
|
2141
|
-
}
|
|
2142
|
-
/** Type-narrowed getter for engine/ modules that need GatewayPluginRuntime. */
|
|
2143
|
-
function getQQBotRuntimeForEngine() {
|
|
2144
|
-
return getQQBotRuntime();
|
|
2145
|
-
}
|
|
2146
|
-
//#endregion
|
|
2147
|
-
export { checkFileSize as A, StreamContentType as B, debugError as C, getNextMsgSeq as D, UploadDailyLimitExceededError as E, getImageMimeType as F, StreamInputState as H, getMaxUploadSize as I, readFileAsync as L, fileExistsAsync as M, formatFileSize as N, UPLOAD_PREPARE_FALLBACK_CODE as O, getFileTypeName as P, formatDuration as R, withTokenRetry as S, debugWarn as T, StreamInputMode as V, sendInputNotify as _, acknowledgeInteraction as a, startBackgroundTokenRefresh as b, createRawInputNotifyFn as c, getMessageApi as d, getPluginUserAgent as f, registerAccount as g, onMessageSent as h, accountToCreds as i, downloadFile as j, openLocalFile as k, getAccessToken as l, initSender as m, getQQBotRuntimeForEngine as n, buildDeliveryTarget as o, initApiConfig as p, setQQBotRuntime as r, clearTokenCache as s, getQQBotRuntime as t, getGatewayUrl as u, sendMedia as v, debugLog as w, stopBackgroundTokenRefresh as x, sendText as y, formatErrorMessage as z };
|
|
2060
|
+
export { getFileTypeName as A, getNextMsgSeq as C, downloadFile as D, checkFileSize as E, StreamInputMode as F, StreamInputState as I, __exportAll as L, getMaxUploadSize as M, readFileAsync as N, fileExistsAsync as O, StreamContentType as P, UploadDailyLimitExceededError as S, openLocalFile as T, sender_exports as _, createRawInputNotifyFn as a, stopBackgroundTokenRefresh as b, getMessageApi as c, initSender as d, onMessageSent as f, sendText as g, sendMedia as h, clearTokenCache as i, getImageMimeType as j, formatFileSize as k, getPluginUserAgent as l, sendInputNotify as m, acknowledgeInteraction as n, getAccessToken as o, registerAccount as p, buildDeliveryTarget as r, getGatewayUrl as s, accountToCreds as t, initApiConfig as u, setOpenClawVersion as v, UPLOAD_PREPARE_FALLBACK_CODE as w, withTokenRetry as x, startBackgroundTokenRefresh as y };
|
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-CQ_DFfPx.js";
|
|
2
2
|
export { qqbotSetupPlugin };
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { n as debugLog } from "./log-SDfMMBWe.js";
|
|
2
|
+
import { a as getHomeDir, f as isWindows, l as getQQBotMediaPath, o as getQQBotDataDir } from "./target-parser-BdCUmxK7.js";
|
|
3
3
|
import { uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { loadJsonFile } from "openclaw/plugin-sdk/json-store";
|
|
7
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
8
7
|
//#region extensions/qqbot/src/engine/commands/builtin/state.ts
|
|
9
8
|
let resolveVersionGetter = () => "unknown";
|
|
10
9
|
let approveRuntimeGetter = null;
|
|
@@ -899,45 +898,4 @@ function getFrameworkVersion() {
|
|
|
899
898
|
return getFrameworkVersionString();
|
|
900
899
|
}
|
|
901
900
|
//#endregion
|
|
902
|
-
|
|
903
|
-
/**
|
|
904
|
-
* Request-level context using AsyncLocalStorage.
|
|
905
|
-
*
|
|
906
|
-
* Provides ambient context (accountId, target openid, chat type, etc.)
|
|
907
|
-
* throughout the request lifecycle without explicit parameter threading.
|
|
908
|
-
*
|
|
909
|
-
* Gateway establishes the scope around each inbound message via
|
|
910
|
-
* `runWithRequestContext()`; any async code within that scope (including
|
|
911
|
-
* AI agent calls and tool `execute` callbacks) can retrieve the current
|
|
912
|
-
* request via `getRequestContext()` without racing with concurrent
|
|
913
|
-
* inbound messages.
|
|
914
|
-
*
|
|
915
|
-
* This is a pure Node.js module with zero framework dependencies,
|
|
916
|
-
* making it trivially portable between the built-in and standalone
|
|
917
|
-
* versions of QQBot.
|
|
918
|
-
*/
|
|
919
|
-
const store = new AsyncLocalStorage();
|
|
920
|
-
/**
|
|
921
|
-
* Execute an async function with request-scoped context.
|
|
922
|
-
*
|
|
923
|
-
* All code running within `fn` (including nested async calls) can
|
|
924
|
-
* retrieve the context via `getRequestContext()`.
|
|
925
|
-
*
|
|
926
|
-
* @param ctx - The context to attach to this request.
|
|
927
|
-
* @param fn - The async function to run within the context.
|
|
928
|
-
* @returns The return value of `fn`.
|
|
929
|
-
*/
|
|
930
|
-
function runWithRequestContext(ctx, fn) {
|
|
931
|
-
return store.run(ctx, fn);
|
|
932
|
-
}
|
|
933
|
-
/**
|
|
934
|
-
* Retrieve the current request context.
|
|
935
|
-
*
|
|
936
|
-
* Returns `undefined` when called outside of a `runWithRequestContext`
|
|
937
|
-
* scope.
|
|
938
|
-
*/
|
|
939
|
-
function getRequestContext() {
|
|
940
|
-
return store.getStore();
|
|
941
|
-
}
|
|
942
|
-
//#endregion
|
|
943
|
-
export { getPluginVersion as a, getFrameworkVersion as i, runWithRequestContext as n, initCommands as o, getFrameworkCommands as r, matchSlashCommand as s, getRequestContext as t };
|
|
901
|
+
export { matchSlashCommand as a, initCommands as i, getFrameworkVersion as n, getPluginVersion as r, getFrameworkCommands as t };
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { c as getPlatformAdapter } from "./string-normalize-R_0cKO7Q.js";
|
|
2
|
+
import { a as formatErrorMessage, n as debugLog, r as debugWarn } from "./log-SDfMMBWe.js";
|
|
3
|
+
import * as fs$1 from "node:fs";
|
|
4
|
+
import * as os$1 from "node:os";
|
|
5
|
+
import * as path$1 from "node:path";
|
|
6
|
+
//#region extensions/qqbot/src/engine/utils/platform.ts
|
|
7
|
+
/**
|
|
8
|
+
* Cross-platform path and detection helpers for core/ modules.
|
|
9
|
+
*
|
|
10
|
+
* Provides home/data/media directory helpers, platform detection,
|
|
11
|
+
* silk-wasm availability checks — all without importing `openclaw/plugin-sdk`.
|
|
12
|
+
* The temp-directory fallback is delegated to the PlatformAdapter.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Resolve the current user's OS home directory safely across platforms.
|
|
16
|
+
*
|
|
17
|
+
* Priority:
|
|
18
|
+
* 1. `os.homedir()`
|
|
19
|
+
* 2. `$HOME` or `%USERPROFILE%`
|
|
20
|
+
* 3. PlatformAdapter.getTempDir() as a last resort
|
|
21
|
+
*
|
|
22
|
+
* This is the *operating-system* home and intentionally ignores
|
|
23
|
+
* `OPENCLAW_HOME`. QQ Bot still checks this tree for legacy state imports and
|
|
24
|
+
* media-path remaps from older releases.
|
|
25
|
+
*/
|
|
26
|
+
function getHomeDir() {
|
|
27
|
+
try {
|
|
28
|
+
const home = os$1.homedir();
|
|
29
|
+
if (home && fs$1.existsSync(home)) return home;
|
|
30
|
+
} catch {}
|
|
31
|
+
const envHome = process.env.HOME || process.env.USERPROFILE;
|
|
32
|
+
if (envHome && fs$1.existsSync(envHome)) return envHome;
|
|
33
|
+
return getPlatformAdapter().getTempDir();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the effective OpenClaw home directory.
|
|
37
|
+
*
|
|
38
|
+
* Mirrors the contract from core (`src/infra/home-dir.ts::resolveEffectiveHomeDir`)
|
|
39
|
+
* so QQ Bot media roots live under the same tree the rest of OpenClaw treats as
|
|
40
|
+
* `~`. The extension cannot import the core helper directly (it is a separate
|
|
41
|
+
* package with `openclaw` as a peer dependency), so this re-implements the
|
|
42
|
+
* minimal contract:
|
|
43
|
+
*
|
|
44
|
+
* 1. `OPENCLAW_HOME` when set (with `~` / `~/...` expanded against the OS home).
|
|
45
|
+
* 2. Otherwise fall back to {@link getHomeDir} so existing single-home
|
|
46
|
+
* deployments are unaffected.
|
|
47
|
+
*
|
|
48
|
+
* Empty / `"undefined"` / `"null"` strings are treated as unset to match how
|
|
49
|
+
* core normalizes the variable.
|
|
50
|
+
*/
|
|
51
|
+
function resolveOpenClawHome() {
|
|
52
|
+
const raw = process.env.OPENCLAW_HOME?.trim();
|
|
53
|
+
if (!raw || raw === "undefined" || raw === "null") return getHomeDir();
|
|
54
|
+
if (raw === "~" || raw.startsWith("~/") || raw.startsWith("~\\")) {
|
|
55
|
+
const osHome = getHomeDir();
|
|
56
|
+
if (raw === "~") return osHome;
|
|
57
|
+
return path$1.join(osHome, raw.slice(2));
|
|
58
|
+
}
|
|
59
|
+
return raw;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Return a legacy path under `~/.openclaw/qqbot` without creating it.
|
|
63
|
+
*
|
|
64
|
+
* Current QQ Bot runtime state lives in plugin SQLite KV. This path remains for
|
|
65
|
+
* legacy imports and media-path remaps from older releases.
|
|
66
|
+
*/
|
|
67
|
+
function getQQBotDataPath(...subPaths) {
|
|
68
|
+
return path$1.join(getHomeDir(), ".openclaw", "qqbot", ...subPaths);
|
|
69
|
+
}
|
|
70
|
+
/** Return a path under `~/.openclaw/qqbot`, creating it on demand. */
|
|
71
|
+
function getQQBotDataDir(...subPaths) {
|
|
72
|
+
const dir = getQQBotDataPath(...subPaths);
|
|
73
|
+
if (!fs$1.existsSync(dir)) fs$1.mkdirSync(dir, { recursive: true });
|
|
74
|
+
return dir;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Return a path under `<openclaw-home>/.openclaw/media/qqbot` without creating it.
|
|
78
|
+
*
|
|
79
|
+
* Unlike `getQQBotDataPath`, this lives under OpenClaw's core media allowlist
|
|
80
|
+
* so downloaded images and audio can be accessed by framework media tooling.
|
|
81
|
+
* The base honors `OPENCLAW_HOME` (when set) so files written by agents into
|
|
82
|
+
* the OpenClaw-managed media tree are reachable by this plugin even when
|
|
83
|
+
* `HOME` and `OPENCLAW_HOME` differ (Docker, multi-user hosts). Fixes #83562.
|
|
84
|
+
*/
|
|
85
|
+
function getQQBotMediaPath(...subPaths) {
|
|
86
|
+
return path$1.join(resolveOpenClawHome(), ".openclaw", "media", "qqbot", ...subPaths);
|
|
87
|
+
}
|
|
88
|
+
/** Return a path under `<openclaw-home>/.openclaw/media/qqbot`, creating it on demand. */
|
|
89
|
+
function getQQBotMediaDir(...subPaths) {
|
|
90
|
+
const dir = getQQBotMediaPath(...subPaths);
|
|
91
|
+
if (!fs$1.existsSync(dir)) fs$1.mkdirSync(dir, { recursive: true });
|
|
92
|
+
return dir;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Return `<openclaw-home>/.openclaw/media`, OpenClaw's shared media root.
|
|
96
|
+
*
|
|
97
|
+
* This mirrors the directory that core's `buildMediaLocalRoots` exposes as an
|
|
98
|
+
* allowlisted location (see `openclaw/src/media/local-roots.ts`). Using it as a
|
|
99
|
+
* QQ Bot payload root lets the plugin trust framework-produced files that live
|
|
100
|
+
* in sibling subdirectories such as `outbound/` (written by
|
|
101
|
+
* `saveMediaBuffer(..., "outbound", ...)`) or `inbound/`, while still keeping
|
|
102
|
+
* the check anchored to a single, well-known directory. Like
|
|
103
|
+
* {@link getQQBotMediaPath}, the base honors `OPENCLAW_HOME`.
|
|
104
|
+
*/
|
|
105
|
+
function getOpenClawMediaDir() {
|
|
106
|
+
return path$1.join(resolveOpenClawHome(), ".openclaw", "media");
|
|
107
|
+
}
|
|
108
|
+
function isWindows() {
|
|
109
|
+
return process.platform === "win32";
|
|
110
|
+
}
|
|
111
|
+
/** Return the preferred temporary directory. */
|
|
112
|
+
function getTempDir() {
|
|
113
|
+
return getPlatformAdapter().getTempDir();
|
|
114
|
+
}
|
|
115
|
+
let silkWasmAvailable = null;
|
|
116
|
+
/** Check whether silk-wasm can run in the current environment. */
|
|
117
|
+
async function checkSilkWasmAvailable() {
|
|
118
|
+
if (silkWasmAvailable !== null) return silkWasmAvailable;
|
|
119
|
+
try {
|
|
120
|
+
const { isSilk } = await import("silk-wasm");
|
|
121
|
+
isSilk(new Uint8Array(0));
|
|
122
|
+
silkWasmAvailable = true;
|
|
123
|
+
debugLog("[platform] silk-wasm: available");
|
|
124
|
+
} catch (err) {
|
|
125
|
+
silkWasmAvailable = false;
|
|
126
|
+
debugWarn(`[platform] silk-wasm: NOT available (${formatErrorMessage(err)})`);
|
|
127
|
+
}
|
|
128
|
+
return silkWasmAvailable;
|
|
129
|
+
}
|
|
130
|
+
/** Expand `~` to the current user's home directory. */
|
|
131
|
+
function expandTilde(p) {
|
|
132
|
+
if (!p) return p;
|
|
133
|
+
if (p === "~") return getHomeDir();
|
|
134
|
+
if (p.startsWith("~/") || p.startsWith("~\\")) return path$1.join(getHomeDir(), p.slice(2));
|
|
135
|
+
return p;
|
|
136
|
+
}
|
|
137
|
+
/** Normalize a user-provided path by trimming, stripping `file://`, and expanding `~`. */
|
|
138
|
+
function normalizePath(p) {
|
|
139
|
+
let result = p.trim();
|
|
140
|
+
if (result.startsWith("file://")) {
|
|
141
|
+
result = result.slice(7);
|
|
142
|
+
try {
|
|
143
|
+
result = decodeURIComponent(result);
|
|
144
|
+
} catch {}
|
|
145
|
+
}
|
|
146
|
+
return expandTilde(result);
|
|
147
|
+
}
|
|
148
|
+
/** Return true when the string looks like a local filesystem path rather than a URL. */
|
|
149
|
+
function isLocalPath(p) {
|
|
150
|
+
if (!p) return false;
|
|
151
|
+
if (p.startsWith("file://")) return true;
|
|
152
|
+
if (p === "~" || p.startsWith("~/") || p.startsWith("~\\")) return true;
|
|
153
|
+
if (p.startsWith("/")) return true;
|
|
154
|
+
if (/^[a-zA-Z]:[\\/]/.test(p)) return true;
|
|
155
|
+
if (p.startsWith("\\\\")) return true;
|
|
156
|
+
if (p.startsWith("./") || p.startsWith("../")) return true;
|
|
157
|
+
if (p.startsWith(".\\") || p.startsWith("..\\")) return true;
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
function isPathWithinRoot(candidate, root) {
|
|
161
|
+
const relative = path$1.relative(root, candidate);
|
|
162
|
+
return relative === "" || !relative.startsWith("..") && !path$1.isAbsolute(relative);
|
|
163
|
+
}
|
|
164
|
+
/** Remap legacy or hallucinated QQ Bot local media paths to real files when possible. */
|
|
165
|
+
function resolveQQBotLocalMediaPath(p) {
|
|
166
|
+
const normalized = normalizePath(p);
|
|
167
|
+
if (!isLocalPath(normalized) || fs$1.existsSync(normalized)) return normalized;
|
|
168
|
+
const osHomeDir = getHomeDir();
|
|
169
|
+
const openclawHomeDir = resolveOpenClawHome();
|
|
170
|
+
const mediaRoot = getQQBotMediaPath();
|
|
171
|
+
const dataRoot = getQQBotDataPath();
|
|
172
|
+
const candidateRoots = [
|
|
173
|
+
...Array.from(new Set([path$1.join(osHomeDir, ".openclaw", "workspace", "qqbot"), path$1.join(openclawHomeDir, ".openclaw", "workspace", "qqbot")])).map((from) => ({
|
|
174
|
+
from,
|
|
175
|
+
to: mediaRoot
|
|
176
|
+
})),
|
|
177
|
+
{
|
|
178
|
+
from: dataRoot,
|
|
179
|
+
to: mediaRoot
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
from: mediaRoot,
|
|
183
|
+
to: dataRoot
|
|
184
|
+
}
|
|
185
|
+
];
|
|
186
|
+
for (const { from, to } of candidateRoots) {
|
|
187
|
+
if (!isPathWithinRoot(normalized, from)) continue;
|
|
188
|
+
const relative = path$1.relative(from, normalized);
|
|
189
|
+
const candidate = path$1.join(to, relative);
|
|
190
|
+
if (fs$1.existsSync(candidate)) {
|
|
191
|
+
debugWarn(`[platform] Remapped missing QQBot media path ${normalized} -> ${candidate}`);
|
|
192
|
+
return candidate;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return normalized;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Resolve a structured-payload local file path and enforce that it stays within
|
|
199
|
+
* QQ Bot-owned storage roots.
|
|
200
|
+
*/
|
|
201
|
+
function resolveQQBotPayloadLocalFilePath(p) {
|
|
202
|
+
const candidate = resolveQQBotLocalMediaPath(p);
|
|
203
|
+
if (!candidate.trim()) return null;
|
|
204
|
+
const resolvedCandidate = path$1.resolve(candidate);
|
|
205
|
+
if (!fs$1.existsSync(resolvedCandidate)) return null;
|
|
206
|
+
const canonicalCandidate = fs$1.realpathSync(resolvedCandidate);
|
|
207
|
+
const allowedRoots = [getOpenClawMediaDir(), getQQBotMediaPath()];
|
|
208
|
+
for (const root of allowedRoots) {
|
|
209
|
+
const resolvedRoot = path$1.resolve(root);
|
|
210
|
+
if (isPathWithinRoot(canonicalCandidate, fs$1.existsSync(resolvedRoot) ? fs$1.realpathSync(resolvedRoot) : resolvedRoot)) return canonicalCandidate;
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
//#endregion
|
|
215
|
+
//#region extensions/qqbot/src/engine/messaging/target-parser.ts
|
|
216
|
+
/**
|
|
217
|
+
* Parse a qqbot target string into a structured delivery target.
|
|
218
|
+
*
|
|
219
|
+
* Supported formats:
|
|
220
|
+
* - `qqbot:c2c:openid` → C2C direct message
|
|
221
|
+
* - `qqbot:group:groupid` → Group message
|
|
222
|
+
* - `qqbot:channel:channelid` → Channel message
|
|
223
|
+
* - `c2c:openid` → C2C (without qqbot: prefix)
|
|
224
|
+
* - `group:groupid` → Group (without qqbot: prefix)
|
|
225
|
+
* - `channel:channelid` → Channel (without qqbot: prefix)
|
|
226
|
+
* - `openid` → C2C (bare openid, default)
|
|
227
|
+
*
|
|
228
|
+
* @param to - Raw target string.
|
|
229
|
+
* @returns Parsed target with type and id.
|
|
230
|
+
* @throws {Error} When the target format is invalid.
|
|
231
|
+
*/
|
|
232
|
+
function parseTarget(to) {
|
|
233
|
+
const id = to.replace(/^qqbot:/i, "");
|
|
234
|
+
if (id.startsWith("c2c:")) {
|
|
235
|
+
const userId = id.slice(4);
|
|
236
|
+
if (!userId) throw new Error(`Invalid c2c target format: ${to} - missing user ID`);
|
|
237
|
+
return {
|
|
238
|
+
type: "c2c",
|
|
239
|
+
id: userId
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (id.startsWith("group:")) {
|
|
243
|
+
const groupId = id.slice(6);
|
|
244
|
+
if (!groupId) throw new Error(`Invalid group target format: ${to} - missing group ID`);
|
|
245
|
+
return {
|
|
246
|
+
type: "group",
|
|
247
|
+
id: groupId
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
if (id.startsWith("channel:")) {
|
|
251
|
+
const channelId = id.slice(8);
|
|
252
|
+
if (!channelId) throw new Error(`Invalid channel target format: ${to} - missing channel ID`);
|
|
253
|
+
return {
|
|
254
|
+
type: "channel",
|
|
255
|
+
id: channelId
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
if (!id) throw new Error(`Invalid target format: ${to} - empty ID after removing qqbot: prefix`);
|
|
259
|
+
return {
|
|
260
|
+
type: "c2c",
|
|
261
|
+
id
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Normalize a QQ Bot target string into the canonical `qqbot:...` form.
|
|
266
|
+
*
|
|
267
|
+
* Returns `undefined` when the target does not look like a QQ Bot address.
|
|
268
|
+
*/
|
|
269
|
+
function normalizeTarget(target) {
|
|
270
|
+
const id = target.replace(/^qqbot:/i, "");
|
|
271
|
+
if (id.startsWith("c2c:") || id.startsWith("group:") || id.startsWith("channel:")) return `qqbot:${id}`;
|
|
272
|
+
if (/^[0-9a-fA-F]{32}$/.test(id)) return `qqbot:c2c:${id}`;
|
|
273
|
+
if (/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(id)) return `qqbot:c2c:${id}`;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Return true when the string looks like a QQ Bot target ID.
|
|
277
|
+
*/
|
|
278
|
+
function looksLikeQQBotTarget(id) {
|
|
279
|
+
if (/^qqbot:(c2c|group|channel):/i.test(id)) return true;
|
|
280
|
+
if (/^(c2c|group|channel):/i.test(id)) return true;
|
|
281
|
+
if (/^[0-9a-fA-F]{32}$/.test(id)) return true;
|
|
282
|
+
return /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(id);
|
|
283
|
+
}
|
|
284
|
+
//#endregion
|
|
285
|
+
export { getHomeDir as a, getQQBotMediaDir as c, isLocalPath as d, isWindows as f, checkSilkWasmAvailable as i, getQQBotMediaPath as l, resolveQQBotPayloadLocalFilePath as m, normalizeTarget as n, getQQBotDataDir as o, normalizePath as p, parseTarget as r, getQQBotDataPath as s, looksLikeQQBotTarget as t, getTempDir as u };
|