@wecom/wecom-openclaw-plugin 2026.3.31-beta.0 → 2026.4.1-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.
Files changed (209) hide show
  1. package/README.md +4 -251
  2. package/dist/cjs/index.js +45 -0
  3. package/dist/cjs/index.js.map +1 -0
  4. package/dist/cjs/src/channel.js +339 -0
  5. package/dist/cjs/src/channel.js.map +1 -0
  6. package/dist/cjs/src/const.js +137 -0
  7. package/dist/cjs/src/const.js.map +1 -0
  8. package/dist/cjs/src/dm-policy.js +89 -0
  9. package/dist/cjs/src/dm-policy.js.map +1 -0
  10. package/dist/cjs/src/group-policy.js +133 -0
  11. package/dist/cjs/src/group-policy.js.map +1 -0
  12. package/dist/cjs/src/mcp/interceptors/biz-error.js +70 -0
  13. package/dist/cjs/src/mcp/interceptors/biz-error.js.map +1 -0
  14. package/dist/cjs/src/mcp/interceptors/index.js +73 -0
  15. package/dist/cjs/src/mcp/interceptors/index.js.map +1 -0
  16. package/dist/cjs/src/mcp/interceptors/msg-media.js +159 -0
  17. package/dist/cjs/src/mcp/interceptors/msg-media.js.map +1 -0
  18. package/dist/cjs/src/mcp/interceptors/smartpage-create.js +157 -0
  19. package/dist/cjs/src/mcp/interceptors/smartpage-create.js.map +1 -0
  20. package/dist/cjs/src/mcp/interceptors/smartpage-export.js +93 -0
  21. package/dist/cjs/src/mcp/interceptors/smartpage-export.js.map +1 -0
  22. package/dist/cjs/src/mcp/schema.js +104 -0
  23. package/dist/cjs/src/mcp/schema.js.map +1 -0
  24. package/dist/cjs/src/mcp/tool.js +208 -0
  25. package/dist/cjs/src/mcp/tool.js.map +1 -0
  26. package/dist/cjs/src/mcp/transport.js +417 -0
  27. package/dist/cjs/src/mcp/transport.js.map +1 -0
  28. package/dist/cjs/src/media-handler.js +129 -0
  29. package/dist/cjs/src/media-handler.js.map +1 -0
  30. package/dist/cjs/src/media-uploader.js +284 -0
  31. package/dist/cjs/src/media-uploader.js.map +1 -0
  32. package/dist/cjs/src/message-parser.js +134 -0
  33. package/dist/cjs/src/message-parser.js.map +1 -0
  34. package/dist/cjs/src/message-sender.js +89 -0
  35. package/dist/cjs/src/message-sender.js.map +1 -0
  36. package/dist/cjs/src/monitor.js +991 -0
  37. package/dist/cjs/src/monitor.js.map +1 -0
  38. package/dist/cjs/src/onboarding.js +182 -0
  39. package/dist/cjs/src/onboarding.js.map +1 -0
  40. package/dist/cjs/src/openclaw-compat.js +400 -0
  41. package/dist/cjs/src/openclaw-compat.js.map +1 -0
  42. package/dist/cjs/src/reqid-store.js +88 -0
  43. package/dist/cjs/src/reqid-store.js.map +1 -0
  44. package/dist/cjs/src/runtime.js +16 -0
  45. package/dist/cjs/src/runtime.js.map +1 -0
  46. package/dist/cjs/src/state-dir-resolve.js +35 -0
  47. package/dist/cjs/src/state-dir-resolve.js.map +1 -0
  48. package/dist/cjs/src/state-manager.js +158 -0
  49. package/dist/cjs/src/state-manager.js.map +1 -0
  50. package/dist/cjs/src/template-card-parser.js +641 -0
  51. package/dist/cjs/src/template-card-parser.js.map +1 -0
  52. package/dist/cjs/src/timeout.js +42 -0
  53. package/dist/cjs/src/timeout.js.map +1 -0
  54. package/dist/cjs/src/utils.js +60 -0
  55. package/dist/cjs/src/utils.js.map +1 -0
  56. package/dist/cjs/src/version.js +26 -0
  57. package/dist/cjs/src/version.js.map +1 -0
  58. package/dist/esm/index.js +41 -0
  59. package/dist/esm/index.js.map +1 -0
  60. package/dist/esm/src/channel.js +337 -0
  61. package/dist/esm/src/channel.js.map +1 -0
  62. package/dist/{src → esm/src}/const.js +30 -77
  63. package/dist/esm/src/const.js.map +1 -0
  64. package/dist/{src → esm/src}/dm-policy.js +9 -6
  65. package/dist/esm/src/dm-policy.js.map +1 -0
  66. package/dist/{src → esm/src}/group-policy.js +7 -3
  67. package/dist/esm/src/group-policy.js.map +1 -0
  68. package/dist/{src → esm/src}/mcp/interceptors/biz-error.js +6 -2
  69. package/dist/esm/src/mcp/interceptors/biz-error.js.map +1 -0
  70. package/dist/{src → esm/src}/mcp/interceptors/index.js +10 -6
  71. package/dist/esm/src/mcp/interceptors/index.js.map +1 -0
  72. package/dist/{src → esm/src}/mcp/interceptors/msg-media.js +10 -6
  73. package/dist/esm/src/mcp/interceptors/msg-media.js.map +1 -0
  74. package/dist/{src → esm/src}/mcp/interceptors/smartpage-create.js +6 -2
  75. package/dist/esm/src/mcp/interceptors/smartpage-create.js.map +1 -0
  76. package/dist/{src → esm/src}/mcp/interceptors/smartpage-export.js +6 -2
  77. package/dist/esm/src/mcp/interceptors/smartpage-export.js.map +1 -0
  78. package/dist/{src → esm/src}/mcp/schema.js +4 -1
  79. package/dist/esm/src/mcp/schema.js.map +1 -0
  80. package/dist/{src → esm/src}/mcp/tool.js +8 -4
  81. package/dist/esm/src/mcp/tool.js.map +1 -0
  82. package/dist/{src → esm/src}/mcp/transport.js +16 -15
  83. package/dist/esm/src/mcp/transport.js.map +1 -0
  84. package/dist/{src → esm/src}/media-handler.js +10 -6
  85. package/dist/esm/src/media-handler.js.map +1 -0
  86. package/dist/{src → esm/src}/media-uploader.js +10 -64
  87. package/dist/esm/src/media-uploader.js.map +1 -0
  88. package/dist/{src → esm/src}/message-parser.js +4 -1
  89. package/dist/esm/src/message-parser.js.map +1 -0
  90. package/dist/{src → esm/src}/message-sender.js +11 -46
  91. package/dist/esm/src/message-sender.js.map +1 -0
  92. package/dist/{src → esm/src}/monitor.js +282 -151
  93. package/dist/esm/src/monitor.js.map +1 -0
  94. package/dist/{src → esm/src}/onboarding.js +26 -22
  95. package/dist/esm/src/onboarding.js.map +1 -0
  96. package/dist/{src → esm/src}/openclaw-compat.js +27 -143
  97. package/dist/esm/src/openclaw-compat.js.map +1 -0
  98. package/dist/{src → esm/src}/reqid-store.js +7 -4
  99. package/dist/esm/src/reqid-store.js.map +1 -0
  100. package/dist/{src → esm/src}/runtime.js +5 -2
  101. package/dist/esm/src/runtime.js.map +1 -0
  102. package/dist/esm/src/state-dir-resolve.js +13 -0
  103. package/dist/esm/src/state-dir-resolve.js.map +1 -0
  104. package/dist/{src → esm/src}/state-manager.js +16 -84
  105. package/dist/esm/src/state-manager.js.map +1 -0
  106. package/dist/{src → esm/src}/template-card-parser.js +8 -17
  107. package/dist/esm/src/template-card-parser.js.map +1 -0
  108. package/dist/{src → esm/src}/timeout.js +5 -2
  109. package/dist/esm/src/timeout.js.map +1 -0
  110. package/dist/{src → esm/src}/utils.js +10 -24
  111. package/dist/esm/src/utils.js.map +1 -0
  112. package/dist/esm/src/version.js +23 -0
  113. package/dist/esm/src/version.js.map +1 -0
  114. package/dist/esm/types/index.d.ts +9 -0
  115. package/dist/{src → esm/types/src}/const.d.ts +0 -46
  116. package/dist/{src → esm/types/src}/interface.d.ts +2 -0
  117. package/dist/{src → esm/types/src}/message-sender.d.ts +0 -18
  118. package/dist/{src → esm/types/src}/openclaw-compat.d.ts +0 -49
  119. package/dist/esm/types/src/state-dir-resolve.d.ts +2 -0
  120. package/dist/{src → esm/types/src}/utils.d.ts +0 -39
  121. package/dist/esm/types/src/version.d.ts +2 -0
  122. package/dist/index.d.ts +4 -2
  123. package/package.json +7 -15
  124. package/dist/index.js +0 -113
  125. package/dist/src/accounts.d.ts +0 -57
  126. package/dist/src/accounts.js +0 -222
  127. package/dist/src/agent/api-client.d.ts +0 -94
  128. package/dist/src/agent/api-client.js +0 -292
  129. package/dist/src/agent/handler.d.ts +0 -63
  130. package/dist/src/agent/handler.js +0 -532
  131. package/dist/src/agent/index.d.ts +0 -5
  132. package/dist/src/agent/index.js +0 -5
  133. package/dist/src/agent/webhook.d.ts +0 -23
  134. package/dist/src/agent/webhook.js +0 -226
  135. package/dist/src/agent/xml.d.ts +0 -21
  136. package/dist/src/agent/xml.js +0 -41
  137. package/dist/src/channel.js +0 -564
  138. package/dist/src/chat-queue.d.ts +0 -31
  139. package/dist/src/chat-queue.js +0 -45
  140. package/dist/src/http.d.ts +0 -26
  141. package/dist/src/http.js +0 -93
  142. package/dist/src/interface.js +0 -4
  143. package/dist/src/mcp/index.js +0 -6
  144. package/dist/src/mcp/interceptors/types.js +0 -7
  145. package/dist/src/shared/command-auth.d.ts +0 -23
  146. package/dist/src/shared/command-auth.js +0 -73
  147. package/dist/src/shared/xml-parser.d.ts +0 -46
  148. package/dist/src/shared/xml-parser.js +0 -210
  149. package/dist/src/target.d.ts +0 -35
  150. package/dist/src/target.js +0 -67
  151. package/dist/src/template-card-manager.d.ts +0 -55
  152. package/dist/src/template-card-manager.js +0 -205
  153. package/dist/src/types/account.d.ts +0 -29
  154. package/dist/src/types/account.js +0 -4
  155. package/dist/src/types/config.d.ts +0 -80
  156. package/dist/src/types/config.js +0 -7
  157. package/dist/src/types/constants.d.ts +0 -42
  158. package/dist/src/types/constants.js +0 -42
  159. package/dist/src/types/index.d.ts +0 -7
  160. package/dist/src/types/index.js +0 -5
  161. package/dist/src/types/message.d.ts +0 -238
  162. package/dist/src/types/message.js +0 -5
  163. package/dist/src/version.d.ts +0 -2
  164. package/dist/src/version.js +0 -24
  165. package/dist/src/webhook/command-auth.d.ts +0 -47
  166. package/dist/src/webhook/command-auth.js +0 -96
  167. package/dist/src/webhook/gateway.d.ts +0 -36
  168. package/dist/src/webhook/gateway.js +0 -227
  169. package/dist/src/webhook/handler.d.ts +0 -18
  170. package/dist/src/webhook/handler.js +0 -420
  171. package/dist/src/webhook/helpers.d.ts +0 -156
  172. package/dist/src/webhook/helpers.js +0 -833
  173. package/dist/src/webhook/http.d.ts +0 -26
  174. package/dist/src/webhook/http.js +0 -94
  175. package/dist/src/webhook/index.d.ts +0 -11
  176. package/dist/src/webhook/index.js +0 -14
  177. package/dist/src/webhook/media.d.ts +0 -29
  178. package/dist/src/webhook/media.js +0 -88
  179. package/dist/src/webhook/monitor.d.ts +0 -59
  180. package/dist/src/webhook/monitor.js +0 -1147
  181. package/dist/src/webhook/state.d.ts +0 -220
  182. package/dist/src/webhook/state.js +0 -491
  183. package/dist/src/webhook/target.d.ts +0 -41
  184. package/dist/src/webhook/target.js +0 -132
  185. package/dist/src/webhook/types.d.ts +0 -347
  186. package/dist/src/webhook/types.js +0 -33
  187. /package/dist/{src → esm/types/src}/channel.d.ts +0 -0
  188. /package/dist/{src → esm/types/src}/dm-policy.d.ts +0 -0
  189. /package/dist/{src → esm/types/src}/group-policy.d.ts +0 -0
  190. /package/dist/{src → esm/types/src}/mcp/index.d.ts +0 -0
  191. /package/dist/{src → esm/types/src}/mcp/interceptors/biz-error.d.ts +0 -0
  192. /package/dist/{src → esm/types/src}/mcp/interceptors/index.d.ts +0 -0
  193. /package/dist/{src → esm/types/src}/mcp/interceptors/msg-media.d.ts +0 -0
  194. /package/dist/{src → esm/types/src}/mcp/interceptors/smartpage-create.d.ts +0 -0
  195. /package/dist/{src → esm/types/src}/mcp/interceptors/smartpage-export.d.ts +0 -0
  196. /package/dist/{src → esm/types/src}/mcp/interceptors/types.d.ts +0 -0
  197. /package/dist/{src → esm/types/src}/mcp/schema.d.ts +0 -0
  198. /package/dist/{src → esm/types/src}/mcp/tool.d.ts +0 -0
  199. /package/dist/{src → esm/types/src}/mcp/transport.d.ts +0 -0
  200. /package/dist/{src → esm/types/src}/media-handler.d.ts +0 -0
  201. /package/dist/{src → esm/types/src}/media-uploader.d.ts +0 -0
  202. /package/dist/{src → esm/types/src}/message-parser.d.ts +0 -0
  203. /package/dist/{src → esm/types/src}/monitor.d.ts +0 -0
  204. /package/dist/{src → esm/types/src}/onboarding.d.ts +0 -0
  205. /package/dist/{src → esm/types/src}/reqid-store.d.ts +0 -0
  206. /package/dist/{src → esm/types/src}/runtime.d.ts +0 -0
  207. /package/dist/{src → esm/types/src}/state-manager.d.ts +0 -0
  208. /package/dist/{src → esm/types/src}/template-card-parser.d.ts +0 -0
  209. /package/dist/{src → esm/types/src}/timeout.d.ts +0 -0
