fix(sessions): keep ignored provider mutations inert

This commit is contained in:
Federico Jaramillo Martinez
2026-07-22 20:58:56 +02:00
parent ee8d9e5359
commit e70b3d6bb9
4 changed files with 48 additions and 7 deletions
+7 -1
View File
@@ -3,13 +3,19 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
import { InMemoryCredentialStore, type AuthPrompt, type Credential } from "@earendil-works/pi-ai";
import { afterEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { OAuthFlowState } from "../../shared/apiTypes.js";
import { AuthService, createModelRuntimeForAgentDir, type AuthChange, type AuthServiceLogger } from "./authService.js";
import { OAuthLoginFlowService } from "./oauthLoginFlowService.js";
const tempDirs: string[] = [];
beforeEach(() => {
// Pi 0.81 uses PI_OFFLINE for refreshes after runtime creation. Auth tests
// exercise local credential behavior and must never fetch provider catalogs.
vi.stubEnv("PI_OFFLINE", "1");
});
afterEach(async () => {
vi.unstubAllEnvs();
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
@@ -157,6 +157,27 @@ describe("bootstrapAndFreezeGlobalExtensionProviders", () => {
expect(JSON.stringify(ignoredMutations)).not.toContain("secret");
});
it("keeps ignored mutations as no-ops when structured logging fails", async () => {
const agentDir = await tempDir("pi-web-global-provider-unit-");
const runtime = await createTestModelRuntime();
const { logger } = capturingLogger();
const loggingError = new Error("provider mutation logger failed");
const throwingLogger: GlobalProviderBootstrapLogger = {
...logger,
info(details, message) {
if (message === "ignored provider mutation after global bootstrap") throw loggingError;
logger.info(details, message);
},
};
await bootstrapAndFreezeGlobalExtensionProviders(runtime, agentDir, throwingLogger);
expect(() => { registerProjectConfigProvider(runtime); }).not.toThrow();
expect(() => { runtime.registerNativeProvider(nativeProvider("project-native")); }).not.toThrow();
expect(() => { runtime.unregisterProvider("project-only"); }).not.toThrow();
expect(runtime.getRegisteredProviderIds()).toEqual([]);
});
it("logs non-fatal Pi bootstrap diagnostics and still freezes the runtime", async () => {
const agentDir = await agentDirWithExtension(`
export default function (pi) {
+8 -4
View File
@@ -62,10 +62,14 @@ function freezeProviderMutations(runtime: ModelRuntime, logger: GlobalProviderBo
const loggedIds = loggedProviderIds[operation];
if (loggedIds.has(providerId)) return;
loggedIds.add(providerId);
logger.info(
{ context: LOG_CONTEXT, operation, providerId },
"ignored provider mutation after global bootstrap",
);
try {
logger.info(
{ context: LOG_CONTEXT, operation, providerId },
"ignored provider mutation after global bootstrap",
);
} catch {
// Logging must not turn an ignored mutation into an extension failure.
}
};
const frozenMethods: ProviderMutationMethods = {
registerProvider(providerId) {
@@ -4,12 +4,22 @@ import { join } from "node:path";
import { createAssistantMessageEventStream, InMemoryCredentialStore, type AssistantMessage } from "@earendil-works/pi-ai";
import type { StreamFn } from "@earendil-works/pi-agent-core";
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
import { describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { PiSessionService } from "./piSessionService.js";
import { CapturingSessionEventHub, createTestModelRuntime, fakeRuntime, runtimeCreator, seedCredential, sessionGateway, sessionRecord, sessionRef, TEST_MODEL_ID, TEST_MODEL_PROVIDER, testModel, testModelRuntime, type RuntimeCreator } from "./piSessionService.testSupport.js";
const TEST_AGENT_DIR = "/tmp/pi-web-test-agent";
beforeEach(() => {
// Pi 0.81 uses PI_OFFLINE for refreshes after runtime creation. These tests
// exercise local model/auth behavior and must never fetch provider catalogs.
vi.stubEnv("PI_OFFLINE", "1");
});
afterEach(() => {
vi.unstubAllEnvs();
});
describe("PiSessionService prompt, queue, and auth warnings", () => {
it("sends prompts to an injected runtime without touching the SDK runtime", async () => {
const fake = fakeRuntime("prompt-session");
@@ -78,7 +88,7 @@ describe("PiSessionService prompt, queue, and auth warnings", () => {
await service.dispose();
});
it("generates a session name for the first prompt via the session's agent.streamFn", async () => {
it("generates a session name for the first prompt via the session's agent.streamFunction", async () => {
const model = testModel();
const streamCalls: unknown[] = [];
const streamFn: StreamFn = (streamModel, context, options) => {