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
| @@ -1,1300 +0,0 @@ | |
| 1 | 
            -
            """Standalone Socket.IO server with independent versioning and deployment agnostic design.
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            This server is designed to run independently of claude-mpm and maintain its own versioning.
         | 
| 4 | 
            -
            It provides a persistent Socket.IO service that can handle multiple claude-mpm client connections.
         | 
| 5 | 
            -
             | 
| 6 | 
            -
            KEY DESIGN PRINCIPLES:
         | 
| 7 | 
            -
            1. Single server per machine - Only one instance should run
         | 
| 8 | 
            -
            2. Persistent across sessions - Server keeps running when code is pushed  
         | 
| 9 | 
            -
            3. Separate versioning - Server has its own version schema independent of claude-mpm
         | 
| 10 | 
            -
            4. Version compatibility mapping - Track which server versions work with which claude-mpm versions
         | 
| 11 | 
            -
            5. Deployment agnostic - Works with local script, PyPI, npm installations
         | 
| 12 | 
            -
             | 
| 13 | 
            -
            WHY standalone architecture:
         | 
| 14 | 
            -
            - Allows server evolution independent of claude-mpm releases
         | 
| 15 | 
            -
            - Enables persistent monitoring across multiple claude-mpm sessions
         | 
| 16 | 
            -
            - Provides better resource management (one server vs multiple)
         | 
| 17 | 
            -
            - Simplifies debugging and maintenance
         | 
| 18 | 
            -
            - Supports different installation methods (PyPI, local, Docker, etc.)
         | 
| 19 | 
            -
            """
         | 
| 20 | 
            -
             | 
| 21 | 
            -
            import asyncio
         | 
| 22 | 
            -
            import json
         | 
| 23 | 
            -
            import logging
         | 
| 24 | 
            -
            import os
         | 
| 25 | 
            -
            import signal
         | 
| 26 | 
            -
            import socket
         | 
| 27 | 
            -
            import sys
         | 
| 28 | 
            -
            import threading
         | 
| 29 | 
            -
            import time
         | 
| 30 | 
            -
            import uuid
         | 
| 31 | 
            -
            from datetime import datetime
         | 
| 32 | 
            -
            from pathlib import Path
         | 
| 33 | 
            -
            from typing import Dict, Any, Optional, List, Set
         | 
| 34 | 
            -
            from collections import deque
         | 
| 35 | 
            -
            import importlib.metadata
         | 
| 36 | 
            -
            import fcntl  # Unix file locking
         | 
| 37 | 
            -
            import platform
         | 
| 38 | 
            -
             | 
| 39 | 
            -
            # Import health monitoring and recovery systems
         | 
| 40 | 
            -
            try:
         | 
| 41 | 
            -
                from .health_monitor import (
         | 
| 42 | 
            -
                    AdvancedHealthMonitor, ProcessResourceChecker, 
         | 
| 43 | 
            -
                    NetworkConnectivityChecker, ServiceHealthChecker,
         | 
| 44 | 
            -
                    HealthStatus, HealthCheckResult
         | 
| 45 | 
            -
                )
         | 
| 46 | 
            -
                from .recovery_manager import RecoveryManager, RecoveryEvent
         | 
| 47 | 
            -
                HEALTH_MONITORING_AVAILABLE = True
         | 
| 48 | 
            -
            except ImportError as e:
         | 
| 49 | 
            -
                HEALTH_MONITORING_AVAILABLE = False
         | 
| 50 | 
            -
                # Create stub classes to prevent errors
         | 
| 51 | 
            -
                class AdvancedHealthMonitor:
         | 
| 52 | 
            -
                    def __init__(self, *args, **kwargs): pass
         | 
| 53 | 
            -
                    def add_checker(self, *args): pass
         | 
| 54 | 
            -
                    def start_monitoring(self): pass
         | 
| 55 | 
            -
                    async def stop_monitoring(self): pass
         | 
| 56 | 
            -
                    def get_current_status(self): return None
         | 
| 57 | 
            -
                    def export_diagnostics(self): return {}
         | 
| 58 | 
            -
                
         | 
| 59 | 
            -
                class RecoveryManager:
         | 
| 60 | 
            -
                    def __init__(self, *args, **kwargs): pass
         | 
| 61 | 
            -
                    def handle_health_result(self, *args): return None
         | 
| 62 | 
            -
                    def get_recovery_status(self): return {}
         | 
| 63 | 
            -
             | 
| 64 | 
            -
            # Import enhanced error classes
         | 
| 65 | 
            -
            try:
         | 
| 66 | 
            -
                from .exceptions import (
         | 
| 67 | 
            -
                    DaemonConflictError, PortConflictError, StaleProcessError,
         | 
| 68 | 
            -
                    RecoveryFailedError, HealthCheckError, format_troubleshooting_guide
         | 
| 69 | 
            -
                )
         | 
| 70 | 
            -
                ENHANCED_ERRORS_AVAILABLE = True
         | 
| 71 | 
            -
            except ImportError as e:
         | 
| 72 | 
            -
                ENHANCED_ERRORS_AVAILABLE = False
         | 
| 73 | 
            -
                # Create stub classes to prevent errors
         | 
| 74 | 
            -
                class DaemonConflictError(Exception): pass
         | 
| 75 | 
            -
                class PortConflictError(Exception): pass
         | 
| 76 | 
            -
                class StaleProcessError(Exception): pass
         | 
| 77 | 
            -
                class RecoveryFailedError(Exception): pass
         | 
| 78 | 
            -
                class HealthCheckError(Exception): pass
         | 
| 79 | 
            -
                def format_troubleshooting_guide(error): return str(error)
         | 
| 80 | 
            -
             | 
| 81 | 
            -
            try:
         | 
| 82 | 
            -
                import psutil
         | 
| 83 | 
            -
                PSUTIL_AVAILABLE = True
         | 
| 84 | 
            -
            except ImportError:
         | 
| 85 | 
            -
                PSUTIL_AVAILABLE = False
         | 
| 86 | 
            -
                psutil = None
         | 
| 87 | 
            -
             | 
| 88 | 
            -
            # Windows file locking support
         | 
| 89 | 
            -
            if platform.system() == 'Windows':
         | 
| 90 | 
            -
                try:
         | 
| 91 | 
            -
                    import msvcrt
         | 
| 92 | 
            -
                    WINDOWS_LOCKING = True
         | 
| 93 | 
            -
                except ImportError:
         | 
| 94 | 
            -
                    WINDOWS_LOCKING = False
         | 
| 95 | 
            -
            else:
         | 
| 96 | 
            -
                WINDOWS_LOCKING = False
         | 
| 97 | 
            -
                msvcrt = None
         | 
| 98 | 
            -
             | 
| 99 | 
            -
            try:
         | 
| 100 | 
            -
                import socketio
         | 
| 101 | 
            -
                from aiohttp import web
         | 
| 102 | 
            -
                SOCKETIO_AVAILABLE = True
         | 
| 103 | 
            -
                
         | 
| 104 | 
            -
                # Get Socket.IO version
         | 
| 105 | 
            -
                try:
         | 
| 106 | 
            -
                    SOCKETIO_VERSION = importlib.metadata.version('python-socketio')
         | 
| 107 | 
            -
                except Exception:
         | 
| 108 | 
            -
                    SOCKETIO_VERSION = 'unknown'
         | 
| 109 | 
            -
                    
         | 
| 110 | 
            -
            except ImportError:
         | 
| 111 | 
            -
                SOCKETIO_AVAILABLE = False
         | 
| 112 | 
            -
                socketio = None
         | 
| 113 | 
            -
                web = None
         | 
| 114 | 
            -
                SOCKETIO_VERSION = 'not-installed'
         | 
| 115 | 
            -
             | 
| 116 | 
            -
            # Standalone server version - independent of claude-mpm
         | 
| 117 | 
            -
            STANDALONE_SERVER_VERSION = "1.0.0"
         | 
| 118 | 
            -
             | 
| 119 | 
            -
            # Compatibility matrix - which server versions work with which claude-mpm versions
         | 
| 120 | 
            -
            COMPATIBILITY_MATRIX = {
         | 
| 121 | 
            -
                "1.0.0": {
         | 
| 122 | 
            -
                    "claude_mpm_versions": [">=0.7.0"],
         | 
| 123 | 
            -
                    "min_python": "3.8",
         | 
| 124 | 
            -
                    "socketio_min": "5.11.0",
         | 
| 125 | 
            -
                    "features": [
         | 
| 126 | 
            -
                        "persistent_server",
         | 
| 127 | 
            -
                        "version_compatibility",
         | 
| 128 | 
            -
                        "process_isolation",
         | 
| 129 | 
            -
                        "health_monitoring",
         | 
| 130 | 
            -
                        "advanced_health_monitoring",
         | 
| 131 | 
            -
                        "automatic_recovery",
         | 
| 132 | 
            -
                        "circuit_breaker",
         | 
| 133 | 
            -
                        "resource_monitoring",
         | 
| 134 | 
            -
                        "event_namespacing",
         | 
| 135 | 
            -
                        "comprehensive_diagnostics",
         | 
| 136 | 
            -
                        "metrics_export"
         | 
| 137 | 
            -
                    ]
         | 
| 138 | 
            -
                }
         | 
| 139 | 
            -
            }
         | 
| 140 | 
            -
             | 
| 141 | 
            -
             | 
| 142 | 
            -
            class StandaloneSocketIOServer:
         | 
| 143 | 
            -
                """Standalone Socket.IO server with independent lifecycle and versioning.
         | 
| 144 | 
            -
                
         | 
| 145 | 
            -
                This server runs independently of claude-mpm processes and provides:
         | 
| 146 | 
            -
                - Version compatibility checking
         | 
| 147 | 
            -
                - Process isolation and management
         | 
| 148 | 
            -
                - Persistent operation across claude-mpm sessions
         | 
| 149 | 
            -
                - Health monitoring and diagnostics
         | 
| 150 | 
            -
                - Event namespacing and routing
         | 
| 151 | 
            -
                """
         | 
| 152 | 
            -
                
         | 
| 153 | 
            -
                def __init__(self, host: str = "localhost", port: int = 8765, 
         | 
| 154 | 
            -
                             server_id: Optional[str] = None):
         | 
| 155 | 
            -
                    self.server_version = STANDALONE_SERVER_VERSION
         | 
| 156 | 
            -
                    self.server_id = server_id or f"socketio-{uuid.uuid4().hex[:8]}"
         | 
| 157 | 
            -
                    self.host = host
         | 
| 158 | 
            -
                    self.port = port
         | 
| 159 | 
            -
                    self.start_time = datetime.utcnow()
         | 
| 160 | 
            -
                    
         | 
| 161 | 
            -
                    # Setup logging
         | 
| 162 | 
            -
                    self.logger = self._setup_logging()
         | 
| 163 | 
            -
                    
         | 
| 164 | 
            -
                    # Server state
         | 
| 165 | 
            -
                    self.running = False
         | 
| 166 | 
            -
                    self.clients: Set[str] = set()
         | 
| 167 | 
            -
                    self.event_history: deque = deque(maxlen=10000)  # Larger history for standalone server
         | 
| 168 | 
            -
                    self.client_versions: Dict[str, str] = {}  # Track client claude-mpm versions
         | 
| 169 | 
            -
                    self.health_stats = {
         | 
| 170 | 
            -
                        "events_processed": 0,
         | 
| 171 | 
            -
                        "clients_served": 0,
         | 
| 172 | 
            -
                        "errors": 0,
         | 
| 173 | 
            -
                        "last_activity": None
         | 
| 174 | 
            -
                    }
         | 
