@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,26 @@
1
+ /**
2
+ * **WecomHttpOptions (HTTP 选项)**
3
+ *
4
+ * @property proxyUrl 代理服务器地址
5
+ * @property timeoutMs 请求超时时间 (毫秒)
6
+ * @property signal AbortSignal 信号
7
+ */
8
+ export type WecomHttpOptions = {
9
+ proxyUrl?: string;
10
+ timeoutMs?: number;
11
+ signal?: AbortSignal;
12
+ };
13
+ /**
14
+ * **wecomFetch (统一 HTTP 请求)**
15
+ *
16
+ * 基于 `undici` 的 fetch 封装,自动处理 ProxyAgent 和 Timeout。
17
+ * 所有对企业微信 API 的调用都应经过此函数。
18
+ */
19
+ export declare function wecomFetch(input: string | URL, init?: RequestInit, opts?: WecomHttpOptions): Promise<Response>;
20
+ /**
21
+ * **readResponseBodyAsBuffer (读取响应 Body)**
22
+ *
23
+ * 将 Response Body 读取为 Buffer,支持最大字节限制以防止内存溢出。
24
+ * 适用于下载媒体文件等场景。
25
+ */
26
+ export declare function readResponseBodyAsBuffer(res: Response, maxBytes?: number): Promise<Buffer>;
@@ -0,0 +1,94 @@
1
+ import { ProxyAgent, fetch as undiciFetch } from "undici";
2
+ const proxyDispatchers = new Map();
3
+ /**
4
+ * **getProxyDispatcher (获取代理 Dispatcher)**
5
+ *
6
+ * 缓存并复用 ProxyAgent,避免重复创建连接池。
7
+ */
8
+ function getProxyDispatcher(proxyUrl) {
9
+ const existing = proxyDispatchers.get(proxyUrl);
10
+ if (existing)
11
+ return existing;
12
+ const created = new ProxyAgent(proxyUrl);
13
+ proxyDispatchers.set(proxyUrl, created);
14
+ return created;
15
+ }
16
+ function mergeAbortSignal(params) {
17
+ const signals = [];
18
+ if (params.signal)
19
+ signals.push(params.signal);
20
+ if (params.timeoutMs && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0) {
21
+ signals.push(AbortSignal.timeout(params.timeoutMs));
22
+ }
23
+ if (!signals.length)
24
+ return undefined;
25
+ if (signals.length === 1)
26
+ return signals[0];
27
+ return AbortSignal.any(signals);
28
+ }
29
+ /**
30
+ * **wecomFetch (统一 HTTP 请求)**
31
+ *
32
+ * 基于 `undici` 的 fetch 封装,自动处理 ProxyAgent 和 Timeout。
33
+ * 所有对企业微信 API 的调用都应经过此函数。
34
+ */
35
+ export async function wecomFetch(input, init, opts) {
36
+ const proxyUrl = opts?.proxyUrl?.trim() ?? "";
37
+ const dispatcher = proxyUrl ? getProxyDispatcher(proxyUrl) : undefined;
38
+ const initSignal = init?.signal ?? undefined;
39
+ const signal = mergeAbortSignal({ signal: opts?.signal ?? initSignal, timeoutMs: opts?.timeoutMs });
40
+ const headers = new Headers(init?.headers ?? {});
41
+ if (!headers.has("User-Agent")) {
42
+ headers.set("User-Agent", "OpenClaw/2.0 (WeCom-Agent)");
43
+ }
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
+ const nextInit = {
46
+ ...(init ?? {}),
47
+ ...(signal ? { signal } : {}),
48
+ ...(dispatcher ? { dispatcher } : {}),
49
+ headers,
50
+ };
51
+ try {
52
+ return await undiciFetch(input, nextInit);
53
+ }
54
+ catch (err) {
55
+ if (err instanceof Error && err.name === "TypeError" && err.message === "fetch failed") {
56
+ const cause = err.cause;
57
+ console.error(`[wecom-http] fetch failed: ${input} (proxy: ${proxyUrl || "none"})${cause ? ` - cause: ${String(cause)}` : ""}`);
58
+ }
59
+ throw err;
60
+ }
61
+ }
62
+ /**
63
+ * **readResponseBodyAsBuffer (读取响应 Body)**
64
+ *
65
+ * 将 Response Body 读取为 Buffer,支持最大字节限制以防止内存溢出。
66
+ * 适用于下载媒体文件等场景。
67
+ */
68
+ export async function readResponseBodyAsBuffer(res, maxBytes) {
69
+ if (!res.body)
70
+ return Buffer.alloc(0);
71
+ const limit = maxBytes && Number.isFinite(maxBytes) && maxBytes > 0 ? maxBytes : undefined;
72
+ const chunks = [];
73
+ let total = 0;
74
+ const reader = res.body.getReader();
75
+ while (true) {
76
+ const { done, value } = await reader.read();
77
+ if (done)
78
+ break;
79
+ if (!value)
80
+ continue;
81
+ total += value.byteLength;
82
+ if (limit && total > limit) {
83
+ try {
84
+ await reader.cancel("body too large");
85
+ }
86
+ catch {
87
+ // ignore
88
+ }
89
+ throw new Error(`response body too large (>${limit} bytes)`);
90
+ }
91
+ chunks.push(value);
92
+ }
93
+ return Buffer.concat(chunks.map((c) => Buffer.from(c)));
94
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Webhook 模块公共入口
3
+ *
4
+ * Re-export 所有 Webhook 模式对外暴露的函数和类型。
5
+ */
6
+ export { handleWecomWebhookRequest } from "./handler.js";
7
+ export { registerWecomWebhookTarget, getRegisteredTargets, getWebhookTargetsMap, hasActiveTargets, parseWebhookPath, } from "./target.js";
8
+ export { startWebhookGateway, stopWebhookGateway, getMonitorState } from "./gateway.js";
9
+ export type { WecomWebhookTarget, WebhookGatewayContext, ResolvedWebhookAccount, WebhookAccountConfig, WecomRuntimeEnv, StreamState, PendingInbound, ActiveReplyState, WebhookInboundMessage, } from "./types.js";
10
+ export { STREAM_TTL_MS, ACTIVE_REPLY_TTL_MS, DEFAULT_DEBOUNCE_MS, STREAM_MAX_BYTES, BOT_WINDOW_MS, BOT_SWITCH_MARGIN_MS, REQUEST_TIMEOUT_MS, PRUNE_INTERVAL_MS, WEBHOOK_PATHS, } from "./types.js";
11
+ export { monitorState, WebhookMonitorState } from "./state.js";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Webhook 模块公共入口
3
+ *
4
+ * Re-export 所有 Webhook 模式对外暴露的函数和类型。
5
+ */
6
+ // ── Handler ─────────────────────────────────────────────────────────
7
+ export { handleWecomWebhookRequest } from "./handler.js";
8
+ // ── Target ──────────────────────────────────────────────────────────
9
+ export { registerWecomWebhookTarget, getRegisteredTargets, getWebhookTargetsMap, hasActiveTargets, parseWebhookPath, } from "./target.js";
10
+ // ── Gateway ─────────────────────────────────────────────────────────
11
+ export { startWebhookGateway, stopWebhookGateway, getMonitorState } from "./gateway.js";
12
+ export { STREAM_TTL_MS, ACTIVE_REPLY_TTL_MS, DEFAULT_DEBOUNCE_MS, STREAM_MAX_BYTES, BOT_WINDOW_MS, BOT_SWITCH_MARGIN_MS, REQUEST_TIMEOUT_MS, PRUNE_INTERVAL_MS, WEBHOOK_PATHS, } from "./types.js";
13
+ // ── State (全局单例) ────────────────────────────────────────────────
14
+ export { monitorState, WebhookMonitorState } from "./state.js";
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Webhook 入站媒体处理
3
+ *
4
+ * 从 @mocrane/wecom media.ts 部分迁移(仅入站解密)。
5
+ * 负责:AES-CBC 解密企微加密媒体文件、MIME 类型检测。
6
+ */
7
+ import { type WecomHttpOptions } from "./http.js";
8
+ /** 企微使用 32 字节 PKCS#7 块大小(不是 AES 的 16 字节块) */
9
+ export declare const WECOM_PKCS7_BLOCK_SIZE = 32;
10
+ /** 解密后的媒体文件及源信息(对齐原版 DecryptedWecomMedia) */
11
+ export type DecryptedWecomMedia = {
12
+ buffer: Buffer;
13
+ /** HTTP Content-Type(归一化后) */
14
+ sourceContentType?: string;
15
+ /** 从 Content-Disposition 提取的文件名 */
16
+ sourceFilename?: string;
17
+ /** 最终请求 URL(跟随重定向后) */
18
+ sourceUrl?: string;
19
+ };
20
+ /**
21
+ * **decryptWecomMediaWithMeta (解密企业微信媒体并返回源信息)**
22
+ *
23
+ * 在返回解密结果的同时,保留下载响应中的元信息(content-type / filename / final url),
24
+ * 供上层更准确地推断文件后缀和 MIME。
25
+ */
26
+ export declare function decryptWecomMediaWithMeta(url: string, encodingAESKey: string, params?: {
27
+ maxBytes?: number;
28
+ http?: WecomHttpOptions;
29
+ }): Promise<DecryptedWecomMedia>;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Webhook 入站媒体处理
3
+ *
4
+ * 从 @mocrane/wecom media.ts 部分迁移(仅入站解密)。
5
+ * 负责:AES-CBC 解密企微加密媒体文件、MIME 类型检测。
6
+ */
7
+ import crypto from "node:crypto";
8
+ import { pkcs7Unpad, decodeEncodingAESKey } from "@wecom/aibot-node-sdk";
9
+ import { wecomFetch, readResponseBodyAsBuffer } from "./http.js";
10
+ // ============================================================================
11
+ // 媒体文件解密
12
+ // ============================================================================
13
+ /** 企微使用 32 字节 PKCS#7 块大小(不是 AES 的 16 字节块) */
14
+ export const WECOM_PKCS7_BLOCK_SIZE = 32;
15
+ /**
16
+ * **decryptWecomMediaWithMeta (解密企业微信媒体并返回源信息)**
17
+ *
18
+ * 在返回解密结果的同时,保留下载响应中的元信息(content-type / filename / final url),
19
+ * 供上层更准确地推断文件后缀和 MIME。
20
+ */
21
+ export async function decryptWecomMediaWithMeta(url, encodingAESKey, params) {
22
+ // 1. Download encrypted content
23
+ const res = await wecomFetch(url, undefined, { ...params?.http, timeoutMs: params?.http?.timeoutMs ?? 15_000 });
24
+ if (!res.ok) {
25
+ throw new Error(`failed to download media: ${res.status}`);
26
+ }
27
+ const sourceContentType = normalizeMime(res.headers.get("content-type"));
28
+ const sourceFilename = extractFilenameFromContentDisposition(res.headers.get("content-disposition"));
29
+ const sourceUrl = res.url || url;
30
+ const encryptedData = await readResponseBodyAsBuffer(res, params?.maxBytes);
31
+ // 2. Prepare Key and IV
32
+ const aesKey = decodeEncodingAESKey(encodingAESKey);
33
+ const iv = aesKey.subarray(0, 16);
34
+ // 3. Decrypt
35
+ const decipher = crypto.createDecipheriv("aes-256-cbc", aesKey, iv);
36
+ decipher.setAutoPadding(false);
37
+ const decryptedPadded = Buffer.concat([
38
+ decipher.update(encryptedData),
39
+ decipher.final(),
40
+ ]);
41
+ // 4. Unpad
42
+ // Note: Unlike msg bodies, usually removing PKCS#7 padding is enough for media files.
43
+ // The Python SDK logic: pad_len = decrypted_data[-1]; decrypted_data = decrypted_data[:-pad_len]
44
+ // Our pkcs7Unpad function does exactly this + validation.
45
+ return {
46
+ buffer: pkcs7Unpad(decryptedPadded, WECOM_PKCS7_BLOCK_SIZE),
47
+ sourceContentType,
48
+ sourceFilename,
49
+ sourceUrl,
50
+ };
51
+ }
52
+ // ============================================================================
53
+ // HTTP 头信息解析(供 decryptWecomMediaWithMeta 使用)
54
+ // ============================================================================
55
+ /** 归一化 MIME 类型 */
56
+ function normalizeMime(contentType) {
57
+ const raw = String(contentType ?? "").trim();
58
+ if (!raw)
59
+ return undefined;
60
+ return raw.split(";")[0]?.trim().toLowerCase() || undefined;
61
+ }
62
+ /** 从 Content-Disposition 提取文件名 */
63
+ function extractFilenameFromContentDisposition(disposition) {
64
+ const raw = String(disposition ?? "").trim();
65
+ if (!raw)
66
+ return undefined;
67
+ // 优先 filename*(RFC 5987 编码)
68
+ const star = raw.match(/filename\*\s*=\s*([^;]+)/i);
69
+ if (star?.[1]) {
70
+ const v = star[1].trim().replace(/^UTF-8''/i, "").replace(/^"(.*)"$/, "$1");
71
+ try {
72
+ const decoded = decodeURIComponent(v);
73
+ if (decoded.trim())
74
+ return decoded.trim();
75
+ }
76
+ catch { /* ignore */ }
77
+ if (v.trim())
78
+ return v.trim();
79
+ }
80
+ // 再尝试 filename
81
+ const plain = raw.match(/filename\s*=\s*([^;]+)/i);
82
+ if (plain?.[1]) {
83
+ const v = plain[1].trim().replace(/^"(.*)"$/, "$1").trim();
84
+ if (v)
85
+ return v;
86
+ }
87
+ return undefined;
88
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Webhook 核心消息处理
3
+ *
4
+ * 从 @mocrane/wecom monitor.ts 部分迁移 + 重构。
5
+ * 负责:入站消息解析、防抖聚合、Agent 调度、流式输出、超时兜底。
6
+ */
7
+ import type { WecomWebhookTarget, WebhookInboundMessage } from "./types.js";
8
+ /**
9
+ * 处理入站消息
10
+ *
11
+ * 解析消息类型和内容,创建/获取 stream,加入防抖队列,返回占位符响应。
12
+ */
13
+ export declare function handleInboundMessage(target: WecomWebhookTarget, message: WebhookInboundMessage, timestamp: string, nonce: string, proxyUrl?: string, msgFilterData?: {
14
+ senderUserId?: string;
15
+ chatId?: string;
16
+ }): Promise<Record<string, unknown> | null>;
17
+ /**
18
+ * 处理 stream_refresh 请求
19
+ *
20
+ * 返回 StreamState 中的当前累积内容、图片附件和 finish 标记。
21
+ */
22
+ export declare function handleStreamRefresh(target: WecomWebhookTarget, message: WebhookInboundMessage): Promise<Record<string, unknown> | null>;
23
+ /**
24
+ * 处理 enter_chat 事件
25
+ *
26
+ * 返回可配置的欢迎消息。
27
+ */
28
+ export declare function handleEnterChat(target: WecomWebhookTarget, message: WebhookInboundMessage): Promise<Record<string, unknown> | null>;
29
+ /**
30
+ * 处理模板卡片事件(对齐原版 template_card_event 逻辑)
31
+ *
32
+ * 原版流程:
33
+ * 1. msgid 去重:跳过已处理的卡片事件
34
+ * 2. 解析卡片交互数据:event_key、selected_items、task_id
35
+ * 3. 立即返回空加密回复(非阻塞)
36
+ * 4. 创建 stream 并标记开始
37
+ * 5. 存储 response_url(用于后续推送)
38
+ * 6. 构造交互描述文本,作为文本消息启动 Agent 处理
39
+ */
40
+ export declare function handleTemplateCardEvent(target: WecomWebhookTarget, message: WebhookInboundMessage, timestamp: string, nonce: string, proxyUrl?: string): Promise<Record<string, unknown> | null>;
41
+ /**
42
+ * **startAgentForStream (启动 Agent 处理流程)**
43
+ *
44
+ * 将接收到的(或聚合的)消息转换为 OpenClaw 内部格式,并分发给对应的 Agent。
45
+ * 包含:
46
+ * 1. 消息解密与媒体保存。
47
+ * 2. 路由解析 (Agent Route)。
48
+ * 3. 会话记录 (Session Recording)。
49
+ * 4. 触发 Agent 响应 (Dispatch Reply)。
50
+ * 5. 处理 Agent 输出(包括文本、Markdown 表格转换、<think> 标签保护、模板卡片识别)。
51
+ */
52
+ export declare function startAgentForStream(params: {
53
+ target: WecomWebhookTarget;
54
+ accountId: string;
55
+ msg: WebhookInboundMessage;
56
+ streamId: string;
57
+ mergedContents?: string;
58
+ mergedMsgids?: string[];
59
+ }): Promise<void>;