claude-mpm 3.9.9__py3-none-any.whl โ†’ 4.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (411) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +2 -2
  3. claude_mpm/__main__.py +3 -2
  4. claude_mpm/agents/__init__.py +85 -79
  5. claude_mpm/agents/agent_loader.py +464 -1003
  6. claude_mpm/agents/agent_loader_integration.py +45 -45
  7. claude_mpm/agents/agents_metadata.py +29 -30
  8. claude_mpm/agents/async_agent_loader.py +156 -138
  9. claude_mpm/agents/base_agent.json +1 -1
  10. claude_mpm/agents/base_agent_loader.py +179 -151
  11. claude_mpm/agents/frontmatter_validator.py +229 -130
  12. claude_mpm/agents/schema/agent_schema.json +1 -1
  13. claude_mpm/agents/system_agent_config.py +213 -147
  14. claude_mpm/agents/templates/__init__.py +13 -13
  15. claude_mpm/agents/templates/code_analyzer.json +2 -2
  16. claude_mpm/agents/templates/data_engineer.json +1 -1
  17. claude_mpm/agents/templates/documentation.json +23 -11
  18. claude_mpm/agents/templates/engineer.json +22 -6
  19. claude_mpm/agents/templates/memory_manager.json +155 -0
  20. claude_mpm/agents/templates/ops.json +2 -2
  21. claude_mpm/agents/templates/project_organizer.json +1 -1
  22. claude_mpm/agents/templates/qa.json +1 -1
  23. claude_mpm/agents/templates/refactoring_engineer.json +222 -0
  24. claude_mpm/agents/templates/research.json +20 -14
  25. claude_mpm/agents/templates/security.json +1 -1
  26. claude_mpm/agents/templates/ticketing.json +1 -1
  27. claude_mpm/agents/templates/version_control.json +1 -1
  28. claude_mpm/agents/templates/web_qa.json +3 -1
  29. claude_mpm/agents/templates/web_ui.json +2 -2
  30. claude_mpm/cli/__init__.py +90 -49
  31. claude_mpm/cli/__main__.py +3 -2
  32. claude_mpm/cli/commands/__init__.py +21 -18
  33. claude_mpm/cli/commands/agents.py +279 -247
  34. claude_mpm/cli/commands/aggregate.py +138 -157
  35. claude_mpm/cli/commands/cleanup.py +147 -147
  36. claude_mpm/cli/commands/config.py +93 -76
  37. claude_mpm/cli/commands/info.py +17 -16
  38. claude_mpm/cli/commands/mcp.py +143 -762
  39. claude_mpm/cli/commands/mcp_command_router.py +139 -0
  40. claude_mpm/cli/commands/mcp_config_commands.py +20 -0
  41. claude_mpm/cli/commands/mcp_install_commands.py +20 -0
  42. claude_mpm/cli/commands/mcp_server_commands.py +175 -0
  43. claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
  44. claude_mpm/cli/commands/memory.py +239 -203
  45. claude_mpm/cli/commands/monitor.py +203 -81
  46. claude_mpm/cli/commands/run.py +380 -429
  47. claude_mpm/cli/commands/run_config_checker.py +160 -0
  48. claude_mpm/cli/commands/socketio_monitor.py +235 -0
  49. claude_mpm/cli/commands/tickets.py +305 -197
  50. claude_mpm/cli/parser.py +24 -1150
  51. claude_mpm/cli/parsers/__init__.py +29 -0
  52. claude_mpm/cli/parsers/agents_parser.py +136 -0
  53. claude_mpm/cli/parsers/base_parser.py +331 -0
  54. claude_mpm/cli/parsers/config_parser.py +85 -0
  55. claude_mpm/cli/parsers/mcp_parser.py +152 -0
  56. claude_mpm/cli/parsers/memory_parser.py +138 -0
  57. claude_mpm/cli/parsers/monitor_parser.py +104 -0
  58. claude_mpm/cli/parsers/run_parser.py +147 -0
  59. claude_mpm/cli/parsers/tickets_parser.py +203 -0
  60. claude_mpm/cli/ticket_cli.py +7 -3
  61. claude_mpm/cli/utils.py +55 -37
  62. claude_mpm/cli_module/__init__.py +6 -6
  63. claude_mpm/cli_module/args.py +188 -140
  64. claude_mpm/cli_module/commands.py +79 -70
  65. claude_mpm/cli_module/migration_example.py +38 -60
  66. claude_mpm/config/__init__.py +32 -25
  67. claude_mpm/config/agent_config.py +151 -119
  68. claude_mpm/config/experimental_features.py +217 -0
  69. claude_mpm/config/paths.py +94 -208
  70. claude_mpm/config/socketio_config.py +84 -73
  71. claude_mpm/constants.py +36 -18
  72. claude_mpm/core/__init__.py +9 -6
  73. claude_mpm/core/agent_name_normalizer.py +68 -71
  74. claude_mpm/core/agent_registry.py +372 -521
  75. claude_mpm/core/agent_session_manager.py +74 -63
  76. claude_mpm/core/base_service.py +116 -87
  77. claude_mpm/core/cache.py +119 -153
  78. claude_mpm/core/claude_runner.py +425 -1120
  79. claude_mpm/core/config.py +263 -168
  80. claude_mpm/core/config_aliases.py +69 -61
  81. claude_mpm/core/config_constants.py +292 -0
  82. claude_mpm/core/constants.py +57 -99
  83. claude_mpm/core/container.py +211 -178
  84. claude_mpm/core/exceptions.py +233 -89
  85. claude_mpm/core/factories.py +92 -54
  86. claude_mpm/core/framework_loader.py +378 -220
  87. claude_mpm/core/hook_manager.py +198 -83
  88. claude_mpm/core/hook_performance_config.py +136 -0
  89. claude_mpm/core/injectable_service.py +61 -55
  90. claude_mpm/core/interactive_session.py +165 -155
  91. claude_mpm/core/interfaces.py +221 -195
  92. claude_mpm/core/lazy.py +96 -96
  93. claude_mpm/core/logger.py +133 -107
  94. claude_mpm/core/logging_config.py +185 -157
  95. claude_mpm/core/minimal_framework_loader.py +20 -15
  96. claude_mpm/core/mixins.py +30 -29
  97. claude_mpm/core/oneshot_session.py +215 -181
  98. claude_mpm/core/optimized_agent_loader.py +134 -138
  99. claude_mpm/core/optimized_startup.py +159 -157
  100. claude_mpm/core/pm_hook_interceptor.py +85 -72
  101. claude_mpm/core/service_registry.py +103 -101
  102. claude_mpm/core/session_manager.py +97 -87
  103. claude_mpm/core/socketio_pool.py +212 -158
  104. claude_mpm/core/tool_access_control.py +58 -51
  105. claude_mpm/core/types.py +46 -24
  106. claude_mpm/core/typing_utils.py +166 -82
  107. claude_mpm/core/unified_agent_registry.py +721 -0
  108. claude_mpm/core/unified_config.py +550 -0
  109. claude_mpm/core/unified_paths.py +549 -0
  110. claude_mpm/dashboard/index.html +1 -1
  111. claude_mpm/dashboard/open_dashboard.py +51 -17
  112. claude_mpm/dashboard/static/css/dashboard.css +27 -8
  113. claude_mpm/dashboard/static/dist/components/agent-inference.js +2 -0
  114. claude_mpm/dashboard/static/dist/components/event-processor.js +2 -0
  115. claude_mpm/dashboard/static/dist/components/event-viewer.js +2 -0
  116. claude_mpm/dashboard/static/dist/components/export-manager.js +2 -0
  117. claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +2 -0
  118. claude_mpm/dashboard/static/dist/components/hud-library-loader.js +2 -0
  119. claude_mpm/dashboard/static/dist/components/hud-manager.js +2 -0
  120. claude_mpm/dashboard/static/dist/components/hud-visualizer.js +2 -0
  121. claude_mpm/dashboard/static/dist/components/module-viewer.js +2 -0
  122. claude_mpm/dashboard/static/dist/components/session-manager.js +2 -0
  123. claude_mpm/dashboard/static/dist/components/socket-manager.js +2 -0
  124. claude_mpm/dashboard/static/dist/components/ui-state-manager.js +2 -0
  125. claude_mpm/dashboard/static/dist/components/working-directory.js +2 -0
  126. claude_mpm/dashboard/static/dist/dashboard.js +2 -0
  127. claude_mpm/dashboard/static/dist/socket-client.js +2 -0
  128. claude_mpm/dashboard/static/js/components/agent-inference.js +80 -76
  129. claude_mpm/dashboard/static/js/components/event-processor.js +71 -67
  130. claude_mpm/dashboard/static/js/components/event-viewer.js +74 -70
  131. claude_mpm/dashboard/static/js/components/export-manager.js +31 -28
  132. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +106 -92
  133. claude_mpm/dashboard/static/js/components/hud-library-loader.js +11 -11
  134. claude_mpm/dashboard/static/js/components/hud-manager.js +73 -73
  135. claude_mpm/dashboard/static/js/components/hud-visualizer.js +163 -163
  136. claude_mpm/dashboard/static/js/components/module-viewer.js +305 -233
  137. claude_mpm/dashboard/static/js/components/session-manager.js +32 -29
  138. claude_mpm/dashboard/static/js/components/socket-manager.js +27 -20
  139. claude_mpm/dashboard/static/js/components/ui-state-manager.js +21 -18
  140. claude_mpm/dashboard/static/js/components/working-directory.js +74 -71
  141. claude_mpm/dashboard/static/js/dashboard.js +178 -453
  142. claude_mpm/dashboard/static/js/extension-error-handler.js +164 -0
  143. claude_mpm/dashboard/static/js/socket-client.js +120 -54
  144. claude_mpm/dashboard/templates/index.html +40 -50
  145. claude_mpm/experimental/cli_enhancements.py +60 -58
  146. claude_mpm/generators/__init__.py +1 -1
  147. claude_mpm/generators/agent_profile_generator.py +75 -65
  148. claude_mpm/hooks/__init__.py +1 -1
  149. claude_mpm/hooks/base_hook.py +33 -28
  150. claude_mpm/hooks/claude_hooks/__init__.py +1 -1
  151. claude_mpm/hooks/claude_hooks/connection_pool.py +120 -0
  152. claude_mpm/hooks/claude_hooks/event_handlers.py +743 -0
  153. claude_mpm/hooks/claude_hooks/hook_handler.py +415 -1331
  154. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +4 -4
  155. claude_mpm/hooks/claude_hooks/memory_integration.py +221 -0
  156. claude_mpm/hooks/claude_hooks/response_tracking.py +348 -0
  157. claude_mpm/hooks/claude_hooks/tool_analysis.py +230 -0
  158. claude_mpm/hooks/memory_integration_hook.py +140 -100
  159. claude_mpm/hooks/tool_call_interceptor.py +89 -76
  160. claude_mpm/hooks/validation_hooks.py +57 -49
  161. claude_mpm/init.py +145 -121
  162. claude_mpm/models/__init__.py +9 -9
  163. claude_mpm/models/agent_definition.py +33 -23
  164. claude_mpm/models/agent_session.py +228 -200
  165. claude_mpm/scripts/__init__.py +1 -1
  166. claude_mpm/scripts/socketio_daemon.py +192 -75
  167. claude_mpm/scripts/socketio_server_manager.py +328 -0
  168. claude_mpm/scripts/start_activity_logging.py +25 -22
  169. claude_mpm/services/__init__.py +68 -43
  170. claude_mpm/services/agent_capabilities_service.py +271 -0
  171. claude_mpm/services/agents/__init__.py +23 -32
  172. claude_mpm/services/agents/deployment/__init__.py +3 -3
  173. claude_mpm/services/agents/deployment/agent_config_provider.py +310 -0
  174. claude_mpm/services/agents/deployment/agent_configuration_manager.py +359 -0
  175. claude_mpm/services/agents/deployment/agent_definition_factory.py +84 -0
  176. claude_mpm/services/agents/deployment/agent_deployment.py +415 -2113
  177. claude_mpm/services/agents/deployment/agent_discovery_service.py +387 -0
  178. claude_mpm/services/agents/deployment/agent_environment_manager.py +293 -0
  179. claude_mpm/services/agents/deployment/agent_filesystem_manager.py +387 -0
  180. claude_mpm/services/agents/deployment/agent_format_converter.py +453 -0
  181. claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +161 -0
  182. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +345 -495
  183. claude_mpm/services/agents/deployment/agent_metrics_collector.py +279 -0
  184. claude_mpm/services/agents/deployment/agent_restore_handler.py +88 -0
  185. claude_mpm/services/agents/deployment/agent_template_builder.py +406 -0
  186. claude_mpm/services/agents/deployment/agent_validator.py +352 -0
  187. claude_mpm/services/agents/deployment/agent_version_manager.py +313 -0
  188. claude_mpm/services/agents/deployment/agent_versioning.py +6 -9
  189. claude_mpm/services/agents/deployment/agents_directory_resolver.py +79 -0
  190. claude_mpm/services/agents/deployment/async_agent_deployment.py +298 -234
  191. claude_mpm/services/agents/deployment/config/__init__.py +13 -0
  192. claude_mpm/services/agents/deployment/config/deployment_config.py +182 -0
  193. claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
  194. claude_mpm/services/agents/deployment/deployment_config_loader.py +54 -0
  195. claude_mpm/services/agents/deployment/deployment_type_detector.py +124 -0
  196. claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
  197. claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
  198. claude_mpm/services/agents/deployment/facade/deployment_executor.py +73 -0
  199. claude_mpm/services/agents/deployment/facade/deployment_facade.py +270 -0
  200. claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
  201. claude_mpm/services/agents/deployment/interface_adapter.py +227 -0
  202. claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
  203. claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
  204. claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
  205. claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
  206. claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +159 -0
  207. claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
  208. claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
  209. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +195 -0
  210. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +119 -0
  211. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +79 -0
  212. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +90 -0
  213. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +100 -0
  214. claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
  215. claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +98 -0
  216. claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
  217. claude_mpm/services/agents/deployment/processors/agent_processor.py +258 -0
  218. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +318 -0
  219. claude_mpm/services/agents/deployment/results/__init__.py +13 -0
  220. claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
  221. claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
  222. claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
  223. claude_mpm/services/agents/deployment/strategies/base_strategy.py +119 -0
  224. claude_mpm/services/agents/deployment/strategies/project_strategy.py +150 -0
  225. claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
  226. claude_mpm/services/agents/deployment/strategies/system_strategy.py +116 -0
  227. claude_mpm/services/agents/deployment/strategies/user_strategy.py +137 -0
  228. claude_mpm/services/agents/deployment/system_instructions_deployer.py +108 -0
  229. claude_mpm/services/agents/deployment/validation/__init__.py +19 -0
  230. claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
  231. claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
  232. claude_mpm/services/agents/deployment/validation/template_validator.py +299 -0
  233. claude_mpm/services/agents/deployment/validation/validation_result.py +226 -0
  234. claude_mpm/services/agents/loading/__init__.py +2 -2
  235. claude_mpm/services/agents/loading/agent_profile_loader.py +259 -229
  236. claude_mpm/services/agents/loading/base_agent_manager.py +90 -81
  237. claude_mpm/services/agents/loading/framework_agent_loader.py +154 -129
  238. claude_mpm/services/agents/management/__init__.py +2 -2
  239. claude_mpm/services/agents/management/agent_capabilities_generator.py +72 -58
  240. claude_mpm/services/agents/management/agent_management_service.py +209 -156
  241. claude_mpm/services/agents/memory/__init__.py +9 -6
  242. claude_mpm/services/agents/memory/agent_memory_manager.py +218 -1152
  243. claude_mpm/services/agents/memory/agent_persistence_service.py +20 -16
  244. claude_mpm/services/agents/memory/analyzer.py +430 -0
  245. claude_mpm/services/agents/memory/content_manager.py +376 -0
  246. claude_mpm/services/agents/memory/template_generator.py +468 -0
  247. claude_mpm/services/agents/registry/__init__.py +7 -10
  248. claude_mpm/services/agents/registry/deployed_agent_discovery.py +122 -97
  249. claude_mpm/services/agents/registry/modification_tracker.py +351 -285
  250. claude_mpm/services/async_session_logger.py +187 -153
  251. claude_mpm/services/claude_session_logger.py +87 -72
  252. claude_mpm/services/command_handler_service.py +217 -0
  253. claude_mpm/services/communication/__init__.py +3 -2
  254. claude_mpm/services/core/__init__.py +50 -97
  255. claude_mpm/services/core/base.py +60 -53
  256. claude_mpm/services/core/interfaces/__init__.py +188 -0
  257. claude_mpm/services/core/interfaces/agent.py +351 -0
  258. claude_mpm/services/core/interfaces/communication.py +343 -0
  259. claude_mpm/services/core/interfaces/infrastructure.py +413 -0
  260. claude_mpm/services/core/interfaces/service.py +434 -0
  261. claude_mpm/services/core/interfaces.py +19 -944
  262. claude_mpm/services/event_aggregator.py +208 -170
  263. claude_mpm/services/exceptions.py +387 -308
  264. claude_mpm/services/framework_claude_md_generator/__init__.py +75 -79
  265. claude_mpm/services/framework_claude_md_generator/content_assembler.py +69 -60
  266. claude_mpm/services/framework_claude_md_generator/content_validator.py +65 -61
  267. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +68 -49
  268. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +34 -34
  269. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +25 -22
  270. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +10 -10
  271. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +4 -3
  272. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
  273. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
  274. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
  275. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
  276. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +4 -3
  277. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
  278. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
  279. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +4 -3
  280. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +5 -4
  281. claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
  282. claude_mpm/services/framework_claude_md_generator/version_manager.py +30 -28
  283. claude_mpm/services/hook_service.py +106 -114
  284. claude_mpm/services/infrastructure/__init__.py +7 -5
  285. claude_mpm/services/infrastructure/context_preservation.py +571 -0
  286. claude_mpm/services/infrastructure/daemon_manager.py +279 -0
  287. claude_mpm/services/infrastructure/logging.py +83 -76
  288. claude_mpm/services/infrastructure/monitoring.py +547 -404
  289. claude_mpm/services/mcp_gateway/__init__.py +40 -23
  290. claude_mpm/services/mcp_gateway/config/__init__.py +2 -2
  291. claude_mpm/services/mcp_gateway/config/config_loader.py +61 -56
  292. claude_mpm/services/mcp_gateway/config/config_schema.py +50 -41
  293. claude_mpm/services/mcp_gateway/config/configuration.py +82 -75
  294. claude_mpm/services/mcp_gateway/core/__init__.py +14 -21
  295. claude_mpm/services/mcp_gateway/core/base.py +80 -67
  296. claude_mpm/services/mcp_gateway/core/exceptions.py +60 -46
  297. claude_mpm/services/mcp_gateway/core/interfaces.py +97 -93
  298. claude_mpm/services/mcp_gateway/main.py +307 -127
  299. claude_mpm/services/mcp_gateway/registry/__init__.py +1 -1
  300. claude_mpm/services/mcp_gateway/registry/service_registry.py +100 -101
  301. claude_mpm/services/mcp_gateway/registry/tool_registry.py +135 -126
  302. claude_mpm/services/mcp_gateway/server/__init__.py +4 -4
  303. claude_mpm/services/mcp_gateway/server/{mcp_server.py โ†’ mcp_gateway.py} +149 -153
  304. claude_mpm/services/mcp_gateway/server/stdio_handler.py +105 -107
  305. claude_mpm/services/mcp_gateway/server/stdio_server.py +691 -0
  306. claude_mpm/services/mcp_gateway/tools/__init__.py +4 -2
  307. claude_mpm/services/mcp_gateway/tools/base_adapter.py +110 -121
  308. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +283 -215
  309. claude_mpm/services/mcp_gateway/tools/hello_world.py +122 -120
  310. claude_mpm/services/mcp_gateway/tools/ticket_tools.py +652 -0
  311. claude_mpm/services/mcp_gateway/tools/unified_ticket_tool.py +606 -0
  312. claude_mpm/services/memory/__init__.py +2 -2
  313. claude_mpm/services/memory/builder.py +451 -362
  314. claude_mpm/services/memory/cache/__init__.py +2 -2
  315. claude_mpm/services/memory/cache/shared_prompt_cache.py +232 -194
  316. claude_mpm/services/memory/cache/simple_cache.py +107 -93
  317. claude_mpm/services/memory/indexed_memory.py +195 -193
  318. claude_mpm/services/memory/optimizer.py +267 -234
  319. claude_mpm/services/memory/router.py +571 -263
  320. claude_mpm/services/memory_hook_service.py +237 -0
  321. claude_mpm/services/port_manager.py +223 -0
  322. claude_mpm/services/project/__init__.py +3 -3
  323. claude_mpm/services/project/analyzer.py +451 -305
  324. claude_mpm/services/project/registry.py +262 -240
  325. claude_mpm/services/recovery_manager.py +287 -231
  326. claude_mpm/services/response_tracker.py +87 -67
  327. claude_mpm/services/runner_configuration_service.py +587 -0
  328. claude_mpm/services/session_management_service.py +304 -0
  329. claude_mpm/services/socketio/__init__.py +4 -4
  330. claude_mpm/services/socketio/client_proxy.py +174 -0
  331. claude_mpm/services/socketio/handlers/__init__.py +3 -3
  332. claude_mpm/services/socketio/handlers/base.py +44 -30
  333. claude_mpm/services/socketio/handlers/connection.py +145 -65
  334. claude_mpm/services/socketio/handlers/file.py +123 -108
  335. claude_mpm/services/socketio/handlers/git.py +607 -373
  336. claude_mpm/services/socketio/handlers/hook.py +170 -0
  337. claude_mpm/services/socketio/handlers/memory.py +4 -4
  338. claude_mpm/services/socketio/handlers/project.py +4 -4
  339. claude_mpm/services/socketio/handlers/registry.py +53 -38
  340. claude_mpm/services/socketio/server/__init__.py +18 -0
  341. claude_mpm/services/socketio/server/broadcaster.py +252 -0
  342. claude_mpm/services/socketio/server/core.py +399 -0
  343. claude_mpm/services/socketio/server/main.py +323 -0
  344. claude_mpm/services/socketio_client_manager.py +160 -133
  345. claude_mpm/services/socketio_server.py +36 -1885
  346. claude_mpm/services/subprocess_launcher_service.py +316 -0
  347. claude_mpm/services/system_instructions_service.py +258 -0
  348. claude_mpm/services/ticket_manager.py +20 -534
  349. claude_mpm/services/utility_service.py +285 -0
  350. claude_mpm/services/version_control/__init__.py +18 -21
  351. claude_mpm/services/version_control/branch_strategy.py +20 -10
  352. claude_mpm/services/version_control/conflict_resolution.py +37 -13
  353. claude_mpm/services/version_control/git_operations.py +52 -21
  354. claude_mpm/services/version_control/semantic_versioning.py +92 -53
  355. claude_mpm/services/version_control/version_parser.py +145 -125
  356. claude_mpm/services/version_service.py +270 -0
  357. claude_mpm/storage/__init__.py +9 -0
  358. claude_mpm/storage/state_storage.py +552 -0
  359. claude_mpm/ticket_wrapper.py +2 -2
  360. claude_mpm/utils/__init__.py +2 -2
  361. claude_mpm/utils/agent_dependency_loader.py +453 -243
  362. claude_mpm/utils/config_manager.py +157 -118
  363. claude_mpm/utils/console.py +1 -1
  364. claude_mpm/utils/dependency_cache.py +102 -107
  365. claude_mpm/utils/dependency_manager.py +52 -47
  366. claude_mpm/utils/dependency_strategies.py +131 -96
  367. claude_mpm/utils/environment_context.py +110 -102
  368. claude_mpm/utils/error_handler.py +75 -55
  369. claude_mpm/utils/file_utils.py +80 -67
  370. claude_mpm/utils/framework_detection.py +12 -11
  371. claude_mpm/utils/import_migration_example.py +12 -60
  372. claude_mpm/utils/imports.py +48 -45
  373. claude_mpm/utils/path_operations.py +100 -93
  374. claude_mpm/utils/robust_installer.py +172 -164
  375. claude_mpm/utils/session_logging.py +30 -23
  376. claude_mpm/utils/subprocess_utils.py +99 -61
  377. claude_mpm/validation/__init__.py +1 -1
  378. claude_mpm/validation/agent_validator.py +151 -111
  379. claude_mpm/validation/frontmatter_validator.py +92 -71
  380. {claude_mpm-3.9.9.dist-info โ†’ claude_mpm-4.0.3.dist-info}/METADATA +51 -2
  381. claude_mpm-4.0.3.dist-info/RECORD +402 -0
  382. {claude_mpm-3.9.9.dist-info โ†’ claude_mpm-4.0.3.dist-info}/entry_points.txt +1 -0
  383. {claude_mpm-3.9.9.dist-info โ†’ claude_mpm-4.0.3.dist-info}/licenses/LICENSE +1 -1
  384. claude_mpm/config/memory_guardian_config.py +0 -325
  385. claude_mpm/core/config_paths.py +0 -150
  386. claude_mpm/dashboard/static/js/dashboard-original.js +0 -4134
  387. claude_mpm/deployment_paths.py +0 -261
  388. claude_mpm/hooks/claude_hooks/hook_handler_fixed.py +0 -454
  389. claude_mpm/models/state_models.py +0 -433
  390. claude_mpm/services/agent/__init__.py +0 -24
  391. claude_mpm/services/agent/deployment.py +0 -2548
  392. claude_mpm/services/agent/management.py +0 -598
  393. claude_mpm/services/agent/registry.py +0 -813
  394. claude_mpm/services/agents/registry/agent_registry.py +0 -813
  395. claude_mpm/services/communication/socketio.py +0 -1935
  396. claude_mpm/services/communication/websocket.py +0 -479
  397. claude_mpm/services/framework_claude_md_generator.py +0 -624
  398. claude_mpm/services/health_monitor.py +0 -893
  399. claude_mpm/services/infrastructure/memory_guardian.py +0 -770
  400. claude_mpm/services/mcp_gateway/server/mcp_server_simple.py +0 -444
  401. claude_mpm/services/optimized_hook_service.py +0 -542
  402. claude_mpm/services/project_analyzer.py +0 -864
  403. claude_mpm/services/project_registry.py +0 -608
  404. claude_mpm/services/standalone_socketio_server.py +0 -1300
  405. claude_mpm/services/ticket_manager_di.py +0 -318
  406. claude_mpm/services/ticketing_service_original.py +0 -510
  407. claude_mpm/utils/paths.py +0 -395
  408. claude_mpm/utils/platform_memory.py +0 -524
  409. claude_mpm-3.9.9.dist-info/RECORD +0 -293
  410. {claude_mpm-3.9.9.dist-info โ†’ claude_mpm-4.0.3.dist-info}/WHEEL +0 -0
  411. {claude_mpm-3.9.9.dist-info โ†’ claude_mpm-4.0.3.dist-info}/top_level.txt +0 -0
