Skip to content

Commit 61e6a64

Browse files
authored
Merge pull request #1261 from kubet/fix/stream-restore-on-thread-navigation
refactor: improve thread and stream handling on navigation
2 parents 4bb4595 + 0705e1a commit 61e6a64

File tree

2 files changed

+42
-21
lines changed

2 files changed

+42
-21
lines changed

frontend/src/app/(dashboard)/projects/[projectId]/thread/_hooks/useThreadData.ts

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -145,23 +145,7 @@ export function useThreadData(threadId: string, projectId: string): UseThreadDat
145145
if (threadQuery.data && messagesQuery.data && agentRunsQuery.data) {
146146
initialLoadCompleted.current = true;
147147
setIsLoading(false);
148-
149-
// Final safety check: if no recent active runs found, ensure status is idle
150-
if (agentRunsCheckedRef.current) {
151-
const thirtySecondsAgo = new Date(Date.now() - 30 * 1000);
152-
const hasRecentActiveRun = agentRunsQuery.data.find((run) => {
153-
const runCreatedAt = new Date(run.started_at || 0);
154-
return run.status === 'running' && runCreatedAt > thirtySecondsAgo;
155-
});
156-
157-
if (!hasRecentActiveRun) {
158-
console.log('[PAGE] Final check: No recent active runs, ensuring idle status');
159-
if (isMounted) {
160-
setAgentStatus('idle');
161-
setAgentRunId(null);
162-
}
163-
}
164-
}
148+
// Removed time-based final check to avoid incorrectly forcing idle while a stream is active
165149
}
166150

167151
} catch (err) {

frontend/src/hooks/useAgentStream.ts

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,19 @@ export function useAgentStream(
116116
textContentRef.current = textContent;
117117
}, [textContent]);
118118

119-
// Update refs if threadId changes (no persistence across navigation)
119+
// On thread change, ensure any existing stream is cleaned up to avoid stale subscriptions
120120
useEffect(() => {
121+
const previousThreadId = threadIdRef.current;
122+
if (previousThreadId && previousThreadId !== threadId && streamCleanupRef.current) {
123+
// Close the existing stream for the previous thread
124+
streamCleanupRef.current();
125+
streamCleanupRef.current = null;
126+
setStatus('idle');
127+
setTextContent([]);
128+
setToolCall(null);
129+
setAgentRunId(null);
130+
currentRunIdRef.current = null;
131+
}
121132
threadIdRef.current = threadId;
122133
}, [threadId]);
123134

@@ -547,12 +558,38 @@ export function useAgentStream(
547558
`[useAgentStream] Agent run ${runId} confirmed running. Setting up EventSource.`,
548559
);
549560
const cleanup = streamAgent(runId, {
550-
onMessage: handleStreamMessage,
551-
onError: handleStreamError,
552-
onClose: handleStreamClose,
561+
onMessage: (data) => {
562+
// Ignore messages if threadId changed while the EventSource stayed open
563+
if (threadIdRef.current !== threadId) return;
564+
handleStreamMessage(data);
565+
},
566+
onError: (err) => {
567+
if (threadIdRef.current !== threadId) return;
568+
handleStreamError(err);
569+
},
570+
onClose: () => {
571+
if (threadIdRef.current !== threadId) return;
572+
handleStreamClose();
573+
},
553574
});
554575
streamCleanupRef.current = cleanup;
555576
// Status will be updated to 'streaming' by the first message received in handleStreamMessage
577+
// If for some reason no message arrives shortly, verify liveness again to avoid zombie state
578+
setTimeout(async () => {
579+
if (!isMountedRef.current) return;
580+
if (currentRunIdRef.current !== runId) return; // Another run started
581+
if (statusRef.current === 'streaming') return; // Already streaming
582+
try {
583+
const latest = await getAgentStatus(runId);
584+
if (!isMountedRef.current) return;
585+
if (currentRunIdRef.current !== runId) return;
586+
if (latest.status !== 'running') {
587+
finalizeStream(mapAgentStatus(latest.status) || 'agent_not_running', runId);
588+
}
589+
} catch {
590+
// ignore
591+
}
592+
}, 1500);
556593
} catch (err) {
557594
if (!isMountedRef.current) return; // Check mount status after async call
558595

0 commit comments

Comments
 (0)