replace recipeConfig with recipe everywhere

This commit is contained in:
Zane Staggs
2025-10-02 17:22:48 -07:00
parent 4de4d11370
commit 1aef9aa97c
16 changed files with 94 additions and 103 deletions
+1 -1
View File
@@ -123,7 +123,7 @@ vi.mock('./contexts/ChatContext', () => ({
title: 'Test Chat',
messages: [],
messageHistoryIndex: 0,
recipeConfig: null,
recipe: null,
},
setChat: vi.fn(),
setPairChat: vi.fn(), // Keep this from HEAD
+1 -1
View File
@@ -297,7 +297,7 @@ export function AppInner() {
title: 'Pair Chat',
messages: [],
messageHistoryIndex: 0,
recipeConfig: null,
recipe: null,
});
const { addExtension } = useConfig();
+29 -37
View File
@@ -153,14 +153,14 @@ function BaseChatContent({
},
onMessageSent: () => {
// Mark that user has started using the recipe
if (recipeConfig) {
if (recipe) {
setHasStartedUsingRecipe(true);
}
},
});
// Use recipe state - no complex heuristics
const recipeState = useRecipeState(location.state?.recipeConfig || chat.recipeConfig);
const recipeState = useRecipeState(location.state?.recipe || chat.recipe);
// Local UI state for modals
const [isParameterModalOpen, setIsParameterModalOpen] = useState(false);
@@ -168,7 +168,7 @@ function BaseChatContent({
const [readyForAutoUserPrompt, setReadyForAutoUserPrompt] = useState(false);
// Computed values using the simple approach
const recipeConfig = recipeState.recipe;
const recipe = recipeState.recipe;
const recipeParameters = chat.recipeParameters;
const filteredParameters = recipeState.filteredParameters;
const hasAllRequiredParameters = recipeState.hasAllRequiredParameters(recipeParameters || null);
@@ -205,15 +205,15 @@ function BaseChatContent({
isParameterModalOpen,
messages.length,
chat.sessionId,
recipeConfig?.title,
recipe?.title,
]);
// Handle recipe warning modal display
useEffect(() => {
if (recipeConfig && !recipeAccepted && hasSecurityWarnings) {
if (recipe && !recipeAccepted && hasSecurityWarnings) {
setIsRecipeWarningModalOpen(true);
}
}, [recipeConfig, recipeAccepted, hasSecurityWarnings]);
}, [recipe, recipeAccepted, hasSecurityWarnings]);
useEffect(() => {
setReadyForAutoUserPrompt(true);
@@ -229,11 +229,7 @@ function BaseChatContent({
setIsParameterModalOpen(false);
try {
await updateSystemPromptWithParameters(
chat.sessionId,
inputValues,
recipeConfig || undefined
);
await updateSystemPromptWithParameters(chat.sessionId, inputValues, recipe || undefined);
// Update session user recipe values via API
await updateSessionUserRecipeValues({
path: { session_id: chat.sessionId },
@@ -257,8 +253,8 @@ function BaseChatContent({
const handleAutoExecution = React.useCallback(
(append: (message: Message) => void, isLoading: boolean, onAutoExecute?: () => void) => {
if (
recipeConfig?.isScheduledExecution &&
recipeConfig?.prompt &&
recipe?.isScheduledExecution &&
recipe?.prompt &&
(!recipeState.requiresParameters || recipeParameters) &&
messages.length === 0 &&
!isLoading &&
@@ -266,8 +262,8 @@ function BaseChatContent({
recipeAccepted
) {
const finalPrompt = recipeParameters
? substituteParameters(recipeConfig.prompt, recipeParameters)
: recipeConfig.prompt;
? substituteParameters(recipe.prompt, recipeParameters)
: recipe.prompt;
const userMessage = createUserMessage(finalPrompt);
append(userMessage);
@@ -275,8 +271,8 @@ function BaseChatContent({
}
},
[
recipeConfig?.isScheduledExecution,
recipeConfig?.prompt,
recipe?.isScheduledExecution,
recipe?.prompt,
recipeState.requiresParameters,
recipeParameters,
messages.length,
@@ -294,7 +290,7 @@ function BaseChatContent({
setChat({
...chat,
messages: [],
recipeConfig: recipe,
recipe: recipe,
recipeParameters: null,
});
};
@@ -302,7 +298,7 @@ function BaseChatContent({
// Reset recipe usage tracking when recipe changes
useEffect(() => {
const previousTitle = currentRecipeTitle;
const newTitle = recipeConfig?.title || null;
const newTitle = recipe?.title || null;
const hasRecipeChanged = newTitle !== currentRecipeTitle;
if (hasRecipeChanged) {
@@ -320,7 +316,7 @@ function BaseChatContent({
setHasStartedUsingRecipe(true);
}
}
}, [recipeConfig?.title, currentRecipeTitle, messages.length]);
}, [recipe?.title, currentRecipeTitle, messages.length]);
// Handle recipe auto-execution
useEffect(() => {
@@ -374,7 +370,7 @@ function BaseChatContent({
const combinedTextFromInput = customEvent.detail?.value || '';
// Mark that user has started using the recipe when they submit a message
if (recipeConfig && combinedTextFromInput.trim()) {
if (recipe && combinedTextFromInput.trim()) {
setHasStartedUsingRecipe(true);
}
@@ -391,7 +387,7 @@ function BaseChatContent({
// Wrapper for append that tracks recipe usage
const appendWithTracking = (text: string | Message) => {
// Mark that user has started using the recipe when they use append
if (recipeConfig) {
if (recipe) {
setHasStartedUsingRecipe(true);
}
append(text);
@@ -435,14 +431,12 @@ function BaseChatContent({
paddingY={0}
>
{/* Recipe agent header - sticky at top of chat container */}
{recipeConfig?.title && (
{recipe?.title && (
<div className="sticky top-0 z-10 bg-background-default px-0 -mx-6 mb-6 pt-6">
<AgentHeader
title={recipeConfig.title}
title={recipe.title}
profileInfo={
recipeConfig.profile
? `${recipeConfig.profile} - ${recipeConfig.mcps || 12} MCPs`
: undefined
recipe.profile ? `${recipe.profile} - ${recipe.mcps || 12} MCPs` : undefined
}
onChangeProfile={() => {
console.log('Change profile clicked');
@@ -456,14 +450,12 @@ function BaseChatContent({
{renderBeforeMessages && renderBeforeMessages()}
{/* Recipe Activities - always show when recipe is active and accepted */}
{recipeConfig && recipeAccepted && !suppressEmptyState && (
{recipe && recipeAccepted && !suppressEmptyState && (
<div className={hasStartedUsingRecipe ? 'mb-6' : ''}>
<RecipeActivities
append={(text: string) => appendWithTracking(text)}
activities={
Array.isArray(recipeConfig.activities) ? recipeConfig.activities : null
}
title={recipeConfig.title}
activities={Array.isArray(recipe.activities) ? recipe.activities : null}
title={recipe.title}
parameterValues={recipeParameters || {}}
/>
</div>
@@ -555,7 +547,7 @@ function BaseChatContent({
<div className="block h-8" />
</>
) : !recipeConfig && showPopularTopics ? (
) : !recipe && showPopularTopics ? (
/* Show PopularChatTopics when no messages, no recipe, and showPopularTopics is true (Pair view) */
<PopularChatTopics append={(text: string) => append(text)} />
) : null /* Show nothing when messages.length === 0 && suppressEmptyState === true */
@@ -603,7 +595,7 @@ function BaseChatContent({
disableAnimation={disableAnimation}
sessionCosts={sessionCosts}
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
recipeConfig={recipeConfig}
recipe={recipe}
recipeAccepted={recipeAccepted}
initialPrompt={initialPrompt}
toolCount={toolCount || 0}
@@ -620,9 +612,9 @@ function BaseChatContent({
onConfirm={handleRecipeAccept}
onCancel={() => handleRecipeCancel()}
recipeDetails={{
title: recipeConfig?.title,
description: recipeConfig?.description,
instructions: recipeConfig?.instructions || undefined,
title: recipe?.title,
description: recipe?.description,
instructions: recipe?.instructions || undefined,
}}
hasSecurityWarnings={hasSecurityWarnings}
/>
+5 -5
View File
@@ -81,7 +81,7 @@ interface ChatInputProps {
};
setIsGoosehintsModalOpen?: (isOpen: boolean) => void;
disableAnimation?: boolean;
recipeConfig?: Recipe | null;
recipe?: Recipe | null;
recipeAccepted?: boolean;
initialPrompt?: string;
toolCount: number;
@@ -108,7 +108,7 @@ export default function ChatInput({
disableAnimation = false,
sessionCosts,
setIsGoosehintsModalOpen,
recipeConfig,
recipe,
recipeAccepted,
initialPrompt,
toolCount,
@@ -318,7 +318,7 @@ export default function ChatInput({
useEffect(() => {
// Only load draft once and if conditions are met
if (!initialValue && !recipeConfig && !draftLoadedRef.current && chatContext) {
if (!initialValue && !recipe && !draftLoadedRef.current && chatContext) {
const draftText = chatContext.draft || '';
if (draftText) {
@@ -329,7 +329,7 @@ export default function ChatInput({
// Always mark as loaded after checking, regardless of whether we found a draft
draftLoadedRef.current = true;
}
}, [chatContext, initialValue, recipeConfig]);
}, [chatContext, initialValue, recipe]);
// Save draft when user types (debounced)
const debouncedSaveDraft = useMemo(
@@ -1624,7 +1624,7 @@ export default function ChatInput({
dropdownRef={dropdownRef}
setView={setView}
alerts={alerts}
recipeConfig={recipeConfig}
recipe={recipe}
hasMessages={messages.length > 0}
/>
</div>
+6 -6
View File
@@ -76,13 +76,13 @@ export default function Pair({
// Load a fresh chat session with new recipe reset behavior
const newChat = await loadCurrentChat({
resumeSessionId: undefined,
recipeConfig: recipe,
recipe: recipe,
setAgentWaitingMessage,
resetOptions: {
resetSession: true,
clearMessages: true,
clearRecipeParameters: true,
// Keep the new recipe from recipeConfig
// Keep the new recipe from recipe
},
});
@@ -116,13 +116,13 @@ export default function Pair({
// BUT only if we're resuming the same session (not starting a new chat)
let finalChat = loadedChat;
if (
!loadedChat.recipeConfig &&
currentChat?.recipeConfig &&
!loadedChat.recipe &&
currentChat?.recipe &&
resumeSessionId === currentChat.sessionId
) {
finalChat = {
...loadedChat,
recipeConfig: currentChat.recipeConfig,
recipe: currentChat.recipe,
recipeParameters: currentChat.recipeParameters || null,
};
}
@@ -171,7 +171,7 @@ export default function Pair({
const { initialPrompt: recipeInitialPrompt } = useRecipeManager(
chat,
recipe || chat.recipeConfig || null
recipe || chat.recipe || null
);
const handleMessageSubmit = () => {
@@ -28,7 +28,7 @@ export default function CreateEditRecipeModal({
isCreateMode = false,
}: CreateEditRecipeModalProps) {
const { getExtensions } = useConfig();
const [recipeConfig] = useState<Recipe | undefined>(config);
const [recipe] = useState<Recipe | undefined>(config);
const getInitialValues = React.useCallback((): RecipeFormData => {
if (config) {
@@ -171,7 +171,7 @@ export default function CreateEditRecipeModal({
}
return {
...recipeConfig,
...recipe,
title,
description,
instructions,
@@ -197,7 +197,7 @@ export default function CreateEditRecipeModal({
.filter(Boolean) as ExtensionConfig[],
};
}, [
recipeConfig,
recipe,
title,
description,
instructions,
@@ -25,7 +25,7 @@ interface ModelsBottomBarProps {
dropdownRef: React.RefObject<HTMLDivElement>;
setView: (view: View) => void;
alerts: Alert[];
recipeConfig?: Recipe | null;
recipe?: Recipe | null;
hasMessages?: boolean; // Add prop to know if there are messages to create a recipe from
}
@@ -34,7 +34,7 @@ export default function ModelsBottomBar({
dropdownRef,
setView,
alerts,
recipeConfig,
recipe,
hasMessages = false,
}: ModelsBottomBarProps) {
const {
@@ -168,14 +168,14 @@ export default function ModelsBottomBar({
// Handle view recipe - open modal instead of navigating
const handleViewRecipe = () => {
if (recipeConfig) {
if (recipe) {
setShowViewRecipeModal(true);
}
};
// Handle save recipe - show save dialog
const handleSaveRecipeClick = () => {
if (recipeConfig) {
if (recipe) {
setShowSaveDialog(true);
}
};
@@ -211,7 +211,7 @@ export default function ModelsBottomBar({
</DropdownMenuItem>
{/* Recipe-specific menu items - only show when actively using a recipe */}
{recipeConfig && (
{recipe && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={handleViewRecipe}>
@@ -226,7 +226,7 @@ export default function ModelsBottomBar({
)}
{/* Only show "Create a recipe from this session" when there are messages AND no recipe is currently active */}
{hasMessages && !recipeConfig && (
{hasMessages && !recipe && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem
@@ -256,20 +256,20 @@ export default function ModelsBottomBar({
) : null}
{/* Save Recipe Dialog */}
{showSaveDialog && recipeConfig && (
{showSaveDialog && recipe && (
<SaveRecipeDialog
isOpen={showSaveDialog}
onClose={() => setShowSaveDialog(false)}
recipe={recipeConfig}
recipe={recipe}
/>
)}
{/* View Recipe Modal */}
{recipeConfig && (
{recipe && (
<CreateEditRecipeModal
isOpen={showViewRecipeModal}
onClose={() => setShowViewRecipeModal(false)}
config={recipeConfig}
config={recipe}
/>
)}
</div>
+3 -3
View File
@@ -58,7 +58,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
title: DEFAULT_CHAT_TITLE,
messages: [],
messageHistoryIndex: 0,
recipeConfig: null,
recipe: null,
recipeParameters: null,
});
clearDraft();
@@ -67,7 +67,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
const setRecipeConfig = (recipe: Recipe | null) => {
setChat({
...chat,
recipeConfig: recipe,
recipe: recipe,
recipeParameters: null,
});
};
@@ -75,7 +75,7 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
const clearRecipeConfig = () => {
setChat({
...chat,
recipeConfig: null,
recipe: null,
});
};
@@ -79,7 +79,7 @@ export function useAgentInitialization(): UseAgentInitializationReturn {
: await startAgent({
body: {
working_dir: window.appConfig.get('GOOSE_WORKING_DIR') as string,
recipe: recipeFromAppConfig ?? initContext.recipeConfig,
recipe: recipeFromAppConfig ?? initContext.recipe,
},
throwOnError: true,
});
@@ -116,13 +116,13 @@ export function useAgentInitialization(): UseAgentInitializationReturn {
// Initialize system and extensions with error boundary
initContext.setAgentWaitingMessage('Extensions are loading');
try {
const recipeConfigForInit = initContext.recipeConfig || agentSession.recipe || undefined;
const recipeForInit = initContext.recipe || agentSession.recipe || undefined;
await initializeSystem(agentSession.id, provider as string, model as string, {
getExtensions,
addExtension,
setIsExtensionsLoading: initContext.setIsExtensionsLoading,
recipeParameters: agentSession.user_recipe_values,
recipeConfig: recipeConfigForInit,
recipe: recipeForInit,
});
} catch (error) {
throw new AgentInitializationError(
@@ -150,9 +150,9 @@ export function useAgentInitialization(): UseAgentInitializationReturn {
messages = [];
}
let recipeConfig = agentSession.recipe;
let recipe = agentSession.recipe;
if (resetOptions.clearRecipe) {
recipeConfig = null;
recipe = null;
}
let recipeParameters = agentSession.user_recipe_values || null;
@@ -162,10 +162,10 @@ export function useAgentInitialization(): UseAgentInitializationReturn {
const initChat: ChatType = {
sessionId: agentSession.id,
title: recipeConfig?.title || agentSession.description || 'New Session',
title: recipe?.title || agentSession.description || 'New Session',
messageHistoryIndex: 0,
messages: messages,
recipeConfig: recipeConfig,
recipe: recipe,
recipeParameters: recipeParameters,
};
@@ -217,7 +217,7 @@ export function useAgentInitialization(): UseAgentInitializationReturn {
title: agentSession.recipe?.title || agentSession.description,
messageHistoryIndex: 0,
messages: messages?.map(convertApiMessageToFrontendMessage),
recipeConfig: agentSession.recipe,
recipe: agentSession.recipe,
recipeParameters: agentSession.user_recipe_values || null,
};
}
+3 -3
View File
@@ -94,10 +94,10 @@ export const useChatEngine = ({
body: {
session_id: chat.sessionId,
session_working_dir: window.appConfig.get('GOOSE_WORKING_DIR'),
...(chat.recipeConfig?.title
...(chat.recipe?.title
? {
recipe_name: chat.recipeConfig.title,
recipe_version: chat.recipeConfig?.version ?? 'unknown',
recipe_name: chat.recipe.title,
recipe_version: chat.recipe?.version ?? 'unknown',
}
: {}),
},
+3 -3
View File
@@ -9,7 +9,7 @@ import { useRecipeUI } from './useRecipeUI';
* This is a transitional hook - long term plan is to eliminate it entirely
* and use useRecipeState and useRecipeUI directly in components
*/
export const useRecipeManager = (chat: ChatType, recipeConfig?: Recipe | null) => {
export const useRecipeManager = (chat: ChatType, recipe?: Recipe | null) => {
const messagesRef = useRef(chat.messages);
useEffect(() => {
@@ -17,7 +17,7 @@ export const useRecipeManager = (chat: ChatType, recipeConfig?: Recipe | null) =
}, [chat.messages]);
// Core recipe state and business logic
const recipeState = useRecipeState(recipeConfig || chat.recipeConfig || null);
const recipeState = useRecipeState(recipe || chat.recipe || null);
// UI-specific recipe interactions
const recipeUI = useRecipeUI(
@@ -43,7 +43,7 @@ export const useRecipeManager = (chat: ChatType, recipeConfig?: Recipe | null) =
return {
// State from useRecipeState
recipeConfig: recipeState.recipe,
recipe: recipeState.recipe,
recipeParameters: chat.recipeParameters,
filteredParameters: recipeState.filteredParameters,
initialPrompt: recipeState.getInitialPrompt(chat.recipeParameters || null),
+5 -6
View File
@@ -111,8 +111,8 @@ type ElectronAPI = {
getUpdateState: () => Promise<{ updateAvailable: boolean; latestVersion?: string } | null>;
// Recipe warning functions
closeWindow: () => void;
hasAcceptedRecipeBefore: (recipeConfig: Recipe) => Promise<boolean>;
recordRecipeHash: (recipeConfig: Recipe) => Promise<boolean>;
hasAcceptedRecipeBefore: (recipe: Recipe) => Promise<boolean>;
recordRecipeHash: (recipe: Recipe) => Promise<boolean>;
openDirectoryInExplorer: (directoryPath: string) => Promise<boolean>;
};
@@ -232,10 +232,9 @@ const electronAPI: ElectronAPI = {
return ipcRenderer.invoke('get-update-state');
},
closeWindow: () => ipcRenderer.send('close-window'),
hasAcceptedRecipeBefore: (recipeConfig: Recipe) =>
ipcRenderer.invoke('has-accepted-recipe-before', recipeConfig),
recordRecipeHash: (recipeConfig: Recipe) =>
ipcRenderer.invoke('record-recipe-hash', recipeConfig),
hasAcceptedRecipeBefore: (recipe: Recipe) =>
ipcRenderer.invoke('has-accepted-recipe-before', recipe),
recordRecipeHash: (recipe: Recipe) => ipcRenderer.invoke('record-recipe-hash', recipe),
openDirectoryInExplorer: (directoryPath: string) =>
ipcRenderer.invoke('open-directory-in-explorer', directoryPath),
};
+1 -1
View File
@@ -30,7 +30,7 @@ export interface ResetOptions {
*/
export interface InitializationContext {
/** Recipe configuration to initialize with */
recipeConfig?: Recipe;
recipe?: Recipe;
/** Session ID to resume from */
resumeSessionId?: string;
/** Callback to set waiting message during initialization */
+1 -1
View File
@@ -6,6 +6,6 @@ export interface ChatType {
title: string;
messageHistoryIndex: number;
messages: Message[];
recipeConfig?: Recipe | null; // Add recipe configuration to chat state
recipe?: Recipe | null; // Add recipe configuration to chat state
recipeParameters?: Record<string, string> | null; // Add recipe parameters to chat state
}
+9 -9
View File
@@ -150,15 +150,15 @@ export const substituteParameters = (text: string, params: Record<string, string
export const updateSystemPromptWithParameters = async (
sessionId: string,
recipeParameters: Record<string, string>,
recipeConfig?: {
recipe?: {
instructions?: string | null;
sub_recipes?: SubRecipe[] | null;
parameters?: RecipeParameter[] | null;
}
): Promise<void> => {
const subRecipes = recipeConfig?.sub_recipes;
const subRecipes = recipe?.sub_recipes;
try {
const originalInstructions = recipeConfig?.instructions;
const originalInstructions = recipe?.instructions;
if (!originalInstructions) {
return;
@@ -200,7 +200,7 @@ export const initializeSystem = async (
addExtension?: (name: string, config: ExtensionConfig, enabled: boolean) => Promise<void>;
setIsExtensionsLoading?: (loading: boolean) => void;
recipeParameters?: Record<string, string> | null;
recipeConfig?: Recipe;
recipe?: Recipe;
}
) => {
try {
@@ -225,11 +225,11 @@ export const initializeSystem = async (
console.log('This will not end well');
}
// Get recipeConfig - prefer from options (session metadata) over app config
const recipeConfig = options?.recipeConfig || window.appConfig?.get?.('recipe');
const recipe_instructions = (recipeConfig as { instructions?: string })?.instructions;
const responseConfig = (recipeConfig as { response?: { json_schema?: unknown } })?.response;
const subRecipes = (recipeConfig as { sub_recipes?: SubRecipe[] })?.sub_recipes;
// Get recipe - prefer from options (session metadata) over app config
const recipe = options?.recipe || window.appConfig?.get?.('recipe');
const recipe_instructions = (recipe as { instructions?: string })?.instructions;
const responseConfig = (recipe as { response?: { json_schema?: unknown } })?.response;
const subRecipes = (recipe as { sub_recipes?: SubRecipe[] })?.sub_recipes;
const hasSubRecipes = subRecipes && subRecipes?.length > 0;
const recipeParameters = options?.recipeParameters;
+6 -6
View File
@@ -3,9 +3,9 @@ import fs from 'node:fs/promises';
import path from 'node:path';
import crypto from 'crypto';
function calculateRecipeHash(recipeConfig: unknown): string {
function calculateRecipeHash(recipe: unknown): string {
const hash = crypto.createHash('sha256');
hash.update(JSON.stringify(recipeConfig));
hash.update(JSON.stringify(recipe));
return hash.digest('hex');
}
@@ -16,8 +16,8 @@ async function getRecipeHashesDir(): Promise<string> {
return hashesDir;
}
ipcMain.handle('has-accepted-recipe-before', async (_event, recipeConfig) => {
const hash = calculateRecipeHash(recipeConfig);
ipcMain.handle('has-accepted-recipe-before', async (_event, recipe) => {
const hash = calculateRecipeHash(recipe);
const hashFile = path.join(await getRecipeHashesDir(), `${hash}.hash`);
try {
await fs.access(hashFile);
@@ -30,8 +30,8 @@ ipcMain.handle('has-accepted-recipe-before', async (_event, recipeConfig) => {
}
});
ipcMain.handle('record-recipe-hash', async (_event, recipeConfig) => {
const hash = calculateRecipeHash(recipeConfig);
ipcMain.handle('record-recipe-hash', async (_event, recipe) => {
const hash = calculateRecipeHash(recipe);
const filePath = path.join(await getRecipeHashesDir(), `${hash}.hash`);
const timestamp = new Date().toISOString();
await fs.writeFile(filePath, timestamp);