@yanhaidao/wecom 2.4.160 → 2.5.110

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 (313) hide show
  1. package/dist/index.js +68 -0
  2. package/dist/src/accounts.js +20 -0
  3. package/dist/src/agent/handler.js +895 -0
  4. package/dist/src/agent/index.js +5 -0
  5. package/dist/src/app/account-runtime.js +216 -0
  6. package/dist/src/app/bootstrap.js +19 -0
  7. package/dist/src/app/index.js +118 -0
  8. package/dist/src/capability/agent/delivery-service.js +63 -0
  9. package/dist/src/capability/agent/fallback-policy.js +6 -0
  10. package/dist/src/capability/agent/ingress-service.js +33 -0
  11. package/dist/src/capability/agent/upstream-delivery-service.js +71 -0
  12. package/dist/src/capability/bot/dispatch-config.js +45 -0
  13. package/dist/src/capability/bot/fallback-delivery.js +147 -0
  14. package/dist/src/capability/bot/local-path-delivery.js +178 -0
  15. package/dist/src/capability/bot/sandbox-media.js +138 -0
  16. package/dist/src/capability/bot/service.js +49 -0
  17. package/dist/src/capability/bot/stream-delivery.js +321 -0
  18. package/dist/src/capability/bot/stream-finalizer.js +81 -0
  19. package/dist/src/capability/bot/stream-orchestrator.js +318 -0
  20. package/dist/src/capability/bot/types.js +1 -0
  21. package/{src/capability/calendar/client.ts → dist/src/capability/calendar/client.js} +118 -241
  22. package/{src/capability/calendar/schema.ts → dist/src/capability/calendar/schema.js} +0 -38
  23. package/dist/src/capability/calendar/tool.js +365 -0
  24. package/dist/src/capability/calendar/types.js +12 -0
  25. package/{src/capability/doc/client.ts → dist/src/capability/doc/client.js} +370 -605
  26. package/{src/capability/doc/schema.ts → dist/src/capability/doc/schema.js} +345 -394
  27. package/dist/src/capability/doc/tool.js +1556 -0
  28. package/dist/src/capability/doc/types.js +113 -0
  29. package/dist/src/capability/mcp/index.js +3 -0
  30. package/dist/src/capability/mcp/schema.js +102 -0
  31. package/dist/src/capability/mcp/tool.js +146 -0
  32. package/dist/src/capability/mcp/transport.js +293 -0
  33. package/dist/src/channel.js +224 -0
  34. package/dist/src/config/accounts.js +236 -0
  35. package/dist/src/config/derived-paths.js +31 -0
  36. package/dist/src/config/index.js +7 -0
  37. package/dist/src/config/media.js +110 -0
  38. package/dist/src/config/network.js +32 -0
  39. package/dist/src/config/routing.js +20 -0
  40. package/dist/src/config/runtime-config.js +25 -0
  41. package/dist/src/config/schema.js +4 -0
  42. package/{src/config-schema.ts → dist/src/config-schema.js} +1 -1
  43. package/dist/src/context-store.js +219 -0
  44. package/{src/crypto/aes.ts → dist/src/crypto/aes.js} +11 -28
  45. package/dist/src/crypto/index.js +9 -0
  46. package/{src/crypto/signature.ts → dist/src/crypto/signature.js} +3 -18
  47. package/{src/crypto/xml.ts → dist/src/crypto/xml.js} +3 -11
  48. package/dist/src/crypto.js +145 -0
  49. package/dist/src/domain/models.js +1 -0
  50. package/dist/src/domain/policies.js +32 -0
  51. package/{src/dynamic-agent.ts → dist/src/dynamic-agent.js} +36 -73
  52. package/dist/src/gateway-monitor.js +139 -0
  53. package/dist/src/http.js +114 -0
  54. package/{src/media.ts → dist/src/media.js} +21 -40
  55. package/dist/src/monitor/limits.js +7 -0
  56. package/dist/src/monitor/state.js +28 -0
  57. package/dist/src/monitor.js +84 -0
  58. package/dist/src/observability/audit-log.js +30 -0
  59. package/dist/src/observability/legacy-operational-event-store.js +22 -0
  60. package/dist/src/observability/raw-envelope-log.js +24 -0
  61. package/dist/src/observability/status-registry.js +9 -0
  62. package/dist/src/observability/transport-session-view.js +14 -0
  63. package/dist/src/onboarding.js +546 -0
  64. package/dist/src/outbound.js +557 -0
  65. package/dist/src/runtime/dispatcher.js +57 -0
  66. package/{src/runtime/index.ts → dist/src/runtime/index.js} +0 -1
  67. package/dist/src/runtime/outbound-intent.js +1 -0
  68. package/dist/src/runtime/reply-orchestrator.js +38 -0
  69. package/dist/src/runtime/routing-bridge.js +26 -0
  70. package/dist/src/runtime/session-manager.js +112 -0
  71. package/dist/src/runtime/source-registry.js +174 -0
  72. package/dist/src/runtime.js +1 -0
  73. package/dist/src/shared/command-auth.js +57 -0
  74. package/{src/shared/index.ts → dist/src/shared/index.js} +0 -1
  75. package/dist/src/shared/media-asset.js +65 -0
  76. package/dist/src/shared/media-service.js +59 -0
  77. package/dist/src/shared/media-types.js +1 -0
  78. package/{src/shared/xml-parser.ts → dist/src/shared/xml-parser.js} +72 -63
  79. package/dist/src/store/active-reply-store.js +41 -0
  80. package/dist/src/store/interfaces.js +1 -0
  81. package/dist/src/store/memory-store.js +33 -0
  82. package/dist/src/store/stream-batch-store.js +319 -0
  83. package/{src/target.ts → dist/src/target.js} +15 -48
  84. package/dist/src/transport/agent-api/client.js +168 -0
  85. package/dist/src/transport/agent-api/core.js +337 -0
  86. package/dist/src/transport/agent-api/delivery.js +28 -0
  87. package/dist/src/transport/agent-api/media-upload.js +4 -0
  88. package/dist/src/transport/agent-api/reply.js +24 -0
  89. package/dist/src/transport/agent-api/upstream-delivery.js +30 -0
  90. package/dist/src/transport/agent-api/upstream-media-upload.js +46 -0
  91. package/dist/src/transport/agent-api/upstream-reply.js +26 -0
  92. package/dist/src/transport/agent-callback/http-handler.js +30 -0
  93. package/dist/src/transport/agent-callback/inbound.js +4 -0
  94. package/dist/src/transport/agent-callback/reply.js +8 -0
  95. package/dist/src/transport/agent-callback/request-handler.js +189 -0
  96. package/dist/src/transport/agent-callback/session.js +15 -0
  97. package/dist/src/transport/bot-webhook/active-reply.js +27 -0
  98. package/dist/src/transport/bot-webhook/http-handler.js +31 -0
  99. package/dist/src/transport/bot-webhook/inbound-normalizer.js +496 -0
  100. package/dist/src/transport/bot-webhook/inbound.js +4 -0
  101. package/dist/src/transport/bot-webhook/message-shape.js +98 -0
  102. package/dist/src/transport/bot-webhook/protocol.js +124 -0
  103. package/dist/src/transport/bot-webhook/reply.js +9 -0
  104. package/dist/src/transport/bot-webhook/request-handler.js +285 -0
  105. package/dist/src/transport/bot-webhook/session.js +15 -0
  106. package/dist/src/transport/bot-ws/inbound.js +147 -0
  107. package/dist/src/transport/bot-ws/media.js +236 -0
  108. package/dist/src/transport/bot-ws/reply.js +310 -0
  109. package/dist/src/transport/bot-ws/sdk-adapter.js +257 -0
  110. package/dist/src/transport/bot-ws/session.js +15 -0
  111. package/dist/src/transport/http/common.js +78 -0
  112. package/dist/src/transport/http/registry.js +71 -0
  113. package/dist/src/transport/http/request-handler.js +51 -0
  114. package/{src/transport/index.ts → dist/src/transport/index.js} +2 -10
  115. package/dist/src/types/account.js +1 -0
  116. package/dist/src/types/config.js +1 -0
  117. package/dist/src/types/constants.js +28 -0
  118. package/dist/src/types/events.js +1 -0
  119. package/dist/src/types/index.js +1 -0
  120. package/dist/src/types/legacy-stream.js +1 -0
  121. package/dist/src/types/message.js +5 -0
  122. package/dist/src/types/runtime-context.js +1 -0
  123. package/dist/src/types/runtime.js +1 -0
  124. package/dist/src/types.js +1 -0
  125. package/dist/src/upstream/index.js +111 -0
  126. package/dist/src/wecom_msg_adapter/markdown_adapter.js +280 -0
  127. package/openclaw.plugin.json +15 -0
  128. package/package.json +18 -1
  129. package/.github/workflows/release.yml +0 -143
  130. package/GOVERNANCE.md +0 -26
  131. package/SKILLS_CAL.md +0 -895
  132. package/SKILLS_DOC.md +0 -2288
  133. package/UPSTREAM_CONFIG.md +0 -170
  134. package/UPSTREAM_PLAN.md +0 -175
  135. package/assets/01.bot-add.png +0 -0
  136. package/assets/01.bot-setp2.png +0 -0
  137. package/assets/01.image.jpg +0 -0
  138. package/assets/02.agent.add.png +0 -0
  139. package/assets/02.agent.api-set.png +0 -0
  140. package/assets/02.image.jpg +0 -0
  141. package/assets/03.agent.page.png +0 -0
  142. package/assets/03.bot.page.png +0 -0
  143. package/assets/link-me.jpg +0 -0
  144. package/assets/register.png +0 -0
  145. package/changelog/v2.2.28.md +0 -70
  146. package/changelog/v2.3.10.md +0 -17
  147. package/changelog/v2.3.11.md +0 -19
  148. package/changelog/v2.3.12.md +0 -25
  149. package/changelog/v2.3.13.md +0 -19
  150. package/changelog/v2.3.14.md +0 -48
  151. package/changelog/v2.3.15.md +0 -15
  152. package/changelog/v2.3.16.md +0 -11
  153. package/changelog/v2.3.18.md +0 -22
  154. package/changelog/v2.3.19.md +0 -73
  155. package/changelog/v2.3.2.md +0 -28
  156. package/changelog/v2.3.26.md +0 -21
  157. package/changelog/v2.3.27.md +0 -33
  158. package/changelog/v2.3.4.md +0 -20
  159. package/changelog/v2.3.9.md +0 -22
  160. package/changelog/v2.4.12.md +0 -37
  161. package/changelog/v2.4.16.md +0 -19
  162. package/compat-single-account.md +0 -148
  163. package/index.test.ts +0 -38
  164. package/scripts/test-proxy.ts +0 -70
  165. package/src/accounts.ts +0 -34
  166. package/src/agent/api-client.upload.test.ts +0 -109
  167. package/src/agent/handler.event-filter.test.ts +0 -100
  168. package/src/agent/handler.ts +0 -1105
  169. package/src/agent/index.ts +0 -12
  170. package/src/app/account-runtime.ts +0 -276
  171. package/src/app/bootstrap.ts +0 -29
  172. package/src/app/index.ts +0 -192
  173. package/src/capability/agent/delivery-service.ts +0 -87
  174. package/src/capability/agent/fallback-policy.ts +0 -13
  175. package/src/capability/agent/ingress-service.ts +0 -38
  176. package/src/capability/agent/upstream-delivery-service.ts +0 -96
  177. package/src/capability/bot/dispatch-config.ts +0 -47
  178. package/src/capability/bot/fallback-delivery.ts +0 -178
  179. package/src/capability/bot/local-path-delivery.ts +0 -215
  180. package/src/capability/bot/sandbox-media.test.ts +0 -221
  181. package/src/capability/bot/sandbox-media.ts +0 -176
  182. package/src/capability/bot/service.ts +0 -56
  183. package/src/capability/bot/stream-delivery.ts +0 -379
  184. package/src/capability/bot/stream-finalizer.ts +0 -120
  185. package/src/capability/bot/stream-orchestrator.ts +0 -371
  186. package/src/capability/bot/types.ts +0 -8
  187. package/src/capability/calendar/SKILLS_CHECKLIST.md +0 -251
  188. package/src/capability/calendar/tool.ts +0 -417
  189. package/src/capability/calendar/types.ts +0 -309
  190. package/src/capability/doc/tool.ts +0 -1629
  191. package/src/capability/doc/types.ts +0 -792
  192. package/src/capability/mcp/index.ts +0 -10
  193. package/src/capability/mcp/schema.ts +0 -107
  194. package/src/capability/mcp/tool.ts +0 -174
  195. package/src/capability/mcp/transport.ts +0 -394
  196. package/src/channel.config.test.ts +0 -147
  197. package/src/channel.lifecycle.test.ts +0 -255
  198. package/src/channel.meta.test.ts +0 -26
  199. package/src/channel.ts +0 -256
  200. package/src/config/accounts.resolve.test.ts +0 -75
  201. package/src/config/accounts.ts +0 -296
  202. package/src/config/derived-paths.test.ts +0 -111
  203. package/src/config/derived-paths.ts +0 -41
  204. package/src/config/index.ts +0 -26
  205. package/src/config/media.test.ts +0 -113
  206. package/src/config/media.ts +0 -139
  207. package/src/config/network.ts +0 -53
  208. package/src/config/routing.test.ts +0 -88
  209. package/src/config/routing.ts +0 -26
  210. package/src/config/runtime-config.ts +0 -46
  211. package/src/config/schema.ts +0 -90
  212. package/src/context-store.ts +0 -297
  213. package/src/crypto/index.ts +0 -24
  214. package/src/crypto.test.ts +0 -32
  215. package/src/crypto.ts +0 -176
  216. package/src/domain/models.ts +0 -7
  217. package/src/domain/policies.ts +0 -36
  218. package/src/dynamic-agent.account-scope.test.ts +0 -17
  219. package/src/gateway-monitor.ts +0 -181
  220. package/src/http.ts +0 -145
  221. package/src/media.test.ts +0 -82
  222. package/src/monitor/limits.ts +0 -7
  223. package/src/monitor/state.queue.test.ts +0 -185
  224. package/src/monitor/state.ts +0 -34
  225. package/src/monitor.active.test.ts +0 -245
  226. package/src/monitor.inbound-filter.test.ts +0 -63
  227. package/src/monitor.integration.test.ts +0 -208
  228. package/src/monitor.ts +0 -121
  229. package/src/monitor.webhook.test.ts +0 -774
  230. package/src/observability/audit-log.ts +0 -48
  231. package/src/observability/legacy-operational-event-store.ts +0 -36
  232. package/src/observability/raw-envelope-log.ts +0 -28
  233. package/src/observability/status-registry.ts +0 -13
  234. package/src/observability/transport-session-view.ts +0 -14
  235. package/src/onboarding.test.ts +0 -336
  236. package/src/onboarding.ts +0 -704
  237. package/src/outbound.test.ts +0 -1271
  238. package/src/outbound.ts +0 -746
  239. package/src/runtime/dispatcher.ts +0 -71
  240. package/src/runtime/outbound-intent.ts +0 -4
  241. package/src/runtime/reply-orchestrator.test.ts +0 -71
  242. package/src/runtime/reply-orchestrator.ts +0 -67
  243. package/src/runtime/routing-bridge.test.ts +0 -115
  244. package/src/runtime/routing-bridge.ts +0 -44
  245. package/src/runtime/session-manager.test.ts +0 -174
  246. package/src/runtime/session-manager.ts +0 -139
  247. package/src/runtime/source-registry.ts +0 -249
  248. package/src/runtime.ts +0 -14
  249. package/src/shared/command-auth.ts +0 -87
  250. package/src/shared/media-asset.ts +0 -78
  251. package/src/shared/media-service.test.ts +0 -111
  252. package/src/shared/media-service.ts +0 -84
  253. package/src/shared/media-types.ts +0 -5
  254. package/src/shared/xml-parser.test.ts +0 -50
  255. package/src/store/active-reply-store.ts +0 -42
  256. package/src/store/interfaces.ts +0 -11
  257. package/src/store/memory-store.ts +0 -43
  258. package/src/store/stream-batch-store.ts +0 -350
  259. package/src/transport/agent-api/client.ts +0 -277
  260. package/src/transport/agent-api/core.ts +0 -463
  261. package/src/transport/agent-api/delivery.ts +0 -41
  262. package/src/transport/agent-api/media-upload.ts +0 -11
  263. package/src/transport/agent-api/reply.ts +0 -39
  264. package/src/transport/agent-api/upstream-delivery.ts +0 -45
  265. package/src/transport/agent-api/upstream-media-upload.ts +0 -70
  266. package/src/transport/agent-api/upstream-reply.ts +0 -43
  267. package/src/transport/agent-callback/http-handler.ts +0 -47
  268. package/src/transport/agent-callback/inbound.ts +0 -5
  269. package/src/transport/agent-callback/reply.ts +0 -13
  270. package/src/transport/agent-callback/request-handler.ts +0 -244
  271. package/src/transport/agent-callback/session.ts +0 -23
  272. package/src/transport/bot-webhook/active-reply.ts +0 -39
  273. package/src/transport/bot-webhook/http-handler.ts +0 -48
  274. package/src/transport/bot-webhook/inbound-normalizer.test.ts +0 -433
  275. package/src/transport/bot-webhook/inbound-normalizer.ts +0 -558
  276. package/src/transport/bot-webhook/inbound.ts +0 -5
  277. package/src/transport/bot-webhook/message-shape.ts +0 -92
  278. package/src/transport/bot-webhook/protocol.ts +0 -148
  279. package/src/transport/bot-webhook/reply.ts +0 -15
  280. package/src/transport/bot-webhook/request-handler.ts +0 -394
  281. package/src/transport/bot-webhook/session.ts +0 -23
  282. package/src/transport/bot-ws/inbound.test.ts +0 -290
  283. package/src/transport/bot-ws/inbound.ts +0 -163
  284. package/src/transport/bot-ws/media.test.ts +0 -44
  285. package/src/transport/bot-ws/media.ts +0 -321
  286. package/src/transport/bot-ws/reply.test.ts +0 -450
  287. package/src/transport/bot-ws/reply.ts +0 -365
  288. package/src/transport/bot-ws/sdk-adapter.test.ts +0 -187
  289. package/src/transport/bot-ws/sdk-adapter.ts +0 -314
  290. package/src/transport/bot-ws/session.ts +0 -28
  291. package/src/transport/http/common.ts +0 -109
  292. package/src/transport/http/registry.ts +0 -92
  293. package/src/transport/http/request-handler.ts +0 -84
  294. package/src/types/account.ts +0 -70
  295. package/src/types/config.ts +0 -114
  296. package/src/types/constants.ts +0 -31
  297. package/src/types/events.ts +0 -21
  298. package/src/types/global.d.ts +0 -9
  299. package/src/types/index.ts +0 -17
  300. package/src/types/legacy-stream.ts +0 -50
  301. package/src/types/message.ts +0 -189
  302. package/src/types/runtime-context.ts +0 -28
  303. package/src/types/runtime.ts +0 -165
  304. package/src/types.ts +0 -41
  305. package/src/upstream/index.ts +0 -150
  306. package/src/upstream.test.ts +0 -84
  307. package/src/wecom_msg_adapter/markdown_adapter.ts +0 -331
  308. package/tsconfig.json +0 -22
  309. package/vitest.config.ts +0 -26
  310. /package/{src/capability/agent/index.ts → dist/src/capability/agent/index.js} +0 -0
  311. /package/{src/capability/bot/index.ts → dist/src/capability/bot/index.js} +0 -0
  312. /package/{src/capability/calendar/index.ts → dist/src/capability/calendar/index.js} +0 -0
  313. /package/{src/capability/index.ts → dist/src/capability/index.js} +0 -0
