Fix Compound Engineering plugin install failing from Settings

Installing the Compound Engineering plugin from Settings → Built-in
Plugins failed with "Plugin manifest not found": the dashboard's
bundled-plugin fallback set (BUNDLED_PLUGIN_IDS in routes.ts) was
missing fusion-plugin-compound-engineering, so when the relative
./plugins/... path missed the server cwd, the bundled lookup was never
attempted. fusion-plugin-cli-printing-press had the same gap.

- Add both ids to the dashboard's BUNDLED_PLUGIN_IDS fallback set.
- Stage fusion-plugin-compound-engineering into dist/plugins via
  bundlePluginEntry in the CLI tsup config so packaged installs can
  resolve the bundled copy (every other bundled plugin already had a
  staging block).
- Add route tests that actually exercise the bundled fallback (the
  existing ones let cwd resolution succeed, so the fallback path was
  untested).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-06-04 12:31:08 -07:00
parent ff0750cd15
commit 4c3a226050
4 changed files with 79 additions and 0 deletions

View File

@@ -6,3 +6,4 @@ Fix the workflow graph editor opening invisibly and bundle the Compound Engineer
- The "Graph editor" button now actually shows the editor: its overlay was rendered without the `open` class, leaving it `display: none`, so opening it looked like the workflow steps view was just dismissed.
- `fusion-plugin-compound-engineering` and `fusion-plugin-roadmap` are now listed in the dashboard's built-in plugins, so they appear under Settings → Built-in Plugins (they were implemented and registered but missing from the list).
- Installing Compound Engineering (and CLI Printing Press) from Settings → Built-in Plugins no longer fails with "Plugin manifest not found": both ids are now in the dashboard's bundled-plugin fallback set, and the Compound Engineering plugin is staged into `dist/plugins/` so packaged installs can resolve it.

View File

@@ -41,6 +41,8 @@ const reportsPluginSrc = join(__dirname, "..", "..", "plugins", "fusion-plugin-r
const reportsPluginDest = join(__dirname, "dist", "plugins", "fusion-plugin-reports");
const cliPrintingPressPluginSrc = join(__dirname, "..", "..", "plugins", "fusion-plugin-cli-printing-press");
const cliPrintingPressPluginDest = join(__dirname, "dist", "plugins", "fusion-plugin-cli-printing-press");
const compoundEngineeringPluginSrc = join(__dirname, "..", "..", "plugins", "fusion-plugin-compound-engineering");
const compoundEngineeringPluginDest = join(__dirname, "dist", "plugins", "fusion-plugin-compound-engineering");
const dashboardClientStub = `<!doctype html>
<html lang="en">
<head>
@@ -241,6 +243,12 @@ const cliBuildConfig = {
destDir: roadmapPluginDest,
});
await bundlePluginEntry({
pluginId: "fusion-plugin-compound-engineering",
srcDir: compoundEngineeringPluginSrc,
destDir: compoundEngineeringPluginDest,
});
if (existsSync(reportsPluginDest)) {
rmSync(reportsPluginDest, { recursive: true, force: true });
}

View File

@@ -549,6 +549,74 @@ describe("POST /api/plugins mode:install — bundled plugin path fallback", () =
);
});
it("installs bundled compound engineering plugin when relative path misses cwd", async () => {
const bundledManifest = {
...VALID_MANIFEST,
id: "fusion-plugin-compound-engineering",
name: "Compound Engineering",
};
// Only the staged bundled copy under dist/plugins exists — the
// cwd-relative path must miss so the bundled fallback is exercised.
mockExistsSync.mockImplementation((p: string) => p.includes("dist/plugins/fusion-plugin-compound-engineering/manifest.json"));
mockAccess.mockImplementation((p: string) => {
if (p.includes("dist/plugins/fusion-plugin-compound-engineering")) return Promise.resolve();
return Promise.reject(new Error("not found"));
});
mockReadFile.mockResolvedValue(JSON.stringify(bundledManifest));
(pluginStore.registerPlugin as ReturnType<typeof vi.fn>).mockResolvedValue({
...INSTALLED_PLUGIN,
id: "fusion-plugin-compound-engineering",
name: "Compound Engineering",
});
const res = await REQUEST(buildApp(), "POST", "/api/plugins", {
mode: "install",
path: "./plugins/fusion-plugin-compound-engineering",
});
expect(res.status).toBe(201);
expect(pluginStore.registerPlugin).toHaveBeenCalledWith(
expect.objectContaining({
manifest: expect.objectContaining({ id: "fusion-plugin-compound-engineering" }),
path: expect.stringContaining("fusion-plugin-compound-engineering"),
}),
);
});
it("installs bundled cli printing press plugin when relative path misses cwd", async () => {
const bundledManifest = {
...VALID_MANIFEST,
id: "fusion-plugin-cli-printing-press",
name: "CLI Printing Press",
};
// Only the staged bundled copy under dist/plugins exists — the
// cwd-relative path must miss so the bundled fallback is exercised.
mockExistsSync.mockImplementation((p: string) => p.includes("dist/plugins/fusion-plugin-cli-printing-press/manifest.json"));
mockAccess.mockImplementation((p: string) => {
if (p.includes("dist/plugins/fusion-plugin-cli-printing-press")) return Promise.resolve();
return Promise.reject(new Error("not found"));
});
mockReadFile.mockResolvedValue(JSON.stringify(bundledManifest));
(pluginStore.registerPlugin as ReturnType<typeof vi.fn>).mockResolvedValue({
...INSTALLED_PLUGIN,
id: "fusion-plugin-cli-printing-press",
name: "CLI Printing Press",
});
const res = await REQUEST(buildApp(), "POST", "/api/plugins", {
mode: "install",
path: "./plugins/fusion-plugin-cli-printing-press",
});
expect(res.status).toBe(201);
expect(pluginStore.registerPlugin).toHaveBeenCalledWith(
expect.objectContaining({
manifest: expect.objectContaining({ id: "fusion-plugin-cli-printing-press" }),
path: expect.stringContaining("fusion-plugin-cli-printing-press"),
}),
);
});
it("returns 404 with helpful message when local and bundled paths are unresolved", async () => {
mockExistsSync.mockReturnValue(false);
mockAccess.mockRejectedValue(new Error("not found"));

View File

@@ -97,6 +97,8 @@ const BUNDLED_PLUGIN_IDS = new Set([
"fusion-plugin-openclaw-runtime",
"fusion-plugin-paperclip-runtime",
"fusion-plugin-cursor-runtime",
"fusion-plugin-cli-printing-press",
"fusion-plugin-compound-engineering",
]);
function extractBundledPluginId(pathInput: string): string | null {