feat(FN-3396): bundle Cursor CLI as a plugin provider with dashboard auth w

Merges FN-3396's full Cursor CLI provider integration (Steps 1–4): defines a CLI-backed provider contract, adds the `fusion-plugin-cursor-runtime` plugin package with process management and runtime probes, wires dashboard auth flows and UI (ProviderCard, onboarding modal, settings), and bundles the

Fusion-Task-Id: FN-3396
This commit is contained in:
Fusion
2026-05-07 04:10:15 -07:00
committed by gsxdsm
parent f3c99eb4fa
commit 3735e0565a
50 changed files with 1292 additions and 3 deletions

View File

@@ -36,6 +36,7 @@ describe("PluginRunner", () => {
getPluginUiSlots: ReturnType<typeof vi.fn>;
getPluginUiContributions: ReturnType<typeof vi.fn>;
getPluginRuntimes: ReturnType<typeof vi.fn>;
getCliProviderContributions: ReturnType<typeof vi.fn>;
getPluginSkills: ReturnType<typeof vi.fn>;
getPluginWorkflowSteps: ReturnType<typeof vi.fn>;
getPluginWorkflowStepTemplates: ReturnType<typeof vi.fn>;
@@ -100,6 +101,7 @@ describe("PluginRunner", () => {
getPluginUiSlots: vi.fn().mockReturnValue([]),
getPluginUiContributions: vi.fn().mockReturnValue([]),
getPluginRuntimes: vi.fn().mockReturnValue([]),
getCliProviderContributions: vi.fn().mockReturnValue([]),
getPluginSkills: vi.fn().mockReturnValue([]),
getPluginWorkflowSteps: vi.fn().mockReturnValue([]),
getPluginWorkflowStepTemplates: vi.fn().mockReturnValue([]),
@@ -832,6 +834,28 @@ describe("PluginRunner", () => {
});
describe("new plugin contribution accessors", () => {
it("getCliProviderContributions returns cached CLI-provider contributions", async () => {
const contributions = [
{
pluginId: "cursor-plugin",
contribution: {
providerId: "cursor-cli",
displayName: "Cursor CLI",
binaryName: "cursor-agent",
providerType: "cli",
statusRoute: "/providers/cursor-cli/status",
authRoute: "/auth/cursor-cli",
},
},
];
mockPluginLoader.getCliProviderContributions.mockReturnValue(contributions);
await pluginRunner.init();
const first = pluginRunner.getCliProviderContributions();
const second = pluginRunner.getCliProviderContributions();
expect(first).toEqual(contributions);
expect(second).toBe(first);
});
it("getPluginSkills returns empty array initially", async () => {
await pluginRunner.init();
expect(pluginRunner.getPluginSkills()).toEqual([]);
@@ -887,6 +911,7 @@ describe("PluginRunner", () => {
it("invalidates new contribution caches on state change and loader events", async () => {
await pluginRunner.init();
pluginRunner.getCliProviderContributions();
pluginRunner.getPluginSkills();
pluginRunner.getPluginWorkflowSteps();
pluginRunner.getPluginWorkflowStepTemplates();
@@ -895,6 +920,7 @@ describe("PluginRunner", () => {
const stateChanged = mockPluginStore.on.mock.calls.find((call) => call[0] === "plugin:stateChanged")?.[1];
stateChanged?.();
pluginRunner.getCliProviderContributions();
pluginRunner.getPluginSkills();
pluginRunner.getPluginWorkflowSteps();
pluginRunner.getPluginWorkflowStepTemplates();
@@ -903,12 +929,14 @@ describe("PluginRunner", () => {
const loaded = mockPluginLoader.on.mock.calls.find((call) => call[0] === "plugin:loaded")?.[1];
loaded?.({ pluginId: "test-plugin" });
pluginRunner.getCliProviderContributions();
pluginRunner.getPluginSkills();
pluginRunner.getPluginWorkflowSteps();
pluginRunner.getPluginWorkflowStepTemplates();
pluginRunner.getPluginPromptContributions();
pluginRunner.getPluginSetupInfo();
expect(mockPluginLoader.getCliProviderContributions).toHaveBeenCalledTimes(3);
expect(mockPluginLoader.getPluginSkills).toHaveBeenCalledTimes(3);
expect(mockPluginLoader.getPluginWorkflowSteps).toHaveBeenCalledTimes(3);
expect(mockPluginLoader.getPluginWorkflowStepTemplates).toHaveBeenCalledTimes(3);

View File

@@ -17,6 +17,7 @@ import type {
PluginUiSlotDefinition,
PluginUiContributionDefinition,
PluginRuntimeRegistration,
CliProviderContribution,
PluginContext,
PluginSkillContribution,
PluginWorkflowStepContribution,
@@ -83,6 +84,11 @@ interface CachedRuntimes {
version: number;
}
interface CachedCliProviderContributions {
contributions: Array<{ pluginId: string; contribution: CliProviderContribution }>;
version: number;
}
interface CachedSkills {
skills: Array<{ pluginId: string; skill: PluginSkillContribution }>;
version: number;
@@ -121,6 +127,7 @@ export class PluginRunner {
private cachedUiSlots: CachedUiSlots | null = null;
private cachedUiContributions: CachedUiContributions | null = null;
private cachedRuntimes: CachedRuntimes | null = null;
private cachedCliProviderContributions: CachedCliProviderContributions | null = null;
private cachedSkills: CachedSkills | null = null;
private cachedWorkflowSteps: CachedWorkflowSteps | null = null;
private cachedWorkflowStepTemplates: CachedWorkflowStepTemplates | null = null;
@@ -131,6 +138,7 @@ export class PluginRunner {
private uiSlotsCacheVersion = 0;
private uiContributionsCacheVersion = 0;
private runtimesCacheVersion = 0;
private cliProviderContributionsCacheVersion = 0;
private skillsCacheVersion = 0;
private workflowStepsCacheVersion = 0;
private workflowStepTemplatesCacheVersion = 0;
@@ -207,6 +215,7 @@ export class PluginRunner {
this.invalidateUiSlotsCache();
this.invalidateUiContributionsCache();
this.invalidateRuntimesCache();
this.invalidateCliProviderContributionsCache();
this.invalidateSkillsCache();
this.invalidateWorkflowStepsCache();
this.invalidateWorkflowStepTemplatesCache();
@@ -318,6 +327,16 @@ export class PluginRunner {
return this.cachedRuntimes.runtimes;
}
getCliProviderContributions(): Array<{ pluginId: string; contribution: CliProviderContribution }> {
if (!this.cachedCliProviderContributions || this.cachedCliProviderContributions.version !== this.cliProviderContributionsCacheVersion) {
this.cachedCliProviderContributions = {
contributions: this.options.pluginLoader.getCliProviderContributions(),
version: this.cliProviderContributionsCacheVersion,
};
}
return this.cachedCliProviderContributions.contributions;
}
getPluginSkills(): Array<{ pluginId: string; skill: PluginSkillContribution }> {
if (!this.cachedSkills || this.cachedSkills.version !== this.skillsCacheVersion) {
this.cachedSkills = {
@@ -475,6 +494,7 @@ export class PluginRunner {
this.invalidateUiSlotsCache();
this.invalidateUiContributionsCache();
this.invalidateRuntimesCache();
this.invalidateCliProviderContributionsCache();
this.invalidateSkillsCache();
this.invalidateWorkflowStepsCache();
this.invalidateWorkflowStepTemplatesCache();
@@ -495,6 +515,7 @@ export class PluginRunner {
this.invalidateUiSlotsCache();
this.invalidateUiContributionsCache();
this.invalidateRuntimesCache();
this.invalidateCliProviderContributionsCache();
this.invalidateSkillsCache();
this.invalidateWorkflowStepsCache();
this.invalidateWorkflowStepTemplatesCache();
@@ -520,6 +541,7 @@ export class PluginRunner {
this.invalidateUiSlotsCache();
this.invalidateUiContributionsCache();
this.invalidateRuntimesCache();
this.invalidateCliProviderContributionsCache();
this.invalidateSkillsCache();
this.invalidateWorkflowStepsCache();
this.invalidateWorkflowStepTemplatesCache();
@@ -545,6 +567,7 @@ export class PluginRunner {
this.invalidateUiSlotsCache();
this.invalidateUiContributionsCache();
this.invalidateRuntimesCache();
this.invalidateCliProviderContributionsCache();
this.invalidateSkillsCache();
this.invalidateWorkflowStepsCache();
this.invalidateWorkflowStepTemplatesCache();
@@ -569,6 +592,7 @@ export class PluginRunner {
this.invalidateUiSlotsCache();
this.invalidateUiContributionsCache();
this.invalidateRuntimesCache();
this.invalidateCliProviderContributionsCache();
this.invalidateSkillsCache();
this.invalidateWorkflowStepsCache();
this.invalidateWorkflowStepTemplatesCache();
@@ -585,6 +609,7 @@ export class PluginRunner {
this.invalidateUiSlotsCache();
this.invalidateUiContributionsCache();
this.invalidateRuntimesCache();
this.invalidateCliProviderContributionsCache();
this.invalidateSkillsCache();
this.invalidateWorkflowStepsCache();
this.invalidateWorkflowStepTemplatesCache();
@@ -601,6 +626,7 @@ export class PluginRunner {
this.invalidateUiSlotsCache();
this.invalidateUiContributionsCache();
this.invalidateRuntimesCache();
this.invalidateCliProviderContributionsCache();
this.invalidateSkillsCache();
this.invalidateWorkflowStepsCache();
this.invalidateWorkflowStepTemplatesCache();
@@ -617,6 +643,7 @@ export class PluginRunner {
this.invalidateUiSlotsCache();
this.invalidateUiContributionsCache();
this.invalidateRuntimesCache();
this.invalidateCliProviderContributionsCache();
this.invalidateSkillsCache();
this.invalidateWorkflowStepsCache();
this.invalidateWorkflowStepTemplatesCache();
@@ -633,6 +660,7 @@ export class PluginRunner {
this.invalidateUiSlotsCache();
this.invalidateUiContributionsCache();
this.invalidateRuntimesCache();
this.invalidateCliProviderContributionsCache();
this.invalidateSkillsCache();
this.invalidateWorkflowStepsCache();
this.invalidateWorkflowStepTemplatesCache();
@@ -848,6 +876,11 @@ export class PluginRunner {
this.log.log(`Runtimes cache invalidated (version: ${this.runtimesCacheVersion})`);
}
private invalidateCliProviderContributionsCache(): void {
this.cliProviderContributionsCacheVersion++;
this.log.log(`CLI provider contributions cache invalidated (version: ${this.cliProviderContributionsCacheVersion})`);
}
private invalidateSkillsCache(): void {
this.skillsCacheVersion++;
this.log.log(`Skills cache invalidated (version: ${this.skillsCacheVersion})`);