claude-mpm 3.9.11__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 (419) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +2 -2
  3. claude_mpm/__main__.py +3 -2
  4. claude_mpm/agents/__init__.py +85 -79
  5. claude_mpm/agents/agent_loader.py +464 -1003
  6. claude_mpm/agents/agent_loader_integration.py +45 -45
  7. claude_mpm/agents/agents_metadata.py +29 -30
  8. claude_mpm/agents/async_agent_loader.py +156 -138
  9. claude_mpm/agents/base_agent.json +1 -1
  10. claude_mpm/agents/base_agent_loader.py +179 -151
  11. claude_mpm/agents/frontmatter_validator.py +229 -130
  12. claude_mpm/agents/schema/agent_schema.json +1 -1
  13. claude_mpm/agents/system_agent_config.py +213 -147
  14. claude_mpm/agents/templates/__init__.py +13 -13
  15. claude_mpm/agents/templates/code_analyzer.json +2 -2
  16. claude_mpm/agents/templates/data_engineer.json +1 -1
  17. claude_mpm/agents/templates/documentation.json +23 -11
  18. claude_mpm/agents/templates/engineer.json +22 -6
  19. claude_mpm/agents/templates/memory_manager.json +1 -1
  20. claude_mpm/agents/templates/ops.json +2 -2
  21. claude_mpm/agents/templates/project_organizer.json +1 -1
  22. claude_mpm/agents/templates/qa.json +1 -1
  23. claude_mpm/agents/templates/refactoring_engineer.json +222 -0
  24. claude_mpm/agents/templates/research.json +20 -14
  25. claude_mpm/agents/templates/security.json +1 -1
  26. claude_mpm/agents/templates/ticketing.json +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 +79 -51
  31. claude_mpm/cli/__main__.py +3 -2
  32. claude_mpm/cli/commands/__init__.py +20 -20
  33. claude_mpm/cli/commands/agents.py +279 -247
  34. claude_mpm/cli/commands/aggregate.py +138 -157
  35. claude_mpm/cli/commands/cleanup.py +147 -147
  36. claude_mpm/cli/commands/config.py +93 -76
  37. claude_mpm/cli/commands/info.py +17 -16
  38. claude_mpm/cli/commands/mcp.py +140 -905
  39. claude_mpm/cli/commands/mcp_command_router.py +139 -0
  40. claude_mpm/cli/commands/mcp_config_commands.py +20 -0
  41. claude_mpm/cli/commands/mcp_install_commands.py +20 -0
  42. claude_mpm/cli/commands/mcp_server_commands.py +175 -0
  43. claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
  44. claude_mpm/cli/commands/memory.py +239 -203
  45. claude_mpm/cli/commands/monitor.py +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 -1156
  51. claude_mpm/cli/parsers/__init__.py +29 -0
  52. claude_mpm/cli/parsers/agents_parser.py +136 -0
  53. claude_mpm/cli/parsers/base_parser.py +331 -0
  54. claude_mpm/cli/parsers/config_parser.py +85 -0
  55. claude_mpm/cli/parsers/mcp_parser.py +152 -0
  56. claude_mpm/cli/parsers/memory_parser.py +138 -0
  57. claude_mpm/cli/parsers/monitor_parser.py +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 +71 -73
  69. claude_mpm/config/paths.py +94 -208
  70. claude_mpm/config/socketio_config.py +84 -73
  71. claude_mpm/constants.py +35 -18
  72. claude_mpm/core/__init__.py +9 -6
  73. claude_mpm/core/agent_name_normalizer.py +68 -71
  74. claude_mpm/core/agent_registry.py +372 -521
  75. claude_mpm/core/agent_session_manager.py +74 -63
  76. claude_mpm/core/base_service.py +116 -87
  77. claude_mpm/core/cache.py +119 -153
  78. claude_mpm/core/claude_runner.py +425 -1120
  79. claude_mpm/core/config.py +263 -168
  80. claude_mpm/core/config_aliases.py +69 -61
  81. claude_mpm/core/config_constants.py +292 -0
  82. claude_mpm/core/constants.py +57 -99
  83. claude_mpm/core/container.py +211 -178
  84. claude_mpm/core/exceptions.py +233 -89
  85. claude_mpm/core/factories.py +92 -54
  86. claude_mpm/core/framework_loader.py +378 -220
  87. claude_mpm/core/hook_manager.py +198 -83
  88. claude_mpm/core/hook_performance_config.py +136 -0
  89. claude_mpm/core/injectable_service.py +61 -55
  90. claude_mpm/core/interactive_session.py +165 -155
  91. claude_mpm/core/interfaces.py +221 -195
  92. claude_mpm/core/lazy.py +96 -96
  93. claude_mpm/core/logger.py +133 -107
  94. claude_mpm/core/logging_config.py +185 -157
  95. claude_mpm/core/minimal_framework_loader.py +20 -15
  96. claude_mpm/core/mixins.py +30 -29
  97. claude_mpm/core/oneshot_session.py +215 -181
  98. claude_mpm/core/optimized_agent_loader.py +134 -138
  99. claude_mpm/core/optimized_startup.py +159 -157
  100. claude_mpm/core/pm_hook_interceptor.py +85 -72
  101. claude_mpm/core/service_registry.py +103 -101
  102. claude_mpm/core/session_manager.py +97 -87
  103. claude_mpm/core/socketio_pool.py +212 -158
  104. claude_mpm/core/tool_access_control.py +58 -51
  105. claude_mpm/core/types.py +46 -24
  106. claude_mpm/core/typing_utils.py +166 -82
  107. claude_mpm/core/unified_agent_registry.py +721 -0
  108. claude_mpm/core/unified_config.py +550 -0
  109. claude_mpm/core/unified_paths.py +549 -0
  110. claude_mpm/dashboard/index.html +1 -1
  111. claude_mpm/dashboard/open_dashboard.py +51 -17
  112. claude_mpm/dashboard/static/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 +233 -199
  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 +30 -13
  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 +13 -20
  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 +87 -84
  298. claude_mpm/services/mcp_gateway/main.py +287 -137
  299. claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
  300. claude_mpm/services/mcp_gateway/registry/service_registry.py +97 -94
  301. claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
  302. claude_mpm/services/mcp_gateway/server/__init__.py +2 -2
  303. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +105 -110
  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 +109 -119
  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 +19 -533
  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 +2 -2
  358. claude_mpm/storage/state_storage.py +177 -181
  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.11.dist-info → claude_mpm-4.0.3.dist-info}/METADATA +27 -1
  381. claude_mpm-4.0.3.dist-info/RECORD +402 -0
  382. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
  383. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/licenses/LICENSE +1 -1
  384. claude_mpm/cli/commands/run_guarded.py +0 -511
  385. claude_mpm/config/memory_guardian_config.py +0 -325
  386. claude_mpm/config/memory_guardian_yaml.py +0 -335
  387. claude_mpm/core/config_paths.py +0 -150
  388. claude_mpm/core/memory_aware_runner.py +0 -353
  389. claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
  390. claude_mpm/deployment_paths.py +0 -261
  391. claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
  392. claude_mpm/models/state_models.py +0 -433
  393. claude_mpm/services/agent/__init__.py +0 -24
  394. claude_mpm/services/agent/deployment.py +0 -2548
  395. claude_mpm/services/agent/management.py +0 -598
  396. claude_mpm/services/agent/registry.py +0 -813
  397. claude_mpm/services/agents/registry/agent_registry.py +0 -813
  398. claude_mpm/services/communication/socketio.py +0 -1935
  399. claude_mpm/services/communication/websocket.py +0 -479
  400. claude_mpm/services/framework_claude_md_generator.py +0 -624
  401. claude_mpm/services/health_monitor.py +0 -893
  402. claude_mpm/services/infrastructure/graceful_degradation.py +0 -616
  403. claude_mpm/services/infrastructure/health_monitor.py +0 -775
  404. claude_mpm/services/infrastructure/memory_dashboard.py +0 -479
  405. claude_mpm/services/infrastructure/memory_guardian.py +0 -944
  406. claude_mpm/services/infrastructure/restart_protection.py +0 -642
  407. claude_mpm/services/infrastructure/state_manager.py +0 -774
  408. claude_mpm/services/mcp_gateway/manager.py +0 -334
  409. claude_mpm/services/optimized_hook_service.py +0 -542
  410. claude_mpm/services/project_analyzer.py +0 -864
  411. claude_mpm/services/project_registry.py +0 -608
  412. claude_mpm/services/standalone_socketio_server.py +0 -1300
  413. claude_mpm/services/ticket_manager_di.py +0 -318
  414. claude_mpm/services/ticketing_service_original.py +0 -510
  415. claude_mpm/utils/paths.py +0 -395
  416. claude_mpm/utils/platform_memory.py +0 -524
  417. claude_mpm-3.9.11.dist-info/RECORD +0 -306
  418. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/WHEEL +0 -0
  419. {claude_mpm-3.9.11.dist-info → claude_mpm-4.0.3.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,5 @@
1
+ from pathlib import Path
2
+
1
3
  """Event Aggregator Service for Claude MPM.
2
4
 
3
5
  WHY: This service connects to the Socket.IO dashboard server as a client and
@@ -11,43 +13,44 @@ aggregator to run alongside the dashboard without any conflicts.
11
13
 
12
14
  import asyncio
13
15
  import json
14
- import logging
15
16
  import os
16
17
  import signal
17
18
  import sys
18
19
  import threading
19
20
  import time
20
- from datetime import datetime
21
- from pathlib import Path
22
- from typing import Dict, Any, Optional, List
23
21
  from collections import defaultdict
22
+ from datetime import datetime
23
+ from typing import Any, Dict, List, Optional
24
24
 
25
25
  try:
26
26
  import socketio
27
+
27
28
  SOCKETIO_AVAILABLE = True
28
29
  except ImportError:
29
30
  SOCKETIO_AVAILABLE = False
30
31
  socketio = None
31
32
 
32
- from ..models.agent_session import AgentSession, EventCategory
33
33
  from ..core.logger import get_logger
34
+ from ..models.agent_session import AgentSession, EventCategory
34
35
 
35
36
 
36
37
  class EventAggregator:
37
38
  """Aggregates Socket.IO events into complete agent sessions.
38
-
39
+
39
40
  WHY: The dashboard emits events in real-time but doesn't persist complete
40
41
  sessions. This service captures those events and builds structured session
41
42
  documents for analysis and debugging.
42
-
43
+
43
44
  DESIGN DECISION: We maintain active sessions in memory and save them when
44
45
  they complete or after a timeout. This balances memory usage with the need
45
46
  to capture all events even if a session doesn't complete cleanly.
46
47
  """
47
-
48
- def __init__(self, host: str = "localhost", port: int = 8765, save_dir: Optional[str] = None):
48
+
49
+ def __init__(
50
+ self, host: str = "localhost", port: int = 8765, save_dir: Optional[str] = None
51
+ ):
49
52
  """Initialize the event aggregator.
50
-
53
+
51
54
  Args:
52
55
  host: Socket.IO server host
53
56
  port: Socket.IO server port
@@ -56,65 +59,78 @@ class EventAggregator:
56
59
  self.host = host
57
60
  self.port = port
58
61
  self.logger = get_logger("event_aggregator")
59
-
62
+
60
63
  # Load configuration
61
64
  from claude_mpm.core.config import Config
65
+
62
66
  self.config = Config()
63
-
67
+
64
68
  # Session storage
65
69
  self.active_sessions: Dict[str, AgentSession] = {}
66
- self.session_timeout = self.config.get('event_aggregator.session_timeout_minutes', 60) * 60
70
+ self.session_timeout = (
71
+ self.config.get("event_aggregator.session_timeout_minutes", 60) * 60
72
+ )
67
73
  self.last_activity: Dict[str, float] = {}
68
-
74
+
69
75
  # Save directory - use config or provided dir or default to .claude-mpm/activity
70
76
  if save_dir is None:
71
- activity_dir = self.config.get('event_aggregator.activity_directory', '.claude-mpm/activity')
72
- self.save_dir = Path.cwd() / activity_dir if not Path(activity_dir).is_absolute() else Path(activity_dir)
77
+ activity_dir = self.config.get(
78
+ "event_aggregator.activity_directory", ".claude-mpm/activity"
79
+ )
80
+ self.save_dir = (
81
+ Path.cwd() / activity_dir
82
+ if not Path(activity_dir).is_absolute()
83
+ else Path(activity_dir)
84
+ )
73
85
  else:
74
86
  self.save_dir = Path(save_dir)
75
87
  self.save_dir.mkdir(parents=True, exist_ok=True)
76
-
88
+
77
89
  # Socket.IO client
78
90
  self.sio_client = None
79
91
  self.connected = False
80
92
  self.running = False
81
93
  self.client_thread = None
82
94
  self.client_loop = None
83
-
95
+
84
96
  # Event statistics
85
97
  self.total_events_captured = 0
86
98
  self.events_by_type = defaultdict(int)
87
99
  self.sessions_completed = 0
88
-
100
+
89
101
  # Cleanup task
90
102
  self.cleanup_task = None
91
-
92
- self.logger.info(f"Event Aggregator initialized - will connect to {host}:{port}")
103
+
104
+ self.logger.info(
105
+ f"Event Aggregator initialized - will connect to {host}:{port}"
106
+ )
93
107
  self.logger.info(f"Sessions will be saved to: {self.save_dir}")
94
-
108
+
95
109
  def start(self) -> bool:
96
110
  """Start the aggregator service.
97
-
111
+
98
112
  Returns:
99
113
  True if started successfully, False otherwise
100
114
  """
101
115
  if not SOCKETIO_AVAILABLE:
102
- self.logger.error("Socket.IO client not available. Install python-socketio package.")
116
+ self.logger.error(
117
+ "Socket.IO client not available. Install python-socketio package."
118
+ )
103
119
  return False
104
-
120
+
105
121
  if self.running:
106
122
  self.logger.warning("Aggregator already running")
107
123
  return True
108
-
124
+
109
125
  self.running = True
110
-
126
+
111
127
  # Start the Socket.IO client in a background thread
112
128
  self.client_thread = threading.Thread(target=self._run_client, daemon=True)
113
129
  self.client_thread.start()
114
-
130
+
115
131
  # Wait a moment for connection
116
132
  time.sleep(1)
117
-
133
+
118
134
  if self.connected:
119
135
  self.logger.info("Event Aggregator started successfully")
120
136
  return True
@@ -122,44 +138,45 @@ class EventAggregator:
122
138
  self.logger.error("Failed to connect to Socket.IO server")
123
139
  self.running = False
124
140
  return False
125
-
141
+
126
142
  def stop(self):
127
143
  """Stop the aggregator service."""
128
144
  self.logger.info("Stopping Event Aggregator...")
129
145
  self.running = False
130
-
146
+
131
147
  # Save all active sessions
132
148
  self._save_all_sessions()
133
-
149
+
134
150
  # Disconnect Socket.IO client
135
151
  if self.sio_client and self.connected:
136
152
  try:
137
153
  asyncio.run_coroutine_threadsafe(
138
- self.sio_client.disconnect(),
139
- self.client_loop
154
+ self.sio_client.disconnect(), self.client_loop
140
155
  ).result(timeout=2)
141
156
  except:
142
157
  pass
143
-
158
+
144
159
  # Stop the client thread
145
160
  if self.client_thread and self.client_thread.is_alive():
146
161
  self.client_thread.join(timeout=3)
147
-
148
- self.logger.info(f"Event Aggregator stopped - captured {self.total_events_captured} events")
162
+
163
+ self.logger.info(
164
+ f"Event Aggregator stopped - captured {self.total_events_captured} events"
165
+ )
149
166
  self.logger.info(f"Completed sessions: {self.sessions_completed}")
150
-
167
+
151
168
  def _run_client(self):
152
169
  """Run the Socket.IO client in a background thread."""
153
170
  self.client_loop = asyncio.new_event_loop()
154
171
  asyncio.set_event_loop(self.client_loop)
155
-
172
+
156
173
  try:
157
174
  self.client_loop.run_until_complete(self._connect_and_listen())
158
175
  except Exception as e:
159
176
  self.logger.error(f"Client thread error: {e}")
160
177
  finally:
161
178
  self.client_loop.close()
162
-
179
+
163
180
  async def _connect_and_listen(self):
164
181
  """Connect to Socket.IO server and listen for events."""
165
182
  try:
@@ -167,24 +184,24 @@ class EventAggregator:
167
184
  reconnection=True,
168
185
  reconnection_attempts=0, # Infinite retries
169
186
  reconnection_delay=1,
170
- reconnection_delay_max=5
187
+ reconnection_delay_max=5,
171
188
  )
