claude-mpm 3.9.11__py3-none-any.whl → 4.0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +2 -2
  3. claude_mpm/__main__.py +3 -2
  4. claude_mpm/agents/__init__.py +85 -79
  5. claude_mpm/agents/agent_loader.py +464 -1003
  6. claude_mpm/agents/agent_loader_integration.py +45 -45
  7. claude_mpm/agents/agents_metadata.py +29 -30
  8. claude_mpm/agents/async_agent_loader.py +156 -138
  9. claude_mpm/agents/base_agent.json +1 -1
  10. claude_mpm/agents/base_agent_loader.py +179 -151
  11. claude_mpm/agents/frontmatter_validator.py +229 -130
  12. claude_mpm/agents/schema/agent_schema.json +1 -1
  13. claude_mpm/agents/system_agent_config.py +213 -147
  14. claude_mpm/agents/templates/__init__.py +13 -13
  15. claude_mpm/agents/templates/code_analyzer.json +2 -2
  16. claude_mpm/agents/templates/data_engineer.json +1 -1
  17. claude_mpm/agents/templates/documentation.json +23 -11
  18. claude_mpm/agents/templates/engineer.json +22 -6
  19. claude_mpm/agents/templates/memory_manager.json +1 -1
  20. claude_mpm/agents/templates/ops.json +2 -2
  21. claude_mpm/agents/templates/project_organizer.json +1 -1
  22. claude_mpm/agents/templates/qa.json +1 -1
  23. claude_mpm/agents/templates/refactoring_engineer.json +222 -0
  24. claude_mpm/agents/templates/research.json +20 -14
  25. claude_mpm/agents/templates/security.json +1 -1
  26. claude_mpm/agents/templates/ticketing.json +2 -2
  27. claude_mpm/agents/templates/version_control.json +1 -1
  28. claude_mpm/agents/templates/web_qa.json +3 -1
  29. claude_mpm/agents/templates/web_ui.json +2 -2
  30. claude_mpm/cli/__init__.py +79 -51
  31. claude_mpm/cli/__main__.py +3 -2
  32. claude_mpm/cli/commands/__init__.py +20 -20
  33. claude_mpm/cli/commands/agents.py +279 -247
  34. claude_mpm/cli/commands/aggregate.py +138 -157
  35. claude_mpm/cli/commands/cleanup.py +147 -147
  36. claude_mpm/cli/commands/config.py +93 -76
  37. claude_mpm/cli/commands/info.py +17 -16
  38. claude_mpm/cli/commands/mcp.py +140 -905
  39. claude_mpm/cli/commands/mcp_command_router.py +139 -0
  40. claude_mpm/cli/commands/mcp_config_commands.py +20 -0
  41. claude_mpm/cli/commands/mcp_install_commands.py +20 -0
  42. claude_mpm/cli/commands/mcp_server_commands.py +175 -0
  43. claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
  44. claude_mpm/cli/commands/memory.py +239 -203
  45. claude_mpm/cli/commands/monitor.py +330 -86
  46. claude_mpm/cli/commands/run.py +380 -429
  47. claude_mpm/cli/commands/run_config_checker.py +160 -0
  48. claude_mpm/cli/commands/socketio_monitor.py +235 -0
  49. claude_mpm/cli/commands/tickets.py +363 -220
  50. claude_mpm/cli/parser.py +24 -1156
  51. claude_mpm/cli/parsers/__init__.py +29 -0
  52. claude_mpm/cli/parsers/agents_parser.py +136 -0
  53. claude_mpm/cli/parsers/base_parser.py +331 -0
  54. claude_mpm/cli/parsers/config_parser.py +85 -0
  55. claude_mpm/cli/parsers/mcp_parser.py +152 -0
  56. claude_mpm/cli/parsers/memory_parser.py +138 -0
  57. claude_mpm/cli/parsers/monitor_parser.py +124 -0
  58. claude_mpm/cli/parsers/run_parser.py +147 -0
  59. claude_mpm/cli/parsers/tickets_parser.py +203 -0
  60. claude_mpm/cli/ticket_cli.py +7 -3
  61. claude_mpm/cli/utils.py +55 -37
  62. claude_mpm/cli_module/__init__.py +6 -6
  63. claude_mpm/cli_module/args.py +188 -140
  64. claude_mpm/cli_module/commands.py +79 -70
  65. claude_mpm/cli_module/migration_example.py +38 -60
  66. claude_mpm/config/__init__.py +32 -25
  67. claude_mpm/config/agent_config.py +151 -119
  68. claude_mpm/config/experimental_features.py +71 -73
  69. claude_mpm/config/paths.py +94 -208
  70. claude_mpm/config/socketio_config.py +84 -73
  71. claude_mpm/constants.py +35 -18
  72. claude_mpm/core/__init__.py +9 -6
  73. claude_mpm/core/agent_name_normalizer.py +68 -71
  74. claude_mpm/core/agent_registry.py +372 -521
  75. claude_mpm/core/agent_session_manager.py +74 -63
  76. claude_mpm/core/base_service.py +116 -87
  77. claude_mpm/core/cache.py +119 -153
  78. claude_mpm/core/claude_runner.py +425 -1120
  79. claude_mpm/core/config.py +263 -168
  80. claude_mpm/core/config_aliases.py +69 -61
  81. claude_mpm/core/config_constants.py +292 -0
  82. claude_mpm/core/constants.py +57 -99
  83. claude_mpm/core/container.py +211 -178
  84. claude_mpm/core/exceptions.py +233 -89
  85. claude_mpm/core/factories.py +92 -54
  86. claude_mpm/core/framework_loader.py +378 -220
  87. claude_mpm/core/hook_manager.py +198 -83
  88. claude_mpm/core/hook_performance_config.py +136 -0
  89. claude_mpm/core/injectable_service.py +61 -55
  90. claude_mpm/core/interactive_session.py +165 -155
  91. claude_mpm/core/interfaces.py +221 -195
  92. claude_mpm/core/lazy.py +96 -96
  93. claude_mpm/core/logger.py +133 -107
  94. claude_mpm/core/logging_config.py +185 -157
  95. claude_mpm/core/minimal_framework_loader.py +20 -15
  96. claude_mpm/core/mixins.py +30 -29
  97. claude_mpm/core/oneshot_session.py +215 -181
  98. claude_mpm/core/optimized_agent_loader.py +134 -138
  99. claude_mpm/core/optimized_startup.py +159 -157
  100. claude_mpm/core/pm_hook_interceptor.py +85 -72
  101. claude_mpm/core/service_registry.py +103 -101
  102. claude_mpm/core/session_manager.py +97 -87
  103. claude_mpm/core/socketio_pool.py +212 -158
  104. claude_mpm/core/tool_access_control.py +58 -51
  105. claude_mpm/core/types.py +46 -24
  106. claude_mpm/core/typing_utils.py +166 -82
  107. claude_mpm/core/unified_agent_registry.py +721 -0
  108. claude_mpm/core/unified_config.py +550 -0
  109. claude_mpm/core/unified_paths.py +549 -0
  110. claude_mpm/dashboard/index.html +1 -1
  111. claude_mpm/dashboard/open_dashboard.py +51 -17
  112. claude_mpm/dashboard/static/built/components/agent-inference.js +2 -0
  113. claude_mpm/dashboard/static/built/components/event-processor.js +2 -0
  114. claude_mpm/dashboard/static/built/components/event-viewer.js +2 -0
  115. claude_mpm/dashboard/static/built/components/export-manager.js +2 -0
  116. claude_mpm/dashboard/static/built/components/file-tool-tracker.js +2 -0
  117. claude_mpm/dashboard/static/built/components/hud-library-loader.js +2 -0
  118. claude_mpm/dashboard/static/built/components/hud-manager.js +2 -0
  119. claude_mpm/dashboard/static/built/components/hud-visualizer.js +2 -0
  120. claude_mpm/dashboard/static/built/components/module-viewer.js +2 -0
  121. claude_mpm/dashboard/static/built/components/session-manager.js +2 -0
  122. claude_mpm/dashboard/static/built/components/socket-manager.js +2 -0
  123. claude_mpm/dashboard/static/built/components/ui-state-manager.js +2 -0
  124. claude_mpm/dashboard/static/built/components/working-directory.js +2 -0
  125. claude_mpm/dashboard/static/built/dashboard.js +2 -0
  126. claude_mpm/dashboard/static/built/socket-client.js +2 -0
  127. claude_mpm/dashboard/static/css/dashboard.css +27 -8
  128. claude_mpm/dashboard/static/dist/components/agent-inference.js +2 -0
  129. claude_mpm/dashboard/static/dist/components/event-processor.js +2 -0
  130. claude_mpm/dashboard/static/dist/components/event-viewer.js +2 -0
  131. claude_mpm/dashboard/static/dist/components/export-manager.js +2 -0
  132. claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +2 -0
  133. claude_mpm/dashboard/static/dist/components/hud-library-loader.js +2 -0
  134. claude_mpm/dashboard/static/dist/components/hud-manager.js +2 -0
  135. claude_mpm/dashboard/static/dist/components/hud-visualizer.js +2 -0
  136. claude_mpm/dashboard/static/dist/components/module-viewer.js +2 -0
  137. claude_mpm/dashboard/static/dist/components/session-manager.js +2 -0
  138. claude_mpm/dashboard/static/dist/components/socket-manager.js +2 -0
  139. claude_mpm/dashboard/static/dist/components/ui-state-manager.js +2 -0
  140. claude_mpm/dashboard/static/dist/components/working-directory.js +2 -0
  141. claude_mpm/dashboard/static/dist/dashboard.js +2 -0
  142. claude_mpm/dashboard/static/dist/socket-client.js +2 -0
  143. claude_mpm/dashboard/static/js/components/agent-inference.js +80 -76
  144. claude_mpm/dashboard/static/js/components/event-processor.js +71 -67
  145. claude_mpm/dashboard/static/js/components/event-viewer.js +93 -72
  146. claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
  147. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +110 -96
  148. claude_mpm/dashboard/static/js/components/hud-library-loader.js +11 -11
  149. claude_mpm/dashboard/static/js/components/hud-manager.js +73 -73
  150. claude_mpm/dashboard/static/js/components/hud-visualizer.js +163 -163
  151. claude_mpm/dashboard/static/js/components/module-viewer.js +305 -233
  152. claude_mpm/dashboard/static/js/components/session-manager.js +32 -29
  153. claude_mpm/dashboard/static/js/components/socket-manager.js +27 -20
  154. claude_mpm/dashboard/static/js/components/ui-state-manager.js +21 -18
  155. claude_mpm/dashboard/static/js/components/working-directory.js +74 -71
  156. claude_mpm/dashboard/static/js/dashboard.js +178 -453
  157. claude_mpm/dashboard/static/js/extension-error-handler.js +164 -0
  158. claude_mpm/dashboard/static/js/socket-client.js +133 -53
  159. claude_mpm/dashboard/templates/index.html +40 -50
  160. claude_mpm/experimental/cli_enhancements.py +60 -58
  161. claude_mpm/generators/__init__.py +1 -1
  162. claude_mpm/generators/agent_profile_generator.py +75 -65
  163. claude_mpm/hooks/__init__.py +1 -1
  164. claude_mpm/hooks/base_hook.py +33 -28
  165. claude_mpm/hooks/claude_hooks/__init__.py +1 -1
  166. claude_mpm/hooks/claude_hooks/connection_pool.py +120 -0
  167. claude_mpm/hooks/claude_hooks/event_handlers.py +743 -0
  168. claude_mpm/hooks/claude_hooks/hook_handler.py +415 -1331
  169. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +4 -4
  170. claude_mpm/hooks/claude_hooks/memory_integration.py +221 -0
  171. claude_mpm/hooks/claude_hooks/response_tracking.py +348 -0
  172. claude_mpm/hooks/claude_hooks/tool_analysis.py +230 -0
  173. claude_mpm/hooks/memory_integration_hook.py +140 -100
  174. claude_mpm/hooks/tool_call_interceptor.py +89 -76
  175. claude_mpm/hooks/validation_hooks.py +57 -49
  176. claude_mpm/init.py +145 -121
  177. claude_mpm/models/__init__.py +9 -9
  178. claude_mpm/models/agent_definition.py +33 -23
  179. claude_mpm/models/agent_session.py +228 -200
  180. claude_mpm/scripts/__init__.py +1 -1
  181. claude_mpm/scripts/socketio_daemon.py +192 -75
  182. claude_mpm/scripts/socketio_server_manager.py +328 -0
  183. claude_mpm/scripts/start_activity_logging.py +25 -22
  184. claude_mpm/services/__init__.py +68 -43
  185. claude_mpm/services/agent_capabilities_service.py +271 -0
  186. claude_mpm/services/agents/__init__.py +23 -32
  187. claude_mpm/services/agents/deployment/__init__.py +3 -3
  188. claude_mpm/services/agents/deployment/agent_config_provider.py +310 -0
  189. claude_mpm/services/agents/deployment/agent_configuration_manager.py +359 -0
  190. claude_mpm/services/agents/deployment/agent_definition_factory.py +84 -0
  191. claude_mpm/services/agents/deployment/agent_deployment.py +415 -2113
  192. claude_mpm/services/agents/deployment/agent_discovery_service.py +387 -0
  193. claude_mpm/services/agents/deployment/agent_environment_manager.py +293 -0
  194. claude_mpm/services/agents/deployment/agent_filesystem_manager.py +387 -0
  195. claude_mpm/services/agents/deployment/agent_format_converter.py +453 -0
  196. claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +161 -0
  197. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +345 -495
  198. claude_mpm/services/agents/deployment/agent_metrics_collector.py +279 -0
  199. claude_mpm/services/agents/deployment/agent_restore_handler.py +88 -0
  200. claude_mpm/services/agents/deployment/agent_template_builder.py +406 -0
  201. claude_mpm/services/agents/deployment/agent_validator.py +352 -0
  202. claude_mpm/services/agents/deployment/agent_version_manager.py +313 -0
  203. claude_mpm/services/agents/deployment/agent_versioning.py +6 -9
  204. claude_mpm/services/agents/deployment/agents_directory_resolver.py +79 -0
  205. claude_mpm/services/agents/deployment/async_agent_deployment.py +298 -234
  206. claude_mpm/services/agents/deployment/config/__init__.py +13 -0
  207. claude_mpm/services/agents/deployment/config/deployment_config.py +182 -0
  208. claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
  209. claude_mpm/services/agents/deployment/deployment_config_loader.py +54 -0
  210. claude_mpm/services/agents/deployment/deployment_type_detector.py +124 -0
  211. claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
  212. claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
  213. claude_mpm/services/agents/deployment/facade/deployment_executor.py +73 -0
  214. claude_mpm/services/agents/deployment/facade/deployment_facade.py +270 -0
  215. claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
  216. claude_mpm/services/agents/deployment/interface_adapter.py +227 -0
  217. claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
  218. claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
  219. claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
  220. claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
  221. claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +159 -0
  222. claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
  223. claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
  224. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +195 -0
  225. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +119 -0
  226. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +79 -0
  227. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +90 -0
  228. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +100 -0
  229. claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
  230. claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +98 -0
  231. claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
  232. claude_mpm/services/agents/deployment/processors/agent_processor.py +258 -0
  233. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +318 -0
  234. claude_mpm/services/agents/deployment/results/__init__.py +13 -0
  235. claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
  236. claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
  237. claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
  238. claude_mpm/services/agents/deployment/strategies/base_strategy.py +119 -0
  239. claude_mpm/services/agents/deployment/strategies/project_strategy.py +150 -0
  240. claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
  241. claude_mpm/services/agents/deployment/strategies/system_strategy.py +116 -0
  242. claude_mpm/services/agents/deployment/strategies/user_strategy.py +137 -0
  243. claude_mpm/services/agents/deployment/system_instructions_deployer.py +108 -0
  244. claude_mpm/services/agents/deployment/validation/__init__.py +19 -0
  245. claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
  246. claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
  247. claude_mpm/services/agents/deployment/validation/template_validator.py +299 -0
  248. claude_mpm/services/agents/deployment/validation/validation_result.py +226 -0
  249. claude_mpm/services/agents/loading/__init__.py +2 -2
  250. claude_mpm/services/agents/loading/agent_profile_loader.py +259 -229
  251. claude_mpm/services/agents/loading/base_agent_manager.py +90 -81
  252. claude_mpm/services/agents/loading/framework_agent_loader.py +154 -129
  253. claude_mpm/services/agents/management/__init__.py +2 -2
  254. claude_mpm/services/agents/management/agent_capabilities_generator.py +72 -58
  255. claude_mpm/services/agents/management/agent_management_service.py +209 -156
  256. claude_mpm/services/agents/memory/__init__.py +9 -6
  257. claude_mpm/services/agents/memory/agent_memory_manager.py +218 -1152
  258. claude_mpm/services/agents/memory/agent_persistence_service.py +20 -16
  259. claude_mpm/services/agents/memory/analyzer.py +430 -0
  260. claude_mpm/services/agents/memory/content_manager.py +376 -0
  261. claude_mpm/services/agents/memory/template_generator.py +468 -0
  262. claude_mpm/services/agents/registry/__init__.py +7 -10
  263. claude_mpm/services/agents/registry/deployed_agent_discovery.py +122 -97
  264. claude_mpm/services/agents/registry/modification_tracker.py +351 -285
  265. claude_mpm/services/async_session_logger.py +187 -153
  266. claude_mpm/services/claude_session_logger.py +87 -72
  267. claude_mpm/services/command_handler_service.py +217 -0
  268. claude_mpm/services/communication/__init__.py +3 -2
  269. claude_mpm/services/core/__init__.py +50 -97
  270. claude_mpm/services/core/base.py +60 -53
  271. claude_mpm/services/core/interfaces/__init__.py +188 -0
  272. claude_mpm/services/core/interfaces/agent.py +351 -0
  273. claude_mpm/services/core/interfaces/communication.py +343 -0
  274. claude_mpm/services/core/interfaces/infrastructure.py +413 -0
  275. claude_mpm/services/core/interfaces/service.py +434 -0
  276. claude_mpm/services/core/interfaces.py +19 -944
  277. claude_mpm/services/event_aggregator.py +208 -170
  278. claude_mpm/services/exceptions.py +387 -308
  279. claude_mpm/services/framework_claude_md_generator/__init__.py +75 -79
  280. claude_mpm/services/framework_claude_md_generator/content_assembler.py +69 -60
  281. claude_mpm/services/framework_claude_md_generator/content_validator.py +65 -61
  282. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +68 -49
  283. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +34 -34
  284. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +25 -22
  285. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +10 -10
  286. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +4 -3
  287. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
  288. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
  289. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
  290. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
  291. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +4 -3
  292. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
  293. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
  294. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +4 -3
  295. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +5 -4
  296. claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
  297. claude_mpm/services/framework_claude_md_generator/version_manager.py +30 -28
  298. claude_mpm/services/hook_service.py +106 -114
  299. claude_mpm/services/infrastructure/__init__.py +7 -5
  300. claude_mpm/services/infrastructure/context_preservation.py +233 -199
  301. claude_mpm/services/infrastructure/daemon_manager.py +279 -0
  302. claude_mpm/services/infrastructure/logging.py +83 -76
  303. claude_mpm/services/infrastructure/monitoring.py +547 -404
  304. claude_mpm/services/mcp_gateway/__init__.py +30 -13
  305. claude_mpm/services/mcp_gateway/config/__init__.py +2 -2
  306. claude_mpm/services/mcp_gateway/config/config_loader.py +61 -56
  307. claude_mpm/services/mcp_gateway/config/config_schema.py +50 -41
  308. claude_mpm/services/mcp_gateway/config/configuration.py +82 -75
  309. claude_mpm/services/mcp_gateway/core/__init__.py +13 -20
  310. claude_mpm/services/mcp_gateway/core/base.py +80 -67
  311. claude_mpm/services/mcp_gateway/core/exceptions.py +60 -46
  312. claude_mpm/services/mcp_gateway/core/interfaces.py +87 -84
  313. claude_mpm/services/mcp_gateway/main.py +287 -137
  314. claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
  315. claude_mpm/services/mcp_gateway/registry/service_registry.py +97 -94
  316. claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
  317. claude_mpm/services/mcp_gateway/server/__init__.py +2 -2
  318. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +105 -110
  319. claude_mpm/services/mcp_gateway/server/stdio_handler.py +105 -107
  320. claude_mpm/services/mcp_gateway/server/stdio_server.py +691 -0
  321. claude_mpm/services/mcp_gateway/tools/__init__.py +4 -2
  322. claude_mpm/services/mcp_gateway/tools/base_adapter.py +109 -119
  323. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +283 -215
  324. claude_mpm/services/mcp_gateway/tools/hello_world.py +122 -120
  325. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +652 -0
  326. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +606 -0
  327. claude_mpm/services/memory/__init__.py +2 -2
  328. claude_mpm/services/memory/builder.py +451 -362
  329. claude_mpm/services/memory/cache/__init__.py +2 -2
  330. claude_mpm/services/memory/cache/shared_prompt_cache.py +232 -194
  331. claude_mpm/services/memory/cache/simple_cache.py +107 -93
  332. claude_mpm/services/memory/indexed_memory.py +195 -193
  333. claude_mpm/services/memory/optimizer.py +267 -234
  334. claude_mpm/services/memory/router.py +571 -263
  335. claude_mpm/services/memory_hook_service.py +237 -0
  336. claude_mpm/services/port_manager.py +575 -0
  337. claude_mpm/services/project/__init__.py +3 -3
  338. claude_mpm/services/project/analyzer.py +451 -305
  339. claude_mpm/services/project/registry.py +262 -240
  340. claude_mpm/services/recovery_manager.py +287 -231
  341. claude_mpm/services/response_tracker.py +87 -67
  342. claude_mpm/services/runner_configuration_service.py +587 -0
  343. claude_mpm/services/session_management_service.py +304 -0
  344. claude_mpm/services/socketio/__init__.py +4 -4
  345. claude_mpm/services/socketio/client_proxy.py +174 -0
  346. claude_mpm/services/socketio/handlers/__init__.py +3 -3
  347. claude_mpm/services/socketio/handlers/base.py +44 -30
  348. claude_mpm/services/socketio/handlers/connection.py +166 -64
  349. claude_mpm/services/socketio/handlers/file.py +123 -108
  350. claude_mpm/services/socketio/handlers/git.py +607 -373
  351. claude_mpm/services/socketio/handlers/hook.py +185 -0
  352. claude_mpm/services/socketio/handlers/memory.py +4 -4
  353. claude_mpm/services/socketio/handlers/project.py +4 -4
  354. claude_mpm/services/socketio/handlers/registry.py +53 -38
  355. claude_mpm/services/socketio/server/__init__.py +18 -0
  356. claude_mpm/services/socketio/server/broadcaster.py +252 -0
  357. claude_mpm/services/socketio/server/core.py +399 -0
  358. claude_mpm/services/socketio/server/main.py +323 -0
  359. claude_mpm/services/socketio_client_manager.py +160 -133
  360. claude_mpm/services/socketio_server.py +36 -1885
  361. claude_mpm/services/subprocess_launcher_service.py +316 -0
  362. claude_mpm/services/system_instructions_service.py +258 -0
  363. claude_mpm/services/ticket_manager.py +19 -533
  364. claude_mpm/services/utility_service.py +285 -0
  365. claude_mpm/services/version_control/__init__.py +18 -21
  366. claude_mpm/services/version_control/branch_strategy.py +20 -10
  367. claude_mpm/services/version_control/conflict_resolution.py +37 -13
  368. claude_mpm/services/version_control/git_operations.py +52 -21
  369. claude_mpm/services/version_control/semantic_versioning.py +92 -53
  370. claude_mpm/services/version_control/version_parser.py +145 -125
  371. claude_mpm/services/version_service.py +270 -0
  372. claude_mpm/storage/__init__.py +2 -2
  373. claude_mpm/storage/state_storage.py +177 -181
  374. claude_mpm/ticket_wrapper.py +2 -2
  375. claude_mpm/utils/__init__.py +2 -2
  376. claude_mpm/utils/agent_dependency_loader.py +453 -243
  377. claude_mpm/utils/config_manager.py +157 -118
  378. claude_mpm/utils/console.py +1 -1
  379. claude_mpm/utils/dependency_cache.py +102 -107
  380. claude_mpm/utils/dependency_manager.py +52 -47
  381. claude_mpm/utils/dependency_strategies.py +131 -96
  382. claude_mpm/utils/environment_context.py +110 -102
  383. claude_mpm/utils/error_handler.py +75 -55
  384. claude_mpm/utils/file_utils.py +80 -67
  385. claude_mpm/utils/framework_detection.py +12 -11
  386. claude_mpm/utils/import_migration_example.py +12 -60
  387. claude_mpm/utils/imports.py +48 -45
  388. claude_mpm/utils/path_operations.py +100 -93
  389. claude_mpm/utils/robust_installer.py +172 -164
  390. claude_mpm/utils/session_logging.py +30 -23
  391. claude_mpm/utils/subprocess_utils.py +99 -61
  392. claude_mpm/validation/__init__.py +1 -1
  393. claude_mpm/validation/agent_validator.py +151 -111
  394. claude_mpm/validation/frontmatter_validator.py +92 -71
  395. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/METADATA +90 -22
  396. claude_mpm-4.0.4.dist-info/RECORD +417 -0
  397. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/entry_points.txt +1 -0
  398. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/licenses/LICENSE +1 -1
  399. claude_mpm/cli/commands/run_guarded.py +0 -511
  400. claude_mpm/config/memory_guardian_config.py +0 -325
  401. claude_mpm/config/memory_guardian_yaml.py +0 -335
  402. claude_mpm/core/config_paths.py +0 -150
  403. claude_mpm/core/memory_aware_runner.py +0 -353
  404. claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
  405. claude_mpm/deployment_paths.py +0 -261
  406. claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
  407. claude_mpm/models/state_models.py +0 -433
  408. claude_mpm/services/agent/__init__.py +0 -24
  409. claude_mpm/services/agent/deployment.py +0 -2548
  410. claude_mpm/services/agent/management.py +0 -598
  411. claude_mpm/services/agent/registry.py +0 -813
  412. claude_mpm/services/agents/registry/agent_registry.py +0 -813
  413. claude_mpm/services/communication/socketio.py +0 -1935
  414. claude_mpm/services/communication/websocket.py +0 -479
  415. claude_mpm/services/framework_claude_md_generator.py +0 -624
  416. claude_mpm/services/health_monitor.py +0 -893
  417. claude_mpm/services/infrastructure/graceful_degradation.py +0 -616
  418. claude_mpm/services/infrastructure/health_monitor.py +0 -775
  419. claude_mpm/services/infrastructure/memory_dashboard.py +0 -479
  420. claude_mpm/services/infrastructure/memory_guardian.py +0 -944
  421. claude_mpm/services/infrastructure/restart_protection.py +0 -642
  422. claude_mpm/services/infrastructure/state_manager.py +0 -774
  423. claude_mpm/services/mcp_gateway/manager.py +0 -334
  424. claude_mpm/services/optimized_hook_service.py +0 -542
  425. claude_mpm/services/project_analyzer.py +0 -864
  426. claude_mpm/services/project_registry.py +0 -608
  427. claude_mpm/services/standalone_socketio_server.py +0 -1300
  428. claude_mpm/services/ticket_manager_di.py +0 -318
  429. claude_mpm/services/ticketing_service_original.py +0 -510
  430. claude_mpm/utils/paths.py +0 -395
  431. claude_mpm/utils/platform_memory.py +0 -524
  432. claude_mpm-3.9.11.dist-info/RECORD +0 -306
  433. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/WHEEL +0 -0
  434. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.4.dist-info}/top_level.txt +0 -0
