mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
- Fix window.Electron to window.isElectron in http-api-client.ts - Use void operator instead of async/await for onClick handlers in git-diff-panel.tsx - Fix critical bug: correct parameter order in useStartAutoMode (maxConcurrency was passed as branchName) - Add error handling for getApiKeys() result in use-cli-status.ts - Add authClaude guard in claude-cli-status.tsx for consistency with deauthClaude - Add optional chaining on api object in cursor-cli-status.tsx Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
399 lines
11 KiB
TypeScript
399 lines
11 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();
|
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
|
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();
|
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
|
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
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* const stopFeature = useStopFeature();
|
|
* // Simple stop
|
|
* stopFeature.mutate('feature-id');
|
|
* // Stop with project path for cache invalidation
|
|
* stopFeature.mutate({ featureId: 'feature-id', projectPath: '/path/to/project' });
|
|
* ```
|
|
*/
|
|
export function useStopFeature() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async (input: string | { featureId: string; projectPath?: string }) => {
|
|
const featureId = typeof input === 'string' ? input : input.featureId;
|
|
const api = getElectronAPI();
|
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
|
const result = await api.autoMode.stopFeature(featureId);
|
|
if (!result.success) {
|
|
throw new Error(result.error || 'Failed to stop feature');
|
|
}
|
|
// Return projectPath for use in onSuccess
|
|
return { ...result, projectPath: typeof input === 'string' ? undefined : input.projectPath };
|
|
},
|
|
onSuccess: (data) => {
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.runningAgents.all() });
|
|
// Also invalidate features cache if projectPath is provided
|
|
if (data.projectPath) {
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.features.all(data.projectPath) });
|
|
}
|
|
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();
|
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
|
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();
|
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
|
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();
|
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
|
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();
|
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
|
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();
|
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
|
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();
|
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
|
const result = await api.autoMode.start(projectPath, undefined, 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();
|
|
if (!api.autoMode) throw new Error('AutoMode API not available');
|
|
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,
|
|
});
|
|
},
|
|
});
|
|
}
|