@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
@@ -2,10 +2,7 @@
2
2
  * WeCom XML 解析器
3
3
  * 用于 Agent 模式解析 XML 格式消息
4
4
  */
5
-
6
5
  import { XMLParser } from "fast-xml-parser";
7
- import type { WecomAgentInboundMessage } from "../types/index.js";
8
-
9
6
  const xmlParser = new XMLParser({
10
7
  ignoreAttributes: false,
11
8
  trimValues: true,
@@ -13,109 +10,113 @@ const xmlParser = new XMLParser({
13
10
  parseTagValue: false,
14
11
  parseAttributeValue: false,
15
12
  });
16
-
17
13
  /**
18
14
  * 解析 XML 字符串为消息对象
19
15
  */
20
- export function parseXml(xml: string): WecomAgentInboundMessage {
16
+ export function parseXml(xml) {
21
17
  const obj = xmlParser.parse(xml);
22
18
  const root = obj?.xml ?? obj;
23
19
  return root ?? {};
24
20
  }
25
-
26
21
  /**
27
22
  * 从 XML 中提取消息类型
28
23
  */
29
- export function extractMsgType(msg: WecomAgentInboundMessage): string {
24
+ export function extractMsgType(msg) {
30
25
  return String(msg.MsgType ?? "").toLowerCase();
31
26
  }
32
-
33
27
  /**
34
28
  * 从 XML 中提取发送者 ID
35
29
  */
36
- export function extractFromUser(msg: WecomAgentInboundMessage): string {
30
+ export function extractFromUser(msg) {
37
31
  return String(msg.FromUserName ?? "");
38
32
  }
39
-
40
33
  /**
41
34
  * 从 XML 中提取文件名(主要用于 file 消息)
42
35
  */
43
- export function extractFileName(msg: WecomAgentInboundMessage): string | undefined {
44
- const raw = (msg as any).FileName ?? (msg as any).Filename ?? (msg as any).fileName ?? (msg as any).filename;
45
- if (raw == null) return undefined;
46
- if (typeof raw === "string") return raw.trim() || undefined;
47
- if (typeof raw === "number" || typeof raw === "boolean" || typeof raw === "bigint") return String(raw);
36
+ export function extractFileName(msg) {
37
+ const raw = msg.FileName ?? msg.Filename ?? msg.fileName ?? msg.filename;
38
+ if (raw == null)
39
+ return undefined;
40
+ if (typeof raw === "string")
41
+ return raw.trim() || undefined;
42
+ if (typeof raw === "number" || typeof raw === "boolean" || typeof raw === "bigint")
43
+ return String(raw);
48
44
  if (Array.isArray(raw)) {
49
45
  const merged = raw.map((v) => (v == null ? "" : String(v))).join("\n").trim();
50
46
  return merged || undefined;
51
47
  }
52
48
  if (typeof raw === "object") {
53
- const obj = raw as Record<string, unknown>;
49
+ const obj = raw;
54
50
  const text = (typeof obj["#text"] === "string" ? obj["#text"] :
55
51
  typeof obj["_text"] === "string" ? obj["_text"] :
56
52
  typeof obj["text"] === "string" ? obj["text"] : undefined);
57
- if (text && text.trim()) return text.trim();
53
+ if (text && text.trim())
54
+ return text.trim();
58
55
  }
59
56
  const s = String(raw);
60
57
  return s.trim() || undefined;
61
58
  }
62
-
63
59
  /**
64
60
  * 从 XML 中提取接收者 ID (CorpID)
65
61
  */
66
- export function extractToUser(msg: WecomAgentInboundMessage): string {
62
+ export function extractToUser(msg) {
67
63
  return String(msg.ToUserName ?? "");
68
64
  }
69
-
70
65
  /**
71
66
  * 从 XML 中提取群聊 ID
72
67
  */
73
- export function extractChatId(msg: WecomAgentInboundMessage): string | undefined {
68
+ export function extractChatId(msg) {
74
69
  return msg.ChatId ? String(msg.ChatId) : undefined;
75
70
  }
76
-
77
71
  /**
78
72
  * 从 XML 中提取 AgentID(兼容 AgentID/agentid 等大小写)
79
73
  */
80
- export function extractAgentId(msg: WecomAgentInboundMessage): string | number | undefined {
81
- const raw =
82
- (msg as any).AgentID ??
83
- (msg as any).AgentId ??
84
- (msg as any).agentid ??
85
- (msg as any).agentId;
86
- if (raw == null) return undefined;
87
- if (typeof raw === "string") return raw.trim() || undefined;
88
- if (typeof raw === "number") return raw;
74
+ export function extractAgentId(msg) {
75
+ const raw = msg.AgentID ??
76
+ msg.AgentId ??
77
+ msg.agentid ??
78
+ msg.agentId;
79
+ if (raw == null)
80
+ return undefined;
81
+ if (typeof raw === "string")
82
+ return raw.trim() || undefined;
83
+ if (typeof raw === "number")
84
+ return raw;
89
85
  const asString = String(raw).trim();
90
86
  return asString || undefined;
91
87
  }
92
-
93
88
  /**
94
89
  * 从 XML 中提取消息内容
95
90
  */
96
- export function extractContent(msg: WecomAgentInboundMessage): string {
91
+ export function extractContent(msg) {
97
92
  const msgType = extractMsgType(msg);
98
-
99
- const asText = (value: unknown): string => {
100
- if (value == null) return "";
101
- if (typeof value === "string") return value;
102
- if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
103
- if (Array.isArray(value)) return value.map(asText).filter(Boolean).join("\n");
93
+ const asText = (value) => {
94
+ if (value == null)
95
+ return "";
96
+ if (typeof value === "string")
97
+ return value;
98
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint")
99
+ return String(value);
100
+ if (Array.isArray(value))
101
+ return value.map(asText).filter(Boolean).join("\n");
104
102
  if (typeof value === "object") {
105
- const obj = value as Record<string, unknown>;
103
+ const obj = value;
106
104
  // fast-xml-parser 在某些情况下(例如带属性)会把文本放在 "#text"
107
- if (typeof obj["#text"] === "string") return obj["#text"];
108
- if (typeof obj["_text"] === "string") return obj["_text"];
109
- if (typeof obj["text"] === "string") return obj["text"];
105
+ if (typeof obj["#text"] === "string")
106
+ return obj["#text"];
107
+ if (typeof obj["_text"] === "string")
108
+ return obj["_text"];
109
+ if (typeof obj["text"] === "string")
110
+ return obj["text"];
110
111
  try {
111
112
  return JSON.stringify(obj);
112
- } catch {
113
+ }
114
+ catch {
113
115
  return String(value);
114
116
  }
115
117
  }
116
118
  return String(value);
117
119
  };
118
-
119
120
  switch (msgType) {
120
121
  case "text":
121
122
  return asText(msg.Content);
@@ -138,30 +139,34 @@ export function extractContent(msg: WecomAgentInboundMessage): string {
138
139
  return `[${msgType || "未知消息类型"}]`;
139
140
  }
140
141
  }
141
-
142
142
  /**
143
143
  * 从 XML 中提取媒体 ID (Image, Voice, Video)
144
144
  * 根据官方文档,MediaId 在 Agent 回调中直接位于根节点
145
145
  */
146
- export function extractMediaId(msg: WecomAgentInboundMessage): string | undefined {
147
- const raw = (msg as any).MediaId ?? (msg as any).MediaID ?? (msg as any).mediaid ?? (msg as any).mediaId;
148
- if (raw == null) return undefined;
149
- if (typeof raw === "string") return raw.trim() || undefined;
150
- if (typeof raw === "number" || typeof raw === "boolean" || typeof raw === "bigint") return String(raw);
146
+ export function extractMediaId(msg) {
147
+ const raw = msg.MediaId ?? msg.MediaID ?? msg.mediaid ?? msg.mediaId;
148
+ if (raw == null)
149
+ return undefined;
150
+ if (typeof raw === "string")
151
+ return raw.trim() || undefined;
152
+ if (typeof raw === "number" || typeof raw === "boolean" || typeof raw === "bigint")
153
+ return String(raw);
151
154
  if (Array.isArray(raw)) {
152
155
  const merged = raw.map((v) => (v == null ? "" : String(v))).join("\n").trim();
153
156
  return merged || undefined;
154
157
  }
155
158
  if (typeof raw === "object") {
156
- const obj = raw as Record<string, unknown>;
159
+ const obj = raw;
157
160
  const text = (typeof obj["#text"] === "string" ? obj["#text"] :
158
161
  typeof obj["_text"] === "string" ? obj["_text"] :
159
162
  typeof obj["text"] === "string" ? obj["text"] : undefined);
160
- if (text && text.trim()) return text.trim();
163
+ if (text && text.trim())
164
+ return text.trim();
161
165
  try {
162
166
  const s = JSON.stringify(obj);
163
167
  return s.trim() || undefined;
164
- } catch {
168
+ }
169
+ catch {
165
170
  const s = String(raw);
166
171
  return s.trim() || undefined;
167
172
  }
@@ -169,29 +174,33 @@ export function extractMediaId(msg: WecomAgentInboundMessage): string | undefine
169
174
  const s = String(raw);
170
175
  return s.trim() || undefined;
171
176
  }
172
-
173
177
  /**
174
178
  * 从 XML 中提取 MsgId(用于去重)
175
179
  */
176
- export function extractMsgId(msg: WecomAgentInboundMessage): string | undefined {
177
- const raw = (msg as any).MsgId ?? (msg as any).MsgID ?? (msg as any).msgid ?? (msg as any).msgId;
178
- if (raw == null) return undefined;
179
- if (typeof raw === "string") return raw.trim() || undefined;
180
- if (typeof raw === "number" || typeof raw === "boolean" || typeof raw === "bigint") return String(raw);
180
+ export function extractMsgId(msg) {
181
+ const raw = msg.MsgId ?? msg.MsgID ?? msg.msgid ?? msg.msgId;
182
+ if (raw == null)
183
+ return undefined;
184
+ if (typeof raw === "string")
185
+ return raw.trim() || undefined;
186
+ if (typeof raw === "number" || typeof raw === "boolean" || typeof raw === "bigint")
187
+ return String(raw);
181
188
  if (Array.isArray(raw)) {
182
189
  const merged = raw.map((v) => (v == null ? "" : String(v))).join("\n").trim();
183
190
  return merged || undefined;
184
191
  }
185
192
  if (typeof raw === "object") {
186
- const obj = raw as Record<string, unknown>;
193
+ const obj = raw;
187
194
  const text = (typeof obj["#text"] === "string" ? obj["#text"] :
188
195
  typeof obj["_text"] === "string" ? obj["_text"] :
189
196
  typeof obj["text"] === "string" ? obj["text"] : undefined);
190
- if (text && text.trim()) return text.trim();
197
+ if (text && text.trim())
198
+ return text.trim();
191
199
  try {
192
200
  const s = JSON.stringify(obj);
193
201
  return s.trim() || undefined;
194
- } catch {
202
+ }
203
+ catch {
195
204
  const s = String(raw);
196
205
  return s.trim() || undefined;
197
206
  }
@@ -0,0 +1,41 @@
1
+ import { LIMITS } from "../monitor/limits.js";
2
+ export class ActiveReplyStore {
3
+ policy;
4
+ activeReplies = new Map();
5
+ constructor(policy = "once") {
6
+ this.policy = policy;
7
+ }
8
+ store(streamId, responseUrl, proxyUrl) {
9
+ const url = responseUrl?.trim();
10
+ if (!url)
11
+ return;
12
+ this.activeReplies.set(streamId, { response_url: url, proxyUrl, createdAt: Date.now() });
13
+ }
14
+ getUrl(streamId) {
15
+ return this.activeReplies.get(streamId)?.response_url;
16
+ }
17
+ async use(streamId, fn) {
18
+ const state = this.activeReplies.get(streamId);
19
+ if (!state?.response_url)
20
+ return;
21
+ if (this.policy === "once" && state.usedAt) {
22
+ throw new Error(`response_url already used for stream ${streamId} (Policy: once)`);
23
+ }
24
+ try {
25
+ await fn({ responseUrl: state.response_url, proxyUrl: state.proxyUrl });
26
+ state.usedAt = Date.now();
27
+ }
28
+ catch (err) {
29
+ state.lastError = err instanceof Error ? err.message : String(err);
30
+ throw err;
31
+ }
32
+ }
33
+ prune(now = Date.now()) {
34
+ const cutoff = now - LIMITS.ACTIVE_REPLY_TTL_MS;
35
+ for (const [id, state] of this.activeReplies.entries()) {
36
+ if (state.createdAt < cutoff) {
37
+ this.activeReplies.delete(id);
38
+ }
39
+ }
40
+ }
41
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,33 @@
1
+ import { buildDedupKey } from "../domain/policies.js";
2
+ export class InMemoryRuntimeStore {
3
+ seen = new Set();
4
+ replyContexts = new Map();
5
+ transportSessions = new Map();
6
+ deliveryTasks = new Map();
7
+ markInboundSeen(event) {
8
+ const key = buildDedupKey(event);
9
+ if (this.seen.has(key)) {
10
+ return false;
11
+ }
12
+ this.seen.add(key);
13
+ return true;
14
+ }
15
+ readReplyContext(messageId) {
16
+ return this.replyContexts.get(messageId);
17
+ }
18
+ writeReplyContext(messageId, context) {
19
+ this.replyContexts.set(messageId, context);
20
+ }
21
+ readTransportSession(accountId, transport) {
22
+ return this.transportSessions.get(`${accountId}:${transport}`);
23
+ }
24
+ writeTransportSession(snapshot) {
25
+ this.transportSessions.set(`${snapshot.accountId}:${snapshot.transport}`, snapshot);
26
+ }
27
+ writeDeliveryTask(task) {
28
+ this.deliveryTasks.set(task.messageId, task);
29
+ }
30
+ readDeliveryTask(messageId) {
31
+ return this.deliveryTasks.get(messageId);
32
+ }
33
+ }
@@ -0,0 +1,319 @@
1
+ import crypto from "node:crypto";
2
+ import { LIMITS } from "../monitor/limits.js";
3
+ export class StreamStore {
4
+ streams = new Map();
5
+ msgidToStreamId = new Map();
6
+ pendingInbounds = new Map();
7
+ conversationState = new Map();
8
+ streamIdToBatchKey = new Map();
9
+ batchKeyToStreamIds = new Map();
10
+ batchStreamIdToAckStreamIds = new Map();
11
+ onFlush;
12
+ setFlushHandler(handler) {
13
+ this.onFlush = handler;
14
+ }
15
+ linkStreamToBatch(streamId, batchKey) {
16
+ const key = String(batchKey ?? "").trim();
17
+ if (!key)
18
+ return;
19
+ this.streamIdToBatchKey.set(streamId, key);
20
+ const linked = this.batchKeyToStreamIds.get(key) ?? new Set();
21
+ linked.add(streamId);
22
+ this.batchKeyToStreamIds.set(key, linked);
23
+ }
24
+ unlinkStreamFromBatch(streamId, batchKey) {
25
+ const key = String(batchKey ?? this.streamIdToBatchKey.get(streamId) ?? "").trim();
26
+ this.streamIdToBatchKey.delete(streamId);
27
+ if (!key)
28
+ return;
29
+ const linked = this.batchKeyToStreamIds.get(key);
30
+ if (!linked)
31
+ return;
32
+ linked.delete(streamId);
33
+ if (linked.size === 0) {
34
+ this.batchKeyToStreamIds.delete(key);
35
+ }
36
+ else {
37
+ this.batchKeyToStreamIds.set(key, linked);
38
+ }
39
+ }
40
+ hasLiveBatchKey(batchKey) {
41
+ const key = batchKey.trim();
42
+ if (!key)
43
+ return false;
44
+ return this.pendingInbounds.has(key) || (this.batchKeyToStreamIds.get(key)?.size ?? 0) > 0;
45
+ }
46
+ clearPendingTimer(pending) {
47
+ if (!pending?.timeout)
48
+ return;
49
+ clearTimeout(pending.timeout);
50
+ pending.timeout = null;
51
+ }
52
+ removePendingBatch(batchKey) {
53
+ const pending = this.pendingInbounds.get(batchKey);
54
+ if (!pending)
55
+ return undefined;
56
+ this.pendingInbounds.delete(batchKey);
57
+ this.clearPendingTimer(pending);
58
+ pending.readyToFlush = false;
59
+ return pending;
60
+ }
61
+ removeStreamRecord(streamId, state) {
62
+ const current = state ?? this.streams.get(streamId);
63
+ if (!current)
64
+ return;
65
+ this.streams.delete(streamId);
66
+ this.unlinkStreamFromBatch(streamId, current.batchKey);
67
+ if (current.msgid && this.msgidToStreamId.get(current.msgid) === streamId) {
68
+ this.msgidToStreamId.delete(current.msgid);
69
+ }
70
+ }
71
+ pruneAckStreamMappings() {
72
+ for (const [batchStreamId, ackIds] of this.batchStreamIdToAckStreamIds.entries()) {
73
+ if (!this.streams.has(batchStreamId)) {
74
+ this.batchStreamIdToAckStreamIds.delete(batchStreamId);
75
+ continue;
76
+ }
77
+ const nextAckIds = ackIds.filter((ackId) => this.streams.has(ackId));
78
+ if (nextAckIds.length === 0) {
79
+ this.batchStreamIdToAckStreamIds.delete(batchStreamId);
80
+ continue;
81
+ }
82
+ this.batchStreamIdToAckStreamIds.set(batchStreamId, nextAckIds);
83
+ }
84
+ }
85
+ pruneConversationState() {
86
+ for (const [convKey, conv] of this.conversationState.entries()) {
87
+ conv.queue = conv.queue.filter((batchKey) => this.pendingInbounds.has(batchKey));
88
+ if (!this.hasLiveBatchKey(conv.activeBatchKey)) {
89
+ const next = conv.queue.shift();
90
+ if (!next) {
91
+ this.conversationState.delete(convKey);
92
+ continue;
93
+ }
94
+ conv.activeBatchKey = next;
95
+ }
96
+ this.conversationState.set(convKey, conv);
97
+ }
98
+ }
99
+ createStream(params) {
100
+ const streamId = crypto.randomBytes(16).toString("hex");
101
+ if (params.msgid) {
102
+ this.msgidToStreamId.set(String(params.msgid), streamId);
103
+ }
104
+ this.streams.set(streamId, {
105
+ streamId,
106
+ msgid: params.msgid,
107
+ conversationKey: params.conversationKey,
108
+ batchKey: params.batchKey,
109
+ createdAt: Date.now(),
110
+ updatedAt: Date.now(),
111
+ started: false,
112
+ finished: false,
113
+ content: "",
114
+ });
115
+ this.linkStreamToBatch(streamId, params.batchKey);
116
+ return streamId;
117
+ }
118
+ getStream(streamId) {
119
+ return this.streams.get(streamId);
120
+ }
121
+ getStreamByMsgId(msgid) {
122
+ return this.msgidToStreamId.get(String(msgid));
123
+ }
124
+ setStreamIdForMsgId(msgid, streamId) {
125
+ const key = String(msgid).trim();
126
+ const value = String(streamId).trim();
127
+ if (!key || !value)
128
+ return;
129
+ this.msgidToStreamId.set(key, value);
130
+ }
131
+ addAckStreamForBatch(params) {
132
+ const batchStreamId = params.batchStreamId.trim();
133
+ const ackStreamId = params.ackStreamId.trim();
134
+ if (!batchStreamId || !ackStreamId)
135
+ return;
136
+ const list = this.batchStreamIdToAckStreamIds.get(batchStreamId) ?? [];
137
+ list.push(ackStreamId);
138
+ this.batchStreamIdToAckStreamIds.set(batchStreamId, list);
139
+ }
140
+ drainAckStreamsForBatch(batchStreamId) {
141
+ const key = batchStreamId.trim();
142
+ if (!key)
143
+ return [];
144
+ const list = this.batchStreamIdToAckStreamIds.get(key) ?? [];
145
+ this.batchStreamIdToAckStreamIds.delete(key);
146
+ return list;
147
+ }
148
+ updateStream(streamId, mutator) {
149
+ const state = this.streams.get(streamId);
150
+ if (state) {
151
+ mutator(state);
152
+ state.updatedAt = Date.now();
153
+ }
154
+ }
155
+ markStarted(streamId) {
156
+ this.updateStream(streamId, (s) => {
157
+ s.started = true;
158
+ });
159
+ }
160
+ markFinished(streamId) {
161
+ this.updateStream(streamId, (s) => {
162
+ s.finished = true;
163
+ });
164
+ }
165
+ addPendingMessage(params) {
166
+ const { conversationKey, target, msg, msgContent, nonce, timestamp, debounceMs } = params;
167
+ const effectiveDebounceMs = debounceMs ?? LIMITS.DEFAULT_DEBOUNCE_MS;
168
+ const state = this.conversationState.get(conversationKey);
169
+ if (!state) {
170
+ const batchKey = conversationKey;
171
+ const streamId = this.createStream({ msgid: msg.msgid, conversationKey, batchKey });
172
+ const pending = {
173
+ streamId,
174
+ conversationKey,
175
+ batchKey,
176
+ target,
177
+ msg,
178
+ contents: [msgContent],
179
+ msgids: msg.msgid ? [msg.msgid] : [],
180
+ nonce,
181
+ timestamp,
182
+ createdAt: Date.now(),
183
+ timeout: setTimeout(() => {
184
+ this.requestFlush(batchKey);
185
+ }, effectiveDebounceMs),
186
+ };
187
+ this.pendingInbounds.set(batchKey, pending);
188
+ this.conversationState.set(conversationKey, { activeBatchKey: batchKey, queue: [], nextSeq: 1 });
189
+ return { streamId, status: "active_new" };
190
+ }
191
+ const activeBatchKey = state.activeBatchKey;
192
+ const activeIsInitial = activeBatchKey === conversationKey;
193
+ const activePending = this.pendingInbounds.get(activeBatchKey);
194
+ if (activePending && !activeIsInitial) {
195
+ const activeStream = this.streams.get(activePending.streamId);
196
+ const activeStarted = Boolean(activeStream?.started);
197
+ if (!activeStarted) {
198
+ activePending.contents.push(msgContent);
199
+ if (msg.msgid) {
200
+ activePending.msgids.push(msg.msgid);
201
+ }
202
+ if (activePending.timeout)
203
+ clearTimeout(activePending.timeout);
204
+ activePending.timeout = setTimeout(() => {
205
+ this.requestFlush(activeBatchKey);
206
+ }, effectiveDebounceMs);
207
+ return { streamId: activePending.streamId, status: "active_merged" };
208
+ }
209
+ }
210
+ const queuedBatchKey = state.queue[0];
211
+ if (queuedBatchKey) {
212
+ const existingQueued = this.pendingInbounds.get(queuedBatchKey);
213
+ if (existingQueued) {
214
+ existingQueued.contents.push(msgContent);
215
+ if (msg.msgid) {
216
+ existingQueued.msgids.push(msg.msgid);
217
+ }
218
+ if (existingQueued.timeout)
219
+ clearTimeout(existingQueued.timeout);
220
+ existingQueued.timeout = setTimeout(() => {
221
+ this.requestFlush(queuedBatchKey);
222
+ }, effectiveDebounceMs);
223
+ return { streamId: existingQueued.streamId, status: "queued_merged" };
224
+ }
225
+ }
226
+ const seq = state.nextSeq++;
227
+ const batchKey = `${conversationKey}#q${seq}`;
228
+ state.queue = [batchKey];
229
+ const streamId = this.createStream({ msgid: msg.msgid, conversationKey, batchKey });
230
+ const pending = {
231
+ streamId,
232
+ conversationKey,
233
+ batchKey,
234
+ target,
235
+ msg,
236
+ contents: [msgContent],
237
+ msgids: msg.msgid ? [msg.msgid] : [],
238
+ nonce,
239
+ timestamp,
240
+ createdAt: Date.now(),
241
+ timeout: setTimeout(() => {
242
+ this.requestFlush(batchKey);
243
+ }, effectiveDebounceMs),
244
+ };
245
+ this.pendingInbounds.set(batchKey, pending);
246
+ this.conversationState.set(conversationKey, state);
247
+ return { streamId, status: "queued_new" };
248
+ }
249
+ requestFlush(batchKey) {
250
+ const pending = this.pendingInbounds.get(batchKey);
251
+ if (!pending)
252
+ return;
253
+ const state = this.conversationState.get(pending.conversationKey);
254
+ const isActive = state?.activeBatchKey === batchKey;
255
+ if (!isActive) {
256
+ if (pending.timeout) {
257
+ clearTimeout(pending.timeout);
258
+ pending.timeout = null;
259
+ }
260
+ pending.readyToFlush = true;
261
+ return;
262
+ }
263
+ this.flushPending(batchKey);
264
+ }
265
+ flushPending(pendingKey) {
266
+ const pending = this.removePendingBatch(pendingKey);
267
+ if (!pending)
268
+ return;
269
+ if (this.onFlush) {
270
+ this.onFlush(pending);
271
+ }
272
+ }
273
+ onStreamFinished(streamId) {
274
+ const batchKey = this.streamIdToBatchKey.get(streamId);
275
+ const state = batchKey ? this.streams.get(streamId) : undefined;
276
+ const conversationKey = state?.conversationKey;
277
+ if (!batchKey || !conversationKey)
278
+ return;
279
+ this.unlinkStreamFromBatch(streamId, batchKey);
280
+ const conv = this.conversationState.get(conversationKey);
281
+ if (!conv)
282
+ return;
283
+ if (conv.activeBatchKey !== batchKey)
284
+ return;
285
+ const next = conv.queue.shift();
286
+ if (!next) {
287
+ this.conversationState.delete(conversationKey);
288
+ return;
289
+ }
290
+ conv.activeBatchKey = next;
291
+ this.conversationState.set(conversationKey, conv);
292
+ const pending = this.pendingInbounds.get(next);
293
+ if (!pending)
294
+ return;
295
+ if (pending.readyToFlush) {
296
+ this.flushPending(next);
297
+ }
298
+ }
299
+ prune(now = Date.now()) {
300
+ const streamCutoff = now - LIMITS.STREAM_TTL_MS;
301
+ for (const [id, state] of this.streams.entries()) {
302
+ if (state.updatedAt < streamCutoff) {
303
+ this.removeStreamRecord(id, state);
304
+ }
305
+ }
306
+ for (const [msgid, id] of this.msgidToStreamId.entries()) {
307
+ if (!this.streams.has(id)) {
308
+ this.msgidToStreamId.delete(msgid);
309
+ }
310
+ }
311
+ for (const [key, pending] of this.pendingInbounds.entries()) {
312
+ if (now - pending.createdAt > LIMITS.STREAM_TTL_MS) {
313
+ this.removePendingBatch(key);
314
+ }
315
+ }
316
+ this.pruneAckStreamMappings();
317
+ this.pruneConversationState();
318
+ }
319
+ }