@mxf-dev/core 1.0.1 → 2.0.1

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 (303) hide show
  1. package/README.md +18 -0
  2. package/dist/adapters/FrameworkAdapter.js +1 -1
  3. package/dist/adapters/FrameworkAdapter.js.map +1 -1
  4. package/dist/config/ConfigManager.d.ts +9 -8
  5. package/dist/config/ConfigManager.d.ts.map +1 -1
  6. package/dist/config/ConfigManager.js +47 -17
  7. package/dist/config/ConfigManager.js.map +1 -1
  8. package/dist/config/memory-strata.config.d.ts +25 -0
  9. package/dist/config/memory-strata.config.d.ts.map +1 -0
  10. package/dist/config/memory-strata.config.js +107 -0
  11. package/dist/config/memory-strata.config.js.map +1 -0
  12. package/dist/constants/ToolNames.d.ts +1 -29
  13. package/dist/constants/ToolNames.d.ts.map +1 -1
  14. package/dist/constants/ToolNames.js +4 -17
  15. package/dist/constants/ToolNames.js.map +1 -1
  16. package/dist/database/DatabaseAdapterFactory.d.ts +14 -2
  17. package/dist/database/DatabaseAdapterFactory.d.ts.map +1 -1
  18. package/dist/database/DatabaseAdapterFactory.js +21 -6
  19. package/dist/database/DatabaseAdapterFactory.js.map +1 -1
  20. package/dist/database/adapters/mongodb/MongoBaseRepository.d.ts +7 -1
  21. package/dist/database/adapters/mongodb/MongoBaseRepository.d.ts.map +1 -1
  22. package/dist/database/adapters/mongodb/MongoBaseRepository.js +12 -2
  23. package/dist/database/adapters/mongodb/MongoBaseRepository.js.map +1 -1
  24. package/dist/events/ClientEventBus.d.ts +16 -77
  25. package/dist/events/ClientEventBus.d.ts.map +1 -1
  26. package/dist/events/ClientEventBus.js +38 -311
  27. package/dist/events/ClientEventBus.js.map +1 -1
  28. package/dist/events/EventBus.d.ts +1 -1
  29. package/dist/events/EventBus.d.ts.map +1 -1
  30. package/dist/events/EventBus.js +5 -4
  31. package/dist/events/EventBus.js.map +1 -1
  32. package/dist/events/EventBusBase.d.ts +139 -34
  33. package/dist/events/EventBusBase.d.ts.map +1 -1
  34. package/dist/events/EventBusBase.js +366 -88
  35. package/dist/events/EventBusBase.js.map +1 -1
  36. package/dist/events/EventNames.d.ts +10 -1
  37. package/dist/events/EventNames.d.ts.map +1 -1
  38. package/dist/events/EventNames.js +3 -0
  39. package/dist/events/EventNames.js.map +1 -1
  40. package/dist/events/PublicEvents.d.ts +6 -2
  41. package/dist/events/PublicEvents.d.ts.map +1 -1
  42. package/dist/events/PublicEvents.js +7 -4
  43. package/dist/events/PublicEvents.js.map +1 -1
  44. package/dist/events/ServerEventBus.d.ts +23 -112
  45. package/dist/events/ServerEventBus.d.ts.map +1 -1
  46. package/dist/events/ServerEventBus.js +58 -323
  47. package/dist/events/ServerEventBus.js.map +1 -1
  48. package/dist/events/event-definitions/LlmBudgetEvents.d.ts +68 -0
  49. package/dist/events/event-definitions/LlmBudgetEvents.d.ts.map +1 -0
  50. package/dist/events/event-definitions/LlmBudgetEvents.js +35 -0
  51. package/dist/events/event-definitions/LlmBudgetEvents.js.map +1 -0
  52. package/dist/interfaces/IMemoryPersistence.d.ts +41 -0
  53. package/dist/interfaces/IMemoryPersistence.d.ts.map +1 -0
  54. package/dist/interfaces/IMemoryPersistence.js +15 -0
  55. package/dist/interfaces/IMemoryPersistence.js.map +1 -0
  56. package/dist/middleware/MxpMiddleware.d.ts +8 -1
  57. package/dist/middleware/MxpMiddleware.d.ts.map +1 -1
  58. package/dist/middleware/MxpMiddleware.js +42 -20
  59. package/dist/middleware/MxpMiddleware.js.map +1 -1
  60. package/dist/models/channelKey.d.ts +1 -0
  61. package/dist/models/channelKey.d.ts.map +1 -1
  62. package/dist/models/channelKey.js +5 -0
  63. package/dist/models/channelKey.js.map +1 -1
  64. package/dist/models/memoryUtility.d.ts +39 -0
  65. package/dist/models/memoryUtility.d.ts.map +1 -0
  66. package/dist/models/memoryUtility.js +39 -0
  67. package/dist/models/memoryUtility.js.map +1 -0
  68. package/dist/models/user.d.ts.map +1 -1
  69. package/dist/models/user.js +23 -1
  70. package/dist/models/user.js.map +1 -1
  71. package/dist/mxp/BinaryProtocolLayer.d.ts +24 -8
  72. package/dist/mxp/BinaryProtocolLayer.d.ts.map +1 -1
  73. package/dist/mxp/BinaryProtocolLayer.js +90 -103
  74. package/dist/mxp/BinaryProtocolLayer.js.map +1 -1
  75. package/dist/mxp/ContextCompressionEngine.d.ts +57 -10
  76. package/dist/mxp/ContextCompressionEngine.d.ts.map +1 -1
  77. package/dist/mxp/ContextCompressionEngine.js +170 -128
  78. package/dist/mxp/ContextCompressionEngine.js.map +1 -1
  79. package/dist/mxp/MxpTokenOptimizer.d.ts +7 -3
  80. package/dist/mxp/MxpTokenOptimizer.d.ts.map +1 -1
  81. package/dist/mxp/MxpTokenOptimizer.js +8 -5
  82. package/dist/mxp/MxpTokenOptimizer.js.map +1 -1
  83. package/dist/prompts/MxfAgentSystemPrompt.d.ts +1 -1
  84. package/dist/prompts/MxfAgentSystemPrompt.d.ts.map +1 -1
  85. package/dist/prompts/MxfAgentSystemPrompt.js +47 -57
  86. package/dist/prompts/MxfAgentSystemPrompt.js.map +1 -1
  87. package/dist/protocols/mcp/ToolError.d.ts +123 -0
  88. package/dist/protocols/mcp/ToolError.d.ts.map +1 -0
  89. package/dist/protocols/mcp/ToolError.js +108 -0
  90. package/dist/protocols/mcp/ToolError.js.map +1 -0
  91. package/dist/protocols/mcp/defineTool.d.ts +133 -0
  92. package/dist/protocols/mcp/defineTool.d.ts.map +1 -0
  93. package/dist/protocols/mcp/defineTool.js +236 -0
  94. package/dist/protocols/mcp/defineTool.js.map +1 -0
  95. package/dist/protocols/mcp/security/HttpTargetGuard.d.ts +38 -0
  96. package/dist/protocols/mcp/security/HttpTargetGuard.d.ts.map +1 -0
  97. package/dist/protocols/mcp/security/HttpTargetGuard.js +227 -0
  98. package/dist/protocols/mcp/security/HttpTargetGuard.js.map +1 -0
  99. package/dist/protocols/mcp/security/McpSecurityGuard.d.ts +9 -1
  100. package/dist/protocols/mcp/security/McpSecurityGuard.d.ts.map +1 -1
  101. package/dist/protocols/mcp/security/McpSecurityGuard.js +10 -2
  102. package/dist/protocols/mcp/security/McpSecurityGuard.js.map +1 -1
  103. package/dist/protocols/mcp/security/McpToolPolicy.d.ts +81 -0
  104. package/dist/protocols/mcp/security/McpToolPolicy.d.ts.map +1 -0
  105. package/dist/protocols/mcp/security/McpToolPolicy.js +202 -0
  106. package/dist/protocols/mcp/security/McpToolPolicy.js.map +1 -0
  107. package/dist/protocols/mcp/services/ExternalMcpServerManager.d.ts +51 -18
  108. package/dist/protocols/mcp/services/ExternalMcpServerManager.d.ts.map +1 -1
  109. package/dist/protocols/mcp/services/ExternalMcpServerManager.js +256 -283
  110. package/dist/protocols/mcp/services/ExternalMcpServerManager.js.map +1 -1
  111. package/dist/protocols/mcp/services/ExternalServerConfigs.d.ts +39 -6
  112. package/dist/protocols/mcp/services/ExternalServerConfigs.d.ts.map +1 -1
  113. package/dist/protocols/mcp/services/ExternalServerConfigs.js +76 -18
  114. package/dist/protocols/mcp/services/ExternalServerConfigs.js.map +1 -1
  115. package/dist/protocols/mcp/tools/AnalyticsTools.js +1 -1
  116. package/dist/protocols/mcp/tools/AnalyticsTools.js.map +1 -1
  117. package/dist/protocols/mcp/tools/CodeAnalysisTools.d.ts.map +1 -1
  118. package/dist/protocols/mcp/tools/CodeAnalysisTools.js +15 -0
  119. package/dist/protocols/mcp/tools/CodeAnalysisTools.js.map +1 -1
  120. package/dist/protocols/mcp/tools/EffectivenessTools.js +3 -3
  121. package/dist/protocols/mcp/tools/EffectivenessTools.js.map +1 -1
  122. package/dist/protocols/mcp/tools/GitTools.d.ts +9 -369
  123. package/dist/protocols/mcp/tools/GitTools.d.ts.map +1 -1
  124. package/dist/protocols/mcp/tools/GitTools.js +222 -517
  125. package/dist/protocols/mcp/tools/GitTools.js.map +1 -1
  126. package/dist/protocols/mcp/tools/InfrastructureTools.d.ts +56 -24
  127. package/dist/protocols/mcp/tools/InfrastructureTools.d.ts.map +1 -1
  128. package/dist/protocols/mcp/tools/InfrastructureTools.js +117 -26
  129. package/dist/protocols/mcp/tools/InfrastructureTools.js.map +1 -1
  130. package/dist/protocols/mcp/tools/MemorySearchTools.d.ts +0 -3
  131. package/dist/protocols/mcp/tools/MemorySearchTools.d.ts.map +1 -1
  132. package/dist/protocols/mcp/tools/MemorySearchTools.js +58 -1
  133. package/dist/protocols/mcp/tools/MemorySearchTools.js.map +1 -1
  134. package/dist/protocols/mcp/tools/OrparTools.d.ts +8 -0
  135. package/dist/protocols/mcp/tools/OrparTools.d.ts.map +1 -1
  136. package/dist/protocols/mcp/tools/OrparTools.js +66 -0
  137. package/dist/protocols/mcp/tools/OrparTools.js.map +1 -1
  138. package/dist/protocols/mcp/tools/SafetyTools.d.ts +7 -525
  139. package/dist/protocols/mcp/tools/SafetyTools.d.ts.map +1 -1
  140. package/dist/protocols/mcp/tools/SafetyTools.js +349 -514
  141. package/dist/protocols/mcp/tools/SafetyTools.js.map +1 -1
  142. package/dist/protocols/mcp/tools/TaskPlanningTools.d.ts.map +1 -1
  143. package/dist/protocols/mcp/tools/TaskPlanningTools.js +56 -26
  144. package/dist/protocols/mcp/tools/TaskPlanningTools.js.map +1 -1
  145. package/dist/protocols/mcp/tools/TestTools.d.ts +15 -376
  146. package/dist/protocols/mcp/tools/TestTools.d.ts.map +1 -1
  147. package/dist/protocols/mcp/tools/TestTools.js +241 -363
  148. package/dist/protocols/mcp/tools/TestTools.js.map +1 -1
  149. package/dist/protocols/mcp/tools/TypeScriptTools.d.ts +6 -276
  150. package/dist/protocols/mcp/tools/TypeScriptTools.d.ts.map +1 -1
  151. package/dist/protocols/mcp/tools/TypeScriptTools.js +223 -337
  152. package/dist/protocols/mcp/tools/TypeScriptTools.js.map +1 -1
  153. package/dist/protocols/mcp/tools/WebTools.d.ts +15 -728
  154. package/dist/protocols/mcp/tools/WebTools.d.ts.map +1 -1
  155. package/dist/protocols/mcp/tools/WebTools.js +151 -257
  156. package/dist/protocols/mcp/tools/WebTools.js.map +1 -1
  157. package/dist/protocols/mcp/tools/shell/ShellExecuteHandler.d.ts +5 -3
  158. package/dist/protocols/mcp/tools/shell/ShellExecuteHandler.d.ts.map +1 -1
  159. package/dist/protocols/mcp/tools/shell/ShellExecuteHandler.js +31 -6
  160. package/dist/protocols/mcp/tools/shell/ShellExecuteHandler.js.map +1 -1
  161. package/dist/schemas/EventPayloadSchema.d.ts +21 -0
  162. package/dist/schemas/EventPayloadSchema.d.ts.map +1 -1
  163. package/dist/schemas/EventPayloadSchema.js +14 -0
  164. package/dist/schemas/EventPayloadSchema.js.map +1 -1
  165. package/dist/services/BackgroundTaskManager.d.ts.map +1 -1
  166. package/dist/services/BackgroundTaskManager.js +12 -4
  167. package/dist/services/BackgroundTaskManager.js.map +1 -1
  168. package/dist/services/BrowserManager.d.ts +1 -1
  169. package/dist/services/BrowserManager.d.ts.map +1 -1
  170. package/dist/services/BrowserManager.js +26 -3
  171. package/dist/services/BrowserManager.js.map +1 -1
  172. package/dist/services/MemoryCompressor.d.ts +11 -2
  173. package/dist/services/MemoryCompressor.d.ts.map +1 -1
  174. package/dist/services/MemoryCompressor.js +8 -26
  175. package/dist/services/MemoryCompressor.js.map +1 -1
  176. package/dist/services/MemoryService.d.ts +16 -3
  177. package/dist/services/MemoryService.d.ts.map +1 -1
  178. package/dist/services/MemoryService.js +79 -55
  179. package/dist/services/MemoryService.js.map +1 -1
  180. package/dist/services/MxfMLService.d.ts +5 -0
  181. package/dist/services/MxfMLService.d.ts.map +1 -1
  182. package/dist/services/MxfMLService.js +32 -6
  183. package/dist/services/MxfMLService.js.map +1 -1
  184. package/dist/services/PredictiveAnalyticsService.d.ts +45 -9
  185. package/dist/services/PredictiveAnalyticsService.d.ts.map +1 -1
  186. package/dist/services/PredictiveAnalyticsService.js +82 -44
  187. package/dist/services/PredictiveAnalyticsService.js.map +1 -1
  188. package/dist/services/QValueManager.d.ts +7 -0
  189. package/dist/services/QValueManager.d.ts.map +1 -1
  190. package/dist/services/QValueManager.js +9 -0
  191. package/dist/services/QValueManager.js.map +1 -1
  192. package/dist/services/RewardSignalProcessor.d.ts +38 -1
  193. package/dist/services/RewardSignalProcessor.d.ts.map +1 -1
  194. package/dist/services/RewardSignalProcessor.js +109 -7
  195. package/dist/services/RewardSignalProcessor.js.map +1 -1
  196. package/dist/services/SharedStateService.d.ts +16 -2
  197. package/dist/services/SharedStateService.d.ts.map +1 -1
  198. package/dist/services/SharedStateService.js +44 -3
  199. package/dist/services/SharedStateService.js.map +1 -1
  200. package/dist/services/TaskEffectivenessService.d.ts +34 -18
  201. package/dist/services/TaskEffectivenessService.d.ts.map +1 -1
  202. package/dist/services/TaskEffectivenessService.js +91 -155
  203. package/dist/services/TaskEffectivenessService.js.map +1 -1
  204. package/dist/services/WebSearchService.d.ts.map +1 -1
  205. package/dist/services/WebSearchService.js.map +1 -1
  206. package/dist/services/dag/TaskDagService.d.ts.map +1 -1
  207. package/dist/services/dag/TaskDagService.js +2 -0
  208. package/dist/services/dag/TaskDagService.js.map +1 -1
  209. package/dist/services/orpar-memory/OrparMemoryCoordinator.d.ts +7 -0
  210. package/dist/services/orpar-memory/OrparMemoryCoordinator.d.ts.map +1 -1
  211. package/dist/services/orpar-memory/OrparMemoryCoordinator.js +71 -10
  212. package/dist/services/orpar-memory/OrparMemoryCoordinator.js.map +1 -1
  213. package/dist/types/MemoryUtilityTypes.d.ts +2 -0
  214. package/dist/types/MemoryUtilityTypes.d.ts.map +1 -1
  215. package/dist/types/MemoryUtilityTypes.js.map +1 -1
  216. package/dist/utils/Logger.d.ts +43 -3
  217. package/dist/utils/Logger.d.ts.map +1 -1
  218. package/dist/utils/Logger.js +78 -36
  219. package/dist/utils/Logger.js.map +1 -1
  220. package/dist/utils/MxpEncryption.d.ts +19 -6
  221. package/dist/utils/MxpEncryption.d.ts.map +1 -1
  222. package/dist/utils/MxpEncryption.js +48 -16
  223. package/dist/utils/MxpEncryption.js.map +1 -1
  224. package/package.json +19 -4
  225. package/src/adapters/FrameworkAdapter.ts +1 -1
  226. package/src/config/ConfigManager.ts +52 -19
  227. package/src/config/memory-strata.config.ts +136 -0
  228. package/src/constants/ToolNames.ts +4 -18
  229. package/src/database/DatabaseAdapterFactory.ts +23 -6
  230. package/src/database/adapters/mongodb/MongoBaseRepository.ts +16 -2
  231. package/src/events/ClientEventBus.ts +68 -403
  232. package/src/events/EventBus.ts +7 -8
  233. package/src/events/EventBusBase.ts +479 -116
  234. package/src/events/EventNames.ts +6 -0
  235. package/src/events/PublicEvents.ts +7 -4
  236. package/src/events/ServerEventBus.ts +78 -473
  237. package/src/events/event-definitions/LlmBudgetEvents.ts +74 -0
  238. package/src/interfaces/IMemoryPersistence.ts +51 -0
  239. package/src/middleware/MxpMiddleware.ts +51 -24
  240. package/src/models/channelKey.ts +18 -4
  241. package/src/models/memoryUtility.ts +64 -0
  242. package/src/models/user.ts +36 -4
  243. package/src/mxp/BinaryProtocolLayer.ts +108 -124
  244. package/src/mxp/ContextCompressionEngine.ts +219 -161
  245. package/src/mxp/MxpTokenOptimizer.ts +10 -10
  246. package/src/prompts/MxfAgentSystemPrompt.ts +45 -55
  247. package/src/protocols/mcp/ToolError.ts +201 -0
  248. package/src/protocols/mcp/defineTool.ts +379 -0
  249. package/src/protocols/mcp/security/HttpTargetGuard.ts +267 -0
  250. package/src/protocols/mcp/security/McpSecurityGuard.ts +10 -3
  251. package/src/protocols/mcp/security/McpToolPolicy.ts +221 -0
  252. package/src/protocols/mcp/services/ExternalMcpServerManager.ts +340 -322
  253. package/src/protocols/mcp/services/ExternalServerConfigs.ts +82 -19
  254. package/src/protocols/mcp/tools/AnalyticsTools.ts +1 -1
  255. package/src/protocols/mcp/tools/CodeAnalysisTools.ts +15 -0
  256. package/src/protocols/mcp/tools/EffectivenessTools.ts +3 -3
  257. package/src/protocols/mcp/tools/GitTools.ts +290 -569
  258. package/src/protocols/mcp/tools/InfrastructureTools.ts +189 -43
  259. package/src/protocols/mcp/tools/MemorySearchTools.ts +77 -1
  260. package/src/protocols/mcp/tools/OrparTools.ts +87 -0
  261. package/src/protocols/mcp/tools/SafetyTools.ts +501 -579
  262. package/src/protocols/mcp/tools/TaskPlanningTools.ts +81 -39
  263. package/src/protocols/mcp/tools/TestTools.ts +339 -419
  264. package/src/protocols/mcp/tools/TypeScriptTools.ts +316 -376
  265. package/src/protocols/mcp/tools/WebTools.ts +209 -307
  266. package/src/protocols/mcp/tools/shell/ShellExecuteHandler.ts +53 -14
  267. package/src/schemas/EventPayloadSchema.ts +37 -0
  268. package/src/services/BackgroundTaskManager.ts +12 -4
  269. package/src/services/BrowserManager.ts +41 -1
  270. package/src/services/MemoryCompressor.ts +20 -32
  271. package/src/services/MemoryService.ts +96 -58
  272. package/src/services/MxfMLService.ts +41 -7
  273. package/src/services/PredictiveAnalyticsService.ts +101 -51
  274. package/src/services/QValueManager.ts +10 -0
  275. package/src/services/RewardSignalProcessor.ts +138 -9
  276. package/src/services/SharedStateService.ts +56 -8
  277. package/src/services/TaskEffectivenessService.ts +114 -187
  278. package/src/services/WebSearchService.ts +2 -1
  279. package/src/services/dag/TaskDagService.ts +2 -0
  280. package/src/services/orpar-memory/OrparMemoryCoordinator.ts +86 -10
  281. package/src/types/MemoryUtilityTypes.ts +2 -0
  282. package/src/utils/Logger.ts +116 -40
  283. package/src/utils/MxpEncryption.ts +69 -34
  284. package/dist/mxp/MxpEventForwardingEnhancer.d.ts +0 -134
  285. package/dist/mxp/MxpEventForwardingEnhancer.d.ts.map +0 -1
  286. package/dist/mxp/MxpEventForwardingEnhancer.js +0 -361
  287. package/dist/mxp/MxpEventForwardingEnhancer.js.map +0 -1
  288. package/dist/protocols/mcp/services/ToolSchemaEnhancements.d.ts +0 -56
  289. package/dist/protocols/mcp/services/ToolSchemaEnhancements.d.ts.map +0 -1
  290. package/dist/protocols/mcp/services/ToolSchemaEnhancements.js +0 -57
  291. package/dist/protocols/mcp/services/ToolSchemaEnhancements.js.map +0 -1
  292. package/dist/protocols/mcp/tools/ControlLoopLifecycle.d.ts +0 -270
  293. package/dist/protocols/mcp/tools/ControlLoopLifecycle.d.ts.map +0 -1
  294. package/dist/protocols/mcp/tools/ControlLoopLifecycle.js +0 -256
  295. package/dist/protocols/mcp/tools/ControlLoopLifecycle.js.map +0 -1
  296. package/dist/protocols/mcp/tools/LspTools.d.ts +0 -495
  297. package/dist/protocols/mcp/tools/LspTools.d.ts.map +0 -1
  298. package/dist/protocols/mcp/tools/LspTools.js +0 -469
  299. package/dist/protocols/mcp/tools/LspTools.js.map +0 -1
  300. package/src/mxp/MxpEventForwardingEnhancer.ts +0 -471
  301. package/src/protocols/mcp/services/ToolSchemaEnhancements.ts +0 -92
  302. package/src/protocols/mcp/tools/ControlLoopLifecycle.ts +0 -302
  303. package/src/protocols/mcp/tools/LspTools.ts +0 -476
