mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 08:13:37 +00:00
Merge pull request #590 from AutoMaker-Org/automode-api
feat: implement cursor model migration and enhance auto mode function…
This commit is contained in:
@@ -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';
|
||||
@@ -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(
|
||||
|
||||
54
apps/server/src/routes/auto-mode/routes/start.ts
Normal file
54
apps/server/src/routes/auto-mode/routes/start.ts
Normal file
@@ -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`,
|
||||
});
|
||||
} catch (error) {
|
||||
logError(error, 'Start auto mode failed');
|
||||
res.status(500).json({ success: false, error: getErrorMessage(error) });
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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');
|
||||
|
||||
54
apps/server/src/routes/auto-mode/routes/stop.ts
Normal file
54
apps/server/src/routes/auto-mode/routes/stop.ts
Normal file
@@ -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) });
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -100,11 +100,60 @@ export function getAbortController(): AbortController | null {
|
||||
return currentAbortController;
|
||||
}
|
||||
|
||||
export function getErrorMessage(error: unknown): string {
|
||||
if (error instanceof Error) {
|
||||
return error.message;
|
||||
/**
|
||||
* 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.';
|
||||
}
|
||||
return String(error);
|
||||
|
||||
// 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) {
|
||||
rawMessage = error.message;
|
||||
} else {
|
||||
rawMessage = String(error);
|
||||
}
|
||||
return mapBacklogPlanError(rawMessage);
|
||||
}
|
||||
|
||||
export function logError(error: unknown, context: string): void {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user