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
|
@@ -12,24 +12,24 @@ from ..logger import get_logger
|
|
|
12
12
|
class PathResolver:
|
|
13
13
|
"""
|
|
14
14
|
Centralized path resolution utility.
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
Reduces duplication by providing standard patterns for:
|
|
17
17
|
- Working directory resolution
|
|
18
18
|
- Configuration directory discovery
|
|
19
19
|
- Agent directory resolution
|
|
20
20
|
- Memory directory resolution
|
|
21
21
|
"""
|
|
22
|
-
|
|
23
|
-
def __init__(self, base_dir: Union[str, Path] = None):
|
|
22
|
+
|
|
23
|
+
def __init__(self, base_dir: Optional[Union[str, Path]] = None):
|
|
24
24
|
"""
|
|
25
25
|
Initialize path resolver.
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
Args:
|
|
28
28
|
base_dir: Base directory for relative paths
|
|
29
29
|
"""
|
|
30
30
|
self.base_dir = Path(base_dir) if base_dir else self._get_working_dir()
|
|
31
31
|
self.logger = get_logger("path_resolver")
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
def _get_working_dir(self) -> Path:
|
|
34
34
|
"""Get working directory respecting CLAUDE_MPM_USER_PWD."""
|
|
35
35
|
# Use CLAUDE_MPM_USER_PWD if available (when called via shell script)
|
|
@@ -37,111 +37,113 @@ class PathResolver:
|
|
|
37
37
|
if user_pwd:
|
|
38
38
|
return Path(user_pwd)
|
|
39
39
|
return Path.cwd()
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
def resolve_config_dir(self, create: bool = False) -> Path:
|
|
42
42
|
"""
|
|
43
43
|
Resolve configuration directory.
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
Args:
|
|
46
46
|
create: Whether to create directory if it doesn't exist
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
Returns:
|
|
49
49
|
Path to configuration directory
|
|
50
50
|
"""
|
|
51
51
|
config_dir = self.base_dir / ".claude-mpm"
|
|
52
|
-
|
|
52
|
+
|
|
53
53
|
if create and not config_dir.exists():
|
|
54
54
|
config_dir.mkdir(parents=True, exist_ok=True)
|
|
55
55
|
self.logger.debug(f"Created config directory: {config_dir}")
|
|
56
|
-
|
|
56
|
+
|
|
57
57
|
return config_dir
|
|
58
|
-
|
|
58
|
+
|
|
59
59
|
def resolve_agents_dir(self, create: bool = False) -> Path:
|
|
60
60
|
"""
|
|
61
61
|
Resolve agents directory.
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
Args:
|
|
64
64
|
create: Whether to create directory if it doesn't exist
|
|
65
|
-
|
|
65
|
+
|
|
66
66
|
Returns:
|
|
67
67
|
Path to agents directory
|
|
68
68
|
"""
|
|
69
69
|
agents_dir = self.resolve_config_dir(create) / "agents"
|
|
70
|
-
|
|
70
|
+
|
|
71
71
|
if create and not agents_dir.exists():
|
|
72
72
|
agents_dir.mkdir(parents=True, exist_ok=True)
|
|
73
73
|
self.logger.debug(f"Created agents directory: {agents_dir}")
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
return agents_dir
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
def resolve_memories_dir(self, create: bool = False) -> Path:
|
|
78
78
|
"""
|
|
79
79
|
Resolve memories directory.
|
|
80
|
-
|
|
80
|
+
|
|
81
81
|
Args:
|
|
82
82
|
create: Whether to create directory if it doesn't exist
|
|
83
|
-
|
|
83
|
+
|
|
84
84
|
Returns:
|
|
85
85
|
Path to memories directory
|
|
86
86
|
"""
|
|
87
87
|
memories_dir = self.resolve_config_dir(create) / "memories"
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
if create and not memories_dir.exists():
|
|
90
90
|
memories_dir.mkdir(parents=True, exist_ok=True)
|
|
91
91
|
self.logger.debug(f"Created memories directory: {memories_dir}")
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
return memories_dir
|
|
94
|
-
|
|
94
|
+
|
|
95
95
|
def resolve_logs_dir(self, create: bool = False) -> Path:
|
|
96
96
|
"""
|
|
97
97
|
Resolve logs directory.
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
Args:
|
|
100
100
|
create: Whether to create directory if it doesn't exist
|
|
101
|
-
|
|
101
|
+
|
|
102
102
|
Returns:
|
|
103
103
|
Path to logs directory
|
|
104
104
|
"""
|
|
105
105
|
logs_dir = self.resolve_config_dir(create) / "logs"
|
|
106
|
-
|
|
106
|
+
|
|
107
107
|
if create and not logs_dir.exists():
|
|
108
108
|
logs_dir.mkdir(parents=True, exist_ok=True)
|
|
109
109
|
self.logger.debug(f"Created logs directory: {logs_dir}")
|
|
110
|
-
|
|
110
|
+
|
|
111
111
|
return logs_dir
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
def resolve_temp_dir(self, create: bool = False) -> Path:
|
|
114
114
|
"""
|
|
115
115
|
Resolve temporary directory.
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
Args:
|
|
118
118
|
create: Whether to create directory if it doesn't exist
|
|
119
|
-
|
|
119
|
+
|
|
120
120
|
Returns:
|
|
121
121
|
Path to temporary directory
|
|
122
122
|
"""
|
|
123
123
|
temp_dir = self.resolve_config_dir(create) / "tmp"
|
|
124
|
-
|
|
124
|
+
|
|
125
125
|
if create and not temp_dir.exists():
|
|
126
126
|
temp_dir.mkdir(parents=True, exist_ok=True)
|
|
127
127
|
self.logger.debug(f"Created temp directory: {temp_dir}")
|
|
128
|
-
|
|
128
|
+
|
|
129
129
|
return temp_dir
|
|
130
|
-
|
|
131
|
-
def find_agent_file(
|
|
130
|
+
|
|
131
|
+
def find_agent_file(
|
|
132
|
+
self, agent_name: str, filename: Optional[str] = None
|
|
133
|
+
) -> Optional[Path]:
|
|
132
134
|
"""
|
|
133
135
|
Find agent file in standard locations.
|
|
134
|
-
|
|
136
|
+
|
|
135
137
|
Args:
|
|
136
138
|
agent_name: Name of the agent
|
|
137
139
|
filename: Specific filename to look for (defaults to agent_name.md)
|
|
138
|
-
|
|
140
|
+
|
|
139
141
|
Returns:
|
|
140
142
|
Path to agent file if found
|
|
141
143
|
"""
|
|
142
144
|
if filename is None:
|
|
143
145
|
filename = f"{agent_name}.md"
|
|
144
|
-
|
|
146
|
+
|
|
145
147
|
# Search locations in order of preference
|
|
146
148
|
search_paths = [
|
|
147
149
|
self.resolve_agents_dir(), # Project agents
|
|
@@ -149,50 +151,52 @@ class PathResolver:
|
|
|
149
151
|
self.base_dir / "agents", # Local agents directory
|
|
150
152
|
self.base_dir, # Current directory
|
|
151
153
|
]
|
|
152
|
-
|
|
154
|
+
|
|
153
155
|
for search_path in search_paths:
|
|
154
156
|
if not search_path.exists():
|
|
155
157
|
continue
|
|
156
|
-
|
|
158
|
+
|
|
157
159
|
agent_file = search_path / filename
|
|
158
160
|
if agent_file.exists() and agent_file.is_file():
|
|
159
161
|
self.logger.debug(f"Found agent file: {agent_file}")
|
|
160
162
|
return agent_file
|
|
161
|
-
|
|
163
|
+
|
|
162
164
|
return None
|
|
163
|
-
|
|
164
|
-
def find_memory_file(
|
|
165
|
+
|
|
166
|
+
def find_memory_file(
|
|
167
|
+
self, agent_name: str, filename: Optional[str] = None
|
|
168
|
+
) -> Optional[Path]:
|
|
165
169
|
"""
|
|
166
170
|
Find memory file for an agent.
|
|
167
|
-
|
|
171
|
+
|
|
168
172
|
Args:
|
|
169
173
|
agent_name: Name of the agent
|
|
170
174
|
filename: Specific filename to look for (defaults to agent_name.md)
|
|
171
|
-
|
|
175
|
+
|
|
172
176
|
Returns:
|
|
173
177
|
Path to memory file if found
|
|
174
178
|
"""
|
|
175
179
|
if filename is None:
|
|
176
180
|
filename = f"{agent_name}.md"
|
|
177
|
-
|
|
181
|
+
|
|
178
182
|
memories_dir = self.resolve_memories_dir()
|
|
179
183
|
memory_file = memories_dir / filename
|
|
180
|
-
|
|
184
|
+
|
|
181
185
|
if memory_file.exists() and memory_file.is_file():
|
|
182
186
|
return memory_file
|
|
183
|
-
|
|
187
|
+
|
|
184
188
|
return None
|
|
185
|
-
|
|
186
|
-
def find_config_file(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
+
|
|
190
|
+
def find_config_file(
|
|
191
|
+
self, filename: str, search_paths: Optional[List[Union[str, Path]]] = None
|
|
192
|
+
) -> Optional[Path]:
|
|
189
193
|
"""
|
|
190
194
|
Find configuration file in standard locations.
|
|
191
|
-
|
|
195
|
+
|
|
192
196
|
Args:
|
|
193
197
|
filename: Configuration filename
|
|
194
198
|
search_paths: Additional search paths
|
|
195
|
-
|
|
199
|
+
|
|
196
200
|
Returns:
|
|
197
201
|
Path to configuration file if found
|
|
198
202
|
"""
|
|
@@ -201,64 +205,64 @@ class PathResolver:
|
|
|
201
205
|
self.base_dir,
|
|
202
206
|
Path.home() / ".claude-mpm",
|
|
203
207
|
]
|
|
204
|
-
|
|
208
|
+
|
|
205
209
|
if search_paths:
|
|
206
210
|
all_paths = [Path(p) for p in search_paths] + default_paths
|
|
207
211
|
else:
|
|
208
212
|
all_paths = default_paths
|
|
209
|
-
|
|
213
|
+
|
|
210
214
|
for search_path in all_paths:
|
|
211
215
|
if not search_path.exists():
|
|
212
216
|
continue
|
|
213
|
-
|
|
217
|
+
|
|
214
218
|
config_file = search_path / filename
|
|
215
219
|
if config_file.exists() and config_file.is_file():
|
|
216
220
|
self.logger.debug(f"Found config file: {config_file}")
|
|
217
221
|
return config_file
|
|
218
|
-
|
|
222
|
+
|
|
219
223
|
return None
|
|
220
|
-
|
|
224
|
+
|
|
221
225
|
def ensure_directory(self, path: Union[str, Path]) -> Path:
|
|
222
226
|
"""
|
|
223
227
|
Ensure directory exists.
|
|
224
|
-
|
|
228
|
+
|
|
225
229
|
Args:
|
|
226
230
|
path: Directory path
|
|
227
|
-
|
|
231
|
+
|
|
228
232
|
Returns:
|
|
229
233
|
Path object for the directory
|
|
230
234
|
"""
|
|
231
235
|
dir_path = Path(path)
|
|
232
|
-
|
|
236
|
+
|
|
233
237
|
if not dir_path.exists():
|
|
234
238
|
dir_path.mkdir(parents=True, exist_ok=True)
|
|
235
239
|
self.logger.debug(f"Created directory: {dir_path}")
|
|
236
240
|
elif not dir_path.is_dir():
|
|
237
241
|
raise ValueError(f"Path exists but is not a directory: {dir_path}")
|
|
238
|
-
|
|
242
|
+
|
|
239
243
|
return dir_path
|
|
240
|
-
|
|
244
|
+
|
|
241
245
|
def resolve_relative_path(self, path: Union[str, Path]) -> Path:
|
|
242
246
|
"""
|
|
243
247
|
Resolve path relative to base directory.
|
|
244
|
-
|
|
248
|
+
|
|
245
249
|
Args:
|
|
246
250
|
path: Path to resolve
|
|
247
|
-
|
|
251
|
+
|
|
248
252
|
Returns:
|
|
249
253
|
Resolved absolute path
|
|
250
254
|
"""
|
|
251
255
|
path_obj = Path(path)
|
|
252
|
-
|
|
256
|
+
|
|
253
257
|
if path_obj.is_absolute():
|
|
254
258
|
return path_obj
|
|
255
|
-
|
|
259
|
+
|
|
256
260
|
return (self.base_dir / path_obj).resolve()
|
|
257
|
-
|
|
261
|
+
|
|
258
262
|
def get_path_info(self) -> dict:
|
|
259
263
|
"""
|
|
260
264
|
Get information about resolved paths.
|
|
261
|
-
|
|
265
|
+
|
|
262
266
|
Returns:
|
|
263
267
|
Dictionary with path information
|
|
264
268
|
"""
|
|
@@ -271,7 +275,7 @@ class PathResolver:
|
|
|
271
275
|
"temp_dir": str(self.resolve_temp_dir()),
|
|
272
276
|
"working_dir_from_env": os.environ.get("CLAUDE_MPM_USER_PWD"),
|
|
273
277
|
}
|
|
274
|
-
|
|
278
|
+
|
|
275
279
|
def __repr__(self) -> str:
|
|
276
280
|
"""String representation."""
|
|
277
281
|
return f"PathResolver(base_dir={self.base_dir})"
|
|
@@ -3,40 +3,38 @@ Shared singleton management utilities to reduce duplication.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import threading
|
|
6
|
-
from typing import Any, Dict,
|
|
6
|
+
from typing import Any, Dict, Type, TypeVar
|
|
7
7
|
|
|
8
8
|
from ..logger import get_logger
|
|
9
9
|
|
|
10
|
-
T = TypeVar(
|
|
10
|
+
T = TypeVar("T")
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class SingletonManager:
|
|
14
14
|
"""
|
|
15
15
|
Centralized singleton management utility.
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
Reduces duplication by providing thread-safe singleton patterns
|
|
18
18
|
that can be used across different classes.
|
|
19
19
|
"""
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
_instances: Dict[Type, Any] = {}
|
|
22
22
|
_locks: Dict[Type, threading.Lock] = {}
|
|
23
23
|
_global_lock = threading.Lock()
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
@classmethod
|
|
26
|
-
def get_instance(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
force_new: bool = False,
|
|
30
|
-
**kwargs) -> T:
|
|
26
|
+
def get_instance(
|
|
27
|
+
cls, singleton_class: Type[T], *args, force_new: bool = False, **kwargs
|
|
28
|
+
) -> T:
|
|
31
29
|
"""
|
|
32
30
|
Get singleton instance of a class.
|
|
33
|
-
|
|
31
|
+
|
|
34
32
|
Args:
|
|
35
33
|
singleton_class: Class to get singleton instance of
|
|
36
34
|
*args: Arguments for class constructor
|
|
37
35
|
force_new: Force creation of new instance
|
|
38
36
|
**kwargs: Keyword arguments for class constructor
|
|
39
|
-
|
|
37
|
+
|
|
40
38
|
Returns:
|
|
41
39
|
Singleton instance
|
|
42
40
|
"""
|
|
@@ -45,38 +43,38 @@ class SingletonManager:
|
|
|
45
43
|
with cls._global_lock:
|
|
46
44
|
if singleton_class not in cls._locks:
|
|
47
45
|
cls._locks[singleton_class] = threading.Lock()
|
|
48
|
-
|
|
46
|
+
|
|
49
47
|
# Get instance with class-specific lock
|
|
50
48
|
with cls._locks[singleton_class]:
|
|
51
49
|
if force_new or singleton_class not in cls._instances:
|
|
52
50
|
logger = get_logger("singleton_manager")
|
|
53
51
|
logger.debug(f"Creating singleton instance: {singleton_class.__name__}")
|
|
54
|
-
|
|
52
|
+
|
|
55
53
|
instance = singleton_class(*args, **kwargs)
|
|
56
54
|
cls._instances[singleton_class] = instance
|
|
57
|
-
|
|
55
|
+
|
|
58
56
|
return instance
|
|
59
|
-
|
|
57
|
+
|
|
60
58
|
return cls._instances[singleton_class]
|
|
61
|
-
|
|
59
|
+
|
|
62
60
|
@classmethod
|
|
63
61
|
def has_instance(cls, singleton_class: Type) -> bool:
|
|
64
62
|
"""
|
|
65
63
|
Check if singleton instance exists.
|
|
66
|
-
|
|
64
|
+
|
|
67
65
|
Args:
|
|
68
66
|
singleton_class: Class to check
|
|
69
|
-
|
|
67
|
+
|
|
70
68
|
Returns:
|
|
71
69
|
True if instance exists
|
|
72
70
|
"""
|
|
73
71
|
return singleton_class in cls._instances
|
|
74
|
-
|
|
72
|
+
|
|
75
73
|
@classmethod
|
|
76
74
|
def clear_instance(cls, singleton_class: Type) -> None:
|
|
77
75
|
"""
|
|
78
76
|
Clear singleton instance.
|
|
79
|
-
|
|
77
|
+
|
|
80
78
|
Args:
|
|
81
79
|
singleton_class: Class to clear instance for
|
|
82
80
|
"""
|
|
@@ -84,9 +82,11 @@ class SingletonManager:
|
|
|
84
82
|
with cls._locks[singleton_class]:
|
|
85
83
|
if singleton_class in cls._instances:
|
|
86
84
|
logger = get_logger("singleton_manager")
|
|
87
|
-
logger.debug(
|
|
85
|
+
logger.debug(
|
|
86
|
+
f"Clearing singleton instance: {singleton_class.__name__}"
|
|
87
|
+
)
|
|
88
88
|
del cls._instances[singleton_class]
|
|
89
|
-
|
|
89
|
+
|
|
90
90
|
@classmethod
|
|
91
91
|
def clear_all_instances(cls) -> None:
|
|
92
92
|
"""Clear all singleton instances."""
|
|
@@ -94,47 +94,47 @@ class SingletonManager:
|
|
|
94
94
|
logger = get_logger("singleton_manager")
|
|
95
95
|
logger.debug(f"Clearing {len(cls._instances)} singleton instances")
|
|
96
96
|
cls._instances.clear()
|
|
97
|
-
|
|
97
|
+
|
|
98
98
|
@classmethod
|
|
99
99
|
def get_instance_info(cls) -> Dict[str, Any]:
|
|
100
100
|
"""
|
|
101
101
|
Get information about managed instances.
|
|
102
|
-
|
|
102
|
+
|
|
103
103
|
Returns:
|
|
104
104
|
Dictionary with instance information
|
|
105
105
|
"""
|
|
106
106
|
return {
|
|
107
107
|
"instance_count": len(cls._instances),
|
|
108
|
-
"instance_types": [cls_type.__name__ for cls_type in cls._instances
|
|
109
|
-
"lock_count": len(cls._locks)
|
|
108
|
+
"instance_types": [cls_type.__name__ for cls_type in cls._instances],
|
|
109
|
+
"lock_count": len(cls._locks),
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
|
|
113
113
|
class SingletonMixin:
|
|
114
114
|
"""
|
|
115
115
|
Mixin class to add singleton behavior to any class.
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
Usage:
|
|
118
118
|
class MyService(SingletonMixin):
|
|
119
119
|
def __init__(self):
|
|
120
120
|
super().__init__()
|
|
121
121
|
# Your initialization code
|
|
122
122
|
"""
|
|
123
|
-
|
|
123
|
+
|
|
124
124
|
def __new__(cls, *args, **kwargs):
|
|
125
125
|
"""Override __new__ to implement singleton pattern."""
|
|
126
126
|
return SingletonManager.get_instance(cls, *args, **kwargs)
|
|
127
|
-
|
|
127
|
+
|
|
128
128
|
@classmethod
|
|
129
129
|
def get_instance(cls, *args, **kwargs):
|
|
130
130
|
"""Get singleton instance."""
|
|
131
131
|
return SingletonManager.get_instance(cls, *args, **kwargs)
|
|
132
|
-
|
|
132
|
+
|
|
133
133
|
@classmethod
|
|
134
134
|
def clear_instance(cls):
|
|
135
135
|
"""Clear singleton instance."""
|
|
136
136
|
SingletonManager.clear_instance(cls)
|
|
137
|
-
|
|
137
|
+
|
|
138
138
|
@classmethod
|
|
139
139
|
def has_instance(cls) -> bool:
|
|
140
140
|
"""Check if instance exists."""
|
|
@@ -144,7 +144,7 @@ class SingletonMixin:
|
|
|
144
144
|
def singleton(cls: Type[T]) -> Type[T]:
|
|
145
145
|
"""
|
|
146
146
|
Decorator to make a class a singleton.
|
|
147
|
-
|
|
147
|
+
|
|
148
148
|
Usage:
|
|
149
149
|
@singleton
|
|
150
150
|
class MyService:
|
|
@@ -152,21 +152,25 @@ def singleton(cls: Type[T]) -> Type[T]:
|
|
|
152
152
|
# Your initialization code
|
|
153
153
|
pass
|
|
154
154
|
"""
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
|
|
157
156
|
def new_new(cls_inner, *args, **kwargs):
|
|
158
157
|
return SingletonManager.get_instance(cls_inner, *args, **kwargs)
|
|
159
|
-
|
|
158
|
+
|
|
160
159
|
cls.__new__ = new_new
|
|
161
|
-
|
|
160
|
+
|
|
162
161
|
# Add convenience methods
|
|
163
|
-
cls.get_instance = classmethod(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
162
|
+
cls.get_instance = classmethod(
|
|
163
|
+
lambda cls_inner, *args, **kwargs: SingletonManager.get_instance(
|
|
164
|
+
cls_inner, *args, **kwargs
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
cls.clear_instance = classmethod(
|
|
168
|
+
lambda cls_inner: SingletonManager.clear_instance(cls_inner)
|
|
169
|
+
)
|
|
170
|
+
cls.has_instance = classmethod(
|
|
171
|
+
lambda cls_inner: SingletonManager.has_instance(cls_inner)
|
|
172
|
+
)
|
|
173
|
+
|
|
170
174
|
return cls
|
|
171
175
|
|
|
172
176
|
|
|
@@ -177,32 +181,34 @@ if __name__ == "__main__":
|
|
|
177
181
|
def __init__(self, config_path: str = "default.yaml"):
|
|
178
182
|
self.config_path = config_path
|
|
179
183
|
self.loaded = True
|
|
180
|
-
|
|
184
|
+
|
|
181
185
|
# Example 2: Using @singleton decorator
|
|
182
186
|
@singleton
|
|
183
187
|
class LoggerService:
|
|
184
188
|
def __init__(self, log_level: str = "INFO"):
|
|
185
189
|
self.log_level = log_level
|
|
186
190
|
self.initialized = True
|
|
187
|
-
|
|
191
|
+
|
|
188
192
|
# Example 3: Using SingletonManager directly
|
|
189
193
|
class DatabaseService:
|
|
190
194
|
def __init__(self, connection_string: str):
|
|
191
195
|
self.connection_string = connection_string
|
|
192
196
|
self.connected = True
|
|
193
|
-
|
|
197
|
+
|
|
194
198
|
# Test the patterns
|
|
195
199
|
config1 = ConfigService("config1.yaml")
|
|
196
200
|
config2 = ConfigService("config2.yaml") # Same instance as config1
|
|
197
201
|
assert config1 is config2
|
|
198
202
|
assert config1.config_path == "config1.yaml" # First initialization wins
|
|
199
|
-
|
|
203
|
+
|
|
200
204
|
logger1 = LoggerService("DEBUG")
|
|
201
205
|
logger2 = LoggerService("ERROR") # Same instance as logger1
|
|
202
206
|
assert logger1 is logger2
|
|
203
207
|
assert logger1.log_level == "DEBUG" # First initialization wins
|
|
204
|
-
|
|
208
|
+
|
|
205
209
|
db1 = SingletonManager.get_instance(DatabaseService, "postgres://localhost")
|
|
206
|
-
db2 = SingletonManager.get_instance(
|
|
210
|
+
db2 = SingletonManager.get_instance(
|
|
211
|
+
DatabaseService, "mysql://localhost"
|
|
212
|
+
) # Same instance
|
|
207
213
|
assert db1 is db2
|
|
208
214
|
assert db1.connection_string == "postgres://localhost" # First initialization wins
|
claude_mpm/core/socketio_pool.py
CHANGED
|
@@ -95,7 +95,7 @@ class CircuitBreaker:
|
|
|
95
95
|
"""Check if execution is allowed based on circuit state."""
|
|
96
96
|
if self.state == CircuitState.CLOSED:
|
|
97
97
|
return True
|
|
98
|
-
|
|
98
|
+
if self.state == CircuitState.OPEN:
|
|
99
99
|
# Check if recovery timeout has passed
|
|
100
100
|
if (
|
|
101
101
|
self.last_failure_time
|
|
@@ -108,7 +108,7 @@ class CircuitBreaker:
|
|
|
108
108
|
)
|
|
109
109
|
return True
|
|
110
110
|
return False
|
|
111
|
-
|
|
111
|
+
if self.state == CircuitState.HALF_OPEN:
|
|
112
112
|
# Allow one test request
|
|
113
113
|
return True
|
|
114
114
|
|
|
@@ -496,7 +496,7 @@ class SocketIOConnectionPool:
|
|
|
496
496
|
await client.emit(event.event, enhanced_data, namespace=namespace)
|
|
497
497
|
|
|
498
498
|
# Update stats
|
|
499
|
-
for
|
|
499
|
+
for _conn_id, stats in self.connection_stats.items():
|
|
500
500
|
if stats.is_connected:
|
|
501
501
|
stats.events_sent += len(events)
|
|
502
502
|
stats.consecutive_errors = 0
|
|
@@ -547,7 +547,7 @@ class SocketIOConnectionPool:
|
|
|
547
547
|
)
|
|
548
548
|
|
|
549
549
|
# Update stats
|
|
550
|
-
for
|
|
550
|
+
for _conn_id, stats in self.connection_stats.items():
|
|
551
551
|
if stats.is_connected:
|
|
552
552
|
stats.events_sent += len(events)
|
|
553
553
|
stats.consecutive_errors = 0
|
|
@@ -562,7 +562,7 @@ class SocketIOConnectionPool:
|
|
|
562
562
|
self.logger.error(f"Failed to emit batch to {namespace}: {e}")
|
|
563
563
|
|
|
564
564
|
# Update stats
|
|
565
|
-
for
|
|
565
|
+
for _conn_id, stats in self.connection_stats.items():
|
|
566
566
|
if stats.is_connected:
|
|
567
567
|
stats.errors += 1
|
|
568
568
|
stats.consecutive_errors += 1
|
|
@@ -572,7 +572,7 @@ class SocketIOConnectionPool:
|
|
|
572
572
|
finally:
|
|
573
573
|
self._return_connection(client)
|
|
574
574
|
# Only close loop if we created it
|
|
575
|
-
if loop and
|
|
575
|
+
if loop and asyncio.get_event_loop() != loop:
|
|
576
576
|
try:
|
|
577
577
|
# Ensure all tasks are done before closing
|
|
578
578
|
pending = asyncio.all_tasks(loop)
|
|
@@ -731,6 +731,26 @@ class SocketIOConnectionPool:
|
|
|
731
731
|
except (asyncio.TimeoutError, Exception):
|
|
732
732
|
return False
|
|
733
733
|
|
|
734
|
+
def emit(self, event: str, data: Dict[str, Any]) -> bool:
|
|
735
|
+
"""Emit an event through the connection pool.
|
|
736
|
+
|
|
737
|
+
This method provides compatibility for the legacy emit() interface
|
|
738
|
+
by mapping to the modern emit_event() method with appropriate defaults.
|
|
739
|
+
|
|
740
|
+
Args:
|
|
741
|
+
event: Event name (e.g., "claude_event")
|
|
742
|
+
data: Event data dictionary
|
|
743
|
+
|
|
744
|
+
Returns:
|
|
745
|
+
bool: True if event was sent successfully (always True for async emission)
|
|
746
|
+
"""
|
|
747
|
+
if not SOCKETIO_AVAILABLE or not self._running:
|
|
748
|
+
return False
|
|
749
|
+
|
|
750
|
+
# Map to the modern emit_event method using default namespace
|
|
751
|
+
self.emit_event("/", event, data)
|
|
752
|
+
return True
|
|
753
|
+
|
|
734
754
|
def get_stats(self) -> Dict[str, Any]:
|
|
735
755
|
"""Get connection pool statistics."""
|
|
736
756
|
with self.pool_lock:
|