Gemini and Antigravity Source Replay Semantics#
Decant's Gemini CLI source parser (src/sources/gemini.ts) treats a chat JSONL file as a stateful event log, not an append-only list of messages. The file must be replayed to reconstruct the final conversation, mirroring how Gemini CLI itself loads sessions before rendering.
Event-Log Replay Model#
The top-level replay() function drives parsing. It iterates every JSONL line and applies three kinds of mutations to an in-memory Map<id, record>:
$set.messages — Authoritative Checkpoint#
When a line contains { $set: { messages: […] } }, the parser calls messages.clear() and then repopulates the Map from the checkpoint array . All prior message state is discarded. This is a full replace, not a merge.
Test coverage:
test/gemini.test.tslines 390–446 shows thatuser-1andassistant-1written before a$set.messagesare replaced; only the IDs present in the checkpoint array survive.
$rewindTo — Forward-Clear from a Target ID#
A line { $rewindTo: "<id>" } invokes rewind(), which deletes the named message and every message inserted after it (Map insertion order is preserved). Edge case: if the target ID is not in the Map, the entire conversation is cleared . This mirrors Gemini CLI's own behavior for unknown rewind targets.
Test coverage:
test/gemini.test.tslines 448–495 confirms that after{ $rewindTo: "user-2" }, only the messages beforeuser-2survive and new messages appended afterward are included normally.
Repeated Message ID — In-Place Replacement#
Any line with an id field is stored as messages.set(id, { record, lineNo }) . Because a JavaScript Map replaces on duplicate key, the last occurrence wins. Gemini CLI re-appends the same ID when usage counts or tool calls arrive after the initial text turn .
Test coverage:
test/gemini.test.tslines 311–361 showsassistant-1appearing three times (text only → text + toolCalls → text + toolCalls + tokens); the final version is the only one that survives.
File Layout and Subagent Detection#
Gemini CLI stores sessions under :
~/.gemini/tmp/<project>/chats/session-<ts>-<hash>.jsonl # main session
~/.gemini/tmp/<project>/chats/<parent-session-id>/<session-id>.jsonl # subagent
The geminiSessionLayout() function distinguishes the two cases by directory depth:
- Main session: filename starts with
session-and its parent directory is namedchats. - Subagent: filename does not use the
session-prefix; the grandparent directory is namedchatsand the parent directory is the parent session ID.
A file that does not follow one of these patterns is rejected, which prevents accidentally claiming a Claude Code session file that happens to start with session- .
Antigravity 2.10.0 — Not Covered by Gemini Session Scanning#
Antigravity 2.10.0 uses a separate storage layout:
- Transcripts at
~/.gemini/antigravity-cli/…/logs/transcript*.jsonl - Session state in SQLite (not JSONL)
Decant's current session-discovery logic scans ~/.gemini/tmp/**/session-*.jsonl and chats/<parent>/<subagent>.jsonl. That glob does not reach the antigravity-cli directory tree, so Antigravity sessions are silently skipped. There is no Antigravity-specific reader or SQLite loader in the codebase as of v0.5.0 — adding Antigravity support requires a distinct discovery path and a transcript parser for the transcript*.jsonl format.