From cc65f9b5ce3f2c153ece48ed4301f80c14b3905a Mon Sep 17 00:00:00 2001 From: sol Date: Mon, 9 Mar 2026 18:57:54 +0000 Subject: [PATCH] fix: clearCompleted must also delete from _knownSessions Without this, _onSessionAdded never fires on reactivation because isKnown=true short-circuits the new-session detection branch. clearCompleted is called on lock file creation / ghost watch fire. It clears _completedSessions cooldown but the session key stayed in _knownSessions (isKnown=true), so poll() treated it as already tracked and silently updated the entry instead of firing _onSessionAdded. Fix: also delete from _knownSessions in clearCompleted() so next poll sees the session as unknown and fires _onSessionAdded -> creates new plugin status box. Also: findExistingPost skipped in plugin mode on session-added to prevent stale post reuse from REST search results. --- src/session-monitor.js | 4 ++++ src/watcher-manager.js | 11 ++++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/session-monitor.js b/src/session-monitor.js index a815cb6..e3cbe8a 100644 --- a/src/session-monitor.js +++ b/src/session-monitor.js @@ -106,6 +106,10 @@ class SessionMonitor extends EventEmitter { */ clearCompleted(sessionKey) { this._completedSessions.delete(sessionKey); + // Also remove from _knownSessions so the next poll sees it as a new session + // and fires _onSessionAdded (which creates the new status box). + // Without this, isKnown=true suppresses _onSessionAdded even after clearCompleted. + this._knownSessions.delete(sessionKey); } /** diff --git a/src/watcher-manager.js b/src/watcher-manager.js index 02c1c9e..04103ed 100644 --- a/src/watcher-manager.js +++ b/src/watcher-manager.js @@ -287,8 +287,11 @@ async function startDaemon() { logger.info({ sessionKey }, 'Reactivating session — creating fresh status box'); } - // Check for existing post (restart recovery) - if (!postId) { + // Check for existing post (restart recovery — REST mode only). + // In plugin mode we always create a fresh post: the plugin manages its own + // KV store and the old post is already marked done. Reusing it would make + // the status box appear stuck at the old position in the thread. + if (!postId && !(usePlugin && pluginClient)) { const saved = savedOffsets[sessionKey]; // eslint-disable-line security/detect-object-injection if (saved) { // Try to find existing post in channel history @@ -300,7 +303,9 @@ async function startDaemon() { if (!postId) { try { if (usePlugin && pluginClient) { - // Plugin mode: create custom_livestatus post via plugin + // Plugin mode: always create a fresh custom_livestatus post. + // The plugin replaces any existing KV entry for this sessionKey so there + // is no risk of duplicate boxes. postId = await pluginClient.createSession(sessionKey, channelId, rootPostId, agentId); logger.info({ sessionKey, postId, channelId, mode: 'plugin' }, 'Created status box via plugin'); } else {