/** * Command Execution Tracker * Monitors command lifecycle, detects behavioral anomalies, * and identifies patterns in command execution failures. */ (function() { 'use strict'; // ============================================================ // COMMAND TRACKER CLASS // ============================================================ class CommandTracker { constructor() { this.pendingCommands = new Map(); this.executionHistory = []; this.maxHistorySize = 100; this.anomalyThreshold = 3; // Report if 3+ similar issues in 5 minutes this.anomalyWindow = 5 * 60 * 1000; // 5 minutes } /** * Generate a unique command ID * @returns {string} - Command ID */ generateId() { return `cmd_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Start tracking a command execution * @param {string} message - Command message * @param {string} mode - Chat mode (webcontainer, auto, native) * @param {string} sessionId - Session ID * @returns {string} - Command ID */ startCommand(message, mode, sessionId) { const commandId = this.generateId(); const commandData = { id: commandId, message: message, extractedCommand: this.extractActualCommand(message), mode: mode, sessionId: sessionId, startTime: Date.now(), state: 'pending', metadata: this.getCommandMetadata(message) }; this.pendingCommands.set(commandId, commandData); console.log('[CommandTracker] Started tracking:', { id: commandId, command: message.substring(0, 50) }); return commandId; } /** * Extract actual command (strip conversational wrappers) * @param {string} message - Original message * @returns {string} - Extracted command */ extractActualCommand(message) { if (window.semanticValidator && typeof window.semanticValidator.extractCommand === 'function') { const extracted = window.semanticValidator.extractCommand(message); return extracted || message; } return message; } /** * Get command metadata for analysis * @param {string} message - Command message * @returns {object} - Metadata */ getCommandMetadata(message) { const metadata = { length: message.length, wordCount: message.split(/\s+/).length, hasSpecialChars: /[\|>]/.test(message), hasPath: /^\//.test(message) || /\//.test(message), isConversational: false, isCommandRequest: false }; if (window.semanticValidator) { metadata.isConversational = window.semanticValidator.isConversational(message); metadata.isCommandRequest = message.match(/\b(run|execute|exec|do)\s+/i) !== null; } return metadata; } /** * Complete command execution * @param {string} commandId - Command ID * @param {number|null} exitCode - Exit code * @param {string} output - Command output */ completeCommand(commandId, exitCode, output) { const command = this.pendingCommands.get(commandId); if (!command) { console.warn('[CommandTracker] Unknown command ID:', commandId); return; } const duration = Date.now() - command.startTime; const success = exitCode === 0; // Update command data command.endTime = Date.now(); command.exitCode = exitCode; command.output = output ? output.substring(0, 500) : ''; command.duration = duration; command.state = success ? 'completed' : 'failed'; command.success = success; // Add to history this.addToHistory(command); // Run semantic checks this.checkForSemanticErrors(command); // Remove from pending this.pendingCommands.delete(commandId); console.log('[CommandTracker] Completed:', { id: commandId, success: success, duration: duration, exitCode: exitCode }); // Periodically check for anomalies if (this.executionHistory.length % 10 === 0) { this.detectBehavioralAnomalies(); } } /** * Add completed command to history * @param {object} command - Command data */ addToHistory(command) { this.executionHistory.push(command); // Trim history if too large if (this.executionHistory.length > this.maxHistorySize) { this.executionHistory = this.executionHistory.slice(-this.maxHistorySize); } } /** * Check for semantic errors in command execution * @param {object} command - Command data */ checkForSemanticErrors(command) { // Check 1: Undefined exit code (UX issue) if (command.exitCode === undefined || command.exitCode === null) { this.reportSemanticError({ type: 'ux_issue', subtype: 'undefined_exit_code', message: 'Command completed with undefined exit code', details: { command: command.message, extractedCommand: command.extractedCommand, output: command.output, suggestedFix: 'Ensure exit code is always returned from command execution' } }); } // Check 2: Conversational message that failed as command if (command.metadata.isConversational && command.exitCode !== 0) { this.reportSemanticError({ type: 'intent_error', subtype: 'conversational_failed_as_command', message: 'Conversational message failed when executed as shell command', details: { command: command.message, mode: command.mode, exitCode: command.exitCode, error: this.extractError(command.output), suggestedFix: 'Improve conversational pattern detection to prevent this' } }); } // Check 3: Command request pattern with no actual command extracted if (command.metadata.isCommandRequest && command.extractedCommand === command.message) { // Command was not extracted properly if (command.exitCode !== 0) { this.reportSemanticError({ type: 'intent_error', subtype: 'command_extraction_failed', message: 'Command request pattern detected but extraction may have failed', details: { command: command.message, exitCode: command.exitCode, error: this.extractError(command.output) } }); } } // Check 4: Long-running command (> 30 seconds) if (command.duration > 30000) { console.warn('[CommandTracker] Long-running command:', { command: command.extractedCommand, duration: command.duration }); this.reportSemanticError({ type: 'performance_issue', subtype: 'long_running_command', message: 'Command took longer than 30 seconds to execute', details: { command: command.extractedCommand, duration: command.duration, suggestedFix: 'Consider timeout or progress indicator' } }); } // Check 5: Empty output for successful command if (command.success && (!command.output || command.output.trim() === '')) { this.reportSemanticError({ type: 'ux_issue', subtype: 'empty_success_output', message: 'Command succeeded but produced no output', details: { command: command.extractedCommand, suggestedFix: 'Show "Command completed successfully" message for empty output' } }); } } /** * Extract error message from output * @param {string} output - Command output * @returns {string} - Error message */ extractError(output) { if (!output) return 'No output'; // Try to extract first line of error const lines = output.split('\n'); const errorLine = lines.find(line => line.toLowerCase().includes('error') || line.toLowerCase().includes('failed') || line.toLowerCase().includes('cannot') ); return errorLine ? errorLine.substring(0, 100) : output.substring(0, 100); } /** * Detect behavioral anomalies in command history */ detectBehavioralAnomalies() { const now = Date.now(); const recentWindow = this.executionHistory.filter( cmd => now - cmd.endTime < this.anomalyWindow ); // Anomaly 1: Multiple conversational messages failing as commands const conversationalFailures = recentWindow.filter(cmd => cmd.metadata.isConversational && cmd.exitCode !== 0 ); if (conversationalFailures.length >= this.anomalyThreshold) { this.reportSemanticError({ type: 'behavioral_anomaly', subtype: 'repeated_conversational_failures', message: `Pattern detected: ${conversationalFailures.length} conversational messages failed as commands in last 5 minutes`, details: { count: conversationalFailures.length, examples: conversationalFailures.slice(0, 3).map(f => f.message), suggestedFix: 'Improve conversational detection or add user education' } }); } // Anomaly 2: High failure rate for specific command patterns const commandFailures = new Map(); recentWindow.forEach(cmd => { if (!cmd.success) { const key = cmd.extractedCommand.split(' ')[0]; // First word (command name) commandFailures.set(key, (commandFailures.get(key) || 0) + 1); } }); commandFailures.forEach((count, commandName) => { if (count >= this.anomalyThreshold) { this.reportSemanticError({ type: 'behavioral_anomaly', subtype: 'high_failure_rate', message: `Command "${commandName}" failed ${count} times in last 5 minutes`, details: { command: commandName, failureCount: count, suggestedFix: 'Check if command exists or is being used correctly' } }); } }); // Anomaly 3: Commands with undefined exit codes const undefinedExitCodes = recentWindow.filter(cmd => cmd.exitCode === undefined || cmd.exitCode === null ); if (undefinedExitCodes.length >= 5) { this.reportSemanticError({ type: 'behavioral_anomaly', subtype: 'undefined_exit_code_pattern', message: `${undefinedExitCodes.length} commands completed with undefined exit code`, details: { count: undefinedExitCodes.length, affectedCommands: undefinedExitCodes.slice(0, 5).map(c => c.extractedCommand), suggestedFix: 'Fix command execution to always return exit code' } }); } } /** * Report a semantic error * @param {object} errorData - Error details */ reportSemanticError(errorData) { const semanticError = { type: 'semantic', subtype: errorData.subtype || errorData.type, message: errorData.message, details: errorData.details || {}, timestamp: new Date().toISOString(), url: window.location.href, context: { chatMode: window.currentChatMode || 'unknown', sessionId: window.attachedSessionId || window.chatSessionId, pendingCommands: this.pendingCommands.size, recentHistory: this.executionHistory.slice(-5).map(cmd => ({ command: cmd.extractedCommand.substring(0, 30), success: cmd.success, timestamp: cmd.endTime })) } }; // Report to bug tracker if (window.bugTracker) { const errorId = window.bugTracker.addError(semanticError); // Only add activity if error was actually added (not skipped) if (errorId) { window.bugTracker.addActivity(errorId, '📊', `Pattern: ${errorData.subtype}`, 'warning'); } } // Report to server fetch('/claude/api/log-error', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(semanticError) }).catch(err => console.error('[CommandTracker] Failed to report error:', err)); console.log('[CommandTracker] Semantic error reported:', semanticError); } /** * Get statistics about command execution * @returns {object} - Statistics */ getStatistics() { const total = this.executionHistory.length; const successful = this.executionHistory.filter(cmd => cmd.success).length; const failed = total - successful; const avgDuration = total > 0 ? this.executionHistory.reduce((sum, cmd) => sum + (cmd.duration || 0), 0) / total : 0; return { total, successful, failed, successRate: total > 0 ? (successful / total * 100).toFixed(1) : 0, avgDuration: Math.round(avgDuration), pending: this.pendingCommands.size }; } /** * Clear old history (older than 1 hour) */ clearOldHistory() { const oneHourAgo = Date.now() - (60 * 60 * 1000); this.executionHistory = this.executionHistory.filter( cmd => cmd.endTime > oneHourAgo ); console.log('[CommandTracker] Cleared old history, remaining:', this.executionHistory.length); } } // ============================================================ // GLOBAL INSTANCE // ============================================================ // Create global instance window.commandTracker = new CommandTracker(); // Auto-clean old history every 10 minutes setInterval(() => { window.commandTracker.clearOldHistory(); }, 10 * 60 * 1000); // Expose statistics to console for debugging window.getCommandStats = () => window.commandTracker.getStatistics(); console.log('[CommandTracker] Command lifecycle tracking initialized'); })();