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
|
@@ -16,18 +16,18 @@ from dataclasses import dataclass
|
|
|
16
16
|
from datetime import datetime
|
|
17
17
|
from typing import Any, Deque, Dict, List, Optional, Set
|
|
18
18
|
|
|
19
|
-
from ....core.logging_config import get_logger
|
|
20
19
|
from ..event_normalizer import EventNormalizer
|
|
21
20
|
|
|
22
21
|
|
|
23
22
|
@dataclass
|
|
24
23
|
class RetryableEvent:
|
|
25
24
|
"""Represents an event that can be retried on failure.
|
|
26
|
-
|
|
25
|
+
|
|
27
26
|
WHY: Network failures are common and transient. By tracking retry
|
|
28
27
|
attempts, we can recover from temporary issues while avoiding
|
|
29
28
|
infinite retry loops.
|
|
30
29
|
"""
|
|
30
|
+
|
|
31
31
|
event_type: str
|
|
32
32
|
data: Dict[str, Any]
|
|
33
33
|
attempt_count: int = 0
|
|
@@ -35,82 +35,73 @@ class RetryableEvent:
|
|
|
35
35
|
created_at: float = None
|
|
36
36
|
last_attempt: float = None
|
|
37
37
|
skip_sid: Optional[str] = None
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
def __post_init__(self):
|
|
40
40
|
if self.created_at is None:
|
|
41
41
|
self.created_at = time.time()
|
|
42
42
|
if self.last_attempt is None:
|
|
43
43
|
self.last_attempt = time.time()
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
def should_retry(self) -> bool:
|
|
46
46
|
"""Check if this event should be retried.
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
WHY: We need to balance reliability with resource usage.
|
|
49
49
|
Events older than 30 seconds or with too many attempts
|
|
50
50
|
should be abandoned.
|
|
51
51
|
"""
|
|
52
52
|
if self.attempt_count >= self.max_retries:
|
|
53
53
|
return False
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
# Don't retry events older than 30 seconds
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
return True
|
|
60
|
-
|
|
56
|
+
return not time.time() - self.created_at > 30
|
|
57
|
+
|
|
61
58
|
def get_backoff_delay(self) -> float:
|
|
62
59
|
"""Calculate exponential backoff delay.
|
|
63
|
-
|
|
60
|
+
|
|
64
61
|
WHY: Exponential backoff prevents overwhelming the system
|
|
65
62
|
during recovery from failures.
|
|
66
63
|
"""
|
|
67
64
|
base_delay = 1.0 # 1 second
|
|
68
|
-
max_delay = 8.0
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
return delay
|
|
65
|
+
max_delay = 8.0 # 8 seconds max
|
|
66
|
+
|
|
67
|
+
return min(base_delay * (2**self.attempt_count), max_delay)
|
|
72
68
|
|
|
73
69
|
|
|
74
70
|
class RetryQueue:
|
|
75
71
|
"""Manages retry queue for failed event broadcasts.
|
|
76
|
-
|
|
72
|
+
|
|
77
73
|
WHY: Transient network issues shouldn't cause event loss.
|
|
78
74
|
This queue provides resilient event delivery with backoff.
|
|
79
75
|
"""
|
|
80
|
-
|
|
76
|
+
|
|
81
77
|
def __init__(self, max_size: int = 1000):
|
|
82
78
|
self.queue: Deque[RetryableEvent] = deque(maxlen=max_size)
|
|
83
79
|
self.lock = asyncio.Lock()
|
|
84
|
-
self.stats = {
|
|
85
|
-
|
|
86
|
-
'retried': 0,
|
|
87
|
-
'succeeded': 0,
|
|
88
|
-
'abandoned': 0
|
|
89
|
-
}
|
|
90
|
-
|
|
80
|
+
self.stats = {"queued": 0, "retried": 0, "succeeded": 0, "abandoned": 0}
|
|
81
|
+
|
|
91
82
|
async def add(self, event: RetryableEvent) -> None:
|
|
92
83
|
"""Add an event to the retry queue."""
|
|
93
84
|
async with self.lock:
|
|
94
85
|
self.queue.append(event)
|
|
95
|
-
self.stats[
|
|
96
|
-
|
|
86
|
+
self.stats["queued"] += 1
|
|
87
|
+
|
|
97
88
|
async def get_ready_events(self) -> List[RetryableEvent]:
|
|
98
89
|
"""Get events that are ready for retry.
|
|
99
|
-
|
|
90
|
+
|
|
100
91
|
WHY: We need to respect backoff delays to avoid
|
|
101
92
|
overwhelming the system during recovery.
|
|
102
93
|
"""
|
|
103
94
|
async with self.lock:
|
|
104
95
|
current_time = time.time()
|
|
105
96
|
ready = []
|
|
106
|
-
|
|
97
|
+
|
|
107
98
|
# Check each event in queue
|
|
108
99
|
remaining = []
|
|
109
100
|
for event in self.queue:
|
|
110
101
|
if not event.should_retry():
|
|
111
|
-
self.stats[
|
|
102
|
+
self.stats["abandoned"] += 1
|
|
112
103
|
continue
|
|
113
|
-
|
|
104
|
+
|
|
114
105
|
# First attempt (attempt_count == 0) should be immediate
|
|
115
106
|
if event.attempt_count == 0:
|
|
116
107
|
ready.append(event)
|
|
@@ -121,32 +112,29 @@ class RetryQueue:
|
|
|
121
112
|
ready.append(event)
|
|
122
113
|
else:
|
|
123
114
|
remaining.append(event)
|
|
124
|
-
|
|
115
|
+
|
|
125
116
|
# Update queue with events not ready yet
|
|
126
117
|
self.queue.clear()
|
|
127
118
|
self.queue.extend(remaining)
|
|
128
|
-
|
|
119
|
+
|
|
129
120
|
return ready
|
|
130
|
-
|
|
121
|
+
|
|
131
122
|
async def mark_success(self, event: RetryableEvent) -> None:
|
|
132
123
|
"""Mark an event as successfully sent."""
|
|
133
|
-
self.stats[
|
|
134
|
-
|
|
124
|
+
self.stats["succeeded"] += 1
|
|
125
|
+
|
|
135
126
|
async def mark_retry(self, event: RetryableEvent) -> None:
|
|
136
127
|
"""Mark an event for retry."""
|
|
137
128
|
event.attempt_count += 1
|
|
138
129
|
event.last_attempt = time.time()
|
|
139
|
-
self.stats[
|
|
140
|
-
|
|
130
|
+
self.stats["retried"] += 1
|
|
131
|
+
|
|
141
132
|
if event.should_retry():
|
|
142
133
|
await self.add(event)
|
|
143
|
-
|
|
134
|
+
|
|
144
135
|
def get_stats(self) -> Dict[str, int]:
|
|
145
136
|
"""Get retry queue statistics."""
|
|
146
|
-
return {
|
|
147
|
-
**self.stats,
|
|
148
|
-
'queue_size': len(self.queue)
|
|
149
|
-
}
|
|
137
|
+
return {**self.stats, "queue_size": len(self.queue)}
|
|
150
138
|
|
|
151
139
|
|
|
152
140
|
class SocketIOEventBroadcaster:
|
|
@@ -174,21 +162,21 @@ class SocketIOEventBroadcaster:
|
|
|
174
162
|
self.logger = logger
|
|
175
163
|
self.loop = None # Will be set by main server
|
|
176
164
|
self.server = server # Reference to main server for event history
|
|
177
|
-
|
|
165
|
+
|
|
178
166
|
# Initialize retry queue for resilient delivery
|
|
179
167
|
self.retry_queue = RetryQueue(max_size=1000)
|
|
180
168
|
self.retry_task = None
|
|
181
169
|
self.retry_interval = 2.0 # Process retry queue every 2 seconds
|
|
182
|
-
|
|
170
|
+
|
|
183
171
|
# Initialize event normalizer for consistent schema
|
|
184
172
|
self.normalizer = EventNormalizer()
|
|
185
|
-
|
|
173
|
+
|
|
186
174
|
def start_retry_processor(self):
|
|
187
175
|
"""Start the background retry processor.
|
|
188
|
-
|
|
176
|
+
|
|
189
177
|
WHY: Failed broadcasts need to be retried automatically
|
|
190
178
|
to ensure reliable event delivery.
|
|
191
|
-
|
|
179
|
+
|
|
192
180
|
IMPORTANT: This method must handle being called from a different thread
|
|
193
181
|
than the one running the event loop.
|
|
194
182
|
"""
|
|
@@ -199,8 +187,12 @@ class SocketIOEventBroadcaster:
|
|
|
199
187
|
running_loop = asyncio.get_running_loop()
|
|
200
188
|
if running_loop == self.loop:
|
|
201
189
|
# Same thread, can use create_task directly
|
|
202
|
-
self.retry_task = asyncio.create_task(
|
|
203
|
-
|
|
190
|
+
self.retry_task = asyncio.create_task(
|
|
191
|
+
self._process_retry_queue()
|
|
192
|
+
)
|
|
193
|
+
self.logger.info(
|
|
194
|
+
"🔄 Started retry queue processor (same thread)"
|
|
195
|
+
)
|
|
204
196
|
else:
|
|
205
197
|
# Different thread, need to schedule in the target loop
|
|
206
198
|
self._start_retry_in_loop()
|
|
@@ -209,13 +201,14 @@ class SocketIOEventBroadcaster:
|
|
|
209
201
|
self._start_retry_in_loop()
|
|
210
202
|
except Exception as e:
|
|
211
203
|
self.logger.error(f"Failed to start retry processor: {e}")
|
|
212
|
-
|
|
204
|
+
|
|
213
205
|
def _start_retry_in_loop(self):
|
|
214
206
|
"""Helper to start retry processor from a different thread."""
|
|
207
|
+
|
|
215
208
|
async def _create_retry_task():
|
|
216
209
|
self.retry_task = asyncio.create_task(self._process_retry_queue())
|
|
217
210
|
self.logger.info("🔄 Started retry queue processor (cross-thread)")
|
|
218
|
-
|
|
211
|
+
|
|
219
212
|
# Schedule the task creation in the target loop
|
|
220
213
|
future = asyncio.run_coroutine_threadsafe(_create_retry_task(), self.loop)
|
|
221
214
|
try:
|
|
@@ -223,43 +216,43 @@ class SocketIOEventBroadcaster:
|
|
|
223
216
|
future.result(timeout=1.0)
|
|
224
217
|
except Exception as e:
|
|
225
218
|
self.logger.error(f"Failed to schedule retry processor: {e}")
|
|
226
|
-
|
|
219
|
+
|
|
227
220
|
def stop_retry_processor(self):
|
|
228
221
|
"""Stop the background retry processor."""
|
|
229
222
|
if self.retry_task:
|
|
230
223
|
self.retry_task.cancel()
|
|
231
224
|
self.retry_task = None
|
|
232
225
|
self.logger.info("🚫 Stopped retry queue processor")
|
|
233
|
-
|
|
226
|
+
|
|
234
227
|
async def _process_retry_queue(self):
|
|
235
228
|
"""Process the retry queue periodically.
|
|
236
|
-
|
|
229
|
+
|
|
237
230
|
WHY: Regular processing ensures failed events are retried
|
|
238
231
|
with appropriate backoff delays.
|
|
239
232
|
"""
|
|
240
233
|
while True:
|
|
241
234
|
try:
|
|
242
235
|
await asyncio.sleep(self.retry_interval)
|
|
243
|
-
|
|
236
|
+
|
|
244
237
|
# Get events ready for retry
|
|
245
238
|
ready_events = await self.retry_queue.get_ready_events()
|
|
246
|
-
|
|
239
|
+
|
|
247
240
|
if ready_events:
|
|
248
241
|
self.logger.debug(
|
|
249
242
|
f"🔄 Processing {len(ready_events)} events from retry queue"
|
|
250
243
|
)
|
|
251
|
-
|
|
244
|
+
|
|
252
245
|
for event in ready_events:
|
|
253
246
|
success = await self._retry_broadcast(event)
|
|
254
|
-
|
|
247
|
+
|
|
255
248
|
if success:
|
|
256
249
|
await self.retry_queue.mark_success(event)
|
|
257
250
|
else:
|
|
258
251
|
await self.retry_queue.mark_retry(event)
|
|
259
|
-
|
|
252
|
+
|
|
260
253
|
# Log stats periodically
|
|
261
254
|
stats = self.retry_queue.get_stats()
|
|
262
|
-
if stats[
|
|
255
|
+
if stats["retried"] > 0 or stats["abandoned"] > 0:
|
|
263
256
|
self.logger.info(
|
|
264
257
|
f"📊 Retry queue stats - "
|
|
265
258
|
f"queued: {stats['queued']}, "
|
|
@@ -268,15 +261,15 @@ class SocketIOEventBroadcaster:
|
|
|
268
261
|
f"abandoned: {stats['abandoned']}, "
|
|
269
262
|
f"current size: {stats['queue_size']}"
|
|
270
263
|
)
|
|
271
|
-
|
|
264
|
+
|
|
272
265
|
except asyncio.CancelledError:
|
|
273
266
|
break
|
|
274
267
|
except Exception as e:
|
|
275
268
|
self.logger.error(f"Error processing retry queue: {e}")
|
|
276
|
-
|
|
269
|
+
|
|
277
270
|
async def _retry_broadcast(self, event: RetryableEvent) -> bool:
|
|
278
271
|
"""Retry broadcasting a failed event.
|
|
279
|
-
|
|
272
|
+
|
|
280
273
|
WHY: Isolated retry logic allows for special handling
|
|
281
274
|
and metrics tracking of retry attempts. Uses normalizer
|
|
282
275
|
to ensure consistent schema.
|
|
@@ -285,27 +278,27 @@ class SocketIOEventBroadcaster:
|
|
|
285
278
|
self.logger.debug(
|
|
286
279
|
f"🔄 Retrying {event.event_type} (attempt {event.attempt_count + 1}/{event.max_retries})"
|
|
287
280
|
)
|
|
288
|
-
|
|
281
|
+
|
|
289
282
|
# Reconstruct the raw event
|
|
290
283
|
raw_event = {
|
|
291
284
|
"type": event.event_type,
|
|
292
285
|
"timestamp": datetime.now().isoformat(),
|
|
293
286
|
"data": {**event.data, "retry_attempt": event.attempt_count + 1},
|
|
294
287
|
}
|
|
295
|
-
|
|
288
|
+
|
|
296
289
|
# Normalize the event
|
|
297
290
|
normalized = self.normalizer.normalize(raw_event)
|
|
298
291
|
full_event = normalized.to_dict()
|
|
299
|
-
|
|
292
|
+
|
|
300
293
|
# Attempt broadcast
|
|
301
294
|
if event.skip_sid:
|
|
302
295
|
await self.sio.emit("claude_event", full_event, skip_sid=event.skip_sid)
|
|
303
296
|
else:
|
|
304
297
|
await self.sio.emit("claude_event", full_event)
|
|
305
|
-
|
|
298
|
+
|
|
306
299
|
self.logger.debug(f"✅ Successfully retried {event.event_type}")
|
|
307
300
|
return True
|
|
308
|
-
|
|
301
|
+
|
|
309
302
|
except Exception as e:
|
|
310
303
|
self.logger.warning(
|
|
311
304
|
f"⚠️ Retry failed for {event.event_type} "
|
|
@@ -313,9 +306,11 @@ class SocketIOEventBroadcaster:
|
|
|
313
306
|
)
|
|
314
307
|
return False
|
|
315
308
|
|
|
316
|
-
def broadcast_event(
|
|
309
|
+
def broadcast_event(
|
|
310
|
+
self, event_type: str, data: Dict[str, Any], skip_sid: Optional[str] = None
|
|
311
|
+
):
|
|
317
312
|
"""Broadcast an event to all connected clients with retry support.
|
|
318
|
-
|
|
313
|
+
|
|
319
314
|
WHY: Enhanced with retry queue to ensure reliable delivery
|
|
320
315
|
even during transient network issues. Now uses EventNormalizer
|
|
321
316
|
to ensure consistent event schema.
|
|
@@ -329,7 +324,7 @@ class SocketIOEventBroadcaster:
|
|
|
329
324
|
"timestamp": datetime.now().isoformat(),
|
|
330
325
|
"data": data,
|
|
331
326
|
}
|
|
332
|
-
|
|
327
|
+
|
|
333
328
|
# Normalize the event to consistent schema
|
|
334
329
|
normalized = self.normalizer.normalize(raw_event)
|
|
335
330
|
event = normalized.to_dict()
|
|
@@ -338,12 +333,14 @@ class SocketIOEventBroadcaster:
|
|
|
338
333
|
with self.buffer_lock:
|
|
339
334
|
self.event_buffer.append(event)
|
|
340
335
|
self.stats["events_buffered"] += 1
|
|
341
|
-
|
|
336
|
+
|
|
342
337
|
# Also add to event history if available (for client replay)
|
|
343
338
|
# Access through server reference to maintain single history source
|
|
344
|
-
if hasattr(self,
|
|
339
|
+
if hasattr(self, "server") and hasattr(self.server, "event_history"):
|
|
345
340
|
self.server.event_history.append(event)
|
|
346
|
-
self.logger.debug(
|
|
341
|
+
self.logger.debug(
|
|
342
|
+
f"Added {event['type']}/{event['subtype']} to history (total: {len(self.server.event_history)})"
|
|
343
|
+
)
|
|
347
344
|
|
|
348
345
|
# Broadcast to all connected clients
|
|
349
346
|
broadcast_success = False
|
|
@@ -355,9 +352,9 @@ class SocketIOEventBroadcaster:
|
|
|
355
352
|
coro = self.sio.emit("claude_event", event, skip_sid=skip_sid)
|
|
356
353
|
else:
|
|
357
354
|
coro = self.sio.emit("claude_event", event)
|
|
358
|
-
|
|
355
|
+
|
|
359
356
|
future = asyncio.run_coroutine_threadsafe(coro, self.loop)
|
|
360
|
-
|
|
357
|
+
|
|
361
358
|
# Wait briefly to see if broadcast succeeds
|
|
362
359
|
try:
|
|
363
360
|
future.result(timeout=0.5) # 500ms timeout
|
|
@@ -374,21 +371,18 @@ class SocketIOEventBroadcaster:
|
|
|
374
371
|
|
|
375
372
|
except Exception as e:
|
|
376
373
|
self.logger.error(f"Failed to broadcast event {event_type}: {e}")
|
|
377
|
-
|
|
374
|
+
|
|
378
375
|
# Add to retry queue if broadcast failed
|
|
379
376
|
if not broadcast_success and self.loop:
|
|
380
377
|
retryable_event = RetryableEvent(
|
|
381
|
-
event_type=event_type,
|
|
382
|
-
data=data,
|
|
383
|
-
skip_sid=skip_sid
|
|
378
|
+
event_type=event_type, data=data, skip_sid=skip_sid
|
|
384
379
|
)
|
|
385
|
-
|
|
380
|
+
|
|
386
381
|
# Queue for retry
|
|
387
382
|
asyncio.run_coroutine_threadsafe(
|
|
388
|
-
self.retry_queue.add(retryable_event),
|
|
389
|
-
self.loop
|
|
383
|
+
self.retry_queue.add(retryable_event), self.loop
|
|
390
384
|
)
|
|
391
|
-
|
|
385
|
+
|
|
392
386
|
self.logger.warning(
|
|
393
387
|
f"⚠️ Queued {event_type} for retry (queue size: {len(self.retry_queue.queue)})"
|
|
394
388
|
)
|
|
@@ -529,10 +523,10 @@ class SocketIOEventBroadcaster:
|
|
|
529
523
|
def system_status(self, status: Dict[str, Any]):
|
|
530
524
|
"""Broadcast system status information."""
|
|
531
525
|
self.broadcast_event("system_status", status)
|
|
532
|
-
|
|
526
|
+
|
|
533
527
|
def broadcast_system_heartbeat(self, heartbeat_data: Dict[str, Any]):
|
|
534
528
|
"""Broadcast system heartbeat event.
|
|
535
|
-
|
|
529
|
+
|
|
536
530
|
WHY: System events are separate from hook events to provide
|
|
537
531
|
server health monitoring independent of Claude activity.
|
|
538
532
|
Now uses broadcast_event for consistency with buffering and normalization.
|