claude-mpm 4.1.0__py3-none-any.whl → 4.1.2__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 (358) hide show
  1. claude_mpm/BUILD_NUMBER +1 -1
  2. claude_mpm/VERSION +1 -1
  3. claude_mpm/__main__.py +1 -1
  4. claude_mpm/agents/BASE_PM.md +74 -46
  5. claude_mpm/agents/INSTRUCTIONS.md +11 -153
  6. claude_mpm/agents/WORKFLOW.md +61 -321
  7. claude_mpm/agents/__init__.py +11 -11
  8. claude_mpm/agents/agent_loader.py +23 -20
  9. claude_mpm/agents/agent_loader_integration.py +1 -1
  10. claude_mpm/agents/agents_metadata.py +27 -0
  11. claude_mpm/agents/async_agent_loader.py +5 -8
  12. claude_mpm/agents/base_agent_loader.py +36 -25
  13. claude_mpm/agents/frontmatter_validator.py +6 -6
  14. claude_mpm/agents/schema/agent_schema.json +1 -1
  15. claude_mpm/agents/system_agent_config.py +9 -9
  16. claude_mpm/agents/templates/api_qa.json +47 -2
  17. claude_mpm/agents/templates/imagemagick.json +256 -0
  18. claude_mpm/agents/templates/qa.json +41 -2
  19. claude_mpm/agents/templates/ticketing.json +5 -5
  20. claude_mpm/agents/templates/web_qa.json +133 -58
  21. claude_mpm/agents/templates/web_ui.json +3 -3
  22. claude_mpm/cli/__init__.py +51 -46
  23. claude_mpm/cli/__main__.py +1 -1
  24. claude_mpm/cli/commands/__init__.py +10 -12
  25. claude_mpm/cli/commands/agent_manager.py +186 -181
  26. claude_mpm/cli/commands/agents.py +271 -268
  27. claude_mpm/cli/commands/aggregate.py +30 -29
  28. claude_mpm/cli/commands/cleanup.py +50 -44
  29. claude_mpm/cli/commands/cleanup_orphaned_agents.py +25 -25
  30. claude_mpm/cli/commands/config.py +162 -127
  31. claude_mpm/cli/commands/doctor.py +52 -62
  32. claude_mpm/cli/commands/info.py +37 -25
  33. claude_mpm/cli/commands/mcp.py +3 -7
  34. claude_mpm/cli/commands/mcp_command_router.py +14 -18
  35. claude_mpm/cli/commands/mcp_install_commands.py +28 -23
  36. claude_mpm/cli/commands/mcp_pipx_config.py +58 -49
  37. claude_mpm/cli/commands/mcp_server_commands.py +23 -17
  38. claude_mpm/cli/commands/memory.py +192 -141
  39. claude_mpm/cli/commands/monitor.py +117 -88
  40. claude_mpm/cli/commands/run.py +120 -84
  41. claude_mpm/cli/commands/run_config_checker.py +4 -5
  42. claude_mpm/cli/commands/socketio_monitor.py +17 -19
  43. claude_mpm/cli/commands/tickets.py +92 -92
  44. claude_mpm/cli/parser.py +1 -5
  45. claude_mpm/cli/parsers/__init__.py +1 -1
  46. claude_mpm/cli/parsers/agent_manager_parser.py +50 -98
  47. claude_mpm/cli/parsers/agents_parser.py +2 -3
  48. claude_mpm/cli/parsers/base_parser.py +7 -5
  49. claude_mpm/cli/parsers/mcp_parser.py +4 -2
  50. claude_mpm/cli/parsers/monitor_parser.py +26 -18
  51. claude_mpm/cli/shared/__init__.py +10 -10
  52. claude_mpm/cli/shared/argument_patterns.py +57 -71
  53. claude_mpm/cli/shared/base_command.py +61 -53
  54. claude_mpm/cli/shared/error_handling.py +62 -58
  55. claude_mpm/cli/shared/output_formatters.py +78 -77
  56. claude_mpm/cli/startup_logging.py +204 -172
  57. claude_mpm/cli/utils.py +10 -11
  58. claude_mpm/cli_module/__init__.py +1 -1
  59. claude_mpm/cli_module/args.py +1 -1
  60. claude_mpm/cli_module/migration_example.py +5 -5
  61. claude_mpm/config/__init__.py +9 -9
  62. claude_mpm/config/agent_config.py +15 -14
  63. claude_mpm/config/experimental_features.py +4 -4
  64. claude_mpm/config/paths.py +0 -1
  65. claude_mpm/config/socketio_config.py +5 -6
  66. claude_mpm/constants.py +1 -2
  67. claude_mpm/core/__init__.py +8 -8
  68. claude_mpm/core/agent_name_normalizer.py +1 -1
  69. claude_mpm/core/agent_registry.py +20 -23
  70. claude_mpm/core/agent_session_manager.py +3 -3
  71. claude_mpm/core/base_service.py +7 -15
  72. claude_mpm/core/cache.py +4 -6
  73. claude_mpm/core/claude_runner.py +85 -113
  74. claude_mpm/core/config.py +43 -28
  75. claude_mpm/core/config_aliases.py +0 -9
  76. claude_mpm/core/config_constants.py +52 -30
  77. claude_mpm/core/constants.py +0 -1
  78. claude_mpm/core/container.py +18 -27
  79. claude_mpm/core/exceptions.py +2 -2
  80. claude_mpm/core/factories.py +10 -12
  81. claude_mpm/core/framework_loader.py +581 -280
  82. claude_mpm/core/hook_manager.py +26 -22
  83. claude_mpm/core/hook_performance_config.py +58 -47
  84. claude_mpm/core/injectable_service.py +1 -1
  85. claude_mpm/core/interactive_session.py +61 -152
  86. claude_mpm/core/interfaces.py +1 -100
  87. claude_mpm/core/lazy.py +5 -5
  88. claude_mpm/core/log_manager.py +587 -0
  89. claude_mpm/core/logger.py +125 -8
  90. claude_mpm/core/logging_config.py +15 -15
  91. claude_mpm/core/minimal_framework_loader.py +5 -8
  92. claude_mpm/core/oneshot_session.py +15 -33
  93. claude_mpm/core/optimized_agent_loader.py +4 -6
  94. claude_mpm/core/optimized_startup.py +2 -1
  95. claude_mpm/core/output_style_manager.py +147 -106
  96. claude_mpm/core/pm_hook_interceptor.py +0 -1
  97. claude_mpm/core/service_registry.py +11 -8
  98. claude_mpm/core/session_manager.py +1 -2
  99. claude_mpm/core/shared/__init__.py +1 -1
  100. claude_mpm/core/shared/config_loader.py +101 -97
  101. claude_mpm/core/shared/path_resolver.py +72 -68
  102. claude_mpm/core/shared/singleton_manager.py +56 -50
  103. claude_mpm/core/socketio_pool.py +26 -6
  104. claude_mpm/core/tool_access_control.py +4 -5
  105. claude_mpm/core/typing_utils.py +50 -59
  106. claude_mpm/core/unified_agent_registry.py +14 -19
  107. claude_mpm/core/unified_config.py +4 -6
  108. claude_mpm/core/unified_paths.py +197 -109
  109. claude_mpm/dashboard/open_dashboard.py +2 -4
  110. claude_mpm/experimental/cli_enhancements.py +51 -36
  111. claude_mpm/generators/agent_profile_generator.py +2 -4
  112. claude_mpm/hooks/base_hook.py +1 -2
  113. claude_mpm/hooks/claude_hooks/connection_pool.py +72 -26
  114. claude_mpm/hooks/claude_hooks/event_handlers.py +93 -38
  115. claude_mpm/hooks/claude_hooks/hook_handler.py +130 -76
  116. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +104 -77
  117. claude_mpm/hooks/claude_hooks/memory_integration.py +2 -4
  118. claude_mpm/hooks/claude_hooks/response_tracking.py +15 -11
  119. claude_mpm/hooks/claude_hooks/tool_analysis.py +12 -18
  120. claude_mpm/hooks/memory_integration_hook.py +5 -5
  121. claude_mpm/hooks/tool_call_interceptor.py +1 -1
  122. claude_mpm/hooks/validation_hooks.py +4 -4
  123. claude_mpm/init.py +4 -9
  124. claude_mpm/models/__init__.py +2 -2
  125. claude_mpm/models/agent_session.py +11 -14
  126. claude_mpm/scripts/mcp_server.py +20 -11
  127. claude_mpm/scripts/mcp_wrapper.py +5 -5
  128. claude_mpm/scripts/mpm_doctor.py +321 -0
  129. claude_mpm/scripts/socketio_daemon.py +28 -25
  130. claude_mpm/scripts/socketio_daemon_hardened.py +298 -258
  131. claude_mpm/scripts/socketio_server_manager.py +116 -95
  132. claude_mpm/services/__init__.py +49 -49
  133. claude_mpm/services/agent_capabilities_service.py +12 -18
  134. claude_mpm/services/agents/__init__.py +22 -22
  135. claude_mpm/services/agents/agent_builder.py +140 -119
  136. claude_mpm/services/agents/deployment/__init__.py +3 -3
  137. claude_mpm/services/agents/deployment/agent_config_provider.py +9 -9
  138. claude_mpm/services/agents/deployment/agent_configuration_manager.py +19 -20
  139. claude_mpm/services/agents/deployment/agent_definition_factory.py +1 -5
  140. claude_mpm/services/agents/deployment/agent_deployment.py +136 -106
  141. claude_mpm/services/agents/deployment/agent_discovery_service.py +4 -8
  142. claude_mpm/services/agents/deployment/agent_environment_manager.py +2 -7
  143. claude_mpm/services/agents/deployment/agent_filesystem_manager.py +6 -10
  144. claude_mpm/services/agents/deployment/agent_format_converter.py +11 -15
  145. claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +2 -3
  146. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +5 -5
  147. claude_mpm/services/agents/deployment/agent_metrics_collector.py +13 -19
  148. claude_mpm/services/agents/deployment/agent_restore_handler.py +0 -1
  149. claude_mpm/services/agents/deployment/agent_template_builder.py +26 -35
  150. claude_mpm/services/agents/deployment/agent_validator.py +0 -1
  151. claude_mpm/services/agents/deployment/agent_version_manager.py +7 -9
  152. claude_mpm/services/agents/deployment/agent_versioning.py +3 -3
  153. claude_mpm/services/agents/deployment/agents_directory_resolver.py +6 -7
  154. claude_mpm/services/agents/deployment/async_agent_deployment.py +51 -38
  155. claude_mpm/services/agents/deployment/config/__init__.py +1 -1
  156. claude_mpm/services/agents/deployment/config/deployment_config.py +7 -8
  157. claude_mpm/services/agents/deployment/deployment_type_detector.py +1 -1
  158. claude_mpm/services/agents/deployment/deployment_wrapper.py +18 -18
  159. claude_mpm/services/agents/deployment/facade/__init__.py +1 -1
  160. claude_mpm/services/agents/deployment/facade/deployment_executor.py +0 -3
  161. claude_mpm/services/agents/deployment/facade/deployment_facade.py +3 -4
  162. claude_mpm/services/agents/deployment/interface_adapter.py +5 -7
  163. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +345 -276
  164. claude_mpm/services/agents/deployment/pipeline/__init__.py +2 -2
  165. claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +1 -1
  166. claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +6 -4
  167. claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +3 -3
  168. claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +2 -2
  169. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +14 -13
  170. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +0 -1
  171. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +1 -1
  172. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +8 -9
  173. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +1 -1
  174. claude_mpm/services/agents/deployment/processors/__init__.py +1 -1
  175. claude_mpm/services/agents/deployment/processors/agent_processor.py +20 -16
  176. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +5 -12
  177. claude_mpm/services/agents/deployment/results/__init__.py +1 -1
  178. claude_mpm/services/agents/deployment/results/deployment_result_builder.py +1 -1
  179. claude_mpm/services/agents/deployment/strategies/__init__.py +2 -2
  180. claude_mpm/services/agents/deployment/strategies/base_strategy.py +1 -7
  181. claude_mpm/services/agents/deployment/strategies/project_strategy.py +1 -4
  182. claude_mpm/services/agents/deployment/strategies/system_strategy.py +2 -3
  183. claude_mpm/services/agents/deployment/strategies/user_strategy.py +3 -7
  184. claude_mpm/services/agents/deployment/validation/__init__.py +1 -1
  185. claude_mpm/services/agents/deployment/validation/agent_validator.py +1 -1
  186. claude_mpm/services/agents/deployment/validation/template_validator.py +2 -2
  187. claude_mpm/services/agents/deployment/validation/validation_result.py +2 -6
  188. claude_mpm/services/agents/loading/__init__.py +1 -1
  189. claude_mpm/services/agents/loading/agent_profile_loader.py +6 -12
  190. claude_mpm/services/agents/loading/base_agent_manager.py +5 -5
  191. claude_mpm/services/agents/loading/framework_agent_loader.py +2 -4
  192. claude_mpm/services/agents/management/__init__.py +1 -1
  193. claude_mpm/services/agents/management/agent_capabilities_generator.py +1 -3
  194. claude_mpm/services/agents/management/agent_management_service.py +5 -9
  195. claude_mpm/services/agents/memory/__init__.py +4 -4
  196. claude_mpm/services/agents/memory/agent_memory_manager.py +280 -160
  197. claude_mpm/services/agents/memory/agent_persistence_service.py +0 -2
  198. claude_mpm/services/agents/memory/content_manager.py +44 -38
  199. claude_mpm/services/agents/memory/template_generator.py +4 -6
  200. claude_mpm/services/agents/registry/__init__.py +10 -6
  201. claude_mpm/services/agents/registry/deployed_agent_discovery.py +30 -27
  202. claude_mpm/services/agents/registry/modification_tracker.py +3 -6
  203. claude_mpm/services/async_session_logger.py +1 -2
  204. claude_mpm/services/claude_session_logger.py +1 -2
  205. claude_mpm/services/command_deployment_service.py +173 -0
  206. claude_mpm/services/command_handler_service.py +20 -22
  207. claude_mpm/services/core/__init__.py +25 -25
  208. claude_mpm/services/core/base.py +0 -5
  209. claude_mpm/services/core/interfaces/__init__.py +32 -32
  210. claude_mpm/services/core/interfaces/agent.py +0 -21
  211. claude_mpm/services/core/interfaces/communication.py +0 -27
  212. claude_mpm/services/core/interfaces/infrastructure.py +0 -56
  213. claude_mpm/services/core/interfaces/service.py +0 -29
  214. claude_mpm/services/diagnostics/__init__.py +1 -1
  215. claude_mpm/services/diagnostics/checks/__init__.py +6 -6
  216. claude_mpm/services/diagnostics/checks/agent_check.py +89 -80
  217. claude_mpm/services/diagnostics/checks/base_check.py +12 -16
  218. claude_mpm/services/diagnostics/checks/claude_desktop_check.py +84 -81
  219. claude_mpm/services/diagnostics/checks/common_issues_check.py +99 -91
  220. claude_mpm/services/diagnostics/checks/configuration_check.py +82 -77
  221. claude_mpm/services/diagnostics/checks/filesystem_check.py +67 -68
  222. claude_mpm/services/diagnostics/checks/installation_check.py +254 -94
  223. claude_mpm/services/diagnostics/checks/mcp_check.py +90 -88
  224. claude_mpm/services/diagnostics/checks/monitor_check.py +75 -76
  225. claude_mpm/services/diagnostics/checks/startup_log_check.py +67 -73
  226. claude_mpm/services/diagnostics/diagnostic_runner.py +67 -59
  227. claude_mpm/services/diagnostics/doctor_reporter.py +107 -70
  228. claude_mpm/services/diagnostics/models.py +21 -19
  229. claude_mpm/services/event_aggregator.py +10 -17
  230. claude_mpm/services/event_bus/__init__.py +1 -1
  231. claude_mpm/services/event_bus/config.py +54 -35
  232. claude_mpm/services/event_bus/event_bus.py +76 -71
  233. claude_mpm/services/event_bus/relay.py +74 -64
  234. claude_mpm/services/events/__init__.py +11 -11
  235. claude_mpm/services/events/consumers/__init__.py +3 -3
  236. claude_mpm/services/events/consumers/dead_letter.py +71 -63
  237. claude_mpm/services/events/consumers/logging.py +39 -37
  238. claude_mpm/services/events/consumers/metrics.py +56 -57
  239. claude_mpm/services/events/consumers/socketio.py +82 -81
  240. claude_mpm/services/events/core.py +110 -99
  241. claude_mpm/services/events/interfaces.py +56 -72
  242. claude_mpm/services/events/producers/__init__.py +1 -1
  243. claude_mpm/services/events/producers/hook.py +38 -38
  244. claude_mpm/services/events/producers/system.py +46 -44
  245. claude_mpm/services/exceptions.py +81 -80
  246. claude_mpm/services/framework_claude_md_generator/__init__.py +2 -4
  247. claude_mpm/services/framework_claude_md_generator/content_assembler.py +3 -5
  248. claude_mpm/services/framework_claude_md_generator/content_validator.py +1 -1
  249. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +4 -4
  250. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +0 -1
  251. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +0 -2
  252. claude_mpm/services/framework_claude_md_generator/version_manager.py +4 -5
  253. claude_mpm/services/hook_service.py +6 -9
  254. claude_mpm/services/infrastructure/__init__.py +1 -1
  255. claude_mpm/services/infrastructure/context_preservation.py +8 -12
  256. claude_mpm/services/infrastructure/monitoring.py +21 -23
  257. claude_mpm/services/mcp_gateway/__init__.py +37 -37
  258. claude_mpm/services/mcp_gateway/auto_configure.py +95 -103
  259. claude_mpm/services/mcp_gateway/config/__init__.py +1 -1
  260. claude_mpm/services/mcp_gateway/config/config_loader.py +23 -25
  261. claude_mpm/services/mcp_gateway/config/config_schema.py +5 -5
  262. claude_mpm/services/mcp_gateway/config/configuration.py +9 -6
  263. claude_mpm/services/mcp_gateway/core/__init__.py +10 -10
  264. claude_mpm/services/mcp_gateway/core/base.py +0 -3
  265. claude_mpm/services/mcp_gateway/core/interfaces.py +1 -38
  266. claude_mpm/services/mcp_gateway/core/process_pool.py +99 -93
  267. claude_mpm/services/mcp_gateway/core/singleton_manager.py +65 -62
  268. claude_mpm/services/mcp_gateway/core/startup_verification.py +75 -74
  269. claude_mpm/services/mcp_gateway/main.py +2 -1
  270. claude_mpm/services/mcp_gateway/registry/service_registry.py +5 -8
  271. claude_mpm/services/mcp_gateway/registry/tool_registry.py +1 -1
  272. claude_mpm/services/mcp_gateway/server/__init__.py +1 -1
  273. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +12 -19
  274. claude_mpm/services/mcp_gateway/server/stdio_handler.py +4 -3
  275. claude_mpm/services/mcp_gateway/server/stdio_server.py +79 -71
  276. claude_mpm/services/mcp_gateway/tools/__init__.py +2 -2
  277. claude_mpm/services/mcp_gateway/tools/base_adapter.py +5 -6
  278. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +13 -22
  279. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +79 -78
  280. claude_mpm/services/mcp_gateway/tools/hello_world.py +12 -14
  281. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +42 -49
  282. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +51 -55
  283. claude_mpm/services/memory/__init__.py +3 -3
  284. claude_mpm/services/memory/builder.py +3 -6
  285. claude_mpm/services/memory/cache/__init__.py +1 -1
  286. claude_mpm/services/memory/cache/shared_prompt_cache.py +3 -5
  287. claude_mpm/services/memory/cache/simple_cache.py +1 -1
  288. claude_mpm/services/memory/indexed_memory.py +5 -7
  289. claude_mpm/services/memory/optimizer.py +7 -10
  290. claude_mpm/services/memory/router.py +8 -9
  291. claude_mpm/services/memory_hook_service.py +48 -34
  292. claude_mpm/services/monitor_build_service.py +77 -73
  293. claude_mpm/services/port_manager.py +130 -108
  294. claude_mpm/services/project/analyzer.py +12 -10
  295. claude_mpm/services/project/registry.py +11 -11
  296. claude_mpm/services/recovery_manager.py +10 -19
  297. claude_mpm/services/response_tracker.py +0 -1
  298. claude_mpm/services/runner_configuration_service.py +19 -20
  299. claude_mpm/services/session_management_service.py +7 -11
  300. claude_mpm/services/shared/__init__.py +1 -1
  301. claude_mpm/services/shared/async_service_base.py +58 -50
  302. claude_mpm/services/shared/config_service_base.py +73 -67
  303. claude_mpm/services/shared/lifecycle_service_base.py +82 -78
  304. claude_mpm/services/shared/manager_base.py +94 -82
  305. claude_mpm/services/shared/service_factory.py +96 -98
  306. claude_mpm/services/socketio/__init__.py +3 -3
  307. claude_mpm/services/socketio/client_proxy.py +5 -5
  308. claude_mpm/services/socketio/event_normalizer.py +199 -181
  309. claude_mpm/services/socketio/handlers/__init__.py +3 -3
  310. claude_mpm/services/socketio/handlers/base.py +5 -4
  311. claude_mpm/services/socketio/handlers/connection.py +163 -136
  312. claude_mpm/services/socketio/handlers/file.py +13 -14
  313. claude_mpm/services/socketio/handlers/git.py +12 -7
  314. claude_mpm/services/socketio/handlers/hook.py +49 -44
  315. claude_mpm/services/socketio/handlers/memory.py +0 -1
  316. claude_mpm/services/socketio/handlers/project.py +0 -1
  317. claude_mpm/services/socketio/handlers/registry.py +37 -19
  318. claude_mpm/services/socketio/migration_utils.py +98 -84
  319. claude_mpm/services/socketio/server/__init__.py +1 -1
  320. claude_mpm/services/socketio/server/broadcaster.py +81 -87
  321. claude_mpm/services/socketio/server/core.py +65 -54
  322. claude_mpm/services/socketio/server/eventbus_integration.py +95 -56
  323. claude_mpm/services/socketio/server/main.py +64 -38
  324. claude_mpm/services/socketio_client_manager.py +10 -12
  325. claude_mpm/services/subprocess_launcher_service.py +4 -7
  326. claude_mpm/services/system_instructions_service.py +13 -14
  327. claude_mpm/services/ticket_manager.py +2 -2
  328. claude_mpm/services/utility_service.py +5 -13
  329. claude_mpm/services/version_control/__init__.py +16 -16
  330. claude_mpm/services/version_control/branch_strategy.py +5 -8
  331. claude_mpm/services/version_control/conflict_resolution.py +9 -23
  332. claude_mpm/services/version_control/git_operations.py +5 -7
  333. claude_mpm/services/version_control/semantic_versioning.py +16 -17
  334. claude_mpm/services/version_control/version_parser.py +13 -18
  335. claude_mpm/services/version_service.py +10 -11
  336. claude_mpm/storage/__init__.py +1 -1
  337. claude_mpm/storage/state_storage.py +22 -28
  338. claude_mpm/utils/__init__.py +6 -6
  339. claude_mpm/utils/agent_dependency_loader.py +47 -33
  340. claude_mpm/utils/config_manager.py +11 -14
  341. claude_mpm/utils/dependency_cache.py +1 -1
  342. claude_mpm/utils/dependency_manager.py +13 -17
  343. claude_mpm/utils/dependency_strategies.py +8 -10
  344. claude_mpm/utils/environment_context.py +3 -9
  345. claude_mpm/utils/error_handler.py +3 -13
  346. claude_mpm/utils/file_utils.py +1 -1
  347. claude_mpm/utils/path_operations.py +8 -12
  348. claude_mpm/utils/robust_installer.py +110 -33
  349. claude_mpm/utils/subprocess_utils.py +5 -6
  350. claude_mpm/validation/agent_validator.py +3 -6
  351. claude_mpm/validation/frontmatter_validator.py +1 -1
  352. {claude_mpm-4.1.0.dist-info → claude_mpm-4.1.2.dist-info}/METADATA +1 -1
  353. claude_mpm-4.1.2.dist-info/RECORD +498 -0
  354. claude_mpm-4.1.0.dist-info/RECORD +0 -494
  355. {claude_mpm-4.1.0.dist-info → claude_mpm-4.1.2.dist-info}/WHEEL +0 -0
  356. {claude_mpm-4.1.0.dist-info → claude_mpm-4.1.2.dist-info}/entry_points.txt +0 -0
  357. {claude_mpm-4.1.0.dist-info → claude_mpm-4.1.2.dist-info}/licenses/LICENSE +0 -0
  358. {claude_mpm-4.1.0.dist-info → claude_mpm-4.1.2.dist-info}/top_level.txt +0 -0
