fix: we load extensions when agent starts so don't do it up front (#6350)
This commit is contained in:
@@ -477,6 +477,11 @@ impl ExtensionManager {
|
|||||||
pub async fn add_extension(&self, config: ExtensionConfig) -> ExtensionResult<()> {
|
pub async fn add_extension(&self, config: ExtensionConfig) -> ExtensionResult<()> {
|
||||||
let config_name = config.key().to_string();
|
let config_name = config.key().to_string();
|
||||||
let sanitized_name = normalize(config_name.clone());
|
let sanitized_name = normalize(config_name.clone());
|
||||||
|
|
||||||
|
if self.extensions.lock().await.contains_key(&sanitized_name) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let mut temp_dir = None;
|
let mut temp_dir = None;
|
||||||
|
|
||||||
let client: Box<dyn McpClientTrait> = match &config {
|
let client: Box<dyn McpClientTrait> = match &config {
|
||||||
|
|||||||
+6
-42
@@ -39,7 +39,7 @@ import PermissionSettingsView from './components/settings/permission/PermissionS
|
|||||||
import ExtensionsView, { ExtensionsViewOptions } from './components/extensions/ExtensionsView';
|
import ExtensionsView, { ExtensionsViewOptions } from './components/extensions/ExtensionsView';
|
||||||
import RecipesView from './components/recipes/RecipesView';
|
import RecipesView from './components/recipes/RecipesView';
|
||||||
import { View, ViewOptions } from './utils/navigationUtils';
|
import { View, ViewOptions } from './utils/navigationUtils';
|
||||||
import { NoProviderOrModelError, useAgent } from './hooks/useAgent';
|
|
||||||
import { useNavigation } from './hooks/useNavigation';
|
import { useNavigation } from './hooks/useNavigation';
|
||||||
import { errorMessage } from './utils/conversionUtils';
|
import { errorMessage } from './utils/conversionUtils';
|
||||||
import { usePageViewTracking } from './hooks/useAnalytics';
|
import { usePageViewTracking } from './hooks/useAnalytics';
|
||||||
@@ -51,10 +51,10 @@ function PageViewTracker() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Route Components
|
// Route Components
|
||||||
const HubRouteWrapper = ({ isExtensionsLoading }: { isExtensionsLoading: boolean }) => {
|
const HubRouteWrapper = () => {
|
||||||
const setView = useNavigation();
|
const setView = useNavigation();
|
||||||
|
|
||||||
return <Hub setView={setView} isExtensionsLoading={isExtensionsLoading} />;
|
return <Hub setView={setView} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PairRouteWrapper = ({
|
const PairRouteWrapper = ({
|
||||||
@@ -363,15 +363,12 @@ const ExtensionsRoute = () => {
|
|||||||
|
|
||||||
export function AppInner() {
|
export function AppInner() {
|
||||||
const [fatalError, setFatalError] = useState<string | null>(null);
|
const [fatalError, setFatalError] = useState<string | null>(null);
|
||||||
const [agentWaitingMessage, setAgentWaitingMessage] = useState<string | null>(null);
|
|
||||||
const [isLoadingSharedSession, setIsLoadingSharedSession] = useState(false);
|
const [isLoadingSharedSession, setIsLoadingSharedSession] = useState(false);
|
||||||
const [sharedSessionError, setSharedSessionError] = useState<string | null>(null);
|
const [sharedSessionError, setSharedSessionError] = useState<string | null>(null);
|
||||||
const [isExtensionsLoading, setIsExtensionsLoading] = useState(false);
|
|
||||||
const [didSelectProvider, setDidSelectProvider] = useState<boolean>(false);
|
const [didSelectProvider, setDidSelectProvider] = useState<boolean>(false);
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const setView = useNavigation();
|
const setView = useNavigation();
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
const [chat, setChat] = useState<ChatType>({
|
const [chat, setChat] = useState<ChatType>({
|
||||||
sessionId: '',
|
sessionId: '',
|
||||||
@@ -384,7 +381,6 @@ export function AppInner() {
|
|||||||
const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
|
const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
|
||||||
|
|
||||||
const { addExtension } = useConfig();
|
const { addExtension } = useConfig();
|
||||||
const { loadCurrentChat } = useAgent();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('Sending reactReady signal to Electron');
|
console.log('Sending reactReady signal to Electron');
|
||||||
@@ -398,28 +394,6 @@ export function AppInner() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Handle URL parameters and deeplinks on app startup
|
|
||||||
const loadingHub = location.pathname === '/';
|
|
||||||
useEffect(() => {
|
|
||||||
if (loadingHub) {
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
const loadedChat = await loadCurrentChat({
|
|
||||||
setAgentWaitingMessage,
|
|
||||||
setIsExtensionsLoading,
|
|
||||||
});
|
|
||||||
setChat(loadedChat);
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof NoProviderOrModelError) {
|
|
||||||
// the onboarding flow will trigger
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
}, [loadCurrentChat, setAgentWaitingMessage, navigate, loadingHub, setChat]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleOpenSharedSession = async (_event: IpcRendererEvent, ...args: unknown[]) => {
|
const handleOpenSharedSession = async (_event: IpcRendererEvent, ...args: unknown[]) => {
|
||||||
const link = args[0] as string;
|
const link = args[0] as string;
|
||||||
@@ -616,18 +590,13 @@ export function AppInner() {
|
|||||||
path="/"
|
path="/"
|
||||||
element={
|
element={
|
||||||
<ProviderGuard didSelectProvider={didSelectProvider}>
|
<ProviderGuard didSelectProvider={didSelectProvider}>
|
||||||
<ChatProvider
|
<ChatProvider chat={chat} setChat={setChat} contextKey="hub">
|
||||||
chat={chat}
|
|
||||||
setChat={setChat}
|
|
||||||
contextKey="hub"
|
|
||||||
agentWaitingMessage={agentWaitingMessage}
|
|
||||||
>
|
|
||||||
<AppLayout />
|
<AppLayout />
|
||||||
</ChatProvider>
|
</ChatProvider>
|
||||||
</ProviderGuard>
|
</ProviderGuard>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Route index element={<HubRouteWrapper isExtensionsLoading={isExtensionsLoading} />} />
|
<Route index element={<HubRouteWrapper />} />
|
||||||
<Route
|
<Route
|
||||||
path="pair"
|
path="pair"
|
||||||
element={
|
element={
|
||||||
@@ -643,12 +612,7 @@ export function AppInner() {
|
|||||||
<Route
|
<Route
|
||||||
path="extensions"
|
path="extensions"
|
||||||
element={
|
element={
|
||||||
<ChatProvider
|
<ChatProvider chat={chat} setChat={setChat} contextKey="extensions">
|
||||||
chat={chat}
|
|
||||||
setChat={setChat}
|
|
||||||
contextKey="extensions"
|
|
||||||
agentWaitingMessage={agentWaitingMessage}
|
|
||||||
>
|
|
||||||
<ExtensionsRoute />
|
<ExtensionsRoute />
|
||||||
</ChatProvider>
|
</ChatProvider>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ interface ChatInputProps {
|
|||||||
initialPrompt?: string;
|
initialPrompt?: string;
|
||||||
toolCount: number;
|
toolCount: number;
|
||||||
append?: (message: Message) => void;
|
append?: (message: Message) => void;
|
||||||
isExtensionsLoading?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ChatInput({
|
export default function ChatInput({
|
||||||
@@ -121,7 +120,6 @@ export default function ChatInput({
|
|||||||
initialPrompt,
|
initialPrompt,
|
||||||
toolCount,
|
toolCount,
|
||||||
append: _append,
|
append: _append,
|
||||||
isExtensionsLoading = false,
|
|
||||||
}: ChatInputProps) {
|
}: ChatInputProps) {
|
||||||
const [_value, setValue] = useState(initialValue);
|
const [_value, setValue] = useState(initialValue);
|
||||||
const [displayValue, setDisplayValue] = useState(initialValue); // For immediate visual feedback
|
const [displayValue, setDisplayValue] = useState(initialValue); // For immediate visual feedback
|
||||||
@@ -1109,8 +1107,7 @@ export default function ChatInput({
|
|||||||
isAnyImageLoading ||
|
isAnyImageLoading ||
|
||||||
isAnyDroppedFileLoading ||
|
isAnyDroppedFileLoading ||
|
||||||
isRecording ||
|
isRecording ||
|
||||||
isTranscribing ||
|
isTranscribing;
|
||||||
isExtensionsLoading;
|
|
||||||
|
|
||||||
// Queue management functions - no storage persistence, only in-memory
|
// Queue management functions - no storage persistence, only in-memory
|
||||||
const handleRemoveQueuedMessage = (messageId: string) => {
|
const handleRemoveQueuedMessage = (messageId: string) => {
|
||||||
@@ -1353,17 +1350,15 @@ export default function ChatInput({
|
|||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>
|
<p>
|
||||||
{isExtensionsLoading
|
{isAnyImageLoading
|
||||||
? 'Loading extensions...'
|
? 'Waiting for images to save...'
|
||||||
: isAnyImageLoading
|
: isAnyDroppedFileLoading
|
||||||
? 'Waiting for images to save...'
|
? 'Processing dropped files...'
|
||||||
: isAnyDroppedFileLoading
|
: isRecording
|
||||||
? 'Processing dropped files...'
|
? 'Recording...'
|
||||||
: isRecording
|
: isTranscribing
|
||||||
? 'Recording...'
|
? 'Transcribing...'
|
||||||
: isTranscribing
|
: 'Send'}
|
||||||
? 'Transcribing...'
|
|
||||||
: 'Send'}
|
|
||||||
</p>
|
</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -23,10 +23,8 @@ import { startNewSession } from '../sessions';
|
|||||||
|
|
||||||
export default function Hub({
|
export default function Hub({
|
||||||
setView,
|
setView,
|
||||||
isExtensionsLoading,
|
|
||||||
}: {
|
}: {
|
||||||
setView: (view: View, viewOptions?: ViewOptions) => void;
|
setView: (view: View, viewOptions?: ViewOptions) => void;
|
||||||
isExtensionsLoading: boolean;
|
|
||||||
}) {
|
}) {
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
const customEvent = e as unknown as CustomEvent;
|
const customEvent = e as unknown as CustomEvent;
|
||||||
@@ -59,7 +57,6 @@ export default function Hub({
|
|||||||
messages={[]}
|
messages={[]}
|
||||||
disableAnimation={false}
|
disableAnimation={false}
|
||||||
sessionCosts={undefined}
|
sessionCosts={undefined}
|
||||||
isExtensionsLoading={isExtensionsLoading}
|
|
||||||
toolCount={0}
|
toolCount={0}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ interface ChatContextType {
|
|||||||
clearRecipe: () => void;
|
clearRecipe: () => void;
|
||||||
// Context identification
|
// Context identification
|
||||||
contextKey: string; // 'hub' or 'pair-{sessionId}'
|
contextKey: string; // 'hub' or 'pair-{sessionId}'
|
||||||
agentWaitingMessage: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatContext = createContext<ChatContextType | undefined>(undefined);
|
const ChatContext = createContext<ChatContextType | undefined>(undefined);
|
||||||
@@ -24,14 +23,12 @@ interface ChatProviderProps {
|
|||||||
chat: ChatType;
|
chat: ChatType;
|
||||||
setChat: (chat: ChatType) => void;
|
setChat: (chat: ChatType) => void;
|
||||||
contextKey?: string; // Optional context key, defaults to 'hub'
|
contextKey?: string; // Optional context key, defaults to 'hub'
|
||||||
agentWaitingMessage: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChatProvider: React.FC<ChatProviderProps> = ({
|
export const ChatProvider: React.FC<ChatProviderProps> = ({
|
||||||
children,
|
children,
|
||||||
chat,
|
chat,
|
||||||
setChat,
|
setChat,
|
||||||
agentWaitingMessage,
|
|
||||||
contextKey = 'hub',
|
contextKey = 'hub',
|
||||||
}) => {
|
}) => {
|
||||||
const resetChat = () => {
|
const resetChat = () => {
|
||||||
@@ -69,7 +66,6 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
|
|||||||
setRecipe,
|
setRecipe,
|
||||||
clearRecipe,
|
clearRecipe,
|
||||||
contextKey,
|
contextKey,
|
||||||
agentWaitingMessage,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
|
return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
|
||||||
|
|||||||
@@ -1,329 +0,0 @@
|
|||||||
import { useCallback, useRef, useState } from 'react';
|
|
||||||
import { useConfig } from '../components/ConfigContext';
|
|
||||||
import { ChatType } from '../types/chat';
|
|
||||||
import { initializeSystem } from '../utils/providerUtils';
|
|
||||||
import {
|
|
||||||
backupConfig,
|
|
||||||
initConfig,
|
|
||||||
readAllConfig,
|
|
||||||
Recipe,
|
|
||||||
recoverConfig,
|
|
||||||
resumeAgent,
|
|
||||||
startAgent,
|
|
||||||
validateConfig,
|
|
||||||
} from '../api';
|
|
||||||
|
|
||||||
export enum AgentState {
|
|
||||||
UNINITIALIZED = 'uninitialized',
|
|
||||||
INITIALIZING = 'initializing',
|
|
||||||
NO_PROVIDER = 'no_provider',
|
|
||||||
INITIALIZED = 'initialized',
|
|
||||||
ERROR = 'error',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InitializationContext {
|
|
||||||
recipe?: Recipe;
|
|
||||||
resumeSessionId?: string;
|
|
||||||
setAgentWaitingMessage: (msg: string | null) => void;
|
|
||||||
setIsExtensionsLoading?: (isLoading: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface UseAgentReturn {
|
|
||||||
agentState: AgentState;
|
|
||||||
resetChat: () => void;
|
|
||||||
loadCurrentChat: (context: InitializationContext) => Promise<ChatType>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NoProviderOrModelError extends Error {
|
|
||||||
constructor() {
|
|
||||||
super('No provider or model configured');
|
|
||||||
this.name = this.constructor.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useAgent(): UseAgentReturn {
|
|
||||||
const [agentState, setAgentState] = useState<AgentState>(AgentState.UNINITIALIZED);
|
|
||||||
const [sessionId, setSessionId] = useState<string | null>(null);
|
|
||||||
const initPromiseRef = useRef<Promise<ChatType> | null>(null);
|
|
||||||
const deletedSessionsRef = useRef<Set<string>>(new Set());
|
|
||||||
const recipeIdFromConfig = useRef<string | null>(
|
|
||||||
(window.appConfig.get('recipeId') as string | null | undefined) ?? null
|
|
||||||
);
|
|
||||||
const recipeDeeplinkFromConfig = useRef<string | null>(
|
|
||||||
(window.appConfig.get('recipeDeeplink') as string | null | undefined) ?? null
|
|
||||||
);
|
|
||||||
const scheduledJobIdFromConfig = useRef<string | null>(
|
|
||||||
(window.appConfig.get('scheduledJobId') as string | null | undefined) ?? null
|
|
||||||
);
|
|
||||||
const { getExtensions, addExtension, read } = useConfig();
|
|
||||||
|
|
||||||
const resetChat = useCallback(() => {
|
|
||||||
setSessionId(null);
|
|
||||||
setAgentState(AgentState.UNINITIALIZED);
|
|
||||||
recipeIdFromConfig.current = null;
|
|
||||||
recipeDeeplinkFromConfig.current = null;
|
|
||||||
scheduledJobIdFromConfig.current = null;
|
|
||||||
deletedSessionsRef.current.clear();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const agentIsInitialized = agentState === AgentState.INITIALIZED;
|
|
||||||
const currentChat = useCallback(
|
|
||||||
async (initContext: InitializationContext): Promise<ChatType> => {
|
|
||||||
// Skip deleted sessions
|
|
||||||
if (
|
|
||||||
initContext.resumeSessionId &&
|
|
||||||
deletedSessionsRef.current.has(initContext.resumeSessionId)
|
|
||||||
) {
|
|
||||||
initContext.resumeSessionId = undefined;
|
|
||||||
|
|
||||||
// Clear from URL
|
|
||||||
const url = new URL(window.location.href);
|
|
||||||
url.searchParams.delete('resumeSessionId');
|
|
||||||
window.history.replaceState({}, '', url.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sessionId && deletedSessionsRef.current.has(sessionId)) {
|
|
||||||
setSessionId(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (agentIsInitialized && sessionId && !deletedSessionsRef.current.has(sessionId)) {
|
|
||||||
let agentResponse;
|
|
||||||
try {
|
|
||||||
agentResponse = await resumeAgent({
|
|
||||||
body: {
|
|
||||||
session_id: sessionId,
|
|
||||||
load_model_and_extensions: false,
|
|
||||||
},
|
|
||||||
throwOnError: true,
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
// Mark session as deleted and clear state
|
|
||||||
deletedSessionsRef.current.add(sessionId);
|
|
||||||
setSessionId(null);
|
|
||||||
|
|
||||||
// Clear from URL
|
|
||||||
const url = new URL(window.location.href);
|
|
||||||
if (url.searchParams.get('resumeSessionId')) {
|
|
||||||
url.searchParams.delete('resumeSessionId');
|
|
||||||
window.history.replaceState({}, '', url.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall through to create new session
|
|
||||||
if (agentResponse?.data) {
|
|
||||||
const agentSession = agentResponse.data;
|
|
||||||
const messages = agentSession.conversation || [];
|
|
||||||
return {
|
|
||||||
sessionId: agentSession.id,
|
|
||||||
name: agentSession.recipe?.title || agentSession.name,
|
|
||||||
messages,
|
|
||||||
recipe: agentSession.recipe,
|
|
||||||
recipeParameterValues: agentSession.user_recipe_values || null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initPromiseRef.current) {
|
|
||||||
return initPromiseRef.current;
|
|
||||||
}
|
|
||||||
|
|
||||||
const initPromise = (async () => {
|
|
||||||
setAgentState(AgentState.INITIALIZING);
|
|
||||||
const agentWaitingMessage = initContext.setAgentWaitingMessage;
|
|
||||||
agentWaitingMessage('Agent is initializing');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const config = window.electron.getConfig();
|
|
||||||
const provider = (await read('GOOSE_PROVIDER', false)) ?? config.GOOSE_DEFAULT_PROVIDER;
|
|
||||||
const model = (await read('GOOSE_MODEL', false)) ?? config.GOOSE_DEFAULT_MODEL;
|
|
||||||
|
|
||||||
if (!provider || !model) {
|
|
||||||
setAgentState(AgentState.NO_PROVIDER);
|
|
||||||
throw new NoProviderOrModelError();
|
|
||||||
}
|
|
||||||
|
|
||||||
let agentResponse;
|
|
||||||
try {
|
|
||||||
agentResponse = initContext.resumeSessionId
|
|
||||||
? await resumeAgent({
|
|
||||||
body: {
|
|
||||||
session_id: initContext.resumeSessionId,
|
|
||||||
load_model_and_extensions: false,
|
|
||||||
},
|
|
||||||
throwOnError: true,
|
|
||||||
})
|
|
||||||
: await startAgent({
|
|
||||||
body: {
|
|
||||||
working_dir: window.appConfig.get('GOOSE_WORKING_DIR') as string,
|
|
||||||
...buildRecipeInput(
|
|
||||||
initContext.recipe,
|
|
||||||
recipeIdFromConfig.current,
|
|
||||||
recipeDeeplinkFromConfig.current
|
|
||||||
),
|
|
||||||
},
|
|
||||||
throwOnError: true,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
// If resuming fails, mark session as deleted and create new agent
|
|
||||||
if (initContext.resumeSessionId) {
|
|
||||||
deletedSessionsRef.current.add(initContext.resumeSessionId);
|
|
||||||
|
|
||||||
// Clear from URL
|
|
||||||
const url = new URL(window.location.href);
|
|
||||||
url.searchParams.delete('resumeSessionId');
|
|
||||||
window.history.replaceState({}, '', url.toString());
|
|
||||||
|
|
||||||
agentResponse = await startAgent({
|
|
||||||
body: {
|
|
||||||
working_dir: window.appConfig.get('GOOSE_WORKING_DIR') as string,
|
|
||||||
...buildRecipeInput(
|
|
||||||
initContext.recipe,
|
|
||||||
recipeIdFromConfig.current,
|
|
||||||
recipeDeeplinkFromConfig.current
|
|
||||||
),
|
|
||||||
},
|
|
||||||
throwOnError: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clear resume flag
|
|
||||||
initContext.resumeSessionId = undefined;
|
|
||||||
} else {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const agentSession = agentResponse.data;
|
|
||||||
if (!agentSession) {
|
|
||||||
throw Error('Failed to get session info');
|
|
||||||
}
|
|
||||||
setSessionId(agentSession.id);
|
|
||||||
|
|
||||||
if (!initContext.recipe && agentSession.recipe && scheduledJobIdFromConfig.current) {
|
|
||||||
agentSession.recipe = {
|
|
||||||
...agentSession.recipe,
|
|
||||||
scheduledJobId: scheduledJobIdFromConfig.current,
|
|
||||||
isScheduledExecution: true,
|
|
||||||
} as Recipe;
|
|
||||||
scheduledJobIdFromConfig.current = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
recipeIdFromConfig.current = null;
|
|
||||||
recipeDeeplinkFromConfig.current = null;
|
|
||||||
|
|
||||||
agentWaitingMessage('Agent is loading config');
|
|
||||||
|
|
||||||
await initConfig();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await readAllConfig({ throwOnError: true });
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Initial config read failed, attempting recovery:', error);
|
|
||||||
await handleConfigRecovery();
|
|
||||||
}
|
|
||||||
|
|
||||||
agentWaitingMessage('Extensions are loading');
|
|
||||||
|
|
||||||
const recipeForInit = initContext.recipe || agentSession.recipe || undefined;
|
|
||||||
await initializeSystem(agentSession.id, provider as string, model as string, {
|
|
||||||
getExtensions,
|
|
||||||
addExtension,
|
|
||||||
setIsExtensionsLoading: initContext.setIsExtensionsLoading,
|
|
||||||
recipeParameters: agentSession.user_recipe_values,
|
|
||||||
recipe: recipeForInit,
|
|
||||||
});
|
|
||||||
|
|
||||||
const recipe = initContext.recipe || agentSession.recipe;
|
|
||||||
const conversation = agentSession.conversation || [];
|
|
||||||
// If we're loading a recipe from initContext (new recipe load), start with empty messages
|
|
||||||
// Otherwise, use the messages from the session
|
|
||||||
const messages = initContext.recipe && !initContext.resumeSessionId ? [] : conversation;
|
|
||||||
let initChat: ChatType = {
|
|
||||||
sessionId: agentSession.id,
|
|
||||||
name: agentSession.recipe?.title || agentSession.name,
|
|
||||||
messages: messages,
|
|
||||||
recipe: recipe,
|
|
||||||
recipeParameterValues: agentSession.user_recipe_values || null,
|
|
||||||
};
|
|
||||||
|
|
||||||
setAgentState(AgentState.INITIALIZED);
|
|
||||||
|
|
||||||
return initChat;
|
|
||||||
} catch (error) {
|
|
||||||
if (
|
|
||||||
(error + '').includes('Failed to create provider') ||
|
|
||||||
error instanceof NoProviderOrModelError
|
|
||||||
) {
|
|
||||||
setAgentState(AgentState.NO_PROVIDER);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
setAgentState(AgentState.ERROR);
|
|
||||||
if (typeof error === 'object' && error !== null && 'message' in error) {
|
|
||||||
let error_message = error.message as string;
|
|
||||||
throw new Error(error_message);
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
} finally {
|
|
||||||
agentWaitingMessage(null);
|
|
||||||
initPromiseRef.current = null;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
initPromiseRef.current = initPromise;
|
|
||||||
return initPromise;
|
|
||||||
},
|
|
||||||
[agentIsInitialized, sessionId, read, getExtensions, addExtension]
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
agentState,
|
|
||||||
resetChat,
|
|
||||||
loadCurrentChat: currentChat,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleConfigRecovery = async () => {
|
|
||||||
const configVersion = localStorage.getItem('configVersion');
|
|
||||||
const shouldMigrateExtensions = !configVersion || parseInt(configVersion, 10) < 3;
|
|
||||||
|
|
||||||
if (shouldMigrateExtensions) {
|
|
||||||
try {
|
|
||||||
await backupConfig({ throwOnError: true });
|
|
||||||
await initConfig();
|
|
||||||
} catch (migrationError) {
|
|
||||||
console.error('Migration failed:', migrationError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await validateConfig({ throwOnError: true });
|
|
||||||
await readAllConfig({ throwOnError: true });
|
|
||||||
} catch {
|
|
||||||
try {
|
|
||||||
await recoverConfig({ throwOnError: true });
|
|
||||||
await readAllConfig({ throwOnError: true });
|
|
||||||
} catch {
|
|
||||||
console.warn('Config recovery failed, reinitializing...');
|
|
||||||
await initConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildRecipeInput = (
|
|
||||||
recipeOverride?: Recipe,
|
|
||||||
recipeId?: string | null,
|
|
||||||
recipeDeeplink?: string | null
|
|
||||||
) => {
|
|
||||||
if (recipeId) {
|
|
||||||
return { recipe_id: recipeId };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recipeDeeplink) {
|
|
||||||
return { recipe_deeplink: recipeDeeplink };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recipeOverride) {
|
|
||||||
return { recipe: recipeOverride };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user