claude-mpm 3.5.4__py3-none-any.whl → 3.5.6__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.
- claude_mpm/.claude-mpm/logs/hooks_20250728.log +10 -0
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/INSTRUCTIONS.md +12 -11
- claude_mpm/agents/agent-template.yaml +83 -0
- claude_mpm/cli/README.md +108 -0
- claude_mpm/cli/commands/agents.py +21 -3
- claude_mpm/cli/commands/run.py +4 -11
- claude_mpm/cli/utils.py +9 -1
- claude_mpm/cli_module/refactoring_guide.md +253 -0
- claude_mpm/config/async_logging_config.yaml +145 -0
- claude_mpm/core/.claude-mpm/logs/hooks_20250730.log +34 -0
- claude_mpm/core/claude_runner.py +94 -33
- claude_mpm/core/config_paths.py +0 -1
- claude_mpm/core/factories.py +9 -3
- claude_mpm/dashboard/.claude-mpm/memories/README.md +36 -0
- claude_mpm/dashboard/README.md +121 -0
- claude_mpm/dashboard/static/js/dashboard.js.backup +1973 -0
- claude_mpm/dashboard/templates/.claude-mpm/memories/README.md +36 -0
- claude_mpm/dashboard/templates/.claude-mpm/memories/engineer_agent.md +39 -0
- claude_mpm/dashboard/templates/.claude-mpm/memories/version_control_agent.md +38 -0
- claude_mpm/hooks/README.md +96 -0
- claude_mpm/init.py +83 -13
- claude_mpm/schemas/agent_schema.json +435 -0
- claude_mpm/services/agents/deployment/agent_deployment.py +165 -9
- claude_mpm/services/agents/management/agent_management_service.py +2 -1
- claude_mpm/services/framework_claude_md_generator/README.md +92 -0
- claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +3 -3
- claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +2 -2
- claude_mpm/services/version_control/VERSION +1 -0
- {claude_mpm-3.5.4.dist-info → claude_mpm-3.5.6.dist-info}/METADATA +43 -1
- {claude_mpm-3.5.4.dist-info → claude_mpm-3.5.6.dist-info}/RECORD +35 -19
- {claude_mpm-3.5.4.dist-info → claude_mpm-3.5.6.dist-info}/WHEEL +0 -0
- {claude_mpm-3.5.4.dist-info → claude_mpm-3.5.6.dist-info}/entry_points.txt +0 -0
- {claude_mpm-3.5.4.dist-info → claude_mpm-3.5.6.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-3.5.4.dist-info → claude_mpm-3.5.6.dist-info}/top_level.txt +0 -0
| @@ -0,0 +1,1973 @@ | |
| 1 | 
            +
            /**
         | 
| 2 | 
            +
             * Refactored Dashboard Coordinator
         | 
| 3 | 
            +
             * 
         | 
| 4 | 
            +
             * Main coordinator class that orchestrates all dashboard modules while maintaining
         | 
| 5 | 
            +
             * backward compatibility with the original dashboard interface.
         | 
| 6 | 
            +
             * 
         | 
| 7 | 
            +
             * WHY: This refactored version breaks down the monolithic 4,133-line dashboard
         | 
| 8 | 
            +
             * into manageable, focused modules while preserving all existing functionality.
         | 
| 9 | 
            +
             * Each module handles a specific concern, improving maintainability and testability.
         | 
| 10 | 
            +
             * 
         | 
| 11 | 
            +
             * DESIGN DECISION: Acts as a thin coordinator layer that initializes modules,
         | 
| 12 | 
            +
             * manages inter-module communication through events, and provides backward
         | 
| 13 | 
            +
             * compatibility for existing code that depends on the dashboard interface.
         | 
| 14 | 
            +
             */
         | 
