claude-mpm 4.0.31__py3-none-any.whl → 4.0.34__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 (71) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +33 -25
  3. claude_mpm/agents/INSTRUCTIONS.md +14 -10
  4. claude_mpm/agents/templates/documentation.json +51 -34
  5. claude_mpm/agents/templates/research.json +0 -11
  6. claude_mpm/cli/__init__.py +63 -26
  7. claude_mpm/cli/commands/agent_manager.py +10 -8
  8. claude_mpm/core/framework_loader.py +272 -113
  9. claude_mpm/dashboard/static/css/dashboard.css +449 -0
  10. claude_mpm/dashboard/static/dist/components/agent-inference.js +1 -1
  11. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  12. claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +1 -1
  13. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  14. claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
  15. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  16. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  17. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +774 -0
  18. claude_mpm/dashboard/static/js/components/agent-inference.js +257 -3
  19. claude_mpm/dashboard/static/js/components/build-tracker.js +289 -0
  20. claude_mpm/dashboard/static/js/components/event-viewer.js +168 -39
  21. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +17 -0
  22. claude_mpm/dashboard/static/js/components/session-manager.js +23 -3
  23. claude_mpm/dashboard/static/js/components/socket-manager.js +2 -0
  24. claude_mpm/dashboard/static/js/dashboard.js +207 -31
  25. claude_mpm/dashboard/static/js/socket-client.js +85 -6
  26. claude_mpm/dashboard/templates/index.html +1 -0
  27. claude_mpm/hooks/claude_hooks/connection_pool.py +12 -2
  28. claude_mpm/hooks/claude_hooks/event_handlers.py +81 -19
  29. claude_mpm/hooks/claude_hooks/hook_handler.py +72 -10
  30. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +398 -0
  31. claude_mpm/hooks/claude_hooks/response_tracking.py +10 -0
  32. claude_mpm/services/agents/deployment/agent_deployment.py +86 -37
  33. claude_mpm/services/agents/deployment/agent_template_builder.py +18 -10
  34. claude_mpm/services/agents/deployment/agents_directory_resolver.py +10 -25
  35. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +189 -3
  36. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +3 -2
  37. claude_mpm/services/agents/deployment/strategies/system_strategy.py +10 -3
  38. claude_mpm/services/agents/deployment/strategies/user_strategy.py +10 -14
  39. claude_mpm/services/agents/deployment/system_instructions_deployer.py +8 -13
  40. claude_mpm/services/agents/memory/agent_memory_manager.py +141 -184
  41. claude_mpm/services/agents/memory/content_manager.py +182 -232
  42. claude_mpm/services/agents/memory/template_generator.py +4 -40
  43. claude_mpm/services/event_bus/__init__.py +18 -0
  44. claude_mpm/services/event_bus/event_bus.py +334 -0
  45. claude_mpm/services/event_bus/relay.py +301 -0
  46. claude_mpm/services/events/__init__.py +44 -0
  47. claude_mpm/services/events/consumers/__init__.py +18 -0
  48. claude_mpm/services/events/consumers/dead_letter.py +296 -0
  49. claude_mpm/services/events/consumers/logging.py +183 -0
  50. claude_mpm/services/events/consumers/metrics.py +242 -0
  51. claude_mpm/services/events/consumers/socketio.py +376 -0
  52. claude_mpm/services/events/core.py +470 -0
  53. claude_mpm/services/events/interfaces.py +230 -0
  54. claude_mpm/services/events/producers/__init__.py +14 -0
  55. claude_mpm/services/events/producers/hook.py +269 -0
  56. claude_mpm/services/events/producers/system.py +327 -0
  57. claude_mpm/services/mcp_gateway/core/process_pool.py +411 -0
  58. claude_mpm/services/mcp_gateway/server/stdio_server.py +13 -0
  59. claude_mpm/services/monitor_build_service.py +345 -0
  60. claude_mpm/services/socketio/event_normalizer.py +667 -0
  61. claude_mpm/services/socketio/handlers/connection.py +78 -20
  62. claude_mpm/services/socketio/handlers/hook.py +14 -5
  63. claude_mpm/services/socketio/migration_utils.py +329 -0
  64. claude_mpm/services/socketio/server/broadcaster.py +26 -33
  65. claude_mpm/services/socketio/server/core.py +4 -3
  66. {claude_mpm-4.0.31.dist-info → claude_mpm-4.0.34.dist-info}/METADATA +4 -3
  67. {claude_mpm-4.0.31.dist-info → claude_mpm-4.0.34.dist-info}/RECORD +71 -50
  68. {claude_mpm-4.0.31.dist-info → claude_mpm-4.0.34.dist-info}/WHEEL +0 -0
  69. {claude_mpm-4.0.31.dist-info → claude_mpm-4.0.34.dist-info}/entry_points.txt +0 -0
  70. {claude_mpm-4.0.31.dist-info → claude_mpm-4.0.34.dist-info}/licenses/LICENSE +0 -0
  71. {claude_mpm-4.0.31.dist-info → claude_mpm-4.0.34.dist-info}/top_level.txt +0 -0