@@ -17,9 +17,9 @@ from .registry import EventHandlerRegistry
17
17
  __all__ = [
18
18
  "BaseEventHandler",
19
19
  "ConnectionEventHandler",
20
- "ProjectEventHandler",
21
- "MemoryEventHandler",
20
+ "EventHandlerRegistry",
22
21
  "FileEventHandler",
23
22
  "GitEventHandler",
24
- "EventHandlerRegistry",
23
+ "MemoryEventHandler",
24
+ "ProjectEventHandler",
25
25
  ]
@@ -5,15 +5,15 @@ logging, error handling, and access to the server instance. All handler
5
5
  classes inherit from this to ensure consistent behavior.
6
6
  """
7
7
 
8
- import logging
9
8
  from datetime import datetime
10
- from logging import Logger
11
9
  from typing import TYPE_CHECKING, Any, Dict, List, Optional
12
10
 
13
11
  from ....core.logger import get_logger
14
12
  from ....core.typing_utils import EventData, EventName, SocketId
15
13
 
16
14
  if TYPE_CHECKING:
15
+ from logging import Logger
16
+
17
17
  import socketio
18
18
 
19
19
  from ..server import SocketIOServer
@@ -33,8 +33,8 @@ class BaseEventHandler:
33
33
  Args:
34
34
  server: The SocketIOServer instance that owns this handler
35
35
  """