172
-
189
+
173
190
  # Register event handlers
174
191
  self._register_handlers()
175
-
192
+
176
193
  # Connect to server
177
- url = f'http://{self.host}:{self.port}'
194
+ url = f"http://{self.host}:{self.port}"
178
195
  self.logger.info(f"Connecting to Socket.IO server at {url}")
179
196
  await self.sio_client.connect(url)
180
-
197
+
181
198
  # Start cleanup task
182
199
  self.cleanup_task = asyncio.create_task(self._periodic_cleanup())
183
-
200
+
184
201
  # Keep running until stopped
185
202
  while self.running:
186
203
  await asyncio.sleep(0.5)
187
-
204
+
188
205
  # Cancel cleanup task
189
206
  if self.cleanup_task:
190
207
  self.cleanup_task.cancel()
@@ -192,35 +209,33 @@ class EventAggregator:
192
209
  await self.cleanup_task
193
210
  except asyncio.CancelledError:
194
211
  pass
195
-
212
+
196
213
  except Exception as e:
197
214
  self.logger.error(f"Connection error: {e}")
198
215
  self.connected = False
199
-
216
+
200
217
  def _register_handlers(self):
201
218
  """Register Socket.IO event handlers."""
202
-
219
+
203
220
  @self.sio_client.event
