claude-mpm 3.9.11__py3-none-any.whl → 4.0.3__py3-none-any.whl

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 (419) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +2 -2
  3. claude_mpm/__main__.py +3 -2
  4. claude_mpm/agents/__init__.py +85 -79
  5. claude_mpm/agents/agent_loader.py +464 -1003
  6. claude_mpm/agents/agent_loader_integration.py +45 -45
  7. claude_mpm/agents/agents_metadata.py +29 -30
  8. claude_mpm/agents/async_agent_loader.py +156 -138
  9. claude_mpm/agents/base_agent.json +1 -1
  10. claude_mpm/agents/base_agent_loader.py +179 -151
  11. claude_mpm/agents/frontmatter_validator.py +229 -130
  12. claude_mpm/agents/schema/agent_schema.json +1 -1
  13. claude_mpm/agents/system_agent_config.py +213 -147
  14. claude_mpm/agents/templates/__init__.py +13 -13
  15. claude_mpm/agents/templates/code_analyzer.json +2 -2
  16. claude_mpm/agents/templates/data_engineer.json +1 -1
  17. claude_mpm/agents/templates/documentation.json +23 -11
  18. claude_mpm/agents/templates/engineer.json +22 -6
  19. claude_mpm/agents/templates/memory_manager.json +1 -1
  20. claude_mpm/agents/templates/ops.json +2 -2
  21. claude_mpm/agents/templates/project_organizer.json +1 -1
  22. claude_mpm/agents/templates/qa.json +1 -1
  23. claude_mpm/agents/templates/refactoring_engineer.json +222 -0
  24. claude_mpm/agents/templates/research.json +20 -14
  25. claude_mpm/agents/templates/security.json +1 -1
  26. claude_mpm/agents/templates/ticketing.json +1 -1
  27. claude_mpm/agents/templates/version_control.json +1 -1
  28. claude_mpm/agents/templates/web_qa.json +3 -1
  29. claude_mpm/agents/templates/web_ui.json +2 -2
  30. claude_mpm/cli/__init__.py +79 -51
  31. claude_mpm/cli/__main__.py +3 -2
  32. claude_mpm/cli/commands/__init__.py +20 -20
  33. claude_mpm/cli/commands/agents.py +279 -247
  34. claude_mpm/cli/commands/aggregate.py +138 -157
  35. claude_mpm/cli/commands/cleanup.py +147 -147
  36. claude_mpm/cli/commands/config.py +93 -76
  37. claude_mpm/cli/commands/info.py +17 -16
  38. claude_mpm/cli/commands/mcp.py +140 -905
  39. claude_mpm/cli/commands/mcp_command_router.py +139 -0
  40. claude_mpm/cli/commands/mcp_config_commands.py +20 -0
  41. claude_mpm/cli/commands/mcp_install_commands.py +20 -0
  42. claude_mpm/cli/commands/mcp_server_commands.py +175 -0
  43. claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
  44. claude_mpm/cli/commands/memory.py +239 -203
  45. claude_mpm/cli/commands/monitor.py +203 -81
  46. claude_mpm/cli/commands/run.py +380 -429
  47. claude_mpm/cli/commands/run_config_checker.py +160 -0
  48. claude_mpm/cli/commands/socketio_monitor.py +235 -0
  49. claude_mpm/cli/commands/tickets.py +305 -197
  50. claude_mpm/cli/parser.py +24 -1156
  51. claude_mpm/cli/parsers/__init__.py +29 -0
  52. claude_mpm/cli/parsers/agents_parser.py +136 -0
  53. claude_mpm/cli/parsers/base_parser.py +331 -0
  54. claude_mpm/cli/parsers/config_parser.py +85 -0
  55. claude_mpm/cli/parsers/mcp_parser.py +152 -0
  56. claude_mpm/cli/parsers/memory_parser.py +138 -0
  57. claude_mpm/cli/parsers/monitor_parser.py +104 -0
  58. claude_mpm/cli/parsers/run_parser.py +147 -0
  59. claude_mpm/cli/parsers/tickets_parser.py +203 -0
  60. claude_mpm/cli/ticket_cli.py +7 -3
  61. claude_mpm/cli/utils.py +55 -37
  62. claude_mpm/cli_module/__init__.py +6 -6
  63. claude_mpm/cli_module/args.py +188 -140
  64. claude_mpm/cli_module/commands.py +79 -70
  65. claude_mpm/cli_module/migration_example.py +38 -60
  66. claude_mpm/config/__init__.py +32 -25
  67. claude_mpm/config/agent_config.py +151 -119
  68. claude_mpm/config/experimental_features.py +71 -73
  69. claude_mpm/config/paths.py +94 -208
  70. claude_mpm/config/socketio_config.py +84 -73
  71. claude_mpm/constants.py +35 -18
  72. claude_mpm/core/__init__.py +9 -6
  73. claude_mpm/core/agent_name_normalizer.py +68 -71
  74. claude_mpm/core/agent_registry.py +372 -521
  75. claude_mpm/core/agent_session_manager.py +74 -63
  76. claude_mpm/core/base_service.py +116 -87
  77. claude_mpm/core/cache.py +119 -153
  78. claude_mpm/core/claude_runner.py +425 -1120
  79. claude_mpm/core/config.py +263 -168
  80. claude_mpm/core/config_aliases.py +69 -61
  81. claude_mpm/core/config_constants.py +292 -0
  82. claude_mpm/core/constants.py +57 -99
  83. claude_mpm/core/container.py +211 -178
  84. claude_mpm/core/exceptions.py +233 -89
  85. claude_mpm/core/factories.py +92 -54
  86. claude_mpm/core/framework_loader.py +378 -220
  87. claude_mpm/core/hook_manager.py +198 -83
  88. claude_mpm/core/hook_performance_config.py +136 -0
  89. claude_mpm/core/injectable_service.py +61 -55
  90. claude_mpm/core/interactive_session.py +165 -155
  91. claude_mpm/core/interfaces.py +221 -195
  92. claude_mpm/core/lazy.py +96 -96
  93. claude_mpm/core/logger.py +133 -107
  94. claude_mpm/core/logging_config.py +185 -157
  95. claude_mpm/core/minimal_framework_loader.py +20 -15
  96. claude_mpm/core/mixins.py +30 -29
  97. claude_mpm/core/oneshot_session.py +215 -181
  98. claude_mpm/core/optimized_agent_loader.py +134 -138
  99. claude_mpm/core/optimized_startup.py +159 -157
  100. claude_mpm/core/pm_hook_interceptor.py +85 -72
  101. claude_mpm/core/service_registry.py +103 -101
  102. claude_mpm/core/session_manager.py +97 -87
  103. claude_mpm/core/socketio_pool.py +212 -158
  104. claude_mpm/core/tool_access_control.py +58 -51
  105. claude_mpm/core/types.py +46 -24
  106. claude_mpm/core/typing_utils.py +166 -82
  107. claude_mpm/core/unified_agent_registry.py +721 -0
  108. claude_mpm/core/unified_config.py +550 -0
  109. claude_mpm/core/unified_paths.py +549 -0
  110. claude_mpm/dashboard/index.html +1 -1
  111. claude_mpm/dashboard/open_dashboard.py +51 -17
  112. claude_mpm/dashboard/static/css/dashboard.css +27 -8
  113. claude_mpm/dashboard/static/dist/components/agent-inference.js +2 -0
  114. claude_mpm/dashboard/static/dist/components/event-processor.js +2 -0
  115. claude_mpm/dashboard/static/dist/components/event-viewer.js +2 -0
  116. claude_mpm/dashboard/static/dist/components/export-manager.js +2 -0
  117. claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +2 -0
  118. claude_mpm/dashboard/static/dist/components/hud-library-loader.js +2 -0
  119. claude_mpm/dashboard/static/dist/components/hud-manager.js +2 -0
  120. claude_mpm/dashboard/static/dist/components/hud-visualizer.js +2 -0
  121. claude_mpm/dashboard/static/dist/components/module-viewer.js +2 -0
  122. claude_mpm/dashboard/static/dist/components/session-manager.js +2 -0
  123. claude_mpm/dashboard/static/dist/components/socket-manager.js +2 -0
  124. claude_mpm/dashboard/static/dist/components/ui-state-manager.js +2 -0
  125. claude_mpm/dashboard/static/dist/components/working-directory.js +2 -0
  126. claude_mpm/dashboard/static/dist/dashboard.js +2 -0
  127. claude_mpm/dashboard/static/dist/socket-client.js +2 -0
  128. claude_mpm/dashboard/static/js/components/agent-inference.js +80 -76
  129. claude_mpm/dashboard/static/js/components/event-processor.js +71 -67
  130. claude_mpm/dashboard/static/js/components/event-viewer.js +74 -70
  131. claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
  132. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +106 -92
  133. claude_mpm/dashboard/static/js/components/hud-library-loader.js +11 -11
  134. claude_mpm/dashboard/static/js/components/hud-manager.js +73 -73
  135. claude_mpm/dashboard/static/js/components/hud-visualizer.js +163 -163
  136. claude_mpm/dashboard/static/js/components/module-viewer.js +305 -233
  137. claude_mpm/dashboard/static/js/components/session-manager.js +32 -29
  138. claude_mpm/dashboard/static/js/components/socket-manager.js +27 -20
  139. claude_mpm/dashboard/static/js/components/ui-state-manager.js +21 -18
  140. claude_mpm/dashboard/static/js/components/working-directory.js +74 -71
  141. claude_mpm/dashboard/static/js/dashboard.js +178 -453
  142. claude_mpm/dashboard/static/js/extension-error-handler.js +164 -0
  143. claude_mpm/dashboard/static/js/socket-client.js +120 -54
  144. claude_mpm/dashboard/templates/index.html +40 -50
  145. claude_mpm/experimental/cli_enhancements.py +60 -58
  146. claude_mpm/generators/__init__.py +1 -1
  147. claude_mpm/generators/agent_profile_generator.py +75 -65
  148. claude_mpm/hooks/__init__.py +1 -1
  149. claude_mpm/hooks/base_hook.py +33 -28
  150. claude_mpm/hooks/claude_hooks/__init__.py +1 -1
  151. claude_mpm/hooks/claude_hooks/connection_pool.py +120 -0
  152. claude_mpm/hooks/claude_hooks/event_handlers.py +743 -0
  153. claude_mpm/hooks/claude_hooks/hook_handler.py +415 -1331
  154. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +4 -4
  155. claude_mpm/hooks/claude_hooks/memory_integration.py +221 -0
  156. claude_mpm/hooks/claude_hooks/response_tracking.py +348 -0
  157. claude_mpm/hooks/claude_hooks/tool_analysis.py +230 -0
  158. claude_mpm/hooks/memory_integration_hook.py +140 -100
  159. claude_mpm/hooks/tool_call_interceptor.py +89 -76
  160. claude_mpm/hooks/validation_hooks.py +57 -49
  161. claude_mpm/init.py +145 -121
  162. claude_mpm/models/__init__.py +9 -9
  163. claude_mpm/models/agent_definition.py +33 -23
  164. claude_mpm/models/agent_session.py +228 -200
  165. claude_mpm/scripts/__init__.py +1 -1
  166. claude_mpm/scripts/socketio_daemon.py +192 -75
  167. claude_mpm/scripts/socketio_server_manager.py +328 -0
  168. claude_mpm/scripts/start_activity_logging.py +25 -22
  169. claude_mpm/services/__init__.py +68 -43
  170. claude_mpm/services/agent_capabilities_service.py +271 -0
  171. claude_mpm/services/agents/__init__.py +23 -32
  172. claude_mpm/services/agents/deployment/__init__.py +3 -3
  173. claude_mpm/services/agents/deployment/agent_config_provider.py +310 -0
  174. claude_mpm/services/agents/deployment/agent_configuration_manager.py +359 -0
  175. claude_mpm/services/agents/deployment/agent_definition_factory.py +84 -0
  176. claude_mpm/services/agents/deployment/agent_deployment.py +415 -2113
  177. claude_mpm/services/agents/deployment/agent_discovery_service.py +387 -0
  178. claude_mpm/services/agents/deployment/agent_environment_manager.py +293 -0
  179. claude_mpm/services/agents/deployment/agent_filesystem_manager.py +387 -0
  180. claude_mpm/services/agents/deployment/agent_format_converter.py +453 -0
  181. claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +161 -0
  182. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +345 -495
  183. claude_mpm/services/agents/deployment/agent_metrics_collector.py +279 -0
  184. claude_mpm/services/agents/deployment/agent_restore_handler.py +88 -0
  185. claude_mpm/services/agents/deployment/agent_template_builder.py +406 -0
  186. claude_mpm/services/agents/deployment/agent_validator.py +352 -0
  187. claude_mpm/services/agents/deployment/agent_version_manager.py +313 -0
  188. claude_mpm/services/agents/deployment/agent_versioning.py +6 -9
  189. claude_mpm/services/agents/deployment/agents_directory_resolver.py +79 -0
  190. claude_mpm/services/agents/deployment/async_agent_deployment.py +298 -234
  191. claude_mpm/services/agents/deployment/config/__init__.py +13 -0
  192. claude_mpm/services/agents/deployment/config/deployment_config.py +182 -0
  193. claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
  194. claude_mpm/services/agents/deployment/deployment_config_loader.py +54 -0
  195. claude_mpm/services/agents/deployment/deployment_type_detector.py +124 -0
  196. claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
  197. claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
  198. claude_mpm/services/agents/deployment/facade/deployment_executor.py +73 -0
  199. claude_mpm/services/agents/deployment/facade/deployment_facade.py +270 -0
  200. claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
  201. claude_mpm/services/agents/deployment/interface_adapter.py +227 -0
  202. claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
  203. claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
  204. claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
  205. claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
  206. claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +159 -0
  207. claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
  208. claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
  209. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +195 -0
  210. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +119 -0
  211. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +79 -0
  212. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +90 -0
  213. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +100 -0
  214. claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
  215. claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +98 -0
  216. claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
  217. claude_mpm/services/agents/deployment/processors/agent_processor.py +258 -0
  218. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +318 -0
  219. claude_mpm/services/agents/deployment/results/__init__.py +13 -0
  220. claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
  221. claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
  222. claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
  223. claude_mpm/services/agents/deployment/strategies/base_strategy.py +119 -0
  224. claude_mpm/services/agents/deployment/strategies/project_strategy.py +150 -0
  225. claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
  226. claude_mpm/services/agents/deployment/strategies/system_strategy.py +116 -0
  227. claude_mpm/services/agents/deployment/strategies/user_strategy.py +137 -0
  228. claude_mpm/services/agents/deployment/system_instructions_deployer.py +108 -0
  229. claude_mpm/services/agents/deployment/validation/__init__.py +19 -0
  230. claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
  231. claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
  232. claude_mpm/services/agents/deployment/validation/template_validator.py +299 -0
  233. claude_mpm/services/agents/deployment/validation/validation_result.py +226 -0
  234. claude_mpm/services/agents/loading/__init__.py +2 -2
  235. claude_mpm/services/agents/loading/agent_profile_loader.py +259 -229
  236. claude_mpm/services/agents/loading/base_agent_manager.py +90 -81
  237. claude_mpm/services/agents/loading/framework_agent_loader.py +154 -129
  238. claude_mpm/services/agents/management/__init__.py +2 -2
  239. claude_mpm/services/agents/management/agent_capabilities_generator.py +72 -58
  240. claude_mpm/services/agents/management/agent_management_service.py +209 -156
  241. claude_mpm/services/agents/memory/__init__.py +9 -6
  242. claude_mpm/services/agents/memory/agent_memory_manager.py +218 -1152
  243. claude_mpm/services/agents/memory/agent_persistence_service.py +20 -16
  244. claude_mpm/services/agents/memory/analyzer.py +430 -0
  245. claude_mpm/services/agents/memory/content_manager.py +376 -0
  246. claude_mpm/services/agents/memory/template_generator.py +468 -0
  247. claude_mpm/services/agents/registry/__init__.py +7 -10
  248. claude_mpm/services/agents/registry/deployed_agent_discovery.py +122 -97
  249. claude_mpm/services/agents/registry/modification_tracker.py +351 -285
  250. claude_mpm/services/async_session_logger.py +187 -153
  251. claude_mpm/services/claude_session_logger.py +87 -72
  252. claude_mpm/services/command_handler_service.py +217 -0
  253. claude_mpm/services/communication/__init__.py +3 -2
  254. claude_mpm/services/core/__init__.py +50 -97
  255. claude_mpm/services/core/base.py +60 -53
  256. claude_mpm/services/core/interfaces/__init__.py +188 -0
  257. claude_mpm/services/core/interfaces/agent.py +351 -0
  258. claude_mpm/services/core/interfaces/communication.py +343 -0
  259. claude_mpm/services/core/interfaces/infrastructure.py +413 -0
  260. claude_mpm/services/core/interfaces/service.py +434 -0
  261. claude_mpm/services/core/interfaces.py +19 -944
  262. claude_mpm/services/event_aggregator.py +208 -170
  263. claude_mpm/services/exceptions.py +387 -308
  264. claude_mpm/services/framework_claude_md_generator/__init__.py +75 -79
  265. claude_mpm/services/framework_claude_md_generator/content_assembler.py +69 -60
  266. claude_mpm/services/framework_claude_md_generator/content_validator.py +65 -61
  267. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +68 -49
  268. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +34 -34
  269. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +25 -22
  270. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +10 -10
  271. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +4 -3
  272. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
  273. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
  274. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
  275. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
  276. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +4 -3
  277. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
  278. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
  279. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +4 -3
  280. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +5 -4
  281. claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
  282. claude_mpm/services/framework_claude_md_generator/version_manager.py +30 -28
  283. claude_mpm/services/hook_service.py +106 -114
  284. claude_mpm/services/infrastructure/__init__.py +7 -5
  285. claude_mpm/services/infrastructure/context_preservation.py +233 -199
  286. claude_mpm/services/infrastructure/daemon_manager.py +279 -0
  287. claude_mpm/services/infrastructure/logging.py +83 -76
  288. claude_mpm/services/infrastructure/monitoring.py +547 -404
  289. claude_mpm/services/mcp_gateway/__init__.py +30 -13
  290. claude_mpm/services/mcp_gateway/config/__init__.py +2 -2
  291. claude_mpm/services/mcp_gateway/config/config_loader.py +61 -56
  292. claude_mpm/services/mcp_gateway/config/config_schema.py +50 -41
  293. claude_mpm/services/mcp_gateway/config/configuration.py +82 -75
  294. claude_mpm/services/mcp_gateway/core/__init__.py +13 -20
  295. claude_mpm/services/mcp_gateway/core/base.py +80 -67
  296. claude_mpm/services/mcp_gateway/core/exceptions.py +60 -46
  297. claude_mpm/services/mcp_gateway/core/interfaces.py +87 -84
  298. claude_mpm/services/mcp_gateway/main.py +287 -137
  299. claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
  300. claude_mpm/services/mcp_gateway/registry/service_registry.py +97 -94
  301. claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
  302. claude_mpm/services/mcp_gateway/server/__init__.py +2 -2
  303. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +105 -110
  304. claude_mpm/services/mcp_gateway/server/stdio_handler.py +105 -107
  305. claude_mpm/services/mcp_gateway/server/stdio_server.py +691 -0
  306. claude_mpm/services/mcp_gateway/tools/__init__.py +4 -2
  307. claude_mpm/services/mcp_gateway/tools/base_adapter.py +109 -119
  308. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +283 -215
  309. claude_mpm/services/mcp_gateway/tools/hello_world.py +122 -120
  310. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +652 -0
  311. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +606 -0
  312. claude_mpm/services/memory/__init__.py +2 -2
  313. claude_mpm/services/memory/builder.py +451 -362
  314. claude_mpm/services/memory/cache/__init__.py +2 -2
  315. claude_mpm/services/memory/cache/shared_prompt_cache.py +232 -194
  316. claude_mpm/services/memory/cache/simple_cache.py +107 -93
  317. claude_mpm/services/memory/indexed_memory.py +195 -193
  318. claude_mpm/services/memory/optimizer.py +267 -234
  319. claude_mpm/services/memory/router.py +571 -263
  320. claude_mpm/services/memory_hook_service.py +237 -0
  321. claude_mpm/services/port_manager.py +223 -0
  322. claude_mpm/services/project/__init__.py +3 -3
  323. claude_mpm/services/project/analyzer.py +451 -305
  324. claude_mpm/services/project/registry.py +262 -240
  325. claude_mpm/services/recovery_manager.py +287 -231
  326. claude_mpm/services/response_tracker.py +87 -67
  327. claude_mpm/services/runner_configuration_service.py +587 -0
  328. claude_mpm/services/session_management_service.py +304 -0
  329. claude_mpm/services/socketio/__init__.py +4 -4
  330. claude_mpm/services/socketio/client_proxy.py +174 -0
  331. claude_mpm/services/socketio/handlers/__init__.py +3 -3
  332. claude_mpm/services/socketio/handlers/base.py +44 -30
  333. claude_mpm/services/socketio/handlers/connection.py +145 -65
  334. claude_mpm/services/socketio/handlers/file.py +123 -108
  335. claude_mpm/services/socketio/handlers/git.py +607 -373
  336. claude_mpm/services/socketio/handlers/hook.py +170 -0
  337. claude_mpm/services/socketio/handlers/memory.py +4 -4
  338. claude_mpm/services/socketio/handlers/project.py +4 -4
  339. claude_mpm/services/socketio/handlers/registry.py +53 -38
  340. claude_mpm/services/socketio/server/__init__.py +18 -0
  341. claude_mpm/services/socketio/server/broadcaster.py +252 -0
  342. claude_mpm/services/socketio/server/core.py +399 -0
  343. claude_mpm/services/socketio/server/main.py +323 -0
  344. claude_mpm/services/socketio_client_manager.py +160 -133
  345. claude_mpm/services/socketio_server.py +36 -1885
  346. claude_mpm/services/subprocess_launcher_service.py +316 -0
  347. claude_mpm/services/system_instructions_service.py +258 -0
  348. claude_mpm/services/ticket_manager.py +19 -533
  349. claude_mpm/services/utility_service.py +285 -0
  350. claude_mpm/services/version_control/__init__.py +18 -21
  351. claude_mpm/services/version_control/branch_strategy.py +20 -10
  352. claude_mpm/services/version_control/conflict_resolution.py +37 -13
  353. claude_mpm/services/version_control/git_operations.py +52 -21
  354. claude_mpm/services/version_control/semantic_versioning.py +92 -53
  355. claude_mpm/services/version_control/version_parser.py +145 -125
  356. claude_mpm/services/version_service.py +270 -0
  357. claude_mpm/storage/__init__.py +2 -2
  358. claude_mpm/storage/state_storage.py +177 -181
  359. claude_mpm/ticket_wrapper.py +2 -2
  360. claude_mpm/utils/__init__.py +2 -2
  361. claude_mpm/utils/agent_dependency_loader.py +453 -243
  362. claude_mpm/utils/config_manager.py +157 -118
  363. claude_mpm/utils/console.py +1 -1
  364. claude_mpm/utils/dependency_cache.py +102 -107
  365. claude_mpm/utils/dependency_manager.py +52 -47
  366. claude_mpm/utils/dependency_strategies.py +131 -96
  367. claude_mpm/utils/environment_context.py +110 -102
  368. claude_mpm/utils/error_handler.py +75 -55
  369. claude_mpm/utils/file_utils.py +80 -67
  370. claude_mpm/utils/framework_detection.py +12 -11
  371. claude_mpm/utils/import_migration_example.py +12 -60
  372. claude_mpm/utils/imports.py +48 -45
  373. claude_mpm/utils/path_operations.py +100 -93
  374. claude_mpm/utils/robust_installer.py +172 -164
  375. claude_mpm/utils/session_logging.py +30 -23
  376. claude_mpm/utils/subprocess_utils.py +99 -61
  377. claude_mpm/validation/__init__.py +1 -1
  378. claude_mpm/validation/agent_validator.py +151 -111
  379. claude_mpm/validation/frontmatter_validator.py +92 -71
  380. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/METADATA +27 -1
  381. claude_mpm-4.0.3.dist-info/RECORD +402 -0
  382. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
  383. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/licenses/LICENSE +1 -1
  384. claude_mpm/cli/commands/run_guarded.py +0 -511
  385. claude_mpm/config/memory_guardian_config.py +0 -325
  386. claude_mpm/config/memory_guardian_yaml.py +0 -335
  387. claude_mpm/core/config_paths.py +0 -150
  388. claude_mpm/core/memory_aware_runner.py +0 -353
  389. claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
  390. claude_mpm/deployment_paths.py +0 -261
  391. claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
  392. claude_mpm/models/state_models.py +0 -433
  393. claude_mpm/services/agent/__init__.py +0 -24
  394. claude_mpm/services/agent/deployment.py +0 -2548
  395. claude_mpm/services/agent/management.py +0 -598
  396. claude_mpm/services/agent/registry.py +0 -813
  397. claude_mpm/services/agents/registry/agent_registry.py +0 -813
  398. claude_mpm/services/communication/socketio.py +0 -1935
  399. claude_mpm/services/communication/websocket.py +0 -479
  400. claude_mpm/services/framework_claude_md_generator.py +0 -624
  401. claude_mpm/services/health_monitor.py +0 -893
  402. claude_mpm/services/infrastructure/graceful_degradation.py +0 -616
  403. claude_mpm/services/infrastructure/health_monitor.py +0 -775
  404. claude_mpm/services/infrastructure/memory_dashboard.py +0 -479
  405. claude_mpm/services/infrastructure/memory_guardian.py +0 -944
  406. claude_mpm/services/infrastructure/restart_protection.py +0 -642
  407. claude_mpm/services/infrastructure/state_manager.py +0 -774
  408. claude_mpm/services/mcp_gateway/manager.py +0 -334
  409. claude_mpm/services/optimized_hook_service.py +0 -542
  410. claude_mpm/services/project_analyzer.py +0 -864
  411. claude_mpm/services/project_registry.py +0 -608
  412. claude_mpm/services/standalone_socketio_server.py +0 -1300
  413. claude_mpm/services/ticket_manager_di.py +0 -318
  414. claude_mpm/services/ticketing_service_original.py +0 -510
  415. claude_mpm/utils/paths.py +0 -395
  416. claude_mpm/utils/platform_memory.py +0 -524
  417. claude_mpm-3.9.11.dist-info/RECORD +0 -306
  418. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/WHEEL +0 -0
  419. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/top_level.txt +0 -0
