fix: prevent duplicate status boxes on session idle/reactivation cycle
- Added completedBoxes map to track idle sessions and their post IDs - On session reactivation, reuse existing post instead of creating new one - Fixed variable scoping bug (saved -> savedState) in session-added handler - Root cause: idle -> forgetSession -> re-detect -> new post -> repeat This was creating 10+ duplicate status boxes per session per hour.
This commit is contained in:
@@ -138,6 +138,11 @@ async function startDaemon() {
|
||||
// Map<sessionKey, { postId, agentId, channelId, rootPostId, children: Map }>
|
||||
const activeBoxes = new Map();
|
||||
|
||||
// Completed sessions: Map<sessionKey, { postId, lastOffset }>
|
||||
// Tracks sessions that went idle so we can reuse their post on reactivation
|
||||
// instead of creating duplicate status boxes.
|
||||
const completedBoxes = new Map();
|
||||
|
||||
// Pending sub-agents: spawnedBy key -> [info] (arrived before parent was added)
|
||||
const pendingSubAgents = new Map();
|
||||
let globalMetrics = {
|
||||
@@ -266,11 +271,21 @@ async function startDaemon() {
|
||||
|
||||
let postId;
|
||||
|
||||
// Check if this session was previously completed (reactivation after idle)
|
||||
const completed = completedBoxes.get(sessionKey);
|
||||
if (completed) {
|
||||
postId = completed.postId;
|
||||
completedBoxes.delete(sessionKey);
|
||||
logger.info({ sessionKey, postId }, 'Reactivating completed session — reusing existing post');
|
||||
}
|
||||
|
||||
// Check for existing post (restart recovery)
|
||||
const saved = savedOffsets[sessionKey]; // eslint-disable-line security/detect-object-injection
|
||||
if (saved) {
|
||||
// Try to find existing post in channel history
|
||||
postId = await findExistingPost(sharedStatusBox, channelId, sessionKey, logger);
|
||||
if (!postId) {
|
||||
const saved = savedOffsets[sessionKey]; // eslint-disable-line security/detect-object-injection
|
||||
if (saved) {
|
||||
// Try to find existing post in channel history
|
||||
postId = await findExistingPost(sharedStatusBox, channelId, sessionKey, logger);
|
||||
}
|
||||
}
|
||||
|
||||
// Create new post if none found
|
||||
@@ -304,8 +319,9 @@ async function startDaemon() {
|
||||
globalMetrics.activeSessions = activeBoxes.size;
|
||||
|
||||
// Register in watcher
|
||||
const initialState = saved
|
||||
? { lastOffset: saved.lastOffset, startTime: saved.startTime, agentId }
|
||||
const savedState = savedOffsets[sessionKey]; // eslint-disable-line security/detect-object-injection
|
||||
const initialState = savedState
|
||||
? { lastOffset: savedState.lastOffset, startTime: savedState.startTime, agentId }
|
||||
: { agentId };
|
||||
watcher.addSession(sessionKey, transcriptFile, initialState);
|
||||
|
||||
@@ -405,9 +421,17 @@ async function startDaemon() {
|
||||
logger.error({ sessionKey, err }, 'Failed to update final status');
|
||||
}
|
||||
|
||||
// Clean up — remove from all tracking so session can be re-detected if it becomes active again
|
||||
// Save to completedBoxes so we can reuse the post ID if the session reactivates
|
||||
completedBoxes.set(sessionKey, {
|
||||
postId: box.postId,
|
||||
lastOffset: state.lastOffset || 0,
|
||||
});
|
||||
|
||||
// Clean up active tracking
|
||||
activeBoxes.delete(sessionKey);
|
||||
watcher.removeSession(sessionKey);
|
||||
// Forget from monitor so it CAN be re-detected — but completedBoxes
|
||||
// ensures we reuse the existing post instead of creating a new one.
|
||||
monitor.forgetSession(sessionKey);
|
||||
globalMetrics.activeSessions = activeBoxes.size;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user