mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
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:
parent
b6d45e6c75
commit
e606f68f45
1 changed files with 62 additions and 9 deletions
|
|
@ -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) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue