feat: AI auto-fix bug tracker with real-time error monitoring
- Real-time error monitoring system with WebSocket - Auto-fix agent that triggers on browser errors - Bug tracker dashboard with floating button (🐛) - Live activity stream showing AI thought process - Fixed 4 JavaScript errors (SyntaxError, TypeError) - Fixed SessionPicker API endpoint error - Enhanced chat input with Monaco editor - Session picker component for project management Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
211
server.js
211
server.js
@@ -562,12 +562,63 @@ app.post('/claude/api/claude/sessions', requireAuth, (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Create session with projectId in metadata
|
||||
// ===== Validate and create working directory =====
|
||||
let validatedWorkingDir = workingDir || VAULT_PATH;
|
||||
|
||||
// Resolve to absolute path
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const resolvedPath = path.resolve(validatedWorkingDir);
|
||||
|
||||
// Security check: ensure path is within allowed boundaries
|
||||
const allowedPaths = [
|
||||
VAULT_PATH,
|
||||
process.env.HOME || '/home/uroma',
|
||||
'/home/uroma'
|
||||
];
|
||||
|
||||
const isAllowed = allowedPaths.some(allowedPath => {
|
||||
return resolvedPath.startsWith(allowedPath);
|
||||
});
|
||||
|
||||
if (!isAllowed) {
|
||||
console.error('[SESSIONS] Working directory outside allowed paths:', resolvedPath);
|
||||
return res.status(403).json({ error: 'Working directory outside allowed paths' });
|
||||
}
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
if (!fs.existsSync(resolvedPath)) {
|
||||
console.log('[SESSIONS] Creating working directory:', resolvedPath);
|
||||
try {
|
||||
fs.mkdirSync(resolvedPath, { recursive: true });
|
||||
} catch (mkdirError) {
|
||||
console.error('[SESSIONS] Failed to create directory:', mkdirError);
|
||||
return res.status(400).json({
|
||||
error: `Failed to create working directory: ${mkdirError.message}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Verify it's actually a directory
|
||||
try {
|
||||
const stats = fs.statSync(resolvedPath);
|
||||
if (!stats.isDirectory()) {
|
||||
return res.status(400).json({ error: 'Working directory is not a directory' });
|
||||
}
|
||||
} catch (statError) {
|
||||
console.error('[SESSIONS] Failed to stat directory:', statError);
|
||||
return res.status(500).json({ error: 'Failed to validate working directory' });
|
||||
}
|
||||
|
||||
console.log('[SESSIONS] Using working directory:', resolvedPath);
|
||||
// ===== END directory validation =====
|
||||
|
||||
// Create session with validated path
|
||||
const sessionMetadata = {
|
||||
...metadata,
|
||||
...(validatedProjectId ? { projectId: validatedProjectId } : {})
|
||||
};
|
||||
const session = claudeService.createSession({ workingDir, metadata: sessionMetadata });
|
||||
const session = claudeService.createSession({ workingDir: resolvedPath, metadata: sessionMetadata });
|
||||
|
||||
// Store session in database with projectId
|
||||
db.prepare(`
|
||||
@@ -777,6 +828,95 @@ app.post('/claude/api/claude/sessions/:id/duplicate', requireAuth, (req, res) =>
|
||||
}
|
||||
});
|
||||
|
||||
// Fork session from a specific message index
|
||||
app.post('/claude/api/claude/sessions/:id/fork', requireAuth, (req, res) => {
|
||||
try {
|
||||
const sessionId = req.params.id;
|
||||
const messageIndex = parseInt(req.query.messageIndex) || -1; // -1 means all messages
|
||||
|
||||
// Get source session
|
||||
let sourceSession = claudeService.sessions.get(sessionId);
|
||||
|
||||
if (!sourceSession) {
|
||||
return res.status(404).json({ error: 'Source session not found' });
|
||||
}
|
||||
|
||||
// Security check: validate workingDir is within VAULT_PATH
|
||||
const fullPath = path.resolve(sourceSession.workingDir);
|
||||
if (!fullPath.startsWith(VAULT_PATH)) {
|
||||
return res.status(400).json({ error: 'Invalid working directory' });
|
||||
}
|
||||
|
||||
// Get messages to fork (1..messageIndex)
|
||||
let messagesToFork = [];
|
||||
if (sourceSession.outputBuffer && sourceSession.outputBuffer.length > 0) {
|
||||
if (messageIndex === -1) {
|
||||
// Fork all messages
|
||||
messagesToFork = [...sourceSession.outputBuffer];
|
||||
} else {
|
||||
// Fork messages up to messageIndex
|
||||
messagesToFork = sourceSession.outputBuffer.slice(0, messageIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Get projectId from source session (if exists)
|
||||
const sourceProjectId = sourceSession.metadata.projectId || null;
|
||||
|
||||
// Create new session with forked context
|
||||
const newSession = claudeService.createSession({
|
||||
workingDir: sourceSession.workingDir,
|
||||
metadata: {
|
||||
...sourceSession.metadata,
|
||||
forkedFrom: sessionId,
|
||||
forkedAtMessageIndex: messageIndex,
|
||||
forkedAt: new Date().toISOString(),
|
||||
source: 'web-ide'
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize output buffer with forked messages
|
||||
if (messagesToFork.length > 0) {
|
||||
newSession.outputBuffer = messagesToFork;
|
||||
}
|
||||
|
||||
// Store forked session in database with same projectId as source
|
||||
db.prepare(`
|
||||
INSERT INTO sessions (id, projectId, deletedAt)
|
||||
VALUES (?, ?, NULL)
|
||||
`).run(newSession.id, sourceProjectId);
|
||||
|
||||
// Update project's lastActivity if session was assigned to a project
|
||||
if (sourceProjectId) {
|
||||
const now = new Date().toISOString();
|
||||
db.prepare(`
|
||||
UPDATE projects
|
||||
SET lastActivity = ?
|
||||
WHERE id = ?
|
||||
`).run(now, sourceProjectId);
|
||||
}
|
||||
|
||||
console.log(`[FORK] Forked session ${sessionId} at message ${messageIndex} -> ${newSession.id}`);
|
||||
console.log(`[FORK] Copied ${messagesToFork.length} messages`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
session: {
|
||||
id: newSession.id,
|
||||
pid: newSession.pid,
|
||||
workingDir: newSession.workingDir,
|
||||
status: newSession.status,
|
||||
createdAt: newSession.createdAt,
|
||||
metadata: newSession.metadata,
|
||||
projectId: sourceProjectId,
|
||||
messageCount: messagesToFork.length
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error forking session:', error);
|
||||
res.status(500).json({ error: 'Failed to fork session' });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete session
|
||||
app.delete('/claude/api/claude/sessions/:id', requireAuth, (req, res) => {
|
||||
try {
|
||||
@@ -1824,19 +1964,24 @@ wss.on('connection', (ws, req) => {
|
||||
ws.on('message', async (message) => {
|
||||
try {
|
||||
const data = JSON.parse(message);
|
||||
console.log('WebSocket message received:', data.type);
|
||||
console.log('[WebSocket] Message received:', {
|
||||
type: data.type,
|
||||
sessionId: data.sessionId?.substring(0, 20) || 'none',
|
||||
hasCommand: !!data.command,
|
||||
commandLength: data.command?.length || 0
|
||||
});
|
||||
|
||||
if (data.type === 'command') {
|
||||
const { sessionId, command } = data;
|
||||
|
||||
console.log(`Sending command to session ${sessionId}: ${command.substring(0, 50)}...`);
|
||||
console.log(`[WebSocket] Sending command to session ${sessionId}: ${command.substring(0, 50)}...`);
|
||||
|
||||
// Send command to Claude Code
|
||||
try {
|
||||
claudeService.sendCommand(sessionId, command);
|
||||
console.log(`Command sent successfully to session ${sessionId}`);
|
||||
console.log(`[WebSocket] ✓ Command sent successfully to session ${sessionId}`);
|
||||
} catch (error) {
|
||||
console.error('Error sending command:', error);
|
||||
console.error(`[WebSocket] ✗ Error sending command:`, error.message);
|
||||
ws.send(JSON.stringify({
|
||||
type: 'error',
|
||||
error: error.message
|
||||
@@ -1848,11 +1993,11 @@ wss.on('connection', (ws, req) => {
|
||||
const client = clients.get(clientId);
|
||||
if (client) {
|
||||
client.sessionId = sessionId;
|
||||
console.log(`Client ${clientId} subscribed to session ${sessionId}`);
|
||||
console.log(`[WebSocket] Client ${clientId} subscribed to session ${sessionId}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('WebSocket error:', error);
|
||||
console.error('[WebSocket] Error:', error);
|
||||
ws.send(JSON.stringify({
|
||||
type: 'error',
|
||||
error: error.message
|
||||
@@ -1962,6 +2107,56 @@ claudeService.on('operations-error', (data) => {
|
||||
|
||||
console.log(`WebSocket server running on ws://localhost:${PORT}/claude/api/claude/chat`);
|
||||
|
||||
// Real-time error monitoring endpoint
|
||||
app.post('/claude/api/log-error', express.json(), (req, res) => {
|
||||
const error = req.body;
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
// Log with visual marker for Claude to notice
|
||||
console.error('\n🚨 [BROWSER_ERROR] ' + '='.repeat(60));
|
||||
console.error('🚨 [BROWSER_ERROR] Time:', timestamp);
|
||||
console.error('🚨 [BROWSER_ERROR] Type:', error.type);
|
||||
console.error('🚨 [BROWSER_ERROR] Message:', error.message);
|
||||
if (error.url) console.error('🚨 [BROWSER_ERROR] URL:', error.url);
|
||||
if (error.line) console.error('🚨 [BROWSER_ERROR] Line:', error.line);
|
||||
if (error.stack) console.error('🚨 [BROWSER_ERROR] Stack:', error.stack);
|
||||
console.error('🚨 [BROWSER_ERROR] ' + '='.repeat(60) + '\n');
|
||||
|
||||
// Also write to error log file
|
||||
const fs = require('fs');
|
||||
const errorLogPath = '/tmp/browser-errors.log';
|
||||
const errorEntry = JSON.stringify({ ...error, loggedAt: timestamp }) + '\n';
|
||||
fs.appendFileSync(errorLogPath, errorEntry);
|
||||
|
||||
// 🤖 AUTO-TRIGGER: Spawn auto-fix agent in background
|
||||
const { spawn } = require('child_process');
|
||||
const agentPath = '/home/uroma/obsidian-web-interface/scripts/auto-fix-agent.js';
|
||||
|
||||
console.log('🤖 [AUTO_FIX] Triggering agent to fix error...');
|
||||
|
||||
const agent = spawn('node', [agentPath, 'process'], {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
env: { ...process.env, ERROR_DATA: JSON.stringify(error) }
|
||||
});
|
||||
|
||||
agent.stdin.write(JSON.stringify(error));
|
||||
agent.stdin.end();
|
||||
|
||||
agent.stdout.on('data', (data) => {
|
||||
console.log('🤖 [AUTO_FIX]', data.toString());
|
||||
});
|
||||
|
||||
agent.stderr.on('data', (data) => {
|
||||
console.error('🤖 [AUTO_FIX] ERROR:', data.toString());
|
||||
});
|
||||
|
||||
agent.on('close', (code) => {
|
||||
console.log(`🤖 [AUTO_FIX] Agent exited with code ${code}`);
|
||||
});
|
||||
|
||||
res.json({ received: true, autoFixTriggered: true });
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGINT', async () => {
|
||||
console.log('\nShutting down gracefully...');
|
||||
|
||||
Reference in New Issue
Block a user