204
221
  async def connect():
205
222
  """Handle connection to server."""
206
223
  self.connected = True
207
224
  self.logger.info("Connected to Socket.IO server")
208
-
225
+
209
226
  # Request event history to catch up on any missed events
210
- await self.sio_client.emit('get_history', {
211
- 'limit': 100
212
- })
213
-
227
+ await self.sio_client.emit("get_history", {"limit": 100})
228
+
214
229
  @self.sio_client.event
215
230
  async def disconnect():
216
231
  """Handle disconnection from server."""
217
232
  self.connected = False
218
233
  self.logger.warning("Disconnected from Socket.IO server")
219
-
234
+
220
235
  @self.sio_client.event
221
236
  async def claude_event(data):
222
237
  """Handle Claude events from the server.
223
-
238
+
224
239
  WHY: This is the main event handler that captures all events
225
240
  emitted by the dashboard and processes them into sessions.
226
241
  """
@@ -228,143 +243,151 @@ class EventAggregator:
228
243
  await self._process_event(data)
229
244
  except Exception as e:
230
245
  self.logger.error(f"Error processing event: {e}")
231
-
246
+
232
247
  @self.sio_client.event
233
248
  async def history(data):
234
249
  """Handle historical events from the server.
235
-
250
+
236
251
  WHY: When we connect, we request recent history to ensure we
237
252
  don't miss events from sessions that started before we connected.
238
253
  """
