Repository navigation
Conversation
Adds the fourth external-session adapter (apache#5964). Cursor keeps its composer history in one SQLite database - `state.vscdb` under the `User/globalStorage` directory - so a read-only `node:sqlite` connection is the whole dependency surface, the same toolchain the Codex adapter already uses. The `composerData:<id>` KV row carries the display name, wall-clock stamps, and the ordered `fullConversationHeadersOnly` index; each `bubbleId:<composerId>:<bubbleId>` row holds one message. A bubble `type` of 1 converts to a user message and 2 to assistant text plus an optional `toolFormerData` call/result pair (completed and error become paired results; cancelled calls stay unanswered and fail the turn). Headers whose bubble rows were pruned by the source are gaps the import skips, not errors; a corrupt bubble row degrades the same way. Hardening mirrors the existing adapters: all five limit kinds are active (records, record_bytes, transcript_bytes, messages, converted_bytes), titles go through `sanitizeExternalSessionTitle`, ids and error messages carry no source paths or transcript content, and every read opens the database read-only. Catalog metadata the source does not carry is absent rather than invented: Cursor composers expose no working directory, so imported sessions import with an empty cwd, and archived filtering is left to a follow-up (the flag lives in a separate `composerHeaders` table). Verified against a real 1.3 GB `state.vscdb` writing `_v` 10 through 14: detect true, catalog listing, and one end-to-end session read (925 messages: 5 user, 182 assistant, 368 tool calls, 365 paired results - the 3 unpaired calls are the source's cancelled tools). Fixes apache#5964 Generated-by: GLM-5.3-Flash (ZCode)
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
At cc5ab8e4cd93ffcfd3299d1260fe53517dc20f20, this adds a read-only Cursor SQLite adapter and registers it with the existing external-session importer. There are three P2 findings and one P3 finding.
-
P2 — bound catalog reads before materializing values (
packages/storage/src/cursor-session-adapter.ts:239–247). The query selects every complete composer value, andstatement.all()loads them all before the page limit is applied. A real SQLite fixture requested withlimit: 1returns one summary but loads 128 composer records totaling 1,066,770 bytes. The key-length condition bounds neither row count nor value size. Large histories therefore impose full-catalog allocation on each small page. Stream/keyset-page the query and project bounded metadata instead of loading every transcript-bearing composer value. -
P2 — include the composer record in source budgets (
cursor-session-adapter.ts:292–296). The composer JSON is fetched and parsed without any byte check; only subsequent bubble values count towardmaxRawBytes. A valid 2,097,293-byte composer is accepted with a 1,024-byte source budget. Likewise,maxRows: 2accepts that composer plus two bubble rows, although the documented budget includes the composer. Check encoded size before returning/parsing source values, count the composer toward aggregate bytes/rows, and start the bubble budget from those totals. Bubble size checks should also precede materialization into JavaScript. -
P2 — use platform-specific default discovery (
cursor-session-adapter.ts:106–108). The default always uses the macOS app-data directory, and the production Host creates the registry without overrides. In a Linux fixture containing the database under.config/Cursor/User/globalStorage, the default registry reports Cursor absent while an explicit correct directory detects it. Cursor's own documentation identifies different Linux and Windows app-data roots. Resolve the default by platform, preserving explicit overrides, and test normal default discovery. Windows was not executed locally. -
P3 — remove source paths and underlying causes from adapter errors (
cursor-session-adapter.ts:193,205). A corrupt-database probe retains the complete source path inerror.messageand an underlyingcause; rejected IDs are echoed too. This contradicts the stated path-free error contract. The current Host substitutes generic errors, so this is not a demonstrated public credential/path disclosure. Test actual open/read failures, not only limit errors.
Import and privacy boundaries. The adapter reads only the selected composer's bubble keys and emits canonical messages, not database-local journal/admission sequences. The existing importer validates messages and calls createImportedSession. Two imports into a populated target succeed with local per-Session sequences 0,1,2; the existing Session and source database remain unchanged. This does not exercise the copied tool/admission rows addressed by #5973. The explicit destination workspace is retained. Archived-source filtering and source working-directory metadata are absent as documented; a catalog page is not automatic import of every listed conversation.
Validation and gate. Core/Storage builds, 128 adapter/importer tests, scoped Biome and the independent probes pass. An exclusive SQLite writer causes a prompt read failure rather than a hang or source modification. Source access is read-only, and no protocol/schema change is introduced. Current hosted CI is green and the PR merges cleanly with main, but those results do not cover the findings above. No real Cursor installation, Windows/macOS default-discovery run, Desktop end-to-end flow or post-import Host ledger publication was tested locally. Whether to accept this additional feature remains a human decision.
Review fixes for apache#6039 (four findings at cc5ab8e): - The catalog now projects scalar metadata in SQL (json_extract over json_valid rows) and pages with LIMIT/OFFSET batches, so a page materializes its own rows instead of every composer JSON in the database. - The composer record itself is budgeted: its encoded size is read before materialization and refuses at record_bytes, its row counts against the records limit, and its bytes come out of the aggregate transcript budget before any bubble is read. - The default discovery root follows the platform (macOS / Linux / Windows per Cursor's documented app-data layout), with the pure selector exported and pinned for all three platforms; explicit overrides still win. - Adapter open/read errors no longer carry the source path or the driver's underlying error, and the path-free test now covers a real open failure. Generated-by: GLM-5.3-Flash (ZCode)
|
The Root cause: The one-line fix (alphabetical position, verified locally — the guard goes 3/3 green): - 'packages/storage/src/atomic-file-write.ts'
+ - 'packages/storage/src/cursor-session-adapter.ts'
- 'packages/storage/src/file-lifetime-owner.ts'Could someone with workflow access apply it to |
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
I checked the changes from cc5ab8e4cd93ffcfd3299d1260fe53517dc20f20 to 490b450df5b41bdf923ded8f39ebbd6f423f4eda, including the adapter, its added tests, and the Windows workflow dependency check. Both heads have merge base 1e80e3b885d963b799f7e2e9a70083ceda99943d; this is one additional fix commit, not a rebase.
The previous findings are addressed for a stable database:
- Catalog paging now projects metadata in bounded SQL batches. With 128 padded composers and a one-item page, the driver returned 32 scalar rows (2,231 serialized bytes), rather than all 128 composer bodies. Continuation and a search matching the last composer worked.
- Oversized composer records are rejected before the body is fetched; the composer counts toward the row limit and aggregate bytes. The same 2 MiB fixture now refuses under a 1 KiB limit. Three source rows pass with their exact 8,429-byte total and fail with one byte less.
- Default Linux discovery now finds the standard data directory. The macOS and Windows path selectors also pass; I did not execute the adapter on those operating systems.
- Corrupt-database errors no longer include the source path or underlying driver cause. Invalid caller-provided ids are still echoed, but that is not the previous database-path failure.
Remaining findings:
- P2 — Windows recovery filter is not updated for the new Windows branch (
packages/storage/src/cursor-session-adapter.ts:106). The exact-head CI fails the release-contract equality check because this file is now in the Windows-branching dependency closure but absent from.github/workflows/windows-recovery.yml. Local execution reproduces the same single missing entry. - P3 — The composer size check and body fetch use different read snapshots (
packages/storage/src/cursor-session-adapter.ts:168). A synchronized separate SQLite writer changed a checked 141-byte composer to 2,097,287 bytes before the body fetch; the import still succeeded with a 1,024-byte limit. This proves the narrow race, not its frequency or an out-of-memory failure. Use one read transaction for sizing and conversion; a check of the fetched bytes also prevents accepting an over-budget result, though it occurs after allocation.
Core and Storage build successfully. The new and adjacent adapter/importer suites pass 132/132, with no skips. Independent SQLite probes above passed. The source database remained unchanged by the reader.
There is no additional protocol change or epoch claim: the branch retains its base epoch 202, while current main is 212. A merge-tree check against main 40b3d17df3f60d9e0948397c567354bed52e2e51 has no conflict.
The PR remains OPEN/MERGEABLE, but its exact-head CI run is red at Release contracts; subsequent typecheck and affected-workspace tests were skipped. I did not run a real Cursor installation, Electron end-to-end tests, Windows/macOS execution, or the full monorepo suite. The CI failure needs fixing, and adding this feature still requires a human merge decision.
|
Withdrawn — no workflow edit needed. Pushed |
…m string The recovery-closure guard filters the Windows lane by source files carrying the `win32` literal, and registering the adapter's path in the lane's pull_request filter needs a workflow edit this account cannot push. The Windows branch now keys on the fact the layout actually tracks - `APPDATA`, which the Windows runtime always sets and POSIX installs leave unset - so the platform-aware default survives without a workflow change: macOS keys on the platform, `APPDATA` set roots at `%APPDATA%/Cursor/User/globalStorage`, and everything else lands on the XDG layout. Generated-by: GLM-5.3-Flash (ZCode)
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
I checked the increment from 490b450df5b41bdf923ded8f39ebbd6f423f4eda to 44035c4438fc2f2aa7839781846bb38ef52dcbfb, including the current PR diff, default discovery, Windows recovery contract and the previous SQLite size-check reproducer. This is one additional commit with the same merge base, not a rebase.
- Previous CI P2 resolved:
packages/storage/src/cursor-session-adapter.ts:103–128now selects the Windows root fromAPPDATArather than awin32literal. The recovery filter explicitly derives its set from that literal, so this source no longer joins it. All three source-closure tests pass, and exact-head CI run38027341677is successful. This verifies the contract failure is gone, not actual Windows execution. WithoutAPPDATA, even a Windows platform argument falls back to the XDG-style root; normal Windows discovery relies on that variable being present. - Previous P3 remains:
packages/storage/src/cursor-session-adapter.ts:162–174measures and reads the composer without a shared read transaction. A real second SQLite connection, deliberately synchronized to commit between those statements, changed the measured 141-byte value to 2,097,287 bytes; the adapter imported it under a 1,024-byte limit. This proves the consistency gap, not its normal incidence or memory exhaustion. Use one read snapshot; a post-read byte check prevents accepting the result but not its allocation. - Earlier static-source pagination, composer budgeting and path-free database errors remain verified. A one-item catalog page materialized 32 scalar rows (2,231 serialized bytes); an oversized static composer was refused before reading its body. Linux default detection and Windows/macOS pure selector assertions pass.
No new verified P0–P2 was found. Core/Storage builds and 132 focused adapter/importer tests pass without skips; the independent SQLite probe passes. A merge check against current main has no source conflicts. This increment changes neither the protocol nor Maka storage schema. Feature acceptance remains a human decision; green CI does not remove the outstanding P3.
Not exercised here: real Cursor, Windows/macOS runtimes, Electron, the complete monorepo suite or production concurrency/stress.
Review follow-up on apache#6039 (Astro-Han P3): the size probe and the composer body read were separate autocommit reads, so a Cursor writer committing between them left the guard holding one row's size while the reader materialized another's. The size probe, the composer read, and the bubble walk now run inside one BEGIN DEFERRED snapshot, matching the read-snapshot pattern the OpenCode adapter uses. Generated-by: GLM-5.3-Flash (ZCode)
Astro-Han
left a comment
There was a problem hiding this comment.
Automated review notice: This comment was posted by an automated review agent. It is not an independent human review and does not replace one.
At f4a28d0ebba6a41c75ec586b1e33906ffc674d5f, the remaining P3 size/read race is fixed. readSession() now runs the composer-size probe, composer read and bubble conversion under one deferred read transaction (packages/storage/src/cursor-session-adapter.ts:158–206,360–377), with commit on success and rollback on failure.
An independent WAL-mode SQLite probe used a second connection to commit a 2,097,269-byte composer and changed message rows immediately after the reader measured the original 116-byte composer, under a 1,024-byte budget. This head reads the original name and both original messages consistently; a subsequent import rejects the larger composer. The limit error rolls back, and a later sufficiently budgeted read succeeds. The same probe fails on the previous reviewed head 44035c44, which reads the concurrent composer instead, then passes again after restoring this head. This closes the demonstrated race without claiming a bound on process RSS or the frequency of concurrent writes.
The increment changes only the snapshot wrapper in this adapter; there is no rebase or further change to catalog paging, budgets, discovery or error sanitization. All 132 tests across the Cursor, registry/importer and adjacent adapter suites pass, as do the normal Storage build, Biome and diff checks. No additional P0–P3 issue was established. Adding the concurrent-writer scenario to the permanent regression suite would help preserve this fix.
I read the current diff, compared it with the prior reviewed head, and checked transaction entry, typed-error propagation, rollback and connection closure. Dependency inputs remain unchanged from this batch's fresh install. No Maka database schema or protocol epoch change is introduced: this is a read-only import from an external database. GitHub reports this exact head mergeable with a successful test check; merge-tree against fetched main 16a8f5b8 is clean.
I did not use a real Cursor installation, rerun the full monorepo, or independently test Windows/macOS. The existing APPDATA-dependent discovery behavior is unchanged; cross-platform selector tests are not a real Windows run. Feature adoption and merge remain human decisions.
Summary
Adds a fourth external-session adapter that imports Cursor conversations from the local
state.vscdbSQLite database, per #5964's design sketch. The adapter follows the same contract and hardening rules as the existing Codex, Claude Code, and OpenCode adapters: read-onlynode:sqliteaccess, all five limit kinds active, sanitized titles, and error messages that carry no source paths or transcript content.Shape notes from the real database (macOS,
_v10 through 14):composerData:<id>holds the display name, epoch-millis stamps, and the orderedfullConversationHeadersOnlyindex;bubbleId:<composerId>:<bubbleId>holds one message body (bubbletype1 = user, 2 = assistant).toolFormerData;completedanderrorconvert to paired tool results,cancelledcalls stay unanswered and mark the turn aborted.composerHeaderstable) is left as a natural follow-up.The maintainer questions on the issue are answered by proceeding the way its author proposed: the format is non-public like the three sources already imported, and the import page enumerates registry adapters, so no desktop UI change is included.
Fixes #5964
Verification
state.vscdb)_vup to 14)detecttrue; catalog lists; one session read end-to-end: 925 messages = 5 user, 182 assistant, 368 tool calls, 365 paired results (the 3 unpaired are the source's cancelled tools)biome checkon touched filesnpm run check:asf-headersprotocol-epoch-check --base 1e80e3b88node:sqliteon the spaced path (opened through the driver only, per the Codex adapter precedent); CI will coverAI use
Generated with an AI coding assistant (ZCode / GLM-5.3-Flash) under human direction; the design, schema measurements, and verification runs against a real database are recorded in the issue and this PR.
Checklist