| 175 | 
            -
                    
         | 
| 176 | 
            -
                    # Asyncio components
         | 
| 177 | 
            -
                    self.loop = None
         | 
| 178 | 
            -
                    self.app = None
         | 
| 179 | 
            -
                    self.sio = None
         | 
| 180 | 
            -
                    self.runner = None
         | 
| 181 | 
            -
                    self.site = None
         | 
| 182 | 
            -
                    
         | 
| 183 | 
            -
                    # Process management
         | 
| 184 | 
            -
                    self.pid = os.getpid()
         | 
| 185 | 
            -
                    self.pidfile_path = self._get_pidfile_path()
         | 
| 186 | 
            -
                    self.pidfile_lock = None  # File lock object
         | 
| 187 | 
            -
                    self.process_start_time = None
         | 
| 188 | 
            -
                    if PSUTIL_AVAILABLE:
         | 
| 189 | 
            -
                        try:
         | 
| 190 | 
            -
                            current_process = psutil.Process(self.pid)
         | 
| 191 | 
            -
                            self.process_start_time = current_process.create_time()
         | 
| 192 | 
            -
                        except Exception as e:
         | 
| 193 | 
            -
                            self.logger.warning(f"Could not get process start time: {e}")
         | 
| 194 | 
            -
                    
         | 
| 195 | 
            -
                    if not SOCKETIO_AVAILABLE:
         | 
| 196 | 
            -
                        self.logger.error("Socket.IO dependencies not available. Install with: pip install python-socketio aiohttp")
         | 
| 197 | 
            -
                        return
         | 
| 198 | 
            -
                    
         | 
| 199 | 
            -
                    # Log initialization with comprehensive info
         | 
| 200 | 
            -
                    self.logger.info(f"Standalone Socket.IO server v{self.server_version} initialized")
         | 
| 201 | 
            -
                    self.logger.info(f"Server ID: {self.server_id}, PID: {self.pid}")
         | 
| 202 | 
            -
                    self.logger.info(f"Using python-socketio v{SOCKETIO_VERSION}")
         | 
| 203 | 
            -
                    self.logger.info(f"Enhanced validation: psutil {'available' if PSUTIL_AVAILABLE else 'not available'}")
         | 
| 204 | 
            -
                    self.logger.info(f"File locking: {platform.system()} {'supported' if (platform.system() != 'Windows' or WINDOWS_LOCKING) else 'not supported'}")
         | 
| 205 | 
            -
                    self.logger.info(f"Health monitoring: {'available' if HEALTH_MONITORING_AVAILABLE else 'not available'}")
         | 
| 206 | 
            -
                    
         | 
| 207 | 
            -
                    # Initialize health monitoring system
         | 
| 208 | 
            -
                    self.health_monitor = None
         | 
| 209 | 
            -
                    self.recovery_manager = None
         | 
| 210 | 
            -
                    if HEALTH_MONITORING_AVAILABLE:
         | 
| 211 | 
            -
                        self._initialize_health_monitoring()
         | 
| 212 | 
            -
                    
         | 
| 213 | 
            -
                    if self.process_start_time:
         | 
| 214 | 
            -
                        self.logger.debug(f"Process start time: {self.process_start_time}")
         | 
| 215 | 
            -
                
         | 
| 216 | 
            -
                def _setup_logging(self) -> logging.Logger:
         | 
| 217 | 
            -
                    """Setup dedicated logging for standalone server."""
         | 
| 218 | 
            -
                    logger = logging.getLogger(f"socketio_standalone_{self.server_id}")
         | 
| 219 | 
            -
                    
         | 
| 220 | 
            -
                    if not logger.handlers:
         | 
| 221 | 
            -
                        handler = logging.StreamHandler()
         | 
| 222 | 
            -
                        formatter = logging.Formatter(
         | 
| 223 | 
            -
                            f'%(asctime)s - StandaloneSocketIO[{self.server_id}] - %(levelname)s - %(message)s'
         | 
| 224 | 
            -
                        )
         | 
| 225 | 
            -
                        handler.setFormatter(formatter)
         | 
| 226 | 
            -
                        logger.addHandler(handler)
         | 
| 227 | 
            -
                        logger.setLevel(logging.INFO)
         | 
| 228 | 
            -
                    
         | 
| 229 | 
            -
                    return logger
         | 
| 230 | 
            -
                
         | 
| 231 | 
            -
                def _initialize_health_monitoring(self):
         | 
| 232 | 
            -
                    """Initialize health monitoring and recovery systems."""
         | 
| 233 | 
            -
                    try:
         | 
| 234 | 
            -
                        # Health monitoring configuration
         | 
| 235 | 
            -
                        health_config = {
         | 
| 236 | 
            -
                            'check_interval': 30,  # Check every 30 seconds
         | 
| 237 | 
            -
                            'history_size': 100,   # Keep 100 health check results
         | 
| 238 | 
            -
                            'aggregation_window': 300  # 5 minute aggregation window
         | 
| 239 | 
            -
                        }
         | 
| 240 | 
            -
                        
         | 
| 241 | 
            -
                        self.health_monitor = AdvancedHealthMonitor(health_config)
         | 
| 242 | 
            -
                        
         | 
| 243 | 
            -
                        # Add health checkers
         | 
| 244 | 
            -
                        
         | 
| 245 | 
            -
                        # Process resource monitoring
         | 
| 246 | 
            -
                        if PSUTIL_AVAILABLE:
         | 
| 247 | 
            -
                            process_checker = ProcessResourceChecker(
         | 
| 248 | 
            -
                                pid=self.pid,
         | 
| 249 | 
            -
                                cpu_threshold=80.0,      # 80% CPU threshold
         | 
| 250 | 
            -
                                memory_threshold_mb=500,  # 500MB memory threshold
         | 
| 251 | 
            -
                                fd_threshold=1000        # 1000 file descriptor threshold
         | 
| 252 | 
            -
                            )
         | 
| 253 | 
            -
                            self.health_monitor.add_checker(process_checker)
         | 
| 254 | 
            -
                        
         | 
| 255 | 
            -
                        # Network connectivity monitoring
         | 
| 256 | 
            -
                        network_checker = NetworkConnectivityChecker(
         | 
| 257 | 
            -
                            host=self.host,
         | 
| 258 | 
            -
                            port=self.port,
         | 
| 259 | 
            -
                            timeout=2.0
         | 
| 260 | 
            -
                        )
         | 
| 261 | 
            -
                        self.health_monitor.add_checker(network_checker)
         | 
| 262 | 
            -
                        
         | 
| 263 | 
            -
                        # Service health monitoring (will be initialized after server stats are available)
         | 
| 264 | 
            -
                        # This is added later in start_async after health_stats is fully initialized
         | 
| 265 | 
            -
                        
         | 
| 266 | 
            -
                        # Recovery manager configuration
         | 
| 267 | 
            -
                        recovery_config = {
         | 
| 268 | 
            -
                            'enabled': True,
         | 
| 269 | 
            -
                            'check_interval': 60,
         | 
| 270 | 
            -
                            'max_recovery_attempts': 5,
         | 
| 271 | 
            -
                            'recovery_timeout': 30,
         | 
| 272 | 
            -
                            'circuit_breaker': {
         | 
| 273 | 
            -
                                'failure_threshold': 5,
         | 
| 274 | 
            -
                                'timeout_seconds': 300,
         | 
| 275 | 
            -
                                'success_threshold': 3
         | 
| 276 | 
            -
                            },
         | 
| 277 | 
            -
                            'strategy': {
         | 
| 278 | 
            -
                                'warning_threshold': 2,
         | 
| 279 | 
            -
                                'critical_threshold': 1,
         | 
| 280 | 
            -
                                'failure_window_seconds': 300,
         | 
| 281 | 
            -
                                'min_recovery_interval': 60
         | 
| 282 | 
            -
                            }
         | 
| 283 | 
            -
                        }
         | 
| 284 | 
            -
                        
         | 
| 285 | 
            -
                        self.recovery_manager = RecoveryManager(recovery_config, self)
         | 
| 286 | 
            -
                        
         | 
| 287 | 
            -
                        # Link health monitor and recovery manager
         | 
| 288 | 
            -
                        self.health_monitor.add_health_callback(self._handle_health_result)
         | 
| 289 | 
            -
                        
         | 
| 290 | 
            -
                        self.logger.info("Health monitoring and recovery systems initialized")
         | 
| 291 | 
            -
                        
         | 
| 292 | 
            -
                    except Exception as e:
         | 
| 293 | 
            -
                        self.logger.error(f"Failed to initialize health monitoring: {e}")
         | 
| 294 | 
            -
                        self.health_monitor = None
         | 
| 295 | 
            -
                        self.recovery_manager = None
         | 
| 296 | 
            -
                
         | 
| 297 | 
            -
                def _handle_health_result(self, health_result: HealthCheckResult):
         | 
| 298 | 
            -
                    """Handle health check results and trigger recovery if needed."""
         | 
| 299 | 
            -
                    try:
         | 
| 300 | 
            -
                        if self.recovery_manager:
         | 
| 301 | 
            -
                            recovery_event = self.recovery_manager.handle_health_result(health_result)
         | 
| 302 | 
            -
                            if recovery_event:
         | 
| 303 | 
            -
                                self.logger.info(f"Recovery triggered: {recovery_event.action.value}")
         | 
| 304 | 
            -
                    except Exception as e:
         | 
| 305 | 
            -
                        self.logger.error(f"Error handling health result: {e}")
         | 
| 306 | 
            -
                        
         | 
| 307 | 
            -
                        # Enhanced error reporting for health check failures
         | 
| 308 | 
            -
                        if ENHANCED_ERRORS_AVAILABLE:
         | 
| 309 | 
            -
                            if hasattr(health_result, 'status') and health_result.status in ['critical', 'failed']:
         | 
| 310 | 
            -
                                health_error = HealthCheckError(
         | 
| 311 | 
            -
                                    check_name=getattr(health_result, 'check_name', 'unknown'),
         | 
| 312 | 
            -
                                    check_status=getattr(health_result, 'status', 'failed'),
         | 
| 313 | 
            -
                                    check_details=getattr(health_result, 'details', {})
         | 
| 314 | 
            -
                                )
         | 
| 315 | 
            -
                                self.logger.error(f"\nHealth Check Failure Details:\n{health_error}")
         | 
| 316 | 
            -
                
         | 
| 317 | 
            -
                def _get_pidfile_path(self) -> Path:
         | 
| 318 | 
            -
                    """Get path for PID file to track running server."""
         | 
| 319 | 
            -
                    # Use system temp directory or user home
         | 
| 320 | 
            -
                    if os.name == 'nt':  # Windows
         | 
| 321 | 
            -
                        temp_dir = Path(os.environ.get('TEMP', os.path.expanduser('~')))
         | 
| 322 | 
            -
                    else:  # Unix-like
         | 
| 323 | 
            -
                        temp_dir = Path('/tmp') if Path('/tmp').exists() else Path.home()
         | 
| 324 | 
            -
                    
         | 
| 325 | 
            -
                    return temp_dir / f"claude_mpm_socketio_{self.port}.pid"
         | 
| 326 | 
            -
                
         | 
| 327 | 
            -
                def check_compatibility(self, client_version: str) -> Dict[str, Any]:
         | 
| 328 | 
            -
                    """Check if client version is compatible with this server version.
         | 
| 329 | 
            -
                    
         | 
| 330 | 
            -
                    Returns compatibility info including warnings and supported features.
         | 
| 331 | 
            -
                    """
         | 
