@wecom/wecom-openclaw-plugin 1.0.9-beta.0 → 1.0.9-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 +2 -2
- package/dist/index.cjs.js +162 -10
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +161 -10
- package/dist/index.esm.js.map +1 -1
- package/dist/src/const.d.ts +6 -0
- package/dist/src/interface.d.ts +11 -0
- package/dist/src/mcp-config.d.ts +29 -0
- package/package.json +1 -1
- package/skills/wecom-doc/SKILL.md +73 -37
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ openclaw gateway restart
|
|
|
62
62
|
| `channels.wecom.secret` | WeCom bot secret | — | — |
|
|
63
63
|
| `channels.wecom.enabled` | Enable the channel | `true` / `false` | `false` |
|
|
64
64
|
| `channels.wecom.websocketUrl` | WebSocket endpoint | — | `wss://openws.work.weixin.qq.com` |
|
|
65
|
-
| `channels.wecom.dmPolicy` | DM access policy | `pairing` / `open` / `allowlist` / `disabled` | `
|
|
65
|
+
| `channels.wecom.dmPolicy` | DM access policy | `pairing` / `open` / `allowlist` / `disabled` | `open` |
|
|
66
66
|
| `channels.wecom.allowFrom` | DM allowlist (user IDs) | — | `[]` |
|
|
67
67
|
| `channels.wecom.groupPolicy` | Group chat access policy | `open` / `allowlist` / `disabled` | `open` |
|
|
68
68
|
| `channels.wecom.groupAllowFrom` | Group allowlist (group IDs) | — | `[]` |
|
|
@@ -74,7 +74,7 @@ openclaw gateway restart
|
|
|
74
74
|
|
|
75
75
|
### DM (Direct Message) Access
|
|
76
76
|
|
|
77
|
-
**Default**: `dmPolicy: "
|
|
77
|
+
**Default**: `dmPolicy: "open"` — all users can send direct messages without approval.
|
|
78
78
|
|
|
79
79
|
#### Approve Pairing
|
|
80
80
|
|
package/dist/index.cjs.js
CHANGED
|
@@ -4,10 +4,14 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var pluginSdk = require('openclaw/plugin-sdk');
|
|
6
6
|
var aibotNodeSdk = require('@wecom/aibot-node-sdk');
|
|
7
|
+
var path = require('path');
|
|
8
|
+
var url = require('url');
|
|
7
9
|
var fileType = require('file-type');
|
|
8
10
|
var os = require('node:os');
|
|
9
|
-
var path = require('node:path');
|
|
11
|
+
var path$1 = require('node:path');
|
|
12
|
+
var os$1 = require('os');
|
|
10
13
|
|
|
14
|
+
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
11
15
|
let runtime = null;
|
|
12
16
|
function setWeComRuntime(r) {
|
|
13
17
|
runtime = r;
|
|
@@ -76,6 +80,20 @@ const MEDIA_DOCUMENT_PLACEHOLDER = "<media:document>";
|
|
|
76
80
|
// ============================================================================
|
|
77
81
|
// 默认值
|
|
78
82
|
// ============================================================================
|
|
83
|
+
// ============================================================================
|
|
84
|
+
// MCP 配置
|
|
85
|
+
// ============================================================================
|
|
86
|
+
/** 获取 MCP 配置的 WebSocket 命令 */
|
|
87
|
+
const MCP_GET_CONFIG_CMD = "aibot_get_mcp_config";
|
|
88
|
+
/** MCP 配置拉取超时时间(毫秒) */
|
|
89
|
+
const MCP_CONFIG_FETCH_TIMEOUT_MS = 15000;
|
|
90
|
+
const __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs.js', document.baseURI).href)));
|
|
91
|
+
const __dirname$1 = path.dirname(__filename$1);
|
|
92
|
+
const PLUGIN_JSON_FILENAME = "openclaw.plugin.json";
|
|
93
|
+
path.resolve(path.join(__dirname$1, "../", PLUGIN_JSON_FILENAME));
|
|
94
|
+
// ============================================================================
|
|
95
|
+
// 默认值
|
|
96
|
+
// ============================================================================
|
|
79
97
|
/** 默认媒体大小上限(MB) */
|
|
80
98
|
const DEFAULT_MEDIA_MAX_MB = 5;
|
|
81
99
|
/** 文本分块大小上限 */
|
|
@@ -494,7 +512,7 @@ async function checkDmPolicy(params) {
|
|
|
494
512
|
if (isGroup) {
|
|
495
513
|
return { allowed: true };
|
|
496
514
|
}
|
|
497
|
-
const dmPolicy = account.config.dmPolicy ?? "
|
|
515
|
+
const dmPolicy = account.config.dmPolicy ?? "open";
|
|
498
516
|
const configAllowFrom = (account.config.allowFrom ?? []).map((v) => String(v));
|
|
499
517
|
// 如果 dmPolicy 是 disabled,直接拒绝
|
|
500
518
|
if (dmPolicy === "disabled") {
|
|
@@ -581,13 +599,13 @@ function resolveStateDirFromEnv(env = process.env) {
|
|
|
581
599
|
return stateOverride;
|
|
582
600
|
}
|
|
583
601
|
if (env.VITEST || env.NODE_ENV === "test") {
|
|
584
|
-
return path.join(os.tmpdir(), ["openclaw-vitest", String(process.pid)].join("-"));
|
|
602
|
+
return path$1.join(os.tmpdir(), ["openclaw-vitest", String(process.pid)].join("-"));
|
|
585
603
|
}
|
|
586
|
-
return path.join(os.homedir(), ".openclaw");
|
|
604
|
+
return path$1.join(os.homedir(), ".openclaw");
|
|
587
605
|
}
|
|
588
606
|
function resolveReqIdFilePath(accountId) {
|
|
589
607
|
const safe = accountId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
590
|
-
return path.join(resolveStateDirFromEnv(), "wecom", `reqid-map-${safe}.json`);
|
|
608
|
+
return path$1.join(resolveStateDirFromEnv(), "wecom", `reqid-map-${safe}.json`);
|
|
591
609
|
}
|
|
592
610
|
// ============================================================================
|
|
593
611
|
// 核心实现
|
|
@@ -943,6 +961,138 @@ async function cleanupAccount(accountId) {
|
|
|
943
961
|
}
|
|
944
962
|
}
|
|
945
963
|
|
|
964
|
+
/**
|
|
965
|
+
* MCP 配置拉取与持久化模块
|
|
966
|
+
*
|
|
967
|
+
* 负责:
|
|
968
|
+
* - 通过 WSClient 发送 aibot_get_mcp_config 请求
|
|
969
|
+
* - 解析服务端响应,提取 MCP 配置(url、type、is_authed)
|
|
970
|
+
* - 将配置写入 openclaw.plugin.json 的 wecomMcp 字段
|
|
971
|
+
*/
|
|
972
|
+
// ============================================================================
|
|
973
|
+
// MCP 配置拉取
|
|
974
|
+
// ============================================================================
|
|
975
|
+
/**
|
|
976
|
+
* 通过 WSClient 发送 aibot_get_mcp_config 命令,获取 MCP 配置
|
|
977
|
+
*
|
|
978
|
+
* @param wsClient - 已认证的 WSClient 实例
|
|
979
|
+
* @returns MCP 配置(url、type、is_authed)
|
|
980
|
+
* @throws 响应错误码非 0 或缺少 url 字段时抛出错误
|
|
981
|
+
*/
|
|
982
|
+
async function fetchMcpConfig(wsClient) {
|
|
983
|
+
const reqId = aibotNodeSdk.generateReqId("mcp_config");
|
|
984
|
+
// 通过 reply 方法发送自定义命令
|
|
985
|
+
const response = await withTimeout(wsClient.reply({ headers: { req_id: reqId } }, { biz_type: "doc" }, MCP_GET_CONFIG_CMD), MCP_CONFIG_FETCH_TIMEOUT_MS, `MCP config fetch timed out after ${MCP_CONFIG_FETCH_TIMEOUT_MS}ms`);
|
|
986
|
+
// 校验响应错误码
|
|
987
|
+
if (response.errcode && response.errcode !== 0) {
|
|
988
|
+
throw new Error(`MCP config request failed: errcode=${response.errcode}, errmsg=${response.errmsg ?? "unknown"}`);
|
|
989
|
+
}
|
|
990
|
+
// 提取并校验 body
|
|
991
|
+
const body = response.body;
|
|
992
|
+
if (!body?.url) {
|
|
993
|
+
throw new Error("MCP config response missing required 'url' field");
|
|
994
|
+
}
|
|
995
|
+
return {
|
|
996
|
+
url: body.url,
|
|
997
|
+
type: 'doc',
|
|
998
|
+
is_authed: body.is_authed,
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
// ============================================================================
|
|
1002
|
+
// 配置持久化
|
|
1003
|
+
// ============================================================================
|
|
1004
|
+
/**
|
|
1005
|
+
* 将 MCP 配置写入 openclaw.plugin.json 的 mcpConfig 字段
|
|
1006
|
+
*
|
|
1007
|
+
* 使用 OpenClaw SDK 提供的文件锁和原子写入,保证并发安全。
|
|
1008
|
+
* 配置格式: { mcpConfig: { [type]: { type, url } } }
|
|
1009
|
+
* 示例: { "mcpConfig": { "doc": { "type": "streamable-http", "url": "..." } } }
|
|
1010
|
+
*
|
|
1011
|
+
* @param config - MCP 配置
|
|
1012
|
+
* @param runtime - 运行时环境
|
|
1013
|
+
*/
|
|
1014
|
+
async function saveMcpConfigToPluginJson(config, runtime) {
|
|
1015
|
+
// const pluginJsonPath = PLUGIN_JSON_PATH;
|
|
1016
|
+
// 获取绝对路径并写入到配置文件
|
|
1017
|
+
// const absolutePath = path.resolve(pluginJsonPath);
|
|
1018
|
+
const wecomConfigDir = path.join(os$1.homedir(), ".openclaw", "wecomConfig");
|
|
1019
|
+
const wecomConfigPath = path.join(wecomConfigDir, "config.json");
|
|
1020
|
+
// // 写入 wecom 配置文件
|
|
1021
|
+
// try {
|
|
1022
|
+
// const lockOptions = {
|
|
1023
|
+
// stale: 60_000,
|
|
1024
|
+
// retries: {
|
|
1025
|
+
// retries: 3,
|
|
1026
|
+
// factor: 1.35,
|
|
1027
|
+
// minTimeout: 8,
|
|
1028
|
+
// maxTimeout: 200,
|
|
1029
|
+
// randomize: true,
|
|
1030
|
+
// },
|
|
1031
|
+
// };
|
|
1032
|
+
// await withFileLock(wecomConfigPath, lockOptions, async () => {
|
|
1033
|
+
// const { value: wecomConfig } = await readJsonFileWithFallback<Record<string, unknown>>(
|
|
1034
|
+
// wecomConfigPath,
|
|
1035
|
+
// {},
|
|
1036
|
+
// );
|
|
1037
|
+
// wecomConfig.pluginConfigPath = absolutePath;
|
|
1038
|
+
// await writeJsonFileAtomically(wecomConfigPath, wecomConfig);
|
|
1039
|
+
// runtime.log?.(`[WeCom] Plugin config path saved to ${wecomConfigPath}`);
|
|
1040
|
+
// });
|
|
1041
|
+
// } catch (err) {
|
|
1042
|
+
// runtime.error?.(`[WeCom] Failed to save plugin config path: ${String(err)}`);
|
|
1043
|
+
// }
|
|
1044
|
+
const lockOptions = {
|
|
1045
|
+
stale: 60000, // 60秒锁过期
|
|
1046
|
+
retries: {
|
|
1047
|
+
retries: 6,
|
|
1048
|
+
factor: 1.35,
|
|
1049
|
+
minTimeout: 8,
|
|
1050
|
+
maxTimeout: 1200,
|
|
1051
|
+
randomize: true,
|
|
1052
|
+
},
|
|
1053
|
+
};
|
|
1054
|
+
await pluginSdk.withFileLock(wecomConfigPath, lockOptions, async () => {
|
|
1055
|
+
// 读取现有配置(不存在时使用空对象)
|
|
1056
|
+
const { value: pluginJson } = await pluginSdk.readJsonFileWithFallback(wecomConfigPath, {});
|
|
1057
|
+
// 确保 mcpConfig 字段存在且为对象
|
|
1058
|
+
if (!pluginJson.mcpConfig || typeof pluginJson.mcpConfig !== 'object') {
|
|
1059
|
+
pluginJson.mcpConfig = {};
|
|
1060
|
+
}
|
|
1061
|
+
// 使用 type 作为键存储配置
|
|
1062
|
+
const typeKey = config.type || 'default';
|
|
1063
|
+
pluginJson.mcpConfig[typeKey] = {
|
|
1064
|
+
type: config.type,
|
|
1065
|
+
url: config.url,
|
|
1066
|
+
};
|
|
1067
|
+
// 原子写入
|
|
1068
|
+
await pluginSdk.writeJsonFileAtomically(wecomConfigPath, pluginJson);
|
|
1069
|
+
runtime.log?.(`[WeCom] MCP config saved to ${wecomConfigPath}`);
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
// ============================================================================
|
|
1073
|
+
// 组合入口
|
|
1074
|
+
// ============================================================================
|
|
1075
|
+
/**
|
|
1076
|
+
* 拉取 MCP 配置并持久化到 openclaw.plugin.json
|
|
1077
|
+
*
|
|
1078
|
+
* 认证成功后调用。失败仅记录日志,不影响 WebSocket 消息正常收发。
|
|
1079
|
+
*
|
|
1080
|
+
* @param wsClient - 已认证的 WSClient 实例
|
|
1081
|
+
* @param accountId - 账户 ID(用于日志)
|
|
1082
|
+
* @param runtime - 运行时环境(用于日志)
|
|
1083
|
+
*/
|
|
1084
|
+
async function fetchAndSaveMcpConfig(wsClient, accountId, runtime) {
|
|
1085
|
+
try {
|
|
1086
|
+
runtime.log?.(`[${accountId}] Fetching MCP config...`);
|
|
1087
|
+
const config = await fetchMcpConfig(wsClient);
|
|
1088
|
+
runtime.log?.(`[${accountId}] MCP config fetched: url=${config.url}, type=${config.type ?? "N/A"}, is_authed=${config.is_authed ?? "N/A"}`);
|
|
1089
|
+
await saveMcpConfigToPluginJson(config, runtime);
|
|
1090
|
+
}
|
|
1091
|
+
catch (err) {
|
|
1092
|
+
runtime.error?.(`[${accountId}] Failed to fetch/save MCP config: ${String(err)}`);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
946
1096
|
/**
|
|
947
1097
|
* 企业微信 WebSocket 监控器主模块
|
|
948
1098
|
*
|
|
@@ -1278,6 +1428,8 @@ async function monitorWeComProvider(options) {
|
|
|
1278
1428
|
wsClient.on("authenticated", () => {
|
|
1279
1429
|
runtime.log?.(`[${account.accountId}] Authentication successful`);
|
|
1280
1430
|
setWeComWebSocket(account.accountId, wsClient);
|
|
1431
|
+
// 认证成功后自动拉取 MCP 配置(异步,失败不影响主流程)
|
|
1432
|
+
fetchAndSaveMcpConfig(wsClient, account.accountId, runtime);
|
|
1281
1433
|
});
|
|
1282
1434
|
// 监听断开事件
|
|
1283
1435
|
wsClient.on("disconnected", (reason) => {
|
|
@@ -1431,7 +1583,7 @@ const dmPolicy = {
|
|
|
1431
1583
|
allowFromKey: `channels.${CHANNEL_ID}.allowFrom`,
|
|
1432
1584
|
getCurrent: (cfg) => {
|
|
1433
1585
|
const account = resolveWeComAccount(cfg);
|
|
1434
|
-
return account.config.dmPolicy ?? "
|
|
1586
|
+
return account.config.dmPolicy ?? "open";
|
|
1435
1587
|
},
|
|
1436
1588
|
setPolicy: (cfg, policy) => {
|
|
1437
1589
|
return setWeComDmPolicy(cfg, policy);
|
|
@@ -1477,7 +1629,7 @@ const wecomOnboardingAdapter = {
|
|
|
1477
1629
|
botId,
|
|
1478
1630
|
secret,
|
|
1479
1631
|
enabled: true,
|
|
1480
|
-
dmPolicy: account.config.dmPolicy ?? "
|
|
1632
|
+
dmPolicy: account.config.dmPolicy ?? "open",
|
|
1481
1633
|
allowFrom: account.config.allowFrom ?? [],
|
|
1482
1634
|
});
|
|
1483
1635
|
return { cfg: cfgWithAccount };
|
|
@@ -1613,7 +1765,7 @@ const wecomPlugin = {
|
|
|
1613
1765
|
resolveDmPolicy: ({ account }) => {
|
|
1614
1766
|
const basePath = `channels.${CHANNEL_ID}.`;
|
|
1615
1767
|
return {
|
|
1616
|
-
policy: account.config.dmPolicy ?? "
|
|
1768
|
+
policy: account.config.dmPolicy ?? "open",
|
|
1617
1769
|
allowFrom: account.config.allowFrom ?? [],
|
|
1618
1770
|
policyPath: `${basePath}dmPolicy`,
|
|
1619
1771
|
allowFromPath: basePath,
|
|
@@ -1624,7 +1776,7 @@ const wecomPlugin = {
|
|
|
1624
1776
|
collectWarnings: ({ account, cfg }) => {
|
|
1625
1777
|
const warnings = [];
|
|
1626
1778
|
// DM 策略警告
|
|
1627
|
-
const dmPolicy = account.config.dmPolicy ?? "
|
|
1779
|
+
const dmPolicy = account.config.dmPolicy ?? "open";
|
|
1628
1780
|
if (dmPolicy === "open") {
|
|
1629
1781
|
const hasWildcard = (account.config.allowFrom ?? []).some((entry) => String(entry).trim() === "*");
|
|
1630
1782
|
if (!hasWildcard) {
|
|
@@ -1666,7 +1818,7 @@ const wecomPlugin = {
|
|
|
1666
1818
|
listGroups: async () => [],
|
|
1667
1819
|
},
|
|
1668
1820
|
outbound: {
|
|
1669
|
-
deliveryMode: "
|
|
1821
|
+
deliveryMode: "gateway",
|
|
1670
1822
|
chunker: (text, limit) => getWeComRuntime().channel.text.chunkMarkdownText(text, limit),
|
|
1671
1823
|
textChunkLimit: TEXT_CHUNK_LIMIT,
|
|
1672
1824
|
sendText: async ({ to, text, accountId, ...rest }) => {
|