claude-mpm 4.1.1__py3-none-any.whl → 4.1.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.
Files changed (389) hide show
  1. claude_mpm/BUILD_NUMBER +1 -1
  2. claude_mpm/VERSION +1 -1
  3. claude_mpm/__main__.py +1 -1
  4. claude_mpm/agents/BASE_PM.md +74 -46
  5. claude_mpm/agents/INSTRUCTIONS.md +11 -153
  6. claude_mpm/agents/WORKFLOW.md +61 -321
  7. claude_mpm/agents/__init__.py +11 -11
  8. claude_mpm/agents/agent_loader.py +23 -20
  9. claude_mpm/agents/agent_loader_integration.py +1 -1
  10. claude_mpm/agents/agents_metadata.py +27 -0
  11. claude_mpm/agents/async_agent_loader.py +5 -8
  12. claude_mpm/agents/base_agent_loader.py +36 -25
  13. claude_mpm/agents/frontmatter_validator.py +6 -6
  14. claude_mpm/agents/schema/agent_schema.json +1 -1
  15. claude_mpm/agents/system_agent_config.py +9 -9
  16. claude_mpm/agents/templates/api_qa.json +47 -2
  17. claude_mpm/agents/templates/engineer.json +33 -11
  18. claude_mpm/agents/templates/imagemagick.json +256 -0
  19. claude_mpm/agents/templates/qa.json +41 -2
  20. claude_mpm/agents/templates/ticketing.json +5 -5
  21. claude_mpm/agents/templates/web_qa.json +50 -2
  22. claude_mpm/cli/__init__.py +51 -46
  23. claude_mpm/cli/__main__.py +1 -1
  24. claude_mpm/cli/commands/__init__.py +10 -12
  25. claude_mpm/cli/commands/agent_manager.py +186 -181
  26. claude_mpm/cli/commands/agents.py +648 -1098
  27. claude_mpm/cli/commands/aggregate.py +30 -29
  28. claude_mpm/cli/commands/cleanup.py +50 -44
  29. claude_mpm/cli/commands/cleanup_orphaned_agents.py +25 -25
  30. claude_mpm/cli/commands/config.py +162 -127
  31. claude_mpm/cli/commands/doctor.py +52 -62
  32. claude_mpm/cli/commands/info.py +37 -25
  33. claude_mpm/cli/commands/mcp.py +3 -7
  34. claude_mpm/cli/commands/mcp_command_router.py +14 -18
  35. claude_mpm/cli/commands/mcp_install_commands.py +28 -23
  36. claude_mpm/cli/commands/mcp_pipx_config.py +58 -49
  37. claude_mpm/cli/commands/mcp_server_commands.py +23 -17
  38. claude_mpm/cli/commands/memory.py +339 -967
  39. claude_mpm/cli/commands/monitor.py +117 -88
  40. claude_mpm/cli/commands/run.py +233 -542
  41. claude_mpm/cli/commands/socketio_monitor.py +17 -19
  42. claude_mpm/cli/commands/tickets.py +92 -92
  43. claude_mpm/cli/parser.py +1 -5
  44. claude_mpm/cli/parsers/__init__.py +1 -1
  45. claude_mpm/cli/parsers/agent_manager_parser.py +50 -98
  46. claude_mpm/cli/parsers/agents_parser.py +2 -3
  47. claude_mpm/cli/parsers/base_parser.py +7 -5
  48. claude_mpm/cli/parsers/mcp_parser.py +4 -2
  49. claude_mpm/cli/parsers/monitor_parser.py +26 -18
  50. claude_mpm/cli/shared/__init__.py +10 -10
  51. claude_mpm/cli/shared/argument_patterns.py +57 -71
  52. claude_mpm/cli/shared/base_command.py +61 -53
  53. claude_mpm/cli/shared/error_handling.py +62 -58
  54. claude_mpm/cli/shared/output_formatters.py +78 -77
  55. claude_mpm/cli/startup_logging.py +280 -172
  56. claude_mpm/cli/utils.py +10 -11
  57. claude_mpm/cli_module/__init__.py +1 -1
  58. claude_mpm/cli_module/args.py +1 -1
  59. claude_mpm/cli_module/migration_example.py +5 -5
  60. claude_mpm/config/__init__.py +9 -9
  61. claude_mpm/config/agent_config.py +15 -14
  62. claude_mpm/config/experimental_features.py +4 -4
  63. claude_mpm/config/paths.py +0 -1
  64. claude_mpm/config/socketio_config.py +5 -6
  65. claude_mpm/constants.py +1 -2
  66. claude_mpm/core/__init__.py +8 -8
  67. claude_mpm/core/agent_name_normalizer.py +1 -1
  68. claude_mpm/core/agent_registry.py +22 -29
  69. claude_mpm/core/agent_session_manager.py +3 -3
  70. claude_mpm/core/base_service.py +7 -15
  71. claude_mpm/core/cache.py +4 -6
  72. claude_mpm/core/claude_runner.py +85 -113
  73. claude_mpm/core/config.py +43 -28
  74. claude_mpm/core/config_aliases.py +0 -9
  75. claude_mpm/core/config_constants.py +52 -30
  76. claude_mpm/core/constants.py +0 -1
  77. claude_mpm/core/container.py +18 -27
  78. claude_mpm/core/exceptions.py +2 -2
  79. claude_mpm/core/factories.py +10 -12
  80. claude_mpm/core/framework_loader.py +500 -680
  81. claude_mpm/core/hook_manager.py +26 -22
  82. claude_mpm/core/hook_performance_config.py +58 -47
  83. claude_mpm/core/injectable_service.py +1 -1
  84. claude_mpm/core/interactive_session.py +61 -152
  85. claude_mpm/core/interfaces.py +1 -100
  86. claude_mpm/core/lazy.py +5 -5
  87. claude_mpm/core/log_manager.py +587 -0
  88. claude_mpm/core/logger.py +125 -8
  89. claude_mpm/core/logging_config.py +15 -17
  90. claude_mpm/core/minimal_framework_loader.py +5 -8
  91. claude_mpm/core/oneshot_session.py +15 -33
  92. claude_mpm/core/optimized_agent_loader.py +4 -6
  93. claude_mpm/core/optimized_startup.py +2 -1
  94. claude_mpm/core/output_style_manager.py +147 -106
  95. claude_mpm/core/pm_hook_interceptor.py +0 -1
  96. claude_mpm/core/service_registry.py +11 -8
  97. claude_mpm/core/session_manager.py +1 -2
  98. claude_mpm/core/shared/__init__.py +1 -1
  99. claude_mpm/core/shared/config_loader.py +101 -97
  100. claude_mpm/core/shared/path_resolver.py +72 -68
  101. claude_mpm/core/shared/singleton_manager.py +56 -50
  102. claude_mpm/core/socketio_pool.py +26 -6
  103. claude_mpm/core/tool_access_control.py +4 -5
  104. claude_mpm/core/typing_utils.py +50 -59
  105. claude_mpm/core/unified_agent_registry.py +14 -19
  106. claude_mpm/core/unified_config.py +4 -6
  107. claude_mpm/core/unified_paths.py +197 -109
  108. claude_mpm/dashboard/open_dashboard.py +2 -4
  109. claude_mpm/experimental/cli_enhancements.py +51 -36
  110. claude_mpm/generators/agent_profile_generator.py +2 -4
  111. claude_mpm/hooks/base_hook.py +1 -2
  112. claude_mpm/hooks/claude_hooks/connection_pool.py +72 -26
  113. claude_mpm/hooks/claude_hooks/event_handlers.py +99 -154
  114. claude_mpm/hooks/claude_hooks/hook_handler.py +110 -720
  115. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +104 -77
  116. claude_mpm/hooks/claude_hooks/hook_handler_original.py +1040 -0
  117. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +347 -0
  118. claude_mpm/hooks/claude_hooks/memory_integration.py +2 -4
  119. claude_mpm/hooks/claude_hooks/response_tracking.py +15 -11
  120. claude_mpm/hooks/claude_hooks/services/__init__.py +13 -0
  121. claude_mpm/hooks/claude_hooks/services/connection_manager.py +190 -0
  122. claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
  123. claude_mpm/hooks/claude_hooks/services/state_manager.py +282 -0
  124. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
  125. claude_mpm/hooks/claude_hooks/tool_analysis.py +12 -18
  126. claude_mpm/hooks/memory_integration_hook.py +5 -5
  127. claude_mpm/hooks/tool_call_interceptor.py +1 -1
  128. claude_mpm/hooks/validation_hooks.py +4 -4
  129. claude_mpm/init.py +4 -9
  130. claude_mpm/models/__init__.py +2 -2
  131. claude_mpm/models/agent_session.py +11 -14
  132. claude_mpm/scripts/mcp_server.py +20 -11
  133. claude_mpm/scripts/mcp_wrapper.py +5 -5
  134. claude_mpm/scripts/mpm_doctor.py +321 -0
  135. claude_mpm/scripts/socketio_daemon.py +28 -25
  136. claude_mpm/scripts/socketio_daemon_hardened.py +298 -258
  137. claude_mpm/scripts/socketio_server_manager.py +116 -95
  138. claude_mpm/services/__init__.py +49 -49
  139. claude_mpm/services/agent_capabilities_service.py +12 -18
  140. claude_mpm/services/agents/__init__.py +22 -22
  141. claude_mpm/services/agents/agent_builder.py +140 -119
  142. claude_mpm/services/agents/deployment/__init__.py +3 -3
  143. claude_mpm/services/agents/deployment/agent_config_provider.py +9 -9
  144. claude_mpm/services/agents/deployment/agent_configuration_manager.py +19 -20
  145. claude_mpm/services/agents/deployment/agent_definition_factory.py +1 -5
  146. claude_mpm/services/agents/deployment/agent_deployment.py +129 -511
  147. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -8
  148. claude_mpm/services/agents/deployment/agent_environment_manager.py +2 -7
  149. claude_mpm/services/agents/deployment/agent_filesystem_manager.py +6 -10
  150. claude_mpm/services/agents/deployment/agent_format_converter.py +11 -15
  151. claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +2 -3
  152. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +5 -5
  153. claude_mpm/services/agents/deployment/agent_metrics_collector.py +13 -19
  154. claude_mpm/services/agents/deployment/agent_restore_handler.py +0 -1
  155. claude_mpm/services/agents/deployment/agent_template_builder.py +26 -35
  156. claude_mpm/services/agents/deployment/agent_validator.py +0 -1
  157. claude_mpm/services/agents/deployment/agent_version_manager.py +7 -9
  158. claude_mpm/services/agents/deployment/agent_versioning.py +3 -3
  159. claude_mpm/services/agents/deployment/agents_directory_resolver.py +6 -7
  160. claude_mpm/services/agents/deployment/async_agent_deployment.py +51 -38
  161. claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
  162. claude_mpm/services/agents/deployment/config/__init__.py +1 -1
  163. claude_mpm/services/agents/deployment/config/deployment_config.py +7 -8
  164. claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
  165. claude_mpm/services/agents/deployment/deployment_type_detector.py +1 -1
  166. claude_mpm/services/agents/deployment/deployment_wrapper.py +18 -18
  167. claude_mpm/services/agents/deployment/facade/__init__.py +1 -1
  168. claude_mpm/services/agents/deployment/facade/deployment_executor.py +0 -3
  169. claude_mpm/services/agents/deployment/facade/deployment_facade.py +3 -4
  170. claude_mpm/services/agents/deployment/interface_adapter.py +5 -7
  171. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +345 -276
  172. claude_mpm/services/agents/deployment/pipeline/__init__.py +2 -2
  173. claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +1 -1
  174. claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +6 -4
  175. claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +3 -3
  176. claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +2 -2
  177. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +14 -13
  178. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +0 -1
  179. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +1 -1
  180. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +8 -9
  181. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +1 -1
  182. claude_mpm/services/agents/deployment/processors/__init__.py +1 -1
  183. claude_mpm/services/agents/deployment/processors/agent_processor.py +20 -16
  184. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +5 -12
  185. claude_mpm/services/agents/deployment/results/__init__.py +1 -1
  186. claude_mpm/services/agents/deployment/results/deployment_result_builder.py +1 -1
  187. claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
  188. claude_mpm/services/agents/deployment/strategies/__init__.py +2 -2
  189. claude_mpm/services/agents/deployment/strategies/base_strategy.py +1 -7
  190. claude_mpm/services/agents/deployment/strategies/project_strategy.py +1 -4
  191. claude_mpm/services/agents/deployment/strategies/system_strategy.py +2 -3
  192. claude_mpm/services/agents/deployment/strategies/user_strategy.py +3 -7
  193. claude_mpm/services/agents/deployment/validation/__init__.py +1 -1
  194. claude_mpm/services/agents/deployment/validation/agent_validator.py +1 -1
  195. claude_mpm/services/agents/deployment/validation/template_validator.py +2 -2
  196. claude_mpm/services/agents/deployment/validation/validation_result.py +2 -6
  197. claude_mpm/services/agents/loading/__init__.py +1 -1
  198. claude_mpm/services/agents/loading/agent_profile_loader.py +6 -12
  199. claude_mpm/services/agents/loading/base_agent_manager.py +5 -5
  200. claude_mpm/services/agents/loading/framework_agent_loader.py +2 -4
  201. claude_mpm/services/agents/management/__init__.py +1 -1
  202. claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -3
  203. claude_mpm/services/agents/management/agent_management_service.py +5 -9
  204. claude_mpm/services/agents/memory/__init__.py +4 -4
  205. claude_mpm/services/agents/memory/agent_memory_manager.py +157 -503
  206. claude_mpm/services/agents/memory/agent_persistence_service.py +0 -2
  207. claude_mpm/services/agents/memory/content_manager.py +44 -38
  208. claude_mpm/services/agents/memory/memory_categorization_service.py +165 -0
  209. claude_mpm/services/agents/memory/memory_file_service.py +103 -0
  210. claude_mpm/services/agents/memory/memory_format_service.py +201 -0
  211. claude_mpm/services/agents/memory/memory_limits_service.py +99 -0
  212. claude_mpm/services/agents/memory/template_generator.py +4 -6
  213. claude_mpm/services/agents/registry/__init__.py +11 -7
  214. claude_mpm/services/agents/registry/deployed_agent_discovery.py +30 -27
  215. claude_mpm/services/agents/registry/modification_tracker.py +3 -6
  216. claude_mpm/services/async_session_logger.py +1 -2
  217. claude_mpm/services/claude_session_logger.py +1 -2
  218. claude_mpm/services/cli/__init__.py +18 -0
  219. claude_mpm/services/cli/agent_cleanup_service.py +407 -0
  220. claude_mpm/services/cli/agent_dependency_service.py +395 -0
  221. claude_mpm/services/cli/agent_listing_service.py +463 -0
  222. claude_mpm/services/cli/agent_output_formatter.py +605 -0
  223. claude_mpm/services/cli/agent_validation_service.py +589 -0
  224. claude_mpm/services/cli/dashboard_launcher.py +424 -0
  225. claude_mpm/services/cli/memory_crud_service.py +617 -0
  226. claude_mpm/services/cli/memory_output_formatter.py +604 -0
  227. claude_mpm/services/cli/session_manager.py +513 -0
  228. claude_mpm/services/cli/socketio_manager.py +498 -0
  229. claude_mpm/services/cli/startup_checker.py +370 -0
  230. claude_mpm/services/command_deployment_service.py +173 -0
  231. claude_mpm/services/command_handler_service.py +20 -22
  232. claude_mpm/services/core/__init__.py +25 -25
  233. claude_mpm/services/core/base.py +0 -5
  234. claude_mpm/services/core/cache_manager.py +311 -0
  235. claude_mpm/services/core/interfaces/__init__.py +32 -32
  236. claude_mpm/services/core/interfaces/agent.py +0 -21
  237. claude_mpm/services/core/interfaces/communication.py +0 -27
  238. claude_mpm/services/core/interfaces/infrastructure.py +0 -56
  239. claude_mpm/services/core/interfaces/service.py +0 -29
  240. claude_mpm/services/core/memory_manager.py +637 -0
  241. claude_mpm/services/core/path_resolver.py +498 -0
  242. claude_mpm/services/core/service_container.py +520 -0
  243. claude_mpm/services/core/service_interfaces.py +436 -0
  244. claude_mpm/services/diagnostics/__init__.py +1 -1
  245. claude_mpm/services/diagnostics/checks/__init__.py +6 -6
  246. claude_mpm/services/diagnostics/checks/agent_check.py +152 -97
  247. claude_mpm/services/diagnostics/checks/base_check.py +12 -16
  248. claude_mpm/services/diagnostics/checks/claude_desktop_check.py +84 -81
  249. claude_mpm/services/diagnostics/checks/common_issues_check.py +99 -91
  250. claude_mpm/services/diagnostics/checks/configuration_check.py +82 -77
  251. claude_mpm/services/diagnostics/checks/filesystem_check.py +67 -68
  252. claude_mpm/services/diagnostics/checks/installation_check.py +254 -94
  253. claude_mpm/services/diagnostics/checks/mcp_check.py +90 -88
  254. claude_mpm/services/diagnostics/checks/monitor_check.py +75 -76
  255. claude_mpm/services/diagnostics/checks/startup_log_check.py +67 -73
  256. claude_mpm/services/diagnostics/diagnostic_runner.py +67 -59
  257. claude_mpm/services/diagnostics/doctor_reporter.py +107 -70
  258. claude_mpm/services/diagnostics/models.py +21 -19
  259. claude_mpm/services/event_aggregator.py +10 -17
  260. claude_mpm/services/event_bus/__init__.py +1 -1
  261. claude_mpm/services/event_bus/config.py +54 -35
  262. claude_mpm/services/event_bus/event_bus.py +76 -71
  263. claude_mpm/services/event_bus/relay.py +74 -64
  264. claude_mpm/services/events/__init__.py +11 -11
  265. claude_mpm/services/events/consumers/__init__.py +3 -3
  266. claude_mpm/services/events/consumers/dead_letter.py +71 -63
  267. claude_mpm/services/events/consumers/logging.py +39 -37
  268. claude_mpm/services/events/consumers/metrics.py +56 -57
  269. claude_mpm/services/events/consumers/socketio.py +82 -81
  270. claude_mpm/services/events/core.py +110 -99
  271. claude_mpm/services/events/interfaces.py +56 -72
  272. claude_mpm/services/events/producers/__init__.py +1 -1
  273. claude_mpm/services/events/producers/hook.py +38 -38
  274. claude_mpm/services/events/producers/system.py +46 -44
  275. claude_mpm/services/exceptions.py +81 -80
  276. claude_mpm/services/framework_claude_md_generator/__init__.py +2 -4
  277. claude_mpm/services/framework_claude_md_generator/content_assembler.py +3 -5
  278. claude_mpm/services/framework_claude_md_generator/content_validator.py +1 -1
  279. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +4 -4
  280. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +0 -1
  281. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +0 -2
  282. claude_mpm/services/framework_claude_md_generator/version_manager.py +4 -5
  283. claude_mpm/services/hook_service.py +6 -9
  284. claude_mpm/services/infrastructure/__init__.py +1 -1
  285. claude_mpm/services/infrastructure/context_preservation.py +8 -12
  286. claude_mpm/services/infrastructure/monitoring.py +21 -23
  287. claude_mpm/services/mcp_gateway/__init__.py +37 -37
  288. claude_mpm/services/mcp_gateway/auto_configure.py +95 -103
  289. claude_mpm/services/mcp_gateway/config/__init__.py +1 -1
  290. claude_mpm/services/mcp_gateway/config/config_loader.py +23 -25
  291. claude_mpm/services/mcp_gateway/config/config_schema.py +5 -5
  292. claude_mpm/services/mcp_gateway/config/configuration.py +9 -6
  293. claude_mpm/services/mcp_gateway/core/__init__.py +10 -10
  294. claude_mpm/services/mcp_gateway/core/base.py +0 -3
  295. claude_mpm/services/mcp_gateway/core/interfaces.py +1 -38
  296. claude_mpm/services/mcp_gateway/core/process_pool.py +99 -93
  297. claude_mpm/services/mcp_gateway/core/singleton_manager.py +65 -62
  298. claude_mpm/services/mcp_gateway/core/startup_verification.py +75 -74
  299. claude_mpm/services/mcp_gateway/main.py +2 -1
  300. claude_mpm/services/mcp_gateway/registry/service_registry.py +5 -8
  301. claude_mpm/services/mcp_gateway/registry/tool_registry.py +1 -1
  302. claude_mpm/services/mcp_gateway/server/__init__.py +1 -1
  303. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +12 -19
  304. claude_mpm/services/mcp_gateway/server/stdio_handler.py +4 -3
  305. claude_mpm/services/mcp_gateway/server/stdio_server.py +79 -71
  306. claude_mpm/services/mcp_gateway/tools/__init__.py +2 -2
  307. claude_mpm/services/mcp_gateway/tools/base_adapter.py +5 -6
  308. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +13 -22
  309. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +79 -78
  310. claude_mpm/services/mcp_gateway/tools/hello_world.py +12 -14
  311. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +42 -49
  312. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +51 -55
  313. claude_mpm/services/memory/__init__.py +3 -3
  314. claude_mpm/services/memory/builder.py +3 -6
  315. claude_mpm/services/memory/cache/__init__.py +1 -1
  316. claude_mpm/services/memory/cache/shared_prompt_cache.py +3 -5
  317. claude_mpm/services/memory/cache/simple_cache.py +1 -1
  318. claude_mpm/services/memory/indexed_memory.py +5 -7
  319. claude_mpm/services/memory/optimizer.py +7 -10
  320. claude_mpm/services/memory/router.py +8 -9
  321. claude_mpm/services/memory_hook_service.py +48 -34
  322. claude_mpm/services/monitor_build_service.py +77 -73
  323. claude_mpm/services/port_manager.py +130 -108
  324. claude_mpm/services/project/analyzer.py +12 -10
  325. claude_mpm/services/project/registry.py +11 -11
  326. claude_mpm/services/recovery_manager.py +10 -19
  327. claude_mpm/services/response_tracker.py +0 -1
  328. claude_mpm/services/runner_configuration_service.py +19 -20
  329. claude_mpm/services/session_management_service.py +7 -11
  330. claude_mpm/services/shared/__init__.py +1 -1
  331. claude_mpm/services/shared/async_service_base.py +58 -50
  332. claude_mpm/services/shared/config_service_base.py +73 -67
  333. claude_mpm/services/shared/lifecycle_service_base.py +82 -78
  334. claude_mpm/services/shared/manager_base.py +94 -82
  335. claude_mpm/services/shared/service_factory.py +96 -98
  336. claude_mpm/services/socketio/__init__.py +3 -3
  337. claude_mpm/services/socketio/client_proxy.py +5 -5
  338. claude_mpm/services/socketio/event_normalizer.py +199 -181
  339. claude_mpm/services/socketio/handlers/__init__.py +3 -3
  340. claude_mpm/services/socketio/handlers/base.py +5 -4
  341. claude_mpm/services/socketio/handlers/connection.py +163 -136
  342. claude_mpm/services/socketio/handlers/file.py +13 -14
  343. claude_mpm/services/socketio/handlers/git.py +12 -7
  344. claude_mpm/services/socketio/handlers/hook.py +49 -44
  345. claude_mpm/services/socketio/handlers/memory.py +0 -1
  346. claude_mpm/services/socketio/handlers/project.py +0 -1
  347. claude_mpm/services/socketio/handlers/registry.py +37 -19
  348. claude_mpm/services/socketio/migration_utils.py +98 -84
  349. claude_mpm/services/socketio/server/__init__.py +1 -1
  350. claude_mpm/services/socketio/server/broadcaster.py +81 -87
  351. claude_mpm/services/socketio/server/core.py +65 -54
  352. claude_mpm/services/socketio/server/eventbus_integration.py +95 -56
  353. claude_mpm/services/socketio/server/main.py +64 -38
  354. claude_mpm/services/socketio_client_manager.py +10 -12
  355. claude_mpm/services/subprocess_launcher_service.py +4 -7
  356. claude_mpm/services/system_instructions_service.py +13 -14
  357. claude_mpm/services/ticket_manager.py +2 -2
  358. claude_mpm/services/utility_service.py +5 -13
  359. claude_mpm/services/version_control/__init__.py +16 -16
  360. claude_mpm/services/version_control/branch_strategy.py +5 -8
  361. claude_mpm/services/version_control/conflict_resolution.py +9 -23
  362. claude_mpm/services/version_control/git_operations.py +5 -7
  363. claude_mpm/services/version_control/semantic_versioning.py +16 -17
  364. claude_mpm/services/version_control/version_parser.py +13 -18
  365. claude_mpm/services/version_service.py +10 -11
  366. claude_mpm/storage/__init__.py +1 -1
  367. claude_mpm/storage/state_storage.py +22 -28
  368. claude_mpm/utils/__init__.py +6 -6
  369. claude_mpm/utils/agent_dependency_loader.py +47 -33
  370. claude_mpm/utils/config_manager.py +11 -14
  371. claude_mpm/utils/dependency_cache.py +1 -1
  372. claude_mpm/utils/dependency_manager.py +13 -17
  373. claude_mpm/utils/dependency_strategies.py +8 -10
  374. claude_mpm/utils/environment_context.py +3 -9
  375. claude_mpm/utils/error_handler.py +3 -13
  376. claude_mpm/utils/file_utils.py +1 -1
  377. claude_mpm/utils/path_operations.py +8 -12
  378. claude_mpm/utils/robust_installer.py +110 -33
  379. claude_mpm/utils/subprocess_utils.py +5 -6
  380. claude_mpm/validation/agent_validator.py +3 -6
  381. claude_mpm/validation/frontmatter_validator.py +1 -1
  382. {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/METADATA +1 -1
  383. claude_mpm-4.1.3.dist-info/RECORD +528 -0
  384. claude_mpm/cli/commands/run_config_checker.py +0 -160
  385. claude_mpm-4.1.1.dist-info/RECORD +0 -494
  386. {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/WHEEL +0 -0
  387. {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/entry_points.txt +0 -0
  388. {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/licenses/LICENSE +0 -0
  389. {claude_mpm-4.1.1.dist-info → claude_mpm-4.1.3.dist-info}/top_level.txt +0 -0
@@ -13,27 +13,23 @@ DESIGN DECISIONS:
13
13
  - Support multiple output formats (json, yaml, table, text)
14
14
  """
15
15
 
16
- import logging
17
- import os
18
16
  import subprocess
19
17
  import sys
20
- import time
21
- import webbrowser
22
18
  from datetime import datetime
23
- from typing import Dict, Any, Optional
19
+ from typing import Optional
24
20
 
25
21
  from ...constants import LogLevel
26
- from ...core.config import Config
27
22
  from ...core.logger import get_logger
28
- from ...core.shared.config_loader import ConfigLoader
29
- from ...core.unified_paths import get_package_root, get_scripts_dir
30
- from ...services.port_manager import PortManager
31
- from ...utils.dependency_manager import ensure_socketio_dependencies
23
+ from ...core.unified_paths import get_scripts_dir
24
+ from ...services.cli.dashboard_launcher import DashboardLauncher
25
+ from ...services.cli.session_manager import SessionManager
26
+ from ...services.cli.socketio_manager import SocketIOManager
27
+ from ...services.cli.startup_checker import StartupCheckerService
32
28
  from ..shared import BaseCommand, CommandResult
33
29
  from ..startup_logging import (
34
- log_startup_status,
30
+ cleanup_old_startup_logs,
31
+ log_startup_status,
35
32
  setup_startup_logging,
36
- cleanup_old_startup_logs
37
33
  )
38
34
  from ..utils import get_user_input, list_agent_versions_at_startup
39
35
 
@@ -159,7 +155,7 @@ def create_session_context(session_id, session_manager):
159
155
 
160
156
  base_context = create_simple_context()
161
157
 
162
- session_data = session_manager.get_session_by_id(session_id)
158
+ session_data = session_manager.get_session_info(session_id)
163
159
  if not session_data:
164
160
  return base_context
165
161
 
@@ -207,14 +203,22 @@ class RunCommand(BaseCommand):
207
203
  # Execute the main run logic
208
204
  success = self._execute_run_session(args)
209
205
 
206
+ # Log memory stats at session completion
207
+ from ..startup_logging import log_memory_stats
208
+
209
+ log_memory_stats(self.logger, "Session End Memory")
210
+
210
211
  if success:
211
- return CommandResult.success_result("Claude session completed successfully")
212
- else:
213
- return CommandResult.error_result("Claude session failed", exit_code=1)
212
+ return CommandResult.success_result(
213
+ "Claude session completed successfully"
214
+ )
215
+ return CommandResult.error_result("Claude session failed", exit_code=1)
214
216
 
215
217
  except KeyboardInterrupt:
216
218
  self.logger.info("Session interrupted by user")
217
- return CommandResult.error_result("Session cancelled by user", exit_code=130)
219
+ return CommandResult.error_result(
220
+ "Session cancelled by user", exit_code=130
221
+ )
218
222
  except Exception as e:
219
223
  self.logger.error(f"Error running Claude session: {e}", exc_info=True)
220
224
  return CommandResult.error_result(f"Error running Claude session: {e}")
@@ -248,7 +252,9 @@ class RunCommand(BaseCommand):
248
252
  self._check_claude_json_memory(args)
249
253
 
250
254
  # Handle session management
251
- session_manager, resume_session_id, resume_context = self._setup_session_management(args)
255
+ session_manager, resume_session_id, resume_context = (
256
+ self._setup_session_management(args)
257
+ )
252
258
 
253
259
  # Handle dependency checking
254
260
  self._handle_dependency_checking(args)
@@ -260,7 +266,9 @@ class RunCommand(BaseCommand):
260
266
  runner = self._setup_claude_runner(args, monitor_mode, websocket_port)
261
267
 
262
268
  # Create context and run session
263
- context = self._create_session_context(args, session_manager, resume_session_id, resume_context)
269
+ context = self._create_session_context(
270
+ args, session_manager, resume_session_id, resume_context
271
+ )
264
272
 
265
273
  # Execute the session
266
274
  return self._execute_session(args, runner, context)
@@ -271,23 +279,29 @@ class RunCommand(BaseCommand):
271
279
 
272
280
  def _check_configuration_health(self):
273
281
  """Check configuration health at startup."""
274
- from .run_config_checker import RunConfigChecker
275
- checker = RunConfigChecker(self.logger)
276
- checker.check_configuration_health()
282
+ # Use new StartupCheckerService
283
+ from ...core.config import Config
284
+
285
+ config_service = Config()
286
+ checker = StartupCheckerService(config_service)
287
+ warnings = checker.check_configuration()
288
+ checker.display_warnings(warnings)
277
289
 
278
290
  def _check_claude_json_memory(self, args):
279
291
  """Check .claude.json file size and warn about memory issues."""
280
- from .run_config_checker import RunConfigChecker
281
- checker = RunConfigChecker(self.logger)
282
- checker.check_claude_json_memory(args)
292
+ # Use new StartupCheckerService
293
+ from ...core.config import Config
294
+
295
+ config_service = Config()
296
+ checker = StartupCheckerService(config_service)
297
+ resume_enabled = getattr(args, "mpm_resume", False)
298
+ warning = checker.check_memory(resume_enabled)
299
+ if warning:
300
+ checker.display_warnings([warning])
283
301
 
284
302
  def _setup_session_management(self, args):
285
303
  """Setup session management and handle resumption."""
286
- try:
287
- from ...core.session_manager import SessionManager
288
- except ImportError:
289
- from claude_mpm.core.session_manager import SessionManager
290
-
304
+ # Use the new SessionManager service from the CLI services layer
291
305
  session_manager = SessionManager()
292
306
  resume_session_id = None
293
307
  resume_context = None
@@ -297,11 +311,15 @@ class RunCommand(BaseCommand):
297
311
  # Resume the last interactive session
298
312
  resume_session_id = session_manager.get_last_interactive_session()
299
313
  if resume_session_id:
300
- session_data = session_manager.get_session_by_id(resume_session_id)
314
+ session_data = session_manager.get_session_info(resume_session_id)
301
315
  if session_data:
302
316
  resume_context = session_data.get("context", "default")
303
- self.logger.info(f"Resuming session {resume_session_id} (context: {resume_context})")
304
- print(f"🔄 Resuming session {resume_session_id[:8]}... (created: {session_data.get('created_at', 'unknown')})")
317
+ self.logger.info(
318
+ f"Resuming session {resume_session_id} (context: {resume_context})"
319
+ )
320
+ print(
321
+ f"🔄 Resuming session {resume_session_id[:8]}... (created: {session_data.get('created_at', 'unknown')})"
322
+ )
305
323
  else:
306
324
  self.logger.warning(f"Session {resume_session_id} not found")
307
325
  else:
@@ -310,11 +328,15 @@ class RunCommand(BaseCommand):
310
328
  else:
311
329
  # Resume specific session by ID
312
330
  resume_session_id = args.mpm_resume
313
- session_data = session_manager.get_session_by_id(resume_session_id)
331
+ session_data = session_manager.get_session_info(resume_session_id)
314
332
  if session_data:
315
333
  resume_context = session_data.get("context", "default")
316
- self.logger.info(f"Resuming session {resume_session_id} (context: {resume_context})")
317
- print(f"🔄 Resuming session {resume_session_id[:8]}... (context: {resume_context})")
334
+ self.logger.info(
335
+ f"Resuming session {resume_session_id} (context: {resume_context})"
336
+ )
337
+ print(
338
+ f"🔄 Resuming session {resume_session_id[:8]}... (context: {resume_context})"
339
+ )
318
340
  else:
319
341
  self.logger.error(f"Session {resume_session_id} not found")
320
342
  print(f"❌ Session {resume_session_id} not found")
@@ -355,17 +377,25 @@ class RunCommand(BaseCommand):
355
377
  # Check dependencies and prompt for installation if needed
356
378
  missing_deps = loader.check_dependencies()
357
379
  if missing_deps:
358
- self.logger.info(f"Found {len(missing_deps)} missing dependencies")
380
+ self.logger.info(
381
+ f"Found {len(missing_deps)} missing dependencies"
382
+ )
359
383
 
360
384
  # Prompt user for installation
361
- print(f"\n📦 Found {len(missing_deps)} missing dependencies:")
385
+ print(
386
+ f"\n📦 Found {len(missing_deps)} missing dependencies:"
387
+ )
362
388
  for dep in missing_deps[:5]: # Show first 5
363
389
  print(f" • {dep}")
364
390
  if len(missing_deps) > 5:
365
391
  print(f" ... and {len(missing_deps) - 5} more")
366
392
 
367
- response = input("\nInstall missing dependencies? (y/N): ").strip().lower()
368
- if response in ['y', 'yes']:
393
+ response = (
394
+ input("\nInstall missing dependencies? (y/N): ")
395
+ .strip()
396
+ .lower()
397
+ )
398
+ if response in ["y", "yes"]:
369
399
  loader.auto_install = True
370
400
  loader.install_dependencies(missing_deps)
371
401
  print("✅ Dependencies installed successfully")
@@ -375,8 +405,12 @@ class RunCommand(BaseCommand):
375
405
  # Just check without prompting
376
406
  missing_deps = loader.check_dependencies()
377
407
  if missing_deps:
378
- self.logger.warning(f"Found {len(missing_deps)} missing dependencies")
379
- print(f"⚠️ Found {len(missing_deps)} missing dependencies. Use --force-check-dependencies to install.")
408
+ self.logger.warning(
409
+ f"Found {len(missing_deps)} missing dependencies"
410
+ )
411
+ print(
412
+ f"⚠️ Found {len(missing_deps)} missing dependencies. Use --force-check-dependencies to install."
413
+ )
380
414
 
381
415
  # Update cache
382
416
  smart_checker.update_cache(deployment_hash)
@@ -389,38 +423,43 @@ class RunCommand(BaseCommand):
389
423
  self.logger.warning(f"Dependency check failed: {e}")
390
424
 
391
425
  def _setup_monitoring(self, args):
392
- """Setup monitoring configuration."""
426
+ """Setup monitoring configuration using SocketIOManager."""
393
427
  monitor_mode = getattr(args, "monitor", False)
394
428
  websocket_port = 8765 # Default port
395
429
 
396
430
  if monitor_mode:
397
- # Ensure Socket.IO dependencies are available
398
- if not ensure_socketio_dependencies():
399
- self.logger.warning("Socket.IO dependencies not available, disabling monitor mode")
431
+ # Use SocketIOManager for server management
432
+ socketio_manager = SocketIOManager(self.logger)
433
+
434
+ # Check dependencies
435
+ deps_ok, error_msg = socketio_manager.ensure_dependencies()
436
+ if not deps_ok:
437
+ self.logger.warning(
438
+ f"Socket.IO dependencies not available: {error_msg}, disabling monitor mode"
439
+ )
400
440
  monitor_mode = False
401
441
  else:
402
- # Get available port
403
- port_manager = PortManager()
404
- websocket_port = port_manager.get_available_port(8765)
405
-
406
- # Start Socket.IO server if not running
407
- if not self._is_socketio_server_running(websocket_port):
408
- if not _start_socketio_server(websocket_port, self.logger):
409
- self.logger.warning("Failed to start Socket.IO server, disabling monitor mode")
410
- monitor_mode = False
411
- else:
412
- # Give server time to start
413
- time.sleep(2)
414
-
415
- if monitor_mode:
416
- # Open browser to monitoring interface
417
- monitor_url = f"http://localhost:{websocket_port}"
418
- self.logger.info(f"Opening monitor interface: {monitor_url}")
419
- try:
420
- webbrowser.open(monitor_url)
421
- args._browser_opened_by_cli = True
422
- except Exception as e:
423
- self.logger.warning(f"Could not open browser: {e}")
442
+ # Find available port and start server
443
+ websocket_port = socketio_manager.find_available_port(8765)
444
+ success, server_info = socketio_manager.start_server(
445
+ port=websocket_port
446
+ )
447
+
448
+ if not success:
449
+ self.logger.warning(
450
+ "Failed to start Socket.IO server, disabling monitor mode"
451
+ )
452
+ monitor_mode = False
453
+ else:
454
+ # Use DashboardLauncher for browser opening only
455
+ dashboard_launcher = DashboardLauncher(self.logger)
456
+ monitor_url = dashboard_launcher.get_dashboard_url(websocket_port)
457
+
458
+ # Try to open browser
459
+ browser_opened = dashboard_launcher._open_browser(monitor_url)
460
+ args._browser_opened_by_cli = browser_opened
461
+
462
+ if not browser_opened:
424
463
  print(f"💡 Monitor interface available at: {monitor_url}")
425
464
 
426
465
  return monitor_mode, websocket_port
@@ -461,11 +500,15 @@ class RunCommand(BaseCommand):
461
500
  # Set browser opening flag for monitor mode
462
501
  if monitor_mode:
463
502
  runner._should_open_monitor_browser = True
464
- runner._browser_opened_by_cli = getattr(args, "_browser_opened_by_cli", False)
503
+ runner._browser_opened_by_cli = getattr(
504
+ args, "_browser_opened_by_cli", False
505
+ )
465
506
 
466
507
  return runner
467
508
 
468
- def _create_session_context(self, args, session_manager, resume_session_id, resume_context):
509
+ def _create_session_context(
510
+ self, args, session_manager, resume_session_id, resume_context
511
+ ):
469
512
  """Create session context."""
470
513
  try:
471
514
  from ...core.claude_runner import create_simple_context
@@ -476,14 +519,16 @@ class RunCommand(BaseCommand):
476
519
  # For resumed sessions, create enhanced context with session information
477
520
  context = create_session_context(resume_session_id, session_manager)
478
521
  # Update session usage
479
- session_manager.active_sessions[resume_session_id]["last_used"] = datetime.now().isoformat()
480
- session_manager.active_sessions[resume_session_id]["use_count"] += 1
481
- session_manager._save_sessions()
522
+ session = session_manager.load_session(resume_session_id)
523
+ if session:
524
+ session.last_used = datetime.now().isoformat()
525
+ session.use_count += 1
526
+ session_manager.save_session(session)
482
527
  else:
483
528
  # Create a new session for tracking
484
- new_session_id = session_manager.create_session("default")
529
+ new_session = session_manager.create_session("default")
485
530
  context = create_simple_context()
486
- self.logger.info(f"Created new session {new_session_id}")
531
+ self.logger.info(f"Created new session {new_session.id}")
487
532
 
488
533
  return context
489
534
 
@@ -501,18 +546,19 @@ class RunCommand(BaseCommand):
501
546
  if not success:
502
547
  self.logger.error("Session failed")
503
548
  return False
504
- else:
505
- # Interactive mode
506
- if getattr(args, "intercept_commands", False):
507
- wrapper_path = get_scripts_dir() / "interactive_wrapper.py"
508
- if wrapper_path.exists():
509
- print("Starting interactive session with command interception...")
510
- subprocess.run([sys.executable, str(wrapper_path)])
511
- else:
512
- self.logger.warning("Interactive wrapper not found, falling back to normal mode")
513
- runner.run_interactive(context)
549
+ # Interactive mode
550
+ elif getattr(args, "intercept_commands", False):
551
+ wrapper_path = get_scripts_dir() / "interactive_wrapper.py"
552
+ if wrapper_path.exists():
553
+ print("Starting interactive session with command interception...")
554
+ subprocess.run([sys.executable, str(wrapper_path)], check=False)
514
555
  else:
556
+ self.logger.warning(
557
+ "Interactive wrapper not found, falling back to normal mode"
558
+ )
515
559
  runner.run_interactive(context)
560
+ else:
561
+ runner.run_interactive(context)
516
562
 
517
563
  return True
518
564
 
@@ -520,17 +566,6 @@ class RunCommand(BaseCommand):
520
566
  self.logger.error(f"Session execution failed: {e}")
521
567
  return False
522
568
 
523
- def _is_socketio_server_running(self, port: int) -> bool:
524
- """Check if Socket.IO server is running on the specified port."""
525
- try:
526
- import socket
527
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
528
- s.settimeout(1)
529
- result = s.connect_ex(('localhost', port))
530
- return result == 0
531
- except Exception:
532
- return False
533
-
534
569
 
535
570
  def run_session(args):
536
571
  """
@@ -561,14 +596,14 @@ def run_session_legacy(args):
561
596
  args: Parsed command line arguments
562
597
  """
563
598
  # Set up startup logging to file early in the process
564
- startup_log_file = setup_startup_logging(Path.cwd())
565
-
599
+ setup_startup_logging(Path.cwd())
600
+
566
601
  logger = get_logger("cli")
567
602
  if args.logging != LogLevel.OFF.value:
568
603
  logger.info("Starting Claude MPM session")
569
- logger.info(f"Startup log: {startup_log_file}")
570
-
571
- # Clean up old startup logs (keep last 7 days or minimum 10 files)
604
+ # Log file already announced in startup_logging.py when created
605
+
606
+ # Clean up old startup logs (using configured retention count)
572
607
  try:
573
608
  deleted_count = cleanup_old_startup_logs(Path.cwd())
574
609
  if deleted_count > 0:
@@ -590,12 +625,10 @@ def run_session_legacy(args):
590
625
 
591
626
  try:
592
627
  from ...core.claude_runner import ClaudeRunner, create_simple_context
593
- from ...core.session_manager import SessionManager
594
628
  except ImportError:
595
629
  from claude_mpm.core.claude_runner import ClaudeRunner, create_simple_context
596
- from claude_mpm.core.session_manager import SessionManager
597
630
 
598
- # Handle session resumption
631
+ # Handle session resumption using the new SessionManager service
599
632
  session_manager = SessionManager()
600
633
  resume_session_id = None
601
634
  resume_context = None
@@ -605,7 +638,7 @@ def run_session_legacy(args):
605
638
  # Resume the last interactive session
606
639
  resume_session_id = session_manager.get_last_interactive_session()
607
640
  if resume_session_id:
608
- session_data = session_manager.get_session_by_id(resume_session_id)
641
+ session_data = session_manager.get_session_info(resume_session_id)
609
642
  if session_data:
610
643
  resume_context = session_data.get("context", "default")
611
644
  logger.info(
@@ -622,7 +655,7 @@ def run_session_legacy(args):
622
655
  else:
623
656
  # Resume specific session by ID
624
657
  resume_session_id = args.mpm_resume
625
- session_data = session_manager.get_session_by_id(resume_session_id)
658
+ session_data = session_manager.get_session_info(resume_session_id)
626
659
  if session_data:
627
660
  resume_context = session_data.get("context", "default")
628
661
  logger.info(
@@ -637,6 +670,14 @@ def run_session_legacy(args):
637
670
  print("💡 Use 'claude-mpm sessions' to list available sessions")
638
671
  return
639
672
 
673
+ # Deploy MPM slash commands to user's Claude configuration
674
+ try:
675
+ from ...services.command_deployment_service import deploy_commands_on_startup
676
+
677
+ deploy_commands_on_startup(force=False)
678
+ except Exception as e:
679
+ logger.debug(f"Failed to deploy MPM commands (non-critical): {e}")
680
+
640
681
  # Skip native agents if disabled
641
682
  if getattr(args, "no_native_agents", False):
642
683
  print("Native agents disabled")
@@ -689,7 +730,7 @@ def run_session_legacy(args):
689
730
 
690
731
  if can_prompt and missing_count > 0:
691
732
  # Interactive prompt for installation
692
- print(f"\n📦 Missing dependencies detected:")
733
+ print("\n📦 Missing dependencies detected:")
693
734
  for dep in results["summary"]["missing_python"][:5]:
694
735
  print(f" - {dep}")
695
736
  if missing_count > 5:
@@ -774,17 +815,17 @@ def run_session_legacy(args):
774
815
  # Create simple runner
775
816
  enable_tickets = not args.no_tickets
776
817
  raw_claude_args = getattr(args, "claude_args", []) or []
777
-
818
+
778
819
  # Add --resume to claude_args if the flag is set
779
820
  resume_flag_present = getattr(args, "resume", False)
780
821
  if resume_flag_present:
781
822
  logger.info("📌 --resume flag detected in args")
782
823
  if "--resume" not in raw_claude_args:
783
- raw_claude_args = ["--resume"] + raw_claude_args
824
+ raw_claude_args = ["--resume", *raw_claude_args]
784
825
  logger.info("✅ Added --resume to claude_args")
785
826
  else:
786
827
  logger.info("ℹ️ --resume already in claude_args")
787
-
828
+
788
829
  # Filter out claude-mpm specific flags before passing to Claude CLI
789
830
  logger.debug(f"Pre-filter claude_args: {raw_claude_args}")
790
831
  claude_args = filter_claude_mpm_args(raw_claude_args)
@@ -794,9 +835,9 @@ def run_session_legacy(args):
794
835
  if raw_claude_args != claude_args:
795
836
  filtered_out = list(set(raw_claude_args) - set(claude_args))
796
837
  logger.debug(f"Filtered out MPM-specific args: {filtered_out}")
797
-
838
+
798
839
  logger.info(f"Final claude_args being passed: {claude_args}")
799
-
840
+
800
841
  # Explicit verification of --resume flag
801
842
  if resume_flag_present:
802
843
  if "--resume" in claude_args:
@@ -814,11 +855,14 @@ def run_session_legacy(args):
814
855
 
815
856
  # Display Socket.IO server info if enabled
816
857
  if enable_websocket:
817
- # Auto-install Socket.IO dependencies if needed
858
+ # Use SocketIOManager for server management
859
+ socketio_manager = SocketIOManager(logger)
860
+
861
+ # Check dependencies
818
862
  print("🔧 Checking Socket.IO dependencies...")
819
- dependencies_ok, error_msg = ensure_socketio_dependencies(logger)
863
+ deps_ok, error_msg = socketio_manager.ensure_dependencies()
820
864
 
821
- if not dependencies_ok:
865
+ if not deps_ok:
822
866
  print(f"❌ Failed to install Socket.IO dependencies: {error_msg}")
823
867
  print(
824
868
  " Please install manually: pip install python-socketio aiohttp python-engineio"
@@ -828,32 +872,36 @@ def run_session_legacy(args):
828
872
  else:
829
873
  print("✓ Socket.IO dependencies ready")
830
874
 
831
- try:
832
- import socketio
833
-
834
- print(f"✓ Socket.IO server enabled at http://localhost:{websocket_port}")
835
- if launch_method == "exec":
836
- print(
837
- " Note: Socket.IO monitoring using exec mode with Claude Code hooks"
838
- )
839
-
840
- # Launch Socket.IO dashboard if in monitor mode
875
+ # Find available port and start server if in monitor mode
841
876
  if monitor_mode:
842
- success, browser_opened = launch_socketio_monitor(
843
- websocket_port, logger
877
+ websocket_port = socketio_manager.find_available_port(websocket_port)
878
+ success, server_info = socketio_manager.start_server(
879
+ port=websocket_port
844
880
  )
845
- if not success:
846
- print(f"⚠️ Failed to launch Socket.IO monitor")
881
+
882
+ if success:
883
+ print(f"✓ Socket.IO server enabled at {server_info.url}")
884
+ if launch_method == "exec":
885
+ print(
886
+ " Note: Socket.IO monitoring using exec mode with Claude Code hooks"
887
+ )
888
+
889
+ # Use DashboardLauncher for browser opening
890
+ dashboard_launcher = DashboardLauncher(logger)
891
+ monitor_url = dashboard_launcher.get_dashboard_url(websocket_port)
892
+ browser_opened = dashboard_launcher._open_browser(monitor_url)
893
+ args._browser_opened_by_cli = browser_opened
894
+
895
+ if not browser_opened:
896
+ print(f"💡 Monitor interface available at: {monitor_url}")
897
+ else:
898
+ print("⚠️ Failed to launch Socket.IO monitor")
847
899
  print(
848
900
  f" You can manually run: python scripts/launch_socketio_dashboard.py --port {websocket_port}"
849
901
  )
850
- # Store whether browser was opened by CLI for coordination with ClaudeRunner
851
- args._browser_opened_by_cli = browser_opened
852
- except ImportError as e:
853
- print(f"⚠️ Socket.IO still not available after installation attempt: {e}")
854
- print(" This might be a virtual environment issue.")
855
- print(" Try: pip install python-socketio aiohttp python-engineio")
856
- print(" Or: pip install claude-mpm[monitor]")
902
+ args._browser_opened_by_cli = False
903
+ else:
904
+ print(f"✓ Socket.IO ready (port: {websocket_port})")
857
905
 
858
906
  runner = ClaudeRunner(
859
907
  enable_tickets=enable_tickets,
@@ -880,16 +928,16 @@ def run_session_legacy(args):
880
928
  # For resumed sessions, create enhanced context with session information
881
929
  context = create_session_context(resume_session_id, session_manager)
882
930
  # Update session usage
883
- session_manager.active_sessions[resume_session_id][
884
- "last_used"
885
- ] = datetime.now().isoformat()
886
- session_manager.active_sessions[resume_session_id]["use_count"] += 1
887
- session_manager._save_sessions()
931
+ session = session_manager.load_session(resume_session_id)
932
+ if session:
933
+ session.last_used = datetime.now().isoformat()
934
+ session.use_count += 1
935
+ session_manager.save_session(session)
888
936
  else:
889
937
  # Create a new session for tracking
890
- new_session_id = session_manager.create_session("default")
938
+ new_session = session_manager.create_session("default")
891
939
  context = create_simple_context()
892
- logger.info(f"Created new session {new_session_id}")
940
+ logger.info(f"Created new session {new_session.id}")
893
941
 
894
942
  # For monitor mode, we handled everything in launch_socketio_monitor
895
943
  # No need for ClaudeRunner browser delegation
@@ -905,429 +953,72 @@ def run_session_legacy(args):
905
953
  success = runner.run_oneshot(user_input, context)
906
954
  if not success:
907
955
  logger.error("Session failed")
908
- else:
909
- # Interactive mode
910
- if getattr(args, "intercept_commands", False):
911
- wrapper_path = get_scripts_dir() / "interactive_wrapper.py"
912
- if wrapper_path.exists():
913
- print("Starting interactive session with command interception...")
914
- subprocess.run([sys.executable, str(wrapper_path)])
915
- else:
916
- logger.warning(
917
- "Interactive wrapper not found, falling back to normal mode"
918
- )
919
- runner.run_interactive(context)
956
+ # Interactive mode
957
+ elif getattr(args, "intercept_commands", False):
958
+ wrapper_path = get_scripts_dir() / "interactive_wrapper.py"
959
+ if wrapper_path.exists():
960
+ print("Starting interactive session with command interception...")
961
+ subprocess.run([sys.executable, str(wrapper_path)], check=False)
920
962
  else:
963
+ logger.warning("Interactive wrapper not found, falling back to normal mode")
921
964
  runner.run_interactive(context)
965
+ else:
966
+ runner.run_interactive(context)
922
967
 
923
968
 
969
+ # Legacy helper functions - now delegating to SocketIOManager
924
970
  def launch_socketio_monitor(port, logger):
925
- """Launch the Socket.IO monitoring dashboard."""
926
- from .socketio_monitor import SocketIOMonitor
971
+ """Launch the Socket.IO monitoring dashboard (legacy compatibility)."""
972
+ socketio_manager = SocketIOManager(logger)
973
+ success, server_info = socketio_manager.start_server(port=port)
927
974
 
928
- monitor = SocketIOMonitor(logger)
929
- return monitor.launch_monitor(port)
975
+ if success:
976
+ # Open browser using DashboardLauncher
977
+ launcher = DashboardLauncher(logger)
978
+ browser_opened = launcher._open_browser(server_info.url)
979
+ return success, browser_opened
930
980
 
931
-
932
- # Socket.IO monitoring functions moved to socketio_monitor.py
981
+ return False, False
933
982
 
934
983
 
935
984
  def _check_socketio_server_running(port, logger):
936
- """Check if a Socket.IO server is running on the specified port."""
937
- from .socketio_monitor import SocketIOMonitor
938
-
939
- monitor = SocketIOMonitor(logger)
940
- return monitor.check_server_running(port)
985
+ """Check if a Socket.IO server is running on the specified port (legacy compatibility)."""
986
+ socketio_manager = SocketIOManager(logger)
987
+ return socketio_manager.is_server_running(port)
941
988
 
942
989
 
943
990
  def _start_standalone_socketio_server(port, logger):
944
- """Start a standalone Socket.IO server using the Python daemon."""
945
- from .socketio_monitor import SocketIOMonitor
946
-
947
- monitor = SocketIOMonitor(logger)
948
- return monitor.start_standalone_server(port)
949
- """
950
- Start a standalone Socket.IO server using the Python daemon.
951
-
952
- WHY: For monitor mode, we want a persistent server that runs independently
953
- of the Claude session. This allows users to monitor multiple sessions and
954
- keeps the dashboard available even when Claude isn't running.
955
-
956
- DESIGN DECISION: We use a pure Python daemon script to manage the server
957
- process. This avoids Node.js dependencies (like PM2) and provides proper
958
- process management with PID tracking.
959
-
960
- Args:
961
- port: Port number for the server
962
- logger: Logger instance for output
963
-
964
- Returns:
965
- bool: True if server started successfully, False otherwise
966
- """
967
- try:
968
- import subprocess
969
-
970
- from ...core.unified_paths import get_scripts_dir
971
-
972
- # Get path to daemon script in package
973
- daemon_script = get_package_root() / "scripts" / "socketio_daemon.py"
974
-
975
- if not daemon_script.exists():
976
- logger.error(f"Socket.IO daemon script not found: {daemon_script}")
977
- return False
978
-
979
- logger.info(f"Starting Socket.IO server daemon on port {port}")
980
-
981
- # Start the daemon
982
- result = subprocess.run(
983
- [sys.executable, str(daemon_script), "start"],
984
- capture_output=True,
985
- text=True,
986
- )
987
-
988
- if result.returncode != 0:
989
- logger.error(f"Failed to start Socket.IO daemon: {result.stderr}")
990
- return False
991
-
992
- # Wait for server using event-based polling instead of fixed delays
993
- # WHY: Replace fixed sleep delays with active polling for faster startup detection
994
- max_wait_time = 15 # Maximum 15 seconds
995
- poll_interval = 0.1 # Start with 100ms polling
996
-
997
- logger.info(f"Waiting up to {max_wait_time} seconds for server to be ready...")
998
-
999
- # Give daemon minimal time to fork
1000
- time.sleep(0.2) # Reduced from 0.5s
1001
-
1002
- start_time = time.time()
1003
- attempt = 0
1004
-
1005
- while time.time() - start_time < max_wait_time:
1006
- attempt += 1
1007
- elapsed = time.time() - start_time
1008
-
1009
- logger.debug(
1010
- f"Checking server readiness (attempt {attempt}, elapsed {elapsed:.1f}s)"
1011
- )
1012
-
1013
- # Adaptive polling - start fast, slow down over time
1014
- if elapsed < 2:
1015
- poll_interval = 0.1 # 100ms for first 2 seconds
1016
- elif elapsed < 5:
1017
- poll_interval = 0.25 # 250ms for next 3 seconds
1018
- else:
1019
- poll_interval = 0.5 # 500ms after 5 seconds
1020
-
1021
- time.sleep(poll_interval)
1022
-
1023
- # Check if the daemon server is accepting connections
1024
- if _check_socketio_server_running(port, logger):
1025
- logger.info(
1026
- f"✅ Standalone Socket.IO server started successfully on port {port}"
1027
- )
1028
- logger.info(f"🕐 Server ready after {attempt} attempts ({elapsed:.1f}s)")
1029
- return True
1030
- else:
1031
- logger.debug(
1032
- f"Server not yet accepting connections on attempt {attempt}"
1033
- )
1034
-
1035
- # Timeout reached
1036
- elapsed_total = time.time() - start_time
1037
- logger.error(
1038
- f"❌ Socket.IO server health check failed after {max_wait_time}s timeout ({attempt} attempts)"
1039
- )
1040
- logger.warning(
1041
- f"⏱️ Server may still be starting - try waiting a few more seconds"
1042
- )
1043
- logger.warning(
1044
- f"💡 The daemon process might be running but not yet accepting HTTP connections"
1045
- )
1046
- logger.error(f"🔧 Troubleshooting steps:")
1047
- logger.error(f" - Wait a few more seconds and try again")
1048
- logger.error(f" - Check for port conflicts: lsof -i :{port}")
1049
- logger.error(f" - Try a different port with --websocket-port")
1050
- logger.error(f" - Verify dependencies: pip install python-socketio aiohttp")
1051
- return False
1052
-
1053
- except Exception as e:
1054
- logger.error(f"❌ Failed to start standalone Socket.IO server: {e}")
1055
- import traceback
1056
-
1057
- logger.error(f"📋 Stack trace: {traceback.format_exc()}")
1058
- logger.error(
1059
- f"💡 This may be a dependency issue - try: pip install python-socketio aiohttp"
1060
- )
1061
- return False
991
+ """Start a standalone Socket.IO server (legacy compatibility)."""
992
+ socketio_manager = SocketIOManager(logger)
993
+ success, _ = socketio_manager.start_server(port=port)
994
+ return success
1062
995
 
1063
996
 
1064
997
  def open_in_browser_tab(url, logger):
1065
998
  """Open URL in browser, attempting to reuse existing tabs when possible."""
1066
- from .socketio_monitor import SocketIOMonitor
1067
-
1068
- monitor = SocketIOMonitor(logger)
1069
- return monitor.open_in_browser_tab(url)
1070
- """
1071
- Open URL in browser, attempting to reuse existing tabs when possible.
1072
-
1073
- WHY: Users prefer reusing browser tabs instead of opening new ones constantly.
1074
- This function attempts platform-specific solutions for tab reuse.
1075
-
1076
- DESIGN DECISION: We try different methods based on platform capabilities,
1077
- falling back to standard webbrowser.open() if needed.
1078
-
1079
- Args:
1080
- url: URL to open
1081
- logger: Logger instance for output
1082
- """
1083
- try:
1084
- # Platform-specific optimizations for tab reuse
1085
- import platform
1086
-
1087
- system = platform.system().lower()
1088
-
1089
- if system == "darwin": # macOS
1090
- # Just use the standard webbrowser module on macOS
1091
- # The AppleScript approach is too unreliable
1092
- webbrowser.open(url, new=0, autoraise=True) # new=0 tries to reuse window
1093
- logger.info("Opened browser on macOS")
1094
-
1095
- elif system == "linux":
1096
- # On Linux, try to use existing browser session
1097
- try:
1098
- # This is a best-effort approach for common browsers
1099
- webbrowser.get().open(
1100
- url, new=0
1101
- ) # new=0 tries to reuse existing window
1102
- logger.info("Attempted Linux browser tab reuse")
1103
- except Exception:
1104
- webbrowser.open(url, autoraise=True)
1105
-
1106
- elif system == "windows":
1107
- # On Windows, try to use existing browser
1108
- try:
1109
- webbrowser.get().open(
1110
- url, new=0
1111
- ) # new=0 tries to reuse existing window
1112
- logger.info("Attempted Windows browser tab reuse")
1113
- except Exception:
1114
- webbrowser.open(url, autoraise=True)
1115
- else:
1116
- # Unknown platform, use standard opening
1117
- webbrowser.open(url, autoraise=True)
1118
-
1119
- except Exception as e:
1120
- logger.warning(f"Browser opening failed: {e}")
1121
- # Final fallback
1122
- webbrowser.open(url)
999
+ launcher = DashboardLauncher(logger)
1000
+ return launcher._open_browser(url)
1123
1001
 
1124
1002
 
1125
1003
  def _check_claude_json_memory(args, logger):
1126
1004
  """Check .claude.json file size and warn about memory issues."""
1127
- from .run_config_checker import RunConfigChecker
1128
-
1129
- checker = RunConfigChecker(logger)
1130
- checker.check_claude_json_memory(args)
1131
- """Check .claude.json file size and warn about memory issues.
1005
+ # Use new StartupCheckerService
1006
+ from ...core.config import Config
1132
1007
 
1133
- WHY: Large .claude.json files (>500KB) cause significant memory issues when
1134
- using --resume. Claude Code loads the entire conversation history into
1135
- memory, leading to 2GB+ memory consumption.
1136
-
1137
- DESIGN DECISIONS:
1138
- - Warn at 500KB (conservative threshold)
1139
- - Suggest cleanup command for remediation
1140
- - Allow bypass with --force flag
1141
- - Only check when using --resume
1142
-
1143
- Args:
1144
- args: Parsed command line arguments
1145
- logger: Logger instance for output
1146
- """
1147
- # Only check if using --mpm-resume
1148
- if not hasattr(args, "mpm_resume") or not args.mpm_resume:
1149
- return
1150
-
1151
- claude_json_path = Path.home() / ".claude.json"
1152
-
1153
- # Check if file exists
1154
- if not claude_json_path.exists():
1155
- logger.debug("No .claude.json file found")
1156
- return
1157
-
1158
- # Check file size
1159
- file_size = claude_json_path.stat().st_size
1160
-
1161
- # Format size for display
1162
- def format_size(size_bytes):
1163
- for unit in ["B", "KB", "MB", "GB"]:
1164
- if size_bytes < 1024.0:
1165
- return f"{size_bytes:.1f}{unit}"
1166
- size_bytes /= 1024.0
1167
- return f"{size_bytes:.1f}TB"
1168
-
1169
- # Get thresholds from configuration
1170
- try:
1171
- config_loader = ConfigLoader()
1172
- config = config_loader.load_main_config()
1173
- memory_config = config.get("memory_management", {})
1174
- warning_threshold = (
1175
- memory_config.get("claude_json_warning_threshold_kb", 500) * 1024
1176
- )
1177
- critical_threshold = (
1178
- memory_config.get("claude_json_critical_threshold_kb", 1024) * 1024
1179
- )
1180
- except Exception as e:
1181
- logger.debug(f"Could not load memory configuration: {e}")
1182
- # Fall back to defaults
1183
- warning_threshold = 500 * 1024 # 500KB
1184
- critical_threshold = 1024 * 1024 # 1MB
1185
-
1186
- if file_size > critical_threshold:
1187
- print(
1188
- f"\n⚠️ CRITICAL: Large .claude.json file detected ({format_size(file_size)})"
1189
- )
1190
- print(f" This WILL cause memory issues when using --resume")
1191
- print(f" Claude Code may consume 2GB+ of memory\n")
1192
-
1193
- if not getattr(args, "force", False):
1194
- print(" Recommended actions:")
1195
- print(" 1. Run 'claude-mpm cleanup-memory' to archive old conversations")
1196
- print(" 2. Use --force to bypass this warning (not recommended)")
1197
- sys.stdout.flush() # Ensure prompt is displayed before input
1198
-
1199
- # Check if we're in a TTY environment for proper input handling
1200
- if not sys.stdin.isatty():
1201
- # In non-TTY environment (like pipes), use readline
1202
- print(
1203
- "\n Would you like to continue anyway? [y/N]: ",
1204
- end="",
1205
- flush=True,
1206
- )
1207
- try:
1208
- response = sys.stdin.readline().strip().lower()
1209
- # Handle various line endings and control characters
1210
- response = response.replace("\r", "").replace("\n", "").strip()
1211
- except (EOFError, KeyboardInterrupt):
1212
- response = "n"
1213
- else:
1214
- # In TTY environment, use normal input()
1215
- print(
1216
- "\n Would you like to continue anyway? [y/N]: ",
1217
- end="",
1218
- flush=True,
1219
- )
1220
- try:
1221
- response = input().strip().lower()
1222
- except (EOFError, KeyboardInterrupt):
1223
- response = "n"
1224
-
1225
- if response != "y":
1226
- print(
1227
- "\n✅ Session cancelled. Run 'claude-mpm cleanup-memory' to fix this issue."
1228
- )
1229
- sys.exit(0)
1230
-
1231
- elif file_size > warning_threshold:
1232
- print(
1233
- f"\n⚠️ Warning: .claude.json file is getting large ({format_size(file_size)})"
1234
- )
1235
- print(" This may cause memory issues when using --resume")
1236
- print(
1237
- " 💡 Consider running 'claude-mpm cleanup-memory' to archive old conversations\n"
1238
- )
1239
- # Just warn, don't block execution
1240
-
1241
- logger.info(f".claude.json size: {format_size(file_size)}")
1008
+ config_service = Config()
1009
+ checker = StartupCheckerService(config_service)
1010
+ resume_enabled = getattr(args, "mpm_resume", False)
1011
+ warning = checker.check_memory(resume_enabled)
1012
+ if warning:
1013
+ checker.display_warnings([warning])
1242
1014
 
1243
1015
 
1244
1016
  def _check_configuration_health(logger):
1245
1017
  """Check configuration health at startup and warn about issues."""
1246
- from .run_config_checker import RunConfigChecker
1247
-
1248
- checker = RunConfigChecker(logger)
1249
- checker.check_configuration_health()
1250
- """Check configuration health at startup and warn about issues.
1251
-
1252
- WHY: Configuration errors can cause silent failures, especially for response
1253
- logging. This function proactively checks configuration at startup and warns
1254
- users about any issues, providing actionable guidance.
1255
-
1256
- DESIGN DECISIONS:
1257
- - Non-blocking: Issues are logged as warnings, not errors
1258
- - Actionable: Provides specific commands to fix issues
1259
- - Focused: Only checks critical configuration that affects runtime
1260
-
1261
- Args:
1262
- logger: Logger instance for output
1263
- """
1264
- try:
1265
- # Load configuration using ConfigLoader
1266
- config_loader = ConfigLoader()
1267
- config = config_loader.load_main_config()
1268
-
1269
- # Validate configuration
1270
- is_valid, errors, warnings = config.validate_configuration()
1271
-
1272
- # Get configuration status for additional context
1273
- status = config.get_configuration_status()
1274
-
1275
- # Report critical errors that will affect functionality
1276
- if errors:
1277
- logger.warning("⚠️ Configuration issues detected:")
1278
- for error in errors[:3]: # Show first 3 errors
1279
- logger.warning(f" • {error}")
1280
- if len(errors) > 3:
1281
- logger.warning(f" • ... and {len(errors) - 3} more")
1282
- logger.info(
1283
- "💡 Run 'claude-mpm config validate' to see all issues and fixes"
1284
- )
1018
+ # Use new StartupCheckerService
1019
+ from ...core.config import Config
1285
1020
 
1286
- # Check response logging specifically since it's commonly misconfigured
1287
- response_logging_enabled = config.get("response_logging.enabled", False)
1288
- if not response_logging_enabled:
1289
- logger.debug(
1290
- "Response logging is disabled (response_logging.enabled=false)"
1291
- )
1292
- else:
1293
- # Check if session directory is writable
1294
- session_dir = Path(
1295
- config.get(
1296
- "response_logging.session_directory", ".claude-mpm/responses"
1297
- )
1298
- )
1299
- if not session_dir.is_absolute():
1300
- session_dir = Path.cwd() / session_dir
1301
-
1302
- if not session_dir.exists():
1303
- try:
1304
- session_dir.mkdir(parents=True, exist_ok=True)
1305
- logger.debug(f"Created response logging directory: {session_dir}")
1306
- except Exception as e:
1307
- logger.warning(
1308
- f"Cannot create response logging directory {session_dir}: {e}"
1309
- )
1310
- logger.info("💡 Fix with: mkdir -p " + str(session_dir))
1311
- elif not os.access(session_dir, os.W_OK):
1312
- logger.warning(
1313
- f"Response logging directory is not writable: {session_dir}"
1314
- )
1315
- logger.info("💡 Fix with: chmod 755 " + str(session_dir))
1316
-
1317
- # Report non-critical warnings (only in debug mode)
1318
- if warnings and logger.isEnabledFor(logging.DEBUG):
1319
- logger.debug("Configuration warnings:")
1320
- for warning in warnings:
1321
- logger.debug(f" • {warning}")
1322
-
1323
- # Log loaded configuration source for debugging
1324
- if status.get("loaded_from") and status["loaded_from"] != "defaults":
1325
- logger.debug(f"Configuration loaded from: {status['loaded_from']}")
1326
-
1327
- except Exception as e:
1328
- # Don't let configuration check errors prevent startup
1329
- logger.debug(f"Configuration check failed (non-critical): {e}")
1330
- # Only show user-facing message if it's likely to affect them
1331
- if "yaml" in str(e).lower():
1332
- logger.warning("⚠️ Configuration file may have YAML syntax errors")
1333
- logger.info("💡 Validate with: claude-mpm config validate")
1021
+ config_service = Config()
1022
+ checker = StartupCheckerService(config_service)
1023
+ warnings = checker.check_configuration()
1024
+ checker.display_warnings(warnings)