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
@@ -10,7 +10,14 @@ class ModuleViewer {
10
10
  this.jsonContainer = null;
11
11
  this.currentEvent = null;
12
12
  this.eventsByClass = new Map();
13
+
14
+ // Global JSON visibility state - persisted across all events
15
+ // When true, all events show JSON expanded; when false, all collapsed
16
+ this.globalJsonExpanded = localStorage.getItem('dashboard-json-expanded') === 'true';
13
17
 
18
+ // Track if keyboard listener has been added to avoid duplicates
19
+ this.keyboardListenerAdded = false;
20
+
14
21
  this.init();
15
22
  }
16
23
 
@@ -29,7 +36,7 @@ class ModuleViewer {
29
36
  setupContainers() {
30
37
  this.dataContainer = document.getElementById('module-data-content');
31
38
  this.jsonContainer = null; // No longer used - JSON is handled via collapsible sections
32
-
39
+
33
40
  if (!this.dataContainer) {
34
41
  console.error('Module viewer data container not found');
35
42
  }
@@ -67,9 +74,9 @@ class ModuleViewer {
67
74
  </div>
68
75
  `;
69
76
  }
70
-
77
+
71
78
  // JSON container no longer exists - handled via collapsible sections
72
-
79
+
73
80
  this.currentEvent = null;
74
81
  }
75
82
 
@@ -79,10 +86,10 @@ class ModuleViewer {
79
86
  */
80
87
  showEventDetails(event) {
81
88
  this.currentEvent = event;
82
-
89
+
83
90
  // Render structured data in top pane
84
91
  this.renderStructuredData(event);
85
-
92
+
86
93
  // Render JSON in bottom pane
87
94
  this.renderJsonData(event);
88
95
  }
@@ -93,19 +100,19 @@ class ModuleViewer {
93
100
  */
94
101
  renderStructuredData(event) {
95
102
  if (!this.dataContainer) return;
96
-
103
+
97
104
  // Create contextual header
98
105
  const contextualHeader = this.createContextualHeader(event);
99
-
106
+
100
107
  // Create structured view based on event type
101
108
  const structuredView = this.createEventStructuredView(event);
102
-
109
+
103
110
  // Create collapsible JSON section
104
111
  const collapsibleJsonSection = this.createCollapsibleJsonSection(event);
105
-
112
+
106
113
  // Combine all sections in data container
107
114
  this.dataContainer.innerHTML = contextualHeader + structuredView + collapsibleJsonSection;
108
-
115
+
109
116
  // Initialize JSON toggle functionality
110
117
  this.initializeJsonToggle();
111
118
  }
@@ -147,7 +154,7 @@ class ModuleViewer {
147
154
  */
148
155
  updateEventsByClass(events) {
149
156
  this.eventsByClass.clear();
150
-
157
+
151
158
  events.forEach(event => {
152
159
  const eventClass = this.getEventClass(event);
153
160
  if (!this.eventsByClass.has(eventClass)) {
@@ -164,7 +171,7 @@ class ModuleViewer {
164
171
  */
165
172
  getEventClass(event) {
166
173
  if (!event.type) return 'unknown';
167
-
174
+
168
175
  // Group similar event types
169
176
  switch (event.type) {
170
177
  case 'session':
@@ -197,7 +204,7 @@ class ModuleViewer {
197
204
  const timestamp = this.formatTimestamp(event.timestamp);
198
205
  const data = event.data || {};
199
206
  let headerText = '';
200
-
207
+
201
208
  // Determine header text based on event type
202
209
  switch (event.type) {
203
210
  case 'hook':
@@ -211,25 +218,25 @@ class ModuleViewer {
211
218
  headerText = `${hookName}: ${agent} ${timestamp}`;
212
219
  }
213
220
  break;
214
-
221
+
215
222
  case 'agent':
216
223
  // For Agents: "Agent: [AgentType] [time]"
217
224
  const agentType = data.agent_type || data.name || 'Unknown';
218
225
  headerText = `Agent: ${agentType} ${timestamp}`;
219
226
  break;
220
-
227
+
221
228
  case 'todo':
222
229
  // For TodoWrite: "TodoWrite: [Agent] [time]"
223
230
  const todoAgent = this.extractAgent(event) || 'PM';
224
231
  headerText = `TodoWrite: ${todoAgent} ${timestamp}`;
225
232
  break;
226
-
233
+
227
234
  case 'memory':
228
235
  // For Memory: "Memory: [Operation] [time]"
229
236
  const operation = data.operation || 'Unknown';
230
237
  headerText = `Memory: ${operation} ${timestamp}`;
231
238
  break;
232
-
239
+
233
240
  case 'session':
234
241
  case 'claude':
235
242
  case 'log':
@@ -239,7 +246,7 @@ class ModuleViewer {
239
246
  const subtype = event.subtype || 'default';
240
247
  headerText = `Event: ${eventType}.${subtype} ${timestamp}`;
241
248
  break;
242
-
249
+
243
250
  default:
244
251
  // For Files and other events: "File: [filename] [time]" or generic
245
252
  const fileName = this.extractFileName(data);
@@ -252,7 +259,7 @@ class ModuleViewer {
252
259
  }
253
260
  break;
254
261
  }
255
-
262
+
256
263
  return `
257
264
  <div class="contextual-header">
258
265
  <h3 class="contextual-header-text">${headerText}</h3>
@@ -316,7 +323,7 @@ class ModuleViewer {
316
323
  createEventDetailCard(eventType, event, count) {
317
324
  const timestamp = new Date(event.timestamp).toLocaleString();
318
325
  const eventIcon = this.getEventIcon(eventType);
319
-
326
+
320
327
  return `
321
328
  <div class="event-detail-card">
322
329
  <div class="event-detail-header">
@@ -329,7 +336,7 @@ class ModuleViewer {
329
336
  ${this.createProperty('Event ID', event.id || 'N/A')}
330
337
  ${this.createProperty('Type', `${eventType}.${event.subtype || 'default'}`)}
331
338
  ${this.createProperty('Class Events', count)}
332
- ${event.data && event.data.session_id ?
339
+ ${event.data && event.data.session_id ?
333
340
  this.createProperty('Session', event.data.session_id) : ''}
334
341
  </div>
335
342
  </div>
@@ -341,7 +348,7 @@ class ModuleViewer {
341
348
  */
342
349
  createAgentStructuredView(event) {
343
350
  const data = event.data || {};
344
-
351
+
345
352
  // Handle Task delegation events (which appear as hook events but contain agent info)
346
353
  if (event.type === 'hook' && data.tool_name === 'Task' && data.tool_parameters?.subagent_type) {
347
354
  const taskData = data.tool_parameters;
@@ -371,7 +378,7 @@ class ModuleViewer {
371
378
  </div>
372
379
  `;
373
380
  }
374
-
381
+
375
382
  // Handle regular agent events
376
383
  return `
377
384
  <div class="structured-view-section">
@@ -392,17 +399,17 @@ class ModuleViewer {
392
399
  */
393
400
  createHookStructuredView(event) {
394
401
  const data = event.data || {};
395
-
402
+
396
403
  // Extract file path information from tool parameters
397
404
  const filePath = this.extractFilePathFromHook(data);
398
405
  const toolInfo = this.extractToolInfoFromHook(data);
399
-
406
+
400
407
  // Note: Git diff functionality moved to Files tab only
401
408
  // Events tab no longer shows git diff buttons
402
409
 
403
410
  // Create inline tool result content if available (without separate section header)
404
411
  const toolResultContent = this.createInlineToolResultContent(data, event);
405
-
412
+
406
413
  return `
407
414
  <div class="structured-view-section">
408
415
  <div class="structured-data">
@@ -428,12 +435,12 @@ class ModuleViewer {
428
435
  */
429
436
  createInlineToolResultContent(data, event = null) {
430
437
  const resultSummary = data.result_summary;
431
-
438
+
432
439
  // Determine if this is a post-tool event
433
440
  // Check multiple possible locations for the event phase
434
441
  const eventPhase = event?.subtype || data.event_type || data.phase;
435
442
  const isPostTool = eventPhase === 'post_tool' || eventPhase?.includes('post');
436
-
443
+
437
444
  // Debug logging to help troubleshoot tool result display issues
438
445
  if (window.DEBUG_TOOL_RESULTS) {
439
446
  console.log('🔧 createInlineToolResultContent debug:', {
@@ -447,20 +454,20 @@ class ModuleViewer {
447
454
  resultSummaryKeys: resultSummary ? Object.keys(resultSummary) : []
448
455
  });
449
456
  }
450
-
457
+
451
458
  // Only show results if we have result data and this is a post-tool event
452
459
  // OR if we have result_summary regardless of phase (some events may not have proper phase info)
453
460
  if (!resultSummary) {
454
461
  return '';
455
462
  }
456
-
463
+
457
464
  // If we know this is a pre-tool event, don't show results
458
465
  if (eventPhase === 'pre_tool' || (eventPhase?.includes('pre') && !eventPhase?.includes('post'))) {
459
466
  return '';
460
467
  }
461
-
468
+
462
469
  let resultContent = '';
463
-
470
+
464
471
  // Add output preview if available
465
472
  if (resultSummary.has_output && resultSummary.output_preview) {
466
473
  resultContent += `
@@ -468,14 +475,14 @@ class ModuleViewer {
468
475
  ${resultSummary.output_lines ? this.createProperty('Output Lines', resultSummary.output_lines) : ''}
469
476
  `;
470
477
  }
471
-
478
+
472
479
  // Add error preview if available
473
480
  if (resultSummary.has_error && resultSummary.error_preview) {
474
481
  resultContent += `
475
482
  ${this.createProperty('Error', this.truncateText(resultSummary.error_preview, 200))}
476
483
  `;
477
484
  }
478
-
485
+
479
486
  // If no specific output or error, but we have other result info
480
487
  if (!resultSummary.has_output && !resultSummary.has_error && Object.keys(resultSummary).length > 3) {
481
488
  // Show other result fields
@@ -483,10 +490,10 @@ class ModuleViewer {
483
490
  .filter(([key, value]) => !['has_output', 'has_error', 'exit_code'].includes(key) && value !== undefined)
484
491
  .map(([key, value]) => this.createProperty(this.formatFieldName(key), String(value)))
485
492
  .join('');
486
-
493
+
487
494
  resultContent += otherFields;
488
495
  }
489
-
496
+
490
497
  return resultContent;
491
498
  }
492
499
 
@@ -498,12 +505,12 @@ class ModuleViewer {
498
505
  */
499
506
  createToolResultSection(data, event = null) {
500
507
  const resultSummary = data.result_summary;
501
-
508
+
502
509
  // Determine if this is a post-tool event
503
510
  // Check multiple possible locations for the event phase
504
511
  const eventPhase = event?.subtype || data.event_type || data.phase;
505
512
  const isPostTool = eventPhase === 'post_tool' || eventPhase?.includes('post');
506
-
513
+
507
514
  // Debug logging to help troubleshoot tool result display issues
508
515
  if (window.DEBUG_TOOL_RESULTS) {
509
516
  console.log('🔧 createToolResultSection debug:', {
@@ -517,23 +524,23 @@ class ModuleViewer {
517
524
  resultSummaryKeys: resultSummary ? Object.keys(resultSummary) : []
518
525
  });
519
526
  }
520
-
527
+
521
528
  // Only show results if we have result data and this is a post-tool event
522
529
  // OR if we have result_summary regardless of phase (some events may not have proper phase info)
523
530
  if (!resultSummary) {
524
531
  return '';
525
532
  }
526
-
533
+
527
534
  // If we know this is a pre-tool event, don't show results
528
535
  if (eventPhase === 'pre_tool' || (eventPhase?.includes('pre') && !eventPhase?.includes('post'))) {
529
536
  return '';
530
537
  }
531
-
538
+
532
539
  // Determine result status and icon
533
540
  let statusIcon = 'âŗ';
534
541
  let statusClass = 'tool-running';
535
542
  let statusText = 'Unknown';
536
-
543
+
537
544
  if (data.success === true) {
538
545
  statusIcon = '✅';
539
546
  statusClass = 'tool-success';
@@ -555,9 +562,9 @@ class ModuleViewer {
555
562
  statusClass = 'tool-failure';
556
563
  statusText = 'Error';
557
564
  }
558
-
565
+
559
566
  let resultContent = '';
560
-
567
+
561
568
  // Add basic result info
562
569
  resultContent += `
563
570
  <div class="tool-result-status ${statusClass}">
@@ -566,7 +573,7 @@ class ModuleViewer {
566
573
  ${data.exit_code !== undefined ? `<span class="tool-exit-code">Exit Code: ${data.exit_code}</span>` : ''}
567
574
  </div>
568
575
  `;
569
-
576
+
570
577
  // Add output preview if available
571
578
  if (resultSummary.has_output && resultSummary.output_preview) {
572
579
  resultContent += `
@@ -579,7 +586,7 @@ class ModuleViewer {
579
586
  </div>
580
587
  `;
581
588
  }
582
-
589
+
583
590
  // Add error preview if available
584
591
  if (resultSummary.has_error && resultSummary.error_preview) {
585
592
  resultContent += `
@@ -591,7 +598,7 @@ class ModuleViewer {
591
598
  </div>
592
599
  `;
593
600
  }
594
-
601
+
595
602
  // If no specific output or error, but we have other result info
596
603
  if (!resultSummary.has_output && !resultSummary.has_error && Object.keys(resultSummary).length > 3) {
597
604
  // Show other result fields
@@ -599,7 +606,7 @@ class ModuleViewer {
599
606
  .filter(([key, value]) => !['has_output', 'has_error', 'exit_code'].includes(key) && value !== undefined)
600
607
  .map(([key, value]) => this.createProperty(this.formatFieldName(key), String(value)))
601
608
  .join('');
602
-
609
+
603
610
  if (otherFields) {
604
611
  resultContent += `
605
612
  <div class="tool-result-other">
@@ -611,12 +618,12 @@ class ModuleViewer {
611
618
  `;
612
619
  }
613
620
  }
614
-
621
+
615
622
  // Only return content if we have something to show
616
623
  if (!resultContent.trim()) {
617
624
  return '';
618
625
  }
619
-
626
+
620
627
  return `
621
628
  <div class="tool-result-section">
622
629
  <div class="contextual-header">
@@ -639,30 +646,30 @@ class ModuleViewer {
639
646
  // Common write operation tool names
640
647
  const writeTools = [
641
648
  'Write',
642
- 'Edit',
649
+ 'Edit',
643
650
  'MultiEdit',
644
651
  'NotebookEdit'
645
652
  ];
646
-
653
+
647
654
  if (writeTools.includes(toolName)) {
648
655
  return true;
649
656
  }
650
-
657
+
651
658
  // Check for write-related parameters in the data
652
659
  if (data.tool_parameters) {
653
660
  const params = data.tool_parameters;
654
-
661
+
655
662
  // Check for content or editing parameters
656
663
  if (params.content || params.new_string || params.edits) {
657
664
  return true;
658
665
  }
659
-
666
+
660
667
  // Check for file modification indicators
661
668
  if (params.edit_mode && params.edit_mode !== 'read') {
662
669
  return true;
663
670
  }
664
671
  }
665
-
672
+
666
673
  // Check event subtype for write operations
667
674
  if (data.event_type === 'post_tool' || data.event_type === 'pre_tool') {
668
675
  // Additional heuristics based on tool usage patterns
@@ -674,7 +681,7 @@ class ModuleViewer {
674
681
  return true;
675
682
  }
676
683
  }
677
-
684
+
678
685
  return false;
679
686
  }
680
687
 
@@ -685,22 +692,22 @@ class ModuleViewer {
685
692
  */
686
693
  isReadOnlyOperation(operation) {
687
694
  if (!operation) return true; // Default to read-only for safety
688
-
695
+
689
696
  const readOnlyOperations = ['read'];
690
697
  const editOperations = ['write', 'edit', 'multiedit', 'create', 'delete', 'move', 'copy'];
691
-
698
+
692
699
  const opLower = operation.toLowerCase();
693
-
700
+
694
701
  // Explicitly read-only operations
695
702
  if (readOnlyOperations.includes(opLower)) {
696
703
  return true;
697
704
  }
698
-
705
+
699
706
  // Explicitly edit operations
700
707
  if (editOperations.includes(opLower)) {
701
708
  return false;
702
709
  }
703
-
710
+
704
711
  // Default to read-only for unknown operations
705
712
  return true;
706
713
  }
@@ -710,7 +717,7 @@ class ModuleViewer {
710
717
  */
711
718
  createTodoStructuredView(event) {
712
719
  const data = event.data || {};
713
-
720
+
714
721
  let content = '';
715
722
 
716
723
  // Add todo checklist if available - start directly with checklist
@@ -736,7 +743,7 @@ class ModuleViewer {
736
743
  */
737
744
  createMemoryStructuredView(event) {
738
745
  const data = event.data || {};
739
-
746
+
740
747
  return `
741
748
  <div class="structured-view-section">
742
749
  <div class="structured-data">
@@ -755,7 +762,7 @@ class ModuleViewer {
755
762
  */
756
763
  createClaudeStructuredView(event) {
757
764
  const data = event.data || {};
758
-
765
+
759
766
  return `
760
767
  <div class="structured-view-section">
761
768
  <div class="structured-data">
@@ -776,7 +783,7 @@ class ModuleViewer {
776
783
  */
777
784
  createSessionStructuredView(event) {
778
785
  const data = event.data || {};
779
-
786
+
780
787
  return `
781
788
  <div class="structured-view-section">
782
789
  <div class="structured-data">
@@ -796,16 +803,16 @@ class ModuleViewer {
796
803
  createGenericStructuredView(event) {
797
804
  const data = event.data || {};
798
805
  const keys = Object.keys(data);
799
-
806
+
800
807
  if (keys.length === 0) {
801
808
  return '';
802
809
  }
803
-
810
+
804
811
  return `
805
812
  <div class="structured-view-section">
806
813
  <div class="structured-data">
807
- ${keys.map(key =>
808
- this.createProperty(key, typeof data[key] === 'object' ?
814
+ ${keys.map(key =>
815
+ this.createProperty(key, typeof data[key] === 'object' ?
809
816
  '[Object]' : String(data[key]))
810
817
  ).join('')}
811
818
  </div>
@@ -815,24 +822,34 @@ class ModuleViewer {
815
822
 
816
823
  /**
817
824
  * Create collapsible JSON section that appears below main content
825
+ * WHY: Uses global state to maintain consistent JSON visibility across all events
826
+ * DESIGN DECISION: Sticky toggle improves debugging workflow by maintaining JSON
827
+ * visibility preference as user navigates through different events
818
828
  * @param {Object} event - The event to render
819
829
  * @returns {string} HTML content
820
830
  */
821
831
  createCollapsibleJsonSection(event) {
822
832
  const uniqueId = 'json-section-' + Math.random().toString(36).substr(2, 9);
823
833
  const jsonString = this.formatJSON(event);
834
+
835
+ // Use global state to determine initial visibility
836
+ const isExpanded = this.globalJsonExpanded;
837
+ const display = isExpanded ? 'block' : 'none';
838
+ const arrow = isExpanded ? '▲' : 'â–ŧ';
839
+ const ariaExpanded = isExpanded ? 'true' : 'false';
840
+
824
841
  return `
825
842
  <div class="collapsible-json-section" id="${uniqueId}">
826
- <div class="json-toggle-header"
827
- onclick="window.moduleViewer.toggleJsonSection()"
828
- role="button"
829
- tabindex="0"
830
- aria-expanded="false"
843
+ <div class="json-toggle-header"
844
+ onclick="window.moduleViewer.toggleJsonSection()"
845
+ role="button"
846
+ tabindex="0"
847
+ aria-expanded="${ariaExpanded}"
831
848
  onkeydown="if(event.key==='Enter'||event.key===' '){window.moduleViewer.toggleJsonSection();event.preventDefault();}">
832
849
  <span class="json-toggle-text">Raw JSON</span>
833
- <span class="json-toggle-arrow">â–ŧ</span>
850
+ <span class="json-toggle-arrow">${arrow}</span>
834
851
  </div>
835
- <div class="json-content-collapsible" style="display: none;" aria-hidden="true">
852
+ <div class="json-content-collapsible" style="display: ${display};" aria-hidden="${!isExpanded}">
836
853
  <div class="json-display" onclick="window.moduleViewer.copyJsonToClipboard(event)">
837
854
  <pre>${jsonString}</pre>
838
855
  </div>
@@ -850,7 +867,7 @@ class ModuleViewer {
850
867
  const rect = event.currentTarget.getBoundingClientRect();
851
868
  const clickX = event.clientX - rect.left;
852
869
  const clickY = event.clientY - rect.top;
853
-
870
+
854
871
  // Check if click is in the top-right corner (copy icon area)
855
872
  if (clickX > rect.width - 50 && clickY < 30) {
856
873
  const preElement = event.currentTarget.querySelector('pre');
@@ -869,49 +886,99 @@ class ModuleViewer {
869
886
 
870
887
  /**
871
888
  * Initialize JSON toggle functionality
889
+ * WHY: Ensures newly rendered events respect the current global JSON visibility state
872
890
  */
873
891
  initializeJsonToggle() {
874
892
  // Make sure the moduleViewer is available globally for onclick handlers
875
893
  window.moduleViewer = this;
876
-
877
- // Add keyboard navigation support
878
- document.addEventListener('keydown', (e) => {
879
- if (e.target.classList.contains('json-toggle-header')) {
880
- if (e.key === 'Enter' || e.key === ' ') {
881
- this.toggleJsonSection();
882
- e.preventDefault();
894
+
895
+ // Apply global state to newly rendered JSON sections
896
+ // This ensures new events respect the current global state
897
+ if (this.globalJsonExpanded) {
898
+ // Small delay to ensure DOM is ready
899
+ setTimeout(() => {
900
+ this.updateAllJsonSections();
901
+ }, 0);
902
+ }
903
+
904
+ // Add keyboard navigation support (only add once to avoid duplicates)
905
+ if (!this.keyboardListenerAdded) {
906
+ this.keyboardListenerAdded = true;
907
+ document.addEventListener('keydown', (e) => {
908
+ if (e.target.classList.contains('json-toggle-header')) {
909
+ if (e.key === 'Enter' || e.key === ' ') {
910
+ this.toggleJsonSection();
911
+ e.preventDefault();
912
+ }
883
913
  }
884
- }
885
- });
914
+ });
915
+ }
886
916
  }
887
917
 
888
918
  /**
889
- * Toggle JSON section visibility with smooth animation
919
+ * Toggle JSON section visibility globally - affects ALL events
920
+ * WHY: Sticky toggle maintains user preference across all events for better debugging
921
+ * DESIGN DECISION: Uses localStorage to persist preference across page refreshes
890
922
  */
891
923
  toggleJsonSection() {
892
- const jsonContent = document.querySelector('.json-content-collapsible');
893
- const arrow = document.querySelector('.json-toggle-arrow');
894
- const toggleHeader = document.querySelector('.json-toggle-header');
924
+ // Toggle the global state
925
+ this.globalJsonExpanded = !this.globalJsonExpanded;
926
+
927
+ // Persist the preference to localStorage
928
+ localStorage.setItem('dashboard-json-expanded', this.globalJsonExpanded.toString());
895
929
 
896
- if (!jsonContent || !arrow) return;
930
+ // Update ALL JSON sections on the page
931
+ this.updateAllJsonSections();
932
+
933
+ // Dispatch event to notify other components of the change
934
+ document.dispatchEvent(new CustomEvent('jsonToggleChanged', {
935
+ detail: { expanded: this.globalJsonExpanded }
936
+ }));
937
+ }
938
+
939
+ /**
940
+ * Update all JSON sections on the page to match global state
941
+ * WHY: Ensures consistent JSON visibility across all displayed events
942
+ */
943
+ updateAllJsonSections() {
944
+ // Find all JSON content sections and toggle headers
945
+ const allJsonContents = document.querySelectorAll('.json-content-collapsible');
946
+ const allArrows = document.querySelectorAll('.json-toggle-arrow');
947
+ const allHeaders = document.querySelectorAll('.json-toggle-header');
897
948
 
898
- const isHidden = jsonContent.style.display === 'none' || !jsonContent.style.display;
949
+ // Update each JSON section
950
+ allJsonContents.forEach((jsonContent, index) => {
951
+ if (this.globalJsonExpanded) {
952
+ // Show JSON content
953
+ jsonContent.style.display = 'block';
954
+ jsonContent.setAttribute('aria-hidden', 'false');
955
+ if (allArrows[index]) {
956
+ allArrows[index].textContent = '▲';
957
+ }
958
+ if (allHeaders[index]) {
959
+ allHeaders[index].setAttribute('aria-expanded', 'true');
960
+ }
961
+ } else {
962
+ // Hide JSON content
963
+ jsonContent.style.display = 'none';
964
+ jsonContent.setAttribute('aria-hidden', 'true');
965
+ if (allArrows[index]) {
966
+ allArrows[index].textContent = 'â–ŧ';
967
+ }
968
+ if (allHeaders[index]) {
969
+ allHeaders[index].setAttribute('aria-expanded', 'false');
970
+ }
971
+ }
972
+ });
899
973
 
900
- if (isHidden) {
901
- // Show JSON content
902
- jsonContent.style.display = 'block';
903
- arrow.textContent = '▲';
904
- toggleHeader.setAttribute('aria-expanded', 'true');
905
-
906
- // Scroll the new content into view if needed
974
+ // If expanded and there's content, scroll the first visible one into view
975
+ if (this.globalJsonExpanded && allJsonContents.length > 0) {
907
976
  setTimeout(() => {
908
- jsonContent.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
977
+ const firstVisible = allJsonContents[0];
978
+ if (firstVisible) {
979
+ firstVisible.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
980
+ }
909
981
  }, 100);
910
- } else {
911
- // Hide JSON content
912
- jsonContent.style.display = 'none';
913
- arrow.textContent = 'â–ŧ';
914
- toggleHeader.setAttribute('aria-expanded', 'false');
915
982
  }
916
983
  }
917
984
 
@@ -920,7 +987,7 @@ class ModuleViewer {
920
987
  */
921
988
  createProperty(key, value) {
922
989
  const displayValue = this.truncateText(String(value), 300);
923
-
990
+
924
991
  // Check if this is a file path property that should be clickable
925
992
  if (this.isFilePathProperty(key, value)) {
926
993
  return `
@@ -932,7 +999,7 @@ class ModuleViewer {
932
999
  </div>
933
1000
  `;
934
1001
  }
935
-
1002
+
936
1003
  return `
937
1004
  <div class="event-property">
938
1005
  <span class="event-property-key">${key}:</span>
@@ -956,16 +1023,16 @@ class ModuleViewer {
956
1023
  'Working Directory',
957
1024
  'working_directory'
958
1025
  ];
959
-
1026
+
960
1027
  // Check if key indicates a file path
961
1028
  if (filePathKeys.some(pathKey => key.toLowerCase().includes(pathKey.toLowerCase()))) {
962
1029
  // Ensure value looks like a file path (contains / or \\ and has reasonable length)
963
1030
  const strValue = String(value);
964
- return strValue.length > 0 &&
1031
+ return strValue.length > 0 &&
965
1032
  (strValue.includes('/') || strValue.includes('\\')) &&
966
1033
  strValue.length < 500; // Reasonable path length limit
967
1034
  }
968
-
1035
+
969
1036
  return false;
970
1037
  }
971
1038
 
@@ -977,9 +1044,9 @@ class ModuleViewer {
977
1044
  createClickableFilePath(filePath) {
978
1045
  const displayPath = this.truncateText(String(filePath), 300);
979
1046
  const escapedPath = filePath.replace(/'/g, "\\'");
980
-
1047
+
981
1048
  return `
982
- <span class="clickable-file-path"
1049
+ <span class="clickable-file-path"
983
1050
  onclick="showFileViewerModal('${escapedPath}')"
984
1051
  title="Click to view file contents with syntax highlighting&#10;Path: ${filePath}">
985
1052
  ${displayPath}
@@ -1037,24 +1104,24 @@ class ModuleViewer {
1037
1104
  // First check if there's a specific hook name in the data
1038
1105
  if (data.hook_name) return data.hook_name;
1039
1106
  if (data.name) return data.name;
1040
-
1107
+
1041
1108
  // Use event.subtype or data.event_type to determine hook name
1042
1109
  const eventType = event.subtype || data.event_type;
1043
-
1110
+
1044
1111
  // Map hook event types to meaningful display names
1045
1112
  const hookNames = {
1046
1113
  'user_prompt': 'User Prompt',
1047
1114
  'pre_tool': 'Tool Execution (Pre)',
1048
- 'post_tool': 'Tool Execution (Post)',
1115
+ 'post_tool': 'Tool Execution (Post)',
1049
1116
  'notification': 'Notification',
1050
1117
  'stop': 'Session Stop',
1051
1118
  'subagent_stop': 'Subagent Stop'
1052
1119
  };
1053
-
1120
+
1054
1121
  if (hookNames[eventType]) {
1055
1122
  return hookNames[eventType];
1056
1123
  }
1057
-
1124
+
1058
1125
  // If it's a compound event type like "hook.user_prompt", extract the part after "hook."
1059
1126
  if (typeof event.type === 'string' && event.type.startsWith('hook.')) {
1060
1127
  const hookType = event.type.replace('hook.', '');
@@ -1062,14 +1129,14 @@ class ModuleViewer {
1062
1129
  return hookNames[hookType];
1063
1130
  }
1064
1131
  }
1065
-
1132
+
1066
1133
  // Fallback to formatting the event type nicely
1067
1134
  if (eventType) {
1068
1135
  return eventType.split('_')
1069
1136
  .map(word => word.charAt(0).toUpperCase() + word.slice(1))
1070
1137
  .join(' ');
1071
1138
  }
1072
-
1139
+
1073
1140
  return 'Unknown Hook';
1074
1141
  }
1075
1142
 
@@ -1081,22 +1148,22 @@ class ModuleViewer {
1081
1148
  if (data.tool_parameters && data.tool_parameters.file_path) {
1082
1149
  return data.tool_parameters.file_path;
1083
1150
  }
1084
-
1151
+
1085
1152
  // Check direct file_path field
1086
1153
  if (data.file_path) {
1087
1154
  return data.file_path;
1088
1155
  }
1089
-
1156
+
1090
1157
  // Check nested in other common locations
1091
1158
  if (data.tool_input && data.tool_input.file_path) {
1092
1159
  return data.tool_input.file_path;
1093
1160
  }
1094
-
1161
+
1095
1162
  // Check for notebook path (alternative field name)
1096
1163
  if (data.tool_parameters && data.tool_parameters.notebook_path) {
1097
1164
  return data.tool_parameters.notebook_path;
1098
1165
  }
1099
-
1166
+
1100
1167
  return null;
1101
1168
  }
1102
1169
 
@@ -1136,7 +1203,7 @@ class ModuleViewer {
1136
1203
  */
1137
1204
  formatTimestamp(timestamp) {
1138
1205
  if (!timestamp) return 'Unknown time';
1139
-
1206
+
1140
1207
  try {
1141
1208
  const date = new Date(timestamp);
1142
1209
  return date.toLocaleTimeString('en-US', {
@@ -1184,7 +1251,7 @@ class ModuleViewer {
1184
1251
  if (data.tool_name) return data.tool_name;
1185
1252
  if (data.tool_parameters && data.tool_parameters.tool_name) return data.tool_parameters.tool_name;
1186
1253
  if (data.tool_input && data.tool_input.tool_name) return data.tool_input.tool_name;
1187
-
1254
+
1188
1255
  // Try to infer from other fields
1189
1256
  if (data.tool_parameters) {
1190
1257
  // Common tool patterns
@@ -1201,7 +1268,7 @@ class ModuleViewer {
1201
1268
  return 'TodoWrite';
1202
1269
  }
1203
1270
  }
1204
-
1271
+
1205
1272
  return null;
1206
1273
  }
1207
1274
 
@@ -1215,17 +1282,17 @@ class ModuleViewer {
1215
1282
  if (data._agentName && data._agentName !== 'Unknown Agent') {
1216
1283
  return data._agentName;
1217
1284
  }
1218
-
1285
+
1219
1286
  // Check inference data if available
1220
1287
  if (data._inference && data._inference.agentName && data._inference.agentName !== 'Unknown') {
1221
1288
  return data._inference.agentName;
1222
1289
  }
1223
-
1290
+
1224
1291
  // Check various locations where agent info might be stored
1225
1292
  if (data.agent) return data.agent;
1226
1293
  if (data.agent_type) return data.agent_type;
1227
1294
  if (data.agent_name) return data.agent_name;
1228
-
1295
+
1229
1296
  // Check session data
1230
1297
  if (data.session_id && typeof data.session_id === 'string') {
1231
1298
  // Extract agent from session ID if it contains agent info
@@ -1234,11 +1301,11 @@ class ModuleViewer {
1234
1301
  return sessionParts[0].toUpperCase();
1235
1302
  }
1236
1303
  }
1237
-
1304
+
1238
1305
  // Infer from context
1239
1306
  if (data.todos) return 'PM'; // TodoWrite typically from PM agent
1240
1307
  if (data.tool_name === 'TodoWrite') return 'PM';
1241
-
1308
+
1242
1309
  return null;
1243
1310
  }
1244
1311
 
@@ -1254,11 +1321,11 @@ class ModuleViewer {
1254
1321
  const pathParts = filePath.split('/');
1255
1322
  return pathParts[pathParts.length - 1];
1256
1323
  }
1257
-
1324
+
1258
1325
  // Check other common file fields
1259
1326
  if (data.filename) return data.filename;
1260
1327
  if (data.file) return data.file;
1261
-
1328
+
1262
1329
  return null;
1263
1330
  }
1264
1331
 
@@ -1287,20 +1354,20 @@ class ModuleViewer {
1287
1354
  // Extract information from pre and post events
1288
1355
  const preEvent = toolCall.pre_event;
1289
1356
  const postEvent = toolCall.post_event;
1290
-
1357
+
1291
1358
  // Get parameters from pre-event
1292
1359
  const parameters = preEvent?.tool_parameters || {};
1293
1360
  const target = preEvent ? this.extractToolTarget(toolName, parameters) : 'Unknown target';
1294
-
1361
+
1295
1362
  // Get execution results from post-event
1296
1363
  const duration = toolCall.duration_ms ? `${toolCall.duration_ms}ms` : '-';
1297
1364
  const success = toolCall.success !== undefined ? toolCall.success : null;
1298
1365
  const exitCode = toolCall.exit_code !== undefined ? toolCall.exit_code : null;
1299
-
1366
+
1300
1367
  // Format result summary
1301
1368
  let resultSummary = toolCall.result_summary || 'No summary available';
1302
1369
  let formattedResultSummary = '';
1303
-
1370
+
1304
1371
  if (typeof resultSummary === 'object' && resultSummary !== null) {
1305
1372
  const parts = [];
1306
1373
  if (resultSummary.exit_code !== undefined) {
@@ -1330,7 +1397,7 @@ class ModuleViewer {
1330
1397
  let statusIcon = 'âŗ';
1331
1398
  let statusText = 'Running...';
1332
1399
  let statusClass = 'tool-running';
1333
-
1400
+
1334
1401
  if (postEvent) {
1335
1402
  if (success === true) {
1336
1403
  statusIcon = '✅';
@@ -1361,7 +1428,7 @@ class ModuleViewer {
1361
1428
  ${parameters.todos.map(todo => {
1362
1429
  const statusIcon = this.getTodoStatusIcon(todo.status);
1363
1430
  const priorityIcon = this.getTodoPriorityIcon(todo.priority);
1364
-
1431
+
1365
1432
  return `
1366
1433
  <div class="todo-item todo-${todo.status || 'pending'}">
1367
1434
  <span class="todo-status">${statusIcon}</span>
@@ -1372,7 +1439,7 @@ class ModuleViewer {
1372
1439
  }).join('')}
1373
1440
  </div>
1374
1441
  `;
1375
-
1442
+
1376
1443
  // Create collapsible JSON section
1377
1444
  const toolCallData = {
1378
1445
  toolCall: toolCall,
@@ -1380,11 +1447,11 @@ class ModuleViewer {
1380
1447
  postEvent: postEvent
1381
1448
  };
1382
1449
  const collapsibleJsonSection = this.createCollapsibleJsonSection(toolCallData);
1383
-
1450
+
1384
1451
  if (this.dataContainer) {
1385
1452
  this.dataContainer.innerHTML = contextualHeader + todoContent + collapsibleJsonSection;
1386
1453
  }
1387
-
1454
+
1388
1455
  // Initialize JSON toggle functionality
1389
1456
  this.initializeJsonToggle();
1390
1457
  } else {
@@ -1419,12 +1486,12 @@ class ModuleViewer {
1419
1486
  </div>
1420
1487
  ` : ''}
1421
1488
  </div>
1422
-
1489
+
1423
1490
  ${this.createToolResultFromToolCall(toolCall)}
1424
1491
  </div>
1425
1492
  </div>
1426
1493
  `;
1427
-
1494
+
1428
1495
  // Create collapsible JSON section
1429
1496
  const toolCallData = {
1430
1497
  toolCall: toolCall,
@@ -1432,11 +1499,11 @@ class ModuleViewer {
1432
1499
  postEvent: postEvent
1433
1500
  };
1434
1501
  const collapsibleJsonSection = this.createCollapsibleJsonSection(toolCallData);
1435
-
1502
+
1436
1503
  if (this.dataContainer) {
1437
1504
  this.dataContainer.innerHTML = contextualHeader + content + collapsibleJsonSection;
1438
1505
  }
1439
-
1506
+
1440
1507
  // Initialize JSON toggle functionality
1441
1508
  this.initializeJsonToggle();
1442
1509
  }
@@ -1461,7 +1528,7 @@ class ModuleViewer {
1461
1528
  const operations = fileData.operations || [];
1462
1529
  const lastOp = operations[operations.length - 1];
1463
1530
  const headerTimestamp = lastOp ? this.formatTimestamp(lastOp.timestamp) : '';
1464
-
1531
+
1465
1532
  // Create contextual header
1466
1533
  const contextualHeader = `
1467
1534
  <div class="contextual-header">
@@ -1487,7 +1554,7 @@ class ModuleViewer {
1487
1554
  <span class="operation-timestamp">${new Date(op.timestamp).toLocaleString()}</span>
1488
1555
  ${this.isReadOnlyOperation(op.operation) ? `
1489
1556
  <!-- Read-only operation: show only file viewer -->
1490
- <span class="file-viewer-icon"
1557
+ <span class="file-viewer-icon"
1491
1558
  onclick="showFileViewerModal('${filePath}')"
1492
1559
  title="View file contents with syntax highlighting"
1493
1560
  style="margin-left: 8px; cursor: pointer; font-size: 16px;">
@@ -1495,13 +1562,13 @@ class ModuleViewer {
1495
1562
  </span>
1496
1563
  ` : `
1497
1564
  <!-- Edit operation: show both file viewer and git diff -->
1498
- <span class="file-viewer-icon"
1565
+ <span class="file-viewer-icon"
1499
1566
  onclick="showFileViewerModal('${filePath}')"
1500
1567
  title="View file contents with syntax highlighting"
1501
1568
  style="margin-left: 8px; cursor: pointer; font-size: 16px;">
1502
1569
  đŸ‘ī¸
1503
1570
  </span>
1504
- <span class="git-diff-icon"
1571
+ <span class="git-diff-icon"
1505
1572
  onclick="showGitDiffModal('${filePath}', '${op.timestamp}')"
1506
1573
  title="View git diff for this file operation"
1507
1574
  style="margin-left: 8px; cursor: pointer; font-size: 16px; display: none;"
@@ -1525,21 +1592,21 @@ class ModuleViewer {
1525
1592
 
1526
1593
  // Check git tracking status and show track control if needed
1527
1594
  this.checkAndShowTrackControl(filePath);
1528
-
1595
+
1529
1596
  // Check git status and conditionally show git diff icons
1530
1597
  this.checkAndShowGitDiffIcons(filePath);
1531
1598
 
1532
1599
  // Create collapsible JSON section for file data
1533
1600
  const collapsibleJsonSection = this.createCollapsibleJsonSection(fileData);
1534
-
1601
+
1535
1602
  // Show structured data with JSON section in data pane
1536
1603
  if (this.dataContainer) {
1537
1604
  this.dataContainer.innerHTML = contextualHeader + content + collapsibleJsonSection;
1538
1605
  }
1539
-
1606
+
1540
1607
  // Initialize JSON toggle functionality
1541
1608
  this.initializeJsonToggle();
1542
-
1609
+
1543
1610
  // Hide JSON pane since data is integrated above
1544
1611
  // JSON container no longer exists - handled via collapsible sections
1545
1612
  }
@@ -1560,18 +1627,18 @@ class ModuleViewer {
1560
1627
  </div>
1561
1628
  </div>
1562
1629
  `;
1563
-
1630
+
1564
1631
  // Create collapsible JSON section for error data
1565
1632
  const errorData = { title, message };
1566
1633
  const collapsibleJsonSection = this.createCollapsibleJsonSection(errorData);
1567
-
1634
+
1568
1635
  if (this.dataContainer) {
1569
1636
  this.dataContainer.innerHTML = content + collapsibleJsonSection;
1570
1637
  }
1571
-
1638
+
1572
1639
  // Initialize JSON toggle functionality
1573
1640
  this.initializeJsonToggle();
1574
-
1641
+
1575
1642
  // JSON container no longer exists - handled via collapsible sections
1576
1643
  }
1577
1644
 
@@ -1599,7 +1666,7 @@ class ModuleViewer {
1599
1666
  // Get agent inference to determine which agent this is
1600
1667
  const agentInference = window.dashboard?.agentInference;
1601
1668
  const eventViewer = window.dashboard?.eventViewer;
1602
-
1669
+
1603
1670
  if (!agentInference || !eventViewer) {
1604
1671
  console.warn('AgentInference or EventViewer not available, falling back to single event view');
1605
1672
  this.showEventDetails(event);
@@ -1608,16 +1675,16 @@ class ModuleViewer {
1608
1675
 
1609
1676
  const inference = agentInference.getInferredAgentForEvent(event);
1610
1677
  const agentName = inference?.agentName || this.extractAgent(event) || 'Unknown';
1611
-
1678
+
1612
1679
  // Get all events from this agent
1613
1680
  const allEvents = eventViewer.events || [];
1614
1681
  const agentEvents = this.getAgentSpecificEvents(allEvents, agentName, agentInference);
1615
-
1682
+
1616
1683
  console.log(`Showing details for agent: ${agentName}, found ${agentEvents.length} related events`);
1617
-
1684
+
1618
1685
  // Extract agent-specific data
1619
1686
  const agentData = this.extractAgentSpecificData(agentName, agentEvents);
1620
-
1687
+
1621
1688
  // Render agent-specific view
1622
1689
  this.renderAgentSpecificView(agentName, agentData, event);
1623
1690
  }
@@ -1634,7 +1701,7 @@ class ModuleViewer {
1634
1701
  // Use agent inference to determine if this event belongs to the agent
1635
1702
  const inference = agentInference.getInferredAgentForEvent(event);
1636
1703
  const eventAgentName = inference?.agentName || this.extractAgent(event) || 'Unknown';
1637
-
1704
+
1638
1705
  // Match agent names (case insensitive)
1639
1706
  return eventAgentName.toLowerCase() === agentName.toLowerCase();
1640
1707
  });
@@ -1662,7 +1729,7 @@ class ModuleViewer {
1662
1729
  agentEvents.forEach(event => {
1663
1730
  const eventData = event.data || {};
1664
1731
  const timestamp = new Date(event.timestamp);
1665
-
1732
+
1666
1733
  // Track timing
1667
1734
  if (!data.firstSeen || timestamp < data.firstSeen) {
1668
1735
  data.firstSeen = timestamp;
@@ -1670,16 +1737,16 @@ class ModuleViewer {
1670
1737
  if (!data.lastSeen || timestamp > data.lastSeen) {
1671
1738
  data.lastSeen = timestamp;
1672
1739
  }
1673
-
1740
+
1674
1741
  // Track sessions
1675
1742
  if (event.session_id || eventData.session_id) {
1676
1743
  data.sessions.add(event.session_id || eventData.session_id);
1677
1744
  }
1678
-
1745
+
1679
1746
  // Track event types
1680
1747
  const eventType = event.hook_event_name || event.type || 'unknown';
1681
1748
  data.eventTypes.add(eventType);
1682
-
1749
+
1683
1750
  // Extract prompt from Task delegation events
1684
1751
  if (event.type === 'hook' && eventData.tool_name === 'Task' && eventData.tool_parameters) {
1685
1752
  const taskParams = eventData.tool_parameters;
@@ -1694,12 +1761,12 @@ class ModuleViewer {
1694
1761
  data.prompt = taskParams.prompt;
1695
1762
  }
1696
1763
  }
1697
-
1764
+
1698
1765
  // Also check for agent-specific prompts in other event types
1699
1766
  if (eventData.prompt && (eventData.agent_type === agentName || eventData.subagent_type === agentName)) {
1700
1767
  data.prompt = eventData.prompt;
1701
1768
  }
1702
-
1769
+
1703
1770
  // Extract todos from TodoWrite events
1704
1771
  if (event.type === 'todo' || (event.type === 'hook' && eventData.tool_name === 'TodoWrite')) {
1705
1772
  const todos = eventData.todos || eventData.tool_parameters?.todos;
@@ -1717,12 +1784,12 @@ class ModuleViewer {
1717
1784
  });
1718
1785
  }
1719
1786
  }
1720
-
1787
+
1721
1788
  // Extract tool calls - collect pre and post events separately first
1722
1789
  if (event.type === 'hook' && eventData.tool_name) {
1723
1790
  const phase = event.subtype || eventData.event_type;
1724
1791
  const toolCallId = this.generateToolCallId(eventData.tool_name, eventData.tool_parameters, timestamp);
1725
-
1792
+
1726
1793
  if (phase === 'pre_tool') {
1727
1794
  // Store pre-tool event data
1728
1795
  if (!data._preToolEvents) data._preToolEvents = new Map();
@@ -1746,20 +1813,20 @@ class ModuleViewer {
1746
1813
  }
1747
1814
  }
1748
1815
  });
1749
-
1816
+
1750
1817
  // Sort todos by timestamp (most recent first)
1751
1818
  data.todos.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
1752
-
1819
+
1753
1820
  // Consolidate pre and post tool events into single tool calls
1754
1821
  data.toolsCalled = this.consolidateToolCalls(data._preToolEvents, data._postToolEvents);
1755
-
1822
+
1756
1823
  // Clean up temporary data
1757
1824
  delete data._preToolEvents;
1758
1825
  delete data._postToolEvents;
1759
-
1826
+
1760
1827
  // Sort tools by timestamp (most recent first)
1761
1828
  data.toolsCalled.sort((a, b) => b.timestamp - a.timestamp);
1762
-
1829
+
1763
1830
  return data;
1764
1831
  }
1765
1832
 
@@ -1774,7 +1841,7 @@ class ModuleViewer {
1774
1841
  // Create a unique identifier based on tool name, key parameters, and approximate timestamp
1775
1842
  // Use a wider time window to account for timing differences between pre/post events
1776
1843
  const timeWindow = Math.floor(timestamp.getTime() / 5000); // Group by 5-second windows
1777
-
1844
+
1778
1845
  // Include key parameters that uniquely identify a tool call
1779
1846
  let paramKey = '';
1780
1847
  if (parameters) {
@@ -1787,15 +1854,15 @@ class ModuleViewer {
1787
1854
  if (parameters.notebook_path) keyParams.push(parameters.notebook_path);
1788
1855
  if (parameters.url) keyParams.push(parameters.url);
1789
1856
  if (parameters.prompt) keyParams.push(parameters.prompt.substring(0, 30));
1790
-
1857
+
1791
1858
  paramKey = keyParams.join('|');
1792
1859
  }
1793
-
1860
+
1794
1861
  // If no specific parameters, use just tool name and time window
1795
1862
  if (!paramKey) {
1796
1863
  paramKey = 'default';
1797
1864
  }
1798
-
1865
+
1799
1866
  return `${toolName}:${timeWindow}:${paramKey}`;
1800
1867
  }
1801
1868
 
@@ -1808,16 +1875,16 @@ class ModuleViewer {
1808
1875
  consolidateToolCalls(preToolEvents, postToolEvents) {
1809
1876
  const consolidatedCalls = [];
1810
1877
  const processedIds = new Set();
1811
-
1878
+
1812
1879
  if (!preToolEvents) preToolEvents = new Map();
1813
1880
  if (!postToolEvents) postToolEvents = new Map();
1814
-
1881
+
1815
1882
  // Process all pre-tool events first
1816
1883
  for (const [toolCallId, preEvent] of preToolEvents) {
1817
1884
  if (processedIds.has(toolCallId)) continue;
1818
-
1885
+
1819
1886
  const postEvent = postToolEvents.get(toolCallId);
1820
-
1887
+
1821
1888
  // Create consolidated tool call
1822
1889
  const consolidatedCall = {
1823
1890
  toolName: preEvent.toolName,
@@ -1828,7 +1895,7 @@ class ModuleViewer {
1828
1895
  statusIcon: this.getToolCallStatusIcon(preEvent, postEvent),
1829
1896
  phase: postEvent ? 'completed' : 'running'
1830
1897
  };
1831
-
1898
+
1832
1899
  // Add post-event data if available
1833
1900
  if (postEvent) {
1834
1901
  consolidatedCall.success = postEvent.success;
@@ -1837,15 +1904,15 @@ class ModuleViewer {
1837
1904
  consolidatedCall.exitCode = postEvent.exitCode;
1838
1905
  consolidatedCall.completedAt = postEvent.timestamp;
1839
1906
  }
1840
-
1907
+
1841
1908
  consolidatedCalls.push(consolidatedCall);
1842
1909
  processedIds.add(toolCallId);
1843
1910
  }
1844
-
1911
+
1845
1912
  // Process any post-tool events that don't have matching pre-tool events (edge case)
1846
1913
  for (const [toolCallId, postEvent] of postToolEvents) {
1847
1914
  if (processedIds.has(toolCallId)) continue;
1848
-
1915
+
1849
1916
  // This is a post-tool event without a corresponding pre-tool event
1850
1917
  const consolidatedCall = {
1851
1918
  toolName: postEvent.toolName,
@@ -1861,11 +1928,11 @@ class ModuleViewer {
1861
1928
  exitCode: postEvent.exitCode,
1862
1929
  completedAt: postEvent.timestamp
1863
1930
  };
1864
-
1931
+
1865
1932
  consolidatedCalls.push(consolidatedCall);
1866
1933
  processedIds.add(toolCallId);
1867
1934
  }
1868
-
1935
+
1869
1936
  return consolidatedCalls;
1870
1937
  }
1871
1938
 
@@ -1879,7 +1946,7 @@ class ModuleViewer {
1879
1946
  if (!postEvent) {
1880
1947
  return 'Running...';
1881
1948
  }
1882
-
1949
+
1883
1950
  if (postEvent.success === true) {
1884
1951
  return 'Success';
1885
1952
  } else if (postEvent.success === false) {
@@ -1891,7 +1958,7 @@ class ModuleViewer {
1891
1958
  } else if (postEvent.exitCode !== undefined && postEvent.exitCode !== 0) {
1892
1959
  return 'Error';
1893
1960
  }
1894
-
1961
+
1895
1962
  return 'Completed';
1896
1963
  }
1897
1964
 
@@ -1905,7 +1972,7 @@ class ModuleViewer {
1905
1972
  if (!postEvent) {
1906
1973
  return 'âŗ'; // Still running
1907
1974
  }
1908
-
1975
+
1909
1976
  if (postEvent.success === true) {
1910
1977
  return '✅'; // Success
1911
1978
  } else if (postEvent.success === false) {
@@ -1917,7 +1984,7 @@ class ModuleViewer {
1917
1984
  } else if (postEvent.exitCode !== undefined && postEvent.exitCode !== 0) {
1918
1985
  return '❌'; // Error
1919
1986
  }
1920
-
1987
+
1921
1988
  return '✅'; // Default to success for completed calls
1922
1989
  }
1923
1990
 
@@ -1928,12 +1995,12 @@ class ModuleViewer {
1928
1995
  */
1929
1996
  estimateTokenCount(text) {
1930
1997
  if (!text || typeof text !== 'string') return 0;
1931
-
1998
+
1932
1999
  // Simple token estimation: words * 1.3 (accounts for subwords)
1933
2000
  // Alternative: characters / 4 (common rule of thumb)
1934
2001
  const wordCount = text.trim().split(/\s+/).length;
1935
2002
  const charBasedEstimate = Math.ceil(text.length / 4);
1936
-
2003
+
1937
2004
  // Use the higher of the two estimates for safety
1938
2005
  return Math.max(wordCount * 1.3, charBasedEstimate);
1939
2006
  }
@@ -1945,19 +2012,19 @@ class ModuleViewer {
1945
2012
  */
1946
2013
  trimPromptWhitespace(text) {
1947
2014
  if (!text || typeof text !== 'string') return '';
1948
-
2015
+
1949
2016
  // Remove leading/trailing whitespace from the entire text
1950
2017
  text = text.trim();
1951
-
2018
+
1952
2019
  // Reduce multiple consecutive newlines to maximum of 2
1953
2020
  text = text.replace(/\n\s*\n\s*\n+/g, '\n\n');
1954
-
2021
+
1955
2022
  // Trim whitespace from each line while preserving intentional indentation
1956
2023
  text = text.split('\n').map(line => {
1957
2024
  // Only trim trailing whitespace, preserve leading whitespace for structure
1958
2025
  return line.replace(/\s+$/, '');
1959
2026
  }).join('\n');
1960
-
2027
+
1961
2028
  return text;
1962
2029
  }
1963
2030
 
@@ -1995,7 +2062,7 @@ class ModuleViewer {
1995
2062
  const trimmedPrompt = this.trimPromptWhitespace(agentData.prompt);
1996
2063
  const tokenCount = Math.round(this.estimateTokenCount(trimmedPrompt));
1997
2064
  const wordCount = trimmedPrompt.trim().split(/\s+/).length;
1998
-
2065
+
1999
2066
  content += `
2000
2067
  <div class="agent-prompt-section">
2001
2068
  <div class="contextual-header">
@@ -2049,7 +2116,7 @@ class ModuleViewer {
2049
2116
  else if (tool.statusIcon === '❌') statusClass = 'status-failed';
2050
2117
  else if (tool.statusIcon === 'âš ī¸') statusClass = 'status-blocked';
2051
2118
  else if (tool.statusIcon === 'âŗ') statusClass = 'status-running';
2052
-
2119
+
2053
2120
  return `
2054
2121
  <div class="tool-call-item">
2055
2122
  <div class="tool-call-header">
@@ -2080,15 +2147,15 @@ class ModuleViewer {
2080
2147
  originalEvent: originalEvent
2081
2148
  };
2082
2149
  const collapsibleJsonSection = this.createCollapsibleJsonSection(agentJsonData);
2083
-
2150
+
2084
2151
  // Show structured data with JSON section in data pane
2085
2152
  if (this.dataContainer) {
2086
2153
  this.dataContainer.innerHTML = contextualHeader + content + collapsibleJsonSection;
2087
2154
  }
2088
-
2155
+
2089
2156
  // Initialize JSON toggle functionality
2090
2157
  this.initializeJsonToggle();
2091
-
2158
+
2092
2159
  // Hide JSON pane since data is integrated above
2093
2160
  // JSON container no longer exists - handled via collapsible sections
2094
2161
  }
@@ -2119,7 +2186,7 @@ class ModuleViewer {
2119
2186
 
2120
2187
  // Get inline result content
2121
2188
  const inlineContent = this.createInlineToolResultContent(mockData, mockEvent);
2122
-
2189
+
2123
2190
  // If we have content, wrap it in a simple section
2124
2191
  if (inlineContent.trim()) {
2125
2192
  return `
@@ -2130,7 +2197,7 @@ class ModuleViewer {
2130
2197
  </div>
2131
2198
  `;
2132
2199
  }
2133
-
2200
+
2134
2201
  return '';
2135
2202
  }
2136
2203
 
@@ -2143,7 +2210,7 @@ class ModuleViewer {
2143
2210
  */
2144
2211
  extractToolTarget(toolName, parameters, altParameters) {
2145
2212
  const params = parameters || altParameters || {};
2146
-
2213
+
2147
2214
  switch (toolName?.toLowerCase()) {
2148
2215
  case 'write':
2149
2216
  case 'read':
@@ -2214,7 +2281,7 @@ class ModuleViewer {
2214
2281
 
2215
2282
  // Get working directory from dashboard with proper fallback
2216
2283
  let workingDir = window.dashboard?.currentWorkingDir;
2217
-
2284
+
2218
2285
  // Don't use 'Unknown' as a working directory
2219
2286
  if (!workingDir || workingDir === 'Unknown' || workingDir.trim() === '') {
2220
2287
  // Try to get from footer element
@@ -2274,7 +2341,7 @@ class ModuleViewer {
2274
2341
  displayTrackingStatus(filePath, result) {
2275
2342
  const statusElementId = `git-track-status-${filePath.replace(/[^a-zA-Z0-9]/g, '-')}`;
2276
2343
  const statusElement = document.getElementById(statusElementId);
2277
-
2344
+
2278
2345
  if (!statusElement) return;
2279
2346
 
2280
2347
  if (result.success && result.is_tracked === false) {
@@ -2283,7 +2350,7 @@ class ModuleViewer {
2283
2350
  <div class="untracked-file-notice">
2284
2351
  <span class="untracked-icon">âš ī¸</span>
2285
2352
  <span class="untracked-text">This file is not tracked by git</span>
2286
- <button class="track-file-button"
2353
+ <button class="track-file-button"
2287
2354
  onclick="window.moduleViewer.trackFile('${filePath}')"
2288
2355
  title="Add this file to git tracking">
2289
2356
  <span class="git-icon">📁</span> Track File
@@ -2326,7 +2393,7 @@ class ModuleViewer {
2326
2393
 
2327
2394
  // Get working directory from dashboard with proper fallback
2328
2395
  let workingDir = window.dashboard?.currentWorkingDir;
2329
-
2396
+
2330
2397
  // Don't use 'Unknown' as a working directory
2331
2398
  if (!workingDir || workingDir === 'Unknown' || workingDir.trim() === '') {
2332
2399
  // Try to get from footer element
@@ -2343,7 +2410,7 @@ class ModuleViewer {
2343
2410
  // Update button to show loading state
2344
2411
  const statusElementId = `git-track-status-${filePath.replace(/[^a-zA-Z0-9]/g, '-')}`;
2345
2412
  const statusElement = document.getElementById(statusElementId);
2346
-
2413
+
2347
2414
  if (statusElement) {
2348
2415
  statusElement.innerHTML = `
2349
2416
  <div class="tracking-file-notice">
@@ -2396,7 +2463,7 @@ class ModuleViewer {
2396
2463
  </div>
2397
2464
  `;
2398
2465
  }
2399
-
2466
+
2400
2467
  // Show success notification
2401
2468
  this.showNotification('File tracked successfully', 'success');
2402
2469
  } else {
@@ -2405,7 +2472,7 @@ class ModuleViewer {
2405
2472
  <div class="tracking-error-notice">
2406
2473
  <span class="error-icon">❌</span>
2407
2474
  <span class="error-text">Failed to track file: ${result.error || 'Unknown error'}</span>
2408
- <button class="track-file-button"
2475
+ <button class="track-file-button"
2409
2476
  onclick="window.moduleViewer.trackFile('${filePath}')"
2410
2477
  title="Try again">
2411
2478
  <span class="git-icon">📁</span> Retry
@@ -2413,24 +2480,24 @@ class ModuleViewer {
2413
2480
  </div>
2414
2481
  `;
2415
2482
  }
2416
-
2483
+
2417
2484
  // Show error notification
2418
2485
  this.showNotification(`Failed to track file: ${result.error}`, 'error');
2419
2486
  }
2420
2487
 
2421
2488
  } catch (error) {
2422
2489
  console.error('❌ Failed to track file:', error);
2423
-
2490
+
2424
2491
  // Update UI to show error
2425
2492
  const statusElementId = `git-track-status-${filePath.replace(/[^a-zA-Z0-9]/g, '-')}`;
2426
2493
  const statusElement = document.getElementById(statusElementId);
2427
-
2494
+
2428
2495
  if (statusElement) {
2429
2496
  statusElement.innerHTML = `
2430
2497
  <div class="tracking-error-notice">
2431
2498
  <span class="error-icon">❌</span>
2432
2499
  <span class="error-text">Error: ${error.message}</span>
2433
- <button class="track-file-button"
2500
+ <button class="track-file-button"
2434
2501
  onclick="window.moduleViewer.trackFile('${filePath}')"
2435
2502
  title="Try again">
2436
2503
  <span class="git-icon">📁</span> Retry
@@ -2438,7 +2505,7 @@ class ModuleViewer {
2438
2505
  </div>
2439
2506
  `;
2440
2507
  }
2441
-
2508
+
2442
2509
  // Show error notification
2443
2510
  this.showNotification(`Error tracking file: ${error.message}`, 'error');
2444
2511
  }
@@ -2469,7 +2536,7 @@ class ModuleViewer {
2469
2536
 
2470
2537
  // Get working directory from dashboard with proper fallback
2471
2538
  let workingDir = window.dashboard?.currentWorkingDir;
2472
-
2539
+
2473
2540
  // Don't use 'Unknown' as a working directory
2474
2541
  if (!workingDir || workingDir === 'Unknown' || workingDir.trim() === '') {
2475
2542
  // Try to get from footer element
@@ -2517,7 +2584,7 @@ class ModuleViewer {
2517
2584
  // Wait for response
2518
2585
  const result = await responsePromise;
2519
2586
  console.debug('[GIT-DIFF-ICONS] Git status check result:', result);
2520
-
2587
+
2521
2588
  // Only show git diff icons if git status check was successful
2522
2589
  if (result.success) {
2523
2590
  console.debug('[GIT-DIFF-ICONS] Git status check successful, showing icons for:', filePath);
@@ -2539,16 +2606,16 @@ class ModuleViewer {
2539
2606
  */
2540
2607
  showGitDiffIconsForFile(filePath) {
2541
2608
  console.debug('[GIT-DIFF-ICONS] Showing git diff icons for file:', filePath);
2542
-
2609
+
2543
2610
  // Find all git diff icons for this file path and show them
2544
2611
  const gitDiffIcons = document.querySelectorAll(`[data-file-path="${filePath}"]`);
2545
2612
  console.debug('[GIT-DIFF-ICONS] Found', gitDiffIcons.length, 'elements with matching file path');
2546
-
2613
+
2547
2614
  let shownCount = 0;
2548
2615
  gitDiffIcons.forEach((icon, index) => {
2549
2616
  console.debug('[GIT-DIFF-ICONS] Processing element', index, ':', icon);
2550
2617
  console.debug('[GIT-DIFF-ICONS] Element classes:', icon.classList.toString());
2551
-
2618
+
2552
2619
  if (icon.classList.contains('git-diff-icon')) {
2553
2620
  console.debug('[GIT-DIFF-ICONS] Setting display to inline for git-diff-icon');
2554
2621
  icon.style.display = 'inline';
@@ -2557,7 +2624,7 @@ class ModuleViewer {
2557
2624
  console.debug('[GIT-DIFF-ICONS] Element is not a git-diff-icon, skipping');
2558
2625
  }
2559
2626
  });
2560
-
2627
+
2561
2628
  console.debug('[GIT-DIFF-ICONS] Showed', shownCount, 'git diff icons for file:', filePath);
2562
2629
  }
2563
2630
 
@@ -2574,7 +2641,7 @@ class ModuleViewer {
2574
2641
  <span class="notification-icon">${type === 'success' ? '✅' : type === 'error' ? '❌' : 'â„šī¸'}</span>
2575
2642
  <span class="notification-message">${message}</span>
2576
2643
  `;
2577
-
2644
+
2578
2645
  // Style the notification
2579
2646
  notification.style.cssText = `
2580
2647
  position: fixed;
@@ -2595,7 +2662,7 @@ class ModuleViewer {
2595
2662
  max-width: 400px;
2596
2663
  animation: slideIn 0.3s ease-out;
2597
2664
  `;
2598
-
2665
+
2599
2666
  // Add animation styles
2600
2667
  const style = document.createElement('style');
2601
2668
  style.textContent = `
@@ -2609,10 +2676,10 @@ class ModuleViewer {
2609
2676
  }
2610
2677
  `;
2611
2678
  document.head.appendChild(style);
2612
-
2679
+
2613
2680
  // Add to page
2614
2681
  document.body.appendChild(notification);
2615
-
2682
+
2616
2683
  // Remove after 5 seconds
2617
2684
  setTimeout(() => {
2618
2685
  notification.style.animation = 'slideOut 0.3s ease-in';
@@ -2687,6 +2754,11 @@ class ModuleViewer {
2687
2754
  }
2688
2755
 
2689
2756
  // Export for global use
2757
+ // ES6 Module export
2758
+ export { ModuleViewer };
2759
+ export default ModuleViewer;
2760
+
2761
+ // Backward compatibility - keep window export for non-module usage
2690
2762
  window.ModuleViewer = ModuleViewer;
2691
2763
 
2692
2764
  // Debug helper function for troubleshooting tool result display
@@ -2698,4 +2770,4 @@ window.enableToolResultDebugging = function() {
2698
2770
  window.disableToolResultDebugging = function() {
2699
2771
  window.DEBUG_TOOL_RESULTS = false;
2700
2772
  console.log('🔧 Tool result debugging disabled.');
2701
- };
2773
+ };