Initial prompt with goose://new-session (#9427)
Signed-off-by: Douwe M Osinga <douwe@sidewalklabs.com> Co-authored-by: Douwe M Osinga <douwe@sidewalklabs.com>
This commit is contained in:
+25
-10
@@ -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(() => {
|
||||
|
||||
@@ -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({
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="flex flex-col items-center justify-center p-8">
|
||||
<div className="text-red-700 dark:text-red-300 bg-red-400/50 p-4 rounded-lg mb-4 max-w-md">
|
||||
<h3 className="font-semibold mb-2">{intl.formatMessage(i18n.failedToLoadSession)}</h3>
|
||||
<h3 className="font-semibold mb-2">
|
||||
{intl.formatMessage(i18n.failedToLoadSession)}
|
||||
</h3>
|
||||
<p className="text-sm">{sessionLoadError}</p>
|
||||
</div>
|
||||
<button
|
||||
@@ -509,14 +520,11 @@ export default function BaseChat({
|
||||
accumulatedOutputTokens={
|
||||
tokenState?.accumulatedOutputTokens ?? session?.accumulated_output_tokens ?? undefined
|
||||
}
|
||||
accumulatedCost={
|
||||
tokenState?.accumulatedCost ?? session?.accumulated_cost ?? undefined
|
||||
}
|
||||
accumulatedCost={tokenState?.accumulatedCost ?? session?.accumulated_cost ?? undefined}
|
||||
droppedFiles={droppedFiles}
|
||||
onFilesProcessed={() => setDroppedFiles([])} // Clear dropped files after processing
|
||||
messages={messages}
|
||||
disableAnimation={disableAnimation}
|
||||
|
||||
recipe={recipe}
|
||||
recipeAccepted={!hasNotAcceptedRecipe}
|
||||
initialPrompt={initialPrompt}
|
||||
@@ -548,16 +556,16 @@ export default function BaseChat({
|
||||
recipe.parameters.length > 0 &&
|
||||
!session?.user_recipe_values &&
|
||||
session?.session_type !== 'scheduled' && (
|
||||
<ParameterInputModal
|
||||
parameters={recipe.parameters}
|
||||
onSubmit={setRecipeUserParams}
|
||||
onClose={() => setView('chat')}
|
||||
initialValues={
|
||||
(window.appConfig?.get('recipeParameters') as Record<string, string> | undefined) ||
|
||||
undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<ParameterInputModal
|
||||
parameters={recipe.parameters}
|
||||
onSubmit={setRecipeUserParams}
|
||||
onClose={() => setView('chat')}
|
||||
initialValues={
|
||||
(window.appConfig?.get('recipeParameters') as Record<string, string> | undefined) ||
|
||||
undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<CreateRecipeFromSessionModal
|
||||
isOpen={isCreateRecipeModalOpen}
|
||||
|
||||
@@ -8,6 +8,7 @@ interface ChatSessionsContainerProps {
|
||||
activeSessions: Array<{
|
||||
sessionId: string;
|
||||
initialMessage?: UserInput;
|
||||
noAutoSubmit?: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
@@ -51,6 +52,7 @@ export default function ChatSessionsContainer({
|
||||
setChat={setChat}
|
||||
sessionId={session.sessionId}
|
||||
initialMessage={session.initialMessage}
|
||||
noAutoSubmit={session.noAutoSubmit}
|
||||
suppressEmptyState={false}
|
||||
isActiveSession={isVisible}
|
||||
/>
|
||||
|
||||
@@ -24,6 +24,7 @@ interface AppLayoutContentProps {
|
||||
activeSessions: Array<{
|
||||
sessionId: string;
|
||||
initialMessage?: UserInput;
|
||||
noAutoSubmit?: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
@@ -116,6 +117,7 @@ interface AppLayoutProps {
|
||||
activeSessions: Array<{
|
||||
sessionId: string;
|
||||
initialMessage?: UserInput;
|
||||
noAutoSubmit?: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
|
||||
+27
-4
@@ -429,7 +429,12 @@ if (process.platform !== 'darwin') {
|
||||
app.whenReady().then(async () => {
|
||||
const recentDirs = loadRecentDirs();
|
||||
const openDir = recentDirs.length > 0 ? recentDirs[0] : null;
|
||||
await createChat(app, { dir: openDir || undefined });
|
||||
const prompt = parsedUrl.searchParams.get('prompt') || undefined;
|
||||
await createChat(app, {
|
||||
dir: openDir || undefined,
|
||||
initialMessage: prompt,
|
||||
initialMessageNoAutoSubmit: prompt !== undefined,
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -514,7 +519,12 @@ async function handleProtocolUrl(url: string, parsedUrl: URL) {
|
||||
const openDir = recentDirs.length > 0 ? recentDirs[0] : null;
|
||||
|
||||
if (parsedUrl.hostname === 'new-session') {
|
||||
await createChat(app, { dir: openDir || undefined });
|
||||
const prompt = parsedUrl.searchParams.get('prompt') || undefined;
|
||||
await createChat(app, {
|
||||
dir: openDir || undefined,
|
||||
initialMessage: prompt,
|
||||
initialMessageNoAutoSubmit: prompt !== undefined,
|
||||
});
|
||||
return;
|
||||
} else if (parsedUrl.hostname === 'resume') {
|
||||
await createResumeChatWindow(parsedUrl, openDir || undefined);
|
||||
@@ -588,7 +598,12 @@ app.on('open-url', async (_event, url) => {
|
||||
if (parsedUrl.hostname === 'new-session') {
|
||||
log.info('[Main] Detected new-session URL, creating new chat window');
|
||||
openUrlHandledLaunch = true;
|
||||
await createChat(app, { dir: openDir || undefined });
|
||||
const prompt = parsedUrl.searchParams.get('prompt') || undefined;
|
||||
await createChat(app, {
|
||||
dir: openDir || undefined,
|
||||
initialMessage: prompt,
|
||||
initialMessageNoAutoSubmit: prompt !== undefined,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -855,9 +870,11 @@ const releaseWindowGoosedLease = async (windowId: number) => {
|
||||
const windowPowerSaveBlockers = new Map<number, number>(); // windowId -> blockerId
|
||||
// Track pending initial messages per window
|
||||
const pendingInitialMessages = new Map<number, string>(); // windowId -> initialMessage
|
||||
const pendingInitialMessageNoAutoSubmit = new Set<number>(); // windowIds whose initialMessage should NOT auto-submit
|
||||
|
||||
interface CreateChatOptions {
|
||||
initialMessage?: string;
|
||||
initialMessageNoAutoSubmit?: boolean;
|
||||
dir?: string;
|
||||
resumeSessionId?: string;
|
||||
viewType?: string;
|
||||
@@ -870,6 +887,7 @@ interface CreateChatOptions {
|
||||
const createChat = async (app: App, options: CreateChatOptions = {}) => {
|
||||
const {
|
||||
initialMessage,
|
||||
initialMessageNoAutoSubmit,
|
||||
dir,
|
||||
resumeSessionId,
|
||||
viewType,
|
||||
@@ -1226,6 +1244,9 @@ const createChat = async (app: App, options: CreateChatOptions = {}) => {
|
||||
// If we have an initial message, store it to send after React is ready
|
||||
if (initialMessage) {
|
||||
pendingInitialMessages.set(mainWindow.id, initialMessage);
|
||||
if (initialMessageNoAutoSubmit) {
|
||||
pendingInitialMessageNoAutoSubmit.add(mainWindow.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Set up local keyboard shortcuts that only work when the window is focused
|
||||
@@ -1636,9 +1657,11 @@ ipcMain.on('react-ready', (event) => {
|
||||
// Send any pending initial message for this window
|
||||
if (windowId && pendingInitialMessages.has(windowId)) {
|
||||
const initialMessage = pendingInitialMessages.get(windowId)!;
|
||||
const noAutoSubmit = pendingInitialMessageNoAutoSubmit.has(windowId);
|
||||
log.info('Sending pending initial message to window:', initialMessage);
|
||||
window.webContents.send('set-initial-message', initialMessage);
|
||||
window.webContents.send('set-initial-message', initialMessage, { noAutoSubmit });
|
||||
pendingInitialMessages.delete(windowId);
|
||||
pendingInitialMessageNoAutoSubmit.delete(windowId);
|
||||
}
|
||||
|
||||
if (windowId && pendingDeepLinks.has(windowId) && window) {
|
||||
|
||||
Reference in New Issue
Block a user