claude-mpm 4.2.7__py3-none-any.whl → 4.2.11__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 (57) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/cli/commands/dashboard.py +62 -120
  3. claude_mpm/cli/commands/monitor.py +71 -212
  4. claude_mpm/cli/commands/run.py +33 -33
  5. claude_mpm/cli/parser.py +79 -2
  6. claude_mpm/cli/parsers/__init__.py +29 -0
  7. claude_mpm/dashboard/static/css/code-tree.css +16 -4
  8. claude_mpm/dashboard/static/css/dashboard.css +15 -1
  9. claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
  10. claude_mpm/dashboard/static/dist/components/file-viewer.js +2 -0
  11. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  12. claude_mpm/dashboard/static/dist/components/unified-data-viewer.js +1 -1
  13. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  14. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  15. claude_mpm/dashboard/static/js/components/code-tree.js +775 -142
  16. claude_mpm/dashboard/static/js/components/file-viewer.js +538 -0
  17. claude_mpm/dashboard/static/js/components/module-viewer.js +26 -0
  18. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +166 -14
  19. claude_mpm/dashboard/static/js/dashboard.js +108 -91
  20. claude_mpm/dashboard/static/js/socket-client.js +9 -7
  21. claude_mpm/dashboard/templates/index.html +5 -2
  22. claude_mpm/hooks/claude_hooks/hook_handler.py +1 -11
  23. claude_mpm/hooks/claude_hooks/services/connection_manager.py +54 -59
  24. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +112 -72
  25. claude_mpm/services/agents/deployment/agent_format_converter.py +3 -3
  26. claude_mpm/services/agents/deployment/agent_template_builder.py +3 -5
  27. claude_mpm/services/cli/unified_dashboard_manager.py +354 -0
  28. claude_mpm/services/monitor/__init__.py +20 -0
  29. claude_mpm/services/monitor/daemon.py +256 -0
  30. claude_mpm/services/monitor/event_emitter.py +279 -0
  31. claude_mpm/services/monitor/handlers/__init__.py +20 -0
  32. claude_mpm/services/monitor/handlers/code_analysis.py +334 -0
  33. claude_mpm/services/monitor/handlers/dashboard.py +298 -0
  34. claude_mpm/services/monitor/handlers/hooks.py +491 -0
  35. claude_mpm/services/monitor/management/__init__.py +18 -0
  36. claude_mpm/services/monitor/management/health.py +124 -0
  37. claude_mpm/services/monitor/management/lifecycle.py +298 -0
  38. claude_mpm/services/monitor/server.py +442 -0
  39. claude_mpm/services/socketio/client_proxy.py +20 -12
  40. claude_mpm/services/socketio/dashboard_server.py +4 -4
  41. claude_mpm/services/socketio/monitor_client.py +4 -6
  42. claude_mpm/tools/code_tree_analyzer.py +33 -17
  43. {claude_mpm-4.2.7.dist-info → claude_mpm-4.2.11.dist-info}/METADATA +1 -1
  44. {claude_mpm-4.2.7.dist-info → claude_mpm-4.2.11.dist-info}/RECORD +48 -43
  45. claude_mpm/cli/commands/socketio_monitor.py +0 -233
  46. claude_mpm/scripts/socketio_daemon.py +0 -571
  47. claude_mpm/scripts/socketio_daemon_hardened.py +0 -937
  48. claude_mpm/scripts/socketio_daemon_wrapper.py +0 -78
  49. claude_mpm/scripts/socketio_server_manager.py +0 -349
  50. claude_mpm/services/cli/dashboard_launcher.py +0 -423
  51. claude_mpm/services/cli/socketio_manager.py +0 -595
  52. claude_mpm/services/dashboard/stable_server.py +0 -962
  53. claude_mpm/services/socketio/monitor_server.py +0 -505
  54. {claude_mpm-4.2.7.dist-info → claude_mpm-4.2.11.dist-info}/WHEEL +0 -0
  55. {claude_mpm-4.2.7.dist-info → claude_mpm-4.2.11.dist-info}/entry_points.txt +0 -0
  56. {claude_mpm-4.2.7.dist-info → claude_mpm-4.2.11.dist-info}/licenses/LICENSE +0 -0
  57. {claude_mpm-4.2.7.dist-info → claude_mpm-4.2.11.dist-info}/top_level.txt +0 -0
