feat: implement running agents view and enhance auto mode functionality

- Added a new `RunningAgentsView` component to display currently active agents working on features.
- Implemented auto-refresh functionality for the running agents list every 2 seconds.
- Enhanced the auto mode service to support project-specific operations, including starting and stopping auto mode for individual projects.
- Updated IPC handlers to manage auto mode status and running agents more effectively.
- Introduced audio settings to mute notifications when agents complete tasks.
- Refactored existing components to accommodate new features and improve overall user experience.
This commit is contained in:
Cody Seibert
2025-12-10 21:51:00 -05:00
parent 5ac81ce5a9
commit d08f922631
24 changed files with 1450 additions and 405 deletions

View File

@@ -58,6 +58,26 @@ import type {
// Feature type - Import from app-store
import type { Feature } from "@/store/app-store";
// Running Agent type
export interface RunningAgent {
featureId: string;
projectPath: string;
projectName: string;
isAutoMode: boolean;
}
export interface RunningAgentsResult {
success: boolean;
runningAgents?: RunningAgent[];
totalCount?: number;
autoLoopRunning?: boolean;
error?: string;
}
export interface RunningAgentsAPI {
getAll: () => Promise<RunningAgentsResult>;
}
// Feature Suggestions types
export interface FeatureSuggestion {
id: string;
@@ -81,9 +101,12 @@ export interface SuggestionsEvent {
error?: string;
}
export type SuggestionType = "features" | "refactoring" | "security" | "performance";
export interface SuggestionsAPI {
generate: (
projectPath: string
projectPath: string,
suggestionType?: SuggestionType
) => Promise<{ success: boolean; error?: string }>;
stop: () => Promise<{ success: boolean; error?: string }>;
status: () => Promise<{
@@ -153,15 +176,18 @@ export interface AutoModeAPI {
projectPath: string,
maxConcurrency?: number
) => Promise<{ success: boolean; error?: string }>;
stop: () => Promise<{ success: boolean; error?: string }>;
stop: (projectPath: string) => Promise<{ success: boolean; error?: string; runningFeatures?: number }>;
stopFeature: (
featureId: string
) => Promise<{ success: boolean; error?: string }>;
status: () => Promise<{
status: (projectPath?: string) => Promise<{
success: boolean;
isRunning?: boolean;
autoLoopRunning?: boolean; // Backend uses this name instead of isRunning
currentFeatureId?: string | null;
runningFeatures?: string[];
runningProjects?: string[];
runningCount?: number;
error?: string;
}>;
runFeature: (
@@ -205,6 +231,7 @@ export interface SaveImageResult {
export interface ElectronAPI {
ping: () => Promise<string>;
openExternalLink: (url: string) => Promise<{ success: boolean; error?: string }>;
openDirectory: () => Promise<DialogResult>;
openFile: (options?: object) => Promise<DialogResult>;
readFile: (filePath: string) => Promise<FileResult>;
@@ -276,6 +303,7 @@ export interface ElectronAPI {
specRegeneration?: SpecRegenerationAPI;
autoMode?: AutoModeAPI;
features?: FeaturesAPI;
runningAgents?: RunningAgentsAPI;
setup?: {
getClaudeStatus: () => Promise<{
success: boolean;
@@ -392,6 +420,12 @@ export const getElectronAPI = (): ElectronAPI => {
return {
ping: async () => "pong (mock)",
openExternalLink: async (url: string) => {
// In web mode, open in a new tab
window.open(url, "_blank", "noopener,noreferrer");
return { success: true };
},
openDirectory: async () => {
// In web mode, we'll use a prompt to simulate directory selection
const path = prompt(
@@ -670,6 +704,9 @@ export const getElectronAPI = (): ElectronAPI => {
// Mock Features API
features: createMockFeaturesAPI(),
// Mock Running Agents API
runningAgents: createMockRunningAgentsAPI(),
};
};
@@ -1010,13 +1047,14 @@ function createMockAutoModeAPI(): AutoModeAPI {
return { success: true };
},
stop: async () => {
stop: async (_projectPath: string) => {
mockAutoModeRunning = false;
const runningCount = mockRunningFeatures.size;
mockRunningFeatures.clear();
// Clear all timeouts
mockAutoModeTimeouts.forEach((timeout) => clearTimeout(timeout));
mockAutoModeTimeouts.clear();
return { success: true };
return { success: true, runningFeatures: runningCount };
},
stopFeature: async (featureId: string) => {
@@ -1045,12 +1083,14 @@ function createMockAutoModeAPI(): AutoModeAPI {
return { success: true };
},
status: async () => {
status: async (_projectPath?: string) => {
return {
success: true,
isRunning: mockAutoModeRunning,
autoLoopRunning: mockAutoModeRunning,
currentFeatureId: mockAutoModeRunning ? "feature-0" : null,
runningFeatures: Array.from(mockRunningFeatures),
runningCount: mockRunningFeatures.size,
};
},
@@ -1431,7 +1471,7 @@ let mockSuggestionsTimeout: NodeJS.Timeout | null = null;
function createMockSuggestionsAPI(): SuggestionsAPI {
return {
generate: async (projectPath: string) => {
generate: async (projectPath: string, suggestionType: SuggestionType = "features") => {
if (mockSuggestionsRunning) {
return {
success: false,
@@ -1440,10 +1480,10 @@ function createMockSuggestionsAPI(): SuggestionsAPI {
}
mockSuggestionsRunning = true;
console.log(`[Mock] Generating suggestions for: ${projectPath}`);
console.log(`[Mock] Generating ${suggestionType} suggestions for: ${projectPath}`);
// Simulate async suggestion generation
simulateSuggestionsGeneration();
simulateSuggestionsGeneration(suggestionType);
return { success: true };
},
@@ -1479,11 +1519,18 @@ function emitSuggestionsEvent(event: SuggestionsEvent) {
mockSuggestionsCallbacks.forEach((cb) => cb(event));
}
async function simulateSuggestionsGeneration() {
async function simulateSuggestionsGeneration(suggestionType: SuggestionType = "features") {
const typeLabels: Record<SuggestionType, string> = {
features: "feature suggestions",
refactoring: "refactoring opportunities",
security: "security vulnerabilities",
performance: "performance issues",
};
// Emit progress events
emitSuggestionsEvent({
type: "suggestions_progress",
content: "Starting project analysis...\n",
content: `Starting project analysis for ${typeLabels[suggestionType]}...\n`,
});
await new Promise((resolve) => {
@@ -1514,7 +1561,7 @@ async function simulateSuggestionsGeneration() {
emitSuggestionsEvent({
type: "suggestions_progress",
content: "Identifying missing features...\n",
content: `Identifying ${typeLabels[suggestionType]}...\n`,
});
await new Promise((resolve) => {
@@ -1522,75 +1569,184 @@ async function simulateSuggestionsGeneration() {
});
if (!mockSuggestionsRunning) return;
// Generate mock suggestions
const mockSuggestions: FeatureSuggestion[] = [
{
id: `suggestion-${Date.now()}-0`,
category: "User Experience",
description: "Add dark mode toggle with system preference detection",
steps: [
"Create a ThemeProvider context to manage theme state",
"Add a toggle component in the settings or header",
"Implement CSS variables for theme colors",
"Add localStorage persistence for user preference",
],
priority: 1,
reasoning:
"Dark mode is a standard feature that improves accessibility and user comfort",
},
{
id: `suggestion-${Date.now()}-1`,
category: "Performance",
description: "Implement lazy loading for heavy components",
steps: [
"Identify components that are heavy or rarely used",
"Use React.lazy() and Suspense for code splitting",
"Add loading states for lazy-loaded components",
],
priority: 2,
reasoning: "Improves initial load time and reduces bundle size",
},
{
id: `suggestion-${Date.now()}-2`,
category: "Accessibility",
description: "Add keyboard navigation support throughout the app",
steps: [
"Implement focus management for modals and dialogs",
"Add keyboard shortcuts for common actions",
"Ensure all interactive elements are focusable",
"Add ARIA labels and roles where needed",
],
priority: 3,
reasoning:
"Improves accessibility for users who rely on keyboard navigation",
},
{
id: `suggestion-${Date.now()}-3`,
category: "Testing",
description: "Add comprehensive unit test coverage",
steps: [
"Set up Jest and React Testing Library",
"Create tests for all utility functions",
"Add component tests for critical UI elements",
"Set up CI pipeline for automated testing",
],
priority: 4,
reasoning: "Ensures code quality and prevents regressions",
},
{
id: `suggestion-${Date.now()}-4`,
category: "Developer Experience",
description: "Add Storybook for component documentation",
steps: [
"Install and configure Storybook",
"Create stories for all UI components",
"Add interaction tests using play functions",
"Set up Chromatic for visual regression testing",
],
priority: 5,
reasoning: "Improves component development workflow and documentation",
},
];
// Generate mock suggestions based on type
let mockSuggestions: FeatureSuggestion[];
switch (suggestionType) {
case "refactoring":
mockSuggestions = [
{
id: `suggestion-${Date.now()}-0`,
category: "Code Smell",
description: "Extract duplicate validation logic into reusable utility",
steps: [
"Identify all files with similar validation patterns",
"Create a validation utilities module",
"Replace duplicate code with utility calls",
"Add unit tests for the new utilities",
],
priority: 1,
reasoning: "Reduces code duplication and improves maintainability",
},
{
id: `suggestion-${Date.now()}-1`,
category: "Complexity",
description: "Break down large handleSubmit function into smaller functions",
steps: [
"Identify the handleSubmit function in form components",
"Extract validation logic into separate function",
"Extract API call logic into separate function",
"Extract success/error handling into separate functions",
],
priority: 2,
reasoning: "Function is too long and handles multiple responsibilities",
},
{
id: `suggestion-${Date.now()}-2`,
category: "Architecture",
description: "Move business logic out of React components into hooks",
steps: [
"Identify business logic in component files",
"Create custom hooks for reusable logic",
"Update components to use the new hooks",
"Add tests for the extracted hooks",
],
priority: 3,
reasoning: "Improves separation of concerns and testability",
},
];
break;
case "security":
mockSuggestions = [
{
id: `suggestion-${Date.now()}-0`,
category: "High",
description: "Sanitize user input before rendering to prevent XSS",
steps: [
"Audit all places where user input is rendered",
"Implement input sanitization using DOMPurify",
"Add Content-Security-Policy headers",
"Test with common XSS payloads",
],
priority: 1,
reasoning: "User input is rendered without proper sanitization",
},
{
id: `suggestion-${Date.now()}-1`,
category: "Medium",
description: "Add rate limiting to authentication endpoints",
steps: [
"Implement rate limiting middleware",
"Configure limits for login attempts",
"Add account lockout after failed attempts",
"Log suspicious activity",
],
priority: 2,
reasoning: "Prevents brute force attacks on authentication",
},
{
id: `suggestion-${Date.now()}-2`,
category: "Low",
description: "Remove sensitive information from error messages",
steps: [
"Audit error handling in API routes",
"Create generic error messages for production",
"Log detailed errors server-side only",
"Implement proper error boundaries",
],
priority: 3,
reasoning: "Error messages may leak implementation details",
},
];
break;
case "performance":
mockSuggestions = [
{
id: `suggestion-${Date.now()}-0`,
category: "Rendering",
description: "Add React.memo to prevent unnecessary re-renders",
steps: [
"Profile component renders with React DevTools",
"Identify components that re-render unnecessarily",
"Wrap pure components with React.memo",
"Use useCallback for event handlers passed as props",
],
priority: 1,
reasoning: "Components re-render even when props haven't changed",
},
{
id: `suggestion-${Date.now()}-1`,
category: "Bundle Size",
description: "Implement code splitting for route components",
steps: [
"Use React.lazy for route components",
"Add Suspense boundaries with loading states",
"Analyze bundle with webpack-bundle-analyzer",
"Consider dynamic imports for heavy libraries",
],
priority: 2,
reasoning: "Initial bundle is larger than necessary",
},
{
id: `suggestion-${Date.now()}-2`,
category: "Caching",
description: "Add memoization for expensive computations",
steps: [
"Identify expensive calculations in render",
"Use useMemo for derived data",
"Consider using react-query for server state",
"Add caching headers for static assets",
],
priority: 3,
reasoning: "Expensive computations run on every render",
},
];
break;
default: // "features"
mockSuggestions = [
{
id: `suggestion-${Date.now()}-0`,
category: "User Experience",
description: "Add dark mode toggle with system preference detection",
steps: [
"Create a ThemeProvider context to manage theme state",
"Add a toggle component in the settings or header",
"Implement CSS variables for theme colors",
"Add localStorage persistence for user preference",
],
priority: 1,
reasoning: "Dark mode is a standard feature that improves accessibility and user comfort",
},
{
id: `suggestion-${Date.now()}-1`,
category: "Performance",
description: "Implement lazy loading for heavy components",
steps: [
"Identify components that are heavy or rarely used",
"Use React.lazy() and Suspense for code splitting",
"Add loading states for lazy-loaded components",
],
priority: 2,
reasoning: "Improves initial load time and reduces bundle size",
},
{
id: `suggestion-${Date.now()}-2`,
category: "Accessibility",
description: "Add keyboard navigation support throughout the app",
steps: [
"Implement focus management for modals and dialogs",
"Add keyboard shortcuts for common actions",
"Ensure all interactive elements are focusable",
"Add ARIA labels and roles where needed",
],
priority: 3,
reasoning: "Improves accessibility for users who rely on keyboard navigation",
},
];
}
emitSuggestionsEvent({
type: "suggestions_complete",
@@ -1911,6 +2067,30 @@ function createMockFeaturesAPI(): FeaturesAPI {
};
}
// Mock Running Agents API implementation
function createMockRunningAgentsAPI(): RunningAgentsAPI {
return {
getAll: async () => {
console.log("[Mock] Getting all running agents");
// Return running agents from mock auto mode state
const runningAgents: RunningAgent[] = Array.from(mockRunningFeatures).map(
(featureId) => ({
featureId,
projectPath: "/mock/project",
projectName: "Mock Project",
isAutoMode: mockAutoModeRunning,
})
);
return {
success: true,
runningAgents,
totalCount: runningAgents.length,
autoLoopRunning: mockAutoModeRunning,
};
},
};
}
// Utility functions for project management
export interface Project {