@@ -67,7 +67,13 @@ export enum ModelType {
67
67
  GRADIENT_BOOSTING = 'gradient_boosting',
68
68
  NEURAL_NETWORK = 'neural_network',
69
69
  ISOLATION_FOREST = 'isolation_forest',
70
- LSTM = 'lstm'
70
+ LSTM = 'lstm',
71
+ /**
72
+ * A rule-based heuristic, not a trained model. Exists so a heuristic result can say
73
+ * what it is: without it, heuristic predictions were reported to callers as
74
+ * GRADIENT_BOOSTING, alongside a hardcoded accuracy figure.
75
+ */
76
+ HEURISTIC = 'heuristic'
71
77
  }
72
78
 
73
79
  /**
@@ -188,14 +194,24 @@ export interface RiskScore {
188
194
  */
189
195
  export interface ModelMetadata {
190
196
  modelId: string;
197
+ /**
198
+ * Which predictor this describes ('error_prediction', 'anomaly_detection',
199
+ * 'pattern_forecast'). Identity has to be separate from `type`, because `type` now
200
+ * changes with training state — a predictor is a HEURISTIC until it is actually
201
+ * trained — so callers can no longer identify a predictor by its algorithm label.
202
+ */
203
+ name: string;
191
204
  type: ModelType;
192
205
  version: string;
193
- trainedAt: number;
206
+ /** Null until the predictor has actually been trained. */
207
+ trainedAt: number | null;
208
+ /** Null for heuristics — they have no measured validation accuracy. */
194
209
  accuracy: number | null;
195
210
  features: string[];
196
211
  hyperparameters: Record<string, any>;
197
212
  trainingDataSize: number;
198
- validationMetrics: Record<string, number>;
213
+ /** Null until a real validation run has produced metrics. */
214
+ validationMetrics: Record<string, number> | null;
199
215
  }
