Cursor 对话导入不能只寻找一个聊天文本文件。当前 ChatCrystal 适配器从工作区取得会话线索,再从全局数据库读取消息。核对这两个位置是否属于同一份用户数据,比仅看“导入完成”更有意义。
本文基于 ChatCrystal 提交 7642768 的 Cursor 适配器。这是应用对已知存储结构的处理方式,不是 Cursor 承诺稳定的公开导出格式。
定位同一个 User 目录
适配器使用的默认基础目录如下:
| 平台 | 基础目录 |
|---|---|
| Windows | %APPDATA%\Cursor\User |
| macOS | ~/Library/Application Support/Cursor/User |
| Linux | ~/.config/Cursor/User |
CURSOR_DATA_DIR 可以覆盖基础目录,值应指向 User 层级,不是某个工作区哈希目录。与其他本机来源相同,变量由应用服务在启动时读取;在已有服务的客户端导入命令前设置它,不会改变那个服务的配置。
User/
├── workspaceStorage/<workspace>/
│ ├── workspace.json
│ └── state.vscdb
└── globalStorage/
└── state.vscdb
不要混用不同机器或不同用户的两个数据库。移动过目录时,也要保留工作区说明文件与它对应的数据库。
工作区提供哪些线索
扫描工作区需要同时存在 workspace.json 和 state.vscdb。适配器从前者读取 folder,剥离 file:/// 前缀并 URL 解码,再从后者的 ItemTable 读取 composer.composerData 中的 allComposers。这个简单转换会把 file:///tmp/project 变为 tmp/project,不保证保留 POSIX 绝对路径;项目显示名与可直接使用的文件系统路径要分别核对。
这个列表提供 composerId 等会话信息。项目显示名来自解析后的文件夹路径末段,而不是哈希目录本身。打不开或不能解析的工作区会被跳过,所以工作区列表不应当作完整历史的证明。
原始结构中的 Composer 名称也不能直接等同于导入后的标题:当前 parse() 返回的 slug 是 null,并没有使用每个 Composer 的 name 填充它。
全局数据库提供消息
全局 state.vscdb 的 cursorDiskKV 表包含形如 bubbleId:<composerId>:<bubbleId> 的 key。适配器按会话 ID 查找对应消息,再从 JSON value 提取文本和辅助字段。
下面是说明字段的构造示例,不是一次真实导入的原始记录:
{
"_v": 3,
"type": 1,
"text": "为什么这个请求会重复执行?",
"createdAt": "2026-10-01T10:00:00Z"
}
当前代码将 type === 1 视为用户,其余值走助手分支,并跳过没有正文的助手 Bubble。思考文本另存,工具与代码字段用于设置标志;它不保证完整保存所有工具输出或编辑器状态。
_v 大于 3 时,代码会发出版本警告后继续按已知字段解析。这不证明新格式已经完整兼容;应检查实际消息内容,不能把警告统一当作无害信息。
孤立会话仍有发现边界
适配器还寻找全局库中不在已知工作区 Composer 列表里的会话。这样可以发现部分工作区已移除、消息仍留下的历史,但它不是无遗漏恢复工具。
当前候选检查只查看每个会话最多 20 个 Bubble,确认其中是否含有非空文本;这个 SQL 查询没有排序。候选检查未看到文本、全局库无法打开或 key 形状变化,都可能影响发现结果。发现候选之后,实际解析再读取该会话匹配的全部 Bubble。
数据库读取工具把主数据库文件读入 sql.js 内存实例,失败时短暂等待后重试一次。它没有把读取失败、文件损坏和格式不支持精确分类,也没有在这里重放 SQLite WAL;读取不到记录不能直接证明 Cursor 已删除历史。
用一个真实会话验收
在默认本机连接下执行:
crystal import
crystal --json conversations --source cursor --limit 20
第二条命令返回包含 items 的对象。选择自己知道内容的真实会话,核对 ID、项目与消息数,再通过应用详情或 GET /api/conversations/<id> 查看实际消息。CLI 的导入来源选项有本机分支限制,详见 Claude Code 导入,不要用它承诺只扫描 Cursor。
至少验证三种情况:正常工作区、只有全局消息的历史、已知包含格式异常或空助手消息的文件。关注实际保留的内容,不用未经测量的“几十条需要几秒”判断成功。
更新历史后重新核对
导入服务用文件元数据和解析内容哈希决定跳过或替换。Cursor 当前扫描中,已知工作区会话的 fileMtime 优先取 Composer 创建时间,fileSize 取全局库大小。因此,不能保证每次内容变化都会仅凭元数据检查被识别,尤其是全局文件大小未变时。
测试一个已导入会话的更新,确认新的消息确实进入应用;否则保留会话 ID、文件状态和对应版本作为排查证据。笔记还需要经过摘要与向量处理,导入记录的变化不自动证明旧笔记仍然正确。首次配置与单条笔记的验收见入门流程。