| 332 | 
            -
                    server_compat = COMPATIBILITY_MATRIX.get(self.server_version, {})
         | 
| 333 | 
            -
                    
         | 
| 334 | 
            -
                    result = {
         | 
| 335 | 
            -
                        "compatible": False,
         | 
| 336 | 
            -
                        "server_version": self.server_version,
         | 
| 337 | 
            -
                        "client_version": client_version,
         | 
| 338 | 
            -
                        "warnings": [],
         | 
| 339 | 
            -
                        "supported_features": server_compat.get("features", []),
         | 
| 340 | 
            -
                        "requirements": {
         | 
| 341 | 
            -
                            "min_python": server_compat.get("min_python", "3.8"),
         | 
| 342 | 
            -
                            "socketio_min": server_compat.get("socketio_min", "5.11.0")
         | 
| 343 | 
            -
                        }
         | 
| 344 | 
            -
                    }
         | 
| 345 | 
            -
                    
         | 
| 346 | 
            -
                    # Simple version compatibility check
         | 
| 347 | 
            -
                    # In production, you'd use proper semantic versioning
         | 
| 348 | 
            -
                    try:
         | 
| 349 | 
            -
                        if client_version >= "0.7.0":  # Minimum supported
         | 
| 350 | 
            -
                            result["compatible"] = True
         | 
| 351 | 
            -
                        else:
         | 
| 352 | 
            -
                            result["warnings"].append(f"Client version {client_version} may not be fully supported")
         | 
| 353 | 
            -
                            result["compatible"] = False
         | 
| 354 | 
            -
                    except Exception as e:
         | 
| 355 | 
            -
                        result["warnings"].append(f"Could not parse client version: {e}")
         | 
| 356 | 
            -
                        result["compatible"] = False
         | 
| 357 | 
            -
                    
         | 
| 358 | 
            -
                    return result
         | 
| 359 | 
            -
                
         | 
| 360 | 
            -
                def _validate_process_identity(self, pid: int, expected_cmdline_patterns: List[str] = None) -> Dict[str, Any]:
         | 
| 361 | 
            -
                    """Validate that a process is actually our Socket.IO server.
         | 
| 362 | 
            -
                    
         | 
| 363 | 
            -
                    Args:
         | 
| 364 | 
            -
                        pid: Process ID to validate
         | 
| 365 | 
            -
                        expected_cmdline_patterns: Command line patterns that should match our server
         | 
| 366 | 
            -
                        
         | 
| 367 | 
            -
                    Returns:
         | 
| 368 | 
            -
                        Dict with validation results and process info
         | 
| 369 | 
            -
                    """
         | 
| 370 | 
            -
                    validation_result = {
         | 
| 371 | 
            -
                        "is_valid": False,
         | 
| 372 | 
            -
                        "is_zombie": False,
         | 
| 373 | 
            -
                        "is_our_server": False,
         | 
| 374 | 
            -
                        "process_info": {},
         | 
| 375 | 
            -
                        "validation_errors": []
         | 
| 376 | 
            -
                    }
         | 
| 377 | 
            -
                    
         | 
| 378 | 
            -
                    if not PSUTIL_AVAILABLE:
         | 
| 379 | 
            -
                        validation_result["validation_errors"].append("psutil not available for enhanced validation")
         | 
| 380 | 
            -
                        # Fallback to basic process existence check
         | 
| 381 | 
            -
                        try:
         | 
| 382 | 
            -
                            os.kill(pid, 0)
         | 
| 383 | 
            -
                            validation_result["is_valid"] = True
         | 
| 384 | 
            -
                            validation_result["process_info"] = {"pid": pid, "method": "basic_os_check"}
         | 
| 385 | 
            -
                        except OSError:
         | 
| 386 | 
            -
                            validation_result["validation_errors"].append(f"Process {pid} does not exist")
         | 
| 387 | 
            -
                        return validation_result
         | 
| 388 | 
            -
                    
         | 
| 389 | 
            -
                    try:
         | 
| 390 | 
            -
                        process = psutil.Process(pid)
         | 
| 391 | 
            -
                        
         | 
| 392 | 
            -
                        # Basic process info
         | 
| 393 | 
            -
                        process_info = {
         | 
| 394 | 
            -
                            "pid": pid,
         | 
| 395 | 
            -
                            "status": process.status(),
         | 
| 396 | 
            -
                            "create_time": process.create_time(),
         | 
| 397 | 
            -
                            "name": process.name(),
         | 
| 398 | 
            -
                            "cwd": None,
         | 
| 399 | 
            -
                            "cmdline": [],
         | 
| 400 | 
            -
                            "memory_info": None
         | 
| 401 | 
            -
                        }
         | 
| 402 | 
            -
                        
         | 
| 403 | 
            -
                        # Check if process is zombie
         | 
| 404 | 
            -
                        if process.status() == psutil.STATUS_ZOMBIE:
         | 
| 405 | 
            -
                            validation_result["is_zombie"] = True
         | 
| 406 | 
            -
                            validation_result["validation_errors"].append(f"Process {pid} is a zombie")
         | 
| 407 | 
            -
                            validation_result["process_info"] = process_info
         | 
| 408 | 
            -
                            return validation_result
         | 
| 409 | 
            -
                        
         | 
| 410 | 
            -
                        # Get additional process details
         | 
| 411 | 
            -
                        try:
         | 
| 412 | 
            -
                            process_info["cwd"] = process.cwd()
         | 
| 413 | 
            -
                            process_info["cmdline"] = process.cmdline()
         | 
| 414 | 
            -
                            process_info["memory_info"] = process.memory_info()._asdict()
         | 
| 415 | 
            -
                        except (psutil.AccessDenied, psutil.NoSuchProcess) as e:
         | 
| 416 | 
            -
                            validation_result["validation_errors"].append(f"Access denied getting process details: {e}")
         | 
| 417 | 
            -
                        
         | 
| 418 | 
            -
                        validation_result["process_info"] = process_info
         | 
| 419 | 
            -
                        validation_result["is_valid"] = True
         | 
| 420 | 
            -
                        
         | 
| 421 | 
            -
                        # Validate this is likely our server process
         | 
| 422 | 
            -
                        cmdline = process_info.get("cmdline", [])
         | 
| 423 | 
            -
                        cmdline_str = " ".join(cmdline).lower()
         | 
| 424 | 
            -
                        
         | 
| 425 | 
            -
                        # Default patterns for our Socket.IO server
         | 
| 426 | 
            -
                        if expected_cmdline_patterns is None:
         | 
| 427 | 
            -
                            expected_cmdline_patterns = [
         | 
| 428 | 
            -
                                "socketio",
         | 
| 429 | 
            -
                                "standalone_socketio_server",
         | 
| 430 | 
            -
                                "claude-mpm",
         | 
| 431 | 
            -
                                str(self.port)
         | 
| 432 | 
            -
                            ]
         | 
| 433 | 
            -
                        
         | 
| 434 | 
            -
                        # Check if any patterns match the command line
         | 
| 435 | 
            -
                        matches = [pattern.lower() in cmdline_str for pattern in expected_cmdline_patterns]
         | 
| 436 | 
            -
                        if any(matches):
         | 
| 437 | 
            -
                            validation_result["is_our_server"] = True
         | 
| 438 | 
            -
                            self.logger.debug(f"Process {pid} matches server patterns: {[p for p, m in zip(expected_cmdline_patterns, matches) if m]}")
         | 
| 439 | 
            -
                        else:
         | 
| 440 | 
            -
                            validation_result["validation_errors"].append(
         | 
| 441 | 
            -
                                f"Process {pid} command line '{cmdline_str}' does not match expected patterns: {expected_cmdline_patterns}"
         | 
| 442 | 
            -
                            )
         | 
| 443 | 
            -
                            self.logger.warning(f"Process {pid} does not appear to be our server: {cmdline}")
         | 
| 444 | 
            -
                        
         | 
| 445 | 
            -
                    except psutil.NoSuchProcess:
         | 
| 446 | 
            -
                        validation_result["validation_errors"].append(f"Process {pid} no longer exists")
         | 
| 447 | 
            -
                    except psutil.AccessDenied as e:
         | 
| 448 | 
            -
                        validation_result["validation_errors"].append(f"Access denied to process {pid}: {e}")
         | 
| 449 | 
            -
                    except Exception as e:
         | 
| 450 | 
            -
                        validation_result["validation_errors"].append(f"Error validating process {pid}: {e}")
         | 
| 451 | 
            -
                    
         | 
| 452 | 
            -
                    return validation_result
         | 
| 453 | 
            -
                
         | 
| 454 | 
            -
                def _acquire_pidfile_lock(self, pidfile_fd) -> bool:
         | 
| 455 | 
            -
                    """Acquire exclusive lock on PID file.
         | 
| 456 | 
            -
                    
         | 
| 457 | 
            -
                    Args:
         | 
| 458 | 
            -
                        pidfile_fd: Open file descriptor for PID file
         | 
| 459 | 
            -
                        
         | 
| 460 | 
            -
                    Returns:
         | 
| 461 | 
            -
                        True if lock acquired successfully, False otherwise
         | 
| 462 | 
            -
                    """
         | 
| 463 | 
            -
                    try:
         | 
| 464 | 
            -
                        if platform.system() == 'Windows' and WINDOWS_LOCKING:
         | 
| 465 | 
            -
                            # Windows file locking
         | 
| 466 | 
            -
                            msvcrt.locking(pidfile_fd.fileno(), msvcrt.LK_NBLCK, 1)
         | 
| 467 | 
            -
                            return True
         | 
| 468 | 
            -
                        else:
         | 
| 469 | 
            -
                            # Unix file locking
         | 
