import { log, error as logError } from '../logger' import { storeListActiveEnvironments, storeUpdateEnvironment, storeMarkAcpAgentOffline, } from '../store' import { storeListSessions } from '../store' import { config } from '../config' import { updateSessionStatus } from './session' export function runDisconnectMonitorSweep(now = Date.now()) { const timeoutMs = config.disconnectTimeout * 1000 // Check environment heartbeat timeout const envs = storeListActiveEnvironments() for (const env of envs) { // Skip ACP agents — they use WS keepalive, not polling if (env.workerType === 'acp') { if (env.lastPollAt && now - env.lastPollAt.getTime() > timeoutMs) { log( `[RCS] ACP agent ${env.id} timed out (no activity for ${Math.round((now - env.lastPollAt.getTime()) / 1000)}s)`, ) storeMarkAcpAgentOffline(env.id) } continue } if (env.lastPollAt && now - env.lastPollAt.getTime() > timeoutMs) { log( `[RCS] Environment ${env.id} timed out (no poll for ${Math.round((now - env.lastPollAt.getTime()) / 1000)}s)`, ) storeUpdateEnvironment(env.id, { status: 'disconnected' }) } } // Check session timeout (2x disconnect timeout with no update) const sessions = storeListSessions() for (const session of sessions) { if (session.status === 'running' || session.status === 'idle') { const elapsed = now - session.updatedAt.getTime() if (elapsed > timeoutMs * 2) { log( `[RCS] Session ${session.id} marked inactive (no update for ${Math.round(elapsed / 1000)}s)`, ) updateSessionStatus(session.id, 'inactive') } } } } export function startDisconnectMonitor() { setInterval(() => { runDisconnectMonitorSweep() }, 60_000) // Check every minute }