ui: clean up extensions (#1914)

This commit is contained in:
Lily Delalande
2025-03-30 11:01:48 -04:00
committed by GitHub
parent 53ae11f601
commit 376a363615
7 changed files with 24 additions and 24 deletions
@@ -60,7 +60,6 @@ export default function ExtensionsSection() {
await fetchExtensions(); // Refresh the list after successful toggle await fetchExtensions(); // Refresh the list after successful toggle
return true; // Indicate success return true; // Indicate success
} catch (error) { } catch (error) {
console.error('Toggle extension failed:', error);
// Don't refresh the extension list on failure - this allows our visual state rollback to work // Don't refresh the extension list on failure - this allows our visual state rollback to work
// The actual state in the config hasn't changed anyway // The actual state in the config hasn't changed anyway
throw error; // Re-throw to let the ExtensionItem component know it failed throw error; // Re-throw to let the ExtensionItem component know it failed
@@ -20,14 +20,15 @@ export async function extensionApiCall(
type: isActivating ? 'activating' : 'removing', type: isActivating ? 'activating' : 'removing',
verb: isActivating ? 'Activating' : 'Removing', verb: isActivating ? 'Activating' : 'Removing',
pastTense: isActivating ? 'activated' : 'removed', pastTense: isActivating ? 'activated' : 'removed',
presentTense: isActivating ? 'activate' : 'remove',
}; };
// for adding the payload is an extensionConfig, for removing payload is just the name // for adding the payload is an extensionConfig, for removing payload is just the name
const extensionName = isActivating ? payload.name : payload; const extensionName = isActivating ? payload.name : payload;
let toastId; let toastId;
// Step 1: Show loading toast (only for activation) // Step 1: Show loading toast (only for activation of stdio)
if (isActivating) { if (isActivating && (payload as ExtensionConfig) && payload.type == 'stdio') {
toastId = toastService.loading({ toastId = toastService.loading({
title: extensionName, title: extensionName,
msg: `${action.verb} ${extensionName} extension...`, msg: `${action.verb} ${extensionName} extension...`,
@@ -75,6 +76,12 @@ export async function extensionApiCall(
} catch (error) { } catch (error) {
// Final catch-all error handler // Final catch-all error handler
toastService.dismiss(toastId); toastService.dismiss(toastId);
const msg = error.length < 100 ? error : `Failed to ${action.presentTense} extension`;
toastService.error({
title: extensionName,
msg: msg,
traceback: error,
});
console.error(`Error in extensionApiCall for ${extensionName}:`, error); console.error(`Error in extensionApiCall for ${extensionName}:`, error);
throw error; throw error;
} }
@@ -142,13 +149,11 @@ export async function addToAgent(
// Check if this is a 428 error and make the message more descriptive // Check if this is a 428 error and make the message more descriptive
if (error.message && error.message.includes('428')) { if (error.message && error.message.includes('428')) {
const enhancedError = new Error( const enhancedError = new Error(
'Agent is not initialized. Please initialize the agent first. (428 Precondition Required)' 'Failed to add extension. Goose Agent was still starting up. Please try again.'
); );
console.error(`Failed to add extension ${extension.name} to agent: ${enhancedError.message}`); console.error(`Failed to add extension ${extension.name} to agent: ${enhancedError.message}`);
throw enhancedError; throw enhancedError;
} }
console.error(`Failed to add extension ${extension.name} to agent:`, error);
throw error; throw error;
} }
} }
@@ -176,7 +176,7 @@ export async function toggleExtension({
showEscMessage: false, showEscMessage: false,
}); });
} catch (error) { } catch (error) {
console.error('Error adding extension to agent. Will try to toggle back off. Error:', error); console.error('Error adding extension to agent. Will try to toggle back off.');
try { try {
await toggleExtension({ await toggleExtension({
toggle: 'toggleOff', toggle: 'toggleOff',
@@ -1,5 +1,6 @@
import { Input } from '../../../ui/input'; import { Input } from '../../../ui/input';
import React from 'react'; import React from 'react';
import { removeShims } from '../utils';
interface ExtensionConfigFieldsProps { interface ExtensionConfigFieldsProps {
type: 'stdio' | 'sse' | 'builtin'; type: 'stdio' | 'sse' | 'builtin';
@@ -25,7 +26,7 @@ export default function ExtensionConfigFields({
<label className="text-sm font-medium mb-2 block text-textStandard">Command</label> <label className="text-sm font-medium mb-2 block text-textStandard">Command</label>
<div className="relative"> <div className="relative">
<Input <Input
value={full_cmd} value={removeShims(full_cmd)}
onChange={(e) => onChange('cmd', e.target.value)} onChange={(e) => onChange('cmd', e.target.value)}
placeholder="e.g. npx -y @modelcontextprotocol/my-extension <filepath>" placeholder="e.g. npx -y @modelcontextprotocol/my-extension <filepath>"
className={`w-full ${!submitAttempted || isValid ? 'border-borderSubtle' : 'border-red-500'} text-textStandard`} className={`w-full ${!submitAttempted || isValid ? 'border-borderSubtle' : 'border-red-500'} text-textStandard`}
@@ -3,7 +3,7 @@ import { FixedExtensionEntry } from '../../../ConfigContext';
import { ExtensionConfig } from '../../../../api/types.gen'; import { ExtensionConfig } from '../../../../api/types.gen';
import ExtensionItem from './ExtensionItem'; import ExtensionItem from './ExtensionItem';
import builtInExtensionsData from '../../../../built-in-extensions.json'; import builtInExtensionsData from '../../../../built-in-extensions.json';
import { combineCmdAndArgs } from '../utils'; import { combineCmdAndArgs, removeShims } from '../utils';
interface ExtensionListProps { interface ExtensionListProps {
extensions: FixedExtensionEntry[]; extensions: FixedExtensionEntry[];
@@ -57,7 +57,8 @@ export function getSubtitle(config: ExtensionConfig): string {
return 'Built-in extension'; return 'Built-in extension';
} }
if (config.type === 'stdio') { if (config.type === 'stdio') {
const full_command = combineCmdAndArgs(config.cmd, config.args); // remove shims for displaying
const full_command = combineCmdAndArgs(removeShims(config.cmd), config.args);
return `STDIO extension${config.description ? `: ${config.description}` : ''}${full_command ? `\n${full_command}` : ''}`; return `STDIO extension${config.description ? `: ${config.description}` : ''}${full_command ? `\n${full_command}` : ''}`;
} }
if (config.type === 'sse') { if (config.type === 'sse') {
@@ -143,3 +143,11 @@ export async function replaceWithShims(cmd: string) {
return cmd; return cmd;
} }
export function removeShims(cmd: string) {
const segments = cmd.split('/');
// Filter out any empty segments (which can happen with trailing slashes)
const nonEmptySegments = segments.filter((segment) => segment.length > 0);
// Return the last segment or empty string if there are no segments
return nonEmptySegments.length > 0 ? nonEmptySegments[nonEmptySegments.length - 1] : '';
}
-14
View File
@@ -23,31 +23,21 @@ export default class ToastService {
} }
configure(options: ToastServiceOptions = {}): void { configure(options: ToastServiceOptions = {}): void {
console.log('ToastService.configure called with options:', options);
if (options.silent !== undefined) { if (options.silent !== undefined) {
console.log(`Setting silent from ${this.silent} to ${options.silent}`);
this.silent = options.silent; this.silent = options.silent;
} }
if (options.showEscMessage !== undefined) { if (options.showEscMessage !== undefined) {
console.log(
`Setting showEscMessage from ${this.showEscMessage} to ${options.showEscMessage}`
);
this.showEscMessage = options.showEscMessage; this.showEscMessage = options.showEscMessage;
} }
if (options.shouldThrow !== undefined) { if (options.shouldThrow !== undefined) {
console.log(`Setting shouldThrow from ${this.shouldThrow} to ${options.shouldThrow}`);
this.shouldThrow = options.shouldThrow; this.shouldThrow = options.shouldThrow;
} }
} }
error({ title, msg, traceback }: { title: string; msg: string; traceback: string }): void { error({ title, msg, traceback }: { title: string; msg: string; traceback: string }): void {
console.log(`ToastService.error called - silent=${this.silent}`, { title, msg });
if (!this.silent) { if (!this.silent) {
toastError({ title, msg, traceback }); toastError({ title, msg, traceback });
} else {
console.log('Toast suppressed because silent=true');
} }
console.error(msg, traceback);
if (this.shouldThrow) { if (this.shouldThrow) {
throw new Error(msg); throw new Error(msg);
@@ -55,9 +45,7 @@ export default class ToastService {
} }
loading({ title, msg }: { title: string; msg: string }): string | number | undefined { loading({ title, msg }: { title: string; msg: string }): string | number | undefined {
console.log(`ToastService.loading called - silent=${this.silent}`, { title, msg });
if (this.silent) { if (this.silent) {
console.log('Toast suppressed because silent=true');
return undefined; return undefined;
} }
@@ -72,9 +60,7 @@ export default class ToastService {
} }
success({ title, msg }: { title: string; msg: string }): void { success({ title, msg }: { title: string; msg: string }): void {
console.log(`ToastService.success called - silent=${this.silent}`, { title, msg });
if (this.silent) { if (this.silent) {
console.log('Toast suppressed because silent=true');
return; return;
} }
toastSuccess({ title, msg }); toastSuccess({ title, msg });