feat(FN-3313): add plugin slot registry, planning mode rewind, and docker n

This merge lands five features spanning the Fusion stack: research settings key renaming (FN-3313, Steps 2–7) across types, defaults, CLI, and engine packages; static plugin slot-host rendering contract documentation (FN-3260); Docker node provisioning routes with a planning modal (FN-3116); plannin

Fusion-Task-Id: FN-3313
This commit is contained in:
Fusion
2026-05-04 20:14:46 -07:00
committed by gsxdsm
parent f5268019f9
commit a02b42106d
16 changed files with 118 additions and 90 deletions

View File

@@ -122,13 +122,13 @@ async function enableResearch(cwd: string): Promise<TaskStore> {
await store.updateGlobalSettings({
researchGlobalEnabled: true,
researchGlobalDefaults: { searchProvider: "searxng" },
researchSearxngUrl: "http://localhost:8888",
researchGlobalSearxngUrl: "http://localhost:8888",
});
await store.updateSettings({
researchEnabled: true,
researchSettings: { enabled: true, searchProvider: "searxng" },
researchWebSearchProvider: "searxng",
researchSearxngUrl: "http://localhost:8888",
researchGlobalWebSearchProvider: "searxng",
researchGlobalSearxngUrl: "http://localhost:8888",
});
return store;
}

View File

@@ -72,7 +72,7 @@ describe("research extension tools", () => {
const store = new TaskStore(tmpDir);
await store.init();
await store.updateSettings({
researchWebSearchProvider: "tavily",
researchGlobalWebSearchProvider: "tavily",
researchSettings: { enabled: true },
researchGlobalDefaults: { searchProvider: "tavily" },
});
@@ -88,8 +88,8 @@ describe("research extension tools", () => {
const store = new TaskStore(tmpDir);
await store.init();
await store.updateSettings({
researchWebSearchProvider: "tavily",
researchTavilyApiKey: "test-key",
researchGlobalWebSearchProvider: "tavily",
researchGlobalTavilyApiKey: "test-key",
researchSettings: { enabled: true },
researchGlobalDefaults: { searchProvider: "tavily" },
});
@@ -117,8 +117,8 @@ describe("research extension tools", () => {
const store = new TaskStore(tmpDir);
await store.init();
await store.updateSettings({
researchWebSearchProvider: "tavily",
researchTavilyApiKey: "test-key",
researchGlobalWebSearchProvider: "tavily",
researchGlobalTavilyApiKey: "test-key",
researchSettings: { enabled: true },
researchGlobalDefaults: { searchProvider: "tavily" },
});

View File

@@ -22,7 +22,7 @@ const researchStoreMock = {
const storeMock = {
init: vi.fn(),
getSettings: vi.fn(async () => ({ researchSettings: { enabled: true }, researchWebSearchProvider: "tavily", researchTavilyApiKey: "x" })),
getSettings: vi.fn(async () => ({ researchSettings: { enabled: true }, researchGlobalWebSearchProvider: "tavily", researchGlobalTavilyApiKey: "x" })),
getResearchStore: vi.fn(() => researchStoreMock),
};
@@ -145,7 +145,7 @@ describe("research commands", () => {
});
it("errors when provider credentials are missing", async () => {
storeMock.getSettings.mockResolvedValueOnce({ researchSettings: { enabled: true }, researchWebSearchProvider: "tavily" });
storeMock.getSettings.mockResolvedValueOnce({ researchSettings: { enabled: true }, researchGlobalWebSearchProvider: "tavily" });
await expect(runResearchCreate({ query: "hello" })).rejects.toThrow("process.exit:1");
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("missing-credentials"));
});

View File

@@ -43,10 +43,10 @@ async function getStore(projectName?: string): Promise<TaskStore> {
function hasProviderCredentials(settings: Awaited<ReturnType<TaskStore["getSettings"]>>, providerId: string | undefined): boolean {
if (!providerId) return false;
if (providerId === "searxng") return Boolean(settings.researchSearxngUrl);
if (providerId === "brave") return Boolean(settings.researchBraveApiKey);
if (providerId === "google") return Boolean(settings.researchGoogleSearchApiKey && settings.researchGoogleSearchCx);
if (providerId === "tavily") return Boolean(settings.researchTavilyApiKey);
if (providerId === "searxng") return Boolean(settings.researchGlobalSearxngUrl);
if (providerId === "brave") return Boolean(settings.researchGlobalBraveApiKey);
if (providerId === "google") return Boolean(settings.researchGlobalGoogleSearchApiKey && settings.researchGlobalGoogleSearchCx);
if (providerId === "tavily") return Boolean(settings.researchGlobalTavilyApiKey);
return false;
}
@@ -57,7 +57,7 @@ async function getResearchRuntime(store: TaskStore) {
throw new Error("feature-disabled: Research is disabled in settings.");
}
const configuredProvider = (resolved.searchProvider as string | undefined) ?? settings.researchWebSearchProvider;
const configuredProvider = (resolved.searchProvider as string | undefined) ?? settings.researchGlobalWebSearchProvider;
if (!configuredProvider) {
throw new Error("provider-unavailable: Research providers are not configured. Add provider credentials in settings.");
}

View File

@@ -139,15 +139,15 @@ async function getResearchAvailability(store: TaskStore): Promise<{ ok: boolean;
return { ok: false, code: "feature-disabled", message: "Research is disabled in settings." };
}
const backend = (resolved.searchProvider as string | undefined) ?? settings.researchWebSearchProvider;
const backend = (resolved.searchProvider as string | undefined) ?? settings.researchGlobalWebSearchProvider;
const configured = backend === "searxng"
? Boolean(settings.researchSearxngUrl)
? Boolean(settings.researchGlobalSearxngUrl)
: backend === "brave"
? Boolean(settings.researchBraveApiKey)
? Boolean(settings.researchGlobalBraveApiKey)
: backend === "google"
? Boolean(settings.researchGoogleSearchApiKey && settings.researchGoogleSearchCx)
? Boolean(settings.researchGlobalGoogleSearchApiKey && settings.researchGlobalGoogleSearchCx)
: backend === "tavily"
? Boolean(settings.researchTavilyApiKey)
? Boolean(settings.researchGlobalTavilyApiKey)
: false;
if (!backend) {