Files
automaker/apps/ui/src/hooks/mutations/use-auto-mode-mutations.ts
Shirone 845674128e feat(ui): add React Query mutation hooks
- Add feature mutations (create, update, delete with optimistic updates)
- Add auto-mode mutations (start, stop, approve plan)
- Add worktree mutations (create, delete, checkout, switch branch)
- Add settings mutations (update global/project, validate API keys)
- Add GitHub mutations (create PR, validate PR)
- Add cursor permissions mutations (apply profile, copy config)
- Add spec mutations (generate, update, save)
- Add pipeline mutations (toggle, update config)
- Add session mutations with cache invalidation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 16:20:38 +01:00

374 lines
9.7 KiB
TypeScript

/**
* Auto Mode Mutations
*
* React Query mutations for auto mode operations like running features,
* stopping features, and plan approval.
*/
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { getElectronAPI } from '@/lib/electron';
import { queryKeys } from '@/lib/query-keys';
import { toast } from 'sonner';
/**
* Start running a feature in auto mode
*
* @param projectPath - Path to the project
* @returns Mutation for starting a feature
*
* @example
* ```tsx
* const startFeature = useStartFeature(projectPath);
* startFeature.mutate({ featureId: 'abc123', useWorktrees: true });
* ```
*/
export function useStartFeature(projectPath: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
featureId,
useWorktrees,
worktreePath,
}: {
featureId: string;
useWorktrees?: boolean;
worktreePath?: string;
}) => {
const api = getElectronAPI();
const result = await api.autoMode.runFeature(
projectPath,
featureId,
useWorktrees,
worktreePath
);
if (!result.success) {
throw new Error(result.error || 'Failed to start feature');
}
return result;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.runningAgents.all() });
queryClient.invalidateQueries({ queryKey: queryKeys.features.all(projectPath) });
},
onError: (error: Error) => {
toast.error('Failed to start feature', {
description: error.message,
});
},
});
}
/**
* Resume a paused or interrupted feature
*
* @param projectPath - Path to the project
* @returns Mutation for resuming a feature
*/
export function useResumeFeature(projectPath: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
featureId,
useWorktrees,
}: {
featureId: string;
useWorktrees?: boolean;
}) => {
const api = getElectronAPI();
const result = await api.autoMode.resumeFeature(projectPath, featureId, useWorktrees);
if (!result.success) {
throw new Error(result.error || 'Failed to resume feature');
}
return result;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.runningAgents.all() });
queryClient.invalidateQueries({ queryKey: queryKeys.features.all(projectPath) });
},
onError: (error: Error) => {
toast.error('Failed to resume feature', {
description: error.message,
});
},
});
}
/**
* Stop a running feature
*
* @returns Mutation for stopping a feature
*/
export function useStopFeature() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (featureId: string) => {
const api = getElectronAPI();
const result = await api.autoMode.stopFeature(featureId);
if (!result.success) {
throw new Error(result.error || 'Failed to stop feature');
}
return result;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.runningAgents.all() });
toast.success('Feature stopped');
},
onError: (error: Error) => {
toast.error('Failed to stop feature', {
description: error.message,
});
},
});
}
/**
* Verify a completed feature
*
* @param projectPath - Path to the project
* @returns Mutation for verifying a feature
*/
export function useVerifyFeature(projectPath: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (featureId: string) => {
const api = getElectronAPI();
const result = await api.autoMode.verifyFeature(projectPath, featureId);
if (!result.success) {
throw new Error(result.error || 'Failed to verify feature');
}
return result;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.features.all(projectPath) });
},
onError: (error: Error) => {
toast.error('Failed to verify feature', {
description: error.message,
});
},
});
}
/**
* Approve or reject a plan
*
* @param projectPath - Path to the project
* @returns Mutation for plan approval
*
* @example
* ```tsx
* const approvePlan = useApprovePlan(projectPath);
* approvePlan.mutate({ featureId: 'abc', approved: true });
* ```
*/
export function useApprovePlan(projectPath: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
featureId,
approved,
editedPlan,
feedback,
}: {
featureId: string;
approved: boolean;
editedPlan?: string;
feedback?: string;
}) => {
const api = getElectronAPI();
const result = await api.autoMode.approvePlan(
projectPath,
featureId,
approved,
editedPlan,
feedback
);
if (!result.success) {
throw new Error(result.error || 'Failed to submit plan decision');
}
return result;
},
onSuccess: (_, { approved }) => {
queryClient.invalidateQueries({ queryKey: queryKeys.features.all(projectPath) });
if (approved) {
toast.success('Plan approved');
} else {
toast.info('Plan rejected');
}
},
onError: (error: Error) => {
toast.error('Failed to submit plan decision', {
description: error.message,
});
},
});
}
/**
* Send a follow-up prompt to a feature
*
* @param projectPath - Path to the project
* @returns Mutation for sending follow-up
*/
export function useFollowUpFeature(projectPath: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({
featureId,
prompt,
imagePaths,
useWorktrees,
}: {
featureId: string;
prompt: string;
imagePaths?: string[];
useWorktrees?: boolean;
}) => {
const api = getElectronAPI();
const result = await api.autoMode.followUpFeature(
projectPath,
featureId,
prompt,
imagePaths,
useWorktrees
);
if (!result.success) {
throw new Error(result.error || 'Failed to send follow-up');
}
return result;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.runningAgents.all() });
queryClient.invalidateQueries({ queryKey: queryKeys.features.all(projectPath) });
},
onError: (error: Error) => {
toast.error('Failed to send follow-up', {
description: error.message,
});
},
});
}
/**
* Commit feature changes
*
* @param projectPath - Path to the project
* @returns Mutation for committing feature
*/
export function useCommitFeature(projectPath: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (featureId: string) => {
const api = getElectronAPI();
const result = await api.autoMode.commitFeature(projectPath, featureId);
if (!result.success) {
throw new Error(result.error || 'Failed to commit changes');
}
return result;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.features.all(projectPath) });
queryClient.invalidateQueries({ queryKey: queryKeys.worktrees.all(projectPath) });
toast.success('Changes committed');
},
onError: (error: Error) => {
toast.error('Failed to commit changes', {
description: error.message,
});
},
});
}
/**
* Analyze project structure
*
* @returns Mutation for project analysis
*/
export function useAnalyzeProject() {
return useMutation({
mutationFn: async (projectPath: string) => {
const api = getElectronAPI();
const result = await api.autoMode.analyzeProject(projectPath);
if (!result.success) {
throw new Error(result.error || 'Failed to analyze project');
}
return result;
},
onSuccess: () => {
toast.success('Project analysis started');
},
onError: (error: Error) => {
toast.error('Failed to analyze project', {
description: error.message,
});
},
});
}
/**
* Start auto mode for all pending features
*
* @param projectPath - Path to the project
* @returns Mutation for starting auto mode
*/
export function useStartAutoMode(projectPath: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (maxConcurrency?: number) => {
const api = getElectronAPI();
const result = await api.autoMode.start(projectPath, maxConcurrency);
if (!result.success) {
throw new Error(result.error || 'Failed to start auto mode');
}
return result;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.runningAgents.all() });
toast.success('Auto mode started');
},
onError: (error: Error) => {
toast.error('Failed to start auto mode', {
description: error.message,
});
},
});
}
/**
* Stop auto mode for all features
*
* @param projectPath - Path to the project
* @returns Mutation for stopping auto mode
*/
export function useStopAutoMode(projectPath: string) {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async () => {
const api = getElectronAPI();
const result = await api.autoMode.stop(projectPath);
if (!result.success) {
throw new Error(result.error || 'Failed to stop auto mode');
}
return result;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: queryKeys.runningAgents.all() });
toast.success('Auto mode stopped');
},
onError: (error: Error) => {
toast.error('Failed to stop auto mode', {
description: error.message,
});
},
});
}