| 15 | 
            +
            class Dashboard {
         | 
| 16 | 
            +
                constructor() {
         | 
| 17 | 
            +
                    // Core components (existing)
         | 
| 18 | 
            +
                    this.eventViewer = null;
         | 
| 19 | 
            +
                    this.moduleViewer = null;
         | 
| 20 | 
            +
                    this.sessionManager = null;
         | 
| 21 | 
            +
                    
         | 
| 22 | 
            +
                    // New modular components
         | 
| 23 | 
            +
                    this.socketManager = null;
         | 
| 24 | 
            +
                    this.agentInference = null;
         | 
| 25 | 
            +
                    this.uiStateManager = null;
         | 
| 26 | 
            +
                    this.eventProcessor = null;
         | 
| 27 | 
            +
                    this.exportManager = null;
         | 
| 28 | 
            +
                    this.workingDirectoryManager = null;
         | 
| 29 | 
            +
                    this.fileToolTracker = null;
         | 
| 30 | 
            +
                    
         | 
| 31 | 
            +
                    // Initialize the dashboard
         | 
| 32 | 
            +
                    this.init();
         | 
| 33 | 
            +
                }
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                /**
         | 
| 36 | 
            +
                 * Initialize the dashboard and all modules
         | 
| 37 | 
            +
                 */
         | 
| 38 | 
            +
                init() {
         | 
| 39 | 
            +
                    console.log('Initializing refactored Claude MPM Dashboard...');
         | 
| 40 | 
            +
                    
         | 
| 41 | 
            +
                    // Initialize modules in dependency order
         | 
| 42 | 
            +
                    this.initializeSocketManager();
         | 
| 43 | 
            +
                    this.initializeCoreComponents();
         | 
| 44 | 
            +
                    this.initializeAgentInference();
         | 
| 45 | 
            +
                    this.initializeUIStateManager();
         | 
| 46 | 
            +
                    this.initializeWorkingDirectoryManager();
         | 
| 47 | 
            +
                    this.initializeFileToolTracker();
         | 
| 48 | 
            +
                    this.initializeEventProcessor();
         | 
| 49 | 
            +
                    this.initializeExportManager();
         | 
| 50 | 
            +
                    
         | 
| 51 | 
            +
                    // Set up inter-module communication
         | 
| 52 | 
            +
                    this.setupModuleInteractions();
         | 
| 53 | 
            +
                    
         | 
| 54 | 
            +
                    // Initialize from URL parameters
         | 
| 55 | 
            +
                    this.initializeFromURL();
         | 
| 56 | 
            +
                    
         | 
| 57 | 
            +
                    console.log('Claude MPM Dashboard initialized successfully');
         | 
| 58 | 
            +
                }
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                /**
         | 
| 61 | 
            +
                 * Initialize socket manager
         | 
| 62 | 
            +
                 */
         | 
| 63 | 
            +
                initializeSocketManager() {
         | 
| 64 | 
            +
                    this.socketManager = new SocketManager();
         | 
| 65 | 
            +
                    
         | 
| 66 | 
            +
                    // Set up connection controls
         | 
| 67 | 
            +
                    this.socketManager.setupConnectionControls();
         | 
| 68 | 
            +
                    
         | 
| 69 | 
            +
                    // Backward compatibility
         | 
| 70 | 
            +
                    this.socketClient = this.socketManager.getSocketClient();
         | 
| 71 | 
            +
                    window.socketClient = this.socketClient;
         | 
| 72 | 
            +
                }
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                /**
         | 
| 75 | 
            +
                 * Initialize core existing components
         | 
| 76 | 
            +
                 */
         | 
| 77 | 
            +
                initializeCoreComponents() {
         | 
| 78 | 
            +
                    // Initialize existing components with socket client
         | 
| 79 | 
            +
                    this.eventViewer = new EventViewer('events-list', this.socketClient);
         | 
| 80 | 
            +
                    this.moduleViewer = new ModuleViewer();
         | 
| 81 | 
            +
                    this.sessionManager = new SessionManager(this.socketClient);
         | 
| 82 | 
            +
                    
         | 
| 83 | 
            +
                    // Backward compatibility
         | 
| 84 | 
            +
                    window.eventViewer = this.eventViewer;
         | 
| 85 | 
            +
                    window.moduleViewer = this.moduleViewer;
         | 
| 86 | 
            +
                    window.sessionManager = this.sessionManager;
         | 
| 87 | 
            +
                }
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                /**
         | 
| 90 | 
            +
                 * Initialize agent inference system
         | 
| 91 | 
            +
                 */
         | 
| 92 | 
            +
                initializeAgentInference() {
         | 
| 93 | 
            +
                    this.agentInference = new AgentInference(this.eventViewer);
         | 
| 94 | 
            +
                    this.agentInference.initialize();
         | 
| 95 | 
            +
                }
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                /**
         | 
| 98 | 
            +
                 * Initialize UI state manager
         | 
| 99 | 
            +
                 */
         | 
| 100 | 
            +
                initializeUIStateManager() {
         | 
| 101 | 
            +
                    this.uiStateManager = new UIStateManager();
         | 
| 102 | 
            +
                    this.setupTabFilters(); // Set up filters after UI state manager
         | 
| 103 | 
            +
                }
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                /**
         | 
| 106 | 
            +
                 * Initialize working directory manager
         | 
| 107 | 
            +
                 */
         | 
| 108 | 
            +
                initializeWorkingDirectoryManager() {
         | 
| 109 | 
            +
                    this.workingDirectoryManager = new WorkingDirectoryManager(this.socketManager);
         | 
| 110 | 
            +
                }
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                /**
         | 
| 113 | 
            +
                 * Initialize file-tool tracker
         | 
| 114 | 
            +
                 */
         | 
| 115 | 
            +
                initializeFileToolTracker() {
         | 
| 116 | 
            +
                    this.fileToolTracker = new FileToolTracker(this.agentInference, this.workingDirectoryManager);
         | 
| 117 | 
            +
                }
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                /**
         | 
| 120 | 
            +
                 * Initialize event processor
         | 
| 121 | 
            +
                 */
         | 
| 122 | 
            +
                initializeEventProcessor() {
         | 
| 123 | 
            +
                    this.eventProcessor = new EventProcessor(this.eventViewer, this.agentInference);
         | 
| 124 | 
            +
                }
         | 
| 125 | 
            +
             | 
| 126 | 
            +
             | 
| 127 | 
            +
                /**
         | 
| 128 | 
            +
                 * Initialize export manager
         | 
| 129 | 
            +
                 */
         | 
| 130 | 
            +
                initializeExportManager() {
         | 
| 131 | 
            +
                    this.exportManager = new ExportManager(this.eventViewer);
         | 
| 132 | 
            +
                }
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                /**
         | 
| 135 | 
            +
                 * Set up interactions between modules
         | 
| 136 | 
            +
                 */
         | 
| 137 | 
            +
                setupModuleInteractions() {
         | 
| 138 | 
            +
                    // Socket events to update file operations and tool calls
         | 
| 139 | 
            +
                    this.socketManager.onEventUpdate((events) => {
         | 
| 140 | 
            +
                        this.fileToolTracker.updateFileOperations(events);
         | 
| 141 | 
            +
                        this.fileToolTracker.updateToolCalls(events);
         | 
| 142 | 
            +
                        
         | 
| 143 | 
            +
                        // Process agent inference for new events
         | 
| 144 | 
            +
                        this.agentInference.processAgentInference();
         | 
| 145 | 
            +
                        
         | 
| 146 | 
            +
                        // Auto-scroll events list if on events tab
         | 
| 147 | 
            +
                        if (this.uiStateManager.getCurrentTab() === 'events') {
         | 
| 148 | 
            +
                            this.exportManager.scrollListToBottom('events-list');
         | 
| 149 | 
            +
                        }
         | 
| 150 | 
            +
                        
         | 
| 151 | 
            +
                        // Re-render current tab
         | 
| 152 | 
            +
                        this.renderCurrentTab();
         | 
| 153 | 
            +
                    });
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                    // Connection status changes
         | 
| 156 | 
            +
                    this.socketManager.onConnectionStatusChange((status, type) => {
         | 
| 157 | 
            +
                        // Set up git branch listener when connected
         | 
| 158 | 
            +
                        if (type === 'connected') {
         | 
| 159 | 
            +
                            this.workingDirectoryManager.updateGitBranch(
         | 
| 160 | 
            +
                                this.workingDirectoryManager.getCurrentWorkingDir()
         | 
| 161 | 
            +
                            );
         | 
| 162 | 
            +
                        }
         | 
| 163 | 
            +
                    });
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                    // Tab changes
         | 
| 166 | 
            +
                    document.addEventListener('tabChanged', (e) => {
         | 
| 167 | 
            +
                        this.renderCurrentTab();
         | 
| 168 | 
            +
                        this.uiStateManager.updateTabNavigationItems();
         | 
| 169 | 
            +
                    });
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                    // Events clearing
         | 
| 172 | 
            +
                    document.addEventListener('eventsClearing', () => {
         | 
| 173 | 
            +
                        this.fileToolTracker.clear();
         | 
| 174 | 
            +
                        this.agentInference.initialize();
         | 
| 175 | 
            +
                    });
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                    // Card details requests
         | 
| 178 | 
            +
                    document.addEventListener('showCardDetails', (e) => {
         | 
| 179 | 
            +
                        this.showCardDetails(e.detail.tabName, e.detail.index);
         | 
| 180 | 
            +
                    });
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                    // Session changes
         | 
| 183 | 
            +
                    document.addEventListener('sessionFilterChanged', (e) => {
         | 
| 184 | 
            +
                        console.log('Session filter changed, re-rendering current tab:', this.uiStateManager.getCurrentTab());
         | 
| 185 | 
            +
                        this.renderCurrentTab();
         | 
| 186 | 
            +
                    });
         | 
| 187 | 
            +
                }
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                /**
         | 
| 190 | 
            +
                 * Set up tab filters
         | 
| 191 | 
            +
                 */
         | 
| 192 | 
            +
                setupTabFilters() {
         | 
| 193 | 
            +
                    // Agents tab filters
         | 
| 194 | 
            +
                    const agentsSearchInput = document.getElementById('agents-search-input');
         | 
| 195 | 
            +
                    const agentsTypeFilter = document.getElementById('agents-type-filter');
         | 
| 196 | 
            +
                    
         | 
| 197 | 
            +
                    if (agentsSearchInput) {
         | 
| 198 | 
            +
                        agentsSearchInput.addEventListener('input', () => {
         | 
| 199 | 
            +
                            if (this.uiStateManager.getCurrentTab() === 'agents') this.renderCurrentTab();
         | 
| 200 | 
            +
                        });
         | 
| 201 | 
            +
                    }
         | 
| 202 | 
            +
                    
         | 
| 203 | 
            +
                    if (agentsTypeFilter) {
         | 
| 204 | 
            +
                        agentsTypeFilter.addEventListener('change', () => {
         | 
| 205 | 
            +
                            if (this.uiStateManager.getCurrentTab() === 'agents') this.renderCurrentTab();
         | 
| 206 | 
            +
                        });
         | 
| 207 | 
            +
                    }
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                    // Tools tab filters
         | 
| 210 | 
            +
                    const toolsSearchInput = document.getElementById('tools-search-input');
         | 
| 211 | 
            +
                    const toolsTypeFilter = document.getElementById('tools-type-filter');
         | 
| 212 | 
            +
                    
         | 
| 213 | 
            +
                    if (toolsSearchInput) {
         | 
| 214 | 
            +
                        toolsSearchInput.addEventListener('input', () => {
         | 
| 215 | 
            +
                            if (this.uiStateManager.getCurrentTab() === 'tools') this.renderCurrentTab();
         | 
| 216 | 
            +
                        });
         | 
| 217 | 
            +
                    }
         | 
| 218 | 
            +
                    
         | 
| 219 | 
            +
                    if (toolsTypeFilter) {
         | 
| 220 | 
            +
                        toolsTypeFilter.addEventListener('change', () => {
         | 
| 221 | 
            +
                            if (this.uiStateManager.getCurrentTab() === 'tools') this.renderCurrentTab();
         | 
| 222 | 
            +
                        });
         | 
| 223 | 
            +
                    }
         | 
| 224 | 
            +
             | 
| 225 | 
            +
                    // Files tab filters  
         | 
| 226 | 
            +
                    const filesSearchInput = document.getElementById('files-search-input');
         | 
| 227 | 
            +
                    const filesTypeFilter = document.getElementById('files-type-filter');
         | 
| 228 | 
            +
                    
         | 
| 229 | 
            +
                    if (filesSearchInput) {
         | 
| 230 | 
            +
                        filesSearchInput.addEventListener('input', () => {
         | 
| 231 | 
            +
                            if (this.uiStateManager.getCurrentTab() === 'files') this.renderCurrentTab();
         | 
| 232 | 
            +
                        });
         | 
| 233 | 
            +
                    }
         | 
| 234 | 
            +
                    
         | 
| 235 | 
            +
                    if (filesTypeFilter) {
         | 
| 236 | 
            +
                        filesTypeFilter.addEventListener('change', () => {
         | 
| 237 | 
            +
                            if (this.uiStateManager.getCurrentTab() === 'files') this.renderCurrentTab();
         | 
| 238 | 
            +
                        });
         | 
| 239 | 
            +
                    }
         | 
| 240 | 
            +
                }
         | 
| 241 | 
            +
             | 
| 242 | 
            +
                /**
         | 
| 243 | 
            +
                 * Initialize from URL parameters
         | 
| 244 | 
            +
                 */
         | 
| 245 | 
            +
                initializeFromURL() {
         | 
| 246 | 
            +
                    const params = new URLSearchParams(window.location.search);
         | 
| 247 | 
            +
                    this.socketManager.initializeFromURL(params);
         | 
| 248 | 
            +
                }
         | 
| 249 | 
            +
             | 
| 250 | 
            +
                /**
         | 
| 251 | 
            +
                 * Render current tab content
         | 
| 252 | 
            +
                 */
         | 
| 253 | 
            +
                renderCurrentTab() {
         | 
| 254 | 
            +
                    const currentTab = this.uiStateManager.getCurrentTab();
         | 
| 255 | 
            +
                    
         | 
| 256 | 
            +
                    switch (currentTab) {
         | 
| 257 | 
            +
                        case 'events':
         | 
| 258 | 
            +
                            // Events tab is handled by EventViewer
         | 
| 259 | 
            +
                            break;
         | 
| 260 | 
            +
                        case 'agents':
         | 
| 261 | 
            +
                            this.renderAgents();
         | 
| 262 | 
            +
                            break;
         | 
| 263 | 
            +
                        case 'tools':
         | 
| 264 | 
            +
                            this.renderTools();
         | 
| 265 | 
            +
                            break;
         | 
| 266 | 
            +
                        case 'files':
         | 
| 267 | 
            +
                            this.renderFiles();
         | 
| 268 | 
            +
                            break;
         | 
| 269 | 
            +
                    }
         | 
| 270 | 
            +
                    
         | 
| 271 | 
            +
                    // Update selection UI if we have a selected card
         | 
| 272 | 
            +
                    const selectedCard = this.uiStateManager.getSelectedCard();
         | 
| 273 | 
            +
                    if (selectedCard.tab === currentTab) {
         | 
| 274 | 
            +
                        this.uiStateManager.updateCardSelectionUI();
         | 
| 275 | 
            +
                    }
         | 
| 276 | 
            +
                    
         | 
| 277 | 
            +
                    // Update unified selection UI to maintain consistency
         | 
| 278 | 
            +
                    this.uiStateManager.updateUnifiedSelectionUI();
         | 
| 279 | 
            +
                }
         | 
| 280 | 
            +
             | 
| 281 | 
            +
                /**
         | 
| 282 | 
            +
                 * Render agents tab with unique instance view (one row per PM delegation)
         | 
| 283 | 
            +
                 */
         | 
| 284 | 
            +
                renderAgents() {
         | 
| 285 | 
            +
                    const agentsList = document.getElementById('agents-list');
         | 
| 286 | 
            +
                    if (!agentsList) return;
         | 
| 287 | 
            +
             | 
| 288 | 
            +
                    // Process agent inference to get PM delegations
         | 
| 289 | 
            +
                    this.agentInference.processAgentInference();
         | 
| 290 | 
            +
                    
         | 
| 291 | 
            +
                    // Generate HTML for unique agent instances
         | 
| 292 | 
            +
                    const events = this.eventProcessor.getFilteredEventsForTab('agents');
         | 
| 293 | 
            +
                    const agentHTML = this.eventProcessor.generateAgentHTML(events);
         | 
| 294 | 
            +
                    
         | 
| 295 | 
            +
                    agentsList.innerHTML = agentHTML;
         | 
| 296 | 
            +
                    this.exportManager.scrollListToBottom('agents-list');
         | 
| 297 | 
            +
                    
         | 
| 298 | 
            +
                    // Update filter dropdowns with unique instances
         | 
| 299 | 
            +
                    const uniqueInstances = this.agentInference.getUniqueAgentInstances();
         | 
| 300 | 
            +
                    this.updateAgentsFilterDropdowns(uniqueInstances);
         | 
| 301 | 
            +
                }
         | 
| 302 | 
            +
             | 
| 303 | 
            +
                /**
         | 
| 304 | 
            +
                 * Render tools tab with unique instance view (one row per unique tool call)
         | 
| 305 | 
            +
                 */
         | 
| 306 | 
            +
                renderTools() {
         | 
| 307 | 
            +
                    const toolsList = document.getElementById('tools-list');
         | 
| 308 | 
            +
                    if (!toolsList) return;
         | 
| 309 | 
            +
             | 
| 310 | 
            +
                    const toolCalls = this.fileToolTracker.getToolCalls();
         | 
| 311 | 
            +
                    const toolCallsArray = Array.from(toolCalls.entries());
         | 
| 312 | 
            +
                    const uniqueToolInstances = this.eventProcessor.getUniqueToolInstances(toolCallsArray);
         | 
| 313 | 
            +
                    const toolHTML = this.eventProcessor.generateToolHTML(uniqueToolInstances);
         | 
| 314 | 
            +
                    
         | 
| 315 | 
            +
                    toolsList.innerHTML = toolHTML;
         | 
| 316 | 
            +
                    this.exportManager.scrollListToBottom('tools-list');
         | 
| 317 | 
            +
                    
         | 
| 318 | 
            +
                    // Update filter dropdowns
         | 
| 319 | 
            +
                    this.updateToolsFilterDropdowns(uniqueToolInstances);
         | 
| 320 | 
            +
                }
         | 
| 321 | 
            +
             | 
| 322 | 
            +
                /**
         | 
| 323 | 
            +
                 * Render files tab with unique instance view (one row per unique file)
         | 
| 324 | 
            +
                 */
         | 
| 325 | 
            +
                renderFiles() {
         | 
| 326 | 
            +
                    const filesList = document.getElementById('files-list');
         | 
| 327 | 
            +
                    if (!filesList) return;
         | 
| 328 | 
            +
             | 
| 329 | 
            +
                    const fileOperations = this.fileToolTracker.getFileOperations();
         | 
| 330 | 
            +
                    const filesArray = Array.from(fileOperations.entries());
         | 
| 331 | 
            +
                    const uniqueFileInstances = this.eventProcessor.getUniqueFileInstances(filesArray);
         | 
| 332 | 
            +
                    const fileHTML = this.eventProcessor.generateFileHTML(uniqueFileInstances);
         | 
| 333 | 
            +
                    
         | 
| 334 | 
            +
                    filesList.innerHTML = fileHTML;
         | 
| 335 | 
            +
                    this.exportManager.scrollListToBottom('files-list');
         | 
| 336 | 
            +
                    
         | 
| 337 | 
            +
                    // Update filter dropdowns
         | 
| 338 | 
            +
                    this.updateFilesFilterDropdowns(filesArray);
         | 
| 339 | 
            +
                }
         | 
| 340 | 
            +
             | 
| 341 | 
            +
                /**
         | 
| 342 | 
            +
                 * Update agents filter dropdowns for unique instances
         | 
| 343 | 
            +
                 */
         | 
| 344 | 
            +
                updateAgentsFilterDropdowns(uniqueInstances) {
         | 
| 345 | 
            +
                    const agentTypes = new Set();
         | 
| 346 | 
            +
                    
         | 
| 347 | 
            +
                    // uniqueInstances is already an array of unique agent instances
         | 
| 348 | 
            +
                    uniqueInstances.forEach(instance => {
         | 
| 349 | 
            +
                        if (instance.agentName && instance.agentName !== 'Unknown') {
         | 
| 350 | 
            +
                            agentTypes.add(instance.agentName);
         | 
| 351 | 
            +
                        }
         | 
| 352 | 
            +
                    });
         | 
| 353 | 
            +
                    
         | 
| 354 | 
            +
                    const sortedTypes = Array.from(agentTypes).filter(type => type && type.trim() !== '');
         | 
| 355 | 
            +
                    this.populateFilterDropdown('agents-type-filter', sortedTypes, 'All Agent Types');
         | 
| 356 | 
            +
                    
         | 
| 357 | 
            +
                    // Debug log
         | 
| 358 | 
            +
                    if (sortedTypes.length > 0) {
         | 
| 359 | 
            +
                        console.log('Agent types found for filter:', sortedTypes);
         | 
| 360 | 
            +
                    } else {
         | 
| 361 | 
            +
                        console.log('No agent types found for filter. Events:', events.length);
         | 
| 362 | 
            +
                    }
         | 
| 363 | 
            +
                }
         | 
| 364 | 
            +
             | 
| 365 | 
            +
                /**
         | 
| 366 | 
            +
                 * Update tools filter dropdowns
         | 
| 367 | 
            +
                 */
         | 
| 368 | 
            +
                updateToolsFilterDropdowns(toolCallsArray) {
         | 
| 369 | 
            +
                    const toolNames = [...new Set(toolCallsArray.map(([key, toolCall]) => toolCall.tool_name))]
         | 
| 370 | 
            +
                        .filter(name => name);
         | 
| 371 | 
            +
                    
         | 
| 372 | 
            +
                    this.populateFilterDropdown('tools-type-filter', toolNames, 'All Tools');
         | 
| 373 | 
            +
                }
         | 
| 374 | 
            +
             | 
| 375 | 
            +
                /**
         | 
| 376 | 
            +
                 * Update files filter dropdowns
         | 
| 377 | 
            +
                 */
         | 
| 378 | 
            +
                updateFilesFilterDropdowns(filesArray) {
         | 
| 379 | 
            +
                    const operations = [...new Set(filesArray.flatMap(([path, data]) => 
         | 
| 380 | 
            +
                        data.operations.map(op => op.operation)
         | 
| 381 | 
            +
                    ))].filter(op => op);
         | 
| 382 | 
            +
                    
         | 
| 383 | 
            +
                    this.populateFilterDropdown('files-type-filter', operations, 'All Operations');
         | 
| 384 | 
            +
                }
         | 
| 385 | 
            +
             | 
| 386 | 
            +
                /**
         | 
| 387 | 
            +
                 * Populate filter dropdown with values
         | 
| 388 | 
            +
                 */
         | 
| 389 | 
            +
                populateFilterDropdown(selectId, values, allOption = 'All') {
         | 
| 390 | 
            +
                    const select = document.getElementById(selectId);
         | 
| 391 | 
            +
                    if (!select) return;
         | 
| 392 | 
            +
             | 
| 393 | 
            +
                    const currentValue = select.value;
         | 
| 394 | 
            +
                    const sortedValues = values.sort((a, b) => a.localeCompare(b));
         | 
| 395 | 
            +
                    
         | 
| 396 | 
            +
                    // Clear existing options except the first "All" option
         | 
| 397 | 
            +
                    select.innerHTML = `<option value="">${allOption}</option>`;
         | 
| 398 | 
            +
                    
         | 
| 399 | 
            +
                    // Add sorted values
         | 
| 400 | 
            +
                    sortedValues.forEach(value => {
         | 
| 401 | 
            +
                        const option = document.createElement('option');
         | 
| 402 | 
            +
                        option.value = value;
         | 
| 403 | 
            +
                        option.textContent = value;
         | 
| 404 | 
            +
                        select.appendChild(option);
         | 
| 405 | 
            +
                    });
         | 
| 406 | 
            +
                    
         | 
| 407 | 
            +
                    // Restore previous selection if it still exists
         | 
| 408 | 
            +
                    if (currentValue && sortedValues.includes(currentValue)) {
         | 
| 409 | 
            +
                        select.value = currentValue;
         | 
| 410 | 
            +
                    }
         | 
| 411 | 
            +
                }
         | 
| 412 | 
            +
             | 
| 413 | 
            +
                /**
         | 
| 414 | 
            +
                 * Show card details for specified tab and index
         | 
| 415 | 
            +
                 */
         | 
| 416 | 
            +
                showCardDetails(tabName, index) {
         | 
| 417 | 
            +
                    switch (tabName) {
         | 
| 418 | 
            +
                        case 'events':
         | 
| 419 | 
            +
                            if (this.eventViewer) {
         | 
| 420 | 
            +
                                this.eventViewer.showEventDetails(index);
         | 
| 421 | 
            +
                            }
         | 
| 422 | 
            +
                            break;
         | 
| 423 | 
            +
                        case 'agents':
         | 
| 424 | 
            +
                            this.showAgentDetailsByIndex(index);
         | 
| 425 | 
            +
                            break;
         | 
| 426 | 
            +
                        case 'tools':
         | 
| 427 | 
            +
                            this.showToolDetailsByIndex(index);
         | 
| 428 | 
            +
                            break;
         | 
| 429 | 
            +
                        case 'files':
         | 
| 430 | 
            +
                            this.showFileDetailsByIndex(index);
         | 
| 431 | 
            +
                            break;
         | 
| 432 | 
            +
                    }
         | 
| 433 | 
            +
                }
         | 
| 434 | 
            +
             | 
| 435 | 
            +
                /**
         | 
| 436 | 
            +
                 * Show agent details by index
         | 
| 437 | 
            +
                 */
         | 
| 438 | 
            +
                showAgentDetailsByIndex(index) {
         | 
| 439 | 
            +
                    const events = this.eventProcessor.getFilteredEventsForTab('agents');
         | 
| 440 | 
            +
                    
         | 
| 441 | 
            +
                    // Defensive checks
         | 
| 442 | 
            +
                    if (!events || !Array.isArray(events) || index < 0 || index >= events.length) {
         | 
| 443 | 
            +
                        console.warn('Dashboard: Invalid agent index or events array');
         | 
| 444 | 
            +
                        return;
         | 
| 445 | 
            +
                    }
         | 
| 446 | 
            +
                    
         | 
| 447 | 
            +
                    const filteredSingleEvent = this.eventProcessor.applyAgentsFilters([events[index]]);
         | 
| 448 | 
            +
                    
         | 
| 449 | 
            +
                    if (filteredSingleEvent.length > 0 && this.moduleViewer && 
         | 
| 450 | 
            +
                        typeof this.moduleViewer.showAgentEvent === 'function') {
         | 
| 451 | 
            +
                        const event = filteredSingleEvent[0];
         | 
| 452 | 
            +
                        this.moduleViewer.showAgentEvent(event, index);
         | 
| 453 | 
            +
                    }
         | 
| 454 | 
            +
                }
         | 
| 455 | 
            +
             | 
| 456 | 
            +
                /**
         | 
| 457 | 
            +
                 * Show agent instance details for unique instance view
         | 
| 458 | 
            +
                 * @param {string} instanceId - Agent instance ID
         | 
| 459 | 
            +
                 */
         | 
| 460 | 
            +
                showAgentInstanceDetails(instanceId) {
         | 
| 461 | 
            +
                    const pmDelegations = this.agentInference.getPMDelegations();
         | 
| 462 | 
            +
                    const instance = pmDelegations.get(instanceId);
         | 
| 463 | 
            +
                    
         | 
| 464 | 
            +
                    if (!instance) {
         | 
| 465 | 
            +
                        // Check if it's an implied delegation
         | 
| 466 | 
            +
                        const uniqueInstances = this.agentInference.getUniqueAgentInstances();
         | 
| 467 | 
            +
                        const impliedInstance = uniqueInstances.find(inst => inst.id === instanceId);
         | 
| 468 | 
            +
                        
         | 
| 469 | 
            +
                        if (!impliedInstance) {
         | 
| 470 | 
            +
                            console.error('Agent instance not found:', instanceId);
         | 
| 471 | 
            +
                            return;
         | 
| 472 | 
            +
                        }
         | 
| 473 | 
            +
                        
         | 
| 474 | 
            +
                        // For implied instances, show basic info
         | 
| 475 | 
            +
                        this.showImpliedAgentDetails(impliedInstance);
         | 
| 476 | 
            +
                        return;
         | 
| 477 | 
            +
                    }
         | 
| 478 | 
            +
                    
         | 
| 479 | 
            +
                    // Show full PM delegation details
         | 
| 480 | 
            +
                    if (this.moduleViewer && typeof this.moduleViewer.showAgentInstance === 'function') {
         | 
| 481 | 
            +
                        this.moduleViewer.showAgentInstance(instance);
         | 
| 482 | 
            +
                    } else {
         | 
| 483 | 
            +
                        // Fallback: show in console or basic modal
         | 
| 484 | 
            +
                        console.log('Agent Instance Details:', {
         | 
| 485 | 
            +
                            id: instanceId,
         | 
| 486 | 
            +
                            agentName: instance.agentName,
         | 
| 487 | 
            +
                            type: 'PM Delegation',
         | 
| 488 | 
            +
                            eventCount: instance.agentEvents.length,
         | 
| 489 | 
            +
                            startTime: instance.timestamp,
         | 
| 490 | 
            +
                            pmCall: instance.pmCall
         | 
| 491 | 
            +
                        });
         | 
| 492 | 
            +
                    }
         | 
| 493 | 
            +
                }
         | 
| 494 | 
            +
             | 
| 495 | 
            +
                /**
         | 
| 496 | 
            +
                 * Show implied agent details (agents without explicit PM delegation)
         | 
| 497 | 
            +
                 * @param {Object} impliedInstance - Implied agent instance
         | 
| 498 | 
            +
                 */
         | 
| 499 | 
            +
                showImpliedAgentDetails(impliedInstance) {
         | 
| 500 | 
            +
                    if (this.moduleViewer && typeof this.moduleViewer.showImpliedAgent === 'function') {
         | 
| 501 | 
            +
                        this.moduleViewer.showImpliedAgent(impliedInstance);
         | 
| 502 | 
            +
                    } else {
         | 
| 503 | 
            +
                        // Fallback: show in console or basic modal
         | 
| 504 | 
            +
                        console.log('Implied Agent Details:', {
         | 
| 505 | 
            +
                            id: impliedInstance.id,
         | 
| 506 | 
            +
                            agentName: impliedInstance.agentName,
         | 
| 507 | 
            +
                            type: 'Implied PM Delegation',
         | 
| 508 | 
            +
                            eventCount: impliedInstance.eventCount,
         | 
| 509 | 
            +
                            startTime: impliedInstance.timestamp,
         | 
| 510 | 
            +
                            note: 'No explicit PM call found - inferred from agent activity'
         | 
| 511 | 
            +
                        });
         | 
| 512 | 
            +
                    }
         | 
| 513 | 
            +
                }
         | 
| 514 | 
            +
             | 
| 515 | 
            +
                /**
         | 
| 516 | 
            +
                 * Show tool details by index
         | 
| 517 | 
            +
                 */
         | 
| 518 | 
            +
                showToolDetailsByIndex(index) {
         | 
| 519 | 
            +
                    const toolCalls = this.fileToolTracker.getToolCalls();
         | 
| 520 | 
            +
                    const toolCallsArray = Array.from(toolCalls.entries());
         | 
| 521 | 
            +
                    const filteredToolCalls = this.eventProcessor.applyToolCallFilters(toolCallsArray);
         | 
| 522 | 
            +
                    
         | 
| 523 | 
            +
                    if (index >= 0 && index < filteredToolCalls.length) {
         | 
| 524 | 
            +
                        const [toolCallKey] = filteredToolCalls[index];
         | 
| 525 | 
            +
                        this.showToolCallDetails(toolCallKey);
         | 
| 526 | 
            +
                    }
         | 
| 527 | 
            +
                }
         | 
| 528 | 
            +
             | 
| 529 | 
            +
                /**
         | 
| 530 | 
            +
                 * Show file details by index  
         | 
| 531 | 
            +
                 */
         | 
| 532 | 
            +
                showFileDetailsByIndex(index) {
         | 
| 533 | 
            +
                    const fileOperations = this.fileToolTracker.getFileOperations();
         | 
| 534 | 
            +
                    let filesArray = Array.from(fileOperations.entries());
         | 
| 535 | 
            +
                    filesArray = this.eventProcessor.applyFilesFilters(filesArray);
         | 
| 536 | 
            +
                    
         | 
| 537 | 
            +
                    if (index >= 0 && index < filesArray.length) {
         | 
| 538 | 
            +
                        const [filePath] = filesArray[index];
         | 
| 539 | 
            +
                        this.showFileDetails(filePath);
         | 
| 540 | 
            +
                    }
         | 
| 541 | 
            +
                }
         | 
| 542 | 
            +
             | 
| 543 | 
            +
                /**
         | 
| 544 | 
            +
                 * Show tool call details
         | 
| 545 | 
            +
                 */
         | 
| 546 | 
            +
                showToolCallDetails(toolCallKey) {
         | 
| 547 | 
            +
                    const toolCall = this.fileToolTracker.getToolCall(toolCallKey);
         | 
| 548 | 
            +
                    if (toolCall && this.moduleViewer) {
         | 
| 549 | 
            +
                        this.moduleViewer.showToolCall(toolCall, toolCallKey);
         | 
| 550 | 
            +
                    }
         | 
| 551 | 
            +
                }
         | 
| 552 | 
            +
             | 
| 553 | 
            +
                /**
         | 
| 554 | 
            +
                 * Show file details
         | 
| 555 | 
            +
                 */
         | 
| 556 | 
            +
                showFileDetails(filePath) {
         | 
| 557 | 
            +
                    const fileData = this.fileToolTracker.getFileOperationsForFile(filePath);
         | 
| 558 | 
            +
                    if (fileData && this.moduleViewer) {
         | 
| 559 | 
            +
                        this.moduleViewer.showFileOperations(fileData, filePath);
         | 
| 560 | 
            +
                    }
         | 
| 561 | 
            +
                }
         | 
| 562 | 
            +
             | 
| 563 | 
            +
                // ====================================
         | 
| 564 | 
            +
                // BACKWARD COMPATIBILITY METHODS
         | 
| 565 | 
            +
                // ====================================
         | 
| 566 | 
            +
             | 
| 567 | 
            +
                /**
         | 
| 568 | 
            +
                 * Switch tab (backward compatibility)
         | 
| 569 | 
            +
                 */
         | 
| 570 | 
            +
                switchTab(tabName) {
         | 
| 571 | 
            +
                    this.uiStateManager.switchTab(tabName);
         | 
| 572 | 
            +
                }
         | 
| 573 | 
            +
             | 
| 574 | 
            +
                /**
         | 
| 575 | 
            +
                 * Select card (backward compatibility)
         | 
| 576 | 
            +
                 */
         | 
| 577 | 
            +
                selectCard(tabName, index, type, data) {
         | 
| 578 | 
            +
                    this.uiStateManager.selectCard(tabName, index, type, data);
         | 
| 579 | 
            +
                }
         | 
| 580 | 
            +
             | 
| 581 | 
            +
                /**
         | 
| 582 | 
            +
                 * Clear events (backward compatibility)
         | 
| 583 | 
            +
                 */
         | 
| 584 | 
            +
                clearEvents() {
         | 
| 585 | 
            +
                    this.exportManager.clearEvents();
         | 
| 586 | 
            +
                }
         | 
| 587 | 
            +
             | 
| 588 | 
            +
                /**
         | 
| 589 | 
            +
                 * Export events (backward compatibility)
         | 
| 590 | 
            +
                 */
         | 
| 591 | 
            +
                exportEvents() {
         | 
| 592 | 
            +
                    this.exportManager.exportEvents();
         | 
| 593 | 
            +
                }
         | 
| 594 | 
            +
             | 
| 595 | 
            +
                /**
         | 
| 596 | 
            +
                 * Clear selection (backward compatibility)
         | 
| 597 | 
            +
                 */
         | 
| 598 | 
            +
                clearSelection() {
         | 
| 599 | 
            +
                    this.uiStateManager.clearSelection();
         | 
| 600 | 
            +
                    if (this.eventViewer) {
         | 
| 601 | 
            +
                        this.eventViewer.clearSelection();
         | 
| 602 | 
            +
                    }
         | 
| 603 | 
            +
                    if (this.moduleViewer) {
         | 
| 604 | 
            +
                        this.moduleViewer.clear();
         | 
| 605 | 
            +
                    }
         | 
| 606 | 
            +
                }
         | 
| 607 | 
            +
             | 
| 608 | 
            +
             | 
| 609 | 
            +
                /**
         | 
| 610 | 
            +
                 * Get current working directory (backward compatibility)
         | 
| 611 | 
            +
                 */
         | 
| 612 | 
            +
                get currentWorkingDir() {
         | 
| 613 | 
            +
                    return this.workingDirectoryManager.getCurrentWorkingDir();
         | 
| 614 | 
            +
                }
         | 
| 615 | 
            +
             | 
| 616 | 
            +
                /**
         | 
| 617 | 
            +
                 * Set current working directory (backward compatibility)
         | 
| 618 | 
            +
                 */
         | 
| 619 | 
            +
                set currentWorkingDir(dir) {
         | 
| 620 | 
            +
                    this.workingDirectoryManager.setWorkingDirectory(dir);
         | 
| 621 | 
            +
                }
         | 
| 622 | 
            +
             | 
| 623 | 
            +
                /**
         | 
| 624 | 
            +
                 * Get current tab (backward compatibility)
         | 
| 625 | 
            +
                 */
         | 
| 626 | 
            +
                get currentTab() {
         | 
| 627 | 
            +
                    return this.uiStateManager.getCurrentTab();
         | 
| 628 | 
            +
                }
         | 
| 629 | 
            +
             | 
| 630 | 
            +
                /**
         | 
| 631 | 
            +
                 * Get selected card (backward compatibility)
         | 
| 632 | 
            +
                 */
         | 
| 633 | 
            +
                get selectedCard() {
         | 
| 634 | 
            +
                    return this.uiStateManager.getSelectedCard();
         | 
| 635 | 
            +
                }
         | 
| 636 | 
            +
             | 
| 637 | 
            +
                /**
         | 
| 638 | 
            +
                 * Get file operations (backward compatibility)
         | 
| 639 | 
            +
                 */
         | 
| 640 | 
            +
                get fileOperations() {
         | 
| 641 | 
            +
                    return this.fileToolTracker.getFileOperations();
         | 
| 642 | 
            +
                }
         | 
| 643 | 
            +
             | 
| 644 | 
            +
                /**
         | 
| 645 | 
            +
                 * Get tool calls (backward compatibility)
         | 
| 646 | 
            +
                 */  
         | 
| 647 | 
            +
                get toolCalls() {
         | 
| 648 | 
            +
                    return this.fileToolTracker.getToolCalls();
         | 
| 649 | 
            +
                }
         | 
| 650 | 
            +
             | 
| 651 | 
            +
             | 
| 652 | 
            +
                /**
         | 
| 653 | 
            +
                 * Get tab navigation state (backward compatibility)
         | 
| 654 | 
            +
                 */
         | 
| 655 | 
            +
                get tabNavigation() {
         | 
| 656 | 
            +
                    return this.uiStateManager ? this.uiStateManager.tabNavigation : null;
         | 
| 657 | 
            +
                }
         | 
| 658 | 
            +
            }
         | 
