Remove dead code and old settings migration (#4180)

This commit is contained in:
Jack Amadeo
2025-08-19 21:11:06 -04:00
committed by GitHub
parent 62121c6823
commit 240084c50f
4 changed files with 5 additions and 340 deletions
+2 -3
View File
@@ -3,11 +3,10 @@ import { useNavigate } from 'react-router-dom';
import { Recipe, generateDeepLink } from '../recipe';
import { Parameter } from '../recipe/index';
import { FullExtensionConfig } from '../extensions';
import { Geese } from './icons/Geese';
import Copy from './icons/Copy';
import { Check, Save, Calendar } from 'lucide-react';
import { useConfig } from './ConfigContext';
import { ExtensionConfig, useConfig } from './ConfigContext';
import { FixedExtensionEntry } from './ConfigContext';
import RecipeActivityEditor from './RecipeActivityEditor';
import RecipeInfoModal from './RecipeInfoModal';
@@ -186,7 +185,7 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
}
return cleanExtension;
})
.filter(Boolean) as FullExtensionConfig[],
.filter(Boolean) as ExtensionConfig[],
};
console.log('Final config extensions:', config.extensions);
@@ -1,11 +1,10 @@
import { useState, useEffect, useCallback } from 'react';
import { Recipe, generateDeepLink } from '../recipe';
import { Parameter } from '../recipe/index';
import { FullExtensionConfig } from '../extensions';
import { Geese } from './icons/Geese';
import Copy from './icons/Copy';
import { Check, Save, Calendar, X } from 'lucide-react';
import { useConfig } from './ConfigContext';
import { ExtensionConfig, useConfig } from './ConfigContext';
import { FixedExtensionEntry } from './ConfigContext';
import RecipeActivityEditor from './RecipeActivityEditor';
import RecipeInfoModal from './RecipeInfoModal';
@@ -171,7 +170,7 @@ export default function ViewRecipeModal({ isOpen, onClose, config }: ViewRecipeM
}
return cleanExtension;
})
.filter(Boolean) as FullExtensionConfig[],
.filter(Boolean) as ExtensionConfig[],
};
return updatedConfig;
-232
View File
@@ -1,232 +0,0 @@
import { getApiUrl } from './config';
import { toast } from 'react-toastify';
import { safeJsonParse } from './utils/jsonUtils';
import builtInExtensionsData from './built-in-extensions.json';
import { toastError, toastLoading, toastSuccess } from './toasts';
// Hardcoded default extension timeout in seconds
export const DEFAULT_EXTENSION_TIMEOUT = 300;
// ExtensionConfig type matching the Rust version
// TODO: refactor this
export type ExtensionConfig =
| {
type: 'sse';
name: string;
uri: string;
env_keys?: string[];
timeout?: number;
}
| {
type: 'streamable_http';
name: string;
uri: string;
env_keys?: string[];
headers?: Record<string, string>;
timeout?: number;
}
| {
type: 'stdio';
name: string;
cmd: string;
args: string[];
env_keys?: string[];
timeout?: number;
}
| {
type: 'builtin';
name: string;
env_keys?: string[];
timeout?: number;
};
// FullExtensionConfig type matching all the fields that come in deep links and are stored in local storage
export type FullExtensionConfig = ExtensionConfig & {
id: string;
description: string;
enabled: boolean;
};
export interface ExtensionPayload {
name?: string;
type?: string;
cmd?: string;
args?: string[];
uri?: string;
env_keys?: string[];
timeout?: number;
}
export const BUILT_IN_EXTENSIONS = builtInExtensionsData as FullExtensionConfig[];
function sanitizeName(name: string) {
return name.toLowerCase().replace(/-/g, '').replace(/_/g, '').replace(/\s/g, '');
}
export async function addExtension(
extension: FullExtensionConfig,
silent: boolean = false
): Promise<Response> {
try {
console.log('Adding extension:', extension);
// Create the config based on the extension type
const config = {
type: extension.type,
...(extension.type === 'stdio' && {
name: sanitizeName(extension.name),
cmd: await replaceWithShims(extension.cmd),
args: extension.args || [],
}),
...(extension.type === 'sse' && {
name: sanitizeName(extension.name),
uri: extension.uri,
}),
...(extension.type === 'streamable_http' && {
name: sanitizeName(extension.name),
uri: extension.uri,
}),
...(extension.type === 'builtin' && {
name: sanitizeName(extension.name),
}),
env_keys: extension.env_keys,
timeout: extension.timeout,
};
let toastId;
if (!silent) toastId = toastLoading({ title: extension.name, msg: 'Adding extension...' });
const response = await fetch(getApiUrl('/extensions/add'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Secret-Key': await window.electron.getSecretKey(),
},
body: JSON.stringify(config),
});
const responseText = await response.text();
if (!response.ok) {
const errorMsg = `Server returned ${response.status}: ${response.statusText}. Response: ${responseText}`;
console.error(errorMsg);
if (toastId) toast.dismiss(toastId);
toastError({
title: extension.name,
msg: 'Failed to add extension',
traceback: errorMsg,
toastOptions: { autoClose: false },
});
return response;
}
// Only try to parse JSON if we got a successful response and have JSON content
let data;
try {
data = JSON.parse(responseText);
} catch (e) {
console.error('Failed to parse response as JSON:', e);
data = { error: true, message: responseText };
}
if (!data.error) {
if (!silent) {
if (toastId) toast.dismiss(toastId);
toastSuccess({ title: extension.name, msg: `Successfully enabled extension` });
}
return response;
}
var errorMessage = `Error adding extension`;
// Attempt to extract the message from inside StdioProcessError()
// NOTE: this may change if the error response from /extensions/add changes
const regex = /StdioProcessError\("(.*?)"\)/;
const match = data.message.match(regex);
if (match) {
const extracted = match[1];
// only display the message if it is less than 100 chars
errorMessage = extracted.length > 100 ? errorMessage : extracted;
}
if (toastId) toast.dismiss(toastId);
toastError({
title: extension.name,
msg: errorMessage,
traceback: data.message,
toastOptions: { autoClose: false },
});
return response;
} catch (error) {
const errorMessage = `Failed to add ${extension.name} extension: ${error instanceof Error ? error.message : 'Unknown error'}`;
console.error(errorMessage);
toastError({
title: extension.name,
msg: 'Failed to add extension',
traceback: error instanceof Error ? error.message : String(error),
toastOptions: { autoClose: false },
});
throw error;
}
}
export async function removeExtension(name: string, silent: boolean = false): Promise<Response> {
try {
const response = await fetch(getApiUrl('/extensions/remove'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Secret-Key': await window.electron.getSecretKey(),
},
body: JSON.stringify(sanitizeName(name)),
});
const data = await safeJsonParse<{ error: boolean; message: string }>(response);
if (!data.error) {
if (!silent) {
toastSuccess({ title: name, msg: 'Successfully disabled extension' });
}
return response;
}
const errorMessage = `Error removing ${name} extension${data.message ? `. ${data.message}` : ''}`;
console.error(errorMessage);
toastError({
title: name,
msg: 'Error removing extension',
traceback: data.message,
toastOptions: { autoClose: false },
});
return response;
} catch (error) {
const errorMessage = `Failed to remove ${name} extension: ${error instanceof Error ? error.message : 'Unknown error'}`;
console.error(errorMessage);
toastError({
title: name,
msg: 'Error removing extension',
traceback: error instanceof Error ? error.message : String(error),
toastOptions: { autoClose: false },
});
throw error;
}
}
// Update the path to the binary based on the command
export async function replaceWithShims(cmd: string) {
const binaryPathMap: Record<string, string> = {
goosed: await window.electron.getBinaryPath('goosed'),
jbang: await window.electron.getBinaryPath('jbang'),
npx: await window.electron.getBinaryPath('npx'),
uvx: await window.electron.getBinaryPath('uvx'),
'npx.cmd': await window.electron.getBinaryPath('npx.cmd'),
};
if (binaryPathMap[cmd]) {
console.log('--------> Replacing command with shim ------>', cmd, binaryPathMap[cmd]);
cmd = binaryPathMap[cmd];
}
return cmd;
}
+1 -102
View File
@@ -1,5 +1,4 @@
import { getApiUrl } from '../config';
import { FullExtensionConfig } from '../extensions';
import { initializeAgent } from '../agent';
import {
initializeBundledExtensions,
@@ -8,16 +7,7 @@ import {
} from '../components/settings/extensions';
import { extractExtensionConfig } from '../components/settings/extensions/utils';
import type { ExtensionConfig, FixedExtensionEntry } from '../components/ConfigContext';
// TODO: remove when removing migration logic
import { toastService } from '../toasts';
import {
ExtensionQuery,
RecipeParameter,
SubRecipe,
addExtension as apiAddExtension,
updateSessionConfig,
extendPrompt,
} from '../api';
import { RecipeParameter, SubRecipe, updateSessionConfig, extendPrompt } from '../api';
import { addSubRecipesToAgent } from '../recipe/add_sub_recipe_on_agent';
export interface Provider {
@@ -119,81 +109,6 @@ export const updateSystemPromptWithParameters = async (
}
};
/**
* Migrates extensions from localStorage to config.yaml (settings v2)
* This function handles the migration from settings v1 to v2 by:
* 1. Reading extensions from localStorage
* 2. Adding non-builtin extensions to config.yaml
* 3. Marking the migration as complete
*
* NOTE: This logic can be removed eventually when enough versions have passed
* We leave the existing user settings in localStorage, in case users downgrade
* or things need to be reverted.
*/
export const migrateExtensionsToSettingsV3 = async () => {
console.log('need to perform extension migration v3');
const userSettingsStr = localStorage.getItem('user_settings');
let localStorageExtensions: FullExtensionConfig[] = [];
try {
if (userSettingsStr) {
const userSettings = JSON.parse(userSettingsStr);
localStorageExtensions = userSettings.extensions ?? [];
}
} catch (error) {
console.error('Failed to parse user settings:', error);
}
if (localStorageExtensions.length === 0) {
localStorage.setItem('configVersion', '3');
console.log('No extensions to migrate. Config version set to 3.');
return;
}
const migrationErrors: { name: string; error: unknown }[] = [];
// Process extensions in parallel for better performance
const migrationPromises = localStorageExtensions
.filter((extension) => extension.type !== 'builtin') // Skip builtins as before
.map(async (extension) => {
console.log(`Migrating extension ${extension.name} to config.yaml`);
try {
const query: ExtensionQuery = {
name: extension.name,
config: extension,
enabled: extension.enabled,
};
await apiAddExtension({
body: query,
throwOnError: true,
});
} catch (err) {
console.error(`Failed to migrate extension ${extension.name}:`, err);
migrationErrors.push({
name: extension.name,
error: `failed migration with ${JSON.stringify(err)}`,
});
}
});
await Promise.allSettled(migrationPromises);
if (migrationErrors.length === 0) {
localStorage.setItem('configVersion', '3');
console.log('Extension migration complete. Config version set to 3.');
} else {
const errorSummaryStr = migrationErrors
.map(({ name, error }) => `- ${name}: ${JSON.stringify(error)}`)
.join('\n');
toastService.error({
title: 'Config Migration Error',
msg: 'There was a problem updating your config file',
traceback: errorSummaryStr,
});
}
};
export const initializeSystem = async (
provider: string,
model: string,
@@ -254,22 +169,6 @@ export const initializeSystem = async (
return;
}
// NOTE: remove when we want to stop migration logic
// Check if we need to migrate extensions from localStorage to config.yaml
const configVersion = localStorage.getItem('configVersion');
const shouldMigrateExtensions = !configVersion || parseInt(configVersion, 10) < 3;
if (shouldMigrateExtensions) {
await migrateExtensionsToSettingsV3();
}
/* NOTE:
* If we've migrated and this is a version update, refreshedExtensions should be > 0
* and we'll want to syncBundledExtensions to ensure any new extensions are added.
* Otherwise if the user has never opened goose - refreshedExtensions will be 0
* and we want to fall into the case to initializeBundledExtensions.
*/
// Initialize or sync built-in extensions into config.yaml
let refreshedExtensions = await options.getExtensions(false);