From db1ebabe40a4eb342e431b1463e6033a5db2d1dc Mon Sep 17 00:00:00 2001 From: zhb <50901800+zhbdesign@users.noreply.github.com> Date: Fri, 19 Jun 2026 00:45:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AE=A2=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E5=87=A0=E4=B9=8E=E5=90=8C=E6=97=B6=E5=8F=91=E9=80=81=20`prewa?= =?UTF-8?q?rm=5Fsession`=20=E5=92=8C=20`user=5Fmessage`=EF=BC=8C`clearPrew?= =?UTF-8?q?armState`=20=E4=BB=80=E4=B9=88=E4=B9=9F=E6=B2=A1=E6=B8=85?= =?UTF-8?q?=E9=99=A4=EF=BC=8C=E5=AF=BC=E8=87=B4kill=20CLI=EF=BC=8C?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=E6=B0=B8=E8=BF=9C=E6=94=B6=E4=B8=8D?= =?UTF-8?q?=E5=88=B0=20`status:=20idle`=20=E6=88=96=20error=EF=BC=8C?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=8D=A1=E5=9C=A8"=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E4=B8=AD"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added safety checks to prevent prewarm idle timer from interrupting active sessions. --- src/server/ws/handler.ts | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/server/ws/handler.ts b/src/server/ws/handler.ts index 0cebff92..5f785c04 100644 --- a/src/server/ws/handler.ts +++ b/src/server/ws/handler.ts @@ -547,6 +547,15 @@ async function handlePrewarmSession(ws: ServerWebSocket) { } const launchInfo = await sessionService.getSessionLaunchInfo(sessionId).catch(() => null) + + // Re-check after async gap: a user_message may have arrived during the await + // and already started (or is starting) the CLI session. If so, skip prewarm + // entirely — the user turn owns this session now, and calling markPrewarmed() + // would arm an idle timer that later kills the active conversation. + if (conversationService.hasSession(sessionId) || sessionStartupPromises.has(sessionId)) { + return + } + if (launchInfo?.repository) { console.log(`[WS] Skipping prewarm for pending repository launch session ${sessionId}`) return @@ -555,7 +564,14 @@ async function handlePrewarmSession(ws: ServerWebSocket) { prewarmPendingSessions.add(sessionId) void ensureCliSessionStarted(ws, sessionId, 'prewarm_session') .then(() => { - if (!prewarmPendingSessions.delete(sessionId)) return + const stillPending = prewarmPendingSessions.delete(sessionId) + if (!stillPending) return + // Safety: if a user message arrived and activated a turn while we were + // waiting for startup, do NOT arm the prewarm idle timer — the session + // is now owned by the user conversation, not prewarm. + if (isSessionTurnActive(sessionId)) { + return + } bindPrewarmMetadataCapture(sessionId) markPrewarmed(sessionId) }) @@ -1230,6 +1246,15 @@ function markPrewarmed(sessionId: string) { const timer = setTimeout(() => { prewarmIdleTimers.delete(sessionId) if (!prewarmedSessions.has(sessionId)) return + const turnActive = isSessionTurnActive(sessionId) + const hasClients = hasActiveClients(sessionId) + // Safety guard: never kill a session that has an active user turn or + // connected clients. The prewarm idle timer is only meant to reclaim + // truly idle prewarmed sessions — not to interrupt an active conversation. + if (turnActive || hasClients) { + prewarmedSessions.delete(sessionId) + return + } console.log(`[WS] Prewarmed session ${sessionId} idle for ${timeoutMs}ms, stopping CLI subprocess`) conversationService.stopSession(sessionId) prewarmedSessions.delete(sessionId)