claude-mpm 3.9.9__py3-none-any.whl → 4.0.3__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 +155 -0
- 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 +1 -1
- 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 +90 -49
- claude_mpm/cli/__main__.py +3 -2
- claude_mpm/cli/commands/__init__.py +21 -18
- 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 +143 -762
- 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 +203 -81
- 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 +305 -197
- claude_mpm/cli/parser.py +24 -1150
- 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 +104 -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 +217 -0
- claude_mpm/config/paths.py +94 -208
- claude_mpm/config/socketio_config.py +84 -73
- claude_mpm/constants.py +36 -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/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 +74 -70
- claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +106 -92
- 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 +120 -54
- 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 +571 -0
- 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 +40 -23
- 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 +14 -21
- 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 +97 -93
- claude_mpm/services/mcp_gateway/main.py +307 -127
- claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
- claude_mpm/services/mcp_gateway/registry/service_registry.py +100 -101
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
- claude_mpm/services/mcp_gateway/server/__init__.py +4 -4
- claude_mpm/services/mcp_gateway/server/{mcp_server.py → mcp_gateway.py} +149 -153
- 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 +110 -121
- 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 +223 -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 +145 -65
- 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 +170 -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 +20 -534
- 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 +9 -0
- claude_mpm/storage/state_storage.py +552 -0
- 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.9.dist-info → claude_mpm-4.0.3.dist-info}/METADATA +51 -2
- claude_mpm-4.0.3.dist-info/RECORD +402 -0
- {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
- {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/licenses/LICENSE +1 -1
- claude_mpm/config/memory_guardian_config.py +0 -325
- claude_mpm/core/config_paths.py +0 -150
- 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/memory_guardian.py +0 -770
- claude_mpm/services/mcp_gateway/server/mcp_server_simple.py +0 -444
- 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.9.dist-info/RECORD +0 -293
- {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/WHEEL +0 -0
- {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/top_level.txt +0 -0
    
        claude_mpm/cli/commands/run.py
    CHANGED
    
    | @@ -1,3 +1,5 @@ | |
| 1 | 
            +
            from pathlib import Path
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            """
         | 
| 2 4 | 
             
            Run command implementation for claude-mpm.
         | 
| 3 5 |  | 
| @@ -5,122 +7,128 @@ WHY: This module handles the main 'run' command which starts Claude sessions. | |
| 5 7 | 
             
            It's the most commonly used command and handles both interactive and non-interactive modes.
         | 
| 6 8 | 
             
            """
         | 
| 7 9 |  | 
| 10 | 
            +
            import logging
         | 
| 8 11 | 
             
            import os
         | 
| 9 12 | 
             
            import subprocess
         | 
| 10 13 | 
             
            import sys
         | 
| 11 14 | 
             
            import time
         | 
| 12 15 | 
             
            import webbrowser
         | 
| 13 | 
            -
            import logging
         | 
| 14 | 
            -
            from pathlib import Path
         | 
| 15 16 | 
             
            from datetime import datetime
         | 
| 16 17 |  | 
| 17 | 
            -
            from ...core.logger import get_logger
         | 
| 18 | 
            -
            from ...core.config import Config
         | 
| 19 18 | 
             
            from ...constants import LogLevel
         | 
| 20 | 
            -
            from  | 
| 19 | 
            +
            from ...core.config import Config
         | 
| 20 | 
            +
            from ...core.logger import get_logger
         | 
| 21 | 
            +
            from ...core.unified_paths import get_package_root, get_scripts_dir
         | 
| 22 | 
            +
            from ...services.port_manager import PortManager
         | 
| 21 23 | 
             
            from ...utils.dependency_manager import ensure_socketio_dependencies
         | 
| 22 | 
            -
            from  | 
| 24 | 
            +
            from ..utils import get_user_input, list_agent_versions_at_startup
         | 
| 23 25 |  | 
| 24 26 |  | 
| 25 27 | 
             
            def filter_claude_mpm_args(claude_args):
         | 
| 26 28 | 
             
                """
         | 
| 27 29 | 
             
                Filter out claude-mpm specific arguments from claude_args before passing to Claude CLI.
         | 
| 28 | 
            -
             | 
| 30 | 
            +
             | 
| 29 31 | 
             
                WHY: The argparse.REMAINDER captures ALL remaining arguments, including claude-mpm
         | 
| 30 32 | 
             
                specific flags like --monitor, etc. Claude CLI doesn't understand these
         | 
| 31 33 | 
             
                flags and will error if they're passed through.
         | 
| 32 | 
            -
             | 
| 34 | 
            +
             | 
| 33 35 | 
             
                DESIGN DECISION: We maintain a list of known claude-mpm flags to filter out,
         | 
| 34 36 | 
             
                ensuring only genuine Claude CLI arguments are passed through.
         | 
| 35 | 
            -
             | 
| 37 | 
            +
             | 
| 36 38 | 
             
                Args:
         | 
| 37 39 | 
             
                    claude_args: List of arguments captured by argparse.REMAINDER
         | 
| 38 | 
            -
             | 
| 40 | 
            +
             | 
| 39 41 | 
             
                Returns:
         | 
| 40 42 | 
             
                    Filtered list of arguments safe to pass to Claude CLI
         | 
| 41 43 | 
             
                """
         | 
| 42 44 | 
             
                if not claude_args:
         | 
| 43 45 | 
             
                    return []
         | 
| 44 | 
            -
             | 
| 46 | 
            +
             | 
| 45 47 | 
             
                # Known claude-mpm specific flags that should NOT be passed to Claude CLI
         | 
| 46 48 | 
             
                # This includes all MPM-specific arguments from the parser
         | 
| 47 49 | 
             
                mpm_flags = {
         | 
| 48 50 | 
             
                    # Run-specific flags
         | 
| 49 | 
            -
                     | 
| 50 | 
            -
                     | 
| 51 | 
            -
                     | 
| 52 | 
            -
                     | 
| 53 | 
            -
                     | 
| 54 | 
            -
                     | 
| 55 | 
            -
                     | 
| 56 | 
            -
                     | 
| 51 | 
            +
                    "--monitor",
         | 
| 52 | 
            +
                    "--websocket-port",
         | 
| 53 | 
            +
                    "--no-hooks",
         | 
| 54 | 
            +
                    "--no-tickets",
         | 
| 55 | 
            +
                    "--intercept-commands",
         | 
| 56 | 
            +
                    "--no-native-agents",
         | 
| 57 | 
            +
                    "--launch-method",
         | 
| 58 | 
            +
                    "--resume",
         | 
| 57 59 | 
             
                    # Dependency checking flags (MPM-specific)
         | 
| 58 | 
            -
                     | 
| 59 | 
            -
                     | 
| 60 | 
            -
                     | 
| 61 | 
            -
                     | 
| 60 | 
            +
                    "--no-check-dependencies",
         | 
| 61 | 
            +
                    "--force-check-dependencies",
         | 
| 62 | 
            +
                    "--no-prompt",
         | 
| 63 | 
            +
                    "--force-prompt",
         | 
| 62 64 | 
             
                    # Input/output flags (these are MPM-specific, not Claude CLI flags)
         | 
| 63 | 
            -
                     | 
| 64 | 
            -
                     | 
| 65 | 
            +
                    "--input",
         | 
| 66 | 
            +
                    "--non-interactive",
         | 
| 65 67 | 
             
                    # Common logging flags (these are MPM-specific, not Claude CLI flags)
         | 
| 66 | 
            -
                     | 
| 67 | 
            -
                     | 
| 68 | 
            -
                     | 
| 68 | 
            +
                    "--debug",
         | 
| 69 | 
            +
                    "--logging",
         | 
| 70 | 
            +
                    "--log-dir",
         | 
| 69 71 | 
             
                    # Framework flags (these are MPM-specific)
         | 
| 70 | 
            -
                     | 
| 71 | 
            -
                     | 
| 72 | 
            +
                    "--framework-path",
         | 
| 73 | 
            +
                    "--agents-dir",
         | 
| 72 74 | 
             
                    # Version flag (handled by MPM)
         | 
| 73 | 
            -
                     | 
| 75 | 
            +
                    "--version",
         | 
| 74 76 | 
             
                    # Short flags (MPM-specific equivalents)
         | 
| 75 | 
            -
                     | 
| 76 | 
            -
                     | 
| 77 | 
            +
                    "-i",  # --input (MPM-specific, not Claude CLI)
         | 
| 78 | 
            +
                    "-d",  # --debug (MPM-specific, not Claude CLI)
         | 
| 77 79 | 
             
                }
         | 
| 78 | 
            -
             | 
| 80 | 
            +
             | 
| 79 81 | 
             
                filtered_args = []
         | 
| 80 82 | 
             
                i = 0
         | 
| 81 83 | 
             
                while i < len(claude_args):
         | 
| 82 84 | 
             
                    arg = claude_args[i]
         | 
| 83 | 
            -
             | 
| 85 | 
            +
             | 
| 84 86 | 
             
                    # Check if this is a claude-mpm flag
         | 
| 85 87 | 
             
                    if arg in mpm_flags:
         | 
| 86 88 | 
             
                        # Skip this flag
         | 
| 87 89 | 
             
                        i += 1
         | 
| 88 90 | 
             
                        # Also skip the next argument if this flag expects a value
         | 
| 89 91 | 
             
                        value_expecting_flags = {
         | 
| 90 | 
            -
                             | 
| 91 | 
            -
                             | 
| 92 | 
            +
                            "--websocket-port",
         | 
| 93 | 
            +
                            "--launch-method",
         | 
| 94 | 
            +
                            "--logging",
         | 
| 95 | 
            +
                            "--log-dir",
         | 
| 96 | 
            +
                            "--framework-path",
         | 
| 97 | 
            +
                            "--agents-dir",
         | 
| 98 | 
            +
                            "-i",
         | 
| 99 | 
            +
                            "--input",
         | 
| 92 100 | 
             
                        }
         | 
| 93 101 | 
             
                        optional_value_flags = {
         | 
| 94 | 
            -
                             | 
| 95 | 
            -
                        }
         | 
| 96 | 
            -
             | 
| 102 | 
            +
                            "--resume"
         | 
| 103 | 
            +
                        }  # These flags can have optional values (nargs="?")
         | 
| 104 | 
            +
             | 
| 97 105 | 
             
                        if arg in value_expecting_flags and i < len(claude_args):
         | 
| 98 106 | 
             
                            i += 1  # Skip the value too
         | 
| 99 107 | 
             
                        elif arg in optional_value_flags and i < len(claude_args):
         | 
| 100 108 | 
             
                            # For optional value flags, only skip next arg if it doesn't start with --
         | 
| 101 109 | 
             
                            next_arg = claude_args[i]
         | 
| 102 | 
            -
                            if not next_arg.startswith( | 
| 110 | 
            +
                            if not next_arg.startswith("--"):
         | 
| 103 111 | 
             
                                i += 1  # Skip the value
         | 
| 104 112 | 
             
                    else:
         | 
| 105 113 | 
             
                        # This is not a claude-mpm flag, keep it
         | 
| 106 114 | 
             
                        filtered_args.append(arg)
         | 
| 107 115 | 
             
                        i += 1
         | 
| 108 | 
            -
             | 
| 116 | 
            +
             | 
| 109 117 | 
             
                return filtered_args
         | 
| 110 118 |  | 
| 111 119 |  | 
| 112 120 | 
             
            def create_session_context(session_id, session_manager):
         | 
| 113 121 | 
             
                """
         | 
| 114 122 | 
             
                Create enhanced context for resumed sessions.
         | 
| 115 | 
            -
             | 
| 123 | 
            +
             | 
| 116 124 | 
             
                WHY: When resuming a session, we want to provide Claude with context about
         | 
| 117 125 | 
             
                the previous session including what agents were used and when it was created.
         | 
| 118 126 | 
             
                This helps maintain continuity across session boundaries.
         | 
| 119 | 
            -
             | 
| 127 | 
            +
             | 
| 120 128 | 
             
                Args:
         | 
| 121 129 | 
             
                    session_id: Session ID being resumed
         | 
| 122 130 | 
             
                    session_manager: SessionManager instance
         | 
| 123 | 
            -
             | 
| 131 | 
            +
             | 
| 124 132 | 
             
                Returns:
         | 
| 125 133 | 
             
                    Enhanced context string with session information
         | 
| 126 134 | 
             
                """
         | 
| @@ -128,13 +136,13 @@ def create_session_context(session_id, session_manager): | |
| 128 136 | 
             
                    from ...core.claude_runner import create_simple_context
         | 
| 129 137 | 
             
                except ImportError:
         | 
| 130 138 | 
             
                    from claude_mpm.core.claude_runner import create_simple_context
         | 
| 131 | 
            -
             | 
| 139 | 
            +
             | 
| 132 140 | 
             
                base_context = create_simple_context()
         | 
| 133 | 
            -
             | 
| 141 | 
            +
             | 
| 134 142 | 
             
                session_data = session_manager.get_session_by_id(session_id)
         | 
| 135 143 | 
             
                if not session_data:
         | 
| 136 144 | 
             
                    return base_context
         | 
| 137 | 
            -
             | 
| 145 | 
            +
             | 
| 138 146 | 
             
                # Add session resumption information
         | 
| 139 147 | 
             
                session_info = f"""
         | 
| 140 148 |  | 
| @@ -146,58 +154,58 @@ You are resuming session {session_id[:8]}... which was: | |
| 146 154 | 
             
            - Context: {session_data.get('context', 'default')}
         | 
| 147 155 | 
             
            - Use count: {session_data.get('use_count', 0)}
         | 
| 148 156 | 
             
            """
         | 
| 149 | 
            -
             | 
| 157 | 
            +
             | 
| 150 158 | 
             
                # Add information about agents previously run in this session
         | 
| 151 | 
            -
                agents_run = session_data.get( | 
| 159 | 
            +
                agents_run = session_data.get("agents_run", [])
         | 
| 152 160 | 
             
                if agents_run:
         | 
| 153 161 | 
             
                    session_info += "\n- Previous agent activity:\n"
         | 
| 154 162 | 
             
                    for agent_info in agents_run[-5:]:  # Show last 5 agents
         | 
| 155 163 | 
             
                        session_info += f"  • {agent_info.get('agent', 'unknown')}: {agent_info.get('task', 'no description')[:50]}...\n"
         | 
| 156 164 | 
             
                    if len(agents_run) > 5:
         | 
| 157 165 | 
             
                        session_info += f"  (and {len(agents_run) - 5} other agent interactions)\n"
         | 
| 158 | 
            -
             | 
| 166 | 
            +
             | 
| 159 167 | 
             
                session_info += "\nContinue from where you left off in this session."
         | 
| 160 | 
            -
             | 
| 168 | 
            +
             | 
| 161 169 | 
             
                return base_context + session_info
         | 
| 162 170 |  | 
| 163 171 |  | 
| 164 172 | 
             
            def run_session(args):
         | 
| 165 173 | 
             
                """
         | 
| 166 174 | 
             
                Run a simplified Claude session.
         | 
| 167 | 
            -
             | 
| 175 | 
            +
             | 
| 168 176 | 
             
                WHY: This is the primary command that users interact with. It sets up the
         | 
| 169 177 | 
             
                environment, optionally deploys agents, and launches Claude with the MPM framework.
         | 
| 170 | 
            -
             | 
| 178 | 
            +
             | 
| 171 179 | 
             
                DESIGN DECISION: We use ClaudeRunner to handle the complexity of
         | 
| 172 180 | 
             
                subprocess management and hook integration, keeping this function focused
         | 
| 173 181 | 
             
                on high-level orchestration.
         | 
| 174 | 
            -
             | 
| 182 | 
            +
             | 
| 175 183 | 
             
                Args:
         | 
| 176 184 | 
             
                    args: Parsed command line arguments
         | 
| 177 185 | 
             
                """
         | 
| 178 186 | 
             
                logger = get_logger("cli")
         | 
| 179 187 | 
             
                if args.logging != LogLevel.OFF.value:
         | 
| 180 188 | 
             
                    logger.info("Starting Claude MPM session")
         | 
| 181 | 
            -
             | 
| 189 | 
            +
             | 
| 182 190 | 
             
                # Perform startup configuration check
         | 
| 183 191 | 
             
                _check_configuration_health(logger)
         | 
| 184 | 
            -
             | 
| 192 | 
            +
             | 
| 185 193 | 
             
                # Check for memory usage issues with .claude.json
         | 
| 186 194 | 
             
                _check_claude_json_memory(args, logger)
         | 
| 187 | 
            -
             | 
| 195 | 
            +
             | 
| 188 196 | 
             
                try:
         | 
| 189 197 | 
             
                    from ...core.claude_runner import ClaudeRunner, create_simple_context
         | 
| 190 198 | 
             
                    from ...core.session_manager import SessionManager
         | 
| 191 199 | 
             
                except ImportError:
         | 
| 192 200 | 
             
                    from claude_mpm.core.claude_runner import ClaudeRunner, create_simple_context
         | 
| 193 201 | 
             
                    from claude_mpm.core.session_manager import SessionManager
         | 
| 194 | 
            -
             | 
| 202 | 
            +
             | 
| 195 203 | 
             
                # Handle session resumption
         | 
| 196 204 | 
             
                session_manager = SessionManager()
         | 
| 197 205 | 
             
                resume_session_id = None
         | 
| 198 206 | 
             
                resume_context = None
         | 
| 199 | 
            -
             | 
| 200 | 
            -
                if hasattr(args,  | 
| 207 | 
            +
             | 
| 208 | 
            +
                if hasattr(args, "resume") and args.resume:
         | 
| 201 209 | 
             
                    if args.resume == "last":
         | 
| 202 210 | 
             
                        # Resume the last interactive session
         | 
| 203 211 | 
             
                        resume_session_id = session_manager.get_last_interactive_session()
         | 
| @@ -205,8 +213,12 @@ def run_session(args): | |
| 205 213 | 
             
                            session_data = session_manager.get_session_by_id(resume_session_id)
         | 
| 206 214 | 
             
                            if session_data:
         | 
| 207 215 | 
             
                                resume_context = session_data.get("context", "default")
         | 
| 208 | 
            -
                                logger.info( | 
| 209 | 
            -
             | 
| 216 | 
            +
                                logger.info(
         | 
| 217 | 
            +
                                    f"Resuming session {resume_session_id} (context: {resume_context})"
         | 
| 218 | 
            +
                                )
         | 
| 219 | 
            +
                                print(
         | 
| 220 | 
            +
                                    f"🔄 Resuming session {resume_session_id[:8]}... (created: {session_data.get('created_at', 'unknown')})"
         | 
| 221 | 
            +
                                )
         | 
| 210 222 | 
             
                            else:
         | 
| 211 223 | 
             
                                logger.warning(f"Session {resume_session_id} not found")
         | 
| 212 224 | 
             
                        else:
         | 
| @@ -218,82 +230,115 @@ def run_session(args): | |
| 218 230 | 
             
                        session_data = session_manager.get_session_by_id(resume_session_id)
         | 
| 219 231 | 
             
                        if session_data:
         | 
| 220 232 | 
             
                            resume_context = session_data.get("context", "default")
         | 
| 221 | 
            -
                            logger.info( | 
| 222 | 
            -
             | 
| 233 | 
            +
                            logger.info(
         | 
| 234 | 
            +
                                f"Resuming session {resume_session_id} (context: {resume_context})"
         | 
| 235 | 
            +
                            )
         | 
| 236 | 
            +
                            print(
         | 
| 237 | 
            +
                                f"🔄 Resuming session {resume_session_id[:8]}... (context: {resume_context})"
         | 
| 238 | 
            +
                            )
         | 
| 223 239 | 
             
                        else:
         | 
| 224 240 | 
             
                            logger.error(f"Session {resume_session_id} not found")
         | 
| 225 241 | 
             
                            print(f"❌ Session {resume_session_id} not found")
         | 
| 226 242 | 
             
                            print("💡 Use 'claude-mpm sessions' to list available sessions")
         | 
| 227 243 | 
             
                            return
         | 
| 228 | 
            -
             | 
| 244 | 
            +
             | 
| 229 245 | 
             
                # Skip native agents if disabled
         | 
| 230 | 
            -
                if getattr(args,  | 
| 246 | 
            +
                if getattr(args, "no_native_agents", False):
         | 
| 231 247 | 
             
                    print("Native agents disabled")
         | 
| 232 248 | 
             
                else:
         | 
| 233 249 | 
             
                    # List deployed agent versions at startup
         | 
| 234 250 | 
             
                    list_agent_versions_at_startup()
         | 
| 235 | 
            -
             | 
| 251 | 
            +
             | 
| 236 252 | 
             
                    # Smart dependency checking - only when needed
         | 
| 237 | 
            -
                    if getattr(args,  | 
| 253 | 
            +
                    if getattr(args, "check_dependencies", True):  # Default to checking
         | 
| 238 254 | 
             
                        try:
         | 
| 239 255 | 
             
                            from ...utils.agent_dependency_loader import AgentDependencyLoader
         | 
| 240 256 | 
             
                            from ...utils.dependency_cache import SmartDependencyChecker
         | 
| 241 257 | 
             
                            from ...utils.environment_context import should_prompt_for_dependencies
         | 
| 242 | 
            -
             | 
| 258 | 
            +
             | 
| 243 259 | 
             
                            # Initialize smart checker
         | 
| 244 260 | 
             
                            smart_checker = SmartDependencyChecker()
         | 
| 245 261 | 
             
                            loader = AgentDependencyLoader(auto_install=False)
         | 
| 246 | 
            -
             | 
| 262 | 
            +
             | 
| 247 263 | 
             
                            # Check if agents have changed
         | 
| 248 264 | 
             
                            has_changed, deployment_hash = loader.has_agents_changed()
         | 
| 249 | 
            -
             | 
| 265 | 
            +
             | 
| 250 266 | 
             
                            # Determine if we should check dependencies
         | 
| 251 267 | 
             
                            should_check, check_reason = smart_checker.should_check_dependencies(
         | 
| 252 | 
            -
                                force_check=getattr(args,  | 
| 253 | 
            -
                                deployment_hash=deployment_hash
         | 
| 268 | 
            +
                                force_check=getattr(args, "force_check_dependencies", False),
         | 
| 269 | 
            +
                                deployment_hash=deployment_hash,
         | 
| 254 270 | 
             
                            )
         | 
| 255 | 
            -
             | 
| 271 | 
            +
             | 
| 256 272 | 
             
                            if should_check:
         | 
| 257 273 | 
             
                                # Check if we're in an environment where prompting makes sense
         | 
| 258 274 | 
             
                                can_prompt, prompt_reason = should_prompt_for_dependencies(
         | 
| 259 | 
            -
                                    force_prompt=getattr(args,  | 
| 260 | 
            -
                                    force_skip=getattr(args,  | 
| 275 | 
            +
                                    force_prompt=getattr(args, "force_prompt", False),
         | 
| 276 | 
            +
                                    force_skip=getattr(args, "no_prompt", False),
         | 
| 261 277 | 
             
                                )
         | 
| 262 | 
            -
             | 
| 278 | 
            +
             | 
| 263 279 | 
             
                                logger.debug(f"Dependency check needed: {check_reason}")
         | 
| 264 | 
            -
                                logger.debug( | 
| 265 | 
            -
             | 
| 280 | 
            +
                                logger.debug(
         | 
| 281 | 
            +
                                    f"Interactive prompting: {can_prompt} ({prompt_reason})"
         | 
| 282 | 
            +
                                )
         | 
| 283 | 
            +
             | 
| 266 284 | 
             
                                # Get or check dependencies
         | 
| 267 285 | 
             
                                results, was_cached = smart_checker.get_or_check_dependencies(
         | 
| 268 286 | 
             
                                    loader=loader,
         | 
| 269 | 
            -
                                    force_check=getattr(args,  | 
| 287 | 
            +
                                    force_check=getattr(args, "force_check_dependencies", False),
         | 
| 270 288 | 
             
                                )
         | 
| 271 | 
            -
             | 
| 289 | 
            +
             | 
| 272 290 | 
             
                                # Show summary if there are missing dependencies
         | 
| 273 | 
            -
                                if results[ | 
| 274 | 
            -
                                    missing_count = len(results[ | 
| 291 | 
            +
                                if results["summary"]["missing_python"]:
         | 
| 292 | 
            +
                                    missing_count = len(results["summary"]["missing_python"])
         | 
| 275 293 | 
             
                                    print(f"⚠️  {missing_count} agent dependencies missing")
         | 
| 276 | 
            -
             | 
| 294 | 
            +
             | 
| 277 295 | 
             
                                    if can_prompt and missing_count > 0:
         | 
| 278 296 | 
             
                                        # Interactive prompt for installation
         | 
| 279 297 | 
             
                                        print(f"\n📦 Missing dependencies detected:")
         | 
| 280 | 
            -
                                        for dep in results[ | 
| 298 | 
            +
                                        for dep in results["summary"]["missing_python"][:5]:
         | 
| 281 299 | 
             
                                            print(f"   - {dep}")
         | 
| 282 300 | 
             
                                        if missing_count > 5:
         | 
| 283 301 | 
             
                                            print(f"   ... and {missing_count - 5} more")
         | 
| 284 | 
            -
             | 
| 302 | 
            +
             | 
| 285 303 | 
             
                                        print("\nWould you like to install them now?")
         | 
| 286 304 | 
             
                                        print("  [y] Yes, install missing dependencies")
         | 
| 287 305 | 
             
                                        print("  [n] No, continue without installing")
         | 
| 288 306 | 
             
                                        print("  [q] Quit")
         | 
| 289 | 
            -
             | 
| 307 | 
            +
             | 
| 308 | 
            +
                                        sys.stdout.flush()  # Ensure prompt is displayed before input
         | 
| 309 | 
            +
             | 
| 310 | 
            +
                                        # Check if we're in a TTY environment for proper input handling
         | 
| 311 | 
            +
                                        if not sys.stdin.isatty():
         | 
| 312 | 
            +
                                            # In non-TTY environment (like pipes), use readline
         | 
| 313 | 
            +
                                            print("\nChoice [y/n/q]: ", end="", flush=True)
         | 
| 314 | 
            +
                                            try:
         | 
| 315 | 
            +
                                                response = sys.stdin.readline().strip().lower()
         | 
| 316 | 
            +
                                                # Handle various line endings and control characters
         | 
| 317 | 
            +
                                                response = (
         | 
| 318 | 
            +
                                                    response.replace("\r", "")
         | 
| 319 | 
            +
                                                    .replace("\n", "")
         | 
| 320 | 
            +
                                                    .strip()
         | 
| 321 | 
            +
                                                )
         | 
| 322 | 
            +
                                            except (EOFError, KeyboardInterrupt):
         | 
| 323 | 
            +
                                                response = "q"
         | 
| 324 | 
            +
                                        else:
         | 
| 325 | 
            +
                                            # In TTY environment, use normal input()
         | 
| 326 | 
            +
                                            try:
         | 
| 327 | 
            +
                                                response = (
         | 
| 328 | 
            +
                                                    input("\nChoice [y/n/q]: ").strip().lower()
         | 
| 329 | 
            +
                                                )
         | 
| 330 | 
            +
                                            except (EOFError, KeyboardInterrupt):
         | 
| 331 | 
            +
                                                response = "q"
         | 
| 332 | 
            +
             | 
| 290 333 | 
             
                                        try:
         | 
| 291 | 
            -
                                            response  | 
| 292 | 
            -
                                            if response == 'y':
         | 
| 334 | 
            +
                                            if response == "y":
         | 
| 293 335 | 
             
                                                print("\n🔧 Installing missing dependencies...")
         | 
| 294 336 | 
             
                                                loader.auto_install = True
         | 
| 295 | 
            -
                                                 | 
| 296 | 
            -
                                                     | 
| 337 | 
            +
                                                (
         | 
| 338 | 
            +
                                                    success,
         | 
| 339 | 
            +
                                                    error,
         | 
| 340 | 
            +
                                                ) = loader.install_missing_dependencies(
         | 
| 341 | 
            +
                                                    results["summary"]["missing_python"]
         | 
| 297 342 | 
             
                                                )
         | 
| 298 343 | 
             
                                                if success:
         | 
| 299 344 | 
             
                                                    print("✅ Dependencies installed successfully")
         | 
| @@ -301,74 +346,91 @@ def run_session(args): | |
| 301 346 | 
             
                                                    smart_checker.cache.invalidate(deployment_hash)
         | 
| 302 347 | 
             
                                                else:
         | 
| 303 348 | 
             
                                                    print(f"❌ Installation failed: {error}")
         | 
| 304 | 
            -
                                            elif response ==  | 
| 349 | 
            +
                                            elif response == "q":
         | 
| 305 350 | 
             
                                                print("👋 Exiting...")
         | 
| 306 351 | 
             
                                                return
         | 
| 307 352 | 
             
                                            else:
         | 
| 308 | 
            -
                                                print( | 
| 353 | 
            +
                                                print(
         | 
| 354 | 
            +
                                                    "⏩ Continuing without installing dependencies"
         | 
| 355 | 
            +
                                                )
         | 
| 309 356 | 
             
                                        except (EOFError, KeyboardInterrupt):
         | 
| 310 357 | 
             
                                            print("\n⏩ Continuing without installing dependencies")
         | 
| 311 358 | 
             
                                    else:
         | 
| 312 359 | 
             
                                        # Non-interactive environment or prompting disabled
         | 
| 313 | 
            -
                                        print( | 
| 360 | 
            +
                                        print(
         | 
| 361 | 
            +
                                            "   Run 'pip install \"claude-mpm[agents]\"' to install all agent dependencies"
         | 
| 362 | 
            +
                                        )
         | 
| 314 363 | 
             
                                        if not can_prompt:
         | 
| 315 | 
            -
                                            logger.debug( | 
| 364 | 
            +
                                            logger.debug(
         | 
| 365 | 
            +
                                                f"Not prompting for installation: {prompt_reason}"
         | 
| 366 | 
            +
                                            )
         | 
| 316 367 | 
             
                                elif was_cached:
         | 
| 317 368 | 
             
                                    logger.debug("Dependencies satisfied (cached result)")
         | 
| 318 369 | 
             
                                else:
         | 
| 319 370 | 
             
                                    logger.debug("All dependencies satisfied")
         | 
| 320 371 | 
             
                            else:
         | 
| 321 372 | 
             
                                logger.debug(f"Skipping dependency check: {check_reason}")
         | 
| 322 | 
            -
             | 
| 373 | 
            +
             | 
| 323 374 | 
             
                        except Exception as e:
         | 
| 324 375 | 
             
                            if args.logging != LogLevel.OFF.value:
         | 
| 325 376 | 
             
                                logger.debug(f"Could not check agent dependencies: {e}")
         | 
| 326 377 | 
             
                            # Continue anyway - don't block execution
         | 
| 327 | 
            -
             | 
| 378 | 
            +
             | 
| 328 379 | 
             
                # Create simple runner
         | 
| 329 380 | 
             
                enable_tickets = not args.no_tickets
         | 
| 330 | 
            -
                raw_claude_args = getattr(args,  | 
| 381 | 
            +
                raw_claude_args = getattr(args, "claude_args", []) or []
         | 
| 331 382 | 
             
                # Filter out claude-mpm specific flags before passing to Claude CLI
         | 
| 332 383 | 
             
                claude_args = filter_claude_mpm_args(raw_claude_args)
         | 
| 333 | 
            -
                monitor_mode = getattr(args,  | 
| 334 | 
            -
             | 
| 384 | 
            +
                monitor_mode = getattr(args, "monitor", False)
         | 
| 385 | 
            +
             | 
| 335 386 | 
             
                # Debug logging for argument filtering
         | 
| 336 387 | 
             
                if raw_claude_args != claude_args:
         | 
| 337 | 
            -
                    logger.debug( | 
| 388 | 
            +
                    logger.debug(
         | 
| 389 | 
            +
                        f"Filtered claude-mpm args: {set(raw_claude_args) - set(claude_args)}"
         | 
| 390 | 
            +
                    )
         | 
| 338 391 | 
             
                    logger.debug(f"Passing to Claude CLI: {claude_args}")
         | 
| 339 | 
            -
             | 
| 392 | 
            +
             | 
| 340 393 | 
             
                # Use the specified launch method (default: exec)
         | 
| 341 | 
            -
                launch_method = getattr(args,  | 
| 342 | 
            -
             | 
| 343 | 
            -
                enable_websocket = getattr(args,  | 
| 344 | 
            -
                websocket_port = getattr(args,  | 
| 345 | 
            -
             | 
| 394 | 
            +
                launch_method = getattr(args, "launch_method", "exec")
         | 
| 395 | 
            +
             | 
| 396 | 
            +
                enable_websocket = getattr(args, "monitor", False) or monitor_mode
         | 
| 397 | 
            +
                websocket_port = getattr(args, "websocket_port", 8765)
         | 
| 398 | 
            +
             | 
| 346 399 | 
             
                # Display Socket.IO server info if enabled
         | 
| 347 400 | 
             
                if enable_websocket:
         | 
| 348 401 | 
             
                    # Auto-install Socket.IO dependencies if needed
         | 
| 349 402 | 
             
                    print("🔧 Checking Socket.IO dependencies...")
         | 
| 350 403 | 
             
                    dependencies_ok, error_msg = ensure_socketio_dependencies(logger)
         | 
| 351 | 
            -
             | 
| 404 | 
            +
             | 
| 352 405 | 
             
                    if not dependencies_ok:
         | 
| 353 406 | 
             
                        print(f"❌ Failed to install Socket.IO dependencies: {error_msg}")
         | 
| 354 | 
            -
                        print( | 
| 407 | 
            +
                        print(
         | 
| 408 | 
            +
                            "  Please install manually: pip install python-socketio aiohttp python-engineio"
         | 
| 409 | 
            +
                        )
         | 
| 355 410 | 
             
                        print("  Or install with extras: pip install claude-mpm[monitor]")
         | 
| 356 411 | 
             
                        # Continue anyway - some functionality might still work
         | 
| 357 412 | 
             
                    else:
         | 
| 358 413 | 
             
                        print("✓ Socket.IO dependencies ready")
         | 
| 359 | 
            -
             | 
| 414 | 
            +
             | 
| 360 415 | 
             
                    try:
         | 
| 361 416 | 
             
                        import socketio
         | 
| 417 | 
            +
             | 
| 362 418 | 
             
                        print(f"✓ Socket.IO server enabled at http://localhost:{websocket_port}")
         | 
| 363 419 | 
             
                        if launch_method == "exec":
         | 
| 364 | 
            -
                            print( | 
| 365 | 
            -
             | 
| 420 | 
            +
                            print(
         | 
| 421 | 
            +
                                "  Note: Socket.IO monitoring using exec mode with Claude Code hooks"
         | 
| 422 | 
            +
                            )
         | 
| 423 | 
            +
             | 
| 366 424 | 
             
                        # Launch Socket.IO dashboard if in monitor mode
         | 
| 367 425 | 
             
                        if monitor_mode:
         | 
| 368 | 
            -
                            success, browser_opened = launch_socketio_monitor( | 
| 426 | 
            +
                            success, browser_opened = launch_socketio_monitor(
         | 
| 427 | 
            +
                                websocket_port, logger
         | 
| 428 | 
            +
                            )
         | 
| 369 429 | 
             
                            if not success:
         | 
| 370 430 | 
             
                                print(f"⚠️  Failed to launch Socket.IO monitor")
         | 
| 371 | 
            -
                                print( | 
| 431 | 
            +
                                print(
         | 
| 432 | 
            +
                                    f"  You can manually run: python scripts/launch_socketio_dashboard.py --port {websocket_port}"
         | 
| 433 | 
            +
                                )
         | 
| 372 434 | 
             
                            # Store whether browser was opened by CLI for coordination with ClaudeRunner
         | 
| 373 435 | 
             
                            args._browser_opened_by_cli = browser_opened
         | 
| 374 436 | 
             
                    except ImportError as e:
         | 
| @@ -376,33 +438,35 @@ def run_session(args): | |
| 376 438 | 
             
                        print("  This might be a virtual environment issue.")
         | 
| 377 439 | 
             
                        print("  Try: pip install python-socketio aiohttp python-engineio")
         | 
| 378 440 | 
             
                        print("  Or: pip install claude-mpm[monitor]")
         | 
| 379 | 
            -
             | 
| 441 | 
            +
             | 
| 380 442 | 
             
                runner = ClaudeRunner(
         | 
| 381 443 | 
             
                    enable_tickets=enable_tickets,
         | 
| 382 444 | 
             
                    log_level=args.logging,
         | 
| 383 445 | 
             
                    claude_args=claude_args,
         | 
| 384 446 | 
             
                    launch_method=launch_method,
         | 
| 385 447 | 
             
                    enable_websocket=enable_websocket,
         | 
| 386 | 
            -
                    websocket_port=websocket_port
         | 
| 448 | 
            +
                    websocket_port=websocket_port,
         | 
| 387 449 | 
             
                )
         | 
| 388 | 
            -
             | 
| 389 | 
            -
                # Agent deployment is handled by ClaudeRunner.setup_agents() and | 
| 450 | 
            +
             | 
| 451 | 
            +
                # Agent deployment is handled by ClaudeRunner.setup_agents() and
         | 
| 390 452 | 
             
                # ClaudeRunner.deploy_project_agents_to_claude() which are called
         | 
| 391 453 | 
             
                # in both run_interactive() and run_oneshot() methods.
         | 
| 392 454 | 
             
                # No need for redundant deployment here.
         | 
| 393 | 
            -
             | 
| 455 | 
            +
             | 
| 394 456 | 
             
                # Set browser opening flag for monitor mode
         | 
| 395 457 | 
             
                if monitor_mode:
         | 
| 396 458 | 
             
                    runner._should_open_monitor_browser = True
         | 
| 397 459 | 
             
                    # Pass information about whether we already opened the browser in run.py
         | 
| 398 | 
            -
                    runner._browser_opened_by_cli = getattr(args,  | 
| 399 | 
            -
             | 
| 460 | 
            +
                    runner._browser_opened_by_cli = getattr(args, "_browser_opened_by_cli", False)
         | 
| 461 | 
            +
             | 
| 400 462 | 
             
                # Create context - use resumed session context if available
         | 
| 401 463 | 
             
                if resume_session_id and resume_context:
         | 
| 402 464 | 
             
                    # For resumed sessions, create enhanced context with session information
         | 
| 403 465 | 
             
                    context = create_session_context(resume_session_id, session_manager)
         | 
| 404 466 | 
             
                    # Update session usage
         | 
| 405 | 
            -
                    session_manager.active_sessions[resume_session_id][ | 
| 467 | 
            +
                    session_manager.active_sessions[resume_session_id][
         | 
| 468 | 
            +
                        "last_used"
         | 
| 469 | 
            +
                    ] = datetime.now().isoformat()
         | 
| 406 470 | 
             
                    session_manager.active_sessions[resume_session_id]["use_count"] += 1
         | 
| 407 471 | 
             
                    session_manager._save_sessions()
         | 
| 408 472 | 
             
                else:
         | 
| @@ -410,14 +474,14 @@ def run_session(args): | |
| 410 474 | 
             
                    new_session_id = session_manager.create_session("default")
         | 
| 411 475 | 
             
                    context = create_simple_context()
         | 
| 412 476 | 
             
                    logger.info(f"Created new session {new_session_id}")
         | 
| 413 | 
            -
             | 
| 477 | 
            +
             | 
| 414 478 | 
             
                # For monitor mode, we handled everything in launch_socketio_monitor
         | 
| 415 479 | 
             
                # No need for ClaudeRunner browser delegation
         | 
| 416 480 | 
             
                if monitor_mode:
         | 
| 417 481 | 
             
                    # Clear any browser opening flags since we handled it completely
         | 
| 418 482 | 
             
                    runner._should_open_monitor_browser = False
         | 
| 419 483 | 
             
                    runner._browser_opened_by_cli = True  # Prevent duplicate opening
         | 
| 420 | 
            -
             | 
| 484 | 
            +
             | 
| 421 485 | 
             
                # Run session based on mode
         | 
| 422 486 | 
             
                if args.non_interactive or args.input:
         | 
| 423 487 | 
             
                    # Non-interactive mode
         | 
| @@ -427,298 +491,109 @@ def run_session(args): | |
| 427 491 | 
             
                        logger.error("Session failed")
         | 
| 428 492 | 
             
                else:
         | 
| 429 493 | 
             
                    # Interactive mode
         | 
| 430 | 
            -
                    if getattr(args,  | 
| 431 | 
            -
                        # Use the interactive wrapper for command interception
         | 
| 432 | 
            -
                        # WHY: Command interception requires special handling of stdin/stdout
         | 
| 433 | 
            -
                        # which is better done in a separate Python script
         | 
| 494 | 
            +
                    if getattr(args, "intercept_commands", False):
         | 
| 434 495 | 
             
                        wrapper_path = get_scripts_dir() / "interactive_wrapper.py"
         | 
| 435 496 | 
             
                        if wrapper_path.exists():
         | 
| 436 497 | 
             
                            print("Starting interactive session with command interception...")
         | 
| 437 498 | 
             
                            subprocess.run([sys.executable, str(wrapper_path)])
         | 
| 438 499 | 
             
                        else:
         | 
| 439 | 
            -
                            logger.warning( | 
| 500 | 
            +
                            logger.warning(
         | 
| 501 | 
            +
                                "Interactive wrapper not found, falling back to normal mode"
         | 
| 502 | 
            +
                            )
         | 
| 440 503 | 
             
                            runner.run_interactive(context)
         | 
| 441 504 | 
             
                    else:
         | 
| 442 505 | 
             
                        runner.run_interactive(context)
         | 
| 443 506 |  | 
| 444 507 |  | 
| 445 508 | 
             
            def launch_socketio_monitor(port, logger):
         | 
| 446 | 
            -
                """
         | 
| 447 | 
            -
                 | 
| 448 | 
            -
             | 
| 449 | 
            -
                 | 
| 450 | 
            -
                 | 
| 451 | 
            -
             | 
| 452 | 
            -
             | 
| 453 | 
            -
             | 
| 454 | 
            -
                Pass the server port as a URL parameter so the dashboard knows which port to connect to.
         | 
| 455 | 
            -
                This decouples the dashboard from the server serving and makes it more robust.
         | 
| 456 | 
            -
                
         | 
| 457 | 
            -
                Args:
         | 
| 458 | 
            -
                    port: Port number for the Socket.IO server
         | 
| 459 | 
            -
                    logger: Logger instance for output
         | 
| 460 | 
            -
                    
         | 
| 461 | 
            -
                Returns:
         | 
| 462 | 
            -
                    tuple: (success: bool, browser_opened: bool) - success status and whether browser was opened
         | 
| 463 | 
            -
                """
         | 
| 464 | 
            -
                try:
         | 
| 465 | 
            -
                    # Verify Socket.IO dependencies are available
         | 
| 466 | 
            -
                    try:
         | 
| 467 | 
            -
                        import socketio
         | 
| 468 | 
            -
                        import aiohttp
         | 
| 469 | 
            -
                        import engineio
         | 
| 470 | 
            -
                        logger.debug("Socket.IO dependencies verified")
         | 
| 471 | 
            -
                    except ImportError as e:
         | 
| 472 | 
            -
                        logger.error(f"Socket.IO dependencies not available: {e}")
         | 
| 473 | 
            -
                        print(f"❌ Socket.IO dependencies missing: {e}")
         | 
| 474 | 
            -
                        print("  This is unexpected - dependency installation may have failed.")
         | 
| 475 | 
            -
                        return False, False
         | 
| 476 | 
            -
                    
         | 
| 477 | 
            -
                    print(f"🚀 Setting up Socket.IO monitor on port {port}...")
         | 
| 478 | 
            -
                    logger.info(f"Launching Socket.IO monitor on port {port}")
         | 
| 479 | 
            -
                    
         | 
| 480 | 
            -
                    socketio_port = port
         | 
| 481 | 
            -
                    
         | 
| 482 | 
            -
                    # Use HTTP URL to access dashboard from Socket.IO server
         | 
| 483 | 
            -
                    dashboard_url = f'http://localhost:{socketio_port}'
         | 
| 484 | 
            -
                    
         | 
| 485 | 
            -
                    # Check if Socket.IO server is already running
         | 
| 486 | 
            -
                    server_running = _check_socketio_server_running(socketio_port, logger)
         | 
| 487 | 
            -
                    
         | 
| 488 | 
            -
                    if server_running:
         | 
| 489 | 
            -
                        print(f"✅ Socket.IO server already running on port {socketio_port}")
         | 
| 490 | 
            -
                        
         | 
| 491 | 
            -
                        # Check if it's managed by our daemon
         | 
| 492 | 
            -
                        daemon_script = get_package_root() / "scripts" / "socketio_daemon.py"
         | 
| 493 | 
            -
                        if daemon_script.exists():
         | 
| 494 | 
            -
                            status_result = subprocess.run(
         | 
| 495 | 
            -
                                [sys.executable, str(daemon_script), "status"],
         | 
| 496 | 
            -
                                capture_output=True,
         | 
| 497 | 
            -
                                text=True
         | 
| 498 | 
            -
                            )
         | 
| 499 | 
            -
                            if "is running" in status_result.stdout:
         | 
| 500 | 
            -
                                print(f"   (Managed by Python daemon)")
         | 
| 501 | 
            -
                        
         | 
| 502 | 
            -
                        print(f"📊 Dashboard: {dashboard_url}")
         | 
| 503 | 
            -
                        
         | 
| 504 | 
            -
                        # Open browser with static HTML file
         | 
| 505 | 
            -
                        try:
         | 
| 506 | 
            -
                            # Check if we should suppress browser opening (for tests)
         | 
| 507 | 
            -
                            if os.environ.get('CLAUDE_MPM_NO_BROWSER') != '1':
         | 
| 508 | 
            -
                                print(f"🌐 Opening dashboard in browser...")
         | 
| 509 | 
            -
                                open_in_browser_tab(dashboard_url, logger)
         | 
| 510 | 
            -
                                logger.info(f"Socket.IO dashboard opened: {dashboard_url}")
         | 
| 511 | 
            -
                            else:
         | 
| 512 | 
            -
                                print(f"🌐 Browser opening suppressed (CLAUDE_MPM_NO_BROWSER=1)")
         | 
| 513 | 
            -
                                logger.info(f"Browser opening suppressed by environment variable")
         | 
| 514 | 
            -
                            return True, True
         | 
| 515 | 
            -
                        except Exception as e:
         | 
| 516 | 
            -
                            logger.warning(f"Failed to open browser: {e}")
         | 
| 517 | 
            -
                            print(f"⚠️  Could not open browser automatically")
         | 
| 518 | 
            -
                            print(f"📊 Please open manually: {dashboard_url}")
         | 
| 519 | 
            -
                            return True, False
         | 
| 520 | 
            -
                    else:
         | 
| 521 | 
            -
                        # Start standalone Socket.IO server
         | 
| 522 | 
            -
                        print(f"🔧 Starting Socket.IO server on port {socketio_port}...")
         | 
| 523 | 
            -
                        server_started = _start_standalone_socketio_server(socketio_port, logger)
         | 
| 524 | 
            -
                        
         | 
| 525 | 
            -
                        if server_started:
         | 
| 526 | 
            -
                            print(f"✅ Socket.IO server started successfully")
         | 
| 527 | 
            -
                            print(f"📊 Dashboard: {dashboard_url}")
         | 
| 528 | 
            -
                            
         | 
| 529 | 
            -
                            # Final verification that server is responsive using event-based checking
         | 
| 530 | 
            -
                            final_check_passed = False
         | 
| 531 | 
            -
                            check_start = time.time()
         | 
| 532 | 
            -
                            max_wait = 3  # Maximum 3 seconds
         | 
| 533 | 
            -
                            
         | 
| 534 | 
            -
                            while time.time() - check_start < max_wait:
         | 
| 535 | 
            -
                                if _check_socketio_server_running(socketio_port, logger):
         | 
| 536 | 
            -
                                    final_check_passed = True
         | 
| 537 | 
            -
                                    break
         | 
| 538 | 
            -
                                # Use a very short sleep just to yield CPU
         | 
| 539 | 
            -
                                time.sleep(0.1)  # 100ms polling interval
         | 
| 540 | 
            -
                            
         | 
| 541 | 
            -
                            if not final_check_passed:
         | 
| 542 | 
            -
                                logger.warning("Server started but final connectivity check failed")
         | 
| 543 | 
            -
                                print(f"⚠️  Server may still be initializing. Dashboard should work once fully ready.")
         | 
| 544 | 
            -
                            
         | 
| 545 | 
            -
                            # Open browser with static HTML file
         | 
| 546 | 
            -
                            try:
         | 
| 547 | 
            -
                                # Check if we should suppress browser opening (for tests)
         | 
| 548 | 
            -
                                if os.environ.get('CLAUDE_MPM_NO_BROWSER') != '1':
         | 
| 549 | 
            -
                                    print(f"🌐 Opening dashboard in browser...")
         | 
| 550 | 
            -
                                    open_in_browser_tab(dashboard_url, logger)
         | 
| 551 | 
            -
                                    logger.info(f"Socket.IO dashboard opened: {dashboard_url}")
         | 
| 552 | 
            -
                                else:
         | 
| 553 | 
            -
                                    print(f"🌐 Browser opening suppressed (CLAUDE_MPM_NO_BROWSER=1)")
         | 
| 554 | 
            -
                                    logger.info(f"Browser opening suppressed by environment variable")
         | 
| 555 | 
            -
                                return True, True
         | 
| 556 | 
            -
                            except Exception as e:
         | 
| 557 | 
            -
                                logger.warning(f"Failed to open browser: {e}")
         | 
| 558 | 
            -
                                print(f"⚠️  Could not open browser automatically")
         | 
| 559 | 
            -
                                print(f"📊 Please open manually: {dashboard_url}")
         | 
| 560 | 
            -
                                return True, False
         | 
| 561 | 
            -
                        else:
         | 
| 562 | 
            -
                            print(f"❌ Failed to start Socket.IO server")
         | 
| 563 | 
            -
                            print(f"💡 Troubleshooting tips:")
         | 
| 564 | 
            -
                            print(f"   - Check if port {socketio_port} is already in use")
         | 
| 565 | 
            -
                            print(f"   - Verify Socket.IO dependencies: pip install python-socketio aiohttp")
         | 
| 566 | 
            -
                            print(f"   - Try a different port with --websocket-port")
         | 
| 567 | 
            -
                            return False, False
         | 
| 568 | 
            -
                    
         | 
| 569 | 
            -
                except Exception as e:
         | 
| 570 | 
            -
                    logger.error(f"Failed to launch Socket.IO monitor: {e}")
         | 
| 571 | 
            -
                    print(f"❌ Failed to launch Socket.IO monitor: {e}")
         | 
| 572 | 
            -
                    return False, False
         | 
| 509 | 
            +
                """Launch the Socket.IO monitoring dashboard."""
         | 
| 510 | 
            +
                from .socketio_monitor import SocketIOMonitor
         | 
| 511 | 
            +
             | 
| 512 | 
            +
                monitor = SocketIOMonitor(logger)
         | 
| 513 | 
            +
                return monitor.launch_monitor(port)
         | 
| 514 | 
            +
             | 
| 515 | 
            +
             | 
| 516 | 
            +
            # Socket.IO monitoring functions moved to socketio_monitor.py
         | 
| 573 517 |  | 
| 574 518 |  | 
| 575 519 | 
             
            def _check_socketio_server_running(port, logger):
         | 
| 576 | 
            -
                """
         | 
| 577 | 
            -
                 | 
| 578 | 
            -
             | 
| 579 | 
            -
                 | 
| 580 | 
            -
                 | 
| 581 | 
            -
                
         | 
| 582 | 
            -
                DESIGN DECISION: We try multiple endpoints and connection methods to ensure
         | 
| 583 | 
            -
                robust detection. Some servers may be starting up and only partially ready.
         | 
| 584 | 
            -
                Added retry logic to handle race conditions during server initialization.
         | 
| 585 | 
            -
                
         | 
| 586 | 
            -
                Args:
         | 
| 587 | 
            -
                    port: Port number to check
         | 
| 588 | 
            -
                    logger: Logger instance for output
         | 
| 589 | 
            -
                    
         | 
| 590 | 
            -
                Returns:
         | 
| 591 | 
            -
                    bool: True if server is running and responding, False otherwise
         | 
| 592 | 
            -
                """
         | 
| 593 | 
            -
                try:
         | 
| 594 | 
            -
                    import urllib.request
         | 
| 595 | 
            -
                    import urllib.error
         | 
| 596 | 
            -
                    import socket
         | 
| 597 | 
            -
                    
         | 
| 598 | 
            -
                    # First, do a basic TCP connection check
         | 
| 599 | 
            -
                    try:
         | 
| 600 | 
            -
                        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
         | 
| 601 | 
            -
                            s.settimeout(2.0)  # Increased from 1.0s for slower connections
         | 
| 602 | 
            -
                            result = s.connect_ex(('127.0.0.1', port))
         | 
| 603 | 
            -
                            if result != 0:
         | 
| 604 | 
            -
                                logger.debug(f"TCP connection to port {port} failed (server not started yet)")
         | 
| 605 | 
            -
                                return False
         | 
| 606 | 
            -
                    except Exception as e:
         | 
| 607 | 
            -
                        logger.debug(f"TCP socket check failed for port {port}: {e}")
         | 
| 608 | 
            -
                        return False
         | 
| 609 | 
            -
                    
         | 
| 610 | 
            -
                    # If TCP connection succeeds, try HTTP health check with retries
         | 
| 611 | 
            -
                    # WHY: Even when TCP is accepting connections, the HTTP handler may not be ready
         | 
| 612 | 
            -
                    max_retries = 3
         | 
| 613 | 
            -
                    for retry in range(max_retries):
         | 
| 614 | 
            -
                        try:
         | 
| 615 | 
            -
                            response = urllib.request.urlopen(f'http://localhost:{port}/status', timeout=10)  # Increased from 5s to 10s
         | 
| 616 | 
            -
                            
         | 
| 617 | 
            -
                            if response.getcode() == 200:
         | 
| 618 | 
            -
                                content = response.read().decode()
         | 
| 619 | 
            -
                                logger.debug(f"✅ Socket.IO server health check passed on port {port} (attempt {retry + 1})")
         | 
| 620 | 
            -
                                logger.debug(f"📄 Server response: {content[:100]}...")
         | 
| 621 | 
            -
                                return True
         | 
| 622 | 
            -
                            else:
         | 
| 623 | 
            -
                                logger.debug(f"⚠️ HTTP response code {response.getcode()} from port {port} (attempt {retry + 1})")
         | 
| 624 | 
            -
                                if retry < max_retries - 1:
         | 
| 625 | 
            -
                                    # Use exponential backoff with shorter initial delay
         | 
| 626 | 
            -
                                    backoff = min(0.1 * (2 ** retry), 1.0)  # 0.1s, 0.2s, 0.4s...
         | 
| 627 | 
            -
                                    time.sleep(backoff)
         | 
| 628 | 
            -
                                
         | 
| 629 | 
            -
                        except urllib.error.HTTPError as e:
         | 
| 630 | 
            -
                            logger.debug(f"⚠️ HTTP error {e.code} from server on port {port} (attempt {retry + 1})")
         | 
| 631 | 
            -
                            if retry < max_retries - 1 and e.code in [404, 503]:  # Server starting but not ready
         | 
| 632 | 
            -
                                logger.debug("Server appears to be starting, retrying...")
         | 
| 633 | 
            -
                                # Use exponential backoff for retries
         | 
| 634 | 
            -
                                backoff = min(0.1 * (2 ** retry), 1.0)
         | 
| 635 | 
            -
                                time.sleep(backoff)
         | 
| 636 | 
            -
                                continue
         | 
| 637 | 
            -
                            return False
         | 
| 638 | 
            -
                        except urllib.error.URLError as e:
         | 
| 639 | 
            -
                            logger.debug(f"⚠️ URL error connecting to port {port} (attempt {retry + 1}): {e.reason}")
         | 
| 640 | 
            -
                            if retry < max_retries - 1:
         | 
| 641 | 
            -
                                logger.debug("Connection refused - server may still be initializing, retrying...")
         | 
| 642 | 
            -
                                # Use exponential backoff for retries
         | 
| 643 | 
            -
                                backoff = min(0.1 * (2 ** retry), 1.0)
         | 
| 644 | 
            -
                                time.sleep(backoff)
         | 
| 645 | 
            -
                                continue
         | 
| 646 | 
            -
                            return False
         | 
| 647 | 
            -
                    
         | 
| 648 | 
            -
                    # All retries exhausted
         | 
| 649 | 
            -
                    logger.debug(f"Health check failed after {max_retries} attempts - server not fully ready")
         | 
| 650 | 
            -
                    return False
         | 
| 651 | 
            -
                        
         | 
| 652 | 
            -
                except (ConnectionError, OSError) as e:
         | 
| 653 | 
            -
                    logger.debug(f"🔌 Connection error checking port {port}: {e}")
         | 
| 654 | 
            -
                except Exception as e:
         | 
| 655 | 
            -
                    logger.debug(f"❌ Unexpected error checking Socket.IO server on port {port}: {e}")
         | 
| 656 | 
            -
                
         | 
| 657 | 
            -
                return False
         | 
| 520 | 
            +
                """Check if a Socket.IO server is running on the specified port."""
         | 
| 521 | 
            +
                from .socketio_monitor import SocketIOMonitor
         | 
| 522 | 
            +
             | 
| 523 | 
            +
                monitor = SocketIOMonitor(logger)
         | 
| 524 | 
            +
                return monitor.check_server_running(port)
         | 
| 658 525 |  | 
| 659 526 |  | 
| 660 527 | 
             
            def _start_standalone_socketio_server(port, logger):
         | 
| 528 | 
            +
                """Start a standalone Socket.IO server using the Python daemon."""
         | 
| 529 | 
            +
                from .socketio_monitor import SocketIOMonitor
         | 
| 530 | 
            +
             | 
| 531 | 
            +
                monitor = SocketIOMonitor(logger)
         | 
| 532 | 
            +
                return monitor.start_standalone_server(port)
         | 
| 661 533 | 
             
                """
         | 
| 662 534 | 
             
                Start a standalone Socket.IO server using the Python daemon.
         | 
| 663 | 
            -
             | 
| 535 | 
            +
             | 
| 664 536 | 
             
                WHY: For monitor mode, we want a persistent server that runs independently
         | 
| 665 537 | 
             
                of the Claude session. This allows users to monitor multiple sessions and
         | 
| 666 538 | 
             
                keeps the dashboard available even when Claude isn't running.
         | 
| 667 | 
            -
             | 
| 539 | 
            +
             | 
| 668 540 | 
             
                DESIGN DECISION: We use a pure Python daemon script to manage the server
         | 
| 669 541 | 
             
                process. This avoids Node.js dependencies (like PM2) and provides proper
         | 
| 670 542 | 
             
                process management with PID tracking.
         | 
| 671 | 
            -
             | 
| 543 | 
            +
             | 
| 672 544 | 
             
                Args:
         | 
| 673 545 | 
             
                    port: Port number for the server
         | 
| 674 546 | 
             
                    logger: Logger instance for output
         | 
| 675 | 
            -
             | 
| 547 | 
            +
             | 
| 676 548 | 
             
                Returns:
         | 
| 677 549 | 
             
                    bool: True if server started successfully, False otherwise
         | 
| 678 550 | 
             
                """
         | 
| 679 551 | 
             
                try:
         | 
| 680 | 
            -
                    from ...deployment_paths import get_scripts_dir
         | 
| 681 552 | 
             
                    import subprocess
         | 
| 682 | 
            -
             | 
| 553 | 
            +
             | 
| 554 | 
            +
                    from ...core.unified_paths import get_scripts_dir
         | 
| 555 | 
            +
             | 
| 683 556 | 
             
                    # Get path to daemon script in package
         | 
| 684 557 | 
             
                    daemon_script = get_package_root() / "scripts" / "socketio_daemon.py"
         | 
| 685 | 
            -
             | 
| 558 | 
            +
             | 
| 686 559 | 
             
                    if not daemon_script.exists():
         | 
| 687 560 | 
             
                        logger.error(f"Socket.IO daemon script not found: {daemon_script}")
         | 
| 688 561 | 
             
                        return False
         | 
| 689 | 
            -
             | 
| 562 | 
            +
             | 
| 690 563 | 
             
                    logger.info(f"Starting Socket.IO server daemon on port {port}")
         | 
| 691 | 
            -
             | 
| 564 | 
            +
             | 
| 692 565 | 
             
                    # Start the daemon
         | 
| 693 566 | 
             
                    result = subprocess.run(
         | 
| 694 567 | 
             
                        [sys.executable, str(daemon_script), "start"],
         | 
| 695 568 | 
             
                        capture_output=True,
         | 
| 696 | 
            -
                        text=True
         | 
| 569 | 
            +
                        text=True,
         | 
| 697 570 | 
             
                    )
         | 
| 698 | 
            -
             | 
| 571 | 
            +
             | 
| 699 572 | 
             
                    if result.returncode != 0:
         | 
| 700 573 | 
             
                        logger.error(f"Failed to start Socket.IO daemon: {result.stderr}")
         | 
| 701 574 | 
             
                        return False
         | 
| 702 | 
            -
             | 
| 575 | 
            +
             | 
| 703 576 | 
             
                    # Wait for server using event-based polling instead of fixed delays
         | 
| 704 577 | 
             
                    # WHY: Replace fixed sleep delays with active polling for faster startup detection
         | 
| 705 578 | 
             
                    max_wait_time = 15  # Maximum 15 seconds
         | 
| 706 579 | 
             
                    poll_interval = 0.1  # Start with 100ms polling
         | 
| 707 | 
            -
             | 
| 580 | 
            +
             | 
| 708 581 | 
             
                    logger.info(f"Waiting up to {max_wait_time} seconds for server to be ready...")
         | 
| 709 | 
            -
             | 
| 582 | 
            +
             | 
| 710 583 | 
             
                    # Give daemon minimal time to fork
         | 
| 711 584 | 
             
                    time.sleep(0.2)  # Reduced from 0.5s
         | 
| 712 | 
            -
             | 
| 585 | 
            +
             | 
| 713 586 | 
             
                    start_time = time.time()
         | 
| 714 587 | 
             
                    attempt = 0
         | 
| 715 | 
            -
             | 
| 588 | 
            +
             | 
| 716 589 | 
             
                    while time.time() - start_time < max_wait_time:
         | 
| 717 590 | 
             
                        attempt += 1
         | 
| 718 591 | 
             
                        elapsed = time.time() - start_time
         | 
| 719 | 
            -
             | 
| 720 | 
            -
                        logger.debug( | 
| 721 | 
            -
             | 
| 592 | 
            +
             | 
| 593 | 
            +
                        logger.debug(
         | 
| 594 | 
            +
                            f"Checking server readiness (attempt {attempt}, elapsed {elapsed:.1f}s)"
         | 
| 595 | 
            +
                        )
         | 
| 596 | 
            +
             | 
| 722 597 | 
             
                        # Adaptive polling - start fast, slow down over time
         | 
| 723 598 | 
             
                        if elapsed < 2:
         | 
| 724 599 | 
             
                            poll_interval = 0.1  # 100ms for first 2 seconds
         | 
| @@ -726,48 +601,65 @@ def _start_standalone_socketio_server(port, logger): | |
| 726 601 | 
             
                            poll_interval = 0.25  # 250ms for next 3 seconds
         | 
| 727 602 | 
             
                        else:
         | 
| 728 603 | 
             
                            poll_interval = 0.5  # 500ms after 5 seconds
         | 
| 729 | 
            -
             | 
| 604 | 
            +
             | 
| 730 605 | 
             
                        time.sleep(poll_interval)
         | 
| 731 | 
            -
             | 
| 606 | 
            +
             | 
| 732 607 | 
             
                        # Check if the daemon server is accepting connections
         | 
| 733 608 | 
             
                        if _check_socketio_server_running(port, logger):
         | 
| 734 | 
            -
                            logger.info( | 
| 609 | 
            +
                            logger.info(
         | 
| 610 | 
            +
                                f"✅ Standalone Socket.IO server started successfully on port {port}"
         | 
| 611 | 
            +
                            )
         | 
| 735 612 | 
             
                            logger.info(f"🕐 Server ready after {attempt} attempts ({elapsed:.1f}s)")
         | 
| 736 613 | 
             
                            return True
         | 
| 737 614 | 
             
                        else:
         | 
| 738 | 
            -
                            logger.debug( | 
| 739 | 
            -
             | 
| 615 | 
            +
                            logger.debug(
         | 
| 616 | 
            +
                                f"Server not yet accepting connections on attempt {attempt}"
         | 
| 617 | 
            +
                            )
         | 
| 618 | 
            +
             | 
| 740 619 | 
             
                    # Timeout reached
         | 
| 741 620 | 
             
                    elapsed_total = time.time() - start_time
         | 
| 742 | 
            -
                    logger.error( | 
| 743 | 
            -
             | 
| 744 | 
            -
                     | 
| 621 | 
            +
                    logger.error(
         | 
| 622 | 
            +
                        f"❌ Socket.IO server health check failed after {max_wait_time}s timeout ({attempt} attempts)"
         | 
| 623 | 
            +
                    )
         | 
| 624 | 
            +
                    logger.warning(
         | 
| 625 | 
            +
                        f"⏱️  Server may still be starting - try waiting a few more seconds"
         | 
| 626 | 
            +
                    )
         | 
| 627 | 
            +
                    logger.warning(
         | 
| 628 | 
            +
                        f"💡 The daemon process might be running but not yet accepting HTTP connections"
         | 
| 629 | 
            +
                    )
         | 
| 745 630 | 
             
                    logger.error(f"🔧 Troubleshooting steps:")
         | 
| 746 631 | 
             
                    logger.error(f"   - Wait a few more seconds and try again")
         | 
| 747 632 | 
             
                    logger.error(f"   - Check for port conflicts: lsof -i :{port}")
         | 
| 748 633 | 
             
                    logger.error(f"   - Try a different port with --websocket-port")
         | 
| 749 634 | 
             
                    logger.error(f"   - Verify dependencies: pip install python-socketio aiohttp")
         | 
| 750 635 | 
             
                    return False
         | 
| 751 | 
            -
             | 
| 636 | 
            +
             | 
| 752 637 | 
             
                except Exception as e:
         | 
| 753 638 | 
             
                    logger.error(f"❌ Failed to start standalone Socket.IO server: {e}")
         | 
| 754 639 | 
             
                    import traceback
         | 
| 640 | 
            +
             | 
| 755 641 | 
             
                    logger.error(f"📋 Stack trace: {traceback.format_exc()}")
         | 
| 756 | 
            -
                    logger.error( | 
| 642 | 
            +
                    logger.error(
         | 
| 643 | 
            +
                        f"💡 This may be a dependency issue - try: pip install python-socketio aiohttp"
         | 
| 644 | 
            +
                    )
         | 
| 757 645 | 
             
                    return False
         | 
| 758 646 |  | 
| 759 647 |  | 
| 760 | 
            -
             | 
| 761 648 | 
             
            def open_in_browser_tab(url, logger):
         | 
| 649 | 
            +
                """Open URL in browser, attempting to reuse existing tabs when possible."""
         | 
| 650 | 
            +
                from .socketio_monitor import SocketIOMonitor
         | 
| 651 | 
            +
             | 
| 652 | 
            +
                monitor = SocketIOMonitor(logger)
         | 
| 653 | 
            +
                return monitor.open_in_browser_tab(url)
         | 
| 762 654 | 
             
                """
         | 
| 763 655 | 
             
                Open URL in browser, attempting to reuse existing tabs when possible.
         | 
| 764 | 
            -
             | 
| 656 | 
            +
             | 
| 765 657 | 
             
                WHY: Users prefer reusing browser tabs instead of opening new ones constantly.
         | 
| 766 658 | 
             
                This function attempts platform-specific solutions for tab reuse.
         | 
| 767 | 
            -
             | 
| 659 | 
            +
             | 
| 768 660 | 
             
                DESIGN DECISION: We try different methods based on platform capabilities,
         | 
| 769 661 | 
             
                falling back to standard webbrowser.open() if needed.
         | 
| 770 | 
            -
             | 
| 662 | 
            +
             | 
| 771 663 | 
             
                Args:
         | 
| 772 664 | 
             
                    url: URL to open
         | 
| 773 665 | 
             
                    logger: Logger instance for output
         | 
| @@ -775,34 +667,39 @@ def open_in_browser_tab(url, logger): | |
| 775 667 | 
             
                try:
         | 
| 776 668 | 
             
                    # Platform-specific optimizations for tab reuse
         | 
| 777 669 | 
             
                    import platform
         | 
| 670 | 
            +
             | 
| 778 671 | 
             
                    system = platform.system().lower()
         | 
| 779 | 
            -
             | 
| 672 | 
            +
             | 
| 780 673 | 
             
                    if system == "darwin":  # macOS
         | 
| 781 674 | 
             
                        # Just use the standard webbrowser module on macOS
         | 
| 782 675 | 
             
                        # The AppleScript approach is too unreliable
         | 
| 783 676 | 
             
                        webbrowser.open(url, new=0, autoraise=True)  # new=0 tries to reuse window
         | 
| 784 677 | 
             
                        logger.info("Opened browser on macOS")
         | 
| 785 | 
            -
             | 
| 678 | 
            +
             | 
| 786 679 | 
             
                    elif system == "linux":
         | 
| 787 680 | 
             
                        # On Linux, try to use existing browser session
         | 
| 788 681 | 
             
                        try:
         | 
| 789 682 | 
             
                            # This is a best-effort approach for common browsers
         | 
| 790 | 
            -
                            webbrowser.get().open( | 
| 683 | 
            +
                            webbrowser.get().open(
         | 
| 684 | 
            +
                                url, new=0
         | 
| 685 | 
            +
                            )  # new=0 tries to reuse existing window
         | 
| 791 686 | 
             
                            logger.info("Attempted Linux browser tab reuse")
         | 
| 792 687 | 
             
                        except Exception:
         | 
| 793 688 | 
             
                            webbrowser.open(url, autoraise=True)
         | 
| 794 | 
            -
             | 
| 689 | 
            +
             | 
| 795 690 | 
             
                    elif system == "windows":
         | 
| 796 691 | 
             
                        # On Windows, try to use existing browser
         | 
| 797 692 | 
             
                        try:
         | 
| 798 | 
            -
                            webbrowser.get().open( | 
| 693 | 
            +
                            webbrowser.get().open(
         | 
| 694 | 
            +
                                url, new=0
         | 
| 695 | 
            +
                            )  # new=0 tries to reuse existing window
         | 
| 799 696 | 
             
                            logger.info("Attempted Windows browser tab reuse")
         | 
| 800 697 | 
             
                        except Exception:
         | 
| 801 698 | 
             
                            webbrowser.open(url, autoraise=True)
         | 
| 802 699 | 
             
                    else:
         | 
| 803 700 | 
             
                        # Unknown platform, use standard opening
         | 
| 804 701 | 
             
                        webbrowser.open(url, autoraise=True)
         | 
| 805 | 
            -
             | 
| 702 | 
            +
             | 
| 806 703 | 
             
                except Exception as e:
         | 
| 807 704 | 
             
                    logger.warning(f"Browser opening failed: {e}")
         | 
| 808 705 | 
             
                    # Final fallback
         | 
| @@ -810,113 +707,155 @@ def open_in_browser_tab(url, logger): | |
| 810 707 |  | 
| 811 708 |  | 
| 812 709 | 
             
            def _check_claude_json_memory(args, logger):
         | 
| 710 | 
            +
                """Check .claude.json file size and warn about memory issues."""
         | 
| 711 | 
            +
                from .run_config_checker import RunConfigChecker
         | 
| 712 | 
            +
             | 
| 713 | 
            +
                checker = RunConfigChecker(logger)
         | 
| 714 | 
            +
                checker.check_claude_json_memory(args)
         | 
| 813 715 | 
             
                """Check .claude.json file size and warn about memory issues.
         | 
| 814 | 
            -
             | 
| 716 | 
            +
             | 
| 815 717 | 
             
                WHY: Large .claude.json files (>500KB) cause significant memory issues when
         | 
| 816 718 | 
             
                using --resume. Claude Desktop loads the entire conversation history into
         | 
| 817 719 | 
             
                memory, leading to 2GB+ memory consumption.
         | 
| 818 | 
            -
             | 
| 720 | 
            +
             | 
| 819 721 | 
             
                DESIGN DECISIONS:
         | 
| 820 722 | 
             
                - Warn at 500KB (conservative threshold)
         | 
| 821 723 | 
             
                - Suggest cleanup command for remediation
         | 
| 822 724 | 
             
                - Allow bypass with --force flag
         | 
| 823 725 | 
             
                - Only check when using --resume
         | 
| 824 | 
            -
             | 
| 726 | 
            +
             | 
| 825 727 | 
             
                Args:
         | 
| 826 728 | 
             
                    args: Parsed command line arguments
         | 
| 827 729 | 
             
                    logger: Logger instance for output
         | 
| 828 730 | 
             
                """
         | 
| 829 731 | 
             
                # Only check if using --resume
         | 
| 830 | 
            -
                if not hasattr(args,  | 
| 732 | 
            +
                if not hasattr(args, "resume") or not args.resume:
         | 
| 831 733 | 
             
                    return
         | 
| 832 | 
            -
             | 
| 734 | 
            +
             | 
| 833 735 | 
             
                claude_json_path = Path.home() / ".claude.json"
         | 
| 834 | 
            -
             | 
| 736 | 
            +
             | 
| 835 737 | 
             
                # Check if file exists
         | 
| 836 738 | 
             
                if not claude_json_path.exists():
         | 
| 837 739 | 
             
                    logger.debug("No .claude.json file found")
         | 
| 838 740 | 
             
                    return
         | 
| 839 | 
            -
             | 
| 741 | 
            +
             | 
| 840 742 | 
             
                # Check file size
         | 
| 841 743 | 
             
                file_size = claude_json_path.stat().st_size
         | 
| 842 | 
            -
             | 
| 744 | 
            +
             | 
| 843 745 | 
             
                # Format size for display
         | 
| 844 746 | 
             
                def format_size(size_bytes):
         | 
| 845 | 
            -
                    for unit in [ | 
| 747 | 
            +
                    for unit in ["B", "KB", "MB", "GB"]:
         | 
| 846 748 | 
             
                        if size_bytes < 1024.0:
         | 
| 847 749 | 
             
                            return f"{size_bytes:.1f}{unit}"
         | 
| 848 750 | 
             
                        size_bytes /= 1024.0
         | 
| 849 751 | 
             
                    return f"{size_bytes:.1f}TB"
         | 
| 850 | 
            -
             | 
| 752 | 
            +
             | 
| 851 753 | 
             
                # Get thresholds from configuration
         | 
| 852 754 | 
             
                try:
         | 
| 853 755 | 
             
                    from ...core.config import Config
         | 
| 756 | 
            +
             | 
| 854 757 | 
             
                    config = Config()
         | 
| 855 | 
            -
                    memory_config = config.get( | 
| 856 | 
            -
                    warning_threshold =  | 
| 857 | 
            -
             | 
| 758 | 
            +
                    memory_config = config.get("memory_management", {})
         | 
| 759 | 
            +
                    warning_threshold = (
         | 
| 760 | 
            +
                        memory_config.get("claude_json_warning_threshold_kb", 500) * 1024
         | 
| 761 | 
            +
                    )
         | 
| 762 | 
            +
                    critical_threshold = (
         | 
| 763 | 
            +
                        memory_config.get("claude_json_critical_threshold_kb", 1024) * 1024
         | 
| 764 | 
            +
                    )
         | 
| 858 765 | 
             
                except Exception as e:
         | 
| 859 766 | 
             
                    logger.debug(f"Could not load memory configuration: {e}")
         | 
| 860 767 | 
             
                    # Fall back to defaults
         | 
| 861 768 | 
             
                    warning_threshold = 500 * 1024  # 500KB
         | 
| 862 769 | 
             
                    critical_threshold = 1024 * 1024  # 1MB
         | 
| 863 | 
            -
             | 
| 770 | 
            +
             | 
| 864 771 | 
             
                if file_size > critical_threshold:
         | 
| 865 | 
            -
                    print( | 
| 772 | 
            +
                    print(
         | 
| 773 | 
            +
                        f"\n⚠️  CRITICAL: Large .claude.json file detected ({format_size(file_size)})"
         | 
| 774 | 
            +
                    )
         | 
| 866 775 | 
             
                    print(f"   This WILL cause memory issues when using --resume")
         | 
| 867 776 | 
             
                    print(f"   Claude Desktop may consume 2GB+ of memory\n")
         | 
| 868 | 
            -
             | 
| 869 | 
            -
                    if not getattr(args,  | 
| 777 | 
            +
             | 
| 778 | 
            +
                    if not getattr(args, "force", False):
         | 
| 870 779 | 
             
                        print("   Recommended actions:")
         | 
| 871 780 | 
             
                        print("   1. Run 'claude-mpm cleanup-memory' to archive old conversations")
         | 
| 872 781 | 
             
                        print("   2. Use --force to bypass this warning (not recommended)")
         | 
| 873 | 
            -
                         | 
| 874 | 
            -
             | 
| 875 | 
            -
                         | 
| 876 | 
            -
             | 
| 877 | 
            -
                             | 
| 878 | 
            -
             | 
| 879 | 
            -
                                 | 
| 880 | 
            -
                                 | 
| 881 | 
            -
             | 
| 882 | 
            -
                             | 
| 883 | 
            -
                             | 
| 782 | 
            +
                        sys.stdout.flush()  # Ensure prompt is displayed before input
         | 
| 783 | 
            +
             | 
| 784 | 
            +
                        # Check if we're in a TTY environment for proper input handling
         | 
| 785 | 
            +
                        if not sys.stdin.isatty():
         | 
| 786 | 
            +
                            # In non-TTY environment (like pipes), use readline
         | 
| 787 | 
            +
                            print(
         | 
| 788 | 
            +
                                "\n   Would you like to continue anyway? [y/N]: ",
         | 
| 789 | 
            +
                                end="",
         | 
| 790 | 
            +
                                flush=True,
         | 
| 791 | 
            +
                            )
         | 
| 792 | 
            +
                            try:
         | 
| 793 | 
            +
                                response = sys.stdin.readline().strip().lower()
         | 
| 794 | 
            +
                                # Handle various line endings and control characters
         | 
| 795 | 
            +
                                response = response.replace("\r", "").replace("\n", "").strip()
         | 
| 796 | 
            +
                            except (EOFError, KeyboardInterrupt):
         | 
| 797 | 
            +
                                response = "n"
         | 
| 798 | 
            +
                        else:
         | 
| 799 | 
            +
                            # In TTY environment, use normal input()
         | 
| 800 | 
            +
                            print(
         | 
| 801 | 
            +
                                "\n   Would you like to continue anyway? [y/N]: ",
         | 
| 802 | 
            +
                                end="",
         | 
| 803 | 
            +
                                flush=True,
         | 
| 804 | 
            +
                            )
         | 
| 805 | 
            +
                            try:
         | 
| 806 | 
            +
                                response = input().strip().lower()
         | 
| 807 | 
            +
                            except (EOFError, KeyboardInterrupt):
         | 
| 808 | 
            +
                                response = "n"
         | 
| 809 | 
            +
             | 
| 810 | 
            +
                        if response != "y":
         | 
| 811 | 
            +
                            print(
         | 
| 812 | 
            +
                                "\n✅ Session cancelled. Run 'claude-mpm cleanup-memory' to fix this issue."
         | 
| 813 | 
            +
                            )
         | 
| 884 814 | 
             
                            sys.exit(0)
         | 
| 885 | 
            -
             | 
| 815 | 
            +
             | 
| 886 816 | 
             
                elif file_size > warning_threshold:
         | 
| 887 | 
            -
                    print( | 
| 817 | 
            +
                    print(
         | 
| 818 | 
            +
                        f"\n⚠️  Warning: .claude.json file is getting large ({format_size(file_size)})"
         | 
| 819 | 
            +
                    )
         | 
| 888 820 | 
             
                    print("   This may cause memory issues when using --resume")
         | 
| 889 | 
            -
                    print( | 
| 821 | 
            +
                    print(
         | 
| 822 | 
            +
                        "   💡 Consider running 'claude-mpm cleanup-memory' to archive old conversations\n"
         | 
| 823 | 
            +
                    )
         | 
| 890 824 | 
             
                    # Just warn, don't block execution
         | 
| 891 | 
            -
             | 
| 825 | 
            +
             | 
| 892 826 | 
             
                logger.info(f".claude.json size: {format_size(file_size)}")
         | 
| 893 827 |  | 
| 894 828 |  | 
| 895 829 | 
             
            def _check_configuration_health(logger):
         | 
| 830 | 
            +
                """Check configuration health at startup and warn about issues."""
         | 
| 831 | 
            +
                from .run_config_checker import RunConfigChecker
         | 
| 832 | 
            +
             | 
| 833 | 
            +
                checker = RunConfigChecker(logger)
         | 
| 834 | 
            +
                checker.check_configuration_health()
         | 
| 896 835 | 
             
                """Check configuration health at startup and warn about issues.
         | 
| 897 | 
            -
             | 
| 836 | 
            +
             | 
| 898 837 | 
             
                WHY: Configuration errors can cause silent failures, especially for response
         | 
| 899 838 | 
             
                logging. This function proactively checks configuration at startup and warns
         | 
| 900 839 | 
             
                users about any issues, providing actionable guidance.
         | 
| 901 | 
            -
             | 
| 840 | 
            +
             | 
| 902 841 | 
             
                DESIGN DECISIONS:
         | 
| 903 842 | 
             
                - Non-blocking: Issues are logged as warnings, not errors
         | 
| 904 843 | 
             
                - Actionable: Provides specific commands to fix issues
         | 
| 905 844 | 
             
                - Focused: Only checks critical configuration that affects runtime
         | 
| 906 | 
            -
             | 
| 845 | 
            +
             | 
| 907 846 | 
             
                Args:
         | 
| 908 847 | 
             
                    logger: Logger instance for output
         | 
| 909 848 | 
             
                """
         | 
| 910 849 | 
             
                try:
         | 
| 911 850 | 
             
                    # Load configuration
         | 
| 912 851 | 
             
                    config = Config()
         | 
| 913 | 
            -
             | 
| 852 | 
            +
             | 
| 914 853 | 
             
                    # Validate configuration
         | 
| 915 854 | 
             
                    is_valid, errors, warnings = config.validate_configuration()
         | 
| 916 | 
            -
             | 
| 855 | 
            +
             | 
| 917 856 | 
             
                    # Get configuration status for additional context
         | 
| 918 857 | 
             
                    status = config.get_configuration_status()
         | 
| 919 | 
            -
             | 
| 858 | 
            +
             | 
| 920 859 | 
             
                    # Report critical errors that will affect functionality
         | 
| 921 860 | 
             
                    if errors:
         | 
| 922 861 | 
             
                        logger.warning("⚠️  Configuration issues detected:")
         | 
| @@ -924,43 +863,55 @@ def _check_configuration_health(logger): | |
| 924 863 | 
             
                            logger.warning(f"  • {error}")
         | 
| 925 864 | 
             
                        if len(errors) > 3:
         | 
| 926 865 | 
             
                            logger.warning(f"  • ... and {len(errors) - 3} more")
         | 
| 927 | 
            -
                        logger.info( | 
| 928 | 
            -
             | 
| 866 | 
            +
                        logger.info(
         | 
| 867 | 
            +
                            "💡 Run 'claude-mpm config validate' to see all issues and fixes"
         | 
| 868 | 
            +
                        )
         | 
| 869 | 
            +
             | 
| 929 870 | 
             
                    # Check response logging specifically since it's commonly misconfigured
         | 
| 930 | 
            -
                    response_logging_enabled = config.get( | 
| 871 | 
            +
                    response_logging_enabled = config.get("response_logging.enabled", False)
         | 
| 931 872 | 
             
                    if not response_logging_enabled:
         | 
| 932 | 
            -
                        logger.debug( | 
| 873 | 
            +
                        logger.debug(
         | 
| 874 | 
            +
                            "Response logging is disabled (response_logging.enabled=false)"
         | 
| 875 | 
            +
                        )
         | 
| 933 876 | 
             
                    else:
         | 
| 934 877 | 
             
                        # Check if session directory is writable
         | 
| 935 | 
            -
                        session_dir = Path( | 
| 878 | 
            +
                        session_dir = Path(
         | 
| 879 | 
            +
                            config.get(
         | 
| 880 | 
            +
                                "response_logging.session_directory", ".claude-mpm/responses"
         | 
| 881 | 
            +
                            )
         | 
| 882 | 
            +
                        )
         | 
| 936 883 | 
             
                        if not session_dir.is_absolute():
         | 
| 937 884 | 
             
                            session_dir = Path.cwd() / session_dir
         | 
| 938 | 
            -
             | 
| 885 | 
            +
             | 
| 939 886 | 
             
                        if not session_dir.exists():
         | 
| 940 887 | 
             
                            try:
         | 
| 941 888 | 
             
                                session_dir.mkdir(parents=True, exist_ok=True)
         | 
| 942 889 | 
             
                                logger.debug(f"Created response logging directory: {session_dir}")
         | 
| 943 890 | 
             
                            except Exception as e:
         | 
| 944 | 
            -
                                logger.warning( | 
| 891 | 
            +
                                logger.warning(
         | 
| 892 | 
            +
                                    f"Cannot create response logging directory {session_dir}: {e}"
         | 
| 893 | 
            +
                                )
         | 
| 945 894 | 
             
                                logger.info("💡 Fix with: mkdir -p " + str(session_dir))
         | 
| 946 895 | 
             
                        elif not os.access(session_dir, os.W_OK):
         | 
| 947 | 
            -
                            logger.warning( | 
| 896 | 
            +
                            logger.warning(
         | 
| 897 | 
            +
                                f"Response logging directory is not writable: {session_dir}"
         | 
| 898 | 
            +
                            )
         | 
| 948 899 | 
             
                            logger.info("💡 Fix with: chmod 755 " + str(session_dir))
         | 
| 949 | 
            -
             | 
| 900 | 
            +
             | 
| 950 901 | 
             
                    # Report non-critical warnings (only in debug mode)
         | 
| 951 902 | 
             
                    if warnings and logger.isEnabledFor(logging.DEBUG):
         | 
| 952 903 | 
             
                        logger.debug("Configuration warnings:")
         | 
| 953 904 | 
             
                        for warning in warnings:
         | 
| 954 905 | 
             
                            logger.debug(f"  • {warning}")
         | 
| 955 | 
            -
             | 
| 906 | 
            +
             | 
| 956 907 | 
             
                    # Log loaded configuration source for debugging
         | 
| 957 | 
            -
                    if status.get( | 
| 908 | 
            +
                    if status.get("loaded_from") and status["loaded_from"] != "defaults":
         | 
| 958 909 | 
             
                        logger.debug(f"Configuration loaded from: {status['loaded_from']}")
         | 
| 959 | 
            -
             | 
| 910 | 
            +
             | 
| 960 911 | 
             
                except Exception as e:
         | 
| 961 912 | 
             
                    # Don't let configuration check errors prevent startup
         | 
| 962 913 | 
             
                    logger.debug(f"Configuration check failed (non-critical): {e}")
         | 
| 963 914 | 
             
                    # Only show user-facing message if it's likely to affect them
         | 
| 964 915 | 
             
                    if "yaml" in str(e).lower():
         | 
| 965 916 | 
             
                        logger.warning("⚠️  Configuration file may have YAML syntax errors")
         | 
| 966 | 
            -
                        logger.info("💡 Validate with: claude-mpm config validate")
         | 
| 917 | 
            +
                        logger.info("💡 Validate with: claude-mpm config validate")
         |