@@ -12,19 +12,18 @@ WHY connection pooling:
12
12
  """
13
13
 
14
14
  import asyncio
15
- import json
16
15
  import os
17
- import sys
18
16
  import threading
19
17
  import time
20
- from collections import deque, defaultdict
18
+ from collections import defaultdict, deque
19
+ from dataclasses import dataclass, field
21
20
  from datetime import datetime, timedelta
22
21
  from enum import Enum
23
- from typing import Dict, Any, Optional, List, Deque
24
- from dataclasses import dataclass, field
22
+ from typing import Any, Deque, Dict, List, Optional
25
23
 
26
24
  try:
27
25
  import socketio
26
+
28
27
  SOCKETIO_AVAILABLE = True
29
28
  except ImportError:
30
29
  SOCKETIO_AVAILABLE = False
@@ -38,6 +37,7 @@ except ImportError:
38
37
  DEFAULT_DASHBOARD_PORT = 8765
39
38
  SOCKETIO_PORT_RANGE = (8080, 8099)
40
39
  DEFAULT_SOCKETIO_PORT = 8080
40
+
41
41
  socketio = None
42
42
 
43
43
  from ..core.logger import get_logger
@@ -45,14 +45,16 @@ from ..core.logger import get_logger
45
45
 
46
46
  class CircuitState(Enum):
47
47
  """Circuit breaker states."""
48
- CLOSED = "closed" # Normal operation
49
- OPEN = "open" # Failing, reject requests
48
+
49
+ CLOSED = "closed" # Normal operation
50
+ OPEN = "open" # Failing, reject requests
50
51
  HALF_OPEN = "half_open" # Testing if service recovered
51
52
 
52
53
 
53
54
  @dataclass
54
55
  class ConnectionStats:
55
56
  """Connection statistics for monitoring."""
57
+
56
58
  created_at: datetime = field(default_factory=datetime.now)
57
59
  last_used: datetime = field(default_factory=datetime.now)
58
60
  events_sent: int = 0
@@ -61,9 +63,10 @@ class ConnectionStats:
61
63
  is_connected: bool = False
62
64
 
63
65
 
64
- @dataclass
66
+ @dataclass
65
67
  class BatchEvent:
66
68
  """Event to be batched."""
69
+
67
70
  namespace: str
68
71
  event: str
69
72
  data: Dict[str, Any]
@@ -72,14 +75,14 @@ class BatchEvent:
72
75
 
73
76
  class CircuitBreaker:
74
77
  """Circuit breaker for Socket.IO failures.
