@sunnoy/wecom 2.1.0 → 2.2.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.
Files changed (43) hide show
  1. package/README.md +6 -2
  2. package/index.js +2 -0
  3. package/openclaw.plugin.json +3 -0
  4. package/package.json +5 -3
  5. package/skills/wecom-contact-lookup/SKILL.md +167 -0
  6. package/skills/wecom-doc-manager/SKILL.md +106 -0
  7. package/skills/wecom-doc-manager/references/api-create-doc.md +56 -0
  8. package/skills/wecom-doc-manager/references/api-edit-doc-content.md +68 -0
  9. package/skills/wecom-doc-manager/references/api-export-document.md +88 -0
  10. package/skills/wecom-edit-todo/SKILL.md +254 -0
  11. package/skills/wecom-get-todo-detail/SKILL.md +148 -0
  12. package/skills/wecom-get-todo-list/SKILL.md +132 -0
  13. package/skills/wecom-meeting-create/SKILL.md +163 -0
  14. package/skills/wecom-meeting-create/references/example-full.md +30 -0
  15. package/skills/wecom-meeting-create/references/example-reminder.md +46 -0
  16. package/skills/wecom-meeting-create/references/example-security.md +22 -0
  17. package/skills/wecom-meeting-manage/SKILL.md +141 -0
  18. package/skills/wecom-meeting-query/SKILL.md +335 -0
  19. package/skills/wecom-preflight/SKILL.md +103 -0
  20. package/skills/wecom-schedule/SKILL.md +164 -0
  21. package/skills/wecom-schedule/references/api-check-availability.md +56 -0
  22. package/skills/wecom-schedule/references/api-create-schedule.md +38 -0
  23. package/skills/wecom-schedule/references/api-get-schedule-detail.md +81 -0
  24. package/skills/wecom-schedule/references/api-update-schedule.md +30 -0
  25. package/skills/wecom-schedule/references/ref-reminders.md +24 -0
  26. package/skills/wecom-smartsheet-data/SKILL.md +76 -0
  27. package/skills/wecom-smartsheet-data/references/api-get-records.md +61 -0
  28. package/skills/wecom-smartsheet-data/references/cell-value-formats.md +120 -0
  29. package/skills/wecom-smartsheet-schema/SKILL.md +96 -0
  30. package/skills/wecom-smartsheet-schema/references/field-types.md +43 -0
  31. package/wecom/accounts.js +1 -0
  32. package/wecom/callback-inbound.js +133 -33
  33. package/wecom/channel-plugin.js +107 -125
  34. package/wecom/constants.js +83 -3
  35. package/wecom/mcp-config.js +146 -0
  36. package/wecom/mcp-tool.js +660 -0
  37. package/wecom/media-uploader.js +208 -0
  38. package/wecom/openclaw-compat.js +302 -0
  39. package/wecom/reqid-store.js +146 -0
  40. package/wecom/target.js +3 -2
  41. package/wecom/workspace-template.js +107 -21
  42. package/wecom/ws-monitor.js +778 -328
  43. package/image-processor.js +0 -175
@@ -26,11 +26,91 @@ export const REQID_FLUSH_DEBOUNCE_MS = 1_000;
26
26
  export const PENDING_REPLY_TTL_MS = 5 * 60 * 1000;
27
27
  export const PENDING_REPLY_MAX_SIZE = 50;
28
28
 
29
+ // WeCom stream messages expire ~6 minutes after creation. Rotate the stream
30
+ // before hitting that hard limit so the user never sees a dead stream.
31
+ export const STREAM_MAX_LIFETIME_MS = 5 * 60 * 1000;
32
+
33
+ export const IMAGE_MAX_BYTES = 10 * 1024 * 1024;
34
+ export const VIDEO_MAX_BYTES = 10 * 1024 * 1024;
35
+ export const VOICE_MAX_BYTES = 2 * 1024 * 1024;
36
+ export const FILE_MAX_BYTES = 20 * 1024 * 1024;
37
+ export const ABSOLUTE_MAX_BYTES = FILE_MAX_BYTES;
38
+
29
39
  export const DEFAULT_MEDIA_MAX_MB = 5;
30
40
  export const TEXT_CHUNK_LIMIT = 4000;
