-
Notifications
You must be signed in to change notification settings - Fork 5.9k
Fix: Recipe Extensions Not Loading in Desktop #6777
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
ce09fd9
a6adbe3
68732d8
632653e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -157,6 +157,7 @@ export default function CreateRecipeFromSessionModal({ | |
| setIsCreating(true); | ||
| try { | ||
| // Create the recipe object from form data | ||
| // Don't set extensions - let the recipe use default enabled extensions | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typical LLM comment |
||
| const recipe: Recipe = { | ||
| title: formData.title, | ||
| description: formData.description, | ||
|
|
@@ -181,7 +182,6 @@ export default function CreateRecipeFromSessionModal({ | |
| json_schema: JSON.parse(formData.jsonSchema), | ||
| } | ||
| : undefined, | ||
| extensions: [], // Will be populated based on current extensions | ||
| }; | ||
|
|
||
| let recipeId = await saveRecipe(recipe, null); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,7 +4,7 @@ import { Button } from '../ui/button'; | |
| import { Input } from '../ui/input'; | ||
| import { ScheduledJob } from '../../schedule'; | ||
| import { CronPicker } from './CronPicker'; | ||
| import { Recipe, decodeRecipe } from '../../recipe'; | ||
| import { Recipe, decodeRecipe, stripEmptyExtensions } from '../../recipe'; | ||
| import { getStorageDirectory } from '../../recipe/recipe_management'; | ||
| import ClockIcon from '../../assets/clock-icon.svg'; | ||
| import * as yaml from 'yaml'; | ||
|
|
@@ -80,25 +80,28 @@ async function parseDeepLink(deepLink: string): Promise<Recipe | null> { | |
| } | ||
|
|
||
| function recipeToYaml(recipe: Recipe): string { | ||
| // Strip empty extensions to ensure scheduled recipes use default enabled extensions | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm, we discussed this, that's not how the scheduler works
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, scheduler always respect the extension config in recipe, not like Desktop. Maybe we should not apply this stripEmptyExtensions here. |
||
| const processedRecipe = stripEmptyExtensions(recipe) as Recipe; | ||
|
|
||
| const cleanRecipe: CleanRecipe = { | ||
| title: recipe.title, | ||
| description: recipe.description, | ||
| title: processedRecipe.title, | ||
| description: processedRecipe.description, | ||
| }; | ||
|
|
||
| if (recipe.instructions) { | ||
| cleanRecipe.instructions = recipe.instructions; | ||
| if (processedRecipe.instructions) { | ||
| cleanRecipe.instructions = processedRecipe.instructions; | ||
| } | ||
|
|
||
| if (recipe.prompt) { | ||
| cleanRecipe.prompt = recipe.prompt; | ||
| if (processedRecipe.prompt) { | ||
| cleanRecipe.prompt = processedRecipe.prompt; | ||
| } | ||
|
|
||
| if (recipe.activities && recipe.activities.length > 0) { | ||
| cleanRecipe.activities = recipe.activities; | ||
| if (processedRecipe.activities && processedRecipe.activities.length > 0) { | ||
| cleanRecipe.activities = processedRecipe.activities; | ||
| } | ||
|
|
||
| if (recipe.extensions && recipe.extensions.length > 0) { | ||
| cleanRecipe.extensions = recipe.extensions.map((ext) => { | ||
| if (processedRecipe.extensions && processedRecipe.extensions.length > 0) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and we're stripping it anyway here, so this feels like a no op |
||
| cleanRecipe.extensions = processedRecipe.extensions.map((ext) => { | ||
| const cleanExt: CleanExtension = { | ||
| name: ext.name, | ||
| type: 'builtin', | ||
|
|
@@ -178,15 +181,15 @@ function recipeToYaml(recipe: Recipe): string { | |
| }); | ||
| } | ||
|
|
||
| if (recipe.author) { | ||
| if (processedRecipe.author) { | ||
| cleanRecipe.author = { | ||
| contact: recipe.author.contact || undefined, | ||
| metadata: recipe.author.metadata || undefined, | ||
| contact: processedRecipe.author.contact || undefined, | ||
| metadata: processedRecipe.author.metadata || undefined, | ||
| }; | ||
| } | ||
|
|
||
| cleanRecipe.schedule = { | ||
| window_title: `${recipe.title} - Scheduled`, | ||
| window_title: `${processedRecipe.title} - Scheduled`, | ||
| }; | ||
|
|
||
| return yaml.stringify(cleanRecipe); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -76,3 +76,16 @@ export async function generateDeepLink(recipe: Recipe): Promise<string> { | |
| const encoded = await encodeRecipe(recipe); | ||
| return `goose://recipe?config=${encoded}`; | ||
| } | ||
|
|
||
| /** | ||
| * Strips empty extensions array from a recipe. | ||
| * This ensures recipes without explicit extensions will use the default enabled extensions | ||
| * from the user's config in the desktop rather than loading with no extensions. | ||
| */ | ||
| export function stripEmptyExtensions<T extends { extensions?: unknown[] | null }>(recipe: T): T { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why this complicated type? shouldn't this just be Recipe in, Recipe out? that would save us some casting above too |
||
| if (Array.isArray(recipe.extensions) && recipe.extensions.length === 0) { | ||
| const { extensions: _, ...rest } = recipe; | ||
| return rest as T; | ||
| } | ||
| return recipe; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we? we do want to make the distinction between empty lists and no recipes. if you now load a recipe that pre-existed and had an empty list of extensions, and you save it, it now has no extensions.
you want to make sure that if we create a recipe the default extensions are undefined and then respect whatever it said already