75
-
78
+
76
79
  WHY circuit breaker pattern:
77
80
  - Prevents cascading failures when Socket.IO server is down
78
81
  - Fails fast instead of hanging on broken connections
79
82
  - Automatically recovers when service is restored
80
83
  - Reduces resource waste during outages
81
84
  """
82
-
85
+
83
86
  def __init__(self, failure_threshold: int = 5, recovery_timeout: int = 30):
84
87
  self.failure_threshold = failure_threshold
85
88
  self.recovery_timeout = recovery_timeout
@@ -87,25 +90,30 @@ class CircuitBreaker:
87
90
  self.last_failure_time = None
88
91
  self.state = CircuitState.CLOSED
89
92
  self.logger = get_logger("circuit_breaker")
90
-
93
+
91
94
  def can_execute(self) -> bool:
92
95
  """Check if execution is allowed based on circuit state."""
93
96
  if self.state == CircuitState.CLOSED:
94
97
  return True
95
98
  elif self.state == CircuitState.OPEN:
96
99
  # Check if recovery timeout has passed
97
- if self.last_failure_time and \
98
- datetime.now() - self.last_failure_time > timedelta(seconds=self.recovery_timeout):
100
+ if (
101
+ self.last_failure_time
102
+ and datetime.now() - self.last_failure_time
103
+ > timedelta(seconds=self.recovery_timeout)
104
+ ):
99
105
  self.state = CircuitState.HALF_OPEN
100
- self.logger.info("Circuit breaker transitioning to HALF_OPEN for testing")
106
+ self.logger.info(
107
+ "Circuit breaker transitioning to HALF_OPEN for testing"
108
+ )
101
109
  return True
102
110
  return False
103
111
  elif self.state == CircuitState.HALF_OPEN:
104
112
  # Allow one test request
105
113
  return True
106
-
114
+
107
115
  return False
108
-
116
+
109
117
  def record_success(self):
110
118
  """Record successful execution."""
111
119
  if self.state == CircuitState.HALF_OPEN:
@@ -115,25 +123,30 @@ class CircuitBreaker:
115
123
  elif self.state == CircuitState.CLOSED:
116
124
  # Reset failure count on success
117
125
  self.failure_count = 0
118
-
126
+
119
127
  def record_failure(self):
120
128
  """Record failed execution."""
121
129
  self.failure_count += 1
122
130
  self.last_failure_time = datetime.now()
123
-
131
+
124
132
  if self.state == CircuitState.HALF_OPEN:
125
133
  # Test failed, go back to OPEN
126
134
  self.state = CircuitState.OPEN
127
135
  self.logger.warning("Circuit breaker OPEN - test failed")
128
- elif self.state == CircuitState.CLOSED and self.failure_count >= self.failure_threshold:
136
+ elif (
137
+ self.state == CircuitState.CLOSED
138
+ and self.failure_count >= self.failure_threshold
139
+ ):
129
140
  # Too many failures, open circuit
130
141
  self.state = CircuitState.OPEN
131
- self.logger.error(f"Circuit breaker OPEN - {self.failure_count} consecutive failures")
142
+ self.logger.error(
143
+ f"Circuit breaker OPEN - {self.failure_count} consecutive failures"
144
+ )
132
145
 
133
146
 
134
147
  class SocketIOConnectionPool:
135
148
  """Connection pool for Socket.IO clients with circuit breaker and batching.
