feat(FN-3338): fix project root resolution in registerExtensionProviders an

The merge adds an auto-reload setting (FN-3334) with UI controls in the settings modal, documentation, and a new version-check module, while also fixing a bug (FN-3338) where extension providers incorrectly resolved the project root when invoked from git worktrees — moving the project-root resolutio

Fusion-Task-Id: FN-3338
This commit is contained in:
Fusion
2026-05-03 16:34:56 -07:00
committed by gsxdsm
parent 8bc76ee52b
commit 00cdc8a1ac
3 changed files with 66 additions and 9 deletions

View File

@@ -22,6 +22,7 @@ const execSyncMock = vi.fn((_cmd?: any, _opts?: any) => "");
const existsSyncMock = vi.fn((_path: PathLike) => false);
const readFileSyncMock = vi.fn((_path?: any) => "{}");
const readCustomProvidersMock = vi.fn(() => []);
const packageManagerCwdCapture = vi.fn();
// Route async `exec` through the `execSync` mock so the promisify bridge works.
// Use Symbol.for("nodejs.util.promisify.custom") directly to avoid async imports
@@ -96,6 +97,9 @@ vi.mock("@mariozechner/pi-coding-agent", () => ({
}
},
DefaultPackageManager: class {
constructor(options: any) {
packageManagerCwdCapture(options?.cwd);
}
async resolve() {
return packageManagerResolveMock();
}
@@ -486,6 +490,46 @@ describe("createFnAgent", () => {
expect(createAgentSessionMock).toHaveBeenCalledTimes(1);
});
it("FN-3338: registerExtensionProviders receives resolved project root when cwd is a subdirectory", async () => {
// Simulate cwd being a subdirectory of the project. resolvePiExtensionProjectRoot
// walks up from /project/src/components checking each dir for .fusion.
existsSyncMock.mockImplementation((path) => {
const value = String(path);
return value === "/project/.fusion";
});
const { createFnAgent } = await import("../pi.js");
await createFnAgent({
cwd: "/project/src/components",
systemPrompt: "test",
tools: "readonly",
});
// registerExtensionProviders should receive the resolved project root,
// not the raw subdirectory cwd. This is verified by checking the
// DefaultPackageManager constructor received "/project" as cwd.
expect(packageManagerCwdCapture).toHaveBeenCalledWith("/project");
expect(createAgentSessionMock).toHaveBeenCalledTimes(1);
});
it("FN-3338: registerExtensionProviders falls back to cwd when no .fusion is found", async () => {
// No .fusion directory exists anywhere above cwd.
existsSyncMock.mockImplementation(() => false);
const { createFnAgent } = await import("../pi.js");
await createFnAgent({
cwd: "/unrelated/directory",
systemPrompt: "test",
tools: "readonly",
});
// Falls back to the raw cwd when no .fusion is found
expect(packageManagerCwdCapture).toHaveBeenCalledWith("/unrelated/directory");
expect(createAgentSessionMock).toHaveBeenCalledTimes(1);
});
it("registers extension providers before resolving configured models", async () => {
packageManagerResolveMock.mockResolvedValueOnce({
extensions: [{ enabled: true, path: "/extensions/zai-provider" }],
@@ -983,11 +1027,13 @@ describe("createFnAgent", () => {
}
},
DefaultPackageManager: class {
constructor(options: any) {
packageManagerCwdCapture(options?.cwd);
}
async resolve() {
return packageManagerResolveMock();
}
},
discoverAndLoadExtensions: discoverAndLoadExtensionsMock,
getAgentDir: () => "/mock-agent-dir",
ModelRegistry: class {
static create(...args: unknown[]) {
@@ -1068,11 +1114,13 @@ describe("createFnAgent", () => {
}
},
DefaultPackageManager: class {
constructor(options: any) {
packageManagerCwdCapture(options?.cwd);
}
async resolve() {
return packageManagerResolveMock();
}
},
discoverAndLoadExtensions: discoverAndLoadExtensionsMock,
getAgentDir: () => "/mock-agent-dir",
ModelRegistry: class {
static create(...args: unknown[]) {
@@ -1150,11 +1198,13 @@ describe("createFnAgent", () => {
}
},
DefaultPackageManager: class {
constructor(options: any) {
packageManagerCwdCapture(options?.cwd);
}
async resolve() {
return packageManagerResolveMock();
}
},
discoverAndLoadExtensions: discoverAndLoadExtensionsMock,
getAgentDir: () => "/mock-agent-dir",
ModelRegistry: class {
static create(...args: unknown[]) {