From a7d2f3721971e3d2147e1f9aaff315b5882c4090 Mon Sep 17 00:00:00 2001 From: uroma Date: Mon, 19 Jan 2026 19:12:18 +0000 Subject: [PATCH] debug: add visual debug panel and comprehensive logging - Added debug panel in terminal view that shows all terminal activity - Added debugLog() method to TerminalManager for consistent logging - Updated connectTerminal, handleTerminalMessage, launchCommand, createTerminal, initializeXTerm with detailed logging - Enhanced backend logging for WebSocket messages and close codes - Logs now show both to console and visual debug panel This should help diagnose the terminal command execution issue without requiring browser console access. --- database.sqlite | Bin 24576 -> 24576 bytes database.sqlite-shm | Bin 32768 -> 32768 bytes database.sqlite-wal | Bin 255472 -> 683952 bytes public/claude-ide/index.html | 11 +++ public/claude-ide/terminal.js | 132 ++++++++++++++++++++++++++-------- services/terminal-service.js | 17 +++-- 6 files changed, 128 insertions(+), 32 deletions(-) diff --git a/database.sqlite b/database.sqlite index 8b63d091671aff9d400fc57e8e9c0994388e8478..cc5ee4207e8884a053e59190b47ef2052a8d96c5 100644 GIT binary patch delta 553 zcmZoTz}Rqrae}lUD+2=q8x%7FY5s{i#^S6DdcD7R`EnRoc*7X@r1;kGZr}~$4cjaz z@PLOYf^BjHuL>jUSA=NAGR!^U5q&BVo^%^)gmZEMTmlA4oRlA59rP?VpQnq2Z>{^Ukw zlSIqZv^4#U{M=Oi(xUv_ME$(fa@`b&N+Sa!GhG8iT|>(dLo+K=Ln}iwJwtQLC|u(8 zj1bF=ZEbBKmemuo4U0_%Rt6xXXJBA}ViPWLLnQTF-~hPKz<;0r36ROKSybRWzcvdq z10$0(vn?mG!;!Ut!i|yt69fMzV5q&|m*Qb$mSrq}1XD3E1VN#ClY##&|4pF$S$=C_ qW^G2a&}0%~*5*VL1GxYSKnZ{uh?#%}+~F7CXIAA5Ni8mc#RUK?oQ5<2 delta 240 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 aI delta 3131 zcmdT`c~BEq7~f4Gg!2Vj6pc($tM#BE34x%gItVz5Qbh!HXl;qGU=WiKf~igsaJ+z8 zOVm0q z_xoOzX-vA^g|FH0+-w)js0vzeP82j5Vh77`-9wK+3pWc*^PbrNw&a~ z+(f|!1Z*IRB&&f?gSsIj$N2I^6DJ~ZSf*YWqtm2eN&3k`J^ZJ|4FdnU@Mn-DNG_5{ zMbhzcQn?~HL=hYu7#bo=c-cN_hi&VC8}4N~98tL6&FnxA0(z>wgV1(>mnFU?aI!tw zoO*uWDF0PIQ?B+RpckIJ1;p+s+uv17`IkxFaJId*b?yF}n`cIUK;iczpugI9pVbRS zvJqGE90z`^NotErt4ey%kp4nnVA|-D2w%f)s4vT)d?`Rc0hzX3kfVoblR2g>UYo4c zW68o~EHg=`(&|+jbx-Nm+&5EFLPBLS@gmI=ze9$1dcKW+pphkJZCM~ehfTC*`9rDo7ewchAGjEmRUT67+;B;wf+>Q<_@AeUD!U<; z6vbw@3~GIJw*+!+cJHNi*VvQI_4R5gpaXfOy6dleAiMTXc>;}ZsXP7)>h4khlc-x; z*0~Z@FP!OMItd;`C=A{<7#gf4ds*b?x1G=^NqxBS{no3 zT@C0-legi-qg{QRi-4ra{_U@@yL8Zb+&-%-f&wt7AMY}u*3)4Mji5jV6h z1HqiGGY+eg9N$ia8EtGmXG?lGslIa^Y{7DNvDi9tvI5ZXrW(V>&d!Ws6uh6+>U+6W zeNz{(+##88?aCW4&5fIO*+;Q2!fXUKfNe8y%|@Jz#T9_g)*g18YTgd;R&}BdmrJD@}I+$`wYBAbM^8Yf8< za*uX9=kqsJ_#`B(dB)_cX-?F|!o3A&9N-~e?iP3kU(TW{3a`RBU)b?=d)vj@yOra& zk=M-YP~KA@V8}T^RkBK{78$UGB9&T?=~9$Qm^jd&(Jj{NFf3DSQ7(~2m#S2&mXr52YO&4& zB-WBZ%-IzpQG`eXrSc%VnKrObhCqpnN7`WXwC(RccYRzrNBWYX!x(F#4DJzic;X!@ z(7zdL^Z;`4@2zAV)IO#xbReLEOwX@C>cz^m?X&WWw>Z!Q3(k|Gi76Ke5e)%I zDH#Go>%+EwtlZgrTE&zLp9}t-@85~t>3|Mf(aVC)kc-sFQ~J?kop;;GMdP|_HSL1h z8m3&B#2LGWeqGLYfQOX`Ebt7uK<+0lm8neec5<Hw)_xquYl^AHUzYhXMO>Av}6Q*Cbdp*V^-+x+5fB? zJC2nJ??f+`QOI2g=z>qvRv`Cb2e?LJ=@@+b#ZWI)G606qtw=vG|DfTaKeWgKKBVWM zS~(moaVN?oYBs)5&^OrHg33VSIk4I|4w_*B6QL2JJlgXBClick "+ New Terminal" to get started

+ + +
+
+

🐛 Terminal Debug Panel

+ +
+
+
Waiting for terminal activity...
+
+
diff --git a/public/claude-ide/terminal.js b/public/claude-ide/terminal.js index 3a4ff5e0..192ac7ac 100644 --- a/public/claude-ide/terminal.js +++ b/public/claude-ide/terminal.js @@ -9,6 +9,7 @@ class TerminalManager { this.xtermLoaded = false; this.terminalsContainer = null; this.terminalTabsContainer = null; + this.debugMessages = []; // Bind methods this.createTerminal = this.createTerminal.bind(this); @@ -18,6 +19,44 @@ class TerminalManager { this.clearScreen = this.clearScreen.bind(this); } + /** + * Log debug message to both console and visual debug panel + */ + debugLog(category, message, data = null) { + const timestamp = new Date().toLocaleTimeString(); + const logEntry = `[${timestamp}] [${category}] ${message}`; + + // Log to console + console.log(logEntry, data || ''); + + // Add to debug panel + this.debugMessages.push({ timestamp, category, message, data }); + if (this.debugMessages.length > 50) { + this.debugMessages.shift(); // Keep only last 50 messages + } + + const debugContent = document.getElementById('terminal-debug-content'); + if (debugContent) { + const colorMap = { + 'INIT': '#4a9eff', + 'WS': '#a78bfa', + 'CMD': '#51cf66', + 'ERROR': '#ff6b6b', + 'READY': '#ffd43b', + 'PTY': '#ffa94d' + }; + const color = colorMap[category] || '#e0e0e0'; + + debugContent.innerHTML = this.debugMessages.map(msg => { + const displayColor = colorMap[msg.category] || '#e0e0e0'; + return `
[${msg.timestamp}] [${msg.category}] ${msg.message}${msg.data ? ` - ${JSON.stringify(msg.data)}` : ''}
`; + }).join(''); + + // Auto-scroll to bottom + debugContent.scrollTop = debugContent.scrollHeight; + } + } + /** * Load xterm.js CSS dynamically */ @@ -170,28 +209,35 @@ class TerminalManager { terminalType = null } = options; + this.debugLog('INIT', `createTerminal called with options`, { workingDir, sessionId, mode, terminalType }); + // Show directory picker if no working directory provided const selection = workingDir ? { directory: workingDir, terminalType: terminalType || 'standard' } : await this.showDirectoryPicker(); if (!selection) { + this.debugLog('INIT', `User cancelled directory picker`); return null; } const { directory: selectedDir, terminalType: selectedTerminalType } = selection; + this.debugLog('INIT', `Directory selected: ${selectedDir}, terminalType: ${selectedTerminalType}`); // If no session provided and not skipping picker, show session picker let sessionSelection = null; if (!sessionId && !skipSessionPicker && selectedTerminalType !== 'claude-cli') { // Skip session picker if Claude Code CLI terminal is selected + this.debugLog('INIT', `Showing session picker...`); sessionSelection = await this.showSessionPicker(); // If user cancelled session picker, still create terminal but without session // sessionSelection will be null or { sessionId: string, source: 'web'|'local' } or { sessionId: 'new', source: 'new' } + this.debugLog('INIT', `Session picker result:`, sessionSelection); } try { // Create terminal via API + this.debugLog('INIT', `Calling /claude/api/terminals to create terminal...`); const res = await fetch('/claude/api/terminals', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -205,27 +251,37 @@ class TerminalManager { const data = await res.json(); if (!data.success) { + this.debugLog('ERROR', `API call failed:`, data); throw new Error(data.error || 'Failed to create terminal'); } const terminalId = data.terminalId; + this.debugLog('INIT', `Terminal created with ID: ${terminalId}`); // Create terminal UI + this.debugLog('INIT', `Creating terminal UI...`); await this.createTerminalUI(terminalId, selectedDir, mode); // Initialize xterm.js FIRST (before connecting WebSocket) // This ensures this.terminals map has the entry ready + this.debugLog('INIT', `Initializing xterm.js...`); await this.initializeXTerm(terminalId); + this.debugLog('INIT', `xterm.js initialized, terminal should be in map now`); // NOW connect WebSocket (terminal entry exists in map) // This waits for the 'ready' message from backend + this.debugLog('INIT', `Connecting WebSocket...`); await this.connectTerminal(terminalId); + this.debugLog('INIT', `WebSocket connected and ready`); // Switch to new terminal + this.debugLog('INIT', `Switching to terminal ${terminalId}...`); this.switchToTerminal(terminalId); + this.debugLog('INIT', `Switched to terminal ${terminalId}`); // Handle terminal type specific initialization if (selectedTerminalType === 'claude-cli') { + this.debugLog('CMD', `Claude Code CLI terminal selected, launching command...`); // Launch Claude CLI with skip permissions flag // Note: Keep mode as 'mixed' since we're not attaching to a session // connectTerminal now waits for 'ready' message, so PTY is definitely ready @@ -631,23 +687,28 @@ class TerminalManager { * Waits for terminal to be ready before sending command */ async launchCommand(terminalId, command) { - console.log(`[TerminalManager] launchCommand: terminalId=${terminalId}, command="${command.trim()}"`); + this.debugLog('CMD', `launchCommand called: terminalId=${terminalId}, command="${command.trim()}"`); // Wait for terminal to be ready (max 5 seconds) - console.log(`[TerminalManager] Waiting for terminal ${terminalId} to be ready...`); + this.debugLog('CMD', `Waiting for terminal ${terminalId} to be ready...`); const ready = await this.waitForTerminalReady(terminalId, 5000); if (!ready) { - console.error(`[TerminalManager] Terminal ${terminalId} NOT ready (timeout after 5s)`); + this.debugLog('ERROR', `Terminal ${terminalId} NOT ready (timeout after 5s)`); showToast('Terminal not ready. Please try again.', 'error'); return; } - console.log(`[TerminalManager] Terminal ${terminalId} is ready! Sending command.`); + this.debugLog('CMD', `Terminal ${terminalId} is ready! Sending command.`); const terminal = this.terminals.get(terminalId); if (!terminal || !terminal.ws || terminal.ws.readyState !== WebSocket.OPEN) { - console.error(`[TerminalManager] Cannot send - WebSocket not ready. terminal=${!!terminal}, ws=${!!terminal?.ws}, state=${terminal?.ws?.readyState}`); + this.debugLog('ERROR', `Cannot send - WebSocket not ready`, { + hasTerminal: !!terminal, + hasWs: !!terminal?.ws, + wsState: terminal?.ws?.readyState, + stateName: terminal?.ws?.readyState === WebSocket.OPEN ? 'OPEN' : 'NOT OPEN' + }); return; } @@ -656,10 +717,10 @@ class TerminalManager { type: 'input', data: command }); - console.log(`[TerminalManager] Sending to WebSocket: ${message}`); + this.debugLog('CMD', `Sending to WebSocket: ${message}`); terminal.ws.send(message); - console.log(`[TerminalManager] Command sent to terminal ${terminalId}: ${command.trim()}`); + this.debugLog('CMD', `Command sent to terminal ${terminalId}: ${command.trim()}`); } /** @@ -820,12 +881,14 @@ class TerminalManager { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}/claude/api/terminals/${terminalId}/ws`; + this.debugLog('WS', `Connecting to WebSocket for terminal ${terminalId}`, { url: wsUrl }); + return new Promise((resolve, reject) => { try { const ws = new WebSocket(wsUrl); ws.onopen = () => { - console.log(`[TerminalManager] WebSocket connected for terminal ${terminalId}`); + this.debugLog('WS', `WebSocket OPENED for terminal ${terminalId}`); // Store WebSocket in terminal entry // NOTE: This assumes initializeXTerm() has already been called @@ -834,8 +897,9 @@ class TerminalManager { if (terminal) { terminal.ws = ws; terminal.ready = false; // Will be set to true when 'ready' message received + this.debugLog('WS', `WebSocket stored in terminal map, waiting for 'ready' message`); } else { - console.error(`[TerminalManager] CRITICAL: Terminal ${terminalId} not found in map! WebSocket connection will be lost.`); + this.debugLog('ERROR', `CRITICAL: Terminal ${terminalId} not found in map!`, { terminalsInMap: Array.from(this.terminals.keys()) }); reject(new Error(`Terminal ${terminalId} not initialized`)); ws.close(); return; @@ -843,30 +907,35 @@ class TerminalManager { }; ws.onmessage = (event) => { - const message = JSON.parse(event.data); - this.handleTerminalMessage(terminalId, message); + try { + const message = JSON.parse(event.data); + this.debugLog('WS', `Message received: type="${message.type}"`, message); + this.handleTerminalMessage(terminalId, message); - // If this is the ready message, resolve the promise - if (message.type === 'ready') { - console.log(`[TerminalManager] Terminal ${terminalId} is ready (PTY initialized)`); - const terminal = this.terminals.get(terminalId); - if (terminal) { - terminal.ready = true; + // If this is the ready message, resolve the promise + if (message.type === 'ready') { + this.debugLog('READY', `✅ Ready message received for ${terminalId}, PTY initialized`, message); + const terminal = this.terminals.get(terminalId); + if (terminal) { + terminal.ready = true; + } + resolve(); } - resolve(); + } catch (error) { + this.debugLog('ERROR', `Failed to parse WebSocket message`, { error: error.message, data: event.data }); } }; ws.onerror = (error) => { - console.error(`[TerminalManager] WebSocket error for terminal ${terminalId}:`, error); + this.debugLog('ERROR', `WebSocket error for terminal ${terminalId}`, error); reject(error); }; - ws.onclose = () => { - console.log(`[TerminalManager] WebSocket closed for terminal ${terminalId}`); + ws.onclose = (event) => { + this.debugLog('WS', `WebSocket CLOSED for terminal ${terminalId}`, { code: event.code, reason: event.reason, wasClean: event.wasClean }); }; } catch (error) { - console.error('[TerminalManager] Error connecting WebSocket:', error); + this.debugLog('ERROR', `Exception connecting WebSocket`, error); reject(error); } }); @@ -876,39 +945,42 @@ class TerminalManager { * Handle terminal message from WebSocket */ handleTerminalMessage(terminalId, message) { - console.log(`[TerminalManager] Received message from backend: type="${message.type}", terminalId="${terminalId}"`, message); + this.debugLog('WS', `Handling message: type="${message.type}"`, message); const terminal = this.terminals.get(terminalId); if (!terminal) { - console.error(`[TerminalManager] Cannot handle message - terminal ${terminalId} not found in map`); + this.debugLog('ERROR', `Cannot handle message - terminal ${terminalId} not found in map`, { terminalsInMap: Array.from(this.terminals.keys()) }); return; } switch (message.type) { case 'ready': - console.log(`[TerminalManager] ✅ Ready message received for ${terminalId}, PTY is initialized`); + this.debugLog('READY', `PTY initialized for ${terminalId}`); break; case 'data': // Write to xterm.js if (terminal.terminal) { + this.debugLog('PTY', `Writing data to xterm.js (${message.data.length} chars)`); terminal.terminal.write(message.data); + } else { + this.debugLog('ERROR', `No xterm.js instance for terminal ${terminalId}`); } break; case 'exit': - console.log(`[TerminalManager] Terminal ${terminalId} exited: ${message.exitCode}`); + this.debugLog('PTY', `Terminal exited: ${message.exitCode || signal}`); showToast(`Terminal exited: ${message.exitCode || 'terminated'}`, 'info'); break; case 'modeChanged': - // Update mode display + this.debugLog('INIT', `Mode changed to ${message.mode}`); this.updateModeDisplay(terminalId, message.mode); break; default: - console.log(`[TerminalManager] Unknown message type: ${message.type}`); + this.debugLog('WS', `Unknown message type: ${message.type}`); } } @@ -916,9 +988,11 @@ class TerminalManager { * Initialize xterm.js instance for terminal */ async initializeXTerm(terminalId) { + this.debugLog('INIT', `initializeXTerm called for ${terminalId}`); const container = document.getElementById(`xterm-${terminalId}`); if (!container) { + this.debugLog('ERROR', `Terminal container not found: ${terminalId}`); throw new Error(`Terminal container not found: ${terminalId}`); } @@ -997,6 +1071,8 @@ class TerminalManager { ready: false // Will be set to true when 'ready' message received }); + this.debugLog('INIT', `xterm.js instance stored in map for ${terminalId}, terminals now has`, { terminalIds: Array.from(this.terminals.keys()) }); + return terminal; } diff --git a/services/terminal-service.js b/services/terminal-service.js index 014f7efb..2d141a3c 100644 --- a/services/terminal-service.js +++ b/services/terminal-service.js @@ -100,6 +100,7 @@ class TerminalService { const terminal = this.terminals.get(terminalId); if (!terminal) { + console.error(`[TerminalService] TERMINAL NOT FOUND: ${terminalId}`); ws.close(1008, 'Terminal not found'); return; } @@ -108,14 +109,18 @@ class TerminalService { terminal.lastActivity = new Date().toISOString(); console.log(`[TerminalService] WebSocket connected for terminal ${terminalId}`); + console.log(`[TerminalService] Terminal info - workingDir: ${terminal.workingDir}, mode: ${terminal.mode}`); // Handle incoming messages from client (user input) ws.on('message', (data) => { + console.log(`[TerminalService] Message received from ${terminalId}: ${data.toString()}`); try { const message = JSON.parse(data); + console.log(`[TerminalService] Parsed message type: ${message.type}`); if (message.type === 'input') { // User typed something - send to PTY + console.log(`[TerminalService] Writing to PTY: "${message.data.replace(/\n/g, '\\n')}"`); terminal.pty.write(message.data); terminal.lastActivity = new Date().toISOString(); @@ -125,6 +130,7 @@ class TerminalService { } } else if (message.type === 'resize') { // Handle terminal resize + console.log(`[TerminalService] Resize to ${message.cols}x${message.rows}`); terminal.pty.resize(message.cols, message.rows); } } catch (error) { @@ -160,8 +166,8 @@ class TerminalService { }); // Handle WebSocket close - ws.on('close', () => { - console.log(`[TerminalService] WebSocket closed for terminal ${terminalId}`); + ws.on('close', (code, reason) => { + console.log(`[TerminalService] WebSocket closed for terminal ${terminalId} - code: ${code}, reason: ${reason || 'none'}`); // Don't kill PTY immediately - allow reconnection // PTY will be killed after timeout or explicit close @@ -173,12 +179,15 @@ class TerminalService { }); // Send initial welcome message - ws.send(JSON.stringify({ + const readyMessage = JSON.stringify({ type: 'ready', terminalId, workingDir: terminal.workingDir, mode: terminal.mode - })); + }); + console.log(`[TerminalService] Sending ready message to ${terminalId}: ${readyMessage}`); + ws.send(readyMessage); + console.log(`[TerminalService] Ready message sent successfully`); } /**