Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/providers/antigravity.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ CodeBurn discovers Antigravity sessions from local directories on disk, then que
1. **Session Discovery:** It scans the following folders for `.pb` or `.db` files:
- **Antigravity CLI:** `%USERPROFILE%\.gemini\antigravity-cli\conversations` (and `implicit`)
- **Antigravity App/older path:** `%USERPROFILE%\.gemini\antigravity\conversations`
- **Antigravity IDE (VSCode-based):** `%USERPROFILE%\.gemini\antigravity-ide\conversations` (and `implicit`)
- **Antigravity IDE:** `%USERPROFILE%\.gemini\antigravity-ide\conversations` (and `implicit`). The IDE also maintains VSCode-style global state at `%APPDATA%\Antigravity IDE\User\globalStorage\state.vscdb`, but that DB stores trajectory metadata (titles, timestamps, workspace paths) — not token usage. Token usage data still comes from the `.db` conversation files.
2. **Language Server RPC Query:** It locates the active language-server process via `ps` on POSIX or `Get-CimInstance Win32_Process` on Windows. It extracts the port and CSRF token from the process arguments, and queries the local HTTPS RPC endpoint `GetCascadeTrajectoryGeneratorMetadata` to parse the session.
3. **Cache Fallback:** If the language server is not running, it falls back to the local results cache.

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1496,9 +1496,10 @@ program
try {
if (action === 'install') {
const result = await installAntigravityStatusLineHook(!!opts.force)
console.log(result === 'already-installed'
? '\n Antigravity CLI usage capture is already installed.\n'
: '\n Antigravity CLI usage capture installed.\n')
const headline = result === 'already-installed'
? 'Antigravity CLI usage capture is already installed.'
: 'Antigravity CLI usage capture installed.'
console.log(`\n ${headline}\n Note: this captures CLI (agy) sessions only. IDE sessions are read from .db files automatically.\n`)
return
}
if (action === 'uninstall') {
Expand Down
30 changes: 29 additions & 1 deletion src/providers/antigravity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const CONVERSATION_ROOTS: readonly AntigravityConversationRoot[] = [
extensions: ['.pb'],
},
] as const
const CACHE_VERSION = 2
const CACHE_VERSION = 4

const RPC_TIMEOUT_MS = 5000
const MAX_RESPONSE_BYTES = 16 * 1024 * 1024
Expand Down Expand Up @@ -468,6 +468,10 @@ export function antigravityAppDataDirFromSourcePath(path: string): 'antigravity'
const lower = path.replace(/\\/g, '/').toLowerCase()
if (lower.includes('/.gemini/antigravity-ide/')) return 'antigravity-ide'
if (lower.includes('/.gemini/antigravity-cli/')) return 'antigravity-cli'
// APPDATA-style install dirs (e.g. `%APPDATA%\Antigravity IDE\...`) — checked
// after the .gemini roots so a profile directory containing "Antigravity IDE"
// cannot misclassify CLI conversation paths.
if (lower.includes('/antigravity ide/')) return 'antigravity-ide'
return 'antigravity'
}

Expand Down Expand Up @@ -1197,6 +1201,24 @@ function applyAntigravityProject(call: ParsedProviderCall, source: SessionSource
call.project = source.project
}

// gen_metadata rows and some RPC entries (missing chatStartMetadata.createdAt)
// carry no per-call timestamp. Left empty, those calls are silently dropped by
// the date-range filters in parser.ts (`if (!callTs) continue`). Every emission
// path stamps the conversation file's mtime as a fallback: all calls in the
// session then share the file's last-write time — best available, since the
// source data has no per-call times. Returns true when a call was repaired so
// cache-hit paths can persist the fix.
function stampFallbackTimestamp(calls: ParsedProviderCall[], fallbackTimestamp: string): boolean {
let repaired = false
for (const call of calls) {
if (!call.timestamp) {
call.timestamp = fallbackTimestamp
repaired = true
}
}
return repaired
}

