@wecom/wecom-openclaw-plugin 2026.4.2 → 2026.4.3-beta.0

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 (215) hide show
  1. package/README.md +395 -10
  2. package/dist/index.d.ts +2 -4
  3. package/dist/index.js +58 -0
  4. package/dist/src/accounts.d.ts +57 -0
  5. package/dist/src/accounts.js +222 -0
  6. package/dist/src/agent/api-client.d.ts +94 -0
  7. package/dist/src/agent/api-client.js +292 -0
  8. package/dist/src/agent/handler.d.ts +63 -0
  9. package/dist/src/agent/handler.js +551 -0
  10. package/dist/src/agent/index.d.ts +5 -0
  11. package/dist/src/agent/index.js +5 -0
  12. package/dist/src/agent/webhook.d.ts +24 -0
  13. package/dist/src/agent/webhook.js +228 -0
  14. package/dist/src/agent/xml.d.ts +21 -0
  15. package/dist/src/agent/xml.js +41 -0
  16. package/dist/src/channel.js +579 -0
  17. package/dist/src/chat-queue.d.ts +31 -0
  18. package/dist/src/chat-queue.js +45 -0
  19. package/dist/{esm/types/src → src}/const.d.ts +46 -0
  20. package/dist/{esm/src → src}/const.js +77 -30
  21. package/dist/{esm/src → src}/dm-policy.js +6 -9
  22. package/dist/src/dynamic-agent.d.ts +37 -0
  23. package/dist/src/dynamic-agent.js +60 -0
  24. package/dist/src/dynamic-routing.d.ts +65 -0
  25. package/dist/src/dynamic-routing.js +58 -0
  26. package/dist/{esm/src → src}/group-policy.js +8 -22
  27. package/dist/src/http.d.ts +26 -0
  28. package/dist/src/http.js +93 -0
  29. package/dist/{esm/types/src → src}/interface.d.ts +0 -2
  30. package/dist/src/interface.js +4 -0
  31. package/dist/src/mcp/index.js +6 -0
  32. package/dist/{esm/src → src}/mcp/interceptors/biz-error.js +2 -6
  33. package/dist/{esm/src → src}/mcp/interceptors/index.js +6 -10
  34. package/dist/{esm/src → src}/mcp/interceptors/msg-media.js +6 -10
  35. package/dist/{esm/src → src}/mcp/interceptors/smartpage-create.js +2 -6
  36. package/dist/{esm/src → src}/mcp/interceptors/smartpage-export.js +2 -6
  37. package/dist/src/mcp/interceptors/types.js +7 -0
  38. package/dist/{esm/src → src}/mcp/schema.js +1 -4
  39. package/dist/{esm/src → src}/mcp/tool.js +4 -8
  40. package/dist/{esm/src → src}/mcp/transport.js +15 -16
  41. package/dist/{esm/src → src}/media-handler.js +6 -10
  42. package/dist/{esm/src → src}/media-uploader.js +64 -10
  43. package/dist/{esm/src → src}/message-parser.js +1 -4
  44. package/dist/{esm/types/src → src}/message-sender.d.ts +18 -0
  45. package/dist/{esm/src → src}/message-sender.js +45 -11
  46. package/dist/{esm/src → src}/monitor.js +145 -292
  47. package/dist/{esm/src → src}/onboarding.js +22 -26
  48. package/dist/{esm/types/src → src}/openclaw-compat.d.ts +49 -0
  49. package/dist/{esm/src → src}/openclaw-compat.js +133 -27
  50. package/dist/{esm/src → src}/reqid-store.js +4 -7
  51. package/dist/{esm/src → src}/runtime.js +2 -5
  52. package/dist/src/shared/command-auth.d.ts +23 -0
  53. package/dist/src/shared/command-auth.js +73 -0
  54. package/dist/src/shared/xml-parser.d.ts +46 -0
  55. package/dist/src/shared/xml-parser.js +210 -0
  56. package/dist/{esm/src → src}/state-dir-resolve.js +3 -7
  57. package/dist/{esm/src → src}/state-manager.js +84 -16
  58. package/dist/src/target.d.ts +35 -0
  59. package/dist/src/target.js +67 -0
  60. package/dist/src/template-card-manager.d.ts +55 -0
  61. package/dist/src/template-card-manager.js +205 -0
  62. package/dist/{esm/src → src}/template-card-parser.js +17 -8
  63. package/dist/{esm/src → src}/timeout.js +2 -5
  64. package/dist/src/types/account.d.ts +29 -0
  65. package/dist/src/types/account.js +4 -0
  66. package/dist/src/types/config.d.ts +91 -0
  67. package/dist/src/types/config.js +7 -0
  68. package/dist/src/types/constants.d.ts +42 -0
  69. package/dist/src/types/constants.js +42 -0
  70. package/dist/src/types/index.d.ts +7 -0
  71. package/dist/src/types/index.js +5 -0
  72. package/dist/src/types/message.d.ts +238 -0
  73. package/dist/src/types/message.js +5 -0
  74. package/dist/{esm/types/src → src}/utils.d.ts +41 -0
  75. package/dist/{esm/src → src}/utils.js +24 -10
  76. package/dist/src/version.d.ts +2 -0
  77. package/dist/src/version.js +24 -0
  78. package/dist/src/webhook/command-auth.d.ts +47 -0
  79. package/dist/src/webhook/command-auth.js +96 -0
  80. package/dist/src/webhook/gateway.d.ts +36 -0
  81. package/dist/src/webhook/gateway.js +235 -0
  82. package/dist/src/webhook/handler.d.ts +18 -0
  83. package/dist/src/webhook/handler.js +420 -0
  84. package/dist/src/webhook/helpers.d.ts +156 -0
  85. package/dist/src/webhook/helpers.js +833 -0
  86. package/dist/src/webhook/http.d.ts +26 -0
  87. package/dist/src/webhook/http.js +94 -0
  88. package/dist/src/webhook/index.d.ts +11 -0
  89. package/dist/src/webhook/index.js +14 -0
  90. package/dist/src/webhook/media.d.ts +29 -0
  91. package/dist/src/webhook/media.js +88 -0
  92. package/dist/src/webhook/monitor.d.ts +59 -0
  93. package/dist/src/webhook/monitor.js +1161 -0
  94. package/dist/src/webhook/state.d.ts +220 -0
  95. package/dist/src/webhook/state.js +491 -0
  96. package/dist/src/webhook/target.d.ts +41 -0
  97. package/dist/src/webhook/target.js +132 -0
  98. package/dist/src/webhook/types.d.ts +347 -0
  99. package/dist/src/webhook/types.js +33 -0
  100. package/dist/src/webhook/video-frame.d.ts +13 -0
  101. package/dist/src/webhook/video-frame.js +30 -0
  102. package/package.json +15 -7
  103. package/dist/cjs/index.js +0 -45
  104. package/dist/cjs/index.js.map +0 -1
  105. package/dist/cjs/src/channel.js +0 -339
  106. package/dist/cjs/src/channel.js.map +0 -1
  107. package/dist/cjs/src/const.js +0 -137
  108. package/dist/cjs/src/const.js.map +0 -1
  109. package/dist/cjs/src/dm-policy.js +0 -89
  110. package/dist/cjs/src/dm-policy.js.map +0 -1
  111. package/dist/cjs/src/group-policy.js +0 -133
  112. package/dist/cjs/src/group-policy.js.map +0 -1
  113. package/dist/cjs/src/mcp/interceptors/biz-error.js +0 -70
  114. package/dist/cjs/src/mcp/interceptors/biz-error.js.map +0 -1
  115. package/dist/cjs/src/mcp/interceptors/index.js +0 -73
  116. package/dist/cjs/src/mcp/interceptors/index.js.map +0 -1
  117. package/dist/cjs/src/mcp/interceptors/msg-media.js +0 -159
  118. package/dist/cjs/src/mcp/interceptors/msg-media.js.map +0 -1
  119. package/dist/cjs/src/mcp/interceptors/smartpage-create.js +0 -157
  120. package/dist/cjs/src/mcp/interceptors/smartpage-create.js.map +0 -1
  121. package/dist/cjs/src/mcp/interceptors/smartpage-export.js +0 -93
  122. package/dist/cjs/src/mcp/interceptors/smartpage-export.js.map +0 -1
  123. package/dist/cjs/src/mcp/schema.js +0 -104
  124. package/dist/cjs/src/mcp/schema.js.map +0 -1
  125. package/dist/cjs/src/mcp/tool.js +0 -208
  126. package/dist/cjs/src/mcp/tool.js.map +0 -1
  127. package/dist/cjs/src/mcp/transport.js +0 -417
  128. package/dist/cjs/src/mcp/transport.js.map +0 -1
  129. package/dist/cjs/src/media-handler.js +0 -129
  130. package/dist/cjs/src/media-handler.js.map +0 -1
  131. package/dist/cjs/src/media-uploader.js +0 -284
  132. package/dist/cjs/src/media-uploader.js.map +0 -1
  133. package/dist/cjs/src/message-parser.js +0 -134
  134. package/dist/cjs/src/message-parser.js.map +0 -1
  135. package/dist/cjs/src/message-sender.js +0 -89
  136. package/dist/cjs/src/message-sender.js.map +0 -1
  137. package/dist/cjs/src/monitor.js +0 -991
  138. package/dist/cjs/src/monitor.js.map +0 -1
  139. package/dist/cjs/src/onboarding.js +0 -182
  140. package/dist/cjs/src/onboarding.js.map +0 -1
  141. package/dist/cjs/src/openclaw-compat.js +0 -400
  142. package/dist/cjs/src/openclaw-compat.js.map +0 -1
  143. package/dist/cjs/src/reqid-store.js +0 -88
  144. package/dist/cjs/src/reqid-store.js.map +0 -1
  145. package/dist/cjs/src/runtime.js +0 -16
  146. package/dist/cjs/src/runtime.js.map +0 -1
  147. package/dist/cjs/src/state-dir-resolve.js +0 -35
  148. package/dist/cjs/src/state-dir-resolve.js.map +0 -1
  149. package/dist/cjs/src/state-manager.js +0 -158
  150. package/dist/cjs/src/state-manager.js.map +0 -1
  151. package/dist/cjs/src/template-card-parser.js +0 -641
  152. package/dist/cjs/src/template-card-parser.js.map +0 -1
  153. package/dist/cjs/src/timeout.js +0 -42
  154. package/dist/cjs/src/timeout.js.map +0 -1
  155. package/dist/cjs/src/utils.js +0 -60
  156. package/dist/cjs/src/utils.js.map +0 -1
  157. package/dist/cjs/src/version.js +0 -26
  158. package/dist/cjs/src/version.js.map +0 -1
  159. package/dist/esm/index.js +0 -41
  160. package/dist/esm/index.js.map +0 -1
  161. package/dist/esm/src/channel.js +0 -337
  162. package/dist/esm/src/channel.js.map +0 -1
  163. package/dist/esm/src/const.js.map +0 -1
  164. package/dist/esm/src/dm-policy.js.map +0 -1
  165. package/dist/esm/src/group-policy.js.map +0 -1
  166. package/dist/esm/src/mcp/interceptors/biz-error.js.map +0 -1
  167. package/dist/esm/src/mcp/interceptors/index.js.map +0 -1
  168. package/dist/esm/src/mcp/interceptors/msg-media.js.map +0 -1
  169. package/dist/esm/src/mcp/interceptors/smartpage-create.js.map +0 -1
  170. package/dist/esm/src/mcp/interceptors/smartpage-export.js.map +0 -1
  171. package/dist/esm/src/mcp/schema.js.map +0 -1
  172. package/dist/esm/src/mcp/tool.js.map +0 -1
  173. package/dist/esm/src/mcp/transport.js.map +0 -1
  174. package/dist/esm/src/media-handler.js.map +0 -1
  175. package/dist/esm/src/media-uploader.js.map +0 -1
  176. package/dist/esm/src/message-parser.js.map +0 -1
  177. package/dist/esm/src/message-sender.js.map +0 -1
  178. package/dist/esm/src/monitor.js.map +0 -1
  179. package/dist/esm/src/onboarding.js.map +0 -1
  180. package/dist/esm/src/openclaw-compat.js.map +0 -1
  181. package/dist/esm/src/reqid-store.js.map +0 -1
  182. package/dist/esm/src/runtime.js.map +0 -1
  183. package/dist/esm/src/state-dir-resolve.js.map +0 -1
  184. package/dist/esm/src/state-manager.js.map +0 -1
  185. package/dist/esm/src/template-card-parser.js.map +0 -1
  186. package/dist/esm/src/timeout.js.map +0 -1
  187. package/dist/esm/src/utils.js.map +0 -1
  188. package/dist/esm/src/version.js +0 -23
  189. package/dist/esm/src/version.js.map +0 -1
  190. package/dist/esm/types/index.d.ts +0 -9
  191. package/dist/esm/types/src/version.d.ts +0 -2
  192. /package/dist/{esm/types/src → src}/channel.d.ts +0 -0
  193. /package/dist/{esm/types/src → src}/dm-policy.d.ts +0 -0
  194. /package/dist/{esm/types/src → src}/group-policy.d.ts +0 -0
  195. /package/dist/{esm/types/src → src}/mcp/index.d.ts +0 -0
  196. /package/dist/{esm/types/src → src}/mcp/interceptors/biz-error.d.ts +0 -0
  197. /package/dist/{esm/types/src → src}/mcp/interceptors/index.d.ts +0 -0
  198. /package/dist/{esm/types/src → src}/mcp/interceptors/msg-media.d.ts +0 -0
  199. /package/dist/{esm/types/src → src}/mcp/interceptors/smartpage-create.d.ts +0 -0
  200. /package/dist/{esm/types/src → src}/mcp/interceptors/smartpage-export.d.ts +0 -0
  201. /package/dist/{esm/types/src → src}/mcp/interceptors/types.d.ts +0 -0
  202. /package/dist/{esm/types/src → src}/mcp/schema.d.ts +0 -0
  203. /package/dist/{esm/types/src → src}/mcp/tool.d.ts +0 -0
  204. /package/dist/{esm/types/src → src}/mcp/transport.d.ts +0 -0
  205. /package/dist/{esm/types/src → src}/media-handler.d.ts +0 -0
  206. /package/dist/{esm/types/src → src}/media-uploader.d.ts +0 -0
  207. /package/dist/{esm/types/src → src}/message-parser.d.ts +0 -0
  208. /package/dist/{esm/types/src → src}/monitor.d.ts +0 -0
  209. /package/dist/{esm/types/src → src}/onboarding.d.ts +0 -0
  210. /package/dist/{esm/types/src → src}/reqid-store.d.ts +0 -0
  211. /package/dist/{esm/types/src → src}/runtime.d.ts +0 -0
  212. /package/dist/{esm/types/src → src}/state-dir-resolve.d.ts +0 -0
  213. /package/dist/{esm/types/src → src}/state-manager.d.ts +0 -0
  214. /package/dist/{esm/types/src → src}/template-card-parser.d.ts +0 -0
  215. /package/dist/{esm/types/src → src}/timeout.d.ts +0 -0
