claude-mpm 3.9.11__py3-none-any.whl → 4.0.4__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/VERSION +1 -1
- claude_mpm/__init__.py +2 -2
- claude_mpm/__main__.py +3 -2
- claude_mpm/agents/__init__.py +85 -79
- claude_mpm/agents/agent_loader.py +464 -1003
- claude_mpm/agents/agent_loader_integration.py +45 -45
- claude_mpm/agents/agents_metadata.py +29 -30
- claude_mpm/agents/async_agent_loader.py +156 -138
- claude_mpm/agents/base_agent.json +1 -1
- claude_mpm/agents/base_agent_loader.py +179 -151
- claude_mpm/agents/frontmatter_validator.py +229 -130
- claude_mpm/agents/schema/agent_schema.json +1 -1
- claude_mpm/agents/system_agent_config.py +213 -147
- claude_mpm/agents/templates/__init__.py +13 -13
- claude_mpm/agents/templates/code_analyzer.json +2 -2
- claude_mpm/agents/templates/data_engineer.json +1 -1
- claude_mpm/agents/templates/documentation.json +23 -11
- claude_mpm/agents/templates/engineer.json +22 -6
- claude_mpm/agents/templates/memory_manager.json +1 -1
- claude_mpm/agents/templates/ops.json +2 -2
- claude_mpm/agents/templates/project_organizer.json +1 -1
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/refactoring_engineer.json +222 -0
- claude_mpm/agents/templates/research.json +20 -14
- claude_mpm/agents/templates/security.json +1 -1
- claude_mpm/agents/templates/ticketing.json +2 -2
- claude_mpm/agents/templates/version_control.json +1 -1
- claude_mpm/agents/templates/web_qa.json +3 -1
- claude_mpm/agents/templates/web_ui.json +2 -2
- claude_mpm/cli/__init__.py +79 -51
- claude_mpm/cli/__main__.py +3 -2
- claude_mpm/cli/commands/__init__.py +20 -20
- claude_mpm/cli/commands/agents.py +279 -247
- claude_mpm/cli/commands/aggregate.py +138 -157
- claude_mpm/cli/commands/cleanup.py +147 -147
- claude_mpm/cli/commands/config.py +93 -76
- claude_mpm/cli/commands/info.py +17 -16
- claude_mpm/cli/commands/mcp.py +140 -905
- claude_mpm/cli/commands/mcp_command_router.py +139 -0
- claude_mpm/cli/commands/mcp_config_commands.py +20 -0
- claude_mpm/cli/commands/mcp_install_commands.py +20 -0
- claude_mpm/cli/commands/mcp_server_commands.py +175 -0
- claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
- claude_mpm/cli/commands/memory.py +239 -203
- claude_mpm/cli/commands/monitor.py +330 -86
- claude_mpm/cli/commands/run.py +380 -429
- claude_mpm/cli/commands/run_config_checker.py +160 -0
- claude_mpm/cli/commands/socketio_monitor.py +235 -0
- claude_mpm/cli/commands/tickets.py +363 -220
- claude_mpm/cli/parser.py +24 -1156
- claude_mpm/cli/parsers/__init__.py +29 -0
- claude_mpm/cli/parsers/agents_parser.py +136 -0
- claude_mpm/cli/parsers/base_parser.py +331 -0
- claude_mpm/cli/parsers/config_parser.py +85 -0
- claude_mpm/cli/parsers/mcp_parser.py +152 -0
- claude_mpm/cli/parsers/memory_parser.py +138 -0
- claude_mpm/cli/parsers/monitor_parser.py +124 -0
- claude_mpm/cli/parsers/run_parser.py +147 -0
- claude_mpm/cli/parsers/tickets_parser.py +203 -0
- claude_mpm/cli/ticket_cli.py +7 -3
- claude_mpm/cli/utils.py +55 -37
- claude_mpm/cli_module/__init__.py +6 -6
- claude_mpm/cli_module/args.py +188 -140
- claude_mpm/cli_module/commands.py +79 -70
- claude_mpm/cli_module/migration_example.py +38 -60
- claude_mpm/config/__init__.py +32 -25
- claude_mpm/config/agent_config.py +151 -119
- claude_mpm/config/experimental_features.py +71 -73
- claude_mpm/config/paths.py +94 -208
- claude_mpm/config/socketio_config.py +84 -73
- claude_mpm/constants.py +35 -18
- claude_mpm/core/__init__.py +9 -6
- claude_mpm/core/agent_name_normalizer.py +68 -71
- claude_mpm/core/agent_registry.py +372 -521
- claude_mpm/core/agent_session_manager.py +74 -63
- claude_mpm/core/base_service.py +116 -87
- claude_mpm/core/cache.py +119 -153
- claude_mpm/core/claude_runner.py +425 -1120
- claude_mpm/core/config.py +263 -168
- claude_mpm/core/config_aliases.py +69 -61
- claude_mpm/core/config_constants.py +292 -0
- claude_mpm/core/constants.py +57 -99
- claude_mpm/core/container.py +211 -178
- claude_mpm/core/exceptions.py +233 -89
- claude_mpm/core/factories.py +92 -54
- claude_mpm/core/framework_loader.py +378 -220
- claude_mpm/core/hook_manager.py +198 -83
- claude_mpm/core/hook_performance_config.py +136 -0
- claude_mpm/core/injectable_service.py +61 -55
- claude_mpm/core/interactive_session.py +165 -155
- claude_mpm/core/interfaces.py +221 -195
- claude_mpm/core/lazy.py +96 -96
- claude_mpm/core/logger.py +133 -107
- claude_mpm/core/logging_config.py +185 -157
- claude_mpm/core/minimal_framework_loader.py +20 -15
- claude_mpm/core/mixins.py +30 -29
- claude_mpm/core/oneshot_session.py +215 -181
- claude_mpm/core/optimized_agent_loader.py +134 -138
- claude_mpm/core/optimized_startup.py +159 -157
- claude_mpm/core/pm_hook_interceptor.py +85 -72
- claude_mpm/core/service_registry.py +103 -101
- claude_mpm/core/session_manager.py +97 -87
- claude_mpm/core/socketio_pool.py +212 -158
- claude_mpm/core/tool_access_control.py +58 -51
- claude_mpm/core/types.py +46 -24
- claude_mpm/core/typing_utils.py +166 -82
- claude_mpm/core/unified_agent_registry.py +721 -0
- claude_mpm/core/unified_config.py +550 -0
- claude_mpm/core/unified_paths.py +549 -0
- claude_mpm/dashboard/index.html +1 -1
- claude_mpm/dashboard/open_dashboard.py +51 -17
- claude_mpm/dashboard/static/built/components/agent-inference.js +2 -0
- claude_mpm/dashboard/static/built/components/event-processor.js +2 -0
- claude_mpm/dashboard/static/built/components/event-viewer.js +2 -0
- claude_mpm/dashboard/static/built/components/export-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/file-tool-tracker.js +2 -0
- claude_mpm/dashboard/static/built/components/hud-library-loader.js +2 -0
- claude_mpm/dashboard/static/built/components/hud-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/hud-visualizer.js +2 -0
- claude_mpm/dashboard/static/built/components/module-viewer.js +2 -0
- claude_mpm/dashboard/static/built/components/session-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/socket-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/ui-state-manager.js +2 -0
- claude_mpm/dashboard/static/built/components/working-directory.js +2 -0
- claude_mpm/dashboard/static/built/dashboard.js +2 -0
- claude_mpm/dashboard/static/built/socket-client.js +2 -0
- claude_mpm/dashboard/static/css/dashboard.css +27 -8
- claude_mpm/dashboard/static/dist/components/agent-inference.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-processor.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/export-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-library-loader.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/hud-visualizer.js +2 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/session-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/socket-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/ui-state-manager.js +2 -0
- claude_mpm/dashboard/static/dist/components/working-directory.js +2 -0
- claude_mpm/dashboard/static/dist/dashboard.js +2 -0
- claude_mpm/dashboard/static/dist/socket-client.js +2 -0
- claude_mpm/dashboard/static/js/components/agent-inference.js +80 -76
- claude_mpm/dashboard/static/js/components/event-processor.js +71 -67
- claude_mpm/dashboard/static/js/components/event-viewer.js +93 -72
- claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +110 -96
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +11 -11
- claude_mpm/dashboard/static/js/components/hud-manager.js +73 -73
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +163 -163
- claude_mpm/dashboard/static/js/components/module-viewer.js +305 -233
- claude_mpm/dashboard/static/js/components/session-manager.js +32 -29
- claude_mpm/dashboard/static/js/components/socket-manager.js +27 -20
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +21 -18
- claude_mpm/dashboard/static/js/components/working-directory.js +74 -71
- claude_mpm/dashboard/static/js/dashboard.js +178 -453
- claude_mpm/dashboard/static/js/extension-error-handler.js +164 -0
- claude_mpm/dashboard/static/js/socket-client.js +133 -53
- claude_mpm/dashboard/templates/index.html +40 -50
- claude_mpm/experimental/cli_enhancements.py +60 -58
- claude_mpm/generators/__init__.py +1 -1
- claude_mpm/generators/agent_profile_generator.py +75 -65
- claude_mpm/hooks/__init__.py +1 -1
- claude_mpm/hooks/base_hook.py +33 -28
- claude_mpm/hooks/claude_hooks/__init__.py +1 -1
- claude_mpm/hooks/claude_hooks/connection_pool.py +120 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +743 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +415 -1331
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +4 -4
- claude_mpm/hooks/claude_hooks/memory_integration.py +221 -0
- claude_mpm/hooks/claude_hooks/response_tracking.py +348 -0
- claude_mpm/hooks/claude_hooks/tool_analysis.py +230 -0
- claude_mpm/hooks/memory_integration_hook.py +140 -100
- claude_mpm/hooks/tool_call_interceptor.py +89 -76
- claude_mpm/hooks/validation_hooks.py +57 -49
- claude_mpm/init.py +145 -121
- claude_mpm/models/__init__.py +9 -9
- claude_mpm/models/agent_definition.py +33 -23
- claude_mpm/models/agent_session.py +228 -200
- claude_mpm/scripts/__init__.py +1 -1
- claude_mpm/scripts/socketio_daemon.py +192 -75
- claude_mpm/scripts/socketio_server_manager.py +328 -0
- claude_mpm/scripts/start_activity_logging.py +25 -22
- claude_mpm/services/__init__.py +68 -43
- claude_mpm/services/agent_capabilities_service.py +271 -0
- claude_mpm/services/agents/__init__.py +23 -32
- claude_mpm/services/agents/deployment/__init__.py +3 -3
- claude_mpm/services/agents/deployment/agent_config_provider.py +310 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +359 -0
- claude_mpm/services/agents/deployment/agent_definition_factory.py +84 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +415 -2113
- claude_mpm/services/agents/deployment/agent_discovery_service.py +387 -0
- claude_mpm/services/agents/deployment/agent_environment_manager.py +293 -0
- claude_mpm/services/agents/deployment/agent_filesystem_manager.py +387 -0
- claude_mpm/services/agents/deployment/agent_format_converter.py +453 -0
- claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +161 -0
- claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +345 -495
- claude_mpm/services/agents/deployment/agent_metrics_collector.py +279 -0
- claude_mpm/services/agents/deployment/agent_restore_handler.py +88 -0
- claude_mpm/services/agents/deployment/agent_template_builder.py +406 -0
- claude_mpm/services/agents/deployment/agent_validator.py +352 -0
- claude_mpm/services/agents/deployment/agent_version_manager.py +313 -0
- claude_mpm/services/agents/deployment/agent_versioning.py +6 -9
- claude_mpm/services/agents/deployment/agents_directory_resolver.py +79 -0
- claude_mpm/services/agents/deployment/async_agent_deployment.py +298 -234
- claude_mpm/services/agents/deployment/config/__init__.py +13 -0
- claude_mpm/services/agents/deployment/config/deployment_config.py +182 -0
- claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
- claude_mpm/services/agents/deployment/deployment_config_loader.py +54 -0
- claude_mpm/services/agents/deployment/deployment_type_detector.py +124 -0
- claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
- claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
- claude_mpm/services/agents/deployment/facade/deployment_executor.py +73 -0
- claude_mpm/services/agents/deployment/facade/deployment_facade.py +270 -0
- claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
- claude_mpm/services/agents/deployment/interface_adapter.py +227 -0
- claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
- claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
- claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +159 -0
- claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
- claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +195 -0
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +119 -0
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +79 -0
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +90 -0
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +100 -0
- claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +98 -0
- claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
- claude_mpm/services/agents/deployment/processors/agent_processor.py +258 -0
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +318 -0
- claude_mpm/services/agents/deployment/results/__init__.py +13 -0
- claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
- claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
- claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
- claude_mpm/services/agents/deployment/strategies/base_strategy.py +119 -0
- claude_mpm/services/agents/deployment/strategies/project_strategy.py +150 -0
- claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
- claude_mpm/services/agents/deployment/strategies/system_strategy.py +116 -0
- claude_mpm/services/agents/deployment/strategies/user_strategy.py +137 -0
- claude_mpm/services/agents/deployment/system_instructions_deployer.py +108 -0
- claude_mpm/services/agents/deployment/validation/__init__.py +19 -0
- claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
- claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
- claude_mpm/services/agents/deployment/validation/template_validator.py +299 -0
- claude_mpm/services/agents/deployment/validation/validation_result.py +226 -0
- claude_mpm/services/agents/loading/__init__.py +2 -2
- claude_mpm/services/agents/loading/agent_profile_loader.py +259 -229
- claude_mpm/services/agents/loading/base_agent_manager.py +90 -81
- claude_mpm/services/agents/loading/framework_agent_loader.py +154 -129
- claude_mpm/services/agents/management/__init__.py +2 -2
- claude_mpm/services/agents/management/agent_capabilities_generator.py +72 -58
- claude_mpm/services/agents/management/agent_management_service.py +209 -156
- claude_mpm/services/agents/memory/__init__.py +9 -6
- claude_mpm/services/agents/memory/agent_memory_manager.py +218 -1152
- claude_mpm/services/agents/memory/agent_persistence_service.py +20 -16
- claude_mpm/services/agents/memory/analyzer.py +430 -0
- claude_mpm/services/agents/memory/content_manager.py +376 -0
- claude_mpm/services/agents/memory/template_generator.py +468 -0
- claude_mpm/services/agents/registry/__init__.py +7 -10
- claude_mpm/services/agents/registry/deployed_agent_discovery.py +122 -97
- claude_mpm/services/agents/registry/modification_tracker.py +351 -285
- claude_mpm/services/async_session_logger.py +187 -153
- claude_mpm/services/claude_session_logger.py +87 -72
- claude_mpm/services/command_handler_service.py +217 -0
- claude_mpm/services/communication/__init__.py +3 -2
- claude_mpm/services/core/__init__.py +50 -97
- claude_mpm/services/core/base.py +60 -53
- claude_mpm/services/core/interfaces/__init__.py +188 -0
- claude_mpm/services/core/interfaces/agent.py +351 -0
- claude_mpm/services/core/interfaces/communication.py +343 -0
- claude_mpm/services/core/interfaces/infrastructure.py +413 -0
- claude_mpm/services/core/interfaces/service.py +434 -0
- claude_mpm/services/core/interfaces.py +19 -944
- claude_mpm/services/event_aggregator.py +208 -170
- claude_mpm/services/exceptions.py +387 -308
- claude_mpm/services/framework_claude_md_generator/__init__.py +75 -79
- claude_mpm/services/framework_claude_md_generator/content_assembler.py +69 -60
- claude_mpm/services/framework_claude_md_generator/content_validator.py +65 -61
- claude_mpm/services/framework_claude_md_generator/deployment_manager.py +68 -49
- claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +34 -34
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +25 -22
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +10 -10
- claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
- claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
- claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
- claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +4 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +5 -4
- claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
- claude_mpm/services/framework_claude_md_generator/version_manager.py +30 -28
- claude_mpm/services/hook_service.py +106 -114
- claude_mpm/services/infrastructure/__init__.py +7 -5
- claude_mpm/services/infrastructure/context_preservation.py +233 -199
- claude_mpm/services/infrastructure/daemon_manager.py +279 -0
- claude_mpm/services/infrastructure/logging.py +83 -76
- claude_mpm/services/infrastructure/monitoring.py +547 -404
- claude_mpm/services/mcp_gateway/__init__.py +30 -13
- claude_mpm/services/mcp_gateway/config/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/config/config_loader.py +61 -56
- claude_mpm/services/mcp_gateway/config/config_schema.py +50 -41
- claude_mpm/services/mcp_gateway/config/configuration.py +82 -75
- claude_mpm/services/mcp_gateway/core/__init__.py +13 -20
- claude_mpm/services/mcp_gateway/core/base.py +80 -67
- claude_mpm/services/mcp_gateway/core/exceptions.py +60 -46
- claude_mpm/services/mcp_gateway/core/interfaces.py +87 -84
- claude_mpm/services/mcp_gateway/main.py +287 -137
- claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +97 -94
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
- claude_mpm/services/mcp_gateway/server/__init__.py +2 -2
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +105 -110
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +105 -107
- claude_mpm/services/mcp_gateway/server/stdio_server.py +691 -0
- claude_mpm/services/mcp_gateway/tools/__init__.py +4 -2
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +109 -119
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +283 -215
- claude_mpm/services/mcp_gateway/tools/hello_world.py +122 -120
- claude_mpm/services/mcp_gateway/tools/ticket_tools.py +652 -0
- claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +606 -0
- claude_mpm/services/memory/__init__.py +2 -2
- claude_mpm/services/memory/builder.py +451 -362
- claude_mpm/services/memory/cache/__init__.py +2 -2
- claude_mpm/services/memory/cache/shared_prompt_cache.py +232 -194
- claude_mpm/services/memory/cache/simple_cache.py +107 -93
- claude_mpm/services/memory/indexed_memory.py +195 -193
- claude_mpm/services/memory/optimizer.py +267 -234
- claude_mpm/services/memory/router.py +571 -263
- claude_mpm/services/memory_hook_service.py +237 -0
- claude_mpm/services/port_manager.py +575 -0
- claude_mpm/services/project/__init__.py +3 -3
- claude_mpm/services/project/analyzer.py +451 -305
- claude_mpm/services/project/registry.py +262 -240
- claude_mpm/services/recovery_manager.py +287 -231
- claude_mpm/services/response_tracker.py +87 -67
- claude_mpm/services/runner_configuration_service.py +587 -0
- claude_mpm/services/session_management_service.py +304 -0
- claude_mpm/services/socketio/__init__.py +4 -4
- claude_mpm/services/socketio/client_proxy.py +174 -0
- claude_mpm/services/socketio/handlers/__init__.py +3 -3
- claude_mpm/services/socketio/handlers/base.py +44 -30
- claude_mpm/services/socketio/handlers/connection.py +166 -64
- claude_mpm/services/socketio/handlers/file.py +123 -108
- claude_mpm/services/socketio/handlers/git.py +607 -373
- claude_mpm/services/socketio/handlers/hook.py +185 -0
- claude_mpm/services/socketio/handlers/memory.py +4 -4
- claude_mpm/services/socketio/handlers/project.py +4 -4
- claude_mpm/services/socketio/handlers/registry.py +53 -38
- claude_mpm/services/socketio/server/__init__.py +18 -0
- claude_mpm/services/socketio/server/broadcaster.py +252 -0
- claude_mpm/services/socketio/server/core.py +399 -0
- claude_mpm/services/socketio/server/main.py +323 -0
- claude_mpm/services/socketio_client_manager.py +160 -133
- claude_mpm/services/socketio_server.py +36 -1885
- claude_mpm/services/subprocess_launcher_service.py +316 -0
- claude_mpm/services/system_instructions_service.py +258 -0
- claude_mpm/services/ticket_manager.py +19 -533
- claude_mpm/services/utility_service.py +285 -0
- claude_mpm/services/version_control/__init__.py +18 -21
- claude_mpm/services/version_control/branch_strategy.py +20 -10
- claude_mpm/services/version_control/conflict_resolution.py +37 -13
- claude_mpm/services/version_control/git_operations.py +52 -21
- claude_mpm/services/version_control/semantic_versioning.py +92 -53
- claude_mpm/services/version_control/version_parser.py +145 -125
- claude_mpm/services/version_service.py +270 -0
- claude_mpm/storage/__init__.py +2 -2
- claude_mpm/storage/state_storage.py +177 -181
- claude_mpm/ticket_wrapper.py +2 -2
- claude_mpm/utils/__init__.py +2 -2
- claude_mpm/utils/agent_dependency_loader.py +453 -243
- claude_mpm/utils/config_manager.py +157 -118
- claude_mpm/utils/console.py +1 -1
- claude_mpm/utils/dependency_cache.py +102 -107
- claude_mpm/utils/dependency_manager.py +52 -47
- claude_mpm/utils/dependency_strategies.py +131 -96
- claude_mpm/utils/environment_context.py +110 -102
- claude_mpm/utils/error_handler.py +75 -55
- claude_mpm/utils/file_utils.py +80 -67
- claude_mpm/utils/framework_detection.py +12 -11
- claude_mpm/utils/import_migration_example.py +12 -60
- claude_mpm/utils/imports.py +48 -45
- claude_mpm/utils/path_operations.py +100 -93
- claude_mpm/utils/robust_installer.py +172 -164
- claude_mpm/utils/session_logging.py +30 -23
- claude_mpm/utils/subprocess_utils.py +99 -61
- claude_mpm/validation/__init__.py +1 -1
- claude_mpm/validation/agent_validator.py +151 -111
- claude_mpm/validation/frontmatter_validator.py +92 -71
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/METADATA +90 -22
- claude_mpm-4.0.4.dist-info/RECORD +417 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/entry_points.txt +1 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/licenses/LICENSE +1 -1
- claude_mpm/cli/commands/run_guarded.py +0 -511
- claude_mpm/config/memory_guardian_config.py +0 -325
- claude_mpm/config/memory_guardian_yaml.py +0 -335
- claude_mpm/core/config_paths.py +0 -150
- claude_mpm/core/memory_aware_runner.py +0 -353
- claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
- claude_mpm/deployment_paths.py +0 -261
- claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
- claude_mpm/models/state_models.py +0 -433
- claude_mpm/services/agent/__init__.py +0 -24
- claude_mpm/services/agent/deployment.py +0 -2548
- claude_mpm/services/agent/management.py +0 -598
- claude_mpm/services/agent/registry.py +0 -813
- claude_mpm/services/agents/registry/agent_registry.py +0 -813
- claude_mpm/services/communication/socketio.py +0 -1935
- claude_mpm/services/communication/websocket.py +0 -479
- claude_mpm/services/framework_claude_md_generator.py +0 -624
- claude_mpm/services/health_monitor.py +0 -893
- claude_mpm/services/infrastructure/graceful_degradation.py +0 -616
- claude_mpm/services/infrastructure/health_monitor.py +0 -775
- claude_mpm/services/infrastructure/memory_dashboard.py +0 -479
- claude_mpm/services/infrastructure/memory_guardian.py +0 -944
- claude_mpm/services/infrastructure/restart_protection.py +0 -642
- claude_mpm/services/infrastructure/state_manager.py +0 -774
- claude_mpm/services/mcp_gateway/manager.py +0 -334
- claude_mpm/services/optimized_hook_service.py +0 -542
- claude_mpm/services/project_analyzer.py +0 -864
- claude_mpm/services/project_registry.py +0 -608
- claude_mpm/services/standalone_socketio_server.py +0 -1300
- claude_mpm/services/ticket_manager_di.py +0 -318
- claude_mpm/services/ticketing_service_original.py +0 -510
- claude_mpm/utils/paths.py +0 -395
- claude_mpm/utils/platform_memory.py +0 -524
- claude_mpm-3.9.11.dist-info/RECORD +0 -306
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/WHEEL +0 -0
- {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/top_level.txt +0 -0
| @@ -0,0 +1,304 @@ | |
| 1 | 
            +
            from pathlib import Path
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            """Session management service for orchestrating Claude sessions.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            This service handles:
         | 
| 6 | 
            +
            1. Interactive session orchestration
         | 
| 7 | 
            +
            2. Oneshot session orchestration
         | 
| 8 | 
            +
            3. Session lifecycle management
         | 
| 9 | 
            +
            4. Session logging and cleanup
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            Extracted from ClaudeRunner to follow Single Responsibility Principle.
         | 
| 12 | 
            +
            """
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            import time
         | 
| 15 | 
            +
            import uuid
         | 
| 16 | 
            +
            from typing import Any, Dict, List, Optional
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            from claude_mpm.core.base_service import BaseService
         | 
| 19 | 
            +
            from claude_mpm.services.core.interfaces import SessionManagementInterface
         | 
| 20 | 
            +
             | 
| 21 | 
            +
             | 
| 22 | 
            +
            class SessionManagementService(BaseService, SessionManagementInterface):
         | 
| 23 | 
            +
                """Service for managing Claude session orchestration."""
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                def __init__(self, runner=None):
         | 
| 26 | 
            +
                    """Initialize the session management service.
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    Args:
         | 
| 29 | 
            +
                        runner: ClaudeRunner instance for delegation
         | 
| 30 | 
            +
                    """
         | 
| 31 | 
            +
                    super().__init__(name="session_management_service")
         | 
| 32 | 
            +
                    self.runner = runner
         | 
| 33 | 
            +
                    self.active_sessions = {}  # Track active sessions
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                async def _initialize(self) -> None:
         | 
| 36 | 
            +
                    """Initialize the service. No special initialization needed."""
         | 
| 37 | 
            +
                    pass
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                async def _cleanup(self) -> None:
         | 
| 40 | 
            +
                    """Cleanup service resources. No cleanup needed."""
         | 
| 41 | 
            +
                    pass
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def run_interactive_session(self, initial_context: Optional[str] = None) -> bool:
         | 
| 44 | 
            +
                    """Run Claude in interactive mode using session delegation.
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    WHY: This method delegates to InteractiveSession class for better
         | 
| 47 | 
            +
                    maintainability and reduced complexity. The session class handles all
         | 
| 48 | 
            +
                    the details while this method provides the orchestration interface.
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    Args:
         | 
| 51 | 
            +
                        initial_context: Optional initial context to pass to Claude
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    Returns:
         | 
| 54 | 
            +
                        bool: True if session completed successfully, False otherwise
         | 
| 55 | 
            +
                    """
         | 
| 56 | 
            +
                    try:
         | 
| 57 | 
            +
                        from claude_mpm.core.interactive_session import InteractiveSession
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                        # Create session handler
         | 
| 60 | 
            +
                        session = InteractiveSession(self.runner)
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                        # Step 1: Initialize session
         | 
| 63 | 
            +
                        success, error = session.initialize_interactive_session()
         | 
| 64 | 
            +
                        if not success:
         | 
| 65 | 
            +
                            self.logger.error(f"Failed to initialize interactive session: {error}")
         | 
| 66 | 
            +
                            return False
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                        # Step 2: Set up environment
         | 
| 69 | 
            +
                        success, environment = session.setup_interactive_environment()
         | 
| 70 | 
            +
                        if not success:
         | 
| 71 | 
            +
                            self.logger.error("Failed to setup interactive environment")
         | 
| 72 | 
            +
                            return False
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                        # Step 3: Handle interactive input/output
         | 
| 75 | 
            +
                        # This is where the actual Claude process runs
         | 
| 76 | 
            +
                        session.handle_interactive_input(environment)
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                        return True
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                    except Exception as e:
         | 
| 81 | 
            +
                        self.logger.error(f"Interactive session failed: {e}")
         | 
| 82 | 
            +
                        return False
         | 
| 83 | 
            +
                    finally:
         | 
| 84 | 
            +
                        # Step 4: Clean up session
         | 
| 85 | 
            +
                        if "session" in locals():
         | 
| 86 | 
            +
                            session.cleanup_interactive_session()
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                def run_oneshot_session(self, prompt: str, context: Optional[str] = None) -> bool:
         | 
| 89 | 
            +
                    """Run Claude with a single prompt using session delegation.
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                    WHY: This method delegates to OneshotSession class for better
         | 
| 92 | 
            +
                    maintainability and reduced complexity. The session class handles
         | 
| 93 | 
            +
                    all the details while this method provides the orchestration interface.
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                    Args:
         | 
| 96 | 
            +
                        prompt: The command or prompt to execute
         | 
| 97 | 
            +
                        context: Optional context to prepend to the prompt
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                    Returns:
         | 
| 100 | 
            +
                        bool: True if successful, False otherwise
         | 
| 101 | 
            +
                    """
         | 
| 102 | 
            +
                    try:
         | 
| 103 | 
            +
                        from claude_mpm.core.oneshot_session import OneshotSession
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                        # Create session handler
         | 
| 106 | 
            +
                        session = OneshotSession(self.runner)
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                        # Step 1: Initialize session
         | 
| 109 | 
            +
                        success, error = session.initialize_session(prompt)
         | 
| 110 | 
            +
                        if not success:
         | 
| 111 | 
            +
                            return False
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                        # Special case: MPM commands return early
         | 
| 114 | 
            +
                        if (
         | 
| 115 | 
            +
                            error is None
         | 
| 116 | 
            +
                            and self.runner.command_handler_service
         | 
| 117 | 
            +
                            and self.runner.command_handler_service.is_mpm_command(prompt)
         | 
| 118 | 
            +
                        ):
         | 
| 119 | 
            +
                            return success
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                        # Step 2: Deploy agents
         | 
| 122 | 
            +
                        if not session.deploy_agents():
         | 
| 123 | 
            +
                            self.logger.warning("Agent deployment had issues, continuing...")
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                        # Step 3: Set up infrastructure
         | 
| 126 | 
            +
                        infrastructure = session.setup_infrastructure()
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                        # Step 4: Execute command
         | 
| 129 | 
            +
                        success, response = session.execute_command(prompt, context, infrastructure)
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                        return success
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                    except Exception as e:
         | 
| 134 | 
            +
                        self.logger.error(f"Oneshot session failed: {e}")
         | 
| 135 | 
            +
                        return False
         | 
| 136 | 
            +
                    finally:
         | 
| 137 | 
            +
                        # Step 5: Clean up session
         | 
| 138 | 
            +
                        if "session" in locals():
         | 
| 139 | 
            +
                            session.cleanup_session()
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                def create_session_log_file(self) -> Optional[Path]:
         | 
| 142 | 
            +
                    """Create a session log file for the current session.
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                    Returns:
         | 
| 145 | 
            +
                        Path to the created log file, or None if creation failed
         | 
| 146 | 
            +
                    """
         | 
| 147 | 
            +
                    try:
         | 
| 148 | 
            +
                        import uuid
         | 
| 149 | 
            +
                        from datetime import datetime
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                        from claude_mpm.config.paths import paths
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                        # Create session logs directory if it doesn't exist
         | 
| 154 | 
            +
                        session_logs_dir = paths.project_root / ".claude-mpm" / "logs" / "sessions"
         | 
| 155 | 
            +
                        session_logs_dir.mkdir(parents=True, exist_ok=True)
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                        # Generate unique session log filename
         | 
| 158 | 
            +
                        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
         | 
| 159 | 
            +
                        session_id = str(uuid.uuid4())[:8]
         | 
| 160 | 
            +
                        log_filename = f"session_{timestamp}_{session_id}.jsonl"
         | 
| 161 | 
            +
                        log_file = session_logs_dir / log_filename
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                        # Create empty log file
         | 
| 164 | 
            +
                        log_file.touch()
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                        self.logger.debug(f"Created session log file: {log_file}")
         | 
| 167 | 
            +
                        return log_file
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                    except Exception as e:
         | 
| 170 | 
            +
                        self.logger.warning(f"Failed to create session log file: {e}")
         | 
| 171 | 
            +
                        return None
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                def log_session_event(self, log_file: Path, event_data: dict):
         | 
| 174 | 
            +
                    """Log an event to the session log file.
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                    Args:
         | 
| 177 | 
            +
                        log_file: Path to the session log file
         | 
| 178 | 
            +
                        event_data: Event data to log
         | 
| 179 | 
            +
                    """
         | 
| 180 | 
            +
                    if not log_file or not log_file.exists():
         | 
| 181 | 
            +
                        return
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                    try:
         | 
| 184 | 
            +
                        import json
         | 
| 185 | 
            +
                        from datetime import datetime
         | 
| 186 | 
            +
             | 
| 187 | 
            +
                        # Add timestamp to event data
         | 
| 188 | 
            +
                        event_data["timestamp"] = datetime.now().isoformat()
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                        # Append to log file as JSONL
         | 
| 191 | 
            +
                        with open(log_file, "a") as f:
         | 
| 192 | 
            +
                            f.write(json.dumps(event_data) + "\n")
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                    except Exception as e:
         | 
| 195 | 
            +
                        self.logger.warning(f"Failed to log session event: {e}")
         | 
| 196 | 
            +
             | 
| 197 | 
            +
                def get_service_status(self) -> dict:
         | 
| 198 | 
            +
                    """Get current session management service status.
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                    Returns:
         | 
| 201 | 
            +
                        dict: Session management service status information
         | 
| 202 | 
            +
                    """
         | 
| 203 | 
            +
                    return {
         | 
| 204 | 
            +
                        "service_available": True,
         | 
| 205 | 
            +
                        "runner_available": self.runner is not None,
         | 
| 206 | 
            +
                        "interactive_session_available": True,
         | 
| 207 | 
            +
                        "oneshot_session_available": True,
         | 
| 208 | 
            +
                        "active_sessions": len(self.active_sessions),
         | 
| 209 | 
            +
                    }
         | 
| 210 | 
            +
             | 
| 211 | 
            +
                # Implementation of abstract methods from SessionManagementInterface
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                def start_session(self, session_config: Dict[str, Any]) -> str:
         | 
| 214 | 
            +
                    """Start a new session.
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                    Args:
         | 
| 217 | 
            +
                        session_config: Configuration for the session
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                    Returns:
         | 
| 220 | 
            +
                        Session ID
         | 
| 221 | 
            +
                    """
         | 
| 222 | 
            +
                    session_id = str(uuid.uuid4())
         | 
| 223 | 
            +
                    session_info = {
         | 
| 224 | 
            +
                        "id": session_id,
         | 
| 225 | 
            +
                        "config": session_config,
         | 
| 226 | 
            +
                        "start_time": time.time(),
         | 
| 227 | 
            +
                        "status": "active",
         | 
| 228 | 
            +
                        "type": session_config.get("type", "interactive"),
         | 
| 229 | 
            +
                    }
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                    self.active_sessions[session_id] = session_info
         | 
| 232 | 
            +
                    self.logger.info(f"Started session {session_id}")
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                    return session_id
         | 
| 235 | 
            +
             | 
| 236 | 
            +
                def end_session(self, session_id: str) -> bool:
         | 
| 237 | 
            +
                    """End an active session.
         | 
| 238 | 
            +
             | 
| 239 | 
            +
                    Args:
         | 
| 240 | 
            +
                        session_id: ID of session to end
         | 
| 241 | 
            +
             | 
| 242 | 
            +
                    Returns:
         | 
| 243 | 
            +
                        True if session ended successfully
         | 
| 244 | 
            +
                    """
         | 
| 245 | 
            +
                    if session_id in self.active_sessions:
         | 
| 246 | 
            +
                        session_info = self.active_sessions[session_id]
         | 
| 247 | 
            +
                        session_info["status"] = "ended"
         | 
| 248 | 
            +
                        session_info["end_time"] = time.time()
         | 
| 249 | 
            +
             | 
| 250 | 
            +
                        # Remove from active sessions
         | 
| 251 | 
            +
                        del self.active_sessions[session_id]
         | 
| 252 | 
            +
             | 
| 253 | 
            +
                        self.logger.info(f"Ended session {session_id}")
         | 
| 254 | 
            +
                        return True
         | 
| 255 | 
            +
                    else:
         | 
| 256 | 
            +
                        self.logger.warning(f"Session {session_id} not found")
         | 
| 257 | 
            +
                        return False
         | 
| 258 | 
            +
             | 
| 259 | 
            +
                def get_session_status(self, session_id: str) -> Dict[str, Any]:
         | 
| 260 | 
            +
                    """Get status of a session.
         | 
| 261 | 
            +
             | 
| 262 | 
            +
                    Args:
         | 
| 263 | 
            +
                        session_id: ID of session
         | 
| 264 | 
            +
             | 
| 265 | 
            +
                    Returns:
         | 
| 266 | 
            +
                        Dictionary with session status information
         | 
| 267 | 
            +
                    """
         | 
| 268 | 
            +
                    if session_id in self.active_sessions:
         | 
| 269 | 
            +
                        return self.active_sessions[session_id].copy()
         | 
| 270 | 
            +
                    else:
         | 
| 271 | 
            +
                        return {
         | 
| 272 | 
            +
                            "id": session_id,
         | 
| 273 | 
            +
                            "status": "not_found",
         | 
| 274 | 
            +
                            "error": "Session not found",
         | 
| 275 | 
            +
                        }
         | 
| 276 | 
            +
             | 
| 277 | 
            +
                def list_active_sessions(self) -> List[str]:
         | 
| 278 | 
            +
                    """List all active session IDs.
         | 
| 279 | 
            +
             | 
| 280 | 
            +
                    Returns:
         | 
| 281 | 
            +
                        List of active session IDs
         | 
| 282 | 
            +
                    """
         | 
| 283 | 
            +
                    return list(self.active_sessions.keys())
         | 
| 284 | 
            +
             | 
| 285 | 
            +
                async def cleanup_sessions(self) -> int:
         | 
| 286 | 
            +
                    """Clean up inactive or expired sessions.
         | 
| 287 | 
            +
             | 
| 288 | 
            +
                    Returns:
         | 
| 289 | 
            +
                        Number of sessions cleaned up
         | 
| 290 | 
            +
                    """
         | 
| 291 | 
            +
                    current_time = time.time()
         | 
| 292 | 
            +
                    expired_sessions = []
         | 
| 293 | 
            +
             | 
| 294 | 
            +
                    # Find sessions older than 24 hours
         | 
| 295 | 
            +
                    for session_id, session_info in self.active_sessions.items():
         | 
| 296 | 
            +
                        if current_time - session_info["start_time"] > 86400:  # 24 hours
         | 
| 297 | 
            +
                            expired_sessions.append(session_id)
         | 
| 298 | 
            +
             | 
| 299 | 
            +
                    # Clean up expired sessions
         | 
| 300 | 
            +
                    for session_id in expired_sessions:
         | 
| 301 | 
            +
                        self.end_session(session_id)
         | 
| 302 | 
            +
             | 
| 303 | 
            +
                    self.logger.info(f"Cleaned up {len(expired_sessions)} expired sessions")
         | 
| 304 | 
            +
                    return len(expired_sessions)
         | 
| @@ -7,11 +7,11 @@ with separated event handlers for improved maintainability. | |
| 7 7 | 
             
            from .handlers import (
         | 
| 8 8 | 
             
                BaseEventHandler,
         | 
| 9 9 | 
             
                ConnectionEventHandler,
         | 
| 10 | 
            -
                 | 
| 11 | 
            -
                MemoryEventHandler,
         | 
| 10 | 
            +
                EventHandlerRegistry,
         | 
| 12 11 | 
             
                FileEventHandler,
         | 
| 13 12 | 
             
                GitEventHandler,
         | 
| 14 | 
            -
                 | 
| 13 | 
            +
                MemoryEventHandler,
         | 
| 14 | 
            +
                ProjectEventHandler,
         | 
| 15 15 | 
             
            )
         | 
| 16 16 |  | 
| 17 17 | 
             
            __all__ = [
         | 
| @@ -22,4 +22,4 @@ __all__ = [ | |
| 22 22 | 
             
                "FileEventHandler",
         | 
| 23 23 | 
             
                "GitEventHandler",
         | 
| 24 24 | 
             
                "EventHandlerRegistry",
         | 
| 25 | 
            -
            ]
         | 
| 25 | 
            +
            ]
         | 
| @@ -0,0 +1,174 @@ | |
| 1 | 
            +
            """
         | 
| 2 | 
            +
            SocketIO Client Proxy for claude-mpm.
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            WHY: This module contains the SocketIOClientProxy class that was extracted from
         | 
| 5 | 
            +
            the monolithic socketio_server.py file. In exec mode, a persistent Socket.IO
         | 
| 6 | 
            +
            server runs in a separate process, and this proxy provides a Socket.IO-like
         | 
| 7 | 
            +
            interface without starting another server.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            DESIGN DECISION: Separated client proxy logic from server logic for better
         | 
| 10 | 
            +
            organization and to reduce the complexity of the main server file.
         | 
| 11 | 
            +
            """
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            import asyncio
         | 
| 14 | 
            +
            import threading
         | 
| 15 | 
            +
            import time
         | 
| 16 | 
            +
            from datetime import datetime
         | 
| 17 | 
            +
            from typing import Any, Dict, List, Optional, Set
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            try:
         | 
| 20 | 
            +
                import socketio
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                SOCKETIO_AVAILABLE = True
         | 
| 23 | 
            +
            except ImportError:
         | 
| 24 | 
            +
                SOCKETIO_AVAILABLE = False
         | 
| 25 | 
            +
                socketio = None
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            from ...core.logging_config import get_logger
         | 
| 28 | 
            +
             | 
| 29 | 
            +
             | 
| 30 | 
            +
            class SocketIOClientProxy:
         | 
| 31 | 
            +
                """Proxy that connects to an existing Socket.IO server as a client.
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                WHY: In exec mode, a persistent Socket.IO server runs in a separate process.
         | 
| 34 | 
            +
                The hook handler in the Claude process needs a Socket.IO-like interface
         | 
| 35 | 
            +
                but shouldn't start another server. This proxy provides that interface
         | 
| 36 | 
            +
                while the actual events are handled by the persistent server.
         | 
| 37 | 
            +
                """
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def __init__(self, host: str = "localhost", port: int = 8765):
         | 
| 40 | 
            +
                    self.host = host
         | 
| 41 | 
            +
                    self.port = port
         | 
| 42 | 
            +
                    self.logger = get_logger(__name__ + ".SocketIOClientProxy")
         | 
| 43 | 
            +
                    self.running = True  # Always "running" for compatibility
         | 
| 44 | 
            +
                    self._sio_client = None
         | 
| 45 | 
            +
                    self._client_thread = None
         | 
| 46 | 
            +
                    self._client_loop = None
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def start_sync(self):
         | 
| 49 | 
            +
                    """Start the Socket.IO client connection to the persistent server."""
         | 
| 50 | 
            +
                    self.logger.debug(
         | 
| 51 | 
            +
                        f"SocketIOClientProxy: Connecting to server on {self.host}:{self.port}"
         | 
| 52 | 
            +
                    )
         | 
| 53 | 
            +
                    if SOCKETIO_AVAILABLE:
         | 
| 54 | 
            +
                        self._start_client()
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def stop_sync(self):
         | 
| 57 | 
            +
                    """Stop the Socket.IO client connection."""
         | 
| 58 | 
            +
                    self.logger.debug(f"SocketIOClientProxy: Disconnecting from server")
         | 
| 59 | 
            +
                    if self._sio_client:
         | 
| 60 | 
            +
                        self._sio_client.disconnect()
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                def _start_client(self):
         | 
| 63 | 
            +
                    """Start Socket.IO client in a background thread."""
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                    def run_client():
         | 
| 66 | 
            +
                        self._client_loop = asyncio.new_event_loop()
         | 
| 67 | 
            +
                        asyncio.set_event_loop(self._client_loop)
         | 
| 68 | 
            +
                        try:
         | 
| 69 | 
            +
                            self._client_loop.run_until_complete(self._connect_and_run())
         | 
| 70 | 
            +
                        except Exception as e:
         | 
| 71 | 
            +
                            self.logger.error(f"SocketIOClientProxy client thread error: {e}")
         | 
| 72 | 
            +
                        finally:
         | 
| 73 | 
            +
                            self._client_loop.close()
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                    self._client_thread = threading.Thread(target=run_client, daemon=True)
         | 
| 76 | 
            +
                    self._client_thread.start()
         | 
| 77 | 
            +
                    # Give it a moment to connect
         | 
| 78 | 
            +
                    time.sleep(0.2)
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                async def _connect_and_run(self):
         | 
| 81 | 
            +
                    """Connect to the persistent Socket.IO server and keep connection alive."""
         | 
| 82 | 
            +
                    try:
         | 
| 83 | 
            +
                        self._sio_client = socketio.AsyncClient()
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                        @self._sio_client.event
         | 
| 86 | 
            +
                        async def connect():
         | 
| 87 | 
            +
                            self.logger.info(
         | 
| 88 | 
            +
                                f"SocketIOClientProxy: Connected to server at http://{self.host}:{self.port}"
         | 
| 89 | 
            +
                            )
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                        @self._sio_client.event
         | 
| 92 | 
            +
                        async def disconnect():
         | 
| 93 | 
            +
                            self.logger.info(f"SocketIOClientProxy: Disconnected from server")
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                        # Connect to the server
         | 
| 96 | 
            +
                        await self._sio_client.connect(f"http://127.0.0.1:{self.port}")
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                        # Keep the connection alive until stopped
         | 
| 99 | 
            +
                        while self.running:
         | 
| 100 | 
            +
                            await asyncio.sleep(1)
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                    except Exception as e:
         | 
| 103 | 
            +
                        self.logger.error(f"SocketIOClientProxy: Connection error: {e}")
         | 
| 104 | 
            +
                        self._sio_client = None
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                def broadcast_event(self, event_type: str, data: Dict[str, Any]):
         | 
| 107 | 
            +
                    """Send event to the persistent Socket.IO server."""
         | 
| 108 | 
            +
                    if not SOCKETIO_AVAILABLE:
         | 
| 109 | 
            +
                        return
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    # Ensure client is started
         | 
| 112 | 
            +
                    if not self._client_thread or not self._client_thread.is_alive():
         | 
| 113 | 
            +
                        self.logger.debug(f"SocketIOClientProxy: Starting client for {event_type}")
         | 
| 114 | 
            +
                        self._start_client()
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                    if self._sio_client and self._sio_client.connected:
         | 
| 117 | 
            +
                        try:
         | 
| 118 | 
            +
                            event = {
         | 
| 119 | 
            +
                                "type": event_type,
         | 
| 120 | 
            +
                                "timestamp": datetime.now().isoformat(),
         | 
| 121 | 
            +
                                "data": data,
         | 
| 122 | 
            +
                            }
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                            # Send event safely using run_coroutine_threadsafe
         | 
| 125 | 
            +
                            if (
         | 
| 126 | 
            +
                                hasattr(self, "_client_loop")
         | 
| 127 | 
            +
                                and self._client_loop
         | 
| 128 | 
            +
                                and not self._client_loop.is_closed()
         | 
| 129 | 
            +
                            ):
         | 
| 130 | 
            +
                                try:
         | 
| 131 | 
            +
                                    future = asyncio.run_coroutine_threadsafe(
         | 
| 132 | 
            +
                                        self._sio_client.emit("claude_event", event),
         | 
| 133 | 
            +
                                        self._client_loop,
         | 
| 134 | 
            +
                                    )
         | 
| 135 | 
            +
                                    # Don't wait for the result to avoid blocking
         | 
| 136 | 
            +
                                    self.logger.debug(
         | 
| 137 | 
            +
                                        f"SocketIOClientProxy: Scheduled emit for {event_type}"
         | 
| 138 | 
            +
                                    )
         | 
| 139 | 
            +
                                except Exception as e:
         | 
| 140 | 
            +
                                    self.logger.error(
         | 
| 141 | 
            +
                                        f"SocketIOClientProxy: Failed to schedule emit for {event_type}: {e}"
         | 
| 142 | 
            +
                                    )
         | 
| 143 | 
            +
                            else:
         | 
| 144 | 
            +
                                self.logger.warning(
         | 
| 145 | 
            +
                                    f"SocketIOClientProxy: Client event loop not available for {event_type}"
         | 
| 146 | 
            +
                                )
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                            self.logger.debug(f"SocketIOClientProxy: Sent event {event_type}")
         | 
| 149 | 
            +
                        except Exception as e:
         | 
| 150 | 
            +
                            self.logger.error(
         | 
| 151 | 
            +
                                f"SocketIOClientProxy: Failed to send event {event_type}: {e}"
         | 
| 152 | 
            +
                            )
         | 
| 153 | 
            +
                    else:
         | 
| 154 | 
            +
                        self.logger.warning(
         | 
| 155 | 
            +
                            f"SocketIOClientProxy: Client not ready for {event_type}"
         | 
| 156 | 
            +
                        )
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                # Compatibility methods for WebSocketServer interface
         | 
| 159 | 
            +
                def session_started(self, session_id: str, launch_method: str, working_dir: str):
         | 
| 160 | 
            +
                    self.logger.debug(f"SocketIOClientProxy: Session started {session_id}")
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                def session_ended(self):
         | 
| 163 | 
            +
                    self.logger.debug(f"SocketIOClientProxy: Session ended")
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                def claude_status_changed(
         | 
| 166 | 
            +
                    self, status: str, pid: Optional[int] = None, message: str = ""
         | 
| 167 | 
            +
                ):
         | 
| 168 | 
            +
                    self.logger.debug(f"SocketIOClientProxy: Claude status {status}")
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                def agent_delegated(self, agent: str, task: str, status: str = "started"):
         | 
| 171 | 
            +
                    self.logger.debug(f"SocketIOClientProxy: Agent {agent} delegated")
         | 
| 172 | 
            +
             | 
| 173 | 
            +
                def todo_updated(self, todos: List[Dict[str, Any]]):
         | 
| 174 | 
            +
                    self.logger.debug(f"SocketIOClientProxy: Todo updated ({len(todos)} todos)")
         | 
| @@ -8,10 +8,10 @@ and maintainability. | |
| 8 8 |  | 
| 9 9 | 
             
            from .base import BaseEventHandler
         | 
| 10 10 | 
             
            from .connection import ConnectionEventHandler
         | 
| 11 | 
            -
            from .project import ProjectEventHandler
         | 
| 12 | 
            -
            from .memory import MemoryEventHandler
         | 
| 13 11 | 
             
            from .file import FileEventHandler
         | 
| 14 12 | 
             
            from .git import GitEventHandler
         | 
| 13 | 
            +
            from .memory import MemoryEventHandler
         | 
| 14 | 
            +
            from .project import ProjectEventHandler
         | 
| 15 15 | 
             
            from .registry import EventHandlerRegistry
         | 
| 16 16 |  | 
| 17 17 | 
             
            __all__ = [
         | 
| @@ -22,4 +22,4 @@ __all__ = [ | |
| 22 22 | 
             
                "FileEventHandler",
         | 
| 23 23 | 
             
                "GitEventHandler",
         | 
| 24 24 | 
             
                "EventHandlerRegistry",
         | 
| 25 | 
            -
            ]
         | 
| 25 | 
            +
            ]
         | 
| @@ -6,52 +6,55 @@ classes inherit from this to ensure consistent behavior. | |
| 6 6 | 
             
            """
         | 
| 7 7 |  | 
| 8 8 | 
             
            import logging
         | 
| 9 | 
            -
            from typing import Any, Dict, Optional, List, TYPE_CHECKING
         | 
| 10 9 | 
             
            from datetime import datetime
         | 
| 11 10 | 
             
            from logging import Logger
         | 
| 11 | 
            +
            from typing import TYPE_CHECKING, Any, Dict, List, Optional
         | 
| 12 12 |  | 
| 13 13 | 
             
            from ....core.logger import get_logger
         | 
| 14 | 
            -
            from ....core.typing_utils import  | 
| 14 | 
            +
            from ....core.typing_utils import EventData, EventName, SocketId
         | 
| 15 15 |  | 
| 16 16 | 
             
            if TYPE_CHECKING:
         | 
| 17 | 
            -
                from ..server import SocketIOServer
         | 
| 18 17 | 
             
                import socketio
         | 
| 19 18 |  | 
| 19 | 
            +
                from ..server import SocketIOServer
         | 
| 20 | 
            +
             | 
| 20 21 |  | 
| 21 22 | 
             
            class BaseEventHandler:
         | 
| 22 23 | 
             
                """Base class for Socket.IO event handlers.
         | 
| 23 | 
            -
             | 
| 24 | 
            +
             | 
| 24 25 | 
             
                WHY: Provides common functionality and structure for all event handlers,
         | 
| 25 26 | 
             
                ensuring consistent error handling, logging, and server access patterns.
         | 
| 26 27 | 
             
                Each handler focuses on a specific domain while sharing common infrastructure.
         | 
| 27 28 | 
             
                """
         | 
| 28 | 
            -
             | 
| 29 | 
            -
                def __init__(self, server:  | 
| 29 | 
            +
             | 
| 30 | 
            +
                def __init__(self, server: "SocketIOServer") -> None:
         | 
| 30 31 | 
             
                    """Initialize the base handler.
         | 
| 31 | 
            -
             | 
| 32 | 
            +
             | 
| 32 33 | 
             
                    Args:
         | 
| 33 34 | 
             
                        server: The SocketIOServer instance that owns this handler
         | 
| 34 35 | 
             
                    """
         | 
| 35 | 
            -
                    self.server:  | 
| 36 | 
            -
                    self.sio:  | 
| 36 | 
            +
                    self.server: "SocketIOServer" = server
         | 
| 37 | 
            +
                    self.sio: "socketio.AsyncServer" = server.sio
         | 
| 37 38 | 
             
                    self.logger: Logger = get_logger(self.__class__.__name__)
         | 
| 38 39 | 
             
                    self.clients: Dict[SocketId, Dict[str, Any]] = server.clients
         | 
| 39 40 | 
             
                    self.event_history: List[Dict[str, Any]] = server.event_history
         | 
| 40 | 
            -
             | 
| 41 | 
            +
             | 
| 41 42 | 
             
                def register_events(self) -> None:
         | 
| 42 43 | 
             
                    """Register all events handled by this handler.
         | 
| 43 | 
            -
             | 
| 44 | 
            +
             | 
| 44 45 | 
             
                    WHY: This method must be implemented by each handler subclass
         | 
| 45 46 | 
             
                    to register its specific events with the Socket.IO server.
         | 
| 46 47 | 
             
                    """
         | 
| 47 48 | 
             
                    raise NotImplementedError("Subclasses must implement register_events()")
         | 
| 48 | 
            -
             | 
| 49 | 
            -
                async def emit_to_client( | 
| 49 | 
            +
             | 
| 50 | 
            +
                async def emit_to_client(
         | 
| 51 | 
            +
                    self, sid: SocketId, event: EventName, data: EventData
         | 
| 52 | 
            +
                ) -> None:
         | 
| 50 53 | 
             
                    """Emit an event to a specific client.
         | 
| 51 | 
            -
             | 
| 54 | 
            +
             | 
| 52 55 | 
             
                    WHY: Centralizes client communication with consistent error handling
         | 
| 53 56 | 
             
                    and logging for debugging connection issues.
         | 
| 54 | 
            -
             | 
| 57 | 
            +
             | 
| 55 58 | 
             
                    Args:
         | 
| 56 59 | 
             
                        sid: Socket.IO session ID of the client
         | 
| 57 60 | 
             
                        event: Event name to emit
         | 
| @@ -63,34 +66,40 @@ class BaseEventHandler: | |
| 63 66 | 
             
                    except Exception as e:
         | 
| 64 67 | 
             
                        self.logger.error(f"Failed to emit {event} to client {sid}: {e}")
         | 
| 65 68 | 
             
                        import traceback
         | 
| 69 | 
            +
             | 
| 66 70 | 
             
                        self.logger.error(f"Stack trace: {traceback.format_exc()}")
         | 
| 67 | 
            -
             | 
| 68 | 
            -
                async def broadcast_event( | 
| 71 | 
            +
             | 
| 72 | 
            +
                async def broadcast_event(
         | 
| 73 | 
            +
                    self, event: EventName, data: EventData, skip_sid: Optional[SocketId] = None
         | 
| 74 | 
            +
                ) -> None:
         | 
| 69 75 | 
             
                    """Broadcast an event to all connected clients.
         | 
| 70 | 
            -
             | 
| 76 | 
            +
             | 
| 71 77 | 
             
                    WHY: Provides consistent broadcasting with optional exclusion
         | 
| 72 78 | 
             
                    of the originating client.
         | 
| 73 | 
            -
             | 
| 79 | 
            +
             | 
| 74 80 | 
             
                    Args:
         | 
| 75 81 | 
             
                        event: Event name to broadcast
         | 
| 76 82 | 
             
                        data: Data to send with the event
         | 
| 77 83 | 
             
                        skip_sid: Optional session ID to skip
         | 
| 78 84 | 
             
                    """
         | 
| 79 85 | 
             
                    try:
         | 
| 86 | 
            +
                        self.logger.info(f"🔔 Broadcasting {event} (skip_sid={skip_sid})")
         | 
| 80 87 | 
             
                        if skip_sid:
         | 
| 81 88 | 
             
                            await self.sio.emit(event, data, skip_sid=skip_sid)
         | 
| 82 89 | 
             
                        else:
         | 
| 83 90 | 
             
                            await self.sio.emit(event, data)
         | 
| 84 | 
            -
                        self.logger. | 
| 91 | 
            +
                        self.logger.info(f"✅ Broadcasted {event} to all clients")
         | 
| 85 92 | 
             
                    except Exception as e:
         | 
| 86 93 | 
             
                        self.logger.error(f"Failed to broadcast {event}: {e}")
         | 
| 87 | 
            -
             | 
| 94 | 
            +
                        import traceback
         | 
| 95 | 
            +
                        self.logger.error(f"Stack trace: {traceback.format_exc()}")
         | 
| 96 | 
            +
             | 
| 88 97 | 
             
                def add_to_history(self, event_type: str, data: EventData) -> None:
         | 
| 89 98 | 
             
                    """Add an event to the server's event history.
         | 
| 90 | 
            -
             | 
| 99 | 
            +
             | 
| 91 100 | 
             
                    WHY: Maintains a history of events for new clients to receive
         | 
| 92 101 | 
             
                    when they connect, ensuring they have context.
         | 
| 93 | 
            -
             | 
| 102 | 
            +
             | 
| 94 103 | 
             
                    Args:
         | 
| 95 104 | 
             
                        event_type: Type of the event
         | 
| 96 105 | 
             
                        data: Event data
         | 
| @@ -98,17 +107,21 @@ class BaseEventHandler: | |
| 98 107 | 
             
                    event = {
         | 
| 99 108 | 
             
                        "type": event_type,
         | 
| 100 109 | 
             
                        "timestamp": datetime.utcnow().isoformat() + "Z",
         | 
| 101 | 
            -
                        "data": data
         | 
| 110 | 
            +
                        "data": data,
         | 
| 102 111 | 
             
                    }
         | 
| 103 112 | 
             
                    self.event_history.append(event)
         | 
| 104 | 
            -
                    self.logger.debug( | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 113 | 
            +
                    self.logger.debug(
         | 
| 114 | 
            +
                        f"Added {event_type} to history (total: {len(self.event_history)})"
         | 
| 115 | 
            +
                    )
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                def log_error(
         | 
| 118 | 
            +
                    self, operation: str, error: Exception, context: Optional[Dict[str, Any]] = None
         | 
| 119 | 
            +
                ) -> None:
         | 
| 107 120 | 
             
                    """Log an error with context.
         | 
| 108 | 
            -
             | 
| 121 | 
            +
             | 
| 109 122 | 
             
                    WHY: Provides consistent error logging with context information
         | 
| 110 123 | 
             
                    for debugging issues in production.
         | 
| 111 | 
            -
             | 
| 124 | 
            +
             | 
| 112 125 | 
             
                    Args:
         | 
| 113 126 | 
             
                        operation: Description of the operation that failed
         | 
| 114 127 | 
             
                        error: The exception that occurred
         | 
| @@ -118,4 +131,5 @@ class BaseEventHandler: | |
| 118 131 | 
             
                    if context:
         | 
| 119 132 | 
             
                        self.logger.error(f"Context: {context}")
         | 
| 120 133 | 
             
                    import traceback
         | 
| 121 | 
            -
             | 
| 134 | 
            +
             | 
| 135 | 
            +
                    self.logger.error(f"Stack trace: {traceback.format_exc()}")
         |