perf(dashboard): speed up startup and eliminate API request storms
Multiple coordinated fixes for the perceived "dashboard takes forever to load" complaint. Per-page-load HTTP requests drop from ~177 to ~101 and duplicate per-project InProcessRuntime creation is eliminated. - engine: shouldUseHybridExecutor no longer auto-enables for local-only multi-project setups (set FUSION_HYBRID_EXECUTOR=1 to force). The duplicate-runtime path was running self-healing twice per project and contending on the same SQLite file. ProjectEngineManager already handles N local projects with one InProcessRuntime each. - dashboard cli: parallelized independent store inits, started CentralCore.init early in background, ran plugin loading concurrently with extension resolution. Sequenced SQLite store inits to avoid a TOCTOU race in addColumnIfMissing migrations across TaskStore / AutomationStore / PluginStore / AgentStore (all open the same .fusion/fusion.db). Restored try/catch around HybridExecutor.initialize and engineManager.ensureEngine so a paused or broken cwd project no longer aborts dashboard startup. - dashboard client: added in-flight request dedupe wrapped around the top API offenders. /api/plugins/ui-slots drops from 17x to 1x per load. dedupe.forceFresh redirects ALL in-flight waiters to receive the fresh post-mutation response, not just the forcing caller. Generation counters in useAgents and AgentListModal protect against slow polls overwriting fresh state. - dashboard SSE: agent event handler now debounces 250ms with a trailing-edge guard so multi-agent activity bursts coalesce to at most 2 refetches per burst. - dashboard route: PATCH /api/projects/:id with isolationMode change returns 503 with actionable guidance when HybridExecutor is unavailable, instead of silently persisting a config the live runtime won't honor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,20 +48,22 @@ describe("shouldUseHybridExecutor", () => {
|
||||
expect(decision).toEqual({ enabled: true, reason: "multi-node" });
|
||||
});
|
||||
|
||||
it("enables for multi-project active/initializing", async () => {
|
||||
it("does NOT enable for local-only multi-project (ProjectEngineManager handles it)", async () => {
|
||||
delete process.env.FUSION_HYBRID_EXECUTOR;
|
||||
const decision = await shouldUseHybridExecutor(
|
||||
createMockCentralCore({
|
||||
listProjects: async () => [{ status: "active" }, { status: "initializing" }],
|
||||
}),
|
||||
);
|
||||
expect(decision).toEqual({ enabled: true, reason: "multi-project" });
|
||||
// HybridExecutor's value is cross-node routing. Local-only N-project
|
||||
// setups don't need it — running it duplicates InProcessRuntime creation.
|
||||
expect(decision).toEqual({ enabled: false, reason: "single-node-local-only" });
|
||||
});
|
||||
|
||||
it("disables for single local project", async () => {
|
||||
it("disables for local-only single-node setup", async () => {
|
||||
delete process.env.FUSION_HYBRID_EXECUTOR;
|
||||
const decision = await shouldUseHybridExecutor(createMockCentralCore());
|
||||
expect(decision).toEqual({ enabled: false, reason: "single-project-local-only" });
|
||||
expect(decision).toEqual({ enabled: false, reason: "single-node-local-only" });
|
||||
});
|
||||
|
||||
it("disables when central APIs throw", async () => {
|
||||
|
||||
@@ -110,7 +110,7 @@ describe("HybridExecutor multi-node routing", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("enables multi-project on a single node", async () => {
|
||||
it("does NOT auto-enable for local-only multi-project (set FUSION_HYBRID_EXECUTOR=1 to force)", async () => {
|
||||
const central = new CentralCore(tempDir);
|
||||
await central.init();
|
||||
try {
|
||||
@@ -124,8 +124,12 @@ describe("HybridExecutor multi-node routing", () => {
|
||||
await central.updateProject(projectA.id, { status: "active" });
|
||||
await central.updateProject(projectB.id, { status: "initializing" });
|
||||
|
||||
await expect(shouldUseHybridExecutor(central)).resolves.toEqual({ enabled: true, reason: "multi-project" });
|
||||
// Gate intentionally OFF: ProjectEngineManager handles local
|
||||
// multi-project; running HybridExecutor here duplicates InProcessRuntime
|
||||
// creation and adds ~7s to cold start.
|
||||
await expect(shouldUseHybridExecutor(central)).resolves.toEqual({ enabled: false, reason: "single-node-local-only" });
|
||||
|
||||
// Force-enabled path still works when explicitly initialized.
|
||||
const executor = new HybridExecutor(central);
|
||||
await executor.initialize();
|
||||
expect(new Set(executor.getProjectIds())).toEqual(new Set([projectA.id, projectB.id]));
|
||||
|
||||
@@ -65,11 +65,11 @@ describe("hybrid executor startup integration", () => {
|
||||
else process.env.FUSION_HYBRID_EXECUTOR = originalEnv;
|
||||
});
|
||||
|
||||
it("disables gate for single local project", async () => {
|
||||
it("disables gate for local-only single-node setup", async () => {
|
||||
const central = createCentralCore();
|
||||
await expect(shouldUseHybridExecutor(central)).resolves.toEqual({
|
||||
enabled: false,
|
||||
reason: "single-project-local-only",
|
||||
reason: "single-node-local-only",
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -29,13 +29,13 @@ export async function shouldUseHybridExecutor(centralCore: CentralCore): Promise
|
||||
return { enabled: true, reason: "multi-node" };
|
||||
}
|
||||
|
||||
const projects = await centralCore.listProjects();
|
||||
const liveProjects = projects.filter((project) => project.status === "active" || project.status === "initializing");
|
||||
if (liveProjects.length > 1) {
|
||||
return { enabled: true, reason: "multi-project" };
|
||||
}
|
||||
|
||||
return { enabled: false, reason: "single-project-local-only" };
|
||||
// Local-only single-node: HybridExecutor's value is cross-node routing.
|
||||
// ProjectEngineManager already handles N local projects with one
|
||||
// InProcessRuntime per project; running HybridExecutor in parallel just
|
||||
// creates a second InProcessRuntime per project, duplicating self-healing
|
||||
// recovery and adding ~7s to cold start. Skip until a remote node is
|
||||
// registered (set FUSION_HYBRID_EXECUTOR=1 to force-enable).
|
||||
return { enabled: false, reason: "single-node-local-only" };
|
||||
} catch {
|
||||
return { enabled: false, reason: "central-unavailable" };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user