Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions apps/server/src/providers/cursor-config-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class CursorConfigManager {

// Return default config with all available models
return {
defaultModel: 'auto',
defaultModel: 'cursor-auto',
models: getAllCursorModelIds(),
};
}
Expand Down Expand Up @@ -77,7 +77,7 @@ export class CursorConfigManager {
* Get the default model
*/
getDefaultModel(): CursorModelId {
return this.config.defaultModel || 'auto';
return this.config.defaultModel || 'cursor-auto';
}

/**
Expand All @@ -93,7 +93,7 @@ export class CursorConfigManager {
* Get enabled models
*/
getEnabledModels(): CursorModelId[] {
return this.config.models || ['auto'];
return this.config.models || ['cursor-auto'];
}

/**
Expand Down Expand Up @@ -174,7 +174,7 @@ export class CursorConfigManager {
*/
reset(): void {
this.config = {
defaultModel: 'auto',
defaultModel: 'cursor-auto',
models: getAllCursorModelIds(),
};
this.saveConfig();
Expand Down
6 changes: 6 additions & 0 deletions apps/server/src/routes/auto-mode/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { validatePathParams } from '../../middleware/validate-paths.js';
import { createStopFeatureHandler } from './routes/stop-feature.js';
import { createStatusHandler } from './routes/status.js';
import { createRunFeatureHandler } from './routes/run-feature.js';
import { createStartHandler } from './routes/start.js';
import { createStopHandler } from './routes/stop.js';
import { createVerifyFeatureHandler } from './routes/verify-feature.js';
import { createResumeFeatureHandler } from './routes/resume-feature.js';
import { createContextExistsHandler } from './routes/context-exists.js';
Expand All @@ -22,6 +24,10 @@ import { createResumeInterruptedHandler } from './routes/resume-interrupted.js';
export function createAutoModeRoutes(autoModeService: AutoModeService): Router {
const router = Router();

// Auto loop control routes
router.post('/start', validatePathParams('projectPath'), createStartHandler(autoModeService));
router.post('/stop', validatePathParams('projectPath'), createStopHandler(autoModeService));

router.post('/stop-feature', createStopFeatureHandler(autoModeService));
router.post('/status', validatePathParams('projectPath?'), createStatusHandler(autoModeService));
router.post(
Expand Down
54 changes: 54 additions & 0 deletions apps/server/src/routes/auto-mode/routes/start.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* POST /start endpoint - Start auto mode loop for a project
*/

import type { Request, Response } from 'express';
import type { AutoModeService } from '../../../services/auto-mode-service.js';
import { createLogger } from '@automaker/utils';
import { getErrorMessage, logError } from '../common.js';

const logger = createLogger('AutoMode');

export function createStartHandler(autoModeService: AutoModeService) {
return async (req: Request, res: Response): Promise<void> => {
try {
const { projectPath, maxConcurrency } = req.body as {
projectPath: string;
maxConcurrency?: number;
};

if (!projectPath) {
res.status(400).json({
success: false,
error: 'projectPath is required',
});
return;
}

// Check if already running
if (autoModeService.isAutoLoopRunningForProject(projectPath)) {
res.json({
success: true,
message: 'Auto mode is already running for this project',
alreadyRunning: true,
});
return;
}

// Start the auto loop for this project
await autoModeService.startAutoLoopForProject(projectPath, maxConcurrency ?? 3);

logger.info(
`Started auto loop for project: ${projectPath} with maxConcurrency: ${maxConcurrency ?? 3}`
);

res.json({
success: true,
message: `Auto mode started with max ${maxConcurrency ?? 3} concurrent features`,
});
Comment on lines +39 to +48
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve maintainability and avoid magic numbers, the default value for maxConcurrency (which is 3) should be extracted into a constant. This value is repeated three times in this block.

You can define a constant at the top of the file:

const DEFAULT_MAX_CONCURRENCY = 3;

And then use it like this:

      const effectiveMaxConcurrency = maxConcurrency ?? 3;

      // Start the auto loop for this project
      await autoModeService.startAutoLoopForProject(projectPath, effectiveMaxConcurrency);

      logger.info(
        `Started auto loop for project: ${projectPath} with maxConcurrency: ${effectiveMaxConcurrency}`
      );

      res.json({
        success: true,
        message: `Auto mode started with max ${effectiveMaxConcurrency} concurrent features`,
      });

} catch (error) {
logError(error, 'Start auto mode failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
}
23 changes: 23 additions & 0 deletions apps/server/src/routes/auto-mode/routes/status.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/**
* POST /status endpoint - Get auto mode status
*
* If projectPath is provided, returns per-project status including autoloop state.
* If no projectPath, returns global status for backward compatibility.
*/

import type { Request, Response } from 'express';
Expand All @@ -9,10 +12,30 @@ import { getErrorMessage, logError } from '../common.js';
export function createStatusHandler(autoModeService: AutoModeService) {
return async (req: Request, res: Response): Promise<void> => {
try {
const { projectPath } = req.body as { projectPath?: string };

// If projectPath is provided, return per-project status
if (projectPath) {
const projectStatus = autoModeService.getStatusForProject(projectPath);
res.json({
success: true,
isRunning: projectStatus.runningCount > 0,
isAutoLoopRunning: projectStatus.isAutoLoopRunning,
runningFeatures: projectStatus.runningFeatures,
runningCount: projectStatus.runningCount,
maxConcurrency: projectStatus.maxConcurrency,
projectPath,
});
return;
}

// Fall back to global status for backward compatibility
const status = autoModeService.getStatus();
const activeProjects = autoModeService.getActiveAutoLoopProjects();
res.json({
success: true,
...status,
activeAutoLoopProjects: activeProjects,
});
} catch (error) {
logError(error, 'Get status failed');
Expand Down
54 changes: 54 additions & 0 deletions apps/server/src/routes/auto-mode/routes/stop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* POST /stop endpoint - Stop auto mode loop for a project
*/

import type { Request, Response } from 'express';
import type { AutoModeService } from '../../../services/auto-mode-service.js';
import { createLogger } from '@automaker/utils';
import { getErrorMessage, logError } from '../common.js';

const logger = createLogger('AutoMode');

export function createStopHandler(autoModeService: AutoModeService) {
return async (req: Request, res: Response): Promise<void> => {
try {
const { projectPath } = req.body as {
projectPath: string;
};

if (!projectPath) {
res.status(400).json({
success: false,
error: 'projectPath is required',
});
return;
}

// Check if running
if (!autoModeService.isAutoLoopRunningForProject(projectPath)) {
res.json({
success: true,
message: 'Auto mode is not running for this project',
wasRunning: false,
});
return;
}

// Stop the auto loop for this project
const runningCount = await autoModeService.stopAutoLoopForProject(projectPath);

logger.info(
`Stopped auto loop for project: ${projectPath}, ${runningCount} features still running`
);

res.json({
success: true,
message: 'Auto mode stopped',
runningFeaturesCount: runningCount,
});
} catch (error) {
logError(error, 'Stop auto mode failed');
res.status(500).json({ success: false, error: getErrorMessage(error) });
}
};
}
53 changes: 51 additions & 2 deletions apps/server/src/routes/backlog-plan/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,60 @@ export function getAbortController(): AbortController | null {
return currentAbortController;
}

/**
* Map SDK/CLI errors to user-friendly messages
*/
export function mapBacklogPlanError(rawMessage: string): string {
// Claude Code spawn failures
if (
rawMessage.includes('Failed to spawn Claude Code process') ||
rawMessage.includes('spawn node ENOENT') ||
rawMessage.includes('Claude Code executable not found') ||
rawMessage.includes('Claude Code native binary not found')
) {
return 'Claude CLI could not be launched. Make sure the Claude CLI is installed and available in PATH, or check that Node.js is correctly installed. Try running "which claude" or "claude --version" in your terminal to verify.';
}

// Claude Code process crash
if (rawMessage.includes('Claude Code process exited')) {
return 'Claude exited unexpectedly. Try again. If it keeps happening, re-run `claude login` or update your API key in Setup.';
}

// Rate limiting
if (rawMessage.toLowerCase().includes('rate limit') || rawMessage.includes('429')) {
return 'Rate limited. Please wait a moment and try again.';
}

// Network errors
if (
rawMessage.toLowerCase().includes('network') ||
rawMessage.toLowerCase().includes('econnrefused') ||
rawMessage.toLowerCase().includes('timeout')
) {
return 'Network error. Check your internet connection and try again.';
}

// Authentication errors
if (
rawMessage.toLowerCase().includes('not authenticated') ||
rawMessage.toLowerCase().includes('unauthorized') ||
rawMessage.includes('401')
) {
return 'Authentication failed. Please check your API key or run `claude login` to authenticate.';
}

// Return original message for unknown errors
return rawMessage;
}

export function getErrorMessage(error: unknown): string {
let rawMessage: string;
if (error instanceof Error) {
return error.message;
rawMessage = error.message;
} else {
rawMessage = String(error);
}
return String(error);
return mapBacklogPlanError(rawMessage);
}

export function logError(error: unknown, context: string): void {
Expand Down
7 changes: 3 additions & 4 deletions apps/server/src/routes/backlog-plan/routes/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,12 @@ export function createGenerateHandler(events: EventEmitter, settingsService?: Se
setRunningState(true, abortController);

// Start generation in background
// Note: generateBacklogPlan handles its own error event emission,
// so we only log here to avoid duplicate error toasts
generateBacklogPlan(projectPath, prompt, events, abortController, settingsService, model)
.catch((error) => {
// Just log - error event already emitted by generateBacklogPlan
logError(error, 'Generate backlog plan failed (background)');
events.emit('backlog-plan:event', {
type: 'backlog_plan_error',
error: getErrorMessage(error),
});
})
.finally(() => {
setRunningState(false, null);
Expand Down
Loading
Loading