36
- self.server: "SocketIOServer" = server
37
- self.sio: "socketio.AsyncServer" = server.sio
36
+ self.server: SocketIOServer = server
37
+ self.sio: socketio.AsyncServer = server.sio
38
38
  self.logger: Logger = get_logger(self.__class__.__name__)
39
39
  self.clients: Dict[SocketId, Dict[str, Any]] = server.clients
40
40
  self.event_history: List[Dict[str, Any]] = server.event_history
@@ -92,6 +92,7 @@ class BaseEventHandler:
92
92
  except Exception as e:
93
93
  self.logger.error(f"Failed to broadcast {event}: {e}")
94
94
  import traceback
95
+
95
96
  self.logger.error(f"Stack trace: {traceback.format_exc()}")
96
97
 
97
98
  def add_to_history(self, event_type: str, data: EventData) -> None:
@@ -9,74 +9,83 @@ import asyncio
9
9
  import functools
10
10
  import time
11
11
  from datetime import datetime
12
- from typing import Any, Callable, Dict, List, Optional, Set
12
+ from typing import Any, Callable, Dict, List, Optional
13
13
 
14
- from ....core.typing_utils import ClaudeStatus, EventData, SocketId
15
14
  from .base import BaseEventHandler
16
15
 
17
16
 
18
17
  def timeout_handler(timeout_seconds: float = 5.0):