239
254
  try:
240
- events = data.get('events', [])
255
+ events = data.get("events", [])
241
256
  self.logger.info(f"Received {len(events)} historical events")
242
-
257
+
243
258
  for event in events:
244
259
  await self._process_event(event)
245
-
260
+
246
261
  except Exception as e:
247
262
  self.logger.error(f"Error processing history: {e}")
248
-
263
+
249
264
  async def _process_event(self, event_data: Dict[str, Any]):
250
265
  """Process a single event and add it to the appropriate session.
251
-
266
+
252
267
  WHY: Each event needs to be routed to the correct session and
253
268
  processed according to its type.
254
269
  """
255
270
  try:
256
271
  # Extract event metadata
257
- event_type = event_data.get('type', 'unknown')
258
- timestamp = event_data.get('timestamp', datetime.utcnow().isoformat() + 'Z')
259
- data = event_data.get('data', {})
260
-
272
+ event_type = event_data.get("type", "unknown")
273
+ timestamp = event_data.get("timestamp", datetime.utcnow().isoformat() + "Z")
274
+ data = event_data.get("data", {})
275
+
261
276
  # Update statistics
262
277
  self.total_events_captured += 1
263
278
  self.events_by_type[event_type] += 1
264
-
279
+
265
280
  # Determine session ID
