Skip to content

feat(cursor-agent): discover and reconstruct sessions from store.db (fixes #986) - #1233

Open
maheshsingh20 wants to merge 1 commit into
getagentseal:mainfrom
maheshsingh20:feat/cursor-agent-store-db-986
Open

feat(cursor-agent): discover and reconstruct sessions from store.db (fixes #986)#1233
maheshsingh20 wants to merge 1 commit into
getagentseal:mainfrom
maheshsingh20:feat/cursor-agent-store-db-986

Conversation

@maheshsingh20

@maheshsingh20 maheshsingh20 commented Sep 2, 2026

Copy link
Copy Markdown

Summary

Fixes #986.

The cursor-agent provider previously missed CLI sessions stored in ~/.cursor/chats/**/store.db. This PR adds full discovery and reconstruction for those sessions, with proper source precedence, deduplication, WAL-aware caching, and blobEncryptionKey redaction.


Changes

src/providers/cursor-agent.ts

Discovery

  • discoverSessions() now scans ~/.cursor/chats/<hash>/<uuid>/store.db before the existing transcript walk, without following symlinks beyond two directory levels.
  • probeRoots() gains a chats entry so codeburn doctor reports it.

Schema validation

  • Checks for required meta + blobs tables before any data is read. A missing or incompatible schema emits a warning and falls through to the transcript fallback.

Metadata decoding

  • meta['0'] is hex-decoded from UTF-8 JSON to extract agentId, latestRootBlobId, name, createdAt, and lastUsedModel.
  • blobEncryptionKey is explicitly excluded from the decoded object and never appears in logs, caches, exports, snapshots, emitted calls, or stderr output.

Blob-graph reconstruction

  • BFS walk from latestRootBlobId, following nextBlobId chains and childBlobIds arrays.
  • Classifies blobs by role (user / assistant / model / request).
  • Handles both plain text strings and content-block arrays ([{type:'text', text:'...'}]).
  • Handles both toolCalls: [{name}] and content-block {type:'tool_use', name} formats.
  • Unknown fields are silently ignored for forward compatibility.

Token provenance

  • Explicit per-request inputTokens / outputTokens used when present and plausible (<=2,000,000). Values above that threshold are treated as context-window gauges and replaced by char-based estimates.
  • cacheCreationInputTokens and cacheReadInputTokens recorded when present.
  • costIsEstimated: true set on any call where at least one count was estimated.

Timestamps

  • Internal blob/request timestamps preferred; session createdAt used as session-level fallback. File mtime is never reported as an exact request timestamp.
  • Both epoch-ms (> 1e12) and epoch-s (< 1e12) values are normalised correctly.

WAL-aware in-memory cache

  • Per-session cache keyed on a fingerprint combining store.db mtime and the -wal sidecar size/mtime. Active sessions always trigger a re-parse when the WAL changes.

Source precedence and deduplication

  • store.db sources are emitted first in discoverSessions().
  • A successfully decoded store records a cursor-agent-store-decoded:<uuid> sentinel in seenKeys; transcript parsers for the same session UUID check it and skip themselves.
  • A store that fails schema/metadata validation does not set the sentinel, so the JSONL/TXT transcript fallback runs normally without double-counting.

tests/providers/cursor-agent-store.test.ts (new, 44 tests)

Category Tests
Discovery Empty/missing chats dir, valid store found, multiple sessions, non-UUID dirs skipped, no store.db skipped, probeRoots
Valid minimal store Single-turn parse, model resolution, costUSD > 0
Multi-turn blob graph 3-turn nextBlobId chain, childBlobIds branching, stable dedup keys, seenKeys respected
Malformed metadata Bad hex, invalid JSON, missing agentId, short blobId, agentId mismatch, missing meta table, missing blobs table
Missing/invalid blobs Absent root blob, broken nextBlobId (partial graph still yields), corrupt blob JSON
Unknown fields Ignored without crash, content-block array format, toolCalls array format
Timestamp normalization Epoch-ms, epoch-s promoted to ms, session-level fallback
Token provenance Exact both, input estimated, output estimated, gauge rejected, cache tokens, all absent
Store + transcript dedup One set of calls per session UUID, transcript fallback on corrupt store, store-only session appears
WAL cache Re-parse on file change, cache hit on unchanged fingerprint
blobEncryptionKey redaction Absent from emitted calls, absent from stderr, absent from source paths

Test results

Tests  44 passed  <- new (cursor-agent-store.test.ts)
Tests  17 passed  <- existing cursor-agent.test.ts, no regressions

…chats/**/store.db

Fixes getagentseal#986.

## What changed

- **Discovery**: discoverSessions() now scans ~/.cursor/chats/<hash>/<uuid>/store.db
  in addition to the existing ~/.cursor/projects/**/agent-transcripts/ transcript
  walk. probeRoots() gains a chats entry so codeburn doctor can report it.

- **Schema validation**: each store.db is checked for the required meta and �lobs
  tables before any reads are attempted. A missing or incompatible schema emits a
  warning and falls through to the transcript fallback.

- **Metadata decoding**: meta['0'] is hex-decoded from UTF-8 JSON to extract
  �gentId, latestRootBlobId,
ame, createdAt, and lastUsedModel.
  �lobEncryptionKey is explicitly excluded from the decoded object and must
  never appear in logs, caches, exports, snapshots, or committed fixtures.

- **Blob-graph reconstruction**: starting at latestRootBlobId, the parser
  walks
extBlobId chains and childBlobIds arrays in BFS order, classifying
  blobs by 
ole (user / assistant / model / request). Unknown fields are
  silently ignored for forward compatibility.

- **Token provenance**: explicit per-request inputTokens/outputTokens fields
  are used when present and plausible (≤ 2 000 000 — values above that are
  treated as context-window gauges and discarded). Missing components are
  estimated from character counts. costIsEstimated is set on calls where any
  count was estimated.

- **Timestamps**: internal request/blob timestamps take precedence; the session
  createdAt is used as a session-level fallback. File mtime is never reported
  as an exact request timestamp.

- **WAL-aware cache**: the in-memory per-session cache is keyed on a fingerprint
  combining the store.db mtime and the -wal sidecar size/mtime, so active
  sessions (written via WAL) always trigger a re-parse.

- **Source precedence + dedup**: store.db sources are emitted first. A
  successfully decoded store records a sentinel in seenKeys; transcript parsers
  for the same session UUID check the sentinel and skip themselves. A store that
  fails schema/metadata validation does not set the sentinel, so the transcript
  fallback runs normally.

## Tests (44 new, 0 regressions)

Covers all items from the issue test matrix: empty chats dir, valid minimal
store, multi-turn blob graph, malformed hex metadata, missing/invalid root blob,
unknown fields, seconds vs milliseconds timestamps, exact/partial/gauge/absent
token data, store+transcript dedup, transcript fallback on corrupt store,
WAL-aware cache invalidation, blobEncryptionKey redaction.

@ozymandiashh ozymandiashh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the work on #986. Requesting changes; as it stands the feature does not run in the CLI, and CI would fail at the first step. Details, all verified on the branch and on a merge with current main (which is clean):

  1. Store sources are discovered but never parsed. appendStoreSources emits path: "cursor-agent-store:<dbPath>:<uuid>". src/parser.ts:3333 fingerprints every source first, and fingerprintFile (src/session-cache.ts:1456) only strips virtual suffixes, so it stats the literal cursor-agent-store:/... string, gets null, and parser.ts:3348 skips the source silently. createSessionParser is never reached for any store.db. The 44 new tests pass only because they call createSessionParser().parse() directly. Fix is the existing convention: real path first, synthetic data as a suffix (<dbPath>#cursor-store=<uuid>, like cursor.ts does with #cursor-ws=). That also gets -wal folding for free and makes the hand-rolled fingerprintStore / storeCache unnecessary.

  2. npx tsc --noEmit has 6 new errors in src/providers/cursor-agent.ts:835-863: let hashDirs: Awaited<ReturnType<typeof readdir>> resolves the Buffer overload. Annotate Dirent[] or let inference from the withFileTypes: true call site work. tests.yml runs tsc before vitest.

  3. tests/provider-probe-roots.test.ts fails on the branch (passes on main) because the new chats probe root was not added to the existing assertion.

  4. Precedence only holds within one pass. The cursor-agent-store-decoded:<uuid> sentinel is set when the store parser runs, but parser.ts only parses changed sources. In the #986 scenario (transcript export lands after the store), the store is unchanged, not re-parsed, no sentinel, and the transcript's calls are added on top of the cached store turns. Key namespaces are disjoint so cache dedup cannot catch it. Precedence has to be decided at discovery time.

  5. Signs this was not run against a real store: decodeStoreMeta decodes lastUsedModel, name, mode and none is used; the model fallback is source.project, i.e. the session UUID, which then flows into calculateCost; the timestamp fallback is new Date(), which CONTRIBUTING forbids for parsers; fetchBlob assumes JSON where the issue describes protobuf; token fields are tried across four aliases each. The PR template's real-data section (npm run dev -- today, models --provider cursor-agent, terminal output against a real ~/.cursor/chats/**/store.db) is missing entirely. CONTRIBUTING is explicit that guessing storage schemas is not acceptable; please install Cursor, generate a session, and paste what the CLI shows.

Credit where due: reusing src/sqlite.ts means read-only open, busy_timeout and SQLITE_BUSY handling are correct, the blobEncryptionKey redaction is real, and there is no path traversal or injection. The skeleton is fine; it needs to be wired in and proven on real data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cursor Agent provider misses CLI sessions stored in ~/.cursor/chats/**/store.db

2 participants