@@ -0,0 +1,210 @@
1
+ /**
2
+ * WeCom XML 解析器
3
+ * 用于 Agent 模式解析 XML 格式消息
4
+ */
5
+ import { XMLParser } from "fast-xml-parser";
6
+ const xmlParser = new XMLParser({
7
+ ignoreAttributes: false,
8
+ trimValues: true,
9
+ processEntities: false,
10
+ parseTagValue: false,
11
+ parseAttributeValue: false,
12
+ });
13
+ /**
14
+ * 解析 XML 字符串为消息对象
15
+ */
16
+ export function parseXml(xml) {
17
+ const obj = xmlParser.parse(xml);
18
+ const root = obj?.xml ?? obj;
19
+ return root ?? {};
20
+ }
21
+ /**
22
+ * 从 XML 中提取消息类型
23
+ */
24
+ export function extractMsgType(msg) {
25
+ return String(msg.MsgType ?? "").toLowerCase();
26
+ }
27
+ /**
28
+ * 从 XML 中提取发送者 ID
29
+ */
30
+ export function extractFromUser(msg) {
31
+ return String(msg.FromUserName ?? "");
32
+ }
33
+ /**
34
+ * 从 XML 中提取文件名(主要用于 file 消息)
35
+ */
36
+ export function extractFileName(msg) {
37
+ const raw = msg.FileName ?? msg.Filename ?? msg.fileName ?? msg.filename;
38
+ if (raw == null)
39
+ return undefined;
40
+ if (typeof raw === "string")
41
+ return raw.trim() || undefined;
42
+ if (typeof raw === "number" || typeof raw === "boolean" || typeof raw === "bigint")
43
+ return String(raw);
44
+ if (Array.isArray(raw)) {
45
+ const merged = raw.map((v) => (v == null ? "" : String(v))).join("\n").trim();
46
+ return merged || undefined;
47
+ }
48
+ if (typeof raw === "object") {
49
+ const obj = raw;
50
+ const text = (typeof obj["#text"] === "string" ? obj["#text"] :
51
+ typeof obj["_text"] === "string" ? obj["_text"] :
52
+ typeof obj["text"] === "string" ? obj["text"] : undefined);
53
+ if (text && text.trim())
54
+ return text.trim();
55
+ }
56
+ const s = String(raw);
57
+ return s.trim() || undefined;
58
+ }
59
+ /**
60
+ * 从 XML 中提取接收者 ID (CorpID)
61
+ */
62
+ export function extractToUser(msg) {
63
+ return String(msg.ToUserName ?? "");
64
+ }
65
+ /**
66
+ * 从 XML 中提取群聊 ID
67
+ */
68
+ export function extractChatId(msg) {
69
+ return msg.ChatId ? String(msg.ChatId) : undefined;
70
+ }
71
+ /**
72
+ * 从 XML 中提取 AgentID(兼容 AgentID/agentid 等大小写)
73
+ */
74
+ export function extractAgentId(msg) {
75
+ const raw = msg.AgentID ??
76
+ msg.AgentId ??
77
+ msg.agentid ??
78
+ msg.agentId;
79
+ if (raw == null)
80
+ return undefined;
81
+ if (typeof raw === "string")
82
+ return raw.trim() || undefined;
83
+ if (typeof raw === "number")
84
+ return raw;
85
+ const asString = String(raw).trim();
86
+ return asString || undefined;
87
+ }
88
+ /**
89
+ * 从 XML 中提取消息内容
90
+ */
91
+ export function extractContent(msg) {
92
+ const msgType = extractMsgType(msg);
93
+ const asText = (value) => {
94
+ if (value == null)
95
+ return "";
96
+ if (typeof value === "string")
97
+ return value;
98
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint")
99
+ return String(value);
100
+ if (Array.isArray(value))
101
+ return value.map(asText).filter(Boolean).join("\n");
102
+ if (typeof value === "object") {
103
+ const obj = value;
104
+ // fast-xml-parser 在某些情况下(例如带属性)会把文本放在 "#text"
105
+ if (typeof obj["#text"] === "string")
106
+ return obj["#text"];
107
+ if (typeof obj["_text"] === "string")
108
+ return obj["_text"];
109
+ if (typeof obj["text"] === "string")
110
+ return obj["text"];
111
+ try {
112
+ return JSON.stringify(obj);
113
+ }
114
+ catch {
115
+ return String(value);
116
+ }
117
+ }
118
+ return String(value);
119
+ };
120
+ switch (msgType) {
121
+ case "text":
122
+ return asText(msg.Content);
123
+ case "voice":
124
+ // 语音识别结果
125
+ return asText(msg.Recognition) || "[语音消息]";
126
+ case "image":
127
+ return `[图片] ${asText(msg.PicUrl)}`;
128
+ case "file":
129
+ return "[文件消息]";
130
+ case "video":
131
+ return "[视频消息]";
132
+ case "location":
133
+ return `[位置] ${asText(msg.Label)} (${asText(msg.Location_X)}, ${asText(msg.Location_Y)})`;
134
+ case "link":
135
+ return `[链接] ${asText(msg.Title)}\n${asText(msg.Description)}\n${asText(msg.Url)}`;
136
+ case "event":
137
+ return `[事件] ${asText(msg.Event)} - ${asText(msg.EventKey)}`;
138
+ default:
139
+ return `[${msgType || "未知消息类型"}]`;
140
+ }
141
+ }
142
+ /**
143
+ * 从 XML 中提取媒体 ID (Image, Voice, Video)
144
+ * 根据官方文档,MediaId 在 Agent 回调中直接位于根节点
145
+ */
146
+ export function extractMediaId(msg) {
147
+ const raw = msg.MediaId ?? msg.MediaID ?? msg.mediaid ?? msg.mediaId;
148
+ if (raw == null)
149
+ return undefined;
150
+ if (typeof raw === "string")
151
+ return raw.trim() || undefined;
152
+ if (typeof raw === "number" || typeof raw === "boolean" || typeof raw === "bigint")
153
+ return String(raw);
154
+ if (Array.isArray(raw)) {
155
+ const merged = raw.map((v) => (v == null ? "" : String(v))).join("\n").trim();
156
+ return merged || undefined;
157
+ }
158
+ if (typeof raw === "object") {
159
+ const obj = raw;
160
+ const text = (typeof obj["#text"] === "string" ? obj["#text"] :
161
+ typeof obj["_text"] === "string" ? obj["_text"] :
162
+ typeof obj["text"] === "string" ? obj["text"] : undefined);
163
+ if (text && text.trim())
164
+ return text.trim();
165
+ try {
166
+ const s = JSON.stringify(obj);
167
+ return s.trim() || undefined;
168
+ }
169
+ catch {
170
+ const s = String(raw);
171
+ return s.trim() || undefined;
172
+ }
173
+ }
174
+ const s = String(raw);
175
+ return s.trim() || undefined;
176
+ }
177
+ /**
178
+ * 从 XML 中提取 MsgId(用于去重)
179
+ */
180
+ export function extractMsgId(msg) {
181
+ const raw = msg.MsgId ?? msg.MsgID ?? msg.msgid ?? msg.msgId;
182
+ if (raw == null)
183
+ return undefined;
184
+ if (typeof raw === "string")
185
+ return raw.trim() || undefined;
186
+ if (typeof raw === "number" || typeof raw === "boolean" || typeof raw === "bigint")
187
+ return String(raw);
188
+ if (Array.isArray(raw)) {
189
+ const merged = raw.map((v) => (v == null ? "" : String(v))).join("\n").trim();
190
+ return merged || undefined;
191
+ }
192
+ if (typeof raw === "object") {
193
+ const obj = raw;
194
+ const text = (typeof obj["#text"] === "string" ? obj["#text"] :
195
+ typeof obj["_text"] === "string" ? obj["_text"] :
196
+ typeof obj["text"] === "string" ? obj["text"] : undefined);
197
+ if (text && text.trim())
198
+ return text.trim();
199
+ try {
200
+ const s = JSON.stringify(obj);
201
+ return s.trim() || undefined;
202
+ }
203
+ catch {
204
+ const s = String(raw);
205
+ return s.trim() || undefined;
206
+ }
207
+ }
208
+ const s = String(raw);
209
+ return s.trim() || undefined;
210
+ }
@@ -1,13 +1,9 @@
1
- import * as path from 'node:path';
2
- import * as os from 'node:os';
3
-
1
+ import * as path from "node:path";
2
+ import * as os from "node:os";
4
3
  /** 解析 openclaw 状态目录 */
