diff --git a/apps/server/src/provider/Drivers/AntigravityDriver.test.ts b/apps/server/src/provider/Drivers/AntigravityDriver.test.ts index 922a27df5768..33e3faa2f74a 100644 --- a/apps/server/src/provider/Drivers/AntigravityDriver.test.ts +++ b/apps/server/src/provider/Drivers/AntigravityDriver.test.ts @@ -58,7 +58,7 @@ function shellQuote(value: string): string { } const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( - options: { readonly config?: Partial } = {}, + options: { readonly config?: Partial; readonly enabled?: boolean } = {}, ) { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; @@ -129,6 +129,16 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( const installation = Layer.mock(AntigravityInstallation)({ managedDirectory: root, + resolve: (binaryPath, environment) => + Effect.gen(function* () { + if (controls.failResolution) { + return yield* new AntigravityInstallationError({ + operation: "resolve", + detail: "Fixture resolution failed.", + }); + } + return controls.selected; + }), acquire: (binaryPath, environment) => Effect.gen(function* () { acquisitions.push({ binaryPath, path: environment?.PATH }); @@ -174,7 +184,7 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( const instance = yield* AntigravityDriver.create({ instanceId, displayName: "Google test account", - enabled: false, + enabled: options.enabled ?? false, config: { ...AntigravityDriver.defaultConfig(), ...options.config }, environment: [ { name: "PATH", value: instancePath }, @@ -435,4 +445,15 @@ it.layer(testLayer)("AntigravityDriver", (it) => { yield* h.assertClosed; }).pipe(Effect.scoped), ); + + it.effect("probes through installation resolution without launching a process", () => + Effect.gen(function* () { + const h = yield* makeHarness({ enabled: true }); + const snapshot = yield* h.instance.snapshot.refresh; + expect(snapshot.installed).toBe(true); + expect(snapshot.version).toBe(h.first.version); + expect(h.launches).toEqual([]); + expect(h.acquisitions).toEqual([]); + }).pipe(Effect.scoped), + ); }); diff --git a/apps/server/src/provider/Drivers/AntigravityDriver.ts b/apps/server/src/provider/Drivers/AntigravityDriver.ts index 0082f3cbdc30..021582aef6fe 100644 --- a/apps/server/src/provider/Drivers/AntigravityDriver.ts +++ b/apps/server/src/provider/Drivers/AntigravityDriver.ts @@ -38,7 +38,10 @@ import { } from "../acp/AntigravityAcpSupport.ts"; import type { AcpSessionRuntime, AcpSessionRuntimeStartResult } from "../acp/AcpSessionRuntime.ts"; import type { ServerProviderDraft } from "../providerSnapshot.ts"; -import { removeAntigravitySessionFiles } from "../acp/AntigravitySessionFiles.ts"; +import { + cleanOrphanedAntigravityTempDirs, + removeAntigravitySessionFiles, +} from "../acp/AntigravitySessionFiles.ts"; import { ProviderDriverError } from "../Errors.ts"; import { makeAntigravityAdapter } from "../Layers/AntigravityAdapter.ts"; import { makeAntigravityProvider } from "../Layers/AntigravityProvider.ts"; @@ -98,6 +101,11 @@ export const AntigravityDriver: ProviderDriver Scope.close(processScope, exit)); - return yield* authFlow - .withProcess( - Scope.close(processScope, Exit.void), - Effect.gen(function* () { - const runtime = yield* makeRuntime({ - cwd: serverConfig.stateDir, - clientInfo: { name: "t3-code-provider-probe", version: "0.0.0" }, - mcpServers: [], - }); - return yield* runtime.initialize(); - }), - ) - .pipe(Effect.provideService(Scope.Scope, processScope)); - }).pipe(Effect.scoped); + if (authConfigIssue !== null) { + return yield* new ProviderSetupError({ + instanceId, + operation: "configure", + detail: authConfigIssue, + }); + } + const executable = yield* installation + .resolve(settings.binaryPath, processEnvironment) + .pipe( + Effect.mapError( + (cause) => + new ProviderSetupError({ + instanceId, + operation: "resolve", + detail: cause.detail, + }), + ), + ); + return { + protocolVersion: 1, + agentCapabilities: { + loadSession: true, + promptCapabilities: { image: true, audio: true, embeddedContext: true }, + sessionCapabilities: { list: {}, resume: {} }, + }, + authMethods: [{ id: "oauth-personal", name: "Log in with Google" }], + agentInfo: { + name: "antigravity-acp", + title: "Google Antigravity", + version: executable.version ?? "unknown", + }, + }; + }); const provider = yield* makeAntigravityProvider(settings, { stampIdentity: classifyModels, diff --git a/apps/server/src/provider/acp/AntigravitySessionFiles.ts b/apps/server/src/provider/acp/AntigravitySessionFiles.ts index b00bffbc1646..0ba486cdc470 100644 --- a/apps/server/src/provider/acp/AntigravitySessionFiles.ts +++ b/apps/server/src/provider/acp/AntigravitySessionFiles.ts @@ -38,6 +38,68 @@ export const removeAntigravitySessionFiles = Effect.fn("removeAntigravitySession recursive: true, force: true, }); + + // Best-effort cleanup of any unlocked temporary unpack directories left by PyInstaller + const tmpDirectory = path.join(acpDirectory, "tmp"); + if (yield* fs.exists(tmpDirectory)) { + const entries = yield* fs.readDirectory(tmpDirectory).pipe(Effect.orElseSucceed(() => [])); + for (const entry of entries) { + if (entry.startsWith("_MEI")) { + yield* fs + .remove(path.join(tmpDirectory, entry), { recursive: true, force: true }) + .pipe(Effect.ignore); + } + } + } }, Effect.catch(() => Effect.logWarning("Could not remove temporary Antigravity session files.")), ); + +/** Sweeps orphaned PyInstaller unpack directories from prior runs. */ +export const cleanOrphanedAntigravityTempDirs = Effect.fn("cleanOrphanedAntigravityTempDirs")( + function* (profileDirectory?: string) { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + + // 1. Clean profile-isolated tmp directory if provided + if (profileDirectory) { + const acpTmp = path.join(profileDirectory, "antigravity-acp", "tmp"); + if (yield* fs.exists(acpTmp)) { + const entries = yield* fs.readDirectory(acpTmp).pipe(Effect.orElseSucceed(() => [])); + for (const entry of entries) { + if (entry.startsWith("_MEI")) { + yield* fs + .remove(path.join(acpTmp, entry), { recursive: true, force: true }) + .pipe(Effect.ignore); + } + } + } + } + + // 2. Clean orphaned _MEI folders in system temp directory left by previous Antigravity probes on Windows + // On Unix, concurrent processes can have their files deleted without file locking protection, + // so system temp sweeping is restricted to Windows where active files are lock-protected. + if (process.platform === "win32") { + const systemTemp = process.env.TEMP || process.env.TMP; + if (systemTemp && (yield* fs.exists(systemTemp))) { + const entries = yield* fs.readDirectory(systemTemp).pipe(Effect.orElseSucceed(() => [])); + for (const entry of entries) { + if (entry.startsWith("_MEI")) { + const fullPath = path.join(systemTemp, entry); + const hasGoogle3 = yield* fs + .exists(path.join(fullPath, "google3")) + .pipe(Effect.orElseSucceed(() => false)); + const hasGoogle = + hasGoogle3 || + (yield* fs + .exists(path.join(fullPath, "google")) + .pipe(Effect.orElseSucceed(() => false))); + if (hasGoogle) { + yield* fs.remove(fullPath, { recursive: true, force: true }).pipe(Effect.ignore); + } + } + } + } + } + }, +); diff --git a/apps/server/src/provider/antigravityAuthSupport.test.ts b/apps/server/src/provider/antigravityAuthSupport.test.ts index 7f065caa4f6a..165dca65f6c5 100644 --- a/apps/server/src/provider/antigravityAuthSupport.test.ts +++ b/apps/server/src/provider/antigravityAuthSupport.test.ts @@ -187,6 +187,29 @@ describe("Antigravity process environment", () => { ).toBeNull(); }); + it("isolates TEMP and TMP to the profile directory on Windows", () => { + const windowsProfile: AntigravityProfile = { + platform: "win32", + geminiHome: "C:\\state\\providers\\antigravity\\profile", + acpDirectory: "C:\\state\\providers\\antigravity\\profile\\antigravity-acp", + tokenPath: "C:\\state\\providers\\antigravity\\profile\\antigravity-acp\\acp_token.json", + browserCommand: "managed-browser-helper", + }; + const spawn = buildAntigravityAcpSpawnInput({ + installation: { + executablePath: "C:\\release\\agy_acp_server.exe", + harnessPath: "C:\\release\\localharness_external.exe", + }, + profile: windowsProfile, + cwd: "C:\\project", + baseEnv: { PATH: "C:\\Windows\\system32", TEMP: "C:\\Users\\user\\AppData\\Local\\Temp" }, + }); + expect(spawn.env?.TEMP).toBe( + "C:\\state\\providers\\antigravity\\profile\\antigravity-acp\\tmp", + ); + expect(spawn.env?.TMP).toBe("C:\\state\\providers\\antigravity\\profile\\antigravity-acp\\tmp"); + }); + it("uses the registry launch arguments for each supported host platform", () => { for (const platform of ["linux", "darwin", "win32"] as const) { const spawn = buildAntigravityAcpSpawnInput({ diff --git a/apps/server/src/provider/antigravityAuthSupport.ts b/apps/server/src/provider/antigravityAuthSupport.ts index 8e0040ae0c3e..acbf2d56596a 100644 --- a/apps/server/src/provider/antigravityAuthSupport.ts +++ b/apps/server/src/provider/antigravityAuthSupport.ts @@ -214,6 +214,10 @@ function antigravityEnvironment( : auth.authMethod === "agent-platform" && auth.apiKey ? { GOOGLE_API_KEY: auth.apiKey } : {}; + const tempDirectory = + profile.platform === "win32" + ? NodePath.win32.join(profile.acpDirectory, "tmp") + : NodePath.join(profile.acpDirectory, "tmp"); return { ...environment, ...credential, @@ -222,6 +226,12 @@ function antigravityEnvironment( BROWSER: profile.browserCommand, PYTHONUNBUFFERED: "1", ELECTRON_RUN_AS_NODE: "1", + ...(profile.platform === "win32" + ? { + TEMP: tempDirectory, + TMP: tempDirectory, + } + : {}), }; } @@ -311,6 +321,7 @@ export const prepareAntigravityProfile = Effect.fn("prepareAntigravityProfile")( const geminiHome = path.resolve(input.profileDirectory); const acpDirectory = path.join(geminiHome, "antigravity-acp"); + const tempDirectory = path.join(acpDirectory, "tmp"); const profile: AntigravityProfile = { platform, geminiHome, @@ -358,7 +369,7 @@ export const prepareAntigravityProfile = Effect.fn("prepareAntigravityProfile")( ), ); - for (const directory of [geminiHome, acpDirectory]) { + for (const directory of [geminiHome, acpDirectory, tempDirectory]) { yield* fs .makeDirectory(directory, { recursive: true, mode: 0o700 }) .pipe(