19
18
  """Decorator to add timeout protection to async handlers.
20
-
19
+
21
20
  WHY: Network operations can hang indefinitely, causing resource leaks
22
21
  and poor user experience. This decorator ensures handlers complete
23
22
  within a reasonable time or fail gracefully.
24
-
23
+
25
24
  Args:
26
25
  timeout_seconds: Maximum time allowed for handler execution (default: 5s)
27
26
  """
27
+
28
28
  def decorator(func: Callable) -> Callable:
29
29
  @functools.wraps(func)
30
30
  async def wrapper(*args, **kwargs):
31
31
  handler_name = func.__name__
32
32
  start_time = time.time()
33
-
33
+
34
34
  try:
35
35
  # Create a task with timeout
36
36
  result = await asyncio.wait_for(
37
- func(*args, **kwargs),
38
- timeout=timeout_seconds
37
+ func(*args, **kwargs), timeout=timeout_seconds
39
38
  )
40
-
39
+
41
40
  elapsed = time.time() - start_time
42
41
  if elapsed > timeout_seconds * 0.8: # Warn if close to timeout
43
42
  # Try to get logger from closure scope or fallback to print
44
43
  try:
45
44
  import logging
45
+
46
46
  logger = logging.getLogger(__name__)
47
47
  logger.warning(
48
48
  f"⚠️ Handler {handler_name} took {elapsed:.2f}s "
49
49
  f"(close to {timeout_seconds}s timeout)"
50
50
  )
