fix(FN-9093): eagerly install bundled Cursor runtime plugin at host boot
The Cursor provider card's Enable action only flips useCursorCli in
settings and never registers fusion-plugin-cursor-runtime, so
getRuntimeById("cursor") missed and every cursor-cli selection hit the
runtime-routed fail-fast error even with an authenticated cursor-agent.
Mirror the FN-7761 Grok eager bootstrap in serve, dashboard, and daemon,
guarded by the same source-scan regression test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
7
.changeset/fn-9093-cursor-runtime-eager-install.md
Normal file
7
.changeset/fn-9093-cursor-runtime-eager-install.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix Cursor CLI models failing with "install and enable the Cursor runtime plugin" after enabling the provider.
|
||||
category: fix
|
||||
dev: serve/dashboard/daemon now eagerly run `ensureBundledCursorRuntimePluginInstalled` at boot, mirroring the FN-7761 Grok bootstrap, so `getRuntimeById("cursor")` resolves for cursor-cli selections.
|
||||
@@ -30,6 +30,29 @@ describe("Grok CLI runtime packaged bootstrap", () => {
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* FNXC:CursorCli 2026-08-16-04:05:
|
||||
* FN-9093 regression guard: the Cursor provider card's Enable action only flips `useCursorCli` in settings,
|
||||
* so without this eager bootstrap nothing ever registers fusion-plugin-cursor-runtime and every cursor-cli
|
||||
* selection fails the runtime-routed fail-fast with "install and enable the Cursor runtime plugin".
|
||||
* Mirrors the FN-7761 Grok guard above: the helper must run before loadAllPlugins() in each long-lived host.
|
||||
*/
|
||||
describe("Cursor CLI runtime packaged bootstrap", () => {
|
||||
for (const command of ["serve", "daemon", "dashboard"] as const) {
|
||||
it(`${command} eagerly ensures the bundled Cursor runtime before loading enabled plugins`, () => {
|
||||
const source = readCommand(command);
|
||||
const importIndex = source.indexOf("ensureBundledCursorRuntimePluginInstalled");
|
||||
const ensureIndex = source.indexOf("ensureBundledCursorRuntimePluginInstalled(pluginStore, pluginLoader)");
|
||||
const loadIndex = source.indexOf("pluginLoader.loadAllPlugins()");
|
||||
|
||||
expect(importIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(ensureIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(loadIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(ensureIndex).toBeLessThan(loadIndex);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
FNXC:GrokCliRouting 2026-07-15-10:17:
|
||||
Hosts must pass a real engine PluginRunner (getRuntimeById) into createServer — never the bare PluginLoader. Engine-mode merge omits onMerge so server.ts derives engine.onMerge. UI-only / bare CLI leave pluginRunner undefined (dual-remediation).
|
||||
|
||||
@@ -80,7 +80,7 @@ import { wrapAuthStorageWithApiKeyProviders } from "./provider-auth.js";
|
||||
import { getPackageManagerAgentDir } from "./auth-paths.js";
|
||||
import { resolveProject } from "../project-context.js";
|
||||
import { startMigrationHoldingServer } from "./migration-holding-server.js";
|
||||
import { ensureBundledDependencyGraphPluginInstalled, ensureBundledGrokRuntimePluginInstalled } from "../plugins/bundled-plugin-install.js";
|
||||
import { ensureBundledCursorRuntimePluginInstalled, ensureBundledDependencyGraphPluginInstalled, ensureBundledGrokRuntimePluginInstalled } from "../plugins/bundled-plugin-install.js";
|
||||
import { handleOpencodeGoApiKeySaved, syncStartupModels } from "./startup-model-sync.js";
|
||||
import { registerCustomProviders, reregisterCustomProviders } from "./custom-provider-registry.js";
|
||||
import { ensureCwdProjectRegistered } from "./ensure-project-registered.js";
|
||||
@@ -572,6 +572,24 @@ export async function runDaemon(opts: DaemonOptions = {}) {
|
||||
console.warn(`[plugins] Failed to auto-install bundled Grok CLI runtime plugin: ${err instanceof Error ? err.message : err}`);
|
||||
}
|
||||
|
||||
/*
|
||||
* FNXC:CursorCli 2026-08-16-04:05:
|
||||
* FN-9093: daemon executors/reviewers/mergers resolve `cursor-cli` through the fail-fast runtime route,
|
||||
* but nothing registered fusion-plugin-cursor-runtime (the provider Enable action only sets `useCursorCli`),
|
||||
* so Cursor sessions failed with the missing-runtime error. Mirror the FN-7761 Grok eager bootstrap so the
|
||||
* Cursor runtime is loaded before agent sessions are created.
|
||||
*/
|
||||
try {
|
||||
const installStatus = await ensureBundledCursorRuntimePluginInstalled(pluginStore, pluginLoader);
|
||||
if (installStatus === "installed") {
|
||||
console.log("[plugins] Installed bundled Cursor CLI runtime plugin");
|
||||
} else if (installStatus === "missing-bundle") {
|
||||
console.warn("[plugins] Bundled Cursor CLI runtime plugin was not found in this build");
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`[plugins] Failed to auto-install bundled Cursor CLI runtime plugin: ${err instanceof Error ? err.message : err}`);
|
||||
}
|
||||
|
||||
// Auto-load all enabled plugins so runtime UI (NewAgentDialog, AgentDetailView)
|
||||
// can discover installed runtimes like Hermes and OpenClaw.
|
||||
try {
|
||||
|
||||
@@ -140,7 +140,7 @@ import {
|
||||
} from "./llama-cpp-extension.js";
|
||||
import { getCachedUpdateStatus, isUpdateCheckEnabled } from "../update-cache.js";
|
||||
import { resolveSelfExtension } from "./self-extension.js";
|
||||
import { ensureBundledDependencyGraphPluginInstalled, ensureBundledGrokRuntimePluginInstalled, ensureBundledPluginInstalled, isBundledPluginId } from "../plugins/bundled-plugin-install.js";
|
||||
import { ensureBundledCursorRuntimePluginInstalled, ensureBundledDependencyGraphPluginInstalled, ensureBundledGrokRuntimePluginInstalled, ensureBundledPluginInstalled, isBundledPluginId } from "../plugins/bundled-plugin-install.js";
|
||||
import { registerCustomProviders, reregisterCustomProviders } from "./custom-provider-registry.js";
|
||||
import { handleOpencodeGoApiKeySaved, syncStartupModels } from "./startup-model-sync.js";
|
||||
import { DashboardTUI, DashboardLogSink, isTTYAvailable, type SystemInfo, type GitStatus, type GitCommit, type GitCommitDetail, type GitBranch, type GitWorktree, type FileEntry, type FileReadResult, type TaskStep as TUITaskStep, type TaskLogEntry as TUITaskLogEntry, type TaskDetailData, type TaskEvent } from "./dashboard-tui/index.js";
|
||||
@@ -1572,6 +1572,27 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* FNXC:CursorCli 2026-08-16-04:05:
|
||||
* FN-9093: the Cursor provider card's Enable action only flips `useCursorCli` in settings and never
|
||||
* registers fusion-plugin-cursor-runtime, so `getRuntimeById("cursor")` missed and every cursor-cli
|
||||
* selection hit the fail-fast "install and enable the Cursor runtime plugin" error after enabling.
|
||||
* Mirror the FN-7761 Grok eager bootstrap so the Cursor runtime is loadable before chat sends.
|
||||
*/
|
||||
try {
|
||||
const installStatus = await ensureBundledCursorRuntimePluginInstalled(pluginStore, pluginLoader);
|
||||
if (installStatus === "installed") {
|
||||
logSink.log("Installed bundled Cursor CLI runtime plugin", "plugins");
|
||||
} else if (installStatus === "missing-bundle") {
|
||||
logSink.log("Bundled Cursor CLI runtime plugin was not found in this build", "plugins");
|
||||
}
|
||||
} catch (err) {
|
||||
logSink.log(
|
||||
`Failed to auto-install bundled Cursor CLI runtime plugin: ${err instanceof Error ? err.message : err}`,
|
||||
"plugins",
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const { loaded, errors } = await pluginLoader.loadAllPlugins();
|
||||
logSink.log(`Loaded ${loaded} plugins (${errors} errors)`, "plugins");
|
||||
|
||||
@@ -80,7 +80,7 @@ import {
|
||||
import { resolveSelfExtension } from "./self-extension.js";
|
||||
import { registerCustomProviders, reregisterCustomProviders } from "./custom-provider-registry.js";
|
||||
import { handleOpencodeGoApiKeySaved, syncStartupModels } from "./startup-model-sync.js";
|
||||
import { ensureBundledDependencyGraphPluginInstalled, ensureBundledGrokRuntimePluginInstalled, ensureBundledPluginInstalled, isBundledPluginId } from "../plugins/bundled-plugin-install.js";
|
||||
import { ensureBundledCursorRuntimePluginInstalled, ensureBundledDependencyGraphPluginInstalled, ensureBundledGrokRuntimePluginInstalled, ensureBundledPluginInstalled, isBundledPluginId } from "../plugins/bundled-plugin-install.js";
|
||||
import { ensureCwdProjectRegistered } from "./ensure-project-registered.js";
|
||||
import { phaseTime } from "../startup-phase.js";
|
||||
|
||||
@@ -657,6 +657,24 @@ export async function runServe(
|
||||
console.warn(`[plugins] Failed to auto-install bundled Grok CLI runtime plugin: ${err instanceof Error ? err.message : err}`);
|
||||
}
|
||||
|
||||
/*
|
||||
* FNXC:CursorCli 2026-08-16-04:05:
|
||||
* FN-9093: `cursor-cli` is runtime-routed with a fail-fast missing-runtime error, but the provider card's
|
||||
* Enable action only flips `useCursorCli` in settings — nothing registered fusion-plugin-cursor-runtime,
|
||||
* so every Cursor selection failed with "install and enable the Cursor runtime plugin" even after enabling.
|
||||
* Mirror the FN-7761 Grok eager bootstrap so the Cursor runtime is discoverable before any session lane starts.
|
||||
*/
|
||||
try {
|
||||
const installStatus = await ensureBundledCursorRuntimePluginInstalled(pluginStore, pluginLoader);
|
||||
if (installStatus === "installed") {
|
||||
console.log("[plugins] Installed bundled Cursor CLI runtime plugin");
|
||||
} else if (installStatus === "missing-bundle") {
|
||||
console.warn("[plugins] Bundled Cursor CLI runtime plugin was not found in this build");
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`[plugins] Failed to auto-install bundled Cursor CLI runtime plugin: ${err instanceof Error ? err.message : err}`);
|
||||
}
|
||||
|
||||
// Lazy-install hook for bundled runtime plugins (Hermes/OpenClaw/Paperclip/Grok).
|
||||
const ensureBundledPluginInstalledCallback = async (pluginId: string): Promise<boolean> => {
|
||||
if (!isBundledPluginId(pluginId)) {
|
||||
|
||||
Reference in New Issue
Block a user