@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.esm.js CHANGED
@@ -4,15 +4,11 @@ import * as path$1 from 'path';
4
4
  import { generateReqId, WSClient, WSAuthFailureError, WSReconnectExhaustedError } from '@wecom/aibot-node-sdk';
5
5
  import * as fs from 'node:fs/promises';
6
6
  import * as path from 'node:path';
7
- import path__default, { dirname, resolve } from 'node:path';
7
+ import { dirname, resolve } from 'node:path';
8
8
  import * as os from 'node:os';
9
- import os__default from 'node:os';
10
9
  import { fileURLToPath } from 'node:url';
11
10
  import { fileTypeFromBuffer } from 'file-type';
12
- import { readJsonFileWithFallback, writeJsonFileAtomically } from 'openclaw/plugin-sdk/json-store';
13
- import { withFileLock } from 'openclaw/plugin-sdk/infra-runtime';
14
11
  import { readFileSync } from 'node:fs';
15
- import { addWildcardAllowFrom } from 'openclaw/plugin-sdk/setup';
16
12
 
17
13
  let runtime = null;
18
14
  function setWeComRuntime(r) {
@@ -46,12 +42,30 @@ const _sdkReady = import('openclaw/plugin-sdk/core')
46
42
  if (typeof sdk.getDefaultMediaLocalRoots === "function") {
47
43
  exports$1.getDefaultMediaLocalRoots = sdk.getDefaultMediaLocalRoots;
48
44
  }
45
+ if (typeof sdk.addWildcardAllowFrom === "function") {
46
+ exports$1.addWildcardAllowFrom = sdk.addWildcardAllowFrom;
47
+ }
49
48
  return exports$1;
50
49
  })
51
50
  .catch(() => {
52
51
  // openclaw/plugin-sdk 不可用或版本过低,全部使用 fallback
53
52
  return {};
54
53
  });
54
+ // 同时尝试从 plugin-sdk/setup 模块探测(缓存同步可用的引用)
55
+ let _cachedAddWildcardAllowFrom;
56
+ import('openclaw/plugin-sdk/setup')
57
+ .then((sdk) => {
58
+ if (typeof sdk.addWildcardAllowFrom === "function") {
59
+ _cachedAddWildcardAllowFrom = sdk.addWildcardAllowFrom;
60
+ }
61
+ })
62
+ .catch(() => { });
63
+ // 同时也从 core 模块的结果中缓存
64
+ _sdkReady.then((sdk) => {
65
+ if (!_cachedAddWildcardAllowFrom && sdk.addWildcardAllowFrom) {
66
+ _cachedAddWildcardAllowFrom = sdk.addWildcardAllowFrom;
67
+ }
68
+ });
55
69
  // ============================================================================
56
70
  // detectMime —— 检测 MIME 类型
57
71
  // ============================================================================
@@ -295,6 +309,36 @@ async function loadOutboundMediaFromUrl(mediaUrl, options = {}) {
295
309
  return loadOutboundMediaFromUrlFallback(mediaUrl, options);
296
310
  }
297
311
  // ============================================================================
312
+ // addWildcardAllowFrom —— 向 allowFrom 列表添加通配符 "*"
313
+ // ============================================================================
314
+ /** fallback 版 addWildcardAllowFrom:确保列表中包含 "*" 通配符 */
315
+ function addWildcardAllowFromFallback(allowFrom) {
316
+ if (allowFrom.includes("*")) {
317
+ return allowFrom;
318
+ }
319
+ return [...allowFrom, "*"];
320
+ }
321
+ /**
322
+ * 向 allowFrom 列表添加通配符 "*"(兼容入口)
323
+ *
324
+ * 当 dmPolicy 为 "open" 时,需要确保 allowFrom 中包含 "*" 以允许所有来源。
325
+ * 优先使用 SDK 版本(plugin-sdk/setup 或 plugin-sdk/core),不可用时使用 fallback。
326
+ *
327
+ * 注意:此函数为同步函数,与 SDK 原始签名一致。
328
+ * SDK 引用在模块加载时异步探测并缓存,调用时同步读取缓存。
329
+ */
330
+ function addWildcardAllowFrom(allowFrom) {
331
+ if (_cachedAddWildcardAllowFrom) {
332
+ try {
333
+ return _cachedAddWildcardAllowFrom(allowFrom);
334
+ }
335
+ catch {
336
+ // SDK 版本异常,降级到 fallback
337
+ }
338
+ }
339
+ return addWildcardAllowFromFallback(allowFrom);
340
+ }
341
+ // ============================================================================
298
342
  // getDefaultMediaLocalRoots —— 获取默认媒体本地路径白名单