@@ -16,6 +16,26 @@ class UnifiedDataViewer {
16
16
  this.container = document.getElementById(containerId);
17
17
  this.currentData = null;
18
18
  this.currentType = null;
19
+
20
+ // Global JSON visibility state - synchronized with localStorage
21
+ // This ensures all JSON sections maintain consistent state
22
+ this.globalJsonExpanded = localStorage.getItem('dashboard-json-expanded') === 'true';
23
+
24
+ // Separate state for "Full Event Data" sections - uses its own localStorage key
25
+ // This allows independent control of Full Event Data visibility
26
+ this.fullEventDataExpanded = localStorage.getItem('dashboard-full-event-expanded') === 'true';
27
+
28
+ // Listen for global JSON toggle changes from other components
29
+ document.addEventListener('jsonToggleChanged', (e) => {
30
+ this.globalJsonExpanded = e.detail.expanded;
31
+ this.updateAllJsonSections();
32
+ });
33
+
34
+ // Listen for full event data toggle changes
35
+ document.addEventListener('fullEventToggleChanged', (e) => {
36
+ this.fullEventDataExpanded = e.detail.expanded;
37
+ this.updateAllFullEventSections();
38
+ });
19
39
  }
20
40
 
21
41
  /**
@@ -1533,6 +1553,112 @@ class UnifiedDataViewer {
1533
1553
  div.textContent = text;
1534
1554
  return div.innerHTML;
1535
1555
  }
1556
+
1557
+ /**
1558
+ * Toggle JSON section visibility and update global state
1559
+ * WHY: Maintains sticky state across all JSON sections for consistent behavior
1560
+ * @param {string} sectionId - ID of the specific section being toggled
1561
+ * @param {HTMLElement} button - The button element that was clicked
1562
+ */
1563
+ toggleJsonSection(sectionId, button) {
1564
+ // Toggle the global state
1565
+ this.globalJsonExpanded = !this.globalJsonExpanded;
1566
+
1567
+ // Persist the preference to localStorage
1568
+ localStorage.setItem('dashboard-json-expanded', this.globalJsonExpanded.toString());
1569
+
1570
+ // Update ALL JSON sections on the page
1571
+ this.updateAllJsonSections();
1572
+
1573
+ // Dispatch event to notify other components (like module-viewer) of the change
1574
+ document.dispatchEvent(new CustomEvent('jsonToggleChanged', {
1575
+ detail: { expanded: this.globalJsonExpanded }
1576
+ }));
1577
+ }
1578
+
1579
+ /**
1580
+ * Toggle Full Event Data section visibility and update state
1581
+ * WHY: Maintains separate sticky state for Full Event Data sections
1582
+ * @param {string} sectionId - ID of the specific section being toggled
1583
+ * @param {HTMLElement} button - The button element that was clicked
1584
+ */
1585
+ toggleFullEventSection(sectionId, button) {
1586
+ // Toggle the full event data state
1587
+ this.fullEventDataExpanded = !this.fullEventDataExpanded;
1588
+
1589
+ // Persist the preference to localStorage
1590
+ localStorage.setItem('dashboard-full-event-expanded', this.fullEventDataExpanded.toString());
1591
+
1592
+ // Update ALL Full Event sections on the page
1593
+ this.updateAllFullEventSections();
1594
+
1595
+ // Dispatch event to notify other components of the change
1596
+ document.dispatchEvent(new CustomEvent('fullEventToggleChanged', {
1597
+ detail: { expanded: this.fullEventDataExpanded }
1598
+ }));
1599
+ }
1600
+
1601
+ /**
1602
+ * Update all JSON sections on the page to match global state
1603
+ * WHY: Ensures all "Structured Data" sections maintain consistent visibility
1604
+ */
1605
+ updateAllJsonSections() {
1606
+ // Find all unified JSON sections (NOT full event sections)
1607
+ const allJsonContents = document.querySelectorAll('.unified-json-content');
1608
+ const allJsonButtons = document.querySelectorAll('.unified-json-toggle');
1609
+
1610
+ // Update each JSON section
1611
+ allJsonContents.forEach(content => {
1612
+ if (this.globalJsonExpanded) {
1613
+ content.style.display = 'block';
1614
+ } else {
1615
+ content.style.display = 'none';
1616
+ }
1617
+ });
1618
+
1619
+ // Update all button states
1620
+ allJsonButtons.forEach(button => {
1621
+ const title = button.textContent.substring(2); // Remove arrow
1622
+ if (this.globalJsonExpanded) {
1623
+ button.innerHTML = '▼ ' + title;
1624
+ button.classList.add('expanded');
1625
+ } else {
1626
+ button.innerHTML = '▶ ' + title;
1627
+ button.classList.remove('expanded');
1628
+ }
1629
+ });
1630
+ }
1631
+
1632
+ /**
1633
+ * Update all Full Event Data sections on the page to match state
1634
+ * WHY: Ensures all "Full Event Data" sections maintain consistent visibility
1635
+ */
1636
+ updateAllFullEventSections() {
1637
+ // Find all full event sections
1638
+ const allFullEventContents = document.querySelectorAll('.full-event-content');
1639
+ const allFullEventButtons = document.querySelectorAll('.full-event-toggle');
1640
+
1641
+ // Update each full event section
1642
+ allFullEventContents.forEach(content => {
1643
+ if (this.fullEventDataExpanded) {
1644
+ content.style.display = 'block';
1645
+ } else {
1646
+ content.style.display = 'none';
1647
+ }
1648
+ });
1649
+
1650
+ // Update all button states
1651
+ allFullEventButtons.forEach(button => {
1652
+ const title = button.textContent.substring(2); // Remove arrow
1653
+ if (this.fullEventDataExpanded) {
1654
+ button.innerHTML = '▼ ' + title;
1655
+ button.classList.add('expanded');
1656
+ } else {
1657
+ button.innerHTML = '▶ ' + title;
1658
+ button.classList.remove('expanded');
1659
+ }
1660
+ });
1661
+ }
1536
1662
 
