| author | |
| committer | |
| log | f3cb2a1fee65a55a4083c4cfe9317ca0d1e7c1d5 |
| tree | f7e5dd7a6cbbb757d0ea7200dee33d4902913208 |
| parent | 66129c6fd518aa01d873ebb47060b41c17312d60 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: javiergusart <42075376+javiergusart@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>7 files changed, 123 insertions(+), 51 deletions(-)
apps/server/src/provider/AntigravityInstallation.test.ts+2| ... | @@ -342,6 +342,8 @@ it.layer(NodeServices.layer)("Antigravity installation", (it) => { | ... | @@ -342,6 +342,8 @@ it.layer(NodeServices.layer)("Antigravity installation", (it) => { |
| 342 | if (!profile) return yield* Effect.die("Expected a disposable validation profile."); | 342 | if (!profile) return yield* Effect.die("Expected a disposable validation profile."); |
| 343 | profiles.add(profile); | 343 | profiles.add(profile); |
| 344 | const helper = command.args[0] === "-e"; | 344 | const helper = command.args[0] === "-e"; |
| 345 | // The runtime unpacks straight into the disposable profile. | ||
| 346 | if (!helper) expect(command.options.env?.TMPDIR).toBe(profile); | ||
| 345 | const output = yield* Queue.unbounded<Uint8Array>(); | 347 | const output = yield* Queue.unbounded<Uint8Array>(); |
| 346 | const exited = yield* Deferred.make<ChildProcessSpawner.ExitCode>(); | 348 | const exited = yield* Deferred.make<ChildProcessSpawner.ExitCode>(); |
| 347 | const terminate = Deferred.succeed(exited, ChildProcessSpawner.ExitCode(0)).pipe( | 349 | const terminate = Deferred.succeed(exited, ChildProcessSpawner.ExitCode(0)).pipe( |
apps/server/src/provider/AntigravityInstallation.ts+3| ... | @@ -476,6 +476,9 @@ export const makeAntigravityInstallation = Effect.fn("AntigravityInstallation.ma | ... | @@ -476,6 +476,9 @@ export const makeAntigravityInstallation = Effect.fn("AntigravityInstallation.ma |
| 476 | profileDirectory, | 476 | profileDirectory, |
| 477 | platform, | 477 | platform, |
| 478 | baseEnv: environment, | 478 | baseEnv: environment, |
| 479 | // The profile is scoped, so it cleans up the unpack; a shallow | ||
| 480 | // root keeps it under Windows' path limit. | ||
| 481 | tempDirectory: profileDirectory, | ||
| 479 | }); | 482 | }); |
| 480 | const runtime = yield* makeAntigravityAcpRuntime({ | 483 | const runtime = yield* makeAntigravityAcpRuntime({ |
| 481 | spawn: buildAntigravityAcpSpawnInput({ | 484 | spawn: buildAntigravityAcpSpawnInput({ |
apps/server/src/provider/Drivers/AntigravityDriver.test.ts+17-10| ... | @@ -31,8 +31,7 @@ import { | ... | @@ -31,8 +31,7 @@ import { |
| 31 | } from "../AntigravityInstallation.ts"; | 31 | } from "../AntigravityInstallation.ts"; |
| 32 | import { | 32 | import { |
| 33 | ANTIGRAVITY_AUTH_STDOUT_PREFIX, | 33 | ANTIGRAVITY_AUTH_STDOUT_PREFIX, |
| 34 | resolveAntigravityProfileDirectory, | 34 | resolveAntigravityInstanceDirectories, |
| 35 | resolveAntigravityRuntimeTempDirectory, | ||
| 36 | } from "../antigravityAuthSupport.ts"; | 35 | } from "../antigravityAuthSupport.ts"; |
| 37 | import { NoOpProviderEventLoggers, ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts"; | 36 | import { NoOpProviderEventLoggers, ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts"; |
| 38 | import * as ModelManifest from "../ModelManifest.ts"; | 37 | import * as ModelManifest from "../ModelManifest.ts"; |
| ... | @@ -74,7 +73,8 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( | ... | @@ -74,7 +73,8 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( |
| 74 | new URL("../../../scripts/acp-mock-agent.ts", import.meta.url), | 73 | new URL("../../../scripts/acp-mock-agent.ts", import.meta.url), |
| 75 | ); | 74 | ); |
| 76 | const requestLog = path.join(root, "requests.jsonl"); | 75 | const requestLog = path.join(root, "requests.jsonl"); |
| 77 | const profileDirectory = resolveAntigravityProfileDirectory(config.stateDir, instanceId); | 76 | const directories = yield* resolveAntigravityInstanceDirectories(config.stateDir, instanceId); |
| 77 | const profileDirectory = directories.profile; | ||
| 78 | const instancePath = `${path.join(root, "instance-bin")}:${baseEnv.PATH ?? ""}`; | 78 | const instancePath = `${path.join(root, "instance-bin")}:${baseEnv.PATH ?? ""}`; |
| 79 | 79 | ||
| 80 | const makeExecutable = Effect.fn("AntigravityDriverTest.makeExecutable")(function* ( | 80 | const makeExecutable = Effect.fn("AntigravityDriverTest.makeExecutable")(function* ( |
| ... | @@ -233,6 +233,7 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( | ... | @@ -233,6 +233,7 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( |
| 233 | fs, | 233 | fs, |
| 234 | path, | 234 | path, |
| 235 | profileDirectory, | 235 | profileDirectory, |
| 236 | directories, | ||
| 236 | instancePath, | 237 | instancePath, |
| 237 | first, | 238 | first, |
| 238 | second, | 239 | second, |
| ... | @@ -475,7 +476,7 @@ it.layer(testLayer)("AntigravityDriver", (it) => { | ... | @@ -475,7 +476,7 @@ it.layer(testLayer)("AntigravityDriver", (it) => { |
| 475 | () => | 476 | () => |
| 476 | Effect.gen(function* () { | 477 | Effect.gen(function* () { |
| 477 | const h = yield* makeHarness(); | 478 | const h = yield* makeHarness(); |
| 478 | const tempRoot = resolveAntigravityRuntimeTempDirectory(h.profileDirectory); | 479 | const tempRoot = h.directories.runtimeTemp; |
| 479 | yield* h.refresh(); | 480 | yield* h.refresh(); |
| 480 | yield* h.refresh(); | 481 | yield* h.refresh(); |
| 481 | const directories = h.launches.flatMap((launch) => | 482 | const directories = h.launches.flatMap((launch) => |
| ... | @@ -498,12 +499,17 @@ it.layer(testLayer)("AntigravityDriver", (it) => { | ... | @@ -498,12 +499,17 @@ it.layer(testLayer)("AntigravityDriver", (it) => { |
| 498 | const path = yield* Path.Path; | 499 | const path = yield* Path.Path; |
| 499 | const config = yield* ServerConfig; | 500 | const config = yield* ServerConfig; |
| 500 | const instanceId = ProviderInstanceId.make("antigravity-orphan-sweep"); | 501 | const instanceId = ProviderInstanceId.make("antigravity-orphan-sweep"); |
| 501 | const tempRoot = resolveAntigravityRuntimeTempDirectory( | 502 | const directories = yield* resolveAntigravityInstanceDirectories( |
| 502 | resolveAntigravityProfileDirectory(config.stateDir, instanceId), | 503 | config.stateDir, |
| 504 | instanceId, | ||
| 503 | ); | 505 | ); |
| 504 | const orphan = path.join(tempRoot, "run-orphan", "_MEI123", "google3"); | 506 | // Older builds unpacked inside the profile. |
| 505 | yield* fs.makeDirectory(orphan, { recursive: true }); | 507 | const legacyRoot = path.join(directories.profile, "antigravity-acp", "tmp"); |
| 506 | yield* fs.writeFileString(path.join(orphan, "payload.bin"), "stale"); | 508 | for (const root of [directories.runtimeTemp, legacyRoot]) { |
| 509 | const orphan = path.join(root, "run-orphan", "_MEI123", "google3"); | ||
| 510 | yield* fs.makeDirectory(orphan, { recursive: true }); | ||
| 511 | yield* fs.writeFileString(path.join(orphan, "payload.bin"), "stale"); | ||
| 512 | } | ||
| 507 | yield* AntigravityDriver.create({ | 513 | yield* AntigravityDriver.create({ |
| 508 | instanceId, | 514 | instanceId, |
| 509 | displayName: "Sweep", | 515 | displayName: "Sweep", |
| ... | @@ -519,7 +525,8 @@ it.layer(testLayer)("AntigravityDriver", (it) => { | ... | @@ -519,7 +525,8 @@ it.layer(testLayer)("AntigravityDriver", (it) => { |
| 519 | }), | 525 | }), |
| 520 | ), | 526 | ), |
| 521 | ); | 527 | ); |
| 522 | expect(yield* fs.exists(tempRoot)).toBe(false); | 528 | expect(yield* fs.exists(directories.runtimeTemp)).toBe(false); |
| 529 | expect(yield* fs.exists(legacyRoot)).toBe(false); | ||
| 523 | }).pipe(Effect.scoped), | 530 | }).pipe(Effect.scoped), |
| 524 | ); | 531 | ); |
| 525 | 532 |
apps/server/src/provider/Drivers/AntigravityDriver.ts+26-7| ... | @@ -33,8 +33,7 @@ import { | ... | @@ -33,8 +33,7 @@ import { |
| 33 | buildAntigravityAcpSpawnInput, | 33 | buildAntigravityAcpSpawnInput, |
| 34 | isAntigravitySignInRequiredError, | 34 | isAntigravitySignInRequiredError, |
| 35 | prepareAntigravityProfile, | 35 | prepareAntigravityProfile, |
| 36 | resolveAntigravityProfileDirectory, | 36 | resolveAntigravityInstanceDirectories, |
| 37 | resolveAntigravityRuntimeTempDirectory, | ||
| 38 | type AntigravityAuthConfig, | 37 | type AntigravityAuthConfig, |
| 39 | } from "../antigravityAuthSupport.ts"; | 38 | } from "../antigravityAuthSupport.ts"; |
| 40 | import { | 39 | import { |
| ... | @@ -103,15 +102,34 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD | ... | @@ -103,15 +102,34 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD |
| 103 | const authConfigIssue = antigravityAuthConfigIssue(auth); | 102 | const authConfigIssue = antigravityAuthConfigIssue(auth); |
| 104 | const processEnvironment = mergeProviderInstanceEnvironment(environment); | 103 | const processEnvironment = mergeProviderInstanceEnvironment(environment); |
| 105 | const userHome = resolveAntigravityUserHome(yield* HostProcessPlatform, processEnvironment); | 104 | const userHome = resolveAntigravityUserHome(yield* HostProcessPlatform, processEnvironment); |
| 106 | const profileDirectory = resolveAntigravityProfileDirectory( | 105 | const directories = yield* resolveAntigravityInstanceDirectories( |
| 107 | serverConfig.stateDir, | 106 | serverConfig.stateDir, |
| 108 | instanceId, | 107 | instanceId, |
| 108 | ).pipe( | ||
| 109 | Effect.provideService(Crypto.Crypto, crypto), | ||
| 110 | Effect.provideService(Path.Path, path), | ||
| 111 | Effect.mapError( | ||
| 112 | (cause) => | ||
| 113 | new ProviderDriverError({ | ||
| 114 | driver: DRIVER, | ||
| 115 | instanceId, | ||
| 116 | detail: "Could not resolve the Antigravity profile directory.", | ||
| 117 | cause, | ||
| 118 | }), | ||
| 119 | ), | ||
| 109 | ); | 120 | ); |
| 121 | const profileDirectory = directories.profile; | ||
| 110 | // No process of this instance exists yet, so every runtime temp | 122 | // No process of this instance exists yet, so every runtime temp |
| 111 | // directory left under the profile is an orphan from a killed server. | 123 | // directory it owns is an orphan from a killed server. Older builds |
| 112 | yield* removeAntigravityRuntimeTempDirs( | 124 | // unpacked inside the profile. |
| 113 | resolveAntigravityRuntimeTempDirectory(profileDirectory), | 125 | for (const directory of [ |
| 114 | ).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem)); | 126 | directories.runtimeTemp, |
| 127 | path.join(profileDirectory, "antigravity-acp", "tmp"), | ||
| 128 | ]) { | ||
| 129 | yield* removeAntigravityRuntimeTempDirs(directory).pipe( | ||
| 130 | Effect.provideService(FileSystem.FileSystem, fileSystem), | ||
| 131 | ); | ||
| 132 | } | ||
| 115 | const continuationIdentity = defaultProviderContinuationIdentity({ | 133 | const continuationIdentity = defaultProviderContinuationIdentity({ |
| 116 | driverKind: DRIVER, | 134 | driverKind: DRIVER, |
| 117 | instanceId, | 135 | instanceId, |
| ... | @@ -165,6 +183,7 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD | ... | @@ -165,6 +183,7 @@ export const AntigravityDriver: ProviderDriver<AntigravitySettings, AntigravityD |
| 165 | baseEnv: processEnvironment, | 183 | baseEnv: processEnvironment, |
| 166 | auth, | 184 | auth, |
| 167 | userHome, | 185 | userHome, |
| 186 | tempDirectory: directories.runtimeTemp, | ||
| 168 | }).pipe( | 187 | }).pipe( |
| 169 | Effect.provideService(FileSystem.FileSystem, fileSystem), | 188 | Effect.provideService(FileSystem.FileSystem, fileSystem), |
| 170 | Effect.provideService(Path.Path, path), | 189 | Effect.provideService(Path.Path, path), |
apps/server/src/provider/acp/AntigravitySessionFiles.ts+4-4| ... | @@ -43,10 +43,10 @@ export const removeAntigravitySessionFiles = Effect.fn("removeAntigravitySession | ... | @@ -43,10 +43,10 @@ export const removeAntigravitySessionFiles = Effect.fn("removeAntigravitySession |
| 43 | ); | 43 | ); |
| 44 | 44 | ||
| 45 | /** | 45 | /** |
| 46 | * Removes every per-process runtime temp directory under the profile. Call | 46 | * Removes every per-process runtime temp directory under an instance's root. |
| 47 | * once when the driver starts, before it launches any process, so a previous | 47 | * Call once when the driver starts, before it launches any process, so a |
| 48 | * server that was killed mid-session cannot leave unpacked runtimes behind. | 48 | * previous server that was killed mid-session cannot leave unpacked runtimes |
| 49 | * Only the profile-owned directory is touched. The system temp directory | 49 | * behind. Only T3-owned directories are touched. The system temp directory |
| 50 | * belongs to other programs and Windows does not lock data files, so sweeping | 50 | * belongs to other programs and Windows does not lock data files, so sweeping |
| 51 | * it could gut a live extraction. | 51 | * it could gut a live extraction. |
| 52 | */ | 52 | */ |
apps/server/src/provider/antigravityAuthSupport.test.ts+42-15| ... | @@ -1,6 +1,8 @@ | ... | @@ -1,6 +1,8 @@ |
| 1 | // @effect-diagnostics-next-line nodeBuiltinImport:off | 1 | // @effect-diagnostics-next-line nodeBuiltinImport:off |
| 2 | import * as NodeChildProcess from "node:child_process"; | 2 | import * as NodeChildProcess from "node:child_process"; |
| 3 | 3 | ||
| 4 | import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; | ||
| 5 | import * as NodePath from "@effect/platform-node/NodePath"; | ||
| 4 | import * as NodeServices from "@effect/platform-node/NodeServices"; | 6 | import * as NodeServices from "@effect/platform-node/NodeServices"; |
| 5 | import { ProviderInstanceId } from "@t3tools/contracts"; | 7 | import { ProviderInstanceId } from "@t3tools/contracts"; |
| 6 | import { | 8 | import { |
| ... | @@ -11,6 +13,7 @@ import { | ... | @@ -11,6 +13,7 @@ import { |
| 11 | import { describe, expect, it } from "@effect/vitest"; | 13 | import { describe, expect, it } from "@effect/vitest"; |
| 12 | import * as Effect from "effect/Effect"; | 14 | import * as Effect from "effect/Effect"; |
| 13 | import * as FileSystem from "effect/FileSystem"; | 15 | import * as FileSystem from "effect/FileSystem"; |
| 16 | import * as Layer from "effect/Layer"; | ||
| 14 | import * as Path from "effect/Path"; | 17 | import * as Path from "effect/Path"; |
| 15 | import * as Result from "effect/Result"; | 18 | import * as Result from "effect/Result"; |
| 16 | import * as Schema from "effect/Schema"; | 19 | import * as Schema from "effect/Schema"; |
| ... | @@ -36,7 +39,7 @@ import { | ... | @@ -36,7 +39,7 @@ import { |
| 36 | makeAntigravityStdoutTransform, | 39 | makeAntigravityStdoutTransform, |
| 37 | parseAntigravityAuthorizationUrl, | 40 | parseAntigravityAuthorizationUrl, |
| 38 | prepareAntigravityProfile, | 41 | prepareAntigravityProfile, |
| 39 | resolveAntigravityProfileDirectory, | 42 | resolveAntigravityInstanceDirectories, |
| 40 | } from "./antigravityAuthSupport.ts"; | 43 | } from "./antigravityAuthSupport.ts"; |
| 41 | 44 | ||
| 42 | const authorizationUrl = | 45 | const authorizationUrl = |
| ... | @@ -246,20 +249,44 @@ describe("Antigravity process environment", () => { | ... | @@ -246,20 +249,44 @@ describe("Antigravity process environment", () => { |
| 246 | } | 249 | } |
| 247 | }); | 250 | }); |
| 248 | 251 | ||
| 249 | it("keeps accounts separate even when instance IDs differ only by case", () => { | 252 | it.effect("keeps accounts separate even when instance IDs differ only by case", () => |
| 250 | const first = resolveAntigravityProfileDirectory( | 253 | Effect.gen(function* () { |
| 251 | "/userdata", | 254 | const first = yield* resolveAntigravityInstanceDirectories( |
| 252 | ProviderInstanceId.make("antigravity"), | 255 | "/userdata", |
| 253 | ); | 256 | ProviderInstanceId.make("antigravity"), |
| 254 | const second = resolveAntigravityProfileDirectory( | 257 | ); |
| 255 | "/userdata", | 258 | const second = yield* resolveAntigravityInstanceDirectories( |
| 256 | ProviderInstanceId.make("Antigravity"), | 259 | "/userdata", |
| 257 | ); | 260 | ProviderInstanceId.make("Antigravity"), |
| 258 | expect(first.toLowerCase()).not.toBe(second.toLowerCase()); | 261 | ); |
| 259 | expect( | 262 | // Existing sign-ins live at this path; it must not move. |
| 260 | resolveAntigravityProfileDirectory("/userdata", ProviderInstanceId.make("antigravity")), | 263 | expect(first.profile).toBe( |
| 261 | ).toBe(first); | 264 | "/userdata/providers/antigravity/ac0a3dfd6dddb20962cecff6ee5fe65e19d3923be20e52c5ab52ff877f7e4c32", |
| 262 | }); | 265 | ); |
| 266 | expect(first.profile.toLowerCase()).not.toBe(second.profile.toLowerCase()); | ||
| 267 | expect(first.runtimeTemp.toLowerCase()).not.toBe(second.runtimeTemp.toLowerCase()); | ||
| 268 | }).pipe(Effect.provide(Layer.mergeAll(NodeCrypto.layer, NodePath.layerPosix))), | ||
| 269 | ); | ||
| 270 | |||
| 271 | it.effect("keeps the unpacked Windows runtime under MAX_PATH for long user names", () => | ||
| 272 | Effect.gen(function* () { | ||
| 273 | const path = yield* Path.Path; | ||
| 274 | // Deepest member of the official agy_acp_server_1.1.1 windows-x86_64 bundle. | ||
| 275 | const deepestMember = | ||
| 276 | "google3\\cloud\\developer_experience\\antigravity_extensions\\acp_server\\_private__agy_acp_server_bin.lazy_imports_info.json"; | ||
| 277 | const directories = yield* resolveAntigravityInstanceDirectories( | ||
| 278 | "C:\\Users\\a-twenty-char-person\\.t3\\userdata", | ||
| 279 | ProviderInstanceId.make("antigravity"), | ||
| 280 | ); | ||
| 281 | const extracted = (tempDirectory: string) => | ||
| 282 | path.join(tempDirectory, "run-AbC123", "_MEI000012ab2", deepestMember); | ||
| 283 | // MAX_PATH is 260 including the terminating NUL. | ||
| 284 | expect(extracted(directories.runtimeTemp).length).toBeLessThan(260); | ||
| 285 | expect( | ||
| 286 | extracted(path.join(directories.profile, "antigravity-acp", "tmp")).length, | ||
| 287 | ).toBeGreaterThanOrEqual(260); | ||
| 288 | }).pipe(Effect.provide(Layer.mergeAll(NodeCrypto.layer, NodePath.layerWin32))), | ||
| 289 | ); | ||
| 263 | }); | 290 | }); |
| 264 | 291 | ||
| 265 | describe("Antigravity authorization URL", () => { | 292 | describe("Antigravity authorization URL", () => { |
apps/server/src/provider/antigravityAuthSupport.ts+29-15| ... | @@ -1,13 +1,12 @@ | ... | @@ -1,13 +1,12 @@ |
| 1 | import * as NodeCrypto from "node:crypto"; | ||
| 2 | // @effect-diagnostics-next-line nodeBuiltinImport:off - Effect's symlink has no type argument, and Windows needs a junction to link without elevation. | 1 | // @effect-diagnostics-next-line nodeBuiltinImport:off - Effect's symlink has no type argument, and Windows needs a junction to link without elevation. |
| 3 | import * as NodeFSP from "node:fs/promises"; | 2 | import * as NodeFSP from "node:fs/promises"; |
| 4 | // @effect-diagnostics-next-line nodeBuiltinImport:off - resolveAntigravityProfileDirectory is a pure sync helper, so it cannot use the Path service. | ||
| 5 | import * as NodePath from "node:path"; | ||
| 6 | 3 | ||
| 7 | import type { AntigravityAuthMethod, ProviderInstanceId } from "@t3tools/contracts"; | 4 | import type { AntigravityAuthMethod, ProviderInstanceId } from "@t3tools/contracts"; |
| 8 | import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; | 5 | import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; |
| 9 | import { resolveNodeExecutable, nodeRuntimeUnavailableMessage } from "@t3tools/shared/nodeRuntime"; | 6 | import { resolveNodeExecutable, nodeRuntimeUnavailableMessage } from "@t3tools/shared/nodeRuntime"; |
| 7 | import * as Crypto from "effect/Crypto"; | ||
| 10 | import * as Effect from "effect/Effect"; | 8 | import * as Effect from "effect/Effect"; |
| 9 | import * as Encoding from "effect/Encoding"; | ||
| 11 | import * as FileSystem from "effect/FileSystem"; | 10 | import * as FileSystem from "effect/FileSystem"; |
| 12 | import * as Path from "effect/Path"; | 11 | import * as Path from "effect/Path"; |
| 13 | import type * as PlatformError from "effect/PlatformError"; | 12 | import type * as PlatformError from "effect/PlatformError"; |
| ... | @@ -185,19 +184,32 @@ export function isAntigravitySignInRequiredError(error: unknown): boolean { | ... | @@ -185,19 +184,32 @@ export function isAntigravitySignInRequiredError(error: unknown): boolean { |
| 185 | ); | 184 | ); |
| 186 | } | 185 | } |
| 187 | 186 | ||
| 188 | /** Keeps case-sensitive instance IDs separate on case-insensitive filesystems. */ | 187 | export interface AntigravityInstanceDirectories { |
| 189 | export function resolveAntigravityProfileDirectory( | 188 | /** GEMINI_HOME for the agent. Holds the instance's Google sign-in. */ |
| 190 | stateDir: string, | 189 | readonly profile: string; |
| 191 | instanceId: ProviderInstanceId, | 190 | /** |
| 192 | ): string { | 191 | * Parent of the per-process directories the agent unpacks into. It sits |
| 193 | const directoryName = NodeCrypto.createHash("sha256").update(instanceId).digest("hex"); | 192 | * beside the profile, not inside it: the agent unpacks members up to 120 |
| 194 | return NodePath.join(stateDir, "providers", "antigravity", directoryName); | 193 | * characters deep, and the profile's longer name would push them past |
| 194 | * Windows' 260-character path limit. | ||
| 195 | */ | ||
| 196 | readonly runtimeTemp: string; | ||
| 195 | } | 197 | } |
| 196 | 198 | ||
| 197 | /** Parent of the per-process runtime temp directories inside a profile. */ | 199 | /** Hashes the instance ID so case-only differences stay separate on case-insensitive filesystems. */ |
| 198 | export function resolveAntigravityRuntimeTempDirectory(profileDirectory: string): string { | 200 | export const resolveAntigravityInstanceDirectories = Effect.fn( |
| 199 | return NodePath.join(profileDirectory, "antigravity-acp", "tmp"); | 201 | "resolveAntigravityInstanceDirectories", |
| 200 | } | 202 | )(function* (stateDir: string, instanceId: ProviderInstanceId) { |
| 203 | const crypto = yield* Crypto.Crypto; | ||
| 204 | const path = yield* Path.Path; | ||
| 205 | const key = Encoding.encodeHex( | ||
| 206 | yield* crypto.digest("SHA-256", new TextEncoder().encode(instanceId)), | ||
| 207 | ); | ||
| 208 | return { | ||
| 209 | profile: path.join(stateDir, "providers", "antigravity", key), | ||
| 210 | runtimeTemp: path.join(stateDir, "antigravity-tmp", key.slice(0, 12)), | ||
| 211 | } satisfies AntigravityInstanceDirectories; | ||
| 212 | }); | ||
| 201 | 213 | ||
| 202 | function quoteBrowserArgument(value: string): string { | 214 | function quoteBrowserArgument(value: string): string { |
| 203 | return `'${value.replaceAll("'", `'"'"'`)}'`; | 215 | return `'${value.replaceAll("'", `'"'"'`)}'`; |
| ... | @@ -300,6 +312,8 @@ export const prepareAntigravityProfile = Effect.fn("prepareAntigravityProfile")( | ... | @@ -300,6 +312,8 @@ export const prepareAntigravityProfile = Effect.fn("prepareAntigravityProfile")( |
| 300 | readonly auth?: AntigravityAuthConfig; | 312 | readonly auth?: AntigravityAuthConfig; |
| 301 | /** Home the agent expands `~` against. Defaults to the launch environment's. */ | 313 | /** Home the agent expands `~` against. Defaults to the launch environment's. */ |
| 302 | readonly userHome?: string; | 314 | readonly userHome?: string; |
| 315 | /** Parent of per-process temp directories. Defaults to one inside the profile. */ | ||
| 316 | readonly tempDirectory?: string; | ||
| 303 | }) { | 317 | }) { |
| 304 | const auth = input.auth ?? ANTIGRAVITY_PERSONAL_AUTH; | 318 | const auth = input.auth ?? ANTIGRAVITY_PERSONAL_AUTH; |
| 305 | const fs = yield* FileSystem.FileSystem; | 319 | const fs = yield* FileSystem.FileSystem; |
| ... | @@ -337,7 +351,7 @@ export const prepareAntigravityProfile = Effect.fn("prepareAntigravityProfile")( | ... | @@ -337,7 +351,7 @@ export const prepareAntigravityProfile = Effect.fn("prepareAntigravityProfile")( |
| 337 | 351 | ||
| 338 | const geminiHome = path.resolve(input.profileDirectory); | 352 | const geminiHome = path.resolve(input.profileDirectory); |
| 339 | const acpDirectory = path.join(geminiHome, "antigravity-acp"); | 353 | const acpDirectory = path.join(geminiHome, "antigravity-acp"); |
| 340 | const tempDirectory = resolveAntigravityRuntimeTempDirectory(geminiHome); | 354 | const tempDirectory = input.tempDirectory ?? path.join(acpDirectory, "tmp"); |
| 341 | const profile: AntigravityProfile = { | 355 | const profile: AntigravityProfile = { |
| 342 | platform, | 356 | platform, |
| 343 | geminiHome, | 357 | geminiHome, |