136
-
149
+
137
150
  WHY this design:
138
151
  - Maintains max 5 persistent connections to reduce overhead
139
152
  - Implements circuit breaker for resilience
@@ -141,139 +154,158 @@ class SocketIOConnectionPool:
141
154
  - Thread-safe connection management
142
155
  - Automatic connection health monitoring
143
156
  """
144
-
145
- def __init__(self, max_connections: int = 5, batch_window_ms: int = 50, health_check_interval: int = 30):
157
+
158
+ def __init__(
159
+ self,
160
+ max_connections: int = 5,
161
+ batch_window_ms: int = 50,
162
+ health_check_interval: int = 30,
163
+ ):
146
164
  self.max_connections = max_connections
147
165
  self.batch_window_ms = batch_window_ms
148
166
  self.health_check_interval = health_check_interval
149
167
  self.logger = get_logger("socketio_pool")
150
-
168
+
151
169
  # Connection pool
152
170
  self.available_connections: Deque[socketio.AsyncClient] = deque()
153
171
  self.active_connections: Dict[str, socketio.AsyncClient] = {}
154
172
  self.connection_stats: Dict[str, ConnectionStats] = {}
155
173
  self.pool_lock = threading.Lock()
156
-
174
+
157
175
  # Circuit breaker
158
176
  self.circuit_breaker = CircuitBreaker()
159
-
177
+
160
178
  # Batch processing
161
179
  self.batch_queue: Deque[BatchEvent] = deque()
162
180
  self.batch_lock = threading.Lock()
163
181
  self.batch_thread = None
164
182
  self.batch_running = False
165
-
183
+
166
184
  # Health monitoring
167
185
  self.health_thread = None
168
186
  self.health_running = False
169
187
  self.last_health_check = datetime.now()
170
-
188
+
171
189
  # Server configuration
172
190
  self.server_url = None
173
191
  self.server_port = None
174
-
192
+
175
193
  # Pool lifecycle
176
194
  self._running = False
177
-
195
+
178
196
  if not SOCKETIO_AVAILABLE:
179
197
  self.logger.warning("Socket.IO not available - connection pool disabled")
180
-
198
+
181
199
  def start(self):
182
200
  """Start the connection pool and batch processor."""
183
201
  if not SOCKETIO_AVAILABLE:
184
202
  return
185
-
203
+
186
204
  self._running = True
187
205
  self._detect_server()
188
-
206
+
189
207
  # Start batch processing thread
190
208
  self.batch_running = True
191
209
  self.batch_thread = threading.Thread(target=self._batch_processor, daemon=True)
192
210
  self.batch_thread.start()
193
-
211
+
194
212
  # Start health monitoring thread
195
213
  self.health_running = True
196
214
  self.health_thread = threading.Thread(target=self._health_monitor, daemon=True)
197
215
  self.health_thread.start()
198
-
199
- self.logger.info(f"Socket.IO connection pool started (max_connections={self.max_connections}, batch_window={self.batch_window_ms}ms, health_check={self.health_check_interval}s)")
200
-
216
+
217
+ self.logger.info(
218
+ f"Socket.IO connection pool started (max_connections={self.max_connections}, batch_window={self.batch_window_ms}ms, health_check={self.health_check_interval}s)"
219
+ )
220
+
201
221
  def stop(self):
202
222
  """Stop the connection pool and cleanup connections."""
203
223
  self._running = False
204
224
  self.batch_running = False
205
225
  self.health_running = False
206
-
226
+
207
227
  if self.batch_thread:
208
228
  self.batch_thread.join(timeout=2.0)
209
-
229
+
210
230
  if self.health_thread:
211
231
  self.health_thread.join(timeout=2.0)
212
-
232
+
213
233
  # Close all connections
214
234
  with self.pool_lock:
215
235
  # Close available connections
216
236
  while self.available_connections:
217
237
  client = self.available_connections.popleft()
218
238
  try:
219
- if hasattr(client, 'disconnect'):
239
+ if hasattr(client, "disconnect"):
220
240
  # Run disconnect in a new event loop if needed
221
241
  try:
222
242
  loop = asyncio.get_event_loop()
223
243
  except RuntimeError:
224
244
  loop = asyncio.new_event_loop()
225
245
  asyncio.set_event_loop(loop)
226
-
246
+
227
247
  if client.connected:
228
248
  loop.run_until_complete(client.disconnect())
229
249
  except Exception as e:
230
250
  self.logger.debug(f"Error closing connection: {e}")
231
-
251
+
232
252
  # Close active connections
233
253
  for conn_id, client in self.active_connections.items():
234
254
  try:
235
- if hasattr(client, 'disconnect') and client.connected:
255
+ if hasattr(client, "disconnect") and client.connected:
236
256
  try:
237
257
  loop = asyncio.get_event_loop()
238
258
  except RuntimeError:
239
259
  loop = asyncio.new_event_loop()
240
260
  asyncio.set_event_loop(loop)
241
-
261
+
242
262
  loop.run_until_complete(client.disconnect())
243
263
  except Exception as e:
244
264
  self.logger.debug(f"Error closing active connection {conn_id}: {e}")
245
-
265
+
246
266
  self.active_connections.clear()
247
267
  self.connection_stats.clear()
248
-
268
+
249
269
  self.logger.info("Socket.IO connection pool stopped")
250
-
270
+
251
271
  def _detect_server(self):
252
272
  """Detect Socket.IO server configuration."""
253
273
  # Check environment variable first
254
- env_port = os.environ.get('CLAUDE_MPM_SOCKETIO_PORT')
274
+ env_port = os.environ.get("CLAUDE_MPM_SOCKETIO_PORT")
255
275
  if env_port:
256
276
  try:
257
277
  self.server_port = int(env_port)
258
278
  self.server_url = f"http://localhost:{self.server_port}"
259
- self.logger.debug(f"Using Socket.IO server from environment: {self.server_url}")
279
+ self.logger.debug(
280
+ f"Using Socket.IO server from environment: {self.server_url}"
281
+ )
260
282
  return
261
283
  except ValueError:
262
284
  pass
263
-
285
+
264
286
  # Try to detect running server on common ports
265
287
  import socket
288
+
266
289
  # Create a list of common ports starting with dashboard port, then socketio range
267
- common_ports = [NetworkConfig.DEFAULT_DASHBOARD_PORT, NetworkConfig.DEFAULT_SOCKETIO_PORT]
290
+ common_ports = [
291
+ NetworkConfig.DEFAULT_DASHBOARD_PORT,
292
+ NetworkConfig.DEFAULT_SOCKETIO_PORT,
293
+ ]
268
294
  # Add other ports from the SocketIO range
269
- for port in range(NetworkConfig.SOCKETIO_PORT_RANGE[0] + 1, min(NetworkConfig.SOCKETIO_PORT_RANGE[0] + 6, NetworkConfig.SOCKETIO_PORT_RANGE[1] + 1)):
295
+ for port in range(
296
+ NetworkConfig.SOCKETIO_PORT_RANGE[0] + 1,
297
+ min(
298
+ NetworkConfig.SOCKETIO_PORT_RANGE[0] + 6,
299
+ NetworkConfig.SOCKETIO_PORT_RANGE[1] + 1,
300
+ ),
301
+ ):
270
302
  common_ports.append(port)
271
-
303
+
272
304
  for port in common_ports:
273
305
  try:
274
306
  with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
275
307
  s.settimeout(0.05)
276
- result = s.connect_ex(('localhost', port))
308
+ result = s.connect_ex(("localhost", port))
277
309
  if result == 0:
278
310
  self.server_port = port
279
311
  self.server_url = f"http://localhost:{port}"
@@ -281,17 +313,17 @@ class SocketIOConnectionPool:
281
313
  return
282
314
  except:
283
315
  continue
284
-
316
+
285
317
  # Fall back to default
286
318
  self.server_port = NetworkConfig.DEFAULT_DASHBOARD_PORT
287
319
  self.server_url = f"http://localhost:{self.server_port}"
288
320
  self.logger.debug(f"Using default Socket.IO server: {self.server_url}")
289
-
321
+
290
322
  def _create_client(self) -> Optional[socketio.AsyncClient]:
291
323
  """Create a new Socket.IO client connection."""
292
324
  if not SOCKETIO_AVAILABLE or not self.server_url:
293
325
  return None
294
-
326
+
295
327
  try:
296
328
  client = socketio.AsyncClient(
297
329
  reconnection=True,
@@ -300,40 +332,40 @@ class SocketIOConnectionPool:
300
332
  reconnection_delay_max=2,
301
333
  randomization_factor=0.2,
302
334
  logger=False,
303
- engineio_logger=False
335
+ engineio_logger=False,
304
336
  )
305
-
337
+
306
338
  # Create connection ID
307
339
  conn_id = f"pool_{len(self.connection_stats)}_{int(time.time())}"
308
-
340
+
309
341
  # Setup event handlers
310
342
  @client.event
311
343
  async def connect():
312
344
  self.connection_stats[conn_id].is_connected = True
313
345
  self.logger.debug(f"Pool connection {conn_id} established")
314
-
346
+
315
347
  @client.event
316
348
  async def disconnect():
317
349
  if conn_id in self.connection_stats:
318
350
  self.connection_stats[conn_id].is_connected = False
319
351
  self.logger.debug(f"Pool connection {conn_id} disconnected")
320
-
352
+
321
353
  @client.event
322
354
  async def connect_error(data):
323
355
  if conn_id in self.connection_stats:
324
356
  self.connection_stats[conn_id].errors += 1
325
357
  self.connection_stats[conn_id].consecutive_errors += 1
326
358
  self.logger.debug(f"Pool connection {conn_id} error: {data}")
327
-
359
+
328
360
  # Initialize stats
329
361
  self.connection_stats[conn_id] = ConnectionStats()
330
-
362
+
331
363
  return client
332
-
364
+
333
365
  except Exception as e:
334
366
  self.logger.error(f"Failed to create Socket.IO client: {e}")
335
367
  return None
336
-
368
+
337
369
  def _get_connection(self) -> Optional[socketio.AsyncClient]:
338
370
  """Get an available connection from the pool."""
339
371
  with self.pool_lock:
@@ -345,7 +377,7 @@ class SocketIOConnectionPool:
345
377
  if stats.is_connected:
346
378
  stats.last_used = datetime.now()
347
379
  return client
348
-
380
+
349
381
  # Create new connection if under limit
350
382
  if len(self.active_connections) < self.max_connections:
351
383
  client = self._create_client()
@@ -353,11 +385,11 @@ class SocketIOConnectionPool:
353
385
  conn_id = f"pool_{len(self.active_connections)}_{int(time.time())}"
354
386
  self.active_connections[conn_id] = client
355
387
  return client
356
-
388
+
357
389
  # Pool exhausted
358
390
  self.logger.warning("Socket.IO connection pool exhausted")
359
391
  return None
360
-
392
+
361
393
  def _return_connection(self, client: socketio.AsyncClient):
362
394
  """Return a connection to the pool."""
363
395
  with self.pool_lock:
@@ -369,15 +401,14 @@ class SocketIOConnectionPool:
369
401
  if client.connected:
370
402
  # Schedule disconnect (don't block)
371
403
  threading.Thread(
372
- target=lambda: asyncio.run(client.disconnect()),
373
- daemon=True
404
+ target=lambda: asyncio.run(client.disconnect()), daemon=True
374
405
  ).start()
375
406
  except Exception as e:
376
407
  self.logger.debug(f"Error closing excess connection: {e}")
377
-
408
+
378
409
  def emit_event(self, namespace: str, event: str, data: Dict[str, Any]):
379
410
  """Emit event using connection pool with batching.