1537
1663
  /**
1538
1664
  * Create a collapsible JSON viewer for secondary details
@@ -1545,22 +1671,28 @@ class UnifiedDataViewer {
1545
1671
  // Filter out sensitive or overly verbose properties
1546
1672
  const cleanData = this.cleanDataForDisplay(data);
1547
1673
 
1674
+ // Determine which state to use based on title
1675
+ // "Full Event Data" and similar titles use the fullEventDataExpanded state
1676
+ // Other titles use the global JSON state (for backward compatibility)
1677
+ const isFullEventData = title.includes('Full Event') || title.includes('Full Details') ||
1678
+ title.includes('Full Agent') || title.includes('Full Tool');
1679
+ const isExpanded = isFullEventData ? this.fullEventDataExpanded : this.globalJsonExpanded;
1680
+ const display = isExpanded ? 'block' : 'none';
1681
+ const arrow = isExpanded ? '▼' : '▶';
1682
+ const expandedClass = isExpanded ? 'expanded' : '';
1683
+
1684
+ // Use different toggle function based on section type
1685
+ const toggleFunction = isFullEventData ? 'toggleFullEventSection' : 'toggleJsonSection';
1686
+
1548
1687
  return `
1549
1688
  <div class="collapsible-json-section">
1550
- <button class="collapsible-json-toggle" onclick="
1551
- const content = document.getElementById('${sectionId}');
1552
- const button = this;
1553
- if (content.style.display === 'none' || content.style.display === '') {
1554
- content.style.display = 'block';
1555
- button.classList.add('expanded');
1556
- button.innerHTML = ' ${title}';
1557
- } else {
1558
- content.style.display = 'none';
1559
- button.classList.remove('expanded');
1560
- button.innerHTML = '▶ ${title}';
1561
- }
1562
- ">▶ ${title}</button>
1563
- <div id="${sectionId}" class="collapsible-json-content" style="display: none;">
1689
+ <button class="collapsible-json-toggle ${isFullEventData ? 'full-event-toggle' : 'unified-json-toggle'} ${expandedClass}"
1690
+ data-section-id="${sectionId}"
1691
+ data-is-full-event="${isFullEventData}"
1692
+ onclick="window.unifiedDataViewer.${toggleFunction}('${sectionId}', this)">
1693
+ ${arrow} ${title}
1694
+ </button>
1695
+ <div id="${sectionId}" class="collapsible-json-content ${isFullEventData ? 'full-event-content' : 'unified-json-content'}" style="display: ${display};">
1564
1696
  <pre class="json-viewer">${this.escapeHtml(JSON.stringify(cleanData, null, 2))}</pre>
1565
1697
  </div>
1566
1698
  </div>
@@ -1639,6 +1771,26 @@ export default UnifiedDataViewer;
1639
1771
  // Make globally available for non-module usage
1640
1772
  window.UnifiedDataViewer = UnifiedDataViewer;
1641
1773
 
1774
+ // Create a global instance immediately for inline onclick handlers
1775
+ // This ensures the instance is available when HTML is rendered dynamically
1776
+ if (typeof window !== 'undefined') {
1777
+ // Always create/update the global instance
1778
+ window.unifiedDataViewer = new UnifiedDataViewer();
1779
+
1780
+ // Also expose the methods directly on window as a fallback
1781
+ window.toggleFullEventSection = function(sectionId, button) {
1782
+ if (window.unifiedDataViewer) {
1783
+ window.unifiedDataViewer.toggleFullEventSection(sectionId, button);
1784
+ }
1785
+ };
1786
+
1787
+ window.toggleJsonSection = function(sectionId, button) {
1788
+ if (window.unifiedDataViewer) {
1789
+ window.unifiedDataViewer.toggleJsonSection(sectionId, button);
1790
+ }
1791
+ };
1792
+ }
1793
+
1642
1794
  // Create a global instance for inline preview functionality
1643
1795
  if (typeof window !== 'undefined') {
1644
1796
  window.addEventListener('DOMContentLoaded', function() {
@@ -1073,35 +1073,7 @@ window.switchTab = function(tabName) {
1073
1073
  }
1074
1074
  };
1075
1075
 
1076
- // File Viewer Modal Functions - REMOVED DUPLICATE (keeping the one at line 1553)
1077
- window.showFileViewerModal = function(filePath, workingDir) {
1078
- // Use the dashboard's current working directory if not provided
1079
- if (!workingDir && window.dashboard && window.dashboard.currentWorkingDir) {
1080
- workingDir = window.dashboard.currentWorkingDir;
1081
- }
1082
-
1083
- // Create modal if it doesn't exist
1084
- let modal = document.getElementById('file-viewer-modal');
1085
- if (!modal) {
1086
- modal = createFileViewerModal();
1087
- document.body.appendChild(modal);
1088
- }
1089
-
1090
- // Update modal content
1091
- updateFileViewerModal(modal, filePath, workingDir);
1092
-
1093
- // Show the modal as flex container
1094
- modal.style.display = 'flex';
1095
- document.body.style.overflow = 'hidden'; // Prevent background scrolling
1096
- };
1097
-
1098
- window.hideFileViewerModal = function() {
1099
- const modal = document.getElementById('file-viewer-modal');
1100
- if (modal) {
1101
- modal.style.display = 'none';
1102
- document.body.style.overflow = ''; // Restore background scrolling
1103
- }
1104
- };
1076
+ // File Viewer Modal Functions - Removed broken duplicate (using the one at line 1505)
1105
1077
 
1106
1078
  window.copyFileContent = function() {
1107
1079
  const modal = document.getElementById('file-viewer-modal');
@@ -1116,11 +1088,13 @@ window.copyFileContent = function() {
1116
1088
  navigator.clipboard.writeText(text).then(() => {
1117
1089
  // Show brief feedback
1118
1090
  const button = modal.querySelector('.file-content-copy');
1119
- const originalText = button.textContent;
1120
- button.textContent = '✅ Copied!';
1121
- setTimeout(() => {
1122
- button.textContent = originalText;
1123
- }, 2000);
1091
+ if (button) {
1092
+ const originalText = button.textContent;
1093
+ button.textContent = '✅ Copied!';
1094
+ setTimeout(() => {
1095
+ button.textContent = originalText;
1096
+ }, 2000);
1097
+ }
1124
1098
  }).catch(err => {
1125
1099
  console.error('Failed to copy text:', err);
1126
1100
  });
@@ -1134,11 +1108,13 @@ window.copyFileContent = function() {
1134
1108
  document.body.removeChild(textarea);
1135
1109
 
1136
1110
  const button = modal.querySelector('.file-content-copy');
1137
- const originalText = button.textContent;
1138
- button.textContent = '✅ Copied!';
1139
- setTimeout(() => {
1140
- button.textContent = originalText;
1141
- }, 2000);
1111
+ if (button) {
1112
+ const originalText = button.textContent;
1113
+ button.textContent = '✅ Copied!';
1114
+ setTimeout(() => {
1115
+ button.textContent = originalText;
1116
+ }, 2000);
1117
+ }
1142
1118
  }
1143
1119
  };
1144
1120
 
@@ -1214,13 +1190,27 @@ async function updateFileViewerModal(modal, filePath, workingDir) {
1214
1190
  const filePathElement = modal.querySelector('.file-viewer-file-path');
1215
1191
  const fileSizeElement = modal.querySelector('.file-viewer-file-size');
1216
1192
 
1217
- filePathElement.textContent = filePath;
1218
- fileSizeElement.textContent = '';
1193
+ if (filePathElement) {
1194
+ filePathElement.textContent = filePath;
1195
+ }
1196
+ if (fileSizeElement) {
1197
+ fileSizeElement.textContent = '';
1198
+ }
1219
1199
 
1220
1200
  // Show loading state
1221
- modal.querySelector('.file-viewer-loading').style.display = 'flex';
1222
- modal.querySelector('.file-viewer-error').style.display = 'none';
1223
- modal.querySelector('.file-viewer-content-area').style.display = 'none';
1201
+ const loadingElement = modal.querySelector('.file-viewer-loading');
1202
+ const errorElement = modal.querySelector('.file-viewer-error');
1203
+ const contentArea = modal.querySelector('.file-viewer-content-area');
1204
+
1205
+ if (loadingElement) {
1206
+ loadingElement.style.display = 'flex';
1207
+ }
1208
+ if (errorElement) {
1209
+ errorElement.style.display = 'none';
1210
+ }
1211
+ if (contentArea) {
1212
+ contentArea.style.display = 'none';
1213
+ }
1224
1214
 
1225
1215
  try {
1226
1216
  // Get the Socket.IO client
@@ -1264,7 +1254,10 @@ async function updateFileViewerModal(modal, filePath, workingDir) {
1264
1254
  // File content received successfully
1265
1255
 
1266
1256
  // Hide loading
1267
- modal.querySelector('.file-viewer-loading').style.display = 'none';
1257
+ const loadingEl = modal.querySelector('.file-viewer-loading');
1258
+ if (loadingEl) {
1259
+ loadingEl.style.display = 'none';
1260
+ }
1268
1261
 
1269
1262
  // Show successful content
1270
1263
  displayFileContent(modal, result);
@@ -1272,7 +1265,10 @@ async function updateFileViewerModal(modal, filePath, workingDir) {
1272
1265
  } catch (error) {
1273
1266
  console.error('❌ Failed to fetch file content:', error);
1274
1267
 
1275
- modal.querySelector('.file-viewer-loading').style.display = 'none';
1268
+ const loadingEl2 = modal.querySelector('.file-viewer-loading');
1269
+ if (loadingEl2) {
1270
+ loadingEl2.style.display = 'none';
1271
+ }
1276
1272
 
1277
1273
  // Create detailed error message
1278
1274
  let errorMessage = error.message || 'Unknown error occurred';
@@ -1373,21 +1369,25 @@ function displayFileError(modal, result) {
1373
1369
 
1374
1370
  let errorMessage = result.error || 'Unknown error occurred';
1375
1371
 
1376
- messageElement.innerHTML = `
1377
- <div class="error-main">${errorMessage}</div>
1378
- ${result.file_path ? `<div class="error-file">File: ${result.file_path}</div>` : ''}
1379
- ${result.working_dir ? `<div class="error-dir">Working directory: ${result.working_dir}</div>` : ''}
1380
- `;
1381
-
1382
- if (result.suggestions && result.suggestions.length > 0) {
1383
- suggestionsElement.innerHTML = `
1384
- <h4>Suggestions:</h4>
1385
- <ul>
1386
- ${result.suggestions.map(s => `<li>${s}</li>`).join('')}
1387
- </ul>
1372
+ if (messageElement) {
1373
+ messageElement.innerHTML = `
1374
+ <div class="error-main">${errorMessage}</div>
1375
+ ${result.file_path ? `<div class="error-file">File: ${result.file_path}</div>` : ''}
1376
+ ${result.working_dir ? `<div class="error-dir">Working directory: ${result.working_dir}</div>` : ''}
1388
1377
  `;
1389
- } else {
1390
- suggestionsElement.innerHTML = '';
1378
+ }
1379
+
1380
+ if (suggestionsElement) {
1381
+ if (result.suggestions && result.suggestions.length > 0) {
1382
+ suggestionsElement.innerHTML = `
1383
+ <h4>Suggestions:</h4>
1384
+ <ul>
1385
+ ${result.suggestions.map(s => `<li>${s}</li>`).join('')}
1386
+ </ul>
1387
+ `;
1388
+ } else {
1389
+ suggestionsElement.innerHTML = '';
1390
+ }
1391
1391
  }
1392
1392
 
1393
1393
  console.log('📋 Displaying file viewer error:', {
@@ -1396,7 +1396,9 @@ function displayFileError(modal, result) {
1396
1396
  suggestions: result.suggestions
1397
1397
  });
1398
1398
 
1399
- errorArea.style.display = 'block';
1399
+ if (errorArea) {
1400
+ errorArea.style.display = 'block';
1401
+ }
1400
1402
  }
1401
1403
 
1402
1404
  function highlightCode(code, extension) {
@@ -1502,7 +1504,7 @@ function formatFileSize(bytes) {
1502
1504
  }
1503
1505
 
1504
1506
  // File Viewer Modal Functions
1505
- window.showFileViewerModal = function(filePath) {
1507
+ window.showFileViewerModal = async function(filePath) {
1506
1508
  // Use the dashboard's current working directory
1507
1509
  let workingDir = '';
1508
1510
  if (window.dashboard && window.dashboard.currentWorkingDir) {
@@ -1514,10 +1516,15 @@ window.showFileViewerModal = function(filePath) {
1514
1516
  if (!modal) {
1515
1517
  modal = createFileViewerModal();
1516
1518
  document.body.appendChild(modal);
1519
+
1520
+ // Small delay to ensure DOM is fully updated
1521
+ await new Promise(resolve => setTimeout(resolve, 10));
1517
1522
  }
1518
1523
 
1519
1524
  // Update modal content
1520
- updateFileViewerModal(modal, filePath, workingDir);
1525
+ updateFileViewerModal(modal, filePath, workingDir).catch(error => {
1526
+ console.error('Error updating file viewer modal:', error);
1527
+ });
1521
1528
 
1522
1529
  // Show the modal as flex container
1523
1530
  modal.style.display = 'flex';
@@ -1545,11 +1552,13 @@ window.copyFileContent = function() {
1545
1552
  navigator.clipboard.writeText(text).then(() => {
1546
1553
  // Show brief feedback
1547
1554
  const button = modal.querySelector('.file-content-copy');
1548
- const originalText = button.textContent;
1549
- button.textContent = '✅ Copied!';
1550
- setTimeout(() => {
1551
- button.textContent = originalText;
1552
- }, 2000);
1555
+ if (button) {
1556
+ const originalText = button.textContent;
1557
+ button.textContent = '✅ Copied!';
1558
+ setTimeout(() => {
1559
+ button.textContent = originalText;
1560
+ }, 2000);
1561
+ }
1553
1562
  }).catch(err => {
1554
1563
  console.error('Failed to copy text:', err);
1555
1564
  });
@@ -1563,11 +1572,13 @@ window.copyFileContent = function() {
1563
1572
  document.body.removeChild(textarea);
1564
1573
 
1565
1574
  const button = modal.querySelector('.file-content-copy');
1566
- const originalText = button.textContent;
1567
- button.textContent = '✅ Copied!';
1568
- setTimeout(() => {
1569
- button.textContent = originalText;
1570
- }, 2000);
1575
+ if (button) {
1576
+ const originalText = button.textContent;
1577
+ button.textContent = '✅ Copied!';
1578
+ setTimeout(() => {
1579
+ button.textContent = originalText;
1580
+ }, 2000);
1581
+ }
1571
1582
  }
1572
1583
  };
1573
1584
 
@@ -1592,25 +1603,29 @@ function displayFileContentError(modal, result) {
1592
1603
  errorMessage = `⚠️ ${errorMessage}`;
1593
1604
  }
1594
1605
 
1595
- messageElement.textContent = errorMessage;
1606
+ if (messageElement) {
1607
+ messageElement.textContent = errorMessage;
1608
+ }
1596
1609
 
1597
1610
  // Add suggestions if available
1598
- if (result.suggestions && result.suggestions.length > 0) {
1599
- suggestionsElement.innerHTML = `
1600
- <h4>Suggestions:</h4>
1601
- <ul>
1602
- ${result.suggestions.map(suggestion => `<li>${suggestion}</li>`).join('')}
1603
- </ul>
1604
- `;
1605
- } else {
1606
- suggestionsElement.innerHTML = `
1607
- <h4>Try:</h4>
1608
- <ul>
1609
- <li>Check if the file exists and is readable</li>
1610
- <li>Verify file permissions</li>
1611
- <li>Ensure the monitoring server has access to this file</li>
1612
- </ul>
1613
- `;
1611
+ if (suggestionsElement) {
1612
+ if (result.suggestions && result.suggestions.length > 0) {
1613
+ suggestionsElement.innerHTML = `
1614
+ <h4>Suggestions:</h4>
1615
+ <ul>
1616
+ ${result.suggestions.map(suggestion => `<li>${suggestion}</li>`).join('')}
1617
+ </ul>
1618
+ `;
1619
+ } else {
1620
+ suggestionsElement.innerHTML = `
1621
+ <h4>Try:</h4>
1622
+ <ul>
1623
+ <li>Check if the file exists and is readable</li>
1624
+ <li>Verify file permissions</li>
1625
+ <li>Ensure the monitoring server has access to this file</li>
1626
+ </ul>
1627
+ `;
1628
+ }
1614
1629
  }
1615
1630
 
1616
1631
  console.log('📋 Displaying file content error:', {
@@ -1619,7 +1634,9 @@ function displayFileContentError(modal, result) {
1619
1634
  suggestions: result.suggestions
1620
1635
  });
1621
1636
 
1622
- errorArea.style.display = 'block';
1637
+ if (errorArea) {
1638
+ errorArea.style.display = 'block';
1639
+ }
1623
1640
  }
1624
1641
 
1625
1642
  // Search Viewer Modal Functions
@@ -530,13 +530,15 @@ class SocketClient {
530
530
  this.addEvent({ type: 'agent', subtype: 'executed', timestamp: new Date().toISOString(), data });
531
531
  });
532
532
 
533
- this.socket.on('hook.pre', (data) => {
534
- this.addEvent({ type: 'hook', subtype: 'pre', timestamp: new Date().toISOString(), data });
535
- });
536
-
537
- this.socket.on('hook.post', (data) => {
538
- this.addEvent({ type: 'hook', subtype: 'post', timestamp: new Date().toISOString(), data });
539
- });
533
+ // DISABLED: Legacy hook handlers - events now come through claude_event pathway
534
+ // to prevent duplication. Hook events are processed by the claude_event handler above.
535
+ // this.socket.on('hook.pre', (data) => {
536
+ // this.addEvent({ type: 'hook', subtype: 'pre', timestamp: new Date().toISOString(), data });
537
+ // });
538
+
539
+ // this.socket.on('hook.post', (data) => {
540
+ // this.addEvent({ type: 'hook', subtype: 'post', timestamp: new Date().toISOString(), data });
541
+ // });
540
542
 
541
543
  this.socket.on('todo.updated', (data) => {
542
544
  this.addEvent({ type: 'todo', subtype: 'updated', timestamp: new Date().toISOString(), data });
@@ -388,7 +388,8 @@
388
388
  <!-- Code Tab -->
389
389
  <div class="tab-content" id="code-tab">
390
390
  <div class="code-container">
391
- <div id="code-tree-container" class="code-tree-container">
391
+ <div class="code-split-container">
392
+ <div id="code-tree-container" class="code-tree-container">
392
393
  <!-- Top-left corner: Language selector -->
393
394
  <div class="tree-corner-controls top-left">
394
395
  <div class="control-group">
@@ -454,6 +455,7 @@
454
455
  </div>
455
456
  </div>
456
457
  </div>
458
+ </div>
457
459
  </div>
458
460
 
459
461
  </div>
@@ -533,7 +535,8 @@
533
535
  loadModule('/static/dist/dashboard.js'),
534
536
  loadModule('/static/dist/components/activity-tree.js'),
535
537
  loadModule('/static/js/components/code-tree.js'), // TEMPORARY: Direct source for debugging
536
- loadModule('/static/dist/components/code-viewer.js')
538
+ loadModule('/static/dist/components/code-viewer.js'),
539
+ loadModule('/static/dist/components/file-viewer.js') // File viewer for viewing file contents
537
540
  ]).then(() => {
538
541
  console.log('All dashboard modules loaded successfully');
539
542
 
@@ -3,7 +3,7 @@
3
3
 
4
4
  This handler uses a service-oriented architecture with:
5
5
  - StateManagerService: Manages state and delegation tracking
6
- - ConnectionManagerService: Handles SocketIO and EventBus connections
6
+ - ConnectionManagerService: Handles SocketIO connections with HTTP fallback
7
7
  - SubagentResponseProcessor: Processes complex subagent responses
8
8
  - DuplicateEventDetector: Detects and filters duplicate events
9
9
 
@@ -73,19 +73,10 @@ DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
73
73
  Conditional imports with graceful fallbacks for testing and modularity.
74
74
 
75
75
  WHY conditional imports:
76
- - EventBus is optional for basic hook functionality
77
76
  - Tests may not have full environment setup
78
77
  - Allows hooks to work in minimal configurations
79
78
  - Graceful degradation when dependencies unavailable
80
79
  """
81
- # Import EventBus availability flag for backward compatibility with tests
82
- try:
83
- from claude_mpm.services.event_bus import EventBus
84
-
85
- EVENTBUS_AVAILABLE = True
86
- except ImportError:
87
- EVENTBUS_AVAILABLE = False
88
- EventBus = None
89
80
 
90
81
  # Import get_connection_pool for backward compatibility with tests
91
82
  try:
@@ -241,7 +232,6 @@ class ClaudeHookHandler:
241
232
 
242
233
  # Backward compatibility properties for tests
243
234
  self.connection_pool = self.connection_manager.connection_pool
244
- self.event_bus = self.connection_manager.event_bus
245
235
 
246
236
  # Expose state manager properties for backward compatibility
247
237
  self.active_delegations = self.state_manager.active_delegations