From 2366935987edd09845d0809e900879114c88e351 Mon Sep 17 00:00:00 2001 From: Douwe Osinga Date: Wed, 29 Oct 2025 13:28:59 -0400 Subject: [PATCH 1/3] pipe errors into goose --- ui/desktop/src/components/hub.tsx | 25 +------ ui/desktop/src/components/pair.tsx | 4 +- .../sessions/SessionHistoryView.tsx | 10 +-- .../components/sessions/SessionsInsights.tsx | 6 +- .../settings/extensions/agent-api.ts | 6 ++ ui/desktop/src/hooks/useNavigation.ts | 2 + ui/desktop/src/sessions.ts | 53 +++++++++++---- ui/desktop/src/toasts.tsx | 67 ++++++++++--------- 8 files changed, 95 insertions(+), 78 deletions(-) diff --git a/ui/desktop/src/components/hub.tsx b/ui/desktop/src/components/hub.tsx index 4ae2e64978d9..8672d79cc8eb 100644 --- a/ui/desktop/src/components/hub.tsx +++ b/ui/desktop/src/components/hub.tsx @@ -19,7 +19,7 @@ import ChatInput from './ChatInput'; import { ChatState } from '../types/chatState'; import 'react-toastify/dist/ReactToastify.css'; import { View, ViewOptions } from '../utils/navigationUtils'; -import { startAgent } from '../api'; +import { startNewSession } from '../sessions'; export default function Hub({ setView, @@ -37,28 +37,7 @@ export default function Hub({ const combinedTextFromInput = customEvent.detail?.value || ''; if (combinedTextFromInput.trim()) { - if (process.env.ALPHA) { - const newAgent = await startAgent({ - body: { - working_dir: window.appConfig.get('GOOSE_WORKING_DIR') as string, - }, - throwOnError: true, - }); - const session = newAgent.data; - setView('pair', { - disableAnimation: true, - initialMessage: combinedTextFromInput, - resumeSessionId: session.id, - }); - } else { - // Navigate to pair page with the message to be submitted - // Pair will handle creating the new chat session - resetChat(); - setView('pair', { - disableAnimation: true, - initialMessage: combinedTextFromInput, - }); - } + await startNewSession(combinedTextFromInput, resetChat, setView); e.preventDefault(); } }; diff --git a/ui/desktop/src/components/pair.tsx b/ui/desktop/src/components/pair.tsx index 44ee659141fe..4b572eac99c9 100644 --- a/ui/desktop/src/components/pair.tsx +++ b/ui/desktop/src/components/pair.tsx @@ -1,5 +1,4 @@ import { useEffect, useState } from 'react'; -import { View, ViewOptions } from '../utils/navigationUtils'; import BaseChat from './BaseChat'; import { useRecipeManager } from '../hooks/useRecipeManager'; import { useIsMobile } from '../hooks/use-mobile'; @@ -10,6 +9,7 @@ import { cn } from '../utils'; import { ChatType } from '../types/chat'; import { useSearchParams } from 'react-router-dom'; +import { setViewType } from '../hooks/useNavigation'; export interface PairRouteState { resumeSessionId?: string; @@ -19,7 +19,7 @@ export interface PairRouteState { interface PairProps { chat: ChatType; setChat: (chat: ChatType) => void; - setView: (view: View, viewOptions?: ViewOptions) => void; + setView: setViewType; setIsGoosehintsModalOpen: (isOpen: boolean) => void; setFatalError: (value: ((prevState: string | null) => string | null) | string | null) => void; setAgentWaitingMessage: (msg: string | null) => void; diff --git a/ui/desktop/src/components/sessions/SessionHistoryView.tsx b/ui/desktop/src/components/sessions/SessionHistoryView.tsx index f68eca0410c4..48624ae33022 100644 --- a/ui/desktop/src/components/sessions/SessionHistoryView.tsx +++ b/ui/desktop/src/components/sessions/SessionHistoryView.tsx @@ -31,6 +31,7 @@ import { SearchView } from '../conversation/SearchView'; import BackButton from '../ui/BackButton'; import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/Tooltip'; import { Message, Session } from '../../api'; +import { useNavigation } from '../../hooks/useNavigation'; // Helper function to determine if a message is a user message (same as useChatEngine) const isUserMessage = (message: Message): boolean => { @@ -150,6 +151,8 @@ const SessionHistoryView: React.FC = ({ const messages = session.conversation || []; + const setView = useNavigation(); + useEffect(() => { const savedSessionConfig = localStorage.getItem('session_sharing_config'); if (savedSessionConfig) { @@ -212,15 +215,14 @@ const SessionHistoryView: React.FC = ({ }); }; - const handleLaunchInNewWindow = () => { + const handleResumeSession = () => { try { - resumeSession(session); + resumeSession(session, setView); } catch (error) { toast.error(`Could not launch session: ${error instanceof Error ? error.message : error}`); } }; - // Define action buttons const actionButtons = showActionButtons ? ( <> @@ -254,7 +256,7 @@ const SessionHistoryView: React.FC = ({ ) : null} - diff --git a/ui/desktop/src/components/sessions/SessionsInsights.tsx b/ui/desktop/src/components/sessions/SessionsInsights.tsx index 713af579558f..15c2ad53578a 100644 --- a/ui/desktop/src/components/sessions/SessionsInsights.tsx +++ b/ui/desktop/src/components/sessions/SessionsInsights.tsx @@ -13,6 +13,7 @@ import { SessionInsights as ApiSessionInsights, } from '../../api'; import { resumeSession } from '../../sessions'; +import { useNavigation } from '../../hooks/useNavigation'; export function SessionInsights() { const [insights, setInsights] = useState(null); @@ -21,6 +22,7 @@ export function SessionInsights() { const [isLoading, setIsLoading] = useState(true); const [isLoadingSessions, setIsLoadingSessions] = useState(true); const navigate = useNavigate(); + const setView = useNavigation(); useEffect(() => { let loadingTimeout: ReturnType; @@ -86,9 +88,7 @@ export function SessionInsights() { const handleSessionClick = async (session: Session) => { try { - resumeSession(session, (sessionId: string) => { - navigate(`/pair?resumeSessionId=${sessionId}`); - }); + resumeSession(session, setView); } catch (error) { console.error('Failed to start session:', error); navigate('/sessions', { diff --git a/ui/desktop/src/components/settings/extensions/agent-api.ts b/ui/desktop/src/components/settings/extensions/agent-api.ts index 01b6463c5a20..d051e289dec0 100644 --- a/ui/desktop/src/components/settings/extensions/agent-api.ts +++ b/ui/desktop/src/components/settings/extensions/agent-api.ts @@ -32,11 +32,17 @@ export async function addToAgent( toastService.dismiss(toastId); } const errMsg = errorMessage(error); + const recoverHints = + `Explain the following error: ${errMsg}. ` + + 'This happened while trying to install an extension. Look out for issues that the ' + + "extension tried to run something faulty, didn't exist or there was trouble with " + + 'the network configuration - VPNs like WARP often cause issues.'; const msg = errMsg.length < 70 ? errMsg : `Failed to add extension`; toastService.error({ title: extensionName, msg: msg, traceback: errMsg, + recoverHints, }); throw error; } diff --git a/ui/desktop/src/hooks/useNavigation.ts b/ui/desktop/src/hooks/useNavigation.ts index 2dfefa3a418b..3a16171b3ea6 100644 --- a/ui/desktop/src/hooks/useNavigation.ts +++ b/ui/desktop/src/hooks/useNavigation.ts @@ -11,3 +11,5 @@ export const useNavigation = () => { const navigate = useNavigate(); return createNavigationHandler(navigate); }; + +export type setViewType = ReturnType; diff --git a/ui/desktop/src/sessions.ts b/ui/desktop/src/sessions.ts index deca3b26da78..ef9df7d792fc 100644 --- a/ui/desktop/src/sessions.ts +++ b/ui/desktop/src/sessions.ts @@ -1,19 +1,17 @@ -import { Session } from './api'; +import { Session, startAgent } from './api'; +import { setViewType } from './hooks/useNavigation'; -export function resumeSession( - session: Session, - navigateInSameWindow?: (sessionId: string) => void -) { - const workingDir = session.working_dir; - if (!workingDir) { - throw new Error('Cannot resume session: working directory is missing in session'); - } - - // When ALPHA is true and we have a navigation callback, resume in the same window - // Otherwise, open in a new window (old behavior) - if (process.env.ALPHA && navigateInSameWindow) { - navigateInSameWindow(session.id); +export function resumeSession(session: Session, setView: setViewType) { + if (process.env.ALPHA) { + setView('pair', { + disableAnimation: true, + resumeSessionId: session.id, + }); } else { + const workingDir = session.working_dir; + if (!workingDir) { + throw new Error('Cannot resume session: working directory is missing in session'); + } window.electron.createChatWindow( undefined, // query workingDir, @@ -22,3 +20,30 @@ export function resumeSession( ); } } + +export async function startNewSession( + initialText: string | undefined, + resetChat: (() => void) | null, + setView: setViewType +) { + if (!resetChat || process.env.ALPHA) { + const newAgent = await startAgent({ + body: { + working_dir: window.appConfig.get('GOOSE_WORKING_DIR') as string, + }, + throwOnError: true, + }); + const session = newAgent.data; + setView('pair', { + disableAnimation: true, + initialMessage: initialText, + resumeSessionId: session.id, + }); + } else { + resetChat(); + setView('pair', { + disableAnimation: true, + initialMessage: initialText, + }); + } +} diff --git a/ui/desktop/src/toasts.tsx b/ui/desktop/src/toasts.tsx index ba34a652997c..99c9b7193bf4 100644 --- a/ui/desktop/src/toasts.tsx +++ b/ui/desktop/src/toasts.tsx @@ -1,12 +1,14 @@ import { toast, ToastOptions } from 'react-toastify'; import { Button } from './components/ui/button'; +import { startNewSession } from './sessions'; +import { useNavigation } from './hooks/useNavigation'; export interface ToastServiceOptions { silent?: boolean; shouldThrow?: boolean; } -export default class ToastService { +class ToastService { private silent: boolean = false; private shouldThrow: boolean = false; @@ -30,13 +32,13 @@ export default class ToastService { } } - error({ title, msg, traceback }: { title: string; msg: string; traceback: string }): void { + error(props: ToastErrorProps): void { if (!this.silent) { - toastError({ title, msg, traceback }); + toastError(props); } if (this.shouldThrow) { - throw new Error(msg); + throw new Error(props.msg); } } @@ -68,7 +70,7 @@ export default class ToastService { handleError(title: string, message: string, options: ToastServiceOptions = {}): void { this.configure(options); this.error({ - title: title || 'Error', + title: title, msg: message, traceback: message, }); @@ -88,6 +90,7 @@ const commonToastOptions: ToastOptions = { }; type ToastSuccessProps = { title?: string; msg?: string; toastOptions?: ToastOptions }; + export function toastSuccess({ title, msg, toastOptions = {} }: ToastSuccessProps) { return toast.success(
@@ -99,26 +102,42 @@ export function toastSuccess({ title, msg, toastOptions = {} }: ToastSuccessProp } type ToastErrorProps = { - title?: string; - msg?: string; + title: string; + msg: string; traceback?: string; - toastOptions?: ToastOptions; + recoverHints?: string; }; -export function toastError({ title, msg, traceback, toastOptions }: ToastErrorProps) { - return toast.error( +function ToastErrorContent({ + title, + msg, + traceback, + recoverHints, +}: Omit) { + const setView = useNavigation(); + const showRecovery = recoverHints && setView; + + return (
- {title ? {title} : null} - {msg ?
{msg}
: null} + {title && {title}} + {msg &&
{msg}
}
-
- {traceback ? ( +
+ {showRecovery ? ( + + ) : traceback ? ( ) : null}
-
, - { ...commonToastOptions, autoClose: traceback ? false : 5000, ...toastOptions } +
+ ); +} + +export function toastError({ title, msg, traceback, recoverHints }: ToastErrorProps) { + return toast.error( + , + { ...commonToastOptions, autoClose: traceback ? false : 5000 } ); } @@ -137,19 +156,3 @@ export function toastLoading({ title, msg, toastOptions }: ToastLoadingProps) { { ...commonToastOptions, autoClose: false, ...toastOptions } ); } - -type ToastInfoProps = { - title?: string; - msg?: string; - toastOptions?: ToastOptions; -}; - -export function toastInfo({ title, msg, toastOptions }: ToastInfoProps) { - return toast.info( -
- {title ? {title} : null} - {msg ?
{msg}
: null} -
, - { ...commonToastOptions, ...toastOptions } - ); -} From 3dee8f0848447e03b3905192e0ec06916aa1d0ba Mon Sep 17 00:00:00 2001 From: Douwe Osinga Date: Wed, 29 Oct 2025 14:26:37 -0400 Subject: [PATCH 2/3] Ask goose --- ui/desktop/src/toasts.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/desktop/src/toasts.tsx b/ui/desktop/src/toasts.tsx index 99c9b7193bf4..2eb51dcb39c7 100644 --- a/ui/desktop/src/toasts.tsx +++ b/ui/desktop/src/toasts.tsx @@ -125,7 +125,7 @@ function ToastErrorContent({
{showRecovery ? ( - + ) : traceback ? ( ) : null} From 889aff5c7d861ebc106f79e8842cef1d16667213 Mon Sep 17 00:00:00 2001 From: Douwe Osinga Date: Thu, 30 Oct 2025 12:59:32 -0400 Subject: [PATCH 3/3] What zane said --- ui/desktop/src/components/pair.tsx | 2 +- ui/desktop/src/sessions.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/desktop/src/components/pair.tsx b/ui/desktop/src/components/pair.tsx index 4b572eac99c9..0aa7353ab55a 100644 --- a/ui/desktop/src/components/pair.tsx +++ b/ui/desktop/src/components/pair.tsx @@ -9,7 +9,7 @@ import { cn } from '../utils'; import { ChatType } from '../types/chat'; import { useSearchParams } from 'react-router-dom'; -import { setViewType } from '../hooks/useNavigation'; +import type { setViewType } from '../hooks/useNavigation'; export interface PairRouteState { resumeSessionId?: string; diff --git a/ui/desktop/src/sessions.ts b/ui/desktop/src/sessions.ts index ef9df7d792fc..ecbcb691b87b 100644 --- a/ui/desktop/src/sessions.ts +++ b/ui/desktop/src/sessions.ts @@ -1,5 +1,5 @@ import { Session, startAgent } from './api'; -import { setViewType } from './hooks/useNavigation'; +import type { setViewType } from './hooks/useNavigation'; export function resumeSession(session: Session, setView: setViewType) { if (process.env.ALPHA) {