diff --git a/ui/desktop/src/components/pair.tsx b/ui/desktop/src/components/pair.tsx index 0aa7353ab55a..45504c096d19 100644 --- a/ui/desktop/src/components/pair.tsx +++ b/ui/desktop/src/components/pair.tsx @@ -62,12 +62,58 @@ export default function Pair({ return prev; }); } catch (error) { - console.log(error); - setFatalError(`Agent init failure: ${error instanceof Error ? error.message : '' + error}`); + console.error('Agent initialization failed:', error); + + // Clear deleted session from URL and retry + if ( + error instanceof Error && + (error.message.includes('Session not found') || error.message.includes('404')) + ) { + console.log('Clearing invalid session ID from URL'); + setSearchParams((prev) => { + prev.delete('resumeSessionId'); + return prev; + }); + + try { + const chat = await loadCurrentChat({ + setAgentWaitingMessage, + }); + setChat(chat); + setSearchParams((prev) => { + prev.set('resumeSessionId', chat.sessionId); + return prev; + }); + } catch (retryError) { + handleInitializationError(retryError); + } + } else { + handleInitializationError(error); + } } finally { setLoadingChat(false); } }; + + const handleInitializationError = (error: unknown) => { + let errorMessage = 'Unknown error occurred'; + if (error) { + if (error instanceof Error) { + errorMessage = error.message; + } else if (typeof error === 'object' && error !== null) { + // Handle case where error is an object with properties + try { + errorMessage = JSON.stringify(error); + } catch { + errorMessage = Object.prototype.toString.call(error); + } + } else { + errorMessage = String(error); + } + } + setFatalError(`Agent init failure: ${errorMessage}`); + }; + initializeFromState(); }, [ agentState, diff --git a/ui/desktop/src/hooks/useAgent.ts b/ui/desktop/src/hooks/useAgent.ts index 136554ab4cee..37fea45d973c 100644 --- a/ui/desktop/src/hooks/useAgent.ts +++ b/ui/desktop/src/hooks/useAgent.ts @@ -47,6 +47,7 @@ export function useAgent(): UseAgentReturn { const [agentState, setAgentState] = useState(AgentState.UNINITIALIZED); const [sessionId, setSessionId] = useState(null); const initPromiseRef = useRef | null>(null); + const deletedSessionsRef = useRef>(new Set()); const recipeIdFromConfig = useRef( (window.appConfig.get('recipeId') as string | null | undefined) ?? null ); @@ -64,30 +65,65 @@ export function useAgent(): UseAgentReturn { recipeIdFromConfig.current = null; recipeDeeplinkFromConfig.current = null; scheduledJobIdFromConfig.current = null; + deletedSessionsRef.current.clear(); }, []); const agentIsInitialized = agentState === AgentState.INITIALIZED; const currentChat = useCallback( async (initContext: InitializationContext): Promise => { - if (agentIsInitialized && sessionId) { - const agentResponse = await resumeAgent({ - body: { - session_id: sessionId, - load_model_and_extensions: false, - }, - throwOnError: true, - }); - - const agentSession = agentResponse.data; - const messages = agentSession.conversation || []; - return { - sessionId: agentSession.id, - name: agentSession.recipe?.title || agentSession.name, - messageHistoryIndex: 0, - messages, - recipe: agentSession.recipe, - recipeParameterValues: agentSession.user_recipe_values || null, - }; + // Skip deleted sessions + if ( + initContext.resumeSessionId && + deletedSessionsRef.current.has(initContext.resumeSessionId) + ) { + initContext.resumeSessionId = undefined; + + // Clear from URL + const url = new URL(window.location.href); + url.searchParams.delete('resumeSessionId'); + window.history.replaceState({}, '', url.toString()); + } + + if (sessionId && deletedSessionsRef.current.has(sessionId)) { + setSessionId(null); + } + + if (agentIsInitialized && sessionId && !deletedSessionsRef.current.has(sessionId)) { + let agentResponse; + try { + agentResponse = await resumeAgent({ + body: { + session_id: sessionId, + load_model_and_extensions: false, + }, + throwOnError: true, + }); + } catch { + // Mark session as deleted and clear state + deletedSessionsRef.current.add(sessionId); + setSessionId(null); + + // Clear from URL + const url = new URL(window.location.href); + if (url.searchParams.get('resumeSessionId')) { + url.searchParams.delete('resumeSessionId'); + window.history.replaceState({}, '', url.toString()); + } + } + + // Fall through to create new session + if (agentResponse?.data) { + const agentSession = agentResponse.data; + const messages = agentSession.conversation || []; + return { + sessionId: agentSession.id, + name: agentSession.recipe?.title || agentSession.name, + messageHistoryIndex: 0, + messages, + recipe: agentSession.recipe, + recipeParameterValues: agentSession.user_recipe_values || null, + }; + } } if (initPromiseRef.current) { @@ -109,15 +145,38 @@ export function useAgent(): UseAgentReturn { throw new NoProviderOrModelError(); } - const agentResponse = initContext.resumeSessionId - ? await resumeAgent({ - body: { - session_id: initContext.resumeSessionId, - load_model_and_extensions: false, - }, - throwOnError: true, - }) - : await startAgent({ + let agentResponse; + try { + agentResponse = initContext.resumeSessionId + ? await resumeAgent({ + body: { + session_id: initContext.resumeSessionId, + load_model_and_extensions: false, + }, + throwOnError: true, + }) + : await startAgent({ + body: { + working_dir: window.appConfig.get('GOOSE_WORKING_DIR') as string, + ...buildRecipeInput( + initContext.recipe, + recipeIdFromConfig.current, + recipeDeeplinkFromConfig.current + ), + }, + throwOnError: true, + }); + } catch (error) { + // If resuming fails, mark session as deleted and create new agent + if (initContext.resumeSessionId) { + deletedSessionsRef.current.add(initContext.resumeSessionId); + + // Clear from URL + const url = new URL(window.location.href); + url.searchParams.delete('resumeSessionId'); + window.history.replaceState({}, '', url.toString()); + + agentResponse = await startAgent({ body: { working_dir: window.appConfig.get('GOOSE_WORKING_DIR') as string, ...buildRecipeInput( @@ -129,6 +188,13 @@ export function useAgent(): UseAgentReturn { throwOnError: true, }); + // Clear resume flag + initContext.resumeSessionId = undefined; + } else { + throw error; + } + } + const agentSession = agentResponse.data; if (!agentSession) { throw Error('Failed to get session info');