299
343
  // ============================================================================
300
344
  /** 解析 openclaw 状态目录 */
@@ -1176,80 +1220,27 @@ async function checkDmPolicy(params) {
1176
1220
  return { allowed: false };
1177
1221
  }
1178
1222
 
1223
+ // ============================================================================
1224
+ // 类型定义
1225
+ // ============================================================================
1179
1226
  // ============================================================================
1180
1227
  // 常量
1181
1228
  // ============================================================================
1182
1229
  const DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 天
1183
1230
  const DEFAULT_MEMORY_MAX_SIZE = 200;
1184
- const DEFAULT_FILE_MAX_ENTRIES = 500;
1185
- const DEFAULT_FLUSH_DEBOUNCE_MS = 1000;
1186
- const DEFAULT_LOCK_OPTIONS = {
1187
- stale: 60000,
1188
- retries: {
1189
- retries: 6,
1190
- factor: 1.35,
1191
- minTimeout: 8,
1192
- maxTimeout: 180,
1193
- randomize: true,
1194
- },
1195
- };
1196
- // ============================================================================
1197
- // 状态目录解析
1198
- // ============================================================================
1199
- function resolveStateDirFromEnv(env = process.env) {
1200
- const stateOverride = env.OPENCLAW_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
1201
- if (stateOverride) {
1202
- return stateOverride;
1203
- }
1204
- if (env.VITEST || env.NODE_ENV === "test") {
1205
- return path__default.join(os__default.tmpdir(), ["openclaw-vitest", String(process.pid)].join("-"));
1206
- }
1207
- return path__default.join(os__default.homedir(), ".openclaw");
1208
- }
1209
- function resolveReqIdFilePath(accountId) {
1210
- const safe = accountId.replace(/[^a-zA-Z0-9_-]/g, "_");
1211
- return path__default.join(resolveStateDirFromEnv(), "wecom", `reqid-map-${safe}.json`);
1212
- }
1213
1231
  // ============================================================================
1214
1232
  // 核心实现
1215
1233
  // ============================================================================