@@ -1,222 +0,0 @@
1
- import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
2
- import { CHANNEL_ID } from "./const.js";
3
- import { DefaultWsUrl } from "./utils.js";
4
- // ============================================================================
5
- // 账号列举
6
- // ============================================================================
7
- /**
8
- * 列出 accounts 字段中配置的所有账号 ID(已 normalize)。
9
- */
10
- function listConfiguredAccountIds(cfg) {
11
- const accounts = cfg.channels?.[CHANNEL_ID]?.accounts;
12
- if (!accounts || typeof accounts !== "object") {
13
- return [];
14
- }
15
- return Object.keys(accounts).filter(Boolean).map(normalizeAccountId);
16
- }
17
- /**
18
- * 判断是否为多账号模式(即配置中存在 accounts 字段)。
19
- * 用于区分单账号/多账号模式的分支判断,替代 `accountId === DEFAULT_ACCOUNT_ID` 的不可靠判断。
20
- */
21
- export function hasMultiAccounts(cfg) {
22
- return listConfiguredAccountIds(cfg).length > 0;
23
- }
24
- /**
25
- * 列出所有企业微信账号 ID。
26
- * 如果没有 accounts 字段,返回 [DEFAULT_ACCOUNT_ID] 以向后兼容。
27
- */
28
- export function listWeComAccountIds(cfg) {
29
- const ids = listConfiguredAccountIds(cfg);
30
- if (ids.length === 0) {
31
- // 向后兼容:未配置 accounts 时使用默认账号
32
- return [DEFAULT_ACCOUNT_ID];
33
- }
34
- return [...ids].sort((a, b) => a.localeCompare(b));
35
- }
36
- // ============================================================================
37
- // 默认账号解析
38
- // ============================================================================
39
- /**
40
- * 解析默认账号 ID。
41
- *
42
- * 优先级:
43
- * 1. 显式设置的 defaultAccount
44
- * 2. 包含 DEFAULT_ACCOUNT_ID 的账号列表
45
- * 3. 字母序第一个账号
46
- */
47
- export function resolveDefaultWeComAccountId(cfg) {
48
- const wecomConfig = cfg.channels?.[CHANNEL_ID];
49
- const preferred = wecomConfig?.defaultAccount?.trim();
50
- if (preferred) {
51
- return normalizeAccountId(preferred);
52
- }
53
- const ids = listWeComAccountIds(cfg);
54
- if (ids.includes(DEFAULT_ACCOUNT_ID)) {
55
- return DEFAULT_ACCOUNT_ID;
56
- }
57
- return ids[0] ?? DEFAULT_ACCOUNT_ID;
58
- }
59
- // ============================================================================
60
- // 配置合并
61
- // ============================================================================
62
- /**
63
- * 合并顶层配置与账号级配置(账号级覆盖顶层)。
64
- *
65
- * 顶层字段(如 dmPolicy、allowFrom)作为所有账号的默认值,
66
- * accounts.xxx 中的字段会覆盖顶层的同名字段。
67
- * 对于 groups 等嵌套 Record 对象,使用深层合并(账号级条目覆盖同 key,但不丢失基础配置中的其他 key)。
68
- */
69
- function mergeWeComAccountConfig(cfg, accountId) {
70
- const wecomConfig = cfg.channels?.[CHANNEL_ID];
71
- // 提取基础配置(排除 accounts 和 defaultAccount 字段避免递归)
72
- const { accounts: _ignored, defaultAccount: _da, ...base } = wecomConfig ?? {};
73
- // 查找账号级覆盖(支持 normalize 后的 key 匹配)
74
- const account = findAccountConfig(wecomConfig?.accounts, accountId);
75
- // 深层合并:对 groups 做嵌套合并,其余字段用账号级覆盖
76
- const { groups: baseGroups, ...baseRest } = base;
77
- const { groups: accountGroups, ...accountRest } = account;
78
- const mergedGroups = baseGroups || accountGroups
79
- ? { ...baseGroups, ...accountGroups }
80
- : undefined;
81
- return {
82
- ...baseRest,
83
- ...accountRest,
84
- ...(mergedGroups !== undefined ? { groups: mergedGroups } : {}),
85
- };
86
- }
87
- /**
88
- * 在 accounts Record 中按 normalize 后的 accountId 查找配置。
89
- * 避免因大小写差异导致找不到账号。
90
- */
91
- function findAccountConfig(accounts, accountId) {
92
- if (!accounts)
93
- return {};
94
- // 精确匹配优先
95
- if (accounts[accountId])
96
- return accounts[accountId];
97
- // normalize 后匹配
98
- const normalized = normalizeAccountId(accountId);
99
- for (const [key, value] of Object.entries(accounts)) {
100
- if (normalizeAccountId(key) === normalized)
101
- return value;
102
- }
103
- return {};
104
- }
105
- // ============================================================================
106
- // 账号解析
107
- // ============================================================================
108
- /**
109
- * 解析单个企业微信账号的完整配置。
110
- *
111
- * 支持:
112
- * - 显式指定 accountId → 使用该 accountId
113
- * - 未指定 → 使用默认账号
114
- * - 单账号模式(无 accounts 字段) → 直接读取顶层配置
115
- */
116
- export function resolveWeComAccountMulti(params) {
117
- const hasExplicitId = typeof params.accountId === "string" && params.accountId.trim() !== "";
118
- const accountId = hasExplicitId
119
- ? normalizeAccountId(params.accountId)
120
- : resolveDefaultWeComAccountId(params.cfg);
121
- const wecomConfig = params.cfg.channels?.[CHANNEL_ID];
122
- // 顶层 enabled 状态
123
- const baseEnabled = wecomConfig?.enabled !== false;
124
- // 合并配置
125
- const merged = mergeWeComAccountConfig(params.cfg, accountId);
126
- // 账号级 enabled 状态
127
- const accountEnabled = merged.enabled !== false;
128
- // 解析 Agent 子配置
129
- const agentCfg = merged.agent;
130
- let agent;
131
- if (agentCfg?.corpId && agentCfg?.corpSecret && agentCfg?.token && agentCfg?.encodingAESKey) {
132
- agent = {
133
- accountId,
134
- enabled: baseEnabled && accountEnabled,
135
- configured: true,
136
- corpId: agentCfg.corpId,
137
- corpSecret: agentCfg.corpSecret,
138
- agentId: typeof agentCfg.agentId === "string" ? Number(agentCfg.agentId) || undefined : agentCfg.agentId,
139
- token: agentCfg.token,
140
- encodingAESKey: agentCfg.encodingAESKey,
141
- config: agentCfg,
142
- network: merged.network,
143
- };
144
- }
145
- return {
146
- accountId,
147
- name: merged.name ?? "企业微信",
148
- enabled: baseEnabled && accountEnabled,
149
- websocketUrl: merged.websocketUrl || DefaultWsUrl,
150
- botId: merged.botId ?? "",
151
- secret: merged.secret ?? "",
152
- sendThinkingMessage: merged.sendThinkingMessage ?? true,
153
- config: merged,
154
- agent,
155
- token: merged.token ?? "",
156
- encodingAESKey: merged.encodingAESKey ?? "",
157
- receiveId: merged.receiveId ?? "",
158
- };
159
- }
160
- // ============================================================================
161
- // 批量查询
162
- // ============================================================================
163
- /**
164
- * 列出所有已启用且已配置凭据的账号。
165
- */
166
- export function listEnabledWeComAccounts(cfg) {
167
- return listWeComAccountIds(cfg)
168
- .map((accountId) => resolveWeComAccountMulti({ cfg, accountId }))
169
- .filter((account) => {
170
- if (!account.enabled)
171
- return false;
172
- const hasBotCredentials = Boolean(account.botId?.trim() && account.secret?.trim());
173
- const hasAgentCredentials = Boolean(account.agent?.configured);
174
- return hasBotCredentials || hasAgentCredentials;
175
- });
176
- }
177
- // ============================================================================
178
- // 配置写入(多账号感知)
179
- // ============================================================================
180
- /**
181
- * 写入企业微信账户配置(自动区分单账号/多账号模式)。
182
- *
183
- * - 单账号模式(无 accounts 字段):写入顶层 channels.wecom
184
- * - 多账号模式:写入 channels.wecom.accounts[accountId]
185
- *
186
- * @param cfg 当前全局配置
187
- * @param updates 要写入的部分配置字段
188
- * @param accountId 目标账号 ID(默认写入默认账号)
189
- */
190
- export function setWeComAccountMulti(cfg, updates, accountId) {
191
- const resolvedAccountId = accountId ?? resolveDefaultWeComAccountId(cfg);
192
- const isMulti = hasMultiAccounts(cfg);
193
- if (!isMulti) {
194
- // 单账号模式:合并到顶层
195
- const existing = (cfg.channels?.[CHANNEL_ID] ?? {});
196
- const merged = { ...existing, ...updates };
197
- return {
198
- ...cfg,
199
- channels: {
200
- ...cfg.channels,
201
- [CHANNEL_ID]: merged,
202
- },
203
- };
204
- }
205
- // 多账号模式:合并到 accounts[accountId]
206
- const wecomConfig = (cfg.channels?.[CHANNEL_ID] ?? {});
207
- const existingAccount = wecomConfig.accounts?.[resolvedAccountId] ?? {};
208
- const mergedAccount = { ...existingAccount, ...updates };
209
- return {
210
- ...cfg,
211
- channels: {
212
- ...cfg.channels,
213
- [CHANNEL_ID]: {
214
- ...wecomConfig,
215
- accounts: {
216
- ...wecomConfig.accounts,
217
- [resolvedAccountId]: mergedAccount,
218
- },
219
- },
220
- },
221
- };
222
- }
@@ -1,94 +0,0 @@
1
- /**
2
- * WeCom Agent API 客户端
3
- * 管理 AccessToken 缓存和 API 调用
4
- */
5
- import type { ResolvedAgentAccount } from "../types/index.js";
6
- /**
7
- * **getAccessToken (获取 AccessToken)**
8
- *
9
- * 获取企业微信 API 调用所需的 AccessToken。
10
- * 具备自动缓存和过期刷新机制。
11
- *
12
- * @param agent Agent 账号信息
13
- * @returns 有效的 AccessToken
14
- */
15
- export declare function getAccessToken(agent: ResolvedAgentAccount): Promise<string>;
16
- /**
17
- * **sendText (发送文本消息)**
18
- *
19
- * 调用 `message/send` (Agent) 或 `appchat/send` (群聊) 发送文本。
20
- *
21
- * @param params.agent 发送方 Agent
22
- * @param params.toUser 接收用户 ID (单聊可选,可与 toParty/toTag 同时使用)
23
- * @param params.toParty 接收部门 ID (单聊可选)
24
- * @param params.toTag 接收标签 ID (单聊可选)
25
- * @param params.chatId 接收群 ID (群聊模式必填,互斥)
26
- * @param params.text 消息内容
27
- */
28
- export declare function sendText(params: {
29
- agent: ResolvedAgentAccount;
30
- toUser?: string;
31
- toParty?: string;
32
- toTag?: string;
33
- chatId?: string;
34
- text: string;
35
- }): Promise<void>;
36
- /**
37
- * **uploadMedia (上传媒体文件)**
38
- *
39
- * 上传临时素材到企业微信。
40
- * 素材有效期为 3 天。
41
- *
42
- * @param params.type 媒体类型 (image, voice, video, file)
43
- * @param params.buffer 文件二进制数据
44
- * @param params.filename 文件名 (需包含正确扩展名)
45
- * @returns 媒体 ID (media_id)
46
- */
47
- export declare function uploadMedia(params: {
48
- agent: ResolvedAgentAccount;
49
- type: "image" | "voice" | "video" | "file";
50
- buffer: Buffer;
51
- filename: string;
52
- }): Promise<string>;
53
- /**
54
- * **sendMedia (发送媒体消息)**
55
- *
56
- * 发送图片、音频、视频或文件。需先通过 `uploadMedia` 获取 media_id。
57
- *
58
- * @param params.agent 发送方 Agent
59
- * @param params.toUser 接收用户 ID (单聊可选)
60
- * @param params.toParty 接收部门 ID (单聊可选)
61
- * @param params.toTag 接收标签 ID (单聊可选)
62
- * @param params.chatId 接收群 ID (群聊模式必填)
63
- * @param params.mediaId 媒体 ID
64
- * @param params.mediaType 媒体类型
65
- * @param params.title 视频标题 (可选)
66
- * @param params.description 视频描述 (可选)
67
- */
68
- export declare function sendMedia(params: {
69
- agent: ResolvedAgentAccount;
70
- toUser?: string;
71
- toParty?: string;
72
- toTag?: string;
73
- chatId?: string;
74
- mediaId: string;
75
- mediaType: "image" | "voice" | "video" | "file";
76
- title?: string;
77
- description?: string;
78
- }): Promise<void>;
79
- /**
80
- * **downloadMedia (下载媒体文件)**
81
- *
82
- * 通过 media_id 从企业微信服务器下载临时素材。
83
- *
84
- * @returns { buffer, contentType }
85
- */
86
- export declare function downloadMedia(params: {
87
- agent: ResolvedAgentAccount;
88
- mediaId: string;
89
- maxBytes?: number;
90
- }): Promise<{
91
- buffer: Buffer;
92
- contentType: string;
93
- filename?: string;
94
- }>;
@@ -1,292 +0,0 @@
1
- /**
2
- * WeCom Agent API 客户端
3
- * 管理 AccessToken 缓存和 API 调用
4
- */
5
- import crypto from "node:crypto";
6
- import { API_ENDPOINTS, LIMITS } from "../types/constants.js";
7
- import { readResponseBodyAsBuffer, wecomFetch } from "../http.js";
8
- function resolveWecomEgressProxyUrlFromNetwork(network) {
9
- return network?.egressProxyUrl;
10
- }
11
- const tokenCaches = new Map();
12
- function normalizeUploadFilename(filename) {
13
- const trimmed = filename.trim();
14
- if (!trimmed)
15
- return "file.bin";
16
- const ext = trimmed.includes(".") ? `.${trimmed.split(".").pop().toLowerCase()}` : "";
17
- const base = ext ? trimmed.slice(0, -ext.length) : trimmed;
18
- const sanitizedBase = base
19
- .replace(/[^\x20-\x7e]/g, "_")
20
- .replace(/["\\\/;=]/g, "_")
21
- .replace(/\s+/g, "_")
22
- .replace(/_+/g, "_")
23
- .replace(/^_+|_+$/g, "");
24
- const safeBase = sanitizedBase || "file";
25
- const safeExt = ext.replace(/[^a-z0-9.]/g, "");
26
- return `${safeBase}${safeExt || ".bin"}`;
27
- }
28
- function guessUploadContentType(filename) {
29
- const ext = filename.split(".").pop()?.toLowerCase() || "";
30
- const contentTypeMap = {
31
- // image
32
- jpg: "image/jpg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp", bmp: "image/bmp",
33
- // audio / video
34
- amr: "voice/amr", mp3: "audio/mpeg", wav: "audio/wav", m4a: "audio/mp4", ogg: "audio/ogg", mp4: "video/mp4", mov: "video/quicktime",
35
- // documents
36
- txt: "text/plain", md: "text/markdown", csv: "text/csv", tsv: "text/tab-separated-values", json: "application/json",
37
- xml: "application/xml", yaml: "application/yaml", yml: "application/yaml",
38
- pdf: "application/pdf", doc: "application/msword", docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
39
- xls: "application/vnd.ms-excel", xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
40
- ppt: "application/vnd.ms-powerpoint", pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
41
- rtf: "application/rtf", odt: "application/vnd.oasis.opendocument.text",
42
- // archives
43
- zip: "application/zip", rar: "application/vnd.rar", "7z": "application/x-7z-compressed",
44
- gz: "application/gzip", tgz: "application/gzip", tar: "application/x-tar",
45
- };
46
- return contentTypeMap[ext] || "application/octet-stream";
47
- }
48
- function requireAgentId(agent) {
49
- if (typeof agent.agentId === "number" && Number.isFinite(agent.agentId))
50
- return agent.agentId;
51
- throw new Error(`wecom agent account=${agent.accountId} missing agentId; sending via cgi-bin/message/send requires agentId`);
52
- }
53
- /**
54
- * **getAccessToken (获取 AccessToken)**
55
- *
56
- * 获取企业微信 API 调用所需的 AccessToken。
57
- * 具备自动缓存和过期刷新机制。
58
- *
59
- * @param agent Agent 账号信息
60
- * @returns 有效的 AccessToken
61
- */
62
- export async function getAccessToken(agent) {
63
- const cacheKey = `${agent.corpId}:${String(agent.agentId ?? "na")}`;
64
- let cache = tokenCaches.get(cacheKey);
65
- if (!cache) {
66
- cache = { token: "", expiresAt: 0, refreshPromise: null };
67
- tokenCaches.set(cacheKey, cache);
68
- }
69
- const now = Date.now();
70
- if (cache.token && cache.expiresAt > now + LIMITS.TOKEN_REFRESH_BUFFER_MS) {
71
- return cache.token;
72
- }
73
- // 防止并发刷新
74
- if (cache.refreshPromise) {
75
- return cache.refreshPromise;
76
- }
77
- cache.refreshPromise = (async () => {
78
- try {
79
- const url = `${API_ENDPOINTS.GET_TOKEN}?corpid=${encodeURIComponent(agent.corpId)}&corpsecret=${encodeURIComponent(agent.corpSecret)}`;
80
- const res = await wecomFetch(url, undefined, { proxyUrl: resolveWecomEgressProxyUrlFromNetwork(agent.network), timeoutMs: LIMITS.REQUEST_TIMEOUT_MS });
81
- const json = await res.json();
82
- if (!json?.access_token) {
83
- throw new Error(`gettoken failed: ${json?.errcode} ${json?.errmsg}`);
84
- }
85
- cache.token = json.access_token;
86
- cache.expiresAt = Date.now() + (json.expires_in ?? 7200) * 1000;
87
- return cache.token;
88
- }
89
- finally {
90
- cache.refreshPromise = null;
91
- }
92
- })();
93
- return cache.refreshPromise;
94
- }
95
- /**
96
- * **sendText (发送文本消息)**
97
- *
98
- * 调用 `message/send` (Agent) 或 `appchat/send` (群聊) 发送文本。
99
- *
100
- * @param params.agent 发送方 Agent
101
- * @param params.toUser 接收用户 ID (单聊可选,可与 toParty/toTag 同时使用)
102
- * @param params.toParty 接收部门 ID (单聊可选)
103
- * @param params.toTag 接收标签 ID (单聊可选)
104
- * @param params.chatId 接收群 ID (群聊模式必填,互斥)
105
- * @param params.text 消息内容
106
- */
107
- export async function sendText(params) {
108
- const { agent, toUser, toParty, toTag, chatId, text } = params;
109
- const token = await getAccessToken(agent);
110
- const useChat = Boolean(chatId);
111
- const url = useChat
112
- ? `${API_ENDPOINTS.SEND_APPCHAT}?access_token=${encodeURIComponent(token)}`
113
- : `${API_ENDPOINTS.SEND_MESSAGE}?access_token=${encodeURIComponent(token)}`;
114
- const body = useChat
115
- ? { chatid: chatId, msgtype: "text", text: { content: text } }
116
- : {
117
- touser: toUser,
118
- toparty: toParty,
119
- totag: toTag,
120
- msgtype: "text",
121
- agentid: requireAgentId(agent),
122
- text: { content: text }
123
- };
124
- const res = await wecomFetch(url, {
125
- method: "POST",
126
- headers: { "Content-Type": "application/json" },
127
- body: JSON.stringify(body),
128
- }, { proxyUrl: resolveWecomEgressProxyUrlFromNetwork(agent.network), timeoutMs: LIMITS.REQUEST_TIMEOUT_MS });
129
- const json = await res.json();
130
- if (json?.errcode !== 0) {
131
- throw new Error(`send failed: ${json?.errcode} ${json?.errmsg}`);
132
- }
133
- if (json?.invaliduser || json?.invalidparty || json?.invalidtag) {
134
- const details = [
135
- json.invaliduser ? `invaliduser=${json.invaliduser}` : "",
136
- json.invalidparty ? `invalidparty=${json.invalidparty}` : "",
137
- json.invalidtag ? `invalidtag=${json.invalidtag}` : ""
138
- ].filter(Boolean).join(", ");
139
- throw new Error(`send partial failure: ${details}`);
140
- }
141
- }
142
- /**
143
- * **uploadMedia (上传媒体文件)**
144
- *
145
- * 上传临时素材到企业微信。
146
- * 素材有效期为 3 天。
147
- *
148
- * @param params.type 媒体类型 (image, voice, video, file)
149
- * @param params.buffer 文件二进制数据
150
- * @param params.filename 文件名 (需包含正确扩展名)
151
- * @returns 媒体 ID (media_id)
152
- */
153
- export async function uploadMedia(params) {
154
- const { agent, type, buffer, filename } = params;
155
- const safeFilename = normalizeUploadFilename(filename);
156
- const token = await getAccessToken(agent);
157
- const proxyUrl = resolveWecomEgressProxyUrlFromNetwork(agent.network);
158
- // 添加 debug=1 参数获取更多错误信息
159
- const url = `${API_ENDPOINTS.UPLOAD_MEDIA}?access_token=${encodeURIComponent(token)}&type=${encodeURIComponent(type)}&debug=1`;
160
- // DEBUG: 输出上传信息
161
- console.log(`[wecom-upload] Uploading media: type=${type}, filename=${safeFilename}, size=${buffer.length} bytes`);
162
- const uploadOnce = async (fileContentType) => {
163
- // 手动构造 multipart/form-data 请求体
164
- // 企业微信要求包含 filename 和 filelength
165
- const boundary = `----WebKitFormBoundary${crypto.randomBytes(16).toString("hex")}`;
166
- const header = Buffer.from(`--${boundary}\r\n` +
167
- `Content-Disposition: form-data; name="media"; filename="${safeFilename}"; filelength=${buffer.length}\r\n` +
168
- `Content-Type: ${fileContentType}\r\n\r\n`);
169
- const footer = Buffer.from(`\r\n--${boundary}--\r\n`);
170
- const body = Buffer.concat([header, buffer, footer]);
171
- console.log(`[wecom-upload] Multipart body size=${body.length}, boundary=${boundary}, fileContentType=${fileContentType}`);
172
- const res = await wecomFetch(url, {
173
- method: "POST",
174
- headers: {
175
- "Content-Type": `multipart/form-data; boundary=${boundary}`,
176
- "Content-Length": String(body.length),
177
- },
178
- body: body,
179
- }, { proxyUrl, timeoutMs: LIMITS.REQUEST_TIMEOUT_MS });
180
- const json = await res.json();
181
- console.log(`[wecom-upload] Response:`, JSON.stringify(json));
182
- return json;
183
- };
184
- const preferredContentType = guessUploadContentType(safeFilename);
185
- let json = await uploadOnce(preferredContentType);
186
- // 某些文件类型在严格网关/企业微信校验下可能失败,回退到通用类型再试一次。
187
- if (!json?.media_id && preferredContentType !== "application/octet-stream") {
188
- console.warn(`[wecom-upload] Upload failed with ${preferredContentType}, retrying as application/octet-stream: ${json?.errcode} ${json?.errmsg}`);
189
- json = await uploadOnce("application/octet-stream");
190
- }
191
- if (!json?.media_id) {
192
- throw new Error(`upload failed: ${json?.errcode} ${json?.errmsg}`);
193
- }
194
- return json.media_id;
195
- }
196
- /**
197
- * **sendMedia (发送媒体消息)**
198
- *
199
- * 发送图片、音频、视频或文件。需先通过 `uploadMedia` 获取 media_id。
200
- *
201
- * @param params.agent 发送方 Agent
202
- * @param params.toUser 接收用户 ID (单聊可选)
203
- * @param params.toParty 接收部门 ID (单聊可选)
204
- * @param params.toTag 接收标签 ID (单聊可选)
205
- * @param params.chatId 接收群 ID (群聊模式必填)
206
- * @param params.mediaId 媒体 ID
207
- * @param params.mediaType 媒体类型
208
- * @param params.title 视频标题 (可选)
209
- * @param params.description 视频描述 (可选)
210
- */
211
- export async function sendMedia(params) {
212
- const { agent, toUser, toParty, toTag, chatId, mediaId, mediaType, title, description } = params;
213
- const token = await getAccessToken(agent);
214
- const useChat = Boolean(chatId);
215
- const url = useChat
216
- ? `${API_ENDPOINTS.SEND_APPCHAT}?access_token=${encodeURIComponent(token)}`
217
- : `${API_ENDPOINTS.SEND_MESSAGE}?access_token=${encodeURIComponent(token)}`;
218
- const mediaPayload = mediaType === "video"
219
- ? { media_id: mediaId, title: title ?? "Video", description: description ?? "" }
220
- : { media_id: mediaId };
221
- const body = useChat
222
- ? { chatid: chatId, msgtype: mediaType, [mediaType]: mediaPayload }
223
- : {
224
- touser: toUser,
225
- toparty: toParty,
226
- totag: toTag,
227
- msgtype: mediaType,
228
- agentid: requireAgentId(agent),
229
- [mediaType]: mediaPayload
230
- };
231
- const res = await wecomFetch(url, {
232
- method: "POST",
233
- headers: { "Content-Type": "application/json" },
234
- body: JSON.stringify(body),
235
- }, { proxyUrl: resolveWecomEgressProxyUrlFromNetwork(agent.network), timeoutMs: LIMITS.REQUEST_TIMEOUT_MS });
236
- const json = await res.json();
237
- if (json?.errcode !== 0) {
238
- throw new Error(`send ${mediaType} failed: ${json?.errcode} ${json?.errmsg}`);
239
- }
240
- if (json?.invaliduser || json?.invalidparty || json?.invalidtag) {
241
- const details = [
242
- json.invaliduser ? `invaliduser=${json.invaliduser}` : "",
243
- json.invalidparty ? `invalidparty=${json.invalidparty}` : "",
244
- json.invalidtag ? `invalidtag=${json.invalidtag}` : ""
245
- ].filter(Boolean).join(", ");
246
- throw new Error(`send ${mediaType} partial failure: ${details}`);
247
- }
248
- }
249
- /**
250
- * **downloadMedia (下载媒体文件)**
251
- *
252
- * 通过 media_id 从企业微信服务器下载临时素材。
253
- *
254
- * @returns { buffer, contentType }
255
- */
256
- export async function downloadMedia(params) {
257
- const { agent, mediaId } = params;
258
- const token = await getAccessToken(agent);
259
- const url = `${API_ENDPOINTS.DOWNLOAD_MEDIA}?access_token=${encodeURIComponent(token)}&media_id=${encodeURIComponent(mediaId)}`;
260
- const res = await wecomFetch(url, undefined, { proxyUrl: resolveWecomEgressProxyUrlFromNetwork(agent.network), timeoutMs: LIMITS.REQUEST_TIMEOUT_MS });
261
- if (!res.ok) {
262
- throw new Error(`download failed: ${res.status}`);
263
- }
264
- const contentType = res.headers.get("content-type") || "application/octet-stream";
265
- const disposition = res.headers.get("content-disposition") || "";
266
- const filename = (() => {
267
- // 兼容:filename="a.md" / filename=a.md / filename*=UTF-8''a%2Eb.md
268
- const mStar = disposition.match(/filename\*\s*=\s*([^;]+)/i);
269
- if (mStar) {
270
- const raw = mStar[1].trim().replace(/^"(.*)"$/, "$1");
271
- const parts = raw.split("''");
272
- const encoded = parts.length === 2 ? parts[1] : raw;
273
- try {
274
- return decodeURIComponent(encoded);
275
- }
276
- catch {
277
- return encoded;
278
- }
279
- }
280
- const m = disposition.match(/filename\s*=\s*([^;]+)/i);
281
- if (!m)
282
- return undefined;
283
- return m[1].trim().replace(/^"(.*)"$/, "$1") || undefined;
284
- })();
285
- // 检查是否返回了错误 JSON
286
- if (contentType.includes("application/json")) {
287
- const json = await res.json();
288
- throw new Error(`download failed: ${json?.errcode} ${json?.errmsg}`);
289
- }
290
- const buffer = await readResponseBodyAsBuffer(res, params.maxBytes);
291
- return { buffer, contentType, filename };
292
- }
@@ -1,63 +0,0 @@
1
- /**
2
- * WeCom Agent Webhook 处理器
3
- * 处理 XML 格式回调
4
- */
5
- import type { IncomingMessage, ServerResponse } from "node:http";
6
- import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk";
7
- import type { ResolvedAgentAccount } from "../types/index.js";
8
- import type { WecomAgentInboundMessage } from "../types/index.js";
9
- /**
10
- * **AgentWebhookParams (Webhook 处理器参数)**
11
- *
12
- * 传递给 Agent Webhook 处理函数的上下文参数集合。
13
- * @property req Node.js 原始请求对象
14
- * @property res Node.js 原始响应对象
15
- * @property agent 解析后的 Agent 账号信息
16
- * @property config 全局插件配置
17
- * @property core OpenClaw 插件运行时
18
- * @property log 可选日志输出函数
19
- * @property error 可选错误输出函数
20
- */
21
- export type AgentWebhookParams = {
22
- req: IncomingMessage;
23
- res: ServerResponse;
24
- /**
25
- * 上游已完成验签/解密时传入,避免重复协议处理。
26
- * 仅用于 POST 消息回调流程。
27
- */
28
- verifiedPost?: {
29
- timestamp: string;
30
- nonce: string;
31
- signature: string;
32
- encrypted: string;
33
- decrypted: string;
34
- parsed: WecomAgentInboundMessage;
35
- };
36
- agent: ResolvedAgentAccount;
37
- config: OpenClawConfig;
38
- core: PluginRuntime;
39
- log?: (msg: string) => void;
40
- error?: (msg: string) => void;
41
- };
42
- export type AgentInboundProcessDecision = {
43
- shouldProcess: boolean;
44
- reason: string;
45
- };
46
- /**
47
- * 仅允许“用户意图消息”进入 AI 会话。
48
- * - event 回调(如 enter_agent/subscribe)不应触发会话与自动回复
49
- * - 系统发送者(sys)不应触发会话与自动回复
50
- * - 缺失发送者时默认丢弃,避免写入异常会话
51
- */
52
- export declare function shouldProcessAgentInboundMessage(params: {
53
- msgType: string;
54
- fromUser: string;
55
- eventType?: string;
56
- }): AgentInboundProcessDecision;
57
- /**
58
- * **handleAgentWebhook (Agent Webhook 入口)**
59
- *
60
- * 统一处理 Agent 模式的 POST 消息回调请求。
61
- * URL 验证与验签/解密由 monitor 层统一处理后再调用本函数。
62
- */
63
- export declare function handleAgentWebhook(params: AgentWebhookParams): Promise<boolean>;