mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 21:03:08 +00:00
refactor(06-04): delete auto-mode-service.ts monolith
- Delete the 2705-line auto-mode-service.ts monolith - Create AutoModeServiceCompat as compatibility layer for routes - Create GlobalAutoModeService for cross-project operations - Update all routes to use AutoModeServiceCompat type - Add SharedServices interface for state sharing across facades - Add getActiveProjects/getActiveWorktrees to AutoLoopCoordinator - Delete obsolete monolith test files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -56,7 +56,7 @@ import {
|
||||
import { createSettingsRoutes } from './routes/settings/index.js';
|
||||
import { AgentService } from './services/agent-service.js';
|
||||
import { FeatureLoader } from './services/feature-loader.js';
|
||||
import { AutoModeService } from './services/auto-mode-service.js';
|
||||
import { AutoModeServiceCompat } from './services/auto-mode/index.js';
|
||||
import { getTerminalService } from './services/terminal-service.js';
|
||||
import { SettingsService } from './services/settings-service.js';
|
||||
import { createSpecRegenerationRoutes } from './routes/app-spec/index.js';
|
||||
@@ -258,7 +258,9 @@ const events: EventEmitter = createEventEmitter();
|
||||
const settingsService = new SettingsService(DATA_DIR);
|
||||
const agentService = new AgentService(DATA_DIR, events, settingsService);
|
||||
const featureLoader = new FeatureLoader();
|
||||
const autoModeService = new AutoModeService(events, settingsService);
|
||||
|
||||
// Auto-mode services: compatibility layer provides old interface while using new architecture
|
||||
const autoModeService = new AutoModeServiceCompat(events, settingsService, featureLoader);
|
||||
const claudeUsageService = new ClaudeUsageService();
|
||||
const codexAppServerService = new CodexAppServerService();
|
||||
const codexModelCacheService = new CodexModelCacheService(DATA_DIR, codexAppServerService);
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
/**
|
||||
* Auto Mode routes - HTTP API for autonomous feature implementation
|
||||
*
|
||||
* Uses the AutoModeService for real feature execution with Claude Agent SDK.
|
||||
* Supports optional facadeFactory for per-project facade creation during migration.
|
||||
* Uses AutoModeServiceCompat which provides the old interface while
|
||||
* delegating to GlobalAutoModeService and per-project facades.
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import type { AutoModeService } from '../../services/auto-mode-service.js';
|
||||
import type { AutoModeServiceFacade } from '../../services/auto-mode/index.js';
|
||||
import type { AutoModeServiceCompat } from '../../services/auto-mode/index.js';
|
||||
import { validatePathParams } from '../../middleware/validate-paths.js';
|
||||
import { createStopFeatureHandler } from './routes/stop-feature.js';
|
||||
import { createStatusHandler } from './routes/status.js';
|
||||
@@ -24,81 +23,63 @@ import { createApprovePlanHandler } from './routes/approve-plan.js';
|
||||
import { createResumeInterruptedHandler } from './routes/resume-interrupted.js';
|
||||
|
||||
/**
|
||||
* Create auto-mode routes with optional facade factory.
|
||||
* Create auto-mode routes.
|
||||
*
|
||||
* @param autoModeService - The AutoModeService instance (for backward compatibility)
|
||||
* @param facadeFactory - Optional factory for creating per-project facades
|
||||
* @param autoModeService - AutoModeServiceCompat instance
|
||||
*/
|
||||
export function createAutoModeRoutes(
|
||||
autoModeService: AutoModeService,
|
||||
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
|
||||
): Router {
|
||||
export function createAutoModeRoutes(autoModeService: AutoModeServiceCompat): Router {
|
||||
const router = Router();
|
||||
|
||||
// Auto loop control routes
|
||||
router.post(
|
||||
'/start',
|
||||
validatePathParams('projectPath'),
|
||||
createStartHandler(autoModeService, facadeFactory)
|
||||
);
|
||||
router.post(
|
||||
'/stop',
|
||||
validatePathParams('projectPath'),
|
||||
createStopHandler(autoModeService, facadeFactory)
|
||||
);
|
||||
router.post('/start', validatePathParams('projectPath'), createStartHandler(autoModeService));
|
||||
router.post('/stop', validatePathParams('projectPath'), createStopHandler(autoModeService));
|
||||
|
||||
// Note: stop-feature doesn't have projectPath, so we pass undefined for facade.
|
||||
// When we fully migrate, we can update stop-feature to use a different approach.
|
||||
router.post('/stop-feature', createStopFeatureHandler(autoModeService));
|
||||
router.post(
|
||||
'/status',
|
||||
validatePathParams('projectPath?'),
|
||||
createStatusHandler(autoModeService, facadeFactory)
|
||||
);
|
||||
router.post('/status', validatePathParams('projectPath?'), createStatusHandler(autoModeService));
|
||||
router.post(
|
||||
'/run-feature',
|
||||
validatePathParams('projectPath'),
|
||||
createRunFeatureHandler(autoModeService, facadeFactory)
|
||||
createRunFeatureHandler(autoModeService)
|
||||
);
|
||||
router.post(
|
||||
'/verify-feature',
|
||||
validatePathParams('projectPath'),
|
||||
createVerifyFeatureHandler(autoModeService, facadeFactory)
|
||||
createVerifyFeatureHandler(autoModeService)
|
||||
);
|
||||
router.post(
|
||||
'/resume-feature',
|
||||
validatePathParams('projectPath'),
|
||||
createResumeFeatureHandler(autoModeService, facadeFactory)
|
||||
createResumeFeatureHandler(autoModeService)
|
||||
);
|
||||
router.post(
|
||||
'/context-exists',
|
||||
validatePathParams('projectPath'),
|
||||
createContextExistsHandler(autoModeService, facadeFactory)
|
||||
createContextExistsHandler(autoModeService)
|
||||
);
|
||||
router.post(
|
||||
'/analyze-project',
|
||||
validatePathParams('projectPath'),
|
||||
createAnalyzeProjectHandler(autoModeService, facadeFactory)
|
||||
createAnalyzeProjectHandler(autoModeService)
|
||||
);
|
||||
router.post(
|
||||
'/follow-up-feature',
|
||||
validatePathParams('projectPath', 'imagePaths[]'),
|
||||
createFollowUpFeatureHandler(autoModeService, facadeFactory)
|
||||
createFollowUpFeatureHandler(autoModeService)
|
||||
);
|
||||
router.post(
|
||||
'/commit-feature',
|
||||
validatePathParams('projectPath', 'worktreePath?'),
|
||||
createCommitFeatureHandler(autoModeService, facadeFactory)
|
||||
createCommitFeatureHandler(autoModeService)
|
||||
);
|
||||
router.post(
|
||||
'/approve-plan',
|
||||
validatePathParams('projectPath'),
|
||||
createApprovePlanHandler(autoModeService, facadeFactory)
|
||||
createApprovePlanHandler(autoModeService)
|
||||
);
|
||||
router.post(
|
||||
'/resume-interrupted',
|
||||
validatePathParams('projectPath'),
|
||||
createResumeInterruptedHandler(autoModeService, facadeFactory)
|
||||
createResumeInterruptedHandler(autoModeService)
|
||||
);
|
||||
|
||||
return router;
|
||||
|
||||
@@ -3,17 +3,13 @@
|
||||
*/
|
||||
|
||||
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 type { AutoModeServiceCompat } 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,
|
||||
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
|
||||
) {
|
||||
export function createAnalyzeProjectHandler(autoModeService: AutoModeServiceCompat) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath } = req.body as { projectPath: string };
|
||||
@@ -23,19 +19,6 @@ export function createAnalyzeProjectHandler(
|
||||
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);
|
||||
|
||||
@@ -3,17 +3,13 @@
|
||||
*/
|
||||
|
||||
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 type { AutoModeServiceCompat } 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,
|
||||
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
|
||||
) {
|
||||
export function createApprovePlanHandler(autoModeService: AutoModeServiceCompat) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { featureId, approved, editedPlan, feedback, projectPath } = req.body as {
|
||||
@@ -50,37 +46,13 @@ export function createApprovePlanHandler(
|
||||
}${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(
|
||||
projectPath || '',
|
||||
featureId,
|
||||
approved,
|
||||
editedPlan,
|
||||
feedback,
|
||||
projectPath
|
||||
feedback
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
|
||||
@@ -3,18 +3,10 @@
|
||||
*/
|
||||
|
||||
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 type { AutoModeServiceCompat } from '../../../services/auto-mode/index.js';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
/**
|
||||
* Create commit feature handler with transition compatibility.
|
||||
* Accepts either autoModeService (legacy) or facadeFactory (new).
|
||||
*/
|
||||
export function createCommitFeatureHandler(
|
||||
autoModeService: AutoModeService,
|
||||
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
|
||||
) {
|
||||
export function createCommitFeatureHandler(autoModeService: AutoModeServiceCompat) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, featureId, worktreePath } = req.body as {
|
||||
@@ -31,15 +23,6 @@ export function createCommitFeatureHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
// Use facade if factory is provided, otherwise fall back to autoModeService
|
||||
if (facadeFactory) {
|
||||
const facade = facadeFactory(projectPath);
|
||||
const commitHash = await facade.commitFeature(featureId, worktreePath);
|
||||
res.json({ success: true, commitHash });
|
||||
return;
|
||||
}
|
||||
|
||||
// Legacy path: use autoModeService directly
|
||||
const commitHash = await autoModeService.commitFeature(projectPath, featureId, worktreePath);
|
||||
res.json({ success: true, commitHash });
|
||||
} catch (error) {
|
||||
|
||||
@@ -3,18 +3,10 @@
|
||||
*/
|
||||
|
||||
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 type { AutoModeServiceCompat } from '../../../services/auto-mode/index.js';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
/**
|
||||
* Create context exists handler with transition compatibility.
|
||||
* Accepts either autoModeService (legacy) or facadeFactory (new).
|
||||
*/
|
||||
export function createContextExistsHandler(
|
||||
autoModeService: AutoModeService,
|
||||
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
|
||||
) {
|
||||
export function createContextExistsHandler(autoModeService: AutoModeServiceCompat) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, featureId } = req.body as {
|
||||
@@ -30,15 +22,6 @@ export function createContextExistsHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
// Use facade if factory is provided, otherwise fall back to autoModeService
|
||||
if (facadeFactory) {
|
||||
const facade = facadeFactory(projectPath);
|
||||
const exists = await facade.contextExists(featureId);
|
||||
res.json({ success: true, exists });
|
||||
return;
|
||||
}
|
||||
|
||||
// Legacy path: use autoModeService directly
|
||||
const exists = await autoModeService.contextExists(projectPath, featureId);
|
||||
res.json({ success: true, exists });
|
||||
} catch (error) {
|
||||
|
||||
@@ -3,17 +3,13 @@
|
||||
*/
|
||||
|
||||
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 type { AutoModeServiceCompat } 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,
|
||||
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
|
||||
) {
|
||||
export function createFollowUpFeatureHandler(autoModeService: AutoModeServiceCompat) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, featureId, prompt, imagePaths, useWorktrees } = req.body as {
|
||||
@@ -32,40 +28,14 @@ export function createFollowUpFeatureHandler(
|
||||
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
|
||||
// Default to false to match run-feature/resume-feature behavior.
|
||||
// Worktrees should only be used when explicitly enabled by the user.
|
||||
autoModeService
|
||||
// Default to false to match run-feature/resume-feature behavior.
|
||||
// Worktrees should only be used when explicitly enabled by the user.
|
||||
.followUpFeature(projectPath, 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 });
|
||||
|
||||
@@ -3,17 +3,13 @@
|
||||
*/
|
||||
|
||||
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 type { AutoModeServiceCompat } from '../../../services/auto-mode/index.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
const logger = createLogger('AutoMode');
|
||||
|
||||
export function createResumeFeatureHandler(
|
||||
autoModeService: AutoModeService,
|
||||
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
|
||||
) {
|
||||
export function createResumeFeatureHandler(autoModeService: AutoModeServiceCompat) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, featureId, useWorktrees } = req.body as {
|
||||
@@ -30,20 +26,6 @@ export function createResumeFeatureHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
// Use facade if factory is provided, otherwise fall back to autoModeService
|
||||
if (facadeFactory) {
|
||||
const facade = facadeFactory(projectPath);
|
||||
// Start resume in background
|
||||
// Default to false - worktrees should only be used when explicitly enabled
|
||||
facade.resumeFeature(featureId, useWorktrees ?? false).catch((error) => {
|
||||
logger.error(`Resume feature ${featureId} error:`, error);
|
||||
});
|
||||
|
||||
res.json({ success: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// Legacy path: use autoModeService directly
|
||||
// Start resume in background
|
||||
// Default to false - worktrees should only be used when explicitly enabled
|
||||
autoModeService
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import type { AutoModeService } from '../../../services/auto-mode-service.js';
|
||||
import type { AutoModeServiceFacade } from '../../../services/auto-mode/index.js';
|
||||
import type { AutoModeServiceCompat } from '../../../services/auto-mode/index.js';
|
||||
|
||||
const logger = createLogger('ResumeInterrupted');
|
||||
|
||||
@@ -16,10 +15,7 @@ interface ResumeInterruptedRequest {
|
||||
projectPath: string;
|
||||
}
|
||||
|
||||
export function createResumeInterruptedHandler(
|
||||
autoModeService: AutoModeService,
|
||||
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
|
||||
) {
|
||||
export function createResumeInterruptedHandler(autoModeService: AutoModeServiceCompat) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
const { projectPath } = req.body as ResumeInterruptedRequest;
|
||||
|
||||
@@ -31,13 +27,7 @@ export function createResumeInterruptedHandler(
|
||||
logger.info(`Checking for interrupted features in ${projectPath}`);
|
||||
|
||||
try {
|
||||
// Use facade if factory is provided, otherwise fall back to autoModeService
|
||||
if (facadeFactory) {
|
||||
const facade = facadeFactory(projectPath);
|
||||
await facade.resumeInterruptedFeatures();
|
||||
} else {
|
||||
await autoModeService.resumeInterruptedFeatures(projectPath);
|
||||
}
|
||||
await autoModeService.resumeInterruptedFeatures(projectPath);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
|
||||
@@ -3,17 +3,13 @@
|
||||
*/
|
||||
|
||||
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 type { AutoModeServiceCompat } 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,
|
||||
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
|
||||
) {
|
||||
export function createRunFeatureHandler(autoModeService: AutoModeServiceCompat) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, featureId, useWorktrees } = req.body as {
|
||||
@@ -30,45 +26,6 @@ export function createRunFeatureHandler(
|
||||
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) {
|
||||
@@ -93,10 +50,6 @@ export function createRunFeatureHandler(
|
||||
.executeFeature(projectPath, 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 });
|
||||
|
||||
@@ -3,17 +3,13 @@
|
||||
*/
|
||||
|
||||
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 type { AutoModeServiceCompat } from '../../../services/auto-mode/index.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
const logger = createLogger('AutoMode');
|
||||
|
||||
export function createStartHandler(
|
||||
autoModeService: AutoModeService,
|
||||
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
|
||||
) {
|
||||
export function createStartHandler(autoModeService: AutoModeServiceCompat) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, branchName, maxConcurrency } = req.body as {
|
||||
@@ -36,40 +32,6 @@ export function createStartHandler(
|
||||
? `worktree ${normalizedBranchName}`
|
||||
: 'main worktree';
|
||||
|
||||
// Use facade if factory is provided, otherwise fall back to autoModeService
|
||||
if (facadeFactory) {
|
||||
const facade = facadeFactory(projectPath);
|
||||
|
||||
// Check if already running
|
||||
if (facade.isAutoLoopRunning(normalizedBranchName)) {
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Auto mode is already running for ${worktreeDesc}`,
|
||||
alreadyRunning: true,
|
||||
branchName: normalizedBranchName,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Start the auto loop for this project/worktree
|
||||
const resolvedMaxConcurrency = await facade.startAutoLoop(
|
||||
normalizedBranchName,
|
||||
maxConcurrency
|
||||
);
|
||||
|
||||
logger.info(
|
||||
`Started auto loop for ${worktreeDesc} in project: ${projectPath} with maxConcurrency: ${resolvedMaxConcurrency}`
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Auto mode started with max ${resolvedMaxConcurrency} concurrent features`,
|
||||
branchName: normalizedBranchName,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Legacy path: use autoModeService directly
|
||||
// Check if already running
|
||||
if (autoModeService.isAutoLoopRunningForProject(projectPath, normalizedBranchName)) {
|
||||
res.json({
|
||||
|
||||
@@ -6,19 +6,13 @@
|
||||
*/
|
||||
|
||||
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 type { AutoModeServiceCompat } from '../../../services/auto-mode/index.js';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
/**
|
||||
* Create status handler with transition compatibility.
|
||||
* Accepts either autoModeService (legacy) or facade (new).
|
||||
* When facade is provided, creates a per-project facade for the request.
|
||||
* Create status handler.
|
||||
*/
|
||||
export function createStatusHandler(
|
||||
autoModeService: AutoModeService,
|
||||
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
|
||||
) {
|
||||
export function createStatusHandler(autoModeService: AutoModeServiceCompat) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, branchName } = req.body as {
|
||||
@@ -31,24 +25,6 @@ export function createStatusHandler(
|
||||
// Normalize branchName: undefined becomes null
|
||||
const normalizedBranchName = branchName ?? null;
|
||||
|
||||
// Use facade if factory is provided, otherwise fall back to autoModeService
|
||||
if (facadeFactory) {
|
||||
const facade = facadeFactory(projectPath);
|
||||
const projectStatus = facade.getStatusForProject(normalizedBranchName);
|
||||
res.json({
|
||||
success: true,
|
||||
isRunning: projectStatus.runningCount > 0,
|
||||
isAutoLoopRunning: projectStatus.isAutoLoopRunning,
|
||||
runningFeatures: projectStatus.runningFeatures,
|
||||
runningCount: projectStatus.runningCount,
|
||||
maxConcurrency: projectStatus.maxConcurrency,
|
||||
projectPath,
|
||||
branchName: normalizedBranchName,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Legacy path: use autoModeService directly
|
||||
const projectStatus = autoModeService.getStatusForProject(
|
||||
projectPath,
|
||||
normalizedBranchName
|
||||
@@ -66,8 +42,7 @@ export function createStatusHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
// Fall back to global status for backward compatibility
|
||||
// Global status uses autoModeService (facade is per-project)
|
||||
// Global status for backward compatibility
|
||||
const status = autoModeService.getStatus();
|
||||
const activeProjects = autoModeService.getActiveAutoLoopProjects();
|
||||
const activeWorktrees = autoModeService.getActiveAutoLoopWorktrees();
|
||||
|
||||
@@ -3,19 +3,10 @@
|
||||
*/
|
||||
|
||||
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 type { AutoModeServiceCompat } from '../../../services/auto-mode/index.js';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
/**
|
||||
* Create stop feature handler with transition compatibility.
|
||||
* Accepts either autoModeService (legacy) or facade (new).
|
||||
* Note: stopFeature is feature-scoped (not project-scoped), so a single facade can be used.
|
||||
*/
|
||||
export function createStopFeatureHandler(
|
||||
autoModeService: AutoModeService,
|
||||
facade?: AutoModeServiceFacade
|
||||
) {
|
||||
export function createStopFeatureHandler(autoModeService: AutoModeServiceCompat) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { featureId } = req.body as { featureId: string };
|
||||
@@ -25,10 +16,7 @@ export function createStopFeatureHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
// Use facade if provided, otherwise fall back to autoModeService
|
||||
const stopped = facade
|
||||
? await facade.stopFeature(featureId)
|
||||
: await autoModeService.stopFeature(featureId);
|
||||
const stopped = await autoModeService.stopFeature(featureId);
|
||||
res.json({ success: true, stopped });
|
||||
} catch (error) {
|
||||
logError(error, 'Stop feature failed');
|
||||
|
||||
@@ -3,21 +3,13 @@
|
||||
*/
|
||||
|
||||
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 type { AutoModeServiceCompat } from '../../../services/auto-mode/index.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
const logger = createLogger('AutoMode');
|
||||
|
||||
/**
|
||||
* Create stop handler with transition compatibility.
|
||||
* Accepts either autoModeService (legacy) or facadeFactory (new).
|
||||
*/
|
||||
export function createStopHandler(
|
||||
autoModeService: AutoModeService,
|
||||
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
|
||||
) {
|
||||
export function createStopHandler(autoModeService: AutoModeServiceCompat) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, branchName } = req.body as {
|
||||
@@ -39,38 +31,6 @@ export function createStopHandler(
|
||||
? `worktree ${normalizedBranchName}`
|
||||
: 'main worktree';
|
||||
|
||||
// Use facade if factory is provided, otherwise fall back to autoModeService
|
||||
if (facadeFactory) {
|
||||
const facade = facadeFactory(projectPath);
|
||||
|
||||
// Check if running
|
||||
if (!facade.isAutoLoopRunning(normalizedBranchName)) {
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Auto mode is not running for ${worktreeDesc}`,
|
||||
wasRunning: false,
|
||||
branchName: normalizedBranchName,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop the auto loop for this project/worktree
|
||||
const runningCount = await facade.stopAutoLoop(normalizedBranchName);
|
||||
|
||||
logger.info(
|
||||
`Stopped auto loop for ${worktreeDesc} in project: ${projectPath}, ${runningCount} features still running`
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Auto mode stopped',
|
||||
runningFeaturesCount: runningCount,
|
||||
branchName: normalizedBranchName,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Legacy path: use autoModeService directly
|
||||
// Check if running
|
||||
if (!autoModeService.isAutoLoopRunningForProject(projectPath, normalizedBranchName)) {
|
||||
res.json({
|
||||
|
||||
@@ -3,18 +3,10 @@
|
||||
*/
|
||||
|
||||
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 type { AutoModeServiceCompat } from '../../../services/auto-mode/index.js';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
/**
|
||||
* Create verify feature handler with transition compatibility.
|
||||
* Accepts either autoModeService (legacy) or facadeFactory (new).
|
||||
*/
|
||||
export function createVerifyFeatureHandler(
|
||||
autoModeService: AutoModeService,
|
||||
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
|
||||
) {
|
||||
export function createVerifyFeatureHandler(autoModeService: AutoModeServiceCompat) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { projectPath, featureId } = req.body as {
|
||||
@@ -30,15 +22,6 @@ export function createVerifyFeatureHandler(
|
||||
return;
|
||||
}
|
||||
|
||||
// Use facade if factory is provided, otherwise fall back to autoModeService
|
||||
if (facadeFactory) {
|
||||
const facade = facadeFactory(projectPath);
|
||||
const passes = await facade.verifyFeature(featureId);
|
||||
res.json({ success: true, passes });
|
||||
return;
|
||||
}
|
||||
|
||||
// Legacy path: use autoModeService directly
|
||||
const passes = await autoModeService.verifyFeature(projectPath, featureId);
|
||||
res.json({ success: true, passes });
|
||||
} catch (error) {
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
import { Router } from 'express';
|
||||
import { FeatureLoader } from '../../services/feature-loader.js';
|
||||
import type { SettingsService } from '../../services/settings-service.js';
|
||||
import type { AutoModeService } from '../../services/auto-mode-service.js';
|
||||
import type { AutoModeServiceFacade } from '../../services/auto-mode/index.js';
|
||||
import type { AutoModeServiceCompat } from '../../services/auto-mode/index.js';
|
||||
import type { EventEmitter } from '../../lib/events.js';
|
||||
import { validatePathParams } from '../../middleware/validate-paths.js';
|
||||
import { createListHandler } from './routes/list.js';
|
||||
@@ -25,15 +24,14 @@ export function createFeaturesRoutes(
|
||||
featureLoader: FeatureLoader,
|
||||
settingsService?: SettingsService,
|
||||
events?: EventEmitter,
|
||||
autoModeService?: AutoModeService,
|
||||
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
|
||||
autoModeService?: AutoModeServiceCompat
|
||||
): Router {
|
||||
const router = Router();
|
||||
|
||||
router.post(
|
||||
'/list',
|
||||
validatePathParams('projectPath'),
|
||||
createListHandler(featureLoader, autoModeService, facadeFactory)
|
||||
createListHandler(featureLoader, autoModeService)
|
||||
);
|
||||
router.post('/get', validatePathParams('projectPath'), createGetHandler(featureLoader));
|
||||
router.post(
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import { FeatureLoader } from '../../../services/feature-loader.js';
|
||||
import type { AutoModeService } from '../../../services/auto-mode-service.js';
|
||||
import type { AutoModeServiceFacade } from '../../../services/auto-mode/index.js';
|
||||
import type { AutoModeServiceCompat } from '../../../services/auto-mode/index.js';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
|
||||
@@ -16,8 +15,7 @@ const logger = createLogger('FeaturesListRoute');
|
||||
|
||||
export function createListHandler(
|
||||
featureLoader: FeatureLoader,
|
||||
autoModeService?: AutoModeService,
|
||||
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
|
||||
autoModeService?: AutoModeServiceCompat
|
||||
) {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
@@ -34,21 +32,7 @@ export function createListHandler(
|
||||
// This detects features whose branches no longer exist (e.g., after merge/delete)
|
||||
// We don't await this to keep the list response fast
|
||||
// Note: detectOrphanedFeatures handles errors internally and always resolves
|
||||
if (facadeFactory) {
|
||||
const facade = facadeFactory(projectPath);
|
||||
facade.detectOrphanedFeatures().then((orphanedFeatures) => {
|
||||
if (orphanedFeatures.length > 0) {
|
||||
logger.info(
|
||||
`[ProjectLoad] Detected ${orphanedFeatures.length} orphaned feature(s) in ${projectPath}`
|
||||
);
|
||||
for (const { feature, missingBranch } of orphanedFeatures) {
|
||||
logger.info(
|
||||
`[ProjectLoad] Orphaned: ${feature.title || feature.id} - branch "${missingBranch}" no longer exists`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (autoModeService) {
|
||||
if (autoModeService) {
|
||||
autoModeService.detectOrphanedFeatures(projectPath).then((orphanedFeatures) => {
|
||||
if (orphanedFeatures.length > 0) {
|
||||
logger.info(
|
||||
|
||||
@@ -4,31 +4,23 @@
|
||||
|
||||
import { Router } from 'express';
|
||||
import type { FeatureLoader } from '../../services/feature-loader.js';
|
||||
import type { AutoModeService } from '../../services/auto-mode-service.js';
|
||||
import type { AutoModeServiceFacade } from '../../services/auto-mode/index.js';
|
||||
import type { AutoModeServiceCompat } from '../../services/auto-mode/index.js';
|
||||
import type { SettingsService } from '../../services/settings-service.js';
|
||||
import type { NotificationService } from '../../services/notification-service.js';
|
||||
import { createOverviewHandler } from './routes/overview.js';
|
||||
|
||||
export function createProjectsRoutes(
|
||||
featureLoader: FeatureLoader,
|
||||
autoModeService: AutoModeService,
|
||||
autoModeService: AutoModeServiceCompat,
|
||||
settingsService: SettingsService,
|
||||
notificationService: NotificationService,
|
||||
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
|
||||
notificationService: NotificationService
|
||||
): Router {
|
||||
const router = Router();
|
||||
|
||||
// GET /overview - Get aggregate status for all projects
|
||||
router.get(
|
||||
'/overview',
|
||||
createOverviewHandler(
|
||||
featureLoader,
|
||||
autoModeService,
|
||||
settingsService,
|
||||
notificationService,
|
||||
facadeFactory
|
||||
)
|
||||
createOverviewHandler(featureLoader, autoModeService, settingsService, notificationService)
|
||||
);
|
||||
|
||||
return router;
|
||||
|
||||
@@ -9,9 +9,8 @@
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
import type { FeatureLoader } from '../../../services/feature-loader.js';
|
||||
import type { AutoModeService } from '../../../services/auto-mode-service.js';
|
||||
import type {
|
||||
AutoModeServiceFacade,
|
||||
AutoModeServiceCompat,
|
||||
RunningAgentInfo,
|
||||
ProjectAutoModeStatus,
|
||||
} from '../../../services/auto-mode/index.js';
|
||||
@@ -152,10 +151,9 @@ function getLastActivityAt(features: Feature[]): string | undefined {
|
||||
|
||||
export function createOverviewHandler(
|
||||
featureLoader: FeatureLoader,
|
||||
autoModeService: AutoModeService,
|
||||
autoModeService: AutoModeServiceCompat,
|
||||
settingsService: SettingsService,
|
||||
notificationService: NotificationService,
|
||||
facadeFactory?: (projectPath: string) => AutoModeServiceFacade
|
||||
notificationService: NotificationService
|
||||
) {
|
||||
return async (_req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
@@ -164,15 +162,7 @@ export function createOverviewHandler(
|
||||
const projectRefs: ProjectRef[] = settings.projects || [];
|
||||
|
||||
// Get all running agents once to count live running features per project
|
||||
// Use facade if available, otherwise fall back to autoModeService
|
||||
let allRunningAgents: RunningAgentInfo[];
|
||||
if (facadeFactory && projectRefs.length > 0) {
|
||||
// For running agents, we can use any project's facade since it's a global query
|
||||
const facade = facadeFactory(projectRefs[0].path);
|
||||
allRunningAgents = await facade.getRunningAgents();
|
||||
} else {
|
||||
allRunningAgents = await autoModeService.getRunningAgents();
|
||||
}
|
||||
const allRunningAgents: RunningAgentInfo[] = await autoModeService.getRunningAgents();
|
||||
|
||||
// Collect project statuses in parallel
|
||||
const projectStatusPromises = projectRefs.map(async (projectRef): Promise<ProjectStatus> => {
|
||||
@@ -183,13 +173,10 @@ export function createOverviewHandler(
|
||||
const totalFeatures = features.length;
|
||||
|
||||
// Get auto-mode status for this project (main worktree, branchName = null)
|
||||
let autoModeStatus: ProjectAutoModeStatus;
|
||||
if (facadeFactory) {
|
||||
const facade = facadeFactory(projectRef.path);
|
||||
autoModeStatus = facade.getStatusForProject(null);
|
||||
} else {
|
||||
autoModeStatus = autoModeService.getStatusForProject(projectRef.path, null);
|
||||
}
|
||||
const autoModeStatus: ProjectAutoModeStatus = autoModeService.getStatusForProject(
|
||||
projectRef.path,
|
||||
null
|
||||
);
|
||||
const isAutoModeRunning = autoModeStatus.isAutoLoopRunning;
|
||||
|
||||
// Count live running features for this project (across all branches)
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import type { AutoModeService } from '../../services/auto-mode-service.js';
|
||||
import type { AutoModeServiceCompat } from '../../services/auto-mode/index.js';
|
||||
import { createIndexHandler } from './routes/index.js';
|
||||
|
||||
export function createRunningAgentsRoutes(autoModeService: AutoModeService): Router {
|
||||
export function createRunningAgentsRoutes(autoModeService: AutoModeServiceCompat): Router {
|
||||
const router = Router();
|
||||
|
||||
router.get('/', createIndexHandler(autoModeService));
|
||||
|
||||
@@ -3,29 +3,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 type { AutoModeServiceCompat } from '../../../services/auto-mode/index.js';
|
||||
import { getBacklogPlanStatus, getRunningDetails } from '../../backlog-plan/common.js';
|
||||
import { getAllRunningGenerations } from '../../app-spec/common.js';
|
||||
import path from 'path';
|
||||
import { getErrorMessage, logError } from '../common.js';
|
||||
|
||||
/**
|
||||
* Create index handler with transition compatibility.
|
||||
* Accepts either autoModeService (legacy) or facade (new).
|
||||
* Note: getRunningAgents is global (not per-project), so facade is created
|
||||
* with an empty path for global queries.
|
||||
*/
|
||||
export function createIndexHandler(
|
||||
autoModeService: AutoModeService,
|
||||
facade?: AutoModeServiceFacade
|
||||
) {
|
||||
export function createIndexHandler(autoModeService: AutoModeServiceCompat) {
|
||||
return async (_req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
// Use facade if provided, otherwise fall back to autoModeService
|
||||
const runningAgents = facade
|
||||
? [...(await facade.getRunningAgents())]
|
||||
: [...(await autoModeService.getRunningAgents())];
|
||||
const runningAgents = [...(await autoModeService.getRunningAgents())];
|
||||
|
||||
const backlogPlanStatus = getBacklogPlanStatus();
|
||||
const backlogPlanDetails = getRunningDetails();
|
||||
|
||||
@@ -363,6 +363,36 @@ export class AutoLoopCoordinator {
|
||||
return projectState?.config ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active auto loop worktrees with their project paths and branch names
|
||||
*/
|
||||
getActiveWorktrees(): Array<{ projectPath: string; branchName: string | null }> {
|
||||
const activeWorktrees: Array<{ projectPath: string; branchName: string | null }> = [];
|
||||
for (const [, state] of this.autoLoopsByProject) {
|
||||
if (state.isRunning) {
|
||||
activeWorktrees.push({
|
||||
projectPath: state.config.projectPath,
|
||||
branchName: state.branchName,
|
||||
});
|
||||
}
|
||||
}
|
||||
return activeWorktrees;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all projects that have auto mode running (returns unique project paths)
|
||||
* @deprecated Use getActiveWorktrees instead for full worktree information
|
||||
*/
|
||||
getActiveProjects(): string[] {
|
||||
const activeProjects = new Set<string>();
|
||||
for (const [, state] of this.autoLoopsByProject) {
|
||||
if (state.isRunning) {
|
||||
activeProjects.add(state.config.projectPath);
|
||||
}
|
||||
}
|
||||
return Array.from(activeProjects);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get count of running features for a specific worktree
|
||||
* Delegates to ConcurrencyManager.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
225
apps/server/src/services/auto-mode/compat.ts
Normal file
225
apps/server/src/services/auto-mode/compat.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
/**
|
||||
* Compatibility Shim - Provides AutoModeService-like interface using the new architecture
|
||||
*
|
||||
* This allows existing routes to work without major changes during the transition.
|
||||
* Routes receive this shim which delegates to GlobalAutoModeService and facades.
|
||||
*
|
||||
* This is a TEMPORARY shim - routes should be updated to use the new interface directly.
|
||||
*/
|
||||
|
||||
import type { Feature } from '@automaker/types';
|
||||
import type { EventEmitter } from '../../lib/events.js';
|
||||
import { GlobalAutoModeService } from './global-service.js';
|
||||
import { AutoModeServiceFacade } from './facade.js';
|
||||
import type { SettingsService } from '../settings-service.js';
|
||||
import type { FeatureLoader } from '../feature-loader.js';
|
||||
import type { FacadeOptions, AutoModeStatus, RunningAgentInfo } from './types.js';
|
||||
|
||||
/**
|
||||
* AutoModeServiceCompat wraps GlobalAutoModeService and facades to provide
|
||||
* the old AutoModeService interface that routes expect.
|
||||
*/
|
||||
export class AutoModeServiceCompat {
|
||||
private readonly globalService: GlobalAutoModeService;
|
||||
private readonly facadeOptions: FacadeOptions;
|
||||
|
||||
constructor(
|
||||
events: EventEmitter,
|
||||
settingsService: SettingsService | null,
|
||||
featureLoader: FeatureLoader
|
||||
) {
|
||||
this.globalService = new GlobalAutoModeService(events, settingsService, featureLoader);
|
||||
const sharedServices = this.globalService.getSharedServices();
|
||||
|
||||
this.facadeOptions = {
|
||||
events,
|
||||
settingsService,
|
||||
featureLoader,
|
||||
sharedServices,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global service for direct access
|
||||
*/
|
||||
getGlobalService(): GlobalAutoModeService {
|
||||
return this.globalService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a facade for a specific project
|
||||
*/
|
||||
createFacade(projectPath: string): AutoModeServiceFacade {
|
||||
return AutoModeServiceFacade.create(projectPath, this.facadeOptions);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// GLOBAL OPERATIONS (delegated to GlobalAutoModeService)
|
||||
// ===========================================================================
|
||||
|
||||
getStatus(): AutoModeStatus {
|
||||
return this.globalService.getStatus();
|
||||
}
|
||||
|
||||
getActiveAutoLoopProjects(): string[] {
|
||||
return this.globalService.getActiveAutoLoopProjects();
|
||||
}
|
||||
|
||||
getActiveAutoLoopWorktrees(): Array<{ projectPath: string; branchName: string | null }> {
|
||||
return this.globalService.getActiveAutoLoopWorktrees();
|
||||
}
|
||||
|
||||
async getRunningAgents(): Promise<RunningAgentInfo[]> {
|
||||
return this.globalService.getRunningAgents();
|
||||
}
|
||||
|
||||
async markAllRunningFeaturesInterrupted(reason?: string): Promise<void> {
|
||||
return this.globalService.markAllRunningFeaturesInterrupted(reason);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// PER-PROJECT OPERATIONS (delegated to facades)
|
||||
// ===========================================================================
|
||||
|
||||
getStatusForProject(
|
||||
projectPath: string,
|
||||
branchName: string | null = null
|
||||
): {
|
||||
isAutoLoopRunning: boolean;
|
||||
runningFeatures: string[];
|
||||
runningCount: number;
|
||||
maxConcurrency: number;
|
||||
branchName: string | null;
|
||||
} {
|
||||
const facade = this.createFacade(projectPath);
|
||||
return facade.getStatusForProject(branchName);
|
||||
}
|
||||
|
||||
isAutoLoopRunningForProject(projectPath: string, branchName: string | null = null): boolean {
|
||||
const facade = this.createFacade(projectPath);
|
||||
return facade.isAutoLoopRunning(branchName);
|
||||
}
|
||||
|
||||
async startAutoLoopForProject(
|
||||
projectPath: string,
|
||||
branchName: string | null = null,
|
||||
maxConcurrency?: number
|
||||
): Promise<number> {
|
||||
const facade = this.createFacade(projectPath);
|
||||
return facade.startAutoLoop(branchName, maxConcurrency);
|
||||
}
|
||||
|
||||
async stopAutoLoopForProject(
|
||||
projectPath: string,
|
||||
branchName: string | null = null
|
||||
): Promise<number> {
|
||||
const facade = this.createFacade(projectPath);
|
||||
return facade.stopAutoLoop(branchName);
|
||||
}
|
||||
|
||||
async executeFeature(
|
||||
projectPath: string,
|
||||
featureId: string,
|
||||
useWorktrees = false,
|
||||
isAutoMode = false,
|
||||
providedWorktreePath?: string,
|
||||
options?: { continuationPrompt?: string; _calledInternally?: boolean }
|
||||
): Promise<void> {
|
||||
const facade = this.createFacade(projectPath);
|
||||
return facade.executeFeature(
|
||||
featureId,
|
||||
useWorktrees,
|
||||
isAutoMode,
|
||||
providedWorktreePath,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
async stopFeature(featureId: string): Promise<boolean> {
|
||||
// Stop feature is tricky - we need to find which project the feature is running in
|
||||
// The concurrency manager tracks this
|
||||
const runningAgents = await this.getRunningAgents();
|
||||
const agent = runningAgents.find((a) => a.featureId === featureId);
|
||||
if (agent) {
|
||||
const facade = this.createFacade(agent.projectPath);
|
||||
return facade.stopFeature(featureId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async resumeFeature(projectPath: string, featureId: string, useWorktrees = false): Promise<void> {
|
||||
const facade = this.createFacade(projectPath);
|
||||
return facade.resumeFeature(featureId, useWorktrees);
|
||||
}
|
||||
|
||||
async followUpFeature(
|
||||
projectPath: string,
|
||||
featureId: string,
|
||||
prompt: string,
|
||||
imagePaths?: string[],
|
||||
useWorktrees = true
|
||||
): Promise<void> {
|
||||
const facade = this.createFacade(projectPath);
|
||||
return facade.followUpFeature(featureId, prompt, imagePaths, useWorktrees);
|
||||
}
|
||||
|
||||
async verifyFeature(projectPath: string, featureId: string): Promise<boolean> {
|
||||
const facade = this.createFacade(projectPath);
|
||||
return facade.verifyFeature(featureId);
|
||||
}
|
||||
|
||||
async commitFeature(
|
||||
projectPath: string,
|
||||
featureId: string,
|
||||
providedWorktreePath?: string
|
||||
): Promise<string | null> {
|
||||
const facade = this.createFacade(projectPath);
|
||||
return facade.commitFeature(featureId, providedWorktreePath);
|
||||
}
|
||||
|
||||
async contextExists(projectPath: string, featureId: string): Promise<boolean> {
|
||||
const facade = this.createFacade(projectPath);
|
||||
return facade.contextExists(featureId);
|
||||
}
|
||||
|
||||
async analyzeProject(projectPath: string): Promise<void> {
|
||||
const facade = this.createFacade(projectPath);
|
||||
return facade.analyzeProject();
|
||||
}
|
||||
|
||||
async resolvePlanApproval(
|
||||
projectPath: string,
|
||||
featureId: string,
|
||||
approved: boolean,
|
||||
editedPlan?: string,
|
||||
feedback?: string
|
||||
): Promise<{ success: boolean; error?: string }> {
|
||||
const facade = this.createFacade(projectPath);
|
||||
return facade.resolvePlanApproval(featureId, approved, editedPlan, feedback);
|
||||
}
|
||||
|
||||
async resumeInterruptedFeatures(projectPath: string): Promise<void> {
|
||||
const facade = this.createFacade(projectPath);
|
||||
return facade.resumeInterruptedFeatures();
|
||||
}
|
||||
|
||||
async checkWorktreeCapacity(
|
||||
projectPath: string,
|
||||
featureId: string
|
||||
): Promise<{
|
||||
hasCapacity: boolean;
|
||||
currentAgents: number;
|
||||
maxAgents: number;
|
||||
branchName: string | null;
|
||||
}> {
|
||||
const facade = this.createFacade(projectPath);
|
||||
return facade.checkWorktreeCapacity(featureId);
|
||||
}
|
||||
|
||||
async detectOrphanedFeatures(
|
||||
projectPath: string
|
||||
): Promise<Array<{ feature: Feature; missingBranch: string }>> {
|
||||
const facade = this.createFacade(projectPath);
|
||||
return facade.detectOrphanedFeatures();
|
||||
}
|
||||
}
|
||||
@@ -86,12 +86,20 @@ export class AutoModeServiceFacade {
|
||||
* @param options - Configuration options including events, settingsService, featureLoader
|
||||
*/
|
||||
static create(projectPath: string, options: FacadeOptions): AutoModeServiceFacade {
|
||||
const { events, settingsService = null, featureLoader = new FeatureLoader() } = options;
|
||||
const {
|
||||
events,
|
||||
settingsService = null,
|
||||
featureLoader = new FeatureLoader(),
|
||||
sharedServices,
|
||||
} = options;
|
||||
|
||||
// Create core services
|
||||
const eventBus = new TypedEventBus(events);
|
||||
const worktreeResolver = new WorktreeResolver();
|
||||
const concurrencyManager = new ConcurrencyManager((p) => worktreeResolver.getCurrentBranch(p));
|
||||
// Use shared services if provided, otherwise create new ones
|
||||
// Shared services allow multiple facades to share state (e.g., running features, auto loops)
|
||||
const eventBus = sharedServices?.eventBus ?? new TypedEventBus(events);
|
||||
const worktreeResolver = sharedServices?.worktreeResolver ?? new WorktreeResolver();
|
||||
const concurrencyManager =
|
||||
sharedServices?.concurrencyManager ??
|
||||
new ConcurrencyManager((p) => worktreeResolver.getCurrentBranch(p));
|
||||
const featureStateManager = new FeatureStateManager(events, featureLoader);
|
||||
const planApprovalService = new PlanApprovalService(
|
||||
eventBus,
|
||||
@@ -151,36 +159,39 @@ export class AutoModeServiceFacade {
|
||||
}
|
||||
);
|
||||
|
||||
// AutoLoopCoordinator
|
||||
const autoLoopCoordinator = new AutoLoopCoordinator(
|
||||
eventBus,
|
||||
concurrencyManager,
|
||||
settingsService,
|
||||
// Callbacks
|
||||
(pPath, featureId, useWorktrees, isAutoMode) =>
|
||||
facadeInstance!.executeFeature(featureId, useWorktrees, isAutoMode),
|
||||
(pPath, branchName) =>
|
||||
featureLoader
|
||||
.getAll(pPath)
|
||||
.then((features) =>
|
||||
features.filter(
|
||||
(f) =>
|
||||
(f.status === 'backlog' || f.status === 'ready') &&
|
||||
(branchName === null
|
||||
? !f.branchName || f.branchName === 'main'
|
||||
: f.branchName === branchName)
|
||||
)
|
||||
),
|
||||
(pPath, branchName, maxConcurrency) =>
|
||||
facadeInstance!.saveExecutionStateForProject(branchName, maxConcurrency),
|
||||
(pPath, branchName) => facadeInstance!.clearExecutionState(branchName),
|
||||
(pPath) => featureStateManager.resetStuckFeatures(pPath),
|
||||
(feature) =>
|
||||
feature.status === 'completed' ||
|
||||
feature.status === 'verified' ||
|
||||
feature.status === 'waiting_approval',
|
||||
(featureId) => concurrencyManager.isRunning(featureId)
|
||||
);
|
||||
// AutoLoopCoordinator - use shared if provided, otherwise create new
|
||||
// Note: When using shared autoLoopCoordinator, callbacks are already set up by the global service
|
||||
const autoLoopCoordinator =
|
||||
sharedServices?.autoLoopCoordinator ??
|
||||
new AutoLoopCoordinator(
|
||||
eventBus,
|
||||
concurrencyManager,
|
||||
settingsService,
|
||||
// Callbacks
|
||||
(pPath, featureId, useWorktrees, isAutoMode) =>
|
||||
facadeInstance!.executeFeature(featureId, useWorktrees, isAutoMode),
|
||||
(pPath, branchName) =>
|
||||
featureLoader
|
||||
.getAll(pPath)
|
||||
.then((features) =>
|
||||
features.filter(
|
||||
(f) =>
|
||||
(f.status === 'backlog' || f.status === 'ready') &&
|
||||
(branchName === null
|
||||
? !f.branchName || f.branchName === 'main'
|
||||
: f.branchName === branchName)
|
||||
)
|
||||
),
|
||||
(pPath, branchName, maxConcurrency) =>
|
||||
facadeInstance!.saveExecutionStateForProject(branchName, maxConcurrency),
|
||||
(pPath, branchName) => facadeInstance!.clearExecutionState(branchName),
|
||||
(pPath) => featureStateManager.resetStuckFeatures(pPath),
|
||||
(feature) =>
|
||||
feature.status === 'completed' ||
|
||||
feature.status === 'verified' ||
|
||||
feature.status === 'waiting_approval',
|
||||
(featureId) => concurrencyManager.isRunning(featureId)
|
||||
);
|
||||
|
||||
// ExecutionService - runAgentFn is a stub
|
||||
const executionService = new ExecutionService(
|
||||
|
||||
200
apps/server/src/services/auto-mode/global-service.ts
Normal file
200
apps/server/src/services/auto-mode/global-service.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* GlobalAutoModeService - Global operations for auto-mode that span across all projects
|
||||
*
|
||||
* This service manages global state and operations that are not project-specific:
|
||||
* - Overall status (all running features across all projects)
|
||||
* - Active auto loop projects and worktrees
|
||||
* - Graceful shutdown (mark all features as interrupted)
|
||||
*
|
||||
* Per-project operations should use AutoModeServiceFacade instead.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import type { Feature } from '@automaker/types';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import type { EventEmitter } from '../../lib/events.js';
|
||||
import { TypedEventBus } from '../typed-event-bus.js';
|
||||
import { ConcurrencyManager } from '../concurrency-manager.js';
|
||||
import { WorktreeResolver } from '../worktree-resolver.js';
|
||||
import { AutoLoopCoordinator } from '../auto-loop-coordinator.js';
|
||||
import { FeatureStateManager } from '../feature-state-manager.js';
|
||||
import { FeatureLoader } from '../feature-loader.js';
|
||||
import type { SettingsService } from '../settings-service.js';
|
||||
import type { SharedServices, AutoModeStatus, RunningAgentInfo } from './types.js';
|
||||
|
||||
const logger = createLogger('GlobalAutoModeService');
|
||||
|
||||
/**
|
||||
* GlobalAutoModeService provides global operations for auto-mode.
|
||||
*
|
||||
* Created once at server startup, shared across all facades.
|
||||
*/
|
||||
export class GlobalAutoModeService {
|
||||
private readonly eventBus: TypedEventBus;
|
||||
private readonly concurrencyManager: ConcurrencyManager;
|
||||
private readonly autoLoopCoordinator: AutoLoopCoordinator;
|
||||
private readonly worktreeResolver: WorktreeResolver;
|
||||
private readonly featureStateManager: FeatureStateManager;
|
||||
private readonly featureLoader: FeatureLoader;
|
||||
|
||||
constructor(
|
||||
events: EventEmitter,
|
||||
settingsService: SettingsService | null,
|
||||
featureLoader: FeatureLoader = new FeatureLoader()
|
||||
) {
|
||||
this.featureLoader = featureLoader;
|
||||
this.eventBus = new TypedEventBus(events);
|
||||
this.worktreeResolver = new WorktreeResolver();
|
||||
this.concurrencyManager = new ConcurrencyManager((p) =>
|
||||
this.worktreeResolver.getCurrentBranch(p)
|
||||
);
|
||||
this.featureStateManager = new FeatureStateManager(events, featureLoader);
|
||||
|
||||
// Create AutoLoopCoordinator with callbacks
|
||||
// These callbacks use placeholders since GlobalAutoModeService doesn't execute features
|
||||
// Feature execution is done via facades
|
||||
this.autoLoopCoordinator = new AutoLoopCoordinator(
|
||||
this.eventBus,
|
||||
this.concurrencyManager,
|
||||
settingsService,
|
||||
// executeFeatureFn - not used by global service, routes handle execution
|
||||
async () => {
|
||||
throw new Error('executeFeatureFn not available in GlobalAutoModeService');
|
||||
},
|
||||
// getBacklogFeaturesFn
|
||||
(pPath, branchName) =>
|
||||
featureLoader
|
||||
.getAll(pPath)
|
||||
.then((features) =>
|
||||
features.filter(
|
||||
(f) =>
|
||||
(f.status === 'backlog' || f.status === 'ready') &&
|
||||
(branchName === null
|
||||
? !f.branchName || f.branchName === 'main'
|
||||
: f.branchName === branchName)
|
||||
)
|
||||
),
|
||||
// saveExecutionStateFn - placeholder
|
||||
async () => {},
|
||||
// clearExecutionStateFn - placeholder
|
||||
async () => {},
|
||||
// resetStuckFeaturesFn
|
||||
(pPath) => this.featureStateManager.resetStuckFeatures(pPath),
|
||||
// isFeatureDoneFn
|
||||
(feature) =>
|
||||
feature.status === 'completed' ||
|
||||
feature.status === 'verified' ||
|
||||
feature.status === 'waiting_approval',
|
||||
// isFeatureRunningFn
|
||||
(featureId) => this.concurrencyManager.isRunning(featureId)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the shared services for use by facades.
|
||||
* This allows facades to share state with the global service.
|
||||
*/
|
||||
getSharedServices(): SharedServices {
|
||||
return {
|
||||
eventBus: this.eventBus,
|
||||
concurrencyManager: this.concurrencyManager,
|
||||
autoLoopCoordinator: this.autoLoopCoordinator,
|
||||
worktreeResolver: this.worktreeResolver,
|
||||
};
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// GLOBAL STATUS (3 methods)
|
||||
// ===========================================================================
|
||||
|
||||
/**
|
||||
* Get global status (all projects combined)
|
||||
*/
|
||||
getStatus(): AutoModeStatus {
|
||||
const allRunning = this.concurrencyManager.getAllRunning();
|
||||
return {
|
||||
isRunning: allRunning.length > 0,
|
||||
runningFeatures: allRunning.map((rf) => rf.featureId),
|
||||
runningCount: allRunning.length,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active auto loop projects (unique project paths)
|
||||
*/
|
||||
getActiveAutoLoopProjects(): string[] {
|
||||
return this.autoLoopCoordinator.getActiveProjects();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active auto loop worktrees
|
||||
*/
|
||||
getActiveAutoLoopWorktrees(): Array<{ projectPath: string; branchName: string | null }> {
|
||||
return this.autoLoopCoordinator.getActiveWorktrees();
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// RUNNING AGENTS (1 method)
|
||||
// ===========================================================================
|
||||
|
||||
/**
|
||||
* Get detailed info about all running agents
|
||||
*/
|
||||
async getRunningAgents(): Promise<RunningAgentInfo[]> {
|
||||
const agents = await Promise.all(
|
||||
this.concurrencyManager.getAllRunning().map(async (rf) => {
|
||||
let title: string | undefined;
|
||||
let description: string | undefined;
|
||||
let branchName: string | undefined;
|
||||
|
||||
try {
|
||||
const feature = await this.featureLoader.get(rf.projectPath, rf.featureId);
|
||||
if (feature) {
|
||||
title = feature.title;
|
||||
description = feature.description;
|
||||
branchName = feature.branchName;
|
||||
}
|
||||
} catch {
|
||||
// Silently ignore
|
||||
}
|
||||
|
||||
return {
|
||||
featureId: rf.featureId,
|
||||
projectPath: rf.projectPath,
|
||||
projectName: path.basename(rf.projectPath),
|
||||
isAutoMode: rf.isAutoMode,
|
||||
model: rf.model,
|
||||
provider: rf.provider,
|
||||
title,
|
||||
description,
|
||||
branchName,
|
||||
};
|
||||
})
|
||||
);
|
||||
return agents;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// LIFECYCLE (1 method)
|
||||
// ===========================================================================
|
||||
|
||||
/**
|
||||
* Mark all running features as interrupted.
|
||||
* Called during graceful shutdown.
|
||||
*
|
||||
* @param reason - Optional reason for the interruption
|
||||
*/
|
||||
async markAllRunningFeaturesInterrupted(reason?: string): Promise<void> {
|
||||
const allRunning = this.concurrencyManager.getAllRunning();
|
||||
|
||||
for (const rf of allRunning) {
|
||||
await this.featureStateManager.markFeatureInterrupted(rf.projectPath, rf.featureId, reason);
|
||||
}
|
||||
|
||||
if (allRunning.length > 0) {
|
||||
logger.info(
|
||||
`Marked ${allRunning.length} running feature(s) as interrupted: ${reason || 'no reason provided'}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,16 @@
|
||||
* Auto Mode Service Module
|
||||
*
|
||||
* Entry point for auto-mode functionality. Exports:
|
||||
* - AutoModeServiceFacade: Clean facade for auto-mode operations
|
||||
* - GlobalAutoModeService: Global operations that span all projects
|
||||
* - AutoModeServiceFacade: Per-project facade for auto-mode operations
|
||||
* - createAutoModeFacade: Convenience factory function
|
||||
* - Types for route consumption
|
||||
*/
|
||||
|
||||
// Main facade export
|
||||
// Main exports
|
||||
export { GlobalAutoModeService } from './global-service.js';
|
||||
export { AutoModeServiceFacade } from './facade.js';
|
||||
export { AutoModeServiceCompat } from './compat.js';
|
||||
|
||||
// Convenience factory function
|
||||
import { AutoModeServiceFacade } from './facade.js';
|
||||
@@ -49,11 +52,13 @@ export function createAutoModeFacade(
|
||||
// Type exports from types.ts
|
||||
export type {
|
||||
FacadeOptions,
|
||||
SharedServices,
|
||||
AutoModeStatus,
|
||||
ProjectAutoModeStatus,
|
||||
WorktreeCapacityInfo,
|
||||
RunningAgentInfo,
|
||||
OrphanedFeatureInfo,
|
||||
GlobalAutoModeOperations,
|
||||
} from './types.js';
|
||||
|
||||
// Re-export types from extracted services for route convenience
|
||||
|
||||
@@ -11,6 +11,10 @@ import type { EventEmitter } from '../../lib/events.js';
|
||||
import type { Feature, ModelProvider } from '@automaker/types';
|
||||
import type { SettingsService } from '../settings-service.js';
|
||||
import type { FeatureLoader } from '../feature-loader.js';
|
||||
import type { ConcurrencyManager } from '../concurrency-manager.js';
|
||||
import type { AutoLoopCoordinator } from '../auto-loop-coordinator.js';
|
||||
import type { WorktreeResolver } from '../worktree-resolver.js';
|
||||
import type { TypedEventBus } from '../typed-event-bus.js';
|
||||
|
||||
// Re-export types from extracted services for route consumption
|
||||
export type { AutoModeConfig, ProjectAutoLoopState } from '../auto-loop-coordinator.js';
|
||||
@@ -25,6 +29,20 @@ export type { PlanApprovalResult, ResolveApprovalResult } from '../plan-approval
|
||||
|
||||
export type { ExecutionState } from '../recovery-service.js';
|
||||
|
||||
/**
|
||||
* Shared services that can be passed to facades to enable state sharing
|
||||
*/
|
||||
export interface SharedServices {
|
||||
/** TypedEventBus for typed event emission */
|
||||
eventBus: TypedEventBus;
|
||||
/** ConcurrencyManager for tracking running features across all projects */
|
||||
concurrencyManager: ConcurrencyManager;
|
||||
/** AutoLoopCoordinator for managing auto loop state across all projects */
|
||||
autoLoopCoordinator: AutoLoopCoordinator;
|
||||
/** WorktreeResolver for git worktree operations */
|
||||
worktreeResolver: WorktreeResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for creating an AutoModeServiceFacade instance
|
||||
*/
|
||||
@@ -35,6 +53,8 @@ export interface FacadeOptions {
|
||||
settingsService?: SettingsService | null;
|
||||
/** FeatureLoader for loading feature data (optional, defaults to new FeatureLoader()) */
|
||||
featureLoader?: FeatureLoader;
|
||||
/** Shared services for state sharing across facades (optional) */
|
||||
sharedServices?: SharedServices;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,3 +109,20 @@ export interface OrphanedFeatureInfo {
|
||||
feature: Feature;
|
||||
missingBranch: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface describing global auto-mode operations (not project-specific).
|
||||
* Used by routes that need global state access.
|
||||
*/
|
||||
export interface GlobalAutoModeOperations {
|
||||
/** Get global status (all projects combined) */
|
||||
getStatus(): AutoModeStatus;
|
||||
/** Get all active auto loop projects (unique project paths) */
|
||||
getActiveAutoLoopProjects(): string[];
|
||||
/** Get all active auto loop worktrees */
|
||||
getActiveAutoLoopWorktrees(): Array<{ projectPath: string; branchName: string | null }>;
|
||||
/** Get detailed info about all running agents */
|
||||
getRunningAgents(): Promise<RunningAgentInfo[]>;
|
||||
/** Mark all running features as interrupted (for graceful shutdown) */
|
||||
markAllRunningFeaturesInterrupted(reason?: string): Promise<void>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user