@@ -1,10 +0,0 @@
1
- export { createWeComMcpToolFactory } from "./tool.js";
2
- export {
3
- clearWecomMcpAccountCache,
4
- clearWecomMcpCategoryCache,
5
- McpHttpError,
6
- McpRpcError,
7
- sendJsonRpc,
8
- type McpToolInfo,
9
- } from "./transport.js";
10
- export { cleanSchemaForGemini } from "./schema.js";
@@ -1,107 +0,0 @@
1
- const GEMINI_UNSUPPORTED_KEYWORDS = new Set([
2
- "patternProperties",
3
- "additionalProperties",
4
- "$schema",
5
- "$id",
6
- "$ref",
7
- "$defs",
8
- "definitions",
9
- "examples",
10
- "minLength",
11
- "maxLength",
12
- "minimum",
13
- "maximum",
14
- "multipleOf",
15
- "pattern",
16
- "format",
17
- "minItems",
18
- "maxItems",
19
- "uniqueItems",
20
- "minProperties",
21
- "maxProperties",
22
- ]);
23
-
24
- export function cleanSchemaForGemini(schema: unknown): unknown {
25
- if (!schema || typeof schema !== "object") return schema;
26
- if (Array.isArray(schema)) return schema.map(cleanSchemaForGemini);
27
-
28
- const obj = schema as Record<string, unknown>;
29
- const defs: Record<string, unknown> = {
30
- ...(obj.$defs && typeof obj.$defs === "object" ? (obj.$defs as Record<string, unknown>) : {}),
31
- ...(obj.definitions && typeof obj.definitions === "object"
32
- ? (obj.definitions as Record<string, unknown>)
33
- : {}),
34
- };
35
- return cleanWithDefs(obj, defs, new Set());
36
- }
37
-
38
- function cleanWithDefs(
39
- schema: unknown,
40
- defs: Record<string, unknown>,
41
- refStack: Set<string>,
42
- ): unknown {
43
- if (!schema || typeof schema !== "object") return schema;
44
- if (Array.isArray(schema)) return schema.map((item) => cleanWithDefs(item, defs, refStack));
45
-
46
- const obj = schema as Record<string, unknown>;
47
- if (obj.$defs && typeof obj.$defs === "object") {
48
- Object.assign(defs, obj.$defs as Record<string, unknown>);
49
- }
50
- if (obj.definitions && typeof obj.definitions === "object") {
51
- Object.assign(defs, obj.definitions as Record<string, unknown>);
52
- }
53
-
54
- if (typeof obj.$ref === "string") {
55
- const ref = obj.$ref;
56
- if (refStack.has(ref)) return {};
57
- const match = ref.match(/^#\/(?:\$defs|definitions)\/(.+)$/);
58
- if (match?.[1] && defs[match[1]]) {
59
- const nextStack = new Set(refStack);
60
- nextStack.add(ref);
61
- return cleanWithDefs(defs[match[1]], defs, nextStack);
62
- }
63
- return {};
64
- }
65
-
66
- const cleaned: Record<string, unknown> = {};
67
- for (const [key, value] of Object.entries(obj)) {
68
- if (GEMINI_UNSUPPORTED_KEYWORDS.has(key)) continue;
69
- if (key === "const") {
70
- cleaned.enum = [value];
71
- continue;
72
- }
73
- if (key === "properties" && value && typeof value === "object" && !Array.isArray(value)) {
74
- cleaned[key] = Object.fromEntries(
75
- Object.entries(value as Record<string, unknown>).map(([propKey, propValue]) => [
76
- propKey,
77
- cleanWithDefs(propValue, defs, refStack),
78
- ]),
79
- );
80
- continue;
81
- }
82
- if (key === "items" && value) {
83
- cleaned[key] = Array.isArray(value)
84
- ? value.map((item) => cleanWithDefs(item, defs, refStack))
85
- : cleanWithDefs(value, defs, refStack);
86
- continue;
87
- }
88
- if ((key === "anyOf" || key === "oneOf" || key === "allOf") && Array.isArray(value)) {
89
- const nonNull = value.filter((variant) => {
90
- if (!variant || typeof variant !== "object") return true;
91
- return (variant as Record<string, unknown>).type !== "null";
92
- });
93
- if (nonNull.length === 1) {
94
- const single = cleanWithDefs(nonNull[0], defs, refStack);
95
- if (single && typeof single === "object" && !Array.isArray(single)) {
96
- Object.assign(cleaned, single as Record<string, unknown>);
97
- }
98
- } else {
99
- cleaned[key] = nonNull.map((variant) => cleanWithDefs(variant, defs, refStack));
100
- }
101
- continue;
102
- }
103
- cleaned[key] = value;
104
- }
105
-
106
- return cleaned;
107
- }
@@ -1,174 +0,0 @@
1
- import type {
2
- OpenClawPluginToolContext,
3
- OpenClawPluginToolFactory,
4
- } from "openclaw/plugin-sdk/core";
5
- import { resolveWecomSourceSnapshot } from "../../runtime/source-registry.js";
6
- import { cleanSchemaForGemini } from "./schema.js";
7
- import { clearWecomMcpCategoryCache, sendJsonRpc, type McpToolInfo } from "./transport.js";
8
-
9
- type WecomMcpParams = {
10
- action: "list" | "call";
11
- category: string;
12
- method?: string;
13
- args?: string | Record<string, unknown>;
14
- };
15
-
16
- const BIZ_CACHE_CLEAR_ERROR_CODES = new Set([850002]);
17
-
18
- function textResult<TDetails>(data: TDetails) {
19
- return {
20
- content: [{ type: "text" as const, text: JSON.stringify(data, null, 2) }],
21
- details: data,
22
- };
23
- }
24
-
25
- function errorResult(error: unknown) {
26
- if (error && typeof error === "object" && "errcode" in error) {
27
- const errcode = Number((error as { errcode?: number }).errcode ?? 0);
28
- const errmsg = String((error as { errmsg?: string }).errmsg ?? `错误码: ${errcode}`);
29
- return textResult({ error: errmsg, errcode });
30
- }
31
- return textResult({
32
- error: error instanceof Error ? error.message : String(error),
33
- });
34
- }
35
-
36
- function parseArgs(args: string | Record<string, unknown> | undefined): Record<string, unknown> {
37
- if (!args) return {};
38
- if (typeof args === "object") return args;
39
- try {
40
- return JSON.parse(args) as Record<string, unknown>;
41
- } catch (error) {
42
- const detail = error instanceof SyntaxError ? error.message : String(error);
43
- throw new Error(`args 不是合法的 JSON: ${args} (${detail})`);
44
- }
45
- }
46
-
47
- function extractToolAccountId(ctx: OpenClawPluginToolContext): string | undefined {
48
- const explicit = String((ctx as { accountId?: string }).accountId ?? "").trim();
49
- if (explicit) return explicit;
50
- const agentAccountId = String(ctx.agentAccountId ?? "").trim();
51
- return agentAccountId || undefined;
52
- }
53
-
54
- async function handleList(accountId: string, category: string): Promise<unknown> {
55
- const result = (await sendJsonRpc(accountId, category, "tools/list")) as
56
- | { tools?: McpToolInfo[] }
57
- | undefined;
58
- const tools = result?.tools ?? [];
59
- return {
60
- accountId,
61
- category,
62
- count: tools.length,
63
- tools: tools.map((tool) => ({
64
- name: tool.name,
65
- description: tool.description ?? "",
66
- inputSchema: tool.inputSchema ? cleanSchemaForGemini(tool.inputSchema) : undefined,
67
- })),
68
- };
69
- }
70
-
71
- function checkBizErrorAndClearCache(result: unknown, accountId: string, category: string): void {
72
- if (!result || typeof result !== "object") return;
73
- const content = (result as { content?: Array<{ type: string; text?: string }> }).content;
74
- if (!Array.isArray(content)) return;
75
- for (const item of content) {
76
- if (item.type !== "text" || !item.text) continue;
77
- try {
78
- const parsed = JSON.parse(item.text) as { errcode?: number };
79
- if (typeof parsed.errcode === "number" && BIZ_CACHE_CLEAR_ERROR_CODES.has(parsed.errcode)) {
80
- clearWecomMcpCategoryCache(accountId, category);
81
- return;
82
- }
83
- } catch {
84
- // Ignore non-JSON content.
85
- }
86
- }
87
- }
88
-
89
- async function handleCall(
90
- accountId: string,
91
- category: string,
92
- method: string,
93
- args: Record<string, unknown>,
94
- ): Promise<unknown> {
95
- const result = await sendJsonRpc(accountId, category, "tools/call", {
96
- name: method,
97
- arguments: args,
98
- });
99
- checkBizErrorAndClearCache(result, accountId, category);
100
- return result;
101
- }
102
-
103
- export function createWeComMcpToolFactory(): OpenClawPluginToolFactory {
104
- return (toolContext: OpenClawPluginToolContext) => {
105
- if (toolContext.messageChannel !== "wecom") {
106
- return null;
107
- }
108
- const accountId = extractToolAccountId(toolContext);
109
- const source = resolveWecomSourceSnapshot({
110
- accountId,
111
- sessionKey: toolContext.sessionKey,
112
- sessionId: toolContext.sessionId,
113
- });
114
- if (!source || source.source !== "bot-ws") {
115
- return null;
116
- }
117
-
118
- return {
119
- name: "wecom_mcp",
120
- label: "WeCom MCP",
121
- description:
122
- "企业微信 Bot WS MCP 工具。仅在 WeCom Bot WS 会话中可用,用于列出和调用企业微信 MCP 能力。",
123
- parameters: {
124
- type: "object" as const,
125
- properties: {
126
- action: {
127
- type: "string",
128
- enum: ["list", "call"],
129
- description: "操作类型:list 或 call",
130
- },
131
- category: {
132
- type: "string",
133
- description: "MCP 品类,如 contact、todo、meeting、doc",
134
- },
135
- method: {
136
- type: "string",
137
- description: "action=call 时要调用的工具方法名",
138
- },
139
- args: {
140
- type: "string",
141
- description: "action=call 时传入的 JSON 字符串参数,默认 {}",
142
- },
143
- },
144
- required: ["action", "category"],
145
- },
146
- async execute(_toolCallId: string, rawParams: unknown) {
147
- try {
148
- const params = rawParams as WecomMcpParams;
149
- const effectiveAccountId = extractToolAccountId(toolContext);
150
- if (!effectiveAccountId) {
151
- throw new Error("当前会话缺少 WeCom accountId,无法调用 wecom_mcp。");
152
- }
153
-
154
- if (params.action === "list") {
155
- return textResult(await handleList(effectiveAccountId, params.category));
156
- }
157
- if (!params.method) {
158
- return textResult({ error: "action=call 时必须提供 method" });
159
- }
160
- return textResult(
161
- await handleCall(
162
- effectiveAccountId,
163
- params.category,
164
- params.method,
165
- parseArgs(params.args),
166
- ),
167
- );
168
- } catch (error) {
169
- return errorResult(error);
170
- }
171
- },
172
- };
173
- };
174
- }
@@ -1,394 +0,0 @@
1
- import { generateReqId } from "@wecom/aibot-node-sdk";
2
- import { getBotWsPushHandle } from "../../runtime.js";
3
-
4
- const HTTP_REQUEST_TIMEOUT_MS = 30_000;
5
- const MCP_CONFIG_FETCH_TIMEOUT_MS = 15_000;
6
- const MCP_GET_CONFIG_CMD = "aibot_get_mcp_config";
7
- const MCP_PLUGIN_VERSION = "wecom-dual-plane";
8
- const LOG_TAG = "[wecom-mcp]";
9
-
10
- interface JsonRpcRequest {
11
- jsonrpc: "2.0";
12
- id?: string;
13
- method: string;
14
- params?: Record<string, unknown>;
15
- }
16
-
17
- interface JsonRpcResponse {
18
- jsonrpc: "2.0";
19
- id: number | string;
20
- result?: unknown;
21
- error?: {
22
- code: number;
23
- message: string;
24
- data?: unknown;
25
- };
26
- }
27
-
28
- interface McpSession {
29
- sessionId: string | null;
30
- initialized: boolean;
31
- stateless: boolean;
32
- }
33
-
34
- const CACHE_CLEAR_ERROR_CODES = new Set([-32001, -32002, -32003]);
35
-
36
- const mcpConfigCache = new Map<string, Record<string, unknown>>();
37
- const mcpSessionCache = new Map<string, McpSession>();
38
- const statelessKeys = new Set<string>();
39
- const inflightInitRequests = new Map<string, Promise<McpSession>>();
40
-
41
- function cacheKey(accountId: string, category: string): string {
42
- return `${accountId}::${category}`;
43
- }
44
-
45
- function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
46
- let timeoutId: ReturnType<typeof setTimeout> | undefined;
47
- const timeoutPromise = new Promise<never>((_, reject) => {
48
- timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs);
49
- });
50
- return Promise.race([promise, timeoutPromise]).finally(() => {
51
- if (timeoutId) clearTimeout(timeoutId);
52
- });
53
- }
54
-
55
- export class McpRpcError extends Error {
56
- constructor(
57
- public readonly code: number,
58
- message: string,
59
- public readonly data?: unknown,
60
- ) {
61
- super(message);
62
- this.name = "McpRpcError";
63
- }
64
- }
65
-
66
- export class McpHttpError extends Error {
67
- constructor(
68
- public readonly statusCode: number,
69
- message: string,
70
- ) {
71
- super(message);
72
- this.name = "McpHttpError";
73
- }
74
- }
75
-
76
- async function fetchMcpConfig(
77
- accountId: string,
78
- category: string,
79
- ): Promise<Record<string, unknown>> {
80
- const handle = getBotWsPushHandle(accountId);
81
- if (!handle?.isConnected()) {
82
- throw new Error(`当前企微账号 MCP 服务未就绪:account=${accountId} 的 Bot WS 未连接。`);
83
- }
84
-
85
- const response = await withTimeout(
86
- handle.replyCommand({
87
- cmd: MCP_GET_CONFIG_CMD,
88
- body: {
89
- biz_type: category,
90
- plugin_version: MCP_PLUGIN_VERSION,
91
- },
92
- headers: {
93
- req_id: generateReqId("mcp_config"),
94
- },
95
- }),
96
- MCP_CONFIG_FETCH_TIMEOUT_MS,
97
- `MCP config fetch timed out after ${MCP_CONFIG_FETCH_TIMEOUT_MS}ms`,
98
- );
99
-
100
- const errcode = Number((response as { errcode?: number }).errcode ?? 0);
101
- if (errcode !== 0) {
102
- throw new Error(
103
- `MCP 配置请求失败: errcode=${String((response as { errcode?: number }).errcode)} errmsg=${String((response as { errmsg?: string }).errmsg ?? "unknown")}`,
104
- );
105
- }
106
-
107
- const body = (response as { body?: { url?: string } }).body;
108
- if (!body?.url) {
109
- throw new Error(`MCP 配置响应缺少 url 字段 (account=${accountId}, category=${category})`);
110
- }
111
-
112
- console.log(`${LOG_TAG} config ready account=${accountId} category=${category} url=${body.url}`);
113
- return body as Record<string, unknown>;
114
- }
115
-
116
- async function getMcpUrl(accountId: string, category: string): Promise<string> {
117
- const key = cacheKey(accountId, category);
118
- const cached = mcpConfigCache.get(key);
119
- if (cached?.url) {
120
- return String(cached.url);
121
- }
122
- const body = await fetchMcpConfig(accountId, category);
123
- mcpConfigCache.set(key, body);
124
- return String(body.url);
125
- }
126
-
127
- async function sendRawJsonRpc(
128
- url: string,
129
- session: McpSession,
130
- body: JsonRpcRequest,
131
- ): Promise<{ rpcResult: unknown; newSessionId: string | null }> {
132
- const controller = new AbortController();
133
- const timeoutId = setTimeout(() => controller.abort(), HTTP_REQUEST_TIMEOUT_MS);
134
- try {
135
- const headers: Record<string, string> = {
136
- "Content-Type": "application/json",
137
- Accept: "application/json, text/event-stream",
138
- };
139
- if (session.sessionId) {
140
- headers["Mcp-Session-Id"] = session.sessionId;
141
- }
142
-
143
- const response = await fetch(url, {
144
- method: "POST",
145
- headers,
146
- body: JSON.stringify(body),
147
- signal: controller.signal,
148
- });
149
- const newSessionId = response.headers.get("mcp-session-id");
150
-
151
- if (!response.ok) {
152
- throw new McpHttpError(
153
- response.status,
154
- `MCP HTTP 请求失败: ${response.status} ${response.statusText}`,
155
- );
156
- }
157
-
158
- const contentLength = response.headers.get("content-length");
159
- if (response.status === 204 || contentLength === "0") {
160
- return { rpcResult: undefined, newSessionId };
161
- }
162
-
163
- const contentType = response.headers.get("content-type") ?? "";
164
- if (contentType.includes("text/event-stream")) {
165
- return {
166
- rpcResult: await parseSseResponse(response),
167
- newSessionId,
168
- };
169
- }
170
-
171
- const text = await response.text();
172
- if (!text.trim()) {
173
- return { rpcResult: undefined, newSessionId };
174
- }
175
-
176
- const rpc = JSON.parse(text) as JsonRpcResponse;
177
- if (rpc.error) {
178
- throw new McpRpcError(
179
- rpc.error.code,
180
- `MCP 调用错误 [${rpc.error.code}]: ${rpc.error.message}`,
181
- rpc.error.data,
182
- );
183
- }
184
- return { rpcResult: rpc.result, newSessionId };
185
- } catch (error) {
186
- if (error instanceof DOMException && error.name === "AbortError") {
187
- throw new Error(`MCP 请求超时 (${HTTP_REQUEST_TIMEOUT_MS}ms)`);
188
- }
189
- throw error;
190
- } finally {
191
- clearTimeout(timeoutId);
192
- }
193
- }
194
-
195
- async function initializeSession(
196
- accountId: string,
197
- category: string,
198
- url: string,
199
- ): Promise<McpSession> {
200
- const key = cacheKey(accountId, category);
201
- const session: McpSession = { sessionId: null, initialized: false, stateless: false };
202
-
203
- const initializeRequest: JsonRpcRequest = {
204
- jsonrpc: "2.0",
205
- id: generateReqId("mcp_init"),
206
- method: "initialize",
207
- params: {
208
- protocolVersion: "2025-03-26",
209
- capabilities: {},
210
- clientInfo: { name: "wecom_mcp", version: MCP_PLUGIN_VERSION },
211
- },
212
- };
213
-
214
- const initResult = await sendRawJsonRpc(url, session, initializeRequest);
215
- if (initResult.newSessionId) {
216
- session.sessionId = initResult.newSessionId;
217
- }
218
- if (!session.sessionId) {
219
- session.stateless = true;
220
- session.initialized = true;
221
- statelessKeys.add(key);
222
- mcpSessionCache.set(key, session);
223
- return session;
224
- }
225
-
226
- const notifyRequest: JsonRpcRequest = {
227
- jsonrpc: "2.0",
228
- method: "notifications/initialized",
229
- };
230
- const notifyResult = await sendRawJsonRpc(url, session, notifyRequest);
231
- if (notifyResult.newSessionId) {
232
- session.sessionId = notifyResult.newSessionId;
233
- }
234
- session.initialized = true;
235
- mcpSessionCache.set(key, session);
236
- return session;
237
- }
238
-
239
- async function getOrCreateSession(
240
- accountId: string,
241
- category: string,
242
- url: string,
243
- ): Promise<McpSession> {
244
- const key = cacheKey(accountId, category);
245
- if (statelessKeys.has(key)) {
246
- const cached = mcpSessionCache.get(key);
247
- if (cached) return cached;
248
- }
249
-
250
- const cached = mcpSessionCache.get(key);
251
- if (cached?.initialized) {
252
- return cached;
253
- }
254
-
255
- const inflight = inflightInitRequests.get(key);
256
- if (inflight) {
257
- return inflight;
258
- }
259
-
260
- const promise = initializeSession(accountId, category, url).finally(() => {
261
- inflightInitRequests.delete(key);
262
- });
263
- inflightInitRequests.set(key, promise);
264
- return promise;
265
- }
266
-
267
- async function rebuildSession(
268
- accountId: string,
269
- category: string,
270
- url: string,
271
- ): Promise<McpSession> {
272
- const key = cacheKey(accountId, category);
273
- const inflight = inflightInitRequests.get(key);
274
- if (inflight) return inflight;
275
- const promise = initializeSession(accountId, category, url).finally(() => {
276
- inflightInitRequests.delete(key);
277
- });
278
- inflightInitRequests.set(key, promise);
279
- return promise;
280
- }
281
-
282
- async function parseSseResponse(response: Response): Promise<unknown> {
283
- const text = await response.text();
284
- const lines = text.split("\n");
285
- let currentParts: string[] = [];
286
- let lastEventData = "";
287
-
288
- for (const line of lines) {
289
- if (line.startsWith("data: ")) {
290
- currentParts.push(line.slice(6));
291
- continue;
292
- }
293
- if (line.startsWith("data:")) {
294
- currentParts.push(line.slice(5));
295
- continue;
296
- }
297
- if (line.trim() === "" && currentParts.length > 0) {
298
- lastEventData = currentParts.join("\n").trim();
299
- currentParts = [];
300
- }
301
- }
302
- if (currentParts.length > 0) {
303
- lastEventData = currentParts.join("\n").trim();
304
- }
305
- if (!lastEventData) {
306
- throw new Error("SSE 响应中未包含有效数据");
307
- }
308
-
309
- const rpc = JSON.parse(lastEventData) as JsonRpcResponse;
310
- if (rpc.error) {
311
- throw new McpRpcError(
312
- rpc.error.code,
313
- `MCP 调用错误 [${rpc.error.code}]: ${rpc.error.message}`,
314
- rpc.error.data,
315
- );
316
- }
317
- return rpc.result;
318
- }
319
-
320
- export function clearWecomMcpCategoryCache(accountId: string, category: string): void {
321
- const key = cacheKey(accountId, category);
322
- console.log(`${LOG_TAG} clear cache account=${accountId} category=${category}`);
323
- mcpConfigCache.delete(key);
324
- mcpSessionCache.delete(key);
325
- statelessKeys.delete(key);
326
- inflightInitRequests.delete(key);
327
- }
328
-
329
- export function clearWecomMcpAccountCache(accountId: string): void {
330
- const prefix = `${accountId}::`;
331
- for (const key of [...mcpConfigCache.keys()]) {
332
- if (key.startsWith(prefix)) mcpConfigCache.delete(key);
333
- }
334
- for (const key of [...mcpSessionCache.keys()]) {
335
- if (key.startsWith(prefix)) mcpSessionCache.delete(key);
336
- }
337
- for (const key of [...statelessKeys]) {
338
- if (key.startsWith(prefix)) statelessKeys.delete(key);
339
- }
340
- for (const key of [...inflightInitRequests.keys()]) {
341
- if (key.startsWith(prefix)) inflightInitRequests.delete(key);
342
- }
343
- }
344
-
345
- export interface McpToolInfo {
346
- name: string;
347
- description?: string;
348
- inputSchema?: Record<string, unknown>;
349
- }
350
-
351
- export async function sendJsonRpc(
352
- accountId: string,
353
- category: string,
354
- method: string,
355
- params?: Record<string, unknown>,
356
- ): Promise<unknown> {
357
- const url = await getMcpUrl(accountId, category);
358
- const body: JsonRpcRequest = {
359
- jsonrpc: "2.0",
360
- id: generateReqId("mcp_rpc"),
361
- method,
362
- ...(params !== undefined ? { params } : {}),
363
- };
364
-
365
- let session = await getOrCreateSession(accountId, category, url);
366
-
367
- try {
368
- const result = await sendRawJsonRpc(url, session, body);
369
- if (result.newSessionId) {
370
- session.sessionId = result.newSessionId;
371
- }
372
- return result.rpcResult;
373
- } catch (error) {
374
- if (error instanceof McpRpcError && CACHE_CLEAR_ERROR_CODES.has(error.code)) {
375
- clearWecomMcpCategoryCache(accountId, category);
376
- }
377
- if (session.stateless) {
378
- throw error;
379
- }
380
- if (error instanceof McpHttpError && error.statusCode === 404) {
381
- mcpSessionCache.delete(cacheKey(accountId, category));
382
- session = await rebuildSession(accountId, category, url);
383
- const result = await sendRawJsonRpc(url, session, body);
384
- if (result.newSessionId) {
385
- session.sessionId = result.newSessionId;
386
- }
387
- return result.rpcResult;
388
- }
389
- console.error(
390
- `${LOG_TAG} rpc failed account=${accountId} category=${category} method=${method} error=${error instanceof Error ? error.message : String(error)}`,
391
- );
392
- throw error;
393
- }
394
- }