fix(FN-1952): restore pi auth and extension loading

This commit is contained in:
gsxdsm
2026-04-16 21:42:16 -07:00
parent 22e411a82b
commit 32a92e961e
30 changed files with 644 additions and 72 deletions

View File

@@ -22,6 +22,10 @@ export function getFusionAuthPath(home = getHomeDir()): string {
return join(home, ".fusion", "agent", "auth.json");
}
export function getFusionModelsPath(home = getHomeDir()): string {
return join(home, ".fusion", "agent", "models.json");
}
function getLegacyAuthPaths(home = getHomeDir()): string[] {
return [
join(home, ".pi", "agent", "auth.json"),
@@ -29,6 +33,22 @@ function getLegacyAuthPaths(home = getHomeDir()): string[] {
];
}
function getLegacyModelsPaths(home = getHomeDir()): string[] {
return [
join(home, ".pi", "agent", "models.json"),
join(home, ".pi", "models.json"),
];
}
export function getModelRegistryModelsPath(home = getHomeDir()): string {
const fusionModelsPath = getFusionModelsPath(home);
if (existsSync(fusionModelsPath)) {
return fusionModelsPath;
}
return getLegacyModelsPaths(home).find((modelsPath) => existsSync(modelsPath)) ?? fusionModelsPath;
}
function readLegacyCredentials(authPaths = getLegacyAuthPaths()): Record<string, StoredCredential> {
const credentials: Record<string, StoredCredential> = {};

View File

@@ -13,7 +13,7 @@ import { Type, type Static } from "@mariozechner/pi-ai";
import { createKbAgent, describeModel, promptWithFallback, compactSessionContext } from "./pi.js";
import { buildSessionSkillContext } from "./session-skill-context.js";
import { reviewStep, type ReviewVerdict } from "./reviewer.js";
import { ModelRegistry, SessionManager, getAgentDir, type ToolDefinition, type AgentSession } from "@mariozechner/pi-coding-agent";
import { ModelRegistry, SessionManager, type ToolDefinition, type AgentSession } from "@mariozechner/pi-coding-agent";
import { PRIORITY_EXECUTE, type AgentSemaphore } from "./concurrency.js";
import { isRegisteredGitWorktree, isUsableTaskWorktree, type WorktreePool } from "./worktree-pool.js";
import { AgentLogger } from "./agent-logger.js";
@@ -42,7 +42,7 @@ import {
createTaskLogTool as sharedCreateTaskLogTool,
} from "./agent-tools.js";
import { getTaskCompletionBlockerForStore } from "./task-completion.js";
import { createFusionAuthStorage } from "./auth-storage.js";
import { createFusionAuthStorage, getModelRegistryModelsPath } from "./auth-storage.js";
// Re-export for backward compatibility (tests import from executor.ts)
export { summarizeToolArgs } from "./agent-logger.js";
@@ -392,7 +392,7 @@ export class TaskExecutor {
private get modelRegistry(): InstanceType<typeof ModelRegistry> {
if (!this._modelRegistry) {
const authStorage = createFusionAuthStorage();
this._modelRegistry = new ModelRegistry(authStorage, join(getAgentDir(), "models.json"));
this._modelRegistry = new ModelRegistry(authStorage, getModelRegistryModelsPath());
this._modelRegistry.refresh();
}
return this._modelRegistry;

View File

@@ -422,7 +422,11 @@ describe("createKbAgent", () => {
defaultModelId: "glm-5.1",
});
expect(discoverAndLoadExtensionsMock).toHaveBeenCalledWith(["/extensions/zai-provider"], "/tmp", undefined);
expect(discoverAndLoadExtensionsMock).toHaveBeenCalledWith(
["/extensions/zai-provider"],
"/tmp",
"/tmp/.fusion/disabled-auto-extension-discovery",
);
expect(registerProviderMock).toHaveBeenCalledWith("zai", expect.objectContaining({
models: [{ id: "glm-5.1" }],
}));

View File

@@ -20,20 +20,20 @@ import {
DefaultResourceLoader,
DefaultPackageManager,
discoverAndLoadExtensions,
getAgentDir,
ModelRegistry,
SessionManager,
SettingsManager,
type AgentSession,
type ToolDefinition,
} from "@mariozechner/pi-coding-agent";
import { getEnabledPiExtensionPaths, getFusionAgentDir, getLegacyPiAgentDir, resolvePiExtensionProjectRoot } from "@fusion/core";
import {
resolveSessionSkills,
createSkillsOverrideFromSelection,
type SkillSelectionContext,
} from "./skill-resolver.js";
import { isContextLimitError } from "./context-limit-detector.js";
import { createFusionAuthStorage } from "./auth-storage.js";
import { createFusionAuthStorage, getModelRegistryModelsPath } from "./auth-storage.js";
export interface AgentResult {
session: AgentSession;
@@ -252,8 +252,9 @@ function readJsonObject(path: string): Record<string, any> {
}
function createReadOnlyPiSettingsView(cwd: string, agentDir: string): PackageManagerSettingsView {
const projectRoot = resolvePiExtensionProjectRoot(cwd);
const globalSettings = readJsonObject(join(agentDir, "settings.json"));
const fusionProjectSettings = readJsonObject(join(cwd, ".fusion", "settings.json"));
const fusionProjectSettings = readJsonObject(join(projectRoot, ".fusion", "settings.json"));
const mergedSettings = { ...globalSettings, ...fusionProjectSettings };
return {
@@ -265,9 +266,22 @@ function createReadOnlyPiSettingsView(cwd: string, agentDir: string): PackageMan
};
}
function getPackageManagerAgentDir(): string {
const fusionAgentDir = getFusionAgentDir();
if (
existsSync(join(fusionAgentDir, "settings.json")) ||
existsSync(join(fusionAgentDir, "extensions"))
) {
return fusionAgentDir;
}
const legacyAgentDir = getLegacyPiAgentDir();
return existsSync(legacyAgentDir) ? legacyAgentDir : fusionAgentDir;
}
async function registerExtensionProviders(cwd: string, modelRegistry: ModelRegistry): Promise<void> {
try {
const agentDir = getAgentDir();
const agentDir = getPackageManagerAgentDir();
const packageManager = new DefaultPackageManager({
cwd,
agentDir,
@@ -278,7 +292,11 @@ async function registerExtensionProviders(cwd: string, modelRegistry: ModelRegis
.filter((resource) => resource.enabled)
.map((resource) => resource.path);
const extensionsResult = await discoverAndLoadExtensions(packageExtensionPaths, cwd, undefined);
const extensionsResult = await discoverAndLoadExtensions(
[...getEnabledPiExtensionPaths(cwd), ...packageExtensionPaths],
cwd,
join(resolvePiExtensionProjectRoot(cwd), ".fusion", "disabled-auto-extension-discovery"),
);
for (const { path, error } of extensionsResult.errors) {
console.error(`[extensions] Failed to load ${path}: ${error}`);
@@ -475,7 +493,7 @@ export function wrapToolsWithBoundary(
export async function createKbAgent(options: AgentOptions): Promise<AgentResult> {
console.error(`[pi] createKbAgent called (cwd=${options.cwd}, tools=${options.tools}, provider=${options.defaultProvider}, model=${options.defaultModelId})`);
const authStorage = createFusionAuthStorage();
const modelRegistry = new ModelRegistry(authStorage, join(getAgentDir(), "models.json"));
const modelRegistry = new ModelRegistry(authStorage, getModelRegistryModelsPath());
await registerExtensionProviders(options.cwd, modelRegistry);
const tools =

View File

@@ -97,6 +97,7 @@ export class ProjectManager extends EventEmitter<ProjectManagerEvents> {
private globalSemaphore: AgentSemaphore;
/** Mutable limit read by the shared semaphore's getter function. */
private currentGlobalLimit = 4;
private globalLimitRefreshInterval: ReturnType<typeof setInterval>;
/**
* @param centralCore - CentralCore reference for global coordination
@@ -112,7 +113,8 @@ export class ProjectManager extends EventEmitter<ProjectManagerEvents> {
// Refresh the global limit periodically
this.refreshGlobalLimit();
setInterval(() => this.refreshGlobalLimit(), 30000); // Refresh every 30s
this.globalLimitRefreshInterval = setInterval(() => this.refreshGlobalLimit(), 30000);
this.globalLimitRefreshInterval.unref?.();
projectManagerLog.log("ProjectManager initialized");
}
@@ -475,6 +477,7 @@ export class ProjectManager extends EventEmitter<ProjectManagerEvents> {
await Promise.all(stopPromises);
clearInterval(this.globalLimitRefreshInterval);
projectManagerLog.log("All project runtimes stopped");
this.removeAllListeners();
}