31
- export const DEFAULT_WELCOME_MESSAGE = ["你好,我是 AI 助手。", "", "可用命令:", "/new", "/compact", "/help", "/status"].join(
32
- "\n",
33
- );
41
+ export const DEFAULT_WELCOME_MESSAGES = [
42
+ [
43
+ "新的一天,元气满满!🌞",
44
+ "",
45
+ "你可以通过斜杠指令管理会话:",
46
+ "/new 新建对话",
47
+ "/compact 压缩对话",
48
+ "/help 帮助",
49
+ "/status 查看状态",
50
+ "/reasoning stream 打开思考动画",
51
+ ].join("\n"),
52
+ [
53
+ "终于唤醒我啦,我已经准备就绪!😄",
54
+ "",
55
+ "试试这些常用指令:",
56
+ "/new 新建对话",
57
+ "/compact 压缩对话",
58
+ "/help 帮助",
59
+ "/status 查看状态",
60
+ "/reasoning stream 打开思考动画",
61
+ ].join("\n"),
62
+ [
63
+ "欢迎回来,准备开始今天的工作吧!✨",
64
+ "",
65
+ "会话管理指令:",
66
+ "/new 新建对话",
67
+ "/compact 压缩对话",
68
+ "/help 帮助",
69
+ "/status 查看状态",
70
+ "/reasoning stream 打开思考动画",
71
+ ].join("\n"),
72
+ [
73
+ "嗨,我已经在线!🤖",
74
+ "",
75
+ "你可以先试试这些命令:",
76
+ "/new 新建对话",
77
+ "/compact 压缩对话",
78
+ "/help 帮助",
79
+ "/status 查看状态",
80
+ "/reasoning stream 打开思考动画",
81
+ ].join("\n"),
82
+ [
83
+ "今天也一起高效开工吧!🚀",
84
+ "",
85
+ "先来看看这些指令:",
86
+ "/new 新建对话",
87
+ "/compact 压缩对话",
88
+ "/help 帮助",
89
+ "/status 查看状态",
90
+ "/reasoning stream 打开思考动画",
91
+ ].join("\n"),
92
+ [
93
+ "叮咚,你的数字助手已就位!🎉",
94
+ "",
95
+ "常用操作给你备好了:",
96
+ "/new 新建对话",
97
+ "/compact 压缩对话",
98
+ "/help 帮助",
99
+ "/status 查看状态",
100
+ "/reasoning stream 打开思考动画",
101
+ ].join("\n"),
102
+ [
103
+ "灵感加载完成,随时可以开聊!💡",
104
+ "",
105
+ "你可以这样开始:",
106
+ "/new 新建对话",
107
+ "/compact 压缩对话",
108
+ "/help 帮助",
109
+ "/status 查看状态",
110
+ "/reasoning stream 打开思考动画",
111
+ ].join("\n"),
112
+ ];
113
+ export const DEFAULT_WELCOME_MESSAGE = DEFAULT_WELCOME_MESSAGES[0];
34
114
 
35
115
  export const MEDIA_CACHE_DIR = join(process.env.HOME || "/tmp", ".openclaw", "media", "wecom");
36
116
 
