diff --git a/frontend/src/pages/agent-detail/AgentDetailPage.tsx b/frontend/src/pages/agent-detail/AgentDetailPage.tsx index da9b03626..041be5b05 100644 --- a/frontend/src/pages/agent-detail/AgentDetailPage.tsx +++ b/frontend/src/pages/agent-detail/AgentDetailPage.tsx @@ -61,6 +61,7 @@ import ToolsTab from './tabs/ToolsTab'; import AgentDirectory from './AgentDirectory'; import { useAgentDetailRoute } from './hooks/useAgentDetailRoute'; import { + activeRunForSession, failClosedSessionActiveRun, mergeTerminalAssistantMessage, runtimeCompletionNeedsMessageRefresh, @@ -2386,6 +2387,7 @@ export default function AgentDetailPage() { const sessionActiveRunRef = useRef>({}); const runtimeEventCursorRef = useRef>({}); const [activeRun, setActiveRun] = useState(null); + const selectedSessionActiveRun = activeRunForSession(activeRun, activeSession?.id); const [reconcilingExecutionId, setReconcilingExecutionId] = useState(null); const [messagesLoadedRuntimeKey, setMessagesLoadedRuntimeKey] = useState(null); const [runtimeStateLoadedRuntimeKey, setRuntimeStateLoadedRuntimeKey] = useState(null); @@ -2968,8 +2970,8 @@ export default function AgentDetailPage() { || (lastChatMessage?.role === 'tool_call' && lastChatMessage.toolStatus === 'running'), ); const showDirectRunThinking = isWaiting || Boolean( - activeRun - && ['queued', 'running'].includes(activeRun.status) + selectedSessionActiveRun + && ['queued', 'running'].includes(selectedSessionActiveRun.status) && !isStreaming && !hasVisibleLiveProgress ); @@ -3701,24 +3703,31 @@ export default function AgentDetailPage() { }; const dispatchChatMessage = (socket: WebSocket, runtimeKey: SessionRuntimeKey, payload: PendingChatMessage) => { - setIsWaiting(true); - setIsStreaming(false); + const [runtimeAgentId, runtimeSessionId] = runtimeKey.split(':'); + const isActiveRuntime = ( + currentAgentIdRef.current === runtimeAgentId + && activeSessionIdRef.current === runtimeSessionId + ); setSessionUiState(runtimeKey, { isWaiting: true, isStreaming: false }); if (payload.resumeRunId) { const current = sessionActiveRunRef.current[runtimeKey]; if (current?.runId === payload.resumeRunId) { const next = { ...current, canResume: false }; sessionActiveRunRef.current[runtimeKey] = next; - setActiveRun(next); + if (isActiveRuntime) setActiveRun(next); } } - setChatMessages(prev => [...prev, parseChatMsg({ - role: 'user', - content: payload.userMsg, - fileName: payload.fileName, - imageUrl: payload.imageUrl, - timestamp: new Date().toISOString() - })]); + if (isActiveRuntime) { + setIsWaiting(true); + setIsStreaming(false); + setChatMessages(prev => [...prev, parseChatMsg({ + role: 'user', + content: payload.userMsg, + fileName: payload.fileName, + imageUrl: payload.imageUrl, + timestamp: new Date().toISOString() + })]); + } socket.send(JSON.stringify({ content: payload.contentForLLM, display_content: payload.userMsg, @@ -3727,7 +3736,6 @@ export default function AgentDetailPage() { ...(payload.resumeRunId ? { run_id: payload.resumeRunId } : {}), ...(payload.resumeCorrelationId ? { correlation_id: payload.resumeCorrelationId } : {}), })); - const [runtimeAgentId, runtimeSessionId] = runtimeKey.split(':'); window.setTimeout(() => { void fetchSessionRuntimeState(runtimeAgentId, runtimeSessionId); }, 250); @@ -3740,15 +3748,15 @@ export default function AgentDetailPage() { reconciliation: ToolReconciliation, outcome: 'applied' | 'not_applied', ) => { - if (!id || !activeSession?.id || !activeRun?.correlationId) return; - const correlationId = activeRun.correlationId; + if (!id || !activeSession?.id || !selectedSessionActiveRun?.correlationId) return; + const correlationId = selectedSessionActiveRun.correlationId; const applied = outcome === 'applied'; const confirmation = applied ? t('agent.chat.reconcileAppliedConfirm', '确认该操作已经生效,并且不得重复执行?') : t('agent.chat.reconcileNotAppliedConfirm', '确认该操作没有生效,可以让 Agent 重新决定是否重试?'); if (!window.confirm(confirmation)) return; - const run = activeRun; + const run = selectedSessionActiveRun; const sessionId = String(activeSession.id); const runtimeKey = buildSessionRuntimeKey(id, sessionId); setReconcilingExecutionId(reconciliation.executionId); @@ -3825,14 +3833,14 @@ export default function AgentDetailPage() { || !activeSession?.id || activeTab !== 'chat' || !isWritableSession(activeSession) - || !activeRun + || !selectedSessionActiveRun ) return; const sessionId = String(activeSession.id); const timer = window.setInterval(() => { void fetchSessionRuntimeState(id, sessionId); }, 1500); return () => window.clearInterval(timer); - }, [id, activeTab, activeSession?.id, activeRun?.runId, activeRun?.status]); + }, [id, activeTab, activeSession?.id, selectedSessionActiveRun?.runId, selectedSessionActiveRun?.status]); const handleWorkspacePathDeleted = useCallback((path: string) => { let removedName = ''; @@ -4722,7 +4730,7 @@ export default function AgentDetailPage() { messagesLoaded: messagesLoadedRuntimeKey === runtimeKey, runtimeStateLoaded: runtimeStateLoadedRuntimeKey === runtimeKey, messageCount: chatMessages.length, - hasActiveRun: activeRun !== null, + hasActiveRun: selectedSessionActiveRun !== null, })) return; const pairKey = onboardingKickoffKey(id, String(currentUser.id)); if (onboardingKickoffRef.current.has(pairKey)) return; @@ -4736,7 +4744,7 @@ export default function AgentDetailPage() { kind: 'onboarding_trigger', model_id: effectiveChatModelId, })); - }, [wsConnected, id, currentUser?.id, activeSession?.id, agent?.onboarded_for_me, llmModelsLoading, effectiveModelReady, effectiveChatModelId, chatMessages.length, messagesLoadedRuntimeKey, runtimeStateLoadedRuntimeKey, activeRun]); + }, [wsConnected, id, currentUser?.id, activeSession?.id, agent?.onboarded_for_me, llmModelsLoading, effectiveModelReady, effectiveChatModelId, chatMessages.length, messagesLoadedRuntimeKey, runtimeStateLoadedRuntimeKey, selectedSessionActiveRun]); const { data: permData } = useQuery({ queryKey: ['agent-permissions', id], @@ -7089,7 +7097,7 @@ export default function AgentDetailPage() { ) : null}
- {activeRun?.pendingToolReconciliations.map((reconciliation) => ( + {selectedSessionActiveRun?.pendingToolReconciliations.map((reconciliation) => (
@@ -7193,7 +7201,7 @@ export default function AgentDetailPage() {