@@ -1,4134 +0,0 @@
1
- /**
2
- * Main Dashboard Application
3
- * Coordinates all components and handles tab management
4
- */
5
-
6
- class Dashboard {
7
- constructor() {
8
- // Components
9
- this.socketClient = null;
10
- this.eventViewer = null;
11
- this.moduleViewer = null;
12
- this.sessionManager = null;
13
- this.hudVisualizer = null;
14
-
15
- // State
16
- this.currentTab = 'events';
17
- this.autoScroll = true;
18
- this.hudMode = false;
19
-
20
- // Working directory state - will be set properly during initialization
21
- this.currentWorkingDir = null;
22
-
23
- // Selection state - tracks the currently selected card across all tabs
24
- this.selectedCard = {
25
- tab: null, // which tab the selection is in
26
- index: null, // index of selected item in that tab
27
- type: null, // 'event', 'agent', 'tool', 'file'
28
- data: null // the actual data object
29
- };
30
-
31
- // Navigation state for each tab
32
- this.tabNavigation = {
33
- events: { selectedIndex: -1, items: [] },
34
- agents: { selectedIndex: -1, items: [] },
35
- tools: { selectedIndex: -1, items: [] },
36
- files: { selectedIndex: -1, items: [] }
37
- };
38
-
39
- // File tracking for files tab
40
- this.fileOperations = new Map(); // Map of file paths to operations
41
-
42
- // Tool call tracking for tools tab
43
- this.toolCalls = new Map(); // Map of tool call keys to paired pre/post events
44
-
45
- // Agent events tracking for agents tab
46
- this.agentEvents = []; // Array of filtered agent events
47
-
48
- this.init();
49
- }
50
-
51
- /**
52
- * Initialize the dashboard
53
- */
54
- init() {
55
- // Initialize components
56
- this.initializeComponents();
57
- this.setupEventHandlers();
58
- this.setupTabNavigation();
59
- this.setupUnifiedKeyboardNavigation();
60
- this.initializeFromURL();
61
-
62
- // Initialize agent inference system
63
- this.initializeAgentInference();
64
-
65
- // Initialize working directory for current session
66
- this.initializeWorkingDirectory();
67
-
68
- // Watch for footer directory changes
69
- this.watchFooterDirectory();
70
-
71
- // Initialize HUD button state
72
- this.updateHUDButtonState();
73
-
74
- console.log('Claude MPM Dashboard initialized');
75
- }
76
-
77
- /**
78
- * Initialize agent inference system
79
- * Based on docs/design/main-subagent-identification.md
80
- */
81
- initializeAgentInference() {
82
- // Agent inference state tracking
83
- this.agentInference = {
84
- // Track current subagent delegation context
85
- currentDelegation: null,
86
- // Map of session_id -> agent context
87
- sessionAgents: new Map(),
88
- // Map of event indices -> inferred agent
89
- eventAgentMap: new Map()
90
- };
91
-
92
- console.log('Agent inference system initialized');
93
- }
94
-
95
- /**
96
- * Infer agent context from event payload
97
- * Based on production-ready detection from design document
98
- * @param {Object} event - Event payload
99
- * @returns {Object} - {type: 'main_agent'|'subagent', confidence: 'definitive'|'high'|'medium'|'default', agentName: string}
100
- */
101
- inferAgentFromEvent(event) {
102
- // Handle both direct properties and nested data properties
103
- const data = event.data || {};
104
- const sessionId = event.session_id || data.session_id || 'unknown';
105
- const eventType = event.hook_event_name || data.hook_event_name || event.type || '';
106
- const subtype = event.subtype || '';
107
- const toolName = event.tool_name || data.tool_name || '';
108
-
109
- // Direct event detection (highest confidence) - from design doc
110
- if (eventType === 'SubagentStop' || subtype === 'subagent_stop') {
111
- const agentName = this.extractAgentNameFromEvent(event);
112
- return {
113
- type: 'subagent',
114
- confidence: 'definitive',
115
- agentName: agentName,
116
- reason: 'SubagentStop event'
117
- };
118
- }
119
-
120
- if (eventType === 'Stop' || subtype === 'stop') {
121
- return {
122
- type: 'main_agent',
123
- confidence: 'definitive',
124
- agentName: 'PM',
125
- reason: 'Stop event'
126
- };
127
- }
128
-
129
- // Tool-based detection (high confidence) - from design doc
130
- if (toolName === 'Task') {
131
- const agentName = this.extractSubagentTypeFromTask(event);
132
- if (agentName) {
133
- return {
134
- type: 'subagent',
135
- confidence: 'high',
136
- agentName: agentName,
137
- reason: 'Task tool with subagent_type'
138
- };
139
- }
140
- }
141
-
142
- // Hook event pattern analysis (high confidence)
143
- if (eventType === 'PreToolUse' && toolName === 'Task') {
144
- const agentName = this.extractSubagentTypeFromTask(event);
145
- if (agentName) {
146
- return {
147
- type: 'subagent',
148
- confidence: 'high',
149
- agentName: agentName,
150
- reason: 'PreToolUse Task delegation'
151
- };
152
- }
153
- }
154
-
155
- // Session pattern analysis (medium confidence) - from design doc
156
- if (sessionId) {
157
- const sessionLower = sessionId.toLowerCase();
158
- if (['subagent', 'task', 'agent-'].some(pattern => sessionLower.includes(pattern))) {
159
- return {
160
- type: 'subagent',
161
- confidence: 'medium',
162
- agentName: 'Subagent',
163
- reason: 'Session ID pattern'
164
- };
165
- }
166
- }
167
-
168
- // Agent type field analysis
169
- const agentType = event.agent_type || event.data?.agent_type;
170
- const subagentType = event.subagent_type || event.data?.subagent_type;
171
-
172
- if (subagentType && subagentType !== 'unknown') {
173
- return {
174
- type: 'subagent',
175
- confidence: 'high',
176
- agentName: subagentType,
177
- reason: 'subagent_type field'
178
- };
179
- }
180
-
181
- if (agentType && agentType !== 'unknown' && agentType !== 'main') {
182
- return {
183
- type: 'subagent',
184
- confidence: 'medium',
185
- agentName: agentType,
186
- reason: 'agent_type field'
187
- };
188
- }
189
-
190
- // Default to main agent (from design doc)
191
- return {
192
- type: 'main_agent',
193
- confidence: 'default',
194
- agentName: 'PM',
195
- reason: 'default classification'
196
- };
197
- }
198
-
199
- /**
200
- * Extract subagent type from Task tool parameters
201
- * @param {Object} event - Event with Task tool
202
- * @returns {string|null} - Subagent type or null
203
- */
204
- extractSubagentTypeFromTask(event) {
205
- // Check tool_parameters directly
206
- if (event.tool_parameters?.subagent_type) {
207
- return event.tool_parameters.subagent_type;
208
- }
209
-
210
- // Check nested in data.tool_parameters (hook events)
211
- if (event.data?.tool_parameters?.subagent_type) {
212
- return event.data.tool_parameters.subagent_type;
213
- }
214
-
215
- // Check delegation_details (new structure)
216
- if (event.data?.delegation_details?.agent_type) {
217
- return event.data.delegation_details.agent_type;
218
- }
219
-
220
- // Check tool_input fallback
221
- if (event.tool_input?.subagent_type) {
222
- return event.tool_input.subagent_type;
223
- }
224
-
225
- return null;
226
- }
227
-
228
- /**
229
- * Extract agent name from any event
230
- * @param {Object} event - Event payload
231
- * @returns {string} - Agent name
232
- */
233
- extractAgentNameFromEvent(event) {
234
- // Priority order based on reliability from design doc
235
- const data = event.data || {};
236
-
237
- // 1. Task tool subagent_type (highest priority)
238
- if (event.tool_name === 'Task' || data.tool_name === 'Task') {
239
- const taskAgent = this.extractSubagentTypeFromTask(event);
240
- if (taskAgent) return taskAgent;
241
- }
242
-
243
- // 2. Direct subagent_type field
244
- if (event.subagent_type && event.subagent_type !== 'unknown') {
245
- return event.subagent_type;
246
- }
247
- if (data.subagent_type && data.subagent_type !== 'unknown') {
248
- return data.subagent_type;
249
- }
250
-
251
- // 3. Agent type fields (but not 'main' or 'unknown')
252
- if (event.agent_type && !['main', 'unknown'].includes(event.agent_type)) {
253
- return event.agent_type;
254
- }
255
- if (data.agent_type && !['main', 'unknown'].includes(data.agent_type)) {
256
- return event.agent_type;
257
- }
258
-
259
- if (event.data?.agent_type && !['main', 'unknown'].includes(event.data.agent_type)) {
260
- return event.data.agent_type;
261
- }
262
-
263
- // 5. Other fallbacks
264
- if (event.agent && event.agent !== 'unknown') {
265
- return event.agent;
266
- }
267
-
268
- if (event.name && event.name !== 'unknown') {
269
- return event.name;
270
- }
271
-
272
- // Default fallback
273
- return 'Unknown';
274
- }
275
-
276
- /**
277
- * Process all events and build agent inference context
278
- * This tracks delegation boundaries and agent context throughout the session
279
- */
280
- processAgentInference() {
281
- const events = this.eventViewer.events;
282
-
283
- // Reset inference state
284
- this.agentInference.currentDelegation = null;
285
- this.agentInference.sessionAgents.clear();
286
- this.agentInference.eventAgentMap.clear();
287
-
288
- console.log('Processing agent inference for', events.length, 'events');
289
-
290
- // Process events chronologically to track delegation context
291
- events.forEach((event, index) => {
292
- const inference = this.inferAgentFromEvent(event);
293
- const sessionId = event.session_id || 'default';
294
-
295
- // Track delegation boundaries
296
- if (event.tool_name === 'Task' && inference.type === 'subagent') {
297
- // Start of subagent delegation
298
- this.agentInference.currentDelegation = {
299
- agentName: inference.agentName,
300
- sessionId: sessionId,
301
- startIndex: index,
302
- endIndex: null
303
- };
304
- console.log('Delegation started:', this.agentInference.currentDelegation);
305
- } else if (inference.confidence === 'definitive' && inference.reason === 'SubagentStop event') {
306
- // End of subagent delegation
307
- if (this.agentInference.currentDelegation) {
308
- this.agentInference.currentDelegation.endIndex = index;
309
- console.log('Delegation ended:', this.agentInference.currentDelegation);
310
- this.agentInference.currentDelegation = null;
311
- }
312
- }
313
-
314
- // Determine agent for this event based on context
315
- let finalAgent = inference;
316
-
317
- // If we're in a delegation context and this event doesn't have high confidence agent info,
318
- // inherit from delegation context
319
- if (this.agentInference.currentDelegation &&
320
- inference.confidence === 'default' &&
321
- sessionId === this.agentInference.currentDelegation.sessionId) {
322
- finalAgent = {
323
- type: 'subagent',
324
- confidence: 'inherited',
325
- agentName: this.agentInference.currentDelegation.agentName,
326
- reason: 'inherited from delegation context'
327
- };
328
- }
329
-
330
- // Store the inference result
331
- this.agentInference.eventAgentMap.set(index, finalAgent);
332
-
333
- // Update session agent tracking
334
- this.agentInference.sessionAgents.set(sessionId, finalAgent);
335
-
336
- // Debug first few inferences
337
- if (index < 5) {
338
- console.log(`Event ${index} agent inference:`, {
339
- event_type: event.type,
340
- subtype: event.subtype,
341
- tool_name: event.tool_name,
342
- inference: finalAgent
343
- });
344
- }
345
- });
346
-
347
- console.log('Agent inference processing complete. Results:', {
348
- total_events: events.length,
349
- inferred_agents: this.agentInference.eventAgentMap.size,
350
- unique_sessions: this.agentInference.sessionAgents.size
351
- });
352
- }
353
-
354
- /**
355
- * Get inferred agent for a specific event
356
- * @param {number} eventIndex - Index of event in events array
357
- * @returns {Object|null} - Agent inference result or null
358
- */
359
- getInferredAgent(eventIndex) {
360
- return this.agentInference.eventAgentMap.get(eventIndex) || null;
361
- }
362
-
363
- /**
364
- * Get inferred agent for an event object
365
- * @param {Object} event - Event object
366
- * @returns {Object|null} - Agent inference result or null
367
- */
368
- getInferredAgentForEvent(event) {
369
- const events = this.eventViewer.events;
370
- const eventIndex = events.indexOf(event);
371
- if (eventIndex === -1) return null;
372
-
373
- return this.getInferredAgent(eventIndex);
374
- }
375
-
376
- /**
377
- * Initialize all components
378
- */
379
- initializeComponents() {
380
- // Initialize socket client
381
- this.socketClient = new SocketClient();
382
-
383
- // Initialize UI components
384
- this.eventViewer = new EventViewer('events-list', this.socketClient);
385
- this.moduleViewer = new ModuleViewer('module-content');
386
- this.sessionManager = new SessionManager(this.socketClient);
387
- this.hudVisualizer = new HUDVisualizer();
388
-
389
- // Store globally for backward compatibility
390
- window.socketClient = this.socketClient;
391
- window.eventViewer = this.eventViewer;
392
- window.moduleViewer = this.moduleViewer;
393
- window.sessionManager = this.sessionManager;
394
- window.hudVisualizer = this.hudVisualizer;
395
-
396
- // Initialize HUD visualizer
397
- this.hudVisualizer.initialize();
398
-
399
- // Setup component interactions
400
- this.setupComponentInteractions();
401
- }
402
-
403
- /**
404
- * Setup interactions between components
405
- */
406
- setupComponentInteractions() {
407
- // Socket connection status is now handled in the header connection status badge
408
- // Footer now focuses on session-specific information
409
-
410
- // Listen for socket events to update file operations and tool calls
411
- this.socketClient.onEventUpdate((events) => {
412
- this.updateFileOperations(events);
413
- this.updateToolCalls(events);
414
- // Process agent inference after events are updated
415
- this.processAgentInference();
416
- this.renderCurrentTab();
417
-
418
- // Process new events for HUD visualization
419
- if (this.hudMode && this.hudVisualizer && events.length > 0) {
420
- // Get the most recent event for HUD processing
421
- const latestEvent = events[events.length - 1];
422
- this.handleHUDEvent(latestEvent);
423
- }
424
-
425
- // Auto-scroll events list if on events tab
426
- if (this.currentTab === 'events') {
427
- this.scrollListToBottom('events-list');
428
- }
429
- });
430
-
431
- // Listen for connection status changes
432
- document.addEventListener('socketConnectionStatus', (e) => {
433
- this.updateConnectionStatus(e.detail.status, e.detail.type);
434
-
435
- // Set up git branch listener when connected
436
- if (e.detail.type === 'connected' && this.socketClient && this.socketClient.socket) {
437
- // Remove any existing listener first
438
- this.socketClient.socket.off('git_branch_response');
439
-
440
- // Add the listener
441
- this.socketClient.socket.on('git_branch_response', (data) => {
442
- if (data.success) {
443
- const footerBranch = document.getElementById('footer-git-branch');
444
- if (footerBranch) {
445
- footerBranch.textContent = data.branch;
446
- }
447
- } else {
448
- const footerBranch = document.getElementById('footer-git-branch');
449
- if (footerBranch) {
450
- footerBranch.textContent = 'No Git';
451
- }
452
- }
453
- });
454
-
455
- // Request git branch for current working directory
456
- this.updateGitBranch(this.currentWorkingDir);
457
- }
458
- });
459
-
460
- // Listen for session filter changes to update dropdown options
461
- document.addEventListener('sessionFilterChanged', (e) => {
462
- console.log('Session filter changed, re-rendering current tab:', this.currentTab);
463
- this.renderCurrentTab();
464
- // Update HUD button state based on session selection
465
- this.updateHUDButtonState();
466
- });
467
- }
468
-
469
- /**
470
- * Setup general event handlers
471
- */
472
- setupEventHandlers() {
473
- // Connection controls
474
- const connectBtn = document.getElementById('connect-btn');
475
- const disconnectBtn = document.getElementById('disconnect-btn');
476
- const portInput = document.getElementById('port-input');
477
-
478
- if (connectBtn) {
479
- connectBtn.addEventListener('click', () => {
480
- const port = portInput ? portInput.value : '8765';
481
- this.socketClient.connect(port);
482
- });
483
- }
484
-
485
- if (disconnectBtn) {
486
- disconnectBtn.addEventListener('click', () => {
487
- this.socketClient.disconnect();
488
- });
489
- }
490
-
491
- // Connection toggle button
492
- const connectionToggleBtn = document.getElementById('connection-toggle-btn');
493
- if (connectionToggleBtn) {
494
- connectionToggleBtn.addEventListener('click', () => {
495
- this.toggleConnectionControls();
496
- });
497
- }
498
-
499
- // Working directory controls
500
- const changeDirBtn = document.getElementById('change-dir-btn');
501
- const workingDirPath = document.getElementById('working-dir-path');
502
-
503
- if (changeDirBtn) {
504
- changeDirBtn.addEventListener('click', () => {
505
- this.showChangeDirDialog();
506
- });
507
- }
508
-
509
- if (workingDirPath) {
510
- workingDirPath.addEventListener('click', () => {
511
- this.showChangeDirDialog();
512
- });
513
- }
514
-
515
- // Action buttons
516
- const clearBtn = document.querySelector('button[onclick="clearEvents()"]');
517
- const exportBtn = document.getElementById('export-btn');
518
-
519
- if (clearBtn) {
520
- clearBtn.addEventListener('click', () => {
521
- this.clearEvents();
522
- });
523
- }
524
-
525
- if (exportBtn) {
526
- exportBtn.addEventListener('click', () => {
527
- this.exportEvents();
528
- });
529
- }
530
-
531
- // HUD toggle button
532
- const hudToggleBtn = document.getElementById('hud-toggle-btn');
533
- if (hudToggleBtn) {
534
- hudToggleBtn.addEventListener('click', () => {
535
- this.toggleHUD();
536
- });
537
- }
538
-
539
- // Clear selection button
540
- const clearSelectionBtn = document.querySelector('button[onclick="clearSelection()"]');
541
- if (clearSelectionBtn) {
542
- clearSelectionBtn.addEventListener('click', () => {
543
- this.clearSelection();
544
- });
545
- }
546
-
547
- // Tab-specific filters
548
- this.setupTabFilters();
549
- }
550
-
551
- /**
552
- * Setup filtering for each tab
553
- */
554
- setupTabFilters() {
555
- // Agents tab filters
556
- const agentsSearchInput = document.getElementById('agents-search-input');
557
- const agentsTypeFilter = document.getElementById('agents-type-filter');
558
-
559
- if (agentsSearchInput) {
560
- agentsSearchInput.addEventListener('input', () => {
561
- if (this.currentTab === 'agents') this.renderCurrentTab();
562
- });
563
- }
564
-
565
- if (agentsTypeFilter) {
566
- agentsTypeFilter.addEventListener('change', () => {
567
- if (this.currentTab === 'agents') this.renderCurrentTab();
568
- });
569
- }
570
-
571
- // Tools tab filters
572
- const toolsSearchInput = document.getElementById('tools-search-input');
573
- const toolsTypeFilter = document.getElementById('tools-type-filter');
574
-
575
- if (toolsSearchInput) {
576
- toolsSearchInput.addEventListener('input', () => {
577
- if (this.currentTab === 'tools') this.renderCurrentTab();
578
- });
579
- }
580
-
581
- if (toolsTypeFilter) {
582
- toolsTypeFilter.addEventListener('change', () => {
583
- if (this.currentTab === 'tools') this.renderCurrentTab();
584
- });
585
- }
586
-
587
- // Files tab filters
588
- const filesSearchInput = document.getElementById('files-search-input');
589
- const filesTypeFilter = document.getElementById('files-type-filter');
590
-
591
- if (filesSearchInput) {
592
- filesSearchInput.addEventListener('input', () => {
593
- if (this.currentTab === 'files') this.renderCurrentTab();
594
- });
595
- }
596
-
597
- if (filesTypeFilter) {
598
- filesTypeFilter.addEventListener('change', () => {
599
- if (this.currentTab === 'files') this.renderCurrentTab();
600
- });
601
- }
602
- }
603
-
604
- /**
605
- * Populate filter dropdown with unique values from data
606
- * @param {string} selectId - ID of the select element
607
- * @param {Array} values - Array of unique values to populate
608
- * @param {string} allOption - Text for the "All" option
609
- */
610
- populateFilterDropdown(selectId, values, allOption) {
611
- const select = document.getElementById(selectId);
612
- if (!select) return;
613
-
614
- // Store current selection
615
- const currentValue = select.value;
616
-
617
- // Clear existing options except the first "All" option
618
- select.innerHTML = `<option value="">${allOption}</option>`;
619
-
620
- // Add unique values, sorted alphabetically
621
- const sortedValues = [...values].sort();
622
- sortedValues.forEach(value => {
623
- const option = document.createElement('option');
624
- option.value = value;
625
- option.textContent = value;
626
- select.appendChild(option);
627
- });
628
-
629
- // Restore selection if it still exists
630
- if (currentValue && sortedValues.includes(currentValue)) {
631
- select.value = currentValue;
632
- }
633
- }
634
-
635
- /**
636
- * Setup tab navigation
637
- */
638
- setupTabNavigation() {
639
- const tabButtons = document.querySelectorAll('.tab-button');
640
- tabButtons.forEach(button => {
641
- button.addEventListener('click', () => {
642
- const tabName = this.getTabNameFromButton(button);
643
- this.switchTab(tabName);
644
- });
645
- });
646
- }
647
-
648
- /**
649
- * Setup unified keyboard navigation for all tabs
650
- */
651
- setupUnifiedKeyboardNavigation() {
652
- document.addEventListener('keydown', (e) => {
653
- // Only handle navigation if no input is focused
654
- if (document.activeElement &&
655
- (document.activeElement.tagName === 'INPUT' ||
656
- document.activeElement.tagName === 'TEXTAREA' ||
657
- document.activeElement.tagName === 'SELECT')) {
658
- return;
659
- }
660
-
661
- if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
662
- e.preventDefault();
663
- this.handleUnifiedArrowNavigation(e.key === 'ArrowDown' ? 1 : -1);
664
- } else if (e.key === 'Enter') {
665
- e.preventDefault();
666
- this.handleUnifiedEnterKey();
667
- } else if (e.key === 'Escape') {
668
- e.preventDefault();
669
- this.clearUnifiedSelection();
670
- }
671
- });
672
- }
673
-
674
- /**
675
- * Get tab name from button text
676
- */
677
- getTabNameFromButton(button) {
678
- const text = button.textContent.toLowerCase();
679
- if (text.includes('events')) return 'events';
680
- if (text.includes('agents')) return 'agents';
681
- if (text.includes('tools')) return 'tools';
682
- if (text.includes('files')) return 'files';
683
- return 'events';
684
- }
685
-
686
- /**
687
- * Initialize from URL parameters
688
- */
689
- initializeFromURL() {
690
- const urlParams = new URLSearchParams(window.location.search);
691
- const defaultPort = urlParams.get('port') || '8765';
692
- const autoConnect = urlParams.get('autoconnect');
693
-
694
- const portInput = document.getElementById('port-input');
695
- if (portInput) {
696
- portInput.value = defaultPort;
697
- }
698
-
699
- // Auto-connect logic:
700
- // - Connect if autoconnect=true (explicit)
701
- // - Connect by default unless autoconnect=false (explicit)
702
- // - Don't connect if already connected or connecting
703
- const shouldAutoConnect = autoConnect === 'true' || (autoConnect !== 'false' && autoConnect === null);
704
-
705
- if (shouldAutoConnect && !this.socketClient.isConnected && !this.socketClient.isConnecting) {
706
- console.log('Auto-connecting to Socket.IO server on page load...');
707
- this.socketClient.connect(defaultPort);
708
- }
709
- }
710
-
711
- /**
712
- * Switch to a different tab
713
- */
714
- switchTab(tabName) {
715
- console.log(`[DEBUG] switchTab called with tabName: ${tabName}`);
716
- this.currentTab = tabName;
717
-
718
- // Update tab buttons
719
- document.querySelectorAll('.tab-button').forEach(btn => {
720
- btn.classList.remove('active');
721
- if (this.getTabNameFromButton(btn) === tabName) {
722
- btn.classList.add('active');
723
- }
724
- });
725
-
726
- // Update tab content
727
- document.querySelectorAll('.tab-content').forEach(content => {
728
- content.classList.remove('active');
729
- });
730
-
731
- const activeTab = document.getElementById(`${tabName}-tab`);
732
- console.log(`[DEBUG] Active tab element found:`, activeTab);
733
- if (activeTab) {
734
- activeTab.classList.add('active');
735
- }
736
-
737
- // Render content for the active tab
738
- console.log(`[DEBUG] About to render current tab: ${tabName}`);
739
- this.renderCurrentTab();
740
-
741
- // Auto-scroll to bottom after tab content is rendered
742
- const listId = `${tabName}-list`;
743
- console.log(`[DEBUG] About to scroll list with ID: ${listId}`);
744
- this.scrollListToBottom(listId);
745
-
746
- // Fallback: Try again with longer delay in case content takes time to render
747
- setTimeout(() => {
748
- console.log(`[DEBUG] Fallback scroll attempt for ${listId}`);
749
- this.scrollListToBottom(listId);
750
- }, 200);
751
-
752
- console.log(`[DEBUG] Switched to ${tabName} tab`);
753
- }
754
-
755
- /**
756
- * Handle unified arrow key navigation across all tabs
757
- * @param {number} direction - Direction: 1 for down, -1 for up
758
- */
759
- handleUnifiedArrowNavigation(direction) {
760
- const tabNav = this.tabNavigation[this.currentTab];
761
- if (!tabNav) return;
762
-
763
- // Update items list for current tab
764
- this.updateTabNavigationItems();
765
-
766
- if (tabNav.items.length === 0) return;
767
-
768
- // Calculate new index
769
- let newIndex = tabNav.selectedIndex + direction;
770
-
771
- // Wrap around
772
- if (newIndex >= tabNav.items.length) {
773
- newIndex = 0;
774
- } else if (newIndex < 0) {
775
- newIndex = tabNav.items.length - 1;
776
- }
777
-
778
- // Update selection
779
- this.selectCardByIndex(this.currentTab, newIndex);
780
- }
781
-
782
- /**
783
- * Handle unified Enter key across all tabs
784
- */
785
- handleUnifiedEnterKey() {
786
- const tabNav = this.tabNavigation[this.currentTab];
787
- if (!tabNav || tabNav.selectedIndex === -1) return;
788
-
789
- // Trigger click on the selected item
790
- const selectedElement = tabNav.items[tabNav.selectedIndex];
791
- if (selectedElement && selectedElement.onclick) {
792
- selectedElement.click();
793
- }
794
- }
795
-
796
- /**
797
- * Clear unified selection across all tabs
798
- */
799
- clearUnifiedSelection() {
800
- // Clear all tab navigation states
801
- Object.keys(this.tabNavigation).forEach(tabName => {
802
- this.tabNavigation[tabName].selectedIndex = -1;
803
- });
804
-
805
- // Clear card selection
806
- this.clearCardSelection();
807
-
808
- // Clear EventViewer selection if it exists
809
- if (this.eventViewer) {
810
- this.eventViewer.clearSelection();
811
- }
812
-
813
- // Clear module viewer
814
- if (this.moduleViewer) {
815
- this.moduleViewer.clear();
816
- }
817
- }
818
-
819
- /**
820
- * Update items list for current tab navigation
821
- */
822
- updateTabNavigationItems() {
823
- const tabNav = this.tabNavigation[this.currentTab];
824
- if (!tabNav) return;
825
-
826
- let containerSelector;
827
- switch (this.currentTab) {
828
- case 'events':
829
- containerSelector = '#events-list .event-item';
830
- break;
831
- case 'agents':
832
- containerSelector = '#agents-list .event-item';
833
- break;
834
- case 'tools':
835
- containerSelector = '#tools-list .event-item';
836
- break;
837
- case 'files':
838
- containerSelector = '#files-list .file-item, #files-list .event-item';
839
- break;
840
- }
841
-
842
- if (containerSelector) {
843
- tabNav.items = Array.from(document.querySelectorAll(containerSelector));
844
- }
845
- }
846
-
847
- /**
848
- * Select a card by index in the specified tab
849
- * @param {string} tabName - Tab name
850
- * @param {number} index - Index of item to select
851
- */
852
- selectCardByIndex(tabName, index) {
853
- const tabNav = this.tabNavigation[tabName];
854
- if (!tabNav || index < 0 || index >= tabNav.items.length) return;
855
-
856
- // Update navigation state
857
- tabNav.selectedIndex = index;
858
-
859
- // Update visual selection
860
- this.updateUnifiedSelectionUI();
861
-
862
- // Scroll selected item into view
863
- const selectedElement = tabNav.items[index];
864
- if (selectedElement) {
865
- selectedElement.scrollIntoView({
866
- behavior: 'smooth',
867
- block: 'nearest'
868
- });
869
- }
870
-
871
- // Update details view based on tab
872
- this.showCardDetails(tabName, index);
873
- }
874
-
875
- /**
876
- * Update visual selection UI for current tab
877
- */
878
- updateUnifiedSelectionUI() {
879
- const tabNav = this.tabNavigation[this.currentTab];
880
- if (!tabNav) return;
881
-
882
- // Clear all selections in current tab
883
- tabNav.items.forEach((item, index) => {
884
- item.classList.toggle('selected', index === tabNav.selectedIndex);
885
- });
886
- }
887
-
888
- /**
889
- * Show card details based on tab and index
890
- * @param {string} tabName - Tab name
891
- * @param {number} index - Index of item
892
- */
893
- showCardDetails(tabName, index) {
894
- switch (tabName) {
895
- case 'events':
896
- // Use EventViewer's existing method
897
- if (this.eventViewer) {
898
- this.eventViewer.showEventDetails(index);
899
- }
900
- break;
901
- case 'agents':
902
- this.showAgentDetailsByIndex(index);
903
- break;
904
- case 'tools':
905
- this.showToolDetailsByIndex(index);
906
- break;
907
- case 'files':
908
- this.showFileDetailsByIndex(index);
909
- break;
910
- }
911
- }
912
-
913
- /**
914
- * Select a card and update the UI
915
- * @param {string} tabName - Tab name (events, agents, tools, files)
916
- * @param {number} index - Index of the item in that tab
917
- * @param {string} type - Type of item (event, agent, tool, file)
918
- * @param {Object} data - The data object for the selected item
919
- */
920
- selectCard(tabName, index, type, data) {
921
- // Clear previous selection
922
- this.clearCardSelection();
923
-
924
- // Update selection state
925
- this.selectedCard = {
926
- tab: tabName,
927
- index: index,
928
- type: type,
929
- data: data
930
- };
931
-
932
- // Update visual selection in the current tab
933
- this.updateCardSelectionUI();
934
-
935
- console.log('Card selected:', this.selectedCard);
936
- }
937
-
938
- /**
939
- * Clear card selection
940
- */
941
- clearCardSelection() {
942
- // Clear visual selection from all tabs
943
- document.querySelectorAll('.event-item.selected, .file-item.selected').forEach(el => {
944
- el.classList.remove('selected');
945
- });
946
-
947
- // Reset selection state
948
- this.selectedCard = {
949
- tab: null,
950
- index: null,
951
- type: null,
952
- data: null
953
- };
954
- }
955
-
956
- /**
957
- * Update visual selection in the current tab
958
- */
959
- updateCardSelectionUI() {
960
- if (!this.selectedCard.tab || this.selectedCard.index === null) return;
961
-
962
- // Get the list container for the selected tab
963
- let listContainer;
964
- switch (this.selectedCard.tab) {
965
- case 'events':
966
- listContainer = document.getElementById('events-list');
967
- break;
968
- case 'agents':
969
- listContainer = document.getElementById('agents-list');
970
- break;
971
- case 'tools':
972
- listContainer = document.getElementById('tools-list');
973
- break;
974
- case 'files':
975
- listContainer = document.getElementById('files-list');
976
- break;
977
- }
978
-
979
- if (listContainer) {
980
- const cards = listContainer.querySelectorAll('.event-item, .file-item');
981
- cards.forEach((card, index) => {
982
- card.classList.toggle('selected', index === this.selectedCard.index);
983
- });
984
- }
985
- }
986
-
987
- /**
988
- * Show agent details by index in the current filtered list
989
- * @param {number} index - Index in the filtered agents list
990
- */
991
- showAgentDetailsByIndex(index) {
992
- // Use stored filtered agent events instead of recalculating
993
- const agentEvents = this.agentEvents;
994
-
995
- if (index < 0 || index >= agentEvents.length) {
996
- console.warn('Invalid agent index:', index, 'Available agents:', agentEvents.length);
997
- return;
998
- }
999
-
1000
- const event = agentEvents[index];
1001
- const eventIndex = this.eventViewer.events.indexOf(event);
1002
- this.showAgentDetails(index, eventIndex);
1003
- }
1004
-
1005
- /**
1006
- * Show tool details by index in the current filtered list
1007
- * @param {number} index - Index in the filtered tool calls list
1008
- */
1009
- showToolDetailsByIndex(index) {
1010
- // Get filtered tool calls array (same as renderTools)
1011
- let toolCallsArray = Array.from(this.toolCalls.entries())
1012
- .filter(([key, toolCall]) => {
1013
- return toolCall.tool_name && (toolCall.pre_event || toolCall.post_event);
1014
- })
1015
- .sort((a, b) => {
1016
- const timeA = new Date(a[1].timestamp || 0);
1017
- const timeB = new Date(b[1].timestamp || 0);
1018
- return timeA - timeB;
1019
- });
1020
-
1021
- // Apply tab-specific filters
1022
- toolCallsArray = this.applyToolCallFilters(toolCallsArray);
1023
-
1024
- if (index < 0 || index >= toolCallsArray.length) return;
1025
-
1026
- const [toolCallKey] = toolCallsArray[index];
1027
- this.showToolCallDetails(toolCallKey);
1028
- }
1029
-
1030
- /**
1031
- * Show file details by index in the current filtered list
1032
- * @param {number} index - Index in the filtered files list
1033
- */
1034
- showFileDetailsByIndex(index) {
1035
- let filesArray = Array.from(this.fileOperations.entries())
1036
- .filter(([filePath, fileData]) => {
1037
- return fileData.operations && fileData.operations.length > 0;
1038
- })
1039
- .sort((a, b) => {
1040
- const timeA = a[1].lastOperation ? new Date(a[1].lastOperation) : new Date(0);
1041
- const timeB = b[1].lastOperation ? new Date(b[1].lastOperation) : new Date(0);
1042
- return timeA - timeB;
1043
- });
1044
-
1045
- filesArray = this.applyFilesFilters(filesArray);
1046
-
1047
- if (index < 0 || index >= filesArray.length) return;
1048
-
1049
- const [filePath] = filesArray[index];
1050
- this.showFileDetails(filePath);
1051
- }
1052
-
1053
- /**
1054
- * Render content for the current tab
1055
- */
1056
- renderCurrentTab() {
1057
- switch (this.currentTab) {
1058
- case 'events':
1059
- // Events are automatically rendered by EventViewer
1060
- break;
1061
- case 'agents':
1062
- this.renderAgents();
1063
- break;
1064
- case 'tools':
1065
- this.renderTools();
1066
- break;
1067
- case 'files':
1068
- this.renderFiles();
1069
- break;
1070
- }
1071
-
1072
- // Update navigation items for the current tab after rendering
1073
- this.updateTabNavigationItems();
1074
-
1075
- // Restore selection after rendering if it's in the current tab
1076
- if (this.selectedCard.tab === this.currentTab) {
1077
- this.updateCardSelectionUI();
1078
- }
1079
-
1080
- // Update unified selection UI to maintain consistency
1081
- this.updateUnifiedSelectionUI();
1082
- }
1083
-
1084
- /**
1085
- * Render agents tab
1086
- */
1087
- renderAgents() {
1088
- console.log('=== RENDERAGENTS DEBUG START ===');
1089
- console.log('1. Function called, checking agentsList element...');
1090
-
1091
- const agentsList = document.getElementById('agents-list');
1092
- if (!agentsList) {
1093
- console.error('agentsList element not found!');
1094
- return;
1095
- }
1096
- console.log('2. agentsList element found:', agentsList);
1097
-
1098
- const events = this.getFilteredEventsForTab('agents');
1099
- console.log('3. Total events from getFilteredEventsForTab:', events.length);
1100
-
1101
- // Enhanced debugging: log first few events to understand structure
1102
- if (events.length > 0) {
1103
- console.log('Agent tab - sample events for analysis:');
1104
- events.slice(0, 3).forEach((event, i) => {
1105
- console.log(` Event ${i}:`, {
1106
- type: event.type,
1107
- subtype: event.subtype,
1108
- tool_name: event.tool_name,
1109
- agent_type: event.agent_type,
1110
- subagent_type: event.subagent_type,
1111
- tool_parameters: event.tool_parameters,
1112
- delegation_details: event.delegation_details,
1113
- data: event.data ? {
1114
- agent_type: event.data.agent_type,
1115
- subagent_type: event.data.subagent_type,
1116
- event_type: event.data.event_type,
1117
- tool_name: event.data.tool_name,
1118
- tool_parameters: event.data.tool_parameters,
1119
- delegation_details: event.data.delegation_details
1120
- } : 'no data field'
1121
- });
1122
- });
1123
-
1124
- // Count events by type and tool_name for debugging
1125
- const eventCounts = {};
1126
- const toolCounts = {};
1127
- const agentCounts = {};
1128
- events.forEach(event => {
1129
- const key = `${event.type}.${event.subtype || 'none'}`;
1130
- eventCounts[key] = (eventCounts[key] || 0) + 1;
1131
-
1132
- if (event.tool_name) {
1133
- toolCounts[event.tool_name] = (toolCounts[event.tool_name] || 0) + 1;
1134
- }
1135
-
1136
- // Count agent types from multiple sources
1137
- const agentTypes = [];
1138
- if (event.agent_type) agentTypes.push(`direct:${event.agent_type}`);
1139
- if (event.subagent_type) agentTypes.push(`sub:${event.subagent_type}`);
1140
- if (event.tool_parameters?.subagent_type) agentTypes.push(`tool_param:${event.tool_parameters.subagent_type}`);
1141
- if (event.data?.agent_type) agentTypes.push(`data:${event.data.agent_type}`);
1142
- if (event.data?.subagent_type) agentTypes.push(`data_sub:${event.data.subagent_type}`);
1143
- if (event.data?.delegation_details?.agent_type) agentTypes.push(`delegation:${event.data.delegation_details.agent_type}`);
1144
-
1145
- agentTypes.forEach(agentType => {
1146
- agentCounts[agentType] = (agentCounts[agentType] || 0) + 1;
1147
- });
1148
- });
1149
- console.log('Agent tab - event type breakdown:', eventCounts);
1150
- console.log('Agent tab - tool breakdown:', toolCounts);
1151
- console.log('Agent tab - agent type breakdown:', agentCounts);
1152
- }
1153
-
1154
- // Use agent inference to filter events instead of hardcoded logic
1155
- let agentEvents = events
1156
- .map((event, index) => ({ event, index, inference: this.inferAgentFromEvent(event) }))
1157
- .filter(({ event, index, inference }) => {
1158
- // Show events that have meaningful agent context
1159
- if (!inference) return false;
1160
-
1161
- // Include events that are definitely agent-related
1162
- const isAgentRelated = inference.type === 'subagent' ||
1163
- (inference.type === 'main_agent' && inference.confidence !== 'default') ||
1164
- event.tool_name === 'Task' ||
1165
- event.hook_event_name === 'SubagentStop' ||
1166
- event.subtype === 'subagent_stop';
1167
-
1168
- // Debug first few events
1169
- if (index < 5) {
1170
- console.log(`Agent filter [${index}] - ${isAgentRelated ? 'MATCHED' : 'SKIPPED'}:`, {
1171
- type: event.type,
1172
- subtype: event.subtype,
1173
- tool_name: event.tool_name,
1174
- inference: inference,
1175
- isAgentRelated
1176
- });
1177
- }
1178
-
1179
- return isAgentRelated;
1180
- })
1181
- .map(({ event, inference }) => ({ event, inference }))
1182
- .sort((a, b) => new Date(a.event.timestamp) - new Date(b.event.timestamp));
1183
-
1184
- // Extract unique agent types from the data for filter dropdown
1185
- const uniqueAgentTypes = new Set();
1186
- agentEvents.forEach(({ event, inference }) => {
1187
- if (inference && inference.agentName && inference.agentName !== 'Unknown') {
1188
- uniqueAgentTypes.add(inference.agentName);
1189
- }
1190
- // Also check for agent_type in the event data
1191
- if (event.agent_type && event.agent_type !== 'unknown' && event.agent_type !== 'main') {
1192
- uniqueAgentTypes.add(event.agent_type);
1193
- }
1194
- if (event.subagent_type) {
1195
- uniqueAgentTypes.add(event.subagent_type);
1196
- }
1197
- });
1198
-
1199
- // Populate the agents filter dropdown
1200
- this.populateFilterDropdown('agents-type-filter', Array.from(uniqueAgentTypes), 'All Agents');
1201
-
1202
- // Apply tab-specific filters to the agentEvents array while preserving inference data
1203
- let filteredAgentEvents = agentEvents.filter(({ event }) => {
1204
- // Create a temporary array with just events for the existing filter function
1205
- const singleEventArray = [event];
1206
- const filteredSingleEvent = this.applyAgentsFilters(singleEventArray);
1207
- return filteredSingleEvent.length > 0;
1208
- });
1209
-
1210
- // Store filtered agent events with inference data in class property
1211
- this.agentEventsWithInference = filteredAgentEvents;
1212
-
1213
- // Also store just the events for backward compatibility
1214
- this.agentEvents = filteredAgentEvents.map(({ event }) => event);
1215
-
1216
- console.log('4. Agent tab - filtering summary:', {
1217
- total_events: events.length,
1218
- agent_events_found: filteredAgentEvents.length,
1219
- percentage: filteredAgentEvents.length > 0 ? ((filteredAgentEvents.length / events.length) * 100).toFixed(1) + '%' : '0%'
1220
- });
1221
-
1222
- if (filteredAgentEvents.length === 0) {
1223
- console.log('5. No agent events found, showing empty message');
1224
- agentsList.innerHTML = '<div class="no-events">No agent events found...</div>';
1225
- return;
1226
- }
1227
-
1228
- console.log('Rendering', filteredAgentEvents.length, 'agent events');
1229
-
1230
- const agentsHtml = filteredAgentEvents.map(({ event, inference }, index) => {
1231
- const timestamp = new Date(event.timestamp).toLocaleTimeString();
1232
-
1233
- let agentName = inference ? inference.agentName : 'Unknown';
1234
- let operation = 'operation';
1235
- let prompt = '';
1236
- let description = '';
1237
- let taskPreview = '';
1238
- let confidence = inference ? inference.confidence : 'unknown';
1239
- let reason = inference ? inference.reason : 'no inference';
1240
-
1241
- // Extract Task tool information if present
1242
- const data = event.data || {};
1243
- if (event.tool_name === 'Task' || data.tool_name === 'Task') {
1244
- operation = 'delegation';
1245
-
1246
- // Try different sources for Task tool data
1247
- const taskParams = event.tool_parameters || data.tool_parameters || data.delegation_details || {};
1248
-
1249
- if (taskParams.prompt) {
1250
- prompt = taskParams.prompt;
1251
- taskPreview = prompt.length > 200 ? prompt.substring(0, 200) + '...' : prompt;
1252
- }
1253
- if (taskParams.description) {
1254
- description = taskParams.description;
1255
- }
1256
- }
1257
-
1258
- // Extract operation from event type/subtype
1259
- if (event.subtype) {
1260
- operation = event.subtype.replace(/_/g, ' ');
1261
- } else {
1262
- operation = this.extractOperation(event.type) || 'operation';
1263
- }
1264
-
1265
- // Add confidence indicator
1266
- const confidenceIcon = {
1267
- 'definitive': '๐ŸŽฏ',
1268
- 'high': 'โœ…',
1269
- 'medium': 'โš ๏ธ',
1270
- 'inherited': '๐Ÿ“‹',
1271
- 'default': 'โ“',
1272
- 'unknown': 'โ”'
1273
- }[confidence] || 'โ”';
1274
-
1275
- const onclickString = `dashboard.selectCard('agents', ${index}, 'agent', ${index}); dashboard.showAgentDetailsByIndex(${index});`;
1276
-
1277
- return `
1278
- <div class="event-item event-agent" onclick="${onclickString}">
1279
- <div class="event-header">
1280
- <span class="event-type">๐Ÿค– ${agentName}</span>
1281
- <span class="confidence-indicator" title="Confidence: ${confidence} (${reason})">${confidenceIcon}</span>
1282
- <span class="event-timestamp">${timestamp}</span>
1283
- </div>
1284
- <div class="event-data">
1285
- <strong>Operation:</strong> ${operation}
1286
- <strong>Inference:</strong> ${inference ? inference.type : 'unknown'} (${confidence})
1287
- ${taskPreview ? `<br><strong>Task Preview:</strong> ${taskPreview}` : ''}
1288
- ${description ? `<br><strong>Description:</strong> ${description}` : ''}
1289
- ${event.session_id || data.session_id ? `<br><strong>Session:</strong> ${(event.session_id || data.session_id).substring(0, 8)}...` : ''}
1290
- </div>
1291
- </div>
1292
- `;
1293
- }).join('');
1294
-
1295
- console.log('9. Generated HTML length:', agentsHtml.length);
1296
- console.log('10. Sample HTML (first 500 chars):', agentsHtml.substring(0, 500));
1297
-
1298
- agentsList.innerHTML = agentsHtml;
1299
-
1300
- // Check if the HTML was actually set
1301
- console.log('11. HTML set in DOM, innerHTML length:', agentsList.innerHTML.length);
1302
- console.log('12. Number of event-agent elements:', agentsList.querySelectorAll('.event-agent').length);
1303
-
1304
- // Test onclick on first element if exists
1305
- const firstAgent = agentsList.querySelector('.event-agent');
1306
- if (firstAgent) {
1307
- console.log('13. First agent element found:', firstAgent);
1308
- console.log('14. First agent onclick attribute:', firstAgent.getAttribute('onclick'));
1309
-
1310
- // Add a test click event listener as well
1311
- firstAgent.addEventListener('click', function(e) {
1312
- console.log('15. CLICK EVENT DETECTED on agent element!', e.target);
1313
- });
1314
- } else {
1315
- console.log('13. No .event-agent elements found in DOM after setting innerHTML');
1316
- }
1317
-
1318
- console.log('=== RENDERAGENTS DEBUG END ===');
1319
- this.scrollListToBottom('agents-list');
1320
- }
1321
-
1322
- /**
1323
- * Render tools tab - shows paired tool calls instead of individual events
1324
- */
1325
- renderTools() {
1326
- const toolsList = document.getElementById('tools-list');
1327
- if (!toolsList) return;
1328
-
1329
- console.log('Tools tab - total tool calls:', this.toolCalls.size);
1330
-
1331
- if (this.toolCalls.size === 0) {
1332
- toolsList.innerHTML = '<div class="no-events">No tool calls found...</div>';
1333
- return;
1334
- }
1335
-
1336
- // Convert to array and sort by timestamp
1337
- let toolCallsArray = Array.from(this.toolCalls.entries())
1338
- .filter(([key, toolCall]) => {
1339
- // Ensure we have valid data
1340
- return toolCall.tool_name && (toolCall.pre_event || toolCall.post_event);
1341
- })
1342
- .sort((a, b) => {
1343
- const timeA = new Date(a[1].timestamp || 0);
1344
- const timeB = new Date(b[1].timestamp || 0);
1345
- return timeA - timeB;
1346
- });
1347
-
1348
- console.log('Tools tab - after filtering:', toolCallsArray.length, 'tool calls');
1349
-
1350
- // Extract unique tool names from the data for filter dropdown
1351
- const uniqueToolNames = new Set();
1352
- toolCallsArray.forEach(([key, toolCall]) => {
1353
- if (toolCall.tool_name) {
1354
- uniqueToolNames.add(toolCall.tool_name);
1355
- }
1356
- });
1357
-
1358
- // Populate the tools filter dropdown
1359
- this.populateFilterDropdown('tools-type-filter', Array.from(uniqueToolNames), 'All Tools');
1360
-
1361
- // Apply tab-specific filters to tool calls
1362
- toolCallsArray = this.applyToolCallFilters(toolCallsArray);
1363
-
1364
- console.log('Tools tab - after search/type filters:', toolCallsArray.length, 'tool calls');
1365
-
1366
- if (toolCallsArray.length === 0) {
1367
- toolsList.innerHTML = '<div class="no-events">No tool calls match current filters...</div>';
1368
- return;
1369
- }
1370
-
1371
- const toolsHtml = toolCallsArray.map(([key, toolCall], index) => {
1372
- const timestamp = new Date(toolCall.timestamp).toLocaleTimeString();
1373
- const toolName = toolCall.tool_name || 'Unknown Tool';
1374
-
1375
- // Use inferred agent data instead of hardcoded 'PM'
1376
- let agentName = 'PM';
1377
- let confidence = 'default';
1378
-
1379
- // Try to get inference from pre_event first, then post_event
1380
- const preEvent = toolCall.pre_event;
1381
- const postEvent = toolCall.post_event;
1382
-
1383
- if (preEvent) {
1384
- const eventIndex = this.eventViewer.events.indexOf(preEvent);
1385
- const inference = this.getInferredAgent(eventIndex);
1386
- if (inference) {
1387
- agentName = inference.agentName;
1388
- confidence = inference.confidence;
1389
- }
1390
- } else if (postEvent) {
1391
- const eventIndex = this.eventViewer.events.indexOf(postEvent);
1392
- const inference = this.getInferredAgent(eventIndex);
1393
- if (inference) {
1394
- agentName = inference.agentName;
1395
- confidence = inference.confidence;
1396
- }
1397
- }
1398
-
1399
- // Fallback to existing logic if no inference available
1400
- if (agentName === 'PM' && confidence === 'default') {
1401
- agentName = toolCall.agent_type || 'PM';
1402
- }
1403
-
1404
- // Extract tool target/parameters from pre_event
1405
- const target = preEvent ? this.extractToolTarget(toolName, preEvent.tool_parameters, preEvent.tool_parameters) : 'Unknown target';
1406
-
1407
- // Determine status and duration
1408
- let statusInfo = '';
1409
- let statusClass = '';
1410
-
1411
- if (toolCall.post_event) {
1412
- // We have completion data
1413
- const duration = toolCall.duration_ms ? `${toolCall.duration_ms}ms` : 'Unknown duration';
1414
- const success = toolCall.success !== undefined ? toolCall.success : 'Unknown';
1415
-
1416
- if (success === true) {
1417
- statusInfo = `โœ… Success (${duration})`;
1418
- statusClass = 'tool-success';
1419
- } else if (success === false) {
1420
- statusInfo = `โŒ Failed (${duration})`;
1421
- statusClass = 'tool-failure';
1422
- } else {
1423
- statusInfo = `โณ Completed (${duration})`;
1424
- statusClass = 'tool-completed';
1425
- }
1426
- } else {
1427
- // Only pre_event - still running or incomplete
1428
- statusInfo = 'โณ Running...';
1429
- statusClass = 'tool-running';
1430
- }
1431
-
1432
- // Add confidence indicator for agent inference
1433
- const confidenceIcon = {
1434
- 'definitive': '๐ŸŽฏ',
1435
- 'high': 'โœ…',
1436
- 'medium': 'โš ๏ธ',
1437
- 'inherited': '๐Ÿ“‹',
1438
- 'default': 'โ“',
1439
- 'unknown': 'โ”'
1440
- }[confidence] || 'โ”';
1441
-
1442
- return `
1443
- <div class="event-item event-tool ${statusClass}" onclick="dashboard.selectCard('tools', ${index}, 'toolCall', '${key}'); dashboard.showToolCallDetails('${key}')">
1444
- <div class="event-header">
1445
- <span class="event-type">๐Ÿ”ง ${toolName}</span>
1446
- <span class="event-timestamp">${timestamp}</span>
1447
- </div>
1448
- <div class="event-data">
1449
- <strong>Agent:</strong> ${agentName} (${confidence})<br>
1450
- <strong>Status:</strong> ${statusInfo}<br>
1451
- <strong>Target:</strong> ${target}
1452
- ${toolCall.session_id ? `<br><strong>Session:</strong> ${toolCall.session_id.substring(0, 8)}...` : ''}
1453
- </div>
1454
- </div>
1455
- `;
1456
- }).join('');
1457
-
1458
- toolsList.innerHTML = toolsHtml;
1459
- this.scrollListToBottom('tools-list');
1460
- }
1461
-
1462
- /**
1463
- * Render files tab with file-centric view
1464
- */
1465
- renderFiles() {
1466
- const filesList = document.getElementById('files-list');
1467
- if (!filesList) return;
1468
-
1469
- console.log('Files tab - file operations:', this.fileOperations.size);
1470
- console.log('Files tab - operations map:', this.fileOperations);
1471
-
1472
- if (this.fileOperations.size === 0) {
1473
- filesList.innerHTML = '<div class="no-events">No file operations found...</div>';
1474
- return;
1475
- }
1476
-
1477
- // Convert to array and sort by most recent operations at bottom (chronological order)
1478
- let filesArray = Array.from(this.fileOperations.entries())
1479
- .filter(([filePath, fileData]) => {
1480
- // Ensure we have valid data
1481
- return fileData.operations && fileData.operations.length > 0;
1482
- })
1483
- .sort((a, b) => {
1484
- const timeA = a[1].lastOperation ? new Date(a[1].lastOperation) : new Date(0);
1485
- const timeB = b[1].lastOperation ? new Date(b[1].lastOperation) : new Date(0);
1486
- return timeA - timeB;
1487
- });
1488
-
1489
- console.log('Files tab - after filtering:', filesArray.length, 'files');
1490
-
1491
- // Extract unique operations from the data for filter dropdown
1492
- const uniqueOperations = new Set();
1493
- filesArray.forEach(([filePath, fileData]) => {
1494
- if (fileData.operations && fileData.operations.length > 0) {
1495
- fileData.operations.forEach(operation => {
1496
- if (operation.operation) {
1497
- uniqueOperations.add(operation.operation);
1498
- }
1499
- });
1500
- }
1501
- });
1502
-
1503
- // Populate the files filter dropdown
1504
- this.populateFilterDropdown('files-type-filter', Array.from(uniqueOperations), 'All Operations');
1505
-
1506
- // Apply tab-specific filters
1507
- filesArray = this.applyFilesFilters(filesArray);
1508
-
1509
- console.log('Files tab - after search/type filters:', filesArray.length, 'files');
1510
-
1511
- if (filesArray.length === 0) {
1512
- filesList.innerHTML = '<div class="no-events">No files match current filters...</div>';
1513
- return;
1514
- }
1515
-
1516
- const filesHtml = filesArray.map(([filePath, fileData], index) => {
1517
- if (!fileData.operations || fileData.operations.length === 0) {
1518
- console.warn('File with no operations:', filePath);
1519
- return '';
1520
- }
1521
-
1522
- const icon = this.getFileOperationIcon(fileData.operations);
1523
- const lastOp = fileData.operations[fileData.operations.length - 1];
1524
- const timestamp = new Date(lastOp.timestamp).toLocaleTimeString();
1525
-
1526
- // Get unique operations as text, joined with |
1527
- const uniqueOperations = [...new Set(fileData.operations.map(op => op.operation))];
1528
- const operationsText = uniqueOperations.join('|');
1529
-
1530
- return `
1531
- <div class="event-item file-item" onclick="dashboard.selectCard('files', ${index}, 'file', '${filePath}'); dashboard.showFileDetails('${filePath}')">
1532
- <div class="event-header">
1533
- <span class="event-type">${icon}</span>
1534
- <span class="file-path">${this.getRelativeFilePath(filePath)}</span>
1535
- <span class="event-timestamp">${timestamp}</span>
1536
- </div>
1537
- <div class="event-data">
1538
- <strong>Operations:</strong> ${operationsText}<br>
1539
- <strong>Agent:</strong> ${lastOp.agent} ${lastOp.confidence ? `(${lastOp.confidence})` : ''}
1540
- </div>
1541
- </div>
1542
- `;
1543
- }).join('');
1544
-
1545
- filesList.innerHTML = filesHtml;
1546
- this.scrollListToBottom('files-list');
1547
- }
1548
-
1549
- /**
1550
- * Show agent details in module viewer
1551
- */
1552
- showAgentDetails(agentIndex, eventIndex) {
1553
- console.log('showAgentDetails called with agentIndex:', agentIndex, 'eventIndex:', eventIndex);
1554
-
1555
- // Use stored filtered agent events with inference data if available
1556
- const agentEventsWithInference = this.agentEventsWithInference || [];
1557
- const agentEvents = this.agentEvents;
1558
-
1559
- let event, inference;
1560
-
1561
- // Try to get event and inference data together
1562
- if (agentEventsWithInference[agentIndex]) {
1563
- event = agentEventsWithInference[agentIndex].event;
1564
- inference = agentEventsWithInference[agentIndex].inference;
1565
- } else if (agentEvents[agentIndex]) {
1566
- // Fallback to just event data
1567
- event = agentEvents[agentIndex];
1568
- inference = null;
1569
- } else {
1570
- return;
1571
- }
1572
-
1573
- // Extract agent information using inference data first, then fallback to event data
1574
- let agentName = 'Unknown Agent';
1575
- let prompt = '';
1576
- let description = '';
1577
- let fullPrompt = '';
1578
-
1579
- // Use inference data for agent name if available
1580
- if (inference && inference.agentName && inference.agentName !== 'Unknown') {
1581
- agentName = inference.agentName;
1582
- } else if (event.tool_name === 'Task' && event.tool_parameters?.subagent_type) {
1583
- agentName = event.tool_parameters.subagent_type;
1584
- } else if (event.subagent_type) {
1585
- agentName = event.subagent_type;
1586
- } else if (event.agent_type && event.agent_type !== 'unknown') {
1587
- agentName = event.agent_type;
1588
- }
1589
-
1590
- // Extract task information
1591
- if (event.tool_name === 'Task' && event.tool_parameters) {
1592
- prompt = event.tool_parameters.prompt || '';
1593
- description = event.tool_parameters.description || '';
1594
- fullPrompt = prompt;
1595
- }
1596
-
1597
- // Add debug logging
1598
- console.log('showAgentDetails called with:', {
1599
- agentIndex,
1600
- eventIndex,
1601
- event,
1602
- inference,
1603
- agentName: agentName,
1604
- hasInferenceData: !!inference
1605
- });
1606
- console.log('moduleViewer available:', !!this.moduleViewer);
1607
-
1608
- // Create enhanced event object with inference data for module viewer
1609
- const enhancedEvent = {
1610
- ...event,
1611
- _inference: inference, // Add inference data as a private property
1612
- _agentName: agentName // Add resolved agent name
1613
- };
1614
-
1615
- // Use the module viewer's ingest method to properly display the agent event
1616
- if (this.moduleViewer) {
1617
- console.log('Calling moduleViewer.ingest with enhanced event:', enhancedEvent);
1618
- this.moduleViewer.ingest(enhancedEvent);
1619
- }
1620
-
1621
- // Also show the event details in EventViewer
1622
- if (eventIndex >= 0) {
1623
- this.eventViewer.showEventDetails(eventIndex);
1624
- }
1625
- }
1626
-
1627
- /**
1628
- * Toggle prompt expansion
1629
- */
1630
- togglePromptExpansion(button) {
1631
- const promptDiv = button.parentElement.previousElementSibling;
1632
- const isExpanded = promptDiv.style.maxHeight !== '300px';
1633
-
1634
- if (isExpanded) {
1635
- promptDiv.style.maxHeight = '300px';
1636
- button.textContent = 'Show More';
1637
- } else {
1638
- promptDiv.style.maxHeight = 'none';
1639
- button.textContent = 'Show Less';
1640
- }
1641
- }
1642
-
1643
- /**
1644
- * Show tool details in module viewer
1645
- */
1646
- showToolDetails(toolIndex, eventIndex) {
1647
- // Get the tool event
1648
- const events = this.getFilteredEventsForTab('tools');
1649
- const toolEvents = this.applyToolsFilters(events.filter(event => {
1650
- const type = event.type || '';
1651
- const subtype = event.subtype || '';
1652
-
1653
- const isHookToolEvent = type === 'hook' && (
1654
- subtype.includes('tool') ||
1655
- subtype.includes('pre_') ||
1656
- subtype.includes('post_')
1657
- );
1658
- const hasToolName = event.tool_name;
1659
- const hasToolsArray = event.tools && Array.isArray(event.tools);
1660
- const isLegacyHookEvent = type.startsWith('hook.') && (
1661
- type.includes('tool') ||
1662
- type.includes('pre') ||
1663
- type.includes('post')
1664
- );
1665
-
1666
- return isHookToolEvent || hasToolName || hasToolsArray || isLegacyHookEvent;
1667
- }));
1668
-
1669
- const event = toolEvents[toolIndex];
1670
- if (!event) return;
1671
-
1672
- // Extract tool information
1673
- let toolName = event.tool_name || 'Unknown Tool';
1674
- if (event.tools && Array.isArray(event.tools) && event.tools.length > 0) {
1675
- toolName = event.tools[0];
1676
- }
1677
-
1678
- let agentName = 'PM';
1679
- if (event.subagent_type) {
1680
- agentName = event.subagent_type;
1681
- } else if (event.agent_type && event.agent_type !== 'main' && event.agent_type !== 'unknown') {
1682
- agentName = event.agent_type;
1683
- }
1684
-
1685
- const target = this.extractToolTarget(toolName, event.tool_parameters, event.tool_parameters);
1686
-
1687
- let operation = 'execution';
1688
- if (event.subtype) {
1689
- if (event.subtype.includes('pre_')) {
1690
- operation = 'pre-execution';
1691
- } else if (event.subtype.includes('post_')) {
1692
- operation = 'post-execution';
1693
- } else {
1694
- operation = event.subtype.replace(/_/g, ' ');
1695
- }
1696
- }
1697
-
1698
- const content = `
1699
- <div class="structured-view-section">
1700
- <div class="structured-view-header">
1701
- <h4>๐Ÿ”ง Tool Details</h4>
1702
- </div>
1703
- <div class="tool-details">
1704
- <div class="tool-info">
1705
- <div class="structured-field">
1706
- <strong>Tool Name:</strong> ${toolName}
1707
- </div>
1708
- <div class="structured-field">
1709
- <strong>Agent:</strong> ${agentName}
1710
- </div>
1711
- <div class="structured-field">
1712
- <strong>Operation:</strong> ${operation}
1713
- </div>
1714
- <div class="structured-field">
1715
- <strong>Target:</strong> ${target}
1716
- </div>
1717
- <div class="structured-field">
1718
- <strong>Timestamp:</strong> ${new Date(event.timestamp).toLocaleString()}
1719
- </div>
1720
- <div class="structured-field">
1721
- <strong>Event Type:</strong> ${event.type}.${event.subtype || 'default'}
1722
- </div>
1723
- ${event.session_id ? `
1724
- <div class="structured-field">
1725
- <strong>Session ID:</strong> ${event.session_id}
1726
- </div>
1727
- ` : ''}
1728
- </div>
1729
-
1730
- ${event.tool_parameters ? `
1731
- <div class="parameters-section">
1732
- <div class="structured-view-header">
1733
- <h4>โš™๏ธ Parameters</h4>
1734
- </div>
1735
- <div class="structured-data">
1736
- <pre style="white-space: pre-wrap; font-family: monospace; font-size: 12px; line-height: 1.4;">${JSON.stringify(event.tool_parameters, null, 2)}</pre>
1737
- </div>
1738
- </div>
1739
- ` : ''}
1740
- </div>
1741
- </div>
1742
- `;
1743
-
1744
- // Use the new dual-pane approach for tools
1745
- if (this.moduleViewer.dataContainer) {
1746
- this.moduleViewer.dataContainer.innerHTML = content;
1747
- }
1748
- if (this.moduleViewer.jsonContainer && event) {
1749
- this.moduleViewer.jsonContainer.innerHTML = `<pre>${JSON.stringify(event, null, 2)}</pre>`;
1750
- }
1751
-
1752
- // Also show the event details in EventViewer
1753
- if (eventIndex >= 0) {
1754
- this.eventViewer.showEventDetails(eventIndex);
1755
- }
1756
- }
1757
-
1758
- /**
1759
- * Show tool call details in module viewer with combined pre/post data
1760
- */
1761
- showToolCallDetails(toolCallKey) {
1762
- const toolCall = this.toolCalls.get(toolCallKey);
1763
- if (!toolCall) return;
1764
-
1765
- const toolName = toolCall.tool_name || 'Unknown Tool';
1766
- const agentName = toolCall.agent_type || 'PM';
1767
- const timestamp = new Date(toolCall.timestamp).toLocaleString();
1768
-
1769
- // Extract information from pre and post events
1770
- const preEvent = toolCall.pre_event;
1771
- const postEvent = toolCall.post_event;
1772
-
1773
- // Get parameters from pre-event
1774
- const parameters = preEvent?.tool_parameters || {};
1775
- const target = preEvent ? this.extractToolTarget(toolName, parameters, parameters) : 'Unknown target';
1776
-
1777
- // Get execution results from post-event
1778
- const duration = toolCall.duration_ms ? `${toolCall.duration_ms}ms` : '-';
1779
- const success = toolCall.success !== undefined ? toolCall.success : null;
1780
- const exitCode = toolCall.exit_code !== undefined ? toolCall.exit_code : null;
1781
- // Format result summary properly if it's an object
1782
- let resultSummary = toolCall.result_summary || 'No summary available';
1783
- let formattedResultSummary = '';
1784
-
1785
- if (typeof resultSummary === 'object' && resultSummary !== null) {
1786
- // Format the result summary object into human-readable text
1787
- const parts = [];
1788
-
1789
- if (resultSummary.exit_code !== undefined) {
1790
- parts.push(`Exit Code: ${resultSummary.exit_code}`);
1791
- }
1792
-
1793
- if (resultSummary.has_output !== undefined) {
1794
- parts.push(`Has Output: ${resultSummary.has_output ? 'Yes' : 'No'}`);
1795
- }
1796
-
1797
- if (resultSummary.has_error !== undefined) {
1798
- parts.push(`Has Error: ${resultSummary.has_error ? 'Yes' : 'No'}`);
1799
- }
1800
-
1801
- if (resultSummary.output_lines !== undefined) {
1802
- parts.push(`Output Lines: ${resultSummary.output_lines}`);
1803
- }
1804
-
1805
- if (resultSummary.output_preview) {
1806
- parts.push(`Output Preview: ${resultSummary.output_preview}`);
1807
- }
1808
-
1809
- if (resultSummary.error_preview) {
1810
- parts.push(`Error Preview: ${resultSummary.error_preview}`);
1811
- }
1812
-
1813
- formattedResultSummary = parts.join('\n');
1814
- } else {
1815
- formattedResultSummary = String(resultSummary);
1816
- }
1817
-
1818
- // Status information
1819
- let statusIcon = 'โณ';
1820
- let statusText = 'Running...';
1821
- let statusClass = 'tool-running';
1822
-
1823
- if (postEvent) {
1824
- if (success === true) {
1825
- statusIcon = 'โœ…';
1826
- statusText = 'Success';
1827
- statusClass = 'tool-success';
1828
- } else if (success === false) {
1829
- statusIcon = 'โŒ';
1830
- statusText = 'Failed';
1831
- statusClass = 'tool-failure';
1832
- } else {
1833
- statusIcon = 'โณ';
1834
- statusText = 'Completed';
1835
- statusClass = 'tool-completed';
1836
- }
1837
- }
1838
-
1839
- const content = `
1840
- <div class="structured-view-section">
1841
- <div class="structured-view-header">
1842
- <h4>๐Ÿ”ง Tool Call Details</h4>
1843
- </div>
1844
- <div class="tool-call-details">
1845
- <div class="tool-call-info ${statusClass}">
1846
- <div class="structured-field">
1847
- <strong>Tool Name:</strong> ${toolName}
1848
- </div>
1849
- <div class="structured-field">
1850
- <strong>Agent:</strong> ${agentName}
1851
- </div>
1852
- <div class="structured-field">
1853
- <strong>Status:</strong> ${statusIcon} ${statusText}
1854
- </div>
1855
- <div class="structured-field">
1856
- <strong>Target:</strong> ${target}
1857
- </div>
1858
- <div class="structured-field">
1859
- <strong>Started:</strong> ${timestamp}
1860
- </div>
1861
- <div class="structured-field">
1862
- <strong>Duration:</strong> ${duration}
1863
- </div>
1864
- ${success !== null ? `
1865
- <div class="structured-field">
1866
- <strong>Success:</strong> ${success}
1867
- </div>
1868
- ` : ''}
1869
- ${exitCode !== null ? `
1870
- <div class="structured-field">
1871
- <strong>Exit Code:</strong> ${exitCode}
1872
- </div>
1873
- ` : ''}
1874
- ${toolCall.session_id ? `
1875
- <div class="structured-field">
1876
- <strong>Session ID:</strong> ${toolCall.session_id}
1877
- </div>
1878
- ` : ''}
1879
- </div>
1880
-
1881
- ${formattedResultSummary && formattedResultSummary !== 'No summary available' ? `
1882
- <div class="result-section">
1883
- <div class="structured-view-header">
1884
- <h4>๐Ÿ“Š Result Summary</h4>
1885
- </div>
1886
- <div class="structured-data">
1887
- <div class="result-summary" style="white-space: pre-wrap; max-height: 200px; overflow-y: auto; padding: 10px; background: #f8fafc; border-radius: 6px; font-family: monospace; font-size: 12px; line-height: 1.4;">
1888
- ${formattedResultSummary}
1889
- </div>
1890
- ${typeof resultSummary === 'object' && resultSummary !== null ? `
1891
- <div class="result-summary-json" style="white-space: pre-wrap; max-height: 200px; overflow-y: auto; padding: 10px; background: #f0f9ff; border-radius: 6px; font-family: monospace; font-size: 11px; line-height: 1.3; margin-top: 10px;">
1892
- <h5 style="margin: 0 0 8px 0; font-size: 11px; color: #4a5568;">Raw JSON:</h5>
1893
- ${JSON.stringify(resultSummary, null, 2)}
1894
- </div>
1895
- ` : ''}
1896
- </div>
1897
- </div>
1898
- ` : ''}
1899
-
1900
- ${toolName === 'TodoWrite' && parameters.todos ? `
1901
- <div class="todos-section">
1902
- <div class="todos-list">
1903
- ${parameters.todos.map(todo => {
1904
- const statusIcon = {
1905
- 'pending': 'โณ',
1906
- 'in_progress': '๐Ÿ”„',
1907
- 'completed': 'โœ…'
1908
- }[todo.status] || 'โ“';
1909
-
1910
- const priorityColor = {
1911
- 'high': '#dc2626',
1912
- 'medium': '#f59e0b',
1913
- 'low': '#10b981'
1914
- }[todo.priority] || '#6b7280';
1915
-
1916
- return `
1917
- <div class="todo-item" style="padding: 8px; margin: 4px 0; border-left: 3px solid ${priorityColor}; background: #f8fafc; border-radius: 4px;">
1918
- <div style="display: flex; align-items: center; gap: 8px;">
1919
- <span style="font-size: 16px;">${statusIcon}</span>
1920
- <span style="font-weight: 500; color: #374151;">${todo.content}</span>
1921
- <span style="font-size: 11px; color: ${priorityColor}; text-transform: uppercase; font-weight: 600; margin-left: auto;">${todo.priority}</span>
1922
- </div>
1923
- </div>
1924
- `;
1925
- }).join('')}
1926
- </div>
1927
- </div>
1928
- ` : ''}
1929
-
1930
- ${Object.keys(parameters).length > 0 && toolName !== 'TodoWrite' ? `
1931
- <div class="parameters-section">
1932
- <div class="structured-view-header">
1933
- <h4>โš™๏ธ Parameters</h4>
1934
- </div>
1935
- <div class="structured-data">
1936
- <pre style="white-space: pre-wrap; font-family: monospace; font-size: 12px; line-height: 1.4;">${JSON.stringify(parameters, null, 2)}</pre>
1937
- </div>
1938
- </div>
1939
- ` : ''}
1940
-
1941
- <div class="raw-data-section">
1942
- <div class="structured-view-header">
1943
- <h4>๐Ÿ”ง JSON Event Data</h4>
1944
- </div>
1945
- <div class="structured-data">
1946
- <div style="margin-bottom: 15px;">
1947
- <strong>Pre-execution Event:</strong>
1948
- <pre style="white-space: pre-wrap; font-family: monospace; font-size: 11px; line-height: 1.3; background: #f0f9ff; padding: 8px; border-radius: 4px; max-height: 300px; overflow-y: auto;">${preEvent ? JSON.stringify(preEvent, null, 2) : 'No pre-event data'}</pre>
1949
- </div>
1950
- <div>
1951
- <strong>Post-execution Event:</strong>
1952
- <pre style="white-space: pre-wrap; font-family: monospace; font-size: 11px; line-height: 1.3; background: #f0f9ff; padding: 8px; border-radius: 4px; max-height: 300px; overflow-y: auto;">${postEvent ? JSON.stringify(postEvent, null, 2) : 'No post-event data'}</pre>
1953
- </div>
1954
- </div>
1955
- </div>
1956
- </div>
1957
- </div>
1958
- `;
1959
-
1960
- // Special handling for TodoWrite - show only checklist with standard header
1961
- if (toolName === 'TodoWrite' && parameters.todos) {
1962
- // Create contextual header matching module-viewer pattern
1963
- const contextualHeader = `
1964
- <div class="contextual-header">
1965
- <h3 class="contextual-header-text">TodoWrite: ${agentName} ${this.formatTimestamp(toolCall.timestamp)}</h3>
1966
- </div>
1967
- `;
1968
-
1969
- const todoContent = `
1970
- <div class="todo-checklist">
1971
- ${parameters.todos.map(todo => {
1972
- const statusIcon = {
1973
- 'pending': 'โณ',
1974
- 'in_progress': '๐Ÿ”„',
1975
- 'completed': 'โœ…'
1976
- }[todo.status] || 'โ“';
1977
-
1978
- const priorityIcon = {
1979
- 'high': '๐Ÿ”ด',
1980
- 'medium': '๐ŸŸก',
1981
- 'low': '๐ŸŸข'
1982
- }[todo.priority] || '๐ŸŸก';
1983
-
1984
- return `
1985
- <div class="todo-item todo-${todo.status || 'pending'}">
1986
- <span class="todo-status">${statusIcon}</span>
1987
- <span class="todo-content">${todo.content || 'No content'}</span>
1988
- <span class="todo-priority priority-${todo.priority || 'medium'}">${priorityIcon}</span>
1989
- </div>
1990
- `;
1991
- }).join('')}
1992
- </div>
1993
- `;
1994
-
1995
- if (this.moduleViewer.dataContainer) {
1996
- this.moduleViewer.dataContainer.innerHTML = contextualHeader + todoContent;
1997
- }
1998
- if (this.moduleViewer.jsonContainer) {
1999
- const toolCallData = {
2000
- toolCall: toolCall,
2001
- preEvent: preEvent,
2002
- postEvent: postEvent
2003
- };
2004
- this.moduleViewer.jsonContainer.innerHTML = `<pre>${JSON.stringify(toolCallData, null, 2)}</pre>`;
2005
- }
2006
- } else {
2007
- // For other tools, use the module viewer's ingest method with pre-event
2008
- if (this.moduleViewer && preEvent) {
2009
- this.moduleViewer.ingest(preEvent);
2010
- }
2011
- }
2012
- }
2013
-
2014
-
2015
-
2016
- /**
2017
- * Show detailed file operations in module viewer
2018
- */
2019
- showFileDetails(filePath) {
2020
- const fileData = this.fileOperations.get(filePath);
2021
- if (!fileData) return;
2022
-
2023
- // Filter operations by selected session if applicable
2024
- let operations = fileData.operations;
2025
- if (this.selectedSessionId) {
2026
- operations = operations.filter(op => op.sessionId === this.selectedSessionId);
2027
- if (operations.length === 0) {
2028
- // No operations from this session
2029
- this.moduleViewer.showErrorMessage(
2030
- 'No Operations in Selected Session',
2031
- `This file has no operations from the selected session.`
2032
- );
2033
- return;
2034
- }
2035
- }
2036
-
2037
- // Get file name from path for header
2038
- const fileName = filePath.split('/').pop() || filePath;
2039
- const lastOp = operations[operations.length - 1];
2040
- const headerTimestamp = this.formatTimestamp(lastOp.timestamp);
2041
-
2042
- // Create contextual header matching module-viewer pattern
2043
- const contextualHeader = `
2044
- <div class="contextual-header">
2045
- <h3 class="contextual-header-text">File: ${fileName} ${headerTimestamp}</h3>
2046
- </div>
2047
- `;
2048
-
2049
- const content = `
2050
- <div class="structured-view-section">
2051
- <div class="file-details">
2052
- <div class="file-path-display">
2053
- <strong>Full Path:</strong> ${filePath}
2054
- </div>
2055
- <div class="operations-list">
2056
- ${operations.map(op => `
2057
- <div class="operation-item">
2058
- <div class="operation-header">
2059
- <span class="operation-icon">${this.getOperationIcon(op.operation)}</span>
2060
- <span class="operation-type">${op.operation}</span>
2061
- <span class="operation-timestamp">${new Date(op.timestamp).toLocaleString()}</span>
2062
- ${(['edit', 'write'].includes(op.operation)) ? `
2063
- <span class="git-diff-icon"
2064
- onclick="showGitDiffModal('${filePath}', '${op.timestamp}')"
2065
- title="View git diff for this file operation"
2066
- style="margin-left: 8px; cursor: pointer; font-size: 16px;">
2067
- ๐Ÿ“‹
2068
- </span>
2069
- ` : ''}
2070
- </div>
2071
- <div class="operation-details">
2072
- <strong>Agent:</strong> ${op.agent}<br>
2073
- <strong>Session:</strong> ${op.sessionId ? op.sessionId.substring(0, 8) + '...' : 'Unknown'}
2074
- ${op.details ? `<br><strong>Details:</strong> ${op.details}` : ''}
2075
- </div>
2076
- </div>
2077
- `).join('')}
2078
- </div>
2079
- </div>
2080
- </div>
2081
- `;
2082
-
2083
- // Use the new dual-pane approach for file details with standard header
2084
- if (this.moduleViewer.dataContainer) {
2085
- this.moduleViewer.dataContainer.innerHTML = contextualHeader + content;
2086
- }
2087
- if (this.moduleViewer.jsonContainer) {
2088
- // Show the file data with operations
2089
- this.moduleViewer.jsonContainer.innerHTML = `<pre>${JSON.stringify(fileData, null, 2)}</pre>`;
2090
- }
2091
- }
2092
-
2093
- /**
2094
- * Update file operations from events
2095
- */
2096
- updateFileOperations(events) {
2097
- // Clear existing data
2098
- this.fileOperations.clear();
2099
-
2100
- console.log('updateFileOperations - processing', events.length, 'events');
2101
-
2102
- // Group events by session and timestamp to match pre/post pairs
2103
- const eventPairs = new Map(); // Key: session_id + timestamp + tool_name
2104
- let fileOperationCount = 0;
2105
-
2106
- // First pass: collect all tool events and group them
2107
- events.forEach((event, index) => {
2108
- const isFileOp = this.isFileOperation(event);
2109
- if (isFileOp) fileOperationCount++;
2110
-
2111
- if (index < 5) { // Debug first 5 events with more detail
2112
- console.log(`Event ${index}:`, {
2113
- type: event.type,
2114
- subtype: event.subtype,
2115
- tool_name: event.tool_name,
2116
- tool_parameters: event.tool_parameters,
2117
- isFileOp: isFileOp
2118
- });
2119
- }
2120
-
2121
- if (isFileOp) {
2122
- const toolName = event.tool_name;
2123
- const sessionId = event.session_id || 'unknown';
2124
- const eventKey = `${sessionId}_${toolName}_${Math.floor(new Date(event.timestamp).getTime() / 1000)}`; // Group by second
2125
-
2126
- if (!eventPairs.has(eventKey)) {
2127
- eventPairs.set(eventKey, {
2128
- pre_event: null,
2129
- post_event: null,
2130
- tool_name: toolName,
2131
- session_id: sessionId
2132
- });
2133
- }
2134
-
2135
- const pair = eventPairs.get(eventKey);
2136
- if (event.subtype === 'pre_tool' || event.type === 'hook' && !event.subtype.includes('post')) {
2137
- pair.pre_event = event;
2138
- } else if (event.subtype === 'post_tool' || event.subtype.includes('post')) {
2139
- pair.post_event = event;
2140
- } else {
2141
- // For events without clear pre/post distinction, treat as both
2142
- pair.pre_event = event;
2143
- pair.post_event = event;
2144
- }
2145
- }
2146
- });
2147
-
2148
- console.log('updateFileOperations - found', fileOperationCount, 'file operations in', eventPairs.size, 'event pairs');
2149
-
2150
- // Second pass: extract file paths and operations from paired events
2151
- eventPairs.forEach((pair, key) => {
2152
- const filePath = this.extractFilePathFromPair(pair);
2153
-
2154
- if (filePath) {
2155
- console.log('File operation detected for:', filePath, 'from pair:', key);
2156
-
2157
- if (!this.fileOperations.has(filePath)) {
2158
- this.fileOperations.set(filePath, {
2159
- path: filePath,
2160
- operations: [],
2161
- lastOperation: null
2162
- });
2163
- }
2164
-
2165
- const fileData = this.fileOperations.get(filePath);
2166
- const operation = this.getFileOperationFromPair(pair);
2167
- const timestamp = pair.post_event?.timestamp || pair.pre_event?.timestamp;
2168
-
2169
- const agentInfo = this.extractAgentFromPair(pair);
2170
- const workingDirectory = this.extractWorkingDirectoryFromPair(pair);
2171
-
2172
- fileData.operations.push({
2173
- operation: operation,
2174
- timestamp: timestamp,
2175
- agent: agentInfo.name,
2176
- confidence: agentInfo.confidence,
2177
- sessionId: pair.session_id,
2178
- details: this.getFileOperationDetailsFromPair(pair),
2179
- workingDirectory: workingDirectory
2180
- });
2181
- fileData.lastOperation = timestamp;
2182
- } else {
2183
- console.log('No file path found for pair:', key, pair);
2184
- }
2185
- });
2186
-
2187
- console.log('updateFileOperations - final result:', this.fileOperations.size, 'file operations');
2188
- if (this.fileOperations.size > 0) {
2189
- console.log('File operations map:', Array.from(this.fileOperations.entries()));
2190
- }
2191
- }
2192
-
2193
- /**
2194
- * Update tool calls from events - pairs pre/post tool events into complete tool calls
2195
- */
2196
- updateToolCalls(events) {
2197
- // Clear existing data
2198
- this.toolCalls.clear();
2199
-
2200
- console.log('updateToolCalls - processing', events.length, 'events');
2201
-
2202
- // Group events by session and timestamp to match pre/post pairs
2203
- const toolCallPairs = new Map(); // Key: session_id + timestamp + tool_name
2204
- let toolOperationCount = 0;
2205
-
2206
- // First pass: collect all tool events and group them
2207
- events.forEach((event, index) => {
2208
- const isToolOp = this.isToolOperation(event);
2209
- if (isToolOp) toolOperationCount++;
2210
-
2211
- if (index < 5) { // Debug first 5 events with more detail
2212
- console.log(`Tool Event ${index}:`, {
2213
- type: event.type,
2214
- subtype: event.subtype,
2215
- tool_name: event.tool_name,
2216
- tool_parameters: event.tool_parameters,
2217
- isToolOp: isToolOp
2218
- });
2219
- }
2220
-
2221
- if (isToolOp) {
2222
- const toolName = event.tool_name;
2223
- const sessionId = event.session_id || 'unknown';
2224
- const eventKey = `${sessionId}_${toolName}_${Math.floor(new Date(event.timestamp).getTime() / 1000)}`; // Group by second
2225
-
2226
- if (!toolCallPairs.has(eventKey)) {
2227
- toolCallPairs.set(eventKey, {
2228
- pre_event: null,
2229
- post_event: null,
2230
- tool_name: toolName,
2231
- session_id: sessionId,
2232
- operation_type: null,
2233
- timestamp: null,
2234
- duration_ms: null,
2235
- success: null,
2236
- exit_code: null,
2237
- result_summary: null,
2238
- agent_type: null
2239
- });
2240
- }
2241
-
2242
- const pair = toolCallPairs.get(eventKey);
2243
- if (event.subtype === 'pre_tool' || (event.type === 'hook' && !event.subtype.includes('post'))) {
2244
- pair.pre_event = event;
2245
- pair.timestamp = event.timestamp;
2246
- pair.operation_type = event.operation_type || 'tool_execution';
2247
- pair.agent_type = event.agent_type || event.subagent_type || 'PM';
2248
- } else if (event.subtype === 'post_tool' || event.subtype.includes('post')) {
2249
- pair.post_event = event;
2250
- pair.duration_ms = event.duration_ms;
2251
- pair.success = event.success;
2252
- pair.exit_code = event.exit_code;
2253
- pair.result_summary = event.result_summary;
2254
- if (!pair.agent_type) {
2255
- pair.agent_type = event.agent_type || event.subagent_type || 'PM';
2256
- }
2257
- } else {
2258
- // For events without clear pre/post distinction, treat as both
2259
- pair.pre_event = event;
2260
- pair.post_event = event;
2261
- pair.timestamp = event.timestamp;
2262
- pair.agent_type = event.agent_type || event.subagent_type || 'PM';
2263
- }
2264
- }
2265
- });
2266
-
2267
- console.log('updateToolCalls - found', toolOperationCount, 'tool operations in', toolCallPairs.size, 'tool call pairs');
2268
-
2269
- // Second pass: store complete tool calls
2270
- toolCallPairs.forEach((pair, key) => {
2271
- // Ensure we have at least a pre_event or post_event
2272
- if (pair.pre_event || pair.post_event) {
2273
- console.log('Tool call detected for:', pair.tool_name, 'from pair:', key);
2274
- this.toolCalls.set(key, pair);
2275
- } else {
2276
- console.log('No valid tool call found for pair:', key, pair);
2277
- }
2278
- });
2279
-
2280
- console.log('updateToolCalls - final result:', this.toolCalls.size, 'tool calls');
2281
- if (this.toolCalls.size > 0) {
2282
- console.log('Tool calls map:', Array.from(this.toolCalls.entries()));
2283
- }
2284
- }
2285
-
2286
- /**
2287
- * Check if event is a tool operation
2288
- */
2289
- isToolOperation(event) {
2290
- const type = event.type || '';
2291
- const subtype = event.subtype || '';
2292
-
2293
- // Check for hook events with tool subtypes
2294
- const isHookToolEvent = type === 'hook' && (
2295
- subtype.includes('tool') ||
2296
- subtype.includes('pre_') ||
2297
- subtype.includes('post_')
2298
- );
2299
-
2300
- // Events with tool_name
2301
- const hasToolName = event.tool_name;
2302
-
2303
- // Events with tools array (multiple tools)
2304
- const hasToolsArray = event.tools && Array.isArray(event.tools);
2305
-
2306
- // Legacy hook events with tool patterns (backward compatibility)
2307
- const isLegacyHookEvent = type.startsWith('hook.') && (
2308
- type.includes('tool') ||
2309
- type.includes('pre') ||
2310
- type.includes('post')
2311
- );
2312
-
2313
- return isHookToolEvent || hasToolName || hasToolsArray || isLegacyHookEvent;
2314
- }
2315
-
2316
- /**
2317
- * Check if event is a file operation
2318
- */
2319
- isFileOperation(event) {
2320
- const toolName = event.tool_name;
2321
- const fileTools = ['Read', 'Write', 'Edit', 'MultiEdit', 'Glob', 'LS', 'NotebookRead', 'NotebookEdit', 'Grep'];
2322
-
2323
- // Check for direct tool name match
2324
- if (fileTools.includes(toolName)) {
2325
- console.log('isFileOperation - direct tool match:', toolName);
2326
- return true;
2327
- }
2328
-
2329
- // Check for hook events that involve file tools (updated for new structure)
2330
- const type = event.type || '';
2331
- const subtype = event.subtype || '';
2332
-
2333
- // Check both legacy format and new format
2334
- const isHookEvent = type === 'hook' || type.startsWith('hook.');
2335
-
2336
- if (isHookEvent) {
2337
- // Check if tool_name indicates file operation
2338
- if (fileTools.includes(event.tool_name)) {
2339
- console.log('isFileOperation - hook tool match:', event.tool_name);
2340
- return true;
2341
- }
2342
-
2343
- // Check if parameters suggest file operation
2344
- const params = event.tool_parameters || {};
2345
- const hasFileParams = !!(params.file_path || params.path || params.notebook_path || params.pattern);
2346
-
2347
- // Also check top-level event for file parameters (flat structure)
2348
- const hasDirectFileParams = !!(event.file_path || event.path || event.notebook_path || event.pattern);
2349
-
2350
- const hasAnyFileParams = hasFileParams || hasDirectFileParams;
2351
- if (hasAnyFileParams) {
2352
- console.log('isFileOperation - file params match:', { hasFileParams, hasDirectFileParams, params, directParams: { file_path: event.file_path, path: event.path } });
2353
- }
2354
-
2355
- return hasAnyFileParams;
2356
- }
2357
-
2358
- return false;
2359
- }
2360
-
2361
- /**
2362
- * Extract file path from event
2363
- */
2364
- extractFilePath(event) {
2365
- // Check tool_parameters first
2366
- const params = event.tool_parameters;
2367
- if (params) {
2368
- if (params.file_path) return params.file_path;
2369
- if (params.path) return params.path;
2370
- if (params.notebook_path) return params.notebook_path;
2371
- }
2372
-
2373
- // Check top-level event (flat structure)
2374
- if (event.file_path) return event.file_path;
2375
- if (event.path) return event.path;
2376
- if (event.notebook_path) return event.notebook_path;
2377
-
2378
- // Check tool_input if available (sometimes path is here)
2379
- if (event.tool_input) {
2380
- if (event.tool_input.file_path) return event.tool_input.file_path;
2381
- if (event.tool_input.path) return event.tool_input.path;
2382
- if (event.tool_input.notebook_path) return event.tool_input.notebook_path;
2383
- }
2384
-
2385
- // Check result/output if available (sometimes path is in result)
2386
- if (event.result) {
2387
- if (event.result.file_path) return event.result.file_path;
2388
- if (event.result.path) return event.result.path;
2389
- }
2390
-
2391
- return null;
2392
- }
2393
-
2394
- /**
2395
- * Extract file path from paired pre/post events
2396
- */
2397
- extractFilePathFromPair(pair) {
2398
- // Try pre_event first, then post_event
2399
- const preEvent = pair.pre_event;
2400
- const postEvent = pair.post_event;
2401
-
2402
- if (preEvent) {
2403
- const prePath = this.extractFilePath(preEvent);
2404
- if (prePath) return prePath;
2405
- }
2406
-
2407
- if (postEvent) {
2408
- const postPath = this.extractFilePath(postEvent);
2409
- if (postPath) return postPath;
2410
- }
2411
-
2412
- return null;
2413
- }
2414
-
2415
- /**
2416
- * Get file operation type
2417
- */
2418
- getFileOperation(event) {
2419
- const toolName = event.tool_name;
2420
- const operationMap = {
2421
- 'Read': 'read',
2422
- 'Write': 'write',
2423
- 'Edit': 'edit',
2424
- 'MultiEdit': 'edit',
2425
- 'Glob': 'search',
2426
- 'LS': 'list',
2427
- 'NotebookRead': 'read',
2428
- 'NotebookEdit': 'edit',
2429
- 'Grep': 'search'
2430
- };
2431
-
2432
- return operationMap[toolName] || 'operation';
2433
- }
2434
-
2435
- /**
2436
- * Get file operation type from paired events
2437
- */
2438
- getFileOperationFromPair(pair) {
2439
- const toolName = pair.tool_name;
2440
- const operationMap = {
2441
- 'Read': 'read',
2442
- 'Write': 'write',
2443
- 'Edit': 'edit',
2444
- 'MultiEdit': 'edit',
2445
- 'Glob': 'search',
2446
- 'LS': 'list',
2447
- 'NotebookRead': 'read',
2448
- 'NotebookEdit': 'edit',
2449
- 'Grep': 'search'
2450
- };
2451
-
2452
- return operationMap[toolName] || 'operation';
2453
- }
2454
-
2455
- /**
2456
- * Extract agent from paired events using inference
2457
- */
2458
- extractAgentFromPair(pair) {
2459
- // Try to get inference from either event
2460
- const preEvent = pair.pre_event;
2461
- const postEvent = pair.post_event;
2462
-
2463
- if (preEvent) {
2464
- const eventIndex = this.eventViewer.events.indexOf(preEvent);
2465
- const inference = this.getInferredAgent(eventIndex);
2466
- if (inference) {
2467
- return {
2468
- name: inference.agentName,
2469
- confidence: inference.confidence
2470
- };
2471
- }
2472
- }
2473
-
2474
- if (postEvent) {
2475
- const eventIndex = this.eventViewer.events.indexOf(postEvent);
2476
- const inference = this.getInferredAgent(eventIndex);
2477
- if (inference) {
2478
- return {
2479
- name: inference.agentName,
2480
- confidence: inference.confidence
2481
- };
2482
- }
2483
- }
2484
-
2485
- // Fallback to legacy logic
2486
- const preAgent = preEvent?.agent_type || preEvent?.subagent_type;
2487
- const postAgent = postEvent?.agent_type || postEvent?.subagent_type;
2488
-
2489
- // Prefer non-'main' and non-'unknown' agents
2490
- if (preAgent && preAgent !== 'main' && preAgent !== 'unknown') {
2491
- return { name: preAgent, confidence: 'legacy' };
2492
- }
2493
- if (postAgent && postAgent !== 'main' && postAgent !== 'unknown') {
2494
- return { name: postAgent, confidence: 'legacy' };
2495
- }
2496
-
2497
- // Fallback to any agent
2498
- const agentName = preAgent || postAgent || 'PM';
2499
- return { name: agentName, confidence: 'fallback' };
2500
- }
2501
-
2502
- /**
2503
- * Extract working directory from event pair
2504
- */
2505
- extractWorkingDirectoryFromPair(pair) {
2506
- // Try to get working directory from either event's data
2507
- const preEvent = pair.pre_event;
2508
- const postEvent = pair.post_event;
2509
-
2510
- // Check pre_event first
2511
- if (preEvent?.data?.working_directory) {
2512
- return preEvent.data.working_directory;
2513
- }
2514
-
2515
- // Check post_event
2516
- if (postEvent?.data?.working_directory) {
2517
- return postEvent.data.working_directory;
2518
- }
2519
-
2520
- // Check tool_parameters for working directory
2521
- if (preEvent?.tool_parameters?.working_dir) {
2522
- return preEvent.tool_parameters.working_dir;
2523
- }
2524
-
2525
- if (postEvent?.tool_parameters?.working_dir) {
2526
- return postEvent.tool_parameters.working_dir;
2527
- }
2528
-
2529
- // Fallback to null (will use default behavior in showGitDiffModal)
2530
- return null;
2531
- }
2532
-
2533
- /**
2534
- * Get file operation details
2535
- */
2536
- getFileOperationDetails(event) {
2537
- const toolName = event.tool_name;
2538
- const params = event.tool_parameters;
2539
-
2540
- switch (toolName) {
2541
- case 'Edit':
2542
- case 'MultiEdit':
2543
- return `Modified content`;
2544
- case 'Write':
2545
- return `Created/updated file`;
2546
- case 'Read':
2547
- return `Read file content`;
2548
- case 'NotebookRead':
2549
- return `Read notebook content`;
2550
- case 'NotebookEdit':
2551
- return `Modified notebook`;
2552
- case 'Glob':
2553
- return `Searched pattern: ${params?.pattern || 'unknown'}`;
2554
- case 'Grep':
2555
- return `Searched pattern: ${params?.pattern || 'unknown'}`;
2556
- case 'LS':
2557
- return `Listed directory`;
2558
- default:
2559
- return '';
2560
- }
2561
- }
2562
-
2563
- /**
2564
- * Get file operation details from paired events
2565
- */
2566
- getFileOperationDetailsFromPair(pair) {
2567
- const toolName = pair.tool_name;
2568
-
2569
- // Get parameters from either event
2570
- const preParams = pair.pre_event?.tool_parameters || {};
2571
- const postParams = pair.post_event?.tool_parameters || {};
2572
- const params = { ...preParams, ...postParams };
2573
-
2574
- switch (toolName) {
2575
- case 'Edit':
2576
- case 'MultiEdit':
2577
- return `Modified content`;
2578
- case 'Write':
2579
- return `Created/updated file`;
2580
- case 'Read':
2581
- return `Read file content`;
2582
- case 'NotebookRead':
2583
- return `Read notebook content`;
2584
- case 'NotebookEdit':
2585
- return `Modified notebook`;
2586
- case 'Glob':
2587
- return `Searched pattern: ${params?.pattern || 'unknown'}`;
2588
- case 'Grep':
2589
- return `Searched pattern: ${params?.pattern || 'unknown'}`;
2590
- case 'LS':
2591
- return `Listed directory`;
2592
- default:
2593
- return '';
2594
- }
2595
- }
2596
-
2597
- /**
2598
- * Get icon for file operations - shows combined icons for read+write
2599
- */
2600
- getFileOperationIcon(operations) {
2601
- // Check for notebook operations first
2602
- const hasNotebook = operations.some(op => op.details && (op.details.includes('notebook') || op.details.includes('Notebook')));
2603
- if (hasNotebook) return '๐Ÿ““';
2604
-
2605
- const hasWrite = operations.some(op => ['write', 'edit'].includes(op.operation));
2606
- const hasRead = operations.some(op => op.operation === 'read');
2607
- const hasSearch = operations.some(op => op.operation === 'search');
2608
- const hasList = operations.some(op => op.operation === 'list');
2609
-
2610
- // Show both icons for read+write combinations
2611
- if (hasWrite && hasRead) return '๐Ÿ“–โœ๏ธ'; // Both read and write
2612
- if (hasWrite) return 'โœ๏ธ'; // Write only
2613
- if (hasRead) return '๐Ÿ“–'; // Read only
2614
- if (hasSearch) return '๐Ÿ”'; // Search only
2615
- if (hasList) return '๐Ÿ“‹'; // List only
2616
- return '๐Ÿ“„'; // Default
2617
- }
2618
-
2619
- /**
2620
- * Get icon for specific operation
2621
- */
2622
- getOperationIcon(operation) {
2623
- const icons = {
2624
- read: '๐Ÿ“–',
2625
- write: '๐Ÿ“',
2626
- edit: 'โœ๏ธ',
2627
- search: '๐Ÿ”',
2628
- list: '๐Ÿ“‹'
2629
- };
2630
- return icons[operation] || '๐Ÿ“„';
2631
- }
2632
-
2633
- /**
2634
- * Format timestamp for display
2635
- * @param {string|number} timestamp - Timestamp to format
2636
- * @returns {string} Formatted time
2637
- */
2638
- formatTimestamp(timestamp) {
2639
- if (!timestamp) return 'Unknown time';
2640
-
2641
- try {
2642
- const date = new Date(timestamp);
2643
- return date.toLocaleTimeString('en-US', {
2644
- hour: 'numeric',
2645
- minute: '2-digit',
2646
- second: '2-digit',
2647
- hour12: true
2648
- });
2649
- } catch (e) {
2650
- return 'Invalid time';
2651
- }
2652
- }
2653
-
2654
- /**
2655
- * Get relative file path for display
2656
- */
2657
- getRelativeFilePath(filePath) {
2658
- // Try to make path relative to common base paths
2659
- const commonPaths = [
2660
- '/Users/masa/Projects/claude-mpm/',
2661
- '.'
2662
- ];
2663
-
2664
- for (const basePath of commonPaths) {
2665
- if (filePath.startsWith(basePath)) {
2666
- return filePath.substring(basePath.length).replace(/^\//, '');
2667
- }
2668
- }
2669
-
2670
- // If no common path found, show last 2-3 path segments
2671
- const parts = filePath.split('/');
2672
- if (parts.length > 3) {
2673
- return '.../' + parts.slice(-2).join('/');
2674
- }
2675
-
2676
- return filePath;
2677
- }
2678
-
2679
- /**
2680
- * Apply agents tab filtering
2681
- */
2682
- applyAgentsFilters(events) {
2683
- const searchInput = document.getElementById('agents-search-input');
2684
- const typeFilter = document.getElementById('agents-type-filter');
2685
-
2686
- const searchText = searchInput ? searchInput.value.toLowerCase() : '';
2687
- const typeValue = typeFilter ? typeFilter.value : '';
2688
-
2689
- return events.filter(event => {
2690
- // Search filter
2691
- if (searchText) {
2692
- const searchableText = [
2693
- event.subagent_type || '',
2694
- event.agent_type || '',
2695
- event.name || '',
2696
- event.type || '',
2697
- event.subtype || ''
2698
- ].join(' ').toLowerCase();
2699
-
2700
- if (!searchableText.includes(searchText)) {
2701
- return false;
2702
- }
2703
- }
2704
-
2705
- // Type filter
2706
- if (typeValue) {
2707
- const agentType = event.subagent_type || event.agent_type || 'unknown';
2708
- if (!agentType.toLowerCase().includes(typeValue.toLowerCase())) {
2709
- return false;
2710
- }
2711
- }
2712
-
2713
- return true;
2714
- });
2715
- }
2716
-
2717
- /**
2718
- * Apply tools tab filtering
2719
- */
2720
- applyToolsFilters(events) {
2721
- const searchInput = document.getElementById('tools-search-input');
2722
- const typeFilter = document.getElementById('tools-type-filter');
2723
-
2724
- const searchText = searchInput ? searchInput.value.toLowerCase() : '';
2725
- const typeValue = typeFilter ? typeFilter.value : '';
2726
-
2727
- return events.filter(event => {
2728
- // Search filter
2729
- if (searchText) {
2730
- const searchableText = [
2731
- event.tool_name || '',
2732
- event.agent_type || '',
2733
- event.type || '',
2734
- event.subtype || ''
2735
- ].join(' ').toLowerCase();
2736
-
2737
- if (!searchableText.includes(searchText)) {
2738
- return false;
2739
- }
2740
- }
2741
-
2742
- // Type filter
2743
- if (typeValue) {
2744
- const toolName = event.tool_name || '';
2745
- if (toolName !== typeValue) {
2746
- return false;
2747
- }
2748
- }
2749
-
2750
- return true;
2751
- });
2752
- }
2753
-
2754
- /**
2755
- * Apply tools tab filtering for tool calls
2756
- */
2757
- applyToolCallFilters(toolCallsArray) {
2758
- const searchInput = document.getElementById('tools-search-input');
2759
- const typeFilter = document.getElementById('tools-type-filter');
2760
-
2761
- const searchText = searchInput ? searchInput.value.toLowerCase() : '';
2762
- const typeValue = typeFilter ? typeFilter.value : '';
2763
-
2764
- return toolCallsArray.filter(([key, toolCall]) => {
2765
- // Search filter
2766
- if (searchText) {
2767
- const searchableText = [
2768
- toolCall.tool_name || '',
2769
- toolCall.agent_type || '',
2770
- 'tool_call'
2771
- ].join(' ').toLowerCase();
2772
-
2773
- if (!searchableText.includes(searchText)) {
2774
- return false;
2775
- }
2776
- }
2777
-
2778
- // Type filter
2779
- if (typeValue) {
2780
- const toolName = toolCall.tool_name || '';
2781
- if (toolName !== typeValue) {
2782
- return false;
2783
- }
2784
- }
2785
-
2786
- return true;
2787
- });
2788
- }
2789
-
2790
- /**
2791
- * Apply files tab filtering
2792
- */
2793
- applyFilesFilters(fileOperations) {
2794
- const searchInput = document.getElementById('files-search-input');
2795
- const typeFilter = document.getElementById('files-type-filter');
2796
-
2797
- const searchText = searchInput ? searchInput.value.toLowerCase() : '';
2798
- const typeValue = typeFilter ? typeFilter.value : '';
2799
-
2800
- return fileOperations.filter(([filePath, fileData]) => {
2801
- // Session filter - filter operations within each file
2802
- if (this.selectedSessionId) {
2803
- // Filter operations for this file by session
2804
- const sessionOperations = fileData.operations.filter(op =>
2805
- op.sessionId === this.selectedSessionId
2806
- );
2807
-
2808
- // If no operations from this session, exclude the file
2809
- if (sessionOperations.length === 0) {
2810
- return false;
2811
- }
2812
-
2813
- // Update the fileData to only include session-specific operations
2814
- // (Note: This creates a filtered view without modifying the original)
2815
- fileData = {
2816
- ...fileData,
2817
- operations: sessionOperations,
2818
- lastOperation: sessionOperations[sessionOperations.length - 1]?.timestamp || fileData.lastOperation
2819
- };
2820
- }
2821
-
2822
- // Search filter
2823
- if (searchText) {
2824
- const searchableText = [
2825
- filePath,
2826
- ...fileData.operations.map(op => op.operation),
2827
- ...fileData.operations.map(op => op.agent)
2828
- ].join(' ').toLowerCase();
2829
-
2830
- if (!searchableText.includes(searchText)) {
2831
- return false;
2832
- }
2833
- }
2834
-
2835
- // Type filter
2836
- if (typeValue) {
2837
- const hasOperationType = fileData.operations.some(op => op.operation === typeValue);
2838
- if (!hasOperationType) {
2839
- return false;
2840
- }
2841
- }
2842
-
2843
- return true;
2844
- });
2845
- }
2846
-
2847
- /**
2848
- * Extract operation from event type
2849
- */
2850
- extractOperation(eventType) {
2851
- if (!eventType) return 'unknown';
2852
-
2853
- if (eventType.includes('pre_')) return 'pre-' + eventType.split('pre_')[1];
2854
- if (eventType.includes('post_')) return 'post-' + eventType.split('post_')[1];
2855
- if (eventType.includes('delegation')) return 'delegation';
2856
- if (eventType.includes('start')) return 'started';
2857
- if (eventType.includes('end')) return 'ended';
2858
-
2859
- // Extract operation from type like "hook.pre_tool" -> "pre_tool"
2860
- const parts = eventType.split('.');
2861
- return parts.length > 1 ? parts[1] : eventType;
2862
- }
2863
-
2864
- /**
2865
- * Extract tool name from hook event type
2866
- */
2867
- extractToolFromHook(eventType) {
2868
- if (!eventType || !eventType.startsWith('hook.')) return null;
2869
-
2870
- // For hook events, the tool name might be in the data
2871
- return 'Tool'; // Fallback - actual tool name should be in event.tool_name
2872
- }
2873
-
2874
- /**
2875
- * Extract tool name from subtype
2876
- */
2877
- extractToolFromSubtype(subtype) {
2878
- if (!subtype) return null;
2879
-
2880
- // Try to extract tool name from subtype patterns like 'pre_tool' or 'post_tool'
2881
- if (subtype.includes('tool')) {
2882
- return 'Tool'; // Generic fallback
2883
- }
2884
-
2885
- return null;
2886
- }
2887
-
2888
- /**
2889
- * Extract tool target for display
2890
- */
2891
- extractToolTarget(toolName, params, toolParameters) {
2892
- const allParams = { ...params, ...toolParameters };
2893
-
2894
- switch (toolName) {
2895
- case 'Read':
2896
- case 'Write':
2897
- case 'Edit':
2898
- case 'MultiEdit':
2899
- return allParams.file_path || 'Unknown file';
2900
- case 'Bash':
2901
- return allParams.command || 'Unknown command';
2902
- case 'Glob':
2903
- return allParams.pattern || 'Unknown pattern';
2904
- case 'Grep':
2905
- return `"${allParams.pattern || 'unknown'}" in ${allParams.path || 'unknown path'}`;
2906
- case 'LS':
2907
- return allParams.path || 'Unknown path';
2908
- default:
2909
- if (Object.keys(allParams).length > 0) {
2910
- return JSON.stringify(allParams).substring(0, 50) + '...';
2911
- }
2912
- return 'No parameters';
2913
- }
2914
- }
2915
-
2916
- /**
2917
- * Get filtered events for a specific tab
2918
- */
2919
- getFilteredEventsForTab(tabName) {
2920
- // Use ALL events, not the EventViewer's filtered events
2921
- // Each tab will apply its own filtering logic
2922
- const events = this.eventViewer.events;
2923
- console.log(`getFilteredEventsForTab(${tabName}) - using RAW events: ${events.length} total`);
2924
-
2925
- // Enhanced debugging for empty events
2926
- if (events.length === 0) {
2927
- console.log(`โŒ NO RAW EVENTS available!`);
2928
- console.log('EventViewer state:', {
2929
- total_events: this.eventViewer.events.length,
2930
- filtered_events: this.eventViewer.filteredEvents.length,
2931
- search_filter: this.eventViewer.searchFilter,
2932
- type_filter: this.eventViewer.typeFilter,
2933
- session_filter: this.eventViewer.sessionFilter
2934
- });
2935
- } else {
2936
- console.log('โœ… Raw events available for', tabName, '- sample:', events[0]);
2937
- console.log('EventViewer filters (IGNORED for tabs):', {
2938
- search_filter: this.eventViewer.searchFilter,
2939
- type_filter: this.eventViewer.typeFilter,
2940
- session_filter: this.eventViewer.sessionFilter
2941
- });
2942
- }
2943
-
2944
- return events;
2945
- }
2946
-
2947
- /**
2948
- * Scroll a list container to the bottom
2949
- * @param {string} listId - The ID of the list container element
2950
- */
2951
- scrollListToBottom(listId) {
2952
- console.log(`[DEBUG] scrollListToBottom called with listId: ${listId}`);
2953
-
2954
- // Use setTimeout to ensure DOM updates are completed
2955
- setTimeout(() => {
2956
- const listElement = document.getElementById(listId);
2957
- console.log(`[DEBUG] Element found for ${listId}:`, listElement);
2958
-
2959
- if (listElement) {
2960
- const scrollHeight = listElement.scrollHeight;
2961
- const clientHeight = listElement.clientHeight;
2962
- const currentScrollTop = listElement.scrollTop;
2963
- const computedStyle = window.getComputedStyle(listElement);
2964
-
2965
- console.log(`[DEBUG] Scroll metrics for ${listId}:`);
2966
- console.log(` - scrollHeight: ${scrollHeight}`);
2967
- console.log(` - clientHeight: ${clientHeight}`);
2968
- console.log(` - currentScrollTop: ${currentScrollTop}`);
2969
- console.log(` - needsScroll: ${scrollHeight > clientHeight}`);
2970
- console.log(` - overflowY: ${computedStyle.overflowY}`);
2971
- console.log(` - height: ${computedStyle.height}`);
2972
- console.log(` - maxHeight: ${computedStyle.maxHeight}`);
2973
-
2974
- if (scrollHeight > clientHeight) {
2975
- // Method 1: Direct scrollTop assignment
2976
- listElement.scrollTop = scrollHeight;
2977
- console.log(`[DEBUG] Method 1 - Scroll applied to ${listId}, new scrollTop: ${listElement.scrollTop}`);
2978
-
2979
- // Method 2: Force layout recalculation and try again if needed
2980
- if (listElement.scrollTop !== scrollHeight) {
2981
- console.log(`[DEBUG] Method 1 failed, trying method 2 with layout recalculation`);
2982
- listElement.offsetHeight; // Force reflow
2983
- listElement.scrollTop = scrollHeight;
2984
- console.log(`[DEBUG] Method 2 - new scrollTop: ${listElement.scrollTop}`);
2985
- }
2986
-
2987
- // Method 3: scrollIntoView on last element if still not working
2988
- if (listElement.scrollTop < scrollHeight - clientHeight - 10) {
2989
- console.log(`[DEBUG] Methods 1-2 failed, trying method 3 with scrollIntoView`);
2990
- const lastElement = listElement.lastElementChild;
2991
- if (lastElement) {
2992
- lastElement.scrollIntoView({ behavior: 'instant', block: 'end' });
2993
- console.log(`[DEBUG] Method 3 - scrollIntoView applied on last element`);
2994
- }
2995
- }
2996
- } else {
2997
- console.log(`[DEBUG] No scroll needed for ${listId} - content fits in container`);
2998
- }
2999
- } else {
3000
- console.error(`[DEBUG] Element not found for ID: ${listId}`);
3001
- // Log all elements with similar IDs to help debug
3002
- const allElements = document.querySelectorAll('[id*="list"]');
3003
- console.log(`[DEBUG] Available elements with 'list' in ID:`, Array.from(allElements).map(el => el.id));
3004
- }
3005
- }, 50); // Small delay to ensure content is rendered
3006
- }
3007
-
3008
- /**
3009
- * Test scroll functionality - adds dummy content and tries to scroll
3010
- * This is for debugging purposes only
3011
- * @returns {Promise<string>} Status message indicating test results
3012
- */
3013
- async testScrollFunctionality() {
3014
- console.log('[DEBUG] Testing scroll functionality...');
3015
- const results = [];
3016
- const listId = `${this.currentTab}-list`;
3017
- const listElement = document.getElementById(listId);
3018
-
3019
- if (!listElement) {
3020
- const errorMsg = `โŒ Could not find list element: ${listId}`;
3021
- console.error(`[DEBUG] ${errorMsg}`);
3022
-
3023
- // Debug: Show available elements
3024
- const allElements = document.querySelectorAll('[id*="list"]');
3025
- const availableIds = Array.from(allElements).map(el => el.id);
3026
- console.log(`[DEBUG] Available elements with 'list' in ID:`, availableIds);
3027
- results.push(errorMsg);
3028
- results.push(`Available list IDs: ${availableIds.join(', ')}`);
3029
- return results.join('\n');
3030
- }
3031
-
3032
- // Get initial state
3033
- const initialScrollTop = listElement.scrollTop;
3034
- const initialScrollHeight = listElement.scrollHeight;
3035
- const initialClientHeight = listElement.clientHeight;
3036
- const computedStyle = window.getComputedStyle(listElement);
3037
-
3038
- results.push(`โœ… Found list element: ${listId}`);
3039
- results.push(`๐Ÿ“Š Initial state: scrollTop=${initialScrollTop}, scrollHeight=${initialScrollHeight}, clientHeight=${initialClientHeight}`);
3040
- results.push(`๐ŸŽจ CSS: overflowY=${computedStyle.overflowY}, height=${computedStyle.height}, maxHeight=${computedStyle.maxHeight}`);
3041
-
3042
- // Test 1: Direct scroll without adding content
3043
- console.log('[DEBUG] Test 1: Direct scroll test');
3044
- listElement.scrollTop = 999999;
3045
- await new Promise(resolve => setTimeout(resolve, 50));
3046
- const directScrollResult = listElement.scrollTop;
3047
- results.push(`๐Ÿงช Test 1 - Direct scroll to 999999: scrollTop=${directScrollResult} ${directScrollResult > 0 ? 'โœ…' : 'โŒ'}`);
3048
-
3049
- // Reset scroll position
3050
- listElement.scrollTop = 0;
3051
-
3052
- // Test 2: Add visible content to force scrolling need
3053
- console.log('[DEBUG] Test 2: Adding test content');
3054
- const originalContent = listElement.innerHTML;
3055
-
3056
- // Add 15 large test items to definitely exceed container height
3057
- for (let i = 0; i < 15; i++) {
3058
- const testItem = document.createElement('div');
3059
- testItem.className = 'event-item test-scroll-item';
3060
- testItem.style.cssText = 'background: #fffacd !important; border: 2px solid #f39c12 !important; padding: 20px; margin: 10px 0; min-height: 60px;';
3061
- testItem.innerHTML = `<strong>๐Ÿงช Test Item ${i + 1}</strong><br>This is a test item to verify scrolling functionality.<br><em>Item height: ~80px total</em>`;
3062
- listElement.appendChild(testItem);
3063
- }
3064
-
3065
- // Force layout recalculation
3066
- listElement.offsetHeight;
3067
-
3068
- const afterContentScrollHeight = listElement.scrollHeight;
3069
- const afterContentClientHeight = listElement.clientHeight;
3070
- const needsScroll = afterContentScrollHeight > afterContentClientHeight;
3071
-
3072
- results.push(`๐Ÿ“ฆ Added 15 test items (expected ~1200px total height)`);
3073
- results.push(`๐Ÿ“Š After content: scrollHeight=${afterContentScrollHeight}, clientHeight=${afterContentClientHeight}`);
3074
- results.push(`๐Ÿ” Needs scroll: ${needsScroll ? 'YES โœ…' : 'NO โŒ'}`);
3075
-
3076
- if (needsScroll) {
3077
- // Test 3: Multiple scroll methods
3078
- console.log('[DEBUG] Test 3: Testing different scroll methods');
3079
-
3080
- // Method A: Direct scrollTop assignment
3081
- listElement.scrollTop = afterContentScrollHeight;
3082
- await new Promise(resolve => setTimeout(resolve, 50));
3083
- const methodAResult = listElement.scrollTop;
3084
- const methodASuccess = methodAResult > (afterContentScrollHeight - afterContentClientHeight - 50);
3085
- results.push(`๐Ÿ”ง Method A - scrollTop assignment: ${methodAResult} ${methodASuccess ? 'โœ…' : 'โŒ'}`);
3086
-
3087
- // Method B: scrollIntoView on last element
3088
- const lastElement = listElement.lastElementChild;
3089
- if (lastElement) {
3090
- lastElement.scrollIntoView({ behavior: 'instant', block: 'end' });
3091
- await new Promise(resolve => setTimeout(resolve, 50));
3092
- const methodBResult = listElement.scrollTop;
3093
- const methodBSuccess = methodBResult > (afterContentScrollHeight - afterContentClientHeight - 50);
3094
- results.push(`๐Ÿ”ง Method B - scrollIntoView: ${methodBResult} ${methodBSuccess ? 'โœ…' : 'โŒ'}`);
3095
- }
3096
-
3097
- // Method C: Using the existing scrollListToBottom method
3098
- console.log('[DEBUG] Test 4: Using scrollListToBottom method');
3099
- this.scrollListToBottom(listId);
3100
- await new Promise(resolve => setTimeout(resolve, 100));
3101
- const methodCResult = listElement.scrollTop;
3102
- const methodCSuccess = methodCResult > (afterContentScrollHeight - afterContentClientHeight - 50);
3103
- results.push(`๐Ÿ”ง Method C - scrollListToBottom: ${methodCResult} ${methodCSuccess ? 'โœ…' : 'โŒ'}`);
3104
- }
3105
-
3106
- // Clean up test content after a visible delay
3107
- setTimeout(() => {
3108
- console.log('[DEBUG] Cleaning up test content...');
3109
- const testItems = listElement.querySelectorAll('.test-scroll-item');
3110
- testItems.forEach(item => item.remove());
3111
- results.push(`๐Ÿงน Cleaned up ${testItems.length} test items`);
3112
- }, 2000);
3113
-
3114
- const finalResult = results.join('\n');
3115
- console.log('[DEBUG] Test complete. Results:', finalResult);
3116
- return finalResult;
3117
- }
3118
-
3119
- /**
3120
- * Update connection status in UI
3121
- */
3122
- updateConnectionStatus(status, type) {
3123
- const statusElement = document.getElementById('connection-status');
3124
- if (statusElement) {
3125
- statusElement.textContent = status;
3126
- statusElement.className = `status-badge status-${type}`;
3127
-
3128
- // Update status indicator
3129
- const indicator = statusElement.querySelector('span');
3130
- if (indicator) {
3131
- indicator.textContent = type === 'connected' ? 'โ—' : 'โ—';
3132
- }
3133
- }
3134
- }
3135
-
3136
- /**
3137
- * Clear all events
3138
- */
3139
- clearEvents() {
3140
- this.eventViewer.clearEvents();
3141
- this.fileOperations.clear();
3142
- this.toolCalls.clear();
3143
- this.agentEvents = [];
3144
- this.renderCurrentTab();
3145
- }
3146
-
3147
- /**
3148
- * Export current events
3149
- */
3150
- exportEvents() {
3151
- this.eventViewer.exportEvents();
3152
- }
3153
-
3154
- /**
3155
- * Toggle connection controls visibility
3156
- */
3157
- toggleConnectionControls() {
3158
- const controlsRow = document.getElementById('connection-controls-row');
3159
- const toggleBtn = document.getElementById('connection-toggle-btn');
3160
-
3161
- if (controlsRow && toggleBtn) {
3162
- const isVisible = controlsRow.classList.contains('show');
3163
-
3164
- if (isVisible) {
3165
- controlsRow.classList.remove('show');
3166
- controlsRow.style.display = 'none';
3167
- toggleBtn.textContent = 'Connection Settings';
3168
- } else {
3169
- controlsRow.style.display = 'flex';
3170
- // Use setTimeout to ensure display change is applied before adding animation class
3171
- setTimeout(() => {
3172
- controlsRow.classList.add('show');
3173
- }, 10);
3174
- toggleBtn.textContent = 'Hide Connection Settings';
3175
- }
3176
- }
3177
- }
3178
-
3179
- /**
3180
- * Clear current selection
3181
- */
3182
- clearSelection() {
3183
- this.clearCardSelection();
3184
- this.eventViewer.clearSelection();
3185
- this.moduleViewer.clear();
3186
- }
3187
-
3188
- /**
3189
- * Show dialog to change working directory
3190
- */
3191
- showChangeDirDialog() {
3192
- const currentDir = this.currentWorkingDir || this.getDefaultWorkingDir();
3193
- const newDir = prompt('Enter new working directory:', currentDir);
3194
-
3195
- if (newDir && newDir !== currentDir) {
3196
- this.setWorkingDirectory(newDir);
3197
- }
3198
- }
3199
-
3200
- /**
3201
- * Set the working directory for the current session
3202
- * @param {string} dir - New working directory path
3203
- */
3204
- setWorkingDirectory(dir) {
3205
- this.currentWorkingDir = dir;
3206
-
3207
- // Update UI
3208
- const pathElement = document.getElementById('working-dir-path');
3209
- if (pathElement) {
3210
- pathElement.textContent = dir;
3211
- pathElement.title = `Click to change from ${dir}`;
3212
- }
3213
-
3214
- // Update footer (with flag to prevent observer loop)
3215
- const footerDir = document.getElementById('footer-working-dir');
3216
- if (footerDir) {
3217
- this._updatingFooter = true;
3218
- footerDir.textContent = dir;
3219
- // Reset flag after a small delay to ensure observer has processed
3220
- setTimeout(() => {
3221
- this._updatingFooter = false;
3222
- }, 10);
3223
- }
3224
-
3225
- // Store in session data if a session is selected
3226
- const sessionSelect = document.getElementById('session-select');
3227
- if (sessionSelect && sessionSelect.value) {
3228
- const sessionId = sessionSelect.value;
3229
- // Store working directory per session in localStorage
3230
- const sessionDirs = JSON.parse(localStorage.getItem('sessionWorkingDirs') || '{}');
3231
- sessionDirs[sessionId] = dir;
3232
- localStorage.setItem('sessionWorkingDirs', JSON.stringify(sessionDirs));
3233
- }
3234
-
3235
- console.log(`Working directory set to: ${dir}`);
3236
-
3237
- // Request git branch for the new directory
3238
- this.updateGitBranch(dir);
3239
- }
3240
-
3241
- /**
3242
- * Update git branch display for current working directory
3243
- * @param {string} dir - Working directory path
3244
- */
3245
- updateGitBranch(dir) {
3246
- if (!this.socketClient || !this.socketClient.socket || !this.socketClient.socket.connected) {
3247
- // Not connected, set to unknown
3248
- const footerBranch = document.getElementById('footer-git-branch');
3249
- if (footerBranch) {
3250
- footerBranch.textContent = 'Not Connected';
3251
- }
3252
- return;
3253
- }
3254
-
3255
- // Request git branch from server
3256
- this.socketClient.socket.emit('get_git_branch', dir);
3257
- }
3258
-
3259
- /**
3260
- * Get default working directory
3261
- */
3262
- getDefaultWorkingDir() {
3263
- // Try to get from footer first (may be set by server)
3264
- const footerDir = document.getElementById('footer-working-dir');
3265
- if (footerDir && footerDir.textContent && footerDir.textContent !== 'Unknown') {
3266
- return footerDir.textContent;
3267
- }
3268
- // Fallback to hardcoded default
3269
- return '/Users/masa/Projects/claude-mpm';
3270
- }
3271
-
3272
- /**
3273
- * Initialize working directory on dashboard load
3274
- */
3275
- initializeWorkingDirectory() {
3276
- // Check if there's a selected session
3277
- const sessionSelect = document.getElementById('session-select');
3278
- if (sessionSelect && sessionSelect.value) {
3279
- // Load working directory for selected session
3280
- this.loadWorkingDirectoryForSession(sessionSelect.value);
3281
- } else {
3282
- // Set default working directory
3283
- this.setWorkingDirectory(this.getDefaultWorkingDir());
3284
- }
3285
- }
3286
-
3287
- /**
3288
- * Watch footer directory for changes and sync working directory
3289
- */
3290
- watchFooterDirectory() {
3291
- const footerDir = document.getElementById('footer-working-dir');
3292
- if (!footerDir) return;
3293
-
3294
- // Store observer reference for later use
3295
- this.footerDirObserver = new MutationObserver((mutations) => {
3296
- // Skip if we're updating from setWorkingDirectory
3297
- if (this._updatingFooter) return;
3298
-
3299
- mutations.forEach((mutation) => {
3300
- if (mutation.type === 'childList' || mutation.type === 'characterData') {
3301
- const newDir = footerDir.textContent.trim();
3302
- // Only update if it's a valid directory path and different from current
3303
- if (newDir &&
3304
- newDir !== 'Unknown' &&
3305
- newDir !== 'Not Connected' &&
3306
- newDir.startsWith('/') &&
3307
- newDir !== this.currentWorkingDir) {
3308
- console.log(`Footer directory changed to: ${newDir}, syncing working directory`);
3309
- this.setWorkingDirectory(newDir);
3310
- }
3311
- }
3312
- });
3313
- });
3314
-
3315
- // Start observing
3316
- this.footerDirObserver.observe(footerDir, {
3317
- childList: true,
3318
- characterData: true,
3319
- subtree: true
3320
- });
3321
- }
3322
-
3323
- /**
3324
- * Load working directory for a session
3325
- * @param {string} sessionId - Session ID
3326
- */
3327
- loadWorkingDirectoryForSession(sessionId) {
3328
- if (!sessionId) {
3329
- // No session selected, use default
3330
- this.setWorkingDirectory(this.getDefaultWorkingDir());
3331
- return;
3332
- }
3333
-
3334
- // Load from localStorage
3335
- const sessionDirs = JSON.parse(localStorage.getItem('sessionWorkingDirs') || '{}');
3336
- const dir = sessionDirs[sessionId] || this.getDefaultWorkingDir();
3337
- this.setWorkingDirectory(dir);
3338
- }
3339
-
3340
- /**
3341
- * Toggle HUD visualizer mode
3342
- */
3343
- toggleHUD() {
3344
- if (!this.isSessionSelected()) {
3345
- console.log('Cannot toggle HUD: No session selected');
3346
- return;
3347
- }
3348
-
3349
- this.hudMode = !this.hudMode;
3350
- this.updateHUDDisplay();
3351
-
3352
- console.log('HUD mode toggled:', this.hudMode ? 'ON' : 'OFF');
3353
- }
3354
-
3355
- /**
3356
- * Check if a session is currently selected
3357
- * @returns {boolean} - True if session is selected
3358
- */
3359
- isSessionSelected() {
3360
- const sessionSelect = document.getElementById('session-select');
3361
- return sessionSelect && sessionSelect.value && sessionSelect.value !== '';
3362
- }
3363
-
3364
- /**
3365
- * Update HUD display based on current mode
3366
- */
3367
- updateHUDDisplay() {
3368
- const eventsWrapper = document.querySelector('.events-wrapper');
3369
- const hudToggleBtn = document.getElementById('hud-toggle-btn');
3370
-
3371
- if (!eventsWrapper || !hudToggleBtn) return;
3372
-
3373
- if (this.hudMode) {
3374
- // Switch to HUD mode
3375
- eventsWrapper.classList.add('hud-mode');
3376
- hudToggleBtn.classList.add('btn-hud-active');
3377
- hudToggleBtn.textContent = 'Normal View';
3378
-
3379
- // Activate the HUD visualizer (with lazy loading)
3380
- if (this.hudVisualizer) {
3381
- this.hudVisualizer.activate().then(() => {
3382
- // Process existing events after libraries are loaded
3383
- this.processExistingEventsForHUD();
3384
- }).catch((error) => {
3385
- console.error('Failed to activate HUD:', error);
3386
- // Optionally revert HUD mode on failure
3387
- // this.hudMode = false;
3388
- // this.updateHUDDisplay();
3389
- });
3390
- }
3391
- } else {
3392
- // Switch to normal mode
3393
- eventsWrapper.classList.remove('hud-mode');
3394
- hudToggleBtn.classList.remove('btn-hud-active');
3395
- hudToggleBtn.textContent = 'HUD';
3396
-
3397
- // Deactivate the HUD visualizer
3398
- if (this.hudVisualizer) {
3399
- this.hudVisualizer.deactivate();
3400
- }
3401
- }
3402
- }
3403
-
3404
- /**
3405
- * Update HUD button state based on session selection
3406
- */
3407
- updateHUDButtonState() {
3408
- const hudToggleBtn = document.getElementById('hud-toggle-btn');
3409
- if (!hudToggleBtn) return;
3410
-
3411
- if (this.isSessionSelected()) {
3412
- hudToggleBtn.disabled = false;
3413
- hudToggleBtn.title = 'Toggle HUD visualizer';
3414
- } else {
3415
- hudToggleBtn.disabled = true;
3416
- hudToggleBtn.title = 'Select a session to enable HUD';
3417
-
3418
- // If HUD is currently active, turn it off
3419
- if (this.hudMode) {
3420
- this.hudMode = false;
3421
- this.updateHUDDisplay();
3422
- }
3423
- }
3424
- }
3425
-
3426
- /**
3427
- * Process existing events for HUD visualization
3428
- */
3429
- processExistingEventsForHUD() {
3430
- if (!this.hudVisualizer || !this.eventViewer) return;
3431
-
3432
- console.log('๐Ÿ”„ Processing existing events for HUD visualization...');
3433
-
3434
- // Clear existing visualization
3435
- this.hudVisualizer.clear();
3436
-
3437
- // Get all events (not just filtered ones) to build complete tree structure
3438
- const allEvents = this.eventViewer.getAllEvents();
3439
-
3440
- if (allEvents.length === 0) {
3441
- console.log('โŒ No events available for HUD visualization');
3442
- return;
3443
- }
3444
-
3445
- // Sort events chronologically to ensure proper tree building
3446
- const sortedEvents = [...allEvents].sort((a, b) => {
3447
- const timeA = new Date(a.timestamp).getTime();
3448
- const timeB = new Date(b.timestamp).getTime();
3449
- return timeA - timeB;
3450
- });
3451
-
3452
- console.log(`๐Ÿ“Š Processing ${sortedEvents.length} events chronologically for HUD:`);
3453
- console.log(` โ€ข Earliest: ${new Date(sortedEvents[0].timestamp).toLocaleString()}`);
3454
- console.log(` โ€ข Latest: ${new Date(sortedEvents[sortedEvents.length - 1].timestamp).toLocaleString()}`);
3455
-
3456
- // Process events with enhanced hierarchy building
3457
- this.hudVisualizer.processExistingEvents(sortedEvents);
3458
-
3459
- console.log(`โœ… Processed ${sortedEvents.length} events for HUD visualization`);
3460
- }
3461
-
3462
- /**
3463
- * Handle new socket events for HUD
3464
- * @param {Object} event - Socket event data
3465
- */
3466
- handleHUDEvent(event) {
3467
- if (this.hudMode && this.hudVisualizer) {
3468
- this.hudVisualizer.processEvent(event);
3469
- }
3470
- }
3471
- }
3472
-
3473
- // Global functions for backward compatibility
3474
- window.connectSocket = function() {
3475
- if (window.dashboard) {
3476
- const port = document.getElementById('port-input')?.value || '8765';
3477
- window.dashboard.socketClient.connect(port);
3478
- }
3479
- };
3480
-
3481
- window.disconnectSocket = function() {
3482
- if (window.dashboard) {
3483
- window.dashboard.socketClient.disconnect();
3484
- }
3485
- };
3486
-
3487
- window.clearEvents = function() {
3488
- if (window.dashboard) {
3489
- window.dashboard.clearEvents();
3490
- }
3491
- };
3492
-
3493
- window.exportEvents = function() {
3494
- if (window.dashboard) {
3495
- window.dashboard.exportEvents();
3496
- }
3497
- };
3498
-
3499
- window.clearSelection = function() {
3500
- if (window.dashboard) {
3501
- window.dashboard.clearSelection();
3502
- }
3503
- };
3504
-
3505
- window.switchTab = function(tabName) {
3506
- if (window.dashboard) {
3507
- window.dashboard.switchTab(tabName);
3508
- }
3509
- };
3510
-
3511
- // Detail view functions
3512
- window.showAgentDetailsByIndex = function(index) {
3513
- if (window.dashboard) {
3514
- window.dashboard.showAgentDetailsByIndex(index);
3515
- }
3516
- };
3517
-
3518
- window.showToolCallDetails = function(toolCallKey) {
3519
- if (window.dashboard) {
3520
- window.dashboard.showToolCallDetails(toolCallKey);
3521
- }
3522
- };
3523
-
3524
- window.showFileDetails = function(filePath) {
3525
- if (window.dashboard) {
3526
- window.dashboard.showFileDetails(filePath);
3527
- }
3528
- };
3529
-
3530
- // Debug function for testing scroll functionality
3531
- window.testScroll = async function() {
3532
- if (window.dashboard) {
3533
- console.log('๐Ÿงช Starting scroll functionality test...');
3534
- try {
3535
- const result = await window.dashboard.testScrollFunctionality();
3536
- console.log('๐Ÿ“‹ Test results:\n' + result);
3537
-
3538
- // Also display results in an alert for easier viewing
3539
- alert('Scroll Test Results:\n\n' + result);
3540
-
3541
- return result;
3542
- } catch (error) {
3543
- const errorMsg = `โŒ Test failed with error: ${error.message}`;
3544
- console.error(errorMsg, error);
3545
- alert(errorMsg);
3546
- return errorMsg;
3547
- }
3548
- } else {
3549
- const errorMsg = 'โŒ Dashboard not initialized';
3550
- console.error(errorMsg);
3551
- alert(errorMsg);
3552
- return errorMsg;
3553
- }
3554
- };
3555
-
3556
- // Simple direct scroll test function
3557
- window.testDirectScroll = function() {
3558
- if (!window.dashboard) {
3559
- console.error('โŒ Dashboard not initialized');
3560
- return 'Dashboard not initialized';
3561
- }
3562
-
3563
- const currentTab = window.dashboard.currentTab;
3564
- const listId = `${currentTab}-list`;
3565
- const element = document.getElementById(listId);
3566
-
3567
- if (!element) {
3568
- const msg = `โŒ Element ${listId} not found`;
3569
- console.error(msg);
3570
- return msg;
3571
- }
3572
-
3573
- console.log(`๐ŸŽฏ Direct scroll test on ${listId}`);
3574
- console.log(`Before: scrollTop=${element.scrollTop}, scrollHeight=${element.scrollHeight}, clientHeight=${element.clientHeight}`);
3575
-
3576
- // Try direct assignment to maximum scroll
3577
- element.scrollTop = 999999;
3578
-
3579
- setTimeout(() => {
3580
- console.log(`After: scrollTop=${element.scrollTop}, scrollHeight=${element.scrollHeight}, clientHeight=${element.clientHeight}`);
3581
- const success = element.scrollTop > 0 || element.scrollHeight <= element.clientHeight;
3582
- const result = `${success ? 'โœ…' : 'โŒ'} Direct scroll test: scrollTop=${element.scrollTop}`;
3583
- console.log(result);
3584
- alert(result);
3585
- return result;
3586
- }, 50);
3587
-
3588
- return 'Test running...';
3589
- };
3590
-
3591
- // CSS layout diagnostic function
3592
- window.diagnoseCSSLayout = function() {
3593
- if (!window.dashboard) {
3594
- return 'Dashboard not initialized';
3595
- }
3596
-
3597
- const currentTab = window.dashboard.currentTab;
3598
- const listId = `${currentTab}-list`;
3599
- const results = [];
3600
-
3601
- // Check the full hierarchy
3602
- const containers = [
3603
- 'events-wrapper',
3604
- 'events-container',
3605
- `${currentTab}-tab`,
3606
- listId
3607
- ];
3608
-
3609
- results.push(`๐Ÿ” CSS Layout Diagnosis for ${currentTab} tab:`);
3610
- results.push('');
3611
-
3612
- containers.forEach(id => {
3613
- const element = document.getElementById(id);
3614
- if (element) {
3615
- const computed = window.getComputedStyle(element);
3616
- const rect = element.getBoundingClientRect();
3617
-
3618
- results.push(`๐Ÿ“ฆ ${id}:`);
3619
- results.push(` Display: ${computed.display}`);
3620
- results.push(` Position: ${computed.position}`);
3621
- results.push(` Width: ${computed.width} (${rect.width}px)`);
3622
- results.push(` Height: ${computed.height} (${rect.height}px)`);
3623
- results.push(` Max-height: ${computed.maxHeight}`);
3624
- results.push(` Overflow-Y: ${computed.overflowY}`);
3625
- results.push(` Flex: ${computed.flex}`);
3626
- results.push(` Flex-direction: ${computed.flexDirection}`);
3627
-
3628
- if (element.scrollHeight !== element.clientHeight) {
3629
- results.push(` ๐Ÿ“Š ScrollHeight: ${element.scrollHeight}, ClientHeight: ${element.clientHeight}`);
3630
- results.push(` ๐Ÿ“Š ScrollTop: ${element.scrollTop} (can scroll: ${element.scrollHeight > element.clientHeight})`);
3631
- }
3632
- results.push('');
3633
- } else {
3634
- results.push(`โŒ ${id}: Not found`);
3635
- results.push('');
3636
- }
3637
- });
3638
-
3639
- const diagnosis = results.join('\n');
3640
- console.log(diagnosis);
3641
- alert(diagnosis);
3642
- return diagnosis;
3643
- };
3644
-
3645
- // Run all scroll diagnostics
3646
- window.runScrollDiagnostics = async function() {
3647
- console.log('๐Ÿ”ฌ Running complete scroll diagnostics...');
3648
-
3649
- // Step 1: CSS Layout diagnosis
3650
- console.log('\n=== STEP 1: CSS Layout Diagnosis ===');
3651
- const cssResult = window.diagnoseCSSLayout();
3652
-
3653
- // Step 2: Direct scroll test
3654
- console.log('\n=== STEP 2: Direct Scroll Test ===');
3655
- await new Promise(resolve => setTimeout(resolve, 1000));
3656
- const directResult = window.testDirectScroll();
3657
-
3658
- // Step 3: Full scroll functionality test
3659
- console.log('\n=== STEP 3: Full Scroll Functionality Test ===');
3660
- await new Promise(resolve => setTimeout(resolve, 2000));
3661
- const fullResult = await window.testScroll();
3662
-
3663
- const summary = `
3664
- ๐Ÿ”ฌ SCROLL DIAGNOSTICS COMPLETE
3665
- ===============================
3666
-
3667
- Step 1 - CSS Layout: See console for details
3668
- Step 2 - Direct Scroll: ${directResult}
3669
- Step 3 - Full Test: See alert for details
3670
-
3671
- Check browser console for complete logs.
3672
- `;
3673
-
3674
- console.log(summary);
3675
- return summary;
3676
- };
3677
-
3678
- // Git Diff Modal Functions
3679
- window.showGitDiffModal = function(filePath, timestamp, workingDir) {
3680
- // Use the dashboard's current working directory if not provided
3681
- if (!workingDir && window.dashboard && window.dashboard.currentWorkingDir) {
3682
- workingDir = window.dashboard.currentWorkingDir;
3683
- }
3684
-
3685
- // Create modal if it doesn't exist
3686
- let modal = document.getElementById('git-diff-modal');
3687
- if (!modal) {
3688
- modal = createGitDiffModal();
3689
- document.body.appendChild(modal);
3690
- }
3691
-
3692
- // Update modal content
3693
- updateGitDiffModal(modal, filePath, timestamp, workingDir);
3694
-
3695
- // Show the modal as flex container
3696
- modal.style.display = 'flex';
3697
- document.body.style.overflow = 'hidden'; // Prevent background scrolling
3698
- };
3699
-
3700
- window.hideGitDiffModal = function() {
3701
- const modal = document.getElementById('git-diff-modal');
3702
- if (modal) {
3703
- modal.style.display = 'none';
3704
- document.body.style.overflow = ''; // Restore background scrolling
3705
- }
3706
- };
3707
-
3708
- function createGitDiffModal() {
3709
- const modal = document.createElement('div');
3710
- modal.id = 'git-diff-modal';
3711
- modal.className = 'modal git-diff-modal';
3712
-
3713
- modal.innerHTML = `
3714
- <div class="modal-content git-diff-content">
3715
- <div class="git-diff-header">
3716
- <h2 class="git-diff-title">
3717
- <span class="git-diff-icon">๐Ÿ“‹</span>
3718
- <span class="git-diff-title-text">Git Diff</span>
3719
- </h2>
3720
- <div class="git-diff-meta">
3721
- <span class="git-diff-file-path"></span>
3722
- <span class="git-diff-timestamp"></span>
3723
- </div>
3724
- <button class="git-diff-close" onclick="hideGitDiffModal()">
3725
- <span>&times;</span>
3726
- </button>
3727
- </div>
3728
- <div class="git-diff-body">
3729
- <div class="git-diff-loading">
3730
- <div class="loading-spinner"></div>
3731
- <span>Loading git diff...</span>
3732
- </div>
3733
- <div class="git-diff-error" style="display: none;">
3734
- <div class="error-icon">โš ๏ธ</div>
3735
- <div class="error-message"></div>
3736
- <div class="error-suggestions"></div>
3737
- </div>
3738
- <div class="git-diff-content-area" style="display: none;">
3739
- <div class="git-diff-toolbar">
3740
- <div class="git-diff-info">
3741
- <span class="commit-hash"></span>
3742
- <span class="diff-method"></span>
3743
- </div>
3744
- <div class="git-diff-actions">
3745
- <button class="git-diff-copy" onclick="copyGitDiff()">
3746
- ๐Ÿ“‹ Copy
3747
- </button>
3748
- </div>
3749
- </div>
3750
- <div class="git-diff-scroll-wrapper">
3751
- <pre class="git-diff-display"><code class="git-diff-code"></code></pre>
3752
- </div>
3753
- </div>
3754
- </div>
3755
- </div>
3756
- `;
3757
-
3758
- // Close modal when clicking outside
3759
- modal.addEventListener('click', (e) => {
3760
- if (e.target === modal) {
3761
- hideGitDiffModal();
3762
- }
3763
- });
3764
-
3765
- // Close modal with Escape key
3766
- document.addEventListener('keydown', (e) => {
3767
- if (e.key === 'Escape' && modal.style.display === 'block') {
3768
- hideGitDiffModal();
3769
- }
3770
- });
3771
-
3772
- return modal;
3773
- }
3774
-
3775
- async function updateGitDiffModal(modal, filePath, timestamp, workingDir) {
3776
- // Update header info
3777
- const filePathElement = modal.querySelector('.git-diff-file-path');
3778
- const timestampElement = modal.querySelector('.git-diff-timestamp');
3779
-
3780
- filePathElement.textContent = filePath;
3781
- timestampElement.textContent = timestamp ? new Date(timestamp).toLocaleString() : 'Latest';
3782
-
3783
- // Show loading state
3784
- modal.querySelector('.git-diff-loading').style.display = 'flex';
3785
- modal.querySelector('.git-diff-error').style.display = 'none';
3786
- modal.querySelector('.git-diff-content-area').style.display = 'none';
3787
-
3788
- try {
3789
- // Get the Socket.IO server port with multiple fallbacks
3790
- let port = 8765; // Default fallback
3791
-
3792
- // Try to get port from socketClient first
3793
- if (window.dashboard && window.dashboard.socketClient && window.dashboard.socketClient.port) {
3794
- port = window.dashboard.socketClient.port;
3795
- }
3796
- // Fallback to port input field if socketClient port is not available
3797
- else {
3798
- const portInput = document.getElementById('port-input');
3799
- if (portInput && portInput.value) {
3800
- port = portInput.value;
3801
- }
3802
- }
3803
-
3804
-
3805
- // Build URL parameters
3806
- const params = new URLSearchParams({
3807
- file: filePath
3808
- });
3809
-
3810
- if (timestamp) {
3811
- params.append('timestamp', timestamp);
3812
- }
3813
- if (workingDir) {
3814
- params.append('working_dir', workingDir);
3815
- }
3816
-
3817
- const requestUrl = `http://localhost:${port}/api/git-diff?${params}`;
3818
- console.log('๐ŸŒ Making git diff request to:', requestUrl);
3819
-
3820
- // Test server connectivity first
3821
- try {
3822
- const healthResponse = await fetch(`http://localhost:${port}/health`, {
3823
- method: 'GET',
3824
- headers: {
3825
- 'Accept': 'application/json',
3826
- 'Content-Type': 'application/json'
3827
- },
3828
- mode: 'cors'
3829
- });
3830
-
3831
- if (!healthResponse.ok) {
3832
- throw new Error(`Server health check failed: ${healthResponse.status} ${healthResponse.statusText}`);
3833
- }
3834
-
3835
- console.log('โœ… Server health check passed');
3836
- } catch (healthError) {
3837
- throw new Error(`Cannot reach server at localhost:${port}. Health check failed: ${healthError.message}`);
3838
- }
3839
-
3840
- // Make the actual git diff request
3841
- const response = await fetch(requestUrl, {
3842
- method: 'GET',
3843
- headers: {
3844
- 'Accept': 'application/json',
3845
- 'Content-Type': 'application/json'
3846
- },
3847
- mode: 'cors'
3848
- });
3849
-
3850
- if (!response.ok) {
3851
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
3852
- }
3853
-
3854
- const result = await response.json();
3855
- console.log('๐Ÿ“ฆ Git diff response:', result);
3856
-
3857
- // Hide loading
3858
- modal.querySelector('.git-diff-loading').style.display = 'none';
3859
-
3860
- if (result.success) {
3861
- console.log('๐Ÿ“Š Displaying successful git diff');
3862
- // Show successful diff
3863
- displayGitDiff(modal, result);
3864
- } else {
3865
- console.log('โš ๏ธ Displaying git diff error:', result);
3866
- // Show error
3867
- displayGitDiffError(modal, result);
3868
- }
3869
-
3870
- } catch (error) {
3871
- console.error('โŒ Failed to fetch git diff:', error);
3872
- console.error('Error details:', {
3873
- name: error.name,
3874
- message: error.message,
3875
- stack: error.stack,
3876
- filePath,
3877
- timestamp,
3878
- workingDir
3879
- });
3880
-
3881
- modal.querySelector('.git-diff-loading').style.display = 'none';
3882
-
3883
- // Create detailed error message based on error type
3884
- let errorMessage = `Network error: ${error.message}`;
3885
- let suggestions = [];
3886
-
3887
- if (error.message.includes('Failed to fetch')) {
3888
- errorMessage = 'Failed to connect to the monitoring server';
3889
- suggestions = [
3890
- 'Check if the monitoring server is running on port 8765',
3891
- 'Verify the port configuration in the dashboard',
3892
- 'Check browser console for CORS or network errors',
3893
- 'Try refreshing the page and reconnecting'
3894
- ];
3895
- } else if (error.message.includes('health check failed')) {
3896
- errorMessage = error.message;
3897
- suggestions = [
3898
- 'The server may be starting up - try again in a few seconds',
3899
- 'Check if another process is using port 8765',
3900
- 'Restart the claude-mpm monitoring server'
3901
- ];
3902
- } else if (error.message.includes('HTTP')) {
3903
- errorMessage = `Server error: ${error.message}`;
3904
- suggestions = [
3905
- 'The server encountered an internal error',
3906
- 'Check the server logs for more details',
3907
- 'Try with a different file or working directory'
3908
- ];
3909
- }
3910
-
3911
- displayGitDiffError(modal, {
3912
- error: errorMessage,
3913
- file_path: filePath,
3914
- working_dir: workingDir,
3915
- suggestions: suggestions,
3916
- debug_info: {
3917
- error_type: error.name,
3918
- original_message: error.message,
3919
- port: window.dashboard?.socketClient?.port || document.getElementById('port-input')?.value || '8765',
3920
- timestamp: new Date().toISOString()
3921
- }
3922
- });
3923
- }
3924
- }
3925
-
3926
- function highlightGitDiff(diffText) {
3927
- /**
3928
- * Apply basic syntax highlighting to git diff output
3929
- * WHY: Git diffs have a standard format that can be highlighted for better readability:
3930
- * - Lines starting with '+' are additions (green)
3931
- * - Lines starting with '-' are deletions (red)
3932
- * - Lines starting with '@@' are context headers (blue)
3933
- * - File headers and metadata get special formatting
3934
- */
3935
- return diffText
3936
- .split('\n')
3937
- .map(line => {
3938
- // Escape HTML entities
3939
- const escaped = line
3940
- .replace(/&/g, '&amp;')
3941
- .replace(/</g, '&lt;')
3942
- .replace(/>/g, '&gt;');
3943
-
3944
- // Apply diff highlighting
3945
- if (line.startsWith('+++') || line.startsWith('---')) {
3946
- return `<span class="diff-header">${escaped}</span>`;
3947
- } else if (line.startsWith('@@')) {
3948
- return `<span class="diff-meta">${escaped}</span>`;
3949
- } else if (line.startsWith('+')) {
3950
- return `<span class="diff-addition">${escaped}</span>`;
3951
- } else if (line.startsWith('-')) {
3952
- return `<span class="diff-deletion">${escaped}</span>`;
3953
- } else if (line.startsWith('commit ') || line.startsWith('Author:') || line.startsWith('Date:')) {
3954
- return `<span class="diff-header">${escaped}</span>`;
3955
- } else {
3956
- return `<span class="diff-context">${escaped}</span>`;
3957
- }
3958
- })
3959
- .join('\n');
3960
- }
3961
-
3962
- function displayGitDiff(modal, result) {
3963
- console.log('๐Ÿ“ displayGitDiff called with:', result);
3964
- const contentArea = modal.querySelector('.git-diff-content-area');
3965
- const commitHashElement = modal.querySelector('.commit-hash');
3966
- const methodElement = modal.querySelector('.diff-method');
3967
- const codeElement = modal.querySelector('.git-diff-code');
3968
-
3969
- console.log('๐Ÿ” Elements found:', {
3970
- contentArea: !!contentArea,
3971
- commitHashElement: !!commitHashElement,
3972
- methodElement: !!methodElement,
3973
- codeElement: !!codeElement
3974
- });
3975
-
3976
- // Update metadata
3977
- if (commitHashElement) commitHashElement.textContent = `Commit: ${result.commit_hash}`;
3978
- if (methodElement) methodElement.textContent = `Method: ${result.method}`;
3979
-
3980
- // Update diff content with basic syntax highlighting
3981
- if (codeElement && result.diff) {
3982
- console.log('๐Ÿ’ก Setting diff content, length:', result.diff.length);
3983
- codeElement.innerHTML = highlightGitDiff(result.diff);
3984
-
3985
- // Force scrolling to work by setting explicit heights
3986
- const wrapper = modal.querySelector('.git-diff-scroll-wrapper');
3987
- if (wrapper) {
3988
- // Give it a moment for content to render
3989
- setTimeout(() => {
3990
- const modalContent = modal.querySelector('.modal-content');
3991
- const header = modal.querySelector('.git-diff-header');
3992
- const toolbar = modal.querySelector('.git-diff-toolbar');
3993
-
3994
- const modalHeight = modalContent?.offsetHeight || 0;
3995
- const headerHeight = header?.offsetHeight || 0;
3996
- const toolbarHeight = toolbar?.offsetHeight || 0;
3997
-
3998
- const availableHeight = modalHeight - headerHeight - toolbarHeight - 40; // 40px for padding
3999
-
4000
- console.log('๐ŸŽฏ Setting explicit scroll height:', {
4001
- modalHeight,
4002
- headerHeight,
4003
- toolbarHeight,
4004
- availableHeight
4005
- });
4006
-
4007
- wrapper.style.maxHeight = `${availableHeight}px`;
4008
- wrapper.style.overflowY = 'auto';
4009
- }, 50);
4010
- }
4011
- } else {
4012
- console.warn('โš ๏ธ Missing codeElement or diff data');
4013
- }
4014
-
4015
- // Show content area
4016
- if (contentArea) {
4017
- contentArea.style.display = 'block';
4018
- console.log('โœ… Content area displayed');
4019
-
4020
- // Debug height information and force scrolling test
4021
- setTimeout(() => {
4022
- const modal = document.querySelector('.modal-content.git-diff-content');
4023
- const body = document.querySelector('.git-diff-body');
4024
- const wrapper = document.querySelector('.git-diff-scroll-wrapper');
4025
- const display = document.querySelector('.git-diff-display');
4026
- const code = document.querySelector('.git-diff-code');
4027
-
4028
- console.log('๐Ÿ” Height debugging:', {
4029
- modalHeight: modal?.offsetHeight,
4030
- bodyHeight: body?.offsetHeight,
4031
- wrapperHeight: wrapper?.offsetHeight,
4032
- wrapperScrollHeight: wrapper?.scrollHeight,
4033
- displayHeight: display?.offsetHeight,
4034
- displayScrollHeight: display?.scrollHeight,
4035
- codeHeight: code?.offsetHeight,
4036
- wrapperStyle: wrapper ? window.getComputedStyle(wrapper).overflow : null,
4037
- bodyStyle: body ? window.getComputedStyle(body).overflow : null
4038
- });
4039
-
4040
- // Force test - add a lot of content to test scrolling
4041
- if (code && code.textContent.length < 1000) {
4042
- console.log('๐Ÿงช Adding test content for scrolling...');
4043
- code.innerHTML = code.innerHTML + '\n\n' + '// TEST SCROLLING\n'.repeat(100);
4044
- }
4045
-
4046
- // Force fix scrolling with inline styles
4047
- if (wrapper) {
4048
- console.log('๐Ÿ”ง Applying scrolling fix...');
4049
- wrapper.style.height = '100%';
4050
- wrapper.style.overflow = 'auto';
4051
- wrapper.style.maxHeight = 'calc(100% - 60px)'; // Account for toolbar
4052
- }
4053
-
4054
- // Also check parent heights
4055
- const contentArea = document.querySelector('.git-diff-content-area');
4056
- if (contentArea) {
4057
- const computedStyle = window.getComputedStyle(contentArea);
4058
- console.log('๐Ÿ“ Content area height:', computedStyle.height);
4059
- if (computedStyle.height === 'auto' || !computedStyle.height) {
4060
- contentArea.style.height = '100%';
4061
- }
4062
- }
4063
- }, 100);
4064
- }
4065
- }
4066
-
4067
- function displayGitDiffError(modal, result) {
4068
- const errorArea = modal.querySelector('.git-diff-error');
4069
- const messageElement = modal.querySelector('.error-message');
4070
- const suggestionsElement = modal.querySelector('.error-suggestions');
4071
-
4072
- messageElement.textContent = result.error || 'Unknown error occurred';
4073
-
4074
- if (result.suggestions && result.suggestions.length > 0) {
4075
- suggestionsElement.innerHTML = `
4076
- <h4>Suggestions:</h4>
4077
- <ul>
4078
- ${result.suggestions.map(s => `<li>${s}</li>`).join('')}
4079
- </ul>
4080
- `;
4081
- } else {
4082
- suggestionsElement.innerHTML = '';
4083
- }
4084
-
4085
- errorArea.style.display = 'block';
4086
- }
4087
-
4088
- window.copyGitDiff = function() {
4089
- const modal = document.getElementById('git-diff-modal');
4090
- if (!modal) return;
4091
-
4092
- const codeElement = modal.querySelector('.git-diff-code');
4093
- if (!codeElement) return;
4094
-
4095
- const text = codeElement.textContent;
4096
-
4097
- if (navigator.clipboard && navigator.clipboard.writeText) {
4098
- navigator.clipboard.writeText(text).then(() => {
4099
- // Show brief feedback
4100
- const button = modal.querySelector('.git-diff-copy');
4101
- const originalText = button.textContent;
4102
- button.textContent = 'โœ… Copied!';
4103
- setTimeout(() => {
4104
- button.textContent = originalText;
4105
- }, 2000);
4106
- }).catch(err => {
4107
- console.error('Failed to copy text:', err);
4108
- });
4109
- } else {
4110
- // Fallback for older browsers
4111
- const textarea = document.createElement('textarea');
4112
- textarea.value = text;
4113
- document.body.appendChild(textarea);
4114
- textarea.select();
4115
- document.execCommand('copy');
4116
- document.body.removeChild(textarea);
4117
-
4118
- const button = modal.querySelector('.git-diff-copy');
4119
- const originalText = button.textContent;
4120
- button.textContent = 'โœ… Copied!';
4121
- setTimeout(() => {
4122
- button.textContent = originalText;
4123
- }, 2000);
4124
- }
4125
- };
4126
-
4127
- // Initialize dashboard when DOM is loaded
4128
- document.addEventListener('DOMContentLoaded', () => {
4129
- window.dashboard = new Dashboard();
4130
- console.log('Dashboard ready');
4131
- });
4132
-
4133
- // Export for use in other modules
4134
- window.Dashboard = Dashboard;