51
51
  except:
52
- print(f"⚠️ Handler {handler_name} took {elapsed:.2f}s (close to {timeout_seconds}s timeout)")
53
-
52
+ print(
53
+ f"⚠️ Handler {handler_name} took {elapsed:.2f}s (close to {timeout_seconds}s timeout)"
54
+ )
55
+
54
56
  return result
55
-
57
+
56
58
  except asyncio.TimeoutError:
57
59
  elapsed = time.time() - start_time
58
60
  # Try to get logger from closure scope or fallback to print
59
61
  try:
60
62
  import logging
63
+
61
64
  logger = logging.getLogger(__name__)
62
- logger.error(f"❌ Handler {handler_name} timed out after {elapsed:.2f}s")
65
+ logger.error(
66
+ f"❌ Handler {handler_name} timed out after {elapsed:.2f}s"
67
+ )
63
68
  except:
64
69
  print(f"❌ Handler {handler_name} timed out after {elapsed:.2f}s")
65
-
70
+
66
71
  return None
67
-
72
+
68
73
  except Exception as e:
69
74
  elapsed = time.time() - start_time
70
75
  # Try to get logger from closure scope or fallback to print
71
76
  try:
72
77
  import logging
78
+
73
79
  logger = logging.getLogger(__name__)
74
- logger.error(f"❌ Handler {handler_name} failed after {elapsed:.2f}s: {e}")
80
+ logger.error(
81
+ f"❌ Handler {handler_name} failed after {elapsed:.2f}s: {e}"
82
+ )
75
83
  except:
76
84
  print(f"❌ Handler {handler_name} failed after {elapsed:.2f}s: {e}")
77
85
  raise
78
-
86
+
79
87
  return wrapper
88
+
80
89
  return decorator
81
90
 
82
91
 
@@ -87,34 +96,34 @@ class ConnectionEventHandler(BaseEventHandler):
87
96
  that deserves its own focused handler. This includes client connections,
88
97
  disconnections, status updates, and event history management.
89
98
  """
90
-
99
+
91
100
  def __init__(self, server):
92
101
  """Initialize connection handler with health monitoring.
93
-
102
+
94
103
  WHY: We need to track connection health metrics and implement
95
104
  ping/pong mechanism for detecting stale connections.
96
105
  """
97
106
  super().__init__(server)
98
-
107
+
99
108
  # Connection health tracking
100
109
  self.connection_metrics = {}
101
110
  self.last_ping_times = {}
102
111
  self.ping_interval = 45 # seconds - avoid conflict with Engine.IO pings
103
112
  self.ping_timeout = 20 # seconds - more lenient timeout
104
113
  self.stale_check_interval = 90 # seconds - less frequent checks
105
-
114
+
106
115
  # Health monitoring tasks (will be started after event registration)
107
116
  self.ping_task = None
108
117
  self.stale_check_task = None
109
118
 
110
119
  def _start_health_monitoring(self):
111
120
  """Start background tasks for connection health monitoring.
