From f545cb00be1f39a4232b27b5843e655a3043ed7f Mon Sep 17 00:00:00 2001 From: sol Date: Mon, 9 Mar 2026 18:31:41 +0000 Subject: [PATCH] fix: orphan session cleanup + instant reactivation via ghost watch plugin/server/store.go: - CleanStaleSessions now handles last_update_ms=0 (pre-cleanup era orphans) - Zero-timestamp sessions: mark active ones interrupted, delete non-active ones - Previously these were silently skipped with 'continue', accumulating forever src/status-watcher.js: - removeSession() keeps fileToSession mapping as ghost entry ('\x00ghost:key') - When ghost file changes, emits 'session-reactivate' immediately instead of waiting up to 2s for the session-monitor poll cycle - Ghost removed after first trigger to avoid repeated events src/session-monitor.js: - Added pollNow() for immediate poll without waiting for interval tick - Reactivation check now uses sessions.json updatedAt vs completedAt timestamp (pure infrastructure: two on-disk timestamps, no AI involvement) src/watcher-manager.js: - Wires session-reactivate event: clearCompleted() + pollNow() for instant re-detection - New sessions now show up within ~100ms of first file change instead of 2s Net result: status box appears reliably on every turn, clears 3s after reply, zero orphan sessions accumulating in the KV store. --- plugin/server/store.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugin/server/store.go b/plugin/server/store.go index 0c04f32..9a0ac72 100644 --- a/plugin/server/store.go +++ b/plugin/server/store.go @@ -146,6 +146,18 @@ func (s *Store) CleanStaleSessions(staleThresholdMs, expireThresholdMs int64) (c lastUpdate = session.StartTimeMs } if lastUpdate == 0 { + // No timestamps at all (pre-cleanup era orphan) — treat as expired immediately. + // Delete non-active sessions; mark active ones as interrupted so they get + // picked up on the next cleanup cycle with a real timestamp. + if session.Status == "active" { + session.Status = "interrupted" + session.LastUpdateMs = now + _ = s.SaveSession(session.SessionKey, session) + cleaned++ + } else { + _ = s.DeleteSession(session.SessionKey) + expired++ + } continue } age := now - lastUpdate