380
-
411
+
381
412
  WHY batching approach:
382
413
  - Collects events in 50ms windows to reduce network overhead
383
414
  - Maintains event ordering within batches
@@ -385,98 +416,106 @@ class SocketIOConnectionPool:
385
416
  """
386
417
  if not SOCKETIO_AVAILABLE or not self._running:
387
418
  return
388
-
419
+
389
420
  # Check circuit breaker
390
421
  if not self.circuit_breaker.can_execute():
391
- self.logger.debug(f"Circuit breaker OPEN - dropping event {namespace}/{event}")
422
+ self.logger.debug(
423
+ f"Circuit breaker OPEN - dropping event {namespace}/{event}"
424
+ )
392
425
  return
393
-
426
+
394
427
  # Add to batch queue
395
428
  batch_event = BatchEvent(namespace, event, data)
396
429
  with self.batch_lock:
397
430
  self.batch_queue.append(batch_event)
398
-
431
+
399
432
  def _batch_processor(self):
400
433
  """Process batched events in micro-batches."""
401
434
  self.logger.debug("Batch processor started")
402
-
435
+
403
436
  while self.batch_running:
404
437
  try:
405
438
  # Sleep for batch window
406
439
  time.sleep(self.batch_window_ms / 1000.0)
407
-
440
+
408
441
  # Collect batch
409
442
  current_batch = []
410
443
  with self.batch_lock:
411
- while self.batch_queue and len(current_batch) < 10: # Max 10 events per batch
444
+ while (
445
+ self.batch_queue and len(current_batch) < 10
446
+ ): # Max 10 events per batch
412
447
  current_batch.append(self.batch_queue.popleft())
413
-
448
+
414
449
  # Process batch
415
450
  if current_batch:
416
451
  self._process_batch(current_batch)
417
-
452
+
418
453
  except Exception as e:
419
454
  self.logger.error(f"Batch processor error: {e}")
420
455
  time.sleep(0.1) # Brief pause on error
421
-
456
+
422
457
  self.logger.debug("Batch processor stopped")
423
-
458
+
424
459
  def _process_batch(self, batch: List[BatchEvent]):
425
460
  """Process a batch of events."""
426
461
  if not batch:
427
462
  return
428
-
463
+
429
464
  # Group events by namespace for efficiency
430
465
  namespace_groups = defaultdict(list)
431
466
  for event in batch:
432
467
  namespace_groups[event.namespace].append(event)
433
-
468
+
434
469
  # Process each namespace group
435
470
  for namespace, events in namespace_groups.items():
436
471
  success = self._emit_batch_to_namespace(namespace, events)
437
-
472
+
438
473
  # Update circuit breaker
439
474
  if success:
440
475
  self.circuit_breaker.record_success()
441
476
  else:
442
477
  self.circuit_breaker.record_failure()
443
-
444
- async def _async_emit_batch(self, client: socketio.AsyncClient, namespace: str, events: List[BatchEvent]) -> bool:
478
+
479
+ async def _async_emit_batch(
480
+ self, client: socketio.AsyncClient, namespace: str, events: List[BatchEvent]
481
+ ) -> bool:
445
482
  """Async version of emit batch."""
446
483
  try:
447
484
  # Connect if not connected
448
485
  if not client.connected:
449
486
  await self._connect_client(client)
450
-
487
+
451
488
  # Emit events
452
489
  for event in events:
453
490
  enhanced_data = {
454
491
  **event.data,
455
492
  "timestamp": event.timestamp.isoformat(),
456
- "batch_id": f"batch_{int(time.time() * 1000)}"
493
+ "batch_id": f"batch_{int(time.time() * 1000)}",
457
494
  }
458
-
495
+
459
496
  await client.emit(event.event, enhanced_data, namespace=namespace)
460
-
497
+
461
498
  # Update stats
462
499
  for conn_id, stats in self.connection_stats.items():
463
500
  if stats.is_connected:
464
501
  stats.events_sent += len(events)
465
502
  stats.consecutive_errors = 0
466
503
  break
467
-
504
+
468
505
  self.logger.debug(f"Emitted batch of {len(events)} events to {namespace}")
469
506
  return True
470
507
  except Exception as e:
471
508
  self.logger.error(f"Failed to emit batch to {namespace}: {e}")
472
509
  return False
473
-
474
- def _emit_batch_to_namespace(self, namespace: str, events: List[BatchEvent]) -> bool:
510
+
511
+ def _emit_batch_to_namespace(
512
+ self, namespace: str, events: List[BatchEvent]
513
+ ) -> bool:
475
514
  """Emit a batch of events to a specific namespace."""
476
515
  client = self._get_connection()
477
516
  if not client:
478
517
  return False
479
-
518
+
480
519
  loop = None
481
520
  try:
482
521
  # Get or create event loop for this thread
@@ -484,50 +523,51 @@ class SocketIOConnectionPool:
484
523
  loop = asyncio.get_running_loop()
485
524
  # We're in an async context, use it directly
486
525
  return asyncio.run_coroutine_threadsafe(
487
- self._async_emit_batch(client, namespace, events),
488
- loop
526
+ self._async_emit_batch(client, namespace, events), loop
489
527
  ).result(timeout=5.0)
490
528
  except RuntimeError:
491
529
  # No running loop, create one
492
530
  loop = asyncio.new_event_loop()
493
531
  asyncio.set_event_loop(loop)
494
-
532
+
495
533
  # Connect if not connected
496
534
  if not client.connected:
497
535
  loop.run_until_complete(self._connect_client(client))
498
-
536
+
499
537
  # Emit events
500
538
  for event in events:
501
539
  enhanced_data = {
502
540
  **event.data,
503
541
  "timestamp": event.timestamp.isoformat(),
504
- "batch_id": f"batch_{int(time.time() * 1000)}"
542
+ "batch_id": f"batch_{int(time.time() * 1000)}",
505
543
  }
506
-
544
+
507
545
  loop.run_until_complete(
508
546
  client.emit(event.event, enhanced_data, namespace=namespace)
509
547
  )
510
-
548
+
511
549
  # Update stats
512
550
  for conn_id, stats in self.connection_stats.items():
513
551
  if stats.is_connected:
514
552
  stats.events_sent += len(events)
515
553
  stats.consecutive_errors = 0
516
554
  break
517
-
518
- self.logger.debug(f"Emitted batch of {len(events)} events to {namespace}")
555
+
556
+ self.logger.debug(
557
+ f"Emitted batch of {len(events)} events to {namespace}"
558
+ )
519
559
  return True
520
-
560
+
521
561
  except Exception as e:
522
562
  self.logger.error(f"Failed to emit batch to {namespace}: {e}")
523
-
563
+
524
564
  # Update stats
525
565
  for conn_id, stats in self.connection_stats.items():
526
566
  if stats.is_connected:
527
567
  stats.errors += 1
528
568
  stats.consecutive_errors += 1
529
569
  break
530
-
570
+
531
571
  return False
532
572
  finally:
533
573
  self._return_connection(client)
@@ -543,33 +583,29 @@ class SocketIOConnectionPool:
543
583
  loop.close()
544
584
  except:
545
585
  pass
546
-
586
+
547
587
  async def _connect_client(self, client: socketio.AsyncClient):
548
588
  """Connect a client with timeout."""
549
589
  try:
550
590
  # Use asyncio timeout instead of signal (thread-safe)
551
591
  import asyncio
552
-
592
+
553
593
  # 2-second timeout for connection
554
594
  await asyncio.wait_for(
555
- client.connect(
556
- self.server_url,
557
- auth={'token': 'dev-token'},
558
- wait=True
559
- ),
560
- timeout=2.0
595
+ client.connect(self.server_url, auth={"token": "dev-token"}, wait=True),
596
+ timeout=2.0,
561
597
  )
562
-
598
+
563
599
  except asyncio.TimeoutError:
564
600
  self.logger.debug("Socket.IO connection timeout")
565
601
  raise TimeoutError("Socket.IO connection timeout")
566
602
  except Exception as e:
567
603
  self.logger.debug(f"Client connection failed: {e}")
568
604
  raise
569
-
605
+
570
606
  def _health_monitor(self):
571
607
  """Monitor health of connections in the pool.