112
-
121
+
113
122
  WHY: We need to actively monitor connection health to detect
114
123
  and clean up stale connections, ensuring reliable event delivery.
115
124
  """
116
125
  # Only start if we have a valid event loop and tasks aren't already running
117
- if hasattr(self.server, 'core') and hasattr(self.server.core, 'loop'):
126
+ if hasattr(self.server, "core") and hasattr(self.server.core, "loop"):
118
127
  loop = self.server.core.loop
119
128
  if loop and not loop.is_closed():
120
129
  if not self.ping_task or self.ping_task.done():
@@ -122,154 +131,160 @@ class ConnectionEventHandler(BaseEventHandler):
122
131
  self._periodic_ping(), loop
123
132
  )
124
133
  self.logger.info("🏓 Started connection ping monitoring")
125
-
134
+
126
135
  if not self.stale_check_task or self.stale_check_task.done():
127
136
  self.stale_check_task = asyncio.run_coroutine_threadsafe(
128
137
  self._check_stale_connections(), loop
129
138
  )
130
139
  self.logger.info("🧹 Started stale connection checker")
131
-
140
+
132
141
  def stop_health_monitoring(self):
133
142
  """Stop health monitoring tasks.
134
-
143
+
135
144
  WHY: Clean shutdown requires stopping background tasks to
136
145
  prevent errors and resource leaks.
137
146
  """
138
147
  if self.ping_task and not self.ping_task.done():
139
148
  self.ping_task.cancel()
140
149
  self.logger.info("🚫 Stopped connection ping monitoring")
141
-
150
+
142
151
  if self.stale_check_task and not self.stale_check_task.done():
143
152
  self.stale_check_task.cancel()
144
153
  self.logger.info("🚫 Stopped stale connection checker")
145
-
154
+
146
155
  async def _periodic_ping(self):
147
156
  """Send periodic pings to all connected clients.
148
-
157
+
149
158
  WHY: WebSocket connections can silently fail. Regular pings
150
159
  help detect dead connections and maintain connection state.
151
160
  """
152
161
  while True:
153
162
  try:
154
163
  await asyncio.sleep(self.ping_interval)
155
-
164
+
156
165
  if not self.clients:
157
166
  continue
158
-
167
+
159
168
  current_time = time.time()
160
169
  disconnected = []
161
-
170
+
162
171
  for sid in list(self.clients):
163
172
  try:
164
173
  # Send ping and record time (using new schema)
165
- await self.sio.emit('ping', {
166
- 'type': 'system',
167
- 'subtype': 'ping',
168
- 'timestamp': current_time,
169
- 'source': 'server'
170
- }, room=sid)
174
+ await self.sio.emit(
175
+ "ping",
176
+ {
177
+ "type": "system",
178
+ "subtype": "ping",
179
+ "timestamp": current_time,
180
+ "source": "server",
181
+ },
182
+ room=sid,
183
+ )
171
184
  self.last_ping_times[sid] = current_time
172
-
185
+
173
186
  # Update connection metrics
174
187
  if sid not in self.connection_metrics:
175
188
  self.connection_metrics[sid] = {
176
- 'connected_at': current_time,
177
- 'reconnects': 0,
178
- 'failures': 0,
179
- 'last_activity': current_time
189
+ "connected_at": current_time,
190
+ "reconnects": 0,
191
+ "failures": 0,
192
+ "last_activity": current_time,
180
193
  }
181
- self.connection_metrics[sid]['last_activity'] = current_time
182
-
194
+ self.connection_metrics[sid]["last_activity"] = current_time
195
+
183
196
  except Exception as e:
184
197
  self.logger.warning(f"Failed to ping client {sid}: {e}")
185
198
  disconnected.append(sid)
186
-
199
+
187
200
  # Clean up failed connections
188
201
  for sid in disconnected:
189
202
  await self._cleanup_stale_connection(sid)
190
-
203
+
191
204
  if self.clients:
192
205
  self.logger.debug(
193
206
  f"🏓 Sent pings to {len(self.clients)} clients, "
194
207
  f"{len(disconnected)} failed"
195
208
  )
196
-
209
+
197
210
  except Exception as e:
198
211
  self.logger.error(f"Error in periodic ping: {e}")
199
-
212
+
200
213
  async def _check_stale_connections(self):
201
214
  """Check for and clean up stale connections.
202
-
215
+
203
216
  WHY: Some clients may not properly disconnect, leaving zombie
204
217
  connections that consume resources and prevent proper cleanup.
205
218
  """
206
219
  while True:
207
220
  try:
208
221
  await asyncio.sleep(self.stale_check_interval)
209
-
222
+
210
223
  current_time = time.time()
211
- stale_threshold = current_time - (self.ping_timeout + self.ping_interval)
224
+ stale_threshold = current_time - (
225
+ self.ping_timeout + self.ping_interval
226
+ )
212
227
  stale_sids = []
213
-
228
+
214
229
  for sid in list(self.clients):
215
230
  last_ping = self.last_ping_times.get(sid, 0)
216
-
231
+
217
232
  if last_ping < stale_threshold:
218
233
  stale_sids.append(sid)
219
234
  self.logger.warning(
220
235
  f"🧟 Detected stale connection {sid} "
221
236
  f"(last ping: {current_time - last_ping:.1f}s ago)"
222
237
  )
223
-
238
+
224
239
  # Clean up stale connections
225
240
  for sid in stale_sids:
226
241
  await self._cleanup_stale_connection(sid)
227
-
242
+
228
243
  if stale_sids:
229
244
  self.logger.info(
230
245
  f"🧹 Cleaned up {len(stale_sids)} stale connections"
231
246
  )
232
-
247
+
233
248
  except Exception as e:
234
249
  self.logger.error(f"Error checking stale connections: {e}")
235
-
250
+
236
251
  async def _cleanup_stale_connection(self, sid: str):
237
252
  """Clean up a stale or dead connection.
238
-
253
+
239
254
  WHY: Proper cleanup prevents memory leaks and ensures
240
255
  accurate connection tracking.
