@wecom/wecom-openclaw-plugin 2026.3.20 → 2026.3.24-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -1
- package/dist/index.cjs.js +26 -186
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.esm.js +16 -177
- package/dist/index.esm.js.map +1 -1
- package/dist/src/channel.d.ts +1 -1
- package/dist/src/dm-policy.d.ts +1 -1
- package/dist/src/group-policy.d.ts +2 -1
- package/dist/src/interface.d.ts +2 -1
- package/dist/src/media-handler.d.ts +2 -1
- package/dist/src/message-sender.d.ts +1 -1
- package/dist/src/onboarding.d.ts +2 -2
- package/dist/src/reqid-store.d.ts +3 -11
- package/dist/src/runtime.d.ts +1 -1
- package/dist/src/state-manager.d.ts +4 -0
- package/dist/src/utils.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as openclaw_plugin_sdk from 'openclaw/plugin-sdk';
|
|
2
|
+
import { OpenClawPluginApi } from 'openclaw/plugin-sdk/core';
|
|
2
3
|
|
|
3
4
|
declare const plugin: {
|
|
4
5
|
id: string;
|
|
5
6
|
name: string;
|
|
6
7
|
description: string;
|
|
7
|
-
configSchema:
|
|
8
|
+
configSchema: openclaw_plugin_sdk.OpenClawPluginConfigSchema;
|
|
8
9
|
register(api: OpenClawPluginApi): void;
|
|
9
10
|
};
|
|
10
11
|
|
package/dist/index.esm.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DEFAULT_ACCOUNT_ID, formatPairingApproveHint, emptyPluginConfigSchema } from 'openclaw/plugin-sdk/core';
|
|
2
2
|
import * as os$1 from 'os';
|
|
3
3
|
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
|
|
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
11
|
import { readFileSync } from 'node:fs';
|
|
12
|
+
import { addWildcardAllowFrom } from 'openclaw/plugin-sdk/setup';
|
|
13
13
|
|
|
14
14
|
let runtime = null;
|
|
15
15
|
function setWeComRuntime(r) {
|
|
@@ -31,7 +31,7 @@ function getWeComRuntime() {
|
|
|
31
31
|
* 本模块在加载时一次性探测 SDK 导出,存在则直接 re-export SDK 版本,
|
|
32
32
|
* 不存在则导出 fallback 实现。其他模块统一从本文件导入,无需关心底层兼容细节。
|
|
33
33
|
*/
|
|
34
|
-
const _sdkReady = import('openclaw/plugin-sdk')
|
|
34
|
+
const _sdkReady = import('openclaw/plugin-sdk/core')
|
|
35
35
|
.then((sdk) => {
|
|
36
36
|
const exports$1 = {};
|
|
37
37
|
if (typeof sdk.loadOutboundMediaFromUrl === "function") {
|
|
@@ -1173,80 +1173,27 @@ async function checkDmPolicy(params) {
|
|
|
1173
1173
|
return { allowed: false };
|
|
1174
1174
|
}
|
|
1175
1175
|
|
|
1176
|
+
// ============================================================================
|
|
1177
|
+
// 类型定义
|
|
1178
|
+
// ============================================================================
|
|
1176
1179
|
// ============================================================================
|
|
1177
1180
|
// 常量
|
|
1178
1181
|
// ============================================================================
|
|
1179
1182
|
const DEFAULT_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 天
|
|
1180
1183
|
const DEFAULT_MEMORY_MAX_SIZE = 200;
|
|
1181
|
-
const DEFAULT_FILE_MAX_ENTRIES = 500;
|
|
1182
|
-
const DEFAULT_FLUSH_DEBOUNCE_MS = 1000;
|
|
1183
|
-
const DEFAULT_LOCK_OPTIONS = {
|
|
1184
|
-
stale: 60000,
|
|
1185
|
-
retries: {
|
|
1186
|
-
retries: 6,
|
|
1187
|
-
factor: 1.35,
|
|
1188
|
-
minTimeout: 8,
|
|
1189
|
-
maxTimeout: 180,
|
|
1190
|
-
randomize: true,
|
|
1191
|
-
},
|
|
1192
|
-
};
|
|
1193
|
-
// ============================================================================
|
|
1194
|
-
// 状态目录解析
|
|
1195
|
-
// ============================================================================
|
|
1196
|
-
function resolveStateDirFromEnv(env = process.env) {
|
|
1197
|
-
const stateOverride = env.OPENCLAW_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
|
|
1198
|
-
if (stateOverride) {
|
|
1199
|
-
return stateOverride;
|
|
1200
|
-
}
|
|
1201
|
-
if (env.VITEST || env.NODE_ENV === "test") {
|
|
1202
|
-
return path__default.join(os__default.tmpdir(), ["openclaw-vitest", String(process.pid)].join("-"));
|
|
1203
|
-
}
|
|
1204
|
-
return path__default.join(os__default.homedir(), ".openclaw");
|
|
1205
|
-
}
|
|
1206
|
-
function resolveReqIdFilePath(accountId) {
|
|
1207
|
-
const safe = accountId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1208
|
-
return path__default.join(resolveStateDirFromEnv(), "wecom", `reqid-map-${safe}.json`);
|
|
1209
|
-
}
|
|
1210
1184
|
// ============================================================================
|
|
1211
1185
|
// 核心实现
|
|
1212
1186
|
// ============================================================================
|
|
1213
1187
|
function createPersistentReqIdStore(accountId, options) {
|
|
1214
1188
|
const ttlMs = DEFAULT_TTL_MS;
|
|
1215
1189
|
const memoryMaxSize = DEFAULT_MEMORY_MAX_SIZE;
|
|
1216
|
-
const fileMaxEntries = DEFAULT_FILE_MAX_ENTRIES;
|
|
1217
|
-
const flushDebounceMs = DEFAULT_FLUSH_DEBOUNCE_MS;
|
|
1218
|
-
const filePath = resolveReqIdFilePath(accountId);
|
|
1219
1190
|
// 内存层:chatId → ReqIdEntry
|
|
1220
1191
|
const memory = new Map();
|
|
1221
|
-
// 防抖写入相关
|
|
1222
|
-
let dirty = false;
|
|
1223
|
-
let flushTimer = null;
|
|
1224
1192
|
// ========== 内部辅助函数 ==========
|
|
1225
1193
|
/** 检查条目是否过期 */
|
|
1226
1194
|
function isExpired(entry, now) {
|
|
1227
1195
|
return now - entry.ts >= ttlMs;
|
|
1228
1196
|
}
|
|
1229
|
-
/** 验证磁盘条目的合法性 */
|
|
1230
|
-
function isValidEntry(entry) {
|
|
1231
|
-
return (typeof entry === "object" &&
|
|
1232
|
-
entry !== null &&
|
|
1233
|
-
typeof entry.reqId === "string" &&
|
|
1234
|
-
typeof entry.ts === "number" &&
|
|
1235
|
-
Number.isFinite(entry.ts));
|
|
1236
|
-
}
|
|
1237
|
-
/** 清理磁盘数据中的无效值,返回干净的 Record */
|
|
1238
|
-
function sanitizeData(value) {
|
|
1239
|
-
if (!value || typeof value !== "object") {
|
|
1240
|
-
return {};
|
|
1241
|
-
}
|
|
1242
|
-
const out = {};
|
|
1243
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
1244
|
-
if (isValidEntry(entry)) {
|
|
1245
|
-
out[key] = entry;
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
return out;
|
|
1249
|
-
}
|
|
1250
1197
|
/**
|
|
1251
1198
|
* 内存容量控制:淘汰最旧的条目。
|
|
1252
1199
|
* 利用 Map 的插入顺序 + touch(先 delete 再 set) 实现类 LRU 效果。
|
|
@@ -1260,61 +1207,6 @@ function createPersistentReqIdStore(accountId, options) {
|
|
|
1260
1207
|
memory.delete(key);
|
|
1261
1208
|
}
|
|
1262
1209
|
}
|
|
1263
|
-
/** 磁盘数据容量控制:先清过期,再按时间淘汰超量 */
|
|
1264
|
-
function pruneFileData(data, now) {
|
|
1265
|
-
{
|
|
1266
|
-
for (const [key, entry] of Object.entries(data)) {
|
|
1267
|
-
if (now - entry.ts >= ttlMs) {
|
|
1268
|
-
delete data[key];
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
const keys = Object.keys(data);
|
|
1273
|
-
if (keys.length <= fileMaxEntries)
|
|
1274
|
-
return;
|
|
1275
|
-
keys
|
|
1276
|
-
.sort((a, b) => data[a].ts - data[b].ts)
|
|
1277
|
-
.slice(0, keys.length - fileMaxEntries)
|
|
1278
|
-
.forEach((key) => delete data[key]);
|
|
1279
|
-
}
|
|
1280
|
-
/** 防抖写入磁盘 */
|
|
1281
|
-
function scheduleDiskFlush() {
|
|
1282
|
-
dirty = true;
|
|
1283
|
-
if (flushTimer)
|
|
1284
|
-
return;
|
|
1285
|
-
flushTimer = setTimeout(async () => {
|
|
1286
|
-
flushTimer = null;
|
|
1287
|
-
if (!dirty)
|
|
1288
|
-
return;
|
|
1289
|
-
await flushToDisk();
|
|
1290
|
-
}, flushDebounceMs);
|
|
1291
|
-
}
|
|
1292
|
-
/** 立即写入磁盘(带文件锁,参考 createPersistentDedupe 的 checkAndRecordInner) */
|
|
1293
|
-
async function flushToDisk() {
|
|
1294
|
-
dirty = false;
|
|
1295
|
-
const now = Date.now();
|
|
1296
|
-
try {
|
|
1297
|
-
await withFileLock(filePath, DEFAULT_LOCK_OPTIONS, async () => {
|
|
1298
|
-
// 读取现有磁盘数据并合并
|
|
1299
|
-
const { value } = await readJsonFileWithFallback(filePath, {});
|
|
1300
|
-
const data = sanitizeData(value);
|
|
1301
|
-
// 将内存中未过期的数据合并到磁盘数据(内存优先)
|
|
1302
|
-
for (const [chatId, entry] of memory) {
|
|
1303
|
-
if (!isExpired(entry, now)) {
|
|
1304
|
-
data[chatId] = entry;
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
// 清理过期和超量
|
|
1308
|
-
pruneFileData(data, now);
|
|
1309
|
-
// 原子写入
|
|
1310
|
-
await writeJsonFileAtomically(filePath, data);
|
|
1311
|
-
});
|
|
1312
|
-
}
|
|
1313
|
-
catch (error) {
|
|
1314
|
-
// 磁盘写入失败不影响内存使用,降级到纯内存模式
|
|
1315
|
-
// console.error(`[WeCom] reqid-store: flush to disk failed: ${String(error)}`);
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
1210
|
// ========== 公开 API ==========
|
|
1319
1211
|
function set(chatId, reqId) {
|
|
1320
1212
|
const entry = { reqId, ts: Date.now() };
|
|
@@ -1322,11 +1214,10 @@ function createPersistentReqIdStore(accountId, options) {
|
|
|
1322
1214
|
memory.delete(chatId);
|
|
1323
1215
|
memory.set(chatId, entry);
|
|
1324
1216
|
pruneMemory();
|
|
1325
|
-
scheduleDiskFlush();
|
|
1326
1217
|
}
|
|
1327
1218
|
async function get(chatId) {
|
|
1328
1219
|
const now = Date.now();
|
|
1329
|
-
//
|
|
1220
|
+
// 仅查内存
|
|
1330
1221
|
const memEntry = memory.get(chatId);
|
|
1331
1222
|
if (memEntry && !isExpired(memEntry, now)) {
|
|
1332
1223
|
return memEntry.reqId;
|
|
@@ -1334,20 +1225,6 @@ function createPersistentReqIdStore(accountId, options) {
|
|
|
1334
1225
|
if (memEntry) {
|
|
1335
1226
|
memory.delete(chatId); // 过期则删除
|
|
1336
1227
|
}
|
|
1337
|
-
// 2. 内存 miss,回查磁盘并回填内存
|
|
1338
|
-
try {
|
|
1339
|
-
const { value } = await readJsonFileWithFallback(filePath, {});
|
|
1340
|
-
const data = sanitizeData(value);
|
|
1341
|
-
const diskEntry = data[chatId];
|
|
1342
|
-
if (diskEntry && !isExpired(diskEntry, now)) {
|
|
1343
|
-
// 回填内存
|
|
1344
|
-
memory.set(chatId, diskEntry);
|
|
1345
|
-
return diskEntry.reqId;
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
catch {
|
|
1349
|
-
// 磁盘读取失败,降级返回 undefined
|
|
1350
|
-
}
|
|
1351
1228
|
return undefined;
|
|
1352
1229
|
}
|
|
1353
1230
|
function getSync(chatId) {
|
|
@@ -1363,34 +1240,6 @@ function createPersistentReqIdStore(accountId, options) {
|
|
|
1363
1240
|
}
|
|
1364
1241
|
function del(chatId) {
|
|
1365
1242
|
memory.delete(chatId);
|
|
1366
|
-
scheduleDiskFlush();
|
|
1367
|
-
}
|
|
1368
|
-
async function warmup(onError) {
|
|
1369
|
-
const now = Date.now();
|
|
1370
|
-
try {
|
|
1371
|
-
const { value } = await readJsonFileWithFallback(filePath, {});
|
|
1372
|
-
const data = sanitizeData(value);
|
|
1373
|
-
let loaded = 0;
|
|
1374
|
-
for (const [chatId, entry] of Object.entries(data)) {
|
|
1375
|
-
if (!isExpired(entry, now)) {
|
|
1376
|
-
memory.set(chatId, entry);
|
|
1377
|
-
loaded++;
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
pruneMemory();
|
|
1381
|
-
return loaded;
|
|
1382
|
-
}
|
|
1383
|
-
catch (error) {
|
|
1384
|
-
onError?.(error);
|
|
1385
|
-
return 0;
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
async function flush() {
|
|
1389
|
-
if (flushTimer) {
|
|
1390
|
-
clearTimeout(flushTimer);
|
|
1391
|
-
flushTimer = null;
|
|
1392
|
-
}
|
|
1393
|
-
await flushToDisk();
|
|
1394
1243
|
}
|
|
1395
1244
|
function clearMemory() {
|
|
1396
1245
|
memory.clear();
|
|
@@ -1403,8 +1252,6 @@ function createPersistentReqIdStore(accountId, options) {
|
|
|
1403
1252
|
get,
|
|
1404
1253
|
getSync,
|
|
1405
1254
|
delete: del,
|
|
1406
|
-
warmup,
|
|
1407
|
-
flush,
|
|
1408
1255
|
clearMemory,
|
|
1409
1256
|
memorySize,
|
|
1410
1257
|
};
|
|
@@ -1507,7 +1354,7 @@ const reqIdStores = new Map();
|
|
|
1507
1354
|
function getOrCreateReqIdStore(accountId) {
|
|
1508
1355
|
let store = reqIdStores.get(accountId);
|
|
1509
1356
|
if (!store) {
|
|
1510
|
-
store = createPersistentReqIdStore(
|
|
1357
|
+
store = createPersistentReqIdStore();
|
|
1511
1358
|
reqIdStores.set(accountId, store);
|
|
1512
1359
|
}
|
|
1513
1360
|
return store;
|
|
@@ -1523,12 +1370,13 @@ function setReqIdForChat(chatId, reqId, accountId = "default") {
|
|
|
1523
1370
|
}
|
|
1524
1371
|
/**
|
|
1525
1372
|
* 启动时预热 reqId 缓存(从磁盘加载到内存)
|
|
1373
|
+
*
|
|
1374
|
+
* 注意:由于移除了磁盘存储,此函数现在只返回 0(无预热条目)
|
|
1526
1375
|
*/
|
|
1527
1376
|
async function warmupReqIdStore(accountId = "default", log) {
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
});
|
|
1377
|
+
// 由于移除了磁盘存储,不再需要预热过程
|
|
1378
|
+
log?.("[WeCom] reqid-store warmup: no-op (disk storage removed)");
|
|
1379
|
+
return 0;
|
|
1532
1380
|
}
|
|
1533
1381
|
// ============================================================================
|
|
1534
1382
|
// 全局 cleanup(断开连接时释放所有资源)
|
|
@@ -1548,17 +1396,8 @@ async function cleanupAccount(accountId) {
|
|
|
1548
1396
|
}
|
|
1549
1397
|
wsClientInstances.delete(accountId);
|
|
1550
1398
|
}
|
|
1551
|
-
// 2. flush reqId
|
|
1552
|
-
|
|
1553
|
-
if (store) {
|
|
1554
|
-
try {
|
|
1555
|
-
await store.flush();
|
|
1556
|
-
}
|
|
1557
|
-
catch {
|
|
1558
|
-
// 忽略 flush 错误
|
|
1559
|
-
}
|
|
1560
|
-
// 注意:不删除 store,因为重连后可能还需要
|
|
1561
|
-
}
|
|
1399
|
+
// 2. 由于移除了磁盘存储,不再需要 flush reqId 存储
|
|
1400
|
+
// 注意:不删除 store,因为重连后可能还需要
|
|
1562
1401
|
}
|
|
1563
1402
|
|
|
1564
1403
|
/** 从 package.json 中读取版本号,兼容打包产物和直接运行 .ts 两种场景 */
|