@@ -0,0 +1,146 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
4
+ import { generateReqId } from "@wecom/aibot-node-sdk";
5
+ import { logger } from "../logger.js";
6
+
7
+ const MCP_GET_CONFIG_CMD = "aibot_get_mcp_config";
8
+ const MCP_CONFIG_FETCH_TIMEOUT_MS = 15_000;
9
+ const MCP_CONFIG_KEY = "doc";
10
+ const DEFAULT_MCP_TRANSPORT = "streamable-http";
11
+
12
+ let mcpConfigWriteQueue = Promise.resolve();
13
+
14
+ function withTimeout(promise, timeoutMs, message) {
15
+ if (!timeoutMs || !Number.isFinite(timeoutMs) || timeoutMs <= 0) {
16
+ return promise;
17
+ }
18
+
19
+ let timer = null;
20
+ const timeout = new Promise((_, reject) => {
21
+ timer = setTimeout(() => reject(new Error(message ?? `Timed out after ${timeoutMs}ms`)), timeoutMs);
22
+ });
23
+
24
+ promise.catch(() => {});
25
+
26
+ return Promise.race([promise, timeout]).finally(() => {
27
+ if (timer) {
28
+ clearTimeout(timer);
29
+ }
30
+ });
31
+ }
32
+
33
+ function getWecomConfigPath() {
34
+ return path.join(os.homedir(), ".openclaw", "wecomConfig", "config.json");
35
+ }
36
+
37
+ function resolveMcpTransport(body = {}) {
38
+ const candidate = String(
39
+ body.transport_type ??
40
+ body.transportType ??
41
+ body.config_type ??
42
+ body.configType ??
43
+ body.type ??
44
+ "",
45
+ )
46
+ .trim()
47
+ .toLowerCase();
48
+
49
+ return candidate || DEFAULT_MCP_TRANSPORT;
50
+ }
51
+
52
+ async function readJsonFile(filePath, fallback = {}) {
53
+ try {
54
+ const raw = await readFile(filePath, "utf8");
55
+ return JSON.parse(raw);
56
+ } catch (error) {
57
+ if (error?.code === "ENOENT") {
58
+ return fallback;
59
+ }
60
+ throw error;
61
+ }
62
+ }
63
+
64
+ async function writeJsonFileAtomically(filePath, value) {
65
+ const dir = path.dirname(filePath);
66
+ await mkdir(dir, { recursive: true });
67
+ const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
68
+ await writeFile(tempPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
69
+ await rename(tempPath, filePath);
70
+ }
71
+
72
+ export async function fetchMcpConfig(wsClient) {
73
+ if (!wsClient || typeof wsClient.reply !== "function") {
74
+ throw new Error("WS client does not support MCP config requests");
75
+ }
76
+
77
+ const reqId = generateReqId("mcp_config");
78
+ const response = await withTimeout(
79
+ wsClient.reply({ headers: { req_id: reqId } }, { biz_type: MCP_CONFIG_KEY }, MCP_GET_CONFIG_CMD),
80
+ MCP_CONFIG_FETCH_TIMEOUT_MS,
81
+ `MCP config fetch timed out after ${MCP_CONFIG_FETCH_TIMEOUT_MS}ms`,
82
+ );
83
+
84
+ if (response?.errcode && response.errcode !== 0) {
85
+ throw new Error(`MCP config request failed: errcode=${response.errcode}, errmsg=${response.errmsg ?? "unknown"}`);
86
+ }
87
+
88
+ const body = response?.body;
89
+ if (!body?.url) {
90
+ throw new Error("MCP config response missing required 'url' field");
91
+ }
92
+
93
+ return {
94
+ key: MCP_CONFIG_KEY,
95
+ type: resolveMcpTransport(body),
96
+ url: body.url,
97
+ isAuthed: body.is_authed,
98
+ };
99
+ }
100
+
101
+ export async function saveMcpConfig(config, runtime) {
102
+ const configPath = getWecomConfigPath();
103
+
104
+ const saveTask = mcpConfigWriteQueue.then(async () => {
105
+ const current = await readJsonFile(configPath, {});
106
+ if (!current.mcpConfig || typeof current.mcpConfig !== "object") {
107
+ current.mcpConfig = {};
108
+ }
109
+
110
+ current.mcpConfig[config.key || MCP_CONFIG_KEY] = {
111
+ type: config.type,
112
+ url: config.url,
113
+ };
114
+
115
+ await writeJsonFileAtomically(configPath, current);
116
+ runtime?.log?.(`[WeCom] MCP config saved to ${configPath}`);
117
+ });
118
+
119
+ mcpConfigWriteQueue = saveTask.catch(() => {});
120
+ return saveTask;
121
+ }
122
+
123
+ export async function fetchAndSaveMcpConfig(wsClient, accountId, runtime) {
124
+ try {
125
+ runtime?.log?.(`[${accountId}] Fetching MCP config...`);
126
+ const config = await fetchMcpConfig(wsClient);
127
+ runtime?.log?.(
128
+ `[${accountId}] MCP config fetched: url=${config.url}, type=${config.type}, is_authed=${config.isAuthed ?? "N/A"}`,
129
+ );
130
+ await saveMcpConfig(config, runtime);
131
+ } catch (error) {
132
+ if (typeof wsClient?.reply !== "function") {
133
+ logger.debug?.(`[${accountId}] Skipping MCP config fetch because WS client has no reply() support`);
134
+ return;
135
+ }
136
+ runtime?.error?.(`[${accountId}] Failed to fetch/save MCP config: ${String(error)}`);
137
+ }
138
+ }
139
+
140
+ export const mcpConfigTesting = {
141
+ getWecomConfigPath,
142
+ resolveMcpTransport,
143
+ resetWriteQueue() {
144
+ mcpConfigWriteQueue = Promise.resolve();
145
+ },
146
+ };