diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index bc71fb921fe5..00d6878cb5d5 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -25,6 +25,7 @@ import { UserInput } from './types/message'; interface PairRouteState { resumeSessionId?: string; initialMessage?: UserInput; + noAutoSubmit?: boolean; } import SettingsView, { SettingsViewOptions } from './components/settings/SettingsView'; import SessionsView from './components/sessions/SessionsView'; @@ -84,8 +85,11 @@ const PairRouteWrapper = ({ activeSessions: Array<{ sessionId: string; initialMessage?: UserInput; + noAutoSubmit?: boolean; }>; - setActiveSessions: (sessions: Array<{ sessionId: string; initialMessage?: UserInput }>) => void; + setActiveSessions: ( + sessions: Array<{ sessionId: string; initialMessage?: UserInput; noAutoSubmit?: boolean }> + ) => void; }) => { const { extensionsList } = useConfig(); const location = useLocation(); @@ -98,6 +102,7 @@ const PairRouteWrapper = ({ const recipeDeeplinkFromConfig = window.appConfig?.get('recipeDeeplink') as string | undefined; const recipeIdFromConfig = window.appConfig?.get('recipeId') as string | undefined; const initialMessage = routeState.initialMessage; + const noAutoSubmit = routeState.noAutoSubmit; // Create session if we have an initialMessage, recipeDeeplink, or recipeId but no sessionId useEffect(() => { @@ -122,6 +127,7 @@ const PairRouteWrapper = ({ detail: { sessionId: newSession.id, initialMessage: sessionInitialMessage, + noAutoSubmit, }, }) ); @@ -162,11 +168,12 @@ const PairRouteWrapper = ({ detail: { sessionId: resumeSessionId, initialMessage: initialMessage, + noAutoSubmit, }, }) ); } - }, [resumeSessionId, activeSessions, initialMessage]); + }, [resumeSessionId, activeSessions, initialMessage, noAutoSubmit]); return null; }; @@ -358,15 +365,16 @@ export function AppInner() { const MAX_ACTIVE_SESSIONS = 10; const [activeSessions, setActiveSessions] = useState< - Array<{ sessionId: string; initialMessage?: UserInput }> + Array<{ sessionId: string; initialMessage?: UserInput; noAutoSubmit?: boolean }> >([]); useEffect(() => { const handleAddActiveSession = (event: Event) => { - const { sessionId, initialMessage } = ( + const { sessionId, initialMessage, noAutoSubmit } = ( event as CustomEvent<{ sessionId: string; initialMessage?: UserInput; + noAutoSubmit?: boolean; }> ).detail; @@ -380,7 +388,7 @@ export function AppInner() { } // New session - add to end with LRU eviction if needed - const newSession = { sessionId, initialMessage }; + const newSession = { sessionId, initialMessage, noAutoSubmit }; const updated = [...prev, newSession]; if (updated.length > MAX_ACTIVE_SESSIONS) { return updated.slice(updated.length - MAX_ACTIVE_SESSIONS); @@ -496,13 +504,18 @@ export function AppInner() { // Show a toast if mesh is the configured provider but isn't running. useEffect(() => { const handler = () => { - toast.warn('Inference Mesh is set as your provider but isn\'t running. Open Settings → Mesh to start it. Keep goose running to stay connected.', { - autoClose: false, - toastId: 'mesh-not-running', - }); + toast.warn( + "Inference Mesh is set as your provider but isn't running. Open Settings → Mesh to start it. Keep goose running to stay connected.", + { + autoClose: false, + toastId: 'mesh-not-running', + } + ); }; window.electron.on('mesh-not-running', handler); - return () => { window.electron.off('mesh-not-running', handler); }; + return () => { + window.electron.off('mesh-not-running', handler); + }; }, []); // Prevent default drag and drop behavior globally to avoid opening files in new windows @@ -606,12 +619,14 @@ export function AppInner() { useEffect(() => { const handleSetInitialMessage = async (_event: IpcRendererEvent, ...args: unknown[]) => { const initialMessage = args[0] as string; + const options = (args[1] as { noAutoSubmit?: boolean } | undefined) || {}; if (initialMessage && !isProcessingRef.current) { isProcessingRef.current = true; navigate('/pair', { state: { initialMessage: { msg: initialMessage, images: [] }, + noAutoSubmit: options.noAutoSubmit, }, }); setTimeout(() => { diff --git a/ui/desktop/src/components/BaseChat.tsx b/ui/desktop/src/components/BaseChat.tsx index dd722175f5a7..0be8abcc28c6 100644 --- a/ui/desktop/src/components/BaseChat.tsx +++ b/ui/desktop/src/components/BaseChat.tsx @@ -1,11 +1,5 @@ import { AppEvents } from '../constants/events'; -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { defineMessages, useIntl } from '../i18n'; import { useLocation, useNavigate } from 'react-router-dom'; import { SearchView } from './conversation/SearchView'; @@ -76,6 +70,7 @@ interface BaseChatProps { sessionId: string; isActiveSession: boolean; initialMessage?: UserInput; + noAutoSubmit?: boolean; } export default function BaseChat({ @@ -85,6 +80,7 @@ export default function BaseChat({ customMainLayoutProps = {}, sessionId, initialMessage, + noAutoSubmit, isActiveSession, }: BaseChatProps) { const intl = useIntl(); @@ -136,7 +132,13 @@ export default function BaseChat({ return initialMessage; }, [initialMessage, recipe?.prompt, session?.user_recipe_values]); - const canAutoSubmit = session?.session_type === 'scheduled' || !recipe || hasNotAcceptedRecipe === false; + // noAutoSubmit only suppresses auto-submitting the initial prompt of a fresh session + // (goose://new-session?prompt=...). Once the conversation has messages, later flows + // such as forks or resumes should auto-submit normally. + const suppressInitialAutoSubmit = noAutoSubmit && messages.length === 0; + const canAutoSubmit = + !suppressInitialAutoSubmit && + (session?.session_type === 'scheduled' || !recipe || hasNotAcceptedRecipe === false); useAutoSubmit({ sessionId, @@ -201,7 +203,11 @@ export default function BaseChat({ const latestInference = useMemo(() => { for (let i = messages.length - 1; i >= 0; i--) { const message = messages[i]; - if (message.role === 'assistant' && message.metadata.userVisible && message.metadata.inference) { + if ( + message.role === 'assistant' && + message.metadata.userVisible && + message.metadata.inference + ) { return message.metadata.inference; } } @@ -360,7 +366,10 @@ export default function BaseChat({ : recipe.prompt; } - const initialPrompt = recipePrompt; + const initialPrompt = + noAutoSubmit && messages.length === 0 && resolvedInitialMessage?.msg + ? resolvedInitialMessage.msg + : recipePrompt; if (sessionLoadError) { return ( @@ -375,7 +384,9 @@ export default function BaseChat({
-

{intl.formatMessage(i18n.failedToLoadSession)}

+

+ {intl.formatMessage(i18n.failedToLoadSession)} +

{sessionLoadError}