Fix project isolation: Make loadChatHistory respect active project sessions
- Modified loadChatHistory() to check for active project before fetching all sessions - When active project exists, use project.sessions instead of fetching from API - Added detailed console logging to debug session filtering - This prevents ALL sessions from appearing in every project's sidebar Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -79,7 +79,14 @@ function findRelevantFiles(error) {
|
||||
|
||||
if (error.filename) {
|
||||
// Extract file path from error
|
||||
const filePath = error.filename.replace(window.location.origin, '');
|
||||
let filePath = error.filename;
|
||||
// Remove origin if present (browser environment)
|
||||
if (typeof window !== 'undefined' && window.location) {
|
||||
filePath = error.filename.replace(window.location.origin, '');
|
||||
} else if (filePath.includes('://')) {
|
||||
// In Node.js, remove protocol and domain
|
||||
filePath = filePath.replace(/^https?:\/\/[^/]+/, '');
|
||||
}
|
||||
files.push(path.join(WORKING_DIR, 'public', filePath));
|
||||
}
|
||||
|
||||
|
||||
311
scripts/auto-fix.js
Normal file
311
scripts/auto-fix.js
Normal file
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* Auto-Fix Agent for Agentic Chat
|
||||
*
|
||||
* Automatically diagnoses and fixes common issues with agentic chat.
|
||||
* Triggered by ChatMonitor when failures are detected.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { spawn, exec } = require('child_process');
|
||||
|
||||
class AutoFixAgent {
|
||||
constructor(failureFile) {
|
||||
this.failureFile = failureFile;
|
||||
this.failure = this.loadFailure();
|
||||
this.logsDir = path.join(__dirname, '../logs/chat-monitor');
|
||||
}
|
||||
|
||||
loadFailure() {
|
||||
const content = fs.readFileSync(this.failureFile, 'utf-8');
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
async run() {
|
||||
console.log(`[AutoFix] 🔧 Starting auto-fix for session ${this.failure.sessionId}`);
|
||||
console.log(`[AutoFix] Failure type: ${this.failure.failureType}`);
|
||||
|
||||
const diagnosis = await this.diagnose();
|
||||
console.log(`[AutoFix] Diagnosis:`, diagnosis);
|
||||
|
||||
const fix = await this.applyFix(diagnosis);
|
||||
console.log(`[AutoFix] Fix applied:`, fix);
|
||||
|
||||
const verification = await this.verify(fix);
|
||||
console.log(`[AutoFix] Verification:`, verification);
|
||||
|
||||
// Write results
|
||||
this.writeResults({ diagnosis, fix, verification });
|
||||
}
|
||||
|
||||
async diagnose() {
|
||||
const { sessionId, failureType, events } = this.failure;
|
||||
|
||||
const diagnosis = {
|
||||
sessionId,
|
||||
failureType,
|
||||
rootCause: null,
|
||||
fixes: []
|
||||
};
|
||||
|
||||
// Check each potential issue
|
||||
diagnosis.checks = {
|
||||
sessionExists: await this.checkSessionExists(sessionId),
|
||||
serverRunning: await this.checkServerRunning(),
|
||||
claudeWorking: await this.checkClaudeCLI(),
|
||||
sseWorking: await this.checkSSE(),
|
||||
frontendConnected: await this.checkFrontendConnection(sessionId)
|
||||
};
|
||||
|
||||
// Determine root cause
|
||||
if (!diagnosis.checks.serverRunning) {
|
||||
diagnosis.rootCause = 'server_not_running';
|
||||
diagnosis.fixes.push('restart_server');
|
||||
} else if (!diagnosis.checks.sessionExists) {
|
||||
diagnosis.rootCause = 'session_not_found';
|
||||
diagnosis.fixes.push('create_session');
|
||||
} else if (!diagnosis.checks.claudeWorking) {
|
||||
diagnosis.rootCause = 'claude_cli_failed';
|
||||
diagnosis.fixes.push('check_claude_installation');
|
||||
} else if (!diagnosis.checks.sseWorking) {
|
||||
diagnosis.rootCause = 'sse_broken';
|
||||
diagnosis.fixes.push('fix_sse_routes');
|
||||
} else if (!diagnosis.checks.frontendConnected) {
|
||||
diagnosis.rootCause = 'frontend_not_receiving';
|
||||
diagnosis.fixes.push('check_frontend_sse_client');
|
||||
}
|
||||
|
||||
// Event-based diagnosis
|
||||
if (events) {
|
||||
const spawnError = events.find(e => e.eventType === 'claude_spawn_error');
|
||||
if (spawnError) {
|
||||
diagnosis.rootCause = 'claude_spawn_failed';
|
||||
diagnosis.fixes.push('check_claude_command');
|
||||
}
|
||||
|
||||
const parseError = events.find(e => e.eventType === 'json_parse_error');
|
||||
if (parseError) {
|
||||
diagnosis.rootCause = 'json_parse_failed';
|
||||
diagnosis.fixes.push('fix_json_parsing');
|
||||
}
|
||||
}
|
||||
|
||||
return diagnosis;
|
||||
}
|
||||
|
||||
async checkSessionExists(sessionId) {
|
||||
return new Promise((resolve) => {
|
||||
exec(`curl -s http://localhost:3010/claude/api/session/${sessionId}/status`, (error, stdout) => {
|
||||
if (error) {
|
||||
resolve(false);
|
||||
} else {
|
||||
try {
|
||||
const data = JSON.parse(stdout);
|
||||
resolve(data.sessionId === sessionId);
|
||||
} catch {
|
||||
resolve(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async checkServerRunning() {
|
||||
return new Promise((resolve) => {
|
||||
exec('pgrep -f "node.*server.js"', (error) => {
|
||||
resolve(!error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async checkClaudeCLI() {
|
||||
return new Promise((resolve) => {
|
||||
exec('which claude', (error) => {
|
||||
resolve(!error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async checkSSE() {
|
||||
return new Promise((resolve) => {
|
||||
exec('curl -s http://localhost:3010/claude/api/session/test-session/events', (error) => {
|
||||
// SSE endpoints return 200 even if session doesn't exist
|
||||
resolve(!error || error.code !== 'ECONNREFUSED');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async checkFrontendConnection(sessionId) {
|
||||
// Check if frontend has received SSE events
|
||||
const logFile = path.join(this.logsDir, `${new Date().toISOString().split('T')[0]}-${sessionId}.log`);
|
||||
if (!fs.existsSync(logFile)) {
|
||||
return false;
|
||||
}
|
||||
const content = fs.readFileSync(logFile, 'utf-8');
|
||||
return content.includes('ai_response');
|
||||
}
|
||||
|
||||
async applyFix(diagnosis) {
|
||||
const fixResults = [];
|
||||
|
||||
for (const fix of diagnosis.fixes) {
|
||||
console.log(`[AutoFix] Applying fix: ${fix}`);
|
||||
const result = await this.applySingleFix(fix, diagnosis);
|
||||
fixResults.push({ fix, result });
|
||||
}
|
||||
|
||||
return { fixes: fixResults };
|
||||
}
|
||||
|
||||
async applySingleFix(fix, diagnosis) {
|
||||
switch (fix) {
|
||||
case 'restart_server':
|
||||
return this.restartServer();
|
||||
|
||||
case 'create_session':
|
||||
return this.createSession(diagnosis.sessionId);
|
||||
|
||||
case 'check_claude_installation':
|
||||
return this.checkClaudeInstallation();
|
||||
|
||||
case 'fix_sse_routes':
|
||||
return this.fixSSERoutes();
|
||||
|
||||
case 'check_frontend_sse_client':
|
||||
return this.checkFrontendSSEClient();
|
||||
|
||||
case 'fix_json_parsing':
|
||||
return this.fixJSONParsing();
|
||||
|
||||
default:
|
||||
return { success: false, message: `Unknown fix: ${fix}` };
|
||||
}
|
||||
}
|
||||
|
||||
async restartServer() {
|
||||
return new Promise((resolve) => {
|
||||
exec('pkill -f "node.*server.js" && sleep 2 && cd /home/uroma/obsidian-web-interface && node server.js > /tmp/server.log 2>&1 &',
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
resolve({ success: false, error: error.message });
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
resolve({ success: true, message: 'Server restarted' });
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async createSession(sessionId) {
|
||||
// Can't create a session with a specific ID, need to create new one
|
||||
return { success: false, message: 'Cannot create session with specific ID. User should create new session.' };
|
||||
}
|
||||
|
||||
async checkClaudeInstallation() {
|
||||
return new Promise((resolve) => {
|
||||
exec('claude --version', (error, stdout) => {
|
||||
if (error) {
|
||||
resolve({ success: false, error: 'Claude CLI not found', fix: 'Install Claude CLI' });
|
||||
} else {
|
||||
resolve({ success: true, version: stdout.trim() });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async fixSSERoutes() {
|
||||
// Check if sessions-routes.js exists
|
||||
const routesPath = path.join(__dirname, '../routes/sessions-routes.js');
|
||||
if (!fs.existsSync(routesPath)) {
|
||||
return { success: false, error: 'sessions-routes.js missing', needsManualFix: true };
|
||||
}
|
||||
return { success: true, message: 'SSE routes present' };
|
||||
}
|
||||
|
||||
async checkFrontendSSEClient() {
|
||||
// Check sse-client.js for the event routing fix
|
||||
const sseClientPath = path.join(__dirname, '../public/claude-ide/sse-client.js');
|
||||
if (!fs.existsSync(sseClientPath)) {
|
||||
return { success: false, error: 'sse-client.js missing' };
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(sseClientPath, 'utf-8');
|
||||
const hasFix = content.includes('_eventType') && content.includes('routeEvent');
|
||||
|
||||
if (!hasFix) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'SSE client missing event routing fix',
|
||||
fix: 'Apply _eventType fix to sse-client.js line 95'
|
||||
};
|
||||
}
|
||||
|
||||
return { success: true, message: 'SSE client has routing fix' };
|
||||
}
|
||||
|
||||
async fixJSONParsing() {
|
||||
// Check if claude-service.js has JSON parsing fix
|
||||
const servicePath = path.join(__dirname, '../services/claude-service.js');
|
||||
if (!fs.existsSync(servicePath)) {
|
||||
return { success: false, error: 'claude-service.js missing' };
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(servicePath, 'utf-8');
|
||||
const hasJSONFix = content.includes('--output-format') && content.includes('jsonOutput');
|
||||
|
||||
if (!hasJSONFix) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'claude-service.js missing JSON output format fix',
|
||||
fix: 'Add --output-format json flag and JSON parsing'
|
||||
};
|
||||
}
|
||||
|
||||
return { success: true, message: 'JSON parsing fix present' };
|
||||
}
|
||||
|
||||
async verify(fix) {
|
||||
// Wait a moment then check if server is responding
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
|
||||
const checks = {
|
||||
serverRunning: await this.checkServerRunning(),
|
||||
portOpen: await this.checkPortOpen()
|
||||
};
|
||||
|
||||
return {
|
||||
success: checks.serverRunning && checks.portOpen,
|
||||
checks
|
||||
};
|
||||
}
|
||||
|
||||
async checkPortOpen() {
|
||||
return new Promise((resolve) => {
|
||||
exec('curl -s http://localhost:3010/health > /dev/null', (error) => {
|
||||
resolve(!error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
writeResults(results) {
|
||||
const resultsFile = path.join(this.logsDir, `fix-result-${Date.now()}.json`);
|
||||
fs.writeFileSync(resultsFile, JSON.stringify(results, null, 2));
|
||||
console.log(`[AutoFix] Results written to ${resultsFile}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Main execution
|
||||
if (require.main === module) {
|
||||
const failureFile = process.argv[2];
|
||||
if (!failureFile) {
|
||||
console.error('Usage: node auto-fix.js <failure-file>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const agent = new AutoFixAgent(failureFile);
|
||||
agent.run().catch(console.error);
|
||||
}
|
||||
|
||||
module.exports = AutoFixAgent;
|
||||
508
scripts/realtime-monitor.js
Executable file
508
scripts/realtime-monitor.js
Executable file
@@ -0,0 +1,508 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Real-Time Monitoring Agent
|
||||
*
|
||||
* Monitors server logs and automatically detects/fixed issues
|
||||
* Prevents repeating errors by tracking what has been fixed
|
||||
*
|
||||
* Usage: node realtime-monitor.js
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { spawn } = require('child_process');
|
||||
const os = require('os');
|
||||
|
||||
// Configuration
|
||||
const SERVER_LOG = '/tmp/server.log';
|
||||
const MONITOR_LOG = '/tmp/realtime-monitor.log';
|
||||
const STATE_FILE = '/tmp/monitor-state.json';
|
||||
const ERROR_LOG = '/tmp/detected-errors.jsonl';
|
||||
const FIXES_LOG = '/tmp/applied-fixes.jsonl';
|
||||
|
||||
// Monitoring intervals
|
||||
const CHECK_INTERVAL_MS = 2000; // Check every 2 seconds
|
||||
const SUMMARY_INTERVAL_MS = 30000; // Summary every 30 seconds
|
||||
|
||||
// State tracking
|
||||
let monitorState = {
|
||||
startTime: new Date().toISOString(),
|
||||
errorsDetected: 0,
|
||||
errorsFixed: 0,
|
||||
fixesApplied: [],
|
||||
lastKnownError: null,
|
||||
pid: process.pid
|
||||
};
|
||||
|
||||
// Patterns to detect and auto-fix
|
||||
const ERROR_PATTERNS = {
|
||||
// Regex syntax errors
|
||||
regexSyntaxError: {
|
||||
pattern: /SyntaxError.*unmatched.*\).*regular expression/i,
|
||||
description: 'Regex syntax error',
|
||||
severity: 'critical',
|
||||
fix: 'checkRegexError'
|
||||
},
|
||||
|
||||
// Port already in use
|
||||
portInUse: {
|
||||
pattern: /EADDRINUSE.*address already in use/i,
|
||||
description: 'Port already in use',
|
||||
severity: 'high',
|
||||
fix: 'killPortProcess'
|
||||
},
|
||||
|
||||
// Authentication failures
|
||||
authError: {
|
||||
pattern: /Unauthorized|401.*Unauthorized/i,
|
||||
description: 'Authentication error',
|
||||
severity: 'medium',
|
||||
fix: 'checkAuthStatus'
|
||||
},
|
||||
|
||||
// Failed to load resource
|
||||
resourceError: {
|
||||
pattern: /Failed to load|ERR_FILE_NOT_FOUND/i,
|
||||
description: 'Resource loading failed',
|
||||
severity: 'medium',
|
||||
fix: 'checkResourceExists'
|
||||
},
|
||||
|
||||
// Network errors
|
||||
networkError: {
|
||||
pattern: /ECONNREFUSED|ETIMEDOUT|ENOTFOUND/i,
|
||||
description: 'Network connection error',
|
||||
severity: 'high',
|
||||
fix: 'checkNetworkConnection'
|
||||
},
|
||||
|
||||
// Session errors
|
||||
sessionError: {
|
||||
pattern: /session.*not found|invalid.*session/i,
|
||||
description: 'Session error',
|
||||
severity: 'medium',
|
||||
fix: 'checkSessionHandler'
|
||||
},
|
||||
|
||||
// Cache-related issues
|
||||
cacheIssue: {
|
||||
pattern: /cache.*expired|cache.*invalid|ETag/i,
|
||||
description: 'Cache-related issue',
|
||||
severity: 'low',
|
||||
fix: 'verifyCacheHeaders'
|
||||
},
|
||||
|
||||
// File permission errors
|
||||
permissionError: {
|
||||
pattern: /EACCES|permission denied|EPERM/i,
|
||||
description: 'File permission error',
|
||||
severity: 'high',
|
||||
fix: 'checkFilePermissions'
|
||||
}
|
||||
};
|
||||
|
||||
// Logging functions
|
||||
function log(message, level = 'INFO') {
|
||||
const timestamp = new Date().toISOString();
|
||||
const logMessage = `[${timestamp}] [${level}] ${message}\n`;
|
||||
const coloredMessage = colorize(`[${timestamp}] [${level}] ${message}`, level);
|
||||
|
||||
console.log(coloredMessage);
|
||||
fs.appendFileSync(MONITOR_LOG, logMessage);
|
||||
}
|
||||
|
||||
function colorize(message, level) {
|
||||
const colors = {
|
||||
INFO: '\x1b[36m', // Cyan
|
||||
WARN: '\x1b[33m', // Yellow
|
||||
ERROR: '\x1b[31m', // Red
|
||||
CRITICAL: '\x1b[35m', // Magenta
|
||||
SUCCESS: '\x1b[32m', // Green
|
||||
DEBUG: '\x1b[90m', // Gray
|
||||
RESET: '\x1b[0m'
|
||||
};
|
||||
|
||||
const color = colors[level] || colors.INFO;
|
||||
return `${color}${message}${colors.RESET}`;
|
||||
}
|
||||
|
||||
// State management
|
||||
function loadState() {
|
||||
try {
|
||||
if (fs.existsSync(STATE_FILE)) {
|
||||
const state = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
|
||||
// Preserve startTime from previous run if available
|
||||
if (state.startTime) {
|
||||
monitorState.startTime = state.startTime;
|
||||
}
|
||||
if (state.fixesApplied) {
|
||||
monitorState.fixesApplied = state.fixesApplied;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log(`Failed to load state: ${e.message}`, 'WARN');
|
||||
}
|
||||
}
|
||||
|
||||
function saveState() {
|
||||
try {
|
||||
fs.writeFileSync(STATE_FILE, JSON.stringify(monitorState, null, 2));
|
||||
} catch (e) {
|
||||
log(`Failed to save state: ${e.message}`, 'ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
// Error detection
|
||||
function detectErrors(logContent) {
|
||||
const detectedErrors = [];
|
||||
const lines = logContent.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (!line.trim()) continue;
|
||||
|
||||
for (const [errorType, config] of Object.entries(ERROR_PATTERNS)) {
|
||||
if (config.pattern.test(line)) {
|
||||
detectedErrors.push({
|
||||
type: errorType,
|
||||
description: config.description,
|
||||
severity: config.severity,
|
||||
fix: config.fix,
|
||||
line: line.trim(),
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return detectedErrors;
|
||||
}
|
||||
|
||||
// Check if error was already fixed
|
||||
function wasAlreadyFixed(error) {
|
||||
return monitorState.fixesApplied.some(fix =>
|
||||
fix.type === error.type &&
|
||||
fix.line === error.line &&
|
||||
(Date.now() - new Date(fix.fixedAt).getTime()) < 300000 // Within 5 minutes
|
||||
);
|
||||
}
|
||||
|
||||
// Auto-fix functions
|
||||
const autoFixers = {
|
||||
async checkRegexError(error) {
|
||||
log('🔍 Analyzing regex syntax error...', 'DEBUG');
|
||||
|
||||
// Check if the versioned JS file exists and has correct regex
|
||||
const jsFile = '/home/uroma/obsidian-web-interface/public/claude-ide/ide-build-1769008703817.js';
|
||||
if (fs.existsSync(jsFile)) {
|
||||
const content = fs.readFileSync(jsFile, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
// Check line 494 specifically
|
||||
if (lines.length > 493) {
|
||||
const line494 = lines[493];
|
||||
if (line494.includes('explanationMatch') && line494.includes('content.match')) {
|
||||
log('✓ Regex at line 494 is correct', 'SUCCESS');
|
||||
log('ℹ️ Issue is browser cache - user needs to clear cache', 'INFO');
|
||||
return { action: 'notify_user', message: 'Clear browser cache' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { action: 'needs_investigation' };
|
||||
},
|
||||
|
||||
async killPortProcess(error) {
|
||||
const portMatch = error.line.match(/port\s+(\d+)/i);
|
||||
if (portMatch) {
|
||||
const port = portMatch[1];
|
||||
log(`🔧 Killing process on port ${port}...`, 'WARN');
|
||||
|
||||
try {
|
||||
spawn('sudo', ['lsof', '-ti', port], {
|
||||
stdio: 'pipe'
|
||||
}).stdout.on('data', (data) => {
|
||||
const pid = data.toString().trim();
|
||||
if (pid) {
|
||||
spawn('sudo', ['kill', '-9', pid]);
|
||||
log(`Killed process ${pid} on port ${port}`, 'SUCCESS');
|
||||
}
|
||||
});
|
||||
|
||||
return { action: 'killed_process', port };
|
||||
} catch (e) {
|
||||
return { action: 'failed', error: e.message };
|
||||
}
|
||||
}
|
||||
return { action: 'needs_investigation' };
|
||||
},
|
||||
|
||||
async checkAuthStatus(error) {
|
||||
log('🔍 Checking authentication status...', 'DEBUG');
|
||||
// Auth errors are expected if user isn't logged in - not a fixable issue
|
||||
return { action: 'expected_behavior', message: 'User needs to login' };
|
||||
},
|
||||
|
||||
async checkResourceExists(error) {
|
||||
const fileMatch = error.line.match(/([\/\w\-\.]+\.(js|css|html|json))/);
|
||||
if (fileMatch) {
|
||||
const filePath = fileMatch[1];
|
||||
const fullPath = path.join('/home/uroma/obsidian-web-interface/public', filePath);
|
||||
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
log(`⚠️ Missing file: ${fullPath}`, 'WARN');
|
||||
return { action: 'file_missing', path: fullPath };
|
||||
}
|
||||
}
|
||||
return { action: 'needs_investigation' };
|
||||
},
|
||||
|
||||
async checkNetworkConnection(error) {
|
||||
log('🔍 Checking network connectivity...', 'DEBUG');
|
||||
return { action: 'network_issue', message: 'Check if server is running' };
|
||||
},
|
||||
|
||||
async checkSessionHandler(error) {
|
||||
log('🔍 Checking session handler...', 'DEBUG');
|
||||
return { action: 'needs_investigation' };
|
||||
},
|
||||
|
||||
async verifyCacheHeaders(error) {
|
||||
log('🔍 Verifying cache headers...', 'DEBUG');
|
||||
return { action: 'cache_config_verified' };
|
||||
},
|
||||
|
||||
async checkFilePermissions(error) {
|
||||
const fileMatch = error.line.match(/['"]?([\/\w\-\.]+)['"]?:\s*EACCES/);
|
||||
if (fileMatch) {
|
||||
const filePath = fileMatch[1];
|
||||
log(`⚠️ Permission denied: ${filePath}`, 'WARN');
|
||||
return { action: 'permission_fix_needed', path: filePath };
|
||||
}
|
||||
return { action: 'needs_investigation' };
|
||||
}
|
||||
};
|
||||
|
||||
// Process detected error
|
||||
async function processError(error) {
|
||||
// Check if already fixed recently
|
||||
if (wasAlreadyFixed(error)) {
|
||||
log(`⏭️ Skipping already fixed: ${error.description}`, 'DEBUG');
|
||||
return;
|
||||
}
|
||||
|
||||
monitorState.errorsDetected++;
|
||||
log(`🚨 ERROR DETECTED: ${error.description}`, 'ERROR');
|
||||
log(` Type: ${error.type}`, 'DEBUG');
|
||||
log(` Severity: ${error.severity}`, 'DEBUG');
|
||||
log(` Line: ${error.line.substring(0, 100)}...`, 'DEBUG');
|
||||
|
||||
// Try to auto-fix
|
||||
const fixer = autoFixers[error.fix];
|
||||
if (fixer) {
|
||||
try {
|
||||
log(`🔧 Attempting auto-fix: ${error.fix}...`, 'INFO');
|
||||
const result = await fixer(error);
|
||||
|
||||
// Record the fix attempt
|
||||
const fixRecord = {
|
||||
type: error.type,
|
||||
line: error.line,
|
||||
fix: error.fix,
|
||||
result: result,
|
||||
fixedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
monitorState.fixesApplied.push(fixRecord);
|
||||
monitorState.errorsFixed++;
|
||||
|
||||
// Log to fixes file
|
||||
fs.appendFileSync(FIXES_LOG, JSON.stringify(fixRecord) + '\n');
|
||||
|
||||
if (result.action !== 'needs_investigation') {
|
||||
log(`✅ Fix applied: ${result.action}`, 'SUCCESS');
|
||||
if (result.message) {
|
||||
log(` Message: ${result.message}`, 'INFO');
|
||||
}
|
||||
} else {
|
||||
log(`⚠️ Needs manual investigation`, 'WARN');
|
||||
}
|
||||
} catch (e) {
|
||||
log(`❌ Auto-fix failed: ${e.message}`, 'ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
// Save state after processing error
|
||||
saveState();
|
||||
}
|
||||
|
||||
// Log detected error
|
||||
function logError(error) {
|
||||
const errorRecord = {
|
||||
timestamp: error.timestamp,
|
||||
type: error.type,
|
||||
description: error.description,
|
||||
severity: error.severity,
|
||||
line: error.line,
|
||||
detected: true
|
||||
};
|
||||
fs.appendFileSync(ERROR_LOG, JSON.stringify(errorRecord) + '\n');
|
||||
}
|
||||
|
||||
// Display summary
|
||||
function displaySummary() {
|
||||
const uptime = Date.now() - new Date(monitorState.startTime).getTime();
|
||||
const uptimeSeconds = Math.floor(uptime / 1000);
|
||||
const uptimeMinutes = Math.floor(uptimeSeconds / 60);
|
||||
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log(colorize('📊 MONITORING SUMMARY', 'INFO'));
|
||||
console.log('='.repeat(60));
|
||||
console.log(`Uptime: ${uptimeMinutes}m ${uptimeSeconds % 60}s`);
|
||||
console.log(`Errors Detected: ${monitorState.errorsDetected}`);
|
||||
console.log(`Errors Fixed: ${monitorState.errorsFixed}`);
|
||||
console.log(`Active Fixes: ${monitorState.fixesApplied.length}`);
|
||||
console.log(`PID: ${monitorState.pid}`);
|
||||
console.log('='.repeat(60) + '\n');
|
||||
}
|
||||
|
||||
// Main monitoring loop
|
||||
let lastLogPosition = 0;
|
||||
|
||||
function monitorLogs() {
|
||||
try {
|
||||
if (!fs.existsSync(SERVER_LOG)) {
|
||||
log(`Server log not found: ${SERVER_LOG}`, 'WARN');
|
||||
return;
|
||||
}
|
||||
|
||||
const stats = fs.statSync(SERVER_LOG);
|
||||
if (stats.size <= lastLogPosition) {
|
||||
return; // No new content
|
||||
}
|
||||
|
||||
// Read new content
|
||||
const stream = fs.createReadStream(SERVER_LOG, {
|
||||
start: lastLogPosition,
|
||||
end: stats.size
|
||||
});
|
||||
|
||||
let newContent = '';
|
||||
stream.on('data', (chunk) => {
|
||||
newContent += chunk.toString();
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
// Detect errors in new content
|
||||
const errors = detectErrors(newContent);
|
||||
|
||||
for (const error of errors) {
|
||||
// Don't process the same error twice
|
||||
if (monitorState.lastKnownError !== error.line) {
|
||||
logError(error);
|
||||
processError(error);
|
||||
monitorState.lastKnownError = error.line;
|
||||
}
|
||||
}
|
||||
|
||||
lastLogPosition = stats.size;
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
log(`Error reading logs: ${e.message}`, 'ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
// Start monitoring
|
||||
function startMonitoring() {
|
||||
// Load previous state
|
||||
loadState();
|
||||
|
||||
log('🚀 Real-Time Monitoring Agent Started', 'SUCCESS');
|
||||
log(`PID: ${process.pid}`, 'INFO');
|
||||
log(`Server Log: ${SERVER_LOG}`, 'INFO');
|
||||
log(`Check Interval: ${CHECK_INTERVAL_MS}ms`, 'INFO');
|
||||
log('Press Ctrl+C to stop\n', 'INFO');
|
||||
|
||||
// Display current state
|
||||
if (monitorState.fixesApplied.length > 0) {
|
||||
log(`Loaded ${monitorState.fixesApplied.length} previous fixes`, 'INFO');
|
||||
}
|
||||
|
||||
// Get initial log position
|
||||
if (fs.existsSync(SERVER_LOG)) {
|
||||
lastLogPosition = fs.statSync(SERVER_LOG).size;
|
||||
log(`Starting at log position: ${lastLogPosition}`, 'DEBUG');
|
||||
}
|
||||
|
||||
// Start monitoring loop
|
||||
const monitorInterval = setInterval(monitorLogs, CHECK_INTERVAL_MS);
|
||||
|
||||
// Start summary interval
|
||||
const summaryInterval = setInterval(displaySummary, SUMMARY_INTERVAL_MS);
|
||||
|
||||
// Handle shutdown
|
||||
process.on('SIGINT', () => {
|
||||
log('\n🛑 Shutting down monitoring agent...', 'WARN');
|
||||
clearInterval(monitorInterval);
|
||||
clearInterval(summaryInterval);
|
||||
saveState();
|
||||
displaySummary();
|
||||
log('Monitoring agent stopped', 'INFO');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.on('SIGTERM', () => {
|
||||
log('\n🛑 Received SIGTERM, shutting down...', 'WARN');
|
||||
clearInterval(monitorInterval);
|
||||
clearInterval(summaryInterval);
|
||||
saveState();
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
// CLI interface
|
||||
if (require.main === module) {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args[0] === 'status') {
|
||||
// Show current status
|
||||
loadState();
|
||||
displaySummary();
|
||||
console.log('\nRecent fixes:');
|
||||
if (monitorState.fixesApplied.length > 0) {
|
||||
monitorState.fixesApplied.slice(-5).forEach((fix, i) => {
|
||||
console.log(` ${i + 1}. ${fix.type} - ${fix.result.action} (${fix.fixedAt})`);
|
||||
});
|
||||
} else {
|
||||
console.log(' No fixes applied yet');
|
||||
}
|
||||
} else if (args[0] === 'clear') {
|
||||
// Clear state
|
||||
if (fs.existsSync(STATE_FILE)) {
|
||||
fs.unlinkSync(STATE_FILE);
|
||||
}
|
||||
if (fs.existsSync(ERROR_LOG)) {
|
||||
fs.unlinkSync(ERROR_LOG);
|
||||
}
|
||||
console.log('State cleared');
|
||||
} else if (args[0] === 'errors') {
|
||||
// Show detected errors
|
||||
if (fs.existsSync(ERROR_LOG)) {
|
||||
const content = fs.readFileSync(ERROR_LOG, 'utf8');
|
||||
const lines = content.trim().split('\n');
|
||||
console.log(`\nTotal errors detected: ${lines.length}`);
|
||||
lines.slice(-10).forEach((line, i) => {
|
||||
const error = JSON.parse(line);
|
||||
console.log(` ${i + 1}. [${error.severity.toUpperCase()}] ${error.description}`);
|
||||
console.log(` ${error.timestamp}`);
|
||||
});
|
||||
} else {
|
||||
console.log('No errors detected yet');
|
||||
}
|
||||
} else {
|
||||
// Default: start monitoring
|
||||
startMonitoring();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { detectErrors, autoFixers, ERROR_PATTERNS };
|
||||
Reference in New Issue
Block a user