241
256
  """
242
257
  try:
243
258
  if sid in self.clients:
244
259
  self.clients.remove(sid)
245
-
260
+
246
261
  if sid in self.last_ping_times:
247
262
  del self.last_ping_times[sid]
248
-
263
+
249
264
  if sid in self.connection_metrics:
250
265
  metrics = self.connection_metrics[sid]
251
- uptime = time.time() - metrics.get('connected_at', 0)
266
+ uptime = time.time() - metrics.get("connected_at", 0)
252
267
  self.logger.info(
253
268
  f"📊 Connection {sid} stats - uptime: {uptime:.1f}s, "
254
269
  f"reconnects: {metrics.get('reconnects', 0)}, "
255
270
  f"failures: {metrics.get('failures', 0)}"
256
271
  )
257
272
  del self.connection_metrics[sid]
258
-
273
+
259
274
  # Force disconnect if still connected
260
275
  try:
261
276
  await self.sio.disconnect(sid)
262
277
  except:
263
278
  pass # Already disconnected
264
-
279
+
265
280
  self.logger.info(f"🔌 Cleaned up stale connection: {sid}")
266
-
281
+
267
282
  except Exception as e:
268
283
  self.logger.error(f"Error cleaning up connection {sid}: {e}")
269
-
284
+
270
285
  def register_events(self) -> None:
271
286
  """Register connection-related event handlers."""
272
-
287
+
273
288
  # Start health monitoring now that we're registering events
274
289
  self._start_health_monitoring()
275
290
 
@@ -293,13 +308,18 @@ class ConnectionEventHandler(BaseEventHandler):
293
308
  monitor_build_info = {}
294
309
  try:
295
310
  from ....services.monitor_build_service import get_monitor_build_service
311
+
296
312
  monitor_service = get_monitor_build_service()
297
313
  monitor_build_info = monitor_service.get_build_info_sync()
298
314
  except Exception as e:
299
315
  self.logger.debug(f"Could not get monitor build info: {e}")
300
316
  monitor_build_info = {
301
- "monitor": {"version": "1.0.0", "build": 1, "formatted_build": "0001"},
302
- "mpm": {"version": "unknown", "build": "unknown"}
317
+ "monitor": {
318
+ "version": "1.0.0",
319
+ "build": 1,
320
+ "formatted_build": "0001",
321
+ },
322
+ "mpm": {"version": "unknown", "build": "unknown"},
303
323
  }
304
324
 
305
325
  # Send initial status immediately with enhanced data (using new schema)
@@ -317,7 +337,7 @@ class ConnectionEventHandler(BaseEventHandler):
317
337
  "server_version": "2.0.0",
318
338
  "client_id": sid,
319
339
  "build_info": monitor_build_info,
320
- }
340
+ },
321
341
  }
322
342
 
323
343
  try:
@@ -336,7 +356,7 @@ class ConnectionEventHandler(BaseEventHandler):
336
356
  "client_id": sid,
337
357
  "server_time": datetime.utcnow().isoformat() + "Z",
338
358
  "build_info": monitor_build_info,
339
- }
359
+ },
340
360
  },
341
361
  )
342
362
 
@@ -362,10 +382,8 @@ class ConnectionEventHandler(BaseEventHandler):
362
382
  self.logger.info(f"🔌 CLIENT DISCONNECTED: {sid}")
363
383
  self.logger.info(f"📉 Total clients now: {len(self.clients)}")
364
384
  else:
365
- self.logger.warning(
366
- f"⚠️ Attempted to disconnect unknown client: {sid}"
367
- )
368
-
385
+ self.logger.warning(f"⚠️ Attempted to disconnect unknown client: {sid}")
386
+
369
387
  # Clean up health tracking
370
388
  if sid in self.last_ping_times:
371
389
  del self.last_ping_times[sid]
@@ -391,7 +409,7 @@ class ConnectionEventHandler(BaseEventHandler):
391
409
  "clients_connected": len(self.clients),
392
410
  "claude_status": self.server.claude_status,
393
411
  "claude_pid": self.server.claude_pid,
394
- }
412
+ },
395
413
  }
396
414
  await self.emit_to_client(sid, "status", status_data)
397
415
 
@@ -432,13 +450,17 @@ class ConnectionEventHandler(BaseEventHandler):
432
450
  for filtered event streaming.
433
451
  """
434
452
  channels = data.get("channels", ["*"]) if data else ["*"]
435
- await self.emit_to_client(sid, "subscribed", {
436
- "type": "connection",
437
- "subtype": "subscribed",
438
- "timestamp": datetime.utcnow().isoformat() + "Z",
439
- "source": "server",
440
- "data": {"channels": channels}
441
- })
453
+ await self.emit_to_client(
454
+ sid,
455
+ "subscribed",
456
+ {
457
+ "type": "connection",
458
+ "subtype": "subscribed",
459
+ "timestamp": datetime.utcnow().isoformat() + "Z",
460
+ "source": "server",
461
+ "data": {"channels": channels},
462
+ },
463
+ )
442
464
 
443
465
  @self.sio.event
444
466
  @timeout_handler(timeout_seconds=5.0)
