feat: implement CodexLens watcher status handling and UI updates

This commit is contained in:
catlog22
2026-01-03 23:47:02 +08:00
parent 1825ed3bcf
commit f28b6c6197
3 changed files with 87 additions and 0 deletions

View File

@@ -1374,6 +1374,19 @@ export async function handleCodexLensRoutes(ctx: RouteContext): Promise<boolean>
start_time: new Date()
};
// Capture stderr for error messages (capped at 4KB to prevent memory leak)
const MAX_STDERR_SIZE = 4096;
let stderrBuffer = '';
if (watcherProcess.stderr) {
watcherProcess.stderr.on('data', (data: Buffer) => {
stderrBuffer += data.toString();
// Cap buffer size to prevent memory leak in long-running watchers
if (stderrBuffer.length > MAX_STDERR_SIZE) {
stderrBuffer = stderrBuffer.slice(-MAX_STDERR_SIZE);
}
});
}
// Handle process output for event counting
if (watcherProcess.stdout) {
watcherProcess.stdout.on('data', (data: Buffer) => {
@@ -1386,11 +1399,38 @@ export async function handleCodexLensRoutes(ctx: RouteContext): Promise<boolean>
});
}
// Handle spawn errors (e.g., ENOENT)
watcherProcess.on('error', (err: Error) => {
console.error(`[CodexLens] Watcher spawn error: ${err.message}`);
watcherStats.running = false;
watcherProcess = null;
broadcastToClients({
type: 'CODEXLENS_WATCHER_STATUS',
payload: { running: false, error: `Spawn error: ${err.message}` }
});
});
// Handle process exit
watcherProcess.on('exit', (code: number) => {
watcherStats.running = false;
watcherProcess = null;
console.log(`[CodexLens] Watcher exited with code ${code}`);
// Broadcast error if exited with non-zero code
if (code !== 0) {
const errorMsg = stderrBuffer.trim() || `Exited with code ${code}`;
// Use stripAnsiCodes helper for consistent ANSI cleanup
const cleanError = stripAnsiCodes(errorMsg);
broadcastToClients({
type: 'CODEXLENS_WATCHER_STATUS',
payload: { running: false, error: cleanError }
});
} else {
broadcastToClients({
type: 'CODEXLENS_WATCHER_STATUS',
payload: { running: false }
});
}
});
// Broadcast watcher started

View File

@@ -455,6 +455,23 @@ function handleNotification(data) {
console.log('[CodexLens] Index progress:', payload.stage, payload.percent + '%');
break;
case 'CODEXLENS_WATCHER_STATUS':
// Handle CodexLens file watcher status updates
if (typeof handleWatcherStatusUpdate === 'function') {
handleWatcherStatusUpdate(payload);
}
if (payload.error) {
console.error('[CodexLens] Watcher error:', payload.error);
if (typeof showRefreshToast === 'function') {
showRefreshToast('Watcher error: ' + payload.error, 'error');
}
} else if (payload.running) {
console.log('[CodexLens] Watcher running:', payload.path);
} else {
console.log('[CodexLens] Watcher stopped');
}
break;
default:
console.log('[WS] Unknown notification type:', type);
}

View File

@@ -4783,3 +4783,33 @@ function closeWatcherModal() {
var modal = document.getElementById('watcherControlModal');
if (modal) modal.remove();
}
/**
* Handle watcher status update from WebSocket
* @param {Object} payload - { running: boolean, path?: string, error?: string }
*/
function handleWatcherStatusUpdate(payload) {
var toggle = document.getElementById('watcherToggle');
var statsDiv = document.getElementById('watcherStats');
var configDiv = document.getElementById('watcherStartConfig');
if (payload.error) {
// Watcher failed - update UI to show stopped state
if (toggle) toggle.checked = false;
if (statsDiv) statsDiv.style.display = 'none';
if (configDiv) configDiv.style.display = 'block';
stopWatcherStatusPolling();
} else if (payload.running) {
// Watcher started
if (toggle) toggle.checked = true;
if (statsDiv) statsDiv.style.display = 'block';
if (configDiv) configDiv.style.display = 'none';
startWatcherStatusPolling();
} else {
// Watcher stopped normally
if (toggle) toggle.checked = false;
if (statsDiv) statsDiv.style.display = 'none';
if (configDiv) configDiv.style.display = 'block';
stopWatcherStatusPolling();
}
}