@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,148 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from "node:http";
2
-
3
- import type { ResolvedBotAccount, WecomBotInboundMessage as WecomInboundMessage } from "../../types/index.js";
4
- import { LIMITS } from "../../monitor/state.js";
5
- import { computeWecomMsgSignature, encryptWecomPlaintext } from "../../crypto.js";
6
- import type { StreamState } from "../../types/legacy-stream.js";
7
- import type { WecomWebhookTarget } from "../../types/runtime-context.js";
8
-
9
- function truncateUtf8Bytes(text: string, maxBytes: number): string {
10
- const buf = Buffer.from(text, "utf8");
11
- if (buf.length <= maxBytes) return text;
12
- const slice = buf.subarray(buf.length - maxBytes);
13
- return slice.toString("utf8");
14
- }
15
-
16
- export function jsonOk(res: ServerResponse, body: unknown): void {
17
- res.statusCode = 200;
18
- res.setHeader("Content-Type", "text/plain; charset=utf-8");
19
- res.end(JSON.stringify(body));
20
- }
21
-
22
- export async function readBotWebhookJsonBody(req: IncomingMessage, maxBytes: number) {
23
- const chunks: Buffer[] = [];
24
- let total = 0;
25
- return await new Promise<{ ok: boolean; value?: unknown; error?: string }>((resolve) => {
26
- req.on("data", (chunk: Buffer) => {
27
- total += chunk.length;
28
- if (total > maxBytes) {
29
- resolve({ ok: false, error: "payload too large" });
30
- req.destroy();
31
- return;
32
- }
33
- chunks.push(chunk);
34
- });
35
- req.on("end", () => {
36
- try {
37
- const raw = Buffer.concat(chunks).toString("utf8");
38
- if (!raw.trim()) {
39
- resolve({ ok: false, error: "empty payload" });
40
- return;
41
- }
42
- resolve({ ok: true, value: JSON.parse(raw) as unknown });
43
- } catch (err) {
44
- resolve({ ok: false, error: err instanceof Error ? err.message : String(err) });
45
- }
46
- });
47
- req.on("error", (err) => {
48
- resolve({ ok: false, error: err instanceof Error ? err.message : String(err) });
49
- });
50
- });
51
- }
52
-
53
- export function buildEncryptedBotWebhookReply(params: {
54
- account: ResolvedBotAccount;
55
- plaintextJson: unknown;
56
- nonce: string;
57
- timestamp: string;
58
- }): { encrypt: string; msgsignature: string; timestamp: string; nonce: string } {
59
- const plaintext = JSON.stringify(params.plaintextJson ?? {});
60
- const encrypt = encryptWecomPlaintext({
61
- encodingAESKey: params.account.encodingAESKey ?? "",
62
- receiveId: params.account.receiveId ?? "",
63
- plaintext,
64
- });
65
- const msgsignature = computeWecomMsgSignature({
66
- token: params.account.token ?? "",
67
- timestamp: params.timestamp,
68
- nonce: params.nonce,
69
- encrypt,
70
- });
71
- return {
72
- encrypt,
73
- msgsignature,
74
- timestamp: params.timestamp,
75
- nonce: params.nonce,
76
- };
77
- }
78
-
79
- export function resolveBotIdentitySet(target: WecomWebhookTarget): Set<string> {
80
- const ids = new Set<string>();
81
- const single = target.account.config.aibotid?.trim();
82
- if (single) ids.add(single);
83
- for (const botId of target.account.config.botIds ?? []) {
84
- const normalized = String(botId ?? "").trim();
85
- if (normalized) ids.add(normalized);
86
- }
87
- return ids;
88
- }
89
-
90
- export function buildStreamPlaceholderReply(params: {
91
- streamId: string;
92
- placeholderContent?: string;
93
- }): { msgtype: "stream"; stream: { id: string; finish: boolean; content: string } } {
94
- const content = params.placeholderContent?.trim() || "1";
95
- return {
96
- msgtype: "stream",
97
- stream: {
98
- id: params.streamId,
99
- finish: false,
100
- content,
101
- },
102
- };
103
- }
104
-
105
- export function buildStreamTextPlaceholderReply(params: {
106
- streamId: string;
107
- content: string;
108
- }): { msgtype: "stream"; stream: { id: string; finish: boolean; content: string } } {
109
- return {
110
- msgtype: "stream",
111
- stream: {
112
- id: params.streamId,
113
- finish: false,
114
- content: params.content.trim() || "1",
115
- },
116
- };
117
- }
118
-
119
- export function buildStreamReplyFromState(state: StreamState): {
120
- msgtype: "stream";
121
- stream: { id: string; finish: boolean; content: string; msg_item?: Array<{ msgtype: string; image: { base64: string; md5: string } }> };
122
- } {
123
- const content = truncateUtf8Bytes(state.content, LIMITS.STREAM_MAX_BYTES);
124
- return {
125
- msgtype: "stream",
126
- stream: {
127
- id: state.streamId,
128
- finish: state.finished,
129
- content,
130
- ...(state.finished && state.images?.length
131
- ? {
132
- msg_item: state.images.map((img) => ({
133
- msgtype: "image",
134
- image: { base64: img.base64, md5: img.md5 },
135
- })),
136
- }
137
- : {}),
138
- },
139
- };
140
- }
141
-
142
- export function parseWecomPlainMessage(raw: string): WecomInboundMessage {
143
- const parsed = JSON.parse(raw) as unknown;
144
- if (!parsed || typeof parsed !== "object") {
145
- return {};
146
- }
147
- return parsed as WecomInboundMessage;
148
- }
@@ -1,15 +0,0 @@
1
- import type { ReplyContext } from "../../types/index.js";
2
-
3
- export function createBotWebhookReplyContext(params: {
4
- accountId: string;
5
- responseUrl?: string;
6
- raw: ReplyContext["raw"];
7
- }): ReplyContext {
8
- return {
9
- transport: "bot-webhook",
10
- accountId: params.accountId,
11
- responseUrl: params.responseUrl,
12
- passiveWindowMs: 5_000,
13
- raw: params.raw,
14
- };
15
- }
@@ -1,394 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from "node:http";
2
-
3
- import { getWecomRuntime } from "../../runtime.js";
4
- import { decryptWecomEncrypted, verifyWecomSignature } from "../../crypto.js";
5
- import { resolveWecomEgressProxyUrl } from "../../config/index.js";
6
- import { LIMITS, type StreamStore } from "../../monitor/state.js";
7
- import type { StartBotAgentStreamParams } from "../../capability/bot/stream-orchestrator.js";
8
- import type { WecomBotInboundMessage as WecomInboundMessage } from "../../types/index.js";
9
- import type { WecomRuntimeAuditEvent, WecomWebhookTarget } from "../../types/runtime-context.js";
10
- import { wecomFetch } from "../../http.js";
11
- import {
12
- logRouteFailure,
13
- resolveQueryParams,
14
- resolveSignatureParam,
15
- type RouteFailureReason,
16
- writeRouteFailure,
17
- } from "../http/common.js";
18
- import {
19
- buildEncryptedBotWebhookReply,
20
- buildStreamPlaceholderReply,
21
- buildStreamReplyFromState,
22
- buildStreamTextPlaceholderReply,
23
- jsonOk,
24
- parseWecomPlainMessage,
25
- readBotWebhookJsonBody,
26
- resolveBotIdentitySet,
27
- } from "./protocol.js";
28
- import { storeActiveReply } from "./active-reply.js";
29
- import { buildInboundBody, resolveWecomSenderUserId, shouldProcessBotInboundMessage } from "./message-shape.js";
30
-
31
- const ERROR_HELP = "\n\n遇到问题?联系作者: YanHaidao (微信: YanHaidao)";
32
-
33
- type RecordBotOperationalEvent = (
34
- target: Pick<WecomWebhookTarget, "account" | "auditSink">,
35
- event: Omit<WecomRuntimeAuditEvent, "transport">,
36
- ) => void;
37
-
38
- export function createBotWebhookRequestHandler(params: {
39
- streamStore: StreamStore;
40
- logInfo: (target: WecomWebhookTarget, message: string) => void;
41
- logVerbose: (target: WecomWebhookTarget, message: string) => void;
42
- recordBotOperationalEvent: RecordBotOperationalEvent;
43
- startAgentForStream: (params: StartBotAgentStreamParams) => Promise<void>;
44
- }) {
45
- const { streamStore, logInfo, logVerbose, recordBotOperationalEvent, startAgentForStream } = params;
46
-
47
- return async function handleBotWebhookRequest(args: {
48
- req: IncomingMessage;
49
- res: ServerResponse;
50
- path: string;
51
- reqId: string;
52
- targets: WecomWebhookTarget[];
53
- }): Promise<boolean> {
54
- const { req, res, path, reqId, targets } = args;
55
- const query = resolveQueryParams(req);
56
- const timestamp = query.get("timestamp") ?? "";
57
- const nonce = query.get("nonce") ?? "";
58
- const signature = resolveSignatureParam(query);
59
-
60
- if (req.method === "GET") {
61
- const echostr = query.get("echostr") ?? "";
62
- const signatureMatches = targets.filter(
63
- (target) =>
64
- target.account.token &&
65
- verifyWecomSignature({ token: target.account.token, timestamp, nonce, encrypt: echostr, signature }),
66
- );
67
- if (signatureMatches.length !== 1) {
68
- const reason: RouteFailureReason =
69
- signatureMatches.length === 0 ? "wecom_account_not_found" : "wecom_account_conflict";
70
- const candidateIds = (signatureMatches.length > 0 ? signatureMatches : targets).map((target) => target.account.accountId);
71
- logRouteFailure({
72
- reqId,
73
- path,
74
- method: "GET",
75
- reason,
76
- candidateAccountIds: candidateIds,
77
- });
78
- writeRouteFailure(
79
- res,
80
- reason,
81
- reason === "wecom_account_conflict"
82
- ? "Bot callback account conflict: multiple accounts matched signature."
83
- : "Bot callback account not found: signature verification failed.",
84
- );
85
- return true;
86
- }
87
- const target = signatureMatches[0]!;
88
- try {
89
- const plain = decryptWecomEncrypted({
90
- encodingAESKey: target.account.encodingAESKey,
91
- receiveId: target.account.receiveId,
92
- encrypt: echostr,
93
- });
94
- res.statusCode = 200;
95
- res.setHeader("Content-Type", "text/plain; charset=utf-8");
96
- res.end(plain);
97
- return true;
98
- } catch (err) {
99
- res.statusCode = 400;
100
- res.setHeader("Content-Type", "text/plain; charset=utf-8");
101
- res.end(`decrypt failed - 解密失败,请检查 EncodingAESKey${ERROR_HELP}`);
102
- return true;
103
- }
104
- }
105
-
106
- if (req.method !== "POST") return false;
107
-
108
- const body = await readBotWebhookJsonBody(req, 1024 * 1024);
109
- if (!body.ok) {
110
- res.statusCode = 400;
111
- res.end(body.error || "invalid payload");
112
- return true;
113
- }
114
- const record = body.value as any;
115
- const encrypt = String(record?.encrypt ?? record?.Encrypt ?? "");
116
- console.log(
117
- `[wecom] inbound(bot): reqId=${reqId} rawJsonBytes=${Buffer.byteLength(JSON.stringify(record), "utf8")} hasEncrypt=${Boolean(encrypt)} encryptLen=${encrypt.length}`,
118
- );
119
- const signatureMatches = targets.filter(
120
- (target) =>
121
- target.account.token && verifyWecomSignature({ token: target.account.token, timestamp, nonce, encrypt, signature }),
122
- );
123
- if (signatureMatches.length !== 1) {
124
- const reason: RouteFailureReason =
125
- signatureMatches.length === 0 ? "wecom_account_not_found" : "wecom_account_conflict";
126
- const candidateIds = (signatureMatches.length > 0 ? signatureMatches : targets).map((target) => target.account.accountId);
127
- logRouteFailure({
128
- reqId,
129
- path,
130
- method: "POST",
131
- reason,
132
- candidateAccountIds: candidateIds,
133
- });
134
- writeRouteFailure(
135
- res,
136
- reason,
137
- reason === "wecom_account_conflict"
138
- ? "Bot callback account conflict: multiple accounts matched signature."
139
- : "Bot callback account not found: signature verification failed.",
140
- );
141
- return true;
142
- }
143
-
144
- const target = signatureMatches[0]!;
145
- let msg: WecomInboundMessage;
146
- try {
147
- const plain = decryptWecomEncrypted({
148
- encodingAESKey: target.account.encodingAESKey,
149
- receiveId: target.account.receiveId,
150
- encrypt,
151
- });
152
- msg = parseWecomPlainMessage(plain);
153
- } catch {
154
- res.statusCode = 400;
155
- res.setHeader("Content-Type", "text/plain; charset=utf-8");
156
- res.end(`decrypt failed - 解密失败,请检查 EncodingAESKey${ERROR_HELP}`);
157
- return true;
158
- }
159
-
160
- const expected = resolveBotIdentitySet(target);
161
- if (expected.size > 0) {
162
- const inboundAibotId = String((msg as any).aibotid ?? "").trim();
163
- if (!inboundAibotId || !expected.has(inboundAibotId)) {
164
- target.runtime.error?.(
165
- `[wecom] inbound(bot): reqId=${reqId} accountId=${target.account.accountId} aibotid_mismatch expected=${Array.from(expected).join(",")} actual=${inboundAibotId || "N/A"}`,
166
- );
167
- }
168
- }
169
-
170
- logInfo(target, `inbound(bot): reqId=${reqId} selectedAccount=${target.account.accountId} path=${path}`);
171
- target.touchTransportSession?.({ lastInboundAt: Date.now(), running: true });
172
- const msgtype = String(msg.msgtype ?? "").toLowerCase();
173
- const proxyUrl = resolveWecomEgressProxyUrl(target.config);
174
-
175
- if (msgtype === "event") {
176
- const eventtype = String((msg as any).event?.eventtype ?? "").toLowerCase();
177
-
178
- if (eventtype === "template_card_event") {
179
- const msgid = msg.msgid ? String(msg.msgid) : undefined;
180
- if (msgid && streamStore.getStreamByMsgId(msgid)) {
181
- logVerbose(target, `template_card_event: already processed msgid=${msgid}, skipping`);
182
- recordBotOperationalEvent(target, {
183
- category: "duplicate-reply",
184
- messageId: msgid,
185
- summary: `duplicate template card event msgid=${msgid}`,
186
- raw: {
187
- transport: "bot-webhook",
188
- envelopeType: "json",
189
- body: msg,
190
- },
191
- });
192
- jsonOk(res, buildEncryptedBotWebhookReply({ account: target.account, plaintextJson: {}, nonce, timestamp }));
193
- return true;
194
- }
195
-
196
- const cardEvent = (msg as any).event?.template_card_event;
197
- let interactionDesc = `[卡片交互] 按钮: ${cardEvent?.event_key || "unknown"}`;
198
- if (cardEvent?.selected_items?.selected_item?.length) {
199
- const selects = cardEvent.selected_items.selected_item.map(
200
- (i: any) => `${i.question_key}=${i.option_ids?.option_id?.join(",")}`,
201
- );
202
- interactionDesc += ` 选择: ${selects.join("; ")}`;
203
- }
204
- if (cardEvent?.task_id) interactionDesc += ` (任务ID: ${cardEvent.task_id})`;
205
-
206
- jsonOk(res, buildEncryptedBotWebhookReply({ account: target.account, plaintextJson: {}, nonce, timestamp }));
207
-
208
- const streamId = streamStore.createStream({ msgid });
209
- streamStore.markStarted(streamId);
210
- storeActiveReply(streamId, msg.response_url);
211
- const core = getWecomRuntime();
212
- startAgentForStream({
213
- target: { ...target, core },
214
- accountId: target.account.accountId,
215
- msg: { ...msg, msgtype: "text", text: { content: interactionDesc } } as any,
216
- streamId,
217
- }).catch((err) => target.runtime.error?.(`interaction failed: ${String(err)}`));
218
- return true;
219
- }
220
-
221
- if (eventtype === "enter_chat") {
222
- const welcome = target.account.config.welcomeText?.trim();
223
- jsonOk(
224
- res,
225
- buildEncryptedBotWebhookReply({
226
- account: target.account,
227
- plaintextJson: welcome ? { msgtype: "text", text: { content: welcome } } : {},
228
- nonce,
229
- timestamp,
230
- }),
231
- );
232
- return true;
233
- }
234
-
235
- jsonOk(res, buildEncryptedBotWebhookReply({ account: target.account, plaintextJson: {}, nonce, timestamp }));
236
- return true;
237
- }
238
-
239
- if (msgtype === "stream") {
240
- const streamId = String((msg as any).stream?.id ?? "").trim();
241
- const state = streamStore.getStream(streamId);
242
- const reply = state
243
- ? buildStreamReplyFromState(state)
244
- : buildStreamReplyFromState({
245
- streamId: streamId || "unknown",
246
- createdAt: Date.now(),
247
- updatedAt: Date.now(),
248
- started: true,
249
- finished: true,
250
- content: "",
251
- });
252
- jsonOk(res, buildEncryptedBotWebhookReply({ account: target.account, plaintextJson: reply, nonce, timestamp }));
253
- return true;
254
- }
255
-
256
- try {
257
- const decision = shouldProcessBotInboundMessage(msg);
258
- if (!decision.shouldProcess) {
259
- logInfo(
260
- target,
261
- `inbound: skipped msgtype=${msgtype} reason=${decision.reason} chattype=${String(msg.chattype ?? "")} chatid=${String(msg.chatid ?? "")} from=${resolveWecomSenderUserId(msg) || "N/A"}`,
262
- );
263
- jsonOk(res, buildEncryptedBotWebhookReply({ account: target.account, plaintextJson: {}, nonce, timestamp }));
264
- return true;
265
- }
266
-
267
- const userid = decision.senderUserId!;
268
- const chatId = decision.chatId ?? userid;
269
- const conversationKey = `wecom:${target.account.accountId}:${userid}:${chatId}`;
270
- const msgContent = buildInboundBody(msg);
271
-
272
- logInfo(
273
- target,
274
- `inbound: msgtype=${msgtype} chattype=${String(msg.chattype ?? "")} chatid=${String(msg.chatid ?? "")} from=${userid} msgid=${String(msg.msgid ?? "")} hasResponseUrl=${Boolean((msg as any).response_url)}`,
275
- );
276
-
277
- if (msg.msgid) {
278
- const existingStreamId = streamStore.getStreamByMsgId(String(msg.msgid));
279
- if (existingStreamId) {
280
- logInfo(target, `message: 重复的 msgid=${msg.msgid},跳过处理并返回占位符 streamId=${existingStreamId}`);
281
- recordBotOperationalEvent(target, {
282
- category: "duplicate-reply",
283
- messageId: String(msg.msgid),
284
- summary: `duplicate inbound msgid=${String(msg.msgid)} streamId=${existingStreamId}`,
285
- raw: {
286
- transport: "bot-webhook",
287
- envelopeType: "json",
288
- body: msg,
289
- },
290
- });
291
- jsonOk(
292
- res,
293
- buildEncryptedBotWebhookReply({
294
- account: target.account,
295
- plaintextJson: buildStreamPlaceholderReply({
296
- streamId: existingStreamId,
297
- placeholderContent: target.account.config.streamPlaceholderContent,
298
- }),
299
- nonce,
300
- timestamp,
301
- }),
302
- );
303
- return true;
304
- }
305
- }
306
-
307
- const { streamId, status } = streamStore.addPendingMessage({
308
- conversationKey,
309
- target,
310
- msg,
311
- msgContent,
312
- nonce,
313
- timestamp,
314
- debounceMs: (target.account.config as any).debounceMs,
315
- });
316
-
317
- if (msg.response_url) {
318
- storeActiveReply(streamId, msg.response_url, proxyUrl);
319
- }
320
-
321
- const defaultPlaceholder = target.account.config.streamPlaceholderContent;
322
- const queuedPlaceholder = "已收到,已排队处理中...";
323
- const mergedQueuedPlaceholder = "已收到,已合并排队处理中...";
324
-
325
- if (status === "active_new") {
326
- jsonOk(
327
- res,
328
- buildEncryptedBotWebhookReply({
329
- account: target.account,
330
- plaintextJson: buildStreamPlaceholderReply({
331
- streamId,
332
- placeholderContent: defaultPlaceholder,
333
- }),
334
- nonce,
335
- timestamp,
336
- }),
337
- );
338
- return true;
339
- }
340
-
341
- if (status === "queued_new") {
342
- logInfo(target, `queue: 已进入下一批次 streamId=${streamId} msgid=${String(msg.msgid ?? "")}`);
343
- jsonOk(
344
- res,
345
- buildEncryptedBotWebhookReply({
346
- account: target.account,
347
- plaintextJson: buildStreamPlaceholderReply({
348
- streamId,
349
- placeholderContent: queuedPlaceholder,
350
- }),
351
- nonce,
352
- timestamp,
353
- }),
354
- );
355
- return true;
356
- }
357
-
358
- const ackStreamId = streamStore.createStream({ msgid: String(msg.msgid ?? "") || undefined });
359
- streamStore.updateStream(ackStreamId, (s) => {
360
- s.finished = false;
361
- s.started = true;
362
- s.content = mergedQueuedPlaceholder;
363
- });
364
- if (msg.msgid) streamStore.setStreamIdForMsgId(String(msg.msgid), ackStreamId);
365
- streamStore.addAckStreamForBatch({ batchStreamId: streamId, ackStreamId });
366
- logInfo(
367
- target,
368
- `queue: 已合并排队(回执流) ackStreamId=${ackStreamId} mergedIntoStreamId=${streamId} msgid=${String(msg.msgid ?? "")}`,
369
- );
370
- jsonOk(
371
- res,
372
- buildEncryptedBotWebhookReply({
373
- account: target.account,
374
- plaintextJson: buildStreamTextPlaceholderReply({ streamId: ackStreamId, content: mergedQueuedPlaceholder }),
375
- nonce,
376
- timestamp,
377
- }),
378
- );
379
- return true;
380
- } catch (err) {
381
- target.runtime.error?.(`[wecom] Bot message handler crashed: ${String(err)}`);
382
- jsonOk(
383
- res,
384
- buildEncryptedBotWebhookReply({
385
- account: target.account,
386
- plaintextJson: { msgtype: "text", text: { content: "服务内部错误:Bot 处理异常,请稍后重试。" } },
387
- nonce,
388
- timestamp,
389
- }),
390
- );
391
- return true;
392
- }
393
- };
394
- }
@@ -1,23 +0,0 @@
1
- import type { TransportSessionSnapshot } from "../../types/index.js";
2
-
3
- export function createBotWebhookSessionSnapshot(params: {
4
- accountId: string;
5
- running: boolean;
6
- lastInboundAt?: number;
7
- lastOutboundAt?: number;
8
- lastError?: string;
9
- }): TransportSessionSnapshot {
10
- return {
11
- accountId: params.accountId,
12
- transport: "bot-webhook",
13
- running: params.running,
14
- ownerId: `${params.accountId}:bot-webhook`,
15
- connected: params.running,
16
- authenticated: true,
17
- lastConnectedAt: params.running ? Date.now() : undefined,
18
- lastDisconnectedAt: params.running ? undefined : Date.now(),
19
- lastInboundAt: params.lastInboundAt,
20
- lastOutboundAt: params.lastOutboundAt,
21
- lastError: params.lastError,
22
- };
23
- }