function createParser(source: SessionSource, seenKeys: Set<string>): SessionParser {
return {
async *parse(): AsyncGenerator<ParsedProviderCall> {
Expand All @@ -1215,9 +1237,11 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
if (!s) return

const projectPath = await extractWorkspacePath(source.path)
const fallbackTimestamp = new Date(s.mtimeMs).toISOString()

const cached = cache.cascades[cascadeId]
if (cached && cached.mtimeMs === s.mtimeMs && cached.sizeBytes === s.size && cached.calls.length > 0) {
if (stampFallbackTimestamp(cached.calls, fallbackTimestamp)) cacheDirty = true
for (const call of cached.calls) {
applyAntigravityProject(call, source, projectPath)
if (seenKeys.has(call.deduplicationKey)) continue
Expand All @@ -1229,6 +1253,7 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars

const sqliteResults = await parseSqliteGenMetadataCalls(source.path, cascadeId)
if (sqliteResults.length > 0) {
stampFallbackTimestamp(sqliteResults, fallbackTimestamp)
for (const call of sqliteResults) {
applyAntigravityProject(call, source, projectPath)
}
Expand All @@ -1251,6 +1276,7 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
const server = await detectServer(antigravityAppDataDirFromSourcePath(source.path))
if (!server) {
if (cached) {
if (stampFallbackTimestamp(cached.calls, fallbackTimestamp)) cacheDirty = true
for (const call of cached.calls) {
applyAntigravityProject(call, source, projectPath)
if (seenKeys.has(call.deduplicationKey)) continue
Expand All @@ -1270,6 +1296,7 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
)
} catch {
if (cached) {
if (stampFallbackTimestamp(cached.calls, fallbackTimestamp)) cacheDirty = true
for (const call of cached.calls) {
applyAntigravityProject(call, source, projectPath)
if (seenKeys.has(call.deduplicationKey)) continue
Expand All @@ -1281,6 +1308,7 @@ function createParser(source: SessionSource, seenKeys: Set<string>): SessionPars
}

const results = buildCallsFromGeneratorMetadata(cascadeId, metadata, modelMap)
stampFallbackTimestamp(results, fallbackTimestamp)
for (const call of results) {
applyAntigravityProject(call, source, projectPath)
}
Expand Down
2 changes: 1 addition & 1 deletion src/session-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ const PROVIDER_PARSE_VERSIONS: Record<string, string> = {
'kilo-code': 'worktree-project-grouping-v1',
'roo-code': 'worktree-project-grouping-v1',
warp: 'worktree-project-grouping-v1',
antigravity: 'worktree-project-grouping-v3',
antigravity: 'worktree-project-grouping-v5',
}

// ── Cache Dir ──────────────────────────────────────────────────────────
Expand Down
72 changes: 71 additions & 1 deletion tests/providers/antigravity.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { mkdtemp, mkdir, readFile, rm, writeFile } from 'fs/promises'
import { mkdtemp, mkdir, readFile, rm, stat, writeFile } from 'fs/promises'
import { tmpdir } from 'os'
import { join } from 'path'
import { createRequire } from 'node:module'
Expand Down Expand Up @@ -605,4 +605,74 @@ describe('antigravity provider helpers', () => {
await rm(tempHome, { recursive: true, force: true })
}
})

async function withTempAntigravityHome(prefix: string, fn: (tempHome: string) => Promise<void>): Promise<void> {
const tempHome = await mkdtemp(join(tmpdir(), prefix))
const previousCacheDir = process.env['CODEBURN_CACHE_DIR']
process.env['CODEBURN_CACHE_DIR'] = join(tempHome, 'cache')
try {
await fn(tempHome)
} finally {
if (previousCacheDir === undefined) delete process.env['CODEBURN_CACHE_DIR']
else process.env['CODEBURN_CACHE_DIR'] = previousCacheDir
await rm(tempHome, { recursive: true, force: true })
}
}

it('stamps file mtime as fallback timestamp for SQLite-parsed calls', async () => {
if (!isSqliteAvailable()) return

await withTempAntigravityHome('codeburn-antigravity-timestamp-', async (tempHome) => {
const fixture = JSON.parse(await readFile(
new URL('../fixtures/antigravity-cli-current/gen-metadata.json', import.meta.url),
'utf-8',
)) as CurrentCliFixture
const conversationsDir = join(tempHome, '.gemini', 'antigravity-ide', 'conversations')

await mkdir(conversationsDir, { recursive: true })

const dbPath = join(conversationsDir, `${fixture.conversationId}.db`)
createCurrentAntigravityCliDb(dbPath, fixture)

const beforeStat = await stat(dbPath)

const parser = createAntigravityProvider().createSessionParser({
path: dbPath,
project: 'antigravity-ide',
provider: 'antigravity',
}, new Set())
const calls: ParsedProviderCall[] = []
for await (const call of parser.parse()) calls.push(call)

expect(calls.length).toBeGreaterThan(0)
for (const call of calls) {
expect(call.timestamp).not.toBe('')
const callTime = new Date(call.timestamp).getTime()
expect(Math.abs(callTime - beforeStat.mtimeMs)).toBeLessThan(5000)
}
})
})

it('classifies APPDATA Antigravity IDE paths as antigravity-ide', () => {
expect(antigravityAppDataDirFromSourcePath(
'C:\\Users\\User\\AppData\\Roaming\\Antigravity IDE\\User\\globalStorage\\state.vscdb',
)).toBe('antigravity-ide')

expect(antigravityAppDataDirFromSourcePath(
'/Users/User/.gemini/antigravity-ide/conversations/abc.db',
)).toBe('antigravity-ide')

expect(antigravityAppDataDirFromSourcePath(
'/Users/User/.gemini/antigravity-cli/conversations/abc.db',
)).toBe('antigravity-cli')

// .gemini roots take precedence over the broad "Antigravity IDE" match.
expect(antigravityAppDataDirFromSourcePath(
'C:\\Users\\Antigravity IDE\\.gemini\\antigravity-cli\\conversations\\abc.db',
)).toBe('antigravity-cli')

expect(antigravityAppDataDirFromSourcePath(
'/Users/User/.gemini/antigravity/conversations/abc.db',
)).toBe('antigravity')
})
})