@@ -450,55 +472,58 @@ class ConnectionEventHandler(BaseEventHandler):
450
472
  """
451
473
  # Add debug logging
452
474
  self.logger.info(f"🔵 Received claude_event from {sid}: {data}")
453
-
475
+
454
476
  # Check if this is a hook event and route to HookEventHandler
455
477
  # Hook events can have either:
456
478
  # 1. Normalized format: type="hook", subtype="pre_tool"
457
479
  # 2. Legacy format: type="hook.pre_tool"
458
480
  if isinstance(data, dict):
459
481
  event_type = data.get("type", "")
460
- event_subtype = data.get("subtype", "")
461
-
482
+ data.get("subtype", "")
483
+
462
484
  # Check for both normalized and legacy formats
463
485
  is_hook_event = False
464
486
  if isinstance(event_type, str):
465
487
  # Legacy format: type="hook.something"
466
- if event_type.startswith("hook."):
467
- is_hook_event = True
468
- # Normalized format: type="hook" with any subtype
469
- elif event_type == "hook":
488
+ if event_type.startswith("hook.") or event_type == "hook":
470
489
  is_hook_event = True
471
-
490
+
472
491
  if is_hook_event:
473
492
  # Get the hook handler if available
474
493
  hook_handler = None
475
494
  # Check if event_registry exists and has handlers
476
- if hasattr(self.server, 'event_registry') and self.server.event_registry:
477
- if hasattr(self.server.event_registry, 'handlers'):
478
- for handler in self.server.event_registry.handlers:
479
- if handler.__class__.__name__ == "HookEventHandler":
480
- hook_handler = handler
481
- break
482
-
495
+ if (
496
+ hasattr(self.server, "event_registry")
497
+ and self.server.event_registry
498
+ ) and hasattr(self.server.event_registry, "handlers"):
499
+ for handler in self.server.event_registry.handlers:
500
+ if handler.__class__.__name__ == "HookEventHandler":
501
+ hook_handler = handler
502
+ break
503
+
483
504
  if hook_handler and hasattr(hook_handler, "process_hook_event"):
484
505
  # Let the hook handler process this event
485
506
  await hook_handler.process_hook_event(data)
486
507
  # Don't double-store or double-broadcast, return early
487
508
  return
488
-
509
+
489
510
  # Normalize event format before storing in history
490
511
  normalized_event = self._normalize_event(data)
491
-
512
+
492
513
  # Store in history - flatten if it's a nested structure
493
514
  # If the normalized event has data.event, promote it to top level
494
- if isinstance(normalized_event, dict) and 'data' in normalized_event:
495
- if isinstance(normalized_event['data'], dict) and 'event' in normalized_event['data']:
515
+ if isinstance(normalized_event, dict) and "data" in normalized_event:
516
+ if (
517
+ isinstance(normalized_event["data"], dict)
518
+ and "event" in normalized_event["data"]
519
+ ):
496
520
  # This is a nested event, flatten it
497
521
  flattened = {
498
- 'type': normalized_event.get('type', 'unknown'),
499
- 'event': normalized_event['data'].get('event'),
500
- 'timestamp': normalized_event.get('timestamp') or normalized_event['data'].get('timestamp'),
501
- 'data': normalized_event['data'].get('data', {})
522
+ "type": normalized_event.get("type", "unknown"),
523
+ "event": normalized_event["data"].get("event"),
524
+ "timestamp": normalized_event.get("timestamp")
525
+ or normalized_event["data"].get("timestamp"),
526
+ "data": normalized_event["data"].get("data", {}),
502
527
  }
503
528
  self.event_history.append(flattened)
504
529
  else:
@@ -510,32 +535,34 @@ class ConnectionEventHandler(BaseEventHandler):
510
535
  )
511
536
 
512
537
  # Re-broadcast to all other clients
513
- self.logger.info(f"📡 Broadcasting claude_event to all clients except {sid}")
538
+ self.logger.info(
539
+ f"📡 Broadcasting claude_event to all clients except {sid}"
540
+ )
514
541
  await self.broadcast_event("claude_event", data, skip_sid=sid)
515
- self.logger.info(f"✅ Broadcast complete")
516
-
542
+ self.logger.info("✅ Broadcast complete")
543
+
517
544
  @self.sio.event
518
545
  async def pong(sid, data=None):
519
546
  """Handle pong response from client.
520
-
547
+
521
548
  WHY: Clients respond to our pings with pongs, confirming
522
549
  they're still alive and the connection is healthy.
523
550
  """
524
551
  current_time = time.time()
525
-
552
+
526
553
  # Update last activity time
527
554
  if sid in self.connection_metrics:
528
- self.connection_metrics[sid]['last_activity'] = current_time
529
-
555
+ self.connection_metrics[sid]["last_activity"] = current_time
556
+
530
557
  # Calculate round-trip time if timestamp provided
531
- if data and 'timestamp' in data:
532
- rtt = current_time - data['timestamp']
558
+ if data and "timestamp" in data:
559
+ rtt = current_time - data["timestamp"]
533
560
  if rtt < 10: # Reasonable RTT
534
561
  self.logger.debug(f"🏓 Pong from {sid}, RTT: {rtt*1000:.1f}ms")
535
562
 
536
563
  def _normalize_event(self, event_data: Dict[str, Any]) -> Dict[str, Any]:
537
564
  """Normalize event format to ensure consistency.
538
-
565
+
539
566
  WHY: Different clients may send events in different formats.
540
567
  This ensures all events have a consistent 'type' field for
541
568
  proper display in the dashboard, while preserving the original
@@ -543,37 +570,37 @@ class ConnectionEventHandler(BaseEventHandler):
543
570
  """
544
571
  if not isinstance(event_data, dict):
545
572
  return event_data
546
-
573
+
547
574
  # Make a copy to avoid modifying the original
548
575
  normalized = dict(event_data)
549
-
576
+
550
577
  # If event has no 'type' but has 'event' field (legacy format)
551
- if 'type' not in normalized and 'event' in normalized:
552
- event_name = normalized['event']
553
-
578
+ if "type" not in normalized and "event" in normalized:
579
+ event_name = normalized["event"]
580
+
554
581
  # Map common event names to proper type
555
- if event_name in ['TestStart', 'TestEnd']:
556
- normalized['type'] = 'test'
557
- elif event_name in ['SubagentStart', 'SubagentStop']:
558
- normalized['type'] = 'subagent'
559
- elif event_name == 'ToolCall':
560
- normalized['type'] = 'tool'
561
- elif event_name == 'UserPrompt':
562
- normalized['type'] = 'hook'
563
- normalized['subtype'] = 'user_prompt'
582
+ if event_name in ["TestStart", "TestEnd"]:
583
+ normalized["type"] = "test"
584
+ elif event_name in ["SubagentStart", "SubagentStop"]:
585
+ normalized["type"] = "subagent"
586
+ elif event_name == "ToolCall":
587
+ normalized["type"] = "tool"
588
+ elif event_name == "UserPrompt":
589
+ normalized["type"] = "hook"
590
+ normalized["subtype"] = "user_prompt"
564
591
  else:
565
592
  # Default to system type for unknown events
566
- normalized['type'] = 'system'
567
-
593
+ normalized["type"] = "system"
594
+
568
595
  # Note: We keep the 'event' field for backward compatibility
569
596
  # Dashboard may use it for display purposes
570
-
597
+
571
598
  # Ensure there's always a type field
572
- if 'type' not in normalized:
573
- normalized['type'] = 'unknown'
574
-
599
+ if "type" not in normalized:
600
+ normalized["type"] = "unknown"
601
+
575
602
  return normalized
576
-
603
+
577
604
  async def _send_event_history(
578
605
  self, sid: str, event_types: Optional[List[str]] = None, limit: int = 50
579
606
  ):