Ensure adding/removing extensions refreshes extensions list (#3695)

This commit is contained in:
Zane
2025-07-31 12:17:09 -07:00
committed by GitHub
parent 9daa464021
commit c9ae9ba1ce
5 changed files with 165 additions and 55 deletions
@@ -45,6 +45,21 @@ export default function ExtensionsSection({
showEnvVars
);
// Update deep link state when props change
useEffect(() => {
setDeepLinkConfigStateVar(deepLinkConfig);
setShowEnvVarsStateVar(showEnvVars);
}, [deepLinkConfig, showEnvVars]);
// Reset deep link state when component is re-mounted (via key prop changes)
useEffect(() => {
return () => {
// Cleanup function to reset state when component unmounts
setDeepLinkConfigStateVar(null);
setShowEnvVarsStateVar(null);
};
}, []);
const fetchExtensions = useCallback(async () => {
const extensionsList = await getExtensions(true); // Force refresh
// Sort extensions by name to maintain consistent order
@@ -69,12 +84,6 @@ export default function ExtensionsSection({
enabled: disableConfiguration ? selectedExtensions.includes(ext.name) : ext.enabled,
}));
console.log(
'Setting extensions with selectedExtensions:',
selectedExtensions,
'Extensions:',
sortedExtensions
);
setExtensions(sortedExtensions);
}, [getExtensions, disableConfiguration, selectedExtensions]);
@@ -129,27 +138,33 @@ export default function ExtensionsSection({
const extensionConfig = createExtensionConfig(formData);
try {
await activateExtension({ addToConfig: addExtension, extensionConfig: extensionConfig });
// Immediately refresh the extensions list after successful activation
await fetchExtensions();
} catch (error) {
console.error('Failed to activate extension:', error);
// Even if activation fails, we don't reopen the modal
} finally {
// Add a small delay to ensure backend has updated, then refresh the extensions list
setTimeout(async () => {
await fetchExtensions();
}, 500);
await fetchExtensions();
}
};
const handleUpdateExtension = async (formData: ExtensionFormData) => {
if (!selectedExtension) {
console.error('No selected extension for update');
return;
}
// Close the modal immediately
handleModalClose();
const extensionConfig = createExtensionConfig(formData);
const originalName = selectedExtension.name;
try {
await updateExtension({
enabled: formData.enabled,
extensionConfig: extensionConfig,
addToConfig: addExtension,
removeFromConfig: removeExtension,
originalName: originalName,
});
} catch (error) {
console.error('Failed to update extension:', error);
@@ -182,6 +197,11 @@ export default function ExtensionsSection({
setIsModalOpen(false);
setIsAddModalOpen(false);
setSelectedExtension(null);
// Clear any navigation state that might be cached
if (window.history.state?.deepLinkConfig) {
window.history.replaceState({}, '', window.location.hash);
}
};
return (
@@ -178,6 +178,6 @@ export async function removeFromAgent(
}
}
function sanitizeName(name: string) {
export function sanitizeName(name: string) {
return name.toLowerCase().replace(/-/g, '').replace(/_/g, '').replace(/\s/g, '');
}
@@ -1,6 +1,6 @@
import type { ExtensionConfig } from '../../../api/types.gen';
import { toastService, ToastServiceOptions } from '../../../toasts';
import { addToAgent, removeFromAgent } from './agent-api';
import { addToAgent, removeFromAgent, sanitizeName } from './agent-api';
interface ActivateExtensionProps {
addToConfig: (name: string, extensionConfig: ExtensionConfig, enabled: boolean) => Promise<void>;
@@ -129,46 +129,121 @@ export async function addToAgentOnStartup({
interface UpdateExtensionProps {
enabled: boolean;
addToConfig: (name: string, extensionConfig: ExtensionConfig, enabled: boolean) => Promise<void>;
removeFromConfig: (name: string) => Promise<void>;
extensionConfig: ExtensionConfig;
originalName?: string;
}
/**
* Updates an extension configuration without changing its enabled state
* Updates an extension configuration, handling name changes
*/
export async function updateExtension({
enabled,
addToConfig,
removeFromConfig,
extensionConfig,
originalName,
}: UpdateExtensionProps) {
if (enabled) {
// Sanitize the new name to match the behavior when adding extensions
const sanitizedNewName = sanitizeName(extensionConfig.name);
const sanitizedOriginalName = originalName ? sanitizeName(originalName) : undefined;
// Check if the sanitized name has changed
const nameChanged = sanitizedOriginalName && sanitizedOriginalName !== sanitizedNewName;
if (nameChanged) {
// Handle name change: remove old extension and add new one
// First remove the old extension from agent (using original name)
try {
// AddToAgent
await addToAgent(extensionConfig);
await removeFromAgent(originalName!, { silent: true }); // Suppress removal toast since we'll show update toast
} catch (error) {
console.error('[updateExtension]: Failed to add extension to agent during update:', error);
// Failed to add to agent -- show that error to user and do not update the config file
console.error('Failed to remove old extension from agent during rename:', error);
// Continue with the process even if agent removal fails
}
// Remove old extension from config (using original name)
try {
await removeFromConfig(originalName!); // We know originalName is not undefined here because nameChanged is true
} catch (error) {
console.error('Failed to remove old extension from config during rename:', error);
throw error; // This is more critical, so we throw
}
// Create a copy of the extension config with the sanitized name
const sanitizedExtensionConfig = {
...extensionConfig,
name: sanitizedNewName,
};
// Add new extension with sanitized name
if (enabled) {
try {
// AddToAgent with silent option to avoid duplicate toasts
await addToAgent(sanitizedExtensionConfig, { silent: true });
} catch (error) {
console.error('[updateExtension]: Failed to add renamed extension to agent:', error);
throw error;
}
}
// Add to config with sanitized name
try {
await addToConfig(sanitizedNewName, sanitizedExtensionConfig, enabled);
} catch (error) {
console.error('[updateExtension]: Failed to add renamed extension to config:', error);
throw error;
}
// Then add to config
try {
await addToConfig(extensionConfig.name, extensionConfig, enabled);
} catch (error) {
console.error('[updateExtension]: Failed to update extension in config:', error);
throw error;
}
} else {
try {
await addToConfig(extensionConfig.name, extensionConfig, enabled);
} catch (error) {
console.error('[updateExtension]: Failed to update disabled extension in config:', error);
throw error;
}
// show a toast that it was successfully updated
toastService.configure({ silent: false });
toastService.success({
title: `Update extension`,
msg: `Successfully updated ${extensionConfig.name} extension`,
msg: `Successfully updated ${sanitizedNewName} extension`,
});
} else {
// Create a copy of the extension config with the sanitized name
const sanitizedExtensionConfig = {
...extensionConfig,
name: sanitizedNewName,
};
if (enabled) {
try {
// AddToAgent with silent option to avoid duplicate toasts
await addToAgent(sanitizedExtensionConfig, { silent: true });
} catch (error) {
console.error('[updateExtension]: Failed to add extension to agent during update:', error);
// Failed to add to agent -- show that error to user and do not update the config file
throw error;
}
// Then add to config
try {
await addToConfig(sanitizedNewName, sanitizedExtensionConfig, enabled);
} catch (error) {
console.error('[updateExtension]: Failed to update extension in config:', error);
throw error;
}
// show a toast that it was successfully updated
toastService.success({
title: `Update extension`,
msg: `Successfully updated ${sanitizedNewName} extension`,
});
} else {
try {
await addToConfig(sanitizedNewName, sanitizedExtensionConfig, enabled);
} catch (error) {
console.error('[updateExtension]: Failed to update disabled extension in config:', error);
throw error;
}
// show a toast that it was successfully updated
toastService.success({
title: `Update extension`,
msg: `Successfully updated ${sanitizedNewName} extension`,
});
}
}
}