@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
@@ -4,23 +4,13 @@
4
4
  * 为每个用户/群组自动生成独立的 Agent ID,实现会话隔离。
5
5
  * 参考: openclaw-plugin-wecom/dynamic-agent.js
6
6
  */
7
-
8
- import type { OpenClawConfig } from "openclaw/plugin-sdk";
9
-
10
- export interface DynamicAgentConfig {
11
- enabled: boolean;
12
- dmCreateAgent: boolean;
13
- groupEnabled: boolean;
14
- adminUsers: string[];
15
- }
16
-
17
7
  /**
18
8
  * **getDynamicAgentConfig (读取动态 Agent 配置)**
19
9
  *
20
10
  * 从全局配置中读取动态 Agent 配置,提供默认值。
21
11
  */
22
- export function getDynamicAgentConfig(config: OpenClawConfig): DynamicAgentConfig {
23
- const dynamicAgents = (config as { channels?: { wecom?: { dynamicAgents?: Partial<DynamicAgentConfig> } } })?.channels?.wecom?.dynamicAgents;
12
+ export function getDynamicAgentConfig(config) {
13
+ const dynamicAgents = config?.channels?.wecom?.dynamicAgents;
24
14
  return {
25
15
  enabled: dynamicAgents?.enabled ?? false,
26
16
  dmCreateAgent: dynamicAgents?.dmCreateAgent ?? true,
@@ -28,114 +18,89 @@ export function getDynamicAgentConfig(config: OpenClawConfig): DynamicAgentConfi
28
18
  adminUsers: dynamicAgents?.adminUsers ?? [],
29
19
  };
30
20
  }
31
-
32
- function sanitizeDynamicIdPart(value: string): string {
21
+ function sanitizeDynamicIdPart(value) {
33
22
  return String(value)
34
23
  .trim()
35
24
  .toLowerCase()
36
25
  .replace(/[^a-z0-9_-]/g, "_");
37
26
  }
38
-
39
27
  /**
40
28
  * **generateAgentId (生成动态 Agent ID)**
41
29
  *
42
30
  * 根据账号 + 聊天类型 + 对端 ID 生成确定性的 Agent ID,避免多账号串会话。
43
31
  * 格式: wecom-{accountId}-{type}-{sanitizedPeerId}
44
32
  */
45
- export function generateAgentId(chatType: "dm" | "group", peerId: string, accountId?: string): string {
33
+ export function generateAgentId(chatType, peerId, accountId) {
46
34
  const sanitizedPeer = sanitizeDynamicIdPart(peerId) || "unknown";
47
35
  const sanitizedAccountId = sanitizeDynamicIdPart(accountId ?? "default") || "default";
48
36
  return `wecom-${sanitizedAccountId}-${chatType}-${sanitizedPeer}`;
49
37
  }
50
-
51
- export function buildAgentSessionTarget(userId: string, accountId?: string): string {
38
+ export function buildAgentSessionTarget(userId, accountId) {
52
39
  const normalizedUserId = String(userId).trim();
53
40
  const sanitizedAccountId = sanitizeDynamicIdPart(accountId ?? "default") || "default";
54
41
  // Always use explicit user: prefix to avoid ambiguity with numeric party IDs
55
42
  return `wecom-agent:${sanitizedAccountId}:user:${normalizedUserId}`;
56
43
  }
57
-
58
44
  /**
59
45
  * **shouldUseDynamicAgent (检查是否使用动态 Agent)**
60
46
  *
61
47
  * 根据配置和发送者信息判断是否应使用动态 Agent。
62
48
  * 管理员(adminUsers)始终绕过动态路由,使用主 Agent。
63
49
  */
64
- export function shouldUseDynamicAgent(params: {
65
- chatType: "dm" | "group";
66
- senderId: string;
67
- config: OpenClawConfig;
68
- }): boolean {
50
+ export function shouldUseDynamicAgent(params) {
69
51
  const { chatType, senderId, config } = params;
70
52
  const dynamicConfig = getDynamicAgentConfig(config);
71
-
72
53
  if (!dynamicConfig.enabled) {
73
54
  return false;
74
55
  }
75
-
76
56
  // 管理员绕过动态路由
77
57
  const sender = String(senderId).trim().toLowerCase();
78
- const isAdmin = dynamicConfig.adminUsers.some(
79
- (admin) => admin.trim().toLowerCase() === sender
80
- );
58
+ const isAdmin = dynamicConfig.adminUsers.some((admin) => admin.trim().toLowerCase() === sender);
81
59
  if (isAdmin) {
82
60
  return false;
83
61
  }
84
-
85
62
  if (chatType === "group") {
86
63
  return dynamicConfig.groupEnabled;
87
64
  }
88
65
  return dynamicConfig.dmCreateAgent;
89
66
  }
90
-
91
67
  /**
92
68
  * 内存中已确保的 Agent ID(避免重复写入)
93
69
  */
94
- const ensuredDynamicAgentIds = new Set<string>();
95
-
70
+ const ensuredDynamicAgentIds = new Set();
96
71
  /**
97
72
  * 写入队列(避免并发冲突)
98
73
  */
99
- let ensureDynamicAgentWriteQueue: Promise<void> = Promise.resolve();
100
-
74
+ let ensureDynamicAgentWriteQueue = Promise.resolve();
101
75
  /**
102
76
  * 将 Agent ID 插入 agents.list(如果不存在)
103
77
  */
104
- function upsertAgentIdOnlyEntry(cfg: Record<string, unknown>, agentId: string): boolean {
78
+ function upsertAgentIdOnlyEntry(cfg, agentId) {
105
79
  if (!cfg.agents || typeof cfg.agents !== "object") {
106
80
  cfg.agents = {};
107
81
  }
108
-
109
- const agentsObj = cfg.agents as Record<string, unknown>;
110
- const currentList: Array<{ id: string }> = Array.isArray(agentsObj.list) ? agentsObj.list as Array<{ id: string }> : [];
111
- const existingIds = new Set(
112
- currentList
113
- .map((entry) => entry?.id?.trim().toLowerCase())
114
- .filter((id): id is string => Boolean(id))
115
- );
116
-
82
+ const agentsObj = cfg.agents;
83
+ const currentList = Array.isArray(agentsObj.list) ? agentsObj.list : [];
84
+ const existingIds = new Set(currentList
85
+ .map((entry) => entry?.id?.trim().toLowerCase())
86
+ .filter((id) => Boolean(id)));
117
87
  let changed = false;
118
88
  const nextList = [...currentList];
119
-
120
89
  // 首次创建时保留 main 作为默认
121
90
  if (nextList.length === 0) {
122
91
  nextList.push({ id: "main" });
123
92
  existingIds.add("main");
124
93
  changed = true;
125
94
  }
126
-
127
95
  if (!existingIds.has(agentId.toLowerCase())) {
128
96
  nextList.push({ id: agentId });
129
97
  changed = true;
130
98
  }
131
-
132
99
  if (changed) {
133
100
  agentsObj.list = nextList;
134
101
  }
135
-
136
102
  return changed;
137
103
  }
138
-
139
104
  /**
140
105
  * **ensureDynamicAgentListed (确保动态 Agent 已添加到 agents.list)**
141
106
  *
@@ -146,40 +111,38 @@ function upsertAgentIdOnlyEntry(cfg: Record<string, unknown>, agentId: string):
146
111
  * - 异步:不阻塞消息处理流程
147
112
  */
148
113
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
149
- export async function ensureDynamicAgentListed(agentId: string, runtime: any): Promise<void> {
114
+ export async function ensureDynamicAgentListed(agentId, runtime) {
150
115
  const normalizedId = String(agentId).trim().toLowerCase();
151
- if (!normalizedId) return;
152
- if (ensuredDynamicAgentIds.has(normalizedId)) return;
153
-
116
+ if (!normalizedId)
117
+ return;
118
+ if (ensuredDynamicAgentIds.has(normalizedId))
119
+ return;
154
120
  const configRuntime = runtime?.config;
155
- if (!configRuntime?.loadConfig || !configRuntime?.writeConfigFile) return;
156
-
121
+ if (!configRuntime?.loadConfig || !configRuntime?.writeConfigFile)
122
+ return;
157
123
  ensureDynamicAgentWriteQueue = ensureDynamicAgentWriteQueue
158
124
  .then(async () => {
159
- if (ensuredDynamicAgentIds.has(normalizedId)) return;
160
-
161
- const latestConfig = configRuntime.loadConfig!();
162
- if (!latestConfig || typeof latestConfig !== "object") return;
163
-
164
- const changed = upsertAgentIdOnlyEntry(latestConfig as Record<string, unknown>, normalizedId);
165
- if (changed) {
166
- await configRuntime.writeConfigFile!(latestConfig as unknown);
167
- }
168
-
169
- ensuredDynamicAgentIds.add(normalizedId);
170
- })
125
+ if (ensuredDynamicAgentIds.has(normalizedId))
126
+ return;
127
+ const latestConfig = configRuntime.loadConfig();
128
+ if (!latestConfig || typeof latestConfig !== "object")
129
+ return;
130
+ const changed = upsertAgentIdOnlyEntry(latestConfig, normalizedId);
131
+ if (changed) {
132
+ await configRuntime.writeConfigFile(latestConfig);
133
+ }
134
+ ensuredDynamicAgentIds.add(normalizedId);
135
+ })
171
136
  .catch((err) => {
172
- console.warn(`[wecom] 动态 Agent 添加失败: ${normalizedId}`, err);
173
- });
174
-
137
+ console.warn(`[wecom] 动态 Agent 添加失败: ${normalizedId}`, err);
138
+ });
175
139
  await ensureDynamicAgentWriteQueue;
176
140
  }
177
-
178
141
  /**
179
142
  * **resetEnsuredCache (重置已确保缓存)**
180
143
  *
181
144
  * 主要用于测试场景,重置内存中的缓存状态。
182
145
  */
183
- export function resetEnsuredCache(): void {
146
+ export function resetEnsuredCache() {
184
147
  ensuredDynamicAgentIds.clear();
185
148
  }
@@ -0,0 +1,139 @@
1
+ import { listWecomAccountIds, resolveWecomAccount, resolveWecomAccountConflict, } from "./config/index.js";
2
+ import { createAccountRuntime } from "./app/bootstrap.js";
3
+ import { registerAccountRuntime, unregisterAccountRuntime } from "./app/index.js";
4
+ import { WecomBotCapabilityService } from "./capability/bot/index.js";
5
+ import { WecomAgentIngressService } from "./capability/agent/index.js";
6
+ const accountRouteRegistry = new Map();
7
+ function logRegisteredRouteSummary(ctx, preferredOrder) {
8
+ const seen = new Set();
9
+ const orderedAccountIds = [
10
+ ...preferredOrder.filter((accountId) => accountRouteRegistry.has(accountId)),
11
+ ...Array.from(accountRouteRegistry.keys())
12
+ .filter((accountId) => !seen.has(accountId))
13
+ .sort((a, b) => a.localeCompare(b)),
14
+ ].filter((accountId) => {
15
+ if (seen.has(accountId))
16
+ return false;
17
+ seen.add(accountId);
18
+ return true;
19
+ });
20
+ const entries = orderedAccountIds
21
+ .map((accountId) => {
22
+ const routes = accountRouteRegistry.get(accountId);
23
+ if (!routes)
24
+ return undefined;
25
+ const botText = routes.botPaths.length > 0 ? routes.botPaths.join(", ") : "未启用";
26
+ const agentText = routes.agentPaths.length > 0 ? routes.agentPaths.join(", ") : "未启用";
27
+ return `accountId=${accountId}(Bot: ${botText};Agent: ${agentText})`;
28
+ })
29
+ .filter((entry) => Boolean(entry));
30
+ const summary = entries.length > 0 ? entries.join("; ") : "无";
31
+ ctx.log?.info(`[${ctx.account.accountId}] 已注册账号路由汇总:${summary}`);
32
+ }
33
+ function resolveExpectedRouteSummaryAccountIds(cfg) {
34
+ return listWecomAccountIds(cfg)
35
+ .filter((accountId) => {
36
+ const conflict = resolveWecomAccountConflict({ cfg, accountId });
37
+ if (conflict)
38
+ return false;
39
+ const account = resolveWecomAccount({ cfg, accountId });
40
+ if (!account.enabled || !account.configured)
41
+ return false;
42
+ return Boolean(account.bot?.configured || account.agent?.configured);
43
+ })
44
+ .sort((a, b) => a.localeCompare(b));
45
+ }
46
+ function waitForAbortSignal(abortSignal) {
47
+ if (abortSignal.aborted) {
48
+ return Promise.resolve();
49
+ }
50
+ return new Promise((resolve) => {
51
+ const onAbort = () => {
52
+ abortSignal.removeEventListener("abort", onAbort);
53
+ resolve();
54
+ };
55
+ abortSignal.addEventListener("abort", onAbort, { once: true });
56
+ });
57
+ }
58
+ /**
59
+ * Keeps WeCom webhook targets registered for the account lifecycle.
60
+ * The promise only settles after gateway abort/reload signals shutdown.
61
+ */
62
+ export async function monitorWecomProvider(ctx) {
63
+ const account = ctx.account;
64
+ const cfg = ctx.cfg;
65
+ const expectedRouteSummaryAccountIds = resolveExpectedRouteSummaryAccountIds(cfg);
66
+ const conflict = resolveWecomAccountConflict({
67
+ cfg,
68
+ accountId: account.accountId,
69
+ });
70
+ if (conflict) {
71
+ ctx.setStatus({
72
+ accountId: account.accountId,
73
+ running: false,
74
+ configured: false,
75
+ lastError: conflict.message,
76
+ });
77
+ throw new Error(conflict.message);
78
+ }
79
+ const bot = account.bot;
80
+ const agent = account.agent;
81
+ const botConfigured = Boolean(bot?.configured);
82
+ const agentConfigured = Boolean(agent?.configured);
83
+ if (!botConfigured && !agentConfigured) {
84
+ ctx.log?.warn(`[${account.accountId}] wecom not configured; channel is idle`);
85
+ ctx.setStatus({ accountId: account.accountId, running: false, configured: false });
86
+ await waitForAbortSignal(ctx.abortSignal);
87
+ return;
88
+ }
89
+ const accountRuntime = createAccountRuntime(ctx);
90
+ registerAccountRuntime(accountRuntime);
91
+ const botPaths = [];
92
+ const agentPaths = [];
93
+ const runtimeEnv = {
94
+ log: (message) => ctx.log?.info(message),
95
+ error: (message) => ctx.log?.error(message),
96
+ };
97
+ const botService = new WecomBotCapabilityService(accountRuntime, cfg, runtimeEnv);
98
+ const agentIngress = new WecomAgentIngressService(accountRuntime, cfg, runtimeEnv);
99
+ try {
100
+ ctx.log?.info(`[${account.accountId}] wecom runtime start bot=${bot?.primaryTransport ?? "disabled"} agent=${agentConfigured ? "callback/api" : "disabled"}`);
101
+ const botRegistration = botService.start();
102
+ if (botRegistration) {
103
+ botPaths.push(...botRegistration.descriptors);
104
+ ctx.log?.info(`[${account.accountId}] wecom bot ${botRegistration.transport} started: ${botRegistration.descriptors.join(", ")}`);
105
+ }
106
+ const agentRegistration = agentIngress.start();
107
+ if (agentRegistration) {
108
+ agentPaths.push(...agentRegistration.descriptors);
109
+ ctx.log?.info(`[${account.accountId}] wecom agent ${agentRegistration.transport} started: ${agentRegistration.descriptors.join(", ")}`);
110
+ }
111
+ accountRouteRegistry.set(account.accountId, { botPaths, agentPaths });
112
+ const shouldLogSummary = expectedRouteSummaryAccountIds.length <= 1 ||
113
+ expectedRouteSummaryAccountIds.every((accountId) => accountRouteRegistry.has(accountId));
114
+ if (shouldLogSummary) {
115
+ logRegisteredRouteSummary(ctx, expectedRouteSummaryAccountIds);
116
+ }
117
+ ctx.setStatus({
118
+ running: true,
119
+ configured: true,
120
+ webhookPath: botPaths[0] ?? agentPaths[0] ?? null,
121
+ lastStartAt: Date.now(),
122
+ ...accountRuntime.buildRuntimeStatus(),
123
+ });
124
+ ctx.log?.info(`[${account.accountId}] runtime status health=${accountRuntime.buildRuntimeStatus().health} transports=${(accountRuntime.buildRuntimeStatus().transportSessions ?? []).join(" | ") || "none"}`);
125
+ await waitForAbortSignal(ctx.abortSignal);
126
+ }
127
+ finally {
128
+ botService.stop();
129
+ agentIngress.stop();
130
+ accountRouteRegistry.delete(account.accountId);
131
+ unregisterAccountRuntime(account.accountId);
132
+ ctx.setStatus({
133
+ running: false,
134
+ lastStopAt: Date.now(),
135
+ ...accountRuntime.buildRuntimeStatus(),
136
+ });
137
+ ctx.log?.info(`[${account.accountId}] wecom runtime stopped`);
138
+ }
139
+ }
@@ -0,0 +1,114 @@
1
+ import { ProxyAgent, fetch as undiciFetch } from "undici";
2
+ const proxyDispatchers = new Map();
3
+ /**
4
+ * **getProxyDispatcher (获取代理 Dispatcher)**
5
+ *
6
+ * 缓存并复用 ProxyAgent,避免重复创建连接池。
7
+ */
8
+ function getProxyDispatcher(proxyUrl) {
9
+ const existing = proxyDispatchers.get(proxyUrl);
10
+ if (existing)
11
+ return existing;
12
+ const created = new ProxyAgent(proxyUrl);
13
+ proxyDispatchers.set(proxyUrl, created);
14
+ return created;
15
+ }
16
+ function summarizeHttpTarget(input) {
17
+ try {
18
+ const url = typeof input === "string" ? new URL(input) : input;
19
+ return `${url.origin}${url.pathname}`;
20
+ }
21
+ catch {
22
+ return String(input);
23
+ }
24
+ }
25
+ function mergeAbortSignal(params) {
26
+ const signals = [];
27
+ if (params.signal)
28
+ signals.push(params.signal);
29
+ if (params.timeoutMs && Number.isFinite(params.timeoutMs) && params.timeoutMs > 0) {
30
+ signals.push(AbortSignal.timeout(params.timeoutMs));
31
+ }
32
+ if (!signals.length)
33
+ return undefined;
34
+ if (signals.length === 1)
35
+ return signals[0];
36
+ return AbortSignal.any(signals);
37
+ }
38
+ /**
39
+ * **wecomFetch (统一 HTTP 请求)**
40
+ *
41
+ * 基于 `undici` 的 fetch 封装,自动处理 ProxyAgent 和 Timeout。
42
+ * 所有对企业微信 API 的调用都应经过此函数。
43
+ */
44
+ export async function wecomFetch(input, init, opts) {
45
+ const proxyUrl = opts?.proxyUrl?.trim() ?? "";
46
+ const dispatcher = proxyUrl ? getProxyDispatcher(proxyUrl) : undefined;
47
+ const startedAt = Date.now();
48
+ const method = (init?.method ?? "GET").toUpperCase();
49
+ const target = summarizeHttpTarget(input);
50
+ const initSignal = init?.signal ?? undefined;
51
+ const signal = mergeAbortSignal({ signal: opts?.signal ?? initSignal, timeoutMs: opts?.timeoutMs });
52
+ const headers = new Headers(init?.headers ?? {});
53
+ if (!headers.has("User-Agent")) {
54
+ headers.set("User-Agent", "OpenClaw/2.0 (WeCom-Agent)");
55
+ }
56
+ const nextInit = {
57
+ ...(init ?? {}),
58
+ ...(signal ? { signal } : {}),
59
+ ...(dispatcher ? { dispatcher } : {}),
60
+ headers,
61
+ };
62
+ try {
63
+ console.log(`[wecom-http] request method=${method} target=${target} proxy=${proxyUrl || "none"} timeoutMs=${String(opts?.timeoutMs ?? "none")}`);
64
+ const response = await undiciFetch(input, nextInit);
65
+ console.log(`[wecom-http] response method=${method} target=${target} status=${response.status} durationMs=${Date.now() - startedAt}`);
66
+ return response;
67
+ }
68
+ catch (err) {
69
+ if (err instanceof Error && err.name === "TimeoutError") {
70
+ console.error(`[wecom-http] timeout method=${method} target=${target} durationMs=${Date.now() - startedAt} proxy=${proxyUrl || "none"}`);
71
+ }
72
+ else if (err instanceof Error && err.name === "TypeError" && err.message === "fetch failed") {
73
+ const cause = err.cause;
74
+ console.error(`[wecom-http] fetch failed method=${method} target=${target} durationMs=${Date.now() - startedAt} proxy=${proxyUrl || "none"}${cause ? ` cause=${String(cause)}` : ""}`);
75
+ }
76
+ else if (err instanceof Error && err.name === "AbortError") {
77
+ console.error(`[wecom-http] aborted method=${method} target=${target} durationMs=${Date.now() - startedAt} proxy=${proxyUrl || "none"}`);
78
+ }
79
+ throw err;
80
+ }
81
+ }
82
+ /**
83
+ * **readResponseBodyAsBuffer (读取响应 Body)**
84
+ *
85
+ * 将 Response Body 读取为 Buffer,支持最大字节限制以防止内存溢出。
86
+ * 适用于下载媒体文件等场景。
87
+ */
88
+ export async function readResponseBodyAsBuffer(res, maxBytes) {
89
+ if (!res.body)
90
+ return Buffer.alloc(0);
91
+ const limit = maxBytes && Number.isFinite(maxBytes) && maxBytes > 0 ? maxBytes : undefined;
92
+ const chunks = [];
93
+ let total = 0;
94
+ const reader = res.body.getReader();
95
+ while (true) {
96
+ const { done, value } = await reader.read();
97
+ if (done)
98
+ break;
99
+ if (!value)
100
+ continue;
101
+ total += value.byteLength;
102
+ if (limit && total > limit) {
103
+ try {
104
+ await reader.cancel("body too large");
105
+ }
106
+ catch {
107
+ // ignore
108
+ }
109
+ throw new Error(`response body too large (>${limit} bytes)`);
110
+ }
111
+ chunks.push(value);
112
+ }
113
+ return Buffer.concat(chunks.map((c) => Buffer.from(c)));
114
+ }
@@ -1,56 +1,49 @@
1
1
  import crypto from "node:crypto";
2
2
  import { decodeEncodingAESKey, pkcs7Unpad, WECOM_PKCS7_BLOCK_SIZE } from "./crypto.js";
3
- import { readResponseBodyAsBuffer, wecomFetch, type WecomHttpOptions } from "./http.js";
4
-
5
- export type DecryptedWecomMedia = {
6
- buffer: Buffer;
7
- sourceContentType?: string;
8
- sourceFilename?: string;
9
- sourceUrl?: string;
10
- };
11
-
12
- function normalizeMime(contentType?: string | null): string | undefined {
3
+ import { readResponseBodyAsBuffer, wecomFetch } from "./http.js";
4
+ function normalizeMime(contentType) {
13
5
  const raw = String(contentType ?? "").trim();
14
- if (!raw) return undefined;
6
+ if (!raw)
7
+ return undefined;
15
8
  return raw.split(";")[0]?.trim().toLowerCase() || undefined;
16
9
  }
17
-
18
- function extractFilenameFromContentDisposition(disposition?: string | null): string | undefined {
10
+ function extractFilenameFromContentDisposition(disposition) {
19
11
  const raw = String(disposition ?? "").trim();
20
- if (!raw) return undefined;
21
-
12
+ if (!raw)
13
+ return undefined;
22
14
  const star = raw.match(/filename\*\s*=\s*([^;]+)/i);
23
15
  if (star?.[1]) {
24
16
  const v = star[1].trim().replace(/^UTF-8''/i, "").replace(/^"(.*)"$/, "$1");
25
17
  try {
26
18
  const decoded = decodeURIComponent(v);
27
- if (decoded.trim()) return decoded.trim();
28
- } catch { /* ignore */ }
29
- if (v.trim()) return v.trim();
19
+ if (decoded.trim())
20
+ return decoded.trim();
21
+ }
22
+ catch { /* ignore */ }
23
+ if (v.trim())
24
+ return v.trim();
30
25
  }
31
-
32
26
  const plain = raw.match(/filename\s*=\s*([^;]+)/i);
33
27
  if (plain?.[1]) {
34
28
  const v = plain[1].trim().replace(/^"(.*)"$/, "$1").trim();
35
- if (v) return v;
29
+ if (v)
30
+ return v;
36
31
  }
37
32
  return undefined;
38
33
  }
39
-
40
34
  /**
41
35
  * **decryptWecomMedia (解密企业微信媒体文件)**
42
- *
36
+ *
43
37
  * 简易封装:直接传入 URL 和 AES Key 下载并解密。
44
38
  * 企业微信媒体文件使用与消息体相同的 AES-256-CBC 加密,IV 为 AES Key 前16字节。
45
39
  * 解密后需移除 PKCS#7 填充。
46
40
  */
47
- export async function decryptWecomMedia(url: string, encodingAESKey: string, maxBytes?: number): Promise<Buffer> {
41
+ export async function decryptWecomMedia(url, encodingAESKey, maxBytes) {
48
42
  return decryptWecomMediaWithHttp(url, encodingAESKey, { maxBytes });
49
43
  }
50
-
51
44
  /**
52
45
  * **decryptWecomMediaWithHttp (解密企业微信媒体 - 高级)**
53
- *
46
+ *
54
47
  * 支持传递 HTTP 选项(如 Proxy、Timeout)。
55
48
  * 流程:
56
49
  * 1. 下载加密内容。
@@ -58,26 +51,17 @@ export async function decryptWecomMedia(url: string, encodingAESKey: string, max
58
51
  * 3. AES-CBC 解密。
59
52
  * 4. PKCS#7 去除填充。
60
53
  */
61
- export async function decryptWecomMediaWithHttp(
62
- url: string,
63
- encodingAESKey: string,
64
- params?: { maxBytes?: number; http?: WecomHttpOptions },
65
- ): Promise<Buffer> {
54
+ export async function decryptWecomMediaWithHttp(url, encodingAESKey, params) {
66
55
  const decrypted = await decryptWecomMediaWithMeta(url, encodingAESKey, params);
67
56
  return decrypted.buffer;
68
57
  }
69
-
70
58
  /**
71
59
  * **decryptWecomMediaWithMeta (解密企业微信媒体并返回源信息)**
72
- *
60
+ *
73
61
  * 在返回解密结果的同时,保留下载响应中的元信息(content-type / filename / final url),
74
62
  * 供上层更准确地推断文件后缀和 MIME。
75
63
  */
76
- export async function decryptWecomMediaWithMeta(
77
- url: string,
78
- encodingAESKey: string,
79
- params?: { maxBytes?: number; http?: WecomHttpOptions },
80
- ): Promise<DecryptedWecomMedia> {
64
+ export async function decryptWecomMediaWithMeta(url, encodingAESKey, params) {
81
65
  // 1. Download encrypted content
82
66
  const res = await wecomFetch(url, undefined, { ...params?.http, timeoutMs: params?.http?.timeoutMs ?? 15_000 });
83
67
  if (!res.ok) {
@@ -87,11 +71,9 @@ export async function decryptWecomMediaWithMeta(
87
71
  const sourceFilename = extractFilenameFromContentDisposition(res.headers.get("content-disposition"));
88
72
  const sourceUrl = res.url || url;
89
73
  const encryptedData = await readResponseBodyAsBuffer(res, params?.maxBytes);
90
-
91
74
  // 2. Prepare Key and IV
92
75
  const aesKey = decodeEncodingAESKey(encodingAESKey);
93
76
  const iv = aesKey.subarray(0, 16);
94
-
95
77
  // 3. Decrypt
96
78
  const decipher = crypto.createDecipheriv("aes-256-cbc", aesKey, iv);
97
79
  decipher.setAutoPadding(false); // We handle padding manually
@@ -99,7 +81,6 @@ export async function decryptWecomMediaWithMeta(
99
81
  decipher.update(encryptedData),
100
82
  decipher.final(),
101
83
  ]);
102
-
103
84
  // 4. Unpad
104
85
  // Note: Unlike msg bodies, usually removing PKCS#7 padding is enough for media files.
105
86
  // The Python SDK logic: pad_len = decrypted_data[-1]; decrypted_data = decrypted_data[:-pad_len]
@@ -0,0 +1,7 @@
1
+ export const LIMITS = {
2
+ STREAM_TTL_MS: 10 * 60 * 1000,
3
+ ACTIVE_REPLY_TTL_MS: 60 * 60 * 1000,
4
+ DEFAULT_DEBOUNCE_MS: 500,
5
+ STREAM_MAX_BYTES: 20_480,
6
+ REQUEST_TIMEOUT_MS: 15_000,
7
+ };
@@ -0,0 +1,28 @@
1
+ import { LegacyOperationalEventStore } from "../observability/legacy-operational-event-store.js";
2
+ import { ActiveReplyStore } from "../store/active-reply-store.js";
3
+ import { StreamStore } from "../store/stream-batch-store.js";
4
+ import { LIMITS } from "./limits.js";
5
+ export { LIMITS, StreamStore, ActiveReplyStore };
6
+ class MonitorState {
7
+ streamStore = new StreamStore();
8
+ activeReplyStore = new ActiveReplyStore("multi");
9
+ operationalEvents = new LegacyOperationalEventStore();
10
+ pruneInterval;
11
+ startPruning(intervalMs = 60_000) {
12
+ if (this.pruneInterval)
13
+ return;
14
+ this.pruneInterval = setInterval(() => {
15
+ const now = Date.now();
16
+ this.streamStore.prune(now);
17
+ this.activeReplyStore.prune(now);
18
+ this.operationalEvents.prune(now);
19
+ }, intervalMs);
20
+ }
21
+ stopPruning() {
22
+ if (this.pruneInterval) {
23
+ clearInterval(this.pruneInterval);
24
+ this.pruneInterval = undefined;
25
+ }
26
+ }
27
+ }
28
+ export const monitorState = new MonitorState();