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

@@ -20,11 +20,64 @@ class AutoModeService {
constructor() {
// Track multiple concurrent feature executions
this.runningFeatures = new Map(); // featureId -> { abortController, query, projectPath, sendToRenderer }
this.autoLoopRunning = false; // Separate flag for the auto loop
this.autoLoopAbortController = null;
this.autoLoopInterval = null; // Timer for periodic checking
// Per-project auto loop state (keyed by projectPath)
this.projectLoops = new Map(); // projectPath -> { isRunning, interval, abortController, sendToRenderer, maxConcurrency }
this.checkIntervalMs = 5000; // Check every 5 seconds
this.maxConcurrency = 3; // Default max concurrency
this.maxConcurrency = 3; // Default max concurrency (global default)
}
/**
* Get or create project loop state
*/
getProjectLoopState(projectPath) {
if (!this.projectLoops.has(projectPath)) {
this.projectLoops.set(projectPath, {
isRunning: false,
interval: null,
abortController: null,
sendToRenderer: null,
maxConcurrency: this.maxConcurrency,
});
}
return this.projectLoops.get(projectPath);
}
/**
* Check if any project has auto mode running
*/
hasAnyAutoLoopRunning() {
for (const [, state] of this.projectLoops) {
if (state.isRunning) return true;
}
return false;
}
/**
* Get running features for a specific project
*/
getRunningFeaturesForProject(projectPath) {
const features = [];
for (const [featureId, execution] of this.runningFeatures) {
if (execution.projectPath === projectPath) {
features.push(featureId);
}
}
return features;
}
/**
* Count running features for a specific project
*/
getRunningCountForProject(projectPath) {
let count = 0;
for (const [, execution] of this.runningFeatures) {
if (execution.projectPath === projectPath) {
count++;
}
}
return count;
}
/**
@@ -43,6 +96,18 @@ class AutoModeService {
return context;
}
/**
* Helper to emit event with projectPath included
*/
emitEvent(projectPath, sendToRenderer, event) {
if (sendToRenderer) {
sendToRenderer({
...event,
projectPath,
});
}
}
/**
* Setup worktree for a feature
* Creates an isolated git worktree where the agent can work
@@ -65,7 +130,7 @@ class AutoModeService {
return { useWorktree: false, workPath: projectPath };
}
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_progress",
featureId: feature.id,
content: "Creating isolated worktree for feature...\n",
@@ -75,7 +140,7 @@ class AutoModeService {
if (!result.success) {
console.warn(`[AutoMode] Failed to create worktree: ${result.error}. Falling back to main project.`);
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_progress",
featureId: feature.id,
content: `Warning: Could not create worktree (${result.error}). Working directly on main project.\n`,
@@ -84,7 +149,7 @@ class AutoModeService {
}
console.log(`[AutoMode] Created worktree at: ${result.worktreePath}, branch: ${result.branchName}`);
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_progress",
featureId: feature.id,
content: `Working in isolated branch: ${result.branchName}\n`,
@@ -107,46 +172,56 @@ class AutoModeService {
}
/**
* Start auto mode - continuously implement features
* Start auto mode for a specific project - continuously implement features
* Each project can have its own independent auto mode loop
*/
async start({ projectPath, sendToRenderer, maxConcurrency }) {
if (this.autoLoopRunning) {
throw new Error("Auto mode loop is already running");
const projectState = this.getProjectLoopState(projectPath);
if (projectState.isRunning) {
throw new Error(`Auto mode loop is already running for project: ${projectPath}`);
}
this.autoLoopRunning = true;
this.maxConcurrency = maxConcurrency || 3;
projectState.isRunning = true;
projectState.maxConcurrency = maxConcurrency || 3;
projectState.sendToRenderer = sendToRenderer;
console.log(
`[AutoMode] Starting auto mode for project: ${projectPath} with max concurrency: ${this.maxConcurrency}`
`[AutoMode] Starting auto mode for project: ${projectPath} with max concurrency: ${projectState.maxConcurrency}`
);
// Start the periodic checking loop
this.runPeriodicLoop(projectPath, sendToRenderer);
// Start the periodic checking loop for this project
this.runPeriodicLoopForProject(projectPath);
return { success: true };
}
/**
* Stop auto mode - stops the auto loop but lets running features complete
* Stop auto mode for a specific project - stops the auto loop but lets running features complete
* This only turns off the auto toggle to prevent picking up new features.
* Running tasks will continue until they complete naturally.
*/
async stop() {
console.log("[AutoMode] Stopping auto mode (letting running features complete)");
async stop({ projectPath }) {
console.log(`[AutoMode] Stopping auto mode for project: ${projectPath} (letting running features complete)`);
this.autoLoopRunning = false;
const projectState = this.projectLoops.get(projectPath);
if (!projectState) {
console.log(`[AutoMode] No auto mode state found for project: ${projectPath}`);
return { success: true, runningFeatures: 0 };
}
// Clear the interval timer
if (this.autoLoopInterval) {
clearInterval(this.autoLoopInterval);
this.autoLoopInterval = null;
projectState.isRunning = false;
// Clear the interval timer for this project
if (projectState.interval) {
clearInterval(projectState.interval);
projectState.interval = null;
}
// Abort auto loop if running
if (this.autoLoopAbortController) {
this.autoLoopAbortController.abort();
this.autoLoopAbortController = null;
if (projectState.abortController) {
projectState.abortController.abort();
projectState.abortController = null;
}
// NOTE: We intentionally do NOT abort running features here.
@@ -154,23 +229,58 @@ class AutoModeService {
// from being picked up. Running features will complete naturally.
// Use stopFeature() to cancel a specific running feature if needed.
const runningCount = this.runningFeatures.size;
console.log(`[AutoMode] Auto loop stopped. ${runningCount} feature(s) still running and will complete.`);
const runningCount = this.getRunningCountForProject(projectPath);
console.log(`[AutoMode] Auto loop stopped for ${projectPath}. ${runningCount} feature(s) still running and will complete.`);
return { success: true, runningFeatures: runningCount };
}
/**
* Get status of auto mode
* Get status of auto mode (global and per-project)
*/
getStatus() {
getStatus({ projectPath } = {}) {
// If projectPath is specified, return status for that project
if (projectPath) {
const projectState = this.projectLoops.get(projectPath);
return {
autoLoopRunning: projectState?.isRunning || false,
runningFeatures: this.getRunningFeaturesForProject(projectPath),
runningCount: this.getRunningCountForProject(projectPath),
};
}
// Otherwise return global status
const allRunningProjects = [];
for (const [path, state] of this.projectLoops) {
if (state.isRunning) {
allRunningProjects.push(path);
}
}
return {
autoLoopRunning: this.autoLoopRunning,
autoLoopRunning: this.hasAnyAutoLoopRunning(),
runningProjects: allRunningProjects,
runningFeatures: Array.from(this.runningFeatures.keys()),
runningCount: this.runningFeatures.size,
};
}
/**
* Get status for all projects with auto mode
*/
getAllProjectStatuses() {
const statuses = {};
for (const [projectPath, state] of this.projectLoops) {
statuses[projectPath] = {
isRunning: state.isRunning,
runningFeatures: this.getRunningFeaturesForProject(projectPath),
runningCount: this.getRunningCountForProject(projectPath),
maxConcurrency: state.maxConcurrency,
};
}
return statuses;
}
/**
* Run a specific feature by ID
* @param {string} projectPath - Path to the project
@@ -218,7 +328,7 @@ class AutoModeService {
projectPath
);
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_feature_start",
featureId: feature.id,
feature: { ...feature, worktreePath: worktreeSetup.workPath, branchName: worktreeSetup.branchName },
@@ -253,7 +363,7 @@ class AutoModeService {
// Keep context file for viewing output later (deleted only when card is removed)
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_feature_complete",
featureId: feature.id,
passes: result.passes,
@@ -288,7 +398,7 @@ class AutoModeService {
console.error("[AutoMode] Failed to update feature status after error:", statusError);
}
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_error",
error: error.message,
featureId: featureId,
@@ -333,7 +443,7 @@ class AutoModeService {
console.log(`[AutoMode] Verifying feature: ${feature.description}`);
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_feature_start",
featureId: feature.id,
feature: feature,
@@ -357,7 +467,7 @@ class AutoModeService {
// Keep context file for viewing output later (deleted only when card is removed)
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_feature_complete",
featureId: feature.id,
passes: result.passes,
@@ -392,7 +502,7 @@ class AutoModeService {
console.error("[AutoMode] Failed to update feature status after error:", statusError);
}
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_error",
error: error.message,
featureId: featureId,
@@ -437,7 +547,7 @@ class AutoModeService {
console.log(`[AutoMode] Resuming feature: ${feature.description}`);
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_feature_start",
featureId: feature.id,
feature: feature,
@@ -481,7 +591,7 @@ class AutoModeService {
`\n\n🔄 Auto-retry #${attempts} - Continuing implementation...\n\n`
);
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_progress",
featureId: feature.id,
content: `\n🔄 Auto-retry #${attempts} - Agent ended early, continuing...\n`,
@@ -524,7 +634,7 @@ class AutoModeService {
// Keep context file for viewing output later (deleted only when card is removed)
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_feature_complete",
featureId: feature.id,
passes: finalResult.passes,
@@ -559,7 +669,7 @@ class AutoModeService {
console.error("[AutoMode] Failed to update feature status after error:", statusError);
}
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_error",
error: error.message,
featureId: featureId,
@@ -572,42 +682,52 @@ class AutoModeService {
}
/**
* New periodic loop - checks available slots and starts features up to max concurrency
* New periodic loop for a specific project - checks available slots and starts features up to max concurrency
* This loop continues running even if there are no backlog items
*/
runPeriodicLoop(projectPath, sendToRenderer) {
runPeriodicLoopForProject(projectPath) {
const projectState = this.getProjectLoopState(projectPath);
console.log(
`[AutoMode] Starting periodic loop with interval: ${this.checkIntervalMs}ms`
`[AutoMode] Starting periodic loop for ${projectPath} with interval: ${this.checkIntervalMs}ms`
);
// Initial check immediately
this.checkAndStartFeatures(projectPath, sendToRenderer);
this.checkAndStartFeaturesForProject(projectPath);
// Then check periodically
this.autoLoopInterval = setInterval(() => {
if (this.autoLoopRunning) {
this.checkAndStartFeatures(projectPath, sendToRenderer);
projectState.interval = setInterval(() => {
if (projectState.isRunning) {
this.checkAndStartFeaturesForProject(projectPath);
}
}, this.checkIntervalMs);
}
/**
* Check how many features are running and start new ones if under max concurrency
* Check how many features are running for a specific project and start new ones if under max concurrency
*/
async checkAndStartFeatures(projectPath, sendToRenderer) {
async checkAndStartFeaturesForProject(projectPath) {
const projectState = this.projectLoops.get(projectPath);
if (!projectState || !projectState.isRunning) {
return;
}
const sendToRenderer = projectState.sendToRenderer;
const maxConcurrency = projectState.maxConcurrency;
try {
// Check how many are currently running
const currentRunningCount = this.runningFeatures.size;
// Check how many are currently running FOR THIS PROJECT
const currentRunningCount = this.getRunningCountForProject(projectPath);
console.log(
`[AutoMode] Checking features - Running: ${currentRunningCount}/${this.maxConcurrency}`
`[AutoMode] [${projectPath}] Checking features - Running: ${currentRunningCount}/${maxConcurrency}`
);
// Calculate available slots
const availableSlots = this.maxConcurrency - currentRunningCount;
// Calculate available slots for this project
const availableSlots = maxConcurrency - currentRunningCount;
if (availableSlots <= 0) {
console.log("[AutoMode] At max concurrency, waiting...");
console.log(`[AutoMode] [${projectPath}] At max concurrency, waiting...`);
return;
}
@@ -616,7 +736,7 @@ class AutoModeService {
const backlogFeatures = features.filter((f) => f.status === "backlog");
if (backlogFeatures.length === 0) {
console.log("[AutoMode] No backlog features available, waiting...");
console.log(`[AutoMode] [${projectPath}] No backlog features available, waiting...`);
return;
}
@@ -624,7 +744,7 @@ class AutoModeService {
const featuresToStart = backlogFeatures.slice(0, availableSlots);
console.log(
`[AutoMode] Starting ${featuresToStart.length} feature(s) from backlog`
`[AutoMode] [${projectPath}] Starting ${featuresToStart.length} feature(s) from backlog`
);
// Start each feature (don't await - run in parallel like drag operations)
@@ -632,7 +752,7 @@ class AutoModeService {
this.startFeatureAsync(feature, projectPath, sendToRenderer);
}
} catch (error) {
console.error("[AutoMode] Error checking/starting features:", error);
console.error(`[AutoMode] [${projectPath}] Error checking/starting features:`, error);
}
}
@@ -678,7 +798,7 @@ class AutoModeService {
projectPath
);
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_feature_start",
featureId: feature.id,
feature: { ...feature, worktreePath: worktreeSetup.workPath, branchName: worktreeSetup.branchName },
@@ -713,7 +833,7 @@ class AutoModeService {
// Keep context file for viewing output later (deleted only when card is removed)
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_feature_complete",
featureId: feature.id,
passes: result.passes,
@@ -746,7 +866,7 @@ class AutoModeService {
console.error("[AutoMode] Failed to update feature status after error:", statusError);
}
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_error",
error: error.message,
featureId: featureId,
@@ -778,7 +898,7 @@ class AutoModeService {
this.runningFeatures.set(analysisId, execution);
try {
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_feature_start",
featureId: analysisId,
feature: {
@@ -796,7 +916,7 @@ class AutoModeService {
execution
);
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_feature_complete",
featureId: analysisId,
passes: result.success,
@@ -806,7 +926,7 @@ class AutoModeService {
return { success: true, message: result.message };
} catch (error) {
console.error("[AutoMode] Error analyzing project:", error);
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_error",
error: error.message,
featureId: analysisId,
@@ -911,7 +1031,7 @@ class AutoModeService {
projectPath
);
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_feature_start",
featureId: feature.id,
feature: feature,
@@ -956,7 +1076,7 @@ class AutoModeService {
// Keep context file for viewing output later (deleted only when card is removed)
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_feature_complete",
featureId: feature.id,
passes: result.passes,
@@ -989,7 +1109,7 @@ class AutoModeService {
console.error("[AutoMode] Failed to update feature status after error:", statusError);
}
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_error",
error: error.message,
featureId: featureId,
@@ -1021,13 +1141,13 @@ class AutoModeService {
throw new Error(`Feature ${featureId} not found`);
}
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_feature_start",
featureId: feature.id,
feature: { ...feature, description: "Committing changes..." },
});
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_phase",
featureId,
phase: "action",
@@ -1051,7 +1171,7 @@ class AutoModeService {
// Keep context file for viewing output later (deleted only when card is removed)
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_feature_complete",
featureId: feature.id,
passes: true,
@@ -1061,7 +1181,7 @@ class AutoModeService {
return { success: true };
} catch (error) {
console.error("[AutoMode] Error committing feature:", error);
sendToRenderer({
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_error",
error: error.message,
featureId: featureId,
@@ -1108,26 +1228,22 @@ class AutoModeService {
// Delete context file
await contextManager.deleteContextFile(projectPath, featureId);
if (sendToRenderer) {
sendToRenderer({
type: "auto_mode_feature_complete",
featureId: featureId,
passes: false,
message: "Feature reverted - all changes discarded",
});
}
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_feature_complete",
featureId: featureId,
passes: false,
message: "Feature reverted - all changes discarded",
});
console.log(`[AutoMode] Feature ${featureId} reverted successfully`);
return { success: true, removedPath: result.removedPath };
} catch (error) {
console.error("[AutoMode] Error reverting feature:", error);
if (sendToRenderer) {
sendToRenderer({
type: "auto_mode_error",
error: error.message,
featureId: featureId,
});
}
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_error",
error: error.message,
featureId: featureId,
});
return { success: false, error: error.message };
}
}
@@ -1147,13 +1263,11 @@ class AutoModeService {
throw new Error(`Feature ${featureId} not found`);
}
if (sendToRenderer) {
sendToRenderer({
type: "auto_mode_progress",
featureId: featureId,
content: "Merging feature branch into main...\n",
});
}
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_progress",
featureId: featureId,
content: "Merging feature branch into main...\n",
});
// Merge the worktree
const result = await worktreeManager.mergeWorktree(projectPath, featureId, {
@@ -1171,26 +1285,22 @@ class AutoModeService {
// Update feature status to verified
await featureLoader.updateFeatureStatus(featureId, "verified", projectPath);
if (sendToRenderer) {
sendToRenderer({
type: "auto_mode_feature_complete",
featureId: featureId,
passes: true,
message: `Feature merged into ${result.intoBranch}`,
});
}
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_feature_complete",
featureId: featureId,
passes: true,
message: `Feature merged into ${result.intoBranch}`,
});
console.log(`[AutoMode] Feature ${featureId} merged successfully`);
return { success: true, mergedBranch: result.mergedBranch };
} catch (error) {
console.error("[AutoMode] Error merging feature:", error);
if (sendToRenderer) {
sendToRenderer({
type: "auto_mode_error",
error: error.message,
featureId: featureId,
});
}
this.emitEvent(projectPath, sendToRenderer, {
type: "auto_mode_error",
error: error.message,
featureId: featureId,
});
return { success: false, error: error.message };
}
}

View File

@@ -355,6 +355,17 @@ ipcMain.handle("ping", () => {
return "pong";
});
// Open external link in default browser
ipcMain.handle("shell:openExternal", async (_, url) => {
try {
await shell.openExternal(url);
return { success: true };
} catch (error) {
console.error("[IPC] shell:openExternal error:", error);
return { success: false, error: error.message };
}
});
// ============================================================================
// Agent IPC Handlers
// ============================================================================
@@ -574,11 +585,11 @@ ipcMain.handle(
);
/**
* Stop auto mode
* Stop auto mode for a specific project
*/
ipcMain.handle("auto-mode:stop", async () => {
ipcMain.handle("auto-mode:stop", async (_, { projectPath }) => {
try {
return await autoModeService.stop();
return await autoModeService.stop({ projectPath });
} catch (error) {
console.error("[IPC] auto-mode:stop error:", error);
return { success: false, error: error.message };
@@ -586,11 +597,11 @@ ipcMain.handle("auto-mode:stop", async () => {
});
/**
* Get auto mode status
* Get auto mode status (optionally for a specific project)
*/
ipcMain.handle("auto-mode:status", () => {
ipcMain.handle("auto-mode:status", (_, { projectPath } = {}) => {
try {
return { success: true, ...autoModeService.getStatus() };
return { success: true, ...autoModeService.getStatus({ projectPath }) };
} catch (error) {
console.error("[IPC] auto-mode:status error:", error);
return { success: false, error: error.message };
@@ -942,9 +953,11 @@ let suggestionsExecution = null;
/**
* Generate feature suggestions by analyzing the project
* @param {string} projectPath - The path to the project
* @param {string} suggestionType - Type of suggestions: "features", "refactoring", "security", "performance"
*/
ipcMain.handle("suggestions:generate", async (_, { projectPath }) => {
console.log("[IPC] suggestions:generate called with:", { projectPath });
ipcMain.handle("suggestions:generate", async (_, { projectPath, suggestionType = "features" }) => {
console.log("[IPC] suggestions:generate called with:", { projectPath, suggestionType });
try {
// Check if already running
@@ -970,7 +983,7 @@ ipcMain.handle("suggestions:generate", async (_, { projectPath }) => {
// Start generating suggestions (runs in background)
featureSuggestionsService
.generateSuggestions(projectPath, sendToRenderer, suggestionsExecution)
.generateSuggestions(projectPath, sendToRenderer, suggestionsExecution, suggestionType)
.catch((error) => {
console.error("[IPC] suggestions:generate background error:", error);
sendToRenderer({
@@ -1776,3 +1789,41 @@ ipcMain.handle(
}
}
);
// ============================================================================
// Running Agents IPC Handlers
// ============================================================================
/**
* Get all currently running agents across all projects
*/
ipcMain.handle("running-agents:getAll", () => {
try {
const status = autoModeService.getStatus();
const allStatuses = autoModeService.getAllProjectStatuses();
// Build a list of running agents with their details
const runningAgents = [];
for (const [projectPath, projectStatus] of Object.entries(allStatuses)) {
for (const featureId of projectStatus.runningFeatures) {
runningAgents.push({
featureId,
projectPath,
projectName: projectPath.split(/[/\\]/).pop() || projectPath,
isAutoMode: projectStatus.isRunning,
});
}
}
return {
success: true,
runningAgents,
totalCount: status.runningCount,
autoLoopRunning: status.autoLoopRunning,
};
} catch (error) {
console.error("[IPC] running-agents:getAll error:", error);
return { success: false, error: error.message };
}
});

View File

@@ -6,6 +6,9 @@ contextBridge.exposeInMainWorld("electronAPI", {
// IPC test
ping: () => ipcRenderer.invoke("ping"),
// Shell APIs
openExternalLink: (url) => ipcRenderer.invoke("shell:openExternal", url),
// Dialog APIs
openDirectory: () => ipcRenderer.invoke("dialog:openDirectory"),
openFile: (options) => ipcRenderer.invoke("dialog:openFile", options),
@@ -97,15 +100,15 @@ contextBridge.exposeInMainWorld("electronAPI", {
// Auto Mode API
autoMode: {
// Start auto mode
// Start auto mode for a specific project
start: (projectPath, maxConcurrency) =>
ipcRenderer.invoke("auto-mode:start", { projectPath, maxConcurrency }),
// Stop auto mode
stop: () => ipcRenderer.invoke("auto-mode:stop"),
// Stop auto mode for a specific project
stop: (projectPath) => ipcRenderer.invoke("auto-mode:stop", { projectPath }),
// Get auto mode status
status: () => ipcRenderer.invoke("auto-mode:status"),
// Get auto mode status (optionally for a specific project)
status: (projectPath) => ipcRenderer.invoke("auto-mode:status", { projectPath }),
// Run a specific feature
runFeature: (projectPath, featureId, useWorktrees) =>
@@ -243,8 +246,9 @@ contextBridge.exposeInMainWorld("electronAPI", {
// Feature Suggestions API
suggestions: {
// Generate feature suggestions
generate: (projectPath) =>
ipcRenderer.invoke("suggestions:generate", { projectPath }),
// suggestionType can be: "features", "refactoring", "security", "performance"
generate: (projectPath, suggestionType = "features") =>
ipcRenderer.invoke("suggestions:generate", { projectPath, suggestionType }),
// Stop generating suggestions
stop: () => ipcRenderer.invoke("suggestions:stop"),
@@ -382,6 +386,12 @@ contextBridge.exposeInMainWorld("electronAPI", {
getAgentOutput: (projectPath, featureId) =>
ipcRenderer.invoke("features:getAgentOutput", { projectPath, featureId }),
},
// Running Agents API
runningAgents: {
// Get all running agents across all projects
getAll: () => ipcRenderer.invoke("running-agents:getAll"),
},
});
// Also expose a flag to detect if we're in Electron

View File

@@ -303,6 +303,30 @@ class ClaudeCliDetector {
};
}
/**
* Get installation info and recommendations
* @returns {Object} Installation status and recommendations
*/
static getInstallationInfo() {
const detection = this.detectClaudeInstallation();
if (detection.installed) {
return {
status: 'installed',
method: detection.method,
version: detection.version,
path: detection.path,
recommendation: 'Claude Code CLI is ready for ultrathink'
};
}
return {
status: 'not_installed',
recommendation: 'Install Claude Code CLI for optimal ultrathink performance',
installCommands: this.getInstallCommands()
};
}
/**
* Get installation commands for different platforms
* @returns {Object} Installation commands

View File

@@ -11,10 +11,14 @@ class FeatureSuggestionsService {
/**
* Generate feature suggestions by analyzing the project
* @param {string} projectPath - Path to the project
* @param {Function} sendToRenderer - Function to send events to renderer
* @param {Object} execution - Execution context with abort controller
* @param {string} suggestionType - Type of suggestions: "features", "refactoring", "security", "performance"
*/
async generateSuggestions(projectPath, sendToRenderer, execution) {
async generateSuggestions(projectPath, sendToRenderer, execution, suggestionType = "features") {
console.log(
`[FeatureSuggestions] Generating suggestions for: ${projectPath}`
`[FeatureSuggestions] Generating ${suggestionType} suggestions for: ${projectPath}`
);
try {
@@ -23,7 +27,7 @@ class FeatureSuggestionsService {
const options = {
model: "claude-sonnet-4-20250514",
systemPrompt: this.getSystemPrompt(),
systemPrompt: this.getSystemPrompt(suggestionType),
maxTurns: 50,
cwd: projectPath,
allowedTools: ["Read", "Glob", "Grep", "Bash"],
@@ -35,7 +39,7 @@ class FeatureSuggestionsService {
abortController: abortController,
};
const prompt = this.buildAnalysisPrompt();
const prompt = this.buildAnalysisPrompt(suggestionType);
sendToRenderer({
type: "suggestions_progress",
@@ -163,36 +167,102 @@ class FeatureSuggestionsService {
/**
* Get the system prompt for feature suggestion analysis
* @param {string} suggestionType - Type of suggestions: "features", "refactoring", "security", "performance"
*/
getSystemPrompt() {
return `You are an expert software architect and product manager. Your job is to analyze a codebase and suggest missing features that would improve the application.
getSystemPrompt(suggestionType = "features") {
const basePrompt = `You are an expert software architect. Your job is to analyze a codebase and provide actionable suggestions.
You should:
1. Thoroughly analyze the project structure, code, and any existing documentation
2. Identify what the application does and what features it currently has (look at the .automaker/app_spec.txt file as well if it exists)
3. Generate a comprehensive list of missing features that would be valuable to users
4. Prioritize features by impact and complexity
5. Provide clear, actionable descriptions and implementation steps
You have access to file reading and search tools. Use them to understand the codebase.
When analyzing, look at:
- README files and documentation
- Package.json, cargo.toml, or similar config files for tech stack
- Source code structure and organization
- Existing features and their implementation patterns
- Common patterns in similar applications
- User experience improvements
- Developer experience improvements
- Performance optimizations
- Security enhancements
- Existing code patterns and implementation styles`;
You have access to file reading and search tools. Use them to understand the codebase.`;
switch (suggestionType) {
case "refactoring":
return `${basePrompt}
Your specific focus is on **refactoring suggestions**. You should:
1. Identify code smells and areas that need cleanup
2. Find duplicated code that could be consolidated
3. Spot overly complex functions or classes that should be broken down
4. Look for inconsistent naming conventions or coding patterns
5. Find opportunities to improve code organization and modularity
6. Identify violations of SOLID principles or common design patterns
7. Look for dead code or unused dependencies
Prioritize suggestions by:
- Impact on maintainability
- Risk level (lower risk refactorings first)
- Complexity of the refactoring`;
case "security":
return `${basePrompt}
Your specific focus is on **security vulnerabilities and improvements**. You should:
1. Identify potential security vulnerabilities (OWASP Top 10)
2. Look for hardcoded secrets, API keys, or credentials
3. Check for proper input validation and sanitization
4. Identify SQL injection, XSS, or command injection risks
5. Review authentication and authorization patterns
6. Check for secure communication (HTTPS, encryption)
7. Look for insecure dependencies or outdated packages
8. Review error handling that might leak sensitive information
9. Check for proper session management
10. Identify insecure file handling or path traversal risks
Prioritize by severity:
- Critical: Exploitable vulnerabilities with high impact
- High: Security issues that could lead to data exposure
- Medium: Best practice violations that weaken security
- Low: Minor improvements to security posture`;
case "performance":
return `${basePrompt}
Your specific focus is on **performance issues and optimizations**. You should:
1. Identify N+1 query problems or inefficient database access
2. Look for unnecessary re-renders in React/frontend code
3. Find opportunities for caching or memoization
4. Identify large bundle sizes or unoptimized imports
5. Look for blocking operations that could be async
6. Find memory leaks or inefficient memory usage
7. Identify slow algorithms or data structure choices
8. Look for missing indexes in database schemas
9. Find opportunities for lazy loading or code splitting
10. Identify unnecessary network requests or API calls
Prioritize by:
- Impact on user experience
- Frequency of the slow path
- Ease of implementation`;
default: // "features"
return `${basePrompt}
Your specific focus is on **missing features and improvements**. You should:
1. Identify what the application does and what features it currently has
2. Look at the .automaker/app_spec.txt file if it exists
3. Generate a comprehensive list of missing features that would be valuable to users
4. Consider user experience improvements
5. Consider developer experience improvements
6. Look at common patterns in similar applications
Prioritize features by:
- Impact on users
- Alignment with project goals
- Complexity of implementation`;
}
}
/**
* Build the prompt for analyzing the project
* @param {string} suggestionType - Type of suggestions: "features", "refactoring", "security", "performance"
*/
buildAnalysisPrompt() {
return `Analyze this project and generate a list of suggested features that are missing or would improve the application.
buildAnalysisPrompt(suggestionType = "features") {
const commonIntro = `Analyze this project and generate a list of actionable suggestions.
**Your Task:**
@@ -200,13 +270,89 @@ You have access to file reading and search tools. Use them to understand the cod
- Read README.md, package.json, or similar config files
- Scan the source code directory structure
- Identify the tech stack and frameworks used
- Look at existing features and how they're implemented
- Look at existing code and how it's implemented
2. Identify what the application does:
- What is the main purpose?
- What features are already implemented?
- What patterns and conventions are used?
`;
const commonOutput = `
**CRITICAL: Output your suggestions as a JSON array** at the end of your response, formatted like this:
\`\`\`json
[
{
"category": "Category Name",
"description": "Clear description of the suggestion",
"steps": [
"Step 1 to implement",
"Step 2 to implement",
"Step 3 to implement"
],
"priority": 1,
"reasoning": "Why this is important"
}
]
\`\`\`
**Important Guidelines:**
- Generate at least 10-15 suggestions
- Order them by priority (1 = highest priority)
- Each suggestion should have clear, actionable steps
- Be specific about what files might need to be modified
- Consider the existing tech stack and patterns
Begin by exploring the project structure.`;
switch (suggestionType) {
case "refactoring":
return `${commonIntro}
3. Look for refactoring opportunities:
- Find code duplication across the codebase
- Identify functions or classes that are too long or complex
- Look for inconsistent patterns or naming conventions
- Find tightly coupled code that should be decoupled
- Identify opportunities to extract reusable utilities
- Look for dead code or unused exports
- Check for proper separation of concerns
Categories to use: "Code Smell", "Duplication", "Complexity", "Architecture", "Naming", "Dead Code", "Coupling", "Testing"
${commonOutput}`;
case "security":
return `${commonIntro}
3. Look for security issues:
- Check for hardcoded secrets or API keys
- Look for potential injection vulnerabilities (SQL, XSS, command)
- Review authentication and authorization code
- Check input validation and sanitization
- Look for insecure dependencies
- Review error handling for information leakage
- Check for proper HTTPS/TLS usage
- Look for insecure file operations
Categories to use: "Critical", "High", "Medium", "Low" (based on severity)
${commonOutput}`;
case "performance":
return `${commonIntro}
3. Look for performance issues:
- Find N+1 queries or inefficient database access patterns
- Look for unnecessary re-renders in React components
- Identify missing memoization opportunities
- Check bundle size and import patterns
- Look for synchronous operations that could be async
- Find potential memory leaks
- Identify slow algorithms or data structures
- Look for missing caching opportunities
- Check for unnecessary network requests
Categories to use: "Database", "Rendering", "Memory", "Bundle Size", "Caching", "Algorithm", "Network"
${commonOutput}`;
default: // "features"
return `${commonIntro}
3. Generate feature suggestions:
- Think about what's missing compared to similar applications
- Consider user experience improvements
@@ -214,45 +360,9 @@ You have access to file reading and search tools. Use them to understand the cod
- Think about performance, security, and reliability
- Consider testing and documentation improvements
4. **CRITICAL: Output your suggestions as a JSON array** at the end of your response, formatted like this:
\`\`\`json
[
{
"category": "User Experience",
"description": "Add dark mode support 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"
},
{
"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"
}
]
\`\`\`
**Important Guidelines:**
- Generate at least 10-20 feature suggestions
- Order them by priority (1 = highest priority)
- Each feature should have clear, actionable steps
- Categories should be meaningful (e.g., "User Experience", "Performance", "Security", "Testing", "Documentation", "Developer Experience", "Accessibility", etc.)
- Be specific about what files might need to be created or modified
- Consider the existing tech stack and patterns when suggesting implementation steps
Begin by exploring the project structure.`;
Categories to use: "User Experience", "Performance", "Security", "Testing", "Documentation", "Developer Experience", "Accessibility", etc.
${commonOutput}`;
}
}
/**

View File

@@ -251,7 +251,7 @@ class ClaudeProvider extends ModelProvider {
async detectInstallation() {
const claudeCliDetector = require('./claude-cli-detector');
return claudeCliDetector.getInstallationInfo();
return claudeCliDetector.getFullStatus();
}
getAvailableModels() {