266
281
  session_id = self._extract_session_id(event_type, data)
267
-
282
+
268
283
  if not session_id:
269
284
  # Some events don't belong to a specific session
270
285
  self.logger.debug(f"Event {event_type} has no session ID, skipping")
271
286
  return
272
-
287
+
273
288
  # Get or create session
274
- session = self._get_or_create_session(session_id, event_type, data, timestamp)
275
-
289
+ session = self._get_or_create_session(
290
+ session_id, event_type, data, timestamp
291
+ )
292
+
276
293
  # Add event to session
277
294
  session.add_event(event_type, data, timestamp)
278
-
295
+
279
296
  # Update last activity time
280
297
  self.last_activity[session_id] = time.time()
281
-
298
+
282
299
  # Check if session ended
283
- if event_type in ['session.end', 'Stop']:
300
+ if event_type in ["session.end", "Stop"]:
284
301
  await self._finalize_session(session_id)
285
-
302
+
286
303
  # Log progress periodically
287
304
  if self.total_events_captured % 100 == 0:
288
- self.logger.info(f"Processed {self.total_events_captured} events, "
289
- f"active sessions: {len(self.active_sessions)}")
290
-
305
+ self.logger.info(
306
+ f"Processed {self.total_events_captured} events, "
307
+ f"active sessions: {len(self.active_sessions)}"
308
+ )
309
+
291
310
  except Exception as e:
292
- self.logger.error(f"Error processing event {event_data.get('type', 'unknown')}: {e}")
293
-
294
- def _extract_session_id(self, event_type: str, data: Dict[str, Any]) -> Optional[str]:
311
+ self.logger.error(
312
+ f"Error processing event {event_data.get('type', 'unknown')}: {e}"
313
+ )
314
+
315
+ def _extract_session_id(
316
+ self, event_type: str, data: Dict[str, Any]
317
+ ) -> Optional[str]:
295
318
  """Extract session ID from event data.
296
-
319
+
297
320
  WHY: Events use different field names for session ID depending on
298
321
  their source and type.
299
322
  """
300
323
  # Try common session ID fields
301
324
  session_id = (
302
- data.get('session_id') or
303
- data.get('sessionId') or
304
- data.get('session') or
305
- data.get('sid')
325
+ data.get("session_id")
326
+ or data.get("sessionId")
327
+ or data.get("session")
328
+ or data.get("sid")
306
329
  )
307
-
330
+
308
331
  # For session.start events, the session_id is the key piece of data
309
- if event_type == 'session.start' and 'session_id' in data:
310
- return data['session_id']
311
-
332
+ if event_type == "session.start" and "session_id" in data:
333
+ return data["session_id"]
334
+
312
335
  # For hook events, check nested data
313
336
  if not session_id and isinstance(data, dict):
314
- for key in ['hook_data', 'event_data', 'context']:
337
+ for key in ["hook_data", "event_data", "context"]:
315
338
  if key in data and isinstance(data[key], dict):
316
- nested_id = data[key].get('session_id') or data[key].get('sessionId')
339
+ nested_id = data[key].get("session_id") or data[key].get(
340
+ "sessionId"
341
+ )
317
342
  if nested_id:
318
343
  return nested_id
319
-
344
+
320
345
  return session_id
321
-
322
- def _get_or_create_session(self, session_id: str, event_type: str,
323
- data: Dict[str, Any], timestamp: str) -> AgentSession:
346
+
347
+ def _get_or_create_session(
348
+ self, session_id: str, event_type: str, data: Dict[str, Any], timestamp: str
349
+ ) -> AgentSession:
324
350
  """Get existing session or create a new one.
325
-
351
+
326
352
  WHY: Sessions are created on demand when we see the first event
327
353
  for a new session ID.
328
354
  """
329
355
  if session_id not in self.active_sessions:
330
356
  # Create new session
331
- session = AgentSession(
332
- session_id=session_id,
333
- start_time=timestamp
334
- )
335
-
357
+ session = AgentSession(session_id=session_id, start_time=timestamp)
358
+
336
359
  # Extract initial metadata if this is a session.start event
337
- if event_type == 'session.start':
338
- session.working_directory = data.get('working_directory', '')
339
- session.launch_method = data.get('launch_method', '')
340
- session.claude_pid = data.get('pid')
341
-
360
+ if event_type == "session.start":
361
+ session.working_directory = data.get("working_directory", "")
362
+ session.launch_method = data.get("launch_method", "")
363
+ session.claude_pid = data.get("pid")
364
+
342
365
  # Try to get git branch and project info
343
- instance_info = data.get('instance_info', {})
344
- session.git_branch = instance_info.get('git_branch')
345
- session.project_root = instance_info.get('working_dir')
346
-
366
+ instance_info = data.get("instance_info", {})
367
+ session.git_branch = instance_info.get("git_branch")
368
+ session.project_root = instance_info.get("working_dir")
369
+
347
370
  self.active_sessions[session_id] = session
