mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 16:39:42 +02:00
fix: add 2-minute grace period for respawn after server restart
Prevents false idle detection when server is rebuilt/restarted. Previously, restored respawn controllers would start immediately and trigger after 30s of "no output" because the session wasn't fully connected yet. Now waits 2 minutes from server startup before activating restored respawn controllers, giving sessions time to reconnect and stabilize. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
+45
-4
@@ -273,6 +273,12 @@ export class WebServer extends EventEmitter {
|
|||||||
// Token recording for daily stats (track what's been recorded to avoid double-counting)
|
// Token recording for daily stats (track what's been recorded to avoid double-counting)
|
||||||
private lastRecordedTokens: Map<string, { input: number; output: number }> = new Map();
|
private lastRecordedTokens: Map<string, { input: number; output: number }> = new Map();
|
||||||
private tokenRecordingTimer: NodeJS.Timeout | null = null;
|
private tokenRecordingTimer: NodeJS.Timeout | null = null;
|
||||||
|
// Server startup time for respawn grace period calculation
|
||||||
|
private readonly serverStartTime: number = Date.now();
|
||||||
|
// Pending respawn start timers (for cleanup on shutdown)
|
||||||
|
private pendingRespawnStarts: Map<string, NodeJS.Timeout> = new Map();
|
||||||
|
// Grace period before starting restored respawn controllers (2 minutes)
|
||||||
|
private static readonly RESPAWN_RESTORE_GRACE_PERIOD_MS = 2 * 60 * 1000;
|
||||||
|
|
||||||
constructor(port: number = 3000, https: boolean = false) {
|
constructor(port: number = 3000, https: boolean = false) {
|
||||||
super();
|
super();
|
||||||
@@ -2068,6 +2074,13 @@ export class WebServer extends EventEmitter {
|
|||||||
this.respawnTimers.delete(sessionId);
|
this.respawnTimers.delete(sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear pending respawn start timer (from restoration grace period)
|
||||||
|
const pendingStart = this.pendingRespawnStarts.get(sessionId);
|
||||||
|
if (pendingStart) {
|
||||||
|
clearTimeout(pendingStart);
|
||||||
|
this.pendingRespawnStarts.delete(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
// Stop transcript watcher
|
// Stop transcript watcher
|
||||||
this.stopTranscriptWatcher(sessionId);
|
this.stopTranscriptWatcher(sessionId);
|
||||||
|
|
||||||
@@ -2497,9 +2510,10 @@ export class WebServer extends EventEmitter {
|
|||||||
this.broadcast('respawn:timerStarted', { sessionId, durationMinutes, endAt, startedAt: now });
|
this.broadcast('respawn:timerStarted', { sessionId, durationMinutes, endAt, startedAt: now });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore a RespawnController from persisted configuration.
|
* Restore a RespawnController from persisted configuration.
|
||||||
* Creates the controller, sets up listeners, starts it, and optionally sets up a timed respawn.
|
* Creates the controller, sets up listeners, but does NOT start it.
|
||||||
*
|
*
|
||||||
* @param session - The session to attach the controller to
|
* @param session - The session to attach the controller to
|
||||||
* @param config - The persisted respawn configuration
|
* @param config - The persisted respawn configuration
|
||||||
@@ -2536,13 +2550,34 @@ export class WebServer extends EventEmitter {
|
|||||||
|
|
||||||
this.respawnControllers.set(session.id, controller);
|
this.respawnControllers.set(session.id, controller);
|
||||||
this.setupRespawnListeners(session.id, controller);
|
this.setupRespawnListeners(session.id, controller);
|
||||||
controller.start();
|
|
||||||
|
// Calculate delay: wait until 2 minutes after server start before starting respawn
|
||||||
|
// This prevents false idle detection immediately after a server restart/rebuild
|
||||||
|
const timeSinceStart = Date.now() - this.serverStartTime;
|
||||||
|
const delayMs = Math.max(0, WebServer.RESPAWN_RESTORE_GRACE_PERIOD_MS - timeSinceStart);
|
||||||
|
|
||||||
|
if (delayMs > 0) {
|
||||||
|
console.log(`[Server] Restored respawn controller for session ${session.id} from ${source} (will start in ${Math.ceil(delayMs / 1000)}s)`);
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
this.pendingRespawnStarts.delete(session.id);
|
||||||
|
// Double-check controller still exists and is stopped
|
||||||
|
const ctrl = this.respawnControllers.get(session.id);
|
||||||
|
if (ctrl && ctrl.state === 'stopped') {
|
||||||
|
ctrl.start();
|
||||||
|
this.broadcast('respawn:started', { sessionId: session.id });
|
||||||
|
console.log(`[Server] Restored respawn controller started for session ${session.id}`);
|
||||||
|
}
|
||||||
|
}, delayMs);
|
||||||
|
this.pendingRespawnStarts.set(session.id, timer);
|
||||||
|
} else {
|
||||||
|
// Grace period has passed, start immediately
|
||||||
|
controller.start();
|
||||||
|
console.log(`[Server] Restored respawn controller for session ${session.id} from ${source} (started immediately)`);
|
||||||
|
}
|
||||||
|
|
||||||
if (config.durationMinutes && config.durationMinutes > 0) {
|
if (config.durationMinutes && config.durationMinutes > 0) {
|
||||||
this.setupTimedRespawn(session.id, config.durationMinutes);
|
this.setupTimedRespawn(session.id, config.durationMinutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[Server] Restored respawn controller for session ${session.id} from ${source}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to get custom CLAUDE.md template path from settings
|
// Helper to get custom CLAUDE.md template path from settings
|
||||||
@@ -3200,6 +3235,12 @@ export class WebServer extends EventEmitter {
|
|||||||
// Stop screen stats collection
|
// Stop screen stats collection
|
||||||
this.screenManager.stopStatsCollection();
|
this.screenManager.stopStatsCollection();
|
||||||
|
|
||||||
|
// Clear all pending respawn start timers (from restoration grace period)
|
||||||
|
for (const timer of this.pendingRespawnStarts.values()) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
|
this.pendingRespawnStarts.clear();
|
||||||
|
|
||||||
// Stop all respawn controllers and remove listeners
|
// Stop all respawn controllers and remove listeners
|
||||||
for (const controller of this.respawnControllers.values()) {
|
for (const controller of this.respawnControllers.values()) {
|
||||||
controller.stop();
|
controller.stop();
|
||||||
|
|||||||
Reference in New Issue
Block a user