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
|
@@ -17,9 +17,9 @@ from .registry import EventHandlerRegistry
|
|
|
17
17
|
__all__ = [
|
|
18
18
|
"BaseEventHandler",
|
|
19
19
|
"ConnectionEventHandler",
|
|
20
|
-
"
|
|
21
|
-
"MemoryEventHandler",
|
|
20
|
+
"EventHandlerRegistry",
|
|
22
21
|
"FileEventHandler",
|
|
23
22
|
"GitEventHandler",
|
|
24
|
-
"
|
|
23
|
+
"MemoryEventHandler",
|
|
24
|
+
"ProjectEventHandler",
|
|
25
25
|
]
|
|
@@ -5,15 +5,15 @@ logging, error handling, and access to the server instance. All handler
|
|
|
5
5
|
classes inherit from this to ensure consistent behavior.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
import logging
|
|
9
8
|
from datetime import datetime
|
|
10
|
-
from logging import Logger
|
|
11
9
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
12
10
|
|
|
13
11
|
from ....core.logger import get_logger
|
|
14
12
|
from ....core.typing_utils import EventData, EventName, SocketId
|
|
15
13
|
|
|
16
14
|
if TYPE_CHECKING:
|
|
15
|
+
from logging import Logger
|
|
16
|
+
|
|
17
17
|
import socketio
|
|
18
18
|
|
|
19
19
|
from ..server import SocketIOServer
|
|
@@ -33,8 +33,8 @@ class BaseEventHandler:
|
|
|
33
33
|
Args:
|
|
34
34
|
server: The SocketIOServer instance that owns this handler
|
|
35
35
|
"""
|
|
36
|
-
self.server:
|
|
37
|
-
self.sio:
|
|
36
|
+
self.server: SocketIOServer = server
|
|
37
|
+
self.sio: socketio.AsyncServer = server.sio
|
|
38
38
|
self.logger: Logger = get_logger(self.__class__.__name__)
|
|
39
39
|
self.clients: Dict[SocketId, Dict[str, Any]] = server.clients
|
|
40
40
|
self.event_history: List[Dict[str, Any]] = server.event_history
|
|
@@ -92,6 +92,7 @@ class BaseEventHandler:
|
|
|
92
92
|
except Exception as e:
|
|
93
93
|
self.logger.error(f"Failed to broadcast {event}: {e}")
|
|
94
94
|
import traceback
|
|
95
|
+
|
|
95
96
|
self.logger.error(f"Stack trace: {traceback.format_exc()}")
|
|
96
97
|
|
|
97
98
|
def add_to_history(self, event_type: str, data: EventData) -> None:
|
|
@@ -9,74 +9,83 @@ import asyncio
|
|
|
9
9
|
import functools
|
|
10
10
|
import time
|
|
11
11
|
from datetime import datetime
|
|
12
|
-
from typing import Any, Callable, Dict, List, Optional
|
|
12
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
13
13
|
|
|
14
|
-
from ....core.typing_utils import ClaudeStatus, EventData, SocketId
|
|
15
14
|
from .base import BaseEventHandler
|
|
16
15
|
|
|
17
16
|
|
|
18
17
|
def timeout_handler(timeout_seconds: float = 5.0):
|
|
19
18
|
"""Decorator to add timeout protection to async handlers.
|
|
20
|
-
|
|
19
|
+
|
|
21
20
|
WHY: Network operations can hang indefinitely, causing resource leaks
|
|
22
21
|
and poor user experience. This decorator ensures handlers complete
|
|
23
22
|
within a reasonable time or fail gracefully.
|
|
24
|
-
|
|
23
|
+
|
|
25
24
|
Args:
|
|
26
25
|
timeout_seconds: Maximum time allowed for handler execution (default: 5s)
|
|
27
26
|
"""
|
|
27
|
+
|
|
28
28
|
def decorator(func: Callable) -> Callable:
|
|
29
29
|
@functools.wraps(func)
|
|
30
30
|
async def wrapper(*args, **kwargs):
|
|
31
31
|
handler_name = func.__name__
|
|
32
32
|
start_time = time.time()
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
try:
|
|
35
35
|
# Create a task with timeout
|
|
36
36
|
result = await asyncio.wait_for(
|
|
37
|
-
func(*args, **kwargs),
|
|
38
|
-
timeout=timeout_seconds
|
|
37
|
+
func(*args, **kwargs), timeout=timeout_seconds
|
|
39
38
|
)
|
|
40
|
-
|
|
39
|
+
|
|
41
40
|
elapsed = time.time() - start_time
|
|
42
41
|
if elapsed > timeout_seconds * 0.8: # Warn if close to timeout
|
|
43
42
|
# Try to get logger from closure scope or fallback to print
|
|
44
43
|
try:
|
|
45
44
|
import logging
|
|
45
|
+
|
|
46
46
|
logger = logging.getLogger(__name__)
|
|
47
47
|
logger.warning(
|
|
48
48
|
f"⚠️ Handler {handler_name} took {elapsed:.2f}s "
|
|
49
49
|
f"(close to {timeout_seconds}s timeout)"
|
|
50
50
|
)
|
|
51
51
|
except:
|
|
52
|
-
print(
|
|
53
|
-
|
|
52
|
+
print(
|
|
53
|
+
f"⚠️ Handler {handler_name} took {elapsed:.2f}s (close to {timeout_seconds}s timeout)"
|
|
54
|
+
)
|
|
55
|
+
|
|
54
56
|
return result
|
|
55
|
-
|
|
57
|
+
|
|
56
58
|
except asyncio.TimeoutError:
|
|
57
59
|
elapsed = time.time() - start_time
|
|
58
60
|
# Try to get logger from closure scope or fallback to print
|
|
59
61
|
try:
|
|
60
62
|
import logging
|
|
63
|
+
|
|
61
64
|
logger = logging.getLogger(__name__)
|
|
62
|
-
logger.error(
|
|
65
|
+
logger.error(
|
|
66
|
+
f"❌ Handler {handler_name} timed out after {elapsed:.2f}s"
|
|
67
|
+
)
|
|
63
68
|
except:
|
|
64
69
|
print(f"❌ Handler {handler_name} timed out after {elapsed:.2f}s")
|
|
65
|
-
|
|
70
|
+
|
|
66
71
|
return None
|
|
67
|
-
|
|
72
|
+
|
|
68
73
|
except Exception as e:
|
|
69
74
|
elapsed = time.time() - start_time
|
|
70
75
|
# Try to get logger from closure scope or fallback to print
|
|
71
76
|
try:
|
|
72
77
|
import logging
|
|
78
|
+
|
|
73
79
|
logger = logging.getLogger(__name__)
|
|
74
|
-
logger.error(
|
|
80
|
+
logger.error(
|
|
81
|
+
f"❌ Handler {handler_name} failed after {elapsed:.2f}s: {e}"
|
|
82
|
+
)
|
|
75
83
|
except:
|
|
76
84
|
print(f"❌ Handler {handler_name} failed after {elapsed:.2f}s: {e}")
|
|
77
85
|
raise
|
|
78
|
-
|
|
86
|
+
|
|
79
87
|
return wrapper
|
|
88
|
+
|
|
80
89
|
return decorator
|
|
81
90
|
|
|
82
91
|
|
|
@@ -87,34 +96,34 @@ class ConnectionEventHandler(BaseEventHandler):
|
|
|
87
96
|
that deserves its own focused handler. This includes client connections,
|
|
88
97
|
disconnections, status updates, and event history management.
|
|
89
98
|
"""
|
|
90
|
-
|
|
99
|
+
|
|
91
100
|
def __init__(self, server):
|
|
92
101
|
"""Initialize connection handler with health monitoring.
|
|
93
|
-
|
|
102
|
+
|
|
94
103
|
WHY: We need to track connection health metrics and implement
|
|
95
104
|
ping/pong mechanism for detecting stale connections.
|
|
96
105
|
"""
|
|
97
106
|
super().__init__(server)
|
|
98
|
-
|
|
107
|
+
|
|
99
108
|
# Connection health tracking
|
|
100
109
|
self.connection_metrics = {}
|
|
101
110
|
self.last_ping_times = {}
|
|
102
111
|
self.ping_interval = 45 # seconds - avoid conflict with Engine.IO pings
|
|
103
112
|
self.ping_timeout = 20 # seconds - more lenient timeout
|
|
104
113
|
self.stale_check_interval = 90 # seconds - less frequent checks
|
|
105
|
-
|
|
114
|
+
|
|
106
115
|
# Health monitoring tasks (will be started after event registration)
|
|
107
116
|
self.ping_task = None
|
|
108
117
|
self.stale_check_task = None
|
|
109
118
|
|
|
110
119
|
def _start_health_monitoring(self):
|
|
111
120
|
"""Start background tasks for connection health monitoring.
|
|
112
|
-
|
|
121
|
+
|
|
113
122
|
WHY: We need to actively monitor connection health to detect
|
|
114
123
|
and clean up stale connections, ensuring reliable event delivery.
|
|
115
124
|
"""
|
|
116
125
|
# Only start if we have a valid event loop and tasks aren't already running
|
|
117
|
-
if hasattr(self.server,
|
|
126
|
+
if hasattr(self.server, "core") and hasattr(self.server.core, "loop"):
|
|
118
127
|
loop = self.server.core.loop
|
|
119
128
|
if loop and not loop.is_closed():
|
|
120
129
|
if not self.ping_task or self.ping_task.done():
|
|
@@ -122,154 +131,160 @@ class ConnectionEventHandler(BaseEventHandler):
|
|
|
122
131
|
self._periodic_ping(), loop
|
|
123
132
|
)
|
|
124
133
|
self.logger.info("🏓 Started connection ping monitoring")
|
|
125
|
-
|
|
134
|
+
|
|
126
135
|
if not self.stale_check_task or self.stale_check_task.done():
|
|
127
136
|
self.stale_check_task = asyncio.run_coroutine_threadsafe(
|
|
128
137
|
self._check_stale_connections(), loop
|
|
129
138
|
)
|
|
130
139
|
self.logger.info("🧹 Started stale connection checker")
|
|
131
|
-
|
|
140
|
+
|
|
132
141
|
def stop_health_monitoring(self):
|
|
133
142
|
"""Stop health monitoring tasks.
|
|
134
|
-
|
|
143
|
+
|
|
135
144
|
WHY: Clean shutdown requires stopping background tasks to
|
|
136
145
|
prevent errors and resource leaks.
|
|
137
146
|
"""
|
|
138
147
|
if self.ping_task and not self.ping_task.done():
|
|
139
148
|
self.ping_task.cancel()
|
|
140
149
|
self.logger.info("🚫 Stopped connection ping monitoring")
|
|
141
|
-
|
|
150
|
+
|
|
142
151
|
if self.stale_check_task and not self.stale_check_task.done():
|
|
143
152
|
self.stale_check_task.cancel()
|
|
144
153
|
self.logger.info("🚫 Stopped stale connection checker")
|
|
145
|
-
|
|
154
|
+
|
|
146
155
|
async def _periodic_ping(self):
|
|
147
156
|
"""Send periodic pings to all connected clients.
|
|
148
|
-
|
|
157
|
+
|
|
149
158
|
WHY: WebSocket connections can silently fail. Regular pings
|
|
150
159
|
help detect dead connections and maintain connection state.
|
|
151
160
|
"""
|
|
152
161
|
while True:
|
|
153
162
|
try:
|
|
154
163
|
await asyncio.sleep(self.ping_interval)
|
|
155
|
-
|
|
164
|
+
|
|
156
165
|
if not self.clients:
|
|
157
166
|
continue
|
|
158
|
-
|
|
167
|
+
|
|
159
168
|
current_time = time.time()
|
|
160
169
|
disconnected = []
|
|
161
|
-
|
|
170
|
+
|
|
162
171
|
for sid in list(self.clients):
|
|
163
172
|
try:
|
|
164
173
|
# Send ping and record time (using new schema)
|
|
165
|
-
await self.sio.emit(
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
174
|
+
await self.sio.emit(
|
|
175
|
+
"ping",
|
|
176
|
+
{
|
|
177
|
+
"type": "system",
|
|
178
|
+
"subtype": "ping",
|
|
179
|
+
"timestamp": current_time,
|
|
180
|
+
"source": "server",
|
|
181
|
+
},
|
|
182
|
+
room=sid,
|
|
183
|
+
)
|
|
171
184
|
self.last_ping_times[sid] = current_time
|
|
172
|
-
|
|
185
|
+
|
|
173
186
|
# Update connection metrics
|
|
174
187
|
if sid not in self.connection_metrics:
|
|
175
188
|
self.connection_metrics[sid] = {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
189
|
+
"connected_at": current_time,
|
|
190
|
+
"reconnects": 0,
|
|
191
|
+
"failures": 0,
|
|
192
|
+
"last_activity": current_time,
|
|
180
193
|
}
|
|
181
|
-
self.connection_metrics[sid][
|
|
182
|
-
|
|
194
|
+
self.connection_metrics[sid]["last_activity"] = current_time
|
|
195
|
+
|
|
183
196
|
except Exception as e:
|
|
184
197
|
self.logger.warning(f"Failed to ping client {sid}: {e}")
|
|
185
198
|
disconnected.append(sid)
|
|
186
|
-
|
|
199
|
+
|
|
187
200
|
# Clean up failed connections
|
|
188
201
|
for sid in disconnected:
|
|
189
202
|
await self._cleanup_stale_connection(sid)
|
|
190
|
-
|
|
203
|
+
|
|
191
204
|
if self.clients:
|
|
192
205
|
self.logger.debug(
|
|
193
206
|
f"🏓 Sent pings to {len(self.clients)} clients, "
|
|
194
207
|
f"{len(disconnected)} failed"
|
|
195
208
|
)
|
|
196
|
-
|
|
209
|
+
|
|
197
210
|
except Exception as e:
|
|
198
211
|
self.logger.error(f"Error in periodic ping: {e}")
|
|
199
|
-
|
|
212
|
+
|
|
200
213
|
async def _check_stale_connections(self):
|
|
201
214
|
"""Check for and clean up stale connections.
|
|
202
|
-
|
|
215
|
+
|
|
203
216
|
WHY: Some clients may not properly disconnect, leaving zombie
|
|
204
217
|
connections that consume resources and prevent proper cleanup.
|
|
205
218
|
"""
|
|
206
219
|
while True:
|
|
207
220
|
try:
|
|
208
221
|
await asyncio.sleep(self.stale_check_interval)
|
|
209
|
-
|
|
222
|
+
|
|
210
223
|
current_time = time.time()
|
|
211
|
-
stale_threshold = current_time - (
|
|
224
|
+
stale_threshold = current_time - (
|
|
225
|
+
self.ping_timeout + self.ping_interval
|
|
226
|
+
)
|
|
212
227
|
stale_sids = []
|
|
213
|
-
|
|
228
|
+
|
|
214
229
|
for sid in list(self.clients):
|
|
215
230
|
last_ping = self.last_ping_times.get(sid, 0)
|
|
216
|
-
|
|
231
|
+
|
|
217
232
|
if last_ping < stale_threshold:
|
|
218
233
|
stale_sids.append(sid)
|
|
219
234
|
self.logger.warning(
|
|
220
235
|
f"🧟 Detected stale connection {sid} "
|
|
221
236
|
f"(last ping: {current_time - last_ping:.1f}s ago)"
|
|
222
237
|
)
|
|
223
|
-
|
|
238
|
+
|
|
224
239
|
# Clean up stale connections
|
|
225
240
|
for sid in stale_sids:
|
|
226
241
|
await self._cleanup_stale_connection(sid)
|
|
227
|
-
|
|
242
|
+
|
|
228
243
|
if stale_sids:
|
|
229
244
|
self.logger.info(
|
|
230
245
|
f"🧹 Cleaned up {len(stale_sids)} stale connections"
|
|
231
246
|
)
|
|
232
|
-
|
|
247
|
+
|
|
233
248
|
except Exception as e:
|
|
234
249
|
self.logger.error(f"Error checking stale connections: {e}")
|
|
235
|
-
|
|
250
|
+
|
|
236
251
|
async def _cleanup_stale_connection(self, sid: str):
|
|
237
252
|
"""Clean up a stale or dead connection.
|
|
238
|
-
|
|
253
|
+
|
|
239
254
|
WHY: Proper cleanup prevents memory leaks and ensures
|
|
240
255
|
accurate connection tracking.
|
|
241
256
|
"""
|
|
242
257
|
try:
|
|
243
258
|
if sid in self.clients:
|
|
244
259
|
self.clients.remove(sid)
|
|
245
|
-
|
|
260
|
+
|
|
246
261
|
if sid in self.last_ping_times:
|
|
247
262
|
del self.last_ping_times[sid]
|
|
248
|
-
|
|
263
|
+
|
|
249
264
|
if sid in self.connection_metrics:
|
|
250
265
|
metrics = self.connection_metrics[sid]
|
|
251
|
-
uptime = time.time() - metrics.get(
|
|
266
|
+
uptime = time.time() - metrics.get("connected_at", 0)
|
|
252
267
|
self.logger.info(
|
|
253
268
|
f"📊 Connection {sid} stats - uptime: {uptime:.1f}s, "
|
|
254
269
|
f"reconnects: {metrics.get('reconnects', 0)}, "
|
|
255
270
|
f"failures: {metrics.get('failures', 0)}"
|
|
256
271
|
)
|
|
257
272
|
del self.connection_metrics[sid]
|
|
258
|
-
|
|
273
|
+
|
|
259
274
|
# Force disconnect if still connected
|
|
260
275
|
try:
|
|
261
276
|
await self.sio.disconnect(sid)
|
|
262
277
|
except:
|
|
263
278
|
pass # Already disconnected
|
|
264
|
-
|
|
279
|
+
|
|
265
280
|
self.logger.info(f"🔌 Cleaned up stale connection: {sid}")
|
|
266
|
-
|
|
281
|
+
|
|
267
282
|
except Exception as e:
|
|
268
283
|
self.logger.error(f"Error cleaning up connection {sid}: {e}")
|
|
269
|
-
|
|
284
|
+
|
|
270
285
|
def register_events(self) -> None:
|
|
271
286
|
"""Register connection-related event handlers."""
|
|
272
|
-
|
|
287
|
+
|
|
273
288
|
# Start health monitoring now that we're registering events
|
|
274
289
|
self._start_health_monitoring()
|
|
275
290
|
|
|
@@ -293,13 +308,18 @@ class ConnectionEventHandler(BaseEventHandler):
|
|
|
293
308
|
monitor_build_info = {}
|
|
294
309
|
try:
|
|
295
310
|
from ....services.monitor_build_service import get_monitor_build_service
|
|
311
|
+
|
|
296
312
|
monitor_service = get_monitor_build_service()
|
|
297
313
|
monitor_build_info = monitor_service.get_build_info_sync()
|
|
298
314
|
except Exception as e:
|
|
299
315
|
self.logger.debug(f"Could not get monitor build info: {e}")
|
|
300
316
|
monitor_build_info = {
|
|
301
|
-
"monitor": {
|
|
302
|
-
|
|
317
|
+
"monitor": {
|
|
318
|
+
"version": "1.0.0",
|
|
319
|
+
"build": 1,
|
|
320
|
+
"formatted_build": "0001",
|
|
321
|
+
},
|
|
322
|
+
"mpm": {"version": "unknown", "build": "unknown"},
|
|
303
323
|
}
|
|
304
324
|
|
|
305
325
|
# Send initial status immediately with enhanced data (using new schema)
|
|
@@ -317,7 +337,7 @@ class ConnectionEventHandler(BaseEventHandler):
|
|
|
317
337
|
"server_version": "2.0.0",
|
|
318
338
|
"client_id": sid,
|
|
319
339
|
"build_info": monitor_build_info,
|
|
320
|
-
}
|
|
340
|
+
},
|
|
321
341
|
}
|
|
322
342
|
|
|
323
343
|
try:
|
|
@@ -336,7 +356,7 @@ class ConnectionEventHandler(BaseEventHandler):
|
|
|
336
356
|
"client_id": sid,
|
|
337
357
|
"server_time": datetime.utcnow().isoformat() + "Z",
|
|
338
358
|
"build_info": monitor_build_info,
|
|
339
|
-
}
|
|
359
|
+
},
|
|
340
360
|
},
|
|
341
361
|
)
|
|
342
362
|
|
|
@@ -362,10 +382,8 @@ class ConnectionEventHandler(BaseEventHandler):
|
|
|
362
382
|
self.logger.info(f"🔌 CLIENT DISCONNECTED: {sid}")
|
|
363
383
|
self.logger.info(f"📉 Total clients now: {len(self.clients)}")
|
|
364
384
|
else:
|
|
365
|
-
self.logger.warning(
|
|
366
|
-
|
|
367
|
-
)
|
|
368
|
-
|
|
385
|
+
self.logger.warning(f"⚠️ Attempted to disconnect unknown client: {sid}")
|
|
386
|
+
|
|
369
387
|
# Clean up health tracking
|
|
370
388
|
if sid in self.last_ping_times:
|
|
371
389
|
del self.last_ping_times[sid]
|
|
@@ -391,7 +409,7 @@ class ConnectionEventHandler(BaseEventHandler):
|
|
|
391
409
|
"clients_connected": len(self.clients),
|
|
392
410
|
"claude_status": self.server.claude_status,
|
|
393
411
|
"claude_pid": self.server.claude_pid,
|
|
394
|
-
}
|
|
412
|
+
},
|
|
395
413
|
}
|
|
396
414
|
await self.emit_to_client(sid, "status", status_data)
|
|
397
415
|
|
|
@@ -432,13 +450,17 @@ class ConnectionEventHandler(BaseEventHandler):
|
|
|
432
450
|
for filtered event streaming.
|
|
433
451
|
"""
|
|
434
452
|
channels = data.get("channels", ["*"]) if data else ["*"]
|
|
435
|
-
await self.emit_to_client(
|
|
436
|
-
|
|
437
|
-
"
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
453
|
+
await self.emit_to_client(
|
|
454
|
+
sid,
|
|
455
|
+
"subscribed",
|
|
456
|
+
{
|
|
457
|
+
"type": "connection",
|
|
458
|
+
"subtype": "subscribed",
|
|
459
|
+
"timestamp": datetime.utcnow().isoformat() + "Z",
|
|
460
|
+
"source": "server",
|
|
461
|
+
"data": {"channels": channels},
|
|
462
|
+
},
|
|
463
|
+
)
|
|
442
464
|
|
|
443
465
|
@self.sio.event
|
|
444
466
|
@timeout_handler(timeout_seconds=5.0)
|
|
@@ -450,55 +472,58 @@ class ConnectionEventHandler(BaseEventHandler):
|
|
|
450
472
|
"""
|
|
451
473
|
# Add debug logging
|
|
452
474
|
self.logger.info(f"🔵 Received claude_event from {sid}: {data}")
|
|
453
|
-
|
|
475
|
+
|
|
454
476
|
# Check if this is a hook event and route to HookEventHandler
|
|
455
477
|
# Hook events can have either:
|
|
456
478
|
# 1. Normalized format: type="hook", subtype="pre_tool"
|
|
457
479
|
# 2. Legacy format: type="hook.pre_tool"
|
|
458
480
|
if isinstance(data, dict):
|
|
459
481
|
event_type = data.get("type", "")
|
|
460
|
-
|
|
461
|
-
|
|
482
|
+
data.get("subtype", "")
|
|
483
|
+
|
|
462
484
|
# Check for both normalized and legacy formats
|
|
463
485
|
is_hook_event = False
|
|
464
486
|
if isinstance(event_type, str):
|
|
465
487
|
# Legacy format: type="hook.something"
|
|
466
|
-
if event_type.startswith("hook."):
|
|
467
|
-
is_hook_event = True
|
|
468
|
-
# Normalized format: type="hook" with any subtype
|
|
469
|
-
elif event_type == "hook":
|
|
488
|
+
if event_type.startswith("hook.") or event_type == "hook":
|
|
470
489
|
is_hook_event = True
|
|
471
|
-
|
|
490
|
+
|
|
472
491
|
if is_hook_event:
|
|
473
492
|
# Get the hook handler if available
|
|
474
493
|
hook_handler = None
|
|
475
494
|
# Check if event_registry exists and has handlers
|
|
476
|
-
if
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
495
|
+
if (
|
|
496
|
+
hasattr(self.server, "event_registry")
|
|
497
|
+
and self.server.event_registry
|
|
498
|
+
) and hasattr(self.server.event_registry, "handlers"):
|
|
499
|
+
for handler in self.server.event_registry.handlers:
|
|
500
|
+
if handler.__class__.__name__ == "HookEventHandler":
|
|
501
|
+
hook_handler = handler
|
|
502
|
+
break
|
|
503
|
+
|
|
483
504
|
if hook_handler and hasattr(hook_handler, "process_hook_event"):
|
|
484
505
|
# Let the hook handler process this event
|
|
485
506
|
await hook_handler.process_hook_event(data)
|
|
486
507
|
# Don't double-store or double-broadcast, return early
|
|
487
508
|
return
|
|
488
|
-
|
|
509
|
+
|
|
489
510
|
# Normalize event format before storing in history
|
|
490
511
|
normalized_event = self._normalize_event(data)
|
|
491
|
-
|
|
512
|
+
|
|
492
513
|
# Store in history - flatten if it's a nested structure
|
|
493
514
|
# If the normalized event has data.event, promote it to top level
|
|
494
|
-
if isinstance(normalized_event, dict) and
|
|
495
|
-
if
|
|
515
|
+
if isinstance(normalized_event, dict) and "data" in normalized_event:
|
|
516
|
+
if (
|
|
517
|
+
isinstance(normalized_event["data"], dict)
|
|
518
|
+
and "event" in normalized_event["data"]
|
|
519
|
+
):
|
|
496
520
|
# This is a nested event, flatten it
|
|
497
521
|
flattened = {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
522
|
+
"type": normalized_event.get("type", "unknown"),
|
|
523
|
+
"event": normalized_event["data"].get("event"),
|
|
524
|
+
"timestamp": normalized_event.get("timestamp")
|
|
525
|
+
or normalized_event["data"].get("timestamp"),
|
|
526
|
+
"data": normalized_event["data"].get("data", {}),
|
|
502
527
|
}
|
|
503
528
|
self.event_history.append(flattened)
|
|
504
529
|
else:
|
|
@@ -510,32 +535,34 @@ class ConnectionEventHandler(BaseEventHandler):
|
|
|
510
535
|
)
|
|
511
536
|
|
|
512
537
|
# Re-broadcast to all other clients
|
|
513
|
-
self.logger.info(
|
|
538
|
+
self.logger.info(
|
|
539
|
+
f"📡 Broadcasting claude_event to all clients except {sid}"
|
|
540
|
+
)
|
|
514
541
|
await self.broadcast_event("claude_event", data, skip_sid=sid)
|
|
515
|
-
self.logger.info(
|
|
516
|
-
|
|
542
|
+
self.logger.info("✅ Broadcast complete")
|
|
543
|
+
|
|
517
544
|
@self.sio.event
|
|
518
545
|
async def pong(sid, data=None):
|
|
519
546
|
"""Handle pong response from client.
|
|
520
|
-
|
|
547
|
+
|
|
521
548
|
WHY: Clients respond to our pings with pongs, confirming
|
|
522
549
|
they're still alive and the connection is healthy.
|
|
523
550
|
"""
|
|
524
551
|
current_time = time.time()
|
|
525
|
-
|
|
552
|
+
|
|
526
553
|
# Update last activity time
|
|
527
554
|
if sid in self.connection_metrics:
|
|
528
|
-
self.connection_metrics[sid][
|
|
529
|
-
|
|
555
|
+
self.connection_metrics[sid]["last_activity"] = current_time
|
|
556
|
+
|
|
530
557
|
# Calculate round-trip time if timestamp provided
|
|
531
|
-
if data and
|
|
532
|
-
rtt = current_time - data[
|
|
558
|
+
if data and "timestamp" in data:
|
|
559
|
+
rtt = current_time - data["timestamp"]
|
|
533
560
|
if rtt < 10: # Reasonable RTT
|
|
534
561
|
self.logger.debug(f"🏓 Pong from {sid}, RTT: {rtt*1000:.1f}ms")
|
|
535
562
|
|
|
536
563
|
def _normalize_event(self, event_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
537
564
|
"""Normalize event format to ensure consistency.
|
|
538
|
-
|
|
565
|
+
|
|
539
566
|
WHY: Different clients may send events in different formats.
|
|
540
567
|
This ensures all events have a consistent 'type' field for
|
|
541
568
|
proper display in the dashboard, while preserving the original
|
|
@@ -543,37 +570,37 @@ class ConnectionEventHandler(BaseEventHandler):
|
|
|
543
570
|
"""
|
|
544
571
|
if not isinstance(event_data, dict):
|
|
545
572
|
return event_data
|
|
546
|
-
|
|
573
|
+
|
|
547
574
|
# Make a copy to avoid modifying the original
|
|
548
575
|
normalized = dict(event_data)
|
|
549
|
-
|
|
576
|
+
|
|
550
577
|
# If event has no 'type' but has 'event' field (legacy format)
|
|
551
|
-
if
|
|
552
|
-
event_name = normalized[
|
|
553
|
-
|
|
578
|
+
if "type" not in normalized and "event" in normalized:
|
|
579
|
+
event_name = normalized["event"]
|
|
580
|
+
|
|
554
581
|
# Map common event names to proper type
|
|
555
|
-
if event_name in [
|
|
556
|
-
normalized[
|
|
557
|
-
elif event_name in [
|
|
558
|
-
normalized[
|
|
559
|
-
elif event_name ==
|
|
560
|
-
normalized[
|
|
561
|
-
elif event_name ==
|
|
562
|
-
normalized[
|
|
563
|
-
normalized[
|
|
582
|
+
if event_name in ["TestStart", "TestEnd"]:
|
|
583
|
+
normalized["type"] = "test"
|
|
584
|
+
elif event_name in ["SubagentStart", "SubagentStop"]:
|
|
585
|
+
normalized["type"] = "subagent"
|
|
586
|
+
elif event_name == "ToolCall":
|
|
587
|
+
normalized["type"] = "tool"
|
|
588
|
+
elif event_name == "UserPrompt":
|
|
589
|
+
normalized["type"] = "hook"
|
|
590
|
+
normalized["subtype"] = "user_prompt"
|
|
564
591
|
else:
|
|
565
592
|
# Default to system type for unknown events
|
|
566
|
-
normalized[
|
|
567
|
-
|
|
593
|
+
normalized["type"] = "system"
|
|
594
|
+
|
|
568
595
|
# Note: We keep the 'event' field for backward compatibility
|
|
569
596
|
# Dashboard may use it for display purposes
|
|
570
|
-
|
|
597
|
+
|
|
571
598
|
# Ensure there's always a type field
|
|
572
|
-
if
|
|
573
|
-
normalized[
|
|
574
|
-
|
|
599
|
+
if "type" not in normalized:
|
|
600
|
+
normalized["type"] = "unknown"
|
|
601
|
+
|
|
575
602
|
return normalized
|
|
576
|
-
|
|
603
|
+
|
|
577
604
|
async def _send_event_history(
|
|
578
605
|
self, sid: str, event_types: Optional[List[str]] = None, limit: int = 50
|
|
579
606
|
):
|