@wecom/wecom-openclaw-plugin 2026.3.24-beta.0 → 2026.3.24-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/index.cjs.js +61 -177
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +61 -178
- package/dist/index.esm.js.map +1 -1
- package/dist/src/openclaw-compat.d.ts +10 -0
- package/dist/src/reqid-store.d.ts +3 -11
- package/dist/src/state-manager.d.ts +4 -0
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -11,10 +11,7 @@ var path = require('node:path');
|
|
|
11
11
|
var os = require('node:os');
|
|
12
12
|
var node_url = require('node:url');
|
|
13
13
|
var fileType = require('file-type');
|
|
14
|
-
var jsonStore = require('openclaw/plugin-sdk/json-store');
|
|
15
|
-
var infraRuntime = require('openclaw/plugin-sdk/infra-runtime');
|
|
16
14
|
var node_fs = require('node:fs');
|
|
17
|
-
var setup = require('openclaw/plugin-sdk/setup');
|
|
18
15
|
|
|
19
16
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
20
17
|
function _interopNamespaceDefault(e) {
|
|
@@ -72,12 +69,30 @@ const _sdkReady = import('openclaw/plugin-sdk/core')
|
|
|
72
69
|
if (typeof sdk.getDefaultMediaLocalRoots === "function") {
|
|
73
70
|
exports$1.getDefaultMediaLocalRoots = sdk.getDefaultMediaLocalRoots;
|
|
74
71
|
}
|
|
72
|
+
if (typeof sdk.addWildcardAllowFrom === "function") {
|
|
73
|
+
exports$1.addWildcardAllowFrom = sdk.addWildcardAllowFrom;
|
|
74
|
+
}
|
|
75
75
|
return exports$1;
|
|
76
76
|
})
|
|
77
77
|
.catch(() => {
|
|
78
78
|
// openclaw/plugin-sdk 不可用或版本过低,全部使用 fallback
|
|
79
79
|
return {};
|
|
80
80
|
});
|
|
81
|
+
// 同时尝试从 plugin-sdk/setup 模块探测(缓存同步可用的引用)
|
|
82
|
+
let _cachedAddWildcardAllowFrom;
|
|
83
|
+
import('openclaw/plugin-sdk/setup')
|
|
84
|
+
.then((sdk) => {
|
|
85
|
+
if (typeof sdk.addWildcardAllowFrom === "function") {
|
|
86
|
+
_cachedAddWildcardAllowFrom = sdk.addWildcardAllowFrom;
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
.catch(() => { });
|
|
90
|
+
// 同时也从 core 模块的结果中缓存
|
|
91
|
+
_sdkReady.then((sdk) => {
|
|
92
|
+
if (!_cachedAddWildcardAllowFrom && sdk.addWildcardAllowFrom) {
|
|
93
|
+
_cachedAddWildcardAllowFrom = sdk.addWildcardAllowFrom;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
81
96
|
// ============================================================================
|
|
82
97
|
// detectMime —— 检测 MIME 类型
|
|
83
98
|
// ============================================================================
|
|
@@ -321,6 +336,36 @@ async function loadOutboundMediaFromUrl(mediaUrl, options = {}) {
|
|
|
321
336
|
return loadOutboundMediaFromUrlFallback(mediaUrl, options);
|
|
322
337
|
}
|
|
323
338
|
// ============================================================================
|
|
339
|
+
// addWildcardAllowFrom —— 向 allowFrom 列表添加通配符 "*"
|
|
340
|
+
// ============================================================================
|
|
341
|
+
/** fallback 版 addWildcardAllowFrom:确保列表中包含 "*" 通配符 */
|
|
342
|
+
function addWildcardAllowFromFallback(allowFrom) {
|
|
343
|
+
if (allowFrom.includes("*")) {
|
|
344
|
+
return allowFrom;
|
|
345
|
+
}
|
|
346
|
+
return [...allowFrom, "*"];
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* 向 allowFrom 列表添加通配符 "*"(兼容入口)
|
|
350
|
+
*
|
|
351
|
+
* 当 dmPolicy 为 "open" 时,需要确保 allowFrom 中包含 "*" 以允许所有来源。
|
|
352
|
+
* 优先使用 SDK 版本(plugin-sdk/setup 或 plugin-sdk/core),不可用时使用 fallback。
|
|
353
|
+
*
|
|
354
|
+
* 注意:此函数为同步函数,与 SDK 原始签名一致。
|
|
355
|
+
* SDK 引用在模块加载时异步探测并缓存,调用时同步读取缓存。
|
|
356
|
+
*/
|
|
357
|
+
function addWildcardAllowFrom(allowFrom) {
|
|
358
|
+
if (_cachedAddWildcardAllowFrom) {
|
|
359
|
+
try {
|
|
360
|
+
return _cachedAddWildcardAllowFrom(allowFrom);
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
// SDK 版本异常,降级到 fallback
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return addWildcardAllowFromFallback(allowFrom);
|
|
367
|
+
}
|
|
368
|
+
// ============================================================================
|
|
324
369
|
// getDefaultMediaLocalRoots —— 获取默认媒体本地路径白名单
|
|
325
370
|
// ============================================================================
|
|
326
371
|
/** 解析 openclaw 状态目录 */
|
|
@@ -1202,80 +1247,27 @@ async function checkDmPolicy(params) {
|
|
|
1202
1247
|
return { allowed: false };
|
|
1203
1248
|
}
|
|
1204
1249
|
|
|
1250
|
+
// ============================================================================
|
|
1251
|
+
// 类型定义
|
|
1252
|
+
// ============================================================================
|
|
1205
1253
|
// ============================================================================
|
|
1206
1254
|
// 常量
|
|
1207
1255
|
// ============================================================================
|
|
1208
1256
|
const DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 天
|
|
1209
1257
|
const DEFAULT_MEMORY_MAX_SIZE = 200;
|
|
1210
|
-
const DEFAULT_FILE_MAX_ENTRIES = 500;
|
|
1211
|
-
const DEFAULT_FLUSH_DEBOUNCE_MS = 1000;
|
|
1212
|
-
const DEFAULT_LOCK_OPTIONS = {
|
|
1213
|
-
stale: 60000,
|
|
1214
|
-
retries: {
|
|
1215
|
-
retries: 6,
|
|
1216
|
-
factor: 1.35,
|
|
1217
|
-
minTimeout: 8,
|
|
1218
|
-
maxTimeout: 180,
|
|
1219
|
-
randomize: true,
|
|
1220
|
-
},
|
|
1221
|
-
};
|
|
1222
|
-
// ============================================================================
|
|
1223
|
-
// 状态目录解析
|
|
1224
|
-
// ============================================================================
|
|
1225
|
-
function resolveStateDirFromEnv(env = process.env) {
|
|
1226
|
-
const stateOverride = env.OPENCLAW_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
|
|
1227
|
-
if (stateOverride) {
|
|
1228
|
-
return stateOverride;
|
|
1229
|
-
}
|
|
1230
|
-
if (env.VITEST || env.NODE_ENV === "test") {
|
|
1231
|
-
return path.join(os.tmpdir(), ["openclaw-vitest", String(process.pid)].join("-"));
|
|
1232
|
-
}
|
|
1233
|
-
return path.join(os.homedir(), ".openclaw");
|
|
1234
|
-
}
|
|
1235
|
-
function resolveReqIdFilePath(accountId) {
|
|
1236
|
-
const safe = accountId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1237
|
-
return path.join(resolveStateDirFromEnv(), "wecom", `reqid-map-${safe}.json`);
|
|
1238
|
-
}
|
|
1239
1258
|
// ============================================================================
|
|
1240
1259
|
// 核心实现
|
|
1241
1260
|
// ============================================================================
|
|
1242
1261
|
function createPersistentReqIdStore(accountId, options) {
|
|
1243
1262
|
const ttlMs = DEFAULT_TTL_MS;
|
|
1244
1263
|
const memoryMaxSize = DEFAULT_MEMORY_MAX_SIZE;
|
|
1245
|
-
const fileMaxEntries = DEFAULT_FILE_MAX_ENTRIES;
|
|
1246
|
-
const flushDebounceMs = DEFAULT_FLUSH_DEBOUNCE_MS;
|
|
1247
|
-
const filePath = resolveReqIdFilePath(accountId);
|
|
1248
1264
|
// 内存层:chatId → ReqIdEntry
|
|
1249
1265
|
const memory = new Map();
|
|
1250
|
-
// 防抖写入相关
|
|
1251
|
-
let dirty = false;
|
|
1252
|
-
let flushTimer = null;
|
|
1253
1266
|
// ========== 内部辅助函数 ==========
|
|
1254
1267
|
/** 检查条目是否过期 */
|
|
1255
1268
|
function isExpired(entry, now) {
|
|
1256
1269
|
return now - entry.ts >= ttlMs;
|
|
1257
1270
|
}
|
|
1258
|
-
/** 验证磁盘条目的合法性 */
|
|
1259
|
-
function isValidEntry(entry) {
|
|
1260
|
-
return (typeof entry === "object" &&
|
|
1261
|
-
entry !== null &&
|
|
1262
|
-
typeof entry.reqId === "string" &&
|
|
1263
|
-
typeof entry.ts === "number" &&
|
|
1264
|
-
Number.isFinite(entry.ts));
|
|
1265
|
-
}
|
|
1266
|
-
/** 清理磁盘数据中的无效值,返回干净的 Record */
|
|
1267
|
-
function sanitizeData(value) {
|
|
1268
|
-
if (!value || typeof value !== "object") {
|
|
1269
|
-
return {};
|
|
1270
|
-
}
|
|
1271
|
-
const out = {};
|
|
1272
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
1273
|
-
if (isValidEntry(entry)) {
|
|
1274
|
-
out[key] = entry;
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
return out;
|
|
1278
|
-
}
|
|
1279
1271
|
/**
|
|
1280
1272
|
* 内存容量控制:淘汰最旧的条目。
|
|
1281
1273
|
* 利用 Map 的插入顺序 + touch(先 delete 再 set) 实现类 LRU 效果。
|
|
@@ -1289,61 +1281,6 @@ function createPersistentReqIdStore(accountId, options) {
|
|
|
1289
1281
|
memory.delete(key);
|
|
1290
1282
|
}
|
|
1291
1283
|
}
|
|
1292
|
-
/** 磁盘数据容量控制:先清过期,再按时间淘汰超量 */
|
|
1293
|
-
function pruneFileData(data, now) {
|
|
1294
|
-
{
|
|
1295
|
-
for (const [key, entry] of Object.entries(data)) {
|
|
1296
|
-
if (now - entry.ts >= ttlMs) {
|
|
1297
|
-
delete data[key];
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
const keys = Object.keys(data);
|
|
1302
|
-
if (keys.length <= fileMaxEntries)
|
|
1303
|
-
return;
|
|
1304
|
-
keys
|
|
1305
|
-
.sort((a, b) => data[a].ts - data[b].ts)
|
|
1306
|
-
.slice(0, keys.length - fileMaxEntries)
|
|
1307
|
-
.forEach((key) => delete data[key]);
|
|
1308
|
-
}
|
|
1309
|
-
/** 防抖写入磁盘 */
|
|
1310
|
-
function scheduleDiskFlush() {
|
|
1311
|
-
dirty = true;
|
|
1312
|
-
if (flushTimer)
|
|
1313
|
-
return;
|
|
1314
|
-
flushTimer = setTimeout(async () => {
|
|
1315
|
-
flushTimer = null;
|
|
1316
|
-
if (!dirty)
|
|
1317
|
-
return;
|
|
1318
|
-
await flushToDisk();
|
|
1319
|
-
}, flushDebounceMs);
|
|
1320
|
-
}
|
|
1321
|
-
/** 立即写入磁盘(带文件锁,参考 createPersistentDedupe 的 checkAndRecordInner) */
|
|
1322
|
-
async function flushToDisk() {
|
|
1323
|
-
dirty = false;
|
|
1324
|
-
const now = Date.now();
|
|
1325
|
-
try {
|
|
1326
|
-
await infraRuntime.withFileLock(filePath, DEFAULT_LOCK_OPTIONS, async () => {
|
|
1327
|
-
// 读取现有磁盘数据并合并
|
|
1328
|
-
const { value } = await jsonStore.readJsonFileWithFallback(filePath, {});
|
|
1329
|
-
const data = sanitizeData(value);
|
|
1330
|
-
// 将内存中未过期的数据合并到磁盘数据(内存优先)
|
|
1331
|
-
for (const [chatId, entry] of memory) {
|
|
1332
|
-
if (!isExpired(entry, now)) {
|
|
1333
|
-
data[chatId] = entry;
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
// 清理过期和超量
|
|
1337
|
-
pruneFileData(data, now);
|
|
1338
|
-
// 原子写入
|
|
1339
|
-
await jsonStore.writeJsonFileAtomically(filePath, data);
|
|
1340
|
-
});
|
|
1341
|
-
}
|
|
1342
|
-
catch (error) {
|
|
1343
|
-
// 磁盘写入失败不影响内存使用,降级到纯内存模式
|
|
1344
|
-
// console.error(`[WeCom] reqid-store: flush to disk failed: ${String(error)}`);
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
1284
|
// ========== 公开 API ==========
|
|
1348
1285
|
function set(chatId, reqId) {
|
|
1349
1286
|
const entry = { reqId, ts: Date.now() };
|
|
@@ -1351,11 +1288,10 @@ function createPersistentReqIdStore(accountId, options) {
|
|
|
1351
1288
|
memory.delete(chatId);
|
|
1352
1289
|
memory.set(chatId, entry);
|
|
1353
1290
|
pruneMemory();
|
|
1354
|
-
scheduleDiskFlush();
|
|
1355
1291
|
}
|
|
1356
1292
|
async function get(chatId) {
|
|
1357
1293
|
const now = Date.now();
|
|
1358
|
-
//
|
|
1294
|
+
// 仅查内存
|
|
1359
1295
|
const memEntry = memory.get(chatId);
|
|
1360
1296
|
if (memEntry && !isExpired(memEntry, now)) {
|
|
1361
1297
|
return memEntry.reqId;
|
|
@@ -1363,20 +1299,6 @@ function createPersistentReqIdStore(accountId, options) {
|
|
|
1363
1299
|
if (memEntry) {
|
|
1364
1300
|
memory.delete(chatId); // 过期则删除
|
|
1365
1301
|
}
|
|
1366
|
-
// 2. 内存 miss,回查磁盘并回填内存
|
|
1367
|
-
try {
|
|
1368
|
-
const { value } = await jsonStore.readJsonFileWithFallback(filePath, {});
|
|
1369
|
-
const data = sanitizeData(value);
|
|
1370
|
-
const diskEntry = data[chatId];
|
|
1371
|
-
if (diskEntry && !isExpired(diskEntry, now)) {
|
|
1372
|
-
// 回填内存
|
|
1373
|
-
memory.set(chatId, diskEntry);
|
|
1374
|
-
return diskEntry.reqId;
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
catch {
|
|
1378
|
-
// 磁盘读取失败,降级返回 undefined
|
|
1379
|
-
}
|
|
1380
1302
|
return undefined;
|
|
1381
1303
|
}
|
|
1382
1304
|
function getSync(chatId) {
|
|
@@ -1392,34 +1314,6 @@ function createPersistentReqIdStore(accountId, options) {
|
|
|
1392
1314
|
}
|
|
1393
1315
|
function del(chatId) {
|
|
1394
1316
|
memory.delete(chatId);
|
|
1395
|
-
scheduleDiskFlush();
|
|
1396
|
-
}
|
|
1397
|
-
async function warmup(onError) {
|
|
1398
|
-
const now = Date.now();
|
|
1399
|
-
try {
|
|
1400
|
-
const { value } = await jsonStore.readJsonFileWithFallback(filePath, {});
|
|
1401
|
-
const data = sanitizeData(value);
|
|
1402
|
-
let loaded = 0;
|
|
1403
|
-
for (const [chatId, entry] of Object.entries(data)) {
|
|
1404
|
-
if (!isExpired(entry, now)) {
|
|
1405
|
-
memory.set(chatId, entry);
|
|
1406
|
-
loaded++;
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
pruneMemory();
|
|
1410
|
-
return loaded;
|
|
1411
|
-
}
|
|
1412
|
-
catch (error) {
|
|
1413
|
-
onError?.(error);
|
|
1414
|
-
return 0;
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
async function flush() {
|
|
1418
|
-
if (flushTimer) {
|
|
1419
|
-
clearTimeout(flushTimer);
|
|
1420
|
-
flushTimer = null;
|
|
1421
|
-
}
|
|
1422
|
-
await flushToDisk();
|
|
1423
1317
|
}
|
|
1424
1318
|
function clearMemory() {
|
|
1425
1319
|
memory.clear();
|
|
@@ -1432,8 +1326,6 @@ function createPersistentReqIdStore(accountId, options) {
|
|
|
1432
1326
|
get,
|
|
1433
1327
|
getSync,
|
|
1434
1328
|
delete: del,
|
|
1435
|
-
warmup,
|
|
1436
|
-
flush,
|
|
1437
1329
|
clearMemory,
|
|
1438
1330
|
memorySize,
|
|
1439
1331
|
};
|
|
@@ -1536,7 +1428,7 @@ const reqIdStores = new Map();
|
|
|
1536
1428
|
function getOrCreateReqIdStore(accountId) {
|
|
1537
1429
|
let store = reqIdStores.get(accountId);
|
|
1538
1430
|
if (!store) {
|
|
1539
|
-
store = createPersistentReqIdStore(
|
|
1431
|
+
store = createPersistentReqIdStore();
|
|
1540
1432
|
reqIdStores.set(accountId, store);
|
|
1541
1433
|
}
|
|
1542
1434
|
return store;
|
|
@@ -1552,12 +1444,13 @@ function setReqIdForChat(chatId, reqId, accountId = "default") {
|
|
|
1552
1444
|
}
|
|
1553
1445
|
/**
|
|
1554
1446
|
* 启动时预热 reqId 缓存(从磁盘加载到内存)
|
|
1447
|
+
*
|
|
1448
|
+
* 注意:由于移除了磁盘存储,此函数现在只返回 0(无预热条目)
|
|
1555
1449
|
*/
|
|
1556
1450
|
async function warmupReqIdStore(accountId = "default", log) {
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
});
|
|
1451
|
+
// 由于移除了磁盘存储,不再需要预热过程
|
|
1452
|
+
log?.("[WeCom] reqid-store warmup: no-op (disk storage removed)");
|
|
1453
|
+
return 0;
|
|
1561
1454
|
}
|
|
1562
1455
|
// ============================================================================
|
|
1563
1456
|
// 全局 cleanup(断开连接时释放所有资源)
|
|
@@ -1577,17 +1470,8 @@ async function cleanupAccount(accountId) {
|
|
|
1577
1470
|
}
|
|
1578
1471
|
wsClientInstances.delete(accountId);
|
|
1579
1472
|
}
|
|
1580
|
-
// 2. flush reqId
|
|
1581
|
-
|
|
1582
|
-
if (store) {
|
|
1583
|
-
try {
|
|
1584
|
-
await store.flush();
|
|
1585
|
-
}
|
|
1586
|
-
catch {
|
|
1587
|
-
// 忽略 flush 错误
|
|
1588
|
-
}
|
|
1589
|
-
// 注意:不删除 store,因为重连后可能还需要
|
|
1590
|
-
}
|
|
1473
|
+
// 2. 由于移除了磁盘存储,不再需要 flush reqId 存储
|
|
1474
|
+
// 注意:不删除 store,因为重连后可能还需要
|
|
1591
1475
|
}
|
|
1592
1476
|
|
|
1593
1477
|
/** 从 package.json 中读取版本号,兼容打包产物和直接运行 .ts 两种场景 */
|
|
@@ -2362,7 +2246,7 @@ function setWeComDmPolicy(cfg, dmPolicy) {
|
|
|
2362
2246
|
const account = resolveWeComAccount(cfg);
|
|
2363
2247
|
const existingAllowFrom = account.config.allowFrom ?? [];
|
|
2364
2248
|
const allowFrom = dmPolicy === "open"
|
|
2365
|
-
?
|
|
2249
|
+
? addWildcardAllowFrom(existingAllowFrom.map((x) => String(x)))
|
|
2366
2250
|
: existingAllowFrom.map((x) => String(x));
|
|
2367
2251
|
return setWeComAccount(cfg, {
|
|
2368
2252
|
dmPolicy,
|