348
371
  self.last_activity[session_id] = time.time()
349
-
372
+
350
373
  self.logger.info(f"Created new session: {session_id[:8]}...")
351
-
374
+
352
375
  return self.active_sessions[session_id]
353
-
376
+
354
377
  async def _finalize_session(self, session_id: str):
355
378
  """Finalize and save a completed session.
356
-
379
+
357
380
  WHY: When a session ends, we need to calculate final metrics
358
381
  and persist it to disk.
359
382
  """
360
383
  if session_id not in self.active_sessions:
361
384
  return
362
-
385
+
363
386
  session = self.active_sessions[session_id]
364
-
387
+
365
388
  # Finalize the session
366
389
  session.finalize()
367
-
390
+
368
391
  # Save to file
369
392
  try:
370
393
  filepath = session.save_to_file(self.save_dir)
@@ -372,20 +395,22 @@ class EventAggregator:
372
395
  self.logger.info(f" - Events: {session.metrics.total_events}")
373
396
  self.logger.info(f" - Delegations: {session.metrics.total_delegations}")
374
397
  self.logger.info(f" - Tools used: {len(session.metrics.tools_used)}")
375
- self.logger.info(f" - Files modified: {len(session.metrics.files_modified)}")
376
-
398
+ self.logger.info(
399
+ f" - Files modified: {len(session.metrics.files_modified)}"
400
+ )
401
+
377
402
  self.sessions_completed += 1
378
403
  except Exception as e:
379
404
  self.logger.error(f"Failed to save session {session_id}: {e}")
380
-
405
+
381
406
  # Remove from active sessions
382
407
  del self.active_sessions[session_id]
383
408
  if session_id in self.last_activity:
384
409
  del self.last_activity[session_id]
385
-
410
+
386
411
  async def _periodic_cleanup(self):
387
412
  """Periodically clean up inactive sessions.
388
-
413
+
389
414
  WHY: Some sessions may not complete cleanly, so we need to
390
415
  periodically save and remove inactive sessions to prevent
391
416
  memory leaks.
@@ -393,26 +418,28 @@ class EventAggregator:
393
418
  while self.running:
394
419
  try:
395
420
  await asyncio.sleep(60) # Check every minute
396
-
421
+
397
422
  current_time = time.time()
398
423
  sessions_to_finalize = []
399
-
424
+
400
425
  for session_id, last_time in list(self.last_activity.items()):
401
426
  if current_time - last_time > self.session_timeout:
402
427
  sessions_to_finalize.append(session_id)
403
-
428
+
404
429
  for session_id in sessions_to_finalize:
405
- self.logger.info(f"Finalizing inactive session: {session_id[:8]}...")
430
+ self.logger.info(
431
+ f"Finalizing inactive session: {session_id[:8]}..."
432
+ )
406
433
  await self._finalize_session(session_id)
407
-
434
+
408
435
  except asyncio.CancelledError:
409
436
  break
410
437
  except Exception as e:
411
438
  self.logger.error(f"Error in cleanup task: {e}")
412
-
439
+
413
440
  def _save_all_sessions(self):
414
441
  """Save all active sessions to disk.
415
-
442
+
416
443
  WHY: Called on shutdown to ensure we don't lose any data.
417
444
  """
418
445
  for session_id in list(self.active_sessions.keys()):
@@ -420,84 +447,95 @@ class EventAggregator:
420
447
  session = self.active_sessions[session_id]
421
448
  session.finalize()
422
449
  filepath = session.save_to_file(self.save_dir)
423
- self.logger.info(f"Saved active session {session_id[:8]}... to {filepath}")
450
+ self.logger.info(
451
+ f"Saved active session {session_id[:8]}... to {filepath}"
452
+ )
424
453
  except Exception as e:
425
454
  self.logger.error(f"Failed to save session {session_id}: {e}")
426
-
455
+
427
456
  def get_status(self) -> Dict[str, Any]:
428
457
  """Get current status of the aggregator.
429
-
458
+
430
459
  Returns:
431
460
  Status dictionary with metrics and state
