perf: optimize for long-running Claude sessions

Critical fixes (unbounded growth):
- Add 64KB line buffer limit with 100ms periodic flush in session.ts
- Add task tracker cleanup (max 100 completed tasks)
- Add scheduled runs auto-cleanup after 1 hour
- Fix session leaks in scheduled run iterations

High priority (performance):
- Add SSE event batching (50ms output, 100ms task updates)
- Parallelize screen stats with Promise.all()
- Add 1MB buffer limit to respawn controller

Medium priority (frontend):
- Make xterm scrollback configurable (default 5000)
- Debounce renderSessionTabs() at 100ms

New feature:
- Add CPU and memory usage display in frontend header
- New /api/system/stats endpoint

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-19 06:51:25 +01:00
co-authored by Claude Opus 4.5
parent 40fd13480d
commit 7979e77924
9 changed files with 389 additions and 19 deletions
+45
View File
@@ -1,5 +1,8 @@
import { EventEmitter } from 'node:events';
// Maximum number of completed tasks to keep in memory
const MAX_COMPLETED_TASKS = 100;
/**
* Represents a background task spawned by Claude Code
*/
@@ -159,12 +162,54 @@ export class TaskTracker extends EventEmitter {
} else {
this.emit('taskCompleted', task);
}
// Clean up old completed tasks to prevent unbounded growth
this.cleanupCompletedTasks();
}
// Clean up pending
this.pendingToolUses.delete(toolUseId);
}
/**
* Remove old completed/failed tasks when exceeding the limit
* Keeps running tasks and the most recent completed tasks
*/
private cleanupCompletedTasks(): void {
const completedTasks: BackgroundTask[] = [];
// Collect all completed/failed tasks
for (const task of this.tasks.values()) {
if (task.status === 'completed' || task.status === 'failed') {
completedTasks.push(task);
}
}
// If under limit, no cleanup needed
if (completedTasks.length <= MAX_COMPLETED_TASKS) {
return;
}
// Sort by end time (oldest first)
completedTasks.sort((a, b) => (a.endTime || 0) - (b.endTime || 0));
// Remove oldest tasks beyond the limit
const toRemove = completedTasks.slice(0, completedTasks.length - MAX_COMPLETED_TASKS);
for (const task of toRemove) {
// Remove from parent's children list if applicable
if (task.parentId) {
const parent = this.tasks.get(task.parentId);
if (parent) {
const childIndex = parent.children.indexOf(task.id);
if (childIndex !== -1) {
parent.children.splice(childIndex, 1);
}
}
}
this.tasks.delete(task.id);
}
}
private createTaskFromTerminal(agentType: string, context: string): void {
const taskId = `terminal-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const parentId = this.taskStack.length > 0 ? this.taskStack[this.taskStack.length - 1] : null;