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
|
@@ -14,67 +14,70 @@ DESIGN DECISION: Transform all events to a consistent schema:
|
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
import re
|
|
17
|
-
from datetime import datetime
|
|
18
|
-
from typing import Any, Dict, Optional, Tuple
|
|
19
17
|
from dataclasses import dataclass, field
|
|
18
|
+
from datetime import datetime
|
|
20
19
|
from enum import Enum
|
|
20
|
+
from typing import Any, Dict, Optional, Tuple
|
|
21
21
|
|
|
22
22
|
from ...core.logging_config import get_logger
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class EventSource(Enum):
|
|
26
26
|
"""Event sources.
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
WHY: Identifying where events come from helps with debugging,
|
|
29
29
|
filtering, and understanding system behavior.
|
|
30
30
|
"""
|
|
31
|
-
|
|
31
|
+
|
|
32
|
+
HOOK = "hook" # Events from Claude Code hooks
|
|
32
33
|
DASHBOARD = "dashboard" # Events from dashboard UI
|
|
33
|
-
SYSTEM = "system"
|
|
34
|
-
AGENT = "agent"
|
|
35
|
-
CLI = "cli"
|
|
36
|
-
API = "api"
|
|
37
|
-
TEST = "test"
|
|
34
|
+
SYSTEM = "system" # System/server operations
|
|
35
|
+
AGENT = "agent" # Agent operations
|
|
36
|
+
CLI = "cli" # CLI commands
|
|
37
|
+
API = "api" # API calls
|
|
38
|
+
TEST = "test" # Test scripts
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
class EventType(Enum):
|
|
41
42
|
"""Main event categories.
|
|
42
|
-
|
|
43
|
+
|
|
43
44
|
WHY: Categorizing events helps with filtering, routing, and understanding
|
|
44
45
|
the system's behavior at a high level.
|
|
45
46
|
"""
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
|
|
48
|
+
HOOK = "hook" # Claude Code hook events
|
|
49
|
+
SYSTEM = "system" # System health and status events
|
|
50
|
+
SESSION = "session" # Session lifecycle events
|
|
51
|
+
FILE = "file" # File system events
|
|
50
52
|
CONNECTION = "connection" # Client connection events
|
|
51
|
-
MEMORY = "memory"
|
|
52
|
-
GIT = "git"
|
|
53
|
-
TODO = "todo"
|
|
54
|
-
TICKET = "ticket"
|
|
55
|
-
AGENT = "agent"
|
|
56
|
-
ERROR = "error"
|
|
53
|
+
MEMORY = "memory" # Memory system events
|
|
54
|
+
GIT = "git" # Git operation events
|
|
55
|
+
TODO = "todo" # Todo list updates
|
|
56
|
+
TICKET = "ticket" # Ticket system events
|
|
57
|
+
AGENT = "agent" # Agent delegation events
|
|
58
|
+
ERROR = "error" # Error events
|
|
57
59
|
PERFORMANCE = "performance" # Performance metrics
|
|
58
|
-
CLAUDE = "claude"
|
|
59
|
-
TEST = "test"
|
|
60
|
-
TOOL = "tool"
|
|
61
|
-
SUBAGENT = "subagent"
|
|
60
|
+
CLAUDE = "claude" # Claude process events
|
|
61
|
+
TEST = "test" # Test events
|
|
62
|
+
TOOL = "tool" # Tool events
|
|
63
|
+
SUBAGENT = "subagent" # Subagent events
|
|
62
64
|
|
|
63
65
|
|
|
64
66
|
@dataclass
|
|
65
67
|
class NormalizedEvent:
|
|
66
68
|
"""Represents a normalized event with consistent structure.
|
|
67
|
-
|
|
69
|
+
|
|
68
70
|
WHY: Using a dataclass ensures type safety and makes the event
|
|
69
71
|
structure explicit and self-documenting.
|
|
70
72
|
"""
|
|
73
|
+
|
|
71
74
|
event: str = "claude_event" # Socket.IO event name
|
|
72
|
-
source: str = ""
|
|
73
|
-
type: str = ""
|
|
74
|
-
subtype: str = ""
|
|
75
|
-
timestamp: str = ""
|
|
75
|
+
source: str = "" # WHERE the event comes from
|
|
76
|
+
type: str = "" # WHAT category of event
|
|
77
|
+
subtype: str = "" # Specific event type
|
|
78
|
+
timestamp: str = "" # ISO format timestamp
|
|
76
79
|
data: Dict[str, Any] = field(default_factory=dict) # Event payload
|
|
77
|
-
|
|
80
|
+
|
|
78
81
|
def to_dict(self) -> Dict[str, Any]:
|
|
79
82
|
"""Convert to dictionary for emission."""
|
|
80
83
|
return {
|
|
@@ -83,17 +86,17 @@ class NormalizedEvent:
|
|
|
83
86
|
"type": self.type,
|
|
84
87
|
"subtype": self.subtype,
|
|
85
88
|
"timestamp": self.timestamp,
|
|
86
|
-
"data": self.data
|
|
89
|
+
"data": self.data,
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
|
|
90
93
|
class EventNormalizer:
|
|
91
94
|
"""Normalizes events to a consistent schema.
|
|
92
|
-
|
|
95
|
+
|
|
93
96
|
WHY: This class handles the transformation of various event formats
|
|
94
97
|
into a single, consistent schema that clients can reliably parse.
|
|
95
98
|
"""
|
|
96
|
-
|
|
99
|
+
|
|
97
100
|
# Mapping of event names to (type, subtype) tuples
|
|
98
101
|
EVENT_MAPPINGS = {
|
|
99
102
|
# Hook events
|
|
@@ -103,81 +106,66 @@ class EventNormalizer:
|
|
|
103
106
|
"post_response": (EventType.HOOK, "post_response"),
|
|
104
107
|
"hook_event": (EventType.HOOK, "generic"),
|
|
105
108
|
"UserPrompt": (EventType.HOOK, "user_prompt"), # Legacy format
|
|
106
|
-
|
|
107
109
|
# Test events (legacy format)
|
|
108
110
|
"TestStart": (EventType.TEST, "start"),
|
|
109
111
|
"TestEnd": (EventType.TEST, "end"),
|
|
110
|
-
|
|
111
|
-
# Tool events (legacy format)
|
|
112
|
+
# Tool events (legacy format)
|
|
112
113
|
"ToolCall": (EventType.TOOL, "call"),
|
|
113
|
-
|
|
114
114
|
# Subagent events (legacy format)
|
|
115
115
|
"SubagentStart": (EventType.SUBAGENT, "start"),
|
|
116
116
|
"SubagentStop": (EventType.SUBAGENT, "stop"),
|
|
117
|
-
|
|
118
117
|
# System events
|
|
119
118
|
"heartbeat": (EventType.SYSTEM, "heartbeat"),
|
|
120
119
|
"system_status": (EventType.SYSTEM, "status"),
|
|
121
120
|
"system_event": (EventType.SYSTEM, "generic"),
|
|
122
|
-
|
|
123
121
|
# Session events
|
|
124
122
|
"session_started": (EventType.SESSION, "started"),
|
|
125
123
|
"session_ended": (EventType.SESSION, "ended"),
|
|
126
124
|
"session_event": (EventType.SESSION, "generic"),
|
|
127
|
-
|
|
128
125
|
# File events
|
|
129
126
|
"file_changed": (EventType.FILE, "changed"),
|
|
130
127
|
"file_created": (EventType.FILE, "created"),
|
|
131
128
|
"file_deleted": (EventType.FILE, "deleted"),
|
|
132
129
|
"file_event": (EventType.FILE, "generic"),
|
|
133
|
-
|
|
134
130
|
# Connection events
|
|
135
131
|
"client_connected": (EventType.CONNECTION, "connected"),
|
|
136
132
|
"client_disconnected": (EventType.CONNECTION, "disconnected"),
|
|
137
133
|
"connection_event": (EventType.CONNECTION, "generic"),
|
|
138
|
-
|
|
139
134
|
# Memory events
|
|
140
135
|
"memory_loaded": (EventType.MEMORY, "loaded"),
|
|
141
136
|
"memory_created": (EventType.MEMORY, "created"),
|
|
142
137
|
"memory_updated": (EventType.MEMORY, "updated"),
|
|
143
138
|
"memory_injected": (EventType.MEMORY, "injected"),
|
|
144
139
|
"memory_event": (EventType.MEMORY, "generic"),
|
|
145
|
-
|
|
146
140
|
# Git events
|
|
147
141
|
"git_operation": (EventType.GIT, "operation"),
|
|
148
142
|
"git_commit": (EventType.GIT, "commit"),
|
|
149
143
|
"git_push": (EventType.GIT, "push"),
|
|
150
144
|
"git_pull": (EventType.GIT, "pull"),
|
|
151
|
-
|
|
152
145
|
# Todo events
|
|
153
146
|
"todo_updated": (EventType.TODO, "updated"),
|
|
154
147
|
"todo_created": (EventType.TODO, "created"),
|
|
155
148
|
"todo_completed": (EventType.TODO, "completed"),
|
|
156
|
-
|
|
157
149
|
# Ticket events
|
|
158
150
|
"ticket_created": (EventType.TICKET, "created"),
|
|
159
151
|
"ticket_updated": (EventType.TICKET, "updated"),
|
|
160
152
|
"ticket_closed": (EventType.TICKET, "closed"),
|
|
161
|
-
|
|
162
153
|
# Agent events
|
|
163
154
|
"agent_delegated": (EventType.AGENT, "delegated"),
|
|
164
155
|
"agent_completed": (EventType.AGENT, "completed"),
|
|
165
|
-
|
|
166
156
|
# Claude events
|
|
167
157
|
"claude_status": (EventType.CLAUDE, "status"),
|
|
168
158
|
"claude_output": (EventType.CLAUDE, "output"),
|
|
169
159
|
"claude_started": (EventType.CLAUDE, "started"),
|
|
170
160
|
"claude_stopped": (EventType.CLAUDE, "stopped"),
|
|
171
|
-
|
|
172
161
|
# Error events
|
|
173
162
|
"error": (EventType.ERROR, "general"),
|
|
174
163
|
"error_occurred": (EventType.ERROR, "occurred"),
|
|
175
|
-
|
|
176
164
|
# Performance events
|
|
177
165
|
"performance": (EventType.PERFORMANCE, "metric"),
|
|
178
166
|
"performance_metric": (EventType.PERFORMANCE, "metric"),
|
|
179
167
|
}
|
|
180
|
-
|
|
168
|
+
|
|
181
169
|
# Patterns to extract event type from various formats
|
|
182
170
|
TYPE_PATTERNS = [
|
|
183
171
|
# Pattern 1: event_type field
|
|
@@ -189,26 +177,28 @@ class EventNormalizer:
|
|
|
189
177
|
# Pattern 4: Hook format (hook:event_name)
|
|
190
178
|
(r'"hook"\s*:\s*"([^"]+)"', lambda m: f"hook_{m.group(1)}"),
|
|
191
179
|
]
|
|
192
|
-
|
|
180
|
+
|
|
193
181
|
def __init__(self):
|
|
194
182
|
self.logger = get_logger(self.__class__.__name__)
|
|
195
183
|
self.stats = {
|
|
196
184
|
"normalized": 0,
|
|
197
185
|
"already_normalized": 0,
|
|
198
186
|
"unknown_format": 0,
|
|
199
|
-
"errors": 0
|
|
187
|
+
"errors": 0,
|
|
200
188
|
}
|
|
201
|
-
|
|
202
|
-
def normalize(
|
|
189
|
+
|
|
190
|
+
def normalize(
|
|
191
|
+
self, event_data: Any, source: Optional[str] = None
|
|
192
|
+
) -> NormalizedEvent:
|
|
203
193
|
"""Normalize an event to the standard schema.
|
|
204
|
-
|
|
194
|
+
|
|
205
195
|
WHY: This method handles various input formats and transforms them
|
|
206
196
|
into a consistent structure that all clients can understand.
|
|
207
|
-
|
|
197
|
+
|
|
208
198
|
Args:
|
|
209
199
|
event_data: The event data in any supported format
|
|
210
200
|
source: Optional source override (e.g., "hook", "dashboard", "test")
|
|
211
|
-
|
|
201
|
+
|
|
212
202
|
Returns:
|
|
213
203
|
NormalizedEvent with consistent structure
|
|
214
204
|
"""
|
|
@@ -217,16 +207,16 @@ class EventNormalizer:
|
|
|
217
207
|
if self._is_normalized(event_data):
|
|
218
208
|
self.stats["already_normalized"] += 1
|
|
219
209
|
return self._validate_normalized(event_data)
|
|
220
|
-
|
|
210
|
+
|
|
221
211
|
# Extract event information from various formats
|
|
222
212
|
event_type, subtype, data = self._extract_event_info(event_data)
|
|
223
|
-
|
|
213
|
+
|
|
224
214
|
# Determine event source
|
|
225
215
|
event_source = self._determine_source(event_data, event_type, source)
|
|
226
|
-
|
|
216
|
+
|
|
227
217
|
# Get or generate timestamp
|
|
228
218
|
timestamp = self._extract_timestamp(event_data)
|
|
229
|
-
|
|
219
|
+
|
|
230
220
|
# Create normalized event
|
|
231
221
|
normalized = NormalizedEvent(
|
|
232
222
|
event="claude_event",
|
|
@@ -234,18 +224,18 @@ class EventNormalizer:
|
|
|
234
224
|
type=event_type,
|
|
235
225
|
subtype=subtype,
|
|
236
226
|
timestamp=timestamp,
|
|
237
|
-
data=data
|
|
227
|
+
data=data,
|
|
238
228
|
)
|
|
239
|
-
|
|
229
|
+
|
|
240
230
|
self.stats["normalized"] += 1
|
|
241
231
|
self.logger.debug(f"Normalized event: {event_type}/{subtype}")
|
|
242
|
-
|
|
232
|
+
|
|
243
233
|
return normalized
|
|
244
|
-
|
|
234
|
+
|
|
245
235
|
except Exception as e:
|
|
246
236
|
self.stats["errors"] += 1
|
|
247
237
|
self.logger.error(f"Failed to normalize event: {e}")
|
|
248
|
-
|
|
238
|
+
|
|
249
239
|
# Return a generic event on error
|
|
250
240
|
return NormalizedEvent(
|
|
251
241
|
event="claude_event",
|
|
@@ -253,24 +243,24 @@ class EventNormalizer:
|
|
|
253
243
|
type="unknown",
|
|
254
244
|
subtype="error",
|
|
255
245
|
timestamp=datetime.now().isoformat(),
|
|
256
|
-
data={"original": str(event_data), "error": str(e)}
|
|
246
|
+
data={"original": str(event_data), "error": str(e)},
|
|
257
247
|
)
|
|
258
|
-
|
|
248
|
+
|
|
259
249
|
def _is_normalized(self, event_data: Any) -> bool:
|
|
260
250
|
"""Check if event is already in normalized format.
|
|
261
|
-
|
|
251
|
+
|
|
262
252
|
WHY: Avoid double-normalization and preserve already correct events.
|
|
263
253
|
"""
|
|
264
254
|
if not isinstance(event_data, dict):
|
|
265
255
|
return False
|
|
266
|
-
|
|
256
|
+
|
|
267
257
|
# Check for normalized format (must have source, type, subtype, timestamp, and data)
|
|
268
258
|
required_fields = {"source", "type", "subtype", "timestamp", "data"}
|
|
269
259
|
return all(field in event_data for field in required_fields)
|
|
270
|
-
|
|
260
|
+
|
|
271
261
|
def _validate_normalized(self, event_data: Dict[str, Any]) -> NormalizedEvent:
|
|
272
262
|
"""Validate and convert an already normalized event.
|
|
273
|
-
|
|
263
|
+
|
|
274
264
|
WHY: Ensure even pre-normalized events are valid and properly typed.
|
|
275
265
|
"""
|
|
276
266
|
# Map source if it's a known indicator
|
|
@@ -280,19 +270,19 @@ class EventNormalizer:
|
|
|
280
270
|
elif source not in [e.value for e in EventSource]:
|
|
281
271
|
# If source is not a valid EventSource value, keep it as-is
|
|
282
272
|
pass
|
|
283
|
-
|
|
273
|
+
|
|
284
274
|
return NormalizedEvent(
|
|
285
275
|
event="claude_event", # Always use standard event name
|
|
286
276
|
source=source,
|
|
287
277
|
type=event_data.get("type", "unknown"),
|
|
288
278
|
subtype=event_data.get("subtype", "generic"),
|
|
289
279
|
timestamp=event_data.get("timestamp", datetime.now().isoformat()),
|
|
290
|
-
data=event_data.get("data", {})
|
|
280
|
+
data=event_data.get("data", {}),
|
|
291
281
|
)
|
|
292
|
-
|
|
282
|
+
|
|
293
283
|
def _extract_event_info(self, event_data: Any) -> Tuple[str, str, Dict[str, Any]]:
|
|
294
284
|
"""Extract event type, subtype, and data from various formats.
|
|
295
|
-
|
|
285
|
+
|
|
296
286
|
WHY: The system has multiple event formats that need to be handled:
|
|
297
287
|
- Simple strings (event names)
|
|
298
288
|
- Dictionaries with type field
|
|
@@ -303,7 +293,7 @@ class EventNormalizer:
|
|
|
303
293
|
if isinstance(event_data, str):
|
|
304
294
|
event_type, subtype = self._map_event_name(event_data)
|
|
305
295
|
return event_type, subtype, {"event_name": event_data}
|
|
306
|
-
|
|
296
|
+
|
|
307
297
|
# Handle dictionary events
|
|
308
298
|
if isinstance(event_data, dict):
|
|
309
299
|
# Special case: type="hook" with event field (legacy hook format)
|
|
@@ -312,25 +302,25 @@ class EventNormalizer:
|
|
|
312
302
|
subtype = event_data["event"]
|
|
313
303
|
data = self._extract_data_payload(event_data)
|
|
314
304
|
return event_type, subtype, data
|
|
315
|
-
|
|
305
|
+
|
|
316
306
|
# Try to extract event name/type
|
|
317
307
|
event_name = self._extract_event_name(event_data)
|
|
318
|
-
|
|
308
|
+
|
|
319
309
|
# Map to type and subtype
|
|
320
310
|
event_type, subtype = self._map_event_name(event_name)
|
|
321
|
-
|
|
311
|
+
|
|
322
312
|
# Extract data payload
|
|
323
313
|
data = self._extract_data_payload(event_data)
|
|
324
|
-
|
|
314
|
+
|
|
325
315
|
return event_type, subtype, data
|
|
326
|
-
|
|
316
|
+
|
|
327
317
|
# Unknown format
|
|
328
318
|
self.stats["unknown_format"] += 1
|
|
329
319
|
return "unknown", "generic", {"original": str(event_data)}
|
|
330
|
-
|
|
320
|
+
|
|
331
321
|
def _extract_event_name(self, event_dict: Dict[str, Any]) -> str:
|
|
332
322
|
"""Extract event name from dictionary.
|
|
333
|
-
|
|
323
|
+
|
|
334
324
|
WHY: Events use different field names for the event identifier.
|
|
335
325
|
"""
|
|
336
326
|
# Priority order for event name fields
|
|
@@ -339,26 +329,28 @@ class EventNormalizer:
|
|
|
339
329
|
value = event_dict[field]
|
|
340
330
|
if isinstance(value, str):
|
|
341
331
|
return value
|
|
342
|
-
|
|
332
|
+
|
|
343
333
|
# Try to extract from JSON string representation
|
|
344
334
|
event_str = str(event_dict)
|
|
345
335
|
for pattern, extractor in self.TYPE_PATTERNS:
|
|
346
336
|
match = re.search(pattern, event_str)
|
|
347
337
|
if match:
|
|
348
338
|
return extractor(match)
|
|
349
|
-
|
|
339
|
+
|
|
350
340
|
return "unknown"
|
|
351
|
-
|
|
341
|
+
|
|
352
342
|
def _map_event_name(self, event_name: str) -> Tuple[str, str]:
|
|
353
343
|
"""Map event name to (type, subtype) tuple.
|
|
354
|
-
|
|
344
|
+
|
|
355
345
|
WHY: Consistent categorization helps clients filter and handle events.
|
|
356
346
|
"""
|
|
357
347
|
# Direct mapping
|
|
358
348
|
if event_name in self.EVENT_MAPPINGS:
|
|
359
349
|
event_type, subtype = self.EVENT_MAPPINGS[event_name]
|
|
360
|
-
return
|
|
361
|
-
|
|
350
|
+
return (
|
|
351
|
+
event_type.value if isinstance(event_type, EventType) else event_type
|
|
352
|
+
), subtype
|
|
353
|
+
|
|
362
354
|
# Handle dotted event names (e.g., "connection.status", "session.started")
|
|
363
355
|
if "." in event_name:
|
|
364
356
|
parts = event_name.split(".", 1)
|
|
@@ -366,19 +358,34 @@ class EventNormalizer:
|
|
|
366
358
|
type_part, subtype_part = parts
|
|
367
359
|
# Map the type part to known types
|
|
368
360
|
type_lower = type_part.lower()
|
|
369
|
-
if type_lower in [
|
|
370
|
-
|
|
371
|
-
|
|
361
|
+
if type_lower in [
|
|
362
|
+
"hook",
|
|
363
|
+
"session",
|
|
364
|
+
"file",
|
|
365
|
+
"system",
|
|
366
|
+
"connection",
|
|
367
|
+
"memory",
|
|
368
|
+
"git",
|
|
369
|
+
"todo",
|
|
370
|
+
"ticket",
|
|
371
|
+
"agent",
|
|
372
|
+
"claude",
|
|
373
|
+
"error",
|
|
374
|
+
"performance",
|
|
375
|
+
"test",
|
|
376
|
+
"tool",
|
|
377
|
+
"subagent",
|
|
378
|
+
]:
|
|
372
379
|
return type_lower, subtype_part
|
|
373
|
-
|
|
380
|
+
|
|
374
381
|
# Try to infer from event name patterns
|
|
375
382
|
event_lower = event_name.lower()
|
|
376
|
-
|
|
383
|
+
|
|
377
384
|
# Check if event name matches a known EventType value directly
|
|
378
385
|
for event_type_enum in EventType:
|
|
379
386
|
if event_lower == event_type_enum.value:
|
|
380
387
|
return event_type_enum.value, "generic"
|
|
381
|
-
|
|
388
|
+
|
|
382
389
|
# Hook events (hook_* or *_hook or hook.*)
|
|
383
390
|
if "hook" in event_lower:
|
|
384
391
|
# Handle "hook.event_name" format
|
|
@@ -388,83 +395,86 @@ class EventNormalizer:
|
|
|
388
395
|
if len(parts) > 1:
|
|
389
396
|
return EventType.HOOK.value, parts[1]
|
|
390
397
|
# Handle pre_ and post_ prefixes
|
|
391
|
-
if event_lower.startswith("pre_"):
|
|
398
|
+
if event_lower.startswith(("pre_", "post_")):
|
|
392
399
|
return EventType.HOOK.value, event_lower
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
else:
|
|
396
|
-
return EventType.HOOK.value, "generic"
|
|
397
|
-
|
|
400
|
+
return EventType.HOOK.value, "generic"
|
|
401
|
+
|
|
398
402
|
# Session events
|
|
399
403
|
if "session" in event_lower:
|
|
400
404
|
if "start" in event_lower:
|
|
401
405
|
return EventType.SESSION.value, "started"
|
|
402
|
-
|
|
406
|
+
if "end" in event_lower:
|
|
403
407
|
return EventType.SESSION.value, "ended"
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
408
|
+
return EventType.SESSION.value, "generic"
|
|
409
|
+
|
|
407
410
|
# File events
|
|
408
411
|
if "file" in event_lower:
|
|
409
412
|
if "create" in event_lower:
|
|
410
413
|
return EventType.FILE.value, "created"
|
|
411
|
-
|
|
414
|
+
if "delete" in event_lower:
|
|
412
415
|
return EventType.FILE.value, "deleted"
|
|
413
|
-
|
|
416
|
+
if "change" in event_lower or "modify" in event_lower:
|
|
414
417
|
return EventType.FILE.value, "changed"
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
+
return EventType.FILE.value, "generic"
|
|
419
|
+
|
|
418
420
|
# System events
|
|
419
421
|
if "system" in event_lower or "heartbeat" in event_lower:
|
|
420
422
|
if "heartbeat" in event_lower:
|
|
421
423
|
return EventType.SYSTEM.value, "heartbeat"
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
424
|
+
return EventType.SYSTEM.value, "status"
|
|
425
|
+
|
|
425
426
|
# Connection events
|
|
426
427
|
if "connect" in event_lower or "client" in event_lower:
|
|
427
428
|
if "disconnect" in event_lower:
|
|
428
429
|
return EventType.CONNECTION.value, "disconnected"
|
|
429
|
-
|
|
430
|
+
if "connect" in event_lower:
|
|
430
431
|
return EventType.CONNECTION.value, "connected"
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
432
|
+
return EventType.CONNECTION.value, "generic"
|
|
433
|
+
|
|
434
434
|
# Memory events
|
|
435
435
|
if "memory" in event_lower:
|
|
436
436
|
if "load" in event_lower:
|
|
437
437
|
return EventType.MEMORY.value, "loaded"
|
|
438
|
-
|
|
438
|
+
if "create" in event_lower:
|
|
439
439
|
return EventType.MEMORY.value, "created"
|
|
440
|
-
|
|
440
|
+
if "update" in event_lower:
|
|
441
441
|
return EventType.MEMORY.value, "updated"
|
|
442
|
-
|
|
442
|
+
if "inject" in event_lower:
|
|
443
443
|
return EventType.MEMORY.value, "injected"
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
444
|
+
return EventType.MEMORY.value, "generic"
|
|
445
|
+
|
|
447
446
|
# Default to unknown with lowercase subtype
|
|
448
447
|
return "unknown", event_name.lower() if event_name else ""
|
|
449
|
-
|
|
448
|
+
|
|
450
449
|
def _extract_data_payload(self, event_dict: Dict[str, Any]) -> Dict[str, Any]:
|
|
451
450
|
"""Extract the data payload from an event dictionary.
|
|
452
|
-
|
|
451
|
+
|
|
453
452
|
WHY: Different event formats store the payload in different places.
|
|
454
453
|
"""
|
|
455
454
|
# If there's a explicit data field, use it
|
|
456
455
|
if "data" in event_dict:
|
|
457
|
-
return
|
|
458
|
-
|
|
456
|
+
return (
|
|
457
|
+
event_dict["data"]
|
|
458
|
+
if isinstance(event_dict["data"], dict)
|
|
459
|
+
else {"value": event_dict["data"]}
|
|
460
|
+
)
|
|
461
|
+
|
|
459
462
|
# Otherwise, use the entire dict minus metadata fields
|
|
460
|
-
metadata_fields = {
|
|
463
|
+
metadata_fields = {
|
|
464
|
+
"event",
|
|
465
|
+
"type",
|
|
466
|
+
"subtype",
|
|
467
|
+
"timestamp",
|
|
468
|
+
"event_type",
|
|
469
|
+
"hook",
|
|
470
|
+
}
|
|
461
471
|
data = {k: v for k, v in event_dict.items() if k not in metadata_fields}
|
|
462
|
-
|
|
472
|
+
|
|
463
473
|
return data if data else event_dict
|
|
464
|
-
|
|
474
|
+
|
|
465
475
|
def _extract_timestamp(self, event_data: Any) -> str:
|
|
466
476
|
"""Extract or generate timestamp.
|
|
467
|
-
|
|
477
|
+
|
|
468
478
|
WHY: Consistent timestamp format is essential for event ordering
|
|
469
479
|
and debugging.
|
|
470
480
|
"""
|
|
@@ -482,28 +492,30 @@ class EventNormalizer:
|
|
|
482
492
|
return datetime.fromtimestamp(timestamp).isoformat()
|
|
483
493
|
except:
|
|
484
494
|
pass
|
|
485
|
-
|
|
495
|
+
|
|
486
496
|
# Generate new timestamp if not found
|
|
487
497
|
return datetime.now().isoformat()
|
|
488
|
-
|
|
489
|
-
def _determine_source(
|
|
498
|
+
|
|
499
|
+
def _determine_source(
|
|
500
|
+
self, event_data: Any, event_type: str, source_override: Optional[str] = None
|
|
501
|
+
) -> str:
|
|
490
502
|
"""Determine the source of an event.
|
|
491
|
-
|
|
503
|
+
|
|
492
504
|
WHY: Knowing where events originate helps with debugging,
|
|
493
505
|
filtering, and understanding system behavior.
|
|
494
|
-
|
|
506
|
+
|
|
495
507
|
Args:
|
|
496
508
|
event_data: The raw event data
|
|
497
509
|
event_type: The determined event type
|
|
498
510
|
source_override: Optional explicit source
|
|
499
|
-
|
|
511
|
+
|
|
500
512
|
Returns:
|
|
501
513
|
The event source as a string
|
|
502
514
|
"""
|
|
503
515
|
# Use explicit source override if provided
|
|
504
516
|
if source_override:
|
|
505
517
|
return source_override
|
|
506
|
-
|
|
518
|
+
|
|
507
519
|
# Check if event data contains source field
|
|
508
520
|
if isinstance(event_data, dict):
|
|
509
521
|
# Direct source field
|
|
@@ -519,48 +531,55 @@ class EventNormalizer:
|
|
|
519
531
|
return source
|
|
520
532
|
# Otherwise, keep the original source value
|
|
521
533
|
return source
|
|
522
|
-
|
|
534
|
+
|
|
523
535
|
# Check for indicators of specific sources
|
|
524
536
|
# Test indicator - only if type is actually "test"
|
|
525
|
-
if event_type == "test" or (
|
|
537
|
+
if event_type == "test" or (
|
|
538
|
+
isinstance(event_data.get("type"), str)
|
|
539
|
+
and event_data.get("type") == "test"
|
|
540
|
+
):
|
|
526
541
|
return EventSource.TEST.value
|
|
527
|
-
|
|
542
|
+
|
|
528
543
|
# Dashboard indicator
|
|
529
544
|
if "dashboard" in str(event_data).lower() or "ui_action" in event_data:
|
|
530
545
|
return EventSource.DASHBOARD.value
|
|
531
|
-
|
|
546
|
+
|
|
532
547
|
# CLI indicator
|
|
533
548
|
if "cli" in str(event_data).lower() or "command" in event_data:
|
|
534
549
|
return EventSource.CLI.value
|
|
535
|
-
|
|
550
|
+
|
|
536
551
|
# API indicator
|
|
537
552
|
if "api" in str(event_data).lower() or "endpoint" in event_data:
|
|
538
553
|
return EventSource.API.value
|
|
539
|
-
|
|
554
|
+
|
|
540
555
|
# Infer from event type
|
|
541
556
|
if event_type == EventType.HOOK.value:
|
|
542
557
|
return EventSource.HOOK.value
|
|
543
|
-
|
|
558
|
+
if event_type == EventType.TEST.value:
|
|
544
559
|
return EventSource.TEST.value
|
|
545
|
-
|
|
560
|
+
if event_type in [EventType.AGENT.value, EventType.SUBAGENT.value]:
|
|
546
561
|
return EventSource.AGENT.value
|
|
547
|
-
|
|
548
|
-
|
|
562
|
+
if event_type in [
|
|
563
|
+
EventType.SYSTEM.value,
|
|
564
|
+
EventType.SESSION.value,
|
|
565
|
+
EventType.CONNECTION.value,
|
|
566
|
+
EventType.PERFORMANCE.value,
|
|
567
|
+
]:
|
|
549
568
|
return EventSource.SYSTEM.value
|
|
550
|
-
|
|
569
|
+
|
|
551
570
|
# Default to system source
|
|
552
571
|
return EventSource.SYSTEM.value
|
|
553
|
-
|
|
572
|
+
|
|
554
573
|
def get_stats(self) -> Dict[str, int]:
|
|
555
574
|
"""Get normalization statistics.
|
|
556
|
-
|
|
575
|
+
|
|
557
576
|
WHY: Monitoring normalization helps identify problematic event sources.
|
|
558
577
|
"""
|
|
559
578
|
return self.stats.copy()
|
|
560
|
-
|
|
579
|
+
|
|
561
580
|
def reset_stats(self):
|
|
562
581
|
"""Reset statistics counters.
|
|
563
|
-
|
|
582
|
+
|
|
564
583
|
WHY: Periodic reset prevents counter overflow and enables
|
|
565
584
|
rate calculations.
|
|
566
585
|
"""
|
|
@@ -568,100 +587,99 @@ class EventNormalizer:
|
|
|
568
587
|
"normalized": 0,
|
|
569
588
|
"already_normalized": 0,
|
|
570
589
|
"unknown_format": 0,
|
|
571
|
-
"errors": 0
|
|
590
|
+
"errors": 0,
|
|
572
591
|
}
|
|
573
592
|
|
|
574
593
|
|
|
575
594
|
# Utility functions for consistent event type checking
|
|
576
595
|
def is_hook_event(event_data: Dict[str, Any]) -> bool:
|
|
577
596
|
"""Check if an event is a hook event (handles both normalized and legacy formats).
|
|
578
|
-
|
|
597
|
+
|
|
579
598
|
WHY: Hook events can come in multiple formats and we need consistent checking
|
|
580
599
|
across the codebase to avoid missing events.
|
|
581
|
-
|
|
600
|
+
|
|
582
601
|
Args:
|
|
583
602
|
event_data: Event dictionary to check
|
|
584
|
-
|
|
603
|
+
|
|
585
604
|
Returns:
|
|
586
605
|
True if this is a hook event, False otherwise
|
|
587
606
|
"""
|
|
588
607
|
if not isinstance(event_data, dict):
|
|
589
608
|
return False
|
|
590
|
-
|
|
609
|
+
|
|
591
610
|
event_type = event_data.get("type", "")
|
|
592
|
-
|
|
611
|
+
|
|
593
612
|
# Check normalized format: type="hook"
|
|
594
613
|
if event_type == "hook":
|
|
595
614
|
return True
|
|
596
|
-
|
|
615
|
+
|
|
597
616
|
# Check legacy format: type="hook.something"
|
|
598
|
-
|
|
599
|
-
return True
|
|
600
|
-
|
|
601
|
-
return False
|
|
617
|
+
return bool(isinstance(event_type, str) and event_type.startswith("hook."))
|
|
602
618
|
|
|
603
619
|
|
|
604
620
|
def get_hook_event_name(event_data: Dict[str, Any]) -> str:
|
|
605
621
|
"""Extract the hook event name from either normalized or legacy format.
|
|
606
|
-
|
|
622
|
+
|
|
607
623
|
WHY: Hook events store their specific name differently in normalized vs legacy
|
|
608
624
|
formats, and we need a consistent way to extract it.
|
|
609
|
-
|
|
625
|
+
|
|
610
626
|
Args:
|
|
611
627
|
event_data: Event dictionary containing a hook event
|
|
612
|
-
|
|
628
|
+
|
|
613
629
|
Returns:
|
|
614
630
|
The specific hook event name (e.g., "pre_tool", "user_prompt")
|
|
615
631
|
or empty string if not a hook event
|
|
616
632
|
"""
|
|
617
633
|
if not is_hook_event(event_data):
|
|
618
634
|
return ""
|
|
619
|
-
|
|
635
|
+
|
|
620
636
|
event_type = event_data.get("type", "")
|
|
621
637
|
event_subtype = event_data.get("subtype", "")
|
|
622
|
-
|
|
638
|
+
|
|
623
639
|
# Normalized format: type="hook", subtype="pre_tool"
|
|
624
640
|
if event_type == "hook" and event_subtype:
|
|
625
641
|
return event_subtype
|
|
626
|
-
|
|
642
|
+
|
|
627
643
|
# Legacy format: type="hook.pre_tool"
|
|
628
644
|
if isinstance(event_type, str) and event_type.startswith("hook."):
|
|
629
645
|
return event_type[5:] # Remove "hook." prefix
|
|
630
|
-
|
|
646
|
+
|
|
631
647
|
# Fallback: check 'event' field (another legacy format)
|
|
632
648
|
return event_data.get("event", "")
|
|
633
649
|
|
|
634
650
|
|
|
635
|
-
def is_event_type(
|
|
651
|
+
def is_event_type(
|
|
652
|
+
event_data: Dict[str, Any], type_name: str, subtype: Optional[str] = None
|
|
653
|
+
) -> bool:
|
|
636
654
|
"""Check if an event matches a specific type and optionally subtype.
|
|
637
|
-
|
|
655
|
+
|
|
638
656
|
WHY: This provides a consistent way to check event types that works with
|
|
639
657
|
both normalized and legacy formats.
|
|
640
|
-
|
|
658
|
+
|
|
641
659
|
Args:
|
|
642
660
|
event_data: Event dictionary to check
|
|
643
661
|
type_name: The type to check for (e.g., "hook", "session", "file")
|
|
644
662
|
subtype: Optional subtype to also check (e.g., "pre_tool", "started")
|
|
645
|
-
|
|
663
|
+
|
|
646
664
|
Returns:
|
|
647
665
|
True if the event matches the specified type (and subtype if provided)
|
|
648
666
|
"""
|
|
649
667
|
if not isinstance(event_data, dict):
|
|
650
668
|
return False
|
|
651
|
-
|
|
669
|
+
|
|
652
670
|
event_type = event_data.get("type", "")
|
|
653
671
|
event_subtype = event_data.get("subtype", "")
|
|
654
|
-
|
|
672
|
+
|
|
655
673
|
# Check normalized format
|
|
656
674
|
if event_type == type_name:
|
|
657
675
|
if subtype is None:
|
|
658
676
|
return True
|
|
659
677
|
return event_subtype == subtype
|
|
660
|
-
|
|
678
|
+
|
|
661
679
|
# Check legacy dotted format (e.g., "hook.pre_tool")
|
|
662
680
|
if subtype and isinstance(event_type, str):
|
|
663
681
|
legacy_type = f"{type_name}.{subtype}"
|
|
664
682
|
if event_type == legacy_type:
|
|
665
683
|
return True
|
|
666
|
-
|
|
667
|
-
return False
|
|
684
|
+
|
|
685
|
+
return False
|