feat(FN-1733): add dedicated pause/resume APIs for projects
- Add pauseProject() and resumeProject() methods to ProjectEngineManager - Wire pause and resume routes to engineManager with proper error handling - Update frontend useProjectActions hook to use dedicated pause/resume APIs - Add comprehensive tests for pause/resume in ProjectEngineManager - Add route tests for project pause/resume with engineManager mocks - Fix mock stubs for getProject/updateProject/updateProjectHealth
This commit is contained in:
@@ -110,6 +110,49 @@ export class ProjectEngineManager {
|
||||
return this.engines.has(projectId) || this.starting.has(projectId);
|
||||
}
|
||||
|
||||
// ── Pause / Resume ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Pause a project: update its status in CentralCore and stop its engine.
|
||||
* This prevents the reconciliation loop from restarting the engine.
|
||||
*/
|
||||
async pauseProject(projectId: string): Promise<void> {
|
||||
if (this.stopped) throw new Error("ProjectEngineManager is stopped");
|
||||
|
||||
runtimeLog.log(`Pausing project ${projectId}`);
|
||||
|
||||
// Update CentralCore status
|
||||
await this.centralCore.updateProject(projectId, { status: "paused" });
|
||||
await this.centralCore.updateProjectHealth(projectId, { status: "paused" });
|
||||
|
||||
// Stop the engine if running
|
||||
const engine = this.engines.get(projectId);
|
||||
if (engine) {
|
||||
await engine.stop();
|
||||
this.engines.delete(projectId);
|
||||
runtimeLog.log(`Stopped engine for paused project ${projectId}`);
|
||||
}
|
||||
|
||||
// Remove from starting set to prevent a stalled start from completing
|
||||
this.starting.delete(projectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume a paused project: update its status in CentralCore and start its engine.
|
||||
*/
|
||||
async resumeProject(projectId: string): Promise<void> {
|
||||
if (this.stopped) throw new Error("ProjectEngineManager is stopped");
|
||||
|
||||
runtimeLog.log(`Resuming project ${projectId}`);
|
||||
|
||||
// Update CentralCore status
|
||||
await this.centralCore.updateProject(projectId, { status: "active" });
|
||||
await this.centralCore.updateProjectHealth(projectId, { status: "active" });
|
||||
|
||||
// Start the engine
|
||||
await this.ensureEngine(projectId);
|
||||
}
|
||||
|
||||
// ── Lifecycle ──
|
||||
|
||||
/**
|
||||
@@ -123,6 +166,12 @@ export class ProjectEngineManager {
|
||||
): Promise<ProjectEngine> {
|
||||
if (this.stopped) throw new Error("ProjectEngineManager is stopped");
|
||||
|
||||
// Check if the project is paused before starting
|
||||
const project = await this.centralCore.getProject(projectId);
|
||||
if (project && (project.status as string) === "paused") {
|
||||
throw new Error(`Project ${projectId} is paused`);
|
||||
}
|
||||
|
||||
const existing = this.engines.get(projectId);
|
||||
if (existing) return existing;
|
||||
|
||||
@@ -283,8 +332,11 @@ export class ProjectEngineManager {
|
||||
const projects = await this.centralCore.listProjects();
|
||||
if (projects.length === 0) return;
|
||||
|
||||
// Filter out paused projects — they should not have engines started
|
||||
const activeProjects = projects.filter((p) => (p.status as string) !== "paused");
|
||||
|
||||
// Find projects that don't have running or pending engines
|
||||
const missing = projects.filter((p) => !this.has(p.id));
|
||||
const missing = activeProjects.filter((p) => !this.has(p.id));
|
||||
if (missing.length === 0) return;
|
||||
|
||||
runtimeLog.log(
|
||||
@@ -318,6 +370,11 @@ export class ProjectEngineManager {
|
||||
throw new Error(`Project ${projectId} not found in CentralCore`);
|
||||
}
|
||||
|
||||
// Prevent starting engines for paused projects
|
||||
if ((project.status as string) === "paused") {
|
||||
throw new Error(`Project ${projectId} is paused`);
|
||||
}
|
||||
|
||||
const runtimeConfig = this.buildRuntimeConfig(project);
|
||||
const engineOptions = this.buildEngineOptions(project, overrides);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user