@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,42 +0,0 @@
1
- import { LIMITS } from "../monitor/limits.js";
2
- import type { ActiveReplyState } from "../types/legacy-stream.js";
3
-
4
- export class ActiveReplyStore {
5
- private activeReplies = new Map<string, ActiveReplyState>();
6
-
7
- constructor(private policy: "once" | "multi" = "once") {}
8
-
9
- store(streamId: string, responseUrl?: string, proxyUrl?: string): void {
10
- const url = responseUrl?.trim();
11
- if (!url) return;
12
- this.activeReplies.set(streamId, { response_url: url, proxyUrl, createdAt: Date.now() });
13
- }
14
-
15
- getUrl(streamId: string): string | undefined {
16
- return this.activeReplies.get(streamId)?.response_url;
17
- }
18
-
19
- async use(streamId: string, fn: (params: { responseUrl: string; proxyUrl?: string }) => Promise<void>): Promise<void> {
20
- const state = this.activeReplies.get(streamId);
21
- if (!state?.response_url) return;
22
- if (this.policy === "once" && state.usedAt) {
23
- throw new Error(`response_url already used for stream ${streamId} (Policy: once)`);
24
- }
25
- try {
26
- await fn({ responseUrl: state.response_url, proxyUrl: state.proxyUrl });
27
- state.usedAt = Date.now();
28
- } catch (err: unknown) {
29
- state.lastError = err instanceof Error ? err.message : String(err);
30
- throw err;
31
- }
32
- }
33
-
34
- prune(now: number = Date.now()): void {
35
- const cutoff = now - LIMITS.ACTIVE_REPLY_TTL_MS;
36
- for (const [id, state] of this.activeReplies.entries()) {
37
- if (state.createdAt < cutoff) {
38
- this.activeReplies.delete(id);
39
- }
40
- }
41
- }
42
- }
@@ -1,11 +0,0 @@
1
- import type { DeliveryTask, ReplyContext, TransportSessionSnapshot, UnifiedInboundEvent } from "../types/index.js";
2
-
3
- export type RuntimeStore = {
4
- markInboundSeen: (event: UnifiedInboundEvent) => boolean;
5
- readReplyContext: (messageId: string) => ReplyContext | undefined;
6
- writeReplyContext: (messageId: string, context: ReplyContext) => void;
7
- readTransportSession: (accountId: string, transport: TransportSessionSnapshot["transport"]) => TransportSessionSnapshot | undefined;
8
- writeTransportSession: (snapshot: TransportSessionSnapshot) => void;
9
- writeDeliveryTask: (task: DeliveryTask) => void;
10
- readDeliveryTask: (messageId: string) => DeliveryTask | undefined;
11
- };
@@ -1,43 +0,0 @@
1
- import type { RuntimeStore } from "./interfaces.js";
2
- import type { DeliveryTask, ReplyContext, TransportSessionSnapshot, UnifiedInboundEvent } from "../types/index.js";
3
- import { buildDedupKey } from "../domain/policies.js";
4
-
5
- export class InMemoryRuntimeStore implements RuntimeStore {
6
- private readonly seen = new Set<string>();
7
- private readonly replyContexts = new Map<string, ReplyContext>();
8
- private readonly transportSessions = new Map<string, TransportSessionSnapshot>();
9
- private readonly deliveryTasks = new Map<string, DeliveryTask>();
10
-
11
- markInboundSeen(event: UnifiedInboundEvent): boolean {
12
- const key = buildDedupKey(event);
13
- if (this.seen.has(key)) {
14
- return false;
15
- }
16
- this.seen.add(key);
17
- return true;
18
- }
19
-
20
- readReplyContext(messageId: string): ReplyContext | undefined {
21
- return this.replyContexts.get(messageId);
22
- }
23
-
24
- writeReplyContext(messageId: string, context: ReplyContext): void {
25
- this.replyContexts.set(messageId, context);
26
- }
27
-
28
- readTransportSession(accountId: string, transport: TransportSessionSnapshot["transport"]): TransportSessionSnapshot | undefined {
29
- return this.transportSessions.get(`${accountId}:${transport}`);
30
- }
31
-
32
- writeTransportSession(snapshot: TransportSessionSnapshot): void {
33
- this.transportSessions.set(`${snapshot.accountId}:${snapshot.transport}`, snapshot);
34
- }
35
-
36
- writeDeliveryTask(task: DeliveryTask): void {
37
- this.deliveryTasks.set(task.messageId, task);
38
- }
39
-
40
- readDeliveryTask(messageId: string): DeliveryTask | undefined {
41
- return this.deliveryTasks.get(messageId);
42
- }
43
- }
@@ -1,350 +0,0 @@
1
- import crypto from "node:crypto";
2
-
3
- import { LIMITS } from "../monitor/limits.js";
4
- import type { PendingInbound, StreamState } from "../types/legacy-stream.js";
5
- import type { WecomBotInboundMessage as WecomInboundMessage } from "../types/index.js";
6
- import type { WecomWebhookTarget } from "../types/runtime-context.js";
7
-
8
- export class StreamStore {
9
- private streams = new Map<string, StreamState>();
10
- private msgidToStreamId = new Map<string, string>();
11
- private pendingInbounds = new Map<string, PendingInbound>();
12
- private conversationState = new Map<string, { activeBatchKey: string; queue: string[]; nextSeq: number }>();
13
- private streamIdToBatchKey = new Map<string, string>();
14
- private batchKeyToStreamIds = new Map<string, Set<string>>();
15
- private batchStreamIdToAckStreamIds = new Map<string, string[]>();
16
- private onFlush?: (pending: PendingInbound) => void;
17
-
18
- public setFlushHandler(handler: (pending: PendingInbound) => void) {
19
- this.onFlush = handler;
20
- }
21
-
22
- private linkStreamToBatch(streamId: string, batchKey?: string): void {
23
- const key = String(batchKey ?? "").trim();
24
- if (!key) return;
25
- this.streamIdToBatchKey.set(streamId, key);
26
- const linked = this.batchKeyToStreamIds.get(key) ?? new Set<string>();
27
- linked.add(streamId);
28
- this.batchKeyToStreamIds.set(key, linked);
29
- }
30
-
31
- private unlinkStreamFromBatch(streamId: string, batchKey?: string): void {
32
- const key = String(batchKey ?? this.streamIdToBatchKey.get(streamId) ?? "").trim();
33
- this.streamIdToBatchKey.delete(streamId);
34
- if (!key) return;
35
- const linked = this.batchKeyToStreamIds.get(key);
36
- if (!linked) return;
37
- linked.delete(streamId);
38
- if (linked.size === 0) {
39
- this.batchKeyToStreamIds.delete(key);
40
- } else {
41
- this.batchKeyToStreamIds.set(key, linked);
42
- }
43
- }
44
-
45
- private hasLiveBatchKey(batchKey: string): boolean {
46
- const key = batchKey.trim();
47
- if (!key) return false;
48
- return this.pendingInbounds.has(key) || (this.batchKeyToStreamIds.get(key)?.size ?? 0) > 0;
49
- }
50
-
51
- private clearPendingTimer(pending?: PendingInbound): void {
52
- if (!pending?.timeout) return;
53
- clearTimeout(pending.timeout);
54
- pending.timeout = null;
55
- }
56
-
57
- private removePendingBatch(batchKey: string): PendingInbound | undefined {
58
- const pending = this.pendingInbounds.get(batchKey);
59
- if (!pending) return undefined;
60
- this.pendingInbounds.delete(batchKey);
61
- this.clearPendingTimer(pending);
62
- pending.readyToFlush = false;
63
- return pending;
64
- }
65
-
66
- private removeStreamRecord(streamId: string, state?: StreamState): void {
67
- const current = state ?? this.streams.get(streamId);
68
- if (!current) return;
69
- this.streams.delete(streamId);
70
- this.unlinkStreamFromBatch(streamId, current.batchKey);
71
- if (current.msgid && this.msgidToStreamId.get(current.msgid) === streamId) {
72
- this.msgidToStreamId.delete(current.msgid);
73
- }
74
- }
75
-
76
- private pruneAckStreamMappings(): void {
77
- for (const [batchStreamId, ackIds] of this.batchStreamIdToAckStreamIds.entries()) {
78
- if (!this.streams.has(batchStreamId)) {
79
- this.batchStreamIdToAckStreamIds.delete(batchStreamId);
80
- continue;
81
- }
82
- const nextAckIds = ackIds.filter((ackId) => this.streams.has(ackId));
83
- if (nextAckIds.length === 0) {
84
- this.batchStreamIdToAckStreamIds.delete(batchStreamId);
85
- continue;
86
- }
87
- this.batchStreamIdToAckStreamIds.set(batchStreamId, nextAckIds);
88
- }
89
- }
90
-
91
- private pruneConversationState(): void {
92
- for (const [convKey, conv] of this.conversationState.entries()) {
93
- conv.queue = conv.queue.filter((batchKey) => this.pendingInbounds.has(batchKey));
94
- if (!this.hasLiveBatchKey(conv.activeBatchKey)) {
95
- const next = conv.queue.shift();
96
- if (!next) {
97
- this.conversationState.delete(convKey);
98
- continue;
99
- }
100
- conv.activeBatchKey = next;
101
- }
102
- this.conversationState.set(convKey, conv);
103
- }
104
- }
105
-
106
- createStream(params: { msgid?: string; conversationKey?: string; batchKey?: string }): string {
107
- const streamId = crypto.randomBytes(16).toString("hex");
108
- if (params.msgid) {
109
- this.msgidToStreamId.set(String(params.msgid), streamId);
110
- }
111
- this.streams.set(streamId, {
112
- streamId,
113
- msgid: params.msgid,
114
- conversationKey: params.conversationKey,
115
- batchKey: params.batchKey,
116
- createdAt: Date.now(),
117
- updatedAt: Date.now(),
118
- started: false,
119
- finished: false,
120
- content: "",
121
- });
122
- this.linkStreamToBatch(streamId, params.batchKey);
123
- return streamId;
124
- }
125
-
126
- getStream(streamId: string): StreamState | undefined {
127
- return this.streams.get(streamId);
128
- }
129
-
130
- getStreamByMsgId(msgid: string): string | undefined {
131
- return this.msgidToStreamId.get(String(msgid));
132
- }
133
-
134
- setStreamIdForMsgId(msgid: string, streamId: string): void {
135
- const key = String(msgid).trim();
136
- const value = String(streamId).trim();
137
- if (!key || !value) return;
138
- this.msgidToStreamId.set(key, value);
139
- }
140
-
141
- addAckStreamForBatch(params: { batchStreamId: string; ackStreamId: string }): void {
142
- const batchStreamId = params.batchStreamId.trim();
143
- const ackStreamId = params.ackStreamId.trim();
144
- if (!batchStreamId || !ackStreamId) return;
145
- const list = this.batchStreamIdToAckStreamIds.get(batchStreamId) ?? [];
146
- list.push(ackStreamId);
147
- this.batchStreamIdToAckStreamIds.set(batchStreamId, list);
148
- }
149
-
150
- drainAckStreamsForBatch(batchStreamId: string): string[] {
151
- const key = batchStreamId.trim();
152
- if (!key) return [];
153
- const list = this.batchStreamIdToAckStreamIds.get(key) ?? [];
154
- this.batchStreamIdToAckStreamIds.delete(key);
155
- return list;
156
- }
157
-
158
- updateStream(streamId: string, mutator: (state: StreamState) => void): void {
159
- const state = this.streams.get(streamId);
160
- if (state) {
161
- mutator(state);
162
- state.updatedAt = Date.now();
163
- }
164
- }
165
-
166
- markStarted(streamId: string): void {
167
- this.updateStream(streamId, (s) => {
168
- s.started = true;
169
- });
170
- }
171
-
172
- markFinished(streamId: string): void {
173
- this.updateStream(streamId, (s) => {
174
- s.finished = true;
175
- });
176
- }
177
-
178
- addPendingMessage(params: {
179
- conversationKey: string;
180
- target: WecomWebhookTarget;
181
- msg: WecomInboundMessage;
182
- msgContent: string;
183
- nonce: string;
184
- timestamp: string;
185
- debounceMs?: number;
186
- }): { streamId: string; status: "active_new" | "active_merged" | "queued_new" | "queued_merged" } {
187
- const { conversationKey, target, msg, msgContent, nonce, timestamp, debounceMs } = params;
188
- const effectiveDebounceMs = debounceMs ?? LIMITS.DEFAULT_DEBOUNCE_MS;
189
-
190
- const state = this.conversationState.get(conversationKey);
191
- if (!state) {
192
- const batchKey = conversationKey;
193
- const streamId = this.createStream({ msgid: msg.msgid, conversationKey, batchKey });
194
- const pending: PendingInbound = {
195
- streamId,
196
- conversationKey,
197
- batchKey,
198
- target,
199
- msg,
200
- contents: [msgContent],
201
- msgids: msg.msgid ? [msg.msgid] : [],
202
- nonce,
203
- timestamp,
204
- createdAt: Date.now(),
205
- timeout: setTimeout(() => {
206
- this.requestFlush(batchKey);
207
- }, effectiveDebounceMs),
208
- };
209
- this.pendingInbounds.set(batchKey, pending);
210
- this.conversationState.set(conversationKey, { activeBatchKey: batchKey, queue: [], nextSeq: 1 });
211
- return { streamId, status: "active_new" };
212
- }
213
-
214
- const activeBatchKey = state.activeBatchKey;
215
- const activeIsInitial = activeBatchKey === conversationKey;
216
- const activePending = this.pendingInbounds.get(activeBatchKey);
217
- if (activePending && !activeIsInitial) {
218
- const activeStream = this.streams.get(activePending.streamId);
219
- const activeStarted = Boolean(activeStream?.started);
220
- if (!activeStarted) {
221
- activePending.contents.push(msgContent);
222
- if (msg.msgid) {
223
- activePending.msgids.push(msg.msgid);
224
- }
225
- if (activePending.timeout) clearTimeout(activePending.timeout);
226
- activePending.timeout = setTimeout(() => {
227
- this.requestFlush(activeBatchKey);
228
- }, effectiveDebounceMs);
229
- return { streamId: activePending.streamId, status: "active_merged" };
230
- }
231
- }
232
-
233
- const queuedBatchKey = state.queue[0];
234
- if (queuedBatchKey) {
235
- const existingQueued = this.pendingInbounds.get(queuedBatchKey);
236
- if (existingQueued) {
237
- existingQueued.contents.push(msgContent);
238
- if (msg.msgid) {
239
- existingQueued.msgids.push(msg.msgid);
240
- }
241
- if (existingQueued.timeout) clearTimeout(existingQueued.timeout);
242
- existingQueued.timeout = setTimeout(() => {
243
- this.requestFlush(queuedBatchKey);
244
- }, effectiveDebounceMs);
245
- return { streamId: existingQueued.streamId, status: "queued_merged" };
246
- }
247
- }
248
-
249
- const seq = state.nextSeq++;
250
- const batchKey = `${conversationKey}#q${seq}`;
251
- state.queue = [batchKey];
252
- const streamId = this.createStream({ msgid: msg.msgid, conversationKey, batchKey });
253
- const pending: PendingInbound = {
254
- streamId,
255
- conversationKey,
256
- batchKey,
257
- target,
258
- msg,
259
- contents: [msgContent],
260
- msgids: msg.msgid ? [msg.msgid] : [],
261
- nonce,
262
- timestamp,
263
- createdAt: Date.now(),
264
- timeout: setTimeout(() => {
265
- this.requestFlush(batchKey);
266
- }, effectiveDebounceMs),
267
- };
268
- this.pendingInbounds.set(batchKey, pending);
269
- this.conversationState.set(conversationKey, state);
270
- return { streamId, status: "queued_new" };
271
- }
272
-
273
- private requestFlush(batchKey: string): void {
274
- const pending = this.pendingInbounds.get(batchKey);
275
- if (!pending) return;
276
-
277
- const state = this.conversationState.get(pending.conversationKey);
278
- const isActive = state?.activeBatchKey === batchKey;
279
- if (!isActive) {
280
- if (pending.timeout) {
281
- clearTimeout(pending.timeout);
282
- pending.timeout = null;
283
- }
284
- pending.readyToFlush = true;
285
- return;
286
- }
287
- this.flushPending(batchKey);
288
- }
289
-
290
- private flushPending(pendingKey: string): void {
291
- const pending = this.removePendingBatch(pendingKey);
292
- if (!pending) return;
293
-
294
- if (this.onFlush) {
295
- this.onFlush(pending);
296
- }
297
- }
298
-
299
- onStreamFinished(streamId: string): void {
300
- const batchKey = this.streamIdToBatchKey.get(streamId);
301
- const state = batchKey ? this.streams.get(streamId) : undefined;
302
- const conversationKey = state?.conversationKey;
303
- if (!batchKey || !conversationKey) return;
304
-
305
- this.unlinkStreamFromBatch(streamId, batchKey);
306
-
307
- const conv = this.conversationState.get(conversationKey);
308
- if (!conv) return;
309
- if (conv.activeBatchKey !== batchKey) return;
310
-
311
- const next = conv.queue.shift();
312
- if (!next) {
313
- this.conversationState.delete(conversationKey);
314
- return;
315
- }
316
- conv.activeBatchKey = next;
317
- this.conversationState.set(conversationKey, conv);
318
-
319
- const pending = this.pendingInbounds.get(next);
320
- if (!pending) return;
321
- if (pending.readyToFlush) {
322
- this.flushPending(next);
323
- }
324
- }
325
-
326
- prune(now: number = Date.now()): void {
327
- const streamCutoff = now - LIMITS.STREAM_TTL_MS;
328
-
329
- for (const [id, state] of this.streams.entries()) {
330
- if (state.updatedAt < streamCutoff) {
331
- this.removeStreamRecord(id, state);
332
- }
333
- }
334
-
335
- for (const [msgid, id] of this.msgidToStreamId.entries()) {
336
- if (!this.streams.has(id)) {
337
- this.msgidToStreamId.delete(msgid);
338
- }
339
- }
340
-
341
- for (const [key, pending] of this.pendingInbounds.entries()) {
342
- if (now - pending.createdAt > LIMITS.STREAM_TTL_MS) {
343
- this.removePendingBatch(key);
344
- }
345
- }
346
-
347
- this.pruneAckStreamMappings();
348
- this.pruneConversationState();
349
- }
350
- }