572
-
608
+
573
609
  WHY health monitoring:
574
610
  - Detects stale/broken connections proactively
575
611
  - Removes unhealthy connections before they cause failures
@@ -577,102 +613,111 @@ class SocketIOConnectionPool:
577
613
  - Reduces connection errors by 40-60%
578
614
  """
579
615
  self.logger.debug("Health monitor started")
580
-
616
+
581
617
  while self.health_running:
582
618
  try:
583
619
  # Sleep for health check interval
584
620
  time.sleep(self.health_check_interval)
585
-
621
+
586
622
  # Check connection health
587
623
  self._check_connections_health()
588
-
624
+
589
625
  # Update last health check time
590
626
  self.last_health_check = datetime.now()
591
-
627
+
592
628
  except Exception as e:
593
629
  self.logger.error(f"Health monitor error: {e}")
594
630
  time.sleep(5) # Brief pause on error
595
-
631
+
596
632
  self.logger.debug("Health monitor stopped")
597
-
633
+
598
634
  def _check_connections_health(self):
599
635
  """Check health of all connections in the pool."""
600
636
  with self.pool_lock:
601
637
  unhealthy_connections = []
602
-
638
+
603
639
  # Check each connection's health
604
640
  for conn_id, client in list(self.active_connections.items()):
605
641
  stats = self.connection_stats.get(conn_id)
606
642
  if not stats:
607
643
  continue
608
-
644
+
609
645
  # Health criteria:
610
646
  # 1. Too many consecutive errors
611
647
  if stats.consecutive_errors > 3:
612
648
  unhealthy_connections.append((conn_id, client, "excessive_errors"))
613
649
  continue
614
-
650
+
615
651
  # 2. Connection is not actually connected
616
652
  if not client.connected and stats.is_connected:
617
653
  unhealthy_connections.append((conn_id, client, "disconnected"))
618
654
  stats.is_connected = False
619
655
  continue
620
-
656
+
621
657
  # 3. Connection idle for too long (>5 minutes)
622
658
  idle_time = (datetime.now() - stats.last_used).total_seconds()
623
- if idle_time > 300 and conn_id not in [id for id, _ in enumerate(self.available_connections)]:
659
+ if idle_time > 300 and conn_id not in [
660
+ id for id, _ in enumerate(self.available_connections)
661
+ ]:
624
662
  unhealthy_connections.append((conn_id, client, "idle_timeout"))
625
663
  continue
626
-
664
+
627
665
  # 4. High error rate (>10% of events)
628
666
  if stats.events_sent > 100 and stats.errors > stats.events_sent * 0.1:
629
667
  unhealthy_connections.append((conn_id, client, "high_error_rate"))
630
-
668
+
631
669
  # Remove unhealthy connections
632
670
  for conn_id, client, reason in unhealthy_connections:
633
- self.logger.warning(f"Removing unhealthy connection {conn_id}: {reason}")
634
-
671
+ self.logger.warning(
672
+ f"Removing unhealthy connection {conn_id}: {reason}"
673
+ )
674
+
635
675
  # Remove from active connections
636
676
  self.active_connections.pop(conn_id, None)
637
-
677
+
638
678
  # Remove from available if present
639
679
  if client in self.available_connections:
640
680
  self.available_connections.remove(client)
641
-
681
+
642
682
  # Try to disconnect
643
683
  try:
644
684
  if client.connected:
645
685
  threading.Thread(
646
- target=lambda: asyncio.run(client.disconnect()),
647
- daemon=True
686
+ target=lambda: asyncio.run(client.disconnect()), daemon=True
648
687
  ).start()
649
688
  except Exception as e:
650
689
  self.logger.debug(f"Error disconnecting unhealthy connection: {e}")
651
-
690
+
652
691
  # Remove stats
653
692
  self.connection_stats.pop(conn_id, None)
654
-
693
+
655
694
  # Log health check results
656
695
  if unhealthy_connections:
657
- self.logger.info(f"Health check removed {len(unhealthy_connections)} unhealthy connections")
658
-
696
+ self.logger.info(
697
+ f"Health check removed {len(unhealthy_connections)} unhealthy connections"
698
+ )
699
+
659
700
  # Pre-create connections if pool is too small
660
- current_total = len(self.active_connections) + len(self.available_connections)
701
+ current_total = len(self.active_connections) + len(
702
+ self.available_connections
703
+ )
661
704
  if current_total < min(2, self.max_connections):
662
705
  self.logger.debug("Pre-creating connections to maintain pool minimum")
663
706
  for _ in range(min(2, self.max_connections) - current_total):
664
707
  client = self._create_client()
665
708
  if client:
666
- conn_id = f"pool_{len(self.active_connections)}_{int(time.time())}"
709
+ conn_id = (
710
+ f"pool_{len(self.active_connections)}_{int(time.time())}"
711
+ )
667
712
  self.active_connections[conn_id] = client
668
713
  self.available_connections.append(client)
669
-
714
+
670
715
  async def _ping_connection(self, client: socketio.AsyncClient) -> bool:
671
716
  """Ping a connection to check if it's alive.