@@ -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 ..utils import get_user_input, list_agent_versions_at_startup
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 ...deployment_paths import get_scripts_dir, get_package_root
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
- '--monitor',
50
- '--websocket-port',
51
- '--no-hooks',
52
- '--no-tickets',
53
- '--intercept-commands',
54
- '--no-native-agents',
55
- '--launch-method',
56
- '--resume',
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
- '--no-check-dependencies',
59
- '--force-check-dependencies',
60
- '--no-prompt',
61
- '--force-prompt',
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
- '--input',
64
- '--non-interactive',
65
+ "--input",
66
+ "--non-interactive",
65
67
  # Common logging flags (these are MPM-specific, not Claude CLI flags)
66
- '--debug',
67
- '--logging',
68
- '--log-dir',
68
+ "--debug",
69
+ "--logging",
70
+ "--log-dir",
69
71
  # Framework flags (these are MPM-specific)
70
- '--framework-path',
71
- '--agents-dir',
72
+ "--framework-path",
73
+ "--agents-dir",
72
74
  # Version flag (handled by MPM)
73
- '--version',
75
+ "--version",
74
76
  # Short flags (MPM-specific equivalents)
75
- '-i', # --input (MPM-specific, not Claude CLI)
76
- '-d' # --debug (MPM-specific, not Claude CLI)
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
- '--websocket-port', '--launch-method', '--logging', '--log-dir',
91
- '--framework-path', '--agents-dir', '-i', '--input'
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
- '--resume' # These flags can have optional values (nargs="?")
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('agents_run', [])
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, 'resume') and args.resume:
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(f"Resuming session {resume_session_id} (context: {resume_context})")
209
- print(f"🔄 Resuming session {resume_session_id[:8]}... (created: {session_data.get('created_at', 'unknown')})")
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(f"Resuming session {resume_session_id} (context: {resume_context})")
222
- print(f"🔄 Resuming session {resume_session_id[:8]}... (context: {resume_context})")
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, 'no_native_agents', False):
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, 'check_dependencies', True): # Default to checking
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, 'force_check_dependencies', False),
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, 'force_prompt', False),
260
- force_skip=getattr(args, 'no_prompt', False)
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(f"Interactive prompting: {can_prompt} ({prompt_reason})")
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, 'force_check_dependencies', False)
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['summary']['missing_python']:
274
- missing_count = len(results['summary']['missing_python'])
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['summary']['missing_python'][:5]:
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 = input("\nChoice [y/n/q]: ").strip().lower()
292
- if response == 'y':
334
+ if response == "y":
293
335
  print("\n🔧 Installing missing dependencies...")