@@ -19,11 +19,13 @@ import { EventViewer } from '@components/event-viewer.js';
19
19
  import { ModuleViewer } from '@components/module-viewer.js';
20
20
  import { SessionManager } from '@components/session-manager.js';
21
21
  import { AgentInference } from '@components/agent-inference.js';
22
+ import { AgentHierarchy } from '@components/agent-hierarchy.js';
22
23
  import { UIStateManager } from '@components/ui-state-manager.js';
23
24
  import { EventProcessor } from '@components/event-processor.js';
24
25
  import { ExportManager } from '@components/export-manager.js';
25
26
  import { WorkingDirectoryManager } from '@components/working-directory.js';
26
27
  import { FileToolTracker } from '@components/file-tool-tracker.js';
28
+ import { BuildTracker } from '@components/build-tracker.js';
27
29
  class Dashboard {
28
30
  constructor() {
29
31
  // Core components (existing)
@@ -34,11 +36,13 @@ class Dashboard {
34
36
  // New modular components
35
37
  this.socketManager = null;
36
38
  this.agentInference = null;
39
+ this.agentHierarchy = null;
37
40
  this.uiStateManager = null;
38
41
  this.eventProcessor = null;
39
42
  this.exportManager = null;
40
43
  this.workingDirectoryManager = null;
41
44
  this.fileToolTracker = null;
45
+ this.buildTracker = null;
42
46
 
43
47
  // Initialize the dashboard
44
48
  this.init();
@@ -50,23 +54,75 @@ class Dashboard {
50
54
  init() {
51
55
  console.log('Initializing refactored Claude MPM Dashboard...');
52
56
 
53
- // Initialize modules in dependency order
54
- this.initializeSocketManager();
55
- this.initializeCoreComponents();
56
- this.initializeAgentInference();
57
- this.initializeUIStateManager();
58
- this.initializeWorkingDirectoryManager();
59
- this.initializeFileToolTracker();
60
- this.initializeEventProcessor();
61
- this.initializeExportManager();
62
-
63
- // Set up inter-module communication
64
- this.setupModuleInteractions();
65
-
66
- // Initialize from URL parameters
67
- this.initializeFromURL();
57
+ try {
58
+ // Initialize modules in dependency order
59
+ this.initializeSocketManager();
60
+ this.initializeCoreComponents();
61
+ this.initializeBuildTracker();
62
+ this.initializeAgentInference();
63
+ this.initializeAgentHierarchy();
64
+ this.initializeUIStateManager();
65
+ this.initializeWorkingDirectoryManager();
66
+ this.initializeFileToolTracker();
67
+ this.initializeEventProcessor();
68
+ this.initializeExportManager();
69
+
70
+ // Set up inter-module communication
71
+ this.setupModuleInteractions();
72
+
73
+ // Initialize from URL parameters
74
+ this.initializeFromURL();
75
+
76
+ console.log('Claude MPM Dashboard initialized successfully');
77
+ } catch (error) {
78
+ console.error('Error during dashboard initialization:', error);
79
+ // Re-throw to be caught by DOMContentLoaded handler
80
+ throw error;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Validate that all critical components are initialized
86
+ * WHY: Ensures dashboard is in a valid state after initialization
87
+ */
88
+ validateInitialization() {
89
+ const criticalComponents = [
90
+ { name: 'socketManager', component: this.socketManager },
91
+ { name: 'eventViewer', component: this.eventViewer },
92
+ { name: 'agentHierarchy', component: this.agentHierarchy }
93
+ ];
94
+
95
+ const missing = criticalComponents.filter(c => !c.component);
96
+ if (missing.length > 0) {
97
+ console.warn('Missing critical components:', missing.map(c => c.name));
98
+ } else {
99
+ console.log('All critical components initialized');
100
+ }
101
+ }
68
102
 
69
- console.log('Claude MPM Dashboard initialized successfully');
103
+ /**
104
+ * Post-initialization setup that requires window.dashboard to be set
105
+ * WHY: Some components need to reference window.dashboard but it's not available
106
+ * during constructor execution. This method is called after the Dashboard instance
107
+ * is assigned to window.dashboard, ensuring proper initialization order.
108
+ *
109
+ * DESIGN DECISION: Separate post-init phase prevents "cannot read property of undefined"
110
+ * errors when components try to access window.dashboard during construction.
111
+ */
112
+ postInit() {
113
+ try {
114
+ // Set global reference for agent hierarchy after dashboard is available
115
+ if (this.agentHierarchy) {
116
+ window.dashboard.agentHierarchy = this.agentHierarchy;
117
+ console.log('Agent hierarchy global reference set');
118
+ }
119
+
120
+ // Initialize any other components that need window.dashboard
121
+ this.validateInitialization();
122
+ } catch (error) {
123
+ console.error('Error in dashboard postInit:', error);
124
+ // Continue execution - non-critical error
125
+ }
70
126
  }
71
127
 
72
128
  /**
@@ -98,6 +154,28 @@ class Dashboard {
98
154
  window.sessionManager = this.sessionManager;
99
155
  }
100
156
 
157
+ /**
158
+ * Initialize build tracker
159
+ */
160
+ initializeBuildTracker() {
161
+ this.buildTracker = new BuildTracker();
162
+
163
+ // Set the socket client for receiving updates
164
+ this.buildTracker.setSocketClient(this.socketClient);
165
+
166
+ // Mount to header - find the best location
167
+ const headerTitle = document.querySelector('.header-title');
168
+ if (headerTitle) {
169
+ // Insert after the title and status badge
170
+ this.buildTracker.mount(headerTitle);
171
+ } else {
172
+ console.warn('Could not find header-title element for build tracker');
173
+ }
174
+
175
+ // Make available globally for debugging
176
+ window.buildTracker = this.buildTracker;
177
+ }
178
+
101
179
  /**
102
180
  * Initialize agent inference system
103
181
  */
@@ -105,6 +183,28 @@ class Dashboard {
105
183
  this.agentInference = new AgentInference(this.eventViewer);
106
184
  this.agentInference.initialize();
107
185
  }
186
+
187
+ /**
188
+ * Initialize agent hierarchy component
189
+ * WHY: Creates the agent hierarchy visualization component but defers global
190
+ * reference setting to postInit() to avoid initialization order issues.
191
+ */
192
+ initializeAgentHierarchy() {
193
+ try {
194
+ this.agentHierarchy = new AgentHierarchy(this.agentInference, this.eventViewer);
195
+ // Global reference will be set in postInit() after window.dashboard exists
196
+ console.log('Agent hierarchy component created');
197
+ } catch (error) {
198
+ console.error('Failed to initialize agent hierarchy:', error);
199
+ // Create a stub to prevent further errors
200
+ this.agentHierarchy = {
201
+ render: () => '<div class="error">Agent hierarchy unavailable</div>',
202
+ expandAllNodes: () => {},
203
+ collapseAllNodes: () => {},
204
+ updateWithNewEvents: () => {}
205
+ };
206
+ }
207
+ }
108
208
 
109
209
  /**
110
210
  * Initialize UI state manager
@@ -154,6 +254,9 @@ class Dashboard {
154
254
 
155
255
  // Process agent inference for new events
156
256
  this.agentInference.processAgentInference();
257
+
258
+ // Update agent hierarchy with new events
259
+ this.agentHierarchy.updateWithNewEvents(events);
157
260
 
158
261
  // Auto-scroll events list if on events tab
159
262
  if (this.uiStateManager.getCurrentTab() === 'events') {
@@ -291,26 +394,74 @@ class Dashboard {
291
394
  }
292
395
 
293
396
  /**
294
- * Render agents tab with unique instance view (one row per PM delegation)
397
+ * Render agents tab with hierarchical view
295
398
  */
296
399
  renderAgents() {
297
400
  const agentsList = document.getElementById('agents-list');
298
401
  if (!agentsList) return;
299
-
300
- // Process agent inference to get PM delegations
301
- this.agentInference.processAgentInference();
302
-
303
- // Generate HTML for unique agent instances
304
- const events = this.eventProcessor.getFilteredEventsForTab('agents');
305
- const agentHTML = this.eventProcessor.generateAgentHTML(events);
306
-
307
- agentsList.innerHTML = agentHTML;
308
- this.exportManager.scrollListToBottom('agents-list');
309
-
310
- // Update filter dropdowns with unique instances
402
+
403
+ // Get filter values
404
+ const searchText = document.getElementById('agents-search-input')?.value || '';
405
+ const agentType = document.getElementById('agents-type-filter')?.value || '';
406
+
407
+ // Build filters object
408
+ const filters = {};
409
+ if (searchText) filters.searchText = searchText;
410
+ if (agentType) filters.agentType = agentType;
411
+
412
+ // Generate hierarchical HTML
413
+ const hierarchyHTML = this.agentHierarchy.render(filters);
414
+ agentsList.innerHTML = hierarchyHTML;
415
+
416
+ // Add expand/collapse all buttons if not present
417
+ this.addHierarchyControls();
418
+
419
+ // Update filter dropdowns with available agent types
311
420
  const uniqueInstances = this.agentInference.getUniqueAgentInstances();
312
421
  this.updateAgentsFilterDropdowns(uniqueInstances);
313
422
  }
423
+
424
+ /**
425
+ * Add hierarchy control buttons
426
+ */
427
+ addHierarchyControls() {
428
+ const tabFilters = document.querySelector('#agents-tab .tab-filters');
429
+ if (!tabFilters) return;
430
+
431
+ // Check if controls already exist
432
+ if (document.getElementById('hierarchy-controls')) return;
433
+
434
+ // Create control buttons
435
+ const controls = document.createElement('div');
436
+ controls.id = 'hierarchy-controls';
437
+ controls.className = 'hierarchy-controls';
438
+ controls.innerHTML = `
439
+ <button data-action="expand-all"
440
+ class="hierarchy-btn hierarchy-expand-all">
441
+ Expand All
442
+ </button>
443
+ <button data-action="collapse-all"
444
+ class="hierarchy-btn hierarchy-collapse-all">
445
+ Collapse All
446
+ </button>
447
+ `;
448
+
449
+ // Add safe event listeners
450
+ controls.addEventListener('click', (event) => {
451
+ const action = event.target.dataset.action;
452
+ if (action && window.dashboard && window.dashboard.agentHierarchy) {
453
+ if (action === 'expand-all') {
454
+ window.dashboard.agentHierarchy.expandAllNodes();
455
+ window.dashboard.renderAgents();
456
+ } else if (action === 'collapse-all') {
457
+ window.dashboard.agentHierarchy.collapseAllNodes();
458
+ window.dashboard.renderAgents();
459
+ }
460
+ }
461
+ });
462
+
463
+ tabFilters.appendChild(controls);
464
+ }
314
465
 
315
466
  /**
316
467
  * Render tools tab with unique instance view (one row per unique tool call)
@@ -1810,8 +1961,33 @@ window.showAgentInstanceDetails = function(instanceId) {
1810
1961
 
1811
1962
  // Initialize dashboard when page loads
1812
1963
  document.addEventListener('DOMContentLoaded', function() {
1813
- window.dashboard = new Dashboard();
1814
- console.log('Dashboard loaded and initialized');
1964
+ try {
1965
+ // Create dashboard instance
1966
+ window.dashboard = new Dashboard();
1967
+
1968
+ // Call post-initialization setup that requires window.dashboard
1969
+ // This must happen after window.dashboard is set
1970
+ if (window.dashboard && typeof window.dashboard.postInit === 'function') {
1971
+ window.dashboard.postInit();
1972
+ }
1973
+
1974
+ console.log('Dashboard loaded and initialized successfully');
1975
+
1976
+ // Dispatch custom event to signal dashboard ready
1977
+ document.dispatchEvent(new CustomEvent('dashboardReady', {
1978
+ detail: { dashboard: window.dashboard }
1979
+ }));
1980
+ } catch (error) {
1981
+ console.error('Failed to initialize dashboard:', error);
1982
+ // Show user-friendly error message
1983
+ document.body.innerHTML = `
1984
+ <div style="padding: 20px; font-family: sans-serif;">
1985
+ <h1>Dashboard Initialization Error</h1>
1986
+ <p>The dashboard failed to load properly. Please refresh the page or check the console for details.</p>
1987
+ <pre style="background: #f5f5f5; padding: 10px; border-radius: 4px;">${error.message}</pre>
1988
+ </div>
1989
+ `;
1990
+ }
1815
1991
  });
1816
1992
 
1817
1993
  // ES6 Module export
@@ -16,6 +16,12 @@ class SocketClient {
16
16
  error: [],
17
17
  event: []
18
18
  };
19
+
20
+ // Event schema validation
21
+ this.eventSchema = {
22
+ required: ['source', 'type', 'subtype', 'timestamp', 'data'],
23
+ optional: ['event', 'session_id']
24
+ };
19
25
 
20
26
  // Connection state
21
27
  this.isConnected = false;
@@ -187,11 +193,18 @@ class SocketClient {
187
193
 
188
194
  // Primary event handler - this is what the server actually emits
189
195
  this.socket.on('claude_event', (data) => {
190
- // console.log('Received claude_event:', data);
191
-
192
- // Transform event to match expected format
193
- const transformedEvent = this.transformEvent(data);
194
- // console.log('Transformed event:', transformedEvent);
196
+ console.log('Received claude_event:', data);
197
+
198
+ // Validate event schema
199
+ const validatedEvent = this.validateEventSchema(data);
200
+ if (!validatedEvent) {
201
+ console.warn('Invalid event schema received:', data);
202
+ return;
203
+ }
204
+
205
+ // Transform event to match expected format (for backward compatibility)
206
+ const transformedEvent = this.transformEvent(validatedEvent);
207
+ console.log('Transformed event:', transformedEvent);
195
208
  this.addEvent(transformedEvent);
196
209
  });
197
210
 
@@ -628,6 +641,51 @@ class SocketClient {
628
641
  };
629
642
  }
630
643
 
644
+ /**
645
+ * Validate event against expected schema
646
+ * @param {Object} eventData - Raw event data
647
+ * @returns {Object|null} Validated event or null if invalid
648
+ */
649
+ validateEventSchema(eventData) {
650
+ if (!eventData || typeof eventData !== 'object') {
651
+ console.warn('Event data is not an object:', eventData);
652
+ return null;
653
+ }
654
+
655
+ // Make a copy to avoid modifying the original
656
+ const validated = { ...eventData };
657
+
658
+ // Check and provide defaults for required fields
659
+ if (!validated.source) {
660
+ validated.source = 'system'; // Default source for backward compatibility
661
+ }
662
+ if (!validated.type) {
663
+ // If there's an event field, use it as the type
664
+ if (validated.event) {
665
+ validated.type = validated.event;
666
+ } else {
667
+ validated.type = 'unknown';
668
+ }
669
+ }
670
+ if (!validated.subtype) {
671
+ validated.subtype = 'generic';
672
+ }
673
+ if (!validated.timestamp) {
674
+ validated.timestamp = new Date().toISOString();
675
+ }
676
+ if (!validated.data) {
677
+ validated.data = {};
678
+ }
679
+
680
+ // Ensure data field is an object
681
+ if (validated.data && typeof validated.data !== 'object') {
682
+ validated.data = { value: validated.data };
683
+ }
684
+
685
+ console.log('Validated event:', validated);
686
+ return validated;
687
+ }
688
+
631
689
  /**
632
690
  * Transform received event to match expected dashboard format
633
691
  * @param {Object} eventData - Raw event data from server
@@ -718,7 +776,14 @@ class SocketClient {
718
776
  Object.keys(eventData.data).forEach(key => {
719
777
  // Only copy if not a protected field
720
778
  if (!protectedFields.includes(key)) {
721
- transformedEvent[key] = eventData.data[key];
779
+ // Special handling for tool_parameters to ensure it's properly preserved
780
+ // This is critical for file path extraction in file-tool-tracker
781
+ if (key === 'tool_parameters' && typeof eventData.data[key] === 'object') {
782
+ // Deep copy the tool_parameters object to preserve all nested fields
783
+ transformedEvent[key] = JSON.parse(JSON.stringify(eventData.data[key]));
784
+ } else {
785
+ transformedEvent[key] = eventData.data[key];
786
+ }
722
787
  } else {
723
788
  // Log warning if data field would overwrite a protected field
724
789
  console.warn(`Protected field '${key}' in data object was not copied to top level to preserve event structure`);
@@ -735,9 +800,23 @@ class SocketClient {
735
800
  type: transformedEvent.type,
736
801
  subtype: transformedEvent.subtype,
737
802
  tool_name: transformedEvent.tool_name,
803
+ has_tool_parameters: !!transformedEvent.tool_parameters,
804
+ tool_parameters: transformedEvent.tool_parameters,
738
805
  has_data: !!transformedEvent.data,
739
806
  keys: Object.keys(transformedEvent).filter(k => k !== 'data')
740
807
  });
808
+
809
+ // Extra debug logging for file-related tools
810
+ const fileTools = ['Read', 'Write', 'Edit', 'MultiEdit', 'NotebookEdit'];
811
+ if (fileTools.includes(transformedEvent.tool_name)) {
812
+ console.log('File tool event details:', {
813
+ tool_name: transformedEvent.tool_name,
814
+ file_path: transformedEvent.tool_parameters?.file_path,
815
+ path: transformedEvent.tool_parameters?.path,
816
+ notebook_path: transformedEvent.tool_parameters?.notebook_path,
817
+ full_parameters: transformedEvent.tool_parameters
818
+ });
819
+ }
741
820
  }
742
821
 
743
822
  return transformedEvent;
@@ -141,6 +141,7 @@
141
141
  <div id="connection-status" class="status-badge status-disconnected">
142
142
  <span>●</span> Disconnected
143
143
  </div>
144
+ <!-- Version display will be inserted here by BuildTracker component -->
144
145
  </div>
145
146
  <div class="metrics-widget">
146
147
  <div class="metric-mini">
@@ -109,7 +109,12 @@ class SocketIOConnectionPool:
109
109
  if client.connected:
110
110
  # Send a keep-alive ping to establish the connection
111
111
  try:
112
- client.emit('ping', {'timestamp': time.time()})
112
+ client.emit('ping', {
113
+ 'type': 'system',
114
+ 'subtype': 'ping',
115
+ 'timestamp': time.time(),
116
+ 'source': 'connection_pool'
117
+ })
113
118
  except:
114
119
  pass # Ignore ping errors
115
120
  return client
@@ -137,7 +142,12 @@ class SocketIOConnectionPool:
137
142
  # This helps detect zombie connections
138
143
  try:
139
144
  # Just emit a ping, don't wait for response (faster)
140
- client.emit('ping', {'timestamp': time.time()})
145
+ client.emit('ping', {
146
+ 'type': 'system',
147
+ 'subtype': 'ping',
148
+ 'timestamp': time.time(),
149
+ 'source': 'connection_pool'
150
+ })
141
151
  return True
142
152
  except:
143
153
  # If ping fails, connection might be dead
@@ -13,13 +13,25 @@ import sys
13
13
  from datetime import datetime
14
14
  from typing import Any, Dict, Optional
15
15
 
16
- from .tool_analysis import (
17
- assess_security_risk,
18
- calculate_duration,
19
- classify_tool_operation,
20
- extract_tool_parameters,
21
- extract_tool_results,
22
- )
16
+ # Import tool analysis with fallback for direct execution
17
+ try:
18
+ # Try relative import first (when imported as module)
19
+ from .tool_analysis import (
20
+ assess_security_risk,
21
+ calculate_duration,
22
+ classify_tool_operation,
23
+ extract_tool_parameters,
24
+ extract_tool_results,
25
+ )
26
+ except ImportError:
27
+ # Fall back to direct import (when parent script is run directly)
28
+ from tool_analysis import (
29
+ assess_security_risk,
30
+ calculate_duration,
31
+ classify_tool_operation,
32
+ extract_tool_parameters,
33
+ extract_tool_results,
34
+ )
23
35
 
24
36
  # Debug mode
25
37
  DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
@@ -99,8 +111,8 @@ class EventHandlers:
99
111
  file=sys.stderr,
100
112
  )
101
113
 
102
- # Emit to /hook namespace
103
- self.hook_handler._emit_socketio_event("/hook", "user_prompt", prompt_data)
114
+ # Emit normalized event (namespace no longer needed with normalized events)
115
+ self.hook_handler._emit_socketio_event("", "user_prompt", prompt_data)
104
116
 
105
117
  def handle_pre_tool_fast(self, event):
106
118
  """Handle pre-tool use with comprehensive data capture.
@@ -152,7 +164,7 @@ class EventHandlers:
152
164
  if tool_name == "Task" and isinstance(tool_input, dict):
153
165
  self._handle_task_delegation(tool_input, pre_tool_data, session_id)
154
166
 
155
- self.hook_handler._emit_socketio_event("/hook", "pre_tool", pre_tool_data)
167
+ self.hook_handler._emit_socketio_event("", "pre_tool", pre_tool_data)
156
168
 
157
169
  def _handle_task_delegation(
158
170
  self, tool_input: dict, pre_tool_data: dict, session_id: str
@@ -242,7 +254,7 @@ class EventHandlers:
242
254
  "hook_event_name": "SubagentStart", # For dashboard compatibility
243
255
  }
244
256
  self.hook_handler._emit_socketio_event(
245
- "/hook", "subagent_start", subagent_start_data
257
+ "", "subagent_start", subagent_start_data
246
258
  )
247
259
 
248
260
  def _get_git_branch(self, working_dir: str = None) -> str:
@@ -362,7 +374,7 @@ class EventHandlers:
362
374
  session_id, agent_type, event, self.hook_handler.delegation_requests
363
375
  )
364
376
 
365
- self.hook_handler._emit_socketio_event("/hook", "post_tool", post_tool_data)
377
+ self.hook_handler._emit_socketio_event("", "post_tool", post_tool_data)
366
378
 
367
379
  def handle_notification_fast(self, event):
368
380
  """Handle notification events from Claude.
@@ -399,9 +411,9 @@ class EventHandlers:
399
411
  ),
400
412
  }
401
413
 
402
- # Emit to /hook namespace
414
+ # Emit normalized event
403
415
  self.hook_handler._emit_socketio_event(
404
- "/hook", "notification", notification_data
416
+ "", "notification", notification_data
405
417
  )
406
418
 
407
419
  def handle_stop_fast(self, event):
@@ -481,8 +493,8 @@ class EventHandlers:
481
493
  "has_output": bool(event.get("final_output")),
482
494
  }
483
495
 
484
- # Emit to /hook namespace
485
- self.hook_handler._emit_socketio_event("/hook", "stop", stop_data)
496
+ # Emit normalized event
497
+ self.hook_handler._emit_socketio_event("", "stop", stop_data)
486
498
 
487
499
  def handle_subagent_stop_fast(self, event):
488
500
  """Handle subagent stop events with improved agent type detection."""
@@ -599,9 +611,9 @@ class EventHandlers:
599
611
  file=sys.stderr,
600
612
  )
601
613
 
602
- # Emit to /hook namespace with high priority
614
+ # Emit normalized event with high priority
603
615
  self.hook_handler._emit_socketio_event(
604
- "/hook", "subagent_stop", subagent_stop_data
616
+ "", "subagent_stop", subagent_stop_data
605
617
  )
606
618
 
607
619
  def _handle_subagent_response_tracking(
@@ -737,7 +749,57 @@ class EventHandlers:
737
749
  )
738
750
 
739
751
  def handle_assistant_response(self, event):
740
- """Handle assistant response events for comprehensive response tracking."""
752
+ """Handle assistant response events for comprehensive response tracking.
753
+
754
+ WHY emit assistant response events:
755
+ - Provides visibility into Claude's responses to user prompts
756
+ - Captures response content and metadata for analysis
757
+ - Enables tracking of conversation flow and response patterns
758
+ - Essential for comprehensive monitoring of Claude interactions
759
+ """
760
+ # Track the response for logging
741
761
  self.hook_handler.response_tracking_manager.track_assistant_response(
742
762
  event, self.hook_handler.pending_prompts
743
763
  )
764
+
765
+ # Get working directory and git branch
766
+ working_dir = event.get("cwd", "")
767
+ git_branch = self._get_git_branch(working_dir) if working_dir else "Unknown"
768
+
769
+ # Extract response data
770
+ response_text = event.get("response", "")
771
+ session_id = event.get("session_id", "")
772
+
773
+ # Prepare assistant response data for Socket.IO emission
774
+ assistant_response_data = {
775
+ "response_text": response_text,
776
+ "response_preview": response_text[:500] if len(response_text) > 500 else response_text,
777
+ "response_length": len(response_text),
778
+ "session_id": session_id,
779
+ "working_directory": working_dir,
780
+ "git_branch": git_branch,
781
+ "timestamp": datetime.now().isoformat(),
782
+ "contains_code": "```" in response_text,
783
+ "contains_json": "```json" in response_text,
784
+ "hook_event_name": "AssistantResponse", # Explicitly set for dashboard
785
+ "has_structured_response": bool(re.search(r"```json\s*\{.*?\}\s*```", response_text, re.DOTALL)),
786
+ }
787
+
788
+ # Check if this is a response to a tracked prompt
789
+ if session_id in self.hook_handler.pending_prompts:
790
+ prompt_data = self.hook_handler.pending_prompts[session_id]
791
+ assistant_response_data["original_prompt"] = prompt_data.get("prompt", "")[:200]
792
+ assistant_response_data["prompt_timestamp"] = prompt_data.get("timestamp", "")
793
+ assistant_response_data["is_tracked_response"] = True
794
+ else:
795
+ assistant_response_data["is_tracked_response"] = False
796
+
797
+ # Debug logging
798
+ if DEBUG:
799
+ print(
800
+ f"Hook handler: Processing AssistantResponse - session: '{session_id}', response_length: {len(response_text)}",
801
+ file=sys.stderr,
802
+ )
803
+
804
+ # Emit normalized event
805
+ self.hook_handler._emit_socketio_event("", "assistant_response", assistant_response_data)