diff --git a/apps/server/src/routes/auto-mode/routes/analyze-project.ts b/apps/server/src/routes/auto-mode/routes/analyze-project.ts index 77c95e27..1903940a 100644 --- a/apps/server/src/routes/auto-mode/routes/analyze-project.ts +++ b/apps/server/src/routes/auto-mode/routes/analyze-project.ts @@ -4,12 +4,16 @@ import type { Request, Response } from 'express'; import type { AutoModeService } from '../../../services/auto-mode-service.js'; +import type { AutoModeServiceFacade } from '../../../services/auto-mode/index.js'; import { createLogger } from '@automaker/utils'; import { getErrorMessage, logError } from '../common.js'; const logger = createLogger('AutoMode'); -export function createAnalyzeProjectHandler(autoModeService: AutoModeService) { +export function createAnalyzeProjectHandler( + autoModeService: AutoModeService, + facadeFactory?: (projectPath: string) => AutoModeServiceFacade +) { return async (req: Request, res: Response): Promise => { try { const { projectPath } = req.body as { projectPath: string }; @@ -19,6 +23,19 @@ export function createAnalyzeProjectHandler(autoModeService: AutoModeService) { return; } + // Use facade if factory is provided, otherwise fall back to autoModeService + if (facadeFactory) { + const facade = facadeFactory(projectPath); + // Start analysis in background + facade.analyzeProject().catch((error) => { + logger.error(`[AutoMode] Project analysis error:`, error); + }); + + res.json({ success: true, message: 'Project analysis started' }); + return; + } + + // Legacy path: use autoModeService directly // Start analysis in background autoModeService.analyzeProject(projectPath).catch((error) => { logger.error(`[AutoMode] Project analysis error:`, error); diff --git a/apps/server/src/routes/auto-mode/routes/approve-plan.ts b/apps/server/src/routes/auto-mode/routes/approve-plan.ts index c006e506..bc989099 100644 --- a/apps/server/src/routes/auto-mode/routes/approve-plan.ts +++ b/apps/server/src/routes/auto-mode/routes/approve-plan.ts @@ -4,12 +4,16 @@ import type { Request, Response } from 'express'; import type { AutoModeService } from '../../../services/auto-mode-service.js'; +import type { AutoModeServiceFacade } from '../../../services/auto-mode/index.js'; import { createLogger } from '@automaker/utils'; import { getErrorMessage, logError } from '../common.js'; const logger = createLogger('AutoMode'); -export function createApprovePlanHandler(autoModeService: AutoModeService) { +export function createApprovePlanHandler( + autoModeService: AutoModeService, + facadeFactory?: (projectPath: string) => AutoModeServiceFacade +) { return async (req: Request, res: Response): Promise => { try { const { featureId, approved, editedPlan, feedback, projectPath } = req.body as { @@ -46,6 +50,30 @@ export function createApprovePlanHandler(autoModeService: AutoModeService) { }${feedback ? ` - Feedback: ${feedback}` : ''}` ); + // Use facade if factory is provided and projectPath is available + if (facadeFactory && projectPath) { + const facade = facadeFactory(projectPath); + const result = await facade.resolvePlanApproval(featureId, approved, editedPlan, feedback); + + if (!result.success) { + res.status(500).json({ + success: false, + error: result.error, + }); + return; + } + + res.json({ + success: true, + approved, + message: approved + ? 'Plan approved - implementation will continue' + : 'Plan rejected - feature execution stopped', + }); + return; + } + + // Legacy path: use autoModeService directly // Resolve the pending approval (with recovery support) const result = await autoModeService.resolvePlanApproval( featureId, diff --git a/apps/server/src/routes/auto-mode/routes/follow-up-feature.ts b/apps/server/src/routes/auto-mode/routes/follow-up-feature.ts index bd9c480d..ffeb8d60 100644 --- a/apps/server/src/routes/auto-mode/routes/follow-up-feature.ts +++ b/apps/server/src/routes/auto-mode/routes/follow-up-feature.ts @@ -4,12 +4,16 @@ import type { Request, Response } from 'express'; import type { AutoModeService } from '../../../services/auto-mode-service.js'; +import type { AutoModeServiceFacade } from '../../../services/auto-mode/index.js'; import { createLogger } from '@automaker/utils'; import { getErrorMessage, logError } from '../common.js'; const logger = createLogger('AutoMode'); -export function createFollowUpFeatureHandler(autoModeService: AutoModeService) { +export function createFollowUpFeatureHandler( + autoModeService: AutoModeService, + facadeFactory?: (projectPath: string) => AutoModeServiceFacade +) { return async (req: Request, res: Response): Promise => { try { const { projectPath, featureId, prompt, imagePaths, useWorktrees } = req.body as { @@ -28,6 +32,28 @@ export function createFollowUpFeatureHandler(autoModeService: AutoModeService) { return; } + // Use facade if factory is provided, otherwise fall back to autoModeService + if (facadeFactory) { + const facade = facadeFactory(projectPath); + // Start follow-up in background + // followUpFeature derives workDir from feature.branchName + facade + // Default to false to match run-feature/resume-feature behavior. + // Worktrees should only be used when explicitly enabled by the user. + .followUpFeature(featureId, prompt, imagePaths, useWorktrees ?? false) + .catch((error) => { + logger.error(`[AutoMode] Follow up feature ${featureId} error:`, error); + }) + .finally(() => { + // Release the starting slot when follow-up completes (success or error) + // Note: The feature should be in runningFeatures by this point + }); + + res.json({ success: true }); + return; + } + + // Legacy path: use autoModeService directly // Start follow-up in background // followUpFeature derives workDir from feature.branchName autoModeService diff --git a/apps/server/src/routes/auto-mode/routes/run-feature.ts b/apps/server/src/routes/auto-mode/routes/run-feature.ts index 2d53c8e5..b789a73c 100644 --- a/apps/server/src/routes/auto-mode/routes/run-feature.ts +++ b/apps/server/src/routes/auto-mode/routes/run-feature.ts @@ -4,12 +4,16 @@ import type { Request, Response } from 'express'; import type { AutoModeService } from '../../../services/auto-mode-service.js'; +import type { AutoModeServiceFacade } from '../../../services/auto-mode/index.js'; import { createLogger } from '@automaker/utils'; import { getErrorMessage, logError } from '../common.js'; const logger = createLogger('AutoMode'); -export function createRunFeatureHandler(autoModeService: AutoModeService) { +export function createRunFeatureHandler( + autoModeService: AutoModeService, + facadeFactory?: (projectPath: string) => AutoModeServiceFacade +) { return async (req: Request, res: Response): Promise => { try { const { projectPath, featureId, useWorktrees } = req.body as { @@ -26,6 +30,45 @@ export function createRunFeatureHandler(autoModeService: AutoModeService) { return; } + // Use facade if factory is provided, otherwise fall back to autoModeService + if (facadeFactory) { + const facade = facadeFactory(projectPath); + + // Check per-worktree capacity before starting + const capacity = await facade.checkWorktreeCapacity(featureId); + if (!capacity.hasCapacity) { + const worktreeDesc = capacity.branchName + ? `worktree "${capacity.branchName}"` + : 'main worktree'; + res.status(429).json({ + success: false, + error: `Agent limit reached for ${worktreeDesc} (${capacity.currentAgents}/${capacity.maxAgents}). Wait for running tasks to complete or increase the limit.`, + details: { + currentAgents: capacity.currentAgents, + maxAgents: capacity.maxAgents, + branchName: capacity.branchName, + }, + }); + return; + } + + // Start execution in background + // executeFeature derives workDir from feature.branchName + facade + .executeFeature(featureId, useWorktrees ?? false, false) + .catch((error) => { + logger.error(`Feature ${featureId} error:`, error); + }) + .finally(() => { + // Release the starting slot when execution completes (success or error) + // Note: The feature should be in runningFeatures by this point + }); + + res.json({ success: true }); + return; + } + + // Legacy path: use autoModeService directly // Check per-worktree capacity before starting const capacity = await autoModeService.checkWorktreeCapacity(projectPath, featureId); if (!capacity.hasCapacity) {