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:
Fusion
2026-04-15 19:53:22 -07:00
committed by gsxdsm
parent 87b40c9f3f
commit f83d3483a6
7 changed files with 445 additions and 20 deletions

View File

@@ -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);