merge: integrate v0.13.0rc with React Query refactor

Resolved conflict in use-project-settings-loader.ts:
- Keep React Query approach from upstream
- Add phaseModelOverrides loading for provider model persistence
- Update both currentProject and projects array to keep in sync
This commit is contained in:
Stefan de Vogelaere
2026-01-20 19:36:30 +01:00
133 changed files with 9283 additions and 3269 deletions

View File

@@ -5,59 +5,53 @@
* configuring which sources to load Skills from (user/project).
*/
import { useState } from 'react';
import { useCallback } from 'react';
import { useAppStore } from '@/store/app-store';
import { toast } from 'sonner';
import { getElectronAPI } from '@/lib/electron';
import { useUpdateGlobalSettings } from '@/hooks/mutations';
export function useSkillsSettings() {
const enabled = useAppStore((state) => state.enableSkills);
const sources = useAppStore((state) => state.skillsSources);
const [isLoading, setIsLoading] = useState(false);
const updateEnabled = async (newEnabled: boolean) => {
setIsLoading(true);
try {
const api = getElectronAPI();
if (!api.settings) {
throw new Error('Settings API not available');
}
await api.settings.updateGlobal({ enableSkills: newEnabled });
// Update local store after successful server update
useAppStore.setState({ enableSkills: newEnabled });
toast.success(newEnabled ? 'Skills enabled' : 'Skills disabled');
} catch (error) {
toast.error('Failed to update skills settings');
console.error(error);
} finally {
setIsLoading(false);
}
};
// React Query mutation (disable default toast)
const updateSettingsMutation = useUpdateGlobalSettings({ showSuccessToast: false });
const updateSources = async (newSources: Array<'user' | 'project'>) => {
setIsLoading(true);
try {
const api = getElectronAPI();
if (!api.settings) {
throw new Error('Settings API not available');
}
await api.settings.updateGlobal({ skillsSources: newSources });
// Update local store after successful server update
useAppStore.setState({ skillsSources: newSources });
toast.success('Skills sources updated');
} catch (error) {
toast.error('Failed to update skills sources');
console.error(error);
} finally {
setIsLoading(false);
}
};
const updateEnabled = useCallback(
(newEnabled: boolean) => {
updateSettingsMutation.mutate(
{ enableSkills: newEnabled },
{
onSuccess: () => {
useAppStore.setState({ enableSkills: newEnabled });
toast.success(newEnabled ? 'Skills enabled' : 'Skills disabled');
},
}
);
},
[updateSettingsMutation]
);
const updateSources = useCallback(
(newSources: Array<'user' | 'project'>) => {
updateSettingsMutation.mutate(
{ skillsSources: newSources },
{
onSuccess: () => {
useAppStore.setState({ skillsSources: newSources });
toast.success('Skills sources updated');
},
}
);
},
[updateSettingsMutation]
);
return {
enabled,
sources,
updateEnabled,
updateSources,
isLoading,
isLoading: updateSettingsMutation.isPending,
};
}

View File

