claude-mpm 3.9.9__py3-none-any.whl → 4.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (411) 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 +155 -0
  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 +1 -1
  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 +90 -49
  31. claude_mpm/cli/__main__.py +3 -2
  32. claude_mpm/cli/commands/__init__.py +21 -18
  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 +143 -762
  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 +203 -81
  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 +305 -197
  50. claude_mpm/cli/parser.py +24 -1150
  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 +104 -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 +217 -0
  69. claude_mpm/config/paths.py +94 -208
  70. claude_mpm/config/socketio_config.py +84 -73
  71. claude_mpm/constants.py +36 -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/css/dashboard.css +27 -8
  113. claude_mpm/dashboard/static/dist/components/agent-inference.js +2 -0
  114. claude_mpm/dashboard/static/dist/components/event-processor.js +2 -0
  115. claude_mpm/dashboard/static/dist/components/event-viewer.js +2 -0
  116. claude_mpm/dashboard/static/dist/components/export-manager.js +2 -0
  117. claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +2 -0
  118. claude_mpm/dashboard/static/dist/components/hud-library-loader.js +2 -0
  119. claude_mpm/dashboard/static/dist/components/hud-manager.js +2 -0
  120. claude_mpm/dashboard/static/dist/components/hud-visualizer.js +2 -0
  121. claude_mpm/dashboard/static/dist/components/module-viewer.js +2 -0
  122. claude_mpm/dashboard/static/dist/components/session-manager.js +2 -0
  123. claude_mpm/dashboard/static/dist/components/socket-manager.js +2 -0
  124. claude_mpm/dashboard/static/dist/components/ui-state-manager.js +2 -0
  125. claude_mpm/dashboard/static/dist/components/working-directory.js +2 -0
  126. claude_mpm/dashboard/static/dist/dashboard.js +2 -0
  127. claude_mpm/dashboard/static/dist/socket-client.js +2 -0
  128. claude_mpm/dashboard/static/js/components/agent-inference.js +80 -76
  129. claude_mpm/dashboard/static/js/components/event-processor.js +71 -67
  130. claude_mpm/dashboard/static/js/components/event-viewer.js +74 -70
  131. claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
  132. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +106 -92
  133. claude_mpm/dashboard/static/js/components/hud-library-loader.js +11 -11
  134. claude_mpm/dashboard/static/js/components/hud-manager.js +73 -73
  135. claude_mpm/dashboard/static/js/components/hud-visualizer.js +163 -163
  136. claude_mpm/dashboard/static/js/components/module-viewer.js +305 -233
  137. claude_mpm/dashboard/static/js/components/session-manager.js +32 -29
  138. claude_mpm/dashboard/static/js/components/socket-manager.js +27 -20
  139. claude_mpm/dashboard/static/js/components/ui-state-manager.js +21 -18
  140. claude_mpm/dashboard/static/js/components/working-directory.js +74 -71
  141. claude_mpm/dashboard/static/js/dashboard.js +178 -453
  142. claude_mpm/dashboard/static/js/extension-error-handler.js +164 -0
  143. claude_mpm/dashboard/static/js/socket-client.js +120 -54
  144. claude_mpm/dashboard/templates/index.html +40 -50
  145. claude_mpm/experimental/cli_enhancements.py +60 -58
  146. claude_mpm/generators/__init__.py +1 -1
  147. claude_mpm/generators/agent_profile_generator.py +75 -65
  148. claude_mpm/hooks/__init__.py +1 -1
  149. claude_mpm/hooks/base_hook.py +33 -28
  150. claude_mpm/hooks/claude_hooks/__init__.py +1 -1
  151. claude_mpm/hooks/claude_hooks/connection_pool.py +120 -0
  152. claude_mpm/hooks/claude_hooks/event_handlers.py +743 -0
  153. claude_mpm/hooks/claude_hooks/hook_handler.py +415 -1331
  154. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +4 -4
  155. claude_mpm/hooks/claude_hooks/memory_integration.py +221 -0
  156. claude_mpm/hooks/claude_hooks/response_tracking.py +348 -0
  157. claude_mpm/hooks/claude_hooks/tool_analysis.py +230 -0
  158. claude_mpm/hooks/memory_integration_hook.py +140 -100
  159. claude_mpm/hooks/tool_call_interceptor.py +89 -76
  160. claude_mpm/hooks/validation_hooks.py +57 -49
  161. claude_mpm/init.py +145 -121
  162. claude_mpm/models/__init__.py +9 -9
  163. claude_mpm/models/agent_definition.py +33 -23
  164. claude_mpm/models/agent_session.py +228 -200
  165. claude_mpm/scripts/__init__.py +1 -1
  166. claude_mpm/scripts/socketio_daemon.py +192 -75
  167. claude_mpm/scripts/socketio_server_manager.py +328 -0
  168. claude_mpm/scripts/start_activity_logging.py +25 -22
  169. claude_mpm/services/__init__.py +68 -43
  170. claude_mpm/services/agent_capabilities_service.py +271 -0
  171. claude_mpm/services/agents/__init__.py +23 -32
  172. claude_mpm/services/agents/deployment/__init__.py +3 -3
  173. claude_mpm/services/agents/deployment/agent_config_provider.py +310 -0
  174. claude_mpm/services/agents/deployment/agent_configuration_manager.py +359 -0
  175. claude_mpm/services/agents/deployment/agent_definition_factory.py +84 -0
  176. claude_mpm/services/agents/deployment/agent_deployment.py +415 -2113
  177. claude_mpm/services/agents/deployment/agent_discovery_service.py +387 -0
  178. claude_mpm/services/agents/deployment/agent_environment_manager.py +293 -0
  179. claude_mpm/services/agents/deployment/agent_filesystem_manager.py +387 -0
  180. claude_mpm/services/agents/deployment/agent_format_converter.py +453 -0
  181. claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +161 -0
  182. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +345 -495
  183. claude_mpm/services/agents/deployment/agent_metrics_collector.py +279 -0
  184. claude_mpm/services/agents/deployment/agent_restore_handler.py +88 -0
  185. claude_mpm/services/agents/deployment/agent_template_builder.py +406 -0
  186. claude_mpm/services/agents/deployment/agent_validator.py +352 -0
  187. claude_mpm/services/agents/deployment/agent_version_manager.py +313 -0
  188. claude_mpm/services/agents/deployment/agent_versioning.py +6 -9
  189. claude_mpm/services/agents/deployment/agents_directory_resolver.py +79 -0
  190. claude_mpm/services/agents/deployment/async_agent_deployment.py +298 -234
  191. claude_mpm/services/agents/deployment/config/__init__.py +13 -0
  192. claude_mpm/services/agents/deployment/config/deployment_config.py +182 -0
  193. claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
  194. claude_mpm/services/agents/deployment/deployment_config_loader.py +54 -0
  195. claude_mpm/services/agents/deployment/deployment_type_detector.py +124 -0
  196. claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
  197. claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
  198. claude_mpm/services/agents/deployment/facade/deployment_executor.py +73 -0
  199. claude_mpm/services/agents/deployment/facade/deployment_facade.py +270 -0
  200. claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
  201. claude_mpm/services/agents/deployment/interface_adapter.py +227 -0
  202. claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
  203. claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
  204. claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
  205. claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
  206. claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +159 -0
  207. claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
  208. claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
  209. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +195 -0
  210. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +119 -0
  211. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +79 -0
  212. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +90 -0
  213. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +100 -0
  214. claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
  215. claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +98 -0
  216. claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
  217. claude_mpm/services/agents/deployment/processors/agent_processor.py +258 -0
  218. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +318 -0
  219. claude_mpm/services/agents/deployment/results/__init__.py +13 -0
  220. claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
  221. claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
  222. claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
  223. claude_mpm/services/agents/deployment/strategies/base_strategy.py +119 -0
  224. claude_mpm/services/agents/deployment/strategies/project_strategy.py +150 -0
  225. claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
  226. claude_mpm/services/agents/deployment/strategies/system_strategy.py +116 -0
  227. claude_mpm/services/agents/deployment/strategies/user_strategy.py +137 -0
  228. claude_mpm/services/agents/deployment/system_instructions_deployer.py +108 -0
  229. claude_mpm/services/agents/deployment/validation/__init__.py +19 -0
  230. claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
  231. claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
  232. claude_mpm/services/agents/deployment/validation/template_validator.py +299 -0
  233. claude_mpm/services/agents/deployment/validation/validation_result.py +226 -0
  234. claude_mpm/services/agents/loading/__init__.py +2 -2
  235. claude_mpm/services/agents/loading/agent_profile_loader.py +259 -229
  236. claude_mpm/services/agents/loading/base_agent_manager.py +90 -81
  237. claude_mpm/services/agents/loading/framework_agent_loader.py +154 -129
  238. claude_mpm/services/agents/management/__init__.py +2 -2
  239. claude_mpm/services/agents/management/agent_capabilities_generator.py +72 -58
  240. claude_mpm/services/agents/management/agent_management_service.py +209 -156
  241. claude_mpm/services/agents/memory/__init__.py +9 -6
  242. claude_mpm/services/agents/memory/agent_memory_manager.py +218 -1152
  243. claude_mpm/services/agents/memory/agent_persistence_service.py +20 -16
  244. claude_mpm/services/agents/memory/analyzer.py +430 -0
  245. claude_mpm/services/agents/memory/content_manager.py +376 -0
  246. claude_mpm/services/agents/memory/template_generator.py +468 -0
  247. claude_mpm/services/agents/registry/__init__.py +7 -10
  248. claude_mpm/services/agents/registry/deployed_agent_discovery.py +122 -97
  249. claude_mpm/services/agents/registry/modification_tracker.py +351 -285
  250. claude_mpm/services/async_session_logger.py +187 -153
  251. claude_mpm/services/claude_session_logger.py +87 -72
  252. claude_mpm/services/command_handler_service.py +217 -0
  253. claude_mpm/services/communication/__init__.py +3 -2
  254. claude_mpm/services/core/__init__.py +50 -97
  255. claude_mpm/services/core/base.py +60 -53
  256. claude_mpm/services/core/interfaces/__init__.py +188 -0
  257. claude_mpm/services/core/interfaces/agent.py +351 -0
  258. claude_mpm/services/core/interfaces/communication.py +343 -0
  259. claude_mpm/services/core/interfaces/infrastructure.py +413 -0
  260. claude_mpm/services/core/interfaces/service.py +434 -0
  261. claude_mpm/services/core/interfaces.py +19 -944
  262. claude_mpm/services/event_aggregator.py +208 -170
  263. claude_mpm/services/exceptions.py +387 -308
  264. claude_mpm/services/framework_claude_md_generator/__init__.py +75 -79
  265. claude_mpm/services/framework_claude_md_generator/content_assembler.py +69 -60
  266. claude_mpm/services/framework_claude_md_generator/content_validator.py +65 -61
  267. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +68 -49
  268. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +34 -34
  269. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +25 -22
  270. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +10 -10
  271. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +4 -3
  272. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
  273. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
  274. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
  275. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
  276. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +4 -3
  277. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
  278. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
  279. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +4 -3
  280. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +5 -4
  281. claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
  282. claude_mpm/services/framework_claude_md_generator/version_manager.py +30 -28
  283. claude_mpm/services/hook_service.py +106 -114
  284. claude_mpm/services/infrastructure/__init__.py +7 -5
  285. claude_mpm/services/infrastructure/context_preservation.py +571 -0
  286. claude_mpm/services/infrastructure/daemon_manager.py +279 -0
  287. claude_mpm/services/infrastructure/logging.py +83 -76
  288. claude_mpm/services/infrastructure/monitoring.py +547 -404
  289. claude_mpm/services/mcp_gateway/__init__.py +40 -23
  290. claude_mpm/services/mcp_gateway/config/__init__.py +2 -2
  291. claude_mpm/services/mcp_gateway/config/config_loader.py +61 -56
  292. claude_mpm/services/mcp_gateway/config/config_schema.py +50 -41
  293. claude_mpm/services/mcp_gateway/config/configuration.py +82 -75
  294. claude_mpm/services/mcp_gateway/core/__init__.py +14 -21
  295. claude_mpm/services/mcp_gateway/core/base.py +80 -67
  296. claude_mpm/services/mcp_gateway/core/exceptions.py +60 -46
  297. claude_mpm/services/mcp_gateway/core/interfaces.py +97 -93
  298. claude_mpm/services/mcp_gateway/main.py +307 -127
  299. claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
  300. claude_mpm/services/mcp_gateway/registry/service_registry.py +100 -101
  301. claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
  302. claude_mpm/services/mcp_gateway/server/__init__.py +4 -4
  303. claude_mpm/services/mcp_gateway/server/{mcp_server.py → mcp_gateway.py} +149 -153
  304. claude_mpm/services/mcp_gateway/server/stdio_handler.py +105 -107
  305. claude_mpm/services/mcp_gateway/server/stdio_server.py +691 -0
  306. claude_mpm/services/mcp_gateway/tools/__init__.py +4 -2
  307. claude_mpm/services/mcp_gateway/tools/base_adapter.py +110 -121
  308. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +283 -215
  309. claude_mpm/services/mcp_gateway/tools/hello_world.py +122 -120
  310. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +652 -0
  311. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +606 -0
  312. claude_mpm/services/memory/__init__.py +2 -2
  313. claude_mpm/services/memory/builder.py +451 -362
  314. claude_mpm/services/memory/cache/__init__.py +2 -2
  315. claude_mpm/services/memory/cache/shared_prompt_cache.py +232 -194
  316. claude_mpm/services/memory/cache/simple_cache.py +107 -93
  317. claude_mpm/services/memory/indexed_memory.py +195 -193
  318. claude_mpm/services/memory/optimizer.py +267 -234
  319. claude_mpm/services/memory/router.py +571 -263
  320. claude_mpm/services/memory_hook_service.py +237 -0
  321. claude_mpm/services/port_manager.py +223 -0
  322. claude_mpm/services/project/__init__.py +3 -3
  323. claude_mpm/services/project/analyzer.py +451 -305
  324. claude_mpm/services/project/registry.py +262 -240
  325. claude_mpm/services/recovery_manager.py +287 -231
  326. claude_mpm/services/response_tracker.py +87 -67
  327. claude_mpm/services/runner_configuration_service.py +587 -0
  328. claude_mpm/services/session_management_service.py +304 -0
  329. claude_mpm/services/socketio/__init__.py +4 -4
  330. claude_mpm/services/socketio/client_proxy.py +174 -0
  331. claude_mpm/services/socketio/handlers/__init__.py +3 -3
  332. claude_mpm/services/socketio/handlers/base.py +44 -30
  333. claude_mpm/services/socketio/handlers/connection.py +145 -65
  334. claude_mpm/services/socketio/handlers/file.py +123 -108
  335. claude_mpm/services/socketio/handlers/git.py +607 -373
  336. claude_mpm/services/socketio/handlers/hook.py +170 -0
  337. claude_mpm/services/socketio/handlers/memory.py +4 -4
  338. claude_mpm/services/socketio/handlers/project.py +4 -4
  339. claude_mpm/services/socketio/handlers/registry.py +53 -38
  340. claude_mpm/services/socketio/server/__init__.py +18 -0
  341. claude_mpm/services/socketio/server/broadcaster.py +252 -0
  342. claude_mpm/services/socketio/server/core.py +399 -0
  343. claude_mpm/services/socketio/server/main.py +323 -0
  344. claude_mpm/services/socketio_client_manager.py +160 -133
  345. claude_mpm/services/socketio_server.py +36 -1885
  346. claude_mpm/services/subprocess_launcher_service.py +316 -0
  347. claude_mpm/services/system_instructions_service.py +258 -0
  348. claude_mpm/services/ticket_manager.py +20 -534
  349. claude_mpm/services/utility_service.py +285 -0
  350. claude_mpm/services/version_control/__init__.py +18 -21
  351. claude_mpm/services/version_control/branch_strategy.py +20 -10
  352. claude_mpm/services/version_control/conflict_resolution.py +37 -13
  353. claude_mpm/services/version_control/git_operations.py +52 -21
  354. claude_mpm/services/version_control/semantic_versioning.py +92 -53
  355. claude_mpm/services/version_control/version_parser.py +145 -125
  356. claude_mpm/services/version_service.py +270 -0
  357. claude_mpm/storage/__init__.py +9 -0
  358. claude_mpm/storage/state_storage.py +552 -0
  359. claude_mpm/ticket_wrapper.py +2 -2
  360. claude_mpm/utils/__init__.py +2 -2
  361. claude_mpm/utils/agent_dependency_loader.py +453 -243
  362. claude_mpm/utils/config_manager.py +157 -118
  363. claude_mpm/utils/console.py +1 -1
  364. claude_mpm/utils/dependency_cache.py +102 -107
  365. claude_mpm/utils/dependency_manager.py +52 -47
  366. claude_mpm/utils/dependency_strategies.py +131 -96
  367. claude_mpm/utils/environment_context.py +110 -102
  368. claude_mpm/utils/error_handler.py +75 -55
  369. claude_mpm/utils/file_utils.py +80 -67
  370. claude_mpm/utils/framework_detection.py +12 -11
  371. claude_mpm/utils/import_migration_example.py +12 -60
  372. claude_mpm/utils/imports.py +48 -45
  373. claude_mpm/utils/path_operations.py +100 -93
  374. claude_mpm/utils/robust_installer.py +172 -164
  375. claude_mpm/utils/session_logging.py +30 -23
  376. claude_mpm/utils/subprocess_utils.py +99 -61
  377. claude_mpm/validation/__init__.py +1 -1
  378. claude_mpm/validation/agent_validator.py +151 -111
  379. claude_mpm/validation/frontmatter_validator.py +92 -71
  380. {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/METADATA +51 -2
  381. claude_mpm-4.0.3.dist-info/RECORD +402 -0
  382. {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
  383. {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/licenses/LICENSE +1 -1
  384. claude_mpm/config/memory_guardian_config.py +0 -325
  385. claude_mpm/core/config_paths.py +0 -150
  386. claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
  387. claude_mpm/deployment_paths.py +0 -261
  388. claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
  389. claude_mpm/models/state_models.py +0 -433
  390. claude_mpm/services/agent/__init__.py +0 -24
  391. claude_mpm/services/agent/deployment.py +0 -2548
  392. claude_mpm/services/agent/management.py +0 -598
  393. claude_mpm/services/agent/registry.py +0 -813
  394. claude_mpm/services/agents/registry/agent_registry.py +0 -813
  395. claude_mpm/services/communication/socketio.py +0 -1935
  396. claude_mpm/services/communication/websocket.py +0 -479
  397. claude_mpm/services/framework_claude_md_generator.py +0 -624
  398. claude_mpm/services/health_monitor.py +0 -893
  399. claude_mpm/services/infrastructure/memory_guardian.py +0 -770
  400. claude_mpm/services/mcp_gateway/server/mcp_server_simple.py +0 -444
  401. claude_mpm/services/optimized_hook_service.py +0 -542
  402. claude_mpm/services/project_analyzer.py +0 -864
  403. claude_mpm/services/project_registry.py +0 -608
  404. claude_mpm/services/standalone_socketio_server.py +0 -1300
  405. claude_mpm/services/ticket_manager_di.py +0 -318
  406. claude_mpm/services/ticketing_service_original.py +0 -510
  407. claude_mpm/utils/paths.py +0 -395
  408. claude_mpm/utils/platform_memory.py +0 -524
  409. claude_mpm-3.9.9.dist-info/RECORD +0 -293
  410. {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/WHEEL +0 -0
  411. {claude_mpm-3.9.9.dist-info → claude_mpm-4.0.3.dist-info}/top_level.txt +0 -0
@@ -1,770 +0,0 @@
1
- """Memory Guardian service for monitoring and managing Claude Code memory usage.
2
-
3
- This service monitors a subprocess (Claude Code) for memory consumption and
4
- performs automatic restarts when memory thresholds are exceeded.
5
-
6
- Design Principles:
7
- - Subprocess lifecycle management with graceful shutdown
8
- - Multi-threshold memory monitoring (warning, critical, emergency)
9
- - Platform-agnostic memory monitoring with fallbacks
10
- - Configurable restart policies with cooldown periods
11
- - State preservation hooks for future enhancement
12
- """
13
-
14
- import asyncio
15
- import json
16
- import logging
17
- import os
18
- import platform
19
- import signal
20
- import subprocess
21
- import sys
22
- import time
23
- from dataclasses import dataclass, field
24
- from datetime import datetime, timedelta
25
- from enum import Enum
26
- from pathlib import Path
27
- from typing import Optional, Dict, Any, List, Callable, Tuple
28
-
29
- from claude_mpm.services.core.base import BaseService
30
- from claude_mpm.config.memory_guardian_config import (
31
- MemoryGuardianConfig,
32
- get_default_config
33
- )
34
- from claude_mpm.utils.platform_memory import (
35
- get_process_memory,
36
- get_system_memory,
37
- check_memory_pressure,
38
- MemoryInfo
39
- )
40
-
41
-
42
- class MemoryState(Enum):
43
- """Memory usage state levels."""
44
- NORMAL = "normal"
45
- WARNING = "warning"
46
- CRITICAL = "critical"
47
- EMERGENCY = "emergency"
48
-
49
-
50
- class ProcessState(Enum):
51
- """Process lifecycle states."""
52
- NOT_STARTED = "not_started"
53
- STARTING = "starting"
54
- RUNNING = "running"
55
- STOPPING = "stopping"
56
- STOPPED = "stopped"
57
- RESTARTING = "restarting"
58
- FAILED = "failed"
59
-
60
-
61
- @dataclass
62
- class RestartAttempt:
63
- """Record of a restart attempt."""
64
- timestamp: float
65
- reason: str
66
- memory_mb: float
67
- success: bool
68
-
69
- def to_dict(self) -> Dict[str, Any]:
70
- """Convert to dictionary."""
71
- return {
72
- 'timestamp': self.timestamp,
73
- 'timestamp_iso': datetime.fromtimestamp(self.timestamp).isoformat(),
74
- 'reason': self.reason,
75
- 'memory_mb': self.memory_mb,
76
- 'success': self.success
77
- }
78
-
79
-
80
- @dataclass
81
- class MemoryStats:
82
- """Memory usage statistics."""
83
- current_mb: float = 0.0
84
- peak_mb: float = 0.0
85
- average_mb: float = 0.0
86
- samples: int = 0
87
- last_check: float = 0.0
88
- state: MemoryState = MemoryState.NORMAL
89
-
90
- def update(self, memory_mb: float) -> None:
91
- """Update statistics with new memory reading."""
92
- self.current_mb = memory_mb
93
- self.peak_mb = max(self.peak_mb, memory_mb)
94
-
95
- # Update running average
96
- if self.samples == 0:
97
- self.average_mb = memory_mb
98
- else:
99
- self.average_mb = ((self.average_mb * self.samples) + memory_mb) / (self.samples + 1)
100
-
101
- self.samples += 1
102
- self.last_check = time.time()
103
-
104
- def to_dict(self) -> Dict[str, Any]:
105
- """Convert to dictionary."""
106
- return {
107
- 'current_mb': round(self.current_mb, 2),
108
- 'peak_mb': round(self.peak_mb, 2),
109
- 'average_mb': round(self.average_mb, 2),
110
- 'samples': self.samples,
111
- 'last_check': self.last_check,
112
- 'last_check_iso': datetime.fromtimestamp(self.last_check).isoformat() if self.last_check > 0 else None,
113
- 'state': self.state.value
114
- }
115
-
116
-
117
- class MemoryGuardian(BaseService):
118
- """Service for monitoring and managing subprocess memory usage."""
119
-
120
- def __init__(self, config: Optional[MemoryGuardianConfig] = None):
121
- """Initialize Memory Guardian service.
122
-
123
- Args:
124
- config: Configuration for memory monitoring and management
125
- """
126
- super().__init__("MemoryGuardian")
127
-
128
- # Configuration
129
- self.config = config or get_default_config()
130
-
131
- # Validate configuration
132
- issues = self.config.validate()
133
- if issues:
134
- for issue in issues:
135
- self.log_warning(f"Configuration issue: {issue}")
136
-
137
- # Process management
138
- self.process: Optional[subprocess.Popen] = None
139
- self.process_state = ProcessState.NOT_STARTED
140
- self.process_pid: Optional[int] = None
141
-
142
- # Memory monitoring
143
- self.memory_stats = MemoryStats()
144
- self.memory_state = MemoryState.NORMAL
145
-
146
- # Restart tracking
147
- self.restart_attempts: List[RestartAttempt] = []
148
- self.last_restart_time: float = 0.0
149
- self.consecutive_failures: int = 0
150
-
151
- # Monitoring tasks
152
- self.monitor_task: Optional[asyncio.Task] = None
153
- self.monitoring = False
154
-
155
- # State preservation hooks (for future implementation)
156
- self.state_save_hooks: List[Callable[[Dict[str, Any]], None]] = []
157
- self.state_restore_hooks: List[Callable[[Dict[str, Any]], None]] = []
158
-
159
- # Statistics
160
- self.start_time = time.time()
161
- self.total_restarts = 0
162
- self.total_uptime = 0.0
163
-
164
- self.log_info(f"Memory Guardian initialized with thresholds: "
165
- f"Warning={self.config.thresholds.warning}MB, "
166
- f"Critical={self.config.thresholds.critical}MB, "
167
- f"Emergency={self.config.thresholds.emergency}MB")
168
-
169
- async def initialize(self) -> bool:
170
- """Initialize the Memory Guardian service.
171
-
172
- Returns:
173
- True if initialization successful
174
- """
175
- try:
176
- self.log_info("Initializing Memory Guardian service")
177
-
178
- # Load persisted state if available
179
- if self.config.persist_state and self.config.state_file:
180
- self._load_state()
181
-
182
- # Auto-start process if configured
183
- if self.config.auto_start and self.config.enabled:
184
- self.log_info("Auto-starting monitored process")
185
- success = await self.start_process()
186
- if not success:
187
- self.log_warning("Failed to auto-start process")
188
-
189
- # Start monitoring if enabled
190
- if self.config.enabled:
191
- self.start_monitoring()
192
-
193
- self._initialized = True
194
- self.log_info("Memory Guardian service initialized successfully")
195
- return True
196
-
197
- except Exception as e:
198
- self.log_error(f"Failed to initialize Memory Guardian: {e}")
199
- return False
200
-
201
- async def shutdown(self) -> None:
202
- """Shutdown the Memory Guardian service gracefully."""
203
- try:
204
- self.log_info("Shutting down Memory Guardian service")
205
-
206
- # Stop monitoring
207
- await self.stop_monitoring()
208
-
209
- # Save state if configured
210
- if self.config.persist_state and self.config.state_file:
211
- self._save_state()
212
-
213
- # Terminate process if running
214
- if self.process and self.process_state == ProcessState.RUNNING:
215
- await self.terminate_process()
216
-
217
- self._shutdown = True
218
- self.log_info("Memory Guardian service shutdown complete")
219
-
220
- except Exception as e:
221
- self.log_error(f"Error during Memory Guardian shutdown: {e}")
222
-
223
- async def start_process(self) -> bool:
224
- """Start the monitored subprocess.
225
-
226
- Returns:
227
- True if process started successfully
228
- """
229
- if self.process and self.process_state == ProcessState.RUNNING:
230
- self.log_warning("Process is already running")
231
- return True
232
-
233
- try:
234
- self.log_info(f"Starting process: {' '.join(self.config.process_command)}")
235
- self.process_state = ProcessState.STARTING
236
-
237
- # Prepare environment
238
- env = os.environ.copy()
239
- env.update(self.config.process_env)
240
-
241
- # Build command
242
- cmd = self.config.process_command + self.config.process_args
243
-
244
- # Start subprocess
245
- self.process = subprocess.Popen(
246
- cmd,
247
- env=env,
248
- cwd=self.config.working_directory,
249
- stdout=subprocess.PIPE,
250
- stderr=subprocess.PIPE,
251
- start_new_session=True # Create new process group for clean termination
252
- )
253
-
254
- self.process_pid = self.process.pid
255
- self.process_state = ProcessState.RUNNING
256
-
257
- # Reset failure counter on successful start
258
- self.consecutive_failures = 0
259
-
260
- self.log_info(f"Process started successfully with PID {self.process_pid}")
261
-
262
- # Give process time to initialize
263
- await asyncio.sleep(2)
264
-
265
- # Check if process is still running
266
- if self.process.poll() is not None:
267
- self.log_error(f"Process exited immediately with code {self.process.returncode}")
268
- self.process_state = ProcessState.FAILED
269
- return False
270
-
271
- return True
272
-
273
- except FileNotFoundError:
274
- self.log_error(f"Command not found: {self.config.process_command[0]}")
275
- self.process_state = ProcessState.FAILED
276
- return False
277
- except Exception as e:
278
- self.log_error(f"Failed to start process: {e}")
279
- self.process_state = ProcessState.FAILED
280
- return False
281
-
282
- async def restart_process(self, reason: str = "Manual restart") -> bool:
283
- """Restart the monitored process with cooldown and retry logic.
284
-
285
- Args:
286
- reason: Reason for restart
287
-
288
- Returns:
289
- True if restart successful
290
- """
291
- self.log_info(f"Initiating process restart: {reason}")
292
-
293
- # Check restart attempts
294
- if not self._can_restart():
295
- self.log_error("Maximum restart attempts exceeded")
296
- self.process_state = ProcessState.FAILED
297
- return False
298
-
299
- # Apply cooldown if needed
300
- cooldown = self._get_restart_cooldown()
301
- if cooldown > 0:
302
- self.log_info(f"Applying restart cooldown of {cooldown} seconds")
303
- await asyncio.sleep(cooldown)
304
-
305
- # Record restart attempt
306
- memory_mb = self.memory_stats.current_mb
307
- self.process_state = ProcessState.RESTARTING
308
-
309
- # Save state before restart
310
- await self._trigger_state_save()
311
-
312
- # Terminate existing process
313
- if self.process:
314
- await self.terminate_process()
315
-
316
- # Start new process
317
- success = await self.start_process()
318
-
319
- # Record attempt
320
- attempt = RestartAttempt(
321
- timestamp=time.time(),
322
- reason=reason,
323
- memory_mb=memory_mb,
324
- success=success
325
- )
326
- self.restart_attempts.append(attempt)
327
-
328
- if success:
329
- self.total_restarts += 1
330
- self.last_restart_time = time.time()
331
- self.log_info("Process restarted successfully")
332
-
333
- # Restore state after restart
334
- await self._trigger_state_restore()
335
- else:
336
- self.consecutive_failures += 1
337
- self.log_error("Process restart failed")
338
-
339
- return success
340
-
341
- async def terminate_process(self, timeout: Optional[int] = None) -> bool:
342
- """Terminate the monitored process gracefully with escalation.
343
-
344
- Args:
345
- timeout: Override timeout for graceful shutdown
346
-
347
- Returns:
348
- True if process terminated successfully
349
- """
350
- if not self.process:
351
- return True
352
-
353
- timeout = timeout or self.config.restart_policy.graceful_timeout
354
-
355
- try:
356
- self.log_info(f"Terminating process {self.process_pid}")
357
- self.process_state = ProcessState.STOPPING
358
-
359
- # Try graceful termination first (SIGTERM)
360
- if platform.system() != 'Windows':
361
- self.process.terminate()
362
- else:
363
- # On Windows, terminate() is already forceful
364
- self.process.terminate()
365
-
366
- # Wait for graceful shutdown
367
- try:
368
- self.log_debug(f"Waiting {timeout}s for graceful shutdown")
369
- await asyncio.wait_for(
370
- asyncio.create_task(self._wait_for_process()),
371
- timeout=timeout
372
- )
373
- self.log_info("Process terminated gracefully")
374
-
375
- except asyncio.TimeoutError:
376
- # Escalate to SIGKILL
377
- self.log_warning("Graceful shutdown timeout, forcing termination")
378
-
379
- if platform.system() != 'Windows':
380
- self.process.kill()
381
- else:
382
- # On Windows, use taskkill /F
383
- subprocess.run(
384
- ['taskkill', '/F', '/PID', str(self.process_pid)],
385
- capture_output=True
386
- )
387
-
388
- # Wait for forced termination
389
- try:
390
- await asyncio.wait_for(
391
- asyncio.create_task(self._wait_for_process()),
392
- timeout=self.config.restart_policy.force_kill_timeout
393
- )
394
- self.log_info("Process terminated forcefully")
395
- except asyncio.TimeoutError:
396
- self.log_error("Failed to terminate process")
397
- return False
398
-
399
- self.process = None
400
- self.process_pid = None
401
- self.process_state = ProcessState.STOPPED
402
- return True
403
-
404
- except Exception as e:
405
- self.log_error(f"Error terminating process: {e}")
406
- return False
407
-
408
- async def _wait_for_process(self) -> None:
409
- """Wait for process to exit."""
410
- while self.process and self.process.poll() is None:
411
- await asyncio.sleep(0.1)
412
-
413
- def get_memory_usage(self) -> Optional[float]:
414
- """Get current memory usage of monitored process in MB.
415
-
416
- Returns:
417
- Memory usage in MB or None if unable to determine
418
- """
419
- if not self.process or self.process_state != ProcessState.RUNNING:
420
- return None
421
-
422
- try:
423
- # Get memory info using platform utilities
424
- mem_info = get_process_memory(self.process_pid)
425
- if mem_info:
426
- return mem_info.rss_mb
427
-
428
- self.log_warning(f"Unable to get memory info for PID {self.process_pid}")
429
- return None
430
-
431
- except Exception as e:
432
- self.log_error(f"Error getting memory usage: {e}")
433
- return None
434
-
435
- async def monitor_memory(self) -> None:
436
- """Check memory usage and take action if thresholds exceeded."""
437
- if not self.process or self.process_state != ProcessState.RUNNING:
438
- return
439
-
440
- # Check if process is still alive
441
- if self.process.poll() is not None:
442
- self.log_warning(f"Process exited with code {self.process.returncode}")
443
- self.process_state = ProcessState.STOPPED
444
- self.process = None
445
- self.process_pid = None
446
-
447
- # Auto-restart if configured
448
- if self.config.auto_start:
449
- await self.restart_process("Process exited unexpectedly")
450
- return
451
-
452
- # Get memory usage
453
- memory_mb = self.get_memory_usage()
454
- if memory_mb is None:
455
- return
456
-
457
- # Update statistics
458
- self.memory_stats.update(memory_mb)
459
-
460
- # Determine memory state
461
- old_state = self.memory_state
462
-
463
- if memory_mb >= self.config.thresholds.emergency:
464
- self.memory_state = MemoryState.EMERGENCY
465
- elif memory_mb >= self.config.thresholds.critical:
466
- self.memory_state = MemoryState.CRITICAL
467
- elif memory_mb >= self.config.thresholds.warning:
468
- self.memory_state = MemoryState.WARNING
469
- else:
470
- self.memory_state = MemoryState.NORMAL
471
-
472
- self.memory_stats.state = self.memory_state
473
-
474
- # Log state changes
475
- if self.memory_state != old_state:
476
- self.log_info(f"Memory state changed: {old_state.value} -> {self.memory_state.value} "
477
- f"(current: {memory_mb:.2f}MB)")
478
-
479
- # Take action based on state
480
- if self.memory_state == MemoryState.EMERGENCY:
481
- self.log_critical(f"Emergency memory threshold exceeded: {memory_mb:.2f}MB")
482
- await self.restart_process(f"Emergency memory threshold exceeded ({memory_mb:.2f}MB)")
483
-
484
- elif self.memory_state == MemoryState.CRITICAL:
485
- self.log_warning(f"Critical memory threshold exceeded: {memory_mb:.2f}MB")
486
- # Check if we've been in critical state for too long
487
- if self._should_restart_for_critical():
488
- await self.restart_process(f"Sustained critical memory usage ({memory_mb:.2f}MB)")
489
-
490
- elif self.memory_state == MemoryState.WARNING:
491
- self.log_debug(f"Warning memory threshold exceeded: {memory_mb:.2f}MB")
492
-
493
- # Log periodic summary
494
- if self.config.monitoring.log_memory_stats:
495
- if time.time() - self.memory_stats.last_check > self.config.monitoring.log_interval:
496
- self._log_memory_summary()
497
-
498
- def start_monitoring(self) -> None:
499
- """Start continuous memory monitoring."""
500
- if self.monitoring:
501
- self.log_warning("Monitoring is already active")
502
- return
503
-
504
- self.monitoring = True
505
- self.monitor_task = asyncio.create_task(self._monitoring_loop())
506
- self.log_info("Started memory monitoring")
507
-
508
- async def stop_monitoring(self) -> None:
509
- """Stop continuous memory monitoring."""
510
- if not self.monitoring:
511
- return
512
-
513
- self.monitoring = False
514
-
515
- if self.monitor_task:
516
- self.monitor_task.cancel()
517
- try:
518
- await self.monitor_task
519
- except asyncio.CancelledError:
520
- pass
521
- self.monitor_task = None
522
-
523
- self.log_info("Stopped memory monitoring")
524
-
525
- async def _monitoring_loop(self) -> None:
526
- """Continuous monitoring loop."""
527
- try:
528
- while self.monitoring:
529
- try:
530
- await self.monitor_memory()
531
-
532
- # Get check interval based on current state
533
- interval = self.config.monitoring.get_check_interval(
534
- self.memory_state.value
535
- )
536
-
537
- await asyncio.sleep(interval)
538
-
539
- except Exception as e:
540
- self.log_error(f"Error in monitoring loop: {e}")
541
- await asyncio.sleep(5) # Brief pause before retry
542
-
543
- except asyncio.CancelledError:
544
- self.log_debug("Monitoring loop cancelled")
545
-
546
- def _can_restart(self) -> bool:
547
- """Check if restart is allowed based on policy.
548
-
549
- Returns:
550
- True if restart is allowed
551
- """
552
- # Check max attempts
553
- if self.config.restart_policy.max_attempts <= 0:
554
- return True # Unlimited restarts
555
-
556
- # Count recent attempts
557
- window_start = time.time() - self.config.restart_policy.attempt_window
558
- recent_attempts = [
559
- a for a in self.restart_attempts
560
- if a.timestamp >= window_start
561
- ]
562
-
563
- return len(recent_attempts) < self.config.restart_policy.max_attempts
564
-
565
- def _get_restart_cooldown(self) -> int:
566
- """Get cooldown period for next restart.
567
-
568
- Returns:
569
- Cooldown period in seconds
570
- """
571
- if not self.restart_attempts:
572
- return 0
573
-
574
- # Calculate based on consecutive failures
575
- return self.config.restart_policy.get_cooldown(self.consecutive_failures + 1)
576
-
577
- def _should_restart_for_critical(self) -> bool:
578
- """Determine if we should restart due to sustained critical memory.
579
-
580
- Returns:
581
- True if restart should be triggered
582
- """
583
- # Check how long we've been in critical state
584
- critical_duration = 60 # seconds
585
-
586
- # Look at recent memory samples
587
- recent_samples = [
588
- s for s in self.restart_attempts
589
- if s.timestamp >= time.time() - critical_duration
590
- ]
591
-
592
- # If we've been critical for the duration, restart
593
- # This is a simplified check - could be enhanced
594
- return self.memory_state == MemoryState.CRITICAL and len(recent_samples) == 0
595
-
596
- async def _trigger_state_save(self) -> None:
597
- """Trigger state preservation hooks."""
598
- if not self.state_save_hooks:
599
- return
600
-
601
- state = self.get_state()
602
-
603
- for hook in self.state_save_hooks:
604
- try:
605
- hook(state)
606
- except Exception as e:
607
- self.log_error(f"State save hook failed: {e}")
608
-
609
- async def _trigger_state_restore(self) -> None:
610
- """Trigger state restoration hooks."""
611
- if not self.state_restore_hooks:
612
- return
613
-
614
- state = self.get_state()
615
-
616
- for hook in self.state_restore_hooks:
617
- try:
618
- hook(state)
619
- except Exception as e:
620
- self.log_error(f"State restore hook failed: {e}")
621
-
622
- def _log_memory_summary(self) -> None:
623
- """Log memory usage summary."""
624
- uptime = time.time() - self.start_time
625
-
626
- self.log_info(
627
- f"Memory Summary - "
628
- f"Current: {self.memory_stats.current_mb:.2f}MB, "
629
- f"Peak: {self.memory_stats.peak_mb:.2f}MB, "
630
- f"Average: {self.memory_stats.average_mb:.2f}MB, "
631
- f"State: {self.memory_state.value}, "
632
- f"Restarts: {self.total_restarts}, "
633
- f"Uptime: {uptime/3600:.2f}h"
634
- )
635
-
636
- def _save_state(self) -> None:
637
- """Save service state to file."""
638
- if not self.config.state_file:
639
- return
640
-
641
- try:
642
- state = self.get_state()
643
- state_path = Path(self.config.state_file)
644
- state_path.parent.mkdir(parents=True, exist_ok=True)
645
-
646
- with open(state_path, 'w') as f:
647
- json.dump(state, f, indent=2)
648
-
649
- self.log_debug(f"Saved state to {state_path}")
650
-
651
- except Exception as e:
652
- self.log_error(f"Failed to save state: {e}")
653
-
654
- def _load_state(self) -> None:
655
- """Load service state from file."""
656
- if not self.config.state_file:
657
- return
658
-
659
- try:
660
- state_path = Path(self.config.state_file)
661
- if not state_path.exists():
662
- return
663
-
664
- with open(state_path, 'r') as f:
665
- state = json.load(f)
666
-
667
- # Restore relevant state
668
- self.total_restarts = state.get('total_restarts', 0)
669
- self.memory_stats.peak_mb = state.get('memory_stats', {}).get('peak_mb', 0.0)
670
-
671
- # Restore restart attempts
672
- attempts = state.get('restart_attempts', [])
673
- for attempt_data in attempts:
674
- attempt = RestartAttempt(
675
- timestamp=attempt_data['timestamp'],
676
- reason=attempt_data['reason'],
677
- memory_mb=attempt_data['memory_mb'],
678
- success=attempt_data['success']
679
- )
680
- self.restart_attempts.append(attempt)
681
-
682
- self.log_debug(f"Loaded state from {state_path}")
683
-
684
- except Exception as e:
685
- self.log_error(f"Failed to load state: {e}")
686
-
687
- def add_state_save_hook(self, hook: Callable[[Dict[str, Any]], None]) -> None:
688
- """Add a hook to be called before process restart.
689
-
690
- Args:
691
- hook: Function to call with current state
692
- """
693
- self.state_save_hooks.append(hook)
694
- self.log_debug(f"Added state save hook: {hook.__name__}")
695
-
696
- def add_state_restore_hook(self, hook: Callable[[Dict[str, Any]], None]) -> None:
697
- """Add a hook to be called after process restart.
698
-
699
- Args:
700
- hook: Function to call with saved state
701
- """
702
- self.state_restore_hooks.append(hook)
703
- self.log_debug(f"Added state restore hook: {hook.__name__}")
704
-
705
- def get_state(self) -> Dict[str, Any]:
706
- """Get current service state.
707
-
708
- Returns:
709
- Dictionary containing service state
710
- """
711
- return {
712
- 'process_state': self.process_state.value,
713
- 'process_pid': self.process_pid,
714
- 'memory_state': self.memory_state.value,
715
- 'memory_stats': self.memory_stats.to_dict(),
716
- 'total_restarts': self.total_restarts,
717
- 'consecutive_failures': self.consecutive_failures,
718
- 'restart_attempts': [a.to_dict() for a in self.restart_attempts[-10:]], # Last 10
719
- 'config': self.config.to_dict(),
720
- 'start_time': self.start_time,
721
- 'monitoring': self.monitoring
722
- }
723
-
724
- def get_status(self) -> Dict[str, Any]:
725
- """Get current service status.
726
-
727
- Returns:
728
- Dictionary containing service status
729
- """
730
- uptime = time.time() - self.start_time if self.process else 0
731
-
732
- # Get system memory info
733
- total_mem, available_mem = get_system_memory()
734
-
735
- return {
736
- 'enabled': self.config.enabled,
737
- 'process': {
738
- 'state': self.process_state.value,
739
- 'pid': self.process_pid,
740
- 'uptime_seconds': uptime,
741
- 'uptime_hours': uptime / 3600 if uptime > 0 else 0
742
- },
743
- 'memory': {
744
- 'current_mb': self.memory_stats.current_mb,
745
- 'peak_mb': self.memory_stats.peak_mb,
746
- 'average_mb': self.memory_stats.average_mb,
747
- 'state': self.memory_state.value,
748
- 'thresholds': {
749
- 'warning_mb': self.config.thresholds.warning,
750
- 'critical_mb': self.config.thresholds.critical,
751
- 'emergency_mb': self.config.thresholds.emergency
752
- },
753
- 'system': {
754
- 'total_mb': total_mem / (1024 * 1024) if total_mem > 0 else 0,
755
- 'available_mb': available_mem / (1024 * 1024) if available_mem > 0 else 0,
756
- 'pressure': check_memory_pressure()
757
- }
758
- },
759
- 'restarts': {
760
- 'total': self.total_restarts,
761
- 'consecutive_failures': self.consecutive_failures,
762
- 'can_restart': self._can_restart(),
763
- 'recent_attempts': [a.to_dict() for a in self.restart_attempts[-5:]]
764
- },
765
- 'monitoring': {
766
- 'active': self.monitoring,
767
- 'check_interval': self.config.monitoring.get_check_interval(self.memory_state.value),
768
- 'samples': self.memory_stats.samples
769
- }
770
- }