| 659 | 
            +
             | 
| 660 | 
            +
            // Global functions for backward compatibility
         | 
| 661 | 
            +
            window.clearEvents = function() {
         | 
| 662 | 
            +
                if (window.dashboard) {
         | 
| 663 | 
            +
                    window.dashboard.clearEvents();
         | 
| 664 | 
            +
                }
         | 
| 665 | 
            +
            };
         | 
| 666 | 
            +
             | 
| 667 | 
            +
            window.exportEvents = function() {
         | 
| 668 | 
            +
                if (window.dashboard) {
         | 
| 669 | 
            +
                    window.dashboard.exportEvents();
         | 
| 670 | 
            +
                }
         | 
| 671 | 
            +
            };
         | 
| 672 | 
            +
             | 
| 673 | 
            +
            window.clearSelection = function() {
         | 
| 674 | 
            +
                if (window.dashboard) {
         | 
| 675 | 
            +
                    window.dashboard.clearSelection();
         | 
| 676 | 
            +
                }
         | 
| 677 | 
            +
            };
         | 
| 678 | 
            +
             | 
| 679 | 
            +
            window.switchTab = function(tabName) {
         | 
| 680 | 
            +
                if (window.dashboard) {
         | 
| 681 | 
            +
                    window.dashboard.switchTab(tabName);
         | 
| 682 | 
            +
                }
         | 
| 683 | 
            +
            };
         | 
| 684 | 
            +
             | 
| 685 | 
            +
            // File Viewer Modal Functions - REMOVED DUPLICATE (keeping the one at line 1553)
         | 
| 686 | 
            +
            window.showFileViewerModal = function(filePath, workingDir) {
         | 
| 687 | 
            +
                // Use the dashboard's current working directory if not provided
         | 
| 688 | 
            +
                if (!workingDir && window.dashboard && window.dashboard.currentWorkingDir) {
         | 
| 689 | 
            +
                    workingDir = window.dashboard.currentWorkingDir;
         | 
| 690 | 
            +
                }
         | 
| 691 | 
            +
                
         | 
| 692 | 
            +
                // Create modal if it doesn't exist
         | 
| 693 | 
            +
                let modal = document.getElementById('file-viewer-modal');
         | 
| 694 | 
            +
                if (!modal) {
         | 
| 695 | 
            +
                    modal = createFileViewerModal();
         | 
| 696 | 
            +
                    document.body.appendChild(modal);
         | 
| 697 | 
            +
                }
         | 
| 698 | 
            +
                
         | 
| 699 | 
            +
                // Update modal content
         | 
| 700 | 
            +
                updateFileViewerModal(modal, filePath, workingDir);
         | 
| 701 | 
            +
                
         | 
| 702 | 
            +
                // Show the modal as flex container
         | 
| 703 | 
            +
                modal.style.display = 'flex';
         | 
| 704 | 
            +
                document.body.style.overflow = 'hidden'; // Prevent background scrolling
         | 
| 705 | 
            +
            };
         | 
| 706 | 
            +
             | 
| 707 | 
            +
            window.hideFileViewerModal = function() {
         | 
| 708 | 
            +
                const modal = document.getElementById('file-viewer-modal');
         | 
| 709 | 
            +
                if (modal) {
         | 
| 710 | 
            +
                    modal.style.display = 'none';
         | 
| 711 | 
            +
                    document.body.style.overflow = ''; // Restore background scrolling
         | 
| 712 | 
            +
                }
         | 
| 713 | 
            +
            };
         | 
| 714 | 
            +
             | 
| 715 | 
            +
            window.copyFileContent = function() {
         | 
| 716 | 
            +
                const modal = document.getElementById('file-viewer-modal');
         | 
| 717 | 
            +
                if (!modal) return;
         | 
| 718 | 
            +
                
         | 
| 719 | 
            +
                const codeElement = modal.querySelector('.file-content-code');
         | 
| 720 | 
            +
                if (!codeElement) return;
         | 
| 721 | 
            +
                
         | 
| 722 | 
            +
                const text = codeElement.textContent;
         | 
| 723 | 
            +
                
         | 
| 724 | 
            +
                if (navigator.clipboard && navigator.clipboard.writeText) {
         | 
| 725 | 
            +
                    navigator.clipboard.writeText(text).then(() => {
         | 
| 726 | 
            +
                        // Show brief feedback
         | 
| 727 | 
            +
                        const button = modal.querySelector('.file-content-copy');
         | 
| 728 | 
            +
                        const originalText = button.textContent;
         | 
| 729 | 
            +
                        button.textContent = '✅ Copied!';
         | 
| 730 | 
            +
                        setTimeout(() => {
         | 
| 731 | 
            +
                            button.textContent = originalText;
         | 
| 732 | 
            +
                        }, 2000);
         | 
| 733 | 
            +
                    }).catch(err => {
         | 
| 734 | 
            +
                        console.error('Failed to copy text:', err);
         | 
| 735 | 
            +
                    });
         | 
| 736 | 
            +
                } else {
         | 
| 737 | 
            +
                    // Fallback for older browsers
         | 
| 738 | 
            +
                    const textarea = document.createElement('textarea');
         | 
| 739 | 
            +
                    textarea.value = text;
         | 
| 740 | 
            +
                    document.body.appendChild(textarea);
         | 
| 741 | 
            +
                    textarea.select();
         | 
| 742 | 
            +
                    document.execCommand('copy');
         | 
| 743 | 
            +
                    document.body.removeChild(textarea);
         | 
| 744 | 
            +
                    
         | 
| 745 | 
            +
                    const button = modal.querySelector('.file-content-copy');
         | 
| 746 | 
            +
                    const originalText = button.textContent;
         | 
| 747 | 
            +
                    button.textContent = '✅ Copied!';
         | 
| 748 | 
            +
                    setTimeout(() => {
         | 
| 749 | 
            +
                        button.textContent = originalText;
         | 
| 750 | 
            +
                    }, 2000);
         | 
| 751 | 
            +
                }
         | 
| 752 | 
            +
            };
         | 
| 753 | 
            +
             | 
