mirror of https://github.com/kortix-ai/suna.git
frontend(thread): avoid streaming for inactive agent runs and quiet expected not-running/404 cases\n\n- Thread page: only auto-start streaming on load when agentStatus === 'running'\n- useAgentStream: pre-check status; finalize quietly for not-running/not-found; suppress toasts for expected cases\n- Reduce noisy logs/toasts when opening historical threads
This commit is contained in:
parent
dad49f8a50
commit
74b89f4020
|
@ -19,15 +19,31 @@ import { isLocalMode } from '@/lib/config';
|
|||
import { ThreadContent } from '@/components/thread/content/ThreadContent';
|
||||
import { ThreadSkeleton } from '@/components/thread/content/ThreadSkeleton';
|
||||
import { useAddUserMessageMutation } from '@/hooks/react-query/threads/use-messages';
|
||||
import { useStartAgentMutation, useStopAgentMutation } from '@/hooks/react-query/threads/use-agent-run';
|
||||
import {
|
||||
useStartAgentMutation,
|
||||
useStopAgentMutation,
|
||||
} from '@/hooks/react-query/threads/use-agent-run';
|
||||
import { useSharedSubscription } from '@/contexts/SubscriptionContext';
|
||||
import { SubscriptionStatus } from '@/components/thread/chat-input/_use-model-selection';
|
||||
|
||||
import { UnifiedMessage, ApiMessageType, ToolCallInput, Project } from '../_types';
|
||||
import { useThreadData, useToolCalls, useBilling, useKeyboardShortcuts } from '../_hooks';
|
||||
import {
|
||||
UnifiedMessage,
|
||||
ApiMessageType,
|
||||
ToolCallInput,
|
||||
Project,
|
||||
} from '../_types';
|
||||
import {
|
||||
useThreadData,
|
||||
useToolCalls,
|
||||
useBilling,
|
||||
useKeyboardShortcuts,
|
||||
} from '../_hooks';
|
||||
import { ThreadError, UpgradeDialog, ThreadLayout } from '../_components';
|
||||
|
||||
import { useThreadAgent, useAgents } from '@/hooks/react-query/agents/use-agents';
|
||||
import {
|
||||
useThreadAgent,
|
||||
useAgents,
|
||||
} from '@/hooks/react-query/agents/use-agents';
|
||||
import { AgentRunLimitDialog } from '@/components/thread/agent-run-limit-dialog';
|
||||
import { useAgentSelection } from '@/lib/stores/agent-selection-store';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
@ -53,17 +69,20 @@ export default function ThreadPage({
|
|||
const [isSending, setIsSending] = useState(false);
|
||||
const [fileViewerOpen, setFileViewerOpen] = useState(false);
|
||||
const [fileToView, setFileToView] = useState<string | null>(null);
|
||||
const [filePathList, setFilePathList] = useState<string[] | undefined>(undefined);
|
||||
const [filePathList, setFilePathList] = useState<string[] | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [showUpgradeDialog, setShowUpgradeDialog] = useState(false);
|
||||
const [debugMode, setDebugMode] = useState(false);
|
||||
const [initialPanelOpenAttempted, setInitialPanelOpenAttempted] = useState(false);
|
||||
const [initialPanelOpenAttempted, setInitialPanelOpenAttempted] =
|
||||
useState(false);
|
||||
// Use Zustand store for agent selection persistence
|
||||
const {
|
||||
selectedAgentId,
|
||||
setSelectedAgent,
|
||||
initializeFromAgents,
|
||||
getCurrentAgent,
|
||||
isSunaAgent
|
||||
isSunaAgent,
|
||||
} = useAgentSelection();
|
||||
|
||||
const { data: agentsResponse } = useAgents();
|
||||
|
@ -77,7 +96,6 @@ export default function ThreadPage({
|
|||
runningThreadIds: string[];
|
||||
} | null>(null);
|
||||
|
||||
|
||||
// Refs - simplified for flex-column-reverse
|
||||
const latestMessageRef = useRef<HTMLDivElement>(null);
|
||||
const initialLayoutAppliedRef = useRef(false);
|
||||
|
@ -165,12 +183,13 @@ export default function ThreadPage({
|
|||
}, [threadAgentData, agents, initializeFromAgents]);
|
||||
|
||||
const { data: subscriptionData } = useSharedSubscription();
|
||||
const subscriptionStatus: SubscriptionStatus = (subscriptionData?.status === 'active' || subscriptionData?.status === 'trialing')
|
||||
const subscriptionStatus: SubscriptionStatus =
|
||||
subscriptionData?.status === 'active' ||
|
||||
subscriptionData?.status === 'trialing'
|
||||
? 'active'
|
||||
: 'no_subscription';
|
||||
|
||||
const handleProjectRenamed = useCallback((newName: string) => {
|
||||
}, []);
|
||||
const handleProjectRenamed = useCallback((newName: string) => {}, []);
|
||||
|
||||
// scrollToBottom for flex-column-reverse layout
|
||||
const scrollToBottom = useCallback(() => {
|
||||
|
@ -179,7 +198,8 @@ export default function ThreadPage({
|
|||
}
|
||||
}, []);
|
||||
|
||||
const handleNewMessageFromStream = useCallback((message: UnifiedMessage) => {
|
||||
const handleNewMessageFromStream = useCallback(
|
||||
(message: UnifiedMessage) => {
|
||||
if (!message.message_id) {
|
||||
console.warn(
|
||||
`[STREAM HANDLER] Received message is missing ID: Type=${message.type}`,
|
||||
|
@ -197,15 +217,16 @@ export default function ThreadPage({
|
|||
} else {
|
||||
// If this is a user message, replace any optimistic user message with temp ID
|
||||
if (message.type === 'user') {
|
||||
const optimisticIndex = prev.findIndex(m =>
|
||||
const optimisticIndex = prev.findIndex(
|
||||
(m) =>
|
||||
m.type === 'user' &&
|
||||
m.message_id?.startsWith('temp-') &&
|
||||
m.content === message.content
|
||||
m.content === message.content,
|
||||
);
|
||||
if (optimisticIndex !== -1) {
|
||||
// Replace the optimistic message with the real one
|
||||
return prev.map((m, index) =>
|
||||
index === optimisticIndex ? message : m
|
||||
index === optimisticIndex ? message : m,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -216,9 +237,12 @@ export default function ThreadPage({
|
|||
if (message.type === 'tool') {
|
||||
setAutoOpenedPanel(false);
|
||||
}
|
||||
}, [setMessages, setAutoOpenedPanel]);
|
||||
},
|
||||
[setMessages, setAutoOpenedPanel],
|
||||
);
|
||||
|
||||
const handleStreamStatusChange = useCallback((hookStatus: string) => {
|
||||
const handleStreamStatusChange = useCallback(
|
||||
(hookStatus: string) => {
|
||||
switch (hookStatus) {
|
||||
case 'idle':
|
||||
case 'completed':
|
||||
|
@ -239,20 +263,26 @@ export default function ThreadPage({
|
|||
setAgentStatus('running');
|
||||
break;
|
||||
}
|
||||
}, [setAgentStatus, setAgentRunId, setAutoOpenedPanel]);
|
||||
},
|
||||
[setAgentStatus, setAgentRunId, setAutoOpenedPanel],
|
||||
);
|
||||
|
||||
const handleStreamError = useCallback((errorMessage: string) => {
|
||||
console.error(`[PAGE] Stream hook error: ${errorMessage}`);
|
||||
if (
|
||||
!errorMessage.toLowerCase().includes('not found') &&
|
||||
!errorMessage.toLowerCase().includes('agent run is not running')
|
||||
) {
|
||||
toast.error(`Stream Error: ${errorMessage}`);
|
||||
const lower = errorMessage.toLowerCase();
|
||||
const isExpected =
|
||||
lower.includes('not found') || lower.includes('agent run is not running');
|
||||
|
||||
// Downgrade log level for expected/benign cases (opening old conversations)
|
||||
if (isExpected) {
|
||||
console.info(`[PAGE] Stream skipped for inactive run: ${errorMessage}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(`[PAGE] Stream hook error: ${errorMessage}`);
|
||||
toast.error(`Stream Error: ${errorMessage}`);
|
||||
}, []);
|
||||
|
||||
const handleStreamClose = useCallback(() => {
|
||||
}, []);
|
||||
const handleStreamClose = useCallback(() => {}, []);
|
||||
|
||||
const {
|
||||
status: streamHookStatus,
|
||||
|
@ -299,39 +329,50 @@ export default function ThreadPage({
|
|||
try {
|
||||
const messagePromise = addUserMessageMutation.mutateAsync({
|
||||
threadId,
|
||||
message
|
||||
message,
|
||||
});
|
||||
|
||||
const agentPromise = startAgentMutation.mutateAsync({
|
||||
threadId,
|
||||
options: {
|
||||
...options,
|
||||
agent_id: selectedAgentId
|
||||
}
|
||||
agent_id: selectedAgentId,
|
||||
},
|
||||
});
|
||||
|
||||
const results = await Promise.allSettled([messagePromise, agentPromise]);
|
||||
const results = await Promise.allSettled([
|
||||
messagePromise,
|
||||
agentPromise,
|
||||
]);
|
||||
|
||||
if (results[0].status === 'rejected') {
|
||||
const reason = results[0].reason;
|
||||
console.error("Failed to send message:", reason);
|
||||
throw new Error(`Failed to send message: ${reason?.message || reason}`);
|
||||
console.error('Failed to send message:', reason);
|
||||
throw new Error(
|
||||
`Failed to send message: ${reason?.message || reason}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (results[1].status === 'rejected') {
|
||||
const error = results[1].reason;
|
||||
console.error("Failed to start agent:", error);
|
||||
console.error('Failed to start agent:', error);
|
||||
|
||||
if (error instanceof BillingError) {
|
||||
setBillingData({
|
||||
currentUsage: error.detail.currentUsage as number | undefined,
|
||||
limit: error.detail.limit as number | undefined,
|
||||
message: error.detail.message || 'Monthly usage limit reached. Please upgrade.',
|
||||
accountId: null
|
||||
message:
|
||||
error.detail.message ||
|
||||
'Monthly usage limit reached. Please upgrade.',
|
||||
accountId: null,
|
||||
});
|
||||
setShowBillingAlert(true);
|
||||
|
||||
setMessages(prev => prev.filter(m => m.message_id !== optimisticUserMessage.message_id));
|
||||
setMessages((prev) =>
|
||||
prev.filter(
|
||||
(m) => m.message_id !== optimisticUserMessage.message_id,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -344,7 +385,11 @@ export default function ThreadPage({
|
|||
});
|
||||
setShowAgentLimitDialog(true);
|
||||
|
||||
setMessages(prev => prev.filter(m => m.message_id !== optimisticUserMessage.message_id));
|
||||
setMessages((prev) =>
|
||||
prev.filter(
|
||||
(m) => m.message_id !== optimisticUserMessage.message_id,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -354,10 +399,12 @@ export default function ThreadPage({
|
|||
const agentResult = results[1].value;
|
||||
setUserInitiatedRun(true);
|
||||
setAgentRunId(agentResult.agent_run_id);
|
||||
|
||||
} catch (err) {
|
||||
console.error('Error sending message or starting agent:', err);
|
||||
if (!(err instanceof BillingError) && !(err instanceof AgentRunLimitError)) {
|
||||
if (
|
||||
!(err instanceof BillingError) &&
|
||||
!(err instanceof AgentRunLimitError)
|
||||
) {
|
||||
toast.error(err instanceof Error ? err.message : 'Operation failed');
|
||||
}
|
||||
setMessages((prev) =>
|
||||
|
@ -367,7 +414,16 @@ export default function ThreadPage({
|
|||
setIsSending(false);
|
||||
}
|
||||
},
|
||||
[threadId, project?.account_id, addUserMessageMutation, startAgentMutation, setMessages, setBillingData, setShowBillingAlert, setAgentRunId],
|
||||
[
|
||||
threadId,
|
||||
project?.account_id,
|
||||
addUserMessageMutation,
|
||||
startAgentMutation,
|
||||
setMessages,
|
||||
setBillingData,
|
||||
setShowBillingAlert,
|
||||
setAgentRunId,
|
||||
],
|
||||
);
|
||||
|
||||
const handleStopAgent = useCallback(async () => {
|
||||
|
@ -384,7 +440,8 @@ export default function ThreadPage({
|
|||
}
|
||||
}, [stopStreaming, agentRunId, stopAgentMutation, setAgentStatus]);
|
||||
|
||||
const handleOpenFileViewer = useCallback((filePath?: string, filePathList?: string[]) => {
|
||||
const handleOpenFileViewer = useCallback(
|
||||
(filePath?: string, filePathList?: string[]) => {
|
||||
if (filePath) {
|
||||
setFileToView(filePath);
|
||||
} else {
|
||||
|
@ -392,7 +449,9 @@ export default function ThreadPage({
|
|||
}
|
||||
setFilePathList(filePathList);
|
||||
setFileViewerOpen(true);
|
||||
}, []);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const toolViewAssistant = useCallback(
|
||||
(assistantContent?: string, toolContent?: string) => {
|
||||
|
@ -404,7 +463,9 @@ export default function ThreadPage({
|
|||
Assistant Message
|
||||
</div>
|
||||
<div className="rounded-md border bg-muted/50 p-3">
|
||||
<div className="text-xs prose prose-xs dark:prose-invert chat-markdown max-w-none">{assistantContent}</div>
|
||||
<div className="text-xs prose prose-xs dark:prose-invert chat-markdown max-w-none">
|
||||
{assistantContent}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -423,7 +484,8 @@ export default function ThreadPage({
|
|||
Tool Result
|
||||
</div>
|
||||
<div
|
||||
className={`px-2 py-0.5 rounded-full text-xs ${isSuccess
|
||||
className={`px-2 py-0.5 rounded-full text-xs ${
|
||||
isSuccess
|
||||
? 'bg-green-50 text-green-700 dark:bg-green-900 dark:text-green-300'
|
||||
: 'bg-red-50 text-red-700 dark:bg-red-900 dark:text-red-300'
|
||||
}`}
|
||||
|
@ -432,7 +494,9 @@ export default function ThreadPage({
|
|||
</div>
|
||||
</div>
|
||||
<div className="rounded-md border bg-muted/50 p-3">
|
||||
<div className="text-xs prose prose-xs dark:prose-invert chat-markdown max-w-none">{toolContent}</div>
|
||||
<div className="text-xs prose prose-xs dark:prose-invert chat-markdown max-w-none">
|
||||
{toolContent}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -464,28 +528,55 @@ export default function ThreadPage({
|
|||
}
|
||||
}
|
||||
}
|
||||
}, [initialPanelOpenAttempted, messages, toolCalls, initialLoadCompleted, setIsSidePanelOpen, setCurrentToolIndex, isMobile]);
|
||||
}, [
|
||||
initialPanelOpenAttempted,
|
||||
messages,
|
||||
toolCalls,
|
||||
initialLoadCompleted,
|
||||
setIsSidePanelOpen,
|
||||
setCurrentToolIndex,
|
||||
isMobile,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
// Start streaming if user initiated a run (don't wait for initialLoadCompleted for first-time users)
|
||||
if (agentRunId && agentRunId !== currentHookRunId && userInitiatedRun) {
|
||||
startStreaming(agentRunId);
|
||||
setUserInitiatedRun(false); // Reset flag after starting
|
||||
return;
|
||||
}
|
||||
// Also start streaming if this is from page load with recent active runs
|
||||
else if (agentRunId && agentRunId !== currentHookRunId && initialLoadCompleted && !userInitiatedRun) {
|
||||
|
||||
// Only auto-start streaming on page load if we know the agent is currently running
|
||||
if (
|
||||
agentRunId &&
|
||||
agentRunId !== currentHookRunId &&
|
||||
initialLoadCompleted &&
|
||||
!userInitiatedRun &&
|
||||
agentStatus === 'running'
|
||||
) {
|
||||
startStreaming(agentRunId);
|
||||
}
|
||||
}, [agentRunId, startStreaming, currentHookRunId, initialLoadCompleted, userInitiatedRun]);
|
||||
}, [
|
||||
agentRunId,
|
||||
startStreaming,
|
||||
currentHookRunId,
|
||||
initialLoadCompleted,
|
||||
userInitiatedRun,
|
||||
agentStatus,
|
||||
]);
|
||||
|
||||
// No auto-scroll needed with flex-column-reverse
|
||||
|
||||
// No intersection observer needed with flex-column-reverse
|
||||
|
||||
useEffect(() => {
|
||||
if ((streamHookStatus === 'completed' || streamHookStatus === 'stopped' ||
|
||||
streamHookStatus === 'agent_not_running' || streamHookStatus === 'error') &&
|
||||
(agentStatus === 'running' || agentStatus === 'connecting')) {
|
||||
if (
|
||||
(streamHookStatus === 'completed' ||
|
||||
streamHookStatus === 'stopped' ||
|
||||
streamHookStatus === 'agent_not_running' ||
|
||||
streamHookStatus === 'error') &&
|
||||
(agentStatus === 'running' || agentStatus === 'connecting')
|
||||
) {
|
||||
setAgentStatus('idle');
|
||||
setAgentRunId(null);
|
||||
}
|
||||
|
@ -531,9 +622,15 @@ export default function ThreadPage({
|
|||
const hasCheckedUpgradeDialog = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialLoadCompleted && subscriptionData && !hasCheckedUpgradeDialog.current) {
|
||||
if (
|
||||
initialLoadCompleted &&
|
||||
subscriptionData &&
|
||||
!hasCheckedUpgradeDialog.current
|
||||
) {
|
||||
hasCheckedUpgradeDialog.current = true;
|
||||
const hasSeenUpgradeDialog = localStorage.getItem('suna_upgrade_dialog_displayed');
|
||||
const hasSeenUpgradeDialog = localStorage.getItem(
|
||||
'suna_upgrade_dialog_displayed',
|
||||
);
|
||||
const isFreeTier = subscriptionStatus === 'no_subscription';
|
||||
if (!hasSeenUpgradeDialog && isFreeTier && !isLocalMode()) {
|
||||
setShowUpgradeDialog(true);
|
||||
|
@ -576,7 +673,9 @@ export default function ThreadPage({
|
|||
|
||||
const scrollContainer = scrollContainerRef.current;
|
||||
if (scrollContainer) {
|
||||
scrollContainer.addEventListener('scroll', handleScroll, { passive: true });
|
||||
scrollContainer.addEventListener('scroll', handleScroll, {
|
||||
passive: true,
|
||||
});
|
||||
// Check initial state
|
||||
setTimeout(() => handleScroll(), 100);
|
||||
|
||||
|
@ -696,27 +795,36 @@ export default function ThreadPage({
|
|||
scrollContainerRef={scrollContainerRef}
|
||||
/>
|
||||
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"fixed bottom-0 z-10 bg-gradient-to-t from-background via-background/90 to-transparent px-4 pt-8",
|
||||
isSidePanelAnimating ? "" : "transition-all duration-200 ease-in-out",
|
||||
leftSidebarState === 'expanded' ? 'left-[72px] md:left-[256px]' : 'left-[40px]',
|
||||
isSidePanelOpen && !isMobile ? 'right-[90%] sm:right-[450px] md:right-[500px] lg:right-[550px] xl:right-[650px]' : 'right-0',
|
||||
isMobile ? 'left-0 right-0' : ''
|
||||
)}>
|
||||
<div className={cn(
|
||||
"mx-auto",
|
||||
isMobile ? "w-full" : "max-w-3xl"
|
||||
)}>
|
||||
'fixed bottom-0 z-10 bg-gradient-to-t from-background via-background/90 to-transparent px-4 pt-8',
|
||||
isSidePanelAnimating
|
||||
? ''
|
||||
: 'transition-all duration-200 ease-in-out',
|
||||
leftSidebarState === 'expanded'
|
||||
? 'left-[72px] md:left-[256px]'
|
||||
: 'left-[40px]',
|
||||
isSidePanelOpen && !isMobile
|
||||
? 'right-[90%] sm:right-[450px] md:right-[500px] lg:right-[550px] xl:right-[650px]'
|
||||
: 'right-0',
|
||||
isMobile ? 'left-0 right-0' : '',
|
||||
)}
|
||||
>
|
||||
<div className={cn('mx-auto', isMobile ? 'w-full' : 'max-w-3xl')}>
|
||||
<ChatInput
|
||||
value={newMessage}
|
||||
onChange={setNewMessage}
|
||||
onSubmit={handleSubmitMessage}
|
||||
placeholder={`Describe what you need help with...`}
|
||||
loading={isSending}
|
||||
disabled={isSending || agentStatus === 'running' || agentStatus === 'connecting'}
|
||||
isAgentRunning={agentStatus === 'running' || agentStatus === 'connecting'}
|
||||
disabled={
|
||||
isSending ||
|
||||
agentStatus === 'running' ||
|
||||
agentStatus === 'connecting'
|
||||
}
|
||||
isAgentRunning={
|
||||
agentStatus === 'running' || agentStatus === 'connecting'
|
||||
}
|
||||
onStopAgent={handleStopAgent}
|
||||
autoFocus={!isLoading}
|
||||
onFileBrowse={handleOpenFileViewer}
|
||||
|
|
|
@ -127,7 +127,11 @@ export function useAgentStream(
|
|||
// On thread change, ensure any existing stream is cleaned up to avoid stale subscriptions
|
||||
useEffect(() => {
|
||||
const previousThreadId = threadIdRef.current;
|
||||
if (previousThreadId && previousThreadId !== threadId && streamCleanupRef.current) {
|
||||
if (
|
||||
previousThreadId &&
|
||||
previousThreadId !== threadId &&
|
||||
streamCleanupRef.current
|
||||
) {
|
||||
// Close the existing stream for the previous thread
|
||||
streamCleanupRef.current();
|
||||
streamCleanupRef.current = null;
|
||||
|
@ -188,14 +192,22 @@ export function useAgentStream(
|
|||
(finalStatus: string, runId: string | null = agentRunId) => {
|
||||
if (!isMountedRef.current) return;
|
||||
|
||||
console.log(`[useAgentStream] Finalizing stream with status: ${finalStatus}, runId: ${runId}`);
|
||||
console.log(
|
||||
`[useAgentStream] Finalizing stream with status: ${finalStatus}, runId: ${runId}`,
|
||||
);
|
||||
|
||||
const currentThreadId = threadIdRef.current; // Get current threadId from ref
|
||||
const currentSetMessages = setMessagesRef.current; // Get current setMessages from ref
|
||||
|
||||
// Only finalize if this is for the current run ID or if no specific run ID is provided
|
||||
if (runId && currentRunIdRef.current && currentRunIdRef.current !== runId) {
|
||||
console.log(`[useAgentStream] Ignoring finalization for old run ID ${runId}, current is ${currentRunIdRef.current}`);
|
||||
if (
|
||||
runId &&
|
||||
currentRunIdRef.current &&
|
||||
currentRunIdRef.current !== runId
|
||||
) {
|
||||
console.log(
|
||||
`[useAgentStream] Ignoring finalization for old run ID ${runId}, current is ${currentRunIdRef.current}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -219,17 +231,27 @@ export function useAgentStream(
|
|||
queryClient.invalidateQueries({ queryKey: agentKeys.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: ['agent-tools', agentId] });
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ['custom-mcp-tools', agentId] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['custom-mcp-tools', agentId],
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: composioKeys.mcpServers() });
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: composioKeys.profiles.all() });
|
||||
queryClient.invalidateQueries({ queryKey: composioKeys.profiles.credentials() });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: composioKeys.profiles.all(),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: composioKeys.profiles.credentials(),
|
||||
});
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: ['triggers', agentId] });
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: workflowKeys.agent(agentId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: workflowKeys.agent(agentId),
|
||||
});
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: knowledgeBaseKeys.agent(agentId) });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: knowledgeBaseKeys.agent(agentId),
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
|
@ -238,8 +260,7 @@ export function useAgentStream(
|
|||
finalStatus === 'stopped' ||
|
||||
finalStatus === 'agent_not_running')
|
||||
) {
|
||||
getAgentStatus(runId).catch((err) => {
|
||||
});
|
||||
getAgentStatus(runId).catch((err) => {});
|
||||
}
|
||||
},
|
||||
[agentRunId, updateStatus, agentId, queryClient],
|
||||
|
@ -278,7 +299,10 @@ export function useAgentStream(
|
|||
try {
|
||||
const jsonData = JSON.parse(processedData);
|
||||
if (jsonData.status === 'error') {
|
||||
console.error('[useAgentStream] Received error status message:', jsonData);
|
||||
console.error(
|
||||
'[useAgentStream] Received error status message:',
|
||||
jsonData,
|
||||
);
|
||||
const errorMessage = jsonData.message || 'Unknown error occurred';
|
||||
setError(errorMessage);
|
||||
toast.error(errorMessage, { duration: 15000 });
|
||||
|
@ -290,7 +314,10 @@ export function useAgentStream(
|
|||
}
|
||||
|
||||
// --- Process JSON messages ---
|
||||
const message = safeJsonParse(processedData, null) as UnifiedMessage | null;
|
||||
const message = safeJsonParse(
|
||||
processedData,
|
||||
null,
|
||||
) as UnifiedMessage | null;
|
||||
if (!message) {
|
||||
console.warn(
|
||||
'[useAgentStream] Failed to parse streamed message:',
|
||||
|
@ -408,11 +435,18 @@ export function useAgentStream(
|
|||
errorMessage = 'Stream connection error';
|
||||
}
|
||||
|
||||
const lower = errorMessage.toLowerCase();
|
||||
const isExpected =
|
||||
lower.includes('not found') || lower.includes('not running');
|
||||
|
||||
if (isExpected) {
|
||||
console.info('[useAgentStream] Streaming skipped/ended:', errorMessage);
|
||||
} else {
|
||||
console.error('[useAgentStream] Streaming error:', errorMessage, err);
|
||||
setError(errorMessage);
|
||||
|
||||
// Show error toast with longer duration
|
||||
toast.error(errorMessage, { duration: 15000 });
|
||||
}
|
||||
|
||||
const runId = currentRunIdRef.current;
|
||||
if (!runId) {
|
||||
|
@ -422,7 +456,6 @@ export function useAgentStream(
|
|||
finalizeStream('error'); // Finalize with generic error if no runId
|
||||
return;
|
||||
}
|
||||
|
||||
},
|
||||
[finalizeStream],
|
||||
);
|
||||
|
@ -431,7 +464,9 @@ export function useAgentStream(
|
|||
if (!isMountedRef.current) return;
|
||||
|
||||
const runId = currentRunIdRef.current;
|
||||
console.log(`[useAgentStream] Stream closed for run ID: ${runId}, status: ${status}`);
|
||||
console.log(
|
||||
`[useAgentStream] Stream closed for run ID: ${runId}, status: ${status}`,
|
||||
);
|
||||
|
||||
if (!runId) {
|
||||
console.warn('[useAgentStream] Stream closed but no active agentRunId.');
|
||||
|
@ -460,11 +495,15 @@ export function useAgentStream(
|
|||
|
||||
// Check if this is still the current run ID
|
||||
if (currentRunIdRef.current !== runId) {
|
||||
console.log(`[useAgentStream] Run ID changed during status check in handleStreamClose, ignoring`);
|
||||
console.log(
|
||||
`[useAgentStream] Run ID changed during status check in handleStreamClose, ignoring`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[useAgentStream] Final status for run ID ${runId}: ${agentStatus.status}`);
|
||||
console.log(
|
||||
`[useAgentStream] Final status for run ID ${runId}: ${agentStatus.status}`,
|
||||
);
|
||||
|
||||
if (agentStatus.status === 'running') {
|
||||
setError('Stream closed unexpectedly while agent was running.');
|
||||
|
@ -481,7 +520,9 @@ export function useAgentStream(
|
|||
|
||||
// Check if this is still the current run ID
|
||||
if (currentRunIdRef.current !== runId) {
|
||||
console.log(`[useAgentStream] Run ID changed during error handling in handleStreamClose, ignoring`);
|
||||
console.log(
|
||||
`[useAgentStream] Run ID changed during error handling in handleStreamClose, ignoring`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -550,27 +591,31 @@ export function useAgentStream(
|
|||
|
||||
// Check if this is still the current run ID we're trying to start
|
||||
if (currentRunIdRef.current !== runId) {
|
||||
console.log(`[useAgentStream] Run ID changed during status check, aborting stream for ${runId}`);
|
||||
console.log(
|
||||
`[useAgentStream] Run ID changed during status check, aborting stream for ${runId}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (agentStatus.status !== 'running') {
|
||||
console.warn(
|
||||
`[useAgentStream] Agent run ${runId} is not in running state (status: ${agentStatus.status}). Cannot start stream.`,
|
||||
// Expected when opening an old conversation; don't surface as error/toast
|
||||
console.info(
|
||||
`[useAgentStream] Skip streaming for inactive run ${runId} (status: ${agentStatus.status}).`,
|
||||
);
|
||||
|
||||
// Only set error and finalize if this is still the current run ID
|
||||
if (currentRunIdRef.current === runId) {
|
||||
setError(`Agent run is not running (status: ${agentStatus.status})`);
|
||||
finalizeStream(
|
||||
mapAgentStatus(agentStatus.status) || 'agent_not_running',
|
||||
runId,
|
||||
);
|
||||
const final =
|
||||
agentStatus.status === 'completed' ||
|
||||
agentStatus.status === 'stopped'
|
||||
? mapAgentStatus(agentStatus.status)
|
||||
: 'agent_not_running';
|
||||
finalizeStream(final, runId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[useAgentStream] Agent run ${runId} is running, creating stream`);
|
||||
console.log(
|
||||
`[useAgentStream] Agent run ${runId} is running, creating stream`,
|
||||
);
|
||||
|
||||
// Agent is running, proceed to create the stream
|
||||
const cleanup = streamAgent(runId, {
|
||||
|
@ -593,7 +638,9 @@ export function useAgentStream(
|
|||
},
|
||||
});
|
||||
streamCleanupRef.current = cleanup;
|
||||
console.log(`[useAgentStream] Stream created successfully for run ID: ${runId}`);
|
||||
console.log(
|
||||
`[useAgentStream] Stream created successfully for run ID: ${runId}`,
|
||||
);
|
||||
|
||||
// Status will be updated to 'streaming' by the first message received in handleStreamMessage
|
||||
// If for some reason no message arrives shortly, verify liveness again to avoid zombie state
|
||||
|
@ -606,7 +653,10 @@ export function useAgentStream(
|
|||
if (!isMountedRef.current) return;
|
||||
if (currentRunIdRef.current !== runId) return;
|
||||
if (latest.status !== 'running') {
|
||||
finalizeStream(mapAgentStatus(latest.status) || 'agent_not_running', runId);
|
||||
finalizeStream(
|
||||
mapAgentStatus(latest.status) || 'agent_not_running',
|
||||
runId,
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
|
@ -617,22 +667,32 @@ export function useAgentStream(
|
|||
|
||||
// Only handle error if this is still the current run ID
|
||||
if (currentRunIdRef.current !== runId) {
|
||||
console.log(`[useAgentStream] Error occurred for old run ID ${runId}, ignoring`);
|
||||
console.log(
|
||||
`[useAgentStream] Error occurred for old run ID ${runId}, ignoring`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
const lower = errorMessage.toLowerCase();
|
||||
const isExpected =
|
||||
lower.includes('not found') ||
|
||||
lower.includes('404') ||
|
||||
lower.includes('does not exist') ||
|
||||
lower.includes('not running');
|
||||
|
||||
if (isExpected) {
|
||||
console.info(
|
||||
`[useAgentStream] Stream not started for ${runId}: ${errorMessage}`,
|
||||
);
|
||||
finalizeStream('agent_not_running', runId);
|
||||
} else {
|
||||
console.error(
|
||||
`[useAgentStream] Error initiating stream for ${runId}: ${errorMessage}`,
|
||||
);
|
||||
setError(errorMessage);
|
||||
|
||||
const isNotFoundError =
|
||||
errorMessage.includes('not found') ||
|
||||
errorMessage.includes('404') ||
|
||||
errorMessage.includes('does not exist');
|
||||
|
||||
finalizeStream(isNotFoundError ? 'agent_not_running' : 'error', runId);
|
||||
finalizeStream('error', runId);
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
|
|
Loading…
Reference in New Issue