432
461
  """
433
462
  return {
434
- 'running': self.running,
435
- 'connected': self.connected,
436
- 'server': f"{self.host}:{self.port}",
437
- 'save_directory': str(self.save_dir),
438
- 'active_sessions': len(self.active_sessions),
439
- 'sessions_completed': self.sessions_completed,
440
- 'total_events': self.total_events_captured,
441
- 'events_by_type': dict(self.events_by_type),
442
- 'active_session_ids': [sid[:8] + '...' for sid in self.active_sessions.keys()]
463
+ "running": self.running,
464
+ "connected": self.connected,
465
+ "server": f"{self.host}:{self.port}",
466
+ "save_directory": str(self.save_dir),
467
+ "active_sessions": len(self.active_sessions),
468
+ "sessions_completed": self.sessions_completed,
469
+ "total_events": self.total_events_captured,
470
+ "events_by_type": dict(self.events_by_type),
471
+ "active_session_ids": [
472
+ sid[:8] + "..." for sid in self.active_sessions.keys()
473
+ ],
443
474
  }
444
-
475
+
445
476
  def list_sessions(self, limit: int = 10) -> List[Dict[str, Any]]:
446
477
  """List captured sessions.
447
-
478
+
448
479
  Args:
449
480
  limit: Maximum number of sessions to return
450
-
481
+
451
482
  Returns:
452
483
  List of session summaries
453
484
  """
454
485
  sessions = []
455
-
486
+
456
487
  # Get saved session files
457
488
  session_files = sorted(
458
- self.save_dir.glob('session_*.json'),
489
+ self.save_dir.glob("session_*.json"),
459
490
  key=lambda p: p.stat().st_mtime,
460
- reverse=True
491
+ reverse=True,
461
492
  )[:limit]
462
-
493
+
463
494
  for filepath in session_files:
464
495
  try:
465
496
  # Load just the metadata, not the full session
466
- with open(filepath, 'r') as f:
497
+ with open(filepath, "r") as f:
467
498
  data = json.load(f)
468
-
469
- sessions.append({
470
- 'file': filepath.name,
471
- 'session_id': data.get('session_id', 'unknown')[:8] + '...',
472
- 'start_time': data.get('start_time', 'unknown'),
473
- 'end_time': data.get('end_time', 'unknown'),
474
- 'events': data.get('metrics', {}).get('total_events', 0),
475
- 'delegations': data.get('metrics', {}).get('total_delegations', 0),
476
- 'initial_prompt': (data.get('initial_prompt', '')[:50] + '...')
477
- if data.get('initial_prompt') else 'N/A'
478
- })
499
+
500
+ sessions.append(
501
+ {
502
+ "file": filepath.name,
503
+ "session_id": data.get("session_id", "unknown")[:8] + "...",
504
+ "start_time": data.get("start_time", "unknown"),
505
+ "end_time": data.get("end_time", "unknown"),
506
+ "events": data.get("metrics", {}).get("total_events", 0),
507
+ "delegations": data.get("metrics", {}).get(
508
+ "total_delegations", 0
509
+ ),
510
+ "initial_prompt": (
511
+ (data.get("initial_prompt", "")[:50] + "...")
512
+ if data.get("initial_prompt")
513
+ else "N/A"
514
+ ),
515
+ }
516
+ )
479
517
  except Exception as e:
480
518
  self.logger.error(f"Error reading session file {filepath}: {e}")
481
-
519
+
482
520
  return sessions
483
-
521
+
484
522
  def load_session(self, session_id_prefix: str) -> Optional[AgentSession]:
485
523
  """Load a session by ID prefix.
486
-
524
+
487
525
  Args:
488
526
  session_id_prefix: First few characters of session ID
489
-
527
+
490
528
  Returns:
491
529
  AgentSession if found, None otherwise
492
530
  """
493
531
  # Search for matching session file
494
- for filepath in self.save_dir.glob('session_*.json'):
532
+ for filepath in self.save_dir.glob("session_*.json"):
495
533
  if session_id_prefix in filepath.name:
496
534
  try:
497
535
  return AgentSession.load_from_file(str(filepath))
498
536
  except Exception as e:
499
537
  self.logger.error(f"Error loading session from {filepath}: {e}")
500
-
538
+
501
539
  return None
502
540
 
503
541
 
@@ -544,4 +582,4 @@ def _signal_handler(signum, frame):
544
582
 
545
583
  # Register signal handlers
546
584
  signal.signal(signal.SIGINT, _signal_handler)
547
- signal.signal(signal.SIGTERM, _signal_handler)
585
+ signal.signal(signal.SIGTERM, _signal_handler)