Merge remote-tracking branch 'origin/main' into pr-36-generic-agent-config

# Conflicts:
#	docs/config.html
#	docs/config.md
#	src/cli.test.ts
#	src/cli.ts
#	src/client/src/components/settings/SettingsSessiondPanel.ts
#	src/client/src/components/settings/settingsConfigDraft.test.ts
#	src/client/src/components/settings/settingsConfigDraft.ts
#	src/server/app.test.ts
#	src/server/app.ts
#	src/server/configRoutes.test.ts
#	src/server/configRoutes.ts
#	src/server/piWebPluginService.test.ts
#	src/server/piWebPluginService.ts
#	src/server/piWebStatus.test.ts
#	src/server/piWebStatus.ts
#	src/server/piWebStatusCache.ts
#	src/server/sessions/authService.test.ts
#	src/server/sessions/piSessionService.ts
#	src/server/sessions/sessionRoutes.test.ts
This commit is contained in:
Federico Jaramillo Martinez
2026-07-13 20:30:08 +02:00
289 changed files with 29164 additions and 6419 deletions
+50 -1
View File
@@ -2,8 +2,10 @@ import { mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { AuthStorage, ModelRegistry } from "@earendil-works/pi-coding-agent";
import { afterEach, describe, expect, it } from "vitest";
import { afterEach, describe, expect, it, vi } from "vitest";
import type { OAuthFlowState } from "../../shared/apiTypes.js";
import { AuthService, type AuthChange } from "./authService.js";
import { OAuthLoginFlowService } from "./oauthLoginFlowService.js";
const tempDirs: string[] = [];
@@ -49,6 +51,39 @@ describe("AuthService", () => {
await expect(readFile(join(agentDir, "auth.json"), "utf8")).resolves.toContain("sk-test");
auth.dispose();
});
it("refreshes auth state after OAuth login completes", () => {
const authStorage = AuthStorage.inMemory();
const modelRegistry = ModelRegistry.create(authStorage);
const authFlows = new CapturingOAuthLoginFlowService();
const auth = new AuthService({ modelRegistry, authFlows });
const changes: AuthChange[] = [];
auth.subscribe((change) => { changes.push(change); });
const reload = vi.spyOn(authStorage, "reload");
const refresh = vi.spyOn(modelRegistry, "refresh");
const provider = authStorage.getOAuthProviders().find((option) => option.id === "anthropic");
if (provider === undefined) throw new Error("Expected built-in OAuth provider");
expect(auth.startOAuthLogin(provider.id)).toMatchObject({ providerId: provider.id, providerName: provider.name, status: "running" });
const startOptions = authFlows.startCalls.at(0);
if (startOptions === undefined) throw new Error("Expected OAuth flow to start");
expect(startOptions.providerId).toBe(provider.id);
expect(startOptions.providerName).toBe(provider.name);
expect(startOptions.authStorage).toBe(authStorage);
expect(changes).toEqual([]);
reload.mockClear();
refresh.mockClear();
if (startOptions.onComplete === undefined) throw new Error("Expected OAuth completion callback");
startOptions.onComplete();
expect(reload).toHaveBeenCalledOnce();
expect(refresh).toHaveBeenCalledOnce();
expect(changes).toEqual([{}]);
auth.dispose();
expect(authFlows.disposed).toBe(true);
});
});
function createAuthService(data: Parameters<typeof AuthStorage.inMemory>[0] = {}) {
@@ -65,3 +100,17 @@ async function tempAgentDir(): Promise<string> {
tempDirs.push(dir);
return dir;
}
class CapturingOAuthLoginFlowService extends OAuthLoginFlowService {
readonly startCalls: Parameters<OAuthLoginFlowService["start"]>[0][] = [];
disposed = false;
override start(options: Parameters<OAuthLoginFlowService["start"]>[0]): OAuthFlowState {
this.startCalls.push(options);
return { flowId: "flow-1", providerId: options.providerId, providerName: options.providerName, status: "running", progress: [] };
}
override dispose(): void {
this.disposed = true;
}
}