| author | |
| committer | |
| log | 8c18b5bb21a5349fbadba8c77456ea34c41b7339 |
| tree | f8bae6ddfd73c9dc9eb2f707d6b528d950aac567 |
| parent | 935c55b3778fdeae0e25b250ce2a9fa7e79c0327 |
| signature | Signed by PGP key B5690EEEBB952194 |
The health probe launched the PyInstaller ACP binary every minute and force killed it, leaving about 1 GB of _MEI files per run. The probe now resolves the install on disk without spawning. Each ACP process gets its own temp directory under the profile that is removed when the runtime closes, and the driver sweeps the profile temp root on create.
Continues #11657 by Vita Skacel. Owned temp directory approach from #9626 by ariszz.
Co-authored-by: Vita Skacel <skacel.vita@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>5 files changed, 244 insertions(+), 20 deletions(-)
apps/server/src/provider/Drivers/AntigravityDriver.test.ts+84-2| ... | ... | @@ -31,6 +31,7 @@ import { |
| 31 | 31 | import { |
| 32 | 32 | ANTIGRAVITY_AUTH_STDOUT_PREFIX, |
| 33 | 33 | resolveAntigravityProfileDirectory, |
| 34 | resolveAntigravityRuntimeTempDirectory, | |
| 34 | 35 | } from "../antigravityAuthSupport.ts"; |
| 35 | 36 | import { NoOpProviderEventLoggers, ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts"; |
| 36 | 37 | import * as ModelManifest from "../ModelManifest.ts"; |
| ... | ... | @@ -58,7 +59,7 @@ function shellQuote(value: string): string { |
| 58 | 59 | } |
| 59 | 60 | |
| 60 | 61 | const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( |
| 61 | options: { readonly config?: Partial<AntigravitySettings> } = {}, | |
| 62 | options: { readonly config?: Partial<AntigravitySettings>; readonly enabled?: boolean } = {}, | |
| 62 | 63 | ) { |
| 63 | 64 | const fs = yield* FileSystem.FileSystem; |
| 64 | 65 | const path = yield* Path.Path; |
| ... | ... | @@ -124,11 +125,22 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( |
| 124 | 125 | forceFileStorage: string | undefined; |
| 125 | 126 | credentialKeys: ReadonlyArray<string>; |
| 126 | 127 | geminiApiKey: string | undefined; |
| 128 | tempDirectory: string | undefined; | |
| 127 | 129 | handle: ChildProcessSpawner.ChildProcessHandle; |
| 128 | 130 | }> = []; |
| 129 | 131 | |
| 130 | 132 | const installation = Layer.mock(AntigravityInstallation)({ |
| 131 | 133 | managedDirectory: root, |
| 134 | resolve: () => | |
| 135 | Effect.gen(function* () { | |
| 136 | if (controls.failResolution) { | |
| 137 | return yield* new AntigravityInstallationError({ | |
| 138 | operation: "resolve", | |
| 139 | detail: "Fixture resolution failed.", | |
| 140 | }); | |
| 141 | } | |
| 142 | return controls.selected; | |
| 143 | }), | |
| 132 | 144 | acquire: (binaryPath, environment) => |
| 133 | 145 | Effect.gen(function* () { |
| 134 | 146 | acquisitions.push({ binaryPath, path: environment?.PATH }); |
| ... | ... | @@ -166,6 +178,10 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( |
| 166 | 178 | blockedCredentialKeys.has(key.toUpperCase()), |
| 167 | 179 | ), |
| 168 | 180 | geminiApiKey: environment.GEMINI_API_KEY, |
| 181 | // Only the agent gets a per-process temp directory. Other launches | |
| 182 | // inherit the host TMPDIR. | |
| 183 | tempDirectory: | |
| 184 | environment.ANTIGRAVITY_HARNESS_PATH === undefined ? undefined : environment.TMPDIR, | |
| 169 | 185 | handle, |
| 170 | 186 | }); |
| 171 | 187 | return handle; |
| ... | ... | @@ -174,7 +190,7 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( |
| 174 | 190 | const instance = yield* AntigravityDriver.create({ |
| 175 | 191 | instanceId, |
| 176 | 192 | displayName: "Google test account", |
| 177 | enabled: false, | |
| 193 | enabled: options.enabled ?? false, | |
| 178 | 194 | config: { ...AntigravityDriver.defaultConfig(), ...options.config }, |
| 179 | 195 | environment: [ |
| 180 | 196 | { name: "PATH", value: instancePath }, |
| ... | ... | @@ -207,12 +223,14 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( |
| 207 | 223 | yield* launch.handle.exitCode.pipe(Effect.ignore); |
| 208 | 224 | expect(yield* launch.handle.isRunning).toBe(false); |
| 209 | 225 | if (launch.cwd) expect(yield* fs.exists(launch.cwd)).toBe(false); |
| 226 | if (launch.tempDirectory) expect(yield* fs.exists(launch.tempDirectory)).toBe(false); | |
| 210 | 227 | } |
| 211 | 228 | }); |
| 212 | 229 | return { |
| 213 | 230 | instance, |
| 214 | 231 | refresh, |
| 215 | 232 | fs, |
| 233 | path, | |
| 216 | 234 | profileDirectory, |
| 217 | 235 | instancePath, |
| 218 | 236 | first, |
| ... | ... | @@ -435,4 +453,68 @@ it.layer(testLayer)("AntigravityDriver", (it) => { |
| 435 | 453 | yield* h.assertClosed; |
| 436 | 454 | }).pipe(Effect.scoped), |
| 437 | 455 | ); |
| 456 | ||
| 457 | it.effect.skipIf(windowsHost)( | |
| 458 | "gives each process its own temp directory and removes it when the process closes", | |
| 459 | () => | |
| 460 | Effect.gen(function* () { | |
| 461 | const h = yield* makeHarness(); | |
| 462 | const tempRoot = resolveAntigravityRuntimeTempDirectory(h.profileDirectory); | |
| 463 | yield* h.refresh(); | |
| 464 | yield* h.refresh(); | |
| 465 | const directories = h.launches.flatMap((launch) => | |
| 466 | launch.tempDirectory === undefined ? [] : [launch.tempDirectory], | |
| 467 | ); | |
| 468 | expect(directories).toHaveLength(2); | |
| 469 | for (const directory of directories) { | |
| 470 | expect(h.path.dirname(directory)).toBe(tempRoot); | |
| 471 | } | |
| 472 | expect(new Set(directories).size).toBe(2); | |
| 473 | yield* h.assertClosed; | |
| 474 | }).pipe(Effect.scoped), | |
| 475 | ); | |
| 476 | ||
| 477 | it.effect.skipIf(windowsHost)( | |
| 478 | "removes runtime temp directories left by a previous server on create", | |
| 479 | () => | |
| 480 | Effect.gen(function* () { | |
| 481 | const fs = yield* FileSystem.FileSystem; | |
| 482 | const path = yield* Path.Path; | |
| 483 | const config = yield* ServerConfig; | |
| 484 | const instanceId = ProviderInstanceId.make("antigravity-orphan-sweep"); | |
| 485 | const tempRoot = resolveAntigravityRuntimeTempDirectory( | |
| 486 | resolveAntigravityProfileDirectory(config.stateDir, instanceId), | |
| 487 | ); | |
| 488 | const orphan = path.join(tempRoot, "run-orphan", "_MEI123", "google3"); | |
| 489 | yield* fs.makeDirectory(orphan, { recursive: true }); | |
| 490 | yield* fs.writeFileString(path.join(orphan, "payload.bin"), "stale"); | |
| 491 | yield* AntigravityDriver.create({ | |
| 492 | instanceId, | |
| 493 | displayName: "Sweep", | |
| 494 | enabled: false, | |
| 495 | config: AntigravityDriver.defaultConfig(), | |
| 496 | environment: [], | |
| 497 | }).pipe( | |
| 498 | Effect.provide( | |
| 499 | Layer.mock(AntigravityInstallation)({ | |
| 500 | managedDirectory: config.stateDir, | |
| 501 | resolve: () => Effect.die("unused"), | |
| 502 | acquire: () => Effect.die("unused"), | |
| 503 | }), | |
| 504 | ), | |
| 505 | ); | |
| 506 | expect(yield* fs.exists(tempRoot)).toBe(false); | |
| 507 | }).pipe(Effect.scoped), | |
| 508 | ); | |
| 509 | ||
| 510 | it.effect("probes through installation resolution without launching a process", () => | |
| 511 | Effect.gen(function* () { | |
| 512 | const h = yield* makeHarness({ enabled: true }); | |
| 513 | const snapshot = yield* h.instance.snapshot.refresh; | |
| 514 | expect(snapshot.installed).toBe(true); | |
| 515 | expect(snapshot.version).toBe(h.first.version); | |
| 516 | expect(h.launches).toEqual([]); | |
| 517 | expect(h.acquisitions).toEqual([]); | |
| 518 | }).pipe(Effect.scoped), | |
| 519 | ); | |
| 438 | 520 | }); |
apps/server/src/provider/Drivers/AntigravityDriver.ts+78-17| ... | ... | @@ -30,6 +30,7 @@ import { |
| 30 | 30 | isAntigravitySignInRequiredError, |
| 31 | 31 | prepareAntigravityProfile, |
| 32 | 32 | resolveAntigravityProfileDirectory, |
| 33 | resolveAntigravityRuntimeTempDirectory, | |
| 33 | 34 | type AntigravityAuthConfig, |
| 34 | 35 | } from "../antigravityAuthSupport.ts"; |
| 35 | 36 | import { |
| ... | ... | @@ -38,7 +39,10 @@ import { |
| 38 | 39 | } from "../acp/AntigravityAcpSupport.ts"; |
| 39 | 40 | import type { AcpSessionRuntime, AcpSessionRuntimeStartResult } from "../acp/AcpSessionRuntime.ts"; |
| 40 | 41 | import type { ServerProviderDraft } from "../providerSnapshot.ts"; |
| 41 | import { removeAntigravitySessionFiles } from "../acp/AntigravitySessionFiles.ts"; | |
| 42 | import { | |
| 43 | removeAntigravityRuntimeTempDirs, | |
| 44 | removeAntigravitySessionFiles, | |
| 45 | } from "../acp/AntigravitySessionFiles.ts"; | |
| 42 | 46 | import { ProviderDriverError } from "../Errors.ts"; |
| 43 | 47 | import { makeAntigravityAdapter } from "../Layers/AntigravityAdapter.ts"; |
| 44 | 48 | import { makeAntigravityProvider } from "../Layers/AntigravityProvider.ts"; |
| ... | ... | @@ -98,6 +102,11 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD |
| 98 | 102 | serverConfig.stateDir, |
| 99 | 103 | instanceId, |
| 100 | 104 | ); |
| 105 | // No process of this instance exists yet, so every runtime temp | |
| 106 | // directory left under the profile is an orphan from a killed server. | |
| 107 | yield* removeAntigravityRuntimeTempDirs( | |
| 108 | resolveAntigravityRuntimeTempDirectory(profileDirectory), | |
| 109 | ).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem)); | |
| 101 | 110 | const continuationIdentity = defaultProviderContinuationIdentity({ |
| 102 | 111 | driverKind: DRIVER, |
| 103 | 112 | instanceId, |
| ... | ... | @@ -142,6 +151,7 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD |
| 142 | 151 | instanceId, |
| 143 | 152 | operation: "resolve", |
| 144 | 153 | detail: cause.detail, |
| 154 | cause, | |
| 145 | 155 | }), |
| 146 | 156 | ), |
| 147 | 157 | ); |
| ... | ... | @@ -155,6 +165,32 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD |
| 155 | 165 | Effect.provideService(Path.Path, path), |
| 156 | 166 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| 157 | 167 | ); |
| 168 | // Each process unpacks into its own directory that dies with the | |
| 169 | // runtime scope, after the child is killed. A shared directory would | |
| 170 | // let one session's teardown delete files a sibling still reads. | |
| 171 | // Removal is best effort: a handle can outlive the kill on Windows, | |
| 172 | // and the sweep on the next driver start reclaims what is left. | |
| 173 | const runtimeTempDirectory = yield* Effect.acquireRelease( | |
| 174 | fileSystem.makeTempDirectory({ directory: profile.tempDirectory, prefix: "run-" }).pipe( | |
| 175 | Effect.mapError( | |
| 176 | (cause) => | |
| 177 | new ProviderSetupError({ | |
| 178 | instanceId, | |
| 179 | operation: "start", | |
| 180 | detail: "Could not create an Antigravity runtime temp directory.", | |
| 181 | cause, | |
| 182 | }), | |
| 183 | ), | |
| 184 | ), | |
| 185 | (directory) => | |
| 186 | fileSystem | |
| 187 | .remove(directory, { recursive: true, force: true }) | |
| 188 | .pipe( | |
| 189 | Effect.catch(() => | |
| 190 | Effect.logWarning("Could not remove an Antigravity runtime temp directory."), | |
| 191 | ), | |
| 192 | ), | |
| 193 | ); | |
| 158 | 194 | const runtime = yield* makeAntigravityAcpRuntime({ |
| 159 | 195 | ...input, |
| 160 | 196 | authMethod: auth.authMethod, |
| ... | ... | @@ -165,6 +201,7 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD |
| 165 | 201 | cwd: input.cwd, |
| 166 | 202 | baseEnv: withAgentDeviceEnvironment(processEnvironment, input), |
| 167 | 203 | auth, |
| 204 | runtimeTempDirectory, | |
| 168 | 205 | }), |
| 169 | 206 | }).pipe(Effect.provideService(Crypto.Crypto, crypto)); |
| 170 | 207 | return { |
| ... | ... | @@ -258,24 +295,48 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD |
| 258 | 295 | // Kick the TTL-gated manifest refresh alongside the health check, as |
| 259 | 296 | // Codex and Claude do. Without it an environment that only runs |
| 260 | 297 | // Antigravity would keep classifying against a stale disk cache. |
| 298 | // The probe must not spawn. The agent is a PyInstaller one-file bundle | |
| 299 | // that unpacks about 1 GB per launch, and the health check runs every | |
| 300 | // minute. Resolving the install on disk is enough to report installed | |
| 301 | // and version. The response below is synthetic: only agentInfo.version | |
| 302 | // is read from it. Sessions and manual refreshes still spawn. | |
| 261 | 303 | const probe = Effect.gen(function* () { |
| 262 | 304 | yield* modelManifest.refreshInBackground; |
| 263 | const processScope = yield* Scope.make(); | |
| 264 | yield* Effect.addFinalizer((exit) => Scope.close(processScope, exit)); | |
| 265 | return yield* authFlow | |
| 266 | .withProcess( | |
| 267 | Scope.close(processScope, Exit.void), | |
| 268 | Effect.gen(function* () { | |
| 269 | const runtime = yield* makeRuntime({ | |
| 270 | cwd: serverConfig.stateDir, | |
| 271 | clientInfo: { name: "t3-code-provider-probe", version: "0.0.0" }, | |
| 272 | mcpServers: [], | |
| 273 | }); | |
| 274 | return yield* runtime.initialize(); | |
| 275 | }), | |
| 276 | ) | |
| 277 | .pipe(Effect.provideService(Scope.Scope, processScope)); | |
| 278 | }).pipe(Effect.scoped); | |
| 305 | if (authConfigIssue !== null) { | |
| 306 | return yield* new ProviderSetupError({ | |
| 307 | instanceId, | |
| 308 | operation: "configure", | |
| 309 | detail: authConfigIssue, | |
| 310 | }); | |
| 311 | } | |
| 312 | const executable = yield* installation | |
| 313 | .resolve(settings.binaryPath, processEnvironment) | |
| 314 | .pipe( | |
| 315 | Effect.mapError( | |
| 316 | (cause) => | |
| 317 | new ProviderSetupError({ | |
| 318 | instanceId, | |
| 319 | operation: "resolve", | |
| 320 | detail: cause.detail, | |
| 321 | cause, | |
| 322 | }), | |
| 323 | ), | |
| 324 | ); | |
| 325 | return { | |
| 326 | protocolVersion: 1, | |
| 327 | agentCapabilities: { | |
| 328 | loadSession: true, | |
| 329 | promptCapabilities: { image: true, audio: true, embeddedContext: true }, | |
| 330 | sessionCapabilities: { list: {}, resume: {} }, | |
| 331 | }, | |
| 332 | authMethods: [{ id: "oauth-personal", name: "Log in with Google" }], | |
| 333 | agentInfo: { | |
| 334 | name: "antigravity-acp", | |
| 335 | title: "Google Antigravity", | |
| 336 | version: executable.version ?? "unknown", | |
| 337 | }, | |
| 338 | }; | |
| 339 | }); | |
| 279 | 340 | |
| 280 | 341 | const provider = yield* makeAntigravityProvider(settings, { |
| 281 | 342 | stampIdentity: classifyModels, |
apps/server/src/provider/acp/AntigravitySessionFiles.ts+18| ... | ... | @@ -41,3 +41,21 @@ export const removeAntigravitySessionFiles = Effect.fn("removeAntigravitySession |
| 41 | 41 | }, |
| 42 | 42 | Effect.catch(() => Effect.logWarning("Could not remove temporary Antigravity session files.")), |
| 43 | 43 | ); |
| 44 | ||
| 45 | /** | |
| 46 | * Removes every per-process runtime temp directory under the profile. Call | |
| 47 | * once when the driver starts, before it launches any process, so a previous | |
| 48 | * server that was killed mid-session cannot leave unpacked runtimes behind. | |
| 49 | * Only the profile-owned directory is touched. The system temp directory | |
| 50 | * belongs to other programs and Windows does not lock data files, so sweeping | |
| 51 | * it could gut a live extraction. | |
| 52 | */ | |
| 53 | export const removeAntigravityRuntimeTempDirs = Effect.fn("removeAntigravityRuntimeTempDirs")( | |
| 54 | function* (tempDirectory: string) { | |
| 55 | const fs = yield* FileSystem.FileSystem; | |
| 56 | yield* fs.remove(tempDirectory, { recursive: true, force: true }); | |
| 57 | }, | |
| 58 | Effect.catch(() => | |
| 59 | Effect.logWarning("Could not remove leftover Antigravity runtime temp files."), | |
| 60 | ), | |
| 61 | ); |
apps/server/src/provider/antigravityAuthSupport.test.ts+43| ... | ... | @@ -51,6 +51,7 @@ describe("Antigravity process environment", () => { |
| 51 | 51 | geminiHome: "/t3/userdata/providers/antigravity/profile", |
| 52 | 52 | acpDirectory: "/t3/userdata/providers/antigravity/profile/antigravity-acp", |
| 53 | 53 | tokenPath: "/t3/userdata/providers/antigravity/profile/antigravity-acp/acp_token.json", |
| 54 | tempDirectory: "/t3/userdata/providers/antigravity/profile/antigravity-acp/tmp", | |
| 54 | 55 | browserCommand: "managed-browser-helper", |
| 55 | 56 | }; |
| 56 | 57 | |
| ... | ... | @@ -101,6 +102,7 @@ describe("Antigravity process environment", () => { |
| 101 | 102 | BROWSER: profile.browserCommand, |
| 102 | 103 | PYTHONUNBUFFERED: "1", |
| 103 | 104 | ELECTRON_RUN_AS_NODE: "1", |
| 105 | TMPDIR: profile.tempDirectory, | |
| 104 | 106 | }, |
| 105 | 107 | }); |
| 106 | 108 | }); |
| ... | ... | @@ -187,6 +189,47 @@ describe("Antigravity process environment", () => { |
| 187 | 189 | ).toBeNull(); |
| 188 | 190 | }); |
| 189 | 191 | |
| 192 | it("isolates TEMP and TMP to the profile directory on Windows", () => { | |
| 193 | const windowsProfile: AntigravityProfile = { | |
| 194 | platform: "win32", | |
| 195 | geminiHome: "C:\\state\\providers\\antigravity\\profile", | |
| 196 | acpDirectory: "C:\\state\\providers\\antigravity\\profile\\antigravity-acp", | |
| 197 | tokenPath: "C:\\state\\providers\\antigravity\\profile\\antigravity-acp\\acp_token.json", | |
| 198 | tempDirectory: "C:\\state\\providers\\antigravity\\profile\\antigravity-acp\\tmp", | |
| 199 | browserCommand: "managed-browser-helper", | |
| 200 | }; | |
| 201 | const input = { | |
| 202 | installation: { | |
| 203 | executablePath: "C:\\release\\agy_acp_server.exe", | |
| 204 | harnessPath: "C:\\release\\localharness_external.exe", | |
| 205 | }, | |
| 206 | profile: windowsProfile, | |
| 207 | cwd: "C:\\project", | |
| 208 | baseEnv: { PATH: "C:\\Windows\\system32", TEMP: "C:\\Users\\user\\AppData\\Local\\Temp" }, | |
| 209 | }; | |
| 210 | const shared = buildAntigravityAcpSpawnInput(input); | |
| 211 | expect(shared.env?.TEMP).toBe(windowsProfile.tempDirectory); | |
| 212 | expect(shared.env?.TMP).toBe(windowsProfile.tempDirectory); | |
| 213 | const perRun = buildAntigravityAcpSpawnInput({ | |
| 214 | ...input, | |
| 215 | runtimeTempDirectory: `${windowsProfile.tempDirectory}\\run-1`, | |
| 216 | }); | |
| 217 | expect(perRun.env?.TEMP).toBe(`${windowsProfile.tempDirectory}\\run-1`); | |
| 218 | expect(perRun.env?.TMP).toBe(`${windowsProfile.tempDirectory}\\run-1`); | |
| 219 | }); | |
| 220 | ||
| 221 | it("isolates TMPDIR to the profile directory on POSIX hosts", () => { | |
| 222 | const spawn = buildAntigravityAcpSpawnInput({ | |
| 223 | installation: { executablePath: "/release/acp", harnessPath: "/release/harness" }, | |
| 224 | profile, | |
| 225 | cwd: "/project", | |
| 226 | baseEnv: { TMPDIR: "/tmp" }, | |
| 227 | runtimeTempDirectory: `${profile.tempDirectory}/run-1`, | |
| 228 | }); | |
| 229 | expect(spawn.env?.TMPDIR).toBe(`${profile.tempDirectory}/run-1`); | |
| 230 | expect(spawn.env?.TEMP).toBeUndefined(); | |
| 231 | }); | |
| 232 | ||
| 190 | 233 | it("uses the registry launch arguments for each supported host platform", () => { |
| 191 | 234 | for (const platform of ["linux", "darwin", "win32"] as const) { |
| 192 | 235 | const spawn = buildAntigravityAcpSpawnInput({ |
apps/server/src/provider/antigravityAuthSupport.ts+21-1| ... | ... | @@ -85,6 +85,8 @@ export interface AntigravityProfile { |
| 85 | 85 | readonly geminiHome: string; |
| 86 | 86 | readonly acpDirectory: string; |
| 87 | 87 | readonly tokenPath: string; |
| 88 | /** Parent of the per-process temp directories PyInstaller unpacks into. */ | |
| 89 | readonly tempDirectory: string; | |
| 88 | 90 | readonly browserCommand: string; |
| 89 | 91 | } |
| 90 | 92 | |
| ... | ... | @@ -191,6 +193,11 @@ export function resolveAntigravityProfileDirectory( |
| 191 | 193 | return NodePath.join(stateDir, "providers", "antigravity", directoryName); |
| 192 | 194 | } |
| 193 | 195 | |
| 196 | /** Parent of the per-process runtime temp directories inside a profile. */ | |
| 197 | export function resolveAntigravityRuntimeTempDirectory(profileDirectory: string): string { | |
| 198 | return NodePath.join(profileDirectory, "antigravity-acp", "tmp"); | |
| 199 | } | |
| 200 | ||
| 194 | 201 | function quoteBrowserArgument(value: string): string { |
| 195 | 202 | return `'${value.replaceAll("'", `'"'"'`)}'`; |
| 196 | 203 | } |
| ... | ... | @@ -199,6 +206,7 @@ function antigravityEnvironment( |
| 199 | 206 | profile: AntigravityProfile, |
| 200 | 207 | baseEnv: NodeJS.ProcessEnv, |
| 201 | 208 | auth: AntigravityAuthConfig, |
| 209 | runtimeTempDirectory?: string, | |
| 202 | 210 | ) { |
| 203 | 211 | const environment: NodeJS.ProcessEnv = {}; |
| 204 | 212 | for (const [key, value] of Object.entries(baseEnv)) { |
| ... | ... | @@ -214,6 +222,10 @@ function antigravityEnvironment( |
| 214 | 222 | : auth.authMethod === "agent-platform" && auth.apiKey |
| 215 | 223 | ? { GOOGLE_API_KEY: auth.apiKey } |
| 216 | 224 | : {}; |
| 225 | // The agent is a PyInstaller one-file bundle. It unpacks about 1 GB into | |
| 226 | // the system temp directory per launch and a force kill leaves that behind. | |
| 227 | // Point it at a T3-owned directory so the driver can reclaim the space. | |
| 228 | const tempDirectory = runtimeTempDirectory ?? profile.tempDirectory; | |
| 217 | 229 | return { |
| 218 | 230 | ...environment, |
| 219 | 231 | ...credential, |
| ... | ... | @@ -222,6 +234,9 @@ function antigravityEnvironment( |
| 222 | 234 | BROWSER: profile.browserCommand, |
| 223 | 235 | PYTHONUNBUFFERED: "1", |
| 224 | 236 | ELECTRON_RUN_AS_NODE: "1", |
| 237 | ...(profile.platform === "win32" | |
| 238 | ? { TEMP: tempDirectory, TMP: tempDirectory } | |
| 239 | : { TMPDIR: tempDirectory }), | |
| 225 | 240 | }; |
| 226 | 241 | } |
| 227 | 242 | |
| ... | ... | @@ -311,11 +326,13 @@ export const prepareAntigravityProfile = Effect.fn("prepareAntigravityProfile")( |
| 311 | 326 | |
| 312 | 327 | const geminiHome = path.resolve(input.profileDirectory); |
| 313 | 328 | const acpDirectory = path.join(geminiHome, "antigravity-acp"); |
| 329 | const tempDirectory = resolveAntigravityRuntimeTempDirectory(geminiHome); | |
| 314 | 330 | const profile: AntigravityProfile = { |
| 315 | 331 | platform, |
| 316 | 332 | geminiHome, |
| 317 | 333 | acpDirectory, |
| 318 | 334 | tokenPath: path.join(acpDirectory, "acp_token.json"), |
| 335 | tempDirectory, | |
| 319 | 336 | browserCommand, |
| 320 | 337 | }; |
| 321 | 338 | const environment = antigravityEnvironment(profile, input.baseEnv ?? process.env, auth); |
| ... | ... | @@ -358,7 +375,7 @@ export const prepareAntigravityProfile = Effect.fn("prepareAntigravityProfile")( |
| 358 | 375 | ), |
| 359 | 376 | ); |
| 360 | 377 | |
| 361 | for (const directory of [geminiHome, acpDirectory]) { | |
| 378 | for (const directory of [geminiHome, acpDirectory, tempDirectory]) { | |
| 362 | 379 | yield* fs |
| 363 | 380 | .makeDirectory(directory, { recursive: true, mode: 0o700 }) |
| 364 | 381 | .pipe( |
| ... | ... | @@ -400,6 +417,8 @@ export function buildAntigravityAcpSpawnInput(input: { |
| 400 | 417 | readonly cwd: string; |
| 401 | 418 | readonly baseEnv?: NodeJS.ProcessEnv; |
| 402 | 419 | readonly auth?: AntigravityAuthConfig; |
| 420 | /** Per-process temp directory. Defaults to the profile's shared temp directory. */ | |
| 421 | readonly runtimeTempDirectory?: string; | |
| 403 | 422 | }): AcpSpawnInput { |
| 404 | 423 | return { |
| 405 | 424 | command: input.installation.executablePath, |
| ... | ... | @@ -410,6 +429,7 @@ export function buildAntigravityAcpSpawnInput(input: { |
| 410 | 429 | input.profile, |
| 411 | 430 | input.baseEnv ?? process.env, |
| 412 | 431 | input.auth ?? ANTIGRAVITY_PERSONAL_AUTH, |
| 432 | input.runtimeTempDirectory, | |
| 413 | 433 | ), |
| 414 | 434 | ANTIGRAVITY_HARNESS_PATH: input.installation.harnessPath, |
| 415 | 435 | }, |