| 470 | 
            -
                            fcntl.flock(pidfile_fd.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
         | 
| 471 | 
            -
                            return True
         | 
| 472 | 
            -
                    except (IOError, OSError) as e:
         | 
| 473 | 
            -
                        self.logger.debug(f"Could not acquire PID file lock: {e}")
         | 
| 474 | 
            -
                        return False
         | 
| 475 | 
            -
                
         | 
| 476 | 
            -
                def _release_pidfile_lock(self, pidfile_fd):
         | 
| 477 | 
            -
                    """Release lock on PID file.
         | 
| 478 | 
            -
                    
         | 
| 479 | 
            -
                    Args:
         | 
| 480 | 
            -
                        pidfile_fd: Open file descriptor for PID file
         | 
| 481 | 
            -
                    """
         | 
| 482 | 
            -
                    try:
         | 
| 483 | 
            -
                        if platform.system() == 'Windows' and WINDOWS_LOCKING:
         | 
| 484 | 
            -
                            msvcrt.locking(pidfile_fd.fileno(), msvcrt.LK_UNLCK, 1)
         | 
| 485 | 
            -
                        else:
         | 
| 486 | 
            -
                            fcntl.flock(pidfile_fd.fileno(), fcntl.LOCK_UN)
         | 
| 487 | 
            -
                    except (IOError, OSError) as e:
         | 
| 488 | 
            -
                        self.logger.debug(f"Error releasing PID file lock: {e}")
         | 
| 489 | 
            -
                
         | 
| 490 | 
            -
                def _validate_pidfile_timestamp(self, pidfile_path: Path, process_start_time: float) -> bool:
         | 
| 491 | 
            -
                    """Validate that PID file was created around the same time as the process.
         | 
| 492 | 
            -
                    
         | 
| 493 | 
            -
                    Args:
         | 
| 494 | 
            -
                        pidfile_path: Path to PID file
         | 
| 495 | 
            -
                        process_start_time: Process start time from psutil
         | 
| 496 | 
            -
                        
         | 
| 497 | 
            -
                    Returns:
         | 
| 498 | 
            -
                        True if timestamps are reasonably close, False otherwise
         | 
| 499 | 
            -
                    """
         | 
| 500 | 
            -
                    try:
         | 
| 501 | 
            -
                        pidfile_mtime = pidfile_path.stat().st_mtime
         | 
| 502 | 
            -
                        time_diff = abs(pidfile_mtime - process_start_time)
         | 
| 503 | 
            -
                        
         | 
| 504 | 
            -
                        # Allow up to 5 seconds difference (process start vs file creation)
         | 
| 505 | 
            -
                        if time_diff <= 5.0:
         | 
| 506 | 
            -
                            return True
         | 
| 507 | 
            -
                        else:
         | 
| 508 | 
            -
                            self.logger.warning(
         | 
| 509 | 
            -
                                f"PID file timestamp ({pidfile_mtime}) and process start time ({process_start_time}) "
         | 
| 510 | 
            -
                                f"differ by {time_diff:.2f} seconds"
         | 
| 511 | 
            -
                            )
         | 
| 512 | 
            -
                            return False
         | 
| 513 | 
            -
                    except Exception as e:
         | 
| 514 | 
            -
                        self.logger.warning(f"Could not validate PID file timestamp: {e}")
         | 
| 515 | 
            -
                        return False
         | 
| 516 | 
            -
                
         | 
| 517 | 
            -
                def is_already_running(self, raise_on_conflict: bool = False) -> bool:
         | 
| 518 | 
            -
                    """Enhanced check if another server instance is already running on this port.
         | 
| 519 | 
            -
                    
         | 
| 520 | 
            -
                    This method performs comprehensive validation including:
         | 
| 521 | 
            -
                    - PID file existence and validity
         | 
| 522 | 
            -
                    - Process identity verification (command line, start time)
         | 
| 523 | 
            -
                    - Zombie process detection
         | 
| 524 | 
            -
                    - Port availability check
         | 
| 525 | 
            -
                    - Automatic cleanup of stale PID files
         | 
| 526 | 
            -
                    
         | 
| 527 | 
            -
                    Returns:
         | 
| 528 | 
            -
                        True if a valid server is already running, False otherwise
         | 
| 529 | 
            -
                    """
         | 
| 530 | 
            -
                    self.logger.debug(f"Checking if server is already running on {self.host}:{self.port}")
         | 
| 531 | 
            -
                    
         | 
| 532 | 
            -
                    try:
         | 
| 533 | 
            -
                        # Step 1: Check PID file existence
         | 
| 534 | 
            -
                        if not self.pidfile_path.exists():
         | 
| 535 | 
            -
                            self.logger.debug("No PID file found")
         | 
| 536 | 
            -
                            return self._check_port_only(raise_on_conflict)
         | 
| 537 | 
            -
                        
         | 
| 538 | 
            -
                        self.logger.debug(f"Found PID file: {self.pidfile_path}")
         | 
| 539 | 
            -
                        
         | 
| 540 | 
            -
                        # Step 2: Read PID from file with support for both JSON and legacy formats
         | 
| 541 | 
            -
                        try:
         | 
| 542 | 
            -
                            with open(self.pidfile_path, 'r') as f:
         | 
| 543 | 
            -
                                pid_content = f.read().strip()
         | 
| 544 | 
            -
                                
         | 
| 545 | 
            -
                                if not pid_content:
         | 
| 546 | 
            -
                                    self.logger.warning("Empty PID file")
         | 
| 547 | 
            -
                                    self._cleanup_stale_pidfile("empty_file")
         | 
| 548 | 
            -
                                    return self._check_port_only(raise_on_conflict)
         | 
| 549 | 
            -
                                
         | 
| 550 | 
            -
                                # Try JSON format first (new format)
         | 
| 551 | 
            -
                                try:
         | 
| 552 | 
            -
                                    pidfile_data = json.loads(pid_content)
         | 
| 553 | 
            -
                                    old_pid = pidfile_data["pid"]
         | 
| 554 | 
            -
                                    server_id = pidfile_data.get("server_id", "unknown")
         | 
| 555 | 
            -
                                    self.logger.debug(f"Found PID {old_pid} for server {server_id} in JSON format")
         | 
| 556 | 
            -
                                except (json.JSONDecodeError, KeyError, TypeError):
         | 
| 557 | 
            -
                                    # Fallback to legacy format (plain PID number)
         | 
| 558 | 
            -
                                    if pid_content.isdigit():
         | 
| 559 | 
            -
                                        old_pid = int(pid_content)
         | 
| 560 | 
            -
                                        self.logger.debug(f"Found PID {old_pid} in legacy format")
         | 
| 561 | 
            -
                                    else:
         | 
| 562 | 
            -
                                        self.logger.warning(f"Invalid PID content in file: '{pid_content[:100]}...' (truncated)")
         | 
| 563 | 
            -
                                        self._cleanup_stale_pidfile("invalid_content")
         | 
| 564 | 
            -
                                        return self._check_port_only(raise_on_conflict)
         | 
| 565 | 
            -
                                
         | 
| 566 | 
            -
                        except (IOError, ValueError) as e:
         | 
| 567 | 
            -
                            self.logger.warning(f"Could not read PID file: {e}")
         | 
| 568 | 
            -
                            self._cleanup_stale_pidfile("read_error")
         | 
| 569 | 
            -
                            return self._check_port_only(raise_on_conflict)
         | 
| 570 | 
            -
                        
         | 
| 571 | 
            -
                        # Step 3: Enhanced process validation
         | 
| 572 | 
            -
                        validation = self._validate_process_identity(old_pid)
         | 
| 573 | 
            -
                        
         | 
| 574 | 
            -
                        if not validation["is_valid"]:
         | 
| 575 | 
            -
                            self.logger.info(f"Process {old_pid} is not valid: {validation['validation_errors']}")
         | 
| 576 | 
            -
                            if raise_on_conflict and ENHANCED_ERRORS_AVAILABLE:
         | 
| 577 | 
            -
                                raise StaleProcessError(
         | 
| 578 | 
            -
                                    pid=old_pid,
         | 
| 579 | 
            -
                                    pidfile_path=self.pidfile_path,
         | 
| 580 | 
            -
                                    process_status="not_found",
         | 
| 581 | 
            -
                                    validation_errors=validation['validation_errors']
         | 
| 582 | 
            -
                                )
         | 
| 583 | 
            -
                            self._cleanup_stale_pidfile("invalid_process")
         | 
| 584 | 
            -
                            return self._check_port_only(raise_on_conflict)
         | 
| 585 | 
            -
                        
         | 
| 586 | 
            -
                        if validation["is_zombie"]:
         | 
| 587 | 
            -
                            self.logger.info(f"Process {old_pid} is a zombie, cleaning up")
         | 
| 588 | 
            -
                            if raise_on_conflict and ENHANCED_ERRORS_AVAILABLE:
         | 
| 589 | 
            -
                                raise StaleProcessError(
         | 
| 590 | 
            -
                                    pid=old_pid,
         | 
| 591 | 
            -
                                    pidfile_path=self.pidfile_path,
         | 
| 592 | 
            -
                                    process_status="zombie",
         | 
| 593 | 
            -
                                    validation_errors=["Process is a zombie (terminated but not reaped)"]
         | 
| 594 | 
            -
                                )
         | 
| 595 | 
            -
                            self._cleanup_stale_pidfile("zombie_process")
         | 
| 596 | 
            -
                            return self._check_port_only(raise_on_conflict)
         | 
| 597 | 
            -
                        
         | 
| 598 | 
            -
                        # Step 4: Verify this is actually our server process
         | 
| 599 | 
            -
                        if not validation["is_our_server"]:
         | 
| 600 | 
            -
                            self.logger.warning(
         | 
| 601 | 
            -
                                f"Process {old_pid} exists but does not appear to be our Socket.IO server. "
         | 
| 602 | 
            -
                                f"Command line: {validation['process_info'].get('cmdline', 'unknown')}"
         | 
| 603 | 
            -
                            )
         | 
| 604 | 
            -
                            # Don't automatically clean up - might be another legitimate process
         | 
| 605 | 
            -
                            return self._check_port_only(raise_on_conflict)
         | 
| 606 | 
            -
                        
         | 
| 607 | 
            -
                        # Step 5: Validate process start time against PID file timestamp
         | 
| 608 | 
            -
                        if PSUTIL_AVAILABLE and 'create_time' in validation['process_info']:
         | 
| 609 | 
            -
                            process_start_time = validation['process_info']['create_time']
         | 
| 610 | 
            -
                            if not self._validate_pidfile_timestamp(self.pidfile_path, process_start_time):
         | 
| 611 | 
            -
                                self.logger.warning("PID file timestamp does not match process start time")
         | 
| 612 | 
            -
                                # Continue anyway - timestamp validation is not critical
         | 
| 613 | 
            -
                        
         | 
| 614 | 
            -
                        # Step 6: All validations passed
         | 
| 615 | 
            -
                        process_info = validation['process_info']
         | 
| 616 | 
            -
                        self.logger.info(
         | 
| 617 | 
            -
                            f"Found valid running server: PID {old_pid}, "
         | 
| 618 | 
            -
                            f"status: {process_info.get('status', 'unknown')}, "
         | 
| 619 | 
            -
                            f"name: {process_info.get('name', 'unknown')}"
         | 
| 620 | 
            -
                        )
         | 
| 621 | 
            -
                        
         | 
| 622 | 
            -
                        if raise_on_conflict and ENHANCED_ERRORS_AVAILABLE:
         | 
| 623 | 
            -
                            # Try to extract server ID from PID file if available
         | 
| 624 | 
            -
                            server_id = "unknown"
         | 
| 625 | 
            -
                            try:
         | 
| 626 | 
            -
                                with open(self.pidfile_path, 'r') as f:
         | 
| 627 | 
            -
                                    content = f.read().strip()
         | 
| 628 | 
            -
                                    if content.startswith('{'):
         | 
| 629 | 
            -
                                        pidfile_data = json.loads(content)
         | 
| 630 | 
            -
                                        server_id = pidfile_data.get("server_id", "unknown")
         | 
| 631 | 
            -
                            except:
         | 
| 632 | 
            -
                                pass
         | 
| 633 | 
            -
                            
         | 
| 634 | 
            -
                            raise DaemonConflictError(
         | 
| 635 | 
            -
                                port=self.port,
         | 
| 636 | 
            -
                                existing_pid=old_pid,
         | 
| 637 | 
            -
                                existing_server_id=server_id,
         | 
| 638 | 
            -
                                process_info=process_info,
         | 
| 639 | 
            -
                                pidfile_path=self.pidfile_path
         | 
| 640 | 
            -
                            )
         | 
| 641 | 
            -
                        
         | 
| 642 | 
            -
                        return True
         | 
| 643 | 
            -
                        
         | 
| 644 | 
            -
                    except (DaemonConflictError, StaleProcessError, PortConflictError) as e:
         | 
| 645 | 
            -
                        # Re-raise our enhanced errors instead of catching them
         | 
| 646 | 
            -
                        raise
         | 
| 647 | 
            -
                    except Exception as e:
         | 
| 648 | 
            -
                        self.logger.error(f"Error during enhanced server check: {e}")
         | 
| 649 | 
            -
                        # Fallback to basic port check on unexpected errors
         | 
| 650 | 
            -
                        return self._check_port_only(raise_on_conflict)
         | 
| 651 | 
            -
                
         | 
| 652 | 
            -
                def _check_port_only(self, raise_on_conflict: bool = False) -> bool:
         | 
| 653 | 
            -
                    """Fallback method to check if port is in use.
         | 
| 654 | 
            -
                    
         | 
| 655 | 
            -
                    Args:
         | 
| 656 | 
            -
                        raise_on_conflict: If True, raises PortConflictError instead of returning True
         | 
| 657 | 
            -
                    
         | 
| 658 | 
            -
                    Returns:
         | 
| 659 | 
            -
                        True if port is in use, False otherwise
         | 
| 660 | 
            -
                        
         | 
| 661 | 
            -
                    Raises:
         | 
| 662 | 
            -
                        PortConflictError: If raise_on_conflict=True and port is in use
         | 
| 663 | 
            -
                    """
         | 
| 664 | 
            -
                    try:
         | 
| 665 | 
            -
                        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
         | 
| 666 | 
            -
                            s.settimeout(1.0)
         | 
| 667 | 
            -
                            result = s.connect_ex((self.host, self.port))
         | 
| 668 | 
            -
                            if result == 0:
         | 
| 669 | 
            -
                                self.logger.info(f"Port {self.port} is in use by some process")
         | 
| 670 | 
            -
                                
         | 
| 671 | 
            -
                                if raise_on_conflict and ENHANCED_ERRORS_AVAILABLE:
         | 
| 672 | 
            -
                                    # Try to identify the conflicting process if psutil is available
         | 
| 673 | 
            -
                                    conflicting_process = {}
         | 
| 674 | 
            -
                                    if PSUTIL_AVAILABLE:
         | 
| 675 | 
            -
                                        try:
         | 
| 676 | 
            -
                                            import psutil
         | 
| 677 | 
            -
                                            for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
         | 
| 678 | 
            -
                                                try:
         | 
| 679 | 
            -
                                                    for conn in proc.connections():
         | 
| 680 | 
            -
                                                        if (conn.laddr.ip == self.host or conn.laddr.ip == '0.0.0.0') and conn.laddr.port == self.port:
         | 
| 681 | 
            -
                                                            conflicting_process = {
         | 
| 682 | 
            -
                                                                'pid': proc.info['pid'],
         | 
| 683 | 
            -
                                                                'name': proc.info['name'],
         | 
| 684 | 
            -
                                                                'cmdline': proc.info['cmdline']
         | 
| 685 | 
            -
                                                            }
         | 
| 686 | 
            -
                                                            break
         | 
| 687 | 
            -
                                                except (psutil.NoSuchProcess, psutil.AccessDenied):
         | 
| 688 | 
            -
                                                    continue
         | 
| 689 | 
            -
                                                if conflicting_process:
         | 
| 690 | 
            -
                                                    break
         | 
| 691 | 
            -
                                        except Exception:
         | 
| 692 | 
            -
                                            pass  # Ignore errors in process discovery
         | 
| 693 | 
            -
                                    
         | 
| 694 | 
            -
                                    raise PortConflictError(
         | 
| 695 | 
            -
                                        port=self.port,
         | 
| 696 | 
            -
                                        host=self.host,
         | 
| 697 | 
            -
                                        conflicting_process=conflicting_process
         | 
| 698 | 
            -
                                    )
         | 
| 699 | 
            -
                                
         | 
| 700 | 
            -
                                return True
         | 
| 701 | 
            -
                    except Exception as e:
         | 
| 702 | 
            -
                        if not isinstance(e, PortConflictError):  # Don't mask our own exceptions
         | 
| 703 | 
            -
                            self.logger.debug(f"Error checking port availability: {e}")
         | 
| 704 | 
            -
                    
         | 
| 705 | 
            -
                    return False
         | 
| 706 | 
            -
                
         | 
| 707 | 
            -
                def _cleanup_stale_pidfile(self, reason: str):
         | 
| 708 | 
            -
                    """Clean up stale PID file with logging.
         | 
| 709 | 
            -
                    
         | 
| 710 | 
            -
                    Args:
         | 
| 711 | 
            -
                        reason: Reason for cleanup (for logging)
         | 
| 712 | 
            -
                    """
         | 
| 713 | 
            -
                    try:
         | 
| 714 | 
            -
                        if self.pidfile_path.exists():
         | 
| 715 | 
            -
                            self.pidfile_path.unlink()
         | 
| 716 | 
            -
                            self.logger.info(f"Cleaned up stale PID file (reason: {reason}): {self.pidfile_path}")
         | 
| 717 | 
            -
                    except Exception as e:
         | 
| 718 | 
            -
                        self.logger.error(f"Failed to clean up stale PID file: {e}")
         | 
| 719 | 
            -
                
         | 
| 720 | 
            -
                def create_pidfile(self):
         | 
| 721 | 
            -
                    """Create PID file with exclusive locking to track this server instance.
         | 
| 722 | 
            -
                    
         | 
| 723 | 
            -
                    This method creates a PID file with exclusive locking to prevent race conditions
         | 
| 724 | 
            -
                    and ensures only one server instance can hold the lock at a time.
         | 
| 725 | 
            -
                    """
         | 
| 726 | 
            -
                    try:
         | 
| 727 | 
            -
                        self.pidfile_path.parent.mkdir(parents=True, exist_ok=True)
         | 
| 728 | 
            -
                        
         | 
| 729 | 
            -
                        # Open file for writing with exclusive creation
         | 
| 730 | 
            -
                        pidfile_fd = open(self.pidfile_path, 'w')
         | 
| 731 | 
            -
                        
         | 
| 732 | 
            -
                        # Try to acquire exclusive lock
         | 
| 733 | 
            -
                        if not self._acquire_pidfile_lock(pidfile_fd):
         | 
| 734 | 
            -
                            pidfile_fd.close()
         | 
| 735 | 
            -
                            if ENHANCED_ERRORS_AVAILABLE:
         | 
| 736 | 
            -
                                raise DaemonConflictError(
         | 
| 737 | 
            -
                                    port=self.port,
         | 
| 738 | 
            -
                                    existing_pid=0,  # Unknown PID since we can't get lock
         | 
| 739 | 
            -
                                    existing_server_id="unknown",
         | 
| 740 | 
            -
                                    pidfile_path=self.pidfile_path
         | 
| 741 | 
            -
                                )
         | 
| 742 | 
            -
                            else:
         | 
| 743 | 
            -
                                raise RuntimeError("Could not acquire exclusive lock on PID file")
         | 
| 744 | 
            -
                        
         | 
| 745 | 
            -
                        # Write PID and additional metadata
         | 
| 746 | 
            -
                        pidfile_content = {
         | 
| 747 | 
            -
                            "pid": self.pid,
         | 
| 748 | 
            -
                            "server_id": self.server_id,
         | 
| 749 | 
            -
                            "server_version": self.server_version,
         | 
| 750 | 
            -
                            "port": self.port,
         | 
| 751 | 
            -
                            "host": self.host,
         | 
| 752 | 
            -
                            "start_time": self.start_time.isoformat() + "Z",
         | 
| 753 | 
            -
                            "process_start_time": self.process_start_time if self.process_start_time else None,
         | 
| 754 | 
            -
                            "python_version": sys.version.split()[0],
         | 
| 755 | 
            -
                            "platform": platform.system(),
         | 
| 756 | 
            -
                            "created_at": datetime.utcnow().isoformat() + "Z"
         | 
| 757 | 
            -
                        }
         | 
| 758 | 
            -
                        
         | 
| 759 | 
            -
                        # Write JSON format for better validation
         | 
| 760 | 
            -
                        pidfile_fd.write(json.dumps(pidfile_content, indent=2))
         | 
| 761 | 
            -
                        pidfile_fd.flush()
         | 
| 762 | 
            -
                        
         | 
| 763 | 
            -
                        # Keep file descriptor open to maintain lock
         | 
| 764 | 
            -
                        self.pidfile_lock = pidfile_fd
         | 
| 765 | 
            -
                        
         | 
| 766 | 
            -
                        self.logger.info(f"Created PID file with exclusive lock: {self.pidfile_path}")
         | 
| 767 | 
            -
                        self.logger.debug(f"PID file content: {pidfile_content}")
         | 
| 768 | 
            -
                        
         | 
| 769 | 
            -
                    except Exception as e:
         | 
| 770 | 
            -
                        self.logger.error(f"Failed to create PID file: {e}")
         | 
| 771 | 
            -
                        if 'pidfile_fd' in locals():
         | 
| 772 | 
            -
                            try:
         | 
| 773 | 
            -
                                pidfile_fd.close()
         | 
| 774 | 
            -
                            except:
         | 
| 775 | 
            -
                                pass
         | 
| 776 | 
            -
                        raise
         | 
| 777 | 
            -
                
         | 
| 778 | 
            -
                def remove_pidfile(self):
         | 
| 779 | 
            -
                    """Remove PID file and release lock on shutdown."""
         | 
| 780 | 
            -
                    try:
         | 
| 781 | 
            -
                        # Release file lock first
         | 
| 782 | 
            -
                        if self.pidfile_lock:
         | 
| 783 | 
            -
                            try:
         | 
| 784 | 
            -
                                self._release_pidfile_lock(self.pidfile_lock)
         | 
| 785 | 
            -
                                self.pidfile_lock.close()
         | 
| 786 | 
            -
                                self.pidfile_lock = None
         | 
| 787 | 
            -
                                self.logger.debug("Released PID file lock")
         | 
| 788 | 
            -
                            except Exception as e:
         | 
| 789 | 
            -
                                self.logger.warning(f"Error releasing PID file lock: {e}")
         | 
| 790 | 
            -
                        
         | 
| 791 | 
            -
                        # Remove PID file
         | 
| 792 | 
            -
                        if self.pidfile_path.exists():
         | 
| 793 | 
            -
                            self.pidfile_path.unlink()
         | 
| 794 | 
            -
                            self.logger.info(f"Removed PID file: {self.pidfile_path}")
         | 
| 795 | 
            -
                            
         | 
| 796 | 
            -
                    except Exception as e:
         | 
| 797 | 
            -
                        self.logger.error(f"Failed to remove PID file: {e}")
         | 
| 798 | 
            -
                
         | 
| 799 | 
            -
                def setup_signal_handlers(self):
         | 
| 800 | 
            -
                    """Setup signal handlers for graceful shutdown."""
         | 
| 801 | 
            -
                    def signal_handler(signum, frame):
         | 
| 802 | 
            -
                        self.logger.info(f"Received signal {signum}, initiating shutdown...")
         | 
| 803 | 
            -
                        self.stop()
         | 
| 804 | 
            -
                        sys.exit(0)
         | 
| 805 | 
            -
                    
         | 
| 806 | 
            -
                    signal.signal(signal.SIGINT, signal_handler)
         | 
| 807 | 
            -
                    signal.signal(signal.SIGTERM, signal_handler)
         | 
| 808 | 
            -
                    
         | 
| 809 | 
            -
                    if hasattr(signal, 'SIGHUP'):
         | 
| 810 | 
            -
                        signal.signal(signal.SIGHUP, signal_handler)
         | 
| 811 | 
            -
                
         | 
| 812 | 
            -
                async def start_async(self):
         | 
| 813 | 
            -
                    """Start the server asynchronously."""
         | 
| 814 | 
            -
                    if not SOCKETIO_AVAILABLE:
         | 
| 815 | 
            -
                        error_msg = "Socket.IO dependencies not available. Install with: pip install python-socketio aiohttp"
         | 
| 816 | 
            -
                        if ENHANCED_ERRORS_AVAILABLE:
         | 
| 817 | 
            -
                            raise RuntimeError(error_msg + "\n\nInstallation steps:\n  1. pip install python-socketio aiohttp\n  2. Restart the server\n  3. Verify installation: python -c 'import socketio; print(socketio.__version__)'")
         | 
| 818 | 
            -
                        else:
         | 
| 819 | 
            -
                            raise RuntimeError(error_msg)
         | 
| 820 | 
            -
                    
         | 
| 821 | 
            -
                    self.logger.info(f"Starting standalone Socket.IO server v{self.server_version}")
         | 
| 822 | 
            -
                    
         | 
| 823 | 
            -
                    # Create Socket.IO server with production settings
         | 
| 824 | 
            -
                    self.sio = socketio.AsyncServer(
         | 
| 825 | 
            -
                        cors_allowed_origins="*",  # Configure appropriately for production
         | 
| 826 | 
            -
                        async_mode='aiohttp',
         | 
| 827 | 
            -
                        ping_timeout=60,
         | 
| 828 | 
            -
                        ping_interval=25,
         | 
| 829 | 
            -
                        max_http_buffer_size=1000000,
         | 
| 830 | 
            -
                        logger=False,  # Use our own logger
         | 
| 831 | 
            -
                        engineio_logger=False
         | 
| 832 | 
            -
                    )
         | 
| 833 | 
            -
                    
         | 
| 834 | 
            -
                    # Create aiohttp application
         | 
| 835 | 
            -
                    self.app = web.Application()
         | 
| 836 | 
            -
                    self.sio.attach(self.app)
         | 
| 837 | 
            -
                    
         | 
| 838 | 
            -
                    # Setup routes and event handlers
         | 
| 839 | 
            -
                    self._setup_routes()
         | 
| 840 | 
            -
                    self._setup_event_handlers()
         | 
| 841 | 
            -
                    
         | 
| 842 | 
            -
                    # Start the server
         | 
| 843 | 
            -
                    try:
         | 
| 844 | 
            -
                        self.runner = web.AppRunner(self.app)
         | 
| 845 | 
            -
                        await self.runner.setup()
         | 
| 846 | 
            -
                        
         | 
| 847 | 
            -
                        self.site = web.TCPSite(self.runner, self.host, self.port)
         | 
| 848 | 
            -
                        await self.site.start()
         | 
| 849 | 
            -
                        
         | 
| 850 | 
            -
                        self.running = True
         | 
| 851 | 
            -
                        
         | 
| 852 | 
            -
                        # Create PID file after successful server start
         | 
| 853 | 
            -
                        self.create_pidfile()
         | 
| 854 | 
            -
                        
         | 
| 855 | 
            -
                        # Start health monitoring
         | 
| 856 | 
            -
                        if self.health_monitor:
         | 
| 857 | 
            -
                            # Add service health checker now that stats are available
         | 
| 858 | 
            -
                            service_checker = ServiceHealthChecker(
         | 
| 859 | 
            -
                                service_stats=self.health_stats,
         | 
| 860 | 
            -
                                max_clients=1000,
         | 
| 861 | 
            -
                                max_error_rate=0.1
         | 
| 862 | 
            -
                            )
         | 
| 863 | 
            -
                            self.health_monitor.add_checker(service_checker)
         | 
| 864 | 
            -
                            
         | 
| 865 | 
            -
                            # Start monitoring
         | 
| 866 | 
            -
                            self.health_monitor.start_monitoring()
         | 
| 867 | 
            -
                            self.logger.info("Health monitoring started")
         | 
| 868 | 
            -
                        
         | 
| 869 | 
            -
                    except Exception as e:
         | 
| 870 | 
            -
                        self.logger.error(f"Failed to start server: {e}")
         | 
| 871 | 
            -
                        # Clean up partial initialization
         | 
| 872 | 
            -
                        if hasattr(self, 'runner') and self.runner:
         | 
| 873 | 
            -
                            try:
         | 
| 874 | 
            -
                                await self.runner.cleanup()
         | 
| 875 | 
            -
                            except:
         | 
| 876 | 
            -
                                pass
         | 
| 877 | 
            -
                        
         | 
| 878 | 
            -
                        # Enhanced error handling for common startup failures
         | 
| 879 | 
            -
                        if ENHANCED_ERRORS_AVAILABLE:
         | 
| 880 | 
            -
                            if "Address already in use" in str(e) or "Permission denied" in str(e):
         | 
| 881 | 
            -
                                # This is likely a port conflict
         | 
| 882 | 
            -
                                try:
         | 
| 883 | 
            -
                                    # Check if port is in use and raise appropriate error
         | 
| 884 | 
            -
                                    self._check_port_only(raise_on_conflict=True)
         | 
| 885 | 
            -
                                except PortConflictError:
         | 
| 886 | 
            -
                                    # Re-raise the more specific error
         | 
| 887 | 
            -
                                    raise
         | 
| 888 | 
            -
                        
         | 
| 889 | 
            -
                        raise
         | 
| 890 | 
            -
                    
         | 
| 891 | 
            -
                    self.logger.info(f"🚀 Standalone Socket.IO server STARTED on http://{self.host}:{self.port}")
         | 
| 892 | 
            -
                    self.logger.info(f"🔧 Server ID: {self.server_id}")
         | 
| 893 | 
            -
                    self.logger.info(f"💾 PID file: {self.pidfile_path}")
         | 
| 894 | 
            -
                
         | 
| 895 | 
            -
                def start(self):
         | 
| 896 | 
            -
                    """Start the server in the main thread (for standalone execution)."""
         | 
| 897 | 
            -
                    if self.is_already_running():
         | 
| 898 | 
            -
                        self.logger.error("Server is already running. Use stop() first or choose a different port.")
         | 
| 899 | 
            -
                        return False
         | 
| 900 | 
            -
                    
         | 
| 901 | 
            -
                    self.setup_signal_handlers()
         | 
| 902 | 
            -
                    
         | 
| 903 | 
            -
                    # Run in main thread for standalone operation
         | 
| 904 | 
            -
                    try:
         | 
| 905 | 
            -
                        self.loop = asyncio.new_event_loop()
         | 
| 906 | 
            -
                        asyncio.set_event_loop(self.loop)
         | 
| 907 | 
            -
                        self.loop.run_until_complete(self._run_forever())
         | 
| 908 | 
            -
                    except KeyboardInterrupt:
         | 
| 909 | 
            -
                        self.logger.info("Received KeyboardInterrupt, shutting down...")
         | 
| 910 | 
            -
                    except Exception as e:
         | 
| 911 | 
            -
                        self.logger.error(f"Server error: {e}")
         | 
| 912 | 
            -
                        raise
         | 
| 913 | 
            -
                    finally:
         | 
| 914 | 
            -
                        self.stop()
         | 
| 915 | 
            -
                    
         | 
| 916 | 
            -
                    return True
         | 
| 917 | 
            -
                
         | 
| 918 | 
            -
                async def _run_forever(self):
         | 
| 919 | 
            -
                    """Run the server until stopped."""
         | 
| 920 | 
            -
                    await self.start_async()
         | 
| 921 | 
            -
                    
         | 
| 922 | 
            -
                    try:
         | 
| 923 | 
            -
                        # Keep server running with periodic health checks
         | 
| 924 | 
            -
                        last_health_check = time.time()
         | 
| 925 | 
            -
                        
         | 
| 926 | 
            -
                        while self.running:
         | 
| 927 | 
            -
                            await asyncio.sleep(1)
         | 
| 928 | 
            -
                            
         | 
| 929 | 
            -
                            # Periodic health check and stats update
         | 
| 930 | 
            -
                            now = time.time()
         | 
| 931 | 
            -
                            if now - last_health_check > 30:  # Every 30 seconds
         | 
| 932 | 
            -
                                self._update_health_stats()
         | 
| 933 | 
            -
                                last_health_check = now
         | 
| 934 | 
            -
                                
         | 
| 935 | 
            -
                    except Exception as e:
         | 
| 936 | 
            -
                        self.logger.error(f"Error in server loop: {e}")
         | 
| 937 | 
            -
                        raise
         | 
| 938 | 
            -
                
         | 
| 939 | 
            -
                def stop(self):
         | 
| 940 | 
            -
                    """Stop the server gracefully."""
         | 
| 941 | 
            -
                    self.logger.info("Stopping standalone Socket.IO server...")
         | 
| 942 | 
            -
                    self.running = False
         | 
| 943 | 
            -
                    
         | 
| 944 | 
            -
                    if self.loop and self.loop.is_running():
         | 
| 945 | 
            -
                        # Schedule shutdown in the event loop
         | 
| 946 | 
            -
                        self.loop.create_task(self._shutdown_async())
         | 
| 947 | 
            -
                    else:
         | 
| 948 | 
            -
                        # Direct shutdown
         | 
| 949 | 
            -
                        asyncio.run(self._shutdown_async())
         | 
| 950 | 
            -
                    
         | 
| 951 | 
            -
                    self.remove_pidfile()
         | 
| 952 | 
            -
                    self.logger.info("Server stopped")
         | 
| 953 | 
            -
                
         | 
| 954 | 
            -
                async def _shutdown_async(self):
         | 
| 955 | 
            -
                    """Async shutdown process."""
         | 
| 956 | 
            -
                    try:
         | 
| 957 | 
            -
                        # Stop health monitoring
         | 
| 958 | 
            -
                        if self.health_monitor:
         | 
| 959 | 
            -
                            await self.health_monitor.stop_monitoring()
         | 
| 960 | 
            -
                            self.logger.info("Health monitoring stopped")
         | 
| 961 | 
            -
                        
         | 
| 962 | 
            -
                        # Close all client connections
         | 
| 963 | 
            -
                        if self.sio:
         | 
| 964 | 
            -
                            await self.sio.shutdown()
         | 
| 965 | 
            -
                        
         | 
| 966 | 
            -
                        # Stop the web server
         | 
| 967 | 
            -
                        if self.site:
         | 
| 968 | 
            -
                            await self.site.stop()
         | 
| 969 | 
            -
                        if self.runner:
         | 
| 970 | 
            -
                            await self.runner.cleanup()
         | 
| 971 | 
            -
                            
         | 
| 972 | 
            -
                    except Exception as e:
         | 
| 973 | 
            -
                        self.logger.error(f"Error during shutdown: {e}")
         | 
| 974 | 
            -
                
         | 
| 975 | 
            -
                def _setup_routes(self):
         | 
| 976 | 
            -
                    """Setup HTTP routes for health checks and admin endpoints."""
         | 
| 977 | 
            -
                    
         | 
| 978 | 
            -
                    async def version_endpoint(request):
         | 
| 979 | 
            -
                        """Version discovery endpoint."""
         | 
| 980 | 
            -
                        compatibility_info = {
         | 
| 981 | 
            -
                            "server_version": self.server_version,
         | 
| 982 | 
            -
                            "server_id": self.server_id,
         | 
| 983 | 
            -
                            "socketio_version": SOCKETIO_VERSION,
         | 
| 984 | 
            -
                            "compatibility_matrix": COMPATIBILITY_MATRIX,
         | 
| 985 | 
            -
                            "supported_client_versions": COMPATIBILITY_MATRIX[self.server_version].get("claude_mpm_versions", []),
         | 
| 986 | 
            -
                            "features": COMPATIBILITY_MATRIX[self.server_version].get("features", [])
         | 
| 987 | 
            -
                        }
         | 
| 988 | 
            -
                        return web.json_response(compatibility_info)
         | 
| 989 | 
            -
                    
         | 
| 990 | 
            -
                    async def health_endpoint(request):
         | 
| 991 | 
            -
                        """Health check endpoint with detailed diagnostics."""
         | 
| 992 | 
            -
                        uptime = (datetime.utcnow() - self.start_time).total_seconds()
         | 
| 993 | 
            -
                        
         | 
| 994 | 
            -
                        health_info = {
         | 
| 995 | 
            -
                            "status": "healthy" if self.running else "stopped",
         | 
| 996 | 
            -
                            "server_version": self.server_version,
         | 
| 997 | 
            -
                            "server_id": self.server_id,
         | 
| 998 | 
            -
                            "pid": self.pid,
         | 
| 999 | 
            -
                            "uptime_seconds": uptime,
         | 
| 1000 | 
            -
                            "start_time": self.start_time.isoformat() + "Z",
         | 
| 1001 | 
            -
                            "timestamp": datetime.utcnow().isoformat() + "Z",
         | 
| 1002 | 
            -
                            "clients_connected": len(self.clients),
         | 
| 1003 | 
            -
                            "client_versions": dict(self.client_versions),
         | 
| 1004 | 
            -
                            "health_stats": dict(self.health_stats),
         | 
| 1005 | 
            -
                            "port": self.port,
         | 
| 1006 | 
            -
                            "host": self.host,
         | 
| 1007 | 
            -
                            "dependencies": {
         | 
| 1008 | 
            -
                                "socketio_version": SOCKETIO_VERSION,
         | 
| 1009 | 
            -
                                "python_version": sys.version.split()[0]
         | 
| 1010 | 
            -
                            }
         | 
| 1011 | 
            -
                        }
         | 
| 1012 | 
            -
                        return web.json_response(health_info)
         | 
| 1013 | 
            -
                    
         | 
| 1014 | 
            -
                    async def compatibility_check(request):
         | 
| 1015 | 
            -
                        """Check compatibility with a specific client version."""
         | 
| 1016 | 
            -
                        data = await request.json()
         | 
| 1017 | 
            -
                        client_version = data.get("client_version", "unknown")
         | 
| 1018 | 
            -
                        
         | 
| 1019 | 
            -
                        compatibility = self.check_compatibility(client_version)
         | 
| 1020 | 
            -
                        return web.json_response(compatibility)
         | 
| 1021 | 
            -
                    
         | 
| 1022 | 
            -
                    async def stats_endpoint(request):
         | 
| 1023 | 
            -
                        """Server statistics endpoint."""
         | 
| 1024 | 
            -
                        stats = {
         | 
| 1025 | 
            -
                            "server_info": {
         | 
| 1026 | 
            -
                                "version": self.server_version,
         | 
| 1027 | 
            -
                                "id": self.server_id,
         | 
| 1028 | 
            -
                                "uptime": (datetime.utcnow() - self.start_time).total_seconds()
         | 
| 1029 | 
            -
                            },
         | 
| 1030 | 
            -
                            "connections": {
         | 
| 1031 | 
            -
                                "current_clients": len(self.clients),
         | 
| 1032 | 
            -
                                "total_served": self.health_stats["clients_served"],
         | 
| 1033 | 
            -
                                "client_versions": dict(self.client_versions)
         | 
| 1034 | 
            -
                            },
         | 
| 1035 | 
            -
                            "events": {
         | 
| 1036 | 
            -
                                "total_processed": self.health_stats["events_processed"],
         | 
| 1037 | 
            -
                                "history_size": len(self.event_history),
         | 
| 1038 | 
            -
                                "last_activity": self.health_stats["last_activity"]
         | 
| 1039 | 
            -
                            },
         | 
| 1040 | 
            -
                            "errors": self.health_stats["errors"]
         | 
| 1041 | 
            -
                        }
         | 
| 1042 | 
            -
                        return web.json_response(stats)
         | 
| 1043 | 
            -
                    
         | 
| 1044 | 
            -
                    # Register routes
         | 
| 1045 | 
            -
                    self.app.router.add_get('/version', version_endpoint)
         | 
| 1046 | 
            -
                    self.app.router.add_get('/health', health_endpoint)
         | 
| 1047 | 
            -
                    self.app.router.add_get('/status', health_endpoint)  # Alias
         | 
| 1048 | 
            -
                    self.app.router.add_post('/compatibility', compatibility_check)
         | 
| 1049 | 
            -
                    self.app.router.add_get('/stats', stats_endpoint)
         | 
| 1050 | 
            -
                    
         | 
| 1051 | 
            -
                    # Serve Socket.IO client library
         | 
| 1052 | 
            -
                    self.app.router.add_static('/socket.io/', 
         | 
| 1053 | 
            -
                                             path=Path(__file__).parent / 'static', 
         | 
| 1054 | 
            -
                                             name='socketio_static')
         | 
| 1055 | 
            -
                
         | 
| 1056 | 
            -
                def _setup_event_handlers(self):
         | 
| 1057 | 
            -
                    """Setup Socket.IO event handlers."""
         | 
| 1058 | 
            -
                    
         | 
| 1059 | 
            -
                    @self.sio.event
         | 
| 1060 | 
            -
                    async def connect(sid, environ, auth):
         | 
| 1061 | 
            -
                        """Handle client connection with version compatibility checking."""
         | 
| 1062 | 
            -
                        self.clients.add(sid)
         | 
| 1063 | 
            -
                        client_addr = environ.get('REMOTE_ADDR', 'unknown')
         | 
| 1064 | 
            -
                        
         | 
| 1065 | 
            -
                        # Extract client version from auth if provided
         | 
| 1066 | 
            -
                        client_version = "unknown"
         | 
| 1067 | 
            -
                        if auth and isinstance(auth, dict):
         | 
| 1068 | 
            -
                            client_version = auth.get('claude_mpm_version', 'unknown')
         | 
| 1069 | 
            -
                        
         | 
| 1070 | 
            -
                        self.client_versions[sid] = client_version
         | 
| 1071 | 
            -
                        self.health_stats["clients_served"] += 1
         | 
| 1072 | 
            -
                        self.health_stats["last_activity"] = datetime.utcnow().isoformat() + "Z"
         | 
| 1073 | 
            -
                        
         | 
| 1074 | 
            -
                        self.logger.info(f"🔗 Client {sid} connected from {client_addr}")
         | 
| 1075 | 
            -
                        self.logger.info(f"📋 Client version: {client_version}")
         | 
| 1076 | 
            -
                        self.logger.info(f"📊 Total clients: {len(self.clients)}")
         | 
| 1077 | 
            -
                        
         | 
| 1078 | 
            -
                        # Check version compatibility
         | 
| 1079 | 
            -
                        compatibility = self.check_compatibility(client_version)
         | 
| 1080 | 
            -
                        
         | 
| 1081 | 
            -
                        # Send connection acknowledgment with compatibility info
         | 
| 1082 | 
            -
                        await self.sio.emit('connection_ack', {
         | 
| 1083 | 
            -
                            "server_version": self.server_version,
         | 
| 1084 | 
            -
                            "server_id": self.server_id,
         | 
| 1085 | 
            -
                            "compatibility": compatibility,
         | 
| 1086 | 
            -
                            "timestamp": datetime.utcnow().isoformat() + "Z"
         | 
| 1087 | 
            -
                        }, room=sid)
         | 
| 1088 | 
            -
                        
         | 
| 1089 | 
            -
                        # Send current server status
         | 
| 1090 | 
            -
                        await self._send_server_status(sid)
         | 
| 1091 | 
            -
                        
         | 
| 1092 | 
            -
                        if not compatibility["compatible"]:
         | 
| 1093 | 
            -
                            self.logger.warning(f"⚠️ Client {sid} version {client_version} has compatibility issues")
         | 
| 1094 | 
            -
                            await self.sio.emit('compatibility_warning', compatibility, room=sid)
         | 
| 1095 | 
            -
                    
         | 
| 1096 | 
            -
                    @self.sio.event
         | 
| 1097 | 
            -
                    async def disconnect(sid):
         | 
| 1098 | 
            -
                        """Handle client disconnection."""
         | 
| 1099 | 
            -
                        if sid in self.clients:
         | 
| 1100 | 
            -
                            self.clients.remove(sid)
         | 
| 1101 | 
            -
                        if sid in self.client_versions:
         | 
| 1102 | 
            -
                            del self.client_versions[sid]
         | 
| 1103 | 
            -
                        
         | 
| 1104 | 
            -
                        self.logger.info(f"🔌 Client {sid} disconnected")
         | 
| 1105 | 
            -
                        self.logger.info(f"📊 Remaining clients: {len(self.clients)}")
         | 
| 1106 | 
            -
                    
         | 
| 1107 | 
            -
                    @self.sio.event
         | 
| 1108 | 
            -
                    async def ping(sid, data=None):
         | 
| 1109 | 
            -
                        """Handle ping requests."""
         | 
| 1110 | 
            -
                        await self.sio.emit('pong', {
         | 
| 1111 | 
            -
                            "timestamp": datetime.utcnow().isoformat() + "Z",
         | 
| 1112 | 
            -
                            "server_id": self.server_id
         | 
| 1113 | 
            -
                        }, room=sid)
         | 
| 1114 | 
            -
                    
         | 
| 1115 | 
            -
                    @self.sio.event
         | 
| 1116 | 
            -
                    async def get_version(sid):
         | 
| 1117 | 
            -
                        """Handle version info requests."""
         | 
| 1118 | 
            -
                        version_info = {
         | 
| 1119 | 
            -
                            "server_version": self.server_version,
         | 
| 1120 | 
            -
                            "server_id": self.server_id,
         | 
| 1121 | 
            -
                            "socketio_version": SOCKETIO_VERSION,
         | 
| 1122 | 
            -
                            "compatibility_matrix": COMPATIBILITY_MATRIX
         | 
| 1123 | 
            -
                        }
         | 
| 1124 | 
            -
                        await self.sio.emit('version_info', version_info, room=sid)
         | 
| 1125 | 
            -
                    
         | 
| 1126 | 
            -
                    @self.sio.event
         | 
| 1127 | 
            -
                    async def claude_event(sid, data):
         | 
| 1128 | 
            -
                        """Handle events from claude-mpm clients and broadcast to other clients."""
         | 
| 1129 | 
            -
                        try:
         | 
| 1130 | 
            -
                            # Add server metadata
         | 
| 1131 | 
            -
                            enhanced_data = {
         | 
| 1132 | 
            -
                                **data,
         | 
| 1133 | 
            -
                                "server_id": self.server_id,
         | 
| 1134 | 
            -
                                "received_at": datetime.utcnow().isoformat() + "Z"
         | 
| 1135 | 
            -
                            }
         | 
| 1136 | 
            -
                            
         | 
| 1137 | 
            -
                            # Store in event history
         | 
| 1138 | 
            -
                            self.event_history.append(enhanced_data)
         | 
| 1139 | 
            -
                            self.health_stats["events_processed"] += 1
         | 
| 1140 | 
            -
                            self.health_stats["last_activity"] = datetime.utcnow().isoformat() + "Z"
         | 
| 1141 | 
            -
                            
         | 
| 1142 | 
            -
                            # Broadcast to all other clients
         | 
| 1143 | 
            -
                            await self.sio.emit('claude_event', enhanced_data, skip_sid=sid)
         | 
| 1144 | 
            -
                            
         | 
| 1145 | 
            -
                            self.logger.debug(f"📤 Broadcasted claude_event from {sid} to {len(self.clients)-1} clients")
         | 
| 1146 | 
            -
                            
         | 
| 1147 | 
            -
                        except Exception as e:
         | 
| 1148 | 
            -
                            self.logger.error(f"Error handling claude_event: {e}")
         | 
| 1149 | 
            -
                            self.health_stats["errors"] += 1
         | 
| 1150 | 
            -
                            
         | 
| 1151 | 
            -
                            # Check if error rate is becoming concerning
         | 
| 1152 | 
            -
                            if ENHANCED_ERRORS_AVAILABLE and self.health_stats["errors"] > 0:
         | 
| 1153 | 
            -
                                error_rate = self.health_stats["errors"] / max(self.health_stats["events_processed"], 1)
         | 
| 1154 | 
            -
                                if error_rate > 0.1:  # More than 10% error rate
         | 
| 1155 | 
            -
                                    self.logger.warning(f"⚠️ High error rate detected: {error_rate:.2%} ({self.health_stats['errors']} errors out of {self.health_stats['events_processed']} events)")
         | 
| 1156 | 
            -
                    
         | 
| 1157 | 
            -
                    @self.sio.event
         | 
| 1158 | 
            -
                    async def get_history(sid, data=None):
         | 
| 1159 | 
            -
                        """Handle event history requests."""
         | 
| 1160 | 
            -
                        params = data or {}
         | 
| 1161 | 
            -
                        limit = min(params.get("limit", 100), len(self.event_history))
         | 
| 1162 | 
            -
                        
         | 
| 1163 | 
            -
                        history = list(self.event_history)[-limit:] if limit > 0 else []
         | 
| 1164 | 
            -
                        
         | 
| 1165 | 
            -
                        await self.sio.emit('event_history', {
         | 
| 1166 | 
            -
                            "events": history,
         | 
| 1167 | 
            -
                            "total_available": len(self.event_history),
         | 
| 1168 | 
            -
                            "returned": len(history)
         | 
| 1169 | 
            -
                        }, room=sid)
         | 
| 1170 | 
            -
                
         | 
| 1171 | 
            -
                async def _send_server_status(self, sid: str):
         | 
| 1172 | 
            -
                    """Send current server status to a client."""
         | 
| 1173 | 
            -
                    status = {
         | 
| 1174 | 
            -
                        "server_version": self.server_version,
         | 
| 1175 | 
            -
                        "server_id": self.server_id,
         | 
| 1176 | 
            -
                        "uptime": (datetime.utcnow() - self.start_time).total_seconds(),
         | 
| 1177 | 
            -
                        "clients_connected": len(self.clients),
         | 
| 1178 | 
            -
                        "events_processed": self.health_stats["events_processed"],
         | 
| 1179 | 
            -
                        "timestamp": datetime.utcnow().isoformat() + "Z"
         | 
| 1180 | 
            -
                    }
         | 
| 1181 | 
            -
                    await self.sio.emit('server_status', status, room=sid)
         | 
| 1182 | 
            -
                
         | 
| 1183 | 
            -
                def _update_health_stats(self):
         | 
| 1184 | 
            -
                    """Update health statistics."""
         | 
| 1185 | 
            -
                    self.logger.debug(f"🏥 Health check - Clients: {len(self.clients)}, "
         | 
| 1186 | 
            -
                                     f"Events: {self.health_stats['events_processed']}, "
         | 
| 1187 | 
            -
                                     f"Errors: {self.health_stats['errors']}")
         | 
| 1188 | 
            -
             | 
| 1189 | 
            -
             | 
| 1190 | 
            -
            def main():
         | 
| 1191 | 
            -
                """Main entry point for standalone server execution."""
         | 
| 1192 | 
            -
                import argparse
         | 
| 1193 | 
            -
                import json
         | 
| 1194 | 
            -
                import time
         | 
| 1195 | 
            -
                
         | 
| 1196 | 
            -
                parser = argparse.ArgumentParser(description="Standalone Claude MPM Socket.IO Server")
         | 
| 1197 | 
            -
                parser.add_argument("--host", default="localhost", help="Host to bind to")  
         | 
| 1198 | 
            -
                parser.add_argument("--port", type=int, default=8765, help="Port to bind to")
         | 
| 1199 | 
            -
                parser.add_argument("--server-id", help="Custom server ID")
         | 
| 1200 | 
            -
                parser.add_argument("--check-running", action="store_true", 
         | 
| 1201 | 
            -
                                   help="Check if server is already running and exit")
         | 
| 1202 | 
            -
                parser.add_argument("--stop", action="store_true", help="Stop running server")
         | 
| 1203 | 
            -
                parser.add_argument("--version", action="store_true", help="Show version info")
         | 
| 1204 | 
            -
                
         | 
| 1205 | 
            -
                args = parser.parse_args()
         | 
| 1206 | 
            -
                
         | 
| 1207 | 
            -
                if args.version:
         | 
| 1208 | 
            -
                    print(f"Standalone Socket.IO Server v{STANDALONE_SERVER_VERSION}")
         | 
| 1209 | 
            -
                    print(f"Socket.IO v{SOCKETIO_VERSION}")
         | 
| 1210 | 
            -
                    print(f"Compatibility: {COMPATIBILITY_MATRIX[STANDALONE_SERVER_VERSION]['claude_mpm_versions']}")
         | 
| 1211 | 
            -
                    return
         | 
| 1212 | 
            -
                
         | 
| 1213 | 
            -
                server = StandaloneSocketIOServer(
         | 
| 1214 | 
            -
                    host=args.host,
         | 
| 1215 | 
            -
                    port=args.port,
         | 
| 1216 | 
            -
                    server_id=args.server_id
         | 
| 1217 | 
            -
                )
         | 
| 1218 | 
            -
                
         | 
| 1219 | 
            -
                if args.check_running:
         | 
| 1220 | 
            -
                    if server.is_already_running():
         | 
| 1221 | 
            -
                        print(f"Server is running on {args.host}:{args.port}")
         | 
| 1222 | 
            -
                        sys.exit(0)
         | 
| 1223 | 
            -
                    else:
         | 
| 1224 | 
            -
                        print(f"No server running on {args.host}:{args.port}")
         | 
| 1225 | 
            -
                        sys.exit(1)
         | 
| 1226 | 
            -
                
         | 
| 1227 | 
            -
                if args.stop:
         | 
| 1228 | 
            -
                    if server.is_already_running():
         | 
| 1229 | 
            -
                        # Send termination signal to running server with enhanced validation
         | 
| 1230 | 
            -
                        try:
         | 
| 1231 | 
            -
                            # Read and validate PID file
         | 
| 1232 | 
            -
                            with open(server.pidfile_path, 'r') as f:
         | 
| 1233 | 
            -
                                content = f.read().strip()
         | 
| 1234 | 
            -
                            
         | 
| 1235 | 
            -
                            # Try to parse as JSON first (new format), fallback to plain PID
         | 
| 1236 | 
            -
                            try:
         | 
| 1237 | 
            -
                                pidfile_data = json.loads(content)
         | 
| 1238 | 
            -
                                pid = pidfile_data["pid"]
         | 
| 1239 | 
            -
                                server_id = pidfile_data.get("server_id", "unknown")
         | 
| 1240 | 
            -
                                print(f"Found server {server_id} with PID {pid}")
         | 
| 1241 | 
            -
                            except (json.JSONDecodeError, KeyError):
         | 
| 1242 | 
            -
                                # Fallback to old format
         | 
| 1243 | 
            -
                                pid = int(content)
         | 
| 1244 | 
            -
                                server_id = "unknown"
         | 
| 1245 | 
            -
                            
         | 
| 1246 | 
            -
                            # Validate the process before attempting to stop it
         | 
| 1247 | 
            -
                            validation = server._validate_process_identity(pid)
         | 
| 1248 | 
            -
                            if not validation["is_valid"]:
         | 
| 1249 | 
            -
                                print(f"Process {pid} is not valid or no longer exists")
         | 
| 1250 | 
            -
                                server._cleanup_stale_pidfile("stop_command_invalid_process")
         | 
| 1251 | 
            -
                                print("Cleaned up stale PID file")
         | 
| 1252 | 
            -
                                sys.exit(1)
         | 
| 1253 | 
            -
                            
         | 
| 1254 | 
            -
                            if validation["is_zombie"]:
         | 
| 1255 | 
            -
                                print(f"Process {pid} is a zombie, cleaning up PID file")
         | 
| 1256 | 
            -
                                server._cleanup_stale_pidfile("stop_command_zombie")
         | 
| 1257 | 
            -
                                sys.exit(0)
         | 
| 1258 | 
            -
                            
         | 
| 1259 | 
            -
                            if not validation["is_our_server"]:
         | 
| 1260 | 
            -
                                print(f"Warning: Process {pid} may not be our Socket.IO server")
         | 
| 1261 | 
            -
                                print(f"Command line: {validation['process_info'].get('cmdline', 'unknown')}")
         | 
| 1262 | 
            -
                                response = input("Stop it anyway? [y/N]: ")
         | 
| 1263 | 
            -
                                if response.lower() != 'y':
         | 
| 1264 | 
            -
                                    print("Aborted")
         | 
| 1265 | 
            -
                                    sys.exit(1)
         | 
| 1266 | 
            -
                            
         | 
| 1267 | 
            -
                            # Send termination signal
         | 
| 1268 | 
            -
                            os.kill(pid, signal.SIGTERM)
         | 
| 1269 | 
            -
                            print(f"Sent stop signal to server (PID: {pid})")
         | 
| 1270 | 
            -
                            
         | 
| 1271 | 
            -
                            # Wait a moment for graceful shutdown
         | 
| 1272 | 
            -
                            time.sleep(2)
         | 
| 1273 | 
            -
                            
         | 
| 1274 | 
            -
                            # Check if process is still running
         | 
| 1275 | 
            -
                            try:
         | 
| 1276 | 
            -
                                os.kill(pid, 0)
         | 
| 1277 | 
            -
                                print(f"Server is still running, sending SIGKILL...")
         | 
| 1278 | 
            -
                                os.kill(pid, signal.SIGKILL)
         | 
| 1279 | 
            -
                                time.sleep(1)
         | 
| 1280 | 
            -
                            except OSError:
         | 
| 1281 | 
            -
                                print("Server stopped successfully")
         | 
| 1282 | 
            -
                            
         | 
| 1283 | 
            -
                        except Exception as e:
         | 
| 1284 | 
            -
                            print(f"Error stopping server: {e}")
         | 
| 1285 | 
            -
                            sys.exit(1)
         | 
| 1286 | 
            -
                    else:
         | 
| 1287 | 
            -
                        print("No server running to stop")
         | 
| 1288 | 
            -
                        sys.exit(1)
         | 
| 1289 | 
            -
                    return
         | 
| 1290 | 
            -
                
         | 
| 1291 | 
            -
                # Start the server
         | 
| 1292 | 
            -
                try:
         | 
| 1293 | 
            -
                    server.start()
         | 
| 1294 | 
            -
                except Exception as e:
         | 
| 1295 | 
            -
                    print(f"Failed to start server: {e}")
         | 
| 1296 | 
            -
                    sys.exit(1)
         | 
| 1297 | 
            -
             | 
| 1298 | 
            -
             | 
| 1299 | 
            -
            if __name__ == "__main__":
         | 
| 1300 | 
            -
                main()
         |