5
- function resolveStateDir() {
4
+ export function resolveStateDir() {
6
5
  const stateOverride = process.env.OPENCLAW_STATE_DIR?.trim() || process.env.CLAWDBOT_STATE_DIR?.trim();
7
6
  if (stateOverride)
8
7
  return stateOverride;
9
8
  return path.join(os.homedir(), ".openclaw");
10
9
  }
11
-
12
- export { resolveStateDir };
13
- //# sourceMappingURL=state-dir-resolve.js.map
@@ -1,12 +1,11 @@
1
- import { createPersistentReqIdStore } from './reqid-store.js';
2
- import { MESSAGE_STATE_CLEANUP_INTERVAL_MS, MESSAGE_STATE_TTL_MS, MESSAGE_STATE_MAX_SIZE } from './const.js';
3
-
4
1
  /**
5
2
  * 企业微信全局状态管理模块
6
3
  *
7
4
  * 负责管理 WSClient 实例、消息状态(带 TTL 清理)、ReqId 存储
8
5
  * 解决全局 Map 的内存泄漏问题
9
6
  */
7
+ import { createPersistentReqIdStore } from "./reqid-store.js";
8
+ import { MESSAGE_STATE_TTL_MS, MESSAGE_STATE_CLEANUP_INTERVAL_MS, MESSAGE_STATE_MAX_SIZE, } from "./const.js";
10
9
  // ============================================================================
11
10
  // WSClient 实例管理
12
11
  // ============================================================================
@@ -15,15 +14,21 @@ const wsClientInstances = new Map();
15
14
  /**
16
15
  * 获取指定账户的 WSClient 实例
17
16
  */
18
- function getWeComWebSocket(accountId) {
17
+ export function getWeComWebSocket(accountId) {
19
18
  return wsClientInstances.get(accountId) ?? null;
20
19
  }
21
20
  /**
22
21
  * 设置指定账户的 WSClient 实例
23
22
  */
24
- function setWeComWebSocket(accountId, client) {
23
+ export function setWeComWebSocket(accountId, client) {
25
24
  wsClientInstances.set(accountId, client);
26
25
  }
26
+ /**
27
+ * 删除指定账户的 WSClient 实例
28
+ */
29
+ export function deleteWeComWebSocket(accountId) {
30
+ wsClientInstances.delete(accountId);
31
+ }
27
32
  /** 消息状态管理 */
28
33
  const messageStates = new Map();
29
34
  /** 定期清理定时器 */
@@ -31,7 +36,7 @@ let cleanupTimer = null;
31
36
  /**
32
37
  * 启动消息状态定期清理(自动 TTL 清理 + 容量限制)
33
38
  */
34
- function startMessageStateCleanup() {
39
+ export function startMessageStateCleanup() {
35
40
  if (cleanupTimer)
36
41
  return;
37
42
  cleanupTimer = setInterval(() => {
@@ -45,7 +50,7 @@ function startMessageStateCleanup() {
45
50
  /**
46
51
  * 停止消息状态定期清理
47
52
  */
48
- function stopMessageStateCleanup() {
53
+ export function stopMessageStateCleanup() {
49
54
  if (cleanupTimer) {
50
55
  clearInterval(cleanupTimer);
51
56
  cleanupTimer = null;
@@ -74,18 +79,38 @@ function pruneMessageStates() {
74
79
  /**
75
80
  * 设置消息状态
76
81
  */
77
- function setMessageState(messageId, state) {
82
+ export function setMessageState(messageId, state) {
78
83
  messageStates.set(messageId, {
79
84
  state,
80
85
  createdAt: Date.now(),
81
86
  });
82
87
  }
88
+ /**
89
+ * 获取消息状态
90
+ */
91
+ export function getMessageState(messageId) {
92
+ const entry = messageStates.get(messageId);
93
+ if (!entry)
94
+ return undefined;
95
+ // 检查 TTL
96
+ if (Date.now() - entry.createdAt >= MESSAGE_STATE_TTL_MS) {
97
+ messageStates.delete(messageId);
98
+ return undefined;
99
+ }
100
+ return entry.state;
101
+ }
83
102
  /**
84
103
  * 删除消息状态
85
104
  */
86
- function deleteMessageState(messageId) {
105
+ export function deleteMessageState(messageId) {
87
106
  messageStates.delete(messageId);
88
107
  }
108
+ /**
109
+ * 清空所有消息状态
110
+ */
111
+ export function clearAllMessageStates() {
112
+ messageStates.clear();
113
+ }
89
114
  // ============================================================================
90
115
  // ReqId 持久化存储管理(按 accountId 隔离)
91
116
  // ============================================================================
@@ -98,7 +123,7 @@ const reqIdStores = new Map();
98
123
  function getOrCreateReqIdStore(accountId) {
99
124
  let store = reqIdStores.get(accountId);
100
125
  if (!store) {
101
- store = createPersistentReqIdStore();
126
+ store = createPersistentReqIdStore(accountId);
102
127
  reqIdStores.set(accountId, store);
103
128
  }
104
129
  return store;
@@ -109,26 +134,52 @@ function getOrCreateReqIdStore(accountId) {
109
134
  /**
110
135
  * 设置 chatId 对应的 reqId(写入内存 + 防抖写磁盘)
111
136
  */
112
- function setReqIdForChat(chatId, reqId, accountId = "default") {
137
+ export function setReqIdForChat(chatId, reqId, accountId = "default") {
113
138
  getOrCreateReqIdStore(accountId).set(chatId, reqId);
114
139
  }
140
+ /**
141
+ * 获取 chatId 对应的 reqId(异步:优先内存,miss 时查磁盘并回填内存)
142
+ */
143
+ export async function getReqIdForChatAsync(chatId, accountId = "default") {
144
+ return getOrCreateReqIdStore(accountId).get(chatId);
145
+ }
146
+ /**
147
+ * 获取 chatId 对应的 reqId(同步:仅内存,保留向后兼容)
148
+ */
149
+ export function getReqIdForChat(chatId, accountId = "default") {
150
+ return getOrCreateReqIdStore(accountId).getSync(chatId);
151
+ }
152
+ /**
153
+ * 删除 chatId 对应的 reqId
154
+ */
155
+ export function deleteReqIdForChat(chatId, accountId = "default") {
156
+ getOrCreateReqIdStore(accountId).delete(chatId);
157
+ }
115
158
  /**
116
159
  * 启动时预热 reqId 缓存(从磁盘加载到内存)
117
160
  *
118
161
  * 注意:由于移除了磁盘存储,此函数现在只返回 0(无预热条目)
119
162
  */
120
- async function warmupReqIdStore(accountId = "default", log) {
163
+ export async function warmupReqIdStore(accountId = "default", log) {
121
164
  // 由于移除了磁盘存储,不再需要预热过程
122
165
  log?.("[WeCom] reqid-store warmup: no-op (disk storage removed)");
123
166
  return 0;
124
167
  }
168
+ /**
169
+ * 立即将 reqId 数据刷写到磁盘(用于优雅退出)
170
+ *
171
+ * 注意:由于移除了磁盘存储,此函数现在是无操作
172
+ */
173
+ export async function flushReqIdStore(accountId = "default") {
174
+ // 由于移除了磁盘存储,不再需要刷写操作
175
+ }
125
176
  // ============================================================================
126
177
  // 全局 cleanup(断开连接时释放所有资源)
127
178
  // ============================================================================
128
179
  /**
129
180
  * 清理指定账户的所有资源
130
181
  */
131
- async function cleanupAccount(accountId) {
182
+ export async function cleanupAccount(accountId) {
132
183
  // 1. 断开 WSClient
133
184
  const wsClient = wsClientInstances.get(accountId);
134
185
  if (wsClient) {
@@ -143,6 +194,23 @@ async function cleanupAccount(accountId) {
143
194
  // 2. 由于移除了磁盘存储,不再需要 flush reqId 存储
144
195
  // 注意:不删除 store,因为重连后可能还需要
145
196
  }
146
-
147
- export { cleanupAccount, deleteMessageState, getWeComWebSocket, setMessageState, setReqIdForChat, setWeComWebSocket, startMessageStateCleanup, stopMessageStateCleanup, warmupReqIdStore };
148
- //# sourceMappingURL=state-manager.js.map
197
+ /**
198
+ * 清理所有资源(用于进程退出)
199
+ */
200
+ export async function cleanupAll() {
201
+ // 停止定期清理
202
+ stopMessageStateCleanup();
203
+ // 清理所有 WSClient
204
+ for (const [accountId, wsClient] of wsClientInstances) {
205
+ try {
206
+ wsClient.disconnect();
207
+ }
208
+ catch {
209
+ // 忽略
210
+ }
211
+ }
212
+ wsClientInstances.clear();
213
+ // 由于移除了磁盘存储,不再需要 flush 所有 reqId 存储
214
+ // 清空消息状态
215
+ clearAllMessageStates();
216
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * WeCom Target Resolver (企业微信目标解析器)
3
+ *
4
+ * 解析 OpenClaw 的 `to` 字段(原始目标字符串),将其转换为企业微信支持的具体接收对象。
5
+ * 支持显式前缀 (party:, tag: 等) 和基于规则的启发式推断。
6
+ *
7
+ * **关于“目标发送”与“消息记录”的对应关系 (Target vs Inbound):**
8
+ * - **发送 (Outbound)**: 支持一对多广播 (Party/Tag)。
9
+ * 例如发送给 `party:1`,消息会触达该部门下所有成员。
10
+ * - **接收 (Inbound)**: 总是来自具体的 **用户 (User)** 或 **群聊 (Chat)**。
11
+ * 当成员回复部门广播消息时,可以视为一个新的单聊会话或在该成员的现有单聊中回复。
12
+ * 因此,Outbound Target (如 Party) 与 Inbound Source (User) 不需要也不可能 1:1 强匹配。
13
+ * 广播是“发后即忘” (Fire-and-Forget) 的通知模式,而回复是具体的会话模式。
14
+ */
15
+ export interface WecomTarget {
16
+ touser?: string;
17
+ toparty?: string;
18
+ totag?: string;
19
+ chatid?: string;
20
+ }
21
+ /**
22
+ * Parses a raw target string into a WeComTarget object.
23
+ * 解析原始目标字符串为 WeComTarget 对象。
24
+ *
25
+ * 逻辑:
26
+ * 1. 移除标准命名空间前缀 (wecom:, qywx: 等)。
27
+ * 2. 检查显式类型前缀 (party:, tag:, group:, user:)。
28
+ * 3. 启发式回退 (无前缀时):
29
+ * - 以 "wr" 或 "wc" 开头 -> Chat ID (群聊)
30
+ * - 纯数字 -> Party ID (部门)
31
+ * - 其他 -> User ID (用户)
32
+ *
33
+ * @param raw - The raw target string (e.g. "party:1", "zhangsan", "wecom:wr123")
34
+ */
35
+ export declare function resolveWecomTarget(raw: string | undefined): WecomTarget | undefined;
@@ -0,0 +1,67 @@
1
+ /**
2
+ * WeCom Target Resolver (企业微信目标解析器)
3
+ *
4
+ * 解析 OpenClaw 的 `to` 字段(原始目标字符串),将其转换为企业微信支持的具体接收对象。
5
+ * 支持显式前缀 (party:, tag: 等) 和基于规则的启发式推断。
6
+ *
7
+ * **关于“目标发送”与“消息记录”的对应关系 (Target vs Inbound):**
8
+ * - **发送 (Outbound)**: 支持一对多广播 (Party/Tag)。
9
+ * 例如发送给 `party:1`,消息会触达该部门下所有成员。
10
+ * - **接收 (Inbound)**: 总是来自具体的 **用户 (User)** 或 **群聊 (Chat)**。
11
+ * 当成员回复部门广播消息时,可以视为一个新的单聊会话或在该成员的现有单聊中回复。
12
+ * 因此,Outbound Target (如 Party) 与 Inbound Source (User) 不需要也不可能 1:1 强匹配。
13
+ * 广播是“发后即忘” (Fire-and-Forget) 的通知模式,而回复是具体的会话模式。
14
+ */
15
+ /**
16
+ * Parses a raw target string into a WeComTarget object.
17
+ * 解析原始目标字符串为 WeComTarget 对象。
18
+ *
19
+ * 逻辑:
20
+ * 1. 移除标准命名空间前缀 (wecom:, qywx: 等)。
21
+ * 2. 检查显式类型前缀 (party:, tag:, group:, user:)。
22
+ * 3. 启发式回退 (无前缀时):
23
+ * - 以 "wr" 或 "wc" 开头 -> Chat ID (群聊)
24
+ * - 纯数字 -> Party ID (部门)
25
+ * - 其他 -> User ID (用户)
26
+ *
27
+ * @param raw - The raw target string (e.g. "party:1", "zhangsan", "wecom:wr123")
28
+ */
29
+ export function resolveWecomTarget(raw) {
30
+ if (!raw?.trim())
31
+ return undefined;
32
+ // 1. Remove standard namespace prefixes (移除标准命名空间前缀)
33
+ let clean = raw.trim().replace(/^(wecom-agent|wecom|wechatwork|wework|qywx):/i, "");
34
+ // 2. Explicit Type Prefixes (显式类型前缀)
35
+ if (/^party:/i.test(clean)) {
36
+ return { toparty: clean.replace(/^party:/i, "").trim() };
37
+ }
38
+ if (/^dept:/i.test(clean)) {
39
+ return { toparty: clean.replace(/^dept:/i, "").trim() };
40
+ }
41
+ if (/^tag:/i.test(clean)) {
42
+ return { totag: clean.replace(/^tag:/i, "").trim() };
43
+ }
44
+ if (/^group:/i.test(clean)) {
45
+ return { chatid: clean.replace(/^group:/i, "").trim() };
46
+ }
47
+ if (/^chat:/i.test(clean)) {
48
+ return { chatid: clean.replace(/^chat:/i, "").trim() };
49
+ }
50
+ if (/^user:/i.test(clean)) {
51
+ return { touser: clean.replace(/^user:/i, "").trim() };
52
+ }
53
+ // 3. Heuristics (启发式规则)
54
+ // Chat ID typically starts with 'wr' or 'wc'
55
+ // 群聊 ID 通常以 'wr' (外部群) 或 'wc' 开头
56
+ if (/^(wr|wc)/i.test(clean)) {
57
+ return { chatid: clean };
58
+ }
59
+ // Pure digits are likely Department IDs (Parties)
60
+ // 纯数字优先被视为部门 ID (Parties),方便运维配置 (如 "1" 代表根部门)
61
+ // 如果必须要发送给纯数字 ID 的用户,请使用显式前缀 "user:1001"
62
+ if (/^\d+$/.test(clean)) {
63
+ return { toparty: clean };
64
+ }
65
+ // Default to User (默认为用户)
66
+ return { touser: clean };
67
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * 模板卡片管理器
3
+ *
4
+ * 负责:
5
+ * - 模板卡片缓存管理(内存级,带 TTL 和大小限制)
6
+ * - 卡片交互事件处理(更新卡片 UI 状态)
7
+ * - 模板卡片发送(通过 wsClient.sendMessage 主动推送)
8
+ * - 从 LLM 回复中检测并处理模板卡片
9
+ */
10
+ import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
11
+ import type { WSClient, WsFrame, TemplateCard } from "@wecom/aibot-node-sdk";
12
+ import type { ResolvedWeComAccount } from "./utils.js";
13
+ import type { MessageState, ExtractedTemplateCard } from "./interface.js";
14
+ export declare function saveTemplateCardToCache(params: {
15
+ accountId: string;
16
+ templateCard: TemplateCard;
17
+ runtime: RuntimeEnv;
18
+ }): void;
19
+ export declare function getTemplateCardFromCache(accountId: string, taskId: string): TemplateCard | undefined;
20
+ export declare function updateTemplateCardOnEvent(params: {
21
+ frame: WsFrame;
22
+ accountId: string;
23
+ runtime: RuntimeEnv;
24
+ wsClient: WSClient;
25
+ }): Promise<void>;
26
+ /**
27
+ * 逐个发送已提取的模板卡片(通过 wsClient.sendMessage 主动推送)
28
+ *
29
+ * 发送失败不阻塞流程,仅记录错误日志。
30
+ */
31
+ export declare function sendTemplateCards(params: {
32
+ wsClient: WSClient;
33
+ frame: WsFrame;
34
+ state: MessageState;
35
+ account: ResolvedWeComAccount;
36
+ runtime: RuntimeEnv;
37
+ cards: ExtractedTemplateCard[];
38
+ }): Promise<void>;
39
+ /**
40
+ * 从累积文本中检测并发送模板卡片。
41
+ *
42
+ * 在 finishThinkingStream 之前调用,将卡片处理和流关闭解耦。
43
+ *
44
+ * @returns 移除卡片代码块后的剩余文本(如果没有卡片则返回 null,表示无需修改)
45
+ */
46
+ export declare function processTemplateCardsIfNeeded(params: {
47
+ wsClient: WSClient;
48
+ frame: WsFrame;
49
+ state: MessageState;
50
+ account: ResolvedWeComAccount;
51
+ runtime: RuntimeEnv;
52
+ }): Promise<{
53
+ remainingText: string;
54
+ cardsDetected: boolean;
55
+ } | null>;