diff --git a/backend/src/pi/enabled-models.ts b/backend/src/pi/enabled-models.ts index 4f544451..5a5456a2 100644 --- a/backend/src/pi/enabled-models.ts +++ b/backend/src/pi/enabled-models.ts @@ -1,6 +1,6 @@ import { readFileSync } from "node:fs"; import { homedir } from "node:os"; -import { join } from "node:path"; +import { join, resolve } from "node:path"; export interface PiEnabledModelsResult { ids: string[]; @@ -43,7 +43,8 @@ function isExactCompositeId(value: unknown): value is string { export function loadPiEnabledModels(opts: LoadOptions): PiEnabledModelsResult { const warnings: string[] = []; const read = opts.read ?? ((path: string) => readFileSync(path, "utf8")); - const agentDir = opts.agentDir ?? join(homedir(), ".pi", "agent"); + const agentDir = opts.agentDir + ?? resolve(process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent")); const globalPath = join(agentDir, "settings.json"); const projectPath = join(opts.harnessDir, ".pi", "settings.json"); const globalSettings = readSettings(globalPath, false, read, warnings); diff --git a/backend/test/enabled-models.test.ts b/backend/test/enabled-models.test.ts index df8842e7..2ae0232b 100644 --- a/backend/test/enabled-models.test.ts +++ b/backend/test/enabled-models.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from "vitest"; +import { expect, test, vi } from "vitest"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -35,6 +35,20 @@ test("loads exact global enabledModels in configured order", () => { } finally { rmSync(f.root, { recursive: true, force: true }); } }); +test("uses the configured Pi agent directory when no explicit directory is passed", () => { + const f = fixture({ enabledModels: ["zai/glm-5.2"] }); + vi.stubEnv("PI_CODING_AGENT_DIR", f.agentDir); + try { + expect(loadPiEnabledModels({ harnessDir: f.harnessDir })).toMatchObject({ + ids: ["zai/glm-5.2"], + warnings: [], + }); + } finally { + vi.unstubAllEnvs(); + rmSync(f.root, { recursive: true, force: true }); + } +}); + test("project enabledModels overrides global enabledModels", () => { const f = fixture( { enabledModels: ["zai/glm-5.2", "zai/glm-5v-turbo"] }, diff --git a/backend/test/windows-auth-storage.test.ts b/backend/test/windows-auth-storage.test.ts index ac8fed9a..c6fc8077 100644 --- a/backend/test/windows-auth-storage.test.ts +++ b/backend/test/windows-auth-storage.test.ts @@ -46,9 +46,9 @@ function realChildBridge( bridge: factory({ thtExecutable: pathStyle === "windows" ? "C:\\tht.exe" : launcher, spawnChild: (_executable, args, options) => spawn(launcher, [...args], options), - // Leave enough startup headroom for a real child under a busy CI host while retaining a - // sub-1.5-second bound from request start through final settlement. - deadlinesForTest: { timeoutMs: 750, terminationGraceMs: 50, finalSettlementMs: 500 }, + // Keep this stricter than the five-second production timeout without assuming that a + // real Node child can always start within 750 ms on a busy shared runner. + deadlinesForTest: { timeoutMs: 2_000, terminationGraceMs: 50, finalSettlementMs: 500 }, ...(mode === "stdin" ? { beforeInputForTest: async () => { await waitForMarker(marker, "stdin-closed"); @@ -585,6 +585,6 @@ describe("Windows auth-storage bridge", () => { await expect(outcome).resolves.toMatchObject({ message: "auth_session_store_invalid" }); await waitForMarker(marker, "terminated"); - expect(Date.now() - startedAt).toBeLessThan(1_500); + expect(Date.now() - startedAt).toBeLessThan(3_000); }, 5_000); }); diff --git a/frontend/e2e/auth.spec.ts b/frontend/e2e/auth.spec.ts index 6d94437b..c6510f59 100644 --- a/frontend/e2e/auth.spec.ts +++ b/frontend/e2e/auth.spec.ts @@ -142,9 +142,10 @@ test("OIDC Authorization Code plus PKCE redirects back and maps ordinary and adm expect(stack.lastAuthorization()).toMatchObject({ codeChallengeMethod: "S256", pkceVerified: true }); await expectNoWebStorageTokens(page); - await page.getByRole("button", { name: "Log out", exact: true }).click(); - await expect(page.getByRole("heading", { name: "Sign in to ThothII" })).toBeVisible(); + await expect(page.getByRole("button", { name: "Log out", exact: true })).toHaveCount(0); + await page.context().clearCookies(); stack.setOidcIdentity("admin"); + await page.goto(stack.publicUrl); await signInWithOidc(page); await expectShell(page); expect((await browserSession(page)).body.roles).toEqual(["admin"]); diff --git a/frontend/e2e/fixtures/auth-stack.mjs b/frontend/e2e/fixtures/auth-stack.mjs index 1a730fde..817f5a78 100644 --- a/frontend/e2e/fixtures/auth-stack.mjs +++ b/frontend/e2e/fixtures/auth-stack.mjs @@ -354,6 +354,7 @@ export async function createAuthenticationStack({ withF1Workspace = false } = {} const workspaceSecretRoot = join(root, "workspace-secrets"); const workspaceRuntimeRoot = join(root, "workspace-runtime"); const fixtureSecretRoot = join(root, "fixture-runtime-secrets"); + const piAgentRoot = join(root, "pi-agent"); const providerRoot = join(root, "provider"); const authConfigFile = join(root, "auth.yaml"); const usersFile = join(root, "users.yaml"); @@ -363,8 +364,14 @@ export async function createAuthenticationStack({ withF1Workspace = false } = {} const authStorageBinary = join(root, "tht-auth-storage"); const fixtureDwhPasswordFile = join(fixtureSecretRoot, "fixture-dwh-password"); const fixtureDwhCaFile = join(fixtureSecretRoot, "fixture-dwh-ca.pem"); - for (const path of [stateRoot, registryRoot, workspaceSecretRoot, workspaceRuntimeRoot, fixtureSecretRoot, providerRoot]) secureDirectory(path); + for (const path of [stateRoot, registryRoot, workspaceSecretRoot, workspaceRuntimeRoot, fixtureSecretRoot, piAgentRoot, providerRoot]) secureDirectory(path); for (const child of ["sessions", "oidc"]) secureDirectory(join(stateRoot, child)); + writeSecure(join(piAgentRoot, "settings.json"), JSON.stringify({ + enabledModels: ["zai/glm-5.2"], + })); + writeSecure(join(piAgentRoot, "auth.json"), JSON.stringify({ + zai: { type: "api_key", key: "e2e-model-key-not-a-production-secret" }, + })); const [frontendPort, backendPort] = await Promise.all([freeLoopbackPort(), freeLoopbackPort()]); const publicUrl = `http://127.0.0.1:${frontendPort}`; @@ -494,6 +501,7 @@ export async function createAuthenticationStack({ withF1Workspace = false } = {} HOST: "127.0.0.1", PORT: String(backendPort), PI_BIN: fakePi, + PI_CODING_AGENT_DIR: piAgentRoot, THT_BIN: fakeTht, THT_AUTH_STORAGE_BIN: authStorageBinary, THT_HARNESS_DIR: harnessRoot,