Optimize session snapshots by scanning for last clear command

- Modified snapshot endpoint to find the last screen clear command
- Only includes content after the last clear for much smaller snapshots
- Preserves the last resize event before clear to maintain terminal dimensions
- Detects common clear sequences: \x1b[2J, \x1b[3J, \x1b[H\x1b[2J, \x1bc
- Logs reduction percentage showing data savings
- Falls back to full content if no clear command found
- Dramatically reduces snapshot size for long-running sessions

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mario Zechner 2025-06-18 06:29:50 +02:00
parent b6d45e6c75
commit e606f68f45

View file

@ -553,7 +553,7 @@ app.get('/api/sessions/:sessionId/stream', async (req, res) => {
res.on('finish', cleanup); res.on('finish', cleanup);
}); });
// Get session snapshot (cast with adjusted timestamps for immediate playback) // Get session snapshot (optimized cast with only content after last clear)
app.get('/api/sessions/:sessionId/snapshot', (req, res) => { app.get('/api/sessions/:sessionId/snapshot', (req, res) => {
const sessionId = req.params.sessionId; const sessionId = req.params.sessionId;
const streamOutPath = path.join(TTY_FWD_CONTROL_DIR, sessionId, 'stream-out'); const streamOutPath = path.join(TTY_FWD_CONTROL_DIR, sessionId, 'stream-out');
@ -567,9 +567,9 @@ app.get('/api/sessions/:sessionId/snapshot', (req, res) => {
const lines = content.trim().split('\n'); const lines = content.trim().split('\n');
let header = null; let header = null;
const events = []; const allEvents = [];
let startTime = null;
// Parse all lines first
for (const line of lines) { for (const line of lines) {
if (line.trim()) { if (line.trim()) {
try { try {
@ -581,10 +581,7 @@ app.get('/api/sessions/:sessionId/snapshot', (req, res) => {
} }
// Event line [timestamp, type, data] // Event line [timestamp, type, data]
else if (Array.isArray(parsed) && parsed.length >= 3) { else if (Array.isArray(parsed) && parsed.length >= 3) {
if (startTime === null) { allEvents.push(parsed);
startTime = parsed[0];
}
events.push([0, parsed[1], parsed[2]]);
} }
} catch (_e) { } catch (_e) {
// Skip invalid lines // Skip invalid lines
@ -592,6 +589,53 @@ app.get('/api/sessions/:sessionId/snapshot', (req, res) => {
} }
} }
// Find the last clear command (usually "\x1b[2J\x1b[3J\x1b[H" or similar)
let lastClearIndex = -1;
let lastResizeBeforeClear = null;
for (let i = allEvents.length - 1; i >= 0; i--) {
const event = allEvents[i];
if (event[1] === 'o' && event[2]) {
// Look for clear screen escape sequences
const data = event[2];
if (
data.includes('\x1b[2J') || // Clear entire screen
data.includes('\x1b[H\x1b[2J') || // Home cursor + clear screen
data.includes('\x1b[3J') || // Clear scrollback
data.includes('\x1bc') // Full reset
) {
lastClearIndex = i;
break;
}
}
}
// Find the last resize event before the clear (if any)
if (lastClearIndex > 0) {
for (let i = lastClearIndex - 1; i >= 0; i--) {
const event = allEvents[i];
if (event[1] === 'r') {
lastResizeBeforeClear = event;
break;
}
}
}
// Build optimized event list
const optimizedEvents = [];
// Include last resize before clear if found
if (lastResizeBeforeClear) {
optimizedEvents.push([0, lastResizeBeforeClear[1], lastResizeBeforeClear[2]]);
}
// Include events after the last clear (or all events if no clear found)
const startIndex = lastClearIndex >= 0 ? lastClearIndex : 0;
for (let i = startIndex; i < allEvents.length; i++) {
const event = allEvents[i];
optimizedEvents.push([0, event[1], event[2]]);
}
// Build the complete cast // Build the complete cast
const cast = []; const cast = [];
@ -610,11 +654,20 @@ app.get('/api/sessions/:sessionId/snapshot', (req, res) => {
); );
} }
// Add all events // Add optimized events
events.forEach((event) => { optimizedEvents.forEach((event) => {
cast.push(JSON.stringify(event)); cast.push(JSON.stringify(event));
}); });
const originalSize = allEvents.length;
const optimizedSize = optimizedEvents.length;
const reduction =
originalSize > 0 ? (((originalSize - optimizedSize) / originalSize) * 100).toFixed(1) : '0';
console.log(
`Snapshot for ${sessionId}: ${originalSize} events → ${optimizedSize} events (${reduction}% reduction)`
);
res.setHeader('Content-Type', 'text/plain'); res.setHeader('Content-Type', 'text/plain');
res.send(cast.join('\n')); res.send(cast.join('\n'));
} catch (_error) { } catch (_error) {