200
216
 
201
217
  /** TF.js model ID for the error prediction Dense classifier */
@@ -234,8 +250,6 @@ export class PredictiveAnalyticsService extends EventEmitter {
234
250
  // Otherwise falls back to the heuristic distance-based isolation score.
235
251
  private tfAnomalyAutoencoderReady: boolean = false;
236
252
 
237
- // Models (simplified in-memory implementation)
238
- private readonly models = new Map<string, any>();
239
253
  private readonly modelMetadata = new Map<string, ModelMetadata>();
240
254
 
241
255
  // Training data
@@ -446,13 +460,10 @@ export class PredictiveAnalyticsService extends EventEmitter {
446
460
  );
447
461
  }
448
462
 
449
- // Schedule automatic retraining with MxfMLService
450
- const config = getTensorFlowConfig();
451
- if (config.autoTrainEnabled) {
452
- mlService.scheduleRetrain(TF_ERROR_PREDICTION_MODEL_ID, async () => {
453
- await this.trainErrorPredictionModel();
454
- });
455
- }
463
+ // Retraining is owned by this service's own interval (startRetrainSchedule),
464
+ // which trains both models sequentially. Do NOT also register an
465
+ // MxfMLService.scheduleRetrain() timer — a second schedule for the same
466
+ // model trains twice per interval and collides on concurrent fit() calls.
456
467
  }
457
468
 
458
469
  // =============================================================================
@@ -549,13 +560,8 @@ export class PredictiveAnalyticsService extends EventEmitter {
549
560
  );
550
561
  }
551
562
 
552
- // Schedule automatic retraining with MxfMLService
553
- const config = getTensorFlowConfig();
554
- if (config.autoTrainEnabled) {
555
- mlService.scheduleRetrain(TF_ANOMALY_AUTOENCODER_MODEL_ID, async () => {
556
- await this.trainAnomalyDetectionModel();
557
- });
558
- }
563
+ // Retraining is owned by this service's own interval (startRetrainSchedule) —
564
+ // see the note in initializeTfErrorPrediction().
559
565
  }
560
566
 
561
567
  // =============================================================================
@@ -601,7 +607,9 @@ export class PredictiveAnalyticsService extends EventEmitter {
601
607
  },
602
608
  features,
603
609
  model: {
604
- type: usedTfModel ? ModelType.NEURAL_NETWORK : ModelType.GRADIENT_BOOSTING,
610
+ // Say which it actually was. A heuristic reported as GRADIENT_BOOSTING is
611
+ // how an unvalidated rule of thumb ends up being trusted as a trained model.
612
+ type: usedTfModel ? ModelType.NEURAL_NETWORK : ModelType.HEURISTIC,
605
613
  version: modelMeta?.version || '1.0',
606
614
  accuracy: modelMeta?.accuracy ?? null
607
615
  }
@@ -1662,37 +1670,36 @@ export class PredictiveAnalyticsService extends EventEmitter {
1662
1670
  /**
1663
1671
  * Initialize models
1664
1672
  */
1673
+ /**
1674
+ * Register metadata for the predictors this service exposes.
1675
+ *
1676
+ * Until a model is actually trained (see trainErrorPredictionModel and friends), every
1677
+ * predictor here is a rule-based heuristic and is described as one. Nothing is
1678
+ * reported as trained, and no accuracy is claimed.
1679
+ *
1680
+ * This previously registered three constant functions — `predict: () => ({score: 0.5,
1681
+ * confidence: 0.7})` labelled GRADIENT_BOOSTING, and `sequence[last] * 1.1` labelled
1682
+ * LSTM — together with a hardcoded accuracy of 0.75, a training-set size of 1000, and
1683
+ * invented precision/recall figures, all of which were served to callers through the
1684
+ * predictive_* tools and the REST layer. The predict functions were dead (nothing ever
1685
+ * read this.models), but the fabricated metadata was not: it was read on every
1686
+ * prediction. Both are gone.
1687
+ */
1665
1688
  private initializeModels(): void {
1666
- // Initialize simplified models
1667
- // In production, would load actual trained models
1668
-
1669
- this.models.set('error_prediction', {
1670
- type: ModelType.GRADIENT_BOOSTING,
1671
- predict: (features: number[]) => ({ score: 0.5, confidence: 0.7 })
1672
- });
1673
-
1674
- this.models.set('anomaly_detection', {
1675
- type: ModelType.ISOLATION_FOREST,
1676
- predict: (features: number[]) => ({ score: 0.5 })
1677
- });
1678
-
1679
- this.models.set('pattern_forecast', {
1680
- type: ModelType.LSTM,
1681
- predict: (sequence: number[]) => sequence[sequence.length - 1] * 1.1
1682
- });
1683
-
1684
- // Set metadata
1685
- for (const [name, model] of this.models) {
1689
+ const heuristicPredictors = ['error_prediction', 'anomaly_detection', 'pattern_forecast'];
1690
+
1691
+ for (const name of heuristicPredictors) {
1686
1692
  this.modelMetadata.set(name, {
1687
1693
  modelId: uuidv4(),
1688
- type: model.type,
1694
+ name,
1695
+ type: ModelType.HEURISTIC,
1689
1696
  version: '1.0.0',
1690
- trainedAt: Date.now(),
1691
- accuracy: 0.75,
1697
+ trainedAt: null,
1698
+ accuracy: null, // heuristics have no measured validation accuracy
1692
1699
  features: [],
1693
1700
  hyperparameters: {},
1694
- trainingDataSize: 1000,
1695
- validationMetrics: { accuracy: 0.75, precision: 0.8, recall: 0.7 }
1701
+ trainingDataSize: 0,
1702
+ validationMetrics: null
1696
1703
  });
1697
1704
  }
1698
1705
  }
@@ -1724,6 +1731,8 @@ export class PredictiveAnalyticsService extends EventEmitter {
1724
1731
  *
1725
1732
  * When TF.js is enabled and sufficient training samples exist:
1726
1733
  * - Vectorizes features and binary labels from collected training data
1734
+ * - Requires both label classes to be present (see the class-balance guard
1735
+ * below) — one-class data cannot train a discriminator
1727
1736
  * - Calls MxfMLService.train() to run supervised training on the Dense classifier
1728
1737
  * - Updates internal model metadata with real training metrics
1729
1738
  * - Saves the trained model to persistent storage (GridFS)
@@ -1779,6 +1788,28 @@ export class PredictiveAnalyticsService extends EventEmitter {
1779
1788
  return;
1780
1789
  }
1781
1790
 
1791
+ // One-class data cannot train a discriminator: binary crossentropy
1792
+ // drives the sigmoid to a constant, reporting perfect accuracy while
1793
+ // learning nothing. Require both classes, each meeting a minority
1794
+ // floor that scales with the sample count.
1795
+ const positives = labels.reduce((sum, label) => sum + label, 0);
1796
+ const negatives = labels.length - positives;
1797
+ const minorityFloor = Math.max(3, Math.ceil(labels.length * 0.02));
1798
+ if (positives < minorityFloor || negatives < minorityFloor) {
1799
+ this.logger.info(
1800
+ `[PredictiveAnalyticsService] Skipping TF.js error prediction training — ` +
1801
+ `class balance too skewed (positives=${positives}, negatives=${negatives}, ` +
1802
+ `minority floor=${minorityFloor}). Heuristic prediction remains active.`
1803
+ );
1804
+ if (!this.tfErrorPredictionReady) {
1805
+ // No usable TF model — keep heuristic bookkeeping current
1806
+ this.updateHeuristicMetadata(features.length);
1807
+ }
1808
+ // A previously trained model (this session or loaded from storage)
1809
+ // keeps serving; skipping retraining must not clobber its metadata.
1810
+ return;
1811
+ }
1812
+
1782
1813
  // Format labels as 2D array for MxfMLService.train()
1783
1814
  const yData = labels.map(l => [l]);
1784
1815
 
@@ -1792,9 +1823,15 @@ export class PredictiveAnalyticsService extends EventEmitter {
1792
1823
  // Update internal metadata with real training metrics
1793
1824
  const modelMeta = this.modelMetadata.get('error_prediction');
1794
1825
  if (modelMeta) {
1826
+ // Backed by a trained classifier now, not the heuristic.
1827
+ modelMeta.type = ModelType.NEURAL_NETWORK;
1795
1828
  modelMeta.trainedAt = Date.now();
1796
1829
  modelMeta.trainingDataSize = features.length;
1797
- modelMeta.accuracy = metrics.accuracy ?? metrics.valAccuracy ?? 0.75;
1830
+ // Prefer validation accuracy training accuracy overstates fit.
1831
+ // If training reported no accuracy, we do not know the accuracy. Say so.
1832
+ // This previously fell back to 0.75 — an invented figure indistinguishable
1833
+ // from a measured one.
1834
+ modelMeta.accuracy = metrics.valAccuracy ?? metrics.accuracy ?? null;
1798
1835
  modelMeta.validationMetrics = {
1799
1836
  accuracy: metrics.accuracy ?? 0,
1800
1837
  val_accuracy: metrics.valAccuracy ?? 0,
@@ -1916,6 +1953,9 @@ export class PredictiveAnalyticsService extends EventEmitter {
1916
1953
  // Update internal metadata with real training metrics
1917
1954
  const modelMeta = this.modelMetadata.get('anomaly_detection');
1918
1955
  if (modelMeta) {
1956
+ // A real autoencoder is now backing this predictor, so it stops being a
1957
+ // heuristic and says so.
1958
+ modelMeta.type = ModelType.NEURAL_NETWORK;
1919
1959
  modelMeta.trainedAt = Date.now();
1920
1960
  modelMeta.trainingDataSize = features.length;
1921
1961
  // For autoencoders, accuracy is not directly applicable —
@@ -2231,13 +2271,23 @@ export class PredictiveAnalyticsService extends EventEmitter {
2231
2271
  /**
2232
2272
  * Start retrain schedule.
2233
2273
  *
2234
- * When TF.js is enabled, both error prediction and anomaly detection
2235
- * retraining are managed by MxfMLService.scheduleRetrain() (configured
2236
- * in initializeTfErrorPrediction() and initializeTfAnomalyAutoencoder()).
2237
- * This interval serves as the retraining path when TF.js is disabled
2238
- * and as a fallback when MxfMLService scheduling is not active.
2274
+ * This interval is the single owner of scheduled retraining. trainModels()
2275
+ * trains the error predictor and the anomaly autoencoder sequentially —
2276
+ * TF.js rejects overlapping fit() calls on one model, and a single timer
2277
+ * avoids overlapping training cycles entirely. Registering per-model
2278
+ * MxfMLService.scheduleRetrain() timers alongside this interval caused
2279
+ * duplicate hourly training and fit() collisions.
2280
+ *
2281
+ * When TF.js is enabled, autoTrainEnabled gates scheduled retraining.
2282
+ * When TF.js is disabled, the interval refreshes heuristic metadata.
2239
2283
  */
2240
2284
  private startRetrainSchedule(): void {
2285
+ if (isTensorFlowEnabled() && !getTensorFlowConfig().autoTrainEnabled) {
2286
+ this.logger.info(
2287
+ '[PredictiveAnalyticsService] TF.js auto-training disabled, not scheduling model retraining'
2288
+ );
2289
+ return;
2290
+ }
2241
2291
  this.retrainInterval = setInterval(() => {
2242
2292
  this.trainModels();
2243
2293
  }, this.config.retrainInterval);
@@ -169,6 +169,16 @@ export class QValueManager {
169
169
  return result;
170
170
  }
171
171
 
172
+ /**
173
+ * Whether a Q-value for this memory is already in the cache.
174
+ *
175
+ * Used by MemoryService.hydrateQValues so a retrieval only reads persistence for
176
+ * memories this process has not scored yet.
177
+ */
178
+ public isCached(memoryId: string): boolean {
179
+ return this.qValueCache.has(memoryId);
180
+ }
181
+
172
182
  /**
173
183
  * Set Q-value in cache (typically called when loading from persistence)
174
184
  */
@@ -91,6 +91,15 @@ export class RewardSignalProcessor {
91
91
  // Memory usage tracking (task ID -> memories used)
92
92
  private taskMemoryUsage: Map<string, MemoryUsageRecord[]> = new Map();
93
93
 
94
+ /**
95
+ * Memories retrieved by an agent that are not yet attributed to a completed task,
96
+ * keyed by `${agentId}:${channelId}`. Drained on task completion.
97
+ */
98
+ private agentMemoryUsage: Map<string, MemoryUsageRecord[]> = new Map();
99
+
100
+ /** Cap on buffered memories per agent+channel, so the buffer cannot grow unbounded. */
101
+ private static readonly MAX_BUFFERED_MEMORIES_PER_AGENT = 200;
102
+
94
103
  // Event listener cleanup functions
95
104
  private eventCleanupFns: (() => void)[] = [];
96
105
 
@@ -157,6 +166,15 @@ export class RewardSignalProcessor {
157
166
  EventBus.server.on(Events.Task.COMPLETED, taskCompletedHandler);
158
167
  this.eventCleanupFns.push(() => EventBus.server.off(Events.Task.COMPLETED, taskCompletedHandler));
159
168
 
169
+ // A failed task is the strongest signal MULS has that the memories it surfaced
170
+ // were unhelpful, and it was being thrown away: nothing listened for
171
+ // Events.Task.FAILED, so failure could never lower a Q-value.
172
+ const taskFailedHandler = (payload: any) => {
173
+ this.handleTaskCompleted(payload, 'failure');
174
+ };
175
+ EventBus.server.on(Events.Task.FAILED, taskFailedHandler);
176
+ this.eventCleanupFns.push(() => EventBus.server.off(Events.Task.FAILED, taskFailedHandler));
177
+
160
178
  // Listen for analytics task completed events (may have evaluation scores)
161
179
  const analyticsCompletedHandler = (payload: any) => {
162
180
  this.handleAnalyticsTaskCompleted(payload);
@@ -183,17 +201,48 @@ export class RewardSignalProcessor {
183
201
  /**
184
202
  * Handle task completion event
185
203
  */
186
- private async handleTaskCompleted(payload: any): Promise<void> {
204
+ private async handleTaskCompleted(payload: any, forcedStatus?: TaskOutcomeStatus): Promise<void> {
187
205
  if (!this.enabled) return;
188
206
 
189
207
  try {
190
208
  const taskId = payload.data?.taskId ?? payload.taskId;
191
- const status = this.mapStatusToOutcome(payload.data?.status ?? payload.status);
192
209
  const agentId = payload.agentId ?? payload.data?.agentId;
193
210
  const channelId = payload.channelId ?? payload.data?.channelId;
194
211
 
195
- // Get memories used during this task
196
- const memoriesUsed = this.taskMemoryUsage.get(taskId) ?? [];
212
+ // Task events carry the task object in `data.task` (see TaskEventData), so the
213
+ // real status lives at data.task.status. Reading only data.status meant the
214
+ // status was always undefined, which the mapper then turned into 'partial' —
215
+ // giving every task, successful or not, the same small positive reward.
216
+ const rawStatus =
217
+ payload.data?.task?.status ??
218
+ payload.data?.status ??
219
+ payload.status;
220
+
221
+ const status = forcedStatus ?? this.mapStatusToOutcome(rawStatus);
222
+
223
+ // Never invent an outcome. Attributing a reward from a status we could not read
224
+ // would teach the system from a number nobody measured.
225
+ if (!status) {
226
+ this.logger.warn(
227
+ `[RewardSignalProcessor] Task ${taskId} completed with an unrecognised status ` +
228
+ `(${String(rawStatus)}); skipping reward attribution rather than guessing one.`
229
+ );
230
+ return;
231
+ }
232
+
233
+ // Memories used during this task: anything explicitly tracked against the task
234
+ // id, plus anything the agent retrieved in this channel since its last
235
+ // completion (retrieval has no task id to attribute to — see
236
+ // trackAgentMemoriesUsage).
237
+ const explicitlyTracked = this.taskMemoryUsage.get(taskId) ?? [];
238
+ const bufferedForAgent = this.drainAgentMemories(agentId, channelId);
239
+
240
+ const memoriesUsed: MemoryUsageRecord[] = [...explicitlyTracked];
241
+ for (const record of bufferedForAgent) {
242
+ if (!memoriesUsed.some(m => m.memoryId === record.memoryId)) {
243
+ memoriesUsed.push(record);
244
+ }
245
+ }
197
246
 
198
247
  if (memoriesUsed.length === 0) {
199
248
  this.logger.debug(`[RewardSignalProcessor] No memories tracked for task ${taskId}`);
@@ -259,7 +308,14 @@ export class RewardSignalProcessor {
259
308
  /**
260
309
  * Map task status string to TaskOutcomeStatus
261
310
  */
262
- private mapStatusToOutcome(status: string): TaskOutcomeStatus {
311
+ /**
312
+ * Map a task status to a reward outcome, or null when the status is unrecognised.
313
+ *
314
+ * Returns null rather than defaulting to 'partial'. The old default meant an
315
+ * unreadable status still earned a positive reward, so memories were reinforced by
316
+ * tasks whose outcome was never actually known.
317
+ */
318
+ private mapStatusToOutcome(status: string): TaskOutcomeStatus | null {
263
319
  switch (status?.toLowerCase()) {
264
320
  case 'completed':
265
321
  case 'success':
@@ -276,7 +332,7 @@ export class RewardSignalProcessor {
276
332
  case 'timed_out':
277
333
  return 'timeout';
278
334
  default:
279
- return 'partial'; // Default to partial for unknown statuses
335
+ return null; // Unknown status the caller must skip attribution, not guess.
280
336
  }
281
337
  }
282
338
 
@@ -494,6 +550,66 @@ export class RewardSignalProcessor {
494
550
  }
495
551
  }
496
552
 
553
+ /**
554
+ * Track memories retrieved by an agent, without needing a task id.
555
+ *
556
+ * Retrieval happens inside MCP tool handlers, and the tool context carries only
557
+ * agentId/channelId — never the task the agent is working on. Rather than plumb a
558
+ * task id through the whole MCP execution path, retrieval records what it returned
559
+ * against the agent+channel, and handleTaskCompleted drains that buffer when the
560
+ * agent's task finishes. A task is executed by one agent in one channel, so the
561
+ * memories buffered for that pair are exactly the ones the task used.
562
+ *
563
+ * This is the call that was missing: nothing in production ever recorded which
564
+ * memories were used, so every task completed with an empty usage list and no
565
+ * reward was ever attributed.
566
+ */
567
+ public trackAgentMemoriesUsage(
568
+ agentId: string,
569
+ channelId: string,
570
+ memoryIds: string[],
571
+ phase: OrparPhase,
572
+ usageType?: MemoryUsageRecord['usageType']
573
+ ): void {
574
+ if (!this.enabled || !this.config.trackMemoryUsage) return;
575
+ if (!agentId || !channelId || memoryIds.length === 0) return;
576
+
577
+ const key = `${agentId}:${channelId}`;
578
+ let records = this.agentMemoryUsage.get(key);
579
+ if (!records) {
580
+ records = [];
581
+ this.agentMemoryUsage.set(key, records);
582
+ }
583
+
584
+ for (const memoryId of memoryIds) {
585
+ if (!records.some(r => r.memoryId === memoryId)) {
586
+ records.push({ memoryId, phase, retrievedAt: new Date(), usageType });
587
+ }
588
+ }
589
+
590
+ // Bound the buffer. An agent that retrieves memories without ever completing a
591
+ // task would otherwise grow this map without limit.
592
+ if (records.length > RewardSignalProcessor.MAX_BUFFERED_MEMORIES_PER_AGENT) {
593
+ records.splice(0, records.length - RewardSignalProcessor.MAX_BUFFERED_MEMORIES_PER_AGENT);
594
+ }
595
+
596
+ this.logger.debug(
597
+ `[RewardSignalProcessor] Buffered ${memoryIds.length} memories for ${key} (phase=${phase})`
598
+ );
599
+ }
600
+
601
+ /**
602
+ * Take and clear the memories buffered for an agent in a channel.
603
+ */
604
+ private drainAgentMemories(agentId?: string, channelId?: string): MemoryUsageRecord[] {
605
+ if (!agentId || !channelId) return [];
606
+
607
+ const key = `${agentId}:${channelId}`;
608
+ const records = this.agentMemoryUsage.get(key) ?? [];
609
+ this.agentMemoryUsage.delete(key);
610
+ return records;
611
+ }
612
+
497
613
  /**
498
614
  * Track multiple memories for a task
499
615
  */
@@ -605,16 +721,29 @@ export class RewardSignalProcessor {
605
721
  }
606
722
 
607
723
  /**
608
- * Get tracking statistics
724
+ * Get tracking statistics.
725
+ *
726
+ * `bufferedMemories` counts memories retrieved by agents that are not yet attributed
727
+ * to a completed task (see trackAgentMemoriesUsage).
609
728
  */
610
- public getTrackingStats(): { activeTasks: number; totalMemoriesTracked: number } {
729
+ public getTrackingStats(): {
730
+ activeTasks: number;
731
+ totalMemoriesTracked: number;
732
+ bufferedMemories: number;
733
+ } {
611
734
  let totalMemories = 0;
612
735
  for (const [, records] of this.taskMemoryUsage) {
613
736
  totalMemories += records.length;
614
737
  }
738
+
739
+ let bufferedMemories = 0;
740
+ for (const [, records] of this.agentMemoryUsage) {
741
+ bufferedMemories += records.length;
742
+ }
615
743
  return {
616
744
  activeTasks: this.taskMemoryUsage.size,
617
- totalMemoriesTracked: totalMemories
745
+ totalMemoriesTracked: totalMemories,
746
+ bufferedMemories
618
747
  };
619
748
  }
620
749
 
@@ -51,16 +51,45 @@ export class SharedStateService {
51
51
  private static instance: SharedStateService;
52
52
  private stateStore = new Map<string, SharedStateEntry>();
53
53
  private channelStates = new Map<string, Set<string>>(); // channelId -> Set of stateIds
54
-
54
+
55
+ /** How long an untouched state entry is kept before cleanup (24 hours). */
56
+ private static readonly STATE_TTL_MS = 24 * 60 * 60 * 1000;
57
+
58
+ /** How often the cleanup sweep runs (1 hour). */
59
+ private static readonly CLEANUP_INTERVAL_MS = 60 * 60 * 1000;
60
+
61
+ private cleanupInterval: NodeJS.Timeout | null = null;
62
+
55
63
  private constructor() {
64
+ // This is a process-lifetime singleton holding an unbounded Map, so the
65
+ // sweep has to be driven by something. cleanupOldStates() existed but had
66
+ // no callers anywhere, so stateStore/channelStates simply grew forever.
67
+ this.cleanupInterval = setInterval(() => {
68
+ this.cleanupOldStates();
69
+ }, SharedStateService.CLEANUP_INTERVAL_MS);
70
+ // Background sweep — must not keep the event loop alive on its own.
71
+ this.cleanupInterval.unref();
72
+
73
+ // Do not hold the event loop open on account of a garbage-collection timer.
74
+ this.cleanupInterval.unref?.();
56
75
  }
57
-
76
+
58
77
  public static getInstance(): SharedStateService {
59
78
  if (!SharedStateService.instance) {
60
79
  SharedStateService.instance = new SharedStateService();
61
80
  }
62
81
  return SharedStateService.instance;
63
82
  }
83
+
84
+ /**
85
+ * Stop the cleanup timer. For tests and shutdown.
86
+ */
87
+ public stopCleanup(): void {
88
+ if (this.cleanupInterval) {
89
+ clearInterval(this.cleanupInterval);
90
+ this.cleanupInterval = null;
91
+ }
92
+ }
64
93
 
65
94
  /**
66
95
  * Store collaborative state that can be referenced by ID
@@ -178,27 +207,46 @@ export class SharedStateService {
178
207
  }
179
208
 
180
209
  /**
181
- * Clean up old states to prevent memory leaks
210
+ * Drop states that have not been touched within the TTL.
211
+ *
212
+ * Runs on a timer from the constructor; also callable directly.
213
+ *
214
+ * @param maxAgeMs Age past which a state is dropped (default: 24 hours)
215
+ * @returns The number of states removed
182
216
  */
183
- public cleanupOldStates(maxAgeMs: number = 24 * 60 * 60 * 1000): void { // Default 24 hours
217
+ public cleanupOldStates(maxAgeMs: number = SharedStateService.STATE_TTL_MS): number {
184
218
  const now = Date.now();
185
219
  const toDelete: string[] = [];
186
-
220
+
187
221
  for (const [stateId, state] of this.stateStore.entries()) {
188
222
  const age = now - (state.lastUpdatedAt || state.createdAt);
189
223
  if (age > maxAgeMs) {
190
224
  toDelete.push(stateId);
191
225
  }
192
226
  }
193
-
227
+
194
228
  for (const stateId of toDelete) {
195
229
  const state = this.stateStore.get(stateId);
196
230
  if (state) {
197
231
  this.stateStore.delete(stateId);
198
- this.channelStates.get(state.channelId)?.delete(stateId);
232
+
233
+ const channelStateIds = this.channelStates.get(state.channelId);
234
+ if (channelStateIds) {
235
+ channelStateIds.delete(stateId);
236
+ // Drop the channel entry too, or channelStates grows forever
237
+ // with empty Sets for channels that are long gone.
238
+ if (channelStateIds.size === 0) {
239
+ this.channelStates.delete(state.channelId);
240
+ }
241
+ }
199
242
  }
200
243
  }
201
-
244
+
245
+ if (toDelete.length > 0) {
246
+ logger.debug(`Removed ${toDelete.length} shared state entries older than ${maxAgeMs}ms`);
247
+ }
248
+
249
+ return toDelete.length;
202
250
  }
203
251
 
204
252
  /**