294
336
  loader.auto_install = True
295
- success, error = loader.install_missing_dependencies(
296
- results['summary']['missing_python']
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 == 'q':
349
+ elif response == "q":
305
350
  print("👋 Exiting...")
306
351
  return
307
352
  else:
308
- print("⏩ Continuing without installing dependencies")
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(" Run 'pip install \"claude-mpm[agents]\"' to install all agent dependencies")
360
+ print(
361
+ " Run 'pip install \"claude-mpm[agents]\"' to install all agent dependencies"
362
+ )
314
363
  if not can_prompt:
315
- logger.debug(f"Not prompting for installation: {prompt_reason}")
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, 'claude_args', []) or []
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, 'monitor', False)
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(f"Filtered claude-mpm args: {set(raw_claude_args) - set(claude_args)}")
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, 'launch_method', 'exec')
342
-
343
- enable_websocket = getattr(args, 'monitor', False) or monitor_mode
344
- websocket_port = getattr(args, 'websocket_port', 8765)
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(" Please install manually: pip install python-socketio aiohttp python-engineio")
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(" Note: Socket.IO monitoring using exec mode with Claude Code hooks")
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(websocket_port, logger)
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(f" You can manually run: python scripts/launch_socketio_dashboard.py --port {websocket_port}")
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, '_browser_opened_by_cli', False)
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]["last_used"] = datetime.now().isoformat()
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, 'intercept_commands', False):
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("Interactive wrapper not found, falling back to normal mode")
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
- Launch the Socket.IO monitoring dashboard using static HTML file.
448
-
449
- WHY: This function opens a static HTML file that connects to the Socket.IO server.
450
- This approach is simpler and more reliable than serving the dashboard from the server.
451
- The HTML file connects to whatever Socket.IO server is running on the specified port.
452
-
453
- DESIGN DECISION: Use file:// protocol to open static HTML file directly from filesystem.
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
- Check if a Socket.IO server is running on the specified port.
578
-
579
- WHY: We need to detect existing servers to avoid conflicts and provide
580
- seamless experience regardless of whether server is already running.
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(f"Checking server readiness (attempt {attempt}, elapsed {elapsed:.1f}s)")
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(f"✅ Standalone Socket.IO server started successfully on port {port}")
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(f"Server not yet accepting connections on attempt {attempt}")
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(f"❌ Socket.IO server health check failed after {max_wait_time}s timeout ({attempt} attempts)")
743
- logger.warning(f"⏱️ Server may still be starting - try waiting a few more seconds")
744
- logger.warning(f"💡 The daemon process might be running but not yet accepting HTTP connections")
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(f"💡 This may be a dependency issue - try: pip install python-socketio aiohttp")
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(url, new=0) # new=0 tries to reuse existing window
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(url, new=0) # new=0 tries to reuse existing window
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, 'resume') or not args.resume:
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 ['B', 'KB', 'MB', 'GB']:
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('memory_management', {})
856
- warning_threshold = memory_config.get('claude_json_warning_threshold_kb', 500) * 1024
857
- critical_threshold = memory_config.get('claude_json_critical_threshold_kb', 1024) * 1024
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(f"\n⚠️ CRITICAL: Large .claude.json file detected ({format_size(file_size)})")
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, 'force', False):
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
- print("\n Would you like to continue anyway? [y/N]: ", end="")
874
-
875
- try:
876
- response = input().strip().lower()
877
- if response != 'y':
878
- print("\n✅ Session cancelled. Run 'claude-mpm cleanup-memory' to fix this issue.")
879
- import sys
880
- sys.exit(0)
881
- except (EOFError, KeyboardInterrupt):
882
- print("\n✅ Session cancelled.")
883
- import sys
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(f"\n⚠️ Warning: .claude.json file is getting large ({format_size(file_size)})")
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(" 💡 Consider running 'claude-mpm cleanup-memory' to archive old conversations\n")
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("💡 Run 'claude-mpm config validate' to see all issues and fixes")
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('response_logging.enabled', False)
871
+ response_logging_enabled = config.get("response_logging.enabled", False)
931
872
  if not response_logging_enabled:
932
- logger.debug("Response logging is disabled (response_logging.enabled=false)")
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(config.get('response_logging.session_directory', '.claude-mpm/responses'))
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(f"Cannot create response logging directory {session_dir}: {e}")
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(f"Response logging directory is not writable: {session_dir}")
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('loaded_from') and status['loaded_from'] != 'defaults':
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")