Add support for changing working dir and extensions in same window/session (#6057)
This commit is contained in:
@@ -2,6 +2,9 @@
|
||||
* Shared constants and utilities for extension error handling
|
||||
*/
|
||||
|
||||
import { ExtensionLoadResult } from '../api/types.gen';
|
||||
import { toastService, ExtensionLoadingStatus } from '../toasts';
|
||||
|
||||
export const MAX_ERROR_MESSAGE_LENGTH = 70;
|
||||
|
||||
/**
|
||||
@@ -28,3 +31,43 @@ export function formatExtensionErrorMessage(
|
||||
): string {
|
||||
return errorMsg.length < MAX_ERROR_MESSAGE_LENGTH ? errorMsg : fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows toast notifications for extension load results.
|
||||
* Uses grouped toast for multiple extensions, individual error toast for single failed extension.
|
||||
* @param results - Array of extension load results from the backend
|
||||
*/
|
||||
export function showExtensionLoadResults(results: ExtensionLoadResult[] | null | undefined): void {
|
||||
if (!results || results.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const failedExtensions = results.filter((r) => !r.success);
|
||||
|
||||
if (results.length === 1 && failedExtensions.length === 1) {
|
||||
const failed = failedExtensions[0];
|
||||
const errorMsg = failed.error || 'Unknown error';
|
||||
const recoverHints = createExtensionRecoverHints(errorMsg);
|
||||
const displayMsg = formatExtensionErrorMessage(errorMsg, 'Failed to load extension');
|
||||
|
||||
toastService.error({
|
||||
title: failed.name,
|
||||
msg: displayMsg,
|
||||
traceback: errorMsg,
|
||||
recoverHints,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const extensionStatuses: ExtensionLoadingStatus[] = results.map((r) => {
|
||||
const errorMsg = r.error || 'Unknown error';
|
||||
return {
|
||||
name: r.name,
|
||||
status: r.success ? 'success' : 'error',
|
||||
error: r.success ? undefined : errorMsg,
|
||||
recoverHints: r.success ? undefined : createExtensionRecoverHints(errorMsg),
|
||||
};
|
||||
});
|
||||
|
||||
toastService.extensionLoading(extensionStatuses, results.length, true);
|
||||
}
|
||||
|
||||
@@ -19,9 +19,7 @@ export type View =
|
||||
| 'recipes'
|
||||
| 'permission';
|
||||
|
||||
// TODO(Douwe): check these for usage, especially key: string for resetChat
|
||||
export type ViewOptions = {
|
||||
extensionId?: string;
|
||||
showEnvVars?: boolean;
|
||||
deepLinkConfig?: unknown;
|
||||
sessionDetails?: unknown;
|
||||
@@ -32,7 +30,6 @@ export type ViewOptions = {
|
||||
parentViewOptions?: ViewOptions;
|
||||
disableAnimation?: boolean;
|
||||
initialMessage?: string;
|
||||
resetChat?: boolean;
|
||||
shareToken?: string;
|
||||
resumeSessionId?: string;
|
||||
pendingScheduleDeepLink?: string;
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import {
|
||||
initializeBundledExtensions,
|
||||
syncBundledExtensions,
|
||||
addToAgentOnStartup,
|
||||
} from '../components/settings/extensions';
|
||||
import type { ExtensionConfig, FixedExtensionEntry } from '../components/ConfigContext';
|
||||
import { Recipe, updateAgentProvider, updateFromSession } from '../api';
|
||||
import { toastService, ExtensionLoadingStatus } from '../toasts';
|
||||
import { errorMessage } from './conversionUtils';
|
||||
import { createExtensionRecoverHints } from './extensionErrorUtils';
|
||||
|
||||
// Helper function to substitute parameters in text
|
||||
export const substituteParameters = (text: string, params: Record<string, string>): string => {
|
||||
@@ -29,7 +25,6 @@ export const initializeSystem = async (
|
||||
options?: {
|
||||
getExtensions?: (b: boolean) => Promise<FixedExtensionEntry[]>;
|
||||
addExtension?: (name: string, config: ExtensionConfig, enabled: boolean) => Promise<void>;
|
||||
setIsExtensionsLoading?: (loading: boolean) => void;
|
||||
recipeParameters?: Record<string, string> | null;
|
||||
recipe?: Recipe;
|
||||
}
|
||||
@@ -72,95 +67,11 @@ export const initializeSystem = async (
|
||||
|
||||
if (refreshedExtensions.length === 0) {
|
||||
await initializeBundledExtensions(options.addExtension);
|
||||
refreshedExtensions = await options.getExtensions(false);
|
||||
} else {
|
||||
await syncBundledExtensions(refreshedExtensions, options.addExtension);
|
||||
}
|
||||
|
||||
// Add enabled extensions to agent in parallel
|
||||
const enabledExtensions = refreshedExtensions.filter((ext) => ext.enabled);
|
||||
|
||||
if (enabledExtensions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
options?.setIsExtensionsLoading?.(true);
|
||||
|
||||
// Initialize extension status tracking
|
||||
const extensionStatuses: Map<string, ExtensionLoadingStatus> = new Map(
|
||||
enabledExtensions.map((ext) => [ext.name, { name: ext.name, status: 'loading' as const }])
|
||||
);
|
||||
|
||||
// Show initial loading toast
|
||||
const updateToast = (isComplete: boolean = false) => {
|
||||
toastService.extensionLoading(
|
||||
Array.from(extensionStatuses.values()),
|
||||
enabledExtensions.length,
|
||||
isComplete
|
||||
);
|
||||
};
|
||||
|
||||
updateToast();
|
||||
|
||||
// Load extensions in parallel and update status
|
||||
const extensionLoadingPromises = enabledExtensions.map(async (extensionConfig) => {
|
||||
const extensionName = extensionConfig.name;
|
||||
|
||||
// SSE is unsupported - fail immediately without calling the backend
|
||||
if (extensionConfig.type === 'sse') {
|
||||
const errMsg = 'SSE is unsupported, migrate to streamable_http';
|
||||
extensionStatuses.set(extensionName, {
|
||||
name: extensionName,
|
||||
status: 'error',
|
||||
error: errMsg,
|
||||
recoverHints: createExtensionRecoverHints(errMsg),
|
||||
});
|
||||
updateToast();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await addToAgentOnStartup({
|
||||
extensionConfig,
|
||||
toastOptions: { silent: true }, // Silent since we're using grouped notification
|
||||
sessionId,
|
||||
});
|
||||
|
||||
// Update status to success
|
||||
extensionStatuses.set(extensionName, {
|
||||
name: extensionName,
|
||||
status: 'success',
|
||||
});
|
||||
updateToast();
|
||||
} catch (error) {
|
||||
console.error(`Failed to load extension ${extensionName}:`, error);
|
||||
|
||||
// Extract error message using shared utility
|
||||
const errMsg = errorMessage(error);
|
||||
|
||||
// Create recovery hints for "Ask goose" button
|
||||
const recoverHints = createExtensionRecoverHints(errMsg);
|
||||
|
||||
// Update status to error
|
||||
extensionStatuses.set(extensionName, {
|
||||
name: extensionName,
|
||||
status: 'error',
|
||||
error: errMsg,
|
||||
recoverHints,
|
||||
});
|
||||
updateToast();
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.allSettled(extensionLoadingPromises);
|
||||
|
||||
// Show final completion toast
|
||||
updateToast(true);
|
||||
|
||||
options?.setIsExtensionsLoading?.(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize agent:', error);
|
||||
options?.setIsExtensionsLoading?.(false);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export const getInitialWorkingDir = (): string => {
|
||||
return (window.appConfig?.get('GOOSE_WORKING_DIR') as string) || '';
|
||||
};
|
||||
Reference in New Issue
Block a user