feat: RHS panel initial fetch, floating widget, session cleanup (#5)

Phase 1: Fix RHS panel to fetch existing sessions on mount
- Add initial API fetch in useAllStatusUpdates() hook
- Allow GET /sessions endpoint without shared secret auth
- RHS panel now shows sessions after page refresh

Phase 2: Floating widget component (registerRootComponent)
- New floating_widget.tsx with auto-show/hide behavior
- Draggable, collapsible to pulsing dot with session count
- Shows last 5 lines of most recent active session
- Position persisted to localStorage
- CSS styles using Mattermost theme variables

Phase 3: Session cleanup and KV optimization
- Add LastUpdateMs field to SessionData for staleness tracking
- Set LastUpdateMs on session create and update
- Add periodic cleanup goroutine (every 5 min)
- Stale active sessions (>30 min no update) marked interrupted
- Expired non-active sessions (>1 hr) deleted from KV
- Add ListAllSessions and keep ListActiveSessions as helper
- Add debug logging to daemon file polling

Closes #5
This commit is contained in:
sol
2026-03-09 14:15:04 +00:00
parent 9ec52a418d
commit 79d5e82803
10 changed files with 520 additions and 32 deletions

View File

@@ -107,16 +107,27 @@ class StatusWatcher extends EventEmitter {
_startFilePoll(sessionKey, state) {
var self = this;
var pollInterval = 500; // 500ms poll
var pollCount = 0;
state._filePollTimer = setInterval(function () {
try {
var stat = fs.statSync(state.transcriptFile);
pollCount++;
if (stat.size > state.lastOffset) {
if (self.logger) {
self.logger.info(
{ sessionKey, fileSize: stat.size, lastOffset: state.lastOffset, delta: stat.size - state.lastOffset, pollCount },
'File poll: new data detected — reading',
);
}
self._readFile(sessionKey, state);
}
} catch (_e) {
// File might not exist yet or was deleted
}
}, pollInterval);
if (self.logger) {
self.logger.info({ sessionKey, pollInterval }, 'File poll timer started');
}
}
/**
@@ -214,6 +225,9 @@ class StatusWatcher extends EventEmitter {
const state = this.sessions.get(sessionKey);
if (!state) return;
if (this.logger) {
this.logger.info({ sessionKey, fullPath: path.basename(fullPath) }, 'fs.watch: file change detected');
}
this._readFile(sessionKey, state);
}