672
-
717
+
673
718
  Args:
674
719
  client: The Socket.IO client to ping
675
-
720
+
676
721
  Returns:
677
722
  True if connection is healthy, False otherwise
678
723
  """
@@ -680,34 +725,43 @@ class SocketIOConnectionPool:
680
725
  # Send a ping and wait for response
681
726
  await asyncio.wait_for(
682
727
  client.emit("ping", {"timestamp": time.time()}, namespace="/health"),
683
- timeout=1.0
728
+ timeout=1.0,
684
729
  )
685
730
  return True
686
731
  except (asyncio.TimeoutError, Exception):
687
732
  return False
688
-
733
+
689
734
  def get_stats(self) -> Dict[str, Any]:
690
735
  """Get connection pool statistics."""
691
736
  with self.pool_lock:
692
737
  # Calculate health metrics
693
738
  healthy_connections = sum(
694
- 1 for stats in self.connection_stats.values()
739
+ 1
740
+ for stats in self.connection_stats.values()
695
741
  if stats.is_connected and stats.consecutive_errors < 3
696
742
  )
697
-
743
+
698
744
  return {
699
745
  "max_connections": self.max_connections,
700
746
  "available_connections": len(self.available_connections),
701
747
  "active_connections": len(self.active_connections),
702
748
  "healthy_connections": healthy_connections,
703
- "total_events_sent": sum(stats.events_sent for stats in self.connection_stats.values()),
704
- "total_errors": sum(stats.errors for stats in self.connection_stats.values()),
749
+ "total_events_sent": sum(
750
+ stats.events_sent for stats in self.connection_stats.values()
751
+ ),
752
+ "total_errors": sum(
753
+ stats.errors for stats in self.connection_stats.values()
754
+ ),
705
755
  "circuit_state": self.circuit_breaker.state.value,
706
756
  "circuit_failures": self.circuit_breaker.failure_count,
707
757
  "batch_queue_size": len(self.batch_queue),
708
758
  "server_url": self.server_url,
709
- "last_health_check": self.last_health_check.isoformat() if hasattr(self, 'last_health_check') else None,
710
- "health_check_interval": self.health_check_interval
759
+ "last_health_check": (
760
+ self.last_health_check.isoformat()
761
+ if hasattr(self, "last_health_check")
762
+ else None
763
+ ),
764
+ "health_check_interval": self.health_check_interval,
711
765
  }
712
766
 
713
767
 
@@ -736,4 +790,4 @@ def stop_connection_pool():
736
790
  def emit_hook_event(namespace: str, event: str, data: Dict[str, Any]):
737
791
  """Emit a hook event using the connection pool."""
738
792
  pool = get_connection_pool()
739
- pool.emit_event(namespace, event, data)
793
+ pool.emit_event(namespace, event, data)