1216
1234
  function createPersistentReqIdStore(accountId, options) {
1217
1235
  const ttlMs = DEFAULT_TTL_MS;
1218
1236
  const memoryMaxSize = DEFAULT_MEMORY_MAX_SIZE;
1219
- const fileMaxEntries = DEFAULT_FILE_MAX_ENTRIES;
1220
- const flushDebounceMs = DEFAULT_FLUSH_DEBOUNCE_MS;
1221
- const filePath = resolveReqIdFilePath(accountId);
1222
1237
  // 内存层:chatId → ReqIdEntry
1223
1238
  const memory = new Map();
1224
- // 防抖写入相关
1225
- let dirty = false;
1226
- let flushTimer = null;
1227
1239
  // ========== 内部辅助函数 ==========
1228
1240
  /** 检查条目是否过期 */
1229
1241
  function isExpired(entry, now) {
1230
1242
  return now - entry.ts >= ttlMs;
1231
1243
  }
1232
- /** 验证磁盘条目的合法性 */
1233
- function isValidEntry(entry) {
1234
- return (typeof entry === "object" &&
1235
- entry !== null &&
1236
- typeof entry.reqId === "string" &&
1237
- typeof entry.ts === "number" &&
1238
- Number.isFinite(entry.ts));
1239
- }
1240
- /** 清理磁盘数据中的无效值,返回干净的 Record */
1241
- function sanitizeData(value) {
1242
- if (!value || typeof value !== "object") {
1243
- return {};
1244
- }
1245
- const out = {};
1246
- for (const [key, entry] of Object.entries(value)) {
1247
- if (isValidEntry(entry)) {
1248
- out[key] = entry;
1249
- }
1250
- }
1251
- return out;
1252
- }
1253
1244
  /**
1254
1245
  * 内存容量控制:淘汰最旧的条目。
1255
1246
  * 利用 Map 的插入顺序 + touch(先 delete 再 set) 实现类 LRU 效果。
@@ -1263,61 +1254,6 @@ function createPersistentReqIdStore(accountId, options) {
1263
1254
  memory.delete(key);
1264
1255
  }
1265
1256
  }
1266
- /** 磁盘数据容量控制:先清过期,再按时间淘汰超量 */
1267
- function pruneFileData(data, now) {
1268
- {
1269
- for (const [key, entry] of Object.entries(data)) {
1270
- if (now - entry.ts >= ttlMs) {
1271
- delete data[key];
1272
- }
1273
- }
1274
- }
1275
- const keys = Object.keys(data);
1276
- if (keys.length <= fileMaxEntries)
1277
- return;
1278
- keys
1279
- .sort((a, b) => data[a].ts - data[b].ts)
1280
- .slice(0, keys.length - fileMaxEntries)
1281
- .forEach((key) => delete data[key]);
1282
- }
1283
- /** 防抖写入磁盘 */
1284
- function scheduleDiskFlush() {
1285
- dirty = true;
1286
- if (flushTimer)
1287
- return;
1288
- flushTimer = setTimeout(async () => {
1289
- flushTimer = null;
1290
- if (!dirty)
1291
- return;
1292
- await flushToDisk();
1293
- }, flushDebounceMs);
1294
- }
1295
- /** 立即写入磁盘(带文件锁,参考 createPersistentDedupe 的 checkAndRecordInner) */
1296
- async function flushToDisk() {
1297
- dirty = false;
1298
- const now = Date.now();
1299
- try {
1300
- await withFileLock(filePath, DEFAULT_LOCK_OPTIONS, async () => {
1301
- // 读取现有磁盘数据并合并
1302
- const { value } = await readJsonFileWithFallback(filePath, {});
1303
- const data = sanitizeData(value);
1304
- // 将内存中未过期的数据合并到磁盘数据(内存优先)
1305
- for (const [chatId, entry] of memory) {
1306
- if (!isExpired(entry, now)) {
1307
- data[chatId] = entry;
1308
- }
1309
- }
1310
- // 清理过期和超量
1311
- pruneFileData(data, now);
1312
- // 原子写入
1313
- await writeJsonFileAtomically(filePath, data);
1314
- });
1315
- }
1316
- catch (error) {
1317
- // 磁盘写入失败不影响内存使用,降级到纯内存模式
1318
- // console.error(`[WeCom] reqid-store: flush to disk failed: ${String(error)}`);
1319
- }
1320
- }
1321
1257
  // ========== 公开 API ==========
1322
1258
  function set(chatId, reqId) {
1323
1259
  const entry = { reqId, ts: Date.now() };
@@ -1325,11 +1261,10 @@ function createPersistentReqIdStore(accountId, options) {
1325
1261
  memory.delete(chatId);
1326
1262
  memory.set(chatId, entry);
1327
1263
  pruneMemory();
1328
- scheduleDiskFlush();
1329
1264
  }
1330
1265
  async function get(chatId) {
1331
1266
  const now = Date.now();
1332
- // 1. 先查内存
1267
+ // 仅查内存
1333
1268
  const memEntry = memory.get(chatId);
1334
1269
  if (memEntry && !isExpired(memEntry, now)) {
1335
1270
  return memEntry.reqId;
@@ -1337,20 +1272,6 @@ function createPersistentReqIdStore(accountId, options) {
1337
1272
  if (memEntry) {
1338
1273
  memory.delete(chatId); // 过期则删除
1339
1274
  }
1340
- // 2. 内存 miss,回查磁盘并回填内存
1341
- try {
1342
- const { value } = await readJsonFileWithFallback(filePath, {});
1343
- const data = sanitizeData(value);
1344
- const diskEntry = data[chatId];
1345
- if (diskEntry && !isExpired(diskEntry, now)) {
1346
- // 回填内存
1347
- memory.set(chatId, diskEntry);
1348
- return diskEntry.reqId;
1349
- }
1350
- }
1351
- catch {
1352
- // 磁盘读取失败,降级返回 undefined
1353
- }
1354
1275
  return undefined;
1355
1276
  }
1356
1277
  function getSync(chatId) {
@@ -1366,34 +1287,6 @@ function createPersistentReqIdStore(accountId, options) {
1366
1287
  }
1367
1288
  function del(chatId) {
1368
1289
  memory.delete(chatId);
1369
- scheduleDiskFlush();
1370
- }
1371
- async function warmup(onError) {
1372
- const now = Date.now();
1373
- try {
1374
- const { value } = await readJsonFileWithFallback(filePath, {});
1375
- const data = sanitizeData(value);
1376
- let loaded = 0;
1377
- for (const [chatId, entry] of Object.entries(data)) {
1378
- if (!isExpired(entry, now)) {
1379
- memory.set(chatId, entry);
1380
- loaded++;
1381
- }
1382
- }
1383
- pruneMemory();
1384
- return loaded;
1385
- }
1386
- catch (error) {
1387
- onError?.(error);
1388
- return 0;
1389
- }
1390
- }
1391
- async function flush() {
1392
- if (flushTimer) {
1393
- clearTimeout(flushTimer);
1394
- flushTimer = null;
1395
- }
1396
- await flushToDisk();
1397
1290
  }
1398
1291
  function clearMemory() {
1399
1292
  memory.clear();
@@ -1406,8 +1299,6 @@ function createPersistentReqIdStore(accountId, options) {
1406
1299
  get,
1407
1300
  getSync,
1408
1301
  delete: del,
1409
- warmup,
1410
- flush,
1411
1302
  clearMemory,
1412
1303
  memorySize,
1413
1304
  };
@@ -1510,7 +1401,7 @@ const reqIdStores = new Map();
1510
1401
  function getOrCreateReqIdStore(accountId) {
1511
1402
  let store = reqIdStores.get(accountId);
1512
1403
  if (!store) {
1513
- store = createPersistentReqIdStore(accountId);
1404
+ store = createPersistentReqIdStore();
1514
1405
  reqIdStores.set(accountId, store);
1515
1406
  }
1516
1407
  return store;
@@ -1526,12 +1417,13 @@ function setReqIdForChat(chatId, reqId, accountId = "default") {
1526
1417
  }
1527
1418
  /**
1528
1419
  * 启动时预热 reqId 缓存(从磁盘加载到内存)
1420
+ *
1421
+ * 注意:由于移除了磁盘存储,此函数现在只返回 0(无预热条目)
1529
1422
  */
1530
1423
  async function warmupReqIdStore(accountId = "default", log) {
1531
- const store = getOrCreateReqIdStore(accountId);
1532
- return store.warmup((error) => {
1533
- log?.(`[WeCom] reqid-store warmup error: ${String(error)}`);
1534
- });
1424
+ // 由于移除了磁盘存储,不再需要预热过程
1425
+ log?.("[WeCom] reqid-store warmup: no-op (disk storage removed)");
1426
+ return 0;
1535
1427
  }
1536
1428
  // ============================================================================
1537
1429
  // 全局 cleanup(断开连接时释放所有资源)
@@ -1551,17 +1443,8 @@ async function cleanupAccount(accountId) {
1551
1443
  }
1552
1444
  wsClientInstances.delete(accountId);
1553
1445
  }
1554
- // 2. flush reqId 存储到磁盘
1555
- const store = reqIdStores.get(accountId);
1556
- if (store) {
1557
- try {
1558
- await store.flush();
1559
- }
1560
- catch {
1561
- // 忽略 flush 错误
1562
- }
1563
- // 注意:不删除 store,因为重连后可能还需要
1564
- }
1446
+ // 2. 由于移除了磁盘存储,不再需要 flush reqId 存储
1447
+ // 注意:不删除 store,因为重连后可能还需要
1565
1448
  }
1566
1449
 
1567
1450
  /** 从 package.json 中读取版本号,兼容打包产物和直接运行 .ts 两种场景 */