| 754 | 
            +
            function createFileViewerModal() {
         | 
| 755 | 
            +
                const modal = document.createElement('div');
         | 
| 756 | 
            +
                modal.id = 'file-viewer-modal';
         | 
| 757 | 
            +
                modal.className = 'modal file-viewer-modal';
         | 
| 758 | 
            +
                
         | 
| 759 | 
            +
                modal.innerHTML = `
         | 
| 760 | 
            +
                    <div class="modal-content file-viewer-content">
         | 
| 761 | 
            +
                        <div class="file-viewer-header">
         | 
| 762 | 
            +
                            <h2 class="file-viewer-title">
         | 
| 763 | 
            +
                                <span class="file-viewer-icon">📄</span>
         | 
| 764 | 
            +
                                <span class="file-viewer-title-text">File Viewer</span>
         | 
| 765 | 
            +
                            </h2>
         | 
| 766 | 
            +
                            <div class="file-viewer-meta">
         | 
| 767 | 
            +
                                <span class="file-viewer-file-path"></span>
         | 
| 768 | 
            +
                                <span class="file-viewer-file-size"></span>
         | 
| 769 | 
            +
                            </div>
         | 
| 770 | 
            +
                            <button class="file-viewer-close" onclick="hideFileViewerModal()">
         | 
| 771 | 
            +
                                <span>×</span>
         | 
| 772 | 
            +
                            </button>
         | 
| 773 | 
            +
                        </div>
         | 
| 774 | 
            +
                        <div class="file-viewer-body">
         | 
| 775 | 
            +
                            <div class="file-viewer-loading">
         | 
| 776 | 
            +
                                <div class="loading-spinner"></div>
         | 
| 777 | 
            +
                                <span>Loading file content...</span>
         | 
| 778 | 
            +
                            </div>
         | 
| 779 | 
            +
                            <div class="file-viewer-error" style="display: none;">
         | 
| 780 | 
            +
                                <div class="error-icon">⚠️</div>
         | 
| 781 | 
            +
                                <div class="error-message"></div>
         | 
| 782 | 
            +
                                <div class="error-suggestions"></div>
         | 
| 783 | 
            +
                            </div>
         | 
| 784 | 
            +
                            <div class="file-viewer-content-area" style="display: none;">
         | 
| 785 | 
            +
                                <div class="file-viewer-toolbar">
         | 
| 786 | 
            +
                                    <div class="file-viewer-info">
         | 
| 787 | 
            +
                                        <span class="file-extension"></span>
         | 
| 788 | 
            +
                                        <span class="file-encoding"></span>
         | 
| 789 | 
            +
                                    </div>
         | 
| 790 | 
            +
                                    <div class="file-viewer-actions">
         | 
| 791 | 
            +
                                        <button class="file-content-copy" onclick="copyFileContent()">
         | 
| 792 | 
            +
                                            📋 Copy
         | 
| 793 | 
            +
                                        </button>
         | 
| 794 | 
            +
                                    </div>
         | 
| 795 | 
            +
                                </div>
         | 
| 796 | 
            +
                                <div class="file-viewer-scroll-wrapper">
         | 
| 797 | 
            +
                                    <pre class="file-content-display"><code class="file-content-code"></code></pre>
         | 
| 798 | 
            +
                                </div>
         | 
| 799 | 
            +
                            </div>
         | 
| 800 | 
            +
                        </div>
         | 
| 801 | 
            +
                    </div>
         | 
| 802 | 
            +
                `;
         | 
| 803 | 
            +
                
         | 
| 804 | 
            +
                // Close modal when clicking outside
         | 
| 805 | 
            +
                modal.addEventListener('click', (e) => {
         | 
| 806 | 
            +
                    if (e.target === modal) {
         | 
| 807 | 
            +
                        hideFileViewerModal();
         | 
| 808 | 
            +
                    }
         | 
| 809 | 
            +
                });
         | 
| 810 | 
            +
                
         | 
| 811 | 
            +
                // Close modal with Escape key
         | 
| 812 | 
            +
                document.addEventListener('keydown', (e) => {
         | 
| 813 | 
            +
                    if (e.key === 'Escape' && modal.style.display === 'flex') {
         | 
| 814 | 
            +
                        hideFileViewerModal();
         | 
| 815 | 
            +
                    }
         | 
| 816 | 
            +
                });
         | 
| 817 | 
            +
                
         | 
| 818 | 
            +
                return modal;
         | 
| 819 | 
            +
            }
         | 
| 820 | 
            +
             | 
| 821 | 
            +
            async function updateFileViewerModal(modal, filePath, workingDir) {
         | 
| 822 | 
            +
                // Update header info
         | 
| 823 | 
            +
                const filePathElement = modal.querySelector('.file-viewer-file-path');
         | 
| 824 | 
            +
                const fileSizeElement = modal.querySelector('.file-viewer-file-size');
         | 
| 825 | 
            +
                
         | 
| 826 | 
            +
                filePathElement.textContent = filePath;
         | 
| 827 | 
            +
                fileSizeElement.textContent = '';
         | 
| 828 | 
            +
                
         | 
| 829 | 
            +
                // Show loading state
         | 
| 830 | 
            +
                modal.querySelector('.file-viewer-loading').style.display = 'flex';
         | 
| 831 | 
            +
                modal.querySelector('.file-viewer-error').style.display = 'none';
         | 
| 832 | 
            +
                modal.querySelector('.file-viewer-content-area').style.display = 'none';
         | 
| 833 | 
            +
                
         | 
| 834 | 
            +
                try {
         | 
| 835 | 
            +
                    // Get the Socket.IO client
         | 
| 836 | 
            +
                    const socket = window.socket || window.dashboard?.socketClient?.socket;
         | 
| 837 | 
            +
                    if (!socket) {
         | 
| 838 | 
            +
                        throw new Error('No socket connection available');
         | 
| 839 | 
            +
                    }
         | 
| 840 | 
            +
                    
         | 
| 841 | 
            +
                    // Set up one-time listener for file content response
         | 
| 842 | 
            +
                    const responsePromise = new Promise((resolve, reject) => {
         | 
| 843 | 
            +
                        const responseHandler = (data) => {
         | 
| 844 | 
            +
                            if (data.file_path === filePath) {
         | 
| 845 | 
            +
                                socket.off('file_content_response', responseHandler);
         | 
| 846 | 
            +
                                if (data.success) {
         | 
| 847 | 
            +
                                    resolve(data);
         | 
| 848 | 
            +
                                } else {
         | 
| 849 | 
            +
                                    reject(new Error(data.error || 'Failed to read file'));
         | 
| 850 | 
            +
                                }
         | 
| 851 | 
            +
                            }
         | 
| 852 | 
            +
                        };
         | 
| 853 | 
            +
                        
         | 
| 854 | 
            +
                        socket.on('file_content_response', responseHandler);
         | 
| 855 | 
            +
                        
         | 
| 856 | 
            +
                        // Timeout after 10 seconds
         | 
| 857 | 
            +
                        setTimeout(() => {
         | 
| 858 | 
            +
                            socket.off('file_content_response', responseHandler);
         | 
| 859 | 
            +
                            reject(new Error('Request timeout'));
         | 
| 860 | 
            +
                        }, 10000);
         | 
| 861 | 
            +
                    });
         | 
| 862 | 
            +
                    
         | 
| 863 | 
            +
                    // Send file read request
         | 
| 864 | 
            +
                    socket.emit('read_file', {
         | 
| 865 | 
            +
                        file_path: filePath,
         | 
| 866 | 
            +
                        working_dir: workingDir
         | 
| 867 | 
            +
                    });
         | 
| 868 | 
            +
                    
         | 
| 869 | 
            +
                    console.log('📄 File viewer request sent:', {
         | 
| 870 | 
            +
                        filePath,
         | 
| 871 | 
            +
                        workingDir
         | 
| 872 | 
            +
                    });
         | 
| 873 | 
            +
                    
         | 
| 874 | 
            +
                    // Wait for response
         | 
| 875 | 
            +
                    const result = await responsePromise;
         | 
| 876 | 
            +
                    console.log('📦 File content received:', result);
         | 
| 877 | 
            +
                    
         | 
| 878 | 
            +
                    // Hide loading
         | 
| 879 | 
            +
                    modal.querySelector('.file-viewer-loading').style.display = 'none';
         | 
| 880 | 
            +
                    
         | 
| 881 | 
            +
                    // Show successful content
         | 
| 882 | 
            +
                    displayFileContent(modal, result);
         | 
| 883 | 
            +
                    
         | 
| 884 | 
            +
                } catch (error) {
         | 
| 885 | 
            +
                    console.error('❌ Failed to fetch file content:', error);
         | 
| 886 | 
            +
                    
         | 
| 887 | 
            +
                    modal.querySelector('.file-viewer-loading').style.display = 'none';
         | 
| 888 | 
            +
                    
         | 
| 889 | 
            +
                    // Create detailed error message
         | 
| 890 | 
            +
                    let errorMessage = error.message || 'Unknown error occurred';
         | 
| 891 | 
            +
                    let suggestions = [];
         | 
| 892 | 
            +
                    
         | 
| 893 | 
            +
                    if (error.message.includes('No socket connection')) {
         | 
| 894 | 
            +
                        errorMessage = 'Failed to connect to the monitoring server';
         | 
| 895 | 
            +
                        suggestions = [
         | 
| 896 | 
            +
                            'Check if the monitoring server is running',
         | 
| 897 | 
            +
                            'Verify the socket connection in the dashboard',
         | 
| 898 | 
            +
                            'Try refreshing the page and reconnecting'
         | 
| 899 | 
            +
                        ];
         | 
| 900 | 
            +
                    } else if (error.message.includes('timeout')) {
         | 
| 901 | 
            +
                        errorMessage = 'Request timed out';
         | 
| 902 | 
            +
                        suggestions = [
         | 
| 903 | 
            +
                            'The file may be too large to load quickly',
         | 
| 904 | 
            +
                            'Check your network connection',
         | 
| 905 | 
            +
                            'Try again in a few moments'
         | 
| 906 | 
            +
                        ];
         | 
| 907 | 
            +
                    } else if (error.message.includes('File does not exist')) {
         | 
| 908 | 
            +
                        errorMessage = 'File not found';
         | 
| 909 | 
            +
                        suggestions = [
         | 
| 910 | 
            +
                            'The file may have been moved or deleted',
         | 
| 911 | 
            +
                            'Check the file path spelling',
         | 
| 912 | 
            +
                            'Refresh the file list to see current files'
         | 
| 913 | 
            +
                        ];
         | 
| 914 | 
            +
                    } else if (error.message.includes('Access denied')) {
         | 
| 915 | 
            +
                        errorMessage = 'Access denied';
         | 
| 916 | 
            +
                        suggestions = [
         | 
| 917 | 
            +
                            'The file is outside the allowed directories',
         | 
| 918 | 
            +
                            'File access is restricted for security reasons'
         | 
| 919 | 
            +
                        ];
         | 
| 920 | 
            +
                    }
         | 
| 921 | 
            +
                    
         | 
| 922 | 
            +
                    displayFileError(modal, {
         | 
| 923 | 
            +
                        error: errorMessage,
         | 
| 924 | 
            +
                        file_path: filePath,
         | 
| 925 | 
            +
                        working_dir: workingDir,
         | 
| 926 | 
            +
                        suggestions: suggestions
         | 
| 927 | 
            +
                    });
         | 
| 928 | 
            +
                }
         | 
| 929 | 
            +
            }
         | 
| 930 | 
            +
             | 
| 931 | 
            +
            function displayFileContent(modal, result) {
         | 
| 932 | 
            +
                console.log('📝 displayFileContent called with:', result);
         | 
| 933 | 
            +
                const contentArea = modal.querySelector('.file-viewer-content-area');
         | 
| 934 | 
            +
                const extensionElement = modal.querySelector('.file-extension');
         | 
| 935 | 
            +
                const encodingElement = modal.querySelector('.file-encoding');
         | 
| 936 | 
            +
                const fileSizeElement = modal.querySelector('.file-viewer-file-size');
         | 
| 937 | 
            +
                const codeElement = modal.querySelector('.file-content-code');
         | 
| 938 | 
            +
                
         | 
| 939 | 
            +
                // Update metadata
         | 
| 940 | 
            +
                if (extensionElement) extensionElement.textContent = `Type: ${result.extension || 'unknown'}`;
         | 
| 941 | 
            +
                if (encodingElement) encodingElement.textContent = `Encoding: ${result.encoding || 'unknown'}`;
         | 
| 942 | 
            +
                if (fileSizeElement) fileSizeElement.textContent = `Size: ${formatFileSize(result.file_size)}`;
         | 
| 943 | 
            +
                
         | 
| 944 | 
            +
                // Update content with basic syntax highlighting
         | 
| 945 | 
            +
                if (codeElement && result.content) {
         | 
| 946 | 
            +
                    console.log('💡 Setting file content, length:', result.content.length);
         | 
| 947 | 
            +
                    codeElement.innerHTML = highlightCode(result.content, result.extension);
         | 
| 948 | 
            +
                    
         | 
| 949 | 
            +
                    // Force scrolling to work by setting explicit heights
         | 
| 950 | 
            +
                    const wrapper = modal.querySelector('.file-viewer-scroll-wrapper');
         | 
| 951 | 
            +
                    if (wrapper) {
         | 
| 952 | 
            +
                        // Give it a moment for content to render
         | 
| 953 | 
            +
                        setTimeout(() => {
         | 
| 954 | 
            +
                            const modalContent = modal.querySelector('.modal-content');
         | 
| 955 | 
            +
                            const header = modal.querySelector('.file-viewer-header');
         | 
| 956 | 
            +
                            const toolbar = modal.querySelector('.file-viewer-toolbar');
         | 
| 957 | 
            +
                            
         | 
| 958 | 
            +
                            const modalHeight = modalContent?.offsetHeight || 0;
         | 
| 959 | 
            +
                            const headerHeight = header?.offsetHeight || 0;
         | 
| 960 | 
            +
                            const toolbarHeight = toolbar?.offsetHeight || 0;
         | 
| 961 | 
            +
                            
         | 
| 962 | 
            +
                            const availableHeight = modalHeight - headerHeight - toolbarHeight - 40; // 40px for padding
         | 
| 963 | 
            +
                            
         | 
| 964 | 
            +
                            console.log('🎯 Setting file viewer scroll height:', {
         | 
| 965 | 
            +
                                modalHeight,
         | 
| 966 | 
            +
                                headerHeight,
         | 
| 967 | 
            +
                                toolbarHeight,
         | 
| 968 | 
            +
                                availableHeight
         | 
| 969 | 
            +
                            });
         | 
| 970 | 
            +
                            
         | 
| 971 | 
            +
                            wrapper.style.maxHeight = `${availableHeight}px`;
         | 
| 972 | 
            +
                            wrapper.style.overflowY = 'auto';
         | 
| 973 | 
            +
                        }, 50);
         | 
| 974 | 
            +
                    }
         | 
| 975 | 
            +
                } else {
         | 
| 976 | 
            +
                    console.warn('⚠️ Missing codeElement or file content');
         | 
| 977 | 
            +
                }
         | 
| 978 | 
            +
                
         | 
| 979 | 
            +
                // Show content area
         | 
| 980 | 
            +
                if (contentArea) {
         | 
| 981 | 
            +
                    contentArea.style.display = 'block';
         | 
| 982 | 
            +
                    console.log('✅ File content area displayed');
         | 
| 983 | 
            +
                }
         | 
| 984 | 
            +
            }
         | 
| 985 | 
            +
             | 
| 986 | 
            +
            function displayFileError(modal, result) {
         | 
| 987 | 
            +
                const errorArea = modal.querySelector('.file-viewer-error');
         | 
| 988 | 
            +
                const messageElement = modal.querySelector('.error-message');
         | 
| 989 | 
            +
                const suggestionsElement = modal.querySelector('.error-suggestions');
         | 
| 990 | 
            +
                
         | 
| 991 | 
            +
                let errorMessage = result.error || 'Unknown error occurred';
         | 
| 992 | 
            +
                
         | 
| 993 | 
            +
                messageElement.innerHTML = `
         | 
| 994 | 
            +
                    <div class="error-main">${errorMessage}</div>
         | 
| 995 | 
            +
                    ${result.file_path ? `<div class="error-file">File: ${result.file_path}</div>` : ''}
         | 
| 996 | 
            +
                    ${result.working_dir ? `<div class="error-dir">Working directory: ${result.working_dir}</div>` : ''}
         | 
| 997 | 
            +
                `;
         | 
| 998 | 
            +
                
         | 
| 999 | 
            +
                if (result.suggestions && result.suggestions.length > 0) {
         | 
| 1000 | 
            +
                    suggestionsElement.innerHTML = `
         | 
| 1001 | 
            +
                        <h4>Suggestions:</h4>
         | 
| 1002 | 
            +
                        <ul>
         | 
| 1003 | 
            +
                            ${result.suggestions.map(s => `<li>${s}</li>`).join('')}
         | 
| 1004 | 
            +
                        </ul>
         | 
| 1005 | 
            +
                    `;
         | 
| 1006 | 
            +
                } else {
         | 
| 1007 | 
            +
                    suggestionsElement.innerHTML = '';
         | 
| 1008 | 
            +
                }
         | 
| 1009 | 
            +
                
         | 
| 1010 | 
            +
                console.log('📋 Displaying file viewer error:', {
         | 
| 1011 | 
            +
                    originalError: result.error,
         | 
| 1012 | 
            +
                    processedMessage: errorMessage,
         | 
| 1013 | 
            +
                    suggestions: result.suggestions
         | 
| 1014 | 
            +
                });
         | 
| 1015 | 
            +
                
         | 
| 1016 | 
            +
                errorArea.style.display = 'block';
         | 
| 1017 | 
            +
            }
         | 