@@ -5,59 +5,53 @@
* configuring which sources to load Subagents from (user/project).
*/
import { useState } from 'react';
import { useCallback } from 'react';
import { useAppStore } from '@/store/app-store';
import { toast } from 'sonner';
import { getElectronAPI } from '@/lib/electron';
import { useUpdateGlobalSettings } from '@/hooks/mutations';
export function useSubagentsSettings() {
const enabled = useAppStore((state) => state.enableSubagents);
const sources = useAppStore((state) => state.subagentsSources);
const [isLoading, setIsLoading] = useState(false);
const updateEnabled = async (newEnabled: boolean) => {
setIsLoading(true);
try {
const api = getElectronAPI();
if (!api.settings) {
throw new Error('Settings API not available');
}
await api.settings.updateGlobal({ enableSubagents: newEnabled });
// Update local store after successful server update
useAppStore.setState({ enableSubagents: newEnabled });
toast.success(newEnabled ? 'Subagents enabled' : 'Subagents disabled');
} catch (error) {
toast.error('Failed to update subagents settings');
console.error(error);
} finally {
setIsLoading(false);
}
};
// React Query mutation (disable default toast)
const updateSettingsMutation = useUpdateGlobalSettings({ showSuccessToast: false });
const updateSources = async (newSources: Array<'user' | 'project'>) => {
setIsLoading(true);
try {
const api = getElectronAPI();
if (!api.settings) {
throw new Error('Settings API not available');
}
await api.settings.updateGlobal({ subagentsSources: newSources });
// Update local store after successful server update
useAppStore.setState({ subagentsSources: newSources });
toast.success('Subagents sources updated');
} catch (error) {
toast.error('Failed to update subagents sources');
console.error(error);
} finally {
setIsLoading(false);
}
};
const updateEnabled = useCallback(
(newEnabled: boolean) => {
updateSettingsMutation.mutate(
{ enableSubagents: newEnabled },
{
onSuccess: () => {
useAppStore.setState({ enableSubagents: newEnabled });
toast.success(newEnabled ? 'Subagents enabled' : 'Subagents disabled');
},
}
);
},
[updateSettingsMutation]
);
const updateSources = useCallback(
(newSources: Array<'user' | 'project'>) => {
updateSettingsMutation.mutate(
{ subagentsSources: newSources },
{
onSuccess: () => {
useAppStore.setState({ subagentsSources: newSources });
toast.success('Subagents sources updated');
},
}
);
},
[updateSettingsMutation]
);
return {
enabled,
sources,
updateEnabled,
updateSources,
isLoading,
isLoading: updateSettingsMutation.isPending,
};
}

View File

@@ -9,10 +9,12 @@
* Agent definitions in settings JSON are used server-side only.
*/
import { useState, useEffect, useCallback } from 'react';
import { useMemo, useCallback } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { useAppStore } from '@/store/app-store';
import type { AgentDefinition } from '@automaker/types';
import { getElectronAPI } from '@/lib/electron';
import { useDiscoveredAgents } from '@/hooks/queries';
import { queryKeys } from '@/lib/query-keys';
export type SubagentScope = 'global' | 'project';
export type SubagentType = 'filesystem';
@@ -35,51 +37,40 @@ interface FilesystemAgent {
}
export function useSubagents() {
const queryClient = useQueryClient();
const currentProject = useAppStore((state) => state.currentProject);
const [isLoading, setIsLoading] = useState(false);
const [subagentsWithScope, setSubagentsWithScope] = useState<SubagentWithScope[]>([]);
// Fetch filesystem agents
const fetchFilesystemAgents = useCallback(async () => {
setIsLoading(true);
try {
const api = getElectronAPI();
if (!api.settings) {
console.warn('Settings API not available');
return;
}
const data = await api.settings.discoverAgents(currentProject?.path, ['user', 'project']);
// Use React Query hook for fetching agents
const {
data: agents = [],
isLoading,
refetch,
} = useDiscoveredAgents(currentProject?.path, ['user', 'project']);
if (data.success && data.agents) {
// Transform filesystem agents to SubagentWithScope format
const agents: SubagentWithScope[] = data.agents.map(
({ name, definition, source, filePath }: FilesystemAgent) => ({
name,
definition,
scope: source === 'user' ? 'global' : 'project',
type: 'filesystem' as const,
source,
filePath,
})
);
setSubagentsWithScope(agents);
}
} catch (error) {
console.error('Failed to fetch filesystem agents:', error);
} finally {
setIsLoading(false);
}
}, [currentProject?.path]);
// Transform agents to SubagentWithScope format
const subagentsWithScope = useMemo((): SubagentWithScope[] => {
return agents.map(({ name, definition, source, filePath }: FilesystemAgent) => ({
name,
definition,
scope: source === 'user' ? 'global' : 'project',
type: 'filesystem' as const,
source,
filePath,
}));
}, [agents]);
// Fetch filesystem agents on mount and when project changes
useEffect(() => {
fetchFilesystemAgents();
}, [fetchFilesystemAgents]);
// Refresh function that invalidates the query cache
const refreshFilesystemAgents = useCallback(async () => {
await queryClient.invalidateQueries({
queryKey: queryKeys.settings.agents(currentProject?.path ?? ''),
});
await refetch();
}, [queryClient, currentProject?.path, refetch]);
return {
subagentsWithScope,
isLoading,
hasProject: !!currentProject,
refreshFilesystemAgents: fetchFilesystemAgents,
refreshFilesystemAgents,
};
}