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:
|
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).
|
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 { getPackageManagerAgentDir } from "./auth-paths.js";
|
||||||
import { resolveProject } from "../project-context.js";
|
import { resolveProject } from "../project-context.js";
|
||||||
import { startMigrationHoldingServer } from "./migration-holding-server.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 { handleOpencodeGoApiKeySaved, syncStartupModels } from "./startup-model-sync.js";
|
||||||
import { registerCustomProviders, reregisterCustomProviders } from "./custom-provider-registry.js";
|
import { registerCustomProviders, reregisterCustomProviders } from "./custom-provider-registry.js";
|
||||||
import { ensureCwdProjectRegistered } from "./ensure-project-registered.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}`);
|
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)
|
// Auto-load all enabled plugins so runtime UI (NewAgentDialog, AgentDetailView)
|
||||||
// can discover installed runtimes like Hermes and OpenClaw.
|
// can discover installed runtimes like Hermes and OpenClaw.
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ import {
|
|||||||
} from "./llama-cpp-extension.js";
|
} from "./llama-cpp-extension.js";
|
||||||
import { getCachedUpdateStatus, isUpdateCheckEnabled } from "../update-cache.js";
|
import { getCachedUpdateStatus, isUpdateCheckEnabled } from "../update-cache.js";
|
||||||
import { resolveSelfExtension } from "./self-extension.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 { registerCustomProviders, reregisterCustomProviders } from "./custom-provider-registry.js";
|
||||||
import { handleOpencodeGoApiKeySaved, syncStartupModels } from "./startup-model-sync.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";
|
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 {
|
try {
|
||||||
const { loaded, errors } = await pluginLoader.loadAllPlugins();
|
const { loaded, errors } = await pluginLoader.loadAllPlugins();
|
||||||
logSink.log(`Loaded ${loaded} plugins (${errors} errors)`, "plugins");
|
logSink.log(`Loaded ${loaded} plugins (${errors} errors)`, "plugins");
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ import {
|
|||||||
import { resolveSelfExtension } from "./self-extension.js";
|
import { resolveSelfExtension } from "./self-extension.js";
|
||||||
import { registerCustomProviders, reregisterCustomProviders } from "./custom-provider-registry.js";
|
import { registerCustomProviders, reregisterCustomProviders } from "./custom-provider-registry.js";
|
||||||
import { handleOpencodeGoApiKeySaved, syncStartupModels } from "./startup-model-sync.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 { ensureCwdProjectRegistered } from "./ensure-project-registered.js";
|
||||||
import { phaseTime } from "../startup-phase.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}`);
|
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).
|
// Lazy-install hook for bundled runtime plugins (Hermes/OpenClaw/Paperclip/Grok).
|
||||||
const ensureBundledPluginInstalledCallback = async (pluginId: string): Promise<boolean> => {
|
const ensureBundledPluginInstalledCallback = async (pluginId: string): Promise<boolean> => {
|
||||||
if (!isBundledPluginId(pluginId)) {
|
if (!isBundledPluginId(pluginId)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user