| 1018 | 
            +
             | 
| 1019 | 
            +
            function highlightCode(code, extension) {
         | 
| 1020 | 
            +
                /**
         | 
| 1021 | 
            +
                 * Apply basic syntax highlighting to code content
         | 
| 1022 | 
            +
                 * WHY: Provides basic highlighting for common file types to improve readability.
         | 
| 1023 | 
            +
                 * This is a simple implementation that can be enhanced with full syntax highlighting
         | 
| 1024 | 
            +
                 * libraries like highlight.js or Prism.js if needed.
         | 
| 1025 | 
            +
                 */
         | 
| 1026 | 
            +
                
         | 
| 1027 | 
            +
                // Escape HTML entities first
         | 
| 1028 | 
            +
                const escaped = code
         | 
| 1029 | 
            +
                    .replace(/&/g, '&')
         | 
| 1030 | 
            +
                    .replace(/</g, '<')
         | 
| 1031 | 
            +
                    .replace(/>/g, '>');
         | 
| 1032 | 
            +
                
         | 
| 1033 | 
            +
                // Basic highlighting based on file extension
         | 
| 1034 | 
            +
                switch (extension) {
         | 
| 1035 | 
            +
                    case '.js':
         | 
| 1036 | 
            +
                    case '.jsx':
         | 
| 1037 | 
            +
                    case '.ts':
         | 
| 1038 | 
            +
                    case '.tsx':
         | 
| 1039 | 
            +
                        return highlightJavaScript(escaped);
         | 
| 1040 | 
            +
                    case '.py':
         | 
| 1041 | 
            +
                        return highlightPython(escaped);
         | 
| 1042 | 
            +
                    case '.json':
         | 
| 1043 | 
            +
                        return highlightJSON(escaped);
         | 
| 1044 | 
            +
                    case '.css':
         | 
| 1045 | 
            +
                        return highlightCSS(escaped);
         | 
| 1046 | 
            +
                    case '.html':
         | 
| 1047 | 
            +
                    case '.htm':
         | 
| 1048 | 
            +
                        return highlightHTML(escaped);
         | 
| 1049 | 
            +
                    case '.md':
         | 
| 1050 | 
            +
                    case '.markdown':
         | 
| 1051 | 
            +
                        return highlightMarkdown(escaped);
         | 
| 1052 | 
            +
                    default:
         | 
| 1053 | 
            +
                        // Return with line numbers for plain text
         | 
| 1054 | 
            +
                        return addLineNumbers(escaped);
         | 
| 1055 | 
            +
                }
         | 
| 1056 | 
            +
            }
         | 
| 1057 | 
            +
             | 
