claude-mpm 4.1.0__py3-none-any.whl → 4.1.2__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/BUILD_NUMBER +1 -1
- claude_mpm/VERSION +1 -1
- claude_mpm/__main__.py +1 -1
- claude_mpm/agents/BASE_PM.md +74 -46
- claude_mpm/agents/INSTRUCTIONS.md +11 -153
- claude_mpm/agents/WORKFLOW.md +61 -321
- claude_mpm/agents/__init__.py +11 -11
- claude_mpm/agents/agent_loader.py +23 -20
- claude_mpm/agents/agent_loader_integration.py +1 -1
- claude_mpm/agents/agents_metadata.py +27 -0
- claude_mpm/agents/async_agent_loader.py +5 -8
- claude_mpm/agents/base_agent_loader.py +36 -25
- claude_mpm/agents/frontmatter_validator.py +6 -6
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/system_agent_config.py +9 -9
- claude_mpm/agents/templates/api_qa.json +47 -2
- claude_mpm/agents/templates/imagemagick.json +256 -0
- claude_mpm/agents/templates/qa.json +41 -2
- claude_mpm/agents/templates/ticketing.json +5 -5
- claude_mpm/agents/templates/web_qa.json +133 -58
- claude_mpm/agents/templates/web_ui.json +3 -3
- claude_mpm/cli/__init__.py +51 -46
- claude_mpm/cli/__main__.py +1 -1
- claude_mpm/cli/commands/__init__.py +10 -12
- claude_mpm/cli/commands/agent_manager.py +186 -181
- claude_mpm/cli/commands/agents.py +271 -268
- claude_mpm/cli/commands/aggregate.py +30 -29
- claude_mpm/cli/commands/cleanup.py +50 -44
- claude_mpm/cli/commands/cleanup_orphaned_agents.py +25 -25
- claude_mpm/cli/commands/config.py +162 -127
- claude_mpm/cli/commands/doctor.py +52 -62
- claude_mpm/cli/commands/info.py +37 -25
- claude_mpm/cli/commands/mcp.py +3 -7
- claude_mpm/cli/commands/mcp_command_router.py +14 -18
- claude_mpm/cli/commands/mcp_install_commands.py +28 -23
- claude_mpm/cli/commands/mcp_pipx_config.py +58 -49
- claude_mpm/cli/commands/mcp_server_commands.py +23 -17
- claude_mpm/cli/commands/memory.py +192 -141
- claude_mpm/cli/commands/monitor.py +117 -88
- claude_mpm/cli/commands/run.py +120 -84
- claude_mpm/cli/commands/run_config_checker.py +4 -5
- claude_mpm/cli/commands/socketio_monitor.py +17 -19
- claude_mpm/cli/commands/tickets.py +92 -92
- claude_mpm/cli/parser.py +1 -5
- claude_mpm/cli/parsers/__init__.py +1 -1
- claude_mpm/cli/parsers/agent_manager_parser.py +50 -98
- claude_mpm/cli/parsers/agents_parser.py +2 -3
- claude_mpm/cli/parsers/base_parser.py +7 -5
- claude_mpm/cli/parsers/mcp_parser.py +4 -2
- claude_mpm/cli/parsers/monitor_parser.py +26 -18
- claude_mpm/cli/shared/__init__.py +10 -10
- claude_mpm/cli/shared/argument_patterns.py +57 -71
- claude_mpm/cli/shared/base_command.py +61 -53
- claude_mpm/cli/shared/error_handling.py +62 -58
- claude_mpm/cli/shared/output_formatters.py +78 -77
- claude_mpm/cli/startup_logging.py +204 -172
- claude_mpm/cli/utils.py +10 -11
- claude_mpm/cli_module/__init__.py +1 -1
- claude_mpm/cli_module/args.py +1 -1
- claude_mpm/cli_module/migration_example.py +5 -5
- claude_mpm/config/__init__.py +9 -9
- claude_mpm/config/agent_config.py +15 -14
- claude_mpm/config/experimental_features.py +4 -4
- claude_mpm/config/paths.py +0 -1
- claude_mpm/config/socketio_config.py +5 -6
- claude_mpm/constants.py +1 -2
- claude_mpm/core/__init__.py +8 -8
- claude_mpm/core/agent_name_normalizer.py +1 -1
- claude_mpm/core/agent_registry.py +20 -23
- claude_mpm/core/agent_session_manager.py +3 -3
- claude_mpm/core/base_service.py +7 -15
- claude_mpm/core/cache.py +4 -6
- claude_mpm/core/claude_runner.py +85 -113
- claude_mpm/core/config.py +43 -28
- claude_mpm/core/config_aliases.py +0 -9
- claude_mpm/core/config_constants.py +52 -30
- claude_mpm/core/constants.py +0 -1
- claude_mpm/core/container.py +18 -27
- claude_mpm/core/exceptions.py +2 -2
- claude_mpm/core/factories.py +10 -12
- claude_mpm/core/framework_loader.py +581 -280
- claude_mpm/core/hook_manager.py +26 -22
- claude_mpm/core/hook_performance_config.py +58 -47
- claude_mpm/core/injectable_service.py +1 -1
- claude_mpm/core/interactive_session.py +61 -152
- claude_mpm/core/interfaces.py +1 -100
- claude_mpm/core/lazy.py +5 -5
- claude_mpm/core/log_manager.py +587 -0
- claude_mpm/core/logger.py +125 -8
- claude_mpm/core/logging_config.py +15 -15
- claude_mpm/core/minimal_framework_loader.py +5 -8
- claude_mpm/core/oneshot_session.py +15 -33
- claude_mpm/core/optimized_agent_loader.py +4 -6
- claude_mpm/core/optimized_startup.py +2 -1
- claude_mpm/core/output_style_manager.py +147 -106
- claude_mpm/core/pm_hook_interceptor.py +0 -1
- claude_mpm/core/service_registry.py +11 -8
- claude_mpm/core/session_manager.py +1 -2
- claude_mpm/core/shared/__init__.py +1 -1
- claude_mpm/core/shared/config_loader.py +101 -97
- claude_mpm/core/shared/path_resolver.py +72 -68
- claude_mpm/core/shared/singleton_manager.py +56 -50
- claude_mpm/core/socketio_pool.py +26 -6
- claude_mpm/core/tool_access_control.py +4 -5
- claude_mpm/core/typing_utils.py +50 -59
- claude_mpm/core/unified_agent_registry.py +14 -19
- claude_mpm/core/unified_config.py +4 -6
- claude_mpm/core/unified_paths.py +197 -109
- claude_mpm/dashboard/open_dashboard.py +2 -4
- claude_mpm/experimental/cli_enhancements.py +51 -36
- claude_mpm/generators/agent_profile_generator.py +2 -4
- claude_mpm/hooks/base_hook.py +1 -2
- claude_mpm/hooks/claude_hooks/connection_pool.py +72 -26
- claude_mpm/hooks/claude_hooks/event_handlers.py +93 -38
- claude_mpm/hooks/claude_hooks/hook_handler.py +130 -76
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +104 -77
- claude_mpm/hooks/claude_hooks/memory_integration.py +2 -4
- claude_mpm/hooks/claude_hooks/response_tracking.py +15 -11
- claude_mpm/hooks/claude_hooks/tool_analysis.py +12 -18
- claude_mpm/hooks/memory_integration_hook.py +5 -5
- claude_mpm/hooks/tool_call_interceptor.py +1 -1
- claude_mpm/hooks/validation_hooks.py +4 -4
- claude_mpm/init.py +4 -9
- claude_mpm/models/__init__.py +2 -2
- claude_mpm/models/agent_session.py +11 -14
- claude_mpm/scripts/mcp_server.py +20 -11
- claude_mpm/scripts/mcp_wrapper.py +5 -5
- claude_mpm/scripts/mpm_doctor.py +321 -0
- claude_mpm/scripts/socketio_daemon.py +28 -25
- claude_mpm/scripts/socketio_daemon_hardened.py +298 -258
- claude_mpm/scripts/socketio_server_manager.py +116 -95
- claude_mpm/services/__init__.py +49 -49
- claude_mpm/services/agent_capabilities_service.py +12 -18
- claude_mpm/services/agents/__init__.py +22 -22
- claude_mpm/services/agents/agent_builder.py +140 -119
- claude_mpm/services/agents/deployment/__init__.py +3 -3
- claude_mpm/services/agents/deployment/agent_config_provider.py +9 -9
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +19 -20
- claude_mpm/services/agents/deployment/agent_definition_factory.py +1 -5
- claude_mpm/services/agents/deployment/agent_deployment.py +136 -106
- claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -8
- claude_mpm/services/agents/deployment/agent_environment_manager.py +2 -7
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +6 -10
- claude_mpm/services/agents/deployment/agent_format_converter.py +11 -15
- claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +2 -3
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +5 -5
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +13 -19
- claude_mpm/services/agents/deployment/agent_restore_handler.py +0 -1
- claude_mpm/services/agents/deployment/agent_template_builder.py +26 -35
- claude_mpm/services/agents/deployment/agent_validator.py +0 -1
- claude_mpm/services/agents/deployment/agent_version_manager.py +7 -9
- claude_mpm/services/agents/deployment/agent_versioning.py +3 -3
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +6 -7
- claude_mpm/services/agents/deployment/async_agent_deployment.py +51 -38
- claude_mpm/services/agents/deployment/config/__init__.py +1 -1
- claude_mpm/services/agents/deployment/config/deployment_config.py +7 -8
- claude_mpm/services/agents/deployment/deployment_type_detector.py +1 -1
- claude_mpm/services/agents/deployment/deployment_wrapper.py +18 -18
- claude_mpm/services/agents/deployment/facade/__init__.py +1 -1
- claude_mpm/services/agents/deployment/facade/deployment_executor.py +0 -3
- claude_mpm/services/agents/deployment/facade/deployment_facade.py +3 -4
- claude_mpm/services/agents/deployment/interface_adapter.py +5 -7
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +345 -276
- claude_mpm/services/agents/deployment/pipeline/__init__.py +2 -2
- claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +6 -4
- claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +3 -3
- claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +2 -2
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +14 -13
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +0 -1
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +8 -9
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +1 -1
- claude_mpm/services/agents/deployment/processors/__init__.py +1 -1
- claude_mpm/services/agents/deployment/processors/agent_processor.py +20 -16
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +5 -12
- claude_mpm/services/agents/deployment/results/__init__.py +1 -1
- claude_mpm/services/agents/deployment/results/deployment_result_builder.py +1 -1
- claude_mpm/services/agents/deployment/strategies/__init__.py +2 -2
- claude_mpm/services/agents/deployment/strategies/base_strategy.py +1 -7
- claude_mpm/services/agents/deployment/strategies/project_strategy.py +1 -4
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +2 -3
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +3 -7
- claude_mpm/services/agents/deployment/validation/__init__.py +1 -1
- claude_mpm/services/agents/deployment/validation/agent_validator.py +1 -1
- claude_mpm/services/agents/deployment/validation/template_validator.py +2 -2
- claude_mpm/services/agents/deployment/validation/validation_result.py +2 -6
- claude_mpm/services/agents/loading/__init__.py +1 -1
- claude_mpm/services/agents/loading/agent_profile_loader.py +6 -12
- claude_mpm/services/agents/loading/base_agent_manager.py +5 -5
- claude_mpm/services/agents/loading/framework_agent_loader.py +2 -4
- claude_mpm/services/agents/management/__init__.py +1 -1
- claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -3
- claude_mpm/services/agents/management/agent_management_service.py +5 -9
- claude_mpm/services/agents/memory/__init__.py +4 -4
- claude_mpm/services/agents/memory/agent_memory_manager.py +280 -160
- claude_mpm/services/agents/memory/agent_persistence_service.py +0 -2
- claude_mpm/services/agents/memory/content_manager.py +44 -38
- claude_mpm/services/agents/memory/template_generator.py +4 -6
- claude_mpm/services/agents/registry/__init__.py +10 -6
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +30 -27
- claude_mpm/services/agents/registry/modification_tracker.py +3 -6
- claude_mpm/services/async_session_logger.py +1 -2
- claude_mpm/services/claude_session_logger.py +1 -2
- claude_mpm/services/command_deployment_service.py +173 -0
- claude_mpm/services/command_handler_service.py +20 -22
- claude_mpm/services/core/__init__.py +25 -25
- claude_mpm/services/core/base.py +0 -5
- claude_mpm/services/core/interfaces/__init__.py +32 -32
- claude_mpm/services/core/interfaces/agent.py +0 -21
- claude_mpm/services/core/interfaces/communication.py +0 -27
- claude_mpm/services/core/interfaces/infrastructure.py +0 -56
- claude_mpm/services/core/interfaces/service.py +0 -29
- claude_mpm/services/diagnostics/__init__.py +1 -1
- claude_mpm/services/diagnostics/checks/__init__.py +6 -6
- claude_mpm/services/diagnostics/checks/agent_check.py +89 -80
- claude_mpm/services/diagnostics/checks/base_check.py +12 -16
- claude_mpm/services/diagnostics/checks/claude_desktop_check.py +84 -81
- claude_mpm/services/diagnostics/checks/common_issues_check.py +99 -91
- claude_mpm/services/diagnostics/checks/configuration_check.py +82 -77
- claude_mpm/services/diagnostics/checks/filesystem_check.py +67 -68
- claude_mpm/services/diagnostics/checks/installation_check.py +254 -94
- claude_mpm/services/diagnostics/checks/mcp_check.py +90 -88
- claude_mpm/services/diagnostics/checks/monitor_check.py +75 -76
- claude_mpm/services/diagnostics/checks/startup_log_check.py +67 -73
- claude_mpm/services/diagnostics/diagnostic_runner.py +67 -59
- claude_mpm/services/diagnostics/doctor_reporter.py +107 -70
- claude_mpm/services/diagnostics/models.py +21 -19
- claude_mpm/services/event_aggregator.py +10 -17
- claude_mpm/services/event_bus/__init__.py +1 -1
- claude_mpm/services/event_bus/config.py +54 -35
- claude_mpm/services/event_bus/event_bus.py +76 -71
- claude_mpm/services/event_bus/relay.py +74 -64
- claude_mpm/services/events/__init__.py +11 -11
- claude_mpm/services/events/consumers/__init__.py +3 -3
- claude_mpm/services/events/consumers/dead_letter.py +71 -63
- claude_mpm/services/events/consumers/logging.py +39 -37
- claude_mpm/services/events/consumers/metrics.py +56 -57
- claude_mpm/services/events/consumers/socketio.py +82 -81
- claude_mpm/services/events/core.py +110 -99
- claude_mpm/services/events/interfaces.py +56 -72
- claude_mpm/services/events/producers/__init__.py +1 -1
- claude_mpm/services/events/producers/hook.py +38 -38
- claude_mpm/services/events/producers/system.py +46 -44
- claude_mpm/services/exceptions.py +81 -80
- claude_mpm/services/framework_claude_md_generator/__init__.py +2 -4
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +3 -5
- claude_mpm/services/framework_claude_md_generator/content_validator.py +1 -1
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +4 -4
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +0 -1
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +0 -2
- claude_mpm/services/framework_claude_md_generator/version_manager.py +4 -5
- claude_mpm/services/hook_service.py +6 -9
- claude_mpm/services/infrastructure/__init__.py +1 -1
- claude_mpm/services/infrastructure/context_preservation.py +8 -12
- claude_mpm/services/infrastructure/monitoring.py +21 -23
- claude_mpm/services/mcp_gateway/__init__.py +37 -37
- claude_mpm/services/mcp_gateway/auto_configure.py +95 -103
- claude_mpm/services/mcp_gateway/config/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/config/config_loader.py +23 -25
- claude_mpm/services/mcp_gateway/config/config_schema.py +5 -5
- claude_mpm/services/mcp_gateway/config/configuration.py +9 -6
- claude_mpm/services/mcp_gateway/core/__init__.py +10 -10
- claude_mpm/services/mcp_gateway/core/base.py +0 -3
- claude_mpm/services/mcp_gateway/core/interfaces.py +1 -38
- claude_mpm/services/mcp_gateway/core/process_pool.py +99 -93
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +65 -62
- claude_mpm/services/mcp_gateway/core/startup_verification.py +75 -74
- claude_mpm/services/mcp_gateway/main.py +2 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +5 -8
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +1 -1
- claude_mpm/services/mcp_gateway/server/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +12 -19
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +4 -3
- claude_mpm/services/mcp_gateway/server/stdio_server.py +79 -71
- claude_mpm/services/mcp_gateway/tools/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +5 -6
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +13 -22
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +79 -78
- claude_mpm/services/mcp_gateway/tools/hello_world.py +12 -14
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +42 -49
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +51 -55
- claude_mpm/services/memory/__init__.py +3 -3
- claude_mpm/services/memory/builder.py +3 -6
- claude_mpm/services/memory/cache/__init__.py +1 -1
- claude_mpm/services/memory/cache/shared_prompt_cache.py +3 -5
- claude_mpm/services/memory/cache/simple_cache.py +1 -1
- claude_mpm/services/memory/indexed_memory.py +5 -7
- claude_mpm/services/memory/optimizer.py +7 -10
- claude_mpm/services/memory/router.py +8 -9
- claude_mpm/services/memory_hook_service.py +48 -34
- claude_mpm/services/monitor_build_service.py +77 -73
- claude_mpm/services/port_manager.py +130 -108
- claude_mpm/services/project/analyzer.py +12 -10
- claude_mpm/services/project/registry.py +11 -11
- claude_mpm/services/recovery_manager.py +10 -19
- claude_mpm/services/response_tracker.py +0 -1
- claude_mpm/services/runner_configuration_service.py +19 -20
- claude_mpm/services/session_management_service.py +7 -11
- claude_mpm/services/shared/__init__.py +1 -1
- claude_mpm/services/shared/async_service_base.py +58 -50
- claude_mpm/services/shared/config_service_base.py +73 -67
- claude_mpm/services/shared/lifecycle_service_base.py +82 -78
- claude_mpm/services/shared/manager_base.py +94 -82
- claude_mpm/services/shared/service_factory.py +96 -98
- claude_mpm/services/socketio/__init__.py +3 -3
- claude_mpm/services/socketio/client_proxy.py +5 -5
- claude_mpm/services/socketio/event_normalizer.py +199 -181
- claude_mpm/services/socketio/handlers/__init__.py +3 -3
- claude_mpm/services/socketio/handlers/base.py +5 -4
- claude_mpm/services/socketio/handlers/connection.py +163 -136
- claude_mpm/services/socketio/handlers/file.py +13 -14
- claude_mpm/services/socketio/handlers/git.py +12 -7
- claude_mpm/services/socketio/handlers/hook.py +49 -44
- claude_mpm/services/socketio/handlers/memory.py +0 -1
- claude_mpm/services/socketio/handlers/project.py +0 -1
- claude_mpm/services/socketio/handlers/registry.py +37 -19
- claude_mpm/services/socketio/migration_utils.py +98 -84
- claude_mpm/services/socketio/server/__init__.py +1 -1
- claude_mpm/services/socketio/server/broadcaster.py +81 -87
- claude_mpm/services/socketio/server/core.py +65 -54
- claude_mpm/services/socketio/server/eventbus_integration.py +95 -56
- claude_mpm/services/socketio/server/main.py +64 -38
- claude_mpm/services/socketio_client_manager.py +10 -12
- claude_mpm/services/subprocess_launcher_service.py +4 -7
- claude_mpm/services/system_instructions_service.py +13 -14
- claude_mpm/services/ticket_manager.py +2 -2
- claude_mpm/services/utility_service.py +5 -13
- claude_mpm/services/version_control/__init__.py +16 -16
- claude_mpm/services/version_control/branch_strategy.py +5 -8
- claude_mpm/services/version_control/conflict_resolution.py +9 -23
- claude_mpm/services/version_control/git_operations.py +5 -7
- claude_mpm/services/version_control/semantic_versioning.py +16 -17
- claude_mpm/services/version_control/version_parser.py +13 -18
- claude_mpm/services/version_service.py +10 -11
- claude_mpm/storage/__init__.py +1 -1
- claude_mpm/storage/state_storage.py +22 -28
- claude_mpm/utils/__init__.py +6 -6
- claude_mpm/utils/agent_dependency_loader.py +47 -33
- claude_mpm/utils/config_manager.py +11 -14
- claude_mpm/utils/dependency_cache.py +1 -1
- claude_mpm/utils/dependency_manager.py +13 -17
- claude_mpm/utils/dependency_strategies.py +8 -10
- claude_mpm/utils/environment_context.py +3 -9
- claude_mpm/utils/error_handler.py +3 -13
- claude_mpm/utils/file_utils.py +1 -1
- claude_mpm/utils/path_operations.py +8 -12
- claude_mpm/utils/robust_installer.py +110 -33
- claude_mpm/utils/subprocess_utils.py +5 -6
- claude_mpm/validation/agent_validator.py +3 -6
- claude_mpm/validation/frontmatter_validator.py +1 -1
- {claude_mpm-4.1.0.dist-info → claude_mpm-4.1.2.dist-info}/METADATA +1 -1
- claude_mpm-4.1.2.dist-info/RECORD +498 -0
- claude_mpm-4.1.0.dist-info/RECORD +0 -494
- {claude_mpm-4.1.0.dist-info → claude_mpm-4.1.2.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.0.dist-info → claude_mpm-4.1.2.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.0.dist-info → claude_mpm-4.1.2.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.0.dist-info → claude_mpm-4.1.2.dist-info}/top_level.txt +0 -0
|
@@ -18,7 +18,7 @@ from ..interfaces import ConsumerConfig, ConsumerPriority, IEventConsumer
|
|
|
18
18
|
class MetricsConsumer(IEventConsumer):
|
|
19
19
|
"""
|
|
20
20
|
Collects metrics and statistics from events.
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
Features:
|
|
23
23
|
- Event counting by topic and type
|
|
24
24
|
- Rate calculation (events per second)
|
|
@@ -26,7 +26,7 @@ class MetricsConsumer(IEventConsumer):
|
|
|
26
26
|
- Top event analysis
|
|
27
27
|
- Time-windowed statistics
|
|
28
28
|
"""
|
|
29
|
-
|
|
29
|
+
|
|
30
30
|
def __init__(
|
|
31
31
|
self,
|
|
32
32
|
window_size: int = 300, # 5 minutes
|
|
@@ -35,36 +35,36 @@ class MetricsConsumer(IEventConsumer):
|
|
|
35
35
|
):
|
|
36
36
|
"""
|
|
37
37
|
Initialize metrics consumer.
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
Args:
|
|
40
40
|
window_size: Time window for statistics (seconds)
|
|
41
41
|
top_n: Number of top events to track
|
|
42
42
|
report_interval: How often to report metrics (seconds)
|
|
43
43
|
"""
|
|
44
44
|
self.logger = get_logger("MetricsConsumer")
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
# Configuration
|
|
47
47
|
self.window_size = window_size
|
|
48
48
|
self.top_n = top_n
|
|
49
49
|
self.report_interval = report_interval
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
# State
|
|
52
52
|
self._initialized = False
|
|
53
53
|
self._last_report_time = time.time()
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
# Metrics storage
|
|
56
56
|
self._event_counts: Dict[str, int] = defaultdict(int)
|
|
57
57
|
self._topic_counts: Dict[str, int] = defaultdict(int)
|
|
58
58
|
self._type_counts: Dict[str, int] = defaultdict(int)
|
|
59
59
|
self._source_counts: Dict[str, int] = defaultdict(int)
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
# Time-windowed metrics
|
|
62
62
|
self._recent_events: Deque[tuple] = deque() # (timestamp, topic, type)
|
|
63
63
|
self._latencies: Deque[float] = deque(maxlen=1000)
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
# Error tracking
|
|
66
66
|
self._error_counts: Dict[str, int] = defaultdict(int)
|
|
67
|
-
|
|
67
|
+
|
|
68
68
|
# Performance metrics
|
|
69
69
|
self._metrics = {
|
|
70
70
|
"total_events": 0,
|
|
@@ -74,64 +74,64 @@ class MetricsConsumer(IEventConsumer):
|
|
|
74
74
|
"unique_topics": 0,
|
|
75
75
|
"unique_types": 0,
|
|
76
76
|
}
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
# Consumer configuration
|
|
79
79
|
self._config = ConsumerConfig(
|
|
80
80
|
name="MetricsConsumer",
|
|
81
81
|
topics=["**"], # Monitor all events
|
|
82
82
|
priority=ConsumerPriority.DEFERRED, # Process after other consumers
|
|
83
83
|
)
|
|
84
|
-
|
|
84
|
+
|
|
85
85
|
async def initialize(self) -> bool:
|
|
86
86
|
"""Initialize the metrics consumer."""
|
|
87
87
|
self._initialized = True
|
|
88
88
|
self.logger.info("Metrics consumer initialized")
|
|
89
89
|
return True
|
|
90
|
-
|
|
90
|
+
|
|
91
91
|
async def consume(self, event: Event) -> bool:
|
|
92
92
|
"""Process a single event for metrics."""
|
|
93
93
|
if not self._initialized:
|
|
94
94
|
return False
|
|
95
|
-
|
|
95
|
+
|
|
96
96
|
try:
|
|
97
97
|
current_time = time.time()
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
# Update counts
|
|
100
100
|
self._event_counts[f"{event.topic}:{event.type}"] += 1
|
|
101
101
|
self._topic_counts[event.topic] += 1
|
|
102
102
|
self._type_counts[event.type] += 1
|
|
103
103
|
self._source_counts[event.source] += 1
|
|
104
104
|
self._metrics["total_events"] += 1
|
|
105
|
-
|
|
105
|
+
|
|
106
106
|
# Track errors
|
|
107
107
|
if event.metadata and event.metadata.consumers_failed:
|
|
108
108
|
for consumer in event.metadata.consumers_failed:
|
|
109
109
|
self._error_counts[consumer] += 1
|
|
110
|
-
|
|
110
|
+
|
|
111
111
|
# Add to recent events
|
|
112
112
|
self._recent_events.append((current_time, event.topic, event.type))
|
|
113
|
-
|
|
113
|
+
|
|
114
114
|
# Calculate latency if timestamp available
|
|
115
115
|
if event.timestamp:
|
|
116
116
|
latency = (current_time - event.timestamp.timestamp()) * 1000
|
|
117
117
|
self._latencies.append(latency)
|
|
118
|
-
|
|
118
|
+
|
|
119
119
|
# Clean old events from window
|
|
120
120
|
cutoff_time = current_time - self.window_size
|
|
121
121
|
while self._recent_events and self._recent_events[0][0] < cutoff_time:
|
|
122
122
|
self._recent_events.popleft()
|
|
123
|
-
|
|
123
|
+
|
|
124
124
|
# Report metrics periodically
|
|
125
125
|
if current_time - self._last_report_time >= self.report_interval:
|
|
126
126
|
await self._report_metrics()
|
|
127
127
|
self._last_report_time = current_time
|
|
128
|
-
|
|
128
|
+
|
|
129
129
|
return True
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
except Exception as e:
|
|
132
132
|
self.logger.error(f"Error processing event for metrics: {e}")
|
|
133
133
|
return False
|
|
134
|
-
|
|
134
|
+
|
|
135
135
|
async def consume_batch(self, events: List[Event]) -> int:
|
|
136
136
|
"""Process multiple events."""
|
|
137
137
|
successful = 0
|
|
@@ -139,45 +139,41 @@ class MetricsConsumer(IEventConsumer):
|
|
|
139
139
|
if await self.consume(event):
|
|
140
140
|
successful += 1
|
|
141
141
|
return successful
|
|
142
|
-
|
|
142
|
+
|
|
143
143
|
async def shutdown(self) -> None:
|
|
144
144
|
"""Shutdown the consumer."""
|
|
145
145
|
# Report final metrics
|
|
146
146
|
await self._report_metrics()
|
|
147
|
-
|
|
147
|
+
|
|
148
148
|
self.logger.info(
|
|
149
149
|
f"Metrics consumer shutdown - processed {self._metrics['total_events']} events"
|
|
150
150
|
)
|
|
151
151
|
self._initialized = False
|
|
152
|
-
|
|
152
|
+
|
|
153
153
|
@property
|
|
154
154
|
def config(self) -> ConsumerConfig:
|
|
155
155
|
"""Get consumer configuration."""
|
|
156
156
|
return self._config
|
|
157
|
-
|
|
157
|
+
|
|
158
158
|
@property
|
|
159
159
|
def is_healthy(self) -> bool:
|
|
160
160
|
"""Check if consumer is healthy."""
|
|
161
161
|
return self._initialized
|
|
162
|
-
|
|
162
|
+
|
|
163
163
|
def get_metrics(self) -> Dict[str, Any]:
|
|
164
164
|
"""Get consumer metrics."""
|
|
165
165
|
# Calculate current metrics
|
|
166
166
|
self._calculate_metrics()
|
|
167
|
-
|
|
167
|
+
|
|
168
168
|
# Get top events
|
|
169
169
|
top_events = sorted(
|
|
170
|
-
self._event_counts.items(),
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
)[:self.top_n]
|
|
174
|
-
|
|
170
|
+
self._event_counts.items(), key=lambda x: x[1], reverse=True
|
|
171
|
+
)[: self.top_n]
|
|
172
|
+
|
|
175
173
|
top_topics = sorted(
|
|
176
|
-
self._topic_counts.items(),
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
)[:self.top_n]
|
|
180
|
-
|
|
174
|
+
self._topic_counts.items(), key=lambda x: x[1], reverse=True
|
|
175
|
+
)[: self.top_n]
|
|
176
|
+
|
|
181
177
|
return {
|
|
182
178
|
**self._metrics,
|
|
183
179
|
"top_events": dict(top_events),
|
|
@@ -185,58 +181,61 @@ class MetricsConsumer(IEventConsumer):
|
|
|
185
181
|
"error_counts": dict(self._error_counts),
|
|
186
182
|
"window_size_seconds": self.window_size,
|
|
187
183
|
}
|
|
188
|
-
|
|
184
|
+
|
|
189
185
|
def _calculate_metrics(self) -> None:
|
|
190
186
|
"""Calculate current metrics."""
|
|
191
187
|
# Events per second
|
|
192
188
|
if self._recent_events:
|
|
193
189
|
time_span = time.time() - self._recent_events[0][0]
|
|
194
190
|
if time_span > 0:
|
|
195
|
-
self._metrics["events_per_second"] =
|
|
196
|
-
|
|
191
|
+
self._metrics["events_per_second"] = (
|
|
192
|
+
len(self._recent_events) / time_span
|
|
193
|
+
)
|
|
194
|
+
|
|
197
195
|
# Average latency
|
|
198
196
|
if self._latencies:
|
|
199
|
-
self._metrics["average_latency_ms"] = sum(self._latencies) / len(
|
|
200
|
-
|
|
197
|
+
self._metrics["average_latency_ms"] = sum(self._latencies) / len(
|
|
198
|
+
self._latencies
|
|
199
|
+
)
|
|
200
|
+
|
|
201
201
|
# Unique counts
|
|
202
202
|
self._metrics["unique_topics"] = len(self._topic_counts)
|
|
203
203
|
self._metrics["unique_types"] = len(self._type_counts)
|
|
204
|
-
|
|
204
|
+
|
|
205
205
|
# Peak rate
|
|
206
|
-
|
|
207
|
-
self._metrics["peak_rate"]
|
|
208
|
-
|
|
206
|
+
self._metrics["peak_rate"] = max(
|
|
207
|
+
self._metrics["peak_rate"], self._metrics["events_per_second"]
|
|
208
|
+
)
|
|
209
|
+
|
|
209
210
|
async def _report_metrics(self) -> None:
|
|
210
211
|
"""Report current metrics to log."""
|
|
211
212
|
self._calculate_metrics()
|
|
212
|
-
|
|
213
|
+
|
|
213
214
|
# Build report
|
|
214
215
|
report = [
|
|
215
|
-
|
|
216
|
+
"=== Event Metrics Report ===",
|
|
216
217
|
f"Total Events: {self._metrics['total_events']}",
|
|
217
218
|
f"Rate: {self._metrics['events_per_second']:.2f} events/sec",
|
|
218
219
|
f"Avg Latency: {self._metrics['average_latency_ms']:.1f}ms",
|
|
219
220
|
f"Unique Topics: {self._metrics['unique_topics']}",
|
|
220
221
|
f"Unique Types: {self._metrics['unique_types']}",
|
|
221
222
|
]
|
|
222
|
-
|
|
223
|
+
|
|
223
224
|
# Add top events
|
|
224
225
|
top_events = sorted(
|
|
225
|
-
self._event_counts.items(),
|
|
226
|
-
key=lambda x: x[1],
|
|
227
|
-
reverse=True
|
|
226
|
+
self._event_counts.items(), key=lambda x: x[1], reverse=True
|
|
228
227
|
)[:5]
|
|
229
|
-
|
|
228
|
+
|
|
230
229
|
if top_events:
|
|
231
230
|
report.append("\nTop Events:")
|
|
232
231
|
for event_key, count in top_events:
|
|
233
232
|
report.append(f" {event_key}: {count}")
|
|
234
|
-
|
|
233
|
+
|
|
235
234
|
# Add error summary
|
|
236
235
|
if self._error_counts:
|
|
237
236
|
report.append("\nErrors by Consumer:")
|
|
238
237
|
for consumer, count in self._error_counts.items():
|
|
239
238
|
report.append(f" {consumer}: {count}")
|
|
240
|
-
|
|
239
|
+
|
|
241
240
|
# Log report
|
|
242
|
-
self.logger.info("\n".join(report))
|
|
241
|
+
self.logger.info("\n".join(report))
|
|
@@ -8,6 +8,7 @@ the rest of the system from the transport layer.
|
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
|
+
import contextlib
|
|
11
12
|
import time
|
|
12
13
|
from typing import Any, Dict, List, Optional
|
|
13
14
|
|
|
@@ -20,7 +21,7 @@ from ..interfaces import ConsumerConfig, ConsumerPriority, IEventConsumer
|
|
|
20
21
|
class SocketIOConsumer(IEventConsumer):
|
|
21
22
|
"""
|
|
22
23
|
Consumes events and emits them via Socket.IO.
|
|
23
|
-
|
|
24
|
+
|
|
24
25
|
Features:
|
|
25
26
|
- Single Socket.IO connection management
|
|
26
27
|
- Automatic reconnection with backoff
|
|
@@ -28,7 +29,7 @@ class SocketIOConsumer(IEventConsumer):
|
|
|
28
29
|
- Connection health monitoring
|
|
29
30
|
- Graceful degradation when Socket.IO unavailable
|
|
30
31
|
"""
|
|
31
|
-
|
|
32
|
+
|
|
32
33
|
def __init__(
|
|
33
34
|
self,
|
|
34
35
|
socketio_server=None,
|
|
@@ -40,7 +41,7 @@ class SocketIOConsumer(IEventConsumer):
|
|
|
40
41
|
):
|
|
41
42
|
"""
|
|
42
43
|
Initialize Socket.IO consumer.
|
|
43
|
-
|
|
44
|
+
|
|
44
45
|
Args:
|
|
45
46
|
socketio_server: Socket.IO server instance (optional)
|
|
46
47
|
port_range: Port range to try for connection
|
|
@@ -50,25 +51,25 @@ class SocketIOConsumer(IEventConsumer):
|
|
|
50
51
|
batch_timeout: Max time to wait for batch
|
|
51
52
|
"""
|
|
52
53
|
self.logger = get_logger("SocketIOConsumer")
|
|
53
|
-
|
|
54
|
+
|
|
54
55
|
# Socket.IO configuration
|
|
55
56
|
self.socketio_server = socketio_server
|
|
56
57
|
self.port_range = port_range
|
|
57
58
|
self.reconnect_delay = reconnect_delay
|
|
58
59
|
self.max_reconnect_delay = max_reconnect_delay
|
|
59
60
|
self.current_reconnect_delay = reconnect_delay
|
|
60
|
-
|
|
61
|
+
|
|
61
62
|
# Batching configuration
|
|
62
63
|
self.batch_size = batch_size
|
|
63
64
|
self.batch_timeout = batch_timeout
|
|
64
|
-
|
|
65
|
+
|
|
65
66
|
# State
|
|
66
67
|
self._initialized = False
|
|
67
68
|
self._connected = False
|
|
68
69
|
self._reconnect_task: Optional[asyncio.Task] = None
|
|
69
70
|
self._event_batch: List[Event] = []
|
|
70
71
|
self._batch_timer: Optional[asyncio.Task] = None
|
|
71
|
-
|
|
72
|
+
|
|
72
73
|
# Metrics
|
|
73
74
|
self._metrics = {
|
|
74
75
|
"events_received": 0,
|
|
@@ -79,7 +80,7 @@ class SocketIOConsumer(IEventConsumer):
|
|
|
79
80
|
"last_emit_time": None,
|
|
80
81
|
"average_emit_time_ms": 0,
|
|
81
82
|
}
|
|
82
|
-
|
|
83
|
+
|
|
83
84
|
# Consumer configuration
|
|
84
85
|
self._config = ConsumerConfig(
|
|
85
86
|
name="SocketIOConsumer",
|
|
@@ -90,114 +91,113 @@ class SocketIOConsumer(IEventConsumer):
|
|
|
90
91
|
max_retries=3,
|
|
91
92
|
retry_backoff=2.0,
|
|
92
93
|
)
|
|
93
|
-
|
|
94
|
+
|
|
94
95
|
async def initialize(self) -> bool:
|
|
95
96
|
"""Initialize the Socket.IO consumer."""
|
|
96
97
|
if self._initialized:
|
|
97
98
|
return True
|
|
98
|
-
|
|
99
|
+
|
|
99
100
|
self.logger.info("Initializing Socket.IO consumer")
|
|
100
|
-
|
|
101
|
+
|
|
101
102
|
# Try to import socketio if not provided
|
|
102
103
|
if self.socketio_server is None:
|
|
103
104
|
try:
|
|
104
105
|
# Try to get existing server instance
|
|
105
106
|
from claude_mpm.services.socketio.server import get_socketio_server
|
|
107
|
+
|
|
106
108
|
self.socketio_server = get_socketio_server()
|
|
107
|
-
|
|
109
|
+
|
|
108
110
|
if self.socketio_server:
|
|
109
111
|
self._connected = True
|
|
110
112
|
self.logger.info("Connected to existing Socket.IO server")
|
|
111
|
-
|
|
113
|
+
|
|
112
114
|
except ImportError:
|
|
113
115
|
self.logger.warning("Socket.IO server not available")
|
|
114
116
|
# Continue without Socket.IO - events will be dropped
|
|
115
|
-
|
|
117
|
+
|
|
116
118
|
self._initialized = True
|
|
117
|
-
|
|
119
|
+
|
|
118
120
|
# Start reconnection task if not connected
|
|
119
121
|
if not self._connected and self.socketio_server:
|
|
120
122
|
self._reconnect_task = asyncio.create_task(self._reconnect_loop())
|
|
121
|
-
|
|
123
|
+
|
|
122
124
|
return True
|
|
123
|
-
|
|
125
|
+
|
|
124
126
|
async def consume(self, event: Event) -> bool:
|
|
125
127
|
"""
|
|
126
128
|
Process a single event.
|
|
127
|
-
|
|
129
|
+
|
|
128
130
|
Events are batched for efficiency and emitted via Socket.IO.
|
|
129
131
|
"""
|
|
130
132
|
if not self._initialized:
|
|
131
133
|
self.logger.warning("Consumer not initialized")
|
|
132
134
|
return False
|
|
133
|
-
|
|
135
|
+
|
|
134
136
|
self._metrics["events_received"] += 1
|
|
135
|
-
|
|
137
|
+
|
|
136
138
|
# Add to batch
|
|
137
139
|
self._event_batch.append(event)
|
|
138
|
-
|
|
140
|
+
|
|
139
141
|
# Process batch if full
|
|
140
142
|
if len(self._event_batch) >= self.batch_size:
|
|
141
143
|
return await self._flush_batch()
|
|
142
|
-
|
|
144
|
+
|
|
143
145
|
# Start batch timer if not running
|
|
144
146
|
if self._batch_timer is None or self._batch_timer.done():
|
|
145
147
|
self._batch_timer = asyncio.create_task(self._batch_timeout_handler())
|
|
146
|
-
|
|
148
|
+
|
|
147
149
|
return True
|
|
148
|
-
|
|
150
|
+
|
|
149
151
|
async def consume_batch(self, events: List[Event]) -> int:
|
|
150
152
|
"""Process multiple events in a batch."""
|
|
151
153
|
if not self._initialized:
|
|
152
154
|
return 0
|
|
153
|
-
|
|
155
|
+
|
|
154
156
|
successful = 0
|
|
155
157
|
for event in events:
|
|
156
158
|
if await self.consume(event):
|
|
157
159
|
successful += 1
|
|
158
|
-
|
|
160
|
+
|
|
159
161
|
return successful
|
|
160
|
-
|
|
162
|
+
|
|
161
163
|
async def shutdown(self) -> None:
|
|
162
164
|
"""Shutdown the consumer gracefully."""
|
|
163
165
|
self.logger.info("Shutting down Socket.IO consumer")
|
|
164
|
-
|
|
166
|
+
|
|
165
167
|
# Cancel reconnection task
|
|
166
168
|
if self._reconnect_task:
|
|
167
169
|
self._reconnect_task.cancel()
|
|
168
|
-
|
|
170
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
169
171
|
await self._reconnect_task
|
|
170
|
-
|
|
171
|
-
pass
|
|
172
|
-
|
|
172
|
+
|
|
173
173
|
# Cancel batch timer
|
|
174
174
|
if self._batch_timer:
|
|
175
175
|
self._batch_timer.cancel()
|
|
176
|
-
|
|
176
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
177
177
|
await self._batch_timer
|
|
178
|
-
|
|
179
|
-
pass
|
|
180
|
-
|
|
178
|
+
|
|
181
179
|
# Flush remaining events
|
|
182
180
|
if self._event_batch:
|
|
183
181
|
await self._flush_batch()
|
|
184
|
-
|
|
182
|
+
|
|
185
183
|
self._initialized = False
|
|
186
184
|
self._connected = False
|
|
187
|
-
|
|
185
|
+
|
|
188
186
|
self.logger.info("Socket.IO consumer shutdown complete")
|
|
189
|
-
|
|
187
|
+
|
|
190
188
|
@property
|
|
191
189
|
def config(self) -> ConsumerConfig:
|
|
192
190
|
"""Get consumer configuration."""
|
|
193
191
|
return self._config
|
|
194
|
-
|
|
192
|
+
|
|
195
193
|
@property
|
|
196
194
|
def is_healthy(self) -> bool:
|
|
197
195
|
"""Check if consumer is healthy."""
|
|
198
196
|
# Healthy if initialized and either connected or attempting to reconnect
|
|
199
|
-
return self._initialized and (
|
|
200
|
-
|
|
197
|
+
return self._initialized and (
|
|
198
|
+
self._connected or self._reconnect_task is not None
|
|
199
|
+
)
|
|
200
|
+
|
|
201
201
|
def get_metrics(self) -> Dict[str, Any]:
|
|
202
202
|
"""Get consumer metrics."""
|
|
203
203
|
return {
|
|
@@ -205,42 +205,42 @@ class SocketIOConsumer(IEventConsumer):
|
|
|
205
205
|
"connected": self._connected,
|
|
206
206
|
"batch_size": len(self._event_batch),
|
|
207
207
|
}
|
|
208
|
-
|
|
208
|
+
|
|
209
209
|
async def _flush_batch(self) -> bool:
|
|
210
210
|
"""
|
|
211
211
|
Flush the current batch of events to Socket.IO.
|
|
212
|
-
|
|
212
|
+
|
|
213
213
|
Returns:
|
|
214
214
|
True if all events emitted successfully
|
|
215
215
|
"""
|
|
216
216
|
if not self._event_batch:
|
|
217
217
|
return True
|
|
218
|
-
|
|
218
|
+
|
|
219
219
|
batch = self._event_batch
|
|
220
220
|
self._event_batch = []
|
|
221
|
-
|
|
221
|
+
|
|
222
222
|
# Cancel batch timer
|
|
223
223
|
if self._batch_timer:
|
|
224
224
|
self._batch_timer.cancel()
|
|
225
225
|
self._batch_timer = None
|
|
226
|
-
|
|
226
|
+
|
|
227
227
|
# Emit events
|
|
228
228
|
success = await self._emit_events(batch)
|
|
229
|
-
|
|
229
|
+
|
|
230
230
|
if not success:
|
|
231
231
|
# Re-queue failed events
|
|
232
232
|
self._event_batch = batch + self._event_batch
|
|
233
233
|
return False
|
|
234
|
-
|
|
234
|
+
|
|
235
235
|
return True
|
|
236
|
-
|
|
236
|
+
|
|
237
237
|
async def _emit_events(self, events: List[Event]) -> bool:
|
|
238
238
|
"""
|
|
239
239
|
Emit events via Socket.IO.
|
|
240
|
-
|
|
240
|
+
|
|
241
241
|
Args:
|
|
242
242
|
events: Events to emit
|
|
243
|
-
|
|
243
|
+
|
|
244
244
|
Returns:
|
|
245
245
|
True if all events emitted successfully
|
|
246
246
|
"""
|
|
@@ -248,57 +248,57 @@ class SocketIOConsumer(IEventConsumer):
|
|
|
248
248
|
self.logger.debug(f"Cannot emit {len(events)} events - not connected")
|
|
249
249
|
self._metrics["events_failed"] += len(events)
|
|
250
250
|
return False
|
|
251
|
-
|
|
251
|
+
|
|
252
252
|
try:
|
|
253
253
|
start_time = time.time()
|
|
254
|
-
|
|
254
|
+
|
|
255
255
|
for event in events:
|
|
256
256
|
# Convert event to Socket.IO format
|
|
257
257
|
socketio_event = self._convert_to_socketio(event)
|
|
258
|
-
|
|
258
|
+
|
|
259
259
|
# Emit event
|
|
260
260
|
await self.socketio_server.emit(
|
|
261
261
|
socketio_event["event"],
|
|
262
262
|
socketio_event["data"],
|
|
263
|
-
namespace=socketio_event.get("namespace", "/")
|
|
263
|
+
namespace=socketio_event.get("namespace", "/"),
|
|
264
264
|
)
|
|
265
|
-
|
|
265
|
+
|
|
266
266
|
self._metrics["events_emitted"] += 1
|
|
267
|
-
|
|
267
|
+
|
|
268
268
|
# Update metrics
|
|
269
269
|
elapsed_ms = (time.time() - start_time) * 1000
|
|
270
270
|
self._metrics["last_emit_time"] = time.time()
|
|
271
|
-
|
|
271
|
+
|
|
272
272
|
# Update rolling average
|
|
273
273
|
avg = self._metrics["average_emit_time_ms"]
|
|
274
274
|
self._metrics["average_emit_time_ms"] = (avg * 0.9) + (elapsed_ms * 0.1)
|
|
275
|
-
|
|
275
|
+
|
|
276
276
|
self.logger.debug(
|
|
277
277
|
f"Emitted {len(events)} events in {elapsed_ms:.1f}ms "
|
|
278
278
|
f"(avg: {self._metrics['average_emit_time_ms']:.1f}ms)"
|
|
279
279
|
)
|
|
280
|
-
|
|
280
|
+
|
|
281
281
|
# Reset reconnect delay on success
|
|
282
282
|
self.current_reconnect_delay = self.reconnect_delay
|
|
283
|
-
|
|
283
|
+
|
|
284
284
|
return True
|
|
285
|
-
|
|
285
|
+
|
|
286
286
|
except Exception as e:
|
|
287
287
|
self.logger.error(f"Error emitting events: {e}")
|
|
288
288
|
self._metrics["events_failed"] += len(events)
|
|
289
289
|
self._metrics["connection_failures"] += 1
|
|
290
290
|
self._connected = False
|
|
291
|
-
|
|
291
|
+
|
|
292
292
|
# Start reconnection
|
|
293
293
|
if self._reconnect_task is None or self._reconnect_task.done():
|
|
294
294
|
self._reconnect_task = asyncio.create_task(self._reconnect_loop())
|
|
295
|
-
|
|
295
|
+
|
|
296
296
|
return False
|
|
297
|
-
|
|
297
|
+
|
|
298
298
|
def _convert_to_socketio(self, event: Event) -> Dict[str, Any]:
|
|
299
299
|
"""
|
|
300
300
|
Convert an Event to Socket.IO format.
|
|
301
|
-
|
|
301
|
+
|
|
302
302
|
Maps our standard event format to Socket.IO's expected format.
|
|
303
303
|
"""
|
|
304
304
|
# Determine Socket.IO event name based on topic
|
|
@@ -314,7 +314,7 @@ class SocketIOConsumer(IEventConsumer):
|
|
|
314
314
|
socketio_event = "build_event"
|
|
315
315
|
else:
|
|
316
316
|
socketio_event = "claude_event"
|
|
317
|
-
|
|
317
|
+
|
|
318
318
|
# Build Socket.IO data
|
|
319
319
|
return {
|
|
320
320
|
"event": socketio_event,
|
|
@@ -329,19 +329,21 @@ class SocketIOConsumer(IEventConsumer):
|
|
|
329
329
|
},
|
|
330
330
|
"namespace": "/",
|
|
331
331
|
}
|
|
332
|
-
|
|
332
|
+
|
|
333
333
|
async def _batch_timeout_handler(self) -> None:
|
|
334
334
|
"""Handle batch timeout - flush partial batch."""
|
|
335
335
|
await asyncio.sleep(self.batch_timeout)
|
|
336
|
-
|
|
336
|
+
|
|
337
337
|
if self._event_batch:
|
|
338
|
-
self.logger.debug(
|
|
338
|
+
self.logger.debug(
|
|
339
|
+
f"Batch timeout - flushing {len(self._event_batch)} events"
|
|
340
|
+
)
|
|
339
341
|
await self._flush_batch()
|
|
340
|
-
|
|
342
|
+
|
|
341
343
|
async def _reconnect_loop(self) -> None:
|
|
342
344
|
"""
|
|
343
345
|
Reconnection loop with exponential backoff.
|
|
344
|
-
|
|
346
|
+
|
|
345
347
|
Attempts to reconnect to Socket.IO server when connection is lost.
|
|
346
348
|
"""
|
|
347
349
|
while not self._connected and self._initialized:
|
|
@@ -350,7 +352,7 @@ class SocketIOConsumer(IEventConsumer):
|
|
|
350
352
|
f"Attempting to reconnect to Socket.IO "
|
|
351
353
|
f"(delay: {self.current_reconnect_delay}s)"
|
|
352
354
|
)
|
|
353
|
-
|
|
355
|
+
|
|
354
356
|
# Try to reconnect
|
|
355
357
|
if self.socketio_server:
|
|
356
358
|
# Test connection
|
|
@@ -358,19 +360,18 @@ class SocketIOConsumer(IEventConsumer):
|
|
|
358
360
|
self._connected = True
|
|
359
361
|
self._metrics["reconnections"] += 1
|
|
360
362
|
self.logger.info("Reconnected to Socket.IO server")
|
|
361
|
-
|
|
363
|
+
|
|
362
364
|
# Reset delay
|
|
363
365
|
self.current_reconnect_delay = self.reconnect_delay
|
|
364
366
|
break
|
|
365
|
-
|
|
367
|
+
|
|
366
368
|
except Exception as e:
|
|
367
369
|
self.logger.error(f"Reconnection failed: {e}")
|
|
368
|
-
|
|
370
|
+
|
|
369
371
|
# Wait before next attempt
|
|
370
372
|
await asyncio.sleep(self.current_reconnect_delay)
|
|
371
|
-
|
|
373
|
+
|
|
372
374
|
# Exponential backoff
|
|
373
375
|
self.current_reconnect_delay = min(
|
|
374
|
-
self.current_reconnect_delay * 2,
|
|
375
|
-
|
|
376
|
-
)
|
|
376
|
+
self.current_reconnect_delay * 2, self.max_reconnect_delay
|
|
377
|
+
)
|