claude-mpm 3.9.9__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/__init__.py +2 -2
- claude_mpm/__main__.py +3 -2
- claude_mpm/agents/__init__.py +85 -79
- claude_mpm/agents/agent_loader.py +464 -1003
- claude_mpm/agents/agent_loader_integration.py +45 -45
- claude_mpm/agents/agents_metadata.py +29 -30
- claude_mpm/agents/async_agent_loader.py +156 -138
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/base_agent_loader.py +179 -151
- claude_mpm/agents/frontmatter_validator.py +229 -130
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/system_agent_config.py +213 -147
- claude_mpm/agents/templates/__init__.py +13 -13
- claude_mpm/agents/templates/code_analyzer.json +2 -2
- claude_mpm/agents/templates/data_engineer.json +1 -1
- claude_mpm/agents/templates/documentation.json +23 -11
- claude_mpm/agents/templates/engineer.json +22 -6
- claude_mpm/agents/templates/memory_manager.json +155 -0
- claude_mpm/agents/templates/ops.json +2 -2
- claude_mpm/agents/templates/project_organizer.json +1 -1
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/refactoring_engineer.json +222 -0
- claude_mpm/agents/templates/research.json +20 -14
- claude_mpm/agents/templates/security.json +1 -1
- claude_mpm/agents/templates/ticketing.json +1 -1
- claude_mpm/agents/templates/version_control.json +1 -1
- claude_mpm/agents/templates/web_qa.json +3 -1
- claude_mpm/agents/templates/web_ui.json +2 -2
- claude_mpm/cli/__init__.py +90 -49
- claude_mpm/cli/__main__.py +3 -2
- claude_mpm/cli/commands/__init__.py +21 -18
- claude_mpm/cli/commands/agents.py +279 -247
- claude_mpm/cli/commands/aggregate.py +138 -157
- claude_mpm/cli/commands/cleanup.py +147 -147
- claude_mpm/cli/commands/config.py +93 -76
- claude_mpm/cli/commands/info.py +17 -16
- claude_mpm/cli/commands/mcp.py +143 -762
- claude_mpm/cli/commands/mcp_command_router.py +139 -0
- claude_mpm/cli/commands/mcp_config_commands.py +20 -0
- claude_mpm/cli/commands/mcp_install_commands.py +20 -0
- claude_mpm/cli/commands/mcp_server_commands.py +175 -0
- claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
- claude_mpm/cli/commands/memory.py +239 -203
- claude_mpm/cli/commands/monitor.py +203 -81
- claude_mpm/cli/commands/run.py +380 -429
- claude_mpm/cli/commands/run_config_checker.py +160 -0
- claude_mpm/cli/commands/socketio_monitor.py +235 -0
- claude_mpm/cli/commands/tickets.py +305 -197
- claude_mpm/cli/parser.py +24 -1150
- claude_mpm/cli/parsers/__init__.py +29 -0
- claude_mpm/cli/parsers/agents_parser.py +136 -0
- claude_mpm/cli/parsers/base_parser.py +331 -0
- claude_mpm/cli/parsers/config_parser.py +85 -0
- claude_mpm/cli/parsers/mcp_parser.py +152 -0
- claude_mpm/cli/parsers/memory_parser.py +138 -0
- claude_mpm/cli/parsers/monitor_parser.py +104 -0
- claude_mpm/cli/parsers/run_parser.py +147 -0
- claude_mpm/cli/parsers/tickets_parser.py +203 -0
- claude_mpm/cli/ticket_cli.py +7 -3
- claude_mpm/cli/utils.py +55 -37
- claude_mpm/cli_module/__init__.py +6 -6
- claude_mpm/cli_module/args.py +188 -140
- claude_mpm/cli_module/commands.py +79 -70
- claude_mpm/cli_module/migration_example.py +38 -60
- claude_mpm/config/__init__.py +32 -25
- claude_mpm/config/agent_config.py +151 -119
- claude_mpm/config/experimental_features.py +217 -0
- claude_mpm/config/paths.py +94 -208
- claude_mpm/config/socketio_config.py +84 -73
- claude_mpm/constants.py +36 -18
- claude_mpm/core/__init__.py +9 -6
- claude_mpm/core/agent_name_normalizer.py +68 -71
- claude_mpm/core/agent_registry.py +372 -521
- claude_mpm/core/agent_session_manager.py +74 -63
- claude_mpm/core/base_service.py +116 -87
- claude_mpm/core/cache.py +119 -153
- claude_mpm/core/claude_runner.py +425 -1120
- claude_mpm/core/config.py +263 -168
- claude_mpm/core/config_aliases.py +69 -61
- claude_mpm/core/config_constants.py +292 -0
- claude_mpm/core/constants.py +57 -99
- claude_mpm/core/container.py +211 -178
- claude_mpm/core/exceptions.py +233 -89
- claude_mpm/core/factories.py +92 -54
- claude_mpm/core/framework_loader.py +378 -220
- claude_mpm/core/hook_manager.py +198 -83
- claude_mpm/core/hook_performance_config.py +136 -0
- claude_mpm/core/injectable_service.py +61 -55
- claude_mpm/core/interactive_session.py +165 -155
- claude_mpm/core/interfaces.py +221 -195
- claude_mpm/core/lazy.py +96 -96
- claude_mpm/core/logger.py +133 -107
- claude_mpm/core/logging_config.py +185 -157
- claude_mpm/core/minimal_framework_loader.py +20 -15
- claude_mpm/core/mixins.py +30 -29
- claude_mpm/core/oneshot_session.py +215 -181
- claude_mpm/core/optimized_agent_loader.py +134 -138
- claude_mpm/core/optimized_startup.py +159 -157
- claude_mpm/core/pm_hook_interceptor.py +85 -72
- claude_mpm/core/service_registry.py +103 -101
- claude_mpm/core/session_manager.py +97 -87
- claude_mpm/core/socketio_pool.py +212 -158
- claude_mpm/core/tool_access_control.py +58 -51
- claude_mpm/core/types.py +46 -24
- claude_mpm/core/typing_utils.py +166 -82
- claude_mpm/core/unified_agent_registry.py +721 -0
- claude_mpm/core/unified_config.py +550 -0
- claude_mpm/core/unified_paths.py +549 -0
- claude_mpm/dashboard/index.html +1 -1
- claude_mpm/dashboard/open_dashboard.py +51 -17
- claude_mpm/dashboard/static/css/dashboard.css +27 -8
- claude_mpm/dashboard/static/dist/components/agent-inference.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-processor.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/export-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-library-loader.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-visualizer.js +2 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/session-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/socket-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/ui-state-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/working-directory.js +2 -0
- claude_mpm/dashboard/static/dist/dashboard.js +2 -0
- claude_mpm/dashboard/static/dist/socket-client.js +2 -0
- claude_mpm/dashboard/static/js/components/agent-inference.js +80 -76
- claude_mpm/dashboard/static/js/components/event-processor.js +71 -67
- claude_mpm/dashboard/static/js/components/event-viewer.js +74 -70
- claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +106 -92
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +11 -11
- claude_mpm/dashboard/static/js/components/hud-manager.js +73 -73
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +163 -163
- claude_mpm/dashboard/static/js/components/module-viewer.js +305 -233
- claude_mpm/dashboard/static/js/components/session-manager.js +32 -29
- claude_mpm/dashboard/static/js/components/socket-manager.js +27 -20
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +21 -18
- claude_mpm/dashboard/static/js/components/working-directory.js +74 -71
- claude_mpm/dashboard/static/js/dashboard.js +178 -453
- claude_mpm/dashboard/static/js/extension-error-handler.js +164 -0
- claude_mpm/dashboard/static/js/socket-client.js +120 -54
- claude_mpm/dashboard/templates/index.html +40 -50
- claude_mpm/experimental/cli_enhancements.py +60 -58
- claude_mpm/generators/__init__.py +1 -1
- claude_mpm/generators/agent_profile_generator.py +75 -65
- claude_mpm/hooks/__init__.py +1 -1
- claude_mpm/hooks/base_hook.py +33 -28
- claude_mpm/hooks/claude_hooks/__init__.py +1 -1
- claude_mpm/hooks/claude_hooks/connection_pool.py +120 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +743 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +415 -1331
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +4 -4
- claude_mpm/hooks/claude_hooks/memory_integration.py +221 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +348 -0
- claude_mpm/hooks/claude_hooks/tool_analysis.py +230 -0
- claude_mpm/hooks/memory_integration_hook.py +140 -100
- claude_mpm/hooks/tool_call_interceptor.py +89 -76
- claude_mpm/hooks/validation_hooks.py +57 -49
- claude_mpm/init.py +145 -121
- claude_mpm/models/__init__.py +9 -9
- claude_mpm/models/agent_definition.py +33 -23
- claude_mpm/models/agent_session.py +228 -200
- claude_mpm/scripts/__init__.py +1 -1
- claude_mpm/scripts/socketio_daemon.py +192 -75
- claude_mpm/scripts/socketio_server_manager.py +328 -0
- claude_mpm/scripts/start_activity_logging.py +25 -22
- claude_mpm/services/__init__.py +68 -43
- claude_mpm/services/agent_capabilities_service.py +271 -0
- claude_mpm/services/agents/__init__.py +23 -32
- claude_mpm/services/agents/deployment/__init__.py +3 -3
- claude_mpm/services/agents/deployment/agent_config_provider.py +310 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +359 -0
- claude_mpm/services/agents/deployment/agent_definition_factory.py +84 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +415 -2113
- claude_mpm/services/agents/deployment/agent_discovery_service.py +387 -0
- claude_mpm/services/agents/deployment/agent_environment_manager.py +293 -0
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +387 -0
- claude_mpm/services/agents/deployment/agent_format_converter.py +453 -0
- claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +161 -0
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +345 -495
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +279 -0
- claude_mpm/services/agents/deployment/agent_restore_handler.py +88 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +406 -0
- claude_mpm/services/agents/deployment/agent_validator.py +352 -0
- claude_mpm/services/agents/deployment/agent_version_manager.py +313 -0
- claude_mpm/services/agents/deployment/agent_versioning.py +6 -9
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +79 -0
- claude_mpm/services/agents/deployment/async_agent_deployment.py +298 -234
- claude_mpm/services/agents/deployment/config/__init__.py +13 -0
- claude_mpm/services/agents/deployment/config/deployment_config.py +182 -0
- claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
- claude_mpm/services/agents/deployment/deployment_config_loader.py +54 -0
- claude_mpm/services/agents/deployment/deployment_type_detector.py +124 -0
- claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
- claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
- claude_mpm/services/agents/deployment/facade/deployment_executor.py +73 -0
- claude_mpm/services/agents/deployment/facade/deployment_facade.py +270 -0
- claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
- claude_mpm/services/agents/deployment/interface_adapter.py +227 -0
- claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
- claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
- claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +159 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
- claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +195 -0
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +119 -0
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +79 -0
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +90 -0
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +100 -0
- claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +98 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
- claude_mpm/services/agents/deployment/processors/agent_processor.py +258 -0
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +318 -0
- claude_mpm/services/agents/deployment/results/__init__.py +13 -0
- claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
- claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
- claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
- claude_mpm/services/agents/deployment/strategies/base_strategy.py +119 -0
- claude_mpm/services/agents/deployment/strategies/project_strategy.py +150 -0
- claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +116 -0
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +137 -0
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +108 -0
- claude_mpm/services/agents/deployment/validation/__init__.py +19 -0
- claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
- claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
- claude_mpm/services/agents/deployment/validation/template_validator.py +299 -0
- claude_mpm/services/agents/deployment/validation/validation_result.py +226 -0
- claude_mpm/services/agents/loading/__init__.py +2 -2
- claude_mpm/services/agents/loading/agent_profile_loader.py +259 -229
- claude_mpm/services/agents/loading/base_agent_manager.py +90 -81
- claude_mpm/services/agents/loading/framework_agent_loader.py +154 -129
- claude_mpm/services/agents/management/__init__.py +2 -2
- claude_mpm/services/agents/management/agent_capabilities_generator.py +72 -58
- claude_mpm/services/agents/management/agent_management_service.py +209 -156
- claude_mpm/services/agents/memory/__init__.py +9 -6
- claude_mpm/services/agents/memory/agent_memory_manager.py +218 -1152
- claude_mpm/services/agents/memory/agent_persistence_service.py +20 -16
- claude_mpm/services/agents/memory/analyzer.py +430 -0
- claude_mpm/services/agents/memory/content_manager.py +376 -0
- claude_mpm/services/agents/memory/template_generator.py +468 -0
- claude_mpm/services/agents/registry/__init__.py +7 -10
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +122 -97
- claude_mpm/services/agents/registry/modification_tracker.py +351 -285
- claude_mpm/services/async_session_logger.py +187 -153
- claude_mpm/services/claude_session_logger.py +87 -72
- claude_mpm/services/command_handler_service.py +217 -0
- claude_mpm/services/communication/__init__.py +3 -2
- claude_mpm/services/core/__init__.py +50 -97
- claude_mpm/services/core/base.py +60 -53
- claude_mpm/services/core/interfaces/__init__.py +188 -0
- claude_mpm/services/core/interfaces/agent.py +351 -0
- claude_mpm/services/core/interfaces/communication.py +343 -0
- claude_mpm/services/core/interfaces/infrastructure.py +413 -0
- claude_mpm/services/core/interfaces/service.py +434 -0
- claude_mpm/services/core/interfaces.py +19 -944
- claude_mpm/services/event_aggregator.py +208 -170
- claude_mpm/services/exceptions.py +387 -308
- claude_mpm/services/framework_claude_md_generator/__init__.py +75 -79
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +69 -60
- claude_mpm/services/framework_claude_md_generator/content_validator.py +65 -61
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +68 -49
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +34 -34
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +25 -22
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +10 -10
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +5 -4
- claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
- claude_mpm/services/framework_claude_md_generator/version_manager.py +30 -28
- claude_mpm/services/hook_service.py +106 -114
- claude_mpm/services/infrastructure/__init__.py +7 -5
- claude_mpm/services/infrastructure/context_preservation.py +571 -0
- claude_mpm/services/infrastructure/daemon_manager.py +279 -0
- claude_mpm/services/infrastructure/logging.py +83 -76
- claude_mpm/services/infrastructure/monitoring.py +547 -404
- claude_mpm/services/mcp_gateway/__init__.py +40 -23
- claude_mpm/services/mcp_gateway/config/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/config/config_loader.py +61 -56
- claude_mpm/services/mcp_gateway/config/config_schema.py +50 -41
- claude_mpm/services/mcp_gateway/config/configuration.py +82 -75
- claude_mpm/services/mcp_gateway/core/__init__.py +14 -21
- claude_mpm/services/mcp_gateway/core/base.py +80 -67
- claude_mpm/services/mcp_gateway/core/exceptions.py +60 -46
- claude_mpm/services/mcp_gateway/core/interfaces.py +97 -93
- claude_mpm/services/mcp_gateway/main.py +307 -127
- claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +100 -101
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
- claude_mpm/services/mcp_gateway/server/__init__.py +4 -4
- claude_mpm/services/mcp_gateway/server/{mcp_server.py → mcp_gateway.py} +149 -153
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +105 -107
- claude_mpm/services/mcp_gateway/server/stdio_server.py +691 -0
- claude_mpm/services/mcp_gateway/tools/__init__.py +4 -2
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +110 -121
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +283 -215
- claude_mpm/services/mcp_gateway/tools/hello_world.py +122 -120
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +652 -0
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +606 -0
- claude_mpm/services/memory/__init__.py +2 -2
- claude_mpm/services/memory/builder.py +451 -362
- claude_mpm/services/memory/cache/__init__.py +2 -2
- claude_mpm/services/memory/cache/shared_prompt_cache.py +232 -194
- claude_mpm/services/memory/cache/simple_cache.py +107 -93
- claude_mpm/services/memory/indexed_memory.py +195 -193
- claude_mpm/services/memory/optimizer.py +267 -234
- claude_mpm/services/memory/router.py +571 -263
- claude_mpm/services/memory_hook_service.py +237 -0
- claude_mpm/services/port_manager.py +223 -0
- claude_mpm/services/project/__init__.py +3 -3
- claude_mpm/services/project/analyzer.py +451 -305
- claude_mpm/services/project/registry.py +262 -240
- claude_mpm/services/recovery_manager.py +287 -231
- claude_mpm/services/response_tracker.py +87 -67
- claude_mpm/services/runner_configuration_service.py +587 -0
- claude_mpm/services/session_management_service.py +304 -0
- claude_mpm/services/socketio/__init__.py +4 -4
- claude_mpm/services/socketio/client_proxy.py +174 -0
- claude_mpm/services/socketio/handlers/__init__.py +3 -3
- claude_mpm/services/socketio/handlers/base.py +44 -30
- claude_mpm/services/socketio/handlers/connection.py +145 -65
- claude_mpm/services/socketio/handlers/file.py +123 -108
- claude_mpm/services/socketio/handlers/git.py +607 -373
- claude_mpm/services/socketio/handlers/hook.py +170 -0
- claude_mpm/services/socketio/handlers/memory.py +4 -4
- claude_mpm/services/socketio/handlers/project.py +4 -4
- claude_mpm/services/socketio/handlers/registry.py +53 -38
- claude_mpm/services/socketio/server/__init__.py +18 -0
- claude_mpm/services/socketio/server/broadcaster.py +252 -0
- claude_mpm/services/socketio/server/core.py +399 -0
- claude_mpm/services/socketio/server/main.py +323 -0
- claude_mpm/services/socketio_client_manager.py +160 -133
- claude_mpm/services/socketio_server.py +36 -1885
- claude_mpm/services/subprocess_launcher_service.py +316 -0
- claude_mpm/services/system_instructions_service.py +258 -0
- claude_mpm/services/ticket_manager.py +20 -534
- claude_mpm/services/utility_service.py +285 -0
- claude_mpm/services/version_control/__init__.py +18 -21
- claude_mpm/services/version_control/branch_strategy.py +20 -10
- claude_mpm/services/version_control/conflict_resolution.py +37 -13
- claude_mpm/services/version_control/git_operations.py +52 -21
- claude_mpm/services/version_control/semantic_versioning.py +92 -53
- claude_mpm/services/version_control/version_parser.py +145 -125
- claude_mpm/services/version_service.py +270 -0
- claude_mpm/storage/__init__.py +9 -0
- claude_mpm/storage/state_storage.py +552 -0
- claude_mpm/ticket_wrapper.py +2 -2
- claude_mpm/utils/__init__.py +2 -2
- claude_mpm/utils/agent_dependency_loader.py +453 -243
- claude_mpm/utils/config_manager.py +157 -118
- claude_mpm/utils/console.py +1 -1
- claude_mpm/utils/dependency_cache.py +102 -107
- claude_mpm/utils/dependency_manager.py +52 -47
- claude_mpm/utils/dependency_strategies.py +131 -96
- claude_mpm/utils/environment_context.py +110 -102
- claude_mpm/utils/error_handler.py +75 -55
- claude_mpm/utils/file_utils.py +80 -67
- claude_mpm/utils/framework_detection.py +12 -11
- claude_mpm/utils/import_migration_example.py +12 -60
- claude_mpm/utils/imports.py +48 -45
- claude_mpm/utils/path_operations.py +100 -93
- claude_mpm/utils/robust_installer.py +172 -164
- claude_mpm/utils/session_logging.py +30 -23
- claude_mpm/utils/subprocess_utils.py +99 -61
- claude_mpm/validation/__init__.py +1 -1
- claude_mpm/validation/agent_validator.py +151 -111
- claude_mpm/validation/frontmatter_validator.py +92 -71
- {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/METADATA +51 -2
- claude_mpm-4.0.3.dist-info/RECORD +402 -0
- {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
- {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/licenses/LICENSE +1 -1
- claude_mpm/config/memory_guardian_config.py +0 -325
- claude_mpm/core/config_paths.py +0 -150
- claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
- claude_mpm/deployment_paths.py +0 -261
- claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
- claude_mpm/models/state_models.py +0 -433
- claude_mpm/services/agent/__init__.py +0 -24
- claude_mpm/services/agent/deployment.py +0 -2548
- claude_mpm/services/agent/management.py +0 -598
- claude_mpm/services/agent/registry.py +0 -813
- claude_mpm/services/agents/registry/agent_registry.py +0 -813
- claude_mpm/services/communication/socketio.py +0 -1935
- claude_mpm/services/communication/websocket.py +0 -479
- claude_mpm/services/framework_claude_md_generator.py +0 -624
- claude_mpm/services/health_monitor.py +0 -893
- claude_mpm/services/infrastructure/memory_guardian.py +0 -770
- claude_mpm/services/mcp_gateway/server/mcp_server_simple.py +0 -444
- claude_mpm/services/optimized_hook_service.py +0 -542
- claude_mpm/services/project_analyzer.py +0 -864
- claude_mpm/services/project_registry.py +0 -608
- claude_mpm/services/standalone_socketio_server.py +0 -1300
- claude_mpm/services/ticket_manager_di.py +0 -318
- claude_mpm/services/ticketing_service_original.py +0 -510
- claude_mpm/utils/paths.py +0 -395
- claude_mpm/utils/platform_memory.py +0 -524
- claude_mpm-3.9.9.dist-info/RECORD +0 -293
- {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/WHEEL +0 -0
- {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/top_level.txt +0 -0
    
        claude_mpm/core/socketio_pool.py
    CHANGED
    
    | @@ -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  | 
| 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  | 
| 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 | 
            -
             | 
| 49 | 
            -
                 | 
| 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  | 
| 98 | 
            -
             | 
| 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( | 
| 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  | 
| 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( | 
| 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__( | 
| 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( | 
| 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,  | 
| 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,  | 
| 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( | 
| 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( | 
| 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 = [ | 
| 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( | 
| 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(( | 
| 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( | 
| 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  | 
| 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( | 
| 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( | 
| 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( | 
| 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 | 
            -
             | 
| 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 [ | 
| 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( | 
| 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( | 
| 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( | 
| 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 =  | 
| 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 | 
| 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( | 
| 704 | 
            -
             | 
| 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":  | 
| 710 | 
            -
             | 
| 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)
         |