| 1058 | 
            +
            function highlightJavaScript(code) {
         | 
| 1059 | 
            +
                return addLineNumbers(code
         | 
| 1060 | 
            +
                    .replace(/\b(function|const|let|var|if|else|for|while|return|import|export|class|extends)\b/g, '<span class="keyword">$1</span>')
         | 
| 1061 | 
            +
                    .replace(/(\/\*[\s\S]*?\*\/|\/\/.*)/g, '<span class="comment">$1</span>')
         | 
| 1062 | 
            +
                    .replace(/('[^']*'|"[^"]*"|`[^`]*`)/g, '<span class="string">$1</span>')
         | 
| 1063 | 
            +
                    .replace(/\b(\d+)\b/g, '<span class="number">$1</span>'));
         | 
| 1064 | 
            +
            }
         | 
| 1065 | 
            +
             | 
| 1066 | 
            +
            function highlightPython(code) {
         | 
| 1067 | 
            +
                return addLineNumbers(code
         | 
| 1068 | 
            +
                    .replace(/\b(def|class|if|elif|else|for|while|return|import|from|as|try|except|finally|with)\b/g, '<span class="keyword">$1</span>')
         | 
| 1069 | 
            +
                    .replace(/(#.*)/g, '<span class="comment">$1</span>')
         | 
| 1070 | 
            +
                    .replace(/('[^']*'|"[^"]*"|"""[\s\S]*?""")/g, '<span class="string">$1</span>')
         | 
| 1071 | 
            +
                    .replace(/\b(\d+)\b/g, '<span class="number">$1</span>'));
         | 
| 1072 | 
            +
            }
         | 
| 1073 | 
            +
             | 
| 1074 | 
            +
            function highlightJSON(code) {
         | 
| 1075 | 
            +
                return addLineNumbers(code
         | 
| 1076 | 
            +
                    .replace(/("[\w\s]*")\s*:/g, '<span class="property">$1</span>:')
         | 
| 1077 | 
            +
                    .replace(/:\s*(".*?")/g, ': <span class="string">$1</span>')
         | 
| 1078 | 
            +
                    .replace(/:\s*(\d+)/g, ': <span class="number">$1</span>')
         | 
| 1079 | 
            +
                    .replace(/:\s*(true|false|null)/g, ': <span class="keyword">$1</span>'));
         | 
| 1080 | 
            +
            }
         | 
| 1081 | 
            +
             | 
| 1082 | 
            +
            function highlightCSS(code) {
         | 
| 1083 | 
            +
                return addLineNumbers(code
         | 
| 1084 | 
            +
                    .replace(/([.#]?[\w-]+)\s*\{/g, '<span class="selector">$1</span> {')
         | 
| 1085 | 
            +
                    .replace(/([\w-]+)\s*:/g, '<span class="property">$1</span>:')
         | 
| 1086 | 
            +
                    .replace(/:\s*([^;]+);/g, ': <span class="value">$1</span>;')
         | 
| 1087 | 
            +
                    .replace(/(\/\*[\s\S]*?\*\/)/g, '<span class="comment">$1</span>'));
         | 
| 1088 | 
            +
            }
         | 
| 1089 | 
            +
             | 
| 1090 | 
            +
            function highlightHTML(code) {
         | 
| 1091 | 
            +
                return addLineNumbers(code
         | 
| 1092 | 
            +
                    .replace(/(<\/?[\w-]+)/g, '<span class="tag">$1</span>')
         | 
| 1093 | 
            +
                    .replace(/([\w-]+)=(['"][^'"]*['"])/g, '<span class="attribute">$1</span>=<span class="string">$2</span>')
         | 
| 1094 | 
            +
                    .replace(/(<!--[\s\S]*?-->)/g, '<span class="comment">$1</span>'));
         | 
| 1095 | 
            +
            }
         | 
| 1096 | 
            +
             | 
| 1097 | 
            +
            function highlightMarkdown(code) {
         | 
| 1098 | 
            +
                return addLineNumbers(code
         | 
| 1099 | 
            +
                    .replace(/^(#{1,6})\s+(.*)$/gm, '<span class="header">$1</span> <span class="header-text">$2</span>')
         | 
| 1100 | 
            +
                    .replace(/\*\*(.*?)\*\*/g, '<span class="bold">**$1**</span>')
         | 
| 1101 | 
            +
                    .replace(/\*(.*?)\*/g, '<span class="italic">*$1*</span>')
         | 
| 1102 | 
            +
                    .replace(/`([^`]+)`/g, '<span class="code">`$1`</span>')
         | 
| 1103 | 
            +
                    .replace(/^\s*[-*+]\s+(.*)$/gm, '<span class="list-marker">•</span> $1'));
         | 
| 1104 | 
            +
            }
         | 
| 1105 | 
            +
             | 
| 1106 | 
            +
            function addLineNumbers(code) {
         | 
| 1107 | 
            +
                const lines = code.split('\n');
         | 
| 1108 | 
            +
                return lines.map((line, index) => 
         | 
| 1109 | 
            +
                    `<span class="line-number">${String(index + 1).padStart(3, ' ')}</span> ${line || ' '}`
         | 
| 1110 | 
            +
                ).join('\n');
         | 
| 1111 | 
            +
            }
         | 
| 1112 | 
            +
             | 
| 1113 | 
            +
            function formatFileSize(bytes) {
         | 
| 1114 | 
            +
                if (!bytes) return '0 B';
         | 
| 1115 | 
            +
                const k = 1024;
         | 
| 1116 | 
            +
                const sizes = ['B', 'KB', 'MB', 'GB'];
         | 
| 1117 | 
            +
                const i = Math.floor(Math.log(bytes) / Math.log(k));
         | 
| 1118 | 
            +
                return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
         | 
| 1119 | 
            +
            }
         | 
| 1120 | 
            +
             | 
| 1121 | 
            +
            // Git Diff Modal Functions - restored from original dashboard
         | 
| 1122 | 
            +
            window.showGitDiffModal = function(filePath, timestamp, workingDir) {
         | 
| 1123 | 
            +
                // Use the dashboard's current working directory if not provided
         | 
| 1124 | 
            +
                if (!workingDir && window.dashboard && window.dashboard.currentWorkingDir) {
         | 
| 1125 | 
            +
                    workingDir = window.dashboard.currentWorkingDir;
         | 
| 1126 | 
            +
                }
         | 
| 1127 | 
            +
                
         | 
| 1128 | 
            +
                // Create modal if it doesn't exist
         | 
| 1129 | 
            +
                let modal = document.getElementById('git-diff-modal');
         | 
| 1130 | 
            +
                if (!modal) {
         | 
| 1131 | 
            +
                    modal = createGitDiffModal();
         | 
| 1132 | 
            +
                    document.body.appendChild(modal);
         | 
| 1133 | 
            +
                }
         | 
| 1134 | 
            +
                
         | 
| 1135 | 
            +
                // Update modal content
         | 
| 1136 | 
            +
                updateGitDiffModal(modal, filePath, timestamp, workingDir);
         | 
| 1137 | 
            +
                
         | 
| 1138 | 
            +
                // Show the modal as flex container
         | 
| 1139 | 
            +
                modal.style.display = 'flex';
         | 
| 1140 | 
            +
                document.body.style.overflow = 'hidden'; // Prevent background scrolling
         | 
| 1141 | 
            +
            };
         | 
| 1142 | 
            +
             | 
| 1143 | 
            +
            window.hideGitDiffModal = function() {
         | 
| 1144 | 
            +
                const modal = document.getElementById('git-diff-modal');
         | 
| 1145 | 
            +
                if (modal) {
         | 
| 1146 | 
            +
                    modal.style.display = 'none';
         | 
| 1147 | 
            +
                    document.body.style.overflow = ''; // Restore background scrolling
         | 
| 1148 | 
            +
                }
         | 
| 1149 | 
            +
            };
         | 
| 1150 | 
            +
             | 
| 1151 | 
            +
            window.copyGitDiff = function() {
         | 
| 1152 | 
            +
                const modal = document.getElementById('git-diff-modal');
         | 
| 1153 | 
            +
                if (!modal) return;
         | 
| 1154 | 
            +
                
         | 
| 1155 | 
            +
                const codeElement = modal.querySelector('.git-diff-code');
         | 
| 1156 | 
            +
                if (!codeElement) return;
         | 
| 1157 | 
            +
                
         | 
| 1158 | 
            +
                const text = codeElement.textContent;
         | 
| 1159 | 
            +
                
         | 
| 1160 | 
            +
                if (navigator.clipboard && navigator.clipboard.writeText) {
         | 
| 1161 | 
            +
                    navigator.clipboard.writeText(text).then(() => {
         | 
| 1162 | 
            +
                        // Show brief feedback
         | 
| 1163 | 
            +
                        const button = modal.querySelector('.git-diff-copy');
         | 
| 1164 | 
            +
                        const originalText = button.textContent;
         | 
| 1165 | 
            +
                        button.textContent = '✅ Copied!';
         | 
| 1166 | 
            +
                        setTimeout(() => {
         | 
| 1167 | 
            +
                            button.textContent = originalText;
         | 
| 1168 | 
            +
                        }, 2000);
         | 
| 1169 | 
            +
                    }).catch(err => {
         | 
| 1170 | 
            +
                        console.error('Failed to copy text:', err);
         | 
| 1171 | 
            +
                    });
         | 
| 1172 | 
            +
                } else {
         | 
| 1173 | 
            +
                    // Fallback for older browsers
         | 
| 1174 | 
            +
                    const textarea = document.createElement('textarea');
         | 
| 1175 | 
            +
                    textarea.value = text;
         | 
| 1176 | 
            +
                    document.body.appendChild(textarea);
         | 
| 1177 | 
            +
                    textarea.select();
         | 
| 1178 | 
            +
                    document.execCommand('copy');
         | 
| 1179 | 
            +
                    document.body.removeChild(textarea);
         | 
| 1180 | 
            +
                    
         | 
| 1181 | 
            +
                    const button = modal.querySelector('.git-diff-copy');
         | 
| 1182 | 
            +
                    const originalText = button.textContent;
         | 
| 1183 | 
            +
                    button.textContent = '✅ Copied!';
         | 
| 1184 | 
            +
                    setTimeout(() => {
         | 
| 1185 | 
            +
                        button.textContent = originalText;
         | 
| 1186 | 
            +
                    }, 2000);
         | 
| 1187 | 
            +
                }
         | 
| 1188 | 
            +
            };
         | 
| 1189 | 
            +
             | 
| 1190 | 
            +
            function createGitDiffModal() {
         | 
| 1191 | 
            +
                const modal = document.createElement('div');
         | 
| 1192 | 
            +
                modal.id = 'git-diff-modal';
         | 
| 1193 | 
            +
                modal.className = 'modal git-diff-modal';
         | 
| 1194 | 
            +
                
         | 
| 1195 | 
            +
                modal.innerHTML = `
         | 
| 1196 | 
            +
                    <div class="modal-content git-diff-content">
         | 
| 1197 | 
            +
                        <div class="git-diff-header">
         | 
| 1198 | 
            +
                            <h2 class="git-diff-title">
         | 
| 1199 | 
            +
                                <span class="git-diff-icon">📋</span>
         | 
| 1200 | 
            +
                                <span class="git-diff-title-text">Git Diff</span>
         | 
| 1201 | 
            +
                            </h2>
         | 
| 1202 | 
            +
                            <div class="git-diff-meta">
         | 
| 1203 | 
            +
                                <span class="git-diff-file-path"></span>
         | 
| 1204 | 
            +
                                <span class="git-diff-timestamp"></span>
         | 
| 1205 | 
            +
                            </div>
         | 
| 1206 | 
            +
                            <button class="git-diff-close" onclick="hideGitDiffModal()">
         | 
| 1207 | 
            +
                                <span>×</span>
         | 
| 1208 | 
            +
                            </button>
         | 
| 1209 | 
            +
                        </div>
         | 
| 1210 | 
            +
                        <div class="git-diff-body">
         | 
| 1211 | 
            +
                            <div class="git-diff-loading">
         | 
| 1212 | 
            +
                                <div class="loading-spinner"></div>
         | 
| 1213 | 
            +
                                <span>Loading git diff...</span>
         | 
| 1214 | 
            +
                            </div>
         | 
| 1215 | 
            +
                            <div class="git-diff-error" style="display: none;">
         | 
| 1216 | 
            +
                                <div class="error-icon">⚠️</div>
         | 
| 1217 | 
            +
                                <div class="error-message"></div>
         | 
| 1218 | 
            +
                                <div class="error-suggestions"></div>
         | 
| 1219 | 
            +
                            </div>
         | 
| 1220 | 
            +
                            <div class="git-diff-content-area" style="display: none;">
         | 
| 1221 | 
            +
                                <div class="git-diff-toolbar">
         | 
| 1222 | 
            +
                                    <div class="git-diff-info">
         | 
| 1223 | 
            +
                                        <span class="commit-hash"></span>
         | 
| 1224 | 
            +
                                        <span class="diff-method"></span>
         | 
| 1225 | 
            +
                                    </div>
         | 
| 1226 | 
            +
                                    <div class="git-diff-actions">
         | 
| 1227 | 
            +
                                        <button class="git-diff-copy" onclick="copyGitDiff()">
         | 
| 1228 | 
            +
                                            📋 Copy
         | 
| 1229 | 
            +
                                        </button>
         | 
| 1230 | 
            +
                                    </div>
         | 
| 1231 | 
            +
                                </div>
         | 
| 1232 | 
            +
                                <div class="git-diff-scroll-wrapper">
         | 
| 1233 | 
            +
                                    <pre class="git-diff-display"><code class="git-diff-code"></code></pre>
         | 
| 1234 | 
            +
                                </div>
         | 
| 1235 | 
            +
                            </div>
         | 
| 1236 | 
            +
                        </div>
         | 
| 1237 | 
            +
                    </div>
         | 
| 1238 | 
            +
                `;
         | 
| 1239 | 
            +
                
         | 
| 1240 | 
            +
                // Close modal when clicking outside
         | 
| 1241 | 
            +
                modal.addEventListener('click', (e) => {
         | 
| 1242 | 
            +
                    if (e.target === modal) {
         | 
| 1243 | 
            +
                        hideGitDiffModal();
         | 
| 1244 | 
            +
                    }
         | 
| 1245 | 
            +
                });
         | 
| 1246 | 
            +
                
         | 
| 1247 | 
            +
                // Close modal with Escape key
         | 
| 1248 | 
            +
                document.addEventListener('keydown', (e) => {
         | 
| 1249 | 
            +
                    if (e.key === 'Escape' && modal.style.display === 'flex') {
         | 
| 1250 | 
            +
                        hideGitDiffModal();
         | 
| 1251 | 
            +
                    }
         | 
| 1252 | 
            +
                });
         | 
| 1253 | 
            +
                
         | 
| 1254 | 
            +
                return modal;
         | 
| 1255 | 
            +
            }
         | 
| 1256 | 
            +
             | 
| 1257 | 
            +
            async function updateGitDiffModal(modal, filePath, timestamp, workingDir) {
         | 
| 1258 | 
            +
                // Update header info
         | 
| 1259 | 
            +
                const filePathElement = modal.querySelector('.git-diff-file-path');
         | 
| 1260 | 
            +
                const timestampElement = modal.querySelector('.git-diff-timestamp');
         | 
| 1261 | 
            +
                
         | 
| 1262 | 
            +
                filePathElement.textContent = filePath;
         | 
| 1263 | 
            +
                timestampElement.textContent = timestamp ? new Date(timestamp).toLocaleString() : 'Latest';
         | 
| 1264 | 
            +
                
         | 
| 1265 | 
            +
                // Show loading state
         | 
| 1266 | 
            +
                modal.querySelector('.git-diff-loading').style.display = 'flex';
         | 
| 1267 | 
            +
                modal.querySelector('.git-diff-error').style.display = 'none';
         | 
| 1268 | 
            +
                modal.querySelector('.git-diff-content-area').style.display = 'none';
         | 
| 1269 | 
            +
                
         | 
| 1270 | 
            +
                try {
         | 
| 1271 | 
            +
                    // Get the Socket.IO server port with multiple fallbacks
         | 
| 1272 | 
            +
                    let port = 8765; // Default fallback
         | 
| 1273 | 
            +
                    
         | 
| 1274 | 
            +
                    // Try to get port from socketClient first
         | 
| 1275 | 
            +
                    if (window.dashboard && window.dashboard.socketClient && window.dashboard.socketClient.port) {
         | 
| 1276 | 
            +
                        port = window.dashboard.socketClient.port;
         | 
| 1277 | 
            +
                    }
         | 
| 1278 | 
            +
                    // Fallback to port input field if socketClient port is not available
         | 
| 1279 | 
            +
                    else {
         | 
| 1280 | 
            +
                        const portInput = document.getElementById('port-input');
         | 
| 1281 | 
            +
                        if (portInput && portInput.value) {
         | 
| 1282 | 
            +
                            port = portInput.value;
         | 
| 1283 | 
            +
                        }
         | 
| 1284 | 
            +
                    }
         | 
| 1285 | 
            +
                    
         | 
| 1286 | 
            +
                    // Build URL parameters
         | 
| 1287 | 
            +
                    const params = new URLSearchParams({
         | 
| 1288 | 
            +
                        file: filePath
         | 
| 1289 | 
            +
                    });
         | 
| 1290 | 
            +
                    
         | 
| 1291 | 
            +
                    if (timestamp) {
         | 
| 1292 | 
            +
                        params.append('timestamp', timestamp);
         | 
| 1293 | 
            +
                    }
         | 
| 1294 | 
            +
                    if (workingDir) {
         | 
| 1295 | 
            +
                        params.append('working_dir', workingDir);
         | 
| 1296 | 
            +
                    }
         | 
| 1297 | 
            +
                    
         | 
| 1298 | 
            +
                    const requestUrl = `http://localhost:${port}/api/git-diff?${params}`;
         | 
| 1299 | 
            +
                    console.log('🌐 Making git diff request to:', requestUrl);
         | 
| 1300 | 
            +
                    console.log('📋 Git diff request parameters:', {
         | 
| 1301 | 
            +
                        filePath,
         | 
| 1302 | 
            +
                        timestamp,
         | 
| 1303 | 
            +
                        workingDir,
         | 
| 1304 | 
            +
                        urlParams: params.toString()
         | 
| 1305 | 
            +
                    });
         | 
| 1306 | 
            +
                    
         | 
| 1307 | 
            +
                    // Test server connectivity first
         | 
| 1308 | 
            +
                    try {
         | 
| 1309 | 
            +
                        const healthResponse = await fetch(`http://localhost:${port}/health`, {
         | 
| 1310 | 
            +
                            method: 'GET',
         | 
| 1311 | 
            +
                            headers: {
         | 
| 1312 | 
            +
                                'Accept': 'application/json',
         | 
| 1313 | 
            +
                                'Content-Type': 'application/json'
         | 
| 1314 | 
            +
                            },
         | 
| 1315 | 
            +
                            mode: 'cors'
         | 
| 1316 | 
            +
                        });
         | 
| 1317 | 
            +
                        
         | 
| 1318 | 
            +
                        if (!healthResponse.ok) {
         | 
| 1319 | 
            +
                            throw new Error(`Server health check failed: ${healthResponse.status} ${healthResponse.statusText}`);
         | 
| 1320 | 
            +
                        }
         | 
| 1321 | 
            +
                        
         | 
| 1322 | 
            +
                        console.log('✅ Server health check passed');
         | 
| 1323 | 
            +
                    } catch (healthError) {
         | 
| 1324 | 
            +
                        throw new Error(`Cannot reach server at localhost:${port}. Health check failed: ${healthError.message}`);
         | 
| 1325 | 
            +
                    }
         | 
| 1326 | 
            +
                    
         | 
| 1327 | 
            +
                    // Make the actual git diff request
         | 
| 1328 | 
            +
                    const response = await fetch(requestUrl, {
         | 
| 1329 | 
            +
                        method: 'GET',
         | 
| 1330 | 
            +
                        headers: {
         | 
| 1331 | 
            +
                            'Accept': 'application/json',
         | 
| 1332 | 
            +
                            'Content-Type': 'application/json'
         | 
| 1333 | 
            +
                        },
         | 
| 1334 | 
            +
                        mode: 'cors'
         | 
| 1335 | 
            +
                    });
         | 
| 1336 | 
            +
                    
         | 
| 1337 | 
            +
                    if (!response.ok) {
         | 
| 1338 | 
            +
                        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
         | 
| 1339 | 
            +
                    }
         | 
| 1340 | 
            +
                    
         | 
| 1341 | 
            +
                    const result = await response.json();
         | 
| 1342 | 
            +
                    console.log('📦 Git diff response:', result);
         | 
| 1343 | 
            +
                    
         | 
| 1344 | 
            +
                    // Hide loading
         | 
| 1345 | 
            +
                    modal.querySelector('.git-diff-loading').style.display = 'none';
         | 
| 1346 | 
            +
                    
         | 
| 1347 | 
            +
                    if (result.success) {
         | 
| 1348 | 
            +
                        console.log('📊 Displaying successful git diff');
         | 
| 1349 | 
            +
                        // Show successful diff
         | 
| 1350 | 
            +
                        displayGitDiff(modal, result);
         | 
| 1351 | 
            +
                    } else {
         | 
| 1352 | 
            +
                        console.log('⚠️ Displaying git diff error:', result);
         | 
| 1353 | 
            +
                        // Show error
         | 
| 1354 | 
            +
                        displayGitDiffError(modal, result);
         | 
| 1355 | 
            +
                    }
         | 
| 1356 | 
            +
                    
         | 
| 1357 | 
            +
                } catch (error) {
         | 
| 1358 | 
            +
                    console.error('❌ Failed to fetch git diff:', error);
         | 
| 1359 | 
            +
                    console.error('Error details:', {
         | 
| 1360 | 
            +
                        name: error.name,
         | 
| 1361 | 
            +
                        message: error.message,
         | 
| 1362 | 
            +
                        stack: error.stack,
         | 
| 1363 | 
            +
                        filePath,
         | 
| 1364 | 
            +
                        timestamp,
         | 
| 1365 | 
            +
                        workingDir
         | 
| 1366 | 
            +
                    });
         | 
| 1367 | 
            +
                    
         | 
| 1368 | 
            +
                    modal.querySelector('.git-diff-loading').style.display = 'none';
         | 
| 1369 | 
            +
                    
         | 
| 1370 | 
            +
                    // Create detailed error message based on error type
         | 
| 1371 | 
            +
                    let errorMessage = `Network error: ${error.message}`;
         | 
| 1372 | 
            +
                    let suggestions = [];
         | 
| 1373 | 
            +
                    
         | 
| 1374 | 
            +
                    if (error.message.includes('Failed to fetch')) {
         | 
| 1375 | 
            +
                        errorMessage = 'Failed to connect to the monitoring server';
         | 
| 1376 | 
            +
                        suggestions = [
         | 
| 1377 | 
            +
                            'Check if the monitoring server is running on port 8765',
         | 
| 1378 | 
            +
                            'Verify the port configuration in the dashboard',
         | 
| 1379 | 
            +
                            'Check browser console for CORS or network errors',
         | 
| 1380 | 
            +
                            'Try refreshing the page and reconnecting'
         | 
| 1381 | 
            +
                        ];
         | 
| 1382 | 
            +
                    } else if (error.message.includes('health check failed')) {
         | 
| 1383 | 
            +
                        errorMessage = error.message;
         | 
| 1384 | 
            +
                        suggestions = [
         | 
| 1385 | 
            +
                            'The server may be starting up - try again in a few seconds',
         | 
| 1386 | 
            +
                            'Check if another process is using port 8765',
         | 
| 1387 | 
            +
                            'Restart the claude-mpm monitoring server'
         | 
| 1388 | 
            +
                        ];
         | 
| 1389 | 
            +
                    } else if (error.message.includes('HTTP')) {
         | 
| 1390 | 
            +
                        errorMessage = `Server error: ${error.message}`;
         | 
| 1391 | 
            +
                        suggestions = [
         | 
| 1392 | 
            +
                            'The server encountered an internal error',
         | 
| 1393 | 
            +
                            'Check the server logs for more details',
         | 
| 1394 | 
            +
                            'Try with a different file or working directory'
         | 
| 1395 | 
            +
                        ];
         | 
| 1396 | 
            +
                    }
         | 
| 1397 | 
            +
                    
         | 
| 1398 | 
            +
                    displayGitDiffError(modal, {
         | 
| 1399 | 
            +
                        error: errorMessage,
         | 
| 1400 | 
            +
                        file_path: filePath,
         | 
| 1401 | 
            +
                        working_dir: workingDir,
         | 
| 1402 | 
            +
                        suggestions: suggestions,
         | 
| 1403 | 
            +
                        debug_info: {
         | 
| 1404 | 
            +
                            error_type: error.name,
         | 
| 1405 | 
            +
                            original_message: error.message,
         | 
| 1406 | 
            +
                            port: window.dashboard?.socketClient?.port || document.getElementById('port-input')?.value || '8765',
         | 
| 1407 | 
            +
                            timestamp: new Date().toISOString()
         | 
| 1408 | 
            +
                        }
         | 
| 1409 | 
            +
                    });
         | 
| 1410 | 
            +
                }
         | 
| 1411 | 
            +
            }
         | 
| 1412 | 
            +
             | 
| 1413 | 
            +
            function highlightGitDiff(diffText) {
         | 
| 1414 | 
            +
                /**
         | 
| 1415 | 
            +
                 * Apply basic syntax highlighting to git diff output
         | 
| 1416 | 
            +
                 * WHY: Git diffs have a standard format that can be highlighted for better readability:
         | 
| 1417 | 
            +
                 * - Lines starting with '+' are additions (green)
         | 
| 1418 | 
            +
                 * - Lines starting with '-' are deletions (red)  
         | 
| 1419 | 
            +
                 * - Lines starting with '@@' are context headers (blue)
         | 
| 1420 | 
            +
                 * - File headers and metadata get special formatting
         | 
| 1421 | 
            +
                 */
         | 
| 1422 | 
            +
                return diffText
         | 
| 1423 | 
            +
                    .split('\n')
         | 
| 1424 | 
            +
                    .map(line => {
         | 
| 1425 | 
            +
                        // Escape HTML entities
         | 
| 1426 | 
            +
                        const escaped = line
         | 
| 1427 | 
            +
                            .replace(/&/g, '&')
         | 
| 1428 | 
            +
                            .replace(/</g, '<')
         | 
| 1429 | 
            +
                            .replace(/>/g, '>');
         | 
| 1430 | 
            +
                        
         | 
| 1431 | 
            +
                        // Apply diff highlighting
         | 
| 1432 | 
            +
                        if (line.startsWith('+++') || line.startsWith('---')) {
         | 
| 1433 | 
            +
                            return `<span class="diff-header">${escaped}</span>`;
         | 
| 1434 | 
            +
                        } else if (line.startsWith('@@')) {
         | 
| 1435 | 
            +
                            return `<span class="diff-meta">${escaped}</span>`;
         | 
| 1436 | 
            +
                        } else if (line.startsWith('+')) {
         | 
| 1437 | 
            +
                            return `<span class="diff-addition">${escaped}</span>`;
         | 
| 1438 | 
            +
                        } else if (line.startsWith('-')) {
         | 
| 1439 | 
            +
                            return `<span class="diff-deletion">${escaped}</span>`;
         | 
| 1440 | 
            +
                        } else if (line.startsWith('commit ') || line.startsWith('Author:') || line.startsWith('Date:')) {
         | 
| 1441 | 
            +
                            return `<span class="diff-header">${escaped}</span>`;
         | 
| 1442 | 
            +
                        } else {
         | 
| 1443 | 
            +
                            return `<span class="diff-context">${escaped}</span>`;
         | 
| 1444 | 
            +
                        }
         | 
| 1445 | 
            +
                    })
         | 
| 1446 | 
            +
                    .join('\n');
         | 
| 1447 | 
            +
            }
         | 
| 1448 | 
            +
             | 
| 1449 | 
            +
            function displayGitDiff(modal, result) {
         | 
| 1450 | 
            +
                console.log('📝 displayGitDiff called with:', result);
         | 
| 1451 | 
            +
                const contentArea = modal.querySelector('.git-diff-content-area');
         | 
| 1452 | 
            +
                const commitHashElement = modal.querySelector('.commit-hash');
         | 
| 1453 | 
            +
                const methodElement = modal.querySelector('.diff-method');
         | 
| 1454 | 
            +
                const codeElement = modal.querySelector('.git-diff-code');
         | 
| 1455 | 
            +
                
         | 
| 1456 | 
            +
                console.log('🔍 Elements found:', {
         | 
| 1457 | 
            +
                    contentArea: !!contentArea,
         | 
| 1458 | 
            +
                    commitHashElement: !!commitHashElement,
         | 
| 1459 | 
            +
                    methodElement: !!methodElement,
         | 
| 1460 | 
            +
                    codeElement: !!codeElement
         | 
| 1461 | 
            +
                });
         | 
| 1462 | 
            +
                
         | 
| 1463 | 
            +
                // Update metadata
         | 
| 1464 | 
            +
                if (commitHashElement) commitHashElement.textContent = `Commit: ${result.commit_hash}`;
         | 
| 1465 | 
            +
                if (methodElement) methodElement.textContent = `Method: ${result.method}`;
         | 
| 1466 | 
            +
                
         | 
| 1467 | 
            +
                // Update diff content with basic syntax highlighting
         | 
| 1468 | 
            +
                if (codeElement && result.diff) {
         | 
| 1469 | 
            +
                    console.log('💡 Setting diff content, length:', result.diff.length);
         | 
| 1470 | 
            +
                    codeElement.innerHTML = highlightGitDiff(result.diff);
         | 
| 1471 | 
            +
                    
         | 
| 1472 | 
            +
                    // Force scrolling to work by setting explicit heights
         | 
| 1473 | 
            +
                    const wrapper = modal.querySelector('.git-diff-scroll-wrapper');
         | 
| 1474 | 
            +
                    if (wrapper) {
         | 
| 1475 | 
            +
                        // Give it a moment for content to render
         | 
| 1476 | 
            +
                        setTimeout(() => {
         | 
| 1477 | 
            +
                            const modalContent = modal.querySelector('.modal-content');
         | 
| 1478 | 
            +
                            const header = modal.querySelector('.git-diff-header');
         | 
| 1479 | 
            +
                            const toolbar = modal.querySelector('.git-diff-toolbar');
         | 
| 1480 | 
            +
                            
         | 
| 1481 | 
            +
                            const modalHeight = modalContent?.offsetHeight || 0;
         | 
| 1482 | 
            +
                            const headerHeight = header?.offsetHeight || 0;
         | 
| 1483 | 
            +
                            const toolbarHeight = toolbar?.offsetHeight || 0;
         | 
| 1484 | 
            +
                            
         | 
| 1485 | 
            +
                            const availableHeight = modalHeight - headerHeight - toolbarHeight - 40; // 40px for padding
         | 
| 1486 | 
            +
                            
         | 
| 1487 | 
            +
                            console.log('🎯 Setting explicit scroll height:', {
         | 
| 1488 | 
            +
                                modalHeight,
         | 
| 1489 | 
            +
                                headerHeight,
         | 
| 1490 | 
            +
                                toolbarHeight,
         | 
| 1491 | 
            +
                                availableHeight
         | 
| 1492 | 
            +
                            });
         | 
| 1493 | 
            +
                            
         | 
| 1494 | 
            +
                            wrapper.style.maxHeight = `${availableHeight}px`;
         | 
| 1495 | 
            +
                            wrapper.style.overflowY = 'auto';
         | 
| 1496 | 
            +
                        }, 50);
         | 
| 1497 | 
            +
                    }
         | 
| 1498 | 
            +
                } else {
         | 
| 1499 | 
            +
                    console.warn('⚠️ Missing codeElement or diff data');
         | 
| 1500 | 
            +
                }
         | 
| 1501 | 
            +
                
         | 
| 1502 | 
            +
                // Show content area
         | 
| 1503 | 
            +
                if (contentArea) {
         | 
| 1504 | 
            +
                    contentArea.style.display = 'block';
         | 
| 1505 | 
            +
                    console.log('✅ Content area displayed');
         | 
| 1506 | 
            +
                }
         | 
| 1507 | 
            +
            }
         | 
| 1508 | 
            +
             | 
| 1509 | 
            +
            function displayGitDiffError(modal, result) {
         | 
| 1510 | 
            +
                const errorArea = modal.querySelector('.git-diff-error');
         | 
| 1511 | 
            +
                const messageElement = modal.querySelector('.error-message');
         | 
| 1512 | 
            +
                const suggestionsElement = modal.querySelector('.error-suggestions');
         | 
| 1513 | 
            +
                
         | 
| 1514 | 
            +
                // Create more user-friendly error messages
         | 
| 1515 | 
            +
                let errorMessage = result.error || 'Unknown error occurred';
         | 
| 1516 | 
            +
                let isUntracked = false;
         | 
| 1517 | 
            +
                
         | 
| 1518 | 
            +
                if (errorMessage.includes('not tracked by git')) {
         | 
| 1519 | 
            +
                    errorMessage = '📝 This file is not tracked by git yet';
         | 
| 1520 | 
            +
                    isUntracked = true;
         | 
| 1521 | 
            +
                } else if (errorMessage.includes('No git history found')) {
         | 
| 1522 | 
            +
                    errorMessage = '📋 No git history available for this file';
         | 
| 1523 | 
            +
                }
         | 
| 1524 | 
            +
                
         | 
| 1525 | 
            +
                messageElement.innerHTML = `
         | 
| 1526 | 
            +
                    <div class="error-main">${errorMessage}</div>
         | 
| 1527 | 
            +
                    ${result.file_path ? `<div class="error-file">File: ${result.file_path}</div>` : ''}
         | 
| 1528 | 
            +
                    ${result.working_dir ? `<div class="error-dir">Working directory: ${result.working_dir}</div>` : ''}
         | 
| 1529 | 
            +
                `;
         | 
| 1530 | 
            +
                
         | 
| 1531 | 
            +
                if (result.suggestions && result.suggestions.length > 0) {
         | 
| 1532 | 
            +
                    const suggestionTitle = isUntracked ? 'How to track this file:' : 'Suggestions:';
         | 
| 1533 | 
            +
                    suggestionsElement.innerHTML = `
         | 
| 1534 | 
            +
                        <h4>${suggestionTitle}</h4>
         | 
| 1535 | 
            +
                        <ul>
         | 
| 1536 | 
            +
                            ${result.suggestions.map(s => `<li>${s}</li>`).join('')}
         | 
| 1537 | 
            +
                        </ul>
         | 
| 1538 | 
            +
                    `;
         | 
| 1539 | 
            +
                } else {
         | 
| 1540 | 
            +
                    suggestionsElement.innerHTML = '';
         | 
| 1541 | 
            +
                }
         | 
| 1542 | 
            +
                
         | 
| 1543 | 
            +
                console.log('📋 Displaying git diff error:', {
         | 
| 1544 | 
            +
                    originalError: result.error,
         | 
| 1545 | 
            +
                    processedMessage: errorMessage,
         | 
| 1546 | 
            +
                    isUntracked,
         | 
| 1547 | 
            +
                    suggestions: result.suggestions
         | 
| 1548 | 
            +
                });
         | 
| 1549 | 
            +
                
         | 
| 1550 | 
            +
                errorArea.style.display = 'block';
         | 
| 1551 | 
            +
            }
         | 
| 1552 | 
            +
             | 
| 1553 | 
            +
            // File Viewer Modal Functions
         | 
| 1554 | 
            +
            window.showFileViewerModal = function(filePath) {
         | 
| 1555 | 
            +
                // Use the dashboard's current working directory
         | 
| 1556 | 
            +
                let workingDir = '';
         | 
| 1557 | 
            +
                if (window.dashboard && window.dashboard.currentWorkingDir) {
         | 
| 1558 | 
            +
                    workingDir = window.dashboard.currentWorkingDir;
         | 
| 1559 | 
            +
                }
         | 
| 1560 | 
            +
                
         | 
| 1561 | 
            +
                // Create modal if it doesn't exist
         | 
| 1562 | 
            +
                let modal = document.getElementById('file-viewer-modal');
         | 
| 1563 | 
            +
                if (!modal) {
         | 
| 1564 | 
            +
                    modal = createFileViewerModal();
         | 
| 1565 | 
            +
                    document.body.appendChild(modal);
         | 
| 1566 | 
            +
                }
         | 
| 1567 | 
            +
                
         | 
| 1568 | 
            +
                // Update modal content
         | 
| 1569 | 
            +
                updateFileViewerModal(modal, filePath, workingDir);
         | 
| 1570 | 
            +
                
         | 
| 1571 | 
            +
                // Show the modal as flex container
         | 
| 1572 | 
            +
                modal.style.display = 'flex';
         | 
| 1573 | 
            +
                document.body.style.overflow = 'hidden'; // Prevent background scrolling
         | 
| 1574 | 
            +
            };
         | 
| 1575 | 
            +
             | 
| 1576 | 
            +
            window.hideFileViewerModal = function() {
         | 
| 1577 | 
            +
                const modal = document.getElementById('file-viewer-modal');
         | 
| 1578 | 
            +
                if (modal) {
         | 
| 1579 | 
            +
                    modal.style.display = 'none';
         | 
| 1580 | 
            +
                    document.body.style.overflow = ''; // Restore background scrolling
         | 
| 1581 | 
            +
                }
         | 
| 1582 | 
            +
            };
         | 
| 1583 | 
            +
             | 
| 1584 | 
            +
            window.copyFileContent = function() {
         | 
| 1585 | 
            +
                const modal = document.getElementById('file-viewer-modal');
         | 
| 1586 | 
            +
                if (!modal) return;
         | 
| 1587 | 
            +
                
         | 
| 1588 | 
            +
                const codeElement = modal.querySelector('.file-content-code');
         | 
| 1589 | 
            +
                if (!codeElement) return;
         | 
| 1590 | 
            +
                
         | 
| 1591 | 
            +
                const text = codeElement.textContent;
         | 
| 1592 | 
            +
                
         | 
| 1593 | 
            +
                if (navigator.clipboard && navigator.clipboard.writeText) {
         | 
| 1594 | 
            +
                    navigator.clipboard.writeText(text).then(() => {
         | 
| 1595 | 
            +
                        // Show brief feedback
         | 
| 1596 | 
            +
                        const button = modal.querySelector('.file-content-copy');
         | 
| 1597 | 
            +
                        const originalText = button.textContent;
         | 
| 1598 | 
            +
                        button.textContent = '✅ Copied!';
         | 
| 1599 | 
            +
                        setTimeout(() => {
         | 
| 1600 | 
            +
                            button.textContent = originalText;
         | 
| 1601 | 
            +
                        }, 2000);
         | 
| 1602 | 
            +
                    }).catch(err => {
         | 
| 1603 | 
            +
                        console.error('Failed to copy text:', err);
         | 
| 1604 | 
            +
                    });
         | 
| 1605 | 
            +
                } else {
         | 
| 1606 | 
            +
                    // Fallback for older browsers
         | 
| 1607 | 
            +
                    const textarea = document.createElement('textarea');
         | 
| 1608 | 
            +
                    textarea.value = text;
         | 
| 1609 | 
            +
                    document.body.appendChild(textarea);
         | 
| 1610 | 
            +
                    textarea.select();
         | 
| 1611 | 
            +
                    document.execCommand('copy');
         | 
| 1612 | 
            +
                    document.body.removeChild(textarea);
         | 
| 1613 | 
            +
                    
         | 
| 1614 | 
            +
                    const button = modal.querySelector('.file-content-copy');
         | 
| 1615 | 
            +
                    const originalText = button.textContent;
         | 
| 1616 | 
            +
                    button.textContent = '✅ Copied!';
         | 
| 1617 | 
            +
                    setTimeout(() => {
         | 
| 1618 | 
            +
                        button.textContent = originalText;
         | 
| 1619 | 
            +
                    }, 2000);
         | 
| 1620 | 
            +
                }
         | 
| 1621 | 
            +
            };
         | 
| 1622 | 
            +
             | 
| 1623 | 
            +
            function createFileViewerModal() {
         | 
| 1624 | 
            +
                const modal = document.createElement('div');
         | 
| 1625 | 
            +
                modal.id = 'file-viewer-modal';
         | 
| 1626 | 
            +
                modal.className = 'modal file-viewer-modal';
         | 
| 1627 | 
            +
                
         | 
| 1628 | 
            +
                modal.innerHTML = `
         | 
| 1629 | 
            +
                    <div class="modal-content file-viewer-content">
         | 
| 1630 | 
            +
                        <div class="file-viewer-header">
         | 
| 1631 | 
            +
                            <h2 class="file-viewer-title">
         | 
| 1632 | 
            +
                                <span class="file-viewer-icon">👁️</span>
         | 
| 1633 | 
            +
                                <span class="file-viewer-title-text">File Viewer</span>
         | 
| 1634 | 
            +
                            </h2>
         | 
| 1635 | 
            +
                            <div class="file-viewer-meta">
         | 
| 1636 | 
            +
                                <span class="file-viewer-file-path"></span>
         | 
| 1637 | 
            +
                                <span class="file-viewer-file-size"></span>
         | 
| 1638 | 
            +
                            </div>
         | 
| 1639 | 
            +
                            <button class="file-viewer-close" onclick="hideFileViewerModal()">
         | 
| 1640 | 
            +
                                <span>×</span>
         | 
| 1641 | 
            +
                            </button>
         | 
| 1642 | 
            +
                        </div>
         | 
| 1643 | 
            +
                        <div class="file-viewer-body">
         | 
| 1644 | 
            +
                            <div class="file-viewer-loading">
         | 
| 1645 | 
            +
                                <div class="loading-spinner"></div>
         | 
| 1646 | 
            +
                                <span>Loading file contents...</span>
         | 
| 1647 | 
            +
                            </div>
         | 
| 1648 | 
            +
                            <div class="file-viewer-error" style="display: none;">
         | 
| 1649 | 
            +
                                <div class="error-icon">⚠️</div>
         | 
| 1650 | 
            +
                                <div class="error-message"></div>
         | 
| 1651 | 
            +
                                <div class="error-suggestions"></div>
         | 
| 1652 | 
            +
                            </div>
         | 
| 1653 | 
            +
                            <div class="file-viewer-content-area" style="display: none;">
         | 
| 1654 | 
            +
                                <div class="file-viewer-toolbar">
         | 
| 1655 | 
            +
                                    <div class="file-viewer-info">
         | 
| 1656 | 
            +
                                        <span class="file-extension"></span>
         | 
| 1657 | 
            +
                                        <span class="file-encoding">UTF-8</span>
         | 
| 1658 | 
            +
                                    </div>
         | 
| 1659 | 
            +
                                    <div class="file-viewer-actions">
         | 
| 1660 | 
            +
                                        <button class="file-content-copy" onclick="copyFileContent()">
         | 
| 1661 | 
            +
                                            📋 Copy
         | 
| 1662 | 
            +
                                        </button>
         | 
| 1663 | 
            +
                                    </div>
         | 
| 1664 | 
            +
                                </div>
         | 
| 1665 | 
            +
                                <div class="file-viewer-scroll-wrapper">
         | 
| 1666 | 
            +
                                    <pre class="file-content-display line-numbers"><code class="file-content-code"></code></pre>
         | 
| 1667 | 
            +
                                </div>
         | 
| 1668 | 
            +
                            </div>
         | 
| 1669 | 
            +
                        </div>
         | 
| 1670 | 
            +
                    </div>
         | 
| 1671 | 
            +
                `;
         | 
| 1672 | 
            +
                
         | 
| 1673 | 
            +
                // Close modal when clicking outside
         | 
| 1674 | 
            +
                modal.addEventListener('click', (e) => {
         | 
| 1675 | 
            +
                    if (e.target === modal) {
         | 
| 1676 | 
            +
                        hideFileViewerModal();
         | 
| 1677 | 
            +
                    }
         | 
| 1678 | 
            +
                });
         | 
| 1679 | 
            +
                
         | 
| 1680 | 
            +
                // Close modal with Escape key
         | 
| 1681 | 
            +
                document.addEventListener('keydown', (e) => {
         | 
| 1682 | 
            +
                    if (e.key === 'Escape' && modal.style.display === 'flex') {
         | 
| 1683 | 
            +
                        hideFileViewerModal();
         | 
| 1684 | 
            +
                    }
         | 
| 1685 | 
            +
                });
         | 
| 1686 | 
            +
                
         | 
| 1687 | 
            +
                return modal;
         | 
| 1688 | 
            +
            }
         | 
| 1689 | 
            +
             | 
| 1690 | 
            +
            async function updateFileViewerModal(modal, filePath, workingDir) {
         | 
| 1691 | 
            +
                // Update header info
         | 
| 1692 | 
            +
                const filePathElement = modal.querySelector('.file-viewer-file-path');
         | 
| 1693 | 
            +
                const fileSizeElement = modal.querySelector('.file-viewer-file-size');
         | 
| 1694 | 
            +
                const fileExtensionElement = modal.querySelector('.file-extension');
         | 
| 1695 | 
            +
                
         | 
| 1696 | 
            +
                filePathElement.textContent = filePath;
         | 
| 1697 | 
            +
                
         | 
| 1698 | 
            +
                // Extract and display file extension
         | 
| 1699 | 
            +
                const extension = filePath.split('.').pop() || 'txt';
         | 
| 1700 | 
            +
                fileExtensionElement.textContent = `Type: ${extension.toUpperCase()}`;
         | 
| 1701 | 
            +
                
         | 
| 1702 | 
            +
                // Show loading state
         | 
| 1703 | 
            +
                modal.querySelector('.file-viewer-loading').style.display = 'flex';
         | 
| 1704 | 
            +
                modal.querySelector('.file-viewer-error').style.display = 'none';
         | 
| 1705 | 
            +
                modal.querySelector('.file-viewer-content-area').style.display = 'none';
         | 
| 1706 | 
            +
                
         | 
| 1707 | 
            +
                try {
         | 
| 1708 | 
            +
                    // Get the Socket.IO server port with multiple fallbacks
         | 
| 1709 | 
            +
                    let port = 8765; // Default fallback
         | 
| 1710 | 
            +
                    
         | 
| 1711 | 
            +
                    // Try to get port from socketClient first
         | 
| 1712 | 
            +
                    if (window.dashboard && window.dashboard.socketClient && window.dashboard.socketClient.port) {
         | 
| 1713 | 
            +
                        port = window.dashboard.socketClient.port;
         | 
| 1714 | 
            +
                    }
         | 
| 1715 | 
            +
                    // Fallback to port input field if socketClient port is not available
         | 
| 1716 | 
            +
                    else if (document.getElementById('port-input')) {
         | 
| 1717 | 
            +
                        const portInput = document.getElementById('port-input');
         | 
| 1718 | 
            +
                        if (portInput.value && !isNaN(portInput.value)) {
         | 
| 1719 | 
            +
                            port = parseInt(portInput.value);
         | 
| 1720 | 
            +
                        }
         | 
| 1721 | 
            +
                    }
         | 
| 1722 | 
            +
                    
         | 
| 1723 | 
            +
                    // Construct API request URL for file content
         | 
| 1724 | 
            +
                    const params = new URLSearchParams();
         | 
| 1725 | 
            +
                    params.append('file_path', filePath);
         | 
| 1726 | 
            +
                    if (workingDir) {
         | 
| 1727 | 
            +
                        params.append('working_dir', workingDir);
         | 
| 1728 | 
            +
                    }
         | 
| 1729 | 
            +
                    
         | 
| 1730 | 
            +
                    const requestUrl = `http://localhost:${port}/api/file-content?${params}`;
         | 
| 1731 | 
            +
                    console.log('🌐 Making file content request to:', requestUrl);
         | 
| 1732 | 
            +
                    console.log('📄 File content request parameters:', {
         | 
| 1733 | 
            +
                        filePath,
         | 
| 1734 | 
            +
                        workingDir,
         | 
| 1735 | 
            +
                        urlParams: params.toString()
         | 
| 1736 | 
            +
                    });
         | 
| 1737 | 
            +
                    
         | 
| 1738 | 
            +
                    // Test server connectivity first
         | 
| 1739 | 
            +
                    try {
         | 
| 1740 | 
            +
                        const healthResponse = await fetch(`http://localhost:${port}/health`, {
         | 
| 1741 | 
            +
                            method: 'GET',
         | 
| 1742 | 
            +
                            mode: 'cors',
         | 
| 1743 | 
            +
                            timeout: 5000
         | 
| 1744 | 
            +
                        });
         | 
| 1745 | 
            +
                        
         | 
| 1746 | 
            +
                        if (!healthResponse.ok) {
         | 
| 1747 | 
            +
                            throw new Error(`Health check failed: ${healthResponse.status}`);
         | 
| 1748 | 
            +
                        }
         | 
| 1749 | 
            +
                        
         | 
| 1750 | 
            +
                        console.log('✅ Server health check passed');
         | 
| 1751 | 
            +
                    } catch (healthError) {
         | 
| 1752 | 
            +
                        console.warn('⚠️ Server health check failed:', healthError);
         | 
| 1753 | 
            +
                        throw new Error(`Unable to connect to monitoring server on port ${port}. Please ensure the server is running.`);
         | 
| 1754 | 
            +
                    }
         | 
| 1755 | 
            +
                    
         | 
| 1756 | 
            +
                    // Make the actual file content request
         | 
| 1757 | 
            +
                    const response = await fetch(requestUrl, {
         | 
| 1758 | 
            +
                        method: 'GET',
         | 
| 1759 | 
            +
                        headers: {
         | 
| 1760 | 
            +
                            'Accept': 'application/json',
         | 
| 1761 | 
            +
                            'Content-Type': 'application/json'
         | 
| 1762 | 
            +
                        },
         | 
| 1763 | 
            +
                        mode: 'cors'
         | 
| 1764 | 
            +
                    });
         | 
| 1765 | 
            +
                    
         | 
| 1766 | 
            +
                    if (!response.ok) {
         | 
| 1767 | 
            +
                        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
         | 
| 1768 | 
            +
                    }
         | 
| 1769 | 
            +
                    
         | 
| 1770 | 
            +
                    const result = await response.json();
         | 
| 1771 | 
            +
                    console.log('📦 File content received:', result);
         | 
| 1772 | 
            +
                    
         | 
| 1773 | 
            +
                    // Hide loading
         | 
| 1774 | 
            +
                    modal.querySelector('.file-viewer-loading').style.display = 'none';
         | 
| 1775 | 
            +
                    
         | 
| 1776 | 
            +
                    if (result.success) {
         | 
| 1777 | 
            +
                        console.log('📊 Displaying file content');
         | 
| 1778 | 
            +
                        // Show file content
         | 
| 1779 | 
            +
                        displayFileContent(modal, result, extension);
         | 
| 1780 | 
            +
                    } else {
         | 
| 1781 | 
            +
                        console.log('⚠️ Displaying file content error:', result);
         | 
| 1782 | 
            +
                        // Show error
         | 
| 1783 | 
            +
                        displayFileContentError(modal, result);
         | 
| 1784 | 
            +
                    }
         | 
| 1785 | 
            +
                    
         | 
| 1786 | 
            +
                } catch (error) {
         | 
| 1787 | 
            +
                    console.error('❌ Failed to fetch file content:', error);
         | 
| 1788 | 
            +
                    
         | 
| 1789 | 
            +
                    modal.querySelector('.file-viewer-loading').style.display = 'none';
         | 
| 1790 | 
            +
                    
         | 
| 1791 | 
            +
                    // Create detailed error message based on error type
         | 
| 1792 | 
            +
                    let errorMessage = `Network error: ${error.message}`;
         | 
| 1793 | 
            +
                    let suggestions = [];
         | 
| 1794 | 
            +
                    
         | 
| 1795 | 
            +
                    if (error.message.includes('Failed to fetch')) {
         | 
| 1796 | 
            +
                        errorMessage = 'Failed to connect to the monitoring server';
         | 
| 1797 | 
            +
                        suggestions = [
         | 
| 1798 | 
            +
                            'Check if the monitoring server is running on port 8765',
         | 
| 1799 | 
            +
                            'Verify the port configuration in the dashboard',
         | 
| 1800 | 
            +
                            'Ensure CORS is enabled on the server'
         | 
| 1801 | 
            +
                        ];
         | 
| 1802 | 
            +
                    } else if (error.message.includes('Health check failed')) {
         | 
| 1803 | 
            +
                        errorMessage = 'Unable to connect to monitoring server';
         | 
| 1804 | 
            +
                        suggestions = [
         | 
| 1805 | 
            +
                            'The server may be starting up - try again in a few seconds',
         | 
| 1806 | 
            +
                            'Check if another process is using the port',
         | 
| 1807 | 
            +
                            'Restart the claude-mpm monitoring server'
         | 
| 1808 | 
            +
                        ];
         | 
| 1809 | 
            +
                    } else if (error.message.includes('HTTP 404')) {
         | 
| 1810 | 
            +
                        errorMessage = 'File content API endpoint not found';
         | 
| 1811 | 
            +
                        suggestions = [
         | 
| 1812 | 
            +
                            'The server may be running an older version',
         | 
| 1813 | 
            +
                            'Try restarting the monitoring server',
         | 
| 1814 | 
            +
                            'Check server logs for errors'
         | 
| 1815 | 
            +
                        ];
         | 
| 1816 | 
            +
                    } else if (error.message.includes('timeout')) {
         | 
| 1817 | 
            +
                        errorMessage = 'Request timed out while fetching file content';
         | 
| 1818 | 
            +
                        suggestions = [
         | 
| 1819 | 
            +
                            'File may be too large or server is slow',
         | 
| 1820 | 
            +
                            'Try again in a moment',
         | 
| 1821 | 
            +
                            'Check server logs for performance issues'
         | 
| 1822 | 
            +
                        ];
         | 
| 1823 | 
            +
                    }
         | 
| 1824 | 
            +
                    
         | 
| 1825 | 
            +
                    displayFileContentError(modal, {
         | 
| 1826 | 
            +
                        success: false,
         | 
| 1827 | 
            +
                        error: errorMessage,
         | 
| 1828 | 
            +
                        suggestions: suggestions
         | 
| 1829 | 
            +
                    });
         | 
| 1830 | 
            +
                }
         | 
| 1831 | 
            +
            }
         | 
| 1832 | 
            +
             | 
| 1833 | 
            +
            function displayFileContent(modal, result, extension) {
         | 
| 1834 | 
            +
                console.log('📝 displayFileContent called with:', result);
         | 
| 1835 | 
            +
                const contentArea = modal.querySelector('.file-viewer-content-area');
         | 
| 1836 | 
            +
                const fileSizeElement = modal.querySelector('.file-viewer-file-size');
         | 
| 1837 | 
            +
                const codeElement = modal.querySelector('.file-content-code');
         | 
| 1838 | 
            +
                
         | 
| 1839 | 
            +
                console.log('🔍 File content elements found:', {
         | 
| 1840 | 
            +
                    contentArea: !!contentArea,
         | 
| 1841 | 
            +
                    fileSizeElement: !!fileSizeElement,
         | 
| 1842 | 
            +
                    codeElement: !!codeElement
         | 
| 1843 | 
            +
                });
         | 
| 1844 | 
            +
                
         | 
| 1845 | 
            +
                // Update file size
         | 
| 1846 | 
            +
                if (fileSizeElement && result.file_size) {
         | 
| 1847 | 
            +
                    const sizeInBytes = result.file_size;
         | 
| 1848 | 
            +
                    const sizeFormatted = sizeInBytes < 1024 
         | 
| 1849 | 
            +
                        ? `${sizeInBytes} bytes`
         | 
| 1850 | 
            +
                        : sizeInBytes < 1024 * 1024
         | 
| 1851 | 
            +
                        ? `${(sizeInBytes / 1024).toFixed(1)} KB`
         | 
| 1852 | 
            +
                        : `${(sizeInBytes / (1024 * 1024)).toFixed(1)} MB`;
         | 
| 1853 | 
            +
                    fileSizeElement.textContent = `Size: ${sizeFormatted}`;
         | 
| 1854 | 
            +
                }
         | 
| 1855 | 
            +
                
         | 
| 1856 | 
            +
                // Set up content with syntax highlighting
         | 
| 1857 | 
            +
                if (codeElement && result.content) {
         | 
| 1858 | 
            +
                    // Determine language for Prism.js
         | 
| 1859 | 
            +
                    const languageMap = {
         | 
| 1860 | 
            +
                        'js': 'javascript',
         | 
| 1861 | 
            +
                        'jsx': 'javascript',
         | 
| 1862 | 
            +
                        'ts': 'typescript',
         | 
| 1863 | 
            +
                        'tsx': 'typescript',
         | 
| 1864 | 
            +
                        'py': 'python',
         | 
| 1865 | 
            +
                        'rb': 'ruby',
         | 
| 1866 | 
            +
                        'php': 'php',
         | 
| 1867 | 
            +
                        'html': 'html',
         | 
| 1868 | 
            +
                        'htm': 'html',
         | 
| 1869 | 
            +
                        'css': 'css',
         | 
| 1870 | 
            +
                        'scss': 'scss',
         | 
| 1871 | 
            +
                        'sass': 'sass',
         | 
| 1872 | 
            +
                        'json': 'json',
         | 
| 1873 | 
            +
                        'xml': 'xml',
         | 
| 1874 | 
            +
                        'yaml': 'yaml',
         | 
| 1875 | 
            +
                        'yml': 'yaml',
         | 
| 1876 | 
            +
                        'md': 'markdown',
         | 
| 1877 | 
            +
                        'sh': 'bash',
         | 
| 1878 | 
            +
                        'bash': 'bash',
         | 
| 1879 | 
            +
                        'zsh': 'bash',
         | 
| 1880 | 
            +
                        'sql': 'sql',
         | 
| 1881 | 
            +
                        'go': 'go',
         | 
| 1882 | 
            +
                        'java': 'java',
         | 
| 1883 | 
            +
                        'c': 'c',
         | 
| 1884 | 
            +
                        'cpp': 'cpp',
         | 
| 1885 | 
            +
                        'h': 'c',
         | 
| 1886 | 
            +
                        'hpp': 'cpp'
         | 
| 1887 | 
            +
                    };
         | 
| 1888 | 
            +
                    
         | 
| 1889 | 
            +
                    const language = languageMap[extension.toLowerCase()] || 'text';
         | 
| 1890 | 
            +
                    
         | 
| 1891 | 
            +
                    // Set up code element with Prism.js classes
         | 
| 1892 | 
            +
                    codeElement.className = `file-content-code language-${language}`;
         | 
| 1893 | 
            +
                    codeElement.textContent = result.content;
         | 
| 1894 | 
            +
                    
         | 
| 1895 | 
            +
                    // Apply syntax highlighting with Prism.js if available
         | 
| 1896 | 
            +
                    if (window.Prism) {
         | 
| 1897 | 
            +
                        // Add line numbers
         | 
| 1898 | 
            +
                        codeElement.parentElement.className = 'file-content-display line-numbers';
         | 
| 1899 | 
            +
                        
         | 
| 1900 | 
            +
                        // Apply highlighting
         | 
| 1901 | 
            +
                        window.Prism.highlightElement(codeElement);
         | 
| 1902 | 
            +
                    }
         | 
| 1903 | 
            +
                    
         | 
| 1904 | 
            +
                    contentArea.style.display = 'flex';
         | 
| 1905 | 
            +
                    
         | 
| 1906 | 
            +
                    console.log('✅ File content displayed successfully');
         | 
| 1907 | 
            +
                } else {
         | 
| 1908 | 
            +
                    console.error('❌ Missing code element or content');
         | 
| 1909 | 
            +
                }
         | 
| 1910 | 
            +
            }
         | 
| 1911 | 
            +
             | 
| 1912 | 
            +
            function displayFileContentError(modal, result) {
         | 
| 1913 | 
            +
                const errorArea = modal.querySelector('.file-viewer-error');
         | 
| 1914 | 
            +
                const messageElement = modal.querySelector('.error-message');
         | 
| 1915 | 
            +
                const suggestionsElement = modal.querySelector('.error-suggestions');
         | 
| 1916 | 
            +
                
         | 
| 1917 | 
            +
                // Create user-friendly error messages
         | 
| 1918 | 
            +
                let errorMessage = result.error || 'Unknown error occurred';
         | 
| 1919 | 
            +
                
         | 
| 1920 | 
            +
                if (errorMessage.includes('not found')) {
         | 
| 1921 | 
            +
                    errorMessage = '📁 File not found or not accessible';
         | 
| 1922 | 
            +
                } else if (errorMessage.includes('permission')) {
         | 
| 1923 | 
            +
                    errorMessage = '🔒 Permission denied accessing this file';
         | 
| 1924 | 
            +
                } else if (errorMessage.includes('too large')) {
         | 
| 1925 | 
            +
                    errorMessage = '📏 File is too large to display';
         | 
| 1926 | 
            +
                } else if (!errorMessage.includes('📁') && !errorMessage.includes('🔒') && !errorMessage.includes('📏')) {
         | 
| 1927 | 
            +
                    errorMessage = `⚠️ ${errorMessage}`;
         | 
| 1928 | 
            +
                }
         | 
| 1929 | 
            +
                
         | 
| 1930 | 
            +
                messageElement.textContent = errorMessage;
         | 
| 1931 | 
            +
                
         | 
| 1932 | 
            +
                // Add suggestions if available
         | 
| 1933 | 
            +
                if (result.suggestions && result.suggestions.length > 0) {
         | 
| 1934 | 
            +
                    suggestionsElement.innerHTML = `
         | 
| 1935 | 
            +
                        <h4>Suggestions:</h4>
         | 
| 1936 | 
            +
                        <ul>
         | 
| 1937 | 
            +
                            ${result.suggestions.map(suggestion => `<li>${suggestion}</li>`).join('')}
         | 
| 1938 | 
            +
                        </ul>
         | 
| 1939 | 
            +
                    `;
         | 
| 1940 | 
            +
                } else {
         | 
| 1941 | 
            +
                    suggestionsElement.innerHTML = `
         | 
| 1942 | 
            +
                        <h4>Try:</h4>
         | 
| 1943 | 
            +
                        <ul>
         | 
| 1944 | 
            +
                            <li>Check if the file exists and is readable</li>
         | 
| 1945 | 
            +
                            <li>Verify file permissions</li>
         | 
| 1946 | 
            +
                            <li>Ensure the monitoring server has access to this file</li>
         | 
| 1947 | 
            +
                        </ul>
         | 
| 1948 | 
            +
                    `;
         | 
| 1949 | 
            +
                }
         | 
| 1950 | 
            +
                
         | 
| 1951 | 
            +
                console.log('📋 Displaying file content error:', {
         | 
| 1952 | 
            +
                    originalError: result.error,
         | 
| 1953 | 
            +
                    processedMessage: errorMessage,
         | 
| 1954 | 
            +
                    suggestions: result.suggestions
         | 
| 1955 | 
            +
                });
         | 
| 1956 | 
            +
                
         | 
| 1957 | 
            +
                errorArea.style.display = 'block';
         | 
| 1958 | 
            +
            }
         | 
| 1959 | 
            +
             | 
| 1960 | 
            +
            // Global window functions for backward compatibility
         | 
| 1961 | 
            +
            window.showAgentInstanceDetails = function(instanceId) {
         | 
| 1962 | 
            +
                if (window.dashboard && typeof window.dashboard.showAgentInstanceDetails === 'function') {
         | 
| 1963 | 
            +
                    window.dashboard.showAgentInstanceDetails(instanceId);
         | 
| 1964 | 
            +
                } else {
         | 
| 1965 | 
            +
                    console.error('Dashboard not available or method not found');
         | 
| 1966 | 
            +
                }
         | 
| 1967 | 
            +
            };
         | 
| 1968 | 
            +
             | 
| 1969 | 
            +
            // Initialize dashboard when page loads
         | 
| 1970 | 
            +
            document.addEventListener('DOMContentLoaded', function() {
         | 
| 1971 | 
            +
                window.dashboard = new Dashboard();
         | 
| 1972 | 
            +
                console.log('Dashboard loaded and initialized');
         | 
| 1973 | 
            +
            });
         |